public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010 and TS2500 MAC-PHY
@ 2026-05-01 19:15 Selvamani Rajagopal
  2026-05-01 20:22 ` Andrew Lunn
  0 siblings, 1 reply; 8+ messages in thread
From: Selvamani Rajagopal @ 2026-05-01 19:15 UTC (permalink / raw)
  To: Piergiorgio Beruto, andrew+netdev@lunn.ch, davem@davemloft.net,
	edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org

Support for onsemi's 10Base-T1S MAC-PHY products. Works with
Open Alliance TC6 framework in the kernel.

Hardware timestamp support added only for TS2500.

Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
 drivers/net/ethernet/Kconfig                  |   1 +
 drivers/net/ethernet/Makefile                 |   1 +
 .../net/ethernet/microchip/lan865x/lan865x.c  |   2 +-
 drivers/net/ethernet/onsemi/Kconfig           |  21 +
 drivers/net/ethernet/onsemi/Makefile          |   7 +
 drivers/net/ethernet/onsemi/ncn260xx/Kconfig  |  22 +
 drivers/net/ethernet/onsemi/ncn260xx/Makefile |   7 +
 .../onsemi/ncn260xx/ncn260xx_ethtool.c        | 259 +++++
 .../onsemi/ncn260xx/ncn260xx_macphy.c         | 927 ++++++++++++++++++
 .../onsemi/ncn260xx/ncn260xx_macphy.h         | 283 ++++++
 .../ethernet/onsemi/ncn260xx/ncn260xx_ptp.c   | 253 +++++
 11 files changed, 1782 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/onsemi/Kconfig
 create mode 100644 drivers/net/ethernet/onsemi/Makefile
 create mode 100644 drivers/net/ethernet/onsemi/ncn260xx/Kconfig
 create mode 100644 drivers/net/ethernet/onsemi/ncn260xx/Makefile
 create mode 100644 drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_ethtool.c
 create mode 100644 drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_macphy.c
 create mode 100644 drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_macphy.h
 create mode 100644 drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_ptp.c

diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index b8f70e2a1..a42656120 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -134,6 +134,7 @@ source "drivers/net/ethernet/8390/Kconfig"
 source "drivers/net/ethernet/nvidia/Kconfig"
 source "drivers/net/ethernet/nxp/Kconfig"
 source "drivers/net/ethernet/oki-semi/Kconfig"
+source "drivers/net/ethernet/onsemi/Kconfig"

 config ETHOC
        tristate "OpenCores 10/100 Mbps Ethernet MAC support"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index 57344fec6..38527c249 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_NET_VENDOR_NI) += ni/
 obj-$(CONFIG_NET_VENDOR_NVIDIA) += nvidia/
 obj-$(CONFIG_LPC_ENET) += nxp/
 obj-$(CONFIG_NET_VENDOR_OKI) += oki-semi/
+obj-$(CONFIG_NET_VENDOR_ONSEMI) += onsemi/
 obj-$(CONFIG_ETHOC) += ethoc.o
 obj-$(CONFIG_NET_VENDOR_PASEMI) += pasemi/
 obj-$(CONFIG_NET_VENDOR_QLOGIC) += qlogic/
diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c
index 0277d9737..fb1ef0855 100644
--- a/drivers/net/ethernet/microchip/lan865x/lan865x.c
+++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c
@@ -346,7 +346,7 @@ static int lan865x_probe(struct spi_device *spi)
        spi_set_drvdata(spi, priv);
        INIT_WORK(&priv->multicast_work, lan865x_multicast_work_handler);

-       priv->tc6 = oa_tc6_init(spi, netdev);
+       priv->tc6 = oa_tc6_init(priv, spi, netdev, NULL);
        if (!priv->tc6) {
                ret = -ENODEV;
                goto free_netdev;
diff --git a/drivers/net/ethernet/onsemi/Kconfig b/drivers/net/ethernet/onsemi/Kconfig
new file mode 100644
index 000000000..43c778a55
--- /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/ncn260xx/Kconfig"
+
+endif # NET_VENDOR_ONSEMI
+
diff --git a/drivers/net/ethernet/onsemi/Makefile b/drivers/net/ethernet/onsemi/Makefile
new file mode 100644
index 000000000..02f6f88a0
--- /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_NCN260XX_MACPHY) += ncn260xx/
+
diff --git a/drivers/net/ethernet/onsemi/ncn260xx/Kconfig b/drivers/net/ethernet/onsemi/ncn260xx/Kconfig
new file mode 100644
index 000000000..350d3e82f
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/ncn260xx/Kconfig
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# onsemi NCN260xx Driver Support
+#
+
+if NET_VENDOR_ONSEMI
+
+config NCN260XX_MACPHY
+       tristate "NCN260xx support"
+       depends on SPI
+       select NCN26000_PHY
+       select OA_TC6
+       select NET_DEVLINK
+       help
+         Support for the onsemi NCN26010/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 ncn26xx.
+
+endif # NET_VENDOR_ONSEMI
diff --git a/drivers/net/ethernet/onsemi/ncn260xx/Makefile b/drivers/net/ethernet/onsemi/ncn260xx/Makefile
new file mode 100644
index 000000000..7d392bb90
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/ncn260xx/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the onsemi network device drivers.
+#
+obj-$(CONFIG_NCN260XX_MACPHY) := ncn260xx.o
+ncn260xx-objs := ncn260xx_macphy.o ncn260xx_ethtool.o ncn260xx_ptp.o
+
diff --git a/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_ethtool.c b/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_ethtool.c
new file mode 100644
index 000000000..fe117fed0
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_ethtool.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's NCN260xx/TS2500 10BASE-T1S MAC-PHY driver
+ */
+
+#include "ncn260xx_macphy.h"
+#include <linux/ethtool.h>
+
+#define ONMPH_NUM_REGS                 38
+#define ONMPH_REGDUMP_LEN              (sizeof(u32) * (ONMPH_NUM_REGS * 2))
+
+static u32 phy_addr_list[ONMPH_NUM_REGS] = {
+       (0x4 << 16) | 0x8000,
+       (0x4 << 16) | 0x8001,
+               (0x4 << 16) | 0x8002,
+               (0x4 << 16) | 0x8003,
+               (0x4 << 16) | 0x8004,
+               (0x4 << 16) | 0x8007,
+               (0x4 << 16) | 0xCC01,
+               (0x4 << 16) | 0xCC02,
+               (0x4 << 16) | 0xCC03,
+               (0x4 << 16) | 0xCC04,
+               (0x4 << 16) | 0xCD00,
+               (0x4 << 16) | 0xCD01,
+               (0x4 << 16) | 0xCD02,
+               (0x4 << 16) | 0xD000,
+               (0x4 << 16) | 0xD001,
+               (0x4 << 16) | 0xD100,
+               (0x4 << 16) | 0xD101,
+               (0xC << 16) | 0x10,
+               (0xC << 16) | 0x11,
+               (0xC << 16) | 0x12,
+               (0xC << 16) | 0x1000,
+               (0xC << 16) | 0x1001,
+               (0xC << 16) | 0x1002,
+               (0xC << 16) | 0x1003,
+               (0xC << 16) | 0x1005,
+               (0xC << 16) | 0x1010,
+               (0xC << 16) | 0x1011,
+               (0xC << 16) | 0x1012,
+               (0xC << 16) | 0x1013,
+               (0xC << 16) | 0x1014,
+               (0xC << 16) | 0x1015,
+               (0xC << 16) | 0x1016,
+               (0xC << 16) | 0x1017,
+               (0xC << 16) | 0x1018,
+               (0xC << 16) | 0x1019,
+               (0xC << 16) | 0x101A,
+               (0xC << 16) | 0x101B,
+               (0xC << 16) | 0x101C,
+       };
+
+static const char onmph_stat_strings[][ETH_GSTRING_LEN] = {
+       "tx-bytes-ok",
+       "tx-frames",
+       "tx-broadcast-frames",
+       "tx-multicast-frames",
+       "tx-64-frames",
+       "tx-65-127-frames",
+       "tx-128-255-frames",
+       "tx-256-511-frames",
+       "tx-512-1023-frames",
+       "tx-1024-1518-frames",
+       "tx-underrun-errors",
+       "tx-single-collision",
+       "tx-multiple-collision",
+       "tx-excessive-collision",
+       "tx-deferred-frames",
+       "tx-carrier-sense-errors",
+       "rx-bytes-ok",
+       "rx-frames",
+       "rx-broadcast-frames",
+       "rx-multicast-frames",
+       "rx-64-frames",
+       "rx-65-127-frames",
+       "rx-128-255-frames",
+       "rx-256-511-frames",
+       "rx-512-1023-frames",
+       "rx-1024-1518-frames",
+       "rx-runt",
+       "rx-too-long-frames",
+       "rx-crc-errors",
+       "rx-symbol-errors",
+       "rx-alignment-errors",
+       "rx-busy-drop-frames",
+       "rx-mismatch-drop-frames",
+       "ts_frames",
+};
+
+#define ONMPH_STATS_LEN ARRAY_SIZE(onmph_stat_strings)
+static_assert(ONMPH_STATS_LEN == ONMPH_STATS_NUM);
+
+#define STAT_REG_OFFSET(x) ((ONMPH_REG_MAC_ST##x) - ONMPH_REG_MAC_FIRST_STAT)
+
+#define STAT_OFF_TX_BYTES_OK 0
+#define STAT_OFF_TX_FRAMES 1
+
+static void onmph_update_mac_stats(struct onmph_info *priv)
+{
+       u64 *data = priv->stats_data;
+       u64 tx_frames_before;
+       u32 *regs;
+       u32 *rptr;
+       int ret;
+
+       regs = kmalloc_array(ONMPH_NUMBER_OF_STAT_REGS, sizeof(u32), GFP_KERNEL);
+       if (!regs)
+               return;
+
+       ret = oa_tc6_read_registers(priv->tc6, ONMPH_REG_MAC_STOCTECTSTXL,
+                                   regs, ONMPH_NUMBER_OF_STAT_REGS);
+       if (ret)
+               goto out;
+
+       rptr = regs;
+
+       /* Workaround for NCN26010 version 0x01 */
+       if (priv->model == ONMPH_MODEL_NCN26010 &&
+           priv->version == ONMPH_NCN26010_V0) {
+               tx_frames_before = priv->stats_data[STAT_OFF_TX_FRAMES];
+       }
+
+       /* 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) < ONMPH_NUMBER_OF_STAT_REGS; ++rptr)
+               *(data++) += *rptr;
+
+       /* model-specific fixes */
+       if (priv->model == ONMPH_MODEL_NCN26010 &&
+           priv->version == ONMPH_NCN26010_V0) {
+               /* Add 4 to transmitted bytes for each transmitted packet
+                * because the HW is not counting the FCS
+                */
+               priv->stats_data[STAT_OFF_TX_BYTES_OK] +=
+                       4 * (priv->stats_data[STAT_OFF_TX_FRAMES] -
+                            tx_frames_before);
+       }
+       priv->stats_data[ONMPH_STATS_NUM - 1] = priv->ts_frames;
+out:
+       kfree(regs);
+}
+
+static void onmph_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));
+       strscpy(info->version, DRV_VERSION, sizeof(info->version));
+}
+
+static int onmph_ethtool_set_link_ksettings(struct net_device *ndev,
+                                           const struct ethtool_link_ksettings *cmd)
+{
+       phy_ethtool_ksettings_set(ndev->phydev, cmd);
+       return 0;
+}
+
+static int onmph_ethtool_get_link_ksettings(struct net_device *ndev,
+                                           struct ethtool_link_ksettings *cmd)
+{
+       phy_ethtool_ksettings_get(ndev->phydev, cmd);
+       return 0;
+}
+
+static int onmph_get_sset_count(struct net_device *ndev, int sset)
+{
+       if (sset == ETH_SS_STATS)
+               return ONMPH_STATS_LEN;
+       else
+               return -EOPNOTSUPP;
+}
+
+static void onmph_get_strings(struct net_device *ndev, u32 stringset, u8 *buf)
+{
+       memcpy(buf, onmph_stat_strings, ONMPH_STATS_LEN * ETH_GSTRING_LEN);
+}
+
+static void onmph_get_ethtool_stats(struct net_device *ndev,
+                                   struct ethtool_stats *stats, u64 *data)
+{
+       struct onmph_info *priv = netdev_priv(ndev);
+
+       onmph_update_mac_stats(priv);
+       memcpy(data, priv->stats_data, sizeof(priv->stats_data));
+}
+
+static int onmph_get_ts_info(struct net_device *ndev,
+                            struct kernel_ethtool_ts_info *ts_info)
+{
+       struct onmph_info *priv = netdev_priv(ndev);
+
+       if (!priv->ptp_clock)
+               return ethtool_op_get_ts_info(ndev, ts_info);
+
+       ts_info->so_timestamping = SOF_TIMESTAMPING_RAW_HARDWARE |
+                                  SOF_TIMESTAMPING_TX_HARDWARE |
+                                  SOF_TIMESTAMPING_RX_HARDWARE;
+       ts_info->phc_index = ptp_clock_index(priv->ptp_clock);
+       ts_info->tx_types = BIT(HWTSTAMP_TX_ON);
+       ts_info->rx_filters = BIT(HWTSTAMP_FILTER_ALL);
+       return 0;
+}
+
+static int onmph_get_regs_len(struct net_device *dev)
+{
+       return ONMPH_REGDUMP_LEN;
+}
+
+static void onmph_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *p)
+{
+       struct onmph_info *priv = netdev_priv(ndev);
+       u32 *pbuff = (u32 *)p;
+       u32 val, reg;
+       int ret = 0;
+       int i;
+
+       regs->version = 0;
+       memset(p, 0, ONMPH_REGDUMP_LEN);
+
+       if (!netif_running(ndev))
+               return;
+
+       for (i = 0; i < ONMPH_NUM_REGS; i++) {
+               val = 0;
+               reg = phy_addr_list[i];
+               ret = oa_tc6_read_register(priv->tc6, reg, &val);
+               if (ret)
+                       continue;
+               *pbuff++ = cpu_to_be32(reg);
+               *pbuff++ = cpu_to_be32(val);
+       }
+}
+
+const struct ethtool_ops onmph_ethtool_ops = {
+       .get_drvinfo        = onmph_get_drvinfo,
+       .get_link           = ethtool_op_get_link,
+       .get_link_ksettings = onmph_ethtool_get_link_ksettings,
+       .set_link_ksettings = onmph_ethtool_set_link_ksettings,
+       .get_sset_count     = onmph_get_sset_count,
+       .get_strings        = onmph_get_strings,
+       .get_ethtool_stats  = onmph_get_ethtool_stats,
+       .get_ts_info        = onmph_get_ts_info,
+       .get_regs_len       = onmph_get_regs_len,
+       .get_regs           = onmph_get_regs,
+};
+
diff --git a/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_macphy.c b/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_macphy.c
new file mode 100644
index 000000000..4c033d65f
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_macphy.c
@@ -0,0 +1,927 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's NCN260xx/TS2500 10BASE-T1S MAC-PHY driver
+ */
+
+#include "ncn260xx_macphy.h"
+
+#include <linux/etherdevice.h>
+#include <linux/if_ether.h>
+#include <linux/irqchip.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/phy.h>
+
+/* ONMPH functions & definitions */
+
+#define ONMPH_STATUS0_MASK     (ONMPH_SPI_ST0_CDPE_BIT | \
+                               ONMPH_SPI_ST0_TXFCSE_BIT | \
+                               ONMPH_SPI_ST0_TTSCAC_BIT | \
+                               ONMPH_SPI_ST0_TTSCAB_BIT | \
+                               ONMPH_SPI_ST0_TTSCAA_BIT | \
+                               ONMPH_SPI_ST0_RESETC_BIT | \
+                               ONMPH_SPI_ST0_HDRE_BIT | \
+                               ONMPH_SPI_ST0_LOFE_BIT | \
+                               ONMPH_SPI_ST0_RXBOE_BIT | \
+                               ONMPH_SPI_ST0_TXBUE_BIT | \
+                               ONMPH_SPI_ST0_TXBOE_BIT | \
+                               ONMPH_SPI_ST0_TXPE_BIT)
+
+/* Converts a MACPHY ID to a device name */
+static inline const char *onmph_id_to_name(u32 id)
+{
+       if (id == ONMPH_MODEL_NCN26010)
+               return "NCN26010";
+       if (id == ONMPH_MODEL_TS2500)
+               return "TS2500";
+       return "unknown";
+}
+
+/* Initializes the net device MAC address by reading the UID stored into the
+ * device internal non-volatile memory.
+ */
+static int onmph_read_mac_from_nvmem(struct onmph_info *priv)
+{
+       u8 addr[ETH_ALEN];
+       u32 mac1 = 0;
+       u32 mac0 = 0;
+       int i, j;
+       u32 val;
+       int ret;
+
+       ret = oa_tc6_read_register(priv->tc6, ONMPH_REG_SPI_PHYID, &val);
+       if (ret)
+               return ret;
+
+       val = (val & ONMPH_SPI_PHYID_OUI_MASK) >> ONMPH_SPI_PHYID_OUI_SHIFT;
+
+       /* Convert the OID in host byte order */
+       for (i = 2; i >= 0; --i) {
+               addr[i] = 0;
+               for (j = 0; j < 8; ++j) {
+                       addr[i] |= (val & 1) << (7 - j);
+                       val >>= 1;
+               }
+       }
+
+       ret = oa_tc6_read_register(priv->tc6, ONMPH_REG_VS_MACID1, &mac1);
+       if (ret)
+               return ret;
+
+       ret = oa_tc6_read_register(priv->tc6, ONMPH_REG_VS_MACID0, &mac0);
+       if (ret)
+               return ret;
+
+       /* Pre-production parts may have 0 */
+       if (mac0 == 0 && mac1 == 0)
+               return -ENXIO;
+
+       addr[3] = mac1 & 0xff;
+       addr[4] = (mac0 >> 8) & 0xff;
+       addr[5] = mac0 & 0xff;
+
+       __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 onmph_set_mac_filter(struct net_device *ndev, const u8 *mac)
+{
+       struct onmph_info *priv = netdev_priv(ndev);
+       u32 val;
+       int ret;
+
+       /* Set unicast address filter */
+       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRMASKL(0),
+                                   0xffffffff);
+       if (ret)
+               return ret;
+
+       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRMASKH(0),
+                                   0xffff);
+       if (ret)
+               return ret;
+
+       val = ((u32)mac[2] << 24) |
+              ((u32)mac[3] << 16) |
+              ((u32)mac[4] << 8) |
+              ((u32)mac[5]);
+
+       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRFILTL(0), val);
+       if (ret)
+               return ret;
+
+       val = ONMPH_MAC_ADDRFILT_EN_BIT | ((u32)mac[0] << 8) | ((u32)mac[1]);
+
+       return oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRFILTH(0), val);
+}
+
+static int onmph_mac_ctrl_clear_bits(struct onmph_info *priv, u32 in_bits, bool clr)
+{
+       u32 reg = ONMPH_REG_MAC_CONTROL;
+       u32 rval = 0;
+       int ret;
+
+       ret = oa_tc6_read_register(priv->tc6, reg, &rval);
+       if (!ret) {
+               u32 wval = 0;
+
+               if (clr)
+                       wval = rval & ~in_bits;
+               else
+                       wval = rval | in_bits;
+               if (rval != wval)
+                       ret = oa_tc6_write_register(priv->tc6, reg, wval);
+       }
+       return ret;
+}
+
+static int onmph_init(struct onmph_info *priv)
+{
+       u32 val;
+       int ret;
+
+       /* Configure the SPI protocol */
+       val = (ONMPH_SPI_CFG0_SYNC_BIT) | ONMPH_SPI_CFG0_RXCTE_BIT |
+             (ONMPH_TXCTHRESH_8 << ONMPH_SPI_CFG0_TXCTHRESH_SHIFT) |
+             (ONMPH_CPS_64 << ONMPH_SPI_CFG0_CPS_SHIFT);
+
+       if (priv->tx_fcs_calc)
+               val |= ONMPH_SPI_CFG0_TXFCSVE_BIT;
+
+       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_SPI_CFG0, val);
+       if (ret)
+               return ret;
+
+       val = (u32)~(ONMPH_SPI_ST0_RESETC_BIT |
+                    ONMPH_SPI_ST0_HDRE_BIT | ONMPH_SPI_ST0_LOFE_BIT |
+                    ONMPH_SPI_ST0_RXBOE_BIT | ONMPH_SPI_ST0_TXBOE_BIT |
+                    ONMPH_SPI_ST0_TXPE_BIT);
+
+       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_SPI_IRQM0, val);
+       if (ret)
+               return ret;
+
+       /* Read the initial value of TX credits */
+       ret = oa_tc6_read_register(priv->tc6, ONMPH_REG_SPI_BUFST, &val);
+       if (ret)
+               return ret;
+
+       /* Program the source MAC address into the device */
+       ret = onmph_set_mac_filter(priv->ndev, priv->ndev->dev_addr);
+
+       val = ONMPH_MAC_CONTROL_ADRF_BIT;
+       if (!priv->tx_fcs_calc)
+               val |= ONMPH_MAC_CONTROL_FCSA_BIT;
+
+       return onmph_mac_ctrl_clear_bits(priv, val, false);
+}
+
+static void onmph_shutdown(struct onmph_info *priv)
+{
+       u32 val = ONMPH_MAC_CONTROL_TXEN_BIT | ONMPH_MAC_CONTROL_RXEN_BIT;
+       struct net_device *ndev = priv->ndev;
+
+       netif_stop_queue(ndev);
+       phy_stop(ndev->phydev);
+
+       onmph_mac_ctrl_clear_bits(priv, val, true);
+}
+
+static int onmph_set_promiscuous_mode(struct onmph_info *priv, unsigned int rx_flags)
+{
+       u32 val = ONMPH_MAC_CONTROL_ADRF_BIT;
+       bool clr = false;
+
+       if (rx_flags & IFF_PROMISC)
+               clr = true;
+       return onmph_mac_ctrl_clear_bits(priv, val, clr);
+}
+
+static int onmph_set_multicast_mode(struct onmph_info *priv, unsigned int rx_flags)
+{
+       int i, ret = 0;
+       u32 val;
+
+       if ((rx_flags & IFF_ALLMULTI) ||
+           (netdev_mc_count(priv->ndev) > ONMPH_N_MCAST_FILTERS)) {
+               /* Disable multicast filter */
+               ret = onmph_mac_ctrl_clear_bits(priv, ONMPH_MAC_CONTROL_MCSF_BIT, true);
+               if (ret)
+                       return ret;
+
+               /* Accept all multicasts (any address with the LSB = 1 in the first byte) */
+               ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRMASKL(1), 0);
+               if (ret)
+                       return ret;
+
+               ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRMASKH(1), 0x100);
+               if (ret)
+                       return ret;
+
+               ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRFILTL(1), 0);
+               if (ret)
+                       return ret;
+
+               val = ONMPH_MAC_ADDRFILT_EN_BIT | 0x00000100;
+               ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRFILTH(1), val);
+       } else if (netdev_mc_count(priv->ndev) == 0) {
+               /* Enable multicast filter */
+               ret = onmph_mac_ctrl_clear_bits(priv, ONMPH_MAC_CONTROL_MCSF_BIT, false);
+               if (ret)
+                       return ret;
+
+               /* Disable filters */
+               for (i = 1; i <= ONMPH_N_MCAST_FILTERS; i++) {
+                       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRFILTH(i), 0);
+                       if (ret)
+                               return ret;
+               }
+       } else {
+               struct netdev_hw_addr *ha;
+               u32 addrh;
+               u32 addrl;
+
+               /* Disable multicast filter */
+               ret = onmph_mac_ctrl_clear_bits(priv, ONMPH_MAC_CONTROL_MCSF_BIT, true);
+               if (ret)
+                       return ret;
+
+               /* Disable filters */
+               for (i = 1; i <= ONMPH_N_MCAST_FILTERS; i++) {
+                       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRFILTH(i), 0);
+                       if (ret)
+                               return ret;
+               }
+
+               i = 1;
+               netdev_for_each_mc_addr(ha, priv->ndev) {
+                       if (i > ONMPH_N_MCAST_FILTERS)
+                               break;
+
+                       addrh = ((ha->addr[0] << 8) | ha->addr[1] |
+                                ONMPH_MAC_ADDRFILT_EN_BIT);
+                       addrl = ((ha->addr[2] << 24) | (ha->addr[3] << 16) |
+                                (ha->addr[4] << 8) | (ha->addr[5]));
+
+                       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRFILTH(i),
+                                                   addrh);
+                       if (ret)
+                               return ret;
+
+                       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRFILTL(i),
+                                                   addrl);
+                       if (ret)
+                               return ret;
+
+                       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRMASKL(i),
+                                                   0xffffffff);
+                       if (ret)
+                               return ret;
+
+                       ret = oa_tc6_write_register(priv->tc6, ONMPH_REG_MAC_ADDRMASKH(i),
+                                                   0xffff);
+                       if (ret)
+                               return ret;
+                       i++;
+               }
+       }
+       return ret;
+}
+
+/* Deferred function for applying RX mode flags in non-atomic context */
+static int onmph_rx_mode_update(struct onmph_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 = onmph_set_promiscuous_mode(priv, rx_flags);
+       if (ret)
+               goto out;
+
+       ret = onmph_set_multicast_mode(priv, rx_flags);
+out:
+       return ret;
+}
+
+static int onmph_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
+{
+       struct onmph_info *priv = netdev_priv(ndev);
+
+       if (!netif_running(ndev))
+               return -EINVAL;
+
+       /* Shouldn't pass hardware timestamp related command to
+        * PHY as it doesn't support natively
+        */
+       if (cmd == SIOCSHWTSTAMP || cmd == SIOCGHWTSTAMP)
+               return onmph_ioctl_timestamp(priv, rq, cmd);
+
+       return phy_do_ioctl(ndev, rq, cmd);
+}
+
+static void onmph_set_rx_mode(struct net_device *ndev)
+{
+       struct onmph_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 onmph_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 onmph_set_mac_filter(ndev, addr->sa_data);
+}
+
+static netdev_tx_t onmph_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+       struct onmph_info *priv = netdev_priv(ndev);
+
+       if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)
+               priv->ts_frames++;
+
+       return oa_tc6_start_xmit(priv->tc6, skb);
+}
+
+static void onmph_process_events(struct onmph_info *priv)
+{
+       u32 val;
+       int ret;
+
+       if (!priv->event_pending)
+               return;
+
+       priv->event_pending = false;
+
+       ret = oa_tc6_read_register(priv->tc6, ONMPH_REG_SPI_ST0, &val);
+       if (ret) {
+               dev_err(&priv->spi->dev, "Error reading ST0 register");
+               return;
+       }
+}
+
+static int onmph_thread_fun(void *data)
+{
+       struct onmph_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 = onmph_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;
+               }
+
+               onmph_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 onmph_open(struct net_device *ndev)
+{
+       struct onmph_info *priv = netdev_priv(ndev);
+       int ret = 0;
+       u32 val;
+
+       dev_info(&priv->spi->dev, "%s", "onmph_open");
+       phy_start(priv->ndev->phydev);
+
+       priv->thread = kthread_run(onmph_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 = ONMPH_MAC_CONTROL_TXEN_BIT | ONMPH_MAC_CONTROL_RXEN_BIT;
+               ret = onmph_mac_ctrl_clear_bits(priv, val, false);
+
+               netif_start_queue(priv->ndev);
+       }
+       return ret;
+}
+
+static int onmph_stop(struct net_device *ndev)
+{
+       struct onmph_info *priv = netdev_priv(ndev);
+
+       dev_info(&priv->spi->dev, "%s", "onmph_stop");
+
+       onmph_shutdown(priv);
+
+       kthread_stop(priv->thread);
+       priv->thread = NULL;
+
+       return 0;
+}
+
+static int onmph_hwtstamp_get(struct net_device *ndev,
+                             struct kernel_hwtstamp_config *cfg)
+{
+       struct onmph_info *priv = netdev_priv(ndev);
+
+       if (!priv->ptp_clock) {
+               cfg->tx_type = 0;
+               cfg->rx_filter = 0;
+       } else {
+               oa_tc6_hwtstamp_get(priv->tc6, cfg);
+       }
+       return 0;
+}
+
+static int onmph_hwtstamp_set(struct net_device *ndev,
+                             struct kernel_hwtstamp_config *cfg,
+                             struct netlink_ext_ack *extack)
+{
+       struct onmph_info *priv = netdev_priv(ndev);
+
+       if (!netif_running(ndev))
+               return -EIO;
+       if (!priv->ptp_clock)
+               return -EPROTONOSUPPORT;
+       return oa_tc6_hwtstamp_set(priv->tc6, cfg);
+}
+
+static const struct net_device_ops onmph_netdev_ops = {
+       .ndo_open            = onmph_open,
+       .ndo_stop            = onmph_stop,
+       .ndo_start_xmit      = onmph_start_xmit,
+       .ndo_set_mac_address = onmph_set_mac_address,
+       .ndo_set_rx_mode     = onmph_set_rx_mode,
+       .ndo_eth_ioctl       = onmph_ioctl,
+       .ndo_hwtstamp_get    = onmph_hwtstamp_get,
+       .ndo_hwtstamp_set    = onmph_hwtstamp_set,
+};
+
+static int mmd2mms(int mmd)
+{
+       int ret = -EOPNOTSUPP;
+
+       switch (mmd) {
+       case MDIO_MMD_PCS:
+               ret = OA_TC6_PHY_C45_PCS_MMS2;
+               break;
+       case MDIO_MMD_PMAPMD:
+               ret = OA_TC6_PHY_C45_PMA_PMD_MMS3;
+               break;
+       case MDIO_MMD_VEND2:
+               ret = OA_TC6_PHY_C45_VS_PLCA_MMS4;
+               break;
+       case MDIO_MMD_VEND1:
+               ret = ONMPH_OA_TC6_VEND1_MMS12;
+               break;
+       default:
+               break;
+       }
+       return ret;
+}
+
+static int onmph_update_model(struct onmph_info *priv, void *tc6_handle)
+{
+       u32 val = 0;
+       int ret;
+
+       if (priv->model != -1)
+               return 0;
+       ret = oa_tc6_read_register(tc6_handle, ONMPH_REG_VS_CHIPID, &val);
+       if (ret)
+               return ret;
+
+       priv->capabilities = ONMPH_CAP_MACADDR;
+       priv->version = (val & ONMPH_CHIPID_REVISION_MASK) >> ONMPH_CHIPID_REVISION_SHIFT;
+
+       priv->model = (val & ONMPH_CHIPID_MODEL_MASK) >> ONMPH_CHIPID_MODEL_SHIFT;
+       if (priv->model == ONMPH_MODEL_TS2500) {
+               priv->capabilities |= ONMPH_CAP_PTP;
+       } else if (priv->model != ONMPH_MODEL_NCN26010) {
+               dev_err(&priv->spi->dev, "Unrecognized macphy. 0x%x\n", val);
+               return -ENODEV;
+       }
+       dev_info(&priv->spi->dev, "Macphy model %s, version %u\n",
+                onmph_id_to_name(priv->model), priv->version);
+       return ret;
+}
+
+static int onmph_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
+                                 int regnum)
+{
+       u32 address, val = 0;
+       int mms, ret;
+
+       /* Only PHY #0 is supported. */
+       if (addr != 0)
+               return 0;
+
+       mms = mmd2mms(devnum);
+       if (mms < 0)
+               return mms;
+
+       address = mms << 16 | regnum;
+       ret = oa_tc6_read_register(bus->priv, address, &val);
+       if (ret)
+               return ret;
+       return val;
+}
+
+static int onmph_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
+                                  int regnum, u16 val)
+{
+       u32 address;
+       int mms;
+
+       if (regnum < 0 || regnum > 0xffff)
+               return -ENXIO;
+
+       /* We support only PHY #0 at this time */
+       if (addr != 0)
+               return 0;
+
+       mms = mmd2mms(devnum);
+       if (mms < 0)
+               return mms;
+
+       address = (u32)regnum | (u32)mms << 16;
+
+       return oa_tc6_write_register(bus->priv, address, val);
+}
+
+static int onmph_mdiobus_read(struct mii_bus *bus, int addr, int reg)
+{
+       struct onmph_info *priv = oa_tc6_priv(bus->priv);
+       u32 val, mms;
+       int ret = 0;
+       u32 address;
+
+       if (addr != 0)
+               return ret;
+
+       if (reg < 0 || reg > 31)
+               return -ENXIO;
+
+       if (reg == MII_MMD_CTRL || reg == MII_MMD_DATA) {
+               dev_err(&priv->spi->dev, "MMD_CTRL/DATA read not supported\n");
+               return -EOPNOTSUPP;
+       }
+
+       onmph_update_model(priv, bus->priv);
+
+       address = reg;
+       mms = ONMPH_OA_TC6_MACPHY_MMS0;
+       if (address < 16)
+               address |= 0xFF00;
+       else
+               mms = ONMPH_OA_TC6_VEND1_MMS12;
+
+       val = 0;
+       ret = oa_tc6_read_register(bus->priv, mms << 16 | address, &val);
+       if (ret != 0)
+               return ret;
+
+       /* By mistake, NCN26010's PHY ID tied to OUI. For NCN26010,
+        * return a consistent value for DEVID1/2, which is the same
+        * value that is used in PHY driver of NCN26000.
+        *
+        * Please note that control comes here from oa_tc6_init. Which
+        * means probe didn't have chance to read the chip ID to figure
+        * out the model.
+        */
+       if (priv->model == ONMPH_MODEL_NCN26010) {
+               if (reg == MDIO_DEVID1)
+                       val = 0x180F;
+               else if (reg == MDIO_DEVID2)
+                       val = 0xF5A1;
+       }
+       return val;
+}
+
+static int onmph_mdiobus_write(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+       struct onmph_info *priv = oa_tc6_priv(bus->priv);
+       u32 mms;
+
+       if (addr != 0)
+               return 0;
+
+       if (reg < 0 || reg > 31)
+               return -ENXIO;
+
+       if (reg == MII_MMD_CTRL || reg == MII_MMD_DATA) {
+               dev_err(&priv->spi->dev, "MMD_CTRL/DATA write not supported\n");
+               return -EOPNOTSUPP;
+       }
+
+       /* Prevent the PHY from being reset from the control register. The
+        * NCN26010 triggers a global soft-reset when resetting the PHY.
+        */
+       if (reg == MDIO_CTRL1 && (val & MDIO_CTRL1_RESET) != 0 &&
+           priv->model == ONMPH_MODEL_NCN26010 &&
+           priv->version == ONMPH_NCN26010_V0)
+               val &= ~MDIO_CTRL1_RESET;
+
+       mms = ONMPH_OA_TC6_MACPHY_MMS0;
+       if (reg < 16)
+               reg |= 0xFF00;
+       else
+               mms = ONMPH_OA_TC6_VEND1_MMS12;
+       return oa_tc6_write_register(bus->priv, mms << 16 | reg, val);
+}
+
+/* Rest will be initialized by oa_tc6.c */
+static void onmph_init_mii_bus_info(struct onmph_info *priv, struct mii_bus *bus)
+{
+       bus->read = onmph_mdiobus_read;
+       bus->write = onmph_mdiobus_write;
+       bus->read_c45 = onmph_mdiobus_read_c45;
+       bus->write_c45 = onmph_mdiobus_write_c45;
+       bus->name = "onmph-mdiobus";
+
+       /* Only phy#0 supported */
+       bus->phy_mask = ~1U;
+}
+
+/* sudo devlink dev param show spi/spi0.0 name plca */
+static int onmph_get_plca_status(struct onmph_info *priv, struct devlink_param_gset_ctx *ctx)
+{
+       u32 addr = ONMPH_REG_PLCADIAG;
+       u32 val = 0;
+       int ret;
+
+       ret = oa_tc6_read_register(priv->tc6, addr, &val);
+       if (ret)
+               return ret;
+       if ((val & ONMPH_PLCADIAG_ERR) == 0) {
+               addr = ONMPH_REG_BCNCNT;
+               val = 0;
+               ret = oa_tc6_read_register(priv->tc6, addr, &val);
+               if (ret)
+                       return ret;
+               snprintf(ctx->val.vstr, sizeof(ctx->val.vstr),
+                        "No error. BCNCNT = 0x%x\n", val);
+       } else {
+               snprintf(ctx->val.vstr, sizeof(ctx->val.vstr),
+                        "Error: %s%s%s",
+                        (val & ONMPH_PLCADIAG_RXINTO) ? "RXINFO " : "",
+                        (val & ONMPH_PLCADIAG_UNEXPB) ? "UNEXPB " : "",
+                        (val & ONMPH_PLCADIAG_BCNBFTO) ? "BCNBFTO" : "");
+       }
+       return 0;
+}
+
+static int onmph_param_get(struct devlink *dl, u32 id,
+                          struct devlink_param_gset_ctx *ctx,
+                          struct netlink_ext_ack *extack)
+{
+       struct onmph_devlink_priv *devl_priv = devlink_priv(dl);
+       struct onmph_info *priv = devl_priv->onmph_priv;
+
+       if (id == ONMPH_PARAM_ID_PLCA)
+               return onmph_get_plca_status(priv, ctx);
+       return -EINVAL;
+}
+
+/* Nothing to set */
+static int onmph_param_set(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx,
+                          struct netlink_ext_ack *extack)
+{
+       return -EIO;
+}
+
+static const struct devlink_param onmph_params[] = {
+       DEVLINK_PARAM_DRIVER(ONMPH_PARAM_ID_PLCA,
+                            "plca", DEVLINK_PARAM_TYPE_STRING,
+                            BIT(DEVLINK_PARAM_CMODE_RUNTIME),
+                            onmph_param_get, onmph_param_set, NULL),
+};
+
+static int onmph_probe(struct spi_device *spi)
+{
+       struct device *dev = &spi->dev;
+       struct net_device *ndev;
+       struct onmph_info *priv;
+       struct mii_bus *bus;
+       u32 val;
+       int ret;
+
+       if (spi->irq < 0)
+               return -ENODEV;
+
+       ndev = devm_alloc_etherdev(dev, sizeof(struct onmph_info));
+       if (!ndev)
+               return -ENOMEM;
+
+       priv = netdev_priv(ndev);
+       priv->model = -1; /* 0 is a valid number for NCN26010. */
+       priv->ndev = ndev;
+       priv->spi = spi;
+       priv->dev = dev;
+
+       SET_NETDEV_DEV(ndev, dev);
+
+       spin_lock_init(&priv->lock);
+       ndev->irq = spi->irq;
+
+       spi->dev.platform_data = priv;
+       spi_set_drvdata(spi, priv);
+
+       ndev->netdev_ops = &onmph_netdev_ops;
+       ndev->ethtool_ops = &onmph_ethtool_ops;
+       ndev->if_port = IF_PORT_10BASET;
+       ndev->priv_flags |= IFF_UNICAST_FLT;
+       ndev->hw_features = NETIF_F_RXALL;
+
+       priv->devlink = devlink_alloc(&priv->devlink_ops,
+                                     sizeof(struct onmph_devlink_priv),
+                                     &spi->dev);
+       if (!priv->devlink)
+               return -ENOMEM;
+
+       ret = devlink_params_register(priv->devlink, onmph_params, ARRAY_SIZE(onmph_params));
+       if (ret)
+               goto devl_reg_err;
+       priv->devlink_priv = devlink_priv(priv->devlink);
+       priv->devlink_priv->onmph_priv = priv;
+       devlink_register(priv->devlink);
+
+       priv->tx_fcs_calc = false;
+       priv->poll_jiff = HZ * 5; /* Poll interval */
+
+       if (!priv->tx_fcs_calc)
+               priv->ndev->hw_features |= NETIF_F_RXFCS;
+
+       /* Pointer "mii_bus" is not saved in anywhere in vendor's code as
+        * oa_tc6.c owns it including the responsibilty to release it.
+        */
+       bus = mdiobus_alloc();
+       if (!bus)
+               return -ENOMEM;
+       onmph_init_mii_bus_info(priv, bus);
+
+       priv->tc6 = oa_tc6_init(priv, spi, ndev, bus);
+
+       /* oa_tc6_init may release mdiobus on error */
+       if (!priv->tc6) {
+               dev_err(&spi->dev, "OA TC6 init failed");
+               return -ENODEV;
+       }
+
+       /* Clear RSTS, if set */
+       oa_tc6_read_register(priv->tc6, ONMPH_REG_MIIM_IRQ_STATUS, &val);
+       val &= MIIM_IRQ_STATUS_RSTS;
+       if (val != 0)
+               oa_tc6_write_register(priv->tc6, ONMPH_REG_MIIM_IRQ_STATUS,
+                                     MIIM_IRQ_STATUS_RSTS);
+
+       /* Acknowledge all IRQ status bits */
+       ret = oa_tc6_read_register(priv->tc6, ONMPH_REG_SPI_ST0, &val);
+       if (!ret) {
+               u32 mask = ONMPH_STATUS0_MASK;
+
+               val &= mask;
+               oa_tc6_write_register(priv->tc6, ONMPH_REG_SPI_ST0, val);
+       }
+
+       /* Start with non-protected control accesses for single register reads.
+        * NOTE: the device starts in this mode after reset, but it is possible
+        * that the PROTE bit was set by a previous module load/unload.
+        * In this case, non-protected register writes won't work, but -single-
+        * unprotected register reads will. Therefore, we can safely probe the
+        * device using regular control accesses and switch to protected mode
+        * later, when resetting the device.
+        */
+
+       ret = onmph_update_model(priv, priv->tc6);
+       if (ret)
+               goto err_reg_read;
+
+       ret = oa_tc6_read_register(priv->tc6, ONMPH_REG_SPI_CFG0, &val);
+       if (ret)
+               goto err_reg_read;
+
+       ret = device_get_ethdev_address(priv->dev, ndev);
+       if (ret && (priv->capabilities & ONMPH_CAP_MACADDR))
+               ret = onmph_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 = onmph_init(priv);
+       if (unlikely(ret)) {
+               dev_err(&spi->dev, "failed to onmph_init the device");
+               goto err_reg_read;
+       }
+
+       /* Configure PTP if the model supports it */
+       if (priv->capabilities & ONMPH_CAP_PTP)
+               onmph_ptp_register(priv);
+
+       ret = register_netdev(ndev);
+       if (ret) {
+               dev_err(&spi->dev, "failed to register the ONMPH device\n");
+               ret = -ENODEV;
+
+               goto err_reg_read;
+       }
+       return 0;
+
+err_reg_read:
+       dev_err(&spi->dev, "could not initialize macphy");
+       devlink_unregister(priv->devlink);
+devl_reg_err:
+       devlink_free(priv->devlink);
+       return ret;
+}
+
+static void onmph_remove(struct spi_device *spi)
+{
+       struct onmph_info *priv = spi->dev.platform_data;
+
+       dev_info(&spi->dev, "%s", "onmph_remove");
+
+       devlink_unregister(priv->devlink);
+       devlink_free(priv->devlink);
+       onmph_ptp_unregister(priv);
+       unregister_netdev(priv->ndev);
+       oa_tc6_exit(priv->tc6);
+}
+
+static const struct of_device_id onmph_of_match[] = {
+       { .compatible = "onnn,ncn260xx" },
+       {}
+};
+
+static const struct spi_device_id onmph_ids[] = {
+       { "ncn260xx" },
+       {}
+};
+
+MODULE_DEVICE_TABLE(spi, onmph_ids);
+
+static struct spi_driver ncn260xx_driver = {
+       .driver = {
+               .name   = DRV_NAME,
+               .of_match_table = onmph_of_match,
+       },
+       .probe          = onmph_probe,
+       .remove         = onmph_remove,
+       .id_table       = onmph_ids,
+};
+
+module_spi_driver(ncn260xx_driver);
+
+MODULE_AUTHOR("Piergiorgio Beruto <Pier.Beruto@onsemi.com>");
+MODULE_DESCRIPTION("onsemi NCN260xx MACPHY ethernet driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_macphy.h b/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_macphy.h
new file mode 100644
index 000000000..dbd1d402e
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_macphy.h
@@ -0,0 +1,283 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's NCN260xx/TS2500 10BASE-T1S MAC-PHY driver
+ */
+
+#ifndef NCN260XX_MACPHY_H
+#define NCN260XX_MACPHY_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>
+#include <net/devlink.h>
+
+#define DRV_NAME                       "onmph"
+#define DRV_VERSION                    "1.0.3.1"
+
+#define ONMPH_N_MCAST_FILTERS          3
+
+#define ONMPH_MODEL_NCN26010           0x0000
+#define ONMPH_MODEL_TS2500             0xA7A8
+#define ONMPH_TS2500_DEVID2            0xF411
+
+#define ONMPH_MAKE_VERSION(maj, min, stage, build) ( \
+                          (((maj) & 0xF) << 12) | \
+                          (((min) & 0xF) << 8) | \
+                          (((stage) & 0x3) << 6) | \
+                          (((build) & 0x3F)))
+
+#define ONMPH_NCN26010_V0              ONMPH_MAKE_VERSION(0, 0, 0, 1)
+
+/* Number of stat counters for ethtool */
+#define ONMPH_STATS_NUM                        34
+
+/* List of device capabilities */
+#define ONMPH_CAP_MACADDR              BIT(0)  /* MAC address in hardware */
+#define ONMPH_CAP_PTP                  BIT(1)  /* PTP support */
+
+/* ONMPH registers */
+
+/* Definitions for MMS defined in Table 6 Open Alliance TC6 standard
+ * that not present in oa_tc6.h
+ */
+#define ONMPH_OA_TC6_MACPHY_MMS0       0
+#define ONMPH_OA_TC6_MAC_MMS1          1
+#define ONMPH_OA_TC6_VEND1_MMS12       12
+
+#define ONMPH_MMS_MII                  (ONMPH_OA_TC6_MACPHY_MMS0 << 16)
+#define ONMPH_MMS_MAC                  (ONMPH_OA_TC6_MAC_MMS1 << 16)
+#define ONMPH_MMS_PMDPMA               (OA_TC6_PHY_C45_PMA_PMD_MMS3 << 16)
+#define ONMPH_MMS_VS1                  (ONMPH_OA_TC6_VEND1_MMS12 << 16)
+#define ONMPH_MMS_VS2                  (OA_TC6_PHY_C45_VS_PLCA_MMS4 << 16)
+
+/* SPI OID and model register */
+#define ONMPH_REG_SPI_PHYID            0x1
+
+#define ONMPH_SPI_PHYID_OUI_SHIFT      10
+#define ONMPH_SPI_PHYID_OUI_MASK       GENMASK(31, ONMPH_SPI_PHYID_OUI_SHIFT)
+#define ONMPH_SPI_PHYID_MODEL_SHIFT    4
+#define ONMPH_SPI_PHYID_MODEL_MASK     GENMASK(9, ONMPH_SPI_PHYID_MODEL_SHIFT)
+#define ONMPH_SPI_PHYID_REV_SHIFT      0
+#define ONMPH_SPI_PHYID_REV_MASK       GENMASK(3, ONMPH_SPI_PHYID_REV_SHIFT)
+
+/* SPI configuration register #0 */
+#define ONMPH_REG_SPI_CFG0             (ONMPH_MMS_MII + 0x4)
+
+#define ONMPH_SPI_CFG0_SYNC_BIT                BIT(15)
+#define ONMPH_SPI_CFG0_TXFCSVE_BIT     BIT(14)
+#define ONMPH_SPI_CFG0_CSARFE_BIT      BIT(13)
+#define ONMPH_SPI_CFG0_TXCTHRESH_SHIFT 10
+#define ONMPH_SPI_CFG0_TXCTHRESH_MASK  GENMASK(11, ONMPH_SPI_CFG0_TXCTHRESH_SHIFT)
+#define ONMPH_SPI_CFG0_TXCTE_BIT       BIT(9)
+#define ONMPH_SPI_CFG0_RXCTE_BIT       BIT(8)
+#define ONMPH_SPI_CFG0_FTSE_BIT                BIT(7)
+#define ONMPH_SPI_CFG0_FTSS_BIT                BIT(6)
+#define ONMPH_SPI_CFG0_PROTE_BIT       BIT(5)
+#define ONMPH_SPI_CFG0_SEQE_BIT                BIT(4)
+#define ONMPH_SPI_CFG0_CPS_SHIFT       0
+#define ONMPH_SPI_CFG0_CPS_MASK                GENMASK(2, ONMPH_SPI_CFG0_CPS_SHIFT)
+
+#define ONMPH_TXCTHRESH_1              0x0
+#define ONMPH_TXCTHRESH_4              0x1
+#define ONMPH_TXCTHRESH_8              0x2
+#define ONMPH_TXCTHRESH_16             0x3
+
+#define ONMPH_CPS_8                    0x3
+#define ONMPH_CPS_16                   0x4
+#define ONMPH_CPS_32                   0x5
+#define ONMPH_CPS_64                   0x6
+
+/* SPI status register #0 */
+#define ONMPH_REG_SPI_ST0              (ONMPH_MMS_MII + 8)
+
+#define ONMPH_SPI_ST0_CDPE_BIT         BIT(12)
+#define ONMPH_SPI_ST0_TXFCSE_BIT       BIT(11)
+#define ONMPH_SPI_ST0_TTSCAC_BIT       BIT(10)
+#define ONMPH_SPI_ST0_TTSCAB_BIT       BIT(9)
+#define ONMPH_SPI_ST0_TTSCAA_BIT       BIT(8)
+#define ONMPH_SPI_ST0_PHYINT_BIT       BIT(7)
+#define ONMPH_SPI_ST0_RESETC_BIT       BIT(6)
+#define ONMPH_SPI_ST0_HDRE_BIT         BIT(5)
+#define ONMPH_SPI_ST0_LOFE_BIT         BIT(4)
+#define ONMPH_SPI_ST0_RXBOE_BIT                BIT(3)
+#define ONMPH_SPI_ST0_TXBUE_BIT                BIT(2)
+#define ONMPH_SPI_ST0_TXBOE_BIT                BIT(1)
+#define ONMPH_SPI_ST0_TXPE_BIT         BIT(0)
+
+/* SPI IRQ enable register #0 (use the ONMPH_SPI_ST0_*_BIT constants) */
+#define ONMPH_REG_SPI_IRQM0            (ONMPH_MMS_MII + 0xc)
+
+/* SPI buffer status register */
+#define ONMPH_REG_SPI_BUFST            0xb
+
+#define ONMPH_SPI_BUFST_TXC_SHIFT      8
+#define ONMPH_SPI_BUFST_TXC_MASK       GENMASK(15, ONMPH_SPI_BUFST_TXC_SHIFT)
+
+#define ONMPH_REG_MAC_CONTROL          (ONMPH_MMS_MAC + 0)
+
+#define ONMPH_MAC_CONTROL_MCSF_BIT     BIT(18)
+#define ONMPH_MAC_CONTROL_ADRF_BIT     BIT(16)
+#define ONMPH_MAC_CONTROL_FCSA_BIT     BIT(8)
+#define ONMPH_MAC_CONTROL_TXEN_BIT     BIT(1)
+#define ONMPH_MAC_CONTROL_RXEN_BIT     BIT(0)
+
+/* MAC address filter registers */
+#define ONMPH_REG_MAC_ADDRFILTL(n)     (ONMPH_MMS_MAC + (16 + 2 * (n)))
+#define ONMPH_REG_MAC_ADDRFILTH(n)     (ONMPH_MMS_MAC + (17 + 2 * (n)))
+#define ONMPH_REG_MAC_ADDRMASKL(n)     (ONMPH_MMS_MAC + (32 + 2 * (n)))
+#define ONMPH_REG_MAC_ADDRMASKH(n)     (ONMPH_MMS_MAC + (33 + 2 * (n)))
+
+#define ONMPH_MAC_ADDRFILT_EN_BIT      BIT(31)
+
+/* MAC statistic registers */
+#define ONMPH_REG_MAC_STOCTECTSTXL     (ONMPH_MMS_MAC + 48)
+#define ONMPH_REG_MAC_STOCTECTSTXH     (ONMPH_MMS_MAC + 49)
+#define ONMPH_REG_MAC_STFRAMESTXOK     (ONMPH_MMS_MAC + 50)
+#define ONMPH_REG_MAC_STBCASTTXOK      (ONMPH_MMS_MAC + 51)
+#define ONMPH_REG_MAC_STMCASTTXOK      (ONMPH_MMS_MAC + 52)
+#define ONMPH_REG_MAC_STFRAMESTX64     (ONMPH_MMS_MAC + 53)
+#define ONMPH_REG_MAC_STFRAMESTX65     (ONMPH_MMS_MAC + 54)
+#define ONMPH_REG_MAC_STFRAMESTX128    (ONMPH_MMS_MAC + 55)
+#define ONMPH_REG_MAC_STFRAMESTX256    (ONMPH_MMS_MAC + 56)
+#define ONMPH_REG_MAC_STFRAMESTX512    (ONMPH_MMS_MAC + 57)
+#define ONMPH_REG_MAC_STFRAMESTX1024   (ONMPH_MMS_MAC + 58)
+#define ONMPH_REG_MAC_STTXUNDEFLOW     (ONMPH_MMS_MAC + 59)
+#define ONMPH_REG_MAC_STSINGLECOL      (ONMPH_MMS_MAC + 60)
+#define ONMPH_REG_MAC_STMULTICOL       (ONMPH_MMS_MAC + 61)
+#define ONMPH_REG_MAC_STEXCESSCOL      (ONMPH_MMS_MAC + 62)
+#define ONMPH_REG_MAC_STDEFERREDTX     (ONMPH_MMS_MAC + 63)
+#define ONMPH_REG_MAC_STCRSERR         (ONMPH_MMS_MAC + 64)
+#define ONMPH_REG_MAC_STOCTECTSRXL     (ONMPH_MMS_MAC + 65)
+#define ONMPH_REG_MAC_STOCTECTSRXH     (ONMPH_MMS_MAC + 66)
+#define ONMPH_REG_MAC_STFRAMESRXOK     (ONMPH_MMS_MAC + 67)
+#define ONMPH_REG_MAC_STBCASTRXOK      (ONMPH_MMS_MAC + 68)
+#define ONMPH_REG_MAC_STMCASTRXOK      (ONMPH_MMS_MAC + 69)
+#define ONMPH_REG_MAC_STFRAMESRX64     (ONMPH_MMS_MAC + 60)
+#define ONMPH_REG_MAC_STFRAMESRX65     (ONMPH_MMS_MAC + 71)
+#define ONMPH_REG_MAC_STFRAMESRX128    (ONMPH_MMS_MAC + 72)
+#define ONMPH_REG_MAC_STFRAMESRX256    (ONMPH_MMS_MAC + 73)
+#define ONMPH_REG_MAC_STFRAMESRX512    (ONMPH_MMS_MAC + 74)
+#define ONMPH_REG_MAC_STFRAMESRX1024   (ONMPH_MMS_MAC + 75)
+#define ONMPH_REG_MAC_STRUNTSERR       (ONMPH_MMS_MAC + 76)
+#define ONMPH_REG_MAC_STRXTOOLONG      (ONMPH_MMS_MAC + 77)
+#define ONMPH_REG_MAC_STFCSERRS                (ONMPH_MMS_MAC + 78)
+#define ONMPH_REG_MAC_STSYMBOLERRS     (ONMPH_MMS_MAC + 79)
+#define ONMPH_REG_MAC_STALIGNERRS      (ONMPH_MMS_MAC + 80)
+#define ONMPH_REG_MAC_STRXOVERFLOW     (ONMPH_MMS_MAC + 81)
+#define ONMPH_REG_MAC_STRXDROPPED      (ONMPH_MMS_MAC + 82)
+
+/* First/last statistic register for sequential access */
+#define ONMPH_REG_MAC_FIRST_STAT       ONMPH_REG_MAC_STOCTECTSTXL
+#define ONMPH_REG_MAC_LAST_STAT                ONMPH_REG_MAC_STRXDROPPED
+
+#define ONMPH_NUMBER_OF_STAT_REGS \
+       (ONMPH_REG_MAC_LAST_STAT - ONMPH_REG_MAC_FIRST_STAT + 1)
+
+/* Permanent MAC address register */
+#define ONMPH_REG_VS_MACID0            (ONMPH_MMS_VS1 + 0x1002)
+#define ONMPH_REG_VS_MACID1            (ONMPH_MMS_VS1 + 0x1003)
+
+#define ONMPH_MACID1_UID_SHIFT         0
+#define ONMPH_MACID1_UID_MASK          GENMASK(7, ONMPH_MACID1_UID_SHIFT)
+
+/* Chip identification register */
+#define ONMPH_REG_VS_CHIPID            (ONMPH_MMS_VS1 + 0x1000)
+
+#define ONMPH_CHIPID_MODEL_SHIFT       16
+#define ONMPH_CHIPID_MODEL_MASK                GENMASK(31, ONMPH_CHIPID_MODEL_SHIFT)
+#define ONMPH_CHIPID_REVISION_SHIFT    0
+#define ONMPH_CHIPID_REVISION_MASK     GENMASK(15, ONMPH_CHIPID_REVISION_SHIFT)
+
+/* MIIM IRQ status register */
+#define ONMPH_REG_MIIM_IRQ_STATUS      (ONMPH_MMS_VS1 + 0x11)
+#define MIIM_IRQ_STATUS_RSTS_SHIFT     15
+#define MIIM_IRQ_STATUS_RSTS           BIT(MIIM_IRQ_STATUS_RSTS_SHIFT)
+
+#define ONMPH_REG_BCNCNT               (ONMPH_MMS_VS1 + 0x101C)
+
+#define ONMPH_REG_PLCADIAG             (ONMPH_MMS_VS2 + 0xCA06)
+#define ONMPH_PLCADIAG_RXINTO_SHIFT    (2)
+#define ONMPH_PLCADIAG_RXINTO          BIT(ONMPH_PLCADIAG_RXINTO_SHIFT)
+#define ONMPH_PLCADIAG_UNEXPB_SHIFT    (1)
+#define ONMPH_PLCADIAG_UNEXPB          BIT(ONMPH_PLCADIAG_UNEXPB_SHIFT)
+#define ONMPH_PLCADIAG_BCNBFTO_SHIFT   (0)
+#define ONMPH_PLCADIAG_BCNBFTO         BIT(ONMPH_PLCADIAG_BCNBFTO_SHIFT)
+#define ONMPH_PLCADIAG_ERR             (ONMPH_PLCADIAG_RXINTO | \
+                                        ONMPH_PLCADIAG_UNEXPB | \
+                                        ONMPH_PLCADIAG_BCNBFTO)
+
+/* PTP registers */
+#define ONMPH_REG_VS_PTP_SEC           (ONMPH_MMS_VS1 + 0x1010)
+#define ONMPH_REG_VS_PTP_SETSEC                (ONMPH_MMS_VS1 + 0x1012)
+#define ONMPH_REG_VS_PTP_ADJ           (ONMPH_MMS_VS1 + 0x1014)
+
+enum onmph_devlink_param_id {
+       ONMPH_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX,
+       ONMPH_PARAM_ID_PLCA,
+};
+
+/* prototypes / forward declarations */
+extern const struct ethtool_ops onmph_ethtool_ops;
+
+struct onmph_info;
+
+struct onmph_devlink_priv {
+       struct onmph_info *onmph_priv;
+};
+
+struct onmph_info {
+       struct device *dev;
+       struct net_device *ndev;
+
+       /* model information */
+       u32 model;
+       u32 version;
+       unsigned int capabilities;
+
+       /* tasks and synchronization variables */
+       spinlock_t lock;
+       struct task_struct *thread;
+
+       /* global state variables */
+       bool event_pending;
+       unsigned int ndev_flags;
+       bool rx_flags_upd;
+
+       bool tx_fcs_calc;
+
+       signed long poll_jiff;
+
+       struct spi_device *spi;
+
+       /* statistic counters variables */
+       u64 stats_data[ONMPH_STATS_NUM];
+
+       /* PTP related variables */
+       struct ptp_clock_info ptp_clock_info;
+       struct ptp_clock *ptp_clock;
+       u32 ts_frames;
+       void *tc6;
+
+       struct devlink_ops devlink_ops;
+       struct onmph_devlink_priv *devlink_priv;
+       struct devlink *devlink;
+};
+
+int onmph_ioctl_timestamp(struct onmph_info *priv, struct ifreq *rq, int cmd);
+void onmph_ptp_unregister(struct onmph_info *priv);
+void onmph_ptp_register(struct onmph_info *priv);
+
+#endif /* NCN260XX_MACPHY_H */
+
diff --git a/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_ptp.c b/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_ptp.c
new file mode 100644
index 000000000..8c3ebbe73
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/ncn260xx/ncn260xx_ptp.c
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's NCN260xx/TS2500 10BASE-T1S MAC-PHY driver
+ */
+
+#include "ncn260xx_macphy.h"
+
+static DEFINE_MUTEX(onmph_ptp_adj_mutex);
+
+static int onmph_ptp_get_time64(struct ptp_clock_info *ptp,
+                               struct timespec64 *ts,
+                               struct ptp_system_timestamp *ptp_sts)
+{
+       struct onmph_info *priv = container_of(ptp, struct onmph_info,
+                                              ptp_clock_info);
+       u32 data[2];
+       int ret;
+
+       ptp_read_system_prets(ptp_sts);
+       ret = oa_tc6_read_registers(priv->tc6, ONMPH_REG_VS_PTP_SEC,
+                                   &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 onmph_ptp_set_time64(struct ptp_clock_info *ptp,
+                               const struct timespec64 *ts)
+{
+       struct onmph_info *priv = container_of(ptp, struct onmph_info,
+                                              ptp_clock_info);
+       u32 data[2];
+
+       if (ts->tv_sec >= (1ULL << 32))
+               return -ERANGE;
+
+       data[0] = (u32)ts->tv_sec;
+       data[1] = ts->tv_nsec | BIT(31); /* bit 31 = execute set command */
+
+       return oa_tc6_write_registers(priv->tc6, ONMPH_REG_VS_PTP_SETSEC,
+                                     &data[0], 2);
+}
+
+static int onmph_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+       struct onmph_info *priv = container_of(ptp, struct onmph_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 TS2500 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(priv->tc6, ONMPH_REG_VS_PTP_ADJ, val);
+}
+
+/* Implemented by using "settime" */
+static int onmph_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+       struct ptp_system_timestamp sts;
+       struct timespec64 target;
+       unsigned int period_ms;
+       struct timespec64 now;
+       int max_iters = 3;
+       s64 scaled_ppm;
+       s64 remaining;
+       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(&onmph_ptp_adj_mutex))
+               return -EINTR;
+
+       /* Try to slew the clock using adjfine for better accuracy. For large
+        * adjustments fall back to setting time directly.
+        */
+       remaining = delta;
+
+       while (remaining != 0 && max_iters--) {
+               s64 abs_delta = remaining > 0 ? remaining : -remaining;
+
+               /* 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) {
+                       memset(&sts, 0, sizeof(sts));
+                       ret = ptp->gettimex64(ptp, &now, &sts);
+                       if (!ret) {
+                               struct timespec64 delta_ts;
+
+                               if (remaining >= 0) {
+                                       delta_ts = ns_to_timespec64(remaining);
+                                       target = timespec64_add(now, delta_ts);
+                               } else {
+                                       delta_ts = ns_to_timespec64(-remaining);
+                                       target = timespec64_sub(now, delta_ts);
+                               }
+                       }
+
+                       if (target.tv_sec < 0 || target.tv_sec >= (1ULL << 32))
+                               ret = -ERANGE;
+                       else
+                               ret = ptp->settime64(ptp, &target);
+
+                       remaining = 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 (remaining >= 0)
+                       target = timespec64_add(now, ns_to_timespec64(remaining));
+               else
+                       target = timespec64_sub(now, ns_to_timespec64(-remaining));
+
+               /* 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 = remaining * 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 with -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);
+               remaining = target_ns - now_ns;
+
+               /* If remaining is small (< 1us), finish */
+               if (remaining > -1000 && remaining < 1000)
+                       remaining = 0;
+       }
+
+       mutex_unlock(&onmph_ptp_adj_mutex);
+       return ret;
+}
+
+int onmph_ioctl_timestamp(struct onmph_info *priv, struct ifreq *rq, int cmd)
+{
+       if (!(priv->capabilities & ONMPH_CAP_PTP))
+               return -EOPNOTSUPP;
+       return oa_tc6_hwtstamp_ioctl(priv->tc6, rq, cmd);
+}
+
+/* Support is not available for alarms, programmable periodic signals, pin
+ * configuration, external timestamping, programmable pins, and PPS support.
+ */
+void onmph_ptp_register(struct onmph_info *priv)
+{
+       struct ptp_clock_info *info = &priv->ptp_clock_info;
+
+       snprintf(info->name, sizeof(info->name), "%s", "TS2500 PTP Clock");
+       info->max_adj = 100000000;
+       info->owner = THIS_MODULE;
+       info->adjfine = onmph_ptp_adjfine;
+       info->gettimex64 = onmph_ptp_get_time64;
+       info->settime64 = onmph_ptp_set_time64;
+       info->adjtime = onmph_ptp_adjtime;
+
+       priv->ptp_clock = ptp_clock_register(info, priv->dev);
+       if (IS_ERR(priv->ptp_clock)) {
+               dev_err(&priv->spi->dev, "Registration of %s failed",
+                       info->name);
+               return;
+       }
+       dev_info(&priv->spi->dev, "Registered %s index %d", info->name,
+                ptp_clock_index(priv->ptp_clock));
+}
+
+void onmph_ptp_unregister(struct onmph_info *priv)
+{
+       if (priv->ptp_clock)
+               ptp_clock_unregister(priv->ptp_clock);
+}
+
--
2.43.0


Public Information


^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-05-04 20:51 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-01 19:15 [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010 and TS2500 MAC-PHY Selvamani Rajagopal
2026-05-01 20:22 ` Andrew Lunn
2026-05-04 19:03   ` Selvamani Rajagopal
2026-05-04 19:15     ` Andrew Lunn
2026-05-04 19:17       ` Selvamani Rajagopal
2026-05-04 19:21         ` Selvamani Rajagopal
2026-05-04 19:23         ` Andrew Lunn
2026-05-04 20:51           ` Selvamani Rajagopal

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox