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:
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
18
drivers/net/ethernet/efinix/Kconfig
Normal file
18
drivers/net/ethernet/efinix/Kconfig
Normal 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
|
||||
3
drivers/net/ethernet/efinix/Makefile
Normal file
3
drivers/net/ethernet/efinix/Makefile
Normal 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
|
||||
1416
drivers/net/ethernet/efinix/efinix_tse.c
Normal file
1416
drivers/net/ethernet/efinix/efinix_tse.c
Normal file
File diff suppressed because it is too large
Load Diff
459
drivers/net/ethernet/efinix/efinix_tse.h
Normal file
459
drivers/net/ethernet/efinix/efinix_tse.h
Normal 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
|
||||
244
drivers/net/ethernet/efinix/efinix_tse_dma.c
Normal file
244
drivers/net/ethernet/efinix/efinix_tse_dma.c
Normal 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;
|
||||
}
|
||||
195
drivers/net/ethernet/efinix/efinix_tse_mdio.c
Normal file
195
drivers/net/ethernet/efinix/efinix_tse_mdio.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user