Signed-off-by: Kenny Cheung <kenny.cheung@elitestek.com> Signed-off-by: Mohamad Noor Alim Hussin <mnalim@efinixinc.com>
1417 lines
38 KiB
C
1417 lines
38 KiB
C
// 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 <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/of_mdio.h>
|
|
#include <linux/of_net.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/math64.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/mii.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/phylink.h>
|
|
#include <linux/ip.h>
|
|
#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 ; i<len ; i+=4) {
|
|
data[i] = tsemac_in32(lp, i);
|
|
}
|
|
}
|
|
|
|
|
|
static void tsemac_ethtools_get_ringparam(struct net_device *ndev,
|
|
struct ethtool_ringparam *ering)
|
|
{
|
|
struct efx_tsemac_local *lp = netdev_priv(ndev);
|
|
|
|
ering->rx_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");
|