aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/lis3lv02d.c
diff options
context:
space:
mode:
authorPavel Machek <pavel@suse.cz>2008-11-12 16:27:02 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2008-11-12 20:17:17 -0500
commit455fbdd376c3ed3a5be8c039348896fdd87e9930 (patch)
tree03ac3947914b66061d4b8c20ae58285efe475f36 /drivers/hwmon/lis3lv02d.c
parent33c5d3d64589c5d379db5a5615735f6d08438369 (diff)
LIS3LV02Dx Accelerometer driver
This adds a driver to the accelerometer sensor found in several HP laptops (under the commercial names of "HP Mobile Data Protection System 3D" and "HP 3D driveguard"). It tries to have more or less the same interfaces as the hdaps and other accelerometer drivers: in sysfs and as a joystick. This driver was first written by Yan Burman. Eric Piel has updated it and slimed it up (including the removal of an interface to access to the free-fall feature of the sensor because it is not reliable enough for now). Pavel Machek removed few more features and switched locking from semaphore to mutex. Several people have contributed to the database of the axes. [eric.piel@tremplin-utc.net: LIS3LV02D: Conform to the new ACPI API] Signed-off-by: Eric Piel <eric.piel@tremplin-utc.net> Signed-off-by: Yan Burman <burman.yan@gmail.com> Signed-off-by: Pavel Machek <pavel@suse.cz> Cc: "Mark M. Hoffman" <mhoffman@lightlink.com> Signed-off-by: Eric Piel <eric.piel@tremplin-utc.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/hwmon/lis3lv02d.c')
-rw-r--r--drivers/hwmon/lis3lv02d.c582
1 files changed, 582 insertions, 0 deletions
diff --git a/drivers/hwmon/lis3lv02d.c b/drivers/hwmon/lis3lv02d.c
new file mode 100644
index 000000000000..752b5c44df9c
--- /dev/null
+++ b/drivers/hwmon/lis3lv02d.c
@@ -0,0 +1,582 @@
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
59struct axis_conversion {
60 s8 x;
61 s8 y;
62 s8 z;
63};
64
65struct 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
80static struct acpi_lis3lv02d adev;
81
82static int lis3lv02d_remove_fs(void);
83static int lis3lv02d_add_fs(struct acpi_device *device);
84
85/* For automatic insertion of the module */
86static struct acpi_device_id lis3lv02d_device_ids[] = {
87 {"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */
88 {"", 0},
89};
90MODULE_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 */
98static 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 */
111static 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 */
133static 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
147static 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 */
164static 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 */
181static 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
194static 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
201static 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
219static 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
226static 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 */
248static 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 */
263static 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 */
276static 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
295static 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
307static void lis3lv02d_joystick_close(struct input_dev *input)
308{
309 kthread_stop(adev.kthread);
310 lis3lv02d_decrease_use(&adev);
311}
312
313
314static inline void lis3lv02d_calibrate_joystick(void)
315{
316 lis3lv02d_get_xyz(adev.device->handle, &adev.xcalib, &adev.ycalib, &adev.zcalib);
317}
318
319static 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
354static 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 */
368static 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
381static 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. */
391static struct axis_conversion lis3lv02d_axis_normal = {1, 2, 3};
392static struct axis_conversion lis3lv02d_axis_y_inverted = {1, -2, 3};
393static struct axis_conversion lis3lv02d_axis_x_inverted = {-1, 2, 3};
394static struct axis_conversion lis3lv02d_axis_z_inverted = {1, 2, -3};
395static struct axis_conversion lis3lv02d_axis_xy_rotated_left = {-2, 1, 3};
396static 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}
406static 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
428static 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
456static 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 */
469static 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
480static 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
486static 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 */
497static int lis3lv02dl_df_val[4] = {40, 160, 640, 2560};
498static 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
511static DEVICE_ATTR(position, S_IRUGO, lis3lv02d_position_show, NULL);
512static DEVICE_ATTR(calibrate, S_IRUGO|S_IWUSR, lis3lv02d_calibrate_show,
513 lis3lv02d_calibrate_store);
514static DEVICE_ATTR(rate, S_IRUGO, lis3lv02d_rate_show, NULL);
515
516static struct attribute *lis3lv02d_attributes[] = {
517 &dev_attr_position.attr,
518 &dev_attr_calibrate.attr,
519 &dev_attr_rate.attr,
520 NULL
521};
522
523static struct attribute_group lis3lv02d_attribute_group = {
524 .attrs = lis3lv02d_attributes
525};
526
527static 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
536static 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 */
544static 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
556static 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
572static void __exit lis3lv02d_exit_module(void)
573{
574 acpi_bus_unregister_driver(&lis3lv02d_driver);
575}
576
577MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver");
578MODULE_AUTHOR("Yan Burman and Eric Piel");
579MODULE_LICENSE("GPL");
580
581module_init(lis3lv02d_init_module);
582module_exit(lis3lv02d_exit_module);