// SPDX-License-Identifier: GPL-2.0+ /* Microchip Sparx5 Switch driver * * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries. * * The Sparx5 Chip Register Model can be browsed at this location: * https://github.com/microchip-ung/sparx-5_reginfo */ #include #include #include #include #include #include #include "sparx5_main_regs.h" #include "sparx5_main.h" #include "sparx5_port.h" #define FDMA_XTR_CHANNEL 6 #define FDMA_INJ_CHANNEL 0 #define FDMA_XTR_BUFFER_SIZE 2048 #define FDMA_WEIGHT 4 static int sparx5_fdma_tx_dataptr_cb(struct fdma *fdma, int dcb, int db, u64 *dataptr) { *dataptr = fdma->dma + (sizeof(struct fdma_dcb) * fdma->n_dcbs) + ((dcb * fdma->n_dbs + db) * fdma->db_size); return 0; } static int sparx5_fdma_rx_dataptr_cb(struct fdma *fdma, int dcb, int db, u64 *dataptr) { struct sparx5 *sparx5 = fdma->priv; struct sparx5_rx *rx = &sparx5->rx; struct sk_buff *skb; skb = __netdev_alloc_skb(rx->ndev, fdma->db_size, GFP_ATOMIC); if (unlikely(!skb)) return -ENOMEM; *dataptr = virt_to_phys(skb->data); rx->skb[dcb][db] = skb; return 0; } static void sparx5_fdma_rx_activate(struct sparx5 *sparx5, struct sparx5_rx *rx) { struct fdma *fdma = &rx->fdma; /* Write the buffer address in the LLP and LLP1 regs */ spx5_wr(((u64)fdma->dma) & GENMASK(31, 0), sparx5, FDMA_DCB_LLP(fdma->channel_id)); spx5_wr(((u64)fdma->dma) >> 32, sparx5, FDMA_DCB_LLP1(fdma->channel_id)); /* Set the number of RX DBs to be used, and DB end-of-frame interrupt */ spx5_wr(FDMA_CH_CFG_CH_DCB_DB_CNT_SET(fdma->n_dbs) | FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY_SET(1) | FDMA_CH_CFG_CH_INJ_PORT_SET(XTR_QUEUE), sparx5, FDMA_CH_CFG(fdma->channel_id)); /* Set the RX Watermark to max */ spx5_rmw(FDMA_XTR_CFG_XTR_FIFO_WM_SET(31), FDMA_XTR_CFG_XTR_FIFO_WM, sparx5, FDMA_XTR_CFG); /* Start RX fdma */ spx5_rmw(FDMA_PORT_CTRL_XTR_STOP_SET(0), FDMA_PORT_CTRL_XTR_STOP, sparx5, FDMA_PORT_CTRL(0)); /* Enable RX channel DB interrupt */ spx5_rmw(BIT(fdma->channel_id), BIT(fdma->channel_id) & FDMA_INTR_DB_ENA_INTR_DB_ENA, sparx5, FDMA_INTR_DB_ENA); /* Activate the RX channel */ spx5_wr(BIT(fdma->channel_id), sparx5, FDMA_CH_ACTIVATE); } static void sparx5_fdma_rx_deactivate(struct sparx5 *sparx5, struct sparx5_rx *rx) { struct fdma *fdma = &rx->fdma; /* Deactivate the RX channel */ spx5_rmw(0, BIT(fdma->channel_id) & FDMA_CH_ACTIVATE_CH_ACTIVATE, sparx5, FDMA_CH_ACTIVATE); /* Disable RX channel DB interrupt */ spx5_rmw(0, BIT(fdma->channel_id) & FDMA_INTR_DB_ENA_INTR_DB_ENA, sparx5, FDMA_INTR_DB_ENA); /* Stop RX fdma */ spx5_rmw(FDMA_PORT_CTRL_XTR_STOP_SET(1), FDMA_PORT_CTRL_XTR_STOP, sparx5, FDMA_PORT_CTRL(0)); } static void sparx5_fdma_tx_activate(struct sparx5 *sparx5, struct sparx5_tx *tx) { struct fdma *fdma = &tx->fdma; /* Write the buffer address in the LLP and LLP1 regs */ spx5_wr(((u64)fdma->dma) & GENMASK(31, 0), sparx5, FDMA_DCB_LLP(fdma->channel_id)); spx5_wr(((u64)fdma->dma) >> 32, sparx5, FDMA_DCB_LLP1(fdma->channel_id)); /* Set the number of TX DBs to be used, and DB end-of-frame interrupt */ spx5_wr(FDMA_CH_CFG_CH_DCB_DB_CNT_SET(fdma->n_dbs) | FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY_SET(1) | FDMA_CH_CFG_CH_INJ_PORT_SET(INJ_QUEUE), sparx5, FDMA_CH_CFG(fdma->channel_id)); /* Start TX fdma */ spx5_rmw(FDMA_PORT_CTRL_INJ_STOP_SET(0), FDMA_PORT_CTRL_INJ_STOP, sparx5, FDMA_PORT_CTRL(0)); /* Activate the channel */ spx5_wr(BIT(fdma->channel_id), sparx5, FDMA_CH_ACTIVATE); } static void sparx5_fdma_tx_deactivate(struct sparx5 *sparx5, struct sparx5_tx *tx) { /* Disable the channel */ spx5_rmw(0, BIT(tx->fdma.channel_id) & FDMA_CH_ACTIVATE_CH_ACTIVATE, sparx5, FDMA_CH_ACTIVATE); } static void sparx5_fdma_reload(struct sparx5 *sparx5, struct fdma *fdma) { /* Reload the RX channel */ spx5_wr(BIT(fdma->channel_id), sparx5, FDMA_CH_RELOAD); } static bool sparx5_fdma_rx_get_frame(struct sparx5 *sparx5, struct sparx5_rx *rx) { struct fdma *fdma = &rx->fdma; struct sparx5_port *port; struct fdma_db *db_hw; struct frame_info fi; struct sk_buff *skb; /* Check if the DCB is done */ db_hw = fdma_db_next_get(fdma); if (unlikely(!fdma_db_is_done(db_hw))) return false; skb = rx->skb[fdma->dcb_index][fdma->db_index]; skb_put(skb, fdma_db_len_get(db_hw)); /* Now do the normal processing of the skb */ sparx5_ifh_parse(sparx5, (u32 *)skb->data, &fi); /* Map to port netdev */ port = fi.src_port < sparx5->data->consts->n_ports ? sparx5->ports[fi.src_port] : NULL; if (!port || !port->ndev) { dev_err(sparx5->dev, "Data on inactive port %d\n", fi.src_port); sparx5_xtr_flush(sparx5, XTR_QUEUE); return false; } skb->dev = port->ndev; skb_pull(skb, IFH_LEN * sizeof(u32)); if (likely(!(skb->dev->features & NETIF_F_RXFCS))) skb_trim(skb, skb->len - ETH_FCS_LEN); sparx5_ptp_rxtstamp(sparx5, skb, fi.timestamp); skb->protocol = eth_type_trans(skb, skb->dev); /* Everything we see on an interface that is in the HW bridge * has already been forwarded */ if (test_bit(port->portno, sparx5->bridge_mask)) skb->offload_fwd_mark = 1; skb->dev->stats.rx_bytes += skb->len; skb->dev->stats.rx_packets++; rx->packets++; netif_receive_skb(skb); return true; } static int sparx5_fdma_napi_callback(struct napi_struct *napi, int weight) { struct sparx5_rx *rx = container_of(napi, struct sparx5_rx, napi); struct sparx5 *sparx5 = container_of(rx, struct sparx5, rx); struct fdma *fdma = &rx->fdma; int counter = 0; while (counter < weight && sparx5_fdma_rx_get_frame(sparx5, rx)) { fdma_db_advance(fdma); counter++; /* Check if the DCB can be reused */ if (fdma_dcb_is_reusable(fdma)) continue; fdma_dcb_add(fdma, fdma->dcb_index, FDMA_DCB_INFO_DATAL(fdma->db_size), FDMA_DCB_STATUS_INTR); fdma_db_reset(fdma); fdma_dcb_advance(fdma); } if (counter < weight) { napi_complete_done(&rx->napi, counter); spx5_rmw(BIT(fdma->channel_id), BIT(fdma->channel_id) & FDMA_INTR_DB_ENA_INTR_DB_ENA, sparx5, FDMA_INTR_DB_ENA); } if (counter) sparx5_fdma_reload(sparx5, fdma); return counter; } int sparx5_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb) { struct sparx5_tx *tx = &sparx5->tx; struct fdma *fdma = &tx->fdma; static bool first_time = true; void *virt_addr; fdma_dcb_advance(fdma); if (!fdma_db_is_done(fdma_db_get(fdma, fdma->dcb_index, 0))) return -EINVAL; /* Get the virtual address of the dataptr for the next DB */ virt_addr = ((u8 *)fdma->dcbs + (sizeof(struct fdma_dcb) * fdma->n_dcbs) + ((fdma->dcb_index * fdma->n_dbs) * fdma->db_size)); memcpy(virt_addr, ifh, IFH_LEN * 4); memcpy(virt_addr + IFH_LEN * 4, skb->data, skb->len); fdma_dcb_add(fdma, fdma->dcb_index, 0, FDMA_DCB_STATUS_SOF | FDMA_DCB_STATUS_EOF | FDMA_DCB_STATUS_BLOCKO(0) | FDMA_DCB_STATUS_BLOCKL(skb->len + IFH_LEN * 4 + 4)); if (first_time) { sparx5_fdma_tx_activate(sparx5, tx); first_time = false; } else { sparx5_fdma_reload(sparx5, fdma); } return NETDEV_TX_OK; } static int sparx5_fdma_rx_alloc(struct sparx5 *sparx5) { struct sparx5_rx *rx = &sparx5->rx; struct fdma *fdma = &rx->fdma; int err; err = fdma_alloc_phys(fdma); if (err) return err; fdma_dcbs_init(fdma, FDMA_DCB_INFO_DATAL(fdma->db_size), FDMA_DCB_STATUS_INTR); netif_napi_add_weight(rx->ndev, &rx->napi, sparx5_fdma_napi_callback, FDMA_WEIGHT); napi_enable(&rx->napi); sparx5_fdma_rx_activate(sparx5, rx); return 0; } static int sparx5_fdma_tx_alloc(struct sparx5 *sparx5) { struct sparx5_tx *tx = &sparx5->tx; struct fdma *fdma = &tx->fdma; int err; err = fdma_alloc_phys(fdma); if (err) return err; fdma_dcbs_init(fdma, FDMA_DCB_INFO_DATAL(fdma->db_size), FDMA_DCB_STATUS_DONE); return 0; } static void sparx5_fdma_rx_init(struct sparx5 *sparx5, struct sparx5_rx *rx, int channel) { struct fdma *fdma = &rx->fdma; int idx; fdma->channel_id = channel; fdma->n_dcbs = FDMA_DCB_MAX; fdma->n_dbs = FDMA_RX_DCB_MAX_DBS; fdma->priv = sparx5; fdma->db_size = ALIGN(FDMA_XTR_BUFFER_SIZE, PAGE_SIZE); fdma->size = fdma_get_size(&sparx5->rx.fdma); fdma->ops.dataptr_cb = &sparx5_fdma_rx_dataptr_cb; fdma->ops.nextptr_cb = &fdma_nextptr_cb; /* Fetch a netdev for SKB and NAPI use, any will do */ for (idx = 0; idx < sparx5->data->consts->n_ports; ++idx) { struct sparx5_port *port = sparx5->ports[idx]; if (port && port->ndev) { rx->ndev = port->ndev; break; } } } static void sparx5_fdma_tx_init(struct sparx5 *sparx5, struct sparx5_tx *tx, int channel) { struct fdma *fdma = &tx->fdma; fdma->channel_id = channel; fdma->n_dcbs = FDMA_DCB_MAX; fdma->n_dbs = FDMA_TX_DCB_MAX_DBS; fdma->priv = sparx5; fdma->db_size = ALIGN(FDMA_XTR_BUFFER_SIZE, PAGE_SIZE); fdma->size = fdma_get_size_contiguous(&sparx5->tx.fdma); fdma->ops.dataptr_cb = &sparx5_fdma_tx_dataptr_cb; fdma->ops.nextptr_cb = &fdma_nextptr_cb; } irqreturn_t sparx5_fdma_handler(int irq, void *args) { struct sparx5 *sparx5 = args; u32 db = 0, err = 0; db = spx5_rd(sparx5, FDMA_INTR_DB); err = spx5_rd(sparx5, FDMA_INTR_ERR); /* Clear interrupt */ if (db) { spx5_wr(0, sparx5, FDMA_INTR_DB_ENA); spx5_wr(db, sparx5, FDMA_INTR_DB); napi_schedule(&sparx5->rx.napi); } if (err) { u32 err_type = spx5_rd(sparx5, FDMA_ERRORS); dev_err_ratelimited(sparx5->dev, "ERR: int: %#x, type: %#x\n", err, err_type); spx5_wr(err, sparx5, FDMA_INTR_ERR); spx5_wr(err_type, sparx5, FDMA_ERRORS); } return IRQ_HANDLED; } static void sparx5_fdma_injection_mode(struct sparx5 *sparx5) { const int byte_swap = 1; int portno; int urgency; /* Change mode to fdma extraction and injection */ spx5_wr(QS_XTR_GRP_CFG_MODE_SET(2) | QS_XTR_GRP_CFG_STATUS_WORD_POS_SET(1) | QS_XTR_GRP_CFG_BYTE_SWAP_SET(byte_swap), sparx5, QS_XTR_GRP_CFG(XTR_QUEUE)); spx5_wr(QS_INJ_GRP_CFG_MODE_SET(2) | QS_INJ_GRP_CFG_BYTE_SWAP_SET(byte_swap), sparx5, QS_INJ_GRP_CFG(INJ_QUEUE)); /* CPU ports capture setup */ for (portno = sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_0); portno <= sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_1); portno++) { /* ASM CPU port: No preamble, IFH, enable padding */ spx5_wr(ASM_PORT_CFG_PAD_ENA_SET(1) | ASM_PORT_CFG_NO_PREAMBLE_ENA_SET(1) | ASM_PORT_CFG_INJ_FORMAT_CFG_SET(1), /* 1 = IFH */ sparx5, ASM_PORT_CFG(portno)); /* Reset WM cnt to unclog queued frames */ spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR_SET(1), DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR, sparx5, DSM_DEV_TX_STOP_WM_CFG(portno)); /* Set Disassembler Stop Watermark level */ spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM_SET(100), DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM, sparx5, DSM_DEV_TX_STOP_WM_CFG(portno)); /* Enable port in queue system */ urgency = sparx5_port_fwd_urg(sparx5, SPEED_2500); spx5_rmw(QFWD_SWITCH_PORT_MODE_PORT_ENA_SET(1) | QFWD_SWITCH_PORT_MODE_FWD_URGENCY_SET(urgency), QFWD_SWITCH_PORT_MODE_PORT_ENA | QFWD_SWITCH_PORT_MODE_FWD_URGENCY, sparx5, QFWD_SWITCH_PORT_MODE(portno)); /* Disable Disassembler buffer underrun watchdog * to avoid truncated packets in XTR */ spx5_rmw(DSM_BUF_CFG_UNDERFLOW_WATCHDOG_DIS_SET(1), DSM_BUF_CFG_UNDERFLOW_WATCHDOG_DIS, sparx5, DSM_BUF_CFG(portno)); /* Disabling frame aging */ spx5_rmw(HSCH_PORT_MODE_AGE_DIS_SET(1), HSCH_PORT_MODE_AGE_DIS, sparx5, HSCH_PORT_MODE(portno)); } } int sparx5_fdma_start(struct sparx5 *sparx5) { int err; /* Reset FDMA state */ spx5_wr(FDMA_CTRL_NRESET_SET(0), sparx5, FDMA_CTRL); spx5_wr(FDMA_CTRL_NRESET_SET(1), sparx5, FDMA_CTRL); /* Force ACP caching but disable read/write allocation */ spx5_rmw(CPU_PROC_CTRL_ACP_CACHE_FORCE_ENA_SET(1) | CPU_PROC_CTRL_ACP_AWCACHE_SET(0) | CPU_PROC_CTRL_ACP_ARCACHE_SET(0), CPU_PROC_CTRL_ACP_CACHE_FORCE_ENA | CPU_PROC_CTRL_ACP_AWCACHE | CPU_PROC_CTRL_ACP_ARCACHE, sparx5, CPU_PROC_CTRL); sparx5_fdma_injection_mode(sparx5); sparx5_fdma_rx_init(sparx5, &sparx5->rx, FDMA_XTR_CHANNEL); sparx5_fdma_tx_init(sparx5, &sparx5->tx, FDMA_INJ_CHANNEL); err = sparx5_fdma_rx_alloc(sparx5); if (err) { dev_err(sparx5->dev, "Could not allocate RX buffers: %d\n", err); return err; } err = sparx5_fdma_tx_alloc(sparx5); if (err) { dev_err(sparx5->dev, "Could not allocate TX buffers: %d\n", err); return err; } return err; } static u32 sparx5_fdma_port_ctrl(struct sparx5 *sparx5) { return spx5_rd(sparx5, FDMA_PORT_CTRL(0)); } int sparx5_fdma_stop(struct sparx5 *sparx5) { u32 val; napi_disable(&sparx5->rx.napi); /* Stop the fdma and channel interrupts */ sparx5_fdma_rx_deactivate(sparx5, &sparx5->rx); sparx5_fdma_tx_deactivate(sparx5, &sparx5->tx); /* Wait for the RX channel to stop */ read_poll_timeout(sparx5_fdma_port_ctrl, val, FDMA_PORT_CTRL_XTR_BUF_IS_EMPTY_GET(val) == 0, 500, 10000, 0, sparx5); fdma_free_phys(&sparx5->rx.fdma); fdma_free_phys(&sparx5->tx.fdma); return 0; }