diff options
Diffstat (limited to 'sys/dev/pci/if_rge.c')
-rw-r--r-- | sys/dev/pci/if_rge.c | 2004 |
1 files changed, 2004 insertions, 0 deletions
diff --git a/sys/dev/pci/if_rge.c b/sys/dev/pci/if_rge.c new file mode 100644 index 00000000000..0bff1022de6 --- /dev/null +++ b/sys/dev/pci/if_rge.c @@ -0,0 +1,2004 @@ +/* $OpenBSD: if_rge.c,v 1.1 2019/11/18 03:03:37 kevlo Exp $ */ + +/* + * Copyright (c) 2019 Kevin Lo <kevlo@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "bpfilter.h" +#include "vlan.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/device.h> +#include <sys/endian.h> + +#include <net/if.h> +#include <net/if_media.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#if NBPFILTER > 0 +#include <net/bpf.h> +#endif + +#include <machine/bus.h> +#include <machine/intr.h> + +#include <dev/mii/mii.h> + +#include <dev/pci/pcivar.h> +#include <dev/pci/pcireg.h> +#include <dev/pci/pcidevs.h> + +#include <dev/pci/if_rgereg.h> + +int rge_match(struct device *, void *, void *); +void rge_attach(struct device *, struct device *, void *); +int rge_intr(void *); +int rge_encap(struct rge_softc *, struct mbuf *, int); +int rge_ioctl(struct ifnet *, u_long, caddr_t); +void rge_start(struct ifqueue *); +void rge_watchdog(struct ifnet *); +int rge_init(struct ifnet *); +void rge_stop(struct ifnet *); +int rge_ifmedia_upd(struct ifnet *); +void rge_ifmedia_sts(struct ifnet *, struct ifmediareq *); +int rge_allocmem(struct rge_softc *); +int rge_newbuf(struct rge_softc *, int); +void rge_discard_rxbuf(struct rge_softc *, int); +int rge_rx_list_init(struct rge_softc *); +void rge_tx_list_init(struct rge_softc *); +int rge_rxeof(struct rge_softc *); +int rge_txeof(struct rge_softc *); +void rge_reset(struct rge_softc *); +void rge_iff(struct rge_softc *); +void rge_set_phy_power(struct rge_softc *, int); +void rge_phy_config(struct rge_softc *); +void rge_set_macaddr(struct rge_softc *, const uint8_t *); +void rge_get_macaddr(struct rge_softc *, uint8_t *); +void rge_hw_init(struct rge_softc *); +void rge_disable_phy_ocp_pwrsave(struct rge_softc *); +void rge_patch_phy_mcu(struct rge_softc *, int); +void rge_add_media_types(struct rge_softc *); +void rge_config_imtype(struct rge_softc *, int); +void rge_disable_sim_im(struct rge_softc *); +void rge_setup_sim_im(struct rge_softc *); +void rge_setup_intr(struct rge_softc *, int); +void rge_exit_oob(struct rge_softc *); +void rge_write_csi(struct rge_softc *, uint32_t, uint32_t); +uint32_t rge_read_csi(struct rge_softc *, uint32_t); +void rge_write_mac_ocp(struct rge_softc *, uint16_t, uint16_t); +uint16_t rge_read_mac_ocp(struct rge_softc *, uint16_t); +void rge_write_ephy(struct rge_softc *, uint16_t, uint16_t); +void rge_write_phy(struct rge_softc *, uint16_t, uint16_t, uint16_t); +void rge_write_phy_ocp(struct rge_softc *, uint16_t, uint16_t); +uint16_t rge_read_phy_ocp(struct rge_softc *, uint16_t); +int rge_get_link_status(struct rge_softc *); +void rge_txstart(void *); +void rge_tick(void *); +void rge_link_state(struct rge_softc *); + +static const struct { + uint16_t reg; + uint16_t val; +} rtl8125_def_bps[] = { + RTL8125_DEF_BPS +}, rtl8125_mac_cfg2_ephy[] = { + RTL8125_MAC_CFG2_EPHY +}, rtl8125_mac_cfg2_mcu[] = { + RTL8125_MAC_CFG2_MCU +}, rtl8125_mac_cfg3_ephy[] = { + RTL8125_MAC_CFG3_EPHY +}, rtl8125_mac_cfg3_mcu[] = { + RTL8125_MAC_CFG3_MCU +}; + +struct cfattach rge_ca = { + sizeof(struct rge_softc), rge_match, rge_attach +}; + +struct cfdriver rge_cd = { + NULL, "rge", DV_IFNET +}; + +const struct pci_matchid rge_devices[] = { + { PCI_VENDOR_REALTEK, PCI_PRODUCT_REALTEK_E3000 }, + { PCI_VENDOR_REALTEK, PCI_PRODUCT_REALTEK_RTL8125 } +}; + +int +rge_match(struct device *parent, void *match, void *aux) +{ + return (pci_matchbyid((struct pci_attach_args *)aux, rge_devices, + nitems(rge_devices))); +} + +void +rge_attach(struct device *parent, struct device *self, void *aux) +{ + struct rge_softc *sc = (struct rge_softc *)self; + struct pci_attach_args *pa = aux; + pci_chipset_tag_t pc = pa->pa_pc; + pci_intr_handle_t ih; + const char *intrstr = NULL; + struct ifnet *ifp; + pcireg_t reg; + uint32_t hwrev; + uint8_t eaddr[ETHER_ADDR_LEN]; + int offset; + + pci_set_powerstate(pa->pa_pc, pa->pa_tag, PCI_PMCSR_STATE_D0); + + /* + * Map control/status registers. + */ + if (pci_mapreg_map(pa, RGE_PCI_BAR2, PCI_MAPREG_TYPE_MEM | + PCI_MAPREG_MEM_TYPE_64BIT, 0, &sc->rge_btag, &sc->rge_bhandle, + NULL, &sc->rge_bsize, 0)) { + if (pci_mapreg_map(pa, RGE_PCI_BAR1, PCI_MAPREG_TYPE_MEM | + PCI_MAPREG_MEM_TYPE_32BIT, 0, &sc->rge_btag, + &sc->rge_bhandle, NULL, &sc->rge_bsize, 0)) { + if (pci_mapreg_map(pa, RGE_PCI_BAR0, PCI_MAPREG_TYPE_IO, + 0, &sc->rge_btag, &sc->rge_bhandle, NULL, + &sc->rge_bsize, 0)) { + printf(": can't map mem or i/o space\n"); + return; + } + } + } + + /* + * Allocate interrupt. + */ + if (pci_intr_map_msi(pa, &ih) == 0) + sc->rge_flags |= RGE_FLAG_MSI; + else if (pci_intr_map(pa, &ih) != 0) { + printf(": couldn't map interrupt\n"); + return; + } + intrstr = pci_intr_string(pc, ih); + sc->sc_ih = pci_intr_establish(pc, ih, IPL_NET | IPL_MPSAFE, rge_intr, + sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + printf(": couldn't establish interrupt"); + if (intrstr != NULL) + printf(" at %s", intrstr); + printf("\n"); + return; + } + printf(": %s", intrstr); + + sc->sc_dmat = pa->pa_dmat; + sc->sc_pc = pa->pa_pc; + sc->sc_tag = pa->pa_tag; + + /* Determine hardware revision */ + hwrev = RGE_READ_4(sc, RGE_TXCFG) & RGE_TXCFG_HWREV; + switch (hwrev) { + case 0x60800000: + sc->rge_type = MAC_CFG2; + break; + case 0x60900000: + sc->rge_type = MAC_CFG3; + break; + default: + printf(": unknown version 0x%08x\n", hwrev); + return; + } + + rge_config_imtype(sc, RGE_IMTYPE_SIM); + + /* + * PCI Express check. + */ + if (pci_get_capability(pa->pa_pc, pa->pa_tag, PCI_CAP_PCIEXPRESS, + &offset, NULL)) { + /* Disable PCIe ASPM and ECPM. */ + reg = pci_conf_read(pa->pa_pc, pa->pa_tag, + offset + PCI_PCIE_LCSR); + reg &= ~(PCI_PCIE_LCSR_ASPM_L0S | PCI_PCIE_LCSR_ASPM_L1 | + PCI_PCIE_LCSR_ECPM); + pci_conf_write(pa->pa_pc, pa->pa_tag, offset + PCI_PCIE_LCSR, + reg); + } + + rge_exit_oob(sc); + rge_hw_init(sc); + + rge_get_macaddr(sc, eaddr); + printf(", address %s\n", ether_sprintf(eaddr)); + + memcpy(sc->sc_arpcom.ac_enaddr, eaddr, ETHER_ADDR_LEN); + + rge_set_phy_power(sc, 1); + rge_phy_config(sc); + + if (rge_allocmem(sc)) + return; + + ifp = &sc->sc_arpcom.ac_if; + ifp->if_softc = sc; + strlcpy(ifp->if_xname, sc->sc_dev.dv_xname, IFNAMSIZ); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_xflags = IFXF_MPSAFE; + ifp->if_ioctl = rge_ioctl; + ifp->if_qstart = rge_start; + ifp->if_watchdog = rge_watchdog; + IFQ_SET_MAXLEN(&ifp->if_snd, RGE_TX_LIST_CNT); + ifp->if_hardmtu = RGE_JUMBO_MTU; + + ifp->if_capabilities = IFCAP_VLAN_MTU | IFCAP_CSUM_IPv4 | + IFCAP_CSUM_TCPv4 | IFCAP_CSUM_UDPv4; + +#if NVLAN > 0 + ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING; +#endif + + timeout_set(&sc->sc_timeout, rge_tick, sc); + task_set(&sc->sc_task, rge_txstart, sc); + + /* Initialize ifmedia structures. */ + ifmedia_init(&sc->sc_media, IFM_IMASK, rge_ifmedia_upd, + rge_ifmedia_sts); + rge_add_media_types(sc); + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_AUTO, 0, NULL); + ifmedia_set(&sc->sc_media, IFM_ETHER | IFM_AUTO); + sc->sc_media.ifm_media = sc->sc_media.ifm_cur->ifm_media; + + if_attach(ifp); + ether_ifattach(ifp); +} + +int +rge_intr(void *arg) +{ + struct rge_softc *sc = arg; + struct ifnet *ifp = &sc->sc_arpcom.ac_if; + uint32_t status; + int claimed = 0, rx, tx; + + if (!(ifp->if_flags & IFF_RUNNING)) + return (0); + + /* Disable interrupts. */ + RGE_WRITE_4(sc, RGE_IMR, 0); + + status = RGE_READ_4(sc, RGE_ISR); + if (!(sc->rge_flags & RGE_FLAG_MSI)) { + if ((status & RGE_INTRS) == 0 || status == 0xffffffff) + return (0); + } + if (status) + RGE_WRITE_4(sc, RGE_ISR, status); + + if (status & RGE_ISR_PCS_TIMEOUT) + claimed = 1; + + rx = tx = 0; + if (status & RGE_INTRS) { + if (status & + (sc->rge_rx_ack | RGE_ISR_RX_ERR | RGE_ISR_RX_FIFO_OFLOW)) { + rx |= rge_rxeof(sc); + claimed = 1; + } + + if (status & (sc->rge_tx_ack | RGE_ISR_TX_ERR)) { + tx |= rge_txeof(sc); + claimed = 1; + } + + if (status & RGE_ISR_SYSTEM_ERR) { + KERNEL_LOCK(); + rge_init(ifp); + KERNEL_UNLOCK(); + claimed = 1; + } + } + + if (sc->rge_timerintr) { + if ((tx | rx) == 0) { + /* + * Nothing needs to be processed, fallback + * to use TX/RX interrupts. + */ + rge_setup_intr(sc, RGE_IMTYPE_NONE); + + /* + * Recollect, mainly to avoid the possible + * race introduced by changing interrupt + * masks. + */ + rge_rxeof(sc); + rge_txeof(sc); + } else + RGE_WRITE_4(sc, RGE_TIMERCNT, 1); + } else if (tx | rx) { + /* + * Assume that using simulated interrupt moderation + * (hardware timer based) could reduce the interrupt + * rate. + */ + rge_setup_intr(sc, RGE_IMTYPE_SIM); + } + + RGE_WRITE_4(sc, RGE_IMR, sc->rge_intrs); + + return (claimed); +} + +int +rge_encap(struct rge_softc *sc, struct mbuf *m, int idx) +{ + struct rge_tx_desc *d = NULL; + struct rge_txq *txq; + bus_dmamap_t txmap; + uint32_t cmdsts, cflags = 0; + int cur, error, i, last, nsegs; + + /* + * Set RGE_TDEXTSTS_IPCSUM if any checksum offloading is requested. + * Otherwise, RGE_TDEXTSTS_TCPCSUM / RGE_TDEXTSTS_UDPCSUM does not + * take affect. + */ + if ((m->m_pkthdr.csum_flags & + (M_IPV4_CSUM_OUT | M_TCP_CSUM_OUT | M_UDP_CSUM_OUT)) != 0) { + cflags |= RGE_TDEXTSTS_IPCSUM; + if (m->m_pkthdr.csum_flags & M_TCP_CSUM_OUT) + cflags |= RGE_TDEXTSTS_TCPCSUM; + if (m->m_pkthdr.csum_flags & M_UDP_CSUM_OUT) + cflags |= RGE_TDEXTSTS_UDPCSUM; + } + + txq = &sc->rge_ldata.rge_txq[idx]; + txmap = txq->txq_dmamap; + + error = bus_dmamap_load_mbuf(sc->sc_dmat, txmap, m, BUS_DMA_NOWAIT); + switch (error) { + case 0: + break; + case EFBIG: /* mbuf chain is too fragmented */ + if (m_defrag(m, M_DONTWAIT) == 0 && + bus_dmamap_load_mbuf(sc->sc_dmat, txmap, m, + BUS_DMA_NOWAIT) == 0) + break; + + /* FALLTHROUGH */ + default: + return (0); + } + + bus_dmamap_sync(sc->sc_dmat, txmap, 0, txmap->dm_mapsize, + BUS_DMASYNC_PREWRITE); + + nsegs = txmap->dm_nsegs; + + /* Set up hardware VLAN tagging. */ +#if NVLAN > 0 + if (m->m_flags & M_VLANTAG) + cflags |= swap16(m->m_pkthdr.ether_vtag | RGE_TDEXTSTS_VTAG); +#endif + + cur = idx; + cmdsts = RGE_TDCMDSTS_SOF; + + for (i = 0; i < txmap->dm_nsegs; i++) { + d = &sc->rge_ldata.rge_tx_list[cur]; + + d->rge_extsts = htole32(cflags); + d->rge_addrlo = htole32(RGE_ADDR_LO(txmap->dm_segs[i].ds_addr)); + d->rge_addrhi = htole32(RGE_ADDR_HI(txmap->dm_segs[i].ds_addr)); + + cmdsts |= txmap->dm_segs[i].ds_len; + + if (cur == RGE_TX_LIST_CNT - 1) + cmdsts |= RGE_TDCMDSTS_EOR; + + d->rge_cmdsts = htole32(cmdsts); + + last = cur; + cmdsts = RGE_TDCMDSTS_OWN; + cur = RGE_NEXT_TX_DESC(cur); + } + + /* Set EOF on the last descriptor. */ + d->rge_cmdsts |= htole32(RGE_TDCMDSTS_EOF); + + /* Transfer ownership of packet to the chip. */ + d = &sc->rge_ldata.rge_tx_list[idx]; + + d->rge_cmdsts |= htole32(RGE_TDCMDSTS_OWN); + + bus_dmamap_sync(sc->sc_dmat, sc->rge_ldata.rge_tx_list_map, + cur * sizeof(struct rge_tx_desc), sizeof(struct rge_tx_desc), + BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + + /* Update info of TX queue and descriptors. */ + txq->txq_mbuf = m; + txq->txq_descidx = last; + + return (nsegs); +} + +int +rge_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct rge_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + int s, error = 0; + + s = splnet(); + + switch (cmd) { + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + if (!(ifp->if_flags & IFF_RUNNING)) + rge_init(ifp); + break; + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_flags & IFF_RUNNING) + error = ENETRESET; + else + rge_init(ifp); + } else { + if (ifp->if_flags & IFF_RUNNING) + rge_stop(ifp); + } + break; + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &sc->sc_media, cmd); + break; + case SIOCSIFMTU: + if (ifr->ifr_mtu > ifp->if_hardmtu) { + error = EINVAL; + break; + } + ifp->if_mtu = ifr->ifr_mtu; + break; + default: + error = ether_ioctl(ifp, &sc->sc_arpcom, cmd, data); + } + + if (error == ENETRESET) { + if (ifp->if_flags & IFF_RUNNING) + rge_iff(sc); + error = 0; + } + + splx(s); + return (error); +} + +void +rge_start(struct ifqueue *ifq) +{ + struct ifnet *ifp = ifq->ifq_if; + struct rge_softc *sc = ifp->if_softc; + struct mbuf *m; + int free, idx, used; + int queued = 0; + + if (!LINK_STATE_IS_UP(ifp->if_link_state)) { + ifq_purge(ifq); + return; + } + + /* Calculate free space. */ + idx = sc->rge_ldata.rge_txq_prodidx; + free = sc->rge_ldata.rge_txq_considx; + if (free <= idx) + free += RGE_TX_LIST_CNT; + free -= idx; + + for (;;) { + if (RGE_TX_NSEGS >= free + 2) { + ifq_set_oactive(&ifp->if_snd); + break; + } + + m = ifq_dequeue(ifq); + if (m == NULL) + break; + + used = rge_encap(sc, m, idx); + if (used == 0) { + m_freem(m); + continue; + } + + KASSERT(used <= free); + free -= used; + +#if NBPFILTER > 0 + if (ifp->if_bpf) + bpf_mtap_ether(ifp->if_bpf, m, BPF_DIRECTION_OUT); +#endif + + idx += used; + if (idx >= RGE_TX_LIST_CNT) + idx -= RGE_TX_LIST_CNT; + + queued++; + } + + if (queued == 0) + return; + + /* Set a timeout in case the chip goes out to lunch. */ + ifp->if_timer = 5; + + sc->rge_ldata.rge_txq_prodidx = idx; + ifq_serialize(ifq, &sc->sc_task); +} + +void +rge_watchdog(struct ifnet *ifp) +{ + struct rge_softc *sc = ifp->if_softc; + + printf("%s: watchdog timeout\n", sc->sc_dev.dv_xname); + ifp->if_oerrors++; + + rge_init(ifp); +} + +int +rge_init(struct ifnet *ifp) +{ + struct rge_softc *sc = ifp->if_softc; + uint32_t val; + uint16_t max_frame_size; + int i; + + rge_stop(ifp); + + /* Set MAC address. */ + rge_set_macaddr(sc, sc->sc_arpcom.ac_enaddr); + + /* Set Maximum frame size but don't let MTU be lass than ETHER_MTU. */ + if (ifp->if_mtu < ETHERMTU) + max_frame_size = ETHERMTU; + else + max_frame_size = ifp->if_mtu; + + max_frame_size += ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN + + ETHER_CRC_LEN + 1; + + if (max_frame_size > RGE_JUMBO_FRAMELEN) + max_frame_size -= 1; + + RGE_WRITE_2(sc, RGE_RXMAXSIZE, max_frame_size); + + /* Initialize RX descriptors list. */ + if (rge_rx_list_init(sc) == ENOBUFS) { + printf("%s: init failed: no memory for RX buffers\n", + sc->sc_dev.dv_xname); + rge_stop(ifp); + return (ENOBUFS); + } + + /* Initialize TX descriptors. */ + rge_tx_list_init(sc); + + /* Load the addresses of the RX and TX lists into the chip. */ + RGE_WRITE_4(sc, RGE_RXDESC_ADDR_LO, + RGE_ADDR_LO(sc->rge_ldata.rge_rx_list_map->dm_segs[0].ds_addr)); + RGE_WRITE_4(sc, RGE_RXDESC_ADDR_HI, + RGE_ADDR_HI(sc->rge_ldata.rge_rx_list_map->dm_segs[0].ds_addr)); + RGE_WRITE_4(sc, RGE_TXDESC_ADDR_LO, + RGE_ADDR_LO(sc->rge_ldata.rge_tx_list_map->dm_segs[0].ds_addr)); + RGE_WRITE_4(sc, RGE_TXDESC_ADDR_HI, + RGE_ADDR_HI(sc->rge_ldata.rge_tx_list_map->dm_segs[0].ds_addr)); + + RGE_SETBIT_1(sc, RGE_EECMD, RGE_EECMD_WRITECFG); + + RGE_CLRBIT_1(sc, 0xf1, 0x80); + RGE_CLRBIT_1(sc, RGE_CFG2, RGE_CFG2_CLKREQ_EN); + RGE_CLRBIT_1(sc, RGE_CFG5, RGE_CFG5_PME_STS); + RGE_CLRBIT_1(sc, RGE_CFG3, RGE_CFG3_RDY_TO_L23); + + /* Clear interrupt moderation timer. */ + for (i = 0; i < 64; i++) + RGE_WRITE_4(sc, RGE_IM(i), 0); + + /* Set the initial RX and TX and configurations. */ + RGE_WRITE_4(sc, RGE_RXCFG, RGE_RXCFG_CONFIG); + RGE_WRITE_4(sc, RGE_TXCFG, RGE_TXCFG_CONFIG); + + val = rge_read_csi(sc, 0x70c) & ~0xff000000; + rge_write_csi(sc, 0x70c, val | 0x27000000); + + /* Enable hardware optimization function. */ + val = pci_conf_read(sc->sc_pc, sc->sc_tag, 0x78) & ~0x00007000; + pci_conf_write(sc->sc_pc, sc->sc_tag, 0x78, val | 0x00005000); + + RGE_WRITE_2(sc, 0x0382, 0x221b); + RGE_WRITE_1(sc, 0x4500, 0); + RGE_WRITE_2(sc, 0x4800, 0); + RGE_CLRBIT_1(sc, RGE_CFG1, RGE_CFG1_SPEED_DOWN); + + rge_write_mac_ocp(sc, 0xc140, 0xffff); + rge_write_mac_ocp(sc, 0xc142, 0xffff); + + val = rge_read_mac_ocp(sc, 0xd3e2) & ~0x0fff; + rge_write_mac_ocp(sc, 0xd3e2, val | 0x03a9); + + RGE_MAC_CLRBIT(sc, 0xd3e4, 0x00ff); + RGE_MAC_SETBIT(sc, 0xe860, 0x0080); + RGE_MAC_SETBIT(sc, 0xeb58, 0x0001); + + val = rge_read_mac_ocp(sc, 0xe614) & ~0x0700; + rge_write_mac_ocp(sc, 0xe614, val | 0x0400); + + RGE_MAC_CLRBIT(sc, 0xe63e, 0x0c00); + + val = rge_read_mac_ocp(sc, 0xe63e) & ~0x0030; + rge_write_mac_ocp(sc, 0xe63e, val | 0x0020); + + RGE_MAC_SETBIT(sc, 0xc0b4, 0x000c); + + val = rge_read_mac_ocp(sc, 0xeb6a) & ~0x007f; + rge_write_mac_ocp(sc, 0xeb6a, val | 0x0033); + + val = rge_read_mac_ocp(sc, 0xeb50) & ~0x03e0; + rge_write_mac_ocp(sc, 0xeb50, val | 0x0040); + + val = rge_read_mac_ocp(sc, 0xe056) & ~0x00f0; + rge_write_mac_ocp(sc, 0xe056, val | 0x0030); + + RGE_WRITE_1(sc, RGE_TDFNR, 0x10); + + RGE_MAC_CLRBIT(sc, 0xe040, 0x1000); + + val = rge_read_mac_ocp(sc, 0xe0c0) & ~0x4f0f; + rge_write_mac_ocp(sc, 0xe0c0, val | 0x4403); + + RGE_MAC_SETBIT(sc, 0xe052, 0x0068); + RGE_MAC_CLRBIT(sc, 0xe052, 0x0080); + + val = rge_read_mac_ocp(sc, 0xc0ac) & ~0x0080; + rge_write_mac_ocp(sc, 0xc0ac, val | 0x1f00); + + val = rge_read_mac_ocp(sc, 0xd430) & ~0x0fff; + rge_write_mac_ocp(sc, 0xd430, val | 0x047f); + + RGE_MAC_SETBIT(sc, 0xe84c, 0x00c0); + + /* Disable EEE plus. */ + RGE_MAC_CLRBIT(sc, 0xe080, 0x0002); + + RGE_MAC_CLRBIT(sc, 0xea1c, 0x0004); + + RGE_MAC_SETBIT(sc, 0xeb54, 0x0001); + DELAY(1); + RGE_MAC_CLRBIT(sc, 0xeb54, 0x0001); + + RGE_CLRBIT_4(sc, 0x1880, 0x0030); + + rge_write_mac_ocp(sc, 0xe098, 0xc302); + + if (ifp->if_capabilities & IFCAP_VLAN_HWTAGGING) + RGE_SETBIT_4(sc, RGE_RXCFG, RGE_RXCFG_VLANSTRIP); + + RGE_SETBIT_2(sc, RGE_CPLUSCMD, RGE_CPLUSCMD_RXCSUM); + + for (i = 0; i < 10; i++) { + if (!(rge_read_mac_ocp(sc, 0xe00e) & 0x2000)) + break; + DELAY(1000); + } + + /* Disable RXDV gate. */ + RGE_CLRBIT_1(sc, RGE_PPSW, 0x08); + DELAY(2000); + + rge_ifmedia_upd(ifp); + + /* Enable transmit and receive. */ + RGE_WRITE_1(sc, RGE_CMD, RGE_CMD_TXENB | RGE_CMD_RXENB); + + /* Program promiscuous mode and multicast filters. */ + rge_iff(sc); + + RGE_CLRBIT_1(sc, RGE_CFG2, RGE_CFG2_CLKREQ_EN); + RGE_CLRBIT_1(sc, RGE_CFG5, RGE_CFG5_PME_STS); + + RGE_CLRBIT_1(sc, RGE_EECMD, RGE_EECMD_WRITECFG); + + /* Enable interrupts. */ + rge_setup_intr(sc, RGE_IMTYPE_SIM); + + ifp->if_flags |= IFF_RUNNING; + ifq_clr_oactive(&ifp->if_snd); + + timeout_add_sec(&sc->sc_timeout, 1); + + return (0); +} + +/* + * Stop the adapter and free any mbufs allocated to the RX and TX lists. + */ +void +rge_stop(struct ifnet *ifp) +{ + struct rge_softc *sc = ifp->if_softc; + int i; + + timeout_del(&sc->sc_timeout); + + ifp->if_timer = 0; + ifp->if_flags &= ~IFF_RUNNING; + sc->rge_timerintr = 0; + + RGE_CLRBIT_4(sc, RGE_RXCFG, RGE_RXCFG_ALLPHYS | RGE_RXCFG_INDIV | + RGE_RXCFG_MULTI | RGE_RXCFG_BROAD | RGE_RXCFG_RUNT | + RGE_RXCFG_ERRPKT); + + RGE_WRITE_4(sc, RGE_IMR, 0); + RGE_WRITE_4(sc, RGE_ISR, 0xffffffff); + + rge_reset(sc); + + intr_barrier(sc->sc_ih); + ifq_barrier(&ifp->if_snd); + ifq_clr_oactive(&ifp->if_snd); + + if (sc->rge_head != NULL) { + m_freem(sc->rge_head); + sc->rge_head = sc->rge_tail = NULL; + } + + /* Free the TX list buffers. */ + for (i = 0; i < RGE_TX_LIST_CNT; i++) { + if (sc->rge_ldata.rge_txq[i].txq_mbuf != NULL) { + bus_dmamap_unload(sc->sc_dmat, + sc->rge_ldata.rge_txq[i].txq_dmamap); + m_freem(sc->rge_ldata.rge_txq[i].txq_mbuf); + sc->rge_ldata.rge_txq[i].txq_mbuf = NULL; + } + } + + /* Free the RX list buffers. */ + for (i = 0; i < RGE_RX_LIST_CNT; i++) { + if (sc->rge_ldata.rge_rxq[i].rxq_mbuf != NULL) { + bus_dmamap_unload(sc->sc_dmat, + sc->rge_ldata.rge_rxq[i].rxq_dmamap); + m_freem(sc->rge_ldata.rge_rxq[i].rxq_mbuf); + sc->rge_ldata.rge_rxq[i].rxq_mbuf = NULL; + } + } +} + +/* + * Set media options. + */ +int +rge_ifmedia_upd(struct ifnet *ifp) +{ + struct rge_softc *sc = ifp->if_softc; + struct ifmedia *ifm = &sc->sc_media; + int anar, gig, val; + + if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER) + return (EINVAL); + + /* Disable Gigabit Lite. */ + RGE_PHY_CLRBIT(sc, 0xa428, 0x0200); + RGE_PHY_CLRBIT(sc, 0xa5ea, 0x0001); + + val = rge_read_phy_ocp(sc, 0xa5d4); + val &= ~RGE_ADV_2500TFDX; + + anar = gig = 0; + switch (IFM_SUBTYPE(ifm->ifm_media)) { + case IFM_AUTO: + anar |= ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10; + gig |= GTCR_ADV_1000TFDX | GTCR_ADV_1000THDX; + val |= RGE_ADV_2500TFDX; + break; + case IFM_2500_T: + anar |= ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10; + gig |= GTCR_ADV_1000TFDX | GTCR_ADV_1000THDX; + val |= RGE_ADV_2500TFDX; + ifp->if_baudrate = IF_Mbps(2500); + break; + case IFM_1000_T: + anar |= ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10; + gig |= GTCR_ADV_1000TFDX | GTCR_ADV_1000THDX; + ifp->if_baudrate = IF_Gbps(1); + break; + case IFM_100_TX: + anar |= ANAR_TX | ANAR_TX_FD; + ifp->if_baudrate = IF_Mbps(100); + break; + case IFM_10_T: + anar |= ANAR_10 | ANAR_10_FD; + ifp->if_baudrate = IF_Mbps(10); + break; + default: + printf("%s: unsupported media type\n", sc->sc_dev.dv_xname); + return (EINVAL); + } + + rge_write_phy(sc, 0, MII_ANAR, anar | ANAR_PAUSE_ASYM | ANAR_FC); + rge_write_phy(sc, 0, MII_100T2CR, gig); + rge_write_phy_ocp(sc, 0xa5d4, val); + rge_write_phy(sc, 0, MII_BMCR, BMCR_AUTOEN | BMCR_STARTNEG); + + return (0); +} + +/* + * Report current media status. + */ +void +rge_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct rge_softc *sc = ifp->if_softc; + uint16_t status = 0; + + ifmr->ifm_status = IFM_AVALID; + ifmr->ifm_active = IFM_ETHER; + + if (rge_get_link_status(sc)) { + ifmr->ifm_status |= IFM_ACTIVE; + + status = RGE_READ_2(sc, RGE_PHYSTAT); + if ((status & RGE_PHYSTAT_FDX) || + (status & RGE_PHYSTAT_2500MBPS)) + ifmr->ifm_active |= IFM_FDX; + else + ifmr->ifm_active |= IFM_HDX; + + if (status & RGE_PHYSTAT_10MBPS) + ifmr->ifm_active |= IFM_10_T; + else if (status & RGE_PHYSTAT_100MBPS) + ifmr->ifm_active |= IFM_100_TX; + else if (status & RGE_PHYSTAT_1000MBPS) + ifmr->ifm_active |= IFM_1000_T; + else if (status & RGE_PHYSTAT_2500MBPS) + ifmr->ifm_active |= IFM_2500_T; + } +} + +/* + * Allocate memory for RX/TX rings. + */ +int +rge_allocmem(struct rge_softc *sc) +{ + int error, i; + + /* Allocate DMA'able memory for the TX ring. */ + error = bus_dmamap_create(sc->sc_dmat, RGE_TX_LIST_SZ, 1, + RGE_TX_LIST_SZ, 0, BUS_DMA_NOWAIT, &sc->rge_ldata.rge_tx_list_map); + if (error) { + printf("%s: can't create TX list map\n", sc->sc_dev.dv_xname); + return (error); + } + error = bus_dmamem_alloc(sc->sc_dmat, RGE_TX_LIST_SZ, RGE_ALIGN, 0, + &sc->rge_ldata.rge_tx_listseg, 1, &sc->rge_ldata.rge_tx_listnseg, + BUS_DMA_NOWAIT| BUS_DMA_ZERO); + if (error) { + printf("%s: can't alloc TX list\n", sc->sc_dev.dv_xname); + return (error); + } + + /* Load the map for the TX ring. */ + error = bus_dmamem_map(sc->sc_dmat, &sc->rge_ldata.rge_tx_listseg, + sc->rge_ldata.rge_tx_listnseg, RGE_TX_LIST_SZ, + (caddr_t *)&sc->rge_ldata.rge_tx_list, + BUS_DMA_NOWAIT | BUS_DMA_COHERENT); + if (error) { + printf("%s: can't map TX dma buffers\n", sc->sc_dev.dv_xname); + bus_dmamem_free(sc->sc_dmat, &sc->rge_ldata.rge_tx_listseg, + sc->rge_ldata.rge_tx_listnseg); + return (error); + } + error = bus_dmamap_load(sc->sc_dmat, sc->rge_ldata.rge_tx_list_map, + sc->rge_ldata.rge_tx_list, RGE_TX_LIST_SZ, NULL, BUS_DMA_NOWAIT); + if (error) { + printf("%s: can't load TX dma map\n", sc->sc_dev.dv_xname); + bus_dmamap_destroy(sc->sc_dmat, sc->rge_ldata.rge_tx_list_map); + bus_dmamem_unmap(sc->sc_dmat, + (caddr_t)sc->rge_ldata.rge_tx_list, RGE_TX_LIST_SZ); + bus_dmamem_free(sc->sc_dmat, &sc->rge_ldata.rge_tx_listseg, + sc->rge_ldata.rge_tx_listnseg); + return (error); + } + + /* Create DMA maps for TX buffers. */ + for (i = 0; i < RGE_TX_LIST_CNT; i++) { + error = bus_dmamap_create(sc->sc_dmat, RGE_JUMBO_FRAMELEN, + RGE_TX_NSEGS, RGE_JUMBO_FRAMELEN, 0, 0, + &sc->rge_ldata.rge_txq[i].txq_dmamap); + if (error) { + printf("%s: can't create DMA map for TX\n", + sc->sc_dev.dv_xname); + return (error); + } + } + + /* Allocate DMA'able memory for the RX ring. */ + error = bus_dmamap_create(sc->sc_dmat, RGE_RX_LIST_SZ, 1, + RGE_RX_LIST_SZ, 0, 0, &sc->rge_ldata.rge_rx_list_map); + if (error) { + printf("%s: can't create RX list map\n", sc->sc_dev.dv_xname); + return (error); + } + error = bus_dmamem_alloc(sc->sc_dmat, RGE_RX_LIST_SZ, RGE_ALIGN, 0, + &sc->rge_ldata.rge_rx_listseg, 1, &sc->rge_ldata.rge_rx_listnseg, + BUS_DMA_NOWAIT| BUS_DMA_ZERO); + if (error) { + printf("%s: can't alloc RX list\n", sc->sc_dev.dv_xname); + return (error); + } + + /* Load the map for the RX ring. */ + error = bus_dmamem_map(sc->sc_dmat, &sc->rge_ldata.rge_rx_listseg, + sc->rge_ldata.rge_rx_listnseg, RGE_RX_LIST_SZ, + (caddr_t *)&sc->rge_ldata.rge_rx_list, + BUS_DMA_NOWAIT | BUS_DMA_COHERENT); + if (error) { + printf("%s: can't map RX dma buffers\n", sc->sc_dev.dv_xname); + bus_dmamem_free(sc->sc_dmat, &sc->rge_ldata.rge_rx_listseg, + sc->rge_ldata.rge_rx_listnseg); + return (error); + } + error = bus_dmamap_load(sc->sc_dmat, sc->rge_ldata.rge_rx_list_map, + sc->rge_ldata.rge_rx_list, RGE_RX_LIST_SZ, NULL, BUS_DMA_NOWAIT); + if (error) { + printf("%s: can't load RX dma map\n", sc->sc_dev.dv_xname); + bus_dmamap_destroy(sc->sc_dmat, sc->rge_ldata.rge_rx_list_map); + bus_dmamem_unmap(sc->sc_dmat, + (caddr_t)sc->rge_ldata.rge_rx_list, RGE_RX_LIST_SZ); + bus_dmamem_free(sc->sc_dmat, &sc->rge_ldata.rge_rx_listseg, + sc->rge_ldata.rge_rx_listnseg); + return (error); + } + + /* Create DMA maps for RX buffers. */ + for (i = 0; i < RGE_RX_LIST_CNT; i++) { + error = bus_dmamap_create(sc->sc_dmat, RGE_JUMBO_FRAMELEN, 1, + RGE_JUMBO_FRAMELEN, 0, 0, + &sc->rge_ldata.rge_rxq[i].rxq_dmamap); + if (error) { + printf("%s: can't create DMA map for RX\n", + sc->sc_dev.dv_xname); + return (error); + } + } + + return (error); +} + +/* + * Initialize the RX descriptor and attach an mbuf cluster. + */ +int +rge_newbuf(struct rge_softc *sc, int idx) +{ + struct mbuf *m; + struct rge_rx_desc *r; + struct rge_rxq *rxq; + bus_dmamap_t rxmap; + + m = MCLGETI(NULL, M_DONTWAIT, NULL, RGE_JUMBO_FRAMELEN); + if (m == NULL) + return (ENOBUFS); + + m->m_len = m->m_pkthdr.len = RGE_JUMBO_FRAMELEN; + + rxq = &sc->rge_ldata.rge_rxq[idx]; + rxmap = rxq->rxq_dmamap; + + if (bus_dmamap_load_mbuf(sc->sc_dmat, rxmap, m, BUS_DMA_NOWAIT)) + goto out; + + bus_dmamap_sync(sc->sc_dmat, rxmap, 0, rxmap->dm_mapsize, + BUS_DMASYNC_PREREAD); + + /* Map the segments into RX descriptors. */ + r = &sc->rge_ldata.rge_rx_list[idx]; + + if (RGE_OWN(r)) { + printf("%s: tried to map busy RX descriptor\n", + sc->sc_dev.dv_xname); + goto out; + } + + rxq->rxq_mbuf = m; + + r->rge_extsts = 0; + r->rge_addrlo = htole32(RGE_ADDR_LO(rxmap->dm_segs[0].ds_addr)); + r->rge_addrhi = htole32(RGE_ADDR_HI(rxmap->dm_segs[0].ds_addr)); + + r->rge_cmdsts = htole32(rxmap->dm_segs[0].ds_len); + if (idx == RGE_RX_LIST_CNT - 1) + r->rge_cmdsts |= htole32(RGE_RDCMDSTS_EOR); + + r->rge_cmdsts |= htole32(RGE_RDCMDSTS_OWN); + + bus_dmamap_sync(sc->sc_dmat, sc->rge_ldata.rge_rx_list_map, + idx * sizeof(struct rge_rx_desc), sizeof(struct rge_rx_desc), + BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + + return (0); +out: + if (m != NULL) + m_freem(m); + return (ENOMEM); +} + +void +rge_discard_rxbuf(struct rge_softc *sc, int idx) +{ + struct rge_rx_desc *r; + + r = &sc->rge_ldata.rge_rx_list[idx]; + + r->rge_cmdsts = htole32(RGE_JUMBO_FRAMELEN); + r->rge_extsts = 0; + if (idx == RGE_RX_LIST_CNT - 1) + r->rge_cmdsts |= htole32(RGE_RDCMDSTS_EOR); + r->rge_cmdsts |= htole32(RGE_RDCMDSTS_OWN); + + bus_dmamap_sync(sc->sc_dmat, sc->rge_ldata.rge_rx_list_map, + idx * sizeof(struct rge_rx_desc), sizeof(struct rge_rx_desc), + BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); +} + +int +rge_rx_list_init(struct rge_softc *sc) +{ + int i; + + memset(sc->rge_ldata.rge_rx_list, 0, RGE_RX_LIST_SZ); + + for (i = 0; i < RGE_RX_LIST_CNT; i++) { + sc->rge_ldata.rge_rxq[i].rxq_mbuf = NULL; + if (rge_newbuf(sc, i) == ENOBUFS) + return (ENOBUFS); + } + + sc->rge_ldata.rge_rxq_prodidx = 0; + sc->rge_head = sc->rge_tail = NULL; + + return (0); +} + +void +rge_tx_list_init(struct rge_softc *sc) +{ + int i; + + memset(sc->rge_ldata.rge_tx_list, 0, RGE_TX_LIST_SZ); + + for (i = 0; i < RGE_TX_LIST_CNT; i++) + sc->rge_ldata.rge_txq[i].txq_mbuf = NULL; + + bus_dmamap_sync(sc->sc_dmat, sc->rge_ldata.rge_tx_list_map, 0, + sc->rge_ldata.rge_tx_list_map->dm_mapsize, + BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + + sc->rge_ldata.rge_txq_prodidx = sc->rge_ldata.rge_txq_considx = 0; +} + +int +rge_rxeof(struct rge_softc *sc) +{ + struct mbuf_list ml = MBUF_LIST_INITIALIZER(); + struct mbuf *m; + struct ifnet *ifp = &sc->sc_arpcom.ac_if; + struct rge_rx_desc *cur_rx; + struct rge_rxq *rxq; + uint32_t rxstat, extsts; + int i, total_len, rx = 0; + + /* Invalidate the descriptor memory. */ + bus_dmamap_sync(sc->sc_dmat, sc->rge_ldata.rge_rx_list_map, + i * sizeof(struct rge_rx_desc), sizeof(struct rge_rx_desc), + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + + for (i = sc->rge_ldata.rge_rxq_prodidx; ; i = RGE_NEXT_RX_DESC(i)) { + cur_rx = &sc->rge_ldata.rge_rx_list[i]; + + if (RGE_OWN(cur_rx)) + break; + + rxstat = letoh32(cur_rx->rge_cmdsts); + extsts = letoh32(cur_rx->rge_extsts); + + total_len = RGE_RXBYTES(cur_rx); + rxq = &sc->rge_ldata.rge_rxq[i]; + m = rxq->rxq_mbuf; + rx = 1; + + /* Invalidate the RX mbuf and unload its map. */ + bus_dmamap_sync(sc->sc_dmat, rxq->rxq_dmamap, 0, + rxq->rxq_dmamap->dm_mapsize, BUS_DMASYNC_POSTREAD); + bus_dmamap_unload(sc->sc_dmat, rxq->rxq_dmamap); + + if ((rxstat & (RGE_RDCMDSTS_SOF | RGE_RDCMDSTS_EOF)) != + (RGE_RDCMDSTS_SOF | RGE_RDCMDSTS_EOF)) { + rge_discard_rxbuf(sc, i); + continue; + } + + if (rxstat & RGE_RDCMDSTS_RXERRSUM) { + ifp->if_ierrors++; + /* + * If this is part of a multi-fragment packet, + * discard all the pieces. + */ + if (sc->rge_head != NULL) { + m_freem(sc->rge_head); + sc->rge_head = sc->rge_tail = NULL; + } + rge_discard_rxbuf(sc, i); + continue; + } + + /* + * If allocating a replacement mbuf fails, + * reload the current one. + */ + + if (rge_newbuf(sc, i) == ENOBUFS) { + if (sc->rge_head != NULL) { + m_freem(sc->rge_head); + sc->rge_head = sc->rge_tail = NULL; + } + rge_discard_rxbuf(sc, i); + continue; + } + + if (sc->rge_head != NULL) { + m->m_len = total_len; + /* + * Special case: if there's 4 bytes or less + * in this buffer, the mbuf can be discarded: + * the last 4 bytes is the CRC, which we don't + * care about anyway. + */ + if (m->m_len <= ETHER_CRC_LEN) { + sc->rge_tail->m_len -= + (ETHER_CRC_LEN - m->m_len); + m_freem(m); + } else { + m->m_len -= ETHER_CRC_LEN; + m->m_flags &= ~M_PKTHDR; + sc->rge_tail->m_next = m; + } + m = sc->rge_head; + sc->rge_head = sc->rge_tail = NULL; + m->m_pkthdr.len = total_len - ETHER_CRC_LEN; + } else + m->m_pkthdr.len = m->m_len = + (total_len - ETHER_CRC_LEN); + + /* Check IP header checksum. */ + if (!(rxstat & RGE_RDCMDSTS_IPCSUMERR) && + (extsts & RGE_RDEXTSTS_IPV4)) + m->m_pkthdr.csum_flags |= M_IPV4_CSUM_IN_OK; + + /* Check TCP/UDP checksum. */ + if ((extsts & (RGE_RDEXTSTS_IPV4 | RGE_RDEXTSTS_IPV6)) && + (((rxstat & RGE_RDCMDSTS_TCPPKT) && + !(rxstat & RGE_RDCMDSTS_TCPCSUMERR)) || + ((rxstat & RGE_RDCMDSTS_UDPPKT) && + !(rxstat & RGE_RDCMDSTS_UDPCSUMERR)))) + m->m_pkthdr.csum_flags |= M_TCP_CSUM_IN_OK | + M_UDP_CSUM_IN_OK; + +#if NVLAN > 0 + if (extsts & RGE_RDEXTSTS_VTAG) { + m->m_pkthdr.ether_vtag = + ntohs(extsts & RGE_RDEXTSTS_VLAN_MASK); + m->m_flags |= M_VLANTAG; + } +#endif + + ml_enqueue(&ml, m); + } + + sc->rge_ldata.rge_rxq_prodidx = i; + + if_input(ifp, &ml); + + return (rx); +} + +int +rge_txeof(struct rge_softc *sc) +{ + struct ifnet *ifp = &sc->sc_arpcom.ac_if; + struct rge_txq *txq; + uint32_t txstat; + int cons, idx, prod; + int free = 0; + + prod = sc->rge_ldata.rge_txq_prodidx; + cons = sc->rge_ldata.rge_txq_considx; + + bus_dmamap_sync(sc->sc_dmat, sc->rge_ldata.rge_tx_list_map, + idx * sizeof(struct rge_tx_desc), sizeof(struct rge_tx_desc), + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + + while (prod != cons) { + txq = &sc->rge_ldata.rge_txq[cons]; + idx = txq->txq_descidx; + + txstat = letoh32(sc->rge_ldata.rge_tx_list[idx].rge_cmdsts); + + if (txstat & RGE_TDCMDSTS_OWN) { + free = 2; + break; + } + + bus_dmamap_sync(sc->sc_dmat, txq->txq_dmamap, 0, + txq->txq_dmamap->dm_mapsize, BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->sc_dmat, txq->txq_dmamap); + m_freem(txq->txq_mbuf); + txq->txq_mbuf = NULL; + + if (txstat & (RGE_TDCMDSTS_EXCESSCOLL | RGE_TDCMDSTS_COLL)) + ifp->if_collisions++; + if (txstat & RGE_TDCMDSTS_TXERR) + ifp->if_oerrors++; + + bus_dmamap_sync(sc->sc_dmat, sc->rge_ldata.rge_tx_list_map, + idx * sizeof(struct rge_tx_desc), + sizeof(struct rge_tx_desc), + BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + + cons = RGE_NEXT_TX_DESC(idx); + free = 1; + } + + if (free == 0) + return (0); + + sc->rge_ldata.rge_txq_considx = cons; + + if (ifq_is_oactive(&ifp->if_snd)) + ifq_restart(&ifp->if_snd); + else if (free == 2) + ifq_serialize(&ifp->if_snd, &sc->sc_task); + else + ifp->if_timer = 0; + + return (1); +} + +void +rge_reset(struct rge_softc *sc) +{ + int i; + + /* Enable RXDV gate. */ + RGE_SETBIT_1(sc, RGE_PPSW, 0x08); + DELAY(2000); + + for (i = 0; i < 10; i++) { + DELAY(100); + if ((RGE_READ_1(sc, RGE_MCUCMD) & (RGE_MCUCMD_RXFIFO_EMPTY | + RGE_MCUCMD_TXFIFO_EMPTY)) == (RGE_MCUCMD_RXFIFO_EMPTY | + RGE_MCUCMD_TXFIFO_EMPTY)) + break; + } + + /* Soft reset. */ + RGE_WRITE_1(sc, RGE_CMD, RGE_CMD_RESET); + + for (i = 0; i < RGE_TIMEOUT; i++) { + DELAY(100); + if (!(RGE_READ_1(sc, RGE_CMD) & RGE_CMD_RESET)) + break; + } + if (i == RGE_TIMEOUT) + printf("%s: reset never completed!\n", sc->sc_dev.dv_xname); +} + +void +rge_iff(struct rge_softc *sc) +{ + struct ifnet *ifp = &sc->sc_arpcom.ac_if; + struct arpcom *ac = &sc->sc_arpcom; + struct ether_multi *enm; + struct ether_multistep step; + uint32_t hashes[2]; + uint32_t rxfilt; + int h = 0; + + rxfilt = RGE_READ_4(sc, RGE_RXCFG); + rxfilt &= ~(RGE_RXCFG_ALLPHYS | RGE_RXCFG_MULTI); + ifp->if_flags &= ~IFF_ALLMULTI; + + /* + * Always accept frames destined to our station address. + * Always accept broadcast frames. + */ + rxfilt |= RGE_RXCFG_INDIV | RGE_RXCFG_BROAD; + + if (ifp->if_flags & IFF_PROMISC || ac->ac_multirangecnt > 0) { + ifp->if_flags |= IFF_ALLMULTI; + rxfilt |= RGE_RXCFG_MULTI; + if (ifp->if_flags & IFF_PROMISC) + rxfilt |= RGE_RXCFG_ALLPHYS; + hashes[0] = hashes[1] = 0xffffffff; + } else { + rxfilt |= RGE_RXCFG_MULTI; + /* Program new filter. */ + memset(hashes, 0, sizeof(hashes)); + + ETHER_FIRST_MULTI(step, ac, enm); + while (enm != NULL) { + h = ether_crc32_be(enm->enm_addrlo, + ETHER_ADDR_LEN) >> 26; + + if (h < 32) + hashes[0] |= (1 << h); + else + hashes[1] |= (1 << (h - 32)); + + ETHER_NEXT_MULTI(step, enm); + } + } + + RGE_WRITE_4(sc, RGE_RXCFG, rxfilt); + RGE_WRITE_4(sc, RGE_MAR0, swap32(hashes[1])); + RGE_WRITE_4(sc, RGE_MAR4, swap32(hashes[0])); +} + +void +rge_set_phy_power(struct rge_softc *sc, int on) +{ + int i; + + if (on) { + RGE_SETBIT_1(sc, RGE_PMCH, 0xc0); + + rge_write_phy(sc, 0, MII_BMCR, BMCR_AUTOEN); + + for (i = 0; i < RGE_TIMEOUT; i++) { + if ((rge_read_phy_ocp(sc, 0xa420) & 0x0080) == 3) + break; + DELAY(1000); + } + } else + rge_write_phy(sc, 0, MII_BMCR, BMCR_AUTOEN | BMCR_PDOWN); +} + +void +rge_phy_config(struct rge_softc *sc) +{ + uint16_t mcode_ver, val; + int i; + static const uint16_t mac_cfg3_a438_value[] = + { 0x0043, 0x00a7, 0x00d6, 0x00ec, 0x00f6, 0x00fb, 0x00fd, 0x00ff, + 0x00bb, 0x0058, 0x0029, 0x0013, 0x0009, 0x0004, 0x0002 }; + + static const uint16_t mac_cfg3_b88e_value[] = + { 0xc091, 0x6e12, 0xc092, 0x1214, 0xc094, 0x1516, 0xc096, 0x171b, + 0xc098, 0x1b1c, 0xc09a, 0x1f1f, 0xc09c, 0x2021, 0xc09e, 0x2224, + 0xc0a0, 0x2424, 0xc0a2, 0x2424, 0xc0a4, 0x2424, 0xc018, 0x0af2, + 0xc01a, 0x0d4a, 0xc01c, 0x0f26, 0xc01e, 0x118d, 0xc020, 0x14f3, + 0xc022, 0x175a, 0xc024, 0x19c0, 0xc026, 0x1c26, 0xc089, 0x6050, + 0xc08a, 0x5f6e, 0xc08c, 0x6e6e, 0xc08e, 0x6e6e, 0xc090, 0x6e12 }; + + /* Read microcode version. */ + rge_write_phy_ocp(sc, 0xa436, 0x801e); + mcode_ver = rge_read_phy_ocp(sc, 0xa438); + + if (sc->rge_type == MAC_CFG2) { + for (i = 0; i < nitems(rtl8125_mac_cfg2_ephy); i++) { + rge_write_ephy(sc, rtl8125_mac_cfg2_ephy[i].reg, + rtl8125_mac_cfg2_ephy[i].val); + } + + if (mcode_ver != RGE_MAC_CFG2_MCODE_VER) { + /* Disable PHY config. */ + RGE_CLRBIT_1(sc, 0xf2, 0x20); + DELAY(1000); + + rge_patch_phy_mcu(sc, 1); + + rge_write_phy_ocp(sc, 0xa436, 0x8024); + rge_write_phy_ocp(sc, 0xa438, 0x8600); + rge_write_phy_ocp(sc, 0xa436, 0xb82e); + rge_write_phy_ocp(sc, 0xa438, 0x0001); + + RGE_PHY_SETBIT(sc, 0xb820, 0x0080); + for (i = 0; i < nitems(rtl8125_mac_cfg2_mcu); i++) { + rge_write_phy_ocp(sc, + rtl8125_mac_cfg2_mcu[i].reg, + rtl8125_mac_cfg2_mcu[i].val); + } + RGE_PHY_CLRBIT(sc, 0xb820, 0x0080); + + rge_write_phy_ocp(sc, 0xa436, 0); + rge_write_phy_ocp(sc, 0xa438, 0); + RGE_PHY_CLRBIT(sc, 0xb82e, 0x0001); + rge_write_phy_ocp(sc, 0xa436, 0x8024); + rge_write_phy_ocp(sc, 0xa438, 0); + + rge_patch_phy_mcu(sc, 0); + + /* Enable PHY config. */ + RGE_SETBIT_1(sc, 0xf2, 0x20); + + /* Write microcode version. */ + rge_write_phy_ocp(sc, 0xa436, 0x801e); + rge_write_phy_ocp(sc, 0xa438, RGE_MAC_CFG2_MCODE_VER); + } + + val = rge_read_phy_ocp(sc, 0xad40) & ~0x03ff; + rge_write_phy_ocp(sc, 0xad40, val | 0x0084); + RGE_PHY_SETBIT(sc, 0xad4e, 0x0010); + val = rge_read_phy_ocp(sc, 0xad16) & ~0x03ff; + rge_write_phy_ocp(sc, 0xad16, val | 0x0006); + val = rge_read_phy_ocp(sc, 0xad32) & ~0x03ff; + rge_write_phy_ocp(sc, 0xad32, val | 0x0006); + RGE_PHY_CLRBIT(sc, 0xac08, 0x1100); + val = rge_read_phy_ocp(sc, 0xac8a) & ~0xf000; + rge_write_phy_ocp(sc, 0xac8a, val | 0x7000); + RGE_PHY_SETBIT(sc, 0xad18, 0x0400); + RGE_PHY_SETBIT(sc, 0xad1a, 0x03ff); + RGE_PHY_SETBIT(sc, 0xad1c, 0x03ff); + + rge_write_phy_ocp(sc, 0xa436, 0x80ea); + val = rge_read_phy_ocp(sc, 0xa438) & ~0xff00; + rge_write_phy_ocp(sc, 0xa438, val | 0xc400); + rge_write_phy_ocp(sc, 0xa436, 0x80eb); + val = rge_read_phy_ocp(sc, 0xa438) & ~0x0700; + rge_write_phy_ocp(sc, 0xa438, val | 0x0300); + rge_write_phy_ocp(sc, 0xa436, 0x80f8); + val = rge_read_phy_ocp(sc, 0xa438) & ~0xff00; + rge_write_phy_ocp(sc, 0xa438, val | 0x1c00); + rge_write_phy_ocp(sc, 0xa436, 0x80f1); + val = rge_read_phy_ocp(sc, 0xa438) & ~0xff00; + rge_write_phy_ocp(sc, 0xa438, val | 0x3000); + rge_write_phy_ocp(sc, 0xa436, 0x80fe); + val = rge_read_phy_ocp(sc, 0xa438) & ~0xff00; + rge_write_phy_ocp(sc, 0xa438, val | 0xa500); + rge_write_phy_ocp(sc, 0xa436, 0x8102); + val = rge_read_phy_ocp(sc, 0xa438) & ~0xff00; + rge_write_phy_ocp(sc, 0xa438, val | 0x5000); + rge_write_phy_ocp(sc, 0xa436, 0x8105); + val = rge_read_phy_ocp(sc, 0xa438) & ~0xff00; + rge_write_phy_ocp(sc, 0xa438, val | 0x3300); + rge_write_phy_ocp(sc, 0xa436, 0x8100); + val = rge_read_phy_ocp(sc, 0xa438) & ~0xff00; + rge_write_phy_ocp(sc, 0xa438, val | 0x7000); + rge_write_phy_ocp(sc, 0xa436, 0x8104); + val = rge_read_phy_ocp(sc, 0xa438) & ~0xff00; + rge_write_phy_ocp(sc, 0xa438, val | 0xf000); + rge_write_phy_ocp(sc, 0xa436, 0x8106); + val = rge_read_phy_ocp(sc, 0xa438) & ~0xff00; + rge_write_phy_ocp(sc, 0xa438, val | 0x6500); + rge_write_phy_ocp(sc, 0xa436, 0x80dc); + val = rge_read_phy_ocp(sc, 0xa438) & ~0xff00; + rge_write_phy_ocp(sc, 0xa438, val | 0xed00); + rge_write_phy_ocp(sc, 0xa436, 0x80df); + RGE_PHY_SETBIT(sc, 0xa438, 0x0100); + rge_write_phy_ocp(sc, 0xa436, 0x80e1); + RGE_PHY_CLRBIT(sc, 0xa438, 0x0100); + val = rge_read_phy_ocp(sc, 0xbf06) & ~0x003f; + rge_write_phy_ocp(sc, 0xbf06, val | 0x0038); + rge_write_phy_ocp(sc, 0xa436, 0x819f); + rge_write_phy_ocp(sc, 0xa438, 0xd0b6); + rge_write_phy_ocp(sc, 0xbc34, 0x5555); + val = rge_read_phy_ocp(sc, 0xbf0a) & ~0x0e00; + rge_write_phy_ocp(sc, 0xbf0a, val | 0x0a00); + RGE_PHY_CLRBIT(sc, 0xa5c0, 0x0400); + RGE_PHY_SETBIT(sc, 0xa442, 0x0800); + } else { + for (i = 0; i < nitems(rtl8125_mac_cfg3_ephy); i++) + rge_write_ephy(sc, rtl8125_mac_cfg3_ephy[i].reg, + rtl8125_mac_cfg3_ephy[i].val); + + if (mcode_ver != RGE_MAC_CFG3_MCODE_VER) { + /* Disable PHY config. */ + RGE_CLRBIT_1(sc, 0xf2, 0x20); + DELAY(1000); + + rge_patch_phy_mcu(sc, 1); + + rge_write_phy_ocp(sc, 0xa436, 0x8024); + rge_write_phy_ocp(sc, 0xa438, 0x8601); + rge_write_phy_ocp(sc, 0xa436, 0xb82e); + rge_write_phy_ocp(sc, 0xa438, 0x0001); + + RGE_PHY_SETBIT(sc, 0xb820, 0x0080); + for (i = 0; i < nitems(rtl8125_mac_cfg3_mcu); i++) { + rge_write_phy_ocp(sc, + rtl8125_mac_cfg3_mcu[i].reg, + rtl8125_mac_cfg3_mcu[i].val); + } + RGE_PHY_CLRBIT(sc, 0xb820, 0x0080); + + rge_write_phy_ocp(sc, 0xa436, 0); + rge_write_phy_ocp(sc, 0xa438, 0); + RGE_PHY_CLRBIT(sc, 0xb82e, 0x0001); + rge_write_phy_ocp(sc, 0xa436, 0x8024); + rge_write_phy_ocp(sc, 0xa438, 0); + + rge_patch_phy_mcu(sc, 0); + + /* Enable PHY config. */ + RGE_SETBIT_1(sc, 0xf2, 0x20); + + /* Write microcode version. */ + rge_write_phy_ocp(sc, 0xa436, 0x801e); + rge_write_phy_ocp(sc, 0xa438, RGE_MAC_CFG3_MCODE_VER); + } + + RGE_PHY_SETBIT(sc, 0xad4e, 0x0010); + val = rge_read_phy_ocp(sc, 0xad16) & ~0x03ff; + rge_write_phy_ocp(sc, 0xad16, val | 0x03ff); + val = rge_read_phy_ocp(sc, 0xad32) & ~0x003f; + rge_write_phy_ocp(sc, 0xad32, val | 0x0006); + RGE_PHY_CLRBIT(sc, 0xac08, 0x1000); + RGE_PHY_CLRBIT(sc, 0xac08, 0x0100); + val = rge_read_phy_ocp(sc, 0xacc0) & ~0x0003; + rge_write_phy_ocp(sc, 0xacc0, val | 0x0002); + val = rge_read_phy_ocp(sc, 0xad40) & ~0x00e0; + rge_write_phy_ocp(sc, 0xad40, val | 0x0040); + val = rge_read_phy_ocp(sc, 0xad40) & ~0x0007; + rge_write_phy_ocp(sc, 0xad40, val | 0x0004); + RGE_PHY_CLRBIT(sc, 0xac14, 0x0080); + RGE_PHY_CLRBIT(sc, 0xac80, 0x0300); + val = rge_read_phy_ocp(sc, 0xac5e) & ~0x0007; + rge_write_phy_ocp(sc, 0xac5e, val | 0x0002); + rge_write_phy_ocp(sc, 0xad4c, 0x00a8); + rge_write_phy_ocp(sc, 0xac5c, 0x01ff); + val = rge_read_phy_ocp(sc, 0xac8a) & ~0x00f0; + rge_write_phy_ocp(sc, 0xac8a, val | 0x0030); + rge_write_phy_ocp(sc, 0xb87c, 0x80a2); + rge_write_phy_ocp(sc, 0xb87e, 0x0153); + rge_write_phy_ocp(sc, 0xb87c, 0x809c); + rge_write_phy_ocp(sc, 0xb87e, 0x0153); + + rge_write_phy_ocp(sc, 0xa436, 0x81b3); + for (i = 0; i < nitems(mac_cfg3_a438_value); i++) + rge_write_phy_ocp(sc, 0xa438, mac_cfg3_a438_value[i]); + for (i = 0; i < 26; i++) + rge_write_phy_ocp(sc, 0xa438, 0); + rge_write_phy_ocp(sc, 0xa436, 0x8257); + rge_write_phy_ocp(sc, 0xa438, 0x020f); + rge_write_phy_ocp(sc, 0xa436, 0x80ea); + rge_write_phy_ocp(sc, 0xa438, 0x7843); + + rge_patch_phy_mcu(sc, 1); + RGE_PHY_CLRBIT(sc, 0xb896, 0x0001); + RGE_PHY_CLRBIT(sc, 0xb892, 0xff00); + for (i = 0; i < nitems(mac_cfg3_b88e_value); i += 2) { + rge_write_phy_ocp(sc, 0xb88e, mac_cfg3_b88e_value[i]); + rge_write_phy_ocp(sc, 0xb890, + mac_cfg3_b88e_value[i + 1]); + } + RGE_PHY_SETBIT(sc, 0xb896, 0x0001); + rge_patch_phy_mcu(sc, 0); + + RGE_PHY_SETBIT(sc, 0xd068, 0x2000); + rge_write_phy_ocp(sc, 0xa436, 0x81a2); + RGE_PHY_SETBIT(sc, 0xa438, 0x0100); + val = rge_read_phy_ocp(sc, 0xb54c) & ~0xff00; + rge_write_phy_ocp(sc, 0xb54c, val | 0xdb00); + RGE_PHY_CLRBIT(sc, 0xa454, 0x0001); + RGE_PHY_SETBIT(sc, 0xa5d4, 0x0020); + RGE_PHY_CLRBIT(sc, 0xad4e, 0x0010); + RGE_PHY_CLRBIT(sc, 0xa86a, 0x0001); + RGE_PHY_SETBIT(sc, 0xa442, 0x0800); + } + + /* Disable EEE. */ + RGE_MAC_CLRBIT(sc, 0xe040, 0x0003); + RGE_MAC_CLRBIT(sc, 0xeb62, 0x0006); + RGE_PHY_CLRBIT(sc, 0xa432, 0x0010); + RGE_PHY_CLRBIT(sc, 0xa5d0, 0x0006); + RGE_PHY_CLRBIT(sc, 0xa6d4, 0x0001); + RGE_PHY_CLRBIT(sc, 0xa6d8, 0x0010); + RGE_PHY_CLRBIT(sc, 0xa428, 0x0080); + RGE_PHY_CLRBIT(sc, 0xa4a2, 0x0200); + + rge_patch_phy_mcu(sc, 1); + RGE_MAC_CLRBIT(sc, 0xe052, 0x0001); + RGE_PHY_CLRBIT(sc, 0xa442, 0x3000); + RGE_PHY_CLRBIT(sc, 0xa430, 0x8000); + rge_patch_phy_mcu(sc, 0); +} + +void +rge_set_macaddr(struct rge_softc *sc, const uint8_t *addr) +{ + RGE_SETBIT_1(sc, RGE_EECMD, RGE_EECMD_WRITECFG); + RGE_WRITE_4(sc, RGE_MAC0, + addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]); + RGE_WRITE_4(sc, RGE_MAC4, + addr[5] << 8 | addr[4]); + RGE_CLRBIT_1(sc, RGE_EECMD, RGE_EECMD_WRITECFG); +} + +void +rge_get_macaddr(struct rge_softc *sc, uint8_t *addr) +{ + *(uint32_t *)&addr[0] = RGE_READ_4(sc, RGE_ADDR0); + *(uint16_t *)&addr[4] = RGE_READ_2(sc, RGE_ADDR1); +} + +void +rge_hw_init(struct rge_softc *sc) +{ + int i; + + RGE_SETBIT_1(sc, RGE_EECMD, RGE_EECMD_WRITECFG); + RGE_CLRBIT_1(sc, RGE_CFG5, RGE_CFG5_PME_STS); + RGE_CLRBIT_1(sc, RGE_CFG2, RGE_CFG2_CLKREQ_EN); + RGE_CLRBIT_1(sc, RGE_EECMD, RGE_EECMD_WRITECFG); + RGE_CLRBIT_1(sc, 0xf1, 0x80); + + /* Disable UPS. */ + RGE_MAC_CLRBIT(sc, 0xd40a, 0x0010); + + /* Configure MAC MCU. */ + rge_write_mac_ocp(sc, 0xfc38, 0); + + for (i = 0xfc28; i < 0xfc38; i += 2) + rge_write_mac_ocp(sc, i, 0); + + DELAY(3000); + rge_write_mac_ocp(sc, 0xfc26, 0); + + if (sc->rge_type == MAC_CFG3) { + for (i = 0; i < nitems(rtl8125_def_bps); i++) + rge_write_mac_ocp(sc, rtl8125_def_bps[i].reg, + rtl8125_def_bps[i].val); + } + + /* Disable PHY power saving. */ + rge_disable_phy_ocp_pwrsave(sc); + + /* Set PCIe uncorrectable error status. */ + rge_write_csi(sc, 0x108, + rge_read_csi(sc, 0x108) | 0x00100000); +} + +void +rge_disable_phy_ocp_pwrsave(struct rge_softc *sc) +{ + if (rge_read_phy_ocp(sc, 0xc416) != 0x0500) { + rge_patch_phy_mcu(sc, 1); + rge_write_phy_ocp(sc, 0xc416, 0); + rge_write_phy_ocp(sc, 0xc416, 0x0500); + rge_patch_phy_mcu(sc, 0); + } +} + +void +rge_patch_phy_mcu(struct rge_softc *sc, int set) +{ + uint16_t val; + int i; + + if (set) + RGE_PHY_SETBIT(sc, 0xb820, 0x0010); + else + RGE_PHY_CLRBIT(sc, 0xb820, 0x0010); + + for (i = 0; i < 1000; i++) { + val = rge_read_phy_ocp(sc, 0xb800) & 0x0040; + DELAY(100); + if (val == 0x0040) + break; + } + if (i == 1000) + printf("%s: timeout waiting to patch phy mcu\n", + sc->sc_dev.dv_xname); +} + +void +rge_add_media_types(struct rge_softc *sc) +{ + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_10_T, 0, NULL); + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_10_T | IFM_FDX, 0, NULL); + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_100_TX, 0, NULL); + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_100_TX | IFM_FDX, 0, NULL); + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_1000_T, 0, NULL); + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_1000_T | IFM_FDX, 0, NULL); + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_2500_T, 0, NULL); + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_2500_T | IFM_FDX, 0, NULL); +} + +void +rge_config_imtype(struct rge_softc *sc, int imtype) +{ + switch (imtype) { + case RGE_IMTYPE_NONE: + sc->rge_intrs = RGE_INTRS; + sc->rge_rx_ack = RGE_ISR_RX_OK | RGE_ISR_RX_DESC_UNAVAIL | + RGE_ISR_RX_FIFO_OFLOW; + sc->rge_tx_ack = RGE_ISR_TX_OK; + break; + case RGE_IMTYPE_SIM: + sc->rge_intrs = RGE_INTRS_TIMER; + sc->rge_rx_ack = RGE_ISR_PCS_TIMEOUT; + sc->rge_tx_ack = RGE_ISR_PCS_TIMEOUT; + break; + default: + panic("%s: unknown imtype %d", sc->sc_dev.dv_xname, imtype); + } +} + +void +rge_disable_sim_im(struct rge_softc *sc) +{ + RGE_WRITE_4(sc, RGE_TIMERINT, 0); + sc->rge_timerintr = 0; +} + +void +rge_setup_sim_im(struct rge_softc *sc) +{ + RGE_WRITE_4(sc, RGE_TIMERINT, 0x2600); + RGE_WRITE_4(sc, RGE_TIMERCNT, 1); + sc->rge_timerintr = 1; +} + +void +rge_setup_intr(struct rge_softc *sc, int imtype) +{ + rge_config_imtype(sc, imtype); + + /* Enable interrupts. */ + RGE_WRITE_4(sc, RGE_IMR, sc->rge_intrs); + + switch (imtype) { + case RGE_IMTYPE_NONE: + rge_disable_sim_im(sc); + break; + case RGE_IMTYPE_SIM: + rge_setup_sim_im(sc); + break; + default: + panic("%s: unknown imtype %d", sc->sc_dev.dv_xname, imtype); + } +} + +void +rge_exit_oob(struct rge_softc *sc) +{ + int i; + + RGE_CLRBIT_4(sc, RGE_RXCFG, RGE_RXCFG_ALLPHYS | RGE_RXCFG_INDIV | + RGE_RXCFG_MULTI | RGE_RXCFG_BROAD | RGE_RXCFG_RUNT | + RGE_RXCFG_ERRPKT); + + /* Disable RealWoW. */ + rge_write_mac_ocp(sc, 0xc0bc, 0x00ff); + + rge_reset(sc); + + /* Disable OOB. */ + RGE_CLRBIT_1(sc, RGE_MCUCMD, RGE_MCUCMD_IS_OOB); + + RGE_MAC_CLRBIT(sc, 0xe8de, 0x4000); + + for (i = 0; i < 10; i++) { + DELAY(100); + if (RGE_READ_2(sc, RGE_TWICMD) & 0x0200) + break; + } + + rge_write_mac_ocp(sc, 0xc0aa, 0x07d0); + rge_write_mac_ocp(sc, 0xc0a6, 0x0150); + rge_write_mac_ocp(sc, 0xc01e, 0x5555); + + for (i = 0; i < 10; i++) { + DELAY(100); + if (RGE_READ_2(sc, RGE_TWICMD) & 0x0200) + break; + } + + if (rge_read_mac_ocp(sc, 0xd42c) & 0x0100) { + for (i = 0; i < RGE_TIMEOUT; i++) { + if ((rge_read_phy_ocp(sc, 0xa420) & 0x0080) == 2) + break; + DELAY(1000); + } + RGE_MAC_CLRBIT(sc, 0xd408, 0x0100); + RGE_PHY_CLRBIT(sc, 0xa468, 0x000a); + } +} + +void +rge_write_csi(struct rge_softc *sc, uint32_t reg, uint32_t val) +{ + int i; + + RGE_WRITE_4(sc, RGE_CSIDR, val); + RGE_WRITE_4(sc, RGE_CSIAR, (1 << 16) | (reg & RGE_CSIAR_ADDR_MASK) | + (RGE_CSIAR_BYTE_EN << RGE_CSIAR_BYTE_EN_SHIFT) | RGE_CSIAR_BUSY); + + for (i = 0; i < 10; i++) { + DELAY(100); + if (!(RGE_READ_4(sc, RGE_CSIAR) & RGE_CSIAR_BUSY)) + break; + } + + DELAY(20); +} + +uint32_t +rge_read_csi(struct rge_softc *sc, uint32_t reg) +{ + int i; + + RGE_WRITE_4(sc, RGE_CSIAR, (1 << 16) | (reg & RGE_CSIAR_ADDR_MASK) | + (RGE_CSIAR_BYTE_EN << RGE_CSIAR_BYTE_EN_SHIFT)); + + for (i = 0; i < 10; i++) { + DELAY(100); + if (RGE_READ_4(sc, RGE_CSIAR) & RGE_CSIAR_BUSY) + break; + } + + DELAY(20); + + return (RGE_READ_4(sc, RGE_CSIDR)); +} + +void +rge_write_mac_ocp(struct rge_softc *sc, uint16_t reg, uint16_t val) +{ + uint32_t tmp; + + tmp = (reg >> 1) << RGE_MACOCP_ADDR_SHIFT; + tmp += val; + tmp |= RGE_MACOCP_BUSY; + RGE_WRITE_4(sc, RGE_MACOCP, tmp); +} + +uint16_t +rge_read_mac_ocp(struct rge_softc *sc, uint16_t reg) +{ + uint32_t val; + + val = (reg >> 1) << RGE_MACOCP_ADDR_SHIFT; + RGE_WRITE_4(sc, RGE_MACOCP, val); + + return (RGE_READ_4(sc, RGE_MACOCP) & RGE_MACOCP_DATA_MASK); +} + +void +rge_write_ephy(struct rge_softc *sc, uint16_t reg, uint16_t val) +{ + uint32_t tmp; + int i; + + tmp = (reg & RGE_EPHYAR_ADDR_MASK) << RGE_EPHYAR_ADDR_SHIFT; + tmp |= RGE_EPHYAR_BUSY | (val & RGE_EPHYAR_DATA_MASK); + RGE_WRITE_4(sc, RGE_EPHYAR, tmp); + + for (i = 0; i < 10; i++) { + DELAY(100); + if (!(RGE_READ_4(sc, RGE_EPHYAR) & RGE_EPHYAR_BUSY)) + break; + } + + DELAY(20); +} + +void +rge_write_phy(struct rge_softc *sc, uint16_t addr, uint16_t reg, uint16_t val) +{ + uint16_t off, phyaddr; + + phyaddr = addr ? addr : RGE_PHYBASE + (reg / 8); + phyaddr <<= 4; + + off = addr ? reg : 0x10 + (reg % 8); + + phyaddr += (off - 16) << 1; + + rge_write_phy_ocp(sc, phyaddr, val); +} + +void +rge_write_phy_ocp(struct rge_softc *sc, uint16_t reg, uint16_t val) +{ + uint32_t tmp; + int i; + + tmp = (reg >> 1) << RGE_PHYOCP_ADDR_SHIFT; + tmp |= RGE_PHYOCP_BUSY | val; + RGE_WRITE_4(sc, RGE_PHYOCP, tmp); + + for (i = 0; i < RGE_TIMEOUT; i++) { + DELAY(1); + if (!(RGE_READ_4(sc, RGE_PHYOCP) & RGE_PHYOCP_BUSY)) + break; + } +} + +uint16_t +rge_read_phy_ocp(struct rge_softc *sc, uint16_t reg) +{ + uint32_t val; + int i; + + val = (reg >> 1) << RGE_PHYOCP_ADDR_SHIFT; + RGE_WRITE_4(sc, RGE_PHYOCP, val); + + for (i = 0; i < RGE_TIMEOUT; i++) { + DELAY(1); + val = RGE_READ_4(sc, RGE_PHYOCP); + if (val & RGE_PHYOCP_BUSY) + break; + } + + return (val & RGE_PHYOCP_DATA_MASK); +} + +int +rge_get_link_status(struct rge_softc *sc) +{ + return ((RGE_READ_2(sc, RGE_PHYSTAT) & RGE_PHYSTAT_LINK) ? 1 : 0); +} + +void +rge_txstart(void *arg) +{ + struct rge_softc *sc = arg; + + RGE_WRITE_2(sc, RGE_TXSTART, RGE_TXSTART_START); +} + +void +rge_tick(void *arg) +{ + struct rge_softc *sc = arg; + int s; + + s = splnet(); + rge_link_state(sc); + splx(s); + + timeout_add_sec(&sc->sc_timeout, 1); +} + +void +rge_link_state(struct rge_softc *sc) +{ + struct ifnet *ifp = &sc->sc_arpcom.ac_if; + int link = LINK_STATE_DOWN; + + if (rge_get_link_status(sc)) + link = LINK_STATE_UP; + + if (ifp->if_link_state != link) { + ifp->if_link_state = link; + if_link_state_change(ifp); + } +} |