/* * ax88180: ASIX AX88180 Non-PCI Gigabit Ethernet u-boot driver * * This program is free software; you can distribute it and/or modify * it under the terms of the GNU General Public License (Version 2) as * published by the Free Software Foundation. * This program is distributed in the hope 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. */ /* * ======================================================================== * ASIX AX88180 Non-PCI 16/32-bit Gigabit Ethernet Linux Driver * * The AX88180 Ethernet controller is a high performance and highly * integrated local CPU bus Ethernet controller with embedded 40K bytes * SRAM and supports both 16-bit and 32-bit SRAM-Like interfaces for any * embedded systems. * The AX88180 is a single chip 10/100/1000Mbps Gigabit Ethernet * controller that supports both MII and RGMII interfaces and is * compliant to IEEE 802.3, IEEE 802.3u and IEEE 802.3z standards. * * Please visit ASIX's web site (http://www.asix.com.tw) for more * details. * * Module Name : ax88180.c * Date : 2008-07-07 * History * 09/06/2006 : New release for AX88180 US2 chip. * 07/07/2008 : Fix up the coding style and using inline functions * instead of macros * ======================================================================== */ #include #include #include #include #include #include "ax88180.h" /* * =========================================================================== * Local SubProgram Declaration * =========================================================================== */ static void ax88180_rx_handler (struct eth_device *dev); static int ax88180_phy_initial (struct eth_device *dev); static void ax88180_media_config (struct eth_device *dev); static unsigned long get_CicadaPHY_media_mode (struct eth_device *dev); static unsigned long get_MarvellPHY_media_mode (struct eth_device *dev); static unsigned short ax88180_mdio_read (struct eth_device *dev, unsigned long regaddr); static void ax88180_mdio_write (struct eth_device *dev, unsigned long regaddr, unsigned short regdata); /* * =========================================================================== * Local SubProgram Bodies * =========================================================================== */ static int ax88180_mdio_check_complete (struct eth_device *dev) { int us_cnt = 10000; unsigned short tmpval; /* MDIO read/write should not take more than 10 ms */ while (--us_cnt) { tmpval = INW (dev, MDIOCTRL); if (((tmpval & READ_PHY) == 0) && ((tmpval & WRITE_PHY) == 0)) break; } return us_cnt; } static unsigned short ax88180_mdio_read (struct eth_device *dev, unsigned long regaddr) { struct ax88180_private *priv = (struct ax88180_private *)dev->priv; unsigned long tmpval = 0; OUTW (dev, (READ_PHY | (regaddr << 8) | priv->PhyAddr), MDIOCTRL); if (ax88180_mdio_check_complete (dev)) tmpval = INW (dev, MDIODP); else printf ("Failed to read PHY register!\n"); return (unsigned short)(tmpval & 0xFFFF); } static void ax88180_mdio_write (struct eth_device *dev, unsigned long regaddr, unsigned short regdata) { struct ax88180_private *priv = (struct ax88180_private *)dev->priv; OUTW (dev, regdata, MDIODP); OUTW (dev, (WRITE_PHY | (regaddr << 8) | priv->PhyAddr), MDIOCTRL); if (!ax88180_mdio_check_complete (dev)) printf ("Failed to write PHY register!\n"); } static int ax88180_phy_reset (struct eth_device *dev) { unsigned short delay_cnt = 500; ax88180_mdio_write (dev, MII_BMCR, (BMCR_RESET | BMCR_ANENABLE)); /* Wait for the reset to complete, or time out (500 ms) */ while (ax88180_mdio_read (dev, MII_BMCR) & BMCR_RESET) { udelay (1000); if (--delay_cnt == 0) { printf ("Failed to reset PHY!\n"); return -1; } } return 0; } static void ax88180_mac_reset (struct eth_device *dev) { unsigned long tmpval; unsigned char i; struct { unsigned short offset, value; } program_seq[] = { { MISC, MISC_NORMAL}, { RXINDICATOR, DEFAULT_RXINDICATOR}, { TXCMD, DEFAULT_TXCMD}, { TXBS, DEFAULT_TXBS}, { TXDES0, DEFAULT_TXDES0}, { TXDES1, DEFAULT_TXDES1}, { TXDES2, DEFAULT_TXDES2}, { TXDES3, DEFAULT_TXDES3}, { TXCFG, DEFAULT_TXCFG}, { MACCFG2, DEFAULT_MACCFG2}, { MACCFG3, DEFAULT_MACCFG3}, { TXLEN, DEFAULT_TXLEN}, { RXBTHD0, DEFAULT_RXBTHD0}, { RXBTHD1, DEFAULT_RXBTHD1}, { RXFULTHD, DEFAULT_RXFULTHD}, { DOGTHD0, DEFAULT_DOGTHD0}, { DOGTHD1, DEFAULT_DOGTHD1},}; OUTW (dev, MISC_RESET_MAC, MISC); tmpval = INW (dev, MISC); for (i = 0; i < ARRAY_SIZE(program_seq); i++) OUTW (dev, program_seq[i].value, program_seq[i].offset); } static int ax88180_poll_tx_complete (struct eth_device *dev) { struct ax88180_private *priv = (struct ax88180_private *)dev->priv; unsigned long tmpval, txbs_txdp; int TimeOutCnt = 10000; txbs_txdp = 1 << priv->NextTxDesc; while (TimeOutCnt--) { tmpval = INW (dev, TXBS); if ((tmpval & txbs_txdp) == 0) break; udelay (100); } if (TimeOutCnt) return 0; else return -TimeOutCnt; } static void ax88180_rx_handler (struct eth_device *dev) { struct ax88180_private *priv = (struct ax88180_private *)dev->priv; unsigned long data_size; unsigned short rxcurt_ptr, rxbound_ptr, next_ptr; int i; #if defined (CONFIG_DRIVER_AX88180_16BIT) unsigned short *rxdata = (unsigned short *)NetRxPackets[0]; #else unsigned long *rxdata = (unsigned long *)NetRxPackets[0]; #endif unsigned short count; rxcurt_ptr = INW (dev, RXCURT); rxbound_ptr = INW (dev, RXBOUND); next_ptr = (rxbound_ptr + 1) & RX_PAGE_NUM_MASK; debug ("ax88180: RX original RXBOUND=0x%04x," " RXCURT=0x%04x\n", rxbound_ptr, rxcurt_ptr); while (next_ptr != rxcurt_ptr) { OUTW (dev, RX_START_READ, RXINDICATOR); data_size = READ_RXBUF (dev) & 0xFFFF; if ((data_size == 0) || (data_size > MAX_RX_SIZE)) { OUTW (dev, RX_STOP_READ, RXINDICATOR); ax88180_mac_reset (dev); printf ("ax88180: Invalid Rx packet length!" " (len=0x%04lx)\n", data_size); debug ("ax88180: RX RXBOUND=0x%04x," "RXCURT=0x%04x\n", rxbound_ptr, rxcurt_ptr); return; } rxbound_ptr += (((data_size + 0xF) & 0xFFF0) >> 4) + 1; rxbound_ptr &= RX_PAGE_NUM_MASK; /* Comput access times */ count = (data_size + priv->PadSize) >> priv->BusWidth; for (i = 0; i < count; i++) { *(rxdata + i) = READ_RXBUF (dev); } OUTW (dev, RX_STOP_READ, RXINDICATOR); /* Pass the packet up to the protocol layers. */ NetReceive (NetRxPackets[0], data_size); OUTW (dev, rxbound_ptr, RXBOUND); rxcurt_ptr = INW (dev, RXCURT); rxbound_ptr = INW (dev, RXBOUND); next_ptr = (rxbound_ptr + 1) & RX_PAGE_NUM_MASK; debug ("ax88180: RX updated RXBOUND=0x%04x," "RXCURT=0x%04x\n", rxbound_ptr, rxcurt_ptr); } return; } static int ax88180_phy_initial (struct eth_device *dev) { struct ax88180_private *priv = (struct ax88180_private *)dev->priv; unsigned long tmp_regval; unsigned short phyaddr; /* Search for first avaliable PHY chipset */ #ifdef CONFIG_PHY_ADDR phyaddr = CONFIG_PHY_ADDR; #else for (phyaddr = 0; phyaddr < 32; ++phyaddr) #endif { priv->PhyAddr = phyaddr; priv->PhyID0 = ax88180_mdio_read(dev, MII_PHYSID1); priv->PhyID1 = ax88180_mdio_read(dev, MII_PHYSID2); switch (priv->PhyID0) { case MARVELL_ALASKA_PHYSID0: debug("ax88180: Found Marvell Alaska PHY family." " (PHY Addr=0x%x)\n", priv->PhyAddr); switch (priv->PhyID1) { case MARVELL_88E1118_PHYSID1: ax88180_mdio_write(dev, M88E1118_PAGE_SEL, 2); ax88180_mdio_write(dev, M88E1118_CR, M88E1118_CR_DEFAULT); ax88180_mdio_write(dev, M88E1118_PAGE_SEL, 3); ax88180_mdio_write(dev, M88E1118_LEDCTL, M88E1118_LEDCTL_DEFAULT); ax88180_mdio_write(dev, M88E1118_LEDMIX, M88E1118_LEDMIX_LED050 | M88E1118_LEDMIX_LED150 | 0x15); ax88180_mdio_write(dev, M88E1118_PAGE_SEL, 0); default: /* Default to 88E1111 Phy */ tmp_regval = ax88180_mdio_read(dev, M88E1111_EXT_SSR); if ((tmp_regval & HWCFG_MODE_MASK) != RGMII_COPPER_MODE) ax88180_mdio_write(dev, M88E1111_EXT_SCR, DEFAULT_EXT_SCR); } if (ax88180_phy_reset(dev) < 0) return 0; ax88180_mdio_write(dev, M88_IER, LINK_CHANGE_INT); return 1; case CICADA_CIS8201_PHYSID0: debug("ax88180: Found CICADA CIS8201 PHY" " chipset. (PHY Addr=0x%x)\n", priv->PhyAddr); ax88180_mdio_write(dev, CIS_IMR, (CIS_INT_ENABLE | LINK_CHANGE_INT)); /* Set CIS_SMI_PRIORITY bit before force the media mode */ tmp_regval = ax88180_mdio_read(dev, CIS_AUX_CTRL_STATUS); tmp_regval &= ~CIS_SMI_PRIORITY; ax88180_mdio_write(dev, CIS_AUX_CTRL_STATUS, tmp_regval); return 1; case 0xffff: /* No PHY at this addr */ break; default: printf("ax88180: Unknown PHY chipset %#x at addr %#x\n", priv->PhyID0, priv->PhyAddr); break; } } printf("ax88180: Unknown PHY chipset!!\n"); return 0; } static void ax88180_media_config (struct eth_device *dev) { struct ax88180_private *priv = (struct ax88180_private *)dev->priv; unsigned long bmcr_val, bmsr_val; unsigned long rxcfg_val, maccfg0_val, maccfg1_val; unsigned long RealMediaMode; int i; /* Waiting 2 seconds for PHY link stable */ for (i = 0; i < 20000; i++) { bmsr_val = ax88180_mdio_read (dev, MII_BMSR); if (bmsr_val & BMSR_LSTATUS) { break; } udelay (100); } bmsr_val = ax88180_mdio_read (dev, MII_BMSR); debug ("ax88180: BMSR=0x%04x\n", (unsigned int)bmsr_val); if (bmsr_val & BMSR_LSTATUS) { bmcr_val = ax88180_mdio_read (dev, MII_BMCR); if (bmcr_val & BMCR_ANENABLE) { /* * Waiting for Auto-negotiation completion, this may * take up to 5 seconds. */ debug ("ax88180: Auto-negotiation is " "enabled. Waiting for NWay completion..\n"); for (i = 0; i < 50000; i++) { bmsr_val = ax88180_mdio_read (dev, MII_BMSR); if (bmsr_val & BMSR_ANEGCOMPLETE) { break; } udelay (100); } } else debug ("ax88180: Auto-negotiation is disabled.\n"); debug ("ax88180: BMCR=0x%04x, BMSR=0x%04x\n", (unsigned int)bmcr_val, (unsigned int)bmsr_val); /* Get real media mode here */ switch (priv->PhyID0) { case MARVELL_ALASKA_PHYSID0: RealMediaMode = get_MarvellPHY_media_mode(dev); break; case CICADA_CIS8201_PHYSID0: RealMediaMode = get_CicadaPHY_media_mode(dev); break; default: RealMediaMode = MEDIA_1000FULL; break; } priv->LinkState = INS_LINK_UP; switch (RealMediaMode) { case MEDIA_1000FULL: debug ("ax88180: 1000Mbps Full-duplex mode.\n"); rxcfg_val = RXFLOW_ENABLE | DEFAULT_RXCFG; maccfg0_val = TXFLOW_ENABLE | DEFAULT_MACCFG0; maccfg1_val = GIGA_MODE_EN | RXFLOW_EN | FULLDUPLEX | DEFAULT_MACCFG1; break; case MEDIA_1000HALF: debug ("ax88180: 1000Mbps Half-duplex mode.\n"); rxcfg_val = DEFAULT_RXCFG; maccfg0_val = DEFAULT_MACCFG0; maccfg1_val = GIGA_MODE_EN | DEFAULT_MACCFG1; break; case MEDIA_100FULL: debug ("ax88180: 100Mbps Full-duplex mode.\n"); rxcfg_val = RXFLOW_ENABLE | DEFAULT_RXCFG; maccfg0_val = SPEED100 | TXFLOW_ENABLE | DEFAULT_MACCFG0; maccfg1_val = RXFLOW_EN | FULLDUPLEX | DEFAULT_MACCFG1; break; case MEDIA_100HALF: debug ("ax88180: 100Mbps Half-duplex mode.\n"); rxcfg_val = DEFAULT_RXCFG; maccfg0_val = SPEED100 | DEFAULT_MACCFG0; maccfg1_val = DEFAULT_MACCFG1; break; case MEDIA_10FULL: debug ("ax88180: 10Mbps Full-duplex mode.\n"); rxcfg_val = RXFLOW_ENABLE | DEFAULT_RXCFG; maccfg0_val = TXFLOW_ENABLE | DEFAULT_MACCFG0; maccfg1_val = RXFLOW_EN | FULLDUPLEX | DEFAULT_MACCFG1; break; case MEDIA_10HALF: debug ("ax88180: 10Mbps Half-duplex mode.\n"); rxcfg_val = DEFAULT_RXCFG; maccfg0_val = DEFAULT_MACCFG0; maccfg1_val = DEFAULT_MACCFG1; break; default: debug ("ax88180: Unknow media mode.\n"); rxcfg_val = DEFAULT_RXCFG; maccfg0_val = DEFAULT_MACCFG0; maccfg1_val = DEFAULT_MACCFG1; priv->LinkState = INS_LINK_DOWN; break; } } else { rxcfg_val = DEFAULT_RXCFG; maccfg0_val = DEFAULT_MACCFG0; maccfg1_val = DEFAULT_MACCFG1; priv->LinkState = INS_LINK_DOWN; } OUTW (dev, rxcfg_val, RXCFG); OUTW (dev, maccfg0_val, MACCFG0); OUTW (dev, maccfg1_val, MACCFG1); return; } static unsigned long get_MarvellPHY_media_mode (struct eth_device *dev) { unsigned long m88_ssr; unsigned long MediaMode; m88_ssr = ax88180_mdio_read (dev, M88_SSR); switch (m88_ssr & SSR_MEDIA_MASK) { case SSR_1000FULL: MediaMode = MEDIA_1000FULL; break; case SSR_1000HALF: MediaMode = MEDIA_1000HALF; break; case SSR_100FULL: MediaMode = MEDIA_100FULL; break; case SSR_100HALF: MediaMode = MEDIA_100HALF; break; case SSR_10FULL: MediaMode = MEDIA_10FULL; break; case SSR_10HALF: MediaMode = MEDIA_10HALF; break; default: MediaMode = MEDIA_UNKNOWN; break; } return MediaMode; } static unsigned long get_CicadaPHY_media_mode (struct eth_device *dev) { unsigned long tmp_regval; unsigned long MediaMode; tmp_regval = ax88180_mdio_read (dev, CIS_AUX_CTRL_STATUS); switch (tmp_regval & CIS_MEDIA_MASK) { case CIS_1000FULL: MediaMode = MEDIA_1000FULL; break; case CIS_1000HALF: MediaMode = MEDIA_1000HALF; break; case CIS_100FULL: MediaMode = MEDIA_100FULL; break; case CIS_100HALF: MediaMode = MEDIA_100HALF; break; case CIS_10FULL: MediaMode = MEDIA_10FULL; break; case CIS_10HALF: MediaMode = MEDIA_10HALF; break; default: MediaMode = MEDIA_UNKNOWN; break; } return MediaMode; } static void ax88180_halt (struct eth_device *dev) { /* Disable AX88180 TX/RX functions */ OUTW (dev, WAKEMOD, CMD); } static int ax88180_init (struct eth_device *dev, bd_t * bd) { struct ax88180_private *priv = (struct ax88180_private *)dev->priv; unsigned short tmp_regval; ax88180_mac_reset (dev); /* Disable interrupt */ OUTW (dev, CLEAR_IMR, IMR); /* Disable AX88180 TX/RX functions */ OUTW (dev, WAKEMOD, CMD); /* Fill the MAC address */ tmp_regval = dev->enetaddr[0] | (((unsigned short)dev->enetaddr[1]) << 8); OUTW (dev, tmp_regval, MACID0); tmp_regval = dev->enetaddr[2] | (((unsigned short)dev->enetaddr[3]) << 8); OUTW (dev, tmp_regval, MACID1); tmp_regval = dev->enetaddr[4] | (((unsigned short)dev->enetaddr[5]) << 8); OUTW (dev, tmp_regval, MACID2); ax88180_media_config (dev); OUTW (dev, DEFAULT_RXFILTER, RXFILTER); /* Initial variables here */ priv->FirstTxDesc = TXDP0; priv->NextTxDesc = TXDP0; /* Check if there is any invalid interrupt status and clear it. */ OUTW (dev, INW (dev, ISR), ISR); /* Start AX88180 TX/RX functions */ OUTW (dev, (RXEN | TXEN | WAKEMOD), CMD); return 0; } /* Get a data block via Ethernet */ static int ax88180_recv (struct eth_device *dev) { unsigned short ISR_Status; unsigned short tmp_regval; /* Read and check interrupt status here. */ ISR_Status = INW (dev, ISR); while (ISR_Status) { /* Clear the interrupt status */ OUTW (dev, ISR_Status, ISR); debug ("\nax88180: The interrupt status = 0x%04x\n", ISR_Status); if (ISR_Status & ISR_PHY) { /* Read ISR register once to clear PHY interrupt bit */ tmp_regval = ax88180_mdio_read (dev, M88_ISR); ax88180_media_config (dev); } if ((ISR_Status & ISR_RX) || (ISR_Status & ISR_RXBUFFOVR)) { ax88180_rx_handler (dev); } /* Read and check interrupt status again */ ISR_Status = INW (dev, ISR); } return 0; } /* Send a data block via Ethernet. */ static int ax88180_send(struct eth_device *dev, void *packet, int length) { struct ax88180_private *priv = (struct ax88180_private *)dev->priv; unsigned short TXDES_addr; unsigned short txcmd_txdp, txbs_txdp; unsigned short tmp_data; int i; #if defined (CONFIG_DRIVER_AX88180_16BIT) volatile unsigned short *txdata = (volatile unsigned short *)packet; #else volatile unsigned long *txdata = (volatile unsigned long *)packet; #endif unsigned short count; if (priv->LinkState != INS_LINK_UP) { return 0; } priv->FirstTxDesc = priv->NextTxDesc; txbs_txdp = 1 << priv->FirstTxDesc; debug ("ax88180: TXDP%d is available\n", priv->FirstTxDesc); txcmd_txdp = priv->FirstTxDesc << 13; TXDES_addr = TXDES0 + (priv->FirstTxDesc << 2); OUTW (dev, (txcmd_txdp | length | TX_START_WRITE), TXCMD); /* Comput access times */ count = (length + priv->PadSize) >> priv->BusWidth; for (i = 0; i < count; i++) { WRITE_TXBUF (dev, *(txdata + i)); } OUTW (dev, txcmd_txdp | length, TXCMD); OUTW (dev, txbs_txdp, TXBS); OUTW (dev, (TXDPx_ENABLE | length), TXDES_addr); priv->NextTxDesc = (priv->NextTxDesc + 1) & TXDP_MASK; /* * Check the available transmit descriptor, if we had exhausted all * transmit descriptor ,then we have to wait for at least one free * descriptor */ txbs_txdp = 1 << priv->NextTxDesc; tmp_data = INW (dev, TXBS); if (tmp_data & txbs_txdp) { if (ax88180_poll_tx_complete (dev) < 0) { ax88180_mac_reset (dev); priv->FirstTxDesc = TXDP0; priv->NextTxDesc = TXDP0; printf ("ax88180: Transmit time out occurred!\n"); } } return 0; } static void ax88180_read_mac_addr (struct eth_device *dev) { unsigned short macid0_val, macid1_val, macid2_val; unsigned short tmp_regval; unsigned short i; /* Reload MAC address from EEPROM */ OUTW (dev, RELOAD_EEPROM, PROMCTRL); /* Waiting for reload eeprom completion */ for (i = 0; i < 500; i++) { tmp_regval = INW (dev, PROMCTRL); if ((tmp_regval & RELOAD_EEPROM) == 0) break; udelay (1000); } /* Get MAC addresses */ macid0_val = INW (dev, MACID0); macid1_val = INW (dev, MACID1); macid2_val = INW (dev, MACID2); if (((macid0_val | macid1_val | macid2_val) != 0) && ((macid0_val & 0x01) == 0)) { dev->enetaddr[0] = (unsigned char)macid0_val; dev->enetaddr[1] = (unsigned char)(macid0_val >> 8); dev->enetaddr[2] = (unsigned char)macid1_val; dev->enetaddr[3] = (unsigned char)(macid1_val >> 8); dev->enetaddr[4] = (unsigned char)macid2_val; dev->enetaddr[5] = (unsigned char)(macid2_val >> 8); } } /* =========================================================================== <<<<<< Exported SubProgram Bodies >>>>>> =========================================================================== */ int ax88180_initialize (bd_t * bis) { struct eth_device *dev; struct ax88180_private *priv; dev = (struct eth_device *)malloc (sizeof *dev); if (NULL == dev) return 0; memset (dev, 0, sizeof *dev); priv = (struct ax88180_private *)malloc (sizeof (*priv)); if (NULL == priv) return 0; memset (priv, 0, sizeof *priv); sprintf (dev->name, "ax88180"); dev->iobase = AX88180_BASE; dev->priv = priv; dev->init = ax88180_init; dev->halt = ax88180_halt; dev->send = ax88180_send; dev->recv = ax88180_recv; priv->BusWidth = BUS_WIDTH_32; priv->PadSize = 3; #if defined (CONFIG_DRIVER_AX88180_16BIT) OUTW (dev, (START_BASE >> 8), BASE); OUTW (dev, DECODE_EN, DECODE); priv->BusWidth = BUS_WIDTH_16; priv->PadSize = 1; #endif ax88180_mac_reset (dev); /* Disable interrupt */ OUTW (dev, CLEAR_IMR, IMR); /* Disable AX88180 TX/RX functions */ OUTW (dev, WAKEMOD, CMD); ax88180_read_mac_addr (dev); eth_register (dev); return ax88180_phy_initial (dev); }