Skip to content

Commit

Permalink
net: pcs: Add helpers for registering and finding PCSs
Browse files Browse the repository at this point in the history
This adds support for getting PCS devices from the device tree. PCS
drivers must first register with phylink_register_pcs. After that, MAC
drivers may look up their PCS using phylink_get_pcs.

To prevent the PCS driver from leaving suddenly, we use try_module_get. To
provide some ordering during probing/removal, we use device links managed
by of_fwnode_add_links. This will reduce the number of probe failures due
to deferral. It will not prevent this for non-standard properties (aka
pcsphy-handle), but the worst that happens is that we re-probe a few times.

At the moment there is no support for specifying the interface used to
talk to the PCS. The MAC driver is expected to know how to talk to the
PCS. This is not a change, but it is perhaps an area for improvement.

Signed-off-by: Sean Anderson <sean.anderson@seco.com>
  • Loading branch information
sean-anderson-seco authored and intel-lab-lkp committed Jul 11, 2022
1 parent 3eb3b09 commit 344e04b
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 0 deletions.
1 change: 1 addition & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -7446,6 +7446,7 @@ F: include/linux/*mdio*.h
F: include/linux/mdio/*.h
F: include/linux/mii.h
F: include/linux/of_net.h
F: include/linux/pcs.h
F: include/linux/phy.h
F: include/linux/phy_fixed.h
F: include/linux/platform_data/mdio-bcm-unimac.h
Expand Down
12 changes: 12 additions & 0 deletions drivers/net/pcs/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@

menu "PCS device drivers"

config PCS
bool "PCS subsystem"
help
This provides common helper functions for registering and looking up
Physical Coding Sublayer (PCS) devices. PCS devices translate between
different interface types. In some use cases, they may either
translate between different types of Medium-Independent Interfaces
(MIIs), such as translating GMII to SGMII. This allows using a fast
serial interface to talk to the phy which translates the MII to the
Medium-Dependent Interface. Alternatively, they may translate a MII
directly to an MDI, such as translating GMII to 1000Base-X.

config PCS_XPCS
tristate "Synopsys DesignWare XPCS controller"
depends on MDIO_DEVICE && MDIO_BUS
Expand Down
2 changes: 2 additions & 0 deletions drivers/net/pcs/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for Linux PCS drivers

obj-$(CONFIG_PCS) += core.o

pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-nxp.o

obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
Expand Down
226 changes: 226 additions & 0 deletions drivers/net/pcs/core.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Sean Anderson <sean.anderson@seco.com>
*/

#include <linux/fwnode.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/pcs.h>
#include <linux/phylink.h>
#include <linux/property.h>

static LIST_HEAD(pcs_devices);
static DEFINE_MUTEX(pcs_mutex);

/**
* pcs_register() - register a new PCS
* @pcs: the PCS to register
*
* Registers a new PCS which can be automatically attached to a phylink.
*
* Return: 0 on success, or -errno on error
*/
int pcs_register(struct phylink_pcs *pcs)
{
if (!pcs->dev || !pcs->ops)
return -EINVAL;
if (!pcs->ops->pcs_an_restart || !pcs->ops->pcs_config ||
!pcs->ops->pcs_get_state)
return -EINVAL;

INIT_LIST_HEAD(&pcs->list);
mutex_lock(&pcs_mutex);
list_add(&pcs->list, &pcs_devices);
mutex_unlock(&pcs_mutex);
return 0;
}
EXPORT_SYMBOL_GPL(pcs_register);

/**
* pcs_unregister() - unregister a PCS
* @pcs: a PCS previously registered with pcs_register()
*/
void pcs_unregister(struct phylink_pcs *pcs)
{
mutex_lock(&pcs_mutex);
list_del(&pcs->list);
mutex_unlock(&pcs_mutex);
}
EXPORT_SYMBOL_GPL(pcs_unregister);

static void devm_pcs_release(struct device *dev, void *res)
{
pcs_unregister(*(struct phylink_pcs **)res);
}

/**
* devm_pcs_register - resource managed pcs_register()
* @dev: device that is registering this PCS
* @pcs: the PCS to register
*
* Managed pcs_register(). For PCSs registered by this function,
* pcs_unregister() is automatically called on driver detach. See
* pcs_register() for more information.
*
* Return: 0 on success, or -errno on failure
*/
int devm_pcs_register(struct device *dev, struct phylink_pcs *pcs)
{
struct phylink_pcs **pcsp;
int ret;

pcsp = devres_alloc(devm_pcs_release, sizeof(*pcsp),
GFP_KERNEL);
if (!pcsp)
return -ENOMEM;

ret = pcs_register(pcs);
if (ret) {
devres_free(pcsp);
return ret;
}

*pcsp = pcs;
devres_add(dev, pcsp);

return ret;
}
EXPORT_SYMBOL_GPL(devm_pcs_register);

/**
* pcs_find() - Find the PCS associated with a fwnode or device
* @fwnode: The PCS's fwnode
* @dev: The PCS's device
*
* Search PCSs registered with pcs_register() for one with a matching
* fwnode or device. Either @fwnode or @dev may be %NULL if matching against a
* fwnode or device is not desired (respectively).
*
* Return: a matching PCS, or %NULL if not found
*/
static struct phylink_pcs *pcs_find(const struct fwnode_handle *fwnode,
const struct device *dev)
{
struct phylink_pcs *pcs;

mutex_lock(&pcs_mutex);
list_for_each_entry(pcs, &pcs_devices, list) {
if (dev && pcs->dev == dev)
goto out;
if (fwnode && pcs->dev->fwnode == fwnode)
goto out;
}
pcs = NULL;

out:
mutex_unlock(&pcs_mutex);
pr_devel("%s: looking for %pfwf or %s %s...%s found\n", __func__,
fwnode, dev ? dev_driver_string(dev) : "(null)",
dev ? dev_name(dev) : "(null)", pcs ? " not" : "");
return pcs;
}

/**
* pcs_get_tail() - Finish getting a PCS
* @pcs: The PCS to get, or %NULL if one could not be found
*
* This performs common operations necessary when getting a PCS (chiefly
* incrementing reference counts)
*
* Return: @pcs, or an error pointer on failure
*/
static struct phylink_pcs *pcs_get_tail(struct phylink_pcs *pcs)
{
if (!pcs)
return ERR_PTR(-EPROBE_DEFER);

if (!try_module_get(pcs->ops->owner))
return ERR_PTR(-ENODEV);
get_device(pcs->dev);

return pcs;
}

/**
* _pcs_get_by_fwnode() - Get a PCS from a fwnode property
* @fwnode: The fwnode to get an associated PCS of
* @id: The name of the PCS to get. May be %NULL to get the first PCS.
* @optional: Whether the PCS is optional or not
*
* Look up a PCS associated with @fwnode and return a reference to it. Every
* call to pcs_get_by_fwnode() must be balanced with one to pcs_put().
*
* If @optional is true, and @id is non-%NULL, then if @id cannot be found in
* pcs-names, %NULL is returned (instead of an error). If @optional is true and
* @id is %NULL, then no error is returned if pcs-handle is absent.
*
* Return: a PCS if found, or an error pointer on failure
*/
struct phylink_pcs *_pcs_get_by_fwnode(const struct fwnode_handle *fwnode,
const char *id, bool optional)
{
int index;
struct phylink_pcs *pcs;
struct fwnode_handle *pcs_fwnode;

if (id)
index = fwnode_property_match_string(fwnode, "pcs-names", id);
else
index = 0;
if (index < 0) {
if (optional && (index == -EINVAL || index == -ENODATA))
return NULL;
return ERR_PTR(index);
}

/* First try pcs-handle, and if that doesn't work fall back to the
* (legacy) pcsphy-handle.
*/
pcs_fwnode = fwnode_find_reference(fwnode, "pcs-handle", index);
if (PTR_ERR(pcs_fwnode) == -ENOENT)
pcs_fwnode = fwnode_find_reference(fwnode, "pcsphy-handle",
index);
if (optional && !id && PTR_ERR(pcs_fwnode) == -ENOENT)
return NULL;
else if (IS_ERR(pcs_fwnode))
return ERR_CAST(pcs_fwnode);

pcs = pcs_find(pcs_fwnode, NULL);
fwnode_handle_put(pcs_fwnode);
return pcs_get_tail(pcs);
}
EXPORT_SYMBOL_GPL(pcs_get_by_fwnode);

/**
* pcs_get_by_provider() - Get a PCS from an existing provider
* @dev: The device providing the PCS
*
* This finds the first PCS registersed by @dev and returns a reference to it.
* Every call to pcs_get_by_provider() must be balanced with one to
* pcs_put().
*
* Return: a PCS if found, or an error pointer on failure
*/
struct phylink_pcs *pcs_get_by_provider(const struct device *dev)
{
return pcs_get_tail(pcs_find(NULL, dev));
}
EXPORT_SYMBOL_GPL(pcs_get_by_provider);

/**
* pcs_put() - Release a previously-acquired PCS
* @pcs: The PCS to put
*
* This frees resources associated with the PCS which were acquired when it was
* gotten.
*/
void pcs_put(struct phylink_pcs *pcs)
{
if (!pcs)
return;

put_device(pcs->dev);
module_put(pcs->ops->owner);
}
EXPORT_SYMBOL_GPL(pcs_put);
2 changes: 2 additions & 0 deletions drivers/of/property.c
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,7 @@ DEFINE_SIMPLE_PROP(pinctrl6, "pinctrl-6", NULL)
DEFINE_SIMPLE_PROP(pinctrl7, "pinctrl-7", NULL)
DEFINE_SIMPLE_PROP(pinctrl8, "pinctrl-8", NULL)
DEFINE_SIMPLE_PROP(remote_endpoint, "remote-endpoint", NULL)
DEFINE_SIMPLE_PROP(pcs_handle, "pcs-handle", NULL)
DEFINE_SIMPLE_PROP(pwms, "pwms", "#pwm-cells")
DEFINE_SIMPLE_PROP(resets, "resets", "#reset-cells")
DEFINE_SIMPLE_PROP(leds, "leds", NULL)
Expand Down Expand Up @@ -1406,6 +1407,7 @@ static const struct supplier_bindings of_supplier_bindings[] = {
{ .parse_prop = parse_pinctrl7, },
{ .parse_prop = parse_pinctrl8, },
{ .parse_prop = parse_remote_endpoint, .node_not_dev = true, },
{ .parse_prop = parse_pcs_handle, },
{ .parse_prop = parse_pwms, },
{ .parse_prop = parse_resets, },
{ .parse_prop = parse_leds, },
Expand Down
33 changes: 33 additions & 0 deletions include/linux/pcs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 Sean Anderson <sean.anderson@seco.com>
*/

#ifndef _PCS_H
#define _PCS_H

struct phylink_pcs;
struct fwnode;

int pcs_register(struct phylink_pcs *pcs);
void pcs_unregister(struct phylink_pcs *pcs);
int devm_pcs_register(struct device *dev, struct phylink_pcs *pcs);
struct phylink_pcs *_pcs_get_by_fwnode(const struct fwnode_handle *fwnode,
const char *id, bool optional);
struct phylink_pcs *pcs_get_by_provider(const struct device *dev);
void pcs_put(struct phylink_pcs *pcs);

static inline struct phylink_pcs
*pcs_get_by_fwnode(const struct fwnode_handle *fwnode,
const char *id)
{
return _pcs_get_by_fwnode(fwnode, id, false);
}

static inline struct phylink_pcs
*pcs_get_by_fwnode_optional(const struct fwnode_handle *fwnode, const char *id)
{
return _pcs_get_by_fwnode(fwnode, id, true);
}

#endif /* PCS_H */
6 changes: 6 additions & 0 deletions include/linux/phylink.h
Original file line number Diff line number Diff line change
Expand Up @@ -396,19 +396,24 @@ struct phylink_pcs_ops;

/**
* struct phylink_pcs - PHYLINK PCS instance
* @dev: the device associated with this PCS
* @ops: a pointer to the &struct phylink_pcs_ops structure
* @list: internal list of PCS devices
* @poll: poll the PCS for link changes
*
* This structure is designed to be embedded within the PCS private data,
* and will be passed between phylink and the PCS.
*/
struct phylink_pcs {
struct device *dev;
const struct phylink_pcs_ops *ops;
struct list_head list;
bool poll;
};

/**
* struct phylink_pcs_ops - MAC PCS operations structure.
* @owner: the module which implements this PCS.
* @pcs_validate: validate the link configuration.
* @pcs_get_state: read the current MAC PCS link state from the hardware.
* @pcs_config: configure the MAC PCS for the selected mode and state.
Expand All @@ -417,6 +422,7 @@ struct phylink_pcs {
* (where necessary).
*/
struct phylink_pcs_ops {
struct module *owner;
int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
const struct phylink_link_state *state);
void (*pcs_get_state)(struct phylink_pcs *pcs,
Expand Down

0 comments on commit 344e04b

Please sign in to comment.