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
308#ifdef CONFIG_PM_SLEEP
309static int hdaps_resume(struct device *dev)
310{
311 return hdaps_device_init();
312}
313#endif
314
315static SIMPLE_DEV_PM_OPS(hdaps_pm, NULL, hdaps_resume);
316
317static struct platform_driver hdaps_driver = {
318 .probe = hdaps_probe,
319 .driver = {
320 .name = "hdaps",
321 .pm = &hdaps_pm,
322 },
323};
324
325
326
327
328static void hdaps_calibrate(void)
329{
330 __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
331}
332
333static void hdaps_mousedev_poll(struct input_polled_dev *dev)
334{
335 struct input_dev *input_dev = dev->input;
336 int x, y;
337
338 mutex_lock(&hdaps_mtx);
339
340 if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
341 goto out;
342
343 input_report_abs(input_dev, ABS_X, x - rest_x);
344 input_report_abs(input_dev, ABS_Y, y - rest_y);
345 input_sync(input_dev);
346
347out:
348 mutex_unlock(&hdaps_mtx);
349}
350
351
352
353
354static ssize_t hdaps_position_show(struct device *dev,
355 struct device_attribute *attr, char *buf)
356{
357 int ret, x, y;
358
359 ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
360 if (ret)
361 return ret;
362
363 return sprintf(buf, "(%d,%d)\n", x, y);
364}
365
366static ssize_t hdaps_variance_show(struct device *dev,
367 struct device_attribute *attr, char *buf)
368{
369 int ret, x, y;
370
371 ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
372 if (ret)
373 return ret;
374
375 return sprintf(buf, "(%d,%d)\n", x, y);
376}
377
378static ssize_t hdaps_temp1_show(struct device *dev,
379 struct device_attribute *attr, char *buf)
380{
381 u8 uninitialized_var(temp);
382 int ret;
383
384 ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
385 if (ret)
386 return ret;
387
388 return sprintf(buf, "%u\n", temp);
389}
390
391static ssize_t hdaps_temp2_show(struct device *dev,
392 struct device_attribute *attr, char *buf)
393{
394 u8 uninitialized_var(temp);
395 int ret;
396
397 ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
398 if (ret)
399 return ret;
400
401 return sprintf(buf, "%u\n", temp);
402}
403
404static ssize_t hdaps_keyboard_activity_show(struct device *dev,
405 struct device_attribute *attr,
406 char *buf)
407{
408 return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
409}
410
411static ssize_t hdaps_mouse_activity_show(struct device *dev,
412 struct device_attribute *attr,
413 char *buf)
414{
415 return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
416}
417
418static ssize_t hdaps_calibrate_show(struct device *dev,
419 struct device_attribute *attr, char *buf)
420{
421 return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
422}
423
424static ssize_t hdaps_calibrate_store(struct device *dev,
425 struct device_attribute *attr,
426 const char *buf, size_t count)
427{
428 mutex_lock(&hdaps_mtx);
429 hdaps_calibrate();
430 mutex_unlock(&hdaps_mtx);
431
432 return count;
433}
434
435static ssize_t hdaps_invert_show(struct device *dev,
436 struct device_attribute *attr, char *buf)
437{
438 return sprintf(buf, "%u\n", hdaps_invert);
439}
440
441static ssize_t hdaps_invert_store(struct device *dev,
442 struct device_attribute *attr,
443 const char *buf, size_t count)
444{
445 int invert;
446
447 if (sscanf(buf, "%d", &invert) != 1 ||
448 invert < 0 || invert > HDAPS_BOTH_AXES)
449 return -EINVAL;
450
451 hdaps_invert = invert;
452 hdaps_calibrate();
453
454 return count;
455}
456
457static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
458static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
459static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
460static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
461static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
462static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
463static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
464static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
465
466static struct attribute *hdaps_attributes[] = {
467 &dev_attr_position.attr,
468 &dev_attr_variance.attr,
469 &dev_attr_temp1.attr,
470 &dev_attr_temp2.attr,
471 &dev_attr_keyboard_activity.attr,
472 &dev_attr_mouse_activity.attr,
473 &dev_attr_calibrate.attr,
474 &dev_attr_invert.attr,
475 NULL,
476};
477
478static struct attribute_group hdaps_attribute_group = {
479 .attrs = hdaps_attributes,
480};
481
482
483
484
485
486static int __init hdaps_dmi_match(const struct dmi_system_id *id)
487{
488 pr_info("%s detected\n", id->ident);
489 return 1;
490}
491
492
493static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
494{
495 hdaps_invert = (unsigned long)id->driver_data;
496 pr_info("inverting axis (%u) readings\n", hdaps_invert);
497 return hdaps_dmi_match(id);
498}
499
500#define HDAPS_DMI_MATCH_INVERT(vendor, model, axes) { \
501 .ident = vendor " " model, \
502 .callback = hdaps_dmi_match_invert, \
503 .driver_data = (void *)axes, \
504 .matches = { \
505 DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
506 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
507 } \
508}
509
510#define HDAPS_DMI_MATCH_NORMAL(vendor, model) \
511 HDAPS_DMI_MATCH_INVERT(vendor, model, 0)
512
513
514
515
516
517static const struct dmi_system_id hdaps_whitelist[] __initconst = {
518 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_BOTH_AXES),
519 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
520 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
521 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
522 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61i", HDAPS_BOTH_AXES),
523 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_BOTH_AXES),
524 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_BOTH_AXES),
525 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
526 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES),
527 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
528 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
529 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES),
530 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES),
531 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES),
532 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES),
533 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
534 HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_Y_AXIS),
535 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_BOTH_AXES),
536 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61s", HDAPS_BOTH_AXES),
537 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_BOTH_AXES),
538 HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
539 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61m", HDAPS_BOTH_AXES),
540 HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad Z61p", HDAPS_BOTH_AXES),
541 { .ident = NULL }
542};
543
544static int __init hdaps_init(void)
545{
546 struct input_dev *idev;
547 int ret;
548
549 if (!dmi_check_system(hdaps_whitelist)) {
550 pr_warn("supported laptop not found!\n");
551 ret = -ENODEV;
552 goto out;
553 }
554
555 if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
556 ret = -ENXIO;
557 goto out;
558 }
559
560 ret = platform_driver_register(&hdaps_driver);
561 if (ret)
562 goto out_region;
563
564 pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
565 if (IS_ERR(pdev)) {
566 ret = PTR_ERR(pdev);
567 goto out_driver;
568 }
569
570 ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
571 if (ret)
572 goto out_device;
573
574 hdaps_idev = input_allocate_polled_device();
575 if (!hdaps_idev) {
576 ret = -ENOMEM;
577 goto out_group;
578 }
579
580 hdaps_idev->poll = hdaps_mousedev_poll;
581 hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
582
583
584 hdaps_calibrate();
585
586
587 idev = hdaps_idev->input;
588 idev->name = "hdaps";
589 idev->phys = "isa1600/input0";
590 idev->id.bustype = BUS_ISA;
591 idev->dev.parent = &pdev->dev;
592 idev->evbit[0] = BIT_MASK(EV_ABS);
593 input_set_abs_params(idev, ABS_X,
594 -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
595 input_set_abs_params(idev, ABS_Y,
596 -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
597
598 ret = input_register_polled_device(hdaps_idev);
599 if (ret)
600 goto out_idev;
601
602 pr_info("driver successfully loaded\n");
603 return 0;
604
605out_idev:
606 input_free_polled_device(hdaps_idev);
607out_group:
608 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
609out_device:
610 platform_device_unregister(pdev);
611out_driver:
612 platform_driver_unregister(&hdaps_driver);
613out_region:
614 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
615out:
616 pr_warn("driver init failed (ret=%d)!\n", ret);
617 return ret;
618}
619
620static void __exit hdaps_exit(void)
621{
622 input_unregister_polled_device(hdaps_idev);
623 input_free_polled_device(hdaps_idev);
624 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
625 platform_device_unregister(pdev);
626 platform_driver_unregister(&hdaps_driver);
627 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
628
629 pr_info("driver unloaded\n");
630}
631
632module_init(hdaps_init);
633module_exit(hdaps_exit);
634
635module_param_named(invert, hdaps_invert, int, 0);
636MODULE_PARM_DESC(invert, "invert data along each axis. 1 invert x-axis, "
637 "2 invert y-axis, 3 invert both axes.");
638
639MODULE_AUTHOR("Robert Love");
640MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
641MODULE_LICENSE("GPL v2");
642