Commit | Line | Data |
---|---|---|
45051539 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
b97d2791 OJ |
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. | |
b97d2791 OJ |
10 | */ |
11 | ||
12 | #include <linux/io.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/types.h> | |
5a0e3ad6 | 15 | #include <linux/slab.h> |
b97d2791 OJ |
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> | |
26a2056e | 21 | #include <linux/of_address.h> |
1dd2d06c | 22 | #include <linux/of_mdio.h> |
5f867dc7 | 23 | #include <linux/of_platform.h> |
b97d2791 OJ |
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, 1); | |
93 | } | |
94 | ||
95 | /* send the start bit (01) and the read opcode (10) or write (10) */ | |
96 | clock_out(bus, 0); | |
97 | clock_out(bus, 1); | |
98 | ||
99 | clock_out(bus, read); | |
100 | clock_out(bus, !read); | |
101 | ||
102 | /* send the PHY address */ | |
103 | for (i = 0; i < 5; i++) { | |
104 | clock_out(bus, (addr & 0x10) != 0); | |
105 | addr <<= 1; | |
106 | } | |
107 | ||
108 | /* send the register address */ | |
109 | for (i = 0; i < 5; i++) { | |
110 | clock_out(bus, (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, 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, 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 | ||
cad5cef6 | 207 | static int gpio_mdio_probe(struct platform_device *ofdev) |
b97d2791 OJ |
208 | { |
209 | struct device *dev = &ofdev->dev; | |
61c7a080 | 210 | struct device_node *np = ofdev->dev.of_node; |
b97d2791 | 211 | struct mii_bus *new_bus; |
b97d2791 OJ |
212 | struct gpio_priv *priv; |
213 | const unsigned int *prop; | |
2dd3c001 | 214 | int err; |
b97d2791 | 215 | |
2dd3c001 | 216 | err = -ENOMEM; |
b97d2791 | 217 | priv = kzalloc(sizeof(struct gpio_priv), GFP_KERNEL); |
2dd3c001 OJ |
218 | if (!priv) |
219 | goto out; | |
b97d2791 | 220 | |
ec2a5652 | 221 | new_bus = mdiobus_alloc(); |
b97d2791 | 222 | |
2dd3c001 OJ |
223 | if (!new_bus) |
224 | goto out_free_priv; | |
b97d2791 | 225 | |
2dd3c001 OJ |
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; | |
b97d2791 | 230 | |
12d371a6 | 231 | prop = of_get_property(np, "reg", NULL); |
9d9326d3 | 232 | snprintf(new_bus->id, MII_BUS_ID_SIZE, "%x", *prop); |
b97d2791 OJ |
233 | new_bus->priv = priv; |
234 | ||
12d371a6 | 235 | prop = of_get_property(np, "mdc-pin", NULL); |
b97d2791 OJ |
236 | priv->mdc_pin = *prop; |
237 | ||
12d371a6 | 238 | prop = of_get_property(np, "mdio-pin", NULL); |
b97d2791 OJ |
239 | priv->mdio_pin = *prop; |
240 | ||
18ee49dd | 241 | new_bus->parent = dev; |
b97d2791 OJ |
242 | dev_set_drvdata(dev, new_bus); |
243 | ||
1dd2d06c | 244 | err = of_mdiobus_register(new_bus, np); |
b97d2791 | 245 | |
2dd3c001 | 246 | if (err != 0) { |
e13606d7 | 247 | pr_err("%s: Cannot register as MDIO bus, err %d\n", |
b97d2791 | 248 | new_bus->name, err); |
2dd3c001 | 249 | goto out_free_irq; |
b97d2791 OJ |
250 | } |
251 | ||
252 | return 0; | |
253 | ||
2dd3c001 | 254 | out_free_irq: |
b97d2791 | 255 | kfree(new_bus); |
2dd3c001 OJ |
256 | out_free_priv: |
257 | kfree(priv); | |
258 | out: | |
b97d2791 OJ |
259 | return err; |
260 | } | |
261 | ||
262 | ||
a454dc50 | 263 | static int gpio_mdio_remove(struct platform_device *dev) |
b97d2791 OJ |
264 | { |
265 | struct mii_bus *bus = dev_get_drvdata(&dev->dev); | |
266 | ||
267 | mdiobus_unregister(bus); | |
268 | ||
269 | dev_set_drvdata(&dev->dev, NULL); | |
270 | ||
271 | kfree(bus->priv); | |
272 | bus->priv = NULL; | |
ec2a5652 | 273 | mdiobus_free(bus); |
b97d2791 OJ |
274 | |
275 | return 0; | |
276 | } | |
277 | ||
ce6d73c9 | 278 | static const struct of_device_id gpio_mdio_match[] = |
b97d2791 OJ |
279 | { |
280 | { | |
281 | .compatible = "gpio-mdio", | |
282 | }, | |
283 | {}, | |
284 | }; | |
5619965f | 285 | MODULE_DEVICE_TABLE(of, gpio_mdio_match); |
b97d2791 | 286 | |
00006124 | 287 | static struct platform_driver gpio_mdio_driver = |
b97d2791 | 288 | { |
b97d2791 OJ |
289 | .probe = gpio_mdio_probe, |
290 | .remove = gpio_mdio_remove, | |
4018294b GL |
291 | .driver = { |
292 | .name = "gpio-mdio-bitbang", | |
4018294b | 293 | .of_match_table = gpio_mdio_match, |
84dd4676 | 294 | }, |
b97d2791 OJ |
295 | }; |
296 | ||
7c98bd72 | 297 | static int gpio_mdio_init(void) |
b97d2791 | 298 | { |
2dd3c001 OJ |
299 | struct device_node *np; |
300 | ||
0d08a847 OJ |
301 | np = of_find_compatible_node(NULL, NULL, "1682m-gpio"); |
302 | if (!np) | |
303 | np = of_find_compatible_node(NULL, NULL, | |
304 | "pasemi,pwrficient-gpio"); | |
2dd3c001 OJ |
305 | if (!np) |
306 | return -ENODEV; | |
307 | gpio_regs = of_iomap(np, 0); | |
308 | of_node_put(np); | |
309 | ||
310 | if (!gpio_regs) | |
311 | return -ENODEV; | |
312 | ||
00006124 | 313 | return platform_driver_register(&gpio_mdio_driver); |
b97d2791 | 314 | } |
2dd3c001 | 315 | module_init(gpio_mdio_init); |
b97d2791 | 316 | |
7c98bd72 | 317 | static void gpio_mdio_exit(void) |
b97d2791 | 318 | { |
00006124 | 319 | platform_driver_unregister(&gpio_mdio_driver); |
2dd3c001 OJ |
320 | if (gpio_regs) |
321 | iounmap(gpio_regs); | |
b97d2791 | 322 | } |
2dd3c001 | 323 | module_exit(gpio_mdio_exit); |
5619965f OJ |
324 | |
325 | MODULE_LICENSE("GPL"); | |
326 | MODULE_AUTHOR("Olof Johansson <olof@lixom.net>"); | |
327 | MODULE_DESCRIPTION("Driver for MDIO over GPIO on PA Semi PWRficient-based boards"); |