diff options
author | Kuo-Jung Su <dantesu@faraday-tech.com> | 2013-05-07 14:33:31 +0800 |
---|---|---|
committer | Joe Hershberger <joe.hershberger@ni.com> | 2013-06-24 19:11:16 -0500 |
commit | c4775476d211a4be027e45c14ef961de7312d5f6 (patch) | |
tree | bee7a7f5ac8f35acfd7e0f34091d4b04ac9a5e46 /drivers/net/ftmac110.c | |
parent | 42a7cb50a96e95cdca26607727c6767876414ced (diff) | |
download | u-boot-imx-c4775476d211a4be027e45c14ef961de7312d5f6.zip u-boot-imx-c4775476d211a4be027e45c14ef961de7312d5f6.tar.gz u-boot-imx-c4775476d211a4be027e45c14ef961de7312d5f6.tar.bz2 |
net: add Faraday FTMAC110 10/100Mbps ethernet support
Faraday FTMAC110 10/100Mbps supports half-word data transfer for Linux.
However it has a weird DMA alignment issue:
(1) Tx DMA Buffer Address:
1 bytes aligned: Invalid
2 bytes aligned: O.K
4 bytes aligned: O.K
(2) Rx DMA Buffer Address:
1 bytes aligned: Invalid
2 bytes aligned: O.K
4 bytes aligned: Invalid!!!
Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
Cc: Joe Hershberger <joe.hershberger@gmail.com>
Cc: Tom Rini <trini@ti.com>
Diffstat (limited to 'drivers/net/ftmac110.c')
-rw-r--r-- | drivers/net/ftmac110.c | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/drivers/net/ftmac110.c b/drivers/net/ftmac110.c new file mode 100644 index 0000000..579dcc7 --- /dev/null +++ b/drivers/net/ftmac110.c @@ -0,0 +1,473 @@ +/* + * Faraday 10/100Mbps Ethernet Controller + * + * (C) Copyright 2010 Faraday Technology + * Dante Su <dantesu@faraday-tech.com> + * + * This file is released under the terms of GPL v2 and any later version. + * See the file COPYING in the root directory of the source tree for details. + */ + +#include <common.h> +#include <command.h> +#include <malloc.h> +#include <net.h> +#include <asm/errno.h> +#include <asm/io.h> +#include <asm/dma-mapping.h> + +#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) +#include <miiphy.h> +#endif + +#include "ftmac110.h" + +#define CFG_RXDES_NUM 8 +#define CFG_TXDES_NUM 2 +#define CFG_XBUF_SIZE 1536 + +#define CFG_MDIORD_TIMEOUT (CONFIG_SYS_HZ >> 1) /* 500 ms */ +#define CFG_MDIOWR_TIMEOUT (CONFIG_SYS_HZ >> 1) /* 500 ms */ +#define CFG_LINKUP_TIMEOUT (CONFIG_SYS_HZ << 2) /* 4 sec */ + +/* + * FTMAC110 DMA design issue + * + * Its DMA engine has a weird restriction that its Rx DMA engine + * accepts only 16-bits aligned address, 32-bits aligned is not + * acceptable. However this restriction does not apply to Tx DMA. + * + * Conclusion: + * (1) Tx DMA Buffer Address: + * 1 bytes aligned: Invalid + * 2 bytes aligned: O.K + * 4 bytes aligned: O.K (-> u-boot ZeroCopy is possible) + * (2) Rx DMA Buffer Address: + * 1 bytes aligned: Invalid + * 2 bytes aligned: O.K + * 4 bytes aligned: Invalid + */ + +struct ftmac110_chip { + void __iomem *regs; + uint32_t imr; + uint32_t maccr; + uint32_t lnkup; + uint32_t phy_addr; + + struct ftmac110_rxd *rxd; + ulong rxd_dma; + uint32_t rxd_idx; + + struct ftmac110_txd *txd; + ulong txd_dma; + uint32_t txd_idx; +}; + +static int ftmac110_reset(struct eth_device *dev); + +static uint16_t mdio_read(struct eth_device *dev, + uint8_t phyaddr, uint8_t phyreg) +{ + struct ftmac110_chip *chip = dev->priv; + struct ftmac110_regs __iomem *regs = chip->regs; + uint32_t tmp, ts; + uint16_t ret = 0xffff; + + tmp = PHYCR_READ + | (phyaddr << PHYCR_ADDR_SHIFT) + | (phyreg << PHYCR_REG_SHIFT); + + writel(tmp, ®s->phycr); + + for (ts = get_timer(0); get_timer(ts) < CFG_MDIORD_TIMEOUT; ) { + tmp = readl(®s->phycr); + if (tmp & PHYCR_READ) + continue; + break; + } + + if (tmp & PHYCR_READ) + printf("ftmac110: mdio read timeout\n"); + else + ret = (uint16_t)(tmp & 0xffff); + + return ret; +} + +static void mdio_write(struct eth_device *dev, + uint8_t phyaddr, uint8_t phyreg, uint16_t phydata) +{ + struct ftmac110_chip *chip = dev->priv; + struct ftmac110_regs __iomem *regs = chip->regs; + uint32_t tmp, ts; + + tmp = PHYCR_WRITE + | (phyaddr << PHYCR_ADDR_SHIFT) + | (phyreg << PHYCR_REG_SHIFT); + + writel(phydata, ®s->phydr); + writel(tmp, ®s->phycr); + + for (ts = get_timer(0); get_timer(ts) < CFG_MDIOWR_TIMEOUT; ) { + if (readl(®s->phycr) & PHYCR_WRITE) + continue; + break; + } + + if (readl(®s->phycr) & PHYCR_WRITE) + printf("ftmac110: mdio write timeout\n"); +} + +static uint32_t ftmac110_phyqry(struct eth_device *dev) +{ + ulong ts; + uint32_t maccr; + uint16_t pa, tmp, bmsr, bmcr; + struct ftmac110_chip *chip = dev->priv; + + /* Default = 100Mbps Full */ + maccr = MACCR_100M | MACCR_FD; + + /* 1. find the phy device */ + for (pa = 0; pa < 32; ++pa) { + tmp = mdio_read(dev, pa, MII_PHYSID1); + if (tmp == 0xFFFF || tmp == 0x0000) + continue; + chip->phy_addr = pa; + break; + } + if (pa >= 32) { + puts("ftmac110: phy device not found!\n"); + goto exit; + } + + /* 2. wait until link-up & auto-negotiation complete */ + chip->lnkup = 0; + bmcr = mdio_read(dev, chip->phy_addr, MII_BMCR); + ts = get_timer(0); + do { + bmsr = mdio_read(dev, chip->phy_addr, MII_BMSR); + chip->lnkup = (bmsr & BMSR_LSTATUS) ? 1 : 0; + if (!chip->lnkup) + continue; + if (!(bmcr & BMCR_ANENABLE) || (bmsr & BMSR_ANEGCOMPLETE)) + break; + } while (get_timer(ts) < CFG_LINKUP_TIMEOUT); + if (!chip->lnkup) { + puts("ftmac110: link down\n"); + goto exit; + } + if (!(bmcr & BMCR_ANENABLE)) + puts("ftmac110: auto negotiation disabled\n"); + else if (!(bmsr & BMSR_ANEGCOMPLETE)) + puts("ftmac110: auto negotiation timeout\n"); + + /* 3. derive MACCR */ + if ((bmcr & BMCR_ANENABLE) && (bmsr & BMSR_ANEGCOMPLETE)) { + tmp = mdio_read(dev, chip->phy_addr, MII_ADVERTISE); + tmp &= mdio_read(dev, chip->phy_addr, MII_LPA); + if (tmp & LPA_100FULL) /* 100Mbps full-duplex */ + maccr = MACCR_100M | MACCR_FD; + else if (tmp & LPA_100HALF) /* 100Mbps half-duplex */ + maccr = MACCR_100M; + else if (tmp & LPA_10FULL) /* 10Mbps full-duplex */ + maccr = MACCR_FD; + else if (tmp & LPA_10HALF) /* 10Mbps half-duplex */ + maccr = 0; + } else { + if (bmcr & BMCR_SPEED100) + maccr = MACCR_100M; + else + maccr = 0; + if (bmcr & BMCR_FULLDPLX) + maccr |= MACCR_FD; + } + +exit: + printf("ftmac110: %d Mbps, %s\n", + (maccr & MACCR_100M) ? 100 : 10, + (maccr & MACCR_FD) ? "Full" : "half"); + return maccr; +} + +static int ftmac110_reset(struct eth_device *dev) +{ + uint8_t *a; + uint32_t i, maccr; + struct ftmac110_chip *chip = dev->priv; + struct ftmac110_regs __iomem *regs = chip->regs; + + /* 1. MAC reset */ + writel(MACCR_RESET, ®s->maccr); + for (i = get_timer(0); get_timer(i) < 1000; ) { + if (readl(®s->maccr) & MACCR_RESET) + continue; + break; + } + if (readl(®s->maccr) & MACCR_RESET) { + printf("ftmac110: reset failed\n"); + return -ENXIO; + } + + /* 1-1. Init tx ring */ + for (i = 0; i < CFG_TXDES_NUM; ++i) { + /* owned by SW */ + chip->txd[i].ct[0] = 0; + } + chip->txd_idx = 0; + + /* 1-2. Init rx ring */ + for (i = 0; i < CFG_RXDES_NUM; ++i) { + /* owned by HW */ + chip->rxd[i].ct[0] = cpu_to_le32(FTMAC110_RXCT0_OWNER); + } + chip->rxd_idx = 0; + + /* 2. PHY status query */ + maccr = ftmac110_phyqry(dev); + + /* 3. Fix up the MACCR value */ + chip->maccr = maccr | MACCR_CRCAPD | MACCR_RXALL | MACCR_RXRUNT + | MACCR_RXEN | MACCR_TXEN | MACCR_RXDMAEN | MACCR_TXDMAEN; + + /* 4. MAC address setup */ + a = dev->enetaddr; + writel(a[1] | (a[0] << 8), ®s->mac[0]); + writel(a[5] | (a[4] << 8) | (a[3] << 16) + | (a[2] << 24), ®s->mac[1]); + + /* 5. MAC registers setup */ + writel(chip->rxd_dma, ®s->rxba); + writel(chip->txd_dma, ®s->txba); + /* interrupt at each tx/rx */ + writel(ITC_DEFAULT, ®s->itc); + /* no tx pool, rx poll = 1 normal cycle */ + writel(APTC_DEFAULT, ®s->aptc); + /* rx threshold = [6/8 fifo, 2/8 fifo] */ + writel(DBLAC_DEFAULT, ®s->dblac); + /* disable & clear all interrupt status */ + chip->imr = 0; + writel(ISR_ALL, ®s->isr); + writel(chip->imr, ®s->imr); + /* enable mac */ + writel(chip->maccr, ®s->maccr); + + return 0; +} + +static int ftmac110_probe(struct eth_device *dev, bd_t *bis) +{ + debug("ftmac110: probe\n"); + + if (ftmac110_reset(dev)) + return -1; + + return 0; +} + +static void ftmac110_halt(struct eth_device *dev) +{ + struct ftmac110_chip *chip = dev->priv; + struct ftmac110_regs __iomem *regs = chip->regs; + + writel(0, ®s->imr); + writel(0, ®s->maccr); + + debug("ftmac110: halt\n"); +} + +static int ftmac110_send(struct eth_device *dev, void *pkt, int len) +{ + struct ftmac110_chip *chip = dev->priv; + struct ftmac110_regs __iomem *regs = chip->regs; + struct ftmac110_txd *des; + + if (!chip->lnkup) + return 0; + + if (len <= 0 || len > CFG_XBUF_SIZE) { + printf("ftmac110: bad tx pkt len(%d)\n", len); + return 0; + } + + len = max(60, len); + + des = &chip->txd[chip->txd_idx]; + if (le32_to_cpu(des->ct[0]) & FTMAC110_TXCT0_OWNER) { + /* kick-off Tx DMA */ + writel(0xffffffff, ®s->txpd); + printf("ftmac110: out of txd\n"); + return 0; + } + + memcpy(des->vbuf, (void *)pkt, len); + dma_map_single(des->vbuf, len, DMA_TO_DEVICE); + + /* update len, fts and lts */ + des->ct[1] &= cpu_to_le32(FTMAC110_TXCT1_END); + des->ct[1] |= cpu_to_le32(FTMAC110_TXCT1_LEN(len) + | FTMAC110_TXCT1_FTS | FTMAC110_TXCT1_LTS); + + /* set owner bit and clear others */ + des->ct[0] = cpu_to_le32(FTMAC110_TXCT0_OWNER); + + /* kick-off Tx DMA */ + writel(0xffffffff, ®s->txpd); + + chip->txd_idx = (chip->txd_idx + 1) % CFG_TXDES_NUM; + + return len; +} + +static int ftmac110_recv(struct eth_device *dev) +{ + struct ftmac110_chip *chip = dev->priv; + struct ftmac110_rxd *des; + uint32_t ct0, len, rlen = 0; + uint8_t *buf; + + if (!chip->lnkup) + return 0; + + do { + des = &chip->rxd[chip->rxd_idx]; + ct0 = le32_to_cpu(des->ct[0]); + if (ct0 & FTMAC110_RXCT0_OWNER) + break; + + len = FTMAC110_RXCT0_LEN(ct0); + buf = des->vbuf; + + if (ct0 & FTMAC110_RXCT0_ERRMASK) { + printf("ftmac110: rx error\n"); + } else { + dma_map_single(buf, len, DMA_FROM_DEVICE); + NetReceive(buf, len); + rlen += len; + } + + /* owned by hardware */ + des->ct[0] = cpu_to_le32(FTMAC110_RXCT0_OWNER); + + chip->rxd_idx = (chip->rxd_idx + 1) % CFG_RXDES_NUM; + } while (0); + + return rlen; +} + +#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) + +static int ftmac110_mdio_read( + const char *devname, uint8_t addr, uint8_t reg, uint16_t *value) +{ + int ret = 0; + struct eth_device *dev; + + dev = eth_get_dev_by_name(devname); + if (dev == NULL) { + printf("%s: no such device\n", devname); + ret = -1; + } else { + *value = mdio_read(dev, addr, reg); + } + + return ret; +} + +static int ftmac110_mdio_write( + const char *devname, uint8_t addr, uint8_t reg, uint16_t value) +{ + int ret = 0; + struct eth_device *dev; + + dev = eth_get_dev_by_name(devname); + if (dev == NULL) { + printf("%s: no such device\n", devname); + ret = -1; + } else { + mdio_write(dev, addr, reg, value); + } + + return ret; +} + +#endif /* #if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) */ + +int ftmac110_initialize(bd_t *bis) +{ + int i, card_nr = 0; + struct eth_device *dev; + struct ftmac110_chip *chip; + + dev = malloc(sizeof(*dev) + sizeof(*chip)); + if (dev == NULL) { + panic("ftmac110: out of memory 1\n"); + return -1; + } + chip = (struct ftmac110_chip *)(dev + 1); + memset(dev, 0, sizeof(*dev) + sizeof(*chip)); + + sprintf(dev->name, "FTMAC110#%d", card_nr); + + dev->iobase = CONFIG_FTMAC110_BASE; + chip->regs = (void __iomem *)dev->iobase; + dev->priv = chip; + dev->init = ftmac110_probe; + dev->halt = ftmac110_halt; + dev->send = ftmac110_send; + dev->recv = ftmac110_recv; + + if (!eth_getenv_enetaddr_by_index("eth", card_nr, dev->enetaddr)) + eth_random_enetaddr(dev->enetaddr); + + /* allocate tx descriptors (it must be 16 bytes aligned) */ + chip->txd = dma_alloc_coherent( + sizeof(struct ftmac110_txd) * CFG_TXDES_NUM, &chip->txd_dma); + if (!chip->txd) + panic("ftmac110: out of memory 3\n"); + memset(chip->txd, 0, + sizeof(struct ftmac110_txd) * CFG_TXDES_NUM); + for (i = 0; i < CFG_TXDES_NUM; ++i) { + void *va = memalign(ARCH_DMA_MINALIGN, CFG_XBUF_SIZE); + if (!va) + panic("ftmac110: out of memory 4\n"); + chip->txd[i].vbuf = va; + chip->txd[i].buf = cpu_to_le32(virt_to_phys(va)); + chip->txd[i].ct[1] = 0; + chip->txd[i].ct[0] = 0; /* owned by SW */ + } + chip->txd[i - 1].ct[1] |= cpu_to_le32(FTMAC110_TXCT1_END); + chip->txd_idx = 0; + + /* allocate rx descriptors (it must be 16 bytes aligned) */ + chip->rxd = dma_alloc_coherent( + sizeof(struct ftmac110_rxd) * CFG_RXDES_NUM, &chip->rxd_dma); + if (!chip->rxd) + panic("ftmac110: out of memory 4\n"); + memset((void *)chip->rxd, 0, + sizeof(struct ftmac110_rxd) * CFG_RXDES_NUM); + for (i = 0; i < CFG_RXDES_NUM; ++i) { + void *va = memalign(ARCH_DMA_MINALIGN, CFG_XBUF_SIZE + 2); + if (!va) + panic("ftmac110: out of memory 5\n"); + /* it needs to be exactly 2 bytes aligned */ + va = ((uint8_t *)va + 2); + chip->rxd[i].vbuf = va; + chip->rxd[i].buf = cpu_to_le32(virt_to_phys(va)); + chip->rxd[i].ct[1] = cpu_to_le32(CFG_XBUF_SIZE); + chip->rxd[i].ct[0] = cpu_to_le32(FTMAC110_RXCT0_OWNER); + } + chip->rxd[i - 1].ct[1] |= cpu_to_le32(FTMAC110_RXCT1_END); + chip->rxd_idx = 0; + + eth_register(dev); + +#if defined(CONFIG_MII) || defined(CONFIG_CMD_MII) + miiphy_register(dev->name, ftmac110_mdio_read, ftmac110_mdio_write); +#endif + + card_nr++; + + return card_nr; +} |