diff options
Diffstat (limited to 'drivers/net/mipsnet.c')
| -rw-r--r-- | drivers/net/mipsnet.c | 372 | 
1 files changed, 372 insertions, 0 deletions
diff --git a/drivers/net/mipsnet.c b/drivers/net/mipsnet.c new file mode 100644 index 00000000000..bbffb585b3b --- /dev/null +++ b/drivers/net/mipsnet.c @@ -0,0 +1,372 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License.  See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#define DEBUG + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/sched.h> +#include <linux/etherdevice.h> +#include <linux/netdevice.h> +#include <linux/platform_device.h> +#include <asm/io.h> +#include <asm/mips-boards/simint.h> + +#include "mipsnet.h"		/* actual device IO mapping */ + +#define MIPSNET_VERSION "2005-06-20" + +#define mipsnet_reg_address(dev, field) (dev->base_addr + field_offset(field)) + +struct mipsnet_priv { +	struct net_device_stats stats; +}; + +static struct platform_device *mips_plat_dev; + +static char mipsnet_string[] = "mipsnet"; + +/* + * Copy data from the MIPSNET rx data port + */ +static int ioiocpy_frommipsnet(struct net_device *dev, unsigned char *kdata, +			int len) +{ +	uint32_t available_len = inl(mipsnet_reg_address(dev, rxDataCount)); +	if (available_len < len) +		return -EFAULT; + +	for (; len > 0; len--, kdata++) { +		*kdata = inb(mipsnet_reg_address(dev, rxDataBuffer)); +	} + +	return inl(mipsnet_reg_address(dev, rxDataCount)); +} + +static inline ssize_t mipsnet_put_todevice(struct net_device *dev, +	struct sk_buff *skb) +{ +	int count_to_go = skb->len; +	char *buf_ptr = skb->data; +	struct mipsnet_priv *mp = netdev_priv(dev); + +	pr_debug("%s: %s(): telling MIPSNET txDataCount(%d)\n", +	         dev->name, __FUNCTION__, skb->len); + +	outl(skb->len, mipsnet_reg_address(dev, txDataCount)); + +	pr_debug("%s: %s(): sending data to MIPSNET txDataBuffer(%d)\n", +	         dev->name, __FUNCTION__, skb->len); + +	for (; count_to_go; buf_ptr++, count_to_go--) { +		outb(*buf_ptr, mipsnet_reg_address(dev, txDataBuffer)); +	} + +	mp->stats.tx_packets++; +	mp->stats.tx_bytes += skb->len; + +	return skb->len; +} + +static int mipsnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ +	pr_debug("%s:%s(): transmitting %d bytes\n", +	         dev->name, __FUNCTION__, skb->len); + +	/* Only one packet at a time. Once TXDONE interrupt is serviced, the +	 * queue will be restarted. +	 */ +	netif_stop_queue(dev); +	mipsnet_put_todevice(dev, skb); + +	return 0; +} + +static inline ssize_t mipsnet_get_fromdev(struct net_device *dev, size_t count) +{ +	struct sk_buff *skb; +	size_t len = count; +	struct mipsnet_priv *mp = netdev_priv(dev); + +	if (!(skb = alloc_skb(len + 2, GFP_KERNEL))) { +		mp->stats.rx_dropped++; +		return -ENOMEM; +	} + +	skb_reserve(skb, 2); +	if (ioiocpy_frommipsnet(dev, skb_put(skb, len), len)) +		return -EFAULT; + +	skb->dev = dev; +	skb->protocol = eth_type_trans(skb, dev); +	skb->ip_summed = CHECKSUM_UNNECESSARY; + +	pr_debug("%s:%s(): pushing RXed data to kernel\n", +	         dev->name, __FUNCTION__); +	netif_rx(skb); + +	mp->stats.rx_packets++; +	mp->stats.rx_bytes += len; + +	return count; +} + +static irqreturn_t +mipsnet_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ +	struct net_device *dev = dev_id; + +	irqreturn_t retval = IRQ_NONE; +	uint64_t interruptFlags; + +	if (irq == dev->irq) { +		pr_debug("%s:%s(): irq %d for device\n", +		         dev->name, __FUNCTION__, irq); + +		retval = IRQ_HANDLED; + +		interruptFlags = +		    inl(mipsnet_reg_address(dev, interruptControl)); +		pr_debug("%s:%s(): intCtl=0x%016llx\n", dev->name, +		         __FUNCTION__, interruptFlags); + +		if (interruptFlags & MIPSNET_INTCTL_TXDONE) { +			pr_debug("%s:%s(): got TXDone\n", +			         dev->name, __FUNCTION__); +			outl(MIPSNET_INTCTL_TXDONE, +			     mipsnet_reg_address(dev, interruptControl)); +			// only one packet at a time, we are done. +			netif_wake_queue(dev); +		} else if (interruptFlags & MIPSNET_INTCTL_RXDONE) { +			pr_debug("%s:%s(): got RX data\n", +			         dev->name, __FUNCTION__); +			mipsnet_get_fromdev(dev, +			            inl(mipsnet_reg_address(dev, rxDataCount))); +			pr_debug("%s:%s(): clearing RX int\n", +			         dev->name, __FUNCTION__); +			outl(MIPSNET_INTCTL_RXDONE, +			     mipsnet_reg_address(dev, interruptControl)); + +		} else if (interruptFlags & MIPSNET_INTCTL_TESTBIT) { +			pr_debug("%s:%s(): got test interrupt\n", +			         dev->name, __FUNCTION__); +			// TESTBIT is cleared on read. +			//    And takes effect after a write with 0 +			outl(0, mipsnet_reg_address(dev, interruptControl)); +		} else { +			pr_debug("%s:%s(): no valid fags 0x%016llx\n", +			         dev->name, __FUNCTION__, interruptFlags); +			// Maybe shared IRQ, just ignore, no clearing. +			retval = IRQ_NONE; +		} + +	} else { +		printk(KERN_INFO "%s: %s(): irq %d for unknown device\n", +		       dev->name, __FUNCTION__, irq); +		retval = IRQ_NONE; +	} +	return retval; +}				//mipsnet_interrupt() + +static int mipsnet_open(struct net_device *dev) +{ +	int err; +	pr_debug("%s: mipsnet_open\n", dev->name); + +	err = request_irq(dev->irq, &mipsnet_interrupt, +			  SA_SHIRQ, dev->name, (void *) dev); + +	if (err) { +		pr_debug("%s: %s(): can't get irq %d\n", +		         dev->name, __FUNCTION__, dev->irq); +		release_region(dev->base_addr, MIPSNET_IO_EXTENT); +		return err; +	} + +	pr_debug("%s: %s(): got IO region at 0x%04lx and irq %d for dev.\n", +	         dev->name, __FUNCTION__, dev->base_addr, dev->irq); + + +	netif_start_queue(dev); + +	// test interrupt handler +	outl(MIPSNET_INTCTL_TESTBIT, +	     mipsnet_reg_address(dev, interruptControl)); + + +	return 0; +} + +static int mipsnet_close(struct net_device *dev) +{ +	pr_debug("%s: %s()\n", dev->name, __FUNCTION__); +	netif_stop_queue(dev); +	return 0; +} + +static struct net_device_stats *mipsnet_get_stats(struct net_device *dev) +{ +	struct mipsnet_priv *mp = netdev_priv(dev); + +	return &mp->stats; +} + +static void mipsnet_set_mclist(struct net_device *dev) +{ +	// we don't do anything +	return; +} + +static int __init mipsnet_probe(struct device *dev) +{ +	struct net_device *netdev; +	int err; + +	netdev = alloc_etherdev(sizeof(struct mipsnet_priv)); +	if (!netdev) { +		err = -ENOMEM; +		goto out; +	} + +	dev_set_drvdata(dev, netdev); + +	netdev->open			= mipsnet_open; +	netdev->stop			= mipsnet_close; +	netdev->hard_start_xmit		= mipsnet_xmit; +	netdev->get_stats		= mipsnet_get_stats; +	netdev->set_multicast_list	= mipsnet_set_mclist; + +	/* +	 * TODO: probe for these or load them from PARAM +	 */ +	netdev->base_addr = 0x4200; +	netdev->irq = MIPSCPU_INT_BASE + MIPSCPU_INT_MB0 + +	              inl(mipsnet_reg_address(netdev, interruptInfo)); + +	// Get the io region now, get irq on open() +	if (!request_region(netdev->base_addr, MIPSNET_IO_EXTENT, "mipsnet")) { +		pr_debug("%s: %s(): IO region {start: 0x%04lux, len: %d} " +		         "for dev is not availble.\n", netdev->name, +		         __FUNCTION__, netdev->base_addr, MIPSNET_IO_EXTENT); +		err = -EBUSY; +		goto out_free_netdev; +	} + +	/* +	 * Lacking any better mechanism to allocate a MAC address we use a +	 * random one ... +	 */ +	random_ether_addr(netdev->dev_addr); + +	err = register_netdev(netdev); +	if (err) { +		printk(KERN_ERR "MIPSNet: failed to register netdev.\n"); +		goto out_free_region; +	} + +	return 0; + +out_free_region: +	release_region(netdev->base_addr, MIPSNET_IO_EXTENT); + +out_free_netdev: +	free_netdev(netdev); + +out: +	return err; +} + +static int __devexit mipsnet_device_remove(struct device *device) +{ +	struct net_device *dev = dev_get_drvdata(device); + +	unregister_netdev(dev); +	release_region(dev->base_addr, MIPSNET_IO_EXTENT); +	free_netdev(dev); +	dev_set_drvdata(device, NULL); + +	return 0; +} + +static struct device_driver mipsnet_driver = { +	.name	= mipsnet_string, +	.bus	= &platform_bus_type, +	.probe	= mipsnet_probe, +	.remove	= __devexit_p(mipsnet_device_remove), +}; + +static void mipsnet_platform_release(struct device *device) +{ +	struct platform_device *pldev; + +	/* free device */ +	pldev = to_platform_device(device); +	kfree(pldev); +} + +static int __init mipsnet_init_module(void) +{ +	struct platform_device *pldev; +	int err; + +	printk(KERN_INFO "MIPSNet Ethernet driver. Version: %s. " +	       "(c)2005 MIPS Technologies, Inc.\n", MIPSNET_VERSION); + +	if (driver_register(&mipsnet_driver)) { +		printk(KERN_ERR "Driver registration failed\n"); +		err = -ENODEV; +		goto out; +	} + +        if (!(pldev = kmalloc (sizeof (*pldev), GFP_KERNEL))) { +		err = -ENOMEM; +		goto out_unregister_driver; +	} + +	memset (pldev, 0, sizeof (*pldev)); +	pldev->name		= mipsnet_string; +	pldev->id		= 0; +	pldev->dev.release	= mipsnet_platform_release; + +	if (platform_device_register(pldev)) { +		err = -ENODEV; +		goto out_free_pldev; +	} + +        if (!pldev->dev.driver) { +		/* +		 * The driver was not bound to this device, there was +                 * no hardware at this address. Unregister it, as the +		 * release fuction will take care of freeing the +		 * allocated structure +		 */ +		platform_device_unregister (pldev); +	} + +	mips_plat_dev		= pldev; + +	return 0; + +out_free_pldev: +	kfree(pldev); + +out_unregister_driver: +	driver_unregister(&mipsnet_driver); +out: +	return err; +} + +static void __exit mipsnet_exit_module(void) +{ +	pr_debug("MIPSNet Ethernet driver exiting\n"); + +	driver_unregister(&mipsnet_driver); +} + +module_init(mipsnet_init_module); +module_exit(mipsnet_exit_module);  | 
