From: Christian Marangi <ansuelsmth@gmail.com>
To: Maxime Chevallier <maxime.chevallier@bootlin.com>,
Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Simon Horman <horms@kernel.org>, Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Christian Marangi <ansuelsmth@gmail.com>,
Lorenzo Bianconi <lorenzo@kernel.org>,
Heiner Kallweit <hkallweit1@gmail.com>,
Russell King <linux@armlinux.org.uk>,
Philipp Zabel <p.zabel@pengutronix.de>,
Nathan Chancellor <nathan@kernel.org>,
Nick Desaulniers <ndesaulniers@google.com>,
Bill Wendling <morbo@google.com>,
Justin Stitt <justinstitt@google.com>,
netdev@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, llvm@lists.linux.dev
Cc: Daniel Golle <daniel@makrotopia.org>
Subject: [PATCH v14 03/12] net: pcs: implement Firmware node support for PCS driver
Date: Thu, 13 Aug 2026 10:35:08 +0200 [thread overview]
Message-ID: <20260813083536.970196-4-ansuelsmth@gmail.com> (raw)
In-Reply-To: <20260813083536.970196-1-ansuelsmth@gmail.com>
Implement the foundation of Firmware node support for PCS driver.
To support this, implement a simple Provider API where a PCS driver can
expose multiple PCS with an xlate .fwnode_xlate function.
PCS driver will have to call fwnode_pcs_add_provider() and pass the
firmware node pointer and a xlate function to return the correct PCS for
the passed #pcs-cells.
This will register the PCS in a global list of providers so that
consumer can access it.
The consumer will then use fwnode_pcs_get() to get the actual PCS by
passing the firmware node pointer and the index for #pcs-cells.
For a simple implementation where #pcs-cells is 0 and the PCS driver
expose a single PCS, the xlate function fwnode_pcs_simple_xlate() is
provided.
For an advanced implementation a custom xlate function is required.
On removal the PCS driver must delete itself from the provider list
using fwnode_pcs_del_provider().
A devm variant devm_fwnode_pcs_add() is provided to automate the release
procedure.
Generic functions fwnode_phylink_pcs_count() and fwnode_phylink_pcs_parse()
are provided for MAC driver that will declare PCS in DT (or ACPI).
Function fwnode_phylink_pcs_count() will parse "pcs-handle" property and
will return the number of PCS entries described in the passed firmware
node.
Function fwnode_phylink_pcs_parse() will parse "pcs-handle" property and
fill the passed available_pcs array with the available PCS found up to
passed num_pcs value. It's worth to mention that this function will ignore
PCS that still needs to be probed (returning -ENODEV) and such PCS won't be
added to the available_pcs array.
Co-developed-by: Daniel Golle <daniel@makrotopia.org>
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/net/pcs/Kconfig | 6 +
drivers/net/pcs/Makefile | 1 +
drivers/net/pcs/pcs.c | 245 +++++++++++++++++++++++++++++++
include/linux/pcs/pcs-provider.h | 70 +++++++++
include/linux/pcs/pcs.h | 74 ++++++++++
5 files changed, 396 insertions(+)
create mode 100644 drivers/net/pcs/pcs.c
create mode 100644 include/linux/pcs/pcs-provider.h
create mode 100644 include/linux/pcs/pcs.h
diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
index e417fd66f660..ba2caec866a5 100644
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -5,6 +5,12 @@
menu "PCS device drivers"
+config FWNODE_PCS
+ bool "PCS Firmware Node"
+ depends on ACPI || OF || COMPILE_TEST
+ help
+ Firmware node PCS accessors
+
config PCS_XPCS
tristate "Synopsys DesignWare Ethernet XPCS"
select PHYLINK
diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
index 4f7920618b90..3005cdd89ab7 100644
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for Linux PCS drivers
+obj-$(CONFIG_FWNODE_PCS) += pcs.o
pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o \
pcs-xpcs-nxp.o pcs-xpcs-wx.o
diff --git a/drivers/net/pcs/pcs.c b/drivers/net/pcs/pcs.c
new file mode 100644
index 000000000000..39550d9a12a2
--- /dev/null
+++ b/drivers/net/pcs/pcs.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/phylink.h>
+#include <linux/pcs/pcs.h>
+#include <linux/pcs/pcs-provider.h>
+
+MODULE_DESCRIPTION("PCS library");
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_LICENSE("GPL");
+
+struct fwnode_pcs_provider {
+ struct list_head link;
+
+ struct fwnode_handle *fwnode;
+ struct phylink_pcs *(*fwnode_xlate)(struct fwnode_reference_args *pcsspec,
+ void *data);
+
+ void *data;
+};
+
+static LIST_HEAD(fwnode_pcs_providers);
+static DEFINE_MUTEX(fwnode_pcs_mutex);
+
+struct phylink_pcs *fwnode_pcs_simple_xlate(struct fwnode_reference_args *pcsspec,
+ void *data)
+{
+ return data;
+}
+EXPORT_SYMBOL_GPL(fwnode_pcs_simple_xlate);
+
+struct fwnode_pcs_provider *
+fwnode_pcs_add_provider(struct fwnode_handle *fwnode,
+ struct phylink_pcs *(*fwnode_xlate)(struct fwnode_reference_args *pcsspec,
+ void *data),
+ void *data)
+{
+ struct fwnode_pcs_provider *pp;
+
+ if (!fwnode)
+ return ERR_PTR(-EINVAL);
+
+ pp = kzalloc_obj(*pp);
+ if (!pp)
+ return ERR_PTR(-ENOMEM);
+
+ pp->fwnode = fwnode_handle_get(fwnode);
+ pp->data = data;
+ pp->fwnode_xlate = fwnode_xlate;
+
+ mutex_lock(&fwnode_pcs_mutex);
+
+ list_add(&pp->link, &fwnode_pcs_providers);
+ fwnode_dev_initialized(fwnode, true);
+
+ mutex_unlock(&fwnode_pcs_mutex);
+
+ pr_debug("Added pcs provider from %pfwf\n", fwnode);
+
+ return pp;
+}
+EXPORT_SYMBOL_GPL(fwnode_pcs_add_provider);
+
+void fwnode_pcs_del_provider(struct fwnode_pcs_provider *pp)
+{
+ if (IS_ERR_OR_NULL(pp))
+ return;
+
+ mutex_lock(&fwnode_pcs_mutex);
+
+ list_del(&pp->link);
+ fwnode_dev_initialized(pp->fwnode, false);
+
+ mutex_unlock(&fwnode_pcs_mutex);
+
+ fwnode_handle_put(pp->fwnode);
+ kfree(pp);
+}
+EXPORT_SYMBOL_GPL(fwnode_pcs_del_provider);
+
+static void devm_fwnode_pcs_del(struct device *dev, void *res)
+{
+ struct fwnode_pcs_provider *pp = *(struct fwnode_pcs_provider **)res;
+
+ fwnode_pcs_del_provider(pp);
+}
+
+struct fwnode_pcs_provider *
+devm_fwnode_pcs_add_provider(struct device *dev, struct fwnode_handle *fwnode,
+ struct phylink_pcs *(*fwnode_xlate)(struct fwnode_reference_args *pcsspec,
+ void *data),
+ void *data)
+{
+ struct fwnode_pcs_provider **ptr, *pp;
+
+ ptr = devres_alloc(devm_fwnode_pcs_del, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ pp = fwnode_pcs_add_provider(fwnode, fwnode_xlate, data);
+
+ if (!IS_ERR(pp)) {
+ *ptr = pp;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return pp;
+}
+EXPORT_SYMBOL_GPL(devm_fwnode_pcs_add_provider);
+
+static int fwnode_parse_pcsspec(const struct fwnode_handle *fwnode,
+ int index, const char *name,
+ struct fwnode_reference_args *out_args)
+{
+ if (!fwnode)
+ return -EINVAL;
+
+ if (name) {
+ index = fwnode_property_match_string(fwnode, "pcs-names",
+ name);
+ if (index < 0)
+ return index;
+ }
+
+ return fwnode_property_get_reference_args(fwnode, "pcs-handle",
+ "#pcs-cells", 0, index,
+ out_args);
+}
+
+static struct phylink_pcs *
+__fwnode_pcs_get_from_pcsspec_provider(struct fwnode_reference_args *pcsspec,
+ struct fwnode_pcs_provider *provider)
+{
+ if (provider->fwnode != pcsspec->fwnode)
+ return ERR_PTR(-EINVAL);
+
+ return provider->fwnode_xlate(pcsspec, provider->data);
+}
+
+static struct phylink_pcs *
+fwnode_pcs_get_from_pcsspec(struct fwnode_reference_args *pcsspec)
+{
+ struct fwnode_pcs_provider *provider;
+ struct phylink_pcs *pcs = NULL;
+
+ if (!pcsspec)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&fwnode_pcs_mutex);
+ list_for_each_entry(provider, &fwnode_pcs_providers, link) {
+ pcs = __fwnode_pcs_get_from_pcsspec_provider(pcsspec, provider);
+ if (!IS_ERR(pcs))
+ break;
+ }
+ mutex_unlock(&fwnode_pcs_mutex);
+
+ return !IS_ERR_OR_NULL(pcs) ? pcs : ERR_PTR(-ENODEV);
+}
+
+static struct phylink_pcs *__fwnode_pcs_get(const struct fwnode_handle *fwnode,
+ unsigned int index, const char *con_id)
+{
+ struct fwnode_reference_args pcsspec;
+ struct phylink_pcs *pcs;
+ int ret;
+
+ ret = fwnode_parse_pcsspec(fwnode, index, con_id, &pcsspec);
+ if (ret)
+ return ERR_PTR(ret);
+
+ pcs = fwnode_pcs_get_from_pcsspec(&pcsspec);
+ fwnode_handle_put(pcsspec.fwnode);
+
+ return pcs;
+}
+
+struct phylink_pcs *fwnode_pcs_get(const struct fwnode_handle *fwnode, unsigned int index)
+{
+ return __fwnode_pcs_get(fwnode, index, NULL);
+}
+EXPORT_SYMBOL_GPL(fwnode_pcs_get);
+
+unsigned int fwnode_phylink_pcs_count(struct fwnode_handle *fwnode)
+{
+ struct fwnode_reference_args out_args;
+ int index = 0;
+ int ret;
+
+ while (true) {
+ ret = fwnode_property_get_reference_args(fwnode, "pcs-handle",
+ "#pcs-cells", 0, index,
+ &out_args);
+ /* We expect to reach an -ENOENT error while counting */
+ if (ret)
+ break;
+
+ fwnode_handle_put(out_args.fwnode);
+ index++;
+ }
+
+ return index;
+}
+EXPORT_SYMBOL_GPL(fwnode_phylink_pcs_count);
+
+int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
+ struct phylink_pcs **available_pcs,
+ unsigned int num_pcs)
+{
+ unsigned int i, found = 0;
+
+ if (!available_pcs)
+ return -EINVAL;
+
+ if (!fwnode_property_present(fwnode, "pcs-handle"))
+ return -ENODEV;
+
+ for (i = 0; i < num_pcs; i++) {
+ struct phylink_pcs *pcs;
+
+ pcs = fwnode_pcs_get(fwnode, i);
+ if (IS_ERR(pcs)) {
+ /* Exit early if no PCS remain.*/
+ if (PTR_ERR(pcs) == -ENOENT)
+ break;
+
+ /*
+ * Ignore -ENODEV error for PCS that still
+ * needs to probe.
+ */
+ if (PTR_ERR(pcs) == -ENODEV)
+ continue;
+
+ return PTR_ERR(pcs);
+ }
+
+ available_pcs[found] = pcs;
+ found++;
+ }
+
+ return found;
+}
+EXPORT_SYMBOL_GPL(fwnode_phylink_pcs_parse);
diff --git a/include/linux/pcs/pcs-provider.h b/include/linux/pcs/pcs-provider.h
new file mode 100644
index 000000000000..15c198ffdd58
--- /dev/null
+++ b/include/linux/pcs/pcs-provider.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __LINUX_PCS_PROVIDER_H
+#define __LINUX_PCS_PROVIDER_H
+
+struct fwnode_pcs_provider;
+
+/**
+ * fwnode_pcs_simple_xlate - Simple xlate function to retrieve PCS
+ * @pcsspec: reference arguments
+ * @data: Context data (assumed assigned to the single PCS)
+ *
+ * Returns: the PCS pointed by data.
+ */
+struct phylink_pcs *fwnode_pcs_simple_xlate(struct fwnode_reference_args *pcsspec,
+ void *data);
+
+/**
+ * fwnode_pcs_add_provider - Registers a new PCS provider
+ * @fwnode: Firmware node
+ * @fwnode_xlate: xlate function to retrieve the PCS
+ * @data: Context data
+ *
+ * Register and add a new PCS provider to the global providers list
+ * for the firmware node. The relevant PCS from the PCS provider
+ * is retrieved from the passed fwnode_xlate function.
+ *
+ * The xlate function MUST return a pointer to an existing, already-allocated
+ * struct phylink_pcs and MUST NOT dynamically allocate a new PCS.
+ * The same PCS object must be returned for a given firmware reference and args,
+ * as PCS pointer identity is used to associate PCS objects with their provider.
+ *
+ * Returns: A pointer to the registered PCS provider on success, or
+ * an ERR_PTR() encoded error code on failure.
+ */
+struct fwnode_pcs_provider *
+fwnode_pcs_add_provider(struct fwnode_handle *fwnode,
+ struct phylink_pcs *(*fwnode_xlate)(struct fwnode_reference_args *pcsspec,
+ void *data),
+ void *data);
+
+/**
+ * fwnode_pcs_del_provider - Removes a PCS provider
+ * @pp: PCS provider returned by fwnode_pcs_add_provider()
+ */
+void fwnode_pcs_del_provider(struct fwnode_pcs_provider *pp);
+
+/**
+ * devm_fwnode_pcs_add_provider - Registers a new PCS provider
+ * @dev: Device of the PCS provider
+ * @fwnode: Firmware node
+ * @fwnode_xlate: xlate function to retrieve the PCS
+ * @data: Context data
+ *
+ * Register and add a new PCS provider to the global providers list
+ * for the firmware node. The relevant PCS from the PCS provider
+ * is retrieved from the passed fwnode_xlate function. While at that, it
+ * also associates the device with the PCS provider using devres.
+ * On driver detach, release function is invoked on the devres data,
+ * then, devres data is freed.
+ *
+ * Returns: A pointer to the registered PCS provider on success, or
+ * an ERR_PTR() encoded error code on failure.
+ */
+struct fwnode_pcs_provider *
+devm_fwnode_pcs_add_provider(struct device *dev, struct fwnode_handle *fwnode,
+ struct phylink_pcs *(*fwnode_xlate)(struct fwnode_reference_args *pcsspec,
+ void *data),
+ void *data);
+
+#endif /* __LINUX_PCS_PROVIDER_H */
diff --git a/include/linux/pcs/pcs.h b/include/linux/pcs/pcs.h
new file mode 100644
index 000000000000..ef2134c578c7
--- /dev/null
+++ b/include/linux/pcs/pcs.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __LINUX_PCS_H
+#define __LINUX_PCS_H
+
+#include <linux/phylink.h>
+
+#if IS_ENABLED(CONFIG_FWNODE_PCS)
+/**
+ * fwnode_pcs_get - Retrieves a PCS from a firmware node
+ * @fwnode: firmware node
+ * @index: index fwnode PCS handle in firmware node
+ *
+ * Get a PCS from the firmware node at index.
+ *
+ * Returns: a pointer to the phylink_pcs or a negative error pointer. Can
+ * return -ENODEV if the PCS is not present in global providers list (either
+ * due to driver still needs to be probed or it failed to probe/removed).
+ */
+struct phylink_pcs *fwnode_pcs_get(const struct fwnode_handle *fwnode,
+ unsigned int index);
+
+/**
+ * fwnode_phylink_pcs_count - Count PCS entries described in firmware node
+ * @fwnode: firmware node
+ *
+ * Helper function to count the number of PCS entries referenced by the
+ * "pcs-handle" property in a firmware node.
+ *
+ * Note that this function counts all PCS references in the firmware node,
+ * regardless of whether the corresponding PCS devices are already probed.
+ *
+ * Returns: number of PCS entries described in the firmware node.
+ */
+unsigned int fwnode_phylink_pcs_count(struct fwnode_handle *fwnode);
+
+/**
+ * fwnode_phylink_pcs_parse - Parse available PCS from firmware node
+ * @fwnode: firmware node
+ * @available_pcs: pointer to preallocated array of PCS
+ * @num_pcs: maximum number of PCS entries to scan
+ *
+ * Helper function that parses PCS references from the "pcs-handle"
+ * property of a firmware node and fills @available_pcs with PCS that are
+ * currently available up to @num_pcs.
+ *
+ * Only PCS that are currently available are stored in @available_pcs.
+ * PCS that returns -ENODEV are skipped.
+ *
+ * Returns: number of PCS stored in @available_pcs, or negative error code.
+ */
+int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
+ struct phylink_pcs **available_pcs,
+ unsigned int num_pcs);
+#else
+static inline struct phylink_pcs *fwnode_pcs_get(const struct fwnode_handle *fwnode,
+ unsigned int index)
+{
+ return ERR_PTR(-ENOENT);
+}
+
+static inline unsigned int fwnode_phylink_pcs_count(struct fwnode_handle *fwnode)
+{
+ return 0;
+}
+
+static inline int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
+ struct phylink_pcs **available_pcs,
+ unsigned int num_pcs)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+#endif /* __LINUX_PCS_H */
--
2.53.0
next prev parent reply other threads:[~2026-08-13 8:35 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-13 8:35 [PATCH v14 00/12] net: pcs: Introduce support for fwnode PCS Christian Marangi
2026-08-13 8:35 ` [PATCH v14 01/12] net: phylink: keep and use MAC supported_interfaces in phylink struct Christian Marangi
2026-08-13 8:35 ` [PATCH v14 02/12] net: phylink: introduce internal phylink PCS handling Christian Marangi
2026-08-14 8:36 ` sashiko-bot
2026-08-13 8:35 ` Christian Marangi [this message]
2026-08-14 8:36 ` [PATCH v14 03/12] net: pcs: implement Firmware node support for PCS driver sashiko-bot
2026-08-13 8:35 ` [PATCH v14 04/12] net: phylink: save phylink instance fwnode on phylink_create Christian Marangi
2026-08-13 8:35 ` [PATCH v14 05/12] net: phylink: support PCS provider release Christian Marangi
2026-08-14 8:36 ` sashiko-bot
2026-08-13 8:35 ` [PATCH v14 06/12] net: phylink: support late PCS provider attach Christian Marangi
2026-08-14 8:36 ` sashiko-bot
2026-08-13 8:35 ` [PATCH v14 07/12] net: Document PCS subsystem Christian Marangi
2026-08-13 8:35 ` [PATCH v14 08/12] MAINTAINERS: add myself as PCS subsystem maintainer Christian Marangi
2026-08-13 8:35 ` [PATCH v14 09/12] net: phylink: add .pcs_link_down PCS OP Christian Marangi
2026-08-13 8:35 ` [PATCH v14 10/12] dt-bindings: net: pcs: Document support for Airoha Ethernet PCS Christian Marangi
2026-08-13 8:35 ` [PATCH v14 11/12] net: pcs: airoha: add PCS driver for Airoha AN7581 SoC Christian Marangi
2026-08-14 8:36 ` sashiko-bot
2026-08-13 8:35 ` [PATCH v14 12/12] net: airoha: add phylink support Christian Marangi
2026-08-14 8:36 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260813083536.970196-4-ansuelsmth@gmail.com \
--to=ansuelsmth@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=conor+dt@kernel.org \
--cc=corbet@lwn.net \
--cc=daniel@makrotopia.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=hkallweit1@gmail.com \
--cc=horms@kernel.org \
--cc=justinstitt@google.com \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mediatek@lists.infradead.org \
--cc=linux@armlinux.org.uk \
--cc=llvm@lists.linux.dev \
--cc=lorenzo@kernel.org \
--cc=maxime.chevallier@bootlin.com \
--cc=morbo@google.com \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=netdev@vger.kernel.org \
--cc=p.zabel@pengutronix.de \
--cc=pabeni@redhat.com \
--cc=robh@kernel.org \
--cc=skhan@linuxfoundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.