Add Clause 45 read/write support to the UniMAC MDIO bus controller driver by implementing the read_c45/write_c45 bus callbacks. Each access performs the two-step C45 sequence (ADDR cycle followed by a READ/WRITE cycle) under the bus lock and switches MDIO_CFG between C22 and C45 mode as needed; MDIO_CFG is only written when the mode actually changes. Also skip the BMSR workaround read in unimac_mdio_reset() for children compatible with "ethernet-phy-ieee802.3-c45", since such PHYs are not expected to respond to C22 accesses. Note that providing read_c45/write_c45 also makes the MDIO core scan for C45 PHYs at registration time, on addresses that are not masked and do not already have a C22 device. Signed-off-by: Florian Fainelli Co-developed-by: Carlos Soto Signed-off-by: Carlos Soto Co-developed-by: Justin Chen Signed-off-by: Justin Chen --- drivers/net/mdio/mdio-bcm-unimac.c | 105 +++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 7 deletions(-) diff --git a/drivers/net/mdio/mdio-bcm-unimac.c b/drivers/net/mdio/mdio-bcm-unimac.c index 15a9880c3573..25f54ee754f9 100644 --- a/drivers/net/mdio/mdio-bcm-unimac.c +++ b/drivers/net/mdio/mdio-bcm-unimac.c @@ -24,10 +24,16 @@ #define MDIO_OP_SHIFT 26 #define MDIO_RD_C22 (2 << MDIO_OP_SHIFT) #define MDIO_WR_C22 (1 << MDIO_OP_SHIFT) +#define MDIO_ADDR_C45 (0 << MDIO_OP_SHIFT) +#define MDIO_WR_C45 (1 << MDIO_OP_SHIFT) +#define MDIO_RD_C45 (3 << MDIO_OP_SHIFT) #define MDIO_PMD_SHIFT 21 #define MDIO_PMD_MASK 0x1F #define MDIO_REG_SHIFT 16 #define MDIO_REG_MASK 0x1F +#define MDIO_DEVAD_SHIFT 16 +#define MDIO_DEVAD_MASK GENMASK(20, 16) +#define MDIO_C45_REG_MASK GENMASK(15, 0) #define MDIO_CFG 0x04 #define MDIO_C22 (1 << 0) @@ -96,7 +102,43 @@ static int unimac_mdio_poll(void *wait_func_data) MDIO_CMD); } -static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg) +static u32 unimac_mdio_c45_devad(int devad) +{ + return ((u32)devad << MDIO_DEVAD_SHIFT) & MDIO_DEVAD_MASK; +} + +static int unimac_mdio_addr_c45(struct unimac_mdio_priv *priv, + int phy_id, int devad, int reg) +{ + u32 cmd; + + /* Prepare the addr c45 operation */ + cmd = MDIO_ADDR_C45 | (phy_id << MDIO_PMD_SHIFT) | + unimac_mdio_c45_devad(devad) | (reg & MDIO_C45_REG_MASK); + unimac_mdio_writel(priv, cmd, MDIO_CMD); + + /* Start MDIO transaction */ + unimac_mdio_start(priv); + + return priv->wait_func(priv->wait_func_data); +} + +static void unimac_mdio_set_clause(struct unimac_mdio_priv *priv, bool is_c45) +{ + u32 cfg = unimac_mdio_readl(priv, MDIO_CFG); + u32 new_cfg = cfg; + + if (is_c45) + new_cfg &= ~MDIO_C22; + else + new_cfg |= MDIO_C22; + + if (new_cfg != cfg) + unimac_mdio_writel(priv, new_cfg, MDIO_CFG); +} + +static int unimac_mdio_do_read(struct mii_bus *bus, int phy_id, int devad, + int reg, bool is_c45) { struct unimac_mdio_priv *priv = bus->priv; int ret; @@ -107,8 +149,19 @@ static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg) return ret; /* Prepare the read operation */ - cmd = MDIO_RD_C22 | (phy_id << MDIO_PMD_SHIFT) | - (reg << MDIO_REG_SHIFT); + unimac_mdio_set_clause(priv, is_c45); + if (is_c45) { + ret = unimac_mdio_addr_c45(priv, phy_id, devad, reg); + if (ret) + goto out; + + cmd = MDIO_RD_C45 | (phy_id << MDIO_PMD_SHIFT) | + unimac_mdio_c45_devad(devad) | + (reg & MDIO_C45_REG_MASK); + } else { + cmd = MDIO_RD_C22 | (phy_id << MDIO_PMD_SHIFT) | + (reg << MDIO_REG_SHIFT); + } unimac_mdio_writel(priv, cmd, MDIO_CMD); /* Start MDIO transaction */ @@ -136,8 +189,8 @@ static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg) return ret; } -static int unimac_mdio_write(struct mii_bus *bus, int phy_id, - int reg, u16 val) +static int unimac_mdio_do_write(struct mii_bus *bus, int phy_id, int devad, + int reg, u16 val, bool is_c45) { struct unimac_mdio_priv *priv = bus->priv; u32 cmd; @@ -148,18 +201,50 @@ static int unimac_mdio_write(struct mii_bus *bus, int phy_id, return ret; /* Prepare the write operation */ - cmd = MDIO_WR_C22 | (phy_id << MDIO_PMD_SHIFT) | - (reg << MDIO_REG_SHIFT) | (0xffff & val); + unimac_mdio_set_clause(priv, is_c45); + if (is_c45) { + ret = unimac_mdio_addr_c45(priv, phy_id, devad, reg); + if (ret) + goto out; + + cmd = MDIO_WR_C45 | (phy_id << MDIO_PMD_SHIFT) | + unimac_mdio_c45_devad(devad) | (0xffff & val); + } else { + cmd = MDIO_WR_C22 | (phy_id << MDIO_PMD_SHIFT) | + (reg << MDIO_REG_SHIFT) | (0xffff & val); + } unimac_mdio_writel(priv, cmd, MDIO_CMD); unimac_mdio_start(priv); ret = priv->wait_func(priv->wait_func_data); +out: clk_disable_unprepare(priv->clk); return ret; } +static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg) +{ + return unimac_mdio_do_read(bus, phy_id, 0, reg, false); +} + +static int unimac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val) +{ + return unimac_mdio_do_write(bus, phy_id, 0, reg, val, false); +} + +static int unimac_mdio_read_c45(struct mii_bus *bus, int addr, int dev, int reg) +{ + return unimac_mdio_do_read(bus, addr, dev, reg, true); +} + +static int unimac_mdio_write_c45(struct mii_bus *bus, int addr, int dev, + int reg, u16 val) +{ + return unimac_mdio_do_write(bus, addr, dev, reg, val, true); +} + /* Workaround for integrated BCM7xxx Gigabit PHYs which have a problem with * their internal MDIO management controller making them fail to successfully * be read from or written to for the first transaction. We insert a dummy @@ -190,6 +275,10 @@ static int unimac_mdio_reset(struct mii_bus *bus) if (addr < 0) continue; + if (of_device_is_compatible(child, + "ethernet-phy-ieee802.3-c45")) + continue; + read_mask |= 1 << addr; } } @@ -301,6 +390,8 @@ static int unimac_mdio_probe(struct platform_device *pdev) bus->parent = &pdev->dev; bus->read = unimac_mdio_read; bus->write = unimac_mdio_write; + bus->read_c45 = unimac_mdio_read_c45; + bus->write_c45 = unimac_mdio_write_c45; bus->reset = unimac_mdio_reset; snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id); -- 2.34.1