1 /*
2 * Linux driver for the PC110 pad
3 */
4
5 /**
6 * DOC: PC110 Digitizer Hardware
7 *
8 * The pad provides triples of data. The first byte has
9 * 0x80=bit 8 X, 0x01=bit 7 X, 0x08=bit 8 Y, 0x01=still down
10 * The second byte is bits 0-6 X
11 * The third is bits 0-6 Y
12 *
13 * This is read internally and used to synthesize a stream of
14 * triples in the form expected from a PS/2 device. Specialist
15 * applications can choose to obtain the pad data in other formats
16 * including a debugging mode.
17 *
18 * It would be good to add a joystick driver mode to this pad so
19 * that doom and other game playing are better. One possible approach
20 * would be to deactive the mouse mode while the joystick port is opened.
21 */
22
23 /*
24 * History
25 *
26 * 0.0 1997-05-16 Alan Cox <alan@redhat.com> - Pad reader
27 * 0.1 1997-05-19 Robin O'Leary <robin@acm.org> - PS/2 emulation
28 * 0.2 1997-06-03 Robin O'Leary <robin@acm.org> - tap gesture
29 * 0.3 1997-06-27 Alan Cox <alan@redhat.com> - 2.1 commit
30 * 0.4 1997-11-09 Alan Cox <alan@redhat.com> - Single Unix VFS API changes
31 * 0.5 2000-02-10 Alan Cox <alan@redhat.com> - 2.3.x cleanup, documentation
32 */
33
34 #include <linux/module.h>
35 #include <linux/kernel.h>
36 #include <linux/signal.h>
37 #include <linux/errno.h>
38 #include <linux/mm.h>
39 #include <linux/miscdevice.h>
40 #include <linux/ptrace.h>
41 #include <linux/poll.h>
42 #include <linux/ioport.h>
43 #include <linux/interrupt.h>
44 #include <linux/smp_lock.h>
45 #include <linux/init.h>
46
47 #include <asm/signal.h>
48 #include <asm/io.h>
49 #include <asm/irq.h>
50 #include <asm/semaphore.h>
51 #include <linux/spinlock.h>
52 #include <asm/uaccess.h>
53
54 #include "pc110pad.h"
55
56
57 static struct pc110pad_params default_params = {
58 mode: PC110PAD_PS2,
59 bounce_interval: 50 MS,
60 tap_interval: 200 MS,
61 irq: 10,
62 io: 0x15E0,
63 };
64
65 static struct pc110pad_params current_params;
66
67
68 /* driver/filesystem interface management */
69 static wait_queue_head_t queue;
70 static struct fasync_struct *asyncptr;
71 static int active; /* number of concurrent open()s */
72 static struct semaphore reader_lock;
73
74 /**
75 * wake_readers:
76 *
77 * Take care of letting any waiting processes know that
78 * now would be a good time to do a read(). Called
79 * whenever a state transition occurs, real or synthetic. Also
80 * issue any SIGIO's to programs that use SIGIO on mice (eg
81 * Executor)
82 */
83
wake_readers(void)84 static void wake_readers(void)
85 {
86 wake_up_interruptible(&queue);
87 kill_fasync(&asyncptr, SIGIO, POLL_IN);
88 }
89
90
91 /*****************************************************************************/
92 /*
93 * Deal with the messy business of synthesizing button tap and drag
94 * events.
95 *
96 * Exports:
97 * notify_pad_up_down()
98 * Must be called whenever debounced pad up/down state changes.
99 * button_pending
100 * Flag is set whenever read_button() has new values
101 * to return.
102 * read_button()
103 * Obtains the current synthetic mouse button state.
104 */
105
106 /*
107 * These keep track of up/down transitions needed to generate the
108 * synthetic mouse button events. While recent_transition is set,
109 * up/down events cause transition_count to increment. tap_timer
110 * turns off the recent_transition flag and may cause some synthetic
111 * up/down mouse events to be created by incrementing synthesize_tap.
112 */
113
114 static int button_pending;
115 static int recent_transition;
116 static int transition_count;
117 static int synthesize_tap;
118 static void tap_timeout(unsigned long data);
119 static struct timer_list tap_timer = { function: tap_timeout };
120
121
122 /**
123 * tap_timeout:
124 * @data: Unused
125 *
126 * This callback goes off a short time after an up/down transition;
127 * before it goes off, transitions will be considered part of a
128 * single PS/2 event and counted in transition_count. Once the
129 * timeout occurs the recent_transition flag is cleared and
130 * any synthetic mouse up/down events are generated.
131 */
132
tap_timeout(unsigned long data)133 static void tap_timeout(unsigned long data)
134 {
135 if(!recent_transition)
136 {
137 printk(KERN_ERR "pc110pad: tap_timeout but no recent transition!\n");
138 }
139 if( transition_count==2 || transition_count==4 || transition_count==6 )
140 {
141 synthesize_tap+=transition_count;
142 button_pending = 1;
143 wake_readers();
144 }
145 recent_transition=0;
146 }
147
148
149 /**
150 * notify_pad_up_down:
151 *
152 * Called by the raw pad read routines when a (debounced) up/down
153 * transition is detected.
154 */
155
notify_pad_up_down(void)156 void notify_pad_up_down(void)
157 {
158 if(recent_transition)
159 {
160 transition_count++;
161 }
162 else
163 {
164 transition_count=1;
165 recent_transition=1;
166 }
167 mod_timer(&tap_timer, jiffies + current_params.tap_interval);
168
169 /* changes to transition_count can cause reported button to change */
170 button_pending = 1;
171 wake_readers();
172 }
173
174 /**
175 * read_button:
176 * @b: pointer to the button status.
177 *
178 * The actual button state depends on what we are seeing. We have to check
179 * for the tap gesture and also for dragging.
180 */
181
read_button(int * b)182 static void read_button(int *b)
183 {
184 if(synthesize_tap)
185 {
186 *b=--synthesize_tap & 1;
187 }
188 else
189 {
190 *b=(!recent_transition && transition_count==3); /* drag */
191 }
192 button_pending=(synthesize_tap>0);
193 }
194
195
196 /*****************************************************************************/
197 /*
198 * Read pad absolute co-ordinates and debounced up/down state.
199 *
200 * Exports:
201 * pad_irq()
202 * Function to be called whenever the pad signals
203 * that it has new data available.
204 * read_raw_pad()
205 * Returns the most current pad state.
206 * xy_pending
207 * Flag is set whenever read_raw_pad() has new values
208 * to return.
209 * Imports:
210 * wake_readers()
211 * Called when movement occurs.
212 * notify_pad_up_down()
213 * Called when debounced up/down status changes.
214 */
215
216 /*
217 * These are up/down state and absolute co-ords read directly from pad
218 */
219
220 static int raw_data[3];
221 static int raw_data_count;
222 static int raw_x, raw_y; /* most recent absolute co-ords read */
223 static int raw_down; /* raw up/down state */
224 static int debounced_down; /* up/down state after debounce processing */
225 static enum { NO_BOUNCE, JUST_GONE_UP, JUST_GONE_DOWN } bounce=NO_BOUNCE;
226 /* set just after an up/down transition */
227 static int xy_pending; /* set if new data have not yet been read */
228
229 /*
230 * Timer goes off a short while after an up/down transition and copies
231 * the value of raw_down to debounced_down.
232 */
233
234 static void bounce_timeout(unsigned long data);
235 static struct timer_list bounce_timer = { function: bounce_timeout };
236
237
238
239 /**
240 * bounce_timeout:
241 * @data: Unused
242 *
243 * No further up/down transitions happened within the
244 * bounce period, so treat this as a genuine transition.
245 */
246
bounce_timeout(unsigned long data)247 static void bounce_timeout(unsigned long data)
248 {
249 switch(bounce)
250 {
251 case NO_BOUNCE:
252 {
253 /*
254 * Strange; the timer callback should only go off if
255 * we were expecting to do bounce processing!
256 */
257 printk(KERN_WARNING "pc110pad, bounce_timeout: bounce flag not set!\n");
258 break;
259 }
260 case JUST_GONE_UP:
261 {
262 /*
263 * The last up we spotted really was an up, so set
264 * debounced state the same as raw state.
265 */
266 bounce=NO_BOUNCE;
267 if(debounced_down==raw_down)
268 {
269 printk(KERN_WARNING "pc110pad, bounce_timeout: raw already debounced!\n");
270 }
271 debounced_down=raw_down;
272
273 notify_pad_up_down();
274 break;
275 }
276 case JUST_GONE_DOWN:
277 {
278 /*
279 * We don't debounce down events, but we still time
280 * out soon after one occurs so we can avoid the (x,y)
281 * skittering that sometimes happens.
282 */
283 bounce=NO_BOUNCE;
284 break;
285 }
286 }
287 }
288
289
290 /**
291 * pad_irq:
292 * @irq: Interrupt number
293 * @ptr: Unused
294 * @regs: Unused
295 *
296 * Callback when pad's irq goes off; copies values in to raw_* globals;
297 * initiates debounce processing. This isn't SMP safe however there are
298 * no SMP machines with a PC110 touchpad on them.
299 */
300
pad_irq(int irq,void * ptr,struct pt_regs * regs)301 static void pad_irq(int irq, void *ptr, struct pt_regs *regs)
302 {
303
304 /* Obtain byte from pad and prime for next byte */
305 {
306 int value=inb_p(current_params.io);
307 int handshake=inb_p(current_params.io+2);
308 outb_p(handshake | 1, current_params.io+2);
309 outb_p(handshake &~1, current_params.io+2);
310 inb_p(0x64);
311
312 raw_data[raw_data_count++]=value;
313 }
314
315 if(raw_data_count==3)
316 {
317 int new_down=raw_data[0]&0x01;
318 int new_x=raw_data[1];
319 int new_y=raw_data[2];
320 if(raw_data[0]&0x10) new_x+=128;
321 if(raw_data[0]&0x80) new_x+=256;
322 if(raw_data[0]&0x08) new_y+=128;
323
324 if( (raw_x!=new_x) || (raw_y!=new_y) )
325 {
326 raw_x=new_x;
327 raw_y=new_y;
328 xy_pending=1;
329 }
330
331 if(new_down != raw_down)
332 {
333 /* Down state has changed. raw_down always holds
334 * the most recently observed state.
335 */
336 raw_down=new_down;
337
338 /* Forget any earlier bounce processing */
339 if(bounce)
340 {
341 del_timer(&bounce_timer);
342 bounce=NO_BOUNCE;
343 }
344
345 if(new_down)
346 {
347 if(debounced_down)
348 {
349 /* pad gone down, but we were reporting
350 * it down anyway because we suspected
351 * (correctly) that the last up was just
352 * a bounce
353 */
354 }
355 else
356 {
357 bounce=JUST_GONE_DOWN;
358 mod_timer(&bounce_timer,
359 jiffies+current_params.bounce_interval);
360 /* start new stroke/tap */
361 debounced_down=new_down;
362 notify_pad_up_down();
363 }
364 }
365 else /* just gone up */
366 {
367 if(recent_transition)
368 {
369 /* early bounces are probably part of
370 * a multi-tap gesture, so process
371 * immediately
372 */
373 debounced_down=new_down;
374 notify_pad_up_down();
375 }
376 else
377 {
378 /* don't trust it yet */
379 bounce=JUST_GONE_UP;
380 mod_timer(&bounce_timer,
381 jiffies+current_params.bounce_interval);
382 }
383 }
384 }
385 wake_readers();
386 raw_data_count=0;
387 }
388 }
389
390 /**
391 * read_raw_pad:
392 * @down: set if the pen is down
393 * @debounced: set if the debounced pen position is down
394 * @x: X position
395 * @y: Y position
396 *
397 * Retrieve the data saved by the interrupt handler and indicate we
398 * have no more pending XY to do.
399 *
400 * FIXME: We should switch to a spinlock for this.
401 */
402
read_raw_pad(int * down,int * debounced,int * x,int * y)403 static void read_raw_pad(int *down, int *debounced, int *x, int *y)
404 {
405 disable_irq(current_params.irq);
406 {
407 *down=raw_down;
408 *debounced=debounced_down;
409 *x=raw_x;
410 *y=raw_y;
411 xy_pending = 0;
412 }
413 enable_irq(current_params.irq);
414 }
415
416 /*****************************************************************************/
417 /*
418 * Filesystem interface
419 */
420
421 /*
422 * Read returns byte triples, so we need to keep track of
423 * how much of a triple has been read. This is shared across
424 * all processes which have this device open---not that anything
425 * will make much sense in that case.
426 */
427 static int read_bytes[3];
428 static int read_byte_count;
429
430 /**
431 * sample_raw:
432 * @d: sample buffer
433 *
434 * Retrieve a triple of sample data.
435 */
436
437
sample_raw(int d[3])438 static void sample_raw(int d[3])
439 {
440 d[0]=raw_data[0];
441 d[1]=raw_data[1];
442 d[2]=raw_data[2];
443 }
444
445 /**
446 * sample_rare:
447 * @d: sample buffer
448 *
449 * Retrieve a triple of sample data and sanitize it. We do the needed
450 * scaling and masking to get the current status.
451 */
452
453
sample_rare(int d[3])454 static void sample_rare(int d[3])
455 {
456 int thisd, thisdd, thisx, thisy;
457
458 read_raw_pad(&thisd, &thisdd, &thisx, &thisy);
459
460 d[0]=(thisd?0x80:0)
461 | (thisx/256)<<4
462 | (thisdd?0x08:0)
463 | (thisy/256)
464 ;
465 d[1]=thisx%256;
466 d[2]=thisy%256;
467 }
468
469 /**
470 * sample_debug:
471 * @d: sample buffer
472 *
473 * Retrieve a triple of sample data and mix it up with the state
474 * information in the gesture parser. Not useful for normal users but
475 * handy when debugging
476 */
477
sample_debug(int d[3])478 static void sample_debug(int d[3])
479 {
480 int thisd, thisdd, thisx, thisy;
481 int b;
482 unsigned long flags;
483
484 save_flags(flags);
485 cli();
486 read_raw_pad(&thisd, &thisdd, &thisx, &thisy);
487 d[0]=(thisd?0x80:0) | (thisdd?0x40:0) | bounce;
488 d[1]=(recent_transition?0x80:0)+transition_count;
489 read_button(&b);
490 d[2]=(synthesize_tap<<4) | (b?0x01:0);
491 restore_flags(flags);
492 }
493
494 /**
495 * sample_ps2:
496 * @d: sample buffer
497 *
498 * Retrieve a triple of sample data and turn the debounced tap and
499 * stroke information into what appears to be a PS/2 mouse. This means
500 * the PC110 pad needs no funny application side support.
501 */
502
503
sample_ps2(int d[3])504 static void sample_ps2(int d[3])
505 {
506 static int lastx, lasty, lastd;
507
508 int thisd, thisdd, thisx, thisy;
509 int dx, dy, b;
510
511 /*
512 * Obtain the current mouse parameters and limit as appropriate for
513 * the return data format. Interrupts are only disabled while
514 * obtaining the parameters, NOT during the puts_fs_byte() calls,
515 * so paging in put_user() does not affect mouse tracking.
516 */
517 read_raw_pad(&thisd, &thisdd, &thisx, &thisy);
518 read_button(&b);
519
520 /* Now compare with previous readings. Note that we use the
521 * raw down flag rather than the debounced one.
522 */
523 if( (thisd && !lastd) /* new stroke */
524 || (bounce!=NO_BOUNCE) )
525 {
526 dx=0;
527 dy=0;
528 }
529 else
530 {
531 dx = (thisx-lastx);
532 dy = -(thisy-lasty);
533 }
534 lastx=thisx;
535 lasty=thisy;
536 lastd=thisd;
537
538 /*
539 d[0]= ((dy<0)?0x20:0)
540 | ((dx<0)?0x10:0)
541 | 0x08
542 | (b? 0x01:0x00)
543 ;
544 */
545 d[0]= ((dy<0)?0x20:0)
546 | ((dx<0)?0x10:0)
547 | (b? 0x00:0x08)
548 ;
549 d[1]=dx;
550 d[2]=dy;
551 }
552
553
554 /**
555 * fasync_pad:
556 * @fd: file number for the file
557 * @filp: file handle
558 * @on: 1 to add, 0 to remove a notifier
559 *
560 * Update the queue of asynchronous event notifiers. We can use the
561 * same helper the mice do and that does almost everything we need.
562 */
563
fasync_pad(int fd,struct file * filp,int on)564 static int fasync_pad(int fd, struct file *filp, int on)
565 {
566 int retval;
567
568 retval = fasync_helper(fd, filp, on, &asyncptr);
569 if (retval < 0)
570 return retval;
571 return 0;
572 }
573
574
575 /**
576 * close_pad:
577 * @inode: inode of pad
578 * @file: file handle to pad
579 *
580 * Close access to the pad. We turn the pad power off if this is the
581 * last user of the pad. I've not actually measured the power draw but
582 * the DOS driver is careful to do this so we follow suit.
583 */
584
close_pad(struct inode * inode,struct file * file)585 static int close_pad(struct inode * inode, struct file * file)
586 {
587 lock_kernel();
588 fasync_pad(-1, file, 0);
589 if (!--active)
590 outb(0x30, current_params.io+2); /* switch off digitiser */
591 unlock_kernel();
592 return 0;
593 }
594
595
596 /**
597 * open_pad:
598 * @inode: inode of pad
599 * @file: file handle to pad
600 *
601 * Open access to the pad. We turn the pad off first (we turned it off
602 * on close but if this is the first open after a crash the state is
603 * indeterminate). The device has a small fifo so we empty that before
604 * we kick it back into action.
605 */
606
open_pad(struct inode * inode,struct file * file)607 static int open_pad(struct inode * inode, struct file * file)
608 {
609 unsigned long flags;
610
611 if (active++)
612 return 0;
613
614 save_flags(flags);
615 cli();
616 outb(0x30, current_params.io+2); /* switch off digitiser */
617 pad_irq(0,0,0); /* read to flush any pending bytes */
618 pad_irq(0,0,0); /* read to flush any pending bytes */
619 pad_irq(0,0,0); /* read to flush any pending bytes */
620 outb(0x38, current_params.io+2); /* switch on digitiser */
621 current_params = default_params;
622 raw_data_count=0; /* re-sync input byte counter */
623 read_byte_count=0; /* re-sync output byte counter */
624 button_pending=0;
625 recent_transition=0;
626 transition_count=0;
627 synthesize_tap=0;
628 del_timer(&bounce_timer);
629 del_timer(&tap_timer);
630 restore_flags(flags);
631
632 return 0;
633 }
634
635
636 /**
637 * write_pad:
638 * @file: File handle to the pad
639 * @buffer: Unused
640 * @count: Unused
641 * @ppos: Unused
642 *
643 * Writes are disallowed. A true PS/2 mouse lets you write stuff. Everyone
644 * seems happy with this and not faking the write modes.
645 */
646
write_pad(struct file * file,const char * buffer,size_t count,loff_t * ppos)647 static ssize_t write_pad(struct file * file, const char * buffer, size_t count, loff_t *ppos)
648 {
649 return -EINVAL;
650 }
651
652
653 /*
654 * new_sample:
655 * @d: sample buffer
656 *
657 * Fetch a new sample according the current mouse mode the pad is
658 * using.
659 */
660
new_sample(int d[3])661 void new_sample(int d[3])
662 {
663 switch(current_params.mode)
664 {
665 case PC110PAD_RAW: sample_raw(d); break;
666 case PC110PAD_RARE: sample_rare(d); break;
667 case PC110PAD_DEBUG: sample_debug(d); break;
668 case PC110PAD_PS2: sample_ps2(d); break;
669 }
670 }
671
672
673 /**
674 * read_pad:
675 * @file: File handle to pad
676 * @buffer: Target for the mouse data
677 * @count: Buffer length
678 * @ppos: Offset (unused)
679 *
680 * Read data from the pad. We use the reader_lock to avoid mess when there are
681 * two readers. This shouldnt be happening anyway but we play safe.
682 */
683
read_pad(struct file * file,char * buffer,size_t count,loff_t * ppos)684 static ssize_t read_pad(struct file * file, char * buffer, size_t count, loff_t *ppos)
685 {
686 int r;
687
688 down(&reader_lock);
689 for(r=0; r<count; r++)
690 {
691 if(!read_byte_count)
692 new_sample(read_bytes);
693 if(put_user(read_bytes[read_byte_count], buffer+r))
694 {
695 r = -EFAULT;
696 break;
697 }
698 read_byte_count = (read_byte_count+1)%3;
699 }
700 up(&reader_lock);
701 return r;
702 }
703
704
705 /**
706 * pad_poll:
707 * @file: File of the pad device
708 * @wait: Poll table
709 *
710 * The pad is ready to read if there is a button or any position change
711 * pending in the queue. The reading and interrupt routines maintain the
712 * required state for us and do needed wakeups.
713 */
714
pad_poll(struct file * file,poll_table * wait)715 static unsigned int pad_poll(struct file *file, poll_table * wait)
716 {
717 poll_wait(file, &queue, wait);
718 if(button_pending || xy_pending)
719 return POLLIN | POLLRDNORM;
720 return 0;
721 }
722
723
724 /**
725 * pad_ioctl;
726 * @inode: Inode of the pad
727 * @file: File handle to the pad
728 * @cmd: Ioctl command
729 * @arg: Argument pointer
730 *
731 * The PC110 pad supports two ioctls both of which use the pc110pad_params
732 * structure. GETP queries the current pad status. SETP changes the pad
733 * configuration. Changing configuration during normal mouse operations
734 * may give momentarily odd results as things like tap gesture state
735 * may be lost.
736 */
737
pad_ioctl(struct inode * inode,struct file * file,unsigned int cmd,unsigned long arg)738 static int pad_ioctl(struct inode *inode, struct file * file,
739 unsigned int cmd, unsigned long arg)
740 {
741 struct pc110pad_params new;
742
743 if (!inode)
744 return -EINVAL;
745
746 switch (cmd) {
747 case PC110PADIOCGETP:
748 new = current_params;
749 if(copy_to_user((void *)arg, &new, sizeof(new)))
750 return -EFAULT;
751 return 0;
752
753 case PC110PADIOCSETP:
754 if(copy_from_user(&new, (void *)arg, sizeof(new)))
755 return -EFAULT;
756
757 if( (new.mode<PC110PAD_RAW)
758 || (new.mode>PC110PAD_PS2)
759 || (new.bounce_interval<0)
760 || (new.tap_interval<0)
761 )
762 return -EINVAL;
763
764 current_params.mode = new.mode;
765 current_params.bounce_interval = new.bounce_interval;
766 current_params.tap_interval = new.tap_interval;
767 return 0;
768 }
769 return -ENOTTY;
770 }
771
772
773 static struct file_operations pad_fops = {
774 owner: THIS_MODULE,
775 read: read_pad,
776 write: write_pad,
777 poll: pad_poll,
778 ioctl: pad_ioctl,
779 open: open_pad,
780 release: close_pad,
781 fasync: fasync_pad,
782 };
783
784
785 static struct miscdevice pc110_pad = {
786 minor: PC110PAD_MINOR,
787 name: "pc110 pad",
788 fops: &pad_fops,
789 };
790
791
792 /**
793 * pc110pad_init_driver:
794 *
795 * We configure the pad with the default parameters (that is PS/2
796 * emulation mode. We then claim the needed I/O and interrupt resources.
797 * Finally as a matter of paranoia we turn the pad off until we are
798 * asked to open it by an application.
799 */
800
801 static char banner[] __initdata = KERN_INFO "PC110 digitizer pad at 0x%X, irq %d.\n";
802
pc110pad_init_driver(void)803 static int __init pc110pad_init_driver(void)
804 {
805 init_MUTEX(&reader_lock);
806 current_params = default_params;
807
808 if (request_irq(current_params.irq, pad_irq, 0, "pc110pad", 0)) {
809 printk(KERN_ERR "pc110pad: Unable to get IRQ.\n");
810 return -EBUSY;
811 }
812 if (!request_region(current_params.io, 4, "pc110pad")) {
813 printk(KERN_ERR "pc110pad: I/O area in use.\n");
814 free_irq(current_params.irq,0);
815 return -EBUSY;
816 }
817 init_waitqueue_head(&queue);
818 printk(banner, current_params.io, current_params.irq);
819 misc_register(&pc110_pad);
820 outb(0x30, current_params.io+2); /* switch off digitiser */
821 return 0;
822 }
823
824 /*
825 * pc110pad_exit_driver:
826 *
827 * Free the resources we acquired when the module was loaded. We also
828 * turn the pad off to be sure we don't leave it using power.
829 */
830
pc110pad_exit_driver(void)831 static void __exit pc110pad_exit_driver(void)
832 {
833 outb(0x30, current_params.io+2); /* switch off digitiser */
834 if (current_params.irq)
835 free_irq(current_params.irq, 0);
836 current_params.irq = 0;
837 release_region(current_params.io, 4);
838 misc_deregister(&pc110_pad);
839 }
840
841 module_init(pc110pad_init_driver);
842 module_exit(pc110pad_exit_driver);
843
844 MODULE_AUTHOR("Alan Cox, Robin O'Leary");
845 MODULE_DESCRIPTION("Driver for the touchpad on the IBM PC110 palmtop");
846 MODULE_LICENSE("GPL");
847
848 EXPORT_NO_SYMBOLS;
849