diff options
Diffstat (limited to 'drivers/net/designware.c')
-rw-r--r-- | drivers/net/designware.c | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/drivers/net/designware.c b/drivers/net/designware.c new file mode 100644 index 0000000000..d0d98277ea --- /dev/null +++ b/drivers/net/designware.c @@ -0,0 +1,531 @@ +/* + * (C) Copyright 2010 + * Vipin Kumar, ST Micoelectronics, vipin.kumar@st.com. + * + * 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 + */ + +/* + * Designware ethernet IP driver for u-boot + */ + +#include <common.h> +#include <miiphy.h> +#include <malloc.h> +#include <linux/err.h> +#include <asm/io.h> +#include "designware.h" + +static void tx_descs_init(struct eth_device *dev) +{ + struct dw_eth_dev *priv = dev->priv; + struct eth_dma_regs *dma_p = priv->dma_regs_p; + struct dmamacdescr *desc_table_p = &priv->tx_mac_descrtable[0]; + char *txbuffs = &priv->txbuffs[0]; + struct dmamacdescr *desc_p; + u32 idx; + + for (idx = 0; idx < CONFIG_TX_DESCR_NUM; idx++) { + desc_p = &desc_table_p[idx]; + desc_p->dmamac_addr = &txbuffs[idx * CONFIG_ETH_BUFSIZE]; + desc_p->dmamac_next = &desc_table_p[idx + 1]; + +#if defined(CONFIG_DW_ALTDESCRIPTOR) + desc_p->txrx_status &= ~(DESC_TXSTS_TXINT | DESC_TXSTS_TXLAST | + DESC_TXSTS_TXFIRST | DESC_TXSTS_TXCRCDIS | \ + DESC_TXSTS_TXCHECKINSCTRL | \ + DESC_TXSTS_TXRINGEND | DESC_TXSTS_TXPADDIS); + + desc_p->txrx_status |= DESC_TXSTS_TXCHAIN; + desc_p->dmamac_cntl = 0; + desc_p->txrx_status &= ~(DESC_TXSTS_MSK | DESC_TXSTS_OWNBYDMA); +#else + desc_p->dmamac_cntl = DESC_TXCTRL_TXCHAIN; + desc_p->txrx_status = 0; +#endif + } + + /* Correcting the last pointer of the chain */ + desc_p->dmamac_next = &desc_table_p[0]; + + writel((ulong)&desc_table_p[0], &dma_p->txdesclistaddr); +} + +static void rx_descs_init(struct eth_device *dev) +{ + struct dw_eth_dev *priv = dev->priv; + struct eth_dma_regs *dma_p = priv->dma_regs_p; + struct dmamacdescr *desc_table_p = &priv->rx_mac_descrtable[0]; + char *rxbuffs = &priv->rxbuffs[0]; + struct dmamacdescr *desc_p; + u32 idx; + + for (idx = 0; idx < CONFIG_RX_DESCR_NUM; idx++) { + desc_p = &desc_table_p[idx]; + desc_p->dmamac_addr = &rxbuffs[idx * CONFIG_ETH_BUFSIZE]; + desc_p->dmamac_next = &desc_table_p[idx + 1]; + + desc_p->dmamac_cntl = + (MAC_MAX_FRAME_SZ & DESC_RXCTRL_SIZE1MASK) | \ + DESC_RXCTRL_RXCHAIN; + + desc_p->txrx_status = DESC_RXSTS_OWNBYDMA; + } + + /* Correcting the last pointer of the chain */ + desc_p->dmamac_next = &desc_table_p[0]; + + writel((ulong)&desc_table_p[0], &dma_p->rxdesclistaddr); +} + +static void descs_init(struct eth_device *dev) +{ + tx_descs_init(dev); + rx_descs_init(dev); +} + +static int mac_reset(struct eth_device *dev) +{ + struct dw_eth_dev *priv = dev->priv; + struct eth_mac_regs *mac_p = priv->mac_regs_p; + struct eth_dma_regs *dma_p = priv->dma_regs_p; + + int timeout = CONFIG_MACRESET_TIMEOUT; + + writel(DMAMAC_SRST, &dma_p->busmode); + writel(MII_PORTSELECT, &mac_p->conf); + + do { + if (!(readl(&dma_p->busmode) & DMAMAC_SRST)) + return 0; + udelay(1000); + } while (timeout--); + + return -1; +} + +static int dw_write_hwaddr(struct eth_device *dev) +{ + struct dw_eth_dev *priv = dev->priv; + struct eth_mac_regs *mac_p = priv->mac_regs_p; + u32 macid_lo, macid_hi; + u8 *mac_id = &dev->enetaddr[0]; + + macid_lo = mac_id[0] + (mac_id[1] << 8) + \ + (mac_id[2] << 16) + (mac_id[3] << 24); + macid_hi = mac_id[4] + (mac_id[5] << 8); + + writel(macid_hi, &mac_p->macaddr0hi); + writel(macid_lo, &mac_p->macaddr0lo); + + return 0; +} + +static int dw_eth_init(struct eth_device *dev, bd_t *bis) +{ + struct dw_eth_dev *priv = dev->priv; + struct eth_mac_regs *mac_p = priv->mac_regs_p; + struct eth_dma_regs *dma_p = priv->dma_regs_p; + u32 conf; + + /* Reset ethernet hardware */ + if (mac_reset(dev) < 0) + return -1; + + writel(FIXEDBURST | PRIORXTX_41 | BURST_16, + &dma_p->busmode); + + writel(FLUSHTXFIFO | readl(&dma_p->opmode), &dma_p->opmode); + writel(STOREFORWARD | TXSECONDFRAME, &dma_p->opmode); + + conf = FRAMEBURSTENABLE | DISABLERXOWN; + + if (priv->speed != SPEED_1000M) + conf |= MII_PORTSELECT; + + if (priv->duplex == FULL_DUPLEX) + conf |= FULLDPLXMODE; + + writel(conf, &mac_p->conf); + + descs_init(dev); + + /* + * Start/Enable xfer at dma as well as mac level + */ + writel(readl(&dma_p->opmode) | RXSTART, &dma_p->opmode); + writel(readl(&dma_p->opmode) | TXSTART, &dma_p->opmode); + + writel(readl(&mac_p->conf) | RXENABLE, &mac_p->conf); + writel(readl(&mac_p->conf) | TXENABLE, &mac_p->conf); + + return 0; +} + +static int dw_eth_send(struct eth_device *dev, volatile void *packet, + int length) +{ + struct dw_eth_dev *priv = dev->priv; + struct eth_dma_regs *dma_p = priv->dma_regs_p; + u32 desc_num = priv->tx_currdescnum; + struct dmamacdescr *desc_p = &priv->tx_mac_descrtable[desc_num]; + + /* Check if the descriptor is owned by CPU */ + if (desc_p->txrx_status & DESC_TXSTS_OWNBYDMA) { + printf("CPU not owner of tx frame\n"); + return -1; + } + + memcpy((void *)desc_p->dmamac_addr, (void *)packet, length); + +#if defined(CONFIG_DW_ALTDESCRIPTOR) + desc_p->txrx_status |= DESC_TXSTS_TXFIRST | DESC_TXSTS_TXLAST; + desc_p->dmamac_cntl |= (length << DESC_TXCTRL_SIZE1SHFT) & \ + DESC_TXCTRL_SIZE1MASK; + + desc_p->txrx_status &= ~(DESC_TXSTS_MSK); + desc_p->txrx_status |= DESC_TXSTS_OWNBYDMA; +#else + desc_p->dmamac_cntl |= ((length << DESC_TXCTRL_SIZE1SHFT) & \ + DESC_TXCTRL_SIZE1MASK) | DESC_TXCTRL_TXLAST | \ + DESC_TXCTRL_TXFIRST; + + desc_p->txrx_status = DESC_TXSTS_OWNBYDMA; +#endif + + /* Test the wrap-around condition. */ + if (++desc_num >= CONFIG_TX_DESCR_NUM) + desc_num = 0; + + priv->tx_currdescnum = desc_num; + + /* Start the transmission */ + writel(POLL_DATA, &dma_p->txpolldemand); + + return 0; +} + +static int dw_eth_recv(struct eth_device *dev) +{ + struct dw_eth_dev *priv = dev->priv; + u32 desc_num = priv->rx_currdescnum; + struct dmamacdescr *desc_p = &priv->rx_mac_descrtable[desc_num]; + + u32 status = desc_p->txrx_status; + int length = 0; + + /* Check if the owner is the CPU */ + if (!(status & DESC_RXSTS_OWNBYDMA)) { + + length = (status & DESC_RXSTS_FRMLENMSK) >> \ + DESC_RXSTS_FRMLENSHFT; + + NetReceive(desc_p->dmamac_addr, length); + + /* + * Make the current descriptor valid again and go to + * the next one + */ + desc_p->txrx_status |= DESC_RXSTS_OWNBYDMA; + + /* Test the wrap-around condition. */ + if (++desc_num >= CONFIG_RX_DESCR_NUM) + desc_num = 0; + } + + priv->rx_currdescnum = desc_num; + + return length; +} + +static void dw_eth_halt(struct eth_device *dev) +{ + struct dw_eth_dev *priv = dev->priv; + + mac_reset(dev); + priv->tx_currdescnum = priv->rx_currdescnum = 0; +} + +static int eth_mdio_read(struct eth_device *dev, u8 addr, u8 reg, u16 *val) +{ + struct dw_eth_dev *priv = dev->priv; + struct eth_mac_regs *mac_p = priv->mac_regs_p; + u32 miiaddr; + int timeout = CONFIG_MDIO_TIMEOUT; + + miiaddr = ((addr << MIIADDRSHIFT) & MII_ADDRMSK) | \ + ((reg << MIIREGSHIFT) & MII_REGMSK); + + writel(miiaddr | MII_CLKRANGE_150_250M | MII_BUSY, &mac_p->miiaddr); + + do { + if (!(readl(&mac_p->miiaddr) & MII_BUSY)) { + *val = readl(&mac_p->miidata); + return 0; + } + udelay(1000); + } while (timeout--); + + return -1; +} + +static int eth_mdio_write(struct eth_device *dev, u8 addr, u8 reg, u16 val) +{ + struct dw_eth_dev *priv = dev->priv; + struct eth_mac_regs *mac_p = priv->mac_regs_p; + u32 miiaddr; + int ret = -1, timeout = CONFIG_MDIO_TIMEOUT; + u16 value; + + writel(val, &mac_p->miidata); + miiaddr = ((addr << MIIADDRSHIFT) & MII_ADDRMSK) | \ + ((reg << MIIREGSHIFT) & MII_REGMSK) | MII_WRITE; + + writel(miiaddr | MII_CLKRANGE_150_250M | MII_BUSY, &mac_p->miiaddr); + + do { + if (!(readl(&mac_p->miiaddr) & MII_BUSY)) + ret = 0; + udelay(1000); + } while (timeout--); + + /* Needed as a fix for ST-Phy */ + eth_mdio_read(dev, addr, reg, &value); + + return ret; +} + +#if defined(CONFIG_DW_SEARCH_PHY) +static int find_phy(struct eth_device *dev) +{ + int phy_addr = 0; + u16 ctrl, oldctrl; + + do { + eth_mdio_read(dev, phy_addr, PHY_BMCR, &ctrl); + oldctrl = ctrl & PHY_BMCR_AUTON; + + ctrl ^= PHY_BMCR_AUTON; + eth_mdio_write(dev, phy_addr, PHY_BMCR, ctrl); + eth_mdio_read(dev, phy_addr, PHY_BMCR, &ctrl); + ctrl &= PHY_BMCR_AUTON; + + if (ctrl == oldctrl) { + phy_addr++; + } else { + ctrl ^= PHY_BMCR_AUTON; + eth_mdio_write(dev, phy_addr, PHY_BMCR, ctrl); + + return phy_addr; + } + } while (phy_addr < 32); + + return -1; +} +#endif + +static int dw_reset_phy(struct eth_device *dev) +{ + struct dw_eth_dev *priv = dev->priv; + u16 ctrl; + int timeout = CONFIG_PHYRESET_TIMEOUT; + u32 phy_addr = priv->address; + + eth_mdio_write(dev, phy_addr, PHY_BMCR, PHY_BMCR_RESET); + do { + eth_mdio_read(dev, phy_addr, PHY_BMCR, &ctrl); + if (!(ctrl & PHY_BMCR_RESET)) + break; + udelay(1000); + } while (timeout--); + + if (timeout < 0) + return -1; + +#ifdef CONFIG_PHY_RESET_DELAY + udelay(CONFIG_PHY_RESET_DELAY); +#endif + return 0; +} + +static int configure_phy(struct eth_device *dev) +{ + struct dw_eth_dev *priv = dev->priv; + int phy_addr; + u16 bmcr, ctrl; +#if defined(CONFIG_DW_AUTONEG) + u16 bmsr; + u32 timeout; + u16 anlpar, btsr; +#endif + +#if defined(CONFIG_DW_SEARCH_PHY) + phy_addr = find_phy(dev); + if (phy_addr > 0) + priv->address = phy_addr; + else + return -1; +#endif + if (dw_reset_phy(dev) < 0) + return -1; + +#if defined(CONFIG_DW_AUTONEG) + bmcr = PHY_BMCR_AUTON | PHY_BMCR_RST_NEG | PHY_BMCR_100MB | \ + PHY_BMCR_DPLX | PHY_BMCR_1000_MBPS; +#else + bmcr = PHY_BMCR_100MB | PHY_BMCR_DPLX; + +#if defined(CONFIG_DW_SPEED10M) + bmcr &= ~PHY_BMCR_100MB; +#endif +#if defined(CONFIG_DW_DUPLEXHALF) + bmcr &= ~PHY_BMCR_DPLX; +#endif +#endif + if (eth_mdio_write(dev, phy_addr, PHY_BMCR, bmcr) < 0) + return -1; + + /* Read the phy status register and populate priv structure */ +#if defined(CONFIG_DW_AUTONEG) + timeout = CONFIG_AUTONEG_TIMEOUT; + do { + eth_mdio_read(dev, phy_addr, PHY_BMSR, &bmsr); + if (bmsr & PHY_BMSR_AUTN_COMP) + break; + udelay(1000); + } while (timeout--); + + eth_mdio_read(dev, phy_addr, PHY_ANLPAR, &anlpar); + eth_mdio_read(dev, phy_addr, PHY_1000BTSR, &btsr); + + if (btsr & (PHY_1000BTSR_1000FD | PHY_1000BTSR_1000HD)) { + priv->speed = SPEED_1000M; + if (btsr & PHY_1000BTSR_1000FD) + priv->duplex = FULL_DUPLEX; + else + priv->duplex = HALF_DUPLEX; + } else { + if (anlpar & PHY_ANLPAR_100) + priv->speed = SPEED_100M; + else + priv->speed = SPEED_10M; + + if (anlpar & (PHY_ANLPAR_10FD | PHY_ANLPAR_TXFD)) + priv->duplex = FULL_DUPLEX; + else + priv->duplex = HALF_DUPLEX; + } +#else + if (eth_mdio_read(dev, phy_addr, PHY_BMCR, &ctrl) < 0) + return -1; + + if (ctrl & PHY_BMCR_DPLX) + priv->duplex = FULL_DUPLEX; + else + priv->duplex = HALF_DUPLEX; + + if (ctrl & PHY_BMCR_1000_MBPS) + priv->speed = SPEED_1000M; + else if (ctrl & PHY_BMCR_100_MBPS) + priv->speed = SPEED_100M; + else + priv->speed = SPEED_10M; +#endif + return 0; +} + +#if defined(CONFIG_MII) +static int dw_mii_read(char *devname, u8 addr, u8 reg, u16 *val) +{ + struct eth_device *dev; + + dev = eth_get_dev_by_name(devname); + if (dev) + eth_mdio_read(dev, addr, reg, val); + + return 0; +} + +static int dw_mii_write(char *devname, u8 addr, u8 reg, u16 val) +{ + struct eth_device *dev; + + dev = eth_get_dev_by_name(devname); + if (dev) + eth_mdio_write(dev, addr, reg, val); + + return 0; +} +#endif + +int designware_initialize(u32 id, ulong base_addr, u32 phy_addr) +{ + struct eth_device *dev; + struct dw_eth_dev *priv; + + dev = (struct eth_device *) malloc(sizeof(struct eth_device)); + if (!dev) + return -ENOMEM; + + /* + * Since the priv structure contains the descriptors which need a strict + * buswidth alignment, memalign is used to allocate memory + */ + priv = (struct dw_eth_dev *) memalign(16, sizeof(struct dw_eth_dev)); + if (!priv) { + free(dev); + return -ENOMEM; + } + + memset(dev, 0, sizeof(struct eth_device)); + memset(priv, 0, sizeof(struct dw_eth_dev)); + + sprintf(dev->name, "mii%d", id); + dev->iobase = (int)base_addr; + dev->priv = priv; + + eth_getenv_enetaddr_by_index(id, &dev->enetaddr[0]); + + priv->dev = dev; + priv->mac_regs_p = (struct eth_mac_regs *)base_addr; + priv->dma_regs_p = (struct eth_dma_regs *)(base_addr + + DW_DMA_BASE_OFFSET); + priv->address = phy_addr; + + if (mac_reset(dev) < 0) + return -1; + + if (configure_phy(dev) < 0) { + printf("Phy could not be configured\n"); + return -1; + } + + dev->init = dw_eth_init; + dev->send = dw_eth_send; + dev->recv = dw_eth_recv; + dev->halt = dw_eth_halt; + dev->write_hwaddr = dw_write_hwaddr; + + eth_register(dev); + +#if defined(CONFIG_MII) + miiphy_register(dev->name, dw_mii_read, dw_mii_write); +#endif + return 1; +} |