mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
Merge branch 'net-add-adin1140-support'
Ciprian Regus says: ==================== net: Add ADIN1140 support This series introduces support for the ADIN1140 (also called AD3306) 10BASE-T1S single port MACPHY. The device integrates the MAC and PHY in the same package. The communication with the host CPU is done through an SPI interface, using the Open Alliance TC6 protocol for control and data transactions. As a result, the oa_tc6 framework is used to implement the communication with the device (register accesses and Ethernet frame RX/TX). The MAC and PHY are connected internally using an MII and MDIO bus. The PHY is a half duplex 10Mbps device, which implements both the PLCA RS (IEEE 802.3 clause 148) and CSMA/CD methods of accessing the Ethernet medium. The 10BASE-T1S standard allows multiple PHY devices to be connected (in parallel) on the same single twisted pair network segment, so PLCA can be configured in order to provide a fair access scheme to all the nodes and reduce the jitter introduced by the unordered CSMA/CD transmits. The PHY's internal register map can be accessed using the direct MDIO mode of the OA TC6. The control, status, phy id 1 & 2 C22 registers are mapped to the 0xFF00 - 0xFF03 range. As for C45 addressable devices, the PHY has PCS, PMA and PLCA blocks. The oa_tc6 framework patches are changes that would make the library usable by the subsequent ADIN1140 MAC driver. The protected mode patch is required because the ADIN1140 only allows protected mode OA TC6 control transactions, which the oa_tc6 framework doesn't currently implement. The OA_TC6_BROKEN_PHY quirk patch is required in order to allow the MAC driver to have a custom implementation for the mii_bus access methods as a workaround for hardware issues: 1. The OA TC6 standard defines the direct and indirect access modes for MDIO transactions. The ADIN1140 incorrectly advertises indirect mode only (supported capabilities register - 0x2, bit 9), while actually implementing just the direct mode. We cannot rely on the CAP register to choose an access method (which oa_tc6 does by default, even though it only implements the direct mode), so the driver has to use its own. 2. The ADIN1140 cannot access the C22 register space of the internal PHY, while the PHY is busy receiving frames. If that happens, the CONFIG0 and CONFIG2 registers of the MAC will get corrupted and the data transfer will stop. Those two registers configure settings for the transfer protocol between the MAC and host, so the value for some of their subfields shouldn't be changed while the netdev is up. Since we know the PHY is internal, the MAC driver can implement a custom mii_bus, which can intercept C22 accesses. Most of the registers mapped in the 0x0 - 0x3 range (the only ones the PHY offers) are read only, and their value can be read from somewhere else (e.g the PHYID 1 & 2 have the same value as 0x1 in the MAC memory map). C45 accesses do not cause this issue, so we can properly implement them. Even though they have different driver, the MAC one cannot function without the PHY driver, since the PHY is not compatible with the generic c22 driver. As such CONFIG_ADIN1140 selects CONFIG_ADIN1140_PHY. Signed-off-by: Ciprian Regus <ciprian.regus@analog.com> ==================== Link: https://patch.msgid.link/20260708-adin1140-driver-v5-0-4aca7b51a58b@analog.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
commit
777f49d336
71
Documentation/devicetree/bindings/net/adi,ad3306.yaml
Normal file
71
Documentation/devicetree/bindings/net/adi,ad3306.yaml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/net/adi,ad3306.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: ADI ADIN1140 10BASE-T1S MAC-PHY
|
||||
|
||||
maintainers:
|
||||
- Ciprian Regus <ciprian.regus@analog.com>
|
||||
|
||||
description: |
|
||||
The ADIN1140 (also called AD3306) is a low power single port
|
||||
10BASE-T1S MAC-PHY. It integrates an Ethernet PHY with a MAC
|
||||
and all the associated analog circuitry.
|
||||
The device tries to implement the Open Alliance TC6 10BASE-T1x MAC-PHY
|
||||
Serial Interface specification and is compliant with the
|
||||
IEEE 802.3cg-2019 Ethernet standard for 10 Mbps single pair
|
||||
Ethernet (SPE). The device has a 4-wire SPI interface for
|
||||
communication between the MAC and host processor.
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/net/ethernet-controller.yaml#
|
||||
- $ref: /schemas/spi/spi-peripheral-props.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- items:
|
||||
- const: adi,adin1140
|
||||
- const: adi,ad3306
|
||||
- const: adi,ad3306
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
spi-max-frequency:
|
||||
maximum: 25000000
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
description: Interrupt from the MAC-PHY for receive data available
|
||||
and error conditions
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- spi-max-frequency
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
ethernet@0 {
|
||||
compatible = "adi,ad3306";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <23000000>;
|
||||
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <6 IRQ_TYPE_LEVEL_LOW>;
|
||||
|
||||
local-mac-address = [ 00 11 22 33 44 55 ];
|
||||
};
|
||||
};
|
||||
|
|
@ -454,7 +454,8 @@ Device drivers API
|
|||
The include/linux/oa_tc6.h defines the following functions:
|
||||
|
||||
.. c:function:: struct oa_tc6 *oa_tc6_init(struct spi_device *spi, \
|
||||
struct net_device *netdev)
|
||||
struct net_device *netdev, \
|
||||
struct oa_tc6_quirks *quirks)
|
||||
|
||||
Initialize OA TC6 lib.
|
||||
|
||||
|
|
|
|||
15
MAINTAINERS
15
MAINTAINERS
|
|
@ -1900,6 +1900,21 @@ S: Supported
|
|||
W: https://ez.analog.com/linux-software-drivers
|
||||
F: drivers/dma/dma-axi-dmac.c
|
||||
|
||||
ANALOG DEVICES INC ETHERNET DRIVERS
|
||||
M: Ciprian Regus <ciprian.regus@analog.com>
|
||||
L: netdev@vger.kernel.org
|
||||
S: Maintained
|
||||
W: https://ez.analog.com/linux-software-drivers
|
||||
F: Documentation/devicetree/bindings/net/adi,ad3306.yaml
|
||||
F: drivers/net/ethernet/adi/adin1140.c
|
||||
|
||||
ANALOG DEVICES INC ETHERNET PHY DRIVERS
|
||||
M: Ciprian Regus <ciprian.regus@analog.com>
|
||||
L: netdev@vger.kernel.org
|
||||
S: Maintained
|
||||
W: https://ez.analog.com/linux-software-drivers
|
||||
F: drivers/net/phy/adin1140-phy.c
|
||||
|
||||
ANALOG DEVICES INC IIO DRIVERS
|
||||
M: Nuno Sá <nuno.sa@analog.com>
|
||||
M: Michael Hennerich <Michael.Hennerich@analog.com>
|
||||
|
|
|
|||
|
|
@ -26,4 +26,16 @@ config ADIN1110
|
|||
Say yes here to build support for Analog Devices ADIN1110
|
||||
Low Power 10BASE-T1L Ethernet MAC-PHY.
|
||||
|
||||
config ADIN1140
|
||||
tristate "Analog Devices ADIN1140 MAC-PHY"
|
||||
depends on SPI
|
||||
select ADIN1140_PHY
|
||||
select OA_TC6
|
||||
help
|
||||
Say yes here to build support for Analog Devices, Inc. ADIN1140
|
||||
10BASE-T1S Ethernet MAC-PHY.
|
||||
|
||||
To compile this driver as a module, choose M here. The module will be
|
||||
called adin1140.
|
||||
|
||||
endif # NET_VENDOR_ADI
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
#
|
||||
|
||||
obj-$(CONFIG_ADIN1110) += adin1110.o
|
||||
obj-$(CONFIG_ADIN1140) += adin1140.o
|
||||
|
|
|
|||
791
drivers/net/ethernet/adi/adin1140.c
Normal file
791
drivers/net/ethernet/adi/adin1140.c
Normal file
|
|
@ -0,0 +1,791 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY
|
||||
*
|
||||
* Copyright 2026 Analog Devices Inc.
|
||||
*/
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mdio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/oa_tc6.h>
|
||||
#include <linux/phy.h>
|
||||
|
||||
#define ADIN1140_MAC_CONFIG2_STAT_CLR_RD BIT(6)
|
||||
#define ADIN1140_MAC_CONFIG2_FWD_UNK2HOST BIT(2)
|
||||
|
||||
#define ADIN1140_MAC_P1_LOOP_ADDR_REG 0xC4
|
||||
|
||||
#define ADIN1140_MAC_ADDR_FILT_UPR_REG 0x50
|
||||
#define ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 BIT(30)
|
||||
#define ADIN1140_MAC_ADDR_FILT_TO_HOST BIT(16)
|
||||
|
||||
#define ADIN1140_MAC_ADDR_FILT_LWR_REG 0x51
|
||||
|
||||
#define ADIN1140_MAC_ADDR_MASK_UPR_REG 0x70
|
||||
#define ADIN1140_MAC_ADDR_MASK_LWR_REG 0x71
|
||||
|
||||
#define ADIN1140_MAC_FILT_MC_SLOT 0U
|
||||
#define ADIN1140_MAC_FILT_BC_SLOT 1U
|
||||
#define ADIN1140_MAC_FILT_UC_SLOT 2U
|
||||
#define ADIN1140_MAC_FILT_MAX_SLOT 16U
|
||||
#define ADIN1140_MAC_FILT_MASK_LIMIT 2U
|
||||
|
||||
#define ADIN1140_MAC_RX_FRAME_CNT 0xA1
|
||||
#define ADIN1140_MAC_RX_BC_FRAME_CNT 0xA2
|
||||
#define ADIN1140_MAC_RX_MC_FRAME_CNT 0xA3
|
||||
#define ADIN1140_MAC_RX_CRC_ERR_CNT 0xA5
|
||||
#define ADIN1140_MAC_RX_ALIGN_ERR_CNT 0xA6
|
||||
#define ADIN1140_MAC_RX_PREAMBLE_ERR_CNT 0xA7
|
||||
#define ADIN1140_MAC_RX_SHORT_ERR_CNT 0xA8
|
||||
#define ADIN1140_MAC_RX_LONG_ERR_CNT 0xA9
|
||||
#define ADIN1140_MAC_RX_PHY_ERR_CNT 0xAA
|
||||
#define ADIN1140_MAC_RX_DRP_FULL_CNT 0xAB
|
||||
#define ADIN1140_MAC_RX_DRP_FILTER_CNT 0xAD
|
||||
#define ADIN1140_MAC_RX_IFG_ERR_CNT 0xAE
|
||||
#define ADIN1140_MAC_TX_FRAME_CNT 0xB1
|
||||
#define ADIN1140_MAC_TX_BC_FRAME_CNT 0xB2
|
||||
#define ADIN1140_MAC_TX_MC_FRAME_CNT 0xB3
|
||||
#define ADIN1140_MAC_TX_SINGLE_COL_CNT 0xB5
|
||||
#define ADIN1140_MAC_TX_MULTI_COL_CNT 0xB6
|
||||
#define ADIN1140_MAC_TX_DEFERRED_CNT 0xB7
|
||||
#define ADIN1140_MAC_TX_LATE_COL_CNT 0xB8
|
||||
#define ADIN1140_MAC_TX_EXCESS_COL_CNT 0xB9
|
||||
#define ADIN1140_MAC_TX_UNDERRUN_CNT 0xBA
|
||||
|
||||
/* ADIN1140_MAC_FILT_MAX_SLOT - 3 (multicast, broadcast and unicast
|
||||
* reserved slots)
|
||||
*/
|
||||
#define ADIN1140_MAC_FILT_AVAIL 13U
|
||||
|
||||
#define ADIN1140_PHY_CTRL_DEFAULT 0x1000
|
||||
#define ADIN1140_PHY_STATUS_DEFAULT 0x082D
|
||||
#define ADIN1140_PHY_ID1 0x0283
|
||||
#define ADIN1140_PHY_ID2 0xBE00
|
||||
|
||||
#define ADIN1140_STATS_CHECK_DELAY (3 * HZ)
|
||||
|
||||
enum adin1140_statistics_entry {
|
||||
rx_frames,
|
||||
rx_bc_frames,
|
||||
rx_mc_frames,
|
||||
rx_crc_errors,
|
||||
rx_align_errors,
|
||||
rx_preamble_errors,
|
||||
rx_short_frame_errors,
|
||||
rx_long_frame_errors,
|
||||
rx_phy_errors,
|
||||
rx_fifo_full_dropped,
|
||||
rx_addr_filter_dropped,
|
||||
rx_ifg_errors,
|
||||
tx_frames,
|
||||
tx_bc_frames,
|
||||
tx_mc_frames,
|
||||
tx_single_collision,
|
||||
tx_multi_collision,
|
||||
tx_deferred,
|
||||
tx_late_collision,
|
||||
tx_excess_collision,
|
||||
tx_underrun,
|
||||
ADIN1140_STATS_CNT,
|
||||
};
|
||||
|
||||
struct adin1140_statistics_reg {
|
||||
const char *name;
|
||||
enum adin1140_statistics_entry idx;
|
||||
};
|
||||
|
||||
struct adin1140_priv {
|
||||
struct net_device *netdev;
|
||||
struct oa_tc6 *tc6;
|
||||
struct mii_bus *mdiobus;
|
||||
struct phy_device *phydev;
|
||||
struct delayed_work stats_work;
|
||||
|
||||
/* Protects stats[] from concurrent updates in adin1140_stats_work
|
||||
* and reads in the get_stats functions
|
||||
*/
|
||||
spinlock_t stat_lock;
|
||||
u64 stats[ADIN1140_STATS_CNT];
|
||||
};
|
||||
|
||||
static const u32 adin1140_stat_regs[] = {
|
||||
[rx_frames] = ADIN1140_MAC_RX_FRAME_CNT,
|
||||
[rx_bc_frames] = ADIN1140_MAC_RX_BC_FRAME_CNT,
|
||||
[rx_mc_frames] = ADIN1140_MAC_RX_MC_FRAME_CNT,
|
||||
[rx_crc_errors] = ADIN1140_MAC_RX_CRC_ERR_CNT,
|
||||
[rx_align_errors] = ADIN1140_MAC_RX_ALIGN_ERR_CNT,
|
||||
[rx_preamble_errors] = ADIN1140_MAC_RX_PREAMBLE_ERR_CNT,
|
||||
[rx_short_frame_errors] = ADIN1140_MAC_RX_SHORT_ERR_CNT,
|
||||
[rx_long_frame_errors] = ADIN1140_MAC_RX_LONG_ERR_CNT,
|
||||
[rx_phy_errors] = ADIN1140_MAC_RX_PHY_ERR_CNT,
|
||||
[rx_fifo_full_dropped] = ADIN1140_MAC_RX_DRP_FULL_CNT,
|
||||
[rx_addr_filter_dropped] = ADIN1140_MAC_RX_DRP_FILTER_CNT,
|
||||
[rx_ifg_errors] = ADIN1140_MAC_RX_IFG_ERR_CNT,
|
||||
[tx_frames] = ADIN1140_MAC_TX_FRAME_CNT,
|
||||
[tx_bc_frames] = ADIN1140_MAC_TX_BC_FRAME_CNT,
|
||||
[tx_mc_frames] = ADIN1140_MAC_TX_MC_FRAME_CNT,
|
||||
[tx_single_collision] = ADIN1140_MAC_TX_SINGLE_COL_CNT,
|
||||
[tx_multi_collision] = ADIN1140_MAC_TX_MULTI_COL_CNT,
|
||||
[tx_deferred] = ADIN1140_MAC_TX_DEFERRED_CNT,
|
||||
[tx_late_collision] = ADIN1140_MAC_TX_LATE_COL_CNT,
|
||||
[tx_excess_collision] = ADIN1140_MAC_TX_EXCESS_COL_CNT,
|
||||
[tx_underrun] = ADIN1140_MAC_TX_UNDERRUN_CNT,
|
||||
};
|
||||
|
||||
static const struct adin1140_statistics_reg adin1140_stats[] = {
|
||||
{.name = "rx_preamble_errors", .idx = rx_preamble_errors},
|
||||
{.name = "rx_ifg_errors", .idx = rx_ifg_errors},
|
||||
};
|
||||
|
||||
static int adin1140_mac_filter_set(struct adin1140_priv *priv,
|
||||
const u8 *addr, const u8 *mask,
|
||||
u8 slot)
|
||||
{
|
||||
u32 reg_address;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (slot >= ADIN1140_MAC_FILT_MAX_SLOT)
|
||||
return -ENOSPC;
|
||||
|
||||
reg_address = ADIN1140_MAC_ADDR_FILT_UPR_REG + 2 * slot;
|
||||
|
||||
ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1,
|
||||
reg_address,
|
||||
get_unaligned_be16(&addr[0]) |
|
||||
ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 |
|
||||
ADIN1140_MAC_ADDR_FILT_TO_HOST);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg_address = ADIN1140_MAC_ADDR_FILT_LWR_REG + 2 * slot;
|
||||
ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1,
|
||||
reg_address,
|
||||
get_unaligned_be32(&addr[2]));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Only the first 2 destination MAC filter slots support masking.
|
||||
* For the other entries, the destination address in the received
|
||||
* frame must match exactly.
|
||||
*/
|
||||
if (slot >= ADIN1140_MAC_FILT_MASK_LIMIT)
|
||||
return 0;
|
||||
|
||||
val = get_unaligned_be16(&mask[0]);
|
||||
reg_address = ADIN1140_MAC_ADDR_MASK_UPR_REG + (2 * slot);
|
||||
|
||||
ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1,
|
||||
reg_address, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = get_unaligned_be32(&mask[2]);
|
||||
reg_address = ADIN1140_MAC_ADDR_MASK_LWR_REG + (2 * slot);
|
||||
|
||||
return oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1,
|
||||
reg_address, val);
|
||||
}
|
||||
|
||||
static int adin1140_mac_filter_clear(struct adin1140_priv *priv, u8 slot)
|
||||
{
|
||||
u8 mask[ETH_ALEN];
|
||||
u8 addr[ETH_ALEN];
|
||||
|
||||
memset(mask, 0xFF, ETH_ALEN);
|
||||
memset(addr, 0x0, ETH_ALEN);
|
||||
|
||||
return adin1140_mac_filter_set(priv, addr, mask, slot);
|
||||
}
|
||||
|
||||
static int adin1140_filter_unicast(struct adin1140_priv *priv)
|
||||
{
|
||||
/* Only the first 2 filter slots support masking, so no unicast
|
||||
* address will ever need a mask. The first slots are used for the
|
||||
* all multicast and broadcast filter.
|
||||
*/
|
||||
return adin1140_mac_filter_set(priv, priv->netdev->dev_addr, NULL,
|
||||
ADIN1140_MAC_FILT_UC_SLOT);
|
||||
}
|
||||
|
||||
static int adin1140_filter_all_multicast(struct adin1140_priv *priv, bool en)
|
||||
{
|
||||
u8 multicast_addr[ETH_ALEN] = {1, 0, 0, 0, 0, 0};
|
||||
|
||||
if (en)
|
||||
return adin1140_mac_filter_set(priv, multicast_addr,
|
||||
multicast_addr,
|
||||
ADIN1140_MAC_FILT_MC_SLOT);
|
||||
|
||||
return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_MC_SLOT);
|
||||
}
|
||||
|
||||
static int adin1140_filter_broadcast(struct adin1140_priv *priv, bool enabled)
|
||||
{
|
||||
u8 mask[ETH_ALEN];
|
||||
|
||||
if (enabled) {
|
||||
memset(mask, 0xFF, ETH_ALEN);
|
||||
return adin1140_mac_filter_set(priv, mask, mask,
|
||||
ADIN1140_MAC_FILT_BC_SLOT);
|
||||
}
|
||||
|
||||
return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_BC_SLOT);
|
||||
}
|
||||
|
||||
static int adin1140_default_filter_config(struct adin1140_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = adin1140_filter_broadcast(priv, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return adin1140_filter_unicast(priv);
|
||||
}
|
||||
|
||||
static int adin1140_promiscuous_mode(struct adin1140_priv *priv, bool enabled)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
ret = oa_tc6_read_register(priv->tc6, OA_TC6_REG_CONFIG2, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (enabled)
|
||||
val |= ADIN1140_MAC_CONFIG2_FWD_UNK2HOST;
|
||||
else
|
||||
val &= ~ADIN1140_MAC_CONFIG2_FWD_UNK2HOST;
|
||||
|
||||
return oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG2, val);
|
||||
}
|
||||
|
||||
static int adin1140_rx_mode(struct net_device *dev,
|
||||
struct netdev_hw_addr_list *uc,
|
||||
struct netdev_hw_addr_list *mc)
|
||||
{
|
||||
struct adin1140_priv *priv = netdev_priv(dev);
|
||||
struct netdev_hw_addr *ha;
|
||||
bool all_multi, promisc;
|
||||
u32 mac_addrs;
|
||||
u8 slot, i;
|
||||
int ret;
|
||||
|
||||
/* The ADIN1140 has 16 dest MAC address filter slots:
|
||||
* 0 - reserved for all multicast filter.
|
||||
* 1 - reserved for broadcast filter.
|
||||
* 2 - reserved for the device's own unicast MAC.
|
||||
* 3 -> 15 - available for other unicast/multicast filters.
|
||||
*/
|
||||
|
||||
mac_addrs = netdev_hw_addr_list_count(uc);
|
||||
all_multi = false;
|
||||
promisc = false;
|
||||
|
||||
if (priv->netdev->flags & IFF_PROMISC)
|
||||
promisc = true;
|
||||
else if (priv->netdev->flags & IFF_ALLMULTI)
|
||||
all_multi = true;
|
||||
else
|
||||
mac_addrs += netdev_hw_addr_list_count(mc);
|
||||
|
||||
if (mac_addrs > ADIN1140_MAC_FILT_AVAIL) {
|
||||
/* The filter table is full. Enable promisc mode. */
|
||||
promisc = true;
|
||||
}
|
||||
|
||||
ret = adin1140_promiscuous_mode(priv, promisc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = adin1140_filter_all_multicast(priv, all_multi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
slot = ADIN1140_MAC_FILT_UC_SLOT + 1;
|
||||
if (!promisc) {
|
||||
netdev_hw_addr_list_for_each(ha, uc) {
|
||||
ret = adin1140_mac_filter_set(priv, ha->addr, NULL,
|
||||
slot);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
slot++;
|
||||
}
|
||||
|
||||
if (!all_multi) {
|
||||
netdev_hw_addr_list_for_each(ha, mc) {
|
||||
ret = adin1140_mac_filter_set(priv, ha->addr,
|
||||
NULL, slot);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
slot++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = slot; i < ADIN1140_MAC_FILT_MAX_SLOT; i++) {
|
||||
ret = adin1140_mac_filter_clear(priv, i);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void adin1140_stats_work(struct work_struct *work)
|
||||
{
|
||||
struct delayed_work *dwork = to_delayed_work(work);
|
||||
struct adin1140_priv *priv;
|
||||
u32 reg_val;
|
||||
int ret;
|
||||
u32 i;
|
||||
|
||||
priv = container_of(dwork, struct adin1140_priv, stats_work);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(adin1140_stat_regs); i++) {
|
||||
ret = oa_tc6_read_register_mms(priv->tc6, OA_TC6_MAC_MMS1,
|
||||
adin1140_stat_regs[i],
|
||||
®_val);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
scoped_guard(spinlock, &priv->stat_lock)
|
||||
priv->stats[i] += reg_val;
|
||||
}
|
||||
|
||||
out:
|
||||
schedule_delayed_work(dwork, ADIN1140_STATS_CHECK_DELAY);
|
||||
}
|
||||
|
||||
static int adin1140_configure(struct adin1140_priv *priv)
|
||||
{
|
||||
u32 reg_val;
|
||||
int ret;
|
||||
|
||||
ret = oa_tc6_zero_align_receive_frame_enable(priv->tc6);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Disable MAC loopback */
|
||||
ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1,
|
||||
ADIN1140_MAC_P1_LOOP_ADDR_REG, 0x0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = oa_tc6_read_register(priv->tc6, OA_TC6_REG_CONFIG2, ®_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg_val |= ADIN1140_MAC_CONFIG2_STAT_CLR_RD;
|
||||
|
||||
ret = oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG2, reg_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return adin1140_default_filter_config(priv);
|
||||
}
|
||||
|
||||
static int adin1140_open(struct net_device *netdev)
|
||||
{
|
||||
struct adin1140_priv *priv = netdev_priv(netdev);
|
||||
|
||||
schedule_delayed_work(&priv->stats_work, ADIN1140_STATS_CHECK_DELAY);
|
||||
|
||||
phy_start(netdev->phydev);
|
||||
netif_start_queue(netdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adin1140_close(struct net_device *netdev)
|
||||
{
|
||||
struct adin1140_priv *priv = netdev_priv(netdev);
|
||||
|
||||
cancel_delayed_work_sync(&priv->stats_work);
|
||||
|
||||
netif_stop_queue(netdev);
|
||||
phy_stop(netdev->phydev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static netdev_tx_t adin1140_start_xmit(struct sk_buff *skb,
|
||||
struct net_device *netdev)
|
||||
{
|
||||
struct adin1140_priv *priv = netdev_priv(netdev);
|
||||
|
||||
/* The MAC doesn't automatically pad the frame to a 60 byte minimum
|
||||
* size in case the host sends a shorter skb, so we have to do it in
|
||||
* the driver. The FCS will be added by the MAC.
|
||||
*/
|
||||
if (skb_put_padto(skb, ETH_ZLEN))
|
||||
return NETDEV_TX_OK;
|
||||
|
||||
return oa_tc6_start_xmit(priv->tc6, skb);
|
||||
}
|
||||
|
||||
static int adin1140_set_mac_address(struct net_device *netdev, void *addr)
|
||||
{
|
||||
struct adin1140_priv *priv = netdev_priv(netdev);
|
||||
struct sockaddr *address = addr;
|
||||
u8 mask[ETH_ALEN];
|
||||
int ret;
|
||||
|
||||
ret = eth_prepare_mac_addr_change(netdev, addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ether_addr_equal(address->sa_data, netdev->dev_addr))
|
||||
return 0;
|
||||
|
||||
memset(mask, 0xFF, ETH_ALEN);
|
||||
ret = adin1140_mac_filter_set(priv, address->sa_data, mask,
|
||||
ADIN1140_MAC_FILT_UC_SLOT);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
eth_commit_mac_addr_change(netdev, addr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __adin1140_ndo_get_stats64(struct adin1140_priv *priv,
|
||||
struct rtnl_link_stats64 *storage)
|
||||
{
|
||||
storage->rx_errors = priv->stats[rx_crc_errors] +
|
||||
priv->stats[rx_align_errors] +
|
||||
priv->stats[rx_preamble_errors] +
|
||||
priv->stats[rx_short_frame_errors] +
|
||||
priv->stats[rx_long_frame_errors] +
|
||||
priv->stats[rx_phy_errors] +
|
||||
priv->stats[rx_ifg_errors];
|
||||
|
||||
storage->tx_errors = priv->stats[tx_excess_collision] +
|
||||
priv->stats[tx_underrun];
|
||||
|
||||
storage->rx_dropped = priv->stats[rx_fifo_full_dropped] +
|
||||
priv->stats[rx_addr_filter_dropped];
|
||||
|
||||
storage->multicast = priv->stats[rx_mc_frames];
|
||||
|
||||
storage->collisions = priv->stats[tx_single_collision] +
|
||||
priv->stats[tx_multi_collision];
|
||||
|
||||
storage->rx_length_errors = priv->stats[rx_short_frame_errors] +
|
||||
priv->stats[rx_long_frame_errors];
|
||||
storage->rx_over_errors = priv->stats[rx_fifo_full_dropped];
|
||||
storage->rx_crc_errors = priv->stats[rx_crc_errors];
|
||||
storage->rx_frame_errors = priv->stats[rx_align_errors];
|
||||
storage->rx_missed_errors = priv->stats[rx_fifo_full_dropped];
|
||||
|
||||
storage->tx_aborted_errors = priv->stats[tx_excess_collision];
|
||||
storage->tx_fifo_errors = priv->stats[tx_underrun];
|
||||
storage->tx_window_errors = priv->stats[tx_late_collision];
|
||||
}
|
||||
|
||||
static void adin1140_ndo_get_stats64(struct net_device *dev,
|
||||
struct rtnl_link_stats64 *storage)
|
||||
{
|
||||
struct adin1140_priv *priv = netdev_priv(dev);
|
||||
|
||||
storage->rx_packets = priv->netdev->stats.rx_packets;
|
||||
storage->tx_packets = priv->netdev->stats.tx_packets;
|
||||
|
||||
storage->rx_bytes = priv->netdev->stats.rx_bytes;
|
||||
storage->tx_bytes = priv->netdev->stats.tx_bytes;
|
||||
|
||||
scoped_guard(spinlock, &priv->stat_lock)
|
||||
__adin1140_ndo_get_stats64(priv, storage);
|
||||
}
|
||||
|
||||
static void adin1140_get_drvinfo(struct net_device *netdev,
|
||||
struct ethtool_drvinfo *info)
|
||||
{
|
||||
strscpy(info->driver, "ADIN1140", sizeof(info->driver));
|
||||
strscpy(info->bus_info, dev_name(netdev->dev.parent),
|
||||
sizeof(info->bus_info));
|
||||
}
|
||||
|
||||
static void adin1140_get_ethtool_stats(struct net_device *netdev,
|
||||
struct ethtool_stats *stats, u64 *data)
|
||||
{
|
||||
struct adin1140_priv *priv = netdev_priv(netdev);
|
||||
u32 i;
|
||||
|
||||
scoped_guard(spinlock, &priv->stat_lock) {
|
||||
for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++)
|
||||
data[i] = priv->stats[adin1140_stats[i].idx];
|
||||
}
|
||||
}
|
||||
|
||||
static void adin1140_get_ethtool_strings(struct net_device *netdev, u32 sset,
|
||||
u8 *p)
|
||||
{
|
||||
u32 i;
|
||||
|
||||
switch (sset) {
|
||||
case ETH_SS_STATS:
|
||||
for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++)
|
||||
ethtool_puts(&p, adin1140_stats[i].name);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int adin1140_get_sset_count(struct net_device *netdev, int sset)
|
||||
{
|
||||
switch (sset) {
|
||||
case ETH_SS_STATS:
|
||||
return ARRAY_SIZE(adin1140_stats);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static void __adin1140_eth_mac_stats(struct adin1140_priv *priv,
|
||||
struct ethtool_eth_mac_stats *mac_stats)
|
||||
{
|
||||
mac_stats->FramesReceivedOK = priv->stats[rx_frames];
|
||||
mac_stats->BroadcastFramesReceivedOK = priv->stats[rx_bc_frames];
|
||||
mac_stats->MulticastFramesReceivedOK = priv->stats[rx_mc_frames];
|
||||
mac_stats->FrameCheckSequenceErrors = priv->stats[rx_crc_errors];
|
||||
mac_stats->AlignmentErrors = priv->stats[rx_align_errors];
|
||||
mac_stats->FrameTooLongErrors = priv->stats[rx_long_frame_errors];
|
||||
mac_stats->FramesLostDueToIntMACRcvError =
|
||||
priv->stats[rx_fifo_full_dropped];
|
||||
mac_stats->FramesTransmittedOK = priv->stats[tx_frames];
|
||||
mac_stats->BroadcastFramesXmittedOK = priv->stats[tx_bc_frames];
|
||||
mac_stats->MulticastFramesXmittedOK = priv->stats[tx_mc_frames];
|
||||
mac_stats->SingleCollisionFrames = priv->stats[tx_single_collision];
|
||||
mac_stats->MultipleCollisionFrames = priv->stats[tx_multi_collision];
|
||||
mac_stats->FramesWithDeferredXmissions = priv->stats[tx_deferred];
|
||||
mac_stats->LateCollisions = priv->stats[tx_late_collision];
|
||||
mac_stats->FramesAbortedDueToXSColls =
|
||||
priv->stats[tx_excess_collision];
|
||||
mac_stats->FramesLostDueToIntMACXmitError = priv->stats[tx_underrun];
|
||||
}
|
||||
|
||||
static void adin1140_get_eth_mac_stats(struct net_device *netdev,
|
||||
struct ethtool_eth_mac_stats *mac_stats)
|
||||
{
|
||||
struct adin1140_priv *priv = netdev_priv(netdev);
|
||||
|
||||
scoped_guard(spinlock, &priv->stat_lock)
|
||||
__adin1140_eth_mac_stats(priv, mac_stats);
|
||||
}
|
||||
|
||||
static int adin1140_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
|
||||
{
|
||||
/* The ADIN1140's standard PHY C22 register map (OA TC6 0xFF00 -
|
||||
* 0xFF1F), of which only 0xFF00 - 0xFF03 are implemented) cannot be
|
||||
* accessed while frames are being received by the PHY. In case this
|
||||
* happens the CONFIG0 and CONFIG2 register values will get corrupted,
|
||||
* getting a random value. Both reads and writes cause the same
|
||||
* behavior. This is a workaround that avoids MDIO accesses all
|
||||
* together. Since this is a 10BASE-T1S PHY, only the loopback and
|
||||
* reset (AN) bits in the control register (0x0) can be written.
|
||||
* These functionalities have custom implementations in the PHY
|
||||
* driver. C45 accesses do not cause this issue.
|
||||
*/
|
||||
|
||||
switch (regnum) {
|
||||
case MII_BMCR:
|
||||
return ADIN1140_PHY_CTRL_DEFAULT;
|
||||
case MII_BMSR:
|
||||
return ADIN1140_PHY_STATUS_DEFAULT;
|
||||
case MII_PHYSID1:
|
||||
return ADIN1140_PHY_ID1;
|
||||
case MII_PHYSID2:
|
||||
return ADIN1140_PHY_ID2;
|
||||
default:
|
||||
return 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
static int adin1140_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
|
||||
u16 val)
|
||||
{
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int adin1140_mdio_register(struct adin1140_priv *priv,
|
||||
struct spi_device *spidev)
|
||||
{
|
||||
priv->mdiobus = devm_mdiobus_alloc(&spidev->dev);
|
||||
if (!priv->mdiobus)
|
||||
return dev_err_probe(&spidev->dev, -ENOMEM,
|
||||
"MDIO bus alloc failed\n");
|
||||
|
||||
priv->mdiobus->name = "adin1140-mdiobus";
|
||||
priv->mdiobus->priv = priv->tc6;
|
||||
priv->mdiobus->parent = &spidev->dev;
|
||||
priv->mdiobus->phy_mask = GENMASK(31, 1);
|
||||
priv->mdiobus->read = adin1140_mdiobus_read;
|
||||
priv->mdiobus->write = adin1140_mdiobus_write;
|
||||
priv->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
|
||||
priv->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
|
||||
|
||||
snprintf(priv->mdiobus->id, MII_BUS_ID_SIZE, "adin1140-%s.%u",
|
||||
dev_name(&spidev->dev), spi_get_chipselect(spidev, 0));
|
||||
|
||||
return devm_mdiobus_register(&spidev->dev, priv->mdiobus);
|
||||
}
|
||||
|
||||
static void adin1140_handle_link_change(struct net_device *netdev)
|
||||
{
|
||||
phy_print_status(netdev->phydev);
|
||||
}
|
||||
|
||||
static void adin1140_phy_remove(void *data)
|
||||
{
|
||||
phy_disconnect(data);
|
||||
}
|
||||
|
||||
static int adin1140_phy_init(struct adin1140_priv *priv,
|
||||
struct spi_device *spidev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = adin1140_mdio_register(priv, spidev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv->phydev = phy_find_first(priv->mdiobus);
|
||||
if (!priv->phydev)
|
||||
return dev_err_probe(&spidev->dev, -ENODEV, "No PHY found\n");
|
||||
|
||||
priv->phydev->is_internal = true;
|
||||
ret = phy_connect_direct(priv->netdev, priv->phydev,
|
||||
&adin1140_handle_link_change,
|
||||
PHY_INTERFACE_MODE_INTERNAL);
|
||||
if (ret)
|
||||
return dev_err_probe(&spidev->dev, ret,
|
||||
"Can't attach PHY to %s\n",
|
||||
priv->mdiobus->id);
|
||||
|
||||
ret = devm_add_action_or_reset(&spidev->dev, adin1140_phy_remove,
|
||||
priv->phydev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
phy_attached_info(priv->phydev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ethtool_ops adin1140_ethtool_ops = {
|
||||
.get_drvinfo = adin1140_get_drvinfo,
|
||||
.get_link = ethtool_op_get_link,
|
||||
.get_ethtool_stats = adin1140_get_ethtool_stats,
|
||||
.get_sset_count = adin1140_get_sset_count,
|
||||
.get_strings = adin1140_get_ethtool_strings,
|
||||
.get_link_ksettings = phy_ethtool_get_link_ksettings,
|
||||
.set_link_ksettings = phy_ethtool_set_link_ksettings,
|
||||
.get_eth_mac_stats = adin1140_get_eth_mac_stats,
|
||||
};
|
||||
|
||||
static const struct net_device_ops adin1140_netdev_ops = {
|
||||
.ndo_open = adin1140_open,
|
||||
.ndo_stop = adin1140_close,
|
||||
.ndo_start_xmit = adin1140_start_xmit,
|
||||
.ndo_set_mac_address = adin1140_set_mac_address,
|
||||
.ndo_validate_addr = eth_validate_addr,
|
||||
.ndo_set_rx_mode_async = adin1140_rx_mode,
|
||||
.ndo_eth_ioctl = phy_do_ioctl_running,
|
||||
.ndo_get_stats64 = adin1140_ndo_get_stats64,
|
||||
};
|
||||
|
||||
static void adin1140_oa_tc6_remove(void *data)
|
||||
{
|
||||
oa_tc6_exit(data);
|
||||
}
|
||||
|
||||
static int adin1140_probe(struct spi_device *spi)
|
||||
{
|
||||
struct oa_tc6_quirks tc6_quirks = {};
|
||||
struct net_device *netdev;
|
||||
struct adin1140_priv *priv;
|
||||
int ret;
|
||||
|
||||
netdev = devm_alloc_etherdev(&spi->dev, sizeof(struct adin1140_priv));
|
||||
if (!netdev)
|
||||
return -ENOMEM;
|
||||
|
||||
priv = netdev_priv(netdev);
|
||||
priv->netdev = netdev;
|
||||
spi_set_drvdata(spi, priv);
|
||||
spin_lock_init(&priv->stat_lock);
|
||||
|
||||
tc6_quirks.quirk_flags = OA_TC6_BROKEN_PHY;
|
||||
|
||||
priv->tc6 = oa_tc6_init(spi, netdev, &tc6_quirks);
|
||||
if (!priv->tc6)
|
||||
return -ENODEV;
|
||||
|
||||
ret = devm_add_action_or_reset(&spi->dev, adin1140_oa_tc6_remove,
|
||||
priv->tc6);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = adin1140_phy_init(priv, spi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (device_get_ethdev_address(&spi->dev, netdev))
|
||||
eth_hw_addr_random(netdev);
|
||||
|
||||
ret = adin1140_configure(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
INIT_DELAYED_WORK(&priv->stats_work, adin1140_stats_work);
|
||||
|
||||
netdev->if_port = IF_PORT_10BASET;
|
||||
netdev->irq = spi->irq;
|
||||
netdev->netdev_ops = &adin1140_netdev_ops;
|
||||
netdev->ethtool_ops = &adin1140_ethtool_ops;
|
||||
netdev->netns_immutable = true;
|
||||
netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE |
|
||||
IFF_UNICAST_FLT;
|
||||
|
||||
ret = devm_register_netdev(&spi->dev, netdev);
|
||||
if (ret)
|
||||
return dev_err_probe(&spi->dev, ret,
|
||||
"Failed to register netdev");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id adin1140_spi_id[] = {
|
||||
{ .name = "ad3306" },
|
||||
{ .name = "adin1140" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, adin1140_spi_id);
|
||||
|
||||
static const struct of_device_id adin1140_match_table[] = {
|
||||
{ .compatible = "adi,ad3306" },
|
||||
{ .compatible = "adi,adin1140" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adin1140_match_table);
|
||||
|
||||
static struct spi_driver adin1140_driver = {
|
||||
.driver = {
|
||||
.name = "adin1140",
|
||||
.of_match_table = adin1140_match_table,
|
||||
},
|
||||
.probe = adin1140_probe,
|
||||
.id_table = adin1140_spi_id,
|
||||
};
|
||||
module_spi_driver(adin1140_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY");
|
||||
MODULE_AUTHOR("Ciprian Regus <ciprian.regus@analog.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -346,7 +346,7 @@ static int lan865x_probe(struct spi_device *spi)
|
|||
spi_set_drvdata(spi, priv);
|
||||
INIT_WORK(&priv->multicast_work, lan865x_multicast_work_handler);
|
||||
|
||||
priv->tc6 = oa_tc6_init(spi, netdev);
|
||||
priv->tc6 = oa_tc6_init(spi, netdev, NULL);
|
||||
if (!priv->tc6) {
|
||||
ret = -ENODEV;
|
||||
goto free_netdev;
|
||||
|
|
|
|||
|
|
@ -12,46 +12,6 @@
|
|||
#include <linux/phy.h>
|
||||
#include <linux/oa_tc6.h>
|
||||
|
||||
/* OPEN Alliance TC6 registers */
|
||||
/* Standard Capabilities Register */
|
||||
#define OA_TC6_REG_STDCAP 0x0002
|
||||
#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
|
||||
|
||||
/* Reset Control and Status Register */
|
||||
#define OA_TC6_REG_RESET 0x0003
|
||||
#define RESET_SWRESET BIT(0) /* Software Reset */
|
||||
|
||||
/* Configuration Register #0 */
|
||||
#define OA_TC6_REG_CONFIG0 0x0004
|
||||
#define CONFIG0_SYNC BIT(15)
|
||||
#define CONFIG0_ZARFE_ENABLE BIT(12)
|
||||
|
||||
/* Status Register #0 */
|
||||
#define OA_TC6_REG_STATUS0 0x0008
|
||||
#define STATUS0_RESETC BIT(6) /* Reset Complete */
|
||||
#define STATUS0_HEADER_ERROR BIT(5)
|
||||
#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4)
|
||||
#define STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3)
|
||||
#define STATUS0_TX_PROTOCOL_ERROR BIT(0)
|
||||
|
||||
/* Buffer Status Register */
|
||||
#define OA_TC6_REG_BUFFER_STATUS 0x000B
|
||||
#define BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8)
|
||||
#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0)
|
||||
|
||||
/* Interrupt Mask Register #0 */
|
||||
#define OA_TC6_REG_INT_MASK0 0x000C
|
||||
#define INT_MASK0_HEADER_ERR_MASK BIT(5)
|
||||
#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4)
|
||||
#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3)
|
||||
#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0)
|
||||
#define INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \
|
||||
GENMASK(12, 7))
|
||||
|
||||
/* PHY Clause 22 registers base address and mask */
|
||||
#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00
|
||||
#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F
|
||||
|
||||
/* Control command header */
|
||||
#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
|
||||
#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
|
||||
|
|
@ -81,23 +41,17 @@
|
|||
#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8)
|
||||
#define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
|
||||
|
||||
/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the
|
||||
* OPEN Alliance specification.
|
||||
*/
|
||||
#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */
|
||||
#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */
|
||||
#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
|
||||
#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
|
||||
#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */
|
||||
|
||||
#define OA_TC6_CTRL_PROT_REPLY_SIZE 4
|
||||
#define OA_TC6_CTRL_HEADER_SIZE 4
|
||||
#define OA_TC6_CTRL_REG_VALUE_SIZE 4
|
||||
#define OA_TC6_CTRL_IGNORED_SIZE 4
|
||||
#define OA_TC6_CTRL_MAX_REGISTERS 128
|
||||
#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\
|
||||
(OA_TC6_CTRL_MAX_REGISTERS *\
|
||||
OA_TC6_CTRL_REG_VALUE_SIZE) +\
|
||||
OA_TC6_CTRL_IGNORED_SIZE)
|
||||
#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\
|
||||
(OA_TC6_CTRL_MAX_REGISTERS *\
|
||||
(OA_TC6_CTRL_REG_VALUE_SIZE +\
|
||||
OA_TC6_CTRL_PROT_REPLY_SIZE)) +\
|
||||
OA_TC6_CTRL_IGNORED_SIZE)
|
||||
|
||||
#define OA_TC6_CHUNK_PAYLOAD_SIZE 64
|
||||
#define OA_TC6_DATA_HEADER_SIZE 4
|
||||
#define OA_TC6_CHUNK_SIZE (OA_TC6_DATA_HEADER_SIZE +\
|
||||
|
|
@ -108,6 +62,8 @@
|
|||
#define STATUS0_RESETC_POLL_DELAY 1000
|
||||
#define STATUS0_RESETC_POLL_TIMEOUT 1000000
|
||||
|
||||
#define OA_TC6_REG_MMS_MASK GENMASK(19, 16)
|
||||
|
||||
/* Internal structure for MAC-PHY drivers */
|
||||
struct oa_tc6 {
|
||||
struct net_device *netdev;
|
||||
|
|
@ -130,6 +86,8 @@ struct oa_tc6 {
|
|||
bool rx_buf_overflow;
|
||||
bool int_flag;
|
||||
bool disable_traffic;
|
||||
bool prot_ctrl;
|
||||
enum oa_tc6_quirk_flag quirk_flags;
|
||||
};
|
||||
|
||||
enum oa_tc6_header_type {
|
||||
|
|
@ -213,25 +171,36 @@ static void oa_tc6_update_ctrl_write_data(struct oa_tc6 *tc6, u32 value[],
|
|||
{
|
||||
__be32 *tx_buf = tc6->spi_ctrl_tx_buf + OA_TC6_CTRL_HEADER_SIZE;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
for (int i = 0; i < length; i++) {
|
||||
*tx_buf++ = cpu_to_be32(value[i]);
|
||||
if (tc6->prot_ctrl)
|
||||
*tx_buf++ = cpu_to_be32(~value[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static u16 oa_tc6_calculate_ctrl_buf_size(u8 length)
|
||||
static u16 oa_tc6_calculate_ctrl_buf_size(u8 length, bool ctrl_prot)
|
||||
{
|
||||
u32 reply_size = OA_TC6_CTRL_REG_VALUE_SIZE;
|
||||
|
||||
if (ctrl_prot)
|
||||
reply_size += OA_TC6_CTRL_PROT_REPLY_SIZE;
|
||||
|
||||
/* Control command consists 4 bytes header + 4 bytes register value for
|
||||
* each register + 4 bytes ignored value.
|
||||
* each register (+ 4 bytes for the register value complement in case
|
||||
* protected mode is used) + 4 bytes ignored value.
|
||||
*/
|
||||
return OA_TC6_CTRL_HEADER_SIZE + OA_TC6_CTRL_REG_VALUE_SIZE * length +
|
||||
return OA_TC6_CTRL_HEADER_SIZE + reply_size * length +
|
||||
OA_TC6_CTRL_IGNORED_SIZE;
|
||||
}
|
||||
|
||||
static void oa_tc6_prepare_ctrl_spi_buf(struct oa_tc6 *tc6, u32 address,
|
||||
u32 value[], u8 length,
|
||||
enum oa_tc6_register_op reg_op)
|
||||
enum oa_tc6_register_op reg_op,
|
||||
u16 buf_size)
|
||||
{
|
||||
__be32 *tx_buf = tc6->spi_ctrl_tx_buf;
|
||||
|
||||
memset(tx_buf, 0, buf_size);
|
||||
*tx_buf = oa_tc6_prepare_ctrl_header(address, length, reg_op);
|
||||
|
||||
if (reg_op == OA_TC6_CTRL_REG_WRITE)
|
||||
|
|
@ -254,10 +223,12 @@ static int oa_tc6_check_ctrl_write_reply(struct oa_tc6 *tc6, u8 size)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 size)
|
||||
static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 length)
|
||||
{
|
||||
u32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE;
|
||||
u32 *tx_buf = tc6->spi_ctrl_tx_buf;
|
||||
__be32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE;
|
||||
__be32 *tx_buf = tc6->spi_ctrl_tx_buf;
|
||||
u32 complement;
|
||||
u32 reply;
|
||||
|
||||
/* The echoed control read header must match with the one that was
|
||||
* transmitted.
|
||||
|
|
@ -265,6 +236,20 @@ static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 size)
|
|||
if (*tx_buf != *rx_buf)
|
||||
return -EPROTO;
|
||||
|
||||
if (tc6->prot_ctrl) {
|
||||
/* Skip past the echoed header to the value/complement pairs */
|
||||
rx_buf += 1;
|
||||
for (int i = 0; i < length; i++) {
|
||||
reply = be32_to_cpu(rx_buf[0]);
|
||||
complement = be32_to_cpu(rx_buf[1]);
|
||||
|
||||
if (complement != ~reply)
|
||||
return -EPROTO;
|
||||
|
||||
rx_buf += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -274,8 +259,13 @@ static void oa_tc6_copy_ctrl_read_data(struct oa_tc6 *tc6, u32 value[],
|
|||
__be32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE +
|
||||
OA_TC6_CTRL_HEADER_SIZE;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
for (int i = 0; i < length; i++) {
|
||||
value[i] = be32_to_cpu(*rx_buf++);
|
||||
|
||||
/* skip complement word */
|
||||
if (tc6->prot_ctrl)
|
||||
rx_buf++;
|
||||
}
|
||||
}
|
||||
|
||||
static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[],
|
||||
|
|
@ -284,10 +274,10 @@ static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[],
|
|||
u16 size;
|
||||
int ret;
|
||||
|
||||
/* Prepare control command and copy to SPI control buffer */
|
||||
oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op);
|
||||
size = oa_tc6_calculate_ctrl_buf_size(length, tc6->prot_ctrl);
|
||||
|
||||
size = oa_tc6_calculate_ctrl_buf_size(length);
|
||||
/* Prepare control command and copy to SPI control buffer */
|
||||
oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op, size);
|
||||
|
||||
/* Perform SPI transfer */
|
||||
ret = oa_tc6_spi_transfer(tc6, OA_TC6_CTRL_HEADER, size);
|
||||
|
|
@ -302,7 +292,7 @@ static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[],
|
|||
return oa_tc6_check_ctrl_write_reply(tc6, size);
|
||||
|
||||
/* Check echoed/received control read command reply for errors */
|
||||
ret = oa_tc6_check_ctrl_read_reply(tc6, size);
|
||||
ret = oa_tc6_check_ctrl_read_reply(tc6, length);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
@ -355,6 +345,27 @@ int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(oa_tc6_read_register);
|
||||
|
||||
/**
|
||||
* oa_tc6_read_register_mms - function for reading a MAC-PHY register in a
|
||||
* specified memory map.
|
||||
* @tc6: oa_tc6 struct.
|
||||
* @mms: Memory map selector for the register.
|
||||
* @address: register address of the MAC-PHY to be read.
|
||||
* @value: value read from the @address register address of the MAC-PHY.
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address,
|
||||
u32 *value)
|
||||
{
|
||||
u32 mms_reg;
|
||||
|
||||
mms_reg = FIELD_PREP(OA_TC6_REG_MMS_MASK, mms) | address;
|
||||
|
||||
return oa_tc6_read_registers(tc6, mms_reg, value, 1);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(oa_tc6_read_register_mms);
|
||||
|
||||
/**
|
||||
* oa_tc6_write_registers - function for writing multiple consecutive registers.
|
||||
* @tc6: oa_tc6 struct.
|
||||
|
|
@ -399,6 +410,27 @@ int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(oa_tc6_write_register);
|
||||
|
||||
/**
|
||||
* oa_tc6_write_register_mms - function for writing a MAC-PHY register in a
|
||||
* specified memory map.
|
||||
* @tc6: oa_tc6 struct.
|
||||
* @mms: Memory map selector for the register.
|
||||
* @address: register address of the MAC-PHY to be written.
|
||||
* @value: value to be written in the @address register address of the MAC-PHY.
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address,
|
||||
u32 value)
|
||||
{
|
||||
u32 mms_reg;
|
||||
|
||||
mms_reg = FIELD_PREP(OA_TC6_REG_MMS_MASK, mms) | address;
|
||||
|
||||
return oa_tc6_write_registers(tc6, mms_reg, &value, 1);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms);
|
||||
|
||||
static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6)
|
||||
{
|
||||
u32 regval;
|
||||
|
|
@ -408,7 +440,7 @@ static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!(regval & STDCAP_DIRECT_PHY_REG_ACCESS))
|
||||
if (!(regval & OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS))
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
|
|
@ -462,36 +494,39 @@ static int oa_tc6_get_phy_c45_mms(int devnum)
|
|||
}
|
||||
}
|
||||
|
||||
static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
|
||||
int regnum)
|
||||
int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
|
||||
int regnum)
|
||||
{
|
||||
struct oa_tc6 *tc6 = bus->priv;
|
||||
u32 regval;
|
||||
int mms;
|
||||
int ret;
|
||||
|
||||
ret = oa_tc6_get_phy_c45_mms(devnum);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
mms = oa_tc6_get_phy_c45_mms(devnum);
|
||||
if (mms < 0)
|
||||
return mms;
|
||||
|
||||
ret = oa_tc6_read_register(tc6, (ret << 16) | regnum, ®val);
|
||||
ret = oa_tc6_read_register_mms(tc6, mms, regnum, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return regval;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_read_c45);
|
||||
|
||||
static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
|
||||
int regnum, u16 val)
|
||||
int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
|
||||
int regnum, u16 val)
|
||||
{
|
||||
struct oa_tc6 *tc6 = bus->priv;
|
||||
int ret;
|
||||
int mms;
|
||||
|
||||
ret = oa_tc6_get_phy_c45_mms(devnum);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
mms = oa_tc6_get_phy_c45_mms(devnum);
|
||||
if (mms < 0)
|
||||
return mms;
|
||||
|
||||
return oa_tc6_write_register(tc6, (ret << 16) | regnum, val);
|
||||
return oa_tc6_write_register_mms(tc6, mms, regnum, val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_write_c45);
|
||||
|
||||
static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
|
||||
{
|
||||
|
|
@ -544,6 +579,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
|
|||
{
|
||||
int ret;
|
||||
|
||||
if (tc6->quirk_flags & OA_TC6_BROKEN_PHY)
|
||||
return 0;
|
||||
|
||||
ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
|
||||
if (ret) {
|
||||
netdev_err(tc6->netdev,
|
||||
|
|
@ -580,6 +618,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
|
|||
|
||||
static void oa_tc6_phy_exit(struct oa_tc6 *tc6)
|
||||
{
|
||||
if (tc6->quirk_flags & OA_TC6_BROKEN_PHY)
|
||||
return;
|
||||
|
||||
phy_disconnect(tc6->phydev);
|
||||
oa_tc6_mdiobus_unregister(tc6);
|
||||
}
|
||||
|
|
@ -601,7 +642,7 @@ static int oa_tc6_read_status0(struct oa_tc6 *tc6)
|
|||
|
||||
static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6)
|
||||
{
|
||||
u32 regval = RESET_SWRESET;
|
||||
u32 regval = OA_TC6_RESET_SWRESET;
|
||||
int ret;
|
||||
|
||||
ret = oa_tc6_write_register(tc6, OA_TC6_REG_RESET, regval);
|
||||
|
|
@ -610,7 +651,7 @@ static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6)
|
|||
|
||||
/* Poll for soft reset complete for every 1ms until 1s timeout */
|
||||
ret = readx_poll_timeout(oa_tc6_read_status0, tc6, regval,
|
||||
regval & STATUS0_RESETC,
|
||||
regval & OA_TC6_STATUS0_RESETC,
|
||||
STATUS0_RESETC_POLL_DELAY,
|
||||
STATUS0_RESETC_POLL_TIMEOUT);
|
||||
if (ret)
|
||||
|
|
@ -629,10 +670,10 @@ static int oa_tc6_unmask_macphy_error_interrupts(struct oa_tc6 *tc6)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
regval &= ~(INT_MASK0_TX_PROTOCOL_ERR_MASK |
|
||||
INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK |
|
||||
INT_MASK0_LOSS_OF_FRAME_ERR_MASK |
|
||||
INT_MASK0_HEADER_ERR_MASK);
|
||||
regval &= ~(OA_TC6_INT_MASK0_TX_PROTOCOL_ERR_MASK |
|
||||
OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK |
|
||||
OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK |
|
||||
OA_TC6_INT_MASK0_HEADER_ERR_MASK);
|
||||
|
||||
return oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
|
||||
}
|
||||
|
|
@ -647,7 +688,7 @@ static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6)
|
|||
return ret;
|
||||
|
||||
/* Enable configuration synchronization for data transfer */
|
||||
value |= CONFIG0_SYNC;
|
||||
value |= OA_TC6_CONFIG0_SYNC;
|
||||
|
||||
return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value);
|
||||
}
|
||||
|
|
@ -692,7 +733,7 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
|
|||
*/
|
||||
static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
|
||||
{
|
||||
u32 regval = INT_MASK0_ALL_INTERRUPTS;
|
||||
u32 regval = OA_TC6_INT_MASK0_ALL_INTERRUPTS;
|
||||
|
||||
tc6->disable_traffic = true;
|
||||
oa_tc6_free_pending_skbs(tc6);
|
||||
|
|
@ -722,25 +763,25 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (FIELD_GET(STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) {
|
||||
if (FIELD_GET(OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) {
|
||||
tc6->rx_buf_overflow = true;
|
||||
oa_tc6_cleanup_ongoing_rx_skb(tc6);
|
||||
net_err_ratelimited("%s: Receive buffer overflow error\n",
|
||||
tc6->netdev->name);
|
||||
return -EAGAIN;
|
||||
}
|
||||
if (FIELD_GET(STATUS0_TX_PROTOCOL_ERROR, value)) {
|
||||
if (FIELD_GET(OA_TC6_STATUS0_TX_PROTOCOL_ERROR, value)) {
|
||||
netdev_err(tc6->netdev, "Transmit protocol error\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
/* TODO: Currently loss of frame and header errors are treated as
|
||||
* non-recoverable errors. They will be handled in the next version.
|
||||
*/
|
||||
if (FIELD_GET(STATUS0_LOSS_OF_FRAME_ERROR, value)) {
|
||||
if (FIELD_GET(OA_TC6_STATUS0_LOSS_OF_FRAME_ERROR, value)) {
|
||||
netdev_err(tc6->netdev, "Loss of frame error\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (FIELD_GET(STATUS0_HEADER_ERROR, value)) {
|
||||
if (FIELD_GET(OA_TC6_STATUS0_HEADER_ERROR, value)) {
|
||||
netdev_err(tc6->netdev, "Header error\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
|
@ -1187,9 +1228,10 @@ static int oa_tc6_update_buffer_status_from_register(struct oa_tc6 *tc6)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
tc6->tx_credits = FIELD_GET(BUFFER_STATUS_TX_CREDITS_AVAILABLE, value);
|
||||
tc6->rx_chunks_available = FIELD_GET(BUFFER_STATUS_RX_CHUNKS_AVAILABLE,
|
||||
value);
|
||||
tc6->tx_credits = FIELD_GET(OA_TC6_BUFFER_STATUS_TX_CREDITS_AVAILABLE,
|
||||
value);
|
||||
tc6->rx_chunks_available =
|
||||
FIELD_GET(OA_TC6_BUFFER_STATUS_RX_CHUNKS_AVAILABLE, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1233,7 +1275,7 @@ int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6)
|
|||
return ret;
|
||||
|
||||
/* Set Zero-Align Receive Frame Enable */
|
||||
regval |= CONFIG0_ZARFE_ENABLE;
|
||||
regval |= OA_TC6_CONFIG0_ZARFE_ENABLE;
|
||||
|
||||
return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, regval);
|
||||
}
|
||||
|
|
@ -1272,15 +1314,31 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(oa_tc6_start_xmit);
|
||||
|
||||
static int oa_tc6_check_ctrl_protection(struct oa_tc6 *tc6)
|
||||
{
|
||||
u32 regval;
|
||||
int ret;
|
||||
|
||||
ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
tc6->prot_ctrl = FIELD_GET(OA_TC6_CONFIG0_PROTE, regval);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* oa_tc6_init - allocates and initializes oa_tc6 structure.
|
||||
* @spi: device with which data will be exchanged.
|
||||
* @netdev: network device interface structure.
|
||||
* @quirks: device specific modifiers for the OA TC6 protocol.
|
||||
*
|
||||
* Return: pointer reference to the oa_tc6 structure if the MAC-PHY
|
||||
* initialization is successful otherwise NULL.
|
||||
*/
|
||||
struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
|
||||
struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
|
||||
struct oa_tc6_quirks *quirks)
|
||||
{
|
||||
struct oa_tc6 *tc6;
|
||||
int ret;
|
||||
|
|
@ -1295,6 +1353,9 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
|
|||
mutex_init(&tc6->spi_ctrl_lock);
|
||||
spin_lock_init(&tc6->tx_skb_lock);
|
||||
|
||||
if (quirks)
|
||||
tc6->quirk_flags = quirks->quirk_flags;
|
||||
|
||||
/* Set the SPI controller to pump at realtime priority */
|
||||
tc6->spi->rt = true;
|
||||
if (spi_setup(tc6->spi) < 0)
|
||||
|
|
@ -1324,6 +1385,14 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
|
|||
if (!tc6->spi_data_rx_buf)
|
||||
return NULL;
|
||||
|
||||
/* Check the PROTE bit status so that we can reset the device */
|
||||
ret = oa_tc6_check_ctrl_protection(tc6);
|
||||
if (ret) {
|
||||
dev_err(&tc6->spi->dev,
|
||||
"Failed to check the protection mode: %d\n", ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = oa_tc6_sw_reset_macphy(tc6);
|
||||
if (ret) {
|
||||
dev_err(&tc6->spi->dev,
|
||||
|
|
|
|||
|
|
@ -136,6 +136,12 @@ config ADIN1100_PHY
|
|||
Currently supports the:
|
||||
- ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet PHY
|
||||
|
||||
config ADIN1140_PHY
|
||||
tristate "Analog Devices ADIN1140 10BASE-T1S PHY"
|
||||
help
|
||||
Adds support for the Analog Devices, Inc. ADIN1140's internal
|
||||
10BASE-T1S PHY.
|
||||
|
||||
config AMCC_QT2025_PHY
|
||||
tristate "AMCC QT2025 PHY"
|
||||
depends on RUST_PHYLIB_ABSTRACTIONS
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m)
|
|||
|
||||
obj-$(CONFIG_ADIN_PHY) += adin.o
|
||||
obj-$(CONFIG_ADIN1100_PHY) += adin1100.o
|
||||
obj-$(CONFIG_ADIN1140_PHY) += adin1140-phy.o
|
||||
obj-$(CONFIG_AIR_AN8801_PHY) += air_an8801.o
|
||||
obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o
|
||||
obj-$(CONFIG_AIR_NET_PHYLIB) += air_phy_lib.o
|
||||
|
|
|
|||
72
drivers/net/phy/adin1140-phy.c
Normal file
72
drivers/net/phy/adin1140-phy.c
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S PHY
|
||||
*
|
||||
* Copyright 2026 Analog Devices Inc.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/phy.h>
|
||||
|
||||
#define ADIN1140_PHY_ID 0x0283be00
|
||||
|
||||
#define ADIN1140_PCS_CTRL 0x08f3
|
||||
#define ADIN1140_PCS_CTRL_LOOPBACK BIT(14)
|
||||
|
||||
static int adin1140_config_aneg(struct phy_device *phydev)
|
||||
{
|
||||
/* phylib tries to clear BIT(12) in MDIO_CTRL1, since AN is disabled.
|
||||
* However, on the ADIN1140, that field is non-standard, being used
|
||||
* to control the reset status of the PHY (thus it needs to remain set).
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adin1140_loopback(struct phy_device *phydev, bool enable, int speed)
|
||||
{
|
||||
if (enable && speed)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return phy_modify_mmd(phydev, MDIO_MMD_PCS, ADIN1140_PCS_CTRL,
|
||||
ADIN1140_PCS_CTRL_LOOPBACK,
|
||||
enable ? ADIN1140_PCS_CTRL_LOOPBACK : 0);
|
||||
}
|
||||
|
||||
static int adin1140_read_status(struct phy_device *phydev)
|
||||
{
|
||||
phydev->link = 1;
|
||||
phydev->duplex = DUPLEX_HALF;
|
||||
phydev->speed = SPEED_10;
|
||||
phydev->autoneg = AUTONEG_DISABLE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phy_driver adin1140_driver[] = {
|
||||
{
|
||||
PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID),
|
||||
.name = "ADIN1140_PHY",
|
||||
.features = PHY_BASIC_T1S_P2MP_FEATURES,
|
||||
.read_status = adin1140_read_status,
|
||||
.config_aneg = adin1140_config_aneg,
|
||||
.set_loopback = adin1140_loopback,
|
||||
.read_mmd = genphy_read_mmd_c45,
|
||||
.write_mmd = genphy_write_mmd_c45,
|
||||
.get_plca_cfg = genphy_c45_plca_get_cfg,
|
||||
.set_plca_cfg = genphy_c45_plca_set_cfg,
|
||||
.get_plca_status = genphy_c45_plca_get_status,
|
||||
},
|
||||
};
|
||||
module_phy_driver(adin1140_driver);
|
||||
|
||||
static const struct mdio_device_id __maybe_unused adin1140_tbl[] = {
|
||||
{ PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID) },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(mdio, adin1140_tbl);
|
||||
|
||||
MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S PHY");
|
||||
MODULE_AUTHOR("Ciprian Regus <ciprian.regus@analog.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -506,34 +506,6 @@ static int lan86xx_read_status(struct phy_device *phydev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and
|
||||
* C45 registers space. If the PHY is discovered via C22 bus protocol it assumes
|
||||
* it uses C22 protocol and always uses C22 registers indirect access to access
|
||||
* C45 registers. This is because, we don't have a clean separation between
|
||||
* C22/C45 register space and C22/C45 MDIO bus protocols. Resulting, PHY C45
|
||||
* registers direct access can't be used which can save multiple SPI bus access.
|
||||
* To support this feature, set .read_mmd/.write_mmd in the PHY driver to call
|
||||
* .read_c45/.write_c45 in the OPEN Alliance framework
|
||||
* drivers/net/ethernet/oa_tc6.c
|
||||
*/
|
||||
static int lan865x_phy_read_mmd(struct phy_device *phydev, int devnum,
|
||||
u16 regnum)
|
||||
{
|
||||
struct mii_bus *bus = phydev->mdio.bus;
|
||||
int addr = phydev->mdio.addr;
|
||||
|
||||
return __mdiobus_c45_read(bus, addr, devnum, regnum);
|
||||
}
|
||||
|
||||
static int lan865x_phy_write_mmd(struct phy_device *phydev, int devnum,
|
||||
u16 regnum, u16 val)
|
||||
{
|
||||
struct mii_bus *bus = phydev->mdio.bus;
|
||||
int addr = phydev->mdio.addr;
|
||||
|
||||
return __mdiobus_c45_write(bus, addr, devnum, regnum, val);
|
||||
}
|
||||
|
||||
static struct phy_driver microchip_t1s_driver[] = {
|
||||
{
|
||||
PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1),
|
||||
|
|
@ -584,8 +556,8 @@ static struct phy_driver microchip_t1s_driver[] = {
|
|||
.features = PHY_BASIC_T1S_P2MP_FEATURES,
|
||||
.config_init = lan865x_revb_config_init,
|
||||
.read_status = lan86xx_read_status,
|
||||
.read_mmd = lan865x_phy_read_mmd,
|
||||
.write_mmd = lan865x_phy_write_mmd,
|
||||
.read_mmd = genphy_read_mmd_c45,
|
||||
.write_mmd = genphy_write_mmd_c45,
|
||||
.get_plca_cfg = genphy_c45_plca_get_cfg,
|
||||
.set_plca_cfg = lan86xx_plca_set_cfg,
|
||||
.get_plca_status = genphy_c45_plca_get_status,
|
||||
|
|
|
|||
|
|
@ -2770,6 +2770,31 @@ int genphy_read_abilities(struct phy_device *phydev)
|
|||
}
|
||||
EXPORT_SYMBOL(genphy_read_abilities);
|
||||
|
||||
/* Some PHYs support direct C45 register access but not C22 indirect
|
||||
* MMD access (registers 13 and 14). When discovered via C22, phylib
|
||||
* routes MMD access through the indirect path, which won't work on
|
||||
* these devices. These helpers bypass indirect access and use the bus
|
||||
* C45 accessors directly.
|
||||
*/
|
||||
int genphy_read_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum)
|
||||
{
|
||||
struct mii_bus *bus = phydev->mdio.bus;
|
||||
int addr = phydev->mdio.addr;
|
||||
|
||||
return __mdiobus_c45_read(bus, addr, devnum, regnum);
|
||||
}
|
||||
EXPORT_SYMBOL(genphy_read_mmd_c45);
|
||||
|
||||
int genphy_write_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum,
|
||||
u16 val)
|
||||
{
|
||||
struct mii_bus *bus = phydev->mdio.bus;
|
||||
int addr = phydev->mdio.addr;
|
||||
|
||||
return __mdiobus_c45_write(bus, addr, devnum, regnum, val);
|
||||
}
|
||||
EXPORT_SYMBOL(genphy_write_mmd_c45);
|
||||
|
||||
/* This is used for the phy device which doesn't support the MMD extended
|
||||
* register access, but it does have side effect when we are trying to access
|
||||
* the MMD register via indirect method.
|
||||
|
|
|
|||
|
|
@ -10,15 +10,86 @@
|
|||
#include <linux/etherdevice.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
/* OPEN Alliance TC6 registers */
|
||||
/* Standard Capabilities Register */
|
||||
#define OA_TC6_REG_STDCAP 0x0002
|
||||
#define OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
|
||||
|
||||
/* Reset Control and Status Register */
|
||||
#define OA_TC6_REG_RESET 0x0003
|
||||
#define OA_TC6_RESET_SWRESET BIT(0) /* Software Reset */
|
||||
|
||||
/* Configuration Register #0 */
|
||||
#define OA_TC6_REG_CONFIG0 0x0004
|
||||
#define OA_TC6_CONFIG0_SYNC BIT(15)
|
||||
#define OA_TC6_CONFIG0_ZARFE_ENABLE BIT(12)
|
||||
#define OA_TC6_CONFIG0_PROTE BIT(5)
|
||||
|
||||
/* Configuration Register #2 */
|
||||
#define OA_TC6_REG_CONFIG2 0x0006
|
||||
|
||||
/* Status Register #0 */
|
||||
#define OA_TC6_REG_STATUS0 0x0008
|
||||
#define OA_TC6_STATUS0_RESETC BIT(6) /* Reset Complete */
|
||||
#define OA_TC6_STATUS0_HEADER_ERROR BIT(5)
|
||||
#define OA_TC6_STATUS0_LOSS_OF_FRAME_ERROR BIT(4)
|
||||
#define OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3)
|
||||
#define OA_TC6_STATUS0_TX_PROTOCOL_ERROR BIT(0)
|
||||
|
||||
/* Buffer Status Register */
|
||||
#define OA_TC6_REG_BUFFER_STATUS 0x000B
|
||||
#define OA_TC6_BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8)
|
||||
#define OA_TC6_BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0)
|
||||
|
||||
/* Interrupt Mask Register #0 */
|
||||
#define OA_TC6_REG_INT_MASK0 0x000C
|
||||
#define OA_TC6_INT_MASK0_HEADER_ERR_MASK BIT(5)
|
||||
#define OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4)
|
||||
#define OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3)
|
||||
#define OA_TC6_INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0)
|
||||
#define OA_TC6_INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \
|
||||
GENMASK(12, 7))
|
||||
|
||||
/* PHY Clause 22 registers base address and mask */
|
||||
#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00
|
||||
#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F
|
||||
|
||||
/* Memory map selector (MMS) values as per table 6 in the
|
||||
* OPEN Alliance specification.
|
||||
*/
|
||||
#define OA_TC6_MAC_MMS1 1
|
||||
#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */
|
||||
#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */
|
||||
#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
|
||||
#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
|
||||
#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */
|
||||
|
||||
struct oa_tc6;
|
||||
|
||||
struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev);
|
||||
enum oa_tc6_quirk_flag {
|
||||
OA_TC6_BROKEN_PHY = BIT(0),
|
||||
};
|
||||
|
||||
struct oa_tc6_quirks {
|
||||
enum oa_tc6_quirk_flag quirk_flags;
|
||||
};
|
||||
|
||||
struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
|
||||
struct oa_tc6_quirks *quirks);
|
||||
void oa_tc6_exit(struct oa_tc6 *tc6);
|
||||
int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value);
|
||||
int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address,
|
||||
u32 value);
|
||||
int oa_tc6_write_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
|
||||
u8 length);
|
||||
int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value);
|
||||
int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address,
|
||||
u32 *value);
|
||||
int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
|
||||
u8 length);
|
||||
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb);
|
||||
int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6);
|
||||
int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
|
||||
int regnum);
|
||||
int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
|
||||
int regnum, u16 val);
|
||||
|
|
|
|||
|
|
@ -2302,6 +2302,9 @@ static inline int genphy_no_config_intr(struct phy_device *phydev)
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
int genphy_read_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum);
|
||||
int genphy_write_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum,
|
||||
u16 val);
|
||||
int genphy_read_mmd_unsupported(struct phy_device *phdev, int devad,
|
||||
u16 regnum);
|
||||
int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user