diff --git a/src/drivers/net/natsemi.c b/src/drivers/net/natsemi.c index 43b7f2df..e69a8f3f 100644 --- a/src/drivers/net/natsemi.c +++ b/src/drivers/net/natsemi.c @@ -79,6 +79,7 @@ #include #include #include +#include #define TX_RING_SIZE 4 #define NUM_RX_DESC 4 @@ -122,6 +123,28 @@ struct natsemi_nic { struct nvo_block nvo; }; + +/* + * Support for fibre connections on Am79C874: + * This phy needs a special setup when connected to a fibre cable. + * http://www.amd.com/files/connectivitysolutions/networking/archivednetworking/22235.pdf + */ +#define PHYID_AM79C874 0x0022561b + +enum { + MII_MCTRL = 0x15, /* mode control register */ + MII_FX_SEL = 0x0001, /* 100BASE-FX (fiber) */ + MII_EN_SCRM = 0x0004, /* enable scrambler (tp) */ +}; + + + +/* values we might find in the silicon revision register */ +#define SRR_DP83815_C 0x0302 +#define SRR_DP83815_D 0x0403 +#define SRR_DP83816_A4 0x0504 +#define SRR_DP83816_A5 0x0505 + /* NATSEMI: Offsets to the device registers. * Unlike software-only systems, device drivers interact with complex hardware. * It's not useful to define symbolic names for every register bit in the @@ -351,6 +374,143 @@ static void nat_reset ( struct natsemi_nic *nat ) { outl ( SavedClkRun, nat->ioaddr + ClkRun ); } + +static int mdio_read(struct net_device *netdev, int reg) { + struct natsemi_nic *nat = netdev->priv; + + /* The 83815 series has two ports: + * - an internal transceiver + * - an external mii bus + */ + return inw(nat->ioaddr+BasicControl+(reg<<2)); +} + +static void mdio_write(struct net_device *netdev, int reg, u16 data) { + struct natsemi_nic *nat = netdev->priv; + + /* The 83815 series has an internal transceiver; handle separately */ + writew(data, nat->ioaddr+BasicControl+(reg<<2)); +} + +static void init_phy_fixup(struct net_device *netdev) { + struct natsemi_nic *nat = netdev->priv; + int i; + u32 cfg; + u16 tmp; + uint16_t advertising; + int mii; + + /* restore stuff lost when power was out */ + tmp = mdio_read(netdev, MII_BMCR); + advertising= mdio_read(netdev, MII_ADVERTISE); +// if (np->autoneg == AUTONEG_ENABLE) { + /* renegotiate if something changed */ + if ((tmp & BMCR_ANENABLE) == 0 + || advertising != mdio_read(netdev, MII_ADVERTISE)) + { + /* turn on autonegotiation and force negotiation */ + tmp |= (BMCR_ANENABLE | BMCR_ANRESTART); + mdio_write(netdev, MII_ADVERTISE, advertising); + } +// } else { + /* turn off auto negotiation, set speed and duplexity */ +// tmp &= ~(BMCR_ANENABLE | BMCR_SPEED100 | BMCR_FULLDPLX); +// if (np->speed == SPEED_100) +/// tmp |= BMCR_SPEED100; +// if (np->duplex == DUPLEX_FULL) +// tmp |= BMCR_FULLDPLX; + /* + * Note: there is no good way to inform the link partner + * that our capabilities changed. The user has to unplug + * and replug the network cable after some changes, e.g. + * after switching from 10HD, autoneg off to 100 HD, + * autoneg off. + */ +// } + mdio_write(netdev, MII_BMCR, tmp); + inl(nat->ioaddr + ChipConfig); + udelay(1); + + /* find out what phy this is */ + mii = (mdio_read(netdev, MII_PHYSID1) << 16) + + mdio_read(netdev, MII_PHYSID2); + + /* handle external phys here */ + switch (mii) { + case PHYID_AM79C874: + /* phy specific configuration for fibre/tp operation */ + tmp = mdio_read(netdev, MII_MCTRL); + tmp &= ~(MII_FX_SEL | MII_EN_SCRM); + //if (dev->if_port == PORT_FIBRE) + // tmp |= MII_FX_SEL; + //else + tmp |= MII_EN_SCRM; + mdio_write(netdev, MII_MCTRL, tmp); + break; + default: + break; + } + cfg = inl(nat->ioaddr + ChipConfig); + if (cfg & CfgExtPhy) + return; + + /* On page 78 of the spec, they recommend some settings for "optimum + performance" to be done in sequence. These settings optimize some + of the 100Mbit autodetection circuitry. They say we only want to + do this for rev C of the chip, but engineers at NSC (Bradley + Kennedy) recommends always setting them. If you don't, you get + errors on some autonegotiations that make the device unusable. + + It seems that the DSP needs a few usec to reinitialize after + the start of the phy. Just retry writing these values until they + stick. + */ + uint32_t srr = inl(nat->ioaddr + SiliconRev); + int NATSEMI_HW_TIMEOUT = 400; + for (i=0;iioaddr + PGSEL); + outw(PMDCSR_VAL, nat->ioaddr + PMDCSR); + outw(TSTDAT_VAL, nat->ioaddr + TSTDAT); + dspcfg = (srr <= SRR_DP83815_C)? + DSPCFG_VAL : (DSPCFG_COEF | readw(nat->ioaddr + DSPCFG)); + outw(dspcfg, nat->ioaddr + DSPCFG); + outw(SDCFG_VAL, nat->ioaddr + SDCFG); + outw(0, nat->ioaddr + PGSEL); + inl(nat->ioaddr + ChipConfig); + udelay(10); + + outw(1, nat->ioaddr + PGSEL); + dspcfg_1 = readw(nat->ioaddr + DSPCFG); + outw(0, nat->ioaddr + PGSEL); + if (dspcfg == dspcfg_1) + break; + } + + if (i==NATSEMI_HW_TIMEOUT) { + DBG ( "Natsemi: DSPCFG mismatch after retrying for" + " %d usec.\n", i*10); + } else { + DBG ( "NATSEMI: DSPCFG accepted after %d usec.\n", + i*10); + } + /* + * Enable PHY Specific event based interrupts. Link state change + * and Auto-Negotiation Completion are among the affected. + * Read the intr status to clear it (needed for wake events). + */ + inw(nat->ioaddr + MIntrStatus); + //MICRIntEn = 0x2 + outw(0x2, nat->ioaddr + MIntrCtrl); +} + + +/* + * Patch up for fixing CRC errors. + * adapted from linux natsemi driver + * + */ static void do_cable_magic ( struct net_device *netdev ) { struct natsemi_nic *nat = netdev->priv; uint16_t data; @@ -478,6 +638,7 @@ static int nat_open ( struct net_device *netdev ) { * testing this feature is required or not */ do_cable_magic ( netdev ); + init_phy_fixup ( netdev ); /* mask the interrupts. note interrupt is not enabled here