1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 /* Copyright (c) 2015, The Linux Foundation. All rights reserved. */ 3 /* Copyright (c) 2020 Sartura Ltd. */ 4 5 #include <linux/delay.h> 6 #include <linux/io.h> 7 #include <linux/iopoll.h> 8 #include <linux/kernel.h> 9 #include <linux/module.h> 10 #include <linux/of_address.h> 11 #include <linux/of_mdio.h> 12 #include <linux/phy.h> 13 #include <linux/platform_device.h> 14 15 #define MDIO_MODE_REG 0x40 16 #define MDIO_ADDR_REG 0x44 17 #define MDIO_DATA_WRITE_REG 0x48 18 #define MDIO_DATA_READ_REG 0x4c 19 #define MDIO_CMD_REG 0x50 20 #define MDIO_CMD_ACCESS_BUSY BIT(16) 21 #define MDIO_CMD_ACCESS_START BIT(8) 22 #define MDIO_CMD_ACCESS_CODE_READ 0 23 #define MDIO_CMD_ACCESS_CODE_WRITE 1 24 #define MDIO_CMD_ACCESS_CODE_C45_ADDR 0 25 #define MDIO_CMD_ACCESS_CODE_C45_WRITE 1 26 #define MDIO_CMD_ACCESS_CODE_C45_READ 2 27 28 /* 0 = Clause 22, 1 = Clause 45 */ 29 #define MDIO_MODE_C45 BIT(8) 30 31 #define IPQ4019_MDIO_TIMEOUT 10000 32 #define IPQ4019_MDIO_SLEEP 10 33 34 struct ipq4019_mdio_data { 35 void __iomem *membase; 36 }; 37 38 static int ipq4019_mdio_wait_busy(struct mii_bus *bus) 39 { 40 struct ipq4019_mdio_data *priv = bus->priv; 41 unsigned int busy; 42 43 return readl_poll_timeout(priv->membase + MDIO_CMD_REG, busy, 44 (busy & MDIO_CMD_ACCESS_BUSY) == 0, 45 IPQ4019_MDIO_SLEEP, IPQ4019_MDIO_TIMEOUT); 46 } 47 48 static int ipq4019_mdio_read(struct mii_bus *bus, int mii_id, int regnum) 49 { 50 struct ipq4019_mdio_data *priv = bus->priv; 51 unsigned int data; 52 unsigned int cmd; 53 54 if (ipq4019_mdio_wait_busy(bus)) 55 return -ETIMEDOUT; 56 57 /* Clause 45 support */ 58 if (regnum & MII_ADDR_C45) { 59 unsigned int mmd = (regnum >> 16) & 0x1F; 60 unsigned int reg = regnum & 0xFFFF; 61 62 /* Enter Clause 45 mode */ 63 data = readl(priv->membase + MDIO_MODE_REG); 64 65 data |= MDIO_MODE_C45; 66 67 writel(data, priv->membase + MDIO_MODE_REG); 68 69 /* issue the phy address and mmd */ 70 writel((mii_id << 8) | mmd, priv->membase + MDIO_ADDR_REG); 71 72 /* issue reg */ 73 writel(reg, priv->membase + MDIO_DATA_WRITE_REG); 74 75 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; 76 } else { 77 /* Enter Clause 22 mode */ 78 data = readl(priv->membase + MDIO_MODE_REG); 79 80 data &= ~MDIO_MODE_C45; 81 82 writel(data, priv->membase + MDIO_MODE_REG); 83 84 /* issue the phy address and reg */ 85 writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); 86 87 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_READ; 88 } 89 90 /* issue read command */ 91 writel(cmd, priv->membase + MDIO_CMD_REG); 92 93 /* Wait read complete */ 94 if (ipq4019_mdio_wait_busy(bus)) 95 return -ETIMEDOUT; 96 97 if (regnum & MII_ADDR_C45) { 98 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_READ; 99 100 writel(cmd, priv->membase + MDIO_CMD_REG); 101 102 if (ipq4019_mdio_wait_busy(bus)) 103 return -ETIMEDOUT; 104 } 105 106 /* Read and return data */ 107 return readl(priv->membase + MDIO_DATA_READ_REG); 108 } 109 110 static int ipq4019_mdio_write(struct mii_bus *bus, int mii_id, int regnum, 111 u16 value) 112 { 113 struct ipq4019_mdio_data *priv = bus->priv; 114 unsigned int data; 115 unsigned int cmd; 116 117 if (ipq4019_mdio_wait_busy(bus)) 118 return -ETIMEDOUT; 119 120 /* Clause 45 support */ 121 if (regnum & MII_ADDR_C45) { 122 unsigned int mmd = (regnum >> 16) & 0x1F; 123 unsigned int reg = regnum & 0xFFFF; 124 125 /* Enter Clause 45 mode */ 126 data = readl(priv->membase + MDIO_MODE_REG); 127 128 data |= MDIO_MODE_C45; 129 130 writel(data, priv->membase + MDIO_MODE_REG); 131 132 /* issue the phy address and mmd */ 133 writel((mii_id << 8) | mmd, priv->membase + MDIO_ADDR_REG); 134 135 /* issue reg */ 136 writel(reg, priv->membase + MDIO_DATA_WRITE_REG); 137 138 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; 139 140 writel(cmd, priv->membase + MDIO_CMD_REG); 141 142 if (ipq4019_mdio_wait_busy(bus)) 143 return -ETIMEDOUT; 144 } else { 145 /* Enter Clause 22 mode */ 146 data = readl(priv->membase + MDIO_MODE_REG); 147 148 data &= ~MDIO_MODE_C45; 149 150 writel(data, priv->membase + MDIO_MODE_REG); 151 152 /* issue the phy address and reg */ 153 writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); 154 } 155 156 /* issue write data */ 157 writel(value, priv->membase + MDIO_DATA_WRITE_REG); 158 159 /* issue write command */ 160 if (regnum & MII_ADDR_C45) 161 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_WRITE; 162 else 163 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_WRITE; 164 165 writel(cmd, priv->membase + MDIO_CMD_REG); 166 167 /* Wait write complete */ 168 if (ipq4019_mdio_wait_busy(bus)) 169 return -ETIMEDOUT; 170 171 return 0; 172 } 173 174 static int ipq4019_mdio_probe(struct platform_device *pdev) 175 { 176 struct ipq4019_mdio_data *priv; 177 struct mii_bus *bus; 178 int ret; 179 180 bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv)); 181 if (!bus) 182 return -ENOMEM; 183 184 priv = bus->priv; 185 186 priv->membase = devm_platform_ioremap_resource(pdev, 0); 187 if (IS_ERR(priv->membase)) 188 return PTR_ERR(priv->membase); 189 190 bus->name = "ipq4019_mdio"; 191 bus->read = ipq4019_mdio_read; 192 bus->write = ipq4019_mdio_write; 193 bus->parent = &pdev->dev; 194 snprintf(bus->id, MII_BUS_ID_SIZE, "%s%d", pdev->name, pdev->id); 195 196 ret = of_mdiobus_register(bus, pdev->dev.of_node); 197 if (ret) { 198 dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); 199 return ret; 200 } 201 202 platform_set_drvdata(pdev, bus); 203 204 return 0; 205 } 206 207 static int ipq4019_mdio_remove(struct platform_device *pdev) 208 { 209 struct mii_bus *bus = platform_get_drvdata(pdev); 210 211 mdiobus_unregister(bus); 212 213 return 0; 214 } 215 216 static const struct of_device_id ipq4019_mdio_dt_ids[] = { 217 { .compatible = "qcom,ipq4019-mdio" }, 218 { } 219 }; 220 MODULE_DEVICE_TABLE(of, ipq4019_mdio_dt_ids); 221 222 static struct platform_driver ipq4019_mdio_driver = { 223 .probe = ipq4019_mdio_probe, 224 .remove = ipq4019_mdio_remove, 225 .driver = { 226 .name = "ipq4019-mdio", 227 .of_match_table = ipq4019_mdio_dt_ids, 228 }, 229 }; 230 231 module_platform_driver(ipq4019_mdio_driver); 232 233 MODULE_DESCRIPTION("ipq4019 MDIO interface driver"); 234 MODULE_AUTHOR("Qualcomm Atheros"); 235 MODULE_LICENSE("Dual BSD/GPL"); 236