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 for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <86c22bb2-93db-4798-805b-3abda728741a@linux.dev>
Date: Mon, 19 May 2025 11:31:24 -0400
From: Sean Anderson <sean.anderson@...ux.dev>
To: Lei Wei <quic_leiwei@...cinc.com>, 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/14/25 12:18, Lei Wei wrote:
> 
> 
> 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?

I don't think this feature is too useful for a PCS so I disabled it. It's
of course possible to not do this if you think there is value.

--Sean

>> +    },
>> +};
>> +
>> +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