diff options
-rw-r--r-- | Documentation/hwmon/hpfall.c | 101 | ||||
-rw-r--r-- | Documentation/hwmon/lis3lv02d | 8 | ||||
-rw-r--r-- | drivers/hwmon/hp_accel.c | 27 | ||||
-rw-r--r-- | drivers/hwmon/lis3lv02d.c | 172 | ||||
-rw-r--r-- | drivers/hwmon/lis3lv02d.h | 5 |
5 files changed, 296 insertions, 17 deletions
diff --git a/Documentation/hwmon/hpfall.c b/Documentation/hwmon/hpfall.c new file mode 100644 index 000000000000..bbea1ccfd46a --- /dev/null +++ b/Documentation/hwmon/hpfall.c | |||
@@ -0,0 +1,101 @@ | |||
1 | /* Disk protection for HP machines. | ||
2 | * | ||
3 | * Copyright 2008 Eric Piel | ||
4 | * Copyright 2009 Pavel Machek <pavel@suse.cz> | ||
5 | * | ||
6 | * GPLv2. | ||
7 | */ | ||
8 | |||
9 | #include <stdio.h> | ||
10 | #include <stdlib.h> | ||
11 | #include <unistd.h> | ||
12 | #include <fcntl.h> | ||
13 | #include <sys/stat.h> | ||
14 | #include <sys/types.h> | ||
15 | #include <string.h> | ||
16 | #include <stdint.h> | ||
17 | #include <errno.h> | ||
18 | #include <signal.h> | ||
19 | |||
20 | void write_int(char *path, int i) | ||
21 | { | ||
22 | char buf[1024]; | ||
23 | int fd = open(path, O_RDWR); | ||
24 | if (fd < 0) { | ||
25 | perror("open"); | ||
26 | exit(1); | ||
27 | } | ||
28 | sprintf(buf, "%d", i); | ||
29 | if (write(fd, buf, strlen(buf)) != strlen(buf)) { | ||
30 | perror("write"); | ||
31 | exit(1); | ||
32 | } | ||
33 | close(fd); | ||
34 | } | ||
35 | |||
36 | void set_led(int on) | ||
37 | { | ||
38 | write_int("/sys/class/leds/hp::hddprotect/brightness", on); | ||
39 | } | ||
40 | |||
41 | void protect(int seconds) | ||
42 | { | ||
43 | write_int("/sys/block/sda/device/unload_heads", seconds*1000); | ||
44 | } | ||
45 | |||
46 | int on_ac(void) | ||
47 | { | ||
48 | // /sys/class/power_supply/AC0/online | ||
49 | } | ||
50 | |||
51 | int lid_open(void) | ||
52 | { | ||
53 | // /proc/acpi/button/lid/LID/state | ||
54 | } | ||
55 | |||
56 | void ignore_me(void) | ||
57 | { | ||
58 | protect(0); | ||
59 | set_led(0); | ||
60 | |||
61 | } | ||
62 | |||
63 | int main(int argc, char* argv[]) | ||
64 | { | ||
65 | int fd, ret; | ||
66 | |||
67 | fd = open("/dev/freefall", O_RDONLY); | ||
68 | if (fd < 0) { | ||
69 | perror("open"); | ||
70 | return EXIT_FAILURE; | ||
71 | } | ||
72 | |||
73 | signal(SIGALRM, ignore_me); | ||
74 | |||
75 | for (;;) { | ||
76 | unsigned char count; | ||
77 | |||
78 | ret = read(fd, &count, sizeof(count)); | ||
79 | alarm(0); | ||
80 | if ((ret == -1) && (errno == EINTR)) { | ||
81 | /* Alarm expired, time to unpark the heads */ | ||
82 | continue; | ||
83 | } | ||
84 | |||
85 | if (ret != sizeof(count)) { | ||
86 | perror("read"); | ||
87 | break; | ||
88 | } | ||
89 | |||
90 | protect(21); | ||
91 | set_led(1); | ||
92 | if (1 || on_ac() || lid_open()) { | ||
93 | alarm(2); | ||
94 | } else { | ||
95 | alarm(20); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | close(fd); | ||
100 | return EXIT_SUCCESS; | ||
101 | } | ||
diff --git a/Documentation/hwmon/lis3lv02d b/Documentation/hwmon/lis3lv02d index 0fcfc4a7ccdc..287f8c902656 100644 --- a/Documentation/hwmon/lis3lv02d +++ b/Documentation/hwmon/lis3lv02d | |||
@@ -33,6 +33,14 @@ rate - reports the sampling rate of the accelerometer device in HZ | |||
33 | This driver also provides an absolute input class device, allowing | 33 | This driver also provides an absolute input class device, allowing |
34 | the laptop to act as a pinball machine-esque joystick. | 34 | the laptop to act as a pinball machine-esque joystick. |
35 | 35 | ||
36 | Another feature of the driver is misc device called "freefall" that | ||
37 | acts similar to /dev/rtc and reacts on free-fall interrupts received | ||
38 | from the device. It supports blocking operations, poll/select and | ||
39 | fasync operation modes. You must read 1 bytes from the device. The | ||
40 | result is number of free-fall interrupts since the last successful | ||
41 | read (or 255 if number of interrupts would not fit). | ||
42 | |||
43 | |||
36 | Axes orientation | 44 | Axes orientation |
37 | ---------------- | 45 | ---------------- |
38 | 46 | ||
diff --git a/drivers/hwmon/hp_accel.c b/drivers/hwmon/hp_accel.c index abf4dfc8ec22..dcefe1d2adbd 100644 --- a/drivers/hwmon/hp_accel.c +++ b/drivers/hwmon/hp_accel.c | |||
@@ -213,6 +213,30 @@ static struct delayed_led_classdev hpled_led = { | |||
213 | .set_brightness = hpled_set, | 213 | .set_brightness = hpled_set, |
214 | }; | 214 | }; |
215 | 215 | ||
216 | static acpi_status | ||
217 | lis3lv02d_get_resource(struct acpi_resource *resource, void *context) | ||
218 | { | ||
219 | if (resource->type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) { | ||
220 | struct acpi_resource_extended_irq *irq; | ||
221 | u32 *device_irq = context; | ||
222 | |||
223 | irq = &resource->data.extended_irq; | ||
224 | *device_irq = irq->interrupts[0]; | ||
225 | } | ||
226 | |||
227 | return AE_OK; | ||
228 | } | ||
229 | |||
230 | static void lis3lv02d_enum_resources(struct acpi_device *device) | ||
231 | { | ||
232 | acpi_status status; | ||
233 | |||
234 | status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, | ||
235 | lis3lv02d_get_resource, &adev.irq); | ||
236 | if (ACPI_FAILURE(status)) | ||
237 | printk(KERN_DEBUG DRIVER_NAME ": Error getting resources\n"); | ||
238 | } | ||
239 | |||
216 | static int lis3lv02d_add(struct acpi_device *device) | 240 | static int lis3lv02d_add(struct acpi_device *device) |
217 | { | 241 | { |
218 | u8 val; | 242 | u8 val; |
@@ -247,6 +271,9 @@ static int lis3lv02d_add(struct acpi_device *device) | |||
247 | if (ret) | 271 | if (ret) |
248 | return ret; | 272 | return ret; |
249 | 273 | ||
274 | /* obtain IRQ number of our device from ACPI */ | ||
275 | lis3lv02d_enum_resources(adev.device); | ||
276 | |||
250 | ret = lis3lv02d_init_device(&adev); | 277 | ret = lis3lv02d_init_device(&adev); |
251 | if (ret) { | 278 | if (ret) { |
252 | flush_work(&hpled_led.work); | 279 | flush_work(&hpled_led.work); |
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 | ||
58 | struct acpi_lis3lv02d adev; | 59 | struct acpi_lis3lv02d adev = { |
60 | .misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(adev.misc_wait), | ||
61 | }; | ||
62 | |||
59 | EXPORT_SYMBOL_GPL(adev); | 63 | EXPORT_SYMBOL_GPL(adev); |
60 | 64 | ||
61 | static int lis3lv02d_add_fs(struct acpi_device *device); | 65 | static 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) | |||
110 | void lis3lv02d_poweroff(acpi_handle handle) | 114 | void 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 | } |
116 | EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); | 118 | EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); |
117 | 119 | ||
118 | void lis3lv02d_poweron(acpi_handle handle) | 120 | void 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 | } |
134 | EXPORT_SYMBOL_GPL(lis3lv02d_poweron); | 125 | EXPORT_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 | ||
156 | static 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 | |||
170 | static 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 | |||
203 | static 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 | |||
212 | static 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 | |||
254 | out: | ||
255 | __set_current_state(TASK_RUNNING); | ||
256 | remove_wait_queue(&adev.misc_wait, &wait); | ||
257 | |||
258 | return retval; | ||
259 | } | ||
260 | |||
261 | static 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 | |||
269 | static int lis3lv02d_misc_fasync(int fd, struct file *file, int on) | ||
270 | { | ||
271 | return fasync_helper(fd, file, on, &adev.async_queue); | ||
272 | } | ||
273 | |||
274 | static 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 | |||
284 | static 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 | |||
207 | static inline void lis3lv02d_calibrate_joystick(void) | 331 | static 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"); | ||
408 | out: | ||
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) | |||
351 | EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); | 489 | EXPORT_SYMBOL_GPL(lis3lv02d_remove_fs); |
352 | 490 | ||
353 | MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver"); | 491 | MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver"); |
354 | MODULE_AUTHOR("Yan Burman and Eric Piel"); | 492 | MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek"); |
355 | MODULE_LICENSE("GPL"); | 493 | MODULE_LICENSE("GPL"); |
356 | 494 | ||
diff --git a/drivers/hwmon/lis3lv02d.h b/drivers/hwmon/lis3lv02d.h index 223f1c0763bb..2e7597c42d80 100644 --- a/drivers/hwmon/lis3lv02d.h +++ b/drivers/hwmon/lis3lv02d.h | |||
@@ -170,6 +170,11 @@ struct acpi_lis3lv02d { | |||
170 | unsigned char is_on; /* whether the device is on or off */ | 170 | unsigned char is_on; /* whether the device is on or off */ |
171 | unsigned char usage; /* usage counter */ | 171 | unsigned char usage; /* usage counter */ |
172 | struct axis_conversion ac; /* hw -> logical axis */ | 172 | struct axis_conversion ac; /* hw -> logical axis */ |
173 | |||
174 | u32 irq; /* IRQ number */ | ||
175 | struct fasync_struct *async_queue; /* queue for the misc device */ | ||
176 | wait_queue_head_t misc_wait; /* Wait queue for the misc device */ | ||
177 | unsigned long misc_opened; /* bit0: whether the device is open */ | ||
173 | }; | 178 | }; |
174 | 179 | ||
175 | int lis3lv02d_init_device(struct acpi_lis3lv02d *dev); | 180 | int lis3lv02d_init_device(struct acpi_lis3lv02d *dev); |