aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/lis3lv02d.c
diff options
context:
space:
mode:
authorPavel Machek <pavel@suse.cz>2009-02-18 17:48:23 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2009-02-18 18:37:54 -0500
commitef2cfc790bf5f0ff189b01eabc0f4feb5e8524df (patch)
tree1e1a4999a6615f66f56ffe19c883e065aa8d3020 /drivers/hwmon/lis3lv02d.c
parent3a5093ee6728c8cbe9c039e685fc1fca8f965048 (diff)
hp accelerometer: add freefall detection
This adds freefall handling to hp_accel driver. According to HP, it should just work, without us having to set the chip up by hand. hpfall.c is example .c program that parks the disk when accelerometer detects free fall. It should work; for now, it uses fixed 20seconds protection period. Signed-off-by: Pavel Machek <pavel@suse.cz> Cc: Thomas Renninger <trenn@suse.de> Cc: Éric 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.c172
1 files changed, 155 insertions, 17 deletions
diff --git a/drivers/hwmon/lis3lv02d.c b/drivers/hwmon/lis3lv02d.c
index 219d2d0d5a62..3afa3afc77f3 100644
--- a/drivers/hwmon/lis3lv02d.c
+++ b/drivers/hwmon/lis3lv02d.c
@@ -3,7 +3,7 @@
3 * 3 *
4 * Copyright (C) 2007-2008 Yan Burman 4 * Copyright (C) 2007-2008 Yan Burman
5 * Copyright (C) 2008 Eric Piel 5 * Copyright (C) 2008 Eric Piel
6 * Copyright (C) 2008 Pavel Machek 6 * Copyright (C) 2008-2009 Pavel Machek
7 * 7 *
8 * This program is free software; you can redistribute it and/or modify 8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by 9 * it under the terms of the GNU General Public License as published by
@@ -35,6 +35,7 @@
35#include <linux/poll.h> 35#include <linux/poll.h>
36#include <linux/freezer.h> 36#include <linux/freezer.h>
37#include <linux/uaccess.h> 37#include <linux/uaccess.h>
38#include <linux/miscdevice.h>
38#include <acpi/acpi_drivers.h> 39#include <acpi/acpi_drivers.h>
39#include <asm/atomic.h> 40#include <asm/atomic.h>
40#include "lis3lv02d.h" 41#include "lis3lv02d.h"
@@ -55,7 +56,10 @@
55/* Maximum value our axis may get for the input device (signed 12 bits) */ 56/* Maximum value our axis may get for the input device (signed 12 bits) */
56#define MDPS_MAX_VAL 2048 57#define MDPS_MAX_VAL 2048
57 58
58struct acpi_lis3lv02d adev; 59struct acpi_lis3lv02d adev = {
60 .misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(adev.misc_wait),
61};
62
59EXPORT_SYMBOL_GPL(adev); 63EXPORT_SYMBOL_GPL(adev);
60 64
61static int lis3lv02d_add_fs(struct acpi_device *device); 65static int lis3lv02d_add_fs(struct acpi_device *device);
@@ -110,26 +114,13 @@ static void lis3lv02d_get_xyz(acpi_handle handle, int *x, int *y, int *z)
110void lis3lv02d_poweroff(acpi_handle handle) 114void lis3lv02d_poweroff(acpi_handle handle)
111{ 115{
112 adev.is_on = 0; 116 adev.is_on = 0;
113 /* disable X,Y,Z axis and power down */
114 adev.write(handle, CTRL_REG1, 0x00);
115} 117}
116EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); 118EXPORT_SYMBOL_GPL(lis3lv02d_poweroff);
117 119
118void lis3lv02d_poweron(acpi_handle handle) 120void lis3lv02d_poweron(acpi_handle handle)
119{ 121{
120 u8 val;
121
122 adev.is_on = 1; 122 adev.is_on = 1;
123 adev.init(handle); 123 adev.init(handle);
124 adev.write(handle, FF_WU_CFG, 0);
125 /*
126 * BDU: LSB and MSB values are not updated until both have been read.
127 * So the value read will always be correct.
128 * IEN: Interrupt for free-fall and DD, not for data-ready.
129 */
130 adev.read(handle, CTRL_REG2, &val);
131 val |= CTRL2_BDU | CTRL2_IEN;
132 adev.write(handle, CTRL_REG2, val);
133} 124}
134EXPORT_SYMBOL_GPL(lis3lv02d_poweron); 125EXPORT_SYMBOL_GPL(lis3lv02d_poweron);
135 126
@@ -162,6 +153,140 @@ static void lis3lv02d_decrease_use(struct acpi_lis3lv02d *dev)
162 mutex_unlock(&dev->lock); 153 mutex_unlock(&dev->lock);
163} 154}
164 155
156static irqreturn_t lis302dl_interrupt(int irq, void *dummy)
157{
158 /*
159 * Be careful: on some HP laptops the bios force DD when on battery and
160 * the lid is closed. This leads to interrupts as soon as a little move
161 * is done.
162 */
163 atomic_inc(&adev.count);
164
165 wake_up_interruptible(&adev.misc_wait);
166 kill_fasync(&adev.async_queue, SIGIO, POLL_IN);
167 return IRQ_HANDLED;
168}
169
170static int lis3lv02d_misc_open(struct inode *inode, struct file *file)
171{
172 int ret;
173
174 if (test_and_set_bit(0, &adev.misc_opened))
175 return -EBUSY; /* already open */
176
177 atomic_set(&adev.count, 0);
178
179 /*
180 * The sensor can generate interrupts for free-fall and direction
181 * detection (distinguishable with FF_WU_SRC and DD_SRC) but to keep
182 * the things simple and _fast_ we activate it only for free-fall, so
183 * no need to read register (very slow with ACPI). For the same reason,
184 * we forbid shared interrupts.
185 *
186 * IRQF_TRIGGER_RISING seems pointless on HP laptops because the
187 * io-apic is not configurable (and generates a warning) but I keep it
188 * in case of support for other hardware.
189 */
190 ret = request_irq(adev.irq, lis302dl_interrupt, IRQF_TRIGGER_RISING,
191 DRIVER_NAME, &adev);
192
193 if (ret) {
194 clear_bit(0, &adev.misc_opened);
195 printk(KERN_ERR DRIVER_NAME ": IRQ%d allocation failed\n", adev.irq);
196 return -EBUSY;
197 }
198 lis3lv02d_increase_use(&adev);
199 printk("lis3: registered interrupt %d\n", adev.irq);
200 return 0;
201}
202
203static int lis3lv02d_misc_release(struct inode *inode, struct file *file)
204{
205 fasync_helper(-1, file, 0, &adev.async_queue);
206 lis3lv02d_decrease_use(&adev);
207 free_irq(adev.irq, &adev);
208 clear_bit(0, &adev.misc_opened); /* release the device */
209 return 0;
210}
211
212static ssize_t lis3lv02d_misc_read(struct file *file, char __user *buf,
213 size_t count, loff_t *pos)
214{
215 DECLARE_WAITQUEUE(wait, current);
216 u32 data;
217 unsigned char byte_data;
218 ssize_t retval = 1;
219
220 if (count < 1)
221 return -EINVAL;
222
223 add_wait_queue(&adev.misc_wait, &wait);
224 while (true) {
225 set_current_state(TASK_INTERRUPTIBLE);
226 data = atomic_xchg(&adev.count, 0);
227 if (data)
228 break;
229
230 if (file->f_flags & O_NONBLOCK) {
231 retval = -EAGAIN;
232 goto out;
233 }
234
235 if (signal_pending(current)) {
236 retval = -ERESTARTSYS;
237 goto out;
238 }
239
240 schedule();
241 }
242
243 if (data < 255)
244 byte_data = data;
245 else
246 byte_data = 255;
247
248 /* make sure we are not going into copy_to_user() with
249 * TASK_INTERRUPTIBLE state */
250 set_current_state(TASK_RUNNING);
251 if (copy_to_user(buf, &byte_data, sizeof(byte_data)))
252 retval = -EFAULT;
253
254out:
255 __set_current_state(TASK_RUNNING);
256 remove_wait_queue(&adev.misc_wait, &wait);
257
258 return retval;
259}
260
261static unsigned int lis3lv02d_misc_poll(struct file *file, poll_table *wait)
262{
263 poll_wait(file, &adev.misc_wait, wait);
264 if (atomic_read(&adev.count))
265 return POLLIN | POLLRDNORM;
266 return 0;
267}
268
269static int lis3lv02d_misc_fasync(int fd, struct file *file, int on)
270{
271 return fasync_helper(fd, file, on, &adev.async_queue);
272}
273
274static const struct file_operations lis3lv02d_misc_fops = {
275 .owner = THIS_MODULE,
276 .llseek = no_llseek,
277 .read = lis3lv02d_misc_read,
278 .open = lis3lv02d_misc_open,
279 .release = lis3lv02d_misc_release,
280 .poll = lis3lv02d_misc_poll,
281 .fasync = lis3lv02d_misc_fasync,
282};
283
284static struct miscdevice lis3lv02d_misc_device = {
285 .minor = MISC_DYNAMIC_MINOR,
286 .name = "freefall",
287 .fops = &lis3lv02d_misc_fops,
288};
289
165/** 290/**
166 * lis3lv02d_joystick_kthread - Kthread polling function 291 * lis3lv02d_joystick_kthread - Kthread polling function
167 * @data: unused - here to conform to threadfn prototype 292 * @data: unused - here to conform to threadfn prototype
@@ -203,7 +328,6 @@ static void lis3lv02d_joystick_close(struct input_dev *input)
203 lis3lv02d_decrease_use(&adev); 328 lis3lv02d_decrease_use(&adev);
204} 329}
205 330
206
207static inline void lis3lv02d_calibrate_joystick(void) 331static inline void lis3lv02d_calibrate_joystick(void)
208{ 332{
209 lis3lv02d_get_xyz(adev.device->handle, &adev.xcalib, &adev.ycalib, &adev.zcalib); 333 lis3lv02d_get_xyz(adev.device->handle, &adev.xcalib, &adev.ycalib, &adev.zcalib);
@@ -250,6 +374,7 @@ void lis3lv02d_joystick_disable(void)
250 if (!adev.idev) 374 if (!adev.idev)
251 return; 375 return;
252 376
377 misc_deregister(&lis3lv02d_misc_device);
253 input_unregister_device(adev.idev); 378 input_unregister_device(adev.idev);
254 adev.idev = NULL; 379 adev.idev = NULL;
255} 380}
@@ -268,6 +393,19 @@ int lis3lv02d_init_device(struct acpi_lis3lv02d *dev)
268 if (lis3lv02d_joystick_enable()) 393 if (lis3lv02d_joystick_enable())
269 printk(KERN_ERR DRIVER_NAME ": joystick initialization failed\n"); 394 printk(KERN_ERR DRIVER_NAME ": joystick initialization failed\n");
270 395
396 printk("lis3_init_device: irq %d\n", dev->irq);
397
398 /* if we did not get an IRQ from ACPI - we have nothing more to do */
399 if (!dev->irq) {
400 printk(KERN_ERR DRIVER_NAME
401 ": No IRQ in ACPI. Disabling /dev/freefall\n");
402 goto out;
403 }
404
405 printk("lis3: registering device\n");
406 if (misc_register(&lis3lv02d_misc_device))
407 printk(KERN_ERR DRIVER_NAME ": misc_register failed\n");
408out:
271 lis3lv02d_decrease_use(dev); 409 lis3lv02d_decrease_use(dev);
272 return 0; 410 return 0;
273} 411}
@@ -351,6 +489,6 @@ int lis3lv02d_remove_fs(void)
351EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); 489EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs);
352 490
353MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver"); 491MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver");
354MODULE_AUTHOR("Yan Burman and Eric Piel"); 492MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek");
355MODULE_LICENSE("GPL"); 493MODULE_LICENSE("GPL");
356 494