Merge branch 'for-linus' of git://brick.kernel.dk/data/git/linux-2.6-block
[linux-2.6] / drivers / hwmon / hdaps.c
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 IBM ThinkPads
8  * starting with the R40, T41, and X40.  It provides a basic two-axis
9  * accelerometer and other data, such as the device's temperature.
10  *
11  * This driver is 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/platform_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 <linux/jiffies.h>
37 #include <asm/io.h>
38
39 #define HDAPS_LOW_PORT          0x1600  /* first port used by hdaps */
40 #define HDAPS_NR_PORTS          0x30    /* number of ports: 0x1600 - 0x162f */
41
42 #define HDAPS_PORT_STATE        0x1611  /* device state */
43 #define HDAPS_PORT_YPOS         0x1612  /* y-axis position */
44 #define HDAPS_PORT_XPOS         0x1614  /* x-axis position */
45 #define HDAPS_PORT_TEMP1        0x1616  /* device temperature, in Celsius */
46 #define HDAPS_PORT_YVAR         0x1617  /* y-axis variance (what is this?) */
47 #define HDAPS_PORT_XVAR         0x1619  /* x-axis variance (what is this?) */
48 #define HDAPS_PORT_TEMP2        0x161b  /* device temperature (again?) */
49 #define HDAPS_PORT_UNKNOWN      0x161c  /* what is this? */
50 #define HDAPS_PORT_KMACT        0x161d  /* keyboard or mouse activity */
51
52 #define STATE_FRESH             0x50    /* accelerometer data is fresh */
53
54 #define KEYBD_MASK              0x20    /* set if keyboard activity */
55 #define MOUSE_MASK              0x40    /* set if mouse activity */
56 #define KEYBD_ISSET(n)          (!! (n & KEYBD_MASK))   /* keyboard used? */
57 #define MOUSE_ISSET(n)          (!! (n & MOUSE_MASK))   /* mouse used? */
58
59 #define INIT_TIMEOUT_MSECS      4000    /* wait up to 4s for device init ... */
60 #define INIT_WAIT_MSECS         200     /* ... in 200ms increments */
61
62 #define HDAPS_POLL_PERIOD       (HZ/20) /* poll for input every 1/20s */
63 #define HDAPS_INPUT_FUZZ        4       /* input event threshold */
64 #define HDAPS_INPUT_FLAT        4
65
66 static struct timer_list hdaps_timer;
67 static struct platform_device *pdev;
68 static struct input_dev *hdaps_idev;
69 static unsigned int hdaps_invert;
70 static u8 km_activity;
71 static int rest_x;
72 static int rest_y;
73
74 static DECLARE_MUTEX(hdaps_sem);
75
76 /*
77  * __get_latch - Get the value from a given port.  Callers must hold hdaps_sem.
78  */
79 static inline u8 __get_latch(u16 port)
80 {
81         return inb(port) & 0xff;
82 }
83
84 /*
85  * __check_latch - Check a port latch for a given value.  Returns zero if the
86  * port contains the given value.  Callers must hold hdaps_sem.
87  */
88 static inline int __check_latch(u16 port, u8 val)
89 {
90         if (__get_latch(port) == val)
91                 return 0;
92         return -EINVAL;
93 }
94
95 /*
96  * __wait_latch - Wait up to 100us for a port latch to get a certain value,
97  * returning zero if the value is obtained.  Callers must hold hdaps_sem.
98  */
99 static int __wait_latch(u16 port, u8 val)
100 {
101         unsigned int i;
102
103         for (i = 0; i < 20; i++) {
104                 if (!__check_latch(port, val))
105                         return 0;
106                 udelay(5);
107         }
108
109         return -EIO;
110 }
111
112 /*
113  * __device_refresh - request a refresh from the accelerometer.  Does not wait
114  * for refresh to complete.  Callers must hold hdaps_sem.
115  */
116 static void __device_refresh(void)
117 {
118         udelay(200);
119         if (inb(0x1604) != STATE_FRESH) {
120                 outb(0x11, 0x1610);
121                 outb(0x01, 0x161f);
122         }
123 }
124
125 /*
126  * __device_refresh_sync - request a synchronous refresh from the
127  * accelerometer.  We wait for the refresh to complete.  Returns zero if
128  * successful and nonzero on error.  Callers must hold hdaps_sem.
129  */
130 static int __device_refresh_sync(void)
131 {
132         __device_refresh();
133         return __wait_latch(0x1604, STATE_FRESH);
134 }
135
136 /*
137  * __device_complete - indicate to the accelerometer that we are done reading
138  * data, and then initiate an async refresh.  Callers must hold hdaps_sem.
139  */
140 static inline void __device_complete(void)
141 {
142         inb(0x161f);
143         inb(0x1604);
144         __device_refresh();
145 }
146
147 /*
148  * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
149  * the given pointer.  Returns zero on success or a negative error on failure.
150  * Can sleep.
151  */
152 static int hdaps_readb_one(unsigned int port, u8 *val)
153 {
154         int ret;
155
156         down(&hdaps_sem);
157
158         /* do a sync refresh -- we need to be sure that we read fresh data */
159         ret = __device_refresh_sync();
160         if (ret)
161                 goto out;
162
163         *val = inb(port);
164         __device_complete();
165
166 out:
167         up(&hdaps_sem);
168         return ret;
169 }
170
171 /* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
172 static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
173                              int *x, int *y)
174 {
175         /* do a sync refresh -- we need to be sure that we read fresh data */
176         if (__device_refresh_sync())
177                 return -EIO;
178
179         *y = inw(port2);
180         *x = inw(port1);
181         km_activity = inb(HDAPS_PORT_KMACT);
182         __device_complete();
183
184         /* if hdaps_invert is set, negate the two values */
185         if (hdaps_invert) {
186                 *x = -*x;
187                 *y = -*y;
188         }
189
190         return 0;
191 }
192
193 /*
194  * hdaps_read_pair - reads the values from a pair of ports, placing the values
195  * in the given pointers.  Returns zero on success.  Can sleep.
196  */
197 static int hdaps_read_pair(unsigned int port1, unsigned int port2,
198                            int *val1, int *val2)
199 {
200         int ret;
201
202         down(&hdaps_sem);
203         ret = __hdaps_read_pair(port1, port2, val1, val2);
204         up(&hdaps_sem);
205
206         return ret;
207 }
208
209 /*
210  * hdaps_device_init - initialize the accelerometer.  Returns zero on success
211  * and negative error code on failure.  Can sleep.
212  */
213 static int hdaps_device_init(void)
214 {
215         int total, ret = -ENXIO;
216
217         down(&hdaps_sem);
218
219         outb(0x13, 0x1610);
220         outb(0x01, 0x161f);
221         if (__wait_latch(0x161f, 0x00))
222                 goto out;
223
224         /*
225          * Most ThinkPads return 0x01.
226          *
227          * Others--namely the R50p, T41p, and T42p--return 0x03.  These laptops
228          * have "inverted" axises.
229          *
230          * The 0x02 value occurs when the chip has been previously initialized.
231          */
232         if (__check_latch(0x1611, 0x03) &&
233                      __check_latch(0x1611, 0x02) &&
234                      __check_latch(0x1611, 0x01))
235                 goto out;
236
237         printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x).\n",
238                __get_latch(0x1611));
239
240         outb(0x17, 0x1610);
241         outb(0x81, 0x1611);
242         outb(0x01, 0x161f);
243         if (__wait_latch(0x161f, 0x00))
244                 goto out;
245         if (__wait_latch(0x1611, 0x00))
246                 goto out;
247         if (__wait_latch(0x1612, 0x60))
248                 goto out;
249         if (__wait_latch(0x1613, 0x00))
250                 goto out;
251         outb(0x14, 0x1610);
252         outb(0x01, 0x1611);
253         outb(0x01, 0x161f);
254         if (__wait_latch(0x161f, 0x00))
255                 goto out;
256         outb(0x10, 0x1610);
257         outb(0xc8, 0x1611);
258         outb(0x00, 0x1612);
259         outb(0x02, 0x1613);
260         outb(0x01, 0x161f);
261         if (__wait_latch(0x161f, 0x00))
262                 goto out;
263         if (__device_refresh_sync())
264                 goto out;
265         if (__wait_latch(0x1611, 0x00))
266                 goto out;
267
268         /* we have done our dance, now let's wait for the applause */
269         for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
270                 int x, y;
271
272                 /* a read of the device helps push it into action */
273                 __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
274                 if (!__wait_latch(0x1611, 0x02)) {
275                         ret = 0;
276                         break;
277                 }
278
279                 msleep(INIT_WAIT_MSECS);
280         }
281
282 out:
283         up(&hdaps_sem);
284         return ret;
285 }
286
287
288 /* Device model stuff */
289
290 static int hdaps_probe(struct platform_device *dev)
291 {
292         int ret;
293
294         ret = hdaps_device_init();
295         if (ret)
296                 return ret;
297
298         printk(KERN_INFO "hdaps: device successfully initialized.\n");
299         return 0;
300 }
301
302 static int hdaps_resume(struct platform_device *dev)
303 {
304         return hdaps_device_init();
305 }
306
307 static struct platform_driver hdaps_driver = {
308         .probe = hdaps_probe,
309         .resume = hdaps_resume,
310         .driver = {
311                 .name = "hdaps",
312                 .owner = THIS_MODULE,
313         },
314 };
315
316 /*
317  * hdaps_calibrate - Set our "resting" values.  Callers must hold hdaps_sem.
318  */
319 static void hdaps_calibrate(void)
320 {
321         __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
322 }
323
324 static void hdaps_mousedev_poll(unsigned long unused)
325 {
326         int x, y;
327
328         /* Cannot sleep.  Try nonblockingly.  If we fail, try again later. */
329         if (down_trylock(&hdaps_sem)) {
330                 mod_timer(&hdaps_timer,jiffies + HDAPS_POLL_PERIOD);
331                 return;
332         }
333
334         if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
335                 goto out;
336
337         input_report_abs(hdaps_idev, ABS_X, x - rest_x);
338         input_report_abs(hdaps_idev, ABS_Y, y - rest_y);
339         input_sync(hdaps_idev);
340
341         mod_timer(&hdaps_timer, jiffies + HDAPS_POLL_PERIOD);
342
343 out:
344         up(&hdaps_sem);
345 }
346
347
348 /* Sysfs Files */
349
350 static ssize_t hdaps_position_show(struct device *dev,
351                                    struct device_attribute *attr, char *buf)
352 {
353         int ret, x, y;
354
355         ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
356         if (ret)
357                 return ret;
358
359         return sprintf(buf, "(%d,%d)\n", x, y);
360 }
361
362 static ssize_t hdaps_variance_show(struct device *dev,
363                                    struct device_attribute *attr, char *buf)
364 {
365         int ret, x, y;
366
367         ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
368         if (ret)
369                 return ret;
370
371         return sprintf(buf, "(%d,%d)\n", x, y);
372 }
373
374 static ssize_t hdaps_temp1_show(struct device *dev,
375                                 struct device_attribute *attr, char *buf)
376 {
377         u8 temp;
378         int ret;
379
380         ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
381         if (ret < 0)
382                 return ret;
383
384         return sprintf(buf, "%u\n", temp);
385 }
386
387 static ssize_t hdaps_temp2_show(struct device *dev,
388                                 struct device_attribute *attr, char *buf)
389 {
390         u8 temp;
391         int ret;
392
393         ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
394         if (ret < 0)
395                 return ret;
396
397         return sprintf(buf, "%u\n", temp);
398 }
399
400 static ssize_t hdaps_keyboard_activity_show(struct device *dev,
401                                             struct device_attribute *attr,
402                                             char *buf)
403 {
404         return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
405 }
406
407 static ssize_t hdaps_mouse_activity_show(struct device *dev,
408                                          struct device_attribute *attr,
409                                          char *buf)
410 {
411         return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
412 }
413
414 static ssize_t hdaps_calibrate_show(struct device *dev,
415                                     struct device_attribute *attr, char *buf)
416 {
417         return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
418 }
419
420 static ssize_t hdaps_calibrate_store(struct device *dev,
421                                      struct device_attribute *attr,
422                                      const char *buf, size_t count)
423 {
424         down(&hdaps_sem);
425         hdaps_calibrate();
426         up(&hdaps_sem);
427
428         return count;
429 }
430
431 static ssize_t hdaps_invert_show(struct device *dev,
432                                  struct device_attribute *attr, char *buf)
433 {
434         return sprintf(buf, "%u\n", hdaps_invert);
435 }
436
437 static ssize_t hdaps_invert_store(struct device *dev,
438                                   struct device_attribute *attr,
439                                   const char *buf, size_t count)
440 {
441         int invert;
442
443         if (sscanf(buf, "%d", &invert) != 1 || (invert != 1 && invert != 0))
444                 return -EINVAL;
445
446         hdaps_invert = invert;
447         hdaps_calibrate();
448
449         return count;
450 }
451
452 static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
453 static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
454 static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
455 static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
456 static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
457 static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
458 static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
459 static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
460
461 static struct attribute *hdaps_attributes[] = {
462         &dev_attr_position.attr,
463         &dev_attr_variance.attr,
464         &dev_attr_temp1.attr,
465         &dev_attr_temp2.attr,
466         &dev_attr_keyboard_activity.attr,
467         &dev_attr_mouse_activity.attr,
468         &dev_attr_calibrate.attr,
469         &dev_attr_invert.attr,
470         NULL,
471 };
472
473 static struct attribute_group hdaps_attribute_group = {
474         .attrs = hdaps_attributes,
475 };
476
477
478 /* Module stuff */
479
480 /* hdaps_dmi_match - found a match.  return one, short-circuiting the hunt. */
481 static int hdaps_dmi_match(struct dmi_system_id *id)
482 {
483         printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
484         return 1;
485 }
486
487 /* hdaps_dmi_match_invert - found an inverted match. */
488 static int hdaps_dmi_match_invert(struct dmi_system_id *id)
489 {
490         hdaps_invert = 1;
491         printk(KERN_INFO "hdaps: inverting axis readings.\n");
492         return hdaps_dmi_match(id);
493 }
494
495 #define HDAPS_DMI_MATCH_NORMAL(model)   {               \
496         .ident = "IBM " model,                          \
497         .callback = hdaps_dmi_match,                    \
498         .matches = {                                    \
499                 DMI_MATCH(DMI_BOARD_VENDOR, "IBM"),     \
500                 DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
501         }                                               \
502 }
503
504 #define HDAPS_DMI_MATCH_INVERT(model)   {               \
505         .ident = "IBM " model,                          \
506         .callback = hdaps_dmi_match_invert,             \
507         .matches = {                                    \
508                 DMI_MATCH(DMI_BOARD_VENDOR, "IBM"),     \
509                 DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
510         }                                               \
511 }
512
513 #define HDAPS_DMI_MATCH_LENOVO(model)   {               \
514         .ident = "Lenovo " model,                       \
515         .callback = hdaps_dmi_match_invert,             \
516         .matches = {                                    \
517                 DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),  \
518                 DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
519         }                                               \
520 }
521
522 static int __init hdaps_init(void)
523 {
524         int ret;
525
526         /* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
527           "ThinkPad T42p", so the order of the entries matters */
528         struct dmi_system_id hdaps_whitelist[] = {
529                 HDAPS_DMI_MATCH_NORMAL("ThinkPad H"),
530                 HDAPS_DMI_MATCH_INVERT("ThinkPad R50p"),
531                 HDAPS_DMI_MATCH_NORMAL("ThinkPad R50"),
532                 HDAPS_DMI_MATCH_NORMAL("ThinkPad R51"),
533                 HDAPS_DMI_MATCH_NORMAL("ThinkPad R52"),
534                 HDAPS_DMI_MATCH_NORMAL("ThinkPad H"),    /* R52 (1846AQG) */
535                 HDAPS_DMI_MATCH_INVERT("ThinkPad T41p"),
536                 HDAPS_DMI_MATCH_NORMAL("ThinkPad T41"),
537                 HDAPS_DMI_MATCH_INVERT("ThinkPad T42p"),
538                 HDAPS_DMI_MATCH_NORMAL("ThinkPad T42"),
539                 HDAPS_DMI_MATCH_NORMAL("ThinkPad T43"),
540                 HDAPS_DMI_MATCH_LENOVO("ThinkPad T60p"),
541                 HDAPS_DMI_MATCH_LENOVO("ThinkPad T60"),
542                 HDAPS_DMI_MATCH_NORMAL("ThinkPad X40"),
543                 HDAPS_DMI_MATCH_NORMAL("ThinkPad X41"),
544                 HDAPS_DMI_MATCH_LENOVO("ThinkPad X60"),
545                 HDAPS_DMI_MATCH_NORMAL("ThinkPad Z60m"),
546                 { .ident = NULL }
547         };
548
549         if (!dmi_check_system(hdaps_whitelist)) {
550                 printk(KERN_WARNING "hdaps: 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_device();
575         if (!hdaps_idev) {
576                 ret = -ENOMEM;
577                 goto out_group;
578         }
579
580         /* initial calibrate for the input device */
581         hdaps_calibrate();
582
583         /* initialize the input class */
584         hdaps_idev->name = "hdaps";
585         hdaps_idev->cdev.dev = &pdev->dev;
586         hdaps_idev->evbit[0] = BIT(EV_ABS);
587         input_set_abs_params(hdaps_idev, ABS_X,
588                         -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
589         input_set_abs_params(hdaps_idev, ABS_Y,
590                         -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
591
592         ret = input_register_device(hdaps_idev);
593         if (ret)
594                 goto out_idev;
595
596         /* start up our timer for the input device */
597         init_timer(&hdaps_timer);
598         hdaps_timer.function = hdaps_mousedev_poll;
599         hdaps_timer.expires = jiffies + HDAPS_POLL_PERIOD;
600         add_timer(&hdaps_timer);
601
602         printk(KERN_INFO "hdaps: driver successfully loaded.\n");
603         return 0;
604
605 out_idev:
606         input_free_device(hdaps_idev);
607 out_group:
608         sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
609 out_device:
610         platform_device_unregister(pdev);
611 out_driver:
612         platform_driver_unregister(&hdaps_driver);
613 out_region:
614         release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
615 out:
616         printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
617         return ret;
618 }
619
620 static void __exit hdaps_exit(void)
621 {
622         del_timer_sync(&hdaps_timer);
623         input_unregister_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         printk(KERN_INFO "hdaps: driver unloaded.\n");
630 }
631
632 module_init(hdaps_init);
633 module_exit(hdaps_exit);
634
635 module_param_named(invert, hdaps_invert, bool, 0);
636 MODULE_PARM_DESC(invert, "invert data along each axis");
637
638 MODULE_AUTHOR("Robert Love");
639 MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
640 MODULE_LICENSE("GPL v2");