fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / 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 __init 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 __init 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(vendor, model) {         \
496         .ident = vendor " " model,                      \
497         .callback = hdaps_dmi_match,                    \
498         .matches = {                                    \
499                 DMI_MATCH(DMI_BOARD_VENDOR, vendor),    \
500                 DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
501         }                                               \
502 }
503
504 #define HDAPS_DMI_MATCH_INVERT(vendor, model) {         \
505         .ident = vendor " " model,                      \
506         .callback = hdaps_dmi_match_invert,             \
507         .matches = {                                    \
508                 DMI_MATCH(DMI_BOARD_VENDOR, vendor),    \
509                 DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
510         }                                               \
511 }
512
513 /* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
514    "ThinkPad T42p", so the order of the entries matters.
515    If your ThinkPad is not recognized, please update to latest
516    BIOS. This is especially the case for some R52 ThinkPads. */
517 static struct dmi_system_id __initdata hdaps_whitelist[] = {
518         HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p"),
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("IBM", "ThinkPad T41p"),
523         HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
524         HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p"),
525         HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
526         HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
527         HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60"),
528         HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
529         HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X41"),
530         HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60"),
531         HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
532         { .ident = NULL }
533 };
534
535 static int __init hdaps_init(void)
536 {
537         int ret;
538
539         if (!dmi_check_system(hdaps_whitelist)) {
540                 printk(KERN_WARNING "hdaps: supported laptop not found!\n");
541                 ret = -ENODEV;
542                 goto out;
543         }
544
545         if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
546                 ret = -ENXIO;
547                 goto out;
548         }
549
550         ret = platform_driver_register(&hdaps_driver);
551         if (ret)
552                 goto out_region;
553
554         pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
555         if (IS_ERR(pdev)) {
556                 ret = PTR_ERR(pdev);
557                 goto out_driver;
558         }
559
560         ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
561         if (ret)
562                 goto out_device;
563
564         hdaps_idev = input_allocate_device();
565         if (!hdaps_idev) {
566                 ret = -ENOMEM;
567                 goto out_group;
568         }
569
570         /* initial calibrate for the input device */
571         hdaps_calibrate();
572
573         /* initialize the input class */
574         hdaps_idev->name = "hdaps";
575         hdaps_idev->cdev.dev = &pdev->dev;
576         hdaps_idev->evbit[0] = BIT(EV_ABS);
577         input_set_abs_params(hdaps_idev, ABS_X,
578                         -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
579         input_set_abs_params(hdaps_idev, ABS_Y,
580                         -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
581
582         ret = input_register_device(hdaps_idev);
583         if (ret)
584                 goto out_idev;
585
586         /* start up our timer for the input device */
587         init_timer(&hdaps_timer);
588         hdaps_timer.function = hdaps_mousedev_poll;
589         hdaps_timer.expires = jiffies + HDAPS_POLL_PERIOD;
590         add_timer(&hdaps_timer);
591
592         printk(KERN_INFO "hdaps: driver successfully loaded.\n");
593         return 0;
594
595 out_idev:
596         input_free_device(hdaps_idev);
597 out_group:
598         sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
599 out_device:
600         platform_device_unregister(pdev);
601 out_driver:
602         platform_driver_unregister(&hdaps_driver);
603 out_region:
604         release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
605 out:
606         printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
607         return ret;
608 }
609
610 static void __exit hdaps_exit(void)
611 {
612         del_timer_sync(&hdaps_timer);
613         input_unregister_device(hdaps_idev);
614         sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
615         platform_device_unregister(pdev);
616         platform_driver_unregister(&hdaps_driver);
617         release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
618
619         printk(KERN_INFO "hdaps: driver unloaded.\n");
620 }
621
622 module_init(hdaps_init);
623 module_exit(hdaps_exit);
624
625 module_param_named(invert, hdaps_invert, bool, 0);
626 MODULE_PARM_DESC(invert, "invert data along each axis");
627
628 MODULE_AUTHOR("Robert Love");
629 MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
630 MODULE_LICENSE("GPL v2");