|
| 1 | +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) |
| 2 | +/* Copyright 2019 NXP */ |
| 3 | + |
| 4 | +#include <linux/mdio.h> |
| 5 | +#include <linux/of_mdio.h> |
| 6 | +#include <linux/iopoll.h> |
| 7 | +#include <linux/of.h> |
| 8 | + |
| 9 | +#include "enetc_pf.h" |
| 10 | + |
| 11 | +struct enetc_mdio_regs { |
| 12 | + u32 mdio_cfg; /* MDIO configuration and status */ |
| 13 | + u32 mdio_ctl; /* MDIO control */ |
| 14 | + u32 mdio_data; /* MDIO data */ |
| 15 | + u32 mdio_addr; /* MDIO address */ |
| 16 | +}; |
| 17 | + |
| 18 | +#define bus_to_enetc_regs(bus) (struct enetc_mdio_regs __iomem *)((bus)->priv) |
| 19 | + |
| 20 | +#define ENETC_MDIO_REG_OFFSET 0x1c00 |
| 21 | +#define ENETC_MDC_DIV 258 |
| 22 | + |
| 23 | +#define MDIO_CFG_CLKDIV(x) ((((x) >> 1) & 0xff) << 8) |
| 24 | +#define MDIO_CFG_BSY BIT(0) |
| 25 | +#define MDIO_CFG_RD_ER BIT(1) |
| 26 | +#define MDIO_CFG_ENC45 BIT(6) |
| 27 | + /* external MDIO only - driven on neg MDC edge */ |
| 28 | +#define MDIO_CFG_NEG BIT(23) |
| 29 | + |
| 30 | +#define MDIO_CTL_DEV_ADDR(x) ((x) & 0x1f) |
| 31 | +#define MDIO_CTL_PORT_ADDR(x) (((x) & 0x1f) << 5) |
| 32 | +#define MDIO_CTL_READ BIT(15) |
| 33 | +#define MDIO_DATA(x) ((x) & 0xffff) |
| 34 | + |
| 35 | +#define TIMEOUT 1000 |
| 36 | +static int enetc_mdio_wait_complete(struct enetc_mdio_regs __iomem *regs) |
| 37 | +{ |
| 38 | + u32 val; |
| 39 | + |
| 40 | + return readx_poll_timeout(enetc_rd_reg, ®s->mdio_cfg, val, |
| 41 | + !(val & MDIO_CFG_BSY), 10, 10 * TIMEOUT); |
| 42 | +} |
| 43 | + |
| 44 | +static int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, |
| 45 | + u16 value) |
| 46 | +{ |
| 47 | + struct enetc_mdio_regs __iomem *regs = bus_to_enetc_regs(bus); |
| 48 | + u32 mdio_ctl, mdio_cfg; |
| 49 | + u16 dev_addr; |
| 50 | + int ret; |
| 51 | + |
| 52 | + mdio_cfg = MDIO_CFG_CLKDIV(ENETC_MDC_DIV) | MDIO_CFG_NEG; |
| 53 | + if (regnum & MII_ADDR_C45) { |
| 54 | + dev_addr = (regnum >> 16) & 0x1f; |
| 55 | + mdio_cfg |= MDIO_CFG_ENC45; |
| 56 | + } else { |
| 57 | + /* clause 22 (ie 1G) */ |
| 58 | + dev_addr = regnum & 0x1f; |
| 59 | + mdio_cfg &= ~MDIO_CFG_ENC45; |
| 60 | + } |
| 61 | + |
| 62 | + enetc_wr_reg(®s->mdio_cfg, mdio_cfg); |
| 63 | + |
| 64 | + ret = enetc_mdio_wait_complete(regs); |
| 65 | + if (ret) |
| 66 | + return ret; |
| 67 | + |
| 68 | + /* set port and dev addr */ |
| 69 | + mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr); |
| 70 | + enetc_wr_reg(®s->mdio_ctl, mdio_ctl); |
| 71 | + |
| 72 | + /* set the register address */ |
| 73 | + if (regnum & MII_ADDR_C45) { |
| 74 | + enetc_wr_reg(®s->mdio_addr, regnum & 0xffff); |
| 75 | + |
| 76 | + ret = enetc_mdio_wait_complete(regs); |
| 77 | + if (ret) |
| 78 | + return ret; |
| 79 | + } |
| 80 | + |
| 81 | + /* write the value */ |
| 82 | + enetc_wr_reg(®s->mdio_data, MDIO_DATA(value)); |
| 83 | + |
| 84 | + ret = enetc_mdio_wait_complete(regs); |
| 85 | + if (ret) |
| 86 | + return ret; |
| 87 | + |
| 88 | + return 0; |
| 89 | +} |
| 90 | + |
| 91 | +static int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum) |
| 92 | +{ |
| 93 | + struct enetc_mdio_regs __iomem *regs = bus_to_enetc_regs(bus); |
| 94 | + u32 mdio_ctl, mdio_cfg; |
| 95 | + u16 dev_addr, value; |
| 96 | + int ret; |
| 97 | + |
| 98 | + mdio_cfg = MDIO_CFG_CLKDIV(ENETC_MDC_DIV) | MDIO_CFG_NEG; |
| 99 | + if (regnum & MII_ADDR_C45) { |
| 100 | + dev_addr = (regnum >> 16) & 0x1f; |
| 101 | + mdio_cfg |= MDIO_CFG_ENC45; |
| 102 | + } else { |
| 103 | + dev_addr = regnum & 0x1f; |
| 104 | + mdio_cfg &= ~MDIO_CFG_ENC45; |
| 105 | + } |
| 106 | + |
| 107 | + enetc_wr_reg(®s->mdio_cfg, mdio_cfg); |
| 108 | + |
| 109 | + ret = enetc_mdio_wait_complete(regs); |
| 110 | + if (ret) |
| 111 | + return ret; |
| 112 | + |
| 113 | + /* set port and device addr */ |
| 114 | + mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr); |
| 115 | + enetc_wr_reg(®s->mdio_ctl, mdio_ctl); |
| 116 | + |
| 117 | + /* set the register address */ |
| 118 | + if (regnum & MII_ADDR_C45) { |
| 119 | + enetc_wr_reg(®s->mdio_addr, regnum & 0xffff); |
| 120 | + |
| 121 | + ret = enetc_mdio_wait_complete(regs); |
| 122 | + if (ret) |
| 123 | + return ret; |
| 124 | + } |
| 125 | + |
| 126 | + /* initiate the read */ |
| 127 | + enetc_wr_reg(®s->mdio_ctl, mdio_ctl | MDIO_CTL_READ); |
| 128 | + |
| 129 | + ret = enetc_mdio_wait_complete(regs); |
| 130 | + if (ret) |
| 131 | + return ret; |
| 132 | + |
| 133 | + /* return all Fs if nothing was there */ |
| 134 | + if (enetc_rd_reg(®s->mdio_cfg) & MDIO_CFG_RD_ER) { |
| 135 | + dev_dbg(&bus->dev, |
| 136 | + "Error while reading PHY%d reg at %d.%hhu\n", |
| 137 | + phy_id, dev_addr, regnum); |
| 138 | + return 0xffff; |
| 139 | + } |
| 140 | + |
| 141 | + value = enetc_rd_reg(®s->mdio_data) & 0xffff; |
| 142 | + |
| 143 | + return value; |
| 144 | +} |
| 145 | + |
| 146 | +int enetc_mdio_probe(struct enetc_pf *pf) |
| 147 | +{ |
| 148 | + struct device *dev = &pf->si->pdev->dev; |
| 149 | + struct enetc_mdio_regs __iomem *regs; |
| 150 | + struct device_node *np; |
| 151 | + struct mii_bus *bus; |
| 152 | + int ret; |
| 153 | + |
| 154 | + bus = mdiobus_alloc_size(sizeof(regs)); |
| 155 | + if (!bus) |
| 156 | + return -ENOMEM; |
| 157 | + |
| 158 | + bus->name = "Freescale ENETC MDIO Bus"; |
| 159 | + bus->read = enetc_mdio_read; |
| 160 | + bus->write = enetc_mdio_write; |
| 161 | + bus->parent = dev; |
| 162 | + snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev)); |
| 163 | + |
| 164 | + /* store the enetc mdio base address for this bus */ |
| 165 | + regs = pf->si->hw.port + ENETC_MDIO_REG_OFFSET; |
| 166 | + bus->priv = regs; |
| 167 | + |
| 168 | + np = of_get_child_by_name(dev->of_node, "mdio"); |
| 169 | + if (!np) { |
| 170 | + dev_err(dev, "MDIO node missing\n"); |
| 171 | + ret = -EINVAL; |
| 172 | + goto err_registration; |
| 173 | + } |
| 174 | + |
| 175 | + ret = of_mdiobus_register(bus, np); |
| 176 | + if (ret) { |
| 177 | + of_node_put(np); |
| 178 | + dev_err(dev, "cannot register MDIO bus\n"); |
| 179 | + goto err_registration; |
| 180 | + } |
| 181 | + |
| 182 | + of_node_put(np); |
| 183 | + pf->mdio = bus; |
| 184 | + |
| 185 | + return 0; |
| 186 | + |
| 187 | +err_registration: |
| 188 | + mdiobus_free(bus); |
| 189 | + |
| 190 | + return ret; |
| 191 | +} |
| 192 | + |
| 193 | +void enetc_mdio_remove(struct enetc_pf *pf) |
| 194 | +{ |
| 195 | + if (pf->mdio) { |
| 196 | + mdiobus_unregister(pf->mdio); |
| 197 | + mdiobus_free(pf->mdio); |
| 198 | + } |
| 199 | +} |
0 commit comments