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.
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.
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.
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.h
diff --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 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..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)
+{
+ 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 */
--
2.48.1
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? > 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.h > > diff --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 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..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 > +{ > + 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 */
© 2016 - 2025 Red Hat, Inc.