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/delay.h> |
35 | #include <linux/gpio/consumer.h> |
36 | #include <linux/platform_device.h> |
37 | #include <linux/regmap.h> |
38 | #include <linux/bitops.h> |
39 | #include <linux/if_bridge.h> |
40 | |
41 | #include "realtek.h" |
42 | #include "realtek-smi.h" |
43 | #include "rtl83xx.h" |
44 | |
45 | #define REALTEK_SMI_ACK_RETRY_COUNT 5 |
46 | |
47 | static inline void realtek_smi_clk_delay(struct realtek_priv *priv) |
48 | { |
49 | ndelay(priv->variant->clk_delay); |
50 | } |
51 | |
52 | static void realtek_smi_start(struct realtek_priv *priv) |
53 | { |
54 | /* Set GPIO pins to output mode, with initial state: |
55 | * SCK = 0, SDA = 1 |
56 | */ |
57 | gpiod_direction_output(desc: priv->mdc, value: 0); |
58 | gpiod_direction_output(desc: priv->mdio, value: 1); |
59 | realtek_smi_clk_delay(priv); |
60 | |
61 | /* CLK 1: 0 -> 1, 1 -> 0 */ |
62 | gpiod_set_value(desc: priv->mdc, value: 1); |
63 | realtek_smi_clk_delay(priv); |
64 | gpiod_set_value(desc: priv->mdc, value: 0); |
65 | realtek_smi_clk_delay(priv); |
66 | |
67 | /* CLK 2: */ |
68 | gpiod_set_value(desc: priv->mdc, value: 1); |
69 | realtek_smi_clk_delay(priv); |
70 | gpiod_set_value(desc: priv->mdio, value: 0); |
71 | realtek_smi_clk_delay(priv); |
72 | gpiod_set_value(desc: priv->mdc, value: 0); |
73 | realtek_smi_clk_delay(priv); |
74 | gpiod_set_value(desc: priv->mdio, value: 1); |
75 | } |
76 | |
77 | static void realtek_smi_stop(struct realtek_priv *priv) |
78 | { |
79 | realtek_smi_clk_delay(priv); |
80 | gpiod_set_value(desc: priv->mdio, value: 0); |
81 | gpiod_set_value(desc: priv->mdc, value: 1); |
82 | realtek_smi_clk_delay(priv); |
83 | gpiod_set_value(desc: priv->mdio, value: 1); |
84 | realtek_smi_clk_delay(priv); |
85 | gpiod_set_value(desc: priv->mdc, value: 1); |
86 | realtek_smi_clk_delay(priv); |
87 | gpiod_set_value(desc: priv->mdc, value: 0); |
88 | realtek_smi_clk_delay(priv); |
89 | gpiod_set_value(desc: priv->mdc, value: 1); |
90 | |
91 | /* Add a click */ |
92 | realtek_smi_clk_delay(priv); |
93 | gpiod_set_value(desc: priv->mdc, value: 0); |
94 | realtek_smi_clk_delay(priv); |
95 | gpiod_set_value(desc: priv->mdc, value: 1); |
96 | |
97 | /* Set GPIO pins to input mode */ |
98 | gpiod_direction_input(desc: priv->mdio); |
99 | gpiod_direction_input(desc: priv->mdc); |
100 | } |
101 | |
102 | static void realtek_smi_write_bits(struct realtek_priv *priv, u32 data, u32 len) |
103 | { |
104 | for (; len > 0; len--) { |
105 | realtek_smi_clk_delay(priv); |
106 | |
107 | /* Prepare data */ |
108 | gpiod_set_value(desc: priv->mdio, value: !!(data & (1 << (len - 1)))); |
109 | realtek_smi_clk_delay(priv); |
110 | |
111 | /* Clocking */ |
112 | gpiod_set_value(desc: priv->mdc, value: 1); |
113 | realtek_smi_clk_delay(priv); |
114 | gpiod_set_value(desc: priv->mdc, value: 0); |
115 | } |
116 | } |
117 | |
118 | static void realtek_smi_read_bits(struct realtek_priv *priv, u32 len, u32 *data) |
119 | { |
120 | gpiod_direction_input(desc: priv->mdio); |
121 | |
122 | for (*data = 0; len > 0; len--) { |
123 | u32 u; |
124 | |
125 | realtek_smi_clk_delay(priv); |
126 | |
127 | /* Clocking */ |
128 | gpiod_set_value(desc: priv->mdc, value: 1); |
129 | realtek_smi_clk_delay(priv); |
130 | u = !!gpiod_get_value(desc: priv->mdio); |
131 | gpiod_set_value(desc: priv->mdc, value: 0); |
132 | |
133 | *data |= (u << (len - 1)); |
134 | } |
135 | |
136 | gpiod_direction_output(desc: priv->mdio, value: 0); |
137 | } |
138 | |
139 | static int realtek_smi_wait_for_ack(struct realtek_priv *priv) |
140 | { |
141 | int retry_cnt; |
142 | |
143 | retry_cnt = 0; |
144 | do { |
145 | u32 ack; |
146 | |
147 | realtek_smi_read_bits(priv, len: 1, data: &ack); |
148 | if (ack == 0) |
149 | break; |
150 | |
151 | if (++retry_cnt > REALTEK_SMI_ACK_RETRY_COUNT) { |
152 | dev_err(priv->dev, "ACK timeout\n" ); |
153 | return -ETIMEDOUT; |
154 | } |
155 | } while (1); |
156 | |
157 | return 0; |
158 | } |
159 | |
160 | static int realtek_smi_write_byte(struct realtek_priv *priv, u8 data) |
161 | { |
162 | realtek_smi_write_bits(priv, data, len: 8); |
163 | return realtek_smi_wait_for_ack(priv); |
164 | } |
165 | |
166 | static int realtek_smi_write_byte_noack(struct realtek_priv *priv, u8 data) |
167 | { |
168 | realtek_smi_write_bits(priv, data, len: 8); |
169 | return 0; |
170 | } |
171 | |
172 | static int realtek_smi_read_byte0(struct realtek_priv *priv, u8 *data) |
173 | { |
174 | u32 t; |
175 | |
176 | /* Read data */ |
177 | realtek_smi_read_bits(priv, len: 8, data: &t); |
178 | *data = (t & 0xff); |
179 | |
180 | /* Send an ACK */ |
181 | realtek_smi_write_bits(priv, data: 0x00, len: 1); |
182 | |
183 | return 0; |
184 | } |
185 | |
186 | static int realtek_smi_read_byte1(struct realtek_priv *priv, u8 *data) |
187 | { |
188 | u32 t; |
189 | |
190 | /* Read data */ |
191 | realtek_smi_read_bits(priv, len: 8, data: &t); |
192 | *data = (t & 0xff); |
193 | |
194 | /* Send an ACK */ |
195 | realtek_smi_write_bits(priv, data: 0x01, len: 1); |
196 | |
197 | return 0; |
198 | } |
199 | |
200 | static int realtek_smi_read_reg(struct realtek_priv *priv, u32 addr, u32 *data) |
201 | { |
202 | unsigned long flags; |
203 | u8 lo = 0; |
204 | u8 hi = 0; |
205 | int ret; |
206 | |
207 | spin_lock_irqsave(&priv->lock, flags); |
208 | |
209 | realtek_smi_start(priv); |
210 | |
211 | /* Send READ command */ |
212 | ret = realtek_smi_write_byte(priv, data: priv->variant->cmd_read); |
213 | if (ret) |
214 | goto out; |
215 | |
216 | /* Set ADDR[7:0] */ |
217 | ret = realtek_smi_write_byte(priv, data: addr & 0xff); |
218 | if (ret) |
219 | goto out; |
220 | |
221 | /* Set ADDR[15:8] */ |
222 | ret = realtek_smi_write_byte(priv, data: addr >> 8); |
223 | if (ret) |
224 | goto out; |
225 | |
226 | /* Read DATA[7:0] */ |
227 | realtek_smi_read_byte0(priv, data: &lo); |
228 | /* Read DATA[15:8] */ |
229 | realtek_smi_read_byte1(priv, data: &hi); |
230 | |
231 | *data = ((u32)lo) | (((u32)hi) << 8); |
232 | |
233 | ret = 0; |
234 | |
235 | out: |
236 | realtek_smi_stop(priv); |
237 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
238 | |
239 | return ret; |
240 | } |
241 | |
242 | static int realtek_smi_write_reg(struct realtek_priv *priv, |
243 | u32 addr, u32 data, bool ack) |
244 | { |
245 | unsigned long flags; |
246 | int ret; |
247 | |
248 | spin_lock_irqsave(&priv->lock, flags); |
249 | |
250 | realtek_smi_start(priv); |
251 | |
252 | /* Send WRITE command */ |
253 | ret = realtek_smi_write_byte(priv, data: priv->variant->cmd_write); |
254 | if (ret) |
255 | goto out; |
256 | |
257 | /* Set ADDR[7:0] */ |
258 | ret = realtek_smi_write_byte(priv, data: addr & 0xff); |
259 | if (ret) |
260 | goto out; |
261 | |
262 | /* Set ADDR[15:8] */ |
263 | ret = realtek_smi_write_byte(priv, data: addr >> 8); |
264 | if (ret) |
265 | goto out; |
266 | |
267 | /* Write DATA[7:0] */ |
268 | ret = realtek_smi_write_byte(priv, data: data & 0xff); |
269 | if (ret) |
270 | goto out; |
271 | |
272 | /* Write DATA[15:8] */ |
273 | if (ack) |
274 | ret = realtek_smi_write_byte(priv, data: data >> 8); |
275 | else |
276 | ret = realtek_smi_write_byte_noack(priv, data: data >> 8); |
277 | if (ret) |
278 | goto out; |
279 | |
280 | ret = 0; |
281 | |
282 | out: |
283 | realtek_smi_stop(priv); |
284 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
285 | |
286 | return ret; |
287 | } |
288 | |
289 | /* There is one single case when we need to use this accessor and that |
290 | * is when issueing soft reset. Since the device reset as soon as we write |
291 | * that bit, no ACK will come back for natural reasons. |
292 | */ |
293 | static int realtek_smi_write_reg_noack(void *ctx, u32 reg, u32 val) |
294 | { |
295 | return realtek_smi_write_reg(priv: ctx, addr: reg, data: val, ack: false); |
296 | } |
297 | |
298 | /* Regmap accessors */ |
299 | |
300 | static int realtek_smi_write(void *ctx, u32 reg, u32 val) |
301 | { |
302 | struct realtek_priv *priv = ctx; |
303 | |
304 | return realtek_smi_write_reg(priv, addr: reg, data: val, ack: true); |
305 | } |
306 | |
307 | static int realtek_smi_read(void *ctx, u32 reg, u32 *val) |
308 | { |
309 | struct realtek_priv *priv = ctx; |
310 | |
311 | return realtek_smi_read_reg(priv, addr: reg, data: val); |
312 | } |
313 | |
314 | static const struct realtek_interface_info realtek_smi_info = { |
315 | .reg_read = realtek_smi_read, |
316 | .reg_write = realtek_smi_write, |
317 | }; |
318 | |
319 | /** |
320 | * realtek_smi_probe() - Probe a platform device for an SMI-connected switch |
321 | * @pdev: platform_device to probe on. |
322 | * |
323 | * This function should be used as the .probe in a platform_driver. After |
324 | * calling the common probe function for both interfaces, it initializes the |
325 | * values specific for SMI-connected devices. Finally, it calls a common |
326 | * function to register the DSA switch. |
327 | * |
328 | * Context: Can sleep. Takes and releases priv->map_lock. |
329 | * Return: Returns 0 on success, a negative error on failure. |
330 | */ |
331 | int realtek_smi_probe(struct platform_device *pdev) |
332 | { |
333 | struct device *dev = &pdev->dev; |
334 | struct realtek_priv *priv; |
335 | int ret; |
336 | |
337 | priv = rtl83xx_probe(dev, interface_info: &realtek_smi_info); |
338 | if (IS_ERR(ptr: priv)) |
339 | return PTR_ERR(ptr: priv); |
340 | |
341 | /* Fetch MDIO pins */ |
342 | priv->mdc = devm_gpiod_get_optional(dev, con_id: "mdc" , flags: GPIOD_OUT_LOW); |
343 | if (IS_ERR(ptr: priv->mdc)) { |
344 | rtl83xx_remove(priv); |
345 | return PTR_ERR(ptr: priv->mdc); |
346 | } |
347 | |
348 | priv->mdio = devm_gpiod_get_optional(dev, con_id: "mdio" , flags: GPIOD_OUT_LOW); |
349 | if (IS_ERR(ptr: priv->mdio)) { |
350 | rtl83xx_remove(priv); |
351 | return PTR_ERR(ptr: priv->mdio); |
352 | } |
353 | |
354 | priv->write_reg_noack = realtek_smi_write_reg_noack; |
355 | |
356 | ret = rtl83xx_register_switch(priv); |
357 | if (ret) { |
358 | rtl83xx_remove(priv); |
359 | return ret; |
360 | } |
361 | |
362 | return 0; |
363 | } |
364 | EXPORT_SYMBOL_NS_GPL(realtek_smi_probe, REALTEK_DSA); |
365 | |
366 | /** |
367 | * realtek_smi_remove() - Remove the driver of a SMI-connected switch |
368 | * @pdev: platform_device to be removed. |
369 | * |
370 | * This function should be used as the .remove_new in a platform_driver. First |
371 | * it unregisters the DSA switch and then it calls the common remove function. |
372 | * |
373 | * Context: Can sleep. |
374 | * Return: Nothing. |
375 | */ |
376 | void realtek_smi_remove(struct platform_device *pdev) |
377 | { |
378 | struct realtek_priv *priv = platform_get_drvdata(pdev); |
379 | |
380 | if (!priv) |
381 | return; |
382 | |
383 | rtl83xx_unregister_switch(priv); |
384 | |
385 | rtl83xx_remove(priv); |
386 | } |
387 | EXPORT_SYMBOL_NS_GPL(realtek_smi_remove, REALTEK_DSA); |
388 | |
389 | /** |
390 | * realtek_smi_shutdown() - Shutdown the driver of a SMI-connected switch |
391 | * @pdev: platform_device shutting down. |
392 | * |
393 | * This function should be used as the .shutdown in a platform_driver. It calls |
394 | * the common shutdown function. |
395 | * |
396 | * Context: Can sleep. |
397 | * Return: Nothing. |
398 | */ |
399 | void realtek_smi_shutdown(struct platform_device *pdev) |
400 | { |
401 | struct realtek_priv *priv = platform_get_drvdata(pdev); |
402 | |
403 | if (!priv) |
404 | return; |
405 | |
406 | rtl83xx_shutdown(priv); |
407 | } |
408 | EXPORT_SYMBOL_NS_GPL(realtek_smi_shutdown, REALTEK_DSA); |
409 | |