1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (c) 2000-2001 Vojtech Pavlik |
4 | * |
5 | * Based on the work of: |
6 | * Alan Cox Robin O'Leary |
7 | */ |
8 | |
9 | /* |
10 | * IBM PC110 touchpad driver for Linux |
11 | */ |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/errno.h> |
16 | #include <linux/ioport.h> |
17 | #include <linux/input.h> |
18 | #include <linux/init.h> |
19 | #include <linux/interrupt.h> |
20 | #include <linux/pci.h> |
21 | #include <linux/delay.h> |
22 | |
23 | #include <asm/io.h> |
24 | #include <asm/irq.h> |
25 | |
26 | MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>" ); |
27 | MODULE_DESCRIPTION("IBM PC110 touchpad driver" ); |
28 | MODULE_LICENSE("GPL" ); |
29 | |
30 | #define PC110PAD_OFF 0x30 |
31 | #define PC110PAD_ON 0x38 |
32 | |
33 | static int pc110pad_irq = 10; |
34 | static int pc110pad_io = 0x15e0; |
35 | |
36 | static struct input_dev *pc110pad_dev; |
37 | static int pc110pad_data[3]; |
38 | static int pc110pad_count; |
39 | |
40 | static irqreturn_t pc110pad_interrupt(int irq, void *ptr) |
41 | { |
42 | int value = inb_p(port: pc110pad_io); |
43 | int handshake = inb_p(port: pc110pad_io + 2); |
44 | |
45 | outb(value: handshake | 1, port: pc110pad_io + 2); |
46 | udelay(2); |
47 | outb(value: handshake & ~1, port: pc110pad_io + 2); |
48 | udelay(2); |
49 | inb_p(port: 0x64); |
50 | |
51 | pc110pad_data[pc110pad_count++] = value; |
52 | |
53 | if (pc110pad_count < 3) |
54 | return IRQ_HANDLED; |
55 | |
56 | input_report_key(dev: pc110pad_dev, BTN_TOUCH, |
57 | value: pc110pad_data[0] & 0x01); |
58 | input_report_abs(dev: pc110pad_dev, ABS_X, |
59 | value: pc110pad_data[1] | ((pc110pad_data[0] << 3) & 0x80) | ((pc110pad_data[0] << 1) & 0x100)); |
60 | input_report_abs(dev: pc110pad_dev, ABS_Y, |
61 | value: pc110pad_data[2] | ((pc110pad_data[0] << 4) & 0x80)); |
62 | input_sync(dev: pc110pad_dev); |
63 | |
64 | pc110pad_count = 0; |
65 | return IRQ_HANDLED; |
66 | } |
67 | |
68 | static void pc110pad_close(struct input_dev *dev) |
69 | { |
70 | outb(PC110PAD_OFF, port: pc110pad_io + 2); |
71 | } |
72 | |
73 | static int pc110pad_open(struct input_dev *dev) |
74 | { |
75 | pc110pad_interrupt(irq: 0, NULL); |
76 | pc110pad_interrupt(irq: 0, NULL); |
77 | pc110pad_interrupt(irq: 0, NULL); |
78 | outb(PC110PAD_ON, port: pc110pad_io + 2); |
79 | pc110pad_count = 0; |
80 | |
81 | return 0; |
82 | } |
83 | |
84 | /* |
85 | * We try to avoid enabling the hardware if it's not |
86 | * there, but we don't know how to test. But we do know |
87 | * that the PC110 is not a PCI system. So if we find any |
88 | * PCI devices in the machine, we don't have a PC110. |
89 | */ |
90 | static int __init pc110pad_init(void) |
91 | { |
92 | int err; |
93 | |
94 | if (!no_pci_devices()) |
95 | return -ENODEV; |
96 | |
97 | if (!request_region(pc110pad_io, 4, "pc110pad" )) { |
98 | printk(KERN_ERR "pc110pad: I/O area %#x-%#x in use.\n" , |
99 | pc110pad_io, pc110pad_io + 4); |
100 | return -EBUSY; |
101 | } |
102 | |
103 | outb(PC110PAD_OFF, port: pc110pad_io + 2); |
104 | |
105 | if (request_irq(irq: pc110pad_irq, handler: pc110pad_interrupt, flags: 0, name: "pc110pad" , NULL)) { |
106 | printk(KERN_ERR "pc110pad: Unable to get irq %d.\n" , pc110pad_irq); |
107 | err = -EBUSY; |
108 | goto err_release_region; |
109 | } |
110 | |
111 | pc110pad_dev = input_allocate_device(); |
112 | if (!pc110pad_dev) { |
113 | printk(KERN_ERR "pc110pad: Not enough memory.\n" ); |
114 | err = -ENOMEM; |
115 | goto err_free_irq; |
116 | } |
117 | |
118 | pc110pad_dev->name = "IBM PC110 TouchPad" ; |
119 | pc110pad_dev->phys = "isa15e0/input0" ; |
120 | pc110pad_dev->id.bustype = BUS_ISA; |
121 | pc110pad_dev->id.vendor = 0x0003; |
122 | pc110pad_dev->id.product = 0x0001; |
123 | pc110pad_dev->id.version = 0x0100; |
124 | |
125 | pc110pad_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); |
126 | pc110pad_dev->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y); |
127 | pc110pad_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); |
128 | |
129 | input_abs_set_max(dev: pc110pad_dev, ABS_X, val: 0x1ff); |
130 | input_abs_set_max(dev: pc110pad_dev, ABS_Y, val: 0x0ff); |
131 | |
132 | pc110pad_dev->open = pc110pad_open; |
133 | pc110pad_dev->close = pc110pad_close; |
134 | |
135 | err = input_register_device(pc110pad_dev); |
136 | if (err) |
137 | goto err_free_dev; |
138 | |
139 | return 0; |
140 | |
141 | err_free_dev: |
142 | input_free_device(dev: pc110pad_dev); |
143 | err_free_irq: |
144 | free_irq(pc110pad_irq, NULL); |
145 | err_release_region: |
146 | release_region(pc110pad_io, 4); |
147 | |
148 | return err; |
149 | } |
150 | |
151 | static void __exit pc110pad_exit(void) |
152 | { |
153 | outb(PC110PAD_OFF, port: pc110pad_io + 2); |
154 | free_irq(pc110pad_irq, NULL); |
155 | input_unregister_device(pc110pad_dev); |
156 | release_region(pc110pad_io, 4); |
157 | } |
158 | |
159 | module_init(pc110pad_init); |
160 | module_exit(pc110pad_exit); |
161 | |