Commit 455fbdd376c3ed3a5be8c039348896fdd87e9930

Authored by Pavel Machek
Committed by Linus Torvalds
1 parent 33c5d3d645

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>

Showing 6 changed files with 805 additions and 0 deletions Side-by-side Diff

Documentation/hwmon/lis3lv02d
  1 +Kernel driver lis3lv02d
  2 +==================
  3 +
  4 +Supported chips:
  5 +
  6 + * STMicroelectronics LIS3LV02DL and LIS3LV02DQ
  7 +
  8 +Author:
  9 + Yan Burman <burman.yan@gmail.com>
  10 + Eric Piel <eric.piel@tremplin-utc.net>
  11 +
  12 +
  13 +Description
  14 +-----------
  15 +
  16 +This driver provides support for the accelerometer found in various HP laptops
  17 +sporting 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
  20 +have their axis automatically oriented on standard way (eg: you can directly
  21 +play neverball). The accelerometer data is readable via
  22 +/sys/devices/platform/lis3lv02d.
  23 +
  24 +Sysfs attributes under /sys/devices/platform/lis3lv02d/:
  25 +position - 3D position that the accelerometer reports. Format: "(x,y,z)"
  26 +calibrate - 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.
  28 +rate - reports the sampling rate of the accelerometer device in HZ
  29 +
  30 +This driver also provides an absolute input class device, allowing
  31 +the laptop to act as a pinball machine-esque joystick.
  32 +
  33 +Axes orientation
  34 +----------------
  35 +
  36 +For better compatibility between the various laptops. The values reported by
  37 +the 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
  40 +and 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 +
  45 +If your laptop model is not recognized (cf "dmesg"), you can send an email to the
  46 +authors to add it to the database. When reporting a new laptop, please include
  47 +the output of "dmidecode" plus the value of /sys/devices/platform/lis3lv02d/position
  48 +in these four cases.
... ... @@ -2703,6 +2703,11 @@
2703 2703 M: acme@ghostprotocols.net
2704 2704 S: Maintained
2705 2705  
  2706 +LIS3LV02D ACCELEROMETER DRIVER
  2707 +P: Eric Piel
  2708 +M: eric.piel@tremplin-utc.net
  2709 +S: Maintained
  2710 +
2706 2711 LM83 HARDWARE MONITOR DRIVER
2707 2712 P: Jean Delvare
2708 2713 M: khali@linux-fr.org
drivers/hwmon/Kconfig
... ... @@ -825,6 +825,25 @@
825 825 Say Y here if you have an applicable laptop and want to experience
826 826 the awesome power of hdaps.
827 827  
  828 +config 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 +
828 847 config SENSORS_APPLESMC
829 848 tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)"
830 849 depends on INPUT && X86
drivers/hwmon/Makefile
... ... @@ -48,6 +48,7 @@
48 48 obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
49 49 obj-$(CONFIG_SENSORS_IT87) += it87.o
50 50 obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
  51 +obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o
51 52 obj-$(CONFIG_SENSORS_LM63) += lm63.o
52 53 obj-$(CONFIG_SENSORS_LM70) += lm70.o
53 54 obj-$(CONFIG_SENSORS_LM75) += lm75.o
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);
drivers/hwmon/lis3lv02d.h
  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 +
  32 +enum 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 +
  66 +enum 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 +};
  76 +enum 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 +
  88 +enum 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 +
  97 +enum 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 +
  108 +enum 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 +
  119 +enum 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 +
  129 +enum 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 +
  140 +enum 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 +};