* [PATCH net-next v9 01/11] dt-bindings: net: add onsemi's S2500
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-09 17:12 ` [PATCH net-next v9 02/11] Documentation: networking: Add timestamp related APIs to OA TC6 framework Selvamani Rajagopal via B4 Relay
` (9 subsequent siblings)
10 siblings, 0 replies; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus, Krzysztof Kozlowski
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Add device tree binding for the onsemi S2500 IEEE 802.3cg
compliant Ethernet transceiver device.
S2500 has both Media Access Controller (MAC) and 10Base-T1S
Ethernet PHY. MAC implements 10 Mbps half duplex Ethernet
MAC. Communication between the host and MAC-PHY is defined in
OPEN Alliance 10BASE-T1x MACPHY Serial Interface document
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- No changes
changes in v8
- Changed the interrupt type to level triggered
- Added reset-gpio entry that was recently supported.
changes in v7
- Added the missing review tag
- Removed the word YAML from the commit message
changes in v6
- Moved the signed off information to the right place.
changes in v5
- no changes
changes in v4:
- added spi-max-frequency as suggested by AI review
- changed interrupt to IRQ_TYPE_EDGE_FALLING as it is
being taken care in net (stable) branch
changes in v3
- Removed URL link that failed verification
changes in v2
- removed spi-max-frequency entry
- changed the compatible string to s2500
changes in v1
- Added the first version of YAML file for onsemi MAC-PHY
---
.../devicetree/bindings/net/onnn,s2500.yaml | 71 ++++++++++++++++++++++
MAINTAINERS | 6 ++
2 files changed, 77 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/onnn,s2500.yaml b/Documentation/devicetree/bindings/net/onnn,s2500.yaml
new file mode 100644
index 000000000000..fadd656629f1
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/onnn,s2500.yaml
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/onnn,s2500.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: onsemi S2500 10BASE-T1S MACPHY Ethernet Controllers
+
+maintainers:
+ - Selva Rajagopal <Selvamani.Rajagopal@onsemi.com>
+
+description:
+ The S2500 combines a Media Access Controller (MAC) and an
+ Ethernet PHY to enable 10BASE‑T1S networks. The Ethernet Media Access
+ Controller (MAC) module implements a 10 Mbps half duplex Ethernet MAC,
+ compatible with the IEEE 802.3 standard and a 10BASE-T1S physical layer
+ transceiver integrated into the S2500. The communication between
+ the host and the MAC-PHY is specified in the OPEN Alliance 10BASE-T1x
+ MACPHY Serial Interface (TC6).
+
+allOf:
+ - $ref: /schemas/net/ethernet-controller.yaml#
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ const: onnn,s2500
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ description:
+ Interrupt from MAC-PHY asserted in the event of Receive Chunks
+ Available, Transmit Chunk Credits Available and Extended Status
+ Event.
+ maxItems: 1
+
+ reset-gpios:
+ description:
+ GPIO connected to the active-low RST_N pin of the MAC-PHY.
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 25000000
+
+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 = "onnn,s2500";
+ reg = <0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <ð0_pins>;
+ interrupt-parent = <&gpio>;
+ interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
+ spi-max-frequency = <15000000>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 509b35b5a0e7..abf9a8f7ee53 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20440,6 +20440,12 @@ S: Supported
W: http://www.onsemi.com
F: drivers/net/phy/ncn*
+ONSEMI S2500 10BASE-T1S MACPHY ETHERNET DRIVER
+M: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+L: netdev@vger.kernel.org
+S: Supported
+F: Documentation/devicetree/bindings/net/onnn,s2500.yaml
+
OP-TEE DRIVER
M: Jens Wiklander <jenswi@kernel.org>
L: op-tee@lists.trustedfirmware.org (moderated for non-subscribers)
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH net-next v9 02/11] Documentation: networking: Add timestamp related APIs to OA TC6 framework
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
2026-10-09 17:12 ` [PATCH net-next v9 01/11] dt-bindings: net: add onsemi's S2500 Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-10 19:40 ` netdev-bot+sashiko
2026-10-09 17:12 ` [PATCH net-next v9 03/11] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory Selvamani Rajagopal via B4 Relay
` (8 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Added new APIs to support hardware timestamp feature as defined in
OPEN Alliance 10BASE-T1x MAC-PHY serial interface specification.
Added the newer read/write APIs that takes MMS as one of its parameters
as these were not documented so far.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- Added Reviewed tag
changes in v8
- Updated the the order of the parameters in oa_tc6_read_register_mms
and oa_tc6_write_register_mms apis.
- Removed the reference to ioctl
changes in v7
- No change
changes in v6
- No change
changes in v5
- Fixed "no blank line" issues in four places
changes in v4
- Added information for new APIs in OA TC6 framework
changes in v3
- Added more information for documentation
changes in v2
- Removed the changes to the existing API
- Updated information about newly added APIs
changes in v1
- Added changes to an existing API
- Added documentation to new hardware timestamp related APIs
---
Documentation/networking/oa-tc6-framework.rst | 59 +++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst
index 2545ef694016..3cfe8cb7cf75 100644
--- a/Documentation/networking/oa-tc6-framework.rst
+++ b/Documentation/networking/oa-tc6-framework.rst
@@ -153,6 +153,10 @@ OPEN Alliance TC6 Framework
- Forwards the received Ethernet frame from 10Base-T1x MAC-PHY to n/w
subsystem.
+- If supported by the hardware and enabled, updates hardware timestamp
+ in skb, when indicated by one of the three timestamp capture registers
+ through TSC fields of the header.
+
Data Transaction
~~~~~~~~~~~~~~~~
@@ -496,3 +500,58 @@ the MAC-PHY.
Zero align receive frame feature can be enabled to align all receive ethernet
frames data to start at the beginning of any receive data chunk payload with a
start word offset (SWO) of zero.
+
+.. c:function:: int oa_tc6_ptp_register(struct oa_tc6 *tc6, \
+ struct ptp_clock_info *info);
+
+Registers the PTP hardware clock related functions with the kernel.
+This API simply registers. Initialization of the fields in the
+ptp_clock_info structure are left to the vendor as programming hardware
+timer is expected to be vendor dependent. The fields max_adj, owner,
+and all the functions for the clock operations, like adjfine, gettimex64,
+settime64, adjtime are expected to be initialized in the structure before
+calling the registering the hardware clock.
+
+.. c:function:: void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
+
+Unregisters the PTP hardware clock related callbacks.
+
+.. c:function:: int oa_tc6_get_ts_info(struct oa_tc6 *tc6, \
+ struct kernel_ethtool_ts_info *info);
+
+Provides timestamp related settings that are supported to ethtool.
+
+.. c:function:: void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6, \
+ struct kernel_hwtstamp_config *cfg);
+
+Returns hardware timestamp configuration. Part of net_device_ops callbacks.
+
+.. c:function:: void oa_tc6_get_ts_stats(struct oa_tc6 *tc6, \
+ struct ethtool_ts_stats *ts_stats);
+
+Provides hardware timestamp related traffic statistics for ethtool.
+
+.. c:function:: int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6, \
+ struct kernel_hwtstamp_config *cfg);
+
+Helper to set hardware timestamp configuration. Part of net_device_ops
+callbacks.
+
+.. c:function:: void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms);
+
+Helper to map MDIO_MMD_VEND1 command to vendor specific Memory Map Select
+(MMS) value. This function offers flexibility for vendors that may have
+used any MMS value between 10 and 15 as allowed by the specification.
+MDIO_MMD_VEND2 is already mapped to MMS4 in the OA TC6 frame work code.
+
+.. c:function:: int oa_tc6_write_register_mms(struct oa_tc6 *tc6, \
+ u8 mms, u16 address, \
+ u32 value);
+
+Write a single register addressed by @mms and @address in the MAC-PHY.
+
+.. c:function:: int oa_tc6_read_register_mms(struct oa_tc6 *tc6, \
+ u8 mms, u16 address, \
+ u32 *value);
+
+Read a single register addressed by @mms and @address in the MAC-PHY.
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH net-next v9 02/11] Documentation: networking: Add timestamp related APIs to OA TC6 framework
2026-10-09 17:12 ` [PATCH net-next v9 02/11] Documentation: networking: Add timestamp related APIs to OA TC6 framework Selvamani Rajagopal via B4 Relay
@ 2026-10-10 19:40 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:40 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: andrew+netdev, davem, kuba, pabeni, robh, krzk+dt, conor+dt,
parthiban.veerasooran, horms, corbet, skhan, rdunlap,
richardcochran, andrew, hkallweit1, linux, edumazet,
piergiorgio.beruto, netdev, devicetree, linux-kernel, linux-doc,
jerry.ray, ciprian.regus
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Baseline decision: false positive, kept only because
requires_validation=true.
- [Low] The new framework bullet in Documentation/networking/oa-
tc6-framework.rst (lines 156-158) reads: 'If supported by the hardware…
--
Documentation: networking: Add timestamp related APIs to OA TC6 framework
This patch documents new OA TC6 framework APIs for hardware timestamping,
PTP clock registration and the VEND1 MMS mapping helper. It also documents
the existing MMS-based register read/write helpers. A new entry in the
framework feature list describes hardware timestamp updates in the skb.
> diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst
> index 2545ef694016f..3cfe8cb7cf75f 100644
> --- a/Documentation/networking/oa-tc6-framework.rst
> +++ b/Documentation/networking/oa-tc6-framework.rst
> @@ -153,6 +153,10 @@ OPEN Alliance TC6 Framework
> - Forwards the received Ethernet frame from 10Base-T1x MAC-PHY to n/w
> subsystem.
>
> +- If supported by the hardware and enabled, updates hardware timestamp
> + in skb, when indicated by one of the three timestamp capture registers
> + through TSC fields of the header.
[Severity: Low]
Does this bullet only describe the transmit side of timestamping?
This document defines TSC as a field of the transmit data header:
TSC (Bit 7..6) - Timestamp Capture. Request a timestamp capture when the
frame is transmitted onto the network.
Receive timestamps use the RTSA/RTSP footer bits instead, and the timestamp
is added to the start of the frame:
RTSA (Bit 7) - Receive Timestamp Added. This bit is set when a 32-bit or
64-bit timestamp has been added to the beginning of the
received Ethernet frame. ...
By the end of the series the framework handles both paths. On transmit it
sets the TSC field, reads the TTSCx capture registers and calls
skb_tstamp_tx(). On receive in oa_tc6.c it parses RTSA/RTSP and checks the
parity of the 64-bit timestamp at the start of the frame. It then stores
the timestamp in skb_hwtstamps(skb) and strips it with skb_pull().
This bullet comes right after the "Forwards the received Ethernet frame"
item and refers to updating the timestamp in the skb. Could a reader take
this to mean that receive timestamps come from the TSC capture registers?
Would it be clearer to have one entry for transmit (TSC and capture
registers) and another for receive (RTSA/RTSP)?
> +
> Data Transaction
> ~~~~~~~~~~~~~~~~
>
> @@ -496,3 +500,58 @@ the MAC-PHY.
> Zero align receive frame feature can be enabled to align all receive ethernet
> frames data to start at the beginning of any receive data chunk payload with a
> start word offset (SWO) of zero.
> +
> +.. c:function:: int oa_tc6_ptp_register(struct oa_tc6 *tc6, \
> + struct ptp_clock_info *info);
[ ... ]
> +.. c:function:: void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms);
> +
> +Helper to map MDIO_MMD_VEND1 command to vendor specific Memory Map Select
> +(MMS) value. This function offers flexibility for vendors that may have
> +used any MMS value between 10 and 15 as allowed by the specification.
> +MDIO_MMD_VEND2 is already mapped to MMS4 in the OA TC6 frame work code.
[Severity: Low]
This isn't a bug, but at this point in the series none of these APIs
exist yet: oa_tc6_ptp_register(), oa_tc6_ptp_unregister(),
oa_tc6_get_ts_info(), oa_tc6_hwtstamp_get(), oa_tc6_get_ts_stats(),
oa_tc6_hwtstamp_set() and oa_tc6_set_vend1_mms(). Also,
oa_tc6_get_phy_c45_mms() does not map MDIO_MMD_VEND1 yet.
All of them are added later in the series, in "net: ethernet: oa_tc6:
Support for hardware timestamp" and "net: ethernet: oa_tc6: Support for
vendor specific MMS". This only affects someone reading the docs partway
through the series.
Would it make sense to move this patch after those two, so the docs land
together with the code?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d%40onsemi.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v9 03/11] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
2026-10-09 17:12 ` [PATCH net-next v9 01/11] dt-bindings: net: add onsemi's S2500 Selvamani Rajagopal via B4 Relay
2026-10-09 17:12 ` [PATCH net-next v9 02/11] Documentation: networking: Add timestamp related APIs to OA TC6 framework Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-09 18:40 ` Selvamani Rajagopal
2026-10-10 19:40 ` netdev-bot+sashiko
2026-10-09 17:12 ` [PATCH net-next v9 04/11] net: ethernet: oa_tc6: Move constant definitions to header file Selvamani Rajagopal via B4 Relay
` (7 subsequent siblings)
10 siblings, 2 replies; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Moving oa_tc6.c to its own directory, drivers/net/ethernet/oa_tc6. This
will facilitate adding more files to support other features
defined by OPEN Alliance 10BASE-T1x Serial Interface specification
This patch series is adding two files, one for hardware
timestamp related functions and one for PTP related APIs.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- Changed MAINTAINERS to add "all files" under oa_tc6
- Added the missing "if COMPILE_TEST"
- Added Reviewed by tag
changes in v8
- No change
changes in v7
- No change
changes in v6
- No change
changes in v5
- No change
changes in v4
- Removed reference to onsemi in Kconfig files
changes in v3
- Moved oa_tc6.c to its own, oa_tc6 directory under ethernet.
- First patch
---
MAINTAINERS | 2 +-
drivers/net/ethernet/Kconfig | 12 +-----------
drivers/net/ethernet/Makefile | 2 +-
drivers/net/ethernet/oa_tc6/Kconfig | 16 ++++++++++++++++
drivers/net/ethernet/oa_tc6/Makefile | 7 +++++++
drivers/net/ethernet/{ => oa_tc6}/oa_tc6.c | 0
6 files changed, 26 insertions(+), 13 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index abf9a8f7ee53..6c1deaf5643b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20470,7 +20470,7 @@ M: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/networking/oa-tc6-framework.rst
-F: drivers/net/ethernet/oa_tc6.c
+F: drivers/net/ethernet/oa_tc6/*
F: include/linux/oa_tc6.h
OPEN FIRMWARE AND FLATTENED DEVICE TREE
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index c2b0161d0bec..5758e0fbbca5 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -135,6 +135,7 @@ source "drivers/net/ethernet/netronome/Kconfig"
source "drivers/net/ethernet/8390/Kconfig"
source "drivers/net/ethernet/nvidia/Kconfig"
source "drivers/net/ethernet/nxp/Kconfig"
+source "drivers/net/ethernet/oa_tc6/Kconfig"
source "drivers/net/ethernet/oki-semi/Kconfig"
config ETHOC
@@ -147,17 +148,6 @@ config ETHOC
help
Say Y here if you want to use the OpenCores 10/100 Mbps Ethernet MAC.
-config OA_TC6
- tristate "OPEN Alliance TC6 10BASE-T1x MAC-PHY support" if COMPILE_TEST
- depends on SPI
- select PHYLIB
- help
- This library implements OPEN Alliance TC6 10BASE-T1x MAC-PHY
- Serial Interface protocol for supporting 10BASE-T1x MAC-PHYs.
-
- To know the implementation details, refer documentation in
- <file:Documentation/networking/oa-tc6-framework.rst>.
-
source "drivers/net/ethernet/pasemi/Kconfig"
source "drivers/net/ethernet/pensando/Kconfig"
source "drivers/net/ethernet/qlogic/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index a306dae23bcb..e0e86c249e42 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_NET_VENDOR_NETRONOME) += netronome/
obj-$(CONFIG_NET_VENDOR_NI) += ni/
obj-$(CONFIG_NET_VENDOR_NVIDIA) += nvidia/
obj-$(CONFIG_LPC_ENET) += nxp/
+obj-$(CONFIG_OA_TC6) += oa_tc6/
obj-$(CONFIG_NET_VENDOR_OKI) += oki-semi/
obj-$(CONFIG_ETHOC) += ethoc.o
obj-$(CONFIG_NET_VENDOR_PASEMI) += pasemi/
@@ -105,5 +106,4 @@ obj-$(CONFIG_NET_VENDOR_XILINX) += xilinx/
obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/
obj-$(CONFIG_NET_VENDOR_SYNOPSYS) += synopsys/
obj-$(CONFIG_NET_VENDOR_PENSANDO) += pensando/
-obj-$(CONFIG_OA_TC6) += oa_tc6.o
obj-$(CONFIG_NET_VENDOR_ZTE) += zte/
diff --git a/drivers/net/ethernet/oa_tc6/Kconfig b/drivers/net/ethernet/oa_tc6/Kconfig
new file mode 100644
index 000000000000..657567276030
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# OA TC6 10BASE-T1x MAC-PHY configuration
+#
+
+config OA_TC6
+ tristate "OPEN Alliance TC6 10BASE-T1x MAC-PHY support" if COMPILE_TEST
+ depends on SPI
+ select PHYLIB
+ help
+ This library implements OPEN Alliance TC6 10BASE-T1x MAC-PHY
+ Serial Interface protocol for supporting 10BASE-T1x MAC-PHYs.
+
+ To know the implementation details, refer documentation in
+ <file:Documentation/networking/oa-tc6-framework.rst>.
+
diff --git a/drivers/net/ethernet/oa_tc6/Makefile b/drivers/net/ethernet/oa_tc6/Makefile
new file mode 100644
index 000000000000..f24aae852ef2
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for OA TC6 10BASE-T1x MAC-PHY
+#
+
+obj-$(CONFIG_OA_TC6) := oa_tc6_mod.o
+oa_tc6_mod-objs := oa_tc6.o
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
similarity index 100%
rename from drivers/net/ethernet/oa_tc6.c
rename to drivers/net/ethernet/oa_tc6/oa_tc6.c
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* RE: [PATCH net-next v9 03/11] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory
2026-10-09 17:12 ` [PATCH net-next v9 03/11] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory Selvamani Rajagopal via B4 Relay
@ 2026-10-09 18:40 ` Selvamani Rajagopal
2026-10-10 19:40 ` netdev-bot+sashiko
1 sibling, 0 replies; 25+ messages in thread
From: Selvamani Rajagopal @ 2026-10-09 18:40 UTC (permalink / raw)
To: Selvamani Rajagopal, Andrew Lunn, David S. Miller,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Parthiban Veerasooran, Simon Horman,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Richard Cochran,
Andrew Lunn, Heiner Kallweit, Russell King, Eric Dumazet,
Piergiorgio Beruto
Cc: netdev, devicetree, linux-kernel, linux-doc, Jerry Ray, Ciprian Regus
Andrew,
Just want to mention that there were couple of minor changes to this patch after you gave "reviewed-by".
> -----Original Message-----
> From: Selvamani Rajagopal via B4 Relay
> Subject: [PATCH net-next v9 03/11] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory
>
> Reviewed-by: Andrew Lunn <andrew@lunn.ch>
> Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
> changes in v9
> - Changed MAINTAINERS to add "all files" under oa_tc6
> - Added the missing "if COMPILE_TEST"
> - Added Reviewed by tag
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v9 03/11] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory
2026-10-09 17:12 ` [PATCH net-next v9 03/11] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory Selvamani Rajagopal via B4 Relay
2026-10-09 18:40 ` Selvamani Rajagopal
@ 2026-10-10 19:40 ` netdev-bot+sashiko
1 sibling, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:40 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: andrew+netdev, davem, kuba, pabeni, robh, krzk+dt, conor+dt,
parthiban.veerasooran, horms, corbet, skhan, rdunlap,
richardcochran, andrew, hkallweit1, linux, edumazet,
piergiorgio.beruto, netdev, devicetree, linux-kernel, linux-doc,
jerry.ray, ciprian.regus
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The commit message presents this as a pure file move ("Move
oa_tc6.c to its own directory", similarity index 100%), but the patch…
--
net: ethernet: oa_tc6: Move oa_tc6.c to its own directory
This patch moves drivers/net/ethernet/oa_tc6.c into a new
drivers/net/ethernet/oa_tc6/ directory, which gets its own Kconfig and
Makefile. It prepares for the hardware timestamping and PTP files that
come later in the series.
> diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
> index a306dae23bcb8..e0e86c249e425 100644
> --- a/drivers/net/ethernet/Makefile
> +++ b/drivers/net/ethernet/Makefile
[ ... ]
> @@ -105,5 +106,4 @@ obj-$(CONFIG_NET_VENDOR_XILINX) += xilinx/
> obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/
> obj-$(CONFIG_NET_VENDOR_SYNOPSYS) += synopsys/
> obj-$(CONFIG_NET_VENDOR_PENSANDO) += pensando/
> -obj-$(CONFIG_OA_TC6) += oa_tc6.o
> obj-$(CONFIG_NET_VENDOR_ZTE) += zte/
[ ... ]
> diff --git a/drivers/net/ethernet/oa_tc6/Makefile b/drivers/net/ethernet/oa_tc6/Makefile
> new file mode 100644
> index 0000000000000..f24aae852ef27
> --- /dev/null
> +++ b/drivers/net/ethernet/oa_tc6/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Makefile for OA TC6 10BASE-T1x MAC-PHY
> +#
> +
> +obj-$(CONFIG_OA_TC6) := oa_tc6_mod.o
> +oa_tc6_mod-objs := oa_tc6.o
[Severity: Medium]
Does this change the name of the module? Before this patch, the parent
Makefile had:
obj-$(CONFIG_OA_TC6) += oa_tc6.o
That built oa_tc6.ko. With the composite target here, CONFIG_OA_TC6=m
builds oa_tc6_mod.ko instead, and KBUILD_MODNAME becomes "oa_tc6_mod".
The subject says "Move oa_tc6.c to its own directory", and the file
rename shows similarity index 100%. Neither the commit message nor the
changelog mentions the module rename, and nothing else in the tree uses
the new name.
Anything that refers to the module by name would stop matching. That
includes "modprobe oa_tc6" and "rmmod oa_tc6", modprobe.d blacklist or
options entries, /sys/module/oa_tc6, and module lists in initramfs or
packaging. Dependent drivers like lan865x still load it automatically,
because depmod resolves the dependency through exported symbols.
The new name also stays in place at the end of the series:
oa_tc6_mod-objs := oa_tc6.o oa_tc6_ptp.o oa_tc6_tstamp.o
Is the rename needed? kbuild only rejects a composite module that has the
same name as one of its own objects.
At this commit there is only one object, so a plain
"obj-$(CONFIG_OA_TC6) += oa_tc6.o" in the subdirectory would keep
oa_tc6.ko.
After the other files are added, renaming the source file would also keep
the old module name. For example, oa_tc6.c could become oa_tc6_core.c,
with:
oa_tc6-y := oa_tc6_core.o oa_tc6_ptp.o oa_tc6_tstamp.o
If the rename is intended, could the commit message say so?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d%40onsemi.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v9 04/11] net: ethernet: oa_tc6: Move constant definitions to header file
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
` (2 preceding siblings ...)
2026-10-09 17:12 ` [PATCH net-next v9 03/11] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-10 19:40 ` netdev-bot+sashiko
2026-10-09 17:12 ` [PATCH net-next v9 05/11] net: ethernet: oa_tc6: Support for hardware timestamp Selvamani Rajagopal via B4 Relay
` (6 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
To help other source files within the module share the
constant definitions, some of the definitions from oa_tc6.c
are moved to a header file. This header file is still
private to OA TC6 framework.
oa_tc6 structure is also moved to this header file as it is
needed for other source files that are part of OA TC6
framework.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- Added Reviewed tag
changes in v8:
- Merged with latest base version's changes
- Added Reviewed tag
changes in v7
- No change
changes in v6
- No change
changes in v5
- No change
changes in v4
- Added MMS values 1 and 12, which are used now
changes in v3
- Moved constant definitions from the source to newly created
header file for other sources in the directory to share.
- Standard specific defines are moved to Linux common header file
- First patch
---
drivers/net/ethernet/oa_tc6/oa_tc6.c | 104 +---------------------
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 125 +++++++++++++++++++++++++++
2 files changed, 126 insertions(+), 103 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index 364027c39fa4..f8e9309a72f1 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -14,109 +14,7 @@
#include <linux/phy.h>
#include <linux/oa_tc6.h>
-/* Control command header */
-#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
-#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
-#define OA_TC6_CTRL_HEADER_MEM_MAP_SELECTOR GENMASK(27, 24)
-#define OA_TC6_CTRL_HEADER_ADDR GENMASK(23, 8)
-#define OA_TC6_CTRL_HEADER_LENGTH GENMASK(7, 1)
-#define OA_TC6_CTRL_HEADER_PARITY BIT(0)
-
-/* Data header */
-#define OA_TC6_DATA_HEADER_DATA_NOT_CTRL BIT(31)
-#define OA_TC6_DATA_HEADER_DATA_VALID BIT(21)
-#define OA_TC6_DATA_HEADER_START_VALID BIT(20)
-#define OA_TC6_DATA_HEADER_START_WORD_OFFSET GENMASK(19, 16)
-#define OA_TC6_DATA_HEADER_END_VALID BIT(14)
-#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET GENMASK(13, 8)
-#define OA_TC6_DATA_HEADER_PARITY BIT(0)
-
-/* Data footer */
-#define OA_TC6_DATA_FOOTER_EXTENDED_STS BIT(31)
-#define OA_TC6_DATA_FOOTER_RXD_HEADER_BAD BIT(30)
-#define OA_TC6_DATA_FOOTER_CONFIG_SYNC BIT(29)
-#define OA_TC6_DATA_FOOTER_RX_CHUNKS GENMASK(28, 24)
-#define OA_TC6_DATA_FOOTER_DATA_VALID BIT(21)
-#define OA_TC6_DATA_FOOTER_START_VALID BIT(20)
-#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET GENMASK(19, 16)
-#define OA_TC6_DATA_FOOTER_END_VALID BIT(14)
-#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8)
-#define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
-
-#define OA_TC6_CTRL_PROT_REPLY_SIZE 4
-#define OA_TC6_CTRL_HEADER_SIZE 4
-#define OA_TC6_CTRL_REG_VALUE_SIZE 4
-#define OA_TC6_CTRL_IGNORED_SIZE 4
-#define OA_TC6_CTRL_MAX_REGISTERS 128
-#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\
- (OA_TC6_CTRL_MAX_REGISTERS *\
- (OA_TC6_CTRL_REG_VALUE_SIZE +\
- OA_TC6_CTRL_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 +\
- OA_TC6_CHUNK_PAYLOAD_SIZE)
-#define OA_TC6_MAX_TX_CHUNKS 48
-#define OA_TC6_SPI_DATA_BUF_SIZE (OA_TC6_MAX_TX_CHUNKS *\
- OA_TC6_CHUNK_SIZE)
-#define STATUS0_RESETC_POLL_DELAY 1000
-#define STATUS0_RESETC_POLL_TIMEOUT 1000000
-
-#define OA_TC6_REG_MMS_MASK GENMASK(19, 16)
-
-/* Internal structure for MAC-PHY drivers */
-struct oa_tc6 {
- struct net_device *netdev;
- struct phy_device *phydev;
- struct mii_bus *mdiobus;
- struct spi_device *spi;
- struct mutex spi_ctrl_lock; /* Protects spi control transfer */
- spinlock_t tx_skb_lock; /* Protects tx skb handling */
- void *spi_ctrl_tx_buf;
- void *spi_ctrl_rx_buf;
- void *spi_data_tx_buf;
- void *spi_data_rx_buf;
- struct sk_buff *ongoing_tx_skb;
- struct sk_buff *waiting_tx_skb;
- struct sk_buff *rx_skb;
- u16 tx_skb_offset;
- u16 spi_data_tx_buf_offset;
- u16 tx_credits;
- u8 rx_chunks_available;
- bool wait_until_start_valid;
- bool int_flag;
- bool disable_traffic;
- bool prot_ctrl;
- enum oa_tc6_quirk_flag quirk_flags;
- struct gpio_desc *reset_gpio;
-};
-
-enum oa_tc6_header_type {
- OA_TC6_CTRL_HEADER,
- OA_TC6_DATA_HEADER,
-};
-
-enum oa_tc6_register_op {
- OA_TC6_CTRL_REG_READ = 0,
- OA_TC6_CTRL_REG_WRITE = 1,
-};
-
-enum oa_tc6_data_valid_info {
- OA_TC6_DATA_INVALID,
- OA_TC6_DATA_VALID,
-};
-
-enum oa_tc6_data_start_valid_info {
- OA_TC6_DATA_START_INVALID,
- OA_TC6_DATA_START_VALID,
-};
-
-enum oa_tc6_data_end_valid_info {
- OA_TC6_DATA_END_INVALID,
- OA_TC6_DATA_END_VALID,
-};
+#include "oa_tc6_std_def.h"
static int oa_tc6_spi_transfer(struct oa_tc6 *tc6,
enum oa_tc6_header_type header_type, u16 length)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
new file mode 100644
index 000000000000..403a9c22b5f1
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Register and driver related definitions to support
+ * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework.
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#ifndef OA_TC6_STD_DEF_H
+#define OA_TC6_STD_DEF_H
+
+#include <linux/ptp_clock_kernel.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/spi/spi.h>
+#include <linux/skbuff.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/phy.h>
+
+/* Control command header */
+#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
+#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
+#define OA_TC6_CTRL_HEADER_MEM_MAP_SELECTOR GENMASK(27, 24)
+#define OA_TC6_CTRL_HEADER_ADDR GENMASK(23, 8)
+#define OA_TC6_CTRL_HEADER_LENGTH GENMASK(7, 1)
+#define OA_TC6_CTRL_HEADER_PARITY BIT(0)
+
+/* Data header */
+#define OA_TC6_DATA_HEADER_DATA_NOT_CTRL BIT(31)
+#define OA_TC6_DATA_HEADER_DATA_VALID BIT(21)
+#define OA_TC6_DATA_HEADER_START_VALID BIT(20)
+#define OA_TC6_DATA_HEADER_START_WORD_OFFSET GENMASK(19, 16)
+#define OA_TC6_DATA_HEADER_END_VALID BIT(14)
+#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET GENMASK(13, 8)
+#define OA_TC6_DATA_HEADER_PARITY BIT(0)
+
+/* Data footer */
+#define OA_TC6_DATA_FOOTER_EXTENDED_STS BIT(31)
+#define OA_TC6_DATA_FOOTER_RXD_HEADER_BAD BIT(30)
+#define OA_TC6_DATA_FOOTER_CONFIG_SYNC BIT(29)
+#define OA_TC6_DATA_FOOTER_RX_CHUNKS GENMASK(28, 24)
+#define OA_TC6_DATA_FOOTER_DATA_VALID BIT(21)
+#define OA_TC6_DATA_FOOTER_START_VALID BIT(20)
+#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET GENMASK(19, 16)
+#define OA_TC6_DATA_FOOTER_END_VALID BIT(14)
+#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8)
+#define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
+
+#define OA_TC6_CTRL_PROT_REPLY_SIZE 4
+#define OA_TC6_CTRL_HEADER_SIZE 4
+#define OA_TC6_CTRL_REG_VALUE_SIZE 4
+#define OA_TC6_CTRL_IGNORED_SIZE 4
+#define OA_TC6_CTRL_MAX_REGISTERS 128
+#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\
+ (OA_TC6_CTRL_MAX_REGISTERS *\
+ (OA_TC6_CTRL_REG_VALUE_SIZE +\
+ OA_TC6_CTRL_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 +\
+ OA_TC6_CHUNK_PAYLOAD_SIZE)
+#define OA_TC6_MAX_TX_CHUNKS 48
+#define OA_TC6_SPI_DATA_BUF_SIZE (OA_TC6_MAX_TX_CHUNKS *\
+ OA_TC6_CHUNK_SIZE)
+#define STATUS0_RESETC_POLL_DELAY 1000
+#define STATUS0_RESETC_POLL_TIMEOUT 1000000
+
+#define OA_TC6_REG_MMS_MASK GENMASK(19, 16)
+
+/* Internal structure for MAC-PHY drivers */
+struct oa_tc6 {
+ struct net_device *netdev;
+ struct phy_device *phydev;
+ struct mii_bus *mdiobus;
+ struct spi_device *spi;
+ struct mutex spi_ctrl_lock; /* Protects spi control transfer */
+ spinlock_t tx_skb_lock; /* Protects tx skb handling */
+ void *spi_ctrl_tx_buf;
+ void *spi_ctrl_rx_buf;
+ void *spi_data_tx_buf;
+ void *spi_data_rx_buf;
+ struct sk_buff *ongoing_tx_skb;
+ struct sk_buff *waiting_tx_skb;
+ struct sk_buff *rx_skb;
+ u16 tx_skb_offset;
+ u16 spi_data_tx_buf_offset;
+ u16 tx_credits;
+ u8 rx_chunks_available;
+ bool wait_until_start_valid;
+ bool int_flag;
+ bool disable_traffic;
+ bool prot_ctrl;
+ enum oa_tc6_quirk_flag quirk_flags;
+ struct gpio_desc *reset_gpio;
+};
+
+enum oa_tc6_header_type {
+ OA_TC6_CTRL_HEADER,
+ OA_TC6_DATA_HEADER,
+};
+
+enum oa_tc6_register_op {
+ OA_TC6_CTRL_REG_READ = 0,
+ OA_TC6_CTRL_REG_WRITE = 1,
+};
+
+enum oa_tc6_data_valid_info {
+ OA_TC6_DATA_INVALID,
+ OA_TC6_DATA_VALID,
+};
+
+enum oa_tc6_data_start_valid_info {
+ OA_TC6_DATA_START_INVALID,
+ OA_TC6_DATA_START_VALID,
+};
+
+enum oa_tc6_data_end_valid_info {
+ OA_TC6_DATA_END_INVALID,
+ OA_TC6_DATA_END_VALID,
+};
+#endif /* OA_TC6_STD_DEF_H */
+
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH net-next v9 04/11] net: ethernet: oa_tc6: Move constant definitions to header file
2026-10-09 17:12 ` [PATCH net-next v9 04/11] net: ethernet: oa_tc6: Move constant definitions to header file Selvamani Rajagopal via B4 Relay
@ 2026-10-10 19:40 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:40 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: andrew+netdev, davem, kuba, pabeni, robh, krzk+dt, conor+dt,
parthiban.veerasooran, horms, corbet, skhan, rdunlap,
richardcochran, andrew, hkallweit1, linux, edumazet,
piergiorgio.beruto, netdev, devicetree, linux-kernel, linux-doc,
jerry.ray, ciprian.regus
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The new private header
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h doesn't compile on its…
--
net: ethernet: oa_tc6: Move constant definitions to header file
This patch moves the control/data header definitions, size constants,
enums and struct oa_tc6 out of oa_tc6.c into a new private header,
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h. Other source files in the
OA TC6 framework can then share them.
> diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
> new file mode 100644
> index 0000000000000..403a9c22b5f14
> --- /dev/null
> +++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
> @@ -0,0 +1,125 @@
[ ... ]
> +#ifndef OA_TC6_STD_DEF_H
> +#define OA_TC6_STD_DEF_H
> +
> +#include <linux/ptp_clock_kernel.h>
> +#include <linux/net_tstamp.h>
> +#include <linux/netdevice.h>
> +#include <linux/spi/spi.h>
> +#include <linux/skbuff.h>
> +#include <linux/sched.h>
> +#include <linux/wait.h>
> +#include <linux/phy.h>
[ ... ]
> +/* Internal structure for MAC-PHY drivers */
> +struct oa_tc6 {
[ ... ]
> + bool prot_ctrl;
> + enum oa_tc6_quirk_flag quirk_flags;
> + struct gpio_desc *reset_gpio;
> +};
[Severity: Low]
This isn't a bug, but is this header meant to be self-contained?
struct oa_tc6 has quirk_flags as a by-value member of type
enum oa_tc6_quirk_flag. That enum is defined only in
include/linux/oa_tc6.h:
include/linux/oa_tc6.h:
enum oa_tc6_quirk_flag {
OA_TC6_BROKEN_PHY = BIT(0),
};
oa_tc6_std_def.h doesn't include <linux/oa_tc6.h>. It compiles today
only because oa_tc6.c includes <linux/oa_tc6.h> right before it:
#include <linux/oa_tc6.h>
#include "oa_tc6_std_def.h"
Any new file that includes "oa_tc6_std_def.h" first would fail to build.
At this commit include/linux/oa_tc6.h has no include guard, so adding
the include here would also need a guard there. The later patch "net:
ethernet: oa_tc6: Support for hardware timestamp" adds _LINUX_OA_TC6_H.
Could this patch add the guard and include <linux/oa_tc6.h> directly
from oa_tc6_std_def.h?
A smaller point: nothing in this patch seems to use
<linux/ptp_clock_kernel.h>, <linux/net_tstamp.h>, <linux/sched.h> or
<linux/wait.h>. They look like they are first needed by the later
hardware timestamp patch.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d%40onsemi.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v9 05/11] net: ethernet: oa_tc6: Support for hardware timestamp
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
` (3 preceding siblings ...)
2026-10-09 17:12 ` [PATCH net-next v9 04/11] net: ethernet: oa_tc6: Move constant definitions to header file Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-10 19:40 ` netdev-bot+sashiko
2026-10-09 17:12 ` [PATCH net-next v9 06/11] net: ethernet: oa_tc6: Support for vendor specific MMS Selvamani Rajagopal via B4 Relay
` (5 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
PTP register/unregister calls are implemented in oa_tc6_ptp.c.
The APIs that work with the hardware for timestamp is provided
by vendor code as it may be vendor dependent.
Interface for ndo_hwtstamp_set/get, control and status
callback for ethtool are provided to support hardware timestamp
feature. ioctl interface passes the request to PHY.
Hardware timestamp functions that handle the traffic are generally
placed in oa_tc6.c. Rest of the timestamp functionalities, mostly
helper functions are in oa_tc6_tstamp.c.
When all the three timestamp capture registers are busy and not
available, driver drops the incoming TX SKB, if it comes with the
request for hardware timestamping. Normal traffic without timestamp
request is not affected.
Practically, application using hardware timestamp is not expected to
hit this scenario as three timestamp capture registers are plenty
considering the amount of traffic expected in such applications.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- Moved the vend1_mms initialization to the correct patch
- Fixed the confusing goto statements to return true or false
- Changed the magic number offset to index into array to names.
- Checking valid tsc instead of relying on SKBTX_IN_PROGRESS
as life of this flag may not be 100% internal to the driver
changes in v8
- Moved few timestamp defintions to header file from source file.
- Improved logic in handling timestamp capture register events.
- timestamp skb queue is protected inside spinlock
- Removed unnecessary ioctl callback
changes in v7
- Fixed the parameter name mismatch in function protoype and
definition
- CONFIG0 is set with 64 bit timestamp support by default.
- Added information about return value for API documentation.
changes in v6
- Fixed the issue of function parameter in oa_tc6_get_ts_stats
not described in comments section for documentation.
- Avoided typecasting __be32 as u32
changes in v5
- As subtracting skb len by FCS size is considered bug, changes
are removed. Will be fixed in stable branch (net repo)
changes in v4
- Fixed the condition check for subtracting the FCS size
from skb len.
changes in v3
- Replaced warning printk with ratelimited printk
- Checking the hardware register before enabling hardware
timestamp
changes in v1
- Added hardware timestamp support to the OA TC6 framework.
---
MAINTAINERS | 1 +
drivers/net/ethernet/adi/Kconfig | 1 +
drivers/net/ethernet/microchip/lan865x/Kconfig | 1 +
drivers/net/ethernet/oa_tc6/Kconfig | 1 +
drivers/net/ethernet/oa_tc6/Makefile | 2 +-
drivers/net/ethernet/oa_tc6/oa_tc6.c | 312 +++++++++++++++++++++++--
drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c | 65 ++++++
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 46 ++++
drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c | 224 ++++++++++++++++++
include/linux/oa_tc6.h | 23 ++
10 files changed, 659 insertions(+), 17 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 6c1deaf5643b..1d1e0563b922 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20467,6 +20467,7 @@ F: drivers/rtc/rtc-optee.c
OPEN ALLIANCE 10BASE-T1S MACPHY SERIAL INTERFACE FRAMEWORK
M: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
+M: Selva Rajagopal <selvamani.rajagopal@onsemi.com> (timestamp support)
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/networking/oa-tc6-framework.rst
diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kconfig
index bdb8ff7d15da..120a52e6b283 100644
--- a/drivers/net/ethernet/adi/Kconfig
+++ b/drivers/net/ethernet/adi/Kconfig
@@ -29,6 +29,7 @@ config ADIN1110
config ADIN1140
tristate "Analog Devices ADIN1140 MAC-PHY"
depends on SPI
+ depends on PTP_1588_CLOCK_OPTIONAL
select ADIN1140_PHY
select OA_TC6
help
diff --git a/drivers/net/ethernet/microchip/lan865x/Kconfig b/drivers/net/ethernet/microchip/lan865x/Kconfig
index 7f2a4e7e1915..342172cb8837 100644
--- a/drivers/net/ethernet/microchip/lan865x/Kconfig
+++ b/drivers/net/ethernet/microchip/lan865x/Kconfig
@@ -8,6 +8,7 @@ if NET_VENDOR_MICROCHIP
config LAN865X
tristate "LAN865x support"
depends on SPI
+ depends on PTP_1588_CLOCK_OPTIONAL
select OA_TC6
help
Support for the Microchip LAN8650/1 Rev.B0/B1 MACPHY Ethernet chip. It
diff --git a/drivers/net/ethernet/oa_tc6/Kconfig b/drivers/net/ethernet/oa_tc6/Kconfig
index 657567276030..2c8c881d5b99 100644
--- a/drivers/net/ethernet/oa_tc6/Kconfig
+++ b/drivers/net/ethernet/oa_tc6/Kconfig
@@ -6,6 +6,7 @@
config OA_TC6
tristate "OPEN Alliance TC6 10BASE-T1x MAC-PHY support" if COMPILE_TEST
depends on SPI
+ depends on PTP_1588_CLOCK_OPTIONAL
select PHYLIB
help
This library implements OPEN Alliance TC6 10BASE-T1x MAC-PHY
diff --git a/drivers/net/ethernet/oa_tc6/Makefile b/drivers/net/ethernet/oa_tc6/Makefile
index f24aae852ef2..964f668efc2d 100644
--- a/drivers/net/ethernet/oa_tc6/Makefile
+++ b/drivers/net/ethernet/oa_tc6/Makefile
@@ -4,4 +4,4 @@
#
obj-$(CONFIG_OA_TC6) := oa_tc6_mod.o
-oa_tc6_mod-objs := oa_tc6.o
+oa_tc6_mod-objs := oa_tc6.o oa_tc6_ptp.o oa_tc6_tstamp.o
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index f8e9309a72f1..0b6e216de8a1 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -8,6 +8,7 @@
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
+#include <linux/bitops.h>
#include <linux/iopoll.h>
#include <linux/interrupt.h>
#include <linux/mdio.h>
@@ -16,6 +17,16 @@
#include "oa_tc6_std_def.h"
+struct oa_tc6_ts_info_rx {
+ bool rtsa;
+ bool rtsp;
+};
+
+struct oa_tc6_ts_info_tx {
+ ktime_t hwtstamp;
+ u8 tsc;
+};
+
static int oa_tc6_spi_transfer(struct oa_tc6 *tc6,
enum oa_tc6_header_type header_type, u16 length)
{
@@ -50,6 +61,193 @@ static int oa_tc6_get_parity(u32 p)
return !((p >> 28) & 1);
}
+static struct oa_tc6_ts_info_tx *oa_tc6_tsinfo_tx(struct sk_buff *skb)
+{
+ return (struct oa_tc6_ts_info_tx *)(skb->cb);
+}
+
+static struct oa_tc6_ts_info_rx *oa_tc6_tsinfo_rx(struct sk_buff *skb)
+{
+ return (struct oa_tc6_ts_info_rx *)(skb->cb);
+}
+
+static u8 oa_tc6_next_tsc(u8 tsc)
+{
+ return tsc < OA_TC6_TTSCC_REG_ID ? tsc + 1 : OA_TC6_TTSCA_REG_ID;
+}
+
+static bool oa_tc6_req_tx_hwtstamp(struct oa_tc6 *tc6, struct sk_buff *skb)
+{
+ u8 tsc;
+ u8 i;
+
+ lockdep_assert_held(&tc6->tx_skb_lock);
+
+ if (!skb || !(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP))
+ return false;
+
+ if (!tc6->hw_tstamp_enabled)
+ return false;
+
+ if (tc6->ts_config.tx_type != HWTSTAMP_TX_ON) {
+ tc6->tx_hwtstamp_lost++;
+ return false;
+ }
+
+ tsc = tc6->ttsc_current_id;
+ for (i = 0; i < OA_TC6_TTSCC_REG_ID; i++) {
+ if (!(tc6->ts_ttsc_pending &
+ BIT(tsc - OA_TC6_TTSCA_REG_ID)))
+ goto slot_found;
+ tsc = oa_tc6_next_tsc(tsc);
+ }
+
+ /* All the three slots are waiting for its event. This skb
+ * can't request timestamp. Marking it to be dropped.
+ */
+ tc6->tx_hwtstamp_lost++;
+ return true;
+
+slot_found:
+ tc6->ts_ttsc_pending |= BIT(tsc - OA_TC6_TTSCA_REG_ID);
+ tc6->ttsc_current_id = oa_tc6_next_tsc(tsc);
+ oa_tc6_tsinfo_tx(skb)->tsc = tsc;
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ return false;
+}
+
+static void oa_tc6_events_handle(struct oa_tc6 *tc6, u32 val, const u32 *ts)
+{
+ struct skb_shared_hwtstamps tstamp;
+ struct oa_tc6_ts_info_tx *ski;
+ struct sk_buff *skb, *tmp;
+ LIST_HEAD(deliver);
+ LIST_HEAD(drop);
+ u8 tsc, bit_num;
+ u32 handled = 0;
+ u32 event;
+
+ spin_lock_bh(&tc6->tx_skb_lock);
+ list_for_each_entry_safe(skb, tmp, &tc6->tx_ts_skb_q, list) {
+ ski = oa_tc6_tsinfo_tx(skb);
+ tsc = ski->tsc;
+ bit_num = tsc - OA_TC6_TTSCA_REG_ID;
+ event = OA_TC6_STATUS0_TTSCAA << bit_num;
+ if (!(val & event))
+ continue;
+ if (handled & event) {
+ dev_warn_ratelimited(&tc6->spi->dev,
+ "Multiple skbs\n");
+ tc6->tx_hwtstamp_err++;
+ list_move_tail(&skb->list, &drop);
+ continue;
+ }
+
+ handled |= event;
+ tc6->ts_ttsc_pending &= ~BIT(bit_num);
+
+ ski->hwtstamp = ktime_set(ts[2 * bit_num],
+ ts[2 * bit_num + 1]);
+ tc6->tx_hwtstamp_pkts++;
+ list_move_tail(&skb->list, &deliver);
+ }
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ /* skb_tstamp_tx() clones the skb and wakes the owning socket, so keep
+ * it out of the lock that oa_tc6_start_xmit() takes.
+ */
+ list_for_each_entry_safe(skb, tmp, &deliver, list) {
+ list_del(&skb->list);
+ memset(&tstamp, 0, sizeof(tstamp));
+ tstamp.hwtstamp = oa_tc6_tsinfo_tx(skb)->hwtstamp;
+ skb_tstamp_tx(skb, &tstamp);
+ dev_kfree_skb(skb);
+ }
+
+ list_for_each_entry_safe(skb, tmp, &drop, list) {
+ list_del(&skb->list);
+ dev_kfree_skb(skb);
+ }
+
+ if (val & ~handled) {
+ tc6->tx_hwtstamp_err += hweight32(val & ~handled);
+ dev_warn_ratelimited(&tc6->spi->dev,
+ "Unexpected event: 0x%x\n",
+ val & ~handled);
+ }
+}
+
+void oa_tc6_cleanup_tx_tstamp_skbs(struct oa_tc6 *tc6)
+{
+ struct sk_buff *skb, *tmp;
+
+ spin_lock_bh(&tc6->tx_skb_lock);
+ list_for_each_entry_safe(skb, tmp, &tc6->tx_ts_skb_q, list) {
+ list_del(&skb->list);
+ tc6->ts_ttsc_pending &= ~BIT(oa_tc6_tsinfo_tx(skb)->tsc -
+ OA_TC6_TTSCA_REG_ID);
+ dev_kfree_skb(skb);
+
+ /* The frame was transmitted, only its timestamp is lost. */
+ tc6->tx_hwtstamp_lost++;
+ }
+ spin_unlock_bh(&tc6->tx_skb_lock);
+}
+
+static int oa_tc6_update_ts_in_rx_skb(struct oa_tc6 *tc6)
+{
+ unsigned int expected_len = ETH_HLEN;
+ struct sk_buff *skb = tc6->rx_skb;
+ struct oa_tc6_ts_info_rx *ski;
+ __be32 ts_val[2];
+ u32 ts[2];
+
+ ski = oa_tc6_tsinfo_rx(skb);
+ if (ski->rtsa)
+ expected_len += OA_TC6_TSTAMP_SZ;
+
+ if (skb->len < expected_len) {
+ tc6->netdev->stats.rx_length_errors++;
+ return -EINVAL;
+ }
+
+ if (!ski->rtsa)
+ return 0;
+
+ memcpy(&ts_val[0], skb->data, 4);
+ memcpy(&ts_val[1], (u32 *)skb->data + 1, 4);
+
+ ts[0] = be32_to_cpu(ts_val[0]);
+ ts[1] = be32_to_cpu(ts_val[1]);
+
+ /* Check parity */
+ if ((oa_tc6_get_parity(ts[0]) ^ oa_tc6_get_parity(ts[1])) ==
+ !ski->rtsp) {
+ struct skb_shared_hwtstamps *hw_ts;
+
+ /* Report timestamp to the upper layers */
+ hw_ts = skb_hwtstamps(skb);
+ memset(hw_ts, 0, sizeof(*hw_ts));
+ hw_ts->hwtstamp = ktime_set(ts[0], ts[1]);
+ }
+ skb_pull(skb, sizeof(ts));
+
+ return 0;
+}
+
+static int oa_tc6_update_standard_capability(struct oa_tc6 *tc6)
+{
+ u32 regval = 0;
+ int ret;
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, ®val);
+ if (ret)
+ return ret;
+ if (regval & OA_TC6_STDCAP_TIMESTAMP_CAPABILITY)
+ tc6->hw_tstamp_supported = true;
+ return 0;
+}
+
static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
enum oa_tc6_register_op reg_op)
{
@@ -625,6 +823,16 @@ static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6)
static void oa_tc6_cleanup_ongoing_tx_skb(struct oa_tc6 *tc6)
{
+ if (tc6->ongoing_tx_skb &&
+ oa_tc6_tsinfo_tx(tc6->ongoing_tx_skb)->tsc) {
+ u8 tsc = oa_tc6_tsinfo_tx(tc6->ongoing_tx_skb)->tsc;
+
+ spin_lock_bh(&tc6->tx_skb_lock);
+ tc6->ts_ttsc_pending &= ~BIT(tsc - OA_TC6_TTSCA_REG_ID);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ }
+
+ tc6->tx_skb_offset = 0;
oa_tc6_drop_tx_skb(tc6, tc6->ongoing_tx_skb);
tc6->ongoing_tx_skb = NULL;
}
@@ -650,6 +858,7 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
{
oa_tc6_free_ongoing_skbs(tc6);
oa_tc6_cleanup_waiting_tx_skb(tc6);
+ oa_tc6_cleanup_tx_tstamp_skbs(tc6);
}
static void oa_tc6_look_for_new_frame(struct oa_tc6 *tc6)
@@ -678,6 +887,8 @@ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
netif_tx_disable(tc6->netdev);
oa_tc6_drop_tx_skb(tc6, skb);
oa_tc6_free_ongoing_skbs(tc6);
+ oa_tc6_cleanup_tx_tstamp_skbs(tc6);
+
oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
oa_tc6_read_register(tc6, OA_TC6_REG_STATUS0, ®val);
oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, regval);
@@ -686,15 +897,28 @@ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
{
+ u32 regs[OA_TC6_STATUS0_TTSC_REG_COUNT];
+ bool ts_valid = !!tc6->ptp_clock;
u32 value;
int ret;
- ret = oa_tc6_read_register(tc6, OA_TC6_REG_STATUS0, &value);
+ /* When timestamp is disabled, there is no behavior change
+ * as it reads only STATUS0 register. When enabled,
+ * TTSCA_HIGH..TTSCC_LOW are fetched together with STATUS0
+ * to avoid having to make second SPI transaction. Reading few
+ * extra registers, even it may not be needed every time this
+ * function is called, it is more efficient than making second
+ * SPI transaction, when needed.
+ */
+ ret = oa_tc6_read_registers(tc6, OA_TC6_REG_STATUS0, regs,
+ ts_valid ?
+ OA_TC6_STATUS0_TTSC_REG_COUNT : 1);
if (ret) {
netdev_err(tc6->netdev, "STATUS0 register read failed: %d\n",
ret);
return ret;
}
+ value = regs[0];
/* This function is called for each chunk received in a given SPI
* transaction. In case, extended status bit is set in more than
@@ -703,6 +927,11 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
if (!value)
return 0;
+ if ((value & OA_TC6_STATUS0_TTSCA_MASK) != 0)
+ oa_tc6_events_handle(tc6, value &
+ OA_TC6_STATUS0_TTSCA_MASK,
+ ®s[OA_TC6_TTSC_REG_OFFSET]);
+
/* Clear the error interrupts status */
ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, value);
if (ret) {
@@ -788,6 +1017,13 @@ static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
tc6->rx_skb->len > ETH_FCS_LEN)
skb_trim(tc6->rx_skb, tc6->rx_skb->len - ETH_FCS_LEN);
+ if (oa_tc6_update_ts_in_rx_skb(tc6)) {
+ tc6->netdev->stats.rx_dropped++;
+ dev_kfree_skb_any(tc6->rx_skb);
+ tc6->rx_skb = NULL;
+ return;
+ }
+
tc6->rx_skb->protocol = eth_type_trans(tc6->rx_skb, tc6->netdev);
tc6->netdev->stats.rx_packets++;
tc6->netdev->stats.rx_bytes += tc6->rx_skb->len;
@@ -823,25 +1059,32 @@ static int oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
* buffer pointer. We should release, if a valid pointer is found before
* allocating a new one.
*/
-static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6)
+static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6, u32 footer)
{
+ struct oa_tc6_ts_info_rx *ski;
+
oa_tc6_cleanup_ongoing_rx_skb(tc6);
- tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu +
- ETH_HLEN + ETH_FCS_LEN);
+ tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev,
+ tc6->netdev->mtu +
+ ETH_HLEN + ETH_FCS_LEN +
+ OA_TC6_TSTAMP_SZ);
if (!tc6->rx_skb) {
tc6->netdev->stats.rx_dropped++;
return -ENOMEM;
}
+ ski = oa_tc6_tsinfo_rx(tc6->rx_skb);
+ ski->rtsa = FIELD_GET(OA_TC6_DATA_FOOTER_RTSA_VALID, footer);
+ ski->rtsp = FIELD_GET(OA_TC6_DATA_FOOTER_RTSP_VALID, footer);
return 0;
}
static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,
- u16 size)
+ u16 size, u32 footer)
{
int ret;
- ret = oa_tc6_allocate_rx_skb(tc6);
+ ret = oa_tc6_allocate_rx_skb(tc6, footer);
if (ret)
return ret;
@@ -854,11 +1097,11 @@ static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,
return 0;
}
-static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size)
+static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size, u32 footer)
{
int ret;
- ret = oa_tc6_allocate_rx_skb(tc6);
+ ret = oa_tc6_allocate_rx_skb(tc6, footer);
if (ret)
return ret;
@@ -905,7 +1148,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
size = end_byte_offset + 1 - start_byte_offset;
return oa_tc6_prcs_complete_rx_frame(tc6,
&data[start_byte_offset],
- size);
+ size, footer);
}
/* Process the chunk with only rx frame start */
@@ -913,7 +1156,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
return oa_tc6_prcs_rx_frame_start(tc6,
&data[start_byte_offset],
- size);
+ size, footer);
}
/* Process the chunk with only rx frame end */
@@ -946,7 +1189,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
return oa_tc6_prcs_rx_frame_start(tc6,
&data[start_byte_offset],
- size);
+ size, footer);
}
/* Process the chunk with ongoing rx frame data */
@@ -1025,13 +1268,15 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
}
static __be32 oa_tc6_prepare_data_header(bool data_valid, bool start_valid,
- bool end_valid, u8 end_byte_offset)
+ bool end_valid, u8 end_byte_offset,
+ u8 tsc)
{
u32 header = FIELD_PREP(OA_TC6_DATA_HEADER_DATA_NOT_CTRL,
OA_TC6_DATA_HEADER) |
FIELD_PREP(OA_TC6_DATA_HEADER_DATA_VALID, data_valid) |
FIELD_PREP(OA_TC6_DATA_HEADER_START_VALID, start_valid) |
FIELD_PREP(OA_TC6_DATA_HEADER_END_VALID, end_valid) |
+ FIELD_PREP(OA_TC6_DATA_HEADER_TSC_OFFSET, tsc) |
FIELD_PREP(OA_TC6_DATA_HEADER_END_BYTE_OFFSET,
end_byte_offset);
@@ -1050,6 +1295,7 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)
enum oa_tc6_data_start_valid_info start_valid;
u8 end_byte_offset = 0;
u16 length_to_copy;
+ u8 tsc = 0;
/* Initial value is assigned here to avoid more than 80 characters in
* the declaration place.
@@ -1059,8 +1305,10 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)
/* Set start valid if the current tx chunk contains the start of the tx
* ethernet frame.
*/
- if (!tc6->tx_skb_offset)
+ if (!tc6->tx_skb_offset) {
start_valid = OA_TC6_DATA_START_VALID;
+ tsc = oa_tc6_tsinfo_tx(tc6->ongoing_tx_skb)->tsc;
+ }
/* If the remaining tx skb length is more than the chunk payload size of
* 64 bytes then copy only 64 bytes and leave the ongoing tx skb for
@@ -1081,17 +1329,29 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)
tc6->tx_skb_offset = 0;
tc6->netdev->stats.tx_bytes += tc6->ongoing_tx_skb->len;
tc6->netdev->stats.tx_packets++;
- kfree_skb(tc6->ongoing_tx_skb);
+
+ /* Free the ones that are not saved for later processing,
+ * like timestamping.
+ */
+ if (!oa_tc6_tsinfo_tx(tc6->ongoing_tx_skb)->tsc) {
+ kfree_skb(tc6->ongoing_tx_skb);
+ } else {
+ spin_lock_bh(&tc6->tx_skb_lock);
+ list_add_tail(&tc6->ongoing_tx_skb->list,
+ &tc6->tx_ts_skb_q);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ }
tc6->ongoing_tx_skb = NULL;
}
*tx_buf = oa_tc6_prepare_data_header(OA_TC6_DATA_VALID, start_valid,
- end_valid, end_byte_offset);
+ end_valid, end_byte_offset, tsc);
tc6->spi_data_tx_buf_offset += OA_TC6_CHUNK_SIZE;
}
static u16 oa_tc6_prepare_spi_tx_buf_for_tx_skbs(struct oa_tc6 *tc6)
{
+ struct sk_buff *drop_skb;
u16 used_tx_credits;
/* Get tx skbs and convert them into tx chunks based on the tx credits
@@ -1100,10 +1360,21 @@ static u16 oa_tc6_prepare_spi_tx_buf_for_tx_skbs(struct oa_tc6 *tc6)
for (used_tx_credits = 0; used_tx_credits < tc6->tx_credits;
used_tx_credits++) {
if (!tc6->ongoing_tx_skb) {
+ drop_skb = NULL;
spin_lock_bh(&tc6->tx_skb_lock);
tc6->ongoing_tx_skb = tc6->waiting_tx_skb;
tc6->waiting_tx_skb = NULL;
+ if (oa_tc6_req_tx_hwtstamp(tc6,
+ tc6->ongoing_tx_skb)) {
+ drop_skb = tc6->ongoing_tx_skb;
+ tc6->ongoing_tx_skb = NULL;
+ if (!tc6->disable_traffic &&
+ netif_running(tc6->netdev) &&
+ netif_queue_stopped(tc6->netdev))
+ netif_wake_queue(tc6->netdev);
+ }
spin_unlock_bh(&tc6->tx_skb_lock);
+ oa_tc6_drop_tx_skb(tc6, drop_skb);
}
if (!tc6->ongoing_tx_skb)
break;
@@ -1120,7 +1391,7 @@ static void oa_tc6_add_empty_chunks_to_spi_buf(struct oa_tc6 *tc6,
header = oa_tc6_prepare_data_header(OA_TC6_DATA_INVALID,
OA_TC6_DATA_START_INVALID,
- OA_TC6_DATA_END_INVALID, 0);
+ OA_TC6_DATA_END_INVALID, 0, 0);
while (needed_empty_chunks--) {
__be32 *tx_buf = tc6->spi_data_tx_buf +
@@ -1312,6 +1583,7 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb)
return NETDEV_TX_OK;
}
+ oa_tc6_tsinfo_tx(skb)->tsc = 0;
spin_lock_bh(&tc6->tx_skb_lock);
if (tc6->waiting_tx_skb) {
netif_stop_queue(tc6->netdev);
@@ -1371,6 +1643,8 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
SET_NETDEV_DEV(netdev, &spi->dev);
mutex_init(&tc6->spi_ctrl_lock);
spin_lock_init(&tc6->tx_skb_lock);
+ tc6->ttsc_current_id = OA_TC6_TTSCA_REG_ID;
+ INIT_LIST_HEAD(&tc6->tx_ts_skb_q);
if (quirks)
tc6->quirk_flags = quirks->quirk_flags;
@@ -1464,6 +1738,12 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
goto phy_exit;
}
+ ret = oa_tc6_update_standard_capability(tc6);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "Failed to read capability\n");
+ goto phy_exit;
+ }
+
ret = devm_request_threaded_irq(&tc6->spi->dev, tc6->spi->irq,
oa_tc6_macphy_isr,
oa_tc6_macphy_threaded_irq,
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
new file mode 100644
index 000000000000..1b515615819b
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for hardware timestamping feature for OPEN Alliance
+ * 10BASE‑T1x MAC‑PHY Serial Interface framework
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/oa_tc6.h>
+
+#include "oa_tc6_std_def.h"
+
+/**
+ * oa_tc6_ptp_register - Registers clock related callbacks
+ * @tc6: oa_tc6 struct.
+ * @info: Describes a PTP hardware clock
+ *
+ * Description: Vendors are expected to set the hardware timestamp
+ * related callbacks before calling this function.
+ *
+ * Return: 0 on success otherwise failed.
+ *
+ */
+int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info)
+{
+ int ret = 0;
+
+ /* Not supporting hardware timestamp isn't an error */
+ if (!tc6->hw_tstamp_supported)
+ return ret;
+
+ snprintf(info->name, sizeof(info->name), "OA TC6 PTP %s",
+ dev_name(&tc6->spi->dev));
+ tc6->ptp_clock = ptp_clock_register(info, &tc6->spi->dev);
+ if (IS_ERR(tc6->ptp_clock)) {
+ ret = PTR_ERR(tc6->ptp_clock);
+ tc6->ptp_clock = NULL;
+ dev_err(&tc6->spi->dev, "Registration of %s failed\n",
+ info->name);
+ }
+
+ if (tc6->ptp_clock)
+ dev_info(&tc6->spi->dev, "%s registered. index %d\n",
+ info->name, ptp_clock_index(tc6->ptp_clock));
+ return ret;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ptp_register);
+
+/**
+ * oa_tc6_ptp_unregister - Unregisters clock related callbacks
+ * @tc6: oa_tc6 struct.
+ */
+void oa_tc6_ptp_unregister(struct oa_tc6 *tc6)
+{
+ if (tc6->ptp_clock) {
+ ptp_clock_unregister(tc6->ptp_clock);
+ tc6->ptp_clock = NULL;
+ }
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ptp_unregister);
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
index 403a9c22b5f1..92e936a18490 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -18,6 +18,9 @@
#include <linux/wait.h>
#include <linux/phy.h>
+/* Tx timestamp capture register A (high) */
+#define OA_TC6_REG_TTSCA_HIGH (0x10)
+
/* Control command header */
#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
@@ -33,6 +36,7 @@
#define OA_TC6_DATA_HEADER_START_WORD_OFFSET GENMASK(19, 16)
#define OA_TC6_DATA_HEADER_END_VALID BIT(14)
#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET GENMASK(13, 8)
+#define OA_TC6_DATA_HEADER_TSC_OFFSET GENMASK(7, 6)
#define OA_TC6_DATA_HEADER_PARITY BIT(0)
/* Data footer */
@@ -44,6 +48,8 @@
#define OA_TC6_DATA_FOOTER_START_VALID BIT(20)
#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET GENMASK(19, 16)
#define OA_TC6_DATA_FOOTER_END_VALID BIT(14)
+#define OA_TC6_DATA_FOOTER_RTSA_VALID BIT(7)
+#define OA_TC6_DATA_FOOTER_RTSP_VALID BIT(6)
#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8)
#define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
@@ -70,6 +76,33 @@
#define OA_TC6_REG_MMS_MASK GENMASK(19, 16)
+#define OA_TC6_TSTAMP_SZ 8
+
+#define OA_TC6_TTSCA_REG_ID 1
+#define OA_TC6_TTSCB_REG_ID 2
+#define OA_TC6_TTSCC_REG_ID 3
+
+/* STATUS0 and the TTSCA/B/C capture registers are contiguous in MMS 0, so
+ * they are fetched with a single control transaction while timestamping is
+ * available.
+ */
+#define OA_TC6_TTSC_REG_OFFSET (OA_TC6_REG_TTSCA_HIGH - \
+ OA_TC6_REG_STATUS0)
+#define OA_TC6_TTSC_REG_COUNT (2 * OA_TC6_TTSCC_REG_ID)
+#define OA_TC6_STATUS0_TTSC_REG_COUNT (OA_TC6_TTSC_REG_OFFSET + \
+ OA_TC6_TTSC_REG_COUNT)
+
+/* Useful for reading all the registers between CONFIG0 and INT_MASK0
+ * in a single SPI transaction
+ */
+#define CFG0_TO_IMASK0_REG_COUNT (OA_TC6_REG_INT_MASK0 - \
+ OA_TC6_REG_CONFIG0 + 1)
+#define CFG0_VAL_OFFSET (0)
+#define STATUS0_VAL_OFFSET (OA_TC6_REG_STATUS0 - \
+ OA_TC6_REG_CONFIG0)
+#define IRQM_VAL_OFFSET (OA_TC6_REG_INT_MASK0 - \
+ OA_TC6_REG_CONFIG0)
+
/* Internal structure for MAC-PHY drivers */
struct oa_tc6 {
struct net_device *netdev;
@@ -95,6 +128,16 @@ struct oa_tc6 {
bool prot_ctrl;
enum oa_tc6_quirk_flag quirk_flags;
struct gpio_desc *reset_gpio;
+ struct hwtstamp_config ts_config;
+ struct list_head tx_ts_skb_q;
+ struct ptp_clock *ptp_clock;
+ bool hw_tstamp_supported;
+ bool hw_tstamp_enabled;
+ u64 tx_hwtstamp_pkts;
+ u64 tx_hwtstamp_lost;
+ u64 tx_hwtstamp_err;
+ u8 ts_ttsc_pending;
+ u8 ttsc_current_id;
};
enum oa_tc6_header_type {
@@ -121,5 +164,8 @@ enum oa_tc6_data_end_valid_info {
OA_TC6_DATA_END_INVALID,
OA_TC6_DATA_END_VALID,
};
+
+void oa_tc6_cleanup_tx_tstamp_skbs(struct oa_tc6 *tc6);
+
#endif /* OA_TC6_STD_DEF_H */
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
new file mode 100644
index 000000000000..539cbd36a1d0
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iopoll.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
+#include <linux/oa_tc6.h>
+
+#include "oa_tc6_std_def.h"
+
+static int oa_tc6_set_hwtstamp_settings(struct oa_tc6 *tc6,
+ const struct hwtstamp_config *ts_cfg)
+{
+ u32 regs[CFG0_TO_IMASK0_REG_COUNT];
+ u32 cfg0, irqm, status0;
+ bool disabled;
+ int ret;
+
+ if (tc6->ts_config.flags == ts_cfg->flags &&
+ tc6->ts_config.tx_type == ts_cfg->tx_type &&
+ tc6->ts_config.rx_filter == ts_cfg->rx_filter)
+ return 0;
+
+ spin_lock_bh(&tc6->tx_skb_lock);
+ disabled = tc6->disable_traffic;
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ if (disabled)
+ return -EIO;
+
+ /* CONFIG0, STATUS0 and INT_MASK0 span one register block, so a
+ * single control transaction covers all three.
+ */
+ ret = oa_tc6_read_registers(tc6, OA_TC6_REG_CONFIG0, regs,
+ ARRAY_SIZE(regs));
+ if (ret) {
+ dev_err(&tc6->spi->dev, "Failed to read CFG0 block\n");
+ goto out;
+ }
+
+ cfg0 = regs[CFG0_VAL_OFFSET];
+ status0 = regs[STATUS0_VAL_OFFSET];
+ irqm = regs[IRQM_VAL_OFFSET];
+
+ /* Hardware timestamp feature supports 64-bit timestamp only */
+ if (ts_cfg->tx_type == HWTSTAMP_TX_ON ||
+ ts_cfg->rx_filter == HWTSTAMP_FILTER_ALL)
+ cfg0 |= (OA_TC6_CONFIG0_FTSE_ENABLE |
+ OA_TC6_CONFIG0_FTSS_64BIT_TSTAMP);
+ else
+ cfg0 &= ~OA_TC6_CONFIG0_FTSE_ENABLE;
+
+ if (ts_cfg->tx_type == HWTSTAMP_TX_ON)
+ irqm &= ~OA_TC6_INT_MASK0_TTSCA_MASK;
+ else
+ irqm |= OA_TC6_INT_MASK0_TTSCA_MASK;
+
+ /* STATUS0 is write-one-to-clear, so this acknowledges a capture
+ * without delivering its timestamp. Skip it while TX timestamping
+ * stays enabled, where a capture may still be outstanding.
+ */
+ status0 &= OA_TC6_STATUS0_TTSCA_MASK;
+ if (status0 && (tc6->ts_config.tx_type != HWTSTAMP_TX_ON ||
+ ts_cfg->tx_type != HWTSTAMP_TX_ON)) {
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0,
+ status0);
+ if (ret) {
+ dev_err(&tc6->spi->dev,
+ "failed to write STATUS0 register\n");
+ goto out;
+ }
+ }
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, irqm);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "failed to write IRQM register\n");
+ goto out;
+ }
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, cfg0);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "failed to write CFG0 register\n");
+ goto out;
+ }
+ spin_lock_bh(&tc6->tx_skb_lock);
+ tc6->ts_config = *ts_cfg;
+ if (cfg0 & OA_TC6_CONFIG0_FTSE_ENABLE)
+ tc6->hw_tstamp_enabled = true;
+ else
+ tc6->hw_tstamp_enabled = false;
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ /* The TX timestamp completion interrupt is masked now. Time
+ * to release the queued skbs.
+ */
+ if (ts_cfg->tx_type != HWTSTAMP_TX_ON)
+ oa_tc6_cleanup_tx_tstamp_skbs(tc6);
+out:
+ return ret;
+}
+
+/**
+ * oa_tc6_hwtstamp_get - gets hardware timestamp config
+ * @tc6: oa_tc6 struct.
+ * @cfg: kernel copy of hardware timestamp config
+ */
+void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg)
+{
+ hwtstamp_config_to_kernel(cfg, &tc6->ts_config);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_get);
+
+/**
+ * oa_tc6_hwtstamp_set - sets hardware timestamp config
+ * @tc6: oa_tc6 struct.
+ * @cfg: kernel copy of hardware timestamp config
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg)
+{
+ struct hwtstamp_config ts_cfg;
+ int ret;
+
+ if (!netif_running(tc6->netdev))
+ return -EIO;
+
+ /* Set only when STDCAP advertised the capability and the PHC
+ * registered, which is what ethtool -T reports.
+ */
+ if (!tc6->ptp_clock)
+ return -EOPNOTSUPP;
+
+ switch (cfg->tx_type) {
+ case HWTSTAMP_TX_OFF:
+ case HWTSTAMP_TX_ON:
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (cfg->rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ case HWTSTAMP_FILTER_ALL:
+ case HWTSTAMP_FILTER_SOME:
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ case HWTSTAMP_FILTER_NTP_ALL:
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ hwtstamp_config_from_kernel(&ts_cfg, cfg);
+
+ /* Supports timestamping all traffic */
+ if (cfg->rx_filter != HWTSTAMP_FILTER_NONE ||
+ cfg->tx_type == HWTSTAMP_TX_ON)
+ ts_cfg.rx_filter = HWTSTAMP_FILTER_ALL;
+
+ ret = oa_tc6_set_hwtstamp_settings(tc6, &ts_cfg);
+ if (ret)
+ return ret;
+
+ /* Report what was programmed, not asked. */
+ cfg->rx_filter = ts_cfg.rx_filter;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_set);
+
+/**
+ * oa_tc6_get_ts_stats - Provides timestamping stats
+ * @tc6: oa_tc6 struct.
+ * @stats: ethtool data structure to fill in
+ */
+void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
+ struct ethtool_ts_stats *stats)
+{
+ stats->pkts = tc6->tx_hwtstamp_pkts;
+ stats->err = tc6->tx_hwtstamp_err;
+ stats->lost = tc6->tx_hwtstamp_lost;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_get_ts_stats);
+
+/**
+ * oa_tc6_get_ts_info - Provides timestamp info for ethtool
+ * @tc6: oa_tc6 struct.
+ * @info: ethtool timestamping info structure
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
+ struct kernel_ethtool_ts_info *info)
+{
+ if (!tc6->ptp_clock)
+ return ethtool_op_get_ts_info(tc6->netdev, info);
+
+ info->so_timestamping = SOF_TIMESTAMPING_RAW_HARDWARE |
+ SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE;
+ info->phc_index = ptp_clock_index(tc6->ptp_clock);
+ info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON);
+ info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
+ BIT(HWTSTAMP_FILTER_ALL);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_get_ts_info);
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 27f652d4920b..365abdbf60d3 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -7,13 +7,18 @@
* Author: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
*/
+#ifndef _LINUX_OA_TC6_H
+#define _LINUX_OA_TC6_H
+
#include <linux/etherdevice.h>
#include <linux/spi/spi.h>
+#include <linux/ptp_clock_kernel.h>
/* OPEN Alliance TC6 registers */
/* Standard Capabilities Register */
#define OA_TC6_REG_STDCAP 0x0002
#define OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
+#define OA_TC6_STDCAP_TIMESTAMP_CAPABILITY BIT(6)
/* Reset Control and Status Register */
#define OA_TC6_REG_RESET 0x0003
@@ -23,6 +28,8 @@
#define OA_TC6_REG_CONFIG0 0x0004
#define OA_TC6_CONFIG0_SYNC BIT(15)
#define OA_TC6_CONFIG0_ZARFE_ENABLE BIT(12)
+#define OA_TC6_CONFIG0_FTSE_ENABLE BIT(7)
+#define OA_TC6_CONFIG0_FTSS_64BIT_TSTAMP BIT(6)
#define OA_TC6_CONFIG0_PROTE BIT(5)
/* Configuration Register #2 */
@@ -30,6 +37,10 @@
/* Status Register #0 */
#define OA_TC6_REG_STATUS0 0x0008
+#define OA_TC6_STATUS0_TTSCAC BIT(10)
+#define OA_TC6_STATUS0_TTSCAB BIT(9)
+#define OA_TC6_STATUS0_TTSCAA BIT(8)
+#define OA_TC6_STATUS0_TTSCA_MASK GENMASK(10, 8)
#define OA_TC6_STATUS0_RESETC BIT(6) /* Reset Complete */
#define OA_TC6_STATUS0_HEADER_ERROR BIT(5)
#define OA_TC6_STATUS0_LOSS_OF_FRAME_ERROR BIT(4)
@@ -43,6 +54,7 @@
/* Interrupt Mask Register #0 */
#define OA_TC6_REG_INT_MASK0 0x000C
+#define OA_TC6_INT_MASK0_TTSCA_MASK GENMASK(10, 8)
#define OA_TC6_INT_MASK0_HEADER_ERR_MASK BIT(5)
#define OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4)
#define OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3)
@@ -93,3 +105,14 @@ int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
int regnum);
int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
int regnum, u16 val);
+int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info);
+int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
+ struct kernel_ethtool_ts_info *info);
+void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg);
+void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
+ struct ethtool_ts_stats *stats);
+int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg);
+void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
+#endif /* _LINUX_OA_TC6_H */
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH net-next v9 05/11] net: ethernet: oa_tc6: Support for hardware timestamp
2026-10-09 17:12 ` [PATCH net-next v9 05/11] net: ethernet: oa_tc6: Support for hardware timestamp Selvamani Rajagopal via B4 Relay
@ 2026-10-10 19:40 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:40 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: andrew+netdev, davem, kuba, pabeni, robh, krzk+dt, conor+dt,
parthiban.veerasooran, horms, corbet, skhan, rdunlap,
richardcochran, andrew, hkallweit1, linux, edumazet,
piergiorgio.beruto, netdev, devicetree, linux-kernel, linux-doc,
jerry.ray, ciprian.regus
Thank you for your contribution! Sashiko AI review found 11 potential
issue(s) to consider.
Critical: 0 · High: 2 · Medium: 5 · Low: 4
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] oa_tc6_process_extended_status() declares 'u32
regs[OA_TC6_STATUS0_TTSC_REG_COUNT]' (14 entries) without initialising…
- [High] net/oa_tc6: TOCTOU race in HWTSTAMP settings unmasks interrupts
- [Medium] A TX timestamp capture slot that never completes is never
reclaimed.
- [Medium] oa_tc6_req_tx_hwtstamp() puts two cases into tx_hwtstamp_lost,
which oa_tc6_get_ts_stats() reports as ethtool_ts_stats.lost.
- [Medium] When tc6->ptp_clock is NULL, oa_tc6_get_ts_info() returns
ethtool_op_get_ts_info().
- [Medium] A TX ON->OFF hwtstamp change races with an in-flight skb that
already holds a slot.
- [Medium] When all three TTSC slots are pending,
oa_tc6_req_tx_hwtstamp() returns true and…
- [Low] Concern: the six exported APIs (oa_tc6_ptp_register/unregister,
oa_tc6_hwtstamp_set/get, oa_tc6_get_ts_info, oa_tc6_get_ts_stats) have…
- [Low] The comment in oa_tc6_process_extended_status() says that when
timestamping is disabled only STATUS0 is read, and that the TTSC…
- [Low] oa_tc6_set_hwtstamp_settings() makes three dependent control
writes in order: STATUS0 W1C (line 69), INT_MASK0 (line 78), CONFIG0…
- [Low] tx_hwtstamp_pkts, tx_hwtstamp_lost and tx_hwtstamp_err in struct
oa_tc6 are plain u64 fields.
--
net: ethernet: oa_tc6: Support for hardware timestamp
This adds hardware timestamping support to the OPEN Alliance TC6
framework: PTP clock register/unregister helpers, hwtstamp get/set, and
ethtool ts_info/ts_stats helpers for vendor drivers. TX frames that
request a timestamp get one of three TTSC capture slots, and RX
timestamps are taken from the frame prefix.
> Interface for ndo_hwtstamp_set/get, control and status
> callback for ethtool are provided to support hardware timestamp
> feature. ioctl interface passes the request to PHY.
[Severity: Low]
This isn't a bug, but does the last sentence apply to this patch? I
couldn't find any ioctl handling added here. It looks like it describes
the vendor driver's .ndo_eth_ioctl = phy_do_ioctl_running instead.
The rest of the paragraph holds up. The exported helpers are used by the
later "onsemi: s2500: Add driver support for S2500 MAC-PHY" patch in this
series. The new PTP_1588_CLOCK_OPTIONAL dependencies on LAN865X and
ADIN1140 are also needed, because both select OA_TC6.
> diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
> index f8e9309a72f11..0b6e216de8a1b 100644
> --- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
> +++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
[ ... ]
> @@ -50,6 +61,193 @@ static int oa_tc6_get_parity(u32 p)
> return !((p >> 28) & 1);
> }
>
[ ... ]
> +static bool oa_tc6_req_tx_hwtstamp(struct oa_tc6 *tc6, struct sk_buff *skb)
> +{
> + u8 tsc;
> + u8 i;
> +
> + lockdep_assert_held(&tc6->tx_skb_lock);
> +
> + if (!skb || !(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP))
> + return false;
> +
> + if (!tc6->hw_tstamp_enabled)
> + return false;
> +
> + if (tc6->ts_config.tx_type != HWTSTAMP_TX_ON) {
> + tc6->tx_hwtstamp_lost++;
> + return false;
> + }
[Severity: Medium]
Should this count as err rather than lost? The kernel-doc for struct
ethtool_ts_stats in include/linux/ethtool.h defines @lost as requests
where the hardware timestamp never arrived. It puts resource exhaustion
and unavailability under @err.
Here TX timestamping is just not enabled (only the RX filter is). The
slot exhaustion case below never asks the hardware for a timestamp
either. Only the increment in oa_tc6_cleanup_tx_tstamp_skbs() seems to
fit the @lost definition.
> +
> + tsc = tc6->ttsc_current_id;
> + for (i = 0; i < OA_TC6_TTSCC_REG_ID; i++) {
> + if (!(tc6->ts_ttsc_pending &
> + BIT(tsc - OA_TC6_TTSCA_REG_ID)))
> + goto slot_found;
> + tsc = oa_tc6_next_tsc(tsc);
> + }
> +
> + /* All the three slots are waiting for its event. This skb
> + * can't request timestamp. Marking it to be dropped.
> + */
> + tc6->tx_hwtstamp_lost++;
> + return true;
[Severity: Medium]
What frees a slot if its capture event never arrives?
During normal operation, a bit in ts_ttsc_pending is cleared only in
oa_tc6_events_handle(). That happens when STATUS0 reports TTSCAx and a
matching skb is already on tx_ts_skb_q. Otherwise only teardown or a TX
ON->OFF change clears it. There is no timeout, and STATUS1, where missed
or overflowed captures are reported, is never read.
A capture can also be acknowledged without being delivered. One way is
the STATUS0 write-one-to-clear in oa_tc6_set_hwtstamp_settings(). Another
is the ON->OFF race described further down.
Once three slots are stuck, every SKBTX_HW_TSTAMP frame takes this path
and oa_tc6_prepare_spi_tx_buf_for_tx_skbs() drops it. The skbs stay
pinned on tx_ts_skb_q. PTP traffic would stop until TX timestamping is
toggled off or the device is torn down.
The commit message says three capture registers "are plenty". Does that
assume slots are always returned?
[ ... ]
> @@ -686,15 +897,28 @@ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
>
> static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
> {
> + u32 regs[OA_TC6_STATUS0_TTSC_REG_COUNT];
> + bool ts_valid = !!tc6->ptp_clock;
> u32 value;
> int ret;
>
> - ret = oa_tc6_read_register(tc6, OA_TC6_REG_STATUS0, &value);
> + /* When timestamp is disabled, there is no behavior change
> + * as it reads only STATUS0 register. When enabled,
> + * TTSCA_HIGH..TTSCC_LOW are fetched together with STATUS0
> + * to avoid having to make second SPI transaction. Reading few
> + * extra registers, even it may not be needed every time this
> + * function is called, it is more efficient than making second
> + * SPI transaction, when needed.
> + */
> + ret = oa_tc6_read_registers(tc6, OA_TC6_REG_STATUS0, regs,
> + ts_valid ?
> + OA_TC6_STATUS0_TTSC_REG_COUNT : 1);
[Severity: Low]
Does this comment match the code? ts_valid comes from tc6->ptp_clock,
which is set once in oa_tc6_ptp_register(). It doesn't depend on the
hwtstamp configuration.
With a PHC registered and hwtstamp left at the default OFF, every
extended status event reads all 14 registers from STATUS0 through
TTSCC_LOW, including the reserved ones in between.
> if (ret) {
> netdev_err(tc6->netdev, "STATUS0 register read failed: %d\n",
> ret);
> return ret;
> }
> + value = regs[0];
>
[ ... ]
> @@ -703,6 +927,11 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
> if (!value)
> return 0;
>
> + if ((value & OA_TC6_STATUS0_TTSCA_MASK) != 0)
> + oa_tc6_events_handle(tc6, value &
> + OA_TC6_STATUS0_TTSCA_MASK,
> + ®s[OA_TC6_TTSC_REG_OFFSET]);
[Severity: High]
Can this pass uninitialized stack to oa_tc6_events_handle()? regs[] is
not initialized, and when ts_valid is false only regs[0] is read. This
call checks only the STATUS0 bits, not ts_valid.
oa_tc6_ptp_unregister() clears ptp_clock but leaves the rest alone. FTSE
stays enabled, the TTSC interrupts stay unmasked, ts_config is unchanged,
and tx_ts_skb_q is not drained:
void oa_tc6_ptp_unregister(struct oa_tc6 *tc6)
{
if (tc6->ptp_clock) {
ptp_clock_unregister(tc6->ptp_clock);
tc6->ptp_clock = NULL;
}
}
The s2500 driver later in this series calls unregister_netdev(), then
oa_tc6_ptp_unregister(), then oa_tc6_exit(). The IRQ thread keeps
running until oa_tc6_exit() calls disable_irq(). ptp_clock_unregister()
can sleep, which makes that window wider.
If a TTSCAx event arrives in that window for an skb still on
tx_ts_skb_q, oa_tc6_events_handle() does:
ski->hwtstamp = ktime_set(ts[2 * bit_num],
ts[2 * bit_num + 1]);
...
skb_tstamp_tx(skb, &tstamp);
Here ts[] points into the uninitialized part of regs[]. That value then
reaches the owning socket's error queue. The socket only needs
SOF_TIMESTAMPING_TX_HARDWARE, which requires no privilege.
Would zero-initializing regs[], or gating this call on ts_valid, fix it?
[ ... ]
> @@ -1100,10 +1360,21 @@ static u16 oa_tc6_prepare_spi_tx_buf_for_tx_skbs(struct oa_tc6 *tc6)
> for (used_tx_credits = 0; used_tx_credits < tc6->tx_credits;
> used_tx_credits++) {
> if (!tc6->ongoing_tx_skb) {
> + drop_skb = NULL;
> spin_lock_bh(&tc6->tx_skb_lock);
> tc6->ongoing_tx_skb = tc6->waiting_tx_skb;
> tc6->waiting_tx_skb = NULL;
> + if (oa_tc6_req_tx_hwtstamp(tc6,
> + tc6->ongoing_tx_skb)) {
> + drop_skb = tc6->ongoing_tx_skb;
> + tc6->ongoing_tx_skb = NULL;
> + if (!tc6->disable_traffic &&
> + netif_running(tc6->netdev) &&
> + netif_queue_stopped(tc6->netdev))
> + netif_wake_queue(tc6->netdev);
> + }
> spin_unlock_bh(&tc6->tx_skb_lock);
> + oa_tc6_drop_tx_skb(tc6, drop_skb);
[Severity: Medium]
Would it be better to send the frame without a timestamp here, rather
than drop it?
Every socket on the device shares the three slots. Once an admin enables
TX timestamping, any local process can set SOF_TIMESTAMPING_TX_HARDWARE
on its own socket and keep the slots busy with small frames. Several
timestamping applications running together could also do this by
accident.
Other users' timestamped frames, such as ptp4l event messages or chrony's
hardware-timestamped NTP, would then not be sent at all. They would not
just lose their timestamp.
[ ... ]
> diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
> new file mode 100644
> index 0000000000000..539cbd36a1d04
> --- /dev/null
> +++ b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
> @@ -0,0 +1,224 @@
[ ... ]
> +static int oa_tc6_set_hwtstamp_settings(struct oa_tc6 *tc6,
> + const struct hwtstamp_config *ts_cfg)
> +{
> + u32 regs[CFG0_TO_IMASK0_REG_COUNT];
> + u32 cfg0, irqm, status0;
> + bool disabled;
> + int ret;
> +
> + if (tc6->ts_config.flags == ts_cfg->flags &&
> + tc6->ts_config.tx_type == ts_cfg->tx_type &&
> + tc6->ts_config.rx_filter == ts_cfg->rx_filter)
> + return 0;
[ ... ]
> + ret = oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, irqm);
> + if (ret) {
> + dev_err(&tc6->spi->dev, "failed to write IRQM register\n");
> + goto out;
> + }
> +
> + ret = oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, cfg0);
> + if (ret) {
> + dev_err(&tc6->spi->dev, "failed to write CFG0 register\n");
> + goto out;
> + }
[Severity: High]
Can oa_tc6_disable_traffic() run in the middle of this sequence?
The disable_traffic check above is done under tx_skb_lock. That lock is
dropped before the CONFIG0..INT_MASK0 block is read.
oa_tc6_read_registers() and oa_tc6_write_register() each take
spi_ctrl_lock only for their own transfer. So the read-modify-write of
INT_MASK0 is not atomic against the SPI thread.
Suppose the SPI thread hits a fatal error in that window.
oa_tc6_disable_traffic() then sets disable_traffic and writes
OA_TC6_INT_MASK0_ALL_INTERRUPTS to stop an interrupt storm. After that,
this function writes its own irqm back, and there are two cases:
- irqm was read before the emergency mask, so it is stale.
- irqm was read after the emergency mask, and then the TTSC mask bits
were cleared for TX ON.
Either way, interrupts are unmasked on a device that was deliberately
silenced. The IRQ is requested with IRQF_TRIGGER_LOW, so this could
bring back the interrupt storm that oa_tc6_disable_traffic() is meant
to prevent. The CONFIG0 write and the ts_config/hw_tstamp_enabled
update also land after the device has already been given up on.
Could this whole sequence be serialized against
oa_tc6_disable_traffic(), for example with a mutex taken on both paths?
Rechecking disable_traffic after the writes and re-masking everything
would narrow the window, but would it close it?
[Severity: Low]
What happens if the CONFIG0 write fails after INT_MASK0 has been written?
On an ON->OFF request, the TTSC interrupts are now masked. FTSE is still
set, ts_config.tx_type is still HWTSTAMP_TX_ON, and hw_tstamp_enabled is
still true. Nothing restores INT_MASK0.
oa_tc6_req_tx_hwtstamp() would keep handing out slots whose completions
can no longer raise an interrupt. A later request for the old ON
configuration hits the cached-config early return at the top of this
function. It returns 0 without fixing INT_MASK0.
Should the earlier writes be rolled back on failure?
> + spin_lock_bh(&tc6->tx_skb_lock);
> + tc6->ts_config = *ts_cfg;
> + if (cfg0 & OA_TC6_CONFIG0_FTSE_ENABLE)
> + tc6->hw_tstamp_enabled = true;
> + else
> + tc6->hw_tstamp_enabled = false;
> + spin_unlock_bh(&tc6->tx_skb_lock);
> +
> + /* The TX timestamp completion interrupt is masked now. Time
> + * to release the queued skbs.
> + */
> + if (ts_cfg->tx_type != HWTSTAMP_TX_ON)
> + oa_tc6_cleanup_tx_tstamp_skbs(tc6);
[Severity: Medium]
Does this miss an skb that already holds a slot but is still in
tc6->ongoing_tx_skb?
When TX credits run out, a multi-chunk frame can stay in ongoing_tx_skb
across several SPI transfers. If this ON->OFF change runs during that
time, oa_tc6_cleanup_tx_tstamp_skbs() drains only what is already on
tx_ts_skb_q.
Later the SPI thread finishes the frame in oa_tc6_add_tx_skb_to_spi_buf()
and queues it based only on its cached tsc:
if (!oa_tc6_tsinfo_tx(tc6->ongoing_tx_skb)->tsc) {
kfree_skb(tc6->ongoing_tx_skb);
} else {
spin_lock_bh(&tc6->tx_skb_lock);
list_add_tail(&tc6->ongoing_tx_skb->list,
&tc6->tx_ts_skb_q);
spin_unlock_bh(&tc6->tx_skb_lock);
}
Its completion interrupt is now masked. On the next OFF->ON change, the
STATUS0 write clears any latched TTSC bit without delivering it. The skb
pins its socket memory until the next ON->OFF change or teardown. Only
two slots stay usable while TX timestamping is ON.
Each later OFF transition drains it, so this alone strands at most one
skb and slot at a time. It can still combine with the missing slot
reclaim mentioned above.
[ ... ]
> +void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
> + struct ethtool_ts_stats *stats)
> +{
> + stats->pkts = tc6->tx_hwtstamp_pkts;
> + stats->err = tc6->tx_hwtstamp_err;
> + stats->lost = tc6->tx_hwtstamp_lost;
> +}
[Severity: Low]
Can these reads tear on 32-bit architectures? These are plain u64 fields
in struct oa_tc6. The SPI thread updates them mostly under tx_skb_lock,
but this function reads them without the lock or a u64_stats_sync.
Separately, oa_tc6_events_handle() updates tx_hwtstamp_err after
spin_unlock_bh():
if (val & ~handled) {
tc6->tx_hwtstamp_err += hweight32(val & ~handled);
The other updates to the same field are done under the lock. All writers
are in the SPI thread, so writers can't race each other, but the locking
is inconsistent.
[ ... ]
> +int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
> + struct kernel_ethtool_ts_info *info)
> +{
> + if (!tc6->ptp_clock)
> + return ethtool_op_get_ts_info(tc6->netdev, info);
[Severity: Medium]
Does this fallback advertise something the driver doesn't do?
ethtool_op_get_ts_info() sets SOF_TIMESTAMPING_TX_SOFTWARE, but neither
oa_tc6_start_xmit() nor oa_tc6_add_tx_skb_to_spi_buf() calls
skb_tx_timestamp().
This path is taken when STDCAP lacks the timestamp capability. It is
also taken with PTP_1588_CLOCK=n, because the ptp_clock_register() stub
returns NULL. Tools such as ptp4l -S would then wait for software TX
timestamps that never arrive.
The core default in net/ethtool/common.c reports only RX_SOFTWARE and
SOFTWARE. Would falling back to that default work here?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d%40onsemi.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v9 06/11] net: ethernet: oa_tc6: Support for vendor specific MMS
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
` (4 preceding siblings ...)
2026-10-09 17:12 ` [PATCH net-next v9 05/11] net: ethernet: oa_tc6: Support for hardware timestamp Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-09 18:43 ` Selvamani Rajagopal
2026-10-10 19:40 ` netdev-bot+sashiko
2026-10-09 17:12 ` [PATCH net-next v9 07/11] net: phy: ncn26000: Enable enhanced noise immunity Selvamani Rajagopal via B4 Relay
` (4 subsequent siblings)
10 siblings, 2 replies; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
OPEN Alliance 10BASE-T1x Serial Interface specification, table 6
allows vendors to use any memory map select (MMS) value between
10 and 15. This new API interface enables vendor to map one of
thes MMS values to MDIO_MMD_VEND1.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- Added the missing vend1_mms initializer to where it belonged
- Added Reviewed tag
changes in v8
- No change
changes in v7
- No change
changes in v6
- No change
changes in v5
- no change
changes in v4
- no change
changes in v3
- no change
changes in v2
- Moved the handling of vendor specific MMS mapping to separate patch
- new patch
---
drivers/net/ethernet/oa_tc6/oa_tc6.c | 21 ++++++++++++++++++---
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 1 +
include/linux/oa_tc6.h | 1 +
3 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index 0b6e216de8a1..0c4f7d3f978f 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -248,6 +248,18 @@ static int oa_tc6_update_standard_capability(struct oa_tc6 *tc6)
return 0;
}
+/**
+ * oa_tc6_set_vend1_mms - Add vendor specific MDIO_MMD to OA TC6 MMS
+ * mapper value.
+ * @tc6: oa_tc6 struct.
+ * @mms: vendor defined MMS value for VEND1 mdio device.
+ */
+void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)
+{
+ tc6->vend1_mms = mms;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_set_vend1_mms);
+
static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
enum oa_tc6_register_op reg_op)
{
@@ -575,7 +587,7 @@ static int oa_tc6_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
val);
}
-static int oa_tc6_get_phy_c45_mms(int devnum)
+static int oa_tc6_get_phy_c45_mms(struct oa_tc6 *tc6, int devnum)
{
switch (devnum) {
case MDIO_MMD_PCS:
@@ -588,6 +600,8 @@ static int oa_tc6_get_phy_c45_mms(int devnum)
return OA_TC6_PHY_C45_AUTO_NEG_MMS5;
case MDIO_MMD_POWER_UNIT:
return OA_TC6_PHY_C45_POWER_UNIT_MMS6;
+ case MDIO_MMD_VEND1:
+ return tc6->vend1_mms;
default:
return -EOPNOTSUPP;
}
@@ -601,7 +615,7 @@ int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
int mms;
int ret;
- mms = oa_tc6_get_phy_c45_mms(devnum);
+ mms = oa_tc6_get_phy_c45_mms(tc6, devnum);
if (mms < 0)
return mms;
@@ -619,7 +633,7 @@ int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
struct oa_tc6 *tc6 = bus->priv;
int mms;
- mms = oa_tc6_get_phy_c45_mms(devnum);
+ mms = oa_tc6_get_phy_c45_mms(tc6, devnum);
if (mms < 0)
return mms;
@@ -1643,6 +1657,7 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
SET_NETDEV_DEV(netdev, &spi->dev);
mutex_init(&tc6->spi_ctrl_lock);
spin_lock_init(&tc6->tx_skb_lock);
+ tc6->vend1_mms = -EOPNOTSUPP;
tc6->ttsc_current_id = OA_TC6_TTSCA_REG_ID;
INIT_LIST_HEAD(&tc6->tx_ts_skb_q);
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
index 92e936a18490..cd62071d7f60 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -138,6 +138,7 @@ struct oa_tc6 {
u64 tx_hwtstamp_err;
u8 ts_ttsc_pending;
u8 ttsc_current_id;
+ int vend1_mms;
};
enum oa_tc6_header_type {
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 365abdbf60d3..88e7d5491b20 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -114,5 +114,6 @@ void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
struct ethtool_ts_stats *stats);
int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
struct kernel_hwtstamp_config *cfg);
+void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms);
void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
#endif /* _LINUX_OA_TC6_H */
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* RE: [PATCH net-next v9 06/11] net: ethernet: oa_tc6: Support for vendor specific MMS
2026-10-09 17:12 ` [PATCH net-next v9 06/11] net: ethernet: oa_tc6: Support for vendor specific MMS Selvamani Rajagopal via B4 Relay
@ 2026-10-09 18:43 ` Selvamani Rajagopal
2026-10-10 19:40 ` netdev-bot+sashiko
1 sibling, 0 replies; 25+ messages in thread
From: Selvamani Rajagopal @ 2026-10-09 18:43 UTC (permalink / raw)
To: Selvamani Rajagopal, Andrew Lunn, David S. Miller,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Parthiban Veerasooran, Simon Horman,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Richard Cochran,
Andrew Lunn, Heiner Kallweit, Russell King, Eric Dumazet,
Piergiorgio Beruto
Cc: netdev, devicetree, linux-kernel, linux-doc, Jerry Ray, Ciprian Regus
Andrew,
Minor change after your reviewed tag.
Sincerely
Selva
> -----Original Message-----
> From: Selvamani Rajagopal via B4 Relay
> Subject: [PATCH net-next v9 06/11] net: ethernet: oa_tc6: Support for vendor specific MMS
>
>
> Reviewed-by: Andrew Lunn <andrew@lunn.ch>
> Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
>
> ---
> changes in v9
> - Added the missing vend1_mms initializer to where it belonged
> - Added Reviewed tag
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v9 06/11] net: ethernet: oa_tc6: Support for vendor specific MMS
2026-10-09 17:12 ` [PATCH net-next v9 06/11] net: ethernet: oa_tc6: Support for vendor specific MMS Selvamani Rajagopal via B4 Relay
2026-10-09 18:43 ` Selvamani Rajagopal
@ 2026-10-10 19:40 ` netdev-bot+sashiko
1 sibling, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:40 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: andrew+netdev, davem, kuba, pabeni, robh, krzk+dt, conor+dt,
parthiban.veerasooran, horms, corbet, skhan, rdunlap,
richardcochran, andrew, hkallweit1, linux, edumazet,
piergiorgio.beruto, netdev, devicetree, linux-kernel, linux-doc,
jerry.ray, ciprian.regus
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Documentation/networking/oa-tc6-framework.rst describes
oa_tc6_set_vend1_mms() as mapping VEND1 to a vendor MMS 'between 10…
--
net: ethernet: oa_tc6: Support for vendor specific MMS
This commit adds an exported helper, oa_tc6_set_vend1_mms(), so a vendor
driver can map MDIO_MMD_VEND1 to a vendor-specific MMS value. With this
change, oa_tc6_get_phy_c45_mms() returns the stored value for
MDIO_MMD_VEND1. The value defaults to -EOPNOTSUPP in oa_tc6_init().
> diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
> index 0b6e216de8a1b..0c4f7d3f978fd 100644
> --- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
> +++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
> @@ -248,6 +248,18 @@ static int oa_tc6_update_standard_capability(struct oa_tc6 *tc6)
> return 0;
> }
>
> +/**
> + * oa_tc6_set_vend1_mms - Add vendor specific MDIO_MMD to OA TC6 MMS
> + * mapper value.
> + * @tc6: oa_tc6 struct.
> + * @mms: vendor defined MMS value for VEND1 mdio device.
> + */
> +void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)
> +{
> + tc6->vend1_mms = mms;
> +}
[Severity: Low]
Should this setter reject values outside the 10..15 range?
Documentation/networking/oa-tc6-framework.rst says the helper is for
vendors that "may have used any MMS value between 10 and 15 as allowed by
the specification". However, the setter stores any int as-is and returns
void.
oa_tc6_mdiobus_read_c45() and oa_tc6_mdiobus_write_c45() only reject
mms < 0. The value is then passed as a u8 to oa_tc6_read_register_mms() and
oa_tc6_write_register_mms(), which do:
mms_reg = FIELD_PREP(OA_TC6_REG_MMS_MASK, mms) | address;
OA_TC6_REG_MMS_MASK is GENMASK(19, 16). That means a value of 0..9 selects
a standard memory map directly, and 16 or 17 wraps to MMS0 or MMS1. VEND1
MDIO reads and writes would then go to a different register space without
any error.
The only caller in the series, s2500_probe(), passes
OA_TC6_PHY_C45_VS_MMS12, which is in range. Nothing in the tree hits this
today.
Would it make sense to add a range check that returns -EINVAL? Another
option is kernel-doc text stating that the caller must pass a value in
10..15.
> +EXPORT_SYMBOL_GPL(oa_tc6_set_vend1_mms);
> +
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d%40onsemi.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v9 07/11] net: phy: ncn26000: Enable enhanced noise immunity
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
` (5 preceding siblings ...)
2026-10-09 17:12 ` [PATCH net-next v9 06/11] net: ethernet: oa_tc6: Support for vendor specific MMS Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-10 19:40 ` netdev-bot+sashiko
2026-10-09 17:12 ` [PATCH net-next v9 08/11] onsemi: s2500: Add driver support for S2500 MAC-PHY Selvamani Rajagopal via B4 Relay
` (3 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
By setting ENI bit, noise immunity is improved and it is
specifically meant for PLCA enabled nodes.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- Added Reviewed tag
changes in v8
- No change
changes in v7
- No change
changes in v6
- No change
changes in v5
- No changes
changes in v4
- No changes
changes in v3
- Moved as a separate patch
---
drivers/net/phy/ncn26000.c | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index cabdd83c614f..6d433f4e2c45 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -35,6 +35,10 @@
#define TO_TMR_DEFAULT 32
+#define NCN26000_REG_PHYCFG1 0x8001
+#define NCN26000_PHYCFG1_ENI BIT(7)
+#define NCN26000_PHYCFG1_ENI_MASK BIT(7)
+
static int ncn26000_config_init(struct phy_device *phydev)
{
/* HW bug workaround: the default value of the PLCA TO_TIMER should be
@@ -100,6 +104,24 @@ static int ncn26000_read_status(struct phy_device *phydev)
return 0;
}
+/* Intercept PLCA enable/disable request to
+ * set the proprietary, ENI mode accordingly
+ */
+static int ncn26000_c45_plca_set_cfg(struct phy_device *phydev,
+ const struct phy_plca_cfg *plca_cfg)
+{
+ int ret = genphy_c45_plca_set_cfg(phydev, plca_cfg);
+ u16 eni_cfg = 0;
+
+ if (ret || plca_cfg->enabled < 0)
+ return ret;
+
+ eni_cfg = (plca_cfg->enabled) ? NCN26000_PHYCFG1_ENI : 0;
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
+ NCN26000_REG_PHYCFG1,
+ NCN26000_PHYCFG1_ENI_MASK, eni_cfg);
+}
+
static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
{
int ret;
@@ -151,7 +173,7 @@ static struct phy_driver ncn26000_driver[] = {
.read_status = ncn26000_read_status,
.handle_interrupt = ncn26000_handle_interrupt,
.get_plca_cfg = genphy_c45_plca_get_cfg,
- .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .set_plca_cfg = ncn26000_c45_plca_set_cfg,
.get_plca_status = genphy_c45_plca_get_status,
.soft_reset = genphy_soft_reset,
},
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH net-next v9 07/11] net: phy: ncn26000: Enable enhanced noise immunity
2026-10-09 17:12 ` [PATCH net-next v9 07/11] net: phy: ncn26000: Enable enhanced noise immunity Selvamani Rajagopal via B4 Relay
@ 2026-10-10 19:40 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:40 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: andrew+netdev, davem, kuba, pabeni, robh, krzk+dt, conor+dt,
parthiban.veerasooran, horms, corbet, skhan, rdunlap,
richardcochran, andrew, hkallweit1, linux, edumazet,
piergiorgio.beruto, netdev, devicetree, linux-kernel, linux-doc,
jerry.ray, ciprian.regus
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] ncn26000_c45_plca_set_cfg() (drivers/net/phy/ncn26000.c) changes
PLCA_EN (through genphy_c45_plca_set_cfg) and ENI (PHYCFG1 BIT(7)) as…
--
net: phy: ncn26000: Enable enhanced noise immunity
This commit wraps genphy_c45_plca_set_cfg() in a new
ncn26000_c45_plca_set_cfg() for the NCN26000. The wrapper sets the
vendor-specific ENI bit in PHYCFG1 when PLCA is enabled and clears it when
PLCA is disabled.
> diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
> index cabdd83c614f0..6d433f4e2c45d 100644
> --- a/drivers/net/phy/ncn26000.c
> +++ b/drivers/net/phy/ncn26000.c
[ ... ]
> @@ -100,6 +104,24 @@ static int ncn26000_read_status(struct phy_device *phydev)
> return 0;
> }
>
> +/* Intercept PLCA enable/disable request to
> + * set the proprietary, ENI mode accordingly
> + */
> +static int ncn26000_c45_plca_set_cfg(struct phy_device *phydev,
> + const struct phy_plca_cfg *plca_cfg)
> +{
> + int ret = genphy_c45_plca_set_cfg(phydev, plca_cfg);
> + u16 eni_cfg = 0;
> +
> + if (ret || plca_cfg->enabled < 0)
> + return ret;
[Severity: Low]
Can PLCA_EN and ENI end up out of sync here when an MDIO access fails?
For a disable request (enabled == 0), genphy_c45_plca_set_cfg() clears
PLCA_EN first and only then does the other writes:
drivers/net/phy/phy-c45.c:genphy_c45_plca_set_cfg() {
...
if (plca_cfg->enabled == 0) {
ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
MDIO_OATC14_PLCA_CTRL0,
MDIO_OATC14_PLCA_EN);
...
}
Suppose a later write in the same request fails (PLCA_CTRL1, TOTMR or
BURST). The early return above then skips the ENI update. The PHY would
run CSMA/CD with the PLCA-only ENI bit still set.
> +
> + eni_cfg = (plca_cfg->enabled) ? NCN26000_PHYCFG1_ENI : 0;
> + return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> + NCN26000_REG_PHYCFG1,
> + NCN26000_PHYCFG1_ENI_MASK, eni_cfg);
For an enable request (enabled > 0), genphy_c45_plca_set_cfg() sets
PLCA_EN as its last step and returns 0. If this phy_modify_mmd() on
PHYCFG1 then fails, PLCA stays enabled with ENI clear. Userspace still
gets an error.
genphy_c45_plca_get_cfg() does not report ENI, so ethtool cannot show the
mismatch. A later request that leaves out enabled (enabled == -1) will not
fix it either.
genphy_c45_plca_set_cfg() already leaves partial state behind on failure,
and resending the same request would bring both bits back in line. Even so,
would it make sense to roll back PLCA_EN (or ENI) on the error paths so the
two bits stay consistent?
> +}
> +
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d%40onsemi.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v9 08/11] onsemi: s2500: Add driver support for S2500 MAC-PHY
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
` (6 preceding siblings ...)
2026-10-09 17:12 ` [PATCH net-next v9 07/11] net: phy: ncn26000: Enable enhanced noise immunity Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-09 23:02 ` Randy Dunlap
2026-10-10 19:40 ` netdev-bot+sashiko
2026-10-09 17:12 ` [PATCH net-next v9 09/11] onsemi: s2500: Added selftest support to onsemi's S2500 driver Selvamani Rajagopal via B4 Relay
` (2 subsequent siblings)
10 siblings, 2 replies; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Support for onsemi's S2500, 802.3 cg compliant Ethernet
transceiver with integrated MAC-PHY. Works with
Open Alliance TC6 framework.
Hardware timestamp support is present in the driver.
adjtime callback is implemented using settime callback.
Driver has ethtool support for printing some device
registers, MAC stats, rmon stats and timestamp related
traffic counters.
Driver supports maximum three multicast filters. If
system has more than 3 multicast addresses to support,
all multicast traffic is allowed.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- moved net_selftest entry in ethtool_ops to the correct patch
- Moved MAINTAINERS changes here for driver files.
- Fix the order of variable declaration to keep reverse christmas
tree format.
changes in v8
- changes to use the new MMS read/write API in oa tc6 framework
- Updated to improve adjtime/adjfine implementation
- cleanup to remove unncessary definitions/initializations
- Removed unnecessary ioctl callback
changes in v7
- Moved the update to MAINTAINERS to the patch that has DTS
as it comes first.
- Added space between {}. Added missing MODULE_DEVICE_TABLE
- Removed the duplicate vendor dependency check in Kconfig
changes in v6
- Changes to ensure line length stay within 80 columns
changes in v5
- integrated the onsemi's selftest support to onsemi's driver
as a single patch as number of patches exceeded the limit
- Fixed the formatting issue in Kconfig file
- Fixed commenting issue
changes in v4
- MAC/PHY Loopback implemented by onsemi replaced by Linux
standard net_selftest APIs
- onsemi defined MMS values are replaced by OA TC definitions.
- removed only model information as it has practical use
- replaced all read/write APIs that use non-zero MMS to
use the new APIs that takes mms as one of its parameters.
- some bit manipulations are changed to use FIELD_GET macro
changes in v3
- split rmon stats and interface states separately.
- moved get_ts_info to OA TC6 framework as this isn't vendor
specific
- removed few static inline code
- moved ptp register code OA TC6 framework
changes in v2
- Removed the support for onsemi's NCN26010 which is legacy
MAC-PHY to keep only onsemi's S2500 MAC-PHY
- Renamed all the functions to start with S2500
changes in v1
- Added onsemi MAC-PHYs NCN26010 and S2500 support
---
MAINTAINERS | 1 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/onsemi/Kconfig | 22 +
drivers/net/ethernet/onsemi/Makefile | 7 +
drivers/net/ethernet/onsemi/s2500/Kconfig | 21 +
drivers/net/ethernet/onsemi/s2500/Makefile | 7 +
drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c | 376 ++++++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h | 160 +++++++
drivers/net/ethernet/onsemi/s2500/s2500_main.c | 523 ++++++++++++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_ptp.c | 174 +++++++
include/linux/oa_tc6.h | 1 +
12 files changed, 1294 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 1d1e0563b922..4e122a5eefa1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20445,6 +20445,7 @@ M: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
L: netdev@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/net/onnn,s2500.yaml
+F: drivers/net/ethernet/onsemi/s2500/*
OP-TEE DRIVER
M: Jens Wiklander <jenswi@kernel.org>
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 5758e0fbbca5..c160bf991345 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -137,6 +137,7 @@ source "drivers/net/ethernet/nvidia/Kconfig"
source "drivers/net/ethernet/nxp/Kconfig"
source "drivers/net/ethernet/oa_tc6/Kconfig"
source "drivers/net/ethernet/oki-semi/Kconfig"
+source "drivers/net/ethernet/onsemi/Kconfig"
config ETHOC
tristate "OpenCores 10/100 Mbps Ethernet MAC support"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index e0e86c249e42..e4bad64bf95c 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -74,6 +74,7 @@ obj-$(CONFIG_NET_VENDOR_NVIDIA) += nvidia/
obj-$(CONFIG_LPC_ENET) += nxp/
obj-$(CONFIG_OA_TC6) += oa_tc6/
obj-$(CONFIG_NET_VENDOR_OKI) += oki-semi/
+obj-$(CONFIG_NET_VENDOR_ONSEMI) += onsemi/
obj-$(CONFIG_ETHOC) += ethoc.o
obj-$(CONFIG_NET_VENDOR_PASEMI) += pasemi/
obj-$(CONFIG_NET_VENDOR_QLOGIC) += qlogic/
diff --git a/drivers/net/ethernet/onsemi/Kconfig b/drivers/net/ethernet/onsemi/Kconfig
new file mode 100644
index 000000000000..484de609462a
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/Kconfig
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# onsemi network device configuration
+#
+
+config NET_VENDOR_ONSEMI
+ bool "onsemi network devices"
+ default y
+ help
+ If you have a network card belonging to this class, say Y.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about onsemi ethernet devices. If you say Y, you
+ will be asked for your specific card in the following questions.
+
+if NET_VENDOR_ONSEMI
+
+source "drivers/net/ethernet/onsemi/s2500/Kconfig"
+
+endif # NET_VENDOR_ONSEMI
+
diff --git a/drivers/net/ethernet/onsemi/Makefile b/drivers/net/ethernet/onsemi/Makefile
new file mode 100644
index 000000000000..f3d4eb154313
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the onsemi network device drivers.
+#
+
+obj-$(CONFIG_S2500_MACPHY) += s2500/
+
diff --git a/drivers/net/ethernet/onsemi/s2500/Kconfig b/drivers/net/ethernet/onsemi/s2500/Kconfig
new file mode 100644
index 000000000000..abd967448af0
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/Kconfig
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# onsemi S2500 Driver Support
+#
+
+config S2500_MACPHY
+ tristate "S2500 support"
+ depends on SPI
+ depends on PTP_1588_CLOCK_OPTIONAL
+ imply NET_SELFTESTS
+ select BITREVERSE
+ select NCN26000_PHY
+ select OA_TC6
+ help
+ Support for the onsemi S2500 MACPHY Ethernet chip.
+ It works under the framework that conform to OPEN Alliance
+ 10BASE-T1x Serial Interface specification.
+
+ To compile this driver as a module, choose M here. The module will be
+ called s2500.
+
diff --git a/drivers/net/ethernet/onsemi/s2500/Makefile b/drivers/net/ethernet/onsemi/s2500/Makefile
new file mode 100644
index 000000000000..61ec705cdf9f
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the onsemi network device drivers.
+#
+obj-$(CONFIG_S2500_MACPHY) := s2500.o
+s2500-objs := s2500_main.o s2500_ethtool.o s2500_ptp.o
+
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
new file mode 100644
index 000000000000..062cab9460dc
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's S2500 10BASE-T1S MAC-PHY driver
+ */
+
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+#include "s2500_hw_def.h"
+
+#define S2500_NUM_REGS 38
+#define S2500_REGDUMP_LEN (sizeof(u32) * (S2500_NUM_REGS * 2))
+
+#define S2500_NUM_MAC_STATS 1
+#define S2500_NUM_RMON_STATS 12
+
+struct s2500_reg_map {
+ u16 reg;
+ u8 mms;
+};
+
+static const struct s2500_reg_map s2500_reg_map[S2500_NUM_REGS] = {
+ { S2500_REG_VS_ONIPVER, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_TWEAKS1, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_PLCAEXT, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_PMATUNE0, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_PMATUNE1, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_T1SWUPTUNE, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_HDD, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_DCQ_TOID, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_DCQ_SQI, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_DCQ_SQI_PLUS, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_HDD_TUNE1, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_HDD_TUNE2, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_HDD_TUNE3, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_WS_STATUS_0, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_WS_CTRL_0, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_WS_STATUS_1, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_WS_CTRL_1, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_MIIMIRQE, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_MIIMIRQS, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_DIOCFG0, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CHIPID, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_TWEAKS2, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_MACID0, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_MACID1, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_NVM_HEALTH, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_PTP_SEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_PTPNSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_PTP_SETSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_SETNSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_PTP_ADJ, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CMPCTL, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CMPSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CMPNSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CMPPER, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CAPCTL, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CAPSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CAPNSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_BCNCNT, OA_TC6_PHY_C45_VS_MMS12 },
+};
+
+enum s2500_stat_idx {
+ S2500_MSTOCTECTSTX_IDX = 0,
+ S2500_MSTFRAMESTXOK_IDX,
+ S2500_MSTBCASTTXOK_IDX,
+ S2500_MSTMCASTTXOK_IDX,
+ S2500_MSTFRAMESTX64_IDX,
+ S2500_MSTFRAMESTX65_IDX,
+ S2500_MSTFRAMESTX128_IDX,
+ S2500_MSTFRAMESTX256_IDX,
+ S2500_MSTFRAMESTX512_IDX,
+ S2500_MSTFRAMESTX1024_IDX,
+ S2500_MSTTXUNDEFLOW_IDX,
+ S2500_MSTSINGLECOL_IDX,
+ S2500_MSTMULTICOL_IDX,
+ S2500_MSTEXCESSCOL_IDX,
+ S2500_MSTDEFERREDTX_IDX,
+ S2500_MSTCRSERR_IDX,
+ S2500_MSTOCTECTSRX_IDX,
+ S2500_MSTFRAMESRXOK_IDX,
+ S2500_MSTBCASTRXOK_IDX,
+ S2500_MSTMCASTRXOK_IDX,
+ S2500_MSTFRAMESRX64_IDX,
+ S2500_MSTFRAMESRX65_IDX,
+ S2500_MSTFRAMESRX128_IDX,
+ S2500_MSTFRAMESRX256_IDX,
+ S2500_MSTFRAMESRX512_IDX,
+ S2500_MSTFRAMESRX1024_IDX,
+ S2500_MSTRUNTSERR_IDX,
+ S2500_MSTRXTOOLONG_IDX,
+ S2500_MSTFCSERRS_IDX,
+ S2500_MSTSYMBOLERRS_IDX,
+ S2500_MSTALIGNERRS_IDX,
+ S2500_MSTRXOVERFLOW_IDX,
+ S2500_MSTRXDROPPED_IDX,
+};
+
+static const char s2500_mac_stat_strings[][ETH_GSTRING_LEN] = {
+ "rx_mismatch_drops",
+};
+
+static const u8 s2500_mac_stat_map[S2500_NUM_MAC_STATS] = {
+ S2500_MSTRXDROPPED_IDX,
+};
+
+static const u8 s2500_rmon_stat_map[S2500_NUM_RMON_STATS] = {
+ S2500_MSTFRAMESTX64_IDX,
+ S2500_MSTFRAMESTX65_IDX,
+ S2500_MSTFRAMESTX128_IDX,
+ S2500_MSTFRAMESTX256_IDX,
+ S2500_MSTFRAMESTX512_IDX,
+ S2500_MSTFRAMESTX1024_IDX,
+ S2500_MSTFRAMESRX64_IDX,
+ S2500_MSTFRAMESRX65_IDX,
+ S2500_MSTFRAMESRX128_IDX,
+ S2500_MSTFRAMESRX256_IDX,
+ S2500_MSTFRAMESRX512_IDX,
+ S2500_MSTFRAMESRX1024_IDX,
+};
+
+static const struct ethtool_rmon_hist_range s2500_rmon_hist_ranges[] = {
+ { 64, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 2000 },
+ { },
+};
+
+#define S2500_MAC_STATS_LEN ARRAY_SIZE(s2500_mac_stat_strings)
+static_assert(S2500_MAC_STATS_LEN == S2500_NUM_MAC_STATS);
+static_assert(ARRAY_SIZE(s2500_mac_stat_map) == S2500_NUM_MAC_STATS);
+static_assert(ARRAY_SIZE(s2500_rmon_stat_map) == S2500_NUM_RMON_STATS);
+
+#define STAT_REG_OFFSET(x) ((S2500_REG_MAC_ST##x) - \
+ S2500_REG_MAC_FIRST_STAT)
+
+static void s2500_update_stats(struct s2500_info *priv)
+{
+ u64 *data = priv->stats_data;
+ u32 *regs, *rptr, addr;
+ int ret;
+
+ if (time_is_after_jiffies(priv->stats_read_at +
+ S2500_STATS_CACHE_PERIOD))
+ return;
+
+ regs = kmalloc_array(S2500_NUM_STAT_REGS, sizeof(u32), GFP_KERNEL);
+ if (!regs)
+ return;
+
+ addr = FIELD_PREP(S2500_REG_MMS_MASK, OA_TC6_MAC_MMS1) |
+ S2500_REG_MAC_STOCTECTSTXL;
+ ret = oa_tc6_read_registers(priv->tc6, addr, regs,
+ S2500_NUM_STAT_REGS);
+ if (ret)
+ goto out;
+
+ priv->stats_read_at = jiffies;
+ rptr = regs;
+
+ /* TX bytes is a 64-bit value that is stored in two regs.
+ * When lower 32 bits are read from OCTETS-L register,
+ * upper bits are sampled and stored in OCTETS-H register.
+ * Request to read multiple registers reads OCTETS-L first.
+ * This way, counter value is read in an atomic manner.
+ */
+ *(data++) += ((u64)*rptr) | (((u64)*(rptr + 1)) << 32);
+
+ /* Rest are 32-bit values until RX bytes register. */
+ for (rptr += 2; (rptr - regs) < STAT_REG_OFFSET(OCTECTSRXL); ++rptr)
+ *(data++) += *rptr;
+
+ /* RX bytes is a 64-bit value that is stored in two regs.
+ * Like TX bytes counter, value is read atomically from
+ * registers.
+ */
+ *(data++) += ((u64)*rptr) | (((u64)*(rptr + 1)) << 32);
+
+ for (rptr += 2; (rptr - regs) < S2500_NUM_STAT_REGS; ++rptr)
+ *(data++) += *rptr;
+out:
+ kfree(regs);
+}
+
+static void s2500_get_drvinfo(struct net_device *ndev,
+ struct ethtool_drvinfo *info)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ strscpy(info->driver, DRV_NAME, sizeof(info->driver));
+ strscpy(info->bus_info, dev_name(&priv->spi->dev),
+ sizeof(info->bus_info));
+}
+
+static int s2500_get_sset_count(struct net_device *ndev, int sset)
+{
+ switch (sset) {
+ case ETH_SS_STATS:
+ return S2500_MAC_STATS_LEN;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static void s2500_get_strings(struct net_device *ndev, u32 stringset,
+ u8 *buf)
+{
+ switch (stringset) {
+ case ETH_SS_STATS:
+ memcpy(buf, s2500_mac_stat_strings,
+ S2500_MAC_STATS_LEN * ETH_GSTRING_LEN);
+ break;
+ }
+}
+
+static void s2500_get_ethtool_stats(struct net_device *ndev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ int i;
+
+ s2500_update_stats(priv);
+ for (i = 0; i < S2500_NUM_MAC_STATS; i++)
+ data[i] = priv->stats_data[s2500_mac_stat_map[i]];
+}
+
+static void s2500_get_eth_mac_stats(struct net_device *ndev,
+ struct ethtool_eth_mac_stats *mac_stats)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ const u64 *st;
+
+ if (mac_stats->src != ETHTOOL_MAC_STATS_SRC_AGGREGATE)
+ return;
+
+ s2500_update_stats(priv);
+ st = priv->stats_data;
+
+ mac_stats->OctetsTransmittedOK = st[S2500_MSTOCTECTSTX_IDX];
+ mac_stats->FramesTransmittedOK = st[S2500_MSTFRAMESTXOK_IDX];
+ mac_stats->BroadcastFramesXmittedOK = st[S2500_MSTBCASTTXOK_IDX];
+ mac_stats->MulticastFramesXmittedOK = st[S2500_MSTMCASTTXOK_IDX];
+ mac_stats->SingleCollisionFrames = st[S2500_MSTSINGLECOL_IDX];
+ mac_stats->MultipleCollisionFrames = st[S2500_MSTMULTICOL_IDX];
+ mac_stats->FramesAbortedDueToXSColls = st[S2500_MSTEXCESSCOL_IDX];
+ mac_stats->FramesWithDeferredXmissions = st[S2500_MSTDEFERREDTX_IDX];
+ mac_stats->CarrierSenseErrors = st[S2500_MSTCRSERR_IDX];
+ mac_stats->FramesLostDueToIntMACXmitError =
+ st[S2500_MSTTXUNDEFLOW_IDX];
+ mac_stats->OctetsReceivedOK = st[S2500_MSTOCTECTSRX_IDX];
+ mac_stats->FramesReceivedOK = st[S2500_MSTFRAMESRXOK_IDX];
+ mac_stats->BroadcastFramesReceivedOK = st[S2500_MSTBCASTRXOK_IDX];
+ mac_stats->MulticastFramesReceivedOK = st[S2500_MSTMCASTRXOK_IDX];
+ mac_stats->FrameTooLongErrors = st[S2500_MSTRXTOOLONG_IDX];
+ mac_stats->FrameCheckSequenceErrors = st[S2500_MSTFCSERRS_IDX];
+ mac_stats->AlignmentErrors = st[S2500_MSTALIGNERRS_IDX];
+ mac_stats->FramesLostDueToIntMACRcvError =
+ st[S2500_MSTRXOVERFLOW_IDX];
+}
+
+static void s2500_get_eth_phy_stats(struct net_device *ndev,
+ struct ethtool_eth_phy_stats *phy_stats)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ if (phy_stats->src != ETHTOOL_MAC_STATS_SRC_AGGREGATE)
+ return;
+
+ s2500_update_stats(priv);
+ phy_stats->SymbolErrorDuringCarrier =
+ priv->stats_data[S2500_MSTSYMBOLERRS_IDX];
+}
+
+static void s2500_rmon_stats(struct net_device *ndev,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ u8 offset;
+ int i;
+
+ if (rmon_stats->src != ETHTOOL_MAC_STATS_SRC_AGGREGATE)
+ return;
+
+ s2500_update_stats(priv);
+
+ rmon_stats->undersize_pkts = priv->stats_data[S2500_MSTRUNTSERR_IDX];
+ rmon_stats->oversize_pkts = priv->stats_data[S2500_MSTRXTOOLONG_IDX];
+
+ for (i = 0; i < 6; i++) {
+ offset = s2500_rmon_stat_map[i];
+ rmon_stats->hist_tx[i] = priv->stats_data[offset];
+ }
+
+ for (i = 0; i < 6; i++) {
+ offset = s2500_rmon_stat_map[i + 6];
+ rmon_stats->hist[i] = priv->stats_data[offset];
+ }
+
+ *ranges = s2500_rmon_hist_ranges;
+}
+
+static int s2500_get_ts_info(struct net_device *ndev,
+ struct kernel_ethtool_ts_info *ts_info)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ return oa_tc6_get_ts_info(priv->tc6, ts_info);
+}
+
+static int s2500_get_regs_len(struct net_device *dev)
+{
+ return S2500_REGDUMP_LEN;
+}
+
+static void s2500_get_regs(struct net_device *ndev,
+ struct ethtool_regs *regs, void *p)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ __be32 *pbuff = p;
+ int ret = 0;
+ u32 val;
+ u16 reg;
+ u8 mms;
+ int i;
+
+ regs->version = 0;
+ memset(p, 0, S2500_REGDUMP_LEN);
+
+ if (!netif_running(ndev))
+ return;
+
+ for (i = 0; i < S2500_NUM_REGS; i++) {
+ val = 0;
+ reg = s2500_reg_map[i].reg;
+ mms = s2500_reg_map[i].mms;
+ ret = oa_tc6_read_register_mms(priv->tc6, mms, reg,
+ &val);
+
+ /* Write a pattern to indicate failure */
+ if (ret)
+ val = 0xDEADC0DE;
+ *pbuff++ = cpu_to_be32(((u32)mms << 16) | reg);
+ *pbuff++ = cpu_to_be32(val);
+ }
+}
+
+static void s2500_get_ts_stats(struct net_device *ndev,
+ struct ethtool_ts_stats *ts_stats)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ oa_tc6_get_ts_stats(priv->tc6, ts_stats);
+}
+
+const struct ethtool_ops s2500_ethtool_ops = {
+ .get_drvinfo = s2500_get_drvinfo,
+ .get_link = ethtool_op_get_link,
+ .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .set_link_ksettings = phy_ethtool_set_link_ksettings,
+ .get_ts_stats = s2500_get_ts_stats,
+ .get_sset_count = s2500_get_sset_count,
+ .get_strings = s2500_get_strings,
+ .get_ethtool_stats = s2500_get_ethtool_stats,
+ .get_eth_mac_stats = s2500_get_eth_mac_stats,
+ .get_eth_phy_stats = s2500_get_eth_phy_stats,
+ .get_rmon_stats = s2500_rmon_stats,
+ .get_ts_info = s2500_get_ts_info,
+ .get_regs_len = s2500_get_regs_len,
+ .get_regs = s2500_get_regs,
+};
+
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h b/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h
new file mode 100644
index 000000000000..0d0b335a397a
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's S2500 10BASE-T1S MAC-PHY driver
+ */
+
+#ifndef S2500_HW_DEF_H
+#define S2500_HW_DEF_H
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/spi/spi.h>
+#include <linux/phylink.h>
+#include <linux/net_tstamp.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/ktime.h>
+#include <linux/errno.h>
+#include <linux/oa_tc6.h>
+
+#define DRV_NAME "s2500"
+
+#define S2500_N_MCAST_FILTERS 3
+
+#define S2500_REG_MMS_MASK GENMASK(19, 16)
+
+/* SPI OID and model register */
+#define S2500_REG_PHYID 0x1
+
+#define S2500_PHYID_OUI_SHIFT 10
+#define S2500_PHYID_OUI_MASK GENMASK(31, S2500_PHYID_OUI_SHIFT)
+#define S2500_PHYID_OUI_MASK0 GENMASK(21, 16)
+#define S2500_PHYID_OUI_MASK1 GENMASK(15, 8)
+#define S2500_PHYID_OUI_MASK2 GENMASK(7, 0)
+
+#define S2500_CONFIG0_TXCTHRESH_SHIFT 10
+#define S2500_CONFIG0_RXCTE BIT(8)
+#define S2500_CONFIG0_CPS_SHIFT 0
+#define S2500_CONFIG0_TXCTHRESH_8 0x2
+#define S2500_CONFIG0_CPS_64 0x6
+
+/* Read only bit in STATUS0 register */
+#define S2500_STATUS0_PHY_INT BIT(7)
+
+#define S2500_REG_MAC_CTRL 0x0
+
+#define S2500_MAC_CTRL_MCSF BIT(18)
+#define S2500_MAC_CTRL_ADRF BIT(16)
+#define S2500_MAC_CTRL_FCSA BIT(8)
+#define S2500_MAC_CTRL_TXEN BIT(1)
+#define S2500_MAC_CTRL_RXEN BIT(0)
+
+/* MAC address filter registers */
+#define S2500_REG_MAC_ADDRFILTL(n) (16 + 2 * (n))
+#define S2500_REG_MAC_ADDRFILTH(n) (17 + 2 * (n))
+#define S2500_REG_MAC_ADDRMASKL(n) (32 + 2 * (n))
+#define S2500_REG_MAC_ADDRMASKH(n) (33 + 2 * (n))
+
+#define S2500_MAC_ADDRFILT_ENABLE BIT(31)
+
+/* MAC statistic registers */
+#define S2500_REG_MAC_STOCTECTSTXL 48
+#define S2500_REG_MAC_STOCTECTSTXH 49
+#define S2500_REG_MAC_STOCTECTSRXL 65
+#define S2500_REG_MAC_STOCTECTSRXH 66
+#define S2500_REG_MAC_STRXDROPPED 82
+
+/* First/last statistic register for sequential access */
+#define S2500_REG_MAC_FIRST_STAT S2500_REG_MAC_STOCTECTSTXL
+#define S2500_REG_MAC_LAST_STAT S2500_REG_MAC_STRXDROPPED
+
+#define S2500_NUM_STAT_REGS \
+ (S2500_REG_MAC_LAST_STAT - S2500_REG_MAC_FIRST_STAT + 1)
+#define S2500_NUM_STAT_VARS (S2500_NUM_STAT_REGS - 2)
+#define S2500_STATS_CACHE_PERIOD msecs_to_jiffies(100)
+
+/* Vendor specific MMS4 registers */
+#define S2500_REG_VS_ONIPVER 0x8000
+#define S2500_REG_VS_TWEAKS1 0x8001
+#define S2500_REG_VS_PLCAEXT 0x8002
+#define S2500_REG_VS_PMATUNE0 0x8003
+#define S2500_REG_VS_PMATUNE1 0x8004
+#define S2500_REG_VS_T1SWUPTUNE 0x8007
+#define S2500_REG_VS_HDD 0xCC01
+#define S2500_REG_VS_DCQ_TOID 0xCC02
+#define S2500_REG_VS_DCQ_SQI 0xCC03
+#define S2500_REG_VS_DCQ_SQI_PLUS 0xCC04
+#define S2500_REG_VS_HDD_TUNE1 0xCD00
+#define S2500_REG_VS_HDD_TUNE2 0xCD01
+#define S2500_REG_VS_HDD_TUNE3 0xCD02
+#define S2500_REG_VS_WS_STATUS_0 0xD000
+#define S2500_REG_VS_WS_CTRL_0 0xD001
+#define S2500_REG_VS_WS_STATUS_1 0xD100
+#define S2500_REG_VS_WS_CTRL_1 0xD101
+
+/* Vendor specific MMS12 registers */
+#define S2500_REG_VS_MIIMIRQE 0x10
+
+/* MIIM IRQ status register */
+#define S2500_REG_VS_MIIMIRQS 0x11
+
+#define S2500_REG_VS_DIOCFG0 0x12
+
+#define S2500_REG_VS_CHIPID 0x1000
+#define S2500_REG_VS_TWEAKS2 0x1001
+
+/* Permanent MAC address register */
+#define S2500_REG_VS_MACID0 0x1002
+#define S2500_VS_MACID0_BYTE4_MASK GENMASK(15, 8)
+#define S2500_VS_MACID0_BYTE5_MASK GENMASK(7, 0)
+
+#define S2500_REG_VS_MACID1 0x1003
+#define S2500_VS_MACID1_BYTE3_MASK GENMASK(7, 0)
+#define S2500_REG_VS_NVM_HEALTH 0x1005
+
+/* PTP registers */
+#define S2500_REG_VS_PTP_SEC 0x1010
+#define S2500_REG_VS_PTP_SETSEC 0x1012
+#define S2500_REG_VS_PTP_ADJ 0x1014
+
+/* Frequency adjust magnitude, 1 LSB = 0.000125 ppm = 1/8 ppb */
+#define S2500_PTP_ADJ_MAX GENMASK(27, 0)
+#define S2500_PTP_MAX_ADJ_PPB (S2500_PTP_ADJ_MAX / 8)
+#define S2500_REG_VS_PTPNSEC 0x1011
+#define S2500_REG_VS_SETNSEC 0x1013
+#define S2500_REG_VS_CMPCTL 0x1015
+#define S2500_REG_VS_CMPSEC 0x1016
+#define S2500_REG_VS_CMPNSEC 0x1017
+#define S2500_REG_VS_CMPPER 0x1018
+#define S2500_REG_VS_CAPCTL 0x1019
+#define S2500_REG_VS_CAPSEC 0x101A
+#define S2500_REG_VS_CAPNSEC 0x101B
+#define S2500_REG_VS_BCNCNT 0x101C
+
+/* prototypes / forward declarations */
+extern const struct ethtool_ops s2500_ethtool_ops;
+
+struct s2500_info {
+ struct device *dev;
+ struct net_device *ndev;
+
+ /* To have atomic operation for PTP time registers */
+ struct mutex ptp_adj_lock;
+
+ struct spi_device *spi;
+
+ /* statistic counters variables */
+ u64 stats_data[S2500_NUM_STAT_VARS];
+ unsigned long stats_read_at;
+
+ /* PTP related variables */
+ struct ptp_clock_info ptp_clock_info;
+ struct oa_tc6 *tc6;
+};
+
+int s2500_ptp_register(struct s2500_info *priv);
+
+#endif /* S2500_HW_DEF_H */
+
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_main.c b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
new file mode 100644
index 000000000000..4cd0f5d10666
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
@@ -0,0 +1,523 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's S2500 10BASE-T1S MAC-PHY driver
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/if_ether.h>
+#include <linux/irqchip.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/bitrev.h>
+#include <linux/phy.h>
+
+#include "s2500_hw_def.h"
+
+/* Initializes the net device MAC address by reading the UID stored
+ * into the device internal non-volatile memory.
+ */
+static int s2500_read_mac_from_nvmem(struct s2500_info *priv)
+{
+ u8 mms = OA_TC6_PHY_C45_VS_MMS12;
+ u8 addr[ETH_ALEN];
+ u32 mac1 = 0;
+ u32 mac0 = 0;
+ u32 val;
+ int ret;
+
+ ret = oa_tc6_read_register_mms(priv->tc6, mms,
+ S2500_REG_VS_MACID1, &mac1);
+ if (ret)
+ return ret;
+
+ ret = oa_tc6_read_register_mms(priv->tc6, mms,
+ S2500_REG_VS_MACID0, &mac0);
+ if (ret)
+ return ret;
+
+ /* Pre-production parts may have 0 */
+ if (mac0 == 0 && mac1 == 0)
+ return -ENXIO;
+
+ ret = oa_tc6_read_register(priv->tc6, S2500_REG_PHYID, &val);
+ if (ret)
+ return ret;
+
+ val = (val & S2500_PHYID_OUI_MASK) >> S2500_PHYID_OUI_SHIFT;
+
+ /* Mapping for bits from PHY ID register to OUI as give below.
+ * - PHY-ID[10:17] provides OUI[16:23]
+ * - PHY-ID[18:25] provides OUI[8:15]
+ * - PHY-ID[26:31] provides OUI[2:7]
+ *
+ * Hardware presents OUI such a way that this requires not just
+ * byte level swap, bit level swap is needed as well.
+ */
+ addr[0] = bitrev8(FIELD_GET(S2500_PHYID_OUI_MASK0, val));
+ addr[1] = bitrev8(FIELD_GET(S2500_PHYID_OUI_MASK1, val));
+ addr[2] = bitrev8(FIELD_GET(S2500_PHYID_OUI_MASK2, val));
+
+ addr[3] = FIELD_GET(S2500_VS_MACID1_BYTE3_MASK, mac1);
+ addr[4] = FIELD_GET(S2500_VS_MACID0_BYTE4_MASK, mac0);
+ addr[5] = FIELD_GET(S2500_VS_MACID0_BYTE5_MASK, mac0);
+
+ __dev_addr_set(priv->ndev, addr, ETH_ALEN);
+ priv->ndev->addr_assign_type = NET_ADDR_PERM;
+ return ret;
+}
+
+/* Writes MAC address to macphy registers */
+static int s2500_set_mac_filter(struct net_device *ndev, const u8 *mac)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ u8 mms = OA_TC6_MAC_MMS1;
+ u32 val;
+ int ret;
+
+ /* Set unicast address filter */
+ ret = oa_tc6_write_register_mms(priv->tc6, mms,
+ S2500_REG_MAC_ADDRMASKL(0),
+ 0xffffffff);
+ if (ret)
+ return ret;
+
+ ret = oa_tc6_write_register_mms(priv->tc6, mms,
+ S2500_REG_MAC_ADDRMASKH(0),
+ 0xffff);
+ if (ret)
+ return ret;
+
+ val = get_unaligned_be32(&mac[2]);
+
+ ret = oa_tc6_write_register_mms(priv->tc6, mms,
+ S2500_REG_MAC_ADDRFILTL(0),
+ val);
+ if (ret)
+ return ret;
+
+ val = S2500_MAC_ADDRFILT_ENABLE | get_unaligned_be16(mac);
+
+ return oa_tc6_write_register_mms(priv->tc6, mms,
+ S2500_REG_MAC_ADDRFILTH(0),
+ val);
+}
+
+static int s2500_mac_ctrl_modify_bits(struct s2500_info *priv,
+ u32 in_bits, bool clr)
+{
+ u32 reg = S2500_REG_MAC_CTRL;
+ u8 mms = OA_TC6_MAC_MMS1;
+ u32 rval = 0;
+ u32 wval = 0;
+ int ret;
+
+ ret = oa_tc6_read_register_mms(priv->tc6, mms, reg, &rval);
+ if (ret)
+ return ret;
+
+ if (clr)
+ wval = rval & ~in_bits;
+ else
+ wval = rval | in_bits;
+ if (rval != wval)
+ ret = oa_tc6_write_register_mms(priv->tc6, mms,
+ reg, wval);
+ return ret;
+}
+
+static int s2500_init(struct s2500_info *priv)
+{
+ u32 val;
+ int ret;
+
+ /* Configure MAC-PHY's behavior */
+ val = OA_TC6_CONFIG0_SYNC | S2500_CONFIG0_RXCTE |
+ (S2500_CONFIG0_TXCTHRESH_8 <<
+ S2500_CONFIG0_TXCTHRESH_SHIFT) |
+ (S2500_CONFIG0_CPS_64 << S2500_CONFIG0_CPS_SHIFT) |
+ OA_TC6_CONFIG0_FTSS_64BIT_TSTAMP;
+
+ ret = oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG0,
+ val);
+ if (ret)
+ return ret;
+
+ /* Program the source MAC address into the device */
+ ret = s2500_set_mac_filter(priv->ndev, priv->ndev->dev_addr);
+ if (ret)
+ return ret;
+
+ val = S2500_MAC_CTRL_ADRF | S2500_MAC_CTRL_FCSA;
+
+ return s2500_mac_ctrl_modify_bits(priv, val, false);
+}
+
+static void s2500_shutdown(struct s2500_info *priv)
+{
+ u32 val = S2500_MAC_CTRL_TXEN | S2500_MAC_CTRL_RXEN;
+ struct net_device *ndev = priv->ndev;
+
+ netif_stop_queue(ndev);
+ phy_stop(ndev->phydev);
+
+ s2500_mac_ctrl_modify_bits(priv, val, true);
+}
+
+static int s2500_set_promiscuous_mode(struct s2500_info *priv,
+ unsigned int rx_flags)
+{
+ u32 val = S2500_MAC_CTRL_ADRF;
+ bool clr = false;
+
+ if (rx_flags & IFF_PROMISC)
+ clr = true;
+ return s2500_mac_ctrl_modify_bits(priv, val, clr);
+}
+
+static int s2500_set_multicast_mode(struct s2500_info *priv,
+ unsigned int rx_flags,
+ struct netdev_hw_addr_list *mc)
+{
+ struct netdev_hw_addr *ha;
+ u8 mms = OA_TC6_MAC_MMS1;
+ int i, ret = 0;
+ int mc_count;
+ u16 addr;
+ u32 val;
+
+ mc_count = netdev_hw_addr_list_count(mc);
+
+ if ((rx_flags & IFF_ALLMULTI) ||
+ mc_count > S2500_N_MCAST_FILTERS) {
+ /* Disable multicast filter */
+ ret = s2500_mac_ctrl_modify_bits(priv,
+ S2500_MAC_CTRL_MCSF,
+ true);
+ if (ret)
+ return ret;
+
+ /* Accept all multicasts */
+ addr = S2500_REG_MAC_ADDRMASKL(1);
+ ret = oa_tc6_write_register_mms(priv->tc6, mms,
+ addr, 0);
+ if (ret)
+ return ret;
+
+ addr = S2500_REG_MAC_ADDRMASKH(1);
+ ret = oa_tc6_write_register_mms(priv->tc6, mms,
+ addr, 0x100);
+ if (ret)
+ return ret;
+
+ addr = S2500_REG_MAC_ADDRFILTL(1);
+ ret = oa_tc6_write_register_mms(priv->tc6, mms,
+ addr, 0);
+ if (ret)
+ return ret;
+
+ val = S2500_MAC_ADDRFILT_ENABLE | 0x00000100;
+ addr = S2500_REG_MAC_ADDRFILTH(1);
+ ret = oa_tc6_write_register_mms(priv->tc6, mms,
+ addr, val);
+ } else if (mc_count == 0) {
+ /* Enable multicast filter */
+ ret = s2500_mac_ctrl_modify_bits(priv,
+ S2500_MAC_CTRL_MCSF,
+ false);
+ if (ret)
+ return ret;
+
+ /* Disable filters */
+ for (i = 1; i <= S2500_N_MCAST_FILTERS; i++) {
+ addr = S2500_REG_MAC_ADDRFILTH(i);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ mms, addr, 0);
+ if (ret)
+ return ret;
+ }
+ } else {
+ u32 addrh, addrl;
+
+ /* Disable multicast filter */
+ ret = s2500_mac_ctrl_modify_bits(priv,
+ S2500_MAC_CTRL_MCSF,
+ true);
+ if (ret)
+ return ret;
+
+ /* Disable filters */
+ for (i = 1; i <= S2500_N_MCAST_FILTERS; i++) {
+ addr = S2500_REG_MAC_ADDRFILTH(i);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ mms, addr, 0);
+ if (ret)
+ return ret;
+ }
+
+ /* Slot 0 holds the unicast MAC, multicast starts at 1 */
+ i = 1;
+ netdev_hw_addr_list_for_each(ha, mc) {
+ const u8 *mac = ha->addr;
+ int slot = i++;
+
+ addrh = S2500_MAC_ADDRFILT_ENABLE |
+ get_unaligned_be16(mac);
+ addrl = get_unaligned_be32(&mac[2]);
+
+ addr = S2500_REG_MAC_ADDRFILTH(slot);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ mms, addr,
+ addrh);
+ if (ret)
+ return ret;
+
+ addr = S2500_REG_MAC_ADDRFILTL(slot);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ mms, addr,
+ addrl);
+ if (ret)
+ return ret;
+
+ addr = S2500_REG_MAC_ADDRMASKL(slot);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ mms, addr,
+ 0xffffffff);
+ if (ret)
+ return ret;
+
+ addr = S2500_REG_MAC_ADDRMASKH(slot);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ mms, addr,
+ 0xffff);
+ if (ret)
+ return ret;
+ }
+ }
+ return ret;
+}
+
+static int s2500_set_rx_mode(struct net_device *ndev,
+ struct netdev_hw_addr_list *uc,
+ struct netdev_hw_addr_list *mc)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ int ret;
+
+ ret = s2500_set_promiscuous_mode(priv, ndev->flags);
+ if (ret)
+ return ret;
+
+ return s2500_set_multicast_mode(priv, ndev->flags, mc);
+}
+
+static int s2500_set_mac_address(struct net_device *ndev, void *p)
+{
+ struct sockaddr *addr = p;
+ int ret;
+
+ if (!is_valid_ether_addr(addr->sa_data))
+ return -EADDRNOTAVAIL;
+
+ ret = s2500_set_mac_filter(ndev, addr->sa_data);
+ if (ret)
+ return ret;
+
+ eth_hw_addr_set(ndev, addr->sa_data);
+ return ret;
+}
+
+static netdev_tx_t s2500_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ return oa_tc6_start_xmit(priv->tc6, skb);
+}
+
+static int s2500_open(struct net_device *ndev)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ u32 val;
+ int ret;
+
+ dev_dbg(&ndev->dev, "%s", "s2500_open\n");
+ phy_start(priv->ndev->phydev);
+
+ val = S2500_MAC_CTRL_TXEN | S2500_MAC_CTRL_RXEN;
+ ret = s2500_mac_ctrl_modify_bits(priv, val, false);
+ if (!ret)
+ netif_start_queue(priv->ndev);
+ else
+ phy_stop(priv->ndev->phydev);
+ return ret;
+}
+
+static int s2500_stop(struct net_device *ndev)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ dev_dbg(&ndev->dev, "%s", "s2500_stop\n");
+
+ s2500_shutdown(priv);
+ return 0;
+}
+
+static int s2500_hwtstamp_get(struct net_device *ndev,
+ struct kernel_hwtstamp_config *k_cfg)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ oa_tc6_hwtstamp_get(priv->tc6, k_cfg);
+ return 0;
+}
+
+static int s2500_hwtstamp_set(struct net_device *ndev,
+ struct kernel_hwtstamp_config *cfg,
+ struct netlink_ext_ack *extack)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ return oa_tc6_hwtstamp_set(priv->tc6, cfg);
+}
+
+static const struct net_device_ops s2500_netdev_ops = {
+ .ndo_open = s2500_open,
+ .ndo_stop = s2500_stop,
+ .ndo_start_xmit = s2500_start_xmit,
+ .ndo_set_mac_address = s2500_set_mac_address,
+ .ndo_set_rx_mode_async = s2500_set_rx_mode,
+ .ndo_eth_ioctl = phy_do_ioctl_running,
+ .ndo_hwtstamp_get = s2500_hwtstamp_get,
+ .ndo_hwtstamp_set = s2500_hwtstamp_set,
+};
+
+static int s2500_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct net_device *ndev;
+ struct s2500_info *priv;
+ u32 val;
+ int ret;
+
+ if (spi->irq < 0)
+ return -ENODEV;
+
+ ndev = devm_alloc_etherdev(dev, sizeof(struct s2500_info));
+ if (!ndev)
+ return -ENOMEM;
+
+ priv = netdev_priv(ndev);
+ priv->ndev = ndev;
+ priv->spi = spi;
+ priv->dev = dev;
+
+ SET_NETDEV_DEV(ndev, dev);
+
+ mutex_init(&priv->ptp_adj_lock);
+ ndev->irq = spi->irq;
+
+ /* Ensures that the first read is not from the cache. */
+ priv->stats_read_at = jiffies - S2500_STATS_CACHE_PERIOD;
+
+ spi_set_drvdata(spi, priv);
+
+ ndev->netdev_ops = &s2500_netdev_ops;
+ ndev->ethtool_ops = &s2500_ethtool_ops;
+ ndev->if_port = IF_PORT_10BASET;
+
+ priv->tc6 = oa_tc6_init(spi, ndev, NULL);
+ if (IS_ERR(priv->tc6)) {
+ ret = PTR_ERR(priv->tc6);
+ dev_err(&spi->dev, "OA TC6 init failed\n");
+ return ret;
+ }
+ oa_tc6_set_vend1_mms(priv->tc6, OA_TC6_PHY_C45_VS_MMS12);
+
+ /* All read-only bits. Some are self clear. No need to write */
+ ret = oa_tc6_read_register_mms(priv->tc6, OA_TC6_PHY_C45_VS_MMS12,
+ S2500_REG_VS_MIIMIRQS, &val);
+ if (ret) {
+ dev_err(&spi->dev, "Failed to read MIIM IRQ status\n");
+ goto err_reg_read;
+ }
+
+ /* Acknowledge all IRQ status bits */
+ ret = oa_tc6_read_register(priv->tc6,
+ OA_TC6_REG_STATUS0, &val);
+ if (!ret) {
+ /* Clear read only bit */
+ val &= ~S2500_STATUS0_PHY_INT;
+ oa_tc6_write_register(priv->tc6,
+ OA_TC6_REG_STATUS0, val);
+ }
+
+ ret = device_get_ethdev_address(priv->dev, ndev);
+ if (ret)
+ ret = s2500_read_mac_from_nvmem(priv);
+
+ if (ret) {
+ eth_hw_addr_random(ndev);
+ dev_warn(&spi->dev, "Using random MAC address %pM\n",
+ ndev->dev_addr);
+ }
+
+ ret = s2500_init(priv);
+ if (ret) {
+ dev_err(&spi->dev, "Failure seen in s2500_init\n");
+ goto err_reg_read;
+ }
+
+ /* Configure PTP as S2500 supports it */
+ ret = s2500_ptp_register(priv);
+ if (ret)
+ goto err_reg_read;
+
+ ret = register_netdev(ndev);
+ if (!ret)
+ return ret;
+ dev_err(&spi->dev, "Failed to register netdev\n");
+ oa_tc6_ptp_unregister(priv->tc6);
+err_reg_read:
+ oa_tc6_exit(priv->tc6);
+ return ret;
+}
+
+static void s2500_remove(struct spi_device *spi)
+{
+ struct s2500_info *priv = spi_get_drvdata(spi);
+
+ dev_dbg(&priv->ndev->dev, "%s", "s2500_remove\n");
+
+ unregister_netdev(priv->ndev);
+ oa_tc6_ptp_unregister(priv->tc6);
+ oa_tc6_exit(priv->tc6);
+}
+
+static const struct of_device_id s2500_of_match[] = {
+ { .compatible = "onnn,s2500" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, s2500_of_match);
+
+static const struct spi_device_id s2500_ids[] = {
+ { "s2500" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, s2500_ids);
+
+static struct spi_driver s2500_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = s2500_of_match,
+ },
+ .probe = s2500_probe,
+ .remove = s2500_remove,
+ .id_table = s2500_ids,
+};
+module_spi_driver(s2500_driver);
+
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
+MODULE_AUTHOR("Piergiorgio Beruto");
+MODULE_DESCRIPTION("onsemi MACPHY ethernet driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
new file mode 100644
index 000000000000..2bcf69c0d6a2
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's S2500 10BASE-T1S MAC-PHY driver
+ */
+
+#include "s2500_hw_def.h"
+
+static int s2500_ptp_get_time64(struct ptp_clock_info *ptp,
+ struct timespec64 *ts,
+ struct ptp_system_timestamp *ptp_sts)
+{
+ struct s2500_info *priv = container_of(ptp, struct s2500_info,
+ ptp_clock_info);
+ u32 data[2];
+ u32 addr;
+ int ret;
+
+ ptp_read_system_prets(ptp_sts);
+ addr = FIELD_PREP(S2500_REG_MMS_MASK,
+ OA_TC6_PHY_C45_VS_MMS12) |
+ S2500_REG_VS_PTP_SEC;
+ ret = oa_tc6_read_registers(priv->tc6, addr, &data[0], 2);
+ ptp_read_system_postts(ptp_sts);
+
+ if (!ret) {
+ ts->tv_sec = data[0];
+ ts->tv_nsec = data[1];
+ }
+
+ return ret;
+}
+
+static int s2500_ptp_write_time(struct s2500_info *priv,
+ const struct timespec64 *ts)
+{
+ u32 data[2];
+ u32 addr;
+
+ lockdep_assert_held(&priv->ptp_adj_lock);
+
+ if (ts->tv_sec >= (1ULL << 32))
+ return -ERANGE;
+
+ data[0] = (u32)ts->tv_sec;
+ /* bit 31 = execute set command */
+ data[1] = ts->tv_nsec | BIT(31);
+ addr = FIELD_PREP(S2500_REG_MMS_MASK,
+ OA_TC6_PHY_C45_VS_MMS12) |
+ S2500_REG_VS_PTP_SETSEC;
+
+ return oa_tc6_write_registers(priv->tc6, addr, &data[0], 2);
+}
+
+static int s2500_ptp_set_time64(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct s2500_info *priv = container_of(ptp, struct s2500_info,
+ ptp_clock_info);
+ int ret;
+
+ mutex_lock(&priv->ptp_adj_lock);
+ ret = s2500_ptp_write_time(priv, ts);
+ mutex_unlock(&priv->ptp_adj_lock);
+
+ return ret;
+}
+
+static int s2500_ptp_set_freq(struct s2500_info *priv, long scaled_ppm)
+{
+ u32 sign_bit = 0;
+ long adj;
+ u32 val;
+ u64 ppm;
+
+ if (scaled_ppm < 0) {
+ /* split sign / mod */
+ sign_bit = 1U << 31;
+ scaled_ppm = ~scaled_ppm + 1;
+ }
+
+ /*
+ * Convert unsigned scaled_ppm to atto-seconds per clock cycles.
+ * The scaled_ppm format is Qx.16 --> 1 lsb = 1/65536 ppm.
+ * The clock period of the S2500 is 8ns (125 MHz), so 1 lsb of
+ * adj register LSB is 1 atto-sec / 8ns = 0.000125 ppm.
+ * Represented in Qx.16 format, this is 0.000125 * 2^16 = 8(.192)
+ * To convert scaled_ppm into a register value we need to divide
+ * it by the LSB value, hence adj = (scaled_ppm * 1000) / 8192 to
+ * minimize the precision loss due to the integer arithmetic.
+ * That further reduces to (scaled_ppm * 125) / 1024.
+ */
+ ppm = (u64)scaled_ppm * 125;
+ do_div(ppm, 1024);
+ adj = (long)ppm;
+
+ /* check overflow */
+ if (adj > S2500_PTP_ADJ_MAX)
+ return -ERANGE;
+
+ val = (u32)adj | sign_bit;
+ return oa_tc6_write_register_mms(priv->tc6, OA_TC6_PHY_C45_VS_MMS12,
+ S2500_REG_VS_PTP_ADJ, val);
+}
+
+static int s2500_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct s2500_info *priv = container_of(ptp, struct s2500_info,
+ ptp_clock_info);
+
+ return s2500_ptp_set_freq(priv, scaled_ppm);
+}
+
+static int s2500_ptp_step_time(struct s2500_info *priv, s64 delta)
+{
+ struct timespec64 target, now, delta_ts;
+ struct ptp_system_timestamp sts;
+ int ret;
+
+ memset(&sts, 0, sizeof(sts));
+ ret = s2500_ptp_get_time64(&priv->ptp_clock_info, &now, &sts);
+ if (ret)
+ return ret;
+ if (delta >= 0) {
+ delta_ts = ns_to_timespec64(delta);
+ target = timespec64_add(now, delta_ts);
+ } else {
+ delta_ts = ns_to_timespec64(-delta);
+ target = timespec64_sub(now, delta_ts);
+ }
+ /* The subtraction above can take the target below the epoch */
+ if (target.tv_sec < 0)
+ return -ERANGE;
+ return s2500_ptp_write_time(priv, &target);
+}
+
+static int s2500_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct s2500_info *priv = container_of(ptp, struct s2500_info,
+ ptp_clock_info);
+ int ret;
+
+ /* Nothing to do */
+ if (delta == 0)
+ return 0;
+
+ /* The device has no add-delta register, so the offset is applied as
+ * a read-modify-write of the time registers.
+ */
+ mutex_lock(&priv->ptp_adj_lock);
+ ret = s2500_ptp_step_time(priv, delta);
+ mutex_unlock(&priv->ptp_adj_lock);
+
+ return ret;
+}
+
+int s2500_ptp_register(struct s2500_info *priv)
+{
+ struct ptp_clock_info *info = &priv->ptp_clock_info;
+ int ret;
+
+ info->max_adj = S2500_PTP_MAX_ADJ_PPB;
+ info->owner = THIS_MODULE;
+ info->adjfine = s2500_ptp_adjfine;
+ info->gettimex64 = s2500_ptp_get_time64;
+ info->settime64 = s2500_ptp_set_time64;
+ info->adjtime = s2500_ptp_adjtime;
+
+ ret = oa_tc6_ptp_register(priv->tc6, info);
+ if (ret)
+ dev_err(&priv->spi->dev, "PTP registration failed\n");
+ return ret;
+}
+
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 88e7d5491b20..86075ee113fe 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -75,6 +75,7 @@
#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */
+#define OA_TC6_PHY_C45_VS_MMS12 12 /* for vendors */
struct oa_tc6;
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH net-next v9 08/11] onsemi: s2500: Add driver support for S2500 MAC-PHY
2026-10-09 17:12 ` [PATCH net-next v9 08/11] onsemi: s2500: Add driver support for S2500 MAC-PHY Selvamani Rajagopal via B4 Relay
@ 2026-10-09 23:02 ` Randy Dunlap
2026-10-10 19:40 ` netdev-bot+sashiko
1 sibling, 0 replies; 25+ messages in thread
From: Randy Dunlap @ 2026-10-09 23:02 UTC (permalink / raw)
To: Selvamani.Rajagopal, Andrew Lunn, David S. Miller,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Parthiban Veerasooran, Simon Horman,
Jonathan Corbet, Shuah Khan, Richard Cochran, Andrew Lunn,
Heiner Kallweit, Russell King, Eric Dumazet, Piergiorgio Beruto
Cc: netdev, devicetree, linux-kernel, linux-doc, Jerry Ray, Ciprian Regus
On 10/9/26 10:12 AM, Selvamani Rajagopal via B4 Relay wrote:
> diff --git a/drivers/net/ethernet/onsemi/s2500/Kconfig b/drivers/net/ethernet/onsemi/s2500/Kconfig
> new file mode 100644
> index 000000000000..abd967448af0
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/Kconfig
> @@ -0,0 +1,21 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# onsemi S2500 Driver Support
> +#
> +
> +config S2500_MACPHY
> + tristate "S2500 support"
> + depends on SPI
> + depends on PTP_1588_CLOCK_OPTIONAL
> + imply NET_SELFTESTS
> + select BITREVERSE
> + select NCN26000_PHY
> + select OA_TC6
> + help
> + Support for the onsemi S2500 MACPHY Ethernet chip.
> + It works under the framework that conform to OPEN Alliance
conforms
> + 10BASE-T1x Serial Interface specification.
> +
> + To compile this driver as a module, choose M here. The module will be
> + called s2500.
--
~Randy
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v9 08/11] onsemi: s2500: Add driver support for S2500 MAC-PHY
2026-10-09 17:12 ` [PATCH net-next v9 08/11] onsemi: s2500: Add driver support for S2500 MAC-PHY Selvamani Rajagopal via B4 Relay
2026-10-09 23:02 ` Randy Dunlap
@ 2026-10-10 19:40 ` netdev-bot+sashiko
1 sibling, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:40 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: andrew+netdev, davem, kuba, pabeni, robh, krzk+dt, conor+dt,
parthiban.veerasooran, horms, corbet, skhan, rdunlap,
richardcochran, andrew, hkallweit1, linux, edumazet,
piergiorgio.beruto, netdev, devicetree, linux-kernel, linux-doc,
jerry.ray, ciprian.regus
Thank you for your contribution! Sashiko AI review found 12 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 6 · Low: 6
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The only driver-private `ethtool -S` counter,
"rx_mismatch_drops", duplicates a standard uAPI counter.
- [Medium] `s2500_init()` writes `OA_TC6_REG_CONFIG0` with a value built
from scratch (`SYNC | RXCTE | TXCTHRESH_8 | CPS_64 |…
- [Medium] Teardown ordering in `s2500_remove()` allows uninitialized
stack data to be delivered as a TX hardware timestamp.
- [Medium] Turning TX timestamping off (`s2500_hwtstamp_set()` →
`oa_tc6_hwtstamp_set()` → `oa_tc6_set_hwtstamp_settings()`) can race…
- [Medium] `s2500_init()` enables receive cut-through
(`S2500_CONFIG0_RXCTE`, CONFIG0 bit 8), but the OA TC6 framework never…
- [Medium] net: ptp: max_adj exceeds 32-bit scaled_ppm limit
- [Low] In `s2500_probe()`, the check `if (spi->irq < 0) return -ENODEV;`
can never be true.
- [Low] In the 1..3-address branch of `s2500_set_multicast_mode()`, each
slot's `ADDRFILTH(slot)` is written with `S2500_MAC_ADDRFILT_ENABLE`…
- [Low] `s2500_ptp_step_time()` (used by `s2500_ptp_adjtime()`) reads
PTP_SEC/NSEC in one SPI control transaction, adds `delta` in software,…
- [Low] On 32-bit kernels, `s2500_ptp_set_freq()` rejects the valid
adjustment `scaled_ppm == LONG_MIN` (-2147483648, about -32768000 ppb).
- [Low] `s2500_probe()` acknowledges STATUS0
(`oa_tc6_read_register(STATUS0)` then `oa_tc6_write_register(STATUS0,…
- [Low] `s2500_update_stats()` adds the hardware MAC statistic registers
into `priv->stats_data[]` with `+=`, which assumes they clear on read.
--
onsemi: s2500: Add driver support for S2500 MAC-PHY
This adds a new SPI driver for the onsemi S2500 10BASE-T1S MAC-PHY,
built on the OA TC6 framework. It includes PTP hardware clock and
timestamping support, ethtool MAC/RMON/PHY stats, a register dump, and up
to three multicast address filters.
> diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
> new file mode 100644
> index 0000000000000..062cab9460dcf
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
> @@ -0,0 +1,376 @@
[ ... ]
> +static const char s2500_mac_stat_strings[][ETH_GSTRING_LEN] = {
> + "rx_mismatch_drops",
> +};
> +
> +static const u8 s2500_mac_stat_map[S2500_NUM_MAC_STATS] = {
> + S2500_MSTRXDROPPED_IDX,
> +};
[Severity: Medium]
Does "rx_mismatch_drops" duplicate a counter that already has a standard
uAPI?
This string maps to S2500_MSTRXDROPPED_IDX (S2500_REG_MAC_STRXDROPPED).
That register counts frames dropped because the destination MAC did not
match. struct rtnl_link_stats64 already has rx_otherhost_dropped, which
include/uapi/linux/if_link.h documents as:
@rx_otherhost_dropped: Number of packets dropped due to mismatch
in destination MAC address.
The driver doesn't implement ndo_get_stats64. That means the value is
only visible through ethtool -S and not through ip -s link.
Could this be reported through rtnl_link_stats64.rx_otherhost_dropped
instead, with the private ETH_SS_STATS string dropped?
[ ... ]
> +static void s2500_update_stats(struct s2500_info *priv)
> +{
> + u64 *data = priv->stats_data;
> + u32 *regs, *rptr, addr;
> + int ret;
> +
> + if (time_is_after_jiffies(priv->stats_read_at +
> + S2500_STATS_CACHE_PERIOD))
> + return;
[ ... ]
> + /* Rest are 32-bit values until RX bytes register. */
> + for (rptr += 2; (rptr - regs) < STAT_REG_OFFSET(OCTECTSRXL); ++rptr)
> + *(data++) += *rptr;
[ ... ]
> + for (rptr += 2; (rptr - regs) < S2500_NUM_STAT_REGS; ++rptr)
> + *(data++) += *rptr;
> +out:
> + kfree(regs);
> +}
[Severity: Low]
Can the 32-bit hardware counters wrap between reads?
s2500_update_stats() adds the clear-on-read registers into
priv->stats_data[] with +=. It only runs when user space calls one of
the ethtool stats callbacks. Nothing collects the counters periodically.
All counters except the TX/RX octet pairs are 32-bit. If nobody reads the
stats for long enough, the hardware counters wrap or saturate and the
64-bit totals silently lose counts. For the frame counters that is about
80 hours of minimum-size frames at 10 Mb/s.
> diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_main.c b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
> new file mode 100644
> index 0000000000000..4cd0f5d106665
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
> @@ -0,0 +1,523 @@
[ ... ]
> +static int s2500_init(struct s2500_info *priv)
> +{
> + u32 val;
> + int ret;
> +
> + /* Configure MAC-PHY's behavior */
> + val = OA_TC6_CONFIG0_SYNC | S2500_CONFIG0_RXCTE |
> + (S2500_CONFIG0_TXCTHRESH_8 <<
> + S2500_CONFIG0_TXCTHRESH_SHIFT) |
> + (S2500_CONFIG0_CPS_64 << S2500_CONFIG0_CPS_SHIFT) |
> + OA_TC6_CONFIG0_FTSS_64BIT_TSTAMP;
> +
> + ret = oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG0,
> + val);
[Severity: Medium]
Is it safe to build CONFIG0 from scratch here instead of doing a
read-modify-write?
Before s2500_init() runs, oa_tc6_init() has already cached CONFIG0.PROTE
in tc6->prot_ctrl. It has also set SYNC with a read-modify-write in
oa_tc6_enable_data_transfer():
drivers/net/ethernet/oa_tc6/oa_tc6.c:oa_tc6_check_ctrl_protection() {
...
tc6->prot_ctrl = FIELD_GET(OA_TC6_CONFIG0_PROTE, regval);
...
}
This write clears every bit it doesn't list: PROTE, ZARFE, TXCTE and any
non-zero power-on defaults.
Suppose PROTE is 1 on this device. The hardware would then switch to
unprotected control framing while the framework keeps building and
checking protected frames. Every later control access would fail,
starting with s2500_set_mac_filter().
Also, TXCTHRESH is programmed without TXCTE, so that field has no effect.
The other CONFIG0 updates in the framework all use read-modify-write:
oa_tc6_enable_data_transfer(), the ZARFE helper and
oa_tc6_set_hwtstamp_settings().
[Severity: Medium]
Does enabling RX cut-through with S2500_CONFIG0_RXCTE work with the
current OA TC6 receive path?
In cut-through mode the MAC-PHY starts streaming a frame before it checks
FCS and length. At the end of the frame it sets the FD (frame drop, bit
15) flag in the data footer to tell the host to discard it.
oa_tc6_std_def.h has no definition for FD, and no code under
drivers/net/ethernet/oa_tc6/ checks it. So frames with a bad FCS, runts
and overflowed frames would reach netif_rx() as good frames. They would
also never be counted as RX errors.
lan865x doesn't set RXCTE, so this driver seems to be the first to run
into the missing FD handling.
[ ... ]
> + } else {
> + u32 addrh, addrl;
[ ... ]
> + /* Slot 0 holds the unicast MAC, multicast starts at 1 */
> + i = 1;
> + netdev_hw_addr_list_for_each(ha, mc) {
> + const u8 *mac = ha->addr;
> + int slot = i++;
> +
> + addrh = S2500_MAC_ADDRFILT_ENABLE |
> + get_unaligned_be16(mac);
> + addrl = get_unaligned_be32(&mac[2]);
> +
> + addr = S2500_REG_MAC_ADDRFILTH(slot);
> + ret = oa_tc6_write_register_mms(priv->tc6,
> + mms, addr,
> + addrh);
> + if (ret)
> + return ret;
[Severity: Low]
Should ADDRFILTH(slot) with S2500_MAC_ADDRFILT_ENABLE be written last,
after the low address and mask registers?
s2500_set_mac_filter() writes the masks, then the low address, then the
high address with the enable bit. This loop enables the slot first.
Take a switch from IFF_ALLMULTI (or more than 3 groups) to a short list.
The earlier loop only zeroes ADDRFILTH, so slot 1 still has ADDRMASKL=0
and ADDRMASKH=0x100 from the wildcard setup. Enabling it again with a
multicast address briefly makes it match all multicast frames.
If one of the later SPI writes fails, the function returns with the slot
enabled on a mix of old and new address and mask values. The hardware
filter then no longer matches the stack's mc list.
[ ... ]
> +static int s2500_hwtstamp_set(struct net_device *ndev,
> + struct kernel_hwtstamp_config *cfg,
> + struct netlink_ext_ack *extack)
> +{
> + struct s2500_info *priv = netdev_priv(ndev);
> +
> + return oa_tc6_hwtstamp_set(priv->tc6, cfg);
> +}
[Severity: Medium]
Can turning TX timestamping off here leave an skb stuck on tx_ts_skb_q?
The call chain is s2500_hwtstamp_set()->oa_tc6_hwtstamp_set()->
oa_tc6_set_hwtstamp_settings(). That function masks the TTSC interrupts,
clears hw_tstamp_enabled and drains tx_ts_skb_q:
if (ts_cfg->tx_type != HWTSTAMP_TX_ON)
oa_tc6_cleanup_tx_tstamp_skbs(tc6);
The cleanup doesn't cover tc6->ongoing_tx_skb.
oa_tc6_prepare_spi_tx_buf_for_tx_skbs() reserves a capture slot (tsc)
for that skb under tx_skb_lock and then drops the lock. A frame longer
than the available TX credits stays in ongoing_tx_skb across SPI
transfers.
When the IRQ thread finishes that frame, oa_tc6_add_tx_skb_to_spi_buf()
queues it again based only on the stored tsc:
if (!oa_tc6_tsinfo_tx(tc6->ongoing_tx_skb)->tsc) {
kfree_skb(tc6->ongoing_tx_skb);
} else {
spin_lock_bh(&tc6->tx_skb_lock);
list_add_tail(&tc6->ongoing_tx_skb->list,
&tc6->tx_ts_skb_q);
The skb and its ts_ttsc_pending slot then stay stuck while the completion
interrupt is masked.
If timestamping is turned back on, the stale STATUS0 capture bit is
cleared without delivering the timestamp. The slot stays reserved until
the next disable or until the driver is removed.
The code involved is the oa_tc6 timestamp support added earlier in the
series. This driver's ndo_hwtstamp_set is the path that reaches it.
[ ... ]
> +static int s2500_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct net_device *ndev;
> + struct s2500_info *priv;
> + u32 val;
> + int ret;
> +
> + if (spi->irq < 0)
> + return -ENODEV;
[Severity: Low]
Can this check ever be true?
spi_probe() in drivers/spi/spi.c clamps negative IRQ values to 0 before
it calls the driver's probe:
if (spi->irq < 0)
spi->irq = 0;
A device without an interrupt therefore passes this check. It goes
through the whole oa_tc6_init() sequence (reset, MDIO/PHY registration,
SYNC enable) and only fails at devm_request_threaded_irq() with irq 0,
which gives a misleading error.
Should this be spi->irq <= 0?
[ ... ]
> + priv->tc6 = oa_tc6_init(spi, ndev, NULL);
[ ... ]
> + /* Acknowledge all IRQ status bits */
> + ret = oa_tc6_read_register(priv->tc6,
> + OA_TC6_REG_STATUS0, &val);
> + if (!ret) {
> + /* Clear read only bit */
> + val &= ~S2500_STATUS0_PHY_INT;
> + oa_tc6_write_register(priv->tc6,
> + OA_TC6_REG_STATUS0, val);
> + }
[Severity: Low]
Does this STATUS0 acknowledge race with the oa_tc6 IRQ thread?
Before it returns, oa_tc6_init() has already requested the threaded IRQ,
set int_flag and called irq_wake_thread().
The IRQ thread does its own read-then-clear of STATUS0 in
oa_tc6_process_extended_status(). It acts on the bits it reads: RX
overflow recovery, TX protocol, loss-of-frame and header errors, and
timestamp captures:
value = regs[0];
...
if (!value)
return 0;
Each SPI access takes spi_ctrl_lock, but the read and the write here run
under separate lock sections. If probe reads and clears a bit first, the
thread then reads 0 and the event is lost.
STATUS0 is owned by the framework. Could this acknowledge be removed, or
done before the IRQ thread can run?
[ ... ]
> +static void s2500_remove(struct spi_device *spi)
> +{
> + struct s2500_info *priv = spi_get_drvdata(spi);
> +
> + dev_dbg(&priv->ndev->dev, "%s", "s2500_remove\n");
> +
> + unregister_netdev(priv->ndev);
> + oa_tc6_ptp_unregister(priv->tc6);
> + oa_tc6_exit(priv->tc6);
> +}
[Severity: Medium]
Is this teardown order safe for TX timestamps that are still pending?
oa_tc6_ptp_unregister() sets tc6->ptp_clock to NULL. The IRQ stays active
until oa_tc6_exit(), which is also the call that drains tx_ts_skb_q.
s2500_stop() doesn't drain that queue, so timestamped skbs can still be
pending at this point.
Suppose the IRQ thread handles a TX timestamp capture in that window.
oa_tc6_process_extended_status() sees ts_valid == false and reads only
STATUS0 into regs[0]. It still passes the rest of the array to the event
handler:
bool ts_valid = !!tc6->ptp_clock;
...
ret = oa_tc6_read_registers(tc6, OA_TC6_REG_STATUS0, regs,
ts_valid ?
OA_TC6_STATUS0_TTSC_REG_COUNT : 1);
...
if ((value & OA_TC6_STATUS0_TTSCA_MASK) != 0)
oa_tc6_events_handle(tc6, value &
OA_TC6_STATUS0_TTSCA_MASK,
®s[OA_TC6_TTSC_REG_OFFSET]);
oa_tc6_events_handle() then builds the timestamp from uninitialized stack
words:
ski->hwtstamp = ktime_set(ts[2 * bit_num],
ts[2 * bit_num + 1]);
and delivers it to the socket through skb_tstamp_tx().
Could the IRQ be quiesced, or the timestamp queue drained, before the PHC
is unregistered? Could oa_tc6_events_handle() also be skipped when the
TTSC registers were not read?
> diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
> new file mode 100644
> index 0000000000000..2bcf69c0d6a2e
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
> @@ -0,0 +1,174 @@
[ ... ]
> +static int s2500_ptp_set_freq(struct s2500_info *priv, long scaled_ppm)
> +{
> + u32 sign_bit = 0;
> + long adj;
> + u32 val;
> + u64 ppm;
> +
> + if (scaled_ppm < 0) {
> + /* split sign / mod */
> + sign_bit = 1U << 31;
> + scaled_ppm = ~scaled_ppm + 1;
> + }
[ ... ]
> + ppm = (u64)scaled_ppm * 125;
> + do_div(ppm, 1024);
> + adj = (long)ppm;
> +
> + /* check overflow */
> + if (adj > S2500_PTP_ADJ_MAX)
> + return -ERANGE;
[Severity: Low]
On 32-bit kernels, what happens here when scaled_ppm == LONG_MIN?
That value is about -32768000 ppb. It passes ptp_clock_adjtime()'s check
against max_adj = S2500_PTP_MAX_ADJ_PPB (33554431).
However, ~scaled_ppm + 1 can't represent +2^31 in a 32-bit long, so the
value stays LONG_MIN. (u64)scaled_ppm then sign-extends, the multiply and
do_div() wrap, and adj ends up as -262144000.
In the adj > S2500_PTP_ADJ_MAX comparison, adj is converted to unsigned
long (4032823296). The function returns -ERANGE even though the correct
magnitude, 262144000, fits in the 28-bit field.
Could the magnitude be computed in a wider unsigned type before the
negation?
[ ... ]
> +static int s2500_ptp_step_time(struct s2500_info *priv, s64 delta)
> +{
> + struct timespec64 target, now, delta_ts;
> + struct ptp_system_timestamp sts;
> + int ret;
> +
> + memset(&sts, 0, sizeof(sts));
> + ret = s2500_ptp_get_time64(&priv->ptp_clock_info, &now, &sts);
> + if (ret)
> + return ret;
> + if (delta >= 0) {
> + delta_ts = ns_to_timespec64(delta);
> + target = timespec64_add(now, delta_ts);
> + } else {
> + delta_ts = ns_to_timespec64(-delta);
> + target = timespec64_sub(now, delta_ts);
> + }
> + /* The subtraction above can take the target below the epoch */
> + if (target.tv_sec < 0)
> + return -ERANGE;
> + return s2500_ptp_write_time(priv, &target);
> +}
[Severity: Low]
Does this read-then-write lose the time spent between the two SPI
transactions?
s2500_ptp_get_time64() latches PTP_SEC/PTPNSEC in one control
transaction. delta is added in software, and s2500_ptp_write_time() sets
the absolute result (bit 31 executes the set) in a second transaction.
The time in between is never added back. It covers two control frames
plus contention with data chunks on the same bus. The pre/post system
timestamps captured in sts are also thrown away.
As a result, every ADJ_SETOFFSET leaves the PHC behind the requested
value by roughly the SPI round-trip latency.
Could the elapsed time measured through sts be used to compensate?
[ ... ]
> @@ -0,0 +1,174 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
> + * onsemi's S2500 10BASE-T1S MAC-PHY driver
> + */
> +
> +#include "s2500_hw_def.h"
> +
> +static int s2500_ptp_get_time64(struct ptp_clock_info *ptp,
> + struct timespec64 *ts,
> + struct ptp_system_timestamp *ptp_sts)
> +{
> + struct s2500_info *priv = container_of(ptp, struct s2500_info,
> + ptp_clock_info);
> + u32 data[2];
> + u32 addr;
> + int ret;
> +
> + ptp_read_system_prets(ptp_sts);
> + addr = FIELD_PREP(S2500_REG_MMS_MASK,
> + OA_TC6_PHY_C45_VS_MMS12) |
> + S2500_REG_VS_PTP_SEC;
> + ret = oa_tc6_read_registers(priv->tc6, addr, &data[0], 2);
> + ptp_read_system_postts(ptp_sts);
> +
> + if (!ret) {
> + ts->tv_sec = data[0];
> + ts->tv_nsec = data[1];
> + }
> +
> + return ret;
> +}
> +
> +static int s2500_ptp_write_time(struct s2500_info *priv,
> + const struct timespec64 *ts)
> +{
> + u32 data[2];
> + u32 addr;
> +
> + lockdep_assert_held(&priv->ptp_adj_lock);
> +
> + if (ts->tv_sec >= (1ULL << 32))
> + return -ERANGE;
> +
> + data[0] = (u32)ts->tv_sec;
> + /* bit 31 = execute set command */
> + data[1] = ts->tv_nsec | BIT(31);
> + addr = FIELD_PREP(S2500_REG_MMS_MASK,
> + OA_TC6_PHY_C45_VS_MMS12) |
> + S2500_REG_VS_PTP_SETSEC;
> +
> + return oa_tc6_write_registers(priv->tc6, addr, &data[0], 2);
> +}
> +
> +static int s2500_ptp_set_time64(struct ptp_clock_info *ptp,
> + const struct timespec64 *ts)
> +{
> + struct s2500_info *priv = container_of(ptp, struct s2500_info,
> + ptp_clock_info);
> + int ret;
> +
> + mutex_lock(&priv->ptp_adj_lock);
> + ret = s2500_ptp_write_time(priv, ts);
> + mutex_unlock(&priv->ptp_adj_lock);
> +
> + return ret;
> +}
> +
> +static int s2500_ptp_set_freq(struct s2500_info *priv, long scaled_ppm)
> +{
> + u32 sign_bit = 0;
> + long adj;
> + u32 val;
> + u64 ppm;
> +
> + if (scaled_ppm < 0) {
> + /* split sign / mod */
> + sign_bit = 1U << 31;
> + scaled_ppm = ~scaled_ppm + 1;
> + }
> +
> + /*
> + * Convert unsigned scaled_ppm to atto-seconds per clock cycles.
> + * The scaled_ppm format is Qx.16 --> 1 lsb = 1/65536 ppm.
> + * The clock period of the S2500 is 8ns (125 MHz), so 1 lsb of
> + * adj register LSB is 1 atto-sec / 8ns = 0.000125 ppm.
> + * Represented in Qx.16 format, this is 0.000125 * 2^16 = 8(.192)
> + * To convert scaled_ppm into a register value we need to divide
> + * it by the LSB value, hence adj = (scaled_ppm * 1000) / 8192 to
> + * minimize the precision loss due to the integer arithmetic.
> + * That further reduces to (scaled_ppm * 125) / 1024.
> + */
> + ppm = (u64)scaled_ppm * 125;
> + do_div(ppm, 1024);
> + adj = (long)ppm;
> +
> + /* check overflow */
> + if (adj > S2500_PTP_ADJ_MAX)
> + return -ERANGE;
> +
> + val = (u32)adj | sign_bit;
> + return oa_tc6_write_register_mms(priv->tc6, OA_TC6_PHY_C45_VS_MMS12,
> + S2500_REG_VS_PTP_ADJ, val);
> +}
> +
> +static int s2500_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
> +{
> + struct s2500_info *priv = container_of(ptp, struct s2500_info,
> + ptp_clock_info);
> +
> + return s2500_ptp_set_freq(priv, scaled_ppm);
> +}
> +
> +static int s2500_ptp_step_time(struct s2500_info *priv, s64 delta)
> +{
> + struct timespec64 target, now, delta_ts;
> + struct ptp_system_timestamp sts;
> + int ret;
> +
> + memset(&sts, 0, sizeof(sts));
> + ret = s2500_ptp_get_time64(&priv->ptp_clock_info, &now, &sts);
> + if (ret)
> + return ret;
> + if (delta >= 0) {
> + delta_ts = ns_to_timespec64(delta);
> + target = timespec64_add(now, delta_ts);
> + } else {
> + delta_ts = ns_to_timespec64(-delta);
> + target = timespec64_sub(now, delta_ts);
> + }
> + /* The subtraction above can take the target below the epoch */
> + if (target.tv_sec < 0)
> + return -ERANGE;
> + return s2500_ptp_write_time(priv, &target);
> +}
> +
> +static int s2500_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
> +{
> + struct s2500_info *priv = container_of(ptp, struct s2500_info,
> + ptp_clock_info);
> + int ret;
> +
> + /* Nothing to do */
> + if (delta == 0)
> + return 0;
> +
> + /* The device has no add-delta register, so the offset is applied as
> + * a read-modify-write of the time registers.
> + */
> + mutex_lock(&priv->ptp_adj_lock);
> + ret = s2500_ptp_step_time(priv, delta);
> + mutex_unlock(&priv->ptp_adj_lock);
> +
> + return ret;
> +}
> +
> +int s2500_ptp_register(struct s2500_info *priv)
> +{
> + struct ptp_clock_info *info = &priv->ptp_clock_info;
> + int ret;
> +
> + info->max_adj = S2500_PTP_MAX_ADJ_PPB;
> + info->owner = THIS_MODULE;
> + info->adjfine = s2500_ptp_adjfine;
> + info->gettimex64 = s2500_ptp_get_time64;
> + info->settime64 = s2500_ptp_set_time64;
> + info->adjtime = s2500_ptp_adjtime;
> +
> + ret = oa_tc6_ptp_register(priv->tc6, info);
> + if (ret)
> + dev_err(&priv->spi->dev, "PTP registration failed\n");
> + return ret;
> +}
> +
[Severity: Medium]
Can the advertised max_adj be larger than what a 32-bit long
scaled_ppm can carry?
S2500_PTP_MAX_ADJ_PPB works out to 268435455 / 8 = 33554431 ppb. The
PTP core passes the adjustment to ->adjfine() as scaled_ppm in a long,
and 1 ppb is 65.536 units of scaled_ppm. On a 32-bit kernel the largest
positive scaled_ppm is LONG_MAX, which is only about 32767999 ppb.
Take a request for +33000000 ppb. The scaled_ppm value is about
2162688000, which does not fit in a 32-bit long. ptp_clock_adjtime()
passes the 64-bit tx->freq to scaled_ppm_to_ppb() and to ->adjfine(),
both of which take a long, so the value is truncated to a large
negative number.
That negative number still converts to roughly -32.5 million ppb. This
is inside [-max_adj, max_adj], so the range check passes.
s2500_ptp_adjfine() then programs a large negative frequency offset
into S2500_REG_VS_PTP_ADJ.
User space does not get -ERANGE. Instead the servo's correction is
applied in the opposite direction, and the PHC is driven away from its
reference rather than towards it. SPI MAC-PHYs like this one are likely
to end up on 32-bit ARM boards, so this is not just a theoretical case.
Should max_adj be capped at 32767999 so the advertised range always
fits in a 32-bit scaled_ppm? Or is there a reason to keep the full
28-bit hardware range? This also interacts with the LONG_MIN handling
in s2500_ptp_set_freq() mentioned above.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d%40onsemi.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v9 09/11] onsemi: s2500: Added selftest support to onsemi's S2500 driver
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
` (7 preceding siblings ...)
2026-10-09 17:12 ` [PATCH net-next v9 08/11] onsemi: s2500: Add driver support for S2500 MAC-PHY Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-10 19:40 ` netdev-bot+sashiko
2026-10-09 17:12 ` [PATCH net-next v9 10/11] net: phy: ncn26000: Support for onsemi's S2500 internal phy Selvamani Rajagopal via B4 Relay
2026-10-09 17:12 ` [PATCH net-next v9 11/11] net: phy: ncn26000: Support for loopback Selvamani Rajagopal via B4 Relay
10 siblings, 1 reply; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Adds selftest support for onsemi S2500 MAC-PHY. Added as a separate
patch for the ease of review.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- Added net_selftest entry to the ethtool_ops in the correct patch
changes in v8
- No change
changes in v7
- No change
changes in v6
- Moved the signed off information to the correct place.
changes in v5
- No change
changes in v4:
- Added a selftest as a separate patch
- First patch
---
drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
index 062cab9460dc..12e59e8d44a6 100644
--- a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
@@ -5,6 +5,7 @@
*/
#include <linux/ethtool.h>
+#include <net/selftests.h>
#include <linux/phy.h>
#include "s2500_hw_def.h"
@@ -201,6 +202,8 @@ static int s2500_get_sset_count(struct net_device *ndev, int sset)
switch (sset) {
case ETH_SS_STATS:
return S2500_MAC_STATS_LEN;
+ case ETH_SS_TEST:
+ return net_selftest_get_count();
default:
return -EOPNOTSUPP;
}
@@ -214,6 +217,9 @@ static void s2500_get_strings(struct net_device *ndev, u32 stringset,
memcpy(buf, s2500_mac_stat_strings,
S2500_MAC_STATS_LEN * ETH_GSTRING_LEN);
break;
+ case ETH_SS_TEST:
+ net_selftest_get_strings(buf);
+ break;
}
}
@@ -372,5 +378,6 @@ const struct ethtool_ops s2500_ethtool_ops = {
.get_ts_info = s2500_get_ts_info,
.get_regs_len = s2500_get_regs_len,
.get_regs = s2500_get_regs,
+ .self_test = net_selftest,
};
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH net-next v9 09/11] onsemi: s2500: Added selftest support to onsemi's S2500 driver
2026-10-09 17:12 ` [PATCH net-next v9 09/11] onsemi: s2500: Added selftest support to onsemi's S2500 driver Selvamani Rajagopal via B4 Relay
@ 2026-10-10 19:40 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:40 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: andrew+netdev, davem, kuba, pabeni, robh, krzk+dt, conor+dt,
parthiban.veerasooran, horms, corbet, skhan, rdunlap,
richardcochran, andrew, hkallweit1, linux, edumazet,
piergiorgio.beruto, netdev, devicetree, linux-kernel, linux-doc,
jerry.ray, ciprian.regus
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] When CONFIG_NET_SELFTESTS=n, s2500_get_sset_count() returns 0
for ETH_SS_TEST, because the include/net/selftests.h stub…
--
onsemi: s2500: Added selftest support to onsemi's S2500 driver
This patch adds ethtool selftest support to the onsemi S2500 MAC-PHY
driver. For ETH_SS_TEST it reports the generic net_selftest count and
strings. It also sets net_selftest() as the self_test callback in
s2500_ethtool_ops.
> diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
> index 062cab9460dcf..12e59e8d44a62 100644
> --- a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
[ ... ]
> @@ -201,6 +202,8 @@ static int s2500_get_sset_count(struct net_device *ndev, int sset)
> switch (sset) {
> case ETH_SS_STATS:
> return S2500_MAC_STATS_LEN;
> + case ETH_SS_TEST:
> + return net_selftest_get_count();
[Severity: Medium]
What happens here when CONFIG_NET_SELFTESTS is not enabled?
In that case include/net/selftests.h provides stubs:
static inline int net_selftest_get_count(void)
{
return 0;
}
So s2500_get_sset_count() returns 0 for ETH_SS_TEST. At the same time,
s2500_ethtool_ops still sets ".self_test = net_selftest", which is the
empty stub.
The ethtool core expects a non-zero count whenever self_test is set. In
net/ethtool/ioctl.c:
ethtool_self_test() {
...
test_len = ops->get_sset_count(dev, ETH_SS_TEST);
if (test_len < 0)
return test_len;
WARN_ON(test_len == 0);
...
}
After the warning, the no-op stub runs and "ethtool -t" reports success
without running any tests.
ETHTOOL_GSTRINGS does not require CAP_NET_ADMIN. With
string_set == ETH_SS_TEST, that path also hits a warning:
ethtool_get_strings() {
...
ret = __ethtool_get_sset_count(dev, gstrings.string_set);
if (ret < 0)
return ret;
...
WARN_ON_ONCE(!ret);
...
}
Can this configuration actually be built? NET_SELFTESTS depends on
PHYLIB && INET. S2500_MACPHY only uses imply for it and has no INET
dependency:
config S2500_MACPHY
tristate "S2500 support"
depends on SPI
depends on PTP_1588_CLOCK_OPTIONAL
imply NET_SELFTESTS
With CONFIG_INET=n, the driver can be built while NET_SELFTESTS=n.
A few existing drivers (fec, ag71xx, asix, lan78xx, smsc95xx) follow
the same pattern.
Would any of these fixes work here?
- return -EOPNOTSUPP for ETH_SS_TEST when net_selftest_get_count()
is 0
- set .self_test only under IS_ENABLED(CONFIG_NET_SELFTESTS)
- add a dependency on NET_SELFTESTS, and therefore on INET
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d%40onsemi.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v9 10/11] net: phy: ncn26000: Support for onsemi's S2500 internal phy
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
` (8 preceding siblings ...)
2026-10-09 17:12 ` [PATCH net-next v9 09/11] onsemi: s2500: Added selftest support to onsemi's S2500 driver Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-10 19:40 ` netdev-bot+sashiko
2026-10-09 17:12 ` [PATCH net-next v9 11/11] net: phy: ncn26000: Support for loopback Selvamani Rajagopal via B4 Relay
10 siblings, 1 reply; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Adding support for internal PHY of the integrated
media access controller S2500. PLCA tx opportunity timer's
default value is correct in this device, compared to
the currently supported PHY NCN26000.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- Added Reviewed tag
changes in v8
- changes to use newer API for read/write_mmd callback
changes in v7
- No change
changes in v6
- No change
changes in v5
- No change
changes in v4
- no change
changes in v3
added new PHY support separate patch
changed model comparison to use phy_id_compare_model
changes in v2
Removed bug fixes. Retained only S2500 specific changes
changes in v1
Added support for an internal PHY of onsemi's MAC-PHY S2500
---
MAINTAINERS | 1 +
drivers/net/phy/ncn26000.c | 36 ++++++++++++++++++++++++++++++++----
2 files changed, 33 insertions(+), 4 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 4e122a5eefa1..c114be851607 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20435,6 +20435,7 @@ F: arch/mips/boot/dts/ralink/omega2p.dts
ONSEMI ETHERNET PHY DRIVERS
M: Piergiorgio Beruto <piergiorgio.beruto@gmail.com>
+M: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
L: netdev@vger.kernel.org
S: Supported
W: http://www.onsemi.com
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index 6d433f4e2c45..a33ebb2a7ece 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -2,7 +2,7 @@
/*
* Driver for the onsemi 10BASE-T1S NCN26000 PHYs family.
*
- * Copyright 2022 onsemi
+ * Copyright 2026 onsemi
*/
#include <linux/kernel.h>
#include <linux/bitfield.h>
@@ -14,6 +14,7 @@
#include "mdio-open-alliance.h"
+#define PHY_ID_S2500 0x180FF411
#define PHY_ID_NCN26000 0x180FF5A1
#define NCN26000_REG_IRQ_CTL 16
@@ -41,13 +42,18 @@
static int ncn26000_config_init(struct phy_device *phydev)
{
+ int ret = 0;
+
/* HW bug workaround: the default value of the PLCA TO_TIMER should be
* 32, where the current version of NCN26000 reports 24. This will be
* fixed in future PHY versions. For the time being, we force the
* correct default here.
*/
- return phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR,
- TO_TMR_DEFAULT);
+ if (phy_id_compare_model(phydev->drv->phy_id, PHY_ID_NCN26000))
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ MDIO_OATC14_PLCA_TOTMR,
+ TO_TMR_DEFAULT);
+ return ret;
}
static int ncn26000_config_aneg(struct phy_device *phydev)
@@ -139,8 +145,8 @@ static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
static int ncn26000_config_intr(struct phy_device *phydev)
{
- int ret;
u16 irqe;
+ int ret;
if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
// acknowledge IRQs
@@ -163,6 +169,26 @@ static int ncn26000_config_intr(struct phy_device *phydev)
}
static struct phy_driver ncn26000_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_S2500),
+ .name = "S2500",
+ .features = PHY_BASIC_T1S_P2MP_FEATURES,
+ .config_init = ncn26000_config_init,
+ .config_intr = ncn26000_config_intr,
+ .config_aneg = ncn26000_config_aneg,
+ .read_status = ncn26000_read_status,
+ .handle_interrupt = ncn26000_handle_interrupt,
+ .set_plca_cfg = ncn26000_c45_plca_set_cfg,
+ .get_plca_cfg = genphy_c45_plca_get_cfg,
+ .get_plca_status = genphy_c45_plca_get_status,
+ .soft_reset = genphy_soft_reset,
+ .get_sqi = genphy_c45_oatc14_get_sqi,
+ .get_sqi_max = genphy_c45_oatc14_get_sqi_max,
+ .read_mmd = genphy_read_mmd_c45,
+ .write_mmd = genphy_write_mmd_c45,
+ .cable_test_get_status = genphy_c45_oatc14_cable_test_get_status,
+ .cable_test_start = genphy_c45_oatc14_cable_test_start,
+ },
{
PHY_ID_MATCH_MODEL(PHY_ID_NCN26000),
.name = "NCN26000",
@@ -183,11 +209,13 @@ module_phy_driver(ncn26000_driver);
static const struct mdio_device_id __maybe_unused ncn26000_tbl[] = {
{ PHY_ID_MATCH_MODEL(PHY_ID_NCN26000) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_S2500) },
{ }
};
MODULE_DEVICE_TABLE(mdio, ncn26000_tbl);
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
MODULE_AUTHOR("Piergiorgio Beruto");
MODULE_DESCRIPTION("onsemi 10BASE-T1S PHY driver");
MODULE_LICENSE("Dual BSD/GPL");
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH net-next v9 10/11] net: phy: ncn26000: Support for onsemi's S2500 internal phy
2026-10-09 17:12 ` [PATCH net-next v9 10/11] net: phy: ncn26000: Support for onsemi's S2500 internal phy Selvamani Rajagopal via B4 Relay
@ 2026-10-10 19:40 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:40 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: andrew+netdev, davem, kuba, pabeni, robh, krzk+dt, conor+dt,
parthiban.veerasooran, horms, corbet, skhan, rdunlap,
richardcochran, andrew, hkallweit1, linux, edumazet,
piergiorgio.beruto, netdev, devicetree, linux-kernel, linux-doc,
jerry.ray, ciprian.regus
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] In this patch, the new S2500 entry in ncn26000_driver[]
(drivers/net/phy/ncn26000.c, lines 172-191) has no .set_loopback…
- [Low] The help text for the NCN26000_PHY Kconfig option
(drivers/net/phy/Kconfig, lines 393-398) still says "Currently…
--
net: phy: ncn26000: Support for onsemi's S2500 internal phy
This patch adds a phy_driver entry for the internal PHY of the onsemi
S2500 MAC-PHY to the ncn26000 driver. The NCN26000 PLCA TO_TIMER
workaround in ncn26000_config_init() now runs only for the NCN26000
model.
> diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
> index 6d433f4e2c45d..a33ebb2a7ecef 100644
> --- a/drivers/net/phy/ncn26000.c
> +++ b/drivers/net/phy/ncn26000.c
[ ... ]
> @@ -163,6 +169,26 @@ static int ncn26000_config_intr(struct phy_device *phydev)
> }
>
> static struct phy_driver ncn26000_driver[] = {
> + {
> + PHY_ID_MATCH_MODEL(PHY_ID_S2500),
> + .name = "S2500",
> + .features = PHY_BASIC_T1S_P2MP_FEATURES,
> + .config_init = ncn26000_config_init,
> + .config_intr = ncn26000_config_intr,
> + .config_aneg = ncn26000_config_aneg,
> + .read_status = ncn26000_read_status,
> + .handle_interrupt = ncn26000_handle_interrupt,
> + .set_plca_cfg = ncn26000_c45_plca_set_cfg,
> + .get_plca_cfg = genphy_c45_plca_get_cfg,
> + .get_plca_status = genphy_c45_plca_get_status,
> + .soft_reset = genphy_soft_reset,
> + .get_sqi = genphy_c45_oatc14_get_sqi,
> + .get_sqi_max = genphy_c45_oatc14_get_sqi_max,
> + .read_mmd = genphy_read_mmd_c45,
> + .write_mmd = genphy_write_mmd_c45,
> + .cable_test_get_status = genphy_c45_oatc14_cable_test_get_status,
> + .cable_test_start = genphy_c45_oatc14_cable_test_start,
> + },
[Severity: Low]
The S2500 entry has no .set_loopback callback, so phy_loopback() falls
back to genphy_loopback(). That function overwrites all of BMCR:
drivers/net/phy/phy_device.c:genphy_loopback() {
...
phy_modify(phydev, MII_BMCR, ~0, ctl);
ret = phy_read_poll_timeout(phydev, MII_BMSR, val,
val & BMSR_LSTATUS,
5000, 500000, true);
...
}
This driver uses that BMCR bit for link control:
#define NCN26000_BCMR_LINK_CTRL_BIT BMCR_ANENABLE
At this commit, does the PHY loopback selftest from "onsemi: s2500: Added
selftest support to onsemi's S2500 driver" fail on S2500? The path is
ethtool -t -> net_selftest -> phy_loopback() -> genphy_loopback().
This is not a regression against the pre-patch state. Before this patch
the generic PHY driver bound to 0x180FF411 and used the same fallback.
The later patch in this series, "net: phy: ncn26000: Support for
loopback", adds genphy_loopback_no_speed() and sets it as .set_loopback
on the S2500 entry. That fixes this by the end of the series.
[ ... ]
> static const struct mdio_device_id __maybe_unused ncn26000_tbl[] = {
> { PHY_ID_MATCH_MODEL(PHY_ID_NCN26000) },
> + { PHY_ID_MATCH_MODEL(PHY_ID_S2500) },
> { }
> };
[Severity: Low]
Should the NCN26000_PHY help text in drivers/net/phy/Kconfig be updated
as well? It still reads:
Currently supports the NCN26000 10BASE-T1S Industrial PHY
with MII interface.
With this patch the driver also binds the S2500 internal PHY. That PHY
is not a discrete MII-attached part; it is reached over the OA TC6 MDIO
bus with C45 accessors.
No later patch in the series changes Kconfig.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d%40onsemi.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v9 11/11] net: phy: ncn26000: Support for loopback
2026-10-09 17:12 [PATCH net-next v9 00/11] Support for onsemi's S2500 10Base-T1S MAC-PHY Selvamani Rajagopal via B4 Relay
` (9 preceding siblings ...)
2026-10-09 17:12 ` [PATCH net-next v9 10/11] net: phy: ncn26000: Support for onsemi's S2500 internal phy Selvamani Rajagopal via B4 Relay
@ 2026-10-09 17:12 ` Selvamani Rajagopal via B4 Relay
2026-10-10 19:40 ` netdev-bot+sashiko
10 siblings, 1 reply; 25+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-10-09 17:12 UTC (permalink / raw)
To: Selva Rajagopal, Andrew Lunn, David S. Miller, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Parthiban Veerasooran, Simon Horman, Jonathan Corbet, Shuah Khan,
Randy Dunlap, Richard Cochran, Andrew Lunn, Heiner Kallweit,
Russell King, Eric Dumazet, Piergiorgio Beruto, Eric Dumazet
Cc: Selva Rajagopal, netdev, devicetree, linux-kernel, linux-doc,
Jerry Ray, Ciprian Regus
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Adding loopback support for S2500 internal PHY to help running
loopback test through ethtool.
This instance requires only manipulating the loopback bit in
the PHY, without setting the speed. So, added a generic helper
function to modify loopback bit of the PHY only. This will
help the PHY drivers, like 10Base-T1S or any other PHYs that
want to handle loopback bit only.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v9
- replaced "fixed_speed" with "no_speed" for the API.
- Improved the comments to be specific about not setting the
speed.
changes in v8
- No code change. Changes were in two different patches.
Merged into one.
changes in v7
- No change
changes in v6
- No change
changes in v5
- No change
changes in v4
- Moved the onsemi implementation to a new, genphy API to
make it vendor agnostic.
changes in v3
- Addd loopback functionality for onsemi's PHY driver
- First patch
---
drivers/net/phy/dp83867.c | 11 +----------
drivers/net/phy/ncn26000.c | 1 +
drivers/net/phy/phy_device.c | 22 ++++++++++++++++++++++
include/linux/phy.h | 2 ++
4 files changed, 26 insertions(+), 10 deletions(-)
diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c
index a3cd5cb11ea0..a62667606422 100644
--- a/drivers/net/phy/dp83867.c
+++ b/drivers/net/phy/dp83867.c
@@ -1058,15 +1058,6 @@ static void dp83867_link_change_notify(struct phy_device *phydev)
}
}
-static int dp83867_loopback(struct phy_device *phydev, bool enable, int speed)
-{
- if (enable && speed)
- return -EOPNOTSUPP;
-
- return phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,
- enable ? BMCR_LOOPBACK : 0);
-}
-
static int
dp83867_led_brightness_set(struct phy_device *phydev,
u8 index, enum led_brightness brightness)
@@ -1292,7 +1283,7 @@ static struct phy_driver dp83867_driver[] = {
.resume = dp83867_resume,
.link_change_notify = dp83867_link_change_notify,
- .set_loopback = dp83867_loopback,
+ .set_loopback = genphy_loopback_no_speed,
.led_brightness_set = dp83867_led_brightness_set,
.led_hw_is_supported = dp83867_led_hw_is_supported,
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index a33ebb2a7ece..de0fafda3876 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -178,6 +178,7 @@ static struct phy_driver ncn26000_driver[] = {
.config_aneg = ncn26000_config_aneg,
.read_status = ncn26000_read_status,
.handle_interrupt = ncn26000_handle_interrupt,
+ .set_loopback = genphy_loopback_no_speed,
.set_plca_cfg = ncn26000_c45_plca_set_cfg,
.get_plca_cfg = genphy_c45_plca_get_cfg,
.get_plca_status = genphy_c45_plca_get_status,
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index bfce8b893644..03419f645587 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -2791,6 +2791,28 @@ int genphy_resume(struct phy_device *phydev)
}
EXPORT_SYMBOL(genphy_resume);
+/**
+ * genphy_loopback_no_speed - Helper to modify the PHY loopback mode
+ * without setting speed. Some PHYs, like 10Base-T1S, don't support
+ * auto-negotiation and therefore setting the speed isn't supported.
+ *
+ * @phydev: The phy_device struct
+ * @enable: Flag to enable or disable the PHY level loopback.
+ * @speed: Speed setting. Not expected to be set. Error if it is set.
+ *
+ * Returns: 0 if successful, negative error code on failure.
+ */
+int genphy_loopback_no_speed(struct phy_device *phydev, bool enable,
+ int speed)
+{
+ if (enable && speed)
+ return -EOPNOTSUPP;
+
+ return phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,
+ enable ? BMCR_LOOPBACK : 0);
+}
+EXPORT_SYMBOL(genphy_loopback_no_speed);
+
int genphy_loopback(struct phy_device *phydev, bool enable, int speed)
{
if (enable) {
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 7c5098a0dd6c..ea3505e49316 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2307,6 +2307,8 @@ int genphy_read_status(struct phy_device *phydev);
int genphy_read_master_slave(struct phy_device *phydev);
int genphy_suspend(struct phy_device *phydev);
int genphy_resume(struct phy_device *phydev);
+int genphy_loopback_no_speed(struct phy_device *phydev, bool enable,
+ int speed);
int genphy_loopback(struct phy_device *phydev, bool enable, int speed);
int genphy_soft_reset(struct phy_device *phydev);
irqreturn_t genphy_handle_interrupt_no_ack(struct phy_device *phydev);
--
2.43.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH net-next v9 11/11] net: phy: ncn26000: Support for loopback
2026-10-09 17:12 ` [PATCH net-next v9 11/11] net: phy: ncn26000: Support for loopback Selvamani Rajagopal via B4 Relay
@ 2026-10-10 19:40 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 19:40 UTC (permalink / raw)
To: Selvamani.Rajagopal
Cc: andrew+netdev, davem, kuba, pabeni, robh, krzk+dt, conor+dt,
parthiban.veerasooran, horms, corbet, skhan, rdunlap,
richardcochran, andrew, hkallweit1, linux, edumazet,
piergiorgio.beruto, netdev, devicetree, linux-kernel, linux-doc,
jerry.ray, ciprian.regus
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] This patch adds loopback support for the S2500 by pointing
.set_loopback at genphy_loopback_no_speed().
- [Low] The kernel-doc for genphy_loopback_no_speed() does not match the
code.
--
net: phy: ncn26000: Support for loopback
This patch adds a generic helper, genphy_loopback_no_speed(), that only
toggles BMCR_LOOPBACK and rejects a speed when loopback is enabled. It
uses the helper as .set_loopback for the S2500 entry in ncn26000. It also
converts dp83867 from its private dp83867_loopback() to the new helper.
> diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
> index a33ebb2a7ecef..de0fafda38761 100644
> --- a/drivers/net/phy/ncn26000.c
> +++ b/drivers/net/phy/ncn26000.c
> @@ -178,6 +178,7 @@ static struct phy_driver ncn26000_driver[] = {
> .config_aneg = ncn26000_config_aneg,
> .read_status = ncn26000_read_status,
> .handle_interrupt = ncn26000_handle_interrupt,
> + .set_loopback = genphy_loopback_no_speed,
[Severity: Medium]
Can ncn26000_config_aneg() quietly take the PHY back out of loopback? It
writes the whole BMCR and does not keep BMCR_LOOPBACK:
drivers/net/phy/ncn26000.c:ncn26000_config_aneg() {
...
// bring up the link
return phy_write(phydev, MII_BMCR, NCN26000_BCMR_LINK_CTRL_BIT);
}
genphy_setup_forced(), by contrast, keeps the bit:
return phy_modify(phydev, MII_BMCR,
~(BMCR_LOOPBACK | BMCR_ISOLATE | BMCR_PDOWN), ctl);
genphy_loopback_no_speed() sets bit 14 and returns. Nothing in the aneg
path checks phydev->loopback_enabled.
Here is one ordering that seems reachable. Bringing the interface up calls
phy_start(), which sets PHY_UP and queues the state machine. If ethtool -t
(net_selftest) takes phydev->lock in phy_loopback(true, 0) before that
work runs, the work then does:
phy_state_machine()
_phy_state_machine() /* PHY_UP */
_phy_start_aneg()
phy_config_aneg()
ncn26000_config_aneg()
phy_write(phydev, MII_BMCR, ...) /* clears BMCR_LOOPBACK */
A cable test that finishes during the self-test (PHY_CABLETEST to PHY_UP)
reaches _phy_start_aneg() the same way.
phy_check_link_status() returns early when phydev->loopback_enabled is
set, so the mismatch is never noticed.
Wouldn't the net_selftest frames then go out onto the shared 10BASE-T1S
medium instead of looping back inside the PHY? That would make the
self-test report false failures, and the final phy_loopback(false) would
clear a bit that is already clear.
The old genphy_loopback() fallback also wrote BMCR_LOOPBACK. With this
helper, though, loopback actually engages, so the new feature depends on
config_aneg leaving bit 14 alone.
Should ncn26000_config_aneg() preserve BMCR_LOOPBACK, for example by using
phy_modify() instead of phy_write()?
> .set_plca_cfg = ncn26000_c45_plca_set_cfg,
> .get_plca_cfg = genphy_c45_plca_get_cfg,
> .get_plca_status = genphy_c45_plca_get_status,
> diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
> index bfce8b893644f..03419f6455875 100644
> --- a/drivers/net/phy/phy_device.c
> +++ b/drivers/net/phy/phy_device.c
> @@ -2791,6 +2791,28 @@ int genphy_resume(struct phy_device *phydev)
> }
> EXPORT_SYMBOL(genphy_resume);
>
> +/**
> + * genphy_loopback_no_speed - Helper to modify the PHY loopback mode
> + * without setting speed. Some PHYs, like 10Base-T1S, don't support
> + * auto-negotiation and therefore setting the speed isn't supported.
> + *
> + * @phydev: The phy_device struct
> + * @enable: Flag to enable or disable the PHY level loopback.
> + * @speed: Speed setting. Not expected to be set. Error if it is set.
> + *
> + * Returns: 0 if successful, negative error code on failure.
> + */
> +int genphy_loopback_no_speed(struct phy_device *phydev, bool enable,
> + int speed)
> +{
> + if (enable && speed)
> + return -EOPNOTSUPP;
[Severity: Low]
This isn't a bug, but the @speed description says "Error if it is set".
The code only returns -EOPNOTSUPP when enable && speed. A disable call
with a non-zero speed succeeds.
That behaviour matches the set_loopback contract in include/linux/phy.h,
where EOPNOTSUPP only applies when loopback is enabled with speed > 0.
Could the kernel-doc describe it that way?
The reason given about 10Base-T1S not supporting auto-negotiation also
fits poorly with dp83867. This patch converts dp83867 to the same helper,
and it is a gigabit PHY that supports autoneg.
Would something like "speed selection in loopback is not supported"
describe the helper more accurately?
> +
> + return phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,
> + enable ? BMCR_LOOPBACK : 0);
> +}
> +EXPORT_SYMBOL(genphy_loopback_no_speed);
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d%40onsemi.com
^ permalink raw reply [flat|nested] 25+ messages in thread