/** <at> file * Support for Marvell 88E1000 Series PHYs * * Copyright (c) 2011-2016, ARM Limited. All rights reserved. * * This program and the accompanying materials * are licensed and made available under the terms and conditions of the BSD License * which accompanies this distribution. The full text of the license may be found at * http://opensource.org/licenses/bsd-license.php * * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. * **/ /*- * Principal Author: Parag Patel * Copyright (c) 2001 * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Additonal Copyright (c) 2001 by Traakan Software under same licence. * Secondary Author: Matthew Jacob */ /* * driver for the Marvell 88E1000 series external 1000/100/10-BT PHY. */ /* * Support added for the Marvell 88E1011 (Alaska) 1000/100/10baseTX and * 1000baseSX PHY. * Nathan Binkert <nate <at> openbsd.org> * Jung-uk Kim <jkim <at> niksun.com> */ #include <Library/MemoryAllocationLib.h> #include <Library/BaseLib.h> #include <Library/DebugLib.h> #include <Library/UefiBootServicesTableLib.h> #include "if_media.h" #include "miivar.h" #include "e1000phyreg.h" static EFI_STATUS e1000phy_probe (const struct mii_attach_args *ma); STATIC VOID e1000phy_attach (struct e1000phy_softc *, const struct mii_attach_args *ma); STATIC VOID e1000phy_service (struct e1000phy_softc *, INTN); STATIC VOID e1000phy_status (struct e1000phy_softc *); STATIC VOID e1000phy_reset (struct e1000phy_softc *); STATIC VOID e1000phy_mii_phy_auto (struct e1000phy_softc *); INTN msk_phy_readreg (VOID *, INTN); INTN msk_phy_writereg (VOID *, INTN, INTN); VOID msk_miibus_statchg (VOID *); static const struct mii_phydesc * mii_phy_match (const struct mii_attach_args *ma, const struct mii_phydesc *mpd); static const struct mii_phydesc * mii_phy_match_gen (const struct mii_attach_args *ma, const struct mii_phydesc *mpd, UINTN endlen); static EFI_STATUS mii_phy_dev_probe (const struct mii_attach_args *ma, const struct mii_phydesc *mpd); STATIC VOID mii_phy_update (struct e1000phy_softc *, INTN); static const struct mii_phydesc e1000phys[] = { MII_PHY_DESC (MARVELL, E1000), MII_PHY_DESC (MARVELL, E1011), MII_PHY_DESC (MARVELL, E1000_3), MII_PHY_DESC (MARVELL, E1000S), MII_PHY_DESC (MARVELL, E1000_5), MII_PHY_DESC (MARVELL, E1000_6), MII_PHY_DESC (MARVELL, E3082), MII_PHY_DESC (MARVELL, E1112), MII_PHY_DESC (MARVELL, E1149), MII_PHY_DESC (MARVELL, E1111), MII_PHY_DESC (MARVELL, E1116), MII_PHY_DESC (MARVELL, E1116R), MII_PHY_DESC (MARVELL, E1118), MII_PHY_DESC (MARVELL, E3016), MII_PHY_DESC (MARVELL, PHYG65G), MII_PHY_DESC (xxMARVELL, E1000), MII_PHY_DESC (xxMARVELL, E1011), MII_PHY_DESC (xxMARVELL, E1000_3), MII_PHY_DESC (xxMARVELL, E1000_5), MII_PHY_DESC (xxMARVELL, E1111), MII_PHY_END }; EFI_STATUS e1000_probe_and_attach ( struct mii_data *mii, const struct msk_mii_data *mmd, VOID *sc_if, VOID **rsc_phy ) { struct mii_attach_args ma; INTN bmsr; EFI_STATUS Status; struct e1000phy_softc *sc_phy; Status = gBS->AllocatePool (EfiBootServicesData, sizeof (struct e1000phy_softc), (VOID**) &sc_phy); if (EFI_ERROR (Status)) { return Status; } gBS->SetMem (sc_phy, sizeof (struct e1000phy_softc), 0); sc_phy->mmd = mmd; sc_phy->sc_if = sc_if; /* * Check to see if there is a PHY at this address. Note, * many braindead PHYs report 0/0 in their ID registers, * so we test for media in the BMSR. */ bmsr = PHY_READ (sc_phy, E1000_SR); if (bmsr == 0 || bmsr == 0xffff || (bmsr & (E1000_SR_EXTENDED_STATUS|E1000_SR_MEDIAMASK)) == 0) { /* Assume no PHY at this address. */ gBS->FreePool (sc_phy); return EFI_DEVICE_ERROR; } /* * Extract the IDs. */ ma.mii_id1 = PHY_READ (sc_phy, E1000_ID1); ma.mii_id2 = PHY_READ (sc_phy, E1000_ID2); ma.mii_data = mii; Status = e1000phy_probe (&ma); if (EFI_ERROR (Status)) { gBS->FreePool (sc_phy); return Status; } e1000phy_attach (sc_phy, &ma); *rsc_phy = sc_phy; return EFI_SUCCESS; } VOID e1000phy_detach ( struct e1000phy_softc *sc_phy ) { if (sc_phy != NULL) { gBS->FreePool (sc_phy); } } EFI_STATUS e1000phy_probe ( const struct mii_attach_args *ma ) { return (mii_phy_dev_probe (ma, e1000phys)); } static void e1000phy_attach ( struct e1000phy_softc *sc_phy, const struct mii_attach_args *ma ) { struct mii_softc *sc; sc = &sc_phy->mii_sc; sc->mii_pdata = ma->mii_data; sc->mii_anegticks = MII_ANEGTICKS_GIGE; sc_phy->mii_model = MII_MODEL (ma->mii_id2); if (sc_phy->mmd != NULL && (sc_phy->mmd->mii_flags & MIIF_HAVEFIBER) != 0) { sc->mii_flags |= MIIF_HAVEFIBER; } switch (sc_phy->mii_model) { case MII_MODEL_MARVELL_E1011: case MII_MODEL_MARVELL_E1112: if (PHY_READ (sc_phy, E1000_ESSR) & E1000_ESSR_FIBER_LINK) { sc->mii_flags |= MIIF_HAVEFIBER; } break; case MII_MODEL_MARVELL_E1149: /* * Some 88E1149 PHY's page select is initialized to * point to other bank instead of copper/fiber bank * which in turn resulted in wrong registers were * accessed during PHY operation. It is believed that * page 0 should be used for copper PHY so reinitialize * E1000_EADR to select default copper PHY. If parent * device know the type of PHY(either copper or fiber), * that information should be used to select default * type of PHY. */ PHY_WRITE (sc_phy, E1000_EADR, 0); break; } e1000phy_reset (sc_phy); sc->mii_capabilities = PHY_READ (sc_phy, E1000_SR) & 0xFFFFFFFF; if (sc->mii_capabilities & E1000_SR_EXTENDED_STATUS) { sc->mii_extcapabilities = PHY_READ (sc_phy, E1000_ESR); } } static void e1000phy_reset ( struct e1000phy_softc *sc_phy ) { UINT16 reg; UINT16 page; struct mii_softc *sc; sc = &sc_phy->mii_sc; reg = PHY_READ (sc_phy, E1000_SCR); if ((sc->mii_flags & MIIF_HAVEFIBER) != 0) { reg &= ~E1000_SCR_AUTO_X_MODE; PHY_WRITE (sc_phy, E1000_SCR, reg); if (sc_phy->mii_model == MII_MODEL_MARVELL_E1112) { // Select 1000BASE-X only mode. page = PHY_READ (sc_phy, E1000_EADR); PHY_WRITE (sc_phy, E1000_EADR, 2); reg = PHY_READ (sc_phy, E1000_SCR); reg &= ~E1000_SCR_MODE_MASK; reg |= E1000_SCR_MODE_1000BX; PHY_WRITE (sc_phy, E1000_SCR, reg); if (sc_phy->mmd != NULL && sc_phy->mmd->pmd == 'P') { // Set SIGDET polarity low for SFP module PHY_WRITE (sc_phy, E1000_EADR, 1); reg = PHY_READ (sc_phy, E1000_SCR); reg |= E1000_SCR_FIB_SIGDET_POLARITY; PHY_WRITE (sc_phy, E1000_SCR, reg); } PHY_WRITE (sc_phy, E1000_EADR, page); } } else { switch (sc_phy->mii_model) { case MII_MODEL_MARVELL_E1111: case MII_MODEL_MARVELL_E1112: case MII_MODEL_MARVELL_E1116: case MII_MODEL_MARVELL_E1118: case MII_MODEL_MARVELL_E1149: case MII_MODEL_MARVELL_PHYG65G: // Disable energy detect mode reg &= ~E1000_SCR_EN_DETECT_MASK; reg |= E1000_SCR_AUTO_X_MODE; if (sc_phy->mii_model == MII_MODEL_MARVELL_E1116) reg &= ~E1000_SCR_POWER_DOWN; reg |= E1000_SCR_ASSERT_CRS_ON_TX; break; case MII_MODEL_MARVELL_E3082: reg |= (E1000_SCR_AUTO_X_MODE >> 1); reg |= E1000_SCR_ASSERT_CRS_ON_TX; break; case MII_MODEL_MARVELL_E3016: reg |= E1000_SCR_AUTO_MDIX; reg &= ~(E1000_SCR_EN_DETECT | E1000_SCR_SCRAMBLER_DISABLE); reg |= E1000_SCR_LPNP; // XXX Enable class A driver for Yukon FE+ A0 PHY_WRITE (sc_phy, 0x1C, PHY_READ (sc_phy, 0x1C) | 0x0001); break; default: reg &= ~E1000_SCR_AUTO_X_MODE; reg |= E1000_SCR_ASSERT_CRS_ON_TX; break; } if (sc_phy->mii_model != MII_MODEL_MARVELL_E3016) { /* Auto correction for reversed cable polarity. */ reg &= ~E1000_SCR_POLARITY_REVERSAL; } PHY_WRITE (sc_phy, E1000_SCR, reg); if (sc_phy->mii_model == MII_MODEL_MARVELL_E1116 || sc_phy->mii_model == MII_MODEL_MARVELL_E1149) { PHY_WRITE (sc_phy, E1000_EADR, 2); reg = PHY_READ (sc_phy, E1000_SCR); reg |= E1000_SCR_RGMII_POWER_UP; PHY_WRITE (sc_phy, E1000_SCR, reg); PHY_WRITE (sc_phy, E1000_EADR, 0); } } switch (sc_phy->mii_model) { case MII_MODEL_MARVELL_E3082: case MII_MODEL_MARVELL_E1112: case MII_MODEL_MARVELL_E1118: break; case MII_MODEL_MARVELL_E1116: page = PHY_READ (sc_phy, E1000_EADR); /* Select page 3, LED control register. */ PHY_WRITE (sc_phy, E1000_EADR, 3); PHY_WRITE (sc_phy, E1000_SCR, E1000_SCR_LED_LOS (1) | /* Link/Act */ E1000_SCR_LED_INIT (8) | /* 10Mbps */ E1000_SCR_LED_STAT1 (7) | /* 100Mbps */ E1000_SCR_LED_STAT0 (7)); /* 1000Mbps */ /* Set blink rate. */ PHY_WRITE (sc_phy, E1000_IER, E1000_PULSE_DUR (E1000_PULSE_170MS) | E1000_BLINK_RATE (E1000_BLINK_84MS)); PHY_WRITE (sc_phy, E1000_EADR, page); break; case MII_MODEL_MARVELL_E3016: /* LED2 -> ACT, LED1 -> LINK, LED0 -> SPEED. */ PHY_WRITE (sc_phy, 0x16, 0x0B << 8 | 0x05 << 4 | 0x04); /* Integrated register calibration workaround. */ PHY_WRITE (sc_phy, 0x1D, 17); PHY_WRITE (sc_phy, 0x1E, 0x3F60); break; default: /* Force TX_CLK to 25MHz clock. */ reg = PHY_READ (sc_phy, E1000_ESCR); reg |= E1000_ESCR_TX_CLK_25; PHY_WRITE (sc_phy, E1000_ESCR, reg); break; } /* Reset the PHY so all changes take effect. */ reg = PHY_READ (sc_phy, E1000_CR); reg |= E1000_CR_RESET; PHY_WRITE (sc_phy, E1000_CR, reg); } static void mii_phy_update ( struct e1000phy_softc *sc_phy, INTN cmd ) { struct mii_softc *sc = &sc_phy->mii_sc; struct mii_data *mii = sc->mii_pdata; if (sc->mii_media_active != mii->mii_media_active || sc->mii_media_status != mii->mii_media_status || cmd == MII_MEDIACHG) { msk_miibus_statchg (sc_phy->sc_if); sc->mii_media_active = mii->mii_media_active; sc->mii_media_status = mii->mii_media_status; } } void e1000phy_tick ( struct e1000phy_softc *sc_phy ) { e1000phy_service (sc_phy, MII_TICK); } void e1000phy_mediachg ( struct e1000phy_softc *sc_phy ) { struct mii_data *mii; mii = sc_phy->mii_sc.mii_pdata; mii->mii_media_status = 0; mii->mii_media_active = IFM_NONE; e1000phy_service (sc_phy, MII_MEDIACHG); } static void e1000phy_service ( struct e1000phy_softc *sc_phy, INTN cmd ) { struct mii_softc *sc; INTN reg; sc = &sc_phy->mii_sc; switch (cmd) { case MII_POLLSTAT: break; case MII_MEDIACHG: // // Always try to auto-negotiate // e1000phy_mii_phy_auto (sc_phy); break; case MII_TICK: /* * check for link. * Read the status register twice; Link Status is latch-low. */ reg = PHY_READ (sc_phy, E1000_SR) | PHY_READ (sc_phy, E1000_SR); if (reg & E1000_SR_LINK_STATUS) { sc->mii_ticks = 0; break; } /* Announce link loss right after it happens. */ if (sc->mii_ticks++ == 0) { break; } if (sc->mii_ticks <= sc->mii_anegticks) { break; } // // Restart the auto-negotiation // sc->mii_ticks = 0; e1000phy_reset (sc_phy); e1000phy_mii_phy_auto (sc_phy); break; } /* Update the media status. */ e1000phy_status (sc_phy); /* Callback if something changed. */ mii_phy_update (sc_phy, cmd); } static void e1000phy_status ( struct e1000phy_softc *sc_phy ) { struct mii_softc *sc; struct mii_data *mii; INTN bmcr; INTN bmsr; INTN gsr; INTN ssr; INTN ar; INTN lpar; sc = &sc_phy->mii_sc; mii = sc->mii_pdata; mii->mii_media_status = IFM_AVALID; mii->mii_media_active = IFM_ETHER; bmsr = PHY_READ (sc_phy, E1000_SR) | PHY_READ (sc_phy, E1000_SR); bmcr = PHY_READ (sc_phy, E1000_CR); ssr = PHY_READ (sc_phy, E1000_SSR); if (bmsr & E1000_SR_LINK_STATUS) { DEBUG ((EFI_D_NET, "Marvell Yukon: e1000phy_status, link up\n")); mii->mii_media_status |= IFM_ACTIVE; } if (bmcr & E1000_CR_LOOPBACK) { mii->mii_media_active |= IFM_LOOP; } if ((bmcr & E1000_CR_AUTO_NEG_ENABLE) != 0 && (ssr & E1000_SSR_SPD_DPLX_RESOLVED) == 0) { /* Erg, still trying, I guess... */ DEBUG ((EFI_D_NET, "Marvell Yukon: e1000phy_status, auto negotiation not complete\n")); mii->mii_media_active |= IFM_NONE; return; } if ((sc->mii_flags & MIIF_HAVEFIBER) == 0) { switch (ssr & E1000_SSR_SPEED) { case E1000_SSR_1000MBS: mii->mii_media_active |= IFM_1000_T; break; case E1000_SSR_100MBS: mii->mii_media_active |= IFM_100_TX; break; case E1000_SSR_10MBS: mii->mii_media_active |= IFM_10_T; break; default: mii->mii_media_active |= IFM_NONE; return; } } else { /* * Some fiber PHY(88E1112) does not seem to set resolved * speed so always assume we've got IFM_1000_SX. */ mii->mii_media_active |= IFM_1000_SX; } if (ssr & E1000_SSR_DUPLEX) { mii->mii_media_active |= IFM_FDX; } else { mii->mii_media_active |= IFM_HDX; } if ((sc->mii_flags & MIIF_HAVEFIBER) == 0) { ar = PHY_READ (sc_phy, E1000_AR); lpar = PHY_READ (sc_phy, E1000_LPAR); /* FLAG0==rx-flow-control FLAG1==tx-flow-control */ if ((ar & E1000_AR_PAUSE) && (lpar & E1000_LPAR_PAUSE)) { mii->mii_media_active |= IFM_FLAG0 | IFM_FLAG1; } else if (!(ar & E1000_AR_PAUSE) && (ar & E1000_AR_ASM_DIR) && (lpar & E1000_LPAR_PAUSE) && (lpar & E1000_LPAR_ASM_DIR)) { mii->mii_media_active |= IFM_FLAG1; } else if ((ar & E1000_AR_PAUSE) && (ar & E1000_AR_ASM_DIR) && !(lpar & E1000_LPAR_PAUSE) && (lpar & E1000_LPAR_ASM_DIR)) { mii->mii_media_active |= IFM_FLAG0; } } /* FLAG2 : local PHY resolved to MASTER */ if ((IFM_SUBTYPE (mii->mii_media_active) == IFM_1000_T) || (IFM_SUBTYPE (mii->mii_media_active) == IFM_1000_SX)) { PHY_READ (sc_phy, E1000_1GSR); gsr = PHY_READ (sc_phy, E1000_1GSR); if ((gsr & E1000_1GSR_MS_CONFIG_RES) != 0) { mii->mii_media_active |= IFM_FLAG2; } } } static void e1000phy_mii_phy_auto ( struct e1000phy_softc *sc_phy ) { struct mii_softc *sc; UINT16 reg; DEBUG ((EFI_D_NET, "Marvell Yukon: e1000phy_mii_phy_auto negotiation started\n")); sc = &sc_phy->mii_sc; if ((sc->mii_flags & MIIF_HAVEFIBER) == 0) { reg = PHY_READ (sc_phy, E1000_AR); reg |= E1000_AR_10T | E1000_AR_10T_FD | E1000_AR_100TX | E1000_AR_100TX_FD | E1000_AR_PAUSE | E1000_AR_ASM_DIR; PHY_WRITE (sc_phy, E1000_AR, reg | E1000_AR_SELECTOR_FIELD); } else { PHY_WRITE (sc_phy, E1000_AR, E1000_FA_1000X_FD | E1000_FA_1000X | E1000_FA_SYM_PAUSE | E1000_FA_ASYM_PAUSE); } if ((sc->mii_extcapabilities & (E1000_ESR_1000T_FD | E1000_ESR_1000T)) != 0) { PHY_WRITE (sc_phy, E1000_1GCR, E1000_1GCR_1000T_FD | E1000_1GCR_1000T); } PHY_WRITE (sc_phy, E1000_CR, E1000_CR_AUTO_NEG_ENABLE | E1000_CR_RESTART_AUTO_NEG); } // // Generic helper functions // const struct mii_phydesc * mii_phy_match_gen ( const struct mii_attach_args *ma, const struct mii_phydesc *mpd, UINTN len ) { for (; mpd->mpd_name != NULL; mpd = (const struct mii_phydesc *) ((const CHAR8 *) mpd + len)) { if (MII_OUI (ma->mii_id1, ma->mii_id2) == mpd->mpd_oui && MII_MODEL (ma->mii_id2) == mpd->mpd_model) { return (mpd); } } return (NULL); } const struct mii_phydesc * mii_phy_match ( const struct mii_attach_args *ma, const struct mii_phydesc *mpd ) { return (mii_phy_match_gen (ma, mpd, sizeof (struct mii_phydesc))); } EFI_STATUS mii_phy_dev_probe ( const struct mii_attach_args *ma, const struct mii_phydesc *mpd ) { mpd = mii_phy_match (ma, mpd); if (mpd != NULL) { DEBUG ((EFI_D_NET, "Marvell Yukon: Found PHY (%a)\n", mpd->mpd_name)); return EFI_SUCCESS; } DEBUG ((DEBUG_NET, "Marvell Yukon: PHY not found (OUI=0x%x, MODEL=0x%x)\n", MII_OUI (ma->mii_id1, ma->mii_id2), MII_MODEL (ma->mii_id2))); return EFI_NOT_FOUND; }