| /* SiS190.c: A Linux PCI Ethernet driver for the SiS190 chips. */ |
| /* |
| ========================================================================= |
| SiS190.c: A SiS190 Gigabit Ethernet driver for Linux kernel 2.6.x. |
| -------------------------------------------------------------------- |
| |
| drivers/net/SiS190.c |
| |
| Maintained by K.M. Liu <kmliu@sis.com> |
| |
| Modified from the driver which is originally written by Donald Becker. |
| |
| This software may be used and distributed according to the terms of |
| the GNU General Public License (GPL), incorporated herein by reference. |
| Drivers based on or derived from this code fall under the GPL and must |
| retain the authorship, copyright and license notice. This file is not |
| a complete program and may only be used when the entire operating |
| system is licensed under the GPL. |
| |
| History: |
| ========================================================================= |
| VERSION 1.0 <2003/8/7> K.M. Liu, Test 100bps Full in 2.6.0 O.K. |
| 1.1 <2003/8/8> K.M. Liu, Add mode detection. |
| |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/pci.h> |
| #include <linux/netdevice.h> |
| #include <linux/etherdevice.h> |
| #include <linux/delay.h> |
| #include <linux/crc32.h> |
| #include <linux/init.h> |
| |
| #include <asm/io.h> |
| |
| #define SiS190_VERSION "1.1" |
| #define MODULENAME "SiS190" |
| #define SiS190_DRIVER_NAME MODULENAME " Gigabit Ethernet driver " SiS190_VERSION |
| #define PFX MODULENAME ": " |
| |
| #ifdef SiS190_DEBUG |
| #define assert(expr) \ |
| if(unlikely(!(expr))) { \ |
| printk( "Assertion failed! %s,%s,%s,line=%d\n", \ |
| #expr,__FILE__,__FUNCTION__,__LINE__); \ |
| } |
| #else |
| #define assert(expr) do {} while (0) |
| #endif |
| |
| /* media options */ |
| #define MAX_UNITS 8 |
| |
| /* Maximum events (Rx packets, etc.) to handle at each interrupt. */ |
| static int max_interrupt_work = 20; |
| |
| /* Maximum number of multicast addresses to filter (vs. Rx-all-multicast). |
| The chips use a 64 element hash table based on the Ethernet CRC. */ |
| static int multicast_filter_limit = 32; |
| |
| /* MAC address length*/ |
| #define MAC_ADDR_LEN 6 |
| |
| /* max supported gigabit ethernet frame size -- must be at least (dev->mtu+14+4).*/ |
| #define MAX_ETH_FRAME_SIZE 1536 |
| |
| #define TX_FIFO_THRESH 256 /* In bytes */ |
| |
| #define RX_FIFO_THRESH 7 /* 7 means NO threshold, Rx buffer level before first PCI xfer. */ |
| #define RX_DMA_BURST 6 /* Maximum PCI burst, '6' is 1024 */ |
| #define TX_DMA_BURST 6 /* Maximum PCI burst, '6' is 1024 */ |
| #define EarlyTxThld 0x3F /* 0x3F means NO early transmit */ |
| #define RxPacketMaxSize 0x0800 /* Maximum size supported is 16K-1 */ |
| #define InterFrameGap 0x03 /* 3 means InterFrameGap = the shortest one */ |
| |
| #define NUM_TX_DESC 64 /* Number of Tx descriptor registers */ |
| #define NUM_RX_DESC 64 /* Number of Rx descriptor registers */ |
| #define TX_DESC_TOTAL_SIZE (NUM_TX_DESC * sizeof (struct TxDesc)) |
| #define RX_DESC_TOTAL_SIZE (NUM_RX_DESC * sizeof (struct RxDesc)) |
| #define RX_BUF_SIZE 1536 /* Rx Buffer size */ |
| |
| #define SiS190_MIN_IO_SIZE 0x80 |
| #define TX_TIMEOUT (6*HZ) |
| |
| /* enhanced PHY access register bit definitions */ |
| #define EhnMIIread 0x0000 |
| #define EhnMIIwrite 0x0020 |
| #define EhnMIIdataShift 16 |
| #define EhnMIIpmdShift 6 /* 7016 only */ |
| #define EhnMIIregShift 11 |
| #define EhnMIIreq 0x0010 |
| #define EhnMIInotDone 0x0010 |
| |
| //------------------------------------------------------------------------- |
| // Bit Mask definitions |
| //------------------------------------------------------------------------- |
| #define BIT_0 0x0001 |
| #define BIT_1 0x0002 |
| #define BIT_2 0x0004 |
| #define BIT_3 0x0008 |
| #define BIT_4 0x0010 |
| #define BIT_5 0x0020 |
| #define BIT_6 0x0040 |
| #define BIT_7 0x0080 |
| #define BIT_8 0x0100 |
| #define BIT_9 0x0200 |
| #define BIT_10 0x0400 |
| #define BIT_11 0x0800 |
| #define BIT_12 0x1000 |
| #define BIT_13 0x2000 |
| #define BIT_14 0x4000 |
| #define BIT_15 0x8000 |
| #define BIT_16 0x10000 |
| #define BIT_17 0x20000 |
| #define BIT_18 0x40000 |
| #define BIT_19 0x80000 |
| #define BIT_20 0x100000 |
| #define BIT_21 0x200000 |
| #define BIT_22 0x400000 |
| #define BIT_23 0x800000 |
| #define BIT_24 0x1000000 |
| #define BIT_25 0x2000000 |
| #define BIT_26 0x04000000 |
| #define BIT_27 0x08000000 |
| #define BIT_28 0x10000000 |
| #define BIT_29 0x20000000 |
| #define BIT_30 0x40000000 |
| #define BIT_31 0x80000000 |
| |
| /* write/read MMIO register */ |
| #define SiS_W8(reg, val8) writeb ((val8), ioaddr + (reg)) |
| #define SiS_W16(reg, val16) writew ((val16), ioaddr + (reg)) |
| #define SiS_W32(reg, val32) writel ((val32), ioaddr + (reg)) |
| #define SiS_R8(reg) readb (ioaddr + (reg)) |
| #define SiS_R16(reg) readw (ioaddr + (reg)) |
| #define SiS_R32(reg) ((unsigned long) readl (ioaddr + (reg))) |
| |
| static struct { |
| const char *name; |
| } board_info[] __devinitdata = { |
| { "SiS190 Gigabit Ethernet" }, |
| }; |
| |
| static struct pci_device_id sis190_pci_tbl[] __devinitdata = { |
| { 0x1039, 0x0190, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, |
| { 0,}, |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, sis190_pci_tbl); |
| |
| enum SiS190_registers { |
| TxControl = 0x0, |
| TxDescStartAddr = 0x4, |
| TxNextDescAddr = 0x0c, |
| RxControl = 0x10, |
| RxDescStartAddr = 0x14, |
| RxNextDescAddr = 0x1c, |
| IntrStatus = 0x20, |
| IntrMask = 0x24, |
| IntrControl = 0x28, |
| IntrTimer = 0x2c, |
| PMControl = 0x30, |
| ROMControl = 0x38, |
| ROMInterface = 0x3c, |
| StationControl = 0x40, |
| GMIIControl = 0x44, |
| TxMacControl = 0x50, |
| RxMacControl = 0x60, |
| RxMacAddr = 0x62, |
| RxHashTable = 0x68, |
| RxWakeOnLan = 0x70, |
| RxMPSControl = 0x78, |
| }; |
| |
| enum sis190_register_content { |
| /*InterruptStatusBits */ |
| |
| SoftInt = 0x40000000, |
| Timeup = 0x20000000, |
| PauseFrame = 0x80000, |
| MagicPacket = 0x40000, |
| WakeupFrame = 0x20000, |
| LinkChange = 0x10000, |
| RxQEmpty = 0x80, |
| RxQInt = 0x40, |
| TxQ1Empty = 0x20, |
| TxQ1Int = 0x10, |
| TxQ0Empty = 0x08, |
| TxQ0Int = 0x04, |
| RxHalt = 0x02, |
| TxHalt = 0x01, |
| |
| /*RxStatusDesc */ |
| RxRES = 0x00200000, |
| RxCRC = 0x00080000, |
| RxRUNT = 0x00100000, |
| RxRWT = 0x00400000, |
| |
| /*ChipCmdBits */ |
| CmdReset = 0x10, |
| CmdRxEnb = 0x08, |
| CmdTxEnb = 0x01, |
| RxBufEmpty = 0x01, |
| |
| /*Cfg9346Bits */ |
| Cfg9346_Lock = 0x00, |
| Cfg9346_Unlock = 0xC0, |
| |
| /*rx_mode_bits */ |
| AcceptErr = 0x20, |
| AcceptRunt = 0x10, |
| AcceptBroadcast = 0x0800, |
| AcceptMulticast = 0x0400, |
| AcceptMyPhys = 0x0200, |
| AcceptAllPhys = 0x0100, |
| |
| /*RxConfigBits */ |
| RxCfgFIFOShift = 13, |
| RxCfgDMAShift = 8, |
| |
| /*TxConfigBits */ |
| TxInterFrameGapShift = 24, |
| TxDMAShift = 8, /* DMA burst value (0-7) is shift this many bits */ |
| |
| /*_PHYstatus */ |
| TBI_Enable = 0x80, |
| TxFlowCtrl = 0x40, |
| RxFlowCtrl = 0x20, |
| |
| _1000bpsF = 0x1c, |
| _1000bpsH = 0x0c, |
| _100bpsF = 0x18, |
| _100bpsH = 0x08, |
| _10bpsF = 0x14, |
| _10bpsH = 0x04, |
| |
| LinkStatus = 0x02, |
| FullDup = 0x01, |
| |
| /*GIGABIT_PHY_registers */ |
| PHY_CTRL_REG = 0, |
| PHY_STAT_REG = 1, |
| PHY_AUTO_NEGO_REG = 4, |
| PHY_1000_CTRL_REG = 9, |
| |
| /*GIGABIT_PHY_REG_BIT */ |
| PHY_Restart_Auto_Nego = 0x0200, |
| PHY_Enable_Auto_Nego = 0x1000, |
| |
| //PHY_STAT_REG = 1; |
| PHY_Auto_Neco_Comp = 0x0020, |
| |
| //PHY_AUTO_NEGO_REG = 4; |
| PHY_Cap_10_Half = 0x0020, |
| PHY_Cap_10_Full = 0x0040, |
| PHY_Cap_100_Half = 0x0080, |
| PHY_Cap_100_Full = 0x0100, |
| |
| //PHY_1000_CTRL_REG = 9; |
| PHY_Cap_1000_Full = 0x0200, |
| |
| PHY_Cap_Null = 0x0, |
| |
| /*_MediaType*/ |
| _10_Half = 0x01, |
| _10_Full = 0x02, |
| _100_Half = 0x04, |
| _100_Full = 0x08, |
| _1000_Full = 0x10, |
| |
| /*_TBICSRBit*/ |
| TBILinkOK = 0x02000000, |
| }; |
| |
| const static struct { |
| const char *name; |
| u8 version; /* depend on docs */ |
| u32 RxConfigMask; /* should clear the bits supported by this chip */ |
| } sis_chip_info[] = { |
| { "SiS-0190", 0x00, 0xff7e1880,}, |
| }; |
| |
| enum _DescStatusBit { |
| OWNbit = 0x80000000, |
| INTbit = 0x40000000, |
| DEFbit = 0x200000, |
| CRCbit = 0x20000, |
| PADbit = 0x10000, |
| ENDbit = 0x80000000, |
| }; |
| |
| struct TxDesc { |
| u32 PSize; |
| u32 status; |
| u32 buf_addr; |
| u32 buf_Len; |
| }; |
| |
| struct RxDesc { |
| u32 PSize; |
| u32 status; |
| u32 buf_addr; |
| u32 buf_Len; |
| }; |
| |
| struct sis190_private { |
| void *mmio_addr; /* memory map physical address */ |
| struct pci_dev *pci_dev; /* Index of PCI device */ |
| struct net_device_stats stats; /* statistics of net device */ |
| spinlock_t lock; /* spin lock flag */ |
| int chipset; |
| unsigned long cur_rx; /* Index into the Rx descriptor buffer of next Rx pkt. */ |
| unsigned long cur_tx; /* Index into the Tx descriptor buffer of next Rx pkt. */ |
| unsigned long dirty_tx; |
| dma_addr_t tx_dma; |
| dma_addr_t rx_dma; |
| struct TxDesc *TxDescArray; /* Index of 256-alignment Tx Descriptor buffer */ |
| struct RxDesc *RxDescArray; /* Index of 256-alignment Rx Descriptor buffer */ |
| unsigned char *RxBufferRings; /* Index of Rx Buffer */ |
| unsigned char *RxBufferRing[NUM_RX_DESC]; /* Index of Rx Buffer array */ |
| struct sk_buff *Tx_skbuff[NUM_TX_DESC]; /* Index of Transmit data buffer */ |
| }; |
| |
| MODULE_AUTHOR("K.M. Liu <kmliu@sis.com>"); |
| MODULE_DESCRIPTION("SiS SiS190 Gigabit Ethernet driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_PARM(media, "1-" __MODULE_STRING(MAX_UNITS) "i"); |
| |
| static int SiS190_open(struct net_device *dev); |
| static int SiS190_start_xmit(struct sk_buff *skb, struct net_device *dev); |
| static irqreturn_t SiS190_interrupt(int irq, void *dev_instance, |
| struct pt_regs *regs); |
| static void SiS190_init_ring(struct net_device *dev); |
| static void SiS190_hw_start(struct net_device *dev); |
| static int SiS190_close(struct net_device *dev); |
| static void SiS190_set_rx_mode(struct net_device *dev); |
| static void SiS190_tx_timeout(struct net_device *dev); |
| static struct net_device_stats *SiS190_get_stats(struct net_device *netdev); |
| |
| static const u32 sis190_intr_mask = |
| RxQEmpty | RxQInt | TxQ1Empty | TxQ1Int | TxQ0Empty | TxQ0Int | RxHalt | |
| TxHalt; |
| |
| void |
| smdio_write(void *ioaddr, int RegAddr, int value) |
| { |
| |
| u32 l; |
| u16 i; |
| u32 pmd; |
| |
| pmd = 1; |
| |
| l = 0; |
| l = EhnMIIwrite | (((u32) RegAddr) << EhnMIIregShift) | EhnMIIreq | |
| (((u32) value) << EhnMIIdataShift) | (((u32) pmd) << |
| EhnMIIpmdShift); |
| |
| SiS_W32(GMIIControl, l); |
| |
| udelay(1000); |
| |
| for (i = 0; i < 1000; i++) { |
| if (SiS_R32(GMIIControl) & EhnMIInotDone) { |
| udelay(100); |
| } else { |
| break; |
| } |
| } |
| |
| if (i > 999) |
| printk(KERN_ERR PFX "Phy write Error!!!\n"); |
| |
| } |
| |
| int |
| smdio_read(void *ioaddr, int RegAddr) |
| { |
| |
| u32 l; |
| u16 i; |
| u32 pmd; |
| |
| pmd = 1; |
| l = 0; |
| l = EhnMIIread | EhnMIIreq | (((u32) RegAddr) << EhnMIIregShift) | |
| (((u32) pmd) << EhnMIIpmdShift); |
| |
| SiS_W32(GMIIControl, l); |
| |
| udelay(1000); |
| |
| for (i = 0; i < 1000; i++) { |
| if ((l == SiS_R32(GMIIControl)) & EhnMIInotDone) { |
| udelay(100); |
| } else { |
| break; |
| } |
| |
| if (i > 999) |
| printk(KERN_ERR PFX "Phy Read Error!!!\n"); |
| } |
| l = SiS_R32(GMIIControl); |
| |
| return ((u16) (l >> EhnMIIdataShift)); |
| |
| } |
| |
| int |
| ReadEEprom(void *ioaddr, u32 RegAddr) |
| { |
| u16 data; |
| u32 i; |
| u32 ulValue; |
| |
| if (!(SiS_R32(ROMControl) & BIT_1)) { |
| return 0; |
| } |
| |
| ulValue = (BIT_7 | (0x2 << 8) | (RegAddr << 10)); |
| |
| SiS_W32(ROMInterface, ulValue); |
| |
| for (i = 0; i < 200; i++) { |
| |
| if (!(SiS_R32(ROMInterface) & BIT_7)) |
| break; |
| |
| udelay(1000); |
| } |
| |
| data = (u16) ((SiS_R32(ROMInterface) & 0xffff0000) >> 16); |
| |
| return data; |
| } |
| |
| static int __devinit |
| SiS190_init_board(struct pci_dev *pdev, struct net_device **dev_out, |
| void **ioaddr_out) |
| { |
| void *ioaddr = NULL; |
| struct net_device *dev; |
| struct sis190_private *tp; |
| u16 rc; |
| unsigned long mmio_start, mmio_end, mmio_flags, mmio_len; |
| |
| assert(pdev != NULL); |
| assert(ioaddr_out != NULL); |
| |
| *ioaddr_out = NULL; |
| *dev_out = NULL; |
| |
| dev = alloc_etherdev(sizeof (*tp)); |
| if (dev == NULL) { |
| printk(KERN_ERR PFX "unable to alloc new ethernet\n"); |
| return -ENOMEM; |
| } |
| |
| SET_MODULE_OWNER(dev); |
| SET_NETDEV_DEV(dev, &pdev->dev); |
| tp = dev->priv; |
| |
| // enable device (incl. PCI PM wakeup and hotplug setup) |
| rc = pci_enable_device(pdev); |
| if (rc) |
| goto err_out; |
| |
| rc = pci_set_dma_mask(pdev, 0xffffffffULL); |
| if (rc) |
| goto err_out_disable; |
| |
| mmio_start = pci_resource_start(pdev, 0); |
| mmio_end = pci_resource_end(pdev, 0); |
| mmio_flags = pci_resource_flags(pdev, 0); |
| mmio_len = pci_resource_len(pdev, 0); |
| |
| // make sure PCI base addr 0 is MMIO |
| if (!(mmio_flags & IORESOURCE_MEM)) { |
| printk(KERN_ERR PFX |
| "region #0 not an MMIO resource, aborting\n"); |
| rc = -ENODEV; |
| goto err_out_disable; |
| } |
| // check for weird/broken PCI region reporting |
| if (mmio_len < SiS190_MIN_IO_SIZE) { |
| printk(KERN_ERR PFX "Invalid PCI region size(s), aborting\n"); |
| rc = -ENODEV; |
| goto err_out_disable; |
| } |
| |
| rc = pci_request_regions(pdev, dev->name); |
| if (rc) |
| goto err_out_disable; |
| |
| // enable PCI bus-mastering |
| pci_set_master(pdev); |
| |
| // ioremap MMIO region |
| ioaddr = ioremap(mmio_start, mmio_len); |
| if (ioaddr == NULL) { |
| printk(KERN_ERR PFX "cannot remap MMIO, aborting\n"); |
| rc = -EIO; |
| goto err_out_free_res; |
| } |
| // Soft reset the chip. |
| SiS_W32(IntrControl, 0x8000); |
| udelay(1000); |
| SiS_W32(IntrControl, 0x0); |
| |
| SiS_W32(TxControl, 0x1a00); |
| SiS_W32(RxControl, 0x1a00); |
| udelay(1000); |
| |
| *ioaddr_out = ioaddr; |
| *dev_out = dev; |
| return 0; |
| |
| err_out_free_res: |
| pci_release_regions(pdev); |
| |
| err_out_disable: |
| pci_disable_device(pdev); |
| err_out: |
| free_netdev(dev); |
| return rc; |
| } |
| |
| static int __devinit |
| SiS190_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) |
| { |
| struct net_device *dev = NULL; |
| struct sis190_private *tp = NULL; |
| void *ioaddr = NULL; |
| static int board_idx = -1; |
| static int printed_version = 0; |
| int i, rc; |
| u16 reg31; |
| int val; |
| |
| assert(pdev != NULL); |
| assert(ent != NULL); |
| |
| board_idx++; |
| |
| if (!printed_version) { |
| printk(KERN_INFO SiS190_DRIVER_NAME " loaded\n"); |
| printed_version = 1; |
| } |
| |
| i = SiS190_init_board(pdev, &dev, &ioaddr); |
| if (i < 0) { |
| return i; |
| } |
| |
| tp = dev->priv; |
| assert(ioaddr != NULL); |
| assert(dev != NULL); |
| assert(tp != NULL); |
| |
| // Get MAC address // |
| // Read node address from the EEPROM |
| |
| if (SiS_R32(ROMControl) & 0x2) { |
| |
| for (i = 0; i < 6; i += 2) { |
| SiS_W16(RxMacAddr + i, ReadEEprom(ioaddr, 3 + (i / 2))); |
| } |
| |
| } else { |
| |
| SiS_W32(RxMacAddr, 0x11111100); //If 9346 does not exist |
| SiS_W32(RxMacAddr + 2, 0x00111111); |
| } |
| |
| for (i = 0; i < MAC_ADDR_LEN; i++) { |
| dev->dev_addr[i] = SiS_R8(RxMacAddr + i); |
| printk("SiS_R8(RxMacAddr+%x)= %x ", i, SiS_R8(RxMacAddr + i)); |
| } |
| |
| dev->open = SiS190_open; |
| dev->hard_start_xmit = SiS190_start_xmit; |
| dev->get_stats = SiS190_get_stats; |
| dev->stop = SiS190_close; |
| dev->tx_timeout = SiS190_tx_timeout; |
| dev->set_multicast_list = SiS190_set_rx_mode; |
| dev->watchdog_timeo = TX_TIMEOUT; |
| dev->irq = pdev->irq; |
| dev->base_addr = (unsigned long) ioaddr; |
| // dev->do_ioctl = mii_ioctl; |
| |
| tp = dev->priv; // private data // |
| tp->pci_dev = pdev; |
| tp->mmio_addr = ioaddr; |
| |
| printk(KERN_DEBUG "%s: Identified chip type is '%s'.\n", dev->name, |
| sis_chip_info[tp->chipset].name); |
| |
| spin_lock_init(&tp->lock); |
| rc = register_netdev(dev); |
| if (rc) { |
| iounmap(ioaddr); |
| pci_release_regions(pdev); |
| pci_disable_device(pdev); |
| free_netdev(dev); |
| return rc; |
| } |
| |
| printk(KERN_DEBUG "%s: Identified chip type is '%s'.\n", dev->name, |
| sis_chip_info[tp->chipset].name); |
| |
| pci_set_drvdata(pdev, dev); |
| |
| printk(KERN_INFO "%s: %s at 0x%lx, " |
| "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x, " |
| "IRQ %d\n", |
| dev->name, |
| board_info[ent->driver_data].name, |
| dev->base_addr, |
| dev->dev_addr[0], dev->dev_addr[1], |
| dev->dev_addr[2], dev->dev_addr[3], |
| dev->dev_addr[4], dev->dev_addr[5], dev->irq); |
| |
| val = smdio_read(ioaddr, PHY_AUTO_NEGO_REG); |
| |
| printk(KERN_INFO "%s: Auto-negotiation Enabled.\n", dev->name); |
| |
| // enable 10/100 Full/Half Mode, leave PHY_AUTO_NEGO_REG bit4:0 unchanged |
| smdio_write(ioaddr, PHY_AUTO_NEGO_REG, |
| PHY_Cap_10_Half | PHY_Cap_10_Full | |
| PHY_Cap_100_Half | PHY_Cap_100_Full | (val & 0x1F)); |
| |
| // enable 1000 Full Mode |
| smdio_write(ioaddr, PHY_1000_CTRL_REG, PHY_Cap_1000_Full); |
| |
| // Enable auto-negotiation and restart auto-nigotiation |
| smdio_write(ioaddr, PHY_CTRL_REG, |
| PHY_Enable_Auto_Nego | PHY_Restart_Auto_Nego); |
| udelay(100); |
| |
| // wait for auto-negotiation process |
| for (i = 10000; i > 0; i--) { |
| //check if auto-negotiation complete |
| if (smdio_read(ioaddr, PHY_STAT_REG) & PHY_Auto_Neco_Comp) { |
| udelay(100); |
| reg31 = smdio_read(ioaddr, 31); |
| reg31 &= 0x1c; //bit 4:2 |
| switch (reg31) { |
| case _1000bpsF: |
| SiS_W16(0x40, 0x1c01); |
| printk |
| ("SiS190 Link on 1000 bps Full Duplex mode. \n"); |
| break; |
| case _1000bpsH: |
| SiS_W16(0x40, 0x0c01); |
| printk |
| ("SiS190 Link on 1000 bps Half Duplex mode. \n"); |
| break; |
| case _100bpsF: |
| SiS_W16(0x40, 0x1801); |
| printk |
| ("SiS190 Link on 100 bps Full Duplex mode. \n"); |
| break; |
| case _100bpsH: |
| SiS_W16(0x40, 0x0801); |
| printk |
| ("SiS190 Link on 100 bps Half Duplex mode. \n"); |
| break; |
| case _10bpsF: |
| SiS_W16(0x40, 0x1401); |
| printk |
| ("SiS190 Link on 10 bps Full Duplex mode. \n"); |
| break; |
| case _10bpsH: |
| SiS_W16(0x40, 0x0401); |
| printk |
| ("SiS190 Link on 10 bps Half Duplex mode. \n"); |
| break; |
| default: |
| printk(KERN_ERR PFX |
| "Error! SiS190 Can not detect mode !!! \n"); |
| break; |
| } |
| |
| break; |
| } else { |
| udelay(100); |
| } |
| } // end for-loop to wait for auto-negotiation process |
| return 0; |
| } |
| |
| static void __devexit |
| SiS190_remove_one(struct pci_dev *pdev) |
| { |
| struct net_device *dev = pci_get_drvdata(pdev); |
| struct sis190_private *tp = (struct sis190_private *) (dev->priv); |
| |
| assert(dev != NULL); |
| assert(tp != NULL); |
| |
| unregister_netdev(dev); |
| iounmap(tp->mmio_addr); |
| pci_release_regions(pdev); |
| |
| free_netdev(dev); |
| pci_set_drvdata(pdev, NULL); |
| } |
| |
| static int |
| SiS190_open(struct net_device *dev) |
| { |
| struct sis190_private *tp = dev->priv; |
| int rc; |
| |
| rc = request_irq(dev->irq, SiS190_interrupt, SA_SHIRQ, dev->name, dev); |
| if (rc) |
| goto out; |
| |
| /* |
| * Rx and Tx descriptors need 256 bytes alignment. |
| * pci_alloc_consistent() guarantees a stronger alignment. |
| */ |
| tp->TxDescArray = pci_alloc_consistent(tp->pci_dev, TX_DESC_TOTAL_SIZE, |
| &tp->tx_dma); |
| if (!tp->TxDescArray) { |
| rc = -ENOMEM; |
| goto err_out; |
| } |
| |
| tp->RxDescArray = pci_alloc_consistent(tp->pci_dev, RX_DESC_TOTAL_SIZE, |
| &tp->rx_dma); |
| if (!tp->RxDescArray) { |
| rc = -ENOMEM; |
| goto err_out_free_tx; |
| } |
| |
| tp->RxBufferRings = kmalloc(RX_BUF_SIZE * NUM_RX_DESC, GFP_KERNEL); |
| if (tp->RxBufferRings == NULL) { |
| printk(KERN_INFO "%s: allocate RxBufferRing failed\n", |
| dev->name); |
| rc = -ENOMEM; |
| goto err_out_free_rx; |
| } |
| |
| SiS190_init_ring(dev); |
| SiS190_hw_start(dev); |
| |
| out: |
| return rc; |
| |
| err_out_free_rx: |
| pci_free_consistent(tp->pci_dev, RX_DESC_TOTAL_SIZE, tp->RxDescArray, |
| tp->rx_dma); |
| err_out_free_tx: |
| pci_free_consistent(tp->pci_dev, TX_DESC_TOTAL_SIZE, tp->TxDescArray, |
| tp->tx_dma); |
| err_out: |
| free_irq(dev->irq, dev); |
| return rc; |
| } |
| |
| static void |
| SiS190_hw_start(struct net_device *dev) |
| { |
| struct sis190_private *tp = dev->priv; |
| void *ioaddr = tp->mmio_addr; |
| |
| /* Soft reset the chip. */ |
| |
| SiS_W32(IntrControl, 0x8000); |
| udelay(1000); |
| SiS_W32(IntrControl, 0x0); |
| |
| SiS_W32(0x0, 0x01a00); |
| SiS_W32(0x4, tp->tx_dma); |
| |
| SiS_W32(0x10, 0x1a00); |
| SiS_W32(0x14, tp->rx_dma); |
| |
| SiS_W32(0x20, 0xffffffff); |
| SiS_W32(0x24, 0x0); |
| SiS_W16(0x40, 0x1901); //default is 100Mbps |
| SiS_W32(0x44, 0x0); |
| SiS_W32(0x50, 0x60); |
| SiS_W16(0x60, 0x02); |
| SiS_W32(0x68, 0x0); |
| SiS_W32(0x6c, 0x0); |
| SiS_W32(0x70, 0x0); |
| SiS_W32(0x74, 0x0); |
| |
| // Set Rx Config register |
| |
| tp->cur_rx = 0; |
| |
| udelay(10); |
| |
| SiS190_set_rx_mode(dev); |
| |
| /* Enable all known interrupts by setting the interrupt mask. */ |
| SiS_W32(IntrMask, sis190_intr_mask); |
| |
| SiS_W32(0x0, 0x1a01); |
| SiS_W32(0x10, 0x1a1d); |
| |
| netif_start_queue(dev); |
| |
| } |
| |
| static void |
| SiS190_init_ring(struct net_device *dev) |
| { |
| struct sis190_private *tp = dev->priv; |
| int i; |
| |
| tp->cur_rx = 0; |
| tp->cur_tx = 0; |
| tp->dirty_tx = 0; |
| memset(tp->TxDescArray, 0x0, NUM_TX_DESC * sizeof (struct TxDesc)); |
| memset(tp->RxDescArray, 0x0, NUM_RX_DESC * sizeof (struct RxDesc)); |
| |
| for (i = 0; i < NUM_TX_DESC; i++) { |
| tp->Tx_skbuff[i] = NULL; |
| } |
| for (i = 0; i < NUM_RX_DESC; i++) { |
| struct RxDesc *desc = tp->RxDescArray + i; |
| |
| desc->PSize = 0x0; |
| |
| if (i == (NUM_RX_DESC - 1)) |
| desc->buf_Len = BIT_31 + RX_BUF_SIZE; //bit 31 is End bit |
| else |
| desc->buf_Len = RX_BUF_SIZE; |
| |
| tp->RxBufferRing[i] = tp->RxBufferRings + i * RX_BUF_SIZE; |
| desc->buf_addr = pci_map_single(tp->pci_dev, |
| tp->RxBufferRing[i], RX_BUF_SIZE, PCI_DMA_FROMDEVICE); |
| desc->status = OWNbit | INTbit; |
| } |
| |
| } |
| |
| static void |
| SiS190_tx_clear(struct sis190_private *tp) |
| { |
| int i; |
| |
| tp->cur_tx = 0; |
| for (i = 0; i < NUM_TX_DESC; i++) { |
| if (tp->Tx_skbuff[i] != NULL) { |
| struct sk_buff *skb; |
| |
| skb = tp->Tx_skbuff[i]; |
| pci_unmap_single(tp->pci_dev, |
| le32_to_cpu(tp->TxDescArray[i].buf_addr), |
| skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len, |
| PCI_DMA_TODEVICE); |
| dev_kfree_skb(skb); |
| tp->Tx_skbuff[i] = NULL; |
| tp->stats.tx_dropped++; |
| } |
| } |
| } |
| |
| static void |
| SiS190_tx_timeout(struct net_device *dev) |
| { |
| struct sis190_private *tp = dev->priv; |
| void *ioaddr = tp->mmio_addr; |
| u8 tmp8; |
| |
| /* disable Tx, if not already */ |
| tmp8 = SiS_R8(TxControl); |
| if (tmp8 & CmdTxEnb) |
| SiS_W8(TxControl, tmp8 & ~CmdTxEnb); |
| |
| /* Disable interrupts by clearing the interrupt mask. */ |
| SiS_W32(IntrMask, 0x0000); |
| |
| /* Stop a shared interrupt from scavenging while we are. */ |
| spin_lock_irq(&tp->lock); |
| SiS190_tx_clear(tp); |
| spin_unlock_irq(&tp->lock); |
| |
| /* ...and finally, reset everything */ |
| SiS190_hw_start(dev); |
| |
| netif_wake_queue(dev); |
| } |
| |
| static int |
| SiS190_start_xmit(struct sk_buff *skb, struct net_device *dev) |
| { |
| struct sis190_private *tp = dev->priv; |
| void *ioaddr = tp->mmio_addr; |
| int entry = tp->cur_tx % NUM_TX_DESC; |
| u32 len; |
| |
| if (unlikely(skb->len < ETH_ZLEN)) { |
| skb = skb_padto(skb, ETH_ZLEN); |
| if (skb == NULL) |
| goto drop_tx; |
| len = ETH_ZLEN; |
| } else { |
| len = skb->len; |
| } |
| |
| spin_lock_irq(&tp->lock); |
| |
| if ((le32_to_cpu(tp->TxDescArray[entry].status) & OWNbit) == 0) { |
| dma_addr_t mapping; |
| |
| mapping = pci_map_single(tp->pci_dev, skb->data, len, |
| PCI_DMA_TODEVICE); |
| |
| tp->Tx_skbuff[entry] = skb; |
| tp->TxDescArray[entry].buf_addr = cpu_to_le32(mapping); |
| tp->TxDescArray[entry].PSize = cpu_to_le32(len); |
| |
| if (entry != (NUM_TX_DESC - 1)) |
| tp->TxDescArray[entry].buf_Len = cpu_to_le32(len); |
| else |
| tp->TxDescArray[entry].buf_Len = |
| cpu_to_le32(len | ENDbit); |
| |
| tp->TxDescArray[entry].status |= |
| cpu_to_le32(OWNbit | INTbit | DEFbit | CRCbit | PADbit); |
| |
| SiS_W32(TxControl, 0x1a11); //Start Send |
| |
| dev->trans_start = jiffies; |
| |
| tp->cur_tx++; |
| } else { |
| spin_unlock_irq(&tp->lock); |
| goto drop_tx; |
| } |
| |
| if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx) |
| netif_stop_queue(dev); |
| |
| spin_unlock_irq(&tp->lock); |
| |
| return 0; |
| |
| drop_tx: |
| tp->stats.tx_dropped++; |
| if (skb) |
| dev_kfree_skb(skb); |
| return 0; |
| } |
| |
| static void |
| SiS190_tx_interrupt(struct net_device *dev, struct sis190_private *tp, |
| void *ioaddr) |
| { |
| unsigned long dirty_tx, tx_left; |
| |
| assert(dev != NULL); |
| assert(tp != NULL); |
| assert(ioaddr != NULL); |
| |
| dirty_tx = tp->dirty_tx; |
| tx_left = tp->cur_tx - dirty_tx; |
| |
| while (tx_left > 0) { |
| int entry = dirty_tx % NUM_TX_DESC; |
| |
| if ((le32_to_cpu(tp->TxDescArray[entry].status) & OWNbit) == 0) { |
| struct sk_buff *skb; |
| |
| skb = tp->Tx_skbuff[entry]; |
| |
| pci_unmap_single(tp->pci_dev, |
| le32_to_cpu(tp->TxDescArray[entry].buf_addr), |
| skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len, |
| PCI_DMA_TODEVICE); |
| |
| dev_kfree_skb_irq(skb); |
| tp->Tx_skbuff[entry] = NULL; |
| tp->stats.tx_packets++; |
| dirty_tx++; |
| tx_left--; |
| } |
| } |
| |
| if (tp->dirty_tx != dirty_tx) { |
| tp->dirty_tx = dirty_tx; |
| netif_wake_queue(dev); |
| } |
| } |
| |
| static void |
| SiS190_rx_interrupt(struct net_device *dev, struct sis190_private *tp, |
| void *ioaddr) |
| { |
| int cur_rx = tp->cur_rx; |
| struct RxDesc *desc = tp->RxDescArray + cur_rx; |
| |
| assert(dev != NULL); |
| assert(tp != NULL); |
| assert(ioaddr != NULL); |
| |
| while ((desc->status & OWNbit) == 0) { |
| |
| if (desc->PSize & 0x0080000) { |
| printk(KERN_INFO "%s: Rx ERROR!!!\n", dev->name); |
| tp->stats.rx_errors++; |
| tp->stats.rx_length_errors++; |
| } else if (!(desc->PSize & 0x0010000)) { |
| printk(KERN_INFO "%s: Rx ERROR!!!\n", dev->name); |
| tp->stats.rx_errors++; |
| tp->stats.rx_crc_errors++; |
| } else { |
| struct sk_buff *skb; |
| int pkt_size; |
| |
| pkt_size = (int) (desc->PSize & 0x0000FFFF) - 4; |
| skb = dev_alloc_skb(pkt_size + 2); |
| if (skb != NULL) { |
| skb->dev = dev; |
| skb_reserve(skb, 2); // 16 byte align the IP fields. // |
| pci_dma_sync_single_for_cpu(tp->pci_dev, |
| desc->buf_addr, |
| RX_BUF_SIZE, |
| PCI_DMA_FROMDEVICE); |
| eth_copy_and_sum(skb, tp->RxBufferRing[cur_rx], |
| pkt_size, 0); |
| pci_dma_sync_single_for_device(tp->pci_dev, |
| desc->buf_addr, |
| RX_BUF_SIZE, |
| PCI_DMA_FROMDEVICE); |
| skb_put(skb, pkt_size); |
| skb->protocol = eth_type_trans(skb, dev); |
| netif_rx(skb); |
| |
| desc->PSize = 0x0; |
| |
| if (cur_rx == (NUM_RX_DESC - 1)) |
| desc->buf_Len = ENDbit + RX_BUF_SIZE; |
| else |
| desc->buf_Len = RX_BUF_SIZE; |
| |
| dev->last_rx = jiffies; |
| tp->stats.rx_bytes += pkt_size; |
| tp->stats.rx_packets++; |
| |
| desc->status = OWNbit | INTbit; |
| } else { |
| printk(KERN_WARNING |
| "%s: Memory squeeze, deferring packet.\n", |
| dev->name); |
| /* We should check that some rx space is free. |
| If not, free one and mark stats->rx_dropped++. */ |
| tp->stats.rx_dropped++; |
| } |
| } |
| |
| cur_rx = (cur_rx + 1) % NUM_RX_DESC; |
| desc = tp->RxDescArray + cur_rx; |
| } |
| |
| tp->cur_rx = cur_rx; |
| } |
| |
| /* The interrupt handler does all of the Rx thread work and cleans up after the Tx thread. */ |
| static irqreturn_t |
| SiS190_interrupt(int irq, void *dev_instance, struct pt_regs *regs) |
| { |
| struct net_device *dev = (struct net_device *) dev_instance; |
| struct sis190_private *tp = dev->priv; |
| int boguscnt = max_interrupt_work; |
| void *ioaddr = tp->mmio_addr; |
| unsigned long status = 0; |
| int handled = 0; |
| |
| do { |
| status = SiS_R32(IntrStatus); |
| |
| /* h/w no longer present (hotplug?) or major error, bail */ |
| |
| SiS_W32(IntrStatus, status); |
| |
| if ((status & (TxQ0Int | RxQInt)) == 0) |
| break; |
| |
| // Rx interrupt |
| if (status & (RxQInt)) { |
| SiS190_rx_interrupt(dev, tp, ioaddr); |
| } |
| // Tx interrupt |
| if (status & (TxQ0Int)) { |
| spin_lock(&tp->lock); |
| SiS190_tx_interrupt(dev, tp, ioaddr); |
| spin_unlock(&tp->lock); |
| } |
| |
| boguscnt--; |
| } while (boguscnt > 0); |
| |
| if (boguscnt <= 0) { |
| printk(KERN_WARNING "%s: Too much work at interrupt!\n", |
| dev->name); |
| /* Clear all interrupt sources. */ |
| SiS_W32(IntrStatus, 0xffffffff); |
| } |
| |
| return IRQ_RETVAL(handled); |
| } |
| |
| static int |
| SiS190_close(struct net_device *dev) |
| { |
| struct sis190_private *tp = dev->priv; |
| void *ioaddr = tp->mmio_addr; |
| int i; |
| |
| netif_stop_queue(dev); |
| |
| spin_lock_irq(&tp->lock); |
| |
| /* Stop the chip's Tx and Rx DMA processes. */ |
| |
| SiS_W32(TxControl, 0x1a00); |
| SiS_W32(RxControl, 0x1a00); |
| |
| /* Disable interrupts by clearing the interrupt mask. */ |
| SiS_W32(IntrMask, 0x0000); |
| |
| /* Update the error counts. */ |
| //tp->stats.rx_missed_errors += _R32(RxMissed); |
| |
| spin_unlock_irq(&tp->lock); |
| |
| synchronize_irq(dev->irq); |
| free_irq(dev->irq, dev); |
| |
| SiS190_tx_clear(tp); |
| pci_free_consistent(tp->pci_dev, TX_DESC_TOTAL_SIZE, tp->TxDescArray, |
| tp->tx_dma); |
| pci_free_consistent(tp->pci_dev, RX_DESC_TOTAL_SIZE, tp->RxDescArray, |
| tp->rx_dma); |
| tp->TxDescArray = NULL; |
| for (i = 0; i < NUM_RX_DESC; i++) { |
| pci_unmap_single(tp->pci_dev, tp->RxDescArray[i].buf_addr, |
| RX_BUF_SIZE, PCI_DMA_FROMDEVICE); |
| tp->RxBufferRing[i] = NULL; |
| } |
| tp->RxDescArray = NULL; |
| kfree(tp->RxBufferRings); |
| |
| return 0; |
| } |
| |
| static void |
| SiS190_set_rx_mode(struct net_device *dev) |
| { |
| struct sis190_private *tp = dev->priv; |
| void *ioaddr = tp->mmio_addr; |
| unsigned long flags; |
| u32 mc_filter[2]; /* Multicast hash filter */ |
| int i, rx_mode; |
| u32 tmp = 0; |
| |
| if (dev->flags & IFF_PROMISC) { |
| /* Unconditionally log net taps. */ |
| printk(KERN_NOTICE "%s: Promiscuous mode enabled.\n", |
| dev->name); |
| rx_mode = |
| AcceptBroadcast | AcceptMulticast | AcceptMyPhys | |
| AcceptAllPhys; |
| mc_filter[1] = mc_filter[0] = 0xffffffff; |
| } else if ((dev->mc_count > multicast_filter_limit) |
| || (dev->flags & IFF_ALLMULTI)) { |
| /* Too many to filter perfectly -- accept all multicasts. */ |
| rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys; |
| mc_filter[1] = mc_filter[0] = 0xffffffff; |
| } else { |
| struct dev_mc_list *mclist; |
| rx_mode = AcceptBroadcast | AcceptMyPhys; |
| mc_filter[1] = mc_filter[0] = 0; |
| for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; |
| i++, mclist = mclist->next) { |
| int bit_nr = |
| ether_crc(ETH_ALEN, mclist->dmi_addr) >> 26; |
| mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31); |
| rx_mode |= AcceptMulticast; |
| } |
| } |
| |
| spin_lock_irqsave(&tp->lock, flags); |
| |
| tmp = rx_mode | 0x2; |
| |
| SiS_W16(RxMacControl, tmp); |
| SiS_W32(RxHashTable, mc_filter[0]); |
| SiS_W32(RxHashTable + 4, mc_filter[1]); |
| |
| spin_unlock_irqrestore(&tp->lock, flags); |
| } |
| |
| struct net_device_stats * |
| SiS190_get_stats(struct net_device *dev) |
| { |
| struct sis190_private *tp = dev->priv; |
| return &tp->stats; |
| } |
| |
| static struct pci_driver sis190_pci_driver = { |
| .name = MODULENAME, |
| .id_table = sis190_pci_tbl, |
| .probe = SiS190_init_one, |
| .remove = SiS190_remove_one, |
| }; |
| |
| static int __init |
| SiS190_init_module(void) |
| { |
| return pci_module_init(&sis190_pci_driver); |
| } |
| |
| static void __exit |
| SiS190_cleanup_module(void) |
| { |
| pci_unregister_driver(&sis190_pci_driver); |
| } |
| |
| module_init(SiS190_init_module); |
| module_exit(SiS190_cleanup_module); |