diff options
-rw-r--r-- | drivers/misc/Kconfig | 2 | ||||
-rw-r--r-- | drivers/misc/thinkpad_acpi.c | 191 |
2 files changed, 138 insertions, 55 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 297a48f85446..3a5d7694b878 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig | |||
@@ -245,6 +245,8 @@ config THINKPAD_ACPI | |||
245 | select HWMON | 245 | select HWMON |
246 | select NVRAM | 246 | select NVRAM |
247 | depends on INPUT | 247 | depends on INPUT |
248 | select NEW_LEDS | ||
249 | select LEDS_CLASS | ||
248 | ---help--- | 250 | ---help--- |
249 | This is a driver for the IBM and Lenovo ThinkPad laptops. It adds | 251 | This is a driver for the IBM and Lenovo ThinkPad laptops. It adds |
250 | support for Fn-Fx key combinations, Bluetooth control, video | 252 | support for Fn-Fx key combinations, Bluetooth control, video |
diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 2b73dfafac9d..5a3fb09f10d3 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c | |||
@@ -67,6 +67,7 @@ | |||
67 | #include <linux/hwmon.h> | 67 | #include <linux/hwmon.h> |
68 | #include <linux/hwmon-sysfs.h> | 68 | #include <linux/hwmon-sysfs.h> |
69 | #include <linux/input.h> | 69 | #include <linux/input.h> |
70 | #include <linux/leds.h> | ||
70 | #include <asm/uaccess.h> | 71 | #include <asm/uaccess.h> |
71 | 72 | ||
72 | #include <linux/dmi.h> | 73 | #include <linux/dmi.h> |
@@ -85,6 +86,8 @@ | |||
85 | #define TP_CMOS_VOLUME_MUTE 2 | 86 | #define TP_CMOS_VOLUME_MUTE 2 |
86 | #define TP_CMOS_BRIGHTNESS_UP 4 | 87 | #define TP_CMOS_BRIGHTNESS_UP 4 |
87 | #define TP_CMOS_BRIGHTNESS_DOWN 5 | 88 | #define TP_CMOS_BRIGHTNESS_DOWN 5 |
89 | #define TP_CMOS_THINKLIGHT_ON 12 | ||
90 | #define TP_CMOS_THINKLIGHT_OFF 13 | ||
88 | 91 | ||
89 | /* NVRAM Addresses */ | 92 | /* NVRAM Addresses */ |
90 | enum tp_nvram_addr { | 93 | enum tp_nvram_addr { |
@@ -269,6 +272,13 @@ static enum { | |||
269 | static int experimental; | 272 | static int experimental; |
270 | static u32 dbg_level; | 273 | static u32 dbg_level; |
271 | 274 | ||
275 | /* Special LED class that can defer work */ | ||
276 | struct tpacpi_led_classdev { | ||
277 | struct led_classdev led_classdev; | ||
278 | struct work_struct work; | ||
279 | enum led_brightness new_brightness; | ||
280 | }; | ||
281 | |||
272 | /**************************************************************************** | 282 | /**************************************************************************** |
273 | **************************************************************************** | 283 | **************************************************************************** |
274 | * | 284 | * |
@@ -3237,6 +3247,39 @@ static struct ibm_struct video_driver_data = { | |||
3237 | TPACPI_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */ | 3247 | TPACPI_HANDLE(lght, root, "\\LGHT"); /* A21e, A2xm/p, T20-22, X20-21 */ |
3238 | TPACPI_HANDLE(ledb, ec, "LEDB"); /* G4x */ | 3248 | TPACPI_HANDLE(ledb, ec, "LEDB"); /* G4x */ |
3239 | 3249 | ||
3250 | static int light_get_status(void) | ||
3251 | { | ||
3252 | int status = 0; | ||
3253 | |||
3254 | if (tp_features.light_status) { | ||
3255 | if (!acpi_evalf(ec_handle, &status, "KBLT", "d")) | ||
3256 | return -EIO; | ||
3257 | return (!!status); | ||
3258 | } | ||
3259 | |||
3260 | return -ENXIO; | ||
3261 | } | ||
3262 | |||
3263 | static int light_set_status(int status) | ||
3264 | { | ||
3265 | int rc; | ||
3266 | |||
3267 | if (tp_features.light) { | ||
3268 | if (cmos_handle) { | ||
3269 | rc = acpi_evalf(cmos_handle, NULL, NULL, "vd", | ||
3270 | (status)? | ||
3271 | TP_CMOS_THINKLIGHT_ON : | ||
3272 | TP_CMOS_THINKLIGHT_OFF); | ||
3273 | } else { | ||
3274 | rc = acpi_evalf(lght_handle, NULL, NULL, "vd", | ||
3275 | (status)? 1 : 0); | ||
3276 | } | ||
3277 | return (rc)? 0 : -EIO; | ||
3278 | } | ||
3279 | |||
3280 | return -ENXIO; | ||
3281 | } | ||
3282 | |||
3240 | static int __init light_init(struct ibm_init_struct *iibm) | 3283 | static int __init light_init(struct ibm_init_struct *iibm) |
3241 | { | 3284 | { |
3242 | vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n"); | 3285 | vdbg_printk(TPACPI_DBG_INIT, "initializing light subdriver\n"); |
@@ -3263,7 +3306,7 @@ static int __init light_init(struct ibm_init_struct *iibm) | |||
3263 | static int light_read(char *p) | 3306 | static int light_read(char *p) |
3264 | { | 3307 | { |
3265 | int len = 0; | 3308 | int len = 0; |
3266 | int status = 0; | 3309 | int status; |
3267 | 3310 | ||
3268 | if (!tp_features.light) { | 3311 | if (!tp_features.light) { |
3269 | len += sprintf(p + len, "status:\t\tnot supported\n"); | 3312 | len += sprintf(p + len, "status:\t\tnot supported\n"); |
@@ -3271,8 +3314,9 @@ static int light_read(char *p) | |||
3271 | len += sprintf(p + len, "status:\t\tunknown\n"); | 3314 | len += sprintf(p + len, "status:\t\tunknown\n"); |
3272 | len += sprintf(p + len, "commands:\ton, off\n"); | 3315 | len += sprintf(p + len, "commands:\ton, off\n"); |
3273 | } else { | 3316 | } else { |
3274 | if (!acpi_evalf(ec_handle, &status, "KBLT", "d")) | 3317 | status = light_get_status(); |
3275 | return -EIO; | 3318 | if (status < 0) |
3319 | return status; | ||
3276 | len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0)); | 3320 | len += sprintf(p + len, "status:\t\t%s\n", onoff(status, 0)); |
3277 | len += sprintf(p + len, "commands:\ton, off\n"); | 3321 | len += sprintf(p + len, "commands:\ton, off\n"); |
3278 | } | 3322 | } |
@@ -3282,31 +3326,22 @@ static int light_read(char *p) | |||
3282 | 3326 | ||
3283 | static int light_write(char *buf) | 3327 | static int light_write(char *buf) |
3284 | { | 3328 | { |
3285 | int cmos_cmd, lght_cmd; | ||
3286 | char *cmd; | 3329 | char *cmd; |
3287 | int success; | 3330 | int newstatus = 0; |
3288 | 3331 | ||
3289 | if (!tp_features.light) | 3332 | if (!tp_features.light) |
3290 | return -ENODEV; | 3333 | return -ENODEV; |
3291 | 3334 | ||
3292 | while ((cmd = next_cmd(&buf))) { | 3335 | while ((cmd = next_cmd(&buf))) { |
3293 | if (strlencmp(cmd, "on") == 0) { | 3336 | if (strlencmp(cmd, "on") == 0) { |
3294 | cmos_cmd = 0x0c; | 3337 | newstatus = 1; |
3295 | lght_cmd = 1; | ||
3296 | } else if (strlencmp(cmd, "off") == 0) { | 3338 | } else if (strlencmp(cmd, "off") == 0) { |
3297 | cmos_cmd = 0x0d; | 3339 | newstatus = 0; |
3298 | lght_cmd = 0; | ||
3299 | } else | 3340 | } else |
3300 | return -EINVAL; | 3341 | return -EINVAL; |
3301 | |||
3302 | success = cmos_handle ? | ||
3303 | acpi_evalf(cmos_handle, NULL, NULL, "vd", cmos_cmd) : | ||
3304 | acpi_evalf(lght_handle, NULL, NULL, "vd", lght_cmd); | ||
3305 | if (!success) | ||
3306 | return -EIO; | ||
3307 | } | 3342 | } |
3308 | 3343 | ||
3309 | return 0; | 3344 | return light_set_status(newstatus); |
3310 | } | 3345 | } |
3311 | 3346 | ||
3312 | static struct ibm_struct light_driver_data = { | 3347 | static struct ibm_struct light_driver_data = { |
@@ -3710,6 +3745,12 @@ enum { /* For TPACPI_LED_OLD */ | |||
3710 | TPACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ | 3745 | TPACPI_LED_EC_HLMS = 0x0e, /* EC reg to select led to command */ |
3711 | }; | 3746 | }; |
3712 | 3747 | ||
3748 | enum led_status_t { | ||
3749 | TPACPI_LED_OFF = 0, | ||
3750 | TPACPI_LED_ON, | ||
3751 | TPACPI_LED_BLINK, | ||
3752 | }; | ||
3753 | |||
3713 | static enum led_access_mode led_supported; | 3754 | static enum led_access_mode led_supported; |
3714 | 3755 | ||
3715 | TPACPI_HANDLE(led, ec, "SLED", /* 570 */ | 3756 | TPACPI_HANDLE(led, ec, "SLED", /* 570 */ |
@@ -3718,6 +3759,69 @@ TPACPI_HANDLE(led, ec, "SLED", /* 570 */ | |||
3718 | "LED", /* all others */ | 3759 | "LED", /* all others */ |
3719 | ); /* R30, R31 */ | 3760 | ); /* R30, R31 */ |
3720 | 3761 | ||
3762 | static int led_get_status(unsigned int led) | ||
3763 | { | ||
3764 | int status; | ||
3765 | |||
3766 | switch (led_supported) { | ||
3767 | case TPACPI_LED_570: | ||
3768 | if (!acpi_evalf(ec_handle, | ||
3769 | &status, "GLED", "dd", 1 << led)) | ||
3770 | return -EIO; | ||
3771 | return (status == 0)? | ||
3772 | TPACPI_LED_OFF : | ||
3773 | ((status == 1)? | ||
3774 | TPACPI_LED_ON : | ||
3775 | TPACPI_LED_BLINK); | ||
3776 | default: | ||
3777 | return -ENXIO; | ||
3778 | } | ||
3779 | |||
3780 | /* not reached */ | ||
3781 | } | ||
3782 | |||
3783 | static int led_set_status(unsigned int led, enum led_status_t ledstatus) | ||
3784 | { | ||
3785 | /* off, on, blink. Index is led_status_t */ | ||
3786 | static const int const led_sled_arg1[] = { 0, 1, 3 }; | ||
3787 | static const int const led_exp_hlbl[] = { 0, 0, 1 }; /* led# * */ | ||
3788 | static const int const led_exp_hlcl[] = { 0, 1, 1 }; /* led# * */ | ||
3789 | static const int const led_led_arg1[] = { 0, 0x80, 0xc0 }; | ||
3790 | |||
3791 | int rc = 0; | ||
3792 | |||
3793 | switch (led_supported) { | ||
3794 | case TPACPI_LED_570: | ||
3795 | /* 570 */ | ||
3796 | led = 1 << led; | ||
3797 | if (!acpi_evalf(led_handle, NULL, NULL, "vdd", | ||
3798 | led, led_sled_arg1[ledstatus])) | ||
3799 | rc = -EIO; | ||
3800 | break; | ||
3801 | case TPACPI_LED_OLD: | ||
3802 | /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ | ||
3803 | led = 1 << led; | ||
3804 | rc = ec_write(TPACPI_LED_EC_HLMS, led); | ||
3805 | if (rc >= 0) | ||
3806 | rc = ec_write(TPACPI_LED_EC_HLBL, | ||
3807 | led * led_exp_hlbl[ledstatus]); | ||
3808 | if (rc >= 0) | ||
3809 | rc = ec_write(TPACPI_LED_EC_HLCL, | ||
3810 | led * led_exp_hlcl[ledstatus]); | ||
3811 | break; | ||
3812 | case TPACPI_LED_NEW: | ||
3813 | /* all others */ | ||
3814 | if (!acpi_evalf(led_handle, NULL, NULL, "vdd", | ||
3815 | led, led_led_arg1[ledstatus])) | ||
3816 | rc = -EIO; | ||
3817 | break; | ||
3818 | default: | ||
3819 | rc = -ENXIO; | ||
3820 | } | ||
3821 | |||
3822 | return rc; | ||
3823 | } | ||
3824 | |||
3721 | static int __init led_init(struct ibm_init_struct *iibm) | 3825 | static int __init led_init(struct ibm_init_struct *iibm) |
3722 | { | 3826 | { |
3723 | vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); | 3827 | vdbg_printk(TPACPI_DBG_INIT, "initializing LED subdriver\n"); |
@@ -3743,7 +3847,9 @@ static int __init led_init(struct ibm_init_struct *iibm) | |||
3743 | return (led_supported != TPACPI_LED_NONE)? 0 : 1; | 3847 | return (led_supported != TPACPI_LED_NONE)? 0 : 1; |
3744 | } | 3848 | } |
3745 | 3849 | ||
3746 | #define led_status(s) ((s) == 0 ? "off" : ((s) == 1 ? "on" : "blinking")) | 3850 | #define str_led_status(s) \ |
3851 | ((s) == TPACPI_LED_OFF ? "off" : \ | ||
3852 | ((s) == TPACPI_LED_ON ? "on" : "blinking")) | ||
3747 | 3853 | ||
3748 | static int led_read(char *p) | 3854 | static int led_read(char *p) |
3749 | { | 3855 | { |
@@ -3759,11 +3865,11 @@ static int led_read(char *p) | |||
3759 | /* 570 */ | 3865 | /* 570 */ |
3760 | int i, status; | 3866 | int i, status; |
3761 | for (i = 0; i < 8; i++) { | 3867 | for (i = 0; i < 8; i++) { |
3762 | if (!acpi_evalf(ec_handle, | 3868 | status = led_get_status(i); |
3763 | &status, "GLED", "dd", 1 << i)) | 3869 | if (status < 0) |
3764 | return -EIO; | 3870 | return -EIO; |
3765 | len += sprintf(p + len, "%d:\t\t%s\n", | 3871 | len += sprintf(p + len, "%d:\t\t%s\n", |
3766 | i, led_status(status)); | 3872 | i, str_led_status(status)); |
3767 | } | 3873 | } |
3768 | } | 3874 | } |
3769 | 3875 | ||
@@ -3773,16 +3879,11 @@ static int led_read(char *p) | |||
3773 | return len; | 3879 | return len; |
3774 | } | 3880 | } |
3775 | 3881 | ||
3776 | /* off, on, blink */ | ||
3777 | static const int led_sled_arg1[] = { 0, 1, 3 }; | ||
3778 | static const int led_exp_hlbl[] = { 0, 0, 1 }; /* led# * */ | ||
3779 | static const int led_exp_hlcl[] = { 0, 1, 1 }; /* led# * */ | ||
3780 | static const int led_led_arg1[] = { 0, 0x80, 0xc0 }; | ||
3781 | |||
3782 | static int led_write(char *buf) | 3882 | static int led_write(char *buf) |
3783 | { | 3883 | { |
3784 | char *cmd; | 3884 | char *cmd; |
3785 | int led, ind, ret; | 3885 | int led, rc; |
3886 | enum led_status_t s; | ||
3786 | 3887 | ||
3787 | if (!led_supported) | 3888 | if (!led_supported) |
3788 | return -ENODEV; | 3889 | return -ENODEV; |
@@ -3792,38 +3893,18 @@ static int led_write(char *buf) | |||
3792 | return -EINVAL; | 3893 | return -EINVAL; |
3793 | 3894 | ||
3794 | if (strstr(cmd, "off")) { | 3895 | if (strstr(cmd, "off")) { |
3795 | ind = 0; | 3896 | s = TPACPI_LED_OFF; |
3796 | } else if (strstr(cmd, "on")) { | 3897 | } else if (strstr(cmd, "on")) { |
3797 | ind = 1; | 3898 | s = TPACPI_LED_ON; |
3798 | } else if (strstr(cmd, "blink")) { | 3899 | } else if (strstr(cmd, "blink")) { |
3799 | ind = 2; | 3900 | s = TPACPI_LED_BLINK; |
3800 | } else | ||
3801 | return -EINVAL; | ||
3802 | |||
3803 | if (led_supported == TPACPI_LED_570) { | ||
3804 | /* 570 */ | ||
3805 | led = 1 << led; | ||
3806 | if (!acpi_evalf(led_handle, NULL, NULL, "vdd", | ||
3807 | led, led_sled_arg1[ind])) | ||
3808 | return -EIO; | ||
3809 | } else if (led_supported == TPACPI_LED_OLD) { | ||
3810 | /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ | ||
3811 | led = 1 << led; | ||
3812 | ret = ec_write(TPACPI_LED_EC_HLMS, led); | ||
3813 | if (ret >= 0) | ||
3814 | ret = ec_write(TPACPI_LED_EC_HLBL, | ||
3815 | led * led_exp_hlbl[ind]); | ||
3816 | if (ret >= 0) | ||
3817 | ret = ec_write(TPACPI_LED_EC_HLCL, | ||
3818 | led * led_exp_hlcl[ind]); | ||
3819 | if (ret < 0) | ||
3820 | return ret; | ||
3821 | } else { | 3901 | } else { |
3822 | /* all others */ | 3902 | return -EINVAL; |
3823 | if (!acpi_evalf(led_handle, NULL, NULL, "vdd", | ||
3824 | led, led_led_arg1[ind])) | ||
3825 | return -EIO; | ||
3826 | } | 3903 | } |
3904 | |||
3905 | rc = led_set_status(led, s); | ||
3906 | if (rc < 0) | ||
3907 | return rc; | ||
3827 | } | 3908 | } |
3828 | 3909 | ||
3829 | return 0; | 3910 | return 0; |