AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/09 11:18 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "NET_VENDOR_ONSEMI",
    "S2500_MACPHY",
    "OA_TC6",
    "NCN26000_PHY"
  ],
  "FocusSymbols": [
    "s2500_probe",
    "s2500_open",
    "s2500_ioctl",
    "s2500_hwtstamp_set",
    "s2500_set_multicast_mode",
    "s2500_ptp_adjtime",
    "oa_tc6_hwtstamp_set",
    "oa_tc6_tstamp_ioctl",
    "oa_tc6_ptp_register",
    "ncn26000_c45_plca_set_cfg"
  ],
  "Reasoning": "The patch introduces a new SPI-based ethernet driver for the onsemi S2500 MACPHY, adds hardware timestamping support to the OA TC6 framework, and updates the NCN26000 PHY driver. These are functional changes to the networking subsystem and PHY drivers that can be interacted with via ethtool, netlink, and ioctls.",
  "WorthFuzzing": true
}

1/1 2026/07/09 11:18 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit dbd555c72da3631ca031e9465dd5b28c8dbc78db\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Jul 9 11:18:42 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/net/onnn,s2500.yaml b/Documentation/devicetree/bindings/net/onnn,s2500.yaml\nnew file mode 100644\nindex 00000000000000..11edf10508d9f3\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/net/onnn,s2500.yaml\n@@ -0,0 +1,67 @@\n+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/net/onnn,s2500.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: onsemi S2500 10BASE-T1S MACPHY Ethernet Controllers\n+\n+maintainers:\n+  - Piergiorgio Beruto \u003cpier.beruto@onsemi.com\u003e\n+  - Selva Rajagopal \u003cSelvamani.Rajagopal@onsemi.com\u003e\n+\n+description:\n+  The S2500 combines a Media Access Controller (MAC) and an\n+  Ethernet PHY to enable 10BASE‑T1S networks. The Ethernet Media Access\n+  Controller (MAC) module implements a 10 Mbps half duplex Ethernet MAC,\n+  compatible with the IEEE 802.3 standard and a 10BASE-T1S physical layer\n+  transceiver integrated into the S2500. The communication between\n+  the host and the MAC-PHY is specified in the OPEN Alliance 10BASE-T1x\n+  MACPHY Serial Interface (TC6).\n+\n+allOf:\n+  - $ref: /schemas/net/ethernet-controller.yaml#\n+  - $ref: /schemas/spi/spi-peripheral-props.yaml#\n+\n+properties:\n+  compatible:\n+    const: onnn,s2500\n+\n+  reg:\n+    maxItems: 1\n+\n+  interrupts:\n+    description:\n+      Interrupt from MAC-PHY asserted in the event of Receive Chunks\n+      Available, Transmit Chunk Credits Available and Extended Status\n+      Event.\n+    maxItems: 1\n+\n+  spi-max-frequency:\n+    maximum: 25000000\n+\n+required:\n+  - compatible\n+  - reg\n+  - interrupts\n+  - spi-max-frequency\n+\n+unevaluatedProperties: false\n+\n+examples:\n+  - |\n+    #include \u003cdt-bindings/interrupt-controller/irq.h\u003e\n+    spi {\n+      #address-cells = \u003c1\u003e;\n+      #size-cells = \u003c0\u003e;\n+\n+      ethernet@0 {\n+        compatible = \"onnn,s2500\";\n+        reg = \u003c0\u003e;\n+        pinctrl-names = \"default\";\n+        pinctrl-0 = \u003c\u0026eth0_pins\u003e;\n+        interrupt-parent = \u003c\u0026gpio\u003e;\n+        interrupts = \u003c25 IRQ_TYPE_EDGE_FALLING\u003e;\n+        spi-max-frequency = \u003c15000000\u003e;\n+      };\n+    };\ndiff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst\nindex fe2aabde923a57..325d299d1a7dc2 100644\n--- a/Documentation/networking/oa-tc6-framework.rst\n+++ b/Documentation/networking/oa-tc6-framework.rst\n@@ -153,6 +153,10 @@ OPEN Alliance TC6 Framework\n - Forwards the received Ethernet frame from 10Base-T1x MAC-PHY to n/w\n   subsystem.\n \n+- If supported by the hardware and enabled, updates hardware timestamp\n+  in skb, when indicated by one of the three timestamp capture registers\n+  through TSC fields of the header.\n+\n Data Transaction\n ~~~~~~~~~~~~~~~~\n \n@@ -495,3 +499,79 @@ the MAC-PHY.\n Zero align receive frame feature can be enabled to align all receive ethernet\n frames data to start at the beginning of any receive data chunk payload with a\n start word offset (SWO) of zero.\n+\n+.. c:function:: int oa_tc6_ptp_register(struct oa_tc6 *tc6, \\\n+                                        struct ptp_clock_info *info);\n+\n+Registers the PTP hardware clock related functions with the kernel.\n+This API simply registers. Initialization of the fields in the\n+ptp_clock_info structure are left to the vendor as programming hardware\n+timer is expected to be vendor dependent. The fields max_adj, owner,\n+and all the functions for the clock operations, like adjfine, gettimex64,\n+settime64, adjtime are expected to be initialized in the structure before\n+calling the registering the hardware clock.\n+\n+.. c:function:: void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);\n+\n+Unregisters the PTP hardware clock related callbacks.\n+\n+.. c:function:: int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, \\\n+                                 int cmd);\n+\n+ioctl interface to handle hardware timestamp and PHY related commands.\n+\n+.. c:function:: int oa_tc6_get_ts_info(struct oa_tc6 *tc6, \\\n+                                       struct kernel_ethtool_ts_info *info);\n+\n+Provides timestamp related settings that are supported to ethtool.\n+\n+.. c:function:: void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6, \\\n+                                         struct kernel_hwtstamp_config *cfg);\n+\n+Returns hardware timestamp configuration. Part of net_device_ops callbacks.\n+\n+.. c:function:: void oa_tc6_get_ts_stats(struct oa_tc6 *tc6, \\\n+                                         struct ethtool_ts_stats *ts_stats);\n+\n+Provides hardware timestamp related traffic statistics for ethtool.\n+\n+.. c:function:: int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6, \\\n+                                         struct kernel_hwtstamp_config *cfg);\n+\n+Helper to set hardware timestamp configuration. Part of net_device_ops\n+callbacks.\n+\n+.. c:function:: void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms);\n+\n+Helper to map MDIO_MMD_VEND1 command to vendor specific Memory Map Select\n+(MMS) value. This function offers flexibility for vendors that may have\n+used any MMS value between 10 and 15 as allowed by the specification.\n+MDIO_MMD_VEND2 is already mapped to MMS4 in the OA TC6 frame work code.\n+\n+.. c:function:: int oa_tc6_write_registers_mms(struct oa_tc6 *tc6, \\\n+                                               u16 address, u16 mms, \\\n+                                               u32 value[], u8 length);\n+\n+Writing multiple consecutive registers starting from @address for the\n+given @mms memory map selector in the MAC-PHY. Maximum of 128 consecutive\n+registers can be written starting at @address.\n+\n+.. c:function:: int oa_tc6_write_register_mms(struct oa_tc6 *tc6, \\\n+                                              u16 address, u16 mms, \\\n+                                              u32 value);\n+\n+Write a single register addressed by @address and @mms in the MAC-PHY.\n+\n+.. c:function:: int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, \\\n+                                              u16 address, u16 mms, \\\n+                                              u32 value[], u8 length);\n+\n+Reading multiple consecutive registers starting from @address for the\n+given @mms memory map selector value, in the MAC-PHY. Maximum of 128\n+consecutive registers can be read starting at @address.\n+\n+.. c:function:: int oa_tc6_read_register_mms(struct oa_tc6 *tc6, \\\n+                                             u16 address, u16 mms, \\\n+                                             u32 *value);\n+\n+Read a single register addressed by @address and @mms in the MAC-PHY.\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 52f1a55eca99bf..958b9f61e36418 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -20182,12 +20182,20 @@ S:\tMaintained\n F:\tarch/mips/boot/dts/ralink/omega2p.dts\n \n ONSEMI ETHERNET PHY DRIVERS\n-M:\tPiergiorgio Beruto \u003cpiergiorgio.beruto@gmail.com\u003e\n+M:\tPiergiorgio Beruto \u003cpier.beruto@onsemi.com\u003e\n+M:\tSelva Rajagopal \u003cselvamani.rajagopal@onsemi.com\u003e\n L:\tnetdev@vger.kernel.org\n S:\tSupported\n W:\thttp://www.onsemi.com\n F:\tdrivers/net/phy/ncn*\n \n+ONSEMI S2500 10BASE-T1S MACPHY ETHERNET DRIVER\n+M:\tSelva Rajagopal \u003cselvamani.rajagopal@onsemi.com\u003e\n+L:\tnetdev@vger.kernel.org\n+S:\tSupported\n+F:\tDocumentation/devicetree/bindings/net/onnn,s2500.yaml\n+F:\tdrivers/net/ethernet/onsemi/s2500/s2500_*\n+\n OP-TEE DRIVER\n M:\tJens Wiklander \u003cjens.wiklander@linaro.org\u003e\n L:\top-tee@lists.trustedfirmware.org (moderated for non-subscribers)\n@@ -20209,10 +20217,11 @@ F:\tdrivers/rtc/rtc-optee.c\n \n OPEN ALLIANCE 10BASE-T1S MACPHY SERIAL INTERFACE FRAMEWORK\n M:\tParthiban Veerasooran \u003cparthiban.veerasooran@microchip.com\u003e\n+M:\tSelva Rajagopal \u003cselvamani.rajagopal@onsemi.com\u003e (timestamp support)\n L:\tnetdev@vger.kernel.org\n S:\tMaintained\n F:\tDocumentation/networking/oa-tc6-framework.rst\n-F:\tdrivers/net/ethernet/oa_tc6.c\n+F:\tdrivers/net/ethernet/oa_tc6/oa_tc6*\n F:\tinclude/linux/oa_tc6.h\n \n OPEN FIRMWARE AND FLATTENED DEVICE TREE\ndiff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig\nindex 78c79ad7bba553..27402a592b294d 100644\n--- a/drivers/net/ethernet/Kconfig\n+++ b/drivers/net/ethernet/Kconfig\n@@ -134,7 +134,9 @@ source \"drivers/net/ethernet/netronome/Kconfig\"\n source \"drivers/net/ethernet/8390/Kconfig\"\n source \"drivers/net/ethernet/nvidia/Kconfig\"\n source \"drivers/net/ethernet/nxp/Kconfig\"\n+source \"drivers/net/ethernet/oa_tc6/Kconfig\"\n source \"drivers/net/ethernet/oki-semi/Kconfig\"\n+source \"drivers/net/ethernet/onsemi/Kconfig\"\n \n config ETHOC\n \ttristate \"OpenCores 10/100 Mbps Ethernet MAC support\"\n@@ -146,17 +148,6 @@ config ETHOC\n \thelp\n \t  Say Y here if you want to use the OpenCores 10/100 Mbps Ethernet MAC.\n \n-config OA_TC6\n-\ttristate \"OPEN Alliance TC6 10BASE-T1x MAC-PHY support\" if COMPILE_TEST\n-\tdepends on SPI\n-\tselect PHYLIB\n-\thelp\n-\t  This library implements OPEN Alliance TC6 10BASE-T1x MAC-PHY\n-\t  Serial Interface protocol for supporting 10BASE-T1x MAC-PHYs.\n-\n-\t  To know the implementation details, refer documentation in\n-\t  \u003cfile:Documentation/networking/oa-tc6-framework.rst\u003e.\n-\n source \"drivers/net/ethernet/pasemi/Kconfig\"\n source \"drivers/net/ethernet/pensando/Kconfig\"\n source \"drivers/net/ethernet/qlogic/Kconfig\"\ndiff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile\nindex bba55d9af38770..77b11d5a7abf13 100644\n--- a/drivers/net/ethernet/Makefile\n+++ b/drivers/net/ethernet/Makefile\n@@ -71,6 +71,7 @@ obj-$(CONFIG_NET_VENDOR_NETRONOME) += netronome/\n obj-$(CONFIG_NET_VENDOR_NI) += ni/\n obj-$(CONFIG_NET_VENDOR_NVIDIA) += nvidia/\n obj-$(CONFIG_LPC_ENET) += nxp/\n+obj-$(CONFIG_OA_TC6) += oa_tc6/\n obj-$(CONFIG_NET_VENDOR_OKI) += oki-semi/\n obj-$(CONFIG_ETHOC) += ethoc.o\n obj-$(CONFIG_NET_VENDOR_PASEMI) += pasemi/\n@@ -104,4 +105,3 @@ obj-$(CONFIG_NET_VENDOR_XILINX) += xilinx/\n obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/\n obj-$(CONFIG_NET_VENDOR_SYNOPSYS) += synopsys/\n obj-$(CONFIG_NET_VENDOR_PENSANDO) += pensando/\n-obj-$(CONFIG_OA_TC6) += oa_tc6.o\ndiff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c\nindex 0277d9737369ad..3b555ee6980410 100644\n--- a/drivers/net/ethernet/microchip/lan865x/lan865x.c\n+++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c\n@@ -13,27 +13,27 @@\n #define DRV_NAME\t\t\t\"lan8650\"\n \n /* MAC Network Control Register */\n-#define LAN865X_REG_MAC_NET_CTL\t\t0x00010000\n+#define LAN865X_REG_MAC_NET_CTL\t\t0x0\n #define MAC_NET_CTL_TXEN\t\tBIT(3) /* Transmit Enable */\n #define MAC_NET_CTL_RXEN\t\tBIT(2) /* Receive Enable */\n \n /* MAC Network Configuration Reg */\n-#define LAN865X_REG_MAC_NET_CFG\t\t0x00010001\n+#define LAN865X_REG_MAC_NET_CFG\t\t0x1\n #define MAC_NET_CFG_PROMISCUOUS_MODE\tBIT(4)\n #define MAC_NET_CFG_MULTICAST_MODE\tBIT(6)\n #define MAC_NET_CFG_UNICAST_MODE\tBIT(7)\n \n /* MAC Hash Register Bottom */\n-#define LAN865X_REG_MAC_L_HASH\t\t0x00010020\n+#define LAN865X_REG_MAC_L_HASH\t\t0x20\n /* MAC Hash Register Top */\n-#define LAN865X_REG_MAC_H_HASH\t\t0x00010021\n+#define LAN865X_REG_MAC_H_HASH\t\t0x21\n /* MAC Specific Addr 1 Bottom Reg */\n-#define LAN865X_REG_MAC_L_SADDR1\t0x00010022\n+#define LAN865X_REG_MAC_L_SADDR1\t0x22\n /* MAC Specific Addr 1 Top Reg */\n-#define LAN865X_REG_MAC_H_SADDR1\t0x00010023\n+#define LAN865X_REG_MAC_H_SADDR1\t0x23\n \n /* MAC TSU Timer Increment Register */\n-#define LAN865X_REG_MAC_TSU_TIMER_INCR\t\t0x00010077\n+#define LAN865X_REG_MAC_TSU_TIMER_INCR\t\t0x77\n #define MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS\t0x0028\n \n struct lan865x_priv {\n@@ -49,7 +49,8 @@ static int lan865x_set_hw_macaddr_low_bytes(struct oa_tc6 *tc6, const u8 *mac)\n \n \tregval = (mac[3] \u003c\u003c 24) | (mac[2] \u003c\u003c 16) | (mac[1] \u003c\u003c 8) | mac[0];\n \n-\treturn oa_tc6_write_register(tc6, LAN865X_REG_MAC_L_SADDR1, regval);\n+\treturn oa_tc6_write_register_mms(tc6, LAN865X_REG_MAC_L_SADDR1,\n+\t\t\t\t\t OA_TC6_PHY_C45_MAC_MMS1, regval);\n }\n \n static int lan865x_set_hw_macaddr(struct lan865x_priv *priv, const u8 *mac)\n@@ -65,8 +66,8 @@ static int lan865x_set_hw_macaddr(struct lan865x_priv *priv, const u8 *mac)\n \n \t/* Prepare and configure MAC address high bytes */\n \tregval = (mac[5] \u003c\u003c 8) | mac[4];\n-\tret = oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_H_SADDR1,\n-\t\t\t\t    regval);\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_H_SADDR1,\n+\t\t\t\t\tOA_TC6_PHY_C45_MAC_MMS1, regval);\n \tif (!ret)\n \t\treturn 0;\n \n@@ -146,14 +147,16 @@ static int lan865x_set_specific_multicast_addr(struct lan865x_priv *priv)\n \t}\n \n \t/* Enabling specific multicast addresses */\n-\tret = oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_H_HASH, hash_hi);\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_H_HASH,\n+\t\t\t\t\tOA_TC6_PHY_C45_MAC_MMS1, hash_hi);\n \tif (ret) {\n \t\tnetdev_err(priv-\u003enetdev, \"Failed to write reg_hashh: %d\\n\",\n \t\t\t   ret);\n \t\treturn ret;\n \t}\n \n-\tret = oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_L_HASH, hash_lo);\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_L_HASH,\n+\t\t\t\t\tOA_TC6_PHY_C45_MAC_MMS1, hash_lo);\n \tif (ret)\n \t\tnetdev_err(priv-\u003enetdev, \"Failed to write reg_hashl: %d\\n\",\n \t\t\t   ret);\n@@ -166,16 +169,16 @@ static int lan865x_set_all_multicast_addr(struct lan865x_priv *priv)\n \tint ret;\n \n \t/* Enabling all multicast addresses */\n-\tret = oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_H_HASH,\n-\t\t\t\t    0xffffffff);\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_H_HASH,\n+\t\t\t\t\tOA_TC6_PHY_C45_MAC_MMS1, 0xffffffff);\n \tif (ret) {\n \t\tnetdev_err(priv-\u003enetdev, \"Failed to write reg_hashh: %d\\n\",\n \t\t\t   ret);\n \t\treturn ret;\n \t}\n \n-\tret = oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_L_HASH,\n-\t\t\t\t    0xffffffff);\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_L_HASH,\n+\t\t\t\t\tOA_TC6_PHY_C45_MAC_MMS1, 0xffffffff);\n \tif (ret)\n \t\tnetdev_err(priv-\u003enetdev, \"Failed to write reg_hashl: %d\\n\",\n \t\t\t   ret);\n@@ -187,14 +190,16 @@ static int lan865x_clear_all_multicast_addr(struct lan865x_priv *priv)\n {\n \tint ret;\n \n-\tret = oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_H_HASH, 0);\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_H_HASH,\n+\t\t\t\t\tOA_TC6_PHY_C45_MAC_MMS1, 0);\n \tif (ret) {\n \t\tnetdev_err(priv-\u003enetdev, \"Failed to write reg_hashh: %d\\n\",\n \t\t\t   ret);\n \t\treturn ret;\n \t}\n \n-\tret = oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_L_HASH, 0);\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_L_HASH,\n+\t\t\t\t\tOA_TC6_PHY_C45_MAC_MMS1, 0);\n \tif (ret)\n \t\tnetdev_err(priv-\u003enetdev, \"Failed to write reg_hashl: %d\\n\",\n \t\t\t   ret);\n@@ -235,7 +240,8 @@ static void lan865x_multicast_work_handler(struct work_struct *work)\n \t\tif (lan865x_clear_all_multicast_addr(priv))\n \t\t\treturn;\n \t}\n-\tret = oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_NET_CFG, regval);\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_NET_CFG,\n+\t\t\t\t\tOA_TC6_PHY_C45_MAC_MMS1, regval);\n \tif (ret)\n \t\tnetdev_err(priv-\u003enetdev, \"Failed to enable promiscuous/multicast/normal mode: %d\\n\",\n \t\t\t   ret);\n@@ -260,12 +266,14 @@ static int lan865x_hw_disable(struct lan865x_priv *priv)\n {\n \tu32 regval;\n \n-\tif (oa_tc6_read_register(priv-\u003etc6, LAN865X_REG_MAC_NET_CTL, \u0026regval))\n+\tif (oa_tc6_read_register_mms(priv-\u003etc6, LAN865X_REG_MAC_NET_CTL,\n+\t\t\t\t     OA_TC6_PHY_C45_MAC_MMS1, \u0026regval))\n \t\treturn -ENODEV;\n \n \tregval \u0026= ~(MAC_NET_CTL_TXEN | MAC_NET_CTL_RXEN);\n \n-\tif (oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_NET_CTL, regval))\n+\tif (oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_NET_CTL,\n+\t\t\t\t      OA_TC6_PHY_C45_MAC_MMS1, regval))\n \t\treturn -ENODEV;\n \n \treturn 0;\n@@ -291,12 +299,14 @@ static int lan865x_hw_enable(struct lan865x_priv *priv)\n {\n \tu32 regval;\n \n-\tif (oa_tc6_read_register(priv-\u003etc6, LAN865X_REG_MAC_NET_CTL, \u0026regval))\n+\tif (oa_tc6_read_register_mms(priv-\u003etc6, LAN865X_REG_MAC_NET_CTL,\n+\t\t\t\t     OA_TC6_PHY_C45_MAC_MMS1, \u0026regval))\n \t\treturn -ENODEV;\n \n \tregval |= MAC_NET_CTL_TXEN | MAC_NET_CTL_RXEN;\n \n-\tif (oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_NET_CTL, regval))\n+\tif (oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_NET_CTL,\n+\t\t\t\t      OA_TC6_PHY_C45_MAC_MMS1, regval))\n \t\treturn -ENODEV;\n \n \treturn 0;\n@@ -359,8 +369,9 @@ static int lan865x_probe(struct spi_device *spi)\n \t * stamping at the end of the Start of Frame Delimiter (SFD) and set the\n \t * Timer Increment reg to 40 ns to be used as a 25 MHz internal clock.\n \t */\n-\tret = oa_tc6_write_register(priv-\u003etc6, LAN865X_REG_MAC_TSU_TIMER_INCR,\n-\t\t\t\t    MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS);\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6, LAN865X_REG_MAC_TSU_TIMER_INCR,\n+\t\t\t\t\tOA_TC6_PHY_C45_MAC_MMS1,\n+\t\t\t\t\tMAC_TSU_TIMER_INCR_COUNT_NANOSECONDS);\n \tif (ret) {\n \t\tdev_err(\u0026spi-\u003edev, \"Failed to config TSU Timer Incr reg: %d\\n\",\n \t\t\tret);\ndiff --git a/drivers/net/ethernet/oa_tc6/Kconfig b/drivers/net/ethernet/oa_tc6/Kconfig\nnew file mode 100644\nindex 00000000000000..97345f345fb9f2\n--- /dev/null\n+++ b/drivers/net/ethernet/oa_tc6/Kconfig\n@@ -0,0 +1,16 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+#\n+# OA TC6 10BASE-T1x MAC-PHY configuration\n+#\n+\n+config OA_TC6\n+\ttristate \"OPEN Alliance TC6 10BASE-T1x MAC-PHY support\"\n+\tdepends on SPI\n+\tselect PHYLIB\n+\thelp\n+\t  This library implements OPEN Alliance TC6 10BASE-T1x MAC-PHY\n+\t  Serial Interface protocol for supporting 10BASE-T1x MAC-PHYs.\n+\n+\t  To know the implementation details, refer documentation in\n+\t  \u003cfile:Documentation/networking/oa-tc6-framework.rst\u003e.\n+\ndiff --git a/drivers/net/ethernet/oa_tc6/Makefile b/drivers/net/ethernet/oa_tc6/Makefile\nnew file mode 100644\nindex 00000000000000..964f668efc2d3a\n--- /dev/null\n+++ b/drivers/net/ethernet/oa_tc6/Makefile\n@@ -0,0 +1,7 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+#\n+# Makefile for OA TC6 10BASE-T1x MAC-PHY\n+#\n+\n+obj-$(CONFIG_OA_TC6) := oa_tc6_mod.o\n+oa_tc6_mod-objs := oa_tc6.o oa_tc6_ptp.o oa_tc6_tstamp.o\ndiff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c\nsimilarity index 79%\nrename from drivers/net/ethernet/oa_tc6.c\nrename to drivers/net/ethernet/oa_tc6/oa_tc6.c\nindex 0727d53345a33b..d4e94724f64912 100644\n--- a/drivers/net/ethernet/oa_tc6.c\n+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c\n@@ -12,149 +12,15 @@\n #include \u003clinux/phy.h\u003e\n #include \u003clinux/oa_tc6.h\u003e\n \n-/* OPEN Alliance TC6 registers */\n-/* Standard Capabilities Register */\n-#define OA_TC6_REG_STDCAP\t\t\t0x0002\n-#define STDCAP_DIRECT_PHY_REG_ACCESS\t\tBIT(8)\n-\n-/* Reset Control and Status Register */\n-#define OA_TC6_REG_RESET\t\t\t0x0003\n-#define RESET_SWRESET\t\t\t\tBIT(0)\t/* Software Reset */\n-\n-/* Configuration Register #0 */\n-#define OA_TC6_REG_CONFIG0\t\t\t0x0004\n-#define CONFIG0_SYNC\t\t\t\tBIT(15)\n-#define CONFIG0_ZARFE_ENABLE\t\t\tBIT(12)\n-\n-/* Status Register #0 */\n-#define OA_TC6_REG_STATUS0\t\t\t0x0008\n-#define STATUS0_RESETC\t\t\t\tBIT(6)\t/* Reset Complete */\n-#define STATUS0_HEADER_ERROR\t\t\tBIT(5)\n-#define STATUS0_LOSS_OF_FRAME_ERROR\t\tBIT(4)\n-#define STATUS0_RX_BUFFER_OVERFLOW_ERROR\tBIT(3)\n-#define STATUS0_TX_PROTOCOL_ERROR\t\tBIT(0)\n-\n-/* Buffer Status Register */\n-#define OA_TC6_REG_BUFFER_STATUS\t\t0x000B\n-#define BUFFER_STATUS_TX_CREDITS_AVAILABLE\tGENMASK(15, 8)\n-#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE\tGENMASK(7, 0)\n-\n-/* Interrupt Mask Register #0 */\n-#define OA_TC6_REG_INT_MASK0\t\t\t0x000C\n-#define INT_MASK0_HEADER_ERR_MASK\t\tBIT(5)\n-#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK\tBIT(4)\n-#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK\tBIT(3)\n-#define INT_MASK0_TX_PROTOCOL_ERR_MASK\t\tBIT(0)\n-#define INT_MASK0_ALL_INTERRUPTS                (GENMASK(5, 0) | \\\n-\t\t\t\t\t\t GENMASK(12, 7))\n-\n-/* PHY Clause 22 registers base address and mask */\n-#define OA_TC6_PHY_STD_REG_ADDR_BASE\t\t0xFF00\n-#define OA_TC6_PHY_STD_REG_ADDR_MASK\t\t0x1F\n-\n-/* Control command header */\n-#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL\tBIT(31)\n-#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ\tBIT(29)\n-#define OA_TC6_CTRL_HEADER_MEM_MAP_SELECTOR\tGENMASK(27, 24)\n-#define OA_TC6_CTRL_HEADER_ADDR\t\t\tGENMASK(23, 8)\n-#define OA_TC6_CTRL_HEADER_LENGTH\t\tGENMASK(7, 1)\n-#define OA_TC6_CTRL_HEADER_PARITY\t\tBIT(0)\n-\n-/* Data header */\n-#define OA_TC6_DATA_HEADER_DATA_NOT_CTRL\tBIT(31)\n-#define OA_TC6_DATA_HEADER_DATA_VALID\t\tBIT(21)\n-#define OA_TC6_DATA_HEADER_START_VALID\t\tBIT(20)\n-#define OA_TC6_DATA_HEADER_START_WORD_OFFSET\tGENMASK(19, 16)\n-#define OA_TC6_DATA_HEADER_END_VALID\t\tBIT(14)\n-#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET\tGENMASK(13, 8)\n-#define OA_TC6_DATA_HEADER_PARITY\t\tBIT(0)\n-\n-/* Data footer */\n-#define OA_TC6_DATA_FOOTER_EXTENDED_STS\t\tBIT(31)\n-#define OA_TC6_DATA_FOOTER_RXD_HEADER_BAD\tBIT(30)\n-#define OA_TC6_DATA_FOOTER_CONFIG_SYNC\t\tBIT(29)\n-#define OA_TC6_DATA_FOOTER_RX_CHUNKS\t\tGENMASK(28, 24)\n-#define OA_TC6_DATA_FOOTER_DATA_VALID\t\tBIT(21)\n-#define OA_TC6_DATA_FOOTER_START_VALID\t\tBIT(20)\n-#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET\tGENMASK(19, 16)\n-#define OA_TC6_DATA_FOOTER_END_VALID\t\tBIT(14)\n-#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET\tGENMASK(13, 8)\n-#define OA_TC6_DATA_FOOTER_TX_CREDITS\t\tGENMASK(5, 1)\n-\n-/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the\n- * OPEN Alliance specification.\n- */\n-#define OA_TC6_PHY_C45_PCS_MMS2\t\t\t2\t/* MMD 3 */\n-#define OA_TC6_PHY_C45_PMA_PMD_MMS3\t\t3\t/* MMD 1 */\n-#define OA_TC6_PHY_C45_VS_PLCA_MMS4\t\t4\t/* MMD 31 */\n-#define OA_TC6_PHY_C45_AUTO_NEG_MMS5\t\t5\t/* MMD 7 */\n-#define OA_TC6_PHY_C45_POWER_UNIT_MMS6\t\t6\t/* MMD 13 */\n-\n-#define OA_TC6_CTRL_HEADER_SIZE\t\t\t4\n-#define OA_TC6_CTRL_REG_VALUE_SIZE\t\t4\n-#define OA_TC6_CTRL_IGNORED_SIZE\t\t4\n-#define OA_TC6_CTRL_MAX_REGISTERS\t\t128\n-#define OA_TC6_CTRL_SPI_BUF_SIZE\t\t(OA_TC6_CTRL_HEADER_SIZE +\\\n-\t\t\t\t\t\t(OA_TC6_CTRL_MAX_REGISTERS *\\\n-\t\t\t\t\t\tOA_TC6_CTRL_REG_VALUE_SIZE) +\\\n-\t\t\t\t\t\tOA_TC6_CTRL_IGNORED_SIZE)\n-#define OA_TC6_CHUNK_PAYLOAD_SIZE\t\t64\n-#define OA_TC6_DATA_HEADER_SIZE\t\t\t4\n-#define OA_TC6_CHUNK_SIZE\t\t\t(OA_TC6_DATA_HEADER_SIZE +\\\n-\t\t\t\t\t\tOA_TC6_CHUNK_PAYLOAD_SIZE)\n-#define OA_TC6_MAX_TX_CHUNKS\t\t\t48\n-#define OA_TC6_SPI_DATA_BUF_SIZE\t\t(OA_TC6_MAX_TX_CHUNKS *\\\n-\t\t\t\t\t\tOA_TC6_CHUNK_SIZE)\n-#define STATUS0_RESETC_POLL_DELAY\t\t1000\n-#define STATUS0_RESETC_POLL_TIMEOUT\t\t1000000\n-\n-/* Internal structure for MAC-PHY drivers */\n-struct oa_tc6 {\n-\tstruct net_device *netdev;\n-\tstruct phy_device *phydev;\n-\tstruct mii_bus *mdiobus;\n-\tstruct spi_device *spi;\n-\tstruct mutex spi_ctrl_lock; /* Protects spi control transfer */\n-\tspinlock_t tx_skb_lock; /* Protects tx skb handling */\n-\tvoid *spi_ctrl_tx_buf;\n-\tvoid *spi_ctrl_rx_buf;\n-\tvoid *spi_data_tx_buf;\n-\tvoid *spi_data_rx_buf;\n-\tstruct sk_buff *ongoing_tx_skb;\n-\tstruct sk_buff *waiting_tx_skb;\n-\tstruct sk_buff *rx_skb;\n-\tu16 tx_skb_offset;\n-\tu16 spi_data_tx_buf_offset;\n-\tu16 tx_credits;\n-\tu8 rx_chunks_available;\n-\tbool rx_buf_overflow;\n-\tbool int_flag;\n-\tbool disable_traffic;\n-};\n-\n-enum oa_tc6_header_type {\n-\tOA_TC6_CTRL_HEADER,\n-\tOA_TC6_DATA_HEADER,\n-};\n-\n-enum oa_tc6_register_op {\n-\tOA_TC6_CTRL_REG_READ = 0,\n-\tOA_TC6_CTRL_REG_WRITE = 1,\n-};\n-\n-enum oa_tc6_data_valid_info {\n-\tOA_TC6_DATA_INVALID,\n-\tOA_TC6_DATA_VALID,\n-};\n+#include \"oa_tc6_std_def.h\"\n \n-enum oa_tc6_data_start_valid_info {\n-\tOA_TC6_DATA_START_INVALID,\n-\tOA_TC6_DATA_START_VALID,\n+struct oa_tc6_ts_info_rx {\n+\tbool rtsa;\n+\tbool rtsp;\n };\n \n-enum oa_tc6_data_end_valid_info {\n-\tOA_TC6_DATA_END_INVALID,\n-\tOA_TC6_DATA_END_VALID,\n+struct oa_tc6_ts_info_tx {\n+\tu8 tsc;\n };\n \n static int oa_tc6_spi_transfer(struct oa_tc6 *tc6,\n@@ -191,6 +57,168 @@ static int oa_tc6_get_parity(u32 p)\n \treturn !((p \u003e\u003e 28) \u0026 1);\n }\n \n+static struct oa_tc6_ts_info_tx *oa_tc6_tsinfo_tx(struct sk_buff *skb)\n+{\n+\treturn (struct oa_tc6_ts_info_tx *)(skb-\u003ecb);\n+}\n+\n+static struct oa_tc6_ts_info_rx *oa_tc6_tsinfo_rx(struct sk_buff *skb)\n+{\n+\treturn (struct oa_tc6_ts_info_rx *)(skb-\u003ecb);\n+}\n+\n+static void oa_tc6_defer_for_hwtstamp(struct oa_tc6 *tc6,\n+\t\t\t\t      struct sk_buff *skb)\n+{\n+\tif (!tc6-\u003ehw_tstamp_enabled)\n+\t\treturn;\n+\tif (!skb || (skb_shinfo(skb)-\u003etx_flags \u0026 SKBTX_HW_TSTAMP) == 0)\n+\t\treturn;\n+\tif (tc6-\u003ets_config.tx_type != HWTSTAMP_TX_ON) {\n+\t\ttc6-\u003etx_hwtstamp_lost++;\n+\t\treturn;\n+\t}\n+\n+\tskb_shinfo(skb)-\u003etx_flags |= SKBTX_IN_PROGRESS;\n+\tu8 ret = tc6-\u003etx_ts_idx++;\n+\n+\tif (ret == OA_TC6_TTSCC_REG_ID)\n+\t\ttc6-\u003etx_ts_idx = OA_TC6_TTSCA_REG_ID;\n+\toa_tc6_tsinfo_tx(skb)-\u003etsc = ret;\n+\n+\tlist_add_tail(\u0026skb-\u003elist, \u0026tc6-\u003etx_ts_skb_q);\n+}\n+\n+static int oa_tc6_process_deferred_skb(struct oa_tc6 *tc6, u8 tsc)\n+{\n+\tstruct skb_shared_hwtstamps tstamp;\n+\tstruct oa_tc6_ts_info_tx *ski;\n+\tstruct sk_buff *skb, *tmp;\n+\tbool found = false;\n+\tint ret = 0;\n+\n+\t/* Size of data must match OA_TC6_TSTAMP_SZ */\n+\tu32 data[2];\n+\n+\tlist_for_each_entry_safe(skb, tmp, \u0026tc6-\u003etx_ts_skb_q, list) {\n+\t\tski = oa_tc6_tsinfo_tx(skb);\n+\t\tif (ski-\u003etsc != tsc)\n+\t\t\tcontinue;\n+\t\tif (found) {\n+\t\t\tdev_warn_ratelimited(\u0026tc6-\u003espi-\u003edev,\n+\t\t\t\t\t     \"Multiple skbs. tsc = %d\\n\",\n+\t\t\t\t\t     tsc);\n+\t\t\ttc6-\u003etx_hwtstamp_err++;\n+\t\t}\n+\t\tfound = true;\n+\t\tlist_del(\u0026skb-\u003elist);\n+\n+\t\t/* Retrieve the timestamping info */\n+\t\tret = oa_tc6_read_registers(tc6,\n+\t\t\t\t\t    OA_TC6_REG_TTSCA_HIGH +\n+\t\t\t\t\t    2 * (tsc - 1), \u0026data[0], 2);\n+\n+\t\tif (!ret) {\n+\t\t\ttstamp.hwtstamp = ktime_set(data[0], data[1]);\n+\t\t\tskb_tstamp_tx(skb, \u0026tstamp);\n+\t\t\ttc6-\u003etx_hwtstamp_pkts++;\n+\t\t}\n+\n+\t\tdev_kfree_skb(skb);\n+\t}\n+\treturn ret;\n+}\n+\n+static void oa_tc6_events_handle(struct oa_tc6 *tc6, u32 val)\n+{\n+\t/* Check TX timestamping */\n+\tif (val \u0026 STATUS0_TTSCAA)\n+\t\toa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCA_REG_ID);\n+\n+\tif (val \u0026 STATUS0_TTSCAB)\n+\t\toa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCB_REG_ID);\n+\n+\tif (val \u0026 STATUS0_TTSCAC)\n+\t\toa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCC_REG_ID);\n+}\n+\n+static void oa_tc6_update_ts_in_rx_skb(struct oa_tc6 *tc6)\n+{\n+\tstruct sk_buff *skb = tc6-\u003erx_skb;\n+\tstruct oa_tc6_ts_info_rx *ski;\n+\t__be32 ts_val[2];\n+\tu32 ts[2];\n+\n+\tif (!tc6-\u003ehw_tstamp_enabled)\n+\t\treturn;\n+\tski = oa_tc6_tsinfo_rx(skb);\n+\tif (!ski-\u003ertsa)\n+\t\treturn;\n+\n+\tmemcpy(\u0026ts_val[0], skb-\u003edata, 4);\n+\tmemcpy(\u0026ts_val[1], (u32 *)skb-\u003edata + 1, 4);\n+\n+\tts[0] = be32_to_cpu(ts_val[0]);\n+\tts[1] = be32_to_cpu(ts_val[1]);\n+\n+\t/* Check parity */\n+\tif ((oa_tc6_get_parity(ts[0]) ^ oa_tc6_get_parity(ts[1])) ==\n+\t    !ski-\u003ertsp) {\n+\t\tstruct skb_shared_hwtstamps *hw_ts;\n+\n+\t\t/* Report timestamp to the upper layers */\n+\t\thw_ts = skb_hwtstamps(skb);\n+\t\tmemset(hw_ts, 0, sizeof(*hw_ts));\n+\t\thw_ts-\u003ehwtstamp = ktime_set(ts[0], ts[1]);\n+\t}\n+\tskb_pull(skb, sizeof(ts));\n+}\n+\n+static int oa_tc6_update_standard_capability(struct oa_tc6 *tc6)\n+{\n+\tu32 regval = 0;\n+\tint ret;\n+\n+\tret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, \u0026regval);\n+\tif (ret)\n+\t\treturn ret;\n+\tif (regval \u0026 STDCAP_FRAME_TIMESTAMP_CAPABILITY)\n+\t\ttc6-\u003ehw_tstamp_supported = true;\n+\treturn 0;\n+}\n+\n+/**\n+ * oa_tc6_ioctl - generic ioctl interface for MAC-PHY drivers.\n+ * @tc6: oa_tc6 struct.\n+ * @rq: request from socket interface\n+ * @cmd: value to set/get timestamp configuration\n+ *\n+ * Return: 0 on success otherwise failed.\n+ */\n+int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)\n+{\n+\tif (!netif_running(tc6-\u003enetdev))\n+\t\treturn -EINVAL;\n+\n+\tif (cmd == SIOCSHWTSTAMP || cmd == SIOCGHWTSTAMP)\n+\t\treturn oa_tc6_tstamp_ioctl(tc6, rq, cmd);\n+\telse\n+\t\treturn phy_do_ioctl_running(tc6-\u003enetdev, rq, cmd);\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_ioctl);\n+\n+/**\n+ * oa_tc6_set_vend1_mms - Add vendor specific MDIO_MMD to OA TC6 MMS\n+ * mapper value.\n+ * @tc6: oa_tc6 struct.\n+ * @mms: vendor defined MMS value for VEND1 mdio device.\n+ */\n+void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)\n+{\n+\ttc6-\u003evend1_mms = mms;\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_set_vend1_mms);\n+\n static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,\n \t\t\t\t\t enum oa_tc6_register_op reg_op)\n {\n@@ -355,6 +383,83 @@ int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value)\n }\n EXPORT_SYMBOL_GPL(oa_tc6_read_register);\n \n+/**\n+ * oa_tc6_read_registers_mms - function for reading multiple consecutive\n+ * registers for the given address, memory map selector pair.\n+ * @tc6: oa_tc6 struct.\n+ * @address: address of the first register to be read in the MAC-PHY.\n+ * @mms: Memory map selector for the registers to be read.\n+ * @value: values to be read from the starting register address @address.\n+ * @length: number of consecutive registers to be read from @address.\n+ *\n+ * Return: 0 on success otherwise failed.\n+ */\n+int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,\n+\t\t\t      u32 value[], u8 length)\n+{\n+\tu32 mms_addr = (u32)mms \u003c\u003c 16 | (u32)address;\n+\n+\treturn oa_tc6_read_registers(tc6, mms_addr, value, length);\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_read_registers_mms);\n+\n+/**\n+ * oa_tc6_read_register_mms - function for reading a MAC-PHY register\n+ * for the given address, memory map selector pair.\n+ * @tc6: oa_tc6 struct.\n+ * @address: register address of the MAC-PHY to be read.\n+ * @mms: Memory Map Selector for the given address\n+ * @value: value read from the @address register address of the MAC-PHY.\n+ *\n+ * Return: 0 on success otherwise failed.\n+ */\n+int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,\n+\t\t\t     u32 *value)\n+{\n+\treturn oa_tc6_read_registers_mms(tc6, address, mms, value, 1);\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_read_register_mms);\n+\n+/**\n+ * oa_tc6_write_registers_mms - function for writing multiple consecutive\n+ * registers for the given address, memory map selector pair.\n+ * @tc6: oa_tc6 struct.\n+ * @address: address of the first register to be written in the MAC-PHY.\n+ * @mms: memory map Selector for the given register.\n+ * @value: values to be written from the starting register address @address.\n+ * @length: number of consecutive registers to be written from @address.\n+ *\n+ * Maximum of 128 consecutive registers can be written starting at @address.\n+ *\n+ * Return: 0 on success otherwise failed.\n+ */\n+int oa_tc6_write_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,\n+\t\t\t       u32 value[], u8 length)\n+{\n+\tu32 mms_addr = (u32)mms \u003c\u003c 16 | (u32)address;\n+\n+\treturn oa_tc6_write_registers(tc6, mms_addr, value, length);\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_write_registers_mms);\n+\n+/**\n+ * oa_tc6_write_register_mms - function for writing a MAC-PHY register\n+ * associated with the given memory map selector.\n+ * @tc6: oa_tc6 struct.\n+ * @address: register address of the MAC-PHY to be written.\n+ * @mms: memory map selector for the given register.\n+ * @value: value to be written in the @address register address of\n+ * the MAC-PHY.\n+ *\n+ * Return: 0 on success otherwise failed.\n+ */\n+int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,\n+\t\t\t      u32 value)\n+{\n+\treturn oa_tc6_write_registers_mms(tc6, address, mms, \u0026value, 1);\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms);\n+\n /**\n  * oa_tc6_write_registers - function for writing multiple consecutive registers.\n  * @tc6: oa_tc6 struct.\n@@ -444,7 +549,7 @@ static int oa_tc6_mdiobus_write(struct mii_bus *bus, int addr, int regnum,\n \t\t\t\t     val);\n }\n \n-static int oa_tc6_get_phy_c45_mms(int devnum)\n+static int oa_tc6_get_phy_c45_mms(struct oa_tc6 *tc6, int devnum)\n {\n \tswitch (devnum) {\n \tcase MDIO_MMD_PCS:\n@@ -457,6 +562,8 @@ static int oa_tc6_get_phy_c45_mms(int devnum)\n \t\treturn OA_TC6_PHY_C45_AUTO_NEG_MMS5;\n \tcase MDIO_MMD_POWER_UNIT:\n \t\treturn OA_TC6_PHY_C45_POWER_UNIT_MMS6;\n+\tcase MDIO_MMD_VEND1:\n+\t\treturn tc6-\u003evend1_mms;\n \tdefault:\n \t\treturn -EOPNOTSUPP;\n \t}\n@@ -466,14 +573,14 @@ static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,\n \t\t\t\t   int regnum)\n {\n \tstruct oa_tc6 *tc6 = bus-\u003epriv;\n+\tint mms, ret;\n \tu32 regval;\n-\tint ret;\n \n-\tret = oa_tc6_get_phy_c45_mms(devnum);\n-\tif (ret \u003c 0)\n-\t\treturn ret;\n+\tmms = oa_tc6_get_phy_c45_mms(tc6, devnum);\n+\tif (mms \u003c 0)\n+\t\treturn mms;\n \n-\tret = oa_tc6_read_register(tc6, (ret \u003c\u003c 16) | regnum, \u0026regval);\n+\tret = oa_tc6_read_register_mms(tc6, (u16)regnum, (u16)mms, \u0026regval);\n \tif (ret)\n \t\treturn ret;\n \n@@ -484,13 +591,13 @@ static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,\n \t\t\t\t    int regnum, u16 val)\n {\n \tstruct oa_tc6 *tc6 = bus-\u003epriv;\n-\tint ret;\n+\tint mms;\n \n-\tret = oa_tc6_get_phy_c45_mms(devnum);\n-\tif (ret \u003c 0)\n-\t\treturn ret;\n+\tmms = oa_tc6_get_phy_c45_mms(tc6, devnum);\n+\tif (mms \u003c 0)\n+\t\treturn mms;\n \n-\treturn oa_tc6_write_register(tc6, (ret \u003c\u003c 16) | regnum, val);\n+\treturn oa_tc6_write_register_mms(tc6, (u16)regnum, (u16)mms, val);\n }\n \n static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)\n@@ -714,6 +821,9 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)\n \t\treturn ret;\n \t}\n \n+\tif ((value \u0026 STATUS0_TTSCA_MASK) != 0)\n+\t\toa_tc6_events_handle(tc6, value \u0026 STATUS0_TTSCA_MASK);\n+\n \t/* Clear the error interrupts status */\n \tret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, value);\n \tif (ret) {\n@@ -796,6 +906,7 @@ static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)\n \t    tc6-\u003erx_skb-\u003elen \u003e ETH_FCS_LEN)\n \t\tskb_trim(tc6-\u003erx_skb, tc6-\u003erx_skb-\u003elen - ETH_FCS_LEN);\n \n+\toa_tc6_update_ts_in_rx_skb(tc6);\n \ttc6-\u003erx_skb-\u003eprotocol = eth_type_trans(tc6-\u003erx_skb, tc6-\u003enetdev);\n \ttc6-\u003enetdev-\u003estats.rx_packets++;\n \ttc6-\u003enetdev-\u003estats.rx_bytes += tc6-\u003erx_skb-\u003elen;\n@@ -810,24 +921,29 @@ static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)\n \tmemcpy(skb_put(tc6-\u003erx_skb, length), payload, length);\n }\n \n-static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6)\n+static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6, u32 footer)\n {\n+\tstruct oa_tc6_ts_info_rx *ski;\n+\n \ttc6-\u003erx_skb = netdev_alloc_skb_ip_align(tc6-\u003enetdev, tc6-\u003enetdev-\u003emtu +\n-\t\t\t\t\t\tETH_HLEN + ETH_FCS_LEN);\n+\t\t\t\t\t\tETH_HLEN + ETH_FCS_LEN + OA_TC6_TSTAMP_SZ);\n \tif (!tc6-\u003erx_skb) {\n \t\ttc6-\u003enetdev-\u003estats.rx_dropped++;\n \t\treturn -ENOMEM;\n \t}\n \n+\tski = oa_tc6_tsinfo_rx(tc6-\u003erx_skb);\n+\tski-\u003ertsa = FIELD_GET(OA_TC6_DATA_FOOTER_RTSA_VALID, footer);\n+\tski-\u003ertsp = FIELD_GET(OA_TC6_DATA_FOOTER_RTSP_VALID, footer);\n \treturn 0;\n }\n \n static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,\n-\t\t\t\t\t u16 size)\n+\t\t\t\t\t u16 size, u32 footer)\n {\n \tint ret;\n \n-\tret = oa_tc6_allocate_rx_skb(tc6);\n+\tret = oa_tc6_allocate_rx_skb(tc6, footer);\n \tif (ret)\n \t\treturn ret;\n \n@@ -838,11 +954,11 @@ static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,\n \treturn 0;\n }\n \n-static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size)\n+static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size, u32 footer)\n {\n \tint ret;\n \n-\tret = oa_tc6_allocate_rx_skb(tc6);\n+\tret = oa_tc6_allocate_rx_skb(tc6, footer);\n \tif (ret)\n \t\treturn ret;\n \n@@ -887,7 +1003,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,\n \t\tsize = end_byte_offset + 1 - start_byte_offset;\n \t\treturn oa_tc6_prcs_complete_rx_frame(tc6,\n \t\t\t\t\t\t     \u0026data[start_byte_offset],\n-\t\t\t\t\t\t     size);\n+\t\t\t\t\t\t     size, footer);\n \t}\n \n \t/* Process the chunk with only rx frame start */\n@@ -895,7 +1011,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,\n \t\tsize = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;\n \t\treturn oa_tc6_prcs_rx_frame_start(tc6,\n \t\t\t\t\t\t  \u0026data[start_byte_offset],\n-\t\t\t\t\t\t  size);\n+\t\t\t\t\t\t  size, footer);\n \t}\n \n \t/* Process the chunk with only rx frame end */\n@@ -920,7 +1036,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,\n \t\tsize = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;\n \t\treturn oa_tc6_prcs_rx_frame_start(tc6,\n \t\t\t\t\t\t  \u0026data[start_byte_offset],\n-\t\t\t\t\t\t  size);\n+\t\t\t\t\t\t  size, footer);\n \t}\n \n \t/* Process the chunk with ongoing rx frame data */\n@@ -974,13 +1090,15 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)\n }\n \n static __be32 oa_tc6_prepare_data_header(bool data_valid, bool start_valid,\n-\t\t\t\t\t bool end_valid, u8 end_byte_offset)\n+\t\t\t\t\t bool end_valid, u8 end_byte_offset,\n+\t\t\t\t\t u8 tsc)\n {\n \tu32 header = FIELD_PREP(OA_TC6_DATA_HEADER_DATA_NOT_CTRL,\n \t\t\t\tOA_TC6_DATA_HEADER) |\n \t\t     FIELD_PREP(OA_TC6_DATA_HEADER_DATA_VALID, data_valid) |\n \t\t     FIELD_PREP(OA_TC6_DATA_HEADER_START_VALID, start_valid) |\n \t\t     FIELD_PREP(OA_TC6_DATA_HEADER_END_VALID, end_valid) |\n+\t\t     FIELD_PREP(OA_TC6_DATA_HEADER_TSC_OFFSET, tsc) |\n \t\t     FIELD_PREP(OA_TC6_DATA_HEADER_END_BYTE_OFFSET,\n \t\t\t\tend_byte_offset);\n \n@@ -999,6 +1117,7 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)\n \tenum oa_tc6_data_start_valid_info start_valid;\n \tu8 end_byte_offset = 0;\n \tu16 length_to_copy;\n+\tu8 tsc = 0;\n \n \t/* Initial value is assigned here to avoid more than 80 characters in\n \t * the declaration place.\n@@ -1008,8 +1127,10 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)\n \t/* Set start valid if the current tx chunk contains the start of the tx\n \t * ethernet frame.\n \t */\n-\tif (!tc6-\u003etx_skb_offset)\n+\tif (!tc6-\u003etx_skb_offset) {\n \t\tstart_valid = OA_TC6_DATA_START_VALID;\n+\t\ttsc = oa_tc6_tsinfo_tx(tc6-\u003eongoing_tx_skb)-\u003etsc;\n+\t}\n \n \t/* If the remaining tx skb length is more than the chunk payload size of\n \t * 64 bytes then copy only 64 bytes and leave the ongoing tx skb for\n@@ -1030,12 +1151,18 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)\n \t\ttc6-\u003etx_skb_offset = 0;\n \t\ttc6-\u003enetdev-\u003estats.tx_bytes += tc6-\u003eongoing_tx_skb-\u003elen;\n \t\ttc6-\u003enetdev-\u003estats.tx_packets++;\n-\t\tkfree_skb(tc6-\u003eongoing_tx_skb);\n+\n+\t\t/* Free the ones that are not saved for later processing,\n+\t\t * like timestamping.\n+\t\t */\n+\t\tif (!(skb_shinfo(tc6-\u003eongoing_tx_skb)-\u003etx_flags \u0026\n+\t\t    SKBTX_IN_PROGRESS))\n+\t\t\tkfree_skb(tc6-\u003eongoing_tx_skb);\n \t\ttc6-\u003eongoing_tx_skb = NULL;\n \t}\n \n \t*tx_buf = oa_tc6_prepare_data_header(OA_TC6_DATA_VALID, start_valid,\n-\t\t\t\t\t     end_valid, end_byte_offset);\n+\t\t\t\t\t     end_valid, end_byte_offset, tsc);\n \ttc6-\u003espi_data_tx_buf_offset += OA_TC6_CHUNK_SIZE;\n }\n \n@@ -1053,6 +1180,8 @@ static u16 oa_tc6_prepare_spi_tx_buf_for_tx_skbs(struct oa_tc6 *tc6)\n \t\t\ttc6-\u003eongoing_tx_skb = tc6-\u003ewaiting_tx_skb;\n \t\t\ttc6-\u003ewaiting_tx_skb = NULL;\n \t\t\tspin_unlock_bh(\u0026tc6-\u003etx_skb_lock);\n+\t\t\toa_tc6_defer_for_hwtstamp(tc6,\n+\t\t\t\t\t\t  tc6-\u003eongoing_tx_skb);\n \t\t}\n \t\tif (!tc6-\u003eongoing_tx_skb)\n \t\t\tbreak;\n@@ -1069,7 +1198,7 @@ static void oa_tc6_add_empty_chunks_to_spi_buf(struct oa_tc6 *tc6,\n \n \theader = oa_tc6_prepare_data_header(OA_TC6_DATA_INVALID,\n \t\t\t\t\t    OA_TC6_DATA_START_INVALID,\n-\t\t\t\t\t    OA_TC6_DATA_END_INVALID, 0);\n+\t\t\t\t\t    OA_TC6_DATA_END_INVALID, 0, false);\n \n \twhile (needed_empty_chunks--) {\n \t\t__be32 *tx_buf = tc6-\u003espi_data_tx_buf +\n@@ -1261,6 +1390,7 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb)\n \t\treturn NETDEV_TX_OK;\n \t}\n \n+\toa_tc6_tsinfo_tx(skb)-\u003etsc = 0;\n \tspin_lock_bh(\u0026tc6-\u003etx_skb_lock);\n \ttc6-\u003ewaiting_tx_skb = skb;\n \tspin_unlock_bh(\u0026tc6-\u003etx_skb_lock);\n@@ -1294,6 +1424,9 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)\n \tSET_NETDEV_DEV(netdev, \u0026spi-\u003edev);\n \tmutex_init(\u0026tc6-\u003espi_ctrl_lock);\n \tspin_lock_init(\u0026tc6-\u003etx_skb_lock);\n+\ttc6-\u003evend1_mms = -EOPNOTSUPP;\n+\ttc6-\u003etx_ts_idx = OA_TC6_TTSCA_REG_ID;\n+\tINIT_LIST_HEAD(\u0026tc6-\u003etx_ts_skb_q);\n \n \t/* Set the SPI controller to pump at realtime priority */\n \ttc6-\u003espi-\u003ert = true;\n@@ -1359,6 +1492,12 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)\n \t\tgoto phy_exit;\n \t}\n \n+\tret = oa_tc6_update_standard_capability(tc6);\n+\tif (ret) {\n+\t\tdev_err(\u0026tc6-\u003espi-\u003edev, \"Failed to read capability\\n\");\n+\t\tgoto phy_exit;\n+\t}\n+\n \tret = devm_request_threaded_irq(\u0026tc6-\u003espi-\u003edev, tc6-\u003espi-\u003eirq,\n \t\t\t\t\toa_tc6_macphy_isr,\n \t\t\t\t\toa_tc6_macphy_threaded_irq,\ndiff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c\nnew file mode 100644\nindex 00000000000000..0cde5545ee27a0\n--- /dev/null\n+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c\n@@ -0,0 +1,70 @@\n+// SPDX-License-Identifier: GPL-2.0+\n+/*\n+ * Support for hardware timestamping feature for OPEN Alliance\n+ * 10BASE‑T1x MAC‑PHY Serial Interface framework\n+ *\n+ * Author: Selva Rajagopal \u003cselvamani.rajagopal@onsemi.com\u003e\n+ */\n+\n+#include \u003clinux/hrtimer.h\u003e\n+#include \u003clinux/irq.h\u003e\n+#include \u003clinux/irqdomain.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/phylink.h\u003e\n+#include \u003clinux/spi/spi.h\u003e\n+#include \u003clinux/oa_tc6.h\u003e\n+#include \u003clinux/net_tstamp.h\u003e\n+#include \u003clinux/ptp_clock_kernel.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/ktime.h\u003e\n+#include \u003clinux/errno.h\u003e\n+\n+#include \"oa_tc6_std_def.h\"\n+\n+/**\n+ * oa_tc6_ptp_register - Registers clock related callbacks\n+ * @tc6: oa_tc6 struct.\n+ * @info: Describes a PTP hardware clock\n+ *\n+ * Description: Vendors are expected to set the hardware timestamp\n+ * related callbacks before calling this function.\n+ *\n+ * Return: 0 on success otherwise failed.\n+ *\n+ */\n+int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info)\n+{\n+\t/* Not supporting hardware timestamp isn't an error */\n+\tif (!tc6-\u003ehw_tstamp_supported)\n+\t\treturn 0;\n+\n+\tsnprintf(info-\u003ename, sizeof(info-\u003ename), \"%s\",\n+\t\t \"OA TC6 PTP clock\");\n+\ttc6-\u003eptp_clock = ptp_clock_register(info, \u0026tc6-\u003espi-\u003edev);\n+\tif (IS_ERR(tc6-\u003eptp_clock)) {\n+\t\tdev_err(\u0026tc6-\u003espi-\u003edev, \"Registration of %s failed\",\n+\t\t\tinfo-\u003ename);\n+\t\treturn -EFAULT;\n+\t}\n+\tdev_info(\u0026tc6-\u003espi-\u003edev, \"%s registered. index %d\", info-\u003ename,\n+\t\t ptp_clock_index(tc6-\u003eptp_clock));\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_ptp_register);\n+\n+/**\n+ * oa_tc6_ptp_unregister - Unregisters clock related callbacks\n+ * @tc6: oa_tc6 struct.\n+ */\n+void oa_tc6_ptp_unregister(struct oa_tc6 *tc6)\n+{\n+\tif (tc6-\u003eptp_clock)\n+\t\tptp_clock_unregister(tc6-\u003eptp_clock);\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_ptp_unregister);\n+\n+MODULE_DESCRIPTION(\"OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib\");\n+MODULE_AUTHOR(\"Selva Rajagopal \u003cselvamani.rajagopal@onsemi.com\u003e\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h\nnew file mode 100644\nindex 00000000000000..3781503be7f562\n--- /dev/null\n+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h\n@@ -0,0 +1,191 @@\n+/* SPDX-License-Identifier: GPL-2.0+ */\n+/*\n+ * Register and driver related definitions to support\n+ * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework.\n+ *\n+ * Author: Selva Rajagopal \u003cselvamani.rajagopal@onsemi.com\u003e\n+ */\n+\n+#ifndef OA_TC6_STD_DEF_H\n+#define OA_TC6_STD_DEF_H\n+\n+#include \u003clinux/ptp_clock_kernel.h\u003e\n+#include \u003clinux/net_tstamp.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/spi/spi.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/sched.h\u003e\n+#include \u003clinux/wait.h\u003e\n+#include \u003clinux/phy.h\u003e\n+\n+/* OPEN Alliance TC6 registers */\n+/* Standard Capabilities Register */\n+#define OA_TC6_REG_STDCAP\t\t\t0x0002\n+#define STDCAP_DIRECT_PHY_REG_ACCESS\t\tBIT(8)\n+#define STDCAP_FRAME_TIMESTAMP_CAPABILITY\tBIT(6)\n+\n+/* Reset Control and Status Register */\n+#define OA_TC6_REG_RESET\t\t\t0x0003\n+#define RESET_SWRESET\t\t\t\tBIT(0)\n+\n+/* Configuration Register #0 */\n+#define OA_TC6_REG_CONFIG0\t\t\t0x0004\n+#define CONFIG0_SYNC\t\t\t\tBIT(15)\n+#define CONFIG0_ZARFE_ENABLE\t\t\tBIT(12)\n+#define CONFIG0_FTSE_ENABLE\t\t\tBIT(7)\n+#define CONFIG0_FTSS_64BIT_TSTAMP\t\tBIT(6)\n+\n+/* Status Register #0 */\n+#define OA_TC6_REG_STATUS0\t\t\t0x0008\n+#define STATUS0_TTSCAC\t\t\t\tBIT(10)\n+#define STATUS0_TTSCAB\t\t\t\tBIT(9)\n+#define STATUS0_TTSCAA\t\t\t\tBIT(8)\n+#define STATUS0_TTSCA_MASK\t\tGENMASK(10, 8)\n+#define STATUS0_RESETC\t\t\t\tBIT(6)\n+#define STATUS0_HEADER_ERROR\t\t\tBIT(5)\n+#define STATUS0_LOSS_OF_FRAME_ERROR\t\tBIT(4)\n+#define STATUS0_RX_BUFFER_OVERFLOW_ERROR\tBIT(3)\n+#define STATUS0_TX_PROTOCOL_ERROR\t\tBIT(0)\n+\n+/* Buffer Status Register */\n+#define OA_TC6_REG_BUFFER_STATUS\t\t0x000B\n+#define BUFFER_STATUS_TX_CREDITS_AVAILABLE\tGENMASK(15, 8)\n+#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE\tGENMASK(7, 0)\n+\n+/* Interrupt Mask Register #0 */\n+#define OA_TC6_REG_INT_MASK0\t\t\t0x000C\n+#define INT_MASK0_TTSCA_MASK\t\t\tGENMASK(10, 8)\n+#define INT_MASK0_HEADER_ERR_MASK\t\tBIT(5)\n+#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK\tBIT(4)\n+#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK\tBIT(3)\n+#define INT_MASK0_TX_PROTOCOL_ERR_MASK\t\tBIT(0)\n+#define INT_MASK0_ALL_INTERRUPTS\t\t(GENMASK(5, 0) | \\\n+\t\t\t\t\t\t GENMASK(12, 7))\n+\n+/* PHY Clause 22 registers base address and mask */\n+#define OA_TC6_PHY_STD_REG_ADDR_BASE\t\t0xFF00\n+#define OA_TC6_PHY_STD_REG_ADDR_MASK\t\t0x1F\n+\n+/* Tx timestamp capture register A (high) */\n+#define OA_TC6_REG_TTSCA_HIGH\t\t\t(0x10)\n+\n+/* Control command header */\n+#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL\tBIT(31)\n+#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ\tBIT(29)\n+#define OA_TC6_CTRL_HEADER_MEM_MAP_SELECTOR\tGENMASK(27, 24)\n+#define OA_TC6_CTRL_HEADER_ADDR\t\t\tGENMASK(23, 8)\n+#define OA_TC6_CTRL_HEADER_LENGTH\t\tGENMASK(7, 1)\n+#define OA_TC6_CTRL_HEADER_PARITY\t\tBIT(0)\n+\n+/* Data header */\n+#define OA_TC6_DATA_HEADER_DATA_NOT_CTRL\tBIT(31)\n+#define OA_TC6_DATA_HEADER_DATA_VALID\t\tBIT(21)\n+#define OA_TC6_DATA_HEADER_START_VALID\t\tBIT(20)\n+#define OA_TC6_DATA_HEADER_START_WORD_OFFSET\tGENMASK(19, 16)\n+#define OA_TC6_DATA_HEADER_END_VALID\t\tBIT(14)\n+#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET\tGENMASK(13, 8)\n+#define OA_TC6_DATA_HEADER_TSC_OFFSET\t\tGENMASK(7, 6)\n+#define OA_TC6_DATA_HEADER_PARITY\t\tBIT(0)\n+\n+/* Data footer */\n+#define OA_TC6_DATA_FOOTER_EXTENDED_STS\t\tBIT(31)\n+#define OA_TC6_DATA_FOOTER_RXD_HEADER_BAD\tBIT(30)\n+#define OA_TC6_DATA_FOOTER_CONFIG_SYNC\t\tBIT(29)\n+#define OA_TC6_DATA_FOOTER_RX_CHUNKS\t\tGENMASK(28, 24)\n+#define OA_TC6_DATA_FOOTER_DATA_VALID\t\tBIT(21)\n+#define OA_TC6_DATA_FOOTER_START_VALID\t\tBIT(20)\n+#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET\tGENMASK(19, 16)\n+#define OA_TC6_DATA_FOOTER_END_VALID\t\tBIT(14)\n+#define OA_TC6_DATA_FOOTER_RTSA_VALID\t\tBIT(7)\n+#define OA_TC6_DATA_FOOTER_RTSP_VALID\t\tBIT(6)\n+#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET\tGENMASK(13, 8)\n+#define OA_TC6_DATA_FOOTER_TX_CREDITS\t\tGENMASK(5, 1)\n+\n+#define OA_TC6_CTRL_HEADER_SIZE\t\t\t4\n+#define OA_TC6_CTRL_REG_VALUE_SIZE\t\t4\n+#define OA_TC6_CTRL_IGNORED_SIZE\t\t4\n+#define OA_TC6_CTRL_MAX_REGISTERS\t\t128\n+#define OA_TC6_CTRL_SPI_BUF_SIZE\t\t(OA_TC6_CTRL_HEADER_SIZE +\\\n+\t\t\t\t\t\t(OA_TC6_CTRL_MAX_REGISTERS *\\\n+\t\t\t\t\t\tOA_TC6_CTRL_REG_VALUE_SIZE) +\\\n+\t\t\t\t\t\tOA_TC6_CTRL_IGNORED_SIZE)\n+#define OA_TC6_CHUNK_PAYLOAD_SIZE\t\t64\n+#define OA_TC6_DATA_HEADER_SIZE\t\t\t4\n+#define OA_TC6_CHUNK_SIZE\t\t\t(OA_TC6_DATA_HEADER_SIZE +\\\n+\t\t\t\t\t\tOA_TC6_CHUNK_PAYLOAD_SIZE)\n+#define OA_TC6_MAX_TX_CHUNKS\t\t\t48\n+#define OA_TC6_SPI_DATA_BUF_SIZE\t\t(OA_TC6_MAX_TX_CHUNKS *\\\n+\t\t\t\t\t\tOA_TC6_CHUNK_SIZE)\n+#define STATUS0_RESETC_POLL_DELAY\t\t1000\n+#define STATUS0_RESETC_POLL_TIMEOUT\t\t1000000\n+\n+#define OA_TC6_TSTAMP_SZ\t\t\t8\n+\n+#define OA_TC6_TTSCA_REG_ID\t\t\t1\n+#define OA_TC6_TTSCB_REG_ID\t\t\t2\n+#define OA_TC6_TTSCC_REG_ID\t\t\t3\n+\n+/* Internal structure for MAC-PHY drivers */\n+struct oa_tc6 {\n+\tstruct net_device *netdev;\n+\tstruct phy_device *phydev;\n+\tstruct mii_bus *mdiobus;\n+\tstruct spi_device *spi;\n+\tstruct mutex spi_ctrl_lock; /* Protects spi control transfer */\n+\tspinlock_t tx_skb_lock; /* Protects tx skb handling */\n+\tvoid *spi_ctrl_tx_buf;\n+\tvoid *spi_ctrl_rx_buf;\n+\tvoid *spi_data_tx_buf;\n+\tvoid *spi_data_rx_buf;\n+\tstruct sk_buff *ongoing_tx_skb;\n+\tstruct sk_buff *waiting_tx_skb;\n+\tstruct sk_buff *rx_skb;\n+\tu16 tx_skb_offset;\n+\tu16 spi_data_tx_buf_offset;\n+\tu16 tx_credits;\n+\tu8 rx_chunks_available;\n+\tbool rx_buf_overflow;\n+\tbool int_flag;\n+\tbool disable_traffic;\n+\tstruct ptp_clock_info ptp_clock_info;\n+\tstruct hwtstamp_config ts_config;\n+\tstruct list_head tx_ts_skb_q;\n+\tstruct ptp_clock *ptp_clock;\n+\tbool hw_tstamp_supported;\n+\tbool hw_tstamp_enabled;\n+\tu32 tx_hwtstamp_pkts;\n+\tu32 tx_hwtstamp_lost;\n+\tu32 tx_hwtstamp_err;\n+\tint vend1_mms;\n+\tu8 tx_ts_idx;\n+};\n+\n+enum oa_tc6_header_type {\n+\tOA_TC6_CTRL_HEADER,\n+\tOA_TC6_DATA_HEADER,\n+};\n+\n+enum oa_tc6_register_op {\n+\tOA_TC6_CTRL_REG_READ = 0,\n+\tOA_TC6_CTRL_REG_WRITE = 1,\n+};\n+\n+enum oa_tc6_data_valid_info {\n+\tOA_TC6_DATA_INVALID,\n+\tOA_TC6_DATA_VALID,\n+};\n+\n+enum oa_tc6_data_start_valid_info {\n+\tOA_TC6_DATA_START_INVALID,\n+\tOA_TC6_DATA_START_VALID,\n+};\n+\n+enum oa_tc6_data_end_valid_info {\n+\tOA_TC6_DATA_END_INVALID,\n+\tOA_TC6_DATA_END_VALID,\n+};\n+\n+int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);\n+\n+#endif /* OA_TC6_STD_DEF_H */\n+\ndiff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c\nnew file mode 100644\nindex 00000000000000..f80f1a4916a3e0\n--- /dev/null\n+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c\n@@ -0,0 +1,205 @@\n+// SPDX-License-Identifier: GPL-2.0+\n+/*\n+ * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework\n+ *\n+ * Author: Selva Rajagopal \u003cselvamani.rajagopal@onsemi.com\u003e\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/iopoll.h\u003e\n+#include \u003clinux/mdio.h\u003e\n+#include \u003clinux/phy.h\u003e\n+#include \u003clinux/oa_tc6.h\u003e\n+\n+#include \"oa_tc6_std_def.h\"\n+\n+static int oa_tc6_set_hwtstamp_settings(struct oa_tc6 *tc6)\n+{\n+\tu32 cfg0, irqm, status0;\n+\tint ret;\n+\n+\tret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, \u0026cfg0);\n+\tif (ret) {\n+\t\tdev_err(\u0026tc6-\u003espi-\u003edev, \"Failed to read CFG0 register\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tret = oa_tc6_read_register(tc6, OA_TC6_REG_INT_MASK0, \u0026irqm);\n+\tif (ret) {\n+\t\tdev_err(\u0026tc6-\u003espi-\u003edev, \"failed to read IRQM register\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\t/* Hardware timestamp feature supports 64-bit timestamp only */\n+\tif (tc6-\u003ets_config.tx_type == HWTSTAMP_TX_ON ||\n+\t    tc6-\u003ets_config.rx_filter == HWTSTAMP_FILTER_ALL)\n+\t\tcfg0 |= (CONFIG0_FTSE_ENABLE |\n+\t\t\t CONFIG0_FTSS_64BIT_TSTAMP);\n+\telse\n+\t\tcfg0 \u0026= ~CONFIG0_FTSE_ENABLE;\n+\n+\tif (tc6-\u003ets_config.tx_type == HWTSTAMP_TX_ON)\n+\t\tirqm \u0026= ~INT_MASK0_TTSCA_MASK;\n+\telse\n+\t\tirqm |= INT_MASK0_TTSCA_MASK;\n+\n+\t/* Clear timestamp related IRQs */\n+\tstatus0 = STATUS0_TTSCA_MASK;\n+\tret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, status0);\n+\tif (ret) {\n+\t\tdev_err(\u0026tc6-\u003espi-\u003edev, \"failed to write STATUS0 register\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tret = oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, irqm);\n+\tif (ret) {\n+\t\tdev_err(\u0026tc6-\u003espi-\u003edev, \"failed to write IRQM register\\n\");\n+\t\tgoto out;\n+\t}\n+\n+\tret = oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, cfg0);\n+\tif (ret) {\n+\t\tdev_err(\u0026tc6-\u003espi-\u003edev, \"failed to write CFG0 register\\n\");\n+\t\tgoto out;\n+\t}\n+\tif (cfg0 \u0026 CONFIG0_FTSE_ENABLE)\n+\t\ttc6-\u003ehw_tstamp_enabled = true;\n+\telse\n+\t\ttc6-\u003ehw_tstamp_enabled = false;\n+out:\n+\treturn ret;\n+}\n+\n+/**\n+ * oa_tc6_hwtstamp_get - gets hardware timestamp config\n+ * @tc6: oa_tc6 struct.\n+ * @cfg: kernel copy of hardware timestamp config\n+ */\n+void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6,\n+\t\t\t struct kernel_hwtstamp_config *cfg)\n+{\n+\thwtstamp_config_to_kernel(cfg, \u0026tc6-\u003ets_config);\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_get);\n+\n+/**\n+ * oa_tc6_hwtstamp_set - sets hardware timestamp config\n+ * @tc6: oa_tc6 struct.\n+ * @cfg: kernel copy of hardware timestamp config\n+ *\n+ * Return: 0 on success otherwise failed.\n+ */\n+int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,\n+\t\t\tstruct kernel_hwtstamp_config *cfg)\n+{\n+\tif (!netif_running(tc6-\u003enetdev))\n+\t\treturn -EIO;\n+\n+\tif (!tc6-\u003ehw_tstamp_supported)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tswitch (cfg-\u003etx_type) {\n+\tcase HWTSTAMP_TX_OFF:\n+\tcase HWTSTAMP_TX_ON:\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -ERANGE;\n+\t}\n+\n+\tswitch (cfg-\u003erx_filter) {\n+\tcase HWTSTAMP_FILTER_NONE:\n+\tcase HWTSTAMP_FILTER_ALL:\n+\tcase HWTSTAMP_FILTER_SOME:\n+\tcase HWTSTAMP_FILTER_PTP_V1_L4_EVENT:\n+\tcase HWTSTAMP_FILTER_PTP_V1_L4_SYNC:\n+\tcase HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:\n+\tcase HWTSTAMP_FILTER_PTP_V2_L4_EVENT:\n+\tcase HWTSTAMP_FILTER_PTP_V2_L4_SYNC:\n+\tcase HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:\n+\tcase HWTSTAMP_FILTER_PTP_V2_L2_EVENT:\n+\tcase HWTSTAMP_FILTER_PTP_V2_L2_SYNC:\n+\tcase HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:\n+\tcase HWTSTAMP_FILTER_PTP_V2_EVENT:\n+\tcase HWTSTAMP_FILTER_PTP_V2_SYNC:\n+\tcase HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:\n+\tcase HWTSTAMP_FILTER_NTP_ALL:\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -ERANGE;\n+\t}\n+\thwtstamp_config_from_kernel(\u0026tc6-\u003ets_config, cfg);\n+\n+\t/* Supports timestamping all traffic */\n+\tif (cfg-\u003erx_filter != HWTSTAMP_FILTER_NONE)\n+\t\ttc6-\u003ets_config.rx_filter = HWTSTAMP_FILTER_ALL;\n+\treturn oa_tc6_set_hwtstamp_settings(tc6);\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_set);\n+\n+/**\n+ * oa_tc6_get_ts_stats - Provides timestamping stats\n+ * @tc6: oa_tc6 struct.\n+ * @stats: ethtool data structure to fill in\n+ */\n+void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,\n+\t\t\t struct ethtool_ts_stats *stats)\n+{\n+\tstats-\u003epkts = tc6-\u003etx_hwtstamp_pkts;\n+\tstats-\u003eerr = tc6-\u003etx_hwtstamp_err;\n+\tstats-\u003elost = tc6-\u003etx_hwtstamp_lost;\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_get_ts_stats);\n+\n+int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)\n+{\n+\tstruct kernel_hwtstamp_config kcfg;\n+\tstruct hwtstamp_config tscfg;\n+\tint ret = 0;\n+\n+\tif (!tc6-\u003ehw_tstamp_supported)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (cmd == SIOCSHWTSTAMP) {\n+\t\tif (copy_from_user(\u0026tscfg, rq-\u003eifr_data,\n+\t\t\t\t   sizeof(tscfg)))\n+\t\t\treturn -EFAULT;\n+\n+\t\tif (tscfg.flags)\n+\t\t\treturn -EINVAL;\n+\t\thwtstamp_config_to_kernel(\u0026kcfg, \u0026tscfg);\n+\t\tret = oa_tc6_hwtstamp_set(tc6, \u0026kcfg);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\tif (copy_to_user(rq-\u003eifr_data, \u0026tc6-\u003ets_config,\n+\t\t\t sizeof(tc6-\u003ets_config)))\n+\t\tret = -EFAULT;\n+\treturn ret;\n+}\n+\n+/**\n+ * oa_tc6_get_ts_info - Provides timestamp info for ethtool\n+ * @tc6: oa_tc6 struct.\n+ * @info: ethtool timestamping info structure\n+ *\n+ * Return: 0 on success otherwise failed.\n+ */\n+int oa_tc6_get_ts_info(struct oa_tc6 *tc6,\n+\t\t       struct kernel_ethtool_ts_info *info)\n+{\n+\tif (!tc6-\u003eptp_clock)\n+\t\treturn ethtool_op_get_ts_info(tc6-\u003enetdev, info);\n+\n+\tinfo-\u003eso_timestamping = SOF_TIMESTAMPING_RAW_HARDWARE |\n+\t\t\t\tSOF_TIMESTAMPING_TX_HARDWARE |\n+\t\t\t\tSOF_TIMESTAMPING_RX_HARDWARE;\n+\tinfo-\u003ephc_index = ptp_clock_index(tc6-\u003eptp_clock);\n+\tinfo-\u003etx_types = BIT(HWTSTAMP_TX_ON);\n+\tinfo-\u003erx_filters = BIT(HWTSTAMP_FILTER_ALL);\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(oa_tc6_get_ts_info);\n+\n+MODULE_DESCRIPTION(\"OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib\");\n+MODULE_AUTHOR(\"Selva Rajagopal \u003cselvamani.rajagopal@onsemi.com\u003e\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/ethernet/onsemi/Kconfig b/drivers/net/ethernet/onsemi/Kconfig\nnew file mode 100644\nindex 00000000000000..8d72194151ea79\n--- /dev/null\n+++ b/drivers/net/ethernet/onsemi/Kconfig\n@@ -0,0 +1,21 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+#\n+# onsemi network device configuration\n+#\n+\n+config NET_VENDOR_ONSEMI\n+\tbool \"onsemi network devices\"\n+\thelp\n+\t  If you have a network card belonging to this class, say Y.\n+\n+\t  Note that the answer to this question doesn't directly affect the\n+\t  kernel: saying N will just cause the configurator to skip all\n+\t  the questions about onsemi ethernet devices. If you say Y, you\n+\t  will be asked for your specific card in the following questions.\n+\n+if NET_VENDOR_ONSEMI\n+\n+source \"drivers/net/ethernet/onsemi/s2500/Kconfig\"\n+\n+endif # NET_VENDOR_ONSEMI\n+\ndiff --git a/drivers/net/ethernet/onsemi/Makefile b/drivers/net/ethernet/onsemi/Makefile\nnew file mode 100644\nindex 00000000000000..f3d4eb154313c6\n--- /dev/null\n+++ b/drivers/net/ethernet/onsemi/Makefile\n@@ -0,0 +1,7 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+#\n+# Makefile for the onsemi network device drivers.\n+#\n+\n+obj-$(CONFIG_S2500_MACPHY) += s2500/\n+\ndiff --git a/drivers/net/ethernet/onsemi/s2500/Kconfig b/drivers/net/ethernet/onsemi/s2500/Kconfig\nnew file mode 100644\nindex 00000000000000..141fbaf1b64187\n--- /dev/null\n+++ b/drivers/net/ethernet/onsemi/s2500/Kconfig\n@@ -0,0 +1,19 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+#\n+# onsemi S2500 Driver Support\n+#\n+\n+config S2500_MACPHY\n+\ttristate \"S2500 support\"\n+\tdepends on SPI\n+\timply NET_SELFTESTS\n+\tselect NCN26000_PHY\n+\tselect OA_TC6\n+\thelp\n+\t  Support for the onsemi TS2500 MACPHY Ethernet chip.\n+\t  It works under the framework that conform to OPEN Alliance\n+\t  10BASE-T1x Serial Interface specification.\n+\n+\t  To compile this driver as a module, choose M here. The module will be\n+\t  called s2500.\n+\ndiff --git a/drivers/net/ethernet/onsemi/s2500/Makefile b/drivers/net/ethernet/onsemi/s2500/Makefile\nnew file mode 100644\nindex 00000000000000..61ec705cdf9f1b\n--- /dev/null\n+++ b/drivers/net/ethernet/onsemi/s2500/Makefile\n@@ -0,0 +1,7 @@\n+# SPDX-License-Identifier: GPL-2.0-only\n+#\n+# Makefile for the onsemi network device drivers.\n+#\n+obj-$(CONFIG_S2500_MACPHY) := s2500.o\n+s2500-objs := s2500_main.o s2500_ethtool.o s2500_ptp.o\n+\ndiff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c\nnew file mode 100644\nindex 00000000000000..0b7ef490d7cac3\n--- /dev/null\n+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c\n@@ -0,0 +1,360 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * Copyright 2026 Semiconductor Components Industries, LLC (\"onsemi\").\n+ * onsemi's S2500 10BASE-T1S MAC-PHY driver\n+ */\n+\n+#include \u003clinux/ethtool.h\u003e\n+#include \u003cnet/selftests.h\u003e\n+#include \u003clinux/phy.h\u003e\n+\n+#include \"s2500_hw_def.h\"\n+\n+#define S2500_NUM_REGS\t\t\t38\n+#define S2500_REGDUMP_LEN\t\t(sizeof(u32) * (S2500_NUM_REGS * 2))\n+\n+#define S2500_NUM_MAC_STATS\t\t21\n+#define S2500_NUM_RMON_STATS\t\t12\n+\n+struct s2500_reg_map {\n+\tu16 reg;\n+\tu16 mms;\n+};\n+\n+static const struct s2500_reg_map s2500_reg_map[S2500_NUM_REGS] = {\n+\t{ S2500_REG_VS_ONIPVER,      OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_TWEAKS1,      OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_PLCAEXT,      OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_PMATUNE0,     OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_PMATUNE1,     OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_T1SWUPTUNE,   OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_HDD,          OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_DCQ_TOID,     OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_DCQ_SQI,      OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_DCQ_SQI_PLUS, OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_HDD_TUNE1,    OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_HDD_TUNE2,    OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_HDD_TUNE3,    OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_WS_STATUS_0,  OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_WS_CTRL_0,    OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_WS_STATUS_1,  OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_WS_CTRL_1,    OA_TC6_PHY_C45_VS_PLCA_MMS4 },\n+\t{ S2500_REG_VS_MIIMIRQE,     OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_MIIMIRQS,     OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_DIOCFG0,      OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_CHIPID,       OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_TWEAKS2,      OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_MACID0,       OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_MACID1,       OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_NVM_HEALTH,   OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_PTP_SEC,      OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_PTPNSEC,      OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_PTP_SETSEC,   OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_SETNSEC,      OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_PTP_ADJ,      OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_CMPCTL,       OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_CMPSEC,       OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_CMPNSEC,      OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_CMPPER,       OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_CAPCTL,       OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_CAPSEC,       OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_CAPNSEC,      OA_TC6_PHY_C45_VS_MMS12 },\n+\t{ S2500_REG_VS_BCNCNT,       OA_TC6_PHY_C45_VS_MMS12 },\n+};\n+\n+enum s2500_stat_idx {\n+\tS2500_MSTOCTECTSTX_IDX = 0,\n+\tS2500_MSTFRAMESTXOK_IDX,\n+\tS2500_MSTBCASTTXOK_IDX,\n+\tS2500_MSTMCASTTXOK_IDX,\n+\tS2500_MSTFRAMESTX64_IDX,\n+\tS2500_MSTFRAMESTX65_IDX,\n+\tS2500_MSTFRAMESTX128_IDX,\n+\tS2500_MSTFRAMESTX256_IDX,\n+\tS2500_MSTFRAMESTX512_IDX,\n+\tS2500_MSTFRAMESTX1024_IDX,\n+\tS2500_MSTTXUNDEFLOW_IDX,\n+\tS2500_MSTSINGLECOL_IDX,\n+\tS2500_MSTMULTICOL_IDX,\n+\tS2500_MSTEXCESSCOL_IDX,\n+\tS2500_MSTDEFERREDTX_IDX,\n+\tS2500_MSTCRSERR_IDX,\n+\tS2500_MSTOCTECTSRX_IDX,\n+\tS2500_MSTFRAMESRXOK_IDX,\n+\tS2500_MSTBCASTRXOK_IDX,\n+\tS2500_MSTMCASTRXOK_IDX,\n+\tS2500_MSTFRAMESRX64_IDX,\n+\tS2500_MSTFRAMESRX65_IDX,\n+\tS2500_MSTFRAMESRX128_IDX,\n+\tS2500_MSTFRAMESRX256_IDX,\n+\tS2500_MSTFRAMESRX512_IDX,\n+\tS2500_MSTFRAMESRX1024_IDX,\n+\tS2500_MSTRUNTSERR_IDX,\n+\tS2500_MSTRXTOOLONG_IDX,\n+\tS2500_MSTFCSERRS_IDX,\n+\tS2500_MSTSYMBOLERRS_IDX,\n+\tS2500_MSTALIGNERRS_IDX,\n+\tS2500_MSTRXOVERFLOW_IDX,\n+\tS2500_MSTRXDROPPED_IDX,\n+};\n+\n+static const char s2500_mac_stat_strings[][ETH_GSTRING_LEN] = {\n+\t\"tx_bytes\",\n+\t\"tx_frames\",\n+\t\"tx_broadcast_frames\",\n+\t\"tx_multicast_frames\",\n+\t\"tx_underflow_errors\",\n+\t\"tx_single_collisions\",\n+\t\"tx_multiple_collisions\",\n+\t\"tx_excessive_collisions\",\n+\t\"tx_deferred_frames\",\n+\t\"tx_carrier_sense_errors\",\n+\t\"rx_bytes\",\n+\t\"rx_frames\",\n+\t\"rx_broadcast_frames\",\n+\t\"rx_multicast_frames\",\n+\t\"rx_runts\",\n+\t\"rx_oversize_frames\",\n+\t\"rx_crc_errors\",\n+\t\"rx_symbol_errors\",\n+\t\"rx_alignment_errors\",\n+\t\"rx_busy_drops\",\n+\t\"rx_mismatch_drops\",\n+};\n+\n+static const u8 s2500_mac_stat_map[S2500_NUM_MAC_STATS] = {\n+\tS2500_MSTOCTECTSTX_IDX,\n+\tS2500_MSTFRAMESTXOK_IDX,\n+\tS2500_MSTBCASTTXOK_IDX,\n+\tS2500_MSTMCASTTXOK_IDX,\n+\tS2500_MSTTXUNDEFLOW_IDX,\n+\tS2500_MSTSINGLECOL_IDX,\n+\tS2500_MSTMULTICOL_IDX,\n+\tS2500_MSTEXCESSCOL_IDX,\n+\tS2500_MSTDEFERREDTX_IDX,\n+\tS2500_MSTCRSERR_IDX,\n+\tS2500_MSTOCTECTSRX_IDX,\n+\tS2500_MSTFRAMESRXOK_IDX,\n+\tS2500_MSTBCASTRXOK_IDX,\n+\tS2500_MSTMCASTRXOK_IDX,\n+\tS2500_MSTRUNTSERR_IDX,\n+\tS2500_MSTRXTOOLONG_IDX,\n+\tS2500_MSTFCSERRS_IDX,\n+\tS2500_MSTSYMBOLERRS_IDX,\n+\tS2500_MSTALIGNERRS_IDX,\n+\tS2500_MSTRXOVERFLOW_IDX,\n+\tS2500_MSTRXDROPPED_IDX,\n+};\n+\n+static const u8 s2500_rmon_stat_map[S2500_NUM_RMON_STATS] = {\n+\tS2500_MSTFRAMESTX64_IDX,\n+\tS2500_MSTFRAMESTX65_IDX,\n+\tS2500_MSTFRAMESTX128_IDX,\n+\tS2500_MSTFRAMESTX256_IDX,\n+\tS2500_MSTFRAMESTX512_IDX,\n+\tS2500_MSTFRAMESTX1024_IDX,\n+\tS2500_MSTFRAMESRX64_IDX,\n+\tS2500_MSTFRAMESRX65_IDX,\n+\tS2500_MSTFRAMESRX128_IDX,\n+\tS2500_MSTFRAMESRX256_IDX,\n+\tS2500_MSTFRAMESRX512_IDX,\n+\tS2500_MSTFRAMESRX1024_IDX,\n+};\n+\n+static const struct ethtool_rmon_hist_range s2500_rmon_hist_ranges[] = {\n+\t{ 64, 64 },\n+\t{ 65, 127 },\n+\t{ 128, 255 },\n+\t{ 256, 511 },\n+\t{ 512, 1023 },\n+\t{ 1024, 2000 },\n+\t{ },\n+};\n+\n+#define S2500_MAC_STATS_LEN ARRAY_SIZE(s2500_mac_stat_strings)\n+static_assert(S2500_MAC_STATS_LEN == S2500_NUM_MAC_STATS);\n+static_assert(ARRAY_SIZE(s2500_mac_stat_map) == S2500_NUM_MAC_STATS);\n+static_assert(ARRAY_SIZE(s2500_rmon_stat_map) == S2500_NUM_RMON_STATS);\n+\n+#define STAT_REG_OFFSET(x) ((S2500_REG_MAC_ST##x) - \\\n+\t\t\t   S2500_REG_MAC_FIRST_STAT)\n+\n+static void s2500_update_stats(struct s2500_info *priv)\n+{\n+\tu64 *data = priv-\u003estats_data;\n+\tu32 *regs, *rptr;\n+\tint ret;\n+\n+\tregs = kmalloc_array(S2500_NUM_STAT_REGS, sizeof(u32), GFP_KERNEL);\n+\tif (!regs)\n+\t\treturn;\n+\n+\tret = oa_tc6_read_registers_mms(priv-\u003etc6, S2500_REG_MAC_STOCTECTSTXL,\n+\t\t\t\t\tOA_TC6_PHY_C45_MAC_MMS1, regs,\n+\t\t\t\t\tS2500_NUM_STAT_REGS);\n+\tif (ret)\n+\t\tgoto out;\n+\n+\trptr = regs;\n+\n+\t/* TX bytes is a 64-bit register that spans over two 32-bit regs\n+\t * note: HW does auto-freeze when reading LSB and un-freeze on MSB\n+\t */\n+\t*(data++) += ((u64)*rptr) | (((u64)*(rptr + 1)) \u003c\u003c 32);\n+\n+\t/* run until the next 64-bit register */\n+\tfor (rptr += 2; (rptr - regs) \u003c STAT_REG_OFFSET(OCTECTSRXL); ++rptr)\n+\t\t*(data++) += *rptr;\n+\n+\t/* RX bytes is a 64-bit register that spans over two 32-bit regs\n+\t * note: HW does auto-freeze when reading LSB and un-freeze on MSB\n+\t */\n+\t*(data++) += ((u64)*rptr) | (((u64)*(rptr + 1)) \u003c\u003c 32);\n+\n+\tfor (rptr += 2; (rptr - regs) \u003c S2500_NUM_STAT_REGS; ++rptr)\n+\t\t*(data++) += *rptr;\n+out:\n+\tkfree(regs);\n+}\n+\n+static void s2500_get_drvinfo(struct net_device *ndev,\n+\t\t\t      struct ethtool_drvinfo *info)\n+{\n+\tstrscpy(info-\u003edriver, DRV_NAME, sizeof(info-\u003edriver));\n+\tstrscpy(info-\u003ebus_info, dev_name(\u0026ndev-\u003edev),\n+\t\tsizeof(info-\u003ebus_info));\n+}\n+\n+static int s2500_get_sset_count(struct net_device *ndev, int sset)\n+{\n+\tswitch (sset) {\n+\tcase ETH_SS_STATS:\n+\t\treturn S2500_MAC_STATS_LEN;\n+\tcase ETH_SS_TEST:\n+\t\treturn net_selftest_get_count();\n+\tdefault:\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+}\n+\n+static void s2500_get_strings(struct net_device *ndev, u32 stringset,\n+\t\t\t      u8 *buf)\n+{\n+\tswitch (stringset) {\n+\tcase ETH_SS_STATS:\n+\t\tmemcpy(buf, s2500_mac_stat_strings,\n+\t\t       S2500_MAC_STATS_LEN * ETH_GSTRING_LEN);\n+\t\tbreak;\n+\tcase ETH_SS_TEST:\n+\t\tnet_selftest_get_strings(buf);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void s2500_get_ethtool_stats(struct net_device *ndev,\n+\t\t\t\t    struct ethtool_stats *stats, u64 *data)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\tint i;\n+\n+\ts2500_update_stats(priv);\n+\tfor (i = 0; i \u003c S2500_NUM_MAC_STATS; i++)\n+\t\tdata[i] = priv-\u003estats_data[s2500_mac_stat_map[i]];\n+}\n+\n+static void s2500_rmon_stats(struct net_device *ndev,\n+\t\t\t     struct ethtool_rmon_stats *rmon_stats,\n+\t\t\t     const struct ethtool_rmon_hist_range **ranges)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\tu8 offset;\n+\tint i;\n+\n+\ts2500_update_stats(priv);\n+\n+\tmemset(rmon_stats, 0, sizeof(*rmon_stats));\n+\trmon_stats-\u003esrc = ETHTOOL_MAC_STATS_SRC_AGGREGATE;\n+\trmon_stats-\u003eundersize_pkts = priv-\u003estats_data[S2500_MSTRUNTSERR_IDX];\n+\trmon_stats-\u003eoversize_pkts = priv-\u003estats_data[S2500_MSTRXTOOLONG_IDX];\n+\trmon_stats-\u003efragments = priv-\u003estats_data[S2500_MSTFCSERRS_IDX];\n+\trmon_stats-\u003ejabbers = priv-\u003estats_data[S2500_MSTALIGNERRS_IDX];\n+\n+\tfor (i = 0; i \u003c 6; i++) {\n+\t\toffset = s2500_rmon_stat_map[i];\n+\t\trmon_stats-\u003ehist_tx[i] = priv-\u003estats_data[offset];\n+\t}\n+\n+\tfor (i = 0; i \u003c 6; i++) {\n+\t\toffset = s2500_rmon_stat_map[i + 6];\n+\t\trmon_stats-\u003ehist[i] = priv-\u003estats_data[offset];\n+\t}\n+\n+\t*ranges = s2500_rmon_hist_ranges;\n+}\n+\n+static int s2500_get_ts_info(struct net_device *ndev,\n+\t\t\t     struct kernel_ethtool_ts_info *ts_info)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\n+\treturn oa_tc6_get_ts_info(priv-\u003etc6, ts_info);\n+}\n+\n+static int s2500_get_regs_len(struct net_device *dev)\n+{\n+\treturn S2500_REGDUMP_LEN;\n+}\n+\n+static void s2500_get_regs(struct net_device *ndev,\n+\t\t\t   struct ethtool_regs *regs, void *p)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\tu32 *pbuff = (u32 *)p;\n+\tu16 reg, mms;\n+\tint ret = 0;\n+\tu32 val;\n+\tint i;\n+\n+\tregs-\u003eversion = 0;\n+\tmemset(p, 0, S2500_REGDUMP_LEN);\n+\n+\tif (!netif_running(ndev))\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c S2500_NUM_REGS; i++) {\n+\t\tval = 0;\n+\t\treg = s2500_reg_map[i].reg;\n+\t\tmms = s2500_reg_map[i].mms;\n+\t\tret = oa_tc6_read_register_mms(priv-\u003etc6, reg, mms,\n+\t\t\t\t\t       \u0026val);\n+\t\tif (ret)\n+\t\t\tcontinue;\n+\t\t*pbuff++ = cpu_to_be32(((u32)mms \u003c\u003c 16) | reg);\n+\t\t*pbuff++ = cpu_to_be32(val);\n+\t}\n+}\n+\n+static void s2500_get_ts_stats(struct net_device *ndev,\n+\t\t\t       struct ethtool_ts_stats *ts_stats)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\n+\toa_tc6_get_ts_stats(priv-\u003etc6, ts_stats);\n+}\n+\n+const struct ethtool_ops s2500_ethtool_ops = {\n+\t.get_drvinfo = s2500_get_drvinfo,\n+\t.get_link = ethtool_op_get_link,\n+\t.get_link_ksettings = phy_ethtool_get_link_ksettings,\n+\t.set_link_ksettings = phy_ethtool_set_link_ksettings,\n+\t.get_ts_stats = s2500_get_ts_stats,\n+\t.get_sset_count = s2500_get_sset_count,\n+\t.get_strings = s2500_get_strings,\n+\t.get_ethtool_stats = s2500_get_ethtool_stats,\n+\t.get_rmon_stats = s2500_rmon_stats,\n+\t.get_ts_info = s2500_get_ts_info,\n+\t.get_regs_len = s2500_get_regs_len,\n+\t.get_regs = s2500_get_regs,\n+\t.self_test = net_selftest,\n+};\n+\ndiff --git a/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h b/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h\nnew file mode 100644\nindex 00000000000000..2d9c0b3ad39bcb\n--- /dev/null\n+++ b/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h\n@@ -0,0 +1,223 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright 2026 Semiconductor Components Industries, LLC (\"onsemi\").\n+ * onsemi's S2500 10BASE-T1S MAC-PHY driver\n+ */\n+\n+#ifndef S2500_HW_DEF_H\n+#define S2500_HW_DEF_H\n+\n+#include \u003clinux/hrtimer.h\u003e\n+#include \u003clinux/irq.h\u003e\n+#include \u003clinux/irqdomain.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/phylink.h\u003e\n+#include \u003clinux/spi/spi.h\u003e\n+#include \u003clinux/oa_tc6.h\u003e\n+#include \u003clinux/net_tstamp.h\u003e\n+#include \u003clinux/ptp_clock_kernel.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/ktime.h\u003e\n+#include \u003clinux/errno.h\u003e\n+\n+#define DRV_NAME\t\t\t\"s2500\"\n+\n+#define S2500_N_MCAST_FILTERS\t\t3\n+\n+/* SPI OID and model register */\n+#define S2500_REG_SPI_PHYID\t\t0x1\n+\n+#define S2500_SPI_PHYID_OUI_SHIFT\t10\n+#define S2500_SPI_PHYID_OUI_MASK\tGENMASK(31, S2500_SPI_PHYID_OUI_SHIFT)\n+#define S2500_SPI_PHYID_OUI_MASK0\tGENMASK(21, 16)\n+#define S2500_SPI_PHYID_OUI_MASK1\tGENMASK(15, 8)\n+#define S2500_SPI_PHYID_OUI_MASK2\tGENMASK(7, 0)\n+/* SPI configuration register #0 */\n+#define S2500_REG_SPI_CFG0\t\t0x4\n+\n+#define S2500_SPI_CFG0_SYNC_BIT\t\tBIT(15)\n+#define S2500_SPI_CFG0_TXCTHRESH_SHIFT\t10\n+#define S2500_SPI_CFG0_RXCTE_BIT\tBIT(8)\n+#define S2500_SPI_CFG0_FTSS_64_BIT\tBIT(6)\n+#define S2500_SPI_CFG0_CPS_SHIFT\t0\n+\n+#define S2500_TXCTHRESH_8\t\t0x2\n+\n+#define S2500_CPS_64\t\t\t0x6\n+\n+/* SPI status register #0 */\n+#define S2500_REG_SPI_ST0\t\t0x8\n+\n+#define S2500_SPI_ST0_CDPE_BIT\t\tBIT(12)\n+#define S2500_SPI_ST0_TXFCSE_BIT\tBIT(11)\n+#define S2500_SPI_ST0_TTSCAC_BIT\tBIT(10)\n+#define S2500_SPI_ST0_TTSCAB_BIT\tBIT(9)\n+#define S2500_SPI_ST0_TTSCAA_BIT\tBIT(8)\n+#define S2500_SPI_ST0_RESETC_BIT\tBIT(6)\n+#define S2500_SPI_ST0_HDRE_BIT\t\tBIT(5)\n+#define S2500_SPI_ST0_LOFE_BIT\t\tBIT(4)\n+#define S2500_SPI_ST0_RXBOE_BIT\t\tBIT(3)\n+#define S2500_SPI_ST0_TXBUE_BIT\t\tBIT(2)\n+#define S2500_SPI_ST0_TXBOE_BIT\t\tBIT(1)\n+#define S2500_SPI_ST0_TXPE_BIT\t\tBIT(0)\n+\n+/* SPI IRQ enable register #0 (use the S2500_SPI_ST0_*_BIT constants) */\n+#define S2500_REG_SPI_IRQM0\t\t0xc\n+\n+/* SPI buffer status register */\n+#define S2500_REG_SPI_BUFST\t\t0xb\n+\n+#define S2500_REG_MAC_CTRL\t\t0x0\n+\n+#define S2500_MAC_CTRL_MCSF_BIT\t\tBIT(18)\n+#define S2500_MAC_CTRL_ADRF_BIT\t\tBIT(16)\n+#define S2500_MAC_CTRL_FCSA_BIT\t\tBIT(8)\n+#define S2500_MAC_CTRL_TXEN_BIT\t\tBIT(1)\n+#define S2500_MAC_CTRL_RXEN_BIT\t\tBIT(0)\n+\n+/* MAC address filter registers */\n+#define S2500_REG_MAC_ADDRFILTL(n)\t(16 + 2 * (n))\n+#define S2500_REG_MAC_ADDRFILTH(n)\t(17 + 2 * (n))\n+#define S2500_REG_MAC_ADDRMASKL(n)\t(32 + 2 * (n))\n+#define S2500_REG_MAC_ADDRMASKH(n)\t(33 + 2 * (n))\n+\n+#define S2500_MAC_ADDRFILT_EN_BIT\tBIT(31)\n+\n+/* MAC statistic registers */\n+#define S2500_REG_MAC_STOCTECTSTXL\t48\n+#define S2500_REG_MAC_STOCTECTSTXH\t49\n+#define S2500_REG_MAC_STFRAMESTXOK\t50\n+#define S2500_REG_MAC_STBCASTTXOK\t51\n+#define S2500_REG_MAC_STMCASTTXOK\t52\n+#define S2500_REG_MAC_STFRAMESTX64\t53\n+#define S2500_REG_MAC_STFRAMESTX65\t54\n+#define S2500_REG_MAC_STFRAMESTX128\t55\n+#define S2500_REG_MAC_STFRAMESTX256\t56\n+#define S2500_REG_MAC_STFRAMESTX512\t57\n+#define S2500_REG_MAC_STFRAMESTX1024\t58\n+#define S2500_REG_MAC_STTXUNDEFLOW\t59\n+#define S2500_REG_MAC_STSINGLECOL\t60\n+#define S2500_REG_MAC_STMULTICOL\t61\n+#define S2500_REG_MAC_STEXCESSCOL\t62\n+#define S2500_REG_MAC_STDEFERREDTX\t63\n+#define S2500_REG_MAC_STCRSERR\t\t64\n+#define S2500_REG_MAC_STOCTECTSRXL\t65\n+#define S2500_REG_MAC_STOCTECTSRXH\t66\n+#define S2500_REG_MAC_STFRAMESRXOK\t67\n+#define S2500_REG_MAC_STBCASTRXOK\t68\n+#define S2500_REG_MAC_STMCASTRXOK\t69\n+#define S2500_REG_MAC_STFRAMESRX64\t70\n+#define S2500_REG_MAC_STFRAMESRX65\t71\n+#define S2500_REG_MAC_STFRAMESRX128\t72\n+#define S2500_REG_MAC_STFRAMESRX256\t73\n+#define S2500_REG_MAC_STFRAMESRX512\t74\n+#define S2500_REG_MAC_STFRAMESRX1024\t75\n+#define S2500_REG_MAC_STRUNTSERR\t76\n+#define S2500_REG_MAC_STRXTOOLONG\t77\n+#define S2500_REG_MAC_STFCSERRS\t\t78\n+#define S2500_REG_MAC_STSYMBOLERRS\t79\n+#define S2500_REG_MAC_STALIGNERRS\t80\n+#define S2500_REG_MAC_STRXOVERFLOW\t81\n+#define S2500_REG_MAC_STRXDROPPED\t82\n+\n+/* First/last statistic register for sequential access */\n+#define S2500_REG_MAC_FIRST_STAT\tS2500_REG_MAC_STOCTECTSTXL\n+#define S2500_REG_MAC_LAST_STAT\t\tS2500_REG_MAC_STRXDROPPED\n+\n+#define S2500_NUM_STAT_REGS \\\n+\t(S2500_REG_MAC_LAST_STAT - S2500_REG_MAC_FIRST_STAT + 1)\n+#define S2500_NUM_STAT_VARS\t\t(S2500_NUM_STAT_REGS - 2)\n+\n+/* Vendor specific MMS4 registers */\n+#define S2500_REG_VS_ONIPVER\t\t0x8000\n+#define S2500_REG_VS_TWEAKS1\t\t0x8001\n+#define S2500_REG_VS_PLCAEXT\t\t0x8002\n+#define S2500_REG_VS_PMATUNE0\t\t0x8003\n+#define S2500_REG_VS_PMATUNE1\t\t0x8004\n+#define S2500_REG_VS_T1SWUPTUNE\t\t0x8007\n+#define S2500_REG_VS_HDD\t\t0xCC01\n+#define S2500_REG_VS_DCQ_TOID\t\t0xCC02\n+#define S2500_REG_VS_DCQ_SQI\t\t0xCC03\n+#define S2500_REG_VS_DCQ_SQI_PLUS\t0xCC04\n+#define S2500_REG_VS_HDD_TUNE1\t\t0xCD00\n+#define S2500_REG_VS_HDD_TUNE2\t\t0xCD01\n+#define S2500_REG_VS_HDD_TUNE3\t\t0xCD02\n+#define S2500_REG_VS_WS_STATUS_0\t0xD000\n+#define S2500_REG_VS_WS_CTRL_0\t\t0xD001\n+#define S2500_REG_VS_WS_STATUS_1\t0xD100\n+#define S2500_REG_VS_WS_CTRL_1\t\t0xD101\n+\n+/* Vendor specific MMS12 registers */\n+#define S2500_REG_VS_MIIMIRQE\t\t0x10\n+\n+/* MIIM IRQ status register */\n+#define S2500_REG_VS_MIIMIRQS\t\t0x11\n+#define MIIM_IRQ_STATUS_RSTS_SHIFT\t15\n+#define MIIM_IRQ_STATUS_RSTS\t\tBIT(MIIM_IRQ_STATUS_RSTS_SHIFT)\n+\n+#define S2500_REG_VS_DIOCFG0\t\t0x12\n+\n+#define S2500_REG_VS_CHIPID\t\t0x1000\n+#define S2500_REG_VS_TWEAKS2\t\t0x1001\n+\n+/* Permanent MAC address register */\n+#define S2500_REG_VS_MACID0\t\t0x1002\n+#define S2500_VS_MACID0_BYTE4_MASK\tGENMASK(15, 8)\n+#define S2500_VS_MACID0_BYTE5_MASK\tGENMASK(7, 0)\n+\n+#define S2500_REG_VS_MACID1\t\t0x1003\n+#define S2500_VS_MACID1_BYTE3_MASK\tGENMASK(7, 0)\n+#define S2500_REG_VS_NVM_HEALTH\t\t0x1005\n+\n+/* PTP registers */\n+#define S2500_REG_VS_PTP_SEC\t\t0x1010\n+#define S2500_REG_VS_PTP_SETSEC\t\t0x1012\n+#define S2500_REG_VS_PTP_ADJ\t\t0x1014\n+#define S2500_REG_VS_PTPNSEC\t\t0x1011\n+#define S2500_REG_VS_SETNSEC\t\t0x1013\n+#define S2500_REG_VS_CMPCTL\t\t0x1015\n+#define S2500_REG_VS_CMPSEC\t\t0x1016\n+#define S2500_REG_VS_CMPNSEC\t\t0x1017\n+#define S2500_REG_VS_CMPPER\t\t0x1018\n+#define S2500_REG_VS_CAPCTL\t\t0x1019\n+#define S2500_REG_VS_CAPSEC\t\t0x101A\n+#define S2500_REG_VS_CAPNSEC\t\t0x101B\n+#define S2500_REG_VS_BCNCNT\t\t0x101C\n+\n+/* prototypes / forward declarations */\n+extern const struct ethtool_ops s2500_ethtool_ops;\n+\n+struct s2500_info {\n+\tstruct device *dev;\n+\tstruct net_device *ndev;\n+\n+\t/* To have atomic set_rx_mode operation */\n+\tspinlock_t lock;\n+\n+\t/* To have atomic operation when time is adjusted */\n+\tstruct mutex ptp_adj_lock;\n+\tstruct task_struct *thread;\n+\n+\t/* global state variables */\n+\tbool event_pending;\n+\tunsigned int ndev_flags;\n+\tbool rx_flags_upd;\n+\n+\tsigned long poll_jiff;\n+\n+\tstruct spi_device *spi;\n+\n+\t/* statistic counters variables */\n+\tu64 stats_data[S2500_NUM_STAT_VARS];\n+\n+\t/* PTP related variables */\n+\tstruct ptp_clock_info ptp_clock_info;\n+\tvoid *tc6;\n+};\n+\n+int s2500_ptp_register(struct s2500_info *priv);\n+\n+#endif /* S2500_HW_DEF_H */\n+\ndiff --git a/drivers/net/ethernet/onsemi/s2500/s2500_main.c b/drivers/net/ethernet/onsemi/s2500/s2500_main.c\nnew file mode 100644\nindex 00000000000000..2d81b44f8c76c2\n--- /dev/null\n+++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c\n@@ -0,0 +1,649 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * Copyright 2026 Semiconductor Components Industries, LLC (\"onsemi\").\n+ * onsemi's S2500 10BASE-T1S MAC-PHY driver\n+ */\n+\n+#include \u003clinux/etherdevice.h\u003e\n+#include \u003clinux/if_ether.h\u003e\n+#include \u003clinux/irqchip.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/platform_device.h\u003e\n+#include \u003clinux/bitrev.h\u003e\n+#include \u003clinux/phy.h\u003e\n+\n+#include \"s2500_hw_def.h\"\n+\n+/* S2500 functions \u0026 definitions */\n+\n+#define S2500_STATUS0_MASK\t(S2500_SPI_ST0_CDPE_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_TXFCSE_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_TTSCAC_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_TTSCAB_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_TTSCAA_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_RESETC_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_HDRE_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_LOFE_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_RXBOE_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_TXBUE_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_TXBOE_BIT | \\\n+\t\t\t\tS2500_SPI_ST0_TXPE_BIT)\n+\n+/* Initializes the net device MAC address by reading the UID stored\n+ * into the device internal non-volatile memory.\n+ */\n+static int s2500_read_mac_from_nvmem(struct s2500_info *priv)\n+{\n+\tu16 mms = OA_TC6_PHY_C45_VS_MMS12;\n+\tu8 addr[ETH_ALEN];\n+\tu32 mac1 = 0;\n+\tu32 mac0 = 0;\n+\tu32 val;\n+\tint ret;\n+\n+\tret = oa_tc6_read_register_mms(priv-\u003etc6, S2500_REG_VS_MACID1,\n+\t\t\t\t       mms, \u0026mac1);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = oa_tc6_read_register_mms(priv-\u003etc6, S2500_REG_VS_MACID0,\n+\t\t\t\t       mms, \u0026mac0);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Pre-production parts may have 0 */\n+\tif (mac0 == 0 \u0026\u0026 mac1 == 0)\n+\t\treturn -ENXIO;\n+\n+\tret = oa_tc6_read_register(priv-\u003etc6, S2500_REG_SPI_PHYID,\n+\t\t\t\t   \u0026val);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tval = (val \u0026 S2500_SPI_PHYID_OUI_MASK) \u003e\u003e\n+\t       S2500_SPI_PHYID_OUI_SHIFT;\n+\n+\t/* Mapping for bits from PHY ID register to OUI as give below.\n+\t * - PHY-ID[10:17] provides OUI[16:23]\n+\t * - PHY-ID[18:25] provides OUI[8:15]\n+\t * - PHY-ID[26:31] provides OUI[2:7]\n+\t *\n+\t * Hardware presents OUI such a way that this requires not just\n+\t * byte level swap, bit level swap is needed as well.\n+\t */\n+\taddr[0] = bitrev8(FIELD_GET(S2500_SPI_PHYID_OUI_MASK0, val));\n+\taddr[1] = bitrev8(FIELD_GET(S2500_SPI_PHYID_OUI_MASK1, val));\n+\taddr[2] = bitrev8(FIELD_GET(S2500_SPI_PHYID_OUI_MASK2, val));\n+\n+\taddr[3] = FIELD_GET(S2500_VS_MACID1_BYTE3_MASK, mac1);\n+\taddr[4] = FIELD_GET(S2500_VS_MACID0_BYTE4_MASK, mac0);\n+\taddr[5] = FIELD_GET(S2500_VS_MACID0_BYTE5_MASK, mac0);\n+\n+\t__dev_addr_set(priv-\u003endev, addr, ETH_ALEN);\n+\tpriv-\u003endev-\u003eaddr_assign_type = NET_ADDR_PERM;\n+\treturn ret;\n+}\n+\n+/* Writes MAC address to macphy registers */\n+static int s2500_set_mac_filter(struct net_device *ndev, const u8 *mac)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\tu16 mms = OA_TC6_PHY_C45_MAC_MMS1;\n+\tu32 val;\n+\tint ret;\n+\n+\t/* Set unicast address filter */\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\tS2500_REG_MAC_ADDRMASKL(0),\n+\t\t\t\t\tmms, 0xffffffff);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\tS2500_REG_MAC_ADDRMASKH(0),\n+\t\t\t\t\tmms, 0xffff);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tval = get_unaligned_be32(\u0026mac[2]);\n+\n+\tret = oa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\tS2500_REG_MAC_ADDRFILTL(0),\n+\t\t\t\t\tmms, val);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tval = S2500_MAC_ADDRFILT_EN_BIT | get_unaligned_be16(mac);\n+\n+\treturn oa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\t S2500_REG_MAC_ADDRFILTH(0),\n+\t\t\t\t\t mms, val);\n+}\n+\n+static int s2500_mac_ctrl_modify_bits(struct s2500_info *priv,\n+\t\t\t\t      u32 in_bits, bool clr)\n+{\n+\tu16 mms = OA_TC6_PHY_C45_MAC_MMS1;\n+\tu32 reg = S2500_REG_MAC_CTRL;\n+\tu32 rval = 0;\n+\tu32 wval = 0;\n+\tint ret;\n+\n+\tret = oa_tc6_read_register_mms(priv-\u003etc6, reg, mms, \u0026rval);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (clr)\n+\t\twval = rval \u0026 ~in_bits;\n+\telse\n+\t\twval = rval | in_bits;\n+\tif (rval != wval)\n+\t\tret = oa_tc6_write_register_mms(priv-\u003etc6, reg,\n+\t\t\t\t\t\tmms, wval);\n+\treturn ret;\n+}\n+\n+static int s2500_init(struct s2500_info *priv)\n+{\n+\tu32 val;\n+\tint ret;\n+\n+\t/* Configure MAC-PHY's behavior */\n+\tval = S2500_SPI_CFG0_SYNC_BIT | S2500_SPI_CFG0_RXCTE_BIT |\n+\t      (S2500_TXCTHRESH_8 \u003c\u003c S2500_SPI_CFG0_TXCTHRESH_SHIFT) |\n+\t      (S2500_CPS_64 \u003c\u003c S2500_SPI_CFG0_CPS_SHIFT) |\n+\t      S2500_SPI_CFG0_FTSS_64_BIT;\n+\n+\tret = oa_tc6_write_register(priv-\u003etc6, S2500_REG_SPI_CFG0, val);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tval = (u32)~(S2500_SPI_ST0_RESETC_BIT |\n+\t\t     S2500_SPI_ST0_HDRE_BIT | S2500_SPI_ST0_LOFE_BIT |\n+\t\t     S2500_SPI_ST0_RXBOE_BIT | S2500_SPI_ST0_TXBOE_BIT |\n+\t\t     S2500_SPI_ST0_TXPE_BIT);\n+\n+\tret = oa_tc6_write_register(priv-\u003etc6, S2500_REG_SPI_IRQM0, val);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Program the source MAC address into the device */\n+\tret = s2500_set_mac_filter(priv-\u003endev, priv-\u003endev-\u003edev_addr);\n+\n+\tval = S2500_MAC_CTRL_ADRF_BIT | S2500_MAC_CTRL_FCSA_BIT;\n+\n+\treturn s2500_mac_ctrl_modify_bits(priv, val, false);\n+}\n+\n+static void s2500_shutdown(struct s2500_info *priv)\n+{\n+\tu32 val = S2500_MAC_CTRL_TXEN_BIT | S2500_MAC_CTRL_RXEN_BIT;\n+\tstruct net_device *ndev = priv-\u003endev;\n+\n+\tnetif_stop_queue(ndev);\n+\tphy_stop(ndev-\u003ephydev);\n+\n+\ts2500_mac_ctrl_modify_bits(priv, val, true);\n+}\n+\n+static int s2500_set_promiscuous_mode(struct s2500_info *priv,\n+\t\t\t\t      unsigned int rx_flags)\n+{\n+\tu32 val = S2500_MAC_CTRL_ADRF_BIT;\n+\tbool clr = false;\n+\n+\tif (rx_flags \u0026 IFF_PROMISC)\n+\t\tclr = true;\n+\treturn s2500_mac_ctrl_modify_bits(priv, val, clr);\n+}\n+\n+static int s2500_set_multicast_mode(struct s2500_info *priv,\n+\t\t\t\t    unsigned int rx_flags)\n+{\n+\tu16 mms = OA_TC6_PHY_C45_MAC_MMS1;\n+\tint i, ret = 0;\n+\tu16 addr;\n+\tu32 val;\n+\n+\tif ((rx_flags \u0026 IFF_ALLMULTI) ||\n+\t    (netdev_mc_count(priv-\u003endev) \u003e S2500_N_MCAST_FILTERS)) {\n+\t\t/* Disable multicast filter */\n+\t\tret = s2500_mac_ctrl_modify_bits(priv,\n+\t\t\t\t\t\t S2500_MAC_CTRL_MCSF_BIT,\n+\t\t\t\t\t\t true);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\t/* Accept all multicasts */\n+\t\taddr = S2500_REG_MAC_ADDRMASKL(1);\n+\t\tret = oa_tc6_write_register_mms(priv-\u003etc6, addr,\n+\t\t\t\t\t\tmms, 0);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\taddr = S2500_REG_MAC_ADDRMASKH(1);\n+\t\tret = oa_tc6_write_register_mms(priv-\u003etc6, addr,\n+\t\t\t\t\t\tmms, 0x100);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\taddr = S2500_REG_MAC_ADDRFILTL(1);\n+\t\tret = oa_tc6_write_register_mms(priv-\u003etc6, addr,\n+\t\t\t\t\t\tmms, 0);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tval = S2500_MAC_ADDRFILT_EN_BIT | 0x00000100;\n+\t\taddr = S2500_REG_MAC_ADDRFILTH(1);\n+\t\tret = oa_tc6_write_register_mms(priv-\u003etc6, addr,\n+\t\t\t\t\t\tmms, val);\n+\t} else if (netdev_mc_count(priv-\u003endev) == 0) {\n+\t\t/* Enable multicast filter */\n+\t\tret = s2500_mac_ctrl_modify_bits(priv,\n+\t\t\t\t\t\t S2500_MAC_CTRL_MCSF_BIT,\n+\t\t\t\t\t\t false);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\t/* Disable filters */\n+\t\tfor (i = 1; i \u003c= S2500_N_MCAST_FILTERS; i++) {\n+\t\t\taddr = S2500_REG_MAC_ADDRFILTH(i);\n+\t\t\tret = oa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\t\t\taddr, mms, 0);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\t} else {\n+\t\tstruct netdev_hw_addr *ha;\n+\t\tu32 addrh, addrl;\n+\n+\t\t/* Disable multicast filter */\n+\t\tret = s2500_mac_ctrl_modify_bits(priv,\n+\t\t\t\t\t\t S2500_MAC_CTRL_MCSF_BIT,\n+\t\t\t\t\t\t true);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\t/* Disable filters */\n+\t\tfor (i = 1; i \u003c= S2500_N_MCAST_FILTERS; i++) {\n+\t\t\taddr = S2500_REG_MAC_ADDRFILTH(i);\n+\t\t\tret = oa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\t\t\taddr, mms, 0);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\n+\t\ti = 1;\n+\t\tnetdev_for_each_mc_addr(ha, priv-\u003endev) {\n+\t\t\tif (i \u003e S2500_N_MCAST_FILTERS)\n+\t\t\t\tbreak;\n+\n+\t\t\taddrh = S2500_MAC_ADDRFILT_EN_BIT |\n+\t\t\t\t get_unaligned_be16(ha-\u003eaddr);\n+\t\t\taddrl = get_unaligned_be32(\u0026ha-\u003eaddr[2]);\n+\n+\t\t\taddr = S2500_REG_MAC_ADDRFILTH(i);\n+\t\t\tret = oa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\t\t\taddr, mms,\n+\t\t\t\t\t\t\taddrh);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\n+\t\t\taddr = S2500_REG_MAC_ADDRFILTL(i);\n+\t\t\tret = oa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\t\t\taddr, mms,\n+\t\t\t\t\t\t\taddrl);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\n+\t\t\taddr = S2500_REG_MAC_ADDRMASKL(i);\n+\t\t\tret = oa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\t\t\taddr, mms,\n+\t\t\t\t\t\t\t0xffffffff);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\n+\t\t\taddr = S2500_REG_MAC_ADDRMASKH(i);\n+\t\t\tret = oa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\t\t\taddr, mms,\n+\t\t\t\t\t\t\t0xffff);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t\ti++;\n+\t\t}\n+\t}\n+\treturn ret;\n+}\n+\n+/* Deferred function for applying RX mode flags in non-atomic context */\n+static int s2500_rx_mode_update(struct s2500_info *priv)\n+{\n+\tunsigned int rx_flags;\n+\tunsigned long flags;\n+\tint ret;\n+\n+\tspin_lock_irqsave(\u0026priv-\u003elock, flags);\n+\n+\trx_flags = priv-\u003endev_flags;\n+\tpriv-\u003erx_flags_upd = false;\n+\n+\tspin_unlock_irqrestore(\u0026priv-\u003elock, flags);\n+\n+\tret = s2500_set_promiscuous_mode(priv, rx_flags);\n+\tif (ret)\n+\t\tgoto out;\n+\n+\tret = s2500_set_multicast_mode(priv, rx_flags);\n+out:\n+\treturn ret;\n+}\n+\n+static void s2500_set_rx_mode(struct net_device *ndev)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\tunsigned long flags;\n+\n+\tspin_lock_irqsave(\u0026priv-\u003elock, flags);\n+\n+\tpriv-\u003erx_flags_upd = true;\n+\tpriv-\u003endev_flags = ndev-\u003eflags;\n+\n+\tspin_unlock_irqrestore(\u0026priv-\u003elock, flags);\n+\n+\tif (priv-\u003ethread)\n+\t\twake_up_process(priv-\u003ethread);\n+}\n+\n+static int s2500_set_mac_address(struct net_device *ndev, void *p)\n+{\n+\tstruct sockaddr *addr = p;\n+\n+\tif (!is_valid_ether_addr(addr-\u003esa_data))\n+\t\treturn -EADDRNOTAVAIL;\n+\n+\teth_hw_addr_set(ndev, addr-\u003esa_data);\n+\treturn s2500_set_mac_filter(ndev, addr-\u003esa_data);\n+}\n+\n+static netdev_tx_t s2500_start_xmit(struct sk_buff *skb,\n+\t\t\t\t    struct net_device *ndev)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\n+\treturn oa_tc6_start_xmit(priv-\u003etc6, skb);\n+}\n+\n+static int s2500_ioctl(struct net_device *ndev, struct ifreq *rq,\n+\t\t       int cmd)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\n+\treturn oa_tc6_ioctl(priv-\u003etc6, rq, cmd);\n+}\n+\n+static void s2500_process_events(struct s2500_info *priv)\n+{\n+\tu32 val;\n+\tint ret;\n+\n+\tif (!priv-\u003eevent_pending)\n+\t\treturn;\n+\n+\tpriv-\u003eevent_pending = false;\n+\n+\tret = oa_tc6_read_register(priv-\u003etc6, S2500_REG_SPI_ST0, \u0026val);\n+\tif (ret) {\n+\t\tdev_err(\u0026priv-\u003espi-\u003edev, \"Error reading ST0 register\");\n+\t\treturn;\n+\t}\n+}\n+\n+static int s2500_thread_fun(void *data)\n+{\n+\tstruct s2500_info *priv = data;\n+\tbool update_rx_mode = false;\n+\tunsigned long flags;\n+\tsigned long tout;\n+\tint ret = 0;\n+\n+\ttout = priv-\u003epoll_jiff;\n+\n+\tdo {\n+\t\tif (update_rx_mode) {\n+\t\t\tret = s2500_rx_mode_update(priv);\n+\t\t\tif (unlikely(ret)) {\n+\t\t\t\tdev_err(\u0026priv-\u003espi-\u003edev,\n+\t\t\t\t\t\"Failed to set new RX mode\");\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (tout == 0) {\n+\t\t\ttout = priv-\u003epoll_jiff;\n+\n+\t\t\t/* Force checking the status register */\n+\t\t\tpriv-\u003eevent_pending = true;\n+\t\t}\n+\n+\t\ts2500_process_events(priv);\n+\n+\t\tspin_lock_irqsave(\u0026priv-\u003elock, flags);\n+\t\t__set_current_state(TASK_INTERRUPTIBLE);\n+\n+\t\tupdate_rx_mode = priv-\u003erx_flags_upd;\n+\t\tret = update_rx_mode;\n+\n+\t\tspin_unlock_irqrestore(\u0026priv-\u003elock, flags);\n+\n+\t\tif (!ret)\n+\t\t\ttout = schedule_timeout(tout);\n+\t\telse\n+\t\t\tset_current_state(TASK_RUNNING);\n+\t} while (!kthread_should_stop());\n+\treturn 0;\n+}\n+\n+static int s2500_open(struct net_device *ndev)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\tint ret = 0;\n+\tu32 val;\n+\n+\tdev_dbg(\u0026ndev-\u003edev, \"%s\", \"s2500_open\");\n+\tphy_start(priv-\u003endev-\u003ephydev);\n+\n+\tpriv-\u003ethread = kthread_run(s2500_thread_fun, priv,\n+\t\t\t\t   DRV_NAME \"/%s:%d\",\n+\t\t\t\t   dev_name(\u0026priv-\u003espi-\u003edev),\n+\t\t\t\t   spi_get_chipselect(priv-\u003espi, 0));\n+\n+\tif (IS_ERR(priv-\u003ethread)) {\n+\t\tret = PTR_ERR(priv-\u003ethread);\n+\t} else {\n+\t\tval = S2500_MAC_CTRL_TXEN_BIT | S2500_MAC_CTRL_RXEN_BIT;\n+\t\tret = s2500_mac_ctrl_modify_bits(priv, val, false);\n+\n+\t\tnetif_start_queue(priv-\u003endev);\n+\t}\n+\treturn ret;\n+}\n+\n+static int s2500_stop(struct net_device *ndev)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\n+\tdev_dbg(\u0026ndev-\u003edev, \"%s\", \"s2500_stop\");\n+\n+\ts2500_shutdown(priv);\n+\n+\tkthread_stop(priv-\u003ethread);\n+\tpriv-\u003ethread = NULL;\n+\n+\treturn 0;\n+}\n+\n+static int s2500_hwtstamp_get(struct net_device *ndev,\n+\t\t\t      struct kernel_hwtstamp_config *k_cfg)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\n+\toa_tc6_hwtstamp_get(priv-\u003etc6, k_cfg);\n+\treturn 0;\n+}\n+\n+static int s2500_hwtstamp_set(struct net_device *ndev,\n+\t\t\t      struct kernel_hwtstamp_config *cfg,\n+\t\t\t      struct netlink_ext_ack *extack)\n+{\n+\tstruct s2500_info *priv = netdev_priv(ndev);\n+\n+\treturn oa_tc6_hwtstamp_set(priv-\u003etc6, cfg);\n+}\n+\n+static const struct net_device_ops s2500_netdev_ops = {\n+\t.ndo_open            = s2500_open,\n+\t.ndo_stop            = s2500_stop,\n+\t.ndo_start_xmit      = s2500_start_xmit,\n+\t.ndo_set_mac_address = s2500_set_mac_address,\n+\t.ndo_set_rx_mode     = s2500_set_rx_mode,\n+\t.ndo_eth_ioctl       = s2500_ioctl,\n+\t.ndo_hwtstamp_get    = s2500_hwtstamp_get,\n+\t.ndo_hwtstamp_set    = s2500_hwtstamp_set,\n+};\n+\n+static int s2500_probe(struct spi_device *spi)\n+{\n+\tstruct device *dev = \u0026spi-\u003edev;\n+\tstruct net_device *ndev;\n+\tstruct s2500_info *priv;\n+\tu32 val;\n+\tint ret;\n+\n+\tif (spi-\u003eirq \u003c 0)\n+\t\treturn -ENODEV;\n+\n+\tndev = devm_alloc_etherdev(dev, sizeof(struct s2500_info));\n+\tif (!ndev)\n+\t\treturn -ENOMEM;\n+\n+\tpriv = netdev_priv(ndev);\n+\tpriv-\u003endev = ndev;\n+\tpriv-\u003espi = spi;\n+\tpriv-\u003edev = dev;\n+\n+\tSET_NETDEV_DEV(ndev, dev);\n+\n+\tspin_lock_init(\u0026priv-\u003elock);\n+\tmutex_init(\u0026priv-\u003eptp_adj_lock);\n+\tndev-\u003eirq = spi-\u003eirq;\n+\n+\tspi-\u003edev.platform_data = priv;\n+\tspi_set_drvdata(spi, priv);\n+\n+\tndev-\u003enetdev_ops = \u0026s2500_netdev_ops;\n+\tndev-\u003eethtool_ops = \u0026s2500_ethtool_ops;\n+\tndev-\u003eif_port = IF_PORT_10BASET;\n+\tndev-\u003epriv_flags |= IFF_UNICAST_FLT;\n+\tndev-\u003ehw_features = NETIF_F_RXALL;\n+\n+\tpriv-\u003epoll_jiff = HZ * 5; /* Poll interval */\n+\n+\tpriv-\u003etc6 = oa_tc6_init(spi, ndev);\n+\tif (!priv-\u003etc6) {\n+\t\tdev_err(\u0026spi-\u003edev, \"OA TC6 init failed\");\n+\t\treturn -ENODEV;\n+\t}\n+\toa_tc6_set_vend1_mms(priv-\u003etc6, OA_TC6_PHY_C45_VS_MMS12);\n+\n+\t/* Clear RSTS, if set */\n+\toa_tc6_read_register_mms(priv-\u003etc6, S2500_REG_VS_MIIMIRQS,\n+\t\t\t\t OA_TC6_PHY_C45_VS_MMS12, \u0026val);\n+\tval \u0026= MIIM_IRQ_STATUS_RSTS;\n+\tif (val != 0)\n+\t\toa_tc6_write_register_mms(priv-\u003etc6,\n+\t\t\t\t\t  S2500_REG_VS_MIIMIRQS,\n+\t\t\t\t\t  OA_TC6_PHY_C45_VS_MMS12,\n+\t\t\t\t\t  MIIM_IRQ_STATUS_RSTS);\n+\n+\t/* Acknowledge all IRQ status bits */\n+\tret = oa_tc6_read_register(priv-\u003etc6, S2500_REG_SPI_ST0, \u0026val);\n+\tif (!ret) {\n+\t\tu32 mask = S2500_STATUS0_MASK;\n+\n+\t\tval \u0026= mask;\n+\t\toa_tc6_write_register(priv-\u003etc6, S2500_REG_SPI_ST0, val);\n+\t}\n+\n+\tret = device_get_ethdev_address(priv-\u003edev, ndev);\n+\tif (ret)\n+\t\tret = s2500_read_mac_from_nvmem(priv);\n+\n+\tif (ret) {\n+\t\teth_hw_addr_random(ndev);\n+\t\tdev_warn(\u0026spi-\u003edev, \"Using random MAC address %pM\",\n+\t\t\t ndev-\u003edev_addr);\n+\t}\n+\n+\tret = s2500_init(priv);\n+\tif (unlikely(ret)) {\n+\t\tdev_err(\u0026spi-\u003edev, \"failed to s2500_init the device\");\n+\t\tgoto err_reg_read;\n+\t}\n+\n+\t/* Configure PTP if the model supports it */\n+\tret = s2500_ptp_register(priv);\n+\tif (unlikely(ret))\n+\t\tgoto err_reg_read;\n+\n+\tret = register_netdev(ndev);\n+\tif (ret) {\n+\t\tdev_err(\u0026spi-\u003edev, \"failed to register netdev\\n\");\n+\t\tret = -ENODEV;\n+\n+\t\tgoto err_reg_read;\n+\t}\n+\treturn 0;\n+\n+err_reg_read:\n+\tdev_err(\u0026spi-\u003edev, \"could not initialize macphy\");\n+\treturn ret;\n+}\n+\n+static void s2500_remove(struct spi_device *spi)\n+{\n+\tstruct s2500_info *priv = spi-\u003edev.platform_data;\n+\n+\tdev_dbg(\u0026priv-\u003endev-\u003edev, \"%s\", \"s2500_remove\");\n+\n+\toa_tc6_ptp_unregister(priv-\u003etc6);\n+\tunregister_netdev(priv-\u003endev);\n+\toa_tc6_exit(priv-\u003etc6);\n+}\n+\n+static const struct of_device_id s2500_of_match[] = {\n+\t{ .compatible = \"onnn,s2500\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(of, s2500_of_match);\n+\n+static const struct spi_device_id s2500_ids[] = {\n+\t{ \"s2500\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(spi, s2500_ids);\n+\n+static struct spi_driver s2500_driver = {\n+\t.driver = {\n+\t\t.name = DRV_NAME,\n+\t\t.of_match_table = s2500_of_match,\n+\t},\n+\t.probe = s2500_probe,\n+\t.remove = s2500_remove,\n+\t.id_table = s2500_ids,\n+};\n+module_spi_driver(s2500_driver);\n+\n+MODULE_AUTHOR(\"Piergiorgio Beruto \u003cpier.beruto@onsemi.com\u003e\");\n+MODULE_AUTHOR(\"Selva Rajagopal \u003cselvamani.rajagopal@onsemi.com\u003e\");\n+MODULE_DESCRIPTION(\"onsemi MACPHY ethernet driver\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c\nnew file mode 100644\nindex 00000000000000..7da660cb5b190a\n--- /dev/null\n+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c\n@@ -0,0 +1,250 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * Copyright 2026 Semiconductor Components Industries, LLC (\"onsemi\").\n+ * onsemi's S2500 10BASE-T1S MAC-PHY driver\n+ */\n+\n+#include \"s2500_hw_def.h\"\n+\n+static int s2500_ptp_get_time64(struct ptp_clock_info *ptp,\n+\t\t\t\tstruct timespec64 *ts,\n+\t\t\t\tstruct ptp_system_timestamp *ptp_sts)\n+{\n+\tstruct s2500_info *priv = container_of(ptp, struct s2500_info,\n+\t\t\t\t\t       ptp_clock_info);\n+\tu32 data[2];\n+\tint ret;\n+\n+\tptp_read_system_prets(ptp_sts);\n+\tret = oa_tc6_read_registers_mms(priv-\u003etc6,\n+\t\t\t\t\tS2500_REG_VS_PTP_SEC,\n+\t\t\t\t\tOA_TC6_PHY_C45_VS_MMS12,\n+\t\t\t\t\t\u0026data[0], 2);\n+\tptp_read_system_postts(ptp_sts);\n+\n+\tif (!ret) {\n+\t\tts-\u003etv_sec = data[0];\n+\t\tts-\u003etv_nsec = data[1];\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int s2500_ptp_set_time64(struct ptp_clock_info *ptp,\n+\t\t\t\tconst struct timespec64 *ts)\n+{\n+\tstruct s2500_info *priv = container_of(ptp, struct s2500_info,\n+\t\t\t\t\t       ptp_clock_info);\n+\tu32 data[2];\n+\n+\tif (ts-\u003etv_sec \u003e= (1ULL \u003c\u003c 32))\n+\t\treturn -ERANGE;\n+\n+\tdata[0] = (u32)ts-\u003etv_sec;\n+\t/* bit 31 = execute set command */\n+\tdata[1] = ts-\u003etv_nsec | BIT(31);\n+\n+\treturn oa_tc6_write_registers_mms(priv-\u003etc6,\n+\t\t\t\t\t  S2500_REG_VS_PTP_SETSEC,\n+\t\t\t\t\t  OA_TC6_PHY_C45_VS_MMS12,\n+\t\t\t\t\t  \u0026data[0], 2);\n+}\n+\n+static int s2500_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)\n+{\n+\tstruct s2500_info *priv = container_of(ptp, struct s2500_info,\n+\t\t\t\t\t       ptp_clock_info);\n+\tu32 sign_bit = 0;\n+\tlong adj;\n+\tu32 val;\n+\tu64 ppm;\n+\n+\tif (scaled_ppm \u003c 0) {\n+\t\t/* split sign / mod */\n+\t\tsign_bit = 1U \u003c\u003c 31;\n+\t\tscaled_ppm = ~scaled_ppm + 1;\n+\t}\n+\n+\t/*\n+\t * Convert unsigned scaled_ppm to atto-seconds per clock cycles.\n+\t * The scaled_ppm format is Qx.16 --\u003e 1 lsb = 1/65536 ppm.\n+\t * The clock period of the S2500 is 8ns (125 MHz), so 1 lsb of\n+\t * adj register LSB is 1 atto-sec / 8ns = 0.000125 ppm.\n+\t * Represented in Qx.16 format, this is 0.000125 * 2^16 = 8(.192)\n+\t * To convert scaled_ppm into a register value we need to divide\n+\t * it by the LSB value, hence adj = (scaled_ppm * 1000) / 8192 to\n+\t * minimize the precision loss due to the integer arithmetic.\n+\t * That further reduces to (scaled_ppm * 125) / 1024.\n+\t */\n+\tppm = (u64)scaled_ppm * 125;\n+\tdo_div(ppm, 1024);\n+\tadj = (long)ppm;\n+\n+\t/* check overflow */\n+\tif (adj \u003e= (1L \u003c\u003c 28))\n+\t\treturn -ERANGE;\n+\n+\tval = (u32)adj | sign_bit;\n+\treturn oa_tc6_write_register_mms(priv-\u003etc6, S2500_REG_VS_PTP_ADJ,\n+\t\t\t\t\t OA_TC6_PHY_C45_VS_MMS12, val);\n+}\n+\n+static int s2500_use_settime(struct ptp_clock_info *ptp,\n+\t\t\t     struct timespec64 now, s64 rem)\n+{\n+\tstruct ptp_system_timestamp sts;\n+\tstruct timespec64 delta_ts;\n+\tstruct timespec64 target;\n+\tint ret;\n+\n+\tmemset(\u0026sts, 0, sizeof(sts));\n+\tret = ptp-\u003egettimex64(ptp, \u0026now, \u0026sts);\n+\tif (ret)\n+\t\treturn ret;\n+\tif (rem \u003e= 0) {\n+\t\tdelta_ts = ns_to_timespec64(rem);\n+\t\ttarget = timespec64_add(now, delta_ts);\n+\t} else {\n+\t\tdelta_ts = ns_to_timespec64(-rem);\n+\t\ttarget = timespec64_sub(now, delta_ts);\n+\t}\n+\tif (target.tv_sec \u003c 0 ||\n+\t    target.tv_sec \u003e= (1ULL \u003c\u003c 32))\n+\t\treturn -ERANGE;\n+\treturn ptp-\u003esettime64(ptp, \u0026target);\n+}\n+\n+static int s2500_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)\n+{\n+\tstruct s2500_info *priv = container_of(ptp, struct s2500_info,\n+\t\t\t\t\t       ptp_clock_info);\n+\tstruct ptp_system_timestamp sts;\n+\tstruct timespec64 target;\n+\tunsigned int period_ms;\n+\tstruct timespec64 now;\n+\ts64 scaled_ppm, rem;\n+\tint max_iters = 3;\n+\ts64 target_ns;\n+\tint ret = 0;\n+\ts64 now_ns;\n+\ts64 num;\n+\ts64 den;\n+\n+\tif (!ptp)\n+\t\treturn -EINVAL;\n+\n+\t/* Nothing to do */\n+\tif (delta == 0)\n+\t\treturn 0;\n+\n+\tif (mutex_lock_interruptible(\u0026priv-\u003eptp_adj_lock))\n+\t\treturn -EINTR;\n+\n+\t/* Try to slew the clock using adjfine for better accuracy.\n+\t * For large adjustments fall back to setting time directly.\n+\t */\n+\trem = delta;\n+\n+\twhile (rem != 0 \u0026\u0026 max_iters--) {\n+\t\ts64 abs_delta = rem \u003e 0 ? rem : -rem;\n+\n+\t\t/* If the adjustment is very large, more than 1 second,\n+\t\t * use settime to avoid very long slewing periods or\n+\t\t * excessive frequency offsets.\n+\t\t */\n+\t\tif (abs_delta \u003e 1000000000LL) {\n+\t\t\tret = s2500_use_settime(ptp, now, rem);\n+\t\t\trem = 0;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* Choose a slewing period depending on magnitude */\n+\t\tif (abs_delta \u003c= 1000000LL) /* \u003c= 1ms */\n+\t\t\tperiod_ms = 1000; /* 1 s */\n+\t\telse if (abs_delta \u003c= 100000000LL) /* \u003c= 100ms */\n+\t\t\tperiod_ms = 10000; /* 10 s */\n+\t\telse\n+\t\t\tperiod_ms = 60000; /* 60 s */\n+\n+\t\t/* Compute current time and fixed target for this\n+\t\t * iteration\n+\t\t */\n+\t\tmemset(\u0026sts, 0, sizeof(sts));\n+\t\tret = ptp-\u003egettimex64(ptp, \u0026now, \u0026sts);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\n+\t\tif (rem \u003e= 0)\n+\t\t\ttarget = timespec64_add(now,\n+\t\t\t\t\t\tns_to_timespec64(rem));\n+\t\telse\n+\t\t\ttarget = timespec64_sub(now,\n+\t\t\t\t\t\tns_to_timespec64(-rem));\n+\n+\t\t/* Compute scaled_ppm (Qx.16). scaled_ppm = ppm * 2^16\n+\t\t * ppm = (delta_seconds / period_seconds) * 1e6\n+\t\t * =\u003e scaled_ppm = delta_ns * 65536 / (period_ms * 1000)\n+\t\t */\n+\t\tnum = rem * 65536LL;\n+\t\tden = (s64)period_ms * 1000LL;\n+\n+\t\t/* Integer division rounds toward zero; keep sign in\n+\t\t * numerator\n+\t\t */\n+\t\tscaled_ppm = div_s64(num, den);\n+\n+\t\t/* Apply frequency adjustment */\n+\t\tret = ptp-\u003eadjfine(ptp, (long)scaled_ppm);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\n+\t\t/* Sleep for the slew period (interruptible). If\n+\t\t * interrupted, clear the adjfine and return -EINTR.\n+\t\t */\n+\t\tif (msleep_interruptible(period_ms)) {\n+\t\t\t/* Clear adjfine */\n+\t\t\tptp-\u003eadjfine(ptp, 0);\n+\t\t\tret = -EINTR;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* Clear adjfine and measure remaining offset */\n+\t\tptp-\u003eadjfine(ptp, 0);\n+\n+\t\tmemset(\u0026sts, 0, sizeof(sts));\n+\t\tret = ptp-\u003egettimex64(ptp, \u0026now, \u0026sts);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\n+\t\t/* remaining = target - now (in ns) */\n+\t\ttarget_ns = timespec64_to_ns(\u0026target);\n+\t\tnow_ns = timespec64_to_ns(\u0026now);\n+\t\trem = target_ns - now_ns;\n+\n+\t\t/* If remaining is small (\u003c 1us), finish */\n+\t\tif (rem \u003e -1000 \u0026\u0026 rem \u003c 1000)\n+\t\t\trem = 0;\n+\t}\n+\n+\tmutex_unlock(\u0026priv-\u003eptp_adj_lock);\n+\treturn ret;\n+}\n+\n+int s2500_ptp_register(struct s2500_info *priv)\n+{\n+\tstruct ptp_clock_info *info = \u0026priv-\u003eptp_clock_info;\n+\tint ret;\n+\n+\tinfo-\u003emax_adj = 100000000;\n+\tinfo-\u003eowner = THIS_MODULE;\n+\tinfo-\u003eadjfine = s2500_ptp_adjfine;\n+\tinfo-\u003egettimex64 = s2500_ptp_get_time64;\n+\tinfo-\u003esettime64 = s2500_ptp_set_time64;\n+\tinfo-\u003eadjtime = s2500_ptp_adjtime;\n+\n+\tret = oa_tc6_ptp_register(priv-\u003etc6, info);\n+\tif (ret)\n+\t\tdev_err(\u0026priv-\u003espi-\u003edev, \"PTP registration failed\");\n+\treturn ret;\n+}\n+\ndiff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c\nindex 88255e92b4cdbd..01ea2e8dd253bc 100644\n--- a/drivers/net/phy/dp83867.c\n+++ b/drivers/net/phy/dp83867.c\n@@ -991,15 +991,6 @@ static void dp83867_link_change_notify(struct phy_device *phydev)\n \t}\n }\n \n-static int dp83867_loopback(struct phy_device *phydev, bool enable, int speed)\n-{\n-\tif (enable \u0026\u0026 speed)\n-\t\treturn -EOPNOTSUPP;\n-\n-\treturn phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,\n-\t\t\t  enable ? BMCR_LOOPBACK : 0);\n-}\n-\n static int\n dp83867_led_brightness_set(struct phy_device *phydev,\n \t\t\t   u8 index, enum led_brightness brightness)\n@@ -1204,7 +1195,7 @@ static struct phy_driver dp83867_driver[] = {\n \t\t.resume\t\t= dp83867_resume,\n \n \t\t.link_change_notify = dp83867_link_change_notify,\n-\t\t.set_loopback\t= dp83867_loopback,\n+\t\t.set_loopback\t= genphy_loopback_fixed_speed,\n \n \t\t.led_brightness_set = dp83867_led_brightness_set,\n \t\t.led_hw_is_supported = dp83867_led_hw_is_supported,\ndiff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c\nindex e601d56b2507f9..0c4dc70641d8b0 100644\n--- a/drivers/net/phy/microchip_t1s.c\n+++ b/drivers/net/phy/microchip_t1s.c\n@@ -506,34 +506,6 @@ static int lan86xx_read_status(struct phy_device *phydev)\n \treturn 0;\n }\n \n-/* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and\n- * C45 registers space. If the PHY is discovered via C22 bus protocol it assumes\n- * it uses C22 protocol and always uses C22 registers indirect access to access\n- * C45 registers. This is because, we don't have a clean separation between\n- * C22/C45 register space and C22/C45 MDIO bus protocols. Resulting, PHY C45\n- * registers direct access can't be used which can save multiple SPI bus access.\n- * To support this feature, set .read_mmd/.write_mmd in the PHY driver to call\n- * .read_c45/.write_c45 in the OPEN Alliance framework\n- * drivers/net/ethernet/oa_tc6.c\n- */\n-static int lan865x_phy_read_mmd(struct phy_device *phydev, int devnum,\n-\t\t\t\tu16 regnum)\n-{\n-\tstruct mii_bus *bus = phydev-\u003emdio.bus;\n-\tint addr = phydev-\u003emdio.addr;\n-\n-\treturn __mdiobus_c45_read(bus, addr, devnum, regnum);\n-}\n-\n-static int lan865x_phy_write_mmd(struct phy_device *phydev, int devnum,\n-\t\t\t\t u16 regnum, u16 val)\n-{\n-\tstruct mii_bus *bus = phydev-\u003emdio.bus;\n-\tint addr = phydev-\u003emdio.addr;\n-\n-\treturn __mdiobus_c45_write(bus, addr, devnum, regnum, val);\n-}\n-\n static struct phy_driver microchip_t1s_driver[] = {\n \t{\n \t\tPHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1),\n@@ -584,8 +556,8 @@ static struct phy_driver microchip_t1s_driver[] = {\n \t\t.features           = PHY_BASIC_T1S_P2MP_FEATURES,\n \t\t.config_init        = lan865x_revb_config_init,\n \t\t.read_status        = lan86xx_read_status,\n-\t\t.read_mmd           = lan865x_phy_read_mmd,\n-\t\t.write_mmd          = lan865x_phy_write_mmd,\n+\t\t.read_mmd           = genphy_phy_read_mmd,\n+\t\t.write_mmd          = genphy_phy_write_mmd,\n \t\t.get_plca_cfg\t    = genphy_c45_plca_get_cfg,\n \t\t.set_plca_cfg\t    = lan86xx_plca_set_cfg,\n \t\t.get_plca_status    = genphy_c45_plca_get_status,\ndiff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c\nindex cabdd83c614f0d..afafa81dc22b82 100644\n--- a/drivers/net/phy/ncn26000.c\n+++ b/drivers/net/phy/ncn26000.c\n@@ -2,7 +2,7 @@\n /*\n  *  Driver for the onsemi 10BASE-T1S NCN26000 PHYs family.\n  *\n- * Copyright 2022 onsemi\n+ * Copyright 2026 onsemi\n  */\n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/bitfield.h\u003e\n@@ -14,6 +14,7 @@\n \n #include \"mdio-open-alliance.h\"\n \n+#define PHY_ID_S2500\t\t\t0x180FF411\n #define PHY_ID_NCN26000\t\t\t0x180FF5A1\n \n #define NCN26000_REG_IRQ_CTL            16\n@@ -35,15 +36,24 @@\n \n #define TO_TMR_DEFAULT\t\t\t32\n \n+#define NCN26000_REG_PHYCFG1\t\t0x8001\n+#define NCN26000_PHYCFG1_ENI\t\tBIT(7)\n+#define NCN26000_PHYCFG1_ENI_MASK\tBIT(7)\n+\n static int ncn26000_config_init(struct phy_device *phydev)\n {\n+\tint ret = 0;\n+\n \t/* HW bug workaround: the default value of the PLCA TO_TIMER should be\n \t * 32, where the current version of NCN26000 reports 24. This will be\n \t * fixed in future PHY versions. For the time being, we force the\n \t * correct default here.\n \t */\n-\treturn phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR,\n-\t\t\t     TO_TMR_DEFAULT);\n+\tif (phy_id_compare_model(phydev-\u003edrv-\u003ephy_id, PHY_ID_NCN26000))\n+\t\tret = phy_write_mmd(phydev, MDIO_MMD_VEND2,\n+\t\t\t\t    MDIO_OATC14_PLCA_TOTMR,\n+\t\t\t\t    TO_TMR_DEFAULT);\n+\treturn ret;\n }\n \n static int ncn26000_config_aneg(struct phy_device *phydev)\n@@ -100,6 +110,24 @@ static int ncn26000_read_status(struct phy_device *phydev)\n \treturn 0;\n }\n \n+/* Intercept PLCA enable/disable request to\n+ * set the proprietary, ENI mode accordingly\n+ */\n+static int ncn26000_c45_plca_set_cfg(struct phy_device *phydev,\n+\t\t\t\t     const struct phy_plca_cfg *plca_cfg)\n+{\n+\tint ret = genphy_c45_plca_set_cfg(phydev, plca_cfg);\n+\tu16 eni_cfg = 0;\n+\n+\tif (ret || plca_cfg-\u003eenabled \u003c 0)\n+\t\treturn ret;\n+\n+\teni_cfg = (plca_cfg-\u003eenabled) ? NCN26000_PHYCFG1_ENI : 0;\n+\treturn phy_modify_mmd(phydev, MDIO_MMD_VEND2,\n+\t\t\t      NCN26000_REG_PHYCFG1,\n+\t\t\t      NCN26000_PHYCFG1_ENI_MASK, eni_cfg);\n+}\n+\n static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)\n {\n \tint ret;\n@@ -117,8 +145,8 @@ static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)\n \n static int ncn26000_config_intr(struct phy_device *phydev)\n {\n-\tint ret;\n \tu16 irqe;\n+\tint ret;\n \n \tif (phydev-\u003einterrupts == PHY_INTERRUPT_ENABLED) {\n \t\t// acknowledge IRQs\n@@ -141,6 +169,27 @@ static int ncn26000_config_intr(struct phy_device *phydev)\n }\n \n static struct phy_driver ncn26000_driver[] = {\n+\t{\n+\t\tPHY_ID_MATCH_MODEL(PHY_ID_S2500),\n+\t\t.name                  = \"S2500\",\n+\t\t.features              = PHY_BASIC_T1S_P2MP_FEATURES,\n+\t\t.config_init           = ncn26000_config_init,\n+\t\t.config_intr           = ncn26000_config_intr,\n+\t\t.config_aneg           = ncn26000_config_aneg,\n+\t\t.read_status           = ncn26000_read_status,\n+\t\t.handle_interrupt      = ncn26000_handle_interrupt,\n+\t\t.set_loopback          = genphy_loopback_fixed_speed,\n+\t\t.set_plca_cfg          = ncn26000_c45_plca_set_cfg,\n+\t\t.get_plca_cfg          = genphy_c45_plca_get_cfg,\n+\t\t.get_plca_status       = genphy_c45_plca_get_status,\n+\t\t.soft_reset            = genphy_soft_reset,\n+\t\t.get_sqi               = genphy_c45_oatc14_get_sqi,\n+\t\t.get_sqi_max           = genphy_c45_oatc14_get_sqi_max,\n+\t\t.read_mmd              = genphy_phy_read_mmd,\n+\t\t.write_mmd             = genphy_phy_write_mmd,\n+\t\t.cable_test_get_status = genphy_c45_oatc14_cable_test_get_status,\n+\t\t.cable_test_start      = genphy_c45_oatc14_cable_test_start,\n+\t},\n \t{\n \t\tPHY_ID_MATCH_MODEL(PHY_ID_NCN26000),\n \t\t.name\t\t\t= \"NCN26000\",\n@@ -151,7 +200,7 @@ static struct phy_driver ncn26000_driver[] = {\n \t\t.read_status\t\t= ncn26000_read_status,\n \t\t.handle_interrupt       = ncn26000_handle_interrupt,\n \t\t.get_plca_cfg\t\t= genphy_c45_plca_get_cfg,\n-\t\t.set_plca_cfg\t\t= genphy_c45_plca_set_cfg,\n+\t\t.set_plca_cfg\t\t= ncn26000_c45_plca_set_cfg,\n \t\t.get_plca_status\t= genphy_c45_plca_get_status,\n \t\t.soft_reset             = genphy_soft_reset,\n \t},\n@@ -161,11 +210,13 @@ module_phy_driver(ncn26000_driver);\n \n static const struct mdio_device_id __maybe_unused ncn26000_tbl[] = {\n \t{ PHY_ID_MATCH_MODEL(PHY_ID_NCN26000) },\n+\t{ PHY_ID_MATCH_MODEL(PHY_ID_S2500) },\n \t{ }\n };\n \n MODULE_DEVICE_TABLE(mdio, ncn26000_tbl);\n \n-MODULE_AUTHOR(\"Piergiorgio Beruto\");\n+MODULE_AUTHOR(\"Piergiorgio Beruto \u003cpier.beruto@onsemi.com\u003e\");\n+MODULE_AUTHOR(\"Selva Rajagopal \u003cselvamani.rajagopal@onsemi.com\u003e\");\n MODULE_DESCRIPTION(\"onsemi 10BASE-T1S PHY driver\");\n MODULE_LICENSE(\"Dual BSD/GPL\");\ndiff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c\nindex 0615228459ef42..11fd204eea16a4 100644\n--- a/drivers/net/phy/phy_device.c\n+++ b/drivers/net/phy/phy_device.c\n@@ -2787,6 +2787,81 @@ int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,\n }\n EXPORT_SYMBOL(genphy_write_mmd_unsupported);\n \n+/**\n+ * genphy_phy_read_mmd - Helper for reading a register without lock\n+ * from the given MMD and PHY.\n+ * @phydev: The phy_device struct\n+ * @devnum: The MMD to read from\n+ * @regnum: The register on the MMD to read\n+ *\n+ * Description: PHYs can have both C22 and C45 registers space. Once PHY\n+ * is discovered via C22 bus protocol, it uses C22 indirect access to\n+ * access C45 registers. Some PHYs, like 10Base-T1S PHYs defined by OPEN\n+ * Alliance 10BASE‑T1x, support only direct access.\n+ *\n+ * If PHY indicates C45 support through DTS entry, it avoid C22 APIs\n+ * entirely and therefore generic MDIO registers are inaccessible.\n+ *\n+ * MDIO bus isn't locked here because when called through read_mmd\n+ * callback of phy_driver, caller is expected to lock the bus as\n+ * implemented in phy_read_mmd.\n+ *\n+ * Returns: Register value if successful, negative error code on failure.\n+ */\n+int genphy_phy_read_mmd(struct phy_device *phydev, int devnum,\n+\t\t\tu16 regnum)\n+{\n+\tstruct mii_bus *bus = phydev-\u003emdio.bus;\n+\tint addr = phydev-\u003emdio.addr;\n+\n+\tlockdep_assert_held(\u0026bus-\u003emdio_lock);\n+\treturn __mdiobus_c45_read(bus, addr, devnum, regnum);\n+}\n+EXPORT_SYMBOL(genphy_phy_read_mmd);\n+\n+/**\n+ * genphy_phy_write_mmd - Helper for writing a register without lock\n+ * to the given MMD and PHY.\n+ * @phydev: The phy_device struct\n+ * @devnum: The MMD to write to\n+ * @regnum: The register on the MMD to write\n+ * @val:    Value to write\n+ *\n+ * Description: Similar to genphy_phy_read_mmd\n+ *\n+ * Returns: 0 if successful, negative error code on failure.\n+ */\n+int genphy_phy_write_mmd(struct phy_device *phydev, int devnum,\n+\t\t\t u16 regnum, u16 val)\n+{\n+\tstruct mii_bus *bus = phydev-\u003emdio.bus;\n+\tint addr = phydev-\u003emdio.addr;\n+\n+\tlockdep_assert_held(\u0026bus-\u003emdio_lock);\n+\treturn __mdiobus_c45_write(bus, addr, devnum, regnum, val);\n+}\n+EXPORT_SYMBOL(genphy_phy_write_mmd);\n+\n+/**\n+ * genphy_loopback_fixed_speed - Helper to modify the PHY loopback mode\n+ * without affecting any other settings.\n+ * @phydev: The phy_device struct\n+ * @enable: Flag to enable or disable the PHY level loopback.\n+ * @speed: Speed setting. Not expected to be set. Error if it is set.\n+ *\n+ * Returns: 0 if successful, negative error code on failure.\n+ */\n+int genphy_loopback_fixed_speed(struct phy_device *phydev, bool enable,\n+\t\t\t\tint speed)\n+{\n+\tif (enable \u0026\u0026 speed)\n+\t\treturn -EOPNOTSUPP;\n+\n+\treturn phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,\n+\t\t\t  enable ? BMCR_LOOPBACK : 0);\n+}\n+EXPORT_SYMBOL(genphy_loopback_fixed_speed);\n+\n int genphy_suspend(struct phy_device *phydev)\n {\n \treturn phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN);\ndiff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h\nindex 15f58e3c56c740..17ac3e0751e122 100644\n--- a/include/linux/oa_tc6.h\n+++ b/include/linux/oa_tc6.h\n@@ -7,8 +7,23 @@\n  * Author: Parthiban Veerasooran \u003cparthiban.veerasooran@microchip.com\u003e\n  */\n \n+#ifndef _LINUX_OA_TC6_H\n+#define _LINUX_OA_TC6_H\n+\n #include \u003clinux/etherdevice.h\u003e\n #include \u003clinux/spi/spi.h\u003e\n+#include \u003clinux/ptp_clock_kernel.h\u003e\n+\n+/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in\n+ * the OPEN Alliance specification.\n+ */\n+#define OA_TC6_PHY_C45_MAC_MMS1\t\t\t1\t/* No MMD */\n+#define OA_TC6_PHY_C45_PCS_MMS2\t\t\t2\t/* MMD 3 */\n+#define OA_TC6_PHY_C45_PMA_PMD_MMS3\t\t3\t/* MMD 1 */\n+#define OA_TC6_PHY_C45_VS_PLCA_MMS4\t\t4\t/* MMD 31 */\n+#define OA_TC6_PHY_C45_AUTO_NEG_MMS5\t\t5\t/* MMD 7 */\n+#define OA_TC6_PHY_C45_POWER_UNIT_MMS6\t\t6\t/* MMD 13 */\n+#define OA_TC6_PHY_C45_VS_MMS12\t\t\t12\t/* for vendors */\n \n struct oa_tc6;\n \n@@ -22,3 +37,24 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],\n \t\t\t  u8 length);\n netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb);\n int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6);\n+int oa_tc6_write_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,\n+\t\t\t       u32 value[], u8 length);\n+int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,\n+\t\t\t      u32 value);\n+int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,\n+\t\t\t      u32 value[], u8 length);\n+int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,\n+\t\t\t     u32 *value);\n+int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info);\n+int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);\n+int oa_tc6_get_ts_info(struct oa_tc6 *tc6,\n+\t\t       struct kernel_ethtool_ts_info *info);\n+void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6,\n+\t\t\t struct kernel_hwtstamp_config *cfg);\n+void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,\n+\t\t\t struct ethtool_ts_stats *stats);\n+int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,\n+\t\t\tstruct kernel_hwtstamp_config *cfg);\n+void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms);\n+void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);\n+#endif /* _LINUX_OA_TC6_H */\ndiff --git a/include/linux/phy.h b/include/linux/phy.h\nindex 199a7aaa341bfd..61bcd71a314324 100644\n--- a/include/linux/phy.h\n+++ b/include/linux/phy.h\n@@ -2301,6 +2301,12 @@ int genphy_read_mmd_unsupported(struct phy_device *phdev, int devad,\n \t\t\t\tu16 regnum);\n int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,\n \t\t\t\t u16 regnum, u16 val);\n+int genphy_loopback_fixed_speed(struct phy_device *phydev, bool enable,\n+\t\t\t\tint speed);\n+int genphy_phy_write_mmd(struct phy_device *phydev, int devnum,\n+\t\t\t u16 regnum, u16 val);\n+int genphy_phy_read_mmd(struct phy_device *phydev, int devnum,\n+\t\t\tu16 regnum);\n \n /* Clause 37 */\n int genphy_c37_config_aneg(struct phy_device *phydev);\n"
}

2/1 2026/07/09 11:18 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "NET_VENDOR_ONSEMI",
    "S2500_MACPHY",
    "OA_TC6",
    "NCN26000_PHY"
  ],
  "FocusSymbols": [
    "s2500_probe",
    "s2500_open",
    "s2500_ioctl",
    "s2500_hwtstamp_set",
    "s2500_set_multicast_mode",
    "s2500_ptp_adjtime",
    "oa_tc6_hwtstamp_set",
    "oa_tc6_tstamp_ioctl",
    "oa_tc6_ptp_register",
    "ncn26000_c45_plca_set_cfg"
  ],
  "Reasoning": "The patch introduces a new SPI-based ethernet driver for the onsemi S2500 MACPHY, adds hardware timestamping support to the OA TC6 framework, and updates the NCN26000 PHY driver. These are functional changes to the networking subsystem and PHY drivers that can be interacted with via ethtool, netlink, and ioctls.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit dbd555c72da3631ca031e9465dd5b28c8dbc78db
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Jul 9 11:18:42 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/devicetree/bindings/net/onnn,s2500.yaml b/Documentation/devicetree/bindings/net/onnn,s2500.yaml
new file mode 100644
index 00000000000000..11edf10508d9f3
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/onnn,s2500.yaml
@@ -0,0 +1,67 @@
+# 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:
+  - Piergiorgio Beruto <pier.beruto@onsemi.com>
+  - Selva Rajagopal <Selvamani.Rajagopal@onsemi.com>
+
+description:
+  The S2500 combines a Media Access Controller (MAC) and an
+  Ethernet PHY to enable 10BASE‑T1S networks. The Ethernet Media Access
+  Controller (MAC) module implements a 10 Mbps half duplex Ethernet MAC,
+  compatible with the IEEE 802.3 standard and a 10BASE-T1S physical layer
+  transceiver integrated into the S2500. The communication between
+  the host and the MAC-PHY is specified in the OPEN Alliance 10BASE-T1x
+  MACPHY Serial Interface (TC6).
+
+allOf:
+  - $ref: /schemas/net/ethernet-controller.yaml#
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    const: onnn,s2500
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    description:
+      Interrupt from MAC-PHY asserted in the event of Receive Chunks
+      Available, Transmit Chunk Credits Available and Extended Status
+      Event.
+    maxItems: 1
+
+  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 = <&eth0_pins>;
+        interrupt-parent = <&gpio>;
+        interrupts = <25 IRQ_TYPE_EDGE_FALLING>;
+        spi-max-frequency = <15000000>;
+      };
+    };
diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst
index fe2aabde923a57..325d299d1a7dc2 100644
--- a/Documentation/networking/oa-tc6-framework.rst
+++ b/Documentation/networking/oa-tc6-framework.rst
@@ -153,6 +153,10 @@ OPEN Alliance TC6 Framework
 - Forwards the received Ethernet frame from 10Base-T1x MAC-PHY to n/w
   subsystem.
 
+- If supported by the hardware and enabled, updates hardware timestamp
+  in skb, when indicated by one of the three timestamp capture registers
+  through TSC fields of the header.
+
 Data Transaction
 ~~~~~~~~~~~~~~~~
 
@@ -495,3 +499,79 @@ the MAC-PHY.
 Zero align receive frame feature can be enabled to align all receive ethernet
 frames data to start at the beginning of any receive data chunk payload with a
 start word offset (SWO) of zero.
+
+.. c:function:: int oa_tc6_ptp_register(struct oa_tc6 *tc6, \
+                                        struct ptp_clock_info *info);
+
+Registers the PTP hardware clock related functions with the kernel.
+This API simply registers. Initialization of the fields in the
+ptp_clock_info structure are left to the vendor as programming hardware
+timer is expected to be vendor dependent. The fields max_adj, owner,
+and all the functions for the clock operations, like adjfine, gettimex64,
+settime64, adjtime are expected to be initialized in the structure before
+calling the registering the hardware clock.
+
+.. c:function:: void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
+
+Unregisters the PTP hardware clock related callbacks.
+
+.. c:function:: int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, \
+                                 int cmd);
+
+ioctl interface to handle hardware timestamp and PHY related commands.
+
+.. c:function:: int oa_tc6_get_ts_info(struct oa_tc6 *tc6, \
+                                       struct kernel_ethtool_ts_info *info);
+
+Provides timestamp related settings that are supported to ethtool.
+
+.. c:function:: void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6, \
+                                         struct kernel_hwtstamp_config *cfg);
+
+Returns hardware timestamp configuration. Part of net_device_ops callbacks.
+
+.. c:function:: void oa_tc6_get_ts_stats(struct oa_tc6 *tc6, \
+                                         struct ethtool_ts_stats *ts_stats);
+
+Provides hardware timestamp related traffic statistics for ethtool.
+
+.. c:function:: int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6, \
+                                         struct kernel_hwtstamp_config *cfg);
+
+Helper to set hardware timestamp configuration. Part of net_device_ops
+callbacks.
+
+.. c:function:: void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms);
+
+Helper to map MDIO_MMD_VEND1 command to vendor specific Memory Map Select
+(MMS) value. This function offers flexibility for vendors that may have
+used any MMS value between 10 and 15 as allowed by the specification.
+MDIO_MMD_VEND2 is already mapped to MMS4 in the OA TC6 frame work code.
+
+.. c:function:: int oa_tc6_write_registers_mms(struct oa_tc6 *tc6, \
+                                               u16 address, u16 mms, \
+                                               u32 value[], u8 length);
+
+Writing multiple consecutive registers starting from @address for the
+given @mms memory map selector in the MAC-PHY. Maximum of 128 consecutive
+registers can be written starting at @address.
+
+.. c:function:: int oa_tc6_write_register_mms(struct oa_tc6 *tc6, \
+                                              u16 address, u16 mms, \
+                                              u32 value);
+
+Write a single register addressed by @address and @mms in the MAC-PHY.
+
+.. c:function:: int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, \
+                                              u16 address, u16 mms, \
+                                              u32 value[], u8 length);
+
+Reading multiple consecutive registers starting from @address for the
+given @mms memory map selector value, in the MAC-PHY. Maximum of 128
+consecutive registers can be read starting at @address.
+
+.. c:function:: int oa_tc6_read_register_mms(struct oa_tc6 *tc6, \
+                                             u16 address, u16 mms, \
+                                             u32 *value);
+
+Read a single register addressed by @address and @mms in the MAC-PHY.
diff --git a/MAINTAINERS b/MAINTAINERS
index 52f1a55eca99bf..958b9f61e36418 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20182,12 +20182,20 @@ S:	Maintained
 F:	arch/mips/boot/dts/ralink/omega2p.dts
 
 ONSEMI ETHERNET PHY DRIVERS
-M:	Piergiorgio Beruto <piergiorgio.beruto@gmail.com>
+M:	Piergiorgio Beruto <pier.beruto@onsemi.com>
+M:	Selva Rajagopal <selvamani.rajagopal@onsemi.com>
 L:	netdev@vger.kernel.org
 S:	Supported
 W:	http://www.onsemi.com
 F:	drivers/net/phy/ncn*
 
+ONSEMI S2500 10BASE-T1S MACPHY ETHERNET DRIVER
+M:	Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+L:	netdev@vger.kernel.org
+S:	Supported
+F:	Documentation/devicetree/bindings/net/onnn,s2500.yaml
+F:	drivers/net/ethernet/onsemi/s2500/s2500_*
+
 OP-TEE DRIVER
 M:	Jens Wiklander <jens.wiklander@linaro.org>
 L:	op-tee@lists.trustedfirmware.org (moderated for non-subscribers)
@@ -20209,10 +20217,11 @@ F:	drivers/rtc/rtc-optee.c
 
 OPEN ALLIANCE 10BASE-T1S MACPHY SERIAL INTERFACE FRAMEWORK
 M:	Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
+M:	Selva Rajagopal <selvamani.rajagopal@onsemi.com> (timestamp support)
 L:	netdev@vger.kernel.org
 S:	Maintained
 F:	Documentation/networking/oa-tc6-framework.rst
-F:	drivers/net/ethernet/oa_tc6.c
+F:	drivers/net/ethernet/oa_tc6/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 78c79ad7bba553..27402a592b294d 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -134,7 +134,9 @@ 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"
+source "drivers/net/ethernet/onsemi/Kconfig"
 
 config ETHOC
 	tristate "OpenCores 10/100 Mbps Ethernet MAC support"
@@ -146,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 bba55d9af38770..77b11d5a7abf13 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -71,6 +71,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/
@@ -104,4 +105,3 @@ 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
diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c
index 0277d9737369ad..3b555ee6980410 100644
--- a/drivers/net/ethernet/microchip/lan865x/lan865x.c
+++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c
@@ -13,27 +13,27 @@
 #define DRV_NAME			"lan8650"
 
 /* MAC Network Control Register */
-#define LAN865X_REG_MAC_NET_CTL		0x00010000
+#define LAN865X_REG_MAC_NET_CTL		0x0
 #define MAC_NET_CTL_TXEN		BIT(3) /* Transmit Enable */
 #define MAC_NET_CTL_RXEN		BIT(2) /* Receive Enable */
 
 /* MAC Network Configuration Reg */
-#define LAN865X_REG_MAC_NET_CFG		0x00010001
+#define LAN865X_REG_MAC_NET_CFG		0x1
 #define MAC_NET_CFG_PROMISCUOUS_MODE	BIT(4)
 #define MAC_NET_CFG_MULTICAST_MODE	BIT(6)
 #define MAC_NET_CFG_UNICAST_MODE	BIT(7)
 
 /* MAC Hash Register Bottom */
-#define LAN865X_REG_MAC_L_HASH		0x00010020
+#define LAN865X_REG_MAC_L_HASH		0x20
 /* MAC Hash Register Top */
-#define LAN865X_REG_MAC_H_HASH		0x00010021
+#define LAN865X_REG_MAC_H_HASH		0x21
 /* MAC Specific Addr 1 Bottom Reg */
-#define LAN865X_REG_MAC_L_SADDR1	0x00010022
+#define LAN865X_REG_MAC_L_SADDR1	0x22
 /* MAC Specific Addr 1 Top Reg */
-#define LAN865X_REG_MAC_H_SADDR1	0x00010023
+#define LAN865X_REG_MAC_H_SADDR1	0x23
 
 /* MAC TSU Timer Increment Register */
-#define LAN865X_REG_MAC_TSU_TIMER_INCR		0x00010077
+#define LAN865X_REG_MAC_TSU_TIMER_INCR		0x77
 #define MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS	0x0028
 
 struct lan865x_priv {
@@ -49,7 +49,8 @@ static int lan865x_set_hw_macaddr_low_bytes(struct oa_tc6 *tc6, const u8 *mac)
 
 	regval = (mac[3] << 24) | (mac[2] << 16) | (mac[1] << 8) | mac[0];
 
-	return oa_tc6_write_register(tc6, LAN865X_REG_MAC_L_SADDR1, regval);
+	return oa_tc6_write_register_mms(tc6, LAN865X_REG_MAC_L_SADDR1,
+					 OA_TC6_PHY_C45_MAC_MMS1, regval);
 }
 
 static int lan865x_set_hw_macaddr(struct lan865x_priv *priv, const u8 *mac)
@@ -65,8 +66,8 @@ static int lan865x_set_hw_macaddr(struct lan865x_priv *priv, const u8 *mac)
 
 	/* Prepare and configure MAC address high bytes */
 	regval = (mac[5] << 8) | mac[4];
-	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_SADDR1,
-				    regval);
+	ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_SADDR1,
+					OA_TC6_PHY_C45_MAC_MMS1, regval);
 	if (!ret)
 		return 0;
 
@@ -146,14 +147,16 @@ static int lan865x_set_specific_multicast_addr(struct lan865x_priv *priv)
 	}
 
 	/* Enabling specific multicast addresses */
-	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH, hash_hi);
+	ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_HASH,
+					OA_TC6_PHY_C45_MAC_MMS1, hash_hi);
 	if (ret) {
 		netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",
 			   ret);
 		return ret;
 	}
 
-	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH, hash_lo);
+	ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_L_HASH,
+					OA_TC6_PHY_C45_MAC_MMS1, hash_lo);
 	if (ret)
 		netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",
 			   ret);
@@ -166,16 +169,16 @@ static int lan865x_set_all_multicast_addr(struct lan865x_priv *priv)
 	int ret;
 
 	/* Enabling all multicast addresses */
-	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH,
-				    0xffffffff);
+	ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_HASH,
+					OA_TC6_PHY_C45_MAC_MMS1, 0xffffffff);
 	if (ret) {
 		netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",
 			   ret);
 		return ret;
 	}
 
-	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH,
-				    0xffffffff);
+	ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_L_HASH,
+					OA_TC6_PHY_C45_MAC_MMS1, 0xffffffff);
 	if (ret)
 		netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",
 			   ret);
@@ -187,14 +190,16 @@ static int lan865x_clear_all_multicast_addr(struct lan865x_priv *priv)
 {
 	int ret;
 
-	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH, 0);
+	ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_HASH,
+					OA_TC6_PHY_C45_MAC_MMS1, 0);
 	if (ret) {
 		netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",
 			   ret);
 		return ret;
 	}
 
-	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH, 0);
+	ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_L_HASH,
+					OA_TC6_PHY_C45_MAC_MMS1, 0);
 	if (ret)
 		netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",
 			   ret);
@@ -235,7 +240,8 @@ static void lan865x_multicast_work_handler(struct work_struct *work)
 		if (lan865x_clear_all_multicast_addr(priv))
 			return;
 	}
-	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CFG, regval);
+	ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CFG,
+					OA_TC6_PHY_C45_MAC_MMS1, regval);
 	if (ret)
 		netdev_err(priv->netdev, "Failed to enable promiscuous/multicast/normal mode: %d\n",
 			   ret);
@@ -260,12 +266,14 @@ static int lan865x_hw_disable(struct lan865x_priv *priv)
 {
 	u32 regval;
 
-	if (oa_tc6_read_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, &regval))
+	if (oa_tc6_read_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+				     OA_TC6_PHY_C45_MAC_MMS1, &regval))
 		return -ENODEV;
 
 	regval &= ~(MAC_NET_CTL_TXEN | MAC_NET_CTL_RXEN);
 
-	if (oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, regval))
+	if (oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+				      OA_TC6_PHY_C45_MAC_MMS1, regval))
 		return -ENODEV;
 
 	return 0;
@@ -291,12 +299,14 @@ static int lan865x_hw_enable(struct lan865x_priv *priv)
 {
 	u32 regval;
 
-	if (oa_tc6_read_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, &regval))
+	if (oa_tc6_read_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+				     OA_TC6_PHY_C45_MAC_MMS1, &regval))
 		return -ENODEV;
 
 	regval |= MAC_NET_CTL_TXEN | MAC_NET_CTL_RXEN;
 
-	if (oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, regval))
+	if (oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+				      OA_TC6_PHY_C45_MAC_MMS1, regval))
 		return -ENODEV;
 
 	return 0;
@@ -359,8 +369,9 @@ static int lan865x_probe(struct spi_device *spi)
 	 * stamping at the end of the Start of Frame Delimiter (SFD) and set the
 	 * Timer Increment reg to 40 ns to be used as a 25 MHz internal clock.
 	 */
-	ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_TSU_TIMER_INCR,
-				    MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS);
+	ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_TSU_TIMER_INCR,
+					OA_TC6_PHY_C45_MAC_MMS1,
+					MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS);
 	if (ret) {
 		dev_err(&spi->dev, "Failed to config TSU Timer Incr reg: %d\n",
 			ret);
diff --git a/drivers/net/ethernet/oa_tc6/Kconfig b/drivers/net/ethernet/oa_tc6/Kconfig
new file mode 100644
index 00000000000000..97345f345fb9f2
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# OA TC6 10BASE-T1x MAC-PHY configuration
+#
+
+config OA_TC6
+	tristate "OPEN Alliance TC6 10BASE-T1x MAC-PHY support"
+	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 00000000000000..964f668efc2d3a
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for OA TC6 10BASE-T1x MAC-PHY
+#
+
+obj-$(CONFIG_OA_TC6) := oa_tc6_mod.o
+oa_tc6_mod-objs := oa_tc6.o oa_tc6_ptp.o oa_tc6_tstamp.o
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
similarity index 79%
rename from drivers/net/ethernet/oa_tc6.c
rename to drivers/net/ethernet/oa_tc6/oa_tc6.c
index 0727d53345a33b..d4e94724f64912 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -12,149 +12,15 @@
 #include <linux/phy.h>
 #include <linux/oa_tc6.h>
 
-/* OPEN Alliance TC6 registers */
-/* Standard Capabilities Register */
-#define OA_TC6_REG_STDCAP			0x0002
-#define STDCAP_DIRECT_PHY_REG_ACCESS		BIT(8)
-
-/* Reset Control and Status Register */
-#define OA_TC6_REG_RESET			0x0003
-#define RESET_SWRESET				BIT(0)	/* Software Reset */
-
-/* Configuration Register #0 */
-#define OA_TC6_REG_CONFIG0			0x0004
-#define CONFIG0_SYNC				BIT(15)
-#define CONFIG0_ZARFE_ENABLE			BIT(12)
-
-/* Status Register #0 */
-#define OA_TC6_REG_STATUS0			0x0008
-#define STATUS0_RESETC				BIT(6)	/* Reset Complete */
-#define STATUS0_HEADER_ERROR			BIT(5)
-#define STATUS0_LOSS_OF_FRAME_ERROR		BIT(4)
-#define STATUS0_RX_BUFFER_OVERFLOW_ERROR	BIT(3)
-#define STATUS0_TX_PROTOCOL_ERROR		BIT(0)
-
-/* Buffer Status Register */
-#define OA_TC6_REG_BUFFER_STATUS		0x000B
-#define BUFFER_STATUS_TX_CREDITS_AVAILABLE	GENMASK(15, 8)
-#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE	GENMASK(7, 0)
-
-/* Interrupt Mask Register #0 */
-#define OA_TC6_REG_INT_MASK0			0x000C
-#define INT_MASK0_HEADER_ERR_MASK		BIT(5)
-#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK	BIT(4)
-#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK	BIT(3)
-#define INT_MASK0_TX_PROTOCOL_ERR_MASK		BIT(0)
-#define INT_MASK0_ALL_INTERRUPTS                (GENMASK(5, 0) | \
-						 GENMASK(12, 7))
-
-/* PHY Clause 22 registers base address and mask */
-#define OA_TC6_PHY_STD_REG_ADDR_BASE		0xFF00
-#define OA_TC6_PHY_STD_REG_ADDR_MASK		0x1F
-
-/* Control command header */
-#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL	BIT(31)
-#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ	BIT(29)
-#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)
-
-/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the
- * OPEN Alliance specification.
- */
-#define OA_TC6_PHY_C45_PCS_MMS2			2	/* MMD 3 */
-#define OA_TC6_PHY_C45_PMA_PMD_MMS3		3	/* MMD 1 */
-#define OA_TC6_PHY_C45_VS_PLCA_MMS4		4	/* MMD 31 */
-#define OA_TC6_PHY_C45_AUTO_NEG_MMS5		5	/* MMD 7 */
-#define OA_TC6_PHY_C45_POWER_UNIT_MMS6		6	/* MMD 13 */
-
-#define OA_TC6_CTRL_HEADER_SIZE			4
-#define OA_TC6_CTRL_REG_VALUE_SIZE		4
-#define OA_TC6_CTRL_IGNORED_SIZE		4
-#define OA_TC6_CTRL_MAX_REGISTERS		128
-#define OA_TC6_CTRL_SPI_BUF_SIZE		(OA_TC6_CTRL_HEADER_SIZE +\
-						(OA_TC6_CTRL_MAX_REGISTERS *\
-						OA_TC6_CTRL_REG_VALUE_SIZE) +\
-						OA_TC6_CTRL_IGNORED_SIZE)
-#define OA_TC6_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
-
-/* 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 rx_buf_overflow;
-	bool int_flag;
-	bool disable_traffic;
-};
-
-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,
-};
+#include "oa_tc6_std_def.h"
 
-enum oa_tc6_data_start_valid_info {
-	OA_TC6_DATA_START_INVALID,
-	OA_TC6_DATA_START_VALID,
+struct oa_tc6_ts_info_rx {
+	bool rtsa;
+	bool rtsp;
 };
 
-enum oa_tc6_data_end_valid_info {
-	OA_TC6_DATA_END_INVALID,
-	OA_TC6_DATA_END_VALID,
+struct oa_tc6_ts_info_tx {
+	u8 tsc;
 };
 
 static int oa_tc6_spi_transfer(struct oa_tc6 *tc6,
@@ -191,6 +57,168 @@ 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 void oa_tc6_defer_for_hwtstamp(struct oa_tc6 *tc6,
+				      struct sk_buff *skb)
+{
+	if (!tc6->hw_tstamp_enabled)
+		return;
+	if (!skb || (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) == 0)
+		return;
+	if (tc6->ts_config.tx_type != HWTSTAMP_TX_ON) {
+		tc6->tx_hwtstamp_lost++;
+		return;
+	}
+
+	skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+	u8 ret = tc6->tx_ts_idx++;
+
+	if (ret == OA_TC6_TTSCC_REG_ID)
+		tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID;
+	oa_tc6_tsinfo_tx(skb)->tsc = ret;
+
+	list_add_tail(&skb->list, &tc6->tx_ts_skb_q);
+}
+
+static int oa_tc6_process_deferred_skb(struct oa_tc6 *tc6, u8 tsc)
+{
+	struct skb_shared_hwtstamps tstamp;
+	struct oa_tc6_ts_info_tx *ski;
+	struct sk_buff *skb, *tmp;
+	bool found = false;
+	int ret = 0;
+
+	/* Size of data must match OA_TC6_TSTAMP_SZ */
+	u32 data[2];
+
+	list_for_each_entry_safe(skb, tmp, &tc6->tx_ts_skb_q, list) {
+		ski = oa_tc6_tsinfo_tx(skb);
+		if (ski->tsc != tsc)
+			continue;
+		if (found) {
+			dev_warn_ratelimited(&tc6->spi->dev,
+					     "Multiple skbs. tsc = %d\n",
+					     tsc);
+			tc6->tx_hwtstamp_err++;
+		}
+		found = true;
+		list_del(&skb->list);
+
+		/* Retrieve the timestamping info */
+		ret = oa_tc6_read_registers(tc6,
+					    OA_TC6_REG_TTSCA_HIGH +
+					    2 * (tsc - 1), &data[0], 2);
+
+		if (!ret) {
+			tstamp.hwtstamp = ktime_set(data[0], data[1]);
+			skb_tstamp_tx(skb, &tstamp);
+			tc6->tx_hwtstamp_pkts++;
+		}
+
+		dev_kfree_skb(skb);
+	}
+	return ret;
+}
+
+static void oa_tc6_events_handle(struct oa_tc6 *tc6, u32 val)
+{
+	/* Check TX timestamping */
+	if (val & STATUS0_TTSCAA)
+		oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCA_REG_ID);
+
+	if (val & STATUS0_TTSCAB)
+		oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCB_REG_ID);
+
+	if (val & STATUS0_TTSCAC)
+		oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCC_REG_ID);
+}
+
+static void oa_tc6_update_ts_in_rx_skb(struct oa_tc6 *tc6)
+{
+	struct sk_buff *skb = tc6->rx_skb;
+	struct oa_tc6_ts_info_rx *ski;
+	__be32 ts_val[2];
+	u32 ts[2];
+
+	if (!tc6->hw_tstamp_enabled)
+		return;
+	ski = oa_tc6_tsinfo_rx(skb);
+	if (!ski->rtsa)
+		return;
+
+	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));
+}
+
+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, &regval);
+	if (ret)
+		return ret;
+	if (regval & STDCAP_FRAME_TIMESTAMP_CAPABILITY)
+		tc6->hw_tstamp_supported = true;
+	return 0;
+}
+
+/**
+ * oa_tc6_ioctl - generic ioctl interface for MAC-PHY drivers.
+ * @tc6: oa_tc6 struct.
+ * @rq: request from socket interface
+ * @cmd: value to set/get timestamp configuration
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)
+{
+	if (!netif_running(tc6->netdev))
+		return -EINVAL;
+
+	if (cmd == SIOCSHWTSTAMP || cmd == SIOCGHWTSTAMP)
+		return oa_tc6_tstamp_ioctl(tc6, rq, cmd);
+	else
+		return phy_do_ioctl_running(tc6->netdev, rq, cmd);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ioctl);
+
+/**
+ * oa_tc6_set_vend1_mms - Add vendor specific MDIO_MMD to OA TC6 MMS
+ * mapper value.
+ * @tc6: oa_tc6 struct.
+ * @mms: vendor defined MMS value for VEND1 mdio device.
+ */
+void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)
+{
+	tc6->vend1_mms = mms;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_set_vend1_mms);
+
 static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
 					 enum oa_tc6_register_op reg_op)
 {
@@ -355,6 +383,83 @@ int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value)
 }
 EXPORT_SYMBOL_GPL(oa_tc6_read_register);
 
+/**
+ * oa_tc6_read_registers_mms - function for reading multiple consecutive
+ * registers for the given address, memory map selector pair.
+ * @tc6: oa_tc6 struct.
+ * @address: address of the first register to be read in the MAC-PHY.
+ * @mms: Memory map selector for the registers to be read.
+ * @value: values to be read from the starting register address @address.
+ * @length: number of consecutive registers to be read from @address.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+			      u32 value[], u8 length)
+{
+	u32 mms_addr = (u32)mms << 16 | (u32)address;
+
+	return oa_tc6_read_registers(tc6, mms_addr, value, length);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_read_registers_mms);
+
+/**
+ * oa_tc6_read_register_mms - function for reading a MAC-PHY register
+ * for the given address, memory map selector pair.
+ * @tc6: oa_tc6 struct.
+ * @address: register address of the MAC-PHY to be read.
+ * @mms: Memory Map Selector for the given address
+ * @value: value read from the @address register address of the MAC-PHY.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+			     u32 *value)
+{
+	return oa_tc6_read_registers_mms(tc6, address, mms, value, 1);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_read_register_mms);
+
+/**
+ * oa_tc6_write_registers_mms - function for writing multiple consecutive
+ * registers for the given address, memory map selector pair.
+ * @tc6: oa_tc6 struct.
+ * @address: address of the first register to be written in the MAC-PHY.
+ * @mms: memory map Selector for the given register.
+ * @value: values to be written from the starting register address @address.
+ * @length: number of consecutive registers to be written from @address.
+ *
+ * Maximum of 128 consecutive registers can be written starting at @address.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_write_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+			       u32 value[], u8 length)
+{
+	u32 mms_addr = (u32)mms << 16 | (u32)address;
+
+	return oa_tc6_write_registers(tc6, mms_addr, value, length);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_write_registers_mms);
+
+/**
+ * oa_tc6_write_register_mms - function for writing a MAC-PHY register
+ * associated with the given memory map selector.
+ * @tc6: oa_tc6 struct.
+ * @address: register address of the MAC-PHY to be written.
+ * @mms: memory map selector for the given register.
+ * @value: value to be written in the @address register address of
+ * the MAC-PHY.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+			      u32 value)
+{
+	return oa_tc6_write_registers_mms(tc6, address, mms, &value, 1);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms);
+
 /**
  * oa_tc6_write_registers - function for writing multiple consecutive registers.
  * @tc6: oa_tc6 struct.
@@ -444,7 +549,7 @@ static int oa_tc6_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
 				     val);
 }
 
-static int oa_tc6_get_phy_c45_mms(int devnum)
+static int oa_tc6_get_phy_c45_mms(struct oa_tc6 *tc6, int devnum)
 {
 	switch (devnum) {
 	case MDIO_MMD_PCS:
@@ -457,6 +562,8 @@ static int oa_tc6_get_phy_c45_mms(int devnum)
 		return OA_TC6_PHY_C45_AUTO_NEG_MMS5;
 	case MDIO_MMD_POWER_UNIT:
 		return OA_TC6_PHY_C45_POWER_UNIT_MMS6;
+	case MDIO_MMD_VEND1:
+		return tc6->vend1_mms;
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -466,14 +573,14 @@ static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
 				   int regnum)
 {
 	struct oa_tc6 *tc6 = bus->priv;
+	int mms, ret;
 	u32 regval;
-	int ret;
 
-	ret = oa_tc6_get_phy_c45_mms(devnum);
-	if (ret < 0)
-		return ret;
+	mms = oa_tc6_get_phy_c45_mms(tc6, devnum);
+	if (mms < 0)
+		return mms;
 
-	ret = oa_tc6_read_register(tc6, (ret << 16) | regnum, &regval);
+	ret = oa_tc6_read_register_mms(tc6, (u16)regnum, (u16)mms, &regval);
 	if (ret)
 		return ret;
 
@@ -484,13 +591,13 @@ static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
 				    int regnum, u16 val)
 {
 	struct oa_tc6 *tc6 = bus->priv;
-	int ret;
+	int mms;
 
-	ret = oa_tc6_get_phy_c45_mms(devnum);
-	if (ret < 0)
-		return ret;
+	mms = oa_tc6_get_phy_c45_mms(tc6, devnum);
+	if (mms < 0)
+		return mms;
 
-	return oa_tc6_write_register(tc6, (ret << 16) | regnum, val);
+	return oa_tc6_write_register_mms(tc6, (u16)regnum, (u16)mms, val);
 }
 
 static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
@@ -714,6 +821,9 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
 		return ret;
 	}
 
+	if ((value & STATUS0_TTSCA_MASK) != 0)
+		oa_tc6_events_handle(tc6, value & STATUS0_TTSCA_MASK);
+
 	/* Clear the error interrupts status */
 	ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, value);
 	if (ret) {
@@ -796,6 +906,7 @@ 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);
 
+	oa_tc6_update_ts_in_rx_skb(tc6);
 	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;
@@ -810,24 +921,29 @@ static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
 	memcpy(skb_put(tc6->rx_skb, length), payload, length);
 }
 
-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;
+
 	tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu +
-						ETH_HLEN + ETH_FCS_LEN);
+						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;
 
@@ -838,11 +954,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;
 
@@ -887,7 +1003,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 */
@@ -895,7 +1011,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 */
@@ -920,7 +1036,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 */
@@ -974,13 +1090,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);
 
@@ -999,6 +1117,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.
@@ -1008,8 +1127,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
@@ -1030,12 +1151,18 @@ 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 (!(skb_shinfo(tc6->ongoing_tx_skb)->tx_flags &
+		    SKBTX_IN_PROGRESS))
+			kfree_skb(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;
 }
 
@@ -1053,6 +1180,8 @@ static u16 oa_tc6_prepare_spi_tx_buf_for_tx_skbs(struct oa_tc6 *tc6)
 			tc6->ongoing_tx_skb = tc6->waiting_tx_skb;
 			tc6->waiting_tx_skb = NULL;
 			spin_unlock_bh(&tc6->tx_skb_lock);
+			oa_tc6_defer_for_hwtstamp(tc6,
+						  tc6->ongoing_tx_skb);
 		}
 		if (!tc6->ongoing_tx_skb)
 			break;
@@ -1069,7 +1198,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, false);
 
 	while (needed_empty_chunks--) {
 		__be32 *tx_buf = tc6->spi_data_tx_buf +
@@ -1261,6 +1390,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);
 	tc6->waiting_tx_skb = skb;
 	spin_unlock_bh(&tc6->tx_skb_lock);
@@ -1294,6 +1424,9 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
 	SET_NETDEV_DEV(netdev, &spi->dev);
 	mutex_init(&tc6->spi_ctrl_lock);
 	spin_lock_init(&tc6->tx_skb_lock);
+	tc6->vend1_mms = -EOPNOTSUPP;
+	tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID;
+	INIT_LIST_HEAD(&tc6->tx_ts_skb_q);
 
 	/* Set the SPI controller to pump at realtime priority */
 	tc6->spi->rt = true;
@@ -1359,6 +1492,12 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
 		goto phy_exit;
 	}
 
+	ret = oa_tc6_update_standard_capability(tc6);
+	if (ret) {
+		dev_err(&tc6->spi->dev, "Failed to read capability\n");
+		goto phy_exit;
+	}
+
 	ret = devm_request_threaded_irq(&tc6->spi->dev, tc6->spi->irq,
 					oa_tc6_macphy_isr,
 					oa_tc6_macphy_threaded_irq,
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
new file mode 100644
index 00000000000000..0cde5545ee27a0
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for hardware timestamping feature for OPEN Alliance
+ * 10BASE‑T1x MAC‑PHY Serial Interface framework
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#include <linux/hrtimer.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/phylink.h>
+#include <linux/spi/spi.h>
+#include <linux/oa_tc6.h>
+#include <linux/net_tstamp.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/ktime.h>
+#include <linux/errno.h>
+
+#include "oa_tc6_std_def.h"
+
+/**
+ * oa_tc6_ptp_register - Registers clock related callbacks
+ * @tc6: oa_tc6 struct.
+ * @info: Describes a PTP hardware clock
+ *
+ * Description: Vendors are expected to set the hardware timestamp
+ * related callbacks before calling this function.
+ *
+ * Return: 0 on success otherwise failed.
+ *
+ */
+int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info)
+{
+	/* Not supporting hardware timestamp isn't an error */
+	if (!tc6->hw_tstamp_supported)
+		return 0;
+
+	snprintf(info->name, sizeof(info->name), "%s",
+		 "OA TC6 PTP clock");
+	tc6->ptp_clock = ptp_clock_register(info, &tc6->spi->dev);
+	if (IS_ERR(tc6->ptp_clock)) {
+		dev_err(&tc6->spi->dev, "Registration of %s failed",
+			info->name);
+		return -EFAULT;
+	}
+	dev_info(&tc6->spi->dev, "%s registered. index %d", info->name,
+		 ptp_clock_index(tc6->ptp_clock));
+	return 0;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ptp_register);
+
+/**
+ * oa_tc6_ptp_unregister - Unregisters clock related callbacks
+ * @tc6: oa_tc6 struct.
+ */
+void oa_tc6_ptp_unregister(struct oa_tc6 *tc6)
+{
+	if (tc6->ptp_clock)
+		ptp_clock_unregister(tc6->ptp_clock);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ptp_unregister);
+
+MODULE_DESCRIPTION("OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
+MODULE_LICENSE("GPL");
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 00000000000000..3781503be7f562
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -0,0 +1,191 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Register and driver related definitions to support
+ * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework.
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#ifndef OA_TC6_STD_DEF_H
+#define OA_TC6_STD_DEF_H
+
+#include <linux/ptp_clock_kernel.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/spi/spi.h>
+#include <linux/skbuff.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/phy.h>
+
+/* OPEN Alliance TC6 registers */
+/* Standard Capabilities Register */
+#define OA_TC6_REG_STDCAP			0x0002
+#define STDCAP_DIRECT_PHY_REG_ACCESS		BIT(8)
+#define STDCAP_FRAME_TIMESTAMP_CAPABILITY	BIT(6)
+
+/* Reset Control and Status Register */
+#define OA_TC6_REG_RESET			0x0003
+#define RESET_SWRESET				BIT(0)
+
+/* Configuration Register #0 */
+#define OA_TC6_REG_CONFIG0			0x0004
+#define CONFIG0_SYNC				BIT(15)
+#define CONFIG0_ZARFE_ENABLE			BIT(12)
+#define CONFIG0_FTSE_ENABLE			BIT(7)
+#define CONFIG0_FTSS_64BIT_TSTAMP		BIT(6)
+
+/* Status Register #0 */
+#define OA_TC6_REG_STATUS0			0x0008
+#define STATUS0_TTSCAC				BIT(10)
+#define STATUS0_TTSCAB				BIT(9)
+#define STATUS0_TTSCAA				BIT(8)
+#define STATUS0_TTSCA_MASK		GENMASK(10, 8)
+#define STATUS0_RESETC				BIT(6)
+#define STATUS0_HEADER_ERROR			BIT(5)
+#define STATUS0_LOSS_OF_FRAME_ERROR		BIT(4)
+#define STATUS0_RX_BUFFER_OVERFLOW_ERROR	BIT(3)
+#define STATUS0_TX_PROTOCOL_ERROR		BIT(0)
+
+/* Buffer Status Register */
+#define OA_TC6_REG_BUFFER_STATUS		0x000B
+#define BUFFER_STATUS_TX_CREDITS_AVAILABLE	GENMASK(15, 8)
+#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE	GENMASK(7, 0)
+
+/* Interrupt Mask Register #0 */
+#define OA_TC6_REG_INT_MASK0			0x000C
+#define INT_MASK0_TTSCA_MASK			GENMASK(10, 8)
+#define INT_MASK0_HEADER_ERR_MASK		BIT(5)
+#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK	BIT(4)
+#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK	BIT(3)
+#define INT_MASK0_TX_PROTOCOL_ERR_MASK		BIT(0)
+#define INT_MASK0_ALL_INTERRUPTS		(GENMASK(5, 0) | \
+						 GENMASK(12, 7))
+
+/* PHY Clause 22 registers base address and mask */
+#define OA_TC6_PHY_STD_REG_ADDR_BASE		0xFF00
+#define OA_TC6_PHY_STD_REG_ADDR_MASK		0x1F
+
+/* 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)
+#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_TSC_OFFSET		GENMASK(7, 6)
+#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_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)
+
+#define OA_TC6_CTRL_HEADER_SIZE			4
+#define OA_TC6_CTRL_REG_VALUE_SIZE		4
+#define OA_TC6_CTRL_IGNORED_SIZE		4
+#define OA_TC6_CTRL_MAX_REGISTERS		128
+#define OA_TC6_CTRL_SPI_BUF_SIZE		(OA_TC6_CTRL_HEADER_SIZE +\
+						(OA_TC6_CTRL_MAX_REGISTERS *\
+						OA_TC6_CTRL_REG_VALUE_SIZE) +\
+						OA_TC6_CTRL_IGNORED_SIZE)
+#define OA_TC6_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_TSTAMP_SZ			8
+
+#define OA_TC6_TTSCA_REG_ID			1
+#define OA_TC6_TTSCB_REG_ID			2
+#define OA_TC6_TTSCC_REG_ID			3
+
+/* 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 rx_buf_overflow;
+	bool int_flag;
+	bool disable_traffic;
+	struct ptp_clock_info ptp_clock_info;
+	struct hwtstamp_config ts_config;
+	struct list_head tx_ts_skb_q;
+	struct ptp_clock *ptp_clock;
+	bool hw_tstamp_supported;
+	bool hw_tstamp_enabled;
+	u32 tx_hwtstamp_pkts;
+	u32 tx_hwtstamp_lost;
+	u32 tx_hwtstamp_err;
+	int vend1_mms;
+	u8 tx_ts_idx;
+};
+
+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,
+};
+
+int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);
+
+#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 00000000000000..f80f1a4916a3e0
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iopoll.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
+#include <linux/oa_tc6.h>
+
+#include "oa_tc6_std_def.h"
+
+static int oa_tc6_set_hwtstamp_settings(struct oa_tc6 *tc6)
+{
+	u32 cfg0, irqm, status0;
+	int ret;
+
+	ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, &cfg0);
+	if (ret) {
+		dev_err(&tc6->spi->dev, "Failed to read CFG0 register\n");
+		goto out;
+	}
+
+	ret = oa_tc6_read_register(tc6, OA_TC6_REG_INT_MASK0, &irqm);
+	if (ret) {
+		dev_err(&tc6->spi->dev, "failed to read IRQM register\n");
+		goto out;
+	}
+
+	/* Hardware timestamp feature supports 64-bit timestamp only */
+	if (tc6->ts_config.tx_type == HWTSTAMP_TX_ON ||
+	    tc6->ts_config.rx_filter == HWTSTAMP_FILTER_ALL)
+		cfg0 |= (CONFIG0_FTSE_ENABLE |
+			 CONFIG0_FTSS_64BIT_TSTAMP);
+	else
+		cfg0 &= ~CONFIG0_FTSE_ENABLE;
+
+	if (tc6->ts_config.tx_type == HWTSTAMP_TX_ON)
+		irqm &= ~INT_MASK0_TTSCA_MASK;
+	else
+		irqm |= INT_MASK0_TTSCA_MASK;
+
+	/* Clear timestamp related IRQs */
+	status0 = STATUS0_TTSCA_MASK;
+	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;
+	}
+	if (cfg0 & CONFIG0_FTSE_ENABLE)
+		tc6->hw_tstamp_enabled = true;
+	else
+		tc6->hw_tstamp_enabled = false;
+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)
+{
+	if (!netif_running(tc6->netdev))
+		return -EIO;
+
+	if (!tc6->hw_tstamp_supported)
+		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(&tc6->ts_config, cfg);
+
+	/* Supports timestamping all traffic */
+	if (cfg->rx_filter != HWTSTAMP_FILTER_NONE)
+		tc6->ts_config.rx_filter = HWTSTAMP_FILTER_ALL;
+	return oa_tc6_set_hwtstamp_settings(tc6);
+}
+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);
+
+int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)
+{
+	struct kernel_hwtstamp_config kcfg;
+	struct hwtstamp_config tscfg;
+	int ret = 0;
+
+	if (!tc6->hw_tstamp_supported)
+		return -EOPNOTSUPP;
+
+	if (cmd == SIOCSHWTSTAMP) {
+		if (copy_from_user(&tscfg, rq->ifr_data,
+				   sizeof(tscfg)))
+			return -EFAULT;
+
+		if (tscfg.flags)
+			return -EINVAL;
+		hwtstamp_config_to_kernel(&kcfg, &tscfg);
+		ret = oa_tc6_hwtstamp_set(tc6, &kcfg);
+		if (ret)
+			return ret;
+	}
+	if (copy_to_user(rq->ifr_data, &tc6->ts_config,
+			 sizeof(tc6->ts_config)))
+		ret = -EFAULT;
+	return ret;
+}
+
+/**
+ * oa_tc6_get_ts_info - Provides timestamp info for ethtool
+ * @tc6: oa_tc6 struct.
+ * @info: ethtool timestamping info structure
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
+		       struct kernel_ethtool_ts_info *info)
+{
+	if (!tc6->ptp_clock)
+		return ethtool_op_get_ts_info(tc6->netdev, info);
+
+	info->so_timestamping = SOF_TIMESTAMPING_RAW_HARDWARE |
+				SOF_TIMESTAMPING_TX_HARDWARE |
+				SOF_TIMESTAMPING_RX_HARDWARE;
+	info->phc_index = ptp_clock_index(tc6->ptp_clock);
+	info->tx_types = BIT(HWTSTAMP_TX_ON);
+	info->rx_filters = BIT(HWTSTAMP_FILTER_ALL);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_get_ts_info);
+
+MODULE_DESCRIPTION("OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/onsemi/Kconfig b/drivers/net/ethernet/onsemi/Kconfig
new file mode 100644
index 00000000000000..8d72194151ea79
--- /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"
+	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 00000000000000..f3d4eb154313c6
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the onsemi network device drivers.
+#
+
+obj-$(CONFIG_S2500_MACPHY) += s2500/
+
diff --git a/drivers/net/ethernet/onsemi/s2500/Kconfig b/drivers/net/ethernet/onsemi/s2500/Kconfig
new file mode 100644
index 00000000000000..141fbaf1b64187
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/Kconfig
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# onsemi S2500 Driver Support
+#
+
+config S2500_MACPHY
+	tristate "S2500 support"
+	depends on SPI
+	imply NET_SELFTESTS
+	select NCN26000_PHY
+	select OA_TC6
+	help
+	  Support for the onsemi TS2500 MACPHY Ethernet chip.
+	  It works under the framework that conform to OPEN Alliance
+	  10BASE-T1x Serial Interface specification.
+
+	  To compile this driver as a module, choose M here. The module will be
+	  called s2500.
+
diff --git a/drivers/net/ethernet/onsemi/s2500/Makefile b/drivers/net/ethernet/onsemi/s2500/Makefile
new file mode 100644
index 00000000000000..61ec705cdf9f1b
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the onsemi network device drivers.
+#
+obj-$(CONFIG_S2500_MACPHY) := s2500.o
+s2500-objs := s2500_main.o s2500_ethtool.o s2500_ptp.o
+
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
new file mode 100644
index 00000000000000..0b7ef490d7cac3
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
@@ -0,0 +1,360 @@
+// 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 <net/selftests.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		21
+#define S2500_NUM_RMON_STATS		12
+
+struct s2500_reg_map {
+	u16 reg;
+	u16 mms;
+};
+
+static const struct s2500_reg_map s2500_reg_map[S2500_NUM_REGS] = {
+	{ S2500_REG_VS_ONIPVER,      OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_TWEAKS1,      OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_PLCAEXT,      OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_PMATUNE0,     OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_PMATUNE1,     OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_T1SWUPTUNE,   OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_HDD,          OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_DCQ_TOID,     OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_DCQ_SQI,      OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_DCQ_SQI_PLUS, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_HDD_TUNE1,    OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_HDD_TUNE2,    OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_HDD_TUNE3,    OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_WS_STATUS_0,  OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_WS_CTRL_0,    OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_WS_STATUS_1,  OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_WS_CTRL_1,    OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+	{ S2500_REG_VS_MIIMIRQE,     OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_MIIMIRQS,     OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_DIOCFG0,      OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_CHIPID,       OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_TWEAKS2,      OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_MACID0,       OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_MACID1,       OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_NVM_HEALTH,   OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_PTP_SEC,      OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_PTPNSEC,      OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_PTP_SETSEC,   OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_SETNSEC,      OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_PTP_ADJ,      OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_CMPCTL,       OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_CMPSEC,       OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_CMPNSEC,      OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_CMPPER,       OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_CAPCTL,       OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_CAPSEC,       OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_CAPNSEC,      OA_TC6_PHY_C45_VS_MMS12 },
+	{ S2500_REG_VS_BCNCNT,       OA_TC6_PHY_C45_VS_MMS12 },
+};
+
+enum s2500_stat_idx {
+	S2500_MSTOCTECTSTX_IDX = 0,
+	S2500_MSTFRAMESTXOK_IDX,
+	S2500_MSTBCASTTXOK_IDX,
+	S2500_MSTMCASTTXOK_IDX,
+	S2500_MSTFRAMESTX64_IDX,
+	S2500_MSTFRAMESTX65_IDX,
+	S2500_MSTFRAMESTX128_IDX,
+	S2500_MSTFRAMESTX256_IDX,
+	S2500_MSTFRAMESTX512_IDX,
+	S2500_MSTFRAMESTX1024_IDX,
+	S2500_MSTTXUNDEFLOW_IDX,
+	S2500_MSTSINGLECOL_IDX,
+	S2500_MSTMULTICOL_IDX,
+	S2500_MSTEXCESSCOL_IDX,
+	S2500_MSTDEFERREDTX_IDX,
+	S2500_MSTCRSERR_IDX,
+	S2500_MSTOCTECTSRX_IDX,
+	S2500_MSTFRAMESRXOK_IDX,
+	S2500_MSTBCASTRXOK_IDX,
+	S2500_MSTMCASTRXOK_IDX,
+	S2500_MSTFRAMESRX64_IDX,
+	S2500_MSTFRAMESRX65_IDX,
+	S2500_MSTFRAMESRX128_IDX,
+	S2500_MSTFRAMESRX256_IDX,
+	S2500_MSTFRAMESRX512_IDX,
+	S2500_MSTFRAMESRX1024_IDX,
+	S2500_MSTRUNTSERR_IDX,
+	S2500_MSTRXTOOLONG_IDX,
+	S2500_MSTFCSERRS_IDX,
+	S2500_MSTSYMBOLERRS_IDX,
+	S2500_MSTALIGNERRS_IDX,
+	S2500_MSTRXOVERFLOW_IDX,
+	S2500_MSTRXDROPPED_IDX,
+};
+
+static const char s2500_mac_stat_strings[][ETH_GSTRING_LEN] = {
+	"tx_bytes",
+	"tx_frames",
+	"tx_broadcast_frames",
+	"tx_multicast_frames",
+	"tx_underflow_errors",
+	"tx_single_collisions",
+	"tx_multiple_collisions",
+	"tx_excessive_collisions",
+	"tx_deferred_frames",
+	"tx_carrier_sense_errors",
+	"rx_bytes",
+	"rx_frames",
+	"rx_broadcast_frames",
+	"rx_multicast_frames",
+	"rx_runts",
+	"rx_oversize_frames",
+	"rx_crc_errors",
+	"rx_symbol_errors",
+	"rx_alignment_errors",
+	"rx_busy_drops",
+	"rx_mismatch_drops",
+};
+
+static const u8 s2500_mac_stat_map[S2500_NUM_MAC_STATS] = {
+	S2500_MSTOCTECTSTX_IDX,
+	S2500_MSTFRAMESTXOK_IDX,
+	S2500_MSTBCASTTXOK_IDX,
+	S2500_MSTMCASTTXOK_IDX,
+	S2500_MSTTXUNDEFLOW_IDX,
+	S2500_MSTSINGLECOL_IDX,
+	S2500_MSTMULTICOL_IDX,
+	S2500_MSTEXCESSCOL_IDX,
+	S2500_MSTDEFERREDTX_IDX,
+	S2500_MSTCRSERR_IDX,
+	S2500_MSTOCTECTSRX_IDX,
+	S2500_MSTFRAMESRXOK_IDX,
+	S2500_MSTBCASTRXOK_IDX,
+	S2500_MSTMCASTRXOK_IDX,
+	S2500_MSTRUNTSERR_IDX,
+	S2500_MSTRXTOOLONG_IDX,
+	S2500_MSTFCSERRS_IDX,
+	S2500_MSTSYMBOLERRS_IDX,
+	S2500_MSTALIGNERRS_IDX,
+	S2500_MSTRXOVERFLOW_IDX,
+	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;
+	int ret;
+
+	regs = kmalloc_array(S2500_NUM_STAT_REGS, sizeof(u32), GFP_KERNEL);
+	if (!regs)
+		return;
+
+	ret = oa_tc6_read_registers_mms(priv->tc6, S2500_REG_MAC_STOCTECTSTXL,
+					OA_TC6_PHY_C45_MAC_MMS1, regs,
+					S2500_NUM_STAT_REGS);
+	if (ret)
+		goto out;
+
+	rptr = regs;
+
+	/* TX bytes is a 64-bit register that spans over two 32-bit regs
+	 * note: HW does auto-freeze when reading LSB and un-freeze on MSB
+	 */
+	*(data++) += ((u64)*rptr) | (((u64)*(rptr + 1)) << 32);
+
+	/* run until the next 64-bit register */
+	for (rptr += 2; (rptr - regs) < STAT_REG_OFFSET(OCTECTSRXL); ++rptr)
+		*(data++) += *rptr;
+
+	/* RX bytes is a 64-bit register that spans over two 32-bit regs
+	 * note: HW does auto-freeze when reading LSB and un-freeze on MSB
+	 */
+	*(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)
+{
+	strscpy(info->driver, DRV_NAME, sizeof(info->driver));
+	strscpy(info->bus_info, dev_name(&ndev->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;
+	case ETH_SS_TEST:
+		return net_selftest_get_count();
+	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;
+	case ETH_SS_TEST:
+		net_selftest_get_strings(buf);
+		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_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;
+
+	s2500_update_stats(priv);
+
+	memset(rmon_stats, 0, sizeof(*rmon_stats));
+	rmon_stats->src = ETHTOOL_MAC_STATS_SRC_AGGREGATE;
+	rmon_stats->undersize_pkts = priv->stats_data[S2500_MSTRUNTSERR_IDX];
+	rmon_stats->oversize_pkts = priv->stats_data[S2500_MSTRXTOOLONG_IDX];
+	rmon_stats->fragments = priv->stats_data[S2500_MSTFCSERRS_IDX];
+	rmon_stats->jabbers = priv->stats_data[S2500_MSTALIGNERRS_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);
+	u32 *pbuff = (u32 *)p;
+	u16 reg, mms;
+	int ret = 0;
+	u32 val;
+	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, reg, mms,
+					       &val);
+		if (ret)
+			continue;
+		*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_rmon_stats = s2500_rmon_stats,
+	.get_ts_info = s2500_get_ts_info,
+	.get_regs_len = s2500_get_regs_len,
+	.get_regs = s2500_get_regs,
+	.self_test = net_selftest,
+};
+
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 00000000000000..2d9c0b3ad39bcb
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h
@@ -0,0 +1,223 @@
+/* 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/hrtimer.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/phylink.h>
+#include <linux/spi/spi.h>
+#include <linux/oa_tc6.h>
+#include <linux/net_tstamp.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/ktime.h>
+#include <linux/errno.h>
+
+#define DRV_NAME			"s2500"
+
+#define S2500_N_MCAST_FILTERS		3
+
+/* SPI OID and model register */
+#define S2500_REG_SPI_PHYID		0x1
+
+#define S2500_SPI_PHYID_OUI_SHIFT	10
+#define S2500_SPI_PHYID_OUI_MASK	GENMASK(31, S2500_SPI_PHYID_OUI_SHIFT)
+#define S2500_SPI_PHYID_OUI_MASK0	GENMASK(21, 16)
+#define S2500_SPI_PHYID_OUI_MASK1	GENMASK(15, 8)
+#define S2500_SPI_PHYID_OUI_MASK2	GENMASK(7, 0)
+/* SPI configuration register #0 */
+#define S2500_REG_SPI_CFG0		0x4
+
+#define S2500_SPI_CFG0_SYNC_BIT		BIT(15)
+#define S2500_SPI_CFG0_TXCTHRESH_SHIFT	10
+#define S2500_SPI_CFG0_RXCTE_BIT	BIT(8)
+#define S2500_SPI_CFG0_FTSS_64_BIT	BIT(6)
+#define S2500_SPI_CFG0_CPS_SHIFT	0
+
+#define S2500_TXCTHRESH_8		0x2
+
+#define S2500_CPS_64			0x6
+
+/* SPI status register #0 */
+#define S2500_REG_SPI_ST0		0x8
+
+#define S2500_SPI_ST0_CDPE_BIT		BIT(12)
+#define S2500_SPI_ST0_TXFCSE_BIT	BIT(11)
+#define S2500_SPI_ST0_TTSCAC_BIT	BIT(10)
+#define S2500_SPI_ST0_TTSCAB_BIT	BIT(9)
+#define S2500_SPI_ST0_TTSCAA_BIT	BIT(8)
+#define S2500_SPI_ST0_RESETC_BIT	BIT(6)
+#define S2500_SPI_ST0_HDRE_BIT		BIT(5)
+#define S2500_SPI_ST0_LOFE_BIT		BIT(4)
+#define S2500_SPI_ST0_RXBOE_BIT		BIT(3)
+#define S2500_SPI_ST0_TXBUE_BIT		BIT(2)
+#define S2500_SPI_ST0_TXBOE_BIT		BIT(1)
+#define S2500_SPI_ST0_TXPE_BIT		BIT(0)
+
+/* SPI IRQ enable register #0 (use the S2500_SPI_ST0_*_BIT constants) */
+#define S2500_REG_SPI_IRQM0		0xc
+
+/* SPI buffer status register */
+#define S2500_REG_SPI_BUFST		0xb
+
+#define S2500_REG_MAC_CTRL		0x0
+
+#define S2500_MAC_CTRL_MCSF_BIT		BIT(18)
+#define S2500_MAC_CTRL_ADRF_BIT		BIT(16)
+#define S2500_MAC_CTRL_FCSA_BIT		BIT(8)
+#define S2500_MAC_CTRL_TXEN_BIT		BIT(1)
+#define S2500_MAC_CTRL_RXEN_BIT		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_EN_BIT	BIT(31)
+
+/* MAC statistic registers */
+#define S2500_REG_MAC_STOCTECTSTXL	48
+#define S2500_REG_MAC_STOCTECTSTXH	49
+#define S2500_REG_MAC_STFRAMESTXOK	50
+#define S2500_REG_MAC_STBCASTTXOK	51
+#define S2500_REG_MAC_STMCASTTXOK	52
+#define S2500_REG_MAC_STFRAMESTX64	53
+#define S2500_REG_MAC_STFRAMESTX65	54
+#define S2500_REG_MAC_STFRAMESTX128	55
+#define S2500_REG_MAC_STFRAMESTX256	56
+#define S2500_REG_MAC_STFRAMESTX512	57
+#define S2500_REG_MAC_STFRAMESTX1024	58
+#define S2500_REG_MAC_STTXUNDEFLOW	59
+#define S2500_REG_MAC_STSINGLECOL	60
+#define S2500_REG_MAC_STMULTICOL	61
+#define S2500_REG_MAC_STEXCESSCOL	62
+#define S2500_REG_MAC_STDEFERREDTX	63
+#define S2500_REG_MAC_STCRSERR		64
+#define S2500_REG_MAC_STOCTECTSRXL	65
+#define S2500_REG_MAC_STOCTECTSRXH	66
+#define S2500_REG_MAC_STFRAMESRXOK	67
+#define S2500_REG_MAC_STBCASTRXOK	68
+#define S2500_REG_MAC_STMCASTRXOK	69
+#define S2500_REG_MAC_STFRAMESRX64	70
+#define S2500_REG_MAC_STFRAMESRX65	71
+#define S2500_REG_MAC_STFRAMESRX128	72
+#define S2500_REG_MAC_STFRAMESRX256	73
+#define S2500_REG_MAC_STFRAMESRX512	74
+#define S2500_REG_MAC_STFRAMESRX1024	75
+#define S2500_REG_MAC_STRUNTSERR	76
+#define S2500_REG_MAC_STRXTOOLONG	77
+#define S2500_REG_MAC_STFCSERRS		78
+#define S2500_REG_MAC_STSYMBOLERRS	79
+#define S2500_REG_MAC_STALIGNERRS	80
+#define S2500_REG_MAC_STRXOVERFLOW	81
+#define S2500_REG_MAC_STRXDROPPED	82
+
+/* First/last statistic register for sequential access */
+#define S2500_REG_MAC_FIRST_STAT	S2500_REG_MAC_STOCTECTSTXL
+#define S2500_REG_MAC_LAST_STAT		S2500_REG_MAC_STRXDROPPED
+
+#define S2500_NUM_STAT_REGS \
+	(S2500_REG_MAC_LAST_STAT - S2500_REG_MAC_FIRST_STAT + 1)
+#define S2500_NUM_STAT_VARS		(S2500_NUM_STAT_REGS - 2)
+
+/* Vendor specific MMS4 registers */
+#define S2500_REG_VS_ONIPVER		0x8000
+#define S2500_REG_VS_TWEAKS1		0x8001
+#define S2500_REG_VS_PLCAEXT		0x8002
+#define S2500_REG_VS_PMATUNE0		0x8003
+#define S2500_REG_VS_PMATUNE1		0x8004
+#define S2500_REG_VS_T1SWUPTUNE		0x8007
+#define S2500_REG_VS_HDD		0xCC01
+#define S2500_REG_VS_DCQ_TOID		0xCC02
+#define S2500_REG_VS_DCQ_SQI		0xCC03
+#define S2500_REG_VS_DCQ_SQI_PLUS	0xCC04
+#define S2500_REG_VS_HDD_TUNE1		0xCD00
+#define S2500_REG_VS_HDD_TUNE2		0xCD01
+#define S2500_REG_VS_HDD_TUNE3		0xCD02
+#define S2500_REG_VS_WS_STATUS_0	0xD000
+#define S2500_REG_VS_WS_CTRL_0		0xD001
+#define S2500_REG_VS_WS_STATUS_1	0xD100
+#define S2500_REG_VS_WS_CTRL_1		0xD101
+
+/* Vendor specific MMS12 registers */
+#define S2500_REG_VS_MIIMIRQE		0x10
+
+/* MIIM IRQ status register */
+#define S2500_REG_VS_MIIMIRQS		0x11
+#define MIIM_IRQ_STATUS_RSTS_SHIFT	15
+#define MIIM_IRQ_STATUS_RSTS		BIT(MIIM_IRQ_STATUS_RSTS_SHIFT)
+
+#define S2500_REG_VS_DIOCFG0		0x12
+
+#define S2500_REG_VS_CHIPID		0x1000
+#define S2500_REG_VS_TWEAKS2		0x1001
+
+/* Permanent MAC address register */
+#define S2500_REG_VS_MACID0		0x1002
+#define S2500_VS_MACID0_BYTE4_MASK	GENMASK(15, 8)
+#define S2500_VS_MACID0_BYTE5_MASK	GENMASK(7, 0)
+
+#define S2500_REG_VS_MACID1		0x1003
+#define S2500_VS_MACID1_BYTE3_MASK	GENMASK(7, 0)
+#define S2500_REG_VS_NVM_HEALTH		0x1005
+
+/* PTP registers */
+#define S2500_REG_VS_PTP_SEC		0x1010
+#define S2500_REG_VS_PTP_SETSEC		0x1012
+#define S2500_REG_VS_PTP_ADJ		0x1014
+#define S2500_REG_VS_PTPNSEC		0x1011
+#define S2500_REG_VS_SETNSEC		0x1013
+#define S2500_REG_VS_CMPCTL		0x1015
+#define S2500_REG_VS_CMPSEC		0x1016
+#define S2500_REG_VS_CMPNSEC		0x1017
+#define S2500_REG_VS_CMPPER		0x1018
+#define S2500_REG_VS_CAPCTL		0x1019
+#define S2500_REG_VS_CAPSEC		0x101A
+#define S2500_REG_VS_CAPNSEC		0x101B
+#define S2500_REG_VS_BCNCNT		0x101C
+
+/* prototypes / forward declarations */
+extern const struct ethtool_ops s2500_ethtool_ops;
+
+struct s2500_info {
+	struct device *dev;
+	struct net_device *ndev;
+
+	/* To have atomic set_rx_mode operation */
+	spinlock_t lock;
+
+	/* To have atomic operation when time is adjusted */
+	struct mutex ptp_adj_lock;
+	struct task_struct *thread;
+
+	/* global state variables */
+	bool event_pending;
+	unsigned int ndev_flags;
+	bool rx_flags_upd;
+
+	signed long poll_jiff;
+
+	struct spi_device *spi;
+
+	/* statistic counters variables */
+	u64 stats_data[S2500_NUM_STAT_VARS];
+
+	/* PTP related variables */
+	struct ptp_clock_info ptp_clock_info;
+	void *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 00000000000000..2d81b44f8c76c2
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
@@ -0,0 +1,649 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's S2500 10BASE-T1S MAC-PHY driver
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/if_ether.h>
+#include <linux/irqchip.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/bitrev.h>
+#include <linux/phy.h>
+
+#include "s2500_hw_def.h"
+
+/* S2500 functions & definitions */
+
+#define S2500_STATUS0_MASK	(S2500_SPI_ST0_CDPE_BIT | \
+				S2500_SPI_ST0_TXFCSE_BIT | \
+				S2500_SPI_ST0_TTSCAC_BIT | \
+				S2500_SPI_ST0_TTSCAB_BIT | \
+				S2500_SPI_ST0_TTSCAA_BIT | \
+				S2500_SPI_ST0_RESETC_BIT | \
+				S2500_SPI_ST0_HDRE_BIT | \
+				S2500_SPI_ST0_LOFE_BIT | \
+				S2500_SPI_ST0_RXBOE_BIT | \
+				S2500_SPI_ST0_TXBUE_BIT | \
+				S2500_SPI_ST0_TXBOE_BIT | \
+				S2500_SPI_ST0_TXPE_BIT)
+
+/* 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)
+{
+	u16 mms = OA_TC6_PHY_C45_VS_MMS12;
+	u8 addr[ETH_ALEN];
+	u32 mac1 = 0;
+	u32 mac0 = 0;
+	u32 val;
+	int ret;
+
+	ret = oa_tc6_read_register_mms(priv->tc6, S2500_REG_VS_MACID1,
+				       mms, &mac1);
+	if (ret)
+		return ret;
+
+	ret = oa_tc6_read_register_mms(priv->tc6, S2500_REG_VS_MACID0,
+				       mms, &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_SPI_PHYID,
+				   &val);
+	if (ret)
+		return ret;
+
+	val = (val & S2500_SPI_PHYID_OUI_MASK) >>
+	       S2500_SPI_PHYID_OUI_SHIFT;
+
+	/* Mapping for bits from PHY ID register to OUI as give below.
+	 * - PHY-ID[10:17] provides OUI[16:23]
+	 * - PHY-ID[18:25] provides OUI[8:15]
+	 * - PHY-ID[26:31] provides OUI[2:7]
+	 *
+	 * Hardware presents OUI such a way that this requires not just
+	 * byte level swap, bit level swap is needed as well.
+	 */
+	addr[0] = bitrev8(FIELD_GET(S2500_SPI_PHYID_OUI_MASK0, val));
+	addr[1] = bitrev8(FIELD_GET(S2500_SPI_PHYID_OUI_MASK1, val));
+	addr[2] = bitrev8(FIELD_GET(S2500_SPI_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);
+	u16 mms = OA_TC6_PHY_C45_MAC_MMS1;
+	u32 val;
+	int ret;
+
+	/* Set unicast address filter */
+	ret = oa_tc6_write_register_mms(priv->tc6,
+					S2500_REG_MAC_ADDRMASKL(0),
+					mms, 0xffffffff);
+	if (ret)
+		return ret;
+
+	ret = oa_tc6_write_register_mms(priv->tc6,
+					S2500_REG_MAC_ADDRMASKH(0),
+					mms, 0xffff);
+	if (ret)
+		return ret;
+
+	val = get_unaligned_be32(&mac[2]);
+
+	ret = oa_tc6_write_register_mms(priv->tc6,
+					S2500_REG_MAC_ADDRFILTL(0),
+					mms, val);
+	if (ret)
+		return ret;
+
+	val = S2500_MAC_ADDRFILT_EN_BIT | get_unaligned_be16(mac);
+
+	return oa_tc6_write_register_mms(priv->tc6,
+					 S2500_REG_MAC_ADDRFILTH(0),
+					 mms, val);
+}
+
+static int s2500_mac_ctrl_modify_bits(struct s2500_info *priv,
+				      u32 in_bits, bool clr)
+{
+	u16 mms = OA_TC6_PHY_C45_MAC_MMS1;
+	u32 reg = S2500_REG_MAC_CTRL;
+	u32 rval = 0;
+	u32 wval = 0;
+	int ret;
+
+	ret = oa_tc6_read_register_mms(priv->tc6, reg, mms, &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, reg,
+						mms, wval);
+	return ret;
+}
+
+static int s2500_init(struct s2500_info *priv)
+{
+	u32 val;
+	int ret;
+
+	/* Configure MAC-PHY's behavior */
+	val = S2500_SPI_CFG0_SYNC_BIT | S2500_SPI_CFG0_RXCTE_BIT |
+	      (S2500_TXCTHRESH_8 << S2500_SPI_CFG0_TXCTHRESH_SHIFT) |
+	      (S2500_CPS_64 << S2500_SPI_CFG0_CPS_SHIFT) |
+	      S2500_SPI_CFG0_FTSS_64_BIT;
+
+	ret = oa_tc6_write_register(priv->tc6, S2500_REG_SPI_CFG0, val);
+	if (ret)
+		return ret;
+
+	val = (u32)~(S2500_SPI_ST0_RESETC_BIT |
+		     S2500_SPI_ST0_HDRE_BIT | S2500_SPI_ST0_LOFE_BIT |
+		     S2500_SPI_ST0_RXBOE_BIT | S2500_SPI_ST0_TXBOE_BIT |
+		     S2500_SPI_ST0_TXPE_BIT);
+
+	ret = oa_tc6_write_register(priv->tc6, S2500_REG_SPI_IRQM0, val);
+	if (ret)
+		return ret;
+
+	/* Program the source MAC address into the device */
+	ret = s2500_set_mac_filter(priv->ndev, priv->ndev->dev_addr);
+
+	val = S2500_MAC_CTRL_ADRF_BIT | S2500_MAC_CTRL_FCSA_BIT;
+
+	return s2500_mac_ctrl_modify_bits(priv, val, false);
+}
+
+static void s2500_shutdown(struct s2500_info *priv)
+{
+	u32 val = S2500_MAC_CTRL_TXEN_BIT | S2500_MAC_CTRL_RXEN_BIT;
+	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_BIT;
+	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)
+{
+	u16 mms = OA_TC6_PHY_C45_MAC_MMS1;
+	int i, ret = 0;
+	u16 addr;
+	u32 val;
+
+	if ((rx_flags & IFF_ALLMULTI) ||
+	    (netdev_mc_count(priv->ndev) > S2500_N_MCAST_FILTERS)) {
+		/* Disable multicast filter */
+		ret = s2500_mac_ctrl_modify_bits(priv,
+						 S2500_MAC_CTRL_MCSF_BIT,
+						 true);
+		if (ret)
+			return ret;
+
+		/* Accept all multicasts */
+		addr = S2500_REG_MAC_ADDRMASKL(1);
+		ret = oa_tc6_write_register_mms(priv->tc6, addr,
+						mms, 0);
+		if (ret)
+			return ret;
+
+		addr = S2500_REG_MAC_ADDRMASKH(1);
+		ret = oa_tc6_write_register_mms(priv->tc6, addr,
+						mms, 0x100);
+		if (ret)
+			return ret;
+
+		addr = S2500_REG_MAC_ADDRFILTL(1);
+		ret = oa_tc6_write_register_mms(priv->tc6, addr,
+						mms, 0);
+		if (ret)
+			return ret;
+
+		val = S2500_MAC_ADDRFILT_EN_BIT | 0x00000100;
+		addr = S2500_REG_MAC_ADDRFILTH(1);
+		ret = oa_tc6_write_register_mms(priv->tc6, addr,
+						mms, val);
+	} else if (netdev_mc_count(priv->ndev) == 0) {
+		/* Enable multicast filter */
+		ret = s2500_mac_ctrl_modify_bits(priv,
+						 S2500_MAC_CTRL_MCSF_BIT,
+						 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,
+							addr, mms, 0);
+			if (ret)
+				return ret;
+		}
+	} else {
+		struct netdev_hw_addr *ha;
+		u32 addrh, addrl;
+
+		/* Disable multicast filter */
+		ret = s2500_mac_ctrl_modify_bits(priv,
+						 S2500_MAC_CTRL_MCSF_BIT,
+						 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,
+							addr, mms, 0);
+			if (ret)
+				return ret;
+		}
+
+		i = 1;
+		netdev_for_each_mc_addr(ha, priv->ndev) {
+			if (i > S2500_N_MCAST_FILTERS)
+				break;
+
+			addrh = S2500_MAC_ADDRFILT_EN_BIT |
+				 get_unaligned_be16(ha->addr);
+			addrl = get_unaligned_be32(&ha->addr[2]);
+
+			addr = S2500_REG_MAC_ADDRFILTH(i);
+			ret = oa_tc6_write_register_mms(priv->tc6,
+							addr, mms,
+							addrh);
+			if (ret)
+				return ret;
+
+			addr = S2500_REG_MAC_ADDRFILTL(i);
+			ret = oa_tc6_write_register_mms(priv->tc6,
+							addr, mms,
+							addrl);
+			if (ret)
+				return ret;
+
+			addr = S2500_REG_MAC_ADDRMASKL(i);
+			ret = oa_tc6_write_register_mms(priv->tc6,
+							addr, mms,
+							0xffffffff);
+			if (ret)
+				return ret;
+
+			addr = S2500_REG_MAC_ADDRMASKH(i);
+			ret = oa_tc6_write_register_mms(priv->tc6,
+							addr, mms,
+							0xffff);
+			if (ret)
+				return ret;
+			i++;
+		}
+	}
+	return ret;
+}
+
+/* Deferred function for applying RX mode flags in non-atomic context */
+static int s2500_rx_mode_update(struct s2500_info *priv)
+{
+	unsigned int rx_flags;
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	rx_flags = priv->ndev_flags;
+	priv->rx_flags_upd = false;
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	ret = s2500_set_promiscuous_mode(priv, rx_flags);
+	if (ret)
+		goto out;
+
+	ret = s2500_set_multicast_mode(priv, rx_flags);
+out:
+	return ret;
+}
+
+static void s2500_set_rx_mode(struct net_device *ndev)
+{
+	struct s2500_info *priv = netdev_priv(ndev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	priv->rx_flags_upd = true;
+	priv->ndev_flags = ndev->flags;
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	if (priv->thread)
+		wake_up_process(priv->thread);
+}
+
+static int s2500_set_mac_address(struct net_device *ndev, void *p)
+{
+	struct sockaddr *addr = p;
+
+	if (!is_valid_ether_addr(addr->sa_data))
+		return -EADDRNOTAVAIL;
+
+	eth_hw_addr_set(ndev, addr->sa_data);
+	return s2500_set_mac_filter(ndev, addr->sa_data);
+}
+
+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_ioctl(struct net_device *ndev, struct ifreq *rq,
+		       int cmd)
+{
+	struct s2500_info *priv = netdev_priv(ndev);
+
+	return oa_tc6_ioctl(priv->tc6, rq, cmd);
+}
+
+static void s2500_process_events(struct s2500_info *priv)
+{
+	u32 val;
+	int ret;
+
+	if (!priv->event_pending)
+		return;
+
+	priv->event_pending = false;
+
+	ret = oa_tc6_read_register(priv->tc6, S2500_REG_SPI_ST0, &val);
+	if (ret) {
+		dev_err(&priv->spi->dev, "Error reading ST0 register");
+		return;
+	}
+}
+
+static int s2500_thread_fun(void *data)
+{
+	struct s2500_info *priv = data;
+	bool update_rx_mode = false;
+	unsigned long flags;
+	signed long tout;
+	int ret = 0;
+
+	tout = priv->poll_jiff;
+
+	do {
+		if (update_rx_mode) {
+			ret = s2500_rx_mode_update(priv);
+			if (unlikely(ret)) {
+				dev_err(&priv->spi->dev,
+					"Failed to set new RX mode");
+				break;
+			}
+		}
+
+		if (tout == 0) {
+			tout = priv->poll_jiff;
+
+			/* Force checking the status register */
+			priv->event_pending = true;
+		}
+
+		s2500_process_events(priv);
+
+		spin_lock_irqsave(&priv->lock, flags);
+		__set_current_state(TASK_INTERRUPTIBLE);
+
+		update_rx_mode = priv->rx_flags_upd;
+		ret = update_rx_mode;
+
+		spin_unlock_irqrestore(&priv->lock, flags);
+
+		if (!ret)
+			tout = schedule_timeout(tout);
+		else
+			set_current_state(TASK_RUNNING);
+	} while (!kthread_should_stop());
+	return 0;
+}
+
+static int s2500_open(struct net_device *ndev)
+{
+	struct s2500_info *priv = netdev_priv(ndev);
+	int ret = 0;
+	u32 val;
+
+	dev_dbg(&ndev->dev, "%s", "s2500_open");
+	phy_start(priv->ndev->phydev);
+
+	priv->thread = kthread_run(s2500_thread_fun, priv,
+				   DRV_NAME "/%s:%d",
+				   dev_name(&priv->spi->dev),
+				   spi_get_chipselect(priv->spi, 0));
+
+	if (IS_ERR(priv->thread)) {
+		ret = PTR_ERR(priv->thread);
+	} else {
+		val = S2500_MAC_CTRL_TXEN_BIT | S2500_MAC_CTRL_RXEN_BIT;
+		ret = s2500_mac_ctrl_modify_bits(priv, val, false);
+
+		netif_start_queue(priv->ndev);
+	}
+	return ret;
+}
+
+static int s2500_stop(struct net_device *ndev)
+{
+	struct s2500_info *priv = netdev_priv(ndev);
+
+	dev_dbg(&ndev->dev, "%s", "s2500_stop");
+
+	s2500_shutdown(priv);
+
+	kthread_stop(priv->thread);
+	priv->thread = NULL;
+
+	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     = s2500_set_rx_mode,
+	.ndo_eth_ioctl       = s2500_ioctl,
+	.ndo_hwtstamp_get    = s2500_hwtstamp_get,
+	.ndo_hwtstamp_set    = s2500_hwtstamp_set,
+};
+
+static int s2500_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct net_device *ndev;
+	struct s2500_info *priv;
+	u32 val;
+	int ret;
+
+	if (spi->irq < 0)
+		return -ENODEV;
+
+	ndev = devm_alloc_etherdev(dev, sizeof(struct s2500_info));
+	if (!ndev)
+		return -ENOMEM;
+
+	priv = netdev_priv(ndev);
+	priv->ndev = ndev;
+	priv->spi = spi;
+	priv->dev = dev;
+
+	SET_NETDEV_DEV(ndev, dev);
+
+	spin_lock_init(&priv->lock);
+	mutex_init(&priv->ptp_adj_lock);
+	ndev->irq = spi->irq;
+
+	spi->dev.platform_data = priv;
+	spi_set_drvdata(spi, priv);
+
+	ndev->netdev_ops = &s2500_netdev_ops;
+	ndev->ethtool_ops = &s2500_ethtool_ops;
+	ndev->if_port = IF_PORT_10BASET;
+	ndev->priv_flags |= IFF_UNICAST_FLT;
+	ndev->hw_features = NETIF_F_RXALL;
+
+	priv->poll_jiff = HZ * 5; /* Poll interval */
+
+	priv->tc6 = oa_tc6_init(spi, ndev);
+	if (!priv->tc6) {
+		dev_err(&spi->dev, "OA TC6 init failed");
+		return -ENODEV;
+	}
+	oa_tc6_set_vend1_mms(priv->tc6, OA_TC6_PHY_C45_VS_MMS12);
+
+	/* Clear RSTS, if set */
+	oa_tc6_read_register_mms(priv->tc6, S2500_REG_VS_MIIMIRQS,
+				 OA_TC6_PHY_C45_VS_MMS12, &val);
+	val &= MIIM_IRQ_STATUS_RSTS;
+	if (val != 0)
+		oa_tc6_write_register_mms(priv->tc6,
+					  S2500_REG_VS_MIIMIRQS,
+					  OA_TC6_PHY_C45_VS_MMS12,
+					  MIIM_IRQ_STATUS_RSTS);
+
+	/* Acknowledge all IRQ status bits */
+	ret = oa_tc6_read_register(priv->tc6, S2500_REG_SPI_ST0, &val);
+	if (!ret) {
+		u32 mask = S2500_STATUS0_MASK;
+
+		val &= mask;
+		oa_tc6_write_register(priv->tc6, S2500_REG_SPI_ST0, val);
+	}
+
+	ret = device_get_ethdev_address(priv->dev, ndev);
+	if (ret)
+		ret = s2500_read_mac_from_nvmem(priv);
+
+	if (ret) {
+		eth_hw_addr_random(ndev);
+		dev_warn(&spi->dev, "Using random MAC address %pM",
+			 ndev->dev_addr);
+	}
+
+	ret = s2500_init(priv);
+	if (unlikely(ret)) {
+		dev_err(&spi->dev, "failed to s2500_init the device");
+		goto err_reg_read;
+	}
+
+	/* Configure PTP if the model supports it */
+	ret = s2500_ptp_register(priv);
+	if (unlikely(ret))
+		goto err_reg_read;
+
+	ret = register_netdev(ndev);
+	if (ret) {
+		dev_err(&spi->dev, "failed to register netdev\n");
+		ret = -ENODEV;
+
+		goto err_reg_read;
+	}
+	return 0;
+
+err_reg_read:
+	dev_err(&spi->dev, "could not initialize macphy");
+	return ret;
+}
+
+static void s2500_remove(struct spi_device *spi)
+{
+	struct s2500_info *priv = spi->dev.platform_data;
+
+	dev_dbg(&priv->ndev->dev, "%s", "s2500_remove");
+
+	oa_tc6_ptp_unregister(priv->tc6);
+	unregister_netdev(priv->ndev);
+	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("Piergiorgio Beruto <pier.beruto@onsemi.com>");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
+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 00000000000000..7da660cb5b190a
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
@@ -0,0 +1,250 @@
+// 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];
+	int ret;
+
+	ptp_read_system_prets(ptp_sts);
+	ret = oa_tc6_read_registers_mms(priv->tc6,
+					S2500_REG_VS_PTP_SEC,
+					OA_TC6_PHY_C45_VS_MMS12,
+					&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_set_time64(struct ptp_clock_info *ptp,
+				const struct timespec64 *ts)
+{
+	struct s2500_info *priv = container_of(ptp, struct s2500_info,
+					       ptp_clock_info);
+	u32 data[2];
+
+	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);
+
+	return oa_tc6_write_registers_mms(priv->tc6,
+					  S2500_REG_VS_PTP_SETSEC,
+					  OA_TC6_PHY_C45_VS_MMS12,
+					  &data[0], 2);
+}
+
+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);
+	u32 sign_bit = 0;
+	long adj;
+	u32 val;
+	u64 ppm;
+
+	if (scaled_ppm < 0) {
+		/* split sign / mod */
+		sign_bit = 1U << 31;
+		scaled_ppm = ~scaled_ppm + 1;
+	}
+
+	/*
+	 * Convert unsigned scaled_ppm to atto-seconds per clock cycles.
+	 * The scaled_ppm format is Qx.16 --> 1 lsb = 1/65536 ppm.
+	 * The clock period of the S2500 is 8ns (125 MHz), so 1 lsb of
+	 * adj register LSB is 1 atto-sec / 8ns = 0.000125 ppm.
+	 * Represented in Qx.16 format, this is 0.000125 * 2^16 = 8(.192)
+	 * To convert scaled_ppm into a register value we need to divide
+	 * it by the LSB value, hence adj = (scaled_ppm * 1000) / 8192 to
+	 * minimize the precision loss due to the integer arithmetic.
+	 * That further reduces to (scaled_ppm * 125) / 1024.
+	 */
+	ppm = (u64)scaled_ppm * 125;
+	do_div(ppm, 1024);
+	adj = (long)ppm;
+
+	/* check overflow */
+	if (adj >= (1L << 28))
+		return -ERANGE;
+
+	val = (u32)adj | sign_bit;
+	return oa_tc6_write_register_mms(priv->tc6, S2500_REG_VS_PTP_ADJ,
+					 OA_TC6_PHY_C45_VS_MMS12, val);
+}
+
+static int s2500_use_settime(struct ptp_clock_info *ptp,
+			     struct timespec64 now, s64 rem)
+{
+	struct ptp_system_timestamp sts;
+	struct timespec64 delta_ts;
+	struct timespec64 target;
+	int ret;
+
+	memset(&sts, 0, sizeof(sts));
+	ret = ptp->gettimex64(ptp, &now, &sts);
+	if (ret)
+		return ret;
+	if (rem >= 0) {
+		delta_ts = ns_to_timespec64(rem);
+		target = timespec64_add(now, delta_ts);
+	} else {
+		delta_ts = ns_to_timespec64(-rem);
+		target = timespec64_sub(now, delta_ts);
+	}
+	if (target.tv_sec < 0 ||
+	    target.tv_sec >= (1ULL << 32))
+		return -ERANGE;
+	return ptp->settime64(ptp, &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);
+	struct ptp_system_timestamp sts;
+	struct timespec64 target;
+	unsigned int period_ms;
+	struct timespec64 now;
+	s64 scaled_ppm, rem;
+	int max_iters = 3;
+	s64 target_ns;
+	int ret = 0;
+	s64 now_ns;
+	s64 num;
+	s64 den;
+
+	if (!ptp)
+		return -EINVAL;
+
+	/* Nothing to do */
+	if (delta == 0)
+		return 0;
+
+	if (mutex_lock_interruptible(&priv->ptp_adj_lock))
+		return -EINTR;
+
+	/* Try to slew the clock using adjfine for better accuracy.
+	 * For large adjustments fall back to setting time directly.
+	 */
+	rem = delta;
+
+	while (rem != 0 && max_iters--) {
+		s64 abs_delta = rem > 0 ? rem : -rem;
+
+		/* If the adjustment is very large, more than 1 second,
+		 * use settime to avoid very long slewing periods or
+		 * excessive frequency offsets.
+		 */
+		if (abs_delta > 1000000000LL) {
+			ret = s2500_use_settime(ptp, now, rem);
+			rem = 0;
+			break;
+		}
+
+		/* Choose a slewing period depending on magnitude */
+		if (abs_delta <= 1000000LL) /* <= 1ms */
+			period_ms = 1000; /* 1 s */
+		else if (abs_delta <= 100000000LL) /* <= 100ms */
+			period_ms = 10000; /* 10 s */
+		else
+			period_ms = 60000; /* 60 s */
+
+		/* Compute current time and fixed target for this
+		 * iteration
+		 */
+		memset(&sts, 0, sizeof(sts));
+		ret = ptp->gettimex64(ptp, &now, &sts);
+		if (ret)
+			break;
+
+		if (rem >= 0)
+			target = timespec64_add(now,
+						ns_to_timespec64(rem));
+		else
+			target = timespec64_sub(now,
+						ns_to_timespec64(-rem));
+
+		/* Compute scaled_ppm (Qx.16). scaled_ppm = ppm * 2^16
+		 * ppm = (delta_seconds / period_seconds) * 1e6
+		 * => scaled_ppm = delta_ns * 65536 / (period_ms * 1000)
+		 */
+		num = rem * 65536LL;
+		den = (s64)period_ms * 1000LL;
+
+		/* Integer division rounds toward zero; keep sign in
+		 * numerator
+		 */
+		scaled_ppm = div_s64(num, den);
+
+		/* Apply frequency adjustment */
+		ret = ptp->adjfine(ptp, (long)scaled_ppm);
+		if (ret)
+			break;
+
+		/* Sleep for the slew period (interruptible). If
+		 * interrupted, clear the adjfine and return -EINTR.
+		 */
+		if (msleep_interruptible(period_ms)) {
+			/* Clear adjfine */
+			ptp->adjfine(ptp, 0);
+			ret = -EINTR;
+			break;
+		}
+
+		/* Clear adjfine and measure remaining offset */
+		ptp->adjfine(ptp, 0);
+
+		memset(&sts, 0, sizeof(sts));
+		ret = ptp->gettimex64(ptp, &now, &sts);
+		if (ret)
+			break;
+
+		/* remaining = target - now (in ns) */
+		target_ns = timespec64_to_ns(&target);
+		now_ns = timespec64_to_ns(&now);
+		rem = target_ns - now_ns;
+
+		/* If remaining is small (< 1us), finish */
+		if (rem > -1000 && rem < 1000)
+			rem = 0;
+	}
+
+	mutex_unlock(&priv->ptp_adj_lock);
+	return ret;
+}
+
+int s2500_ptp_register(struct s2500_info *priv)
+{
+	struct ptp_clock_info *info = &priv->ptp_clock_info;
+	int ret;
+
+	info->max_adj = 100000000;
+	info->owner = THIS_MODULE;
+	info->adjfine = s2500_ptp_adjfine;
+	info->gettimex64 = s2500_ptp_get_time64;
+	info->settime64 = s2500_ptp_set_time64;
+	info->adjtime = s2500_ptp_adjtime;
+
+	ret = oa_tc6_ptp_register(priv->tc6, info);
+	if (ret)
+		dev_err(&priv->spi->dev, "PTP registration failed");
+	return ret;
+}
+
diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c
index 88255e92b4cdbd..01ea2e8dd253bc 100644
--- a/drivers/net/phy/dp83867.c
+++ b/drivers/net/phy/dp83867.c
@@ -991,15 +991,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)
@@ -1204,7 +1195,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_fixed_speed,
 
 		.led_brightness_set = dp83867_led_brightness_set,
 		.led_hw_is_supported = dp83867_led_hw_is_supported,
diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c
index e601d56b2507f9..0c4dc70641d8b0 100644
--- a/drivers/net/phy/microchip_t1s.c
+++ b/drivers/net/phy/microchip_t1s.c
@@ -506,34 +506,6 @@ static int lan86xx_read_status(struct phy_device *phydev)
 	return 0;
 }
 
-/* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and
- * C45 registers space. If the PHY is discovered via C22 bus protocol it assumes
- * it uses C22 protocol and always uses C22 registers indirect access to access
- * C45 registers. This is because, we don't have a clean separation between
- * C22/C45 register space and C22/C45 MDIO bus protocols. Resulting, PHY C45
- * registers direct access can't be used which can save multiple SPI bus access.
- * To support this feature, set .read_mmd/.write_mmd in the PHY driver to call
- * .read_c45/.write_c45 in the OPEN Alliance framework
- * drivers/net/ethernet/oa_tc6.c
- */
-static int lan865x_phy_read_mmd(struct phy_device *phydev, int devnum,
-				u16 regnum)
-{
-	struct mii_bus *bus = phydev->mdio.bus;
-	int addr = phydev->mdio.addr;
-
-	return __mdiobus_c45_read(bus, addr, devnum, regnum);
-}
-
-static int lan865x_phy_write_mmd(struct phy_device *phydev, int devnum,
-				 u16 regnum, u16 val)
-{
-	struct mii_bus *bus = phydev->mdio.bus;
-	int addr = phydev->mdio.addr;
-
-	return __mdiobus_c45_write(bus, addr, devnum, regnum, val);
-}
-
 static struct phy_driver microchip_t1s_driver[] = {
 	{
 		PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1),
@@ -584,8 +556,8 @@ static struct phy_driver microchip_t1s_driver[] = {
 		.features           = PHY_BASIC_T1S_P2MP_FEATURES,
 		.config_init        = lan865x_revb_config_init,
 		.read_status        = lan86xx_read_status,
-		.read_mmd           = lan865x_phy_read_mmd,
-		.write_mmd          = lan865x_phy_write_mmd,
+		.read_mmd           = genphy_phy_read_mmd,
+		.write_mmd          = genphy_phy_write_mmd,
 		.get_plca_cfg	    = genphy_c45_plca_get_cfg,
 		.set_plca_cfg	    = lan86xx_plca_set_cfg,
 		.get_plca_status    = genphy_c45_plca_get_status,
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index cabdd83c614f0d..afafa81dc22b82 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -2,7 +2,7 @@
 /*
  *  Driver for the onsemi 10BASE-T1S NCN26000 PHYs family.
  *
- * Copyright 2022 onsemi
+ * Copyright 2026 onsemi
  */
 #include <linux/kernel.h>
 #include <linux/bitfield.h>
@@ -14,6 +14,7 @@
 
 #include "mdio-open-alliance.h"
 
+#define PHY_ID_S2500			0x180FF411
 #define PHY_ID_NCN26000			0x180FF5A1
 
 #define NCN26000_REG_IRQ_CTL            16
@@ -35,15 +36,24 @@
 
 #define TO_TMR_DEFAULT			32
 
+#define NCN26000_REG_PHYCFG1		0x8001
+#define NCN26000_PHYCFG1_ENI		BIT(7)
+#define NCN26000_PHYCFG1_ENI_MASK	BIT(7)
+
 static int ncn26000_config_init(struct phy_device *phydev)
 {
+	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)
@@ -100,6 +110,24 @@ static int ncn26000_read_status(struct phy_device *phydev)
 	return 0;
 }
 
+/* Intercept PLCA enable/disable request to
+ * set the proprietary, ENI mode accordingly
+ */
+static int ncn26000_c45_plca_set_cfg(struct phy_device *phydev,
+				     const struct phy_plca_cfg *plca_cfg)
+{
+	int ret = genphy_c45_plca_set_cfg(phydev, plca_cfg);
+	u16 eni_cfg = 0;
+
+	if (ret || plca_cfg->enabled < 0)
+		return ret;
+
+	eni_cfg = (plca_cfg->enabled) ? NCN26000_PHYCFG1_ENI : 0;
+	return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
+			      NCN26000_REG_PHYCFG1,
+			      NCN26000_PHYCFG1_ENI_MASK, eni_cfg);
+}
+
 static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
 {
 	int ret;
@@ -117,8 +145,8 @@ static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
 
 static int ncn26000_config_intr(struct phy_device *phydev)
 {
-	int ret;
 	u16 irqe;
+	int ret;
 
 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
 		// acknowledge IRQs
@@ -141,6 +169,27 @@ static int ncn26000_config_intr(struct phy_device *phydev)
 }
 
 static struct phy_driver ncn26000_driver[] = {
+	{
+		PHY_ID_MATCH_MODEL(PHY_ID_S2500),
+		.name                  = "S2500",
+		.features              = PHY_BASIC_T1S_P2MP_FEATURES,
+		.config_init           = ncn26000_config_init,
+		.config_intr           = ncn26000_config_intr,
+		.config_aneg           = ncn26000_config_aneg,
+		.read_status           = ncn26000_read_status,
+		.handle_interrupt      = ncn26000_handle_interrupt,
+		.set_loopback          = genphy_loopback_fixed_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_phy_read_mmd,
+		.write_mmd             = genphy_phy_write_mmd,
+		.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",
@@ -151,7 +200,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,
 	},
@@ -161,11 +210,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("Piergiorgio Beruto");
+MODULE_AUTHOR("Piergiorgio Beruto <pier.beruto@onsemi.com>");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
 MODULE_DESCRIPTION("onsemi 10BASE-T1S PHY driver");
 MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 0615228459ef42..11fd204eea16a4 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -2787,6 +2787,81 @@ int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,
 }
 EXPORT_SYMBOL(genphy_write_mmd_unsupported);
 
+/**
+ * genphy_phy_read_mmd - Helper for reading a register without lock
+ * from the given MMD and PHY.
+ * @phydev: The phy_device struct
+ * @devnum: The MMD to read from
+ * @regnum: The register on the MMD to read
+ *
+ * Description: PHYs can have both C22 and C45 registers space. Once PHY
+ * is discovered via C22 bus protocol, it uses C22 indirect access to
+ * access C45 registers. Some PHYs, like 10Base-T1S PHYs defined by OPEN
+ * Alliance 10BASE‑T1x, support only direct access.
+ *
+ * If PHY indicates C45 support through DTS entry, it avoid C22 APIs
+ * entirely and therefore generic MDIO registers are inaccessible.
+ *
+ * MDIO bus isn't locked here because when called through read_mmd
+ * callback of phy_driver, caller is expected to lock the bus as
+ * implemented in phy_read_mmd.
+ *
+ * Returns: Register value if successful, negative error code on failure.
+ */
+int genphy_phy_read_mmd(struct phy_device *phydev, int devnum,
+			u16 regnum)
+{
+	struct mii_bus *bus = phydev->mdio.bus;
+	int addr = phydev->mdio.addr;
+
+	lockdep_assert_held(&bus->mdio_lock);
+	return __mdiobus_c45_read(bus, addr, devnum, regnum);
+}
+EXPORT_SYMBOL(genphy_phy_read_mmd);
+
+/**
+ * genphy_phy_write_mmd - Helper for writing a register without lock
+ * to the given MMD and PHY.
+ * @phydev: The phy_device struct
+ * @devnum: The MMD to write to
+ * @regnum: The register on the MMD to write
+ * @val:    Value to write
+ *
+ * Description: Similar to genphy_phy_read_mmd
+ *
+ * Returns: 0 if successful, negative error code on failure.
+ */
+int genphy_phy_write_mmd(struct phy_device *phydev, int devnum,
+			 u16 regnum, u16 val)
+{
+	struct mii_bus *bus = phydev->mdio.bus;
+	int addr = phydev->mdio.addr;
+
+	lockdep_assert_held(&bus->mdio_lock);
+	return __mdiobus_c45_write(bus, addr, devnum, regnum, val);
+}
+EXPORT_SYMBOL(genphy_phy_write_mmd);
+
+/**
+ * genphy_loopback_fixed_speed - Helper to modify the PHY loopback mode
+ * without affecting any other settings.
+ * @phydev: The phy_device struct
+ * @enable: Flag to enable or disable the PHY level loopback.
+ * @speed: Speed setting. Not expected to be set. Error if it is set.
+ *
+ * Returns: 0 if successful, negative error code on failure.
+ */
+int genphy_loopback_fixed_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_fixed_speed);
+
 int genphy_suspend(struct phy_device *phydev)
 {
 	return phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN);
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 15f58e3c56c740..17ac3e0751e122 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -7,8 +7,23 @@
  * Author: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
  */
 
+#ifndef _LINUX_OA_TC6_H
+#define _LINUX_OA_TC6_H
+
 #include <linux/etherdevice.h>
 #include <linux/spi/spi.h>
+#include <linux/ptp_clock_kernel.h>
+
+/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in
+ * the OPEN Alliance specification.
+ */
+#define OA_TC6_PHY_C45_MAC_MMS1			1	/* No MMD */
+#define OA_TC6_PHY_C45_PCS_MMS2			2	/* MMD 3 */
+#define OA_TC6_PHY_C45_PMA_PMD_MMS3		3	/* MMD 1 */
+#define OA_TC6_PHY_C45_VS_PLCA_MMS4		4	/* MMD 31 */
+#define OA_TC6_PHY_C45_AUTO_NEG_MMS5		5	/* MMD 7 */
+#define OA_TC6_PHY_C45_POWER_UNIT_MMS6		6	/* MMD 13 */
+#define OA_TC6_PHY_C45_VS_MMS12			12	/* for vendors */
 
 struct oa_tc6;
 
@@ -22,3 +37,24 @@ 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_write_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+			       u32 value[], u8 length);
+int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+			      u32 value);
+int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+			      u32 value[], u8 length);
+int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+			     u32 *value);
+int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info);
+int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);
+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_set_vend1_mms(struct oa_tc6 *tc6, int mms);
+void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
+#endif /* _LINUX_OA_TC6_H */
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 199a7aaa341bfd..61bcd71a314324 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2301,6 +2301,12 @@ int genphy_read_mmd_unsupported(struct phy_device *phdev, int devad,
 				u16 regnum);
 int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,
 				 u16 regnum, u16 val);
+int genphy_loopback_fixed_speed(struct phy_device *phydev, bool enable,
+				int speed);
+int genphy_phy_write_mmd(struct phy_device *phydev, int devnum,
+			 u16 regnum, u16 val);
+int genphy_phy_read_mmd(struct phy_device *phydev, int devnum,
+			u16 regnum);
 
 /* Clause 37 */
 int genphy_c37_config_aneg(struct phy_device *phydev);

3/2 2026/07/09 11:18 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 52539
							output: 275
							thoughts: 2544
						

Thoughts:
**Fuzzing Analysis: Onsemi S2500 10BASE-T1S MACPHY Driver**

Okay, so we have a new patch series here adding support for the Onsemi S2500 MACPHY ethernet controller. This also touches the OA TC6 framework for hardware timestamping and some MMS functions. The core of this is an SPI-based Ethernet driver. My immediate thought is, "Can we actually fuzz this effectively in a virtualized environment?".

First, let's establish the context. This driver is based on SPI, which immediately raises questions about reachability. SPI devices *can* be instantiated using device tree or sysfs, or with tools like `spi-mock` or `spidev`, but will syzkaller even be able to get to the driver probe? Syzkaller usually fuzzes network devices via netlink (like veth, tun, etc.) or by targeting the SPI host controller directly. It has some limited SPI support via `spidev`, but not for arbitrary devices (like this one). It might work if we can easily instantiate an SPI device using `syz_spi_setup` or a device tree overlay, but that's not a standard, and current syzkaller support is limited to some select SPI devices.

The real question is: Is this driver worth fuzzing? Yes, it's a network driver and a PHY driver, which are high-value targets. This adds `S2500_MACPHY`, modifies `OA_TC6`, and selects `NCN26000_PHY`. Enabling all of these, along with `NET_VENDOR_ONSEMI`, is critical.

Looking at the code, the key functions are: `s2500_probe`, `s2500_open`, `s2500_stop`, `s2500_set_mac_address`, `s2500_set_rx_mode`, `s2500_ioctl`, `s2500_hwtstamp_get`, `s2500_hwtstamp_set`, `s2500_set_multicast_mode`, `oa_tc6_ioctl`, and `oa_tc6_hwtstamp_set`, `oa_tc6_tstamp_ioctl`, `oa_tc6_ptp_register`, plus some PTP functions.

But the probe is what's really important. Can we get it to fire? Syzkaller has no standard way to inject arbitrary SPI devices unless it's using device tree overlays or similar, which is not an upstream standard. We're running in QEMU, which *could* support attaching SPI devices, but it's not a default configuration. I need to make sure this is reachable.

The key here is that, while this is an SPI driver, it modifies the `oa_tc6` library. This is used by other SPI drivers like `lan865x.c`, this will likely make it worth fuzzing! It also adds the `NCN26000_PHY` configuration, which is part of the PHY driver, and can be tested using `mdio` or `ethtool` with a net device. Therefore, `s2500_probe`, `s2500_open`, `s2500_ioctl`, `s2500_hwtstamp_set`, `s2500_set_multicast_mode`, `s2500_ptp_adjtime`, `oa_tc6_hwtstamp_set`, `oa_tc6_tstamp_ioctl`, `oa_tc6_ptp_register`, and  `ncn26000_c45_plca_set_cfg` look very important.

My decision here is to classify this as WorthFuzzing=true. Let the fuzzer work its magic. We just need to make sure to configure those symbols correctly, along with `NET_VENDOR_ONSEMI`, `S2500_MACPHY`, `OA_TC6`, `NCN26000_PHY`.



4/2 2026/07/09 11:19 tool set-results
0m Args:
{
  "EnableConfigs": [
    "NET_VENDOR_ONSEMI",
    "S2500_MACPHY",
    "OA_TC6",
    "NCN26000_PHY"
  ],
  "FocusSymbols": [
    "s2500_probe",
    "s2500_open",
    "s2500_ioctl",
    "s2500_hwtstamp_set",
    "s2500_set_multicast_mode",
    "s2500_ptp_adjtime",
    "oa_tc6_hwtstamp_set",
    "oa_tc6_tstamp_ioctl",
    "oa_tc6_ptp_register",
    "ncn26000_c45_plca_set_cfg"
  ],
  "Reasoning": "The patch introduces a new SPI-based ethernet driver for the onsemi S2500 MACPHY, adds hardware timestamping support to the OA TC6 framework, and updates the NCN26000 PHY driver. These are functional changes to the networking subsystem and PHY drivers that can be interacted with via ethtool, netlink, and ioctls.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "NET_VENDOR_ONSEMI",
    "S2500_MACPHY",
    "OA_TC6",
    "NCN26000_PHY"
  ],
  "FocusSymbols": [
    "s2500_probe",
    "s2500_open",
    "s2500_ioctl",
    "s2500_hwtstamp_set",
    "s2500_set_multicast_mode",
    "s2500_ptp_adjtime",
    "oa_tc6_hwtstamp_set",
    "oa_tc6_tstamp_ioctl",
    "oa_tc6_ptp_register",
    "ncn26000_c45_plca_set_cfg"
  ],
  "Reasoning": "The patch introduces a new SPI-based ethernet driver for the onsemi S2500 MACPHY, adds hardware timestamping support to the OA TC6 framework, and updates the NCN26000 PHY driver. These are functional changes to the networking subsystem and PHY drivers that can be interacted with via ethtool, netlink, and ioctls.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)