summaryrefslogtreecommitdiffstats
path: root/sys/pci/if_pcn.c
diff options
context:
space:
mode:
authormarius <marius@FreeBSD.org>2006-11-28 01:33:17 +0000
committermarius <marius@FreeBSD.org>2006-11-28 01:33:17 +0000
commitafd1d490a36553dd8602bec755aa243b0243287f (patch)
tree875ba3786f2575b0a24e543acf8d7b333097a1df /sys/pci/if_pcn.c
parent90cd17ca93f04a8bbe97d57bf53d0a4cf497bafd (diff)
downloadFreeBSD-src-afd1d490a36553dd8602bec755aa243b0243287f.zip
FreeBSD-src-afd1d490a36553dd8602bec755aa243b0243287f.tar.gz
- Clear the PCN_MISC_ASEL bit so the media port can be actually set
via the PCN_CSR_MODE register. Along with sys/dev/mii/nsphy.c 1.26 this fixes the case of certain Am79c971-based HP cards and on-board ones in IBM machines reporting link but not actually passing any traffic. [1] - Add support for the internal 10baseT PHY, which actually is used on at least said HP cards (together with an external DP83840A in a multiple PHYs configuration). With cards that don't make use of this internal PHY it'll also show up in FreeBSD but not cause any harm. This is still missing support for multiple PHYs configuration using the internal 100baseTX and/or HomePNA PHYs together with external PHYs or multiple external PHYs though. - In pcn_ifmedia_upd() call pcn_reset() as otherwise the Am79C971 of at least said HP cards can wedge when switching from the internal 10baseT PHY to the external PHY. This means that we need to also initialize and possibly start the chip again in pcn_ifmedia_upd(), which isn't that bad though as for setting the media port the chip has to be powered down or stopped anyway and unlike documented doesn't take effect until the next initialization. PR: 27995, 25959, 72966 (likely) [1] MFC after: 2 weeks
Diffstat (limited to 'sys/pci/if_pcn.c')
-rw-r--r--sys/pci/if_pcn.c78
1 files changed, 66 insertions, 12 deletions
diff --git a/sys/pci/if_pcn.c b/sys/pci/if_pcn.c
index 50a713c..13bf6cb 100644
--- a/sys/pci/if_pcn.c
+++ b/sys/pci/if_pcn.c
@@ -276,7 +276,18 @@ pcn_miibus_readreg(dev, phy, reg)
sc = device_get_softc(dev);
- if (sc->pcn_phyaddr && phy > sc->pcn_phyaddr)
+ /*
+ * At least Am79C971 with DP83840A wedge when isolating the
+ * external PHY so we can't allow multiple external PHYs.
+ * This needs refinement as there are some Allied Telesyn
+ * card models which use multiple external PHYs.
+ * For internal PHYs it doesn't really matter whether we can
+ * isolate the remaining internal and the external ones in
+ * the PHY drivers as the internal PHYs have to be enabled
+ * individually in PCN_BCR_PHYSEL, PCN_CSR_MODE, etc.
+ */
+ if (phy != PCN_PHYAD_10BT && sc->pcn_extphyaddr != -1 &&
+ phy != sc->pcn_extphyaddr)
return(0);
pcn_bcr_write(sc, PCN_BCR_MIIADDR, reg | (phy << 5));
@@ -284,7 +295,8 @@ pcn_miibus_readreg(dev, phy, reg)
if (val == 0xFFFF)
return(0);
- sc->pcn_phyaddr = phy;
+ if (phy != PCN_PHYAD_10BT && sc->pcn_extphyaddr != -1)
+ sc->pcn_extphyaddr = phy;
return(val);
}
@@ -532,6 +544,8 @@ pcn_attach(dev)
{
u_int32_t eaddr[2];
struct pcn_softc *sc;
+ struct mii_data *mii;
+ struct mii_softc *miisc;
struct ifnet *ifp;
int error = 0, rid;
@@ -613,12 +627,31 @@ pcn_attach(dev)
/*
* Do MII setup.
*/
+ sc->pcn_extphyaddr = -1;
if (mii_phy_probe(dev, &sc->pcn_miibus,
pcn_ifmedia_upd, pcn_ifmedia_sts)) {
device_printf(dev, "MII without any PHY!\n");
error = ENXIO;
goto fail;
}
+ /*
+ * Record the media instances of internal PHYs, which map the
+ * built-in interfaces to the MII, so we can set the active
+ * PHY/port based on the currently selected media.
+ */
+ sc->pcn_inst_10bt = -1;
+ mii = device_get_softc(sc->pcn_miibus);
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list) {
+ switch (miisc->mii_phy) {
+ case PCN_PHYAD_10BT:
+ sc->pcn_inst_10bt = miisc->mii_inst;
+ break;
+ /*
+ * XXX deal with the Am79C97{3,5} internal 100baseT
+ * and the Am79C978 internal HomePNA PHYs.
+ */
+ }
+ }
/*
* Call MI attach routine.
@@ -1151,6 +1184,7 @@ pcn_init_locked(sc)
{
struct ifnet *ifp = sc->pcn_ifp;
struct mii_data *mii = NULL;
+ struct ifmedia_entry *ife;
PCN_LOCK_ASSERT(sc);
@@ -1161,6 +1195,7 @@ pcn_init_locked(sc)
pcn_reset(sc);
mii = device_get_softc(sc->pcn_miibus);
+ ife = mii->mii_media.ifm_cur;
/* Set MAC address */
pcn_csr_write(sc, PCN_CSR_PAR0,
@@ -1183,8 +1218,19 @@ pcn_init_locked(sc)
*/
pcn_list_tx_init(sc);
- /* Set up the mode register. */
- pcn_csr_write(sc, PCN_CSR_MODE, PCN_PORT_MII);
+ /* Clear PCN_MISC_ASEL so we can set the port via PCN_CSR_MODE. */
+ PCN_BCR_CLRBIT(sc, PCN_BCR_MISCCFG, PCN_MISC_ASEL);
+
+ /*
+ * Set up the port based on the currently selected media.
+ * For Am79C978 we've to unconditionally set PCN_PORT_MII and
+ * set the PHY in PCN_BCR_PHYSEL instead.
+ */
+ if (sc->pcn_type != Am79C978 &&
+ IFM_INST(ife->ifm_media) == sc->pcn_inst_10bt)
+ pcn_csr_write(sc, PCN_CSR_MODE, PCN_PORT_10BASET);
+ else
+ pcn_csr_write(sc, PCN_CSR_MODE, PCN_PORT_MII);
/* Set up RX filter. */
pcn_setfilt(ifp);
@@ -1234,6 +1280,7 @@ pcn_init_locked(sc)
PCN_BCR_SETBIT(sc, PCN_BCR_MIICTL, PCN_MIICTL_DANAS);
if (sc->pcn_type == Am79C978)
+ /* XXX support other PHYs? */
pcn_bcr_write(sc, PCN_BCR_PHYSEL,
PCN_PHYSEL_PCNET|PCN_PHY_HOMEPNA);
@@ -1258,19 +1305,26 @@ pcn_ifmedia_upd(ifp)
struct ifnet *ifp;
{
struct pcn_softc *sc;
- struct mii_data *mii;
sc = ifp->if_softc;
- mii = device_get_softc(sc->pcn_miibus);
PCN_LOCK(sc);
+
+ /*
+ * At least Am79C971 with DP83840A can wedge when switching
+ * from the internal 10baseT PHY to the external PHY without
+ * issuing pcn_reset(). For setting the port in PCN_CSR_MODE
+ * the PCnet chip has to be powered down or stopped anyway
+ * and although documented otherwise it doesn't take effect
+ * until the next initialization.
+ */
sc->pcn_link = 0;
- if (mii->mii_instance) {
- struct mii_softc *miisc;
- LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
- mii_phy_reset(miisc);
- }
- mii_mediachg(mii);
+ pcn_stop(sc);
+ pcn_reset(sc);
+ pcn_init_locked(sc);
+ if (ifp->if_snd.ifq_head != NULL)
+ pcn_start_locked(ifp);
+
PCN_UNLOCK(sc);
return(0);
OpenPOWER on IntegriCloud