[U-Boot] [PATCH] AT92RM9200 EMAC driver for NET MULTI API

This patch adds a new net api driver for AT91RM9200 EMAC
* adds NET_MULTI api EMAC (CONFIG_DRIVER_AT91EMAC) * generic PHY initialization
Signed-off-by: Jens Scharsig esw@bus-elektronik.de ---
This patch was tested with full MII interface and LXT971 Phy on our upcomming new board. I have also tested the RMII interface with DM9161 Phy on a CARMEVA-board.
I have no u-boot supported AT91RM9200 boards.
My appeal to all AT91RM9200 boards maintainers: Please test
Changes to make your board use NET_MULTI api:
* include/configs/<board>.h 1. change CONFIG_DRIVER_ETHER to CONFIG_DRIVER_AT91EMAC 2. define CONFIG_NET_MULTI 3. set CONFIG_SYS_RX_ETH_BUFFER, if you want more than standard rx buffers 4. define CONFIG_AT91C_USE_RMII, if you board use RMII interface
* add following code to board file #ifdef CONFIG_DRIVER_AT91EMAC int board_eth_init(bd_t *bis) { int rc = 0; rc = at91emac_register(bis, AT91C_BASE_EMAC); return rc; } #endif
diff --git a/README b/README index dee0e67..00d33be 100644 --- a/README +++ b/README @@ -817,6 +817,16 @@ The following options need to be configured:
- NETWORK Support (other):
+ CONFIG_DRIVER_AT91EMAC + Support for AT91RM9200 EMAC. + + CONFIG_RMII + Define this to use reduced MII inteface + + CONFIG_DRIVER_AT91EMAC_QUIET + If this defined, the driver is quiet. + The driver doen't show link status messages. + CONFIG_DRIVER_LAN91C96 Support for SMSC's LAN91C96 chips.
diff --git a/drivers/net/Makefile b/drivers/net/Makefile index fc9887b..3899052 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -27,6 +27,7 @@ LIB := $(obj)libnet.a
COBJS-$(CONFIG_DRIVER_3C589) += 3c589.o COBJS-$(CONFIG_PPC4xx_EMAC) += 4xx_enet.o +COBJS-$(CONFIG_DRIVER_AT91EMAC) += at91_emac.o COBJS-$(CONFIG_DRIVER_AX88180) += ax88180.o COBJS-$(CONFIG_BCM570x) += bcm570x.o bcm570x_autoneg.o 5701rls.o COBJS-$(CONFIG_BFIN_MAC) += bfin_mac.o diff --git a/drivers/net/at91_emac.c b/drivers/net/at91_emac.c new file mode 100644 index 0000000..269559c --- /dev/null +++ b/drivers/net/at91_emac.c @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2009 BuS Elektronik GmbH & Co. KG + * Jens Scharsig (esw@bus-elektronik.de) + * + * (C) Copyright 2003 + * Author : Hamid Ikdoumi (Atmel) + + * See file CREDITS for list of people who contributed to this + * project. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <asm/arch/hardware.h> +#include <net.h> +#include <netdev.h> +#include <malloc.h> +#include <miiphy.h> +#include <linux/mii.h> + +#undef MII_DEBUG +#undef ET_DEBUG + +#if (CONFIG_SYS_RX_ETH_BUFFER > 1024) +#error AT91 EMAC supports max 1024 RX buffers. \ + Please decrease the CONFIG_SYS_RX_ETH_BUFFER value +#endif + +/* MDIO clock must not exceed 2.5 MHz, so enable MCK divider */ +#if (AT91C_MASTER_CLOCK > 80000000) + #define HCLK_DIV AT91C_EMAC_CLK_HCLK_64 +#elif (AT91C_MASTER_CLOCK > 40000000) + #define HCLK_DIV AT91C_EMAC_CLK_HCLK_32 +#elif (AT91C_MASTER_CLOCK > 20000000) + #define HCLK_DIV AT91C_EMAC_CLK_HCLK_16 +#else + #define HCLK_DIV AT91C_EMAC_CLK_HCLK_8 +#endif + +#ifdef ET_DEBUG +#define DEBUG_AT91EAMC(...) printf(__VA_ARGS__); +#else +#define DEBUG_AT91EAMC(...) +#endif + +#ifdef MII_DEBUG +#define DEBUG_AT91PHY(...) printf(__VA_ARGS__); +#else +#define DEBUG_AT91PHY(...) +#endif + +#ifndef CONFIG_DRIVER_AT91EMAC_QUIET +#define VERBOSEP(...) printf(__VA_ARGS__); +#else +#define VERBOSEP(...) +#endif + +#define RBF_ADDR 0xfffffffc +#define RBF_OWNER (1<<0) +#define RBF_WRAP (1<<1) +#define RBF_BROADCAST (1<<31) +#define RBF_MULTICAST (1<<30) +#define RBF_UNICAST (1<<29) +#define RBF_EXTERNAL (1<<28) +#define RBF_UNKOWN (1<<27) +#define RBF_SIZE 0x07ff +#define RBF_LOCAL4 (1<<26) +#define RBF_LOCAL3 (1<<25) +#define RBF_LOCAL2 (1<<24) +#define RBF_LOCAL1 (1<<23) + +#define RBF_FRAMEMAX CONFIG_SYS_RX_ETH_BUFFER +#define RBF_FRAMELEN 0x600 + +typedef struct { + unsigned long addr, size; +} rbf_t; + +typedef struct { + rbf_t rbfdt[RBF_FRAMEMAX]; + unsigned long rbindex; +} emac_device; + +void at91emac_EnableMDIO(AT91PS_EMAC at91mac) +{ + /* Mac CTRL reg set for MDIO enable */ + at91mac->EMAC_CTL |= AT91C_EMAC_MPE; /* Management port enable */ +} + +void at91emac_DisableMDIO(AT91PS_EMAC at91mac) +{ + /* Mac CTRL reg set for MDIO disable */ + at91mac->EMAC_CTL &= ~AT91C_EMAC_MPE; /* Management port disable */ +} + +int at91emac_read(AT91PS_EMAC at91mac, unsigned char addr, + unsigned char reg, unsigned short *value) +{ + at91emac_EnableMDIO(at91mac); + + at91mac->EMAC_MAN = (AT91C_EMAC_HIGH & ~AT91C_EMAC_LOW) | + (AT91C_EMAC_RW_R) | (reg << 18) | + (AT91C_EMAC_CODE_802_3); + udelay(10000); + *value = (unsigned short) at91mac->EMAC_MAN; + + at91emac_DisableMDIO(at91mac); + + DEBUG_AT91PHY("AT91PHY read %x REG(%d)=%x\n", at91mac, reg, *value) + + return 0; +} + +int at91emac_write(AT91PS_EMAC at91mac, unsigned char addr, + unsigned char reg, unsigned short value) +{ + DEBUG_AT91PHY("AT91PHY write %x REG(%d)=%x\n", at91mac, reg, &value) + + at91emac_EnableMDIO(at91mac); + at91mac->EMAC_MAN = (AT91C_EMAC_HIGH & ~AT91C_EMAC_LOW) | + (AT91C_EMAC_CODE_802_3) | AT91C_EMAC_RW_W | + (reg << 18) | value; + + udelay(10000); + + at91emac_DisableMDIO(at91mac); + return 0; +} + +#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) + +int at91emac_mii_read(char *devname, unsigned char addr, + unsigned char reg, unsigned short *value) +{ + struct eth_device *netdev; + netdev = eth_get_dev_by_name(devname); + at91emac_read((AT91PS_EMAC) netdev->iobase, addr, reg, value); + return 0; +} + + +int at91emac_mii_write(char *devname, unsigned char addr, + unsigned char reg, unsigned short value) +{ + struct eth_device *netdev; + netdev = eth_get_dev_by_name(devname); + + at91emac_write((AT91PS_EMAC) netdev->iobase, addr, reg, value); + + return 0; +} + +#endif + +static int at91emac_phy_reset(struct eth_device *netdev) +{ + AT91PS_EMAC at91mac; + + int i; + u16 status, adv; + + at91mac = (AT91PS_EMAC) netdev->iobase; + + adv = ADVERTISE_CSMA | ADVERTISE_ALL; + at91emac_write(at91mac, 0, MII_ADVERTISE, adv); + VERBOSEP("%s: Starting autonegotiation...\n", netdev->name); + at91emac_write(at91mac, 0, MII_BMCR, (BMCR_ANENABLE + | BMCR_ANRESTART)); + + for (i = 0; i < 100000 / 100; i++) { + at91emac_read(at91mac, 0, MII_BMSR, &status); + if (status & BMSR_ANEGCOMPLETE) + break; + udelay(100); + } + + if (status & BMSR_ANEGCOMPLETE) { + VERBOSEP("%s: Autonegotiation complete\n", netdev->name); + } else { + printf("%s: Autonegotiation timed out (status=0x%04x)\n", + netdev->name, status); + return 1; + } + return 0; +} + +static int at91emac_phy_init(struct eth_device *netdev) +{ + u16 phy_id, status, adv, lpa; + int media, speed, duplex; + int i; + AT91PS_EMAC at91mac; + + at91mac = (AT91PS_EMAC) netdev->iobase; + + /* Check if the PHY is up to snuff... */ + at91emac_read(at91mac, 0, MII_PHYSID1, &phy_id); + if (phy_id == 0xffff) { + printf("%s: No PHY present\n", netdev->name); + return 1; + } + + at91emac_read(at91mac, 0, MII_BMSR, &status); + + if (!(status & BMSR_LSTATUS)) { + /* Try to re-negotiate if we don't have link already. */ + if (at91emac_phy_reset(netdev)) + return 2; + + for (i = 0; i < 100000 / 100; i++) { + at91emac_read(at91mac, 0, MII_BMSR, &status); + if (status & BMSR_LSTATUS) + break; + udelay(100); + } + } + if (!(status & BMSR_LSTATUS)) { + VERBOSEP("%s: link down\n", netdev->name, status); + return 3; + } else { + at91emac_read(at91mac, 0, MII_ADVERTISE, &adv); + at91emac_read(at91mac, 0, MII_LPA, &lpa); + media = mii_nway_result(lpa & adv); + speed = (media & (ADVERTISE_100FULL | ADVERTISE_100HALF) + ? 1 : 0); + duplex = (media & ADVERTISE_FULL) ? 1 : 0; + VERBOSEP("%s: link up, %sMbps %s-duplex\n", + netdev->name, + speed ? "100" : "10", + duplex ? "full" : "half", + lpa); + } + return 0; +} + +int at91emac_UpdateLinkSpeed(AT91PS_EMAC at91mac) +{ + unsigned short stat1; + + at91emac_read(at91mac, 0, MII_BMSR, &stat1); + + if (!(stat1 & BMSR_LSTATUS)) /* link status up? */ + return 1; + + if (stat1 & BMSR_100FULL) { + /*set Emac for 100BaseTX and Full Duplex */ + at91mac->EMAC_CFG |= AT91C_EMAC_SPD | AT91C_EMAC_FD; + return 0; + } + + if (stat1 & BMSR_10FULL) { + /*set MII for 10BaseT and Full Duplex */ + at91mac->EMAC_CFG = (at91mac->EMAC_CFG & + ~(AT91C_EMAC_SPD | AT91C_EMAC_FD)) + | AT91C_EMAC_FD; + return 0; + } + + if (stat1 & BMSR_100HALF) { + /*set MII for 100BaseTX and Half Duplex */ + at91mac->EMAC_CFG = (at91mac->EMAC_CFG & + ~(AT91C_EMAC_SPD | AT91C_EMAC_FD)) + | AT91C_EMAC_SPD; + return 0; + } + + if (stat1 & BMSR_10HALF) { + /*set MII for 10BaseT and Half Duplex */ + at91mac->EMAC_CFG &= ~(AT91C_EMAC_SPD | AT91C_EMAC_FD); + return 0; + } + return 1; +} + +static int at91emac_init(struct eth_device *netdev, bd_t *bd) +{ + int i; + emac_device *emac; + AT91PS_EMAC at91mac; + at91mac = (AT91PS_EMAC) netdev->iobase; + emac = (emac_device *) netdev->priv; + + /* PIO Disable Register */ + *AT91C_PIOA_PDR = + AT91C_PA16_EMDIO | AT91C_PA15_EMDC | AT91C_PA14_ERXER | + AT91C_PA13_ERX1 | AT91C_PA12_ERX0 | AT91C_PA11_ECRS_ECRSDV | + AT91C_PA10_ETX1 | AT91C_PA9_ETX0 | + AT91C_PA8_ETXEN | AT91C_PA7_ETXCK_EREFCK; + *AT91C_PIOA_ASR = + AT91C_PA16_EMDIO | AT91C_PA15_EMDC | AT91C_PA14_ERXER | + AT91C_PA13_ERX1 | AT91C_PA12_ERX0 | AT91C_PA11_ECRS_ECRSDV | + AT91C_PA10_ETX1 | AT91C_PA9_ETX0 | + AT91C_PA8_ETXEN | AT91C_PA7_ETXCK_EREFCK; +#ifdef CONFIG_RMII + *AT91C_PIOB_PDR = AT91C_PB19_ERXCK; + *AT91C_PIOB_BSR = AT91C_PB19_ERXCK; +#else + *AT91C_PIOB_PDR = AT91C_PB19_ERXCK | AT91C_PB18_ECOL | + AT91C_PB17_ERXDV | AT91C_PB16_ERX3 | AT91C_PB15_ERX2 | + AT91C_PB14_ETXER | AT91C_PB13_ETX3 | AT91C_PB12_ETX2; + + *AT91C_PIOB_BSR = AT91C_PB19_ERXCK | AT91C_PB18_ECOL | + AT91C_PB17_ERXDV | AT91C_PB16_ERX3 | AT91C_PB15_ERX2 | + AT91C_PB14_ETXER | AT91C_PB13_ETX3 | AT91C_PB12_ETX2; +#endif + + *AT91C_PMC_PCER = 1 << AT91C_ID_EMAC; /* Peripheral Clock Enable */ + at91mac->EMAC_CFG |= AT91C_EMAC_CSR; /* Clear statistics */ + + /* Init Ethernet buffers */ + for (i = 0; i < RBF_FRAMEMAX; i++) { + emac->rbfdt[i].addr = (unsigned long) NetRxPackets[i]; + emac->rbfdt[i].size = 0; + } + emac->rbfdt[RBF_FRAMEMAX - 1].addr |= RBF_WRAP; + emac->rbindex = 0; + + at91mac->EMAC_SA2L = cpu_to_le32(*((u32 *)netdev->enetaddr)); + at91mac->EMAC_SA2H = cpu_to_le16(*((u16 *)(netdev->enetaddr + 4))); + DEBUG_AT91EAMC("init MAC-ADDR %x%x \n", + at91mac->EMAC_SA2H, at91mac->EMAC_SA2L); + + at91mac->EMAC_RBQP = (long) (&(emac->rbfdt[0])); + at91mac->EMAC_RSR &= + ~(AT91C_EMAC_RSR_OVR | AT91C_EMAC_REC | AT91C_EMAC_BNA); + + at91mac->EMAC_CFG = + (at91mac->EMAC_CFG | AT91C_EMAC_CAF | AT91C_EMAC_NBC) + & ~AT91C_EMAC_CLK; + + at91mac->EMAC_CFG = + (at91mac->EMAC_CFG & ~AT91C_EMAC_CLK) | + AT91C_EMAC_CAF | AT91C_EMAC_NBC | + HCLK_DIV; + +#ifdef CONFIG_RMII + at91mac->EMAC_CFG |= AT91C_EMAC_RMII; +#endif + at91mac->EMAC_CTL |= AT91C_EMAC_TE | AT91C_EMAC_RE; + + if (!at91emac_phy_init(netdev)) { + at91emac_UpdateLinkSpeed(at91mac); + return 0; + } + return 1; +} + +static void at91emac_halt(struct eth_device *netdev) +{ + AT91PS_EMAC at91mac; + + at91mac = (AT91PS_EMAC) netdev->iobase; + at91mac->EMAC_CTL &= ~(AT91C_EMAC_TE | AT91C_EMAC_RE); + DEBUG_AT91EAMC("halt MAC-ADDR \n"); +} + +static int at91emac_send(struct eth_device *netdev, volatile void *packet, + int length) +{ + AT91PS_EMAC at91mac; + at91mac = (AT91PS_EMAC) netdev->iobase; + while (!(at91mac->EMAC_TSR & AT91C_EMAC_BNQ)) + ; + at91mac->EMAC_TAR = (long) packet; + at91mac->EMAC_TCR = length; + while (at91mac->EMAC_TCR & 0x7ff) + ; + DEBUG_AT91EAMC("Send %d \n", length); + at91mac->EMAC_TSR |= AT91C_EMAC_COMP; + return 0; +} + +static int at91emac_recv(struct eth_device *netdev) +{ + emac_device *emac; + AT91PS_EMAC at91mac; + rbf_t *rbfp; + int size; + + at91mac = (AT91PS_EMAC) netdev->iobase; + emac = (emac_device *) netdev->priv; + + rbfp = &emac->rbfdt[emac->rbindex]; + while (rbfp->addr & RBF_OWNER) { + size = rbfp->size & RBF_SIZE; + NetReceive(NetRxPackets[emac->rbindex], size); + + DEBUG_AT91EAMC("Recv[%d]: %d bytes @ %x \n", + emac->rbindex, size, rbfp->addr); + + rbfp->addr &= ~RBF_OWNER; + rbfp->size = 0; + if (emac->rbindex < (RBF_FRAMEMAX-1)) + emac->rbindex++; + else + emac->rbindex = 0; + + rbfp = &(emac->rbfdt[emac->rbindex]); + if (!(rbfp->addr & RBF_OWNER)) + at91mac->EMAC_RSR |= AT91C_EMAC_REC; + } + + if (at91mac->EMAC_ISR & AT91C_EMAC_RBNA) { + /* EMAC silicon bug 41.3.1 workaround 1 */ + at91mac->EMAC_CTL &= ~AT91C_EMAC_RE; + at91mac->EMAC_CTL |= AT91C_EMAC_RE; + emac->rbindex = 0; + printf("%s: reset receiver (EMAC dead lock bug)\n", + netdev->name); + } + return 0; +} + +int at91emac_register(bd_t *bis, unsigned long iobase) +{ + emac_device *emac; + emac_device *emacfix; + struct eth_device *dev; + + emac = malloc(sizeof(*emac)+512); + if (emac == NULL) + return 1; + dev = malloc(sizeof(*dev)); + if (dev == NULL) { + free(emac); + return 1; + } + /* alignment as per Errata (64 bytes) is insufficient! */ + emacfix = (emac_device *) (((unsigned long) emac + 0x1ff) & 0xFFFFFE00); + memset(emacfix, 0, sizeof(emac_device)); + + memset(dev, 0, sizeof(*dev)); +#ifndef CONFIG_RMII + sprintf(dev->name, "AT91 EMAC"); +#else + sprintf(dev->name, "AT91 EMAC RMII"); +#endif + dev->iobase = iobase; + dev->priv = emacfix; + dev->init = at91emac_init; + dev->halt = at91emac_halt; + dev->send = at91emac_send; + dev->recv = at91emac_recv; + + eth_register(dev); + +#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) + miiphy_register(dev->name, at91emac_mii_read, at91emac_mii_write); +#endif + return 1; +} + diff --git a/include/netdev.h b/include/netdev.h index a91368e..f771105 100644 --- a/include/netdev.h +++ b/include/netdev.h @@ -41,6 +41,7 @@ int board_eth_init(bd_t *bis); int cpu_eth_init(bd_t *bis);
/* Driver initialization prototypes */ +int at91emac_register(bd_t *bis, unsigned long iobase); int au1x00_enet_initialize(bd_t*); int bfin_EMAC_initialize(bd_t *bis); int cs8900_initialize(u8 dev_num, int base_addr);

Dear Ben,
In message 4ACDDE30.8030107@bus-elektronik.de Jens Scharsig wrote:
This patch adds a new net api driver for AT91RM9200 EMAC
- adds NET_MULTI api EMAC (CONFIG_DRIVER_AT91EMAC)
- generic PHY initialization
Signed-off-by: Jens Scharsig esw@bus-elektronik.de
This patch was tested with full MII interface and LXT971 Phy on our upcomming new board. I have also tested the RMII interface with DM9161 Phy on a CARMEVA-board.
I have no u-boot supported AT91RM9200 boards.
My appeal to all AT91RM9200 boards maintainers: Please test
Changes to make your board use NET_MULTI api:
include/configs/<board>.h
- change CONFIG_DRIVER_ETHER to CONFIG_DRIVER_AT91EMAC
- define CONFIG_NET_MULTI
- set CONFIG_SYS_RX_ETH_BUFFER, if you want more than standard rx buffers
- define CONFIG_AT91C_USE_RMII, if you board use RMII interface
add following code to board file #ifdef CONFIG_DRIVER_AT91EMAC int board_eth_init(bd_t *bis) { int rc = 0; rc = at91emac_register(bis, AT91C_BASE_EMAC); return rc; } #endif
Could you please check the status of this patch: http://thread.gmane.org/gmane.comp.boot-loaders.u-boot/69484
Best regards,
Wolfgang Denk

Dear Wolfgang Denk, Dear Ben Warren,
I'm still working on a patch set for AT91 SoC access with c structures. This will include a reintroduce of this patch.
Best regards,
Jens Scharsig

Hi Jens,
On Sun, Dec 6, 2009 at 11:36 AM, Jens Scharsig js_at_ng@scharsoft.dewrote:
Dear Wolfgang Denk, Dear Ben Warren,
I'm still working on a patch set for AT91 SoC access with c structures. This will include a reintroduce of this patch.
That's great. After some time off I'm just getting back into things, so
this is good timing. A couple of comments:
1. Please add the file summary at the top of your patch (list of files modified/added etc.) git-format-patch takes care of this. 2. If you wish this to go in mainline, all boards that use this controller must also support your new implementation. In other words, you need to touch all include/configs/*.h and their respective board/foo/foo.c files, adding or modifying board_eth_init() calls. In the past I've been able to automate most of this with a simple shell script and can help you if you need it. Don't worry about whether you have hardware or not, especially if it's early in the release cycle.
Best regards,
Jens Scharsig
regards, Ben
participants (4)
-
Ben Warren
-
Jens Scharsig
-
Jens Scharsig
-
Wolfgang Denk