1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
30
31#include <linux/delay.h>
32#include <linux/platform_device.h>
33#include <linux/input-polldev.h>
34#include <linux/kernel.h>
35#include <linux/mutex.h>
36#include <linux/module.h>
37#include <linux/timer.h>
38#include <linux/dmi.h>
39#include <linux/jiffies.h>
40#include <linux/io.h>
41
42#define HDAPS_LOW_PORT 0x1600
43#define HDAPS_NR_PORTS 0x30
44
45#define HDAPS_PORT_STATE 0x1611
46#define HDAPS_PORT_YPOS 0x1612
47#define HDAPS_PORT_XPOS 0x1614
48#define HDAPS_PORT_TEMP1 0x1616
49#define HDAPS_PORT_YVAR 0x1617
50#define HDAPS_PORT_XVAR 0x1619
51#define HDAPS_PORT_TEMP2 0x161b
52#define HDAPS_PORT_UNKNOWN 0x161c
53#define HDAPS_PORT_KMACT 0x161d
54
55#define STATE_FRESH 0x50
56
57#define KEYBD_MASK 0x20
58#define MOUSE_MASK 0x40
59#define KEYBD_ISSET(n) (!! (n & KEYBD_MASK))
60#define MOUSE_ISSET(n) (!! (n & MOUSE_MASK))
61
62#define INIT_TIMEOUT_MSECS 4000
63#define INIT_WAIT_MSECS 200
64
65#define HDAPS_POLL_INTERVAL 50
66#define HDAPS_INPUT_FUZZ 4
67#define HDAPS_INPUT_FLAT 4
68
69#define HDAPS_X_AXIS (1 << 0)
70#define HDAPS_Y_AXIS (1 << 1)
71#define HDAPS_BOTH_AXES (HDAPS_X_AXIS | HDAPS_Y_AXIS)
72
73static struct platform_device *pdev;
74static struct input_polled_dev *hdaps_idev;
75static unsigned int hdaps_invert;
76static u8 km_activity;
77static int rest_x;
78static int rest_y;
79
80static DEFINE_MUTEX(hdaps_mtx);
81
82
83
84
85static inline u8 __get_latch(u16 port)
86{
87 return inb(port) & 0xff;
88}
89
90
91
92
93
94static inline int __check_latch(u16 port, u8 val)
95{
96 if (__get_latch(port) == val)
97 return 0;
98 return -EINVAL;
99}
100
101
102
103
104
105static int __wait_latch(u16 port, u8 val)
106{
107 unsigned int i;
108
109 for (i = 0; i < 20; i++) {
110 if (!__check_latch(port, val))
111 return 0;
112 udelay(5);
113 }
114
115 return -EIO;
116}
117
118
119
120
121
122static void __device_refresh(void)
123{
124 udelay(200);
125 if (inb(0x1604) != STATE_FRESH) {
126 outb(0x11, 0x1610);
127 outb(0x01, 0x161f);
128 }
129}
130
131
132
133
134
135
136static int __device_refresh_sync(void)
137{
138 __device_refresh();
139 return __wait_latch(0x1604, STATE_FRESH);
140}
141
142
143
144
145
146static inline void __device_complete(void)
147{
148 inb(0x161f);
149 inb(0x1604);
150 __device_refresh();
151}
152
153
154
155
156
157
158static int hdaps_readb_one(unsigned int port, u8 *val)
159{
160 int ret;
161
162 mutex_lock(&hdaps_mtx);
163
164
165 ret = __device_refresh_sync();
166 if (ret)
167 goto out;
168
169 *val = inb(port);
170 __device_complete();
171
172out:
173 mutex_unlock(&hdaps_mtx);
174 return ret;
175}
176
177
178static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
179 int *x, int *y)
180{
181
182 if (__device_refresh_sync())
183 return -EIO;
184
185 *y = inw(port2);
186 *x = inw(port1);
187 km_activity = inb(HDAPS_PORT_KMACT);
188 __device_complete();
189
190
191 if (hdaps_invert & HDAPS_X_AXIS)
192 *x = -*x;
193 if (hdaps_invert & HDAPS_Y_AXIS)
194 *y = -*y;
195
196 return 0;
197}
198
199
200
201
202
203static int hdaps_read_pair(unsigned int port1, unsigned int port2,
204 int *val1, int *val2)
205{
206 int ret;
207
208 mutex_lock(&hdaps_mtx);
209 ret = __hdaps_read_pair(port1, port2, val1, val2);
210 mutex_unlock(&hdaps_mtx);
211
212 return ret;
213}
214
215
216
217
218
219static int hdaps_device_init(void)
220{
221 int total, ret = -ENXIO;
222
223 mutex_lock(&hdaps_mtx);
224
225 outb(0x13, 0x1610);
226 outb(0x01, 0x161f);
227 if (__wait_latch(0x161f, 0x00))
228 goto out;
229
230
231
232
233
234
235
236
237
238 if (__check_latch(0x1611, 0x03) &&
239 __check_latch(0x1611, 0x02) &&
240 __check_latch(0x1611, 0x01))
241 goto out;
242
243 printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x)\n",
244 __get_latch(0x1611));
245
246 outb(0x17, 0x1610);
247 outb(0x81, 0x1611);
248 outb(0x01, 0x161f);
249 if (__wait_latch(0x161f, 0x00))
250 goto out;
251 if (__wait_latch(0x1611, 0x00))
252 goto out;
253 if (__wait_latch(0x1612, 0x60))
254 goto out;
255 if (__wait_latch(0x1613, 0x00))
256 goto out;
257 outb(0x14, 0x1610);
258 outb(0x01, 0x1611);
259 outb(0x01, 0x161f);
260 if (__wait_latch(0x161f, 0x00))
261 goto out;
262 outb(0x10, 0x1610);
263 outb(0xc8, 0x1611);
264 outb(0x00, 0x1612);
265 outb(0x02, 0x1613);
266 outb(0x01, 0x161f);
267 if (__wait_latch(0x161f, 0x00))
268 goto out;
269 if (__device_refresh_sync())
270 goto out;
271 if (__wait_latch(0x1611, 0x00))
272 goto out;
273
274
275 for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
276 int x, y;
277
278
279 __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
280 if (!__wait_latch(0x1611, 0x02)) {
281 ret = 0;
282 break;
283 }
284
285 msleep(INIT_WAIT_MSECS);
286 }
287
288out:
289 mutex_unlock(&hdaps_mtx);
290 return ret;
291}
292
293
294
295
296static int hdaps_probe(struct platform_device *dev)
297{
298 int ret;
299
300 ret = hdaps_device_init();
301 if (ret)
302 return ret;
303
304 pr_info("device successfully initialized\n");
305 return 0;
306}
307
308static int hdaps_resume(struct platform_device *dev)
309{
310 return hdaps_device_init();
311}
312
313static struct platform_driver hdaps_driver = {
314 .probe = hdaps_probe,
315 .resume = hdaps_resume,
316 .driver = {
317 .name = "hdaps",
318 .owner = THIS_MODULE,
319 },
320};
321
322
323
324
325static void hdaps_calibrate(void)
326{
327 __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
328}
329
330static void hdaps_mousedev_poll(struct input_polled_dev *dev)
331{
332 struct input_dev *input_dev = dev->input;
333 int x, y;
334
335 mutex_lock(&hdaps_mtx);
336
337 if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
338 goto out;
339
340 input_report_abs(input_dev, ABS_X, x - rest_x);
341 input_report_abs(input_dev, ABS_Y, y - rest_y);
342 input_sync(input_dev);
343
344out:
345 mutex_unlock(&hdaps_mtx);
346}
347
348
349
350
351static ssize_t hdaps_position_show(struct device *dev,
352 struct device_attribute *attr, char *buf)
353{
354 int ret, x, y;
355
356 ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
357 if (ret)
358 return ret;
359
360 return sprintf(buf, "(%d,%d)\n", x, y);
361}
362
363static ssize_t hdaps_variance_show(struct device *dev,
364 struct device_attribute *attr, char *buf)
365{
366 int ret, x, y;
367
368 ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
369 if (ret)
370 return ret;
371
372 return sprintf(buf, "(%d,%d)\n", x, y);
373}
374
375static ssize_t hdaps_temp1_show(struct device *dev,
376 struct device_attribute *attr, char *buf)
377{
378 u8 uninitialized_var(temp);
379 int ret;
380
381 ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
382 if (ret)
383 return ret;
384
385 return sprintf(buf, "%u\n", temp);
386}
387
388static ssize_t hdaps_temp2_show(struct device *dev,
389 struct device_attribute *attr, char *buf)
390{
391 u8 uninitialized_var(temp);
392 int ret;
393
394 ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
395 if (ret)
396 return ret;
397
398 return sprintf(buf, "%u\n", temp);
399}
400
401static ssize_t hdaps_keyboard_activity_show(struct device *dev,
402 struct device_attribute *attr,
403 char *buf)
404{
405 return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
406}
407
408static ssize_t hdaps_mouse_activity_show(struct device *dev,
409 struct device_attribute *attr,
410 char *buf)
411{
412 return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
413}
414
415static ssize_t hdaps_calibrate_show(struct device *dev,
416 struct device_attribute *attr, char *buf)
417{
418 return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
419}
420
421static ssize_t hdaps_calibrate_store(struct device *dev,
422 struct device_attribute *attr,
423 const char *buf, size_t count)
424{
425 mutex_lock(&hdaps_mtx);
426 hdaps_calibrate();
427 mutex_unlock(&hdaps_mtx);
428
429 return count;
430}
431
432static ssize_t hdaps_invert_show(struct device *dev,
433 struct device_attribute *attr, char *buf)
434{
435 return sprintf(buf, "%u\n", hdaps_invert);
436}
437
438static ssize_t hdaps_invert_store(struct device *dev,
439 struct device_attribute *attr,
440 const char *buf, size_t count)
441{
442 int invert;
443
444 if (sscanf(buf, "%d", &invert) != 1 ||
445 invert < 0 || invert > HDAPS_BOTH_AXES)
446 return -EINVAL;
447
448 hdaps_invert = invert;
449 hdaps_calibrate();
450
451 return count;
452}
453
454static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
455static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
456static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
457static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
458static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
459static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
460static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
461static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
462
463static struct attribute *hdaps_attributes[] = {
464 &dev_attr_position.attr,
465 &dev_attr_variance.attr,
466 &dev_attr_temp1.attr,
467 &dev_attr_temp2.attr,
468 &dev_attr_keyboard_activity.attr,
469 &dev_attr_mouse_activity.attr,
470 &dev_attr_calibrate.attr,
471 &dev_attr_invert.attr,
472 NULL,
473};
474
475static struct attribute_group hdaps_attribute_group = {
476 .attrs = hdaps_attributes,
477};
478
479
480
481
482
483static int __init hdaps_dmi_match(const struct dmi_system_id *id)
484{
485 pr_info("%s detected\n", id->ident);
486 return 1;
487}
488
489
490static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
491{
492 hdaps_invert = (unsigned long)id->driver_data;
493 pr_info("inverting axis (%u) readings\n", hdaps_invert);
494 return hdaps_dmi_match(id);
495}
496
497#define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) { \
498 .ident = vendor " " model, \
499 .callback = hdaps_dmi_match_invert, \
500 .driver_data = (void *)axes, \
501 .matches = { \
502 DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
503 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
504 } \
505}
506
507#define HDAPS_DMI_MATCH_NORMAL(vendor, model) \
508 HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
509
510
511
512
513
514static struct dmi_system_id __initdata hdaps_whitelist[] = {
515 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
516 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
517 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
518 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
519 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
520 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
521 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
522 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
523 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
524 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
525 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
526 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
527 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
528 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
529 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
530 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
531 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
532 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
533 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
534 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
535 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
536 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
537 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
538 { .ident = NULL }
539};
540
541static int __init hdaps_init(void)
542{
543 struct input_dev *idev;
544 int ret;
545
546 if (!dmi_check_system(hdaps_whitelist)) {
547 pr_warn("supported laptop not found!\n");
548 ret = -ENODEV;
549 goto out;
550 }
551
552 if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
553 ret = -ENXIO;
554 goto out;
555 }
556
557 ret = platform_driver_register(&hdaps_driver);
558 if (ret)
559 goto out_region;
560
561 pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
562 if (IS_ERR(pdev)) {
563 ret = PTR_ERR(pdev);
564 goto out_driver;
565 }
566
567 ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
568 if (ret)
569 goto out_device;
570
571 hdaps_idev = input_allocate_polled_device();
572 if (!hdaps_idev) {
573 ret = -ENOMEM;
574 goto out_group;
575 }
576
577 hdaps_idev->poll = hdaps_mousedev_poll;
578 hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
579
580
581 hdaps_calibrate();
582
583
584 idev = hdaps_idev->input;
585 idev->name = "hdaps";
586 idev->phys = "isa1600/input0";
587 idev->id.bustype = BUS_ISA;
588 idev->dev.parent = &pdev->dev;
589 idev->evbit[0] = BIT_MASK(EV_ABS);
590 input_set_abs_params(idev, ABS_X,
591 -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
592 input_set_abs_params(idev, ABS_Y,
593 -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
594
595 ret = input_register_polled_device(hdaps_idev);
596 if (ret)
597 goto out_idev;
598
599 pr_info("driver successfully loaded\n");
600 return 0;
601
602out_idev:
603 input_free_polled_device(hdaps_idev);
604out_group:
605 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
606out_device:
607 platform_device_unregister(pdev);
608out_driver:
609 platform_driver_unregister(&hdaps_driver);
610out_region:
611 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
612out:
613 pr_warn("driver init failed (ret=%d)!\n", ret);
614 return ret;
615}
616
617static void __exit hdaps_exit(void)
618{
619 input_unregister_polled_device(hdaps_idev);
620 input_free_polled_device(hdaps_idev);
621 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
622 platform_device_unregister(pdev);
623 platform_driver_unregister(&hdaps_driver);
624 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
625
626 pr_info("driver unloaded\n");
627}
628
629module_init(hdaps_init);
630module_exit(hdaps_exit);
631
632module_param_named(invert, hdaps_invert, int, 0);
633MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
634 "2 invert y-axis, 3 invert both axes.");
635
636MODULE_AUTHOR("Robert Love");
637MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
638MODULE_LICENSE("GPL v2");
639