* [PATCH net-next v11 00/14] (no cover subject)
@ 2026-11-13 16:59 ` Selvamani Rajagopal
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Changes in v11:
- Create new patches to move vendor changes to its own patch
- Improve commit message to be more informative of the changes
- Add clarity to kernel documentation in oa-tc6-framework.rst
- Fix issues potential Oops issue in S2500 driver.
- Add logic to take care of frame drop bit from MAC-PHY
- Link to v10: https://lore.kernel.org/r/20261106-s2500-mac-phy-support-v10-0-31b5a69b85b7@onsemi.com
Support for onsemi's S2500 10Base-T1S MAC-PHY
This patch series brings support for onsemi's S2500 that iss
IEEE 802.3cg compliant Ethernet transceiver with an integrated
Media Access Controller (MAC-PHY)
Driver implementation is compatible and works with OA TC6
framework that is already present. S2500 driver supports
hardware timestamping. Along with this driver, OA TC6
frame work is also changed to add hardware timestamping
support.
Driver has support for running selftest and loopback tests.
Through ethtool, it can provide traffic stats, rmon stats,
and timestamping related traffic stats.
As S2500 has an internal PHY, changes have been added
to onsemi's PHY driver to support this device.
To: Andrew Lunn <andrew@lunn.ch>
To: Heiner Kallweit <hkallweit1@gmail.com>
To: Russell King <linux@armlinux.org.uk>
To: David S. Miller <davem@davemloft.net>
To: Eric Dumazet <edumazet@google.com>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
To: Andrew Lunn <andrew+netdev@lunn.ch>
To: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
To: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
To: Richard Cochran <richardcochran@gmail.com>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Simon Horman <horms@kernel.org>
To: Jonathan Corbet <corbet@lwn.net>
To: Shuah Khan <skhan@linuxfoundation.org>
To: Randy Dunlap <rdunlap@infradead.org>
To: Eric Dumazet <edumazet@kernel.org>
To: Piergiorgio Beruto <piergiorgio.beruto@gmail.com>
Cc: netdev@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-doc@vger.kernel.org
Cc: Jerry Ray <jerry.ray@microchip.com>
Cc: Ciprian Regus <ciprian.regus@analog.com>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Changes in v10:
- Removed config_intr callback in S2500 PHY driver
- Removed writing status0/config0 from s2500 driver
- Implemented API to set tx credit threshold and
to enable RX cut-through mode
- Drop the frame when frame drop bit in footer is set
- Module name restored back to oa_tc6.ko from oa_tc6_mod.ko
- Link to v9: https://lore.kernel.org/r/20261009-s2500-mac-phy-support-v9-0-dcefe1d0bf0d@onsemi.com
Changes in v9:
- Renamed the new phy API from having "fixed_speed" to "no_speed"
- Fixed compilation issue due to having changes in the wrong patch
- Added the MAINTAINERS changes to where files were introduced.
- Fixed coding style issues reported.
- Moved the order of patches so that selftest changes goes after
s2500 support.
- Made Pier's email ID consistent across to be consistent across
the codebase.
- Link to v8: https://lore.kernel.org/r/20260928-s2500-mac-phy-support-v8-0-7e011aacc309@onsemi.com
Changes in v8:
- Modified to use newer MMD based read/write APIs
- clean up by removing unused definitions/initializations
- Better handling of timestamp capture events by using
bit fields so that we don't lose any event
- timestamp skb queue is protected with spinlock
- added reset-gpio in YAML file as support is recently added
- removed ioctl from OA TC6 framework as it is not needed.
- Link to v7: https://lore.kernel.org/all/20260708-s2500-mac-phy-support-v7-0-478c877aa1a9@onsemi.com
Changes in v7:
- Fixed compiler warning due to prototype mismatch
- Moved the order of patches to ensure dt-bindings comes first
before it is being used
- Added missing review tag
- Added missing MODULE_DEVICE_TABLE
- OA_TC6 framework enables 64 bit timestamping mode by default.
- Compiler related warnings fixed.
- Link to v6: https://lore.kernel.org/r/20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com
Changes in v6:
- Changes to ensure onsemi code stay within 80 columns.
- Fixed the error of "not described in comments section"
in comments section.
- Fixed compiler warnings.
- Link to v5: https://patch.msgid.link/20260614-s2500-mac-phy-support-v5-0-89874b72f725@onsemi.com
Changes in v5:
- kernel doc related changes in oa_tc.c, onsemi driver files and
oa tc6 rst file
- Link to v4: https://lore.kernel.org/r/20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com
Changes in v4:
- Added return value comment for genphy_read/write_phy_mmd functions
- Added genphy_loopback_fixed_speed helper function to be used in
set_loopback callbacks
- Updated networking documentation for OA TC6 framework to elaborate
on what is expected in the ptp_clock_info structure for registration.
- added spi-max-frequency in YAML file based on alert from sashiko-bot
- Removed model/version from the onsemi driver's private structure as
they were useful as "information-only" data.
- Replaced the non-standard selftest with Linux's standard selftest
and made it as a separate patch
- Changed bit manipulation, shift operations to use macros so that
it is clean and readable.
- added new read_register and write_register apis with _mms postfix
so that MMS (memory map selector) can be given as a parameter.
- Fixed the wrong condition check with NETIF_F_RXFCS to subtract
FCS size from the length of the frame.
- Link to v3: https://lore.kernel.org/r/CY8PR02MB92499C9080614FA3CEB8CD0483162@CY8PR02MB9249.namprd02.prod.outlook.com
Changes in v3:
- Moved OA TC6 framework to its own directory.
- Split changes to smaller patches based on feedback.
- Added PHY loopback support as a separate patch.
- New, read and write register APIs with extra parameter MMS
- Link to v2: https://lore.kernel.org/r/CY8PR02MB924920C8825C7AE5D22EFA4483382@CY8PR02MB9249.namprd02.prod.outlook.com
Changes in v2:
- API to add vendor specific MMS to mdio device map.
- Link to v1: https://lore.kernel.org/r/CY8PR02MB9249D083B637477C254F9B0583322@CY8PR02MB9249.namprd02.prod.outlook.com
---
Selvamani Rajagopal (14):
dt-bindings: net: add onsemi's S2500
Documentation: networking: Add timestamp related APIs to OA TC6 framework
net: ethernet: oa_tc6: Move oa_tc6.c to its own directory
net: ethernet: oa_tc6: Move constant definitions to header file
net: ethernet: microchip: lan865x: Depends on PTP_1588_CLOCK_OPTIONAL
net: ethernet: adi: adin1140: Depends on PTP_1588_CLOCK_OPTIONAL
net: ethernet: oa_tc6: Support for hardware timestamp
net: ethernet: oa_tc6: Drop frames if footer has FD bit set
net: phy: ncn26000: Enable enhanced noise immunity
net: ethernet: onsemi: s2500: Add driver support for S2500
net: ethernet: onsemi: s2500: Add selftest support
net: phy: Add helper to modify only the PHY loopback bit
net: phy: ncn26000: Support for onsemi's S2500 internal phy
net: phy: dp83867: Use genphy_loopback_no_speed()
.../devicetree/bindings/net/onnn,s2500.yaml | 71 +++
Documentation/networking/oa-tc6-framework.rst | 81 +++-
MAINTAINERS | 11 +-
drivers/net/ethernet/Kconfig | 13 +-
drivers/net/ethernet/Makefile | 3 +-
drivers/net/ethernet/adi/Kconfig | 1 +
drivers/net/ethernet/microchip/lan865x/Kconfig | 1 +
drivers/net/ethernet/oa_tc6/Kconfig | 16 +
drivers/net/ethernet/oa_tc6/Makefile | 7 +
.../ethernet/{oa_tc6.c => oa_tc6/oa_tc6_main.c} | 504 ++++++++++++++++-----
drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c | 68 +++
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 173 +++++++
drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c | 224 +++++++++
drivers/net/ethernet/onsemi/Kconfig | 21 +
drivers/net/ethernet/onsemi/Makefile | 6 +
drivers/net/ethernet/onsemi/s2500/Kconfig | 20 +
drivers/net/ethernet/onsemi/s2500/Makefile | 6 +
drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c | 388 ++++++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h | 151 ++++++
drivers/net/ethernet/onsemi/s2500/s2500_main.c | 498 ++++++++++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_ptp.c | 156 +++++++
drivers/net/phy/Kconfig | 2 +-
drivers/net/phy/dp83867.c | 11 +-
drivers/net/phy/ncn26000.c | 67 ++-
drivers/net/phy/phy_device.c | 24 +
include/linux/oa_tc6.h | 32 ++
include/linux/phy.h | 2 +
27 files changed, 2412 insertions(+), 145 deletions(-)
---
base-commit: d1f4b7c6b997c29de67ad7178ababac2328cb1b4
change-id: 20260601-s2500-mac-phy-support-4f3ae920fb73
Best regards,
--
Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
^ permalink raw reply [flat|nested] 32+ messages in thread* [PATCH net-next v11 01/14] dt-bindings: net: add onsemi's S2500
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal via B4 Relay
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
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 v11
- No change
changes in v10
- No change
changes in v9
- No changes
changes in v8
- Changed the interrupt type to level triggered
- Added reset-gpios 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..e1df674c0f1e
--- /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:
+ - Selvamani 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 c583367c88bc..4a4c26694ba7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21013,6 +21013,12 @@ S: Supported
W: http://www.onsemi.com
F: drivers/net/phy/ncn*
+ONSEMI S2500 10BASE-T1S MACPHY ETHERNET DRIVER
+M: Selvamani 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 related [flat|nested] 32+ messages in thread* [PATCH net-next v11 01/14] dt-bindings: net: add onsemi's S2500
@ 2026-11-13 16:59 ` Selvamani Rajagopal via B4 Relay
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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 v11
- No change
changes in v10
- No change
changes in v9
- No changes
changes in v8
- Changed the interrupt type to level triggered
- Added reset-gpios 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..e1df674c0f1e
--- /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:
+ - Selvamani 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 c583367c88bc..4a4c26694ba7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21013,6 +21013,12 @@ S: Supported
W: http://www.onsemi.com
F: drivers/net/phy/ncn*
+ONSEMI S2500 10BASE-T1S MACPHY ETHERNET DRIVER
+M: Selvamani 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 related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 02/14] Documentation: networking: Add timestamp related APIs to OA TC6 framework
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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>
Add new APIs to support hardware timestamp feature as defined in
OPEN Alliance 10BASE-T1x MAC-PHY serial interface specification.
Add the newer read/write APIs that take 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 v11
- Changed the wording of timestamp support to make it more clear
changes in v10
- Added new API oa_tc6_tx_credit_threshold_set
- Vendor introduced APIs to vendor support patch
changes in v9
- Added Reviewed tag
changes in v8
- Updated 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 | 54 +++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst
index 2545ef694016..111583609933 100644
--- a/Documentation/networking/oa-tc6-framework.rst
+++ b/Documentation/networking/oa-tc6-framework.rst
@@ -153,6 +153,11 @@ OPEN Alliance TC6 Framework
- Forwards the received Ethernet frame from 10Base-T1x MAC-PHY to n/w
subsystem.
+- If supported and enabled, timestamps are stored in the skb. Transmit
+ timestamps are requested via the TSC header field and read from
+ timestamp capture register A, B or C. Receive timestamps are
+ prepended to the frame when the RTSA footer bit is set.
+
Data Transaction
~~~~~~~~~~~~~~~~
@@ -496,3 +501,52 @@ 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 (PHC) with the kernel. Returns 0 immediately
+when the MAC-PHY does not support hardware timestamp. Otherwise it sets the
+name field of ptp_clock_info and registers the clock. Initialization of the
+other fields in the ptp_clock_info structure is left to the vendor as the
+PHC registers and their programming are vendor specific. 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 registering the hardware clock.
+
+.. c:function:: void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
+
+Unregisters the PTP hardware clock.
+
+.. 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 *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:: 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 related [flat|nested] 32+ messages in thread* [PATCH net-next v11 02/14] Documentation: networking: Add timestamp related APIs to OA TC6 framework
@ 2026-11-13 16:59 ` Selvamani Rajagopal
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Add new APIs to support hardware timestamp feature as defined in
OPEN Alliance 10BASE-T1x MAC-PHY serial interface specification.
Add the newer read/write APIs that take 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 v11
- Changed the wording of timestamp support to make it more clear
changes in v10
- Added new API oa_tc6_tx_credit_threshold_set
- Vendor introduced APIs to vendor support patch
changes in v9
- Added Reviewed tag
changes in v8
- Updated 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 | 54 +++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst
index 2545ef694016..111583609933 100644
--- a/Documentation/networking/oa-tc6-framework.rst
+++ b/Documentation/networking/oa-tc6-framework.rst
@@ -153,6 +153,11 @@ OPEN Alliance TC6 Framework
- Forwards the received Ethernet frame from 10Base-T1x MAC-PHY to n/w
subsystem.
+- If supported and enabled, timestamps are stored in the skb. Transmit
+ timestamps are requested via the TSC header field and read from
+ timestamp capture register A, B or C. Receive timestamps are
+ prepended to the frame when the RTSA footer bit is set.
+
Data Transaction
~~~~~~~~~~~~~~~~
@@ -496,3 +501,52 @@ 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 (PHC) with the kernel. Returns 0 immediately
+when the MAC-PHY does not support hardware timestamp. Otherwise it sets the
+name field of ptp_clock_info and registers the clock. Initialization of the
+other fields in the ptp_clock_info structure is left to the vendor as the
+PHC registers and their programming are vendor specific. 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 registering the hardware clock.
+
+.. c:function:: void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
+
+Unregisters the PTP hardware clock.
+
+.. 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 *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:: 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 related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 03/14] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal via B4 Relay
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Move oa_tc6.c to its own directory, drivers/net/ethernet/oa_tc6.
File oa_tc6.c is renamed to oa_tc6_main.c to maintain the kernel
module's name as oa_tc6. This will facilitate adding more files
to support other features defined by OPEN Alliance 10BASE-T1x
Serial Interface specification.
This patch series adds 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 v11
- Mentioned the renaming of oa_tc6.c in the commit message
changes in v10
- Kernel module restored to oa_tc6.ko from oa_tc6_mod.ko
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 | 15 +++++++++++++++
drivers/net/ethernet/oa_tc6/Makefile | 7 +++++++
drivers/net/ethernet/{oa_tc6.c => oa_tc6/oa_tc6_main.c} | 0
6 files changed, 25 insertions(+), 13 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 4a4c26694ba7..028fa967674e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21043,7 +21043,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..ec2787eae4b1
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/Kconfig
@@ -0,0 +1,15 @@
+# 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..c51621fae8be
--- /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.o
+oa_tc6-objs := oa_tc6_main.o
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
similarity index 100%
rename from drivers/net/ethernet/oa_tc6.c
rename to drivers/net/ethernet/oa_tc6/oa_tc6_main.c
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH net-next v11 03/14] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory
@ 2026-11-13 16:59 ` Selvamani Rajagopal via B4 Relay
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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>
Move oa_tc6.c to its own directory, drivers/net/ethernet/oa_tc6.
File oa_tc6.c is renamed to oa_tc6_main.c to maintain the kernel
module's name as oa_tc6. This will facilitate adding more files
to support other features defined by OPEN Alliance 10BASE-T1x
Serial Interface specification.
This patch series adds 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 v11
- Mentioned the renaming of oa_tc6.c in the commit message
changes in v10
- Kernel module restored to oa_tc6.ko from oa_tc6_mod.ko
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 | 15 +++++++++++++++
drivers/net/ethernet/oa_tc6/Makefile | 7 +++++++
drivers/net/ethernet/{oa_tc6.c => oa_tc6/oa_tc6_main.c} | 0
6 files changed, 25 insertions(+), 13 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 4a4c26694ba7..028fa967674e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21043,7 +21043,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..ec2787eae4b1
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/Kconfig
@@ -0,0 +1,15 @@
+# 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..c51621fae8be
--- /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.o
+oa_tc6-objs := oa_tc6_main.o
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
similarity index 100%
rename from drivers/net/ethernet/oa_tc6.c
rename to drivers/net/ethernet/oa_tc6/oa_tc6_main.c
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 04/14] net: ethernet: oa_tc6: Move constant definitions to header file
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal via B4 Relay
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
To help other source files within the module share the
constant definitions, some of the definitions from
oa_tc6_main.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 v11
- Removed the reference to oa_tc6.c in commit message. It is
oa_tc6_main.c now.
changes in v10
- No change
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_main.c | 107 +----------------------
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 125 +++++++++++++++++++++++++++
2 files changed, 127 insertions(+), 105 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
index 8e86cfde6d1c..341d13c97f91 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
@@ -12,111 +12,8 @@
#include <linux/interrupt.h>
#include <linux/mdio.h>
#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..c2c120ddcc07
--- /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/bits.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/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,
+};
+#endif /* OA_TC6_STD_DEF_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH net-next v11 04/14] net: ethernet: oa_tc6: Move constant definitions to header file
@ 2026-11-13 16:59 ` Selvamani Rajagopal via B4 Relay
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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_main.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 v11
- Removed the reference to oa_tc6.c in commit message. It is
oa_tc6_main.c now.
changes in v10
- No change
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_main.c | 107 +----------------------
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 125 +++++++++++++++++++++++++++
2 files changed, 127 insertions(+), 105 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
index 8e86cfde6d1c..341d13c97f91 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
@@ -12,111 +12,8 @@
#include <linux/interrupt.h>
#include <linux/mdio.h>
#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..c2c120ddcc07
--- /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/bits.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/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,
+};
+#endif /* OA_TC6_STD_DEF_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 05/14] net: ethernet: microchip: lan865x: Depends on PTP_1588_CLOCK_OPTIONAL
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal via B4 Relay
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Add the PTP_1588_CLOCK_OPTIONAL dependency to LAN865X. A following
patch makes OA_TC6 depend on it, but "select" ignores that, so
LAN865X built in with PTP_1588_CLOCK=m would link-fail on
ptp_clock_register().
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Moved the vendor changes into its own patch.
- New patch in v11.
---
drivers/net/ethernet/microchip/lan865x/Kconfig | 1 +
1 file changed, 1 insertion(+)
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
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 05/14] net: ethernet: microchip: lan865x: Depends on PTP_1588_CLOCK_OPTIONAL
@ 2026-11-13 16:59 ` Selvamani Rajagopal via B4 Relay
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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>
Add the PTP_1588_CLOCK_OPTIONAL dependency to LAN865X. A following
patch makes OA_TC6 depend on it, but "select" ignores that, so
LAN865X built in with PTP_1588_CLOCK=m would link-fail on
ptp_clock_register().
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Moved the vendor changes into its own patch.
- New patch in v11.
---
drivers/net/ethernet/microchip/lan865x/Kconfig | 1 +
1 file changed, 1 insertion(+)
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
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 06/14] net: ethernet: adi: adin1140: Depends on PTP_1588_CLOCK_OPTIONAL
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal via B4 Relay
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Add the PTP_1588_CLOCK_OPTIONAL dependency to ADIN1140. A following
patch makes OA_TC6 depend on it, but "select" ignores that, so
ADIN1140 built in with PTP_1588_CLOCK=m would link-fail on
ptp_clock_register().
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Moved the vendor changes into its own patch.
- New patch in v11.
---
drivers/net/ethernet/adi/Kconfig | 1 +
1 file changed, 1 insertion(+)
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
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 06/14] net: ethernet: adi: adin1140: Depends on PTP_1588_CLOCK_OPTIONAL
@ 2026-11-13 16:59 ` Selvamani Rajagopal via B4 Relay
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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>
Add the PTP_1588_CLOCK_OPTIONAL dependency to ADIN1140. A following
patch makes OA_TC6 depend on it, but "select" ignores that, so
ADIN1140 built in with PTP_1588_CLOCK=m would link-fail on
ptp_clock_register().
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Moved the vendor changes into its own patch.
- New patch in v11.
---
drivers/net/ethernet/adi/Kconfig | 1 +
1 file changed, 1 insertion(+)
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
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 07/14] net: ethernet: oa_tc6: Support for hardware timestamp
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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 are provided
by vendor code as it may be vendor dependent.
Export helpers for the vendor driver's ndo_hwtstamp_set/get and
ethtool get_ts_info/get_ts_stats callbacks to support the hardware
timestamp feature.
Hardware timestamp functions that handle the traffic are generally
placed in oa_tc6_main.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, an 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.
When timestamping is enabled, the MAC-PHY prepends an 8-byte timestamp
to received frames. The RX skb is now OA_TC6_TSTAMP_SZ bytes larger,
so a full-MTU frame is not dropped for lack of tailroom. RX frame
shorter than ETH_HLEN (plus 8 bytes when timestamp is present) is
dropped now and counted in rx_length_errors and rx_dropped.
TX timestamps are reported to the stack with skb_tstamp_tx() once the
MAC-PHY signals the capture. For RX, the prepended timestamp is parsed
into the skb's hardware timestamp.
With timestamp support, the OA_TC6 framework depends on
PTP_1588_CLOCK_OPTIONAL. Drivers selecting OA_TC6 must add this
dependency to avoid a link failure when PTP_1588_CLOCK is built as a
module.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Changed oa_tc6.c to oa_tc6_main.c in commit message. Also
added skb size change in the commit message.
- Fixed the issue of accidentally enabling software timestamping
when PTP clock is not registered in oa_tc6_get_ts_info callback
- Reference to ioctl in commit message is dropped as the helper is
removed
changes in v10
- Added protection to skb queue handling when timestamp
feature is enabled/disabled.
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 of 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 definitions 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 prototype 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/oa_tc6/Kconfig | 1 +
drivers/net/ethernet/oa_tc6/Makefile | 2 +-
drivers/net/ethernet/oa_tc6/oa_tc6_main.c | 325 +++++++++++++++++++++++++--
drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c | 68 ++++++
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 53 ++++-
drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c | 224 ++++++++++++++++++
include/linux/oa_tc6.h | 25 +++
8 files changed, 679 insertions(+), 20 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 028fa967674e..415d02d59681 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21040,6 +21040,7 @@ F: drivers/rtc/rtc-optee.c
OPEN ALLIANCE 10BASE-T1S MACPHY SERIAL INTERFACE FRAMEWORK
M: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
+M: Selvamani 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/oa_tc6/Kconfig b/drivers/net/ethernet/oa_tc6/Kconfig
index ec2787eae4b1..904366a5c339 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 c51621fae8be..a645584bdca0 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.o
-oa_tc6-objs := oa_tc6_main.o
+oa_tc6-objs := oa_tc6_main.o oa_tc6_ptp.o oa_tc6_tstamp.o
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
index 341d13c97f91..2b31370da2d2 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_main.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>
@@ -15,6 +16,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)
{
@@ -49,6 +60,212 @@ 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 ||
+ tc6->ts_config.tx_type != HWTSTAMP_TX_ON)
+ 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 their event. This skb
+ * can't request timestamp. Marking it to be dropped.
+ */
+ tc6->tx_hwtstamp_err++;
+ 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;
+}
+
+/* Hand a fully sent frame over to wait for its timestamp event. */
+static void oa_tc6_queue_tx_tstamp_skb(struct oa_tc6 *tc6,
+ struct sk_buff *skb)
+{
+ u8 tsc = oa_tc6_tsinfo_tx(skb)->tsc;
+
+ spin_lock_bh(&tc6->tx_skb_lock);
+
+ /* Clear if oa_tc6_cleanup_tx_tstamp_skbs() abandoned this frame's
+ * slot while the frame was being sent.
+ */
+ if (tc6->ts_ttsc_pending & BIT(tsc - OA_TC6_TTSCA_REG_ID)) {
+ list_add_tail(&skb->list, &tc6->tx_ts_skb_q);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ return;
+ }
+
+ tc6->tx_hwtstamp_lost++;
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ dev_kfree_skb(skb);
+}
+
+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);
+ dev_kfree_skb(skb);
+
+ /* The frame was transmitted, only its timestamp is lost. */
+ tc6->tx_hwtstamp_lost++;
+ }
+
+ /* Also abandons the slot of a frame still being sent. */
+ tc6->ts_ttsc_pending = 0;
+ 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_FTSC_CAPABILITY) /* Timestamp */
+ tc6->ftsc_capable = true;
+ return 0;
+}
+
static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
enum oa_tc6_register_op reg_op)
{
@@ -624,6 +841,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;
}
@@ -649,6 +876,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)
@@ -677,6 +905,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);
@@ -685,15 +915,25 @@ 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);
+ /* Only STATUS0 is read without a PTP clock. With a PTP clock, the
+ * registers up to TTSCC_LOW are read in the same transaction. That
+ * is cheaper than a second SPI transaction when a capture is pending,
+ * and costs extra register reads otherwise.
+ */
+ 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
@@ -702,6 +942,12 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
if (!value)
return 0;
+ /* Without ts_valid the TTSC registers were not read. */
+ if (ts_valid && (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) {
@@ -787,6 +1033,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;
@@ -822,25 +1075,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;
@@ -853,11 +1113,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;
@@ -904,7 +1164,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 */
@@ -912,7 +1172,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 */
@@ -945,7 +1205,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 */
@@ -1024,13 +1284,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);
@@ -1049,6 +1311,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.
@@ -1058,8 +1321,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
@@ -1080,17 +1345,25 @@ 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
+ oa_tc6_queue_tx_tstamp_skb(tc6, tc6->ongoing_tx_skb);
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
@@ -1099,10 +1372,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;
@@ -1119,7 +1403,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 +
@@ -1311,6 +1595,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);
@@ -1370,6 +1655,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;
@@ -1435,6 +1722,12 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
return ERR_PTR(ret);
}
+ ret = oa_tc6_update_standard_capability(tc6);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "Failed to read capability\n");
+ return ERR_PTR(ret);
+ }
+
ret = oa_tc6_unmask_macphy_error_interrupts(tc6);
if (ret) {
dev_err(&tc6->spi->dev,
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..07ce0a750fcf
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for hardware timestamping feature for OPEN Alliance
+ * 10BASE-T1x MAC-PHY Serial Interface framework
+ *
+ * Author: Selvamani 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 the PTP clock
+ * @tc6: oa_tc6 struct.
+ * @info: Describes a PTP hardware clock
+ *
+ * Description: Vendors are expected to set the PTP clock operations
+ * in @info before calling this function. This function returns
+ * immediately if the MAC-PHY does not support hardware timestamp.
+ * Otherwise it sets @info->name and registers the clock.
+ *
+ * Return: 0 on success or when hardware timestamp is not supported,
+ * otherwise a negative error code.
+ *
+ */
+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->ftsc_capable)
+ 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 the PTP clock
+ * @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 c2c120ddcc07..98d815f8f0b4 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -1,9 +1,7 @@
/* 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>
+ * OPEN Alliance 10BASE-T1x MAC-PHY Serial Interface framework.
*/
#ifndef OA_TC6_STD_DEF_H
@@ -12,6 +10,7 @@
#include <linux/bits.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/net_tstamp.h>
+#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include <linux/spi/spi.h>
#include <linux/skbuff.h>
@@ -19,6 +18,9 @@
#include <linux/wait.h>
#include <linux/oa_tc6.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)
@@ -34,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,7 +47,10 @@
#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_FRAME_DROP BIT(15)
#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)
@@ -71,6 +77,34 @@
#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
+
+/* OA TC6 register addresses count 32-bit registers, not bytes. The difference
+ * of two addresses is therefore the index into a u32 array that was read
+ * starting at the first address, so the OFFSET macros below are array
+ * indices.
+ */
+#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;
@@ -96,6 +130,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 ftsc_capable; /* Timestamp capability */
+ 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 {
@@ -122,4 +166,7 @@ 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..b3cc032311a9
--- /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: Selvamani 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 what was requested */
+ 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 0;
+
+ 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..32c25b5e2aa3 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -7,13 +7,20 @@
* 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/ethtool.h>
+#include <linux/phy.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_FTSC_CAPABILITY BIT(6)
/* Reset Control and Status Register */
#define OA_TC6_REG_RESET 0x0003
@@ -23,6 +30,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 +39,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 +56,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 +107,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 related [flat|nested] 32+ messages in thread* [PATCH net-next v11 07/14] net: ethernet: oa_tc6: Support for hardware timestamp
@ 2026-11-13 16:59 ` Selvamani Rajagopal
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
PTP register/unregister calls are implemented in oa_tc6_ptp.c.
The APIs that work with the hardware for timestamp are provided
by vendor code as it may be vendor dependent.
Export helpers for the vendor driver's ndo_hwtstamp_set/get and
ethtool get_ts_info/get_ts_stats callbacks to support the hardware
timestamp feature.
Hardware timestamp functions that handle the traffic are generally
placed in oa_tc6_main.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, an 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.
When timestamping is enabled, the MAC-PHY prepends an 8-byte timestamp
to received frames. The RX skb is now OA_TC6_TSTAMP_SZ bytes larger,
so a full-MTU frame is not dropped for lack of tailroom. RX frame
shorter than ETH_HLEN (plus 8 bytes when timestamp is present) is
dropped now and counted in rx_length_errors and rx_dropped.
TX timestamps are reported to the stack with skb_tstamp_tx() once the
MAC-PHY signals the capture. For RX, the prepended timestamp is parsed
into the skb's hardware timestamp.
With timestamp support, the OA_TC6 framework depends on
PTP_1588_CLOCK_OPTIONAL. Drivers selecting OA_TC6 must add this
dependency to avoid a link failure when PTP_1588_CLOCK is built as a
module.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Changed oa_tc6.c to oa_tc6_main.c in commit message. Also
added skb size change in the commit message.
- Fixed the issue of accidentally enabling software timestamping
when PTP clock is not registered in oa_tc6_get_ts_info callback
- Reference to ioctl in commit message is dropped as the helper is
removed
changes in v10
- Added protection to skb queue handling when timestamp
feature is enabled/disabled.
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 of 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 definitions 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 prototype 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/oa_tc6/Kconfig | 1 +
drivers/net/ethernet/oa_tc6/Makefile | 2 +-
drivers/net/ethernet/oa_tc6/oa_tc6_main.c | 325 +++++++++++++++++++++++++--
drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c | 68 ++++++
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 53 ++++-
drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c | 224 ++++++++++++++++++
include/linux/oa_tc6.h | 25 +++
8 files changed, 679 insertions(+), 20 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 028fa967674e..415d02d59681 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21040,6 +21040,7 @@ F: drivers/rtc/rtc-optee.c
OPEN ALLIANCE 10BASE-T1S MACPHY SERIAL INTERFACE FRAMEWORK
M: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
+M: Selvamani 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/oa_tc6/Kconfig b/drivers/net/ethernet/oa_tc6/Kconfig
index ec2787eae4b1..904366a5c339 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 c51621fae8be..a645584bdca0 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.o
-oa_tc6-objs := oa_tc6_main.o
+oa_tc6-objs := oa_tc6_main.o oa_tc6_ptp.o oa_tc6_tstamp.o
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
index 341d13c97f91..2b31370da2d2 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_main.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>
@@ -15,6 +16,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)
{
@@ -49,6 +60,212 @@ 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 ||
+ tc6->ts_config.tx_type != HWTSTAMP_TX_ON)
+ 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 their event. This skb
+ * can't request timestamp. Marking it to be dropped.
+ */
+ tc6->tx_hwtstamp_err++;
+ 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;
+}
+
+/* Hand a fully sent frame over to wait for its timestamp event. */
+static void oa_tc6_queue_tx_tstamp_skb(struct oa_tc6 *tc6,
+ struct sk_buff *skb)
+{
+ u8 tsc = oa_tc6_tsinfo_tx(skb)->tsc;
+
+ spin_lock_bh(&tc6->tx_skb_lock);
+
+ /* Clear if oa_tc6_cleanup_tx_tstamp_skbs() abandoned this frame's
+ * slot while the frame was being sent.
+ */
+ if (tc6->ts_ttsc_pending & BIT(tsc - OA_TC6_TTSCA_REG_ID)) {
+ list_add_tail(&skb->list, &tc6->tx_ts_skb_q);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ return;
+ }
+
+ tc6->tx_hwtstamp_lost++;
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ dev_kfree_skb(skb);
+}
+
+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);
+ dev_kfree_skb(skb);
+
+ /* The frame was transmitted, only its timestamp is lost. */
+ tc6->tx_hwtstamp_lost++;
+ }
+
+ /* Also abandons the slot of a frame still being sent. */
+ tc6->ts_ttsc_pending = 0;
+ 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_FTSC_CAPABILITY) /* Timestamp */
+ tc6->ftsc_capable = true;
+ return 0;
+}
+
static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
enum oa_tc6_register_op reg_op)
{
@@ -624,6 +841,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;
}
@@ -649,6 +876,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)
@@ -677,6 +905,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);
@@ -685,15 +915,25 @@ 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);
+ /* Only STATUS0 is read without a PTP clock. With a PTP clock, the
+ * registers up to TTSCC_LOW are read in the same transaction. That
+ * is cheaper than a second SPI transaction when a capture is pending,
+ * and costs extra register reads otherwise.
+ */
+ 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
@@ -702,6 +942,12 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
if (!value)
return 0;
+ /* Without ts_valid the TTSC registers were not read. */
+ if (ts_valid && (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) {
@@ -787,6 +1033,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;
@@ -822,25 +1075,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;
@@ -853,11 +1113,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;
@@ -904,7 +1164,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 */
@@ -912,7 +1172,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 */
@@ -945,7 +1205,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 */
@@ -1024,13 +1284,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);
@@ -1049,6 +1311,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.
@@ -1058,8 +1321,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
@@ -1080,17 +1345,25 @@ 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
+ oa_tc6_queue_tx_tstamp_skb(tc6, tc6->ongoing_tx_skb);
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
@@ -1099,10 +1372,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;
@@ -1119,7 +1403,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 +
@@ -1311,6 +1595,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);
@@ -1370,6 +1655,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;
@@ -1435,6 +1722,12 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
return ERR_PTR(ret);
}
+ ret = oa_tc6_update_standard_capability(tc6);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "Failed to read capability\n");
+ return ERR_PTR(ret);
+ }
+
ret = oa_tc6_unmask_macphy_error_interrupts(tc6);
if (ret) {
dev_err(&tc6->spi->dev,
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..07ce0a750fcf
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for hardware timestamping feature for OPEN Alliance
+ * 10BASE-T1x MAC-PHY Serial Interface framework
+ *
+ * Author: Selvamani 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 the PTP clock
+ * @tc6: oa_tc6 struct.
+ * @info: Describes a PTP hardware clock
+ *
+ * Description: Vendors are expected to set the PTP clock operations
+ * in @info before calling this function. This function returns
+ * immediately if the MAC-PHY does not support hardware timestamp.
+ * Otherwise it sets @info->name and registers the clock.
+ *
+ * Return: 0 on success or when hardware timestamp is not supported,
+ * otherwise a negative error code.
+ *
+ */
+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->ftsc_capable)
+ 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 the PTP clock
+ * @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 c2c120ddcc07..98d815f8f0b4 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -1,9 +1,7 @@
/* 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>
+ * OPEN Alliance 10BASE-T1x MAC-PHY Serial Interface framework.
*/
#ifndef OA_TC6_STD_DEF_H
@@ -12,6 +10,7 @@
#include <linux/bits.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/net_tstamp.h>
+#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include <linux/spi/spi.h>
#include <linux/skbuff.h>
@@ -19,6 +18,9 @@
#include <linux/wait.h>
#include <linux/oa_tc6.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)
@@ -34,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,7 +47,10 @@
#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_FRAME_DROP BIT(15)
#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)
@@ -71,6 +77,34 @@
#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
+
+/* OA TC6 register addresses count 32-bit registers, not bytes. The difference
+ * of two addresses is therefore the index into a u32 array that was read
+ * starting at the first address, so the OFFSET macros below are array
+ * indices.
+ */
+#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;
@@ -96,6 +130,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 ftsc_capable; /* Timestamp capability */
+ 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 {
@@ -122,4 +166,7 @@ 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..b3cc032311a9
--- /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: Selvamani 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 what was requested */
+ 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 0;
+
+ 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..32c25b5e2aa3 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -7,13 +7,20 @@
* 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/ethtool.h>
+#include <linux/phy.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_FTSC_CAPABILITY BIT(6)
/* Reset Control and Status Register */
#define OA_TC6_REG_RESET 0x0003
@@ -23,6 +30,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 +39,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 +56,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 +107,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 related [flat|nested] 32+ messages in thread* Re: [PATCH net-next v11 07/14] net: ethernet: oa_tc6: Support for hardware timestamp
2026-11-13 16:59 ` Selvamani Rajagopal
(?)
@ 2026-11-14 16:59 ` sashiko-bot
-1 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-11-14 16:59 UTC (permalink / raw)
To: Selvamani Rajagopal; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 4 potential
issues to consider:
- [Medium] net: oa_tc6: unreleased TTSC slots in
oa_tc6_req_tx_hwtstamp() wedge TX
- [Medium] net: oa_tc6: partial state update in
oa_tc6_set_hwtstamp_settings() on error
- [Medium] net: oa_tc6: race and partial update in
oa_tc6_set_hwtstamp_settings()
- [Low] net: oa_tc6: unlocked 64-bit stats access in
oa_tc6_get_ts_stats()
--
commit e0da8a70d844cec71943991d3a812fc7925cd09a
Author: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
net: ethernet: oa_tc6: Support for hardware timestamp
This commit adds hardware timestamping and PTP clock registration helpers
to the OPEN Alliance TC6 10BASE-T1x MAC-PHY framework.
> diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
> index 341d13c97f91f..2b31370da2d2b 100644
> --- a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
> +++ b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
[ ... ]
> @@ -49,6 +60,212 @@ 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 ||
> + tc6->ts_config.tx_type != HWTSTAMP_TX_ON)
> + 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 their event. This skb
> + * can't request timestamp. Marking it to be dropped.
> + */
> + tc6->tx_hwtstamp_err++;
> + 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;
> +}
[Severity: Medium]
Can unreleased slots in tc6->ts_ttsc_pending permanently wedge TX hardware
timestamping?
Once a frame is queued on tc6->tx_ts_skb_q, its bit in
tc6->ts_ttsc_pending is only cleared during normal operation when the
MAC-PHY signals a capture in OA_TC6_REG_STATUS0. If a frame buffered in
the MAC-PHY is aborted on the medium or flushed when the interface stops,
no capture event arrives and there is no timeout or ndo_stop cleanup to
reclaim the slot.
After three lost captures, all three bits stay set, causing
oa_tc6_req_tx_hwtstamp() to fail and oa_tc6_prepare_spi_tx_buf_for_tx_skbs()
to drop every subsequent SKBTX_HW_TSTAMP packet while the old skbs remain
pinned on tc6->tx_ts_skb_q. Restarting the PTP daemon with HWTSTAMP_TX_ON
will not recover either because oa_tc6_set_hwtstamp_settings() returns
early when the requested config matches tc6->ts_config.
> 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..b3cc032311a9d
> --- /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;
> +
> + 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);
[Severity: Medium]
Can oa_tc6_set_hwtstamp_settings() race with the threaded IRQ handler when
disabling TX hardware timestamping or disabling traffic?
CPU0 CPU1 (oa_tc6_macphy_threaded_irq)
oa_tc6_set_hwtstamp_settings()
oa_tc6_read_registers(CONFIG0..)
oa_tc6_write_register(STATUS0)
oa_tc6_prepare_spi_tx_buf_for_tx_skbs()
oa_tc6_req_tx_hwtstamp()
/* hw_tstamp_enabled still true */
/* frame transmits, latches TTSCA
* in STATUS0 */
oa_tc6_write_register(INT_MASK0)
oa_tc6_write_register(CONFIG0)
spin_lock_bh(&tc6->tx_skb_lock)
tc6->ts_config = *ts_cfg
spin_unlock_bh(&tc6->tx_skb_lock)
oa_tc6_cleanup_tx_tstamp_skbs()
tc6->ts_ttsc_pending = 0
oa_tc6_process_extended_status()
oa_tc6_events_handle()
/* unmatched TTSCA bit ->
* Unexpected event warning */
Because tc6->ts_config and tc6->hw_tstamp_enabled are updated only after
the blocking SPI register reads and writes, the threaded IRQ can keep
allocating TTSC slots and latching new capture bits in OA_TC6_REG_STATUS0
after STATUS0 was sampled and cleared. Once oa_tc6_cleanup_tx_tstamp_skbs()
empties tc6->tx_ts_skb_q, those late captures trigger false unexpected
event warnings and increment tx_hwtstamp_err, or can be matched to a newly
queued skb if TX timestamping is re-enabled before STATUS0 is read.
In addition, tc6->disable_traffic is only checked before the SPI reads, so
if oa_tc6_disable_traffic() runs concurrently on CPU1 and writes
OA_TC6_INT_MASK0_ALL_INTERRUPTS to OA_TC6_REG_INT_MASK0, CPU0 can
overwrite OA_TC6_REG_INT_MASK0 with its stale irqm value and re-enable
interrupts.
> + 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;
> +}
[Severity: Medium]
If writing OA_TC6_REG_INT_MASK0 or OA_TC6_REG_CONFIG0 fails when turning
off TX timestamping, does jumping to out strand queued skbs in
tc6->tx_ts_skb_q and leave hardware and software state out of sync?
At that point OA_TC6_REG_STATUS0 has already been cleared (and
OA_TC6_REG_INT_MASK0 has already masked TTSCA interrupts if the
OA_TC6_REG_CONFIG0 write fails), but the error path skips
oa_tc6_cleanup_tx_tstamp_skbs() and leaves tc6->ts_config.tx_type set to
HWTSTAMP_TX_ON. Any in-flight skbs whose capture bits were cleared or
masked will never complete, leaking their TTSC slots until all three slots
fill and subsequent timestamped TX packets are dropped.
[ ... ]
> +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 oa_tc6_get_ts_stats() read torn 64-bit values on 32-bit architectures?
tx_hwtstamp_pkts, tx_hwtstamp_err, and tx_hwtstamp_lost are plain u64
fields updated under tc6->tx_skb_lock (except in oa_tc6_events_handle(),
where tc6->tx_hwtstamp_err += hweight32(val & ~handled) runs after
spin_unlock_bh(&tc6->tx_skb_lock)), while oa_tc6_get_ts_stats() reads them
without holding tc6->tx_skb_lock.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-s2500-mac-phy-support-v11-0-7a186141952c@onsemi.com?part=7
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH net-next v11 08/14] net: ethernet: oa_tc6: Drop frames if footer has FD bit set
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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>
Drop a receive frame when the data footer of its last chunk has
the Frame Drop (FD) bit set. Dropping the frame with FD bit set
is in OPEN Alliance specification.
Here is one scenario when FD bit will be set. With RX cut-through
enabled, the MAC-PHY can start forwarding a frame before it knows
the frame is bad, and uses FD to tell the host to discard it.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Act on FD bit on the footer in the received data chunk.
- New patch in v11
---
drivers/net/ethernet/oa_tc6/oa_tc6_main.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
index 2b31370da2d2..5d01ef5a13ad 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
@@ -1150,6 +1150,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
footer);
bool start_valid = FIELD_GET(OA_TC6_DATA_FOOTER_START_VALID, footer);
bool end_valid = FIELD_GET(OA_TC6_DATA_FOOTER_END_VALID, footer);
+ bool frame_drop = FIELD_GET(OA_TC6_DATA_FOOTER_FRAME_DROP, footer);
u16 size;
/* Restart the new rx frame after receiving rx buffer overflow error */
@@ -1159,6 +1160,25 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
if (tc6->wait_until_start_valid)
return 0;
+ /* Drop the frame, if footer sets FD bit. Checking FD bit
+ * becomes important, when cut-through operation is enabled.
+ */
+ if (end_valid && frame_drop) {
+ oa_tc6_cleanup_ongoing_rx_skb(tc6);
+
+ /* Full frame in this chunk. Nothing to free. Move on to
+ * next chunk.
+ */
+ if (start_valid && start_byte_offset < end_byte_offset) {
+ tc6->netdev->stats.rx_dropped++;
+ return 0;
+ }
+
+ /* The chunk carries only the end of the dropped frame. */
+ if (!start_valid)
+ return 0;
+ }
+
/* Process the chunk with complete rx frame */
if (start_valid && end_valid && start_byte_offset < end_byte_offset) {
size = end_byte_offset + 1 - start_byte_offset;
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH net-next v11 08/14] net: ethernet: oa_tc6: Drop frames if footer has FD bit set
@ 2026-11-13 16:59 ` Selvamani Rajagopal
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Drop a receive frame when the data footer of its last chunk has
the Frame Drop (FD) bit set. Dropping the frame with FD bit set
is in OPEN Alliance specification.
Here is one scenario when FD bit will be set. With RX cut-through
enabled, the MAC-PHY can start forwarding a frame before it knows
the frame is bad, and uses FD to tell the host to discard it.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Act on FD bit on the footer in the received data chunk.
- New patch in v11
---
drivers/net/ethernet/oa_tc6/oa_tc6_main.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
index 2b31370da2d2..5d01ef5a13ad 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
@@ -1150,6 +1150,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
footer);
bool start_valid = FIELD_GET(OA_TC6_DATA_FOOTER_START_VALID, footer);
bool end_valid = FIELD_GET(OA_TC6_DATA_FOOTER_END_VALID, footer);
+ bool frame_drop = FIELD_GET(OA_TC6_DATA_FOOTER_FRAME_DROP, footer);
u16 size;
/* Restart the new rx frame after receiving rx buffer overflow error */
@@ -1159,6 +1160,25 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
if (tc6->wait_until_start_valid)
return 0;
+ /* Drop the frame, if footer sets FD bit. Checking FD bit
+ * becomes important, when cut-through operation is enabled.
+ */
+ if (end_valid && frame_drop) {
+ oa_tc6_cleanup_ongoing_rx_skb(tc6);
+
+ /* Full frame in this chunk. Nothing to free. Move on to
+ * next chunk.
+ */
+ if (start_valid && start_byte_offset < end_byte_offset) {
+ tc6->netdev->stats.rx_dropped++;
+ return 0;
+ }
+
+ /* The chunk carries only the end of the dropped frame. */
+ if (!start_valid)
+ return 0;
+ }
+
/* Process the chunk with complete rx frame */
if (start_valid && end_valid && start_byte_offset < end_byte_offset) {
size = end_byte_offset + 1 - start_byte_offset;
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 09/14] net: phy: ncn26000: Enable enhanced noise immunity
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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>
Intercept PLCA enable/disable requests in set_plca_cfg() and set or
clear the proprietary ENI bit accordingly. By setting the 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 v11
- Updated commit message to be more accurate
changes in v10
- No change
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 | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index cabdd83c614f..888d74b83125 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -35,6 +35,9 @@
#define TO_TMR_DEFAULT 32
+#define NCN26000_REG_PHYCFG1 0x8001
+#define NCN26000_PHYCFG1_ENI 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 +103,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, eni_cfg);
+}
+
static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
{
int ret;
@@ -151,7 +172,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 related [flat|nested] 32+ messages in thread* [PATCH net-next v11 09/14] net: phy: ncn26000: Enable enhanced noise immunity
@ 2026-11-13 16:59 ` Selvamani Rajagopal
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Intercept PLCA enable/disable requests in set_plca_cfg() and set or
clear the proprietary ENI bit accordingly. By setting the 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 v11
- Updated commit message to be more accurate
changes in v10
- No change
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 | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index cabdd83c614f..888d74b83125 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -35,6 +35,9 @@
#define TO_TMR_DEFAULT 32
+#define NCN26000_REG_PHYCFG1 0x8001
+#define NCN26000_PHYCFG1_ENI 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 +103,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, eni_cfg);
+}
+
static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
{
int ret;
@@ -151,7 +172,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 related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 10/14] net: ethernet: onsemi: s2500: Add driver support for S2500
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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.3cg compliant Ethernet
transceiver with integrated MAC-PHY. Works with
OPEN Alliance TC6 framework.
Hardware timestamp support is present in the driver. adjtime
functionality is implemented as a read-modify-write of the time
registers as the device has no add-delta register.
Driver has ethtool support for printing some device
registers, MAC stats, rmon stats and timestamp related
traffic counters.
Driver supports a maximum of three multicast filters. If
the system has more than 3 multicast addresses to support,
all multicast traffic is allowed.
Raise the transmit credit threshold from the default of 1
credit to 8, using the new oa_tc6_tx_credit_threshold_set()
helper, so that the MAC-PHY asserts the interrupt only when at
least 8 transmit credits are free.
Add the OA_TC6_CONFIG_RX_CUT_THROUGH quirk flag to the OPEN
Alliance TC6 framework and use it to enable RX cut-through,
as it must be enabled before the SYNC bit is set.
Enable the MAC's hardware FCS generation, so the host does
not have to append the FCS to transmitted frames.
Statistics read from the device are cached for 100 ms, so
repeated ethtool queries within that period return the
cached counters.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- fixed a potential "Oops" due to jumping to wrong label on error
while enabling RX cut-through feature
- Function that enables RX cut-through is made private
- Moved the frame drop feature to separate patch as it is not
related to S2500 driver support.
- MAINTAINERS file changed to include files under onsemi too.
changes in v10
- Removed the code that overwrites CONFIG0 after oa_tc6_init
is done.
- Removed the code that clears status0 register that would
interfere interrupt handling
- setting tx credit threshold to 8 credits
- New API documentation moved to this patch
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 unnecessary 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 no 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
---
Documentation/networking/oa-tc6-framework.rst | 27 +-
MAINTAINERS | 1 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/oa_tc6/oa_tc6_main.c | 62 +++
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 1 +
drivers/net/ethernet/onsemi/Kconfig | 21 +
drivers/net/ethernet/onsemi/Makefile | 6 +
drivers/net/ethernet/onsemi/s2500/Kconfig | 20 +
drivers/net/ethernet/onsemi/s2500/Makefile | 6 +
drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c | 379 ++++++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h | 151 +++++++
drivers/net/ethernet/onsemi/s2500/s2500_main.c | 498 ++++++++++++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_ptp.c | 156 +++++++
include/linux/oa_tc6.h | 7 +
15 files changed, 1336 insertions(+), 1 deletion(-)
diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst
index 111583609933..29bf6d4d80eb 100644
--- a/Documentation/networking/oa-tc6-framework.rst
+++ b/Documentation/networking/oa-tc6-framework.rst
@@ -462,7 +462,15 @@ The include/linux/oa_tc6.h defines the following functions:
struct net_device *netdev, \
struct oa_tc6_quirks *quirks)
-Initialize OA TC6 lib.
+Initialize OA TC6 lib. The device specific modifiers of the protocol are
+passed in the quirk_flags field of struct oa_tc6_quirks, which is optional
+and may be NULL:
+
+- OA_TC6_BROKEN_PHY: Leave MDIO bus registration and PHY connection to the
+ vendor driver instead of the framework.
+- OA_TC6_CONFIG_RX_CUT_THROUGH: Enable receive cut-through in the CONFIG0
+ register. It is ignored if the MAC-PHY does not report receive cut-through
+ capability.
.. c:function:: void oa_tc6_exit(struct oa_tc6 *tc6)
@@ -502,6 +510,23 @@ 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_tx_credit_threshold_set(struct oa_tc6 *tc6, \
+ u8 thresh);
+
+Sets the minimum number of free transmit credits that must be available
+before the MAC-PHY asserts the interrupt. The default is one credit
+according to OPEN Alliance 10BASE-T1x MAC-PHY Serial Interface specification.
+
+The thresh argument is the encoded TXCTHRESH field value of the CONFIG0
+register, not the credit count:
+
+- 0: one or more credits (default)
+- 1: four or more credits
+- 2: eight or more credits (OA_TC6_CONFIG0_TXCTHRESH_8)
+- 3: sixteen or more credits
+
+A value above 3 does not fit the field and fails with -EINVAL.
+
.. c:function:: int oa_tc6_ptp_register(struct oa_tc6 *tc6, \
struct ptp_clock_info *info);
diff --git a/MAINTAINERS b/MAINTAINERS
index 415d02d59681..e5ac39026ef2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21018,6 +21018,7 @@ M: Selvamani 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/
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/oa_tc6/oa_tc6_main.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
index 5d01ef5a13ad..7a25b6a96311 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
@@ -263,9 +263,29 @@ static int oa_tc6_update_standard_capability(struct oa_tc6 *tc6)
return ret;
if (regval & OA_TC6_STDCAP_FTSC_CAPABILITY) /* Timestamp */
tc6->ftsc_capable = true;
+ if (regval & OA_TC6_STDCAP_CTC_CAPABILITY) /* Cut-through */
+ tc6->ctc_capable = true;
return 0;
}
+static int oa_tc6_rx_cut_through_enable(struct oa_tc6 *tc6)
+{
+ int ret = 0;
+ u32 regval;
+
+ if (!tc6->ctc_capable)
+ return ret;
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, ®val);
+ if (ret)
+ return ret;
+
+ /* Set Receive Cut-Through Enable */
+ regval |= OA_TC6_CONFIG0_RXCTE;
+
+ return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, regval);
+}
+
static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
enum oa_tc6_register_op reg_op)
{
@@ -1596,6 +1616,36 @@ int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6)
}
EXPORT_SYMBOL_GPL(oa_tc6_zero_align_receive_frame_enable);
+/**
+ * oa_tc6_tx_credit_threshold_set - function to set the minimum number of free
+ * transmit credits needed before the MAC-PHY asserts the interrupt.
+ * @tc6: oa_tc6 struct.
+ * @thresh: transmit credit threshold field value, e.g.
+ * OA_TC6_CONFIG0_TXCTHRESH_8.
+ *
+ * Return: 0 on success, -EINVAL if @thresh does not fit the TXCTHRESH
+ * field, otherwise a negative error code from the register access.
+ */
+int oa_tc6_tx_credit_threshold_set(struct oa_tc6 *tc6, u8 thresh)
+{
+ u32 regval;
+ int ret;
+
+ if (thresh > OA_TC6_CONFIG0_TXCTHRESH_16)
+ return -EINVAL;
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, ®val);
+ if (ret)
+ return ret;
+
+ /* Set Transmit Credit Threshold */
+ regval &= ~OA_TC6_CONFIG0_TXCTHRESH_MASK;
+ regval |= FIELD_PREP(OA_TC6_CONFIG0_TXCTHRESH_MASK, thresh);
+
+ return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, regval);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_tx_credit_threshold_set);
+
/**
* oa_tc6_start_xmit - function for sending the tx skb which consists ethernet
* frame.
@@ -1748,6 +1798,18 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
return ERR_PTR(ret);
}
+ /* Cut-through bits must be configured before SYNC bit is set
+ * in CONFIG0 register.
+ */
+ if (tc6->quirk_flags & OA_TC6_CONFIG_RX_CUT_THROUGH) {
+ ret = oa_tc6_rx_cut_through_enable(tc6);
+ if (ret) {
+ dev_err(&tc6->spi->dev,
+ "Failed to enable RX cut-through: %d\n", ret);
+ return ERR_PTR(ret);
+ }
+ }
+
ret = oa_tc6_unmask_macphy_error_interrupts(tc6);
if (ret) {
dev_err(&tc6->spi->dev,
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 98d815f8f0b4..80aeac514c89 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -134,6 +134,7 @@ struct oa_tc6 {
struct list_head tx_ts_skb_q;
struct ptp_clock *ptp_clock;
bool ftsc_capable; /* Timestamp capability */
+ bool ctc_capable; /* cut-through capability */
bool hw_tstamp_enabled;
u64 tx_hwtstamp_pkts;
u64 tx_hwtstamp_lost;
diff --git a/drivers/net/ethernet/onsemi/Kconfig b/drivers/net/ethernet/onsemi/Kconfig
new file mode 100644
index 000000000000..afef0c9ebc72
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/Kconfig
@@ -0,0 +1,21 @@
+# 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..1091c55dcbe6
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/Makefile
@@ -0,0 +1,6 @@
+# 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..4bd28dc560e6
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/Kconfig
@@ -0,0 +1,20 @@
+# 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 conforms 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..807eabaa7a3c
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/Makefile
@@ -0,0 +1,6 @@
+# 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..2fc09107aed3
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
@@ -0,0 +1,379 @@
+// 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, S2500_VS_MMS12 },
+ { S2500_REG_VS_MIIMIRQS, S2500_VS_MMS12 },
+ { S2500_REG_VS_DIOCFG0, S2500_VS_MMS12 },
+ { S2500_REG_VS_CHIPID, S2500_VS_MMS12 },
+ { S2500_REG_VS_TWEAKS2, S2500_VS_MMS12 },
+ { S2500_REG_VS_MACID0, S2500_VS_MMS12 },
+ { S2500_REG_VS_MACID1, S2500_VS_MMS12 },
+ { S2500_REG_VS_NVM_HEALTH, S2500_VS_MMS12 },
+ { S2500_REG_VS_PTP_SEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_PTPNSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_PTP_SETSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_SETNSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_PTP_ADJ, S2500_VS_MMS12 },
+ { S2500_REG_VS_CMPCTL, S2500_VS_MMS12 },
+ { S2500_REG_VS_CMPSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_CMPNSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_CMPPER, S2500_VS_MMS12 },
+ { S2500_REG_VS_CAPCTL, S2500_VS_MMS12 },
+ { S2500_REG_VS_CAPSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_CAPNSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_BCNCNT, S2500_VS_MMS12 },
+};
+
+enum s2500_stat_idx {
+ S2500_MSTOCTETSTX_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_MSTTXUNDERFLOW_IDX,
+ S2500_MSTSINGLECOL_IDX,
+ S2500_MSTMULTICOL_IDX,
+ S2500_MSTEXCESSCOL_IDX,
+ S2500_MSTDEFERREDTX_IDX,
+ S2500_MSTCRSERR_IDX,
+ S2500_MSTOCTETSRX_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_STOCTETSTXL;
+ ret = oa_tc6_read_registers(priv->tc6, addr, regs,
+ S2500_NUM_STAT_REGS);
+ if (ret)
+ goto out;
+
+ priv->stats_read_at = jiffies;
+ rptr = regs;
+
+ /* Counters are cleared by the hardware when read, so each read
+ * returns only the delta since the previous read. Hence the
+ * values are accumulated.
+ *
+ * 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(OCTETSRXL); ++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_MSTOCTETSTX_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_MSTTXUNDERFLOW_IDX];
+ mac_stats->OctetsReceivedOK = st[S2500_MSTOCTETSRX_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..cb9c53b936b8
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h
@@ -0,0 +1,151 @@
+/* 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/net_tstamp.h>
+#include <linux/ptp_clock_kernel.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
+
+/* Vendor specific memory map selector (MMS 10..15 are vendor defined) */
+#define S2500_VS_MMS12 12
+
+#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_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_STOCTETSTXL 48
+#define S2500_REG_MAC_STOCTETSRXL 65
+#define S2500_REG_MAC_STRXDROPPED 82
+
+/* First/last statistic register for sequential access */
+#define S2500_REG_MAC_FIRST_STAT S2500_REG_MAC_STOCTETSTXL
+#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
+
+/* MIIM IRQ enable register */
+#define S2500_REG_VS_MIIMIRQE 0x10
+
+/* MIIM IRQ status register */
+#define S2500_REG_VS_MIIMIRQS 0x11
+
+/* Digital I/O config register */
+#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
+
+/* Frequency adjust magnitude, 1 LSB = 0.000125 ppm = 1/8 ppb */
+#define S2500_PTP_ADJ_MAX GENMASK(27, 0)
+#define S2500_PTP_ADJ_BASE 8000000000ULL
+#define S2500_PTP_MAX_ADJ_PPB (S2500_PTP_ADJ_MAX / 8)
+
+/* PTP registers */
+#define S2500_REG_VS_PTP_SEC 0x1010
+#define S2500_REG_VS_PTPNSEC 0x1011
+#define S2500_REG_VS_PTP_SETSEC 0x1012
+#define S2500_REG_VS_SETNSEC 0x1013
+#define S2500_REG_VS_PTP_ADJ 0x1014
+#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..f176475cfd7e
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
@@ -0,0 +1,498 @@
+// 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/module.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 = S2500_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 given 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 */
+ ret = oa_tc6_tx_credit_threshold_set(priv->tc6,
+ OA_TC6_CONFIG0_TXCTHRESH_8);
+ 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_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;
+
+ addr = S2500_REG_MAC_ADDRFILTL(slot);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ mms, addr,
+ addrl);
+ if (ret)
+ return ret;
+
+ /* Enable the slot last. */
+ addr = S2500_REG_MAC_ADDRFILTH(slot);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ mms, addr,
+ addrh);
+ 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 oa_tc6_quirks tc6_quirks;
+ struct device *dev = &spi->dev;
+ struct net_device *ndev;
+ struct s2500_info *priv;
+ int ret;
+
+ if (!spi->irq)
+ return dev_err_probe(dev, -ENODEV, "Missing interrupt\n");
+
+ 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;
+
+ tc6_quirks.quirk_flags = OA_TC6_CONFIG_RX_CUT_THROUGH;
+ priv->tc6 = oa_tc6_init(spi, ndev, &tc6_quirks);
+ if (IS_ERR(priv->tc6)) {
+ ret = PTR_ERR(priv->tc6);
+ dev_err(&spi->dev, "OA TC6 init failed\n");
+ return ret;
+ }
+
+ 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("Selvamani 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..595067a97ae5
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
@@ -0,0 +1,156 @@
+// 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, S2500_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, S2500_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;
+ u64 adj;
+ u32 val;
+
+ if (diff_by_scaled_ppm(S2500_PTP_ADJ_BASE, scaled_ppm, &adj))
+ sign_bit = 1U << 31;
+
+ /* check overflow */
+ if (adj > S2500_PTP_ADJ_MAX)
+ return -ERANGE;
+
+ val = (u32)adj | sign_bit;
+ return oa_tc6_write_register_mms(priv->tc6, S2500_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;
+ ktime_t start;
+ s64 elapsed;
+ int ret;
+
+ start = ktime_get_raw();
+ ret = s2500_ptp_get_time64(&priv->ptp_clock_info, &now, NULL);
+ 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);
+ }
+
+ /* Adjust the time to take the SPI transaction time into
+ * account.
+ */
+ elapsed = ktime_to_ns(ktime_sub(ktime_get_raw(), start));
+ timespec64_add_ns(&target, elapsed);
+
+ /* 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;
+
+ 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;
+
+ return oa_tc6_ptp_register(priv->tc6, info);
+}
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 32c25b5e2aa3..e3180d8eae16 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -20,6 +20,7 @@
/* Standard Capabilities Register */
#define OA_TC6_REG_STDCAP 0x0002
#define OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
+#define OA_TC6_STDCAP_CTC_CAPABILITY BIT(7)
#define OA_TC6_STDCAP_FTSC_CAPABILITY BIT(6)
/* Reset Control and Status Register */
@@ -30,6 +31,10 @@
#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_TXCTHRESH_MASK GENMASK(11, 10)
+#define OA_TC6_CONFIG0_TXCTHRESH_8 2 /* 0b10: >=8 credits */
+#define OA_TC6_CONFIG0_TXCTHRESH_16 3 /* 0b11: >=16 credits */
+#define OA_TC6_CONFIG0_RXCTE BIT(8)
#define OA_TC6_CONFIG0_FTSE_ENABLE BIT(7)
#define OA_TC6_CONFIG0_FTSS_64BIT_TSTAMP BIT(6)
#define OA_TC6_CONFIG0_PROTE BIT(5)
@@ -82,6 +87,7 @@ struct oa_tc6;
enum oa_tc6_quirk_flag {
OA_TC6_BROKEN_PHY = BIT(0),
+ OA_TC6_CONFIG_RX_CUT_THROUGH = BIT(1),
};
struct oa_tc6_quirks {
@@ -103,6 +109,7 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
u8 length);
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb);
int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6);
+int oa_tc6_tx_credit_threshold_set(struct oa_tc6 *tc6, u8 thresh);
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,
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH net-next v11 10/14] net: ethernet: onsemi: s2500: Add driver support for S2500
@ 2026-11-13 16:59 ` Selvamani Rajagopal
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Support for onsemi's S2500, 802.3cg compliant Ethernet
transceiver with integrated MAC-PHY. Works with
OPEN Alliance TC6 framework.
Hardware timestamp support is present in the driver. adjtime
functionality is implemented as a read-modify-write of the time
registers as the device has no add-delta register.
Driver has ethtool support for printing some device
registers, MAC stats, rmon stats and timestamp related
traffic counters.
Driver supports a maximum of three multicast filters. If
the system has more than 3 multicast addresses to support,
all multicast traffic is allowed.
Raise the transmit credit threshold from the default of 1
credit to 8, using the new oa_tc6_tx_credit_threshold_set()
helper, so that the MAC-PHY asserts the interrupt only when at
least 8 transmit credits are free.
Add the OA_TC6_CONFIG_RX_CUT_THROUGH quirk flag to the OPEN
Alliance TC6 framework and use it to enable RX cut-through,
as it must be enabled before the SYNC bit is set.
Enable the MAC's hardware FCS generation, so the host does
not have to append the FCS to transmitted frames.
Statistics read from the device are cached for 100 ms, so
repeated ethtool queries within that period return the
cached counters.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- fixed a potential "Oops" due to jumping to wrong label on error
while enabling RX cut-through feature
- Function that enables RX cut-through is made private
- Moved the frame drop feature to separate patch as it is not
related to S2500 driver support.
- MAINTAINERS file changed to include files under onsemi too.
changes in v10
- Removed the code that overwrites CONFIG0 after oa_tc6_init
is done.
- Removed the code that clears status0 register that would
interfere interrupt handling
- setting tx credit threshold to 8 credits
- New API documentation moved to this patch
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 unnecessary 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 no 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
---
Documentation/networking/oa-tc6-framework.rst | 27 +-
MAINTAINERS | 1 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/oa_tc6/oa_tc6_main.c | 62 +++
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 1 +
drivers/net/ethernet/onsemi/Kconfig | 21 +
drivers/net/ethernet/onsemi/Makefile | 6 +
drivers/net/ethernet/onsemi/s2500/Kconfig | 20 +
drivers/net/ethernet/onsemi/s2500/Makefile | 6 +
drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c | 379 ++++++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h | 151 +++++++
drivers/net/ethernet/onsemi/s2500/s2500_main.c | 498 ++++++++++++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_ptp.c | 156 +++++++
include/linux/oa_tc6.h | 7 +
15 files changed, 1336 insertions(+), 1 deletion(-)
diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst
index 111583609933..29bf6d4d80eb 100644
--- a/Documentation/networking/oa-tc6-framework.rst
+++ b/Documentation/networking/oa-tc6-framework.rst
@@ -462,7 +462,15 @@ The include/linux/oa_tc6.h defines the following functions:
struct net_device *netdev, \
struct oa_tc6_quirks *quirks)
-Initialize OA TC6 lib.
+Initialize OA TC6 lib. The device specific modifiers of the protocol are
+passed in the quirk_flags field of struct oa_tc6_quirks, which is optional
+and may be NULL:
+
+- OA_TC6_BROKEN_PHY: Leave MDIO bus registration and PHY connection to the
+ vendor driver instead of the framework.
+- OA_TC6_CONFIG_RX_CUT_THROUGH: Enable receive cut-through in the CONFIG0
+ register. It is ignored if the MAC-PHY does not report receive cut-through
+ capability.
.. c:function:: void oa_tc6_exit(struct oa_tc6 *tc6)
@@ -502,6 +510,23 @@ 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_tx_credit_threshold_set(struct oa_tc6 *tc6, \
+ u8 thresh);
+
+Sets the minimum number of free transmit credits that must be available
+before the MAC-PHY asserts the interrupt. The default is one credit
+according to OPEN Alliance 10BASE-T1x MAC-PHY Serial Interface specification.
+
+The thresh argument is the encoded TXCTHRESH field value of the CONFIG0
+register, not the credit count:
+
+- 0: one or more credits (default)
+- 1: four or more credits
+- 2: eight or more credits (OA_TC6_CONFIG0_TXCTHRESH_8)
+- 3: sixteen or more credits
+
+A value above 3 does not fit the field and fails with -EINVAL.
+
.. c:function:: int oa_tc6_ptp_register(struct oa_tc6 *tc6, \
struct ptp_clock_info *info);
diff --git a/MAINTAINERS b/MAINTAINERS
index 415d02d59681..e5ac39026ef2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21018,6 +21018,7 @@ M: Selvamani 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/
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/oa_tc6/oa_tc6_main.c b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
index 5d01ef5a13ad..7a25b6a96311 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_main.c
@@ -263,9 +263,29 @@ static int oa_tc6_update_standard_capability(struct oa_tc6 *tc6)
return ret;
if (regval & OA_TC6_STDCAP_FTSC_CAPABILITY) /* Timestamp */
tc6->ftsc_capable = true;
+ if (regval & OA_TC6_STDCAP_CTC_CAPABILITY) /* Cut-through */
+ tc6->ctc_capable = true;
return 0;
}
+static int oa_tc6_rx_cut_through_enable(struct oa_tc6 *tc6)
+{
+ int ret = 0;
+ u32 regval;
+
+ if (!tc6->ctc_capable)
+ return ret;
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, ®val);
+ if (ret)
+ return ret;
+
+ /* Set Receive Cut-Through Enable */
+ regval |= OA_TC6_CONFIG0_RXCTE;
+
+ return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, regval);
+}
+
static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
enum oa_tc6_register_op reg_op)
{
@@ -1596,6 +1616,36 @@ int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6)
}
EXPORT_SYMBOL_GPL(oa_tc6_zero_align_receive_frame_enable);
+/**
+ * oa_tc6_tx_credit_threshold_set - function to set the minimum number of free
+ * transmit credits needed before the MAC-PHY asserts the interrupt.
+ * @tc6: oa_tc6 struct.
+ * @thresh: transmit credit threshold field value, e.g.
+ * OA_TC6_CONFIG0_TXCTHRESH_8.
+ *
+ * Return: 0 on success, -EINVAL if @thresh does not fit the TXCTHRESH
+ * field, otherwise a negative error code from the register access.
+ */
+int oa_tc6_tx_credit_threshold_set(struct oa_tc6 *tc6, u8 thresh)
+{
+ u32 regval;
+ int ret;
+
+ if (thresh > OA_TC6_CONFIG0_TXCTHRESH_16)
+ return -EINVAL;
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, ®val);
+ if (ret)
+ return ret;
+
+ /* Set Transmit Credit Threshold */
+ regval &= ~OA_TC6_CONFIG0_TXCTHRESH_MASK;
+ regval |= FIELD_PREP(OA_TC6_CONFIG0_TXCTHRESH_MASK, thresh);
+
+ return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, regval);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_tx_credit_threshold_set);
+
/**
* oa_tc6_start_xmit - function for sending the tx skb which consists ethernet
* frame.
@@ -1748,6 +1798,18 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev,
return ERR_PTR(ret);
}
+ /* Cut-through bits must be configured before SYNC bit is set
+ * in CONFIG0 register.
+ */
+ if (tc6->quirk_flags & OA_TC6_CONFIG_RX_CUT_THROUGH) {
+ ret = oa_tc6_rx_cut_through_enable(tc6);
+ if (ret) {
+ dev_err(&tc6->spi->dev,
+ "Failed to enable RX cut-through: %d\n", ret);
+ return ERR_PTR(ret);
+ }
+ }
+
ret = oa_tc6_unmask_macphy_error_interrupts(tc6);
if (ret) {
dev_err(&tc6->spi->dev,
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 98d815f8f0b4..80aeac514c89 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -134,6 +134,7 @@ struct oa_tc6 {
struct list_head tx_ts_skb_q;
struct ptp_clock *ptp_clock;
bool ftsc_capable; /* Timestamp capability */
+ bool ctc_capable; /* cut-through capability */
bool hw_tstamp_enabled;
u64 tx_hwtstamp_pkts;
u64 tx_hwtstamp_lost;
diff --git a/drivers/net/ethernet/onsemi/Kconfig b/drivers/net/ethernet/onsemi/Kconfig
new file mode 100644
index 000000000000..afef0c9ebc72
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/Kconfig
@@ -0,0 +1,21 @@
+# 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..1091c55dcbe6
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/Makefile
@@ -0,0 +1,6 @@
+# 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..4bd28dc560e6
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/Kconfig
@@ -0,0 +1,20 @@
+# 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 conforms 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..807eabaa7a3c
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/Makefile
@@ -0,0 +1,6 @@
+# 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..2fc09107aed3
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
@@ -0,0 +1,379 @@
+// 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, S2500_VS_MMS12 },
+ { S2500_REG_VS_MIIMIRQS, S2500_VS_MMS12 },
+ { S2500_REG_VS_DIOCFG0, S2500_VS_MMS12 },
+ { S2500_REG_VS_CHIPID, S2500_VS_MMS12 },
+ { S2500_REG_VS_TWEAKS2, S2500_VS_MMS12 },
+ { S2500_REG_VS_MACID0, S2500_VS_MMS12 },
+ { S2500_REG_VS_MACID1, S2500_VS_MMS12 },
+ { S2500_REG_VS_NVM_HEALTH, S2500_VS_MMS12 },
+ { S2500_REG_VS_PTP_SEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_PTPNSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_PTP_SETSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_SETNSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_PTP_ADJ, S2500_VS_MMS12 },
+ { S2500_REG_VS_CMPCTL, S2500_VS_MMS12 },
+ { S2500_REG_VS_CMPSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_CMPNSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_CMPPER, S2500_VS_MMS12 },
+ { S2500_REG_VS_CAPCTL, S2500_VS_MMS12 },
+ { S2500_REG_VS_CAPSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_CAPNSEC, S2500_VS_MMS12 },
+ { S2500_REG_VS_BCNCNT, S2500_VS_MMS12 },
+};
+
+enum s2500_stat_idx {
+ S2500_MSTOCTETSTX_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_MSTTXUNDERFLOW_IDX,
+ S2500_MSTSINGLECOL_IDX,
+ S2500_MSTMULTICOL_IDX,
+ S2500_MSTEXCESSCOL_IDX,
+ S2500_MSTDEFERREDTX_IDX,
+ S2500_MSTCRSERR_IDX,
+ S2500_MSTOCTETSRX_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_STOCTETSTXL;
+ ret = oa_tc6_read_registers(priv->tc6, addr, regs,
+ S2500_NUM_STAT_REGS);
+ if (ret)
+ goto out;
+
+ priv->stats_read_at = jiffies;
+ rptr = regs;
+
+ /* Counters are cleared by the hardware when read, so each read
+ * returns only the delta since the previous read. Hence the
+ * values are accumulated.
+ *
+ * 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(OCTETSRXL); ++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_MSTOCTETSTX_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_MSTTXUNDERFLOW_IDX];
+ mac_stats->OctetsReceivedOK = st[S2500_MSTOCTETSRX_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..cb9c53b936b8
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h
@@ -0,0 +1,151 @@
+/* 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/net_tstamp.h>
+#include <linux/ptp_clock_kernel.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
+
+/* Vendor specific memory map selector (MMS 10..15 are vendor defined) */
+#define S2500_VS_MMS12 12
+
+#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_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_STOCTETSTXL 48
+#define S2500_REG_MAC_STOCTETSRXL 65
+#define S2500_REG_MAC_STRXDROPPED 82
+
+/* First/last statistic register for sequential access */
+#define S2500_REG_MAC_FIRST_STAT S2500_REG_MAC_STOCTETSTXL
+#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
+
+/* MIIM IRQ enable register */
+#define S2500_REG_VS_MIIMIRQE 0x10
+
+/* MIIM IRQ status register */
+#define S2500_REG_VS_MIIMIRQS 0x11
+
+/* Digital I/O config register */
+#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
+
+/* Frequency adjust magnitude, 1 LSB = 0.000125 ppm = 1/8 ppb */
+#define S2500_PTP_ADJ_MAX GENMASK(27, 0)
+#define S2500_PTP_ADJ_BASE 8000000000ULL
+#define S2500_PTP_MAX_ADJ_PPB (S2500_PTP_ADJ_MAX / 8)
+
+/* PTP registers */
+#define S2500_REG_VS_PTP_SEC 0x1010
+#define S2500_REG_VS_PTPNSEC 0x1011
+#define S2500_REG_VS_PTP_SETSEC 0x1012
+#define S2500_REG_VS_SETNSEC 0x1013
+#define S2500_REG_VS_PTP_ADJ 0x1014
+#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..f176475cfd7e
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
@@ -0,0 +1,498 @@
+// 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/module.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 = S2500_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 given 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 */
+ ret = oa_tc6_tx_credit_threshold_set(priv->tc6,
+ OA_TC6_CONFIG0_TXCTHRESH_8);
+ 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_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;
+
+ addr = S2500_REG_MAC_ADDRFILTL(slot);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ mms, addr,
+ addrl);
+ if (ret)
+ return ret;
+
+ /* Enable the slot last. */
+ addr = S2500_REG_MAC_ADDRFILTH(slot);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ mms, addr,
+ addrh);
+ 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 oa_tc6_quirks tc6_quirks;
+ struct device *dev = &spi->dev;
+ struct net_device *ndev;
+ struct s2500_info *priv;
+ int ret;
+
+ if (!spi->irq)
+ return dev_err_probe(dev, -ENODEV, "Missing interrupt\n");
+
+ 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;
+
+ tc6_quirks.quirk_flags = OA_TC6_CONFIG_RX_CUT_THROUGH;
+ priv->tc6 = oa_tc6_init(spi, ndev, &tc6_quirks);
+ if (IS_ERR(priv->tc6)) {
+ ret = PTR_ERR(priv->tc6);
+ dev_err(&spi->dev, "OA TC6 init failed\n");
+ return ret;
+ }
+
+ 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("Selvamani 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..595067a97ae5
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
@@ -0,0 +1,156 @@
+// 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, S2500_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, S2500_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;
+ u64 adj;
+ u32 val;
+
+ if (diff_by_scaled_ppm(S2500_PTP_ADJ_BASE, scaled_ppm, &adj))
+ sign_bit = 1U << 31;
+
+ /* check overflow */
+ if (adj > S2500_PTP_ADJ_MAX)
+ return -ERANGE;
+
+ val = (u32)adj | sign_bit;
+ return oa_tc6_write_register_mms(priv->tc6, S2500_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;
+ ktime_t start;
+ s64 elapsed;
+ int ret;
+
+ start = ktime_get_raw();
+ ret = s2500_ptp_get_time64(&priv->ptp_clock_info, &now, NULL);
+ 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);
+ }
+
+ /* Adjust the time to take the SPI transaction time into
+ * account.
+ */
+ elapsed = ktime_to_ns(ktime_sub(ktime_get_raw(), start));
+ timespec64_add_ns(&target, elapsed);
+
+ /* 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;
+
+ 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;
+
+ return oa_tc6_ptp_register(priv->tc6, info);
+}
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 32c25b5e2aa3..e3180d8eae16 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -20,6 +20,7 @@
/* Standard Capabilities Register */
#define OA_TC6_REG_STDCAP 0x0002
#define OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
+#define OA_TC6_STDCAP_CTC_CAPABILITY BIT(7)
#define OA_TC6_STDCAP_FTSC_CAPABILITY BIT(6)
/* Reset Control and Status Register */
@@ -30,6 +31,10 @@
#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_TXCTHRESH_MASK GENMASK(11, 10)
+#define OA_TC6_CONFIG0_TXCTHRESH_8 2 /* 0b10: >=8 credits */
+#define OA_TC6_CONFIG0_TXCTHRESH_16 3 /* 0b11: >=16 credits */
+#define OA_TC6_CONFIG0_RXCTE BIT(8)
#define OA_TC6_CONFIG0_FTSE_ENABLE BIT(7)
#define OA_TC6_CONFIG0_FTSS_64BIT_TSTAMP BIT(6)
#define OA_TC6_CONFIG0_PROTE BIT(5)
@@ -82,6 +87,7 @@ struct oa_tc6;
enum oa_tc6_quirk_flag {
OA_TC6_BROKEN_PHY = BIT(0),
+ OA_TC6_CONFIG_RX_CUT_THROUGH = BIT(1),
};
struct oa_tc6_quirks {
@@ -103,6 +109,7 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
u8 length);
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb);
int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6);
+int oa_tc6_tx_credit_threshold_set(struct oa_tc6 *tc6, u8 thresh);
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,
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH net-next v11 10/14] net: ethernet: onsemi: s2500: Add driver support for S2500
2026-11-13 16:59 ` Selvamani Rajagopal
(?)
@ 2026-11-14 16:59 ` sashiko-bot
-1 siblings, 0 replies; 32+ messages in thread
From: sashiko-bot @ 2026-11-14 16:59 UTC (permalink / raw)
To: Selvamani Rajagopal; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] net: s2500: ethtool -S rx_mismatch_drops duplicates
rtnl_link_stats64
- [Medium] net: s2500: S2500_PTP_MAX_ADJ_PPB overflows 32-bit long
scaled_ppm
- [Medium] net: s2500: s2500_set_multicast_mode() enables MCSF filter
under IFF_PROMISC
--
commit 98b1718831c733b46bf9d80e2766e580982ab454
Author: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
net: ethernet: onsemi: s2500: Add driver support for S2500
This commit adds driver support for the onsemi S2500 10BASE-T1S MAC-PHY,
including ethtool statistics, multicast filtering, and PTP hardware clock
operations, along with RX cut-through and TX credit threshold helpers in
the OA TC6 framework.
> 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..2fc09107aed3e
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
[ ... ]
> +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 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]];
> +}
[Severity: Medium]
Should S2500_MSTRXDROPPED_IDX be reported via rx_otherhost_dropped in
struct rtnl_link_stats64 instead of a custom "rx_mismatch_drops" counter in
ethtool -S?
Frames dropped due to destination MAC address filter mismatch match the
standard rx_otherhost_dropped counter (see
Documentation/networking/statistics.rst), and exposing them only as a
driver-private ethtool -S string hides them from standard netlink tools
like ip -s link.
> 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 0000000000000..cb9c53b936b83
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h
[ ... ]
> +/* Frequency adjust magnitude, 1 LSB = 0.000125 ppm = 1/8 ppb */
> +#define S2500_PTP_ADJ_MAX GENMASK(27, 0)
> +#define S2500_PTP_ADJ_BASE 8000000000ULL
> +#define S2500_PTP_MAX_ADJ_PPB (S2500_PTP_ADJ_MAX / 8)
[Severity: Medium]
Can S2500_PTP_MAX_ADJ_PPB (33,554,431 ppb) overflow the 32-bit signed long
scaled_ppm parameter on 32-bit architectures?
ptp_clock_adjtime() passes tx->freq as a signed long to
scaled_ppm_to_ppb() and info->adjfine(). On 32-bit kernels, LONG_MAX
holds at most 32,767,999 ppb, so requesting an adjustment between
+32,768,000 and +33,554,431 ppb wraps scaled_ppm to a negative value that
passes the max_adj check and programs S2500_REG_VS_PTP_ADJ in the wrong
direction. When scaled_ppm is LONG_MIN (-32,768,000 ppb), negating it in
diff_by_scaled_ppm() also overflows and fails with -ERANGE.
> 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..f176475cfd7ed
> --- /dev/null
> +++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
[ ... ]
> +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);
[ ... ]
> + } else if (mc_count == 0) {
> + /* Enable multicast filter */
> + ret = s2500_mac_ctrl_modify_bits(priv,
> + S2500_MAC_CTRL_MCSF,
> + false);
[ ... ]
> +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);
> +}
[Severity: Medium]
Does s2500_set_multicast_mode() leave S2500_MAC_CTRL_MCSF enabled when
IFF_PROMISC is set?
s2500_set_rx_mode() calls s2500_set_multicast_mode() after
s2500_set_promiscuous_mode(), and s2500_set_multicast_mode() only checks
IFF_ALLMULTI. When IFF_PROMISC is set without IFF_ALLMULTI and mc_count
is 0, the else-if branch sets S2500_MAC_CTRL_MCSF and disables the address
filter slots, which can filter out multicast traffic while in promiscuous
mode.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-s2500-mac-phy-support-v11-0-7a186141952c@onsemi.com?part=10
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH net-next v11 11/14] net: ethernet: onsemi: s2500: Add selftest support
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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>
Add selftest support for onsemi S2500 MAC-PHY. Make it a separate
patch for the ease of review.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- No change
changes in v10
- Added a check for CONFIG_NET_SELFTESTS before returning the
count value
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 | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
index 2fc09107aed3..feacb406778d 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"
@@ -205,6 +206,10 @@ 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:
+ if (!IS_ENABLED(CONFIG_NET_SELFTESTS))
+ return -EOPNOTSUPP;
+ return net_selftest_get_count();
default:
return -EOPNOTSUPP;
}
@@ -218,6 +223,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;
}
}
@@ -376,4 +384,5 @@ 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 related [flat|nested] 32+ messages in thread* [PATCH net-next v11 11/14] net: ethernet: onsemi: s2500: Add selftest support
@ 2026-11-13 16:59 ` Selvamani Rajagopal
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Add selftest support for onsemi S2500 MAC-PHY. Make it a separate
patch for the ease of review.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- No change
changes in v10
- Added a check for CONFIG_NET_SELFTESTS before returning the
count value
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 | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
index 2fc09107aed3..feacb406778d 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"
@@ -205,6 +206,10 @@ 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:
+ if (!IS_ENABLED(CONFIG_NET_SELFTESTS))
+ return -EOPNOTSUPP;
+ return net_selftest_get_count();
default:
return -EOPNOTSUPP;
}
@@ -218,6 +223,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;
}
}
@@ -376,4 +384,5 @@ 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 related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 12/14] net: phy: Add helper to modify only the PHY loopback bit
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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>
Add a generic helper function, genphy_loopback_no_speed(), to modify
the loopback bit of the PHY only, without setting the speed. This will
help the PHY drivers, like 10BASE-T1S or any other PHYs that want to
handle the loopback bit only.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Made the changes to phy core into a separate patch. Previously
changes were in a patch that also had vendor code changes.
- Documentation in the comments section is made accurate.
changes in v10
- No change
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
- Added loopback functionality for onsemi's PHY driver
- First patch
---
drivers/net/phy/phy_device.c | 24 ++++++++++++++++++++++++
include/linux/phy.h | 2 ++
2 files changed, 26 insertions(+)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index b39e945cebd7..80d93bb43312 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -2923,6 +2923,30 @@ int genphy_resume(struct phy_device *phydev)
}
EXPORT_SYMBOL(genphy_resume);
+/**
+ * genphy_loopback_no_speed - Enable or disable PHY loopback, speed unchanged
+ * @phydev: The phy_device struct
+ * @enable: Flag to enable or disable the PHY level loopback.
+ * @speed: Speed setting. Not supported: -EOPNOTSUPP is returned if it is
+ * non-zero when enabling loopback. Ignored when disabling.
+ *
+ * Description: Only BMCR_LOOPBACK is modified. It fails with -EOPNOTSUPP if
+ * @speed is non-zero when enabling loopback, or with the error from the
+ * BMCR access.
+ *
+ * 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 21afc2082b17..6e57f83757bb 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2317,6 +2317,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 related [flat|nested] 32+ messages in thread* [PATCH net-next v11 12/14] net: phy: Add helper to modify only the PHY loopback bit
@ 2026-11-13 16:59 ` Selvamani Rajagopal
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Add a generic helper function, genphy_loopback_no_speed(), to modify
the loopback bit of the PHY only, without setting the speed. This will
help the PHY drivers, like 10BASE-T1S or any other PHYs that want to
handle the loopback bit only.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Made the changes to phy core into a separate patch. Previously
changes were in a patch that also had vendor code changes.
- Documentation in the comments section is made accurate.
changes in v10
- No change
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
- Added loopback functionality for onsemi's PHY driver
- First patch
---
drivers/net/phy/phy_device.c | 24 ++++++++++++++++++++++++
include/linux/phy.h | 2 ++
2 files changed, 26 insertions(+)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index b39e945cebd7..80d93bb43312 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -2923,6 +2923,30 @@ int genphy_resume(struct phy_device *phydev)
}
EXPORT_SYMBOL(genphy_resume);
+/**
+ * genphy_loopback_no_speed - Enable or disable PHY loopback, speed unchanged
+ * @phydev: The phy_device struct
+ * @enable: Flag to enable or disable the PHY level loopback.
+ * @speed: Speed setting. Not supported: -EOPNOTSUPP is returned if it is
+ * non-zero when enabling loopback. Ignored when disabling.
+ *
+ * Description: Only BMCR_LOOPBACK is modified. It fails with -EOPNOTSUPP if
+ * @speed is non-zero when enabling loopback, or with the error from the
+ * BMCR access.
+ *
+ * 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 21afc2082b17..6e57f83757bb 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2317,6 +2317,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 related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 13/14] net: phy: ncn26000: Support for onsemi's S2500 internal phy
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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>
Add 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.
The S2500 entry additionally supports SQI reporting, OATC14 cable
testing and loopback, and enables direct Clause 45 MMD access. The
S2500 entry works in polled mode only.
S2500 uses its own config_aneg that sets BMCR_ANENABLE without
affecting other bits in BMCR register. Also this keeps NCN26000
behavior unchanged.
Also add S2500 support to the Kconfig help text, add a MAINTAINERS
entry and a MODULE_AUTHOR line.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Copyright year changed from 2022 to 2022-2026
Previous patch had 2026 only.
- Added more information in commit message
- s2500 has its own config_aneg so that it won't cause any
change in behavior to ncn26000.
changes in v10
- removed config_intr callback as PHY driver is in polled mode
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 in a 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/Kconfig | 2 +-
drivers/net/phy/ncn26000.c | 44 +++++++++++++++++++++++++++++++++++++++++---
3 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index e5ac39026ef2..78f5de606df0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21008,6 +21008,7 @@ F: arch/mips/boot/dts/ralink/omega2p.dts
ONSEMI ETHERNET PHY DRIVERS
M: Piergiorgio Beruto <piergiorgio.beruto@gmail.com>
+M: Selvamani Rajagopal <selvamani.rajagopal@onsemi.com>
L: netdev@vger.kernel.org
S: Supported
W: http://www.onsemi.com
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 30e9d22c8d1a..cff58dbe391d 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -396,7 +396,7 @@ config NCN26000_PHY
help
Adds support for the onsemi 10BASE-T1S Ethernet PHY.
Currently supports the NCN26000 10BASE-T1S Industrial PHY
- with MII interface.
+ with MII interface and the S2500 10BASE-T1S PHY.
source "drivers/net/phy/qcom/Kconfig"
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index 888d74b83125..0e2c978b73d4 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 2022-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
@@ -40,13 +41,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)
@@ -62,6 +68,17 @@ static int ncn26000_config_aneg(struct phy_device *phydev)
return phy_write(phydev, MII_BMCR, NCN26000_BCMR_LINK_CTRL_BIT);
}
+/* Kept separate to leave ncn26000_config_aneg() unchanged. BMCR is
+ * modified instead of overwritten, to keep loopback enabled.
+ */
+static int s2500_config_aneg(struct phy_device *phydev)
+{
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+ phydev->mdix = ETH_TP_MDI;
+
+ return phy_set_bits(phydev, MII_BMCR, NCN26000_BCMR_LINK_CTRL_BIT);
+}
+
static int ncn26000_read_status(struct phy_device *phydev)
{
/* The NCN26000 reports NCN26000_LINK_STATUS_BIT if the link status of
@@ -162,6 +179,25 @@ 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_aneg = s2500_config_aneg,
+ .read_status = ncn26000_read_status,
+ .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,
+ .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",
@@ -182,11 +218,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("Selvamani 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 related [flat|nested] 32+ messages in thread* [PATCH net-next v11 13/14] net: phy: ncn26000: Support for onsemi's S2500 internal phy
@ 2026-11-13 16:59 ` Selvamani Rajagopal
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Add 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.
The S2500 entry additionally supports SQI reporting, OATC14 cable
testing and loopback, and enables direct Clause 45 MMD access. The
S2500 entry works in polled mode only.
S2500 uses its own config_aneg that sets BMCR_ANENABLE without
affecting other bits in BMCR register. Also this keeps NCN26000
behavior unchanged.
Also add S2500 support to the Kconfig help text, add a MAINTAINERS
entry and a MODULE_AUTHOR line.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Copyright year changed from 2022 to 2022-2026
Previous patch had 2026 only.
- Added more information in commit message
- s2500 has its own config_aneg so that it won't cause any
change in behavior to ncn26000.
changes in v10
- removed config_intr callback as PHY driver is in polled mode
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 in a 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/Kconfig | 2 +-
drivers/net/phy/ncn26000.c | 44 +++++++++++++++++++++++++++++++++++++++++---
3 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index e5ac39026ef2..78f5de606df0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21008,6 +21008,7 @@ F: arch/mips/boot/dts/ralink/omega2p.dts
ONSEMI ETHERNET PHY DRIVERS
M: Piergiorgio Beruto <piergiorgio.beruto@gmail.com>
+M: Selvamani Rajagopal <selvamani.rajagopal@onsemi.com>
L: netdev@vger.kernel.org
S: Supported
W: http://www.onsemi.com
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 30e9d22c8d1a..cff58dbe391d 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -396,7 +396,7 @@ config NCN26000_PHY
help
Adds support for the onsemi 10BASE-T1S Ethernet PHY.
Currently supports the NCN26000 10BASE-T1S Industrial PHY
- with MII interface.
+ with MII interface and the S2500 10BASE-T1S PHY.
source "drivers/net/phy/qcom/Kconfig"
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index 888d74b83125..0e2c978b73d4 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 2022-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
@@ -40,13 +41,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)
@@ -62,6 +68,17 @@ static int ncn26000_config_aneg(struct phy_device *phydev)
return phy_write(phydev, MII_BMCR, NCN26000_BCMR_LINK_CTRL_BIT);
}
+/* Kept separate to leave ncn26000_config_aneg() unchanged. BMCR is
+ * modified instead of overwritten, to keep loopback enabled.
+ */
+static int s2500_config_aneg(struct phy_device *phydev)
+{
+ phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+ phydev->mdix = ETH_TP_MDI;
+
+ return phy_set_bits(phydev, MII_BMCR, NCN26000_BCMR_LINK_CTRL_BIT);
+}
+
static int ncn26000_read_status(struct phy_device *phydev)
{
/* The NCN26000 reports NCN26000_LINK_STATUS_BIT if the link status of
@@ -162,6 +179,25 @@ 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_aneg = s2500_config_aneg,
+ .read_status = ncn26000_read_status,
+ .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,
+ .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",
@@ -182,11 +218,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("Selvamani 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 related [flat|nested] 32+ messages in thread
* [PATCH net-next v11 14/14] net: phy: dp83867: Use genphy_loopback_no_speed()
2026-11-13 16:59 ` Selvamani Rajagopal
@ 2026-11-13 16:59 ` Selvamani Rajagopal
-1 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal via B4 Relay @ 2026-11-13 16:59 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>
Replace dp83867_loopback() with genphy_loopback_no_speed(), which
does the same. No functional change.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Moved the vendor changes into its own patch.
- New patch in v11.
---
drivers/net/phy/dp83867.c | 11 +----------
1 file changed, 1 insertion(+), 10 deletions(-)
diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c
index b4b4931c6f8c..dc00484c6c7a 100644
--- a/drivers/net/phy/dp83867.c
+++ b/drivers/net/phy/dp83867.c
@@ -1057,15 +1057,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)
@@ -1291,7 +1282,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,
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH net-next v11 14/14] net: phy: dp83867: Use genphy_loopback_no_speed()
@ 2026-11-13 16:59 ` Selvamani Rajagopal
0 siblings, 0 replies; 32+ messages in thread
From: Selvamani Rajagopal @ 2026-11-13 16:59 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
Replace dp83867_loopback() with genphy_loopback_no_speed(), which
does the same. No functional change.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v11
- Moved the vendor changes into its own patch.
- New patch in v11.
---
drivers/net/phy/dp83867.c | 11 +----------
1 file changed, 1 insertion(+), 10 deletions(-)
diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c
index b4b4931c6f8c..dc00484c6c7a 100644
--- a/drivers/net/phy/dp83867.c
+++ b/drivers/net/phy/dp83867.c
@@ -1057,15 +1057,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)
@@ -1291,7 +1282,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,
--
2.43.0
^ permalink raw reply related [flat|nested] 32+ messages in thread