From 2ba799df13f3ac4f243c0ba64074f7e20377030b Mon Sep 17 00:00:00 2001 From: Warner Losh Date: Tue, 25 Sep 2007 20:47:24 +0000 Subject: [PATCH] Add support for the AX88178 and AX88772 based devices. Submitted by: sam@ Approved by: re@ (blanket) --- sys/dev/usb/if_axe.c | 233 ++++++++++++++++++++++++++++++++++++---- sys/dev/usb/if_axereg.h | 23 +++- 2 files changed, 230 insertions(+), 26 deletions(-) diff --git a/sys/dev/usb/if_axe.c b/sys/dev/usb/if_axe.c index 85bf85ff11f0..84dd0e022697 100644 --- a/sys/dev/usb/if_axe.c +++ b/sys/dev/usb/if_axe.c @@ -34,8 +34,8 @@ __FBSDID("$FreeBSD$"); /* - * ASIX Electronics AX88172 USB 2.0 ethernet driver. Used in the - * LinkSys USB200M and various other adapters. + * ASIX Electronics AX88172/AX88178/AX88778 USB 2.0 ethernet driver. + * Used in the LinkSys USB200M and various other adapters. * * Manuals available from: * http://www.asix.com.tw/datasheet/mac/Ax88172.PDF @@ -69,6 +69,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include @@ -291,13 +292,30 @@ axe_miibus_writereg(device_t dev, int phy, int reg, int val) static void axe_miibus_statchg(device_t dev) { -#ifdef notdef struct axe_softc *sc = device_get_softc(dev); struct mii_data *mii = GET_MII(sc); -#endif - /* doesn't seem to be necessary */ + int val, err; - return; + val = (mii->mii_media_active & IFM_GMASK) == IFM_FDX ? + AXE_MEDIA_FULL_DUPLEX : 0; + if (sc->axe_flags & (AX178|AX772)) { + val |= AXE_178_MEDIA_RX_EN | AXE_178_MEDIA_MAGIC; + + switch (IFM_SUBTYPE(mii->mii_media_active)) { + case IFM_1000_T: + val |= AXE_178_MEDIA_GMII | AXE_178_MEDIA_ENCK; + break; + case IFM_100_TX: + val |= AXE_178_MEDIA_100TX; + break; + case IFM_10_T: + /* doesn't need to be handled */ + break; + } + } + err = axe_cmd(sc, AXE_CMD_WRITE_MEDIA, 0, val, NULL); + if (err) + device_printf(dev, "media change failed, error %d\n", err); } /* @@ -376,6 +394,93 @@ axe_setmulti(struct axe_softc *sc) return; } +static void +axe_ax88178_init(struct axe_softc *sc) +{ + int gpio0 = 0, phymode = 0; + u_int16_t eeprom; + + axe_cmd(sc, AXE_CMD_SROM_WR_ENABLE, 0, 0, NULL); + /* XXX magic */ + axe_cmd(sc, AXE_CMD_SROM_READ, 0, 0x0017, &eeprom); + axe_cmd(sc, AXE_CMD_SROM_WR_DISABLE, 0, 0, NULL); + + /* if EEPROM is invalid we have to use to GPIO0 */ + if (eeprom == 0xffff) { + phymode = 0; + gpio0 = 1; + } else { + phymode = eeprom & 7; + gpio0 = (eeprom & 0x80) ? 0 : 1; + } + + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x008c, NULL); + usbd_delay_ms(sc->axe_udev, 40); + if ((eeprom >> 8) != 1) { + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x003c, NULL); + usbd_delay_ms(sc->axe_udev, 30); + + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x001c, NULL); + usbd_delay_ms(sc->axe_udev, 300); + + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x003c, NULL); + usbd_delay_ms(sc->axe_udev, 30); + } else { + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x0004, NULL); + usbd_delay_ms(sc->axe_udev, 30); + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x000c, NULL); + usbd_delay_ms(sc->axe_udev, 30); + } + + /* soft reset */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, 0, NULL); + usbd_delay_ms(sc->axe_udev, 150); + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_PRL | AXE_178_RESET_MAGIC, NULL); + usbd_delay_ms(sc->axe_udev, 150); + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); +} + +static void +axe_ax88772_init(struct axe_softc *sc) +{ + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x00b0, NULL); + usbd_delay_ms(sc->axe_udev, 40); + + if (sc->axe_phyaddrs[1] == AXE_INTPHY) { + /* ask for embedded PHY */ + axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x01, NULL); + usbd_delay_ms(sc->axe_udev, 10); + + /* power down and reset state, pin reset state */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_CLEAR, NULL); + usbd_delay_ms(sc->axe_udev, 60); + + /* power down/reset state, pin operating state */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL); + usbd_delay_ms(sc->axe_udev, 150); + + /* power up, reset */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_PRL, NULL); + + /* power up, operating */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPRL | AXE_SW_RESET_PRL, NULL); + } else { + /* ask for external PHY */ + axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x00, NULL); + usbd_delay_ms(sc->axe_udev, 10); + + /* power down/reset state, pin operating state */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL); + } + + usbd_delay_ms(sc->axe_udev, 150); + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); +} + static void axe_reset(struct axe_softc *sc) { @@ -416,6 +521,7 @@ axe_attach(device_t self) { struct axe_softc *sc = device_get_softc(self); struct usb_attach_arg *uaa = device_get_ivars(self); + const struct axe_type *type; u_char eaddr[ETHER_ADDR_LEN]; struct ifnet *ifp; usb_interface_descriptor_t *id; @@ -424,6 +530,9 @@ axe_attach(device_t self) sc->axe_udev = uaa->device; sc->axe_dev = self; + type = axe_lookup(uaa->vendor, uaa->product); + if (type != NULL) + sc->axe_flags = type->axe_flags; if (usbd_set_config_no(sc->axe_udev, AXE_CONFIG_NO, 1)) { device_printf(sc->axe_dev, "getting interface handle failed\n"); @@ -438,6 +547,26 @@ axe_attach(device_t self) return ENXIO; } + sc->axe_boundary = 64; +#if 0 + if (sc->axe_flags & (AX178|AX772)) { + if (sc->axe_udev->speed == USB_SPEED_HIGH) { + sc->axe_bufsz = AXE_178_MAX_BUFSZ; + sc->axe_boundary = 512; + } else + sc->axe_bufsz = AXE_178_MIN_BUFSZ; + } else + sc->axe_bufsz = AXE_172_BUFSZ +#else + sc->axe_bufsz = AXE_172_BUFSZ; +#endif +{ /* XXX debug */ +device_printf(sc->axe_dev, "%s, bufsz %d, boundary %d\n", + sc->axe_flags & AX178 ? "AX88178" : + sc->axe_flags & AX772 ? "AX88772" : "AX88172", + sc->axe_bufsz, sc->axe_boundary); +} + id = usbd_get_interface_descriptor(sc->axe_iface); /* Find endpoints. */ @@ -465,16 +594,26 @@ axe_attach(device_t self) AXE_SLEEPLOCK(sc); AXE_LOCK(sc); + /* We need the PHYID for the init dance in some cases */ + axe_cmd(sc, AXE_CMD_READ_PHYID, 0, 0, (void *)&sc->axe_phyaddrs); + + if (sc->axe_flags & AX178) + axe_ax88178_init(sc); + else if (sc->axe_flags & AX772) + axe_ax88772_init(sc); + /* * Get station address. */ - axe_cmd(sc, AXE_172_CMD_READ_NODEID, 0, 0, &eaddr); + if (sc->axe_flags & (AX178|AX772)) + axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, &eaddr); + else + axe_cmd(sc, AXE_172_CMD_READ_NODEID, 0, 0, &eaddr); /* - * Load IPG values and PHY indexes. + * Fetch IPG values. */ axe_cmd(sc, AXE_CMD_READ_IPG012, 0, 0, (void *)&sc->axe_ipgs); - axe_cmd(sc, AXE_CMD_READ_PHYID, 0, 0, (void *)&sc->axe_phyaddrs); /* * Work around broken adapters that appear to lie about @@ -605,7 +744,8 @@ axe_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) struct ue_chain *c; struct mbuf *m; struct ifnet *ifp; - int total_len = 0; + struct axe_sframe_hdr hdr; + int total_len = 0, pktlen; c = priv; sc = c->ue_sc; @@ -633,15 +773,36 @@ axe_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); m = c->ue_mbuf; + /* XXX don't handle multiple packets in one transfer */ + if (sc->axe_flags & (AX178|AX772)) { + if (total_len < sizeof(hdr)) { + ifp->if_ierrors++; + goto done; + } + m_copydata(m, 0, sizeof(hdr), (caddr_t) &hdr); + total_len -= sizeof(hdr); - if (total_len < sizeof(struct ether_header)) { - ifp->if_ierrors++; - goto done; + if ((hdr.len ^ hdr.ilen) != 0xffff) { + ifp->if_ierrors++; + goto done; + } + pktlen = le16toh(hdr.len); + if (pktlen > total_len) { + ifp->if_ierrors++; + goto done; + } + m_adj(m, sizeof(hdr)); + } else { + if (total_len < sizeof(struct ether_header)) { + ifp->if_ierrors++; + goto done; + } + pktlen = total_len; } ifp->if_ipackets++; m->m_pkthdr.rcvif = (void *)&sc->axe_qdat; - m->m_pkthdr.len = m->m_len = total_len; + m->m_pkthdr.len = m->m_len = pktlen; /* Put the packet on the special USB input queue. */ usb_ether_input(m); @@ -768,6 +929,8 @@ axe_encap(struct axe_softc *sc, struct mbuf *m, int idx) { struct ue_chain *c; usbd_status err; + struct axe_sframe_hdr hdr; + int length; c = &sc->axe_cdata.ue_tx_chain[idx]; @@ -775,12 +938,30 @@ axe_encap(struct axe_softc *sc, struct mbuf *m, int idx) * Copy the mbuf data into a contiguous buffer, leaving two * bytes at the beginning to hold the frame length. */ - m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf); + if (sc->axe_flags & (AX178|AX772)) { + hdr.len = htole16(m->m_pkthdr.len); + hdr.ilen = ~hdr.len; + + memcpy(c->ue_buf, &hdr, sizeof(hdr)); + length = sizeof(hdr); + + m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf + sizeof(hdr)); + length += m->m_pkthdr.len; + + if ((length % sc->axe_boundary) == 0) { + hdr.len = 0; + hdr.ilen = 0xffff; + memcpy(c->ue_buf + length, &hdr, sizeof(hdr)); + length += sizeof(hdr); + } + } else { + m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf); + length = m->m_pkthdr.len; + } c->ue_mbuf = m; usbd_setup_xfer(c->ue_xfer, sc->axe_ep[AXE_ENDPT_TX], - c, c->ue_buf, m->m_pkthdr.len, USBD_FORCE_SHORT_XFER, - 10000, axe_txeof); + c, c->ue_buf, length, USBD_FORCE_SHORT_XFER, 10000, axe_txeof); /* Transmit */ err = usbd_transfer(c->ue_xfer); @@ -894,12 +1075,22 @@ axe_init(void *xsc) } /* Set transmitter IPG values */ - axe_cmd(sc, AXE_172_CMD_WRITE_IPG0, 0, sc->axe_ipgs[0], NULL); - axe_cmd(sc, AXE_172_CMD_WRITE_IPG1, 0, sc->axe_ipgs[1], NULL); - axe_cmd(sc, AXE_172_CMD_WRITE_IPG2, 0, sc->axe_ipgs[2], NULL); + if (sc->axe_flags & (AX178|AX772)) { + axe_cmd(sc, AXE_178_CMD_WRITE_IPG012, sc->axe_ipgs[2], + (sc->axe_ipgs[1]<<8) | sc->axe_ipgs[0], NULL); + } else { + axe_cmd(sc, AXE_172_CMD_WRITE_IPG0, 0, sc->axe_ipgs[0], NULL); + axe_cmd(sc, AXE_172_CMD_WRITE_IPG1, 0, sc->axe_ipgs[1], NULL); + axe_cmd(sc, AXE_172_CMD_WRITE_IPG2, 0, sc->axe_ipgs[2], NULL); + } /* Enable receiver, set RX mode */ - rxmode = AXE_172_RXCMD_UNICAST|AXE_RXCMD_MULTICAST|AXE_RXCMD_ENABLE; + rxmode = AXE_RXCMD_MULTICAST|AXE_RXCMD_ENABLE; + if (sc->axe_flags & (AX178|AX772)) { + if (sc->axe_bufsz == AXE_178_MAX_BUFSZ) + rxmode |= AXE_178_RXCMD_MFB_16384; + } else + rxmode |= AXE_172_RXCMD_UNICAST; /* If we want promiscuous mode, set the allframes bit. */ if (ifp->if_flags & IFF_PROMISC) diff --git a/sys/dev/usb/if_axereg.h b/sys/dev/usb/if_axereg.h index e0e6c8f4c42b..15bbaf5891c2 100644 --- a/sys/dev/usb/if_axereg.h +++ b/sys/dev/usb/if_axereg.h @@ -107,8 +107,9 @@ #define AXE_178_MEDIA_GMII 0x0001 #define AXE_MEDIA_FULL_DUPLEX 0x0002 #define AXE_172_MEDIA_TX_ABORT_ALLOW 0x0004 -/* AX88178 documentation says to always write 1 to reserved bit... */ +/* AX88178/88772 documentation says to always write 1 to bit 2 */ #define AXE_178_MEDIA_MAGIC 0x0004 +/* AX88772 documentation says to always write 0 to bit 3 */ #define AXE_178_MEDIA_ENCK 0x0008 #define AXE_172_MEDIA_FLOW_CONTROL_EN 0x0010 #define AXE_178_MEDIA_RXFLOW_CONTROL_EN 0x0010 @@ -126,8 +127,13 @@ #define AXE_178_RXCMD_KEEP_INVALID_CRC 0x0004 #define AXE_RXCMD_BROADCAST 0x0008 #define AXE_RXCMD_MULTICAST 0x0010 +#define AXE_178_RXCMD_AP 0x0020 #define AXE_RXCMD_ENABLE 0x0080 -#define AXE_178_RXCMD_MFB 0x0300 +#define AXE_178_RXCMD_MFB 0x0300 /* Max Frame Burst */ +#define AXE_178_RXCMD_MFB_2048 0x0000 +#define AXE_178_RXCMD_MFB_4096 0x0100 +#define AXE_178_RXCMD_MFB_8192 0x0200 +#define AXE_178_RXCMD_MFB_16384 0x0300 #define AXE_NOPHY 0xE0 #define AXE_INTPHY 0x10 @@ -158,11 +164,17 @@ #define AXE_ENDPT_INTR 0x2 #define AXE_ENDPT_MAX 0x3 +struct axe_sframe_hdr { + uint16_t len; + uint16_t ilen; +} __packed; + struct axe_type { struct usb_devno axe_dev; uint32_t axe_flags; -#define AX178 0x0001 /* AX88178 */ -#define AX772 0x0002 /* AX88772 */ +#define AX172 0x0000 /* AX88172 */ +#define AX178 0x0001 /* AX88178 */ +#define AX772 0x0002 /* AX88772 */ }; #define AXE_INC(x, y) (x) = (x + 1) % y @@ -197,7 +209,8 @@ struct axe_softc { struct timeval axe_rx_notice; struct usb_qdat axe_qdat; struct usb_task axe_tick_task; - u_int axe_bufsz; + int axe_bufsz; + int axe_boundary; }; #if 0