/* $OpenBSD: if_dwxe.c,v 1.24 2024/02/27 10:47:20 kettenis Exp $ */ /* * Copyright (c) 2008 Mark Kettenis * Copyright (c) 2017 Patrick Wildt * * 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. */ /* * Driver for the ethernet controller on the Allwinner H3/A64 SoCs. */ #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if NBPFILTER > 0 #include #endif #include #include /* * DWXE registers. */ #define DWXE_BASIC_CTL0 0x00 #define DWXE_BASIC_CTL0_DUPLEX (1 << 0) #define DWXE_BASIC_CTL0_LOOPBACK (1 << 1) #define DWXE_BASIC_CTL0_SPEED_1000 (0 << 2) #define DWXE_BASIC_CTL0_SPEED_10 (2 << 2) #define DWXE_BASIC_CTL0_SPEED_100 (3 << 2) #define DWXE_BASIC_CTL0_SPEED_MASK (3 << 2) #define DWXE_BASIC_CTL1 0x04 #define DWXE_BASIC_CTL1_SOFT_RST (1 << 0) #define DWXE_BASIC_CTL1_RX_TX_PRI (1 << 1) #define DWXE_BASIC_CTL1_BURST_LEN_MASK (0x3f << 24) #define DWXE_BASIC_CTL1_BURST_LEN(x) ((x) << 24) #define DWXE_INT_STA 0x08 #define DWXE_INT_STA_TX_INT (1 << 0) #define DWXE_INT_STA_TX_DMA_STOP_INT (1 << 1) #define DWXE_INT_STA_TX_BUF_UA_INT (1 << 2) #define DWXE_INT_STA_TX_TIMEOUT_INT (1 << 3) #define DWXE_INT_STA_TX_UNDERFLOW_INT (1 << 4) #define DWXE_INT_STA_TX_EARLY_INT (1 << 5) #define DWXE_INT_STA_RX_INT (1 << 8) #define DWXE_INT_STA_RX_BUF_UA_INT (1 << 9) #define DWXE_INT_STA_RX_DMA_STOP_INT (1 << 10) #define DWXE_INT_STA_RX_TIMEOUT_INT (1 << 11) #define DWXE_INT_STA_RX_OVERFLOW_INT (1 << 12) #define DWXE_INT_STA_RX_EARLY_INT (1 << 13) #define DWXE_INT_STA_RGMII_STA_INT (1 << 16) #define DWXE_INT_EN 0x0C #define DWXE_INT_EN_TX_INT (1 << 0) #define DWXE_INT_EN_TX_DMA_STOP_INT (1 << 1) #define DWXE_INT_EN_TX_BUF_UA_INT (1 << 2) #define DWXE_INT_EN_TX_TIMEOUT_INT (1 << 3) #define DWXE_INT_EN_TX_UNDERFLOW_INT (1 << 4) #define DWXE_INT_EN_TX_EARLY_INT (1 << 5) #define DWXE_INT_EN_RX_INT (1 << 8) #define DWXE_INT_EN_RX_BUF_UA_INT (1 << 9) #define DWXE_INT_EN_RX_DMA_STOP_INT (1 << 10) #define DWXE_INT_EN_RX_TIMEOUT_INT (1 << 11) #define DWXE_INT_EN_RX_OVERFLOW_INT (1 << 12) #define DWXE_INT_EN_RX_EARLY_INT (1 << 13) #define DWXE_INT_EN_RGMII_EN_INT (1 << 16) #define DWXE_TX_CTL0 0x10 #define DWXE_TX_CTL0_TX_TRANSMITTER_EN (1U << 31) #define DWXE_TX_CTL1 0x14 #define DWXE_TX_CTL1_TX_FIFO_FLUSH (1 << 0) #define DWXE_TX_CTL1_TX_MD (1 << 1) #define DWXE_TX_CTL1_TX_NEXT_FRM (1 << 2) #define DWXE_TX_CTL1_TX_TH_MASK (0x3 << 8) #define DWXE_TX_CTL1_TX_TH_64 0 #define DWXE_TX_CTL1_TX_TH_128 (0x1 << 8) #define DWXE_TX_CTL1_TX_TH_192 (0x2 << 8) #define DWXE_TX_CTL1_TX_TH_256 (0x3 << 8) #define DWXE_TX_CTL1_TX_DMA_EN (1 << 30) #define DWXE_TX_CTL1_TX_DMA_START (1U << 31) #define DWXE_TX_FLOW_CTL 0x1C #define DWXE_TX_FLOW_CTL_EN (1 << 0) #define DWXE_TX_DESC_LIST 0x20 #define DWXE_RX_CTL0 0x24 #define DWXE_RX_CTL0_RX_FLOW_CTL_EN (1 << 16) #define DWXE_RX_CTL0_RX_DO_CRC (1 << 27) #define DWXE_RX_CTL0_RX_RECEIVER_EN (1U << 31) #define DWXE_RX_CTL1 0x28 #define DWXE_RX_CTL1_RX_MD (1 << 1) #define DWXE_RX_CTL1_RX_TH_MASK (0x3 << 4) #define DWXE_RX_CTL1_RX_TH_32 (0x0 << 4) #define DWXE_RX_CTL1_RX_TH_64 (0x1 << 4) #define DWXE_RX_CTL1_RX_TH_96 (0x2 << 4) #define DWXE_RX_CTL1_RX_TH_128 (0x3 << 4) #define DWXE_RX_CTL1_RX_DMA_EN (1 << 30) #define DWXE_RX_CTL1_RX_DMA_START (1U << 31) #define DWXE_RX_DESC_LIST 0x34 #define DWXE_RX_FRM_FLT 0x38 #define DWXE_RX_FRM_FLT_RX_ALL (1 << 0) #define DWXE_RX_FRM_FLT_HASH_UNICAST (1 << 8) #define DWXE_RX_FRM_FLT_HASH_MULTICAST (1 << 9) #define DWXE_RX_FRM_FLT_CTL (1 << 13) #define DWXE_RX_FRM_FLT_RX_ALL_MULTICAST (1 << 16) #define DWXE_RX_FRM_FLT_DIS_BROADCAST (1 << 17) #define DWXE_RX_FRM_FLT_DIS_ADDR_FILTER (1U << 31) #define DWXE_RX_HASH0 0x40 #define DWXE_RX_HASH1 0x44 #define DWXE_MDIO_CMD 0x48 #define DWXE_MDIO_CMD_MII_BUSY (1 << 0) #define DWXE_MDIO_CMD_MII_WRITE (1 << 1) #define DWXE_MDIO_CMD_PHY_REG_SHIFT 4 #define DWXE_MDIO_CMD_PHY_ADDR_SHIFT 12 #define DWXE_MDIO_CMD_MDC_DIV_RATIO_M_SHIFT 20 #define DWXE_MDIO_CMD_MDC_DIV_RATIO_M_MASK 0x7 #define DWXE_MDIO_CMD_MDC_DIV_RATIO_M_16 0 #define DWXE_MDIO_CMD_MDC_DIV_RATIO_M_32 1 #define DWXE_MDIO_CMD_MDC_DIV_RATIO_M_64 2 #define DWXE_MDIO_CMD_MDC_DIV_RATIO_M_128 3 #define DWXE_MDIO_DATA 0x4C #define DWXE_MACADDR_HI 0x50 #define DWXE_MACADDR_LO 0x54 #define DWXE_TX_DMA_STA 0xB0 #define DWXE_TX_CUR_DESC 0xB4 #define DWXE_TX_CUR_BUF 0xB8 #define DWXE_RX_DMA_STA 0xC0 #define DWXE_RX_CUR_DESC 0xC4 #define DWXE_RX_CUR_BUF 0xC8 /* * DWXE descriptors. */ struct dwxe_desc { uint32_t sd_status; uint32_t sd_len; uint32_t sd_addr; uint32_t sd_next; }; /* Tx status bits. */ #define DWXE_TX_DEFER (1 << 0) #define DWXE_TX_UNDERFLOW_ERR (1 << 1) #define DWXE_TX_DEFER_ERR (1 << 2) #define DWXE_TX_COL_CNT_MASK (0xf << 3) #define DWXE_TX_COL_CNT_SHIFT 3 #define DWXE_TX_COL_ERR_1 (1 << 8) #define DWXE_TX_COL_ERR_0 (1 << 9) #define DWXE_TX_CRS_ERR (1 << 10) #define DWXE_TX_PAYLOAD_ERR (1 << 12) #define DWXE_TX_LENGTH_ERR (1 << 14) #define DWXE_TX_HEADER_ERR (1 << 16) #define DWXE_TX_DESC_CTL (1U << 31) /* Rx status bits */ #define DWXE_RX_PAYLOAD_ERR (1 << 0) #define DWXE_RX_CRC_ERR (1 << 1) #define DWXE_RX_PHY_ERR (1 << 3) #define DWXE_RX_LENGTH_ERR (1 << 4) #define DWXE_RX_FRM_TYPE (1 << 5) #define DWXE_RX_COL_ERR (1 << 6) #define DWXE_RX_HEADER_ERR (1 << 7) #define DWXE_RX_LAST_DESC (1 << 8) #define DWXE_RX_FIR_DESC (1 << 9) #define DWXE_RX_OVERFLOW_ERR (1 << 11) #define DWXE_RX_SAF_FAIL (1 << 13) #define DWXE_RX_NO_ENOUGH_BUF_ERR (1 << 14) #define DWXE_RX_FRM_LEN_MASK 0x3fff #define DWXE_RX_FRM_LEN_SHIFT 16 #define DWXE_RX_DAF_FAIL (1 << 30) #define DWXE_RX_DESC_CTL (1U << 31) /* Tx size bits */ #define DWXE_TX_BUF_SIZE (0xfff << 0) #define DWXE_TX_CRC_CTL (1 << 26) #define DWXE_TX_CHECKSUM_CTL_MASK (0x3 << 27) #define DWXE_TX_CHECKSUM_CTL_IP (1 << 27) #define DWXE_TX_CHECKSUM_CTL_NO_PSE (2 << 27) #define DWXE_TX_CHECKSUM_CTL_FULL (3 << 27) #define DWXE_TX_FIR_DESC (1 << 29) #define DWXE_TX_LAST_DESC (1 << 30) #define DWXE_TX_INT_CTL (1U << 31) /* Rx size bits */ #define DWXE_RX_BUF_SIZE (0xfff << 0) #define DWXE_RX_INT_CTL (1U << 31) /* EMAC syscon bits */ #define SYSCON_EMAC 0x30 #define SYSCON_ETCS_MASK (0x3 << 0) #define SYSCON_ETCS_MII (0 << 0) #define SYSCON_ETCS_EXT_GMII (1 << 0) #define SYSCON_ETCS_INT_GMII (2 << 0) #define SYSCON_EPIT (1 << 2) /* 1: RGMII, 0: MII */ #define SYSCON_ERXDC_MASK (0xf << 5) #define SYSCON_ERXDC_SHIFT 5 #define SYSCON_ETXDC_MASK (0x7 << 10) #define SYSCON_ETXDC_SHIFT 10 #define SYSCON_RMII_EN (1 << 13) /* 1: enable RMII (overrides EPIT) */ #define SYSCON_H3_EPHY_SELECT (1 << 15) /* 1: internal PHY, 0: external PHY */ #define SYSCON_H3_EPHY_SHUTDOWN (1 << 16) /* 1: shutdown, 0: power up */ #define SYSCON_H3_EPHY_LED_POL (1 << 17) /* 1: active low, 0: active high */ #define SYSCON_H3_EPHY_CLK_SEL (1 << 18) /* 1: 24MHz, 0: 25MHz */ #define SYSCON_H3_EPHY_ADDR_MASK (0x1f << 20) #define SYSCON_H3_EPHY_ADDR_SHIFT 20 /* GMAC syscon bits (Allwinner R40) */ #define SYSCON_GMAC 0x00 #define SYSCON_GTCS_MASK SYSCON_ETCS_MASK #define SYSCON_GTCS_MII SYSCON_ETCS_MII #define SYSCON_GTCS_EXT_GMII SYSCON_ETCS_EXT_GMII #define SYSCON_GTCS_INT_GMII SYSCON_ETCS_INT_GMII #define SYSCON_GPIT SYSCON_EPIT #define SYSCON_GRXDC_MASK (0x7 << 5) #define SYSCON_GRXDC_SHIFT 5 struct dwxe_buf { bus_dmamap_t tb_map; struct mbuf *tb_m; }; #define DWXE_NTXDESC 256 #define DWXE_NTXSEGS 16 #define DWXE_NRXDESC 256 struct dwxe_dmamem { bus_dmamap_t tdm_map; bus_dma_segment_t tdm_seg; size_t tdm_size; caddr_t tdm_kva; }; #define DWXE_DMA_MAP(_tdm) ((_tdm)->tdm_map) #define DWXE_DMA_LEN(_tdm) ((_tdm)->tdm_size) #define DWXE_DMA_DVA(_tdm) ((_tdm)->tdm_map->dm_segs[0].ds_addr) #define DWXE_DMA_KVA(_tdm) ((void *)(_tdm)->tdm_kva) struct dwxe_softc { struct device sc_dev; int sc_node; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; bus_dma_tag_t sc_dmat; void *sc_ih; struct arpcom sc_ac; #define sc_lladdr sc_ac.ac_enaddr struct mii_data sc_mii; #define sc_media sc_mii.mii_media int sc_link; int sc_phyloc; struct dwxe_dmamem *sc_txring; struct dwxe_buf *sc_txbuf; struct dwxe_desc *sc_txdesc; int sc_tx_prod; int sc_tx_cons; struct dwxe_dmamem *sc_rxring; struct dwxe_buf *sc_rxbuf; struct dwxe_desc *sc_rxdesc; int sc_rx_prod; struct if_rxring sc_rx_ring; int sc_rx_cons; struct timeout sc_tick; struct timeout sc_rxto; uint32_t sc_clk; }; #define DEVNAME(_s) ((_s)->sc_dev.dv_xname) int dwxe_match(struct device *, void *, void *); void dwxe_attach(struct device *, struct device *, void *); int dwxe_activate(struct device *, int); void dwxe_init(struct dwxe_softc *sc); void dwxe_phy_setup_emac(struct dwxe_softc *); void dwxe_phy_setup_gmac(struct dwxe_softc *); const struct cfattach dwxe_ca = { sizeof(struct dwxe_softc), dwxe_match, dwxe_attach, NULL, dwxe_activate }; struct cfdriver dwxe_cd = { NULL, "dwxe", DV_IFNET }; uint32_t dwxe_read(struct dwxe_softc *, bus_addr_t); void dwxe_write(struct dwxe_softc *, bus_addr_t, uint32_t); int dwxe_ioctl(struct ifnet *, u_long, caddr_t); void dwxe_start(struct ifqueue *); void dwxe_watchdog(struct ifnet *); int dwxe_media_change(struct ifnet *); void dwxe_media_status(struct ifnet *, struct ifmediareq *); int dwxe_mii_readreg(struct device *, int, int); void dwxe_mii_writereg(struct device *, int, int, int); void dwxe_mii_statchg(struct device *); void dwxe_lladdr_read(struct dwxe_softc *, uint8_t *); void dwxe_lladdr_write(struct dwxe_softc *); void dwxe_tick(void *); void dwxe_rxtick(void *); int dwxe_intr(void *); void dwxe_tx_proc(struct dwxe_softc *); void dwxe_rx_proc(struct dwxe_softc *); void dwxe_up(struct dwxe_softc *); void dwxe_down(struct dwxe_softc *); void dwxe_iff(struct dwxe_softc *); int dwxe_encap(struct dwxe_softc *, struct mbuf *, int *, int *); void dwxe_reset(struct dwxe_softc *); void dwxe_stop_dma(struct dwxe_softc *); struct dwxe_dmamem * dwxe_dmamem_alloc(struct dwxe_softc *, bus_size_t, bus_size_t); void dwxe_dmamem_free(struct dwxe_softc *, struct dwxe_dmamem *); struct mbuf *dwxe_alloc_mbuf(struct dwxe_softc *, bus_dmamap_t); void dwxe_fill_rx_ring(struct dwxe_softc *); int dwxe_match(struct device *parent, void *cfdata, void *aux) { struct fdt_attach_args *faa = aux; return OF_is_compatible(faa->fa_node, "allwinner,sun8i-h3-emac") || OF_is_compatible(faa->fa_node, "allwinner,sun8i-r40-gmac") || OF_is_compatible(faa->fa_node, "allwinner,sun50i-a64-emac"); } void dwxe_attach(struct device *parent, struct device *self, void *aux) { struct dwxe_softc *sc = (void *)self; struct fdt_attach_args *faa = aux; char phy_mode[16] = { 0 }; struct ifnet *ifp; uint32_t phy; int mii_flags = 0; int node; sc->sc_node = faa->fa_node; sc->sc_iot = faa->fa_iot; if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, faa->fa_reg[0].size, 0, &sc->sc_ioh)) { printf("%s: cannot map registers\n", self->dv_xname); return; } sc->sc_dmat = faa->fa_dmat; OF_getprop(faa->fa_node, "phy-mode", phy_mode, sizeof(phy_mode)); if (strcmp(phy_mode, "rgmii") == 0) mii_flags |= MIIF_SETDELAY; else if (strcmp(phy_mode, "rgmii-rxid") == 0) mii_flags |= MIIF_SETDELAY | MIIF_RXID; else if (strcmp(phy_mode, "rgmii-txid") == 0) mii_flags |= MIIF_SETDELAY | MIIF_TXID; else if (strcmp(phy_mode, "rgmii-id") == 0) mii_flags |= MIIF_SETDELAY | MIIF_RXID | MIIF_TXID; /* Lookup PHY. */ phy = OF_getpropint(faa->fa_node, "phy-handle", 0); node = OF_getnodebyphandle(phy); if (node) sc->sc_phyloc = OF_getpropint(node, "reg", MII_PHY_ANY); else sc->sc_phyloc = MII_PHY_ANY; sc->sc_mii.mii_node = node; sc->sc_clk = clock_get_frequency(faa->fa_node, "stmmaceth"); if (sc->sc_clk > 160000000) sc->sc_clk = DWXE_MDIO_CMD_MDC_DIV_RATIO_M_128; else if (sc->sc_clk > 80000000) sc->sc_clk = DWXE_MDIO_CMD_MDC_DIV_RATIO_M_64; else if (sc->sc_clk > 40000000) sc->sc_clk = DWXE_MDIO_CMD_MDC_DIV_RATIO_M_32; else sc->sc_clk = DWXE_MDIO_CMD_MDC_DIV_RATIO_M_16; if (OF_getprop(faa->fa_node, "local-mac-address", &sc->sc_lladdr, ETHER_ADDR_LEN) != ETHER_ADDR_LEN) dwxe_lladdr_read(sc, sc->sc_lladdr); printf(": address %s\n", ether_sprintf(sc->sc_lladdr)); dwxe_init(sc); timeout_set(&sc->sc_tick, dwxe_tick, sc); timeout_set(&sc->sc_rxto, dwxe_rxtick, sc); ifp = &sc->sc_ac.ac_if; ifp->if_softc = sc; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_xflags = IFXF_MPSAFE; ifp->if_ioctl = dwxe_ioctl; ifp->if_qstart = dwxe_start; ifp->if_watchdog = dwxe_watchdog; ifq_init_maxlen(&ifp->if_snd, DWXE_NTXDESC - 1); bcopy(sc->sc_dev.dv_xname, ifp->if_xname, IFNAMSIZ); ifp->if_capabilities = IFCAP_VLAN_MTU; sc->sc_mii.mii_ifp = ifp; sc->sc_mii.mii_readreg = dwxe_mii_readreg; sc->sc_mii.mii_writereg = dwxe_mii_writereg; sc->sc_mii.mii_statchg = dwxe_mii_statchg; ifmedia_init(&sc->sc_media, 0, dwxe_media_change, dwxe_media_status); mii_attach(self, &sc->sc_mii, 0xffffffff, sc->sc_phyloc, MII_OFFSET_ANY, MIIF_NOISOLATE | mii_flags); if (LIST_FIRST(&sc->sc_mii.mii_phys) == NULL) { printf("%s: no PHY found!\n", sc->sc_dev.dv_xname); ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_MANUAL, 0, NULL); ifmedia_set(&sc->sc_media, IFM_ETHER|IFM_MANUAL); } else ifmedia_set(&sc->sc_media, IFM_ETHER|IFM_AUTO); if_attach(ifp); ether_ifattach(ifp); sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_NET | IPL_MPSAFE, dwxe_intr, sc, sc->sc_dev.dv_xname); if (sc->sc_ih == NULL) printf("%s: can't establish interrupt\n", sc->sc_dev.dv_xname); } int dwxe_activate(struct device *self, int act) { struct dwxe_softc *sc = (struct dwxe_softc *)self; struct ifnet *ifp = &sc->sc_ac.ac_if; switch (act) { case DVACT_SUSPEND: if (ifp->if_flags & IFF_RUNNING) dwxe_down(sc); break; case DVACT_RESUME: dwxe_init(sc); if (ifp->if_flags & IFF_UP) dwxe_up(sc); break; } return 0; } void dwxe_init(struct dwxe_softc *sc) { uint32_t phy_supply; pinctrl_byname(sc->sc_node, "default"); /* Enable clock. */ clock_enable(sc->sc_node, "stmmaceth"); reset_deassert(sc->sc_node, "stmmaceth"); delay(5000); /* Power up PHY. */ phy_supply = OF_getpropint(sc->sc_node, "phy-supply", 0); if (phy_supply) regulator_enable(phy_supply); /* Do hardware specific initializations. */ if (OF_is_compatible(sc->sc_node, "allwinner,sun8i-r40-gmac")) dwxe_phy_setup_gmac(sc); else dwxe_phy_setup_emac(sc); dwxe_reset(sc); } void dwxe_phy_setup_emac(struct dwxe_softc *sc) { struct regmap *rm; uint32_t syscon; uint32_t tx_delay, rx_delay; char *phy_mode; int len; rm = regmap_byphandle(OF_getpropint(sc->sc_node, "syscon", 0)); if (rm == NULL) return; syscon = regmap_read_4(rm, SYSCON_EMAC); syscon &= ~(SYSCON_ETCS_MASK|SYSCON_EPIT|SYSCON_RMII_EN); syscon &= ~(SYSCON_ETXDC_MASK | SYSCON_ERXDC_MASK); syscon &= ~SYSCON_H3_EPHY_SELECT; if ((len = OF_getproplen(sc->sc_node, "phy-mode")) <= 0) return; phy_mode = malloc(len, M_TEMP, M_WAITOK); OF_getprop(sc->sc_node, "phy-mode", phy_mode, len); if (!strncmp(phy_mode, "rgmii", strlen("rgmii"))) syscon |= SYSCON_EPIT | SYSCON_ETCS_INT_GMII; else if (!strncmp(phy_mode, "rmii", strlen("rmii"))) syscon |= SYSCON_EPIT | SYSCON_ETCS_EXT_GMII; else if (!strncmp(phy_mode, "mii", strlen("mii")) && OF_is_compatible(sc->sc_node, "allwinner,sun8i-h3-emac")) { syscon &= ~SYSCON_H3_EPHY_SHUTDOWN; syscon |= SYSCON_H3_EPHY_SELECT | SYSCON_H3_EPHY_CLK_SEL; if (OF_getproplen(sc->sc_node, "allwinner,leds-active-low") == 0) syscon |= SYSCON_H3_EPHY_LED_POL; else syscon &= ~SYSCON_H3_EPHY_LED_POL; syscon &= ~SYSCON_H3_EPHY_ADDR_MASK; syscon |= (sc->sc_phyloc << SYSCON_H3_EPHY_ADDR_SHIFT); } free(phy_mode, M_TEMP, len); tx_delay = OF_getpropint(sc->sc_node, "allwinner,tx-delay-ps", 0); rx_delay = OF_getpropint(sc->sc_node, "allwinner,rx-delay-ps", 0); syscon |= ((tx_delay / 100) << SYSCON_ETXDC_SHIFT) & SYSCON_ETXDC_MASK; syscon |= ((rx_delay / 100) << SYSCON_ERXDC_SHIFT) & SYSCON_ERXDC_MASK; regmap_write_4(rm, SYSCON_EMAC, syscon); } void dwxe_phy_setup_gmac(struct dwxe_softc *sc) { struct regmap *rm; uint32_t syscon; uint32_t rx_delay; char *phy_mode; int len; rm = regmap_byphandle(OF_getpropint(sc->sc_node, "syscon", 0)); if (rm == NULL) return; syscon = regmap_read_4(rm, SYSCON_GMAC); syscon &= ~(SYSCON_GTCS_MASK|SYSCON_GPIT|SYSCON_ERXDC_MASK); if ((len = OF_getproplen(sc->sc_node, "phy-mode")) <= 0) return; phy_mode = malloc(len, M_TEMP, M_WAITOK); OF_getprop(sc->sc_node, "phy-mode", phy_mode, len); if (!strncmp(phy_mode, "rgmii", strlen("rgmii"))) syscon |= SYSCON_GPIT | SYSCON_GTCS_INT_GMII; else if (!strncmp(phy_mode, "rmii", strlen("rmii"))) syscon |= SYSCON_GPIT | SYSCON_GTCS_EXT_GMII; free(phy_mode, M_TEMP, len); rx_delay = OF_getpropint(sc->sc_node, "allwinner,rx-delay-ps", 0); syscon |= ((rx_delay / 100) << SYSCON_ERXDC_SHIFT) & SYSCON_ERXDC_MASK; regmap_write_4(rm, SYSCON_GMAC, syscon); } uint32_t dwxe_read(struct dwxe_softc *sc, bus_addr_t addr) { return bus_space_read_4(sc->sc_iot, sc->sc_ioh, addr); } void dwxe_write(struct dwxe_softc *sc, bus_addr_t addr, uint32_t data) { bus_space_write_4(sc->sc_iot, sc->sc_ioh, addr, data); } void dwxe_lladdr_read(struct dwxe_softc *sc, uint8_t *lladdr) { uint32_t machi, maclo; machi = dwxe_read(sc, DWXE_MACADDR_HI); maclo = dwxe_read(sc, DWXE_MACADDR_LO); lladdr[0] = (maclo >> 0) & 0xff; lladdr[1] = (maclo >> 8) & 0xff; lladdr[2] = (maclo >> 16) & 0xff; lladdr[3] = (maclo >> 24) & 0xff; lladdr[4] = (machi >> 0) & 0xff; lladdr[5] = (machi >> 8) & 0xff; } void dwxe_lladdr_write(struct dwxe_softc *sc) { dwxe_write(sc, DWXE_MACADDR_HI, sc->sc_lladdr[5] << 8 | sc->sc_lladdr[4] << 0); dwxe_write(sc, DWXE_MACADDR_LO, sc->sc_lladdr[3] << 24 | sc->sc_lladdr[2] << 16 | sc->sc_lladdr[1] << 8 | sc->sc_lladdr[0] << 0); } void dwxe_start(struct ifqueue *ifq) { struct ifnet *ifp = ifq->ifq_if; struct dwxe_softc *sc = ifp->if_softc; struct mbuf *m; int error, idx, left, used; if (!(ifp->if_flags & IFF_RUNNING)) return; if (ifq_is_oactive(&ifp->if_snd)) return; if (ifq_empty(&ifp->if_snd)) return; if (!sc->sc_link) return; idx = sc->sc_tx_prod; left = sc->sc_tx_cons; if (left <= idx) left += DWXE_NTXDESC; left -= idx; used = 0; for (;;) { if (used + DWXE_NTXSEGS + 1 > left) { ifq_set_oactive(ifq); break; } m = ifq_dequeue(ifq); if (m == NULL) break; error = dwxe_encap(sc, m, &idx, &used); if (error == EFBIG) { m_freem(m); /* give up: drop it */ ifp->if_oerrors++; continue; } #if NBPFILTER > 0 if (ifp->if_bpf) bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT); #endif } if (sc->sc_tx_prod != idx) { sc->sc_tx_prod = idx; /* Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; dwxe_write(sc, DWXE_TX_CTL1, dwxe_read(sc, DWXE_TX_CTL1) | DWXE_TX_CTL1_TX_DMA_START); } } int dwxe_ioctl(struct ifnet *ifp, u_long cmd, caddr_t addr) { struct dwxe_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *)addr; int error = 0, s; s = splnet(); switch (cmd) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; /* FALLTHROUGH */ case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) { if (ifp->if_flags & IFF_RUNNING) error = ENETRESET; else dwxe_up(sc); } else { if (ifp->if_flags & IFF_RUNNING) dwxe_down(sc); } break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &sc->sc_media, cmd); break; case SIOCGIFRXR: error = if_rxr_ioctl((struct if_rxrinfo *)ifr->ifr_data, NULL, MCLBYTES, &sc->sc_rx_ring); break; default: error = ether_ioctl(ifp, &sc->sc_ac, cmd, addr); break; } if (error == ENETRESET) { if (ifp->if_flags & IFF_RUNNING) dwxe_iff(sc); error = 0; } splx(s); return (error); } void dwxe_watchdog(struct ifnet *ifp) { printf("%s\n", __func__); } int dwxe_media_change(struct ifnet *ifp) { struct dwxe_softc *sc = ifp->if_softc; if (LIST_FIRST(&sc->sc_mii.mii_phys)) mii_mediachg(&sc->sc_mii); return (0); } void dwxe_media_status(struct ifnet *ifp, struct ifmediareq *ifmr) { struct dwxe_softc *sc = ifp->if_softc; if (LIST_FIRST(&sc->sc_mii.mii_phys)) { mii_pollstat(&sc->sc_mii); ifmr->ifm_active = sc->sc_mii.mii_media_active; ifmr->ifm_status = sc->sc_mii.mii_media_status; } } int dwxe_mii_readreg(struct device *self, int phy, int reg) { struct dwxe_softc *sc = (void *)self; int n; dwxe_write(sc, DWXE_MDIO_CMD, sc->sc_clk << DWXE_MDIO_CMD_MDC_DIV_RATIO_M_SHIFT | phy << DWXE_MDIO_CMD_PHY_ADDR_SHIFT | reg << DWXE_MDIO_CMD_PHY_REG_SHIFT | DWXE_MDIO_CMD_MII_BUSY); for (n = 0; n < 1000; n++) { if ((dwxe_read(sc, DWXE_MDIO_CMD) & DWXE_MDIO_CMD_MII_BUSY) == 0) return dwxe_read(sc, DWXE_MDIO_DATA); delay(10); } printf("%s: mii_read timeout\n", sc->sc_dev.dv_xname); return (0); } void dwxe_mii_writereg(struct device *self, int phy, int reg, int val) { struct dwxe_softc *sc = (void *)self; int n; dwxe_write(sc, DWXE_MDIO_DATA, val); dwxe_write(sc, DWXE_MDIO_CMD, sc->sc_clk << DWXE_MDIO_CMD_MDC_DIV_RATIO_M_SHIFT | phy << DWXE_MDIO_CMD_PHY_ADDR_SHIFT | reg << DWXE_MDIO_CMD_PHY_REG_SHIFT | DWXE_MDIO_CMD_MII_WRITE | DWXE_MDIO_CMD_MII_BUSY); for (n = 0; n < 1000; n++) { if ((dwxe_read(sc, DWXE_MDIO_CMD) & DWXE_MDIO_CMD_MII_BUSY) == 0) return; delay(10); } printf("%s: mii_write timeout\n", sc->sc_dev.dv_xname); } void dwxe_mii_statchg(struct device *self) { struct dwxe_softc *sc = (void *)self; uint32_t basicctrl; basicctrl = dwxe_read(sc, DWXE_BASIC_CTL0); basicctrl &= ~DWXE_BASIC_CTL0_SPEED_MASK; switch (IFM_SUBTYPE(sc->sc_mii.mii_media_active)) { case IFM_1000_SX: case IFM_1000_LX: case IFM_1000_CX: case IFM_1000_T: basicctrl |= DWXE_BASIC_CTL0_SPEED_1000; sc->sc_link = 1; break; case IFM_100_TX: basicctrl |= DWXE_BASIC_CTL0_SPEED_100; sc->sc_link = 1; break; case IFM_10_T: basicctrl |= DWXE_BASIC_CTL0_SPEED_10; sc->sc_link = 1; break; default: sc->sc_link = 0; return; } if (sc->sc_link == 0) return; basicctrl &= ~DWXE_BASIC_CTL0_DUPLEX; if ((sc->sc_mii.mii_media_active & IFM_GMASK) == IFM_FDX) basicctrl |= DWXE_BASIC_CTL0_DUPLEX; /* XXX: RX/TX flow control? */ dwxe_write(sc, DWXE_BASIC_CTL0, basicctrl); } void dwxe_tick(void *arg) { struct dwxe_softc *sc = arg; int s; s = splnet(); mii_tick(&sc->sc_mii); splx(s); timeout_add_sec(&sc->sc_tick, 1); } void dwxe_rxtick(void *arg) { struct dwxe_softc *sc = arg; uint32_t ctl; int s; s = splnet(); ctl = dwxe_read(sc, DWXE_RX_CTL1); dwxe_write(sc, DWXE_RX_CTL1, ctl & ~DWXE_RX_CTL1_RX_DMA_EN); bus_dmamap_sync(sc->sc_dmat, DWXE_DMA_MAP(sc->sc_rxring), 0, DWXE_DMA_LEN(sc->sc_rxring), BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE); dwxe_write(sc, DWXE_RX_DESC_LIST, 0); sc->sc_rx_prod = sc->sc_rx_cons = 0; dwxe_fill_rx_ring(sc); bus_dmamap_sync(sc->sc_dmat, DWXE_DMA_MAP(sc->sc_rxring), 0, DWXE_DMA_LEN(sc->sc_rxring), BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); dwxe_write(sc, DWXE_RX_DESC_LIST, DWXE_DMA_DVA(sc->sc_rxring)); dwxe_write(sc, DWXE_RX_CTL1, ctl); splx(s); } int dwxe_intr(void *arg) { struct dwxe_softc *sc = arg; uint32_t reg; reg = dwxe_read(sc, DWXE_INT_STA); dwxe_write(sc, DWXE_INT_STA, reg); if (reg & DWXE_INT_STA_RX_INT) dwxe_rx_proc(sc); if (reg & DWXE_INT_STA_TX_INT || reg & DWXE_INT_STA_TX_BUF_UA_INT) dwxe_tx_proc(sc); return (1); } void dwxe_tx_proc(struct dwxe_softc *sc) { struct ifnet *ifp = &sc->sc_ac.ac_if; struct dwxe_desc *txd; struct dwxe_buf *txb; int idx, txfree; bus_dmamap_sync(sc->sc_dmat, DWXE_DMA_MAP(sc->sc_txring), 0, DWXE_DMA_LEN(sc->sc_txring), BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); txfree = 0; while (sc->sc_tx_cons != sc->sc_tx_prod) { idx = sc->sc_tx_cons; KASSERT(idx < DWXE_NTXDESC); txd = &sc->sc_txdesc[idx]; if (txd->sd_status & DWXE_TX_DESC_CTL) break; txb = &sc->sc_txbuf[idx]; if (txb->tb_m) { bus_dmamap_sync(sc->sc_dmat, txb->tb_map, 0, txb->tb_map->dm_mapsize, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_dmat, txb->tb_map); m_freem(txb->tb_m); txb->tb_m = NULL; } txfree++; if (sc->sc_tx_cons == (DWXE_NTXDESC - 1)) sc->sc_tx_cons = 0; else sc->sc_tx_cons++; txd->sd_status = 0; } if (sc->sc_tx_cons == sc->sc_tx_prod) ifp->if_timer = 0; if (txfree) { if (ifq_is_oactive(&ifp->if_snd)) ifq_restart(&ifp->if_snd); } } void dwxe_rx_proc(struct dwxe_softc *sc) { struct ifnet *ifp = &sc->sc_ac.ac_if; struct dwxe_desc *rxd; struct dwxe_buf *rxb; struct mbuf_list ml = MBUF_LIST_INITIALIZER(); struct mbuf *m; int idx, len, cnt, put; if ((ifp->if_flags & IFF_RUNNING) == 0) return; bus_dmamap_sync(sc->sc_dmat, DWXE_DMA_MAP(sc->sc_rxring), 0, DWXE_DMA_LEN(sc->sc_rxring), BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); cnt = if_rxr_inuse(&sc->sc_rx_ring); put = 0; while (put < cnt) { idx = sc->sc_rx_cons; KASSERT(idx < DWXE_NRXDESC); rxd = &sc->sc_rxdesc[idx]; if (rxd->sd_status & DWXE_RX_DESC_CTL) break; len = (rxd->sd_status >> DWXE_RX_FRM_LEN_SHIFT) & DWXE_RX_FRM_LEN_MASK; rxb = &sc->sc_rxbuf[idx]; KASSERT(rxb->tb_m); bus_dmamap_sync(sc->sc_dmat, rxb->tb_map, 0, len, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->sc_dmat, rxb->tb_map); /* Strip off CRC. */ len -= ETHER_CRC_LEN; KASSERT(len > 0); m = rxb->tb_m; rxb->tb_m = NULL; m->m_pkthdr.len = m->m_len = len; ml_enqueue(&ml, m); put++; if (sc->sc_rx_cons == (DWXE_NRXDESC - 1)) sc->sc_rx_cons = 0; else sc->sc_rx_cons++; } if_rxr_put(&sc->sc_rx_ring, put); if (ifiq_input(&ifp->if_rcv, &ml)) if_rxr_livelocked(&sc->sc_rx_ring); dwxe_fill_rx_ring(sc); bus_dmamap_sync(sc->sc_dmat, DWXE_DMA_MAP(sc->sc_rxring), 0, DWXE_DMA_LEN(sc->sc_rxring), BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } void dwxe_up(struct dwxe_softc *sc) { struct ifnet *ifp = &sc->sc_ac.ac_if; struct dwxe_buf *txb, *rxb; int i; /* Allocate Tx descriptor ring. */ sc->sc_txring = dwxe_dmamem_alloc(sc, DWXE_NTXDESC * sizeof(struct dwxe_desc), 8); sc->sc_txdesc = DWXE_DMA_KVA(sc->sc_txring); sc->sc_txbuf = malloc(sizeof(struct dwxe_buf) * DWXE_NTXDESC, M_DEVBUF, M_WAITOK); for (i = 0; i < DWXE_NTXDESC; i++) { txb = &sc->sc_txbuf[i]; bus_dmamap_create(sc->sc_dmat, MCLBYTES, DWXE_NTXSEGS, MCLBYTES, 0, BUS_DMA_WAITOK, &txb->tb_map); txb->tb_m = NULL; sc->sc_txdesc[i].sd_next = DWXE_DMA_DVA(sc->sc_txring) + ((i+1) % DWXE_NTXDESC) * sizeof(struct dwxe_desc); } bus_dmamap_sync(sc->sc_dmat, DWXE_DMA_MAP(sc->sc_txring), 0, DWXE_DMA_LEN(sc->sc_txring), BUS_DMASYNC_PREWRITE); sc->sc_tx_prod = sc->sc_tx_cons = 0; dwxe_write(sc, DWXE_TX_DESC_LIST, DWXE_DMA_DVA(sc->sc_txring)); /* Allocate descriptor ring. */ sc->sc_rxring = dwxe_dmamem_alloc(sc, DWXE_NRXDESC * sizeof(struct dwxe_desc), 8); sc->sc_rxdesc = DWXE_DMA_KVA(sc->sc_rxring); sc->sc_rxbuf = malloc(sizeof(struct dwxe_buf) * DWXE_NRXDESC, M_DEVBUF, M_WAITOK); for (i = 0; i < DWXE_NRXDESC; i++) { rxb = &sc->sc_rxbuf[i]; bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1, MCLBYTES, 0, BUS_DMA_WAITOK, &rxb->tb_map); rxb->tb_m = NULL; sc->sc_rxdesc[i].sd_next = DWXE_DMA_DVA(sc->sc_rxring) + ((i+1) % DWXE_NRXDESC) * sizeof(struct dwxe_desc); } if_rxr_init(&sc->sc_rx_ring, 2, DWXE_NRXDESC); sc->sc_rx_prod = sc->sc_rx_cons = 0; dwxe_fill_rx_ring(sc); bus_dmamap_sync(sc->sc_dmat, DWXE_DMA_MAP(sc->sc_rxring), 0, DWXE_DMA_LEN(sc->sc_rxring), BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); dwxe_write(sc, DWXE_RX_DESC_LIST, DWXE_DMA_DVA(sc->sc_rxring)); dwxe_lladdr_write(sc); //dwxe_write(sc, DWXE_BASIC_CTL1, DWXE_BASIC_CTL1_SOFT_RST); /* Configure media. */ if (LIST_FIRST(&sc->sc_mii.mii_phys)) mii_mediachg(&sc->sc_mii); /* Program promiscuous mode and multicast filters. */ dwxe_iff(sc); ifp->if_flags |= IFF_RUNNING; ifq_clr_oactive(&ifp->if_snd); dwxe_write(sc, DWXE_INT_EN, DWXE_INT_EN_RX_INT | DWXE_INT_EN_TX_INT | DWXE_INT_EN_TX_BUF_UA_INT); dwxe_write(sc, DWXE_TX_CTL1, dwxe_read(sc, DWXE_TX_CTL1) | DWXE_TX_CTL1_TX_MD | DWXE_TX_CTL1_TX_NEXT_FRM | DWXE_TX_CTL1_TX_DMA_EN); dwxe_write(sc, DWXE_RX_CTL1, dwxe_read(sc, DWXE_RX_CTL1) | DWXE_RX_CTL1_RX_MD | DWXE_RX_CTL1_RX_DMA_EN); dwxe_write(sc, DWXE_TX_CTL0, dwxe_read(sc, DWXE_TX_CTL0) | DWXE_TX_CTL0_TX_TRANSMITTER_EN); dwxe_write(sc, DWXE_RX_CTL0, dwxe_read(sc, DWXE_RX_CTL0) | DWXE_RX_CTL0_RX_RECEIVER_EN | DWXE_RX_CTL0_RX_DO_CRC); timeout_add_sec(&sc->sc_tick, 1); } void dwxe_down(struct dwxe_softc *sc) { struct ifnet *ifp = &sc->sc_ac.ac_if; struct dwxe_buf *txb, *rxb; uint32_t dmactrl; int i; timeout_del(&sc->sc_rxto); timeout_del(&sc->sc_tick); ifp->if_flags &= ~IFF_RUNNING; ifq_clr_oactive(&ifp->if_snd); ifp->if_timer = 0; dwxe_stop_dma(sc); dwxe_write(sc, DWXE_TX_CTL0, dwxe_read(sc, DWXE_TX_CTL0) & ~DWXE_TX_CTL0_TX_TRANSMITTER_EN); dwxe_write(sc, DWXE_RX_CTL0, dwxe_read(sc, DWXE_RX_CTL0) & ~DWXE_RX_CTL0_RX_RECEIVER_EN); dmactrl = dwxe_read(sc, DWXE_TX_CTL1); dmactrl &= ~DWXE_TX_CTL1_TX_DMA_EN; dwxe_write(sc, DWXE_TX_CTL1, dmactrl); dmactrl = dwxe_read(sc, DWXE_RX_CTL1); dmactrl &= ~DWXE_RX_CTL1_RX_DMA_EN; dwxe_write(sc, DWXE_RX_CTL1, dmactrl); dwxe_write(sc, DWXE_INT_EN, 0); intr_barrier(sc->sc_ih); ifq_barrier(&ifp->if_snd); for (i = 0; i < DWXE_NTXDESC; i++) { txb = &sc->sc_txbuf[i]; if (txb->tb_m) { bus_dmamap_sync(sc->sc_dmat, txb->tb_map, 0, txb->tb_map->dm_mapsize, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_dmat, txb->tb_map); m_freem(txb->tb_m); } bus_dmamap_destroy(sc->sc_dmat, txb->tb_map); } dwxe_dmamem_free(sc, sc->sc_txring); free(sc->sc_txbuf, M_DEVBUF, 0); for (i = 0; i < DWXE_NRXDESC; i++) { rxb = &sc->sc_rxbuf[i]; if (rxb->tb_m) { bus_dmamap_sync(sc->sc_dmat, rxb->tb_map, 0, rxb->tb_map->dm_mapsize, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->sc_dmat, rxb->tb_map); m_freem(rxb->tb_m); } bus_dmamap_destroy(sc->sc_dmat, rxb->tb_map); } dwxe_dmamem_free(sc, sc->sc_rxring); free(sc->sc_rxbuf, M_DEVBUF, 0); } /* Bit Reversal - http://aggregate.org/MAGIC/#Bit%20Reversal */ static uint32_t bitrev32(uint32_t x) { x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1)); x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2)); x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4)); x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8)); return (x >> 16) | (x << 16); } void dwxe_iff(struct dwxe_softc *sc) { struct arpcom *ac = &sc->sc_ac; struct ifnet *ifp = &sc->sc_ac.ac_if; struct ether_multi *enm; struct ether_multistep step; uint32_t crc, hash[2], hashbit, hashreg; uint32_t reg; reg = 0; ifp->if_flags &= ~IFF_ALLMULTI; bzero(hash, sizeof(hash)); if (ifp->if_flags & IFF_PROMISC || ac->ac_multirangecnt > 0) { ifp->if_flags |= IFF_ALLMULTI; reg |= DWXE_RX_FRM_FLT_RX_ALL_MULTICAST; if (ifp->if_flags & IFF_PROMISC) reg |= DWXE_RX_FRM_FLT_DIS_ADDR_FILTER; } else { reg |= DWXE_RX_FRM_FLT_HASH_MULTICAST; ETHER_FIRST_MULTI(step, ac, enm); while (enm != NULL) { crc = ether_crc32_le(enm->enm_addrlo, ETHER_ADDR_LEN) & 0x7f; crc = bitrev32(~crc) >> 26; hashreg = (crc >> 5); hashbit = (crc & 0x1f); hash[hashreg] |= (1 << hashbit); ETHER_NEXT_MULTI(step, enm); } } dwxe_lladdr_write(sc); dwxe_write(sc, DWXE_RX_HASH0, hash[1]); dwxe_write(sc, DWXE_RX_HASH1, hash[0]); dwxe_write(sc, DWXE_RX_FRM_FLT, reg); } int dwxe_encap(struct dwxe_softc *sc, struct mbuf *m, int *idx, int *used) { struct dwxe_desc *txd, *txd_start; bus_dmamap_t map; int cur, frag, i; cur = frag = *idx; map = sc->sc_txbuf[cur].tb_map; if (bus_dmamap_load_mbuf(sc->sc_dmat, map, m, BUS_DMA_NOWAIT)) { if (m_defrag(m, M_DONTWAIT)) return (EFBIG); if (bus_dmamap_load_mbuf(sc->sc_dmat, map, m, BUS_DMA_NOWAIT)) return (EFBIG); } /* Sync the DMA map. */ bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize, BUS_DMASYNC_PREWRITE); txd = txd_start = &sc->sc_txdesc[frag]; for (i = 0; i < map->dm_nsegs; i++) { txd->sd_addr = map->dm_segs[i].ds_addr; txd->sd_len = map->dm_segs[i].ds_len; if (i == 0) txd->sd_len |= DWXE_TX_FIR_DESC; if (i == (map->dm_nsegs - 1)) txd->sd_len |= DWXE_TX_LAST_DESC | DWXE_TX_INT_CTL; if (i != 0) txd->sd_status = DWXE_TX_DESC_CTL; bus_dmamap_sync(sc->sc_dmat, DWXE_DMA_MAP(sc->sc_txring), frag * sizeof(*txd), sizeof(*txd), BUS_DMASYNC_PREWRITE); cur = frag; if (frag == (DWXE_NTXDESC - 1)) { txd = &sc->sc_txdesc[0]; frag = 0; } else { txd++; frag++; } KASSERT(frag != sc->sc_tx_cons); } txd_start->sd_status = DWXE_TX_DESC_CTL; bus_dmamap_sync(sc->sc_dmat, DWXE_DMA_MAP(sc->sc_txring), *idx * sizeof(*txd), sizeof(*txd), BUS_DMASYNC_PREWRITE); KASSERT(sc->sc_txbuf[cur].tb_m == NULL); sc->sc_txbuf[*idx].tb_map = sc->sc_txbuf[cur].tb_map; sc->sc_txbuf[cur].tb_map = map; sc->sc_txbuf[cur].tb_m = m; *idx = frag; *used += map->dm_nsegs; return (0); } void dwxe_reset(struct dwxe_softc *sc) { int n; dwxe_stop_dma(sc); dwxe_write(sc, DWXE_BASIC_CTL1, DWXE_BASIC_CTL1_SOFT_RST); for (n = 0; n < 1000; n++) { if ((dwxe_read(sc, DWXE_BASIC_CTL1) & DWXE_BASIC_CTL1_SOFT_RST) == 0) return; delay(10); } printf("%s: reset timeout\n", sc->sc_dev.dv_xname); } void dwxe_stop_dma(struct dwxe_softc *sc) { uint32_t dmactrl; /* Stop DMA. */ dmactrl = dwxe_read(sc, DWXE_TX_CTL1); dmactrl &= ~DWXE_TX_CTL1_TX_DMA_EN; dmactrl |= DWXE_TX_CTL1_TX_FIFO_FLUSH; dwxe_write(sc, DWXE_TX_CTL1, dmactrl); } struct dwxe_dmamem * dwxe_dmamem_alloc(struct dwxe_softc *sc, bus_size_t size, bus_size_t align) { struct dwxe_dmamem *tdm; int nsegs; tdm = malloc(sizeof(*tdm), M_DEVBUF, M_WAITOK | M_ZERO); tdm->tdm_size = size; if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW, &tdm->tdm_map) != 0) goto tdmfree; if (bus_dmamem_alloc(sc->sc_dmat, size, align, 0, &tdm->tdm_seg, 1, &nsegs, BUS_DMA_WAITOK) != 0) goto destroy; if (bus_dmamem_map(sc->sc_dmat, &tdm->tdm_seg, nsegs, size, &tdm->tdm_kva, BUS_DMA_WAITOK | BUS_DMA_COHERENT) != 0) goto free; if (bus_dmamap_load(sc->sc_dmat, tdm->tdm_map, tdm->tdm_kva, size, NULL, BUS_DMA_WAITOK) != 0) goto unmap; bzero(tdm->tdm_kva, size); return (tdm); unmap: bus_dmamem_unmap(sc->sc_dmat, tdm->tdm_kva, size); free: bus_dmamem_free(sc->sc_dmat, &tdm->tdm_seg, 1); destroy: bus_dmamap_destroy(sc->sc_dmat, tdm->tdm_map); tdmfree: free(tdm, M_DEVBUF, 0); return (NULL); } void dwxe_dmamem_free(struct dwxe_softc *sc, struct dwxe_dmamem *tdm) { bus_dmamem_unmap(sc->sc_dmat, tdm->tdm_kva, tdm->tdm_size); bus_dmamem_free(sc->sc_dmat, &tdm->tdm_seg, 1); bus_dmamap_destroy(sc->sc_dmat, tdm->tdm_map); free(tdm, M_DEVBUF, 0); } struct mbuf * dwxe_alloc_mbuf(struct dwxe_softc *sc, bus_dmamap_t map) { struct mbuf *m = NULL; m = MCLGETL(NULL, M_DONTWAIT, MCLBYTES); if (!m) return (NULL); m->m_len = m->m_pkthdr.len = MCLBYTES; m_adj(m, ETHER_ALIGN); if (bus_dmamap_load_mbuf(sc->sc_dmat, map, m, BUS_DMA_NOWAIT) != 0) { printf("%s: could not load mbuf DMA map", DEVNAME(sc)); m_freem(m); return (NULL); } bus_dmamap_sync(sc->sc_dmat, map, 0, m->m_pkthdr.len, BUS_DMASYNC_PREREAD); return (m); } void dwxe_fill_rx_ring(struct dwxe_softc *sc) { struct dwxe_desc *rxd; struct dwxe_buf *rxb; u_int slots; for (slots = if_rxr_get(&sc->sc_rx_ring, DWXE_NRXDESC); slots > 0; slots--) { rxb = &sc->sc_rxbuf[sc->sc_rx_prod]; rxb->tb_m = dwxe_alloc_mbuf(sc, rxb->tb_map); if (rxb->tb_m == NULL) break; rxd = &sc->sc_rxdesc[sc->sc_rx_prod]; rxd->sd_len = rxb->tb_map->dm_segs[0].ds_len - 1; rxd->sd_addr = rxb->tb_map->dm_segs[0].ds_addr; rxd->sd_status = DWXE_RX_DESC_CTL; if (sc->sc_rx_prod == (DWXE_NRXDESC - 1)) sc->sc_rx_prod = 0; else sc->sc_rx_prod++; } if_rxr_put(&sc->sc_rx_ring, slots); if (if_rxr_inuse(&sc->sc_rx_ring) == 0) timeout_add(&sc->sc_rxto, 1); }