diff options
Diffstat (limited to 'drivers/net/mdio')
-rw-r--r-- | drivers/net/mdio/Kconfig | 7 | ||||
-rw-r--r-- | drivers/net/mdio/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/mdio/fwnode_mdio.c | 26 | ||||
-rw-r--r-- | drivers/net/mdio/mdio-airoha.c | 276 | ||||
-rw-r--r-- | drivers/net/mdio/mdio-mux-gpio.c | 3 |
5 files changed, 299 insertions, 14 deletions
diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig index 7db40aaa079d..e1e32b687068 100644 --- a/drivers/net/mdio/Kconfig +++ b/drivers/net/mdio/Kconfig @@ -27,6 +27,13 @@ config ACPI_MDIO help ACPI MDIO bus (Ethernet PHY) accessors +config MDIO_AIROHA + tristate "Airoha AN7583 MDIO bus controller" + depends on ARCH_AIROHA || COMPILE_TEST + help + This module provides a driver for the MDIO busses found in the + Airoha AN7583 SoC's. + config MDIO_SUN4I tristate "Allwinner sun4i MDIO interface support" depends on ARCH_SUNXI || COMPILE_TEST diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile index c23778e73890..fbec636700e7 100644 --- a/drivers/net/mdio/Makefile +++ b/drivers/net/mdio/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_ACPI_MDIO) += acpi_mdio.o obj-$(CONFIG_FWNODE_MDIO) += fwnode_mdio.o obj-$(CONFIG_OF_MDIO) += of_mdio.o +obj-$(CONFIG_MDIO_AIROHA) += mdio-airoha.o obj-$(CONFIG_MDIO_ASPEED) += mdio-aspeed.o obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o diff --git a/drivers/net/mdio/fwnode_mdio.c b/drivers/net/mdio/fwnode_mdio.c index aea0f0357568..9b41d4697a40 100644 --- a/drivers/net/mdio/fwnode_mdio.c +++ b/drivers/net/mdio/fwnode_mdio.c @@ -18,7 +18,8 @@ MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("FWNODE MDIO bus (Ethernet PHY) accessors"); static struct pse_control * -fwnode_find_pse_control(struct fwnode_handle *fwnode) +fwnode_find_pse_control(struct fwnode_handle *fwnode, + struct phy_device *phydev) { struct pse_control *psec; struct device_node *np; @@ -30,7 +31,7 @@ fwnode_find_pse_control(struct fwnode_handle *fwnode) if (!np) return NULL; - psec = of_pse_control_get(np); + psec = of_pse_control_get(np, phydev); if (PTR_ERR(psec) == -ENOENT) return NULL; @@ -128,15 +129,9 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus, u32 phy_id; int rc; - psec = fwnode_find_pse_control(child); - if (IS_ERR(psec)) - return PTR_ERR(psec); - mii_ts = fwnode_find_mii_timestamper(child); - if (IS_ERR(mii_ts)) { - rc = PTR_ERR(mii_ts); - goto clean_pse; - } + if (IS_ERR(mii_ts)) + return PTR_ERR(mii_ts); is_c45 = fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45"); if (is_c45 || fwnode_get_phy_id(child, &phy_id)) @@ -169,6 +164,12 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus, goto clean_phy; } + psec = fwnode_find_pse_control(child, phy); + if (IS_ERR(psec)) { + rc = PTR_ERR(psec); + goto unregister_phy; + } + phy->psec = psec; /* phy->mii_ts may already be defined by the PHY driver. A @@ -180,12 +181,13 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus, return 0; +unregister_phy: + if (is_acpi_node(child) || is_of_node(child)) + phy_device_remove(phy); clean_phy: phy_device_free(phy); clean_mii_ts: unregister_mii_timestamper(mii_ts); -clean_pse: - pse_control_put(psec); return rc; } diff --git a/drivers/net/mdio/mdio-airoha.c b/drivers/net/mdio/mdio-airoha.c new file mode 100644 index 000000000000..1dc9939c8d7d --- /dev/null +++ b/drivers/net/mdio/mdio-airoha.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Airoha AN7583 MDIO interface driver + * + * Copyright (C) 2025 Christian Marangi <ansuelsmth@gmail.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_mdio.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +/* MII address register definitions */ +#define AN7583_MII_BUSY BIT(31) +#define AN7583_MII_RDY BIT(30) /* RO signal BUS is ready */ +#define AN7583_MII_CL22_REG_ADDR GENMASK(29, 25) +#define AN7583_MII_CL45_DEV_ADDR AN7583_MII_CL22_REG_ADDR +#define AN7583_MII_PHY_ADDR GENMASK(24, 20) +#define AN7583_MII_CMD GENMASK(19, 18) +#define AN7583_MII_CMD_CL22_WRITE FIELD_PREP_CONST(AN7583_MII_CMD, 0x1) +#define AN7583_MII_CMD_CL22_READ FIELD_PREP_CONST(AN7583_MII_CMD, 0x2) +#define AN7583_MII_CMD_CL45_ADDR FIELD_PREP_CONST(AN7583_MII_CMD, 0x0) +#define AN7583_MII_CMD_CL45_WRITE FIELD_PREP_CONST(AN7583_MII_CMD, 0x1) +#define AN7583_MII_CMD_CL45_POSTREAD_INCADDR FIELD_PREP_CONST(AN7583_MII_CMD, 0x2) +#define AN7583_MII_CMD_CL45_READ FIELD_PREP_CONST(AN7583_MII_CMD, 0x3) +#define AN7583_MII_ST GENMASK(17, 16) +#define AN7583_MII_ST_CL45 FIELD_PREP_CONST(AN7583_MII_ST, 0x0) +#define AN7583_MII_ST_CL22 FIELD_PREP_CONST(AN7583_MII_ST, 0x1) +#define AN7583_MII_RWDATA GENMASK(15, 0) +#define AN7583_MII_CL45_REG_ADDR AN7583_MII_RWDATA + +#define AN7583_MII_MDIO_DELAY_USEC 100 +#define AN7583_MII_MDIO_RETRY_MSEC 100 + +struct airoha_mdio_data { + u32 base_addr; + struct regmap *regmap; + struct clk *clk; + struct reset_control *reset; +}; + +static int airoha_mdio_wait_busy(struct airoha_mdio_data *priv) +{ + u32 busy; + + return regmap_read_poll_timeout(priv->regmap, priv->base_addr, busy, + !(busy & AN7583_MII_BUSY), + AN7583_MII_MDIO_DELAY_USEC, + AN7583_MII_MDIO_RETRY_MSEC * USEC_PER_MSEC); +} + +static void airoha_mdio_reset(struct airoha_mdio_data *priv) +{ + /* There seems to be Hardware bug where AN7583_MII_RWDATA + * is not wiped in the context of unconnected PHY and the + * previous read value is returned. + * + * Example: (only one PHY on the BUS at 0x1f) + * - read at 0x1f report at 0x2 0x7500 + * - read at 0x0 report 0x7500 on every address + * + * To workaround this, we reset the Mdio BUS at every read + * to have consistent values on read operation. + */ + reset_control_assert(priv->reset); + reset_control_deassert(priv->reset); +} + +static int airoha_mdio_read(struct mii_bus *bus, int addr, int regnum) +{ + struct airoha_mdio_data *priv = bus->priv; + u32 val; + int ret; + + airoha_mdio_reset(priv); + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL22 | + AN7583_MII_CMD_CL22_READ; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL22_REG_ADDR, regnum); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, priv->base_addr, &val); + if (ret) + return ret; + + return FIELD_GET(AN7583_MII_RWDATA, val); +} + +static int airoha_mdio_write(struct mii_bus *bus, int addr, int regnum, + u16 value) +{ + struct airoha_mdio_data *priv = bus->priv; + u32 val; + int ret; + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL22 | + AN7583_MII_CMD_CL22_WRITE; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL22_REG_ADDR, regnum); + val |= FIELD_PREP(AN7583_MII_RWDATA, value); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + + return ret; +} + +static int airoha_mdio_cl45_read(struct mii_bus *bus, int addr, int devnum, + int regnum) +{ + struct airoha_mdio_data *priv = bus->priv; + u32 val; + int ret; + + airoha_mdio_reset(priv); + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | + AN7583_MII_CMD_CL45_ADDR; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); + val |= FIELD_PREP(AN7583_MII_CL45_REG_ADDR, regnum); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + if (ret) + return ret; + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | + AN7583_MII_CMD_CL45_READ; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, priv->base_addr, &val); + if (ret) + return ret; + + return FIELD_GET(AN7583_MII_RWDATA, val); +} + +static int airoha_mdio_cl45_write(struct mii_bus *bus, int addr, int devnum, + int regnum, u16 value) +{ + struct airoha_mdio_data *priv = bus->priv; + u32 val; + int ret; + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | + AN7583_MII_CMD_CL45_ADDR; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); + val |= FIELD_PREP(AN7583_MII_CL45_REG_ADDR, regnum); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + if (ret) + return ret; + + val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 | + AN7583_MII_CMD_CL45_WRITE; + val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr); + val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum); + val |= FIELD_PREP(AN7583_MII_RWDATA, value); + + ret = regmap_write(priv->regmap, priv->base_addr, val); + if (ret) + return ret; + + ret = airoha_mdio_wait_busy(priv); + + return ret; +} + +static int airoha_mdio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct airoha_mdio_data *priv; + struct mii_bus *bus; + u32 addr, freq; + int ret; + + ret = of_property_read_u32(dev->of_node, "reg", &addr); + if (ret) + return ret; + + bus = devm_mdiobus_alloc_size(dev, sizeof(*priv)); + if (!bus) + return -ENOMEM; + + priv = bus->priv; + priv->base_addr = addr; + priv->regmap = device_node_to_regmap(dev->parent->of_node); + + priv->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + reset_control_deassert(priv->reset); + + bus->name = "airoha_mdio_bus"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev)); + bus->parent = dev; + bus->read = airoha_mdio_read; + bus->write = airoha_mdio_write; + bus->read_c45 = airoha_mdio_cl45_read; + bus->write_c45 = airoha_mdio_cl45_write; + + /* Check if a custom frequency is defined in DT or default to 2.5 MHz */ + if (of_property_read_u32(dev->of_node, "clock-frequency", &freq)) + freq = 2500000; + + ret = clk_set_rate(priv->clk, freq); + if (ret) + return ret; + + ret = devm_of_mdiobus_register(dev, bus, dev->of_node); + if (ret) { + reset_control_assert(priv->reset); + return ret; + } + + return 0; +} + +static const struct of_device_id airoha_mdio_dt_ids[] = { + { .compatible = "airoha,an7583-mdio" }, + { } +}; +MODULE_DEVICE_TABLE(of, airoha_mdio_dt_ids); + +static struct platform_driver airoha_mdio_driver = { + .probe = airoha_mdio_probe, + .driver = { + .name = "airoha-mdio", + .of_match_table = airoha_mdio_dt_ids, + }, +}; + +module_platform_driver(airoha_mdio_driver); + +MODULE_DESCRIPTION("Airoha AN7583 MDIO interface driver"); +MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/mdio/mdio-mux-gpio.c b/drivers/net/mdio/mdio-mux-gpio.c index ef77bd1abae9..fefa40ea5227 100644 --- a/drivers/net/mdio/mdio-mux-gpio.c +++ b/drivers/net/mdio/mdio-mux-gpio.c @@ -30,8 +30,7 @@ static int mdio_mux_gpio_switch_fn(int current_child, int desired_child, values[0] = desired_child; - gpiod_set_array_value_cansleep(s->gpios->ndescs, s->gpios->desc, - s->gpios->info, values); + gpiod_multi_set_value_cansleep(s->gpios, values); return 0; } |