| // 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"); |