From 4a4b5d88753844ef33988a5fdc6f466393614da1 Mon Sep 17 00:00:00 2001 From: Kenny Cheung Date: Thu, 30 Mar 2023 14:28:01 +0800 Subject: [PATCH] drivers: add efinix tsemac driver Signed-off-by: Kenny Cheung Signed-off-by: Mohamad Noor Alim Hussin --- drivers/net/ethernet/Kconfig | 1 + drivers/net/ethernet/Makefile | 1 + drivers/net/ethernet/efinix/Kconfig | 18 + drivers/net/ethernet/efinix/Makefile | 3 + drivers/net/ethernet/efinix/efinix_tse.c | 1416 +++++++++++++++++ drivers/net/ethernet/efinix/efinix_tse.h | 459 ++++++ drivers/net/ethernet/efinix/efinix_tse_dma.c | 244 +++ drivers/net/ethernet/efinix/efinix_tse_mdio.c | 195 +++ 8 files changed, 2337 insertions(+) create mode 100644 drivers/net/ethernet/efinix/Kconfig create mode 100644 drivers/net/ethernet/efinix/Makefile create mode 100644 drivers/net/ethernet/efinix/efinix_tse.c create mode 100644 drivers/net/ethernet/efinix/efinix_tse.h create mode 100644 drivers/net/ethernet/efinix/efinix_tse_dma.c create mode 100644 drivers/net/ethernet/efinix/efinix_tse_mdio.c diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index fad9a2c77..047241c8f 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -182,5 +182,6 @@ source "drivers/net/ethernet/via/Kconfig" source "drivers/net/ethernet/wiznet/Kconfig" source "drivers/net/ethernet/xilinx/Kconfig" source "drivers/net/ethernet/xircom/Kconfig" +source "drivers/net/ethernet/efinix/Kconfig" endif # ETHERNET diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index f8f38dcb5..59358f1f7 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -95,3 +95,4 @@ obj-$(CONFIG_NET_VENDOR_XILINX) += xilinx/ obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/ obj-$(CONFIG_NET_VENDOR_SYNOPSYS) += synopsys/ obj-$(CONFIG_NET_VENDOR_PENSANDO) += pensando/ +obj-$(CONFIG_NET_VENDOR_EFINIX) += efinix/ diff --git a/drivers/net/ethernet/efinix/Kconfig b/drivers/net/ethernet/efinix/Kconfig new file mode 100644 index 000000000..46f13de5a --- /dev/null +++ b/drivers/net/ethernet/efinix/Kconfig @@ -0,0 +1,18 @@ + + +config NET_VENDOR_EFINIX + bool "Efinix devices" + default y + help + type 'N' to to skip all configuration about Efinix + +if NET_VENDOR_EFINIX + +config EFINIX_TSEMAC + tristate "Efinix Triple Speed Ethernet MAC support" + depends on HAS_IOMEM + select PHYLINK + help + This driver supports the Triple Spped Ethernet MAC Core from Efinix. + +endif # NET_VENDOR_EFINIX diff --git a/drivers/net/ethernet/efinix/Makefile b/drivers/net/ethernet/efinix/Makefile new file mode 100644 index 000000000..51062131b --- /dev/null +++ b/drivers/net/ethernet/efinix/Makefile @@ -0,0 +1,3 @@ + +efinix_tsemac-objs := efinix_tse.o efinix_tse_dma.o efinix_tse_mdio.o +obj-$(CONFIG_EFINIX_TSEMAC) += efinix_tsemac.o diff --git a/drivers/net/ethernet/efinix/efinix_tse.c b/drivers/net/ethernet/efinix/efinix_tse.c new file mode 100644 index 000000000..ad17f22f6 --- /dev/null +++ b/drivers/net/ethernet/efinix/efinix_tse.c @@ -0,0 +1,1416 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Ethernet driver for the Efinix Triple Speed Ethernet device + * + * Copyright (c) 2023 Efinix, Inc. All rights reserved. + */ + +//FIXME: somtimes receive RX interrupt, but completed bit in BD's status is low +//TODO: implement multicasting +//TODO: support interrupt coalescing +//TODO: support sgmii +//TODO: add 64-bit system support +//TODO: offline checksum +//TODO: implement multicast +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "efinix_tse.h" + +#define DRIVER_NAME "efx_tsemac" +#define DRIVER_DESCRIPTION "Efinix TSEMAC driver" +#define DRIVER_VERSION "0.01" + +#define LAST_REGISTER_ADDR TSEMAC_DST_MAC_ADDR_HI + +#define TX_BD_NUM_DEFAULT 512 +#define RX_BD_NUM_DEFAULT 512 +#define TX_BD_NUM_MIN (MAX_SKB_FRAGS + 1) +#define TX_BD_NUM_MAX 4096 +#define RX_BD_NUM_MAX 4096 + +static const struct of_device_id tsemac_of_match[] = { + { .compatible = "efinix,tsemac-0.01.a", } +}; +MODULE_DEVICE_TABLE(of, tsemac_of_match); + +int __tsemac_device_reset(struct efx_tsemac_local *lp); +static int tsemac_device_reset(struct net_device *ndev); +static void tsemac_dma_err_handler(struct work_struct *work); +static int tsemac_probe(struct platform_device *pdev); + +static void tsemac_hw_set_tx_csum(struct net_device *ndev, bool enable) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + + if (enable) { + lp->features |= TSEMAC_FEATURE_FULL_TX_CSUM; + tsemac_out32(lp, ETHERNET_CTRL_HW_TX_CHECKSUM_EN, 1); + } else { + lp->features &= ~TSEMAC_FEATURE_FULL_TX_CSUM; + tsemac_out32(lp, ETHERNET_CTRL_HW_TX_CHECKSUM_EN, 0); + } +} + +static void tsemac_hw_set_rx_csum(struct net_device *ndev, bool enable) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + + if (enable) { + lp->features |= TSEMAC_FEATURE_FULL_RX_CSUM; + tsemac_out32(lp, ETHERNET_CTRL_HW_RX_CHECKSUM_EN, 1); + } else { + lp->features &= ~TSEMAC_FEATURE_FULL_RX_CSUM; + tsemac_out32(lp, ETHERNET_CTRL_HW_RX_CHECKSUM_EN, 0); + } +} + +static void tsemac_set_mac_address(struct net_device *ndev, const void *address) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + + if (address) + memcpy(ndev->dev_addr, address, ETH_ALEN); + if (!is_valid_ether_addr(ndev->dev_addr)) + eth_hw_addr_random(ndev); + + /* Set up unicast MAC address filter set its mac address */ + tsemac_out32(lp, TSEMAC_MAC_ADDR_LO, + (ndev->dev_addr[0]) | + (ndev->dev_addr[1] << 8) | + (ndev->dev_addr[2] << 16) | + (ndev->dev_addr[3] << 24)); + tsemac_out32(lp, TSEMAC_MAC_ADDR_HI, + (ndev->dev_addr[4]) | + (ndev->dev_addr[5] << 8)); + + tsemac_out32(lp, TSEMAC_MAC_ADDR_MAKE_LO, 0xFFFFFFFF); + tsemac_out32(lp, TSEMAC_MAC_ADDR_MAKE_HI, 0x0000FFFF); +} + +static int netdev_set_mac_address(struct net_device *ndev, void *p) +{ + struct sockaddr *addr = p; + tsemac_set_mac_address(ndev, addr->sa_data); + return 0; +} + +static void tsemac_set_multicast_list(struct net_device *ndev) +{ + u32 reg; + struct efx_tsemac_local *lp = netdev_priv(ndev); + + //TODO: support other IFF mode and multicast + if (ndev->flags & (IFF_ALLMULTI | IFF_PROMISC) || + netdev_mc_count(ndev) > 1 ) { + ndev->flags |= IFF_PROMISC; + reg = tsemac_in32(lp, TSEMAC_COMMAND_CONFIG); + reg |= ETHERNET_CMD_PROMIS_EN; + tsemac_out32(lp, TSEMAC_COMMAND_CONFIG, reg); + dev_info(&ndev->dev, "Promiscuous mode enabled.\n"); + } else { + reg = tsemac_in32(lp, TSEMAC_COMMAND_CONFIG); + reg &= ~ETHERNET_CMD_PROMIS_EN; + tsemac_out32(lp, TSEMAC_COMMAND_CONFIG, reg); + dev_info(&ndev->dev, "Promiscuous mode disabled.\n"); + } +} + +static netdev_features_t tsemac_fix_features(struct net_device *ndev, netdev_features_t features) +{ + features &= ndev->hw_features; + + return features; +} + +static int tsemac_set_features(struct net_device *ndev, netdev_features_t features) +{ + tsemac_hw_set_tx_csum(ndev, !! (features & NETIF_F_HW_CSUM)); + tsemac_hw_set_rx_csum(ndev, !! (features & NETIF_F_RXCSUM)); + + return 0; +} + +static inline u32 tsemac_check_tx_bd_head(struct efx_tsemac_local *lp, u32 first_bd, u32 last_bd) +{ + struct dmasg_descriptor *cur_p; + u32 status; + u32 completed; + u32 bd = first_bd % lp->tx_bd_num; + last_bd = last_bd % lp->tx_bd_num; + while (bd != last_bd) { + cur_p = &lp->tx_bd_v[bd]; + data_cache_invalidate_address(&(cur_p->status)); + data_cache_invalidate_address(&(cur_p->control)); + status = cur_p->status; + completed = (status & DMASG_DESCRIPTOR_STATUS_COMPLETED) && (cur_p->control != 0); + + if (!completed) + return bd; + + bd = (bd + 1) % lp->tx_bd_num; + } + + return last_bd; +} + +static inline int tsemac_check_tx_bd_space(struct efx_tsemac_local *lp, int num_frag) +{ + struct dmasg_descriptor *cur_p; + + /** Ensure we see all descriptor updates from device or TX polling + * Add constant 1 because a bd is reserved for indicating the tail + */ + rmb(); + cur_p = &lp->tx_bd_v[(READ_ONCE(lp->tx_bd_tail) + num_frag + 1) % + lp->tx_bd_num]; + data_cache_invalidate_address(&cur_p->control); + if (cur_p->control) + return NETDEV_TX_BUSY; + return 0; +} + +static netdev_tx_t tsemac_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + u32 ii; + u32 num_frag; + skb_frag_t *frag; + dma_addr_t phys; + u32 orig_tail_ptr, new_tail_ptr; + struct efx_tsemac_local *lp = netdev_priv(ndev); + struct dmasg_descriptor *cur_p; + u32 cur_head; + u32 head; + + orig_tail_ptr = lp->tx_bd_tail; + new_tail_ptr = orig_tail_ptr; + + num_frag = skb_shinfo(skb)->nr_frags; + cur_p = &lp->tx_bd_v[orig_tail_ptr]; + + if (tsemac_check_tx_bd_space(lp, num_frag + 1)) { + /* Should not happen as last start_xmit call should have + * checked for sufficient space and queue should only be + * woken when sufficient space is available. + */ + netif_stop_queue(ndev); + if (net_ratelimit()) + netdev_warn(ndev, "TX ring unexpectedly full\n"); + + return NETDEV_TX_BUSY; + } + + // assign descriptors for frag first + for (ii = 0; ii < num_frag; ii++) { + if (++new_tail_ptr >= lp->tx_bd_num) + new_tail_ptr = 0; + cur_p = &lp->tx_bd_v[new_tail_ptr]; + frag = &skb_shinfo(skb)->frags[ii]; + phys = dma_map_single(lp->dev, + skb_frag_address(frag), + skb_frag_size(frag), + DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(lp->dev, phys))) { + if (net_ratelimit()) + netdev_err(ndev, "TX DMA mapping error\n"); + ndev->stats.tx_dropped++; + tsemac_free_tx_chain(lp, orig_tail_ptr, ii + 1, + true, NULL, 0); + return NETDEV_TX_OK; + } + desc_set_tx_phys_addr(lp, phys, cur_p); + cur_p->control = (skb_frag_size(frag) - 1) + & DMASG_DESCRIPTOR_CONTROL_BYTES; + cur_p->status = 0; + + } + phys = dma_map_single(lp->dev, skb->data, + skb_headlen(skb), DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(lp->dev, phys))) { + if (net_ratelimit()) + netdev_err(ndev, "TX DMA mapping error\n"); + ndev->stats.tx_dropped++; + tsemac_free_tx_chain(lp, orig_tail_ptr, 1, + true, NULL, 0); + return NETDEV_TX_OK; + } + desc_set_tx_phys_addr(lp, phys, &lp->tx_bd_v[orig_tail_ptr]); + cur_p->skb = skb; + + if (lp->features & TSEMAC_FEATURE_FULL_TX_CSUM) + skb->ip_summed = CHECKSUM_PARTIAL; + else + skb->ip_summed = CHECKSUM_NONE; + + spin_lock(&lp->lock); + if (orig_tail_ptr != new_tail_ptr) { + cur_p->control |= DMASG_DESCRIPTOR_CONTROL_END_OF_PACKET; + // TODO: assignment to control & status need to be atomic + lp->tx_bd_v[orig_tail_ptr].control = (skb_headlen(skb) - 1) + & DMASG_DESCRIPTOR_CONTROL_BYTES; + lp->tx_bd_v[orig_tail_ptr].status = 0; + } else { + // TODO: assignment to control & status need to be atomic + lp->tx_bd_v[orig_tail_ptr].control = DMASG_DESCRIPTOR_CONTROL_END_OF_PACKET | ((skb_headlen(skb) - 1) & DMASG_DESCRIPTOR_CONTROL_BYTES); + lp->tx_bd_v[orig_tail_ptr].status = 0; + } + spin_unlock(&lp->lock); + + if (++new_tail_ptr >= lp->tx_bd_num) + new_tail_ptr = 0; + WRITE_ONCE(lp->tx_bd_tail, new_tail_ptr); + lp->tx_bd_v[new_tail_ptr].status = DMASG_DESCRIPTOR_STATUS_COMPLETED; + dma_wmb(); + + head = tsemac_check_tx_bd_head(lp, lp->tx_bd_ci, orig_tail_ptr); + cur_head = lower_32_bits(lp->tx_bd_p + sizeof(*lp->tx_bd_v) * head); + + // Start the transfer + if (!(tsemac_dma_in32(lp, DMA_CH_STATUS, DMASG_TX_BASE) & DMA_CH_STATUS_BUSY)) { + tsemac_dma_out32(lp, DMA_CH_LINKED_LIST_HEAD, DMASG_TX_BASE, cur_head); + tsemac_dma_out32(lp, DMA_CH_STATUS, DMASG_TX_BASE, + DMA_CH_STATUS_LINKED_LIST_START); + + } + + /* Stop queue if next transmit may not have space */ + if (tsemac_check_tx_bd_space(lp, MAX_SKB_FRAGS + 1)) { + netif_stop_queue(ndev); + + /* Matches barrier in tsemac_tx_poll */ + smp_mb(); + + /* Space might have just been freed - check again */ + if (!tsemac_check_tx_bd_space(lp, MAX_SKB_FRAGS + 1)) + netif_wake_queue(ndev); + } + + return NETDEV_TX_OK; +} + +static irqreturn_t tsemac_tx_irq(int irq, void *_ndev) +{ + unsigned int status; + struct net_device *ndev = _ndev; + struct efx_tsemac_local *lp = netdev_priv(ndev); + u32 cr; + + status = tsemac_dma_in32(lp, DMA_CH_INTERRUPT_PENDING, DMASG_TX_BASE); + + if (!(status & DMASG_IRQ_ALL_MASK)) + return IRQ_NONE; + + cr = lp->tx_dma_cr; + cr &= ~(DMASG_IRQ_ALL_MASK); + tsemac_dma_out32(lp, DMA_CH_INTERRUPT_ENABLE, DMASG_TX_BASE, cr); + tsemac_dma_out32(lp, DMA_CH_INTERRUPT_PENDING, DMASG_TX_BASE, status); + + napi_schedule(&lp->napi_tx); + + return IRQ_HANDLED; +} + +static irqreturn_t tsemac_rx_irq(int irq, void *_ndev) +{ + unsigned int status; + struct net_device *ndev = _ndev; + struct efx_tsemac_local *lp = netdev_priv(ndev); + u32 cr; + + status = tsemac_dma_in32(lp, DMA_CH_INTERRUPT_PENDING, DMASG_RX_BASE); + + if (!(status & DMASG_IRQ_ALL_MASK)) + return IRQ_NONE; + + /* Disable further RX completion interrupts and schedule + * NAPI receive. + */ + cr = lp->rx_dma_cr; + cr &= ~(DMASG_IRQ_ALL_MASK); + tsemac_dma_out32(lp, DMA_CH_INTERRUPT_ENABLE, DMASG_RX_BASE, cr); + tsemac_dma_out32(lp, DMA_CH_INTERRUPT_PENDING, DMASG_RX_BASE, status); + + napi_schedule(&lp->napi_rx); + + return IRQ_HANDLED; +} + +static int tsemac_open(struct net_device *ndev) +{ + int ret; + struct efx_tsemac_local *lp = netdev_priv(ndev); + + dev_dbg(&ndev->dev, "tsemac_open()\n"); + + /* When we do an Axi Ethernet reset, it resets the complete core + * including the MDIO. MDIO must be disabled before resetting. + * Hold MDIO bus lock to avoid MDIO accesses during the reset. + */ + tsemac_lock_mii(lp); + ret = tsemac_device_reset(ndev); + tsemac_unlock_mii(lp); + tsemac_out32(lp, ETHERNET_CTRL_DMA_RX_RESET, 1); + tsemac_out32(lp, ETHERNET_CTRL_DMA_TX_RESET, 1); + + ret = phylink_of_phy_connect(lp->phylink, lp->dev->of_node, 0); + if (ret) { + dev_err(lp->dev, "phylink_of_phy_connect() failed: %d\n", ret); + return ret; + } + + phylink_start(lp->phylink); + + /* Enable worker thread for Axi DMA error handling */ + INIT_WORK(&lp->dma_err_task, tsemac_dma_err_handler); + + napi_enable(&lp->napi_rx); + napi_enable(&lp->napi_tx); + + /* Enable interrupts for Axi DMA Tx */ + ret = request_irq(lp->tx_irq, tsemac_tx_irq, IRQF_SHARED, + "eth tx", ndev); + if (ret) + goto err_tx_irq; + tsemac_out32(lp, ETHERNET_CTRL_DMA_TX_RESET, 0); + /* Enable interrupts for Axi DMA Rx */ + ret = request_irq(lp->rx_irq, tsemac_rx_irq, IRQF_SHARED, + "eth rx", ndev); + if (ret) + goto err_rx_irq; + tsemac_out32(lp, ETHERNET_CTRL_DMA_RX_RESET, 0); + tsemac_set_32bit(lp, TSEMAC_COMMAND_CONFIG, + ETHERNET_CMD_TX_ENA | ETHERNET_CMD_RX_ENA | ETHERNET_CMD_CRC_FWD); + + return 0; + +err_rx_irq: + tsemac_out32(lp, ETHERNET_CTRL_DMA_RX_RESET, 0); + free_irq(lp->tx_irq, ndev); +err_tx_irq: + tsemac_out32(lp, ETHERNET_CTRL_DMA_TX_RESET, 0); + napi_disable(&lp->napi_tx); + napi_disable(&lp->napi_rx); + phylink_stop(lp->phylink); + phylink_disconnect_phy(lp->phylink); + cancel_work_sync(&lp->dma_err_task); + dev_err(lp->dev, "request_irq() failed\n"); + return ret; +} + + +static int tsemac_stop(struct net_device *ndev) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + + dev_dbg(&ndev->dev, "tsemac_close()\n"); + + napi_disable(&lp->napi_tx); + napi_disable(&lp->napi_rx); + + phylink_stop(lp->phylink); + phylink_disconnect_phy(lp->phylink); + + tsemac_clear_32bit(lp, TSEMAC_COMMAND_CONFIG, + ETHERNET_CMD_TX_ENA | ETHERNET_CMD_RX_ENA); + + tsemac_dma_stop(lp); + + cancel_work_sync(&lp->dma_err_task); + + free_irq(lp->tx_irq, ndev); + free_irq(lp->rx_irq, ndev); + + tsemac_dma_bd_release(ndev); + return 0; +} + +static int tsemac_change_mtu(struct net_device *ndev, int new_mtu) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + + if (netif_running(ndev)) + return -EBUSY; + + // invalid if greater than buffer size + if ((new_mtu + ETHERNET_HDR_SIZE + ETHERNET_TRL_SIZE) > lp->rxmem) + return -EINVAL; + + ndev->mtu = new_mtu; + + return 0; +} + +//TODO: determine whether need to implement tsemac_poll_controller +//static void tsemac_poll_controller(struct net_device *ndev); +static void tsemac_ethtools_get_drvinfo(struct net_device *ndev, struct ethtool_drvinfo *ed) +{ + strlcpy(ed->driver, DRIVER_NAME, sizeof(ed->driver)); + strlcpy(ed->version, DRIVER_VERSION, sizeof(ed->version)); +} + +static int tsemac_ethtools_get_regs_len(struct net_device *ndev) +{ + (void) ndev; + return LAST_REGISTER_ADDR + 4; +} + +static void tsemac_ethtools_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *ret) +{ + int i; + u32 *data = (u32 *) ret; + size_t len = sizeof(u32) * LAST_REGISTER_ADDR; + struct efx_tsemac_local *lp = netdev_priv(ndev); + + regs->version = 0; + regs->len = tsemac_ethtools_get_regs_len(ndev); + + memset(data, 0, regs->len); + for(i=0 ; irx_max_pending = RX_BD_NUM_MAX; + ering->rx_mini_max_pending = 0; + ering->rx_jumbo_max_pending = 0; + ering->tx_max_pending = TX_BD_NUM_MAX; + ering->rx_pending = lp->rx_bd_num; + ering->rx_mini_pending = 0; + ering->rx_jumbo_pending = 0; + ering->tx_pending = lp->tx_bd_num; +} + + +static int tsemac_ethtools_set_ringparam(struct net_device *ndev, + struct ethtool_ringparam *ering) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + + if (ering->rx_pending > RX_BD_NUM_MAX || + ering->rx_mini_pending || + ering->rx_jumbo_pending || + ering->tx_pending < TX_BD_NUM_MIN || + ering->tx_pending > TX_BD_NUM_MAX) + return -EINVAL; + + if (netif_running(ndev)) + return -EBUSY; + + lp->rx_bd_num = ering->rx_pending; + lp->tx_bd_num = ering->tx_pending; + return 0; +} + +static int tsemac_tx_poll(struct napi_struct *napi, int budget) +{ + struct efx_tsemac_local *lp = container_of(napi, struct efx_tsemac_local, napi_tx); + struct net_device *ndev = lp->ndev; + u32 size = 0; + u32 err; + int packets; + + packets = tsemac_free_tx_chain(lp, lp->tx_bd_ci, budget, false, &size, budget); + if (packets) { + ndev->stats.tx_packets += packets; + ndev->stats.tx_bytes += size; + err = tsemac_in32(lp, TSEMAC_IF_OUT_ERRORS); + if (err > lp->prev_tx_err) + ndev->stats.tx_errors += err - lp->prev_tx_err; + lp->prev_tx_err = err; + + /* Matches barrier in tsemac_start_xmit */ + smp_mb(); + + if (!tsemac_check_tx_bd_space(lp, MAX_SKB_FRAGS + 1)) + netif_wake_queue(ndev); + } + + if (packets < budget && napi_complete_done(napi, packets)) + tsemac_dma_out32(lp, DMA_CH_INTERRUPT_ENABLE, DMASG_TX_BASE, + lp->tx_dma_cr); + + return packets; +} + +static int tsemac_rx_poll(struct napi_struct *napi, int budget) +{ + u32 length; + u32 size = 0; + int packets = 0; + struct dmasg_descriptor *cur_p; + struct sk_buff *skb, *new_skb; + struct efx_tsemac_local *lp = container_of(napi, struct efx_tsemac_local, napi_rx); + u32 new_err, prev, delta; + + data_cache_invalidate_all(); + dma_rmb(); + cur_p = &lp->rx_bd_v[lp->rx_bd_ci]; + + length = READ_ONCE(cur_p->status) & DMASG_DESCRIPTOR_STATUS_BYTES; + + /* Increase the lp->rx_bd_ci if current length is 0 */ + if (length == 0) { + if (++lp->rx_bd_ci >= lp->rx_bd_num) + lp->rx_bd_ci = 0; + cur_p = &lp->rx_bd_v[lp->rx_bd_ci]; + } + + while (packets < budget && (READ_ONCE(cur_p->status) & DMASG_DESCRIPTOR_STATUS_COMPLETED)) { + dma_addr_t phys; + + /* Ensure we see complete descriptor update */ + dma_rmb(); + + skb = cur_p->skb; + cur_p->skb = NULL; + + /* skb could be NULL if a previous pass already received the + * packet for this slot in the ring, but failed to refill it + * with a newly allocated buffer. In this case, don't try to + * receive it again. + */ + if (likely(skb)) { + length = READ_ONCE(cur_p->status) & DMASG_DESCRIPTOR_STATUS_BYTES; + + if (unlikely(length == 0 || length > lp->max_frm_size)) { + dev_kfree_skb(skb); + goto rearm; + } + + phys = desc_get_rx_phys_addr(lp, cur_p); + dma_unmap_single(lp->dev, phys, lp->max_frm_size, + DMA_FROM_DEVICE); + + skb_put(skb, length); + skb->protocol = eth_type_trans(skb, lp->ndev); + /*skb_checksum_none_assert(skb);*/ + + if (lp->csum_offload_on_rx_path & TSEMAC_FEATURE_FULL_RX_CSUM) + skb->ip_summed = CHECKSUM_UNNECESSARY; + else + skb->ip_summed = CHECKSUM_NONE; + + napi_gro_receive(napi, skb); + + size += length; + packets++; + } else { + /* DONE but skb is NULL. Count as drop to avoid silent loss */ + lp->ndev->stats.rx_dropped++; + } + +rearm: + /* Refill descriptor for HW */ + new_skb = napi_alloc_skb(napi, lp->max_frm_size); + if (unlikely((!new_skb))) { + break; + } + + phys = dma_map_single(lp->dev, new_skb->data, + lp->max_frm_size, + DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(lp->dev, phys))) { + if (net_ratelimit()) + netdev_err(lp->ndev, "RX DMA mapping error\n"); + dev_kfree_skb(new_skb); + break; + } + desc_set_rx_phys_addr(lp, phys, cur_p); + + cur_p->control = (lp->max_frm_size - 1) & DMASG_DESCRIPTOR_CONTROL_BYTES; + wmb(); + cur_p->status = 0; + cur_p->skb = new_skb; + + if (++lp->rx_bd_ci >= lp->rx_bd_num) + lp->rx_bd_ci = 0; + cur_p = &lp->rx_bd_v[lp->rx_bd_ci]; + data_cache_invalidate_address(&(cur_p->status)); + } + + lp->ndev->stats.rx_packets += packets; + lp->ndev->stats.rx_bytes += size; + new_err = tsemac_in32(lp, TSEMAC_IF_IN_ERRORS); + prev = lp->prev_rx_err; + delta = (new_err >= prev) ? (new_err - prev) : (new_err + (UINT_MAX - prev) + 1); + lp->ndev->stats.rx_errors += delta; + lp->prev_rx_err = new_err; + + /* + * End-of-poll decision: + * - If we hit budget, return budget to force immediate repoll (IRQ still masked). + * - If head has DONE, keep NAPI alive by returning budget. + * - Otherwise, ring is empty: complete NAPI and re-enable RX IRQ. + */ + + if (packets == budget) + return budget; + + /* work remains, keep polling */ + if (READ_ONCE(lp->rx_bd_v[lp->rx_bd_ci].status) & DMASG_DESCRIPTOR_STATUS_COMPLETED) + return budget; + + if (napi_complete_done(napi, packets)) { + dma_wmb(); + tsemac_dma_out32(lp, DMA_CH_INTERRUPT_ENABLE, DMASG_RX_BASE, lp->rx_dma_cr); + + /* Restart linked list */ + if (!(tsemac_dma_in32(lp, DMA_CH_STATUS, DMASG_RX_BASE) & DMA_CH_STATUS_BUSY)) { + tsemac_dma_out32(lp, DMA_CH_STATUS, DMASG_RX_BASE, DMA_CH_STATUS_LINKED_LIST_START); + } + } + + return packets; +} + +static void tsemac_ethtools_get_pauseparam(struct net_device *ndev, struct ethtool_pauseparam *epauseparm) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + phylink_ethtool_get_pauseparam(lp->phylink, epauseparm); +} + +static int tsemac_ethtools_set_pauseparam(struct net_device *ndev, struct ethtool_pauseparam *epauseparm) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + return phylink_ethtool_set_pauseparam(lp->phylink, epauseparm); +} + +static int +tsemac_ethtools_get_coalesce(struct net_device *ndev, + struct ethtool_coalesce *ecoalesce) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + + ecoalesce->rx_max_coalesced_frames = lp->coalesce_count_rx; + ecoalesce->rx_coalesce_usecs = lp->coalesce_usec_rx; + ecoalesce->tx_max_coalesced_frames = lp->coalesce_count_tx; + ecoalesce->tx_coalesce_usecs = lp->coalesce_usec_tx; + return 0; +} + +static int +tsemac_ethtools_set_coalesce(struct net_device *ndev, + struct ethtool_coalesce *ecoalesce) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + + if (netif_running(ndev)) { + netdev_err(ndev, + "Please stop netif before applying configuration\n"); + return -EBUSY; + } + + if (ecoalesce->rx_max_coalesced_frames) + lp->coalesce_count_rx = ecoalesce->rx_max_coalesced_frames; + if (ecoalesce->rx_coalesce_usecs) + lp->coalesce_usec_rx = ecoalesce->rx_coalesce_usecs; + if (ecoalesce->tx_max_coalesced_frames) + lp->coalesce_count_tx = ecoalesce->tx_max_coalesced_frames; + if (ecoalesce->tx_coalesce_usecs) + lp->coalesce_usec_tx = ecoalesce->tx_coalesce_usecs; + + return 0; +} + +static int +tsemac_ethtools_get_link_ksettings(struct net_device *ndev, + struct ethtool_link_ksettings *cmd) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + + return phylink_ethtool_ksettings_get(lp->phylink, cmd); +} + +static int +tsemac_ethtools_set_link_ksettings(struct net_device *ndev, + const struct ethtool_link_ksettings *cmd) +{ + struct efx_tsemac_local *lp = netdev_priv(ndev); + + return phylink_ethtool_ksettings_set(lp->phylink, cmd); +} + +static int tsemac_ethtools_nway_reset(struct net_device *dev) +{ + struct efx_tsemac_local *lp = netdev_priv(dev); + + return phylink_ethtool_nway_reset(lp->phylink); +} + + +static struct efx_tsemac_local *pcs_to_tsemac_local(struct phylink_pcs *pcs) +{ + return container_of(pcs, struct efx_tsemac_local, pcs); +} + +static void tsemac_pcs_get_state(struct phylink_pcs *pcs, + struct phylink_link_state *state) +{ + struct mdio_device *pcs_phy = pcs_to_tsemac_local(pcs)->pcs_phy; + + phylink_mii_c22_pcs_get_state(pcs_phy, state); +} + +static void tsemac_pcs_an_restart(struct phylink_pcs *pcs) +{ + struct mdio_device *pcs_phy = pcs_to_tsemac_local(pcs)->pcs_phy; + + phylink_mii_c22_pcs_an_restart(pcs_phy); +} + +static int tsemac_pcs_config(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + struct mdio_device *pcs_phy = pcs_to_tsemac_local(pcs)->pcs_phy; + struct net_device *ndev = pcs_to_tsemac_local(pcs)->ndev; + int ret; + + ret = phylink_mii_c22_pcs_config(pcs_phy, mode, interface, advertising); + if (ret < 0) + netdev_warn(ndev, "Failed to configure PCS: %d\n", ret); + + return ret; +} + +static struct phylink_pcs *tsemac_mac_select_pcs(struct phylink_config *config, + phy_interface_t interface) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct efx_tsemac_local *lp = netdev_priv(ndev); + + if (interface == PHY_INTERFACE_MODE_1000BASEX || + interface == PHY_INTERFACE_MODE_SGMII) + return &lp->pcs; + + return NULL; +} +static void tsemac_mac_pcs_get_state(struct phylink_config *config, + struct phylink_link_state *state) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct efx_tsemac_local *lp = netdev_priv(ndev); + u32 cmd_cfg; + + state->interface = lp->phy_mode; + + cmd_cfg = tsemac_in32(lp, TSEMAC_COMMAND_CONFIG); + if (cmd_cfg & ETH_SPEED_MASK_1000) + state->speed = SPEED_1000; + else if (cmd_cfg & ETH_SPEED_MASK_100) + state->speed = SPEED_100; + else { + if (!(cmd_cfg & ETH_SPEED_MASK_10)) + netdev_warn(ndev, "Ethernet speed cannot be recognized. Reset to default value 10Mhz"); + state->speed = SPEED_10; + } + + state->pause = 0; + //TODO: support TX pause + if (cmd_cfg & ETHERNET_CMD_PAUSE_IGNORE) + state->pause |= MLO_PAUSE_RX; + + state->an_complete = 0; + state->duplex = 1; +} + +static void tsemac_mac_an_restart(struct phylink_config *config) +{ + /* Unsupported, do nothing */ +} + +static void tsemac_mac_config(struct phylink_config *config, unsigned int mode, + const struct phylink_link_state *state) +{ + /* nothing to do */ +} + +static void tsemac_mac_link_down(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface) +{ + /* nothing to do */ +} + +static void tsemac_mac_link_up(struct phylink_config *config, + struct phy_device *phy, + unsigned int mode, phy_interface_t interface, + int speed, int duplex, + bool tx_pause, bool rx_pause) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct efx_tsemac_local *lp = netdev_priv(ndev); + u32 cmd_cfg; + + cmd_cfg = tsemac_in32(lp, TSEMAC_COMMAND_CONFIG); + cmd_cfg &= ~ETH_SPEED_MASK; + + switch (speed) { + case SPEED_1000: + cmd_cfg |= ETH_SPEED_MASK_1000; + break; + case SPEED_100: + cmd_cfg |= ETH_SPEED_MASK_100; + break; + case SPEED_10: + cmd_cfg |= ETH_SPEED_MASK_10; + break; + default: + dev_err(&ndev->dev, + "Speed other than 10, 100 or 1Gbps is not supported\n"); + break; + } + + /* Enable PHY LED */ + phy_write(phy, 0x1f, 0x0a43); + phy_write(phy, 0x1b, 0x8011); + phy_write(phy, 0x1c, 0xd73f); + + phy_write(phy, 0x1f, 0xd04); + phy_write(phy, 0x10, 0xe251); + phy_write(phy, 0x1f, 0x0a42); + + //TODO: handle tx_pause + if (rx_pause) + cmd_cfg &= ~ETHERNET_CMD_PAUSE_IGNORE; + else + cmd_cfg |= ETHERNET_CMD_PAUSE_IGNORE; + + tsemac_out32(lp, TSEMAC_COMMAND_CONFIG, cmd_cfg); +} + +int __tsemac_device_reset(struct efx_tsemac_local *lp) +{ + struct net_device *ndev = lp->ndev; + tsemac_out32(lp, ETHERNET_CTRL_PHY_RST, 1); + tsemac_out32(lp, ETHERNET_CTRL_MAC_RST, 1); + mdelay(100); + tsemac_out32(lp, ETHERNET_CTRL_MAC_RST, 0); + mdelay(100); + tsemac_out32(lp, ETHERNET_CTRL_PHY_RST, 0); + mdelay(100); + + /* Disabled TX & RX checksum offload */ + tsemac_hw_set_tx_csum(ndev, false); + tsemac_hw_set_rx_csum(ndev, false); + + return 0; +} + +static int tsemac_device_reset(struct net_device *ndev) +{ + u32 frm_size_temp; + struct efx_tsemac_local *lp = netdev_priv(ndev); + int ret; + + ret = __tsemac_device_reset(lp); + if (ret) + return ret; + + lp->max_frm_size = ETHERNET_MAX_FRAME_SIZE; + lp->prev_tx_err = 0; + lp->prev_rx_err = 0; + + if ((ndev->mtu > ETHERNET_MTU) && (ndev->mtu <= ETHERNET_JUMBO_MTU)) { + frm_size_temp = ndev->mtu + ETHERNET_HDR_SIZE + + ETHERNET_TRL_SIZE; + + if (frm_size_temp <= lp->rxmem) + lp->max_frm_size = frm_size_temp; + } + + ret = tsemac_dma_bd_init(ndev); + if (ret) { + netdev_err(ndev, "%s: descriptor allocation failed\n", + __func__); + return ret; + } + + // enable RX flow control + tsemac_clear_32bit(lp, TSEMAC_COMMAND_CONFIG, ETHERNET_CMD_PAUSE_IGNORE); + + tsemac_clear_32bit(lp, TSEMAC_COMMAND_CONFIG, + ETHERNET_CMD_TX_ENA | ETHERNET_CMD_RX_ENA); + tsemac_set_mac_address(ndev, NULL); + tsemac_set_multicast_list(ndev); + + netif_trans_update(ndev); + + return 0; +} + +static void tsemac_dma_err_handler(struct work_struct *work) +{ + u32 i; + struct dmasg_descriptor *cur_p; + struct efx_tsemac_local *lp = container_of(work, struct efx_tsemac_local, + dma_err_task); + struct net_device *ndev = lp->ndev; + + data_cache_invalidate_all(); + + napi_disable(&lp->napi_tx); + napi_disable(&lp->napi_rx); + + tsemac_clear_32bit(lp, TSEMAC_COMMAND_CONFIG, + ETHERNET_CMD_TX_ENA | ETHERNET_CMD_RX_ENA); + + tsemac_dma_stop(lp); + + for (i = 0; i < lp->tx_bd_num; i++) { + cur_p = &lp->tx_bd_v[i]; + if (cur_p->control & DMASG_DESCRIPTOR_CONTROL_BYTES) { + dma_addr_t phys = desc_get_tx_phys_addr(lp, cur_p); + + dma_unmap_single(lp->dev, phys, + (cur_p->control & DMASG_DESCRIPTOR_CONTROL_BYTES) + 1, + DMA_TO_DEVICE); + } + if (cur_p->skb) + dev_kfree_skb_irq(cur_p->skb); + cur_p->status = 0ULL; + cur_p->control = 0ULL; + cur_p->from = 0ULL; + cur_p->to = 0ULL; + cur_p->next = 0ULL; + cur_p->skb = NULL; + } + + for (i = 0; i < lp->rx_bd_num; i++) { + cur_p = &lp->rx_bd_v[i]; + cur_p->status = 0ULL; + cur_p->from = 0ULL; + cur_p->to = 0ULL; + } + + lp->tx_bd_ci = 0; + lp->tx_bd_tail = 0; + lp->rx_bd_ci = 0; + + tsemac_dma_start(lp); + + tsemac_clear_32bit(lp, TSEMAC_COMMAND_CONFIG, + ETHERNET_CMD_TX_ENA | ETHERNET_CMD_RX_ENA); + tsemac_set_mac_address(ndev, NULL); + tsemac_set_multicast_list(ndev); + napi_enable(&lp->napi_rx); + napi_enable(&lp->napi_tx); +} + +static int tsemac_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct efx_tsemac_local *lp = netdev_priv(ndev); + + unregister_netdev(ndev); + + if (lp->phylink) + phylink_destroy(lp->phylink); + + if (lp->pcs_phy) + put_device(&lp->pcs_phy->dev); + + tsemac_mdio_teardown(lp); + + clk_disable_unprepare(lp->axi_clk); + + free_netdev(ndev); + + return 0; +} + +static int tsemac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct efx_tsemac_local *lp = netdev_priv(dev); + + if (!netif_running(dev)) + return -EBUSY; + + return phylink_mii_ioctl(lp->phylink, rq, cmd); +} + +static void tsemac_shutdown(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + + rtnl_lock(); + netif_device_detach(ndev); + + if (netif_running(ndev)) + dev_close(ndev); + + rtnl_unlock(); +} + +static void tsemac_validate(struct phylink_config *config, + unsigned long *supported, + struct phylink_link_state *state) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct efx_tsemac_local *lp = netdev_priv(ndev); + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; + + /* Only support the mode we are configured for */ + if (state->interface != PHY_INTERFACE_MODE_NA && + state->interface != lp->phy_mode) { + bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS); + return; + } + + phylink_set_port_modes(mask); + phylink_set(mask, Autoneg); + phylink_set(mask, Pause); + phylink_set(mask, Asym_Pause); + phylink_set(mask, 1000baseX_Full); + phylink_set(mask, 10baseT_Full); + phylink_set(mask, 100baseT_Full); + phylink_set(mask, 1000baseT_Full); + phylink_set(mask, MII); + //FIXME: use bitmap_and instead of override + *supported = *mask; + *state->advertising = *mask; + // bitmap_and(supported, supported, mask, + // __ETHTOOL_LINK_MODE_MASK_NBITS); + // bitmap_and(state->advertising, state->advertising, mask, + // __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static const struct phylink_pcs_ops tsemac_pcs_ops = { + .pcs_get_state = tsemac_pcs_get_state, + .pcs_config = tsemac_pcs_config, + .pcs_an_restart = tsemac_pcs_an_restart, +}; + +static const struct phylink_mac_ops tsemac_phylink_ops = { + .validate = tsemac_validate, + // .mac_select_pcs = tsemac_mac_select_pcs, + .mac_pcs_get_state = tsemac_mac_pcs_get_state, + .mac_an_restart = tsemac_mac_an_restart, + .mac_config = tsemac_mac_config, + .mac_link_down = tsemac_mac_link_down, + .mac_link_up = tsemac_mac_link_up, +}; + +static const struct net_device_ops tsemac_netdev_ops = { + .ndo_open = tsemac_open, + .ndo_stop = tsemac_stop, + .ndo_start_xmit = tsemac_start_xmit, + .ndo_change_mtu = tsemac_change_mtu, + .ndo_set_mac_address = netdev_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_do_ioctl = tsemac_ioctl, + .ndo_set_rx_mode = tsemac_set_multicast_list, + .ndo_set_features = tsemac_set_features, + .ndo_fix_features = tsemac_fix_features, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = tsemac_poll_controller, +#endif +}; + +static const struct ethtool_ops tsemac_ethtool_ops = { + .supported_coalesce_params = ETHTOOL_COALESCE_MAX_FRAMES | + ETHTOOL_COALESCE_USECS, + .get_drvinfo = tsemac_ethtools_get_drvinfo, + .get_regs_len = tsemac_ethtools_get_regs_len, + .get_regs = tsemac_ethtools_get_regs, + .get_link = ethtool_op_get_link, + .get_ringparam = tsemac_ethtools_get_ringparam, + .set_ringparam = tsemac_ethtools_set_ringparam, + .get_pauseparam = tsemac_ethtools_get_pauseparam, + .set_pauseparam = tsemac_ethtools_set_pauseparam, + .get_coalesce = tsemac_ethtools_get_coalesce, + .set_coalesce = tsemac_ethtools_set_coalesce, + .get_link_ksettings = tsemac_ethtools_get_link_ksettings, + .set_link_ksettings = tsemac_ethtools_set_link_ksettings, + .nway_reset = tsemac_ethtools_nway_reset +}; + +static struct platform_driver efinix_tse_driver = { + .probe = tsemac_probe, + .remove = tsemac_remove, + .shutdown = tsemac_shutdown, + .driver = { + .name = "efinix_tsemac", + .of_match_table = tsemac_of_match, + }, +}; + +static int tsemac_probe(struct platform_device *pdev) +{ + int ret; + struct device_node *np; + struct efx_tsemac_local *lp; + struct net_device *ndev; + struct resource *ethres; + const void *mac_addr; + int addr_width = 32; + u32 value; + + ndev = alloc_etherdev(sizeof(*lp)); + if (!ndev) + return -ENOMEM; + + platform_set_drvdata(pdev, ndev); + + SET_NETDEV_DEV(ndev, &pdev->dev); + ndev->flags &= ~IFF_MULTICAST; + ndev->netdev_ops = &tsemac_netdev_ops; + ndev->ethtool_ops = &tsemac_ethtool_ops; + + /* MTU range: 64 - 1500 */ + ndev->min_mtu = 64; + ndev->max_mtu = ETHERNET_MTU; + + lp = netdev_priv(ndev); + lp->ndev = ndev; + lp->dev = &pdev->dev; + lp->rx_bd_num = RX_BD_NUM_DEFAULT; + lp->tx_bd_num = TX_BD_NUM_DEFAULT; + + spin_lock_init(&lp->lock); + netif_napi_add(ndev, &lp->napi_rx, tsemac_rx_poll, NAPI_POLL_WEIGHT); + netif_napi_add(ndev, &lp->napi_tx, tsemac_tx_poll, NAPI_POLL_WEIGHT); + + /* get the first clock as AXI clock */ + lp->axi_clk = devm_clk_get_optional(&pdev->dev, NULL); + if (IS_ERR(lp->axi_clk)) { + ret = PTR_ERR(lp->axi_clk); + dev_err(&pdev->dev, "Unable to get AXI clock: %d\n", ret); + goto free_netdev; + } + ret = clk_prepare_enable(lp->axi_clk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable AXI clock: %d\n", ret); + goto free_netdev; + } + + /* Map device registers */ + lp->regs = devm_platform_get_and_ioremap_resource(pdev, 0, ðres); + if (IS_ERR(lp->regs)) { + dev_err(&pdev->dev, "could not map Efinix TSEMAC regs.\n"); + ret = PTR_ERR(lp->regs); + goto cleanup_clk; + } + lp->regs_start = ethres->start; + + /* Setup checksum offload, but default to off if not specified */ + lp->features = 0; + + /* Advertise capability */ + ndev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM | NETIF_F_SG; + ndev->features |= NETIF_F_SG; + + ret = of_property_read_u32(pdev->dev.of_node, "efx,txcsum", &value); + if (!ret) { + switch (value) { + case 1: + lp->csum_offload_on_tx_path = + TSEMAC_FEATURE_FULL_TX_CSUM; + lp->features |= TSEMAC_FEATURE_FULL_TX_CSUM; + ndev->features |= NETIF_F_HW_CSUM; + tsemac_hw_set_tx_csum(ndev, true); + break; + default: + lp->csum_offload_on_tx_path = TSEMAC_NO_CSUM_OFFLOAD; + ndev->features &= ~NETIF_F_HW_CSUM; + tsemac_hw_set_tx_csum(ndev, false); + } + } + ret = of_property_read_u32(pdev->dev.of_node, "efx,rxcsum", &value); + if (!ret) { + switch (value) { + case 1: + lp->csum_offload_on_rx_path = + TSEMAC_FEATURE_FULL_RX_CSUM; + lp->features |= TSEMAC_FEATURE_FULL_RX_CSUM; + ndev->features |= NETIF_F_RXCSUM; + tsemac_hw_set_rx_csum(ndev, true); + break; + default: + lp->csum_offload_on_rx_path = TSEMAC_NO_CSUM_OFFLOAD; + ndev->features &= ~NETIF_F_RXCSUM; + tsemac_hw_set_rx_csum(ndev, false); + } + } + + of_property_read_u32(pdev->dev.of_node, "efx,rxmem", &lp->rxmem); + + ret = of_property_read_u32(pdev->dev.of_node, "efx,phy-type", &value); + if (!ret) { + netdev_warn(ndev, "Please upgrade your device tree binary blob to use phy-mode"); + switch (value) { + case TSEMAC_PHY_TYPE_MII: + lp->phy_mode = PHY_INTERFACE_MODE_MII; + break; + case TSEMAC_PHY_TYPE_GMII: + lp->phy_mode = PHY_INTERFACE_MODE_GMII; + break; + case TSEMAC_PHY_TYPE_RGMII_2_0: + lp->phy_mode = PHY_INTERFACE_MODE_RGMII_ID; + break; + case TSEMAC_PHY_TYPE_SGMII: + lp->phy_mode = PHY_INTERFACE_MODE_SGMII; + break; + case TSEMAC_PHY_TYPE_1000BASE_X: + lp->phy_mode = PHY_INTERFACE_MODE_1000BASEX; + break; + default: + ret = -EINVAL; + goto cleanup_clk; + } + } else { + ret = of_get_phy_mode(pdev->dev.of_node, &lp->phy_mode); + if (ret) + goto cleanup_clk; + } + + /* Find the DMA node, map the DMA registers, and decode the DMA IRQs */ + np = of_parse_phandle(pdev->dev.of_node, "axistream-connected", 0); + if (np) { + struct resource dmares; + + ret = of_address_to_resource(np, 0, &dmares); + if (ret) { + dev_err(&pdev->dev, + "unable to get DMA resource\n"); + of_node_put(np); + goto cleanup_clk; + } + lp->dma_regs = devm_ioremap_resource(&pdev->dev, + &dmares); + lp->rx_irq = irq_of_parse_and_map(np, 0); + lp->tx_irq = irq_of_parse_and_map(np, 1); + of_node_put(np); + } else { + lp->dma_regs = devm_platform_get_and_ioremap_resource(pdev, 1, NULL); + lp->rx_irq = platform_get_irq(pdev, 0); + lp->tx_irq = platform_get_irq(pdev, 1); + } + if (IS_ERR(lp->dma_regs)) { + dev_err(&pdev->dev, "could not map DMA regs\n"); + ret = PTR_ERR(lp->dma_regs); + goto cleanup_clk; + } + if ((lp->rx_irq <= 0) || (lp->tx_irq <= 0)) { + dev_err(&pdev->dev, "could not determine irqs\n"); + ret = -ENOMEM; + goto cleanup_clk; + } + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(addr_width)); + if (ret) { + dev_err(&pdev->dev, "No suitable DMA available\n"); + goto cleanup_clk; + } + + /* Retrieve the MAC address from device */ + mac_addr = of_get_mac_address(pdev->dev.of_node); + if (IS_ERR(mac_addr)) { + dev_warn(&pdev->dev, "could not find MAC address property: %ld\n", + PTR_ERR(mac_addr)); + mac_addr = NULL; + } + tsemac_set_mac_address(ndev, mac_addr); + + lp->coalesce_count_rx = EFXTSE_RX_COUNT; + lp->coalesce_usec_rx = EFXTSE_RX_USEC; + lp->coalesce_count_tx = EFXTSE_TX_COUNT; + lp->coalesce_usec_tx = EFXTSE_TX_USEC; + + /* Reset core now that clocks are enabled, prior to accessing MDIO */ + ret = __tsemac_device_reset(lp); + if (ret) + goto cleanup_clk; + + tsemac_clear_32bit(lp, TSEMAC_COMMAND_CONFIG, + ETHERNET_CMD_TX_ENA | ETHERNET_CMD_RX_ENA); + ret = tsemac_mdio_setup(lp); + if (ret) + dev_warn(&pdev->dev, "error registering MDIO bus: %d\n", ret); + + if (lp->phy_mode == PHY_INTERFACE_MODE_SGMII || + lp->phy_mode == PHY_INTERFACE_MODE_1000BASEX) { + np = of_parse_phandle(pdev->dev.of_node, "pcs-handle", 0); + if (!np) { + dev_err(&pdev->dev, "pcs-handle required for 1000BaseX/SGMII\n"); + ret = -EINVAL; + goto cleanup_mdio; + } + lp->pcs_phy = of_mdio_find_device(np); + if (!lp->pcs_phy) { + ret = -EPROBE_DEFER; + of_node_put(np); + goto cleanup_mdio; + } + of_node_put(np); + lp->pcs.ops = &tsemac_pcs_ops; + lp->pcs.poll = true; + } + + lp->phylink_config.dev = &ndev->dev; + lp->phylink_config.type = PHYLINK_NETDEV; + // lp->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | + // MAC_10FD | MAC_100FD | MAC_1000FD; //TODO: verify what can the MAC do + + + lp->phylink = phylink_create(&lp->phylink_config, pdev->dev.fwnode, + lp->phy_mode, + &tsemac_phylink_ops); + if (IS_ERR(lp->phylink)) { + ret = PTR_ERR(lp->phylink); + dev_err(&pdev->dev, "phylink_create error (%i)\n", ret); + goto cleanup_mdio; + } + + ret = register_netdev(lp->ndev); + if (ret) { + dev_err(lp->dev, "register_netdev() error (%i)\n", ret); + goto cleanup_phylink; + } + + return 0; + +cleanup_phylink: + phylink_destroy(lp->phylink); +cleanup_mdio: + if (lp->pcs_phy) + put_device(&lp->pcs_phy->dev); + if (lp->mii_bus) + tsemac_mdio_teardown(lp); +cleanup_clk: + clk_disable_unprepare(lp->axi_clk); +free_netdev: + free_netdev(ndev); + + return ret; +} + +module_platform_driver(efinix_tse_driver); + +MODULE_AUTHOR("Efinix"); +MODULE_DESCRIPTION("Efinix Triple Speed Ethernet MAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/ethernet/efinix/efinix_tse.h b/drivers/net/ethernet/efinix/efinix_tse.h new file mode 100644 index 000000000..e103ec66b --- /dev/null +++ b/drivers/net/ethernet/efinix/efinix_tse.h @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Efinix, Inc. All rights reserved. + */ + +#ifndef EFINIX_TSE_H +#define EFINIX_TSE_H + +#include +#include +#include +#include +#include +#include + +#define ETHERNET_HDR_SIZE 14 /* Size of Ethernet header */ +#define ETHERNET_TRL_SIZE 4 /* Size of Ethernet trailer (FCS) */ +#define ETHERNET_MTU 1500 /* Max MTU of an Ethernet frame */ +#define ETHERNET_JUMBO_MTU 9000 /* Max MTU of a jumbo Eth. frame */ +#define ETHERNET_MAX_FRAME_SIZE (ETHERNET_HDR_SIZE + ETHERNET_TRL_SIZE + ETHERNET_MTU) + +#define EFXTSE_TX_COUNT 0 +#define EFXTSE_TX_USEC 0 +#define EFXTSE_RX_COUNT 0 +#define EFXTSE_RX_USEC 0 + +#define BIT_0 (1U << 0) +#define BIT_1 (1U << 1) +#define BIT_2 (1U << 2) +#define BIT_3 (1U << 3) +#define BIT_4 (1U << 4) +#define BIT_5 (1U << 5) +#define BIT_6 (1U << 6) +#define BIT_7 (1U << 7) +#define BIT_8 (1U << 8) +#define BIT_9 (1U << 9) +#define BIT_10 (1U << 10) +#define BIT_11 (1U << 11) +#define BIT_12 (1U << 12) +#define BIT_13 (1U << 13) +#define BIT_14 (1U << 14) +#define BIT_15 (1U << 15) +#define BIT_16 (1U << 16) +#define BIT_17 (1U << 17) +#define BIT_18 (1U << 18) +#define BIT_19 (1U << 19) +#define BIT_20 (1U << 20) +#define BIT_21 (1U << 21) +#define BIT_22 (1U << 22) +#define BIT_23 (1U << 23) +#define BIT_24 (1U << 24) +#define BIT_25 (1U << 25) +#define BIT_26 (1U << 26) +#define BIT_27 (1U << 27) +#define BIT_28 (1U << 28) +#define BIT_29 (1U << 29) +#define BIT_30 (1U << 30) +#define BIT_31 (1U << 31) + +#define ETHERNET_CMD_TX_ENA BIT_0 +#define ETHERNET_CMD_RX_ENA BIT_1 +#define ETHERNET_CMD_XON_GEN BIT_2 +#define ETHERNET_CMD_PROMIS_EN BIT_4 +#define ETHERNET_CMD_CRC_FWD BIT_6 +#define ETHERNET_CMD_PAUSE_IGNORE BIT_8 +#define ETHERNET_CMD_TX_ADDR_INS BIT_9 +#define ETHERNET_CMD_RGMII_LOOP_ENA BIT_15 +#define ETHERNET_CMD_ETH_SPEED BIT_16 +#define ETHERNET_CMD_XOFF_GEN BIT_22 +#define ETHERNET_CMD_CNT_RESET BIT_31 + +//MAC Configuration Registers +#define TSEMAC_VERSION 0x0000 +#define TSEMAC_COMMAND_CONFIG 0x0008 +#define TSEMAC_MAC_ADDR_LO 0x000C +#define TSEMAC_MAC_ADDR_HI 0x0010 +#define TSEMAC_FRM_LENGHT 0x0014 +#define TSEMAC_PAUSE_QUANT 0x0018 +#define TSEMAC_TX_IPG_LEN 0x005C + +// //MDIO Configuration Registers +// #define TSEMAC_DIVIDER_PRE 0x0100 +// #define TSEMAC_RD_WR_EN 0x0104 +// #define TSEMAC_REG_PHY_ADDR 0x0108 +// #define TSEMAC_WR_DATA 0x010C +// #define TSEMAC_RD_DATA 0x0110 +// #define TSEMAC_STATUS 0x0114 +#define TSEMAC_A_FRAMES_TRANSMITTED_OK 0x68 +#define TSEMAC_A_FRAMES_RECEIVED_OK 0x6C +#define TSEMAC_CRC_ERRORS 0x70 +#define TSEMAC_IF_IN_ERRORS 0x88 +#define TSEMAC_IF_OUT_ERRORS 0x8C +#define TSEMAC_ETHER_STATS_UNDER_SIZE_PKTS 0xB8 + +//Receive Supplementary Registers +#define TSEMAC_BOARD_FILTER_EN 0x0140 +#define TSEMAC_MAC_ADDR_MAKE_LO 0x0144 +#define TSEMAC_MAC_ADDR_MAKE_HI 0x0148 +#define TSEMAC_TX_DST_ADDR_INS 0x0180 +#define TSEMAC_DST_MAC_ADDR_LO 0x0184 +#define TSEMAC_DST_MAC_ADDR_HI 0x0188 + +// additional TSEMAC control +#define ETHERNET_CTRL_MAC_RST 0x200 +#define ETHERNET_CTRL_PHY_RST 0x204 +#define ETHERNET_CTRL_DMA_RX_RESET 0x208 +#define ETHERNET_CTRL_DMA_TX_RESET 0x20C +#define ETHERNET_CTRL_HW_RX_CHECKSUM_EN 0x210 +#define ETHERNET_CTRL_HW_TX_CHECKSUM_EN 0x214 + +#define TSEMAC_PHY_TYPE_MII 0 +#define TSEMAC_PHY_TYPE_GMII 1 +#define TSEMAC_PHY_TYPE_RGMII_1_3 2 +#define TSEMAC_PHY_TYPE_RGMII_2_0 3 +#define TSEMAC_PHY_TYPE_SGMII 4 +#define TSEMAC_PHY_TYPE_1000BASE_X 5 + +#define ETH_SPEED_MASK 0x00070000 +#define ETH_SPEED_MASK_10 0x00010000 +#define ETH_SPEED_MASK_100 0x00020000 +#define ETH_SPEED_MASK_1000 0x00040000 + + +#define dmasg_ca(base, channel) (base + channel*0x80) +#define DMA_CH_BYTE_PER_BURST_MASK 0xFFF +#define DMA_CH_INPUT_ADDRESS 0x00 +#define DMA_CH_INPUT_STREAM 0x08 +#define DMA_CH_INPUT_CONFIG 0x0C +#define DMA_CH_INPUT_CONFIG_MEMORY BIT_12 +#define DMA_CH_INPUT_CONFIG_COMPLETION_ON_PACKET BIT_13 +#define DMA_CH_INPUT_CONFIG_WAIT_ON_PACKET BIT_14 +#define DMA_CH_OUTPUT_ADDRESS 0x10 +#define DMA_CH_OUTPUT_STREAM 0x18 +#define DMA_CH_OUTPUT_CONFIG 0x1C +#define DMA_CH_OUTPUT_CONFIG_MEMORY BIT_12 +#define DMA_CH_OUTPUT_CONFIG_LAST BIT_13 +#define DMA_CH_DIRECT_BYTES 0x20 +#define DMA_CH_STATUS 0x2C +#define DMA_CH_STATUS_DIRECT_START BIT_0 +#define DMA_CH_STATUS_BUSY BIT_0 +#define DMA_CH_STATUS_SELF_RESTART BIT_1 +#define DMA_CH_STATUS_STOP BIT_2 +#define DMA_CH_STATUS_LINKED_LIST_START BIT_4 +#define DMA_CH_FIFO 0x40 +#define DMA_CH_PRIORITY 0x44 +#define DMA_CH_INTERRUPT_ENABLE 0x50 +#define DMA_CH_INTERRUPT_PENDING 0x54 +#define DMA_CH_PROGRESS_BYTES 0x60 +#define DMA_CH_LINKED_LIST_HEAD 0x70 +#define DMA_CH_PORT_BASE 0 +#define DMA_CH_SINK_ID_BASE 8 +#define DMA_CH_DEST_ID_BASE 16 +#define DMA_CH_PRIORITY_MASK 0x7 +#define DMA_CH_WEIGHT_MASK 0x7 +// Interrupt at the end of each descriptor +#define DMA_CH_INTERRUPT_DESCRIPTOR_COMPLETION_MASK BIT_0 +// Interrupt at the middle of each descriptor, require the half_completion_interrupt option to be enabled for the channel +#define DMA_CH_INTERRUPT_DESCRIPTOR_COMPLETION_HALF_MASK BIT_1 +// Interrupt when the channel is going off (not busy anymore) +#define DMA_CH_INTERRUPT_CHANNEL_COMPLETION_MASK BIT_2 +// Interrupt each time that a linked list's descriptor status field is updated +#define DMA_CH_INTERRUPT_LINKED_LIST_UPDATE_MASK BIT_3 +// Interrupt each time a S -> M channel has done transferring a packet into the memory +#define DMA_CH_INTERRUPT_INPUT_PACKET_MASK BIT_4 +// Number of bytes (minus one) reserved at the descriptor FROM/TO addresses. +// If you want to transfer 10 bytes, this field should take the value 9 +#define DMASG_DESCRIPTOR_CONTROL_BYTES 0x7FFFFFF +//Only for M -> S transfers, specify if a end of packet should be send at the end of the transfer +#define DMASG_DESCRIPTOR_CONTROL_END_OF_PACKET BIT_30 +// Number of bytes transferred by the DMA for this descriptor. +#define DMASG_DESCRIPTOR_STATUS_BYTES 0x7FFFFFF +// Only for S -> M transfers, specify if the descriptor mark the end of a received packet +// Can be used when the dmasg_input_stream function is called with completion_on_packet set. +#define DMASG_DESCRIPTOR_STATUS_END_OF_PACKET BIT_30 +// Specify if the descriptor was executed by the DMA. +// If the DMA read a completed descriptor, the channel is stopped and will produce a CHANNEL_COMPLETION interrupt. +#define DMASG_DESCRIPTOR_STATUS_COMPLETED BIT_31 +#define DMASG_RX_BASE 0x0 +#define DMASG_TX_BASE 0x80 + +#define DMASG_IRQ_ALL_MASK 0x1F + +#define TSEMAC_FEATURE_PARTIAL_RX_CSUM (1 << 0) +#define TSEMAC_FEATURE_PARTIAL_TX_CSUM (1 << 1) +#define TSEMAC_FEATURE_FULL_RX_CSUM (1 << 2) +#define TSEMAC_FEATURE_FULL_TX_CSUM (1 << 3) +#define TSEMAC_FEATURE_DMA_64BIT (1 << 4) + +#define TSEMAC_NO_CSUM_OFFLOAD 0 + + +asm(".set regnum_x0 , 0"); +asm(".set regnum_x1 , 1"); +asm(".set regnum_x2 , 2"); +asm(".set regnum_x3 , 3"); +asm(".set regnum_x4 , 4"); +asm(".set regnum_x5 , 5"); +asm(".set regnum_x6 , 6"); +asm(".set regnum_x7 , 7"); +asm(".set regnum_x8 , 8"); +asm(".set regnum_x9 , 9"); +asm(".set regnum_x10 , 10"); +asm(".set regnum_x11 , 11"); +asm(".set regnum_x12 , 12"); +asm(".set regnum_x13 , 13"); +asm(".set regnum_x14 , 14"); +asm(".set regnum_x15 , 15"); +asm(".set regnum_x16 , 16"); +asm(".set regnum_x17 , 17"); +asm(".set regnum_x18 , 18"); +asm(".set regnum_x19 , 19"); +asm(".set regnum_x20 , 20"); +asm(".set regnum_x21 , 21"); +asm(".set regnum_x22 , 22"); +asm(".set regnum_x23 , 23"); +asm(".set regnum_x24 , 24"); +asm(".set regnum_x25 , 25"); +asm(".set regnum_x26 , 26"); +asm(".set regnum_x27 , 27"); +asm(".set regnum_x28 , 28"); +asm(".set regnum_x29 , 29"); +asm(".set regnum_x30 , 30"); +asm(".set regnum_x31 , 31"); + +asm(".set regnum_zero, 0"); +asm(".set regnum_ra , 1"); +asm(".set regnum_sp , 2"); +asm(".set regnum_gp , 3"); +asm(".set regnum_tp , 4"); +asm(".set regnum_t0 , 5"); +asm(".set regnum_t1 , 6"); +asm(".set regnum_t2 , 7"); +asm(".set regnum_s0 , 8"); +asm(".set regnum_s1 , 9"); +asm(".set regnum_a0 , 10"); +asm(".set regnum_a1 , 11"); +asm(".set regnum_a2 , 12"); +asm(".set regnum_a3 , 13"); +asm(".set regnum_a4 , 14"); +asm(".set regnum_a5 , 15"); +asm(".set regnum_a6 , 16"); +asm(".set regnum_a7 , 17"); +asm(".set regnum_s2 , 18"); +asm(".set regnum_s3 , 19"); +asm(".set regnum_s4 , 20"); +asm(".set regnum_s5 , 21"); +asm(".set regnum_s6 , 22"); +asm(".set regnum_s7 , 23"); +asm(".set regnum_s8 , 24"); +asm(".set regnum_s9 , 25"); +asm(".set regnum_s10 , 26"); +asm(".set regnum_s11 , 27"); +asm(".set regnum_t3 , 28"); +asm(".set regnum_t4 , 29"); +asm(".set regnum_t5 , 30"); +asm(".set regnum_t6 , 31"); + +#if defined(CONFIG_32BIT) +//Invalidate the whole data cache +#define data_cache_invalidate_all() asm(".word(0x500F)"); + +//Invalidate all the data cache ways lines which could store the given address +#define data_cache_invalidate_address(address) \ +({ \ + asm volatile( \ + ".word ((0x500F) | (regnum_%0 << 15));" \ + : \ + : "r" (address) \ + ); \ +}) +#else +#define data_cache_invalidate_all() +#define data_cache_invalidate_address(address) +#endif /* CONFIG_32_BIT */ + +//Invalidate the whole instruction cache +#define instruction_cache_invalidate() asm("fence.i"); + + + +struct dmasg_descriptor { + // See all DMASG_DESCRIPTOR_STATUS_* defines + // Updated by the DMA at the end of each descriptor and when a S -> M packet is completely transferred into memory + u32 status; + // See all DMASG_DESCRIPTOR_CONTROL_* defines + u32 control; + // For M -> ? transfers, memory address of the input data + u64 from; + // For ? -> M transfers, memory address of the output data + u64 to; + // Memory address of the next descriptor + u64 next; + struct sk_buff *skb; +} __aligned(0x40); + +struct efx_tsemac_local { + struct net_device *ndev; + struct device *dev; + + /* Connection to PHY device */ + struct phylink *phylink; + struct phylink_config phylink_config; + + struct mdio_device *pcs_phy; + struct phylink_pcs pcs; + + struct clk *axi_clk; + /* MDIO bus data */ + struct mii_bus *mii_bus; /* MII bus reference */ + u8 mii_clk_div; + + /* IO registers, dma functions and IRQs */ + resource_size_t regs_start; + void __iomem *regs; + void __iomem *dma_regs; + + struct work_struct dma_err_task; + + int tx_irq; + int rx_irq; + int eth_irq; + phy_interface_t phy_mode; + + /* Buffer descriptors */ + struct napi_struct napi_rx; + struct napi_struct napi_tx; + + struct dmasg_descriptor *tx_bd_v; + dma_addr_t tx_bd_p; + u32 tx_bd_ci; + u32 tx_bd_tail; + u32 tx_dma_cr; + u32 tx_bd_num; + + struct dmasg_descriptor *rx_bd_v; + dma_addr_t rx_bd_p; + u32 rx_bd_ci; + u32 rx_dma_cr; + u32 rx_bd_num; + + u64_stats_t rx_packets; + u64_stats_t rx_bytes; + struct u64_stats_sync rx_stat_sync; + u64_stats_t tx_packets; + u64_stats_t tx_bytes; + struct u64_stats_sync tx_stat_sync; + u32 options; + u32 features; + + u32 max_frm_size; + u32 rxmem; + + int csum_offload_on_tx_path; + int csum_offload_on_rx_path; + + u32 coalesce_count_rx; + u32 coalesce_usec_rx; + u32 coalesce_count_tx; + u32 coalesce_usec_tx; + + /* statistics */ + u64 prev_tx_err; + u64 prev_rx_err; + + spinlock_t lock; +}; + +static inline void tsemac_lock_mii(struct efx_tsemac_local *lp) +{ + if (lp->mii_bus) + mutex_lock(&lp->mii_bus->mdio_lock); +} + +static inline void tsemac_unlock_mii(struct efx_tsemac_local *lp) +{ + if (lp->mii_bus) + mutex_unlock(&lp->mii_bus->mdio_lock); +} + +static inline void desc_set_tx_phys_addr(struct efx_tsemac_local *lp, dma_addr_t addr, + struct dmasg_descriptor *desc) +{ + desc->from = lower_32_bits(addr); +} + +static inline dma_addr_t desc_get_tx_phys_addr(struct efx_tsemac_local *lp, + struct dmasg_descriptor *desc) +{ + return desc->from; +} + +static inline void desc_set_rx_phys_addr(struct efx_tsemac_local *lp, dma_addr_t addr, + struct dmasg_descriptor *desc) +{ + desc->to = lower_32_bits(addr); +} + +static inline dma_addr_t desc_get_rx_phys_addr(struct efx_tsemac_local *lp, + struct dmasg_descriptor *desc) +{ + return desc->to; +} + +static inline u32 tsemac_in32(struct efx_tsemac_local *lp, off_t reg) +{ + return ioread32(lp->regs + reg); +} + +static inline void tsemac_out32(struct efx_tsemac_local *lp, off_t reg, + u32 value) +{ + iowrite32(value, lp->regs + reg); +} + +static inline void tsemac_set_32bit(struct efx_tsemac_local *lp, off_t reg, + u32 value) +{ + u32 temp = tsemac_in32(lp, reg); + temp |= value; + tsemac_out32(lp, reg, temp); +} + +static inline void tsemac_clear_32bit(struct efx_tsemac_local *lp, off_t reg, + u32 value) +{ + u32 temp = tsemac_in32(lp, reg); + temp &= ~value; + tsemac_out32(lp, reg, temp); +} + +static inline u32 tsemac_dma_in32(struct efx_tsemac_local *lp, off_t reg, off_t ch_offset) +{ + return ioread32(lp->dma_regs + reg + ch_offset); +} + +static inline void tsemac_dma_out32(struct efx_tsemac_local *lp, off_t reg, off_t ch_offset, + u32 value) +{ + return iowrite32(value, lp->dma_regs + reg + ch_offset); +} + +int __tsemac_device_reset(struct efx_tsemac_local *lp); + +int tsemac_free_tx_chain(struct efx_tsemac_local *lp, u32 first_bd, + int nr_bds, bool force, u32 *sizep, int budget); + +void tsemac_dma_stop(struct efx_tsemac_local *lp); + +void tsemac_dma_bd_release(struct net_device *ndev); + +int tsemac_dma_bd_init(struct net_device *ndev); + +void tsemac_dma_start(struct efx_tsemac_local *lp); + +void tsemac_mdio_teardown(struct efx_tsemac_local *lp); + +int tsemac_mdio_setup(struct efx_tsemac_local *lp); + +#endif diff --git a/drivers/net/ethernet/efinix/efinix_tse_dma.c b/drivers/net/ethernet/efinix/efinix_tse_dma.c new file mode 100644 index 000000000..bf4fd7293 --- /dev/null +++ b/drivers/net/ethernet/efinix/efinix_tse_dma.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DMA driver for the Efinix Triple Speed Ethernet device + * + * Copyright (c) 2023 Efinix, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "efinix_tse.h" + +#define DMASG_BYTE_PER_BURST 64 + +int tsemac_free_tx_chain(struct efx_tsemac_local *lp, u32 first_bd, + int nr_bds, bool force, u32 *sizep, int budget) +{ + struct dmasg_descriptor *cur_p; + u32 status; + dma_addr_t phys; + int i; + u32 completed; + u32 num; + + for (i = 0; i < nr_bds; i++) { + num = (first_bd + i) % lp->tx_bd_num; + cur_p = &lp->tx_bd_v[num]; + + spin_lock(&lp->lock); + data_cache_invalidate_address(&(cur_p->status)); + data_cache_invalidate_address(&(cur_p->control)); + status = cur_p->status; + + completed = (status & DMASG_DESCRIPTOR_STATUS_COMPLETED) + && (cur_p->control != 0); + spin_unlock(&lp->lock); + + /* If force is not specified, clean up only descriptors + * that have been completed by the MAC. + */ + + if (!force && !completed) + break; + + /* Ensure we see complete descriptor update */ + dma_rmb(); + phys = desc_get_tx_phys_addr(lp, cur_p); + dma_unmap_single(lp->dev, phys, + (cur_p->control & DMASG_DESCRIPTOR_CONTROL_BYTES) + 1, + DMA_TO_DEVICE); + + if (cur_p->skb && completed) + napi_consume_skb(cur_p->skb, budget); + + if (sizep) + *sizep += (cur_p->control & DMASG_DESCRIPTOR_CONTROL_BYTES) + 1; + cur_p->skb = NULL; + /* ensure our transmit path and device don't prematurely see status cleared */ + wmb(); + cur_p->control = 0; + cur_p->status = DMASG_DESCRIPTOR_STATUS_COMPLETED; + + lp->tx_bd_ci += 1; + if (lp->tx_bd_ci >= lp->tx_bd_num) + lp->tx_bd_ci %= lp->tx_bd_num; + + } + + return i; +} + +void tsemac_dma_bd_release(struct net_device *ndev) +{ + int i; + struct efx_tsemac_local *lp = netdev_priv(ndev); + + /* If we end up here, tx_bd_v must have been DMA allocated. */ + dma_free_coherent(lp->dev, + sizeof(*lp->tx_bd_v) * lp->tx_bd_num, + lp->tx_bd_v, + lp->tx_bd_p); + + if (!lp->rx_bd_v) + return; + + for (i = 0; i < lp->rx_bd_num; i++) { + dma_addr_t phys; + + /* A NULL skb means this descriptor has not been initialised + * at all. + */ + if (!lp->rx_bd_v[i].skb) + break; + + dev_kfree_skb(lp->rx_bd_v[i].skb); + + /* For each descriptor, we programmed control with the (non-zero) + * descriptor size, after it had been successfully allocated. + * So a non-zero value in there means we need to unmap it. + */ + if (lp->rx_bd_v[i].control) { + phys = desc_get_rx_phys_addr(lp, &lp->rx_bd_v[i]); + dma_unmap_single(lp->dev, phys, + lp->max_frm_size, DMA_FROM_DEVICE); + } + } + + dma_free_coherent(lp->dev, + sizeof(*lp->rx_bd_v) * lp->rx_bd_num, + lp->rx_bd_v, + lp->rx_bd_p); +} + +void tsemac_dma_stop(struct efx_tsemac_local *lp) +{ + int count; + u32 cr, sr; + + cr = DMA_CH_STATUS_STOP; + tsemac_dma_out32(lp, DMA_CH_STATUS, DMASG_RX_BASE, cr); + tsemac_dma_out32(lp, DMA_CH_INTERRUPT_ENABLE, DMASG_RX_BASE, 0); + synchronize_irq(lp->rx_irq); + + cr = DMA_CH_STATUS_STOP; + tsemac_dma_out32(lp, DMA_CH_STATUS, DMASG_TX_BASE, cr); + tsemac_dma_out32(lp, DMA_CH_INTERRUPT_ENABLE, DMASG_TX_BASE, 0); + synchronize_irq(lp->tx_irq); + + sr = tsemac_dma_in32(lp, DMA_CH_STATUS, DMASG_RX_BASE); + for (count = 0; (sr & DMA_CH_STATUS_BUSY) && count < 5; ++count) { + msleep(20); + sr = tsemac_dma_in32(lp, DMA_CH_STATUS, DMASG_RX_BASE); + } + + sr = tsemac_dma_in32(lp, DMA_CH_STATUS, DMASG_TX_BASE); + for (count = 0; (sr & DMA_CH_STATUS_BUSY) && count < 5; ++count) { + msleep(20); + sr = tsemac_dma_in32(lp, DMA_CH_STATUS, DMASG_TX_BASE); + } + + /* Do a reset to ensure DMA is really stopped */ + tsemac_lock_mii(lp); + __tsemac_device_reset(lp); + tsemac_unlock_mii(lp); +} + +void tsemac_dma_start(struct efx_tsemac_local *lp) +{ + tsemac_dma_out32(lp, DMA_CH_INPUT_CONFIG, DMASG_RX_BASE, + DMA_CH_INPUT_CONFIG_COMPLETION_ON_PACKET | + DMA_CH_INPUT_CONFIG_WAIT_ON_PACKET); + tsemac_dma_out32(lp, DMA_CH_OUTPUT_CONFIG, DMASG_RX_BASE, + DMA_CH_OUTPUT_CONFIG_MEMORY | + ((DMASG_BYTE_PER_BURST-1) & DMA_CH_BYTE_PER_BURST_MASK)); + tsemac_dma_out32(lp, DMA_CH_LINKED_LIST_HEAD, DMASG_RX_BASE, lower_32_bits(lp->rx_bd_p)); + tsemac_dma_out32(lp, DMA_CH_PRIORITY, DMASG_RX_BASE, 0 & DMA_CH_PRIORITY_MASK); + lp->rx_dma_cr = DMA_CH_INTERRUPT_LINKED_LIST_UPDATE_MASK; + tsemac_dma_out32(lp, DMA_CH_INTERRUPT_ENABLE, DMASG_RX_BASE, lp->rx_dma_cr); + tsemac_dma_out32(lp, DMA_CH_STATUS, DMASG_RX_BASE, DMA_CH_STATUS_LINKED_LIST_START); + + tsemac_dma_out32(lp, DMA_CH_STATUS, DMASG_TX_BASE, DMA_CH_STATUS_STOP); + tsemac_dma_out32(lp, DMA_CH_INPUT_CONFIG, DMASG_TX_BASE, + DMA_CH_INPUT_CONFIG_MEMORY | + ((DMASG_BYTE_PER_BURST-1) & DMA_CH_BYTE_PER_BURST_MASK)); + /* Make sure BIT_12 of DMA_CH_OUTPUT_CONFIG register is zero */ + tsemac_dma_out32(lp, DMA_CH_OUTPUT_CONFIG, DMASG_TX_BASE, 0); + tsemac_dma_out32(lp, DMA_CH_LINKED_LIST_HEAD, DMASG_TX_BASE, lower_32_bits(lp->tx_bd_p)); + tsemac_dma_out32(lp, DMA_CH_PRIORITY, DMASG_TX_BASE, 1 & DMA_CH_PRIORITY_MASK); + lp->tx_dma_cr = DMA_CH_INTERRUPT_CHANNEL_COMPLETION_MASK; + tsemac_dma_out32(lp, DMA_CH_INTERRUPT_ENABLE, DMASG_TX_BASE, lp->tx_dma_cr); +} + +int tsemac_dma_bd_init(struct net_device *ndev) +{ + int i; + struct sk_buff *skb; + struct efx_tsemac_local *lp = netdev_priv(ndev); + + /* Reset the indexes which are used for accessing the BDs */ + lp->tx_bd_ci = 0; + lp->tx_bd_tail = 0; + lp->rx_bd_ci = 0; + + /* Allocate the Tx and Rx buffer descriptors. */ + lp->tx_bd_v = dma_alloc_coherent(lp->dev, + sizeof(*lp->tx_bd_v) * lp->tx_bd_num, + &lp->tx_bd_p, GFP_DMA); + if (!lp->tx_bd_v) + return -ENOMEM; + + lp->rx_bd_v = dma_alloc_coherent(lp->dev, + sizeof(*lp->rx_bd_v) * lp->rx_bd_num, + &lp->rx_bd_p, GFP_DMA); + if (!lp->rx_bd_v) + goto out; + + for (i = 0; i < lp->tx_bd_num; i++) { + /* next address of the last descriptor is the first descriptor */ + dma_addr_t next_addr = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * + ((i + 1) % lp->tx_bd_num); + lp->tx_bd_v[i].next = lower_32_bits(next_addr); + lp->tx_bd_v[i].status = DMASG_DESCRIPTOR_STATUS_COMPLETED; + } + + for (i = 0; i < lp->rx_bd_num; i++) { + dma_addr_t addr = lp->rx_bd_p + sizeof(*lp->rx_bd_v) * + ((i + 1) % lp->rx_bd_num); + lp->rx_bd_v[i].next = lower_32_bits(addr); + + skb = netdev_alloc_skb_ip_align(ndev, lp->max_frm_size); + if (!skb) + goto out; + + lp->rx_bd_v[i].skb = skb; + addr = dma_map_single(lp->dev, skb->data, + lp->max_frm_size, DMA_FROM_DEVICE); + if (dma_mapping_error(lp->dev, addr)) { + netdev_err(ndev, "DMA mapping error\n"); + goto out; + } + desc_set_rx_phys_addr(lp, addr, &lp->rx_bd_v[i]); + + lp->rx_bd_v[i].control = (lp->max_frm_size-1) & DMASG_DESCRIPTOR_CONTROL_BYTES; + } + + tsemac_dma_start(lp); + + return 0; +out: + tsemac_dma_bd_release(ndev); + return -ENOMEM; +} diff --git a/drivers/net/ethernet/efinix/efinix_tse_mdio.c b/drivers/net/ethernet/efinix/efinix_tse_mdio.c new file mode 100644 index 000000000..cf085da93 --- /dev/null +++ b/drivers/net/ethernet/efinix/efinix_tse_mdio.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MDIO bus driver for the Efinix Triple Speed Ethernet device + * + * Copyright (c) 2023 Efinix, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include "efinix_tse.h" + +#define MAX_MDIO_FREQ 2500000 /* 2.5 MHz */ +#define DEFAULT_HOST_CLOCK 100000000 /* 100 MHz */ + + +#define MDIO_REG_DIVIDER_PRE 0x0100 +#define MDIO_REG_RD_WR_EN 0x0104 +#define MDIO_REG_REG_PHY_ADDR 0x0108 +#define MDIO_REG_WR_DATA 0x010C +#define MDIO_REG_RD_DATA 0x0110 +#define MDIO_REG_STATUS 0x0114 + +#define MDIO_DIVIDER_MASK 0x000000FF +#define MDIO_NOPRE BIT_8 +#define MDIO_RD_EN BIT_0 +#define MDIO_WR_EN BIT_1 +#define MDIO_REG_ADDR_BASE 0U +#define MDIO_REG_ADDR_MASK 0x0000001F +#define MDIO_PHY_ADDR_BASE 8U +#define MDIO_PHY_ADDR_MASK 0x00001F00 +#define MDIO_WRITE_DATA_MASK 0x0000FFFF +#define MDIO_READ_DATA_MASK 0x0000FFFF +#define MDIO_STATUS_LINK_FAIL BIT_0 +#define MDIO_STATUS_BUSY BIT_1 +#define MDIO_STATUS_INVALID BIT_2 + +static inline u32 tsemac_in32_mdio_status(struct efx_tsemac_local *lp) +{ + return tsemac_in32(lp, MDIO_REG_STATUS); +} + +static int tsemac_mdio_wait_until_ready(struct efx_tsemac_local *lp) +{ + u32 val; + + return readx_poll_timeout(tsemac_in32_mdio_status, lp, + val, !(val & (MDIO_STATUS_BUSY)), + 1, 20000); +} + +static int tsemac_mdio_read(struct mii_bus *bus, int phy_id, int reg) +{ + u32 rc; + int ret; + struct efx_tsemac_local *lp = bus->priv; + + ret = tsemac_mdio_wait_until_ready(lp); + if (ret < 0) { + return ret; + } + + tsemac_out32(lp, MDIO_REG_REG_PHY_ADDR, ((reg << MDIO_REG_ADDR_BASE) & MDIO_REG_ADDR_MASK) | + ((phy_id << MDIO_PHY_ADDR_BASE) & MDIO_PHY_ADDR_MASK)); + tsemac_out32(lp, MDIO_REG_RD_WR_EN, MDIO_RD_EN); + + ret = tsemac_mdio_wait_until_ready(lp); + if (ret < 0) { + return ret; + } + + rc = tsemac_in32(lp, MDIO_REG_RD_DATA) & MDIO_READ_DATA_MASK; + + dev_dbg(lp->dev, "tsemac_mdio_read(phy_id=%i, reg=%x) == %x\n", + phy_id, reg, rc); + + return rc; +} + +static int tsemac_mdio_write(struct mii_bus *bus, int phy_id, int reg, + u16 val) +{ + //TODO: + int ret; + struct efx_tsemac_local *lp = bus->priv; + + dev_dbg(lp->dev, "tsemac_mdio_write(phy_id=%i, reg=%x, val=%x)\n", + phy_id, reg, val); + + ret = tsemac_mdio_wait_until_ready(lp); + if (ret < 0) { + return ret; + } + + tsemac_out32(lp, MDIO_REG_REG_PHY_ADDR, ((reg << MDIO_REG_ADDR_BASE) & MDIO_REG_ADDR_MASK) | + ((phy_id << MDIO_PHY_ADDR_BASE) & MDIO_PHY_ADDR_MASK)); + tsemac_out32(lp, MDIO_REG_WR_DATA, val & MDIO_WRITE_DATA_MASK); + tsemac_out32(lp, MDIO_REG_RD_WR_EN, MDIO_WR_EN); + + ret = tsemac_mdio_wait_until_ready(lp); + if (ret < 0) { + return ret; + } + return 0; +} + +int tsemac_mdio_enable(struct efx_tsemac_local *lp) +{ + u32 host_clock; + + lp->mii_clk_div = 0; + + if (lp->axi_clk) { + host_clock = clk_get_rate(lp->axi_clk); + } else { + struct device_node *np1; + + /* Legacy fallback: detect CPU clock frequency and use as AXI + * bus clock frequency. This only works on certain platforms. + */ + np1 = of_find_node_by_name(NULL, "cpus"); + if (!np1) { + netdev_warn(lp->ndev, "Could not find CPU device node.\n"); + host_clock = DEFAULT_HOST_CLOCK; + } else { + int ret = of_property_read_u32(np1, "timebase-frequency", + &host_clock); + if (ret) { + netdev_warn(lp->ndev, "CPU timebase-frequency property not found.\n"); + host_clock = DEFAULT_HOST_CLOCK; + } + of_node_put(np1); + } + netdev_info(lp->ndev, "Setting assumed host clock to %u\n", + host_clock); + } + + lp->mii_clk_div = (host_clock / (MAX_MDIO_FREQ * 2)) - 1; + + if (host_clock % (MAX_MDIO_FREQ * 2)) + lp->mii_clk_div++; + + netdev_info(lp->ndev, + "Setting MDIO clock divisor to %u/%u Hz host clock.\n", + host_clock, lp->mii_clk_div); + + tsemac_out32(lp, MDIO_REG_DIVIDER_PRE, lp->mii_clk_div | MDIO_DIVIDER_MASK); + + return tsemac_mdio_wait_until_ready(lp); +} + +int tsemac_mdio_setup(struct efx_tsemac_local *lp) +{ + struct device_node *mdio_node; + struct mii_bus *bus; + int ret; + + ret = tsemac_mdio_enable(lp); + if (ret < 0) + return ret; + + bus = mdiobus_alloc(); + if (!bus) + return -ENOMEM; + + snprintf(bus->id, MII_BUS_ID_SIZE, "tsemac-%.8llx", + (unsigned long long)lp->regs_start); + + bus->priv = lp; + bus->name = "Efinix TSEMAC MDIO"; + bus->read = tsemac_mdio_read; + bus->write = tsemac_mdio_write; + bus->parent = lp->dev; + lp->mii_bus = bus; + + mdio_node = of_get_child_by_name(lp->dev->of_node, "mdio"); + ret = of_mdiobus_register(bus, mdio_node); + of_node_put(mdio_node); + if (ret) { + mdiobus_free(bus); + lp->mii_bus = NULL; + return ret; + } + return 0; +} + +void tsemac_mdio_teardown(struct efx_tsemac_local *lp) +{ + mdiobus_unregister(lp->mii_bus); + mdiobus_free(lp->mii_bus); + lp->mii_bus = NULL; +}