From nobody Sun Jun 14 06:09:25 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 55F9B186A; Sat, 2 May 2026 23:25:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777764337; cv=none; b=eoBkygfKY80qseBue3x92PeFb4Nik0zz2Pc/a5sz7CBbsf02E44MIlnu3wscGG6cnUK2ylyJi8gQ1PSnNU+DzRirHhGFlWuRF8kUUkJB0mQb2B7HLnMT2JrQdlUNRsozmTcsdJo+GMut3dROIw89m04VtME3OF7RAkYk+7Ph4nY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777764337; c=relaxed/simple; bh=IpSw53TOh/n7IS6SsjWLenMx6OMEcXR4c08TD6yYtic=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=sFDJNziuK6ul9RHxaai0bMxWBlBE8kCslaM7mFuPpALi4mBKoLbBL5ZTdFjhcSFr+E+F9yqESb/plmvaVzl2tlqvJ32QMg/60QPH5lCfEnnaNcD9DcmG9DECOkmaG9PrkMXEaIWBRhT5ygQ9/XZ9IGPN7uIbejN+Quctt3LWfXs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=K+IuxdTE; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="K+IuxdTE" Received: by smtp.kernel.org (Postfix) with ESMTPS id 26A80C2BCB9; Sat, 2 May 2026 23:25:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1777764337; bh=IpSw53TOh/n7IS6SsjWLenMx6OMEcXR4c08TD6yYtic=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=K+IuxdTEywD5QY+rQWZ5rG9iS5aAQ6JgK3t5rb4sav/TpCDyOFdDV4b6onzLb/Idl BDJ9xb6qJSNKmIM6Px8Ovgx+5Ow7JE0/irGTH3dogSgZZ6jXlIZcscjvldymaLH2G/ NoK3c6twlqZxD8lHP+wOiWAFR7mQPRQ/qofaflsDlFkeH+TQlM68OV1zn7MbKYuqY6 jU71ze3g3B3B4fs7+ABaKcpgrNZ9/LIlmkzfw/Wn6wyJOZGQ5sy3jmDKTMmIrrnU/7 fbIbysvyrRDvn2RjnyTZFFePqGE+uoWeqKM4Y6WkBp21s+EWvocpxO8kk7m2ji+ZM3 8ltXg5r50D/9w== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 176DBCD3426; Sat, 2 May 2026 23:25:37 +0000 (UTC) From: Ciprian Regus via B4 Relay Date: Sun, 03 May 2026 02:24:50 +0300 Subject: [PATCH net-next 1/5] net: ethernet: oa_tc6: Handle the OA TC6 SPI protected mode Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260503-adin1140-driver-v1-1-dd043cdd88f0@analog.com> References: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com> In-Reply-To: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com> 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@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, devicetree@vger.kernel.org, Ciprian Regus X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1777764335; l=8778; i=ciprian.regus@analog.com; s=20260330; h=from:subject:message-id; bh=smd/ALBtlVdeWdMQcAG2kvWHkVX9YyfLReANHzFrjYA=; b=NgfRGoWt8KE67G6lRWZb1Ea1QUW1DMXqpn2nuURz9Uy0/fttF0/WI/FJp0hgkjR6YkutURFiR o6O1K0tt6zoCj9ktDT1nEyqhWrapNUHQFKi69YfpTPE0z3TciutYAol X-Developer-Key: i=ciprian.regus@analog.com; a=ed25519; pk=8WoNhI0kQcQUl8YqJO5ZevROYk9HP8lOIeIgIYgjfbc= X-Endpoint-Received: by B4 Relay for ciprian.regus@analog.com/20260330 with auth_id=703 X-Original-From: Ciprian Regus Reply-To: ciprian.regus@analog.com From: Ciprian Regus 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 =3D 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 =3D 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 --- 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) =20 /* 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 */ =20 +#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; }; =20 enum oa_tc6_header_type { @@ -212,25 +222,36 @@ static void oa_tc6_update_ctrl_write_data(struct oa_t= c6 *tc6, u32 value[], { __be32 *tx_buf =3D tc6->spi_ctrl_tx_buf + OA_TC6_CTRL_HEADER_SIZE; =20 - for (int i =3D 0; i < length; i++) + for (int i =3D 0; i < length; i++) { *tx_buf++ =3D cpu_to_be32(value[i]); + if (tc6->prot_ctrl) + *tx_buf++ =3D cpu_to_be32(~value[i]); + } } =20 -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 =3D OA_TC6_CTRL_REG_VALUE_SIZE; + + if (ctrl_prot) + reply_size +=3D 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; } =20 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 =3D tc6->spi_ctrl_tx_buf; =20 + memset(tx_buf, 0, buf_size); *tx_buf =3D oa_tc6_prepare_ctrl_header(address, length, reg_op); =20 if (reg_op =3D=3D OA_TC6_CTRL_REG_WRITE) @@ -253,10 +274,12 @@ static int oa_tc6_check_ctrl_write_reply(struct oa_tc= 6 *tc6, u8 size) return 0; } =20 -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 =3D tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE; - u32 *tx_buf =3D tc6->spi_ctrl_tx_buf; + __be32 *rx_buf =3D tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE; + __be32 *tx_buf =3D tc6->spi_ctrl_tx_buf; + u32 complement; + u32 reply; =20 /* 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 !=3D *rx_buf) return -EPROTO; =20 + if (tc6->prot_ctrl) { + /* Skip past the echoed header to the value/complement pairs */ + rx_buf +=3D 1; + for (int i =3D 0; i < length; i++) { + reply =3D be32_to_cpu(rx_buf[0]); + complement =3D be32_to_cpu(rx_buf[1]); + + if (complement !=3D ~reply) + return -EPROTO; + + rx_buf +=3D 2; + } + } + return 0; } =20 @@ -273,8 +310,13 @@ static void oa_tc6_copy_ctrl_read_data(struct oa_tc6 *= tc6, u32 value[], __be32 *rx_buf =3D tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE + OA_TC6_CTRL_HEADER_SIZE; =20 - for (int i =3D 0; i < length; i++) + for (int i =3D 0; i < length; i++) { value[i] =3D be32_to_cpu(*rx_buf++); + + /* skip complement word */ + if (tc6->prot_ctrl) + rx_buf++; + } } =20 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, u3= 2 address, u32 value[], u16 size; int ret; =20 - /* Prepare control command and copy to SPI control buffer */ - oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op); + size =3D oa_tc6_calculate_ctrl_buf_size(length, tc6->prot_ctrl); =20 - size =3D 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); =20 /* Perform SPI transfer */ ret =3D 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); =20 /* Check echoed/received control read command reply for errors */ - ret =3D oa_tc6_check_ctrl_read_reply(tc6, size); + ret =3D oa_tc6_check_ctrl_read_reply(tc6, length); if (ret) return ret; =20 @@ -1224,6 +1266,20 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, st= ruct sk_buff *skb) } EXPORT_SYMBOL_GPL(oa_tc6_start_xmit); =20 +static int oa_tc6_check_ctrl_protection(struct oa_tc6 *tc6) +{ + u32 regval; + int ret; + + ret =3D oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, ®val); + if (ret) + return ret; + + tc6->prot_ctrl =3D 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, s= truct net_device *netdev) if (!tc6->spi_data_rx_buf) return NULL; =20 + ret =3D 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 =3D 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 =3D 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 =3D oa_tc6_sw_reset_macphy(tc6); if (ret) { dev_err(&tc6->spi->dev, --=20 2.43.0 From nobody Sun Jun 14 06:09:25 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7917629BDBF; Sat, 2 May 2026 23:25:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777764337; cv=none; b=SHcljhnqJXLVkZi+fisSdVrWoqwNIAxuXLz17bUGvGb+Ogh+pYhKxRpdLd6bxLDye4+8aBwykhgM/lFpiOyeixj/6dirdD9h2nuThkfxXuOyRRWjXNPWh8AfB1zrt2puf/JkwAafR2/HioNoSNponfY+zTwU5j0tqOz/uLm9EYk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777764337; c=relaxed/simple; bh=lSp5RtkRrkLS3UnLdEXDQPx8rCZF2JtRoDbFylwnoyc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=dtn59fi8h71GbkayWM4sgQzAqe4OkeoDyeLoUHIynXCPl4+Qs/cSaf02ePzBbHKjkpjoAYio5Q8Vp1U4Ndh4ReX/M2EqEbc5Fa+fJpKQG0K7eXN5XXk7RhcKcl7I4k/cWTMMvXb43He0sqw2aRZm/NG0sXQlKIdneE0VrKyqDa0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=BlUO0Em0; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="BlUO0Em0" Received: by smtp.kernel.org (Postfix) with ESMTPS id 35802C4AF0B; Sat, 2 May 2026 23:25:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1777764337; bh=lSp5RtkRrkLS3UnLdEXDQPx8rCZF2JtRoDbFylwnoyc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=BlUO0Em0KNw1bzJAzm/c/teQ9tbDVv1kLunkwvmOOs2dlt0+IMHNLtLG1GovxL4G8 ArnSLueJNVE7DLY++hj8qcJQQiWUQAYSpaWa00JADq8kWJb0Iz+PRrmENIHiYJaVlg aUADQfABvbu2OGv/Fn86vkg16r8ajTqvIEoggVczlDQngMu0mBqxpNU0vwitJbrSjV 7ZpMyXCrNqjpGcoJ+zxB4yF13c/nzXPcgOwH3/lz/doIWdPOTR5wtqaMrNZ2jC9icT dVjfTbgdxqt35EYVojTmdpsPzqa8FkKC8BgjlfJgsU5mm41XpkhJ9+5+az/P7yvRtU AF1Fbi3CuRk8A== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 27F97CD13DA; Sat, 2 May 2026 23:25:37 +0000 (UTC) From: Ciprian Regus via B4 Relay Date: Sun, 03 May 2026 02:24:51 +0300 Subject: [PATCH net-next 2/5] net: ethernet: oa_tc6: Allow custom mii_bus Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260503-adin1140-driver-v1-2-dd043cdd88f0@analog.com> References: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com> In-Reply-To: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com> 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@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, devicetree@vger.kernel.org, Ciprian Regus X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1777764335; l=8828; i=ciprian.regus@analog.com; s=20260330; h=from:subject:message-id; bh=i2IZDXFhSiWWDn9rUet0TbTRwmDcB1jlwrQRqyKLBfM=; b=j2nbNGaipLRK/u1xIXBvUubr0GD80MDP1a9NnSREu10kT692oHrykvzN3rnYq+pJuYBraZkAI y2b55NeHWy8BxW7NSgrk2Xo51GJRNFWBg3BmmR0Fr/5sNxwD62u6Czu X-Developer-Key: i=ciprian.regus@analog.com; a=ed25519; pk=8WoNhI0kQcQUl8YqJO5ZevROYk9HP8lOIeIgIYgjfbc= X-Endpoint-Received: by B4 Relay for ciprian.regus@analog.com/20260330 with auth_id=703 X-Original-From: Ciprian Regus Reply-To: ciprian.regus@analog.com From: Ciprian Regus 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 --- 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 =20 The include/linux/oa_tc6.h defines the following functions: =20 -.. 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); =20 Initialize OA TC6 lib. =20 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 = =3D { =20 static int lan865x_probe(struct spi_device *spi) { + struct oa_tc6_config tc6_config =3D {}; 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); =20 - priv->tc6 =3D oa_tc6_init(spi, netdev); + tc6_config.spi =3D spi; + tc6_config.netdev =3D netdev; + + priv->tc6 =3D oa_tc6_init(&tc6_config); if (!priv->tc6) { ret =3D -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; }; =20 enum oa_tc6_header_type { @@ -538,32 +539,37 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6) { int ret; =20 - tc6->mdiobus =3D mdiobus_alloc(); if (!tc6->mdiobus) { - netdev_err(tc6->netdev, "MDIO bus alloc failed\n"); - return -ENOMEM; + tc6->mdiobus =3D mdiobus_alloc(); + if (!tc6->mdiobus) { + netdev_err(tc6->netdev, "MDIO bus alloc failed\n"); + return -ENOMEM; + } + + tc6->mdiobus->read =3D oa_tc6_mdiobus_read; + tc6->mdiobus->write =3D 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 =3D oa_tc6_mdiobus_read_c45; + tc6->mdiobus->write_c45 =3D oa_tc6_mdiobus_write_c45; + + tc6->own_mdiobus =3D true; } =20 tc6->mdiobus->priv =3D tc6; - tc6->mdiobus->read =3D oa_tc6_mdiobus_read; - tc6->mdiobus->write =3D 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 =3D oa_tc6_mdiobus_read_c45; - tc6->mdiobus->write_c45 =3D oa_tc6_mdiobus_write_c45; - tc6->mdiobus->name =3D "oa-tc6-mdiobus"; tc6->mdiobus->parent =3D tc6->dev; + tc6->mdiobus->name =3D "oa-tc6-mdiobus"; =20 snprintf(tc6->mdiobus->id, ARRAY_SIZE(tc6->mdiobus->id), "%s", - dev_name(&tc6->spi->dev)); + dev_name(&tc6->spi->dev)); =20 ret =3D mdiobus_register(tc6->mdiobus); if (ret) { @@ -577,19 +583,30 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6) =20 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); } =20 static int oa_tc6_phy_init(struct oa_tc6 *tc6) { int ret; =20 - ret =3D 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 =3D 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; + } } =20 ret =3D oa_tc6_mdiobus_register(tc6); @@ -621,7 +638,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6) =20 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); } =20 @@ -1282,24 +1301,28 @@ static int oa_tc6_check_ctrl_protection(struct oa_t= c6 *tc6) =20 /** * 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 *netd= ev) +struct oa_tc6 *oa_tc6_init(struct oa_tc6_config *config) { struct oa_tc6 *tc6; int ret; =20 - tc6 =3D devm_kzalloc(&spi->dev, sizeof(*tc6), GFP_KERNEL); + if (!config) + return NULL; + + tc6 =3D devm_kzalloc(&config->spi->dev, sizeof(*tc6), GFP_KERNEL); if (!tc6) return NULL; =20 - tc6->spi =3D spi; - tc6->netdev =3D netdev; - SET_NETDEV_DEV(netdev, &spi->dev); + tc6->spi =3D config->spi; + tc6->netdev =3D config->netdev; + tc6->mdiobus =3D config->mii_bus; + SET_NETDEV_DEV(tc6->netdev, &tc6->spi->dev); mutex_init(&tc6->spi_ctrl_lock); spin_lock_init(&tc6->tx_skb_lock); =20 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 @@ */ =20 #include +#include #include =20 struct oa_tc6; =20 -struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netd= ev); +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[], --=20 2.43.0 From nobody Sun Jun 14 06:09:25 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 84B6F31353B; Sat, 2 May 2026 23:25:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777764337; cv=none; b=UUf+8g0sE6R+4y0Vo1Py9lY4myU0FCOCDAWGr7QHn3Ah2+wNiL0FfedOek6CqC95+B+b+U4pAzG2AgsGLDSOo/t/Kc9eJrrbzwPbC11DZK5UIR5ArWeKIoAOuXkaRdlMJl1cOi/RUZPZW+9403ts6e+BMfagSY8BZs7dHN9a39g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777764337; c=relaxed/simple; bh=nsGjpBrmrNcbrS1KY/oqrfp59alJQXxPXMGfQLwRs3o=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=rwsC3ozlBjVcFN8Hw4WNtdWldTicJI3+DAdkSeppU1KdKFgntZ/usN1mIh6c4mlGuf0voPal8tzNWk2k5kZj5lKHtvNqoO4SWqYgtDfBdS49iySO7RpwgAarA5GbQiz1yK1D+3euZrWjIusJSWfQiZYA0Dpyuc6+fnzYGAuvva4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=D0b/zTKn; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="D0b/zTKn" Received: by smtp.kernel.org (Postfix) with ESMTPS id 44826C2BCC6; Sat, 2 May 2026 23:25:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1777764337; bh=nsGjpBrmrNcbrS1KY/oqrfp59alJQXxPXMGfQLwRs3o=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=D0b/zTKnigElRCmCActLpXXwMpZXXgldhI/0W3qdLozpX0ql/qt3zqpdVpr81sTEg 8VMeQllZIkRK1TapO4Z6MU3pjBk00YZkkPea8OdHcP3F9//MySCONldlcdkchlHJCJ Ba1Cd9nuB7UhgzAiXKMF8f893a0pKLLn8+plySpIdpU8R+J8Moq2j3W0WNPV0E8WQA 9IdfmBl+gNHfG+EwFs0WmLQdkdrw0BqYMNtaOy3PxWPWARJoSypcZYBIJDEcxKPenH j5nJj5AzlGNznLKq8vRlD4fqFxRuNM1N6YqT5zHgD2cY1MPBRgw1eLtPDonv97Ky/B zEN4NPhY3iZ6g== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 389BFCD342F; Sat, 2 May 2026 23:25:37 +0000 (UTC) From: Ciprian Regus via B4 Relay Date: Sun, 03 May 2026 02:24:52 +0300 Subject: [PATCH net-next 3/5] net: phy: Add support for the ADIN1140 PHY Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260503-adin1140-driver-v1-3-dd043cdd88f0@analog.com> References: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com> In-Reply-To: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com> 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@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, devicetree@vger.kernel.org, Ciprian Regus X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1777764335; l=5616; i=ciprian.regus@analog.com; s=20260330; h=from:subject:message-id; bh=HCb/AunC9RbeCHs1GdUT4EU6xyEzkCQ+rUmpQ9/cYnE=; b=RLzPPlA+Ge2DIw1rBKJmszjrTRIGSzAmF32/2XMLWBMiN/lWREY4lnbDhj0wc5mBgtjcdb1jq SCmDXx+BehiAk263RqEEV6XkuHJ1qBVwEjB5/CfDpoeNwXQryhLJBC5 X-Developer-Key: i=ciprian.regus@analog.com; a=ed25519; pk=8WoNhI0kQcQUl8YqJO5ZevROYk9HP8lOIeIgIYgjfbc= X-Endpoint-Received: by B4 Relay for ciprian.regus@analog.com/20260330 with auth_id=703 X-Original-From: Ciprian Regus Reply-To: ciprian.regus@analog.com From: Ciprian Regus 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 =3D 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 --- 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 =20 +ANALOG DEVICES INC ETHERNET PHY DRIVERS +M: Ciprian Regus +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 M: Michael Hennerich 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 =20 +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 +=3D $(sfp-obj-y) $(sfp-obj-m) =20 obj-$(CONFIG_ADIN_PHY) +=3D adin.o obj-$(CONFIG_ADIN1100_PHY) +=3D adin1100.o +obj-$(CONFIG_ADIN1140_PHY) +=3D adin1140.o obj-$(CONFIG_AIR_EN8811H_PHY) +=3D air_en8811h.o obj-$(CONFIG_AMD_PHY) +=3D amd.o obj-$(CONFIG_AMCC_QT2025_PHY) +=3D 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 +#include +#include + +#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 =3D phydev->mdio.bus; + int addr =3D 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 =3D phydev->mdio.bus; + int addr =3D 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 =3D 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 s= peed) +{ + 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 =3D 1; + phydev->duplex =3D DUPLEX_HALF; + phydev->speed =3D SPEED_10; + phydev->autoneg =3D AUTONEG_DISABLE; + + return 0; +} + +static struct phy_driver adin1140_driver[] =3D { + { + PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID), + .name =3D "ADIN1140", + .features =3D PHY_BASIC_T1S_P2MP_FEATURES, + .read_status =3D adin1140_read_status, + .config_init =3D adin1140_config_init, + .config_aneg =3D adin1140_config_aneg, + .set_loopback =3D adin1140_loopback, + .read_mmd =3D adin1140_phy_read_mmd, + .write_mmd =3D adin1140_phy_write_mmd, + .get_plca_cfg =3D genphy_c45_plca_get_cfg, + .set_plca_cfg =3D genphy_c45_plca_set_cfg, + .get_plca_status =3D genphy_c45_plca_get_status, + }, +}; +module_phy_driver(adin1140_driver); + +static const struct mdio_device_id __maybe_unused adin1140_tbl[] =3D { + { 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 "); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Sun Jun 14 06:09:25 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 89ADF313547; Sat, 2 May 2026 23:25:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777764337; cv=none; b=JL8jNZV5huPm/WxaJQocXiZYBNsOVOR/otPCjl/Djk82P2D3870Vst1cVxbwvBULd97ssXHjampAHHdkf+QwoPYjo8ZZuQjOwHGifzWydQs7RirBZKpL93e0MhVyCDujFp4jCh2GwQDsTek+lR+WdNke7G5kGqlicVHx8uSxIP4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777764337; c=relaxed/simple; bh=/G0bmAwugWgnxotkFNUeWbuCwyW5AA/WsKNp4QvzG2o=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=IkJcHTkO8Ib/4DBLR0UTc0vvCNj8gTNBNaynzhBWzRh8eTkfzK5c8vXtRyjIlIHRKT9KMlCMnp0U55Re5Zu4I6xzUm4iseFBOWqFnCbuOi7qT4IyL2AS+QXK3DmC8VIFtxceRx1n/8b4s8tDs4r0knUBIBhcMnjlMXtogBuc5o0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=lfyJFSxG; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="lfyJFSxG" Received: by smtp.kernel.org (Postfix) with ESMTPS id 53647C2BCFC; Sat, 2 May 2026 23:25:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1777764337; bh=/G0bmAwugWgnxotkFNUeWbuCwyW5AA/WsKNp4QvzG2o=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=lfyJFSxGpB/349+nxK0jdgDV19/KnG0Oufd4rUt8CUF830Xt8s1RXGItHTxrF8K8O D5cqWIqEZuuV4j919Bs6LyhVajck5gW6pJHq0pD34J3BemxYSF0sdxsLLY5raQ9Ubq uNZT7FplK6QlGvbLCpGEoPt0U95sq4Xkqiv9oekIRakikVirgY7LQ8BMi3XYqVriwU vEZ/e+1Qqz3C246igxRHAHw5jElG4+aUuUctV5tqiFup2jzLQOBlTp7k5PW0uovYTc /EJQgPeFPMTCxZOdoCRH+1l4ZXcfT8lYDOrNi/Qm3UUDyOOPVS4NRtZ8/+MkyjFjJ7 2qHLFL4vBCx8A== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4BB46CD3423; Sat, 2 May 2026 23:25:37 +0000 (UTC) From: Ciprian Regus via B4 Relay Date: Sun, 03 May 2026 02:24:53 +0300 Subject: [PATCH net-next 4/5] net: ethernet: adi: Add a driver for the ADIN1140 MACPHY Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260503-adin1140-driver-v1-4-dd043cdd88f0@analog.com> References: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com> In-Reply-To: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com> 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@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, devicetree@vger.kernel.org, Ciprian Regus X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1777764335; l=27881; i=ciprian.regus@analog.com; s=20260330; h=from:subject:message-id; bh=EUl6ZXO1Ca+o8UEwI3xqiQLfVVKUGYQ6i5gLOlUE+hw=; b=Nt6jqHuhZdO5z66ZLjnW6BXS9uCHct7cmrHbmK1GeplHeXyTr6xKPIIQikmdvmnSaTPvtrlHk ZijQd6oaNoqDRrPCOW67kLfb0eaRY3bJzM6PtHVfblAcpEy988qAAyJ X-Developer-Key: i=ciprian.regus@analog.com; a=ed25519; pk=8WoNhI0kQcQUl8YqJO5ZevROYk9HP8lOIeIgIYgjfbc= X-Endpoint-Received: by B4 Relay for ciprian.regus@analog.com/20260330 with auth_id=703 X-Original-From: Ciprian Regus Reply-To: ciprian.regus@analog.com From: Ciprian Regus 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 --- 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 =20 +ANALOG DEVICES INC ETHERNET DRIVERS +M: Ciprian Regus +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 L: netdev@vger.kernel.org diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kc= onfig 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. =20 +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/M= akefile index d0383d94303c..0390ca8ccc49 100644 --- a/drivers/net/ethernet/adi/Makefile +++ b/drivers/net/ethernet/adi/Makefile @@ -4,3 +4,4 @@ # =20 obj-$(CONFIG_ADIN1110) +=3D adin1110.o +obj-$(CONFIG_ADIN1140) +=3D 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 +#include +#include +#include +#include +#include + +#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[] =3D { + {.name =3D "rx_frames", .addr =3D ADIN1140_RX_FRAME_CNT}, + {.name =3D "rx_broadcast_frames", .addr =3D ADIN1140_RX_BC_FRAME_CNT}, + {.name =3D "rx_multicast_frames", .addr =3D ADIN1140_RX_MC_FRAME_CNT}, + {.name =3D "rx_unicast_frames", .addr =3D ADIN1140_RX_UC_FRAME_CNT}, + {.name =3D "rx_crc_errors", .addr =3D ADIN1140_RX_CRC_ERR_CNT}, + {.name =3D "rx_align_errors", .addr =3D ADIN1140_RX_ALIGN_ERR_CNT}, + {.name =3D "rx_preamble_errors", .addr =3D ADIN1140_RX_PREAMBLE_ERR_CNT}, + {.name =3D "rx_short_frame_errors", .addr =3D ADIN1140_RX_SHORT_ERR_CNT}, + {.name =3D "rx_long_frame_errors", .addr =3D ADIN1140_RX_LONG_ERR_CNT}, + {.name =3D "rx_phy_errors", .addr =3D ADIN1140_RX_PHY_ERR_CNT}, + {.name =3D "rx_fifo_full_dropped", .addr =3D ADIN1140_RX_DRP_FULL_CNT}, + {.name =3D "rx_addr_filt_dropped", .addr =3D ADIN1140_RX_DRP_FILTER_CNT}, + {.name =3D "rx_ifg_errors", .addr =3D ADIN1140_RX_IFG_ERR_CNT}, + {.name =3D "tx_frames", .addr =3D ADIN1140_TX_FRAME_CNT}, + {.name =3D "tx_broadcast_frames", .addr =3D ADIN1140_TX_BC_FRAME_CNT}, + {.name =3D "tx_multicast_frames", .addr =3D ADIN1140_TX_MC_FRAME_CNT}, + {.name =3D "tx_unicast_frames", .addr =3D ADIN1140_TX_UC_FRAME_CNT}, + {.name =3D "tx_single_collision", .addr =3D ADIN1140_TX_SINGLE_COL_CNT}, + {.name =3D "tx_multi_collision", .addr =3D ADIN1140_TX_MULTI_COL_CNT}, + {.name =3D "tx_deferred", .addr =3D ADIN1140_TX_DEFERRED_CNT}, + {.name =3D "tx_late_collision", .addr =3D ADIN1140_TX_LATE_COL_CNT}, + {.name =3D "tx_excess_collision", .addr =3D ADIN1140_TX_EXCESS_COL_CNT}, + {.name =3D "tx_underrun", .addr =3D 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 >=3D ADIN1140_MAC_FILT_MAX_SLOT) + return -ENOSPC; + + ret =3D 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 =3D oa_tc6_write_register(priv->tc6, + ADIN1140_MAC_ADDR_FILT_LWR_REG + 2 * slot, + get_unaligned_be32(&addr[2])); + if (ret) + return ret; + + val =3D get_unaligned_be16(&mask[0]); + mask_reg =3D ADIN1140_MAC_ADDR_MASK_UPR_REG + (2 * slot); + + ret =3D oa_tc6_write_register(priv->tc6, mask_reg, val); + if (ret) + return ret; + + val =3D get_unaligned_be32(&mask[2]); + mask_reg =3D 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] =3D {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 enab= led) +{ + 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 =3D adin1140_filter_broadcast(priv, true); + if (ret) + return ret; + + return adin1140_filter_unicast(priv); +} + +static int adin1140_promiscuous_mode(struct adin1140_priv *priv, bool enab= led) +{ + int ret; + u32 val; + + ret =3D oa_tc6_read_register(priv->tc6, ADIN1140_CONFIG2_REG, &val); + if (ret) + return ret; + + if (enabled) + val |=3D ADIN1140_CONFIG2_FWD_UNK2HOST; + else + val &=3D ~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 =3D 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 =3D netdev_uc_count(priv->netdev) + + netdev_mc_count(priv->netdev); + + if (priv->netdev->flags & IFF_PROMISC) { + promisc =3D true; + all_multi =3D false; + } else if (priv->netdev->flags & IFF_ALLMULTI) { + promisc =3D false; + all_multi =3D true; + } else if (mac_addrs <=3D ADIN1140_MAC_FILT_AVAIL) { + promisc =3D false; + all_multi =3D false; + + slot =3D ADIN1140_MAC_FILT_UC_SLOT + 1; + memset(mask, 0xFF, ETH_ALEN); + + netdev_for_each_uc_addr(ha, priv->netdev) { + ret =3D adin1140_mac_filter_set(priv, ha->addr, mask, + slot); + if (ret) + return; + + slot++; + } + + netdev_for_each_mc_addr(ha, priv->netdev) { + ret =3D adin1140_mac_filter_set(priv, ha->addr, mask, + slot); + if (ret) + return; + + slot++; + } + } else { + /* The filter table is full. Enable promisc mode. */ + promisc =3D true; + all_multi =3D false; + + start =3D ADIN1140_MAC_FILT_UC_SLOT + 1; + end =3D ADIN1140_MAC_FILT_MAX_SLOT; + for (i =3D start; i < end; i++) { + ret =3D adin1140_mac_filter_clear(priv, i); + if (ret) + return; + } + } + + ret =3D 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 =3D netdev_priv(netdev); + + schedule_work(&priv->rx_mode_work); +} + +static void adin1140_stats_work(struct work_struct *work) +{ + struct delayed_work *dwork =3D to_delayed_work(work); + u64 stat_buff[ADIN1140_STATS_CNT] =3D {}; + struct adin1140_priv *priv; + u32 reg_val; + int ret; + u32 i; + + priv =3D container_of(dwork, struct adin1140_priv, stats_work); + + for (i =3D 0; i < ARRAY_SIZE(adin1140_stats); i++) { + ret =3D oa_tc6_read_register(priv->tc6, adin1140_stats[i].addr, + ®_val); + if (ret) + break; + + stat_buff[i] =3D 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 =3D oa_tc6_zero_align_receive_frame_enable(priv->tc6); + if (ret) + return ret; + + ret =3D oa_tc6_read_register(priv->tc6, ADIN1140_CONFIG0_REG, &val); + if (ret) + return ret; + + /* Zero-Align Receive Frame Enable */ + val |=3D ADIN1140_CONFIG0_RFA_ZARFE; + + /* Transmit Frame Check Sequence Validation must be disabled + * to allow CRC appending by MAC (CONFIG2.CRC_APPEND) + */ + val &=3D ~ADIN1140_CONFIG0_TXFCSVE; + val |=3D ADIN1140_CONFIG0_CPS_64; + + ret =3D oa_tc6_write_register(priv->tc6, ADIN1140_CONFIG0_REG, val); + if (ret) + return ret; + + /* Disable MAC loopback */ + ret =3D 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 =3D 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 =3D 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 =3D 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 =3D netdev_priv(netdev); + struct sockaddr *address =3D addr; + u8 mask[ETH_ALEN]; + int ret; + + ret =3D 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 =3D 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 =3D netdev_priv(dev); + + storage->rx_packets =3D priv->netdev->stats.rx_packets; + storage->tx_packets =3D priv->netdev->stats.tx_packets; + + storage->rx_bytes =3D priv->netdev->stats.rx_bytes; + storage->tx_bytes =3D priv->netdev->stats.tx_bytes; + + spin_lock(&priv->stat_lock); + + storage->rx_errors =3D 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 =3D priv->stats[tx_excess_collision] + + priv->stats[tx_underrun]; + + storage->rx_dropped =3D priv->stats[rx_fifo_full_dropped] + + priv->stats[rx_addr_filter_dropped]; + + storage->multicast =3D priv->stats[rx_multicast_frames]; + + storage->collisions =3D priv->stats[tx_single_collision] + + priv->stats[tx_multi_collision]; + + storage->rx_length_errors =3D priv->stats[rx_short_frame_errors] + + priv->stats[rx_long_frame_errors]; + storage->rx_over_errors =3D priv->stats[rx_fifo_full_dropped]; + storage->rx_crc_errors =3D priv->stats[rx_crc_errors]; + storage->rx_frame_errors =3D priv->stats[rx_align_errors]; + storage->rx_missed_errors =3D priv->stats[rx_fifo_full_dropped]; + + storage->tx_aborted_errors =3D priv->stats[tx_excess_collision]; + storage->tx_fifo_errors =3D priv->stats[tx_underrun]; + storage->tx_window_errors =3D 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 =3D 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 ss= et, + u8 *p) +{ + u32 i; + + switch (sset) { + case ETH_SS_STATS: + for (i =3D 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 =3D bus->priv; + u32 regval; + u32 mms; + int ret; + + mms =3D adin1140_get_phy_c45_mms(devnum); + ret =3D 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 =3D bus->priv; + int ret; + + ret =3D 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 =3D 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 =3D 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 =3D 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 regnu= m, + u16 val) +{ + return 0; +} + +static int adin1140_mdio_register(struct adin1140_priv *priv) +{ + priv->mdiobus =3D mdiobus_alloc(); + if (!priv->mdiobus) { + netdev_err(priv->netdev, "MDIO bus alloc failed\n"); + return -ENOMEM; + } + + priv->mdiobus->read =3D adin1140_mdiobus_read; + priv->mdiobus->write =3D adin1140_mdiobus_write; + priv->mdiobus->read_c45 =3D adin1140_mdiobus_read_c45; + priv->mdiobus->write_c45 =3D adin1140_mdiobus_write_c45; + + return 0; +} + +static const struct ethtool_ops adin1140_ethtool_ops =3D { + .get_drvinfo =3D adin1140_get_drvinfo, + .get_link =3D ethtool_op_get_link, + .get_ethtool_stats =3D adin1140_get_ethtool_stats, + .get_sset_count =3D adin1140_get_sset_count, + .get_strings =3D adin1140_get_ethtool_strings, + .get_link_ksettings =3D phy_ethtool_get_link_ksettings, + .set_link_ksettings =3D phy_ethtool_set_link_ksettings, +}; + +static const struct net_device_ops adin1140_netdev_ops =3D { + .ndo_open =3D adin1140_open, + .ndo_stop =3D adin1140_close, + .ndo_start_xmit =3D adin1140_start_xmit, + .ndo_set_mac_address =3D adin1140_set_mac_address, + .ndo_validate_addr =3D eth_validate_addr, + .ndo_set_rx_mode =3D adin1140_rx_mode, + .ndo_eth_ioctl =3D adin1140_ioctl, + .ndo_get_stats64 =3D adin1140_ndo_get_stats64, +}; + +static int adin1140_probe(struct spi_device *spi) +{ + struct oa_tc6_config tc6_config =3D {}; + struct net_device *netdev; + struct adin1140_priv *priv; + int ret; + + netdev =3D alloc_etherdev(sizeof(struct adin1140_priv)); + if (!netdev) + return -ENOMEM; + + priv =3D netdev_priv(netdev); + priv->netdev =3D netdev; + spi_set_drvdata(spi, priv); + spin_lock_init(&priv->stat_lock); + + ret =3D adin1140_mdio_register(priv); + if (ret) + goto netdev_free; + + tc6_config.spi =3D spi; + tc6_config.netdev =3D netdev; + tc6_config.mii_bus =3D priv->mdiobus; + + priv->tc6 =3D oa_tc6_init(&tc6_config); + if (!priv->tc6) { + ret =3D -ENODEV; + goto mdio_free; + } + + if (device_get_ethdev_address(&spi->dev, netdev)) + eth_hw_addr_random(netdev); + + ret =3D 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 =3D IF_PORT_10BASET; + netdev->irq =3D spi->irq; + netdev->netdev_ops =3D &adin1140_netdev_ops; + netdev->ethtool_ops =3D &adin1140_ethtool_ops; + netdev->netns_immutable =3D true; + netdev->priv_flags |=3D IFF_LIVE_ADDR_CHANGE | + IFF_UNICAST_FLT; + + ret =3D 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 =3D 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[] =3D { + { .name =3D "adin1140" }, + { .name =3D "ad3306" }, + {}, +}; +MODULE_DEVICE_TABLE(spi, adin1140_spi_id); + +static const struct of_device_id adin1140_match_table[] =3D { + { .compatible =3D "adi,adin1140" }, + { .compatible =3D "adi,ad3306" }, + { } +}; +MODULE_DEVICE_TABLE(of, adin1140_match_table); + +static struct spi_driver adin1140_driver =3D { + .driver =3D { + .name =3D "adin1140", + .of_match_table =3D adin1140_match_table, + }, + .probe =3D adin1140_probe, + .remove =3D adin1140_remove, + .id_table =3D adin1140_spi_id, +}; +module_spi_driver(adin1140_driver); + +MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY"); +MODULE_AUTHOR("Ciprian Regus "); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Sun Jun 14 06:09:25 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 96C9C2C11E4; Sat, 2 May 2026 23:25:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777764337; cv=none; b=UHS4nKbWMBIsbSSr4Nl5rpLTfnOwOBC0SKQNc1qDllEstKwukI6I66iV6sANHAdHrhKOM53VWX2EQVBv5xttvjS+tyUwL2QUp+waSTeD2LYxvwfQZMcGNiIRqZKn5oqJcPrVcSfKVSryHdcLAAWDGb33PeCo8eVan4H5zxWDu8U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777764337; c=relaxed/simple; bh=PU/PgJRELyFZ9axF6kSaLQk+fnNU+Qed4LVJD3zSKoY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=LNHVFH6eXf4fqHAcmrm3ZACDOHGbwvjUKUeIb1hloZyocMURtzyFf0dRj9R4hsUzbBVP8PAXv++lgxNoGzecvXImPxrG9dgQfFTCclBLGYh2MUbjKLR2a0Te1yGyYU/ENK43nuoyzVtaEoCVVwbqPVLRRJwl6roe8f5Y1DkkufQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ZQIHiS/j; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ZQIHiS/j" Received: by smtp.kernel.org (Postfix) with ESMTPS id 62AD2C2BCF6; Sat, 2 May 2026 23:25:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1777764337; bh=PU/PgJRELyFZ9axF6kSaLQk+fnNU+Qed4LVJD3zSKoY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ZQIHiS/jKnUyyk1Kx6a95GRHM+hkpT5cRE3tORgtshMYtXKzx6KZ4kjqpHXIkVk/F Y6yhidls8VmsczvZR5FcfwEtp6kdc5K5cWLYEs90bFYPXUFAUT379wY0hBI6FPNAeo k8QdrknPlKWGfwphczAronlP//Pj1Snm9Hk8zL3/GNZJkuCj8dHzCborITqpQWkYIu lWoN3zBsMKdqXvYk1NhLanIhd9PSC8LqDRND+tbHo8Y9d7RlC9KvQTxen8Cq1l/24n YSyBlu+mAl5q/OGVQ6pKN5UHWjcsoZhMhNAZkHN99AF5RrJnUdPnOSQjuCrwsG8WVQ zT4rzhBDnw6pw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5B22ECD3426; Sat, 2 May 2026 23:25:37 +0000 (UTC) From: Ciprian Regus via B4 Relay Date: Sun, 03 May 2026 02:24:54 +0300 Subject: [PATCH net-next 5/5] dt-bindings: net: Add bindings for the ADIN1140 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260503-adin1140-driver-v1-5-dd043cdd88f0@analog.com> References: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com> In-Reply-To: <20260503-adin1140-driver-v1-0-dd043cdd88f0@analog.com> 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@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, devicetree@vger.kernel.org, Ciprian Regus X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1777764335; l=2909; i=ciprian.regus@analog.com; s=20260330; h=from:subject:message-id; bh=fsypCuzG+8KsvoEcvGQ6yCJv/rH7hOitcPb70G23Ry8=; b=IfQ3JGxqRuCDGFpO/P9bRwjhIFJQOyUBMejKbfs/rBS33QxPU7X5rXn5hE4r4EhqBykipUBB2 dkVrqgIOr5ZAehaPGZErUqfkPGoO24jQjlt5vghv6N9DQoutLODifLf X-Developer-Key: i=ciprian.regus@analog.com; a=ed25519; pk=8WoNhI0kQcQUl8YqJO5ZevROYk9HP8lOIeIgIYgjfbc= X-Endpoint-Received: by B4 Relay for ciprian.regus@analog.com/20260330 with auth_id=703 X-Original-From: Ciprian Regus Reply-To: ciprian.regus@analog.com From: Ciprian Regus 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 --- .../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/Docu= mentation/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 + +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 + + spi { + #address-cells =3D <1>; + #size-cells =3D <0>; + + ethernet@0 { + compatible =3D "adi,adin1140"; + reg =3D <0>; + spi-max-frequency =3D <23000000>; + + interrupt-parent =3D <&gpio>; + interrupts =3D <6 IRQ_TYPE_EDGE_FALLING>; + + local-mac-address =3D [ 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 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 =20 ANALOG DEVICES INC ETHERNET PHY DRIVERS --=20 2.43.0