summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/pci/if_pcn.c78
-rw-r--r--sys/pci/if_pcnreg.h12
2 files changed, 74 insertions, 16 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);
diff --git a/sys/pci/if_pcnreg.h b/sys/pci/if_pcnreg.h
index da7e567..406d438 100644
--- a/sys/pci/if_pcnreg.h
+++ b/sys/pci/if_pcnreg.h
@@ -206,7 +206,7 @@
#define PCN_MODE_RXNOBROAD 0x4000
#define PCN_MODE_PROMISC 0x8000
-/* Settings for PCN_MODE_PORTSEL when ASEL (BCR2[1] is 0 */
+/* Settings for PCN_MODE_PORTSEL when ASEL (BCR2[1]) is 0 */
#define PCN_PORT_AUI 0x0000
#define PCN_PORT_10BASET 0x0080
#define PCN_PORT_GPSI 0x0100
@@ -339,7 +339,11 @@
* MII address register (BCR33)
*/
#define PCN_MIIADDR_REGAD 0x001F
-#define PCN_MIIADDR_PHYADD 0x03E0
+#define PCN_MIIADDR_PHYAD 0x03E0
+
+/* addresses of internal PHYs */
+#define PCN_PHYAD_100BTX 30
+#define PCN_PHYAD_10BT 31
/*
* MII data register (BCR34)
@@ -453,7 +457,8 @@ struct pcn_softc {
void *pcn_intrhand;
device_t pcn_miibus;
u_int8_t pcn_link;
- u_int8_t pcn_phyaddr;
+ int8_t pcn_extphyaddr;
+ int8_t pcn_inst_10bt;
int pcn_if_flags;
int pcn_type;
struct pcn_list_data *pcn_ldata;
@@ -481,7 +486,6 @@ struct pcn_softc {
#define CSR_READ_2(sc, reg) \
bus_space_read_2(sc->pcn_btag, sc->pcn_bhandle, reg)
-
#define PCN_TIMEOUT 1000
#define ETHER_ALIGN 2
#define PCN_RXLEN 1536
OpenPOWER on IntegriCloud