/* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This file may contain confidential information of Nvidia * and should not be distributed in source form without approval * from Sun Legal. */ #pragma ident "%Z%%M% %I% %E% SMI" #include "nge.h" #undef NGE_DBG #define NGE_DBG NGE_DBG_MII /* debug flag for this code */ /* * The arrays below can be indexed by the MODE bits from the mac2phy * register to determine the current speed/duplex settings. */ static const int16_t nge_copper_link_speed[] = { 0, /* MII_AUX_STATUS_MODE_NONE */ 10, /* MII_AUX_STAT0,US_MODE_10 */ 100, /* MII_AUX_STAT0,US_MODE_100 */ 1000, /* MII_AUX_STAT0,US_MODE_1000 */ }; static const int8_t nge_copper_link_duplex[] = { LINK_DUPLEX_UNKNOWN, /* MII_DUPLEX_NONE */ LINK_DUPLEX_HALF, /* MII_DUPLEX_HALF */ LINK_DUPLEX_FULL, /* MII_DUPLEX_FULL */ }; static uint16_t nge_mii_access(nge_t *ngep, nge_regno_t regno, uint16_t data, uint32_t cmd); #pragma inline(nge_mii_access) static uint16_t nge_mii_access(nge_t *ngep, nge_regno_t regno, uint16_t data, uint32_t cmd) { uint16_t tries; uint16_t mdio_data; nge_mdio_adr mdio_adr; nge_mintr_src intr_src; NGE_TRACE(("nge_mii_access($%p, 0x%lx, 0x%x, 0x%x)", (void *)ngep, regno, data, cmd)); /* * Clear the privous interrupt event */ intr_src.src_val = nge_reg_get8(ngep, NGE_MINTR_SRC); nge_reg_put8(ngep, NGE_MINTR_SRC, intr_src.src_val); /* * Check whether the current operation has been finished */ mdio_adr.adr_val = nge_reg_get16(ngep, NGE_MDIO_ADR); for (tries = 0; tries < 30; tries ++) { if (mdio_adr.adr_bits.mdio_clc == NGE_CLEAR) break; drv_usecwait(10); mdio_adr.adr_val = nge_reg_get16(ngep, NGE_MDIO_ADR); } /* * The current operation can not be finished successfully * The driver should halt the current operation */ if (tries == 30) { mdio_adr.adr_bits.mdio_clc = NGE_SET; nge_reg_put16(ngep, NGE_MDIO_ADR, mdio_adr.adr_val); drv_usecwait(100); } /* * Assemble the operation cmd */ mdio_adr.adr_bits.phy_reg = regno; mdio_adr.adr_bits.phy_adr = ngep->phy_xmii_addr; mdio_adr.adr_bits.mdio_rw = (cmd == NGE_MDIO_WRITE) ? 1 : 0; if (cmd == NGE_MDIO_WRITE) nge_reg_put16(ngep, NGE_MDIO_DATA, data); nge_reg_put16(ngep, NGE_MDIO_ADR, mdio_adr.adr_val); /* * To check whether the read/write operation is finished */ for (tries = 0; tries < 300; tries ++) { drv_usecwait(10); mdio_adr.adr_val = nge_reg_get16(ngep, NGE_MDIO_ADR); if (mdio_adr.adr_bits.mdio_clc == NGE_CLEAR) break; } if (tries == 300) return ((uint16_t)~0); /* * Read the data from MDIO data register */ if (cmd == NGE_MDIO_READ) mdio_data = nge_reg_get16(ngep, NGE_MDIO_DATA); /* * To check whether the read/write operation is valid */ intr_src.src_val = nge_reg_get8(ngep, NGE_MINTR_SRC); nge_reg_put8(ngep, NGE_MINTR_SRC, intr_src.src_val); if (intr_src.src_bits.mrei == NGE_SET) return ((uint16_t)~0); return (mdio_data); } uint16_t nge_mii_get16(nge_t *ngep, nge_regno_t regno); #pragma inline(nge_mii_get16) uint16_t nge_mii_get16(nge_t *ngep, nge_regno_t regno) { return (nge_mii_access(ngep, regno, 0, NGE_MDIO_READ)); } void nge_mii_put16(nge_t *ngep, nge_regno_t regno, uint16_t data); #pragma inline(nge_mii_put16) void nge_mii_put16(nge_t *ngep, nge_regno_t regno, uint16_t data) { (void) nge_mii_access(ngep, regno, data, NGE_MDIO_WRITE); } /* * Basic low-level function to probe for a PHY * * Returns TRUE if the PHY responds with valid data, FALSE otherwise */ static boolean_t nge_phy_probe(nge_t *ngep) { int i; uint16_t phy_status; uint16_t phyidh; uint16_t phyidl; NGE_TRACE(("nge_phy_probe($%p)", (void *)ngep)); /* * Scan the phys to find the right address * of the phy * * Probe maximum for 32 phy addresses */ for (i = 0; i < NGE_PHY_NUMBER; i++) { ngep->phy_xmii_addr = i; /* * Read the MII_STATUS register twice, in * order to clear any sticky bits (but they should * have been cleared by the RESET, I think). */ phy_status = nge_mii_get16(ngep, MII_STATUS); phy_status = nge_mii_get16(ngep, MII_STATUS); if (phy_status != 0xffff) { phyidh = nge_mii_get16(ngep, MII_PHYIDH); phyidl = nge_mii_get16(ngep, MII_PHYIDL); ngep->phy_id = (((uint32_t)phyidh << 16) |(phyidl & MII_IDL_MASK)); NGE_DEBUG(("nge_phy_probe: status 0x%x, phy id 0x%x", phy_status, ngep->phy_id)); return (B_TRUE); } } return (B_FALSE); } /* * Basic low-level function to powerup the phy and remove the isolation */ static boolean_t nge_phy_recover(nge_t *ngep) { uint16_t control; uint16_t count; NGE_TRACE(("nge_phy_recover($%p)", (void *)ngep)); control = nge_mii_get16(ngep, MII_CONTROL); control &= ~(MII_CONTROL_PWRDN | MII_CONTROL_ISOLATE); nge_mii_put16(ngep, MII_CONTROL, control); for (count = 0; ++count < 10; ) { drv_usecwait(5); control = nge_mii_get16(ngep, MII_CONTROL); if (BIC(control, MII_CONTROL_PWRDN)) return (B_TRUE); } return (B_FALSE); } /* * Basic low-level function to reset the PHY. * Doesn't incorporate any special-case workarounds. * * Returns TRUE on success, FALSE if the RESET bit doesn't clear */ boolean_t nge_phy_reset(nge_t *ngep) { uint16_t control; uint_t count; NGE_TRACE(("nge_phy_reset($%p)", (void *)ngep)); ASSERT(mutex_owned(ngep->genlock)); /* * Set the PHY RESET bit, then wait up to 5 ms for it to self-clear */ control = nge_mii_get16(ngep, MII_CONTROL); control |= MII_CONTROL_RESET; nge_mii_put16(ngep, MII_CONTROL, control); drv_usecwait(30); for (count = 0; ++count < 10; ) { drv_usecwait(5); control = nge_mii_get16(ngep, MII_CONTROL); if (BIC(control, MII_CONTROL_RESET)) return (B_TRUE); } NGE_DEBUG(("nge_phy_reset: FAILED, control now 0x%x", control)); return (B_FALSE); } static void nge_phy_restart(nge_t *ngep) { uint16_t mii_reg; (void) nge_phy_recover(ngep); (void) nge_phy_reset(ngep); if (PHY_MANUFACTURER(ngep->phy_id) == MII_ID_CICADA) { if (ngep->phy_mode == RGMII_IN) { mii_reg = nge_mii_get16(ngep, MII_CICADA_EXT_CONTROL); mii_reg &= ~(MII_CICADA_MODE_SELECT_BITS | MII_CICADA_POWER_SUPPLY_BITS); mii_reg |= (MII_CICADA_MODE_SELECT_RGMII | MII_CICADA_POWER_SUPPLY_2_5V); nge_mii_put16(ngep, MII_CICADA_EXT_CONTROL, mii_reg); mii_reg = nge_mii_get16(ngep, MII_CICADA_AUXCTRL_STATUS); mii_reg |= MII_CICADA_PIN_PRORITY_SETTING; nge_mii_put16(ngep, MII_CICADA_AUXCTRL_STATUS, mii_reg); } else { mii_reg = nge_mii_get16(ngep, MII_CICADA_10BASET_CONTROL); mii_reg |= MII_CICADA_DISABLE_ECHO_MODE; nge_mii_put16(ngep, MII_CICADA_10BASET_CONTROL, mii_reg); mii_reg = nge_mii_get16(ngep, MII_CICADA_BYPASS_CONTROL); mii_reg &= (~CICADA_125MHZ_CLOCK_ENABLE); nge_mii_put16(ngep, MII_CICADA_BYPASS_CONTROL, mii_reg); } } } /* * Synchronise the (copper) PHY's speed/duplex/autonegotiation capabilities * and advertisements with the required settings as specified by the various * param_* variables that can be poked via the NDD interface. * * We always reset the PHY and reprogram *all* the relevant registers, * not just those changed. This should cause the link to go down, and then * back up again once the link is stable and autonegotiation (if enabled) * is complete. We should get a link state change interrupt somewhere along * the way ... * * NOTE: must already be held by the caller */ static void nge_update_copper(nge_t *ngep) { uint16_t control; uint16_t gigctrl; uint16_t anar; boolean_t adv_autoneg; boolean_t adv_pause; boolean_t adv_asym_pause; boolean_t adv_1000fdx; boolean_t adv_100fdx; boolean_t adv_100hdx; boolean_t adv_10fdx; boolean_t adv_10hdx; NGE_TRACE(("nge_update_copper($%p)", (void *)ngep)); ASSERT(mutex_owned(ngep->genlock)); NGE_DEBUG(("nge_update_copper: autoneg %d " "pause %d asym_pause %d " "1000fdx %d " "100fdx %d 100hdx %d " "10fdx %d 10hdx %d ", ngep->param_adv_autoneg, ngep->param_adv_pause, ngep->param_adv_asym_pause, ngep->param_adv_1000fdx, ngep->param_adv_100fdx, ngep->param_adv_100hdx, ngep->param_adv_10fdx, ngep->param_adv_10hdx)); control = anar = gigctrl = 0; /* * PHY settings are normally based on the param_* variables, * but if any loopback mode is in effect, that takes precedence. * * NGE supports MAC-internal loopback, PHY-internal loopback, * and External loopback at a variety of speeds (with a special * cable). In all cases, autoneg is turned OFF, full-duplex * is turned ON, and the speed/mastership is forced. */ switch (ngep->param_loop_mode) { case NGE_LOOP_NONE: default: adv_pause = ngep->param_adv_pause; adv_autoneg = ngep->param_adv_autoneg; adv_asym_pause = ngep->param_adv_asym_pause; if (ngep->phy_mode == MII_IN) { adv_1000fdx = ngep->param_adv_1000fdx = B_FALSE; } adv_1000fdx = ngep->param_adv_1000fdx; adv_100fdx = ngep->param_adv_100fdx; adv_100hdx = ngep->param_adv_100hdx; adv_10fdx = ngep->param_adv_10fdx; adv_10hdx = ngep->param_adv_10hdx; break; case NGE_LOOP_EXTERNAL_100: case NGE_LOOP_EXTERNAL_10: case NGE_LOOP_INTERNAL_PHY: adv_autoneg = adv_pause = adv_asym_pause = B_FALSE; adv_1000fdx = adv_100fdx = adv_10fdx = B_FALSE; adv_100hdx = adv_10hdx = B_FALSE; ngep->param_link_duplex = LINK_DUPLEX_FULL; switch (ngep->param_loop_mode) { case NGE_LOOP_EXTERNAL_100: ngep->param_link_speed = 100; adv_100fdx = B_TRUE; break; case NGE_LOOP_EXTERNAL_10: ngep->param_link_speed = 10; adv_10fdx = B_TRUE; break; case NGE_LOOP_INTERNAL_PHY: ngep->param_link_speed = 1000; adv_1000fdx = B_TRUE; break; } } NGE_DEBUG(("nge_update_copper: autoneg %d " "pause %d asym_pause %d " "1000fdx %d " "100fdx %d 100hdx %d " "10fdx %d 10hdx %d ", adv_autoneg, adv_pause, adv_asym_pause, adv_1000fdx, adv_100fdx, adv_100hdx, adv_10fdx, adv_10hdx)); /* * We should have at least one technology capability set; * if not, we select a default of 10Mb/s half-duplex */ if (!adv_1000fdx && !adv_100fdx && !adv_10fdx && !adv_100hdx && !adv_10hdx) adv_10hdx = B_TRUE; /* * Now transform the adv_* variables into the proper settings * of the PHY registers ... * * If autonegotiation is (now) enabled, we want to trigger * a new autonegotiation cycle once the PHY has been * programmed with the capabilities to be advertised. */ if (adv_autoneg) control |= MII_CONTROL_ANE|MII_CONTROL_RSAN; if (adv_1000fdx) control |= MII_CONTROL_1000MB|MII_CONTROL_FDUPLEX; else if (adv_100fdx) control |= MII_CONTROL_100MB|MII_CONTROL_FDUPLEX; else if (adv_100hdx) control |= MII_CONTROL_100MB; else if (adv_10fdx) control |= MII_CONTROL_FDUPLEX; else if (adv_10hdx) control |= 0; else { _NOTE(EMPTY); } /* Can't get here anyway ... */ if (adv_1000fdx) gigctrl |= MII_1000BT_CTL_ADV_FDX; if (adv_100fdx) anar |= MII_ABILITY_100BASE_TX_FD; if (adv_100hdx) anar |= MII_ABILITY_100BASE_TX; if (adv_10fdx) anar |= MII_ABILITY_10BASE_T_FD; if (adv_10hdx) anar |= MII_ABILITY_10BASE_T; if (adv_pause) anar |= MII_ABILITY_PAUSE; if (adv_asym_pause) anar |= MII_ABILITY_ASYM_PAUSE; /* * Munge in any other fixed bits we require ... */ anar |= MII_AN_SELECTOR_8023; /* * Restart the PHY and write the new values. */ nge_mii_put16(ngep, MII_AN_ADVERT, anar); nge_mii_put16(ngep, MII_CONTROL, control); nge_mii_put16(ngep, MII_1000BASE_T_CONTROL, gigctrl); nge_phy_restart(ngep); /* * Loopback bit in control register is not reset sticky * write it after PHY restart. */ if (ngep->param_loop_mode == NGE_LOOP_INTERNAL_PHY) { control = nge_mii_get16(ngep, MII_CONTROL); control |= MII_CONTROL_LOOPBACK; nge_mii_put16(ngep, MII_CONTROL, control); } } static boolean_t nge_check_copper(nge_t *ngep) { uint16_t mii_status; uint16_t mii_exstatus; uint16_t mii_excontrol; uint16_t anar; uint16_t lpan; uint_t speed; uint_t duplex; boolean_t linkup; nge_mii_cs mii_cs; nge_mintr_src mintr_src; speed = UNKOWN_SPEED; duplex = UNKOWN_DUPLEX; /* * Read the status from the PHY (which is self-clearing * on read!); also read & clear the main (Ethernet) MAC status * (the relevant bits of this are write-one-to-clear). */ mii_status = nge_mii_get16(ngep, MII_STATUS); mii_cs.cs_val = nge_reg_get32(ngep, NGE_MII_CS); mintr_src.src_val = nge_reg_get32(ngep, NGE_MINTR_SRC); nge_reg_put32(ngep, NGE_MINTR_SRC, mintr_src.src_val); NGE_DEBUG(("nge_check_copper: link %d/%s, MII status 0x%x " "(was 0x%x)", ngep->link_state, UPORDOWN(ngep->param_link_up), mii_status, ngep->phy_gen_status)); do { /* * If the PHY status changed, record the time */ switch (ngep->phy_mode) { default: case RGMII_IN: /* * Judge the giga speed by reading control * and status register */ mii_excontrol = nge_mii_get16(ngep, MII_1000BASE_T_CONTROL); mii_exstatus = nge_mii_get16(ngep, MII_1000BASE_T_STATUS); if ((mii_excontrol & MII_1000BT_CTL_ADV_FDX) && (mii_exstatus & MII_1000BT_STAT_LP_FDX_CAP)) { speed = NGE_1000M; duplex = NGE_FD; } else { anar = nge_mii_get16(ngep, MII_AN_ADVERT); lpan = nge_mii_get16(ngep, MII_AN_LPABLE); if (lpan != 0) anar = (anar & lpan); if (anar & MII_100BASET_FD) { speed = NGE_100M; duplex = NGE_FD; } else if (anar & MII_100BASET_HD) { speed = NGE_100M; duplex = NGE_HD; } else if (anar & MII_10BASET_FD) { speed = NGE_10M; duplex = NGE_FD; } else if (anar & MII_10BASET_HD) { speed = NGE_10M; duplex = NGE_HD; } } break; case MII_IN: anar = nge_mii_get16(ngep, MII_AN_ADVERT); lpan = nge_mii_get16(ngep, MII_AN_LPABLE); if (lpan != 0) anar = (anar & lpan); if (anar & MII_100BASET_FD) { speed = NGE_100M; duplex = NGE_FD; } else if (anar & MII_100BASET_HD) { speed = NGE_100M; duplex = NGE_HD; } else if (anar & MII_10BASET_FD) { speed = NGE_10M; duplex = NGE_FD; } else if (anar & MII_10BASET_HD) { speed = NGE_10M; duplex = NGE_HD; } break; } /* * We will only consider the link UP if all the readings * are consistent and give meaningful results ... */ linkup = nge_copper_link_speed[speed] > 0; linkup &= nge_copper_link_duplex[duplex] != LINK_DUPLEX_UNKNOWN; linkup &= BIS(mii_status, MII_STATUS_LINKUP); linkup &= BIS(mii_cs.cs_val, MII_STATUS_LINKUP); /* * Record current register values, then reread status * register & loop until it stabilises ... */ ngep->phy_gen_status = mii_status; mii_status = nge_mii_get16(ngep, MII_STATUS); } while (mii_status != ngep->phy_gen_status); /* Get the Link Partner Ability */ mii_exstatus = nge_mii_get16(ngep, MII_1000BASE_T_STATUS); lpan = nge_mii_get16(ngep, MII_AN_LPABLE); if (mii_exstatus & MII_1000BT_STAT_LP_FDX_CAP) { ngep->param_lp_autoneg = B_TRUE; ngep->param_link_autoneg = B_TRUE; ngep->param_lp_1000fdx = B_TRUE; } if (mii_exstatus & MII_1000BT_STAT_LP_HDX_CAP) { ngep->param_lp_autoneg = B_TRUE; ngep->param_link_autoneg = B_TRUE; ngep->param_lp_1000hdx = B_TRUE; } if (lpan & MII_100BASET_FD) ngep->param_lp_100fdx = B_TRUE; if (lpan & MII_100BASET_HD) ngep->param_lp_100hdx = B_TRUE; if (lpan & MII_10BASET_FD) ngep->param_lp_10fdx = B_TRUE; if (lpan & MII_10BASET_HD) ngep->param_lp_10hdx = B_TRUE; if (lpan & MII_LP_ASYM_PAUSE) ngep->param_lp_asym_pause = B_TRUE; if (lpan & MII_LP_PAUSE) ngep->param_lp_pause = B_TRUE; ngep->param_link_tx_pause = B_FALSE; if (ngep->param_adv_autoneg) ngep->param_link_rx_pause = B_FALSE; else ngep->param_link_rx_pause = ngep->param_adv_pause; if (linkup) { ngep->param_link_up = linkup; ngep->param_link_speed = nge_copper_link_speed[speed]; ngep->param_link_duplex = nge_copper_link_duplex[duplex]; } else { ngep->param_link_up = B_FALSE; ngep->param_link_speed = 0; ngep->param_link_duplex = LINK_DUPLEX_UNKNOWN; } NGE_DEBUG(("nge_check_copper: link now %s speed %d duplex %d", UPORDOWN(ngep->param_link_up), ngep->param_link_speed, ngep->param_link_duplex)); return (B_FALSE); } /* * Because the network chipset embedded in Ck8-04 bridge is only a mac chipset, * the different vendor can use different media(serdes and copper). * To make it easier to extend the driver to support more platforms with ck8-04, * For example, one platform with serdes support, * wrapper phy operation functions. * But now, only supply copper phy operations. */ static const phys_ops_t copper_ops = { nge_phy_restart, nge_update_copper, nge_check_copper }; /* * Here we have to determine which media we're using (copper or serdes). * Once that's done, we can initialise the physical layer appropriately. */ void nge_phys_init(nge_t *ngep) { nge_mac2phy m2p; NGE_TRACE(("nge_phys_init($%p)", (void *)ngep)); /* Get the phy type from MAC2PHY register */ m2p.m2p_val = nge_reg_get32(ngep, NGE_MAC2PHY); ngep->phy_mode = m2p.m2p_bits.in_type; if ((ngep->phy_mode != RGMII_IN) && (ngep->phy_mode != MII_IN)) { ngep->phy_mode = RGMII_IN; m2p.m2p_bits.in_type = RGMII_IN; nge_reg_put32(ngep, NGE_MAC2PHY, m2p.m2p_val); } /* * Probe for the type of the PHY. */ ngep->phy_xmii_addr = 1; (void) nge_phy_probe(ngep); ngep->chipinfo.flags |= CHIP_FLAG_COPPER; ngep->physops = &copper_ops; (*(ngep->physops->phys_restart))(ngep); }