Skip to content

Commit

Permalink
hp_accel: do not call ACPI from invalid context
Browse files Browse the repository at this point in the history
The LED on HP notebooks is connected through ACPI.  That unfortunately
means that it needs to be delayed by using schedule_work() to avoid
calling the ACPI interpreter from an invalid context.

[akpm@linux-foundation.org: use flush_work() rather than sort-of reimplementing it]
Signed-off-by: Pavel Machek <pavel@suse.cz>
Cc: Éric Piel <eric.piel@tremplin-utc.net>
Cc: Len Brown <lenb@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
Pavel Machek authored and Linus Torvalds committed Jan 16, 2009
1 parent 219beb2 commit 9e1c9d8
Showing 1 changed file with 49 additions and 19 deletions.
68 changes: 49 additions & 19 deletions drivers/hwmon/hp_accel.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Copyright (C) 2007-2008 Yan Burman
* Copyright (C) 2008 Eric Piel
* Copyright (C) 2008 Pavel Machek
* Copyright (C) 2008-2009 Pavel Machek
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -44,6 +44,36 @@
#define DRIVER_NAME "lis3lv02d"
#define ACPI_MDPS_CLASS "accelerometer"

/* Delayed LEDs infrastructure ------------------------------------ */

/* Special LED class that can defer work */
struct delayed_led_classdev {
struct led_classdev led_classdev;
struct work_struct work;
enum led_brightness new_brightness;

unsigned int led; /* For driver */
void (*set_brightness)(struct delayed_led_classdev *data, enum led_brightness value);
};

static inline void delayed_set_status_worker(struct work_struct *work)
{
struct delayed_led_classdev *data =
container_of(work, struct delayed_led_classdev, work);

data->set_brightness(data, data->new_brightness);
}

static inline void delayed_sysfs_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct delayed_led_classdev *data = container_of(led_cdev,
struct delayed_led_classdev, led_classdev);
data->new_brightness = brightness;
schedule_work(&data->work);
}

/* HP-specific accelerometer driver ------------------------------------ */

/* For automatic insertion of the module */
static struct acpi_device_id lis3lv02d_device_ids[] = {
Expand Down Expand Up @@ -155,28 +185,27 @@ static struct dmi_system_id lis3lv02d_dmi_ids[] = {
*/
};

static acpi_status hpled_acpi_write(acpi_handle handle, int reg)
static void hpled_set(struct delayed_led_classdev *led_cdev, enum led_brightness value)
{
acpi_handle handle = adev.device->handle;
unsigned long long ret; /* Not used when writing */
union acpi_object in_obj[1];
struct acpi_object_list args = { 1, in_obj };

in_obj[0].type = ACPI_TYPE_INTEGER;
in_obj[0].integer.value = reg;
in_obj[0].integer.value = !!value;

return acpi_evaluate_integer(handle, "ALED", &args, &ret);
}

static void hpled_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
hpled_acpi_write(adev.device->handle, !!value);
acpi_evaluate_integer(handle, "ALED", &args, &ret);
}

static struct led_classdev hpled_led = {
.name = "hp:red:hddprotection",
.default_trigger = "none",
.brightness_set = hpled_set,
static struct delayed_led_classdev hpled_led = {
.led_classdev = {
.name = "hp::hddprotect",
.default_trigger = "none",
.brightness_set = delayed_sysfs_set,
.flags = LED_CORE_SUSPENDRESUME,
},
.set_brightness = hpled_set,
};

static int lis3lv02d_add(struct acpi_device *device)
Expand Down Expand Up @@ -208,13 +237,15 @@ static int lis3lv02d_add(struct acpi_device *device)
adev.ac = lis3lv02d_axis_normal;
}

ret = led_classdev_register(NULL, &hpled_led);
INIT_WORK(&hpled_led.work, delayed_set_status_worker);
ret = led_classdev_register(NULL, &hpled_led.led_classdev);
if (ret)
return ret;

ret = lis3lv02d_init_device(&adev);
if (ret) {
led_classdev_unregister(&hpled_led);
flush_work(&hpled_led.work);
led_classdev_unregister(&hpled_led.led_classdev);
return ret;
}

Expand All @@ -229,7 +260,8 @@ static int lis3lv02d_remove(struct acpi_device *device, int type)
lis3lv02d_joystick_disable();
lis3lv02d_poweroff(device->handle);

led_classdev_unregister(&hpled_led);
flush_work(&hpled_led.work);
led_classdev_unregister(&hpled_led.led_classdev);

return lis3lv02d_remove_fs();
}
Expand All @@ -240,7 +272,6 @@ static int lis3lv02d_suspend(struct acpi_device *device, pm_message_t state)
{
/* make sure the device is off when we suspend */
lis3lv02d_poweroff(device->handle);
led_classdev_suspend(&hpled_led);
return 0;
}

Expand All @@ -253,7 +284,6 @@ static int lis3lv02d_resume(struct acpi_device *device)
else
lis3lv02d_poweroff(device->handle);
mutex_unlock(&adev.lock);
led_classdev_resume(&hpled_led);
return 0;
}
#else
Expand Down

0 comments on commit 9e1c9d8

Please sign in to comment.