/* genericPhy.c - driver for generic 10/100/1000 ethernet PHY chips */ /* * Copyright (c) 2005-2011, 2013-2017 Wind River Systems, Inc. * * The right to copy, distribute, modify or otherwise make use * of this software may be licensed only pursuant to the terms * of an applicable Wind River license agreement. */ /* modification history -------------------- 14mar17,myt fix link issue for 82574L and I210T (VXW6-86128) 27jun16,fao fix the genPhyInit routine. (VXW6-85481) 30oct15,wyt release pDrvCtl in genPhyInstUnlink(). (VXW6-84942) 24jul15,d_l add flow control advertise selection. (VXW6-84701) fix genPhyProbe comment. (VXW6-84685) 16mar15,p_x Use MII_CR_RESTART instead of reset to restart auto negotiation in genPhyModeSet(). (VXW6-84227) 02feb15,p_x Support turning off auto-negotiation. (VXW6-83965) 16mar14,xms add handle error status. (VXW6-28077) 20jan14,xms optimize the genPhyInit routine. (VXW6-38227) 01r,04sep13,xms fix NULL_RETURNS error. (WIND00414265) 01q,27feb13,y_y Add timeout check when phy reset. (WIND00404792) 01p,15mar11,x_z Remove workaround for MICREL KS8001 rev3 01o,14sep10,d_c Cleanup debug prints 01n,10sep10,d_c Add debug prints 01m,06aug10,x_z Add workaround for MICREL KS8001 rev3. 01l,09dec09,h_k increased VxBus version. 01k,05may08,tor update version 01k,21mar08,z_l Fix 1000M mode support error. (WIND00120712) 01j,20sep07,tor VXB_VERSION_3 01i,05jul07,wap Be sure to initialize BMCR register correctly in genPhyInit() 01h,25oct06,wap Detect gigE mode in modeSet routine when autoneg is disabled 01g,17oct06,pdg replaced VXB_METHOD_DRIVER_UNLINK with an address 01f,20jun06,wap Advertise manually set modes 01e,25may06,wap Add removal support 01d,31jan06,pdg updated vxbus version 01c,19jan06,wap Fix probe routine, use correct extended caps bit in status register 01b,05jan06,wap Swap InstInit2 and InstConnect methods 01a,15sep05,wap written */ /* DESCRIPTION This module implements generic PHY access routines for 10/100/1000 copper ethernet PHYs that comply with the 802.3u MII specification. Methods are provided to initialize the PHY, set the media mode and check the current media mode and link status. Ideally, this driver should work with any 10/100 MII PHY. In practice, this isn't always feasible. Some chips have quirks that require the use of custom code. Also, the MII spec does not cover the use of interrupts. Not all PHYs are capable of generating interrupts, but those that do must be configured through vendor-specific registers, meaning that providing generic interrupt methods is pretty much impossible. The methods are accessed by calling the genPhyMethodsGet() routine, which returns a pointer to a phyFuncs structure that contains function pointers to the method routines. The functions can be invoked through convenience macros declared in phyLib.h. The initialization method resets always resets the PHY to a known state, usually by toggling the power down bit in the control register and then setting the reset bit. Then it programs the PHY for autonegotiation. Chip-specific drivers may also do additional setup or fixups if necessary. The mode set routine can be used to program the PHY for a specific media configuration. The mode word is based on the generic ifmedia mode definitions, i.e. IFM_AUTO, IFM_100_TX, IFM_10_T, IFM_FDX and IFM_HDX. Selecting a specific mode (other than auto) programs the PHY for just that mode. This can be used to force the chip to run at 10, 100Mbps or 1000Mbps and full or half duplex. The mode get routine retrieves the current link speed and duplex mode (i.e. what the link setting is right now) and the link state. If the IFM_ACTIVE bit is not set in the link state, then the mode information is set to IFM_NONE (no link available). The interrupt control method can be used to turn interrupts for link state change events on or off. Interrupts may be generated for duplex, speed and link state changes. The interrupt acknowledge method acks any pending interrupts so that the chip will de-assert its interrupt pin. By default, this driver doesn't advertise the flow control ability when do auto-negotiation. But this can be overrided by adding a parameter to the VXB_INST_PARAM_OVERRIDE table in hwconf.c { "genericPhy", 0, "fcmode", VXB_PARAM_INT32, {(void *)} } The selection can be MII_FCADV_NONE, MII_FCADV_PAUSE, MII_FCADV_ASM, MII_FCADV_PAUSE_ASM. */ #include #include #include #include #include #include #include "miiBus.h" #include <../src/hwif/h/mii/genericPhy.h> /* defines */ #undef GENERICPHY_DEBUG #ifdef GENERICPHY_DEBUG #define GENERICPHY_LOGMSG(fmt,p1,p2,p3,p4,p5,p6) logMsg(fmt,p1,p2,p3,p4,p5,p6) #else #define GENERICPHY_LOGMSG(fmt,p1,p2,p3,p4,p5,p6) #endif /* externs */ IMPORT BOOL autoNegForce; /* locals */ LOCAL void genPhyInit (VXB_DEVICE_ID); LOCAL STATUS genPhyModeSet (VXB_DEVICE_ID, UINT32); LOCAL STATUS genPhyModeGet (VXB_DEVICE_ID, UINT32 *, UINT32 *); LOCAL STATUS genPhyIntCtl (VXB_DEVICE_ID, int); LOCAL STATUS genPhyIntAck (VXB_DEVICE_ID); LOCAL void genPhyDevInstInit(VXB_DEVICE_ID pDev); LOCAL void genPhyDevInstInit2(VXB_DEVICE_ID pDev); LOCAL void genPhyDevInstConnect(VXB_DEVICE_ID pDev); LOCAL BOOL genPhyProbe(VXB_DEVICE_ID pDev); LOCAL STATUS genPhyInstUnlink (VXB_DEVICE_ID, void *); LOCAL device_method_t genPhyMethods[] = { DEVMETHOD(miiModeGet, genPhyModeGet), DEVMETHOD(miiModeSet, genPhyModeSet), DEVMETHOD(vxbDrvUnlink, genPhyInstUnlink), { 0, 0 } }; LOCAL struct drvBusFuncs genPhyFuncs = { genPhyDevInstInit, /* devInstanceInit */ genPhyDevInstInit2, /* devInstanceInit2 */ genPhyDevInstConnect /* devInstanceConnect */ }; LOCAL VXB_PARAMETERS genPhyParamDefaults[] = { {"fcmode", VXB_PARAM_INT32, {(void *)MII_FCADV_NONE}}, {NULL, VXB_PARAM_END_OF_LIST, {NULL}} }; struct vxbDevRegInfo genPhyDevRegistration = { NULL, /* pNext */ VXB_DEVID_DEVICE, /* devID */ VXB_BUSID_MII, /* busID = MII Bus */ VXB_VER_5_0_0, /* busVer */ "genericPhy", /* drvName */ &genPhyFuncs, /* pDrvBusFuncs */ genPhyMethods, /* pMethods */ genPhyProbe, /* devProbe */ genPhyParamDefaults /* parameter defaults */ }; void genPhyRegister(void) { vxbDevRegister (&genPhyDevRegistration); return; } LOCAL void genPhyDevInstInit ( VXB_DEVICE_ID pDev ) { vxbNextUnitGet (pDev); return; } LOCAL void genPhyDevInstConnect ( VXB_DEVICE_ID pDev ) { return; } /********************************************************************* * * genPhyInstUnlink - VxBus unlink handler * * This function implements the VxBus unlink method for this driver. * We delete each media type that was originally added to the MII bus * instance by this device and take ourselves off the miiMonitor task * list. * * RETURNS: OK if shutdown succeeds, else ERROR * * ERRNO: N/A */ LOCAL STATUS genPhyInstUnlink ( VXB_DEVICE_ID pDev, void * unused ) { VXB_DEVICE_ID pBus; MII_DRV_CTRL * pDrvCtrl; UINT16 miiSts; pDrvCtrl = (MII_DRV_CTRL *)pDev->pDrvCtrl; if (pDrvCtrl->miiInitialized == FALSE) return (ERROR); /* Only our parent bus can delete us. */ if (pDrvCtrl->miiLeaving == FALSE) return (ERROR); /* Remove ourselves from the miiMonitor task list. */ miiBusListDel (pDev); /* Remove media list entries. */ if ((pBus = vxbDevParent (pDev)) == NULL) return (ERROR); miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_STAT_REG, &miiSts); if (miiSts & MII_SR_EXT_STS) { miiBusMediaDel (pBus, IFM_ETHER|IFM_1000_T); miiBusMediaDel (pBus, IFM_ETHER|IFM_1000_T|IFM_FDX); } if (miiSts & MII_SR_TX_HALF_DPX) miiBusMediaDel (pBus, IFM_ETHER|IFM_100_TX); if (miiSts & MII_SR_TX_FULL_DPX) miiBusMediaDel (pBus, IFM_ETHER|IFM_100_TX|IFM_FDX); if (miiSts & MII_SR_10T_HALF_DPX) miiBusMediaDel (pBus, IFM_ETHER|IFM_10_T); if (miiSts & MII_SR_10T_FULL_DPX) miiBusMediaDel (pBus, IFM_ETHER|IFM_10_T|IFM_FDX); if (miiSts & MII_SR_AUTO_SEL) miiBusMediaDel (pBus, IFM_ETHER|IFM_AUTO); pDrvCtrl->miiInitialized = FALSE; free (pDrvCtrl); return (OK); } /********************************************************************* * * genPhyDevInstInit2 - vxBus instInit2 handler * * This routine does the final driver setup. The PHY registers * its media types with its parent bus and adds itself to the MII * monitoring list. * * RETURNS: N/A * * ERRNO: N/A */ LOCAL void genPhyDevInstInit2 ( VXB_DEVICE_ID pDev ) { VXB_DEVICE_ID pBus; MII_DRV_CTRL * pDrvCtrl; VXB_INST_PARAM_VALUE val; UINT16 miiSts; GENERICPHY_LOGMSG("genPhyDevInstInit2(): entry for pDev: 0x%x\n", (int)pDev, 0,0,0,0,0); pDrvCtrl = (MII_DRV_CTRL *)pDev->pDrvCtrl; if (pDrvCtrl->miiInitialized == TRUE) { GENERICPHY_LOGMSG("genPhyDevInstInit2(): already initialized\n", 0,0,0,0,0,0); return; } pDrvCtrl->miiInitialized = TRUE; /* * Tell miiBus about the media we support. */ if ((pBus = vxbDevParent (pDev)) == NULL) return; miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_STAT_REG, &miiSts); if (miiSts & MII_SR_EXT_STS) { miiBusMediaAdd (pBus, IFM_ETHER|IFM_1000_T); miiBusMediaAdd (pBus, IFM_ETHER|IFM_1000_T|IFM_FDX); } if (miiSts & MII_SR_TX_HALF_DPX) miiBusMediaAdd (pBus, IFM_ETHER|IFM_100_TX); if (miiSts & MII_SR_TX_FULL_DPX) miiBusMediaAdd (pBus, IFM_ETHER|IFM_100_TX|IFM_FDX); if (miiSts & MII_SR_10T_HALF_DPX) miiBusMediaAdd (pBus, IFM_ETHER|IFM_10_T); if (miiSts & MII_SR_10T_FULL_DPX) miiBusMediaAdd (pBus, IFM_ETHER|IFM_10_T|IFM_FDX); if (miiSts & MII_SR_AUTO_SEL) miiBusMediaAdd (pBus, IFM_ETHER|IFM_AUTO); miiBusMediaDefaultSet (pBus, IFM_ETHER|IFM_AUTO); /* * Initialize the PHY. This may perform DSP code * tweaking as needed. */ genPhyInit (pDev); /* Add to the monitor list. */ miiBusListAdd (pDev); /* * paramDesc { * The fcmode parameter specifies how we advertise * the device flow control ability. The selection can be * MII_FCADV_NONE, MII_FCADV_PAUSE, MII_FCADV_ASM, * MII_FCADV_PAUSE_ASM. } */ if (vxbInstParamByNameGet (pDev, "fcmode", VXB_PARAM_INT32, &val) == OK) { pDrvCtrl->fcmode = (UINT16) (val.int32Val); } else pDrvCtrl->fcmode = (UINT16) MII_FCADV_NONE; return; } /********************************************************************* * * genPhyProbe - vxBus genericPhy probe handler * * This routine always returns TRUE. * * RETURNS: TRUE always. * * ERRNO: N/A */ LOCAL BOOL genPhyProbe ( VXB_DEVICE_ID pDev ) { return (TRUE); } LOCAL void genPhyInit ( VXB_DEVICE_ID pDev ) { MII_DRV_CTRL * pDrvCtrl; UINT16 miiSts; UINT16 miiCtl; UINT16 miiVal; int i; pDrvCtrl = (MII_DRV_CTRL *)pDev->pDrvCtrl; /* Get status register so we can look for extended capabilities. */ miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_STAT_REG, &miiSts); miiVal = MII_CR_POWER_DOWN; miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, MII_CTRL_REG, miiVal); miiVal = 0; miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, MII_CTRL_REG, miiVal); /* Set reset bit and then wait for it to clear. */ miiVal = MII_CR_RESET; miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, MII_CTRL_REG, miiVal); for (i = 0; i < 1000; i++) { miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_CTRL_REG, &miiCtl); if (!(miiCtl & MII_CR_RESET)) break; } if (i == 1000) { return; } /* * If the extended capabilities bit is set, this is a gigE * PHY, so make sure we advertise gigE modes. */ if (miiSts & MII_SR_EXT_STS) { /* Enable advertisement of gigE modes. */ miiVal = MII_MASSLA_CTRL_1000T_FD|MII_MASSLA_CTRL_1000T_HD; miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, MII_MASSLA_CTRL_REG, miiVal); } /* * Some PHYs come out of reset with their isolate bit set. Make * sure we don't write that bit back when setting the control * register. */ miiCtl = MII_CR_AUTO_EN|MII_CR_RESTART; miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, MII_CTRL_REG, miiCtl); return; } LOCAL STATUS genPhyModeSet ( VXB_DEVICE_ID pDev, UINT32 mode ) { MII_DRV_CTRL * pDrvCtrl; UINT16 miiVal; UINT16 miiAnar = 0; UINT16 gmiiAnar = 0; UINT16 miiCtl = 0; UINT16 miiSts; BOOL autoneg = TRUE; pDrvCtrl = (MII_DRV_CTRL *)pDev->pDrvCtrl; /* Get status register so we can look for extended capabilities. */ miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_STAT_REG, &miiSts); switch(IFM_SUBTYPE(mode)) { case IFM_AUTO: /* Set autoneg advertisement to advertise all modes. */ miiAnar = MII_ANAR_10TX_HD|MII_ANAR_10TX_FD| MII_ANAR_100TX_HD|MII_ANAR_100TX_FD; if (miiSts & MII_SR_EXT_STS) gmiiAnar = MII_MASSLA_CTRL_1000T_FD|MII_MASSLA_CTRL_1000T_HD; miiCtl = MII_CR_AUTO_EN|MII_CR_RESTART; break; case IFM_1000_T: /* Auto-negotiation is mandatory per IEEE in 1000BASE-T. */ if (!(miiSts & MII_SR_EXT_STS)) return(ERROR); if ((mode & IFM_GMASK) == IFM_FDX) gmiiAnar = MII_MASSLA_CTRL_1000T_FD; else gmiiAnar = MII_MASSLA_CTRL_1000T_HD; miiCtl = MII_CR_AUTO_EN|MII_CR_RESTART; break; case IFM_100_TX: if (autoNegForce) { miiCtl = MII_CR_100|MII_CR_AUTO_EN|MII_CR_RESTART; if ((mode & IFM_GMASK) == IFM_FDX) { miiAnar = MII_ANAR_100TX_FD; miiCtl |= MII_CR_FDX; } else miiAnar = MII_ANAR_100TX_HD; } else { autoneg = FALSE; /* * Intel's PHY requires restarting auto-negotiation * or software reset in order for speed/duplex changes * to take effect. Use restarting auto-neg here. */ miiCtl = MII_CR_100|MII_CR_RESTART; if ((mode & IFM_GMASK) == IFM_FDX) miiCtl |= MII_CR_FDX; } break; case IFM_10_T: if (autoNegForce) { miiCtl = MII_CR_AUTO_EN|MII_CR_RESTART; if ((mode & IFM_GMASK) == IFM_FDX) { miiAnar = MII_ANAR_10TX_FD; miiCtl |= MII_CR_FDX; } else miiAnar = MII_ANAR_10TX_HD; } else { autoneg = FALSE; miiCtl = MII_CR_RESTART; if ((mode & IFM_GMASK) == IFM_FDX) miiCtl |= MII_CR_FDX; } break; default: return (ERROR); } genPhyInit (pDev); /* set flow control advertise ability */ miiAnar |= (pDrvCtrl->fcmode) << MII_ANAR_PAUSE_SHIFT; if (autoneg) { miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_AN_ADS_REG, &miiVal); miiVal &= ~(MII_ANAR_10TX_HD|MII_ANAR_10TX_FD| MII_ANAR_100TX_HD|MII_ANAR_100TX_FD); miiVal |= miiAnar; miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, MII_AN_ADS_REG, miiVal); if (miiSts & MII_SR_EXT_STS) { miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_MASSLA_CTRL_REG, &miiVal); miiVal &= ~(MII_MASSLA_CTRL_1000T_HD|MII_MASSLA_CTRL_1000T_FD); miiVal |= gmiiAnar; miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, MII_MASSLA_CTRL_REG, miiVal); } } miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_CTRL_REG, &miiVal); miiVal &= ~(MII_CR_FDX|MII_CR_100|MII_CR_1000|MII_CR_AUTO_EN|MII_CR_RESTART); miiVal |= miiCtl; miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, MII_CTRL_REG, miiVal); return(OK); } LOCAL STATUS genPhyModeGet ( VXB_DEVICE_ID pDev, UINT32 * mode, UINT32 * status ) { UINT16 miiSts; UINT16 miiCtl; UINT16 miiAnar; UINT16 miiLpar; UINT16 gmiiAnar = 0; UINT16 gmiiLpar = 0; UINT16 anlpar; MII_DRV_CTRL * pDrvCtrl; pDrvCtrl = (MII_DRV_CTRL *)pDev->pDrvCtrl; *mode = IFM_ETHER; *status = IFM_AVALID; GENERICPHY_LOGMSG("genPhyModeGet(): entry\n", 0,0,0,0,0,0); /* read MII status register once to unlatch link status bit */ miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_STAT_REG, &miiSts); /* read again to know its current value */ miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_STAT_REG, &miiSts); /* no link bit means no carrier. */ if (!(miiSts & MII_SR_LINK_STATUS) || (miiSts == 0xFFFF)) { *mode |= IFM_NONE; GENERICPHY_LOGMSG("genPhyModeGet(): pDev: 0x%x no carrier\n", (int)pDev,0,0,0,0,0); return (OK); } *status |= IFM_ACTIVE; /* * read the control, ability advertisement and link * partner advertisement registers. */ miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_CTRL_REG, &miiCtl); miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_AN_ADS_REG, &miiAnar); miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_AN_PRTN_REG, &miiLpar); if (miiSts & MII_SR_EXT_STS) { miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_MASSLA_CTRL_REG, &gmiiAnar); miiBusRead (pDev, pDrvCtrl->miiPhyAddr, MII_MASSLA_STAT_REG, &gmiiLpar); } /* * If autoneg is on, figure out the link settings from the * advertisement and partner ability registers. If autoneg is * off, use the settings in the control register. */ if (miiCtl & MII_CR_AUTO_EN) { anlpar = miiAnar & miiLpar; if ((gmiiAnar & MII_MASSLA_CTRL_1000T_FD) && \ (gmiiLpar & MII_MASSLA_STAT_LP1000T_FD)) *mode |= IFM_1000_T|IFM_FDX; else if ((gmiiAnar & MII_MASSLA_CTRL_1000T_HD) && \ (gmiiLpar & MII_MASSLA_STAT_LP1000T_HD)) *mode |= IFM_1000_T|IFM_HDX; else if (anlpar & MII_ANAR_100TX_FD) *mode |= IFM_100_TX|IFM_FDX; else if (anlpar & MII_ANAR_100TX_HD) *mode |= IFM_100_TX|IFM_HDX; else if (anlpar & MII_ANAR_10TX_FD) *mode |= IFM_10_T|IFM_FDX; else if (anlpar & MII_ANAR_10TX_HD) *mode |= IFM_10_T|IFM_HDX; else *mode |= IFM_NONE; GENERICPHY_LOGMSG("genPhyModeGet(): pDev: 0x%x auto-neg ON," " mode: 0x%x\n", (int)pDev,(int)*mode,0,0,0,0); } else { if (miiCtl & MII_CR_FDX) *mode |= IFM_FDX; else *mode |= IFM_HDX; if ((miiCtl & (MII_CR_100 | MII_CR_1000)) == (MII_CR_100 | MII_CR_1000)) *mode |= IFM_1000_T; else if (miiCtl & MII_CR_100) *mode |= IFM_100_TX; else *mode |= IFM_10_T; GENERICPHY_LOGMSG("genPhyModeGet(): pDev: 0x%x auto-neg off," " mode: 0x%x\n",(int)pDev,(int)*mode,0,0,0,0); } return (OK); } /* * Enable or disable PHY interrupts. If no interrupts supported, * return ERROR. */ LOCAL STATUS genPhyIntCtl ( VXB_DEVICE_ID pDev, int ctl ) { return (ERROR); } /* * Ack PHY interrupts. Return OK if an interrupt was pending, error * if not. */ LOCAL STATUS genPhyIntAck ( VXB_DEVICE_ID pDev ) { return (ERROR); }