lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <2f9ab3f6-6477-4d94-ba8a-1f2af865461e@quicinc.com>
Date: Thu, 15 May 2025 00:18:35 +0800
From: Lei Wei <quic_leiwei@...cinc.com>
To: Sean Anderson <sean.anderson@...ux.dev>, <netdev@...r.kernel.org>,
        Andrew
 Lunn <andrew+netdev@...n.ch>,
        "David S . Miller" <davem@...emloft.net>,
        Eric
 Dumazet <edumazet@...gle.com>, Jakub Kicinski <kuba@...nel.org>,
        Paolo Abeni
	<pabeni@...hat.com>, Russell King <linux@...linux.org.uk>
CC: <upstream@...oha.com>, Kory Maincent <kory.maincent@...tlin.com>,
        Simon
 Horman <horms@...nel.org>,
        Christian Marangi <ansuelsmth@...il.com>,
        <linux-kernel@...r.kernel.org>, Heiner Kallweit <hkallweit1@...il.com>,
        Michal Simek <michal.simek@....com>,
        Radhey Shyam Pandey
	<radhey.shyam.pandey@....com>,
        Robert Hancock <robert.hancock@...ian.com>,
        <linux-arm-kernel@...ts.infradead.org>
Subject: Re: [net-next PATCH v4 07/11] net: pcs: Add Xilinx PCS driver



On 5/13/2025 12:10 AM, Sean Anderson wrote:
> This adds support for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII device.
> This is a soft device which converts between GMII and either SGMII,
> 1000Base-X, or 2500Base-X. If configured correctly, it can also switch
> between SGMII and 1000BASE-X at runtime. Thoretically this is also possible
> for 2500Base-X, but that requires reconfiguring the serdes. The exact
> capabilities depend on synthesis parameters, so they are read from the
> devicetree.
> 
> This device has a c22-compliant PHY interface, so for the most part we can
> just use the phylink helpers. This device supports an interrupt which is
> triggered on autonegotiation completion. I'm not sure how useful this is,
> since we can never detect a link down (in the PCS).
> 
> This device supports sharing some logic between different implementations
> of the device. In this case, one device contains the "shared logic" and the
> clocks are connected to other devices. To coordinate this, one device
> registers a clock that the other devices can request.  The clock is enabled
> in the probe function by releasing the device from reset. There are no othe
> software controls, so the clock ops are empty.
> 
> Later in this series, we will convert the Xilinx AXI Ethernet driver to use
> this PCS. To help out, we provide a compatibility function to bind this
> driver in the event the MDIO device has no compatible.
> 
> Signed-off-by: Sean Anderson <sean.anderson@...ux.dev>
> ---
> 
> Changes in v4:
> - Re-add documentation for axienet_xilinx_pcs_get that was accidentally
>    removed
> 
> Changes in v3:
> - Adjust axienet_xilinx_pcs_get for changes to pcs_find_fwnode API
> - Call devm_pcs_register instead of devm_pcs_register_provider
> 
> Changes in v2:
> - Add support for #pcs-cells
> - Change compatible to just xlnx,pcs
> - Drop PCS_ALTERA_TSE which was accidentally added while rebasing
> - Rework xilinx_pcs_validate to just clear out half-duplex modes instead
>    of constraining modes based on the interface.
> 
>   MAINTAINERS                  |   6 +
>   drivers/net/pcs/Kconfig      |  21 ++
>   drivers/net/pcs/Makefile     |   2 +
>   drivers/net/pcs/pcs-xilinx.c | 488 +++++++++++++++++++++++++++++++++++
>   include/linux/pcs-xilinx.h   |  15 ++
>   5 files changed, 532 insertions(+)
>   create mode 100644 drivers/net/pcs/pcs-xilinx.c
>   create mode 100644 include/linux/pcs-xilinx.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 65f936521d65..4f41237b1f36 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -26454,6 +26454,12 @@ L:	netdev@...r.kernel.org
>   S:	Orphan
>   F:	drivers/net/ethernet/xilinx/ll_temac*
>   
> +XILINX PCS DRIVER
> +M:	Sean Anderson <sean.anderson@...ux.dev>
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/net/xilinx,pcs.yaml
> +F:	drivers/net/pcs/pcs-xilinx.c
> +
>   XILINX PWM DRIVER
>   M:	Sean Anderson <sean.anderson@...o.com>
>   S:	Maintained
> diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
> index ef3dc57da1b5..5c2209cc8b31 100644
> --- a/drivers/net/pcs/Kconfig
> +++ b/drivers/net/pcs/Kconfig
> @@ -51,4 +51,25 @@ config PCS_RZN1_MIIC
>   	  on RZ/N1 SoCs. This PCS converts MII to RMII/RGMII or can be set in
>   	  pass-through mode for MII.
>   
> +config PCS_XILINX
> +	depends on OF
> +	depends on GPIOLIB
> +	depends on COMMON_CLK
> +	depends on PCS
> +	select MDIO_DEVICE
> +	select PHYLINK
> +	tristate "Xilinx PCS driver"
> +	help
> +	  PCS driver for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII device.
> +	  This device can either act as a PCS+PMA for 1000BASE-X or 2500BASE-X,
> +	  or as a GMII-to-SGMII bridge. It can also switch between 1000BASE-X
> +	  and SGMII dynamically if configured correctly when synthesized.
> +	  Typical applications use this device on an FPGA connected to a GEM or
> +	  TEMAC on the GMII side. The other side is typically connected to
> +	  on-device gigabit transceivers, off-device SERDES devices using TBI,
> +	  or LVDS IO resources directly.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pcs-xilinx.
> +
>   endmenu
> diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
> index 35e3324fc26e..347afd91f034 100644
> --- a/drivers/net/pcs/Makefile
> +++ b/drivers/net/pcs/Makefile
> @@ -10,3 +10,5 @@ obj-$(CONFIG_PCS_XPCS)		+= pcs_xpcs.o
>   obj-$(CONFIG_PCS_LYNX)		+= pcs-lynx.o
>   obj-$(CONFIG_PCS_MTK_LYNXI)	+= pcs-mtk-lynxi.o
>   obj-$(CONFIG_PCS_RZN1_MIIC)	+= pcs-rzn1-miic.o
> +obj-$(CONFIG_PCS_ALTERA_TSE)	+= pcs-altera-tse.o
> +obj-$(CONFIG_PCS_XILINX)	+= pcs-xilinx.o
> diff --git a/drivers/net/pcs/pcs-xilinx.c b/drivers/net/pcs/pcs-xilinx.c
> new file mode 100644
> index 000000000000..cc42e2a22cd2
> --- /dev/null
> +++ b/drivers/net/pcs/pcs-xilinx.c
> @@ -0,0 +1,488 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2021-25 Sean Anderson <sean.anderson@...o.com>
> + *
> + * This is the driver for the Xilinx 1G/2.5G Ethernet PCS/PMA or SGMII LogiCORE
> + * IP. A typical setup will look something like
> + *
> + * MAC <--GMII--> PCS/PMA <--1000BASE-X--> SFP module (PMD)
> + *
> + * The IEEE model mostly describes this device, but the PCS layer has a
> + * separate sublayer for 8b/10b en/decoding:
> + *
> + * - When using a device-specific transceiver (serdes), the serdes handles 8b/10b
> + *   en/decoding and PMA functions. The IP implements other PCS functions.
> + * - When using LVDS IO resources, the IP implements PCS and PMA functions,
> + *   including 8b/10b en/decoding and (de)serialization.
> + * - When using an external serdes (accessed via TBI), the IP implements all
> + *   PCS functions, including 8b/10b en/decoding.
> + *
> + * The link to the PMD is not modeled by this driver, except for refclk. It is
> + * assumed that the serdes (if present) needs no configuration, though it
> + * should be fairly easy to add support. It is also possible to go from SGMII
> + * to GMII (PHY mode), but this is not supported.
> + *
> + * This driver was written with reference to PG047:
> + * https://docs.amd.com/r/en-US/pg047-gig-eth-pcs-pma
> + */
> +
> +#include <linux/bitmap.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/iopoll.h>
> +#include <linux/mdio.h>
> +#include <linux/of.h>
> +#include <linux/pcs.h>
> +#include <linux/pcs-xilinx.h>
> +#include <linux/phylink.h>
> +#include <linux/property.h>
> +
> +#include "../phy/phy-caps.h"
> +
> +/* Vendor-specific MDIO registers */
> +#define XILINX_PCS_ANICR 16 /* Auto-Negotiation Interrupt Control Register */
> +#define XILINX_PCS_SSR   17 /* Standard Selection Register */
> +
> +#define XILINX_PCS_ANICR_IE BIT(0) /* Interrupt Enable */
> +#define XILINX_PCS_ANICR_IS BIT(1) /* Interrupt Status */
> +
> +#define XILINX_PCS_SSR_SGMII BIT(0) /* Select SGMII standard */
> +
> +/**
> + * struct xilinx_pcs - Private data for Xilinx PCS devices
> + * @pcs: The phylink PCS
> + * @mdiodev: The mdiodevice used to access the PCS
> + * @refclk: The reference clock for the PMD
> + * @refclk_out: Optional reference clock for other PCSs using this PCS's shared
> + *              logic
> + * @reset: The reset line for the PCS
> + * @done: Optional GPIO for reset_done
> + * @irq: IRQ, or -EINVAL if polling
> + * @enabled: Set if @pcs.link_change is valid and we can call phylink_pcs_change()
> + */
> +struct xilinx_pcs {
> +	struct phylink_pcs pcs;
> +	struct clk_hw refclk_out;
> +	struct clk *refclk;
> +	struct gpio_desc *reset, *done;
> +	struct mdio_device *mdiodev;
> +	int irq;
> +	bool enabled;
> +};
> +
> +static inline struct xilinx_pcs *pcs_to_xilinx(struct phylink_pcs *pcs)
> +{
> +	return container_of(pcs, struct xilinx_pcs, pcs);
> +}
> +
> +static irqreturn_t xilinx_pcs_an_irq(int irq, void *dev_id)
> +{
> +	struct xilinx_pcs *xp = dev_id;
> +
> +	if (mdiodev_modify_changed(xp->mdiodev, XILINX_PCS_ANICR,
> +				   XILINX_PCS_ANICR_IS, 0) <= 0)
> +		return IRQ_NONE;
> +
> +	/* paired with xilinx_pcs_enable/disable; protects xp->pcs->link_change */
> +	if (smp_load_acquire(&xp->enabled))
> +		phylink_pcs_change(&xp->pcs, true);
> +	return IRQ_HANDLED;
> +}
> +
> +static int xilinx_pcs_enable(struct phylink_pcs *pcs)
> +{
> +	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
> +	struct device *dev = &xp->mdiodev->dev;
> +	int ret;
> +
> +	if (xp->irq < 0)
> +		return 0;
> +
> +	ret = mdiodev_modify(xp->mdiodev, XILINX_PCS_ANICR, 0,
> +			     XILINX_PCS_ANICR_IE);
> +	if (ret)
> +		dev_err(dev, "could not clear IRQ enable: %d\n", ret);
> +	else
> +		/* paired with xilinx_pcs_an_irq */
> +		smp_store_release(&xp->enabled, true);
> +	return ret;
> +}
> +
> +static void xilinx_pcs_disable(struct phylink_pcs *pcs)
> +{
> +	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
> +	struct device *dev = &xp->mdiodev->dev;
> +	int err;
> +
> +	if (xp->irq < 0)
> +		return;
> +
> +	WRITE_ONCE(xp->enabled, false);
> +	/* paired with xilinx_pcs_an_irq */
> +	smp_wmb();
> +
> +	err = mdiodev_modify(xp->mdiodev, XILINX_PCS_ANICR,
> +			     XILINX_PCS_ANICR_IE, 0);
> +	if (err)
> +		dev_err(dev, "could not clear IRQ enable: %d\n", err);
> +}
> +
> +static __ETHTOOL_DECLARE_LINK_MODE_MASK(half_duplex) __ro_after_init;
> +
> +static int xilinx_pcs_validate(struct phylink_pcs *pcs,
> +			       unsigned long *supported,
> +			       const struct phylink_link_state *state)
> +{
> +	linkmode_andnot(supported, supported, half_duplex);
> +	return 0;
> +}
> +
> +static void xilinx_pcs_get_state(struct phylink_pcs *pcs,
> +				 unsigned int neg_mode,
> +				 struct phylink_link_state *state)
> +{
> +	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
> +
> +	phylink_mii_c22_pcs_get_state(xp->mdiodev, neg_mode, state);
> +}
> +
> +static int xilinx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
> +			     phy_interface_t interface,
> +			     const unsigned long *advertising,
> +			     bool permit_pause_to_mac)
> +{
> +	int ret, changed = 0;
> +	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
> +
> +	if (test_bit(PHY_INTERFACE_MODE_SGMII, pcs->supported_interfaces) &&
> +	    test_bit(PHY_INTERFACE_MODE_1000BASEX, pcs->supported_interfaces)) {
> +		u16 ssr;
> +
> +		if (interface == PHY_INTERFACE_MODE_SGMII)
> +			ssr = XILINX_PCS_SSR_SGMII;
> +		else
> +			ssr = 0;
> +
> +		changed = mdiodev_modify_changed(xp->mdiodev, XILINX_PCS_SSR,
> +						 XILINX_PCS_SSR_SGMII, ssr);
> +		if (changed < 0)
> +			return changed;
> +	}
> +
> +	ret = phylink_mii_c22_pcs_config(xp->mdiodev, interface, advertising,
> +					 neg_mode);
> +	return ret ?: changed;
> +}
> +
> +static void xilinx_pcs_an_restart(struct phylink_pcs *pcs)
> +{
> +	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
> +
> +	phylink_mii_c22_pcs_an_restart(xp->mdiodev);
> +}
> +
> +static void xilinx_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
> +			       phy_interface_t interface, int speed, int duplex)
> +{
> +	int bmcr;
> +	struct xilinx_pcs *xp = pcs_to_xilinx(pcs);
> +
> +	if (phylink_autoneg_inband(mode))
> +		return;
> +
> +	bmcr = mdiodev_read(xp->mdiodev, MII_BMCR);
> +	if (bmcr < 0) {
> +		dev_err(&xp->mdiodev->dev, "could not read BMCR (err=%d)\n",
> +			bmcr);
> +		return;
> +	}
> +
> +	bmcr &= ~(BMCR_SPEED1000 | BMCR_SPEED100);
> +	switch (speed) {
> +	case SPEED_2500:
> +	case SPEED_1000:
> +		bmcr |= BMCR_SPEED1000;
> +		break;
> +	case SPEED_100:
> +		bmcr |= BMCR_SPEED100;
> +		break;
> +	case SPEED_10:
> +		bmcr |= BMCR_SPEED10;
> +		break;
> +	default:
> +		dev_err(&xp->mdiodev->dev, "invalid speed %d\n", speed);
> +	}
> +
> +	bmcr = mdiodev_write(xp->mdiodev, MII_BMCR, bmcr);
> +	if (bmcr < 0)
> +		dev_err(&xp->mdiodev->dev, "could not write BMCR (err=%d)\n",
> +			bmcr);
> +}
> +
> +static const struct phylink_pcs_ops xilinx_pcs_ops = {
> +	.pcs_validate = xilinx_pcs_validate,
> +	.pcs_enable = xilinx_pcs_enable,
> +	.pcs_disable = xilinx_pcs_disable,
> +	.pcs_get_state = xilinx_pcs_get_state,
> +	.pcs_config = xilinx_pcs_config,
> +	.pcs_an_restart = xilinx_pcs_an_restart,
> +	.pcs_link_up = xilinx_pcs_link_up,
> +};
> +
> +static const struct clk_ops xilinx_pcs_clk_ops = { };
> +
> +static const phy_interface_t xilinx_pcs_interfaces[] = {
> +	PHY_INTERFACE_MODE_SGMII,
> +	PHY_INTERFACE_MODE_1000BASEX,
> +	PHY_INTERFACE_MODE_2500BASEX,
> +};
> +
> +static int xilinx_pcs_probe(struct mdio_device *mdiodev)
> +{
> +	struct device *dev = &mdiodev->dev;
> +	struct fwnode_handle *fwnode = dev->fwnode;
> +	int ret, i, j, mode_count;
> +	struct xilinx_pcs *xp;
> +	const char **modes;
> +	u32 phy_id;
> +
> +	xp = devm_kzalloc(dev, sizeof(*xp), GFP_KERNEL);
> +	if (!xp)
> +		return -ENOMEM;
> +	xp->mdiodev = mdiodev;
> +	dev_set_drvdata(dev, xp);
> +
> +	xp->irq = fwnode_irq_get_byname(fwnode, "an");
> +	/* There's no _optional variant, so this is the best we've got */
> +	if (xp->irq < 0 && xp->irq != -EINVAL)
> +		return dev_err_probe(dev, xp->irq, "could not get IRQ\n");
> +
> +	mode_count = fwnode_property_string_array_count(fwnode,
> +							"xlnx,pcs-modes");
> +	if (!mode_count)
> +		mode_count = -ENODATA;
> +	if (mode_count < 0) {
> +		dev_err(dev, "could not read xlnx,pcs-modes: %d", mode_count);
> +		return mode_count;
> +	}
> +
> +	modes = kcalloc(mode_count, sizeof(*modes), GFP_KERNEL);
> +	if (!modes)
> +		return -ENOMEM;
> +
> +	ret = fwnode_property_read_string_array(fwnode, "xlnx,pcs-modes",
> +						modes, mode_count);
> +	if (ret < 0) {
> +		dev_err(dev, "could not read xlnx,pcs-modes: %d\n", ret);
> +		kfree(modes);
> +		return ret;
> +	}
> +
> +	for (i = 0; i < mode_count; i++) {
> +		for (j = 0; j < ARRAY_SIZE(xilinx_pcs_interfaces); j++) {
> +			if (!strcmp(phy_modes(xilinx_pcs_interfaces[j]), modes[i])) {
> +				__set_bit(xilinx_pcs_interfaces[j],
> +					  xp->pcs.supported_interfaces);
> +				goto next;
> +			}
> +		}
> +
> +		dev_err(dev, "invalid pcs-mode \"%s\"\n", modes[i]);
> +		kfree(modes);
> +		return -EINVAL;
> +next:
> +	}
> +
> +	kfree(modes);
> +	if ((test_bit(PHY_INTERFACE_MODE_SGMII, xp->pcs.supported_interfaces) ||
> +	     test_bit(PHY_INTERFACE_MODE_1000BASEX, xp->pcs.supported_interfaces)) &&
> +	    test_bit(PHY_INTERFACE_MODE_2500BASEX, xp->pcs.supported_interfaces)) {
> +		dev_err(dev,
> +			"Switching from SGMII or 1000Base-X to 2500Base-X not supported\n");
> +		return -EINVAL;
> +	}
> +
> +	xp->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> +	if (IS_ERR(xp->reset))
> +		return dev_err_probe(dev, PTR_ERR(xp->reset),
> +				     "could not get reset gpio\n");
> +
> +	xp->done = devm_gpiod_get_optional(dev, "done", GPIOD_IN);
> +	if (IS_ERR(xp->done))
> +		return dev_err_probe(dev, PTR_ERR(xp->done),
> +				     "could not get done gpio\n");
> +
> +	xp->refclk = devm_clk_get_optional_enabled(dev, "refclk");
> +	if (IS_ERR(xp->refclk))
> +		return dev_err_probe(dev, PTR_ERR(xp->refclk),
> +				     "could not get/enable reference clock\n");
> +
> +	gpiod_set_value_cansleep(xp->reset, 0);
> +	if (xp->done) {
> +		if (read_poll_timeout(gpiod_get_value_cansleep, ret, ret, 1000,
> +				      100000, true, xp->done))
> +			return dev_err_probe(dev, -ETIMEDOUT,
> +					     "timed out waiting for reset\n");
> +	} else {
> +		/* Just wait for a while and hope we're done */
> +		usleep_range(50000, 100000);
> +	}
> +
> +	if (fwnode_property_present(fwnode, "#clock-cells")) {
> +		const char *parent = "refclk";
> +		struct clk_init_data init = {
> +			.name = fwnode_get_name(fwnode),
> +			.ops = &xilinx_pcs_clk_ops,
> +			.parent_names = &parent,
> +			.num_parents = 1,
> +			.flags = 0,
> +		};
> +
> +		xp->refclk_out.init = &init;
> +		ret = devm_clk_hw_register(dev, &xp->refclk_out);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "could not register refclk\n");
> +
> +		ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
> +						  &xp->refclk_out);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "could not register refclk\n");
> +	}
> +
> +	/* Sanity check */
> +	ret = get_phy_c22_id(mdiodev->bus, mdiodev->addr, &phy_id);
> +	if (ret) {
> +		dev_err_probe(dev, ret, "could not read id\n");
> +		return ret;
> +	}
> +	if ((phy_id & 0xfffffff0) != 0x01740c00)
> +		dev_warn(dev, "unknown phy id %x\n", phy_id);
> +
> +	if (xp->irq < 0) {
> +		xp->pcs.poll = true;
> +	} else {
> +		/* The IRQ is enabled by default; turn it off */
> +		ret = mdiodev_write(xp->mdiodev, XILINX_PCS_ANICR, 0);
> +		if (ret) {
> +			dev_err(dev, "could not disable IRQ: %d\n", ret);
> +			return ret;
> +		}
> +
> +		/* Some PCSs have a bad habit of re-enabling their IRQ!
> +		 * Request the IRQ in probe so we don't end up triggering the
> +		 * spurious IRQ logic.
> +		 */
> +		ret = devm_request_threaded_irq(dev, xp->irq, NULL, xilinx_pcs_an_irq,
> +						IRQF_SHARED | IRQF_ONESHOT,
> +						dev_name(dev), xp);
> +		if (ret) {
> +			dev_err(dev, "could not request IRQ: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	xp->pcs.ops = &xilinx_pcs_ops;
> +	ret = devm_pcs_register(dev, &xp->pcs);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "could not register PCS\n");
> +
> +	if (xp->irq < 0)
> +		dev_info(dev, "probed with irq=poll\n");
> +	else
> +		dev_info(dev, "probed with irq=%d\n", xp->irq);
> +	return 0;
> +}
> +
> +static const struct of_device_id xilinx_pcs_of_match[] = {
> +	{ .compatible = "xlnx,pcs", },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, xilinx_pcs_of_match);
> +
> +static struct mdio_driver xilinx_pcs_driver = {
> +	.probe = xilinx_pcs_probe,
> +	.mdiodrv.driver = {
> +		.name = "xilinx-pcs",
> +		.of_match_table = of_match_ptr(xilinx_pcs_of_match),
> +		.suppress_bind_attrs = true,

Do we support pcs removal for this device through the sysfs method of
driver unbind?

> +	},
> +};
> +
> +static int __init xilinx_pcs_init(void)
> +{
> +	phy_caps_linkmodes(LINK_CAPA_10HD | LINK_CAPA_100HD | LINK_CAPA_1000HD,
> +			   half_duplex);
> +	return mdio_driver_register(&xilinx_pcs_driver);
> +}
> +module_init(xilinx_pcs_init);
> +
> +static void __exit xilinx_pcs_exit(void)
> +{
> +	mdio_driver_unregister(&xilinx_pcs_driver);
> +}
> +module_exit(xilinx_pcs_exit)
> +
> +static int axienet_xilinx_pcs_fixup(struct of_changeset *ocs,
> +				    struct device_node *np, void *data)
> +{
> +#ifdef CONFIG_OF_DYNAMIC
> +	unsigned int interface, mode_count, mode = 0;
> +	const unsigned long *interfaces = data;
> +	const char **modes;
> +	int ret;
> +
> +	mode_count = bitmap_weight(interfaces, PHY_INTERFACE_MODE_MAX);
> +	WARN_ON_ONCE(!mode_count);
> +	modes = kcalloc(mode_count, sizeof(*modes), GFP_KERNEL);
> +	if (!modes)
> +		return -ENOMEM;
> +
> +	for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX)
> +		modes[mode++] = phy_modes(interface);
> +	ret = of_changeset_add_prop_string_array(ocs, np, "xlnx,pcs-modes",
> +						 modes, mode_count);
> +	kfree(modes);
> +	if (ret)
> +		return ret;
> +
> +	return of_changeset_add_prop_string(ocs, np, "compatible",
> +					    "xlnx,pcs");
> +#else
> +	return -ENODEV;
> +#endif
> +}
> +
> +/**
> + * axienet_xilinx_pcs_get() - Compatibility function for the AXI Ethernet driver
> + * @dev: The MAC device
> + * @interfaces: The interfaces to use as a fallback
> + *
> + * This is a helper function for the AXI Ethernet driver to ensure backwards
> + * compatibility with device trees which do not include compatible strings for
> + * the PCS. It should not be used by new code.
> + *
> + * Return: a PCS, or an error pointer
> + */
> +struct phylink_pcs *axienet_xilinx_pcs_get(struct device *dev,
> +					   const unsigned long *interfaces)
> +{
> +	struct fwnode_handle *fwnode;
> +	struct phylink_pcs *pcs;
> +
> +	fwnode = pcs_find_fwnode(dev_fwnode(dev), NULL, "phy-handle", false);
> +	if (IS_ERR(fwnode))
> +		return ERR_CAST(fwnode);
> +
> +	pcs = pcs_get_by_fwnode_compat(dev, fwnode, axienet_xilinx_pcs_fixup,
> +				       (void *)interfaces);
> +	fwnode_handle_put(fwnode);
> +	return pcs;
> +}
> +EXPORT_SYMBOL_GPL(axienet_xilinx_pcs_get);
> +
> +MODULE_ALIAS("platform:xilinx-pcs");
> +MODULE_DESCRIPTION("Xilinx PCS driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/pcs-xilinx.h b/include/linux/pcs-xilinx.h
> new file mode 100644
> index 000000000000..28ff65226c3c
> --- /dev/null
> +++ b/include/linux/pcs-xilinx.h
> @@ -0,0 +1,15 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2024 Sean Anderson <sean.anderson@...o.com>
> + */
> +
> +#ifndef PCS_XILINX_H
> +#define PCS_XILINX_H
> +
> +struct device;
> +struct phylink_pcs;
> +
> +struct phylink_pcs *axienet_xilinx_pcs_get(struct device *dev,
> +					   const unsigned long *interfaces);
> +
> +#endif /* PCS_XILINX_H */


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ