Commit 55e331cf7ebe20665253770589cd9eb06048bf25
Committed by
Linus Torvalds
1 parent
f324edc85e
Exists in
master
and in
4 other branches
drivers: add support for the TI VLYNQ bus
Add support for the TI VLYNQ high-speed, serial and packetized bus. This bus allows external devices to be connected to the System-on-Chip and appear in the main system memory just like any memory mapped peripheral. It is widely used in TI's networking and multimedia SoC, including the AR7 SoC. Signed-off-by: Eugene Konev <ejka@imfi.kspu.ru> Signed-off-by: Florian Fainelli <florian@openwrt.org> Cc: Ralf Baechle <ralf@linux-mips.org> Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> Cc: Greg KH <greg@kroah.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Showing 7 changed files with 1011 additions and 0 deletions Side-by-side Diff
MAINTAINERS
... | ... | @@ -6281,6 +6281,14 @@ |
6281 | 6281 | F: include/linux/if_*vlan.h |
6282 | 6282 | F: net/8021q/ |
6283 | 6283 | |
6284 | +VLYNQ BUS | |
6285 | +P: Florian Fainelli | |
6286 | +M: florian@openwrt.org | |
6287 | +L: openwrt-devel@lists.openwrt.org | |
6288 | +S: Maintained | |
6289 | +F: drivers/vlynq/vlynq.c | |
6290 | +F: include/linux/vlynq.h | |
6291 | + | |
6284 | 6292 | VOLTAGE AND CURRENT REGULATOR FRAMEWORK |
6285 | 6293 | P: Liam Girdwood |
6286 | 6294 | M: lrg@slimlogic.co.uk |
drivers/Kconfig
drivers/Makefile
drivers/vlynq/Kconfig
1 | +menu "TI VLYNQ" | |
2 | + | |
3 | +config VLYNQ | |
4 | + bool "TI VLYNQ bus support" | |
5 | + depends on AR7 && EXPERIMENTAL | |
6 | + help | |
7 | + Support for Texas Instruments(R) VLYNQ bus. | |
8 | + The VLYNQ bus is a high-speed, serial and packetized | |
9 | + data bus which allows external peripherals of a SoC | |
10 | + to appear into the system's main memory. | |
11 | + | |
12 | + If unsure, say N | |
13 | + | |
14 | +config VLYNQ_DEBUG | |
15 | + bool "VLYNQ bus debug" | |
16 | + depends on VLYNQ && KERNEL_DEBUG | |
17 | + help | |
18 | + Turn on VLYNQ bus debugging. | |
19 | + | |
20 | +endmenu |
drivers/vlynq/Makefile
drivers/vlynq/vlynq.c
1 | +/* | |
2 | + * Copyright (C) 2006, 2007 Eugene Konev <ejka@openwrt.org> | |
3 | + * | |
4 | + * This program is free software; you can redistribute it and/or modify | |
5 | + * it under the terms of the GNU General Public License as published by | |
6 | + * the Free Software Foundation; either version 2 of the License, or | |
7 | + * (at your option) any later version. | |
8 | + * | |
9 | + * This program is distributed in the hope that it will be useful, | |
10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | + * GNU General Public License for more details. | |
13 | + * | |
14 | + * You should have received a copy of the GNU General Public License | |
15 | + * along with this program; if not, write to the Free Software | |
16 | + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
17 | + * | |
18 | + * Parts of the VLYNQ specification can be found here: | |
19 | + * http://www.ti.com/litv/pdf/sprue36a | |
20 | + */ | |
21 | + | |
22 | +#include <linux/init.h> | |
23 | +#include <linux/types.h> | |
24 | +#include <linux/kernel.h> | |
25 | +#include <linux/string.h> | |
26 | +#include <linux/device.h> | |
27 | +#include <linux/module.h> | |
28 | +#include <linux/errno.h> | |
29 | +#include <linux/platform_device.h> | |
30 | +#include <linux/interrupt.h> | |
31 | +#include <linux/device.h> | |
32 | +#include <linux/delay.h> | |
33 | +#include <linux/io.h> | |
34 | + | |
35 | +#include <linux/vlynq.h> | |
36 | + | |
37 | +#define VLYNQ_CTRL_PM_ENABLE 0x80000000 | |
38 | +#define VLYNQ_CTRL_CLOCK_INT 0x00008000 | |
39 | +#define VLYNQ_CTRL_CLOCK_DIV(x) (((x) & 7) << 16) | |
40 | +#define VLYNQ_CTRL_INT_LOCAL 0x00004000 | |
41 | +#define VLYNQ_CTRL_INT_ENABLE 0x00002000 | |
42 | +#define VLYNQ_CTRL_INT_VECTOR(x) (((x) & 0x1f) << 8) | |
43 | +#define VLYNQ_CTRL_INT2CFG 0x00000080 | |
44 | +#define VLYNQ_CTRL_RESET 0x00000001 | |
45 | + | |
46 | +#define VLYNQ_CTRL_CLOCK_MASK (0x7 << 16) | |
47 | + | |
48 | +#define VLYNQ_INT_OFFSET 0x00000014 | |
49 | +#define VLYNQ_REMOTE_OFFSET 0x00000080 | |
50 | + | |
51 | +#define VLYNQ_STATUS_LINK 0x00000001 | |
52 | +#define VLYNQ_STATUS_LERROR 0x00000080 | |
53 | +#define VLYNQ_STATUS_RERROR 0x00000100 | |
54 | + | |
55 | +#define VINT_ENABLE 0x00000100 | |
56 | +#define VINT_TYPE_EDGE 0x00000080 | |
57 | +#define VINT_LEVEL_LOW 0x00000040 | |
58 | +#define VINT_VECTOR(x) ((x) & 0x1f) | |
59 | +#define VINT_OFFSET(irq) (8 * ((irq) % 4)) | |
60 | + | |
61 | +#define VLYNQ_AUTONEGO_V2 0x00010000 | |
62 | + | |
63 | +struct vlynq_regs { | |
64 | + u32 revision; | |
65 | + u32 control; | |
66 | + u32 status; | |
67 | + u32 int_prio; | |
68 | + u32 int_status; | |
69 | + u32 int_pending; | |
70 | + u32 int_ptr; | |
71 | + u32 tx_offset; | |
72 | + struct vlynq_mapping rx_mapping[4]; | |
73 | + u32 chip; | |
74 | + u32 autonego; | |
75 | + u32 unused[6]; | |
76 | + u32 int_device[8]; | |
77 | +}; | |
78 | + | |
79 | +#ifdef VLYNQ_DEBUG | |
80 | +static void vlynq_dump_regs(struct vlynq_device *dev) | |
81 | +{ | |
82 | + int i; | |
83 | + | |
84 | + printk(KERN_DEBUG "VLYNQ local=%p remote=%p\n", | |
85 | + dev->local, dev->remote); | |
86 | + for (i = 0; i < 32; i++) { | |
87 | + printk(KERN_DEBUG "VLYNQ: local %d: %08x\n", | |
88 | + i + 1, ((u32 *)dev->local)[i]); | |
89 | + printk(KERN_DEBUG "VLYNQ: remote %d: %08x\n", | |
90 | + i + 1, ((u32 *)dev->remote)[i]); | |
91 | + } | |
92 | +} | |
93 | + | |
94 | +static void vlynq_dump_mem(u32 *base, int count) | |
95 | +{ | |
96 | + int i; | |
97 | + | |
98 | + for (i = 0; i < (count + 3) / 4; i++) { | |
99 | + if (i % 4 == 0) | |
100 | + printk(KERN_DEBUG "\nMEM[0x%04x]:", i * 4); | |
101 | + printk(KERN_DEBUG " 0x%08x", *(base + i)); | |
102 | + } | |
103 | + printk(KERN_DEBUG "\n"); | |
104 | +} | |
105 | +#endif | |
106 | + | |
107 | +/* Check the VLYNQ link status with a given device */ | |
108 | +static int vlynq_linked(struct vlynq_device *dev) | |
109 | +{ | |
110 | + int i; | |
111 | + | |
112 | + for (i = 0; i < 100; i++) | |
113 | + if (readl(&dev->local->status) & VLYNQ_STATUS_LINK) | |
114 | + return 1; | |
115 | + else | |
116 | + cpu_relax(); | |
117 | + | |
118 | + return 0; | |
119 | +} | |
120 | + | |
121 | +static void vlynq_reset(struct vlynq_device *dev) | |
122 | +{ | |
123 | + writel(readl(&dev->local->control) | VLYNQ_CTRL_RESET, | |
124 | + &dev->local->control); | |
125 | + | |
126 | + /* Wait for the devices to finish resetting */ | |
127 | + msleep(5); | |
128 | + | |
129 | + /* Remove reset bit */ | |
130 | + writel(readl(&dev->local->control) & ~VLYNQ_CTRL_RESET, | |
131 | + &dev->local->control); | |
132 | + | |
133 | + /* Give some time for the devices to settle */ | |
134 | + msleep(5); | |
135 | +} | |
136 | + | |
137 | +static void vlynq_irq_unmask(unsigned int irq) | |
138 | +{ | |
139 | + u32 val; | |
140 | + struct vlynq_device *dev = get_irq_chip_data(irq); | |
141 | + int virq; | |
142 | + | |
143 | + BUG_ON(!dev); | |
144 | + virq = irq - dev->irq_start; | |
145 | + val = readl(&dev->remote->int_device[virq >> 2]); | |
146 | + val |= (VINT_ENABLE | virq) << VINT_OFFSET(virq); | |
147 | + writel(val, &dev->remote->int_device[virq >> 2]); | |
148 | +} | |
149 | + | |
150 | +static void vlynq_irq_mask(unsigned int irq) | |
151 | +{ | |
152 | + u32 val; | |
153 | + struct vlynq_device *dev = get_irq_chip_data(irq); | |
154 | + int virq; | |
155 | + | |
156 | + BUG_ON(!dev); | |
157 | + virq = irq - dev->irq_start; | |
158 | + val = readl(&dev->remote->int_device[virq >> 2]); | |
159 | + val &= ~(VINT_ENABLE << VINT_OFFSET(virq)); | |
160 | + writel(val, &dev->remote->int_device[virq >> 2]); | |
161 | +} | |
162 | + | |
163 | +static int vlynq_irq_type(unsigned int irq, unsigned int flow_type) | |
164 | +{ | |
165 | + u32 val; | |
166 | + struct vlynq_device *dev = get_irq_chip_data(irq); | |
167 | + int virq; | |
168 | + | |
169 | + BUG_ON(!dev); | |
170 | + virq = irq - dev->irq_start; | |
171 | + val = readl(&dev->remote->int_device[virq >> 2]); | |
172 | + switch (flow_type & IRQ_TYPE_SENSE_MASK) { | |
173 | + case IRQ_TYPE_EDGE_RISING: | |
174 | + case IRQ_TYPE_EDGE_FALLING: | |
175 | + case IRQ_TYPE_EDGE_BOTH: | |
176 | + val |= VINT_TYPE_EDGE << VINT_OFFSET(virq); | |
177 | + val &= ~(VINT_LEVEL_LOW << VINT_OFFSET(virq)); | |
178 | + break; | |
179 | + case IRQ_TYPE_LEVEL_HIGH: | |
180 | + val &= ~(VINT_TYPE_EDGE << VINT_OFFSET(virq)); | |
181 | + val &= ~(VINT_LEVEL_LOW << VINT_OFFSET(virq)); | |
182 | + break; | |
183 | + case IRQ_TYPE_LEVEL_LOW: | |
184 | + val &= ~(VINT_TYPE_EDGE << VINT_OFFSET(virq)); | |
185 | + val |= VINT_LEVEL_LOW << VINT_OFFSET(virq); | |
186 | + break; | |
187 | + default: | |
188 | + return -EINVAL; | |
189 | + } | |
190 | + writel(val, &dev->remote->int_device[virq >> 2]); | |
191 | + return 0; | |
192 | +} | |
193 | + | |
194 | +static void vlynq_local_ack(unsigned int irq) | |
195 | +{ | |
196 | + struct vlynq_device *dev = get_irq_chip_data(irq); | |
197 | + | |
198 | + u32 status = readl(&dev->local->status); | |
199 | + | |
200 | + pr_debug("%s: local status: 0x%08x\n", | |
201 | + dev_name(&dev->dev), status); | |
202 | + writel(status, &dev->local->status); | |
203 | +} | |
204 | + | |
205 | +static void vlynq_remote_ack(unsigned int irq) | |
206 | +{ | |
207 | + struct vlynq_device *dev = get_irq_chip_data(irq); | |
208 | + | |
209 | + u32 status = readl(&dev->remote->status); | |
210 | + | |
211 | + pr_debug("%s: remote status: 0x%08x\n", | |
212 | + dev_name(&dev->dev), status); | |
213 | + writel(status, &dev->remote->status); | |
214 | +} | |
215 | + | |
216 | +static irqreturn_t vlynq_irq(int irq, void *dev_id) | |
217 | +{ | |
218 | + struct vlynq_device *dev = dev_id; | |
219 | + u32 status; | |
220 | + int virq = 0; | |
221 | + | |
222 | + status = readl(&dev->local->int_status); | |
223 | + writel(status, &dev->local->int_status); | |
224 | + | |
225 | + if (unlikely(!status)) | |
226 | + spurious_interrupt(); | |
227 | + | |
228 | + while (status) { | |
229 | + if (status & 1) | |
230 | + do_IRQ(dev->irq_start + virq); | |
231 | + status >>= 1; | |
232 | + virq++; | |
233 | + } | |
234 | + | |
235 | + return IRQ_HANDLED; | |
236 | +} | |
237 | + | |
238 | +static struct irq_chip vlynq_irq_chip = { | |
239 | + .name = "vlynq", | |
240 | + .unmask = vlynq_irq_unmask, | |
241 | + .mask = vlynq_irq_mask, | |
242 | + .set_type = vlynq_irq_type, | |
243 | +}; | |
244 | + | |
245 | +static struct irq_chip vlynq_local_chip = { | |
246 | + .name = "vlynq local error", | |
247 | + .unmask = vlynq_irq_unmask, | |
248 | + .mask = vlynq_irq_mask, | |
249 | + .ack = vlynq_local_ack, | |
250 | +}; | |
251 | + | |
252 | +static struct irq_chip vlynq_remote_chip = { | |
253 | + .name = "vlynq local error", | |
254 | + .unmask = vlynq_irq_unmask, | |
255 | + .mask = vlynq_irq_mask, | |
256 | + .ack = vlynq_remote_ack, | |
257 | +}; | |
258 | + | |
259 | +static int vlynq_setup_irq(struct vlynq_device *dev) | |
260 | +{ | |
261 | + u32 val; | |
262 | + int i, virq; | |
263 | + | |
264 | + if (dev->local_irq == dev->remote_irq) { | |
265 | + printk(KERN_ERR | |
266 | + "%s: local vlynq irq should be different from remote\n", | |
267 | + dev_name(&dev->dev)); | |
268 | + return -EINVAL; | |
269 | + } | |
270 | + | |
271 | + /* Clear local and remote error bits */ | |
272 | + writel(readl(&dev->local->status), &dev->local->status); | |
273 | + writel(readl(&dev->remote->status), &dev->remote->status); | |
274 | + | |
275 | + /* Now setup interrupts */ | |
276 | + val = VLYNQ_CTRL_INT_VECTOR(dev->local_irq); | |
277 | + val |= VLYNQ_CTRL_INT_ENABLE | VLYNQ_CTRL_INT_LOCAL | | |
278 | + VLYNQ_CTRL_INT2CFG; | |
279 | + val |= readl(&dev->local->control); | |
280 | + writel(VLYNQ_INT_OFFSET, &dev->local->int_ptr); | |
281 | + writel(val, &dev->local->control); | |
282 | + | |
283 | + val = VLYNQ_CTRL_INT_VECTOR(dev->remote_irq); | |
284 | + val |= VLYNQ_CTRL_INT_ENABLE; | |
285 | + val |= readl(&dev->remote->control); | |
286 | + writel(VLYNQ_INT_OFFSET, &dev->remote->int_ptr); | |
287 | + writel(val, &dev->remote->int_ptr); | |
288 | + writel(val, &dev->remote->control); | |
289 | + | |
290 | + for (i = dev->irq_start; i <= dev->irq_end; i++) { | |
291 | + virq = i - dev->irq_start; | |
292 | + if (virq == dev->local_irq) { | |
293 | + set_irq_chip_and_handler(i, &vlynq_local_chip, | |
294 | + handle_level_irq); | |
295 | + set_irq_chip_data(i, dev); | |
296 | + } else if (virq == dev->remote_irq) { | |
297 | + set_irq_chip_and_handler(i, &vlynq_remote_chip, | |
298 | + handle_level_irq); | |
299 | + set_irq_chip_data(i, dev); | |
300 | + } else { | |
301 | + set_irq_chip_and_handler(i, &vlynq_irq_chip, | |
302 | + handle_simple_irq); | |
303 | + set_irq_chip_data(i, dev); | |
304 | + writel(0, &dev->remote->int_device[virq >> 2]); | |
305 | + } | |
306 | + } | |
307 | + | |
308 | + if (request_irq(dev->irq, vlynq_irq, IRQF_SHARED, "vlynq", dev)) { | |
309 | + printk(KERN_ERR "%s: request_irq failed\n", | |
310 | + dev_name(&dev->dev)); | |
311 | + return -EAGAIN; | |
312 | + } | |
313 | + | |
314 | + return 0; | |
315 | +} | |
316 | + | |
317 | +static void vlynq_device_release(struct device *dev) | |
318 | +{ | |
319 | + struct vlynq_device *vdev = to_vlynq_device(dev); | |
320 | + kfree(vdev); | |
321 | +} | |
322 | + | |
323 | +static int vlynq_device_match(struct device *dev, | |
324 | + struct device_driver *drv) | |
325 | +{ | |
326 | + struct vlynq_device *vdev = to_vlynq_device(dev); | |
327 | + struct vlynq_driver *vdrv = to_vlynq_driver(drv); | |
328 | + struct vlynq_device_id *ids = vdrv->id_table; | |
329 | + | |
330 | + while (ids->id) { | |
331 | + if (ids->id == vdev->dev_id) { | |
332 | + vdev->divisor = ids->divisor; | |
333 | + vlynq_set_drvdata(vdev, ids); | |
334 | + printk(KERN_INFO "Driver found for VLYNQ " | |
335 | + "device: %08x\n", vdev->dev_id); | |
336 | + return 1; | |
337 | + } | |
338 | + printk(KERN_DEBUG "Not using the %08x VLYNQ device's driver" | |
339 | + " for VLYNQ device: %08x\n", ids->id, vdev->dev_id); | |
340 | + ids++; | |
341 | + } | |
342 | + return 0; | |
343 | +} | |
344 | + | |
345 | +static int vlynq_device_probe(struct device *dev) | |
346 | +{ | |
347 | + struct vlynq_device *vdev = to_vlynq_device(dev); | |
348 | + struct vlynq_driver *drv = to_vlynq_driver(dev->driver); | |
349 | + struct vlynq_device_id *id = vlynq_get_drvdata(vdev); | |
350 | + int result = -ENODEV; | |
351 | + | |
352 | + if (drv->probe) | |
353 | + result = drv->probe(vdev, id); | |
354 | + if (result) | |
355 | + put_device(dev); | |
356 | + return result; | |
357 | +} | |
358 | + | |
359 | +static int vlynq_device_remove(struct device *dev) | |
360 | +{ | |
361 | + struct vlynq_driver *drv = to_vlynq_driver(dev->driver); | |
362 | + | |
363 | + if (drv->remove) | |
364 | + drv->remove(to_vlynq_device(dev)); | |
365 | + | |
366 | + return 0; | |
367 | +} | |
368 | + | |
369 | +int __vlynq_register_driver(struct vlynq_driver *driver, struct module *owner) | |
370 | +{ | |
371 | + driver->driver.name = driver->name; | |
372 | + driver->driver.bus = &vlynq_bus_type; | |
373 | + return driver_register(&driver->driver); | |
374 | +} | |
375 | +EXPORT_SYMBOL(__vlynq_register_driver); | |
376 | + | |
377 | +void vlynq_unregister_driver(struct vlynq_driver *driver) | |
378 | +{ | |
379 | + driver_unregister(&driver->driver); | |
380 | +} | |
381 | +EXPORT_SYMBOL(vlynq_unregister_driver); | |
382 | + | |
383 | +/* | |
384 | + * A VLYNQ remote device can clock the VLYNQ bus master | |
385 | + * using a dedicated clock line. In that case, both the | |
386 | + * remove device and the bus master should have the same | |
387 | + * serial clock dividers configured. Iterate through the | |
388 | + * 8 possible dividers until we actually link with the | |
389 | + * device. | |
390 | + */ | |
391 | +static int __vlynq_try_remote(struct vlynq_device *dev) | |
392 | +{ | |
393 | + int i; | |
394 | + | |
395 | + vlynq_reset(dev); | |
396 | + for (i = dev->dev_id ? vlynq_rdiv2 : vlynq_rdiv8; dev->dev_id ? | |
397 | + i <= vlynq_rdiv8 : i >= vlynq_rdiv2; | |
398 | + dev->dev_id ? i++ : i--) { | |
399 | + | |
400 | + if (!vlynq_linked(dev)) | |
401 | + break; | |
402 | + | |
403 | + writel((readl(&dev->remote->control) & | |
404 | + ~VLYNQ_CTRL_CLOCK_MASK) | | |
405 | + VLYNQ_CTRL_CLOCK_INT | | |
406 | + VLYNQ_CTRL_CLOCK_DIV(i - vlynq_rdiv1), | |
407 | + &dev->remote->control); | |
408 | + writel((readl(&dev->local->control) | |
409 | + & ~(VLYNQ_CTRL_CLOCK_INT | | |
410 | + VLYNQ_CTRL_CLOCK_MASK)) | | |
411 | + VLYNQ_CTRL_CLOCK_DIV(i - vlynq_rdiv1), | |
412 | + &dev->local->control); | |
413 | + | |
414 | + if (vlynq_linked(dev)) { | |
415 | + printk(KERN_DEBUG | |
416 | + "%s: using remote clock divisor %d\n", | |
417 | + dev_name(&dev->dev), i - vlynq_rdiv1 + 1); | |
418 | + dev->divisor = i; | |
419 | + return 0; | |
420 | + } else { | |
421 | + vlynq_reset(dev); | |
422 | + } | |
423 | + } | |
424 | + | |
425 | + return -ENODEV; | |
426 | +} | |
427 | + | |
428 | +/* | |
429 | + * A VLYNQ remote device can be clocked by the VLYNQ bus | |
430 | + * master using a dedicated clock line. In that case, only | |
431 | + * the bus master configures the serial clock divider. | |
432 | + * Iterate through the 8 possible dividers until we | |
433 | + * actually get a link with the device. | |
434 | + */ | |
435 | +static int __vlynq_try_local(struct vlynq_device *dev) | |
436 | +{ | |
437 | + int i; | |
438 | + | |
439 | + vlynq_reset(dev); | |
440 | + | |
441 | + for (i = dev->dev_id ? vlynq_ldiv2 : vlynq_ldiv8; dev->dev_id ? | |
442 | + i <= vlynq_ldiv8 : i >= vlynq_ldiv2; | |
443 | + dev->dev_id ? i++ : i--) { | |
444 | + | |
445 | + writel((readl(&dev->local->control) & | |
446 | + ~VLYNQ_CTRL_CLOCK_MASK) | | |
447 | + VLYNQ_CTRL_CLOCK_INT | | |
448 | + VLYNQ_CTRL_CLOCK_DIV(i - vlynq_ldiv1), | |
449 | + &dev->local->control); | |
450 | + | |
451 | + if (vlynq_linked(dev)) { | |
452 | + printk(KERN_DEBUG | |
453 | + "%s: using local clock divisor %d\n", | |
454 | + dev_name(&dev->dev), i - vlynq_ldiv1 + 1); | |
455 | + dev->divisor = i; | |
456 | + return 0; | |
457 | + } else { | |
458 | + vlynq_reset(dev); | |
459 | + } | |
460 | + } | |
461 | + | |
462 | + return -ENODEV; | |
463 | +} | |
464 | + | |
465 | +/* | |
466 | + * When using external clocking method, serial clock | |
467 | + * is supplied by an external oscillator, therefore we | |
468 | + * should mask the local clock bit in the clock control | |
469 | + * register for both the bus master and the remote device. | |
470 | + */ | |
471 | +static int __vlynq_try_external(struct vlynq_device *dev) | |
472 | +{ | |
473 | + vlynq_reset(dev); | |
474 | + if (!vlynq_linked(dev)) | |
475 | + return -ENODEV; | |
476 | + | |
477 | + writel((readl(&dev->remote->control) & | |
478 | + ~VLYNQ_CTRL_CLOCK_INT), | |
479 | + &dev->remote->control); | |
480 | + | |
481 | + writel((readl(&dev->local->control) & | |
482 | + ~VLYNQ_CTRL_CLOCK_INT), | |
483 | + &dev->local->control); | |
484 | + | |
485 | + if (vlynq_linked(dev)) { | |
486 | + printk(KERN_DEBUG "%s: using external clock\n", | |
487 | + dev_name(&dev->dev)); | |
488 | + dev->divisor = vlynq_div_external; | |
489 | + return 0; | |
490 | + } | |
491 | + | |
492 | + return -ENODEV; | |
493 | +} | |
494 | + | |
495 | +static int __vlynq_enable_device(struct vlynq_device *dev) | |
496 | +{ | |
497 | + int result; | |
498 | + struct plat_vlynq_ops *ops = dev->dev.platform_data; | |
499 | + | |
500 | + result = ops->on(dev); | |
501 | + if (result) | |
502 | + return result; | |
503 | + | |
504 | + switch (dev->divisor) { | |
505 | + case vlynq_div_external: | |
506 | + case vlynq_div_auto: | |
507 | + /* When the device is brought from reset it should have clock | |
508 | + * generation negotiated by hardware. | |
509 | + * Check which device is generating clocks and perform setup | |
510 | + * accordingly */ | |
511 | + if (vlynq_linked(dev) && readl(&dev->remote->control) & | |
512 | + VLYNQ_CTRL_CLOCK_INT) { | |
513 | + if (!__vlynq_try_remote(dev) || | |
514 | + !__vlynq_try_local(dev) || | |
515 | + !__vlynq_try_external(dev)) | |
516 | + return 0; | |
517 | + } else { | |
518 | + if (!__vlynq_try_external(dev) || | |
519 | + !__vlynq_try_local(dev) || | |
520 | + !__vlynq_try_remote(dev)) | |
521 | + return 0; | |
522 | + } | |
523 | + break; | |
524 | + case vlynq_ldiv1: | |
525 | + case vlynq_ldiv2: | |
526 | + case vlynq_ldiv3: | |
527 | + case vlynq_ldiv4: | |
528 | + case vlynq_ldiv5: | |
529 | + case vlynq_ldiv6: | |
530 | + case vlynq_ldiv7: | |
531 | + case vlynq_ldiv8: | |
532 | + writel(VLYNQ_CTRL_CLOCK_INT | | |
533 | + VLYNQ_CTRL_CLOCK_DIV(dev->divisor - | |
534 | + vlynq_ldiv1), &dev->local->control); | |
535 | + writel(0, &dev->remote->control); | |
536 | + if (vlynq_linked(dev)) { | |
537 | + printk(KERN_DEBUG | |
538 | + "%s: using local clock divisor %d\n", | |
539 | + dev_name(&dev->dev), | |
540 | + dev->divisor - vlynq_ldiv1 + 1); | |
541 | + return 0; | |
542 | + } | |
543 | + break; | |
544 | + case vlynq_rdiv1: | |
545 | + case vlynq_rdiv2: | |
546 | + case vlynq_rdiv3: | |
547 | + case vlynq_rdiv4: | |
548 | + case vlynq_rdiv5: | |
549 | + case vlynq_rdiv6: | |
550 | + case vlynq_rdiv7: | |
551 | + case vlynq_rdiv8: | |
552 | + writel(0, &dev->local->control); | |
553 | + writel(VLYNQ_CTRL_CLOCK_INT | | |
554 | + VLYNQ_CTRL_CLOCK_DIV(dev->divisor - | |
555 | + vlynq_rdiv1), &dev->remote->control); | |
556 | + if (vlynq_linked(dev)) { | |
557 | + printk(KERN_DEBUG | |
558 | + "%s: using remote clock divisor %d\n", | |
559 | + dev_name(&dev->dev), | |
560 | + dev->divisor - vlynq_rdiv1 + 1); | |
561 | + return 0; | |
562 | + } | |
563 | + break; | |
564 | + } | |
565 | + | |
566 | + ops->off(dev); | |
567 | + return -ENODEV; | |
568 | +} | |
569 | + | |
570 | +int vlynq_enable_device(struct vlynq_device *dev) | |
571 | +{ | |
572 | + struct plat_vlynq_ops *ops = dev->dev.platform_data; | |
573 | + int result = -ENODEV; | |
574 | + | |
575 | + result = __vlynq_enable_device(dev); | |
576 | + if (result) | |
577 | + return result; | |
578 | + | |
579 | + result = vlynq_setup_irq(dev); | |
580 | + if (result) | |
581 | + ops->off(dev); | |
582 | + | |
583 | + dev->enabled = !result; | |
584 | + return result; | |
585 | +} | |
586 | +EXPORT_SYMBOL(vlynq_enable_device); | |
587 | + | |
588 | + | |
589 | +void vlynq_disable_device(struct vlynq_device *dev) | |
590 | +{ | |
591 | + struct plat_vlynq_ops *ops = dev->dev.platform_data; | |
592 | + | |
593 | + dev->enabled = 0; | |
594 | + free_irq(dev->irq, dev); | |
595 | + ops->off(dev); | |
596 | +} | |
597 | +EXPORT_SYMBOL(vlynq_disable_device); | |
598 | + | |
599 | +int vlynq_set_local_mapping(struct vlynq_device *dev, u32 tx_offset, | |
600 | + struct vlynq_mapping *mapping) | |
601 | +{ | |
602 | + int i; | |
603 | + | |
604 | + if (!dev->enabled) | |
605 | + return -ENXIO; | |
606 | + | |
607 | + writel(tx_offset, &dev->local->tx_offset); | |
608 | + for (i = 0; i < 4; i++) { | |
609 | + writel(mapping[i].offset, &dev->local->rx_mapping[i].offset); | |
610 | + writel(mapping[i].size, &dev->local->rx_mapping[i].size); | |
611 | + } | |
612 | + return 0; | |
613 | +} | |
614 | +EXPORT_SYMBOL(vlynq_set_local_mapping); | |
615 | + | |
616 | +int vlynq_set_remote_mapping(struct vlynq_device *dev, u32 tx_offset, | |
617 | + struct vlynq_mapping *mapping) | |
618 | +{ | |
619 | + int i; | |
620 | + | |
621 | + if (!dev->enabled) | |
622 | + return -ENXIO; | |
623 | + | |
624 | + writel(tx_offset, &dev->remote->tx_offset); | |
625 | + for (i = 0; i < 4; i++) { | |
626 | + writel(mapping[i].offset, &dev->remote->rx_mapping[i].offset); | |
627 | + writel(mapping[i].size, &dev->remote->rx_mapping[i].size); | |
628 | + } | |
629 | + return 0; | |
630 | +} | |
631 | +EXPORT_SYMBOL(vlynq_set_remote_mapping); | |
632 | + | |
633 | +int vlynq_set_local_irq(struct vlynq_device *dev, int virq) | |
634 | +{ | |
635 | + int irq = dev->irq_start + virq; | |
636 | + if (dev->enabled) | |
637 | + return -EBUSY; | |
638 | + | |
639 | + if ((irq < dev->irq_start) || (irq > dev->irq_end)) | |
640 | + return -EINVAL; | |
641 | + | |
642 | + if (virq == dev->remote_irq) | |
643 | + return -EINVAL; | |
644 | + | |
645 | + dev->local_irq = virq; | |
646 | + | |
647 | + return 0; | |
648 | +} | |
649 | +EXPORT_SYMBOL(vlynq_set_local_irq); | |
650 | + | |
651 | +int vlynq_set_remote_irq(struct vlynq_device *dev, int virq) | |
652 | +{ | |
653 | + int irq = dev->irq_start + virq; | |
654 | + if (dev->enabled) | |
655 | + return -EBUSY; | |
656 | + | |
657 | + if ((irq < dev->irq_start) || (irq > dev->irq_end)) | |
658 | + return -EINVAL; | |
659 | + | |
660 | + if (virq == dev->local_irq) | |
661 | + return -EINVAL; | |
662 | + | |
663 | + dev->remote_irq = virq; | |
664 | + | |
665 | + return 0; | |
666 | +} | |
667 | +EXPORT_SYMBOL(vlynq_set_remote_irq); | |
668 | + | |
669 | +static int vlynq_probe(struct platform_device *pdev) | |
670 | +{ | |
671 | + struct vlynq_device *dev; | |
672 | + struct resource *regs_res, *mem_res, *irq_res; | |
673 | + int len, result; | |
674 | + | |
675 | + regs_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); | |
676 | + if (!regs_res) | |
677 | + return -ENODEV; | |
678 | + | |
679 | + mem_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mem"); | |
680 | + if (!mem_res) | |
681 | + return -ENODEV; | |
682 | + | |
683 | + irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "devirq"); | |
684 | + if (!irq_res) | |
685 | + return -ENODEV; | |
686 | + | |
687 | + dev = kzalloc(sizeof(*dev), GFP_KERNEL); | |
688 | + if (!dev) { | |
689 | + printk(KERN_ERR | |
690 | + "vlynq: failed to allocate device structure\n"); | |
691 | + return -ENOMEM; | |
692 | + } | |
693 | + | |
694 | + dev->id = pdev->id; | |
695 | + dev->dev.bus = &vlynq_bus_type; | |
696 | + dev->dev.parent = &pdev->dev; | |
697 | + dev_set_name(&dev->dev, "vlynq%d", dev->id); | |
698 | + dev->dev.platform_data = pdev->dev.platform_data; | |
699 | + dev->dev.release = vlynq_device_release; | |
700 | + | |
701 | + dev->regs_start = regs_res->start; | |
702 | + dev->regs_end = regs_res->end; | |
703 | + dev->mem_start = mem_res->start; | |
704 | + dev->mem_end = mem_res->end; | |
705 | + | |
706 | + len = regs_res->end - regs_res->start; | |
707 | + if (!request_mem_region(regs_res->start, len, dev_name(&dev->dev))) { | |
708 | + printk(KERN_ERR "%s: Can't request vlynq registers\n", | |
709 | + dev_name(&dev->dev)); | |
710 | + result = -ENXIO; | |
711 | + goto fail_request; | |
712 | + } | |
713 | + | |
714 | + dev->local = ioremap(regs_res->start, len); | |
715 | + if (!dev->local) { | |
716 | + printk(KERN_ERR "%s: Can't remap vlynq registers\n", | |
717 | + dev_name(&dev->dev)); | |
718 | + result = -ENXIO; | |
719 | + goto fail_remap; | |
720 | + } | |
721 | + | |
722 | + dev->remote = (struct vlynq_regs *)((void *)dev->local + | |
723 | + VLYNQ_REMOTE_OFFSET); | |
724 | + | |
725 | + dev->irq = platform_get_irq_byname(pdev, "irq"); | |
726 | + dev->irq_start = irq_res->start; | |
727 | + dev->irq_end = irq_res->end; | |
728 | + dev->local_irq = dev->irq_end - dev->irq_start; | |
729 | + dev->remote_irq = dev->local_irq - 1; | |
730 | + | |
731 | + if (device_register(&dev->dev)) | |
732 | + goto fail_register; | |
733 | + platform_set_drvdata(pdev, dev); | |
734 | + | |
735 | + printk(KERN_INFO "%s: regs 0x%p, irq %d, mem 0x%p\n", | |
736 | + dev_name(&dev->dev), (void *)dev->regs_start, dev->irq, | |
737 | + (void *)dev->mem_start); | |
738 | + | |
739 | + dev->dev_id = 0; | |
740 | + dev->divisor = vlynq_div_auto; | |
741 | + result = __vlynq_enable_device(dev); | |
742 | + if (result == 0) { | |
743 | + dev->dev_id = readl(&dev->remote->chip); | |
744 | + ((struct plat_vlynq_ops *)(dev->dev.platform_data))->off(dev); | |
745 | + } | |
746 | + if (dev->dev_id) | |
747 | + printk(KERN_INFO "Found a VLYNQ device: %08x\n", dev->dev_id); | |
748 | + | |
749 | + return 0; | |
750 | + | |
751 | +fail_register: | |
752 | + iounmap(dev->local); | |
753 | +fail_remap: | |
754 | +fail_request: | |
755 | + release_mem_region(regs_res->start, len); | |
756 | + kfree(dev); | |
757 | + return result; | |
758 | +} | |
759 | + | |
760 | +static int vlynq_remove(struct platform_device *pdev) | |
761 | +{ | |
762 | + struct vlynq_device *dev = platform_get_drvdata(pdev); | |
763 | + | |
764 | + device_unregister(&dev->dev); | |
765 | + iounmap(dev->local); | |
766 | + release_mem_region(dev->regs_start, dev->regs_end - dev->regs_start); | |
767 | + | |
768 | + kfree(dev); | |
769 | + | |
770 | + return 0; | |
771 | +} | |
772 | + | |
773 | +static struct platform_driver vlynq_platform_driver = { | |
774 | + .driver.name = "vlynq", | |
775 | + .probe = vlynq_probe, | |
776 | + .remove = __devexit_p(vlynq_remove), | |
777 | +}; | |
778 | + | |
779 | +struct bus_type vlynq_bus_type = { | |
780 | + .name = "vlynq", | |
781 | + .match = vlynq_device_match, | |
782 | + .probe = vlynq_device_probe, | |
783 | + .remove = vlynq_device_remove, | |
784 | +}; | |
785 | +EXPORT_SYMBOL(vlynq_bus_type); | |
786 | + | |
787 | +static int __devinit vlynq_init(void) | |
788 | +{ | |
789 | + int res = 0; | |
790 | + | |
791 | + res = bus_register(&vlynq_bus_type); | |
792 | + if (res) | |
793 | + goto fail_bus; | |
794 | + | |
795 | + res = platform_driver_register(&vlynq_platform_driver); | |
796 | + if (res) | |
797 | + goto fail_platform; | |
798 | + | |
799 | + return 0; | |
800 | + | |
801 | +fail_platform: | |
802 | + bus_unregister(&vlynq_bus_type); | |
803 | +fail_bus: | |
804 | + return res; | |
805 | +} | |
806 | + | |
807 | +static void __devexit vlynq_exit(void) | |
808 | +{ | |
809 | + platform_driver_unregister(&vlynq_platform_driver); | |
810 | + bus_unregister(&vlynq_bus_type); | |
811 | +} | |
812 | + | |
813 | +module_init(vlynq_init); | |
814 | +module_exit(vlynq_exit); |
include/linux/vlynq.h
1 | +/* | |
2 | + * Copyright (C) 2006, 2007 Eugene Konev <ejka@openwrt.org> | |
3 | + * | |
4 | + * This program is free software; you can redistribute it and/or modify | |
5 | + * it under the terms of the GNU General Public License as published by | |
6 | + * the Free Software Foundation; either version 2 of the License, or | |
7 | + * (at your option) any later version. | |
8 | + * | |
9 | + * This program is distributed in the hope that it will be useful, | |
10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | + * GNU General Public License for more details. | |
13 | + * | |
14 | + * You should have received a copy of the GNU General Public License | |
15 | + * along with this program; if not, write to the Free Software | |
16 | + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
17 | + */ | |
18 | + | |
19 | +#ifndef __VLYNQ_H__ | |
20 | +#define __VLYNQ_H__ | |
21 | + | |
22 | +#include <linux/device.h> | |
23 | +#include <linux/module.h> | |
24 | +#include <linux/types.h> | |
25 | + | |
26 | +#define VLYNQ_NUM_IRQS 32 | |
27 | + | |
28 | +struct vlynq_mapping { | |
29 | + u32 size; | |
30 | + u32 offset; | |
31 | +}; | |
32 | + | |
33 | +enum vlynq_divisor { | |
34 | + vlynq_div_auto = 0, | |
35 | + vlynq_ldiv1, | |
36 | + vlynq_ldiv2, | |
37 | + vlynq_ldiv3, | |
38 | + vlynq_ldiv4, | |
39 | + vlynq_ldiv5, | |
40 | + vlynq_ldiv6, | |
41 | + vlynq_ldiv7, | |
42 | + vlynq_ldiv8, | |
43 | + vlynq_rdiv1, | |
44 | + vlynq_rdiv2, | |
45 | + vlynq_rdiv3, | |
46 | + vlynq_rdiv4, | |
47 | + vlynq_rdiv5, | |
48 | + vlynq_rdiv6, | |
49 | + vlynq_rdiv7, | |
50 | + vlynq_rdiv8, | |
51 | + vlynq_div_external | |
52 | +}; | |
53 | + | |
54 | +struct vlynq_device_id { | |
55 | + u32 id; | |
56 | + enum vlynq_divisor divisor; | |
57 | + unsigned long driver_data; | |
58 | +}; | |
59 | + | |
60 | +struct vlynq_regs; | |
61 | +struct vlynq_device { | |
62 | + u32 id, dev_id; | |
63 | + int local_irq; | |
64 | + int remote_irq; | |
65 | + enum vlynq_divisor divisor; | |
66 | + u32 regs_start, regs_end; | |
67 | + u32 mem_start, mem_end; | |
68 | + u32 irq_start, irq_end; | |
69 | + int irq; | |
70 | + int enabled; | |
71 | + struct vlynq_regs *local; | |
72 | + struct vlynq_regs *remote; | |
73 | + struct device dev; | |
74 | +}; | |
75 | + | |
76 | +struct vlynq_driver { | |
77 | + char *name; | |
78 | + struct vlynq_device_id *id_table; | |
79 | + int (*probe)(struct vlynq_device *dev, struct vlynq_device_id *id); | |
80 | + void (*remove)(struct vlynq_device *dev); | |
81 | + struct device_driver driver; | |
82 | +}; | |
83 | + | |
84 | +struct plat_vlynq_ops { | |
85 | + int (*on)(struct vlynq_device *dev); | |
86 | + void (*off)(struct vlynq_device *dev); | |
87 | +}; | |
88 | + | |
89 | +static inline struct vlynq_driver *to_vlynq_driver(struct device_driver *drv) | |
90 | +{ | |
91 | + return container_of(drv, struct vlynq_driver, driver); | |
92 | +} | |
93 | + | |
94 | +static inline struct vlynq_device *to_vlynq_device(struct device *device) | |
95 | +{ | |
96 | + return container_of(device, struct vlynq_device, dev); | |
97 | +} | |
98 | + | |
99 | +extern struct bus_type vlynq_bus_type; | |
100 | + | |
101 | +extern int __vlynq_register_driver(struct vlynq_driver *driver, | |
102 | + struct module *owner); | |
103 | + | |
104 | +static inline int vlynq_register_driver(struct vlynq_driver *driver) | |
105 | +{ | |
106 | + return __vlynq_register_driver(driver, THIS_MODULE); | |
107 | +} | |
108 | + | |
109 | +static inline void *vlynq_get_drvdata(struct vlynq_device *dev) | |
110 | +{ | |
111 | + return dev_get_drvdata(&dev->dev); | |
112 | +} | |
113 | + | |
114 | +static inline void vlynq_set_drvdata(struct vlynq_device *dev, void *data) | |
115 | +{ | |
116 | + dev_set_drvdata(&dev->dev, data); | |
117 | +} | |
118 | + | |
119 | +static inline u32 vlynq_mem_start(struct vlynq_device *dev) | |
120 | +{ | |
121 | + return dev->mem_start; | |
122 | +} | |
123 | + | |
124 | +static inline u32 vlynq_mem_end(struct vlynq_device *dev) | |
125 | +{ | |
126 | + return dev->mem_end; | |
127 | +} | |
128 | + | |
129 | +static inline u32 vlynq_mem_len(struct vlynq_device *dev) | |
130 | +{ | |
131 | + return dev->mem_end - dev->mem_start + 1; | |
132 | +} | |
133 | + | |
134 | +static inline int vlynq_virq_to_irq(struct vlynq_device *dev, int virq) | |
135 | +{ | |
136 | + int irq = dev->irq_start + virq; | |
137 | + if ((irq < dev->irq_start) || (irq > dev->irq_end)) | |
138 | + return -EINVAL; | |
139 | + | |
140 | + return irq; | |
141 | +} | |
142 | + | |
143 | +static inline int vlynq_irq_to_virq(struct vlynq_device *dev, int irq) | |
144 | +{ | |
145 | + if ((irq < dev->irq_start) || (irq > dev->irq_end)) | |
146 | + return -EINVAL; | |
147 | + | |
148 | + return irq - dev->irq_start; | |
149 | +} | |
150 | + | |
151 | +extern void vlynq_unregister_driver(struct vlynq_driver *driver); | |
152 | +extern int vlynq_enable_device(struct vlynq_device *dev); | |
153 | +extern void vlynq_disable_device(struct vlynq_device *dev); | |
154 | +extern int vlynq_set_local_mapping(struct vlynq_device *dev, u32 tx_offset, | |
155 | + struct vlynq_mapping *mapping); | |
156 | +extern int vlynq_set_remote_mapping(struct vlynq_device *dev, u32 tx_offset, | |
157 | + struct vlynq_mapping *mapping); | |
158 | +extern int vlynq_set_local_irq(struct vlynq_device *dev, int virq); | |
159 | +extern int vlynq_set_remote_irq(struct vlynq_device *dev, int virq); | |
160 | + | |
161 | +#endif /* __VLYNQ_H__ */ |