/* $NetBSD: fec.c,v 1.1 2026/06/27 13:28:34 rkujawa Exp $ */ /*- * Copyright (c) 2008, 2026 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Radoslaw Kujawa and Robert Swindells. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Driver for the MPC5200B Fast Ethernet Controller (FEC). */ #include __KERNEL_RCSID(0, "$NetBSD: fec.c,v 1.1 2026/06/27 13:28:34 rkujawa Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FEC_READ(sc, r) bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (r)) #define FEC_WRITE(sc, r, v) bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (r), (v)) #define FEC_RX_TASK 3 /* TASK_FEC_RX image slot */ #define FEC_TX_TASK 2 /* TASK_FEC_TX image slot */ #define FEC_RX_INITIATOR 3 /* INITIATOR_FEC_RX */ #define FEC_TX_INITIATOR 4 /* INITIATOR_FEC_TX */ static const struct bestcomm_bd_layout fec_rx_layout = BESTCOMM_LAYOUT_FEC_RX; static const struct bestcomm_bd_layout fec_tx_layout = BESTCOMM_LAYOUT_FEC_TX; static int fec_match(device_t, cfdata_t, void *); static void fec_attach(device_t, device_t, void *); static int fec_miibus_readreg(device_t, int, int, uint16_t *); static int fec_miibus_writereg(device_t, int, int, uint16_t); static void fec_miibus_statchg(struct ifnet *); static int fec_ioctl(struct ifnet *, u_long, void *); static int fec_init(struct ifnet *); static void fec_stop(struct ifnet *, int); static void fec_start(struct ifnet *); static void fec_tick(void *); static void fec_reset(struct fec_softc *); static void fec_mac_setup(struct fec_softc *); static int fec_newbuf(struct fec_softc *, u_int); static int fec_intr(void *); static int fec_rxintr(void *); static int fec_txintr(void *); CFATTACH_DECL_NEW(fec, sizeof(struct fec_softc), fec_match, fec_attach, NULL, NULL); static int fec_match(device_t parent, cfdata_t cf, void *aux) { struct obio_attach_args *oba = aux; char compat[40]; int len; if (strcmp(oba->obio_name, "ethernet") == 0) return 1; len = OF_getprop(oba->obio_node, "compatible", compat, sizeof(compat)); if (len > 0 && (strcmp(compat, "mpc5200-fec") == 0 || strcmp(compat, "mpc5200b-fec") == 0)) return 1; return 0; } static void fec_attach(device_t parent, device_t self, void *aux) { struct fec_softc *sc = device_private(self); struct obio_attach_args *oba = aux; struct ifnet *ifp = &sc->sc_if; struct mii_data *mii = &sc->sc_mii; uint8_t enaddr[ETHER_ADDR_LEN]; bus_size_t size; int ist; sc->sc_dev = self; sc->sc_iot = oba->obio_bst; sc->sc_dmat = oba->obio_dmat; sc->sc_pa = oba->obio_addr; callout_init(&sc->sc_tick_ch, 0); callout_setfunc(&sc->sc_tick_ch, fec_tick, sc); size = oba->obio_size != 0 ? oba->obio_size : FEC_REG_SIZE; if (bus_space_map(sc->sc_iot, oba->obio_addr, size, 0, &sc->sc_ioh) != 0) { aprint_error(": can't map registers\n"); return; } if (OF_getprop(oba->obio_node, "local-mac-address", enaddr, sizeof(enaddr)) != sizeof(enaddr) && OF_getprop(oba->obio_node, "mac-address", enaddr, sizeof(enaddr)) != sizeof(enaddr)) { aprint_error(": no MAC address\n"); return; } memcpy(sc->sc_enaddr, enaddr, ETHER_ADDR_LEN); if (obio_decode_interrupt(oba->obio_node, 0, &sc->sc_irq, &ist)) sc->sc_ih = intr_establish(sc->sc_irq, ist, IPL_NET, fec_intr, sc); aprint_normal(": MPC5200 FEC, address %s\n", ether_sprintf(sc->sc_enaddr)); /* Reset the controller and program the MII management clock. */ sc->sc_ipb_freq = mpc5200_cdm_get_ipb_freq(); fec_reset(sc); /* MII / PHY. */ mii->mii_ifp = ifp; mii->mii_readreg = fec_miibus_readreg; mii->mii_writereg = fec_miibus_writereg; mii->mii_statchg = fec_miibus_statchg; sc->sc_ec.ec_mii = mii; ifmedia_init(&mii->mii_media, 0, ether_mediachange, ether_mediastatus); mii_attach(self, mii, 0xffffffff, MII_PHY_ANY, MII_OFFSET_ANY, 0); if (LIST_FIRST(&mii->mii_phys) == NULL) { aprint_error_dev(self, "no PHY found\n"); ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_MANUAL, 0, NULL); ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_MANUAL); } else ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO); strlcpy(ifp->if_xname, device_xname(self), IFNAMSIZ); ifp->if_softc = sc; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_init = fec_init; ifp->if_stop = fec_stop; ifp->if_start = fec_start; ifp->if_ioctl = fec_ioctl; IFQ_SET_READY(&ifp->if_snd); if_attach(ifp); if_deferred_start_init(ifp, NULL); ether_ifattach(ifp, sc->sc_enaddr); } /* * Soft-reset the controller and set the MII management clock divider so MDC * stays at or below 2.5 MHz, derived from the IPB clock. */ static void fec_reset(struct fec_softc *sc) { uint32_t div; int i; FEC_WRITE(sc, FEC_ECNTRL, FEC_ECNTRL_RESET); for (i = 0; i < 1000; i++) { if ((FEC_READ(sc, FEC_ECNTRL) & FEC_ECNTRL_RESET) == 0) break; DELAY(10); } FEC_WRITE(sc, FEC_IMASK, 0); /* mask all events */ FEC_WRITE(sc, FEC_IEVENT, 0xffffffff); /* clear events */ div = howmany(sc->sc_ipb_freq, 2 * FEC_MII_MAX_CLK); if (div > 0x3f) div = 0x3f; FEC_WRITE(sc, FEC_MII_SPEED, FEC_MII_SPEED_DIV(div)); } static int fec_mii_wait(struct fec_softc *sc) { int i; for (i = 0; i < FEC_PHY_TIMEOUT; i++) { if (FEC_READ(sc, FEC_IEVENT) & FEC_INT_MII) { FEC_WRITE(sc, FEC_IEVENT, FEC_INT_MII); return 0; } DELAY(10); } return ETIMEDOUT; } static int fec_miibus_readreg(device_t self, int phy, int reg, uint16_t *val) { struct fec_softc *sc = device_private(self); FEC_WRITE(sc, FEC_IEVENT, FEC_INT_MII); FEC_WRITE(sc, FEC_MII_DATA, FEC_MII_READ(phy, reg)); if (fec_mii_wait(sc) != 0) return ETIMEDOUT; *val = FEC_READ(sc, FEC_MII_DATA) & FEC_MII_DATA_MASK; return 0; } static int fec_miibus_writereg(device_t self, int phy, int reg, uint16_t val) { struct fec_softc *sc = device_private(self); FEC_WRITE(sc, FEC_IEVENT, FEC_INT_MII); FEC_WRITE(sc, FEC_MII_DATA, FEC_MII_WRITE(phy, reg, val)); return fec_mii_wait(sc); } static void fec_miibus_statchg(struct ifnet *ifp) { struct fec_softc *sc = ifp->if_softc; uint32_t xcr; xcr = FEC_READ(sc, FEC_X_CNTRL); if (sc->sc_mii.mii_media_active & IFM_FDX) xcr |= FEC_X_CNTRL_FDEN; else xcr &= ~FEC_X_CNTRL_FDEN; FEC_WRITE(sc, FEC_X_CNTRL, xcr); } static void fec_tick(void *arg) { struct fec_softc *sc = arg; int s; s = splnet(); mii_tick(&sc->sc_mii); splx(s); /* * Meh. Under load the receive FIFO can overrun, work it around. */ if ((sc->sc_if.if_flags & IFF_RUNNING) && (FEC_READ(sc, FEC_ECNTRL) & FEC_ECNTRL_ETHER_EN) == 0) { if_statinc(&sc->sc_if, if_ierrors); aprint_error_dev(sc->sc_dev, "ETHER_EN cleared, resetting\n"); s = splnet(); (void)fec_init(&sc->sc_if); /* reschedules the callout */ splx(s); return; } callout_schedule(&sc->sc_tick_ch, hz); } /* Program the station address and receive/transmit MAC control. */ static void fec_mac_setup(struct fec_softc *sc) { FEC_WRITE(sc, FEC_PADDR1, (sc->sc_enaddr[0] << 24) | (sc->sc_enaddr[1] << 16) | (sc->sc_enaddr[2] << 8) | sc->sc_enaddr[3]); FEC_WRITE(sc, FEC_PADDR2, (sc->sc_enaddr[4] << 24) | (sc->sc_enaddr[5] << 16) | 0x8808); FEC_WRITE(sc, FEC_IADDR1, 0); FEC_WRITE(sc, FEC_IADDR2, 0); /* * XXX: Accept all multicast groups via the hash filter. */ FEC_WRITE(sc, FEC_GADDR1, 0xffffffff); FEC_WRITE(sc, FEC_GADDR2, 0xffffffff); FEC_WRITE(sc, FEC_R_CNTRL, FEC_R_CNTRL_MAX_FL(ETHER_MAX_LEN) | FEC_R_CNTRL_MII_MODE); FEC_WRITE(sc, FEC_X_CNTRL, FEC_X_CNTRL_FDEN); /* * Enable frame mode on both FIFOs */ FEC_WRITE(sc, FEC_RESET_CNTRL, FEC_RESET_CNTRL_FIFO_EN); FEC_WRITE(sc, FEC_RFIFO_CONTROL, FEC_FIFO_CTRL_INIT); FEC_WRITE(sc, FEC_TFIFO_CONTROL, FEC_FIFO_CTRL_INIT); /* * The transmit FIFO requests data from BestComm only while it holds * fewer than "alarm" bytes! */ FEC_WRITE(sc, FEC_TFIFO_ALARM, 0x80); } /* Allocate a cluster for receive descriptor idx and hand it to the engine. */ static int fec_newbuf(struct fec_softc *sc, u_int idx) { struct mbuf *m; int error; MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return ENOBUFS; MCLGET(m, M_DONTWAIT); if ((m->m_flags & M_EXT) == 0) { m_freem(m); return ENOBUFS; } m->m_len = m->m_pkthdr.len = MCLBYTES; error = bus_dmamap_load_mbuf(sc->sc_dmat, sc->sc_rxmap[idx], m, BUS_DMA_NOWAIT | BUS_DMA_READ); if (error) { m_freem(m); return error; } bus_dmamap_sync(sc->sc_dmat, sc->sc_rxmap[idx], 0, MCLBYTES, BUS_DMASYNC_PREREAD); sc->sc_rxmbuf[idx] = m; bestcomm_bd_post(&sc->sc_rxring, idx, sc->sc_rxmap[idx]->dm_segs[0].ds_addr, MCLBYTES, 0); return 0; } static int fec_init(struct ifnet *ifp) { struct fec_softc *sc = ifp->if_softc; u_int i; int error; fec_stop(ifp, 0); if (!bestcomm_available()) { aprint_error_ifnet(ifp, "BestComm SDMA not available\n"); return ENXIO; } fec_reset(sc); fec_mac_setup(sc); if (bestcomm_bd_setup(&sc->sc_rxring, FEC_RX_TASK, &fec_rx_layout, sc->sc_pa + FEC_RFIFO_DATA, FEC_NRXDESC, MCLBYTES, 4, FEC_RX_INITIATOR, 6) != 0 || bestcomm_bd_setup(&sc->sc_txring, FEC_TX_TASK, &fec_tx_layout, sc->sc_pa + FEC_TFIFO_DATA, FEC_NTXDESC, MCLBYTES, 4, FEC_TX_INITIATOR, 5) != 0) { aprint_error_ifnet(ifp, "cannot set up SDMA rings\n"); return ENOMEM; } for (i = 0; i < FEC_NRXDESC; i++) { if (sc->sc_rxmap[i] == NULL && bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0, BUS_DMA_NOWAIT, &sc->sc_rxmap[i]) != 0) return ENOMEM; if ((error = fec_newbuf(sc, i)) != 0) return error; } for (i = 0; i < FEC_NTXDESC; i++) { if (sc->sc_txmap[i] == NULL && bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0, BUS_DMA_NOWAIT, &sc->sc_txmap[i]) != 0) return ENOMEM; } sc->sc_rxptr = 0; sc->sc_txnext = sc->sc_txdirty = sc->sc_txbusy = 0; /* Hook the BestComm task-completion interrupts (via the SIU cascade). */ sc->sc_rxih = intr_establish(bestcomm_task_irq(FEC_RX_TASK), IST_LEVEL, IPL_NET, fec_rxintr, sc); sc->sc_txih = intr_establish(bestcomm_task_irq(FEC_TX_TASK), IST_LEVEL, IPL_NET, fec_txintr, sc); /* * Bring the link up before enabling the controller. */ mii_mediachg(&sc->sc_mii); for (i = 0; i < 500; i++) { mii_pollstat(&sc->sc_mii); if (sc->sc_mii.mii_media_status & IFM_ACTIVE) break; DELAY(10000); } FEC_WRITE(sc, FEC_ECNTRL, FEC_ECNTRL_ETHER_EN); bestcomm_task_start(FEC_RX_TASK); bestcomm_task_start(FEC_TX_TASK); ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; callout_schedule(&sc->sc_tick_ch, hz); return 0; } static void fec_stop(struct ifnet *ifp, int disable) { struct fec_softc *sc = ifp->if_softc; u_int i; callout_stop(&sc->sc_tick_ch); if (ifp->if_flags & IFF_RUNNING) { bestcomm_task_stop(FEC_RX_TASK); bestcomm_task_stop(FEC_TX_TASK); } FEC_WRITE(sc, FEC_ECNTRL, 0); if (sc->sc_rxih != NULL) { intr_disestablish(sc->sc_rxih); sc->sc_rxih = NULL; } if (sc->sc_txih != NULL) { intr_disestablish(sc->sc_txih); sc->sc_txih = NULL; } mii_down(&sc->sc_mii); for (i = 0; i < FEC_NRXDESC; i++) { if (sc->sc_rxmbuf[i] != NULL) { bus_dmamap_unload(sc->sc_dmat, sc->sc_rxmap[i]); m_freem(sc->sc_rxmbuf[i]); sc->sc_rxmbuf[i] = NULL; } } for (i = 0; i < FEC_NTXDESC; i++) { if (sc->sc_txmbuf[i] != NULL) { bus_dmamap_unload(sc->sc_dmat, sc->sc_txmap[i]); m_freem(sc->sc_txmbuf[i]); sc->sc_txmbuf[i] = NULL; } } ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); } static void fec_start(struct ifnet *ifp) { struct fec_softc *sc = ifp->if_softc; struct mbuf *m, *mn; u_int idx; int error, posted = 0; if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING) return; while (sc->sc_txbusy < FEC_NTXDESC) { IFQ_POLL(&ifp->if_snd, m); if (m == NULL) break; idx = sc->sc_txnext; error = bus_dmamap_load_mbuf(sc->sc_dmat, sc->sc_txmap[idx], m, BUS_DMA_NOWAIT | BUS_DMA_WRITE); if (error == EFBIG) { /* The buffer descriptor holds one segment; collapse. */ mn = m_defrag(m, M_DONTWAIT); if (mn != NULL) { m = mn; error = bus_dmamap_load_mbuf(sc->sc_dmat, sc->sc_txmap[idx], m, BUS_DMA_NOWAIT | BUS_DMA_WRITE); } } IFQ_DEQUEUE(&ifp->if_snd, m); if (error) { m_freem(m); if_statinc(ifp, if_oerrors); continue; } bus_dmamap_sync(sc->sc_dmat, sc->sc_txmap[idx], 0, m->m_pkthdr.len, BUS_DMASYNC_PREWRITE); bpf_mtap(ifp, m, BPF_D_OUT); sc->sc_txmbuf[idx] = m; bestcomm_bd_post(&sc->sc_txring, idx, sc->sc_txmap[idx]->dm_segs[0].ds_addr, m->m_pkthdr.len, BESTCOMM_BD_FLAG_LAST | BESTCOMM_BD_FLAG_INT); sc->sc_txnext = (idx + 1) % FEC_NTXDESC; sc->sc_txbusy++; posted = 1; } /* * (Re-)enable the transmit task. */ if (posted) bestcomm_task_start(FEC_TX_TASK); if (sc->sc_txbusy == FEC_NTXDESC) ifp->if_flags |= IFF_OACTIVE; } /* BestComm FEC_RX task completion: hand received frames up, refill the ring. */ static int fec_rxintr(void *arg) { struct fec_softc *sc = arg; struct ifnet *ifp = &sc->sc_if; struct mbuf *m; uint32_t st; u_int idx; int len, handled = 0; for (;;) { idx = sc->sc_rxptr; st = bestcomm_bd_status(&sc->sc_rxring, idx); if (st & BESTCOMM_BD_READY) break; handled = 1; bus_dmamap_sync(sc->sc_dmat, sc->sc_rxmap[idx], 0, MCLBYTES, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->sc_dmat, sc->sc_rxmap[idx]); m = sc->sc_rxmbuf[idx]; sc->sc_rxmbuf[idx] = NULL; len = st & BESTCOMM_BD_LEN_MASK; if (len > ETHER_CRC_LEN) len -= ETHER_CRC_LEN; /* strip FCS */ if (fec_newbuf(sc, idx) != 0) { /* No replacement: recycle this buffer, drop the frame. */ sc->sc_rxmbuf[idx] = m; (void)bus_dmamap_load_mbuf(sc->sc_dmat, sc->sc_rxmap[idx], m, BUS_DMA_NOWAIT | BUS_DMA_READ); bus_dmamap_sync(sc->sc_dmat, sc->sc_rxmap[idx], 0, MCLBYTES, BUS_DMASYNC_PREREAD); bestcomm_bd_post(&sc->sc_rxring, idx, sc->sc_rxmap[idx]->dm_segs[0].ds_addr, MCLBYTES, 0); if_statinc(ifp, if_ierrors); } else { m_set_rcvif(m, ifp); m->m_pkthdr.len = m->m_len = len; if_percpuq_enqueue(ifp->if_percpuq, m); } sc->sc_rxptr = (idx + 1) % FEC_NRXDESC; } return handled; } /* BestComm FEC_TX task completion: reap transmitted frames. */ static int fec_txintr(void *arg) { struct fec_softc *sc = arg; struct ifnet *ifp = &sc->sc_if; uint32_t st; u_int idx; int handled = 0; while (sc->sc_txbusy > 0) { idx = sc->sc_txdirty; st = bestcomm_bd_status(&sc->sc_txring, idx); if (st & BESTCOMM_BD_READY) break; handled = 1; bus_dmamap_sync(sc->sc_dmat, sc->sc_txmap[idx], 0, sc->sc_txmbuf[idx]->m_pkthdr.len, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_dmat, sc->sc_txmap[idx]); m_freem(sc->sc_txmbuf[idx]); sc->sc_txmbuf[idx] = NULL; if_statinc(ifp, if_opackets); sc->sc_txdirty = (idx + 1) % FEC_NTXDESC; sc->sc_txbusy--; } if (handled) { ifp->if_flags &= ~IFF_OACTIVE; if_schedule_deferred_start(ifp); } return handled; } static int fec_ioctl(struct ifnet *ifp, u_long cmd, void *data) { int s, error; s = splnet(); error = ether_ioctl(ifp, cmd, data); if (error == ENETRESET) { /* * XXX: Just acknowledge it. */ error = 0; } splx(s); return error; } static int fec_intr(void *arg) { struct fec_softc *sc = arg; uint32_t ievent; ievent = FEC_READ(sc, FEC_IEVENT); FEC_WRITE(sc, FEC_IEVENT, ievent); /* acknowledge */ /* Frame rx/tx is serviced by the BestComm task-completion handlers. */ return ievent != 0; }