aboutsummaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--Documentation/hwmon/lis3lv02d49
-rw-r--r--MAINTAINERS5
-rw-r--r--drivers/hwmon/Kconfig19
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/lis3lv02d.c582
-rw-r--r--drivers/hwmon/lis3lv02d.h149
6 files changed, 805 insertions, 0 deletions
diff --git a/Documentation/hwmon/lis3lv02d b/Documentation/hwmon/lis3lv02d
new file mode 100644
index 000000000000..65dfb0c0fd67
--- /dev/null
+++ b/Documentation/hwmon/lis3lv02d
@@ -0,0 +1,49 @@
1Kernel driver lis3lv02d
2==================
3
4Supported chips:
5
6 * STMicroelectronics LIS3LV02DL and LIS3LV02DQ
7
8Author:
9 Yan Burman <burman.yan@gmail.com>
10 Eric Piel <eric.piel@tremplin-utc.net>
11
12
13Description
14-----------
15
16This driver provides support for the accelerometer found in various HP laptops
17sporting the feature officially called "HP Mobile Data Protection System 3D" or
18"HP 3D DriveGuard". It detect automatically laptops with this sensor. Known models
19(for now the HP 2133, nc6420, nc2510, nc8510, nc84x0, nw9440 and nx9420) will
20have their axis automatically oriented on standard way (eg: you can directly
21play neverball). The accelerometer data is readable via
22/sys/devices/platform/lis3lv02d.
23
24Sysfs attributes under /sys/devices/platform/lis3lv02d/:
25position - 3D position that the accelerometer reports. Format: "(x,y,z)"
26calibrate - read: values (x, y, z) that are used as the base for input class device operation.
27 write: forces the base to be recalibrated with the current position.
28rate - reports the sampling rate of the accelerometer device in HZ
29
30This driver also provides an absolute input class device, allowing
31the laptop to act as a pinball machine-esque joystick.
32
33Axes orientation
34----------------
35
36For better compatibility between the various laptops. The values reported by
37the accelerometer are converted into a "standard" organisation of the axes
38(aka "can play neverball out of the box"):
39 * When the laptop is horizontal the position reported is about 0 for X and Y
40and a positive value for Z
41 * If the left side is elevated, X increases (becomes positive)
42 * If the front side (where the touchpad is) is elevated, Y decreases (becomes negative)
43 * If the laptop is put upside-down, Z becomes negative
44
45If your laptop model is not recognized (cf "dmesg"), you can send an email to the
46authors to add it to the database. When reporting a new laptop, please include
47the output of "dmidecode" plus the value of /sys/devices/platform/lis3lv02d/position
48in these four cases.
49
diff --git a/MAINTAINERS b/MAINTAINERS
index 272d04e95faa..8e0777fae3ce 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2703,6 +2703,11 @@ P: Arnaldo Carvalho de Melo
2703M: acme@ghostprotocols.net 2703M: acme@ghostprotocols.net
2704S: Maintained 2704S: Maintained
2705 2705
2706LIS3LV02D ACCELEROMETER DRIVER
2707P: Eric Piel
2708M: eric.piel@tremplin-utc.net
2709S: Maintained
2710
2706LM83 HARDWARE MONITOR DRIVER 2711LM83 HARDWARE MONITOR DRIVER
2707P: Jean Delvare 2712P: Jean Delvare
2708M: khali@linux-fr.org 2713M: khali@linux-fr.org
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 6de1e0ffd391..1c44f5c47aab 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -825,6 +825,25 @@ config SENSORS_HDAPS
825 Say Y here if you have an applicable laptop and want to experience 825 Say Y here if you have an applicable laptop and want to experience
826 the awesome power of hdaps. 826 the awesome power of hdaps.
827 827
828config SENSORS_LIS3LV02D
829 tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer"
830 depends on ACPI && INPUT
831 default n
832 help
833 This driver provides support for the LIS3LV02Dx accelerometer. In
834 particular, it can be found in a number of HP laptops, which have the
835 "Mobile Data Protection System 3D" or "3D DriveGuard" feature. On such
836 systems the driver should load automatically (via ACPI). The
837 accelerometer might also be found in other systems, connected via SPI
838 or I2C. The accelerometer data is readable via
839 /sys/devices/platform/lis3lv02d.
840
841 This driver also provides an absolute input class device, allowing
842 the laptop to act as a pinball machine-esque joystick.
843
844 This driver can also be built as a module. If so, the module
845 will be called lis3lv02d.
846
828config SENSORS_APPLESMC 847config SENSORS_APPLESMC
829 tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" 848 tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)"
830 depends on INPUT && X86 849 depends on INPUT && X86
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 042d5a78622e..e0d90a68bea0 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
48obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o 48obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
49obj-$(CONFIG_SENSORS_IT87) += it87.o 49obj-$(CONFIG_SENSORS_IT87) += it87.o
50obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o 50obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
51obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o
51obj-$(CONFIG_SENSORS_LM63) += lm63.o 52obj-$(CONFIG_SENSORS_LM63) += lm63.o
52obj-$(CONFIG_SENSORS_LM70) += lm70.o 53obj-$(CONFIG_SENSORS_LM70) += lm70.o
53obj-$(CONFIG_SENSORS_LM75) += lm75.o 54obj-$(CONFIG_SENSORS_LM75) += lm75.o
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);
diff --git a/drivers/hwmon/lis3lv02d.h b/drivers/hwmon/lis3lv02d.h
new file mode 100644
index 000000000000..330cfc60e948
--- /dev/null
+++ b/drivers/hwmon/lis3lv02d.h
@@ -0,0 +1,149 @@
1/*
2 * lis3lv02d.h - 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/*
23 * The actual chip is STMicroelectronics LIS3LV02DL or LIS3LV02DQ that seems to
24 * be connected via SPI. There exists also several similar chips (such as LIS302DL or
25 * LIS3L02DQ) but not in the HP laptops and they have slightly different registers.
26 * They can also be connected via I²C.
27 */
28
29#define LIS3LV02DL_ID 0x3A /* Also the LIS3LV02DQ */
30#define LIS302DL_ID 0x3B /* Also the LIS202DL! */
31
32enum lis3lv02d_reg {
33 WHO_AM_I = 0x0F,
34 OFFSET_X = 0x16,
35 OFFSET_Y = 0x17,
36 OFFSET_Z = 0x18,
37 GAIN_X = 0x19,
38 GAIN_Y = 0x1A,
39 GAIN_Z = 0x1B,
40 CTRL_REG1 = 0x20,
41 CTRL_REG2 = 0x21,
42 CTRL_REG3 = 0x22,
43 HP_FILTER_RESET = 0x23,
44 STATUS_REG = 0x27,
45 OUTX_L = 0x28,
46 OUTX_H = 0x29,
47 OUTY_L = 0x2A,
48 OUTY_H = 0x2B,
49 OUTZ_L = 0x2C,
50 OUTZ_H = 0x2D,
51 FF_WU_CFG = 0x30,
52 FF_WU_SRC = 0x31,
53 FF_WU_ACK = 0x32,
54 FF_WU_THS_L = 0x34,
55 FF_WU_THS_H = 0x35,
56 FF_WU_DURATION = 0x36,
57 DD_CFG = 0x38,
58 DD_SRC = 0x39,
59 DD_ACK = 0x3A,
60 DD_THSI_L = 0x3C,
61 DD_THSI_H = 0x3D,
62 DD_THSE_L = 0x3E,
63 DD_THSE_H = 0x3F,
64};
65
66enum lis3lv02d_ctrl1 {
67 CTRL1_Xen = 0x01,
68 CTRL1_Yen = 0x02,
69 CTRL1_Zen = 0x04,
70 CTRL1_ST = 0x08,
71 CTRL1_DF0 = 0x10,
72 CTRL1_DF1 = 0x20,
73 CTRL1_PD0 = 0x40,
74 CTRL1_PD1 = 0x80,
75};
76enum lis3lv02d_ctrl2 {
77 CTRL2_DAS = 0x01,
78 CTRL2_SIM = 0x02,
79 CTRL2_DRDY = 0x04,
80 CTRL2_IEN = 0x08,
81 CTRL2_BOOT = 0x10,
82 CTRL2_BLE = 0x20,
83 CTRL2_BDU = 0x40, /* Block Data Update */
84 CTRL2_FS = 0x80, /* Full Scale selection */
85};
86
87
88enum lis3lv02d_ctrl3 {
89 CTRL3_CFS0 = 0x01,
90 CTRL3_CFS1 = 0x02,
91 CTRL3_FDS = 0x10,
92 CTRL3_HPFF = 0x20,
93 CTRL3_HPDD = 0x40,
94 CTRL3_ECK = 0x80,
95};
96
97enum lis3lv02d_status_reg {
98 STATUS_XDA = 0x01,
99 STATUS_YDA = 0x02,
100 STATUS_ZDA = 0x04,
101 STATUS_XYZDA = 0x08,
102 STATUS_XOR = 0x10,
103 STATUS_YOR = 0x20,
104 STATUS_ZOR = 0x40,
105 STATUS_XYZOR = 0x80,
106};
107
108enum lis3lv02d_ff_wu_cfg {
109 FF_WU_CFG_XLIE = 0x01,
110 FF_WU_CFG_XHIE = 0x02,
111 FF_WU_CFG_YLIE = 0x04,
112 FF_WU_CFG_YHIE = 0x08,
113 FF_WU_CFG_ZLIE = 0x10,
114 FF_WU_CFG_ZHIE = 0x20,
115 FF_WU_CFG_LIR = 0x40,
116 FF_WU_CFG_AOI = 0x80,
117};
118
119enum lis3lv02d_ff_wu_src {
120 FF_WU_SRC_XL = 0x01,
121 FF_WU_SRC_XH = 0x02,
122 FF_WU_SRC_YL = 0x04,
123 FF_WU_SRC_YH = 0x08,
124 FF_WU_SRC_ZL = 0x10,
125 FF_WU_SRC_ZH = 0x20,
126 FF_WU_SRC_IA = 0x40,
127};
128
129enum lis3lv02d_dd_cfg {
130 DD_CFG_XLIE = 0x01,
131 DD_CFG_XHIE = 0x02,
132 DD_CFG_YLIE = 0x04,
133 DD_CFG_YHIE = 0x08,
134 DD_CFG_ZLIE = 0x10,
135 DD_CFG_ZHIE = 0x20,
136 DD_CFG_LIR = 0x40,
137 DD_CFG_IEND = 0x80,
138};
139
140enum lis3lv02d_dd_src {
141 DD_SRC_XL = 0x01,
142 DD_SRC_XH = 0x02,
143 DD_SRC_YL = 0x04,
144 DD_SRC_YH = 0x08,
145 DD_SRC_ZL = 0x10,
146 DD_SRC_ZH = 0x20,
147 DD_SRC_IA = 0x40,
148};
149