root/drivers/net/phy/phy_package.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * PHY package support
 */

#include <linux/of.h>
#include <linux/phy.h>

#include "phylib.h"
#include "phylib-internal.h"

/**
 * struct phy_package_shared - Shared information in PHY packages
 * @base_addr: Base PHY address of PHY package used to combine PHYs
 *   in one package and for offset calculation of phy_package_read/write
 * @np: Pointer to the Device Node if PHY package defined in DT
 * @refcnt: Number of PHYs connected to this shared data
 * @flags: Initialization of PHY package
 * @priv_size: Size of the shared private data @priv
 * @priv: Driver private data shared across a PHY package
 *
 * Represents a shared structure between different phydev's in the same
 * package, for example a quad PHY. See phy_package_join() and
 * phy_package_leave().
 */
struct phy_package_shared {
        u8 base_addr;
        /* With PHY package defined in DT this points to the PHY package node */
        struct device_node *np;
        refcount_t refcnt;
        unsigned long flags;
        size_t priv_size;

        /* private data pointer */
        /* note that this pointer is shared between different phydevs and
         * the user has to take care of appropriate locking. It is allocated
         * and freed automatically by phy_package_join() and
         * phy_package_leave().
         */
        void *priv;
};

struct device_node *phy_package_get_node(struct phy_device *phydev)
{
        return phydev->shared->np;
}
EXPORT_SYMBOL_GPL(phy_package_get_node);

void *phy_package_get_priv(struct phy_device *phydev)
{
        return phydev->shared->priv;
}
EXPORT_SYMBOL_GPL(phy_package_get_priv);

static int phy_package_address(struct phy_device *phydev,
                               unsigned int addr_offset)
{
        struct phy_package_shared *shared = phydev->shared;
        u8 base_addr = shared->base_addr;

        if (addr_offset >= PHY_MAX_ADDR - base_addr)
                return -EIO;

        /* we know that addr will be in the range 0..31 and thus the
         * implicit cast to a signed int is not a problem.
         */
        return base_addr + addr_offset;
}

int __phy_package_read(struct phy_device *phydev, unsigned int addr_offset,
                       u32 regnum)
{
        int addr = phy_package_address(phydev, addr_offset);

        if (addr < 0)
                return addr;

        return __mdiobus_read(phydev->mdio.bus, addr, regnum);
}
EXPORT_SYMBOL_GPL(__phy_package_read);

int __phy_package_write(struct phy_device *phydev, unsigned int addr_offset,
                        u32 regnum, u16 val)
{
        int addr = phy_package_address(phydev, addr_offset);

        if (addr < 0)
                return addr;

        return __mdiobus_write(phydev->mdio.bus, addr, regnum, val);
}
EXPORT_SYMBOL_GPL(__phy_package_write);

/**
 * __phy_package_read_mmd - read MMD reg relative to PHY package base addr
 * @phydev: The phy_device struct
 * @addr_offset: The offset to be added to PHY package base_addr
 * @devad: The MMD to read from
 * @regnum: The register on the MMD to read
 *
 * Convenience helper for reading a register of an MMD on a given PHY
 * using the PHY package base address. The base address is added to
 * the addr_offset value.
 *
 * Same calling rules as for __phy_read();
 *
 * NOTE: It's assumed that the entire PHY package is either C22 or C45.
 */
int __phy_package_read_mmd(struct phy_device *phydev,
                           unsigned int addr_offset, int devad,
                           u32 regnum)
{
        int addr = phy_package_address(phydev, addr_offset);

        if (addr < 0)
                return addr;

        if (regnum > (u16)~0 || devad > 32)
                return -EINVAL;

        return mmd_phy_read(phydev->mdio.bus, addr, phydev->is_c45, devad,
                            regnum);
}
EXPORT_SYMBOL(__phy_package_read_mmd);

/**
 * __phy_package_write_mmd - write MMD reg relative to PHY package base addr
 * @phydev: The phy_device struct
 * @addr_offset: The offset to be added to PHY package base_addr
 * @devad: The MMD to write to
 * @regnum: The register on the MMD to write
 * @val: value to write to @regnum
 *
 * Convenience helper for writing a register of an MMD on a given PHY
 * using the PHY package base address. The base address is added to
 * the addr_offset value.
 *
 * Same calling rules as for __phy_write();
 *
 * NOTE: It's assumed that the entire PHY package is either C22 or C45.
 */
int __phy_package_write_mmd(struct phy_device *phydev,
                            unsigned int addr_offset, int devad,
                            u32 regnum, u16 val)
{
        int addr = phy_package_address(phydev, addr_offset);

        if (addr < 0)
                return addr;

        if (regnum > (u16)~0 || devad > 32)
                return -EINVAL;

        return mmd_phy_write(phydev->mdio.bus, addr, phydev->is_c45, devad,
                             regnum, val);
}
EXPORT_SYMBOL(__phy_package_write_mmd);

static bool __phy_package_set_once(struct phy_device *phydev, unsigned int b)
{
        struct phy_package_shared *shared = phydev->shared;

        if (!shared)
                return false;

        return !test_and_set_bit(b, &shared->flags);
}

bool phy_package_init_once(struct phy_device *phydev)
{
        return __phy_package_set_once(phydev, 0);
}
EXPORT_SYMBOL_GPL(phy_package_init_once);

bool phy_package_probe_once(struct phy_device *phydev)
{
        return __phy_package_set_once(phydev, 1);
}
EXPORT_SYMBOL_GPL(phy_package_probe_once);

/**
 * phy_package_join - join a common PHY group
 * @phydev: target phy_device struct
 * @base_addr: cookie and base PHY address of PHY package for offset
 *   calculation of global register access
 * @priv_size: if non-zero allocate this amount of bytes for private data
 *
 * This joins a PHY group and provides a shared storage for all phydevs in
 * this group. This is intended to be used for packages which contain
 * more than one PHY, for example a quad PHY transceiver.
 *
 * The base_addr parameter serves as cookie which has to have the same values
 * for all members of one group and as the base PHY address of the PHY package
 * for offset calculation to access generic registers of a PHY package.
 * Usually, one of the PHY addresses of the different PHYs in the package
 * provides access to these global registers.
 * The address which is given here, will be used in the __phy_package_read()
 * and __phy_package_write() convenience functions as base and added to the
 * passed offset in those functions.
 *
 * This will set the shared pointer of the phydev to the shared storage.
 * If this is the first call for a this cookie the shared storage will be
 * allocated. If priv_size is non-zero, the given amount of bytes are
 * allocated for the priv member.
 *
 * Returns < 1 on error, 0 on success. Esp. calling phy_package_join()
 * with the same cookie but a different priv_size is an error.
 */
int phy_package_join(struct phy_device *phydev, int base_addr, size_t priv_size)
{
        struct mii_bus *bus = phydev->mdio.bus;
        struct phy_package_shared *shared;
        int ret;

        if (base_addr < 0 || base_addr >= PHY_MAX_ADDR)
                return -EINVAL;

        mutex_lock(&bus->shared_lock);
        shared = bus->shared[base_addr];
        if (!shared) {
                ret = -ENOMEM;
                shared = kzalloc_obj(*shared);
                if (!shared)
                        goto err_unlock;
                if (priv_size) {
                        shared->priv = kzalloc(priv_size, GFP_KERNEL);
                        if (!shared->priv)
                                goto err_free;
                        shared->priv_size = priv_size;
                }
                shared->base_addr = base_addr;
                shared->np = NULL;
                refcount_set(&shared->refcnt, 1);
                bus->shared[base_addr] = shared;
        } else {
                ret = -EINVAL;
                if (priv_size && priv_size != shared->priv_size)
                        goto err_unlock;
                refcount_inc(&shared->refcnt);
        }
        mutex_unlock(&bus->shared_lock);

        phydev->shared = shared;

        return 0;

err_free:
        kfree(shared);
err_unlock:
        mutex_unlock(&bus->shared_lock);
        return ret;
}
EXPORT_SYMBOL_GPL(phy_package_join);

/**
 * of_phy_package_join - join a common PHY group in PHY package
 * @phydev: target phy_device struct
 * @priv_size: if non-zero allocate this amount of bytes for private data
 *
 * This is a variant of phy_package_join for PHY package defined in DT.
 *
 * The parent node of the @phydev is checked as a valid PHY package node
 * structure (by matching the node name "ethernet-phy-package") and the
 * base_addr for the PHY package is passed to phy_package_join.
 *
 * With this configuration the shared struct will also have the np value
 * filled to use additional DT defined properties in PHY specific
 * probe_once and config_init_once PHY package OPs.
 *
 * Returns < 0 on error, 0 on success. Esp. calling phy_package_join()
 * with the same cookie but a different priv_size is an error. Or a parent
 * node is not detected or is not valid or doesn't match the expected node
 * name for PHY package.
 */
int of_phy_package_join(struct phy_device *phydev, size_t priv_size)
{
        struct device_node *node = phydev->mdio.dev.of_node;
        struct device_node *package_node;
        u32 base_addr;
        int ret;

        if (!node)
                return -EINVAL;

        package_node = of_get_parent(node);
        if (!package_node)
                return -EINVAL;

        if (!of_node_name_eq(package_node, "ethernet-phy-package")) {
                ret = -EINVAL;
                goto exit;
        }

        if (of_property_read_u32(package_node, "reg", &base_addr)) {
                ret = -EINVAL;
                goto exit;
        }

        ret = phy_package_join(phydev, base_addr, priv_size);
        if (ret)
                goto exit;

        phydev->shared->np = package_node;

        return 0;
exit:
        of_node_put(package_node);
        return ret;
}
EXPORT_SYMBOL_GPL(of_phy_package_join);

/**
 * phy_package_leave - leave a common PHY group
 * @phydev: target phy_device struct
 *
 * This leaves a PHY group created by phy_package_join(). If this phydev
 * was the last user of the shared data between the group, this data is
 * freed. Resets the phydev->shared pointer to NULL.
 */
void phy_package_leave(struct phy_device *phydev)
{
        struct phy_package_shared *shared = phydev->shared;
        struct mii_bus *bus = phydev->mdio.bus;

        if (!shared)
                return;

        /* Decrease the node refcount on leave if present */
        if (shared->np)
                of_node_put(shared->np);

        if (refcount_dec_and_mutex_lock(&shared->refcnt, &bus->shared_lock)) {
                bus->shared[shared->base_addr] = NULL;
                mutex_unlock(&bus->shared_lock);
                kfree(shared->priv);
                kfree(shared);
        }

        phydev->shared = NULL;
}
EXPORT_SYMBOL_GPL(phy_package_leave);

static void devm_phy_package_leave(struct device *dev, void *res)
{
        phy_package_leave(*(struct phy_device **)res);
}

/**
 * devm_phy_package_join - resource managed phy_package_join()
 * @dev: device that is registering this PHY package
 * @phydev: target phy_device struct
 * @base_addr: cookie and base PHY address of PHY package for offset
 *   calculation of global register access
 * @priv_size: if non-zero allocate this amount of bytes for private data
 *
 * Managed phy_package_join(). Shared storage fetched by this function,
 * phy_package_leave() is automatically called on driver detach. See
 * phy_package_join() for more information.
 */
int devm_phy_package_join(struct device *dev, struct phy_device *phydev,
                          int base_addr, size_t priv_size)
{
        struct phy_device **ptr;
        int ret;

        ptr = devres_alloc(devm_phy_package_leave, sizeof(*ptr),
                           GFP_KERNEL);
        if (!ptr)
                return -ENOMEM;

        ret = phy_package_join(phydev, base_addr, priv_size);

        if (!ret) {
                *ptr = phydev;
                devres_add(dev, ptr);
        } else {
                devres_free(ptr);
        }

        return ret;
}
EXPORT_SYMBOL_GPL(devm_phy_package_join);

/**
 * devm_of_phy_package_join - resource managed of_phy_package_join()
 * @dev: device that is registering this PHY package
 * @phydev: target phy_device struct
 * @priv_size: if non-zero allocate this amount of bytes for private data
 *
 * Managed of_phy_package_join(). Shared storage fetched by this function,
 * phy_package_leave() is automatically called on driver detach. See
 * of_phy_package_join() for more information.
 */
int devm_of_phy_package_join(struct device *dev, struct phy_device *phydev,
                             size_t priv_size)
{
        struct phy_device **ptr;
        int ret;

        ptr = devres_alloc(devm_phy_package_leave, sizeof(*ptr),
                           GFP_KERNEL);
        if (!ptr)
                return -ENOMEM;

        ret = of_phy_package_join(phydev, priv_size);

        if (!ret) {
                *ptr = phydev;
                devres_add(dev, ptr);
        } else {
                devres_free(ptr);
        }

        return ret;
}
EXPORT_SYMBOL_GPL(devm_of_phy_package_join);

MODULE_DESCRIPTION("PHY package support");
MODULE_LICENSE("GPL");