| // SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) |
| /* |
| * Copyright (c) 2003-2012 Broadcom Corporation |
| * All Rights Reserved |
| */ |
| |
| #include <linux/phy.h> |
| #include <linux/delay.h> |
| #include <linux/netdevice.h> |
| #include <linux/smp.h> |
| #include <linux/ethtool.h> |
| #include <linux/module.h> |
| #include <linux/etherdevice.h> |
| #include <linux/skbuff.h> |
| #include <linux/jiffies.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| |
| #include <asm/mipsregs.h> |
| /* |
| * fmn.h - For FMN credit configuration and registering fmn_handler. |
| * FMN is communication mechanism that allows processing agents within |
| * XLR/XLS to communicate each other. |
| */ |
| #include <asm/netlogic/xlr/fmn.h> |
| |
| #include "platform_net.h" |
| #include "xlr_net.h" |
| |
| /* |
| * The readl/writel implementation byteswaps on XLR/XLS, so |
| * we need to use __raw_ IO to read the NAE registers |
| * because they are in the big-endian MMIO area on the SoC. |
| */ |
| static inline void xlr_nae_wreg(u32 __iomem *base, unsigned int reg, u32 val) |
| { |
| __raw_writel(val, base + reg); |
| } |
| |
| static inline u32 xlr_nae_rdreg(u32 __iomem *base, unsigned int reg) |
| { |
| return __raw_readl(base + reg); |
| } |
| |
| static inline void xlr_reg_update(u32 *base_addr, u32 off, u32 val, u32 mask) |
| { |
| u32 tmp; |
| |
| tmp = xlr_nae_rdreg(base_addr, off); |
| xlr_nae_wreg(base_addr, off, (tmp & ~mask) | (val & mask)); |
| } |
| |
| #define MAC_SKB_BACK_PTR_SIZE SMP_CACHE_BYTES |
| |
| static int send_to_rfr_fifo(struct xlr_net_priv *priv, void *addr) |
| { |
| struct nlm_fmn_msg msg; |
| int ret = 0, num_try = 0, stnid; |
| unsigned long paddr, mflags; |
| |
| paddr = virt_to_bus(addr); |
| msg.msg0 = (u64)paddr & 0xffffffffe0ULL; |
| msg.msg1 = 0; |
| msg.msg2 = 0; |
| msg.msg3 = 0; |
| stnid = priv->nd->rfr_station; |
| do { |
| mflags = nlm_cop2_enable_irqsave(); |
| ret = nlm_fmn_send(1, 0, stnid, &msg); |
| nlm_cop2_disable_irqrestore(mflags); |
| if (ret == 0) |
| return 0; |
| } while (++num_try < 10000); |
| |
| netdev_err(priv->ndev, "Send to RFR failed in RX path\n"); |
| return ret; |
| } |
| |
| static inline unsigned char *xlr_alloc_skb(void) |
| { |
| struct sk_buff *skb; |
| int buf_len = sizeof(struct sk_buff *); |
| unsigned char *skb_data; |
| |
| /* skb->data is cache aligned */ |
| skb = alloc_skb(XLR_RX_BUF_SIZE, GFP_ATOMIC); |
| if (!skb) |
| return NULL; |
| skb_data = skb->data; |
| skb_put(skb, MAC_SKB_BACK_PTR_SIZE); |
| skb_pull(skb, MAC_SKB_BACK_PTR_SIZE); |
| memcpy(skb_data, &skb, buf_len); |
| |
| return skb->data; |
| } |
| |
| static void xlr_net_fmn_handler(int bkt, int src_stnid, int size, int code, |
| struct nlm_fmn_msg *msg, void *arg) |
| { |
| struct sk_buff *skb; |
| void *skb_data = NULL; |
| struct net_device *ndev; |
| struct xlr_net_priv *priv; |
| u32 port, length; |
| unsigned char *addr; |
| struct xlr_adapter *adapter = arg; |
| |
| length = (msg->msg0 >> 40) & 0x3fff; |
| if (length == 0) { |
| addr = bus_to_virt(msg->msg0 & 0xffffffffffULL); |
| addr = addr - MAC_SKB_BACK_PTR_SIZE; |
| skb = (struct sk_buff *)(*(unsigned long *)addr); |
| dev_kfree_skb_any((struct sk_buff *)addr); |
| } else { |
| addr = (unsigned char *) |
| bus_to_virt(msg->msg0 & 0xffffffffe0ULL); |
| length = length - BYTE_OFFSET - MAC_CRC_LEN; |
| port = ((int)msg->msg0) & 0x0f; |
| addr = addr - MAC_SKB_BACK_PTR_SIZE; |
| skb = (struct sk_buff *)(*(unsigned long *)addr); |
| skb->dev = adapter->netdev[port]; |
| if (!skb->dev) |
| return; |
| ndev = skb->dev; |
| priv = netdev_priv(ndev); |
| |
| /* 16 byte IP header align */ |
| skb_reserve(skb, BYTE_OFFSET); |
| skb_put(skb, length); |
| skb->protocol = eth_type_trans(skb, skb->dev); |
| netif_rx(skb); |
| /* Fill rx ring */ |
| skb_data = xlr_alloc_skb(); |
| if (skb_data) |
| send_to_rfr_fifo(priv, skb_data); |
| } |
| } |
| |
| static struct phy_device *xlr_get_phydev(struct xlr_net_priv *priv) |
| { |
| return mdiobus_get_phy(priv->mii_bus, priv->phy_addr); |
| } |
| |
| /* |
| * Ethtool operation |
| */ |
| static int xlr_get_link_ksettings(struct net_device *ndev, |
| struct ethtool_link_ksettings *ecmd) |
| { |
| struct xlr_net_priv *priv = netdev_priv(ndev); |
| struct phy_device *phydev = xlr_get_phydev(priv); |
| |
| if (!phydev) |
| return -ENODEV; |
| |
| phy_ethtool_ksettings_get(phydev, ecmd); |
| |
| return 0; |
| } |
| |
| static int xlr_set_link_ksettings(struct net_device *ndev, |
| const struct ethtool_link_ksettings *ecmd) |
| { |
| struct xlr_net_priv *priv = netdev_priv(ndev); |
| struct phy_device *phydev = xlr_get_phydev(priv); |
| |
| if (!phydev) |
| return -ENODEV; |
| return phy_ethtool_ksettings_set(phydev, ecmd); |
| } |
| |
| static const struct ethtool_ops xlr_ethtool_ops = { |
| .get_link_ksettings = xlr_get_link_ksettings, |
| .set_link_ksettings = xlr_set_link_ksettings, |
| }; |
| |
| /* |
| * Net operations |
| */ |
| static int xlr_net_fill_rx_ring(struct net_device *ndev) |
| { |
| void *skb_data; |
| struct xlr_net_priv *priv = netdev_priv(ndev); |
| int i; |
| |
| for (i = 0; i < MAX_FRIN_SPILL / 4; i++) { |
| skb_data = xlr_alloc_skb(); |
| if (!skb_data) { |
| netdev_err(ndev, "SKB allocation failed\n"); |
| return -ENOMEM; |
| } |
| send_to_rfr_fifo(priv, skb_data); |
| } |
| netdev_info(ndev, "Rx ring setup done\n"); |
| return 0; |
| } |
| |
| static int xlr_net_open(struct net_device *ndev) |
| { |
| u32 err; |
| struct xlr_net_priv *priv = netdev_priv(ndev); |
| struct phy_device *phydev = xlr_get_phydev(priv); |
| |
| /* schedule a link state check */ |
| phy_start(phydev); |
| |
| err = phy_start_aneg(phydev); |
| if (err) { |
| pr_err("Autoneg failed\n"); |
| return err; |
| } |
| /* Setup the speed from PHY to internal reg*/ |
| xlr_set_gmac_speed(priv); |
| |
| netif_tx_start_all_queues(ndev); |
| |
| return 0; |
| } |
| |
| static int xlr_net_stop(struct net_device *ndev) |
| { |
| struct xlr_net_priv *priv = netdev_priv(ndev); |
| struct phy_device *phydev = xlr_get_phydev(priv); |
| |
| phy_stop(phydev); |
| netif_tx_stop_all_queues(ndev); |
| return 0; |
| } |
| |
| static void xlr_make_tx_desc(struct nlm_fmn_msg *msg, unsigned long addr, |
| struct sk_buff *skb) |
| { |
| unsigned long physkb = virt_to_phys(skb); |
| int cpu_core = nlm_core_id(); |
| int fr_stn_id = cpu_core * 8 + XLR_FB_STN; /* FB to 6th bucket */ |
| |
| msg->msg0 = (((u64)1 << 63) | /* End of packet descriptor */ |
| ((u64)127 << 54) | /* No Free back */ |
| (u64)skb->len << 40 | /* Length of data */ |
| ((u64)addr)); |
| msg->msg1 = (((u64)1 << 63) | |
| ((u64)fr_stn_id << 54) | /* Free back id */ |
| (u64)0 << 40 | /* Set len to 0 */ |
| ((u64)physkb & 0xffffffff)); /* 32bit address */ |
| msg->msg2 = 0; |
| msg->msg3 = 0; |
| } |
| |
| static netdev_tx_t xlr_net_start_xmit(struct sk_buff *skb, |
| struct net_device *ndev) |
| { |
| struct nlm_fmn_msg msg; |
| struct xlr_net_priv *priv = netdev_priv(ndev); |
| int ret; |
| u32 flags; |
| |
| xlr_make_tx_desc(&msg, virt_to_phys(skb->data), skb); |
| flags = nlm_cop2_enable_irqsave(); |
| ret = nlm_fmn_send(2, 0, priv->tx_stnid, &msg); |
| nlm_cop2_disable_irqrestore(flags); |
| if (ret) |
| dev_kfree_skb_any(skb); |
| return NETDEV_TX_OK; |
| } |
| |
| static void xlr_hw_set_mac_addr(struct net_device *ndev) |
| { |
| struct xlr_net_priv *priv = netdev_priv(ndev); |
| |
| /* set mac station address */ |
| xlr_nae_wreg(priv->base_addr, R_MAC_ADDR0, |
| ((ndev->dev_addr[5] << 24) | (ndev->dev_addr[4] << 16) | |
| (ndev->dev_addr[3] << 8) | (ndev->dev_addr[2]))); |
| xlr_nae_wreg(priv->base_addr, R_MAC_ADDR0 + 1, |
| ((ndev->dev_addr[1] << 24) | (ndev->dev_addr[0] << 16))); |
| |
| xlr_nae_wreg(priv->base_addr, R_MAC_ADDR_MASK2, 0xffffffff); |
| xlr_nae_wreg(priv->base_addr, R_MAC_ADDR_MASK2 + 1, 0xffffffff); |
| xlr_nae_wreg(priv->base_addr, R_MAC_ADDR_MASK3, 0xffffffff); |
| xlr_nae_wreg(priv->base_addr, R_MAC_ADDR_MASK3 + 1, 0xffffffff); |
| |
| xlr_nae_wreg(priv->base_addr, R_MAC_FILTER_CONFIG, |
| (1 << O_MAC_FILTER_CONFIG__BROADCAST_EN) | |
| (1 << O_MAC_FILTER_CONFIG__ALL_MCAST_EN) | |
| (1 << O_MAC_FILTER_CONFIG__MAC_ADDR0_VALID)); |
| |
| if (priv->nd->phy_interface == PHY_INTERFACE_MODE_RGMII || |
| priv->nd->phy_interface == PHY_INTERFACE_MODE_SGMII) |
| xlr_reg_update(priv->base_addr, R_IPG_IFG, MAC_B2B_IPG, 0x7f); |
| } |
| |
| static int xlr_net_set_mac_addr(struct net_device *ndev, void *data) |
| { |
| int err; |
| |
| err = eth_mac_addr(ndev, data); |
| if (err) |
| return err; |
| xlr_hw_set_mac_addr(ndev); |
| return 0; |
| } |
| |
| static void xlr_set_rx_mode(struct net_device *ndev) |
| { |
| struct xlr_net_priv *priv = netdev_priv(ndev); |
| u32 regval; |
| |
| regval = xlr_nae_rdreg(priv->base_addr, R_MAC_FILTER_CONFIG); |
| |
| if (ndev->flags & IFF_PROMISC) { |
| regval |= (1 << O_MAC_FILTER_CONFIG__BROADCAST_EN) | |
| (1 << O_MAC_FILTER_CONFIG__PAUSE_FRAME_EN) | |
| (1 << O_MAC_FILTER_CONFIG__ALL_MCAST_EN) | |
| (1 << O_MAC_FILTER_CONFIG__ALL_UCAST_EN); |
| } else { |
| regval &= ~((1 << O_MAC_FILTER_CONFIG__PAUSE_FRAME_EN) | |
| (1 << O_MAC_FILTER_CONFIG__ALL_UCAST_EN)); |
| } |
| |
| xlr_nae_wreg(priv->base_addr, R_MAC_FILTER_CONFIG, regval); |
| } |
| |
| static void xlr_stats(struct net_device *ndev, struct rtnl_link_stats64 *stats) |
| { |
| struct xlr_net_priv *priv = netdev_priv(ndev); |
| |
| stats->rx_packets = xlr_nae_rdreg(priv->base_addr, RX_PACKET_COUNTER); |
| stats->tx_packets = xlr_nae_rdreg(priv->base_addr, TX_PACKET_COUNTER); |
| stats->rx_bytes = xlr_nae_rdreg(priv->base_addr, RX_BYTE_COUNTER); |
| stats->tx_bytes = xlr_nae_rdreg(priv->base_addr, TX_BYTE_COUNTER); |
| stats->tx_errors = xlr_nae_rdreg(priv->base_addr, TX_FCS_ERROR_COUNTER); |
| stats->rx_dropped = xlr_nae_rdreg(priv->base_addr, |
| RX_DROP_PACKET_COUNTER); |
| stats->tx_dropped = xlr_nae_rdreg(priv->base_addr, |
| TX_DROP_FRAME_COUNTER); |
| |
| stats->multicast = xlr_nae_rdreg(priv->base_addr, |
| RX_MULTICAST_PACKET_COUNTER); |
| stats->collisions = xlr_nae_rdreg(priv->base_addr, |
| TX_TOTAL_COLLISION_COUNTER); |
| |
| stats->rx_length_errors = xlr_nae_rdreg(priv->base_addr, |
| RX_FRAME_LENGTH_ERROR_COUNTER); |
| stats->rx_over_errors = xlr_nae_rdreg(priv->base_addr, |
| RX_DROP_PACKET_COUNTER); |
| stats->rx_crc_errors = xlr_nae_rdreg(priv->base_addr, |
| RX_FCS_ERROR_COUNTER); |
| stats->rx_frame_errors = xlr_nae_rdreg(priv->base_addr, |
| RX_ALIGNMENT_ERROR_COUNTER); |
| |
| stats->rx_fifo_errors = xlr_nae_rdreg(priv->base_addr, |
| RX_DROP_PACKET_COUNTER); |
| stats->rx_missed_errors = xlr_nae_rdreg(priv->base_addr, |
| RX_CARRIER_SENSE_ERROR_COUNTER); |
| |
| stats->rx_errors = (stats->rx_over_errors + stats->rx_crc_errors + |
| stats->rx_frame_errors + stats->rx_fifo_errors + |
| stats->rx_missed_errors); |
| |
| stats->tx_aborted_errors = xlr_nae_rdreg(priv->base_addr, |
| TX_EXCESSIVE_COLLISION_PACKET_COUNTER); |
| stats->tx_carrier_errors = xlr_nae_rdreg(priv->base_addr, |
| TX_DROP_FRAME_COUNTER); |
| stats->tx_fifo_errors = xlr_nae_rdreg(priv->base_addr, |
| TX_DROP_FRAME_COUNTER); |
| } |
| |
| static const struct net_device_ops xlr_netdev_ops = { |
| .ndo_open = xlr_net_open, |
| .ndo_stop = xlr_net_stop, |
| .ndo_start_xmit = xlr_net_start_xmit, |
| .ndo_select_queue = dev_pick_tx_cpu_id, |
| .ndo_set_mac_address = xlr_net_set_mac_addr, |
| .ndo_set_rx_mode = xlr_set_rx_mode, |
| .ndo_get_stats64 = xlr_stats, |
| }; |
| |
| /* |
| * Gmac init |
| */ |
| static void *xlr_config_spill(struct xlr_net_priv *priv, int reg_start_0, |
| int reg_start_1, int reg_size, int size) |
| { |
| void *spill; |
| u32 *base; |
| unsigned long phys_addr; |
| u32 spill_size; |
| |
| base = priv->base_addr; |
| spill_size = size; |
| spill = kmalloc(spill_size + SMP_CACHE_BYTES, GFP_ATOMIC); |
| if (!spill) { |
| pr_err("Unable to allocate memory for spill area!\n"); |
| return ZERO_SIZE_PTR; |
| } |
| |
| spill = PTR_ALIGN(spill, SMP_CACHE_BYTES); |
| phys_addr = virt_to_phys(spill); |
| dev_dbg(&priv->ndev->dev, "Allocated spill %d bytes at %lx\n", |
| size, phys_addr); |
| xlr_nae_wreg(base, reg_start_0, (phys_addr >> 5) & 0xffffffff); |
| xlr_nae_wreg(base, reg_start_1, ((u64)phys_addr >> 37) & 0x07); |
| xlr_nae_wreg(base, reg_size, spill_size); |
| |
| return spill; |
| } |
| |
| /* |
| * Configure the 6 FIFO's that are used by the network accelarator to |
| * communicate with the rest of the XLx device. 4 of the FIFO's are for |
| * packets from NA --> cpu (called Class FIFO's) and 2 are for feeding |
| * the NA with free descriptors. |
| */ |
| static void xlr_config_fifo_spill_area(struct xlr_net_priv *priv) |
| { |
| priv->frin_spill = xlr_config_spill(priv, |
| R_REG_FRIN_SPILL_MEM_START_0, |
| R_REG_FRIN_SPILL_MEM_START_1, |
| R_REG_FRIN_SPILL_MEM_SIZE, |
| MAX_FRIN_SPILL * sizeof(u64)); |
| priv->frout_spill = xlr_config_spill(priv, |
| R_FROUT_SPILL_MEM_START_0, |
| R_FROUT_SPILL_MEM_START_1, |
| R_FROUT_SPILL_MEM_SIZE, |
| MAX_FROUT_SPILL * sizeof(u64)); |
| priv->class_0_spill = xlr_config_spill(priv, |
| R_CLASS0_SPILL_MEM_START_0, |
| R_CLASS0_SPILL_MEM_START_1, |
| R_CLASS0_SPILL_MEM_SIZE, |
| MAX_CLASS_0_SPILL * sizeof(u64)); |
| priv->class_1_spill = xlr_config_spill(priv, |
| R_CLASS1_SPILL_MEM_START_0, |
| R_CLASS1_SPILL_MEM_START_1, |
| R_CLASS1_SPILL_MEM_SIZE, |
| MAX_CLASS_1_SPILL * sizeof(u64)); |
| priv->class_2_spill = xlr_config_spill(priv, |
| R_CLASS2_SPILL_MEM_START_0, |
| R_CLASS2_SPILL_MEM_START_1, |
| R_CLASS2_SPILL_MEM_SIZE, |
| MAX_CLASS_2_SPILL * sizeof(u64)); |
| priv->class_3_spill = xlr_config_spill(priv, |
| R_CLASS3_SPILL_MEM_START_0, |
| R_CLASS3_SPILL_MEM_START_1, |
| R_CLASS3_SPILL_MEM_SIZE, |
| MAX_CLASS_3_SPILL * sizeof(u64)); |
| } |
| |
| /* |
| * Configure PDE to Round-Robin distribution of packets to the |
| * available cpu |
| */ |
| static void xlr_config_pde(struct xlr_net_priv *priv) |
| { |
| int i = 0; |
| u64 bkt_map = 0; |
| |
| /* Each core has 8 buckets(station) */ |
| for (i = 0; i < hweight32(priv->nd->cpu_mask); i++) |
| bkt_map |= (0xff << (i * 8)); |
| |
| xlr_nae_wreg(priv->base_addr, R_PDE_CLASS_0, (bkt_map & 0xffffffff)); |
| xlr_nae_wreg(priv->base_addr, R_PDE_CLASS_0 + 1, |
| ((bkt_map >> 32) & 0xffffffff)); |
| |
| xlr_nae_wreg(priv->base_addr, R_PDE_CLASS_1, (bkt_map & 0xffffffff)); |
| xlr_nae_wreg(priv->base_addr, R_PDE_CLASS_1 + 1, |
| ((bkt_map >> 32) & 0xffffffff)); |
| |
| xlr_nae_wreg(priv->base_addr, R_PDE_CLASS_2, (bkt_map & 0xffffffff)); |
| xlr_nae_wreg(priv->base_addr, R_PDE_CLASS_2 + 1, |
| ((bkt_map >> 32) & 0xffffffff)); |
| |
| xlr_nae_wreg(priv->base_addr, R_PDE_CLASS_3, (bkt_map & 0xffffffff)); |
| xlr_nae_wreg(priv->base_addr, R_PDE_CLASS_3 + 1, |
| ((bkt_map >> 32) & 0xffffffff)); |
| } |
| |
| /* |
| * Setup the Message ring credits, bucket size and other |
| * common configuration |
| */ |
| static int xlr_config_common(struct xlr_net_priv *priv) |
| { |
| struct xlr_fmn_info *gmac = priv->nd->gmac_fmn_info; |
| int start_stn_id = gmac->start_stn_id; |
| int end_stn_id = gmac->end_stn_id; |
| int *bucket_size = priv->nd->bucket_size; |
| int i, j, err; |
| |
| /* Setting non-core MsgBktSize(0x321 - 0x325) */ |
| for (i = start_stn_id; i <= end_stn_id; i++) { |
| xlr_nae_wreg(priv->base_addr, |
| R_GMAC_RFR0_BUCKET_SIZE + i - start_stn_id, |
| bucket_size[i]); |
| } |
| |
| /* |
| * Setting non-core Credit counter register |
| * Distributing Gmac's credit to CPU's |
| */ |
| for (i = 0; i < 8; i++) { |
| for (j = 0; j < 8; j++) |
| xlr_nae_wreg(priv->base_addr, |
| (R_CC_CPU0_0 + (i * 8)) + j, |
| gmac->credit_config[(i * 8) + j]); |
| } |
| |
| xlr_nae_wreg(priv->base_addr, R_MSG_TX_THRESHOLD, 3); |
| xlr_nae_wreg(priv->base_addr, R_DMACR0, 0xffffffff); |
| xlr_nae_wreg(priv->base_addr, R_DMACR1, 0xffffffff); |
| xlr_nae_wreg(priv->base_addr, R_DMACR2, 0xffffffff); |
| xlr_nae_wreg(priv->base_addr, R_DMACR3, 0xffffffff); |
| xlr_nae_wreg(priv->base_addr, R_FREEQCARVE, 0); |
| |
| err = xlr_net_fill_rx_ring(priv->ndev); |
| if (err) |
| return err; |
| nlm_register_fmn_handler(start_stn_id, end_stn_id, xlr_net_fmn_handler, |
| priv->adapter); |
| return 0; |
| } |
| |
| static void xlr_config_translate_table(struct xlr_net_priv *priv) |
| { |
| u32 cpu_mask; |
| u32 val; |
| int bkts[32]; /* one bucket is assumed for each cpu */ |
| int b1, b2, c1, c2, i, j, k; |
| int use_bkt; |
| |
| use_bkt = 0; |
| cpu_mask = priv->nd->cpu_mask; |
| |
| pr_info("Using %s-based distribution\n", |
| (use_bkt) ? "bucket" : "class"); |
| j = 0; |
| for (i = 0; i < 32; i++) { |
| if ((1 << i) & cpu_mask) { |
| /* for each cpu, mark the 4+threadid bucket */ |
| bkts[j] = ((i / 4) * 8) + (i % 4); |
| j++; |
| } |
| } |
| |
| /*configure the 128 * 9 Translation table to send to available buckets*/ |
| k = 0; |
| c1 = 3; |
| c2 = 0; |
| for (i = 0; i < 64; i++) { |
| /* |
| * On use_bkt set the b0, b1 are used, else |
| * the 4 classes are used, here implemented |
| * a logic to distribute the packets to the |
| * buckets equally or based on the class |
| */ |
| c1 = (c1 + 1) & 3; |
| c2 = (c1 + 1) & 3; |
| b1 = bkts[k]; |
| k = (k + 1) % j; |
| b2 = bkts[k]; |
| k = (k + 1) % j; |
| |
| val = ((c1 << 23) | (b1 << 17) | (use_bkt << 16) | |
| (c2 << 7) | (b2 << 1) | (use_bkt << 0)); |
| dev_dbg(&priv->ndev->dev, "Table[%d] b1=%d b2=%d c1=%d c2=%d\n", |
| i, b1, b2, c1, c2); |
| xlr_nae_wreg(priv->base_addr, R_TRANSLATETABLE + i, val); |
| c1 = c2; |
| } |
| } |
| |
| static void xlr_config_parser(struct xlr_net_priv *priv) |
| { |
| u32 val; |
| |
| /* Mark it as ETHERNET type */ |
| xlr_nae_wreg(priv->base_addr, R_L2TYPE_0, 0x01); |
| |
| /* Use 7bit CRChash for flow classification with 127 as CRC polynomial*/ |
| xlr_nae_wreg(priv->base_addr, R_PARSERCONFIGREG, |
| ((0x7f << 8) | (1 << 1))); |
| |
| /* configure the parser : L2 Type is configured in the bootloader */ |
| /* extract IP: src, dest protocol */ |
| xlr_nae_wreg(priv->base_addr, R_L3CTABLE, |
| (9 << 20) | (1 << 19) | (1 << 18) | (0x01 << 16) | |
| (0x0800 << 0)); |
| xlr_nae_wreg(priv->base_addr, R_L3CTABLE + 1, |
| (9 << 25) | (1 << 21) | (12 << 14) | (4 << 10) | |
| (16 << 4) | 4); |
| |
| /* Configure to extract SRC port and Dest port for TCP and UDP pkts */ |
| xlr_nae_wreg(priv->base_addr, R_L4CTABLE, 6); |
| xlr_nae_wreg(priv->base_addr, R_L4CTABLE + 2, 17); |
| val = ((0 << 21) | (2 << 17) | (2 << 11) | (2 << 7)); |
| xlr_nae_wreg(priv->base_addr, R_L4CTABLE + 1, val); |
| xlr_nae_wreg(priv->base_addr, R_L4CTABLE + 3, val); |
| |
| xlr_config_translate_table(priv); |
| } |
| |
| static int xlr_phy_write(u32 *base_addr, int phy_addr, int regnum, u16 val) |
| { |
| unsigned long timeout, stoptime, checktime; |
| int timedout; |
| |
| /* 100ms timeout*/ |
| timeout = msecs_to_jiffies(100); |
| stoptime = jiffies + timeout; |
| timedout = 0; |
| |
| xlr_nae_wreg(base_addr, R_MII_MGMT_ADDRESS, (phy_addr << 8) | regnum); |
| |
| /* Write the data which starts the write cycle */ |
| xlr_nae_wreg(base_addr, R_MII_MGMT_WRITE_DATA, (u32)val); |
| |
| /* poll for the read cycle to complete */ |
| while (!timedout) { |
| checktime = jiffies; |
| if (xlr_nae_rdreg(base_addr, R_MII_MGMT_INDICATORS) == 0) |
| break; |
| timedout = time_after(checktime, stoptime); |
| } |
| if (timedout) { |
| pr_info("Phy device write err: device busy"); |
| return -EBUSY; |
| } |
| |
| return 0; |
| } |
| |
| static int xlr_phy_read(u32 *base_addr, int phy_addr, int regnum) |
| { |
| unsigned long timeout, stoptime, checktime; |
| int timedout; |
| |
| /* 100ms timeout*/ |
| timeout = msecs_to_jiffies(100); |
| stoptime = jiffies + timeout; |
| timedout = 0; |
| |
| /* setup the phy reg to be used */ |
| xlr_nae_wreg(base_addr, R_MII_MGMT_ADDRESS, |
| (phy_addr << 8) | (regnum << 0)); |
| |
| /* Issue the read command */ |
| xlr_nae_wreg(base_addr, R_MII_MGMT_COMMAND, |
| (1 << O_MII_MGMT_COMMAND__rstat)); |
| |
| /* poll for the read cycle to complete */ |
| while (!timedout) { |
| checktime = jiffies; |
| if (xlr_nae_rdreg(base_addr, R_MII_MGMT_INDICATORS) == 0) |
| break; |
| timedout = time_after(checktime, stoptime); |
| } |
| if (timedout) { |
| pr_info("Phy device read err: device busy"); |
| return -EBUSY; |
| } |
| |
| /* clear the read cycle */ |
| xlr_nae_wreg(base_addr, R_MII_MGMT_COMMAND, 0); |
| |
| /* Read the data */ |
| return xlr_nae_rdreg(base_addr, R_MII_MGMT_STATUS); |
| } |
| |
| static int xlr_mii_write(struct mii_bus *bus, int phy_addr, int regnum, u16 val) |
| { |
| struct xlr_net_priv *priv = bus->priv; |
| int ret; |
| |
| ret = xlr_phy_write(priv->mii_addr, phy_addr, regnum, val); |
| dev_dbg(&priv->ndev->dev, "mii_write phy %d : %d <- %x [%x]\n", |
| phy_addr, regnum, val, ret); |
| return ret; |
| } |
| |
| static int xlr_mii_read(struct mii_bus *bus, int phy_addr, int regnum) |
| { |
| struct xlr_net_priv *priv = bus->priv; |
| int ret; |
| |
| ret = xlr_phy_read(priv->mii_addr, phy_addr, regnum); |
| dev_dbg(&priv->ndev->dev, "mii_read phy %d : %d [%x]\n", |
| phy_addr, regnum, ret); |
| return ret; |
| } |
| |
| /* |
| * XLR ports are RGMII. XLS ports are SGMII mostly except the port0, |
| * which can be configured either SGMII or RGMII, considered SGMII |
| * by default, if board setup to RGMII the port_type need to set |
| * accordingly.Serdes and PCS layer need to configured for SGMII |
| */ |
| static void xlr_sgmii_init(struct xlr_net_priv *priv) |
| { |
| int phy; |
| |
| xlr_phy_write(priv->serdes_addr, 26, 0, 0x6DB0); |
| xlr_phy_write(priv->serdes_addr, 26, 1, 0xFFFF); |
| xlr_phy_write(priv->serdes_addr, 26, 2, 0xB6D0); |
| xlr_phy_write(priv->serdes_addr, 26, 3, 0x00FF); |
| xlr_phy_write(priv->serdes_addr, 26, 4, 0x0000); |
| xlr_phy_write(priv->serdes_addr, 26, 5, 0x0000); |
| xlr_phy_write(priv->serdes_addr, 26, 6, 0x0005); |
| xlr_phy_write(priv->serdes_addr, 26, 7, 0x0001); |
| xlr_phy_write(priv->serdes_addr, 26, 8, 0x0000); |
| xlr_phy_write(priv->serdes_addr, 26, 9, 0x0000); |
| xlr_phy_write(priv->serdes_addr, 26, 10, 0x0000); |
| |
| /* program GPIO values for serdes init parameters */ |
| xlr_nae_wreg(priv->gpio_addr, 0x20, 0x7e6802); |
| xlr_nae_wreg(priv->gpio_addr, 0x10, 0x7104); |
| |
| xlr_nae_wreg(priv->gpio_addr, 0x22, 0x7e6802); |
| xlr_nae_wreg(priv->gpio_addr, 0x21, 0x7104); |
| |
| /* enable autoneg - more magic */ |
| phy = priv->phy_addr % 4 + 27; |
| xlr_phy_write(priv->pcs_addr, phy, 0, 0x1000); |
| xlr_phy_write(priv->pcs_addr, phy, 0, 0x0200); |
| } |
| |
| void xlr_set_gmac_speed(struct xlr_net_priv *priv) |
| { |
| struct phy_device *phydev = xlr_get_phydev(priv); |
| int speed; |
| |
| if (phydev->interface == PHY_INTERFACE_MODE_SGMII) |
| xlr_sgmii_init(priv); |
| |
| if (phydev->speed != priv->phy_speed) { |
| speed = phydev->speed; |
| if (speed == SPEED_1000) { |
| /* Set interface to Byte mode */ |
| xlr_nae_wreg(priv->base_addr, R_MAC_CONFIG_2, 0x7217); |
| priv->phy_speed = speed; |
| } else if (speed == SPEED_100 || speed == SPEED_10) { |
| /* Set interface to Nibble mode */ |
| xlr_nae_wreg(priv->base_addr, R_MAC_CONFIG_2, 0x7117); |
| priv->phy_speed = speed; |
| } |
| /* Set SGMII speed in Interface control reg */ |
| if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { |
| if (speed == SPEED_10) |
| xlr_nae_wreg(priv->base_addr, |
| R_INTERFACE_CONTROL, |
| SGMII_SPEED_10); |
| if (speed == SPEED_100) |
| xlr_nae_wreg(priv->base_addr, |
| R_INTERFACE_CONTROL, |
| SGMII_SPEED_100); |
| if (speed == SPEED_1000) |
| xlr_nae_wreg(priv->base_addr, |
| R_INTERFACE_CONTROL, |
| SGMII_SPEED_1000); |
| } |
| if (speed == SPEED_10) |
| xlr_nae_wreg(priv->base_addr, R_CORECONTROL, 0x2); |
| if (speed == SPEED_100) |
| xlr_nae_wreg(priv->base_addr, R_CORECONTROL, 0x1); |
| if (speed == SPEED_1000) |
| xlr_nae_wreg(priv->base_addr, R_CORECONTROL, 0x0); |
| } |
| pr_info("gmac%d : %dMbps\n", priv->port_id, priv->phy_speed); |
| } |
| |
| static void xlr_gmac_link_adjust(struct net_device *ndev) |
| { |
| struct xlr_net_priv *priv = netdev_priv(ndev); |
| struct phy_device *phydev = xlr_get_phydev(priv); |
| u32 intreg; |
| |
| intreg = xlr_nae_rdreg(priv->base_addr, R_INTREG); |
| if (phydev->link) { |
| if (phydev->speed != priv->phy_speed) { |
| xlr_set_gmac_speed(priv); |
| pr_info("gmac%d : Link up\n", priv->port_id); |
| } |
| } else { |
| xlr_set_gmac_speed(priv); |
| pr_info("gmac%d : Link down\n", priv->port_id); |
| } |
| } |
| |
| static int xlr_mii_probe(struct xlr_net_priv *priv) |
| { |
| struct phy_device *phydev = xlr_get_phydev(priv); |
| |
| if (!phydev) { |
| pr_err("no PHY found on phy_addr %d\n", priv->phy_addr); |
| return -ENODEV; |
| } |
| |
| /* Attach MAC to PHY */ |
| phydev = phy_connect(priv->ndev, phydev_name(phydev), |
| xlr_gmac_link_adjust, priv->nd->phy_interface); |
| |
| if (IS_ERR(phydev)) { |
| pr_err("could not attach PHY\n"); |
| return PTR_ERR(phydev); |
| } |
| phydev->supported &= (ADVERTISED_10baseT_Full |
| | ADVERTISED_10baseT_Half |
| | ADVERTISED_100baseT_Full |
| | ADVERTISED_100baseT_Half |
| | ADVERTISED_1000baseT_Full |
| | ADVERTISED_Autoneg |
| | ADVERTISED_MII); |
| |
| phydev->advertising = phydev->supported; |
| phy_attached_info(phydev); |
| return 0; |
| } |
| |
| static int xlr_setup_mdio(struct xlr_net_priv *priv, |
| struct platform_device *pdev) |
| { |
| int err; |
| |
| priv->mii_bus = mdiobus_alloc(); |
| if (!priv->mii_bus) { |
| pr_err("mdiobus alloc failed\n"); |
| return -ENOMEM; |
| } |
| |
| priv->mii_bus->priv = priv; |
| priv->mii_bus->name = "xlr-mdio"; |
| snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "%s-%d", |
| priv->mii_bus->name, priv->port_id); |
| priv->mii_bus->read = xlr_mii_read; |
| priv->mii_bus->write = xlr_mii_write; |
| priv->mii_bus->parent = &pdev->dev; |
| |
| /* Scan only the enabled address */ |
| priv->mii_bus->phy_mask = ~(1 << priv->phy_addr); |
| |
| /* setting clock divisor to 54 */ |
| xlr_nae_wreg(priv->base_addr, R_MII_MGMT_CONFIG, 0x7); |
| |
| err = mdiobus_register(priv->mii_bus); |
| if (err) { |
| mdiobus_free(priv->mii_bus); |
| pr_err("mdio bus registration failed\n"); |
| return err; |
| } |
| |
| pr_info("Registered mdio bus id : %s\n", priv->mii_bus->id); |
| err = xlr_mii_probe(priv); |
| if (err) { |
| mdiobus_free(priv->mii_bus); |
| return err; |
| } |
| return 0; |
| } |
| |
| static void xlr_port_enable(struct xlr_net_priv *priv) |
| { |
| u32 prid = (read_c0_prid() & 0xf000); |
| |
| /* Setup MAC_CONFIG reg if (xls & rgmii) */ |
| if ((prid == 0x8000 || prid == 0x4000 || prid == 0xc000) && |
| priv->nd->phy_interface == PHY_INTERFACE_MODE_RGMII) |
| xlr_reg_update(priv->base_addr, R_RX_CONTROL, |
| (1 << O_RX_CONTROL__RGMII), |
| (1 << O_RX_CONTROL__RGMII)); |
| |
| /* Rx Tx enable */ |
| xlr_reg_update(priv->base_addr, R_MAC_CONFIG_1, |
| ((1 << O_MAC_CONFIG_1__rxen) | |
| (1 << O_MAC_CONFIG_1__txen) | |
| (1 << O_MAC_CONFIG_1__rxfc) | |
| (1 << O_MAC_CONFIG_1__txfc)), |
| ((1 << O_MAC_CONFIG_1__rxen) | |
| (1 << O_MAC_CONFIG_1__txen) | |
| (1 << O_MAC_CONFIG_1__rxfc) | |
| (1 << O_MAC_CONFIG_1__txfc))); |
| |
| /* Setup tx control reg */ |
| xlr_reg_update(priv->base_addr, R_TX_CONTROL, |
| ((1 << O_TX_CONTROL__TXENABLE) | |
| (512 << O_TX_CONTROL__TXTHRESHOLD)), 0x3fff); |
| |
| /* Setup rx control reg */ |
| xlr_reg_update(priv->base_addr, R_RX_CONTROL, |
| 1 << O_RX_CONTROL__RXENABLE, |
| 1 << O_RX_CONTROL__RXENABLE); |
| } |
| |
| static void xlr_port_disable(struct xlr_net_priv *priv) |
| { |
| /* Setup MAC_CONFIG reg */ |
| /* Rx Tx disable*/ |
| xlr_reg_update(priv->base_addr, R_MAC_CONFIG_1, |
| ((1 << O_MAC_CONFIG_1__rxen) | |
| (1 << O_MAC_CONFIG_1__txen) | |
| (1 << O_MAC_CONFIG_1__rxfc) | |
| (1 << O_MAC_CONFIG_1__txfc)), 0x0); |
| |
| /* Setup tx control reg */ |
| xlr_reg_update(priv->base_addr, R_TX_CONTROL, |
| ((1 << O_TX_CONTROL__TXENABLE) | |
| (512 << O_TX_CONTROL__TXTHRESHOLD)), 0); |
| |
| /* Setup rx control reg */ |
| xlr_reg_update(priv->base_addr, R_RX_CONTROL, |
| 1 << O_RX_CONTROL__RXENABLE, 0); |
| } |
| |
| /* |
| * Initialization of gmac |
| */ |
| static int xlr_gmac_init(struct xlr_net_priv *priv, |
| struct platform_device *pdev) |
| { |
| int ret; |
| |
| pr_info("Initializing the gmac%d\n", priv->port_id); |
| |
| xlr_port_disable(priv); |
| |
| xlr_nae_wreg(priv->base_addr, R_DESC_PACK_CTRL, |
| (1 << O_DESC_PACK_CTRL__MAXENTRY) | |
| (BYTE_OFFSET << O_DESC_PACK_CTRL__BYTEOFFSET) | |
| (1600 << O_DESC_PACK_CTRL__REGULARSIZE)); |
| |
| ret = xlr_setup_mdio(priv, pdev); |
| if (ret) |
| return ret; |
| xlr_port_enable(priv); |
| |
| /* Enable Full-duplex/1000Mbps/CRC */ |
| xlr_nae_wreg(priv->base_addr, R_MAC_CONFIG_2, 0x7217); |
| /* speed 2.5Mhz */ |
| xlr_nae_wreg(priv->base_addr, R_CORECONTROL, 0x02); |
| /* Setup Interrupt mask reg */ |
| xlr_nae_wreg(priv->base_addr, R_INTMASK, (1 << O_INTMASK__TXILLEGAL) | |
| (1 << O_INTMASK__MDINT) | (1 << O_INTMASK__TXFETCHERROR) | |
| (1 << O_INTMASK__P2PSPILLECC) | (1 << O_INTMASK__TAGFULL) | |
| (1 << O_INTMASK__UNDERRUN) | (1 << O_INTMASK__ABORT)); |
| |
| /* Clear all stats */ |
| xlr_reg_update(priv->base_addr, R_STATCTRL, 0, 1 << O_STATCTRL__CLRCNT); |
| xlr_reg_update(priv->base_addr, R_STATCTRL, 1 << 2, 1 << 2); |
| return 0; |
| } |
| |
| static int xlr_net_probe(struct platform_device *pdev) |
| { |
| struct xlr_net_priv *priv = NULL; |
| struct net_device *ndev; |
| struct resource *res; |
| struct xlr_adapter *adapter; |
| int err, port; |
| |
| pr_info("XLR/XLS Ethernet Driver controller %d\n", pdev->id); |
| /* |
| * Allocate our adapter data structure and attach it to the device. |
| */ |
| adapter = devm_kzalloc(&pdev->dev, sizeof(*adapter), GFP_KERNEL); |
| if (!adapter) |
| return -ENOMEM; |
| |
| /* |
| * XLR and XLS have 1 and 2 NAE controller respectively |
| * Each controller has 4 gmac ports, mapping each controller |
| * under one parent device, 4 gmac ports under one device. |
| */ |
| for (port = 0; port < pdev->num_resources / 2; port++) { |
| ndev = alloc_etherdev_mq(sizeof(struct xlr_net_priv), 32); |
| if (!ndev) { |
| dev_err(&pdev->dev, |
| "Allocation of Ethernet device failed\n"); |
| return -ENOMEM; |
| } |
| |
| priv = netdev_priv(ndev); |
| priv->pdev = pdev; |
| priv->ndev = ndev; |
| priv->port_id = (pdev->id * 4) + port; |
| priv->nd = (struct xlr_net_data *)pdev->dev.platform_data; |
| res = platform_get_resource(pdev, IORESOURCE_MEM, port); |
| priv->base_addr = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(priv->base_addr)) { |
| err = PTR_ERR(priv->base_addr); |
| goto err_gmac; |
| } |
| priv->adapter = adapter; |
| adapter->netdev[port] = ndev; |
| |
| res = platform_get_resource(pdev, IORESOURCE_IRQ, port); |
| if (!res) { |
| dev_err(&pdev->dev, "No irq resource for MAC %d\n", |
| priv->port_id); |
| err = -ENODEV; |
| goto err_gmac; |
| } |
| |
| ndev->irq = res->start; |
| |
| priv->phy_addr = priv->nd->phy_addr[port]; |
| priv->tx_stnid = priv->nd->tx_stnid[port]; |
| priv->mii_addr = priv->nd->mii_addr; |
| priv->serdes_addr = priv->nd->serdes_addr; |
| priv->pcs_addr = priv->nd->pcs_addr; |
| priv->gpio_addr = priv->nd->gpio_addr; |
| |
| ndev->netdev_ops = &xlr_netdev_ops; |
| ndev->watchdog_timeo = HZ; |
| |
| /* Setup Mac address and Rx mode */ |
| eth_hw_addr_random(ndev); |
| xlr_hw_set_mac_addr(ndev); |
| xlr_set_rx_mode(ndev); |
| |
| priv->num_rx_desc += MAX_NUM_DESC_SPILL; |
| ndev->ethtool_ops = &xlr_ethtool_ops; |
| SET_NETDEV_DEV(ndev, &pdev->dev); |
| |
| xlr_config_fifo_spill_area(priv); |
| /* Configure PDE to Round-Robin pkt distribution */ |
| xlr_config_pde(priv); |
| xlr_config_parser(priv); |
| |
| /* Call init with respect to port */ |
| if (strcmp(res->name, "gmac") == 0) { |
| err = xlr_gmac_init(priv, pdev); |
| if (err) { |
| dev_err(&pdev->dev, "gmac%d init failed\n", |
| priv->port_id); |
| goto err_gmac; |
| } |
| } |
| |
| if (priv->port_id == 0 || priv->port_id == 4) { |
| err = xlr_config_common(priv); |
| if (err) |
| goto err_netdev; |
| } |
| |
| err = register_netdev(ndev); |
| if (err) { |
| dev_err(&pdev->dev, |
| "Registering netdev failed for gmac%d\n", |
| priv->port_id); |
| goto err_netdev; |
| } |
| platform_set_drvdata(pdev, priv); |
| } |
| |
| return 0; |
| |
| err_netdev: |
| mdiobus_free(priv->mii_bus); |
| err_gmac: |
| free_netdev(ndev); |
| return err; |
| } |
| |
| static int xlr_net_remove(struct platform_device *pdev) |
| { |
| struct xlr_net_priv *priv = platform_get_drvdata(pdev); |
| |
| unregister_netdev(priv->ndev); |
| mdiobus_unregister(priv->mii_bus); |
| mdiobus_free(priv->mii_bus); |
| free_netdev(priv->ndev); |
| return 0; |
| } |
| |
| static struct platform_driver xlr_net_driver = { |
| .probe = xlr_net_probe, |
| .remove = xlr_net_remove, |
| .driver = { |
| .name = "xlr-net", |
| }, |
| }; |
| |
| module_platform_driver(xlr_net_driver); |
| |
| MODULE_AUTHOR("Ganesan Ramalingam <ganesanr@broadcom.com>"); |
| MODULE_DESCRIPTION("Ethernet driver for Netlogic XLR/XLS"); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| MODULE_ALIAS("platform:xlr-net"); |