diff options
author | Ben Dooks <ben-linux@fluff.org> | 2006-06-25 08:48:03 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-06-25 13:01:09 -0400 |
commit | ad4063b0b2ffd7c8359b62c830e88152fc39ab20 (patch) | |
tree | 8bfc0132bd7bf851dbc495b4f9a7460904d332b8 /drivers | |
parent | 11e64757f9fb32f13f51596bbf01988f42fca764 (diff) |
[PATCH] AX88796 parallel port driver
Driver for the simple parallel port interface on the Asix AX88796 chip on
an platform_bus.
[akpm@osdl.org: x86_64 build fix]
Signed-off-by: Ben Dooks <ben-linux@fluff.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/parport/Kconfig | 12 | ||||
-rw-r--r-- | drivers/parport/Makefile | 3 | ||||
-rw-r--r-- | drivers/parport/parport_ax88796.c | 443 |
3 files changed, 457 insertions, 1 deletions
diff --git a/drivers/parport/Kconfig b/drivers/parport/Kconfig index 4d8dc27ea9d1..c7fa28a28b9f 100644 --- a/drivers/parport/Kconfig +++ b/drivers/parport/Kconfig | |||
@@ -136,6 +136,18 @@ config PARPORT_SUNBPP | |||
136 | found on many Sun machines. Note that many of the newer Ultras | 136 | found on many Sun machines. Note that many of the newer Ultras |
137 | actually have pc style hardware instead. | 137 | actually have pc style hardware instead. |
138 | 138 | ||
139 | config PARPORT_AX88796 | ||
140 | tristate "AX88796 Parallel Port" | ||
141 | depends on PARPORT | ||
142 | select PARPORT_NOT_PC | ||
143 | help | ||
144 | Say Y here if you need support for the parallel port hardware on | ||
145 | the AX88796 network controller chip. This code is also available | ||
146 | as a module (say M), called parport_ax88796. | ||
147 | |||
148 | The driver is not dependant on the AX88796 network driver, and | ||
149 | should not interfere with the networking functions of the chip. | ||
150 | |||
139 | config PARPORT_1284 | 151 | config PARPORT_1284 |
140 | bool "IEEE 1284 transfer modes" | 152 | bool "IEEE 1284 transfer modes" |
141 | depends on PARPORT | 153 | depends on PARPORT |
diff --git a/drivers/parport/Makefile b/drivers/parport/Makefile index a19de35f8de2..696b8d4ca887 100644 --- a/drivers/parport/Makefile +++ b/drivers/parport/Makefile | |||
@@ -17,4 +17,5 @@ obj-$(CONFIG_PARPORT_MFC3) += parport_mfc3.o | |||
17 | obj-$(CONFIG_PARPORT_ATARI) += parport_atari.o | 17 | obj-$(CONFIG_PARPORT_ATARI) += parport_atari.o |
18 | obj-$(CONFIG_PARPORT_SUNBPP) += parport_sunbpp.o | 18 | obj-$(CONFIG_PARPORT_SUNBPP) += parport_sunbpp.o |
19 | obj-$(CONFIG_PARPORT_GSC) += parport_gsc.o | 19 | obj-$(CONFIG_PARPORT_GSC) += parport_gsc.o |
20 | obj-$(CONFIG_PARPORT_IP32) += parport_ip32.o | 20 | obj-$(CONFIG_PARPORT_AX88796) += parport_ax88796.o |
21 | obj-$(CONFIG_PARPORT_IP32) += parport_ip32.o \ No newline at end of file | ||
diff --git a/drivers/parport/parport_ax88796.c b/drivers/parport/parport_ax88796.c new file mode 100644 index 000000000000..4baa719439a2 --- /dev/null +++ b/drivers/parport/parport_ax88796.c | |||
@@ -0,0 +1,443 @@ | |||
1 | /* linux/drivers/parport/parport_ax88796.c | ||
2 | * | ||
3 | * (c) 2005,2006 Simtec Electronics | ||
4 | * Ben Dooks <ben@simtec.co.uk> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/parport.h> | ||
15 | #include <linux/interrupt.h> | ||
16 | #include <linux/errno.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | |||
19 | #include <asm/io.h> | ||
20 | #include <asm/irq.h> | ||
21 | |||
22 | #define AX_SPR_BUSY (1<<7) | ||
23 | #define AX_SPR_ACK (1<<6) | ||
24 | #define AX_SPR_PE (1<<5) | ||
25 | #define AX_SPR_SLCT (1<<4) | ||
26 | #define AX_SPR_ERR (1<<3) | ||
27 | |||
28 | #define AX_CPR_nDOE (1<<5) | ||
29 | #define AX_CPR_SLCTIN (1<<3) | ||
30 | #define AX_CPR_nINIT (1<<2) | ||
31 | #define AX_CPR_ATFD (1<<1) | ||
32 | #define AX_CPR_STRB (1<<0) | ||
33 | |||
34 | struct ax_drvdata { | ||
35 | struct parport *parport; | ||
36 | struct parport_state suspend; | ||
37 | |||
38 | struct device *dev; | ||
39 | struct resource *io; | ||
40 | |||
41 | unsigned char irq_enabled; | ||
42 | |||
43 | void __iomem *base; | ||
44 | void __iomem *spp_data; | ||
45 | void __iomem *spp_spr; | ||
46 | void __iomem *spp_cpr; | ||
47 | }; | ||
48 | |||
49 | static inline struct ax_drvdata *pp_to_drv(struct parport *p) | ||
50 | { | ||
51 | return p->private_data; | ||
52 | } | ||
53 | |||
54 | static unsigned char | ||
55 | parport_ax88796_read_data(struct parport *p) | ||
56 | { | ||
57 | struct ax_drvdata *dd = pp_to_drv(p); | ||
58 | |||
59 | return readb(dd->spp_data); | ||
60 | } | ||
61 | |||
62 | static void | ||
63 | parport_ax88796_write_data(struct parport *p, unsigned char data) | ||
64 | { | ||
65 | struct ax_drvdata *dd = pp_to_drv(p); | ||
66 | |||
67 | writeb(data, dd->spp_data); | ||
68 | } | ||
69 | |||
70 | static unsigned char | ||
71 | parport_ax88796_read_control(struct parport *p) | ||
72 | { | ||
73 | struct ax_drvdata *dd = pp_to_drv(p); | ||
74 | unsigned int cpr = readb(dd->spp_cpr); | ||
75 | unsigned int ret = 0; | ||
76 | |||
77 | if (!(cpr & AX_CPR_STRB)) | ||
78 | ret |= PARPORT_CONTROL_STROBE; | ||
79 | |||
80 | if (!(cpr & AX_CPR_ATFD)) | ||
81 | ret |= PARPORT_CONTROL_AUTOFD; | ||
82 | |||
83 | if (cpr & AX_CPR_nINIT) | ||
84 | ret |= PARPORT_CONTROL_INIT; | ||
85 | |||
86 | if (!(cpr & AX_CPR_SLCTIN)) | ||
87 | ret |= PARPORT_CONTROL_SELECT; | ||
88 | |||
89 | return ret; | ||
90 | } | ||
91 | |||
92 | static void | ||
93 | parport_ax88796_write_control(struct parport *p, unsigned char control) | ||
94 | { | ||
95 | struct ax_drvdata *dd = pp_to_drv(p); | ||
96 | unsigned int cpr = readb(dd->spp_cpr); | ||
97 | |||
98 | cpr &= AX_CPR_nDOE; | ||
99 | |||
100 | if (!(control & PARPORT_CONTROL_STROBE)) | ||
101 | cpr |= AX_CPR_STRB; | ||
102 | |||
103 | if (!(control & PARPORT_CONTROL_AUTOFD)) | ||
104 | cpr |= AX_CPR_ATFD; | ||
105 | |||
106 | if (control & PARPORT_CONTROL_INIT) | ||
107 | cpr |= AX_CPR_nINIT; | ||
108 | |||
109 | if (!(control & PARPORT_CONTROL_SELECT)) | ||
110 | cpr |= AX_CPR_SLCTIN; | ||
111 | |||
112 | dev_dbg(dd->dev, "write_control: ctrl=%02x, cpr=%02x\n", control, cpr); | ||
113 | writeb(cpr, dd->spp_cpr); | ||
114 | |||
115 | if (parport_ax88796_read_control(p) != control) { | ||
116 | dev_err(dd->dev, "write_control: read != set (%02x, %02x)\n", | ||
117 | parport_ax88796_read_control(p), control); | ||
118 | } | ||
119 | } | ||
120 | |||
121 | static unsigned char | ||
122 | parport_ax88796_read_status(struct parport *p) | ||
123 | { | ||
124 | struct ax_drvdata *dd = pp_to_drv(p); | ||
125 | unsigned int status = readb(dd->spp_spr); | ||
126 | unsigned int ret = 0; | ||
127 | |||
128 | if (status & AX_SPR_BUSY) | ||
129 | ret |= PARPORT_STATUS_BUSY; | ||
130 | |||
131 | if (status & AX_SPR_ACK) | ||
132 | ret |= PARPORT_STATUS_ACK; | ||
133 | |||
134 | if (status & AX_SPR_ERR) | ||
135 | ret |= PARPORT_STATUS_ERROR; | ||
136 | |||
137 | if (status & AX_SPR_SLCT) | ||
138 | ret |= PARPORT_STATUS_SELECT; | ||
139 | |||
140 | if (status & AX_SPR_PE) | ||
141 | ret |= PARPORT_STATUS_PAPEROUT; | ||
142 | |||
143 | return ret; | ||
144 | } | ||
145 | |||
146 | static unsigned char | ||
147 | parport_ax88796_frob_control(struct parport *p, unsigned char mask, | ||
148 | unsigned char val) | ||
149 | { | ||
150 | struct ax_drvdata *dd = pp_to_drv(p); | ||
151 | unsigned char old = parport_ax88796_read_control(p); | ||
152 | |||
153 | dev_dbg(dd->dev, "frob: mask=%02x, val=%02x, old=%02x\n", | ||
154 | mask, val, old); | ||
155 | |||
156 | parport_ax88796_write_control(p, (old & ~mask) | val); | ||
157 | return old; | ||
158 | } | ||
159 | |||
160 | static void | ||
161 | parport_ax88796_enable_irq(struct parport *p) | ||
162 | { | ||
163 | struct ax_drvdata *dd = pp_to_drv(p); | ||
164 | unsigned long flags; | ||
165 | |||
166 | local_irq_save(flags); | ||
167 | if (!dd->irq_enabled) { | ||
168 | enable_irq(p->irq); | ||
169 | dd->irq_enabled = 1; | ||
170 | } | ||
171 | local_irq_restore(flags); | ||
172 | } | ||
173 | |||
174 | static void | ||
175 | parport_ax88796_disable_irq(struct parport *p) | ||
176 | { | ||
177 | struct ax_drvdata *dd = pp_to_drv(p); | ||
178 | unsigned long flags; | ||
179 | |||
180 | local_irq_save(flags); | ||
181 | if (dd->irq_enabled) { | ||
182 | disable_irq(p->irq); | ||
183 | dd->irq_enabled = 0; | ||
184 | } | ||
185 | local_irq_restore(flags); | ||
186 | } | ||
187 | |||
188 | static void | ||
189 | parport_ax88796_data_forward(struct parport *p) | ||
190 | { | ||
191 | struct ax_drvdata *dd = pp_to_drv(p); | ||
192 | void __iomem *cpr = dd->spp_cpr; | ||
193 | |||
194 | writeb((readb(cpr) & ~AX_CPR_nDOE), cpr); | ||
195 | } | ||
196 | |||
197 | static void | ||
198 | parport_ax88796_data_reverse(struct parport *p) | ||
199 | { | ||
200 | struct ax_drvdata *dd = pp_to_drv(p); | ||
201 | void __iomem *cpr = dd->spp_cpr; | ||
202 | |||
203 | writeb(readb(cpr) | AX_CPR_nDOE, cpr); | ||
204 | } | ||
205 | |||
206 | static void | ||
207 | parport_ax88796_init_state(struct pardevice *d, struct parport_state *s) | ||
208 | { | ||
209 | struct ax_drvdata *dd = pp_to_drv(d->port); | ||
210 | |||
211 | memset(s, 0, sizeof(struct parport_state)); | ||
212 | |||
213 | dev_dbg(dd->dev, "init_state: %p: state=%p\n", d, s); | ||
214 | s->u.ax88796.cpr = readb(dd->spp_cpr); | ||
215 | } | ||
216 | |||
217 | static void | ||
218 | parport_ax88796_save_state(struct parport *p, struct parport_state *s) | ||
219 | { | ||
220 | struct ax_drvdata *dd = pp_to_drv(p); | ||
221 | |||
222 | dev_dbg(dd->dev, "save_state: %p: state=%p\n", p, s); | ||
223 | s->u.ax88796.cpr = readb(dd->spp_cpr); | ||
224 | } | ||
225 | |||
226 | static void | ||
227 | parport_ax88796_restore_state(struct parport *p, struct parport_state *s) | ||
228 | { | ||
229 | struct ax_drvdata *dd = pp_to_drv(p); | ||
230 | |||
231 | dev_dbg(dd->dev, "restore_state: %p: state=%p\n", p, s); | ||
232 | writeb(s->u.ax88796.cpr, dd->spp_cpr); | ||
233 | } | ||
234 | |||
235 | static irqreturn_t | ||
236 | parport_ax88796_interrupt(int irq, void *dev_id, struct pt_regs *regs) | ||
237 | { | ||
238 | parport_generic_irq(irq, dev_id, regs); | ||
239 | return IRQ_HANDLED; | ||
240 | } | ||
241 | |||
242 | |||
243 | static struct parport_operations parport_ax88796_ops = { | ||
244 | .write_data = parport_ax88796_write_data, | ||
245 | .read_data = parport_ax88796_read_data, | ||
246 | |||
247 | .write_control = parport_ax88796_write_control, | ||
248 | .read_control = parport_ax88796_read_control, | ||
249 | .frob_control = parport_ax88796_frob_control, | ||
250 | |||
251 | .read_status = parport_ax88796_read_status, | ||
252 | |||
253 | .enable_irq = parport_ax88796_enable_irq, | ||
254 | .disable_irq = parport_ax88796_disable_irq, | ||
255 | |||
256 | .data_forward = parport_ax88796_data_forward, | ||
257 | .data_reverse = parport_ax88796_data_reverse, | ||
258 | |||
259 | .init_state = parport_ax88796_init_state, | ||
260 | .save_state = parport_ax88796_save_state, | ||
261 | .restore_state = parport_ax88796_restore_state, | ||
262 | |||
263 | .epp_write_data = parport_ieee1284_epp_write_data, | ||
264 | .epp_read_data = parport_ieee1284_epp_read_data, | ||
265 | .epp_write_addr = parport_ieee1284_epp_write_addr, | ||
266 | .epp_read_addr = parport_ieee1284_epp_read_addr, | ||
267 | |||
268 | .ecp_write_data = parport_ieee1284_ecp_write_data, | ||
269 | .ecp_read_data = parport_ieee1284_ecp_read_data, | ||
270 | .ecp_write_addr = parport_ieee1284_ecp_write_addr, | ||
271 | |||
272 | .compat_write_data = parport_ieee1284_write_compat, | ||
273 | .nibble_read_data = parport_ieee1284_read_nibble, | ||
274 | .byte_read_data = parport_ieee1284_read_byte, | ||
275 | |||
276 | .owner = THIS_MODULE, | ||
277 | }; | ||
278 | |||
279 | static int parport_ax88796_probe(struct platform_device *pdev) | ||
280 | { | ||
281 | struct device *_dev = &pdev->dev; | ||
282 | struct ax_drvdata *dd; | ||
283 | struct parport *pp = NULL; | ||
284 | struct resource *res; | ||
285 | unsigned long size; | ||
286 | int spacing; | ||
287 | int irq; | ||
288 | int ret; | ||
289 | |||
290 | dd = kzalloc(sizeof(struct ax_drvdata), GFP_KERNEL); | ||
291 | if (dd == NULL) { | ||
292 | dev_err(_dev, "no memory for private data\n"); | ||
293 | return -ENOMEM; | ||
294 | } | ||
295 | |||
296 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
297 | if (res == NULL) { | ||
298 | dev_err(_dev, "no MEM specified\n"); | ||
299 | ret = -ENXIO; | ||
300 | goto exit_mem; | ||
301 | } | ||
302 | |||
303 | size = (res->end - res->start) + 1; | ||
304 | spacing = size / 3; | ||
305 | |||
306 | dd->io = request_mem_region(res->start, size, pdev->name); | ||
307 | if (dd->io == NULL) { | ||
308 | dev_err(_dev, "cannot reserve memory\n"); | ||
309 | ret = -ENXIO; | ||
310 | goto exit_mem; | ||
311 | } | ||
312 | |||
313 | dd->base = ioremap(res->start, size); | ||
314 | if (dd->base == NULL) { | ||
315 | dev_err(_dev, "cannot ioremap region\n"); | ||
316 | ret = -ENXIO; | ||
317 | goto exit_res; | ||
318 | } | ||
319 | |||
320 | irq = platform_get_irq(pdev, 0); | ||
321 | if (irq <= 0) | ||
322 | irq = PARPORT_IRQ_NONE; | ||
323 | |||
324 | pp = parport_register_port((unsigned long)dd->base, irq, | ||
325 | PARPORT_DMA_NONE, | ||
326 | &parport_ax88796_ops); | ||
327 | |||
328 | if (pp == NULL) { | ||
329 | dev_err(_dev, "failed to register parallel port\n"); | ||
330 | ret = -ENOMEM; | ||
331 | goto exit_unmap; | ||
332 | } | ||
333 | |||
334 | pp->private_data = dd; | ||
335 | dd->parport = pp; | ||
336 | dd->dev = _dev; | ||
337 | |||
338 | dd->spp_data = dd->base; | ||
339 | dd->spp_spr = dd->base + (spacing * 1); | ||
340 | dd->spp_cpr = dd->base + (spacing * 2); | ||
341 | |||
342 | /* initialise the port controls */ | ||
343 | writeb(AX_CPR_STRB, dd->spp_cpr); | ||
344 | |||
345 | if (irq >= 0) { | ||
346 | /* request irq */ | ||
347 | ret = request_irq(irq, parport_ax88796_interrupt, | ||
348 | SA_TRIGGER_FALLING, pdev->name, pp); | ||
349 | |||
350 | if (ret < 0) | ||
351 | goto exit_port; | ||
352 | |||
353 | dd->irq_enabled = 1; | ||
354 | } | ||
355 | |||
356 | platform_set_drvdata(pdev, pp); | ||
357 | |||
358 | dev_info(_dev, "attached parallel port driver\n"); | ||
359 | parport_announce_port(pp); | ||
360 | |||
361 | return 0; | ||
362 | |||
363 | exit_port: | ||
364 | parport_remove_port(pp); | ||
365 | exit_unmap: | ||
366 | iounmap(dd->base); | ||
367 | exit_res: | ||
368 | release_resource(dd->io); | ||
369 | kfree(dd->io); | ||
370 | exit_mem: | ||
371 | kfree(dd); | ||
372 | return ret; | ||
373 | } | ||
374 | |||
375 | static int parport_ax88796_remove(struct platform_device *pdev) | ||
376 | { | ||
377 | struct parport *p = platform_get_drvdata(pdev); | ||
378 | struct ax_drvdata *dd = pp_to_drv(p); | ||
379 | |||
380 | free_irq(p->irq, p); | ||
381 | parport_remove_port(p); | ||
382 | iounmap(dd->base); | ||
383 | release_resource(dd->io); | ||
384 | kfree(dd->io); | ||
385 | kfree(dd); | ||
386 | |||
387 | return 0; | ||
388 | } | ||
389 | |||
390 | #ifdef CONFIG_PM | ||
391 | |||
392 | static int parport_ax88796_suspend(struct platform_device *dev, | ||
393 | pm_message_t state) | ||
394 | { | ||
395 | struct parport *p = platform_get_drvdata(dev); | ||
396 | struct ax_drvdata *dd = pp_to_drv(p); | ||
397 | |||
398 | parport_ax88796_save_state(p, &dd->suspend); | ||
399 | writeb(AX_CPR_nDOE | AX_CPR_STRB, dd->spp_cpr); | ||
400 | return 0; | ||
401 | } | ||
402 | |||
403 | static int parport_ax88796_resume(struct platform_device *dev) | ||
404 | { | ||
405 | struct parport *p = platform_get_drvdata(dev); | ||
406 | struct ax_drvdata *dd = pp_to_drv(p); | ||
407 | |||
408 | parport_ax88796_restore_state(p, &dd->suspend); | ||
409 | return 0; | ||
410 | } | ||
411 | |||
412 | #else | ||
413 | #define parport_ax88796_suspend NULL | ||
414 | #define parport_ax88796_resume NULL | ||
415 | #endif | ||
416 | |||
417 | static struct platform_driver axdrv = { | ||
418 | .driver = { | ||
419 | .name = "ax88796-pp", | ||
420 | .owner = THIS_MODULE, | ||
421 | }, | ||
422 | .probe = parport_ax88796_probe, | ||
423 | .remove = parport_ax88796_remove, | ||
424 | .suspend = parport_ax88796_suspend, | ||
425 | .resume = parport_ax88796_resume, | ||
426 | }; | ||
427 | |||
428 | static int __init parport_ax88796_init(void) | ||
429 | { | ||
430 | return platform_driver_register(&axdrv); | ||
431 | } | ||
432 | |||
433 | static void __exit parport_ax88796_exit(void) | ||
434 | { | ||
435 | platform_driver_unregister(&axdrv); | ||
436 | } | ||
437 | |||
438 | module_init(parport_ax88796_init) | ||
439 | module_exit(parport_ax88796_exit) | ||
440 | |||
441 | MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); | ||
442 | MODULE_DESCRIPTION("AX88796 Parport parallel port driver"); | ||
443 | MODULE_LICENSE("GPL"); | ||