📄 at91_ether.c
字号:
/* * Ethernet driver for the Atmel AT91RM9200 (Thunder) * * (c) SAN People (Pty) Ltd * * Based on an earlier Atmel EMAC macrocell driver by Atmel and Lineo Inc. * Initial version by Rick Bronson 01/11/2003 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */#include <linux/module.h>#include <linux/init.h>#include <linux/config.h>#include <linux/mii.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/skbuff.h>#include <asm/io.h>#include <linux/pci.h>#include <linux/crc32.h>#include <asm/uaccess.h>#include <linux/ethtool.h>#include <asm/arch/AT91RM9200_EMAC.h>#include <asm/arch/pio.h>#include "at91_ether.h"static struct net_device at91_dev;/* ........................... PHY INTERFACE ........................... *//* * Enable the MDIO bit in MAC control register * When not called from an interrupt-handler, access to the PHY must be * protected by a spinlock. */static void enable_mdi(AT91PS_EMAC regs){ regs->EMAC_CTL |= AT91C_EMAC_MPE; /* enable management port */}/* * Disable the MDIO bit in the MAC control register */static void disable_mdi(AT91PS_EMAC regs){ regs->EMAC_CTL &= ~AT91C_EMAC_MPE; /* disable management port */}/* * Write value to the a PHY register * Note: MDI interface is assumed to already have been enabled. */static void write_phy(AT91PS_EMAC regs, unsigned char address, unsigned int value){ regs->EMAC_MAN = (AT91C_EMAC_HIGH | AT91C_EMAC_CODE_802_3 | AT91C_EMAC_RW_W | (address << 18)) + (value & 0xffff); /* Wait until IDLE bit in Network Status register is cleared */ // TODO: Enforce some maximum loop-count? while (!(regs->EMAC_SR & AT91C_EMAC_IDLE)) { barrier(); }}/* * Read value stored in a PHY register. * Note: MDI interface is assumed to already have been enabled. */static void read_phy(AT91PS_EMAC regs, unsigned char address, unsigned int *value){ regs->EMAC_MAN = AT91C_EMAC_HIGH | AT91C_EMAC_CODE_802_3 | AT91C_EMAC_RW_R | (address << 18); /* Wait until IDLE bit in Network Status register is cleared */ // TODO: Enforce some maximum loop-count? while (!(regs->EMAC_SR & AT91C_EMAC_IDLE)) { barrier(); } *value = (regs->EMAC_MAN & 0x0000ffff);}/* ........................... PHY MANAGEMENT .......................... *//* * Access the PHY to determine the current Link speed and Mode, and update the * MAC accordingly. * If no link or auto-negotiation is busy, then no changes are made. * Returns: 0 : OK * -1 : No link * -2 : AutoNegotiation still in progress */static int update_linkspeed(struct net_device *dev, AT91PS_EMAC regs) { unsigned int bmsr, bmcr, lpa, mac_cfg; unsigned int speed, duplex; /* Link status is latched, so read twice to get current value */ read_phy(regs, MII_BMSR, &bmsr); read_phy(regs, MII_BMSR, &bmsr); if (!(bmsr & BMSR_LSTATUS)) return -1; /* no link */ read_phy(regs, MII_BMCR, &bmcr); if (bmcr & BMCR_ANENABLE) { /* AutoNegotiation is enabled */ if (!(bmsr & BMSR_ANEGCOMPLETE)) return -2; /* auto-negotitation in progress */ read_phy(regs, MII_LPA, &lpa); if ((lpa & LPA_100FULL) || (lpa & LPA_100HALF)) speed = SPEED_100; else speed = SPEED_10; if ((lpa & LPA_100FULL) || (lpa & LPA_10FULL)) duplex = DUPLEX_FULL; else duplex = DUPLEX_HALF; } else { speed = (bmcr & BMCR_SPEED100) ? SPEED_100 : SPEED_10; duplex = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF; } /* Update the MAC */ mac_cfg = regs->EMAC_CFG & ~(AT91C_EMAC_SPD | AT91C_EMAC_FD); if (speed == SPEED_100) { if (duplex == DUPLEX_FULL) /* 100 Full Duplex */ regs->EMAC_CFG = mac_cfg | AT91C_EMAC_SPD | AT91C_EMAC_FD; else /* 100 Half Duplex */ regs->EMAC_CFG = mac_cfg | AT91C_EMAC_SPD; } else { if (duplex == DUPLEX_FULL) /* 10 Full Duplex */ regs->EMAC_CFG = mac_cfg | AT91C_EMAC_FD; else /* 10 Half Duplex */ regs->EMAC_CFG = mac_cfg; } printk(KERN_INFO "%s: Link now %i-%s\n", dev->name, speed, (duplex == DUPLEX_FULL) ? "FullDuplex" : "HalfDuplex"); return 0;}/* * Handle interrupts from the PHY */void at91ether_phy_interrupt(int irq, void *dev_id, struct pt_regs *regs){ struct net_device *dev = (struct net_device *) dev_id; AT91PS_EMAC emac = (AT91PS_EMAC) dev->base_addr; int status; unsigned int phy; enable_mdi(emac); read_phy(emac, MII_DSINTR_REG, &phy); /* acknowledge interrupt in PHY */ status = AT91_SYS->PIOC_ISR; /* acknowledge interrupt in PIO */ status = update_linkspeed(dev, emac); if (status == -1) { /* link is down */ netif_carrier_off(dev); printk(KERN_INFO "%s: Link down.\n", dev->name); } else if (status == -2) { /* auto-negotiation in progress */ /* Do nothing - another interrupt generated when negotiation complete */ } else { /* link is operational */ netif_carrier_on(dev); } disable_mdi(emac);}/* * Initialize and enable the PHY interrupt when link-state changes */void enable_phyirq(struct net_device *dev, AT91PS_EMAC regs){ struct at91_private *lp = (struct at91_private *) dev->priv; unsigned int dsintr, status; static int first_init = 0; if (first_init == 0) { // TODO: Check error code. Really need a generic PIO (interrupt) // layer since we're really only interested in the PC4 line. (void) request_irq(4, at91ether_phy_interrupt, 0, dev->name, dev); AT91_SYS->PIOC_ODR = AT91C_PIO_PC4; /* Configure as input */ first_init = 1; } else { status = AT91_SYS->PIOC_ISR; /* clear any pending PIO interrupts */ AT91_SYS->PIOC_IER = AT91C_PIO_PC4; /* Enable interrupt */ spin_lock_irq(&lp->lock); enable_mdi(regs); read_phy(regs, MII_DSINTR_REG, &dsintr); dsintr = dsintr & ~0xf00; /* clear bits 8..11 */ write_phy(regs, MII_DSINTR_REG, dsintr); disable_mdi(regs); spin_unlock_irq(&lp->lock); }}/* * Disable the PHY interrupt */void disable_phyirq(struct net_device *dev, AT91PS_EMAC regs){ struct at91_private *lp = (struct at91_private *) dev->priv; unsigned int dsintr; spin_lock_irq(&lp->lock); enable_mdi(regs); read_phy(regs, MII_DSINTR_REG, &dsintr); dsintr = dsintr | 0xf00; /* set bits 8..11 */ write_phy(regs, MII_DSINTR_REG, dsintr); disable_mdi(regs); spin_unlock_irq(&lp->lock); AT91_SYS->PIOC_IDR = AT91C_PIO_PC4; /* Disable interrupt */}/* ......................... ADDRESS MANAGEMENT ........................ *//* * Set the ethernet MAC address in dev->dev_addr */void get_mac_address(struct net_device *dev) { AT91PS_EMAC regs = (AT91PS_EMAC) dev->base_addr; char addr[6]; unsigned int hi, lo; /* Check if bootloader set address in Specific-Address 1 */ hi = regs->EMAC_SA1H; lo = regs->EMAC_SA1L; addr[0] = (lo & 0xff); addr[1] = (lo & 0xff00) >> 8; addr[2] = (lo & 0xff0000) >> 16; addr[3] = (lo & 0xff000000) >> 24; addr[4] = (hi & 0xff); addr[5] = (hi & 0xff00) >> 8; if (is_valid_ether_addr(addr)) { memcpy(dev->dev_addr, &addr, 6); return; } /* Check if bootloader set address in Specific-Address 2 */ hi = regs->EMAC_SA2H; lo = regs->EMAC_SA2L; addr[0] = (lo & 0xff); addr[1] = (lo & 0xff00) >> 8; addr[2] = (lo & 0xff0000) >> 16; addr[3] = (lo & 0xff000000) >> 24; addr[4] = (hi & 0xff); addr[5] = (hi & 0xff00) >> 8; if (is_valid_ether_addr(addr)) { memcpy(dev->dev_addr, &addr, 6); return; }}/* * Program the hardware MAC address from dev->dev_addr. */static void update_mac_address(struct net_device *dev){ AT91PS_EMAC regs = (AT91PS_EMAC) dev->base_addr; regs->EMAC_SA1L = (dev->dev_addr[3] << 24) | (dev->dev_addr[2] << 16) | (dev->dev_addr[1] << 8) | (dev->dev_addr[0]); regs->EMAC_SA1H = (dev->dev_addr[5] << 8) | (dev->dev_addr[4]);}#ifdef AT91_ETHER_ADDR_CONFIGURABLE/* * Store the new hardware address in dev->dev_addr, and update the MAC. */static int set_mac_address(struct net_device *dev, void* addr){ struct sockaddr *address = addr; if (!is_valid_ether_addr(address->sa_data)) return -EADDRNOTAVAIL; memcpy(dev->dev_addr, address->sa_data, dev->addr_len); update_mac_address(dev); printk("%s: Setting MAC address to %02x:%02x:%02x:%02x:%02x:%02x\n", dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); return 0;}#endif/* * Add multicast addresses to the internal multicast-hash table. */static void at91ether_sethashtable(struct net_device *dev, AT91PS_EMAC regs){ struct dev_mc_list *curr; unsigned char mc_filter[2]; unsigned int i, bitnr; mc_filter[0] = mc_filter[1] = 0; curr = dev->mc_list; for (i = 0; i < dev->mc_count; i++, curr = curr->next) { if (!curr) break; /* unexpected end of list */ bitnr = ether_crc(ETH_ALEN, curr->dmi_addr) >> 26; mc_filter[bitnr >> 5] |= 1 << (bitnr & 31); } regs->EMAC_HSH = mc_filter[1]; regs->EMAC_HSL = mc_filter[0];}/* * Enable/Disable promiscuous and multicast modes. */static void at91ether_set_rx_mode(struct net_device *dev){ AT91PS_EMAC regs = (AT91PS_EMAC) dev->base_addr; if (dev->flags & IFF_PROMISC) { /* Enable promiscuous mode */ regs->EMAC_CFG |= AT91C_EMAC_CAF; } else if (dev->flags & (~IFF_PROMISC)) { /* Disable promiscuous mode */ regs->EMAC_CFG &= ~AT91C_EMAC_CAF; } if (dev->flags & IFF_ALLMULTI) { /* Enable all multicast mode */ regs->EMAC_HSH = -1; regs->EMAC_HSL = -1; regs->EMAC_CFG |= AT91C_EMAC_MTI; } else if (dev->mc_count > 0) { /* Enable specific multicasts */ at91ether_sethashtable(dev, regs); regs->EMAC_CFG |= AT91C_EMAC_MTI; } else if (dev->flags & (~IFF_ALLMULTI)) { /* Disable all multicast mode */ regs->EMAC_HSH = 0; regs->EMAC_HSL = 0; regs->EMAC_CFG &= ~AT91C_EMAC_MTI; }}/* ............................... IOCTL ............................... */static int mdio_read(struct net_device *dev, int phy_id, int location){ AT91PS_EMAC regs = (AT91PS_EMAC) dev->base_addr; unsigned int value; read_phy(regs, location, &value); return value;}static void mdio_write(struct net_device *dev, int phy_id, int location, int value){ AT91PS_EMAC regs = (AT91PS_EMAC) dev->base_addr; write_phy(regs, location, value);}/* * ethtool support. */static int at91ether_ethtool_ioctl (struct net_device *dev, void *useraddr){ struct at91_private *lp = (struct at91_private *) dev->priv; AT91PS_EMAC regs = (AT91PS_EMAC) dev->base_addr; u32 ethcmd; int res = 0; if (copy_from_user (ðcmd, useraddr, sizeof (ethcmd))) return -EFAULT; spin_lock_irq(&lp->lock); enable_mdi(regs); switch (ethcmd) { case ETHTOOL_GSET: { struct ethtool_cmd ecmd = { ETHTOOL_GSET }; res = mii_ethtool_gset(&lp->mii, &ecmd); if (copy_to_user(useraddr, &ecmd, sizeof(ecmd))) res = -EFAULT; break; } case ETHTOOL_SSET: { struct ethtool_cmd ecmd; if (copy_from_user(&ecmd, useraddr, sizeof(ecmd))) res = -EFAULT; else res = mii_ethtool_sset(&lp->mii, &ecmd); break; } case ETHTOOL_NWAY_RST: { res = mii_nway_restart(&lp->mii); break; } case ETHTOOL_GLINK: { struct ethtool_value edata = { ETHTOOL_GLINK }; edata.data = mii_link_ok(&lp->mii); if (copy_to_user(useraddr, &edata, sizeof(edata))) res = -EFAULT; break; } default: res = -EOPNOTSUPP; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -