/* vxbYt8521Phy.c - driver for yt8521 10/100/1000 ethernet PHY chips */ /* * * This program is OPEN SOURCE software: you can redistribute it and/or modify it; * 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. * */ #include #include #include #include #include #include #include <../src/hwif/h/mii/miiBus.h> #include /* defines */ #undef YT8521PHY_DEBUG #ifdef YT8521PHY_DEBUG #define YT8521PHY_LOGMSG(fmt,p1,p2,p3,p4,p5,p6) logMsg(fmt,p1,p2,p3,p4,p5,p6) #else #define YT8521PHY_LOGMSG(fmt,p1,p2,p3,p4,p5,p6) #endif /* externs */ IMPORT BOOL autoNegForce; /* locals */ LOCAL STATUS ytPhyExtRead(VXB_DEVICE_ID ,UINT32 , UINT16*); LOCAL STATUS ytPhyExtWrite(VXB_DEVICE_ID ,UINT32 ,UINT16); LOCAL STATUS ytPhyConfigInit(VXB_DEVICE_ID pDev); LOCAL void ytPhyInit (VXB_DEVICE_ID); LOCAL STATUS ytPhyModeSet (VXB_DEVICE_ID, UINT32); LOCAL STATUS ytPhyModeGet (VXB_DEVICE_ID, UINT32 *, UINT32 *); LOCAL void ytPhyDevInstInit(VXB_DEVICE_ID pDev); LOCAL void ytPhyDevInstInit2(VXB_DEVICE_ID pDev); LOCAL void ytPhyDevInstConnect(VXB_DEVICE_ID pDev); LOCAL BOOL ytPhyProbe(VXB_DEVICE_ID pDev); LOCAL STATUS ytPhyInstUnlink (VXB_DEVICE_ID, void *); LOCAL device_method_t ytPhyMethods[] = { DEVMETHOD(miiModeGet, ytPhyModeGet), DEVMETHOD(miiModeSet, ytPhyModeSet), DEVMETHOD(vxbDrvUnlink, ytPhyInstUnlink), { 0, 0 } }; LOCAL struct drvBusFuncs ytPhyFuncs = { ytPhyDevInstInit, /* devInstanceInit */ ytPhyDevInstInit2, /* devInstanceInit2 */ ytPhyDevInstConnect /* devInstanceConnect */ }; LOCAL VXB_PARAMETERS ytPhyParamDefaults[] = { {"fcmode", VXB_PARAM_INT32, {(void *)MII_FCADV_NONE}}, {NULL, VXB_PARAM_END_OF_LIST, {NULL}} }; struct vxbDevRegInfo ytPhyDevRegistration = { NULL, /* pNext */ VXB_DEVID_DEVICE, /* devID */ VXB_BUSID_MII, /* busID = MII Bus */ VXB_VER_5_0_0, /* busVer */ "yt8521Phy", /* drvName */ &ytPhyFuncs, /* pDrvBusFuncs */ ytPhyMethods, /* pMethods */ ytPhyProbe, /* devProbe */ ytPhyParamDefaults /* parameter defaults */ }; void ytPhyRegister(void) { vxbDevRegister (&ytPhyDevRegistration); return; } LOCAL void ytPhyDevInstInit ( VXB_DEVICE_ID pDev ) { vxbNextUnitGet (pDev); return; } LOCAL void ytPhyDevInstConnect ( VXB_DEVICE_ID pDev ) { return; } /********************************************************************* * * ytPhyInstUnlink - 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 ytPhyInstUnlink ( 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); } /********************************************************************* * * ytPhyDevInstInit2 - 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 ytPhyDevInstInit2 ( VXB_DEVICE_ID pDev ) { VXB_DEVICE_ID pBus; MII_DRV_CTRL * pDrvCtrl; VXB_INST_PARAM_VALUE val; UINT16 miiSts; YT8521PHY_LOGMSG("ytPhyDevInstInit2(): entry for pDev: 0x%x\n", (int)pDev, 0,0,0,0,0); pDrvCtrl = (MII_DRV_CTRL *)pDev->pDrvCtrl; if (pDrvCtrl->miiInitialized == TRUE) { YT8521PHY_LOGMSG("ytPhyDevInstInit2(): 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. */ ytPhyInit (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; } /********************************************************************* * * ytPhyProbe - vxBus yt8521Phy probe handler * * This routine always returns TRUE. * * RETURNS: TRUE always. * * ERRNO: N/A */ LOCAL BOOL ytPhyProbe ( VXB_DEVICE_ID pDev ) { MII_DRV_CTRL * pDrvCtrl; UINT16 miiId1; UINT16 miiId2; pDrvCtrl = pDev->pDrvCtrl; miiId1 = pDrvCtrl->miiId1; miiId2 = pDrvCtrl->miiId2; if ((miiId1 == 0x0000)&&(miiId2==0x011a)) return (TRUE); return (FALSE); } LOCAL void ytPhyInit ( VXB_DEVICE_ID pDev ) { MII_DRV_CTRL * pDrvCtrl; UINT16 miiSts; UINT16 miiCtl; UINT16 miiVal; int i; pDrvCtrl = (MII_DRV_CTRL *)pDev->pDrvCtrl; ytPhyConfigInit(pDev); /* 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 ytPhyModeSet ( 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); } ytPhyInit (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 ytPhyModeGet ( 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; YT8521PHY_LOGMSG("ytPhyModeGet(): 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; YT8521PHY_LOGMSG("ytPhyModeGet(): 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; YT8521PHY_LOGMSG("ytPhyModeGet(): 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; YT8521PHY_LOGMSG("ytPhyModeGet(): pDev: 0x%x auto-neg off," " mode: 0x%x\n",(int)pDev,(int)*mode,0,0,0,0); } return (OK); } LOCAL STATUS ytPhyConfigInit ( VXB_DEVICE_ID pDev ) { STATUS ret; UINT16 miiVal; /* disable auto sleep */ ret = ytPhyExtRead(pDev,YT8521_EXTREG_SLEEP_CONTROL1,&miiVal); if (ret < 0) return ret; miiVal &= ~(1 << YT8521_EN_SLEEP_SW_BIT); ret = ytPhyExtWrite(pDev,YT8521_EXTREG_SLEEP_CONTROL1,miiVal); if (ret < 0) return ret; /* enable RXC clock when no wire plug */ ret = ytPhyExtWrite(pDev,YT8521_EXTREG_SMI_SDS_PHY,0); if (ret < 0) return ret; ret = ytPhyExtRead(pDev,YT8521_EXTREG_C,&miiVal); if (ret < 0) return ret; miiVal &= ~(1 << 12); ret = ytPhyExtWrite(pDev,YT8521_EXTREG_C,miiVal); return ret; } LOCAL STATUS ytPhyExtRead ( VXB_DEVICE_ID pDev, UINT32 reg, UINT16* val ) { STATUS ret; MII_DRV_CTRL * pDrvCtrl; pDrvCtrl = (MII_DRV_CTRL *)pDev->pDrvCtrl; ret = miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, REG_DEBUG_ADDR_OFFSET, reg); if (ret < 0) return ret; ret = miiBusRead (pDev, pDrvCtrl->miiPhyAddr, REG_DEBUG_DATA, val); return ret; } LOCAL STATUS ytPhyExtWrite ( VXB_DEVICE_ID pDev, UINT32 reg, UINT16 val ) { STATUS ret; MII_DRV_CTRL * pDrvCtrl; pDrvCtrl = (MII_DRV_CTRL *)pDev->pDrvCtrl; ret = miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, REG_DEBUG_ADDR_OFFSET, reg); if (ret < 0) return ret; ret = miiBusWrite (pDev, pDrvCtrl->miiPhyAddr, REG_DEBUG_DATA, val); return (ret); }