diff options
author | Andrew Victor <andrew@sanpeople.com> | 2006-03-24 11:50:17 +0200 |
---|---|---|
committer | Jeff Garzik <jeff@garzik.org> | 2006-03-24 12:10:55 -0500 |
commit | d4b7780ea1d2e08410fcc9963a57254147ae577a (patch) | |
tree | 3625bc3f6f0220b348793e9dc1e1a131899b49c9 /drivers | |
parent | e93252faca2eb1a14b44369705be7c79eba2f037 (diff) | |
download | blackbird-op-linux-d4b7780ea1d2e08410fcc9963a57254147ae577a.tar.gz blackbird-op-linux-d4b7780ea1d2e08410fcc9963a57254147ae577a.zip |
[PATCH] AT91RM9200 Ethernet driver
This patch adds support for the Ethernet controller integrated in the
Atmel AT91RM9200 SoC processor.
Changes since the previous submission (01/02/2006) are:
- Make use of the clk.h clock infrastructure.
- The multicast hash function is not crc32. [Patch by Pedro Perez]
Signed-off-by: Andrew Victor <andrew@sanpeople.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/arm/Kconfig | 8 | ||||
-rw-r--r-- | drivers/net/arm/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/arm/at91_ether.c | 1110 | ||||
-rw-r--r-- | drivers/net/arm/at91_ether.h | 101 |
4 files changed, 1220 insertions, 0 deletions
diff --git a/drivers/net/arm/Kconfig b/drivers/net/arm/Kconfig index 625184b65e38..77fe20dbea35 100644 --- a/drivers/net/arm/Kconfig +++ b/drivers/net/arm/Kconfig @@ -31,3 +31,11 @@ config ARM_ETHERH help If you have an Acorn system with one of these network cards, you should say Y to this option if you wish to use it with Linux. + +config ARM_AT91_ETHER + tristate "AT91RM9200 Ethernet support" + depends on NET_ETHERNET && ARM && ARCH_AT91RM9200 + select MII + help + If you wish to compile a kernel for the AT91RM9200 and enable + ethernet support, then you should always answer Y to this. diff --git a/drivers/net/arm/Makefile b/drivers/net/arm/Makefile index bc263edf06a7..42c95b79c261 100644 --- a/drivers/net/arm/Makefile +++ b/drivers/net/arm/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_ARM_AM79C961A) += am79c961a.o obj-$(CONFIG_ARM_ETHERH) += etherh.o obj-$(CONFIG_ARM_ETHER3) += ether3.o obj-$(CONFIG_ARM_ETHER1) += ether1.o +obj-$(CONFIG_ARM_AT91_ETHER) += at91_ether.o diff --git a/drivers/net/arm/at91_ether.c b/drivers/net/arm/at91_ether.c new file mode 100644 index 000000000000..5503dc8a66e4 --- /dev/null +++ b/drivers/net/arm/at91_ether.c @@ -0,0 +1,1110 @@ +/* + * Ethernet driver for the Atmel AT91RM9200 (Thunder) + * + * Copyright (C) 2003 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 + * + * Intel LXT971A PHY support by Christopher Bahns & David Knickerbocker + * (Polaroid Corporation) + * + * Realtek RTL8201(B)L PHY support by Roman Avramenko <roman@imsystems.ru> + * + * 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 <linux/dma-mapping.h> +#include <linux/ethtool.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/mach-types.h> + +#include <asm/arch/at91rm9200_emac.h> +#include <asm/arch/gpio.h> +#include <asm/arch/board.h> + +#include "at91_ether.h" + +#define DRV_NAME "at91_ether" +#define DRV_VERSION "1.0" + +static struct net_device *at91_dev; +static struct clk *ether_clk; + +/* ..................................................................... */ + +/* + * Read from a EMAC register. + */ +static inline unsigned long at91_emac_read(unsigned int reg) +{ + void __iomem *emac_base = (void __iomem *)AT91_VA_BASE_EMAC; + + return __raw_readl(emac_base + reg); +} + +/* + * Write to a EMAC register. + */ +static inline void at91_emac_write(unsigned int reg, unsigned long value) +{ + void __iomem *emac_base = (void __iomem *)AT91_VA_BASE_EMAC; + + __raw_writel(value, emac_base + reg); +} + +/* ........................... 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(void) +{ + unsigned long ctl; + + ctl = at91_emac_read(AT91_EMAC_CTL); + at91_emac_write(AT91_EMAC_CTL, ctl | AT91_EMAC_MPE); /* enable management port */ +} + +/* + * Disable the MDIO bit in the MAC control register + */ +static void disable_mdi(void) +{ + unsigned long ctl; + + ctl = at91_emac_read(AT91_EMAC_CTL); + at91_emac_write(AT91_EMAC_CTL, ctl & ~AT91_EMAC_MPE); /* disable management port */ +} + +/* + * Wait until the PHY operation is complete. + */ +static inline void at91_phy_wait(void) { + unsigned long timeout = jiffies + 2; + + while (!(at91_emac_read(AT91_EMAC_SR) & AT91_EMAC_SR_IDLE)) { + if (time_after(jiffies, timeout)) { + printk("at91_ether: MIO timeout\n"); + break; + } + cpu_relax(); + } +} + +/* + * Write value to the a PHY register + * Note: MDI interface is assumed to already have been enabled. + */ +static void write_phy(unsigned char phy_addr, unsigned char address, unsigned int value) +{ + at91_emac_write(AT91_EMAC_MAN, AT91_EMAC_MAN_802_3 | AT91_EMAC_RW_W + | ((phy_addr & 0x1f) << 23) | (address << 18) | (value & AT91_EMAC_DATA)); + + /* Wait until IDLE bit in Network Status register is cleared */ + at91_phy_wait(); +} + +/* + * Read value stored in a PHY register. + * Note: MDI interface is assumed to already have been enabled. + */ +static void read_phy(unsigned char phy_addr, unsigned char address, unsigned int *value) +{ + at91_emac_write(AT91_EMAC_MAN, AT91_EMAC_MAN_802_3 | AT91_EMAC_RW_R + | ((phy_addr & 0x1f) << 23) | (address << 18)); + + /* Wait until IDLE bit in Network Status register is cleared */ + at91_phy_wait(); + + *value = at91_emac_read(AT91_EMAC_MAN) & AT91_EMAC_DATA; +} + +/* ........................... 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. + */ +static void update_linkspeed(struct net_device *dev) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + unsigned int bmsr, bmcr, lpa, mac_cfg; + unsigned int speed, duplex; + + if (!mii_link_ok(&lp->mii)) { /* no link */ + netif_carrier_off(dev); + printk(KERN_INFO "%s: Link down.\n", dev->name); + return; + } + + /* Link up, or auto-negotiation still in progress */ + read_phy(lp->phy_address, MII_BMSR, &bmsr); + read_phy(lp->phy_address, MII_BMCR, &bmcr); + if (bmcr & BMCR_ANENABLE) { /* AutoNegotiation is enabled */ + if (!(bmsr & BMSR_ANEGCOMPLETE)) + return; /* Do nothing - another interrupt generated when negotiation complete */ + + read_phy(lp->phy_address, 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 = at91_emac_read(AT91_EMAC_CFG) & ~(AT91_EMAC_SPD | AT91_EMAC_FD); + if (speed == SPEED_100) { + if (duplex == DUPLEX_FULL) /* 100 Full Duplex */ + mac_cfg |= AT91_EMAC_SPD | AT91_EMAC_FD; + else /* 100 Half Duplex */ + mac_cfg |= AT91_EMAC_SPD; + } else { + if (duplex == DUPLEX_FULL) /* 10 Full Duplex */ + mac_cfg |= AT91_EMAC_FD; + else {} /* 10 Half Duplex */ + } + at91_emac_write(AT91_EMAC_CFG, mac_cfg); + + printk(KERN_INFO "%s: Link now %i-%s\n", dev->name, speed, (duplex == DUPLEX_FULL) ? "FullDuplex" : "HalfDuplex"); + netif_carrier_on(dev); +} + +/* + * Handle interrupts from the PHY + */ +static irqreturn_t at91ether_phy_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = (struct net_device *) dev_id; + struct at91_private *lp = (struct at91_private *) dev->priv; + unsigned int phy; + + /* + * This hander is triggered on both edges, but the PHY chips expect + * level-triggering. We therefore have to check if the PHY actually has + * an IRQ pending. + */ + enable_mdi(); + if ((lp->phy_type == MII_DM9161_ID) || (lp->phy_type == MII_DM9161A_ID)) { + read_phy(lp->phy_address, MII_DSINTR_REG, &phy); /* ack interrupt in Davicom PHY */ + if (!(phy & (1 << 0))) + goto done; + } + else if (lp->phy_type == MII_LXT971A_ID) { + read_phy(lp->phy_address, MII_ISINTS_REG, &phy); /* ack interrupt in Intel PHY */ + if (!(phy & (1 << 2))) + goto done; + } + else if (lp->phy_type == MII_BCM5221_ID) { + read_phy(lp->phy_address, MII_BCMINTR_REG, &phy); /* ack interrupt in Broadcom PHY */ + if (!(phy & (1 << 0))) + goto done; + } + else if (lp->phy_type == MII_KS8721_ID) { + read_phy(lp->phy_address, MII_TPISTATUS, &phy); /* ack interrupt in Micrel PHY */ + if (!(phy & ((1 << 2) | 1))) + goto done; + } + + update_linkspeed(dev); + +done: + disable_mdi(); + + return IRQ_HANDLED; +} + +/* + * Initialize and enable the PHY interrupt for link-state changes + */ +static void enable_phyirq(struct net_device *dev) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + unsigned int dsintr, irq_number; + int status; + + if (lp->phy_type == MII_RTL8201_ID) /* RTL8201 does not have an interrupt */ + return; + if (lp->phy_type == MII_DP83847_ID) /* DP83847 does not have an interrupt */ + return; + if (lp->phy_type == MII_AC101L_ID) /* AC101L interrupt not supported yet */ + return; + + irq_number = lp->board_data.phy_irq_pin; + status = request_irq(irq_number, at91ether_phy_interrupt, 0, dev->name, dev); + if (status) { + printk(KERN_ERR "at91_ether: PHY IRQ %d request failed - status %d!\n", irq_number, status); + return; + } + + spin_lock_irq(&lp->lock); + enable_mdi(); + + if ((lp->phy_type == MII_DM9161_ID) || (lp->phy_type == MII_DM9161A_ID)) { /* for Davicom PHY */ + read_phy(lp->phy_address, MII_DSINTR_REG, &dsintr); + dsintr = dsintr & ~0xf00; /* clear bits 8..11 */ + write_phy(lp->phy_address, MII_DSINTR_REG, dsintr); + } + else if (lp->phy_type == MII_LXT971A_ID) { /* for Intel PHY */ + read_phy(lp->phy_address, MII_ISINTE_REG, &dsintr); + dsintr = dsintr | 0xf2; /* set bits 1, 4..7 */ + write_phy(lp->phy_address, MII_ISINTE_REG, dsintr); + } + else if (lp->phy_type == MII_BCM5221_ID) { /* for Broadcom PHY */ + dsintr = (1 << 15) | ( 1 << 14); + write_phy(lp->phy_address, MII_BCMINTR_REG, dsintr); + } + else if (lp->phy_type == MII_KS8721_ID) { /* for Micrel PHY */ + dsintr = (1 << 10) | ( 1 << 8); + write_phy(lp->phy_address, MII_TPISTATUS, dsintr); + } + + disable_mdi(); + spin_unlock_irq(&lp->lock); +} + +/* + * Disable the PHY interrupt + */ +static void disable_phyirq(struct net_device *dev) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + unsigned int dsintr; + unsigned int irq_number; + + if (lp->phy_type == MII_RTL8201_ID) /* RTL8201 does not have an interrupt */ + return; + if (lp->phy_type == MII_DP83847_ID) /* DP83847 does not have an interrupt */ + return; + if (lp->phy_type == MII_AC101L_ID) /* AC101L interrupt not supported yet */ + return; + + spin_lock_irq(&lp->lock); + enable_mdi(); + + if ((lp->phy_type == MII_DM9161_ID) || (lp->phy_type == MII_DM9161A_ID)) { /* for Davicom PHY */ + read_phy(lp->phy_address, MII_DSINTR_REG, &dsintr); + dsintr = dsintr | 0xf00; /* set bits 8..11 */ + write_phy(lp->phy_address, MII_DSINTR_REG, dsintr); + } + else if (lp->phy_type == MII_LXT971A_ID) { /* for Intel PHY */ + read_phy(lp->phy_address, MII_ISINTE_REG, &dsintr); + dsintr = dsintr & ~0xf2; /* clear bits 1, 4..7 */ + write_phy(lp->phy_address, MII_ISINTE_REG, dsintr); + } + else if (lp->phy_type == MII_BCM5221_ID) { /* for Broadcom PHY */ + read_phy(lp->phy_address, MII_BCMINTR_REG, &dsintr); + dsintr = ~(1 << 14); + write_phy(lp->phy_address, MII_BCMINTR_REG, dsintr); + } + else if (lp->phy_type == MII_KS8721_ID) { /* for Micrel PHY */ + read_phy(lp->phy_address, MII_TPISTATUS, &dsintr); + dsintr = ~((1 << 10) | (1 << 8)); + write_phy(lp->phy_address, MII_TPISTATUS, dsintr); + } + + disable_mdi(); + spin_unlock_irq(&lp->lock); + + irq_number = lp->board_data.phy_irq_pin; + free_irq(irq_number, dev); /* Free interrupt handler */ +} + +/* + * Perform a software reset of the PHY. + */ +#if 0 +static void reset_phy(struct net_device *dev) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + unsigned int bmcr; + + spin_lock_irq(&lp->lock); + enable_mdi(); + + /* Perform PHY reset */ + write_phy(lp->phy_address, MII_BMCR, BMCR_RESET); + + /* Wait until PHY reset is complete */ + do { + read_phy(lp->phy_address, MII_BMCR, &bmcr); + } while (!(bmcr && BMCR_RESET)); + + disable_mdi(); + spin_unlock_irq(&lp->lock); +} +#endif + +/* ......................... ADDRESS MANAGEMENT ........................ */ + +/* + * NOTE: Your bootloader must always set the MAC address correctly before + * booting into Linux. + * + * - It must always set the MAC address after reset, even if it doesn't + * happen to access the Ethernet while it's booting. Some versions of + * U-Boot on the AT91RM9200-DK do not do this. + * + * - Likewise it must store the addresses in the correct byte order. + * MicroMonitor (uMon) on the CSB337 does this incorrectly (and + * continues to do so, for bug-compatibility). + */ + +static short __init unpack_mac_address(struct net_device *dev, unsigned int hi, unsigned int lo) +{ + char addr[6]; + + if (machine_is_csb337()) { + addr[5] = (lo & 0xff); /* The CSB337 bootloader stores the MAC the wrong-way around */ + addr[4] = (lo & 0xff00) >> 8; + addr[3] = (lo & 0xff0000) >> 16; + addr[2] = (lo & 0xff000000) >> 24; + addr[1] = (hi & 0xff); + addr[0] = (hi & 0xff00) >> 8; + } + else { + 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 1; + } + return 0; +} + +/* + * Set the ethernet MAC address in dev->dev_addr + */ +static void __init get_mac_address(struct net_device *dev) +{ + /* Check Specific-Address 1 */ + if (unpack_mac_address(dev, at91_emac_read(AT91_EMAC_SA1H), at91_emac_read(AT91_EMAC_SA1L))) + return; + /* Check Specific-Address 2 */ + if (unpack_mac_address(dev, at91_emac_read(AT91_EMAC_SA2H), at91_emac_read(AT91_EMAC_SA2L))) + return; + /* Check Specific-Address 3 */ + if (unpack_mac_address(dev, at91_emac_read(AT91_EMAC_SA3H), at91_emac_read(AT91_EMAC_SA3L))) + return; + /* Check Specific-Address 4 */ + if (unpack_mac_address(dev, at91_emac_read(AT91_EMAC_SA4H), at91_emac_read(AT91_EMAC_SA4L))) + return; + + printk(KERN_ERR "at91_ether: Your bootloader did not configure a MAC address.\n"); +} + +/* + * Program the hardware MAC address from dev->dev_addr. + */ +static void update_mac_address(struct net_device *dev) +{ + at91_emac_write(AT91_EMAC_SA1L, (dev->dev_addr[3] << 24) | (dev->dev_addr[2] << 16) | (dev->dev_addr[1] << 8) | (dev->dev_addr[0])); + at91_emac_write(AT91_EMAC_SA1H, (dev->dev_addr[5] << 8) | (dev->dev_addr[4])); + + at91_emac_write(AT91_EMAC_SA2L, 0); + at91_emac_write(AT91_EMAC_SA2H, 0); +} + +/* + * 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; +} + +static int inline hash_bit_value(int bitnr, __u8 *addr) +{ + if (addr[bitnr / 8] & (1 << (bitnr % 8))) + return 1; + return 0; +} + +/* + * The hash address register is 64 bits long and takes up two locations in the memory map. + * The least significant bits are stored in EMAC_HSL and the most significant + * bits in EMAC_HSH. + * + * The unicast hash enable and the multicast hash enable bits in the network configuration + * register enable the reception of hash matched frames. The destination address is + * reduced to a 6 bit index into the 64 bit hash register using the following hash function. + * The hash function is an exclusive or of every sixth bit of the destination address. + * hash_index[5] = da[5] ^ da[11] ^ da[17] ^ da[23] ^ da[29] ^ da[35] ^ da[41] ^ da[47] + * hash_index[4] = da[4] ^ da[10] ^ da[16] ^ da[22] ^ da[28] ^ da[34] ^ da[40] ^ da[46] + * hash_index[3] = da[3] ^ da[09] ^ da[15] ^ da[21] ^ da[27] ^ da[33] ^ da[39] ^ da[45] + * hash_index[2] = da[2] ^ da[08] ^ da[14] ^ da[20] ^ da[26] ^ da[32] ^ da[38] ^ da[44] + * hash_index[1] = da[1] ^ da[07] ^ da[13] ^ da[19] ^ da[25] ^ da[31] ^ da[37] ^ da[43] + * hash_index[0] = da[0] ^ da[06] ^ da[12] ^ da[18] ^ da[24] ^ da[30] ^ da[36] ^ da[42] + * da[0] represents the least significant bit of the first byte received, that is, the multicast/ + * unicast indicator, and da[47] represents the most significant bit of the last byte + * received. + * If the hash index points to a bit that is set in the hash register then the frame will be + * matched according to whether the frame is multicast or unicast. + * A multicast match will be signalled if the multicast hash enable bit is set, da[0] is 1 and + * the hash index points to a bit set in the hash register. + * A unicast match will be signalled if the unicast hash enable bit is set, da[0] is 0 and the + * hash index points to a bit set in the hash register. + * To receive all multicast frames, the hash register should be set with all ones and the + * multicast hash enable bit should be set in the network configuration register. + */ + +/* + * Return the hash index value for the specified address. + */ +static int hash_get_index(__u8 *addr) +{ + int i, j, bitval; + int hash_index = 0; + + for (j = 0; j < 6; j++) { + for (i = 0, bitval = 0; i < 8; i++) + bitval ^= hash_bit_value(i*6 + j, addr); + + hash_index |= (bitval << j); + } + + return hash_index; +} + +/* + * Add multicast addresses to the internal multicast-hash table. + */ +static void at91ether_sethashtable(struct net_device *dev) +{ + struct dev_mc_list *curr; + unsigned long 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 = hash_get_index(curr->dmi_addr); + mc_filter[bitnr >> 5] |= 1 << (bitnr & 31); + } + + at91_emac_write(AT91_EMAC_HSH, mc_filter[0]); + at91_emac_write(AT91_EMAC_HSL, mc_filter[1]); +} + +/* + * Enable/Disable promiscuous and multicast modes. + */ +static void at91ether_set_rx_mode(struct net_device *dev) +{ + unsigned long cfg; + + cfg = at91_emac_read(AT91_EMAC_CFG); + + if (dev->flags & IFF_PROMISC) /* Enable promiscuous mode */ + cfg |= AT91_EMAC_CAF; + else if (dev->flags & (~IFF_PROMISC)) /* Disable promiscuous mode */ + cfg &= ~AT91_EMAC_CAF; + + if (dev->flags & IFF_ALLMULTI) { /* Enable all multicast mode */ + at91_emac_write(AT91_EMAC_HSH, -1); + at91_emac_write(AT91_EMAC_HSL, -1); + cfg |= AT91_EMAC_MTI; + } else if (dev->mc_count > 0) { /* Enable specific multicasts */ + at91ether_sethashtable(dev); + cfg |= AT91_EMAC_MTI; + } else if (dev->flags & (~IFF_ALLMULTI)) { /* Disable all multicast mode */ + at91_emac_write(AT91_EMAC_HSH, 0); + at91_emac_write(AT91_EMAC_HSL, 0); + cfg &= ~AT91_EMAC_MTI; + } + + at91_emac_write(AT91_EMAC_CFG, cfg); +} + + +/* ......................... ETHTOOL SUPPORT ........................... */ + + +static int mdio_read(struct net_device *dev, int phy_id, int location) +{ + unsigned int value; + + read_phy(phy_id, location, &value); + return value; +} + +static void mdio_write(struct net_device *dev, int phy_id, int location, int value) +{ + write_phy(phy_id, location, value); +} + +static int at91ether_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + int ret; + + spin_lock_irq(&lp->lock); + enable_mdi(); + + ret = mii_ethtool_gset(&lp->mii, cmd); + + disable_mdi(); + spin_unlock_irq(&lp->lock); + + if (lp->phy_media == PORT_FIBRE) { /* override media type since mii.c doesn't know */ + cmd->supported = SUPPORTED_FIBRE; + cmd->port = PORT_FIBRE; + } + + return ret; +} + +static int at91ether_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + int ret; + + spin_lock_irq(&lp->lock); + enable_mdi(); + + ret = mii_ethtool_sset(&lp->mii, cmd); + + disable_mdi(); + spin_unlock_irq(&lp->lock); + + return ret; +} + +static int at91ether_nwayreset(struct net_device *dev) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + int ret; + + spin_lock_irq(&lp->lock); + enable_mdi(); + + ret = mii_nway_restart(&lp->mii); + + disable_mdi(); + spin_unlock_irq(&lp->lock); + + return ret; +} + +static void at91ether_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_VERSION, sizeof(info->version)); + strlcpy(info->bus_info, dev->class_dev.dev->bus_id, sizeof(info->bus_info)); +} + +static struct ethtool_ops at91ether_ethtool_ops = { + .get_settings = at91ether_get_settings, + .set_settings = at91ether_set_settings, + .get_drvinfo = at91ether_get_drvinfo, + .nway_reset = at91ether_nwayreset, + .get_link = ethtool_op_get_link, +}; + + +/* ................................ MAC ................................ */ + +/* + * Initialize and start the Receiver and Transmit subsystems + */ +static void at91ether_start(struct net_device *dev) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + struct recv_desc_bufs *dlist, *dlist_phys; + int i; + unsigned long ctl; + + dlist = lp->dlist; + dlist_phys = lp->dlist_phys; + + for (i = 0; i < MAX_RX_DESCR; i++) { + dlist->descriptors[i].addr = (unsigned int) &dlist_phys->recv_buf[i][0]; + dlist->descriptors[i].size = 0; + } + + /* Set the Wrap bit on the last descriptor */ + dlist->descriptors[i-1].addr |= EMAC_DESC_WRAP; + + /* Reset buffer index */ + lp->rxBuffIndex = 0; + + /* Program address of descriptor list in Rx Buffer Queue register */ + at91_emac_write(AT91_EMAC_RBQP, (unsigned long) dlist_phys); + + /* Enable Receive and Transmit */ + ctl = at91_emac_read(AT91_EMAC_CTL); + at91_emac_write(AT91_EMAC_CTL, ctl | AT91_EMAC_RE | AT91_EMAC_TE); +} + +/* + * Open the ethernet interface + */ +static int at91ether_open(struct net_device *dev) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + unsigned long ctl; + + if (!is_valid_ether_addr(dev->dev_addr)) + return -EADDRNOTAVAIL; + + clk_enable(ether_clk); /* Re-enable Peripheral clock */ + + /* Clear internal statistics */ + ctl = at91_emac_read(AT91_EMAC_CTL); + at91_emac_write(AT91_EMAC_CTL, ctl | AT91_EMAC_CSR); + + /* Update the MAC address (incase user has changed it) */ + update_mac_address(dev); + + /* Enable PHY interrupt */ + enable_phyirq(dev); + + /* Enable MAC interrupts */ + at91_emac_write(AT91_EMAC_IER, AT91_EMAC_RCOM | AT91_EMAC_RBNA + | AT91_EMAC_TUND | AT91_EMAC_RTRY | AT91_EMAC_TCOM + | AT91_EMAC_ROVR | AT91_EMAC_ABT); + + /* Determine current link speed */ + spin_lock_irq(&lp->lock); + enable_mdi(); + update_linkspeed(dev); + disable_mdi(); + spin_unlock_irq(&lp->lock); + + at91ether_start(dev); + netif_start_queue(dev); + return 0; +} + +/* + * Close the interface + */ +static int at91ether_close(struct net_device *dev) +{ + unsigned long ctl; + + /* Disable Receiver and Transmitter */ + ctl = at91_emac_read(AT91_EMAC_CTL); + at91_emac_write(AT91_EMAC_CTL, ctl & ~(AT91_EMAC_TE | AT91_EMAC_RE)); + + /* Disable PHY interrupt */ + disable_phyirq(dev); + + /* Disable MAC interrupts */ + at91_emac_write(AT91_EMAC_IDR, AT91_EMAC_RCOM | AT91_EMAC_RBNA + | AT91_EMAC_TUND | AT91_EMAC_RTRY | AT91_EMAC_TCOM + | AT91_EMAC_ROVR | AT91_EMAC_ABT); + + netif_stop_queue(dev); + + clk_disable(ether_clk); /* Disable Peripheral clock */ + + return 0; +} + +/* + * Transmit packet. + */ +static int at91ether_tx(struct sk_buff *skb, struct net_device *dev) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + + if (at91_emac_read(AT91_EMAC_TSR) & AT91_EMAC_TSR_BNQ) { + netif_stop_queue(dev); + + /* Store packet information (to free when Tx completed) */ + lp->skb = skb; + lp->skb_length = skb->len; + lp->skb_physaddr = dma_map_single(NULL, skb->data, skb->len, DMA_TO_DEVICE); + lp->stats.tx_bytes += skb->len; + + /* Set address of the data in the Transmit Address register */ + at91_emac_write(AT91_EMAC_TAR, lp->skb_physaddr); + /* Set length of the packet in the Transmit Control register */ + at91_emac_write(AT91_EMAC_TCR, skb->len); + + dev->trans_start = jiffies; + } else { + printk(KERN_ERR "at91_ether.c: at91ether_tx() called, but device is busy!\n"); + return 1; /* if we return anything but zero, dev.c:1055 calls kfree_skb(skb) + on this skb, he also reports -ENETDOWN and printk's, so either + we free and return(0) or don't free and return 1 */ + } + + return 0; +} + +/* + * Update the current statistics from the internal statistics registers. + */ +static struct net_device_stats *at91ether_stats(struct net_device *dev) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + int ale, lenerr, seqe, lcol, ecol; + + if (netif_running(dev)) { + lp->stats.rx_packets += at91_emac_read(AT91_EMAC_OK); /* Good frames received */ + ale = at91_emac_read(AT91_EMAC_ALE); + lp->stats.rx_frame_errors += ale; /* Alignment errors */ + lenerr = at91_emac_read(AT91_EMAC_ELR) + at91_emac_read(AT91_EMAC_USF); + lp->stats.rx_length_errors += lenerr; /* Excessive Length or Undersize Frame error */ + seqe = at91_emac_read(AT91_EMAC_SEQE); + lp->stats.rx_crc_errors += seqe; /* CRC error */ + lp->stats.rx_fifo_errors += at91_emac_read(AT91_EMAC_DRFC); /* Receive buffer not available */ + lp->stats.rx_errors += (ale + lenerr + seqe + + at91_emac_read(AT91_EMAC_CDE) + at91_emac_read(AT91_EMAC_RJB)); + + lp->stats.tx_packets += at91_emac_read(AT91_EMAC_FRA); /* Frames successfully transmitted */ + lp->stats.tx_fifo_errors += at91_emac_read(AT91_EMAC_TUE); /* Transmit FIFO underruns */ + lp->stats.tx_carrier_errors += at91_emac_read(AT91_EMAC_CSE); /* Carrier Sense errors */ + lp->stats.tx_heartbeat_errors += at91_emac_read(AT91_EMAC_SQEE);/* Heartbeat error */ + + lcol = at91_emac_read(AT91_EMAC_LCOL); + ecol = at91_emac_read(AT91_EMAC_ECOL); + lp->stats.tx_window_errors += lcol; /* Late collisions */ + lp->stats.tx_aborted_errors += ecol; /* 16 collisions */ + + lp->stats.collisions += (at91_emac_read(AT91_EMAC_SCOL) + at91_emac_read(AT91_EMAC_MCOL) + lcol + ecol); + } + return &lp->stats; +} + +/* + * Extract received frame from buffer descriptors and sent to upper layers. + * (Called from interrupt context) + */ +static void at91ether_rx(struct net_device *dev) +{ + struct at91_private *lp = (struct at91_private *) dev->priv; + struct recv_desc_bufs *dlist; + unsigned char *p_recv; + struct sk_buff *skb; + unsigned int pktlen; + + dlist = lp->dlist; + while (dlist->descriptors[lp->rxBuffIndex].addr & EMAC_DESC_DONE) { + p_recv = dlist->recv_buf[lp->rxBuffIndex]; + pktlen = dlist->descriptors[lp->rxBuffIndex].size & 0x7ff; /* Length of frame including FCS */ + skb = alloc_skb(pktlen + 2, GFP_ATOMIC); + if (skb != NULL) { + skb_reserve(skb, 2); + memcpy(skb_put(skb, pktlen), p_recv, pktlen); + + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->len = pktlen; + dev->last_rx = jiffies; + lp->stats.rx_bytes += pktlen; + netif_rx(skb); + } + else { + lp->stats.rx_dropped += 1; + printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", dev->name); + } + + if (dlist->descriptors[lp->rxBuffIndex].size & EMAC_MULTICAST) + lp->stats.multicast++; + + dlist->descriptors[lp->rxBuffIndex].addr &= ~EMAC_DESC_DONE; /* reset ownership bit */ + if (lp->rxBuffIndex == MAX_RX_DESCR-1) /* wrap after last buffer */ + lp->rxBuffIndex = 0; + else + lp->rxBuffIndex++; + } +} + +/* + * MAC interrupt handler + */ +static irqreturn_t at91ether_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = (struct net_device *) dev_id; + struct at91_private *lp = (struct at91_private *) dev->priv; + unsigned long intstatus, ctl; + + /* MAC Interrupt Status register indicates what interrupts are pending. + It is automatically cleared once read. */ + intstatus = at91_emac_read(AT91_EMAC_ISR); + + if (intstatus & AT91_EMAC_RCOM) /* Receive complete */ + at91ether_rx(dev); + + if (intstatus & AT91_EMAC_TCOM) { /* Transmit complete */ + /* The TCOM bit is set even if the transmission failed. */ + if (intstatus & (AT91_EMAC_TUND | AT91_EMAC_RTRY)) + lp->stats.tx_errors += 1; + + if (lp->skb) { + dev_kfree_skb_irq(lp->skb); + lp->skb = NULL; + dma_unmap_single(NULL, lp->skb_physaddr, lp->skb_length, DMA_TO_DEVICE); + } + netif_wake_queue(dev); + } + + /* Work-around for Errata #11 */ + if (intstatus & AT91_EMAC_RBNA) { + ctl = at91_emac_read(AT91_EMAC_CTL); + at91_emac_write(AT91_EMAC_CTL, ctl & ~AT91_EMAC_RE); + at91_emac_write(AT91_EMAC_CTL, ctl | AT91_EMAC_RE); + } + + if (intstatus & AT91_EMAC_ROVR) + printk("%s: ROVR error\n", dev->name); + + return IRQ_HANDLED; +} + +/* + * Initialize the ethernet interface + */ +static int __init at91ether_setup(unsigned long phy_type, unsigned short phy_address, struct platform_device *pdev) +{ + struct at91_eth_data *board_data = pdev->dev.platform_data; + struct net_device *dev; + struct at91_private *lp; + unsigned int val; + int res; + + if (at91_dev) /* already initialized */ + return 0; + + dev = alloc_etherdev(sizeof(struct at91_private)); + if (!dev) + return -ENOMEM; + + dev->base_addr = AT91_VA_BASE_EMAC; + dev->irq = AT91_ID_EMAC; + SET_MODULE_OWNER(dev); + + /* Install the interrupt handler */ + if (request_irq(dev->irq, at91ether_interrupt, 0, dev->name, dev)) { + free_netdev(dev); + return -EBUSY; + } + + /* Allocate memory for DMA Receive descriptors */ + lp = (struct at91_private *)dev->priv; + lp->dlist = (struct recv_desc_bufs *) dma_alloc_coherent(NULL, sizeof(struct recv_desc_bufs), (dma_addr_t *) &lp->dlist_phys, GFP_KERNEL); + if (lp->dlist == NULL) { + free_irq(dev->irq, dev); + free_netdev(dev); + return -ENOMEM; + } + lp->board_data = *board_data; + platform_set_drvdata(pdev, dev); + + spin_lock_init(&lp->lock); + + ether_setup(dev); + dev->open = at91ether_open; + dev->stop = at91ether_close; + dev->hard_start_xmit = at91ether_tx; + dev->get_stats = at91ether_stats; + dev->set_multicast_list = at91ether_set_rx_mode; + dev->set_mac_address = set_mac_address; + dev->ethtool_ops = &at91ether_ethtool_ops; + + SET_NETDEV_DEV(dev, &pdev->dev); + + get_mac_address(dev); /* Get ethernet address and store it in dev->dev_addr */ + update_mac_address(dev); /* Program ethernet address into MAC */ + + at91_emac_write(AT91_EMAC_CTL, 0); + + if (lp->board_data.is_rmii) + at91_emac_write(AT91_EMAC_CFG, AT91_EMAC_CLK_DIV32 | AT91_EMAC_BIG | AT91_EMAC_RMII); + else + at91_emac_write(AT91_EMAC_CFG, AT91_EMAC_CLK_DIV32 | AT91_EMAC_BIG); + + /* Perform PHY-specific initialization */ + spin_lock_irq(&lp->lock); + enable_mdi(); + if ((phy_type == MII_DM9161_ID) || (lp->phy_type == MII_DM9161A_ID)) { + read_phy(phy_address, MII_DSCR_REG, &val); + if ((val & (1 << 10)) == 0) /* DSCR bit 10 is 0 -- fiber mode */ + lp->phy_media = PORT_FIBRE; + } else if (machine_is_csb337()) { + /* mix link activity status into LED2 link state */ + write_phy(phy_address, MII_LEDCTRL_REG, 0x0d22); + } + disable_mdi(); + spin_unlock_irq(&lp->lock); + + lp->mii.dev = dev; /* Support for ethtool */ + lp->mii.mdio_read = mdio_read; + lp->mii.mdio_write = mdio_write; + + lp->phy_type = phy_type; /* Type of PHY connected */ + lp->phy_address = phy_address; /* MDI address of PHY */ + + /* Register the network interface */ + res = register_netdev(dev); + if (res) { + free_irq(dev->irq, dev); + free_netdev(dev); + dma_free_coherent(NULL, sizeof(struct recv_desc_bufs), lp->dlist, (dma_addr_t)lp->dlist_phys); + return res; + } + at91_dev = dev; + + /* Determine current link speed */ + spin_lock_irq(&lp->lock); + enable_mdi(); + update_linkspeed(dev); + disable_mdi(); + spin_unlock_irq(&lp->lock); + netif_carrier_off(dev); /* will be enabled in open() */ + + /* Display ethernet banner */ + printk(KERN_INFO "%s: AT91 ethernet at 0x%08x int=%d %s%s (%02x:%02x:%02x:%02x:%02x:%02x)\n", + dev->name, (uint) dev->base_addr, dev->irq, + at91_emac_read(AT91_EMAC_CFG) & AT91_EMAC_SPD ? "100-" : "10-", + at91_emac_read(AT91_EMAC_CFG) & AT91_EMAC_FD ? "FullDuplex" : "HalfDuplex", + dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], + dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); + if ((phy_type == MII_DM9161_ID) || (lp->phy_type == MII_DM9161A_ID)) + printk(KERN_INFO "%s: Davicom 9196 PHY %s\n", dev->name, (lp->phy_media == PORT_FIBRE) ? "(Fiber)" : "(Copper)"); + else if (phy_type == MII_LXT971A_ID) + printk(KERN_INFO "%s: Intel LXT971A PHY\n", dev->name); + else if (phy_type == MII_RTL8201_ID) + printk(KERN_INFO "%s: Realtek RTL8201(B)L PHY\n", dev->name); + else if (phy_type == MII_BCM5221_ID) + printk(KERN_INFO "%s: Broadcom BCM5221 PHY\n", dev->name); + else if (phy_type == MII_DP83847_ID) + printk(KERN_INFO "%s: National Semiconductor DP83847 PHY\n", dev->name); + else if (phy_type == MII_AC101L_ID) + printk(KERN_INFO "%s: Altima AC101L PHY\n", dev->name); + else if (phy_type == MII_KS8721_ID) + printk(KERN_INFO "%s: Micrel KS8721 PHY\n", dev->name); + + return 0; +} + +/* + * Detect MAC and PHY and perform initialization + */ +static int __init at91ether_probe(struct platform_device *pdev) +{ + unsigned int phyid1, phyid2; + int detected = -1; + unsigned long phy_id; + unsigned short phy_address = 0; + + ether_clk = clk_get(&pdev->dev, "ether_clk"); + if (!ether_clk) { + printk(KERN_ERR "at91_ether: no clock defined\n"); + return -ENODEV; + } + clk_enable(ether_clk); /* Enable Peripheral clock */ + + while ((detected != 0) && (phy_address < 32)) { + /* Read the PHY ID registers */ + enable_mdi(); + read_phy(phy_address, MII_PHYSID1, &phyid1); + read_phy(phy_address, MII_PHYSID2, &phyid2); + disable_mdi(); + + phy_id = (phyid1 << 16) | (phyid2 & 0xfff0); + switch (phy_id) { + case MII_DM9161_ID: /* Davicom 9161: PHY_ID1 = 0x181, PHY_ID2 = B881 */ + case MII_DM9161A_ID: /* Davicom 9161A: PHY_ID1 = 0x181, PHY_ID2 = B8A0 */ + case MII_LXT971A_ID: /* Intel LXT971A: PHY_ID1 = 0x13, PHY_ID2 = 78E0 */ + case MII_RTL8201_ID: /* Realtek RTL8201: PHY_ID1 = 0, PHY_ID2 = 0x8201 */ + case MII_BCM5221_ID: /* Broadcom BCM5221: PHY_ID1 = 0x40, PHY_ID2 = 0x61e0 */ + case MII_DP83847_ID: /* National Semiconductor DP83847: */ + case MII_AC101L_ID: /* Altima AC101L: PHY_ID1 = 0x22, PHY_ID2 = 0x5520 */ + case MII_KS8721_ID: /* Micrel KS8721: PHY_ID1 = 0x22, PHY_ID2 = 0x1610 */ + detected = at91ether_setup(phy_id, phy_address, pdev); + break; + } + + phy_address++; + } + + clk_disable(ether_clk); /* Disable Peripheral clock */ + + return detected; +} + +static int __devexit at91ether_remove(struct platform_device *pdev) +{ + struct at91_private *lp = (struct at91_private *) at91_dev->priv; + + unregister_netdev(at91_dev); + free_irq(at91_dev->irq, at91_dev); + dma_free_coherent(NULL, sizeof(struct recv_desc_bufs), lp->dlist, (dma_addr_t)lp->dlist_phys); + clk_put(ether_clk); + + free_netdev(at91_dev); + at91_dev = NULL; + return 0; +} + +static struct platform_driver at91ether_driver = { + .probe = at91ether_probe, + .remove = __devexit_p(at91ether_remove), + /* FIXME: support suspend and resume */ + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init at91ether_init(void) +{ + return platform_driver_register(&at91ether_driver); +} + +static void __exit at91ether_exit(void) +{ + platform_driver_unregister(&at91ether_driver); +} + +module_init(at91ether_init) +module_exit(at91ether_exit) + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("AT91RM9200 EMAC Ethernet driver"); +MODULE_AUTHOR("Andrew Victor"); diff --git a/drivers/net/arm/at91_ether.h b/drivers/net/arm/at91_ether.h new file mode 100644 index 000000000000..9885735c9c8a --- /dev/null +++ b/drivers/net/arm/at91_ether.h @@ -0,0 +1,101 @@ +/* + * Ethernet driver for the Atmel AT91RM9200 (Thunder) + * + * Copyright (C) SAN People (Pty) Ltd + * + * Based on an earlier Atmel EMAC macrocell driver by Atmel and Lineo Inc. + * Initial version by Rick Bronson. + * + * 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. + */ + +#ifndef AT91_ETHERNET +#define AT91_ETHERNET + + +/* Davicom 9161 PHY */ +#define MII_DM9161_ID 0x0181b880 +#define MII_DM9161A_ID 0x0181b8a0 + +/* Davicom specific registers */ +#define MII_DSCR_REG 16 +#define MII_DSCSR_REG 17 +#define MII_DSINTR_REG 21 + +/* Intel LXT971A PHY */ +#define MII_LXT971A_ID 0x001378E0 + +/* Intel specific registers */ +#define MII_ISINTE_REG 18 +#define MII_ISINTS_REG 19 +#define MII_LEDCTRL_REG 20 + +/* Realtek RTL8201 PHY */ +#define MII_RTL8201_ID 0x00008200 + +/* Broadcom BCM5221 PHY */ +#define MII_BCM5221_ID 0x004061e0 + +/* Broadcom specific registers */ +#define MII_BCMINTR_REG 26 + +/* National Semiconductor DP83847 */ +#define MII_DP83847_ID 0x20005c30 + +/* Altima AC101L PHY */ +#define MII_AC101L_ID 0x00225520 + +/* Micrel KS8721 PHY */ +#define MII_KS8721_ID 0x00221610 + +/* ........................................................................ */ + +#define MAX_RBUFF_SZ 0x600 /* 1518 rounded up */ +#define MAX_RX_DESCR 9 /* max number of receive buffers */ + +#define EMAC_DESC_DONE 0x00000001 /* bit for if DMA is done */ +#define EMAC_DESC_WRAP 0x00000002 /* bit for wrap */ + +#define EMAC_BROADCAST 0x80000000 /* broadcast address */ +#define EMAC_MULTICAST 0x40000000 /* multicast address */ +#define EMAC_UNICAST 0x20000000 /* unicast address */ + +struct rbf_t +{ + unsigned int addr; + unsigned long size; +}; + +struct recv_desc_bufs +{ + struct rbf_t descriptors[MAX_RX_DESCR]; /* must be on sizeof (rbf_t) boundary */ + char recv_buf[MAX_RX_DESCR][MAX_RBUFF_SZ]; /* must be on long boundary */ +}; + +struct at91_private +{ + struct net_device_stats stats; + struct mii_if_info mii; /* ethtool support */ + struct at91_eth_data board_data; /* board-specific configuration */ + + /* PHY */ + unsigned long phy_type; /* type of PHY (PHY_ID) */ + spinlock_t lock; /* lock for MDI interface */ + short phy_media; /* media interface type */ + unsigned short phy_address; /* 5-bit MDI address of PHY (0..31) */ + + /* Transmit */ + struct sk_buff *skb; /* holds skb until xmit interrupt completes */ + dma_addr_t skb_physaddr; /* phys addr from pci_map_single */ + int skb_length; /* saved skb length for pci_unmap_single */ + + /* Receive */ + int rxBuffIndex; /* index into receive descriptor list */ + struct recv_desc_bufs *dlist; /* descriptor list address */ + struct recv_desc_bufs *dlist_phys; /* descriptor list physical address */ +}; + +#endif |