Commit 3a407086 authored by Tony Vroon's avatar Tony Vroon Committed by Len Brown

fujitsu-laptop: Add BL power, LED control and radio state information

The FUNC interface in the Fujitsu-Siemens DSDT was unused until now. It exposes
state information that is now reported in additional platform files (whether the
radios are killed by the hardware switch or operational, whether the machine is
docked and whether the lid is open).
Support for the backlight class is now extended with the ability to power the
backlight on & off. Optional support for the LED class allows the keyboard
headlamps found on the U810 netbook and the Fujitsu logo illumination on the
P8010 notebook to be turned on & off.

This was fed through checkpatch.pl and tested on the S6420, P8010 & U810 platforms.
Signed-off-by: default avatarStephen Gildea <stepheng+linux@gildea.com>
Tested-by: default avatarStephen Gildea <stepheng+linux@gildea.com>
Tested-by: default avatarJulian Brown <jules@panic.cs-bristol.org.uk>
Signed-off-by: default avatarTony Vroon <tony@linx.net>
Tested-by: default avatarPeter Gruber <nokos@gmx.net>
Acked-by: default avatarJonathan Woithe <jwoithe@physics.adelaide.edu.au>
Signed-off-by: default avatarLen Brown <len.brown@intel.com>
parent 4a6908a3
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
/* /*
Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@physics.adelaide.edu.au> Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@physics.adelaide.edu.au>
Copyright (C) 2008 Peter Gruber <nokos@gmx.net> Copyright (C) 2008 Peter Gruber <nokos@gmx.net>
Copyright (C) 2008 Tony Vroon <tony@linx.net>
Based on earlier work: Based on earlier work:
Copyright (C) 2003 Shane Spencer <shane@bogomip.com> Copyright (C) 2003 Shane Spencer <shane@bogomip.com>
Adrian Yee <brewt-fujitsu@brewt.org> Adrian Yee <brewt-fujitsu@brewt.org>
...@@ -65,8 +66,11 @@ ...@@ -65,8 +66,11 @@
#include <linux/kfifo.h> #include <linux/kfifo.h>
#include <linux/video_output.h> #include <linux/video_output.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#ifdef CONFIG_LEDS_CLASS
#include <linux/leds.h>
#endif
#define FUJITSU_DRIVER_VERSION "0.4.3" #define FUJITSU_DRIVER_VERSION "0.5.0"
#define FUJITSU_LCD_N_LEVELS 8 #define FUJITSU_LCD_N_LEVELS 8
...@@ -83,6 +87,24 @@ ...@@ -83,6 +87,24 @@
#define ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS 0x86 #define ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS 0x86
#define ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS 0x87 #define ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS 0x87
/* FUNC interface - command values */
#define FUNC_RFKILL 0x1000
#define FUNC_LEDS 0x1001
#define FUNC_BUTTONS 0x1002
#define FUNC_BACKLIGHT 0x1004
/* FUNC interface - responses */
#define UNSUPPORTED_CMD 0x80000000
#ifdef CONFIG_LEDS_CLASS
/* FUNC interface - LED control */
#define FUNC_LED_OFF 0x1
#define FUNC_LED_ON 0x30001
#define KEYBOARD_LAMPS 0x100
#define LOGOLAMP_POWERON 0x2000
#define LOGOLAMP_ALWAYS 0x4000
#endif
/* Hotkey details */ /* Hotkey details */
#define KEY1_CODE 0x410 /* codes for the keys in the GIRB register */ #define KEY1_CODE 0x410 /* codes for the keys in the GIRB register */
#define KEY2_CODE 0x411 #define KEY2_CODE 0x411
...@@ -145,8 +167,9 @@ struct fujitsu_hotkey_t { ...@@ -145,8 +167,9 @@ struct fujitsu_hotkey_t {
struct platform_device *pf_device; struct platform_device *pf_device;
struct kfifo *fifo; struct kfifo *fifo;
spinlock_t fifo_lock; spinlock_t fifo_lock;
int rfkill_state;
unsigned int irb; /* info about the pressed buttons */ int logolamp_registered;
int kblamps_registered;
}; };
static struct fujitsu_hotkey_t *fujitsu_hotkey; static struct fujitsu_hotkey_t *fujitsu_hotkey;
...@@ -154,12 +177,139 @@ static struct fujitsu_hotkey_t *fujitsu_hotkey; ...@@ -154,12 +177,139 @@ static struct fujitsu_hotkey_t *fujitsu_hotkey;
static void acpi_fujitsu_hotkey_notify(acpi_handle handle, u32 event, static void acpi_fujitsu_hotkey_notify(acpi_handle handle, u32 event,
void *data); void *data);
#ifdef CONFIG_LEDS_CLASS
static enum led_brightness logolamp_get(struct led_classdev *cdev);
static void logolamp_set(struct led_classdev *cdev,
enum led_brightness brightness);
struct led_classdev logolamp_led = {
.name = "fujitsu::logolamp",
.brightness_get = logolamp_get,
.brightness_set = logolamp_set
};
static enum led_brightness kblamps_get(struct led_classdev *cdev);
static void kblamps_set(struct led_classdev *cdev,
enum led_brightness brightness);
struct led_classdev kblamps_led = {
.name = "fujitsu::kblamps",
.brightness_get = kblamps_get,
.brightness_set = kblamps_set
};
#endif
#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
static u32 dbg_level = 0x03; static u32 dbg_level = 0x03;
#endif #endif
static void acpi_fujitsu_notify(acpi_handle handle, u32 event, void *data); static void acpi_fujitsu_notify(acpi_handle handle, u32 event, void *data);
/* Fujitsu ACPI interface function */
static int call_fext_func(int cmd, int arg0, int arg1, int arg2)
{
acpi_status status = AE_OK;
union acpi_object params[4] = {
{ .type = ACPI_TYPE_INTEGER },
{ .type = ACPI_TYPE_INTEGER },
{ .type = ACPI_TYPE_INTEGER },
{ .type = ACPI_TYPE_INTEGER }
};
struct acpi_object_list arg_list = { 4, &params[0] };
struct acpi_buffer output;
union acpi_object out_obj;
acpi_handle handle = NULL;
status = acpi_get_handle(fujitsu_hotkey->acpi_handle, "FUNC", &handle);
if (ACPI_FAILURE(status)) {
vdbg_printk(FUJLAPTOP_DBG_ERROR,
"FUNC interface is not present\n");
return -ENODEV;
}
params[0].integer.value = cmd;
params[1].integer.value = arg0;
params[2].integer.value = arg1;
params[3].integer.value = arg2;
output.length = sizeof(out_obj);
output.pointer = &out_obj;
status = acpi_evaluate_object(handle, NULL, &arg_list, &output);
if (ACPI_FAILURE(status)) {
vdbg_printk(FUJLAPTOP_DBG_WARN,
"FUNC 0x%x (args 0x%x, 0x%x, 0x%x) call failed\n",
cmd, arg0, arg1, arg2);
return -ENODEV;
}
if (out_obj.type != ACPI_TYPE_INTEGER) {
vdbg_printk(FUJLAPTOP_DBG_WARN,
"FUNC 0x%x (args 0x%x, 0x%x, 0x%x) did not "
"return an integer\n",
cmd, arg0, arg1, arg2);
return -ENODEV;
}
vdbg_printk(FUJLAPTOP_DBG_TRACE,
"FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
cmd, arg0, arg1, arg2, (int)out_obj.integer.value);
return out_obj.integer.value;
}
#ifdef CONFIG_LEDS_CLASS
/* LED class callbacks */
static void logolamp_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
if (brightness >= LED_FULL) {
call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, FUNC_LED_ON);
call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, FUNC_LED_ON);
} else if (brightness >= LED_HALF) {
call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, FUNC_LED_ON);
call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, FUNC_LED_OFF);
} else {
call_fext_func(FUNC_LEDS, 0x1, LOGOLAMP_POWERON, FUNC_LED_OFF);
}
}
static void kblamps_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
if (brightness >= LED_FULL)
call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_ON);
else
call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_OFF);
}
static enum led_brightness logolamp_get(struct led_classdev *cdev)
{
enum led_brightness brightness = LED_OFF;
int poweron, always;
poweron = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0);
if (poweron == FUNC_LED_ON) {
brightness = LED_HALF;
always = call_fext_func(FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0);
if (always == FUNC_LED_ON)
brightness = LED_FULL;
}
return brightness;
}
static enum led_brightness kblamps_get(struct led_classdev *cdev)
{
enum led_brightness brightness = LED_OFF;
if (call_fext_func(FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON)
brightness = LED_FULL;
return brightness;
}
#endif
/* Hardware access for LCD brightness control */ /* Hardware access for LCD brightness control */
static int set_lcd_level(int level) static int set_lcd_level(int level)
...@@ -297,10 +447,25 @@ static int bl_get_brightness(struct backlight_device *b) ...@@ -297,10 +447,25 @@ static int bl_get_brightness(struct backlight_device *b)
static int bl_update_status(struct backlight_device *b) static int bl_update_status(struct backlight_device *b)
{ {
int ret;
if (b->props.power == 4)
ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x3);
else
ret = call_fext_func(FUNC_BACKLIGHT, 0x1, 0x4, 0x0);
if (ret != 0)
vdbg_printk(FUJLAPTOP_DBG_ERROR,
"Unable to adjust backlight power, error code %i\n",
ret);
if (use_alt_lcd_levels) if (use_alt_lcd_levels)
return set_lcd_level_alt(b->props.brightness); ret = set_lcd_level_alt(b->props.brightness);
else else
return set_lcd_level(b->props.brightness); ret = set_lcd_level(b->props.brightness);
if (ret != 0)
vdbg_printk(FUJLAPTOP_DBG_ERROR,
"Unable to adjust LCD brightness, error code %i\n",
ret);
return ret;
} }
static struct backlight_ops fujitsubl_ops = { static struct backlight_ops fujitsubl_ops = {
...@@ -382,42 +547,64 @@ static ssize_t store_lcd_level(struct device *dev, ...@@ -382,42 +547,64 @@ static ssize_t store_lcd_level(struct device *dev,
return count; return count;
} }
/* Hardware access for hotkey device */ static ssize_t
ignore_store(struct device *dev,
static int get_irb(void) struct device_attribute *attr, const char *buf, size_t count)
{ {
unsigned long long state = 0; return count;
acpi_status status = AE_OK; }
vdbg_printk(FUJLAPTOP_DBG_TRACE, "Get irb\n");
status =
acpi_evaluate_integer(fujitsu_hotkey->acpi_handle, "GIRB", NULL,
&state);
if (status < 0)
return status;
fujitsu_hotkey->irb = state; static ssize_t
show_lid_state(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (fujitsu_hotkey->rfkill_state == UNSUPPORTED_CMD)
return sprintf(buf, "unknown\n");
if (fujitsu_hotkey->rfkill_state & 0x100)
return sprintf(buf, "open\n");
else
return sprintf(buf, "closed\n");
}
return fujitsu_hotkey->irb; static ssize_t
show_dock_state(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (fujitsu_hotkey->rfkill_state == UNSUPPORTED_CMD)
return sprintf(buf, "unknown\n");
if (fujitsu_hotkey->rfkill_state & 0x200)
return sprintf(buf, "docked\n");
else
return sprintf(buf, "undocked\n");
} }
static ssize_t static ssize_t
ignore_store(struct device *dev, show_radios_state(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count) struct device_attribute *attr, char *buf)
{ {
return count; if (fujitsu_hotkey->rfkill_state == UNSUPPORTED_CMD)
return sprintf(buf, "unknown\n");
if (fujitsu_hotkey->rfkill_state & 0x20)
return sprintf(buf, "on\n");
else
return sprintf(buf, "killed\n");
} }
static DEVICE_ATTR(max_brightness, 0444, show_max_brightness, ignore_store); static DEVICE_ATTR(max_brightness, 0444, show_max_brightness, ignore_store);
static DEVICE_ATTR(brightness_changed, 0444, show_brightness_changed, static DEVICE_ATTR(brightness_changed, 0444, show_brightness_changed,
ignore_store); ignore_store);
static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level); static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
static DEVICE_ATTR(lid, 0444, show_lid_state, ignore_store);
static DEVICE_ATTR(dock, 0444, show_dock_state, ignore_store);
static DEVICE_ATTR(radios, 0444, show_radios_state, ignore_store);
static struct attribute *fujitsupf_attributes[] = { static struct attribute *fujitsupf_attributes[] = {
&dev_attr_brightness_changed.attr, &dev_attr_brightness_changed.attr,
&dev_attr_max_brightness.attr, &dev_attr_max_brightness.attr,
&dev_attr_lcd_level.attr, &dev_attr_lcd_level.attr,
&dev_attr_lid.attr,
&dev_attr_dock.attr,
&dev_attr_radios.attr,
NULL NULL
}; };
...@@ -771,7 +958,8 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) ...@@ -771,7 +958,8 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device)
input->id.bustype = BUS_HOST; input->id.bustype = BUS_HOST;
input->id.product = 0x06; input->id.product = 0x06;
input->dev.parent = &device->dev; input->dev.parent = &device->dev;
input->evbit[0] = BIT(EV_KEY);
set_bit(EV_KEY, input->evbit);
set_bit(fujitsu->keycode1, input->keybit); set_bit(fujitsu->keycode1, input->keybit);
set_bit(fujitsu->keycode2, input->keybit); set_bit(fujitsu->keycode2, input->keybit);
set_bit(fujitsu->keycode3, input->keybit); set_bit(fujitsu->keycode3, input->keybit);
...@@ -803,10 +991,44 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) ...@@ -803,10 +991,44 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device)
printk(KERN_ERR "_INI Method failed\n"); printk(KERN_ERR "_INI Method failed\n");
} }
i = 0; /* Discard hotkey ringbuffer */ i = 0;
while (get_irb() != 0 && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) ; while (call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0
&& (i++) < MAX_HOTKEY_RINGBUFFER_SIZE)
; /* No action, result is discarded */
vdbg_printk(FUJLAPTOP_DBG_INFO, "Discarded %i ringbuffer entries\n", i); vdbg_printk(FUJLAPTOP_DBG_INFO, "Discarded %i ringbuffer entries\n", i);
fujitsu_hotkey->rfkill_state =
call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0);
/* Suspect this is a keymap of the application panel, print it */
printk(KERN_INFO "fujitsu-laptop: BTNI: [0x%x]\n",
call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0));
#ifdef CONFIG_LEDS_CLASS
if (call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
result = led_classdev_register(&fujitsu->pf_device->dev,
&logolamp_led);
if (result == 0) {
fujitsu_hotkey->logolamp_registered = 1;
} else {
printk(KERN_ERR "fujitsu-laptop: Could not register "
"LED handler for logo lamp, error %i\n", result);
}
}
if ((call_fext_func(FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) &&
(call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) {
result = led_classdev_register(&fujitsu->pf_device->dev,
&kblamps_led);
if (result == 0) {
fujitsu_hotkey->kblamps_registered = 1;
} else {
printk(KERN_ERR "fujitsu-laptop: Could not register "
"LED handler for keyboard lamps, error %i\n", result);
}
}
#endif
return result; return result;
end: end:
...@@ -852,16 +1074,15 @@ static void acpi_fujitsu_hotkey_notify(acpi_handle handle, u32 event, ...@@ -852,16 +1074,15 @@ static void acpi_fujitsu_hotkey_notify(acpi_handle handle, u32 event,
input = fujitsu_hotkey->input; input = fujitsu_hotkey->input;
vdbg_printk(FUJLAPTOP_DBG_TRACE, "Hotkey event\n"); fujitsu_hotkey->rfkill_state =
call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0);
switch (event) { switch (event) {
case ACPI_FUJITSU_NOTIFY_CODE1: case ACPI_FUJITSU_NOTIFY_CODE1:
i = 0; i = 0;
while ((irb = get_irb()) != 0 while ((irb =
call_fext_func(FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0
&& (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) { && (i++) < MAX_HOTKEY_RINGBUFFER_SIZE) {
vdbg_printk(FUJLAPTOP_DBG_TRACE, "GIRB result [%x]\n",
irb);
switch (irb & 0x4ff) { switch (irb & 0x4ff) {
case KEY1_CODE: case KEY1_CODE:
keycode = fujitsu->keycode1; keycode = fujitsu->keycode1;
...@@ -1035,6 +1256,15 @@ static int __init fujitsu_init(void) ...@@ -1035,6 +1256,15 @@ static int __init fujitsu_init(void)
goto fail_hotkey1; goto fail_hotkey1;
} }
/* Sync backlight power status (needs FUJ02E3 device, hence deferred) */
if (!acpi_video_backlight_support()) {
if (call_fext_func(FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3)
fujitsu->bl_device->props.power = 4;
else
fujitsu->bl_device->props.power = 0;
}
printk(KERN_INFO "fujitsu-laptop: driver " FUJITSU_DRIVER_VERSION printk(KERN_INFO "fujitsu-laptop: driver " FUJITSU_DRIVER_VERSION
" successfully loaded.\n"); " successfully loaded.\n");
...@@ -1074,6 +1304,14 @@ fail_acpi: ...@@ -1074,6 +1304,14 @@ fail_acpi:
static void __exit fujitsu_cleanup(void) static void __exit fujitsu_cleanup(void)
{ {
#ifdef CONFIG_LEDS_CLASS
if (fujitsu_hotkey->logolamp_registered != 0)
led_classdev_unregister(&logolamp_led);
if (fujitsu_hotkey->kblamps_registered != 0)
led_classdev_unregister(&kblamps_led);
#endif
sysfs_remove_group(&fujitsu->pf_device->dev.kobj, sysfs_remove_group(&fujitsu->pf_device->dev.kobj,
&fujitsupf_attribute_group); &fujitsupf_attribute_group);
platform_device_unregister(fujitsu->pf_device); platform_device_unregister(fujitsu->pf_device);
...@@ -1108,12 +1346,13 @@ module_param_named(debug, dbg_level, uint, 0644); ...@@ -1108,12 +1346,13 @@ module_param_named(debug, dbg_level, uint, 0644);
MODULE_PARM_DESC(debug, "Sets debug level bit-mask"); MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
#endif #endif
MODULE_AUTHOR("Jonathan Woithe, Peter Gruber"); MODULE_AUTHOR("Jonathan Woithe, Peter Gruber, Tony Vroon");
MODULE_DESCRIPTION("Fujitsu laptop extras support"); MODULE_DESCRIPTION("Fujitsu laptop extras support");
MODULE_VERSION(FUJITSU_DRIVER_VERSION); MODULE_VERSION(FUJITSU_DRIVER_VERSION);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1D3:*:cvrS6410:*"); MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1D3:*:cvrS6410:*");
MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1E6:*:cvrS6420:*");
MODULE_ALIAS("dmi:*:svnFUJITSU:*:pvr:rvnFUJITSU:rnFJNB19C:*:cvrS7020:*"); MODULE_ALIAS("dmi:*:svnFUJITSU:*:pvr:rvnFUJITSU:rnFJNB19C:*:cvrS7020:*");
static struct pnp_device_id pnp_ids[] = { static struct pnp_device_id pnp_ids[] = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment