root/drivers/net/ethernet/sunplus/spl2sw_int.c
// SPDX-License-Identifier: GPL-2.0
/* Copyright Sunplus Technology Co., Ltd.
 *       All rights reserved.
 */

#include <linux/platform_device.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/bitfield.h>
#include <linux/spinlock.h>
#include <linux/of_mdio.h>

#include "spl2sw_register.h"
#include "spl2sw_define.h"
#include "spl2sw_int.h"

int spl2sw_rx_poll(struct napi_struct *napi, int budget)
{
        struct spl2sw_common *comm = container_of(napi, struct spl2sw_common, rx_napi);
        struct spl2sw_mac_desc *desc, *h_desc;
        struct net_device_stats *stats;
        struct sk_buff *skb, *new_skb;
        struct spl2sw_skb_info *sinfo;
        int budget_left = budget;
        unsigned long flags;
        u32 rx_pos, pkg_len;
        u32 num, rx_count;
        s32 queue;
        u32 mask;
        int port;
        u32 cmd;
        u32 len;

        /* Process high-priority queue and then low-priority queue. */
        for (queue = 0; queue < RX_DESC_QUEUE_NUM; queue++) {
                rx_pos = comm->rx_pos[queue];
                rx_count = comm->rx_desc_num[queue];

                for (num = 0; num < rx_count && budget_left; num++) {
                        sinfo = comm->rx_skb_info[queue] + rx_pos;
                        desc = comm->rx_desc[queue] + rx_pos;
                        cmd = desc->cmd1;

                        if (cmd & RXD_OWN)
                                break;

                        port = FIELD_GET(RXD_PKT_SP, cmd);
                        if (port < MAX_NETDEV_NUM && comm->ndev[port])
                                stats = &comm->ndev[port]->stats;
                        else
                                goto spl2sw_rx_poll_rec_err;

                        pkg_len = FIELD_GET(RXD_PKT_LEN, cmd);
                        if (unlikely((cmd & RXD_ERR_CODE) || pkg_len < ETH_ZLEN + 4)) {
                                stats->rx_length_errors++;
                                stats->rx_dropped++;
                                goto spl2sw_rx_poll_rec_err;
                        }

                        dma_unmap_single(&comm->pdev->dev, sinfo->mapping,
                                         comm->rx_desc_buff_size, DMA_FROM_DEVICE);

                        skb = sinfo->skb;
                        skb_put(skb, pkg_len - 4); /* Minus FCS */
                        skb->ip_summed = CHECKSUM_NONE;
                        skb->protocol = eth_type_trans(skb, comm->ndev[port]);
                        len = skb->len;
                        netif_receive_skb(skb);

                        stats->rx_packets++;
                        stats->rx_bytes += len;

                        /* Allocate a new skb for receiving. */
                        new_skb = netdev_alloc_skb(NULL, comm->rx_desc_buff_size);
                        if (unlikely(!new_skb)) {
                                desc->cmd2 = (rx_pos == comm->rx_desc_num[queue] - 1) ?
                                             RXD_EOR : 0;
                                sinfo->skb = NULL;
                                sinfo->mapping = 0;
                                desc->addr1 = 0;
                                goto spl2sw_rx_poll_alloc_err;
                        }

                        sinfo->mapping = dma_map_single(&comm->pdev->dev, new_skb->data,
                                                        comm->rx_desc_buff_size,
                                                        DMA_FROM_DEVICE);
                        if (dma_mapping_error(&comm->pdev->dev, sinfo->mapping)) {
                                dev_kfree_skb_irq(new_skb);
                                desc->cmd2 = (rx_pos == comm->rx_desc_num[queue] - 1) ?
                                             RXD_EOR : 0;
                                sinfo->skb = NULL;
                                sinfo->mapping = 0;
                                desc->addr1 = 0;
                                goto spl2sw_rx_poll_alloc_err;
                        }

                        sinfo->skb = new_skb;
                        desc->addr1 = sinfo->mapping;

spl2sw_rx_poll_rec_err:
                        desc->cmd2 = (rx_pos == comm->rx_desc_num[queue] - 1) ?
                                     RXD_EOR | comm->rx_desc_buff_size :
                                     comm->rx_desc_buff_size;

                        wmb();  /* Set RXD_OWN after other fields are effective. */
                        desc->cmd1 = RXD_OWN;

spl2sw_rx_poll_alloc_err:
                        /* Move rx_pos to next position */
                        rx_pos = ((rx_pos + 1) == comm->rx_desc_num[queue]) ? 0 : rx_pos + 1;

                        budget_left--;

                        /* If there are packets in high-priority queue,
                         * stop processing low-priority queue.
                         */
                        if (queue == 1 && !(h_desc->cmd1 & RXD_OWN))
                                break;
                }

                comm->rx_pos[queue] = rx_pos;

                /* Save pointer to last rx descriptor of high-priority queue. */
                if (queue == 0)
                        h_desc = comm->rx_desc[queue] + rx_pos;
        }

        spin_lock_irqsave(&comm->int_mask_lock, flags);
        mask = readl(comm->l2sw_reg_base + L2SW_SW_INT_MASK_0);
        mask &= ~MAC_INT_RX;
        writel(mask, comm->l2sw_reg_base + L2SW_SW_INT_MASK_0);
        spin_unlock_irqrestore(&comm->int_mask_lock, flags);

        napi_complete(napi);
        return budget - budget_left;
}

int spl2sw_tx_poll(struct napi_struct *napi, int budget)
{
        struct spl2sw_common *comm = container_of(napi, struct spl2sw_common, tx_napi);
        struct spl2sw_skb_info *skbinfo;
        struct net_device_stats *stats;
        int budget_left = budget;
        unsigned long flags;
        u32 tx_done_pos;
        u32 mask;
        u32 cmd;
        int i;

        spin_lock(&comm->tx_lock);

        tx_done_pos = comm->tx_done_pos;
        while (((tx_done_pos != comm->tx_pos) || (comm->tx_desc_full == 1)) && budget_left) {
                cmd = comm->tx_desc[tx_done_pos].cmd1;
                if (cmd & TXD_OWN)
                        break;

                skbinfo = &comm->tx_temp_skb_info[tx_done_pos];
                if (unlikely(!skbinfo->skb))
                        goto spl2sw_tx_poll_next;

                i = ffs(FIELD_GET(TXD_VLAN, cmd)) - 1;
                if (i < MAX_NETDEV_NUM && comm->ndev[i])
                        stats = &comm->ndev[i]->stats;
                else
                        goto spl2sw_tx_poll_unmap;

                if (unlikely(cmd & (TXD_ERR_CODE))) {
                        stats->tx_errors++;
                } else {
                        stats->tx_packets++;
                        stats->tx_bytes += skbinfo->len;
                }

spl2sw_tx_poll_unmap:
                dma_unmap_single(&comm->pdev->dev, skbinfo->mapping, skbinfo->len,
                                 DMA_TO_DEVICE);
                skbinfo->mapping = 0;
                dev_kfree_skb_irq(skbinfo->skb);
                skbinfo->skb = NULL;

spl2sw_tx_poll_next:
                /* Move tx_done_pos to next position */
                tx_done_pos = ((tx_done_pos + 1) == TX_DESC_NUM) ? 0 : tx_done_pos + 1;

                if (comm->tx_desc_full == 1)
                        comm->tx_desc_full = 0;

                budget_left--;
        }

        comm->tx_done_pos = tx_done_pos;
        if (!comm->tx_desc_full)
                for (i = 0; i < MAX_NETDEV_NUM; i++)
                        if (comm->ndev[i])
                                if (netif_queue_stopped(comm->ndev[i]))
                                        netif_wake_queue(comm->ndev[i]);

        spin_unlock(&comm->tx_lock);

        spin_lock_irqsave(&comm->int_mask_lock, flags);
        mask = readl(comm->l2sw_reg_base + L2SW_SW_INT_MASK_0);
        mask &= ~MAC_INT_TX;
        writel(mask, comm->l2sw_reg_base + L2SW_SW_INT_MASK_0);
        spin_unlock_irqrestore(&comm->int_mask_lock, flags);

        napi_complete(napi);
        return budget - budget_left;
}

irqreturn_t spl2sw_ethernet_interrupt(int irq, void *dev_id)
{
        struct spl2sw_common *comm = (struct spl2sw_common *)dev_id;
        u32 status;
        u32 mask;
        int i;

        status = readl(comm->l2sw_reg_base + L2SW_SW_INT_STATUS_0);
        if (unlikely(!status)) {
                dev_dbg(&comm->pdev->dev, "Interrupt status is null!\n");
                goto spl2sw_ethernet_int_out;
        }
        writel(status, comm->l2sw_reg_base + L2SW_SW_INT_STATUS_0);

        if (status & MAC_INT_RX) {
                /* Disable RX interrupts. */
                spin_lock(&comm->int_mask_lock);
                mask = readl(comm->l2sw_reg_base + L2SW_SW_INT_MASK_0);
                mask |= MAC_INT_RX;
                writel(mask, comm->l2sw_reg_base + L2SW_SW_INT_MASK_0);
                spin_unlock(&comm->int_mask_lock);

                if (unlikely(status & MAC_INT_RX_DES_ERR)) {
                        for (i = 0; i < MAX_NETDEV_NUM; i++)
                                if (comm->ndev[i]) {
                                        comm->ndev[i]->stats.rx_fifo_errors++;
                                        break;
                                }
                        dev_dbg(&comm->pdev->dev, "Illegal RX Descriptor!\n");
                }

                napi_schedule(&comm->rx_napi);
        }

        if (status & MAC_INT_TX) {
                /* Disable TX interrupts. */
                spin_lock(&comm->int_mask_lock);
                mask = readl(comm->l2sw_reg_base + L2SW_SW_INT_MASK_0);
                mask |= MAC_INT_TX;
                writel(mask, comm->l2sw_reg_base + L2SW_SW_INT_MASK_0);
                spin_unlock(&comm->int_mask_lock);

                if (unlikely(status & MAC_INT_TX_DES_ERR)) {
                        for (i = 0; i < MAX_NETDEV_NUM; i++)
                                if (comm->ndev[i]) {
                                        comm->ndev[i]->stats.tx_fifo_errors++;
                                        break;
                                }
                        dev_dbg(&comm->pdev->dev, "Illegal TX Descriptor Error\n");

                        spin_lock(&comm->int_mask_lock);
                        mask = readl(comm->l2sw_reg_base + L2SW_SW_INT_MASK_0);
                        mask &= ~MAC_INT_TX;
                        writel(mask, comm->l2sw_reg_base + L2SW_SW_INT_MASK_0);
                        spin_unlock(&comm->int_mask_lock);
                } else {
                        napi_schedule(&comm->tx_napi);
                }
        }

spl2sw_ethernet_int_out:
        return IRQ_HANDLED;
}