From 09c56012a45df438b8e8dbd728c80e23db541497 Mon Sep 17 00:00:00 2001 From: Andrey Gusakov Date: Sun, 31 Jan 2016 19:10:07 -0800 Subject: net: Port bitbanged MDIO code from Linux kernel Port bitbanged MDIO framework and gpiolib MDIO bus driver that uses it from Linux kernel. Signed-off-by: Andrey Gusakov Signed-off-by: Andrey Smirnov Signed-off-by: Sascha Hauer --- drivers/net/phy/Kconfig | 15 +++ drivers/net/phy/Makefile | 2 + drivers/net/phy/mdio-bitbang.c | 228 ++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/mdio-gpio.c | 231 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 476 insertions(+) create mode 100644 drivers/net/phy/mdio-bitbang.c create mode 100644 drivers/net/phy/mdio-gpio.c (limited to 'drivers') diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index d0a02c1e40..d30f65b8e6 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -46,6 +46,21 @@ config MDIO_MVEBU ---help--- Driver for the MDIO bus found on Marvell EBU SoCs. +config MDIO_BITBANG + bool "Support for bitbanged MDIO buses" + ---help--- + This module implements the MDIO bus protocol in software, + for use by low level drivers that export the ability to + drive the relevant pins. + + If in doubt, say N. + +config MDIO_GPIO + bool "Support for GPIO lib-based bitbanged MDIO buses" + depends on MDIO_BITBANG && GPIOLIB + ---help--- + Supports GPIO lib-based MDIO busses. + endif endmenu diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 94b9be83ea..10732f8070 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_NATIONAL_PHY) += national.o obj-$(CONFIG_SMSC_PHY) += smsc.o obj-$(CONFIG_MDIO_MVEBU) += mdio-mvebu.o +obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o +obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o diff --git a/drivers/net/phy/mdio-bitbang.c b/drivers/net/phy/mdio-bitbang.c new file mode 100644 index 0000000000..4f610e375f --- /dev/null +++ b/drivers/net/phy/mdio-bitbang.c @@ -0,0 +1,228 @@ +/* + * Bitbanged MDIO support. + * + * Author: Scott Wood + * Copyright (c) 2007 Freescale Semiconductor + * + * Based on CPM2 MDIO code which is: + * + * Copyright (c) 2003 Intracom S.A. + * by Pantelis Antoniou + * + * 2005 (c) MontaVista Software, Inc. + * Vitaly Bordug + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include + +#define MDIO_READ 2 +#define MDIO_WRITE 1 + +#define MDIO_C45 (1<<15) +#define MDIO_C45_ADDR (MDIO_C45 | 0) +#define MDIO_C45_READ (MDIO_C45 | 3) +#define MDIO_C45_WRITE (MDIO_C45 | 1) + +#define MDIO_SETUP_TIME 10 +#define MDIO_HOLD_TIME 10 + +/* Minimum MDC period is 400 ns, plus some margin for error. MDIO_DELAY + * is done twice per period. + */ +#define MDIO_DELAY 250 + +/* The PHY may take up to 300 ns to produce data, plus some margin + * for error. + */ +#define MDIO_READ_DELAY 350 + +/* MDIO must already be configured as output. */ +static void mdiobb_send_bit(struct mdiobb_ctrl *ctrl, int val) +{ + const struct mdiobb_ops *ops = ctrl->ops; + + ops->set_mdio_data(ctrl, val); + ndelay(MDIO_DELAY); + ops->set_mdc(ctrl, 1); + ndelay(MDIO_DELAY); + ops->set_mdc(ctrl, 0); +} + +/* MDIO must already be configured as input. */ +static int mdiobb_get_bit(struct mdiobb_ctrl *ctrl) +{ + const struct mdiobb_ops *ops = ctrl->ops; + + ndelay(MDIO_DELAY); + ops->set_mdc(ctrl, 1); + ndelay(MDIO_READ_DELAY); + ops->set_mdc(ctrl, 0); + + return ops->get_mdio_data(ctrl); +} + +/* MDIO must already be configured as output. */ +static void mdiobb_send_num(struct mdiobb_ctrl *ctrl, u16 val, int bits) +{ + int i; + + for (i = bits - 1; i >= 0; i--) + mdiobb_send_bit(ctrl, (val >> i) & 1); +} + +/* MDIO must already be configured as input. */ +static u16 mdiobb_get_num(struct mdiobb_ctrl *ctrl, int bits) +{ + int i; + u16 ret = 0; + + for (i = bits - 1; i >= 0; i--) { + ret <<= 1; + ret |= mdiobb_get_bit(ctrl); + } + + return ret; +} + +/* Utility to send the preamble, address, and + * register (common to read and write). + */ +static void mdiobb_cmd(struct mdiobb_ctrl *ctrl, int op, u8 phy, u8 reg) +{ + const struct mdiobb_ops *ops = ctrl->ops; + int i; + + ops->set_mdio_dir(ctrl, 1); + + /* + * Send a 32 bit preamble ('1's) with an extra '1' bit for good + * measure. The IEEE spec says this is a PHY optional + * requirement. The AMD 79C874 requires one after power up and + * one after a MII communications error. This means that we are + * doing more preambles than we need, but it is safer and will be + * much more robust. + */ + + for (i = 0; i < 32; i++) + mdiobb_send_bit(ctrl, 1); + + /* send the start bit (01) and the read opcode (10) or write (10). + Clause 45 operation uses 00 for the start and 11, 10 for + read/write */ + mdiobb_send_bit(ctrl, 0); + if (op & MDIO_C45) + mdiobb_send_bit(ctrl, 0); + else + mdiobb_send_bit(ctrl, 1); + mdiobb_send_bit(ctrl, (op >> 1) & 1); + mdiobb_send_bit(ctrl, (op >> 0) & 1); + + mdiobb_send_num(ctrl, phy, 5); + mdiobb_send_num(ctrl, reg, 5); +} + +/* In clause 45 mode all commands are prefixed by MDIO_ADDR to specify the + lower 16 bits of the 21 bit address. This transfer is done identically to a + MDIO_WRITE except for a different code. To enable clause 45 mode or + MII_ADDR_C45 into the address. Theoretically clause 45 and normal devices + can exist on the same bus. Normal devices should ignore the MDIO_ADDR + phase. */ +static int mdiobb_cmd_addr(struct mdiobb_ctrl *ctrl, int phy, u32 addr) +{ + unsigned int dev_addr = (addr >> 16) & 0x1F; + unsigned int reg = addr & 0xFFFF; + mdiobb_cmd(ctrl, MDIO_C45_ADDR, phy, dev_addr); + + /* send the turnaround (10) */ + mdiobb_send_bit(ctrl, 1); + mdiobb_send_bit(ctrl, 0); + + mdiobb_send_num(ctrl, reg, 16); + + ctrl->ops->set_mdio_dir(ctrl, 0); + mdiobb_get_bit(ctrl); + + return dev_addr; +} + +static int mdiobb_read(struct mii_bus *bus, int phy, int reg) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + int ret, i; + + if (reg & MII_ADDR_C45) { + reg = mdiobb_cmd_addr(ctrl, phy, reg); + mdiobb_cmd(ctrl, MDIO_C45_READ, phy, reg); + } else + mdiobb_cmd(ctrl, MDIO_READ, phy, reg); + + ctrl->ops->set_mdio_dir(ctrl, 0); + + /* check the turnaround bit: the PHY should be driving it to zero, if this + * PHY is listed in phy_ignore_ta_mask as having broken TA, skip that + */ + if (mdiobb_get_bit(ctrl) != 0) { + /* PHY didn't drive TA low -- flush any bits it + * may be trying to send. + */ + for (i = 0; i < 32; i++) + mdiobb_get_bit(ctrl); + + return 0xffff; + } + + ret = mdiobb_get_num(ctrl, 16); + mdiobb_get_bit(ctrl); + return ret; +} + +static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + + if (reg & MII_ADDR_C45) { + reg = mdiobb_cmd_addr(ctrl, phy, reg); + mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, reg); + } else + mdiobb_cmd(ctrl, MDIO_WRITE, phy, reg); + + /* send the turnaround (10) */ + mdiobb_send_bit(ctrl, 1); + mdiobb_send_bit(ctrl, 0); + + mdiobb_send_num(ctrl, val, 16); + + ctrl->ops->set_mdio_dir(ctrl, 0); + mdiobb_get_bit(ctrl); + return 0; +} + +static int mdiobb_reset(struct mii_bus *bus) +{ + struct mdiobb_ctrl *ctrl = bus->priv; + if (ctrl->reset) + ctrl->reset(bus); + return 0; +} + +struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl) +{ + struct mii_bus *bus; + + bus = xzalloc(sizeof(*bus)); + + bus->read = mdiobb_read; + bus->write = mdiobb_write; + bus->reset = mdiobb_reset; + bus->priv = ctrl; + + return bus; +} +EXPORT_SYMBOL(alloc_mdio_bitbang); diff --git a/drivers/net/phy/mdio-gpio.c b/drivers/net/phy/mdio-gpio.c new file mode 100644 index 0000000000..a839f2dee8 --- /dev/null +++ b/drivers/net/phy/mdio-gpio.c @@ -0,0 +1,231 @@ +/* + * GPIO based MDIO bitbang driver. + * Supports OpenFirmware. + * + * (C) Copyright 2015 + * CogentEmbedded, Andrey Gusakov + * + * based on mvmdio driver from Linux + * Copyright (c) 2008 CSE Semaphore Belgium. + * by Laurent Pinchart + * + * Copyright (C) 2008, Paulius Zaleckas + * + * Based on earlier work by + * + * Copyright (c) 2003 Intracom S.A. + * by Pantelis Antoniou + * + * 2005 (c) MontaVista Software, Inc. + * Vitaly Bordug + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct mdio_gpio_info { + struct mdiobb_ctrl ctrl; + int mdc, mdio, mdo; + int mdc_active_low, mdio_active_low, mdo_active_low; +}; + +struct mdio_gpio_info *mdio_gpio_of_get_info(struct device_d *dev) +{ + int ret; + enum of_gpio_flags flags; + struct mdio_gpio_info *info; + + info = xzalloc(sizeof(*info)); + + ret = of_get_gpio_flags(dev->device_node, 0, &flags); + if (ret < 0) { + dev_dbg(dev, "failed to get MDC inforamtion from DT\n"); + goto free_info; + } + + info->mdc = ret; + info->mdc_active_low = flags & OF_GPIO_ACTIVE_LOW; + + ret = of_get_gpio_flags(dev->device_node, 1, &flags); + if (ret < 0) { + dev_dbg(dev, "failed to get MDIO inforamtion from DT\n"); + goto free_info; + } + + info->mdio = ret; + info->mdio_active_low = flags & OF_GPIO_ACTIVE_LOW; + + ret = of_get_gpio_flags(dev->device_node, 2, &flags); + if (ret > 0) { + dev_dbg(dev, "found MDO information in DT\n"); + info->mdo = ret; + info->mdo_active_low = flags & OF_GPIO_ACTIVE_LOW; + } + + return info; + +free_info: + free(info); + return ERR_PTR(ret); +} + +static void mdio_dir(struct mdiobb_ctrl *ctrl, int dir) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + if (bitbang->mdo) { + /* Separate output pin. Always set its value to high + * when changing direction. If direction is input, + * assume the pin serves as pull-up. If direction is + * output, the default value is high. + */ + gpio_set_value(bitbang->mdo, + 1 ^ bitbang->mdo_active_low); + return; + } + + if (dir) + gpio_direction_output(bitbang->mdio, + 1 ^ bitbang->mdio_active_low); + else + gpio_direction_input(bitbang->mdio); +} + +static int mdio_get(struct mdiobb_ctrl *ctrl) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + return gpio_get_value(bitbang->mdio) ^ + bitbang->mdio_active_low; +} + +static void mdio_set(struct mdiobb_ctrl *ctrl, int what) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + if (bitbang->mdo) + gpio_set_value(bitbang->mdo, + what ^ bitbang->mdo_active_low); + else + gpio_set_value(bitbang->mdio, + what ^ bitbang->mdio_active_low); +} + +static void mdc_set(struct mdiobb_ctrl *ctrl, int what) +{ + struct mdio_gpio_info *bitbang = + container_of(ctrl, struct mdio_gpio_info, ctrl); + + gpio_set_value(bitbang->mdc, what ^ bitbang->mdc_active_low); +} + +static struct mdiobb_ops mdio_gpio_ops = { + .set_mdc = mdc_set, + .set_mdio_dir = mdio_dir, + .set_mdio_data = mdio_set, + .get_mdio_data = mdio_get, +}; + +static int mdio_gpio_probe(struct device_d *dev) +{ + int ret; + struct device_node *np = dev->device_node; + struct mdio_gpio_info *info; + struct mii_bus *bus; + + info = mdio_gpio_of_get_info(dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->ctrl.ops = &mdio_gpio_ops; + + ret = gpio_request(info->mdc, "mdc"); + if (ret < 0) { + dev_dbg(dev, "failed to request MDC\n"); + goto free_info; + } + + ret = gpio_request(info->mdio, "mdio"); + if (ret < 0) { + dev_dbg(dev, "failed to request MDIO\n"); + goto free_mdc; + } + + if (info->mdo) { + ret = gpio_request(info->mdo, "mdo"); + if (ret < 0) { + dev_dbg(dev, "failed to request MDO\n"); + goto free_mdio; + } + + ret = gpio_direction_output(info->mdo, 1); + if (ret < 0) { + dev_dbg(dev, "failed to set MDO as output\n"); + goto free_mdo; + } + + ret = gpio_direction_input(info->mdio); + if (ret < 0) { + dev_dbg(dev, "failed to set MDIO as input\n"); + goto free_mdo; + } + } + + ret = gpio_direction_output(info->mdc, 0); + if (ret < 0) { + dev_dbg(dev, "failed to set MDC as output\n"); + goto free_mdo; + } + + bus = alloc_mdio_bitbang(&info->ctrl); + bus->parent = dev; + bus->dev.device_node = np; + + dev->priv = bus; + + ret = mdiobus_register(bus); + if (!ret) + return 0; + + free(bus); +free_mdo: + gpio_free(info->mdo); +free_mdc: + gpio_free(info->mdc); +free_mdio: + gpio_free(info->mdio); +free_info: + free(info); + return ret; +} + +static const struct of_device_id gpio_mdio_dt_ids[] = { + { .compatible = "virtual,mdio-gpio", }, + { /* sentinel */ } +}; + +static struct driver_d mdio_gpio_driver = { + .name = "mdio-gpio", + .probe = mdio_gpio_probe, + .of_compatible = DRV_OF_COMPAT(gpio_mdio_dt_ids), +}; +device_platform_driver(mdio_gpio_driver); -- cgit v1.2.3 From dfe0745ba2b2cf0796e6bfb199479c23de6dd1d4 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Sun, 31 Jan 2016 19:10:09 -0800 Subject: mdio_bus: Change dev_info to dev_dbg Change dev_info to dev_dbg in mdiobus_detect for displaying phy's registration status as it is in of_mdiobus_register_phy(). While that information is useful for debugging, user doesn't really need to see that information every time they call miitool for the first time. Signed-off-by: Sascha Hauer --- drivers/net/phy/mdio_bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 0959c45e62..54d2f8fae6 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -43,7 +43,7 @@ int mdiobus_detect(struct device_d *dev) ret = phy_register_device(phydev); if (ret) dev_err(dev, "failed to register phy: %s\n", strerror(-ret)); - dev_info(dev, "registered phy as /dev/%s\n", phydev->cdev.name); + dev_dbg(dev, "registered phy as /dev/%s\n", phydev->cdev.name); } return 0; -- cgit v1.2.3 From 109543cf88d9357e23d2be6381a044e84aa5ef1b Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Sun, 31 Jan 2016 19:10:10 -0800 Subject: mdio_bus: Add mdiobus_get_bus() function Add mdiobus_get_bus() -- a function to get a MDIO bus by its number Signed-off-by: Andrey Smirnov Signed-off-by: Sascha Hauer --- drivers/net/phy/mdio_bus.c | 19 +++++++++++++++++++ include/linux/phy.h | 2 ++ 2 files changed, 21 insertions(+) (limited to 'drivers') diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 54d2f8fae6..b74b27e991 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -187,6 +187,25 @@ struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr) } EXPORT_SYMBOL(mdiobus_scan); + +/** + * + * mdio_get_bus - get a MDIO bus from its busnum + * + * @param busnum the desired bus number + * + */ +struct mii_bus *mdiobus_get_bus(int busnum) +{ + struct mii_bus *mii; + + for_each_mii_bus(mii) + if (mii->dev.id == busnum) + return mii; + + return NULL; +} + /** * mdio_bus_match - determine if given PHY driver supports the given PHY device * @dev: target PHY device diff --git a/include/linux/phy.h b/include/linux/phy.h index 58e69da7bc..4e88936bad 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -114,6 +114,8 @@ int mdiobus_detect(struct device_d *dev); #define for_each_mii_bus(mii) \ list_for_each_entry(mii, &mii_bus_list, list) +struct mii_bus *mdiobus_get_bus(int busnum); + /** * mdiobus_read - Convenience function for reading a given MII mgmt register * @bus: the mii_bus struct -- cgit v1.2.3 From 993a28aa48f26da8d7e06ee4c3011c66c47df8c9 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Sun, 31 Jan 2016 19:10:12 -0800 Subject: mdio_bus: Change PHY's naming scheme Change the way PHY devices are named upon creation. This commit replaces sequentialy numbered "/dev/phy%d" with "/dev/mdio%d-phy%02x". This way it is significanlty easier to identify which PHY in real-life (e.g. on a schematic) corresponds to which device in /dev. Also, replace asprintf with xasprintf to provide some form of memory allocation failure checking. Signed-off-by: Andrey Smirnov Signed-off-by: Sascha Hauer --- drivers/net/phy/mdio_bus.c | 4 +++- drivers/net/phy/phy.c | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index b74b27e991..41bf018141 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -333,7 +333,9 @@ static int mdio_bus_probe(struct device_d *_dev) dev_add_param_int_ro(&dev->dev, "phy_addr", dev->addr, "%d"); dev_add_param_int_ro(&dev->dev, "phy_id", dev->phy_id, "0x%08x"); - dev->cdev.name = asprintf("phy%d", _dev->id); + dev->cdev.name = xasprintf("mdio%d-phy%02x", + dev->bus->dev.id, + dev->addr); dev->cdev.size = 64; dev->cdev.ops = &phydev_ops; dev->cdev.priv = dev; diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 25c999c550..d128a5e8d5 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -168,8 +168,10 @@ static struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int p phydev->bus = bus; phydev->dev.bus = &mdio_bus_type; - strcpy(phydev->dev.name, "phy"); - phydev->dev.id = DEVICE_ID_DYNAMIC; + sprintf(phydev->dev.name, "mdio%d-phy%02x", + phydev->bus->dev.id, + phydev->addr); + phydev->dev.id = DEVICE_ID_SINGLE; return phydev; } -- cgit v1.2.3 From ae2fb9b9184f9b9b70caf01ecbd0dcc64d7c1cc0 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Sun, 31 Jan 2016 19:10:13 -0800 Subject: miitool: Add code to register a PHY This commit changes the behaviour of the 'miitool'. Now in order to show PHY's link information 'miitool' should be invoked as such: miitool -s [PHY] Also, implment code to allow to register a dummy PHY device in order to be able to perform raw MDIO bus access. Signed-off-by: Andrey Smirnov Signed-off-by: Sascha Hauer --- commands/miitool.c | 94 +++++++++++++++++++++++++++++++++++++++++---------- drivers/net/phy/phy.c | 9 +++-- include/linux/phy.h | 1 + 3 files changed, 84 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/commands/miitool.c b/commands/miitool.c index 9ea5ab5cd3..ba6e604633 100644 --- a/commands/miitool.c +++ b/commands/miitool.c @@ -260,53 +260,111 @@ static void mdiobus_show(struct device_d *dev, char *phydevname, int verbose) return; } +enum miitool_operations { + MIITOOL_NOOP, + MIITOOL_SHOW, + MIITOOL_REGISTER, +}; + static int do_miitool(int argc, char *argv[]) { - char *phydevname; + char *phydevname = NULL; + char *regstr = NULL; + char *endp; struct mii_bus *mii; - int opt; - int argc_min; - int verbose; + int opt, ret; + int verbose = 0; + struct phy_device *phydev; + enum miitool_operations action = MIITOOL_NOOP; + int addr, bus; - verbose = 0; - while ((opt = getopt(argc, argv, "v")) > 0) { + while ((opt = getopt(argc, argv, "vs:r:")) > 0) { switch (opt) { + case 'a': + addr = simple_strtol(optarg, NULL, 0); + break; + case 'b': + bus = simple_strtoul(optarg, NULL, 0); + break; + case 's': + action = MIITOOL_SHOW; + phydevname = xstrdup(optarg); + break; + case 'r': + action = MIITOOL_REGISTER; + regstr = optarg; + break; case 'v': verbose++; break; default: - return COMMAND_ERROR_USAGE; + ret = COMMAND_ERROR_USAGE; + goto free_phydevname; } } - argc_min = optind + 1; + switch (action) { + case MIITOOL_REGISTER: + bus = simple_strtoul(regstr, &endp, 0); + if (*endp != ':') { + printf("No colon between bus and address\n"); + return COMMAND_ERROR_USAGE; + } + endp++; + addr = simple_strtoul(endp, NULL, 0); + + if (addr >= PHY_MAX_ADDR) + printf("Address out of range (max %d)\n", PHY_MAX_ADDR - 1); - phydevname = NULL; - if (argc >= argc_min) { - phydevname = argv[optind]; - } + mii = mdiobus_get_bus(bus); + if (!mii) { + printf("Can't find MDIO bus #%d\n", bus); + ret = COMMAND_ERROR; + goto free_phydevname; + } - for_each_mii_bus(mii) { - mdiobus_detect(&mii->dev); - mdiobus_show(&mii->dev, phydevname, verbose); + phydev = phy_device_create(mii, addr, -1); + ret = phy_register_device(phydev); + if (ret) { + printf("failed to register phy %s: %s\n", + dev_name(&phydev->dev), strerror(-ret)); + goto free_phydevname; + } else { + printf("registered phy %s\n", dev_name(&phydev->dev)); + } + break; + default: + case MIITOOL_SHOW: + for_each_mii_bus(mii) { + mdiobus_detect(&mii->dev); + mdiobus_show(&mii->dev, phydevname, verbose); + } + break; } - return COMMAND_SUCCESS; + ret = COMMAND_SUCCESS; + +free_phydevname: + free(phydevname); + return ret; } BAREBOX_CMD_HELP_START(miitool) BAREBOX_CMD_HELP_TEXT("This utility checks or sets the status of a network interface's") -BAREBOX_CMD_HELP_TEXT("Media Independent Interface (MII) unit. Most fast ethernet") +BAREBOX_CMD_HELP_TEXT("Media Independent Interface (MII) unit as well as allowing to") +BAREBOX_CMD_HELP_TEXT("register dummy PHY devices for raw MDIO access. Most fast ethernet") BAREBOX_CMD_HELP_TEXT("adapters use an MII to autonegotiate link speed and duplex setting.") BAREBOX_CMD_HELP_TEXT("") BAREBOX_CMD_HELP_TEXT("Options:") BAREBOX_CMD_HELP_OPT("-v", "increase verbosity") +BAREBOX_CMD_HELP_OPT("-s ", "show PHY status (not providing PHY prints status of all)") +BAREBOX_CMD_HELP_OPT("-r :", "register a PHY") BAREBOX_CMD_HELP_END BAREBOX_CMD_START(miitool) .cmd = do_miitool, BAREBOX_CMD_DESC("view media-independent interface status") - BAREBOX_CMD_OPTS("[-v] PHY") + BAREBOX_CMD_OPTS("[-vsr]") BAREBOX_CMD_GROUP(CMD_GRP_NET) BAREBOX_CMD_HELP(cmd_miitool_help) BAREBOX_CMD_END diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index d128a5e8d5..be2c68bf68 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -147,8 +147,13 @@ int phy_scan_fixups(struct phy_device *phydev) return 0; } - -static struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id) +/** + * phy_device_create - creates a struct phy_device. + * @bus: the target MII bus + * @addr: PHY address on the MII bus + * @phy_id: PHY ID. + */ +struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id) { struct phy_device *phydev; diff --git a/include/linux/phy.h b/include/linux/phy.h index 4e88936bad..38b0670187 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -271,6 +271,7 @@ struct phy_device *get_phy_device(struct mii_bus *bus, int addr); int phy_init(void); int phy_init_hw(struct phy_device *phydev); +struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id); int phy_register_device(struct phy_device* dev); void phy_unregister_device(struct phy_device *phydev); -- cgit v1.2.3 From 7be3ed46a6b057c4b2a1e27d09b3c4680112100d Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Tue, 2 Feb 2016 12:17:30 +0100 Subject: net: phy: micrel: Staticise ksz8873mll_read_status() Based on kernel commit 32d73b144eac ("net: phy: micrel: Staticise ksz8873mll_read_status()") by Jingoo Han : ksz8873mll_read_status() is used only in this file. Fix the following sparse warning: drivers/net/phy/micrel.c:147:5: warning: symbol 'ksz8873mll_read_status' was not declared. Should it be static? Signed-off-by: Philipp Zabel Signed-off-by: Sascha Hauer --- drivers/net/phy/micrel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 095563ad3b..2f319068eb 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -233,7 +233,7 @@ static int ksz9031_config_init(struct phy_device *phydev) #define KSZ8873MLL_GLOBAL_CONTROL_4 0x06 #define KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX (1 << 6) #define KSZ8873MLL_GLOBAL_CONTROL_4_SPEED (1 << 4) -int ksz8873mll_read_status(struct phy_device *phydev) +static int ksz8873mll_read_status(struct phy_device *phydev) { int regval; -- cgit v1.2.3 From ea89f30bbd5cd356d27870d6d83fe215c01b14fd Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Tue, 2 Feb 2016 12:17:31 +0100 Subject: net: phy: micrel: use BIT macro Based on kernel commit 00aee095000c ("net: phy: micrel: use BIT macro") by Johan Hovold . Signed-off-by: Philipp Zabel Signed-off-by: Sascha Hauer --- drivers/net/phy/micrel.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 2f319068eb..0c97e25e9a 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -25,17 +25,17 @@ /* Operation Mode Strap Override */ #define MII_KSZPHY_OMSO 0x16 -#define KSZPHY_OMSO_B_CAST_OFF (1 << 9) -#define KSZPHY_OMSO_RMII_OVERRIDE (1 << 1) -#define KSZPHY_OMSO_MII_OVERRIDE (1 << 0) +#define KSZPHY_OMSO_B_CAST_OFF BIT(9) +#define KSZPHY_OMSO_RMII_OVERRIDE BIT(1) +#define KSZPHY_OMSO_MII_OVERRIDE BIT(0) /* general PHY control reg in vendor specific block. */ #define MII_KSZPHY_CTRL 0x1F /* bitmap of PHY register to set interrupt mode */ -#define KSZPHY_CTRL_INT_ACTIVE_HIGH (1 << 9) -#define KSZ9021_CTRL_INT_ACTIVE_HIGH (1 << 14) -#define KS8737_CTRL_INT_ACTIVE_HIGH (1 << 14) -#define KSZ8051_RMII_50MHZ_CLK (1 << 7) +#define KSZPHY_CTRL_INT_ACTIVE_HIGH BIT(9) +#define KSZ9021_CTRL_INT_ACTIVE_HIGH BIT(14) +#define KS8737_CTRL_INT_ACTIVE_HIGH BIT(14) +#define KSZ8051_RMII_50MHZ_CLK BIT(7) /* Write/read to/from extended registers */ #define MII_KSZPHY_EXTREG 0x0b @@ -231,8 +231,8 @@ static int ksz9031_config_init(struct phy_device *phydev) } #define KSZ8873MLL_GLOBAL_CONTROL_4 0x06 -#define KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX (1 << 6) -#define KSZ8873MLL_GLOBAL_CONTROL_4_SPEED (1 << 4) +#define KSZ8873MLL_GLOBAL_CONTROL_4_DUPLEX BIT(6) +#define KSZ8873MLL_GLOBAL_CONTROL_4_SPEED BIT(4) static int ksz8873mll_read_status(struct phy_device *phydev) { int regval; -- cgit v1.2.3 From 9fc7b70a28d14d1d28d918bf9de44127d7c775de Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Tue, 2 Feb 2016 12:17:32 +0100 Subject: net: phy: micrel: Be more const correct Based on kernel commit 3c9a9f7fb0ee ("net/phy: micrel: Be more const correct") by Jaeden Amero : In a few places in this driver, we weren't using const where we could have. Use const more. In addition, change the arrays of strings in ksz9031_config_init() to be not only const, but also static. Signed-off-by: Philipp Zabel Signed-off-by: Sascha Hauer --- drivers/net/phy/micrel.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 0c97e25e9a..6f49e73530 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -91,8 +91,8 @@ static int ks8051_config_init(struct phy_device *phydev) } static int ksz9021_load_values_from_of(struct phy_device *phydev, - struct device_node *of_node, u16 reg, - const char *field[]) + const struct device_node *of_node, + u16 reg, const char *field[]) { int val, regval, i; @@ -113,8 +113,8 @@ static int ksz9021_load_values_from_of(struct phy_device *phydev, static int ksz9021_config_init(struct phy_device *phydev) { - struct device_d *dev = &phydev->dev; - struct device_node *of_node = dev->device_node; + const struct device_d *dev = &phydev->dev; + const struct device_node *of_node = dev->device_node; const char *clk_pad_skew_names[] = { "txen-skew-ps", "txc-skew-ps", "rxdv-skew-ps", "rxc-skew-ps" @@ -155,9 +155,9 @@ static int ksz9021_config_init(struct phy_device *phydev) #define MII_KSZ9031RN_CLK_PAD_SKEW 8 static int ksz9031_of_load_skew_values(struct phy_device *phydev, - struct device_node *of_node, + const struct device_node *of_node, u16 reg, size_t field_sz, - char *field[], u8 numfields) + const char *field[], u8 numfields) { int val[4] = {-1, -2, -3, -4}; int matches = 0; @@ -194,18 +194,18 @@ static int ksz9031_of_load_skew_values(struct phy_device *phydev, static int ksz9031_config_init(struct phy_device *phydev) { - struct device_d *dev = &phydev->dev; - struct device_node *of_node = dev->device_node; - char *clk_skews[2] = {"rxc-skew-ps", "txc-skew-ps"}; - char *rx_data_skews[4] = { + const struct device_d *dev = &phydev->dev; + const struct device_node *of_node = dev->device_node; + static const char *clk_skews[2] = {"rxc-skew-ps", "txc-skew-ps"}; + static const char *rx_data_skews[4] = { "rxd0-skew-ps", "rxd1-skew-ps", "rxd2-skew-ps", "rxd3-skew-ps" }; - char *tx_data_skews[4] = { + static const char *tx_data_skews[4] = { "txd0-skew-ps", "txd1-skew-ps", "txd2-skew-ps", "txd3-skew-ps" }; - char *control_skews[2] = {"txen-skew-ps", "rxdv-skew-ps"}; + static const char *control_skews[2] = {"txen-skew-ps", "rxdv-skew-ps"}; if (!of_node && dev->parent->device_node) of_node = dev->parent->device_node; -- cgit v1.2.3 From da89ee8f2e04e116410632a185024f58b8262d87 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Tue, 2 Feb 2016 12:17:33 +0100 Subject: net: phy: micrel: Center FLP timing at 16ms Based on kernel commit 6270e1ae804a ("net/phy: micrel: Center FLP timing at 16ms") by Jaeden Amero : Signed-off-by: Philipp Zabel Signed-off-by: Sascha Hauer --- drivers/net/phy/micrel.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 6f49e73530..570272ff65 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -149,6 +149,11 @@ static int ksz9021_config_init(struct phy_device *phydev) #define KSZ9031_PS_TO_REG 60 /* Extended registers */ +/* MMD Address 0x0 */ +#define MII_KSZ9031RN_FLP_BURST_TX_LO 3 +#define MII_KSZ9031RN_FLP_BURST_TX_HI 4 + +/* MMD Address 0x2 */ #define MII_KSZ9031RN_CONTROL_PAD_SKEW 4 #define MII_KSZ9031RN_RX_DATA_PAD_SKEW 5 #define MII_KSZ9031RN_TX_DATA_PAD_SKEW 6 @@ -192,6 +197,15 @@ static int ksz9031_of_load_skew_values(struct phy_device *phydev, return 0; } +static int ksz9031_center_flp_timing(struct phy_device *phydev) +{ + /* Center KSZ9031RNX FLP timing at 16ms. */ + phy_write_mmd_indirect(phydev, MII_KSZ9031RN_FLP_BURST_TX_HI, 0, 0x0006); + phy_write_mmd_indirect(phydev, MII_KSZ9031RN_FLP_BURST_TX_LO, 0, 0x1a80); + + return genphy_restart_aneg(phydev); +} + static int ksz9031_config_init(struct phy_device *phydev) { const struct device_d *dev = &phydev->dev; @@ -227,7 +241,8 @@ static int ksz9031_config_init(struct phy_device *phydev) MII_KSZ9031RN_TX_DATA_PAD_SKEW, 4, tx_data_skews, 4); } - return 0; + + return ksz9031_center_flp_timing(phydev); } #define KSZ8873MLL_GLOBAL_CONTROL_4 0x06 -- cgit v1.2.3 From a4cee7207a14ec5445f4fe755fda0d1d28372bc5 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Tue, 2 Feb 2016 12:17:34 +0100 Subject: net: phy: micrel: Add workaround for bad autoneg Based on kernel commit d2fd719bcb0e ("net/phy: micrel: Add workaround for bad autoneg") by Nathan Sullivan : Very rarely, the KSZ9031 will appear to complete autonegotiation, but will drop all traffic afterwards. When this happens, the idle error count will read 0xFF after autonegotiation completes. Reset the PHY when in that state. Signed-off-by: Philipp Zabel Signed-off-by: Sascha Hauer --- drivers/net/phy/micrel.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 570272ff65..e8a566deba 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -273,6 +273,27 @@ static int ksz8873mll_read_status(struct phy_device *phydev) return 0; } +static int ksz9031_read_status(struct phy_device *phydev) +{ + int err; + int regval; + + err = genphy_read_status(phydev); + if (err) + return err; + + /* Make sure the PHY is not broken. Read idle error count, + * and reset the PHY if it is maxed out. + */ + regval = phy_read(phydev, MII_STAT1000); + if ((regval & 0xff) == 0xff) { + phy_init_hw(phydev); + phydev->link = 0; + } + + return 0; +} + static int ksz8873mll_config_aneg(struct phy_device *phydev) { return 0; @@ -368,7 +389,7 @@ static struct phy_driver ksphy_driver[] = { .features = (PHY_GBIT_FEATURES | SUPPORTED_Pause), .config_init = ksz9031_config_init, .config_aneg = genphy_config_aneg, - .read_status = genphy_read_status, + .read_status = ksz9031_read_status, }, { .phy_id = PHY_ID_KSZ8873MLL, .phy_id_mask = 0x00fffff0, -- cgit v1.2.3