Files
efinix-linux/drivers/net/ethernet/efinix/efinix_tse.c
Kenny Cheung 4a4b5d8875 drivers: add efinix tsemac driver
Signed-off-by: Kenny Cheung <kenny.cheung@elitestek.com>
Signed-off-by: Mohamad Noor Alim Hussin <mnalim@efinixinc.com>
2026-08-09 22:42:41 -07:00

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, &ethres);
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");