Commit | Line | Data |
---|---|---|
5a18c343 POF |
1 | /* |
2 | * HTC Shift touchscreen driver | |
3 | * | |
4 | * Copyright (C) 2008 Pau Oliva Fora <pof@eslack.org> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License version 2 as published | |
8 | * by the Free Software Foundation. | |
9 | */ | |
10 | ||
11 | #include <linux/errno.h> | |
12 | #include <linux/kernel.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/input.h> | |
15 | #include <linux/interrupt.h> | |
16 | #include <linux/io.h> | |
17 | #include <linux/init.h> | |
18 | #include <linux/irq.h> | |
19 | #include <linux/isa.h> | |
20 | #include <linux/ioport.h> | |
21 | #include <linux/dmi.h> | |
22 | ||
23 | MODULE_AUTHOR("Pau Oliva Fora <pau@eslack.org>"); | |
24 | MODULE_DESCRIPTION("HTC Shift touchscreen driver"); | |
25 | MODULE_LICENSE("GPL"); | |
26 | ||
27 | #define HTCPEN_PORT_IRQ_CLEAR 0x068 | |
28 | #define HTCPEN_PORT_INIT 0x06c | |
29 | #define HTCPEN_PORT_INDEX 0x0250 | |
30 | #define HTCPEN_PORT_DATA 0x0251 | |
31 | #define HTCPEN_IRQ 3 | |
32 | ||
33 | #define DEVICE_ENABLE 0xa2 | |
34 | #define DEVICE_DISABLE 0xa3 | |
35 | ||
36 | #define X_INDEX 3 | |
37 | #define Y_INDEX 5 | |
38 | #define TOUCH_INDEX 0xb | |
39 | #define LSB_XY_INDEX 0xc | |
40 | #define X_AXIS_MAX 2040 | |
41 | #define Y_AXIS_MAX 2040 | |
42 | ||
90ab5ee9 | 43 | static bool invert_x; |
5a18c343 POF |
44 | module_param(invert_x, bool, 0644); |
45 | MODULE_PARM_DESC(invert_x, "If set, X axis is inverted"); | |
90ab5ee9 | 46 | static bool invert_y; |
5a18c343 POF |
47 | module_param(invert_y, bool, 0644); |
48 | MODULE_PARM_DESC(invert_y, "If set, Y axis is inverted"); | |
49 | ||
5a18c343 POF |
50 | static irqreturn_t htcpen_interrupt(int irq, void *handle) |
51 | { | |
52 | struct input_dev *htcpen_dev = handle; | |
53 | unsigned short x, y, xy; | |
54 | ||
55 | /* 0 = press; 1 = release */ | |
56 | outb_p(TOUCH_INDEX, HTCPEN_PORT_INDEX); | |
57 | ||
58 | if (inb_p(HTCPEN_PORT_DATA)) { | |
59 | input_report_key(htcpen_dev, BTN_TOUCH, 0); | |
60 | } else { | |
61 | outb_p(X_INDEX, HTCPEN_PORT_INDEX); | |
62 | x = inb_p(HTCPEN_PORT_DATA); | |
63 | ||
64 | outb_p(Y_INDEX, HTCPEN_PORT_INDEX); | |
65 | y = inb_p(HTCPEN_PORT_DATA); | |
66 | ||
67 | outb_p(LSB_XY_INDEX, HTCPEN_PORT_INDEX); | |
68 | xy = inb_p(HTCPEN_PORT_DATA); | |
69 | ||
70 | /* get high resolution value of X and Y using LSB */ | |
71 | x = X_AXIS_MAX - ((x * 8) + ((xy >> 4) & 0xf)); | |
72 | y = (y * 8) + (xy & 0xf); | |
73 | if (invert_x) | |
74 | x = X_AXIS_MAX - x; | |
75 | if (invert_y) | |
76 | y = Y_AXIS_MAX - y; | |
77 | ||
78 | if (x != X_AXIS_MAX && x != 0) { | |
79 | input_report_key(htcpen_dev, BTN_TOUCH, 1); | |
80 | input_report_abs(htcpen_dev, ABS_X, x); | |
81 | input_report_abs(htcpen_dev, ABS_Y, y); | |
82 | } | |
83 | } | |
84 | ||
85 | input_sync(htcpen_dev); | |
86 | ||
87 | inb_p(HTCPEN_PORT_IRQ_CLEAR); | |
88 | ||
89 | return IRQ_HANDLED; | |
90 | } | |
91 | ||
92 | static int htcpen_open(struct input_dev *dev) | |
93 | { | |
94 | outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); | |
95 | ||
96 | return 0; | |
97 | } | |
98 | ||
99 | static void htcpen_close(struct input_dev *dev) | |
100 | { | |
101 | outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); | |
102 | synchronize_irq(HTCPEN_IRQ); | |
103 | } | |
104 | ||
5298cc4c | 105 | static int htcpen_isa_probe(struct device *dev, unsigned int id) |
5a18c343 POF |
106 | { |
107 | struct input_dev *htcpen_dev; | |
108 | int err = -EBUSY; | |
109 | ||
110 | if (!request_region(HTCPEN_PORT_IRQ_CLEAR, 1, "htcpen")) { | |
111 | printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", | |
112 | HTCPEN_PORT_IRQ_CLEAR); | |
113 | goto request_region1_failed; | |
114 | } | |
115 | ||
116 | if (!request_region(HTCPEN_PORT_INIT, 1, "htcpen")) { | |
117 | printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", | |
118 | HTCPEN_PORT_INIT); | |
119 | goto request_region2_failed; | |
120 | } | |
121 | ||
122 | if (!request_region(HTCPEN_PORT_INDEX, 2, "htcpen")) { | |
123 | printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", | |
124 | HTCPEN_PORT_INDEX); | |
125 | goto request_region3_failed; | |
126 | } | |
127 | ||
128 | htcpen_dev = input_allocate_device(); | |
129 | if (!htcpen_dev) { | |
130 | printk(KERN_ERR "htcpen: can't allocate device\n"); | |
131 | err = -ENOMEM; | |
132 | goto input_alloc_failed; | |
133 | } | |
134 | ||
135 | htcpen_dev->name = "HTC Shift EC TouchScreen"; | |
136 | htcpen_dev->id.bustype = BUS_ISA; | |
137 | ||
138 | htcpen_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); | |
139 | htcpen_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); | |
140 | input_set_abs_params(htcpen_dev, ABS_X, 0, X_AXIS_MAX, 0, 0); | |
141 | input_set_abs_params(htcpen_dev, ABS_Y, 0, Y_AXIS_MAX, 0, 0); | |
142 | ||
143 | htcpen_dev->open = htcpen_open; | |
144 | htcpen_dev->close = htcpen_close; | |
145 | ||
146 | err = request_irq(HTCPEN_IRQ, htcpen_interrupt, 0, "htcpen", | |
147 | htcpen_dev); | |
148 | if (err) { | |
149 | printk(KERN_ERR "htcpen: irq busy\n"); | |
150 | goto request_irq_failed; | |
151 | } | |
152 | ||
153 | inb_p(HTCPEN_PORT_IRQ_CLEAR); | |
154 | ||
155 | err = input_register_device(htcpen_dev); | |
156 | if (err) | |
157 | goto input_register_failed; | |
158 | ||
159 | dev_set_drvdata(dev, htcpen_dev); | |
160 | ||
161 | return 0; | |
162 | ||
163 | input_register_failed: | |
164 | free_irq(HTCPEN_IRQ, htcpen_dev); | |
165 | request_irq_failed: | |
166 | input_free_device(htcpen_dev); | |
167 | input_alloc_failed: | |
168 | release_region(HTCPEN_PORT_INDEX, 2); | |
169 | request_region3_failed: | |
170 | release_region(HTCPEN_PORT_INIT, 1); | |
171 | request_region2_failed: | |
172 | release_region(HTCPEN_PORT_IRQ_CLEAR, 1); | |
173 | request_region1_failed: | |
174 | return err; | |
175 | } | |
176 | ||
e2619cf7 | 177 | static int htcpen_isa_remove(struct device *dev, unsigned int id) |
5a18c343 POF |
178 | { |
179 | struct input_dev *htcpen_dev = dev_get_drvdata(dev); | |
180 | ||
181 | input_unregister_device(htcpen_dev); | |
182 | ||
183 | free_irq(HTCPEN_IRQ, htcpen_dev); | |
184 | ||
185 | release_region(HTCPEN_PORT_INDEX, 2); | |
186 | release_region(HTCPEN_PORT_INIT, 1); | |
187 | release_region(HTCPEN_PORT_IRQ_CLEAR, 1); | |
188 | ||
189 | dev_set_drvdata(dev, NULL); | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
194 | #ifdef CONFIG_PM | |
195 | static int htcpen_isa_suspend(struct device *dev, unsigned int n, | |
196 | pm_message_t state) | |
197 | { | |
198 | outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); | |
199 | ||
200 | return 0; | |
201 | } | |
202 | ||
203 | static int htcpen_isa_resume(struct device *dev, unsigned int n) | |
204 | { | |
205 | outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); | |
206 | ||
207 | return 0; | |
208 | } | |
209 | #endif | |
210 | ||
211 | static struct isa_driver htcpen_isa_driver = { | |
212 | .probe = htcpen_isa_probe, | |
1cb0aa88 | 213 | .remove = htcpen_isa_remove, |
5a18c343 POF |
214 | #ifdef CONFIG_PM |
215 | .suspend = htcpen_isa_suspend, | |
216 | .resume = htcpen_isa_resume, | |
217 | #endif | |
218 | .driver = { | |
219 | .owner = THIS_MODULE, | |
220 | .name = "htcpen", | |
221 | } | |
222 | }; | |
223 | ||
f01868dc | 224 | static struct dmi_system_id htcshift_dmi_table[] __initdata = { |
5a18c343 POF |
225 | { |
226 | .ident = "Shift", | |
227 | .matches = { | |
228 | DMI_MATCH(DMI_SYS_VENDOR, "High Tech Computer Corp"), | |
229 | DMI_MATCH(DMI_PRODUCT_NAME, "Shift"), | |
230 | }, | |
231 | }, | |
232 | { } | |
233 | }; | |
e23ed600 | 234 | MODULE_DEVICE_TABLE(dmi, htcshift_dmi_table); |
5a18c343 POF |
235 | |
236 | static int __init htcpen_isa_init(void) | |
237 | { | |
238 | if (!dmi_check_system(htcshift_dmi_table)) | |
239 | return -ENODEV; | |
240 | ||
241 | return isa_register_driver(&htcpen_isa_driver, 1); | |
242 | } | |
243 | ||
244 | static void __exit htcpen_isa_exit(void) | |
245 | { | |
246 | isa_unregister_driver(&htcpen_isa_driver); | |
247 | } | |
248 | ||
249 | module_init(htcpen_isa_init); | |
250 | module_exit(htcpen_isa_exit); |