aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Love <rml@novell.com>2005-08-31 23:57:59 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2005-09-09 17:23:07 -0400
commit860e1d6b46bd4cbc67d8d065f0f682143513382f (patch)
tree704018339937594b5242d1e153752e0749fec6b9
parent5dce225bd9ea60e28e17076de63df0dee51b2883 (diff)
[PATCH] updated hdaps driver.
Driver for the IBM Hard Drive Active Protection System (HDAPS), an accelerometer found in most modern ThinkPads. Signed-off-by: Robert Love <rml@novell.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--MAINTAINERS7
-rw-r--r--drivers/hwmon/Kconfig17
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/hdaps.c739
4 files changed, 764 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index eaa46594f021..1dc2a6b0f642 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -951,6 +951,13 @@ L: lm-sensors@lm-sensors.org
951W: http://www.lm-sensors.nu/ 951W: http://www.lm-sensors.nu/
952S: Maintained 952S: Maintained
953 953
954HARD DRIVE ACTIVE PROTECTION SYSTEM (HDAPS) DRIVER
955P: Robert Love
956M: rlove@rlove.org
957M: linux-kernel@vger.kernel.org
958W: http://www.kernel.org/pub/linux/kernel/people/rml/hdaps/
959S: Maintained
960
954HARMONY SOUND DRIVER 961HARMONY SOUND DRIVER
955P: Kyle McMartin 962P: Kyle McMartin
956M: kyle@parisc-linux.org 963M: kyle@parisc-linux.org
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 138dc50270e3..7e72e922b41c 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -411,6 +411,23 @@ config SENSORS_W83627EHF
411 This driver can also be built as a module. If so, the module 411 This driver can also be built as a module. If so, the module
412 will be called w83627ehf. 412 will be called w83627ehf.
413 413
414config SENSORS_HDAPS
415 tristate "IBM Hard Drive Active Protection System (hdaps)"
416 depends on HWMON && INPUT && X86
417 default n
418 help
419 This driver provides support for the IBM Hard Drive Active Protection
420 System (hdaps), which provides an accelerometer and other misc. data.
421 Supported laptops include the IBM ThinkPad T41, T42, T43, and R51.
422 The accelerometer data is readable via sysfs.
423
424 This driver also provides an input class device, allowing the
425 laptop to act as a pinball machine-esque mouse. This is off by
426 default but enabled via sysfs or the module parameter "mousedev".
427
428 Say Y here if you have an applicable laptop and want to experience
429 the awesome power of hdaps.
430
414config HWMON_DEBUG_CHIP 431config HWMON_DEBUG_CHIP
415 bool "Hardware Monitoring Chip debugging messages" 432 bool "Hardware Monitoring Chip debugging messages"
416 depends on HWMON 433 depends on HWMON
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 381f1bf04cc5..f7d6a2f61ee7 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_SENSORS_FSCHER) += fscher.o
22obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o 22obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o
23obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o 23obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
24obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o 24obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
25obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
25obj-$(CONFIG_SENSORS_IT87) += it87.o 26obj-$(CONFIG_SENSORS_IT87) += it87.o
26obj-$(CONFIG_SENSORS_LM63) += lm63.o 27obj-$(CONFIG_SENSORS_LM63) += lm63.o
27obj-$(CONFIG_SENSORS_LM75) += lm75.o 28obj-$(CONFIG_SENSORS_LM75) += lm75.o
diff --git a/drivers/hwmon/hdaps.c b/drivers/hwmon/hdaps.c
new file mode 100644
index 000000000000..eaebfc14c933
--- /dev/null
+++ b/drivers/hwmon/hdaps.c
@@ -0,0 +1,739 @@
1/*
2 * drivers/hwmon/hdaps.c - driver for IBM's Hard Drive Active Protection System
3 *
4 * Copyright (C) 2005 Robert Love <rml@novell.com>
5 * Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.com>
6 *
7 * The HardDisk Active Protection System (hdaps) is present in the IBM ThinkPad
8 * T41, T42, T43, R51, and X40, at least. It provides a basic two-axis
9 * accelerometer and other data, such as the device's temperature.
10 *
11 * Based on the document by Mark A. Smith available at
12 * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
13 * and error.
14 *
15 * This program is free software; you can redistribute it and/or modify it
16 * under the terms of the GNU General Public License v2 as published by the
17 * Free Software Foundation.
18 *
19 * This program is distributed in the hope that it will be useful, but WITHOUT
20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
22 * more details.
23 *
24 * You should have received a copy of the GNU General Public License along with
25 * this program; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27 */
28
29#include <linux/delay.h>
30#include <linux/device.h>
31#include <linux/input.h>
32#include <linux/kernel.h>
33#include <linux/module.h>
34#include <linux/timer.h>
35#include <linux/dmi.h>
36#include <asm/io.h>
37
38#define HDAPS_LOW_PORT 0x1600 /* first port used by hdaps */
39#define HDAPS_NR_PORTS 0x30 /* 0x1600 - 0x162f */
40
41#define STATE_FRESH 0x50 /* accelerometer data is fresh */
42
43#define REFRESH_ASYNC 0x00 /* do asynchronous refresh */
44#define REFRESH_SYNC 0x01 /* do synchronous refresh */
45
46#define HDAPS_PORT_STATE 0x1611 /* device state */
47#define HDAPS_PORT_YPOS 0x1612 /* y-axis position */
48#define HDAPS_PORT_XPOS 0x1614 /* x-axis position */
49#define HDAPS_PORT_TEMP1 0x1616 /* device temperature, in celcius */
50#define HDAPS_PORT_YVAR 0x1617 /* y-axis variance (what is this?) */
51#define HDAPS_PORT_XVAR 0x1619 /* x-axis variance (what is this?) */
52#define HDAPS_PORT_TEMP2 0x161b /* device temperature (again?) */
53#define HDAPS_PORT_UNKNOWN 0x161c /* what is this? */
54#define HDAPS_PORT_KMACT 0x161d /* keyboard or mouse activity */
55
56#define HDAPS_READ_MASK 0xff /* some reads have the low 8 bits set */
57
58#define KEYBD_MASK 0x20 /* set if keyboard activity */
59#define MOUSE_MASK 0x40 /* set if mouse activity */
60#define KEYBD_ISSET(n) (!! (n & KEYBD_MASK)) /* keyboard used? */
61#define MOUSE_ISSET(n) (!! (n & MOUSE_MASK)) /* mouse used? */
62
63#define INIT_TIMEOUT_MSECS 4000 /* wait up to 4s for device init ... */
64#define INIT_WAIT_MSECS 200 /* ... in 200ms increments */
65
66static struct platform_device *pdev;
67static struct input_dev hdaps_idev;
68static struct timer_list hdaps_timer;
69static unsigned int hdaps_mousedev_threshold = 4;
70static unsigned long hdaps_poll_ms = 50;
71static unsigned int hdaps_mousedev;
72static unsigned int hdaps_invert;
73static u8 km_activity;
74static int rest_x;
75static int rest_y;
76
77static DECLARE_MUTEX(hdaps_sem);
78
79/*
80 * __get_latch - Get the value from a given port. Callers must hold hdaps_sem.
81 */
82static inline u8 __get_latch(u16 port)
83{
84 return inb(port) & HDAPS_READ_MASK;
85}
86
87/*
88 * __check_latch - Check a port latch for a given value. Callers must hold
89 * hdaps_sem. Returns zero if the port contains the given value.
90 */
91static inline unsigned int __check_latch(u16 port, u8 val)
92{
93 if (__get_latch(port) == val)
94 return 0;
95 return -EINVAL;
96}
97
98/*
99 * __wait_latch - Wait up to 100us for a port latch to get a certain value,
100 * returning zero if the value is obtained. Callers must hold hdaps_sem.
101 */
102static unsigned int __wait_latch(u16 port, u8 val)
103{
104 unsigned int i;
105
106 for (i = 0; i < 20; i++) {
107 if (!__check_latch(port, val))
108 return 0;
109 udelay(5);
110 }
111
112 return -EINVAL;
113}
114
115/*
116 * __device_refresh - Request a refresh from the accelerometer.
117 *
118 * If sync is REFRESH_SYNC, we perform a synchronous refresh and will wait.
119 * Returns zero if successful and nonzero on error.
120 *
121 * If sync is REFRESH_ASYNC, we merely kick off a new refresh if the device is
122 * not up-to-date. Always returns zero.
123 *
124 * Callers must hold hdaps_sem.
125 */
126static int __device_refresh(unsigned int sync)
127{
128 u8 state;
129
130 udelay(100);
131
132 state = inb(0x1604);
133 if (state == STATE_FRESH)
134 return 0;
135
136 outb(0x11, 0x1610);
137 outb(0x01, 0x161f);
138 if (sync == REFRESH_ASYNC)
139 return 0;
140
141 return __wait_latch(0x1604, STATE_FRESH);
142}
143
144/*
145 * __device_complete - Indicate to the accelerometer that we are done reading
146 * data, and then initiate an async refresh. Callers must hold hdaps_sem.
147 */
148static inline void __device_complete(void)
149{
150 inb(0x161f);
151 inb(0x1604);
152 __device_refresh(REFRESH_ASYNC);
153}
154
155static int __hdaps_readb_one(unsigned int port, u8 *val)
156{
157 /* do a sync refresh -- we need to be sure that we read fresh data */
158 if (__device_refresh(REFRESH_SYNC))
159 return -EIO;
160
161 *val = inb(port);
162 __device_complete();
163
164 return 0;
165}
166
167/*
168 * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
169 * the given pointer. Returns zero on success or a negative error on failure.
170 * Can sleep.
171 */
172static int hdaps_readb_one(unsigned int port, u8 *val)
173{
174 int ret;
175
176 down(&hdaps_sem);
177 ret = __hdaps_readb_one(port, val);
178 up(&hdaps_sem);
179
180 return ret;
181}
182
183static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
184 int *x, int *y)
185{
186 /* do a sync refresh -- we need to be sure that we read fresh data */
187 if (__device_refresh(REFRESH_SYNC))
188 return -EIO;
189
190 *y = inw(port2);
191 *x = inw(port1);
192 km_activity = inb(HDAPS_PORT_KMACT);
193 __device_complete();
194
195 /* if hdaps_invert is set, negate the two values */
196 if (hdaps_invert) {
197 *x = -*x;
198 *y = -*y;
199 }
200
201 return 0;
202}
203
204/*
205 * hdaps_read_pair - reads the values from a pair of ports, placing the values
206 * in the given pointers. Returns zero on success. Can sleep.
207 */
208static int hdaps_read_pair(unsigned int port1, unsigned int port2,
209 int *val1, int *val2)
210{
211 int ret;
212
213 down(&hdaps_sem);
214 ret = __hdaps_read_pair(port1, port2, val1, val2);
215 up(&hdaps_sem);
216
217 return ret;
218}
219
220/* initialize the accelerometer */
221static int hdaps_device_init(void)
222{
223 unsigned int total_msecs = INIT_TIMEOUT_MSECS;
224 int ret = -ENXIO;
225
226 down(&hdaps_sem);
227
228 outb(0x13, 0x1610);
229 outb(0x01, 0x161f);
230 if (__wait_latch(0x161f, 0x00))
231 goto out;
232
233 /*
234 * The 0x03 value appears to only work on some thinkpads, such as the
235 * T42p. Others return 0x01.
236 *
237 * The 0x02 value occurs when the chip has been previously initialized.
238 */
239 if (__check_latch(0x1611, 0x03) &&
240 __check_latch(0x1611, 0x02) &&
241 __check_latch(0x1611, 0x01))
242 goto out;
243
244 printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x).\n",
245 __get_latch(0x1611));
246
247 outb(0x17, 0x1610);
248 outb(0x81, 0x1611);
249 outb(0x01, 0x161f);
250 if (__wait_latch(0x161f, 0x00))
251 goto out;
252 if (__wait_latch(0x1611, 0x00))
253 goto out;
254 if (__wait_latch(0x1612, 0x60))
255 goto out;
256 if (__wait_latch(0x1613, 0x00))
257 goto out;
258 outb(0x14, 0x1610);
259 outb(0x01, 0x1611);
260 outb(0x01, 0x161f);
261 if (__wait_latch(0x161f, 0x00))
262 goto out;
263 outb(0x10, 0x1610);
264 outb(0xc8, 0x1611);
265 outb(0x00, 0x1612);
266 outb(0x02, 0x1613);
267 outb(0x01, 0x161f);
268 if (__wait_latch(0x161f, 0x00))
269 goto out;
270 if (__device_refresh(REFRESH_SYNC))
271 goto out;
272 if (__wait_latch(0x1611, 0x00))
273 goto out;
274
275 /* we have done our dance, now let's wait for the applause */
276 while (total_msecs > 0) {
277 u8 ignored;
278
279 /* a read of the device helps push it into action */
280 __hdaps_readb_one(HDAPS_PORT_UNKNOWN, &ignored);
281 if (!__wait_latch(0x1611, 0x02)) {
282 ret = 0;
283 break;
284 }
285
286 msleep(INIT_WAIT_MSECS);
287 total_msecs -= INIT_WAIT_MSECS;
288 }
289
290out:
291 up(&hdaps_sem);
292 return ret;
293}
294
295
296/* Input class stuff */
297
298/*
299 * hdaps_calibrate - Zero out our "resting" values. Callers must hold hdaps_sem.
300 */
301static void hdaps_calibrate(void)
302{
303 int x, y;
304
305 if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
306 return;
307
308 rest_x = x;
309 rest_y = y;
310}
311
312static void hdaps_mousedev_poll(unsigned long unused)
313{
314 int x, y;
315
316 /* Cannot sleep. Try nonblockingly. If we fail, try again later. */
317 if (down_trylock(&hdaps_sem)) {
318 mod_timer(&hdaps_timer,jiffies+msecs_to_jiffies(hdaps_poll_ms));
319 return;
320 }
321
322 if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
323 goto out;
324
325 x -= rest_x;
326 y -= rest_y;
327 if (abs(x) > hdaps_mousedev_threshold)
328 input_report_rel(&hdaps_idev, REL_X, x);
329 if (abs(y) > hdaps_mousedev_threshold)
330 input_report_rel(&hdaps_idev, REL_Y, y);
331 input_sync(&hdaps_idev);
332
333 mod_timer(&hdaps_timer, jiffies + msecs_to_jiffies(hdaps_poll_ms));
334
335out:
336 up(&hdaps_sem);
337}
338
339/*
340 * hdaps_mousedev_enable - enable the input class device. Can sleep.
341 */
342static void hdaps_mousedev_enable(void)
343{
344 down(&hdaps_sem);
345
346 /* calibrate the device before enabling */
347 hdaps_calibrate();
348
349 /* initialize the input class */
350 init_input_dev(&hdaps_idev);
351 hdaps_idev.dev = &pdev->dev;
352 hdaps_idev.evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
353 hdaps_idev.relbit[0] = BIT(REL_X) | BIT(REL_Y);
354 hdaps_idev.keybit[LONG(BTN_LEFT)] = BIT(BTN_LEFT);
355 input_register_device(&hdaps_idev);
356
357 /* start up our timer */
358 init_timer(&hdaps_timer);
359 hdaps_timer.function = hdaps_mousedev_poll;
360 hdaps_timer.expires = jiffies + msecs_to_jiffies(hdaps_poll_ms);
361 add_timer(&hdaps_timer);
362
363 hdaps_mousedev = 1;
364
365 up(&hdaps_sem);
366
367 printk(KERN_INFO "hdaps: input device enabled.\n");
368}
369
370/*
371 * hdaps_mousedev_disable - disable the input class device. Caller must hold
372 * hdaps_sem.
373 */
374static void hdaps_mousedev_disable(void)
375{
376 down(&hdaps_sem);
377 if (hdaps_mousedev) {
378 hdaps_mousedev = 0;
379 del_timer_sync(&hdaps_timer);
380 input_unregister_device(&hdaps_idev);
381 }
382 up(&hdaps_sem);
383}
384
385
386/* Device model stuff */
387
388static int hdaps_probe(struct device *dev)
389{
390 int ret;
391
392 ret = hdaps_device_init();
393 if (ret)
394 return ret;
395
396 printk(KERN_INFO "hdaps: device successfully initialized.\n");
397 return 0;
398}
399
400static int hdaps_resume(struct device *dev, u32 level)
401{
402 if (level == RESUME_ENABLE)
403 return hdaps_device_init();
404 return 0;
405}
406
407static struct device_driver hdaps_driver = {
408 .name = "hdaps",
409 .bus = &platform_bus_type,
410 .owner = THIS_MODULE,
411 .probe = hdaps_probe,
412 .resume = hdaps_resume
413};
414
415
416/* Sysfs Files */
417
418static ssize_t hdaps_position_show(struct device *dev,
419 struct device_attribute *attr, char *buf)
420{
421 int ret, x, y;
422
423 ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
424 if (ret)
425 return ret;
426
427 return sprintf(buf, "(%d,%d)\n", x, y);
428}
429
430static ssize_t hdaps_variance_show(struct device *dev,
431 struct device_attribute *attr, char *buf)
432{
433 int ret, x, y;
434
435 ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
436 if (ret)
437 return ret;
438
439 return sprintf(buf, "(%d,%d)\n", x, y);
440}
441
442static ssize_t hdaps_temp1_show(struct device *dev,
443 struct device_attribute *attr, char *buf)
444{
445 u8 temp;
446 int ret;
447
448 ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
449 if (ret < 0)
450 return ret;
451
452 return sprintf(buf, "%u\n", temp);
453}
454
455static ssize_t hdaps_temp2_show(struct device *dev,
456 struct device_attribute *attr, char *buf)
457{
458 u8 temp;
459 int ret;
460
461 ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
462 if (ret < 0)
463 return ret;
464
465 return sprintf(buf, "%u\n", temp);
466}
467
468static ssize_t hdaps_keyboard_activity_show(struct device *dev,
469 struct device_attribute *attr,
470 char *buf)
471{
472 return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
473}
474
475static ssize_t hdaps_mouse_activity_show(struct device *dev,
476 struct device_attribute *attr,
477 char *buf)
478{
479 return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
480}
481
482static ssize_t hdaps_calibrate_show(struct device *dev,
483 struct device_attribute *attr, char *buf)
484{
485 return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
486}
487
488static ssize_t hdaps_calibrate_store(struct device *dev,
489 struct device_attribute *attr,
490 const char *buf, size_t count)
491{
492 down(&hdaps_sem);
493 hdaps_calibrate();
494 up(&hdaps_sem);
495
496 return count;
497}
498
499static ssize_t hdaps_invert_show(struct device *dev,
500 struct device_attribute *attr, char *buf)
501{
502 return sprintf(buf, "%u\n", hdaps_invert);
503}
504
505static ssize_t hdaps_invert_store(struct device *dev,
506 struct device_attribute *attr,
507 const char *buf, size_t count)
508{
509 int invert;
510
511 if (sscanf(buf, "%d", &invert) != 1 || (invert != 1 && invert != 0))
512 return -EINVAL;
513
514 hdaps_invert = invert;
515 hdaps_calibrate();
516
517 return count;
518}
519
520static ssize_t hdaps_mousedev_show(struct device *dev,
521 struct device_attribute *attr, char *buf)
522{
523 return sprintf(buf, "%d\n", hdaps_mousedev);
524}
525
526static ssize_t hdaps_mousedev_store(struct device *dev,
527 struct device_attribute *attr,
528 const char *buf, size_t count)
529{
530 int enable;
531
532 if (sscanf(buf, "%d", &enable) != 1)
533 return -EINVAL;
534
535 if (enable == 1)
536 hdaps_mousedev_enable();
537 else if (enable == 0)
538 hdaps_mousedev_disable();
539 else
540 return -EINVAL;
541
542 return count;
543}
544
545static ssize_t hdaps_poll_show(struct device *dev,
546 struct device_attribute *attr, char *buf)
547{
548 return sprintf(buf, "%lu\n", hdaps_poll_ms);
549}
550
551static ssize_t hdaps_poll_store(struct device *dev,
552 struct device_attribute *attr,
553 const char *buf, size_t count)
554{
555 unsigned int poll;
556
557 if (sscanf(buf, "%u", &poll) != 1 || poll == 0)
558 return -EINVAL;
559 hdaps_poll_ms = poll;
560
561 return count;
562}
563
564static ssize_t hdaps_threshold_show(struct device *dev,
565 struct device_attribute *attr, char *buf)
566{
567 return sprintf(buf, "%u\n", hdaps_mousedev_threshold);
568}
569
570static ssize_t hdaps_threshold_store(struct device *dev,
571 struct device_attribute *attr,
572 const char *buf, size_t count)
573{
574 unsigned int threshold;
575
576 if (sscanf(buf, "%u", &threshold) != 1 || threshold == 0)
577 return -EINVAL;
578 hdaps_mousedev_threshold = threshold;
579
580 return count;
581}
582
583static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
584static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
585static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
586static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
587static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
588static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
589static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
590static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
591static DEVICE_ATTR(mousedev, 0644, hdaps_mousedev_show, hdaps_mousedev_store);
592static DEVICE_ATTR(mousedev_poll_ms, 0644, hdaps_poll_show, hdaps_poll_store);
593static DEVICE_ATTR(mousedev_threshold, 0644, hdaps_threshold_show,
594 hdaps_threshold_store);
595
596static struct attribute *hdaps_attributes[] = {
597 &dev_attr_position.attr,
598 &dev_attr_variance.attr,
599 &dev_attr_temp1.attr,
600 &dev_attr_temp2.attr,
601 &dev_attr_keyboard_activity.attr,
602 &dev_attr_mouse_activity.attr,
603 &dev_attr_calibrate.attr,
604 &dev_attr_mousedev.attr,
605 &dev_attr_mousedev_threshold.attr,
606 &dev_attr_mousedev_poll_ms.attr,
607 &dev_attr_invert.attr,
608 NULL,
609};
610
611static struct attribute_group hdaps_attribute_group = {
612 .attrs = hdaps_attributes,
613};
614
615
616/* Module stuff */
617
618/*
619 * XXX: We should be able to return nonzero and halt the detection process.
620 * But there is a bug in dmi_check_system() where a nonzero return from the
621 * first match will result in a return of failure from dmi_check_system().
622 * I fixed this; the patch is in 2.6-mm. Once in Linus's tree we can make
623 * hdaps_dmi_match_invert() return hdaps_dmi_match(), which in turn returns 1.
624 */
625static int hdaps_dmi_match(struct dmi_system_id *id)
626{
627 printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
628 return 0;
629}
630
631static int hdaps_dmi_match_invert(struct dmi_system_id *id)
632{
633 hdaps_invert = 1;
634 printk(KERN_INFO "hdaps: inverting axis readings.\n");
635 return 0;
636}
637
638#define HDAPS_DMI_MATCH_NORMAL(model) { \
639 .ident = "IBM " model, \
640 .callback = hdaps_dmi_match, \
641 .matches = { \
642 DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
643 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
644 } \
645}
646
647#define HDAPS_DMI_MATCH_INVERT(model) { \
648 .ident = "IBM " model, \
649 .callback = hdaps_dmi_match_invert, \
650 .matches = { \
651 DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
652 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
653 } \
654}
655
656static int __init hdaps_init(void)
657{
658 int ret;
659
660 /* Note that DMI_MATCH(...,"ThinkPad T42") will match "ThinkPad T42p" */
661 struct dmi_system_id hdaps_whitelist[] = {
662 HDAPS_DMI_MATCH_INVERT("ThinkPad R50p"),
663 HDAPS_DMI_MATCH_NORMAL("ThinkPad R50"),
664 HDAPS_DMI_MATCH_NORMAL("ThinkPad R51"),
665 HDAPS_DMI_MATCH_INVERT("ThinkPad T41p"),
666 HDAPS_DMI_MATCH_NORMAL("ThinkPad T41"),
667 HDAPS_DMI_MATCH_INVERT("ThinkPad T42p"),
668 HDAPS_DMI_MATCH_NORMAL("ThinkPad T42"),
669 HDAPS_DMI_MATCH_NORMAL("ThinkPad T43"),
670 HDAPS_DMI_MATCH_NORMAL("ThinkPad X40"),
671 { .ident = NULL }
672 };
673
674 if (!dmi_check_system(hdaps_whitelist)) {
675 printk(KERN_WARNING "hdaps: supported laptop not found!\n");
676 ret = -ENXIO;
677 goto out;
678 }
679
680 if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
681 ret = -ENXIO;
682 goto out;
683 }
684
685 ret = driver_register(&hdaps_driver);
686 if (ret)
687 goto out_region;
688
689 pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
690 if (IS_ERR(pdev)) {
691 ret = PTR_ERR(pdev);
692 goto out_driver;
693 }
694
695 ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
696 if (ret)
697 goto out_device;
698
699 if (hdaps_mousedev)
700 hdaps_mousedev_enable();
701
702 printk(KERN_INFO "hdaps: driver successfully loaded.\n");
703 return 0;
704
705out_device:
706 platform_device_unregister(pdev);
707out_driver:
708 driver_unregister(&hdaps_driver);
709out_region:
710 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
711out:
712 printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
713 return ret;
714}
715
716static void __exit hdaps_exit(void)
717{
718 hdaps_mousedev_disable();
719
720 sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
721 platform_device_unregister(pdev);
722 driver_unregister(&hdaps_driver);
723 release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
724
725 printk(KERN_INFO "hdaps: driver unloaded.\n");
726}
727
728module_init(hdaps_init);
729module_exit(hdaps_exit);
730
731module_param_named(mousedev, hdaps_mousedev, bool, 0);
732MODULE_PARM_DESC(mousedev, "enable the input class device");
733
734module_param_named(invert, hdaps_invert, bool, 0);
735MODULE_PARM_DESC(invert, "invert data along each axis");
736
737MODULE_AUTHOR("Robert Love");
738MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
739MODULE_LICENSE("GPL v2");