2 * drivers/hwmon/hdaps.c - driver for IBM's Hard Drive Active Protection System
4 * Copyright (C) 2005 Robert Love <rml@novell.com>
5 * Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.com>
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.
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
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.
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
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
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>
38 #define HDAPS_LOW_PORT 0x1600 /* first port used by hdaps */
39 #define HDAPS_NR_PORTS 0x30 /* 0x1600 - 0x162f */
41 #define STATE_FRESH 0x50 /* accelerometer data is fresh */
43 #define REFRESH_ASYNC 0x00 /* do asynchronous refresh */
44 #define REFRESH_SYNC 0x01 /* do synchronous refresh */
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 */
56 #define HDAPS_READ_MASK 0xff /* some reads have the low 8 bits set */
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? */
63 #define INIT_TIMEOUT_MSECS 4000 /* wait up to 4s for device init ... */
64 #define INIT_WAIT_MSECS 200 /* ... in 200ms increments */
66 static struct platform_device
*pdev
;
67 static struct input_dev hdaps_idev
;
68 static struct timer_list hdaps_timer
;
69 static unsigned int hdaps_mousedev_threshold
= 4;
70 static unsigned long hdaps_poll_ms
= 50;
71 static unsigned int hdaps_mousedev
;
72 static unsigned int hdaps_invert
;
73 static u8 km_activity
;
77 static DECLARE_MUTEX(hdaps_sem
);
80 * __get_latch - Get the value from a given port. Callers must hold hdaps_sem.
82 static inline u8
__get_latch(u16 port
)
84 return inb(port
) & HDAPS_READ_MASK
;
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.
91 static inline unsigned int __check_latch(u16 port
, u8 val
)
93 if (__get_latch(port
) == val
)
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.
102 static unsigned int __wait_latch(u16 port
, u8 val
)
106 for (i
= 0; i
< 20; i
++) {
107 if (!__check_latch(port
, val
))
116 * __device_refresh - Request a refresh from the accelerometer.
118 * If sync is REFRESH_SYNC, we perform a synchronous refresh and will wait.
119 * Returns zero if successful and nonzero on error.
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.
124 * Callers must hold hdaps_sem.
126 static int __device_refresh(unsigned int sync
)
133 if (state
== STATE_FRESH
)
138 if (sync
== REFRESH_ASYNC
)
141 return __wait_latch(0x1604, STATE_FRESH
);
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.
148 static inline void __device_complete(void)
152 __device_refresh(REFRESH_ASYNC
);
155 static int __hdaps_readb_one(unsigned int port
, u8
*val
)
157 /* do a sync refresh -- we need to be sure that we read fresh data */
158 if (__device_refresh(REFRESH_SYNC
))
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.
172 static int hdaps_readb_one(unsigned int port
, u8
*val
)
177 ret
= __hdaps_readb_one(port
, val
);
183 static int __hdaps_read_pair(unsigned int port1
, unsigned int port2
,
186 /* do a sync refresh -- we need to be sure that we read fresh data */
187 if (__device_refresh(REFRESH_SYNC
))
192 km_activity
= inb(HDAPS_PORT_KMACT
);
195 /* if hdaps_invert is set, negate the two values */
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.
208 static int hdaps_read_pair(unsigned int port1
, unsigned int port2
,
209 int *val1
, int *val2
)
214 ret
= __hdaps_read_pair(port1
, port2
, val1
, val2
);
220 /* initialize the accelerometer */
221 static int hdaps_device_init(void)
223 unsigned int total_msecs
= INIT_TIMEOUT_MSECS
;
230 if (__wait_latch(0x161f, 0x00))
234 * The 0x03 value appears to only work on some thinkpads, such as the
235 * T42p. Others return 0x01.
237 * The 0x02 value occurs when the chip has been previously initialized.
239 if (__check_latch(0x1611, 0x03) &&
240 __check_latch(0x1611, 0x02) &&
241 __check_latch(0x1611, 0x01))
244 printk(KERN_DEBUG
"hdaps: initial latch check good (0x%02x).\n",
245 __get_latch(0x1611));
250 if (__wait_latch(0x161f, 0x00))
252 if (__wait_latch(0x1611, 0x00))
254 if (__wait_latch(0x1612, 0x60))
256 if (__wait_latch(0x1613, 0x00))
261 if (__wait_latch(0x161f, 0x00))
268 if (__wait_latch(0x161f, 0x00))
270 if (__device_refresh(REFRESH_SYNC
))
272 if (__wait_latch(0x1611, 0x00))
275 /* we have done our dance, now let's wait for the applause */
276 while (total_msecs
> 0) {
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)) {
286 msleep(INIT_WAIT_MSECS
);
287 total_msecs
-= INIT_WAIT_MSECS
;
296 /* Input class stuff */
299 * hdaps_calibrate - Zero out our "resting" values. Callers must hold hdaps_sem.
301 static void hdaps_calibrate(void)
305 if (__hdaps_read_pair(HDAPS_PORT_XPOS
, HDAPS_PORT_YPOS
, &x
, &y
))
312 static void hdaps_mousedev_poll(unsigned long unused
)
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
));
322 if (__hdaps_read_pair(HDAPS_PORT_XPOS
, HDAPS_PORT_YPOS
, &x
, &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
);
333 mod_timer(&hdaps_timer
, jiffies
+ msecs_to_jiffies(hdaps_poll_ms
));
340 * hdaps_mousedev_enable - enable the input class device. Can sleep.
342 static void hdaps_mousedev_enable(void)
346 /* calibrate the device before enabling */
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
);
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
);
367 printk(KERN_INFO
"hdaps: input device enabled.\n");
371 * hdaps_mousedev_disable - disable the input class device. Caller must hold
374 static void hdaps_mousedev_disable(void)
377 if (hdaps_mousedev
) {
379 del_timer_sync(&hdaps_timer
);
380 input_unregister_device(&hdaps_idev
);
386 /* Device model stuff */
388 static int hdaps_probe(struct device
*dev
)
392 ret
= hdaps_device_init();
396 printk(KERN_INFO
"hdaps: device successfully initialized.\n");
400 static int hdaps_resume(struct device
*dev
, u32 level
)
402 if (level
== RESUME_ENABLE
)
403 return hdaps_device_init();
407 static struct device_driver hdaps_driver
= {
409 .bus
= &platform_bus_type
,
410 .owner
= THIS_MODULE
,
411 .probe
= hdaps_probe
,
412 .resume
= hdaps_resume
418 static ssize_t
hdaps_position_show(struct device
*dev
,
419 struct device_attribute
*attr
, char *buf
)
423 ret
= hdaps_read_pair(HDAPS_PORT_XPOS
, HDAPS_PORT_YPOS
, &x
, &y
);
427 return sprintf(buf
, "(%d,%d)\n", x
, y
);
430 static ssize_t
hdaps_variance_show(struct device
*dev
,
431 struct device_attribute
*attr
, char *buf
)
435 ret
= hdaps_read_pair(HDAPS_PORT_XVAR
, HDAPS_PORT_YVAR
, &x
, &y
);
439 return sprintf(buf
, "(%d,%d)\n", x
, y
);
442 static ssize_t
hdaps_temp1_show(struct device
*dev
,
443 struct device_attribute
*attr
, char *buf
)
448 ret
= hdaps_readb_one(HDAPS_PORT_TEMP1
, &temp
);
452 return sprintf(buf
, "%u\n", temp
);
455 static ssize_t
hdaps_temp2_show(struct device
*dev
,
456 struct device_attribute
*attr
, char *buf
)
461 ret
= hdaps_readb_one(HDAPS_PORT_TEMP2
, &temp
);
465 return sprintf(buf
, "%u\n", temp
);
468 static ssize_t
hdaps_keyboard_activity_show(struct device
*dev
,
469 struct device_attribute
*attr
,
472 return sprintf(buf
, "%u\n", KEYBD_ISSET(km_activity
));
475 static ssize_t
hdaps_mouse_activity_show(struct device
*dev
,
476 struct device_attribute
*attr
,
479 return sprintf(buf
, "%u\n", MOUSE_ISSET(km_activity
));
482 static ssize_t
hdaps_calibrate_show(struct device
*dev
,
483 struct device_attribute
*attr
, char *buf
)
485 return sprintf(buf
, "(%d,%d)\n", rest_x
, rest_y
);
488 static ssize_t
hdaps_calibrate_store(struct device
*dev
,
489 struct device_attribute
*attr
,
490 const char *buf
, size_t count
)
499 static ssize_t
hdaps_invert_show(struct device
*dev
,
500 struct device_attribute
*attr
, char *buf
)
502 return sprintf(buf
, "%u\n", hdaps_invert
);
505 static ssize_t
hdaps_invert_store(struct device
*dev
,
506 struct device_attribute
*attr
,
507 const char *buf
, size_t count
)
511 if (sscanf(buf
, "%d", &invert
) != 1 || (invert
!= 1 && invert
!= 0))
514 hdaps_invert
= invert
;
520 static ssize_t
hdaps_mousedev_show(struct device
*dev
,
521 struct device_attribute
*attr
, char *buf
)
523 return sprintf(buf
, "%d\n", hdaps_mousedev
);
526 static ssize_t
hdaps_mousedev_store(struct device
*dev
,
527 struct device_attribute
*attr
,
528 const char *buf
, size_t count
)
532 if (sscanf(buf
, "%d", &enable
) != 1)
536 hdaps_mousedev_enable();
537 else if (enable
== 0)
538 hdaps_mousedev_disable();
545 static ssize_t
hdaps_poll_show(struct device
*dev
,
546 struct device_attribute
*attr
, char *buf
)
548 return sprintf(buf
, "%lu\n", hdaps_poll_ms
);
551 static ssize_t
hdaps_poll_store(struct device
*dev
,
552 struct device_attribute
*attr
,
553 const char *buf
, size_t count
)
557 if (sscanf(buf
, "%u", &poll
) != 1 || poll
== 0)
559 hdaps_poll_ms
= poll
;
564 static ssize_t
hdaps_threshold_show(struct device
*dev
,
565 struct device_attribute
*attr
, char *buf
)
567 return sprintf(buf
, "%u\n", hdaps_mousedev_threshold
);
570 static ssize_t
hdaps_threshold_store(struct device
*dev
,
571 struct device_attribute
*attr
,
572 const char *buf
, size_t count
)
574 unsigned int threshold
;
576 if (sscanf(buf
, "%u", &threshold
) != 1 || threshold
== 0)
578 hdaps_mousedev_threshold
= threshold
;
583 static DEVICE_ATTR(position
, 0444, hdaps_position_show
, NULL
);
584 static DEVICE_ATTR(variance
, 0444, hdaps_variance_show
, NULL
);
585 static DEVICE_ATTR(temp1
, 0444, hdaps_temp1_show
, NULL
);
586 static DEVICE_ATTR(temp2
, 0444, hdaps_temp2_show
, NULL
);
587 static DEVICE_ATTR(keyboard_activity
, 0444, hdaps_keyboard_activity_show
, NULL
);
588 static DEVICE_ATTR(mouse_activity
, 0444, hdaps_mouse_activity_show
, NULL
);
589 static DEVICE_ATTR(calibrate
, 0644, hdaps_calibrate_show
,hdaps_calibrate_store
);
590 static DEVICE_ATTR(invert
, 0644, hdaps_invert_show
, hdaps_invert_store
);
591 static DEVICE_ATTR(mousedev
, 0644, hdaps_mousedev_show
, hdaps_mousedev_store
);
592 static DEVICE_ATTR(mousedev_poll_ms
, 0644, hdaps_poll_show
, hdaps_poll_store
);
593 static DEVICE_ATTR(mousedev_threshold
, 0644, hdaps_threshold_show
,
594 hdaps_threshold_store
);
596 static 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
,
611 static struct attribute_group hdaps_attribute_group
= {
612 .attrs
= hdaps_attributes
,
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.
625 static int hdaps_dmi_match(struct dmi_system_id
*id
)
627 printk(KERN_INFO
"hdaps: %s detected.\n", id
->ident
);
631 static int hdaps_dmi_match_invert(struct dmi_system_id
*id
)
634 printk(KERN_INFO
"hdaps: inverting axis readings.\n");
638 #define HDAPS_DMI_MATCH_NORMAL(model) { \
639 .ident = "IBM " model, \
640 .callback = hdaps_dmi_match, \
642 DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
643 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
647 #define HDAPS_DMI_MATCH_INVERT(model) { \
648 .ident = "IBM " model, \
649 .callback = hdaps_dmi_match_invert, \
651 DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
652 DMI_MATCH(DMI_PRODUCT_VERSION, model) \
656 static int __init
hdaps_init(void)
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"),
674 if (!dmi_check_system(hdaps_whitelist
)) {
675 printk(KERN_WARNING
"hdaps: supported laptop not found!\n");
680 if (!request_region(HDAPS_LOW_PORT
, HDAPS_NR_PORTS
, "hdaps")) {
685 ret
= driver_register(&hdaps_driver
);
689 pdev
= platform_device_register_simple("hdaps", -1, NULL
, 0);
695 ret
= sysfs_create_group(&pdev
->dev
.kobj
, &hdaps_attribute_group
);
700 hdaps_mousedev_enable();
702 printk(KERN_INFO
"hdaps: driver successfully loaded.\n");
706 platform_device_unregister(pdev
);
708 driver_unregister(&hdaps_driver
);
710 release_region(HDAPS_LOW_PORT
, HDAPS_NR_PORTS
);
712 printk(KERN_WARNING
"hdaps: driver init failed (ret=%d)!\n", ret
);
716 static void __exit
hdaps_exit(void)
718 hdaps_mousedev_disable();
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
);
725 printk(KERN_INFO
"hdaps: driver unloaded.\n");
728 module_init(hdaps_init
);
729 module_exit(hdaps_exit
);
731 module_param_named(mousedev
, hdaps_mousedev
, bool, 0);
732 MODULE_PARM_DESC(mousedev
, "enable the input class device");
734 module_param_named(invert
, hdaps_invert
, bool, 0);
735 MODULE_PARM_DESC(invert
, "invert data along each axis");
737 MODULE_AUTHOR("Robert Love");
738 MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
739 MODULE_LICENSE("GPL v2");