drivers: add efinix tsemac driver

Signed-off-by: Kenny Cheung <kenny.cheung@elitestek.com>
Signed-off-by: Mohamad Noor Alim Hussin <mnalim@efinixinc.com>
This commit is contained in:
Kenny Cheung
2023-03-30 14:28:01 +08:00
committed by Byron Lathi
parent c7d01374b1
commit 4a4b5d8875
8 changed files with 2337 additions and 0 deletions

View File

@@ -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

View File

@@ -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/

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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 <linux/netdevice.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/if_vlan.h>
#include <linux/phylink.h>
#include <linux/completion.h>
#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

View File

@@ -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 <linux/types.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/spinlock.h>
#include <linux/phy.h>
#include <linux/mii.h>
#include <linux/ethtool.h>
#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;
}

View File

@@ -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 <linux/clk.h>
#include <linux/of_address.h>
#include <linux/of_mdio.h>
#include <linux/jiffies.h>
#include <linux/iopoll.h>
#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;
}