Backport pending PCS standalone feature for kernel 6.12 and all the required dependency patch. All affected patch automatically refreshed. Link: https://github.com/openwrt/openwrt/pull/23271 Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
385 lines
10 KiB
Diff
385 lines
10 KiB
Diff
From a90c644c73bbffd400cd3839fc17ffdfc69ea1e8 Mon Sep 17 00:00:00 2001
|
|
From: Christian Marangi <ansuelsmth@gmail.com>
|
|
Date: Mon, 17 Mar 2025 01:46:53 +0100
|
|
Subject: [PATCH 4/7] net: pcs: implement Firmware node support for PCS driver
|
|
|
|
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.
|
|
|
|
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 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 advanced implementation a custom xlate function is required.
|
|
|
|
PCS driver on removal should first delete as a provider with
|
|
the usage of fwnode_pcs_del_provider() and then call
|
|
phylink_release_pcs() on every PCS the driver provides and
|
|
|
|
A generic function fwnode_phylink_pcs_parse() is provided for any 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 | 7 ++
|
|
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, 306 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
|
|
|
|
--- a/drivers/net/pcs/Kconfig
|
|
+++ b/drivers/net/pcs/Kconfig
|
|
@@ -5,6 +5,13 @@
|
|
|
|
menu "PCS device drivers"
|
|
|
|
+config FWNODE_PCS
|
|
+ tristate
|
|
+ depends on (ACPI || OF)
|
|
+ depends on PHYLINK
|
|
+ help
|
|
+ Firmware node PCS accessors
|
|
+
|
|
config PCS_XPCS
|
|
tristate "Synopsys DesignWare Ethernet XPCS"
|
|
select PHYLINK
|
|
--- 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
|
|
|
|
--- /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 = -ENOENT;
|
|
+
|
|
+ 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);
|
|
--- /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
|
|
+ * @np: 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 */
|
|
--- /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 */
|