- Don't access VPD even if hardware advertised the capability.
It seems that some revisions of the controllers hang while accessing the VPD. Because VPD access routine is now unused, nuke it. - Let TWSI reload EEPROM if VPD capability is detected. Reloading the EEPROM will also set the Ethernet address, so age(4) now reads AGE_PAR0 and AGE_PAR1 register to get the Ethernet address. This removes removes a lot of hacks and enhance readability a lot. - Double PHY reset timeout as it takes more time to take the PHY out of power-saving state. - Explicitly check power-saving state by checking undocumented PHY registers. If link is not up, poke undocumented registers to take PHY out of power-saving state. This is the same thing done by the Linux driver. - Don't rely on auto-clearing feature of master reset bit, just wait 1ms and check idle status of MAC. From FreeBSD via OpenBSD.
This commit is contained in:
parent
b297f62c5a
commit
6d027845ab
@ -1,4 +1,4 @@
|
|||||||
/* $NetBSD: if_age.c,v 1.29 2009/08/04 13:17:55 cegger Exp $ */
|
/* $NetBSD: if_age.c,v 1.30 2009/08/05 12:07:16 cegger Exp $ */
|
||||||
/* $OpenBSD: if_age.c,v 1.1 2009/01/16 05:00:34 kevlo Exp $ */
|
/* $OpenBSD: if_age.c,v 1.1 2009/01/16 05:00:34 kevlo Exp $ */
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
@ -31,7 +31,7 @@
|
|||||||
/* Driver for Attansic Technology Corp. L1 Gigabit Ethernet. */
|
/* Driver for Attansic Technology Corp. L1 Gigabit Ethernet. */
|
||||||
|
|
||||||
#include <sys/cdefs.h>
|
#include <sys/cdefs.h>
|
||||||
__KERNEL_RCSID(0, "$NetBSD: if_age.c,v 1.29 2009/08/04 13:17:55 cegger Exp $");
|
__KERNEL_RCSID(0, "$NetBSD: if_age.c,v 1.30 2009/08/05 12:07:16 cegger Exp $");
|
||||||
|
|
||||||
#include "bpfilter.h"
|
#include "bpfilter.h"
|
||||||
#include "vlan.h"
|
#include "vlan.h"
|
||||||
@ -97,7 +97,6 @@ static void age_mediastatus(struct ifnet *, struct ifmediareq *);
|
|||||||
static int age_mediachange(struct ifnet *);
|
static int age_mediachange(struct ifnet *);
|
||||||
|
|
||||||
static int age_intr(void *);
|
static int age_intr(void *);
|
||||||
static int age_read_vpd_word(struct age_softc *, uint32_t, uint32_t, uint32_t *);
|
|
||||||
static int age_dma_alloc(struct age_softc *);
|
static int age_dma_alloc(struct age_softc *);
|
||||||
static void age_dma_free(struct age_softc *);
|
static void age_dma_free(struct age_softc *);
|
||||||
static void age_get_macaddr(struct age_softc *, uint8_t[]);
|
static void age_get_macaddr(struct age_softc *, uint8_t[]);
|
||||||
@ -558,37 +557,11 @@ back:
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
age_read_vpd_word(struct age_softc *sc, uint32_t vpdc, uint32_t offset,
|
|
||||||
uint32_t *word)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
pcireg_t rv;
|
|
||||||
|
|
||||||
pci_conf_write(sc->sc_pct, sc->sc_pcitag, PCI_VPD_ADDRESS(vpdc),
|
|
||||||
offset << PCI_VPD_ADDRESS_SHIFT);
|
|
||||||
for (i = AGE_TIMEOUT; i > 0; i--) {
|
|
||||||
DELAY(10);
|
|
||||||
rv = pci_conf_read(sc->sc_pct, sc->sc_pcitag,
|
|
||||||
PCI_VPD_ADDRESS(vpdc));
|
|
||||||
if ((rv & PCI_VPD_OPFLAG) == PCI_VPD_OPFLAG)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (i == 0) {
|
|
||||||
printf("%s: VPD read timeout!\n", device_xname(sc->sc_dev));
|
|
||||||
*word = 0;
|
|
||||||
return ETIMEDOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
*word = pci_conf_read(sc->sc_pct, sc->sc_pcitag, PCI_VPD_DATAREG(vpdc));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
age_get_macaddr(struct age_softc *sc, uint8_t eaddr[])
|
age_get_macaddr(struct age_softc *sc, uint8_t eaddr[])
|
||||||
{
|
{
|
||||||
uint32_t ea[2], off, reg, word;
|
uint32_t ea[2], reg;
|
||||||
int vpd_error, match, vpdc;
|
int i, vpdc;
|
||||||
|
|
||||||
reg = CSR_READ_4(sc, AGE_SPI_CTRL);
|
reg = CSR_READ_4(sc, AGE_SPI_CTRL);
|
||||||
if ((reg & SPI_VPD_ENB) != 0) {
|
if ((reg & SPI_VPD_ENB) != 0) {
|
||||||
@ -597,67 +570,22 @@ age_get_macaddr(struct age_softc *sc, uint8_t eaddr[])
|
|||||||
CSR_WRITE_4(sc, AGE_SPI_CTRL, reg);
|
CSR_WRITE_4(sc, AGE_SPI_CTRL, reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
vpd_error = 0;
|
if (pci_get_capability(sc->sc_pct, sc->sc_pcitag,
|
||||||
ea[0] = ea[1] = 0;
|
PCI_CAP_VPD, &vpdc, NULL)) {
|
||||||
if ((vpd_error = pci_get_capability(sc->sc_pct, sc->sc_pcitag,
|
|
||||||
PCI_CAP_VPD, &vpdc, NULL))) {
|
|
||||||
/*
|
/*
|
||||||
* PCI VPD capability exists, but it seems that it's
|
* PCI VPD capability found, let TWSI reload EEPROM.
|
||||||
* not in the standard form as stated in PCI VPD
|
* This will set Ethernet address of controller.
|
||||||
* specification such that driver could not use
|
|
||||||
* pci_get_vpd_readonly(9) with keyword 'NA'.
|
|
||||||
* Search VPD data starting at address 0x0100. The data
|
|
||||||
* should be used as initializers to set AGE_PAR0,
|
|
||||||
* AGE_PAR1 register including other PCI configuration
|
|
||||||
* registers.
|
|
||||||
*/
|
*/
|
||||||
word = 0;
|
CSR_WRITE_4(sc, AGE_TWSI_CTRL, CSR_READ_4(sc, AGE_TWSI_CTRL) |
|
||||||
match = 0;
|
TWSI_CTRL_SW_LD_START);
|
||||||
reg = 0;
|
for (i = 100; i > 0; i++) {
|
||||||
for (off = AGE_VPD_REG_CONF_START; off < AGE_VPD_REG_CONF_END;
|
DELAY(1000);
|
||||||
off += sizeof(uint32_t)) {
|
reg = CSR_READ_4(sc, AGE_TWSI_CTRL);
|
||||||
vpd_error = age_read_vpd_word(sc, vpdc, off, &word);
|
if ((reg & TWSI_CTRL_SW_LD_START) == 0)
|
||||||
if (vpd_error != 0)
|
|
||||||
break;
|
|
||||||
if (match != 0) {
|
|
||||||
switch (reg) {
|
|
||||||
case AGE_PAR0:
|
|
||||||
ea[0] = word;
|
|
||||||
break;
|
|
||||||
case AGE_PAR1:
|
|
||||||
ea[1] = word;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match = 0;
|
if (i == 0)
|
||||||
} else if ((word & 0xFF) == AGE_VPD_REG_CONF_SIG) {
|
printf("%s: reloading EEPROM timeout!\n",
|
||||||
match = 1;
|
|
||||||
reg = word >> 16;
|
|
||||||
} else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (off >= AGE_VPD_REG_CONF_END)
|
|
||||||
vpd_error = ENOENT;
|
|
||||||
if (vpd_error == 0) {
|
|
||||||
/*
|
|
||||||
* Don't blindly trust ethernet address obtained
|
|
||||||
* from VPD. Check whether ethernet address is
|
|
||||||
* valid one. Otherwise fall-back to reading
|
|
||||||
* PAR register.
|
|
||||||
*/
|
|
||||||
ea[1] &= 0xFFFF;
|
|
||||||
if ((ea[0] == 0 && ea[1] == 0) ||
|
|
||||||
(ea[0] == 0xFFFFFFFF && ea[1] == 0xFFFF)) {
|
|
||||||
if (agedebug)
|
|
||||||
printf("%s: invalid ethernet address "
|
|
||||||
"returned from VPD.\n",
|
|
||||||
device_xname(sc->sc_dev));
|
|
||||||
vpd_error = EINVAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (vpd_error != 0 && (agedebug))
|
|
||||||
printf("%s: VPD access failure!\n",
|
|
||||||
device_xname(sc->sc_dev));
|
device_xname(sc->sc_dev));
|
||||||
} else {
|
} else {
|
||||||
if (agedebug)
|
if (agedebug)
|
||||||
@ -665,26 +593,9 @@ age_get_macaddr(struct age_softc *sc, uint8_t eaddr[])
|
|||||||
device_xname(sc->sc_dev));
|
device_xname(sc->sc_dev));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* It seems that L1 also provides a way to extract ethernet
|
|
||||||
* address via SPI flash interface. Because SPI flash memory
|
|
||||||
* device of different vendors vary in their instruction
|
|
||||||
* codes for read ID instruction, it's very hard to get
|
|
||||||
* instructions codes without detailed information for the
|
|
||||||
* flash memory device used on ethernet controller. To simplify
|
|
||||||
* code, just read AGE_PAR0/AGE_PAR1 register to get ethernet
|
|
||||||
* address which is supposed to be set by hardware during
|
|
||||||
* power on reset.
|
|
||||||
*/
|
|
||||||
if (vpd_error != 0) {
|
|
||||||
/*
|
|
||||||
* VPD is mapped to SPI flash memory or BIOS set it.
|
|
||||||
*/
|
|
||||||
ea[0] = CSR_READ_4(sc, AGE_PAR0);
|
ea[0] = CSR_READ_4(sc, AGE_PAR0);
|
||||||
ea[1] = CSR_READ_4(sc, AGE_PAR1);
|
ea[1] = CSR_READ_4(sc, AGE_PAR1);
|
||||||
}
|
|
||||||
|
|
||||||
ea[1] &= 0xFFFF;
|
|
||||||
eaddr[0] = (ea[1] >> 8) & 0xFF;
|
eaddr[0] = (ea[1] >> 8) & 0xFF;
|
||||||
eaddr[1] = (ea[1] >> 0) & 0xFF;
|
eaddr[1] = (ea[1] >> 0) & 0xFF;
|
||||||
eaddr[2] = (ea[0] >> 24) & 0xFF;
|
eaddr[2] = (ea[0] >> 24) & 0xFF;
|
||||||
@ -696,11 +607,79 @@ age_get_macaddr(struct age_softc *sc, uint8_t eaddr[])
|
|||||||
static void
|
static void
|
||||||
age_phy_reset(struct age_softc *sc)
|
age_phy_reset(struct age_softc *sc)
|
||||||
{
|
{
|
||||||
|
uint16_t reg, pn;
|
||||||
|
int i, linkup;
|
||||||
|
|
||||||
/* Reset PHY. */
|
/* Reset PHY. */
|
||||||
CSR_WRITE_4(sc, AGE_GPHY_CTRL, GPHY_CTRL_RST);
|
CSR_WRITE_4(sc, AGE_GPHY_CTRL, GPHY_CTRL_RST);
|
||||||
DELAY(1000);
|
DELAY(2000);
|
||||||
CSR_WRITE_4(sc, AGE_GPHY_CTRL, GPHY_CTRL_CLR);
|
CSR_WRITE_4(sc, AGE_GPHY_CTRL, GPHY_CTRL_CLR);
|
||||||
|
DELAY(2000);
|
||||||
|
|
||||||
|
#define ATPHY_DBG_ADDR 0x1D
|
||||||
|
#define ATPHY_DBG_DATA 0x1E
|
||||||
|
#define ATPHY_CDTC 0x16
|
||||||
|
#define PHY_CDTC_ENB 0x0001
|
||||||
|
#define PHY_CDTC_POFF 8
|
||||||
|
#define ATPHY_CDTS 0x1C
|
||||||
|
#define PHY_CDTS_STAT_OK 0x0000
|
||||||
|
#define PHY_CDTS_STAT_SHORT 0x0100
|
||||||
|
#define PHY_CDTS_STAT_OPEN 0x0200
|
||||||
|
#define PHY_CDTS_STAT_INVAL 0x0300
|
||||||
|
#define PHY_CDTS_STAT_MASK 0x0300
|
||||||
|
|
||||||
|
/* Check power saving mode. Magic from Linux. */
|
||||||
|
age_miibus_writereg(sc->sc_dev, sc->age_phyaddr, MII_BMCR, BMCR_RESET);
|
||||||
|
for (linkup = 0, pn = 0; pn < 4; pn++) {
|
||||||
|
age_miibus_writereg(sc->sc_dev, sc->age_phyaddr, ATPHY_CDTC,
|
||||||
|
(pn << PHY_CDTC_POFF) | PHY_CDTC_ENB);
|
||||||
|
for (i = 200; i > 0; i--) {
|
||||||
DELAY(1000);
|
DELAY(1000);
|
||||||
|
reg = age_miibus_readreg(sc->sc_dev, sc->age_phyaddr,
|
||||||
|
ATPHY_CDTC);
|
||||||
|
if ((reg & PHY_CDTC_ENB) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
DELAY(1000);
|
||||||
|
reg = age_miibus_readreg(sc->sc_dev, sc->age_phyaddr,
|
||||||
|
ATPHY_CDTS);
|
||||||
|
if ((reg & PHY_CDTS_STAT_MASK) != PHY_CDTS_STAT_OPEN) {
|
||||||
|
linkup++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
age_miibus_writereg(sc->sc_dev, sc->age_phyaddr, MII_BMCR,
|
||||||
|
BMCR_RESET | BMCR_AUTOEN | BMCR_STARTNEG);
|
||||||
|
if (linkup == 0) {
|
||||||
|
age_miibus_writereg(sc->sc_dev, sc->age_phyaddr,
|
||||||
|
ATPHY_DBG_ADDR, 0);
|
||||||
|
age_miibus_writereg(sc->sc_dev, sc->age_phyaddr,
|
||||||
|
ATPHY_DBG_DATA, 0x124E);
|
||||||
|
age_miibus_writereg(sc->sc_dev, sc->age_phyaddr,
|
||||||
|
ATPHY_DBG_ADDR, 1);
|
||||||
|
reg = age_miibus_readreg(sc->sc_dev, sc->age_phyaddr,
|
||||||
|
ATPHY_DBG_DATA);
|
||||||
|
age_miibus_writereg(sc->sc_dev, sc->age_phyaddr,
|
||||||
|
ATPHY_DBG_DATA, reg | 0x03);
|
||||||
|
/* XXX */
|
||||||
|
DELAY(1500 * 1000);
|
||||||
|
age_miibus_writereg(sc->sc_dev, sc->age_phyaddr,
|
||||||
|
ATPHY_DBG_ADDR, 0);
|
||||||
|
age_miibus_writereg(sc->sc_dev, sc->age_phyaddr,
|
||||||
|
ATPHY_DBG_DATA, 0x024E);
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef ATPHY_DBG_ADDR
|
||||||
|
#undef ATPHY_DBG_DATA
|
||||||
|
#undef ATPHY_CDTC
|
||||||
|
#undef PHY_CDTC_ENB
|
||||||
|
#undef PHY_CDTC_POFF
|
||||||
|
#undef ATPHY_CDTS
|
||||||
|
#undef PHY_CDTS_STAT_OK
|
||||||
|
#undef PHY_CDTS_STAT_SHORT
|
||||||
|
#undef PHY_CDTS_STAT_OPEN
|
||||||
|
#undef PHY_CDTS_STAT_INVAL
|
||||||
|
#undef PHY_CDTS_STAT_MASK
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -1616,14 +1595,8 @@ age_reset(struct age_softc *sc)
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
CSR_WRITE_4(sc, AGE_MASTER_CFG, MASTER_RESET);
|
CSR_WRITE_4(sc, AGE_MASTER_CFG, MASTER_RESET);
|
||||||
for (i = AGE_RESET_TIMEOUT; i > 0; i--) {
|
CSR_READ_4(sc, AGE_MASTER_CFG);
|
||||||
DELAY(1);
|
DELAY(1000);
|
||||||
if ((CSR_READ_4(sc, AGE_MASTER_CFG) & MASTER_RESET) == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (i == 0)
|
|
||||||
printf("%s: master reset timeout!\n", device_xname(sc->sc_dev));
|
|
||||||
|
|
||||||
for (i = AGE_RESET_TIMEOUT; i > 0; i--) {
|
for (i = AGE_RESET_TIMEOUT; i > 0; i--) {
|
||||||
if ((reg = CSR_READ_4(sc, AGE_IDLE_STATUS)) == 0)
|
if ((reg = CSR_READ_4(sc, AGE_IDLE_STATUS)) == 0)
|
||||||
break;
|
break;
|
||||||
|
Loading…
Reference in New Issue
Block a user