[PATCH net-next v3 2/5] net: spacemit: Add K1 Ethernet MAC
Simon Horman
horms at kernel.org
Thu Jul 3 00:19:49 PDT 2025
On Wed, Jul 02, 2025 at 02:01:41PM +0800, Vivian Wang wrote:
...
> diff --git a/drivers/net/ethernet/spacemit/k1_emac.c b/drivers/net/ethernet/spacemit/k1_emac.c
...
> +static int emac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> +{
> + struct emac_priv *priv = netdev_priv(ndev);
> + int nfrags = skb_shinfo(skb)->nr_frags;
> + struct device *dev = &priv->pdev->dev;
> +
> + if (unlikely(emac_tx_avail(priv) < nfrags + 1)) {
> + if (!netif_queue_stopped(ndev)) {
> + netif_stop_queue(ndev);
> + dev_err_ratelimited(dev, "TX ring full, stop TX queue\n");
> + }
> + return NETDEV_TX_BUSY;
> + }
> +
> + emac_tx_mem_map(priv, skb);
> +
> + ndev->stats.tx_packets++;
> + ndev->stats.tx_bytes += skb->len;
> +
> + /* Make sure there is space in the ring for the next TX. */
> + if (unlikely(emac_tx_avail(priv) <= MAX_SKB_FRAGS + 2))
> + netif_stop_queue(ndev);
> +
> + return NETDEV_TX_OK;
> +}
> +
> +static u32 emac_tx_read_stat_cnt(struct emac_priv *priv, u8 cnt)
> +{
> + u32 val, tmp;
> + int ret;
> +
> + val = 0x8000 | cnt;
> + emac_wr(priv, MAC_TX_STATCTR_CONTROL, val);
> + val = emac_rd(priv, MAC_TX_STATCTR_CONTROL);
> +
> + ret = readl_poll_timeout_atomic(priv->iobase + MAC_TX_STATCTR_CONTROL,
> + val, !(val & 0x8000), 100, 10000);
> +
> + if (ret) {
> + netdev_err(priv->ndev, "read TX stat timeout\n");
> + return ret;
> + }
> +
> + tmp = emac_rd(priv, MAC_TX_STATCTR_DATA_HIGH);
> + val = tmp << 16;
> + tmp = emac_rd(priv, MAC_TX_STATCTR_DATA_LOW);
> + val |= tmp;
> +
> + return val;
> +}
> +
> +static u32 emac_rx_read_stat_cnt(struct emac_priv *priv, u8 cnt)
> +{
> + u32 val, tmp;
> + int ret;
> +
> + val = 0x8000 | cnt;
> + emac_wr(priv, MAC_RX_STATCTR_CONTROL, val);
> + val = emac_rd(priv, MAC_RX_STATCTR_CONTROL);
> +
> + ret = readl_poll_timeout_atomic(priv->iobase + MAC_RX_STATCTR_CONTROL,
> + val, !(val & 0x8000), 100, 10000);
> +
> + if (ret) {
> + netdev_err(priv->ndev, "read RX stat timeout\n");
> + return ret;
> + }
> +
> + tmp = emac_rd(priv, MAC_RX_STATCTR_DATA_HIGH);
> + val = tmp << 16;
> + tmp = emac_rd(priv, MAC_RX_STATCTR_DATA_LOW);
> + val |= tmp;
> +
> + return val;
> +}
> +
> +static int emac_set_mac_address(struct net_device *ndev, void *addr)
> +{
> + struct emac_priv *priv = netdev_priv(ndev);
> + int ret = eth_mac_addr(ndev, addr);
> +
> + if (ret)
> + return ret;
> +
> + /* If running, set now; if not running it will be set in emac_up. */
> + if (netif_running(ndev))
> + emac_set_mac_addr(priv, ndev->dev_addr);
> +
> + return 0;
> +}
> +
> +static void emac_mac_multicast_filter_clear(struct emac_priv *priv)
> +{
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE1, 0x0);
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE2, 0x0);
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE3, 0x0);
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE4, 0x0);
> +}
> +
> +/* Configure Multicast and Promiscuous modes */
> +static void emac_rx_mode_set(struct net_device *ndev)
> +{
> + struct emac_priv *priv = netdev_priv(ndev);
> + u32 crc32, bit, reg, hash, val;
> + struct netdev_hw_addr *ha;
> + u32 mc_filter[4] = { 0 };
> +
> + val = emac_rd(priv, MAC_ADDRESS_CONTROL);
> +
> + val &= ~MREGBIT_PROMISCUOUS_MODE;
> +
> + if (ndev->flags & IFF_PROMISC) {
> + /* Enable promisc mode */
> + val |= MREGBIT_PROMISCUOUS_MODE;
> + } else if ((ndev->flags & IFF_ALLMULTI) ||
> + (netdev_mc_count(ndev) > HASH_TABLE_SIZE)) {
> + /* Accept all multicast frames by setting every bit */
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE1, 0xffff);
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE2, 0xffff);
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE3, 0xffff);
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE4, 0xffff);
> + } else if (!netdev_mc_empty(ndev)) {
> + emac_mac_multicast_filter_clear(priv);
> + netdev_for_each_mc_addr(ha, ndev) {
> + /* Calculate the CRC of the MAC address */
> + crc32 = ether_crc(ETH_ALEN, ha->addr);
> +
> + /*
> + * The hash table is an array of 4 16-bit registers. It
> + * is treated like an array of 64 bits (bits[hash]). Use
> + * the upper 6 bits of the above CRC as the hash value.
> + */
> + hash = (crc32 >> 26) & 0x3F;
> + reg = hash / 16;
> + bit = hash % 16;
> + mc_filter[reg] |= BIT(bit);
> + }
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE1, mc_filter[0]);
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE2, mc_filter[1]);
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE3, mc_filter[2]);
> + emac_wr(priv, MAC_MULTICAST_HASH_TABLE4, mc_filter[3]);
> + }
> +
> + emac_wr(priv, MAC_ADDRESS_CONTROL, val);
> +}
> +
> +static int emac_change_mtu(struct net_device *ndev, int mtu)
> +{
> + struct emac_priv *priv = netdev_priv(ndev);
> + u32 frame_len;
> +
> + if (netif_running(ndev)) {
> + netdev_err(ndev, "must be stopped to change MTU\n");
> + return -EBUSY;
> + }
> +
> + frame_len = mtu + ETH_HLEN + ETH_FCS_LEN;
> +
> + if (frame_len <= EMAC_DEFAULT_BUFSIZE)
> + priv->dma_buf_sz = EMAC_DEFAULT_BUFSIZE;
> + else if (frame_len <= EMAC_RX_BUF_2K)
> + priv->dma_buf_sz = EMAC_RX_BUF_2K;
> + else
> + priv->dma_buf_sz = EMAC_RX_BUF_4K;
> +
> + ndev->mtu = mtu;
> +
> + return 0;
> +}
> +
> +static void emac_tx_timeout(struct net_device *ndev, unsigned int txqueue)
> +{
> + struct emac_priv *priv = netdev_priv(ndev);
> +
> + schedule_work(&priv->tx_timeout_task);
> +}
> +
> +static int emac_mii_read(struct mii_bus *bus, int phy_addr, int regnum)
> +{
> + struct emac_priv *priv = bus->priv;
> + u32 cmd = 0, val;
> + int ret;
> +
> + cmd |= phy_addr & 0x1F;
> + cmd |= (regnum & 0x1F) << 5;
> + cmd |= MREGBIT_START_MDIO_TRANS | MREGBIT_MDIO_READ_WRITE;
> +
> + emac_wr(priv, MAC_MDIO_DATA, 0x0);
> + emac_wr(priv, MAC_MDIO_CONTROL, cmd);
> +
> + ret = readl_poll_timeout(priv->iobase + MAC_MDIO_CONTROL, val,
> + !((val >> 15) & 0x1), 100, 10000);
> +
> + if (ret)
> + return ret;
> +
> + val = emac_rd(priv, MAC_MDIO_DATA);
> + return val;
> +}
> +
> +static int emac_mii_write(struct mii_bus *bus, int phy_addr, int regnum,
> + u16 value)
> +{
> + struct emac_priv *priv = bus->priv;
> + u32 cmd = 0, val;
> + int ret;
> +
> + emac_wr(priv, MAC_MDIO_DATA, value);
> +
> + cmd |= phy_addr & 0x1F;
> + cmd |= (regnum & 0x1F) << 5;
> + cmd |= MREGBIT_START_MDIO_TRANS;
> +
> + emac_wr(priv, MAC_MDIO_CONTROL, cmd);
> +
> + ret = readl_poll_timeout(priv->iobase + MAC_MDIO_CONTROL, val,
> + !((val >> 15) & 0x1), 100, 10000);
> +
> + return ret;
> +}
> +
> +static int emac_mdio_init(struct emac_priv *priv)
> +{
> + struct device *dev = &priv->pdev->dev;
> + struct device_node *mii_np;
> + struct mii_bus *mii;
> + int ret;
> +
> + mii = devm_mdiobus_alloc(dev);
> + if (!mii)
> + return -ENOMEM;
> +
> + mii->priv = priv;
> + mii->name = "k1_emac_mii";
> + mii->read = emac_mii_read;
> + mii->write = emac_mii_write;
> + mii->parent = dev;
> + mii->phy_mask = 0xffffffff;
> + snprintf(mii->id, MII_BUS_ID_SIZE, "%s", priv->pdev->name);
> +
> + mii_np = of_get_available_child_by_name(dev->of_node, "mdio-bus");
> +
> + ret = devm_of_mdiobus_register(dev, mii, mii_np);
> + if (ret)
> + dev_err_probe(dev, ret, "Failed to register mdio bus\n");
> +
> + of_node_put(mii_np);
> + return ret;
> +}
> +
> +static void emac_get_strings(struct net_device *dev, u32 stringset, u8 *data)
> +{
> + int i;
> +
> + switch (stringset) {
> + case ETH_SS_STATS:
> + for (i = 0; i < ARRAY_SIZE(emac_ethtool_stats); i++) {
> + memcpy(data, emac_ethtool_stats[i].str,
> + ETH_GSTRING_LEN);
> + data += ETH_GSTRING_LEN;
> + }
> + break;
> + }
> +}
> +
> +static int emac_get_sset_count(struct net_device *dev, int sset)
> +{
> + switch (sset) {
> + case ETH_SS_STATS:
> + return ARRAY_SIZE(emac_ethtool_stats);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static void emac_stats_update(struct emac_priv *priv)
> +{
> + struct emac_hw_stats *hwstats = priv->hw_stats;
> + u32 *stats = (u32 *)hwstats;
> + int i;
> +
> + for (i = 0; i < EMAC_TX_STATS_NUM; i++)
> + stats[i] = emac_tx_read_stat_cnt(priv, i);
> +
> + for (i = 0; i < EMAC_RX_STATS_NUM; i++)
> + stats[i + EMAC_TX_STATS_NUM] = emac_rx_read_stat_cnt(priv, i);
> +}
> +
> +static void emac_get_ethtool_stats(struct net_device *dev,
> + struct ethtool_stats *stats, u64 *data)
> +{
> + struct emac_priv *priv = netdev_priv(dev);
> + struct emac_hw_stats *hwstats;
> + unsigned long flags;
> + u32 *data_src;
> + u64 *data_dst;
> + int i;
> +
> + hwstats = priv->hw_stats;
> +
> + if (netif_running(dev) && netif_device_present(dev)) {
> + if (spin_trylock_irqsave(&priv->stats_lock, flags)) {
> + emac_stats_update(priv);
> + spin_unlock_irqrestore(&priv->stats_lock, flags);
> + }
> + }
> +
> + data_dst = data;
> +
> + for (i = 0; i < ARRAY_SIZE(emac_ethtool_stats); i++) {
> + data_src = (u32 *)hwstats + emac_ethtool_stats[i].offset;
> + *data_dst++ = (u64)(*data_src);
> + }
> +}
> +
> +static int emac_ethtool_get_regs_len(struct net_device *dev)
> +{
> + return (EMAC_DMA_REG_CNT + EMAC_MAC_REG_CNT) * sizeof(u32);
> +}
> +
> +static void emac_ethtool_get_regs(struct net_device *dev,
> + struct ethtool_regs *regs, void *space)
> +{
> + struct emac_priv *priv = netdev_priv(dev);
> + u32 *reg_space = space;
> + int i;
> +
> + regs->version = 1;
> +
> + for (i = 0; i < EMAC_DMA_REG_CNT; i++)
> + reg_space[i] = emac_rd(priv, DMA_CONFIGURATION + i * 4);
> +
> + for (i = 0; i < EMAC_MAC_REG_CNT; i++)
> + reg_space[i + EMAC_DMA_REG_CNT] =
> + emac_rd(priv, MAC_GLOBAL_CONTROL + i * 4);
> +}
> +
> +static void emac_get_drvinfo(struct net_device *dev,
> + struct ethtool_drvinfo *info)
> +{
> + strscpy(info->driver, DRIVER_NAME, sizeof(info->driver));
> + info->n_stats = ARRAY_SIZE(emac_ethtool_stats);
> +}
> +
> +static void emac_tx_timeout_task(struct work_struct *work)
> +{
> + struct net_device *ndev;
> + struct emac_priv *priv;
> +
> + priv = container_of(work, struct emac_priv, tx_timeout_task);
> + ndev = priv->ndev;
> +
> + rtnl_lock();
> +
> + /* No need to reset if already down */
> + if (!netif_running(ndev)) {
> + rtnl_unlock();
> + return;
> + }
> +
> + netdev_err(ndev, "MAC reset due to TX timeout\n");
> +
> + netif_trans_update(ndev); /* prevent tx timeout */
> + dev_close(ndev);
> + dev_open(ndev, NULL);
> +
> + rtnl_unlock();
> +}
> +
> +static void emac_sw_init(struct emac_priv *priv)
> +{
> + priv->dma_buf_sz = EMAC_DEFAULT_BUFSIZE;
> +
> + priv->tx_ring.total_cnt = DEFAULT_TX_RING_NUM;
> + priv->rx_ring.total_cnt = DEFAULT_RX_RING_NUM;
> +
> + spin_lock_init(&priv->stats_lock);
> +
> + INIT_WORK(&priv->tx_timeout_task, emac_tx_timeout_task);
> +
> + priv->tx_coal_frames = EMAC_TX_FRAMES;
> + priv->tx_coal_timeout = EMAC_TX_COAL_TIMEOUT;
> +
> + timer_setup(&priv->txtimer, emac_tx_coal_timer, 0);
> +}
> +
...
> +static irqreturn_t emac_interrupt_handler(int irq, void *dev_id)
> +{
> + struct net_device *ndev = (struct net_device *)dev_id;
> + struct emac_priv *priv = netdev_priv(ndev);
> + bool should_schedule = false;
> + u32 status;
> + u32 clr = 0;
nit: Reverse xmas tree - longest line to shortest - for
these local variable declarations please.
Edward Cree's tool can be helpful here:
https://github.com/ecree-solarflare/xmastree/commits/master/
...
> +static const struct net_device_ops emac_netdev_ops = {
> + .ndo_open = emac_open,
> + .ndo_stop = emac_close,
> + .ndo_start_xmit = emac_start_xmit,
I think that of emac_start_xmit should return netdev_tx_t rather than int
to match the type of the .ndo_start_xmit member of this structure.
Flagged by Clang 20.1.7 [-Wincompatible-function-pointer-types-strict]
> + .ndo_set_mac_address = emac_set_mac_address,
> + .ndo_eth_ioctl = phy_do_ioctl_running,
> + .ndo_change_mtu = emac_change_mtu,
> + .ndo_tx_timeout = emac_tx_timeout,
> + .ndo_set_rx_mode = emac_rx_mode_set,
> +};
...
More information about the linux-riscv
mailing list