diff options
author | Jiri Slaby <jirislaby@gmail.com> | 2007-05-08 03:31:45 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-05-08 14:15:14 -0400 |
commit | cef2cf07273d12ac3453d2baff096423f17b7403 (patch) | |
tree | 807ce95b91d1a09f449d61301f1148c85d7818db /drivers/misc/phantom.c | |
parent | 6f7f02e78a75a09195d963e0392b195bc2d55c5c (diff) |
Misc: add sensable phantom driver
Add sensable phantom driver
Signed-off-by: Jiri Slaby <jirislaby@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/misc/phantom.c')
-rw-r--r-- | drivers/misc/phantom.c | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/drivers/misc/phantom.c b/drivers/misc/phantom.c new file mode 100644 index 000000000000..35b139b0e5f2 --- /dev/null +++ b/drivers/misc/phantom.c | |||
@@ -0,0 +1,463 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2005-2007 Jiri Slaby <jirislaby@gmail.com> | ||
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 | * You need an userspace library to cooperate with this driver. It (and other | ||
10 | * info) may be obtained here: | ||
11 | * http://www.fi.muni.cz/~xslaby/phantom.html | ||
12 | */ | ||
13 | |||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/module.h> | ||
16 | #include <linux/device.h> | ||
17 | #include <linux/pci.h> | ||
18 | #include <linux/fs.h> | ||
19 | #include <linux/poll.h> | ||
20 | #include <linux/interrupt.h> | ||
21 | #include <linux/cdev.h> | ||
22 | #include <linux/phantom.h> | ||
23 | |||
24 | #include <asm/atomic.h> | ||
25 | #include <asm/io.h> | ||
26 | |||
27 | #define PHANTOM_VERSION "n0.9.5" | ||
28 | |||
29 | #define PHANTOM_MAX_MINORS 8 | ||
30 | |||
31 | #define PHN_IRQCTL 0x4c /* irq control in caddr space */ | ||
32 | |||
33 | #define PHB_RUNNING 1 | ||
34 | |||
35 | static struct class *phantom_class; | ||
36 | static int phantom_major; | ||
37 | |||
38 | struct phantom_device { | ||
39 | unsigned int opened; | ||
40 | void __iomem *caddr; | ||
41 | u32 __iomem *iaddr; | ||
42 | u32 __iomem *oaddr; | ||
43 | unsigned long status; | ||
44 | atomic_t counter; | ||
45 | |||
46 | wait_queue_head_t wait; | ||
47 | struct cdev cdev; | ||
48 | |||
49 | struct mutex open_lock; | ||
50 | }; | ||
51 | |||
52 | static unsigned char phantom_devices[PHANTOM_MAX_MINORS]; | ||
53 | |||
54 | static int phantom_status(struct phantom_device *dev, unsigned long newstat) | ||
55 | { | ||
56 | pr_debug("phantom_status %lx %lx\n", dev->status, newstat); | ||
57 | |||
58 | if (!(dev->status & PHB_RUNNING) && (newstat & PHB_RUNNING)) { | ||
59 | atomic_set(&dev->counter, 0); | ||
60 | iowrite32(PHN_CTL_IRQ, dev->iaddr + PHN_CONTROL); | ||
61 | iowrite32(0x43, dev->caddr + PHN_IRQCTL); | ||
62 | } else if ((dev->status & PHB_RUNNING) && !(newstat & PHB_RUNNING)) | ||
63 | iowrite32(0, dev->caddr + PHN_IRQCTL); | ||
64 | |||
65 | dev->status = newstat; | ||
66 | |||
67 | return 0; | ||
68 | } | ||
69 | |||
70 | /* | ||
71 | * File ops | ||
72 | */ | ||
73 | |||
74 | static int phantom_ioctl(struct inode *inode, struct file *file, u_int cmd, | ||
75 | u_long arg) | ||
76 | { | ||
77 | struct phantom_device *dev = file->private_data; | ||
78 | struct phm_regs rs; | ||
79 | struct phm_reg r; | ||
80 | void __user *argp = (void __user *)arg; | ||
81 | unsigned int i; | ||
82 | |||
83 | if (_IOC_TYPE(cmd) != PH_IOC_MAGIC || | ||
84 | _IOC_NR(cmd) > PH_IOC_MAXNR) | ||
85 | return -ENOTTY; | ||
86 | |||
87 | switch (cmd) { | ||
88 | case PHN_SET_REG: | ||
89 | if (copy_from_user(&r, argp, sizeof(r))) | ||
90 | return -EFAULT; | ||
91 | |||
92 | if (r.reg > 7) | ||
93 | return -EINVAL; | ||
94 | |||
95 | if (r.reg == PHN_CONTROL && (r.value & PHN_CTL_IRQ) && | ||
96 | phantom_status(dev, dev->status | PHB_RUNNING)) | ||
97 | return -ENODEV; | ||
98 | |||
99 | pr_debug("phantom: writing %x to %u\n", r.value, r.reg); | ||
100 | iowrite32(r.value, dev->iaddr + r.reg); | ||
101 | |||
102 | if (r.reg == PHN_CONTROL && !(r.value & PHN_CTL_IRQ)) | ||
103 | phantom_status(dev, dev->status & ~PHB_RUNNING); | ||
104 | break; | ||
105 | case PHN_SET_REGS: | ||
106 | if (copy_from_user(&rs, argp, sizeof(rs))) | ||
107 | return -EFAULT; | ||
108 | |||
109 | pr_debug("phantom: SRS %u regs %x\n", rs.count, rs.mask); | ||
110 | for (i = 0; i < min(rs.count, 8U); i++) | ||
111 | if ((1 << i) & rs.mask) | ||
112 | iowrite32(rs.values[i], dev->oaddr + i); | ||
113 | break; | ||
114 | case PHN_GET_REG: | ||
115 | if (copy_from_user(&r, argp, sizeof(r))) | ||
116 | return -EFAULT; | ||
117 | |||
118 | if (r.reg > 7) | ||
119 | return -EINVAL; | ||
120 | |||
121 | r.value = ioread32(dev->iaddr + r.reg); | ||
122 | |||
123 | if (copy_to_user(argp, &r, sizeof(r))) | ||
124 | return -EFAULT; | ||
125 | break; | ||
126 | case PHN_GET_REGS: | ||
127 | if (copy_from_user(&rs, argp, sizeof(rs))) | ||
128 | return -EFAULT; | ||
129 | |||
130 | pr_debug("phantom: GRS %u regs %x\n", rs.count, rs.mask); | ||
131 | for (i = 0; i < min(rs.count, 8U); i++) | ||
132 | if ((1 << i) & rs.mask) | ||
133 | rs.values[i] = ioread32(dev->iaddr + i); | ||
134 | |||
135 | if (copy_to_user(argp, &rs, sizeof(rs))) | ||
136 | return -EFAULT; | ||
137 | break; | ||
138 | default: | ||
139 | return -ENOTTY; | ||
140 | } | ||
141 | |||
142 | return 0; | ||
143 | } | ||
144 | |||
145 | static int phantom_open(struct inode *inode, struct file *file) | ||
146 | { | ||
147 | struct phantom_device *dev = container_of(inode->i_cdev, | ||
148 | struct phantom_device, cdev); | ||
149 | |||
150 | nonseekable_open(inode, file); | ||
151 | |||
152 | if (mutex_lock_interruptible(&dev->open_lock)) | ||
153 | return -ERESTARTSYS; | ||
154 | |||
155 | if (dev->opened) { | ||
156 | mutex_unlock(&dev->open_lock); | ||
157 | return -EINVAL; | ||
158 | } | ||
159 | |||
160 | file->private_data = dev; | ||
161 | |||
162 | dev->opened++; | ||
163 | mutex_unlock(&dev->open_lock); | ||
164 | |||
165 | return 0; | ||
166 | } | ||
167 | |||
168 | static int phantom_release(struct inode *inode, struct file *file) | ||
169 | { | ||
170 | struct phantom_device *dev = file->private_data; | ||
171 | |||
172 | mutex_lock(&dev->open_lock); | ||
173 | |||
174 | dev->opened = 0; | ||
175 | phantom_status(dev, dev->status & ~PHB_RUNNING); | ||
176 | |||
177 | mutex_unlock(&dev->open_lock); | ||
178 | |||
179 | return 0; | ||
180 | } | ||
181 | |||
182 | static unsigned int phantom_poll(struct file *file, poll_table *wait) | ||
183 | { | ||
184 | struct phantom_device *dev = file->private_data; | ||
185 | unsigned int mask = 0; | ||
186 | |||
187 | pr_debug("phantom_poll: %d\n", atomic_read(&dev->counter)); | ||
188 | poll_wait(file, &dev->wait, wait); | ||
189 | if (atomic_read(&dev->counter)) { | ||
190 | mask = POLLIN | POLLRDNORM; | ||
191 | atomic_dec(&dev->counter); | ||
192 | } else if ((dev->status & PHB_RUNNING) == 0) | ||
193 | mask = POLLIN | POLLRDNORM | POLLERR; | ||
194 | pr_debug("phantom_poll end: %x/%d\n", mask, atomic_read(&dev->counter)); | ||
195 | |||
196 | return mask; | ||
197 | } | ||
198 | |||
199 | static struct file_operations phantom_file_ops = { | ||
200 | .open = phantom_open, | ||
201 | .release = phantom_release, | ||
202 | .ioctl = phantom_ioctl, | ||
203 | .poll = phantom_poll, | ||
204 | }; | ||
205 | |||
206 | static irqreturn_t phantom_isr(int irq, void *data) | ||
207 | { | ||
208 | struct phantom_device *dev = data; | ||
209 | |||
210 | if (!(ioread32(dev->iaddr + PHN_CONTROL) & PHN_CTL_IRQ)) | ||
211 | return IRQ_NONE; | ||
212 | |||
213 | iowrite32(0, dev->iaddr); | ||
214 | iowrite32(0xc0, dev->iaddr); | ||
215 | |||
216 | atomic_inc(&dev->counter); | ||
217 | wake_up_interruptible(&dev->wait); | ||
218 | |||
219 | return IRQ_HANDLED; | ||
220 | } | ||
221 | |||
222 | /* | ||
223 | * Init and deinit driver | ||
224 | */ | ||
225 | |||
226 | static unsigned int __devinit phantom_get_free(void) | ||
227 | { | ||
228 | unsigned int i; | ||
229 | |||
230 | for (i = 0; i < PHANTOM_MAX_MINORS; i++) | ||
231 | if (phantom_devices[i] == 0) | ||
232 | break; | ||
233 | |||
234 | return i; | ||
235 | } | ||
236 | |||
237 | static int __devinit phantom_probe(struct pci_dev *pdev, | ||
238 | const struct pci_device_id *pci_id) | ||
239 | { | ||
240 | struct phantom_device *pht; | ||
241 | unsigned int minor; | ||
242 | int retval; | ||
243 | |||
244 | retval = pci_enable_device(pdev); | ||
245 | if (retval) | ||
246 | goto err; | ||
247 | |||
248 | minor = phantom_get_free(); | ||
249 | if (minor == PHANTOM_MAX_MINORS) { | ||
250 | dev_err(&pdev->dev, "too many devices found!\n"); | ||
251 | retval = -EIO; | ||
252 | goto err_dis; | ||
253 | } | ||
254 | |||
255 | phantom_devices[minor] = 1; | ||
256 | |||
257 | retval = pci_request_regions(pdev, "phantom"); | ||
258 | if (retval) | ||
259 | goto err_null; | ||
260 | |||
261 | retval = -ENOMEM; | ||
262 | pht = kzalloc(sizeof(*pht), GFP_KERNEL); | ||
263 | if (pht == NULL) { | ||
264 | dev_err(&pdev->dev, "unable to allocate device\n"); | ||
265 | goto err_reg; | ||
266 | } | ||
267 | |||
268 | pht->caddr = pci_iomap(pdev, 0, 0); | ||
269 | if (pht->caddr == NULL) { | ||
270 | dev_err(&pdev->dev, "can't remap conf space\n"); | ||
271 | goto err_fr; | ||
272 | } | ||
273 | pht->iaddr = pci_iomap(pdev, 2, 0); | ||
274 | if (pht->iaddr == NULL) { | ||
275 | dev_err(&pdev->dev, "can't remap input space\n"); | ||
276 | goto err_unmc; | ||
277 | } | ||
278 | pht->oaddr = pci_iomap(pdev, 3, 0); | ||
279 | if (pht->oaddr == NULL) { | ||
280 | dev_err(&pdev->dev, "can't remap output space\n"); | ||
281 | goto err_unmi; | ||
282 | } | ||
283 | |||
284 | mutex_init(&pht->open_lock); | ||
285 | init_waitqueue_head(&pht->wait); | ||
286 | cdev_init(&pht->cdev, &phantom_file_ops); | ||
287 | pht->cdev.owner = THIS_MODULE; | ||
288 | |||
289 | iowrite32(0, pht->caddr + PHN_IRQCTL); | ||
290 | retval = request_irq(pdev->irq, phantom_isr, | ||
291 | IRQF_SHARED | IRQF_DISABLED, "phantom", pht); | ||
292 | if (retval) { | ||
293 | dev_err(&pdev->dev, "can't establish ISR\n"); | ||
294 | goto err_unmo; | ||
295 | } | ||
296 | |||
297 | retval = cdev_add(&pht->cdev, MKDEV(phantom_major, minor), 1); | ||
298 | if (retval) { | ||
299 | dev_err(&pdev->dev, "chardev registration failed\n"); | ||
300 | goto err_irq; | ||
301 | } | ||
302 | |||
303 | if (IS_ERR(device_create(phantom_class, &pdev->dev, MKDEV(phantom_major, | ||
304 | minor), "phantom%u", minor))) | ||
305 | dev_err(&pdev->dev, "can't create device\n"); | ||
306 | |||
307 | pci_set_drvdata(pdev, pht); | ||
308 | |||
309 | return 0; | ||
310 | err_irq: | ||
311 | free_irq(pdev->irq, pht); | ||
312 | err_unmo: | ||
313 | pci_iounmap(pdev, pht->oaddr); | ||
314 | err_unmi: | ||
315 | pci_iounmap(pdev, pht->iaddr); | ||
316 | err_unmc: | ||
317 | pci_iounmap(pdev, pht->caddr); | ||
318 | err_fr: | ||
319 | kfree(pht); | ||
320 | err_reg: | ||
321 | pci_release_regions(pdev); | ||
322 | err_null: | ||
323 | phantom_devices[minor] = 0; | ||
324 | err_dis: | ||
325 | pci_disable_device(pdev); | ||
326 | err: | ||
327 | return retval; | ||
328 | } | ||
329 | |||
330 | static void __devexit phantom_remove(struct pci_dev *pdev) | ||
331 | { | ||
332 | struct phantom_device *pht = pci_get_drvdata(pdev); | ||
333 | unsigned int minor = MINOR(pht->cdev.dev); | ||
334 | |||
335 | device_destroy(phantom_class, MKDEV(phantom_major, minor)); | ||
336 | |||
337 | cdev_del(&pht->cdev); | ||
338 | |||
339 | iowrite32(0, pht->caddr + PHN_IRQCTL); | ||
340 | free_irq(pdev->irq, pht); | ||
341 | |||
342 | pci_iounmap(pdev, pht->oaddr); | ||
343 | pci_iounmap(pdev, pht->iaddr); | ||
344 | pci_iounmap(pdev, pht->caddr); | ||
345 | |||
346 | kfree(pht); | ||
347 | |||
348 | pci_release_regions(pdev); | ||
349 | |||
350 | phantom_devices[minor] = 0; | ||
351 | |||
352 | pci_disable_device(pdev); | ||
353 | } | ||
354 | |||
355 | #ifdef CONFIG_PM | ||
356 | static int phantom_suspend(struct pci_dev *pdev, pm_message_t state) | ||
357 | { | ||
358 | struct phantom_device *dev = pci_get_drvdata(pdev); | ||
359 | |||
360 | iowrite32(0, dev->caddr + PHN_IRQCTL); | ||
361 | |||
362 | return 0; | ||
363 | } | ||
364 | |||
365 | static int phantom_resume(struct pci_dev *pdev) | ||
366 | { | ||
367 | struct phantom_device *dev = pci_get_drvdata(pdev); | ||
368 | |||
369 | iowrite32(0, dev->caddr + PHN_IRQCTL); | ||
370 | |||
371 | return 0; | ||
372 | } | ||
373 | #else | ||
374 | #define phantom_suspend NULL | ||
375 | #define phantom_resume NULL | ||
376 | #endif | ||
377 | |||
378 | static struct pci_device_id phantom_pci_tbl[] __devinitdata = { | ||
379 | { PCI_DEVICE(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050), | ||
380 | .class = PCI_CLASS_BRIDGE_OTHER << 8, .class_mask = 0xffff00 }, | ||
381 | { 0, } | ||
382 | }; | ||
383 | MODULE_DEVICE_TABLE(pci, phantom_pci_tbl); | ||
384 | |||
385 | static struct pci_driver phantom_pci_driver = { | ||
386 | .name = "phantom", | ||
387 | .id_table = phantom_pci_tbl, | ||
388 | .probe = phantom_probe, | ||
389 | .remove = __devexit_p(phantom_remove), | ||
390 | .suspend = phantom_suspend, | ||
391 | .resume = phantom_resume | ||
392 | }; | ||
393 | |||
394 | static ssize_t phantom_show_version(struct class *cls, char *buf) | ||
395 | { | ||
396 | return sprintf(buf, PHANTOM_VERSION "\n"); | ||
397 | } | ||
398 | |||
399 | static CLASS_ATTR(version, 0444, phantom_show_version, NULL); | ||
400 | |||
401 | static int __init phantom_init(void) | ||
402 | { | ||
403 | int retval; | ||
404 | dev_t dev; | ||
405 | |||
406 | phantom_class = class_create(THIS_MODULE, "phantom"); | ||
407 | if (IS_ERR(phantom_class)) { | ||
408 | retval = PTR_ERR(phantom_class); | ||
409 | printk(KERN_ERR "phantom: can't register phantom class\n"); | ||
410 | goto err; | ||
411 | } | ||
412 | retval = class_create_file(phantom_class, &class_attr_version); | ||
413 | if (retval) { | ||
414 | printk(KERN_ERR "phantom: can't create sysfs version file\n"); | ||
415 | goto err_class; | ||
416 | } | ||
417 | |||
418 | retval = alloc_chrdev_region(&dev, 0, PHANTOM_MAX_MINORS, "phantom"); | ||
419 | if (retval) { | ||
420 | printk(KERN_ERR "phantom: can't register character device\n"); | ||
421 | goto err_attr; | ||
422 | } | ||
423 | phantom_major = MAJOR(dev); | ||
424 | |||
425 | retval = pci_register_driver(&phantom_pci_driver); | ||
426 | if (retval) { | ||
427 | printk(KERN_ERR "phantom: can't register pci driver\n"); | ||
428 | goto err_unchr; | ||
429 | } | ||
430 | |||
431 | printk(KERN_INFO "Phantom Linux Driver, version " PHANTOM_VERSION ", " | ||
432 | "init OK\n"); | ||
433 | |||
434 | return 0; | ||
435 | err_unchr: | ||
436 | unregister_chrdev_region(dev, PHANTOM_MAX_MINORS); | ||
437 | err_attr: | ||
438 | class_remove_file(phantom_class, &class_attr_version); | ||
439 | err_class: | ||
440 | class_destroy(phantom_class); | ||
441 | err: | ||
442 | return retval; | ||
443 | } | ||
444 | |||
445 | static void __exit phantom_exit(void) | ||
446 | { | ||
447 | pci_unregister_driver(&phantom_pci_driver); | ||
448 | |||
449 | unregister_chrdev_region(MKDEV(phantom_major, 0), PHANTOM_MAX_MINORS); | ||
450 | |||
451 | class_remove_file(phantom_class, &class_attr_version); | ||
452 | class_destroy(phantom_class); | ||
453 | |||
454 | pr_debug("phantom: module successfully removed\n"); | ||
455 | } | ||
456 | |||
457 | module_init(phantom_init); | ||
458 | module_exit(phantom_exit); | ||
459 | |||
460 | MODULE_AUTHOR("Jiri Slaby <jirislaby@gmail.com>"); | ||
461 | MODULE_DESCRIPTION("Sensable Phantom driver"); | ||
462 | MODULE_LICENSE("GPL"); | ||
463 | MODULE_VERSION(PHANTOM_VERSION); | ||