diff options
Diffstat (limited to 'drivers/net/dsa/dsa_mvmdio.c')
-rw-r--r-- | drivers/net/dsa/dsa_mvmdio.c | 528 |
1 files changed, 528 insertions, 0 deletions
diff --git a/drivers/net/dsa/dsa_mvmdio.c b/drivers/net/dsa/dsa_mvmdio.c new file mode 100644 index 000000000000..38c620fc52f5 --- /dev/null +++ b/drivers/net/dsa/dsa_mvmdio.c @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2019 Marvell International Ltd. */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/capability.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/netdevice.h> +#include <linux/of_mdio.h> + +#define MV_PHY_CMD_REG 0 +#define MV_PHY_DATA_REG 1 +#define MV_SMIBUSY_OFFSET 15 +#define MV_SMIFUNC_OFFSET 13 +#define MV_SMIFUNC_INT 0 +#define MV_SMIFUNC_EXT 1 +#define MV_SMIFUNC_SIZE 2 +#define MV_SMIMODE_OFFSET 12 +#define MV_SMIOP_OFFSET 10 +#define MV_SMIOP_SIZE 2 +#define MV_SMIOP_READ 2 +#define MV_SMIOP_WRITE 1 +#define MV_DEVAD_OFFSET 5 +#define MV_DEVAD_SIZE 5 +#define MV_DEVAD_MASK 0x1F +#define MV_REGAD_OFFSET 0 +#define MV_REGAD_SIZE 5 +#define MV_REGAD_MASK 0x1F +#define MV_MAX_REGS_PER_PORT 0x20 +#define MV_SMI_PHY_CMD 0x18 +#define MV_SMI_PHY_DATA 0x19 +#define MV_GLOBAL1 0x1B +#define MV_GLOBAL2 0x1C +#define MV_MAX_CHARS 1024 +#define MV_INVALID_PHY_ADDR 0xFF + +static struct mii_bus *mv_mii_bus; +static struct mii_bus *mv_xmii_bus; +static unsigned int mv_phy_addr; + +enum mv_reg_type { + MV_REG_TYPE_SWITCH, + MV_REG_TYPE_PHY_INT, + MV_REG_TYPE_PHY_EXT, + MV_REG_TYPE_MDIO, + MV_REG_TYPE_XMDIO +}; + +/* Read regular phy register that connected on mdio bus. + * Returns: register value on success or error value on failure. + */ +static int dsa_mvmdio_read_mdio(unsigned char phy, unsigned char reg) +{ + return mdiobus_read(mv_mii_bus, phy, reg); +} + +/* Write regular phy register that is connected on mdio bus. + * Returns: 0 on success or an error value on failure + */ +static int dsa_mvmdio_write_mdio(unsigned char phy, + unsigned char reg, + unsigned short val) +{ + return mdiobus_write(mv_mii_bus, phy, reg, val); +} + +/* Read extended phy register that connected on xmdio bus. + * Returns: register value on success or error value on failure. + */ +static int dsa_mvmdio_read_xmdio(unsigned char phy, + unsigned char dev, + unsigned char reg) +{ + return mdiobus_read(mv_xmii_bus, phy, (dev << 16) | reg); +} + +/* Write extended phy register that is connected on xmdio bus. + * Returns: 0 on success or an error value on failure + */ +static int dsa_mvmdio_write_xmdio(unsigned char phy, + unsigned char dev, + unsigned char reg, + unsigned short val) +{ + return mdiobus_write(mv_xmii_bus, phy, (dev << 16) | reg, val); +} + +/* Read switch register that is connected on mdio bus. + * Uses direct access if the switch is configured in siglechip addressing mode. + * Otherwise in multichip addressing mode it uses indirect acces through + * command and data registers of switch. + * Returns: register value on success or error value on failure. + */ +static int dsa_mvmdio_read_register(unsigned char dev, unsigned char reg) +{ + int ret; + unsigned short cmd_data; + + if (mv_phy_addr == 0) + return mdiobus_read(mv_mii_bus, dev, reg); + + /* Write to SMI Command Register */ + cmd_data = (1 << MV_SMIBUSY_OFFSET) | + (MV_SMIFUNC_INT << MV_SMIFUNC_OFFSET) | + (1 << MV_SMIMODE_OFFSET) | (MV_SMIOP_READ << MV_SMIOP_OFFSET) | + ((dev & MV_DEVAD_MASK) << MV_DEVAD_OFFSET) | + ((reg & MV_REGAD_MASK) << MV_REGAD_OFFSET); + + ret = mdiobus_write(mv_mii_bus, mv_phy_addr, MV_PHY_CMD_REG, + cmd_data); + if (ret < 0) + return ret; + + /* Read from SMI Data Register */ + ret = mdiobus_read(mv_mii_bus, mv_phy_addr, MV_PHY_DATA_REG); + + return ret; +} + +/* Write switch register that is connected on mdio bus. + * Uses direct access if the switch is configured in siglechip addressing mode. + * Otherwise in multichip addressing mode it uses indirect acces through + * command and data registers of switch. + * Returns: 0 on success or an error value on failure. + */ +static int dsa_mvmdio_write_register(unsigned char dev, + unsigned char reg, + unsigned short data) +{ + int ret; + unsigned short cmd_data; + + if (mv_phy_addr == 0) + return mdiobus_write(mv_mii_bus, dev, reg, data); + + /* Write data to SMI Data Register */ + ret = mdiobus_write(mv_mii_bus, mv_phy_addr, MV_PHY_DATA_REG, data); + if (ret < 0) + return ret; + + /* Write to SMI Command Register */ + cmd_data = (1 << MV_SMIBUSY_OFFSET) | + (MV_SMIFUNC_INT << MV_SMIFUNC_OFFSET) | + (1 << MV_SMIMODE_OFFSET) | + (MV_SMIOP_WRITE << MV_SMIOP_OFFSET) | + ((dev & MV_DEVAD_MASK) << MV_DEVAD_OFFSET) | + ((reg & MV_REGAD_MASK) << MV_REGAD_OFFSET); + + ret = mdiobus_write(mv_mii_bus, mv_phy_addr, MV_PHY_CMD_REG, + cmd_data); + + return ret; +} + +/* Read switch internal phy register when smi_func = 0. + * Read external phy register that is connected to the switch port when + * smi_func = 1. + * Returns: register value on success or error value on failure. + */ +static int dsa_mvmdio_phy_read_register(unsigned char dev, + unsigned char reg, + unsigned char smi_func) +{ + int ret; + unsigned short cmd_data; + + /* Write to SMI Command Register */ + cmd_data = (1 << MV_SMIBUSY_OFFSET) | (smi_func << MV_SMIFUNC_OFFSET) | + (1 << MV_SMIMODE_OFFSET) | (MV_SMIOP_READ << MV_SMIOP_OFFSET) | + ((dev & MV_DEVAD_MASK) << MV_DEVAD_OFFSET) | + ((reg & MV_REGAD_MASK) << MV_REGAD_OFFSET); + + ret = dsa_mvmdio_write_register(MV_GLOBAL2, MV_SMI_PHY_CMD, cmd_data); + if (ret < 0) + return ret; + + /* Read from SMI Data Register */ + ret = dsa_mvmdio_read_register(MV_GLOBAL2, MV_SMI_PHY_DATA); + return ret; +} + +/* Write switch internal phy register when smi_func = 0. + * Write external phy register that is connected to the switch port when + * smi_func = 1. + * Returns: 0 on success or an error value on failure. + */ +static int dsa_mvmdio_phy_write_register(unsigned char dev, + unsigned char reg, + unsigned short data, + unsigned char smi_func) +{ + int ret; + unsigned short cmd_data; + + /* Write data to SMI Data Register */ + ret = dsa_mvmdio_write_register(MV_GLOBAL2, MV_SMI_PHY_DATA, data); + if (ret < 0) + return ret; + + /* Write to SMI Command Register */ + cmd_data = (1 << MV_SMIBUSY_OFFSET) | (smi_func << MV_SMIFUNC_OFFSET) | + (1 << MV_SMIMODE_OFFSET) | (MV_SMIOP_WRITE << MV_SMIOP_OFFSET) | + ((dev & MV_DEVAD_MASK) << MV_DEVAD_OFFSET) | + ((reg & MV_REGAD_MASK) << MV_REGAD_OFFSET); + + ret = dsa_mvmdio_write_register(MV_GLOBAL2, MV_SMI_PHY_CMD, cmd_data); + + return ret; +} + +/* Processing "read" command in sysfs. + * Returns: register value on success or error value on failure for a given + * register type. + */ +static int dsa_mvmdio_read(unsigned char port, + unsigned char dev_addr, + unsigned char reg, + unsigned char type, + unsigned int *value) +{ + int ret = 0; + + if (type == MV_REG_TYPE_SWITCH) + ret = dsa_mvmdio_read_register(port, reg); + else if (type == MV_REG_TYPE_PHY_INT) + ret = dsa_mvmdio_phy_read_register(port, reg, MV_SMIFUNC_INT); + else if (type == MV_REG_TYPE_PHY_EXT) + ret = dsa_mvmdio_phy_read_register(port, reg, MV_SMIFUNC_EXT); + else if (type == MV_REG_TYPE_MDIO) + ret = dsa_mvmdio_read_mdio(port, reg); + else if (type == MV_REG_TYPE_XMDIO) + ret = dsa_mvmdio_read_xmdio(port, dev_addr, reg); + + if (ret < 0) + return ret; + + *value = ret; + + return 0; +} + +/* Processing "write" command in sysfsi. + * Returns: 0 on success or error value on failure. + */ +static int dsa_mvmdio_write(unsigned char port, + unsigned char dev_addr, + unsigned char reg, + unsigned char type, + unsigned short value) +{ + int ret = 0; + + if (type == MV_REG_TYPE_SWITCH) + ret = dsa_mvmdio_write_register(port, reg, value); + else if (type == MV_REG_TYPE_PHY_INT) + ret = dsa_mvmdio_phy_write_register(port, reg, value, + MV_SMIFUNC_INT); + else if (type == MV_REG_TYPE_PHY_EXT) + ret = dsa_mvmdio_phy_write_register(port, reg, value, + MV_SMIFUNC_EXT); + else if (type == MV_REG_TYPE_MDIO) + ret = dsa_mvmdio_write_mdio(port, reg, value); + else if (type == MV_REG_TYPE_XMDIO) + ret = dsa_mvmdio_write_xmdio(port, dev_addr, reg, value); + + if (ret < 0) + return ret; + + return 0; +} + +/* Processing "dump" command in sysfs. + * Print 0 to 32 registers values of a given register type. + * Returns: 0 on success or error value on failure. + */ +static int dsa_mvmdio_dump(unsigned char port, + unsigned char dev_addr, + unsigned char type) +{ + int i; + int max_regs = MV_MAX_REGS_PER_PORT; + int off = 0; + char buf[MV_MAX_CHARS]; + + for (i = 0; i < max_regs; i++) { + if (i % 4 == 0) + off += sprintf(buf + off, "(%02X-%02X) ", i, i + 3); + if (type == MV_REG_TYPE_SWITCH) + off += sprintf(buf + off, "%04X ", + dsa_mvmdio_read_register(port, i)); + else if (type == MV_REG_TYPE_PHY_INT) + off += sprintf(buf + off, "%04X ", + dsa_mvmdio_phy_read_register(port, i, MV_SMIFUNC_INT)); + else if (type == MV_REG_TYPE_PHY_EXT) + off += sprintf(buf + off, "%04X ", + dsa_mvmdio_phy_read_register(port, i, MV_SMIFUNC_EXT)); + else if (type == MV_REG_TYPE_MDIO) + off += sprintf(buf + off, "%04X ", + dsa_mvmdio_read_mdio(port, i)); + else if (type == MV_REG_TYPE_XMDIO) + off += sprintf(buf + off, "%04X ", + dsa_mvmdio_read_xmdio(port, dev_addr, i)); + if (i % 4 == 3) + off += sprintf(buf + off, "\n"); + } + + pr_err("%s", buf); + return 0; +} + +/* Processing "help" command in sysfs */ +static ssize_t dsa_mvmdio_help(char *buf) +{ + int off = 0; + + off += scnprintf(buf + off, PAGE_SIZE - off, "cat help - print help\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, "echo [t] [p] [x] [r] > read - read register\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, "echo [t] [p] [x] [r] [v] > write - write register\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, "echo [t] [p] [x] > dump - dump 32 registers\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, "parameters (in hexadecimal):\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " [t] type. 0-switch, 1-internal phy, 2-external phy regs\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " 3-regular phy, 4-extended phy\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " [p] port addr or phy-id.\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " [x] device address. valid only for extended phy.\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " [r] register address.\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " [v] value.\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, "Examples:\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " 1. echo 0 1 0 3 > read - read switch register\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " 2. echo 0 1b 0 1c > read - read switch global1 register\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " 3. echo 1 3 0 2 > read - read internal phy register\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " 4. echo 2 0 0 2 > read - read external phy register\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " 5. echo 3 1 0 2 > read - read regular phy register, phyid=1\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " 6. echo 4 0 7 3c > read - read xmdio phy, EEE advertisement register\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " 7. echo 0 2 0 7 5 > write - write switch register, set vlan id\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " 8. echo 1 3 0 > dump - dump internal switch phy registers\n"); + off += scnprintf(buf + off, PAGE_SIZE - off, " 9. echo 4 0 7 > dump - dump xmdio phy registers for dev-addr=7"); + return off; +} + +static ssize_t dsa_mvmdio_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int off = 0; + ssize_t len = 0; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + len = dsa_mvmdio_help(buf); + pr_err("%s\n", buf); + + return off; +} + +static ssize_t dsa_mvmdio_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + const char *name = attr->attr.name; + unsigned long flags; + unsigned int err = 0, dev_addr = 0, port = 0, reg = 0, type = 0; + unsigned int val; + unsigned int data = 0; + int ret; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + /* Read arguments */ + ret = sscanf(buf, "%x %x %x %x %x", + &type, &port, &dev_addr, ®, &val); + + if (mv_phy_addr == MV_INVALID_PHY_ADDR && type < MV_REG_TYPE_MDIO) { + pr_err("\"sw-smi-addr\" property not defined in dts file. Assuming switch not connected\n"); + return len; + } + + local_irq_save(flags); + if (!strcmp(name, "read")) { + err = dsa_mvmdio_read((unsigned char)port, + (unsigned char)dev_addr, + (unsigned char)reg, + (unsigned char)type, &data); + if (err) + pr_err("Register read failed, err - %d\n", err); + else + pr_err("read:: type:%d, port=0x%X, dev=0x%X,reg=0x%X, val=0x%04X\n", type, port, dev_addr, reg, data); + } else if (!strcmp(name, "write")) { + err = dsa_mvmdio_write((unsigned char)port, + (unsigned char)dev_addr, + (unsigned char)reg, + (unsigned char)type, + (unsigned short)val); + if (err) + pr_err("Register write failed, err - %d\n", err); + else + pr_err("write:: type:%d, port=0x%X, dev=0x%X,reg=0x%X, val=0x%X\n", type, port, dev_addr, reg, val); + } else if (!strcmp(name, "dump")) { + err = dsa_mvmdio_dump((unsigned char)port, + (unsigned char)dev_addr, + (unsigned char)type); + if (err) + pr_err("Register dump failed, err - %d\n", err); + else + pr_err("dump:: type %d, port=0x%X, dev=0x%X\n", + type, port, dev_addr); + } + + local_irq_restore(flags); + + return err ? -EINVAL : len; +} + +static DEVICE_ATTR(read, 0200, dsa_mvmdio_show, dsa_mvmdio_store); +static DEVICE_ATTR(write, 0200, dsa_mvmdio_show, dsa_mvmdio_store); +static DEVICE_ATTR(dump, 0200, dsa_mvmdio_show, dsa_mvmdio_store); +static DEVICE_ATTR(help, 0400, dsa_mvmdio_show, dsa_mvmdio_store); + +static struct attribute *dsa_mvmdio_attrs[] = { + &dev_attr_read.attr, + &dev_attr_write.attr, + &dev_attr_dump.attr, + &dev_attr_help.attr, + NULL +}; + +static struct attribute_group dsa_mvmdio_group = { + .name = "dsa_mvmdio", + .attrs = dsa_mvmdio_attrs, +}; + +static int dsa_mvmdio_probe(struct platform_device *pdev) +{ + struct device_node *np; + struct device_node *mdio; + struct device_node *xmdio; + int ret; + + np = pdev->dev.of_node; + mdio = of_parse_phandle(np, "mii-bus", 0); + if (!mdio) { + pr_err("%s : parse mii-bus handle failed\n", __func__); + return -EINVAL; + } + + mv_mii_bus = of_mdio_find_bus(mdio); + if (!mv_mii_bus) { + pr_err("%s : mdio find bus failed\n", __func__); + return -EINVAL; + } + + ret = of_property_read_u32(np, "reg", &mv_phy_addr); + if (ret) { + pr_err("%s : switch smi addr not defined\n", __func__); + mv_phy_addr = MV_INVALID_PHY_ADDR; + } + + xmdio = of_parse_phandle(np, "xmii-bus", 0); + if (!xmdio) { + pr_err("%s : parse handle failed\n", __func__); + return -EINVAL; + } + + mv_xmii_bus = of_mdio_find_bus(xmdio); + if (!mv_xmii_bus) { + pr_err("%s : xmdio find bus failed\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int dsa_mvmdio_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id dsa_mvmdio_match[] = { + { .compatible = "marvell,dsa-mvmdio" }, + { } +}; + +MODULE_DEVICE_TABLE(of, dsa_mvmdio_match); + +static struct platform_driver dsa_mvmdio_driver = { + .probe = dsa_mvmdio_probe, + .remove = dsa_mvmdio_remove, + .driver = { + .name = "dsa-mvmdio", + .of_match_table = dsa_mvmdio_match, + }, +}; + +static int dsa_mvmdio_init(void) +{ + int err; + struct device *pd; + + err = platform_driver_register(&dsa_mvmdio_driver); + if (err) { + pr_err("register dsa_mvmdio_driver() failed\n"); + return err; + } + + pd = &platform_bus; + err = sysfs_create_group(&pd->kobj, &dsa_mvmdio_group); + if (err) + pr_err("init sysfs group %s failed %d\n", + dsa_mvmdio_group.name, err); + + return err; +} + +static void dsa_mvmdio_exit(void) +{ + platform_driver_unregister(&dsa_mvmdio_driver); +} + +late_initcall(dsa_mvmdio_init); +module_exit(dsa_mvmdio_exit); + +MODULE_AUTHOR("Ravindra Reddy K. <ravindra@marvell.com>"); +MODULE_DESCRIPTION("Mdio access for switch from userspace through sysfs"); +MODULE_LICENSE("GPL v2"); |