@@ -5,6 +5,13 @@
menu "PCS device drivers"
+config OF_PCS
+ tristate
+ depends on OF
+ depends on PHYLINK
+ help
+ OpenFirmware PCS accessors
+
config PCS_XPCS
tristate "Synopsys DesignWare Ethernet XPCS"
select PHYLINK
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for Linux PCS drivers
+obj-$(CONFIG_OF_PCS) += pcs.o
pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o \
pcs-xpcs-nxp.o pcs-xpcs-wx.o
new file mode 100644
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/phylink.h>
+#include <linux/pcs/pcs.h>
+#include <linux/pcs/pcs-provider.h>
+
+struct of_pcs_provider {
+ struct list_head link;
+
+ struct device_node *node;
+ struct phylink_pcs *(*get)(struct of_phandle_args *pcsspec,
+ void *data,
+ phy_interface_t interface);
+
+ void *data;
+};
+
+static LIST_HEAD(of_pcs_providers);
+static DEFINE_MUTEX(of_pcs_mutex);
+
+struct phylink_pcs *of_pcs_simple_get(struct of_phandle_args *pcsspec, void *data,
+ phy_interface_t interface)
+{
+ struct phylink_pcs *pcs = data;
+
+ if (!pcs_supports_interface(pcs, interface))
+ return ERR_PTR(-EOPNOTSUPP);
+
+ return data;
+}
+EXPORT_SYMBOL_GPL(of_pcs_simple_get);
+
+int of_pcs_add_provider(struct device_node *np,
+ struct phylink_pcs *(*get)(struct of_phandle_args *pcsspec,
+ void *data,
+ phy_interface_t interface),
+ void *data)
+{
+ struct of_pcs_provider *pp;
+
+ if (!np)
+ return 0;
+
+ pp = kzalloc(sizeof(*pp), GFP_KERNEL);
+ if (!pp)
+ return -ENOMEM;
+
+ pp->node = of_node_get(np);
+ pp->data = data;
+ pp->get = get;
+
+ mutex_lock(&of_pcs_mutex);
+ list_add(&pp->link, &of_pcs_providers);
+ mutex_unlock(&of_pcs_mutex);
+ pr_debug("Added pcs provider from %pOF\n", np);
+
+ fwnode_dev_initialized(&np->fwnode, true);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_pcs_add_provider);
+
+void of_pcs_del_provider(struct device_node *np)
+{
+ struct of_pcs_provider *pp;
+
+ if (!np)
+ return;
+
+ mutex_lock(&of_pcs_mutex);
+ list_for_each_entry(pp, &of_pcs_providers, link) {
+ if (pp->node == np) {
+ list_del(&pp->link);
+ fwnode_dev_initialized(&np->fwnode, false);
+ of_node_put(pp->node);
+ kfree(pp);
+ break;
+ }
+ }
+ mutex_unlock(&of_pcs_mutex);
+}
+EXPORT_SYMBOL_GPL(of_pcs_del_provider);
+
+static int of_parse_pcsspec(const struct device_node *np, int index,
+ const char *name, struct of_phandle_args *out_args)
+{
+ int ret = -ENOENT;
+
+ if (!np)
+ return -ENOENT;
+
+ if (name)
+ index = of_property_match_string(np, "pcs-names", name);
+
+ ret = of_parse_phandle_with_args(np, "pcs-handle", "#pcs-cells",
+ index, out_args);
+ if (ret || (name && index < 0))
+ return ret;
+
+ return 0;
+}
+
+static struct phylink_pcs *
+of_pcs_get_from_pcsspec(struct of_phandle_args *pcsspec,
+ phy_interface_t interface)
+{
+ struct of_pcs_provider *provider;
+ struct phylink_pcs *pcs = ERR_PTR(-EPROBE_DEFER);
+
+ if (!pcsspec)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&of_pcs_mutex);
+ list_for_each_entry(provider, &of_pcs_providers, link) {
+ if (provider->node == pcsspec->np) {
+ pcs = provider->get(pcsspec, provider->data,
+ interface);
+ if (!IS_ERR(pcs))
+ break;
+ }
+ }
+ mutex_unlock(&of_pcs_mutex);
+
+ return pcs;
+}
+
+static struct phylink_pcs *__of_pcs_get(struct device_node *np, int index,
+ const char *con_id,
+ phy_interface_t interface)
+{
+ struct of_phandle_args pcsspec;
+ struct phylink_pcs *pcs;
+ int ret;
+
+ ret = of_parse_pcsspec(np, index, con_id, &pcsspec);
+ if (ret)
+ return ERR_PTR(ret);
+
+ pcs = of_pcs_get_from_pcsspec(&pcsspec, interface);
+ of_node_put(pcsspec.np);
+
+ return pcs;
+}
+
+struct phylink_pcs *of_pcs_get(struct device_node *np, int index,
+ phy_interface_t interface)
+{
+ return __of_pcs_get(np, index, NULL, interface);
+}
+EXPORT_SYMBOL_GPL(of_pcs_get);
+
+struct phylink_pcs *of_phylink_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface)
+{
+ int i, count;
+ struct device *dev = config->dev;
+ struct device_node *np = dev->of_node;
+ struct phylink_pcs *pcs = ERR_PTR(-ENODEV);
+
+ /* To enable using_mac_select_pcs on phylink_create */
+ if (interface == PHY_INTERFACE_MODE_NA)
+ return NULL;
+
+ /* Reject configuring PCS with Internal mode */
+ if (interface == PHY_INTERFACE_MODE_INTERNAL)
+ return ERR_PTR(-EINVAL);
+
+ if (!of_property_present(np, "pcs-handle"))
+ return pcs;
+
+ count = of_count_phandle_with_args(np, "pcs-handle", "#pcs-cells");
+ if (count < 0)
+ return ERR_PTR(count);
+
+ for (i = 0; i < count; i++) {
+ pcs = of_pcs_get(np, i, interface);
+ if (!IS_ERR_OR_NULL(pcs))
+ return pcs;
+ }
+
+ return pcs;
+}
+EXPORT_SYMBOL_GPL(of_phylink_mac_select_pcs);
@@ -1130,6 +1130,27 @@ int phylink_pcs_pre_init(struct phylink *pl, struct phylink_pcs *pcs)
}
EXPORT_SYMBOL_GPL(phylink_pcs_pre_init);
+/**
+ * phylink_pcs_release() - release a PCS
+ * @pl: a pointer to &struct phylink_pcs
+ *
+ * PCS provider can use this to release a PCS from a phylink
+ * instance by stopping the attached netdev. This is only done
+ * if the PCS is actually attached to a phylink, otherwise is
+ * ignored.
+ */
+void phylink_pcs_release(struct phylink_pcs *pcs)
+{
+ struct phylink *pl = pcs->phylink;
+
+ if (pl) {
+ rtnl_lock();
+ dev_close(pl->netdev);
+ rtnl_unlock();
+ }
+}
+EXPORT_SYMBOL_GPL(phylink_pcs_release);
+
static void phylink_mac_config(struct phylink *pl,
const struct phylink_link_state *state)
{
new file mode 100644
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __LINUX_PCS_PROVIDER_H
+#define __LINUX_PCS_PROVIDER_H
+
+#include <linux/phy.h>
+
+/**
+ * of_pcs_simple_get - Simple xlate function to retrieve PCS
+ * @pcsspec: Phandle arguments
+ * @data: Context data (assumed assigned to the single PCS)
+ * @interface: requested PHY interface type for PCS
+ *
+ * Returns the PCS (pointed by data) or an -EOPNOTSUPP pointer
+ * if the PCS doesn't support the requested interface.
+ */
+struct phylink_pcs *of_pcs_simple_get(struct of_phandle_args *pcsspec, void *data,
+ phy_interface_t interface);
+
+/**
+ * of_pcs_add_provider - Registers a new PCS provider
+ * @np: Device node
+ * @get: xlate function to retrieve the PCS
+ * @data: Context data
+ *
+ * Register and add a new PCS to the global providers list
+ * for the device node. A function to get the PCS from
+ * device node with the use of phandle args.
+ * 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 of_pcs_add_provider(struct device_node *np,
+ struct phylink_pcs *(*get)(struct of_phandle_args *pcsspec,
+ void *data,
+ phy_interface_t interface),
+ void *data);
+
+/**
+ * of_pcs_del_provider - Removes a PCS provider
+ * @np: Device node
+ */
+void of_pcs_del_provider(struct device_node *np);
+
+#endif /* __LINUX_PCS_PROVIDER_H */
new file mode 100644
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __LINUX_PCS_H
+#define __LINUX_PCS_H
+
+#include <linux/phy.h>
+#include <linux/phylink.h>
+
+static inline bool pcs_supports_interface(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ return test_bit(interface, pcs->supported_interfaces);
+}
+
+#ifdef CONFIG_OF_PCS
+/**
+ * of_pcs_get - Retrieves a PCS from a device node
+ * @np: Device node
+ * @index: Index of PCS handle in Device Node
+ * @interface: requested PHY interface type for PCS
+ *
+ * Get a PCS for the requested PHY interface type from the
+ * device 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 *of_pcs_get(struct device_node *np, int index,
+ phy_interface_t interface);
+
+/**
+ * of_phylink_mac_select_pcs - Generic MAC select pcs for OF PCS provider
+ * @config: phylink config pointer
+ * @interface: requested PHY interface type for PCS
+ *
+ * Generic helper function to get a PCS from a "pcs-handle" OF property
+ * defined in device tree. Each phandle defined in "pcs-handle" will be
+ * tested until a PCS that supports the requested PHY interface is found.
+ *
+ * Returns a pointer to the selected PCS or an error pointer.
+ * Return NULL for PHY_INTERFACE_MODE_NA and a -EINVAL error pointer
+ * for PHY_INTERFACE_MODE_INTERNAL. It can also return -EPROBE_DEFER,
+ * refer to of_pcs_get for details about it.
+ */
+struct phylink_pcs *of_phylink_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface);
+#else
+static inline struct phylink_pcs *of_pcs_get(struct device_node *np, int index,
+ phy_interface_t interface)
+{
+ return PTR_ERR(-ENOENT);
+}
+
+static inline struct phylink_pcs *of_phylink_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface)
+{
+ return PTR_ERR(-EOPNOTSUPP);
+}
+#endif
+
+#endif /* __LINUX_PCS_H */
@@ -695,6 +695,8 @@ void phylink_pcs_change(struct phylink_pcs *, bool up);
int phylink_pcs_pre_init(struct phylink *pl, struct phylink_pcs *pcs);
+void phylink_pcs_release(struct phylink_pcs *pcs);
+
void phylink_start(struct phylink *);
void phylink_stop(struct phylink *);