1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2006-2007 PA Semi, Inc |
4 | * |
5 | * Author: Olof Johansson, PA Semi |
6 | * |
7 | * Maintained by: Olof Johansson <olof@lixom.net> |
8 | * |
9 | * Based on drivers/net/fs_enet/mii-bitbang.c. |
10 | */ |
11 | |
12 | #include <linux/io.h> |
13 | #include <linux/module.h> |
14 | #include <linux/types.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/sched.h> |
17 | #include <linux/errno.h> |
18 | #include <linux/ioport.h> |
19 | #include <linux/interrupt.h> |
20 | #include <linux/phy.h> |
21 | #include <linux/of_address.h> |
22 | #include <linux/of_mdio.h> |
23 | #include <linux/platform_device.h> |
24 | |
25 | #define DELAY 1 |
26 | |
27 | static void __iomem *gpio_regs; |
28 | |
29 | struct gpio_priv { |
30 | int mdc_pin; |
31 | int mdio_pin; |
32 | }; |
33 | |
34 | #define MDC_PIN(bus) (((struct gpio_priv *)bus->priv)->mdc_pin) |
35 | #define MDIO_PIN(bus) (((struct gpio_priv *)bus->priv)->mdio_pin) |
36 | |
37 | static inline void mdio_lo(struct mii_bus *bus) |
38 | { |
39 | out_le32(gpio_regs+0x10, 1 << MDIO_PIN(bus)); |
40 | } |
41 | |
42 | static inline void mdio_hi(struct mii_bus *bus) |
43 | { |
44 | out_le32(gpio_regs, 1 << MDIO_PIN(bus)); |
45 | } |
46 | |
47 | static inline void mdc_lo(struct mii_bus *bus) |
48 | { |
49 | out_le32(gpio_regs+0x10, 1 << MDC_PIN(bus)); |
50 | } |
51 | |
52 | static inline void mdc_hi(struct mii_bus *bus) |
53 | { |
54 | out_le32(gpio_regs, 1 << MDC_PIN(bus)); |
55 | } |
56 | |
57 | static inline void mdio_active(struct mii_bus *bus) |
58 | { |
59 | out_le32(gpio_regs+0x20, (1 << MDC_PIN(bus)) | (1 << MDIO_PIN(bus))); |
60 | } |
61 | |
62 | static inline void mdio_tristate(struct mii_bus *bus) |
63 | { |
64 | out_le32(gpio_regs+0x30, (1 << MDIO_PIN(bus))); |
65 | } |
66 | |
67 | static inline int mdio_read(struct mii_bus *bus) |
68 | { |
69 | return !!(in_le32(gpio_regs+0x40) & (1 << MDIO_PIN(bus))); |
70 | } |
71 | |
72 | static void clock_out(struct mii_bus *bus, int bit) |
73 | { |
74 | if (bit) |
75 | mdio_hi(bus); |
76 | else |
77 | mdio_lo(bus); |
78 | udelay(DELAY); |
79 | mdc_hi(bus); |
80 | udelay(DELAY); |
81 | mdc_lo(bus); |
82 | } |
83 | |
84 | /* Utility to send the preamble, address, and register (common to read and write). */ |
85 | static void bitbang_pre(struct mii_bus *bus, int read, u8 addr, u8 reg) |
86 | { |
87 | int i; |
88 | |
89 | /* CFE uses a really long preamble (40 bits). We'll do the same. */ |
90 | mdio_active(bus); |
91 | for (i = 0; i < 40; i++) { |
92 | clock_out(bus, bit: 1); |
93 | } |
94 | |
95 | /* send the start bit (01) and the read opcode (10) or write (10) */ |
96 | clock_out(bus, bit: 0); |
97 | clock_out(bus, bit: 1); |
98 | |
99 | clock_out(bus, bit: read); |
100 | clock_out(bus, bit: !read); |
101 | |
102 | /* send the PHY address */ |
103 | for (i = 0; i < 5; i++) { |
104 | clock_out(bus, bit: (addr & 0x10) != 0); |
105 | addr <<= 1; |
106 | } |
107 | |
108 | /* send the register address */ |
109 | for (i = 0; i < 5; i++) { |
110 | clock_out(bus, bit: (reg & 0x10) != 0); |
111 | reg <<= 1; |
112 | } |
113 | } |
114 | |
115 | static int gpio_mdio_read(struct mii_bus *bus, int phy_id, int location) |
116 | { |
117 | u16 rdreg; |
118 | int ret, i; |
119 | u8 addr = phy_id & 0xff; |
120 | u8 reg = location & 0xff; |
121 | |
122 | bitbang_pre(bus, read: 1, addr, reg); |
123 | |
124 | /* tri-state our MDIO I/O pin so we can read */ |
125 | mdio_tristate(bus); |
126 | udelay(DELAY); |
127 | mdc_hi(bus); |
128 | udelay(DELAY); |
129 | mdc_lo(bus); |
130 | |
131 | /* read 16 bits of register data, MSB first */ |
132 | rdreg = 0; |
133 | for (i = 0; i < 16; i++) { |
134 | mdc_lo(bus); |
135 | udelay(DELAY); |
136 | mdc_hi(bus); |
137 | udelay(DELAY); |
138 | mdc_lo(bus); |
139 | udelay(DELAY); |
140 | rdreg <<= 1; |
141 | rdreg |= mdio_read(bus); |
142 | } |
143 | |
144 | mdc_hi(bus); |
145 | udelay(DELAY); |
146 | mdc_lo(bus); |
147 | udelay(DELAY); |
148 | |
149 | ret = rdreg; |
150 | |
151 | return ret; |
152 | } |
153 | |
154 | static int gpio_mdio_write(struct mii_bus *bus, int phy_id, int location, u16 val) |
155 | { |
156 | int i; |
157 | |
158 | u8 addr = phy_id & 0xff; |
159 | u8 reg = location & 0xff; |
160 | u16 value = val & 0xffff; |
161 | |
162 | bitbang_pre(bus, read: 0, addr, reg); |
163 | |
164 | /* send the turnaround (10) */ |
165 | mdc_lo(bus); |
166 | mdio_hi(bus); |
167 | udelay(DELAY); |
168 | mdc_hi(bus); |
169 | udelay(DELAY); |
170 | mdc_lo(bus); |
171 | mdio_lo(bus); |
172 | udelay(DELAY); |
173 | mdc_hi(bus); |
174 | udelay(DELAY); |
175 | |
176 | /* write 16 bits of register data, MSB first */ |
177 | for (i = 0; i < 16; i++) { |
178 | mdc_lo(bus); |
179 | if (value & 0x8000) |
180 | mdio_hi(bus); |
181 | else |
182 | mdio_lo(bus); |
183 | udelay(DELAY); |
184 | mdc_hi(bus); |
185 | udelay(DELAY); |
186 | value <<= 1; |
187 | } |
188 | |
189 | /* |
190 | * Tri-state the MDIO line. |
191 | */ |
192 | mdio_tristate(bus); |
193 | mdc_lo(bus); |
194 | udelay(DELAY); |
195 | mdc_hi(bus); |
196 | udelay(DELAY); |
197 | return 0; |
198 | } |
199 | |
200 | static int gpio_mdio_reset(struct mii_bus *bus) |
201 | { |
202 | /*nothing here - dunno how to reset it*/ |
203 | return 0; |
204 | } |
205 | |
206 | |
207 | static int gpio_mdio_probe(struct platform_device *ofdev) |
208 | { |
209 | struct device *dev = &ofdev->dev; |
210 | struct device_node *np = ofdev->dev.of_node; |
211 | struct mii_bus *new_bus; |
212 | struct gpio_priv *priv; |
213 | const unsigned int *prop; |
214 | int err; |
215 | |
216 | err = -ENOMEM; |
217 | priv = kzalloc(size: sizeof(struct gpio_priv), GFP_KERNEL); |
218 | if (!priv) |
219 | goto out; |
220 | |
221 | new_bus = mdiobus_alloc(); |
222 | |
223 | if (!new_bus) |
224 | goto out_free_priv; |
225 | |
226 | new_bus->name = "pasemi gpio mdio bus" ; |
227 | new_bus->read = &gpio_mdio_read; |
228 | new_bus->write = &gpio_mdio_write; |
229 | new_bus->reset = &gpio_mdio_reset; |
230 | |
231 | prop = of_get_property(node: np, name: "reg" , NULL); |
232 | snprintf(buf: new_bus->id, MII_BUS_ID_SIZE, fmt: "%x" , *prop); |
233 | new_bus->priv = priv; |
234 | |
235 | prop = of_get_property(node: np, name: "mdc-pin" , NULL); |
236 | priv->mdc_pin = *prop; |
237 | |
238 | prop = of_get_property(node: np, name: "mdio-pin" , NULL); |
239 | priv->mdio_pin = *prop; |
240 | |
241 | new_bus->parent = dev; |
242 | dev_set_drvdata(dev, data: new_bus); |
243 | |
244 | err = of_mdiobus_register(mdio: new_bus, np); |
245 | |
246 | if (err != 0) { |
247 | pr_err("%s: Cannot register as MDIO bus, err %d\n" , |
248 | new_bus->name, err); |
249 | goto out_free_irq; |
250 | } |
251 | |
252 | return 0; |
253 | |
254 | out_free_irq: |
255 | kfree(objp: new_bus); |
256 | out_free_priv: |
257 | kfree(objp: priv); |
258 | out: |
259 | return err; |
260 | } |
261 | |
262 | |
263 | static void gpio_mdio_remove(struct platform_device *dev) |
264 | { |
265 | struct mii_bus *bus = dev_get_drvdata(dev: &dev->dev); |
266 | |
267 | mdiobus_unregister(bus); |
268 | |
269 | dev_set_drvdata(dev: &dev->dev, NULL); |
270 | |
271 | kfree(objp: bus->priv); |
272 | bus->priv = NULL; |
273 | mdiobus_free(bus); |
274 | } |
275 | |
276 | static const struct of_device_id gpio_mdio_match[] = |
277 | { |
278 | { |
279 | .compatible = "gpio-mdio" , |
280 | }, |
281 | {}, |
282 | }; |
283 | MODULE_DEVICE_TABLE(of, gpio_mdio_match); |
284 | |
285 | static struct platform_driver gpio_mdio_driver = |
286 | { |
287 | .probe = gpio_mdio_probe, |
288 | .remove_new = gpio_mdio_remove, |
289 | .driver = { |
290 | .name = "gpio-mdio-bitbang" , |
291 | .of_match_table = gpio_mdio_match, |
292 | }, |
293 | }; |
294 | |
295 | static int __init gpio_mdio_init(void) |
296 | { |
297 | struct device_node *np; |
298 | |
299 | np = of_find_compatible_node(NULL, NULL, compat: "1682m-gpio" ); |
300 | if (!np) |
301 | np = of_find_compatible_node(NULL, NULL, |
302 | compat: "pasemi,pwrficient-gpio" ); |
303 | if (!np) |
304 | return -ENODEV; |
305 | gpio_regs = of_iomap(node: np, index: 0); |
306 | of_node_put(node: np); |
307 | |
308 | if (!gpio_regs) |
309 | return -ENODEV; |
310 | |
311 | return platform_driver_register(&gpio_mdio_driver); |
312 | } |
313 | module_init(gpio_mdio_init); |
314 | |
315 | static void __exit gpio_mdio_exit(void) |
316 | { |
317 | platform_driver_unregister(&gpio_mdio_driver); |
318 | if (gpio_regs) |
319 | iounmap(addr: gpio_regs); |
320 | } |
321 | module_exit(gpio_mdio_exit); |
322 | |
323 | MODULE_LICENSE("GPL" ); |
324 | MODULE_AUTHOR("Olof Johansson <olof@lixom.net>" ); |
325 | MODULE_DESCRIPTION("Driver for MDIO over GPIO on PA Semi PWRficient-based boards" ); |
326 | |