// SPDX-License-Identifier: (GPL-2.0 OR MIT) /* * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates. * stmmac XGMAC support. */ #include #include #include "stmmac.h" #include "dwxgmac2.h" static void dwxgmac2_core_init(struct mac_device_info *hw, struct net_device *dev) { void __iomem *ioaddr = hw->pcsr; int mtu = dev->mtu; u32 tx, rx; tx = readl(ioaddr + XGMAC_TX_CONFIG); rx = readl(ioaddr + XGMAC_RX_CONFIG); tx |= XGMAC_CORE_INIT_TX; rx |= XGMAC_CORE_INIT_RX; if (mtu >= 9000) { rx |= XGMAC_CONFIG_GPSLCE; rx |= XGMAC_JUMBO_LEN << XGMAC_CONFIG_GPSL_SHIFT; rx |= XGMAC_CONFIG_WD; } else if (mtu > 2000) { rx |= XGMAC_CONFIG_JE; } else if (mtu > 1500) { rx |= XGMAC_CONFIG_S2KP; } if (hw->ps) { tx |= XGMAC_CONFIG_TE; tx &= ~hw->link.speed_mask; switch (hw->ps) { case SPEED_10000: tx |= hw->link.xgmii.speed10000; break; case SPEED_2500: tx |= hw->link.speed2500; break; case SPEED_1000: default: tx |= hw->link.speed1000; break; } } writel(tx, ioaddr + XGMAC_TX_CONFIG); writel(rx, ioaddr + XGMAC_RX_CONFIG); writel(XGMAC_INT_DEFAULT_EN, ioaddr + XGMAC_INT_EN); } static void dwxgmac2_set_mac(void __iomem *ioaddr, bool enable) { u32 tx = readl(ioaddr + XGMAC_TX_CONFIG); u32 rx = readl(ioaddr + XGMAC_RX_CONFIG); if (enable) { tx |= XGMAC_CONFIG_TE; rx |= XGMAC_CONFIG_RE; } else { tx &= ~XGMAC_CONFIG_TE; rx &= ~XGMAC_CONFIG_RE; } writel(tx, ioaddr + XGMAC_TX_CONFIG); writel(rx, ioaddr + XGMAC_RX_CONFIG); } static int dwxgmac2_rx_ipc(struct mac_device_info *hw) { void __iomem *ioaddr = hw->pcsr; u32 value; value = readl(ioaddr + XGMAC_RX_CONFIG); if (hw->rx_csum) value |= XGMAC_CONFIG_IPC; else value &= ~XGMAC_CONFIG_IPC; writel(value, ioaddr + XGMAC_RX_CONFIG); return !!(readl(ioaddr + XGMAC_RX_CONFIG) & XGMAC_CONFIG_IPC); } static void dwxgmac2_rx_queue_enable(struct mac_device_info *hw, u8 mode, u32 queue) { void __iomem *ioaddr = hw->pcsr; u32 value; value = readl(ioaddr + XGMAC_RXQ_CTRL0) & ~XGMAC_RXQEN(queue); if (mode == MTL_QUEUE_AVB) value |= 0x1 << XGMAC_RXQEN_SHIFT(queue); else if (mode == MTL_QUEUE_DCB) value |= 0x2 << XGMAC_RXQEN_SHIFT(queue); writel(value, ioaddr + XGMAC_RXQ_CTRL0); } static void dwxgmac2_rx_queue_prio(struct mac_device_info *hw, u32 prio, u32 queue) { void __iomem *ioaddr = hw->pcsr; u32 value, reg; reg = (queue < 4) ? XGMAC_RXQ_CTRL2 : XGMAC_RXQ_CTRL3; if (queue >= 4) queue -= 4; value = readl(ioaddr + reg); value &= ~XGMAC_PSRQ(queue); value |= (prio << XGMAC_PSRQ_SHIFT(queue)) & XGMAC_PSRQ(queue); writel(value, ioaddr + reg); } static void dwxgmac2_prog_mtl_rx_algorithms(struct mac_device_info *hw, u32 rx_alg) { void __iomem *ioaddr = hw->pcsr; u32 value; value = readl(ioaddr + XGMAC_MTL_OPMODE); value &= ~XGMAC_RAA; switch (rx_alg) { case MTL_RX_ALGORITHM_SP: break; case MTL_RX_ALGORITHM_WSP: value |= XGMAC_RAA; break; default: break; } writel(value, ioaddr + XGMAC_MTL_OPMODE); } static void dwxgmac2_prog_mtl_tx_algorithms(struct mac_device_info *hw, u32 tx_alg) { void __iomem *ioaddr = hw->pcsr; u32 value; value = readl(ioaddr + XGMAC_MTL_OPMODE); value &= ~XGMAC_ETSALG; switch (tx_alg) { case MTL_TX_ALGORITHM_WRR: value |= XGMAC_WRR; break; case MTL_TX_ALGORITHM_WFQ: value |= XGMAC_WFQ; break; case MTL_TX_ALGORITHM_DWRR: value |= XGMAC_DWRR; break; default: break; } writel(value, ioaddr + XGMAC_MTL_OPMODE); } static void dwxgmac2_map_mtl_to_dma(struct mac_device_info *hw, u32 queue, u32 chan) { void __iomem *ioaddr = hw->pcsr; u32 value, reg; reg = (queue < 4) ? XGMAC_MTL_RXQ_DMA_MAP0 : XGMAC_MTL_RXQ_DMA_MAP1; if (queue >= 4) queue -= 4; value = readl(ioaddr + reg); value &= ~XGMAC_QxMDMACH(queue); value |= (chan << XGMAC_QxMDMACH_SHIFT(queue)) & XGMAC_QxMDMACH(queue); writel(value, ioaddr + reg); } static void dwxgmac2_config_cbs(struct mac_device_info *hw, u32 send_slope, u32 idle_slope, u32 high_credit, u32 low_credit, u32 queue) { void __iomem *ioaddr = hw->pcsr; u32 value; writel(send_slope, ioaddr + XGMAC_MTL_TCx_SENDSLOPE(queue)); writel(idle_slope, ioaddr + XGMAC_MTL_TCx_QUANTUM_WEIGHT(queue)); writel(high_credit, ioaddr + XGMAC_MTL_TCx_HICREDIT(queue)); writel(low_credit, ioaddr + XGMAC_MTL_TCx_LOCREDIT(queue)); value = readl(ioaddr + XGMAC_MTL_TCx_ETS_CONTROL(queue)); value |= XGMAC_CC | XGMAC_CBS; writel(value, ioaddr + XGMAC_MTL_TCx_ETS_CONTROL(queue)); } static int dwxgmac2_host_irq_status(struct mac_device_info *hw, struct stmmac_extra_stats *x) { void __iomem *ioaddr = hw->pcsr; u32 stat, en; en = readl(ioaddr + XGMAC_INT_EN); stat = readl(ioaddr + XGMAC_INT_STATUS); stat &= en; if (stat & XGMAC_PMTIS) { x->irq_receive_pmt_irq_n++; readl(ioaddr + XGMAC_PMT); } return 0; } static int dwxgmac2_host_mtl_irq_status(struct mac_device_info *hw, u32 chan) { void __iomem *ioaddr = hw->pcsr; int ret = 0; u32 status; status = readl(ioaddr + XGMAC_MTL_INT_STATUS); if (status & BIT(chan)) { u32 chan_status = readl(ioaddr + XGMAC_MTL_QINT_STATUS(chan)); if (chan_status & XGMAC_RXOVFIS) ret |= CORE_IRQ_MTL_RX_OVERFLOW; writel(~0x0, ioaddr + XGMAC_MTL_QINT_STATUS(chan)); } return ret; } static void dwxgmac2_flow_ctrl(struct mac_device_info *hw, unsigned int duplex, unsigned int fc, unsigned int pause_time, u32 tx_cnt) { void __iomem *ioaddr = hw->pcsr; u32 i; if (fc & FLOW_RX) writel(XGMAC_RFE, ioaddr + XGMAC_RX_FLOW_CTRL); if (fc & FLOW_TX) { for (i = 0; i < tx_cnt; i++) { u32 value = XGMAC_TFE; if (duplex) value |= pause_time << XGMAC_PT_SHIFT; writel(value, ioaddr + XGMAC_Qx_TX_FLOW_CTRL(i)); } } } static void dwxgmac2_pmt(struct mac_device_info *hw, unsigned long mode) { void __iomem *ioaddr = hw->pcsr; u32 val = 0x0; if (mode & WAKE_MAGIC) val |= XGMAC_PWRDWN | XGMAC_MGKPKTEN; if (mode & WAKE_UCAST) val |= XGMAC_PWRDWN | XGMAC_GLBLUCAST | XGMAC_RWKPKTEN; if (val) { u32 cfg = readl(ioaddr + XGMAC_RX_CONFIG); cfg |= XGMAC_CONFIG_RE; writel(cfg, ioaddr + XGMAC_RX_CONFIG); } writel(val, ioaddr + XGMAC_PMT); } static void dwxgmac2_set_umac_addr(struct mac_device_info *hw, unsigned char *addr, unsigned int reg_n) { void __iomem *ioaddr = hw->pcsr; u32 value; value = (addr[5] << 8) | addr[4]; writel(value | XGMAC_AE, ioaddr + XGMAC_ADDRx_HIGH(reg_n)); value = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; writel(value, ioaddr + XGMAC_ADDRx_LOW(reg_n)); } static void dwxgmac2_get_umac_addr(struct mac_device_info *hw, unsigned char *addr, unsigned int reg_n) { void __iomem *ioaddr = hw->pcsr; u32 hi_addr, lo_addr; /* Read the MAC address from the hardware */ hi_addr = readl(ioaddr + XGMAC_ADDRx_HIGH(reg_n)); lo_addr = readl(ioaddr + XGMAC_ADDRx_LOW(reg_n)); /* Extract the MAC address from the high and low words */ addr[0] = lo_addr & 0xff; addr[1] = (lo_addr >> 8) & 0xff; addr[2] = (lo_addr >> 16) & 0xff; addr[3] = (lo_addr >> 24) & 0xff; addr[4] = hi_addr & 0xff; addr[5] = (hi_addr >> 8) & 0xff; } static void dwxgmac2_set_mchash(void __iomem *ioaddr, u32 *mcfilterbits, int mcbitslog2) { int numhashregs, regs; switch (mcbitslog2) { case 6: numhashregs = 2; break; case 7: numhashregs = 4; break; case 8: numhashregs = 8; break; default: return; } for (regs = 0; regs < numhashregs; regs++) writel(mcfilterbits[regs], ioaddr + XGMAC_HASH_TABLE(regs)); } static void dwxgmac2_set_filter(struct mac_device_info *hw, struct net_device *dev) { void __iomem *ioaddr = (void __iomem *)dev->base_addr; u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); int mcbitslog2 = hw->mcast_bits_log2; u32 mc_filter[8]; int i; value &= ~(XGMAC_FILTER_PR | XGMAC_FILTER_HMC | XGMAC_FILTER_PM); value |= XGMAC_FILTER_HPF; memset(mc_filter, 0, sizeof(mc_filter)); if (dev->flags & IFF_PROMISC) { value |= XGMAC_FILTER_PR; value |= XGMAC_FILTER_PCF; } else if ((dev->flags & IFF_ALLMULTI) || (netdev_mc_count(dev) > hw->multicast_filter_bins)) { value |= XGMAC_FILTER_PM; for (i = 0; i < XGMAC_MAX_HASH_TABLE; i++) writel(~0x0, ioaddr + XGMAC_HASH_TABLE(i)); } else if (!netdev_mc_empty(dev)) { struct netdev_hw_addr *ha; value |= XGMAC_FILTER_HMC; netdev_for_each_mc_addr(ha, dev) { int nr = (bitrev32(~crc32_le(~0, ha->addr, 6)) >> (32 - mcbitslog2)); mc_filter[nr >> 5] |= (1 << (nr & 0x1F)); } } dwxgmac2_set_mchash(ioaddr, mc_filter, mcbitslog2); /* Handle multiple unicast addresses */ if (netdev_uc_count(dev) > hw->unicast_filter_entries) { value |= XGMAC_FILTER_PR; } else { struct netdev_hw_addr *ha; int reg = 1; netdev_for_each_uc_addr(ha, dev) { dwxgmac2_set_umac_addr(hw, ha->addr, reg); reg++; } for ( ; reg < XGMAC_ADDR_MAX; reg++) { writel(0, ioaddr + XGMAC_ADDRx_HIGH(reg)); writel(0, ioaddr + XGMAC_ADDRx_LOW(reg)); } } writel(value, ioaddr + XGMAC_PACKET_FILTER); } static void dwxgmac2_set_mac_loopback(void __iomem *ioaddr, bool enable) { u32 value = readl(ioaddr + XGMAC_RX_CONFIG); if (enable) value |= XGMAC_CONFIG_LM; else value &= ~XGMAC_CONFIG_LM; writel(value, ioaddr + XGMAC_RX_CONFIG); } const struct stmmac_ops dwxgmac210_ops = { .core_init = dwxgmac2_core_init, .set_mac = dwxgmac2_set_mac, .rx_ipc = dwxgmac2_rx_ipc, .rx_queue_enable = dwxgmac2_rx_queue_enable, .rx_queue_prio = dwxgmac2_rx_queue_prio, .tx_queue_prio = NULL, .rx_queue_routing = NULL, .prog_mtl_rx_algorithms = dwxgmac2_prog_mtl_rx_algorithms, .prog_mtl_tx_algorithms = dwxgmac2_prog_mtl_tx_algorithms, .set_mtl_tx_queue_weight = NULL, .map_mtl_to_dma = dwxgmac2_map_mtl_to_dma, .config_cbs = dwxgmac2_config_cbs, .dump_regs = NULL, .host_irq_status = dwxgmac2_host_irq_status, .host_mtl_irq_status = dwxgmac2_host_mtl_irq_status, .flow_ctrl = dwxgmac2_flow_ctrl, .pmt = dwxgmac2_pmt, .set_umac_addr = dwxgmac2_set_umac_addr, .get_umac_addr = dwxgmac2_get_umac_addr, .set_eee_mode = NULL, .reset_eee_mode = NULL, .set_eee_timer = NULL, .set_eee_pls = NULL, .pcs_ctrl_ane = NULL, .pcs_rane = NULL, .pcs_get_adv_lp = NULL, .debug = NULL, .set_filter = dwxgmac2_set_filter, .set_mac_loopback = dwxgmac2_set_mac_loopback, }; int dwxgmac2_setup(struct stmmac_priv *priv) { struct mac_device_info *mac = priv->hw; dev_info(priv->device, "\tXGMAC2\n"); priv->dev->priv_flags |= IFF_UNICAST_FLT; mac->pcsr = priv->ioaddr; mac->multicast_filter_bins = priv->plat->multicast_filter_bins; mac->unicast_filter_entries = priv->plat->unicast_filter_entries; mac->mcast_bits_log2 = 0; if (mac->multicast_filter_bins) mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins); mac->link.duplex = 0; mac->link.speed10 = XGMAC_CONFIG_SS_10_MII; mac->link.speed100 = XGMAC_CONFIG_SS_100_MII; mac->link.speed1000 = XGMAC_CONFIG_SS_1000_GMII; mac->link.speed2500 = XGMAC_CONFIG_SS_2500_GMII; mac->link.xgmii.speed2500 = XGMAC_CONFIG_SS_2500; mac->link.xgmii.speed5000 = XGMAC_CONFIG_SS_5000; mac->link.xgmii.speed10000 = XGMAC_CONFIG_SS_10000; mac->link.speed_mask = XGMAC_CONFIG_SS_MASK; mac->mii.addr = XGMAC_MDIO_ADDR; mac->mii.data = XGMAC_MDIO_DATA; mac->mii.addr_shift = 16; mac->mii.addr_mask = GENMASK(20, 16); mac->mii.reg_shift = 0; mac->mii.reg_mask = GENMASK(15, 0); mac->mii.clk_csr_shift = 19; mac->mii.clk_csr_mask = GENMASK(21, 19); return 0; }