| /* |
| * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved. |
| * |
| * Permission to use, copy, modify, and/or 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. |
| */ |
| |
| #include <linux/interrupt.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_platform.h> |
| #include <linux/debugfs.h> |
| #include <linux/netdevice.h> |
| #include <fal/fal_vsi.h> |
| #include "edma_data_plane.h" |
| #include "edma_regs.h" |
| #include "nss_dp_api_if.h" |
| #include "nss_dp_dev.h" |
| |
| /* |
| * EDMA hardware instance |
| */ |
| struct edma_hw edma_hw; |
| |
| /* |
| * edma_reg_read() |
| * Read EDMA register |
| */ |
| uint32_t edma_reg_read(uint32_t reg_off) |
| { |
| return hal_read_reg(edma_hw.reg_base, reg_off); |
| } |
| |
| /* |
| * edma_reg_write() |
| * Write EDMA register |
| */ |
| void edma_reg_write(uint32_t reg_off, uint32_t val) |
| { |
| hal_write_reg(edma_hw.reg_base, reg_off, val); |
| } |
| |
| /* |
| * edma_disable_interrupts() |
| * Disable EDMA RX/TX interrupt masks. |
| */ |
| static void edma_disable_interrupts(void) |
| { |
| struct edma_rxdesc_ring *rxdesc_ring = NULL; |
| struct edma_rxfill_ring *rxfill_ring = NULL; |
| struct edma_txcmpl_ring *txcmpl_ring = NULL; |
| int i; |
| |
| for (i = 0; i < edma_hw.rxdesc_rings; i++) { |
| rxdesc_ring = &edma_hw.rxdesc_ring[i]; |
| edma_reg_write(EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->id), |
| EDMA_MASK_INT_CLEAR); |
| } |
| |
| for (i = 0; i < edma_hw.txcmpl_rings; i++) { |
| txcmpl_ring = &edma_hw.txcmpl_ring[i]; |
| edma_reg_write(EDMA_REG_TX_INT_MASK(txcmpl_ring->id), |
| EDMA_MASK_INT_CLEAR); |
| } |
| |
| for (i = 0; i < edma_hw.rxfill_rings; i++) { |
| rxfill_ring = &edma_hw.rxfill_ring[i]; |
| edma_reg_write(EDMA_REG_RXFILL_INT_MASK(rxfill_ring->id), |
| EDMA_MASK_INT_CLEAR); |
| } |
| |
| /* |
| * Clear MISC interrupt mask. |
| */ |
| edma_reg_write(EDMA_REG_MISC_INT_MASK, EDMA_MASK_INT_CLEAR); |
| } |
| |
| /* |
| * edma_enable_interrupts() |
| * Enable RX/TX EDMA interrupt masks. |
| */ |
| static void edma_enable_interrupts(void) |
| { |
| struct edma_rxdesc_ring *rxdesc_ring = NULL; |
| struct edma_rxfill_ring *rxfill_ring = NULL; |
| struct edma_txcmpl_ring *txcmpl_ring = NULL; |
| int i; |
| |
| for (i = 0; i < edma_hw.rxfill_rings; i++) { |
| rxfill_ring = &edma_hw.rxfill_ring[i]; |
| edma_reg_write(EDMA_REG_RXFILL_INT_MASK(rxfill_ring->id), |
| edma_hw.rxfill_intr_mask); |
| } |
| |
| for (i = 0; i < edma_hw.txcmpl_rings; i++) { |
| txcmpl_ring = &edma_hw.txcmpl_ring[i]; |
| edma_reg_write(EDMA_REG_TX_INT_MASK(txcmpl_ring->id), |
| edma_hw.txcmpl_intr_mask); |
| } |
| |
| for (i = 0; i < edma_hw.rxdesc_rings; i++) { |
| rxdesc_ring = &edma_hw.rxdesc_ring[i]; |
| edma_reg_write(EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->id), |
| edma_hw.rxdesc_intr_mask); |
| } |
| |
| /* |
| * Enable MISC interrupt mask. |
| */ |
| edma_reg_write(EDMA_REG_MISC_INT_MASK, edma_hw.misc_intr_mask); |
| } |
| |
| /* |
| * edma_disable_port() |
| * EDMA disable port |
| */ |
| static void edma_disable_port(void) |
| { |
| edma_reg_write(EDMA_REG_PORT_CTRL, EDMA_DISABLE); |
| } |
| |
| /* |
| * edma_of_get_pdata() |
| * Read the device tree details for EDMA |
| */ |
| static int edma_of_get_pdata(struct resource *edma_res) |
| { |
| /* |
| * Find EDMA node in device tree |
| */ |
| edma_hw.device_node = of_find_node_by_name(NULL, |
| EDMA_DEVICE_NODE_NAME); |
| if (!edma_hw.device_node) { |
| pr_warn("EDMA device tree node (%s) not found\n", |
| EDMA_DEVICE_NODE_NAME); |
| return -EINVAL; |
| } |
| |
| /* |
| * Get EDMA device node |
| */ |
| edma_hw.pdev = of_find_device_by_node(edma_hw.device_node); |
| if (!edma_hw.pdev) { |
| pr_warn("Platform device for node %px(%s) not found\n", |
| edma_hw.device_node, |
| (edma_hw.device_node)->name); |
| return -EINVAL; |
| } |
| |
| /* |
| * Get EDMA register resource |
| */ |
| if (of_address_to_resource(edma_hw.device_node, 0, edma_res) != 0) { |
| pr_warn("Unable to get register address for edma device: " |
| EDMA_DEVICE_NODE_NAME"\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Get id of first TXDESC ring |
| */ |
| if (of_property_read_u32(edma_hw.device_node, "qcom,txdesc-ring-start", |
| &edma_hw.txdesc_ring_start) != 0) { |
| pr_warn("Read error 1st TXDESC ring (txdesc_ring_start)\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Get number of TXDESC rings |
| */ |
| if (of_property_read_u32(edma_hw.device_node, "qcom,txdesc-rings", |
| &edma_hw.txdesc_rings) != 0) { |
| pr_warn("Unable to read number of txdesc rings.\n"); |
| return -EINVAL; |
| } |
| edma_hw.txdesc_ring_end = edma_hw.txdesc_ring_start + |
| edma_hw.txdesc_rings; |
| |
| /* |
| * Get id of first TXCMPL ring |
| */ |
| if (of_property_read_u32(edma_hw.device_node, "qcom,txcmpl-ring-start", |
| &edma_hw.txcmpl_ring_start) != 0) { |
| pr_warn("Read error 1st TXCMPL ring (txcmpl_ring_start)\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Get number of TXCMPL rings |
| */ |
| if (of_property_read_u32(edma_hw.device_node, "qcom,txcmpl-rings", |
| &edma_hw.txcmpl_rings) != 0) { |
| pr_warn("Unable to read number of txcmpl rings.\n"); |
| return -EINVAL; |
| } |
| edma_hw.txcmpl_ring_end = edma_hw.txcmpl_ring_start + |
| edma_hw.txcmpl_rings; |
| |
| /* |
| * Get id of first RXFILL ring |
| */ |
| if (of_property_read_u32(edma_hw.device_node, "qcom,rxfill-ring-start", |
| &edma_hw.rxfill_ring_start) != 0) { |
| pr_warn("Read error 1st RXFILL ring (rxfill-ring-start)\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Get number of RXFILL rings |
| */ |
| if (of_property_read_u32(edma_hw.device_node, "qcom,rxfill-rings", |
| &edma_hw.rxfill_rings) != 0) { |
| pr_warn("Unable to read number of rxfill rings.\n"); |
| return -EINVAL; |
| } |
| edma_hw.rxfill_ring_end = edma_hw.rxfill_ring_start + |
| edma_hw.rxfill_rings; |
| |
| /* |
| * Get id of first RXDESC ring |
| */ |
| if (of_property_read_u32(edma_hw.device_node, "qcom,rxdesc-ring-start", |
| &edma_hw.rxdesc_ring_start) != 0) { |
| pr_warn("Read error 1st RXDESC ring (rxdesc-ring-start)\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Get number of RXDESC rings |
| */ |
| if (of_property_read_u32(edma_hw.device_node, "qcom,rxdesc-rings", |
| &edma_hw.rxdesc_rings) != 0) { |
| pr_warn("Unable to read number of rxdesc rings.\n"); |
| return -EINVAL; |
| } |
| edma_hw.rxdesc_ring_end = edma_hw.rxdesc_ring_start + |
| edma_hw.rxdesc_rings; |
| |
| return 0; |
| } |
| |
| /* |
| * edma_init() |
| * EDMA init |
| */ |
| int edma_init(void) |
| { |
| int ret = 0; |
| struct resource res_edma; |
| |
| /* |
| * Get all the DTS data needed |
| */ |
| if (edma_of_get_pdata(&res_edma) < 0) { |
| pr_warn("Unable to get EDMA DTS data.\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Request memory region for EDMA registers |
| */ |
| edma_hw.reg_resource = request_mem_region(res_edma.start, |
| resource_size(&res_edma), |
| EDMA_DEVICE_NODE_NAME); |
| if (!edma_hw.reg_resource) { |
| pr_warn("Unable to request EDMA register memory.\n"); |
| return -EFAULT; |
| } |
| |
| /* |
| * Remap register resource |
| */ |
| edma_hw.reg_base = ioremap_nocache((edma_hw.reg_resource)->start, |
| resource_size(edma_hw.reg_resource)); |
| if (!edma_hw.reg_base) { |
| pr_warn("Unable to remap EDMA register memory.\n"); |
| ret = -EFAULT; |
| goto edma_init_remap_fail; |
| } |
| |
| if (edma_hw_init(&edma_hw) != 0) { |
| ret = -EFAULT; |
| goto edma_init_hw_init_fail; |
| } |
| |
| platform_set_drvdata(edma_hw.pdev, (void *)&edma_hw); |
| |
| edma_hw.napi_added = 0; |
| |
| return 0; |
| |
| edma_init_hw_init_fail: |
| iounmap(edma_hw.reg_base); |
| |
| edma_init_remap_fail: |
| release_mem_region((edma_hw.reg_resource)->start, |
| resource_size(edma_hw.reg_resource)); |
| return ret; |
| } |
| |
| /* |
| * edma_cleanup() |
| * EDMA cleanup |
| */ |
| void edma_cleanup(bool is_dp_override) |
| { |
| int i; |
| struct edma_txcmpl_ring *txcmpl_ring = NULL; |
| struct edma_rxdesc_ring *rxdesc_ring = NULL; |
| |
| /* |
| * The cleanup can happen from data plane override |
| * or from module_exit, we want to cleanup only once |
| */ |
| if (!edma_hw.edma_initialized) { |
| /* |
| * Disable EDMA only at module exit time, since NSS firmware |
| * depends on this setting. |
| */ |
| if (!is_dp_override) { |
| edma_disable_port(); |
| } |
| return; |
| } |
| |
| /* |
| * Disable Rx rings used by this driver |
| */ |
| for (i = edma_hw.rxdesc_ring_start; i < edma_hw.rxdesc_ring_end; i++) |
| edma_reg_write(EDMA_REG_RXDESC_CTRL(i), EDMA_RING_DISABLE); |
| |
| /* |
| * Disable Tx rings used by this driver |
| */ |
| for (i = edma_hw.txdesc_ring_start; i < edma_hw.txdesc_ring_end; i++) { |
| txcmpl_ring = &edma_hw.txcmpl_ring[i]; |
| edma_reg_write(EDMA_REG_TXDESC_CTRL(i), |
| EDMA_RING_DISABLE); |
| } |
| |
| /* |
| * Disable RxFill Rings used by this driver |
| */ |
| for (i = edma_hw.rxfill_ring_start; i < edma_hw.rxfill_ring_end; i++) |
| edma_reg_write(EDMA_REG_RXFILL_RING_EN(i), EDMA_RING_DISABLE); |
| |
| /* |
| * Clear interrupt mask |
| */ |
| for (i = 0; i < edma_hw.rxdesc_rings; i++) { |
| rxdesc_ring = &edma_hw.rxdesc_ring[i]; |
| edma_reg_write(EDMA_REG_RXDESC_INT_MASK(rxdesc_ring->id), |
| EDMA_MASK_INT_CLEAR); |
| } |
| |
| for (i = 0; i < edma_hw.txcmpl_rings; i++) { |
| txcmpl_ring = &edma_hw.txcmpl_ring[i]; |
| edma_reg_write(EDMA_REG_TX_INT_MASK(txcmpl_ring->id), |
| EDMA_MASK_INT_CLEAR); |
| } |
| |
| edma_reg_write(EDMA_REG_MISC_INT_MASK, EDMA_MASK_INT_CLEAR); |
| |
| /* |
| * Remove interrupt handlers and NAPI |
| */ |
| if (edma_hw.napi_added) { |
| /* |
| * Free IRQ for TXCMPL rings |
| */ |
| for (i = 0; i < edma_hw.txcmpl_rings; i++) { |
| synchronize_irq(edma_hw.txcmpl_intr[i]); |
| free_irq(edma_hw.txcmpl_intr[i], |
| (void *)(edma_hw.pdev)); |
| } |
| |
| /* |
| * Free IRQ for RXFILL rings |
| */ |
| for (i = 0; i < edma_hw.rxfill_rings; i++) { |
| synchronize_irq(edma_hw.rxfill_intr[i]); |
| free_irq(edma_hw.rxfill_intr[i], |
| (void *)(edma_hw.pdev)); |
| } |
| |
| /* |
| * Free IRQ for RXDESC rings |
| */ |
| for (i = 0; i < edma_hw.rxdesc_rings; i++) { |
| synchronize_irq(edma_hw.rxdesc_intr[i]); |
| free_irq(edma_hw.rxdesc_intr[i], |
| (void *)(edma_hw.pdev)); |
| } |
| |
| /* |
| * Free Misc IRQ |
| */ |
| synchronize_irq(edma_hw.misc_intr); |
| free_irq(edma_hw.misc_intr, (void *)(edma_hw.pdev)); |
| |
| netif_napi_del(&edma_hw.napi); |
| edma_hw.napi_added = 0; |
| } |
| |
| /* |
| * Disable EDMA only at module exit time, since NSS firmware |
| * depends on this setting. |
| */ |
| if (!is_dp_override) { |
| edma_disable_port(); |
| } |
| |
| /* |
| * cleanup rings and free |
| */ |
| edma_cleanup_rings(&edma_hw); |
| iounmap(edma_hw.reg_base); |
| release_mem_region((edma_hw.reg_resource)->start, |
| resource_size(edma_hw.reg_resource)); |
| |
| /* |
| * Mark initialize false, so that we do not |
| * try to cleanup again |
| */ |
| edma_hw.edma_initialized = false; |
| } |
| |
| /* |
| * nss_dp_edma_if_open() |
| * Do slow path data plane open |
| */ |
| static int edma_if_open(struct nss_dp_data_plane_ctx *dpc, |
| uint32_t tx_desc_ring, uint32_t rx_desc_ring, |
| uint32_t mode) |
| { |
| if (!dpc->dev) |
| return NSS_DP_FAILURE; |
| |
| /* |
| * Enable NAPI |
| */ |
| if (edma_hw.active++ != 0) |
| return NSS_DP_SUCCESS; |
| |
| napi_enable(&edma_hw.napi); |
| |
| /* |
| * Enable the interrupt masks. |
| */ |
| edma_enable_interrupts(); |
| |
| return NSS_DP_SUCCESS; |
| } |
| |
| /* |
| * edma_if_close() |
| * Do slow path data plane close |
| */ |
| static int edma_if_close(struct nss_dp_data_plane_ctx *dpc) |
| { |
| if (--edma_hw.active != 0) |
| return NSS_DP_SUCCESS; |
| |
| /* |
| * Disable the interrupt masks. |
| */ |
| edma_disable_interrupts(); |
| |
| /* |
| * Disable NAPI |
| */ |
| napi_disable(&edma_hw.napi); |
| return NSS_DP_SUCCESS; |
| } |
| |
| /* |
| * edma_if_link_state() |
| */ |
| static int edma_if_link_state(struct nss_dp_data_plane_ctx *dpc, |
| uint32_t link_state) |
| { |
| return NSS_DP_SUCCESS; |
| } |
| |
| /* |
| * edma_if_mac_addr() |
| */ |
| static int edma_if_mac_addr(struct nss_dp_data_plane_ctx *dpc, uint8_t *addr) |
| { |
| return NSS_DP_SUCCESS; |
| } |
| |
| /* |
| * edma_if_change_mtu() |
| */ |
| static int edma_if_change_mtu(struct nss_dp_data_plane_ctx *dpc, uint32_t mtu) |
| { |
| return NSS_DP_SUCCESS; |
| } |
| |
| /* |
| * edma_if_xmit() |
| * Transmit a packet using EDMA |
| */ |
| static netdev_tx_t edma_if_xmit(struct nss_dp_data_plane_ctx *dpc, |
| struct sk_buff *skb) |
| { |
| struct net_device *netdev = dpc->dev; |
| int ret; |
| uint32_t tx_ring, skbq, nhead, ntail; |
| bool expand_skb = false; |
| |
| if (skb->len < ETH_HLEN) { |
| netdev_dbg(netdev, "skb->len < ETH_HLEN\n"); |
| goto drop; |
| } |
| |
| /* |
| * Select a Tx ring |
| */ |
| skbq = skb_get_queue_mapping(skb); |
| tx_ring = 0; |
| if ((edma_hw.txdesc_rings > 1) && (skbq > 0)) |
| tx_ring = edma_hw.txdesc_rings % skbq; |
| |
| /* |
| * Check for non-linear skb |
| */ |
| if (skb_is_nonlinear(skb)) { |
| netdev_dbg(netdev, "cannot Tx non-linear skb:%px\n", skb); |
| goto drop; |
| } |
| |
| /* |
| * Check for headroom/tailroom and clone |
| */ |
| nhead = netdev->needed_headroom; |
| ntail = netdev->needed_tailroom; |
| |
| if (skb_cloned(skb) || |
| (skb_headroom(skb) < nhead) || |
| (skb_headroom(skb) < ntail)) { |
| expand_skb = true; |
| } |
| |
| /* |
| * Expand the skb. This also unclones a cloned skb. |
| */ |
| if (expand_skb && pskb_expand_head(skb, nhead, ntail, GFP_ATOMIC)) { |
| netdev_dbg(netdev, "cannot expand skb:%px\n", skb); |
| goto drop; |
| } |
| |
| /* |
| * Transmit the packet |
| */ |
| ret = edma_ring_xmit(&edma_hw, netdev, skb, |
| &edma_hw.txdesc_ring[tx_ring]); |
| if (ret == EDMA_TX_OK) |
| return NETDEV_TX_OK; |
| |
| /* |
| * Not enough descriptors. Stop netdev Tx queue. |
| */ |
| if (ret == EDMA_TX_DESC) { |
| netif_stop_queue(netdev); |
| return NETDEV_TX_BUSY; |
| } |
| |
| drop: |
| dev_kfree_skb_any(skb); |
| netdev->stats.tx_dropped++; |
| |
| return NETDEV_TX_OK; |
| } |
| |
| /* |
| * edma_if_set_features() |
| * Set the supported net_device features |
| */ |
| static void edma_if_set_features(struct nss_dp_data_plane_ctx *dpc) |
| { |
| /* |
| * TODO - add flags to support HIGHMEM/cksum offload VLAN |
| * the features are enabled. |
| */ |
| } |
| |
| /* TODO - check if this is needed */ |
| /* |
| * edma_if_pause_on_off() |
| * Set pause frames on or off |
| * |
| * No need to send a message if we defaulted to slow path. |
| */ |
| static int edma_if_pause_on_off(struct nss_dp_data_plane_ctx *dpc, |
| uint32_t pause_on) |
| { |
| return NSS_DP_SUCCESS; |
| } |
| |
| #ifdef CONFIG_RFS_ACCEL |
| /* |
| * edma_if_rx_flow_steer() |
| * Flow steer of the data plane |
| * |
| * Initial receive flow steering function for data plane operation. |
| */ |
| static int edma_if_rx_flow_steer(struct nss_dp_data_plane_ctx *dpc, struct sk_buff *skb, |
| uint32_t cpu, bool is_add) |
| { |
| return NSS_DP_SUCCESS; |
| } |
| #endif |
| |
| /* |
| * edma_if_deinit() |
| * Free edma resources |
| */ |
| static int edma_if_deinit(struct nss_dp_data_plane_ctx *dpc) |
| { |
| /* |
| * Free up resources used by EDMA if all the |
| * interfaces have been overridden |
| * */ |
| if (edma_hw.dp_override_cnt == EDMA_MAX_GMACS - 1) { |
| edma_cleanup(true); |
| } else { |
| edma_hw.dp_override_cnt++; |
| } |
| |
| return NSS_DP_SUCCESS; |
| } |
| |
| /* |
| * edma_irq_init() |
| * Initialize interrupt handlers for the driver |
| */ |
| static int edma_irq_init(void) |
| { |
| int err; |
| uint32_t entry_num, i; |
| |
| /* |
| * Get TXCMPL rings IRQ numbers |
| */ |
| entry_num = 0; |
| for (i = 0; i < edma_hw.txcmpl_rings; i++, entry_num++) { |
| edma_hw.txcmpl_intr[i] = |
| platform_get_irq(edma_hw.pdev, entry_num); |
| if (edma_hw.txcmpl_intr[i] < 0) { |
| pr_warn("%s: txcmpl_intr[%u] irq get failed\n", |
| (edma_hw.device_node)->name, i); |
| return -1; |
| } |
| |
| pr_debug("%s: txcmpl_intr[%u] = %u\n", |
| (edma_hw.device_node)->name, |
| i, edma_hw.txcmpl_intr[i]); |
| } |
| |
| /* |
| * Get RXFILL rings IRQ numbers |
| */ |
| for (i = 0; i < edma_hw.rxfill_rings; i++, entry_num++) { |
| edma_hw.rxfill_intr[i] = |
| platform_get_irq(edma_hw.pdev, entry_num); |
| if (edma_hw.rxfill_intr[i] < 0) { |
| pr_warn("%s: rxfill_intr[%u] irq get failed\n", |
| (edma_hw.device_node)->name, i); |
| return -1; |
| } |
| |
| pr_debug("%s: rxfill_intr[%u] = %u\n", |
| (edma_hw.device_node)->name, |
| i, edma_hw.rxfill_intr[i]); |
| } |
| |
| /* |
| * Get RXDESC rings IRQ numbers |
| * |
| */ |
| for (i = 0; i < edma_hw.rxdesc_rings; i++, entry_num++) { |
| edma_hw.rxdesc_intr[i] = |
| platform_get_irq(edma_hw.pdev, entry_num); |
| if (edma_hw.rxdesc_intr[i] < 0) { |
| pr_warn("%s: rxdesc_intr[%u] irq get failed\n", |
| (edma_hw.device_node)->name, i); |
| return -1; |
| } |
| |
| pr_debug("%s: rxdesc_intr[%u] = %u\n", |
| (edma_hw.device_node)->name, |
| i, edma_hw.rxdesc_intr[i]); |
| } |
| |
| /* |
| * Get misc IRQ number |
| */ |
| edma_hw.misc_intr = platform_get_irq(edma_hw.pdev, entry_num); |
| pr_debug("%s: misc IRQ:%u\n", |
| (edma_hw.device_node)->name, |
| edma_hw.misc_intr); |
| |
| /* |
| * Request IRQ for TXCMPL rings |
| */ |
| for (i = 0; i < edma_hw.txcmpl_rings; i++) { |
| err = request_irq(edma_hw.txcmpl_intr[i], |
| edma_handle_irq, IRQF_SHARED, |
| "edma_txcmpl", (void *)edma_hw.pdev); |
| if (err) { |
| pr_debug("TXCMPL ring IRQ:%d request failed\n", |
| edma_hw.txcmpl_intr[i]); |
| return -1; |
| |
| } |
| } |
| |
| /* |
| * Request IRQ for RXFILL rings |
| */ |
| for (i = 0; i < edma_hw.rxfill_rings; i++) { |
| err = request_irq(edma_hw.rxfill_intr[i], |
| edma_handle_irq, IRQF_SHARED, |
| "edma_rxfill", (void *)edma_hw.pdev); |
| if (err) { |
| pr_debug("RXFILL ring IRQ:%d request failed\n", |
| edma_hw.rxfill_intr[i]); |
| goto rx_fill_ring_intr_req_fail; |
| } |
| } |
| |
| /* |
| * Request IRQ for RXDESC rings |
| */ |
| for (i = 0; i < edma_hw.rxdesc_rings; i++) { |
| err = request_irq(edma_hw.rxdesc_intr[i], |
| edma_handle_irq, IRQF_SHARED, |
| "edma_rxdesc", (void *)edma_hw.pdev); |
| if (err) { |
| pr_debug("RXDESC ring IRQ:%d request failed\n", |
| edma_hw.rxdesc_intr[i]); |
| goto rx_desc_ring_intr_req_fail; |
| } |
| } |
| |
| /* |
| * Request Misc IRQ |
| */ |
| err = request_irq(edma_hw.misc_intr, edma_handle_misc_irq, |
| IRQF_SHARED, "edma_misc", |
| (void *)edma_hw.pdev); |
| if (err) { |
| pr_debug("MISC IRQ:%d request failed\n", |
| edma_hw.misc_intr); |
| goto misc_intr_req_fail; |
| } |
| |
| return 0; |
| |
| misc_intr_req_fail: |
| |
| /* |
| * Free IRQ for RXDESC rings |
| */ |
| for (i = 0; i < edma_hw.rxdesc_rings; i++) { |
| synchronize_irq(edma_hw.rxdesc_intr[i]); |
| free_irq(edma_hw.rxdesc_intr[i], |
| (void *)&(edma_hw.pdev)->dev); |
| } |
| |
| rx_desc_ring_intr_req_fail: |
| |
| /* |
| * Free IRQ for RXFILL rings |
| */ |
| for (i = 0; i < edma_hw.rxfill_rings; i++) { |
| synchronize_irq(edma_hw.rxfill_intr[i]); |
| free_irq(edma_hw.rxfill_intr[i], |
| (void *)&(edma_hw.pdev)->dev); |
| } |
| |
| rx_fill_ring_intr_req_fail: |
| |
| /* |
| * Free IRQ for TXCMPL rings |
| */ |
| for (i = 0; i < edma_hw.txcmpl_rings; i++) { |
| |
| synchronize_irq(edma_hw.txcmpl_intr[i]); |
| free_irq(edma_hw.txcmpl_intr[i], |
| (void *)&(edma_hw.pdev)->dev); |
| } |
| |
| return -1; |
| } |
| |
| /* |
| * edma_register_netdevice() |
| * Register netdevice with EDMA |
| */ |
| static int edma_register_netdevice(struct net_device *netdev, uint32_t macid) |
| { |
| if (!netdev) { |
| pr_info("nss_dp_edma: Invalid netdev pointer %px\n", netdev); |
| return -EINVAL; |
| } |
| |
| if ((macid < EDMA_START_GMACS) || (macid > EDMA_MAX_GMACS)) { |
| netdev_dbg(netdev, "nss_dp_edma: Invalid macid(%d) for %s\n", |
| macid, netdev->name); |
| return -EINVAL; |
| } |
| |
| netdev_info(netdev, "nss_dp_edma: Registering netdev %s(qcom-id:%d) with EDMA\n", |
| netdev->name, macid); |
| |
| /* |
| * We expect 'macid' to correspond to ports numbers on |
| * IPQ807x. These begin from '1' and hence we subtract |
| * one when using it as an array index. |
| */ |
| edma_hw.netdev_arr[macid - 1] = netdev; |
| |
| /* |
| * NAPI add |
| */ |
| if (!edma_hw.napi_added) { |
| netif_napi_add(netdev, &edma_hw.napi, edma_napi, |
| EDMA_NAPI_WORK); |
| /* |
| * Register the interrupt handlers and enable interrupts |
| */ |
| if (edma_irq_init() < 0) |
| return -EINVAL; |
| |
| edma_hw.napi_added = 1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * edma_if_init() |
| */ |
| static int edma_if_init(struct nss_dp_data_plane_ctx *dpc) |
| { |
| |
| struct net_device *netdev = dpc->dev; |
| struct nss_dp_dev *dp_dev = (struct nss_dp_dev *)netdev_priv(netdev); |
| int ret = 0; |
| |
| /* |
| * Register the netdev |
| */ |
| ret = edma_register_netdevice(netdev, dp_dev->macid); |
| if (ret) { |
| netdev_dbg(netdev, |
| "Error registering netdevice with EDMA %s\n", |
| netdev->name); |
| return NSS_DP_FAILURE; |
| } |
| |
| /* |
| * Headroom needed for Tx preheader |
| */ |
| netdev->needed_headroom += EDMA_TX_PREHDR_SIZE; |
| |
| return NSS_DP_SUCCESS; |
| } |
| |
| /* |
| * nss_dp_edma_ops |
| */ |
| struct nss_dp_data_plane_ops nss_dp_edma_ops = { |
| .init = edma_if_init, |
| .open = edma_if_open, |
| .close = edma_if_close, |
| .link_state = edma_if_link_state, |
| .mac_addr = edma_if_mac_addr, |
| .change_mtu = edma_if_change_mtu, |
| .xmit = edma_if_xmit, |
| .set_features = edma_if_set_features, |
| .pause_on_off = edma_if_pause_on_off, |
| #ifdef CONFIG_RFS_ACCEL |
| .rx_flow_steer = edma_if_rx_flow_steer, |
| #endif |
| .deinit = edma_if_deinit, |
| }; |