#include <sys/types.h>
#include <sys/debug.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/devops.h>
#include <sys/modctl.h>
#include <sys/cmn_err.h>
#include <sys/miiregs.h>
#include "dnet_mii.h"
#ifdef DEBUG
#define MIIDEBUG
int miidebug = 0;
#define MIITRACE 1
#define MIIDUMP 2
#define MIIPROBE 4
#define MIICOMPAT 8
#endif
static struct phydata *mii_get_valid_phydata(mii_handle_t mac, int phy);
static void mii_portmon(mii_handle_t mac);
static void dump_NS83840(mii_handle_t, int);
static void dump_ICS1890(struct mii_info *, int);
static int getspeed_NS83840(mii_handle_t, int, int *, int *);
static int getspeed_82553(mii_handle_t, int, int *, int *);
static int getspeed_ICS1890(mii_handle_t, int, int *, int *);
static int getspeed_generic(mii_handle_t, int, int *, int *);
static void postreset_ICS1890(mii_handle_t mac, int phy);
static void postreset_NS83840(mii_handle_t mac, int phy);
int
mii_create(dev_info_t *dip,
mii_writefunc_t writefunc,
mii_readfunc_t readfunc,
mii_handle_t *macp)
{
mii_handle_t mac;
if ((mac = (mii_handle_t)
kmem_zalloc(sizeof (struct mii_info), KM_NOSLEEP)) == NULL)
return (MII_NOMEM);
mac->mii_write = writefunc;
mac->mii_read = readfunc;
mac->mii_dip = dip;
*macp = mac;
return (MII_SUCCESS);
}
int
mii_probe_phy(mii_handle_t mac, int phy)
{
ushort_t status;
dev_info_t *dip;
if (!mac || phy < 0 || phy > 31)
return (MII_PARAM);
dip = mac->mii_dip;
mac->mii_read(dip, phy, MII_STATUS);
status = mac->mii_read(dip, phy, MII_STATUS);
#ifdef MIIDEBUG
mac->mii_read(dip, phy, MII_CONTROL);
if (miidebug & MIIPROBE)
cmn_err(CE_NOTE, "PHY Probe: Control=%x, Status=%x",
mac->mii_read(dip, phy, MII_CONTROL), status);
#endif
if (status == 0xffff || status == 0x0000)
return (MII_PHYNOTPRESENT);
return (MII_SUCCESS);
}
int
mii_init_phy(mii_handle_t mac, int phy)
{
ushort_t status;
void *dip;
struct phydata *phydata;
if ((mac == (mii_handle_t)NULL) || phy < 0 || phy > 31)
return (MII_PARAM);
dip = mac->mii_dip;
if (mac->phys[phy])
return (MII_PHYPRESENT);
mac->phys[phy] = phydata = (struct phydata *)
kmem_zalloc(sizeof (struct phydata), KM_NOSLEEP);
if (!phydata)
return (MII_NOMEM);
phydata->id = (ulong_t)mac->mii_read(dip, phy, MII_PHYIDH) << 16;
phydata->id |= (ulong_t)mac->mii_read(dip, phy, MII_PHYIDL);
phydata->state = phy_state_unknown;
phydata->fix_duplex =
ddi_getprop(DDI_DEV_T_NONE,
mac->mii_dip, DDI_PROP_DONTPASS, "full-duplex", 0);
phydata->fix_speed =
ddi_getprop(DDI_DEV_T_NONE,
mac->mii_dip, DDI_PROP_DONTPASS, "speed", 0);
status = mac->mii_read(dip, phy, MII_STATUS);
if (!(status & MII_STATUS_CANAUTONEG) ||
phydata->fix_speed || phydata->fix_duplex) {
if ((status & (MII_STATUS_100_BASEX | MII_STATUS_100_BASEX_FD |
MII_STATUS_100_BASE_T4)) && phydata->fix_speed == 0) {
phydata->fix_speed = 100;
} else if ((status & (MII_STATUS_10 | MII_STATUS_10_FD)) &&
phydata->fix_speed == 0) {
phydata->fix_speed = 10;
} else if (phydata->fix_speed == 0) {
kmem_free(mac->phys[phy], sizeof (struct phydata));
mac->phys[phy] = NULL;
return (MII_NOTSUPPORTED);
}
} else
phydata->control = MII_CONTROL_ANE;
switch (MII_PHY_MFG(phydata->id)) {
case OUI_NATIONAL_SEMICONDUCTOR:
switch (MII_PHY_MODEL(phydata->id)) {
case NS_DP83840:
phydata->phy_postreset = postreset_NS83840;
phydata->phy_dump = dump_NS83840;
phydata->description =
"National Semiconductor DP-83840";
phydata->phy_getspeed = getspeed_NS83840;
break;
default:
phydata->description = "Unknown NS";
break;
}
break;
case OUI_INTEL:
switch (MII_PHY_MODEL(phydata->id)) {
case INTEL_82553_CSTEP:
phydata->description = "Intel 82553 C-step";
phydata->phy_getspeed = getspeed_82553;
break;
case INTEL_82555:
phydata->description = "Intel 82555";
phydata->phy_getspeed = getspeed_82553;
break;
case INTEL_82562_EH:
phydata->description = "Intel 82562 EH";
phydata->phy_getspeed = getspeed_82553;
break;
case INTEL_82562_ET:
phydata->description = "Intel 82562 ET";
phydata->phy_getspeed = getspeed_82553;
break;
case INTEL_82562_EM:
phydata->description = "Intel 82562 EM";
phydata->phy_getspeed = getspeed_82553;
break;
default:
phydata->description = "Unknown INTEL";
break;
}
break;
case OUI_ICS:
switch (MII_PHY_MODEL(phydata->id)) {
case ICS_1890:
case ICS_1889:
phydata->phy_postreset = postreset_ICS1890;
phydata->description = "ICS 1890/1889 PHY";
phydata->phy_getspeed = getspeed_ICS1890;
phydata->phy_dump = dump_ICS1890;
break;
default:
phydata->description = "ICS Unknown PHY";
break;
}
break;
default:
phydata->description = "Unknown PHY";
phydata->phy_dump = NULL;
phydata->phy_getspeed = getspeed_generic;
break;
}
(void) mii_sync(mac, phy);
if (ddi_getprop(DDI_DEV_T_NONE, mac->mii_dip, DDI_PROP_DONTPASS,
"dump-phy", 0))
(void) mii_dump_phy(mac, phy);
return (MII_SUCCESS);
}
int
mii_reset_phy(mii_handle_t mac, int phy, enum mii_wait_type wait)
{
int i;
struct phydata *phyd;
ushort_t control;
if (!(phyd = mii_get_valid_phydata(mac, phy)))
return (MII_PARAM);
mac->mii_write(mac->mii_dip, phy, MII_CONTROL,
phyd->control | MII_CONTROL_RESET);
phyd->state = phy_state_unknown;
if (wait == mii_wait_interrupt || wait == mii_wait_user) {
for (i = 100; i--; ) {
control = mac->mii_read(mac->mii_dip, phy, MII_CONTROL);
if (!(control & MII_CONTROL_RESET))
break;
drv_usecwait(10);
}
if (i)
goto reset_completed;
}
if (wait == mii_wait_user) {
for (i = 50; i--; ) {
control = mac->mii_read(mac->mii_dip, phy, MII_CONTROL);
if (!(control & MII_CONTROL_RESET))
break;
delay(drv_usectohz(10000));
}
if (i)
goto reset_completed;
return (MII_HARDFAIL);
}
return (MII_TIMEOUT);
reset_completed:
(void) mii_sync(mac, phy);
return (MII_SUCCESS);
}
int
mii_sync(mii_handle_t mac, int phy)
{
struct phydata *phyd = mac->phys[phy];
int len, i, numprop;
struct regprop {
int reg;
int value;
} *regprop;
#ifdef MIIDEBUG
if (miidebug & MIITRACE)
cmn_err(CE_NOTE, "mii_sync (phy addr %d)", phy);
#endif
len = 0;
if (ddi_getlongprop(DDI_DEV_T_ANY, mac->mii_dip,
DDI_PROP_DONTPASS, "phy-registers", (caddr_t)®prop,
&len) == DDI_PROP_SUCCESS) {
numprop = len / sizeof (struct regprop);
for (i = 0; i < numprop; i++) {
mac->mii_write(mac->mii_dip, phy,
regprop[i].reg, regprop[i].value);
#ifdef MIIDEBUG
if (miidebug & MIITRACE)
cmn_err(CE_NOTE, "PHY Write reg %d=%x",
regprop[i].reg, regprop[i].value);
#endif
}
kmem_free(regprop, len);
} else {
mac->mii_write(mac->mii_dip, phy, MII_CONTROL, phyd->control);
if (phyd->phy_postreset)
phyd->phy_postreset(mac, phy);
if (phyd->fix_speed || phyd->fix_duplex) {
(void) mii_fixspeed(mac, phy, phyd->fix_speed,
phyd->fix_duplex);
}
}
return (MII_SUCCESS);
}
int
mii_disable_fullduplex(mii_handle_t mac, int phy)
{
void *dip = mac->mii_dip;
ushort_t expansion, miiadvert;
const int fullduplex = MII_ABILITY_10BASE_T_FD
| MII_ABILITY_100BASE_TX_FD;
if (!(mac->mii_read(dip, phy, MII_STATUS) & MII_STATUS_CANAUTONEG)) {
return (MII_SUCCESS);
}
miiadvert = mac->mii_read(dip, phy, MII_AN_ADVERT);
if (miiadvert & fullduplex)
mac->mii_write(dip, phy, MII_AN_ADVERT,
miiadvert & ~fullduplex);
expansion = mac->mii_read(dip, phy, MII_AN_EXPANSION);
if (expansion & MII_AN_EXP_LPCANAN)
return (mii_rsan(mac, phy, mii_wait_none));
else
return (MII_SUCCESS);
}
int
mii_autoneg_enab(mii_handle_t mac, int phy)
{
struct phydata *phyd;
if (!(phyd = mii_get_valid_phydata(mac, phy)))
return (MII_PARAM);
phyd->control |= MII_CONTROL_ANE;
mac->mii_write(mac->mii_dip, phy, MII_CONTROL, phyd->control);
return (MII_SUCCESS);
}
int
mii_linkup(mii_handle_t mac, int phy)
{
ushort_t status;
mac->mii_read(mac->mii_dip, phy, MII_STATUS);
status = mac->mii_read(mac->mii_dip, phy, MII_STATUS);
if (status != 0xffff && (status & MII_STATUS_LINKUP))
return (1);
else
return (0);
}
int
mii_getspeed(mii_handle_t mac, int phy, int *speed, int *fulld)
{
struct phydata *phyd;
if (!(phyd = mii_get_valid_phydata(mac, phy)))
return (MII_PARAM);
if (!(phyd->control & MII_CONTROL_ANE)) {
*speed = phyd->control & MII_CONTROL_100MB ? 100:10;
*fulld = phyd->control & MII_CONTROL_FDUPLEX ? 1:0;
return (MII_SUCCESS);
}
if (!phyd->phy_getspeed)
return (MII_NOTSUPPORTED);
return (phyd->phy_getspeed(mac, phy, speed, fulld));
}
int
mii_fixspeed(mii_handle_t mac, int phy, int speed, int fullduplex)
{
struct phydata *phyd;
#ifdef MIIDEBUG
cmn_err(CE_CONT, "!%s: setting speed to %d, %s duplex",
ddi_get_name(mac->mii_dip), speed,
fullduplex ? "full" : "half");
#endif
if (!(phyd = mii_get_valid_phydata(mac, phy)))
return (MII_PARAM);
phyd->control &= ~MII_CONTROL_ANE;
if (speed == 100)
phyd->control |= MII_CONTROL_100MB;
else if (speed == 10)
phyd->control &= ~MII_CONTROL_100MB;
else
cmn_err(CE_NOTE, "%s: mii does not support %d Mb/s speed",
ddi_get_name(mac->mii_dip), speed);
if (fullduplex)
phyd->control |= MII_CONTROL_FDUPLEX;
else
phyd->control &= ~MII_CONTROL_FDUPLEX;
mac->mii_write(mac->mii_dip, phy, MII_CONTROL, phyd->control);
phyd->fix_speed = speed;
phyd->fix_duplex = fullduplex;
return (MII_SUCCESS);
}
int
mii_isolate(mii_handle_t mac, int phy)
{
struct phydata *phyd;
if (!(phyd = mii_get_valid_phydata(mac, phy)))
return (MII_PARAM);
phyd->control |= MII_CONTROL_ISOLATE;
mac->mii_write(mac->mii_dip, phy, MII_CONTROL, phyd->control);
drv_usecwait(50);
return (MII_SUCCESS);
}
int
mii_unisolate(mii_handle_t mac, int phy)
{
struct phydata *phyd;
if (!(phyd = mii_get_valid_phydata(mac, phy)))
return (MII_PARAM);
phyd->control &= ~MII_CONTROL_ISOLATE;
mac->mii_write(mac->mii_dip, phy, MII_CONTROL, phyd->control);
return (MII_SUCCESS);
}
int
mii_rsan(mii_handle_t mac, int phy, enum mii_wait_type wait)
{
int i;
void *dip;
struct phydata *phyd;
if (wait == mii_wait_interrupt ||
!(phyd = mii_get_valid_phydata(mac, phy)))
return (MII_PARAM);
if (phyd->fix_speed)
return (MII_STATE);
dip = mac->mii_dip;
phyd->control |= MII_CONTROL_ANE;
mac->mii_write(dip, phy, MII_CONTROL, phyd->control|MII_CONTROL_RSAN);
if (wait == mii_wait_user) {
for (i = 200; i--; ) {
delay(drv_usectohz(10000));
if (mac->mii_read(dip, phy, MII_STATUS) &
MII_STATUS_ANDONE)
return (MII_SUCCESS);
}
cmn_err(CE_NOTE,
"!%s:Timed out waiting for autonegotiation",
ddi_get_name(mac->mii_dip));
return (MII_TIMEOUT);
}
return (MII_TIMEOUT);
}
int
mii_dump_phy(mii_handle_t mac, int phy)
{
struct phydata *phydat;
char *miiregs[] = {
"Control ",
"Status ",
"PHY Id(H) ",
"PHY Id(L) ",
"Advertisement ",
"Link Partner Ability",
"Expansion ",
"Next Page Transmit ",
0
};
int i;
if (!(phydat = mii_get_valid_phydata(mac, phy)))
return (MII_PARAM);
cmn_err(CE_NOTE, "%s: PHY %d, type %s", ddi_get_name(mac->mii_dip), phy,
phydat->description ? phydat->description: "Unknown");
for (i = 0; miiregs[i]; i++)
cmn_err(CE_NOTE, "%s:\t%x",
miiregs[i], mac->mii_read(mac->mii_dip, phy, i));
if (phydat->phy_dump)
phydat->phy_dump((struct mii_info *)mac, phy);
return (MII_SUCCESS);
}
int
mii_start_portmon(mii_handle_t mac, mii_linkfunc_t notify, kmutex_t *lock)
{
if (mac->mii_linknotify || mac->portmon_timer)
return (MII_STATE);
mac->mii_linknotify = notify;
mac->lock = NULL;
mii_portmon(mac);
mac->lock = lock;
return (MII_SUCCESS);
}
int
mii_stop_portmon(mii_handle_t mac)
{
if (!mac->mii_linknotify || !mac->portmon_timer)
return (MII_STATE);
mac->mii_linknotify = NULL;
mac->lock = NULL;
(void) untimeout(mac->portmon_timer);
mac->portmon_timer = 0;
return (MII_SUCCESS);
}
static void
mii_portmon(mii_handle_t mac)
{
int i;
enum mii_phy_state state;
struct phydata *phydata;
if (!mac->mii_linknotify)
return;
if (mac->lock)
mutex_enter(mac->lock);
for (i = 0; i < 32; i++) {
if ((phydata = mac->phys[i]) != 0) {
state = mii_linkup(mac, i) ?
phy_state_linkup : phy_state_linkdown;
if (state != phydata->state) {
#ifdef MIIDEBUG
if (miidebug)
cmn_err(CE_NOTE, "%s: PHY %d link %s",
ddi_get_name(mac->mii_dip), i,
state == phy_state_linkup ?
"up" : "down");
#endif
phydata->state = state;
mac->mii_linknotify(mac->mii_dip, i, state);
}
}
}
mac->portmon_timer = timeout((void (*)(void*))mii_portmon, (void *)mac,
(clock_t)(5 * drv_usectohz(1000000)));
if (mac->lock)
mutex_exit(mac->lock);
}
void
mii_destroy(mii_handle_t mac)
{
int i;
(void) mii_stop_portmon(mac);
for (i = 0; i < 32; i++)
if (mac->phys[i])
kmem_free(mac->phys[i], sizeof (struct phydata));
kmem_free(mac, sizeof (*mac));
}
static struct phydata *
mii_get_valid_phydata(mii_handle_t mac, int phy)
{
if (!mac || phy > 31 || phy < 0 || !mac->phys[phy]) {
ASSERT(!"MII: Bad invocation");
return (NULL);
}
return (mac->phys[phy]);
}
#define BIT(bit, value) ((value) & (1<<(bit)))
static void
dump_NS83840(mii_handle_t mac, int phy)
{
ushort_t reg;
void *dip;
dip = mac->mii_dip;
cmn_err(CE_NOTE, "Disconnect count: %x",
mac->mii_read(dip, phy, 0x12));
cmn_err(CE_NOTE, "False Carrier detect count: %x",
mac->mii_read(dip, phy, 0x13));
cmn_err(CE_NOTE, "Receive error count: %x",
mac->mii_read(dip, phy, 0x15));
cmn_err(CE_NOTE, "Silicon revision: %x",
mac->mii_read(dip, phy, 0x16));
cmn_err(CE_NOTE, "PCS Configuration : %x",
mac->mii_read(dip, phy, 0x17));
cmn_err(CE_NOTE, "Loopback, Bypass and Receiver error mask: %x",
mac->mii_read(dip, phy, 0x18));
cmn_err(CE_NOTE, "Wired phy address: %x",
mac->mii_read(dip, phy, 0x19)&0xf);
reg = mac->mii_read(dip, phy, 0x1b);
cmn_err(CE_NOTE, "10 Base T in %s mode",
BIT(9, reg) ? "serial":"nibble");
cmn_err(CE_NOTE, "%slink pulses, %sheartbeat, %s,%s squelch,jabber %s",
BIT(reg, 5) ? "" : "no ",
BIT(reg, 4) ? "" : "no ",
BIT(reg, 3) ? "UTP" : "STP",
BIT(reg, 2) ? "low" : "normal",
BIT(reg, 0) ? "enabled" : "disabled");
}
static int
getspeed_NS83840(mii_handle_t mac, int phy, int *speed, int *fulld)
{
int exten = mac->mii_read(mac->mii_dip, phy, MII_AN_EXPANSION);
if (exten & MII_AN_EXP_LPCANAN) {
int lpable, anadv, mask;
lpable = mac->mii_read(mac->mii_dip, phy, MII_AN_LPABLE);
anadv = mac->mii_read(mac->mii_dip, phy, MII_AN_ADVERT);
mask = anadv & lpable;
if (mask & MII_ABILITY_100BASE_TX_FD) {
*speed = 100;
*fulld = 1;
} else if (mask & MII_ABILITY_100BASE_T4) {
*speed = 100;
*fulld = 0;
} else if (mask & MII_ABILITY_100BASE_TX) {
*speed = 100;
*fulld = 0;
} else if (mask & MII_ABILITY_10BASE_T_FD) {
*speed = 10;
*fulld = 1;
} else if (mask & MII_ABILITY_10BASE_T) {
*speed = 10;
*fulld = 0;
}
} else {
int addr = mac->mii_read(mac->mii_dip, phy, MII_83840_ADDR);
*speed = (addr & NS83840_ADDR_SPEED10) ? 10:100;
*fulld = 0;
}
return (0);
}
static int
getspeed_82553(mii_handle_t mac, int phy, int *speed, int *fulld)
{
int ex0 = mac->mii_read(mac->mii_dip, phy, MII_82553_EX0);
*fulld = (ex0 & I82553_EX0_FDUPLEX) ? 1:0;
*speed = (ex0 & I82553_EX0_100MB) ? 100:10;
return (0);
}
static int
getspeed_ICS1890(mii_handle_t mac, int phy, int *speed, int *fulld)
{
ushort_t quickpoll = mac->mii_read(mac->mii_dip, phy, ICS_QUICKPOLL);
*speed = (quickpoll & ICS_QUICKPOLL_100MB) ? 100 : 10;
*fulld = (quickpoll & ICS_QUICKPOLL_FDUPLEX) ? 1 : 0;
return (0);
}
static void
dump_ICS1890(mii_handle_t mac, int phy)
{
ushort_t quickpoll = mac->mii_read(mac->mii_dip, phy, ICS_QUICKPOLL);
cmn_err(CE_NOTE, "QuickPoll:%x (Speed:%d FullDuplex:%c) ",
quickpoll,
quickpoll & ICS_QUICKPOLL_100MB ? 100:10,
quickpoll & ICS_QUICKPOLL_FDUPLEX ? 'Y' : 'N');
}
static void
postreset_NS83840(mii_handle_t mac, int phy)
{
ushort_t reg;
struct phydata *phyd = mac->phys[phy];
reg = mac->mii_read(mac->mii_dip, phy, 23) | (1<<10) | (1<<5);
mac->mii_write(mac->mii_dip, phy, 23, reg);
if (!phyd->fix_speed) {
#ifdef MIIDEBUG
if (miidebug & MIICOMPAT)
cmn_err(CE_NOTE, "Reset value of AN_ADV reg:%x",
mac->mii_read(mac->mii_dip, phy, MII_AN_ADVERT));
#endif
mac->mii_write(mac->mii_dip, phy, MII_AN_ADVERT, 0x1e1);
}
}
void
postreset_ICS1890(mii_handle_t mac, int phy)
{
(void) mii_unisolate(mac, phy);
}
static int
getspeed_generic(mii_handle_t mac, int phy, int *speed, int *fulld)
{
int exten = mac->mii_read(mac->mii_dip, phy, MII_AN_EXPANSION);
if (exten & MII_AN_EXP_LPCANAN) {
int lpable, anadv, mask;
lpable = mac->mii_read(mac->mii_dip, phy, MII_AN_LPABLE);
anadv = mac->mii_read(mac->mii_dip, phy, MII_AN_ADVERT);
mask = anadv & lpable;
if (mask & MII_ABILITY_100BASE_TX_FD) {
*speed = 100;
*fulld = 1;
} else if (mask & MII_ABILITY_100BASE_T4) {
*speed = 100;
*fulld = 0;
} else if (mask & MII_ABILITY_100BASE_TX) {
*speed = 100;
*fulld = 0;
} else if (mask & MII_ABILITY_10BASE_T_FD) {
*speed = 10;
*fulld = 1;
} else if (mask & MII_ABILITY_10BASE_T) {
*speed = 10;
*fulld = 0;
}
} else {
*speed = 0;
*fulld = 0;
}
return (MII_SUCCESS);
}