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 | #include <linux/clk.h> |
15 | |
16 | #define MDIO_MODE_REG 0x40 |
17 | #define MDIO_MODE_MDC_MODE BIT(12) |
18 | /* 0 = Clause 22, 1 = Clause 45 */ |
19 | #define MDIO_MODE_C45 BIT(8) |
20 | #define MDIO_MODE_DIV_MASK GENMASK(7, 0) |
21 | #define MDIO_MODE_DIV(x) FIELD_PREP(MDIO_MODE_DIV_MASK, (x) - 1) |
22 | #define MDIO_MODE_DIV_1 0x0 |
23 | #define MDIO_MODE_DIV_2 0x1 |
24 | #define MDIO_MODE_DIV_4 0x3 |
25 | #define MDIO_MODE_DIV_8 0x7 |
26 | #define MDIO_MODE_DIV_16 0xf |
27 | #define MDIO_MODE_DIV_32 0x1f |
28 | #define MDIO_MODE_DIV_64 0x3f |
29 | #define MDIO_MODE_DIV_128 0x7f |
30 | #define MDIO_MODE_DIV_256 0xff |
31 | #define MDIO_ADDR_REG 0x44 |
32 | #define MDIO_DATA_WRITE_REG 0x48 |
33 | #define MDIO_DATA_READ_REG 0x4c |
34 | #define MDIO_CMD_REG 0x50 |
35 | #define MDIO_CMD_ACCESS_BUSY BIT(16) |
36 | #define MDIO_CMD_ACCESS_START BIT(8) |
37 | #define MDIO_CMD_ACCESS_CODE_READ 0 |
38 | #define MDIO_CMD_ACCESS_CODE_WRITE 1 |
39 | #define MDIO_CMD_ACCESS_CODE_C45_ADDR 0 |
40 | #define MDIO_CMD_ACCESS_CODE_C45_WRITE 1 |
41 | #define MDIO_CMD_ACCESS_CODE_C45_READ 2 |
42 | |
43 | #define IPQ4019_MDIO_TIMEOUT 10000 |
44 | #define IPQ4019_MDIO_SLEEP 10 |
45 | |
46 | /* MDIO clock source frequency is fixed to 100M */ |
47 | #define IPQ_MDIO_CLK_RATE 100000000 |
48 | |
49 | #define IPQ_PHY_SET_DELAY_US 100000 |
50 | |
51 | struct ipq4019_mdio_data { |
52 | void __iomem *membase; |
53 | void __iomem *eth_ldo_rdy; |
54 | struct clk *mdio_clk; |
55 | unsigned int mdc_rate; |
56 | }; |
57 | |
58 | static int ipq4019_mdio_wait_busy(struct mii_bus *bus) |
59 | { |
60 | struct ipq4019_mdio_data *priv = bus->priv; |
61 | unsigned int busy; |
62 | |
63 | return readl_poll_timeout(priv->membase + MDIO_CMD_REG, busy, |
64 | (busy & MDIO_CMD_ACCESS_BUSY) == 0, |
65 | IPQ4019_MDIO_SLEEP, IPQ4019_MDIO_TIMEOUT); |
66 | } |
67 | |
68 | static int ipq4019_mdio_read_c45(struct mii_bus *bus, int mii_id, int mmd, |
69 | int reg) |
70 | { |
71 | struct ipq4019_mdio_data *priv = bus->priv; |
72 | unsigned int data; |
73 | unsigned int cmd; |
74 | |
75 | if (ipq4019_mdio_wait_busy(bus)) |
76 | return -ETIMEDOUT; |
77 | |
78 | data = readl(addr: priv->membase + MDIO_MODE_REG); |
79 | |
80 | data |= MDIO_MODE_C45; |
81 | |
82 | writel(val: data, addr: priv->membase + MDIO_MODE_REG); |
83 | |
84 | /* issue the phy address and mmd */ |
85 | writel(val: (mii_id << 8) | mmd, addr: priv->membase + MDIO_ADDR_REG); |
86 | |
87 | /* issue reg */ |
88 | writel(val: reg, addr: priv->membase + MDIO_DATA_WRITE_REG); |
89 | |
90 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; |
91 | |
92 | /* issue read command */ |
93 | writel(val: cmd, addr: priv->membase + MDIO_CMD_REG); |
94 | |
95 | /* Wait read complete */ |
96 | if (ipq4019_mdio_wait_busy(bus)) |
97 | return -ETIMEDOUT; |
98 | |
99 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_READ; |
100 | |
101 | writel(val: cmd, addr: priv->membase + MDIO_CMD_REG); |
102 | |
103 | if (ipq4019_mdio_wait_busy(bus)) |
104 | return -ETIMEDOUT; |
105 | |
106 | /* Read and return data */ |
107 | return readl(addr: priv->membase + MDIO_DATA_READ_REG); |
108 | } |
109 | |
110 | static int ipq4019_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum) |
111 | { |
112 | struct ipq4019_mdio_data *priv = bus->priv; |
113 | unsigned int data; |
114 | unsigned int cmd; |
115 | |
116 | if (ipq4019_mdio_wait_busy(bus)) |
117 | return -ETIMEDOUT; |
118 | |
119 | data = readl(addr: priv->membase + MDIO_MODE_REG); |
120 | |
121 | data &= ~MDIO_MODE_C45; |
122 | |
123 | writel(val: data, addr: priv->membase + MDIO_MODE_REG); |
124 | |
125 | /* issue the phy address and reg */ |
126 | writel(val: (mii_id << 8) | regnum, addr: priv->membase + MDIO_ADDR_REG); |
127 | |
128 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_READ; |
129 | |
130 | /* issue read command */ |
131 | writel(val: cmd, addr: priv->membase + MDIO_CMD_REG); |
132 | |
133 | /* Wait read complete */ |
134 | if (ipq4019_mdio_wait_busy(bus)) |
135 | return -ETIMEDOUT; |
136 | |
137 | /* Read and return data */ |
138 | return readl(addr: priv->membase + MDIO_DATA_READ_REG); |
139 | } |
140 | |
141 | static int ipq4019_mdio_write_c45(struct mii_bus *bus, int mii_id, int mmd, |
142 | int reg, u16 value) |
143 | { |
144 | struct ipq4019_mdio_data *priv = bus->priv; |
145 | unsigned int data; |
146 | unsigned int cmd; |
147 | |
148 | if (ipq4019_mdio_wait_busy(bus)) |
149 | return -ETIMEDOUT; |
150 | |
151 | data = readl(addr: priv->membase + MDIO_MODE_REG); |
152 | |
153 | data |= MDIO_MODE_C45; |
154 | |
155 | writel(val: data, addr: priv->membase + MDIO_MODE_REG); |
156 | |
157 | /* issue the phy address and mmd */ |
158 | writel(val: (mii_id << 8) | mmd, addr: priv->membase + MDIO_ADDR_REG); |
159 | |
160 | /* issue reg */ |
161 | writel(val: reg, addr: priv->membase + MDIO_DATA_WRITE_REG); |
162 | |
163 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; |
164 | |
165 | writel(val: cmd, addr: priv->membase + MDIO_CMD_REG); |
166 | |
167 | if (ipq4019_mdio_wait_busy(bus)) |
168 | return -ETIMEDOUT; |
169 | |
170 | /* issue write data */ |
171 | writel(val: value, addr: priv->membase + MDIO_DATA_WRITE_REG); |
172 | |
173 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_WRITE; |
174 | writel(val: cmd, addr: priv->membase + MDIO_CMD_REG); |
175 | |
176 | /* Wait write complete */ |
177 | if (ipq4019_mdio_wait_busy(bus)) |
178 | return -ETIMEDOUT; |
179 | |
180 | return 0; |
181 | } |
182 | |
183 | static int ipq4019_mdio_write_c22(struct mii_bus *bus, int mii_id, int regnum, |
184 | u16 value) |
185 | { |
186 | struct ipq4019_mdio_data *priv = bus->priv; |
187 | unsigned int data; |
188 | unsigned int cmd; |
189 | |
190 | if (ipq4019_mdio_wait_busy(bus)) |
191 | return -ETIMEDOUT; |
192 | |
193 | /* Enter Clause 22 mode */ |
194 | data = readl(addr: priv->membase + MDIO_MODE_REG); |
195 | |
196 | data &= ~MDIO_MODE_C45; |
197 | |
198 | writel(val: data, addr: priv->membase + MDIO_MODE_REG); |
199 | |
200 | /* issue the phy address and reg */ |
201 | writel(val: (mii_id << 8) | regnum, addr: priv->membase + MDIO_ADDR_REG); |
202 | |
203 | /* issue write data */ |
204 | writel(val: value, addr: priv->membase + MDIO_DATA_WRITE_REG); |
205 | |
206 | /* issue write command */ |
207 | cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_WRITE; |
208 | |
209 | writel(val: cmd, addr: priv->membase + MDIO_CMD_REG); |
210 | |
211 | /* Wait write complete */ |
212 | if (ipq4019_mdio_wait_busy(bus)) |
213 | return -ETIMEDOUT; |
214 | |
215 | return 0; |
216 | } |
217 | |
218 | static int ipq4019_mdio_set_div(struct ipq4019_mdio_data *priv) |
219 | { |
220 | unsigned long ahb_rate; |
221 | int div; |
222 | u32 val; |
223 | |
224 | /* If we don't have a clock for AHB use the fixed value */ |
225 | ahb_rate = IPQ_MDIO_CLK_RATE; |
226 | if (priv->mdio_clk) |
227 | ahb_rate = clk_get_rate(clk: priv->mdio_clk); |
228 | |
229 | /* MDC rate is ahb_rate/(MDIO_MODE_DIV + 1) |
230 | * While supported, internal documentation doesn't |
231 | * assure correct functionality of the MDIO bus |
232 | * with divider of 1, 2 or 4. |
233 | */ |
234 | for (div = 8; div <= 256; div *= 2) { |
235 | /* The requested rate is supported by the div */ |
236 | if (priv->mdc_rate == DIV_ROUND_UP(ahb_rate, div)) { |
237 | val = readl(addr: priv->membase + MDIO_MODE_REG); |
238 | val &= ~MDIO_MODE_DIV_MASK; |
239 | val |= MDIO_MODE_DIV(div); |
240 | writel(val, addr: priv->membase + MDIO_MODE_REG); |
241 | |
242 | return 0; |
243 | } |
244 | } |
245 | |
246 | /* The requested rate is not supported */ |
247 | return -EINVAL; |
248 | } |
249 | |
250 | static int ipq_mdio_reset(struct mii_bus *bus) |
251 | { |
252 | struct ipq4019_mdio_data *priv = bus->priv; |
253 | u32 val; |
254 | int ret; |
255 | |
256 | /* To indicate CMN_PLL that ethernet_ldo has been ready if platform resource 1 |
257 | * is specified in the device tree. |
258 | */ |
259 | if (priv->eth_ldo_rdy) { |
260 | val = readl(addr: priv->eth_ldo_rdy); |
261 | val |= BIT(0); |
262 | writel(val, addr: priv->eth_ldo_rdy); |
263 | fsleep(IPQ_PHY_SET_DELAY_US); |
264 | } |
265 | |
266 | /* Configure MDIO clock source frequency if clock is specified in the device tree */ |
267 | ret = clk_set_rate(clk: priv->mdio_clk, IPQ_MDIO_CLK_RATE); |
268 | if (ret) |
269 | return ret; |
270 | |
271 | ret = clk_prepare_enable(clk: priv->mdio_clk); |
272 | if (ret) |
273 | return ret; |
274 | |
275 | mdelay(10); |
276 | |
277 | /* Restore MDC rate */ |
278 | return ipq4019_mdio_set_div(priv); |
279 | } |
280 | |
281 | static void ipq4019_mdio_select_mdc_rate(struct platform_device *pdev, |
282 | struct ipq4019_mdio_data *priv) |
283 | { |
284 | unsigned long ahb_rate; |
285 | int div; |
286 | u32 val; |
287 | |
288 | /* MDC rate defined in DT, we don't have to decide a default value */ |
289 | if (!of_property_read_u32(np: pdev->dev.of_node, propname: "clock-frequency" , |
290 | out_value: &priv->mdc_rate)) |
291 | return; |
292 | |
293 | /* If we don't have a clock for AHB use the fixed value */ |
294 | ahb_rate = IPQ_MDIO_CLK_RATE; |
295 | if (priv->mdio_clk) |
296 | ahb_rate = clk_get_rate(clk: priv->mdio_clk); |
297 | |
298 | /* Check what is the current div set */ |
299 | val = readl(addr: priv->membase + MDIO_MODE_REG); |
300 | div = FIELD_GET(MDIO_MODE_DIV_MASK, val); |
301 | |
302 | /* div is not set to the default value of /256 |
303 | * Probably someone changed that (bootloader, other drivers) |
304 | * Keep this and don't overwrite it. |
305 | */ |
306 | if (div != MDIO_MODE_DIV_256) { |
307 | priv->mdc_rate = DIV_ROUND_UP(ahb_rate, div + 1); |
308 | return; |
309 | } |
310 | |
311 | /* If div is /256 assume nobody have set this value and |
312 | * try to find one MDC rate that is close the 802.3 spec of |
313 | * 2.5MHz |
314 | */ |
315 | for (div = 256; div >= 8; div /= 2) { |
316 | /* Stop as soon as we found a divider that |
317 | * reached the closest value to 2.5MHz |
318 | */ |
319 | if (DIV_ROUND_UP(ahb_rate, div) > 2500000) |
320 | break; |
321 | |
322 | priv->mdc_rate = DIV_ROUND_UP(ahb_rate, div); |
323 | } |
324 | } |
325 | |
326 | static int ipq4019_mdio_probe(struct platform_device *pdev) |
327 | { |
328 | struct ipq4019_mdio_data *priv; |
329 | struct mii_bus *bus; |
330 | struct resource *res; |
331 | int ret; |
332 | |
333 | bus = devm_mdiobus_alloc_size(dev: &pdev->dev, sizeof_priv: sizeof(*priv)); |
334 | if (!bus) |
335 | return -ENOMEM; |
336 | |
337 | priv = bus->priv; |
338 | |
339 | priv->membase = devm_platform_ioremap_resource(pdev, index: 0); |
340 | if (IS_ERR(ptr: priv->membase)) |
341 | return PTR_ERR(ptr: priv->membase); |
342 | |
343 | priv->mdio_clk = devm_clk_get_optional(dev: &pdev->dev, id: "gcc_mdio_ahb_clk" ); |
344 | if (IS_ERR(ptr: priv->mdio_clk)) |
345 | return PTR_ERR(ptr: priv->mdio_clk); |
346 | |
347 | ipq4019_mdio_select_mdc_rate(pdev, priv); |
348 | ret = ipq4019_mdio_set_div(priv); |
349 | if (ret) |
350 | return ret; |
351 | |
352 | /* The platform resource is provided on the chipset IPQ5018 */ |
353 | /* This resource is optional */ |
354 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
355 | if (res) |
356 | priv->eth_ldo_rdy = devm_ioremap_resource(dev: &pdev->dev, res); |
357 | |
358 | bus->name = "ipq4019_mdio" ; |
359 | bus->read = ipq4019_mdio_read_c22; |
360 | bus->write = ipq4019_mdio_write_c22; |
361 | bus->read_c45 = ipq4019_mdio_read_c45; |
362 | bus->write_c45 = ipq4019_mdio_write_c45; |
363 | bus->reset = ipq_mdio_reset; |
364 | bus->parent = &pdev->dev; |
365 | snprintf(buf: bus->id, MII_BUS_ID_SIZE, fmt: "%s%d" , pdev->name, pdev->id); |
366 | |
367 | ret = of_mdiobus_register(mdio: bus, np: pdev->dev.of_node); |
368 | if (ret) { |
369 | dev_err(&pdev->dev, "Cannot register MDIO bus!\n" ); |
370 | return ret; |
371 | } |
372 | |
373 | platform_set_drvdata(pdev, data: bus); |
374 | |
375 | return 0; |
376 | } |
377 | |
378 | static void ipq4019_mdio_remove(struct platform_device *pdev) |
379 | { |
380 | struct mii_bus *bus = platform_get_drvdata(pdev); |
381 | |
382 | mdiobus_unregister(bus); |
383 | } |
384 | |
385 | static const struct of_device_id ipq4019_mdio_dt_ids[] = { |
386 | { .compatible = "qcom,ipq4019-mdio" }, |
387 | { .compatible = "qcom,ipq5018-mdio" }, |
388 | { } |
389 | }; |
390 | MODULE_DEVICE_TABLE(of, ipq4019_mdio_dt_ids); |
391 | |
392 | static struct platform_driver ipq4019_mdio_driver = { |
393 | .probe = ipq4019_mdio_probe, |
394 | .remove_new = ipq4019_mdio_remove, |
395 | .driver = { |
396 | .name = "ipq4019-mdio" , |
397 | .of_match_table = ipq4019_mdio_dt_ids, |
398 | }, |
399 | }; |
400 | |
401 | module_platform_driver(ipq4019_mdio_driver); |
402 | |
403 | MODULE_DESCRIPTION("ipq4019 MDIO interface driver" ); |
404 | MODULE_AUTHOR("Qualcomm Atheros" ); |
405 | MODULE_LICENSE("Dual BSD/GPL" ); |
406 | |