Re: [net-next PATCH v4 05/11] net: pcs: implement Firmware node support for PCS driver
flat view
From: Sean Anderson <hidden>
Date: 2025-05-13 18:41:11
Also in:
linux-arm-kernel, linux-devicetree, linux-mediatek, lkml, llvm
On 5/11/25 16:12, Christian Marangi wrote:
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 .get 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_get() is provided. For an advanced implementation a custom xlate function is required.
There is no use case for this. All PCSs have a fwnode per PCS. Removing support for pcs cells will simplify the lookup code as well as the registration code and API.
One removal the PCS driver should first delete itself from the provider list using fwnode_pcs_del_provider() and then call phylink_release_pcs() on every PCS the driver provides.
And things like this can be done automatically.
A generic function fwnode_phylink_pcs_parse() is provided for MAC driver that will declare PCS in DT (or ACPI). This function will parse "pcs-handle" property and fill the passed array with the parsed PCS in availabel_pcs up to the passed num_pcs value. It's also possible to pass NULL as array to only parse the PCS and update the num_pcs value with the count of scanned PCS.
When is this useful?
quoted hunk ↗ jump to hunk
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 | 201 +++++++++++++++++++++++++++++++ include/linux/pcs/pcs-provider.h | 41 +++++++ include/linux/pcs/pcs.h | 56 +++++++++ 5 files changed, 305 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.hdiff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig index f6aa437473de..0d54bea1f663 100644 --- a/drivers/net/pcs/Kconfig +++ b/drivers/net/pcs/Kconfig@@ -5,6 +5,12 @@ menu "PCS device drivers" +config FWNODE_PCS + tristate + depends on (ACPI || OF) + help + Firmware node PCS accessors + config PCS_XPCS tristate "Synopsys DesignWare Ethernet XPCS" select PHYLINKdiff --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.odiff --git a/drivers/net/pcs/pcs.c b/drivers/net/pcs/pcs.c new file mode 100644 index 000000000000..26d07a2edfce --- /dev/null +++ b/drivers/net/pcs/pcs.c@@ -0,0 +1,201 @@ +// 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 *(*get)(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_get(struct fwnode_reference_args *pcsspec, + void *data) +{ + return data; +} +EXPORT_SYMBOL_GPL(fwnode_pcs_simple_get); + +int fwnode_pcs_add_provider(struct fwnode_handle *fwnode, + struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec, + void *data), + void *data) +{ + struct fwnode_pcs_provider *pp; + + if (!fwnode) + return 0; + + pp = kzalloc(sizeof(*pp), GFP_KERNEL); + if (!pp) + return -ENOMEM; + + pp->fwnode = fwnode_handle_get(fwnode); + pp->data = data; + pp->get = get; + + mutex_lock(&fwnode_pcs_mutex); + list_add(&pp->link, &fwnode_pcs_providers); + mutex_unlock(&fwnode_pcs_mutex); + pr_debug("Added pcs provider from %pfwf\n", fwnode); + + fwnode_dev_initialized(fwnode, true); + + return 0; +} +EXPORT_SYMBOL_GPL(fwnode_pcs_add_provider); + +void fwnode_pcs_del_provider(struct fwnode_handle *fwnode) +{ + struct fwnode_pcs_provider *pp; + + if (!fwnode) + return; + + mutex_lock(&fwnode_pcs_mutex); + list_for_each_entry(pp, &fwnode_pcs_providers, link) { + if (pp->fwnode == fwnode) { + list_del(&pp->link); + fwnode_dev_initialized(pp->fwnode, false); + fwnode_handle_put(pp->fwnode); + kfree(pp); + break; + } + } + mutex_unlock(&fwnode_pcs_mutex); +} +EXPORT_SYMBOL_GPL(fwnode_pcs_del_provider); + +static int fwnode_parse_pcsspec(const struct fwnode_handle *fwnode, int index, + const char *name, + struct fwnode_reference_args *out_args) +{ + int ret; + + if (!fwnode) + return -ENOENT; + + if (name) + index = fwnode_property_match_string(fwnode, "pcs-names", + name); + + ret = fwnode_property_get_reference_args(fwnode, "pcs-handle", + "#pcs-cells", + -1, index, out_args); + if (ret || (name && index < 0)) + return ret; + + return 0; +} + +static struct phylink_pcs * +fwnode_pcs_get_from_pcsspec(struct fwnode_reference_args *pcsspec) +{ + struct fwnode_pcs_provider *provider; + struct phylink_pcs *pcs = ERR_PTR(-EPROBE_DEFER); + + if (!pcsspec) + return ERR_PTR(-EINVAL); + + mutex_lock(&fwnode_pcs_mutex); + list_for_each_entry(provider, &fwnode_pcs_providers, link) { + if (provider->fwnode == pcsspec->fwnode) { + pcs = provider->get(pcsspec, provider->data); + if (!IS_ERR(pcs)) + break; + } + } + mutex_unlock(&fwnode_pcs_mutex); + + return pcs; +} + +static struct phylink_pcs *__fwnode_pcs_get(struct fwnode_handle *fwnode, + int index, const char *con_id)
Many existing drivers have to support non-standard PCS handle names (e.g. pcsphy-handle, phy-handle, etc.). Support for this is important for converting those drivers to use this system. --Sean
quoted hunk ↗ jump to hunk
+{ + 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(struct fwnode_handle *fwnode, int index) +{ + return __fwnode_pcs_get(fwnode, index, NULL); +} +EXPORT_SYMBOL_GPL(fwnode_pcs_get); + +static int fwnode_phylink_pcs_count(struct fwnode_handle *fwnode, + unsigned int *num_pcs) +{ + struct fwnode_reference_args out_args; + int index = 0; + int ret; + + while (true) { + ret = fwnode_property_get_reference_args(fwnode, "pcs-handle", + "#pcs-cells", + -1, index, &out_args); + /* We expect to reach an -ENOENT error while counting */ + if (ret) + break; + + fwnode_handle_put(out_args.fwnode); + index++; + } + + /* Update num_pcs with parsed PCS */ + *num_pcs = index; + + /* Return error if we didn't found any PCS */ + return index > 0 ? 0 : -ENOENT; +} + +int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode, + struct phylink_pcs **available_pcs, + unsigned int *num_pcs) +{ + int i; + + if (!fwnode_property_present(fwnode, "pcs-handle")) + return -ENODEV; + + /* With available_pcs NULL, only count the PCS */ + if (!available_pcs) + return fwnode_phylink_pcs_count(fwnode, num_pcs); + + for (i = 0; i < *num_pcs; i++) { + struct phylink_pcs *pcs; + + pcs = fwnode_pcs_get(fwnode, i); + if (IS_ERR(pcs)) + return PTR_ERR(pcs); + + available_pcs[i] = pcs; + } + + return 0; +} +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..ae51c108147e --- /dev/null +++ b/include/linux/pcs/pcs-provider.h@@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __LINUX_PCS_PROVIDER_H +#define __LINUX_PCS_PROVIDER_H + +/** + * fwnode_pcs_simple_get - 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_get(struct fwnode_reference_args *pcsspec, + void *data); + +/** + * fwnode_pcs_add_provider - Registers a new PCS provider + * @fwnode: Firmware node + * @get: xlate function to retrieve the PCS + * @data: Context data + * + * Register and add a new PCS to the global providers list + * for the firmware node. A function to get the PCS from + * firmware node with the use fwnode reference arguments. + * To the get function is also passed the interface type + * requested for the PHY. PCS driver will use the passed + * interface to understand if the PCS can support it or not. + * + * Returns: 0 on success or -ENOMEM on allocation failure. + */ +int fwnode_pcs_add_provider(struct fwnode_handle *fwnode, + struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec, + void *data), + void *data); + +/** + * fwnode_pcs_del_provider - Removes a PCS provider + * @fwnode: Firmware node + */ +void fwnode_pcs_del_provider(struct fwnode_handle *fwnode); + +#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..33244e3a442b --- /dev/null +++ b/include/linux/pcs/pcs.h@@ -0,0 +1,56 @@ +/* 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 -EPROBE_DEFER 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(struct fwnode_handle *fwnode, + int index); + +/** + * fwnode_phylink_pcs_parse - generic PCS parse for fwnode PCS provider + * @fwnode: firmware node + * @available_pcs: pointer to preallocated array of PCS + * @num_pcs: where to store count of parsed PCS + * + * Generic helper function to fill available_pcs array with PCS parsed + * from a "pcs-handle" fwnode property defined in firmware node up to + * passed num_pcs. + * + * If available_pcs is NULL, num_pcs is updated with the count of the + * parsed PCS. + * + * Returns: 0 or a negative error. + */ +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(struct fwnode_handle *fwnode, + int index) +{ + return ERR_PTR(-ENOENT); +} + +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 */