| /* |
| * Copyright (c) 2014 - 2018, 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/cpu_rmap.h> |
| #include <linux/of_net.h> |
| #include <linux/timer.h> |
| #include <linux/bitmap.h> |
| #include "edma.h" |
| #include "ess_edma.h" |
| |
| /* Weight round robin and virtual QID mask */ |
| #define EDMA_WRR_VID_SCTL_MASK 0xffff |
| |
| /* Weight round robin and virtual QID shift */ |
| #define EDMA_WRR_VID_SCTL_SHIFT 16 |
| |
| static const u32 edma_idt_tbl[EDMA_CPU_CORES_SUPPORTED][EDMA_NUM_IDT] = { |
| |
| /* For 1 core */ |
| { |
| 0x0, 0x0, 0x0, 0x0, |
| 0x0, 0x0, 0x0, 0x0, |
| 0x0, 0x0, 0x0, 0x0, |
| 0x0, 0x0, 0x0, 0x0 |
| }, |
| |
| /* For 2 cores */ |
| { |
| 0x20202020, 0x20202020, 0x20202020, 0x20202020, |
| 0x20202020, 0x20202020, 0x20202020, 0x20202020, |
| 0x20202020, 0x20202020, 0x20202020, 0x20202020, |
| 0x20202020, 0x20202020, 0x20202020, 0x20202020 |
| }, |
| |
| /* For 3 cores */ |
| { |
| 0x20420420, 0x04204204, 0x42042042, 0x20420420, |
| 0x04204204, 0x42042042, 0x20420420, 0x04204204, |
| 0x42042042, 0x20420420, 0x04204204, 0x42042042, |
| 0x20420420, 0x04204204, 0x42042042, 0x20420420 |
| }, |
| |
| /* For 4 cores */ |
| { |
| 0x64206420, 0x64206420, 0x64206420, 0x64206420, |
| 0x64206420, 0x64206420, 0x64206420, 0x64206420, |
| 0x64206420, 0x64206420, 0x64206420, 0x64206420, |
| 0x64206420, 0x64206420, 0x64206420, 0x64206420 |
| } |
| }; |
| |
| /* Set the mask for tx-completion mapping for tx packets on each core. |
| * We set tx-mask for each core, depending on number of cores supported |
| * by the platform. |
| * For instance, For platform supporting 2 cores, we have the following map |
| * 0x00F0: For tx on Core0, map the tx-completion to Core1 |
| * 0x000F: For tx on Core1, map the tx-completion to Core0 |
| * 0xF000: For tx on Core2, map the tx-completion to Core3 |
| * 0x0F00: For tx on Core3, map the tx-completion to Core2 |
| */ |
| static const u32 edma_tx_mask[EDMA_CPU_CORES_SUPPORTED][4] = { |
| { |
| 0x000F, 0x00F0, 0x0F00, 0xF000 /* For 1 core */ |
| }, |
| |
| { |
| 0x00F0, 0x000F, 0xF000, 0x0F00 /* For 2 cores */ |
| }, |
| |
| { |
| 0x00F0, 0x0F00, 0x000F, 0xF000 /* For 3 cores */ |
| }, |
| |
| { |
| 0x0F00, 0xF000, 0x000F, 0x00F0 /* For 4 cores */ |
| }, |
| }; |
| |
| /* tx_comp_start will be the start of the queue on which Tx-completion |
| * for that Core will take place which means We will have 4 queues |
| * for each core start from tx_comp_start |
| * |
| * For instance, For platform supporting 2 cores, we have the following map |
| * 4: For tx on Core0, Tx-completion will be on Core 1 |
| * hence tx_comp_start will commence from tx_queue 4 |
| * 0: For tx on Core1, Tx-completion will be on Core 0 |
| * hence tx_comp_start will commence from tx_queue 01 |
| * 12: For tx on Core2, Tx-completion will be on Core 3 |
| * hence tx_comp_start will commence from tx_queue 12 |
| * 8: For tx on Core3, Tx-completion will be on Core 2 |
| * hence tx_comp_start will commence from tx_queue 8 |
| */ |
| static const u32 edma_tx_comp_start[EDMA_CPU_CORES_SUPPORTED][4] = { |
| { |
| 0, 4, 8, 12 /* For 1 core */ |
| }, |
| |
| { |
| 4, 0, 12, 8 /* For 2 cores */ |
| }, |
| |
| { |
| 4, 8, 0, 12 /* For 3 cores */ |
| }, |
| |
| { |
| 8, 12, 0, 4 /* For 4 cores */ |
| }, |
| }; |
| |
| /* We export these values for smp_affinity as sysctls |
| * which is read and set by the qca-edma script at run-time. |
| */ |
| static const u32 edma_tx_affinity[EDMA_CPU_CORES_SUPPORTED][4] = { |
| /* Order is from left to right; Core0 to Core3 */ |
| { |
| 1, 2, 4, 8 /* For 1 core */ |
| }, |
| |
| { |
| 2, 1, 8, 4 /* For 2 cores */ |
| }, |
| |
| { |
| 2, 4, 1, 8 /* For 3 cores */ |
| }, |
| |
| { |
| 4, 8, 1, 2 /* For 4 cores */ |
| }, |
| }; |
| |
| char edma_axi_driver_name[] = "ess_edma"; |
| static const u32 default_msg = NETIF_MSG_DRV | NETIF_MSG_PROBE | |
| NETIF_MSG_LINK | NETIF_MSG_TIMER | NETIF_MSG_IFDOWN | NETIF_MSG_IFUP; |
| |
| static u32 edma_hw_addr; |
| |
| struct timer_list edma_stats_timer; |
| static struct mii_bus *miibus_gb; |
| |
| char edma_tx_irq[16][64]; |
| char edma_rx_irq[8][64]; |
| struct net_device *edma_netdev[EDMA_MAX_PORTID_SUPPORTED]; |
| |
| extern u8 edma_dscp2ac_tbl[EDMA_PRECEDENCE_MAX]; |
| extern u32 edma_per_prec_stats_enable; |
| extern u32 edma_prec_stats_reset; |
| extern u32 edma_iad_stats_enable; |
| extern u32 edma_iad_stats_reset; |
| extern u32 edma_max_valid_ifd_usec; |
| static u32 edma_print_flow_table __read_mostly = 0; |
| extern struct edma_flow_attrib edma_flow_tbl[EDMA_MAX_IAD_FLOW_STATS_SUPPORTED]; |
| |
| static struct phy_device *edma_phydev[EDMA_MAX_PORTID_SUPPORTED]; |
| static int edma_link_detect_bmp; |
| static int phy_dev_state[EDMA_MAX_PORTID_SUPPORTED]; |
| |
| static u32 edma_default_ltag __read_mostly = EDMA_LAN_DEFAULT_VLAN; |
| static u32 edma_default_wtag __read_mostly = EDMA_WAN_DEFAULT_VLAN; |
| static u32 edma_default_group1_vtag __read_mostly = EDMA_DEFAULT_GROUP1_VLAN; |
| static u32 edma_default_group2_vtag __read_mostly = EDMA_DEFAULT_GROUP2_VLAN; |
| static u32 edma_default_group3_vtag __read_mostly = EDMA_DEFAULT_GROUP3_VLAN; |
| static u32 edma_default_group4_vtag __read_mostly = EDMA_DEFAULT_GROUP4_VLAN; |
| static u32 edma_default_group5_vtag __read_mostly = EDMA_DEFAULT_GROUP5_VLAN; |
| static u32 edma_num_cores __read_mostly = EDMA_CPU_CORES_SUPPORTED; |
| static u32 edma_core_completion_affinity[EDMA_CPU_CORES_SUPPORTED]; |
| |
| static u32 edma_default_group1_bmp __read_mostly = EDMA_DEFAULT_GROUP1_BMP; |
| static u32 edma_default_group2_bmp __read_mostly = EDMA_DEFAULT_GROUP2_BMP; |
| static u32 edma_disable_rss __read_mostly = EDMA_DEFAULT_DISABLE_RSS; |
| |
| u32 edma_disable_queue_stop __read_mostly = EDMA_DEFAULT_DISABLE_QUEUE_STOP; |
| |
| static int edma_weight_assigned_to_q __read_mostly; |
| static int edma_queue_to_virtual_q __read_mostly; |
| static bool edma_enable_rstp __read_mostly; |
| static int edma_athr_hdr_eth_type __read_mostly; |
| |
| static int page_mode; |
| module_param(page_mode, int, 0); |
| MODULE_PARM_DESC(page_mode, "enable page mode"); |
| |
| static int overwrite_mode; |
| module_param(overwrite_mode, int, 0); |
| MODULE_PARM_DESC(overwrite_mode, "overwrite default page_mode setting"); |
| |
| static int jumbo_mru = EDMA_RX_HEAD_BUFF_SIZE; |
| module_param(jumbo_mru, int, 0); |
| MODULE_PARM_DESC(jumbo_mru, "enable fraglist support"); |
| |
| static int num_rxq = 4; |
| module_param(num_rxq, int, 0); |
| MODULE_PARM_DESC(num_rxq, "change the number of rx queues"); |
| |
| void edma_write_reg(u16 reg_addr, u32 reg_value) |
| { |
| writel(reg_value, ((void __iomem *)(edma_hw_addr + reg_addr))); |
| } |
| |
| void edma_read_reg(u16 reg_addr, volatile u32 *reg_value) |
| { |
| *reg_value = readl((void __iomem *)(edma_hw_addr + reg_addr)); |
| } |
| |
| /* edma_change_tx_coalesce() |
| * change tx interrupt moderation timer |
| */ |
| void edma_change_tx_coalesce(int usecs) |
| { |
| u32 reg_value; |
| |
| /* Here, we right shift the value from the user by 1, this is |
| * done because IMT resolution timer is 2usecs. 1 count |
| * of this register corresponds to 2 usecs. |
| */ |
| edma_read_reg(EDMA_REG_IRQ_MODRT_TIMER_INIT, ®_value); |
| reg_value = ((reg_value & 0xffff) | ((usecs >> 1) << 16)); |
| edma_write_reg(EDMA_REG_IRQ_MODRT_TIMER_INIT, reg_value); |
| } |
| |
| /* edma_change_rx_coalesce() |
| * change rx interrupt moderation timer |
| */ |
| void edma_change_rx_coalesce(int usecs) |
| { |
| u32 reg_value; |
| |
| /* Here, we right shift the value from the user by 1, this is |
| * done because IMT resolution timer is 2usecs. 1 count |
| * of this register corresponds to 2 usecs. |
| */ |
| edma_read_reg(EDMA_REG_IRQ_MODRT_TIMER_INIT, ®_value); |
| reg_value = ((reg_value & 0xffff0000) | (usecs >> 1)); |
| edma_write_reg(EDMA_REG_IRQ_MODRT_TIMER_INIT, reg_value); |
| } |
| |
| /* edma_get_tx_rx_coalesce() |
| * Get tx/rx interrupt moderation value |
| */ |
| void edma_get_tx_rx_coalesce(u32 *reg_val) |
| { |
| edma_read_reg(EDMA_REG_IRQ_MODRT_TIMER_INIT, reg_val); |
| } |
| |
| void edma_read_append_stats(struct edma_common_info *edma_cinfo) |
| { |
| u64 *p; |
| int i; |
| u32 stat; |
| |
| spin_lock(&edma_cinfo->stats_lock); |
| p = (u64 *)&(edma_cinfo->edma_ethstats); |
| |
| for (i = 0; i < EDMA_MAX_TRANSMIT_QUEUE; i++) { |
| edma_read_reg(EDMA_REG_TX_STAT_PKT_Q(i), &stat); |
| *p += stat; |
| p++; |
| } |
| |
| for (i = 0; i < EDMA_MAX_TRANSMIT_QUEUE; i++) { |
| edma_read_reg(EDMA_REG_TX_STAT_BYTE_Q(i), &stat); |
| *p += stat; |
| p++; |
| } |
| |
| for (i = 0; i < EDMA_MAX_RECEIVE_QUEUE; i++) { |
| edma_read_reg(EDMA_REG_RX_STAT_PKT_Q(i), &stat); |
| *p += stat; |
| p++; |
| } |
| |
| for (i = 0; i < EDMA_MAX_RECEIVE_QUEUE; i++) { |
| edma_read_reg(EDMA_REG_RX_STAT_BYTE_Q(i), &stat); |
| *p += stat; |
| p++; |
| } |
| |
| spin_unlock(&edma_cinfo->stats_lock); |
| } |
| |
| static void edma_statistics_timer(unsigned long data) |
| { |
| struct edma_common_info *edma_cinfo = (struct edma_common_info *)data; |
| |
| edma_read_append_stats(edma_cinfo); |
| |
| mod_timer(&edma_stats_timer, jiffies + 1*HZ); |
| } |
| |
| static int edma_enable_stp_rstp(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| int ret; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| if (write) |
| edma_set_stp_rstp(edma_enable_rstp); |
| |
| return ret; |
| } |
| |
| static int edma_ath_hdr_eth_type(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| int ret; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| if (write) |
| edma_assign_ath_hdr_type(edma_athr_hdr_eth_type); |
| |
| return ret; |
| } |
| |
| static int edma_get_port_from_phydev(struct phy_device *phydev) |
| { |
| int i; |
| |
| for (i = 0; i < EDMA_MAX_PORTID_SUPPORTED; i++) { |
| if (phydev == edma_phydev[i]) |
| return i; |
| } |
| |
| pr_err("Invalid PHY devive\n"); |
| return -1; |
| } |
| |
| static int edma_is_port_used(int portid) |
| { |
| int used_portid_bmp; |
| used_portid_bmp = edma_link_detect_bmp >> 1; |
| |
| while (used_portid_bmp) { |
| int port_bit_set = ffs(used_portid_bmp); |
| if (port_bit_set == portid) |
| return 1; |
| used_portid_bmp &= ~(1 << (port_bit_set - 1)); |
| } |
| |
| return 0; |
| } |
| |
| static int edma_change_default_lan_vlan(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| int ret; |
| |
| if (!edma_netdev[1]) { |
| pr_err("Netdevice for default_lan does not exist\n"); |
| return -1; |
| } |
| |
| adapter = netdev_priv(edma_netdev[1]); |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| |
| if (write) |
| adapter->default_vlan_tag = edma_default_ltag; |
| |
| return ret; |
| } |
| |
| static int edma_change_default_wan_vlan(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| int ret; |
| |
| if (!edma_netdev[0]) { |
| pr_err("Netdevice for default_wan does not exist\n"); |
| return -1; |
| } |
| |
| adapter = netdev_priv(edma_netdev[0]); |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| |
| if (write) |
| adapter->default_vlan_tag = edma_default_wtag; |
| |
| return ret; |
| } |
| |
| static int edma_change_group1_vtag(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| struct edma_common_info *edma_cinfo; |
| int ret; |
| |
| if (!edma_netdev[0]) { |
| pr_err("Netdevice for Group 1 does not exist\n"); |
| return -1; |
| } |
| |
| adapter = netdev_priv(edma_netdev[0]); |
| edma_cinfo = adapter->edma_cinfo; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| |
| if (write) |
| adapter->default_vlan_tag = edma_default_group1_vtag; |
| |
| return ret; |
| } |
| |
| static int edma_change_group2_vtag(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| struct edma_common_info *edma_cinfo; |
| int ret; |
| |
| if (!edma_netdev[1]) { |
| pr_err("Netdevice for Group 2 does not exist\n"); |
| return -1; |
| } |
| |
| adapter = netdev_priv(edma_netdev[1]); |
| edma_cinfo = adapter->edma_cinfo; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| |
| if (write) |
| adapter->default_vlan_tag = edma_default_group2_vtag; |
| |
| return ret; |
| } |
| |
| static int edma_change_group3_vtag(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| struct edma_common_info *edma_cinfo; |
| int ret; |
| |
| if (!edma_netdev[2]) { |
| pr_err("Netdevice for Group 3 does not exist\n"); |
| return -1; |
| } |
| |
| adapter = netdev_priv(edma_netdev[2]); |
| edma_cinfo = adapter->edma_cinfo; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| |
| if (write) |
| adapter->default_vlan_tag = edma_default_group3_vtag; |
| |
| return ret; |
| } |
| |
| static int edma_change_group4_vtag(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| struct edma_common_info *edma_cinfo; |
| int ret; |
| |
| if (!edma_netdev[3]) { |
| pr_err("Netdevice for Group 4 does not exist\n"); |
| return -1; |
| } |
| |
| adapter = netdev_priv(edma_netdev[3]); |
| edma_cinfo = adapter->edma_cinfo; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| |
| if (write) |
| adapter->default_vlan_tag = edma_default_group4_vtag; |
| |
| return ret; |
| } |
| |
| static int edma_change_group5_vtag(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| struct edma_common_info *edma_cinfo; |
| int ret; |
| |
| if (!edma_netdev[4]) { |
| pr_err("Netdevice for Group 5 does not exist\n"); |
| return -1; |
| } |
| |
| adapter = netdev_priv(edma_netdev[4]); |
| edma_cinfo = adapter->edma_cinfo; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| |
| if (write) |
| adapter->default_vlan_tag = edma_default_group5_vtag; |
| |
| return ret; |
| } |
| |
| static int edma_change_group1_bmp(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| struct edma_common_info *edma_cinfo; |
| struct net_device *ndev; |
| struct phy_device *phydev; |
| int ret, num_ports_enabled; |
| u32 portid_bmp, port_bit, prev_bmp, port_id; |
| |
| ndev = edma_netdev[0]; |
| if (!ndev) { |
| pr_err("Netdevice for Group 0 does not exist\n"); |
| return -1; |
| } |
| |
| prev_bmp = edma_default_group1_bmp; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| if ((!write) || (prev_bmp == edma_default_group1_bmp)) |
| return ret; |
| |
| adapter = netdev_priv(ndev); |
| edma_cinfo = adapter->edma_cinfo; |
| |
| /* We ignore the bit for CPU Port */ |
| portid_bmp = edma_default_group1_bmp >> 1; |
| port_bit = ffs(portid_bmp); |
| if (port_bit > EDMA_MAX_PORTID_SUPPORTED) |
| return -1; |
| |
| /* If this group has no ports, |
| * we disable polling for the adapter, stop the queues and return |
| */ |
| if (!port_bit) { |
| adapter->dp_bitmap = edma_default_group1_bmp; |
| if (adapter->poll_required) { |
| mutex_lock(&adapter->poll_mutex); |
| adapter->poll_required = 0; |
| mutex_unlock(&adapter->poll_mutex); |
| adapter->link_state = __EDMA_LINKDOWN; |
| netif_carrier_off(ndev); |
| netif_tx_stop_all_queues(ndev); |
| } |
| return 0; |
| } |
| |
| /* Our array indexes are for 5 ports (0 - 4) */ |
| port_bit--; |
| edma_link_detect_bmp = 0; |
| |
| /* Do we have more ports in this group */ |
| num_ports_enabled = bitmap_weight((long unsigned int *)&portid_bmp, 32); |
| |
| /* If this group has more then one port, |
| * we disable polling for the adapter as link detection |
| * should be disabled, stop the phy state machine of previous |
| * phy adapter attached to group and start the queues |
| */ |
| if (num_ports_enabled > 1) { |
| mutex_lock(&adapter->poll_mutex); |
| if (adapter->poll_required) { |
| adapter->poll_required = 0; |
| if (adapter->phydev) { |
| port_id = edma_get_port_from_phydev( |
| adapter->phydev); |
| |
| /* We check if phydev attached to this group is |
| * already started and if yes, we stop |
| * the state machine for the phy |
| */ |
| if (phy_dev_state[port_id]) { |
| phy_stop_machine(adapter->phydev); |
| phy_dev_state[port_id] = 0; |
| } |
| |
| adapter->phydev = NULL; |
| } |
| |
| /* Start the tx queues for this netdev |
| * with link detection disabled |
| */ |
| if (adapter->link_state == __EDMA_LINKDOWN) { |
| adapter->link_state = __EDMA_LINKUP; |
| netif_tx_start_all_queues(ndev); |
| netif_carrier_on(ndev); |
| } |
| } |
| mutex_unlock(&adapter->poll_mutex); |
| |
| goto set_bitmap; |
| } |
| |
| mutex_lock(&adapter->poll_mutex); |
| adapter->poll_required = adapter->poll_required_dynamic; |
| mutex_unlock(&adapter->poll_mutex); |
| |
| if (!adapter->poll_required) |
| goto set_bitmap; |
| |
| phydev = adapter->phydev; |
| |
| /* If this group has only one port, |
| * if phydev exists we start the phy state machine |
| * and if it doesn't we create a phydev and start it. |
| */ |
| if (edma_phydev[port_bit]) { |
| adapter->phydev = edma_phydev[port_bit]; |
| set_bit(port_bit, (long unsigned int*)&edma_link_detect_bmp); |
| |
| /* If the Phy device has changed group, |
| * we need to reassign the netdev |
| */ |
| if (adapter->phydev->attached_dev != ndev) |
| adapter->phydev->attached_dev = ndev; |
| |
| if (!phy_dev_state[port_bit]) { |
| phy_start_machine(adapter->phydev); |
| phy_dev_state[port_bit] = 1; |
| } |
| } else { |
| snprintf(adapter->phy_id, |
| MII_BUS_ID_SIZE + 3, PHY_ID_FMT, |
| miibus_gb->id, |
| port_bit); |
| |
| adapter->phydev = phy_connect(ndev, |
| (const char *)adapter->phy_id, |
| &edma_adjust_link, |
| PHY_INTERFACE_MODE_SGMII); |
| |
| if (IS_ERR(adapter->phydev)) { |
| adapter->phydev = phydev; |
| pr_err("PHY attach FAIL for port %d", port_bit); |
| return -1; |
| } |
| |
| if (adapter->phydev->attached_dev != ndev) |
| adapter->phydev->attached_dev = ndev; |
| |
| edma_phydev[port_bit] = adapter->phydev; |
| phy_dev_state[port_bit] = 1; |
| set_bit(port_bit, (long unsigned int *)&edma_link_detect_bmp); |
| adapter->phydev->advertising |= |
| (ADVERTISED_Pause | |
| ADVERTISED_Asym_Pause); |
| adapter->phydev->supported |= |
| (SUPPORTED_Pause | |
| SUPPORTED_Asym_Pause); |
| phy_start(adapter->phydev); |
| phy_start_aneg(adapter->phydev); |
| } |
| |
| /* We check if this phydev is in use by other Groups |
| * and stop phy machine only if it is not stopped |
| */ |
| if (phydev) { |
| port_id = edma_get_port_from_phydev(phydev); |
| if (phy_dev_state[port_id]) { |
| phy_stop_machine(phydev); |
| phy_dev_state[port_id] = 0; |
| } |
| } |
| |
| mutex_lock(&adapter->poll_mutex); |
| adapter->poll_required = 1; |
| mutex_unlock(&adapter->poll_mutex); |
| adapter->link_state = __EDMA_LINKDOWN; |
| |
| set_bitmap: |
| while (portid_bmp) { |
| int port_bit_set = ffs(portid_bmp); |
| edma_cinfo->portid_netdev_lookup_tbl[port_bit_set] = ndev; |
| portid_bmp &= ~(1 << (port_bit_set - 1)); |
| } |
| |
| adapter->dp_bitmap = edma_default_group1_bmp; |
| |
| return 0; |
| } |
| |
| static int edma_change_group2_bmp(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| struct edma_common_info *edma_cinfo; |
| struct net_device *ndev; |
| struct phy_device *phydev; |
| int ret; |
| u32 prev_bmp, portid_bmp, port_bit, num_ports_enabled, port_id; |
| |
| ndev = edma_netdev[1]; |
| if (!ndev) { |
| pr_err("Netdevice for Group 1 does not exist\n"); |
| return -1; |
| } |
| |
| prev_bmp = edma_default_group2_bmp; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| if ((!write) || (prev_bmp == edma_default_group2_bmp)) |
| return ret; |
| |
| adapter = netdev_priv(ndev); |
| edma_cinfo = adapter->edma_cinfo; |
| |
| /* We ignore the bit for CPU Port */ |
| portid_bmp = edma_default_group2_bmp >> 1; |
| port_bit = ffs(portid_bmp); |
| if (port_bit > EDMA_MAX_PORTID_SUPPORTED) |
| return -1; |
| |
| /* If this group has no ports, |
| * we disable polling for the adapter, stop the queues and return |
| */ |
| if (!port_bit) { |
| adapter->dp_bitmap = edma_default_group2_bmp; |
| if (adapter->poll_required) { |
| mutex_lock(&adapter->poll_mutex); |
| adapter->poll_required = 0; |
| mutex_unlock(&adapter->poll_mutex); |
| adapter->link_state = __EDMA_LINKDOWN; |
| netif_carrier_off(ndev); |
| netif_tx_stop_all_queues(ndev); |
| } |
| return 0; |
| } |
| |
| /* Our array indexes are for 5 ports (0 - 4) */ |
| port_bit--; |
| |
| /* Do we have more ports in this group */ |
| num_ports_enabled = bitmap_weight((long unsigned int *)&portid_bmp, 32); |
| |
| /* If this group has more then one port, |
| * we disable polling for the adapter as link detection |
| * should be disabled, stop the phy state machine of previous |
| * phy adapter attached to group and start the queues |
| */ |
| if (num_ports_enabled > 1) { |
| mutex_lock(&adapter->poll_mutex); |
| if (adapter->poll_required) { |
| adapter->poll_required = 0; |
| if (adapter->phydev) { |
| port_id = edma_get_port_from_phydev( |
| adapter->phydev); |
| |
| /* We check if this phydev is in use by |
| * other Groups and stop phy machine only |
| * if that is NOT the case |
| */ |
| if (!edma_is_port_used(port_id)) { |
| if (phy_dev_state[port_id]) { |
| phy_stop_machine( |
| adapter->phydev); |
| phy_dev_state[port_id] = 0; |
| } |
| } |
| |
| adapter->phydev = NULL; |
| } |
| |
| /* Start the tx queues for this netdev |
| * with link detection disabled |
| */ |
| if (adapter->link_state == __EDMA_LINKDOWN) { |
| adapter->link_state = __EDMA_LINKUP; |
| netif_carrier_on(ndev); |
| netif_tx_start_all_queues(ndev); |
| } |
| } |
| mutex_unlock(&adapter->poll_mutex); |
| |
| goto set_bitmap; |
| } |
| |
| mutex_lock(&adapter->poll_mutex); |
| adapter->poll_required = adapter->poll_required_dynamic; |
| mutex_unlock(&adapter->poll_mutex); |
| |
| if (!adapter->poll_required) |
| goto set_bitmap; |
| |
| phydev = adapter->phydev; |
| |
| /* If this group has only one port, |
| * if phydev exists we start the phy state machine |
| * and if it doesn't we create a phydev and start it. |
| */ |
| if (edma_phydev[port_bit]) { |
| adapter->phydev = edma_phydev[port_bit]; |
| |
| /* If the Phy device has changed group, |
| * we need to reassign the netdev |
| */ |
| if (adapter->phydev->attached_dev != ndev) |
| adapter->phydev->attached_dev = ndev; |
| |
| if (!phy_dev_state[port_bit]) { |
| phy_start_machine(adapter->phydev); |
| phy_dev_state[port_bit] = 1; |
| set_bit(port_bit, |
| (long unsigned int *)&edma_link_detect_bmp); |
| } |
| } else { |
| snprintf(adapter->phy_id, |
| MII_BUS_ID_SIZE + 3, PHY_ID_FMT, |
| miibus_gb->id, |
| port_bit); |
| |
| adapter->phydev = phy_connect(ndev, |
| (const char *)adapter->phy_id, |
| &edma_adjust_link, |
| PHY_INTERFACE_MODE_SGMII); |
| |
| if (IS_ERR(adapter->phydev)) { |
| adapter->phydev = phydev; |
| pr_err("PHY attach FAIL for port %d", port_bit); |
| return -1; |
| } |
| |
| if (adapter->phydev->attached_dev != ndev) |
| adapter->phydev->attached_dev = ndev; |
| |
| edma_phydev[port_bit] = adapter->phydev; |
| phy_dev_state[port_bit] = 1; |
| set_bit(port_bit, (long unsigned int *)&edma_link_detect_bmp); |
| adapter->phydev->advertising |= |
| (ADVERTISED_Pause | |
| ADVERTISED_Asym_Pause); |
| adapter->phydev->supported |= |
| (SUPPORTED_Pause | |
| SUPPORTED_Asym_Pause); |
| phy_start(adapter->phydev); |
| phy_start_aneg(adapter->phydev); |
| } |
| |
| /* We check if this phydev is in use by other Groups |
| * and stop phy machine only if that is NOT the case |
| */ |
| if (phydev) { |
| port_id = edma_get_port_from_phydev(phydev); |
| if (!edma_is_port_used(port_id)) { |
| if (phy_dev_state[port_id]) { |
| phy_stop_machine(phydev); |
| phy_dev_state[port_id] = 0; |
| } |
| } |
| } |
| |
| mutex_lock(&adapter->poll_mutex); |
| adapter->poll_required = 1; |
| mutex_unlock(&adapter->poll_mutex); |
| adapter->link_state = __EDMA_LINKDOWN; |
| |
| set_bitmap: |
| while (portid_bmp) { |
| int port_bit_set = ffs(portid_bmp); |
| edma_cinfo->portid_netdev_lookup_tbl[port_bit_set] = ndev; |
| portid_bmp &= ~(1 << (port_bit_set - 1)); |
| } |
| |
| adapter->dp_bitmap = edma_default_group2_bmp; |
| |
| return 0; |
| } |
| |
| static int edma_disable_rss_func(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| struct edma_common_info *edma_cinfo; |
| struct edma_hw *hw; |
| int ret; |
| |
| if (!edma_netdev[0]) { |
| pr_err("Invalid Netdevice\n"); |
| return -1; |
| } |
| |
| adapter = netdev_priv(edma_netdev[0]); |
| edma_cinfo = adapter->edma_cinfo; |
| hw = &edma_cinfo->hw; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| |
| if ((!write) || (ret)) |
| return ret; |
| |
| switch (edma_disable_rss) { |
| case EDMA_RSS_DISABLE: |
| hw->rss_type = 0; |
| edma_write_reg(EDMA_REG_RSS_TYPE, hw->rss_type); |
| break; |
| case EDMA_RSS_ENABLE: |
| hw->rss_type = EDMA_RSS_TYPE_IPV4TCP | |
| EDMA_RSS_TYPE_IPV6_TCP | |
| EDMA_RSS_TYPE_IPV4_UDP | |
| EDMA_RSS_TYPE_IPV6UDP | |
| EDMA_RSS_TYPE_IPV4 | |
| EDMA_RSS_TYPE_IPV6; |
| edma_write_reg(EDMA_REG_RSS_TYPE, hw->rss_type); |
| break; |
| default: |
| pr_err("Invalid input\n"); |
| ret = -1; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int edma_weight_assigned_to_queues(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| int ret, queue_id, weight; |
| u32 reg_data, data, reg_addr; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| if (write) { |
| queue_id = edma_weight_assigned_to_q & EDMA_WRR_VID_SCTL_MASK; |
| if (queue_id < 0 || queue_id > 15) { |
| pr_err("queue_id not within desired range\n"); |
| return -EINVAL; |
| } |
| |
| weight = edma_weight_assigned_to_q >> EDMA_WRR_VID_SCTL_SHIFT; |
| if (weight < 0 || weight > 0xF) { |
| pr_err("queue_id not within desired range\n"); |
| return -EINVAL; |
| } |
| |
| data = weight << EDMA_WRR_SHIFT(queue_id); |
| |
| reg_addr = EDMA_REG_WRR_CTRL_Q0_Q3 + (queue_id & ~0x3); |
| edma_read_reg(reg_addr, ®_data); |
| reg_data &= ~(1 << EDMA_WRR_SHIFT(queue_id)); |
| edma_write_reg(reg_addr, data | reg_data); |
| } |
| |
| return ret; |
| } |
| |
| static int edma_queue_to_virtual_queue_map(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| int ret, queue_id, virtual_qid; |
| u32 reg_data, data, reg_addr; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| if (write) { |
| queue_id = edma_queue_to_virtual_q & EDMA_WRR_VID_SCTL_MASK; |
| if (queue_id < 0 || queue_id > 15) { |
| pr_err("queue_id not within desired range\n"); |
| return -EINVAL; |
| } |
| |
| virtual_qid = edma_queue_to_virtual_q >> |
| EDMA_WRR_VID_SCTL_SHIFT; |
| if (virtual_qid < 0 || virtual_qid > 7) { |
| pr_err("queue_id not within desired range\n"); |
| return -EINVAL; |
| } |
| |
| data = virtual_qid << EDMA_VQ_ID_SHIFT(queue_id); |
| |
| reg_addr = EDMA_REG_VQ_CTRL0 + ((queue_id & ~0x7) >> 1); |
| edma_read_reg(reg_addr, ®_data); |
| reg_data &= ~(0x7 << EDMA_VQ_ID_SHIFT(queue_id)); |
| edma_write_reg(reg_addr, data | reg_data); |
| } |
| |
| return ret; |
| } |
| |
| static int edma_disable_queue_stop_func(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| struct edma_adapter *adapter; |
| int ret; |
| |
| adapter = netdev_priv(edma_netdev[0]); |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| |
| return ret; |
| } |
| |
| static struct ctl_table edma_table[] = { |
| { |
| .procname = "default_lan_tag", |
| .data = &edma_default_ltag, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_change_default_lan_vlan |
| }, |
| { |
| .procname = "default_wan_tag", |
| .data = &edma_default_wtag, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_change_default_wan_vlan |
| }, |
| { |
| .procname = "weight_assigned_to_queues", |
| .data = &edma_weight_assigned_to_q, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_weight_assigned_to_queues |
| }, |
| { |
| .procname = "queue_to_virtual_queue_map", |
| .data = &edma_queue_to_virtual_q, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_queue_to_virtual_queue_map |
| }, |
| { |
| .procname = "enable_stp_rstp", |
| .data = &edma_enable_rstp, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_enable_stp_rstp |
| }, |
| { |
| .procname = "athr_hdr_eth_type", |
| .data = &edma_athr_hdr_eth_type, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_ath_hdr_eth_type |
| }, |
| { |
| .procname = "default_group1_vlan_tag", |
| .data = &edma_default_group1_vtag, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_change_group1_vtag |
| }, |
| { |
| .procname = "default_group2_vlan_tag", |
| .data = &edma_default_group2_vtag, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_change_group2_vtag |
| }, |
| { |
| .procname = "default_group3_vlan_tag", |
| .data = &edma_default_group3_vtag, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_change_group3_vtag |
| }, |
| { |
| .procname = "default_group4_vlan_tag", |
| .data = &edma_default_group4_vtag, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_change_group4_vtag |
| }, |
| { |
| .procname = "default_group5_vlan_tag", |
| .data = &edma_default_group5_vtag, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_change_group5_vtag |
| }, |
| { |
| .procname = "default_group1_bmp", |
| .data = &edma_default_group1_bmp, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_change_group1_bmp |
| }, |
| { |
| .procname = "default_group2_bmp", |
| .data = &edma_default_group2_bmp, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_change_group2_bmp |
| }, |
| { |
| .procname = "edma_disable_rss", |
| .data = &edma_disable_rss, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_disable_rss_func |
| }, |
| { |
| .procname = "dscp2ac", |
| .data = &edma_dscp2ac_tbl, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_dscp2ac_mapping_update |
| }, |
| { |
| .procname = "per_prec_stats_enable", |
| .data = &edma_per_prec_stats_enable, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_per_prec_stats_enable_handler, |
| }, |
| { |
| .procname = "per_prec_stats_reset", |
| .data = &edma_prec_stats_reset, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_prec_stats_reset_handler, |
| }, |
| { |
| .procname = "iad_stats_enable", |
| .data = &edma_iad_stats_enable, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_iad_stats_enable_handler, |
| }, |
| { |
| .procname = "iad_stats_reset", |
| .data = &edma_iad_stats_reset, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_iad_stats_reset_handler, |
| }, |
| { |
| .procname = "iad_print_flow_table", |
| .data = &edma_print_flow_table, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_print_flow_table_handler, |
| }, |
| { |
| .procname = "max_valid_ifd_usec", |
| .data = &edma_max_valid_ifd_usec, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_max_valid_ifd_usec_handler, |
| }, |
| { |
| .procname = "edma_disable_queue_stop", |
| .data = &edma_disable_queue_stop, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = edma_disable_queue_stop_func |
| }, |
| { |
| .procname = "core0_completion_affinity", |
| .data = &edma_core_completion_affinity[0], |
| .maxlen = sizeof(int), |
| .mode = 0444, |
| .proc_handler = proc_dointvec, |
| }, |
| { |
| .procname = "core1_completion_affinity", |
| .data = &edma_core_completion_affinity[1], |
| .maxlen = sizeof(int), |
| .mode = 0444, |
| .proc_handler = proc_dointvec, |
| }, |
| { |
| .procname = "core2_completion_affinity", |
| .data = &edma_core_completion_affinity[2], |
| .maxlen = sizeof(int), |
| .mode = 0444, |
| .proc_handler = proc_dointvec, |
| }, |
| { |
| .procname = "core3_completion_affinity", |
| .data = &edma_core_completion_affinity[3], |
| .maxlen = sizeof(int), |
| .mode = 0444, |
| .proc_handler = proc_dointvec, |
| }, |
| { |
| .procname = "num_cores", |
| .data = &edma_num_cores, |
| .maxlen = sizeof(int), |
| .mode = 0444, |
| .proc_handler = proc_dointvec, |
| }, |
| {} |
| }; |
| |
| /* edma_axi_netdev_ops |
| * Describe the operations supported by registered netdevices |
| * |
| * static const struct net_device_ops edma_axi_netdev_ops = { |
| * .ndo_open = edma_open, |
| * .ndo_stop = edma_close, |
| * .ndo_start_xmit = edma_xmit_frame, |
| * .ndo_set_mac_address = edma_set_mac_addr, |
| * } |
| */ |
| static const struct net_device_ops edma_axi_netdev_ops = { |
| .ndo_open = edma_open, |
| .ndo_stop = edma_close, |
| .ndo_start_xmit = edma_xmit, |
| .ndo_set_mac_address = edma_set_mac_addr, |
| .ndo_select_queue = edma_select_xps_queue, |
| #ifdef CONFIG_RFS_ACCEL |
| .ndo_rx_flow_steer = edma_rx_flow_steer, |
| .ndo_register_rfs_filter = edma_register_rfs_filter, |
| .ndo_get_default_vlan_tag = edma_get_default_vlan_tag, |
| #endif |
| .ndo_get_stats64 = edma_get_stats64, |
| .ndo_change_mtu = edma_change_mtu, |
| }; |
| |
| /* edma_axi_probe() |
| * Initialise an adapter identified by a platform_device structure. |
| * |
| * The OS initialization, configuring of the adapter private structure, |
| * and a hardware reset occur in the probe. |
| */ |
| static int edma_axi_probe(struct platform_device *pdev) |
| { |
| struct edma_common_info *edma_cinfo; |
| struct edma_hw *hw; |
| struct edma_adapter *adapter[EDMA_MAX_PORTID_SUPPORTED]; |
| struct resource *res; |
| struct device_node *np = pdev->dev.of_node; |
| struct device_node *pnp; |
| struct device_node *mdio_node = NULL; |
| struct platform_device *mdio_plat = NULL; |
| struct mii_bus *miibus = NULL; |
| struct edma_mdio_data *mdio_data = NULL; |
| int i, j, k, err = 0; |
| u32 portid_bmp; |
| int idx = 0, idx_mac = 0; |
| |
| if (CONFIG_NR_CPUS != EDMA_CPU_CORES_SUPPORTED) { |
| dev_err(&pdev->dev, "Invalid CPU Cores\n"); |
| return -EINVAL; |
| } |
| |
| if ((num_rxq != 4) && (num_rxq != 8)) { |
| dev_err(&pdev->dev, "Invalid RX queue, edma probe failed\n"); |
| return -EINVAL; |
| } |
| edma_cinfo = kzalloc(sizeof(struct edma_common_info), GFP_KERNEL); |
| if (!edma_cinfo) { |
| err = -ENOMEM; |
| goto err_alloc; |
| } |
| |
| edma_cinfo->pdev = pdev; |
| |
| of_property_read_u32(np, "qcom,num-gmac", &edma_cinfo->num_gmac); |
| if (edma_cinfo->num_gmac > EDMA_MAX_PORTID_SUPPORTED) { |
| pr_err("Invalid DTSI Entry for qcom,num_gmac\n"); |
| err = -EINVAL; |
| goto err_cinfo; |
| } |
| |
| /* Initialize the netdev array before allocation |
| * to avoid double free |
| */ |
| for (i = 0 ; i < edma_cinfo->num_gmac ; i++) |
| edma_netdev[i] = NULL; |
| |
| for (i = 0 ; i < edma_cinfo->num_gmac ; i++) { |
| edma_netdev[i] = alloc_etherdev_mqs(sizeof(struct edma_adapter), |
| EDMA_NETDEV_TX_QUEUE, EDMA_NETDEV_RX_QUEUE); |
| |
| if (!edma_netdev[i]) { |
| dev_err(&pdev->dev, |
| "net device alloc fails for index=%d\n", i); |
| err = -ENODEV; |
| goto err_ioremap; |
| } |
| |
| SET_NETDEV_DEV(edma_netdev[i], &pdev->dev); |
| platform_set_drvdata(pdev, edma_netdev[i]); |
| edma_cinfo->netdev[i] = edma_netdev[i]; |
| } |
| |
| /* Fill ring details */ |
| edma_cinfo->num_tx_queues = EDMA_MAX_TRANSMIT_QUEUE; |
| edma_cinfo->num_txq_per_core = (EDMA_MAX_TRANSMIT_QUEUE / 4); |
| edma_cinfo->tx_ring_count = EDMA_TX_RING_SIZE; |
| |
| /* Update num rx queues based on module parameter */ |
| edma_cinfo->num_rx_queues = num_rxq; |
| edma_cinfo->num_rxq_per_core = ((num_rxq == 4) ? 1 : 2); |
| |
| edma_cinfo->rx_ring_count = EDMA_RX_RING_SIZE; |
| |
| hw = &edma_cinfo->hw; |
| |
| /* Fill HW defaults */ |
| hw->tx_intr_mask = EDMA_TX_IMR_NORMAL_MASK; |
| hw->rx_intr_mask = EDMA_RX_IMR_NORMAL_MASK; |
| |
| edma_num_cores = EDMA_CPU_CORES_SUPPORTED; |
| |
| if (of_property_read_bool(np, "qcom,num-cores")) { |
| of_property_read_u32(np, "qcom,num-cores", |
| &edma_num_cores); |
| |
| if (!edma_num_cores || |
| edma_num_cores > EDMA_CPU_CORES_SUPPORTED) |
| edma_num_cores = EDMA_CPU_CORES_SUPPORTED; |
| } |
| |
| if (of_property_read_bool(np, "qcom,tx-ring-count")) |
| of_property_read_u32(np, "qcom,tx-ring-count", |
| &edma_cinfo->tx_ring_count); |
| |
| if (of_property_read_bool(np, "qcom,rx-ring-count")) |
| of_property_read_u32(np, "qcom,rx-ring-count", |
| &edma_cinfo->rx_ring_count); |
| |
| /* Set tx completion affinity map for the edma script */ |
| for (i = 0; i < EDMA_CPU_CORES_SUPPORTED; i++) { |
| edma_core_completion_affinity[i] = |
| edma_tx_affinity[edma_num_cores - 1][i]; |
| } |
| |
| of_property_read_u32(np, "qcom,page-mode", &edma_cinfo->page_mode); |
| of_property_read_u32(np, "qcom,rx-head-buf-size", |
| &hw->rx_head_buff_size); |
| |
| if (overwrite_mode) { |
| dev_info(&pdev->dev, "page mode overwritten"); |
| edma_cinfo->page_mode = page_mode; |
| } |
| |
| if (jumbo_mru) |
| edma_cinfo->fraglist_mode = 1; |
| |
| if (edma_cinfo->page_mode) |
| hw->rx_head_buff_size = EDMA_RX_HEAD_BUFF_SIZE_JUMBO; |
| else if (edma_cinfo->fraglist_mode) |
| hw->rx_head_buff_size = jumbo_mru; |
| else if (!hw->rx_head_buff_size) |
| hw->rx_head_buff_size = EDMA_RX_HEAD_BUFF_SIZE; |
| |
| hw->misc_intr_mask = 0; |
| hw->wol_intr_mask = 0; |
| |
| hw->intr_clear_type = EDMA_INTR_CLEAR_TYPE; |
| hw->intr_sw_idx_w = EDMA_INTR_SW_IDX_W_TYPE; |
| |
| /* configure RSS type to the different protocol that can be |
| * supported |
| */ |
| hw->rss_type = EDMA_RSS_TYPE_IPV4TCP | EDMA_RSS_TYPE_IPV6_TCP | |
| EDMA_RSS_TYPE_IPV4_UDP | EDMA_RSS_TYPE_IPV6UDP | |
| EDMA_RSS_TYPE_IPV4 | EDMA_RSS_TYPE_IPV6; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| |
| edma_cinfo->hw.hw_addr = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(edma_cinfo->hw.hw_addr)) { |
| err = PTR_ERR(edma_cinfo->hw.hw_addr); |
| goto err_ioremap; |
| } |
| |
| edma_hw_addr = (u32)edma_cinfo->hw.hw_addr; |
| |
| /* Parse tx queue interrupt number from device tree */ |
| for (i = 0; i < edma_cinfo->num_tx_queues; i++) |
| edma_cinfo->tx_irq[i] = platform_get_irq(pdev, i); |
| |
| /* Parse rx queue interrupt number from device tree |
| * Here we are setting j to point to the point where we |
| * left tx interrupt parsing(i.e 16) and run run the loop |
| * from 0 to 7 to parse rx interrupt number. |
| */ |
| for (i = 0, j = edma_cinfo->num_tx_queues, k = 0; |
| i < edma_cinfo->num_rx_queues; i++) { |
| edma_cinfo->rx_irq[k] = platform_get_irq(pdev, j); |
| k += ((num_rxq == 4) ? 2 : 1); |
| j += ((num_rxq == 4) ? 2 : 1); |
| } |
| |
| edma_cinfo->rx_head_buffer_len = edma_cinfo->hw.rx_head_buff_size; |
| edma_cinfo->rx_page_buffer_len = PAGE_SIZE; |
| |
| err = edma_alloc_queues_tx(edma_cinfo); |
| if (err) { |
| dev_err(&pdev->dev, "Allocation of TX queue failed\n"); |
| goto err_tx_qinit; |
| } |
| |
| err = edma_alloc_queues_rx(edma_cinfo); |
| if (err) { |
| dev_err(&pdev->dev, "Allocation of RX queue failed\n"); |
| goto err_rx_qinit; |
| } |
| |
| err = edma_alloc_tx_rings(edma_cinfo); |
| if (err) { |
| dev_err(&pdev->dev, "Allocation of TX resources failed\n"); |
| goto err_tx_rinit; |
| } |
| |
| err = edma_alloc_rx_rings(edma_cinfo); |
| if (err) { |
| dev_err(&pdev->dev, "Allocation of RX resources failed\n"); |
| goto err_rx_rinit; |
| } |
| |
| /* Initialize netdev and netdev bitmap for transmit descriptor rings */ |
| for (i = 0; i < edma_cinfo->num_tx_queues; i++) { |
| struct edma_tx_desc_ring *etdr = edma_cinfo->tpd_ring[i]; |
| int j; |
| |
| etdr->netdev_bmp = 0; |
| for (j = 0; j < EDMA_MAX_NETDEV_PER_QUEUE; j++) { |
| etdr->netdev[j] = NULL; |
| etdr->nq[j] = NULL; |
| } |
| } |
| |
| if (of_property_read_bool(np, "qcom,mdio-supported")) { |
| mdio_node = of_find_compatible_node(NULL, NULL, |
| "qcom,ipq40xx-mdio"); |
| if (!mdio_node) { |
| dev_err(&pdev->dev, "cannot find mdio node by phandle"); |
| err = -EIO; |
| goto err_mdiobus_init_fail; |
| } |
| |
| mdio_plat = of_find_device_by_node(mdio_node); |
| if (!mdio_plat) { |
| dev_err(&pdev->dev, |
| "cannot find platform device from mdio node"); |
| of_node_put(mdio_node); |
| err = -EIO; |
| goto err_mdiobus_init_fail; |
| } |
| |
| mdio_data = dev_get_drvdata(&mdio_plat->dev); |
| if (!mdio_data) { |
| dev_err(&pdev->dev, |
| "cannot get mii bus reference from device data"); |
| of_node_put(mdio_node); |
| err = -EIO; |
| goto err_mdiobus_init_fail; |
| } |
| |
| miibus = mdio_data->mii_bus; |
| miibus_gb = mdio_data->mii_bus; |
| } |
| |
| for_each_available_child_of_node(np, pnp) { |
| const char *mac_addr; |
| |
| /* this check is needed if parent and daughter dts have |
| * different number of gmac nodes |
| */ |
| if (idx_mac == edma_cinfo->num_gmac) { |
| of_node_put(np); |
| break; |
| } |
| |
| mac_addr = of_get_mac_address(pnp); |
| if (mac_addr) |
| memcpy(edma_netdev[idx_mac]->dev_addr, mac_addr, ETH_ALEN); |
| |
| idx_mac++; |
| } |
| |
| /* Populate the adapter structure register the netdevice */ |
| for (i = 0; i < edma_cinfo->num_gmac; i++) { |
| int k, m; |
| |
| adapter[i] = netdev_priv(edma_netdev[i]); |
| adapter[i]->netdev = edma_netdev[i]; |
| adapter[i]->pdev = pdev; |
| mutex_init(&adapter[i]->poll_mutex); |
| for (j = 0; j < CONFIG_NR_CPUS; j++) { |
| m = i % 2; |
| adapter[i]->tx_start_offset[j] = |
| ((j << EDMA_TX_CPU_START_SHIFT) + (m << 1)); |
| /* Share the queues with available net-devices. |
| * For instance , with 5 net-devices |
| * eth0/eth2/eth4 will share q0,q1,q4,q5,q8,q9,q12,q13 |
| * and eth1/eth3 will get the remaining. |
| */ |
| for (k = adapter[i]->tx_start_offset[j]; k < |
| (adapter[i]->tx_start_offset[j] + 2); k++) { |
| if (edma_fill_netdev(edma_cinfo, k, i, j)) { |
| pr_err("Netdev overflow Error\n"); |
| goto err_register; |
| } |
| } |
| } |
| |
| adapter[i]->edma_cinfo = edma_cinfo; |
| edma_netdev[i]->netdev_ops = &edma_axi_netdev_ops; |
| edma_netdev[i]->features = NETIF_F_HW_CSUM | NETIF_F_RXCSUM |
| | NETIF_F_HW_VLAN_CTAG_TX |
| | NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_SG | |
| NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_GRO; |
| edma_netdev[i]->hw_features = NETIF_F_HW_CSUM | NETIF_F_RXCSUM | |
| NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_TX |
| | NETIF_F_SG | NETIF_F_TSO | NETIF_F_TSO6 | |
| NETIF_F_GRO; |
| edma_netdev[i]->vlan_features = NETIF_F_HW_CSUM | NETIF_F_SG | |
| NETIF_F_TSO | NETIF_F_TSO6 | |
| NETIF_F_GRO; |
| edma_netdev[i]->wanted_features = NETIF_F_HW_CSUM | NETIF_F_SG | |
| NETIF_F_TSO | NETIF_F_TSO6 | |
| NETIF_F_GRO; |
| |
| #ifdef CONFIG_RFS_ACCEL |
| edma_netdev[i]->features |= NETIF_F_RXHASH | NETIF_F_NTUPLE; |
| edma_netdev[i]->hw_features |= NETIF_F_RXHASH | NETIF_F_NTUPLE; |
| edma_netdev[i]->vlan_features |= NETIF_F_RXHASH | NETIF_F_NTUPLE; |
| edma_netdev[i]->wanted_features |= NETIF_F_RXHASH | NETIF_F_NTUPLE; |
| #endif |
| if (edma_cinfo->fraglist_mode) { |
| edma_netdev[i]->features |= NETIF_F_FRAGLIST; |
| edma_netdev[i]->hw_features |= NETIF_F_FRAGLIST; |
| edma_netdev[i]->vlan_features |= NETIF_F_FRAGLIST; |
| edma_netdev[i]->wanted_features |= NETIF_F_FRAGLIST; |
| } |
| |
| edma_set_ethtool_ops(edma_netdev[i]); |
| |
| /* This just fill in some default MAC address |
| */ |
| if (!is_valid_ether_addr(edma_netdev[i]->dev_addr)) { |
| random_ether_addr(edma_netdev[i]->dev_addr); |
| pr_info("EDMA using MAC@ - using"); |
| pr_info("%02x:%02x:%02x:%02x:%02x:%02x\n", |
| *(edma_netdev[i]->dev_addr), |
| *(edma_netdev[i]->dev_addr + 1), |
| *(edma_netdev[i]->dev_addr + 2), |
| *(edma_netdev[i]->dev_addr + 3), |
| *(edma_netdev[i]->dev_addr + 4), |
| *(edma_netdev[i]->dev_addr + 5)); |
| } |
| |
| err = register_netdev(edma_netdev[i]); |
| if (err) |
| goto err_register; |
| |
| /* carrier off reporting is important to |
| * ethtool even BEFORE open |
| */ |
| netif_carrier_off(edma_netdev[i]); |
| |
| /* Allocate reverse irq cpu mapping structure for |
| * receive queues |
| */ |
| #ifdef CONFIG_RFS_ACCEL |
| edma_netdev[i]->rx_cpu_rmap = |
| alloc_irq_cpu_rmap(EDMA_NETDEV_RX_QUEUE); |
| if (!edma_netdev[i]->rx_cpu_rmap) { |
| err = -ENOMEM; |
| goto err_rmap_alloc_fail; |
| } |
| #endif |
| } |
| |
| for (i = 0; i < EDMA_MAX_PORTID_BITMAP_INDEX; i++) |
| edma_cinfo->portid_netdev_lookup_tbl[i] = NULL; |
| |
| for_each_available_child_of_node(np, pnp) { |
| const uint32_t *vlan_tag = NULL; |
| int len; |
| |
| /* this check is needed if parent and daughter dts have |
| * different number of gmac nodes |
| */ |
| if (idx == edma_cinfo->num_gmac) |
| break; |
| |
| /* Populate port-id to netdev lookup table */ |
| vlan_tag = of_get_property(pnp, "vlan-tag", &len); |
| if (!vlan_tag) { |
| pr_err("Vlan tag parsing Failed.\n"); |
| goto err_rmap_alloc_fail; |
| } |
| |
| adapter[idx]->default_vlan_tag = of_read_number(vlan_tag, 1); |
| vlan_tag++; |
| portid_bmp = of_read_number(vlan_tag, 1); |
| adapter[idx]->dp_bitmap = portid_bmp; |
| |
| portid_bmp = portid_bmp >> 1; /* We ignore CPU Port bit 0 */ |
| while (portid_bmp) { |
| int port_bit = ffs(portid_bmp); |
| |
| if (port_bit > EDMA_MAX_PORTID_SUPPORTED) |
| goto err_rmap_alloc_fail; |
| edma_cinfo->portid_netdev_lookup_tbl[port_bit] = |
| edma_netdev[idx]; |
| portid_bmp &= ~(1 << (port_bit - 1)); |
| } |
| |
| if (of_property_read_u32(pnp, "qcom,poll-required-dynamic", |
| &adapter[idx]->poll_required_dynamic)) |
| adapter[idx]->poll_required_dynamic = 0; |
| |
| if (!of_property_read_u32(pnp, "qcom,poll-required", |
| &adapter[idx]->poll_required)) { |
| if (adapter[idx]->poll_required) { |
| of_property_read_u32(pnp, "qcom,phy-mdio-addr", |
| &adapter[idx]->phy_mdio_addr); |
| of_property_read_u32(pnp, "qcom,forced-speed", |
| &adapter[idx]->forced_speed); |
| of_property_read_u32(pnp, "qcom,forced-duplex", |
| &adapter[idx]->forced_duplex); |
| |
| /* create a phyid using MDIO bus id |
| * and MDIO bus address |
| */ |
| snprintf(adapter[idx]->phy_id, |
| MII_BUS_ID_SIZE + 3, PHY_ID_FMT, |
| miibus->id, |
| adapter[idx]->phy_mdio_addr); |
| } |
| } else { |
| adapter[idx]->poll_required = 0; |
| adapter[idx]->forced_speed = SPEED_1000; |
| adapter[idx]->forced_duplex = DUPLEX_FULL; |
| } |
| |
| idx++; |
| } |
| |
| edma_cinfo->edma_ctl_table_hdr = register_net_sysctl(&init_net, |
| "net/edma", |
| edma_table); |
| if (!edma_cinfo->edma_ctl_table_hdr) { |
| dev_err(&pdev->dev, "edma sysctl table hdr not registered\n"); |
| goto err_unregister_sysctl_tbl; |
| } |
| |
| /* Disable all 16 Tx and 8 rx irqs */ |
| edma_irq_disable(edma_cinfo); |
| |
| err = edma_reset(edma_cinfo); |
| if (err) { |
| err = -EIO; |
| goto err_reset; |
| } |
| |
| /* populate per_core_info, do a napi_Add, request 16 TX irqs, |
| * 8 RX irqs, do a napi enable |
| */ |
| for (i = 0; i < CONFIG_NR_CPUS; i++) { |
| u8 rx_start; |
| |
| edma_cinfo->edma_percpu_info[i].napi.state = 0; |
| |
| netif_napi_add(edma_netdev[0], |
| &edma_cinfo->edma_percpu_info[i].napi, |
| edma_poll, 64); |
| napi_enable(&edma_cinfo->edma_percpu_info[i].napi); |
| edma_cinfo->edma_percpu_info[i].tx_mask = |
| edma_tx_mask[edma_num_cores - 1][i]; |
| edma_cinfo->edma_percpu_info[i].rx_mask = EDMA_RX_PER_CPU_MASK |
| << (i << EDMA_RX_PER_CPU_MASK_SHIFT); |
| edma_cinfo->edma_percpu_info[i].tx_comp_start = |
| edma_tx_comp_start[edma_num_cores - 1][i]; |
| edma_cinfo->edma_percpu_info[i].rx_start = |
| i << EDMA_RX_CPU_START_SHIFT; |
| rx_start = i << EDMA_RX_CPU_START_SHIFT; |
| edma_cinfo->edma_percpu_info[i].tx_status = 0; |
| edma_cinfo->edma_percpu_info[i].rx_status = 0; |
| edma_cinfo->edma_percpu_info[i].edma_cinfo = edma_cinfo; |
| |
| /* Request irq per core */ |
| for (j = edma_cinfo->edma_percpu_info[i].tx_comp_start; |
| j < edma_tx_comp_start[edma_num_cores - 1][i] + 4; j++) { |
| snprintf(&edma_tx_irq[j][0], sizeof(edma_tx_irq[0]), "edma_eth_tx%d", j); |
| err = request_irq(edma_cinfo->tx_irq[j], |
| edma_interrupt, |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 18, 21)) |
| IRQF_DISABLED, |
| #else |
| 0, |
| #endif |
| &edma_tx_irq[j][0], |
| &edma_cinfo->edma_percpu_info[i]); |
| if (err) |
| goto err_reset; |
| } |
| |
| for (j = edma_cinfo->edma_percpu_info[i].rx_start; |
| j < (rx_start + |
| ((edma_cinfo->num_rx_queues == 4) ? 1 : 2)); |
| j++) { |
| snprintf(&edma_rx_irq[j][0], sizeof(edma_rx_irq[0]), "edma_eth_rx%d", j); |
| err = request_irq(edma_cinfo->rx_irq[j], |
| edma_interrupt, |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 18, 21)) |
| IRQF_DISABLED, |
| #else |
| 0, |
| #endif |
| &edma_rx_irq[j][0], |
| &edma_cinfo->edma_percpu_info[i]); |
| if (err) |
| goto err_reset; |
| } |
| |
| #ifdef CONFIG_RFS_ACCEL |
| for (j = edma_cinfo->edma_percpu_info[i].rx_start; |
| j < rx_start + 2; j += 2) { |
| err = irq_cpu_rmap_add(edma_netdev[0]->rx_cpu_rmap, |
| edma_cinfo->rx_irq[j]); |
| if (err) |
| goto err_rmap_add_fail; |
| } |
| #endif |
| } |
| |
| /* Used to clear interrupt status, allocate rx buffer, |
| * configure edma descriptors registers |
| */ |
| err = edma_configure(edma_cinfo); |
| if (err) { |
| err = -EIO; |
| goto err_configure; |
| } |
| |
| /* Configure RSS indirection table. |
| * RSS Indirection table maps 128 hash values to EDMA HW RX queues |
| * 128 hash will be configured in the following |
| * pattern: hash{0,1,2,3} = {Q0,Q2,Q4,Q6} respectively |
| * and so on */ |
| for (i = 0; i < EDMA_NUM_IDT; i++) |
| edma_write_reg(EDMA_REG_RSS_IDT(i), |
| edma_idt_tbl[edma_num_cores - 1][i]); |
| |
| /* Configure load balance mapping table. |
| * 4 table entry will be configured according to the |
| * following pattern: load_balance{0,1,2,3} = {Q0,Q1,Q3,Q4} |
| * respectively. |
| */ |
| edma_write_reg(EDMA_REG_LB_RING, EDMA_LB_REG_VALUE); |
| |
| /* Configure Virtual queue for Tx rings |
| * User can also change this value runtime through |
| * a sysctl |
| */ |
| edma_write_reg(EDMA_REG_VQ_CTRL0, EDMA_VQ_REG_VALUE); |
| edma_write_reg(EDMA_REG_VQ_CTRL1, EDMA_VQ_REG_VALUE); |
| |
| /* Configure Max AXI Burst write size to 128 bytes*/ |
| edma_write_reg(EDMA_REG_AXIW_CTRL_MAXWRSIZE, |
| EDMA_AXIW_MAXWRSIZE_VALUE); |
| |
| /* Enable All 16 tx and 8 rx irq mask */ |
| edma_irq_enable(edma_cinfo); |
| edma_enable_tx_ctrl(&edma_cinfo->hw); |
| edma_enable_rx_ctrl(&edma_cinfo->hw); |
| |
| for (i = 0; i < edma_cinfo->num_gmac; i++) { |
| u32 port_id; |
| if (!(adapter[i]->poll_required)) { |
| adapter[i]->phydev = NULL; |
| } else { |
| adapter[i]->phydev = |
| phy_connect(edma_netdev[i], |
| (const char *)adapter[i]->phy_id, |
| &edma_adjust_link, |
| PHY_INTERFACE_MODE_SGMII); |
| if (IS_ERR(adapter[i]->phydev)) { |
| dev_dbg(&pdev->dev, "PHY attach FAIL"); |
| err = -EIO; |
| goto edma_phy_attach_fail; |
| } else { |
| adapter[i]->phydev->advertising |= |
| ADVERTISED_Pause | |
| ADVERTISED_Asym_Pause; |
| adapter[i]->phydev->supported |= |
| SUPPORTED_Pause | |
| SUPPORTED_Asym_Pause; |
| portid_bmp = adapter[i]->dp_bitmap >> 1; |
| port_id = ffs(portid_bmp); |
| edma_phydev[port_id - 1] = adapter[i]->phydev; |
| phy_dev_state[port_id - 1] = 1; |
| } |
| } |
| } |
| |
| spin_lock_init(&edma_cinfo->stats_lock); |
| |
| init_timer(&edma_stats_timer); |
| edma_stats_timer.expires = jiffies + 1*HZ; |
| edma_stats_timer.data = (unsigned long)edma_cinfo; |
| edma_stats_timer.function = edma_statistics_timer; /* timer handler */ |
| add_timer(&edma_stats_timer); |
| |
| /* |
| * Initialize dscp2ac mapping table |
| */ |
| for (i = 0 ; i < EDMA_PRECEDENCE_MAX ; i++) |
| edma_dscp2ac_tbl[i] = EDMA_AC_BE; |
| |
| memset(edma_flow_tbl, 0, sizeof(struct edma_flow_attrib) * EDMA_MAX_IAD_FLOW_STATS_SUPPORTED); |
| |
| return 0; |
| |
| edma_phy_attach_fail: |
| miibus = NULL; |
| err_configure: |
| #ifdef CONFIG_RFS_ACCEL |
| for (i = 0; i < edma_cinfo->num_gmac; i++) { |
| free_irq_cpu_rmap(adapter[i]->netdev->rx_cpu_rmap); |
| adapter[i]->netdev->rx_cpu_rmap = NULL; |
| } |
| #endif |
| err_rmap_add_fail: |
| edma_free_irqs(adapter[0]); |
| for (i = 0; i < CONFIG_NR_CPUS; i++) |
| napi_disable(&edma_cinfo->edma_percpu_info[i].napi); |
| err_reset: |
| err_unregister_sysctl_tbl: |
| err_rmap_alloc_fail: |
| for (i = 0; i < edma_cinfo->num_gmac; i++) |
| unregister_netdev(edma_netdev[i]); |
| err_register: |
| err_mdiobus_init_fail: |
| edma_free_rx_rings(edma_cinfo); |
| err_rx_rinit: |
| edma_free_tx_rings(edma_cinfo); |
| err_tx_rinit: |
| edma_free_queues(edma_cinfo); |
| err_rx_qinit: |
| err_tx_qinit: |
| iounmap(edma_cinfo->hw.hw_addr); |
| err_ioremap: |
| for (i = 0; i < edma_cinfo->num_gmac; i++) { |
| if (edma_netdev[i]) |
| free_netdev(edma_netdev[i]); |
| } |
| err_cinfo: |
| kfree(edma_cinfo); |
| err_alloc: |
| return err; |
| } |
| |
| /* edma_axi_remove() |
| * Device Removal Routine |
| * |
| * edma_axi_remove is called by the platform subsystem to alert the driver |
| * that it should release a platform device. |
| */ |
| static int edma_axi_remove(struct platform_device *pdev) |
| { |
| struct edma_adapter *adapter = netdev_priv(edma_netdev[0]); |
| struct edma_common_info *edma_cinfo = adapter->edma_cinfo; |
| struct edma_hw *hw = &edma_cinfo->hw; |
| int i; |
| |
| for (i = 0; i < edma_cinfo->num_gmac; i++) |
| unregister_netdev(edma_netdev[i]); |
| |
| edma_stop_rx_tx(hw); |
| for (i = 0; i < CONFIG_NR_CPUS; i++) |
| napi_disable(&edma_cinfo->edma_percpu_info[i].napi); |
| |
| edma_irq_disable(edma_cinfo); |
| edma_write_reg(EDMA_REG_RX_ISR, 0xff); |
| edma_write_reg(EDMA_REG_TX_ISR, 0xffff); |
| #ifdef CONFIG_RFS_ACCEL |
| for (i = 0; i < edma_cinfo->num_gmac; i++) { |
| free_irq_cpu_rmap(edma_netdev[0]->rx_cpu_rmap); |
| edma_netdev[0]->rx_cpu_rmap = NULL; |
| } |
| #endif |
| |
| for (i = 0; i < EDMA_MAX_PORTID_SUPPORTED; i++) { |
| if (edma_phydev[i]) |
| phy_disconnect(edma_phydev[i]); |
| } |
| |
| del_timer_sync(&edma_stats_timer); |
| edma_free_irqs(adapter); |
| unregister_net_sysctl_table(edma_cinfo->edma_ctl_table_hdr); |
| edma_free_tx_resources(edma_cinfo); |
| edma_free_rx_resources(edma_cinfo); |
| edma_free_tx_rings(edma_cinfo); |
| edma_free_rx_rings(edma_cinfo); |
| edma_free_queues(edma_cinfo); |
| for (i = 0; i < edma_cinfo->num_gmac; i++) |
| free_netdev(edma_netdev[i]); |
| |
| kfree(edma_cinfo); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id edma_of_mtable[] = { |
| {.compatible = "qcom,ess-edma" }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(of, edma_of_mtable); |
| |
| static struct platform_driver edma_axi_driver = { |
| .driver = { |
| .name = edma_axi_driver_name, |
| .of_match_table = edma_of_mtable, |
| }, |
| .probe = edma_axi_probe, |
| .remove = edma_axi_remove, |
| }; |
| |
| module_platform_driver(edma_axi_driver); |
| |
| MODULE_DESCRIPTION("QCA ESS EDMA driver"); |
| MODULE_LICENSE("Dual BSD/GPL"); |