* [PATCH net-next 0/5] net: Add ADIN1140 support
@ 2026-05-02 23:24 Ciprian Regus via B4 Relay
2026-05-02 23:24 ` [PATCH net-next 1/5] net: ethernet: oa_tc6: Handle the OA TC6 SPI protected mode Ciprian Regus via B4 Relay
` (4 more replies)
0 siblings, 5 replies; 21+ messages in thread
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
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 first 2 patches in the series are changes to the oa_tc6, that would
make the framework usable by the subsequent ADIN1140 MAC driver.
The first commit is required because the ADIN1140 only allows protected
mode OA TC6 control transactions, which the oa_tc6 framework doesn't
currently implement.
The second commit 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>
---
Ciprian Regus (5):
net: ethernet: oa_tc6: Handle the OA TC6 SPI protected mode
net: ethernet: oa_tc6: Allow custom mii_bus
net: phy: Add support for the ADIN1140 PHY
net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
dt-bindings: net: Add bindings for the ADIN1140
.../devicetree/bindings/net/adi,adin1140.yaml | 69 ++
Documentation/networking/oa-tc6-framework.rst | 3 +-
MAINTAINERS | 15 +
drivers/net/ethernet/adi/Kconfig | 12 +
drivers/net/ethernet/adi/Makefile | 1 +
drivers/net/ethernet/adi/adin1140.c | 805 +++++++++++++++++++++
drivers/net/ethernet/microchip/lan865x/lan865x.c | 6 +-
drivers/net/ethernet/oa_tc6.c | 194 +++--
drivers/net/phy/Kconfig | 6 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/adin1140.c | 102 +++
include/linux/oa_tc6.h | 9 +-
12 files changed, 1173 insertions(+), 50 deletions(-)
---
base-commit: fbf6f64a4322cfeb0d98f39baf8ce18246dd12c0
change-id: 20260429-adin1140-driver-93ae0d376318
Best regards,
--
Ciprian Regus <ciprian.regus@analog.com>
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH net-next 1/5] net: ethernet: oa_tc6: Handle the OA TC6 SPI protected mode
2026-05-02 23:24 [PATCH net-next 0/5] net: Add ADIN1140 support Ciprian Regus via B4 Relay
@ 2026-05-02 23:24 ` Ciprian Regus via B4 Relay
2026-05-02 23:24 ` [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus Ciprian Regus via B4 Relay
` (3 subsequent siblings)
4 siblings, 0 replies; 21+ messages in thread
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
From: Ciprian Regus <ciprian.regus@analog.com>
Implement the OA TC6 standard defined protected mode for control (register
access) transactions. In addition to the current register access formats
the oa_tc6 driver handles, 1's complement values of the data field
are included (by both the host and the MACPHY) in the SPI transfer frames.
This feature acts as an integrity check.
Control write transactions look like this:
|<- 32 bits ->|<--- data_size --->|<- 32 bits ->|
MOSI: | ctrl header | reg write data | ignored |
MISO: | (discard) | echoed ctrl hdr | echoed data |
data_size (LEN = number of registers to read in a sequence):
Unprotected: 32 x (LEN + 1) bits
Protected: 2 x 32 x (LEN + 1) bits
Control read transaction:
|<- 32 bits ->|<--- 32 bits --> |<- data_size ->|
MOSI: | ctrl header | ignored ... |
MISO: | (discard) | echoed ctrl hdr | reg read data |
data_size (LEN = number of registers to read in a sequence):
Unprotected: 32 x (LEN + 1) bits
Protected: 2 x 32 x (LEN + 1) bits
Register data format ("reg write data" and "reg read data"):
Unprotected:
| W1 (normal) | W2 (normal) | ... | Wx (normal) |
Protected:
| W1 (normal) | W1 (complement) | ... | Wx (normal) | Wx (complement)|
The protected mode state can be read from the bit 5 of CONFIG0 (0x4)
register, and this setting is usually only configured during the
MACPHY's reset (depending on the device it can be done by setting the
state of a pin). We can read the protected mode configuration before any
other register access and since the SPI transfer is initially sized for an
unprotected read, the MACPHY's complement words are never clocked out
and no checking is required. The data transactions (Ethernet frames)
remain unchanged.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
drivers/net/ethernet/oa_tc6.c | 105 ++++++++++++++++++++++++++++++++++++------
1 file changed, 92 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 91a906a7918a..546ca652d974 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -24,6 +24,7 @@
#define OA_TC6_REG_CONFIG0 0x0004
#define CONFIG0_SYNC BIT(15)
#define CONFIG0_ZARFE_ENABLE BIT(12)
+#define CONFIG0_PROTE BIT(5)
/* Status Register #0 */
#define OA_TC6_REG_STATUS0 0x0008
@@ -87,6 +88,7 @@
#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
@@ -95,6 +97,13 @@
(OA_TC6_CTRL_MAX_REGISTERS *\
OA_TC6_CTRL_REG_VALUE_SIZE) +\
OA_TC6_CTRL_IGNORED_SIZE)
+
+#define OA_TC6_CTRL_SPI_BUF_PROT_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 +\
@@ -129,6 +138,7 @@ struct oa_tc6 {
u8 rx_chunks_available;
bool rx_buf_overflow;
bool int_flag;
+ bool prot_ctrl;
};
enum oa_tc6_header_type {
@@ -212,25 +222,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)
@@ -253,10 +274,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.
@@ -264,6 +287,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;
}
@@ -273,8 +310,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[],
@@ -283,10 +325,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);
@@ -301,7 +343,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;
@@ -1224,6 +1266,20 @@ 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(CONFIG0_PROTE, regval);
+
+ return 0;
+}
+
/**
* oa_tc6_init - allocates and initializes oa_tc6 structure.
* @spi: device with which data will be exchanged.
@@ -1276,6 +1332,29 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
if (!tc6->spi_data_rx_buf)
return NULL;
+ 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;
+ }
+
+ if (tc6->prot_ctrl) {
+ tc6->spi_ctrl_tx_buf = devm_krealloc(&tc6->spi->dev,
+ tc6->spi_ctrl_tx_buf,
+ OA_TC6_CTRL_SPI_BUF_PROT_SIZE,
+ GFP_KERNEL);
+ if (!tc6->spi_ctrl_tx_buf)
+ return NULL;
+
+ tc6->spi_ctrl_rx_buf = devm_krealloc(&tc6->spi->dev,
+ tc6->spi_ctrl_rx_buf,
+ OA_TC6_CTRL_SPI_BUF_PROT_SIZE,
+ GFP_KERNEL);
+ if (!tc6->spi_ctrl_rx_buf)
+ return NULL;
+ }
+
ret = oa_tc6_sw_reset_macphy(tc6);
if (ret) {
dev_err(&tc6->spi->dev,
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus
2026-05-02 23:24 [PATCH net-next 0/5] net: Add ADIN1140 support Ciprian Regus via B4 Relay
2026-05-02 23:24 ` [PATCH net-next 1/5] net: ethernet: oa_tc6: Handle the OA TC6 SPI protected mode Ciprian Regus via B4 Relay
@ 2026-05-02 23:24 ` Ciprian Regus via B4 Relay
2026-05-03 3:50 ` Andrew Lunn
2026-05-02 23:24 ` [PATCH net-next 3/5] net: phy: Add support for the ADIN1140 PHY Ciprian Regus via B4 Relay
` (2 subsequent siblings)
4 siblings, 1 reply; 21+ messages in thread
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
From: Ciprian Regus <ciprian.regus@analog.com>
Some drivers that use oa_tc6 have to use their own mdio bus access
functions as a workaround for hardware issues. Support these cases by
adding a new parameter for the mii_bus in the oa_tc6_init(). In this
case, drivers are responsible for allocating the mii_bus struct, assign
the bus access methods and free the memory after it's no longer used
by oa_tc6. The mii_bus is registered/unregistered by oa_tc6. The phy
connection process does not change and it's still done by oa_tc6.
Drivers can still choose to use the default mii_bus access functions
implemented by oa_tc6 by passing a NULL reference in the mii_bus param.
To avoid extending the function signature every time a new configuration
option is needed, convert oa_tc6_init() to take a config struct.
Also, update the affected drivers and the oa_tc6 framework documentation.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
Documentation/networking/oa-tc6-framework.rst | 3 +-
drivers/net/ethernet/microchip/lan865x/lan865x.c | 6 +-
drivers/net/ethernet/oa_tc6.c | 89 +++++++++++++++---------
include/linux/oa_tc6.h | 9 ++-
4 files changed, 70 insertions(+), 37 deletions(-)
diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst
index fe2aabde923a..eaa5b4b85b34 100644
--- a/Documentation/networking/oa-tc6-framework.rst
+++ b/Documentation/networking/oa-tc6-framework.rst
@@ -453,8 +453,7 @@ 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)
+.. c:function:: struct oa_tc6 *oa_tc6_init(struct oa_tc6_config *config);
Initialize OA TC6 lib.
diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c
index 0277d9737369..c509c8a3e321 100644
--- a/drivers/net/ethernet/microchip/lan865x/lan865x.c
+++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c
@@ -332,6 +332,7 @@ static const struct net_device_ops lan865x_netdev_ops = {
static int lan865x_probe(struct spi_device *spi)
{
+ struct oa_tc6_config tc6_config = {};
struct net_device *netdev;
struct lan865x_priv *priv;
int ret;
@@ -346,7 +347,10 @@ 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);
+ tc6_config.spi = spi;
+ tc6_config.netdev = netdev;
+
+ priv->tc6 = oa_tc6_init(&tc6_config);
if (!priv->tc6) {
ret = -ENODEV;
goto free_netdev;
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 546ca652d974..fa89b820133f 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -139,6 +139,7 @@ struct oa_tc6 {
bool rx_buf_overflow;
bool int_flag;
bool prot_ctrl;
+ bool own_mdiobus;
};
enum oa_tc6_header_type {
@@ -538,32 +539,37 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
{
int ret;
- tc6->mdiobus = mdiobus_alloc();
if (!tc6->mdiobus) {
- netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
- return -ENOMEM;
+ tc6->mdiobus = mdiobus_alloc();
+ if (!tc6->mdiobus) {
+ netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
+ return -ENOMEM;
+ }
+
+ tc6->mdiobus->read = oa_tc6_mdiobus_read;
+ tc6->mdiobus->write = oa_tc6_mdiobus_write;
+ /* 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, PHY
+ * drivers can set .read_mmd/.write_mmd in the PHY driver to call
+ * .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
+ */
+ tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
+ tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
+
+ tc6->own_mdiobus = true;
}
tc6->mdiobus->priv = tc6;
- tc6->mdiobus->read = oa_tc6_mdiobus_read;
- tc6->mdiobus->write = oa_tc6_mdiobus_write;
- /* 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, PHY
- * drivers can set .read_mmd/.write_mmd in the PHY driver to call
- * .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
- */
- tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
- tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
- tc6->mdiobus->name = "oa-tc6-mdiobus";
tc6->mdiobus->parent = tc6->dev;
+ tc6->mdiobus->name = "oa-tc6-mdiobus";
snprintf(tc6->mdiobus->id, ARRAY_SIZE(tc6->mdiobus->id), "%s",
- dev_name(&tc6->spi->dev));
+ dev_name(&tc6->spi->dev));
ret = mdiobus_register(tc6->mdiobus);
if (ret) {
@@ -577,19 +583,30 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
static void oa_tc6_mdiobus_unregister(struct oa_tc6 *tc6)
{
+ if (!tc6->mdiobus)
+ return;
+
mdiobus_unregister(tc6->mdiobus);
- mdiobus_free(tc6->mdiobus);
+
+ if (tc6->own_mdiobus)
+ mdiobus_free(tc6->mdiobus);
}
static int oa_tc6_phy_init(struct oa_tc6 *tc6)
{
int ret;
- ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
- if (ret) {
- netdev_err(tc6->netdev,
- "Direct PHY register access is not supported by the MAC-PHY\n");
- return ret;
+ /* If the driver provided a mii_bus, it is also responsible for
+ * implementing the bus access methods, so we don't have to worry
+ * about checking the PHY access mode.
+ */
+ if (!tc6->mdiobus) {
+ ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
+ if (ret) {
+ netdev_err(tc6->netdev,
+ "Direct PHY register access is not supported by the MAC-PHY\n");
+ return ret;
+ }
}
ret = oa_tc6_mdiobus_register(tc6);
@@ -621,7 +638,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6)
static void oa_tc6_phy_exit(struct oa_tc6 *tc6)
{
- phy_disconnect(tc6->phydev);
+ if (tc6->phydev)
+ phy_disconnect(tc6->phydev);
+
oa_tc6_mdiobus_unregister(tc6);
}
@@ -1282,24 +1301,28 @@ static int oa_tc6_check_ctrl_protection(struct oa_tc6 *tc6)
/**
* oa_tc6_init - allocates and initializes oa_tc6 structure.
- * @spi: device with which data will be exchanged.
- * @netdev: network device interface structure.
+ * @config: pointer to a caller-filled structure describing the MACPHY
+ * (SPI device, net_device, and config flags).
*
* 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 oa_tc6_config *config)
{
struct oa_tc6 *tc6;
int ret;
- tc6 = devm_kzalloc(&spi->dev, sizeof(*tc6), GFP_KERNEL);
+ if (!config)
+ return NULL;
+
+ tc6 = devm_kzalloc(&config->spi->dev, sizeof(*tc6), GFP_KERNEL);
if (!tc6)
return NULL;
- tc6->spi = spi;
- tc6->netdev = netdev;
- SET_NETDEV_DEV(netdev, &spi->dev);
+ tc6->spi = config->spi;
+ tc6->netdev = config->netdev;
+ tc6->mdiobus = config->mii_bus;
+ SET_NETDEV_DEV(tc6->netdev, &tc6->spi->dev);
mutex_init(&tc6->spi_ctrl_lock);
spin_lock_init(&tc6->tx_skb_lock);
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 15f58e3c56c7..7ed7769bac88 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -8,11 +8,18 @@
*/
#include <linux/etherdevice.h>
+#include <linux/mdio.h>
#include <linux/spi/spi.h>
struct oa_tc6;
-struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev);
+struct oa_tc6_config {
+ struct spi_device *spi;
+ struct net_device *netdev;
+ struct mii_bus *mii_bus;
+};
+
+struct oa_tc6 *oa_tc6_init(struct oa_tc6_config *config);
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_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH net-next 3/5] net: phy: Add support for the ADIN1140 PHY
2026-05-02 23:24 [PATCH net-next 0/5] net: Add ADIN1140 support Ciprian Regus via B4 Relay
2026-05-02 23:24 ` [PATCH net-next 1/5] net: ethernet: oa_tc6: Handle the OA TC6 SPI protected mode Ciprian Regus via B4 Relay
2026-05-02 23:24 ` [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus Ciprian Regus via B4 Relay
@ 2026-05-02 23:24 ` Ciprian Regus via B4 Relay
2026-05-03 0:40 ` Andrew Lunn
2026-05-02 23:24 ` [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY Ciprian Regus via B4 Relay
2026-05-02 23:24 ` [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140 Ciprian Regus via B4 Relay
4 siblings, 1 reply; 21+ messages in thread
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
From: Ciprian Regus <ciprian.regus@analog.com>
Add a driver for the ADIN1140's internal 10BASE-T1S PHY. The device
doesn't implement autonegotiation, so the link is always reported as
being up. Since the PHY has no link-change interrupts and the link is
always up, we set phydev->irq = PHY_MAC_INTERRUPT to prevent phylib from
polling the link state.
The device implements both C22 and C45 MDIO access methods, but can only
be discovered over C22, since the C45 MMD devices lack the MDIO_DEVID1 and
MDIO_DEVID2 registers. The indirect C45 over C22 feature is not
supported.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
MAINTAINERS | 7 ++++
drivers/net/phy/Kconfig | 6 +++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/adin1140.c | 102 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 116 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 27a073f53cea..1e58da5ef47a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1843,6 +1843,13 @@ S: Supported
W: https://ez.analog.com/linux-software-drivers
F: drivers/dma/dma-axi-dmac.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.c
+
ANALOG DEVICES INC IIO DRIVERS
M: Lars-Peter Clausen <lars@metafoo.de>
M: Michael Hennerich <Michael.Hennerich@analog.com>
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index b5ee338b620d..fa5cd59a3825 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -124,6 +124,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
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 05e4878af27a..2519364bc334 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -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.o
obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o
obj-$(CONFIG_AMD_PHY) += amd.o
obj-$(CONFIG_AMCC_QT2025_PHY) += qt2025.o
diff --git a/drivers/net/phy/adin1140.c b/drivers/net/phy/adin1140.c
new file mode 100644
index 000000000000..3244107ce9ef
--- /dev/null
+++ b/drivers/net/phy/adin1140.c
@@ -0,0 +1,102 @@
+// 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_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 adin1140_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 int adin1140_config_init(struct phy_device *phydev)
+{
+ /* The link status of the PHY doesn't need to be polled, because
+ * the device doesn't implement AN and there is no other mechanism
+ * to report the link state.
+ */
+ phydev->irq = PHY_MAC_INTERRUPT;
+
+ return 0;
+}
+
+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",
+ .features = PHY_BASIC_T1S_P2MP_FEATURES,
+ .read_status = adin1140_read_status,
+ .config_init = adin1140_config_init,
+ .config_aneg = adin1140_config_aneg,
+ .set_loopback = adin1140_loopback,
+ .read_mmd = adin1140_phy_read_mmd,
+ .write_mmd = adin1140_phy_write_mmd,
+ .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");
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
2026-05-02 23:24 [PATCH net-next 0/5] net: Add ADIN1140 support Ciprian Regus via B4 Relay
` (2 preceding siblings ...)
2026-05-02 23:24 ` [PATCH net-next 3/5] net: phy: Add support for the ADIN1140 PHY Ciprian Regus via B4 Relay
@ 2026-05-02 23:24 ` Ciprian Regus via B4 Relay
2026-05-03 0:59 ` Andrew Lunn
` (3 more replies)
2026-05-02 23:24 ` [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140 Ciprian Regus via B4 Relay
4 siblings, 4 replies; 21+ messages in thread
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
From: Ciprian Regus <ciprian.regus@analog.com>
Add a driver for ADIN1140. The device is a 10BASE-T1S MAC-PHY
(integrated in the same package) that connects to a CPU over an SPI bus,
and implements the Open Alliance TC6 protocol for control and frame
transfers. As such, this driver relies on oa_tc6 for the communication
with the device. The device has an alternative name (AD3306), so the
driver can be probed using one of the two compatible strings.
For control transactions, ADIN1140 only implements the protected mode.
The driver has 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).
For the fields that are R/W (loopback and AN/reset) in the control
register, the PHY driver already implements the set_loopback() and
config_aneg() functions. The C22 write function of the driver is a
no-op and is used to protect against the ioctl MDIO access path.
C45 accesses do not cause this issue, so we can properly implement
them.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
MAINTAINERS | 7 +
drivers/net/ethernet/adi/Kconfig | 12 +
drivers/net/ethernet/adi/Makefile | 1 +
drivers/net/ethernet/adi/adin1140.c | 805 ++++++++++++++++++++++++++++++++++++
4 files changed, 825 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 1e58da5ef47a..f9784c25beac 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1843,6 +1843,13 @@ 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: drivers/net/ethernet/adi/adin1140.c
+
ANALOG DEVICES INC ETHERNET PHY DRIVERS
M: Ciprian Regus <ciprian.regus@analog.com>
L: netdev@vger.kernel.org
diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kconfig
index 760a9a60bc15..bdb8ff7d15da 100644
--- a/drivers/net/ethernet/adi/Kconfig
+++ b/drivers/net/ethernet/adi/Kconfig
@@ -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
diff --git a/drivers/net/ethernet/adi/Makefile b/drivers/net/ethernet/adi/Makefile
index d0383d94303c..0390ca8ccc49 100644
--- a/drivers/net/ethernet/adi/Makefile
+++ b/drivers/net/ethernet/adi/Makefile
@@ -4,3 +4,4 @@
#
obj-$(CONFIG_ADIN1110) += adin1110.o
+obj-$(CONFIG_ADIN1140) += adin1140.o
diff --git a/drivers/net/ethernet/adi/adin1140.c b/drivers/net/ethernet/adi/adin1140.c
new file mode 100644
index 000000000000..5bc3f5732ed8
--- /dev/null
+++ b/drivers/net/ethernet/adi/adin1140.c
@@ -0,0 +1,805 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#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_MMS_REG(m, r) ((((m) & GENMASK(3, 0)) << 16) | \
+ ((r) & GENMASK(15, 0)))
+
+#define ADIN1140_MACPHY_ID_REG ADIN1140_MMS_REG(0x0, 0x1)
+
+#define ADIN1140_CONFIG0_REG 0x0004
+#define ADIN1140_CONFIG0_TXFCSVE BIT(14)
+#define ADIN1140_CONFIG0_RFA_ZARFE BIT(12)
+#define ADIN1140_CONFIG0_CPS_64 GENMASK(2, 1)
+
+#define ADIN1140_CONFIG2_REG ADIN1140_MMS_REG(0x0, 0x6)
+#define ADIN1140_CONFIG2_FWD_UNK2HOST BIT(2)
+
+#define ADIN1140_MAC_P1_LOOP_ADDR_REG ADIN1140_MMS_REG(0x1, 0xC4)
+
+#define ADIN1140_MAC_ADDR_FILT_UPR_REG ADIN1140_MMS_REG(0x1, 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 ADIN1140_MMS_REG(0x1, 0x51)
+
+#define ADIN1140_MAC_ADDR_MASK_UPR_REG ADIN1140_MMS_REG(0x1, 0x70)
+#define ADIN1140_MAC_ADDR_MASK_LWR_REG ADIN1140_MMS_REG(0x1, 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_RX_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xA1)
+#define ADIN1140_RX_BC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xA2)
+#define ADIN1140_RX_MC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xA3)
+#define ADIN1140_RX_UC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xA4)
+#define ADIN1140_RX_CRC_ERR_CNT ADIN1140_MMS_REG(0x1, 0xA5)
+#define ADIN1140_RX_ALIGN_ERR_CNT ADIN1140_MMS_REG(0x1, 0xA6)
+#define ADIN1140_RX_PREAMBLE_ERR_CNT ADIN1140_MMS_REG(0x1, 0xA7)
+#define ADIN1140_RX_SHORT_ERR_CNT ADIN1140_MMS_REG(0x1, 0xA8)
+#define ADIN1140_RX_LONG_ERR_CNT ADIN1140_MMS_REG(0x1, 0xA9)
+#define ADIN1140_RX_PHY_ERR_CNT ADIN1140_MMS_REG(0x1, 0xAA)
+#define ADIN1140_RX_DRP_FULL_CNT ADIN1140_MMS_REG(0x1, 0xAB)
+#define ADIN1140_RX_DRP_FILTER_CNT ADIN1140_MMS_REG(0x1, 0xAD)
+#define ADIN1140_RX_IFG_ERR_CNT ADIN1140_MMS_REG(0x1, 0xAE)
+#define ADIN1140_TX_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xB1)
+#define ADIN1140_TX_BC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xB2)
+#define ADIN1140_TX_MC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xB3)
+#define ADIN1140_TX_UC_FRAME_CNT ADIN1140_MMS_REG(0x1, 0xB4)
+#define ADIN1140_TX_SINGLE_COL_CNT ADIN1140_MMS_REG(0x1, 0xB5)
+#define ADIN1140_TX_MULTI_COL_CNT ADIN1140_MMS_REG(0x1, 0xB6)
+#define ADIN1140_TX_DEFERRED_CNT ADIN1140_MMS_REG(0x1, 0xB7)
+#define ADIN1140_TX_LATE_COL_CNT ADIN1140_MMS_REG(0x1, 0xB8)
+#define ADIN1140_TX_EXCESS_COL_CNT ADIN1140_MMS_REG(0x1, 0xB9)
+#define ADIN1140_TX_UNDERRUN_CNT ADIN1140_MMS_REG(0x1, 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_C45_PCS_MMS2 2 /* MMD 3 */
+#define ADIN1140_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */
+#define ADIN1140_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
+
+#define ADIN1140_STATS_CNT 23
+#define ADIN1140_STATS_CHECK_DELAY (3 * HZ)
+
+struct adin1140_statistics_reg {
+ const char *name;
+ u32 addr;
+};
+
+struct adin1140_priv {
+ struct net_device *netdev;
+ struct oa_tc6 *tc6;
+ struct mii_bus *mdiobus;
+ struct work_struct rx_mode_work;
+ struct delayed_work stats_work;
+ /* Protect the stats array from concurrent accesses from
+ * adin1140_stats_work, adin1140_ndo_get_stats64
+ * and adin1140_get_ethtool_stats
+ */
+ spinlock_t stat_lock;
+
+ u64 stats[ADIN1140_STATS_CNT];
+};
+
+enum adin1140_statistics_entry {
+ rx_frames,
+ rx_broadcast_frames,
+ rx_multicast_frames,
+ rx_unicast_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_broadcast_frames,
+ tx_multicast_frames,
+ tx_unicast_frames,
+ tx_single_collision,
+ tx_multi_collision,
+ tx_deferred,
+ tx_late_collision,
+ tx_excess_collision,
+ tx_underrun,
+};
+
+static const struct adin1140_statistics_reg adin1140_stats[] = {
+ {.name = "rx_frames", .addr = ADIN1140_RX_FRAME_CNT},
+ {.name = "rx_broadcast_frames", .addr = ADIN1140_RX_BC_FRAME_CNT},
+ {.name = "rx_multicast_frames", .addr = ADIN1140_RX_MC_FRAME_CNT},
+ {.name = "rx_unicast_frames", .addr = ADIN1140_RX_UC_FRAME_CNT},
+ {.name = "rx_crc_errors", .addr = ADIN1140_RX_CRC_ERR_CNT},
+ {.name = "rx_align_errors", .addr = ADIN1140_RX_ALIGN_ERR_CNT},
+ {.name = "rx_preamble_errors", .addr = ADIN1140_RX_PREAMBLE_ERR_CNT},
+ {.name = "rx_short_frame_errors", .addr = ADIN1140_RX_SHORT_ERR_CNT},
+ {.name = "rx_long_frame_errors", .addr = ADIN1140_RX_LONG_ERR_CNT},
+ {.name = "rx_phy_errors", .addr = ADIN1140_RX_PHY_ERR_CNT},
+ {.name = "rx_fifo_full_dropped", .addr = ADIN1140_RX_DRP_FULL_CNT},
+ {.name = "rx_addr_filt_dropped", .addr = ADIN1140_RX_DRP_FILTER_CNT},
+ {.name = "rx_ifg_errors", .addr = ADIN1140_RX_IFG_ERR_CNT},
+ {.name = "tx_frames", .addr = ADIN1140_TX_FRAME_CNT},
+ {.name = "tx_broadcast_frames", .addr = ADIN1140_TX_BC_FRAME_CNT},
+ {.name = "tx_multicast_frames", .addr = ADIN1140_TX_MC_FRAME_CNT},
+ {.name = "tx_unicast_frames", .addr = ADIN1140_TX_UC_FRAME_CNT},
+ {.name = "tx_single_collision", .addr = ADIN1140_TX_SINGLE_COL_CNT},
+ {.name = "tx_multi_collision", .addr = ADIN1140_TX_MULTI_COL_CNT},
+ {.name = "tx_deferred", .addr = ADIN1140_TX_DEFERRED_CNT},
+ {.name = "tx_late_collision", .addr = ADIN1140_TX_LATE_COL_CNT},
+ {.name = "tx_excess_collision", .addr = ADIN1140_TX_EXCESS_COL_CNT},
+ {.name = "tx_underrun", .addr = ADIN1140_TX_UNDERRUN_CNT},
+};
+
+static int adin1140_mac_filter_set(struct adin1140_priv *priv,
+ const u8 *addr, const u8 *mask,
+ u8 slot)
+{
+ u32 mask_reg;
+ u32 val;
+ int ret;
+
+ if (slot >= ADIN1140_MAC_FILT_MAX_SLOT)
+ return -ENOSPC;
+
+ ret = oa_tc6_write_register(priv->tc6,
+ ADIN1140_MAC_ADDR_FILT_UPR_REG + 2 * slot,
+ get_unaligned_be16(&addr[0]) |
+ ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 |
+ ADIN1140_MAC_ADDR_FILT_TO_HOST);
+ if (ret)
+ return ret;
+
+ ret = oa_tc6_write_register(priv->tc6,
+ ADIN1140_MAC_ADDR_FILT_LWR_REG + 2 * slot,
+ get_unaligned_be32(&addr[2]));
+ if (ret)
+ return ret;
+
+ val = get_unaligned_be16(&mask[0]);
+ mask_reg = ADIN1140_MAC_ADDR_MASK_UPR_REG + (2 * slot);
+
+ ret = oa_tc6_write_register(priv->tc6, mask_reg, val);
+ if (ret)
+ return ret;
+
+ val = get_unaligned_be32(&mask[2]);
+ mask_reg = ADIN1140_MAC_ADDR_MASK_LWR_REG + (2 * slot);
+
+ return oa_tc6_write_register(priv->tc6, mask_reg, 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)
+{
+ u8 mask[ETH_ALEN];
+
+ memset(mask, 0xFF, ETH_ALEN);
+
+ return adin1140_mac_filter_set(priv, priv->netdev->dev_addr, mask,
+ 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, ADIN1140_CONFIG2_REG, &val);
+ if (ret)
+ return ret;
+
+ if (enabled)
+ val |= ADIN1140_CONFIG2_FWD_UNK2HOST;
+ else
+ val &= ~ADIN1140_CONFIG2_FWD_UNK2HOST;
+
+ return oa_tc6_write_register(priv->tc6, ADIN1140_CONFIG2_REG, val);
+}
+
+static void adin1140_rx_mode_work(struct work_struct *work)
+{
+ struct adin1140_priv *priv = container_of(work, struct adin1140_priv,
+ rx_mode_work);
+ struct netdev_hw_addr *ha;
+ bool all_multi, promisc;
+ u8 mask[ETH_ALEN];
+ u8 start, end;
+ 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_uc_count(priv->netdev) +
+ netdev_mc_count(priv->netdev);
+
+ if (priv->netdev->flags & IFF_PROMISC) {
+ promisc = true;
+ all_multi = false;
+ } else if (priv->netdev->flags & IFF_ALLMULTI) {
+ promisc = false;
+ all_multi = true;
+ } else if (mac_addrs <= ADIN1140_MAC_FILT_AVAIL) {
+ promisc = false;
+ all_multi = false;
+
+ slot = ADIN1140_MAC_FILT_UC_SLOT + 1;
+ memset(mask, 0xFF, ETH_ALEN);
+
+ netdev_for_each_uc_addr(ha, priv->netdev) {
+ ret = adin1140_mac_filter_set(priv, ha->addr, mask,
+ slot);
+ if (ret)
+ return;
+
+ slot++;
+ }
+
+ netdev_for_each_mc_addr(ha, priv->netdev) {
+ ret = adin1140_mac_filter_set(priv, ha->addr, mask,
+ slot);
+ if (ret)
+ return;
+
+ slot++;
+ }
+ } else {
+ /* The filter table is full. Enable promisc mode. */
+ promisc = true;
+ all_multi = false;
+
+ start = ADIN1140_MAC_FILT_UC_SLOT + 1;
+ end = ADIN1140_MAC_FILT_MAX_SLOT;
+ for (i = start; i < end; i++) {
+ ret = adin1140_mac_filter_clear(priv, i);
+ if (ret)
+ return;
+ }
+ }
+
+ ret = adin1140_promiscuous_mode(priv, promisc);
+ if (ret)
+ return;
+
+ adin1140_filter_all_multicast(priv, all_multi);
+}
+
+static void adin1140_rx_mode(struct net_device *netdev)
+{
+ struct adin1140_priv *priv = netdev_priv(netdev);
+
+ schedule_work(&priv->rx_mode_work);
+}
+
+static void adin1140_stats_work(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ u64 stat_buff[ADIN1140_STATS_CNT] = {};
+ 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_stats); i++) {
+ ret = oa_tc6_read_register(priv->tc6, adin1140_stats[i].addr,
+ ®_val);
+ if (ret)
+ break;
+
+ stat_buff[i] = reg_val;
+ }
+
+ spin_lock(&priv->stat_lock);
+ memcpy(&priv->stats, stat_buff, sizeof(priv->stats));
+ spin_unlock(&priv->stat_lock);
+
+ schedule_delayed_work(dwork, ADIN1140_STATS_CHECK_DELAY);
+}
+
+static int adin1140_configure(struct adin1140_priv *priv)
+{
+ u32 val;
+ int ret;
+
+ ret = oa_tc6_zero_align_receive_frame_enable(priv->tc6);
+ if (ret)
+ return ret;
+
+ ret = oa_tc6_read_register(priv->tc6, ADIN1140_CONFIG0_REG, &val);
+ if (ret)
+ return ret;
+
+ /* Zero-Align Receive Frame Enable */
+ val |= ADIN1140_CONFIG0_RFA_ZARFE;
+
+ /* Transmit Frame Check Sequence Validation must be disabled
+ * to allow CRC appending by MAC (CONFIG2.CRC_APPEND)
+ */
+ val &= ~ADIN1140_CONFIG0_TXFCSVE;
+ val |= ADIN1140_CONFIG0_CPS_64;
+
+ ret = oa_tc6_write_register(priv->tc6, ADIN1140_CONFIG0_REG, val);
+ if (ret)
+ return ret;
+
+ /* Disable MAC loopback */
+ ret = oa_tc6_write_register(priv->tc6, ADIN1140_MAC_P1_LOOP_ADDR_REG,
+ 0x0);
+ 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);
+
+ /* Pad frames to minimum Ethernet frame size (60 bytes without FCS).
+ * The MAC will append the FCS, but we need to ensure the frame is
+ * at least ETH_ZLEN bytes.
+ */
+ if (skb_put_padto(skb, ETH_ZLEN))
+ return NETDEV_TX_OK;
+
+ return oa_tc6_start_xmit(priv->tc6, skb);
+}
+
+static int adin1140_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
+{
+ if (!netif_running(netdev))
+ return -EINVAL;
+
+ return phy_do_ioctl(netdev, rq, cmd);
+}
+
+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 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;
+
+ spin_lock(&priv->stat_lock);
+
+ 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_multicast_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];
+
+ spin_unlock(&priv->stat_lock);
+}
+
+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);
+
+ spin_lock(&priv->stat_lock);
+ memcpy(data, &priv->stats, sizeof(u64) * ARRAY_SIZE(adin1140_stats));
+ spin_unlock(&priv->stat_lock);
+}
+
+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 int adin1140_get_phy_c45_mms(int devnum)
+{
+ switch (devnum) {
+ case MDIO_MMD_PCS:
+ return ADIN1140_PHY_C45_PCS_MMS2;
+ case MDIO_MMD_PMAPMD:
+ return ADIN1140_PHY_C45_PMA_PMD_MMS3;
+ case MDIO_MMD_VEND2:
+ return ADIN1140_PHY_C45_VS_PLCA_MMS4;
+ default:
+ return devnum;
+ }
+}
+
+static int adin1140_mdiobus_read_c45(struct mii_bus *bus, int addr,
+ int devnum, int regnum)
+{
+ struct oa_tc6 *tc6 = bus->priv;
+ u32 regval;
+ u32 mms;
+ int ret;
+
+ mms = adin1140_get_phy_c45_mms(devnum);
+ ret = oa_tc6_read_register(tc6, ADIN1140_MMS_REG(mms, regnum),
+ ®val);
+ if (ret)
+ return ret;
+
+ return regval;
+}
+
+static int adin1140_mdiobus_write_c45(struct mii_bus *bus, int addr,
+ int devnum, int regnum, u16 val)
+{
+ struct oa_tc6 *tc6 = bus->priv;
+ int ret;
+
+ ret = adin1140_get_phy_c45_mms(devnum);
+ if (ret < 0)
+ return ret;
+
+ return oa_tc6_write_register(tc6, ADIN1140_MMS_REG(ret, regnum), val);
+}
+
+static int adin1140_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
+{
+ struct oa_tc6 *tc6 = bus->priv;
+ u32 reg_val;
+ int ret;
+
+ /* 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. Since the MAC and PHY are integrated in the same device, we
+ * can read the OA TC6 MACPHY ID register instead of the PHYID (0x2
+ * and 0x3) ones, as their value matches. 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:
+ ret = oa_tc6_read_register(tc6, ADIN1140_MACPHY_ID_REG,
+ ®_val);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(GENMASK(31, 16), reg_val);
+ case MII_PHYSID2:
+ ret = oa_tc6_read_register(tc6, ADIN1140_MACPHY_ID_REG,
+ ®_val);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(GENMASK(15, 0), reg_val);
+ default:
+ return 0xFFFF;
+ }
+}
+
+static int adin1140_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
+ u16 val)
+{
+ return 0;
+}
+
+static int adin1140_mdio_register(struct adin1140_priv *priv)
+{
+ priv->mdiobus = mdiobus_alloc();
+ if (!priv->mdiobus) {
+ netdev_err(priv->netdev, "MDIO bus alloc failed\n");
+ return -ENOMEM;
+ }
+
+ priv->mdiobus->read = adin1140_mdiobus_read;
+ priv->mdiobus->write = adin1140_mdiobus_write;
+ priv->mdiobus->read_c45 = adin1140_mdiobus_read_c45;
+ priv->mdiobus->write_c45 = adin1140_mdiobus_write_c45;
+
+ 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,
+};
+
+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 = adin1140_rx_mode,
+ .ndo_eth_ioctl = adin1140_ioctl,
+ .ndo_get_stats64 = adin1140_ndo_get_stats64,
+};
+
+static int adin1140_probe(struct spi_device *spi)
+{
+ struct oa_tc6_config tc6_config = {};
+ struct net_device *netdev;
+ struct adin1140_priv *priv;
+ int ret;
+
+ netdev = alloc_etherdev(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);
+
+ ret = adin1140_mdio_register(priv);
+ if (ret)
+ goto netdev_free;
+
+ tc6_config.spi = spi;
+ tc6_config.netdev = netdev;
+ tc6_config.mii_bus = priv->mdiobus;
+
+ priv->tc6 = oa_tc6_init(&tc6_config);
+ if (!priv->tc6) {
+ ret = -ENODEV;
+ goto mdio_free;
+ }
+
+ if (device_get_ethdev_address(&spi->dev, netdev))
+ eth_hw_addr_random(netdev);
+
+ ret = adin1140_configure(priv);
+ if (ret)
+ goto oa_tc6_exit;
+
+ INIT_WORK(&priv->rx_mode_work, adin1140_rx_mode_work);
+ 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 = register_netdev(netdev);
+ if (ret) {
+ dev_err(&spi->dev, "Failed to register netdev (%d)", ret);
+ goto oa_tc6_exit;
+ }
+
+ return 0;
+
+oa_tc6_exit:
+ oa_tc6_exit(priv->tc6);
+mdio_free:
+ mdiobus_free(priv->mdiobus);
+netdev_free:
+ free_netdev(priv->netdev);
+
+ return ret;
+}
+
+static void adin1140_remove(struct spi_device *spi)
+{
+ struct adin1140_priv *priv = spi_get_drvdata(spi);
+
+ cancel_work_sync(&priv->rx_mode_work);
+ unregister_netdev(priv->netdev);
+ oa_tc6_exit(priv->tc6);
+ mdiobus_free(priv->mdiobus);
+ free_netdev(priv->netdev);
+}
+
+static const struct spi_device_id adin1140_spi_id[] = {
+ { .name = "adin1140" },
+ { .name = "ad3306" },
+ {},
+};
+MODULE_DEVICE_TABLE(spi, adin1140_spi_id);
+
+static const struct of_device_id adin1140_match_table[] = {
+ { .compatible = "adi,adin1140" },
+ { .compatible = "adi,ad3306" },
+ { }
+};
+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,
+ .remove = adin1140_remove,
+ .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");
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140
2026-05-02 23:24 [PATCH net-next 0/5] net: Add ADIN1140 support Ciprian Regus via B4 Relay
` (3 preceding siblings ...)
2026-05-02 23:24 ` [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY Ciprian Regus via B4 Relay
@ 2026-05-02 23:24 ` Ciprian Regus via B4 Relay
2026-05-03 1:06 ` Andrew Lunn
2026-05-06 8:20 ` Krzysztof Kozlowski
4 siblings, 2 replies; 21+ messages in thread
From: Ciprian Regus via B4 Relay @ 2026-05-02 23:24 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: netdev, linux-kernel, linux-doc, devicetree, Ciprian Regus
From: Ciprian Regus <ciprian.regus@analog.com>
Add DT bindings for the ADIN1140 10BASE-T1S MACPHY. Update the
MAINTAINERS entry to include the bindings file as well.
Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
---
.../devicetree/bindings/net/adi,adin1140.yaml | 69 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 70 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/adi,adin1140.yaml b/Documentation/devicetree/bindings/net/adi,adin1140.yaml
new file mode 100644
index 000000000000..26cd40d36f9b
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/adi,adin1140.yaml
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/adi,adin1140.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 implements 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:
+ enum:
+ - adi,adin1140
+ - 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,adin1140";
+ reg = <0>;
+ spi-max-frequency = <23000000>;
+
+ interrupt-parent = <&gpio>;
+ interrupts = <6 IRQ_TYPE_EDGE_FALLING>;
+
+ local-mac-address = [ 00 11 22 33 44 55 ];
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index f9784c25beac..55e1e78fe04e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1848,6 +1848,7 @@ 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,adin1140.yaml
F: drivers/net/ethernet/adi/adin1140.c
ANALOG DEVICES INC ETHERNET PHY DRIVERS
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 3/5] net: phy: Add support for the ADIN1140 PHY
2026-05-02 23:24 ` [PATCH net-next 3/5] net: phy: Add support for the ADIN1140 PHY Ciprian Regus via B4 Relay
@ 2026-05-03 0:40 ` Andrew Lunn
0 siblings, 0 replies; 21+ messages in thread
From: Andrew Lunn @ 2026-05-03 0:40 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
> +static int adin1140_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 adin1140_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);
> +}
Why do these exist?
> +static int adin1140_config_init(struct phy_device *phydev)
> +{
> + /* The link status of the PHY doesn't need to be polled, because
> + * the device doesn't implement AN and there is no other mechanism
> + * to report the link state.
> + */
> + phydev->irq = PHY_MAC_INTERRUPT;
I would prefer you don't abuse this.
> +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;
> +}
This should have no really cost, so just let phylib poll.
Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
2026-05-02 23:24 ` [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY Ciprian Regus via B4 Relay
@ 2026-05-03 0:59 ` Andrew Lunn
2026-05-03 1:01 ` Andrew Lunn
` (2 subsequent siblings)
3 siblings, 0 replies; 21+ messages in thread
From: Andrew Lunn @ 2026-05-03 0:59 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
> +static int adin1140_get_phy_c45_mms(int devnum)
> +{
> + switch (devnum) {
> + case MDIO_MMD_PCS:
> + return ADIN1140_PHY_C45_PCS_MMS2;
> + case MDIO_MMD_PMAPMD:
> + return ADIN1140_PHY_C45_PMA_PMD_MMS3;
> + case MDIO_MMD_VEND2:
> + return ADIN1140_PHY_C45_VS_PLCA_MMS4;
> + default:
> + return devnum;
> + }
> +}
> +
> +static int adin1140_mdiobus_read_c45(struct mii_bus *bus, int addr,
> + int devnum, int regnum)
> +{
> + struct oa_tc6 *tc6 = bus->priv;
> + u32 regval;
> + u32 mms;
> + int ret;
> +
> + mms = adin1140_get_phy_c45_mms(devnum);
> + ret = oa_tc6_read_register(tc6, ADIN1140_MMS_REG(mms, regnum),
> + ®val);
> + if (ret)
> + return ret;
> +
> + return regval;
> +}
> +
> +static int adin1140_mdiobus_write_c45(struct mii_bus *bus, int addr,
> + int devnum, int regnum, u16 val)
> +{
> + struct oa_tc6 *tc6 = bus->priv;
> + int ret;
> +
> + ret = adin1140_get_phy_c45_mms(devnum);
> + if (ret < 0)
> + return ret;
> +
> + return oa_tc6_write_register(tc6, ADIN1140_MMS_REG(ret, regnum), val);
> +}
At a quick look, these seem the same as oa_tc6_mdiobus_read_c45() and
oa_tc6_mdiobus_write_c45(). Please export them and use them.
> +static int adin1140_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
> +{
> + struct oa_tc6 *tc6 = bus->priv;
> + u32 reg_val;
> + int ret;
> +
> + /* 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. Since the MAC and PHY are integrated in the same device, we
> + * can read the OA TC6 MACPHY ID register instead of the PHYID (0x2
> + * and 0x3) ones, as their value matches. 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:
> + ret = oa_tc6_read_register(tc6, ADIN1140_MACPHY_ID_REG,
> + ®_val);
> + if (ret)
> + return ret;
> +
> + return FIELD_GET(GENMASK(31, 16), reg_val);
> + case MII_PHYSID2:
> + ret = oa_tc6_read_register(tc6, ADIN1140_MACPHY_ID_REG,
> + ®_val);
> + if (ret)
> + return ret;
Is it even worth reading this register? Why not hard code this as
well? Or do you expect a new version of the device which is less
FUBAR, and having a different PHY ID?
> +static int adin1140_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
> + u16 val)
> +{
> + return 0;
-EIO. Since writes are not support, you want to know if something
actually does a write.
> +static int adin1140_mdio_register(struct adin1140_priv *priv)
> +{
> + priv->mdiobus = mdiobus_alloc();
> + if (!priv->mdiobus) {
> + netdev_err(priv->netdev, "MDIO bus alloc failed\n");
> + return -ENOMEM;
> + }
> +
> + priv->mdiobus->read = adin1140_mdiobus_read;
> + priv->mdiobus->write = adin1140_mdiobus_write;
> + priv->mdiobus->read_c45 = adin1140_mdiobus_read_c45;
> + priv->mdiobus->write_c45 = adin1140_mdiobus_write_c45;
Name? id?
Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
2026-05-02 23:24 ` [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY Ciprian Regus via B4 Relay
2026-05-03 0:59 ` Andrew Lunn
@ 2026-05-03 1:01 ` Andrew Lunn
2026-05-03 3:15 ` Andrew Lunn
2026-05-03 3:36 ` Andrew Lunn
3 siblings, 0 replies; 21+ messages in thread
From: Andrew Lunn @ 2026-05-03 1:01 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
> +static int adin1140_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
> +{
> + if (!netif_running(netdev))
> + return -EINVAL;
> +
> + return phy_do_ioctl(netdev, rq, cmd);
> +}
phy_do_ioctl_running()
Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140
2026-05-02 23:24 ` [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140 Ciprian Regus via B4 Relay
@ 2026-05-03 1:06 ` Andrew Lunn
2026-05-04 7:33 ` Regus, Ciprian
2026-05-06 8:20 ` Krzysztof Kozlowski
1 sibling, 1 reply; 21+ messages in thread
From: Andrew Lunn @ 2026-05-03 1:06 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
> + 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 implements the Open Alliance TC6 10BASE-T1x MAC-PHY
The device _tries_ to implements the Open Alliance TC6 10BASE-T1x MAC-PHY.
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + ethernet@0 {
> + compatible = "adi,adin1140";
> + reg = <0>;
> + spi-max-frequency = <23000000>;
> +
> + interrupt-parent = <&gpio>;
> + interrupts = <6 IRQ_TYPE_EDGE_FALLING>;
Table 1: OPEN serial 10BASE-T1x Interface Pin Definition
IRQn MAC-PHY Interrupt Request (Active Low)
Or is this something else which the device gets wrong?
Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
2026-05-02 23:24 ` [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY Ciprian Regus via B4 Relay
2026-05-03 0:59 ` Andrew Lunn
2026-05-03 1:01 ` Andrew Lunn
@ 2026-05-03 3:15 ` Andrew Lunn
2026-05-03 3:36 ` Andrew Lunn
3 siblings, 0 replies; 21+ messages in thread
From: Andrew Lunn @ 2026-05-03 3:15 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
> +enum adin1140_statistics_entry {
> + rx_frames,
> + rx_broadcast_frames,
> + rx_multicast_frames,
> + rx_unicast_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_broadcast_frames,
> + tx_multicast_frames,
> + tx_unicast_frames,
> + tx_single_collision,
> + tx_multi_collision,
> + tx_deferred,
> + tx_late_collision,
> + tx_excess_collision,
> + tx_underrun,
> +};
Many of these seem to be ethtool_eth_mac_stats. Please use that to
report the. You should only use the free form strings/values for none
standard statistics.
Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
2026-05-02 23:24 ` [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY Ciprian Regus via B4 Relay
` (2 preceding siblings ...)
2026-05-03 3:15 ` Andrew Lunn
@ 2026-05-03 3:36 ` Andrew Lunn
2026-05-03 15:15 ` Andrew Lunn
3 siblings, 1 reply; 21+ messages in thread
From: Andrew Lunn @ 2026-05-03 3:36 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
On Sun, May 03, 2026 at 02:24:53AM +0300, Ciprian Regus via B4 Relay wrote:
> From: Ciprian Regus <ciprian.regus@analog.com>
>
> Add a driver for ADIN1140. The device is a 10BASE-T1S MAC-PHY
> (integrated in the same package) that connects to a CPU over an SPI bus,
> and implements the Open Alliance TC6 protocol for control and frame
> transfers. As such, this driver relies on oa_tc6 for the communication
> with the device. The device has an alternative name (AD3306), so the
> driver can be probed using one of the two compatible strings.
>
> For control transactions, ADIN1140 only implements the protected mode.
> The driver has 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).
> For the fields that are R/W (loopback and AN/reset) in the control
> register, the PHY driver already implements the set_loopback() and
> config_aneg() functions. The C22 write function of the driver is a
> no-op and is used to protect against the ioctl MDIO access path.
> C45 accesses do not cause this issue, so we can properly implement
> them.
>
> Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
> ---
> MAINTAINERS | 7 +
> drivers/net/ethernet/adi/Kconfig | 12 +
> drivers/net/ethernet/adi/Makefile | 1 +
> drivers/net/ethernet/adi/adin1140.c | 805 ++++++++++++++++++++++++++++++++++++
> 4 files changed, 825 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1e58da5ef47a..f9784c25beac 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1843,6 +1843,13 @@ 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: drivers/net/ethernet/adi/adin1140.c
> +
> ANALOG DEVICES INC ETHERNET PHY DRIVERS
> M: Ciprian Regus <ciprian.regus@analog.com>
> L: netdev@vger.kernel.org
> diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kconfig
> index 760a9a60bc15..bdb8ff7d15da 100644
> --- a/drivers/net/ethernet/adi/Kconfig
> +++ b/drivers/net/ethernet/adi/Kconfig
> @@ -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
> diff --git a/drivers/net/ethernet/adi/Makefile b/drivers/net/ethernet/adi/Makefile
> index d0383d94303c..0390ca8ccc49 100644
> --- a/drivers/net/ethernet/adi/Makefile
> +++ b/drivers/net/ethernet/adi/Makefile
> @@ -4,3 +4,4 @@
> #
>
> obj-$(CONFIG_ADIN1110) += adin1110.o
> +obj-$(CONFIG_ADIN1140) += adin1140.o
> diff --git a/drivers/net/ethernet/adi/adin1140.c b/drivers/net/ethernet/adi/adin1140.c
> new file mode 100644
> index 000000000000..5bc3f5732ed8
> --- /dev/null
> +++ b/drivers/net/ethernet/adi/adin1140.c
> @@ -0,0 +1,805 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY
> + *
> + * Copyright 2026 Analog Devices Inc.
> + */
> +
> +#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_MMS_REG(m, r) ((((m) & GENMASK(3, 0)) << 16) | \
> + ((r) & GENMASK(15, 0)))
> +
> +#define ADIN1140_MACPHY_ID_REG ADIN1140_MMS_REG(0x0, 0x1)
This is not an ADIN1140 MACPHY_ID_REG, it is the TC6 PHYID register.
> +
> +#define ADIN1140_CONFIG0_REG 0x0004
> +#define ADIN1140_CONFIG0_TXFCSVE BIT(14)
> +#define ADIN1140_CONFIG0_RFA_ZARFE BIT(12)
> +#define ADIN1140_CONFIG0_CPS_64 GENMASK(2, 1)
> +
> +#define ADIN1140_CONFIG2_REG ADIN1140_MMS_REG(0x0, 0x6)
This is not an ADIN1140 CONFIG2 register. It is the TC6 CONFIG2
register.
Please add the TC6 registers to include/linux/oa_tc6.
Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus
2026-05-02 23:24 ` [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus Ciprian Regus via B4 Relay
@ 2026-05-03 3:50 ` Andrew Lunn
2026-05-03 17:34 ` Selvamani Rajagopal
0 siblings, 1 reply; 21+ messages in thread
From: Andrew Lunn @ 2026-05-03 3:50 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
> @@ -538,32 +539,37 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
> {
> int ret;
>
> - tc6->mdiobus = mdiobus_alloc();
> if (!tc6->mdiobus) {
> - netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
> - return -ENOMEM;
> + tc6->mdiobus = mdiobus_alloc();
> + if (!tc6->mdiobus) {
> + netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
> + return -ENOMEM;
> + }
> +
> + tc6->mdiobus->read = oa_tc6_mdiobus_read;
> + tc6->mdiobus->write = oa_tc6_mdiobus_write;
> + /* 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, PHY
> + * drivers can set .read_mmd/.write_mmd in the PHY driver to call
> + * .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
> + */
> + tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
> + tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
> +
> + tc6->own_mdiobus = true;
> }
>
> tc6->mdiobus->priv = tc6;
> - tc6->mdiobus->read = oa_tc6_mdiobus_read;
> - tc6->mdiobus->write = oa_tc6_mdiobus_write;
> - /* 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, PHY
> - * drivers can set .read_mmd/.write_mmd in the PHY driver to call
> - * .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
> - */
> - tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
> - tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
> - tc6->mdiobus->name = "oa-tc6-mdiobus";
> tc6->mdiobus->parent = tc6->dev;
> + tc6->mdiobus->name = "oa-tc6-mdiobus";
>
> snprintf(tc6->mdiobus->id, ARRAY_SIZE(tc6->mdiobus->id), "%s",
> - dev_name(&tc6->spi->dev));
> + dev_name(&tc6->spi->dev));
>
> ret = mdiobus_register(tc6->mdiobus);
> if (ret) {
> @@ -577,19 +583,30 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
>
> static void oa_tc6_mdiobus_unregister(struct oa_tc6 *tc6)
> {
> + if (!tc6->mdiobus)
> + return;
> +
> mdiobus_unregister(tc6->mdiobus);
> - mdiobus_free(tc6->mdiobus);
> +
> + if (tc6->own_mdiobus)
> + mdiobus_free(tc6->mdiobus);
> }
>
> static int oa_tc6_phy_init(struct oa_tc6 *tc6)
> {
> int ret;
>
> - ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
> - if (ret) {
> - netdev_err(tc6->netdev,
> - "Direct PHY register access is not supported by the MAC-PHY\n");
> - return ret;
> + /* If the driver provided a mii_bus, it is also responsible for
> + * implementing the bus access methods, so we don't have to worry
> + * about checking the PHY access mode.
> + */
> + if (!tc6->mdiobus) {
> + ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
> + if (ret) {
> + netdev_err(tc6->netdev,
> + "Direct PHY register access is not supported by the MAC-PHY\n");
> + return ret;
> + }
This all seems pretty invasive and ugly. Please could you think what
happens if instead of passing in an mdiobus, you pass a phydev. Is the
change to the core simpler and cleaner?
Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
2026-05-03 3:36 ` Andrew Lunn
@ 2026-05-03 15:15 ` Andrew Lunn
2026-05-03 18:19 ` Regus, Ciprian
0 siblings, 1 reply; 21+ messages in thread
From: Andrew Lunn @ 2026-05-03 15:15 UTC (permalink / raw)
To: ciprian.regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, linux-kernel,
linux-doc, devicetree
On Sun, May 03, 2026 at 05:36:13AM +0200, Andrew Lunn wrote:
> On Sun, May 03, 2026 at 02:24:53AM +0300, Ciprian Regus via B4 Relay wrote:
> > From: Ciprian Regus <ciprian.regus@analog.com>
> >
> > Add a driver for ADIN1140. The device is a 10BASE-T1S MAC-PHY
> > (integrated in the same package) that connects to a CPU over an SPI bus,
> > and implements the Open Alliance TC6 protocol for control and frame
> > transfers. As such, this driver relies on oa_tc6 for the communication
> > with the device. The device has an alternative name (AD3306), so the
> > driver can be probed using one of the two compatible strings.
> >
> > For control transactions, ADIN1140 only implements the protected mode.
> > The driver has 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.
This device is pretty broken. Has it been shipped to customers? Is
there going to be a new stepping of the silicon which is less broken?
A new device to replace this one?
Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* RE: [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus
2026-05-03 3:50 ` Andrew Lunn
@ 2026-05-03 17:34 ` Selvamani Rajagopal
2026-05-03 18:06 ` Andrew Lunn
0 siblings, 1 reply; 21+ messages in thread
From: Selvamani Rajagopal @ 2026-05-03 17:34 UTC (permalink / raw)
To: Andrew Lunn, ciprian.regus@analog.com
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org,
devicetree@vger.kernel.org
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Saturday, May 2, 2026 8:51 PM
> To: ciprian.regus@analog.com
> Cc: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>; Andrew Lunn
> <andrew+netdev@lunn.ch>; David S. Miller <davem@davemloft.net>; Eric Dumazet
> <edumazet@google.com>; Jakub Kicinski <kuba@kernel.org>; Paolo Abeni
> <pabeni@redhat.com>; Simon Horman <horms@kernel.org>; Jonathan Corbet
> <corbet@lwn.net>; Shuah Khan <skhan@linuxfoundation.org>; Heiner Kallweit
> <hkallweit1@gmail.com>; Russell King <linux@armlinux.org.uk>; Rob Herring
> <robh@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley
> <conor+dt@kernel.org>; netdev@vger.kernel.org; linux-kernel@vger.kernel.org; linux-
> doc@vger.kernel.org; devicetree@vger.kernel.org
> Subject: Re: [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus
>
>
> This Message Is From an External Sender
> This message came from outside your organization.
>
> > @@ -538,32 +539,37 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
> > {
> > int ret;
> >
> > - tc6->mdiobus = mdiobus_alloc();
> > if (!tc6->mdiobus) {
> > - netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
> > - return -ENOMEM;
> > + tc6->mdiobus = mdiobus_alloc();
> > + if (!tc6->mdiobus) {
> > + netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
> > + return -ENOMEM;
> > + }
> > +
> > + tc6->mdiobus->read = oa_tc6_mdiobus_read;
> > + tc6->mdiobus->write = oa_tc6_mdiobus_write;
> > + /* 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, PHY
> > + * drivers can set .read_mmd/.write_mmd in the PHY driver to call
> > + * .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
> > + */
> > + tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
> > + tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
> > +
> > + tc6->own_mdiobus = true;
> > }
> >
> > tc6->mdiobus->priv = tc6;
> > - tc6->mdiobus->read = oa_tc6_mdiobus_read;
> > - tc6->mdiobus->write = oa_tc6_mdiobus_write;
> > - /* 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, PHY
> > - * drivers can set .read_mmd/.write_mmd in the PHY driver to call
> > - * .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
> > - */
> > - tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
> > - tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
> > - tc6->mdiobus->name = "oa-tc6-mdiobus";
> > tc6->mdiobus->parent = tc6->dev;
> > + tc6->mdiobus->name = "oa-tc6-mdiobus";
> >
> > snprintf(tc6->mdiobus->id, ARRAY_SIZE(tc6->mdiobus->id), "%s",
> > - dev_name(&tc6->spi->dev));
> > + dev_name(&tc6->spi->dev));
> >
> > ret = mdiobus_register(tc6->mdiobus);
> > if (ret) {
> > @@ -577,19 +583,30 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
> >
> > static void oa_tc6_mdiobus_unregister(struct oa_tc6 *tc6)
> > {
> > + if (!tc6->mdiobus)
> > + return;
> > +
> > mdiobus_unregister(tc6->mdiobus);
> > - mdiobus_free(tc6->mdiobus);
> > +
> > + if (tc6->own_mdiobus)
> > + mdiobus_free(tc6->mdiobus);
> > }
> >
> > static int oa_tc6_phy_init(struct oa_tc6 *tc6)
> > {
> > int ret;
> >
> > - ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
> > - if (ret) {
> > - netdev_err(tc6->netdev,
> > - "Direct PHY register access is not supported by the MAC-PHY\n");
> > - return ret;
> > + /* If the driver provided a mii_bus, it is also responsible for
> > + * implementing the bus access methods, so we don't have to worry
> > + * about checking the PHY access mode.
> > + */
> > + if (!tc6->mdiobus) {
> > + ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
> > + if (ret) {
> > + netdev_err(tc6->netdev,
> > + "Direct PHY register access is not supported by the MAC-PHY\n");
> > + return ret;
> > + }
>
> This all seems pretty invasive and ugly. Please could you think what
> happens if instead of passing in an mdiobus, you pass a phydev. Is the
> change to the core simpler and cleaner?
>
> Andrew
Kind of agree. Initially we were thinking about changing the existing code (Microchip's vendor code) to alloc mii_bus so that code would be same across multiple vendors. Either way, it would be invasive changes. So, we decide to go with minimal change to other vendor's code.
Trying to understand your suggestion. Are you suggesting to move entire mii_bus allocation/APIs implementation to vendor side and keep only phy dev usage in oa_tc6.c?
If my understanding is correct, I guess it would be cleaner. I can try that. Let me know.
Selva
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus
2026-05-03 17:34 ` Selvamani Rajagopal
@ 2026-05-03 18:06 ` Andrew Lunn
2026-05-03 18:50 ` Selvamani Rajagopal
0 siblings, 1 reply; 21+ messages in thread
From: Andrew Lunn @ 2026-05-03 18:06 UTC (permalink / raw)
To: Selvamani Rajagopal
Cc: ciprian.regus@analog.com, Parthiban Veerasooran, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Heiner Kallweit,
Russell King, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-doc@vger.kernel.org, devicetree@vger.kernel.org
> > This all seems pretty invasive and ugly. Please could you think what
> > happens if instead of passing in an mdiobus, you pass a phydev. Is the
> > change to the core simpler and cleaner?
> >
> > Andrew
>
> Kind of agree. Initially we were thinking about changing the
> existing code (Microchip's vendor code) to alloc mii_bus so that
> code would be same across multiple vendors. Either way, it would be
> invasive changes. So, we decide to go with minimal change to other
> vendor's code.
That would be wrong. The standard defines this, so it should be in the
core. Anything which the standard defines should be in the core, so
that drivers for hardware which actually follow the standard are
minimal. Also, we try to keep workarounds for broken hardware out of
the core, hide it in the driver. That is not always possible, but the
aim should be to make the core clean. We don't want to penalise
vendors which got the implementation correct because of vendors who
got is wrong.
> Trying to understand your suggestion. Are you suggesting to move
> entire mii_bus allocation/APIs implementation to vendor side and
> keep only phy dev usage in oa_tc6.c?
No. I'm thinking maybe extend oa_tc6_init, similar to what you
did. Add a quirks flag, and define TC6_QUIRK_BROKEN_PHY. And allow a
phydev to be passed as well.
If the quirk is set, don't call oa_tc6_mdiobus_register() or
phy_find_first(), nor oa_tc6_mdiobus_unregister().
You probably want to start with a patch which breaks oa_tc6_phy_init()
into two, since you still need the phy_connect_direct() and
phy_attached_info(). Then add the quirk, and lastly your driver making
use of the quirk.
The quirks flag could also be used for devices which have MMD 30
mapped into a vendor reserved MMS.
Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* RE: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY
2026-05-03 15:15 ` Andrew Lunn
@ 2026-05-03 18:19 ` Regus, Ciprian
0 siblings, 0 replies; 21+ messages in thread
From: Regus, Ciprian @ 2026-05-03 18:19 UTC (permalink / raw)
To: Andrew Lunn
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org,
devicetree@vger.kernel.org
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Sunday, May 3, 2026 6:16 PM
> To: Regus, Ciprian <Ciprian.Regus@analog.com>
> Cc: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>;
> Andrew Lunn <andrew+netdev@lunn.ch>; David S. Miller
> <davem@davemloft.net>; Eric Dumazet <edumazet@google.com>; Jakub
> Kicinski <kuba@kernel.org>; Paolo Abeni <pabeni@redhat.com>; Simon
> Horman <horms@kernel.org>; Jonathan Corbet <corbet@lwn.net>; Shuah
> Khan <skhan@linuxfoundation.org>; Heiner Kallweit
> <hkallweit1@gmail.com>; Russell King <linux@armlinux.org.uk>; Rob Herring
> <robh@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor
> Dooley <conor+dt@kernel.org>; netdev@vger.kernel.org; linux-
> kernel@vger.kernel.org; linux-doc@vger.kernel.org;
> devicetree@vger.kernel.org
> Subject: Re: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the
> ADIN1140 MACPHY
>
> [External]
>
> On Sun, May 03, 2026 at 05:36:13AM +0200, Andrew Lunn wrote:
> > On Sun, May 03, 2026 at 02:24:53AM +0300, Ciprian Regus via B4 Relay
> wrote:
> > > From: Ciprian Regus <ciprian.regus@analog.com>
> > >
> > > Add a driver for ADIN1140. The device is a 10BASE-T1S MAC-PHY
> > > (integrated in the same package) that connects to a CPU over an SPI bus,
> > > and implements the Open Alliance TC6 protocol for control and frame
> > > transfers. As such, this driver relies on oa_tc6 for the communication
> > > with the device. The device has an alternative name (AD3306), so the
> > > driver can be probed using one of the two compatible strings.
> > >
> > > For control transactions, ADIN1140 only implements the protected mode.
> > > The driver has 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.
>
> This device is pretty broken. Has it been shipped to customers? Is
> there going to be a new stepping of the silicon which is less broken?
> A new device to replace this one?
Hello Andrew,
The device has been out for some time, but it's only now being made public.
There are customers using it at the moment. I'm not aware of future revisions
of the silicon, but I can ask further about that.
>
> Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* RE: [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus
2026-05-03 18:06 ` Andrew Lunn
@ 2026-05-03 18:50 ` Selvamani Rajagopal
0 siblings, 0 replies; 21+ messages in thread
From: Selvamani Rajagopal @ 2026-05-03 18:50 UTC (permalink / raw)
To: Andrew Lunn
Cc: ciprian.regus@analog.com, Parthiban Veerasooran, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Heiner Kallweit,
Russell King, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-doc@vger.kernel.org, devicetree@vger.kernel.org
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Sunday, May 3, 2026 11:07 AM
> To: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
> Cc: ciprian.regus@analog.com; Parthiban Veerasooran
> <parthiban.veerasooran@microchip.com>; Andrew Lunn <andrew+netdev@lunn.ch>;
> David S. Miller <davem@davemloft.net>; Eric Dumazet <edumazet@google.com>;
> Jakub Kicinski <kuba@kernel.org>; Paolo Abeni <pabeni@redhat.com>; Simon Horman
> <horms@kernel.org>; Jonathan Corbet <corbet@lwn.net>; Shuah Khan
> <skhan@linuxfoundation.org>; Heiner Kallweit <hkallweit1@gmail.com>; Russell King
> <linux@armlinux.org.uk>; Rob Herring <robh@kernel.org>; Krzysztof Kozlowski
> <krzk+dt@kernel.org>; Conor Dooley <conor+dt@kernel.org>; netdev@vger.kernel.org;
> linux-kernel@vger.kernel.org; linux-doc@vger.kernel.org; devicetree@vger.kernel.org
> Subject: Re: [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus
>
>
> This Message Is From an External Sender
> This message came from outside your organization.
>
> > > This all seems pretty invasive and ugly. Please could you think what
> > > happens if instead of passing in an mdiobus, you pass a phydev. Is the
> > > change to the core simpler and cleaner?
> > >
> > > Andrew
> >
>
> > Kind of agree. Initially we were thinking about changing the
> > existing code (Microchip's vendor code) to alloc mii_bus so that
> > code would be same across multiple vendors. Either way, it would be
> > invasive changes. So, we decide to go with minimal change to other
> > vendor's code.
>
> That would be wrong. The standard defines this, so it should be in the
> core. Anything which the standard defines should be in the core, so
> that drivers for hardware which actually follow the standard are
> minimal. Also, we try to keep workarounds for broken hardware out of
> the core, hide it in the driver. That is not always possible, but the
> aim should be to make the core clean. We don't want to penalise
> vendors which got the implementation correct because of vendors who
> got is wrong.
>
> > Trying to understand your suggestion. Are you suggesting to move
> > entire mii_bus allocation/APIs implementation to vendor side and
> > keep only phy dev usage in oa_tc6.c?
>
> No. I'm thinking maybe extend oa_tc6_init, similar to what you
> did. Add a quirks flag, and define TC6_QUIRK_BROKEN_PHY. And allow a
> phydev to be passed as well.
>
> If the quirk is set, don't call oa_tc6_mdiobus_register() or
> phy_find_first(), nor oa_tc6_mdiobus_unregister().
>
> You probably want to start with a patch which breaks oa_tc6_phy_init()
> into two, since you still need the phy_connect_direct() and
> phy_attached_info(). Then add the quirk, and lastly your driver making
> use of the quirk.
>
> The quirks flag could also be used for devices which have MMD 30
> mapped into a vendor reserved MMS.
>
> Andrew
Andrew,
I realized that I wrongly picked this thread to ask my question. This email thread wasn't ours. But I got your suggestion. Will try/test. Will discuss this in our thread.
Sincerely
Selva
^ permalink raw reply [flat|nested] 21+ messages in thread
* RE: [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140
2026-05-03 1:06 ` Andrew Lunn
@ 2026-05-04 7:33 ` Regus, Ciprian
2026-05-04 12:11 ` Andrew Lunn
0 siblings, 1 reply; 21+ messages in thread
From: Regus, Ciprian @ 2026-05-04 7:33 UTC (permalink / raw)
To: Andrew Lunn
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org,
devicetree@vger.kernel.org
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Sunday, May 3, 2026 4:07 AM
> To: Regus, Ciprian <Ciprian.Regus@analog.com>
> Cc: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>;
> Andrew Lunn <andrew+netdev@lunn.ch>; David S. Miller
> <davem@davemloft.net>; Eric Dumazet <edumazet@google.com>; Jakub
> Kicinski <kuba@kernel.org>; Paolo Abeni <pabeni@redhat.com>; Simon
> Horman <horms@kernel.org>; Jonathan Corbet <corbet@lwn.net>; Shuah
> Khan <skhan@linuxfoundation.org>; Heiner Kallweit
> <hkallweit1@gmail.com>; Russell King <linux@armlinux.org.uk>; Rob Herring
> <robh@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor
> Dooley <conor+dt@kernel.org>; netdev@vger.kernel.org; linux-
> kernel@vger.kernel.org; linux-doc@vger.kernel.org;
> devicetree@vger.kernel.org
> Subject: Re: [PATCH net-next 5/5] dt-bindings: net: Add bindings for the
> ADIN1140
>
> [External]
>
> > + 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 implements the Open Alliance TC6 10BASE-T1x MAC-PHY
>
> The device _tries_ to implements the Open Alliance TC6 10BASE-T1x MAC-
> PHY.
Will update in v2.
>
> > +examples:
> > + - |
> > + #include <dt-bindings/interrupt-controller/irq.h>
> > +
> > + spi {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + ethernet@0 {
> > + compatible = "adi,adin1140";
> > + reg = <0>;
> > + spi-max-frequency = <23000000>;
> > +
> > + interrupt-parent = <&gpio>;
> > + interrupts = <6 IRQ_TYPE_EDGE_FALLING>;
>
> Table 1: OPEN serial 10BASE-T1x Interface Pin Definition
>
> IRQn MAC-PHY Interrupt Request (Active Low)
>
> Or is this something else which the device gets wrong?
The device generates interrupts correctly (the IRQ signal remains
asserted while there are active interrupt conditions that have not
been cleared yet). The oa_tc6 driver requests the interrupt with
the IRQF_TRIGGER_FALLING flag set, so the DT flag will be overridden
and the behavior remains the same.
However, the devicetree shouldn't care about this. I'll update to
IRQ_TYPE_LEVEL_LOW in v2.
>
> Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140
2026-05-04 7:33 ` Regus, Ciprian
@ 2026-05-04 12:11 ` Andrew Lunn
0 siblings, 0 replies; 21+ messages in thread
From: Andrew Lunn @ 2026-05-04 12:11 UTC (permalink / raw)
To: Regus, Ciprian
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Heiner Kallweit, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org,
devicetree@vger.kernel.org
> > > + ethernet@0 {
> > > + compatible = "adi,adin1140";
> > > + reg = <0>;
> > > + spi-max-frequency = <23000000>;
> > > +
> > > + interrupt-parent = <&gpio>;
> > > + interrupts = <6 IRQ_TYPE_EDGE_FALLING>;
> >
> > Table 1: OPEN serial 10BASE-T1x Interface Pin Definition
> >
> > IRQn MAC-PHY Interrupt Request (Active Low)
> >
> > Or is this something else which the device gets wrong?
>
> The device generates interrupts correctly (the IRQ signal remains
> asserted while there are active interrupt conditions that have not
> been cleared yet). The oa_tc6 driver requests the interrupt with
> the IRQF_TRIGGER_FALLING flag set
Ah, that is a bug in oa_tc6.c. A falling edge appears to work, until
it does not and then all interrupts stop. So bugs like this are not
obvious. I've been looking out for this more over the last few years
since PHYs are level, not edge, but many developers get them wrong in
DT.
Please could you submit a patch to net to fix this?
Andrew
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140
2026-05-02 23:24 ` [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140 Ciprian Regus via B4 Relay
2026-05-03 1:06 ` Andrew Lunn
@ 2026-05-06 8:20 ` Krzysztof Kozlowski
1 sibling, 0 replies; 21+ messages in thread
From: Krzysztof Kozlowski @ 2026-05-06 8:20 UTC (permalink / raw)
To: Ciprian Regus
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Andrew Lunn, Heiner Kallweit, Russell King,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, netdev,
linux-kernel, linux-doc, devicetree
On Sun, May 03, 2026 at 02:24:54AM +0300, Ciprian Regus wrote:
> Add DT bindings for the ADIN1140 10BASE-T1S MACPHY. Update the
> MAINTAINERS entry to include the bindings file as well.
Beside other review, two things since I expect a v2 anyway:
A nit, subject: drop second/last, redundant "bindings for the". The
"dt-bindings" prefix is already stating that these are bindings.
See also:
https://elixir.bootlin.com/linux/v6.17-rc3/source/Documentation/devicetree/bindings/submitting-patches.rst#L18
>
> Signed-off-by: Ciprian Regus <ciprian.regus@analog.com>
> ---
> .../devicetree/bindings/net/adi,adin1140.yaml | 69 ++++++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 70 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/net/adi,adin1140.yaml b/Documentation/devicetree/bindings/net/adi,adin1140.yaml
> new file mode 100644
> index 000000000000..26cd40d36f9b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/adi,adin1140.yaml
> @@ -0,0 +1,69 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/adi,adin1140.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 implements 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:
> + enum:
> + - adi,adin1140
> + - adi,ad3306
I guess reversed order as numbers are before letters in most sorting.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2026-05-06 8:21 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-02 23:24 [PATCH net-next 0/5] net: Add ADIN1140 support Ciprian Regus via B4 Relay
2026-05-02 23:24 ` [PATCH net-next 1/5] net: ethernet: oa_tc6: Handle the OA TC6 SPI protected mode Ciprian Regus via B4 Relay
2026-05-02 23:24 ` [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus Ciprian Regus via B4 Relay
2026-05-03 3:50 ` Andrew Lunn
2026-05-03 17:34 ` Selvamani Rajagopal
2026-05-03 18:06 ` Andrew Lunn
2026-05-03 18:50 ` Selvamani Rajagopal
2026-05-02 23:24 ` [PATCH net-next 3/5] net: phy: Add support for the ADIN1140 PHY Ciprian Regus via B4 Relay
2026-05-03 0:40 ` Andrew Lunn
2026-05-02 23:24 ` [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY Ciprian Regus via B4 Relay
2026-05-03 0:59 ` Andrew Lunn
2026-05-03 1:01 ` Andrew Lunn
2026-05-03 3:15 ` Andrew Lunn
2026-05-03 3:36 ` Andrew Lunn
2026-05-03 15:15 ` Andrew Lunn
2026-05-03 18:19 ` Regus, Ciprian
2026-05-02 23:24 ` [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140 Ciprian Regus via B4 Relay
2026-05-03 1:06 ` Andrew Lunn
2026-05-04 7:33 ` Regus, Ciprian
2026-05-04 12:11 ` Andrew Lunn
2026-05-06 8:20 ` Krzysztof Kozlowski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox