#include "nge.h"
#undef NGE_DBG
#define NGE_DBG NGE_DBG_MII
static const int16_t nge_copper_link_speed[] = {
0,
10,
100,
1000,
};
static const int8_t nge_copper_link_duplex[] = {
LINK_DUPLEX_UNKNOWN,
LINK_DUPLEX_HALF,
LINK_DUPLEX_FULL,
};
static uint16_t nge_mii_access(nge_t *ngep, nge_regno_t regno,
uint16_t data, uint32_t cmd);
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));
intr_src.src_val = nge_reg_get8(ngep, NGE_MINTR_SRC);
nge_reg_put8(ngep, NGE_MINTR_SRC, intr_src.src_val);
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);
}
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);
}
mdio_adr.adr_bits.phy_reg = (uint16_t)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);
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);
if (cmd == NGE_MDIO_READ)
mdio_data = nge_reg_get16(ngep, NGE_MDIO_DATA);
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);
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);
void
nge_mii_put16(nge_t *ngep, nge_regno_t regno, uint16_t data)
{
(void) nge_mii_access(ngep, regno, data, NGE_MDIO_WRITE);
}
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));
for (i = 0; i < NGE_PHY_NUMBER; i++) {
ngep->phy_xmii_addr = i;
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);
}
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);
}
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));
control = nge_mii_get16(ngep, MII_CONTROL);
control |= MII_CONTROL_RESET;
nge_mii_put16(ngep, MII_CONTROL, control);
delay(drv_usectohz(500000));
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 boolean_t
nge_phy_restart(nge_t *ngep)
{
uint16_t mii_reg;
if (!nge_phy_recover(ngep))
return (B_FALSE);
if (!nge_phy_reset(ngep))
return (B_FALSE);
if (MII_PHY_MFG(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);
}
}
return (B_TRUE);
}
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;
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));
if (!adv_1000fdx && !adv_100fdx && !adv_10fdx &&
!adv_100hdx && !adv_10hdx)
adv_10hdx = B_TRUE;
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); }
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_ASMPAUSE;
anar |= MII_AN_SELECTOR_8023;
nge_mii_put16(ngep, MII_AN_ADVERT, anar);
nge_mii_put16(ngep, MII_CONTROL, control);
nge_mii_put16(ngep, MII_1000BASE_T_CONTROL, gigctrl);
if (!nge_phy_restart(ngep))
nge_error(ngep, "nge_update_copper: failed to restart phy");
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;
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 {
switch (ngep->phy_mode) {
default:
case RGMII_IN:
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;
}
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);
ngep->phy_gen_status = mii_status;
mii_status = nge_mii_get16(ngep, MII_STATUS);
} while (mii_status != ngep->phy_gen_status);
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;
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);
}
static const phys_ops_t copper_ops = {
nge_phy_restart,
nge_update_copper,
nge_check_copper
};
void
nge_phys_init(nge_t *ngep)
{
nge_mac2phy m2p;
NGE_TRACE(("nge_phys_init($%p)", (void *)ngep));
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);
}
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);
}