Commit | Line | Data |
---|---|---|
466ed24f RM |
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> | |
466ed24f RM |
6 | #include <linux/io.h> |
7 | #include <linux/iopoll.h> | |
1bf34366 CJ |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> | |
466ed24f RM |
10 | #include <linux/of_address.h> |
11 | #include <linux/of_mdio.h> | |
12 | #include <linux/phy.h> | |
13 | #include <linux/platform_device.h> | |
23a890d4 | 14 | #include <linux/clk.h> |
466ed24f | 15 | |
06fb5606 | 16 | #define MDIO_MODE_REG 0x40 |
466ed24f RM |
17 | #define MDIO_ADDR_REG 0x44 |
18 | #define MDIO_DATA_WRITE_REG 0x48 | |
19 | #define MDIO_DATA_READ_REG 0x4c | |
20 | #define MDIO_CMD_REG 0x50 | |
21 | #define MDIO_CMD_ACCESS_BUSY BIT(16) | |
22 | #define MDIO_CMD_ACCESS_START BIT(8) | |
23 | #define MDIO_CMD_ACCESS_CODE_READ 0 | |
24 | #define MDIO_CMD_ACCESS_CODE_WRITE 1 | |
06fb5606 RM |
25 | #define MDIO_CMD_ACCESS_CODE_C45_ADDR 0 |
26 | #define MDIO_CMD_ACCESS_CODE_C45_WRITE 1 | |
27 | #define MDIO_CMD_ACCESS_CODE_C45_READ 2 | |
28 | ||
29 | /* 0 = Clause 22, 1 = Clause 45 */ | |
30 | #define MDIO_MODE_C45 BIT(8) | |
466ed24f | 31 | |
b840ec1e RM |
32 | #define IPQ4019_MDIO_TIMEOUT 10000 |
33 | #define IPQ4019_MDIO_SLEEP 10 | |
466ed24f | 34 | |
23a890d4 LJ |
35 | /* MDIO clock source frequency is fixed to 100M */ |
36 | #define IPQ_MDIO_CLK_RATE 100000000 | |
37 | ||
38 | #define IPQ_PHY_SET_DELAY_US 100000 | |
39 | ||
466ed24f RM |
40 | struct ipq4019_mdio_data { |
41 | void __iomem *membase; | |
23a890d4 LJ |
42 | void __iomem *eth_ldo_rdy; |
43 | struct clk *mdio_clk; | |
466ed24f RM |
44 | }; |
45 | ||
46 | static int ipq4019_mdio_wait_busy(struct mii_bus *bus) | |
47 | { | |
48 | struct ipq4019_mdio_data *priv = bus->priv; | |
49 | unsigned int busy; | |
50 | ||
51 | return readl_poll_timeout(priv->membase + MDIO_CMD_REG, busy, | |
52 | (busy & MDIO_CMD_ACCESS_BUSY) == 0, | |
b840ec1e | 53 | IPQ4019_MDIO_SLEEP, IPQ4019_MDIO_TIMEOUT); |
466ed24f RM |
54 | } |
55 | ||
c58e3994 AL |
56 | static int ipq4019_mdio_read_c45(struct mii_bus *bus, int mii_id, int mmd, |
57 | int reg) | |
466ed24f RM |
58 | { |
59 | struct ipq4019_mdio_data *priv = bus->priv; | |
06fb5606 | 60 | unsigned int data; |
466ed24f RM |
61 | unsigned int cmd; |
62 | ||
466ed24f RM |
63 | if (ipq4019_mdio_wait_busy(bus)) |
64 | return -ETIMEDOUT; | |
65 | ||
c58e3994 | 66 | data = readl(priv->membase + MDIO_MODE_REG); |
06fb5606 | 67 | |
c58e3994 | 68 | data |= MDIO_MODE_C45; |
06fb5606 | 69 | |
c58e3994 | 70 | writel(data, priv->membase + MDIO_MODE_REG); |
06fb5606 | 71 | |
c58e3994 AL |
72 | /* issue the phy address and mmd */ |
73 | writel((mii_id << 8) | mmd, priv->membase + MDIO_ADDR_REG); | |
06fb5606 | 74 | |
c58e3994 AL |
75 | /* issue reg */ |
76 | writel(reg, priv->membase + MDIO_DATA_WRITE_REG); | |
06fb5606 | 77 | |
c58e3994 | 78 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; |
466ed24f | 79 | |
c58e3994 AL |
80 | /* issue read command */ |
81 | writel(cmd, priv->membase + MDIO_CMD_REG); | |
06fb5606 | 82 | |
c58e3994 AL |
83 | /* Wait read complete */ |
84 | if (ipq4019_mdio_wait_busy(bus)) | |
85 | return -ETIMEDOUT; | |
06fb5606 | 86 | |
c58e3994 | 87 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_READ; |
06fb5606 | 88 | |
c58e3994 | 89 | writel(cmd, priv->membase + MDIO_CMD_REG); |
06fb5606 | 90 | |
c58e3994 AL |
91 | if (ipq4019_mdio_wait_busy(bus)) |
92 | return -ETIMEDOUT; | |
466ed24f | 93 | |
c58e3994 AL |
94 | /* Read and return data */ |
95 | return readl(priv->membase + MDIO_DATA_READ_REG); | |
96 | } | |
97 | ||
98 | static int ipq4019_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum) | |
99 | { | |
100 | struct ipq4019_mdio_data *priv = bus->priv; | |
101 | unsigned int data; | |
102 | unsigned int cmd; | |
466ed24f | 103 | |
466ed24f RM |
104 | if (ipq4019_mdio_wait_busy(bus)) |
105 | return -ETIMEDOUT; | |
106 | ||
c58e3994 | 107 | data = readl(priv->membase + MDIO_MODE_REG); |
06fb5606 | 108 | |
c58e3994 | 109 | data &= ~MDIO_MODE_C45; |
06fb5606 | 110 | |
c58e3994 AL |
111 | writel(data, priv->membase + MDIO_MODE_REG); |
112 | ||
113 | /* issue the phy address and reg */ | |
114 | writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); | |
115 | ||
116 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_READ; | |
117 | ||
118 | /* issue read command */ | |
119 | writel(cmd, priv->membase + MDIO_CMD_REG); | |
120 | ||
121 | /* Wait read complete */ | |
122 | if (ipq4019_mdio_wait_busy(bus)) | |
123 | return -ETIMEDOUT; | |
06fb5606 | 124 | |
466ed24f RM |
125 | /* Read and return data */ |
126 | return readl(priv->membase + MDIO_DATA_READ_REG); | |
127 | } | |
128 | ||
c58e3994 AL |
129 | static int ipq4019_mdio_write_c45(struct mii_bus *bus, int mii_id, int mmd, |
130 | int reg, u16 value) | |
466ed24f RM |
131 | { |
132 | struct ipq4019_mdio_data *priv = bus->priv; | |
06fb5606 | 133 | unsigned int data; |
466ed24f RM |
134 | unsigned int cmd; |
135 | ||
466ed24f RM |
136 | if (ipq4019_mdio_wait_busy(bus)) |
137 | return -ETIMEDOUT; | |
138 | ||
c58e3994 | 139 | data = readl(priv->membase + MDIO_MODE_REG); |
06fb5606 | 140 | |
c58e3994 | 141 | data |= MDIO_MODE_C45; |
06fb5606 | 142 | |
c58e3994 | 143 | writel(data, priv->membase + MDIO_MODE_REG); |
06fb5606 | 144 | |
c58e3994 AL |
145 | /* issue the phy address and mmd */ |
146 | writel((mii_id << 8) | mmd, priv->membase + MDIO_ADDR_REG); | |
06fb5606 | 147 | |
c58e3994 AL |
148 | /* issue reg */ |
149 | writel(reg, priv->membase + MDIO_DATA_WRITE_REG); | |
06fb5606 | 150 | |
c58e3994 | 151 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; |
06fb5606 | 152 | |
c58e3994 | 153 | writel(cmd, priv->membase + MDIO_CMD_REG); |
06fb5606 | 154 | |
c58e3994 AL |
155 | if (ipq4019_mdio_wait_busy(bus)) |
156 | return -ETIMEDOUT; | |
06fb5606 | 157 | |
c58e3994 AL |
158 | /* issue write data */ |
159 | writel(value, priv->membase + MDIO_DATA_WRITE_REG); | |
06fb5606 | 160 | |
c58e3994 AL |
161 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_WRITE; |
162 | writel(cmd, priv->membase + MDIO_CMD_REG); | |
06fb5606 | 163 | |
c58e3994 AL |
164 | /* Wait write complete */ |
165 | if (ipq4019_mdio_wait_busy(bus)) | |
166 | return -ETIMEDOUT; | |
06fb5606 | 167 | |
c58e3994 AL |
168 | return 0; |
169 | } | |
170 | ||
171 | static int ipq4019_mdio_write_c22(struct mii_bus *bus, int mii_id, int regnum, | |
172 | u16 value) | |
173 | { | |
174 | struct ipq4019_mdio_data *priv = bus->priv; | |
175 | unsigned int data; | |
176 | unsigned int cmd; | |
177 | ||
178 | if (ipq4019_mdio_wait_busy(bus)) | |
179 | return -ETIMEDOUT; | |
180 | ||
181 | /* Enter Clause 22 mode */ | |
182 | data = readl(priv->membase + MDIO_MODE_REG); | |
183 | ||
184 | data &= ~MDIO_MODE_C45; | |
185 | ||
186 | writel(data, priv->membase + MDIO_MODE_REG); | |
187 | ||
188 | /* issue the phy address and reg */ | |
189 | writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); | |
466ed24f RM |
190 | |
191 | /* issue write data */ | |
192 | writel(value, priv->membase + MDIO_DATA_WRITE_REG); | |
193 | ||
466ed24f | 194 | /* issue write command */ |
c58e3994 | 195 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_WRITE; |
06fb5606 | 196 | |
466ed24f RM |
197 | writel(cmd, priv->membase + MDIO_CMD_REG); |
198 | ||
199 | /* Wait write complete */ | |
200 | if (ipq4019_mdio_wait_busy(bus)) | |
201 | return -ETIMEDOUT; | |
202 | ||
203 | return 0; | |
204 | } | |
205 | ||
23a890d4 LJ |
206 | static int ipq_mdio_reset(struct mii_bus *bus) |
207 | { | |
208 | struct ipq4019_mdio_data *priv = bus->priv; | |
209 | u32 val; | |
210 | int ret; | |
211 | ||
212 | /* To indicate CMN_PLL that ethernet_ldo has been ready if platform resource 1 | |
213 | * is specified in the device tree. | |
214 | */ | |
215 | if (priv->eth_ldo_rdy) { | |
216 | val = readl(priv->eth_ldo_rdy); | |
217 | val |= BIT(0); | |
218 | writel(val, priv->eth_ldo_rdy); | |
219 | fsleep(IPQ_PHY_SET_DELAY_US); | |
220 | } | |
221 | ||
222 | /* Configure MDIO clock source frequency if clock is specified in the device tree */ | |
223 | ret = clk_set_rate(priv->mdio_clk, IPQ_MDIO_CLK_RATE); | |
224 | if (ret) | |
225 | return ret; | |
226 | ||
b6ad6261 BS |
227 | ret = clk_prepare_enable(priv->mdio_clk); |
228 | if (ret == 0) | |
229 | mdelay(10); | |
230 | ||
231 | return ret; | |
23a890d4 LJ |
232 | } |
233 | ||
466ed24f RM |
234 | static int ipq4019_mdio_probe(struct platform_device *pdev) |
235 | { | |
236 | struct ipq4019_mdio_data *priv; | |
237 | struct mii_bus *bus; | |
9e28cfea | 238 | struct resource *res; |
466ed24f RM |
239 | int ret; |
240 | ||
241 | bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv)); | |
242 | if (!bus) | |
243 | return -ENOMEM; | |
244 | ||
245 | priv = bus->priv; | |
246 | ||
247 | priv->membase = devm_platform_ioremap_resource(pdev, 0); | |
248 | if (IS_ERR(priv->membase)) | |
249 | return PTR_ERR(priv->membase); | |
250 | ||
23a890d4 LJ |
251 | priv->mdio_clk = devm_clk_get_optional(&pdev->dev, "gcc_mdio_ahb_clk"); |
252 | if (IS_ERR(priv->mdio_clk)) | |
253 | return PTR_ERR(priv->mdio_clk); | |
254 | ||
255 | /* The platform resource is provided on the chipset IPQ5018 */ | |
9e28cfea CH |
256 | /* This resource is optional */ |
257 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | |
258 | if (res) | |
259 | priv->eth_ldo_rdy = devm_ioremap_resource(&pdev->dev, res); | |
23a890d4 | 260 | |
466ed24f | 261 | bus->name = "ipq4019_mdio"; |
c58e3994 AL |
262 | bus->read = ipq4019_mdio_read_c22; |
263 | bus->write = ipq4019_mdio_write_c22; | |
264 | bus->read_c45 = ipq4019_mdio_read_c45; | |
265 | bus->write_c45 = ipq4019_mdio_write_c45; | |
23a890d4 | 266 | bus->reset = ipq_mdio_reset; |
466ed24f RM |
267 | bus->parent = &pdev->dev; |
268 | snprintf(bus->id, MII_BUS_ID_SIZE, "%s%d", pdev->name, pdev->id); | |
269 | ||
270 | ret = of_mdiobus_register(bus, pdev->dev.of_node); | |
271 | if (ret) { | |
272 | dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); | |
273 | return ret; | |
274 | } | |
275 | ||
276 | platform_set_drvdata(pdev, bus); | |
277 | ||
278 | return 0; | |
279 | } | |
280 | ||
b9ac5c42 | 281 | static void ipq4019_mdio_remove(struct platform_device *pdev) |
466ed24f RM |
282 | { |
283 | struct mii_bus *bus = platform_get_drvdata(pdev); | |
284 | ||
285 | mdiobus_unregister(bus); | |
466ed24f RM |
286 | } |
287 | ||
288 | static const struct of_device_id ipq4019_mdio_dt_ids[] = { | |
289 | { .compatible = "qcom,ipq4019-mdio" }, | |
c76ee263 | 290 | { .compatible = "qcom,ipq5018-mdio" }, |
466ed24f RM |
291 | { } |
292 | }; | |
293 | MODULE_DEVICE_TABLE(of, ipq4019_mdio_dt_ids); | |
294 | ||
295 | static struct platform_driver ipq4019_mdio_driver = { | |
296 | .probe = ipq4019_mdio_probe, | |
b9ac5c42 | 297 | .remove_new = ipq4019_mdio_remove, |
466ed24f RM |
298 | .driver = { |
299 | .name = "ipq4019-mdio", | |
300 | .of_match_table = ipq4019_mdio_dt_ids, | |
301 | }, | |
302 | }; | |
303 | ||
304 | module_platform_driver(ipq4019_mdio_driver); | |
305 | ||
306 | MODULE_DESCRIPTION("ipq4019 MDIO interface driver"); | |
307 | MODULE_AUTHOR("Qualcomm Atheros"); | |
308 | MODULE_LICENSE("Dual BSD/GPL"); |