* Re: [PATCH] sched/topology: Allow EAS without schedutil for artificial Energy Models
From: Lucas Lima @ 2026-06-29 19:07 UTC (permalink / raw)
To: Lukasz Luba
Cc: dietmar.eggemann, rostedt, vincent.guittot, mingo, bsegall,
mgorman, vschneid, kprateek.nayak, corbet, skhan, linux-pm,
linux-doc, linux-kernel, juri.lelli, rafael, viresh.kumar, peterz
In-Reply-To: <7f960dca-2de2-4831-8690-a40fdec848dd@arm.com>
Em seg., 29 de jun. de 2026 às 11:05, Lukasz Luba
<lukasz.luba@arm.com> escreveu:
>
>
>
> On 6/29/26 09:35, Lucas de Lima Nóbrega wrote:
> > EAS currently refuses to enable energy-aware scheduling on a root
> > domain unless schedutil is the active CPUFreq governor for all of its
> > CPUs (cpufreq_ready_for_eas()). This requirement exists to protect the
> > accuracy of the energy estimate: EAS predicts the OPP a CPU will run
> > at from its utilization, which is only meaningful if the active
> > governor actually requests OPPs that way, and schedutil is the only
> > one that does.
> >
> > That requirement does not apply to artificial Energy Models
> > (EM_PERF_DOMAIN_ARTIFICIAL). An artificial EM is built from a
> > get_cost() callback instead of real power numbers, and only encodes a
> > cost ranking between CPUs (e.g. P-cores cost more than E-cores at a
> > given utilization). It never claims to predict real energy use at any
> > specific OPP, so there is no per-OPP accuracy for the governor
> > requirement to protect, regardless of which governor is in control or
> > whether it tracks utilization at all.
> >
> > intel_pstate registers exactly this kind of artificial EM for hybrid
> > (P/E-core) systems without SMT, regardless of whether it operates in
> > active or passive mode. In active mode it never uses schedutil, since
> > HWP picks frequency autonomously, so on these systems EAS never
>
> When frequency is picked autonomously then EAS and energy estimations
> don't make sense IMHO.
>
> Do you have any data from experiments how it runs?
>
> Regards,
> Lukasz
Through my testings, HWP does seem follow an strictly non decreasing
duty-cycle to freq curve, with or without frequency clamping and turbo
boost activated. For me, that feels enough to justify the use of the
simplified energy model already implemented.
^ permalink raw reply
* Re: [PATCH] sched/topology: Allow EAS without schedutil for artificial Energy Models
From: Rafael J. Wysocki @ 2026-06-29 19:06 UTC (permalink / raw)
To: Lucas de Lima Nóbrega
Cc: viresh.kumar, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, corbet, skhan, linux-pm, linux-doc, linux-kernel
In-Reply-To: <CAJZ5v0iVD90XPsWgT8B+fw9vmFRZTVL7MasPP-=Ci0OdUmNR=A@mail.gmail.com>
On Monday, June 29, 2026 5:16:17 PM CEST Rafael J. Wysocki (Intel) wrote:
> On Mon, Jun 29, 2026 at 10:36 AM Lucas de Lima Nóbrega
> <lucaslnobrega38@gmail.com> wrote:
> >
> > EAS currently refuses to enable energy-aware scheduling on a root
> > domain unless schedutil is the active CPUFreq governor for all of its
> > CPUs (cpufreq_ready_for_eas()). This requirement exists to protect the
> > accuracy of the energy estimate: EAS predicts the OPP a CPU will run
> > at from its utilization, which is only meaningful if the active
> > governor actually requests OPPs that way, and schedutil is the only
> > one that does.
> >
> > That requirement does not apply to artificial Energy Models
> > (EM_PERF_DOMAIN_ARTIFICIAL). An artificial EM is built from a
> > get_cost() callback instead of real power numbers, and only encodes a
> > cost ranking between CPUs (e.g. P-cores cost more than E-cores at a
> > given utilization). It never claims to predict real energy use at any
> > specific OPP, so there is no per-OPP accuracy for the governor
> > requirement to protect, regardless of which governor is in control or
> > whether it tracks utilization at all.
>
> But it is still about comparing the cost of running on different CPUs
> at different performance levels.
>
> For instance, say the scale-invariant utilization of a task is 256 and
> it can run either by itself on a P-core, or with another task whose
> utilization is 128 on an E-core, and say the P-core's and E-core's
> capacity is 1024 and 512, respectively.
>
> Say the cost function tells EAS that running a P-core at 1/4 of the
> capacity is cheaper than running an E-core at 3/4 capacity, so it will
> pick up the P-core to run that task, but if cpufreq ramps up the
> frequency of the P-core to the max when the task gets to it, it may
> actually turn out to be more expensive.
>
> This means that EAS still has an expectation regarding cpufreq which
> is that it will generally tend to run tasks at the performance level
> corresponding to the sum of their scale-invariant utilization at least
> roughly.
>
> IIUC this actually has nothing to do with whether or not the energy
> model used by EAS is artificial. The schedutil requirement is about
> choosing a performance level proportional to the utilization (which
> schedutil generally tends to do by design).
>
> > intel_pstate registers exactly this kind of artificial EM for hybrid
> > (P/E-core) systems without SMT, regardless of whether it operates in
> > active or passive mode. In active mode it never uses schedutil, since
> > HWP picks frequency autonomously, so on these systems EAS never
> > engages even though SD_ASYM_CPUCAPACITY, frequency invariance and the
> > EM are all in place: find_energy_efficient_cpu() is never reached
> > because is_rd_overutilized() is hardcoded to true whenever
> > sched_energy_enabled() is false. cppc_cpufreq registers the same kind
> > of ranking-only artificial EM and is affected the same way with any
> > non-schedutil governor.
> >
> > Allow EAS to be enabled when every CPU's EM in the root domain is
> > artificial, even when schedutil is not the active governor.
> >
> > Tested on a Raptor Lake-P laptop with nosmt=force and intel_pstate in
> > active/HWP mode: find_energy_efficient_cpu() was never called before
> > this change (confirmed via the sched_overutilized_tp tracepoint and
> > ftrace) and is exercised as expected afterwards.
>
> If this is about allowing EAS to work with intel_pstate running in the
> active mode, you may argue that what the processor firmware is doing
> when intel_pstate runs in the active mode is not much different from
> what schedutil would do. So a driver implementing an internal
> governor (that is, using the .set_policy() callback) would need to
> declare that its internal governor is as good as schedutil from EAS'
> perspective and so it will pass the "cpufreq readiness" check.
And I have a prototype patch (on top of 7.2-rc1) doing this which is
appended.
I wonder if it works for you (that is, if it allows intel_pstate and EAS to
work together both with schedutil and when intel_pstate operates in the
active mode with the "powersave" policy on your system).
Also I wonder why exactly you want intel_pstate in the active mode to
work with EAS. Do you see any significant improvement in that case?
---
drivers/cpufreq/cpufreq.c | 2 +-
drivers/cpufreq/intel_pstate.c | 11 +++++++++++
include/linux/cpufreq.h | 16 +++++++---------
kernel/sched/cpufreq_schedutil.c | 7 ++-----
4 files changed, 21 insertions(+), 15 deletions(-)
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -3058,7 +3058,7 @@ static bool cpufreq_policy_is_good_for_e
return false;
}
- return sugov_is_governor(policy);
+ return policy->eas_compatible;
}
bool cpufreq_ready_for_eas(const struct cpumask *cpu_mask)
--- a/drivers/cpufreq/intel_pstate.c
+++ b/drivers/cpufreq/intel_pstate.c
@@ -2921,6 +2921,9 @@ static int intel_pstate_set_policy(struc
if (!hwp_boost)
intel_pstate_clear_update_util_hook(policy->cpu);
intel_pstate_hwp_set(policy->cpu);
+
+ policy->eas_compatible = hwp_is_hybrid &&
+ cpu->policy != CPUFREQ_POLICY_PERFORMANCE;
}
/*
* policy->cur is never updated with the intel_pstate driver, but it
@@ -2930,6 +2933,9 @@ static int intel_pstate_set_policy(struc
mutex_unlock(&intel_pstate_limits_lock);
+ if (policy->eas_compatible)
+ em_rebuild_sched_domains();
+
return 0;
}
@@ -3030,6 +3036,11 @@ static void intel_pstate_cpu_exit(struct
pr_debug("CPU %d exiting\n", policy->cpu);
policy->fast_switch_possible = false;
+
+ if (policy->eas_compatible) {
+ policy->eas_compatible = false;
+ em_rebuild_sched_domains();
+ }
}
static int __intel_pstate_cpu_init(struct cpufreq_policy *policy)
--- a/include/linux/cpufreq.h
+++ b/include/linux/cpufreq.h
@@ -118,6 +118,13 @@ struct cpufreq_policy {
bool strict_target;
/*
+ * Set if the current governor meets EAS' expectations regarding
+ * performance scaling (that is, it selects performance levels
+ * proportional to CPU utilization at least roughly).
+ */
+ bool eas_compatible;
+
+ /*
* Set if inefficient frequencies were found in the frequency table.
* This indicates if the relation flag CPUFREQ_RELATION_E can be
* honored.
@@ -657,15 +664,6 @@ module_exit(__governor##_exit)
struct cpufreq_governor *cpufreq_default_governor(void);
struct cpufreq_governor *cpufreq_fallback_governor(void);
-#ifdef CONFIG_CPU_FREQ_GOV_SCHEDUTIL
-bool sugov_is_governor(struct cpufreq_policy *policy);
-#else
-static inline bool sugov_is_governor(struct cpufreq_policy *policy)
-{
- return false;
-}
-#endif
-
static inline void cpufreq_policy_apply_limits(struct cpufreq_policy *policy)
{
if (policy->max < policy->cur)
--- a/kernel/sched/cpufreq_schedutil.c
+++ b/kernel/sched/cpufreq_schedutil.c
@@ -797,6 +797,7 @@ out:
* Schedutil is the preferred governor for EAS, so rebuild sched domains
* on governor changes to make sure the scheduler knows about them.
*/
+ policy->eas_compatible = true;
em_rebuild_sched_domains();
mutex_unlock(&global_tunables_lock);
return 0;
@@ -839,6 +840,7 @@ static void sugov_exit(struct cpufreq_po
sugov_policy_free(sg_policy);
cpufreq_disable_fast_switch(policy);
+ policy->eas_compatible = false;
em_rebuild_sched_domains();
}
@@ -931,9 +933,4 @@ struct cpufreq_governor *cpufreq_default
}
#endif
-bool sugov_is_governor(struct cpufreq_policy *policy)
-{
- return policy->governor == &schedutil_gov;
-}
-
cpufreq_governor_init(schedutil_gov);
^ permalink raw reply
* Re: [PATCH v6 03/12] PCI: liveupdate: Track incoming preserved PCI devices
From: David Matlack @ 2026-06-29 18:48 UTC (permalink / raw)
To: Pratyush Yadav
Cc: Pasha Tatashin, kexec, linux-doc, linux-kernel, linux-mm,
linux-pci, Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pranjal Shrivastava,
Saeed Mahameed, Samiullah Khawaja, Shuah Khan, Vipin Sharma,
William Tu, Yi Liu
In-Reply-To: <2vxzechulmcp.fsf@kernel.org>
On Thu, Jun 25, 2026 at 7:35 AM Pratyush Yadav <pratyush@kernel.org> wrote:
>
> Hi David,
>
> On Mon, Jun 15 2026, David Matlack wrote:
>
> > On 2026-06-14 01:38 PM, Pasha Tatashin wrote:
> >> On Fri, 22 May 2026 20:24:01 +0000, David Matlack <dmatlack@google.com> wrote:
> [...]
> >> > + }
> >> > +
> >> > + pci_info(dev, "Device was preserved by previous kernel across Live Update\n");
> >> > + dev->liveupdate.incoming = dev_ser;
> >> > +
> >> > + /*
> >> > + * Hold the ref on the incoming FLB until pci_liveupdate_finish() so
> >> > + * that dev->liveupdate.incoming does not get freed while it is in use.
> >> > + */
> >>
> >> How would that work? If finish is not called FLB stays around until the
> >> next reboot.
> >
> > True... I think if the PCI core trusts drivers to call
> > pci_liveupdate_finish() then we don't need to hold onto the incoming
> > reference here.
>
> That was my point when I was arguing against refcounts on outgoing FLBs.
> This is very easy to abuse, especially when we are talking about device
> drivers. And this refcounting mechanism makes the FLB no longer
> file-lifecycle-bound, since now it is entirely up to drivers to decide
> the lifecycle of this data.
The PCI core holds a reference to the incoming FLB for as long as it
maintains a pointer to that FLB in struct pci_dev
(dev->liveupdate.incoming). The lifetime of that pointer is aligned
with the lifetime of the file as long as the driver calls
pci_liveupdate_finish() in its file finish() callback.
If there is a bug in the driver that causes it to not call
pci_liveupdate_finish() then the FLB will leak past the file yes. But
the alternative would be to leak a pointer to freed memory in
dev->liveupdate.incoming, which could lead to UAF.
Leaking the FLB seems safer than UAF, which is why I went for the
refcounting approach.
Another approach entirely would be to drop the
dev->liveupdate.incoming and do the xarray lookup everytime instead.
>
> I have been thinking about this a bit more in the last couple days, and
> I wonder if we are doing this right. Here's an idea I have been thinking
> of.
>
> We should make live update a first class citizen in PCI. Instead of
> patching in liveupdate via the liveupdate.incoming field, and letting
> drivers figure out when to use it, we should separate out probe and
> retrieve paths entirely.
>
> Probe and retrieve are fundamentally different operations. While they
> may share some common initialization logic for the _software_ state, how
> they interface with the hardware is completely different. I think mixing
> the two will result in driver code being more spaghetti by having
> liveupdate checks sprayed out all over.
We are only planning on supporting Live Update for VFIO drivers for
the forseeable future. The VFIO work during probe is almost entirely
software state setup. The only hardware logic we need to "if" out in
the vfio-pci driver's probe() is putting the device into a low-power
mode via the runtime power manager. So I don't think we will get any
benefit from this approach, and it would be a lot more intrusive to
both the PCI core driver framework, and VFIO itself, to support this.
> This series doesn't add support for any drivers, but looking at some of
> the code we have downstream, I see this problem. The liveupdate code is
> all over the place in the driver and it is very hard to wrap one's head
> around how the device is actually retrieved.
You can find the vfio-pci driver changes here:
https://lore.kernel.org/kvm/20260511234802.2280368-1-vipinsh@google.com/
Let's keep the discussion focused on upstream VFIO drivers since that
is all we are planning to support right now due to LUO's requirement
of file-based preservation. The downstream driver changes we are
carrying is not reflective of what we want to support upstream.
> So I think PCI core should track preserved devices, and if the device is
> preserved, it should skip the probe and wait for retrieve. Retrieve does
> the full initialization of the device. This fits in with the LUO model
> as well. You can make retrieve a callback of struct pci_driver and do
> some wrappers to talk with LUO, so device drivers don't directly
> interface with LUO at all.
>
> We should do similar things on the shutdown path. Shutdown is a
> fundamentally different operation from freeze, and so we should separate
> them out as well.
This is speculative. In practice, we haven't needed to change VFIO's
shutdown() or probe() functions so far. The only change I anticipate
needing is skipping runtime power management "put" during probe() I
mentioned above.
If we actually made retrieve() a first-class callback and used that
instead of probe(), VFIO would internally just call its probe()
function because that would be the cleanest way to set up all the
software state it needs to manage the device.
> This solves the lifetime problem as well. When PCI core is initializing,
> it knows for sure that no retrievals are going to happen. That's because
> none of the drivers have registered yet. So it can safely access the FLB
> and initialize its state. After that, drivers can register themselves
> and start accepting retrieve() calls. Once the last driver goes away,
> the FLB is freed automatically.
It's not so simple. The PCI core does not really initialize itself.
Scanning devices gets triggered externally, e.g. by ACPI, device
trees, runtime hotplug events, etc., and that is when the PCI core
gets notified about a device. None of this is synchronized with "when
drivers have registered", which I assume you are referring to
registering with LUO.
>
> I am sorry for suggesting a big refactor at v6, but the early versions
> looked good to me at the time, and I only thought more deeply about this
> when trying to figure out how we can make the lifetimes cleaner.
>
> What do you think? Does this make sense?
>
> --
> Regards,
> Pratyush Yadav
^ permalink raw reply
* Re: [PATCH v5 1/6] mm/zswap: Fix global shrinker when memory cgroup is disabled
From: Nhat Pham @ 2026-06-29 18:37 UTC (permalink / raw)
To: Hao Jia
Cc: akpm, tj, hannes, shakeel.butt, mhocko, yosry, mkoutny,
chengming.zhou, muchun.song, roman.gushchin, linux-mm,
linux-kernel, linux-doc, Hao Jia, stable
In-Reply-To: <20260629112032.20423-2-jiahao.kernel@gmail.com>
On Mon, Jun 29, 2026 at 4:20 AM Hao Jia <jiahao.kernel@gmail.com> wrote:
>
> From: Hao Jia <jiahao1@lixiang.com>
>
> When memory cgroup is disabled, mem_cgroup_iter() always returns NULL.
> Therefore, the global shrinker shrink_worker() always takes the !memcg
> branch. After MAX_RECLAIM_RETRIES empty walks, the worker simply gives up,
> so it fails to write back anything.
>
> Therefore, when memory cgroup is disabled, fall through with the !memcg
> branch and shrink the root memcg directly. Stop the loop once
> shrink_memcg() reports -ENOENT, since the root LRU is the only target and
> -ENOENT means it has been exhausted.
>
> Fixes: a65b0e7607cc ("zswap: make shrinking memcg-aware")
> Cc: stable@vger.kernel.org
> Reported-by: Yosry Ahmed <yosry@kernel.org>
> Closes: https://lore.kernel.org/all/CAO9r8zPVzMKFbCixxD-qgtRrkFxWVrHiZZeLc=eyTPKPVQgX4g@mail.gmail.com
> Signed-off-by: Hao Jia <jiahao1@lixiang.com>
Ah good catch.
> ---
> mm/zswap.c | 16 ++++++++++++++--
> 1 file changed, 14 insertions(+), 2 deletions(-)
>
> diff --git a/mm/zswap.c b/mm/zswap.c
> index 761cd699e0a3..0f8f04f22888 100644
> --- a/mm/zswap.c
> +++ b/mm/zswap.c
> @@ -1356,7 +1356,12 @@ static void shrink_worker(struct work_struct *w)
> } while (memcg && !mem_cgroup_tryget_online(memcg));
> spin_unlock(&zswap_shrink_lock);
>
> - if (!memcg) {
> + /*
> + * Reaching a NULL memcg means a full hierarchy pass completed.
> + * Exclude the memcg-disabled case, where it is always NULL, and
> + * fall through to shrink the root LRU directly.
> + */
> + if (!memcg && !mem_cgroup_disabled()) {
> /*
> * Continue shrinking without incrementing failures if
> * we found candidate memcgs in the last tree walk.
nit: I wonder if we can just merge this comment with the new comment
you just added.
> @@ -1378,8 +1383,15 @@ static void shrink_worker(struct work_struct *w)
> * with pages in zswap. Skip this without incrementing attempts
> * and failures.
> */
> - if (ret == -ENOENT)
> + if (ret == -ENOENT) {
> + /*
> + * With memcg disabled the root LRU is the only target, so
> + * we should abort if it has no writeback-candidate pages.
> + */
> + if (mem_cgroup_disabled())
> + break;
Hmm do we need to do this? Consider a system with cgroup enabled but
with just one cgroup (root?). The behavior would just be trying that
cgroup for MAX_RECLAIM_RETRIES failure attempts, correct?
In that case, we don't need to do this check, and we would get the
same behavior. The loop would terminate after MAX_RECLAIM_RETRIES :)
Could you fact-check me? :)
^ permalink raw reply
* RE: [PATCH v14 4/5] gpio: rpmsg: add generic rpmsg GPIO driver
From: Shenwei Wang (OSS) @ 2026-06-29 18:26 UTC (permalink / raw)
To: Andrew Davis, Linus Walleij, Bartosz Golaszewski, Jonathan Corbet,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Mathieu Poirier, Frank Li, Sascha Hauer
Cc: Shuah Khan, linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, Pengutronix Kernel Team,
Fabio Estevam, Shenwei Wang, Peng Fan, devicetree@vger.kernel.org,
linux-remoteproc@vger.kernel.org, imx@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, dl-linux-imx,
Arnaud POULIQUEN, b-padhi@ti.com, Andrew Lunn,
Bartosz Golaszewski
In-Reply-To: <655ef1d6-08fd-43a9-8507-c2d478c058d8@ti.com>
> -----Original Message-----
> From: Andrew Davis <afd@ti.com>
> Sent: Thursday, June 25, 2026 3:32 PM
...
> Subject: Re: [PATCH v14 4/5] gpio: rpmsg: add generic rpmsg GPIO driver
> > + Say yes here to support the generic GPIO functions over the RPMSG
> > + bus. Currently supported devices: i.MX7ULP, i.MX8ULP, i.MX8x, and
> > + i.MX9x.
>
> The support would depend on if the right firmware is loaded/running on the given
> remote core. Also if you want to make this generic, then any vendor should be
> able to make a firmware that implements this protocol and make use of this
> driver.
> Suggest dropping this NXP specific device list.
>
Agree.
> > +
> > + If unsure, say N.
> > +
> > +endmenu
> > +
> > menu "SPI GPIO expanders"
> > depends on SPI_MASTER
> >
> > diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index
> > b267598b517d..ee75c0e65b8b 100644
> > --- a/drivers/gpio/Makefile
> > +++ b/drivers/gpio/Makefile
> > @@ -157,6 +157,7 @@ obj-$(CONFIG_GPIO_RDC321X) += gpio-
...
> > +
> > +static int rpmsg_gpio_channel_probe(struct rpmsg_device *rpdev) {
> > + struct device *dev = &rpdev->dev;
> > + struct device_node *np;
> > + const char *rproc_name;
> > + int idx;
> > +
> > + idx = rpmsg_get_gpio_index(rpdev->id.name, CHAN_NAME_PREFIX);
> > + if (idx < 0)
> > + return -EINVAL;
> > +
> > + if (!dev->of_node) {
> > + np = rpmsg_get_channel_ofnode(rpdev, GPIO_COMPAT_STR, idx);
> > + if (!np)
> > + return -ENODEV;
>
> This seems to imply that DT nodes are required. RPMSG is a discoverable bus
> with a nameservice that can bind/probe new devices. While then optionally
> binding to a DT node when available so sub-devices can be described in DT is fine,
> I don't see why it should be required.
>
First, a GPIO node typically acts as a provider for other devices.
Second, by requiring a DT node, we can ensure that only explicitly enabled GPIO resources are managed and accessible.
> > +static struct rpmsg_driver rpmsg_gpio_channel_client = {
> > + .callback = rpmsg_gpio_channel_callback,
> > + .id_table = rpmsg_gpio_channel_id_table,
> > + .probe = rpmsg_gpio_channel_probe,
> > + .drv = {
> > + .name = KBUILD_MODNAME,
> > + .of_match_table = rpmsg_gpio_dt_ids,
>
> Does this line actually do anything anymore? Maybe it did when this was a
> platform_driver, but this is a rpmsg_driver and will probe though .id_table
> matches.
>
Yes, it can be removed because the driver will find the dt node on its own.
Thanks,
Shenwei
> Andrew
>
> > + },
> > +};
> > +module_rpmsg_driver(rpmsg_gpio_channel_client);
> > +
> > +MODULE_AUTHOR("Shenwei Wang <shenwei.wang@nxp.com>");
> > +MODULE_DESCRIPTION("generic rpmsg gpio driver");
> > +MODULE_LICENSE("GPL");
>
^ permalink raw reply
* [PATCH net-next V4 6/6] net/mlx5: Apply devlink eswitch mode boot default on probe
From: Mark Bloch @ 2026-06-29 18:21 UTC (permalink / raw)
To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
Mark Bloch
In-Reply-To: <20260629182102.245150-1-mbloch@nvidia.com>
Apply devlink_eswitch_mode= boot defaults for mlx5 after the initial
probe finishes device initialization while holding the devlink instance
lock.
At this point the devlink instance is registered and mlx5 can perform an
eswitch mode change. Calling devl_apply_default_esw_mode() also clears
any pending default apply work queued by devl_register(), so the queued
work will not apply the same default again.
Keep this call in mlx5_init_one() rather than the lower-level
devl-locked init helper. That helper is also used by devlink reload, and
devlink core already applies the boot default after a successful
DRIVER_REINIT reload.
Signed-off-by: Mark Bloch <mbloch@nvidia.com>
---
drivers/net/ethernet/mellanox/mlx5/core/main.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c
index 643b4aac2033..0712efea74cc 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/main.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c
@@ -1392,6 +1392,17 @@ static void mlx5_unload(struct mlx5_core_dev *dev)
mlx5_free_bfreg(dev, &dev->priv.bfreg);
}
+static void mlx5_devl_apply_default_esw_mode(struct mlx5_core_dev *dev)
+{
+ struct devlink *devlink = priv_to_devlink(dev);
+
+ if (!MLX5_ESWITCH_MANAGER(dev))
+ return;
+
+ devl_assert_locked(devlink);
+ devl_apply_default_esw_mode(devlink);
+}
+
int mlx5_init_one_devl_locked(struct mlx5_core_dev *dev)
{
bool light_probe = mlx5_dev_is_lightweight(dev);
@@ -1471,6 +1482,8 @@ int mlx5_init_one(struct mlx5_core_dev *dev)
err = mlx5_init_one_devl_locked(dev);
if (err)
devl_unregister(devlink);
+ else
+ mlx5_devl_apply_default_esw_mode(dev);
unlock:
devl_unlock(devlink);
return err;
--
2.43.0
^ permalink raw reply related
* [PATCH net-next V4 4/6] devlink: Apply eswitch mode boot defaults
From: Mark Bloch @ 2026-06-29 18:20 UTC (permalink / raw)
To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
Mark Bloch
In-Reply-To: <20260629182102.245150-1-mbloch@nvidia.com>
Apply parsed devlink_eswitch_mode= defaults after devlink registration
and after successful reload.
devl_register() may still be called before the device is ready for an
eswitch mode change, so keep a per-devlink delayed work item and pending
flag for the registration path. Registration queues the work, and the
worker tries to take the devlink instance lock.
If the lock is busy, the worker requeues itself with a delay.
For successful reloads that performed DRIVER_REINIT, devlink_reload()
already holds the devlink instance lock and the driver has completed
reload_up(). Clear pending work and apply the default directly from the
reload path instead of queueing work.
If a user sets eswitch mode through netlink before the pending
registration work runs, clear the pending flag so the queued default does
not override that user request. Cancel pending default apply work when
freeing the devlink instance.
Signed-off-by: Mark Bloch <mbloch@nvidia.com>
---
net/devlink/core.c | 198 +++++++++++++++++++++++++++++++-----
net/devlink/dev.c | 6 ++
net/devlink/devl_internal.h | 5 +
3 files changed, 182 insertions(+), 27 deletions(-)
diff --git a/net/devlink/core.c b/net/devlink/core.c
index 5126509a9c4e..998e4ffd5dce 100644
--- a/net/devlink/core.c
+++ b/net/devlink/core.c
@@ -5,6 +5,7 @@
*/
#include <linux/init.h>
+#include <linux/jiffies.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/string.h>
@@ -22,8 +23,12 @@ DEFINE_XARRAY_FLAGS(devlinks, XA_FLAGS_ALLOC);
static char *devlink_default_esw_mode_param;
static bool devlink_default_esw_mode_match_all;
+static bool devlink_default_esw_mode_enabled;
static enum devlink_eswitch_mode devlink_default_esw_mode;
static LIST_HEAD(devlink_default_esw_mode_nodes);
+static struct workqueue_struct *devlink_default_esw_mode_wq;
+
+#define DEVLINK_DEFAULT_ESW_MODE_APPLY_DELAY msecs_to_jiffies(100)
struct devlink_default_esw_mode_node {
struct list_head list;
@@ -166,6 +171,7 @@ static void __init devlink_default_esw_mode_nodes_clear(void)
}
devlink_default_esw_mode_match_all = false;
+ devlink_default_esw_mode_enabled = false;
}
static int __init devlink_default_esw_mode_parse(char *str)
@@ -192,14 +198,113 @@ static int __init devlink_default_esw_mode_parse(char *str)
return err;
err = devlink_default_esw_mode_handles_parse(handles);
- if (err)
+ if (err) {
devlink_default_esw_mode_nodes_clear();
- else
+ } else {
devlink_default_esw_mode = esw_mode;
+ devlink_default_esw_mode_enabled = true;
+ }
return err;
}
+static bool devlink_default_esw_mode_match(struct devlink *devlink)
+{
+ const char *bus_name = devlink_bus_name(devlink);
+ const char *dev_name = devlink_dev_name(devlink);
+ struct devlink_default_esw_mode_node *node;
+
+ if (devlink_default_esw_mode_match_all)
+ return true;
+
+ node = devlink_default_esw_mode_node_find(bus_name, dev_name);
+ return !!node;
+}
+
+void devlink_default_esw_mode_apply(struct devlink *devlink)
+{
+ const struct devlink_ops *ops = devlink->ops;
+ int err;
+
+ devl_assert_locked(devlink);
+
+ if (!devlink_default_esw_mode_match(devlink))
+ return;
+
+ if (!ops->eswitch_mode_set) {
+ if (!devlink_default_esw_mode_match_all)
+ devl_warn(devlink,
+ "devlink_eswitch_mode= selected this device but eswitch mode setting is not supported\n");
+ return;
+ }
+
+ err = devlink_eswitch_mode_set(devlink, devlink_default_esw_mode, NULL);
+ if (err)
+ devl_warn(devlink,
+ "Couldn't apply default eswitch mode, err %d\n",
+ err);
+}
+
+static void
+devlink_default_esw_mode_apply_queue(struct devlink *devlink,
+ unsigned long delay)
+{
+ if (!devlink_default_esw_mode_enabled || !devlink_default_esw_mode_wq)
+ return;
+ if (!devlink_try_get(devlink))
+ return;
+ if (!queue_delayed_work(devlink_default_esw_mode_wq,
+ &devlink->default_esw_mode_apply_dw,
+ delay))
+ devlink_put(devlink);
+}
+
+static void devlink_default_esw_mode_apply_work(struct work_struct *work)
+{
+ unsigned long delay = DEVLINK_DEFAULT_ESW_MODE_APPLY_DELAY;
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct devlink *devlink;
+
+ devlink = container_of(dwork, struct devlink,
+ default_esw_mode_apply_dw);
+ if (!devl_trylock(devlink)) {
+ if (__devl_is_registered(devlink))
+ devlink_default_esw_mode_apply_queue(devlink, delay);
+ devlink_put(devlink);
+ return;
+ }
+
+ if (devl_is_registered(devlink) &&
+ devlink->default_esw_mode_apply_pending) {
+ devlink_default_esw_mode_apply(devlink);
+ devlink->default_esw_mode_apply_pending = false;
+ }
+
+ devl_unlock(devlink);
+ devlink_put(devlink);
+}
+
+void devlink_default_esw_mode_apply_schedule(struct devlink *devlink)
+{
+ devl_assert_locked(devlink);
+
+ devlink->default_esw_mode_apply_pending = true;
+ devlink_default_esw_mode_apply_queue(devlink, 0);
+}
+
+void devlink_default_esw_mode_apply_disable(struct devlink *devlink)
+{
+ devl_assert_locked(devlink);
+
+ devlink->default_esw_mode_apply_pending = false;
+}
+
+static void devlink_default_esw_mode_apply_cancel(struct devlink *devlink)
+{
+ if (cancel_delayed_work_sync(&devlink->default_esw_mode_apply_dw))
+ devlink_put(devlink);
+}
+
static int __init devlink_default_esw_mode_setup(char *str)
{
devlink_default_esw_mode_param = str;
@@ -577,6 +682,12 @@ struct devlink *devlinks_xa_lookup_get(struct net *net, unsigned long index)
* Make @devlink visible to userspace. Drivers must call this only after the
* instance is fully initialized and its devlink operations can be called.
*
+ * If a matching devlink_eswitch_mode= default was provided on the kernel
+ * command line, devlink core schedules async work to apply it after
+ * registration. Drivers implementing eswitch_mode_set() must therefore be
+ * ready to perform the same work as a userspace eswitch mode set request from
+ * this point, including creation of representors and other eswitch state.
+ *
* Context: Caller must hold the devlink instance lock. Use devlink_register()
* when the lock is not already held.
*
@@ -590,6 +701,7 @@ int devl_register(struct devlink *devlink)
xa_set_mark(&devlinks, devlink->index, DEVLINK_REGISTERED);
devlink_notify_register(devlink);
devlink_rel_nested_in_notify(devlink);
+ devlink_default_esw_mode_apply_schedule(devlink);
return 0;
}
@@ -612,6 +724,7 @@ void devl_unregister(struct devlink *devlink)
ASSERT_DEVLINK_REGISTERED(devlink);
devl_assert_locked(devlink);
+ devlink_default_esw_mode_apply_disable(devlink);
devlink_notify_unregister(devlink);
xa_clear_mark(&devlinks, devlink->index, DEVLINK_REGISTERED);
devlink_rel_put(devlink);
@@ -673,6 +786,9 @@ struct devlink *__devlink_alloc(const struct devlink_ops *ops, size_t priv_size,
INIT_LIST_HEAD(&devlink->trap_group_list);
INIT_LIST_HEAD(&devlink->trap_policer_list);
INIT_RCU_WORK(&devlink->rwork, devlink_release);
+ INIT_DELAYED_WORK(&devlink->default_esw_mode_apply_dw,
+ devlink_default_esw_mode_apply_work);
+ devlink->default_esw_mode_apply_pending = true;
lockdep_register_key(&devlink->lock_key);
mutex_init(&devlink->lock);
lockdep_set_class(&devlink->lock, &devlink->lock_key);
@@ -716,6 +832,7 @@ EXPORT_SYMBOL_GPL(devlink_alloc_ns);
void devlink_free(struct devlink *devlink)
{
ASSERT_DEVLINK_NOT_REGISTERED(devlink);
+ devlink_default_esw_mode_apply_cancel(devlink);
devlink_rel_put(devlink);
@@ -775,35 +892,59 @@ static struct notifier_block devlink_port_netdevice_nb = {
.notifier_call = devlink_port_netdevice_event,
};
-static int __init devlink_init(void)
+static int __init devlink_default_esw_mode_init(void)
{
+ char *def;
int err;
- if (devlink_default_esw_mode_param) {
- char *def;
-
- def = kstrdup(devlink_default_esw_mode_param, GFP_KERNEL);
- if (!def) {
- devlink_default_esw_mode_param = NULL;
- pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate memory\n");
- } else {
- err = devlink_default_esw_mode_parse(def);
- kfree(def);
- if (err == -EEXIST) {
- devlink_default_esw_mode_param = NULL;
- pr_warn("devlink: duplicate eswitch mode handles ignored\n");
- } else if (err == -EINVAL) {
- devlink_default_esw_mode_param = NULL;
- pr_warn("devlink: invalid devlink_eswitch_mode parameter ignored\n");
- } else if (err == -ENOMEM) {
- devlink_default_esw_mode_param = NULL;
- pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate memory\n");
- } else if (err) {
- goto out;
- }
- }
+ if (!devlink_default_esw_mode_param)
+ return 0;
+
+ def = kstrdup(devlink_default_esw_mode_param, GFP_KERNEL);
+ if (!def) {
+ devlink_default_esw_mode_param = NULL;
+ pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate memory\n");
+ return 0;
+ }
+
+ err = devlink_default_esw_mode_parse(def);
+ kfree(def);
+ if (err == -EEXIST) {
+ devlink_default_esw_mode_param = NULL;
+ pr_warn("devlink: duplicate eswitch mode handles ignored\n");
+ return 0;
+ } else if (err == -EINVAL) {
+ devlink_default_esw_mode_param = NULL;
+ pr_warn("devlink: invalid devlink_eswitch_mode parameter ignored\n");
+ return 0;
+ } else if (err == -ENOMEM) {
+ devlink_default_esw_mode_param = NULL;
+ pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate memory\n");
+ return 0;
+ } else if (err) {
+ return err;
}
+ devlink_default_esw_mode_wq = alloc_workqueue("devlink_default_esw_mode",
+ WQ_UNBOUND | WQ_MEM_RECLAIM,
+ 0);
+ if (!devlink_default_esw_mode_wq) {
+ devlink_default_esw_mode_param = NULL;
+ devlink_default_esw_mode_nodes_clear();
+ pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate workqueue\n");
+ }
+
+ return 0;
+}
+
+static int __init devlink_init(void)
+{
+ int err;
+
+ err = devlink_default_esw_mode_init();
+ if (err)
+ goto out;
+
err = register_pernet_subsys(&devlink_pernet_ops);
if (err)
goto out;
@@ -819,8 +960,11 @@ static int __init devlink_init(void)
out_unreg_pernet_subsys:
unregister_pernet_subsys(&devlink_pernet_ops);
out:
- if (err)
+ if (err) {
+ if (devlink_default_esw_mode_wq)
+ destroy_workqueue(devlink_default_esw_mode_wq);
devlink_default_esw_mode_nodes_clear();
+ }
WARN_ON(err);
return err;
diff --git a/net/devlink/dev.c b/net/devlink/dev.c
index 4fb02bb993c1..7f6ed52a5f73 100644
--- a/net/devlink/dev.c
+++ b/net/devlink/dev.c
@@ -478,6 +478,11 @@ int devlink_reload(struct devlink *devlink, struct net *dest_net,
return err;
WARN_ON(!(*actions_performed & BIT(action)));
+ if (*actions_performed & BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT)) {
+ devlink_default_esw_mode_apply_disable(devlink);
+ devlink_default_esw_mode_apply(devlink);
+ }
+
/* Catch driver on updating the remote action within devlink reload */
WARN_ON(memcmp(remote_reload_stats, devlink->stats.remote_reload_stats,
sizeof(remote_reload_stats)));
@@ -731,6 +736,7 @@ int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info)
u16 mode;
if (info->attrs[DEVLINK_ATTR_ESWITCH_MODE]) {
+ devlink_default_esw_mode_apply_disable(devlink);
mode = nla_get_u16(info->attrs[DEVLINK_ATTR_ESWITCH_MODE]);
err = devlink_eswitch_mode_set(devlink, mode, info->extack);
if (err)
diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h
index 97be77d3ed42..d6ff233da974 100644
--- a/net/devlink/devl_internal.h
+++ b/net/devlink/devl_internal.h
@@ -58,8 +58,10 @@ struct devlink {
struct mutex lock;
struct lock_class_key lock_key;
u8 reload_failed:1;
+ u8 default_esw_mode_apply_pending:1;
refcount_t refcount;
struct rcu_work rwork;
+ struct delayed_work default_esw_mode_apply_dw;
struct devlink_rel *rel;
struct xarray nested_rels;
char priv[] __aligned(NETDEV_ALIGN);
@@ -71,6 +73,9 @@ extern struct genl_family devlink_nl_family;
struct devlink *__devlink_alloc(const struct devlink_ops *ops, size_t priv_size,
struct net *net, struct device *dev,
const struct device_driver *dev_driver);
+void devlink_default_esw_mode_apply(struct devlink *devlink);
+void devlink_default_esw_mode_apply_schedule(struct devlink *devlink);
+void devlink_default_esw_mode_apply_disable(struct devlink *devlink);
#define devl_warn(devlink, format, args...) \
do { \
--
2.43.0
^ permalink raw reply related
* [PATCH net-next V4 5/6] devlink: Add API to apply eswitch mode boot default
From: Mark Bloch @ 2026-06-29 18:21 UTC (permalink / raw)
To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
Mark Bloch
In-Reply-To: <20260629182102.245150-1-mbloch@nvidia.com>
Add devl_apply_default_esw_mode() for drivers that can apply the
devlink_eswitch_mode= boot default once their device is ready instead of
waiting for the asynchronous registration work.
Signed-off-by: Mark Bloch <mbloch@nvidia.com>
---
include/net/devlink.h | 1 +
net/devlink/core.c | 22 ++++++++++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/include/net/devlink.h b/include/net/devlink.h
index dd546dbd57cf..b71d282c6d52 100644
--- a/include/net/devlink.h
+++ b/include/net/devlink.h
@@ -1652,6 +1652,7 @@ static inline struct devlink *devlink_alloc(const struct devlink_ops *ops,
int devl_register(struct devlink *devlink);
void devl_unregister(struct devlink *devlink);
+void devl_apply_default_esw_mode(struct devlink *devlink);
void devlink_register(struct devlink *devlink);
void devlink_unregister(struct devlink *devlink);
void devlink_free(struct devlink *devlink);
diff --git a/net/devlink/core.c b/net/devlink/core.c
index 998e4ffd5dce..d8f273e1732c 100644
--- a/net/devlink/core.c
+++ b/net/devlink/core.c
@@ -299,6 +299,28 @@ void devlink_default_esw_mode_apply_disable(struct devlink *devlink)
devlink->default_esw_mode_apply_pending = false;
}
+/**
+ * devl_apply_default_esw_mode - Apply devlink eswitch mode boot default
+ * @devlink: devlink
+ *
+ * Apply a matching devlink_eswitch_mode= boot default immediately. Drivers may
+ * use this helper when the device is ready for an eswitch mode change and the
+ * caller already holds the devlink instance lock.
+ *
+ * Any pending asynchronous default apply is cleared before applying the
+ * default, so work queued by devl_register() will not apply it again.
+ *
+ * Context: Caller must hold the devlink instance lock.
+ */
+void devl_apply_default_esw_mode(struct devlink *devlink)
+{
+ devl_assert_locked(devlink);
+
+ devlink->default_esw_mode_apply_pending = false;
+ devlink_default_esw_mode_apply(devlink);
+}
+EXPORT_SYMBOL_GPL(devl_apply_default_esw_mode);
+
static void devlink_default_esw_mode_apply_cancel(struct devlink *devlink)
{
if (cancel_delayed_work_sync(&devlink->default_esw_mode_apply_dw))
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v4 06/16] riscv: Add Ziccamoa, Ziccif, Ziccrse, and Za64rs to cpufeature and hwprobe
From: Jesse T @ 2026-06-29 18:21 UTC (permalink / raw)
To: Guodong Xu
Cc: Jonathan Corbet, Shuah Khan, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Zong Li, Deepak Gupta, Anup Patel,
Atish Patra, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Yixun Lan, Chen Wang, Inochi Amaoto, linux-doc, linux-riscv,
linux-kernel, kvm, kvm-riscv, Paul Walmsley, Conor Dooley,
devicetree, spacemit, sophgo, linux-kselftest, Palmer Dabbelt,
Andrew Jones
In-Reply-To: <20260611-rva23u64-hwprobe-v2-v4-6-3f01a2449488@gmail.com>
On Thu, Jun 11, 2026 at 4:14 PM Guodong Xu <docular.xu@gmail.com> wrote:
>
> From: Andrew Jones <andrew.jones@oss.qualcomm.com>
>
> Add Ziccamoa, Ziccif, and Za64rs to riscv_isa_ext[] so they can be
> parsed from devicetree/ACPI ISA strings. Ziccrse is already present
> in cpufeature; this patch only adds its hwprobe exposure.
>
> Expose all four extensions via hwprobe through new bits in
> RISCV_HWPROBE_KEY_IMA_EXT_1 (RISCV_HWPROBE_EXT_ZICCAMOA, _ZICCIF,
> _ZICCRSE, _ZA64RS), so userspace can probe each of these
> RVA23U64-mandatory extensions individually.
>
> Rationale for the validation dependencies added for Ziccamoa and Za64rs:
>
> 1) Ziccamoa depends on Zaamo. The RVA23 profile prose was updated
> post-ratification to spell out the Zaamo reference: commit
> 2b218613752d in riscv/riscv-profiles ("Improve description of
> Ziccamoa (#224)") reworded the rva23-profile.adoc (and other profiles
> that include Ziccamoa) text from "must support all atomics in A" to
> "must support all atomics in the Zaamo extension" [1].
>
> 2) Za64rs depends on Zalrsc. The unprivileged ISA manual src/zars.adoc,
> integrated in commit ebe06adc22cd ("Integrate profiles as Volume III
> (#2771)"), defines Za64rs as: "The Za64rs extension requires that the
> reservation sets used by the instructions in the Zalrsc extension be
> contiguous, naturally aligned, and at most 64 bytes in size" [2].
>
> Link: https://github.com/riscv/riscv-profiles/commit/2b218613752d63287286b5ae801b820cbd8cc10c [1]
> Link: https://github.com/riscv/riscv-isa-manual/blob/main/src/unpriv/zars.adoc [2]
> Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
> Signed-off-by: Guodong Xu <docular.xu@gmail.com>
Reviewed-by: Jesse Taube <jtaubepe@redhat.com>
> ---
> v4: No change.
> v3: Indent the added hwprobe.rst entries to match the normalized style; no other change.
> v2:
> - Rebased to v7.1-rc2.
> - Reworded subject and expanded commit message.
> - Validation added for Ziccamoa depending on Zaamo and Za64rs depending
> on Zalrsc.
> ---
> Documentation/arch/riscv/hwprobe.rst | 16 ++++++++++++++++
> arch/riscv/include/asm/hwcap.h | 3 +++
> arch/riscv/include/uapi/asm/hwprobe.h | 4 ++++
> arch/riscv/kernel/cpufeature.c | 21 +++++++++++++++++++++
> arch/riscv/kernel/sys_hwprobe.c | 4 ++++
> 5 files changed, 48 insertions(+)
>
> diff --git a/Documentation/arch/riscv/hwprobe.rst b/Documentation/arch/riscv/hwprobe.rst
> index 49d9fb68632d0..893e1a1215d23 100644
> --- a/Documentation/arch/riscv/hwprobe.rst
> +++ b/Documentation/arch/riscv/hwprobe.rst
> @@ -405,3 +405,19 @@ The following keys are defined:
> * :c:macro:`RISCV_HWPROBE_EXT_ZICCLSM`: The Zicclsm extension is supported,
> as defined in the RISC-V Profiles specification starting from commit
> b1d80660 ("Updated to ratified state.")
> +
> + * :c:macro:`RISCV_HWPROBE_EXT_ZICCAMOA`: The Ziccamoa extension is supported,
> + as defined in the RISC-V Profiles specification starting from commit
> + b1d80660 ("Updated to ratified state.")
> +
> + * :c:macro:`RISCV_HWPROBE_EXT_ZICCIF`: The Ziccif extension is supported,
> + as defined in the RISC-V Profiles specification starting from commit
> + b1d80660 ("Updated to ratified state.")
> +
> + * :c:macro:`RISCV_HWPROBE_EXT_ZICCRSE`: The Ziccrse extension is supported,
> + as defined in the RISC-V Profiles specification starting from commit
> + b1d80660 ("Updated to ratified state.")
> +
> + * :c:macro:`RISCV_HWPROBE_EXT_ZA64RS`: The Za64rs extension is supported,
> + as defined in the RISC-V Profiles specification starting from commit
> + b1d80660 ("Updated to ratified state.")
> diff --git a/arch/riscv/include/asm/hwcap.h b/arch/riscv/include/asm/hwcap.h
> index e8f4a7dd96a93..0acb7a01ecc0f 100644
> --- a/arch/riscv/include/asm/hwcap.h
> +++ b/arch/riscv/include/asm/hwcap.h
> @@ -113,6 +113,9 @@
> #define RISCV_ISA_EXT_ZICFILP 104
> #define RISCV_ISA_EXT_ZICFISS 105
> #define RISCV_ISA_EXT_ZICCLSM 106
> +#define RISCV_ISA_EXT_ZICCAMOA 107
> +#define RISCV_ISA_EXT_ZICCIF 108
> +#define RISCV_ISA_EXT_ZA64RS 109
>
> #define RISCV_ISA_EXT_XLINUXENVCFG 127
>
> diff --git a/arch/riscv/include/uapi/asm/hwprobe.h b/arch/riscv/include/uapi/asm/hwprobe.h
> index 6819df159c51e..58d1e86e47ae7 100644
> --- a/arch/riscv/include/uapi/asm/hwprobe.h
> +++ b/arch/riscv/include/uapi/asm/hwprobe.h
> @@ -117,6 +117,10 @@ struct riscv_hwprobe {
> #define RISCV_HWPROBE_KEY_IMA_EXT_1 16
> #define RISCV_HWPROBE_EXT_ZICFISS (1ULL << 0)
> #define RISCV_HWPROBE_EXT_ZICCLSM (1ULL << 1)
> +#define RISCV_HWPROBE_EXT_ZICCAMOA (1ULL << 2)
> +#define RISCV_HWPROBE_EXT_ZICCIF (1ULL << 3)
> +#define RISCV_HWPROBE_EXT_ZICCRSE (1ULL << 4)
> +#define RISCV_HWPROBE_EXT_ZA64RS (1ULL << 5)
>
> /* Increase RISCV_HWPROBE_MAX_KEY when adding items. */
>
> diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c
> index 1fb595581adcf..b9538e69fa1b3 100644
> --- a/arch/riscv/kernel/cpufeature.c
> +++ b/arch/riscv/kernel/cpufeature.c
> @@ -90,6 +90,24 @@ static int riscv_ext_f_depends(const struct riscv_isa_ext_data *data,
> return -EPROBE_DEFER;
> }
>
> +static int riscv_ext_zaamo_depends(const struct riscv_isa_ext_data *data,
> + const unsigned long *isa_bitmap)
> +{
> + if (__riscv_isa_extension_available(isa_bitmap, RISCV_ISA_EXT_ZAAMO))
> + return 0;
> +
> + return -EPROBE_DEFER;
> +}
> +
> +static int riscv_ext_zalrsc_depends(const struct riscv_isa_ext_data *data,
> + const unsigned long *isa_bitmap)
> +{
> + if (__riscv_isa_extension_available(isa_bitmap, RISCV_ISA_EXT_ZALRSC))
> + return 0;
> +
> + return -EPROBE_DEFER;
> +}
> +
> static int riscv_ext_zicbom_validate(const struct riscv_isa_ext_data *data,
> const unsigned long *isa_bitmap)
> {
> @@ -502,6 +520,8 @@ const struct riscv_isa_ext_data riscv_isa_ext[] = {
> __RISCV_ISA_EXT_SUPERSET_VALIDATE(zicbom, RISCV_ISA_EXT_ZICBOM, riscv_xlinuxenvcfg_exts, riscv_ext_zicbom_validate),
> __RISCV_ISA_EXT_DATA_VALIDATE(zicbop, RISCV_ISA_EXT_ZICBOP, riscv_ext_zicbop_validate),
> __RISCV_ISA_EXT_SUPERSET_VALIDATE(zicboz, RISCV_ISA_EXT_ZICBOZ, riscv_xlinuxenvcfg_exts, riscv_ext_zicboz_validate),
> + __RISCV_ISA_EXT_DATA_VALIDATE(ziccamoa, RISCV_ISA_EXT_ZICCAMOA, riscv_ext_zaamo_depends),
> + __RISCV_ISA_EXT_DATA(ziccif, RISCV_ISA_EXT_ZICCIF),
> __RISCV_ISA_EXT_DATA(zicclsm, RISCV_ISA_EXT_ZICCLSM),
> __RISCV_ISA_EXT_DATA(ziccrse, RISCV_ISA_EXT_ZICCRSE),
> __RISCV_ISA_EXT_SUPERSET_VALIDATE(zicfilp, RISCV_ISA_EXT_ZICFILP, riscv_xlinuxenvcfg_exts,
> @@ -516,6 +536,7 @@ const struct riscv_isa_ext_data riscv_isa_ext[] = {
> __RISCV_ISA_EXT_DATA(zihintpause, RISCV_ISA_EXT_ZIHINTPAUSE),
> __RISCV_ISA_EXT_DATA(zihpm, RISCV_ISA_EXT_ZIHPM),
> __RISCV_ISA_EXT_DATA(zimop, RISCV_ISA_EXT_ZIMOP),
> + __RISCV_ISA_EXT_DATA_VALIDATE(za64rs, RISCV_ISA_EXT_ZA64RS, riscv_ext_zalrsc_depends),
> __RISCV_ISA_EXT_DATA(zaamo, RISCV_ISA_EXT_ZAAMO),
> __RISCV_ISA_EXT_DATA(zabha, RISCV_ISA_EXT_ZABHA),
> __RISCV_ISA_EXT_DATA(zacas, RISCV_ISA_EXT_ZACAS),
> diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c
> index 9cf62266f1890..b15ac9adf7920 100644
> --- a/arch/riscv/kernel/sys_hwprobe.c
> +++ b/arch/riscv/kernel/sys_hwprobe.c
> @@ -206,6 +206,10 @@ static void hwprobe_isa_ext1(struct riscv_hwprobe *pair,
> */
> EXT_KEY(isainfo->isa, ZICFISS, pair->value, missing);
> EXT_KEY(isainfo->isa, ZICCLSM, pair->value, missing);
> + EXT_KEY(isainfo->isa, ZICCAMOA, pair->value, missing);
> + EXT_KEY(isainfo->isa, ZICCIF, pair->value, missing);
> + EXT_KEY(isainfo->isa, ZICCRSE, pair->value, missing);
> + EXT_KEY(isainfo->isa, ZA64RS, pair->value, missing);
> }
>
> /* Now turn off reporting features if any CPU is missing it. */
>
> --
> 2.43.0
>
>
> _______________________________________________
> linux-riscv mailing list
> linux-riscv@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply
* [PATCH net-next V4 3/6] devlink: Parse eswitch mode boot defaults
From: Mark Bloch @ 2026-06-29 18:20 UTC (permalink / raw)
To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
Mark Bloch
In-Reply-To: <20260629182102.245150-1-mbloch@nvidia.com>
Add devlink_eswitch_mode= kernel command line parsing for a default
eswitch mode.
The supported syntax selects either all devlink handles or one explicit
comma-separated handle list:
devlink_eswitch_mode=*=<mode>
devlink_eswitch_mode=<handle>[,<handle>...]=<mode>
where <mode> is one of legacy, switchdev or switchdev_inactive. All
selected handles receive the same mode. Assigning different modes to
different handle lists in the same parameter value is not supported.
Store the parsed selector and mode in devlink core so the default can be
applied by a downstream patch.
Document the devlink_eswitch_mode= syntax and duplicate handle handling.
Signed-off-by: Mark Bloch <mbloch@nvidia.com>
---
.../admin-guide/kernel-parameters.txt | 25 ++
.../networking/devlink/devlink-defaults.rst | 78 ++++++
Documentation/networking/devlink/index.rst | 1 +
net/devlink/core.c | 227 ++++++++++++++++++
4 files changed, 331 insertions(+)
create mode 100644 Documentation/networking/devlink/devlink-defaults.rst
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index b5493a7f8f22..117300dd589c 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1249,6 +1249,31 @@ Kernel parameters
dell_smm_hwmon.fan_max=
[HW] Maximum configurable fan speed.
+ devlink_eswitch_mode=
+ [NET]
+ Format:
+ <selector>=<mode>
+
+ <selector>:
+ * | <handle>[,<handle>...]
+
+ <handle>:
+ <bus-name>/<dev-name>
+
+ Configure default devlink eswitch mode for matching
+ devlink instances during device initialization.
+
+ <mode>:
+ legacy | switchdev | switchdev_inactive
+
+ Examples:
+ devlink_eswitch_mode=*=switchdev
+ devlink_eswitch_mode=pci/0000:08:00.0=switchdev
+ devlink_eswitch_mode=pci/0000:08:00.0,pci/0000:09:00.1=switchdev_inactive
+
+ See Documentation/networking/devlink/devlink-defaults.rst
+ for the full syntax.
+
dfltcc= [HW,S390]
Format: { on | off | def_only | inf_only | always }
on: s390 zlib hardware support for compression on
diff --git a/Documentation/networking/devlink/devlink-defaults.rst b/Documentation/networking/devlink/devlink-defaults.rst
new file mode 100644
index 000000000000..380c9e99210e
--- /dev/null
+++ b/Documentation/networking/devlink/devlink-defaults.rst
@@ -0,0 +1,78 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+==============================
+Devlink Eswitch Mode Defaults
+==============================
+
+Devlink eswitch mode defaults allow the eswitch mode to be provided on the
+kernel command line and applied to matching devlink instances during device
+initialization.
+
+The devlink device is selected by its devlink handle. For PCI devices this is
+the same handle shown by ``devlink dev show``, for example
+``pci/0000:08:00.0``.
+
+Kernel command line syntax
+==========================
+
+Defaults are specified with the ``devlink_eswitch_mode=`` kernel command line
+parameter.
+
+The general syntax is::
+
+ devlink_eswitch_mode=<selector>=<mode>
+
+``<selector>`` is either ``*`` or one or more devlink handles::
+
+ * | <bus-name>/<dev-name>[,<bus-name>/<dev-name>...]
+
+``*`` applies the mode to every devlink instance. All handles in the same
+selector receive the same eswitch mode.
+
+``<mode>`` is one of ``legacy``, ``switchdev`` or ``switchdev_inactive``.
+
+Syntax rules
+------------
+
+The following syntax rules apply:
+
+* Specify the default in one ``devlink_eswitch_mode=`` parameter. Repeated
+ ``devlink_eswitch_mode=`` parameters are not accumulated.
+* The ``devlink_eswitch_mode=`` value is limited by the kernel command line
+ size.
+* Whitespace is not allowed within the parameter value.
+* ``<selector>`` must be either ``*`` or a handle list. ``*`` cannot be
+ combined with explicit handles.
+* ``<bus-name>`` and ``<dev-name>`` must not be empty.
+* ``<dev-name>`` may contain ``:``. This allows PCI names such as
+ ``0000:08:00.0``.
+* Handles must not contain whitespace, ``*``, ``=`` or more than one ``/``.
+* A comma separates handles.
+* Comma-separated default assignments are not supported.
+* Duplicate handles are rejected and the devlink eswitch mode default is
+ ignored.
+
+The eswitch mode default corresponds to the userspace command::
+
+ devlink dev eswitch set <handle> mode <value>
+
+
+Examples
+========
+
+Set all devlink instances to switchdev mode::
+
+ devlink_eswitch_mode=*=switchdev
+
+Set one PCI devlink instance to switchdev mode::
+
+ devlink_eswitch_mode=pci/0000:08:00.0=switchdev
+
+Set two PCI devlink instances to switchdev inactive mode::
+
+ devlink_eswitch_mode=pci/0000:08:00.0,pci/0000:09:00.1=switchdev_inactive
+
+The following is invalid because comma-separated default assignments are not
+supported::
+
+ devlink_eswitch_mode=pci/0000:08:00.0=switchdev,pci/0000:09:00.0=switchdev_inactive
diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst
index 32f70879ddd0..93f09cb18c44 100644
--- a/Documentation/networking/devlink/index.rst
+++ b/Documentation/networking/devlink/index.rst
@@ -56,6 +56,7 @@ general.
:maxdepth: 1
devlink-dpipe
+ devlink-defaults
devlink-eswitch-attr
devlink-flash
devlink-health
diff --git a/net/devlink/core.c b/net/devlink/core.c
index fe9f6a0a67d5..5126509a9c4e 100644
--- a/net/devlink/core.c
+++ b/net/devlink/core.c
@@ -4,6 +4,10 @@
* Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com>
*/
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/string.h>
#include <net/genetlink.h>
#define CREATE_TRACE_POINTS
#include <trace/events/devlink.h>
@@ -16,6 +20,193 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(devlink_trap_report);
DEFINE_XARRAY_FLAGS(devlinks, XA_FLAGS_ALLOC);
+static char *devlink_default_esw_mode_param;
+static bool devlink_default_esw_mode_match_all;
+static enum devlink_eswitch_mode devlink_default_esw_mode;
+static LIST_HEAD(devlink_default_esw_mode_nodes);
+
+struct devlink_default_esw_mode_node {
+ struct list_head list;
+ char *bus_name;
+ char *dev_name;
+};
+
+static int __init
+devlink_default_esw_mode_to_value(const char *str,
+ enum devlink_eswitch_mode *mode)
+{
+ if (!strcmp(str, "legacy")) {
+ *mode = DEVLINK_ESWITCH_MODE_LEGACY;
+ return 0;
+ }
+ if (!strcmp(str, "switchdev")) {
+ *mode = DEVLINK_ESWITCH_MODE_SWITCHDEV;
+ return 0;
+ }
+ if (!strcmp(str, "switchdev_inactive")) {
+ *mode = DEVLINK_ESWITCH_MODE_SWITCHDEV_INACTIVE;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int __init
+devlink_default_esw_mode_handle_parse(char *handle, char **bus_name,
+ char **dev_name)
+{
+ char *slash;
+ char *p;
+
+ if (!*handle)
+ return -EINVAL;
+
+ for (p = handle; *p; p++) {
+ if (*p == '*' || *p == '=')
+ return -EINVAL;
+ }
+
+ slash = strchr(handle, '/');
+ if (!slash || slash == handle || !slash[1])
+ return -EINVAL;
+ if (strchr(slash + 1, '/'))
+ return -EINVAL;
+
+ *slash = '\0';
+
+ *bus_name = handle;
+ *dev_name = slash + 1;
+ return 0;
+}
+
+static struct devlink_default_esw_mode_node *
+devlink_default_esw_mode_node_find(const char *bus_name, const char *dev_name)
+{
+ struct devlink_default_esw_mode_node *node;
+
+ list_for_each_entry(node, &devlink_default_esw_mode_nodes, list) {
+ if (!strcmp(node->bus_name, bus_name) &&
+ !strcmp(node->dev_name, dev_name))
+ return node;
+ }
+
+ return NULL;
+}
+
+static int __init
+devlink_default_esw_mode_node_add(const char *bus_name, const char *dev_name)
+{
+ struct devlink_default_esw_mode_node *node;
+
+ if (devlink_default_esw_mode_node_find(bus_name, dev_name))
+ return -EEXIST;
+
+ node = kzalloc_obj(*node);
+ if (!node)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&node->list);
+ node->bus_name = kstrdup(bus_name, GFP_KERNEL);
+ node->dev_name = kstrdup(dev_name, GFP_KERNEL);
+ if (!node->bus_name || !node->dev_name) {
+ kfree(node->bus_name);
+ kfree(node->dev_name);
+ kfree(node);
+ return -ENOMEM;
+ }
+
+ list_add_tail(&node->list, &devlink_default_esw_mode_nodes);
+ return 0;
+}
+
+static int __init devlink_default_esw_mode_handles_parse(char *handles)
+{
+ char *handle;
+ int err;
+
+ if (!strcmp(handles, "*")) {
+ devlink_default_esw_mode_match_all = true;
+ return 0;
+ }
+
+ while ((handle = strsep(&handles, ",")) != NULL) {
+ char *bus_name;
+ char *dev_name;
+
+ err = devlink_default_esw_mode_handle_parse(handle, &bus_name,
+ &dev_name);
+ if (err)
+ return err;
+
+ err = devlink_default_esw_mode_node_add(bus_name, dev_name);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static void __init
+devlink_default_esw_mode_node_free(struct devlink_default_esw_mode_node *node)
+{
+ kfree(node->bus_name);
+ kfree(node->dev_name);
+ kfree(node);
+}
+
+static void __init devlink_default_esw_mode_nodes_clear(void)
+{
+ struct devlink_default_esw_mode_node *node;
+ struct devlink_default_esw_mode_node *node_tmp;
+
+ list_for_each_entry_safe(node, node_tmp,
+ &devlink_default_esw_mode_nodes, list) {
+ list_del(&node->list);
+ devlink_default_esw_mode_node_free(node);
+ }
+
+ devlink_default_esw_mode_match_all = false;
+}
+
+static int __init devlink_default_esw_mode_parse(char *str)
+{
+ char *handles;
+ char *separator;
+ char *mode;
+ enum devlink_eswitch_mode esw_mode;
+ int err;
+
+ if (!*str)
+ return -EINVAL;
+
+ separator = strrchr(str, '=');
+ if (!separator || separator == str || !separator[1])
+ return -EINVAL;
+
+ *separator = '\0';
+ handles = str;
+ mode = separator + 1;
+
+ err = devlink_default_esw_mode_to_value(mode, &esw_mode);
+ if (err)
+ return err;
+
+ err = devlink_default_esw_mode_handles_parse(handles);
+ if (err)
+ devlink_default_esw_mode_nodes_clear();
+ else
+ devlink_default_esw_mode = esw_mode;
+
+ return err;
+}
+
+static int __init devlink_default_esw_mode_setup(char *str)
+{
+ devlink_default_esw_mode_param = str;
+ return 1;
+}
+__setup("devlink_eswitch_mode=", devlink_default_esw_mode_setup);
+
static struct devlink *devlinks_xa_get(unsigned long index)
{
struct devlink *devlink;
@@ -382,6 +573,14 @@ struct devlink *devlinks_xa_lookup_get(struct net *net, unsigned long index)
/**
* devl_register - Register devlink instance
* @devlink: devlink
+ *
+ * Make @devlink visible to userspace. Drivers must call this only after the
+ * instance is fully initialized and its devlink operations can be called.
+ *
+ * Context: Caller must hold the devlink instance lock. Use devlink_register()
+ * when the lock is not already held.
+ *
+ * Return: 0 on success.
*/
int devl_register(struct devlink *devlink)
{
@@ -580,6 +779,31 @@ static int __init devlink_init(void)
{
int err;
+ if (devlink_default_esw_mode_param) {
+ char *def;
+
+ def = kstrdup(devlink_default_esw_mode_param, GFP_KERNEL);
+ if (!def) {
+ devlink_default_esw_mode_param = NULL;
+ pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate memory\n");
+ } else {
+ err = devlink_default_esw_mode_parse(def);
+ kfree(def);
+ if (err == -EEXIST) {
+ devlink_default_esw_mode_param = NULL;
+ pr_warn("devlink: duplicate eswitch mode handles ignored\n");
+ } else if (err == -EINVAL) {
+ devlink_default_esw_mode_param = NULL;
+ pr_warn("devlink: invalid devlink_eswitch_mode parameter ignored\n");
+ } else if (err == -ENOMEM) {
+ devlink_default_esw_mode_param = NULL;
+ pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate memory\n");
+ } else if (err) {
+ goto out;
+ }
+ }
+ }
+
err = register_pernet_subsys(&devlink_pernet_ops);
if (err)
goto out;
@@ -595,7 +819,10 @@ static int __init devlink_init(void)
out_unreg_pernet_subsys:
unregister_pernet_subsys(&devlink_pernet_ops);
out:
+ if (err)
+ devlink_default_esw_mode_nodes_clear();
WARN_ON(err);
+
return err;
}
--
2.43.0
^ permalink raw reply related
* [PATCH net-next V4 2/6] devlink: Factor out eswitch mode setting
From: Mark Bloch @ 2026-06-29 18:20 UTC (permalink / raw)
To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
Mark Bloch
In-Reply-To: <20260629182102.245150-1-mbloch@nvidia.com>
Move the common eswitch mode set checks into a small helper and use it
from the netlink eswitch set command. Making the same validation
available to the devlink core path that applies eswitch mode defaults.
Signed-off-by: Mark Bloch <mbloch@nvidia.com>
---
net/devlink/dev.c | 27 ++++++++++++++++++++-------
net/devlink/devl_internal.h | 3 +++
2 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/net/devlink/dev.c b/net/devlink/dev.c
index 57b2b8f03543..4fb02bb993c1 100644
--- a/net/devlink/dev.c
+++ b/net/devlink/dev.c
@@ -702,6 +702,25 @@ int devlink_nl_eswitch_get_doit(struct sk_buff *skb, struct genl_info *info)
return genlmsg_reply(msg, info);
}
+int devlink_eswitch_mode_set(struct devlink *devlink,
+ enum devlink_eswitch_mode mode,
+ struct netlink_ext_ack *extack)
+{
+ const struct devlink_ops *ops = devlink->ops;
+ int err;
+
+ devl_assert_locked(devlink);
+
+ if (!ops->eswitch_mode_set)
+ return -EOPNOTSUPP;
+
+ err = devlink_rates_check(devlink, devlink_rate_is_node, extack);
+ if (err)
+ return err;
+
+ return ops->eswitch_mode_set(devlink, mode, extack);
+}
+
int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info)
{
struct devlink *devlink = info->user_ptr[0];
@@ -712,14 +731,8 @@ int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info)
u16 mode;
if (info->attrs[DEVLINK_ATTR_ESWITCH_MODE]) {
- if (!ops->eswitch_mode_set)
- return -EOPNOTSUPP;
- err = devlink_rates_check(devlink, devlink_rate_is_node,
- info->extack);
- if (err)
- return err;
mode = nla_get_u16(info->attrs[DEVLINK_ATTR_ESWITCH_MODE]);
- err = ops->eswitch_mode_set(devlink, mode, info->extack);
+ err = devlink_eswitch_mode_set(devlink, mode, info->extack);
if (err)
return err;
}
diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h
index e4e48ee2da5a..97be77d3ed42 100644
--- a/net/devlink/devl_internal.h
+++ b/net/devlink/devl_internal.h
@@ -328,6 +328,9 @@ bool devlink_rate_is_node(const struct devlink_rate *devlink_rate);
int devlink_rates_check(struct devlink *devlink,
bool (*rate_filter)(const struct devlink_rate *),
struct netlink_ext_ack *extack);
+int devlink_eswitch_mode_set(struct devlink *devlink,
+ enum devlink_eswitch_mode mode,
+ struct netlink_ext_ack *extack);
/* Linecards */
unsigned int devlink_linecard_index(struct devlink_linecard *linecard);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next V4 0/6] evlink: Add boot-time eswitch mode defaults
From: Mark Bloch @ 2026-06-29 18:20 UTC (permalink / raw)
To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
Mark Bloch
This series adds a devlink_eswitch_mode= kernel command line parameter
for setting a default devlink eswitch mode during boot.
Following the discussion with Jakub[1] and the feedback on the RFC
postings, this version keeps the scope limited to a boot-time devlink
eswitch mode default only.
The option selects either all devlink handles or an explicit
comma-separated handle list:
devlink_eswitch_mode=*=switchdev
devlink_eswitch_mode=pci/0000:08:00.0,pci/0000:09:00.1=switchdev_inactive
The supported modes are legacy, switchdev and switchdev_inactive. The
selected mode is applied through the existing eswitch_mode_set() devlink
operation, the same operation used by the devlink eswitch mode command.
Registration may happen before a driver is ready to change eswitch mode,
so devlink core queues an asynchronous apply request from devl_register().
The worker takes the devlink instance lock before calling into the driver.
After a successful reload that performed DRIVER_REINIT, devlink core
already holds the devlink instance lock and the driver completed
reload_up(), so the default is applied directly from the reload path.
Drivers that know exactly when the device is ready can call
devl_apply_default_esw_mode() directly. mlx5 uses this after initial
probe, when the device is initialized and the devlink lock is already
held.
Patch 1 clears the mlx5 FW reset-in-progress bit before reload.
Patch 2 factors the common eswitch mode set validation into a helper.
Patch 3 adds the devlink_eswitch_mode= parser and documentation.
Patch 4 applies parsed defaults from devlink core.
Patch 5 adds devl_apply_default_esw_mode() for drivers.
Patch 6 wires mlx5 to apply the default after initial probe.
Changelog:
v3 -> v4:
- Rework registration time apply to use per devlink delayed work instead
of calling eswitch_mode_set() directly from devl_register().
- Apply the default directly after successful DRIVER_REINIT devlink reload,
where the devlink lock is already held and reload_up() has completed.
- Add devl_apply_default_esw_mode() for drivers that know their exact ready
point.
- Drop the driver registration-ordering preparation patches that are no
longer needed with the async registration apply path.
v2 -> v3:
- Change the devlink_eswitch_mode= API syntax to use <selector>=<mode>
instead of [<selector>]:<mode>, following a comment from Randy Dunlap.
v1 -> v2:
- Move default eswitch mode application into devlink core. The default is
now applied during devlink registration and after a successful devlink
reload that performed DRIVER_REINIT.
- Remove the exported devl_apply_default_esw_mode() driver API and the mlx5
driver-side call to it.
- Skip devlink health recovery notifications while the devlink instance is
not registered, so drivers can move registration later without early
health work hitting registration assertions.
- Move mlx5 devlink registration after device initialization, including the
lightweight init path, so the core can apply the default through the
normal registration flow.
- Move the matching netdevsim and mlx5 unregister paths before object
teardown, so unregister notifications come from devl_unregister() and the
later object teardown paths run while the devlink instance is no longer
registered.
- Add registration-ordering preparation patches for netdevsim and octeontx2
AF/PF, so their eswitch state is ready before registration-time defaults
may call eswitch_mode_set().
[1] lore.kernel.org/r/20260502184153.4fd8d06f@kernel.org/
RFC v1: lore.kernel.org/r/20260506123739.1959770-1-mbloch@nvidia.com/
RFC v2: lore.kernel.org/r/20260510185424.2041415-1-mbloch@nvidia.com/
v1: lore.kernel.org/r/20260521072434.362624-1-tariqt@nvidia.com/
v2: lore.kernel.org/all/20260603193259.3412464-1-mbloch@nvidia.com/
v3: lore.kernel.org/all/20260605181030.3486619-1-mbloch@nvidia.com/
Mark Bloch (6):
net/mlx5: Clear FW reset-in-progress bit before reload
devlink: Factor out eswitch mode setting
devlink: Parse eswitch mode boot defaults
devlink: Apply eswitch mode boot defaults
devlink: Add API to apply eswitch mode boot default
net/mlx5: Apply devlink eswitch mode boot default on probe
.../admin-guide/kernel-parameters.txt | 25 ++
.../networking/devlink/devlink-defaults.rst | 78 ++++
Documentation/networking/devlink/index.rst | 1 +
.../ethernet/mellanox/mlx5/core/fw_reset.c | 28 +-
.../net/ethernet/mellanox/mlx5/core/main.c | 13 +
include/net/devlink.h | 1 +
net/devlink/core.c | 393 ++++++++++++++++++
net/devlink/dev.c | 33 +-
net/devlink/devl_internal.h | 8 +
9 files changed, 562 insertions(+), 18 deletions(-)
create mode 100644 Documentation/networking/devlink/devlink-defaults.rst
base-commit: 805185b7c7a1069e407b6f7b3bc98e44d415f484
--
2.43.0
^ permalink raw reply
* [PATCH net-next V4 1/6] net/mlx5: Clear FW reset-in-progress bit before reload
From: Mark Bloch @ 2026-06-29 18:20 UTC (permalink / raw)
To: Jiri Pirko, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc,
Mark Bloch, Shay Drori, Moshe Shemesh
In-Reply-To: <20260629182102.245150-1-mbloch@nvidia.com>
mlx5 sets MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS when acknowledging a sync
reset request. This bit blocks devlink reload and other devlink operations
while the firmware reset is running, but it was kept set until after the
driver reload finished.
Clear the reset-in-progress bit once the reset unload flow is done and PCI
access is back, before reloading the device. For a reset initiated through
devlink, clear it before completing the reload waiter. For a reset reported
through an asynchronous firmware event, keep the unload flow outside
devl_lock, then take devl_lock before clearing the bit and reloading
through the devl-locked load helper.
Reviewed-by: Shay Drori <shayd@nvidia.com>
Reviewed-by: Moshe Shemesh <moshe@nvidia.com>
Signed-off-by: Mark Bloch <mbloch@nvidia.com>
---
.../ethernet/mellanox/mlx5/core/fw_reset.c | 28 +++++++++++--------
1 file changed, 17 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c b/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c
index 07440c58713a..7283e5b49eed 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/fw_reset.c
@@ -238,24 +238,30 @@ static void mlx5_fw_reset_complete_reload(struct mlx5_core_dev *dev)
{
struct mlx5_fw_reset *fw_reset = dev->priv.fw_reset;
struct devlink *devlink = priv_to_devlink(dev);
+ int err;
/* if this is the driver that initiated the fw reset, devlink completed the reload */
if (test_bit(MLX5_FW_RESET_FLAGS_PENDING_COMP, &fw_reset->reset_flags)) {
+ clear_bit(MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS,
+ &fw_reset->reset_flags);
complete(&fw_reset->done);
- } else {
- mlx5_sync_reset_unload_flow(dev, false);
- if (mlx5_health_wait_pci_up(dev))
- mlx5_core_err(dev, "reset reload flow aborted, PCI reads still not working\n");
- else
- mlx5_load_one(dev, true);
- devl_lock(devlink);
- devlink_remote_reload_actions_performed(devlink, 0,
- BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT) |
- BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE));
- devl_unlock(devlink);
+ return;
}
+ mlx5_sync_reset_unload_flow(dev, false);
+ err = mlx5_health_wait_pci_up(dev);
+
+ devl_lock(devlink);
clear_bit(MLX5_FW_RESET_FLAGS_RESET_IN_PROGRESS, &fw_reset->reset_flags);
+ if (err)
+ mlx5_core_err(dev, "reset reload flow aborted, PCI reads still not working\n");
+ else
+ mlx5_load_one_devl_locked(dev, true);
+
+ devlink_remote_reload_actions_performed(devlink, 0,
+ BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT) |
+ BIT(DEVLINK_RELOAD_ACTION_FW_ACTIVATE));
+ devl_unlock(devlink);
}
static void mlx5_stop_sync_reset_poll(struct mlx5_core_dev *dev)
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v2] docs/mm: Fix braces
From: Vishal Moola @ 2026-06-29 18:17 UTC (permalink / raw)
To: Manuel Ebner
Cc: Jason Gunthorpe, Leon Romanovsky, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, Shakeel Butt, Randy Dunlap, linux-mm,
linux-doc, linux-kernel
In-Reply-To: <20260629161156.90213-2-manuelebner@mailbox.org>
On Mon, Jun 29, 2026 at 06:11:56PM +0200, Manuel Ebner wrote:
> Correct typos in mm documentation by balancing parentheses.
>
> Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
> Acked-by: Randy Dunlap <rdunlap@infradead.org>
> Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
Reviewed-by: Vishal Moola <vishal.moola@gmail.com>
^ permalink raw reply
* [PATCH net-next v6 15/15] Documentation: networking: Add timestamp related APIs to OA TC6 framework
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Added new APIs to support hardware timestamp feature as defined in
OPEN Alliance 10BASE-T1x MAC-PHY serial interface specification.
Expanded read/write APIs to take new mms parameter to avoid
bit manipulation before calling regular read/write APIs
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- No change
changes in v5
- Fixed "no blank line" issues in four places
changes in v4
- Added information for new APIs in OA TC6 framework
changes in v3
- Added more information for documentation
changes in v2
- Removed the changes to the existing API
- Updated information about newly added APIs
changes in v1
- Added changes to an existing API
- Added documentation to new hardware timestamp related APIs
---
Documentation/networking/oa-tc6-framework.rst | 80 +++++++++++++++++++++++++++
1 file changed, 80 insertions(+)
diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst
index fe2aabde923a..325d299d1a7d 100644
--- a/Documentation/networking/oa-tc6-framework.rst
+++ b/Documentation/networking/oa-tc6-framework.rst
@@ -153,6 +153,10 @@ OPEN Alliance TC6 Framework
- Forwards the received Ethernet frame from 10Base-T1x MAC-PHY to n/w
subsystem.
+- If supported by the hardware and enabled, updates hardware timestamp
+ in skb, when indicated by one of the three timestamp capture registers
+ through TSC fields of the header.
+
Data Transaction
~~~~~~~~~~~~~~~~
@@ -495,3 +499,79 @@ the MAC-PHY.
Zero align receive frame feature can be enabled to align all receive ethernet
frames data to start at the beginning of any receive data chunk payload with a
start word offset (SWO) of zero.
+
+.. c:function:: int oa_tc6_ptp_register(struct oa_tc6 *tc6, \
+ struct ptp_clock_info *info);
+
+Registers the PTP hardware clock related functions with the kernel.
+This API simply registers. Initialization of the fields in the
+ptp_clock_info structure are left to the vendor as programming hardware
+timer is expected to be vendor dependent. The fields max_adj, owner,
+and all the functions for the clock operations, like adjfine, gettimex64,
+settime64, adjtime are expected to be initialized in the structure before
+calling the registering the hardware clock.
+
+.. c:function:: void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
+
+Unregisters the PTP hardware clock related callbacks.
+
+.. c:function:: int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, \
+ int cmd);
+
+ioctl interface to handle hardware timestamp and PHY related commands.
+
+.. c:function:: int oa_tc6_get_ts_info(struct oa_tc6 *tc6, \
+ struct kernel_ethtool_ts_info *info);
+
+Provides timestamp related settings that are supported to ethtool.
+
+.. c:function:: void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6, \
+ struct kernel_hwtstamp_config *cfg);
+
+Returns hardware timestamp configuration. Part of net_device_ops callbacks.
+
+.. c:function:: void oa_tc6_get_ts_stats(struct oa_tc6 *tc6, \
+ struct ethtool_ts_stats *ts_stats);
+
+Provides hardware timestamp related traffic statistics for ethtool.
+
+.. c:function:: int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6, \
+ struct kernel_hwtstamp_config *cfg);
+
+Helper to set hardware timestamp configuration. Part of net_device_ops
+callbacks.
+
+.. c:function:: void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms);
+
+Helper to map MDIO_MMD_VEND1 command to vendor specific Memory Map Select
+(MMS) value. This function offers flexibility for vendors that may have
+used any MMS value between 10 and 15 as allowed by the specification.
+MDIO_MMD_VEND2 is already mapped to MMS4 in the OA TC6 frame work code.
+
+.. c:function:: int oa_tc6_write_registers_mms(struct oa_tc6 *tc6, \
+ u16 address, u16 mms, \
+ u32 value[], u8 length);
+
+Writing multiple consecutive registers starting from @address for the
+given @mms memory map selector in the MAC-PHY. Maximum of 128 consecutive
+registers can be written starting at @address.
+
+.. c:function:: int oa_tc6_write_register_mms(struct oa_tc6 *tc6, \
+ u16 address, u16 mms, \
+ u32 value);
+
+Write a single register addressed by @address and @mms in the MAC-PHY.
+
+.. c:function:: int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, \
+ u16 address, u16 mms, \
+ u32 value[], u8 length);
+
+Reading multiple consecutive registers starting from @address for the
+given @mms memory map selector value, in the MAC-PHY. Maximum of 128
+consecutive registers can be read starting at @address.
+
+.. c:function:: int oa_tc6_read_register_mms(struct oa_tc6 *tc6, \
+ u16 address, u16 mms, \
+ u32 *value);
+
+Read a single register addressed by @address and @mms in the MAC-PHY.
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 14/15] dt-bindings: net: add onsemi's S2500
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Add YAML device tree binding for the onsemi S2500 IEEE 802.3cg
compliant Ethernet transceiver device.
We use IRQF_TRIGGER_FALLING, though OPEN Alliance 10BASE-T1x
Serial Interface specification calls for IRQF_TRIGGER_LOW.
This is to match IRQF_TRIGGER_FALLING used by OA TC6 framework code.
This bug fix requires changes to the stable branch. At that time,
this will be changed to IRQF_TRIGGER_LOW.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- Moved the signed off information to the right place.
changes in v5
- no changes
changes in v4:
- added spi-max-frequency as suggested by AI review
- changed interrupt to IRQ_TYPE_EDGE_FALLING as it is
being taken care in net (stable) branch
changes in v3
- Removed URL link that failed verification
changes in v2
- removed spi-max-frequency entry
- changed the compatible string to s2500
changes in v1
- Added the first version of YAML file for onsemi MAC-PHY
---
.../devicetree/bindings/net/onnn,s2500.yaml | 67 ++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/onnn,s2500.yaml b/Documentation/devicetree/bindings/net/onnn,s2500.yaml
new file mode 100644
index 000000000000..11edf10508d9
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/onnn,s2500.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/onnn,s2500.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: onsemi S2500 10BASE-T1S MACPHY Ethernet Controllers
+
+maintainers:
+ - Piergiorgio Beruto <pier.beruto@onsemi.com>
+ - Selva Rajagopal <Selvamani.Rajagopal@onsemi.com>
+
+description:
+ The S2500 combines a Media Access Controller (MAC) and an
+ Ethernet PHY to enable 10BASE‑T1S networks. The Ethernet Media Access
+ Controller (MAC) module implements a 10 Mbps half duplex Ethernet MAC,
+ compatible with the IEEE 802.3 standard and a 10BASE-T1S physical layer
+ transceiver integrated into the S2500. The communication between
+ the host and the MAC-PHY is specified in the OPEN Alliance 10BASE-T1x
+ MACPHY Serial Interface (TC6).
+
+allOf:
+ - $ref: /schemas/net/ethernet-controller.yaml#
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ const: onnn,s2500
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ description:
+ Interrupt from MAC-PHY asserted in the event of Receive Chunks
+ Available, Transmit Chunk Credits Available and Extended Status
+ Event.
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 25000000
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - spi-max-frequency
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet@0 {
+ compatible = "onnn,s2500";
+ reg = <0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <ð0_pins>;
+ interrupt-parent = <&gpio>;
+ interrupts = <25 IRQ_TYPE_EDGE_FALLING>;
+ spi-max-frequency = <15000000>;
+ };
+ };
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 13/15] onsemi: s2500: Added selftest support to onsemi's S2500 driver
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Adds selftest support for onsemi S2500 MAC-PHY. Added as a separate
patch for the ease of review.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- Moved the signed off information to the correct place.
changes in v5
- No change
changes in v4:
- Added a selftest as a separate patch
- First patch
---
drivers/net/ethernet/onsemi/s2500/Kconfig | 1 +
drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c | 7 +++++++
2 files changed, 8 insertions(+)
diff --git a/drivers/net/ethernet/onsemi/s2500/Kconfig b/drivers/net/ethernet/onsemi/s2500/Kconfig
index f2e8d5d1429d..7bb4b413d5f9 100644
--- a/drivers/net/ethernet/onsemi/s2500/Kconfig
+++ b/drivers/net/ethernet/onsemi/s2500/Kconfig
@@ -8,6 +8,7 @@ if NET_VENDOR_ONSEMI
config S2500_MACPHY
tristate "S2500 support"
depends on SPI
+ imply NET_SELFTESTS
select NCN26000_PHY
select OA_TC6
help
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
index 7760cec57849..b149cb72ed77 100644
--- a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
@@ -5,6 +5,7 @@
*/
#include <linux/ethtool.h>
+#include <net/selftests.h>
#include <linux/phy.h>
#include "s2500_hw_def.h"
@@ -229,6 +230,8 @@ static int s2500_get_sset_count(struct net_device *ndev, int sset)
switch (sset) {
case ETH_SS_STATS:
return S2500_MAC_STATS_LEN;
+ case ETH_SS_TEST:
+ return net_selftest_get_count();
default:
return -EOPNOTSUPP;
}
@@ -242,6 +245,9 @@ static void s2500_get_strings(struct net_device *ndev, u32 stringset,
memcpy(buf, s2500_mac_stat_strings,
S2500_MAC_STATS_LEN * ETH_GSTRING_LEN);
break;
+ case ETH_SS_TEST:
+ net_selftest_get_strings(buf);
+ break;
}
}
@@ -349,5 +355,6 @@ const struct ethtool_ops s2500_ethtool_ops = {
.get_ts_info = s2500_get_ts_info,
.get_regs_len = s2500_get_regs_len,
.get_regs = s2500_get_regs,
+ .self_test = net_selftest,
};
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 12/15] onsemi: s2500: Add driver support for TS2500 MAC-PHY
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Support for onsemi's S2500, 802.3 cg compliant Ethernet
transceiver with integrated MAC-PHY. Works with
Open Alliance TC6 framework.
adjtime callback is implemented using adjfine. If time
delta is too big, bigger than 1 second, using adjtime
would take long to reduce the delta. In those cases,
settime callback is used to reduce the delta. Once delta
becomes less than a second, it uses adjfine to reduce
the drift further.
Driver has ethtool support for printing some device
registers, traffic stats, rmon stats and hardware
timestamp realated settings and traffic counters.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- Changes to ensure line length stay within 80 columns
changes in v5
- integrated the onsemi's selftest support to onsemi's driver
as a single patch as number of patches exceeded the limit
- Fixed the formatting issue in Kconfig file
- Fixed commenting issue
changes in v4
- MAC/PHY Loopback implemented by onsemi replaced by Linux
standard net_selftest APIs
- onsemi defined MMS values are replaced by OA TC definitions.
- removed only model information as it has practical use
- replaced all read/write APIs that use non-zero MMS to
use the new APIs that takes mms as one of its parameters.
- some bit manipulations are changed to use FIELD_GET macro
changes in v3
- split rmon stats and interface states separately.
- moved get_ts_info to OA TC6 framework as this isn't vendor
specific
- removed few static inline code
- moved ptp register code OA TC6 framework
changes in v2
- Removed the support for onsemi's NCN26010 which is legacy
MAC-PHY to keep only onsemi's S2500 MAC-PHY
- Renamed all the functions to start with S2500
changes in v1
- Added onsemi MAC-PHYs NCN26010 and S2500 support
---
MAINTAINERS | 7 +
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 2 +-
drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c | 3 +-
drivers/net/ethernet/onsemi/Kconfig | 21 +
drivers/net/ethernet/onsemi/Makefile | 7 +
drivers/net/ethernet/onsemi/s2500/Kconfig | 21 +
drivers/net/ethernet/onsemi/s2500/Makefile | 7 +
drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c | 353 ++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h | 225 ++++++++
drivers/net/ethernet/onsemi/s2500/s2500_main.c | 655 ++++++++++++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_ptp.c | 250 +++++++++
11 files changed, 1548 insertions(+), 3 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 85b6dbc09d59..00d1b919842a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20194,6 +20194,13 @@ S: Supported
W: http://www.onsemi.com
F: drivers/net/phy/ncn*
+ONSEMI S2500 10BASE-T1S MACPHY ETHERNET DRIVER
+M: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+L: netdev@vger.kernel.org
+S: Supported
+F: Documentation/devicetree/bindings/net/onnn,s2500.yaml
+F: drivers/net/ethernet/onsemi/s2500/s2500_*
+
OP-TEE DRIVER
M: Jens Wiklander <jens.wiklander@linaro.org>
L: op-tee@lists.trustedfirmware.org (moderated for non-subscribers)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
index e8ec379dd60d..c894c9d22182 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -66,7 +66,7 @@
#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F
/* Tx timestamp capture register A (high) */
-#define OA_TC6_REG_TTSCA_HIGH (0x1010)
+#define OA_TC6_REG_TTSCA_HIGH (0x10)
/* Control command header */
#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
index 272701a4081d..a2b1a1b5690e 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
@@ -137,7 +137,7 @@ EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_set);
/**
* oa_tc6_get_ts_stats - Provides timestamping stats
* @tc6: oa_tc6 struct.
- * @ts_stats: ethtool data structure to fill in
+ * @stats: ethtool data structure to fill in
*/
void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
struct ethtool_ts_stats *stats)
@@ -179,7 +179,6 @@ int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)
* oa_tc6_get_ts_info - Provides timestamp info for ethtool
* @tc6: oa_tc6 struct.
* @info: ethtool timestamping info structure
- * @ts_stats: ethtool data structure to fill in
*/
int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
struct kernel_ethtool_ts_info *info)
diff --git a/drivers/net/ethernet/onsemi/Kconfig b/drivers/net/ethernet/onsemi/Kconfig
new file mode 100644
index 000000000000..8d72194151ea
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/Kconfig
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# onsemi network device configuration
+#
+
+config NET_VENDOR_ONSEMI
+ bool "onsemi network devices"
+ help
+ If you have a network card belonging to this class, say Y.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about onsemi ethernet devices. If you say Y, you
+ will be asked for your specific card in the following questions.
+
+if NET_VENDOR_ONSEMI
+
+source "drivers/net/ethernet/onsemi/s2500/Kconfig"
+
+endif # NET_VENDOR_ONSEMI
+
diff --git a/drivers/net/ethernet/onsemi/Makefile b/drivers/net/ethernet/onsemi/Makefile
new file mode 100644
index 000000000000..f3d4eb154313
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the onsemi network device drivers.
+#
+
+obj-$(CONFIG_S2500_MACPHY) += s2500/
+
diff --git a/drivers/net/ethernet/onsemi/s2500/Kconfig b/drivers/net/ethernet/onsemi/s2500/Kconfig
new file mode 100644
index 000000000000..f2e8d5d1429d
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/Kconfig
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# onsemi S2500 Driver Support
+#
+
+if NET_VENDOR_ONSEMI
+
+config S2500_MACPHY
+ tristate "S2500 support"
+ depends on SPI
+ select NCN26000_PHY
+ select OA_TC6
+ help
+ Support for the onsemi TS2500 MACPHY Ethernet chip.
+ It works under the framework that conform to OPEN Alliance
+ 10BASE-T1x Serial Interface specification.
+
+ To compile this driver as a module, choose M here. The module will be
+ called s2500.
+
+endif # NET_VENDOR_ONSEMI
diff --git a/drivers/net/ethernet/onsemi/s2500/Makefile b/drivers/net/ethernet/onsemi/s2500/Makefile
new file mode 100644
index 000000000000..61ec705cdf9f
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the onsemi network device drivers.
+#
+obj-$(CONFIG_S2500_MACPHY) := s2500.o
+s2500-objs := s2500_main.o s2500_ethtool.o s2500_ptp.o
+
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
new file mode 100644
index 000000000000..7760cec57849
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's S2500 10BASE-T1S MAC-PHY driver
+ */
+
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+#include "s2500_hw_def.h"
+
+#define S2500_NUM_REGS 38
+#define S2500_REGDUMP_LEN (sizeof(u32) * (S2500_NUM_REGS * 2))
+
+#define S2500_NUM_MAC_STATS 21
+#define S2500_NUM_RMON_STATS 12
+
+struct s2500_reg_map {
+ u16 reg;
+ u16 mms;
+};
+
+static const struct s2500_reg_map s2500_reg_map[S2500_NUM_REGS] = {
+ { S2500_REG_VS_ONIPVER, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_TWEAKS1, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_PLCAEXT, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_PMATUNE0, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_PMATUNE1, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_T1SWUPTUNE, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_HDD, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_DCQ_TOID, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_DCQ_SQI, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_DCQ_SQI_PLUS, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_HDD_TUNE1, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_HDD_TUNE2, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_HDD_TUNE3, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_WS_STATUS_0, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_WS_CTRL_0, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_WS_STATUS_1, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_WS_CTRL_1, OA_TC6_PHY_C45_VS_PLCA_MMS4 },
+ { S2500_REG_VS_MIIMIRQE, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_MIIMIRQS, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_DIOCFG0, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CHIPID, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_TWEAKS2, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_MACID0, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_MACID1, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_NVM_HEALTH, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_PTP_SEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_PTPNSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_PTP_SETSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_SETNSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_PTP_ADJ, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CMPCTL, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CMPSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CMPNSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CMPPER, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CAPCTL, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CAPSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_CAPNSEC, OA_TC6_PHY_C45_VS_MMS12 },
+ { S2500_REG_VS_BCNCNT, OA_TC6_PHY_C45_VS_MMS12 },
+};
+
+enum s2500_stat_idx {
+ S2500_MSTOCTECTSTX_IDX = 0,
+ S2500_MSTFRAMESTXOK_IDX,
+ S2500_MSTBCASTTXOK_IDX,
+ S2500_MSTMCASTTXOK_IDX,
+ S2500_MSTFRAMESTX64_IDX,
+ S2500_MSTFRAMESTX65_IDX,
+ S2500_MSTFRAMESTX128_IDX,
+ S2500_MSTFRAMESTX256_IDX,
+ S2500_MSTFRAMESTX512_IDX,
+ S2500_MSTFRAMESTX1024_IDX,
+ S2500_MSTTXUNDEFLOW_IDX,
+ S2500_MSTSINGLECOL_IDX,
+ S2500_MSTMULTICOL_IDX,
+ S2500_MSTEXCESSCOL_IDX,
+ S2500_MSTDEFERREDTX_IDX,
+ S2500_MSTCRSERR_IDX,
+ S2500_MSTOCTECTSRX_IDX,
+ S2500_MSTFRAMESRXOK_IDX,
+ S2500_MSTBCASTRXOK_IDX,
+ S2500_MSTMCASTRXOK_IDX,
+ S2500_MSTFRAMESRX64_IDX,
+ S2500_MSTFRAMESRX65_IDX,
+ S2500_MSTFRAMESRX128_IDX,
+ S2500_MSTFRAMESRX256_IDX,
+ S2500_MSTFRAMESRX512_IDX,
+ S2500_MSTFRAMESRX1024_IDX,
+ S2500_MSTRUNTSERR_IDX,
+ S2500_MSTRXTOOLONG_IDX,
+ S2500_MSTFCSERRS_IDX,
+ S2500_MSTSYMBOLERRS_IDX,
+ S2500_MSTALIGNERRS_IDX,
+ S2500_MSTRXOVERFLOW_IDX,
+ S2500_MSTRXDROPPED_IDX,
+};
+
+static const char s2500_mac_stat_strings[][ETH_GSTRING_LEN] = {
+ "tx_bytes",
+ "tx_frames",
+ "tx_broadcast_frames",
+ "tx_multicast_frames",
+ "tx_underflow_errors",
+ "tx_single_collisions",
+ "tx_multiple_collisions",
+ "tx_excessive_collisions",
+ "tx_deferred_frames",
+ "tx_carrier_sense_errors",
+ "rx_bytes",
+ "rx_frames",
+ "rx_broadcast_frames",
+ "rx_multicast_frames",
+ "rx_runts",
+ "rx_oversize_frames",
+ "rx_crc_errors",
+ "rx_symbol_errors",
+ "rx_alignment_errors",
+ "rx_busy_drops",
+ "rx_mismatch_drops",
+};
+
+static const u8 s2500_mac_stat_map[S2500_NUM_MAC_STATS] = {
+ S2500_MSTOCTECTSTX_IDX,
+ S2500_MSTFRAMESTXOK_IDX,
+ S2500_MSTBCASTTXOK_IDX,
+ S2500_MSTMCASTTXOK_IDX,
+ S2500_MSTTXUNDEFLOW_IDX,
+ S2500_MSTSINGLECOL_IDX,
+ S2500_MSTMULTICOL_IDX,
+ S2500_MSTEXCESSCOL_IDX,
+ S2500_MSTDEFERREDTX_IDX,
+ S2500_MSTCRSERR_IDX,
+ S2500_MSTOCTECTSRX_IDX,
+ S2500_MSTFRAMESRXOK_IDX,
+ S2500_MSTBCASTRXOK_IDX,
+ S2500_MSTMCASTRXOK_IDX,
+ S2500_MSTRUNTSERR_IDX,
+ S2500_MSTRXTOOLONG_IDX,
+ S2500_MSTFCSERRS_IDX,
+ S2500_MSTSYMBOLERRS_IDX,
+ S2500_MSTALIGNERRS_IDX,
+ S2500_MSTRXOVERFLOW_IDX,
+ S2500_MSTRXDROPPED_IDX,
+};
+
+static const u8 s2500_rmon_stat_map[S2500_NUM_RMON_STATS] = {
+ S2500_MSTFRAMESTX64_IDX,
+ S2500_MSTFRAMESTX65_IDX,
+ S2500_MSTFRAMESTX128_IDX,
+ S2500_MSTFRAMESTX256_IDX,
+ S2500_MSTFRAMESTX512_IDX,
+ S2500_MSTFRAMESTX1024_IDX,
+ S2500_MSTFRAMESRX64_IDX,
+ S2500_MSTFRAMESRX65_IDX,
+ S2500_MSTFRAMESRX128_IDX,
+ S2500_MSTFRAMESRX256_IDX,
+ S2500_MSTFRAMESRX512_IDX,
+ S2500_MSTFRAMESRX1024_IDX,
+};
+
+static const struct ethtool_rmon_hist_range s2500_rmon_hist_ranges[] = {
+ { 64, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 2000 },
+ { },
+};
+
+#define S2500_MAC_STATS_LEN ARRAY_SIZE(s2500_mac_stat_strings)
+static_assert(S2500_MAC_STATS_LEN == S2500_NUM_MAC_STATS);
+static_assert(ARRAY_SIZE(s2500_mac_stat_map) == S2500_NUM_MAC_STATS);
+static_assert(ARRAY_SIZE(s2500_rmon_stat_map) == S2500_NUM_RMON_STATS);
+
+#define STAT_REG_OFFSET(x) ((S2500_REG_MAC_ST##x) - \
+ S2500_REG_MAC_FIRST_STAT)
+
+static void s2500_update_stats(struct s2500_info *priv)
+{
+ u64 *data = priv->stats_data;
+ u32 *regs, *rptr;
+ int ret;
+
+ regs = kmalloc_array(S2500_NUM_STAT_REGS, sizeof(u32), GFP_KERNEL);
+ if (!regs)
+ return;
+
+ ret = oa_tc6_read_registers_mms(priv->tc6, S2500_REG_MAC_STOCTECTSTXL,
+ OA_TC6_PHY_C45_MAC_MMS1, regs,
+ S2500_NUM_STAT_REGS);
+ if (ret)
+ goto out;
+
+ rptr = regs;
+
+ /* TX bytes is a 64-bit register that spans over two 32-bit regs
+ * note: HW does auto-freeze when reading LSB and un-freeze on MSB
+ */
+ *(data++) += ((u64)*rptr) | (((u64)*(rptr + 1)) << 32);
+
+ /* run until the next 64-bit register */
+ for (rptr += 2; (rptr - regs) < STAT_REG_OFFSET(OCTECTSRXL); ++rptr)
+ *(data++) += *rptr;
+
+ /* RX bytes is a 64-bit register that spans over two 32-bit regs
+ * note: HW does auto-freeze when reading LSB and un-freeze on MSB
+ */
+ *(data++) += ((u64)*rptr) | (((u64)*(rptr + 1)) << 32);
+
+ for (rptr += 2; (rptr - regs) < S2500_NUM_STAT_REGS; ++rptr)
+ *(data++) += *rptr;
+out:
+ kfree(regs);
+}
+
+static void s2500_get_drvinfo(struct net_device *ndev,
+ struct ethtool_drvinfo *info)
+{
+ strscpy(info->driver, DRV_NAME, sizeof(info->driver));
+ strscpy(info->bus_info, dev_name(&ndev->dev),
+ sizeof(info->bus_info));
+}
+
+static int s2500_get_sset_count(struct net_device *ndev, int sset)
+{
+ switch (sset) {
+ case ETH_SS_STATS:
+ return S2500_MAC_STATS_LEN;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static void s2500_get_strings(struct net_device *ndev, u32 stringset,
+ u8 *buf)
+{
+ switch (stringset) {
+ case ETH_SS_STATS:
+ memcpy(buf, s2500_mac_stat_strings,
+ S2500_MAC_STATS_LEN * ETH_GSTRING_LEN);
+ break;
+ }
+}
+
+static void s2500_get_ethtool_stats(struct net_device *ndev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ int i;
+
+ s2500_update_stats(priv);
+ for (i = 0; i < S2500_NUM_MAC_STATS; i++)
+ data[i] = priv->stats_data[s2500_mac_stat_map[i]];
+}
+
+static void s2500_rmon_stats(struct net_device *ndev,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ u8 offset;
+ int i;
+
+ s2500_update_stats(priv);
+
+ memset(rmon_stats, 0, sizeof(*rmon_stats));
+ rmon_stats->src = ETHTOOL_MAC_STATS_SRC_AGGREGATE;
+ rmon_stats->undersize_pkts = priv->stats_data[S2500_MSTRUNTSERR_IDX];
+ rmon_stats->oversize_pkts = priv->stats_data[S2500_MSTRXTOOLONG_IDX];
+ rmon_stats->fragments = priv->stats_data[S2500_MSTFCSERRS_IDX];
+ rmon_stats->jabbers = priv->stats_data[S2500_MSTALIGNERRS_IDX];
+
+ for (i = 0; i < 6; i++) {
+ offset = s2500_rmon_stat_map[i];
+ rmon_stats->hist_tx[i] = priv->stats_data[offset];
+ }
+
+ for (i = 0; i < 6; i++) {
+ offset = s2500_rmon_stat_map[i + 6];
+ rmon_stats->hist[i] = priv->stats_data[offset];
+ }
+
+ *ranges = s2500_rmon_hist_ranges;
+}
+
+static int s2500_get_ts_info(struct net_device *ndev,
+ struct kernel_ethtool_ts_info *ts_info)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ return oa_tc6_get_ts_info(priv->tc6, ts_info);
+}
+
+static int s2500_get_regs_len(struct net_device *dev)
+{
+ return S2500_REGDUMP_LEN;
+}
+
+static void s2500_get_regs(struct net_device *ndev,
+ struct ethtool_regs *regs, void *p)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ u32 *pbuff = (u32 *)p;
+ u16 reg, mms;
+ int ret = 0;
+ u32 val;
+ int i;
+
+ regs->version = 0;
+ memset(p, 0, S2500_REGDUMP_LEN);
+
+ if (!netif_running(ndev))
+ return;
+
+ for (i = 0; i < S2500_NUM_REGS; i++) {
+ val = 0;
+ reg = s2500_reg_map[i].reg;
+ mms = s2500_reg_map[i].mms;
+ ret = oa_tc6_read_register_mms(priv->tc6, reg, mms,
+ &val);
+ if (ret)
+ continue;
+ *pbuff++ = cpu_to_be32(((u32)mms << 16) | reg);
+ *pbuff++ = cpu_to_be32(val);
+ }
+}
+
+static void s2500_get_ts_stats(struct net_device *ndev,
+ struct ethtool_ts_stats *ts_stats)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ oa_tc6_get_ts_stats(priv->tc6, ts_stats);
+}
+
+const struct ethtool_ops s2500_ethtool_ops = {
+ .get_drvinfo = s2500_get_drvinfo,
+ .get_link = ethtool_op_get_link,
+ .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .set_link_ksettings = phy_ethtool_set_link_ksettings,
+ .get_ts_stats = s2500_get_ts_stats,
+ .get_sset_count = s2500_get_sset_count,
+ .get_strings = s2500_get_strings,
+ .get_ethtool_stats = s2500_get_ethtool_stats,
+ .get_rmon_stats = s2500_rmon_stats,
+ .get_ts_info = s2500_get_ts_info,
+ .get_regs_len = s2500_get_regs_len,
+ .get_regs = s2500_get_regs,
+};
+
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h b/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h
new file mode 100644
index 000000000000..2cd0aa1593b6
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h
@@ -0,0 +1,225 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's S2500 10BASE-T1S MAC-PHY driver
+ */
+
+#ifndef S2500_HW_DEF_H
+#define S2500_HW_DEF_H
+
+#include <linux/hrtimer.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/phylink.h>
+#include <linux/spi/spi.h>
+#include <linux/oa_tc6.h>
+#include <linux/net_tstamp.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/ktime.h>
+#include <linux/errno.h>
+
+#define DRV_NAME "s2500"
+
+#define S2500_N_MCAST_FILTERS 3
+
+/* SPI OID and model register */
+#define S2500_REG_SPI_PHYID 0x1
+
+#define S2500_SPI_PHYID_OUI_SHIFT 10
+#define S2500_SPI_PHYID_OUI_MASK GENMASK(31, S2500_SPI_PHYID_OUI_SHIFT)
+#define S2500_SPI_PHYID_OUI_MASK0 GENMASK(21, 16)
+#define S2500_SPI_PHYID_OUI_MASK1 GENMASK(15, 8)
+#define S2500_SPI_PHYID_OUI_MASK2 GENMASK(7, 0)
+/* SPI configuration register #0 */
+#define S2500_REG_SPI_CFG0 0x4
+
+#define S2500_SPI_CFG0_SYNC_BIT BIT(15)
+#define S2500_SPI_CFG0_TXCTHRESH_SHIFT 10
+#define S2500_SPI_CFG0_RXCTE_BIT BIT(8)
+#define S2500_SPI_CFG0_FTSS_64_BIT BIT(6)
+#define S2500_SPI_CFG0_CPS_SHIFT 0
+
+#define S2500_TXCTHRESH_8 0x2
+
+#define S2500_CPS_64 0x6
+
+/* SPI status register #0 */
+#define S2500_REG_SPI_ST0 0x8
+
+#define S2500_SPI_ST0_CDPE_BIT BIT(12)
+#define S2500_SPI_ST0_TXFCSE_BIT BIT(11)
+#define S2500_SPI_ST0_TTSCAC_BIT BIT(10)
+#define S2500_SPI_ST0_TTSCAB_BIT BIT(9)
+#define S2500_SPI_ST0_TTSCAA_BIT BIT(8)
+#define S2500_SPI_ST0_RESETC_BIT BIT(6)
+#define S2500_SPI_ST0_HDRE_BIT BIT(5)
+#define S2500_SPI_ST0_LOFE_BIT BIT(4)
+#define S2500_SPI_ST0_RXBOE_BIT BIT(3)
+#define S2500_SPI_ST0_TXBUE_BIT BIT(2)
+#define S2500_SPI_ST0_TXBOE_BIT BIT(1)
+#define S2500_SPI_ST0_TXPE_BIT BIT(0)
+
+/* SPI IRQ enable register #0 (use the S2500_SPI_ST0_*_BIT constants) */
+#define S2500_REG_SPI_IRQM0 0xc
+
+/* SPI buffer status register */
+#define S2500_REG_SPI_BUFST 0xb
+
+#define S2500_REG_MAC_CTRL 0x0
+
+#define S2500_MAC_CTRL_MCSF_BIT BIT(18)
+#define S2500_MAC_CTRL_ADRF_BIT BIT(16)
+#define S2500_MAC_CTRL_FCSA_BIT BIT(8)
+#define S2500_MAC_CTRL_TXEN_BIT BIT(1)
+#define S2500_MAC_CTRL_RXEN_BIT BIT(0)
+
+/* MAC address filter registers */
+#define S2500_REG_MAC_ADDRFILTL(n) (16 + 2 * (n))
+#define S2500_REG_MAC_ADDRFILTH(n) (17 + 2 * (n))
+#define S2500_REG_MAC_ADDRMASKL(n) (32 + 2 * (n))
+#define S2500_REG_MAC_ADDRMASKH(n) (33 + 2 * (n))
+
+#define S2500_MAC_ADDRFILT_EN_BIT BIT(31)
+
+/* MAC statistic registers */
+#define S2500_REG_MAC_STOCTECTSTXL 48
+#define S2500_REG_MAC_STOCTECTSTXH 49
+#define S2500_REG_MAC_STFRAMESTXOK 50
+#define S2500_REG_MAC_STBCASTTXOK 51
+#define S2500_REG_MAC_STMCASTTXOK 52
+#define S2500_REG_MAC_STFRAMESTX64 53
+#define S2500_REG_MAC_STFRAMESTX65 54
+#define S2500_REG_MAC_STFRAMESTX128 55
+#define S2500_REG_MAC_STFRAMESTX256 56
+#define S2500_REG_MAC_STFRAMESTX512 57
+#define S2500_REG_MAC_STFRAMESTX1024 58
+#define S2500_REG_MAC_STTXUNDEFLOW 59
+#define S2500_REG_MAC_STSINGLECOL 60
+#define S2500_REG_MAC_STMULTICOL 61
+#define S2500_REG_MAC_STEXCESSCOL 62
+#define S2500_REG_MAC_STDEFERREDTX 63
+#define S2500_REG_MAC_STCRSERR 64
+#define S2500_REG_MAC_STOCTECTSRXL 65
+#define S2500_REG_MAC_STOCTECTSRXH 66
+#define S2500_REG_MAC_STFRAMESRXOK 67
+#define S2500_REG_MAC_STBCASTRXOK 68
+#define S2500_REG_MAC_STMCASTRXOK 69
+#define S2500_REG_MAC_STFRAMESRX64 70
+#define S2500_REG_MAC_STFRAMESRX65 71
+#define S2500_REG_MAC_STFRAMESRX128 72
+#define S2500_REG_MAC_STFRAMESRX256 73
+#define S2500_REG_MAC_STFRAMESRX512 74
+#define S2500_REG_MAC_STFRAMESRX1024 75
+#define S2500_REG_MAC_STRUNTSERR 76
+#define S2500_REG_MAC_STRXTOOLONG 77
+#define S2500_REG_MAC_STFCSERRS 78
+#define S2500_REG_MAC_STSYMBOLERRS 79
+#define S2500_REG_MAC_STALIGNERRS 80
+#define S2500_REG_MAC_STRXOVERFLOW 81
+#define S2500_REG_MAC_STRXDROPPED 82
+
+/* First/last statistic register for sequential access */
+#define S2500_REG_MAC_FIRST_STAT S2500_REG_MAC_STOCTECTSTXL
+#define S2500_REG_MAC_LAST_STAT S2500_REG_MAC_STRXDROPPED
+
+#define S2500_NUM_STAT_REGS \
+ (S2500_REG_MAC_LAST_STAT - S2500_REG_MAC_FIRST_STAT + 1)
+#define S2500_NUM_STAT_VARS (S2500_NUM_STAT_REGS - 2)
+
+/* Vendor specific MMS4 registers */
+#define S2500_REG_VS_ONIPVER 0x8000
+#define S2500_REG_VS_TWEAKS1 0x8001
+#define S2500_REG_VS_PLCAEXT 0x8002
+#define S2500_REG_VS_PMATUNE0 0x8003
+#define S2500_REG_VS_PMATUNE1 0x8004
+#define S2500_REG_VS_T1SWUPTUNE 0x8007
+#define S2500_REG_VS_HDD 0xCC01
+#define S2500_REG_VS_DCQ_TOID 0xCC02
+#define S2500_REG_VS_DCQ_SQI 0xCC03
+#define S2500_REG_VS_DCQ_SQI_PLUS 0xCC04
+#define S2500_REG_VS_HDD_TUNE1 0xCD00
+#define S2500_REG_VS_HDD_TUNE2 0xCD01
+#define S2500_REG_VS_HDD_TUNE3 0xCD02
+#define S2500_REG_VS_WS_STATUS_0 0xD000
+#define S2500_REG_VS_WS_CTRL_0 0xD001
+#define S2500_REG_VS_WS_STATUS_1 0xD100
+#define S2500_REG_VS_WS_CTRL_1 0xD101
+
+/* Vendor specific MMS12 registers */
+#define S2500_REG_VS_MIIMIRQE 0x10
+
+/* MIIM IRQ status register */
+#define S2500_REG_VS_MIIMIRQS 0x11
+#define MIIM_IRQ_STATUS_RSTS_SHIFT 15
+#define MIIM_IRQ_STATUS_RSTS BIT(MIIM_IRQ_STATUS_RSTS_SHIFT)
+
+#define S2500_REG_VS_DIOCFG0 0x12
+
+#define S2500_REG_VS_CHIPID 0x1000
+#define S2500_REG_VS_TWEAKS2 0x1001
+
+/* Permanent MAC address register */
+#define S2500_REG_VS_MACID0 0x1002
+#define S2500_VS_MACID0_BYTE4_MASK GENMASK(15, 8)
+#define S2500_VS_MACID0_BYTE5_MASK GENMASK(7, 0)
+
+#define S2500_REG_VS_MACID1 0x1003
+#define S2500_VS_MACID1_BYTE3_MASK GENMASK(7, 0)
+#define S2500_REG_VS_NVM_HEALTH 0x1005
+
+/* PTP registers */
+#define S2500_REG_VS_PTP_SEC 0x1010
+#define S2500_REG_VS_PTP_SETSEC 0x1012
+#define S2500_REG_VS_PTP_ADJ 0x1014
+#define S2500_REG_VS_PTPNSEC 0x1011
+#define S2500_REG_VS_SETNSEC 0x1013
+#define S2500_REG_VS_CMPCTL 0x1015
+#define S2500_REG_VS_CMPSEC 0x1016
+#define S2500_REG_VS_CMPNSEC 0x1017
+#define S2500_REG_VS_CMPPER 0x1018
+#define S2500_REG_VS_CAPCTL 0x1019
+#define S2500_REG_VS_CAPSEC 0x101A
+#define S2500_REG_VS_CAPNSEC 0x101B
+#define S2500_REG_VS_BCNCNT 0x101C
+
+/* prototypes / forward declarations */
+extern const struct ethtool_ops s2500_ethtool_ops;
+
+struct s2500_info;
+
+struct s2500_info {
+ struct device *dev;
+ struct net_device *ndev;
+
+ /* To have atomic set_rx_mode operation */
+ spinlock_t lock;
+
+ /* To have atomic operation when time is adjusted */
+ struct mutex ptp_adj_lock;
+ struct task_struct *thread;
+
+ /* global state variables */
+ bool event_pending;
+ unsigned int ndev_flags;
+ bool rx_flags_upd;
+
+ signed long poll_jiff;
+
+ struct spi_device *spi;
+
+ /* statistic counters variables */
+ u64 stats_data[S2500_NUM_STAT_VARS];
+
+ /* PTP related variables */
+ struct ptp_clock_info ptp_clock_info;
+ void *tc6;
+};
+
+int s2500_ptp_register(struct s2500_info *priv);
+
+#endif /* S2500_HW_DEF_H */
+
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_main.c b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
new file mode 100644
index 000000000000..687c98b69cc0
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_main.c
@@ -0,0 +1,655 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's S2500 10BASE-T1S MAC-PHY driver
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/if_ether.h>
+#include <linux/irqchip.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/bitrev.h>
+#include <linux/phy.h>
+
+#include "s2500_hw_def.h"
+
+/* S2500 functions & definitions */
+
+#define S2500_STATUS0_MASK (S2500_SPI_ST0_CDPE_BIT | \
+ S2500_SPI_ST0_TXFCSE_BIT | \
+ S2500_SPI_ST0_TTSCAC_BIT | \
+ S2500_SPI_ST0_TTSCAB_BIT | \
+ S2500_SPI_ST0_TTSCAA_BIT | \
+ S2500_SPI_ST0_RESETC_BIT | \
+ S2500_SPI_ST0_HDRE_BIT | \
+ S2500_SPI_ST0_LOFE_BIT | \
+ S2500_SPI_ST0_RXBOE_BIT | \
+ S2500_SPI_ST0_TXBUE_BIT | \
+ S2500_SPI_ST0_TXBOE_BIT | \
+ S2500_SPI_ST0_TXPE_BIT)
+
+/* Initializes the net device MAC address by reading the UID stored
+ * into the device internal non-volatile memory.
+ */
+static int s2500_read_mac_from_nvmem(struct s2500_info *priv)
+{
+ u16 mms = OA_TC6_PHY_C45_VS_MMS12;
+ u8 addr[ETH_ALEN];
+ u32 mac1 = 0;
+ u32 mac0 = 0;
+ u32 val;
+ int ret;
+
+ ret = oa_tc6_read_register_mms(priv->tc6, S2500_REG_VS_MACID1,
+ mms, &mac1);
+ if (ret)
+ return ret;
+
+ ret = oa_tc6_read_register_mms(priv->tc6, S2500_REG_VS_MACID0,
+ mms, &mac0);
+ if (ret)
+ return ret;
+
+ /* Pre-production parts may have 0 */
+ if (mac0 == 0 && mac1 == 0)
+ return -ENXIO;
+
+ ret = oa_tc6_read_register(priv->tc6, S2500_REG_SPI_PHYID,
+ &val);
+ if (ret)
+ return ret;
+
+ val = (val & S2500_SPI_PHYID_OUI_MASK) >>
+ S2500_SPI_PHYID_OUI_SHIFT;
+
+ /* Mapping for bits from PHY ID register to OUI as give below.
+ * - PHY-ID[10:17] provides OUI[16:23]
+ * - PHY-ID[18:25] provides OUI[8:15]
+ * - PHY-ID[26:31] provides OUI[2:7]
+ *
+ * Hardware presents OUI such a way that this requires not just
+ * byte level swap, bit level swap is needed as well.
+ */
+ addr[0] = bitrev8(FIELD_GET(S2500_SPI_PHYID_OUI_MASK0, val));
+ addr[1] = bitrev8(FIELD_GET(S2500_SPI_PHYID_OUI_MASK1, val));
+ addr[2] = bitrev8(FIELD_GET(S2500_SPI_PHYID_OUI_MASK2, val));
+
+ addr[3] = FIELD_GET(S2500_VS_MACID1_BYTE3_MASK, mac1);
+ addr[4] = FIELD_GET(S2500_VS_MACID0_BYTE4_MASK, mac0);
+ addr[5] = FIELD_GET(S2500_VS_MACID0_BYTE5_MASK, mac0);
+
+ __dev_addr_set(priv->ndev, addr, ETH_ALEN);
+ priv->ndev->addr_assign_type = NET_ADDR_PERM;
+ return ret;
+}
+
+/* Writes MAC address to macphy registers */
+static int s2500_set_mac_filter(struct net_device *ndev, const u8 *mac)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ u16 mms = OA_TC6_PHY_C45_MAC_MMS1;
+ u32 val;
+ int ret;
+
+ /* Set unicast address filter */
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ S2500_REG_MAC_ADDRMASKL(0),
+ mms, 0xffffffff);
+ if (ret)
+ return ret;
+
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ S2500_REG_MAC_ADDRMASKH(0),
+ mms, 0xffff);
+ if (ret)
+ return ret;
+
+ val = get_unaligned_be32(&mac[2]);
+
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ S2500_REG_MAC_ADDRFILTL(0),
+ mms, val);
+ if (ret)
+ return ret;
+
+ val = S2500_MAC_ADDRFILT_EN_BIT | get_unaligned_be16(mac);
+
+ return oa_tc6_write_register_mms(priv->tc6,
+ S2500_REG_MAC_ADDRFILTH(0),
+ mms, val);
+}
+
+static int s2500_mac_ctrl_modify_bits(struct s2500_info *priv,
+ u32 in_bits, bool clr)
+{
+ u16 mms = OA_TC6_PHY_C45_MAC_MMS1;
+ u32 reg = S2500_REG_MAC_CTRL;
+ u32 rval = 0;
+ u32 wval = 0;
+ int ret;
+
+ ret = oa_tc6_read_register_mms(priv->tc6, reg, mms, &rval);
+ if (ret)
+ return ret;
+
+ if (clr)
+ wval = rval & ~in_bits;
+ else
+ wval = rval | in_bits;
+ if (rval != wval)
+ ret = oa_tc6_write_register_mms(priv->tc6, reg,
+ mms, wval);
+ return ret;
+}
+
+static int s2500_init(struct s2500_info *priv)
+{
+ u32 val;
+ int ret;
+
+ /* Configure the SPI protocol + 64 bit timestamp */
+ val = S2500_SPI_CFG0_SYNC_BIT | S2500_SPI_CFG0_RXCTE_BIT |
+ (S2500_TXCTHRESH_8 << S2500_SPI_CFG0_TXCTHRESH_SHIFT) |
+ (S2500_CPS_64 << S2500_SPI_CFG0_CPS_SHIFT) |
+ S2500_SPI_CFG0_FTSS_64_BIT;
+
+ ret = oa_tc6_write_register(priv->tc6, S2500_REG_SPI_CFG0, val);
+ if (ret)
+ return ret;
+
+ val = (u32)~(S2500_SPI_ST0_RESETC_BIT |
+ S2500_SPI_ST0_HDRE_BIT | S2500_SPI_ST0_LOFE_BIT |
+ S2500_SPI_ST0_RXBOE_BIT | S2500_SPI_ST0_TXBOE_BIT |
+ S2500_SPI_ST0_TXPE_BIT);
+
+ ret = oa_tc6_write_register(priv->tc6, S2500_REG_SPI_IRQM0, val);
+ if (ret)
+ return ret;
+
+ /* Read the initial value of TX credits */
+ ret = oa_tc6_read_register(priv->tc6, S2500_REG_SPI_BUFST, &val);
+ if (ret)
+ return ret;
+
+ /* Program the source MAC address into the device */
+ ret = s2500_set_mac_filter(priv->ndev, priv->ndev->dev_addr);
+
+ val = S2500_MAC_CTRL_ADRF_BIT | S2500_MAC_CTRL_FCSA_BIT;
+
+ return s2500_mac_ctrl_modify_bits(priv, val, false);
+}
+
+static void s2500_shutdown(struct s2500_info *priv)
+{
+ u32 val = S2500_MAC_CTRL_TXEN_BIT | S2500_MAC_CTRL_RXEN_BIT;
+ struct net_device *ndev = priv->ndev;
+
+ netif_stop_queue(ndev);
+ phy_stop(ndev->phydev);
+
+ s2500_mac_ctrl_modify_bits(priv, val, true);
+}
+
+static int s2500_set_promiscuous_mode(struct s2500_info *priv,
+ unsigned int rx_flags)
+{
+ u32 val = S2500_MAC_CTRL_ADRF_BIT;
+ bool clr = false;
+
+ if (rx_flags & IFF_PROMISC)
+ clr = true;
+ return s2500_mac_ctrl_modify_bits(priv, val, clr);
+}
+
+static int s2500_set_multicast_mode(struct s2500_info *priv,
+ unsigned int rx_flags)
+{
+ u16 mms = OA_TC6_PHY_C45_MAC_MMS1;
+ int i, ret = 0;
+ u16 addr;
+ u32 val;
+
+ if ((rx_flags & IFF_ALLMULTI) ||
+ (netdev_mc_count(priv->ndev) > S2500_N_MCAST_FILTERS)) {
+ /* Disable multicast filter */
+ ret = s2500_mac_ctrl_modify_bits(priv,
+ S2500_MAC_CTRL_MCSF_BIT,
+ true);
+ if (ret)
+ return ret;
+
+ /* Accept all multicasts */
+ addr = S2500_REG_MAC_ADDRMASKL(1);
+ ret = oa_tc6_write_register_mms(priv->tc6, addr,
+ mms, 0);
+ if (ret)
+ return ret;
+
+ addr = S2500_REG_MAC_ADDRMASKH(1);
+ ret = oa_tc6_write_register_mms(priv->tc6, addr,
+ mms, 0x100);
+ if (ret)
+ return ret;
+
+ addr = S2500_REG_MAC_ADDRFILTL(1);
+ ret = oa_tc6_write_register_mms(priv->tc6, addr,
+ mms, 0);
+ if (ret)
+ return ret;
+
+ val = S2500_MAC_ADDRFILT_EN_BIT | 0x00000100;
+ addr = S2500_REG_MAC_ADDRFILTH(1);
+ ret = oa_tc6_write_register_mms(priv->tc6, addr,
+ mms, val);
+ } else if (netdev_mc_count(priv->ndev) == 0) {
+ /* Enable multicast filter */
+ ret = s2500_mac_ctrl_modify_bits(priv,
+ S2500_MAC_CTRL_MCSF_BIT,
+ false);
+ if (ret)
+ return ret;
+
+ /* Disable filters */
+ for (i = 1; i <= S2500_N_MCAST_FILTERS; i++) {
+ addr = S2500_REG_MAC_ADDRFILTH(i);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ addr, mms, 0);
+ if (ret)
+ return ret;
+ }
+ } else {
+ struct netdev_hw_addr *ha;
+ u32 addrh, addrl;
+
+ /* Disable multicast filter */
+ ret = s2500_mac_ctrl_modify_bits(priv,
+ S2500_MAC_CTRL_MCSF_BIT,
+ true);
+ if (ret)
+ return ret;
+
+ /* Disable filters */
+ for (i = 1; i <= S2500_N_MCAST_FILTERS; i++) {
+ addr = S2500_REG_MAC_ADDRFILTH(i);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ addr, mms, 0);
+ if (ret)
+ return ret;
+ }
+
+ i = 1;
+ netdev_for_each_mc_addr(ha, priv->ndev) {
+ if (i > S2500_N_MCAST_FILTERS)
+ break;
+
+ addrh = S2500_MAC_ADDRFILT_EN_BIT |
+ get_unaligned_be16(ha->addr);
+ addrl = get_unaligned_be32(&ha->addr[2]);
+
+ addr = S2500_REG_MAC_ADDRFILTH(i);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ addr, mms,
+ addrh);
+ if (ret)
+ return ret;
+
+ addr = S2500_REG_MAC_ADDRFILTL(i);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ addr, mms,
+ addrl);
+ if (ret)
+ return ret;
+
+ addr = S2500_REG_MAC_ADDRMASKL(i);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ addr, mms,
+ 0xffffffff);
+ if (ret)
+ return ret;
+
+ addr = S2500_REG_MAC_ADDRMASKH(i);
+ ret = oa_tc6_write_register_mms(priv->tc6,
+ addr, mms,
+ 0xffff);
+ if (ret)
+ return ret;
+ i++;
+ }
+ }
+ return ret;
+}
+
+/* Deferred function for applying RX mode flags in non-atomic context */
+static int s2500_rx_mode_update(struct s2500_info *priv)
+{
+ unsigned int rx_flags;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ rx_flags = priv->ndev_flags;
+ priv->rx_flags_upd = false;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ ret = s2500_set_promiscuous_mode(priv, rx_flags);
+ if (ret)
+ goto out;
+
+ ret = s2500_set_multicast_mode(priv, rx_flags);
+out:
+ return ret;
+}
+
+static void s2500_set_rx_mode(struct net_device *ndev)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ priv->rx_flags_upd = true;
+ priv->ndev_flags = ndev->flags;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (priv->thread)
+ wake_up_process(priv->thread);
+}
+
+static int s2500_set_mac_address(struct net_device *ndev, void *p)
+{
+ struct sockaddr *addr = p;
+
+ if (!is_valid_ether_addr(addr->sa_data))
+ return -EADDRNOTAVAIL;
+
+ eth_hw_addr_set(ndev, addr->sa_data);
+ return s2500_set_mac_filter(ndev, addr->sa_data);
+}
+
+static netdev_tx_t s2500_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ return oa_tc6_start_xmit(priv->tc6, skb);
+}
+
+static int s2500_ioctl(struct net_device *ndev, struct ifreq *rq,
+ int cmd)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ return oa_tc6_ioctl(priv->tc6, rq, cmd);
+}
+
+static void s2500_process_events(struct s2500_info *priv)
+{
+ u32 val;
+ int ret;
+
+ if (!priv->event_pending)
+ return;
+
+ priv->event_pending = false;
+
+ ret = oa_tc6_read_register(priv->tc6, S2500_REG_SPI_ST0, &val);
+ if (ret) {
+ dev_err(&priv->spi->dev, "Error reading ST0 register");
+ return;
+ }
+}
+
+static int s2500_thread_fun(void *data)
+{
+ struct s2500_info *priv = data;
+ bool update_rx_mode = false;
+ unsigned long flags;
+ signed long tout;
+ int ret = 0;
+
+ tout = priv->poll_jiff;
+
+ do {
+ if (update_rx_mode) {
+ ret = s2500_rx_mode_update(priv);
+ if (unlikely(ret)) {
+ dev_err(&priv->spi->dev,
+ "Failed to set new RX mode");
+ break;
+ }
+ }
+
+ if (tout == 0) {
+ tout = priv->poll_jiff;
+
+ /* Force checking the status register */
+ priv->event_pending = true;
+ }
+
+ s2500_process_events(priv);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ __set_current_state(TASK_INTERRUPTIBLE);
+
+ update_rx_mode = priv->rx_flags_upd;
+ ret = update_rx_mode;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (!ret)
+ tout = schedule_timeout(tout);
+ else
+ set_current_state(TASK_RUNNING);
+ } while (!kthread_should_stop());
+ return 0;
+}
+
+static int s2500_open(struct net_device *ndev)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+ int ret = 0;
+ u32 val;
+
+ dev_dbg(&ndev->dev, "%s", "s2500_open");
+ phy_start(priv->ndev->phydev);
+
+ priv->thread = kthread_run(s2500_thread_fun, priv,
+ DRV_NAME "/%s:%d",
+ dev_name(&priv->spi->dev),
+ spi_get_chipselect(priv->spi, 0));
+
+ if (IS_ERR(priv->thread)) {
+ ret = PTR_ERR(priv->thread);
+ } else {
+ val = S2500_MAC_CTRL_TXEN_BIT | S2500_MAC_CTRL_RXEN_BIT;
+ ret = s2500_mac_ctrl_modify_bits(priv, val, false);
+
+ netif_start_queue(priv->ndev);
+ }
+ return ret;
+}
+
+static int s2500_stop(struct net_device *ndev)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ dev_dbg(&ndev->dev, "%s", "s2500_stop");
+
+ s2500_shutdown(priv);
+
+ kthread_stop(priv->thread);
+ priv->thread = NULL;
+
+ return 0;
+}
+
+static int s2500_hwtstamp_get(struct net_device *ndev,
+ struct kernel_hwtstamp_config *k_cfg)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ oa_tc6_hwtstamp_get(priv->tc6, k_cfg);
+ return 0;
+}
+
+static int s2500_hwtstamp_set(struct net_device *ndev,
+ struct kernel_hwtstamp_config *cfg,
+ struct netlink_ext_ack *extack)
+{
+ struct s2500_info *priv = netdev_priv(ndev);
+
+ return oa_tc6_hwtstamp_set(priv->tc6, cfg);
+}
+
+static const struct net_device_ops s2500_netdev_ops = {
+ .ndo_open = s2500_open,
+ .ndo_stop = s2500_stop,
+ .ndo_start_xmit = s2500_start_xmit,
+ .ndo_set_mac_address = s2500_set_mac_address,
+ .ndo_set_rx_mode = s2500_set_rx_mode,
+ .ndo_eth_ioctl = s2500_ioctl,
+ .ndo_hwtstamp_get = s2500_hwtstamp_get,
+ .ndo_hwtstamp_set = s2500_hwtstamp_set,
+};
+
+static int s2500_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct net_device *ndev;
+ struct s2500_info *priv;
+ u32 val;
+ int ret;
+
+ if (spi->irq < 0)
+ return -ENODEV;
+
+ ndev = devm_alloc_etherdev(dev, sizeof(struct s2500_info));
+ if (!ndev)
+ return -ENOMEM;
+
+ priv = netdev_priv(ndev);
+ priv->ndev = ndev;
+ priv->spi = spi;
+ priv->dev = dev;
+
+ SET_NETDEV_DEV(ndev, dev);
+
+ spin_lock_init(&priv->lock);
+ mutex_init(&priv->ptp_adj_lock);
+ ndev->irq = spi->irq;
+
+ spi->dev.platform_data = priv;
+ spi_set_drvdata(spi, priv);
+
+ ndev->netdev_ops = &s2500_netdev_ops;
+ ndev->ethtool_ops = &s2500_ethtool_ops;
+ ndev->if_port = IF_PORT_10BASET;
+ ndev->priv_flags |= IFF_UNICAST_FLT;
+ ndev->hw_features = NETIF_F_RXALL;
+
+ priv->poll_jiff = HZ * 5; /* Poll interval */
+
+ priv->tc6 = oa_tc6_init(spi, ndev);
+ if (!priv->tc6) {
+ dev_err(&spi->dev, "OA TC6 init failed");
+ return -ENODEV;
+ }
+ oa_tc6_set_vend1_mms(priv->tc6, OA_TC6_PHY_C45_VS_MMS12);
+
+ /* Clear RSTS, if set */
+ oa_tc6_read_register_mms(priv->tc6, S2500_REG_VS_MIIMIRQS,
+ OA_TC6_PHY_C45_VS_MMS12, &val);
+ val &= MIIM_IRQ_STATUS_RSTS;
+ if (val != 0)
+ oa_tc6_write_register_mms(priv->tc6,
+ S2500_REG_VS_MIIMIRQS,
+ OA_TC6_PHY_C45_VS_MMS12,
+ MIIM_IRQ_STATUS_RSTS);
+
+ /* Acknowledge all IRQ status bits */
+ ret = oa_tc6_read_register(priv->tc6, S2500_REG_SPI_ST0, &val);
+ if (!ret) {
+ u32 mask = S2500_STATUS0_MASK;
+
+ val &= mask;
+ oa_tc6_write_register(priv->tc6, S2500_REG_SPI_ST0, val);
+ }
+
+ ret = device_get_ethdev_address(priv->dev, ndev);
+ if (ret)
+ ret = s2500_read_mac_from_nvmem(priv);
+
+ if (ret) {
+ eth_hw_addr_random(ndev);
+ dev_warn(&spi->dev, "Using random MAC address %pM",
+ ndev->dev_addr);
+ }
+
+ ret = s2500_init(priv);
+ if (unlikely(ret)) {
+ dev_err(&spi->dev, "failed to s2500_init the device");
+ goto err_reg_read;
+ }
+
+ /* Configure PTP if the model supports it */
+ ret = s2500_ptp_register(priv);
+ if (unlikely(ret))
+ goto err_reg_read;
+
+ ret = register_netdev(ndev);
+ if (ret) {
+ dev_err(&spi->dev, "failed to register netdev\n");
+ ret = -ENODEV;
+
+ goto err_reg_read;
+ }
+ return 0;
+
+err_reg_read:
+ dev_err(&spi->dev, "could not initialize macphy");
+ return ret;
+}
+
+static void s2500_remove(struct spi_device *spi)
+{
+ struct s2500_info *priv = spi->dev.platform_data;
+
+ dev_dbg(&priv->ndev->dev, "%s", "s2500_remove");
+
+ oa_tc6_ptp_unregister(priv->tc6);
+ unregister_netdev(priv->ndev);
+ oa_tc6_exit(priv->tc6);
+}
+
+static const struct of_device_id s2500_of_match[] = {
+ { .compatible = "onnn,s2500" },
+ {}
+};
+
+static const struct spi_device_id s2500_ids[] = {
+ { "s2500" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(spi, s2500_ids);
+
+static struct spi_driver s2500_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = s2500_of_match,
+ },
+ .probe = s2500_probe,
+ .remove = s2500_remove,
+ .id_table = s2500_ids,
+};
+
+module_spi_driver(s2500_driver);
+
+MODULE_AUTHOR("Piergiorgio Beruto <pier.beruto@onsemi.com>");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
+MODULE_DESCRIPTION("onsemi MACPHY ethernet driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
new file mode 100644
index 000000000000..7da660cb5b19
--- /dev/null
+++ b/drivers/net/ethernet/onsemi/s2500/s2500_ptp.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2026 Semiconductor Components Industries, LLC ("onsemi").
+ * onsemi's S2500 10BASE-T1S MAC-PHY driver
+ */
+
+#include "s2500_hw_def.h"
+
+static int s2500_ptp_get_time64(struct ptp_clock_info *ptp,
+ struct timespec64 *ts,
+ struct ptp_system_timestamp *ptp_sts)
+{
+ struct s2500_info *priv = container_of(ptp, struct s2500_info,
+ ptp_clock_info);
+ u32 data[2];
+ int ret;
+
+ ptp_read_system_prets(ptp_sts);
+ ret = oa_tc6_read_registers_mms(priv->tc6,
+ S2500_REG_VS_PTP_SEC,
+ OA_TC6_PHY_C45_VS_MMS12,
+ &data[0], 2);
+ ptp_read_system_postts(ptp_sts);
+
+ if (!ret) {
+ ts->tv_sec = data[0];
+ ts->tv_nsec = data[1];
+ }
+
+ return ret;
+}
+
+static int s2500_ptp_set_time64(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct s2500_info *priv = container_of(ptp, struct s2500_info,
+ ptp_clock_info);
+ u32 data[2];
+
+ if (ts->tv_sec >= (1ULL << 32))
+ return -ERANGE;
+
+ data[0] = (u32)ts->tv_sec;
+ /* bit 31 = execute set command */
+ data[1] = ts->tv_nsec | BIT(31);
+
+ return oa_tc6_write_registers_mms(priv->tc6,
+ S2500_REG_VS_PTP_SETSEC,
+ OA_TC6_PHY_C45_VS_MMS12,
+ &data[0], 2);
+}
+
+static int s2500_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct s2500_info *priv = container_of(ptp, struct s2500_info,
+ ptp_clock_info);
+ u32 sign_bit = 0;
+ long adj;
+ u32 val;
+ u64 ppm;
+
+ if (scaled_ppm < 0) {
+ /* split sign / mod */
+ sign_bit = 1U << 31;
+ scaled_ppm = ~scaled_ppm + 1;
+ }
+
+ /*
+ * Convert unsigned scaled_ppm to atto-seconds per clock cycles.
+ * The scaled_ppm format is Qx.16 --> 1 lsb = 1/65536 ppm.
+ * The clock period of the S2500 is 8ns (125 MHz), so 1 lsb of
+ * adj register LSB is 1 atto-sec / 8ns = 0.000125 ppm.
+ * Represented in Qx.16 format, this is 0.000125 * 2^16 = 8(.192)
+ * To convert scaled_ppm into a register value we need to divide
+ * it by the LSB value, hence adj = (scaled_ppm * 1000) / 8192 to
+ * minimize the precision loss due to the integer arithmetic.
+ * That further reduces to (scaled_ppm * 125) / 1024.
+ */
+ ppm = (u64)scaled_ppm * 125;
+ do_div(ppm, 1024);
+ adj = (long)ppm;
+
+ /* check overflow */
+ if (adj >= (1L << 28))
+ return -ERANGE;
+
+ val = (u32)adj | sign_bit;
+ return oa_tc6_write_register_mms(priv->tc6, S2500_REG_VS_PTP_ADJ,
+ OA_TC6_PHY_C45_VS_MMS12, val);
+}
+
+static int s2500_use_settime(struct ptp_clock_info *ptp,
+ struct timespec64 now, s64 rem)
+{
+ struct ptp_system_timestamp sts;
+ struct timespec64 delta_ts;
+ struct timespec64 target;
+ int ret;
+
+ memset(&sts, 0, sizeof(sts));
+ ret = ptp->gettimex64(ptp, &now, &sts);
+ if (ret)
+ return ret;
+ if (rem >= 0) {
+ delta_ts = ns_to_timespec64(rem);
+ target = timespec64_add(now, delta_ts);
+ } else {
+ delta_ts = ns_to_timespec64(-rem);
+ target = timespec64_sub(now, delta_ts);
+ }
+ if (target.tv_sec < 0 ||
+ target.tv_sec >= (1ULL << 32))
+ return -ERANGE;
+ return ptp->settime64(ptp, &target);
+}
+
+static int s2500_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct s2500_info *priv = container_of(ptp, struct s2500_info,
+ ptp_clock_info);
+ struct ptp_system_timestamp sts;
+ struct timespec64 target;
+ unsigned int period_ms;
+ struct timespec64 now;
+ s64 scaled_ppm, rem;
+ int max_iters = 3;
+ s64 target_ns;
+ int ret = 0;
+ s64 now_ns;
+ s64 num;
+ s64 den;
+
+ if (!ptp)
+ return -EINVAL;
+
+ /* Nothing to do */
+ if (delta == 0)
+ return 0;
+
+ if (mutex_lock_interruptible(&priv->ptp_adj_lock))
+ return -EINTR;
+
+ /* Try to slew the clock using adjfine for better accuracy.
+ * For large adjustments fall back to setting time directly.
+ */
+ rem = delta;
+
+ while (rem != 0 && max_iters--) {
+ s64 abs_delta = rem > 0 ? rem : -rem;
+
+ /* If the adjustment is very large, more than 1 second,
+ * use settime to avoid very long slewing periods or
+ * excessive frequency offsets.
+ */
+ if (abs_delta > 1000000000LL) {
+ ret = s2500_use_settime(ptp, now, rem);
+ rem = 0;
+ break;
+ }
+
+ /* Choose a slewing period depending on magnitude */
+ if (abs_delta <= 1000000LL) /* <= 1ms */
+ period_ms = 1000; /* 1 s */
+ else if (abs_delta <= 100000000LL) /* <= 100ms */
+ period_ms = 10000; /* 10 s */
+ else
+ period_ms = 60000; /* 60 s */
+
+ /* Compute current time and fixed target for this
+ * iteration
+ */
+ memset(&sts, 0, sizeof(sts));
+ ret = ptp->gettimex64(ptp, &now, &sts);
+ if (ret)
+ break;
+
+ if (rem >= 0)
+ target = timespec64_add(now,
+ ns_to_timespec64(rem));
+ else
+ target = timespec64_sub(now,
+ ns_to_timespec64(-rem));
+
+ /* Compute scaled_ppm (Qx.16). scaled_ppm = ppm * 2^16
+ * ppm = (delta_seconds / period_seconds) * 1e6
+ * => scaled_ppm = delta_ns * 65536 / (period_ms * 1000)
+ */
+ num = rem * 65536LL;
+ den = (s64)period_ms * 1000LL;
+
+ /* Integer division rounds toward zero; keep sign in
+ * numerator
+ */
+ scaled_ppm = div_s64(num, den);
+
+ /* Apply frequency adjustment */
+ ret = ptp->adjfine(ptp, (long)scaled_ppm);
+ if (ret)
+ break;
+
+ /* Sleep for the slew period (interruptible). If
+ * interrupted, clear the adjfine and return -EINTR.
+ */
+ if (msleep_interruptible(period_ms)) {
+ /* Clear adjfine */
+ ptp->adjfine(ptp, 0);
+ ret = -EINTR;
+ break;
+ }
+
+ /* Clear adjfine and measure remaining offset */
+ ptp->adjfine(ptp, 0);
+
+ memset(&sts, 0, sizeof(sts));
+ ret = ptp->gettimex64(ptp, &now, &sts);
+ if (ret)
+ break;
+
+ /* remaining = target - now (in ns) */
+ target_ns = timespec64_to_ns(&target);
+ now_ns = timespec64_to_ns(&now);
+ rem = target_ns - now_ns;
+
+ /* If remaining is small (< 1us), finish */
+ if (rem > -1000 && rem < 1000)
+ rem = 0;
+ }
+
+ mutex_unlock(&priv->ptp_adj_lock);
+ return ret;
+}
+
+int s2500_ptp_register(struct s2500_info *priv)
+{
+ struct ptp_clock_info *info = &priv->ptp_clock_info;
+ int ret;
+
+ info->max_adj = 100000000;
+ info->owner = THIS_MODULE;
+ info->adjfine = s2500_ptp_adjfine;
+ info->gettimex64 = s2500_ptp_get_time64;
+ info->settime64 = s2500_ptp_set_time64;
+ info->adjtime = s2500_ptp_adjtime;
+
+ ret = oa_tc6_ptp_register(priv->tc6, info);
+ if (ret)
+ dev_err(&priv->spi->dev, "PTP registration failed");
+ return ret;
+}
+
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 11/15] net: phy: ncn26000: Support for loopback
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Adding loopback support for S2500 internal PHY to help running
loopback test through ethtool.
This instance requires only manipulating the loopback bit in
the PHY. nothing else. The existing genphy interface
implementation for loopback, does more than flipping the
loopback bit. This is the reason to add a new API.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- No change
changes in v5
- No change
changes in v4
- Moved the onsemi implementation to a new, genphy API to
make it vendor agnostic.
changes in v3
- Addd loopback functionality for onsemi's PHY driver
- First patch
---
drivers/net/phy/ncn26000.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index c3a34b2c524d..afafa81dc22b 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -178,6 +178,7 @@ static struct phy_driver ncn26000_driver[] = {
.config_aneg = ncn26000_config_aneg,
.read_status = ncn26000_read_status,
.handle_interrupt = ncn26000_handle_interrupt,
+ .set_loopback = genphy_loopback_fixed_speed,
.set_plca_cfg = ncn26000_c45_plca_set_cfg,
.get_plca_cfg = genphy_c45_plca_get_cfg,
.get_plca_status = genphy_c45_plca_get_status,
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 10/15] net: phy: ncn26000: Enable enhanced noise immunity
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
By setting ENI bit, noise immunity is improved and it is
specifically meant for PLCA enabled nodes.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- No change
changes in v5
- No changes
changes in v4
- No changes
changes in v3
- Moved as a separate patch
---
drivers/net/phy/ncn26000.c | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index 2c8601c3f94a..c3a34b2c524d 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -36,6 +36,10 @@
#define TO_TMR_DEFAULT 32
+#define NCN26000_REG_PHYCFG1 0x8001
+#define NCN26000_PHYCFG1_ENI BIT(7)
+#define NCN26000_PHYCFG1_ENI_MASK BIT(7)
+
static int ncn26000_config_init(struct phy_device *phydev)
{
int ret = 0;
@@ -106,6 +110,24 @@ static int ncn26000_read_status(struct phy_device *phydev)
return 0;
}
+/* Intercept PLCA enable/disable request to
+ * set the proprietary, ENI mode accordingly
+ */
+static int ncn26000_c45_plca_set_cfg(struct phy_device *phydev,
+ const struct phy_plca_cfg *plca_cfg)
+{
+ int ret = genphy_c45_plca_set_cfg(phydev, plca_cfg);
+ u16 eni_cfg = 0;
+
+ if (ret || plca_cfg->enabled < 0)
+ return ret;
+
+ eni_cfg = (plca_cfg->enabled) ? NCN26000_PHYCFG1_ENI : 0;
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
+ NCN26000_REG_PHYCFG1,
+ NCN26000_PHYCFG1_ENI_MASK, eni_cfg);
+}
+
static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
{
int ret;
@@ -156,7 +178,7 @@ static struct phy_driver ncn26000_driver[] = {
.config_aneg = ncn26000_config_aneg,
.read_status = ncn26000_read_status,
.handle_interrupt = ncn26000_handle_interrupt,
- .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .set_plca_cfg = ncn26000_c45_plca_set_cfg,
.get_plca_cfg = genphy_c45_plca_get_cfg,
.get_plca_status = genphy_c45_plca_get_status,
.soft_reset = genphy_soft_reset,
@@ -177,7 +199,7 @@ static struct phy_driver ncn26000_driver[] = {
.read_status = ncn26000_read_status,
.handle_interrupt = ncn26000_handle_interrupt,
.get_plca_cfg = genphy_c45_plca_get_cfg,
- .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .set_plca_cfg = ncn26000_c45_plca_set_cfg,
.get_plca_status = genphy_c45_plca_get_status,
.soft_reset = genphy_soft_reset,
},
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 09/15] net: phy: ncn26000: Support for onsemi's S2500 internal phy
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Adding support for internal PHY of the integrated
media access controller S2500. PLCA tx opportunity timer's
default value is correct in this device, compared to
NCN26000.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- No change
changes in v5
- No change
changes in v4
- no change
changes in v3
added new PHY support separate patch
changed model comparison to use phy_id_compare_model
changes in v2
Removed bug fixes. Retained only S2500 specific changes
changes in v1
Added support for an internal PHY of onsemi's MAC-PHY S2500
---
MAINTAINERS | 3 ++-
drivers/net/phy/ncn26000.c | 38 +++++++++++++++++++++++++++++++++-----
2 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index ca9f39b46b96..85b6dbc09d59 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20187,7 +20187,8 @@ S: Maintained
F: arch/mips/boot/dts/ralink/omega2p.dts
ONSEMI ETHERNET PHY DRIVERS
-M: Piergiorgio Beruto <piergiorgio.beruto@gmail.com>
+M: Piergiorgio Beruto <pier.beruto@onsemi.com>
+M: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
L: netdev@vger.kernel.org
S: Supported
W: http://www.onsemi.com
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index cabdd83c614f..2c8601c3f94a 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -2,7 +2,7 @@
/*
* Driver for the onsemi 10BASE-T1S NCN26000 PHYs family.
*
- * Copyright 2022 onsemi
+ * Copyright 2026 onsemi
*/
#include <linux/kernel.h>
#include <linux/bitfield.h>
@@ -14,6 +14,7 @@
#include "mdio-open-alliance.h"
+#define PHY_ID_S2500 0x180FF411
#define PHY_ID_NCN26000 0x180FF5A1
#define NCN26000_REG_IRQ_CTL 16
@@ -37,13 +38,18 @@
static int ncn26000_config_init(struct phy_device *phydev)
{
+ int ret = 0;
+
/* HW bug workaround: the default value of the PLCA TO_TIMER should be
* 32, where the current version of NCN26000 reports 24. This will be
* fixed in future PHY versions. For the time being, we force the
* correct default here.
*/
- return phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR,
- TO_TMR_DEFAULT);
+ if (phy_id_compare_model(phydev->drv->phy_id, PHY_ID_NCN26000))
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ MDIO_OATC14_PLCA_TOTMR,
+ TO_TMR_DEFAULT);
+ return ret;
}
static int ncn26000_config_aneg(struct phy_device *phydev)
@@ -117,8 +123,8 @@ static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
static int ncn26000_config_intr(struct phy_device *phydev)
{
- int ret;
u16 irqe;
+ int ret;
if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
// acknowledge IRQs
@@ -141,6 +147,26 @@ static int ncn26000_config_intr(struct phy_device *phydev)
}
static struct phy_driver ncn26000_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_S2500),
+ .name = "S2500",
+ .features = PHY_BASIC_T1S_P2MP_FEATURES,
+ .config_init = ncn26000_config_init,
+ .config_intr = ncn26000_config_intr,
+ .config_aneg = ncn26000_config_aneg,
+ .read_status = ncn26000_read_status,
+ .handle_interrupt = ncn26000_handle_interrupt,
+ .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .get_plca_cfg = genphy_c45_plca_get_cfg,
+ .get_plca_status = genphy_c45_plca_get_status,
+ .soft_reset = genphy_soft_reset,
+ .get_sqi = genphy_c45_oatc14_get_sqi,
+ .get_sqi_max = genphy_c45_oatc14_get_sqi_max,
+ .read_mmd = genphy_phy_read_mmd,
+ .write_mmd = genphy_phy_write_mmd,
+ .cable_test_get_status = genphy_c45_oatc14_cable_test_get_status,
+ .cable_test_start = genphy_c45_oatc14_cable_test_start,
+ },
{
PHY_ID_MATCH_MODEL(PHY_ID_NCN26000),
.name = "NCN26000",
@@ -161,11 +187,13 @@ module_phy_driver(ncn26000_driver);
static const struct mdio_device_id __maybe_unused ncn26000_tbl[] = {
{ PHY_ID_MATCH_MODEL(PHY_ID_NCN26000) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_S2500) },
{ }
};
MODULE_DEVICE_TABLE(mdio, ncn26000_tbl);
-MODULE_AUTHOR("Piergiorgio Beruto");
+MODULE_AUTHOR("Piergiorgio Beruto <pier.beruto@onsemi.com>");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
MODULE_DESCRIPTION("onsemi 10BASE-T1S PHY driver");
MODULE_LICENSE("Dual BSD/GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 08/15] net: ethernet: oa_tc6: read, write interface with MMS option
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Vendors are allowed to use any memory map selector that
is between 10 and 15.
Current read/write API interface expects register address with
the value of MMS (memory map selector) embedded in it.
This requires vendors to encoding the address whenever the call
to read/write register is made. To avoid this extra step, and
to bring consistency in usage of the API by different vendors,
new APIs have been added to write and read registers with
MMS as one of the parameters.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- No change
changes in v5
- No change
changes in v4
- New API added to take MMS as a parameter to avoid need for
read/write calls to encode MMS to the address.
- first patch
---
drivers/net/ethernet/microchip/lan865x/lan865x.c | 61 +++++++++------
drivers/net/ethernet/oa_tc6/oa_tc6.c | 97 +++++++++++++++++++++---
include/linux/oa_tc6.h | 8 ++
3 files changed, 131 insertions(+), 35 deletions(-)
diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c
index 0277d9737369..3b555ee69804 100644
--- a/drivers/net/ethernet/microchip/lan865x/lan865x.c
+++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c
@@ -13,27 +13,27 @@
#define DRV_NAME "lan8650"
/* MAC Network Control Register */
-#define LAN865X_REG_MAC_NET_CTL 0x00010000
+#define LAN865X_REG_MAC_NET_CTL 0x0
#define MAC_NET_CTL_TXEN BIT(3) /* Transmit Enable */
#define MAC_NET_CTL_RXEN BIT(2) /* Receive Enable */
/* MAC Network Configuration Reg */
-#define LAN865X_REG_MAC_NET_CFG 0x00010001
+#define LAN865X_REG_MAC_NET_CFG 0x1
#define MAC_NET_CFG_PROMISCUOUS_MODE BIT(4)
#define MAC_NET_CFG_MULTICAST_MODE BIT(6)
#define MAC_NET_CFG_UNICAST_MODE BIT(7)
/* MAC Hash Register Bottom */
-#define LAN865X_REG_MAC_L_HASH 0x00010020
+#define LAN865X_REG_MAC_L_HASH 0x20
/* MAC Hash Register Top */
-#define LAN865X_REG_MAC_H_HASH 0x00010021
+#define LAN865X_REG_MAC_H_HASH 0x21
/* MAC Specific Addr 1 Bottom Reg */
-#define LAN865X_REG_MAC_L_SADDR1 0x00010022
+#define LAN865X_REG_MAC_L_SADDR1 0x22
/* MAC Specific Addr 1 Top Reg */
-#define LAN865X_REG_MAC_H_SADDR1 0x00010023
+#define LAN865X_REG_MAC_H_SADDR1 0x23
/* MAC TSU Timer Increment Register */
-#define LAN865X_REG_MAC_TSU_TIMER_INCR 0x00010077
+#define LAN865X_REG_MAC_TSU_TIMER_INCR 0x77
#define MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS 0x0028
struct lan865x_priv {
@@ -49,7 +49,8 @@ static int lan865x_set_hw_macaddr_low_bytes(struct oa_tc6 *tc6, const u8 *mac)
regval = (mac[3] << 24) | (mac[2] << 16) | (mac[1] << 8) | mac[0];
- return oa_tc6_write_register(tc6, LAN865X_REG_MAC_L_SADDR1, regval);
+ return oa_tc6_write_register_mms(tc6, LAN865X_REG_MAC_L_SADDR1,
+ OA_TC6_PHY_C45_MAC_MMS1, regval);
}
static int lan865x_set_hw_macaddr(struct lan865x_priv *priv, const u8 *mac)
@@ -65,8 +66,8 @@ static int lan865x_set_hw_macaddr(struct lan865x_priv *priv, const u8 *mac)
/* Prepare and configure MAC address high bytes */
regval = (mac[5] << 8) | mac[4];
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_SADDR1,
- regval);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_SADDR1,
+ OA_TC6_PHY_C45_MAC_MMS1, regval);
if (!ret)
return 0;
@@ -146,14 +147,16 @@ static int lan865x_set_specific_multicast_addr(struct lan865x_priv *priv)
}
/* Enabling specific multicast addresses */
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH, hash_hi);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, hash_hi);
if (ret) {
netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",
ret);
return ret;
}
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH, hash_lo);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_L_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, hash_lo);
if (ret)
netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",
ret);
@@ -166,16 +169,16 @@ static int lan865x_set_all_multicast_addr(struct lan865x_priv *priv)
int ret;
/* Enabling all multicast addresses */
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH,
- 0xffffffff);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, 0xffffffff);
if (ret) {
netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",
ret);
return ret;
}
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH,
- 0xffffffff);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_L_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, 0xffffffff);
if (ret)
netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",
ret);
@@ -187,14 +190,16 @@ static int lan865x_clear_all_multicast_addr(struct lan865x_priv *priv)
{
int ret;
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH, 0);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, 0);
if (ret) {
netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",
ret);
return ret;
}
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH, 0);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_L_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, 0);
if (ret)
netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",
ret);
@@ -235,7 +240,8 @@ static void lan865x_multicast_work_handler(struct work_struct *work)
if (lan865x_clear_all_multicast_addr(priv))
return;
}
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CFG, regval);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CFG,
+ OA_TC6_PHY_C45_MAC_MMS1, regval);
if (ret)
netdev_err(priv->netdev, "Failed to enable promiscuous/multicast/normal mode: %d\n",
ret);
@@ -260,12 +266,14 @@ static int lan865x_hw_disable(struct lan865x_priv *priv)
{
u32 regval;
- if (oa_tc6_read_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, ®val))
+ if (oa_tc6_read_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+ OA_TC6_PHY_C45_MAC_MMS1, ®val))
return -ENODEV;
regval &= ~(MAC_NET_CTL_TXEN | MAC_NET_CTL_RXEN);
- if (oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, regval))
+ if (oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+ OA_TC6_PHY_C45_MAC_MMS1, regval))
return -ENODEV;
return 0;
@@ -291,12 +299,14 @@ static int lan865x_hw_enable(struct lan865x_priv *priv)
{
u32 regval;
- if (oa_tc6_read_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, ®val))
+ if (oa_tc6_read_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+ OA_TC6_PHY_C45_MAC_MMS1, ®val))
return -ENODEV;
regval |= MAC_NET_CTL_TXEN | MAC_NET_CTL_RXEN;
- if (oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, regval))
+ if (oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+ OA_TC6_PHY_C45_MAC_MMS1, regval))
return -ENODEV;
return 0;
@@ -359,8 +369,9 @@ static int lan865x_probe(struct spi_device *spi)
* stamping at the end of the Start of Frame Delimiter (SFD) and set the
* Timer Increment reg to 40 ns to be used as a 25 MHz internal clock.
*/
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_TSU_TIMER_INCR,
- MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_TSU_TIMER_INCR,
+ OA_TC6_PHY_C45_MAC_MMS1,
+ MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS);
if (ret) {
dev_err(&spi->dev, "Failed to config TSU Timer Incr reg: %d\n",
ret);
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index 0b8e29bbd703..818eb28c1b2d 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -383,6 +383,83 @@ int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value)
}
EXPORT_SYMBOL_GPL(oa_tc6_read_register);
+/**
+ * oa_tc6_read_registers_mms - function for reading multiple consecutive
+ * registers for the given address, memory map selector pair.
+ * @tc6: oa_tc6 struct.
+ * @address: address of the first register to be read in the MAC-PHY.
+ * @mms: Memory map selector for the registers to be read.
+ * @value: values to be read from the starting register address @address.
+ * @length: number of consecutive registers to be read from @address.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value[], u8 length)
+{
+ u32 mms_addr = (u32)mms << 16 | (u32)address;
+
+ return oa_tc6_read_registers(tc6, mms_addr, value, length);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_read_registers_mms);
+
+/**
+ * oa_tc6_read_register_mms - function for reading a MAC-PHY register
+ * for the given address, memory map selector pair.
+ * @tc6: oa_tc6 struct.
+ * @address: register address of the MAC-PHY to be read.
+ * @mms: Memory Map Selector for the given address
+ * @value: value read from the @address register address of the MAC-PHY.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 *value)
+{
+ return oa_tc6_read_registers_mms(tc6, address, mms, value, 1);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_read_register_mms);
+
+/**
+ * oa_tc6_write_registers_mms - function for writing multiple consecutive
+ * registers for the given address, memory map selector pair.
+ * @tc6: oa_tc6 struct.
+ * @address: address of the first register to be written in the MAC-PHY.
+ * @mms: memory map Selector for the given register.
+ * @value: values to be written from the starting register address @address.
+ * @length: number of consecutive registers to be written from @address.
+ *
+ * Maximum of 128 consecutive registers can be written starting at @address.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_write_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value[], u8 length)
+{
+ u32 mms_addr = (u32)mms << 16 | (u32)address;
+
+ return oa_tc6_write_registers(tc6, mms_addr, value, length);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_write_registers_mms);
+
+/**
+ * oa_tc6_write_register_mms - function for writing a MAC-PHY register
+ * associated with the given memory map selector.
+ * @tc6: oa_tc6 struct.
+ * @address: register address of the MAC-PHY to be written.
+ * @mms: memory map selector for the given register.
+ * @value: value to be written in the @address register address of
+ * the MAC-PHY.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value)
+{
+ return oa_tc6_write_registers_mms(tc6, address, mms, &value, 1);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms);
+
/**
* oa_tc6_write_registers - function for writing multiple consecutive registers.
* @tc6: oa_tc6 struct.
@@ -496,14 +573,14 @@ static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
int regnum)
{
struct oa_tc6 *tc6 = bus->priv;
+ int mms, ret;
u32 regval;
- int ret;
- ret = oa_tc6_get_phy_c45_mms(tc6, devnum);
- if (ret < 0)
- return ret;
+ mms = oa_tc6_get_phy_c45_mms(tc6, devnum);
+ if (mms < 0)
+ return mms;
- ret = oa_tc6_read_register(tc6, (ret << 16) | regnum, ®val);
+ ret = oa_tc6_read_register_mms(tc6, (u16)regnum, (u16)mms, ®val);
if (ret)
return ret;
@@ -514,13 +591,13 @@ static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
int regnum, u16 val)
{
struct oa_tc6 *tc6 = bus->priv;
- int ret;
+ int mms;
- ret = oa_tc6_get_phy_c45_mms(tc6, devnum);
- if (ret < 0)
- return ret;
+ mms = oa_tc6_get_phy_c45_mms(tc6, devnum);
+ if (mms < 0)
+ return mms;
- return oa_tc6_write_register(tc6, (ret << 16) | regnum, val);
+ return oa_tc6_write_register_mms(tc6, (u16)regnum, (u16)mms, val);
}
static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index a89151267713..3d50971f0f5b 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -37,6 +37,14 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
u8 length);
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb);
int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6);
+int oa_tc6_write_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value[], u8 length);
+int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value);
+int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value[], u8 length);
+int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 *value);
int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info);
int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);
int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 07/15] net: ethernet: oa_tc6: Support for vendor specific MMS
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
OPEN Alliance 10BASE-T1x Serial Interface specification, table 6
allows vendors to use any memory map select (MMS) value between
10 and 15. This new API interface enables vendor to map one of
thes MMS values to MDIO_MMD_VEND1.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- No change
changes in v5
- no change
changes in v4
- no change
changes in v3
- no change
changes in v2
- Moved the handling of vendor specific MMS mapping to separate patch
- new patch
---
drivers/net/ethernet/oa_tc6/oa_tc6.c | 21 ++++++++++++++++++---
include/linux/oa_tc6.h | 1 +
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index 6cc7c76d1d3c..0b8e29bbd703 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -207,6 +207,18 @@ int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)
}
EXPORT_SYMBOL_GPL(oa_tc6_ioctl);
+/**
+ * oa_tc6_set_vend1_mms - Add vendor specific MDIO_MMD to OA TC6 MMS
+ * mapper value.
+ * @tc6: oa_tc6 struct.
+ * @mms: vendor defined MMS value for VEND1 mdio device.
+ */
+void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)
+{
+ tc6->vend1_mms = mms;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_set_vend1_mms);
+
static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
enum oa_tc6_register_op reg_op)
{
@@ -460,7 +472,7 @@ static int oa_tc6_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
val);
}
-static int oa_tc6_get_phy_c45_mms(int devnum)
+static int oa_tc6_get_phy_c45_mms(struct oa_tc6 *tc6, int devnum)
{
switch (devnum) {
case MDIO_MMD_PCS:
@@ -473,6 +485,8 @@ static int oa_tc6_get_phy_c45_mms(int devnum)
return OA_TC6_PHY_C45_AUTO_NEG_MMS5;
case MDIO_MMD_POWER_UNIT:
return OA_TC6_PHY_C45_POWER_UNIT_MMS6;
+ case MDIO_MMD_VEND1:
+ return tc6->vend1_mms;
default:
return -EOPNOTSUPP;
}
@@ -485,7 +499,7 @@ static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
u32 regval;
int ret;
- ret = oa_tc6_get_phy_c45_mms(devnum);
+ ret = oa_tc6_get_phy_c45_mms(tc6, devnum);
if (ret < 0)
return ret;
@@ -502,7 +516,7 @@ static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
struct oa_tc6 *tc6 = bus->priv;
int ret;
- ret = oa_tc6_get_phy_c45_mms(devnum);
+ ret = oa_tc6_get_phy_c45_mms(tc6, devnum);
if (ret < 0)
return ret;
@@ -1332,6 +1346,7 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
SET_NETDEV_DEV(netdev, &spi->dev);
mutex_init(&tc6->spi_ctrl_lock);
spin_lock_init(&tc6->tx_skb_lock);
+ tc6->vend1_mms = -EOPNOTSUPP;
tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID;
INIT_LIST_HEAD(&tc6->tx_ts_skb_q);
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 4047c22a366a..a89151267713 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -47,5 +47,6 @@ void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
struct ethtool_ts_stats *ts_stats);
int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
struct kernel_hwtstamp_config *cfg);
+void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms);
void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
#endif /* _LINUX_OA_TC6_H */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 06/15] net: ethernet: oa_tc6: Support for hardware timestamp
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
PTP register/unregister calls are implemented in oa_tc6_ptp.c.
The APIs that work with the hardware for timestamp is provided
by vendor code as it may be vendor dependent.
Interface for ndo_hwtstamp_set/get, ioctl, control and status
callback for ethtool are provided to support hardware timestamp
feature.
Besides ioctl interface, hardware timestamp functions that handles
header and footer data are in oa_tc6.c. Helper functions are in
oa_tc6_tstamp.c.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- Fixed the issue of function parameter in oa_tc6_get_ts_stats
not described in comments section for documentation.
- Avoided typecasting __be32 as u32
changes in v5
- As subtracting skb len by FCS size is considered bug, changes
are removed. Will be fixed in stable branch (net repo)
changes in v4
- Fixed the condition check for subtracting the FCS size
from skb len.
changes in v3
- Replaced warning printk with ratelimited printk
- Checking the hardware register before enabling hardware
timestamp
changes in v1
- Added hardware timestamp support to the OA TC6 framework.
---
MAINTAINERS | 1 +
drivers/net/ethernet/oa_tc6/Makefile | 2 +-
drivers/net/ethernet/oa_tc6/oa_tc6.c | 217 +++++++++++++++++++++++++--
drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c | 67 +++++++++
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 33 ++++
drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c | 202 +++++++++++++++++++++++++
include/linux/oa_tc6.h | 12 ++
7 files changed, 519 insertions(+), 15 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index ff1295d37ae2..ca9f39b46b96 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20214,6 +20214,7 @@ F: drivers/rtc/rtc-optee.c
OPEN ALLIANCE 10BASE-T1S MACPHY SERIAL INTERFACE FRAMEWORK
M: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
+M: Selva Rajagopal <selvamani.rajagopal@onsemi.com> (timestamp support)
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/networking/oa-tc6-framework.rst
diff --git a/drivers/net/ethernet/oa_tc6/Makefile b/drivers/net/ethernet/oa_tc6/Makefile
index f24aae852ef2..964f668efc2d 100644
--- a/drivers/net/ethernet/oa_tc6/Makefile
+++ b/drivers/net/ethernet/oa_tc6/Makefile
@@ -4,4 +4,4 @@
#
obj-$(CONFIG_OA_TC6) := oa_tc6_mod.o
-oa_tc6_mod-objs := oa_tc6.o
+oa_tc6_mod-objs := oa_tc6.o oa_tc6_ptp.o oa_tc6_tstamp.o
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index bf96e8d1ccb9..6cc7c76d1d3c 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -14,6 +14,15 @@
#include "oa_tc6_std_def.h"
+struct oa_tc6_ts_info_rx {
+ bool rtsa;
+ bool rtsp;
+};
+
+struct oa_tc6_ts_info_tx {
+ u8 tsc;
+};
+
static int oa_tc6_spi_transfer(struct oa_tc6 *tc6,
enum oa_tc6_header_type header_type, u16 length)
{
@@ -48,6 +57,156 @@ static int oa_tc6_get_parity(u32 p)
return !((p >> 28) & 1);
}
+static struct oa_tc6_ts_info_tx *oa_tc6_tsinfo_tx(struct sk_buff *skb)
+{
+ return (struct oa_tc6_ts_info_tx *)(skb->cb);
+}
+
+static struct oa_tc6_ts_info_rx *oa_tc6_tsinfo_rx(struct sk_buff *skb)
+{
+ return (struct oa_tc6_ts_info_rx *)(skb->cb);
+}
+
+static void oa_tc6_defer_for_hwtstamp(struct oa_tc6 *tc6,
+ struct sk_buff *skb)
+{
+ if (!tc6->hw_tstamp_enabled)
+ return;
+ if (!skb || (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) == 0)
+ return;
+ if (tc6->ts_config.tx_type != HWTSTAMP_TX_ON) {
+ tc6->tx_hwtstamp_lost++;
+ return;
+ }
+
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ u8 ret = tc6->tx_ts_idx++;
+
+ if (ret == OA_TC6_TTSCC_REG_ID)
+ tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID;
+ oa_tc6_tsinfo_tx(skb)->tsc = ret;
+
+ list_add_tail(&skb->list, &tc6->tx_ts_skb_q);
+}
+
+static int oa_tc6_process_deferred_skb(struct oa_tc6 *tc6, u8 tsc)
+{
+ struct skb_shared_hwtstamps tstamp;
+ struct oa_tc6_ts_info_tx *ski;
+ struct sk_buff *skb, *tmp;
+ bool found = false;
+ int ret = 0;
+
+ /* Size of data must match OA_TC6_TSTAMP_SZ */
+ u32 data[2];
+
+ list_for_each_entry_safe(skb, tmp, &tc6->tx_ts_skb_q, list) {
+ ski = oa_tc6_tsinfo_tx(skb);
+ if (ski->tsc != tsc)
+ continue;
+ if (found) {
+ dev_warn_ratelimited(&tc6->spi->dev,
+ "Multiple skbs. tsc = %d\n",
+ tsc);
+ tc6->tx_hwtstamp_err++;
+ }
+ found = true;
+ list_del(&skb->list);
+
+ /* Retrieve the timestamping info */
+ ret = oa_tc6_read_registers(tc6,
+ OA_TC6_REG_TTSCA_HIGH +
+ 2 * (tsc - 1), &data[0], 2);
+
+ if (!ret) {
+ tstamp.hwtstamp = ktime_set(data[0], data[1]);
+ skb_tstamp_tx(skb, &tstamp);
+ tc6->tx_hwtstamp_pkts++;
+ }
+
+ dev_kfree_skb(skb);
+ }
+ return ret;
+}
+
+static void oa_tc6_events_handle(struct oa_tc6 *tc6, u32 val)
+{
+ /* Check TX timestamping */
+ if (val & STATUS0_TTSCAA)
+ oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCA_REG_ID);
+
+ if (val & STATUS0_TTSCAB)
+ oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCB_REG_ID);
+
+ if (val & STATUS0_TTSCAC)
+ oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCC_REG_ID);
+}
+
+static void oa_tc6_update_ts_in_rx_skb(struct oa_tc6 *tc6)
+{
+ struct sk_buff *skb = tc6->rx_skb;
+ struct oa_tc6_ts_info_rx *ski;
+ __be32 ts_val[2];
+ u32 ts[2];
+
+ if (!tc6->hw_tstamp_enabled)
+ return;
+ ski = oa_tc6_tsinfo_rx(skb);
+ if (!ski->rtsa)
+ return;
+
+ memcpy(&ts_val[0], skb->data, 4);
+ memcpy(&ts_val[1], (u32 *)skb->data + 1, 4);
+
+ ts[0] = be32_to_cpu(ts_val[0]);
+ ts[1] = be32_to_cpu(ts_val[1]);
+
+ /* Check parity */
+ if ((oa_tc6_get_parity(ts[0]) ^ oa_tc6_get_parity(ts[1])) ==
+ !ski->rtsp) {
+ struct skb_shared_hwtstamps *hw_ts;
+
+ /* Report timestamp to the upper layers */
+ hw_ts = skb_hwtstamps(skb);
+ memset(hw_ts, 0, sizeof(*hw_ts));
+ hw_ts->hwtstamp = ktime_set(ts[0], ts[1]);
+ }
+ skb_pull(skb, sizeof(ts));
+}
+
+static int oa_tc6_update_standard_capability(struct oa_tc6 *tc6)
+{
+ u32 regval = 0;
+ int ret;
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, ®val);
+ if (ret)
+ return ret;
+ if (regval & STDCAP_FRAME_TIMESTAMP_CAPABILITY)
+ tc6->hw_tstamp_supported = true;
+ return 0;
+}
+
+/**
+ * oa_tc6_ioctl - generic ioctl interface for MAC-PHY drivers.
+ * @tc6: oa_tc6 struct.
+ * @rq: request from socket interface
+ * @cmd: value to set/get timestamp configuration
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)
+{
+ if (!netif_running(tc6->netdev))
+ return -EINVAL;
+
+ if (cmd == SIOCSHWTSTAMP || cmd == SIOCGHWTSTAMP)
+ return oa_tc6_tstamp_ioctl(tc6, rq, cmd);
+ else
+ return phy_do_ioctl_running(tc6->netdev, rq, cmd);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ioctl);
+
static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
enum oa_tc6_register_op reg_op)
{
@@ -571,6 +730,9 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
return ret;
}
+ if ((value & STATUS0_TTSCA_MASK) != 0)
+ oa_tc6_events_handle(tc6, value & STATUS0_TTSCA_MASK);
+
/* Clear the error interrupts status */
ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, value);
if (ret) {
@@ -653,6 +815,7 @@ static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
tc6->rx_skb->len > ETH_FCS_LEN)
skb_trim(tc6->rx_skb, tc6->rx_skb->len - ETH_FCS_LEN);
+ oa_tc6_update_ts_in_rx_skb(tc6);
tc6->rx_skb->protocol = eth_type_trans(tc6->rx_skb, tc6->netdev);
tc6->netdev->stats.rx_packets++;
tc6->netdev->stats.rx_bytes += tc6->rx_skb->len;
@@ -667,24 +830,29 @@ static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
memcpy(skb_put(tc6->rx_skb, length), payload, length);
}
-static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6)
+static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6, u32 footer)
{
+ struct oa_tc6_ts_info_rx *ski;
+
tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu +
- ETH_HLEN + ETH_FCS_LEN);
+ ETH_HLEN + ETH_FCS_LEN + OA_TC6_TSTAMP_SZ);
if (!tc6->rx_skb) {
tc6->netdev->stats.rx_dropped++;
return -ENOMEM;
}
+ ski = oa_tc6_tsinfo_rx(tc6->rx_skb);
+ ski->rtsa = FIELD_GET(OA_TC6_DATA_FOOTER_RTSA_VALID, footer);
+ ski->rtsp = FIELD_GET(OA_TC6_DATA_FOOTER_RTSP_VALID, footer);
return 0;
}
static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,
- u16 size)
+ u16 size, u32 footer)
{
int ret;
- ret = oa_tc6_allocate_rx_skb(tc6);
+ ret = oa_tc6_allocate_rx_skb(tc6, footer);
if (ret)
return ret;
@@ -695,11 +863,11 @@ static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,
return 0;
}
-static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size)
+static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size, u32 footer)
{
int ret;
- ret = oa_tc6_allocate_rx_skb(tc6);
+ ret = oa_tc6_allocate_rx_skb(tc6, footer);
if (ret)
return ret;
@@ -744,7 +912,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
size = end_byte_offset + 1 - start_byte_offset;
return oa_tc6_prcs_complete_rx_frame(tc6,
&data[start_byte_offset],
- size);
+ size, footer);
}
/* Process the chunk with only rx frame start */
@@ -752,7 +920,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
return oa_tc6_prcs_rx_frame_start(tc6,
&data[start_byte_offset],
- size);
+ size, footer);
}
/* Process the chunk with only rx frame end */
@@ -777,7 +945,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
return oa_tc6_prcs_rx_frame_start(tc6,
&data[start_byte_offset],
- size);
+ size, footer);
}
/* Process the chunk with ongoing rx frame data */
@@ -831,13 +999,15 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
}
static __be32 oa_tc6_prepare_data_header(bool data_valid, bool start_valid,
- bool end_valid, u8 end_byte_offset)
+ bool end_valid, u8 end_byte_offset,
+ u8 tsc)
{
u32 header = FIELD_PREP(OA_TC6_DATA_HEADER_DATA_NOT_CTRL,
OA_TC6_DATA_HEADER) |
FIELD_PREP(OA_TC6_DATA_HEADER_DATA_VALID, data_valid) |
FIELD_PREP(OA_TC6_DATA_HEADER_START_VALID, start_valid) |
FIELD_PREP(OA_TC6_DATA_HEADER_END_VALID, end_valid) |
+ FIELD_PREP(OA_TC6_DATA_HEADER_TSC_OFFSET, tsc) |
FIELD_PREP(OA_TC6_DATA_HEADER_END_BYTE_OFFSET,
end_byte_offset);
@@ -856,6 +1026,7 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)
enum oa_tc6_data_start_valid_info start_valid;
u8 end_byte_offset = 0;
u16 length_to_copy;
+ u8 tsc = 0;
/* Initial value is assigned here to avoid more than 80 characters in
* the declaration place.
@@ -865,8 +1036,10 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)
/* Set start valid if the current tx chunk contains the start of the tx
* ethernet frame.
*/
- if (!tc6->tx_skb_offset)
+ if (!tc6->tx_skb_offset) {
start_valid = OA_TC6_DATA_START_VALID;
+ tsc = oa_tc6_tsinfo_tx(tc6->ongoing_tx_skb)->tsc;
+ }
/* If the remaining tx skb length is more than the chunk payload size of
* 64 bytes then copy only 64 bytes and leave the ongoing tx skb for
@@ -887,12 +1060,17 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)
tc6->tx_skb_offset = 0;
tc6->netdev->stats.tx_bytes += tc6->ongoing_tx_skb->len;
tc6->netdev->stats.tx_packets++;
- kfree_skb(tc6->ongoing_tx_skb);
+
+ /* Free the ones that are not saved for later processing,
+ * like timestamping.
+ */
+ if (!(skb_shinfo(tc6->ongoing_tx_skb)->tx_flags & SKBTX_IN_PROGRESS))
+ kfree_skb(tc6->ongoing_tx_skb);
tc6->ongoing_tx_skb = NULL;
}
*tx_buf = oa_tc6_prepare_data_header(OA_TC6_DATA_VALID, start_valid,
- end_valid, end_byte_offset);
+ end_valid, end_byte_offset, tsc);
tc6->spi_data_tx_buf_offset += OA_TC6_CHUNK_SIZE;
}
@@ -910,6 +1088,8 @@ static u16 oa_tc6_prepare_spi_tx_buf_for_tx_skbs(struct oa_tc6 *tc6)
tc6->ongoing_tx_skb = tc6->waiting_tx_skb;
tc6->waiting_tx_skb = NULL;
spin_unlock_bh(&tc6->tx_skb_lock);
+ oa_tc6_defer_for_hwtstamp(tc6,
+ tc6->ongoing_tx_skb);
}
if (!tc6->ongoing_tx_skb)
break;
@@ -926,7 +1106,7 @@ static void oa_tc6_add_empty_chunks_to_spi_buf(struct oa_tc6 *tc6,
header = oa_tc6_prepare_data_header(OA_TC6_DATA_INVALID,
OA_TC6_DATA_START_INVALID,
- OA_TC6_DATA_END_INVALID, 0);
+ OA_TC6_DATA_END_INVALID, 0, false);
while (needed_empty_chunks--) {
__be32 *tx_buf = tc6->spi_data_tx_buf +
@@ -1118,6 +1298,7 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb)
return NETDEV_TX_OK;
}
+ oa_tc6_tsinfo_tx(skb)->tsc = 0;
spin_lock_bh(&tc6->tx_skb_lock);
tc6->waiting_tx_skb = skb;
spin_unlock_bh(&tc6->tx_skb_lock);
@@ -1151,6 +1332,8 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
SET_NETDEV_DEV(netdev, &spi->dev);
mutex_init(&tc6->spi_ctrl_lock);
spin_lock_init(&tc6->tx_skb_lock);
+ tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID;
+ INIT_LIST_HEAD(&tc6->tx_ts_skb_q);
/* Set the SPI controller to pump at realtime priority */
tc6->spi->rt = true;
@@ -1216,6 +1399,12 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
goto phy_exit;
}
+ ret = oa_tc6_update_standard_capability(tc6);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "Failed to read capability\n");
+ goto phy_exit;
+ }
+
ret = devm_request_threaded_irq(&tc6->spi->dev, tc6->spi->irq,
oa_tc6_macphy_isr,
oa_tc6_macphy_threaded_irq,
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
new file mode 100644
index 000000000000..921191ec6829
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for hardware timestamping feature for OPEN Alliance
+ * 10BASE‑T1x MAC‑PHY Serial Interface framework
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#include <linux/hrtimer.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/phylink.h>
+#include <linux/spi/spi.h>
+#include <linux/oa_tc6.h>
+#include <linux/net_tstamp.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/ktime.h>
+#include <linux/errno.h>
+
+#include "oa_tc6_std_def.h"
+
+/**
+ * oa_tc6_ptp_register - Registers clock related callbacks
+ * @tc6: oa_tc6 struct.
+ * @info: Describes a PTP hardware clock
+ *
+ * Description: Vendors are expected to set the hardware timestamp
+ * related callbacks before calling this function.
+ */
+int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info)
+{
+ /* Not supporting hardware timestamp isn't an error */
+ if (!tc6->hw_tstamp_supported)
+ return 0;
+
+ snprintf(info->name, sizeof(info->name), "%s",
+ "OA TC6 PTP clock");
+ tc6->ptp_clock = ptp_clock_register(info, &tc6->spi->dev);
+ if (IS_ERR(tc6->ptp_clock)) {
+ dev_err(&tc6->spi->dev, "Registration of %s failed",
+ info->name);
+ return -EFAULT;
+ }
+ dev_info(&tc6->spi->dev, "%s registered. index %d", info->name,
+ ptp_clock_index(tc6->ptp_clock));
+ return 0;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ptp_register);
+
+/**
+ * oa_tc6_ptp_unregister - Unregisters clock related callbacks
+ * @tc6: oa_tc6 struct.
+ */
+void oa_tc6_ptp_unregister(struct oa_tc6 *tc6)
+{
+ if (tc6->ptp_clock)
+ ptp_clock_unregister(tc6->ptp_clock);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ptp_unregister);
+
+MODULE_DESCRIPTION("OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
index bc58834a3368..e8ec379dd60d 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -22,6 +22,7 @@
/* Standard Capabilities Register */
#define OA_TC6_REG_STDCAP 0x0002
#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
+#define STDCAP_FRAME_TIMESTAMP_CAPABILITY BIT(6)
/* Reset Control and Status Register */
#define OA_TC6_REG_RESET 0x0003
@@ -31,9 +32,14 @@
#define OA_TC6_REG_CONFIG0 0x0004
#define CONFIG0_SYNC BIT(15)
#define CONFIG0_ZARFE_ENABLE BIT(12)
+#define CONFIG0_FTSE_ENABLE BIT(7)
/* Status Register #0 */
#define OA_TC6_REG_STATUS0 0x0008
+#define STATUS0_TTSCAC BIT(10)
+#define STATUS0_TTSCAB BIT(9)
+#define STATUS0_TTSCAA BIT(8)
+#define STATUS0_TTSCA_MASK GENMASK(10, 8)
#define STATUS0_RESETC BIT(6) /* Reset Complete */
#define STATUS0_HEADER_ERROR BIT(5)
#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4)
@@ -47,6 +53,7 @@
/* Interrupt Mask Register #0 */
#define OA_TC6_REG_INT_MASK0 0x000C
+#define INT_MASK0_TTSCA_MASK GENMASK(10, 8)
#define INT_MASK0_HEADER_ERR_MASK BIT(5)
#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4)
#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3)
@@ -58,6 +65,9 @@
#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00
#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F
+/* Tx timestamp capture register A (high) */
+#define OA_TC6_REG_TTSCA_HIGH (0x1010)
+
/* Control command header */
#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
@@ -73,6 +83,7 @@
#define OA_TC6_DATA_HEADER_START_WORD_OFFSET GENMASK(19, 16)
#define OA_TC6_DATA_HEADER_END_VALID BIT(14)
#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET GENMASK(13, 8)
+#define OA_TC6_DATA_HEADER_TSC_OFFSET GENMASK(7, 6)
#define OA_TC6_DATA_HEADER_PARITY BIT(0)
/* Data footer */
@@ -84,6 +95,8 @@
#define OA_TC6_DATA_FOOTER_START_VALID BIT(20)
#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET GENMASK(19, 16)
#define OA_TC6_DATA_FOOTER_END_VALID BIT(14)
+#define OA_TC6_DATA_FOOTER_RTSA_VALID BIT(7)
+#define OA_TC6_DATA_FOOTER_RTSP_VALID BIT(6)
#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8)
#define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
@@ -105,6 +118,12 @@
#define STATUS0_RESETC_POLL_DELAY 1000
#define STATUS0_RESETC_POLL_TIMEOUT 1000000
+#define OA_TC6_TSTAMP_SZ 8
+
+#define OA_TC6_TTSCA_REG_ID 1
+#define OA_TC6_TTSCB_REG_ID 2
+#define OA_TC6_TTSCC_REG_ID 3
+
/* Internal structure for MAC-PHY drivers */
struct oa_tc6 {
struct net_device *netdev;
@@ -127,6 +146,17 @@ struct oa_tc6 {
bool rx_buf_overflow;
bool int_flag;
bool disable_traffic;
+ struct ptp_clock_info ptp_clock_info;
+ struct hwtstamp_config ts_config;
+ struct list_head tx_ts_skb_q;
+ struct ptp_clock *ptp_clock;
+ bool hw_tstamp_supported;
+ bool hw_tstamp_enabled;
+ u32 tx_hwtstamp_pkts;
+ u32 tx_hwtstamp_lost;
+ u32 tx_hwtstamp_err;
+ int vend1_mms;
+ u8 tx_ts_idx;
};
enum oa_tc6_header_type {
@@ -153,5 +183,8 @@ enum oa_tc6_data_end_valid_info {
OA_TC6_DATA_END_INVALID,
OA_TC6_DATA_END_VALID,
};
+
+int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);
+
#endif /* OA_TC6_STD_DEF_H */
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
new file mode 100644
index 000000000000..272701a4081d
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iopoll.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
+#include <linux/oa_tc6.h>
+
+#include "oa_tc6_std_def.h"
+
+static int oa_tc6_set_hwtstamp_settings(struct oa_tc6 *tc6)
+{
+ u32 cfg0, irqm, status0;
+ int ret;
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, &cfg0);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "Failed to read CFG0 register\n");
+ goto out;
+ }
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_INT_MASK0, &irqm);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "failed to read IRQM register\n");
+ goto out;
+ }
+
+ if (tc6->ts_config.tx_type == HWTSTAMP_TX_ON ||
+ tc6->ts_config.rx_filter == HWTSTAMP_FILTER_ALL)
+ cfg0 |= CONFIG0_FTSE_ENABLE;
+ else
+ cfg0 &= ~CONFIG0_FTSE_ENABLE;
+
+ if (tc6->ts_config.tx_type == HWTSTAMP_TX_ON)
+ irqm &= ~INT_MASK0_TTSCA_MASK;
+ else
+ irqm |= INT_MASK0_TTSCA_MASK;
+
+ /* Clear timestamp related IRQs */
+ status0 = STATUS0_TTSCA_MASK;
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, status0);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "failed to write STATUS0 register\n");
+ goto out;
+ }
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, irqm);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "failed to write IRQM register\n");
+ goto out;
+ }
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, cfg0);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "failed to write CFG0 register\n");
+ goto out;
+ }
+ if (cfg0 & CONFIG0_FTSE_ENABLE)
+ tc6->hw_tstamp_enabled = true;
+ else
+ tc6->hw_tstamp_enabled = false;
+out:
+ return ret;
+}
+
+/**
+ * oa_tc6_hwtstamp_get - gets hardware timestamp config
+ * @tc6: oa_tc6 struct.
+ * @cfg: kernel copy of hardware timestamp config
+ */
+void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg)
+{
+ hwtstamp_config_to_kernel(cfg, &tc6->ts_config);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_get);
+
+/**
+ * oa_tc6_hwtstamp_set - sets hardware timestamp config
+ * @tc6: oa_tc6 struct.
+ * @cfg: kernel copy of hardware timestamp config
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg)
+{
+ if (!netif_running(tc6->netdev))
+ return -EIO;
+
+ if (!tc6->hw_tstamp_supported)
+ return -EOPNOTSUPP;
+
+ switch (cfg->tx_type) {
+ case HWTSTAMP_TX_OFF:
+ case HWTSTAMP_TX_ON:
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (cfg->rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ case HWTSTAMP_FILTER_ALL:
+ case HWTSTAMP_FILTER_SOME:
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ case HWTSTAMP_FILTER_NTP_ALL:
+ break;
+ default:
+ return -ERANGE;
+ }
+ hwtstamp_config_from_kernel(&tc6->ts_config, cfg);
+
+ /* Supports timestamping all traffic */
+ if (cfg->rx_filter != HWTSTAMP_FILTER_NONE)
+ tc6->ts_config.rx_filter = HWTSTAMP_FILTER_ALL;
+ return oa_tc6_set_hwtstamp_settings(tc6);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_set);
+
+/**
+ * oa_tc6_get_ts_stats - Provides timestamping stats
+ * @tc6: oa_tc6 struct.
+ * @ts_stats: ethtool data structure to fill in
+ */
+void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
+ struct ethtool_ts_stats *stats)
+{
+ stats->pkts = tc6->tx_hwtstamp_pkts;
+ stats->err = tc6->tx_hwtstamp_err;
+ stats->lost = tc6->tx_hwtstamp_lost;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_get_ts_stats);
+
+int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)
+{
+ struct kernel_hwtstamp_config kcfg;
+ struct hwtstamp_config tscfg;
+ int ret = 0;
+
+ if (!tc6->hw_tstamp_supported)
+ return -EOPNOTSUPP;
+
+ if (cmd == SIOCSHWTSTAMP) {
+ if (copy_from_user(&tscfg, rq->ifr_data,
+ sizeof(tscfg)))
+ return -EFAULT;
+
+ if (tscfg.flags)
+ return -EINVAL;
+ hwtstamp_config_to_kernel(&kcfg, &tscfg);
+ ret = oa_tc6_hwtstamp_set(tc6, &kcfg);
+ if (ret)
+ return ret;
+ }
+ if (copy_to_user(rq->ifr_data, &tc6->ts_config,
+ sizeof(tc6->ts_config)))
+ ret = -EFAULT;
+ return ret;
+}
+
+/**
+ * oa_tc6_get_ts_info - Provides timestamp info for ethtool
+ * @tc6: oa_tc6 struct.
+ * @info: ethtool timestamping info structure
+ * @ts_stats: ethtool data structure to fill in
+ */
+int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
+ struct kernel_ethtool_ts_info *info)
+{
+ if (!tc6->ptp_clock)
+ return ethtool_op_get_ts_info(tc6->netdev, info);
+
+ info->so_timestamping = SOF_TIMESTAMPING_RAW_HARDWARE |
+ SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE;
+ info->phc_index = ptp_clock_index(tc6->ptp_clock);
+ info->tx_types = BIT(HWTSTAMP_TX_ON);
+ info->rx_filters = BIT(HWTSTAMP_FILTER_ALL);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_get_ts_info);
+
+MODULE_DESCRIPTION("OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 39b80033dfa9..4047c22a366a 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -12,6 +12,7 @@
#include <linux/etherdevice.h>
#include <linux/spi/spi.h>
+#include <linux/ptp_clock_kernel.h>
/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in
* the OPEN Alliance specification.
@@ -36,4 +37,15 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
u8 length);
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb);
int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6);
+int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info);
+int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);
+int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
+ struct kernel_ethtool_ts_info *ts_info);
+void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg);
+void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
+ struct ethtool_ts_stats *ts_stats);
+int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg);
+void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
#endif /* _LINUX_OA_TC6_H */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v6 05/15] net: ethernet: oa_tc6: Move constant definitions to header file
From: Selvamani Rajagopal via B4 Relay @ 2026-06-29 17:23 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260629-s2500-mac-phy-support-v6-0-18ce79500371@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
To help other source files within the module share the
constant definitions, they are moved to a header file.
The memory map selector(MMS) values that are defined in
in Table 6 of OPEN Alliance 10BASE-T1x Serial Interface
specification and currently used are added.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
changes in v6
- No change
changes in v5
- No change
changes in v4
- Added MMS values 1 and 12, which are used now
changes in v3
- Moved constant definitions from the source to newly created
header file for other sources in the directory to share.
- Standard specific defines are moved to Linux common header file
- First patch
---
drivers/net/ethernet/oa_tc6/oa_tc6.c | 145 +------------------------
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 157 +++++++++++++++++++++++++++
include/linux/oa_tc6.h | 15 +++
3 files changed, 173 insertions(+), 144 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index 0727d53345a3..bf96e8d1ccb9 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -12,150 +12,7 @@
#include <linux/phy.h>
#include <linux/oa_tc6.h>
-/* OPEN Alliance TC6 registers */
-/* Standard Capabilities Register */
-#define OA_TC6_REG_STDCAP 0x0002
-#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
-
-/* Reset Control and Status Register */
-#define OA_TC6_REG_RESET 0x0003
-#define RESET_SWRESET BIT(0) /* Software Reset */
-
-/* Configuration Register #0 */
-#define OA_TC6_REG_CONFIG0 0x0004
-#define CONFIG0_SYNC BIT(15)
-#define CONFIG0_ZARFE_ENABLE BIT(12)
-
-/* Status Register #0 */
-#define OA_TC6_REG_STATUS0 0x0008
-#define STATUS0_RESETC BIT(6) /* Reset Complete */
-#define STATUS0_HEADER_ERROR BIT(5)
-#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4)
-#define STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3)
-#define STATUS0_TX_PROTOCOL_ERROR BIT(0)
-
-/* Buffer Status Register */
-#define OA_TC6_REG_BUFFER_STATUS 0x000B
-#define BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8)
-#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0)
-
-/* Interrupt Mask Register #0 */
-#define OA_TC6_REG_INT_MASK0 0x000C
-#define INT_MASK0_HEADER_ERR_MASK BIT(5)
-#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4)
-#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3)
-#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0)
-#define INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \
- GENMASK(12, 7))
-
-/* PHY Clause 22 registers base address and mask */
-#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00
-#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F
-
-/* Control command header */
-#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
-#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
-#define OA_TC6_CTRL_HEADER_MEM_MAP_SELECTOR GENMASK(27, 24)
-#define OA_TC6_CTRL_HEADER_ADDR GENMASK(23, 8)
-#define OA_TC6_CTRL_HEADER_LENGTH GENMASK(7, 1)
-#define OA_TC6_CTRL_HEADER_PARITY BIT(0)
-
-/* Data header */
-#define OA_TC6_DATA_HEADER_DATA_NOT_CTRL BIT(31)
-#define OA_TC6_DATA_HEADER_DATA_VALID BIT(21)
-#define OA_TC6_DATA_HEADER_START_VALID BIT(20)
-#define OA_TC6_DATA_HEADER_START_WORD_OFFSET GENMASK(19, 16)
-#define OA_TC6_DATA_HEADER_END_VALID BIT(14)
-#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET GENMASK(13, 8)
-#define OA_TC6_DATA_HEADER_PARITY BIT(0)
-
-/* Data footer */
-#define OA_TC6_DATA_FOOTER_EXTENDED_STS BIT(31)
-#define OA_TC6_DATA_FOOTER_RXD_HEADER_BAD BIT(30)
-#define OA_TC6_DATA_FOOTER_CONFIG_SYNC BIT(29)
-#define OA_TC6_DATA_FOOTER_RX_CHUNKS GENMASK(28, 24)
-#define OA_TC6_DATA_FOOTER_DATA_VALID BIT(21)
-#define OA_TC6_DATA_FOOTER_START_VALID BIT(20)
-#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET GENMASK(19, 16)
-#define OA_TC6_DATA_FOOTER_END_VALID BIT(14)
-#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8)
-#define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
-
-/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the
- * OPEN Alliance specification.
- */
-#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */
-#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */
-#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
-#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
-#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */
-
-#define OA_TC6_CTRL_HEADER_SIZE 4
-#define OA_TC6_CTRL_REG_VALUE_SIZE 4
-#define OA_TC6_CTRL_IGNORED_SIZE 4
-#define OA_TC6_CTRL_MAX_REGISTERS 128
-#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\
- (OA_TC6_CTRL_MAX_REGISTERS *\
- OA_TC6_CTRL_REG_VALUE_SIZE) +\
- OA_TC6_CTRL_IGNORED_SIZE)
-#define OA_TC6_CHUNK_PAYLOAD_SIZE 64
-#define OA_TC6_DATA_HEADER_SIZE 4
-#define OA_TC6_CHUNK_SIZE (OA_TC6_DATA_HEADER_SIZE +\
- OA_TC6_CHUNK_PAYLOAD_SIZE)
-#define OA_TC6_MAX_TX_CHUNKS 48
-#define OA_TC6_SPI_DATA_BUF_SIZE (OA_TC6_MAX_TX_CHUNKS *\
- OA_TC6_CHUNK_SIZE)
-#define STATUS0_RESETC_POLL_DELAY 1000
-#define STATUS0_RESETC_POLL_TIMEOUT 1000000
-
-/* Internal structure for MAC-PHY drivers */
-struct oa_tc6 {
- struct net_device *netdev;
- struct phy_device *phydev;
- struct mii_bus *mdiobus;
- struct spi_device *spi;
- struct mutex spi_ctrl_lock; /* Protects spi control transfer */
- spinlock_t tx_skb_lock; /* Protects tx skb handling */
- void *spi_ctrl_tx_buf;
- void *spi_ctrl_rx_buf;
- void *spi_data_tx_buf;
- void *spi_data_rx_buf;
- struct sk_buff *ongoing_tx_skb;
- struct sk_buff *waiting_tx_skb;
- struct sk_buff *rx_skb;
- u16 tx_skb_offset;
- u16 spi_data_tx_buf_offset;
- u16 tx_credits;
- u8 rx_chunks_available;
- bool rx_buf_overflow;
- bool int_flag;
- bool disable_traffic;
-};
-
-enum oa_tc6_header_type {
- OA_TC6_CTRL_HEADER,
- OA_TC6_DATA_HEADER,
-};
-
-enum oa_tc6_register_op {
- OA_TC6_CTRL_REG_READ = 0,
- OA_TC6_CTRL_REG_WRITE = 1,
-};
-
-enum oa_tc6_data_valid_info {
- OA_TC6_DATA_INVALID,
- OA_TC6_DATA_VALID,
-};
-
-enum oa_tc6_data_start_valid_info {
- OA_TC6_DATA_START_INVALID,
- OA_TC6_DATA_START_VALID,
-};
-
-enum oa_tc6_data_end_valid_info {
- OA_TC6_DATA_END_INVALID,
- OA_TC6_DATA_END_VALID,
-};
+#include "oa_tc6_std_def.h"
static int oa_tc6_spi_transfer(struct oa_tc6 *tc6,
enum oa_tc6_header_type header_type, u16 length)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
new file mode 100644
index 000000000000..bc58834a3368
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Register and driver related definitions to support
+ * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework.
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#ifndef OA_TC6_STD_DEF_H
+#define OA_TC6_STD_DEF_H
+
+#include <linux/ptp_clock_kernel.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/spi/spi.h>
+#include <linux/skbuff.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/phy.h>
+
+/* OPEN Alliance TC6 registers */
+/* Standard Capabilities Register */
+#define OA_TC6_REG_STDCAP 0x0002
+#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
+
+/* Reset Control and Status Register */
+#define OA_TC6_REG_RESET 0x0003
+#define RESET_SWRESET BIT(0) /* Software Reset */
+
+/* Configuration Register #0 */
+#define OA_TC6_REG_CONFIG0 0x0004
+#define CONFIG0_SYNC BIT(15)
+#define CONFIG0_ZARFE_ENABLE BIT(12)
+
+/* Status Register #0 */
+#define OA_TC6_REG_STATUS0 0x0008
+#define STATUS0_RESETC BIT(6) /* Reset Complete */
+#define STATUS0_HEADER_ERROR BIT(5)
+#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4)
+#define STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3)
+#define STATUS0_TX_PROTOCOL_ERROR BIT(0)
+
+/* Buffer Status Register */
+#define OA_TC6_REG_BUFFER_STATUS 0x000B
+#define BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8)
+#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0)
+
+/* Interrupt Mask Register #0 */
+#define OA_TC6_REG_INT_MASK0 0x000C
+#define INT_MASK0_HEADER_ERR_MASK BIT(5)
+#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4)
+#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3)
+#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0)
+#define INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \
+ GENMASK(12, 7))
+
+/* PHY Clause 22 registers base address and mask */
+#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00
+#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F
+
+/* Control command header */
+#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
+#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
+#define OA_TC6_CTRL_HEADER_MEM_MAP_SELECTOR GENMASK(27, 24)
+#define OA_TC6_CTRL_HEADER_ADDR GENMASK(23, 8)
+#define OA_TC6_CTRL_HEADER_LENGTH GENMASK(7, 1)
+#define OA_TC6_CTRL_HEADER_PARITY BIT(0)
+
+/* Data header */
+#define OA_TC6_DATA_HEADER_DATA_NOT_CTRL BIT(31)
+#define OA_TC6_DATA_HEADER_DATA_VALID BIT(21)
+#define OA_TC6_DATA_HEADER_START_VALID BIT(20)
+#define OA_TC6_DATA_HEADER_START_WORD_OFFSET GENMASK(19, 16)
+#define OA_TC6_DATA_HEADER_END_VALID BIT(14)
+#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET GENMASK(13, 8)
+#define OA_TC6_DATA_HEADER_PARITY BIT(0)
+
+/* Data footer */
+#define OA_TC6_DATA_FOOTER_EXTENDED_STS BIT(31)
+#define OA_TC6_DATA_FOOTER_RXD_HEADER_BAD BIT(30)
+#define OA_TC6_DATA_FOOTER_CONFIG_SYNC BIT(29)
+#define OA_TC6_DATA_FOOTER_RX_CHUNKS GENMASK(28, 24)
+#define OA_TC6_DATA_FOOTER_DATA_VALID BIT(21)
+#define OA_TC6_DATA_FOOTER_START_VALID BIT(20)
+#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET GENMASK(19, 16)
+#define OA_TC6_DATA_FOOTER_END_VALID BIT(14)
+#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8)
+#define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
+
+#define OA_TC6_CTRL_HEADER_SIZE 4
+#define OA_TC6_CTRL_REG_VALUE_SIZE 4
+#define OA_TC6_CTRL_IGNORED_SIZE 4
+#define OA_TC6_CTRL_MAX_REGISTERS 128
+#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\
+ (OA_TC6_CTRL_MAX_REGISTERS *\
+ OA_TC6_CTRL_REG_VALUE_SIZE) +\
+ OA_TC6_CTRL_IGNORED_SIZE)
+#define OA_TC6_CHUNK_PAYLOAD_SIZE 64
+#define OA_TC6_DATA_HEADER_SIZE 4
+#define OA_TC6_CHUNK_SIZE (OA_TC6_DATA_HEADER_SIZE +\
+ OA_TC6_CHUNK_PAYLOAD_SIZE)
+#define OA_TC6_MAX_TX_CHUNKS 48
+#define OA_TC6_SPI_DATA_BUF_SIZE (OA_TC6_MAX_TX_CHUNKS *\
+ OA_TC6_CHUNK_SIZE)
+#define STATUS0_RESETC_POLL_DELAY 1000
+#define STATUS0_RESETC_POLL_TIMEOUT 1000000
+
+/* Internal structure for MAC-PHY drivers */
+struct oa_tc6 {
+ struct net_device *netdev;
+ struct phy_device *phydev;
+ struct mii_bus *mdiobus;
+ struct spi_device *spi;
+ struct mutex spi_ctrl_lock; /* Protects spi control transfer */
+ spinlock_t tx_skb_lock; /* Protects tx skb handling */
+ void *spi_ctrl_tx_buf;
+ void *spi_ctrl_rx_buf;
+ void *spi_data_tx_buf;
+ void *spi_data_rx_buf;
+ struct sk_buff *ongoing_tx_skb;
+ struct sk_buff *waiting_tx_skb;
+ struct sk_buff *rx_skb;
+ u16 tx_skb_offset;
+ u16 spi_data_tx_buf_offset;
+ u16 tx_credits;
+ u8 rx_chunks_available;
+ bool rx_buf_overflow;
+ bool int_flag;
+ bool disable_traffic;
+};
+
+enum oa_tc6_header_type {
+ OA_TC6_CTRL_HEADER,
+ OA_TC6_DATA_HEADER,
+};
+
+enum oa_tc6_register_op {
+ OA_TC6_CTRL_REG_READ = 0,
+ OA_TC6_CTRL_REG_WRITE = 1,
+};
+
+enum oa_tc6_data_valid_info {
+ OA_TC6_DATA_INVALID,
+ OA_TC6_DATA_VALID,
+};
+
+enum oa_tc6_data_start_valid_info {
+ OA_TC6_DATA_START_INVALID,
+ OA_TC6_DATA_START_VALID,
+};
+
+enum oa_tc6_data_end_valid_info {
+ OA_TC6_DATA_END_INVALID,
+ OA_TC6_DATA_END_VALID,
+};
+#endif /* OA_TC6_STD_DEF_H */
+
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 15f58e3c56c7..39b80033dfa9 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -7,9 +7,23 @@
* Author: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
*/
+#ifndef _LINUX_OA_TC6_H
+#define _LINUX_OA_TC6_H
+
#include <linux/etherdevice.h>
#include <linux/spi/spi.h>
+/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in
+ * the OPEN Alliance specification.
+ */
+#define OA_TC6_PHY_C45_MAC_MMS1 1 /* No MMD */
+#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */
+#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */
+#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
+#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
+#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */
+#define OA_TC6_PHY_C45_VS_MMS12 12 /* for vendors */
+
struct oa_tc6;
struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev);
@@ -22,3 +36,4 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
u8 length);
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb);
int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6);
+#endif /* _LINUX_OA_TC6_H */
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox