root/drivers/net/phy/mdio_bus.c
// SPDX-License-Identifier: GPL-2.0+
/* MDIO Bus interface
 *
 * Author: Andy Fleming
 *
 * Copyright (c) 2004 Freescale Semiconductor, Inc.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/device.h>
#include <linux/errno.h>
#include <linux/ethtool.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mii.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/unistd.h>

#define CREATE_TRACE_POINTS
#include <trace/events/mdio.h>

static struct mdio_device *mdiobus_find_device(struct mii_bus *bus, int addr)
{
        bool addr_valid = addr >= 0 && addr < ARRAY_SIZE(bus->mdio_map);

        if (WARN_ONCE(!addr_valid, "addr %d out of range\n", addr))
                return NULL;

        return bus->mdio_map[addr];
}

struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr)
{
        struct mdio_device *mdiodev;

        mdiodev = mdiobus_find_device(bus, addr);
        if (!mdiodev)
                return NULL;

        if (!(mdiodev->flags & MDIO_DEVICE_FLAG_PHY))
                return NULL;

        return container_of(mdiodev, struct phy_device, mdio);
}
EXPORT_SYMBOL(mdiobus_get_phy);

bool mdiobus_is_registered_device(struct mii_bus *bus, int addr)
{
        return mdiobus_find_device(bus, addr) != NULL;
}
EXPORT_SYMBOL(mdiobus_is_registered_device);

static void mdiobus_stats_acct(struct mdio_bus_stats *stats, bool op, int ret)
{
        preempt_disable();
        u64_stats_update_begin(&stats->syncp);

        u64_stats_inc(&stats->transfers);
        if (ret < 0) {
                u64_stats_inc(&stats->errors);
                goto out;
        }

        if (op)
                u64_stats_inc(&stats->reads);
        else
                u64_stats_inc(&stats->writes);
out:
        u64_stats_update_end(&stats->syncp);
        preempt_enable();
}

/**
 * __mdiobus_read - Unlocked version of the mdiobus_read function
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to read
 *
 * Return: The register value if successful, negative error code on failure
 *
 * Read a MDIO bus register. Caller must hold the mdio bus lock.
 *
 * NOTE: MUST NOT be called from interrupt context.
 */
int __mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
{
        int retval;

        lockdep_assert_held_once(&bus->mdio_lock);

        if (addr >= PHY_MAX_ADDR)
                return -ENXIO;

        if (bus->read)
                retval = bus->read(bus, addr, regnum);
        else
                retval = -EOPNOTSUPP;

        trace_mdio_access(bus, 1, addr, regnum, retval, retval);
        mdiobus_stats_acct(&bus->stats[addr], true, retval);

        return retval;
}
EXPORT_SYMBOL(__mdiobus_read);

/**
 * __mdiobus_write - Unlocked version of the mdiobus_write function
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to write
 * @val: value to write to @regnum
 *
 * Return: Zero if successful, negative error code on failure
 *
 * Write a MDIO bus register. Caller must hold the mdio bus lock.
 *
 * NOTE: MUST NOT be called from interrupt context.
 */
int __mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
{
        int err;

        lockdep_assert_held_once(&bus->mdio_lock);

        if (addr >= PHY_MAX_ADDR)
                return -ENXIO;

        if (bus->write)
                err = bus->write(bus, addr, regnum, val);
        else
                err = -EOPNOTSUPP;

        trace_mdio_access(bus, 0, addr, regnum, val, err);
        mdiobus_stats_acct(&bus->stats[addr], false, err);

        return err;
}
EXPORT_SYMBOL(__mdiobus_write);

/**
 * __mdiobus_modify_changed - Unlocked version of the mdiobus_modify function
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to modify
 * @mask: bit mask of bits to clear
 * @set: bit mask of bits to set
 *
 * Return: 1 if the register was modified, 0 if no change was needed,
 *         negative on any error condition
 *
 * Read, modify, and if any change, write the register value back to the
 * device.
 *
 * NOTE: MUST NOT be called from interrupt context.
 */
int __mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
                             u16 mask, u16 set)
{
        int new, ret;

        ret = __mdiobus_read(bus, addr, regnum);
        if (ret < 0)
                return ret;

        new = (ret & ~mask) | set;
        if (new == ret)
                return 0;

        ret = __mdiobus_write(bus, addr, regnum, new);

        return ret < 0 ? ret : 1;
}
EXPORT_SYMBOL_GPL(__mdiobus_modify_changed);

/**
 * __mdiobus_c45_read - Unlocked version of the mdiobus_c45_read function
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @devad: device address to read
 * @regnum: register number to read
 *
 * Return: The register value if successful, negative error code on failure
 *
 * Read a MDIO bus register. Caller must hold the mdio bus lock.
 *
 * NOTE: MUST NOT be called from interrupt context.
 */
int __mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum)
{
        int retval;

        lockdep_assert_held_once(&bus->mdio_lock);

        if (addr >= PHY_MAX_ADDR)
                return -ENXIO;

        if (bus->read_c45)
                retval = bus->read_c45(bus, addr, devad, regnum);
        else
                retval = -EOPNOTSUPP;

        trace_mdio_access(bus, 1, addr, regnum, retval, retval);
        mdiobus_stats_acct(&bus->stats[addr], true, retval);

        return retval;
}
EXPORT_SYMBOL(__mdiobus_c45_read);

/**
 * __mdiobus_c45_write - Unlocked version of the mdiobus_write function
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @devad: device address to read
 * @regnum: register number to write
 * @val: value to write to @regnum
 *
 * Return: Zero if successful, negative error code on failure
 *
 * Write a MDIO bus register. Caller must hold the mdio bus lock.
 *
 * NOTE: MUST NOT be called from interrupt context.
 */
int __mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
                        u16 val)
{
        int err;

        lockdep_assert_held_once(&bus->mdio_lock);

        if (addr >= PHY_MAX_ADDR)
                return -ENXIO;

        if (bus->write_c45)
                err = bus->write_c45(bus, addr, devad, regnum, val);
        else
                err = -EOPNOTSUPP;

        trace_mdio_access(bus, 0, addr, regnum, val, err);
        mdiobus_stats_acct(&bus->stats[addr], false, err);

        return err;
}
EXPORT_SYMBOL(__mdiobus_c45_write);

/**
 * __mdiobus_c45_modify_changed - Unlocked version of the mdiobus_modify function
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @devad: device address to read
 * @regnum: register number to modify
 * @mask: bit mask of bits to clear
 * @set: bit mask of bits to set
 *
 * Return: 1 if the register was modified, 0 if no change was needed,
 *         negative on any error condition
 *
 * Read, modify, and if any change, write the register value back to the
 * device. Any error returns a negative number.
 *
 * NOTE: MUST NOT be called from interrupt context.
 */
static int __mdiobus_c45_modify_changed(struct mii_bus *bus, int addr,
                                        int devad, u32 regnum, u16 mask,
                                        u16 set)
{
        int new, ret;

        ret = __mdiobus_c45_read(bus, addr, devad, regnum);
        if (ret < 0)
                return ret;

        new = (ret & ~mask) | set;
        if (new == ret)
                return 0;

        ret = __mdiobus_c45_write(bus, addr, devad, regnum, new);

        return ret < 0 ? ret : 1;
}

/**
 * mdiobus_read_nested - Nested version of the mdiobus_read function
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to read
 *
 * Return: The register value if successful, negative error code on failure
 *
 * In case of nested MDIO bus access avoid lockdep false positives by
 * using mutex_lock_nested().
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
int mdiobus_read_nested(struct mii_bus *bus, int addr, u32 regnum)
{
        int retval;

        mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
        retval = __mdiobus_read(bus, addr, regnum);
        mutex_unlock(&bus->mdio_lock);

        return retval;
}
EXPORT_SYMBOL(mdiobus_read_nested);

/**
 * mdiobus_read - Convenience function for reading a given MII mgmt register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to read
 *
 * Return: The register value if successful, negative error code on failure
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum)
{
        int retval;

        mutex_lock(&bus->mdio_lock);
        retval = __mdiobus_read(bus, addr, regnum);
        mutex_unlock(&bus->mdio_lock);

        return retval;
}
EXPORT_SYMBOL(mdiobus_read);

/**
 * mdiobus_c45_read - Convenience function for reading a given MII mgmt register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @devad: device address to read
 * @regnum: register number to read
 *
 * Return: The register value if successful, negative error code on failure
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
int mdiobus_c45_read(struct mii_bus *bus, int addr, int devad, u32 regnum)
{
        int retval;

        mutex_lock(&bus->mdio_lock);
        retval = __mdiobus_c45_read(bus, addr, devad, regnum);
        mutex_unlock(&bus->mdio_lock);

        return retval;
}
EXPORT_SYMBOL(mdiobus_c45_read);

/**
 * mdiobus_c45_read_nested - Nested version of the mdiobus_c45_read function
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @devad: device address to read
 * @regnum: register number to read
 *
 * Return: The register value if successful, negative error code on failure
 *
 * In case of nested MDIO bus access avoid lockdep false positives by
 * using mutex_lock_nested().
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
int mdiobus_c45_read_nested(struct mii_bus *bus, int addr, int devad,
                            u32 regnum)
{
        int retval;

        mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
        retval = __mdiobus_c45_read(bus, addr, devad, regnum);
        mutex_unlock(&bus->mdio_lock);

        return retval;
}
EXPORT_SYMBOL(mdiobus_c45_read_nested);

/**
 * mdiobus_write_nested - Nested version of the mdiobus_write function
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to write
 * @val: value to write to @regnum
 *
 * Return: Zero if successful, negative error code on failure
 *
 * In case of nested MDIO bus access avoid lockdep false positives by
 * using mutex_lock_nested().
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
int mdiobus_write_nested(struct mii_bus *bus, int addr, u32 regnum, u16 val)
{
        int err;

        mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
        err = __mdiobus_write(bus, addr, regnum, val);
        mutex_unlock(&bus->mdio_lock);

        return err;
}
EXPORT_SYMBOL(mdiobus_write_nested);

/**
 * mdiobus_write - Convenience function for writing a given MII mgmt register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to write
 * @val: value to write to @regnum
 *
 * Return: Zero if successful, negative error code on failure
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val)
{
        int err;

        mutex_lock(&bus->mdio_lock);
        err = __mdiobus_write(bus, addr, regnum, val);
        mutex_unlock(&bus->mdio_lock);

        return err;
}
EXPORT_SYMBOL(mdiobus_write);

/**
 * mdiobus_c45_write - Convenience function for writing a given MII mgmt register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @devad: device address to read
 * @regnum: register number to write
 * @val: value to write to @regnum
 *
 * Return: Zero if successful, negative error code on failure
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
int mdiobus_c45_write(struct mii_bus *bus, int addr, int devad, u32 regnum,
                      u16 val)
{
        int err;

        mutex_lock(&bus->mdio_lock);
        err = __mdiobus_c45_write(bus, addr, devad, regnum, val);
        mutex_unlock(&bus->mdio_lock);

        return err;
}
EXPORT_SYMBOL(mdiobus_c45_write);

/**
 * mdiobus_c45_write_nested - Nested version of the mdiobus_c45_write function
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @devad: device address to read
 * @regnum: register number to write
 * @val: value to write to @regnum
 *
 * Return: Zero if successful, negative error code on failure
 *
 * In case of nested MDIO bus access avoid lockdep false positives by
 * using mutex_lock_nested().
 *
 * NOTE: MUST NOT be called from interrupt context,
 * because the bus read/write functions may wait for an interrupt
 * to conclude the operation.
 */
int mdiobus_c45_write_nested(struct mii_bus *bus, int addr, int devad,
                             u32 regnum, u16 val)
{
        int err;

        mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
        err = __mdiobus_c45_write(bus, addr, devad, regnum, val);
        mutex_unlock(&bus->mdio_lock);

        return err;
}
EXPORT_SYMBOL(mdiobus_c45_write_nested);

/*
 * __mdiobus_modify - Convenience function for modifying a given mdio device
 *      register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to write
 * @mask: bit mask of bits to clear
 * @set: bit mask of bits to set
 *
 * Return: 0 on success, negative on any error condition
 */
int __mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask,
                     u16 set)
{
        int err;

        err = __mdiobus_modify_changed(bus, addr, regnum, mask, set);

        return err < 0 ? err : 0;
}
EXPORT_SYMBOL_GPL(__mdiobus_modify);

/**
 * mdiobus_modify - Convenience function for modifying a given mdio device
 *      register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to write
 * @mask: bit mask of bits to clear
 * @set: bit mask of bits to set
 *
 * Return: 0 on success, negative on any error condition
 */
int mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask, u16 set)
{
        int err;

        mutex_lock(&bus->mdio_lock);
        err = __mdiobus_modify(bus, addr, regnum, mask, set);
        mutex_unlock(&bus->mdio_lock);

        return err;
}
EXPORT_SYMBOL_GPL(mdiobus_modify);

/**
 * mdiobus_c45_modify - Convenience function for modifying a given mdio device
 *      register
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @devad: device address to read
 * @regnum: register number to write
 * @mask: bit mask of bits to clear
 * @set: bit mask of bits to set
 *
 * Return: 0 on success, negative on any error condition
 */
int mdiobus_c45_modify(struct mii_bus *bus, int addr, int devad, u32 regnum,
                       u16 mask, u16 set)
{
        int err;

        mutex_lock(&bus->mdio_lock);
        err = __mdiobus_c45_modify_changed(bus, addr, devad, regnum,
                                           mask, set);
        mutex_unlock(&bus->mdio_lock);

        return err < 0 ? err : 0;
}
EXPORT_SYMBOL_GPL(mdiobus_c45_modify);

/**
 * mdiobus_modify_changed - Convenience function for modifying a given mdio
 *      device register and returning if it changed
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @regnum: register number to write
 * @mask: bit mask of bits to clear
 * @set: bit mask of bits to set
 *
 * Return: 1 if the register was modified, 0 if no change was needed,
 *         negative on any error condition
 */
int mdiobus_modify_changed(struct mii_bus *bus, int addr, u32 regnum,
                           u16 mask, u16 set)
{
        int err;

        mutex_lock(&bus->mdio_lock);
        err = __mdiobus_modify_changed(bus, addr, regnum, mask, set);
        mutex_unlock(&bus->mdio_lock);

        return err;
}
EXPORT_SYMBOL_GPL(mdiobus_modify_changed);

/**
 * mdiobus_c45_modify_changed - Convenience function for modifying a given mdio
 *      device register and returning if it changed
 * @bus: the mii_bus struct
 * @addr: the phy address
 * @devad: device address to read
 * @regnum: register number to write
 * @mask: bit mask of bits to clear
 * @set: bit mask of bits to set
 *
 * Return: 1 if the register was modified, 0 if no change was needed,
 *         negative on any error condition
 */
int mdiobus_c45_modify_changed(struct mii_bus *bus, int addr, int devad,
                               u32 regnum, u16 mask, u16 set)
{
        int err;

        mutex_lock(&bus->mdio_lock);
        err = __mdiobus_c45_modify_changed(bus, addr, devad, regnum, mask, set);
        mutex_unlock(&bus->mdio_lock);

        return err;
}
EXPORT_SYMBOL_GPL(mdiobus_c45_modify_changed);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MDIO bus/device layer");