]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blob - drivers/hwmon/lis3lv02d.c
752b5c44df9c3ebb10cfc73e6df00b7d1641fb45
[linux-2.6-omap-h63xx.git] / drivers / hwmon / lis3lv02d.c
1 /*
2  *  lis3lv02d.c - ST LIS3LV02DL accelerometer driver
3  *
4  *  Copyright (C) 2007-2008 Yan Burman
5  *  Copyright (C) 2008 Eric Piel
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include <linux/kernel.h>
23 #include <linux/init.h>
24 #include <linux/dmi.h>
25 #include <linux/module.h>
26 #include <linux/types.h>
27 #include <linux/platform_device.h>
28 #include <linux/interrupt.h>
29 #include <linux/input.h>
30 #include <linux/kthread.h>
31 #include <linux/semaphore.h>
32 #include <linux/delay.h>
33 #include <linux/wait.h>
34 #include <linux/poll.h>
35 #include <linux/freezer.h>
36 #include <linux/version.h>
37 #include <linux/uaccess.h>
38 #include <acpi/acpi_drivers.h>
39 #include <asm/atomic.h>
40 #include "lis3lv02d.h"
41
42 #define DRIVER_NAME     "lis3lv02d"
43 #define ACPI_MDPS_CLASS "accelerometer"
44
45 /* joystick device poll interval in milliseconds */
46 #define MDPS_POLL_INTERVAL 50
47 /*
48  * The sensor can also generate interrupts (DRDY) but it's pretty pointless
49  * because their are generated even if the data do not change. So it's better
50  * to keep the interrupt for the free-fall event. The values are updated at
51  * 40Hz (at the lowest frequency), but as it can be pretty time consuming on
52  * some low processor, we poll the sensor only at 20Hz... enough for the
53  * joystick.
54  */
55
56 /* Maximum value our axis may get for the input device (signed 12 bits) */
57 #define MDPS_MAX_VAL 2048
58
59 struct axis_conversion {
60         s8      x;
61         s8      y;
62         s8      z;
63 };
64
65 struct acpi_lis3lv02d {
66         struct acpi_device      *device;   /* The ACPI device */
67         struct input_dev        *idev;     /* input device */
68         struct task_struct      *kthread;  /* kthread for input */
69         struct mutex            lock;
70         struct platform_device  *pdev;     /* platform device */
71         atomic_t                count;     /* interrupt count after last read */
72         int                     xcalib;    /* calibrated null value for x */
73         int                     ycalib;    /* calibrated null value for y */
74         int                     zcalib;    /* calibrated null value for z */
75         unsigned char           is_on;     /* whether the device is on or off */
76         unsigned char           usage;     /* usage counter */
77         struct axis_conversion  ac;        /* hw -> logical axis */
78 };
79
80 static struct acpi_lis3lv02d adev;
81
82 static int lis3lv02d_remove_fs(void);
83 static int lis3lv02d_add_fs(struct acpi_device *device);
84
85 /* For automatic insertion of the module */
86 static struct acpi_device_id lis3lv02d_device_ids[] = {
87         {"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */
88         {"", 0},
89 };
90 MODULE_DEVICE_TABLE(acpi, lis3lv02d_device_ids);
91
92 /**
93  * lis3lv02d_acpi_init - ACPI _INI method: initialize the device.
94  * @handle: the handle of the device
95  *
96  * Returns AE_OK on success.
97  */
98 static inline acpi_status lis3lv02d_acpi_init(acpi_handle handle)
99 {
100         return acpi_evaluate_object(handle, METHOD_NAME__INI, NULL, NULL);
101 }
102
103 /**
104  * lis3lv02d_acpi_read - ACPI ALRD method: read a register
105  * @handle: the handle of the device
106  * @reg:    the register to read
107  * @ret:    result of the operation
108  *
109  * Returns AE_OK on success.
110  */
111 static acpi_status lis3lv02d_acpi_read(acpi_handle handle, int reg, u8 *ret)
112 {
113         union acpi_object arg0 = { ACPI_TYPE_INTEGER };
114         struct acpi_object_list args = { 1, &arg0 };
115         unsigned long long lret;
116         acpi_status status;
117
118         arg0.integer.value = reg;
119
120         status = acpi_evaluate_integer(handle, "ALRD", &args, &lret);
121         *ret = lret;
122         return status;
123 }
124
125 /**
126  * lis3lv02d_acpi_write - ACPI ALWR method: write to a register
127  * @handle: the handle of the device
128  * @reg:    the register to write to
129  * @val:    the value to write
130  *
131  * Returns AE_OK on success.
132  */
133 static acpi_status lis3lv02d_acpi_write(acpi_handle handle, int reg, u8 val)
134 {
135         unsigned long long ret; /* Not used when writting */
136         union acpi_object in_obj[2];
137         struct acpi_object_list args = { 2, in_obj };
138
139         in_obj[0].type          = ACPI_TYPE_INTEGER;
140         in_obj[0].integer.value = reg;
141         in_obj[1].type          = ACPI_TYPE_INTEGER;
142         in_obj[1].integer.value = val;
143
144         return acpi_evaluate_integer(handle, "ALWR", &args, &ret);
145 }
146
147 static s16 lis3lv02d_read_16(acpi_handle handle, int reg)
148 {
149         u8 lo, hi;
150
151         lis3lv02d_acpi_read(handle, reg, &lo);
152         lis3lv02d_acpi_read(handle, reg + 1, &hi);
153         /* In "12 bit right justified" mode, bit 6, bit 7, bit 8 = bit 5 */
154         return (s16)((hi << 8) | lo);
155 }
156
157 /**
158  * lis3lv02d_get_axis - For the given axis, give the value converted
159  * @axis:      1,2,3 - can also be negative
160  * @hw_values: raw values returned by the hardware
161  *
162  * Returns the converted value.
163  */
164 static inline int lis3lv02d_get_axis(s8 axis, int hw_values[3])
165 {
166         if (axis > 0)
167                 return hw_values[axis - 1];
168         else
169                 return -hw_values[-axis - 1];
170 }
171
172 /**
173  * lis3lv02d_get_xyz - Get X, Y and Z axis values from the accelerometer
174  * @handle: the handle to the device
175  * @x:      where to store the X axis value
176  * @y:      where to store the Y axis value
177  * @z:      where to store the Z axis value
178  *
179  * Note that 40Hz input device can eat up about 10% CPU at 800MHZ
180  */
181 static void lis3lv02d_get_xyz(acpi_handle handle, int *x, int *y, int *z)
182 {
183         int position[3];
184
185         position[0] = lis3lv02d_read_16(handle, OUTX_L);
186         position[1] = lis3lv02d_read_16(handle, OUTY_L);
187         position[2] = lis3lv02d_read_16(handle, OUTZ_L);
188
189         *x = lis3lv02d_get_axis(adev.ac.x, position);
190         *y = lis3lv02d_get_axis(adev.ac.y, position);
191         *z = lis3lv02d_get_axis(adev.ac.z, position);
192 }
193
194 static inline void lis3lv02d_poweroff(acpi_handle handle)
195 {
196         adev.is_on = 0;
197         /* disable X,Y,Z axis and power down */
198         lis3lv02d_acpi_write(handle, CTRL_REG1, 0x00);
199 }
200
201 static void lis3lv02d_poweron(acpi_handle handle)
202 {
203         u8 val;
204
205         adev.is_on = 1;
206         lis3lv02d_acpi_init(handle);
207         lis3lv02d_acpi_write(handle, FF_WU_CFG, 0);
208         /*
209          * BDU: LSB and MSB values are not updated until both have been read.
210          *      So the value read will always be correct.
211          * IEN: Interrupt for free-fall and DD, not for data-ready.
212          */
213         lis3lv02d_acpi_read(handle, CTRL_REG2, &val);
214         val |= CTRL2_BDU | CTRL2_IEN;
215         lis3lv02d_acpi_write(handle, CTRL_REG2, val);
216 }
217
218 #ifdef CONFIG_PM
219 static int lis3lv02d_suspend(struct acpi_device *device, pm_message_t state)
220 {
221         /* make sure the device is off when we suspend */
222         lis3lv02d_poweroff(device->handle);
223         return 0;
224 }
225
226 static int lis3lv02d_resume(struct acpi_device *device)
227 {
228         /* put back the device in the right state (ACPI might turn it on) */
229         mutex_lock(&adev.lock);
230         if (adev.usage > 0)
231                 lis3lv02d_poweron(device->handle);
232         else
233                 lis3lv02d_poweroff(device->handle);
234         mutex_unlock(&adev.lock);
235         return 0;
236 }
237 #else
238 #define lis3lv02d_suspend NULL
239 #define lis3lv02d_resume NULL
240 #endif
241
242
243 /*
244  * To be called before starting to use the device. It makes sure that the
245  * device will always be on until a call to lis3lv02d_decrease_use(). Not to be
246  * used from interrupt context.
247  */
248 static void lis3lv02d_increase_use(struct acpi_lis3lv02d *dev)
249 {
250         mutex_lock(&dev->lock);
251         dev->usage++;
252         if (dev->usage == 1) {
253                 if (!dev->is_on)
254                         lis3lv02d_poweron(dev->device->handle);
255         }
256         mutex_unlock(&dev->lock);
257 }
258
259 /*
260  * To be called whenever a usage of the device is stopped.
261  * It will make sure to turn off the device when there is not usage.
262  */
263 static void lis3lv02d_decrease_use(struct acpi_lis3lv02d *dev)
264 {
265         mutex_lock(&dev->lock);
266         dev->usage--;
267         if (dev->usage == 0)
268                 lis3lv02d_poweroff(dev->device->handle);
269         mutex_unlock(&dev->lock);
270 }
271
272 /**
273  * lis3lv02d_joystick_kthread - Kthread polling function
274  * @data: unused - here to conform to threadfn prototype
275  */
276 static int lis3lv02d_joystick_kthread(void *data)
277 {
278         int x, y, z;
279
280         while (!kthread_should_stop()) {
281                 lis3lv02d_get_xyz(adev.device->handle, &x, &y, &z);
282                 input_report_abs(adev.idev, ABS_X, x - adev.xcalib);
283                 input_report_abs(adev.idev, ABS_Y, y - adev.ycalib);
284                 input_report_abs(adev.idev, ABS_Z, z - adev.zcalib);
285
286                 input_sync(adev.idev);
287
288                 try_to_freeze();
289                 msleep_interruptible(MDPS_POLL_INTERVAL);
290         }
291
292         return 0;
293 }
294
295 static int lis3lv02d_joystick_open(struct input_dev *input)
296 {
297         lis3lv02d_increase_use(&adev);
298         adev.kthread = kthread_run(lis3lv02d_joystick_kthread, NULL, "klis3lv02d");
299         if (IS_ERR(adev.kthread)) {
300                 lis3lv02d_decrease_use(&adev);
301                 return PTR_ERR(adev.kthread);
302         }
303
304         return 0;
305 }
306
307 static void lis3lv02d_joystick_close(struct input_dev *input)
308 {
309         kthread_stop(adev.kthread);
310         lis3lv02d_decrease_use(&adev);
311 }
312
313
314 static inline void lis3lv02d_calibrate_joystick(void)
315 {
316         lis3lv02d_get_xyz(adev.device->handle, &adev.xcalib, &adev.ycalib, &adev.zcalib);
317 }
318
319 static int lis3lv02d_joystick_enable(void)
320 {
321         int err;
322
323         if (adev.idev)
324                 return -EINVAL;
325
326         adev.idev = input_allocate_device();
327         if (!adev.idev)
328                 return -ENOMEM;
329
330         lis3lv02d_calibrate_joystick();
331
332         adev.idev->name       = "ST LIS3LV02DL Accelerometer";
333         adev.idev->phys       = DRIVER_NAME "/input0";
334         adev.idev->id.bustype = BUS_HOST;
335         adev.idev->id.vendor  = 0;
336         adev.idev->dev.parent = &adev.pdev->dev;
337         adev.idev->open       = lis3lv02d_joystick_open;
338         adev.idev->close      = lis3lv02d_joystick_close;
339
340         set_bit(EV_ABS, adev.idev->evbit);
341         input_set_abs_params(adev.idev, ABS_X, -MDPS_MAX_VAL, MDPS_MAX_VAL, 3, 3);
342         input_set_abs_params(adev.idev, ABS_Y, -MDPS_MAX_VAL, MDPS_MAX_VAL, 3, 3);
343         input_set_abs_params(adev.idev, ABS_Z, -MDPS_MAX_VAL, MDPS_MAX_VAL, 3, 3);
344
345         err = input_register_device(adev.idev);
346         if (err) {
347                 input_free_device(adev.idev);
348                 adev.idev = NULL;
349         }
350
351         return err;
352 }
353
354 static void lis3lv02d_joystick_disable(void)
355 {
356         if (!adev.idev)
357                 return;
358
359         input_unregister_device(adev.idev);
360         adev.idev = NULL;
361 }
362
363
364 /*
365  * Initialise the accelerometer and the various subsystems.
366  * Should be rather independant of the bus system.
367  */
368 static int lis3lv02d_init_device(struct acpi_lis3lv02d *dev)
369 {
370         mutex_init(&dev->lock);
371         lis3lv02d_add_fs(dev->device);
372         lis3lv02d_increase_use(dev);
373
374         if (lis3lv02d_joystick_enable())
375                 printk(KERN_ERR DRIVER_NAME ": joystick initialization failed\n");
376
377         lis3lv02d_decrease_use(dev);
378         return 0;
379 }
380
381 static int lis3lv02d_dmi_matched(const struct dmi_system_id *dmi)
382 {
383         adev.ac = *((struct axis_conversion *)dmi->driver_data);
384         printk(KERN_INFO DRIVER_NAME ": hardware type %s found.\n", dmi->ident);
385
386         return 1;
387 }
388
389 /* Represents, for each axis seen by userspace, the corresponding hw axis (+1).
390  * If the value is negative, the opposite of the hw value is used. */
391 static struct axis_conversion lis3lv02d_axis_normal = {1, 2, 3};
392 static struct axis_conversion lis3lv02d_axis_y_inverted = {1, -2, 3};
393 static struct axis_conversion lis3lv02d_axis_x_inverted = {-1, 2, 3};
394 static struct axis_conversion lis3lv02d_axis_z_inverted = {1, 2, -3};
395 static struct axis_conversion lis3lv02d_axis_xy_rotated_left = {-2, 1, 3};
396 static struct axis_conversion lis3lv02d_axis_xy_swap_inverted = {-2, -1, 3};
397
398 #define AXIS_DMI_MATCH(_ident, _name, _axis) {          \
399         .ident = _ident,                                \
400         .callback = lis3lv02d_dmi_matched,              \
401         .matches = {                                    \
402                 DMI_MATCH(DMI_PRODUCT_NAME, _name)      \
403         },                                              \
404         .driver_data = &lis3lv02d_axis_##_axis          \
405 }
406 static struct dmi_system_id lis3lv02d_dmi_ids[] = {
407         /* product names are truncated to match all kinds of a same model */
408         AXIS_DMI_MATCH("NC64x0", "HP Compaq nc64", x_inverted),
409         AXIS_DMI_MATCH("NC84x0", "HP Compaq nc84", z_inverted),
410         AXIS_DMI_MATCH("NX9420", "HP Compaq nx9420", x_inverted),
411         AXIS_DMI_MATCH("NW9440", "HP Compaq nw9440", x_inverted),
412         AXIS_DMI_MATCH("NC2510", "HP Compaq 2510", y_inverted),
413         AXIS_DMI_MATCH("NC8510", "HP Compaq 8510", xy_swap_inverted),
414         AXIS_DMI_MATCH("HP2133", "HP 2133", xy_rotated_left),
415         { NULL, }
416 /* Laptop models without axis info (yet):
417  * "NC651xx" "HP Compaq 651"
418  * "NC671xx" "HP Compaq 671"
419  * "NC6910" "HP Compaq 6910"
420  * HP Compaq 8710x Notebook PC / Mobile Workstation
421  * "NC2400" "HP Compaq nc2400"
422  * "NX74x0" "HP Compaq nx74"
423  * "NX6325" "HP Compaq nx6325"
424  * "NC4400" "HP Compaq nc4400"
425  */
426 };
427
428 static int lis3lv02d_add(struct acpi_device *device)
429 {
430         u8 val;
431
432         if (!device)
433                 return -EINVAL;
434
435         adev.device = device;
436         strcpy(acpi_device_name(device), DRIVER_NAME);
437         strcpy(acpi_device_class(device), ACPI_MDPS_CLASS);
438         device->driver_data = &adev;
439
440         lis3lv02d_acpi_read(device->handle, WHO_AM_I, &val);
441         if ((val != LIS3LV02DL_ID) && (val != LIS302DL_ID)) {
442                 printk(KERN_ERR DRIVER_NAME
443                                 ": Accelerometer chip not LIS3LV02D{L,Q}\n");
444         }
445
446         /* If possible use a "standard" axes order */
447         if (dmi_check_system(lis3lv02d_dmi_ids) == 0) {
448                 printk(KERN_INFO DRIVER_NAME ": laptop model unknown, "
449                                  "using default axes configuration\n");
450                 adev.ac = lis3lv02d_axis_normal;
451         }
452
453         return lis3lv02d_init_device(&adev);
454 }
455
456 static int lis3lv02d_remove(struct acpi_device *device, int type)
457 {
458         if (!device)
459                 return -EINVAL;
460
461         lis3lv02d_joystick_disable();
462         lis3lv02d_poweroff(device->handle);
463
464         return lis3lv02d_remove_fs();
465 }
466
467
468 /* Sysfs stuff */
469 static ssize_t lis3lv02d_position_show(struct device *dev,
470                                 struct device_attribute *attr, char *buf)
471 {
472         int x, y, z;
473
474         lis3lv02d_increase_use(&adev);
475         lis3lv02d_get_xyz(adev.device->handle, &x, &y, &z);
476         lis3lv02d_decrease_use(&adev);
477         return sprintf(buf, "(%d,%d,%d)\n", x, y, z);
478 }
479
480 static ssize_t lis3lv02d_calibrate_show(struct device *dev,
481                                 struct device_attribute *attr, char *buf)
482 {
483         return sprintf(buf, "(%d,%d,%d)\n", adev.xcalib, adev.ycalib, adev.zcalib);
484 }
485
486 static ssize_t lis3lv02d_calibrate_store(struct device *dev,
487                                 struct device_attribute *attr,
488                                 const char *buf, size_t count)
489 {
490         lis3lv02d_increase_use(&adev);
491         lis3lv02d_calibrate_joystick();
492         lis3lv02d_decrease_use(&adev);
493         return count;
494 }
495
496 /* conversion btw sampling rate and the register values */
497 static int lis3lv02dl_df_val[4] = {40, 160, 640, 2560};
498 static ssize_t lis3lv02d_rate_show(struct device *dev,
499                         struct device_attribute *attr, char *buf)
500 {
501         u8 ctrl;
502         int val;
503
504         lis3lv02d_increase_use(&adev);
505         lis3lv02d_acpi_read(adev.device->handle, CTRL_REG1, &ctrl);
506         lis3lv02d_decrease_use(&adev);
507         val = (ctrl & (CTRL1_DF0 | CTRL1_DF1)) >> 4;
508         return sprintf(buf, "%d\n", lis3lv02dl_df_val[val]);
509 }
510
511 static DEVICE_ATTR(position, S_IRUGO, lis3lv02d_position_show, NULL);
512 static DEVICE_ATTR(calibrate, S_IRUGO|S_IWUSR, lis3lv02d_calibrate_show,
513         lis3lv02d_calibrate_store);
514 static DEVICE_ATTR(rate, S_IRUGO, lis3lv02d_rate_show, NULL);
515
516 static struct attribute *lis3lv02d_attributes[] = {
517         &dev_attr_position.attr,
518         &dev_attr_calibrate.attr,
519         &dev_attr_rate.attr,
520         NULL
521 };
522
523 static struct attribute_group lis3lv02d_attribute_group = {
524         .attrs = lis3lv02d_attributes
525 };
526
527 static int lis3lv02d_add_fs(struct acpi_device *device)
528 {
529         adev.pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
530         if (IS_ERR(adev.pdev))
531                 return PTR_ERR(adev.pdev);
532
533         return sysfs_create_group(&adev.pdev->dev.kobj, &lis3lv02d_attribute_group);
534 }
535
536 static int lis3lv02d_remove_fs(void)
537 {
538         sysfs_remove_group(&adev.pdev->dev.kobj, &lis3lv02d_attribute_group);
539         platform_device_unregister(adev.pdev);
540         return 0;
541 }
542
543 /* For the HP MDPS aka 3D Driveguard */
544 static struct acpi_driver lis3lv02d_driver = {
545         .name  = DRIVER_NAME,
546         .class = ACPI_MDPS_CLASS,
547         .ids   = lis3lv02d_device_ids,
548         .ops = {
549                 .add     = lis3lv02d_add,
550                 .remove  = lis3lv02d_remove,
551                 .suspend = lis3lv02d_suspend,
552                 .resume  = lis3lv02d_resume,
553         }
554 };
555
556 static int __init lis3lv02d_init_module(void)
557 {
558         int ret;
559
560         if (acpi_disabled)
561                 return -ENODEV;
562
563         ret = acpi_bus_register_driver(&lis3lv02d_driver);
564         if (ret < 0)
565                 return ret;
566
567         printk(KERN_INFO DRIVER_NAME " driver loaded.\n");
568
569         return 0;
570 }
571
572 static void __exit lis3lv02d_exit_module(void)
573 {
574         acpi_bus_unregister_driver(&lis3lv02d_driver);
575 }
576
577 MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver");
578 MODULE_AUTHOR("Yan Burman and Eric Piel");
579 MODULE_LICENSE("GPL");
580
581 module_init(lis3lv02d_init_module);
582 module_exit(lis3lv02d_exit_module);