]>
Commit | Line | Data |
---|---|---|
d8652956 LW |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* Realtek Simple Management Interface (SMI) driver | |
3 | * It can be discussed how "simple" this interface is. | |
4 | * | |
5 | * The SMI protocol piggy-backs the MDIO MDC and MDIO signals levels | |
6 | * but the protocol is not MDIO at all. Instead it is a Realtek | |
7 | * pecularity that need to bit-bang the lines in a special way to | |
8 | * communicate with the switch. | |
9 | * | |
10 | * ASICs we intend to support with this driver: | |
11 | * | |
12 | * RTL8366 - The original version, apparently | |
13 | * RTL8369 - Similar enough to have the same datsheet as RTL8366 | |
14 | * RTL8366RB - Probably reads out "RTL8366 revision B", has a quite | |
15 | * different register layout from the other two | |
16 | * RTL8366S - Is this "RTL8366 super"? | |
17 | * RTL8367 - Has an OpenWRT driver as well | |
18 | * RTL8368S - Seems to be an alternative name for RTL8366RB | |
19 | * RTL8370 - Also uses SMI | |
20 | * | |
21 | * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> | |
22 | * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com> | |
23 | * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv> | |
24 | * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com> | |
25 | * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> | |
26 | */ | |
27 | ||
28 | #include <linux/kernel.h> | |
29 | #include <linux/module.h> | |
30 | #include <linux/device.h> | |
31 | #include <linux/spinlock.h> | |
32 | #include <linux/skbuff.h> | |
33 | #include <linux/of.h> | |
34 | #include <linux/of_device.h> | |
35 | #include <linux/of_mdio.h> | |
36 | #include <linux/delay.h> | |
37 | #include <linux/gpio/consumer.h> | |
38 | #include <linux/platform_device.h> | |
39 | #include <linux/regmap.h> | |
40 | #include <linux/bitops.h> | |
41 | #include <linux/if_bridge.h> | |
42 | ||
43 | #include "realtek-smi.h" | |
44 | ||
45 | #define REALTEK_SMI_ACK_RETRY_COUNT 5 | |
46 | #define REALTEK_SMI_HW_STOP_DELAY 25 /* msecs */ | |
47 | #define REALTEK_SMI_HW_START_DELAY 100 /* msecs */ | |
48 | ||
49 | static inline void realtek_smi_clk_delay(struct realtek_smi *smi) | |
50 | { | |
51 | ndelay(smi->clk_delay); | |
52 | } | |
53 | ||
54 | static void realtek_smi_start(struct realtek_smi *smi) | |
55 | { | |
56 | /* Set GPIO pins to output mode, with initial state: | |
57 | * SCK = 0, SDA = 1 | |
58 | */ | |
59 | gpiod_direction_output(smi->mdc, 0); | |
60 | gpiod_direction_output(smi->mdio, 1); | |
61 | realtek_smi_clk_delay(smi); | |
62 | ||
63 | /* CLK 1: 0 -> 1, 1 -> 0 */ | |
64 | gpiod_set_value(smi->mdc, 1); | |
65 | realtek_smi_clk_delay(smi); | |
66 | gpiod_set_value(smi->mdc, 0); | |
67 | realtek_smi_clk_delay(smi); | |
68 | ||
69 | /* CLK 2: */ | |
70 | gpiod_set_value(smi->mdc, 1); | |
71 | realtek_smi_clk_delay(smi); | |
72 | gpiod_set_value(smi->mdio, 0); | |
73 | realtek_smi_clk_delay(smi); | |
74 | gpiod_set_value(smi->mdc, 0); | |
75 | realtek_smi_clk_delay(smi); | |
76 | gpiod_set_value(smi->mdio, 1); | |
77 | } | |
78 | ||
79 | static void realtek_smi_stop(struct realtek_smi *smi) | |
80 | { | |
81 | realtek_smi_clk_delay(smi); | |
82 | gpiod_set_value(smi->mdio, 0); | |
83 | gpiod_set_value(smi->mdc, 1); | |
84 | realtek_smi_clk_delay(smi); | |
85 | gpiod_set_value(smi->mdio, 1); | |
86 | realtek_smi_clk_delay(smi); | |
87 | gpiod_set_value(smi->mdc, 1); | |
88 | realtek_smi_clk_delay(smi); | |
89 | gpiod_set_value(smi->mdc, 0); | |
90 | realtek_smi_clk_delay(smi); | |
91 | gpiod_set_value(smi->mdc, 1); | |
92 | ||
93 | /* Add a click */ | |
94 | realtek_smi_clk_delay(smi); | |
95 | gpiod_set_value(smi->mdc, 0); | |
96 | realtek_smi_clk_delay(smi); | |
97 | gpiod_set_value(smi->mdc, 1); | |
98 | ||
99 | /* Set GPIO pins to input mode */ | |
100 | gpiod_direction_input(smi->mdio); | |
101 | gpiod_direction_input(smi->mdc); | |
102 | } | |
103 | ||
104 | static void realtek_smi_write_bits(struct realtek_smi *smi, u32 data, u32 len) | |
105 | { | |
106 | for (; len > 0; len--) { | |
107 | realtek_smi_clk_delay(smi); | |
108 | ||
109 | /* Prepare data */ | |
110 | gpiod_set_value(smi->mdio, !!(data & (1 << (len - 1)))); | |
111 | realtek_smi_clk_delay(smi); | |
112 | ||
113 | /* Clocking */ | |
114 | gpiod_set_value(smi->mdc, 1); | |
115 | realtek_smi_clk_delay(smi); | |
116 | gpiod_set_value(smi->mdc, 0); | |
117 | } | |
118 | } | |
119 | ||
120 | static void realtek_smi_read_bits(struct realtek_smi *smi, u32 len, u32 *data) | |
121 | { | |
122 | gpiod_direction_input(smi->mdio); | |
123 | ||
124 | for (*data = 0; len > 0; len--) { | |
125 | u32 u; | |
126 | ||
127 | realtek_smi_clk_delay(smi); | |
128 | ||
129 | /* Clocking */ | |
130 | gpiod_set_value(smi->mdc, 1); | |
131 | realtek_smi_clk_delay(smi); | |
132 | u = !!gpiod_get_value(smi->mdio); | |
133 | gpiod_set_value(smi->mdc, 0); | |
134 | ||
135 | *data |= (u << (len - 1)); | |
136 | } | |
137 | ||
138 | gpiod_direction_output(smi->mdio, 0); | |
139 | } | |
140 | ||
141 | static int realtek_smi_wait_for_ack(struct realtek_smi *smi) | |
142 | { | |
143 | int retry_cnt; | |
144 | ||
145 | retry_cnt = 0; | |
146 | do { | |
147 | u32 ack; | |
148 | ||
149 | realtek_smi_read_bits(smi, 1, &ack); | |
150 | if (ack == 0) | |
151 | break; | |
152 | ||
153 | if (++retry_cnt > REALTEK_SMI_ACK_RETRY_COUNT) { | |
154 | dev_err(smi->dev, "ACK timeout\n"); | |
155 | return -ETIMEDOUT; | |
156 | } | |
157 | } while (1); | |
158 | ||
159 | return 0; | |
160 | } | |
161 | ||
162 | static int realtek_smi_write_byte(struct realtek_smi *smi, u8 data) | |
163 | { | |
164 | realtek_smi_write_bits(smi, data, 8); | |
165 | return realtek_smi_wait_for_ack(smi); | |
166 | } | |
167 | ||
168 | static int realtek_smi_write_byte_noack(struct realtek_smi *smi, u8 data) | |
169 | { | |
170 | realtek_smi_write_bits(smi, data, 8); | |
171 | return 0; | |
172 | } | |
173 | ||
174 | static int realtek_smi_read_byte0(struct realtek_smi *smi, u8 *data) | |
175 | { | |
176 | u32 t; | |
177 | ||
178 | /* Read data */ | |
179 | realtek_smi_read_bits(smi, 8, &t); | |
180 | *data = (t & 0xff); | |
181 | ||
182 | /* Send an ACK */ | |
183 | realtek_smi_write_bits(smi, 0x00, 1); | |
184 | ||
185 | return 0; | |
186 | } | |
187 | ||
188 | static int realtek_smi_read_byte1(struct realtek_smi *smi, u8 *data) | |
189 | { | |
190 | u32 t; | |
191 | ||
192 | /* Read data */ | |
193 | realtek_smi_read_bits(smi, 8, &t); | |
194 | *data = (t & 0xff); | |
195 | ||
196 | /* Send an ACK */ | |
197 | realtek_smi_write_bits(smi, 0x01, 1); | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
202 | static int realtek_smi_read_reg(struct realtek_smi *smi, u32 addr, u32 *data) | |
203 | { | |
204 | unsigned long flags; | |
205 | u8 lo = 0; | |
206 | u8 hi = 0; | |
207 | int ret; | |
208 | ||
209 | spin_lock_irqsave(&smi->lock, flags); | |
210 | ||
211 | realtek_smi_start(smi); | |
212 | ||
213 | /* Send READ command */ | |
214 | ret = realtek_smi_write_byte(smi, smi->cmd_read); | |
215 | if (ret) | |
216 | goto out; | |
217 | ||
218 | /* Set ADDR[7:0] */ | |
219 | ret = realtek_smi_write_byte(smi, addr & 0xff); | |
220 | if (ret) | |
221 | goto out; | |
222 | ||
223 | /* Set ADDR[15:8] */ | |
224 | ret = realtek_smi_write_byte(smi, addr >> 8); | |
225 | if (ret) | |
226 | goto out; | |
227 | ||
228 | /* Read DATA[7:0] */ | |
229 | realtek_smi_read_byte0(smi, &lo); | |
230 | /* Read DATA[15:8] */ | |
231 | realtek_smi_read_byte1(smi, &hi); | |
232 | ||
233 | *data = ((u32)lo) | (((u32)hi) << 8); | |
234 | ||
235 | ret = 0; | |
236 | ||
237 | out: | |
238 | realtek_smi_stop(smi); | |
239 | spin_unlock_irqrestore(&smi->lock, flags); | |
240 | ||
241 | return ret; | |
242 | } | |
243 | ||
244 | static int realtek_smi_write_reg(struct realtek_smi *smi, | |
245 | u32 addr, u32 data, bool ack) | |
246 | { | |
247 | unsigned long flags; | |
248 | int ret; | |
249 | ||
250 | spin_lock_irqsave(&smi->lock, flags); | |
251 | ||
252 | realtek_smi_start(smi); | |
253 | ||
254 | /* Send WRITE command */ | |
255 | ret = realtek_smi_write_byte(smi, smi->cmd_write); | |
256 | if (ret) | |
257 | goto out; | |
258 | ||
259 | /* Set ADDR[7:0] */ | |
260 | ret = realtek_smi_write_byte(smi, addr & 0xff); | |
261 | if (ret) | |
262 | goto out; | |
263 | ||
264 | /* Set ADDR[15:8] */ | |
265 | ret = realtek_smi_write_byte(smi, addr >> 8); | |
266 | if (ret) | |
267 | goto out; | |
268 | ||
269 | /* Write DATA[7:0] */ | |
270 | ret = realtek_smi_write_byte(smi, data & 0xff); | |
271 | if (ret) | |
272 | goto out; | |
273 | ||
274 | /* Write DATA[15:8] */ | |
275 | if (ack) | |
276 | ret = realtek_smi_write_byte(smi, data >> 8); | |
277 | else | |
278 | ret = realtek_smi_write_byte_noack(smi, data >> 8); | |
279 | if (ret) | |
280 | goto out; | |
281 | ||
282 | ret = 0; | |
283 | ||
284 | out: | |
285 | realtek_smi_stop(smi); | |
286 | spin_unlock_irqrestore(&smi->lock, flags); | |
287 | ||
288 | return ret; | |
289 | } | |
290 | ||
291 | /* There is one single case when we need to use this accessor and that | |
292 | * is when issueing soft reset. Since the device reset as soon as we write | |
293 | * that bit, no ACK will come back for natural reasons. | |
294 | */ | |
295 | int realtek_smi_write_reg_noack(struct realtek_smi *smi, u32 addr, | |
296 | u32 data) | |
297 | { | |
298 | return realtek_smi_write_reg(smi, addr, data, false); | |
299 | } | |
300 | EXPORT_SYMBOL_GPL(realtek_smi_write_reg_noack); | |
301 | ||
302 | /* Regmap accessors */ | |
303 | ||
304 | static int realtek_smi_write(void *ctx, u32 reg, u32 val) | |
305 | { | |
306 | struct realtek_smi *smi = ctx; | |
307 | ||
308 | return realtek_smi_write_reg(smi, reg, val, true); | |
309 | } | |
310 | ||
311 | static int realtek_smi_read(void *ctx, u32 reg, u32 *val) | |
312 | { | |
313 | struct realtek_smi *smi = ctx; | |
314 | ||
315 | return realtek_smi_read_reg(smi, reg, val); | |
316 | } | |
317 | ||
318 | static const struct regmap_config realtek_smi_mdio_regmap_config = { | |
319 | .reg_bits = 10, /* A4..A0 R4..R0 */ | |
320 | .val_bits = 16, | |
321 | .reg_stride = 1, | |
322 | /* PHY regs are at 0x8000 */ | |
323 | .max_register = 0xffff, | |
324 | .reg_format_endian = REGMAP_ENDIAN_BIG, | |
325 | .reg_read = realtek_smi_read, | |
326 | .reg_write = realtek_smi_write, | |
327 | .cache_type = REGCACHE_NONE, | |
328 | }; | |
329 | ||
330 | static int realtek_smi_mdio_read(struct mii_bus *bus, int addr, int regnum) | |
331 | { | |
332 | struct realtek_smi *smi = bus->priv; | |
333 | ||
334 | return smi->ops->phy_read(smi, addr, regnum); | |
335 | } | |
336 | ||
337 | static int realtek_smi_mdio_write(struct mii_bus *bus, int addr, int regnum, | |
338 | u16 val) | |
339 | { | |
340 | struct realtek_smi *smi = bus->priv; | |
341 | ||
342 | return smi->ops->phy_write(smi, addr, regnum, val); | |
343 | } | |
344 | ||
345 | int realtek_smi_setup_mdio(struct realtek_smi *smi) | |
346 | { | |
347 | struct device_node *mdio_np; | |
348 | int ret; | |
349 | ||
3f1bb6ab | 350 | mdio_np = of_get_compatible_child(smi->dev->of_node, "realtek,smi-mdio"); |
d8652956 LW |
351 | if (!mdio_np) { |
352 | dev_err(smi->dev, "no MDIO bus node\n"); | |
353 | return -ENODEV; | |
354 | } | |
355 | ||
356 | smi->slave_mii_bus = devm_mdiobus_alloc(smi->dev); | |
3f1bb6ab JH |
357 | if (!smi->slave_mii_bus) { |
358 | ret = -ENOMEM; | |
359 | goto err_put_node; | |
360 | } | |
d8652956 LW |
361 | smi->slave_mii_bus->priv = smi; |
362 | smi->slave_mii_bus->name = "SMI slave MII"; | |
363 | smi->slave_mii_bus->read = realtek_smi_mdio_read; | |
364 | smi->slave_mii_bus->write = realtek_smi_mdio_write; | |
365 | snprintf(smi->slave_mii_bus->id, MII_BUS_ID_SIZE, "SMI-%d", | |
366 | smi->ds->index); | |
367 | smi->slave_mii_bus->dev.of_node = mdio_np; | |
368 | smi->slave_mii_bus->parent = smi->dev; | |
369 | smi->ds->slave_mii_bus = smi->slave_mii_bus; | |
370 | ||
371 | ret = of_mdiobus_register(smi->slave_mii_bus, mdio_np); | |
372 | if (ret) { | |
373 | dev_err(smi->dev, "unable to register MDIO bus %s\n", | |
374 | smi->slave_mii_bus->id); | |
3f1bb6ab | 375 | goto err_put_node; |
d8652956 LW |
376 | } |
377 | ||
378 | return 0; | |
3f1bb6ab JH |
379 | |
380 | err_put_node: | |
381 | of_node_put(mdio_np); | |
382 | ||
383 | return ret; | |
d8652956 LW |
384 | } |
385 | ||
386 | static int realtek_smi_probe(struct platform_device *pdev) | |
387 | { | |
388 | const struct realtek_smi_variant *var; | |
389 | struct device *dev = &pdev->dev; | |
390 | struct realtek_smi *smi; | |
391 | struct device_node *np; | |
392 | int ret; | |
393 | ||
394 | var = of_device_get_match_data(dev); | |
395 | np = dev->of_node; | |
396 | ||
397 | smi = devm_kzalloc(dev, sizeof(*smi), GFP_KERNEL); | |
398 | if (!smi) | |
399 | return -ENOMEM; | |
400 | smi->map = devm_regmap_init(dev, NULL, smi, | |
401 | &realtek_smi_mdio_regmap_config); | |
402 | if (IS_ERR(smi->map)) { | |
403 | ret = PTR_ERR(smi->map); | |
404 | dev_err(dev, "regmap init failed: %d\n", ret); | |
405 | return ret; | |
406 | } | |
407 | ||
408 | /* Link forward and backward */ | |
409 | smi->dev = dev; | |
410 | smi->clk_delay = var->clk_delay; | |
411 | smi->cmd_read = var->cmd_read; | |
412 | smi->cmd_write = var->cmd_write; | |
413 | smi->ops = var->ops; | |
414 | ||
415 | dev_set_drvdata(dev, smi); | |
416 | spin_lock_init(&smi->lock); | |
417 | ||
418 | /* TODO: if power is software controlled, set up any regulators here */ | |
419 | ||
420 | /* Assert then deassert RESET */ | |
421 | smi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); | |
422 | if (IS_ERR(smi->reset)) { | |
423 | dev_err(dev, "failed to get RESET GPIO\n"); | |
424 | return PTR_ERR(smi->reset); | |
425 | } | |
426 | msleep(REALTEK_SMI_HW_STOP_DELAY); | |
427 | gpiod_set_value(smi->reset, 0); | |
428 | msleep(REALTEK_SMI_HW_START_DELAY); | |
429 | dev_info(dev, "deasserted RESET\n"); | |
430 | ||
431 | /* Fetch MDIO pins */ | |
432 | smi->mdc = devm_gpiod_get_optional(dev, "mdc", GPIOD_OUT_LOW); | |
433 | if (IS_ERR(smi->mdc)) | |
434 | return PTR_ERR(smi->mdc); | |
435 | smi->mdio = devm_gpiod_get_optional(dev, "mdio", GPIOD_OUT_LOW); | |
436 | if (IS_ERR(smi->mdio)) | |
437 | return PTR_ERR(smi->mdio); | |
438 | ||
439 | smi->leds_disabled = of_property_read_bool(np, "realtek,disable-leds"); | |
440 | ||
441 | ret = smi->ops->detect(smi); | |
442 | if (ret) { | |
443 | dev_err(dev, "unable to detect switch\n"); | |
444 | return ret; | |
445 | } | |
446 | ||
447 | smi->ds = dsa_switch_alloc(dev, smi->num_ports); | |
448 | if (!smi->ds) | |
449 | return -ENOMEM; | |
450 | smi->ds->priv = smi; | |
451 | ||
452 | smi->ds->ops = var->ds_ops; | |
453 | ret = dsa_register_switch(smi->ds); | |
454 | if (ret) { | |
455 | dev_err(dev, "unable to register switch ret = %d\n", ret); | |
456 | return ret; | |
457 | } | |
458 | return 0; | |
459 | } | |
460 | ||
461 | static int realtek_smi_remove(struct platform_device *pdev) | |
462 | { | |
463 | struct realtek_smi *smi = dev_get_drvdata(&pdev->dev); | |
464 | ||
465 | dsa_unregister_switch(smi->ds); | |
3f1bb6ab JH |
466 | if (smi->slave_mii_bus) |
467 | of_node_put(smi->slave_mii_bus->dev.of_node); | |
d8652956 LW |
468 | gpiod_set_value(smi->reset, 1); |
469 | ||
470 | return 0; | |
471 | } | |
472 | ||
473 | static const struct of_device_id realtek_smi_of_match[] = { | |
474 | { | |
475 | .compatible = "realtek,rtl8366rb", | |
476 | .data = &rtl8366rb_variant, | |
477 | }, | |
478 | { | |
479 | /* FIXME: add support for RTL8366S and more */ | |
480 | .compatible = "realtek,rtl8366s", | |
481 | .data = NULL, | |
482 | }, | |
483 | { /* sentinel */ }, | |
484 | }; | |
485 | MODULE_DEVICE_TABLE(of, realtek_smi_of_match); | |
486 | ||
487 | static struct platform_driver realtek_smi_driver = { | |
488 | .driver = { | |
489 | .name = "realtek-smi", | |
490 | .of_match_table = of_match_ptr(realtek_smi_of_match), | |
491 | }, | |
492 | .probe = realtek_smi_probe, | |
493 | .remove = realtek_smi_remove, | |
494 | }; | |
495 | module_platform_driver(realtek_smi_driver); | |
be5a8ffa RD |
496 | |
497 | MODULE_LICENSE("GPL"); |