From: Ciprian Regus The OPEN Alliance TC6 specification defines two ways for the SPI host to access the registers of the PHY integrated in a MAC-PHY: direct access, where the PHY registers are mapped into the MAC-PHY register space, and indirect access, through the MDIOACC registers. A MAC-PHY reports support for direct access through the DPRAC bit in the STDCAP register. The framework currently supports only direct access and fails to register the MDIO bus when DPRAC is not set. Read STDCAP when registering the MDIO bus and, if direct access is not supported, implement the Clause 22 and Clause 45 MDIO operations as indirect transactions through MDIOACC: for Clause 45, write the address command to MDIOACC0 and the read or write command to MDIOACC1; for Clause 22, write the command to MDIOACC1. Then poll for TRDONE. If TAERR (turnaround error) is set, no PHY answered at that address and the DATA field must be ignored. Return 0xffff in that case, as for an empty address on an MDIO bus, so that the PHY bus scan skips the address instead of failing. Return -EIO for a write. Signed-off-by: Ciprian Regus Signed-off-by: Iustin Stolniceanu --- drivers/net/ethernet/oa_tc6.c | 171 +++++++++++++++++++++++++++++++--- include/linux/oa_tc6.h | 15 +++ 2 files changed, 171 insertions(+), 15 deletions(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 68437b226528..1c67ea0ff22b 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -89,6 +89,7 @@ struct oa_tc6 { bool int_flag; bool disable_traffic; bool prot_ctrl; + bool mdio_indirect_mode; u32 reset_settle_us; enum oa_tc6_quirk_flag quirk_flags; struct gpio_desc *reset_gpio; @@ -435,24 +436,149 @@ int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, } EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms); -static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6) +static void oa_tc6_handle_link_change(struct net_device *netdev) { - u32 regval; + phy_print_status(netdev->phydev); +} + +static int oa_tc6_mdioacc_wait_done(struct oa_tc6 *tc6, u32 *regval) +{ + int ret, err; + + ret = read_poll_timeout(oa_tc6_read_register, err, + err || (*regval & OA_TC6_MDIOACC_TRDONE), + 100, 30000, false, tc6, OA_TC6_REG_MDIOACC(1), + regval); + if (ret) + return ret; + + return err; +} + +/* On a turnaround error no PHY answered and the DATA field must be ignored. + * Return all ones, like an MDIO bus with no PHY at that address, so that + * bus scanning skips the address instead of failing. + */ +static int oa_tc6_mdioacc_read_data(u32 reg_val) +{ + if (reg_val & OA_TC6_MDIOACC_TAERR) + return 0xffff; + + return FIELD_GET(OA_TC6_MDIOACC_DATA, reg_val); +} + +static int oa_tc6_mdioacc_write_done(struct oa_tc6 *tc6) +{ + u32 reg_val; int ret; - ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, ®val); + ret = oa_tc6_mdioacc_wait_done(tc6, ®_val); if (ret) return ret; - if (!(regval & OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS)) - return -ENODEV; + return reg_val & OA_TC6_MDIOACC_TAERR ? -EIO : 0; +} - return 0; +static int oa_tc6_indirect_mdiobus_read_c45(struct oa_tc6 *tc6, int addr, + int devnum, int regnum) +{ + u32 reg_val; + int ret; + + reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_ADDR); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C45); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, devnum); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DATA, regnum); + + ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(0), reg_val); + if (ret) + return ret; + + reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_RD); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C45); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, devnum); + + ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(1), reg_val); + if (ret) + return ret; + + ret = oa_tc6_mdioacc_wait_done(tc6, ®_val); + if (ret) + return ret; + + return oa_tc6_mdioacc_read_data(reg_val); } -static void oa_tc6_handle_link_change(struct net_device *netdev) +static int oa_tc6_indirect_mdiobus_write_c45(struct oa_tc6 *tc6, int addr, + int devnum, int regnum, u16 val) { - phy_print_status(netdev->phydev); + u32 reg_val; + int ret; + + reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_ADDR); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C45); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, devnum); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DATA, regnum); + + ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(0), reg_val); + if (ret) + return ret; + + reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_WR); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C45); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, devnum); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DATA, val); + + ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(1), reg_val); + if (ret) + return ret; + + return oa_tc6_mdioacc_write_done(tc6); +} + +static int oa_tc6_indirect_mdiobus_read(struct oa_tc6 *tc6, int addr, + int regnum) +{ + u32 reg_val; + int ret; + + reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_RD); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C22); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, regnum); + + ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(1), reg_val); + if (ret) + return ret; + + ret = oa_tc6_mdioacc_wait_done(tc6, ®_val); + if (ret) + return ret; + + return oa_tc6_mdioacc_read_data(reg_val); +} + +static int oa_tc6_indirect_mdiobus_write(struct oa_tc6 *tc6, int addr, + int regnum, u16 val) +{ + u32 reg_val; + int ret; + + reg_val = FIELD_PREP(OA_TC6_MDIOACC_OP, OA_TC6_MDIOACC_OP_WR); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_ST, OA_TC6_MDIOACC_ST_C22); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_PRTAD, addr); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DEVAD, regnum); + reg_val |= FIELD_PREP(OA_TC6_MDIOACC_DATA, val); + + ret = oa_tc6_write_register(tc6, OA_TC6_REG_MDIOACC(1), reg_val); + if (ret) + return ret; + + return oa_tc6_mdioacc_write_done(tc6); } static int oa_tc6_mdiobus_read(struct mii_bus *bus, int addr, int regnum) @@ -461,6 +587,9 @@ static int oa_tc6_mdiobus_read(struct mii_bus *bus, int addr, int regnum) u32 regval; int ret; + if (tc6->mdio_indirect_mode) + return oa_tc6_indirect_mdiobus_read(tc6, addr, regnum); + ret = oa_tc6_read_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE | (regnum & OA_TC6_PHY_STD_REG_ADDR_MASK), ®val); @@ -475,6 +604,9 @@ static int oa_tc6_mdiobus_write(struct mii_bus *bus, int addr, int regnum, { struct oa_tc6 *tc6 = bus->priv; + if (tc6->mdio_indirect_mode) + return oa_tc6_indirect_mdiobus_write(tc6, addr, regnum, val); + return oa_tc6_write_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE | (regnum & OA_TC6_PHY_STD_REG_ADDR_MASK), val); @@ -506,6 +638,10 @@ int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, int mms; int ret; + if (tc6->mdio_indirect_mode) + return oa_tc6_indirect_mdiobus_read_c45(tc6, addr, devnum, + regnum); + mms = oa_tc6_get_phy_c45_mms(devnum); if (mms < 0) return mms; @@ -524,6 +660,10 @@ int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, struct oa_tc6 *tc6 = bus->priv; int mms; + if (tc6->mdio_indirect_mode) + return oa_tc6_indirect_mdiobus_write_c45(tc6, addr, devnum, + regnum, val); + mms = oa_tc6_get_phy_c45_mms(devnum); if (mms < 0) return mms; @@ -534,8 +674,16 @@ EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_write_c45); static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6) { + u32 regval; int ret; + ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, ®val); + if (ret) + return ret; + + tc6->mdio_indirect_mode = !(OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS & + regval); + tc6->mdiobus = mdiobus_alloc(); if (!tc6->mdiobus) { netdev_err(tc6->netdev, "MDIO bus alloc failed\n"); @@ -586,13 +734,6 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6) if (tc6->quirk_flags & OA_TC6_BROKEN_PHY) return 0; - ret = oa_tc6_check_phy_reg_direct_access_capability(tc6); - if (ret) { - netdev_err(tc6->netdev, - "Direct PHY register access is not supported by the MAC-PHY\n"); - return ret; - } - ret = oa_tc6_mdiobus_register(tc6); if (ret) return ret; diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h index 139dd4c33950..e0627b99e569 100644 --- a/include/linux/oa_tc6.h +++ b/include/linux/oa_tc6.h @@ -50,6 +50,21 @@ #define OA_TC6_INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \ GENMASK(12, 7)) +#define OA_TC6_REG_MDIOACC(x) (0x20 + (x)) +#define OA_TC6_MDIOACC_TRDONE BIT(31) +#define OA_TC6_MDIOACC_TAERR BIT(30) +#define OA_TC6_MDIOACC_ST GENMASK(29, 28) +#define OA_TC6_MDIOACC_OP GENMASK(27, 26) +#define OA_TC6_MDIOACC_PRTAD GENMASK(25, 21) +#define OA_TC6_MDIOACC_DEVAD GENMASK(20, 16) +#define OA_TC6_MDIOACC_DATA GENMASK(15, 0) + +#define OA_TC6_MDIOACC_ST_C45 0x0 +#define OA_TC6_MDIOACC_ST_C22 0x1 +#define OA_TC6_MDIOACC_OP_ADDR 0x0 +#define OA_TC6_MDIOACC_OP_WR 0x1 +#define OA_TC6_MDIOACC_OP_RD 0x3 + /* PHY Clause 22 registers base address and mask */ #define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00 #define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F -- 2.43.0