diff options
Diffstat (limited to 'drivers/net/phy/phy_device.c')
-rw-r--r-- | drivers/net/phy/phy_device.c | 324 |
1 files changed, 242 insertions, 82 deletions
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index b4978c5fb2ca..1b9523595839 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -9,28 +9,29 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include <linux/kernel.h> -#include <linux/string.h> -#include <linux/errno.h> -#include <linux/unistd.h> -#include <linux/slab.h> -#include <linux/interrupt.h> -#include <linux/init.h> +#include <linux/bitmap.h> #include <linux/delay.h> -#include <linux/netdevice.h> +#include <linux/errno.h> #include <linux/etherdevice.h> -#include <linux/skbuff.h> +#include <linux/ethtool.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mdio.h> +#include <linux/mii.h> #include <linux/mm.h> #include <linux/module.h> -#include <linux/mii.h> -#include <linux/ethtool.h> -#include <linux/bitmap.h> +#include <linux/netdevice.h> #include <linux/phy.h> #include <linux/phy_led_triggers.h> +#include <linux/property.h> #include <linux/sfp.h> -#include <linux/mdio.h> -#include <linux/io.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <linux/string.h> #include <linux/uaccess.h> +#include <linux/unistd.h> MODULE_DESCRIPTION("PHY library"); MODULE_AUTHOR("Andy Fleming"); @@ -105,10 +106,9 @@ const int phy_10gbit_features_array[1] = { }; EXPORT_SYMBOL_GPL(phy_10gbit_features_array); -const int phy_10gbit_fec_features_array[1] = { +static const int phy_10gbit_fec_features_array[1] = { ETHTOOL_LINK_MODE_10000baseR_FEC_BIT, }; -EXPORT_SYMBOL_GPL(phy_10gbit_fec_features_array); __ETHTOOL_DECLARE_LINK_MODE_MASK(phy_10gbit_full_features) __ro_after_init; EXPORT_SYMBOL_GPL(phy_10gbit_full_features); @@ -226,7 +226,6 @@ static void phy_mdio_device_remove(struct mdio_device *mdiodev) } static struct phy_driver genphy_driver; -extern struct phy_driver genphy_c45_driver; static LIST_HEAD(phy_fixup_list); static DEFINE_MUTEX(phy_fixup_lock); @@ -661,6 +660,28 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id, } EXPORT_SYMBOL(phy_device_create); +/* phy_c45_probe_present - checks to see if a MMD is present in the package + * @bus: the target MII bus + * @prtad: PHY package address on the MII bus + * @devad: PHY device (MMD) address + * + * Read the MDIO_STAT2 register, and check whether a device is responding + * at this address. + * + * Returns: negative error number on bus access error, zero if no device + * is responding, or positive if a device is present. + */ +static int phy_c45_probe_present(struct mii_bus *bus, int prtad, int devad) +{ + int stat2; + + stat2 = mdiobus_c45_read(bus, prtad, devad, MDIO_STAT2); + if (stat2 < 0) + return stat2; + + return (stat2 & MDIO_STAT2_DEVPRST) == MDIO_STAT2_DEVPRST_VAL; +} + /* get_phy_c45_devs_in_pkg - reads a MMD's devices in package registers. * @bus: the target MII bus * @addr: PHY address on the MII bus @@ -687,9 +708,6 @@ static int get_phy_c45_devs_in_pkg(struct mii_bus *bus, int addr, int dev_addr, return -EIO; *devices_in_package |= phy_reg; - /* Bit 0 doesn't represent a device, it indicates c22 regs presence */ - *devices_in_package &= ~BIT(0); - return 0; } @@ -697,54 +715,78 @@ static int get_phy_c45_devs_in_pkg(struct mii_bus *bus, int addr, int dev_addr, * get_phy_c45_ids - reads the specified addr for its 802.3-c45 IDs. * @bus: the target MII bus * @addr: PHY address on the MII bus - * @phy_id: where to store the ID retrieved. * @c45_ids: where to store the c45 ID information. * - * If the PHY devices-in-package appears to be valid, it and the - * corresponding identifiers are stored in @c45_ids, zero is stored - * in @phy_id. Otherwise 0xffffffff is stored in @phy_id. Returns - * zero on success. + * Read the PHY "devices in package". If this appears to be valid, read + * the PHY identifiers for each device. Return the "devices in package" + * and identifiers in @c45_ids. * + * Returns zero on success, %-EIO on bus access error, or %-ENODEV if + * the "devices in package" is invalid. */ -static int get_phy_c45_ids(struct mii_bus *bus, int addr, u32 *phy_id, +static int get_phy_c45_ids(struct mii_bus *bus, int addr, struct phy_c45_device_ids *c45_ids) { const int num_ids = ARRAY_SIZE(c45_ids->device_ids); - u32 *devs = &c45_ids->devices_in_package; - int i, phy_reg; + u32 devs_in_pkg = 0; + int i, ret, phy_reg; /* Find first non-zero Devices In package. Device zero is reserved * for 802.3 c45 complied PHYs, so don't probe it at first. */ - for (i = 1; i < num_ids && *devs == 0; i++) { - phy_reg = get_phy_c45_devs_in_pkg(bus, addr, i, devs); - if (phy_reg < 0) - return -EIO; - - if ((*devs & 0x1fffffff) == 0x1fffffff) { - /* If mostly Fs, there is no device there, - * then let's continue to probe more, as some - * 10G PHYs have zero Devices In package, - * e.g. Cortina CS4315/CS4340 PHY. + for (i = 1; i < MDIO_MMD_NUM && (devs_in_pkg == 0 || + (devs_in_pkg & 0x1fffffff) == 0x1fffffff); i++) { + if (i == MDIO_MMD_VEND1 || i == MDIO_MMD_VEND2) { + /* Check that there is a device present at this + * address before reading the devices-in-package + * register to avoid reading garbage from the PHY. + * Some PHYs (88x3310) vendor space is not IEEE802.3 + * compliant. */ - phy_reg = get_phy_c45_devs_in_pkg(bus, addr, 0, devs); - if (phy_reg < 0) + ret = phy_c45_probe_present(bus, addr, i); + if (ret < 0) return -EIO; - /* no device there, let's get out of here */ - if ((*devs & 0x1fffffff) == 0x1fffffff) { - *phy_id = 0xffffffff; - return 0; - } else { - break; - } + + if (!ret) + continue; } + phy_reg = get_phy_c45_devs_in_pkg(bus, addr, i, &devs_in_pkg); + if (phy_reg < 0) + return -EIO; + } + + if ((devs_in_pkg & 0x1fffffff) == 0x1fffffff) { + /* If mostly Fs, there is no device there, then let's probe + * MMD 0, as some 10G PHYs have zero Devices In package, + * e.g. Cortina CS4315/CS4340 PHY. + */ + phy_reg = get_phy_c45_devs_in_pkg(bus, addr, 0, &devs_in_pkg); + if (phy_reg < 0) + return -EIO; + + /* no device there, let's get out of here */ + if ((devs_in_pkg & 0x1fffffff) == 0x1fffffff) + return -ENODEV; } /* Now probe Device Identifiers for each device present. */ for (i = 1; i < num_ids; i++) { - if (!(c45_ids->devices_in_package & (1 << i))) + if (!(devs_in_pkg & (1 << i))) continue; + if (i == MDIO_MMD_VEND1 || i == MDIO_MMD_VEND2) { + /* Probe the "Device Present" bits for the vendor MMDs + * to ignore these if they do not contain IEEE 802.3 + * registers. + */ + ret = phy_c45_probe_present(bus, addr, i); + if (ret < 0) + return ret; + + if (!ret) + continue; + } + phy_reg = mdiobus_c45_read(bus, addr, i, MII_PHYSID1); if (phy_reg < 0) return -EIO; @@ -755,34 +797,29 @@ static int get_phy_c45_ids(struct mii_bus *bus, int addr, u32 *phy_id, return -EIO; c45_ids->device_ids[i] |= phy_reg; } - *phy_id = 0; + + c45_ids->devices_in_package = devs_in_pkg; + /* Bit 0 doesn't represent a device, it indicates c22 regs presence */ + c45_ids->mmds_present = devs_in_pkg & ~BIT(0); + return 0; } /** - * get_phy_id - reads the specified addr for its ID. + * get_phy_c22_id - reads the specified addr for its clause 22 ID. * @bus: the target MII bus * @addr: PHY address on the MII bus * @phy_id: where to store the ID retrieved. - * @is_c45: If true the PHY uses the 802.3 clause 45 protocol - * @c45_ids: where to store the c45 ID information. - * - * Description: In the case of a 802.3-c22 PHY, reads the ID registers - * of the PHY at @addr on the @bus, stores it in @phy_id and returns - * zero on success. - * - * In the case of a 802.3-c45 PHY, get_phy_c45_ids() is invoked, and - * its return value is in turn returned. * + * Read the 802.3 clause 22 PHY ID from the PHY at @addr on the @bus, + * placing it in @phy_id. Return zero on successful read and the ID is + * valid, %-EIO on bus access error, or %-ENODEV if no device responds + * or invalid ID. */ -static int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id, - bool is_c45, struct phy_c45_device_ids *c45_ids) +static int get_phy_c22_id(struct mii_bus *bus, int addr, u32 *phy_id) { int phy_reg; - if (is_c45) - return get_phy_c45_ids(bus, addr, phy_id, c45_ids); - /* Grab the bits from PHYIR1, and put them in the upper half */ phy_reg = mdiobus_read(bus, addr, MII_PHYSID1); if (phy_reg < 0) { @@ -801,6 +838,10 @@ static int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id, *phy_id |= phy_reg; + /* If the phy_id is mostly Fs, there is no device there */ + if ((*phy_id & 0x1fffffff) == 0x1fffffff) + return -ENODEV; + return 0; } @@ -811,8 +852,17 @@ static int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id, * @addr: PHY address on the MII bus * @is_c45: If true the PHY uses the 802.3 clause 45 protocol * - * Description: Reads the ID registers of the PHY at @addr on the - * @bus, then allocates and returns the phy_device to represent it. + * Probe for a PHY at @addr on @bus. + * + * When probing for a clause 22 PHY, then read the ID registers. If we find + * a valid ID, allocate and return a &struct phy_device. + * + * When probing for a clause 45 PHY, read the "devices in package" registers. + * If the "devices in package" appears valid, read the ID registers for each + * MMD, allocate and return a &struct phy_device. + * + * Returns an allocated &struct phy_device on success, %-ENODEV if there is + * no PHY present, or %-EIO on bus access error. */ struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45) { @@ -821,16 +871,17 @@ struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45) int r; c45_ids.devices_in_package = 0; + c45_ids.mmds_present = 0; memset(c45_ids.device_ids, 0xff, sizeof(c45_ids.device_ids)); - r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids); + if (is_c45) + r = get_phy_c45_ids(bus, addr, &c45_ids); + else + r = get_phy_c22_id(bus, addr, &phy_id); + if (r) return ERR_PTR(r); - /* If the phy_id is mostly Fs, there is no device there */ - if ((phy_id & 0x1fffffff) == 0x1fffffff) - return ERR_PTR(-ENODEV); - return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids); } EXPORT_SYMBOL(get_phy_device); @@ -2663,6 +2714,104 @@ void phy_get_pause(struct phy_device *phydev, bool *tx_pause, bool *rx_pause) } EXPORT_SYMBOL(phy_get_pause); +#if IS_ENABLED(CONFIG_OF_MDIO) +static int phy_get_int_delay_property(struct device *dev, const char *name) +{ + s32 int_delay; + int ret; + + ret = device_property_read_u32(dev, name, &int_delay); + if (ret) + return ret; + + return int_delay; +} +#else +static int phy_get_int_delay_property(struct device *dev, const char *name) +{ + return -EINVAL; +} +#endif + +/** + * phy_get_delay_index - returns the index of the internal delay + * @phydev: phy_device struct + * @dev: pointer to the devices device struct + * @delay_values: array of delays the PHY supports + * @size: the size of the delay array + * @is_rx: boolean to indicate to get the rx internal delay + * + * Returns the index within the array of internal delay passed in. + * If the device property is not present then the interface type is checked + * if the interface defines use of internal delay then a 1 is returned otherwise + * a 0 is returned. + * The array must be in ascending order. If PHY does not have an ascending order + * array then size = 0 and the value of the delay property is returned. + * Return -EINVAL if the delay is invalid or cannot be found. + */ +s32 phy_get_internal_delay(struct phy_device *phydev, struct device *dev, + const int *delay_values, int size, bool is_rx) +{ + s32 delay; + int i; + + if (is_rx) { + delay = phy_get_int_delay_property(dev, "rx-internal-delay-ps"); + if (delay < 0 && size == 0) { + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) + return 1; + else + return 0; + } + + } else { + delay = phy_get_int_delay_property(dev, "tx-internal-delay-ps"); + if (delay < 0 && size == 0) { + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) + return 1; + else + return 0; + } + } + + if (delay < 0) + return delay; + + if (delay && size == 0) + return delay; + + if (delay < delay_values[0] || delay > delay_values[size - 1]) { + phydev_err(phydev, "Delay %d is out of range\n", delay); + return -EINVAL; + } + + if (delay == delay_values[0]) + return 0; + + for (i = 1; i < size; i++) { + if (delay == delay_values[i]) + return i; + + /* Find an approximate index by looking up the table */ + if (delay > delay_values[i - 1] && + delay < delay_values[i]) { + if (delay - delay_values[i - 1] < + delay_values[i] - delay) + return i - 1; + else + return i; + } + } + + phydev_err(phydev, "error finding internal delay index for %d\n", + delay); + + return -EINVAL; +} +EXPORT_SYMBOL(phy_get_internal_delay); + static bool phy_drv_supports_irq(struct phy_driver *phydrv) { return phydrv->config_intr && phydrv->ack_interrupt; @@ -2696,16 +2845,13 @@ static int phy_probe(struct device *dev) mutex_lock(&phydev->lock); - if (phydev->drv->probe) { - /* Deassert the reset signal */ - phy_device_reset(phydev, 0); + /* Deassert the reset signal */ + phy_device_reset(phydev, 0); + if (phydev->drv->probe) { err = phydev->drv->probe(phydev); - if (err) { - /* Assert the reset signal */ - phy_device_reset(phydev, 1); + if (err) goto out; - } } /* Start out supporting everything. Eventually, @@ -2767,6 +2913,10 @@ static int phy_probe(struct device *dev) phydev->state = PHY_READY; out: + /* Assert the reset signal */ + if (err) + phy_device_reset(phydev, 1); + mutex_unlock(&phydev->lock); return err; @@ -2785,12 +2935,12 @@ static int phy_remove(struct device *dev) sfp_bus_del_upstream(phydev->sfp_bus); phydev->sfp_bus = NULL; - if (phydev->drv && phydev->drv->remove) { + if (phydev->drv && phydev->drv->remove) phydev->drv->remove(phydev); - /* Assert the reset signal */ - phy_device_reset(phydev, 1); - } + /* Assert the reset signal */ + phy_device_reset(phydev, 1); + phydev->drv = NULL; return 0; @@ -2878,6 +3028,14 @@ static struct phy_driver genphy_driver = { .set_loopback = genphy_loopback, }; +static const struct ethtool_phy_ops phy_ethtool_phy_ops = { + .get_sset_count = phy_ethtool_get_sset_count, + .get_strings = phy_ethtool_get_strings, + .get_stats = phy_ethtool_get_stats, + .start_cable_test = phy_start_cable_test, + .start_cable_test_tdr = phy_start_cable_test_tdr, +}; + static int __init phy_init(void) { int rc; @@ -2886,6 +3044,7 @@ static int __init phy_init(void) if (rc) return rc; + ethtool_set_ethtool_phy_ops(&phy_ethtool_phy_ops); features_init(); rc = phy_driver_register(&genphy_c45_driver, THIS_MODULE); @@ -2907,6 +3066,7 @@ static void __exit phy_exit(void) phy_driver_unregister(&genphy_c45_driver); phy_driver_unregister(&genphy_driver); mdio_bus_exit(); + ethtool_set_ethtool_phy_ops(NULL); } subsys_initcall(phy_init); |