| 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | /* Copyright (C) 2019 IBM Corp. */ |
| 3 | |
| 4 | #include <linux/bitfield.h> |
| 5 | #include <linux/delay.h> |
| 6 | #include <linux/reset.h> |
| 7 | #include <linux/iopoll.h> |
| 8 | #include <linux/mdio.h> |
| 9 | #include <linux/module.h> |
| 10 | #include <linux/of.h> |
| 11 | #include <linux/of_mdio.h> |
| 12 | #include <linux/phy.h> |
| 13 | #include <linux/platform_device.h> |
| 14 | |
| 15 | #define DRV_NAME "mdio-aspeed" |
| 16 | |
| 17 | #define ASPEED_MDIO_CTRL 0x0 |
| 18 | #define ASPEED_MDIO_CTRL_FIRE BIT(31) |
| 19 | #define ASPEED_MDIO_CTRL_ST BIT(28) |
| 20 | #define ASPEED_MDIO_CTRL_ST_C45 0 |
| 21 | #define ASPEED_MDIO_CTRL_ST_C22 1 |
| 22 | #define ASPEED_MDIO_CTRL_OP GENMASK(27, 26) |
| 23 | #define MDIO_C22_OP_WRITE 0b01 |
| 24 | #define MDIO_C22_OP_READ 0b10 |
| 25 | #define MDIO_C45_OP_ADDR 0b00 |
| 26 | #define MDIO_C45_OP_WRITE 0b01 |
| 27 | #define MDIO_C45_OP_PREAD 0b10 |
| 28 | #define MDIO_C45_OP_READ 0b11 |
| 29 | #define ASPEED_MDIO_CTRL_PHYAD GENMASK(25, 21) |
| 30 | #define ASPEED_MDIO_CTRL_REGAD GENMASK(20, 16) |
| 31 | #define ASPEED_MDIO_CTRL_MIIWDATA GENMASK(15, 0) |
| 32 | |
| 33 | #define ASPEED_MDIO_DATA 0x4 |
| 34 | #define ASPEED_MDIO_DATA_MDC_THRES GENMASK(31, 24) |
| 35 | #define ASPEED_MDIO_DATA_MDIO_EDGE BIT(23) |
| 36 | #define ASPEED_MDIO_DATA_MDIO_LATCH GENMASK(22, 20) |
| 37 | #define ASPEED_MDIO_DATA_IDLE BIT(16) |
| 38 | #define ASPEED_MDIO_DATA_MIIRDATA GENMASK(15, 0) |
| 39 | |
| 40 | #define ASPEED_MDIO_INTERVAL_US 100 |
| 41 | #define ASPEED_MDIO_TIMEOUT_US (ASPEED_MDIO_INTERVAL_US * 10) |
| 42 | |
| 43 | struct aspeed_mdio { |
| 44 | void __iomem *base; |
| 45 | struct reset_control *reset; |
| 46 | }; |
| 47 | |
| 48 | static int aspeed_mdio_op(struct mii_bus *bus, u8 st, u8 op, u8 phyad, u8 regad, |
| 49 | u16 data) |
| 50 | { |
| 51 | struct aspeed_mdio *ctx = bus->priv; |
| 52 | u32 ctrl; |
| 53 | |
| 54 | dev_dbg(&bus->dev, "%s: st: %u op: %u, phyad: %u, regad: %u, data: %u\n" , |
| 55 | __func__, st, op, phyad, regad, data); |
| 56 | |
| 57 | ctrl = ASPEED_MDIO_CTRL_FIRE |
| 58 | | FIELD_PREP(ASPEED_MDIO_CTRL_ST, st) |
| 59 | | FIELD_PREP(ASPEED_MDIO_CTRL_OP, op) |
| 60 | | FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, phyad) |
| 61 | | FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, regad) |
| 62 | | FIELD_PREP(ASPEED_MDIO_DATA_MIIRDATA, data); |
| 63 | |
| 64 | iowrite32(ctrl, ctx->base + ASPEED_MDIO_CTRL); |
| 65 | |
| 66 | /* Workaround for read-after-write issue. |
| 67 | * The controller may return stale data if a read follows immediately |
| 68 | * after a write. A dummy read forces the hardware to update its |
| 69 | * internal state, ensuring that the next real read returns correct data. |
| 70 | */ |
| 71 | ioread32(ctx->base + ASPEED_MDIO_CTRL); |
| 72 | |
| 73 | return readl_poll_timeout(ctx->base + ASPEED_MDIO_CTRL, ctrl, |
| 74 | !(ctrl & ASPEED_MDIO_CTRL_FIRE), |
| 75 | ASPEED_MDIO_INTERVAL_US, |
| 76 | ASPEED_MDIO_TIMEOUT_US); |
| 77 | } |
| 78 | |
| 79 | static int aspeed_mdio_get_data(struct mii_bus *bus) |
| 80 | { |
| 81 | struct aspeed_mdio *ctx = bus->priv; |
| 82 | u32 data; |
| 83 | int rc; |
| 84 | |
| 85 | rc = readl_poll_timeout(ctx->base + ASPEED_MDIO_DATA, data, |
| 86 | data & ASPEED_MDIO_DATA_IDLE, |
| 87 | ASPEED_MDIO_INTERVAL_US, |
| 88 | ASPEED_MDIO_TIMEOUT_US); |
| 89 | if (rc < 0) |
| 90 | return rc; |
| 91 | |
| 92 | return FIELD_GET(ASPEED_MDIO_DATA_MIIRDATA, data); |
| 93 | } |
| 94 | |
| 95 | static int aspeed_mdio_read_c22(struct mii_bus *bus, int addr, int regnum) |
| 96 | { |
| 97 | int rc; |
| 98 | |
| 99 | rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C22, MDIO_C22_OP_READ, |
| 100 | phyad: addr, regad: regnum, data: 0); |
| 101 | if (rc < 0) |
| 102 | return rc; |
| 103 | |
| 104 | return aspeed_mdio_get_data(bus); |
| 105 | } |
| 106 | |
| 107 | static int aspeed_mdio_write_c22(struct mii_bus *bus, int addr, int regnum, |
| 108 | u16 val) |
| 109 | { |
| 110 | return aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C22, MDIO_C22_OP_WRITE, |
| 111 | phyad: addr, regad: regnum, data: val); |
| 112 | } |
| 113 | |
| 114 | static int aspeed_mdio_read_c45(struct mii_bus *bus, int addr, int devad, |
| 115 | int regnum) |
| 116 | { |
| 117 | int rc; |
| 118 | |
| 119 | rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_ADDR, |
| 120 | phyad: addr, regad: devad, data: regnum); |
| 121 | if (rc < 0) |
| 122 | return rc; |
| 123 | |
| 124 | rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_READ, |
| 125 | phyad: addr, regad: devad, data: 0); |
| 126 | if (rc < 0) |
| 127 | return rc; |
| 128 | |
| 129 | return aspeed_mdio_get_data(bus); |
| 130 | } |
| 131 | |
| 132 | static int aspeed_mdio_write_c45(struct mii_bus *bus, int addr, int devad, |
| 133 | int regnum, u16 val) |
| 134 | { |
| 135 | int rc; |
| 136 | |
| 137 | rc = aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_ADDR, |
| 138 | phyad: addr, regad: devad, data: regnum); |
| 139 | if (rc < 0) |
| 140 | return rc; |
| 141 | |
| 142 | return aspeed_mdio_op(bus, ASPEED_MDIO_CTRL_ST_C45, MDIO_C45_OP_WRITE, |
| 143 | phyad: addr, regad: devad, data: val); |
| 144 | } |
| 145 | |
| 146 | static int aspeed_mdio_probe(struct platform_device *pdev) |
| 147 | { |
| 148 | struct aspeed_mdio *ctx; |
| 149 | struct mii_bus *bus; |
| 150 | int rc; |
| 151 | |
| 152 | bus = devm_mdiobus_alloc_size(dev: &pdev->dev, sizeof_priv: sizeof(*ctx)); |
| 153 | if (!bus) |
| 154 | return -ENOMEM; |
| 155 | |
| 156 | ctx = bus->priv; |
| 157 | ctx->base = devm_platform_ioremap_resource(pdev, index: 0); |
| 158 | if (IS_ERR(ptr: ctx->base)) |
| 159 | return PTR_ERR(ptr: ctx->base); |
| 160 | |
| 161 | ctx->reset = devm_reset_control_get_optional_shared(dev: &pdev->dev, NULL); |
| 162 | if (IS_ERR(ptr: ctx->reset)) |
| 163 | return PTR_ERR(ptr: ctx->reset); |
| 164 | |
| 165 | reset_control_deassert(rstc: ctx->reset); |
| 166 | |
| 167 | bus->name = DRV_NAME; |
| 168 | snprintf(buf: bus->id, MII_BUS_ID_SIZE, fmt: "%s%d" , pdev->name, pdev->id); |
| 169 | bus->parent = &pdev->dev; |
| 170 | bus->read = aspeed_mdio_read_c22; |
| 171 | bus->write = aspeed_mdio_write_c22; |
| 172 | bus->read_c45 = aspeed_mdio_read_c45; |
| 173 | bus->write_c45 = aspeed_mdio_write_c45; |
| 174 | |
| 175 | rc = of_mdiobus_register(mdio: bus, np: pdev->dev.of_node); |
| 176 | if (rc) { |
| 177 | dev_err(&pdev->dev, "Cannot register MDIO bus!\n" ); |
| 178 | reset_control_assert(rstc: ctx->reset); |
| 179 | return rc; |
| 180 | } |
| 181 | |
| 182 | platform_set_drvdata(pdev, data: bus); |
| 183 | |
| 184 | return 0; |
| 185 | } |
| 186 | |
| 187 | static void aspeed_mdio_remove(struct platform_device *pdev) |
| 188 | { |
| 189 | struct mii_bus *bus = (struct mii_bus *)platform_get_drvdata(pdev); |
| 190 | struct aspeed_mdio *ctx = bus->priv; |
| 191 | |
| 192 | reset_control_assert(rstc: ctx->reset); |
| 193 | mdiobus_unregister(bus); |
| 194 | } |
| 195 | |
| 196 | static const struct of_device_id aspeed_mdio_of_match[] = { |
| 197 | { .compatible = "aspeed,ast2600-mdio" , }, |
| 198 | { }, |
| 199 | }; |
| 200 | MODULE_DEVICE_TABLE(of, aspeed_mdio_of_match); |
| 201 | |
| 202 | static struct platform_driver aspeed_mdio_driver = { |
| 203 | .driver = { |
| 204 | .name = DRV_NAME, |
| 205 | .of_match_table = aspeed_mdio_of_match, |
| 206 | }, |
| 207 | .probe = aspeed_mdio_probe, |
| 208 | .remove = aspeed_mdio_remove, |
| 209 | }; |
| 210 | |
| 211 | module_platform_driver(aspeed_mdio_driver); |
| 212 | |
| 213 | MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>" ); |
| 214 | MODULE_LICENSE("GPL" ); |
| 215 | MODULE_DESCRIPTION("ASPEED MDIO bus controller" ); |
| 216 | |