* [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
* Re: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010 and TS2500 MAC-PHY
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
0 siblings, 1 reply; 8+ messages in thread
From: Andrew Lunn @ 2026-05-01 20:22 UTC (permalink / raw)
To: Selvamani Rajagopal
Cc: 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
> +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;
> +}
So you seem to be compliant with the standard. I've not seen anything
use MDIO_MMD_POWER_UNIT so not having that should not be an
issue. MDIO_MMD_AN is used by a number of PHYs, but i assume yours
does not.
802.3 C45 says that if a register does not exist, it should read
0. What would happen if a read was made to
OA_TC6_PHY_C45_AUTO_NEG_MMS5, rather than returning EOPNOTSUPP?
Table 6 says nothing about MMD 30, which you map to 12. 10-15 are
defined as vendor specific, so that is O.K.
But can we simply this. Add something like
void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)
{
tc6->vend1_mms = mms;
}
and make oa_tc6_get_phy_c45_mms() look at its value?
Andrew
^ permalink raw reply [flat|nested] 8+ messages in thread
* RE: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010 and TS2500 MAC-PHY
2026-05-01 20:22 ` Andrew Lunn
@ 2026-05-04 19:03 ` Selvamani Rajagopal
2026-05-04 19:15 ` Andrew Lunn
0 siblings, 1 reply; 8+ messages in thread
From: Selvamani Rajagopal @ 2026-05-04 19:03 UTC (permalink / raw)
To: Andrew Lunn
Cc: 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
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Friday, May 1, 2026 1:22 PM
> To: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
> Cc: Piergiorgio Beruto <Pier.Beruto@onsemi.com>; 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
> Subject: Re: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010
> and TS2500 MAC-PHY
>
>
> This Message Is From an External Sender
> This message came from outside your organization.
>
> > +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;
> > +}
>
> So you seem to be compliant with the standard. I've not seen anything
> use MDIO_MMD_POWER_UNIT so not having that should not be an
> issue. MDIO_MMD_AN is used by a number of PHYs, but i assume yours
> does not.
>
> 802.3 C45 says that if a register does not exist, it should read
> 0. What would happen if a read was made to
> OA_TC6_PHY_C45_AUTO_NEG_MMS5, rather than returning EOPNOTSUPP?
>
> Table 6 says nothing about MMD 30, which you map to 12. 10-15 are
> defined as vendor specific, so that is O.K.
>
> But can we simply this. Add something like
>
> void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)
> {
> tc6->vend1_mms = mms;
> }
>
> and make oa_tc6_get_phy_c45_mms() look at its value?
Sure. I can do that.
Since we don't support PCS, and POWER_UNIT, may I suggest alternative solution?
What if, instead of passing mms, we pass a function pointer?.
In oa_tc6.c, we will add a function,
void oa_tc6_set_mmd_mms_mapper(struct oa_tc6 *tc6, int (*mapper_function)(int devnum))
{
tc6-> mmd_mms_mapper = mapper;
}
And all the calls to oa_tc6_get_phy_c45_mms in oa_tc6, could be replaced with tc6->mmd_mms_mapper(denum)
This gives flexibility to vendors as mostly likely, all the vendors may not share the same set of MDIO_MMD_XXX support.
>
> Andrew
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010 and TS2500 MAC-PHY
2026-05-04 19:03 ` Selvamani Rajagopal
@ 2026-05-04 19:15 ` Andrew Lunn
2026-05-04 19:17 ` Selvamani Rajagopal
0 siblings, 1 reply; 8+ messages in thread
From: Andrew Lunn @ 2026-05-04 19:15 UTC (permalink / raw)
To: Selvamani Rajagopal
Cc: 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
> > So you seem to be compliant with the standard. I've not seen anything
> > use MDIO_MMD_POWER_UNIT so not having that should not be an
> > issue. MDIO_MMD_AN is used by a number of PHYs, but i assume yours
> > does not.
> >
> > 802.3 C45 says that if a register does not exist, it should read
> > 0. What would happen if a read was made to
> > OA_TC6_PHY_C45_AUTO_NEG_MMS5, rather than returning EOPNOTSUPP?
> >
> > Table 6 says nothing about MMD 30, which you map to 12. 10-15 are
> > defined as vendor specific, so that is O.K.
> >
> > But can we simply this. Add something like
> >
> > void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)
> > {
> > tc6->vend1_mms = mms;
> > }
> >
> > and make oa_tc6_get_phy_c45_mms() look at its value?
>
> Sure. I can do that.
>
> Since we don't support PCS, and POWER_UNIT, may I suggest alternative solution?
I would prefer to rely on 802.3 defined C45 behaviour. A read to a
register which does not exists gives 0. That is what PHYs and phylib
expect.
You failed to answer my question about this. What happens with your
device if you access C45 registers which don't exist?
> This gives flexibility to vendors as mostly likely, all the vendors
> may not share the same set of MDIO_MMD_XXX support.
Well, all vendors should support what is defined in the standard. It
is only MDIO_MMD_VEND1 which is left undefined, and different vendors
could put that at different MMS in the 10-15 range, but i think the
rest is well defined.
Andrew
^ permalink raw reply [flat|nested] 8+ messages in thread
* RE: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010 and TS2500 MAC-PHY
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
0 siblings, 2 replies; 8+ messages in thread
From: Selvamani Rajagopal @ 2026-05-04 19:17 UTC (permalink / raw)
To: Andrew Lunn
Cc: 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
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Monday, May 4, 2026 12:15 PM
> To: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
> Cc: Piergiorgio Beruto <Pier.Beruto@onsemi.com>; 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
> Subject: Re: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010
> and TS2500 MAC-PHY
>
>
> This Message Is From an External Sender
> This message came from outside your organization.
>
> > > So you seem to be compliant with the standard. I've not seen anything
> > > use MDIO_MMD_POWER_UNIT so not having that should not be an
> > > issue. MDIO_MMD_AN is used by a number of PHYs, but i assume yours
> > > does not.
> > >
> > > 802.3 C45 says that if a register does not exist, it should read
> > > 0. What would happen if a read was made to
> > > OA_TC6_PHY_C45_AUTO_NEG_MMS5, rather than returning EOPNOTSUPP?
> > >
> > > Table 6 says nothing about MMD 30, which you map to 12. 10-15 are
> > > defined as vendor specific, so that is O.K.
> > >
> > > But can we simply this. Add something like
> > >
> > > void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)
> > > {
> > > tc6->vend1_mms = mms;
> > > }
> > >
> > > and make oa_tc6_get_phy_c45_mms() look at its value?
> >
> > Sure. I can do that.
> >
> > Since we don't support PCS, and POWER_UNIT, may I suggest alternative solution?
>
> I would prefer to rely on 802.3 defined C45 behaviour. A read to a
> register which does not exists gives 0. That is what PHYs and phylib
> expect.
>
> You failed to answer my question about this. What happens with your
> device if you access C45 registers which don't exist?
Sorry. Waiting for an answer from hardware designer.
>
> > This gives flexibility to vendors as mostly likely, all the vendors
> > may not share the same set of MDIO_MMD_XXX support.
>
> Well, all vendors should support what is defined in the standard. It
> is only MDIO_MMD_VEND1 which is left undefined, and different vendors
> could put that at different MMS in the 10-15 range, but i think the
> rest is well defined.
>
> Andrew
^ permalink raw reply [flat|nested] 8+ messages in thread
* RE: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010 and TS2500 MAC-PHY
2026-05-04 19:17 ` Selvamani Rajagopal
@ 2026-05-04 19:21 ` Selvamani Rajagopal
2026-05-04 19:23 ` Andrew Lunn
1 sibling, 0 replies; 8+ messages in thread
From: Selvamani Rajagopal @ 2026-05-04 19:21 UTC (permalink / raw)
To: Andrew Lunn
Cc: 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
> -----Original Message-----
> From: Selvamani Rajagopal
> Sent: Monday, May 4, 2026 12:18 PM
> To: 'Andrew Lunn' <andrew@lunn.ch>
> Cc: Piergiorgio Beruto <Pier.Beruto@onsemi.com>; 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
> Subject: RE: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010
> and TS2500 MAC-PHY
>
>
>
> > -----Original Message-----
> > From: Andrew Lunn <andrew@lunn.ch>
> > Sent: Monday, May 4, 2026 12:15 PM
> > To: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
> > Cc: Piergiorgio Beruto <Pier.Beruto@onsemi.com>; 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
> > Subject: Re: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010
> > and TS2500 MAC-PHY
> >
> >
> > This Message Is From an External Sender
> > This message came from outside your organization.
> >
> > > > So you seem to be compliant with the standard. I've not seen anything
> > > > use MDIO_MMD_POWER_UNIT so not having that should not be an
> > > > issue. MDIO_MMD_AN is used by a number of PHYs, but i assume yours
> > > > does not.
> > > >
> > > > 802.3 C45 says that if a register does not exist, it should read
> > > > 0. What would happen if a read was made to
> > > > OA_TC6_PHY_C45_AUTO_NEG_MMS5, rather than returning EOPNOTSUPP?
> > > >
> > > > Table 6 says nothing about MMD 30, which you map to 12. 10-15 are
> > > > defined as vendor specific, so that is O.K.
> > > >
> > > > But can we simply this. Add something like
> > > >
> > > > void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)
> > > > {
> > > > tc6->vend1_mms = mms;
> > > > }
> > > >
> > > > and make oa_tc6_get_phy_c45_mms() look at its value?
> > >
> > > Sure. I can do that.
> > >
> > > Since we don't support PCS, and POWER_UNIT, may I suggest alternative solution?
> >
> > I would prefer to rely on 802.3 defined C45 behaviour. A read to a
> > register which does not exists gives 0. That is what PHYs and phylib
> > expect.
> >
> > You failed to answer my question about this. What happens with your
> > device if you access C45 registers which don't exist?
>
> Sorry. Waiting for an answer from hardware designer.
It is confirmed that we will return 0 if unsupported registers are read.
>
> >
> > > This gives flexibility to vendors as mostly likely, all the vendors
> > > may not share the same set of MDIO_MMD_XXX support.
> >
> > Well, all vendors should support what is defined in the standard. It
> > is only MDIO_MMD_VEND1 which is left undefined, and different vendors
> > could put that at different MMS in the 10-15 range, but i think the
> > rest is well defined.
> >
> > Andrew
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010 and TS2500 MAC-PHY
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
1 sibling, 1 reply; 8+ messages in thread
From: Andrew Lunn @ 2026-05-04 19:23 UTC (permalink / raw)
To: Selvamani Rajagopal
Cc: 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
> > You failed to answer my question about this. What happens with your
> > device if you access C45 registers which don't exist?
>
> Sorry. Waiting for an answer from hardware designer.
It should not be too difficult to program up something like:
for (int mmd = 0 ; mmd < 32; mmd++)
for (int reg = 0; reg < 65535; reg++) {
val = phy_read_mmd(phydev, mmd, reg);
printf("%2x %4x: %4x\m", mmd, reg, val);
}
Andrew
^ permalink raw reply [flat|nested] 8+ messages in thread
* RE: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010 and TS2500 MAC-PHY
2026-05-04 19:23 ` Andrew Lunn
@ 2026-05-04 20:51 ` Selvamani Rajagopal
0 siblings, 0 replies; 8+ messages in thread
From: Selvamani Rajagopal @ 2026-05-04 20:51 UTC (permalink / raw)
To: Andrew Lunn
Cc: 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
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Monday, May 4, 2026 12:24 PM
> To: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
> Cc: Piergiorgio Beruto <Pier.Beruto@onsemi.com>; 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
> Subject: Re: [PATCH net-next 4/5] onsemi: ncn260xx: Add driver support for NCN26010
> and TS2500 MAC-PHY
>
>
> This Message Is From an External Sender
> This message came from outside your organization.
>
> > > You failed to answer my question about this. What happens with your
> > > device if you access C45 registers which don't exist?
> >
> > Sorry. Waiting for an answer from hardware designer.
>
> It should not be too difficult to program up something like:
>
> for (int mmd = 0 ; mmd < 32; mmd++)
> for (int reg = 0; reg < 65535; reg++) {
> val = phy_read_mmd(phydev, mmd, reg);
> printf("%2x %4x: %4x\m", mmd, reg, val);
> }
Andrew, I had replied with the answer earlier. The answer is, yes. it would return 0.
Recent update is that after talking to our product team, we want to submit the support for TS2500 first which doesn't require the quirks we are discussing. It is the older chip, NCN26010 that requires
the quirk flag you suggested.
In short, next version would have modification to OA TC6 framework to add hardware timestamp support only. We will revisit support for older, NCN26010 that requires modification to the way MDIO bus API are handled, later.
Will re-submit soon after removing support for NCN26010.
>
> Andrew
^ permalink raw reply [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