diff options
Diffstat (limited to 'drivers/hwmon/lis3lv02d.c')
-rw-r--r-- | drivers/hwmon/lis3lv02d.c | 195 |
1 files changed, 160 insertions, 35 deletions
diff --git a/drivers/hwmon/lis3lv02d.c b/drivers/hwmon/lis3lv02d.c index 219d2d0d5a62..8bb2158f0453 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" |
@@ -52,24 +53,14 @@ | |||
52 | * joystick. | 53 | * joystick. |
53 | */ | 54 | */ |
54 | 55 | ||
55 | /* Maximum value our axis may get for the input device (signed 12 bits) */ | 56 | struct acpi_lis3lv02d adev = { |
56 | #define MDPS_MAX_VAL 2048 | 57 | .misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(adev.misc_wait), |
58 | }; | ||
57 | 59 | ||
58 | struct acpi_lis3lv02d adev; | ||
59 | EXPORT_SYMBOL_GPL(adev); | 60 | EXPORT_SYMBOL_GPL(adev); |
60 | 61 | ||
61 | static int lis3lv02d_add_fs(struct acpi_device *device); | 62 | static int lis3lv02d_add_fs(struct acpi_device *device); |
62 | 63 | ||
63 | static s16 lis3lv02d_read_16(acpi_handle handle, int reg) | ||
64 | { | ||
65 | u8 lo, hi; | ||
66 | |||
67 | adev.read(handle, reg, &lo); | ||
68 | adev.read(handle, reg + 1, &hi); | ||
69 | /* In "12 bit right justified" mode, bit 6, bit 7, bit 8 = bit 5 */ | ||
70 | return (s16)((hi << 8) | lo); | ||
71 | } | ||
72 | |||
73 | /** | 64 | /** |
74 | * lis3lv02d_get_axis - For the given axis, give the value converted | 65 | * lis3lv02d_get_axis - For the given axis, give the value converted |
75 | * @axis: 1,2,3 - can also be negative | 66 | * @axis: 1,2,3 - can also be negative |
@@ -98,9 +89,9 @@ static void lis3lv02d_get_xyz(acpi_handle handle, int *x, int *y, int *z) | |||
98 | { | 89 | { |
99 | int position[3]; | 90 | int position[3]; |
100 | 91 | ||
101 | position[0] = lis3lv02d_read_16(handle, OUTX_L); | 92 | position[0] = adev.read_data(handle, OUTX); |
102 | position[1] = lis3lv02d_read_16(handle, OUTY_L); | 93 | position[1] = adev.read_data(handle, OUTY); |
103 | position[2] = lis3lv02d_read_16(handle, OUTZ_L); | 94 | position[2] = adev.read_data(handle, OUTZ); |
104 | 95 | ||
105 | *x = lis3lv02d_get_axis(adev.ac.x, position); | 96 | *x = lis3lv02d_get_axis(adev.ac.x, position); |
106 | *y = lis3lv02d_get_axis(adev.ac.y, position); | 97 | *y = lis3lv02d_get_axis(adev.ac.y, position); |
@@ -110,26 +101,13 @@ static void lis3lv02d_get_xyz(acpi_handle handle, int *x, int *y, int *z) | |||
110 | void lis3lv02d_poweroff(acpi_handle handle) | 101 | void lis3lv02d_poweroff(acpi_handle handle) |
111 | { | 102 | { |
112 | adev.is_on = 0; | 103 | adev.is_on = 0; |
113 | /* disable X,Y,Z axis and power down */ | ||
114 | adev.write(handle, CTRL_REG1, 0x00); | ||
115 | } | 104 | } |
116 | EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); | 105 | EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); |
117 | 106 | ||
118 | void lis3lv02d_poweron(acpi_handle handle) | 107 | void lis3lv02d_poweron(acpi_handle handle) |
119 | { | 108 | { |
120 | u8 val; | ||
121 | |||
122 | adev.is_on = 1; | 109 | adev.is_on = 1; |
123 | adev.init(handle); | 110 | 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 | } | 111 | } |
134 | EXPORT_SYMBOL_GPL(lis3lv02d_poweron); | 112 | EXPORT_SYMBOL_GPL(lis3lv02d_poweron); |
135 | 113 | ||
@@ -162,6 +140,140 @@ static void lis3lv02d_decrease_use(struct acpi_lis3lv02d *dev) | |||
162 | mutex_unlock(&dev->lock); | 140 | mutex_unlock(&dev->lock); |
163 | } | 141 | } |
164 | 142 | ||
143 | static irqreturn_t lis302dl_interrupt(int irq, void *dummy) | ||
144 | { | ||
145 | /* | ||
146 | * Be careful: on some HP laptops the bios force DD when on battery and | ||
147 | * the lid is closed. This leads to interrupts as soon as a little move | ||
148 | * is done. | ||
149 | */ | ||
150 | atomic_inc(&adev.count); | ||
151 | |||
152 | wake_up_interruptible(&adev.misc_wait); | ||
153 | kill_fasync(&adev.async_queue, SIGIO, POLL_IN); | ||
154 | return IRQ_HANDLED; | ||
155 | } | ||
156 | |||
157 | static int lis3lv02d_misc_open(struct inode *inode, struct file *file) | ||
158 | { | ||
159 | int ret; | ||
160 | |||
161 | if (test_and_set_bit(0, &adev.misc_opened)) | ||
162 | return -EBUSY; /* already open */ | ||
163 | |||
164 | atomic_set(&adev.count, 0); | ||
165 | |||
166 | /* | ||
167 | * The sensor can generate interrupts for free-fall and direction | ||
168 | * detection (distinguishable with FF_WU_SRC and DD_SRC) but to keep | ||
169 | * the things simple and _fast_ we activate it only for free-fall, so | ||
170 | * no need to read register (very slow with ACPI). For the same reason, | ||
171 | * we forbid shared interrupts. | ||
172 | * | ||
173 | * IRQF_TRIGGER_RISING seems pointless on HP laptops because the | ||
174 | * io-apic is not configurable (and generates a warning) but I keep it | ||
175 | * in case of support for other hardware. | ||
176 | */ | ||
177 | ret = request_irq(adev.irq, lis302dl_interrupt, IRQF_TRIGGER_RISING, | ||
178 | DRIVER_NAME, &adev); | ||
179 | |||
180 | if (ret) { | ||
181 | clear_bit(0, &adev.misc_opened); | ||
182 | printk(KERN_ERR DRIVER_NAME ": IRQ%d allocation failed\n", adev.irq); | ||
183 | return -EBUSY; | ||
184 | } | ||
185 | lis3lv02d_increase_use(&adev); | ||
186 | printk("lis3: registered interrupt %d\n", adev.irq); | ||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | static int lis3lv02d_misc_release(struct inode *inode, struct file *file) | ||
191 | { | ||
192 | fasync_helper(-1, file, 0, &adev.async_queue); | ||
193 | lis3lv02d_decrease_use(&adev); | ||
194 | free_irq(adev.irq, &adev); | ||
195 | clear_bit(0, &adev.misc_opened); /* release the device */ | ||
196 | return 0; | ||
197 | } | ||
198 | |||
199 | static ssize_t lis3lv02d_misc_read(struct file *file, char __user *buf, | ||
200 | size_t count, loff_t *pos) | ||
201 | { | ||
202 | DECLARE_WAITQUEUE(wait, current); | ||
203 | u32 data; | ||
204 | unsigned char byte_data; | ||
205 | ssize_t retval = 1; | ||
206 | |||
207 | if (count < 1) | ||
208 | return -EINVAL; | ||
209 | |||
210 | add_wait_queue(&adev.misc_wait, &wait); | ||
211 | while (true) { | ||
212 | set_current_state(TASK_INTERRUPTIBLE); | ||
213 | data = atomic_xchg(&adev.count, 0); | ||
214 | if (data) | ||
215 | break; | ||
216 | |||
217 | if (file->f_flags & O_NONBLOCK) { | ||
218 | retval = -EAGAIN; | ||
219 | goto out; | ||
220 | } | ||
221 | |||
222 | if (signal_pending(current)) { | ||
223 | retval = -ERESTARTSYS; | ||
224 | goto out; | ||
225 | } | ||
226 | |||
227 | schedule(); | ||
228 | } | ||
229 | |||
230 | if (data < 255) | ||
231 | byte_data = data; | ||
232 | else | ||
233 | byte_data = 255; | ||
234 | |||
235 | /* make sure we are not going into copy_to_user() with | ||
236 | * TASK_INTERRUPTIBLE state */ | ||
237 | set_current_state(TASK_RUNNING); | ||
238 | if (copy_to_user(buf, &byte_data, sizeof(byte_data))) | ||
239 | retval = -EFAULT; | ||
240 | |||
241 | out: | ||
242 | __set_current_state(TASK_RUNNING); | ||
243 | remove_wait_queue(&adev.misc_wait, &wait); | ||
244 | |||
245 | return retval; | ||
246 | } | ||
247 | |||
248 | static unsigned int lis3lv02d_misc_poll(struct file *file, poll_table *wait) | ||
249 | { | ||
250 | poll_wait(file, &adev.misc_wait, wait); | ||
251 | if (atomic_read(&adev.count)) | ||
252 | return POLLIN | POLLRDNORM; | ||
253 | return 0; | ||
254 | } | ||
255 | |||
256 | static int lis3lv02d_misc_fasync(int fd, struct file *file, int on) | ||
257 | { | ||
258 | return fasync_helper(fd, file, on, &adev.async_queue); | ||
259 | } | ||
260 | |||
261 | static const struct file_operations lis3lv02d_misc_fops = { | ||
262 | .owner = THIS_MODULE, | ||
263 | .llseek = no_llseek, | ||
264 | .read = lis3lv02d_misc_read, | ||
265 | .open = lis3lv02d_misc_open, | ||
266 | .release = lis3lv02d_misc_release, | ||
267 | .poll = lis3lv02d_misc_poll, | ||
268 | .fasync = lis3lv02d_misc_fasync, | ||
269 | }; | ||
270 | |||
271 | static struct miscdevice lis3lv02d_misc_device = { | ||
272 | .minor = MISC_DYNAMIC_MINOR, | ||
273 | .name = "freefall", | ||
274 | .fops = &lis3lv02d_misc_fops, | ||
275 | }; | ||
276 | |||
165 | /** | 277 | /** |
166 | * lis3lv02d_joystick_kthread - Kthread polling function | 278 | * lis3lv02d_joystick_kthread - Kthread polling function |
167 | * @data: unused - here to conform to threadfn prototype | 279 | * @data: unused - here to conform to threadfn prototype |
@@ -203,7 +315,6 @@ static void lis3lv02d_joystick_close(struct input_dev *input) | |||
203 | lis3lv02d_decrease_use(&adev); | 315 | lis3lv02d_decrease_use(&adev); |
204 | } | 316 | } |
205 | 317 | ||
206 | |||
207 | static inline void lis3lv02d_calibrate_joystick(void) | 318 | static inline void lis3lv02d_calibrate_joystick(void) |
208 | { | 319 | { |
209 | lis3lv02d_get_xyz(adev.device->handle, &adev.xcalib, &adev.ycalib, &adev.zcalib); | 320 | lis3lv02d_get_xyz(adev.device->handle, &adev.xcalib, &adev.ycalib, &adev.zcalib); |
@@ -231,9 +342,9 @@ int lis3lv02d_joystick_enable(void) | |||
231 | adev.idev->close = lis3lv02d_joystick_close; | 342 | adev.idev->close = lis3lv02d_joystick_close; |
232 | 343 | ||
233 | set_bit(EV_ABS, adev.idev->evbit); | 344 | set_bit(EV_ABS, adev.idev->evbit); |
234 | input_set_abs_params(adev.idev, ABS_X, -MDPS_MAX_VAL, MDPS_MAX_VAL, 3, 3); | 345 | input_set_abs_params(adev.idev, ABS_X, -adev.mdps_max_val, adev.mdps_max_val, 3, 3); |
235 | input_set_abs_params(adev.idev, ABS_Y, -MDPS_MAX_VAL, MDPS_MAX_VAL, 3, 3); | 346 | input_set_abs_params(adev.idev, ABS_Y, -adev.mdps_max_val, adev.mdps_max_val, 3, 3); |
236 | input_set_abs_params(adev.idev, ABS_Z, -MDPS_MAX_VAL, MDPS_MAX_VAL, 3, 3); | 347 | input_set_abs_params(adev.idev, ABS_Z, -adev.mdps_max_val, adev.mdps_max_val, 3, 3); |
237 | 348 | ||
238 | err = input_register_device(adev.idev); | 349 | err = input_register_device(adev.idev); |
239 | if (err) { | 350 | if (err) { |
@@ -250,6 +361,7 @@ void lis3lv02d_joystick_disable(void) | |||
250 | if (!adev.idev) | 361 | if (!adev.idev) |
251 | return; | 362 | return; |
252 | 363 | ||
364 | misc_deregister(&lis3lv02d_misc_device); | ||
253 | input_unregister_device(adev.idev); | 365 | input_unregister_device(adev.idev); |
254 | adev.idev = NULL; | 366 | adev.idev = NULL; |
255 | } | 367 | } |
@@ -268,6 +380,19 @@ int lis3lv02d_init_device(struct acpi_lis3lv02d *dev) | |||
268 | if (lis3lv02d_joystick_enable()) | 380 | if (lis3lv02d_joystick_enable()) |
269 | printk(KERN_ERR DRIVER_NAME ": joystick initialization failed\n"); | 381 | printk(KERN_ERR DRIVER_NAME ": joystick initialization failed\n"); |
270 | 382 | ||
383 | printk("lis3_init_device: irq %d\n", dev->irq); | ||
384 | |||
385 | /* if we did not get an IRQ from ACPI - we have nothing more to do */ | ||
386 | if (!dev->irq) { | ||
387 | printk(KERN_ERR DRIVER_NAME | ||
388 | ": No IRQ in ACPI. Disabling /dev/freefall\n"); | ||
389 | goto out; | ||
390 | } | ||
391 | |||
392 | printk("lis3: registering device\n"); | ||
393 | if (misc_register(&lis3lv02d_misc_device)) | ||
394 | printk(KERN_ERR DRIVER_NAME ": misc_register failed\n"); | ||
395 | out: | ||
271 | lis3lv02d_decrease_use(dev); | 396 | lis3lv02d_decrease_use(dev); |
272 | return 0; | 397 | return 0; |
273 | } | 398 | } |
@@ -351,6 +476,6 @@ int lis3lv02d_remove_fs(void) | |||
351 | EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); | 476 | EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); |
352 | 477 | ||
353 | MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver"); | 478 | MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver"); |
354 | MODULE_AUTHOR("Yan Burman and Eric Piel"); | 479 | MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); |
355 | MODULE_LICENSE("GPL"); | 480 | MODULE_LICENSE("GPL"); |
356 | 481 | ||