toshiba_acpi: Add keyboard backlight support
Toshiba laptops equiped with an illuminated keyboard can operate in two different modes: Auto and FN-Z. The Auto mode turns on the led on keystrokes and automatically turns it off after some (configurable) time the last key was pressed. The FN-Z mode is used to toggle the keyboard led on/off by userspace. This patch adds support to set the desired KBD mode and timeout via sysfs, creates and registers toshiba::kbd_backlight led device whenever the mode is set to FN-Z. The acceptable values for mode are: 1 (Auto) and 2 (Fn-Z) The time values range are: 1-60 seconds Signed-off-by: Azael Avalos <coproscefalo@gmail.com> Signed-off-by: Matthew Garrett <matthew.garrett@nebula.com>
This commit is contained in:
parent
fdb79081fe
commit
360f0f39d0
|
@ -111,7 +111,9 @@ MODULE_LICENSE("GPL");
|
|||
#define HCI_HOTKEY_EVENT 0x001e
|
||||
#define HCI_LCD_BRIGHTNESS 0x002a
|
||||
#define HCI_WIRELESS 0x0056
|
||||
#define HCI_KBD_ILLUMINATION 0x0095
|
||||
#define SCI_ILLUMINATION 0x014e
|
||||
#define SCI_KBD_ILLUM_STATUS 0x015c
|
||||
|
||||
/* field definitions */
|
||||
#define HCI_HOTKEY_DISABLE 0x0b
|
||||
|
@ -119,6 +121,7 @@ MODULE_LICENSE("GPL");
|
|||
#define HCI_LCD_BRIGHTNESS_BITS 3
|
||||
#define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS)
|
||||
#define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS)
|
||||
#define HCI_MISC_SHIFT 0x10
|
||||
#define HCI_VIDEO_OUT_LCD 0x1
|
||||
#define HCI_VIDEO_OUT_CRT 0x2
|
||||
#define HCI_VIDEO_OUT_TV 0x4
|
||||
|
@ -126,6 +129,8 @@ MODULE_LICENSE("GPL");
|
|||
#define HCI_WIRELESS_BT_PRESENT 0x0f
|
||||
#define HCI_WIRELESS_BT_ATTACH 0x40
|
||||
#define HCI_WIRELESS_BT_POWER 0x80
|
||||
#define SCI_KBD_MODE_FNZ 0x1
|
||||
#define SCI_KBD_MODE_AUTO 0x2
|
||||
|
||||
struct toshiba_acpi_dev {
|
||||
struct acpi_device *acpi_dev;
|
||||
|
@ -135,10 +140,13 @@ struct toshiba_acpi_dev {
|
|||
struct work_struct hotkey_work;
|
||||
struct backlight_device *backlight_dev;
|
||||
struct led_classdev led_dev;
|
||||
struct led_classdev kbd_led;
|
||||
|
||||
int force_fan;
|
||||
int last_key_event;
|
||||
int key_event_valid;
|
||||
int kbd_mode;
|
||||
int kbd_time;
|
||||
|
||||
unsigned int illumination_supported:1;
|
||||
unsigned int video_supported:1;
|
||||
|
@ -147,6 +155,9 @@ struct toshiba_acpi_dev {
|
|||
unsigned int ntfy_supported:1;
|
||||
unsigned int info_supported:1;
|
||||
unsigned int tr_backlight_supported:1;
|
||||
unsigned int kbd_illum_supported:1;
|
||||
unsigned int kbd_led_registered:1;
|
||||
unsigned int sysfs_created:1;
|
||||
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
@ -433,6 +444,89 @@ static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev)
|
|||
|
||||
return state ? LED_FULL : LED_OFF;
|
||||
}
|
||||
|
||||
/* KBD Illumination */
|
||||
static int toshiba_kbd_illum_status_set(struct toshiba_acpi_dev *dev, u32 time)
|
||||
{
|
||||
u32 result;
|
||||
acpi_status status;
|
||||
|
||||
if (!sci_open(dev))
|
||||
return -EIO;
|
||||
|
||||
status = sci_write(dev, SCI_KBD_ILLUM_STATUS, time, &result);
|
||||
sci_close(dev);
|
||||
if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) {
|
||||
pr_err("ACPI call to set KBD backlight status failed\n");
|
||||
return -EIO;
|
||||
} else if (result == HCI_NOT_SUPPORTED) {
|
||||
pr_info("Keyboard backlight status not supported\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int toshiba_kbd_illum_status_get(struct toshiba_acpi_dev *dev, u32 *time)
|
||||
{
|
||||
u32 result;
|
||||
acpi_status status;
|
||||
|
||||
if (!sci_open(dev))
|
||||
return -EIO;
|
||||
|
||||
status = sci_read(dev, SCI_KBD_ILLUM_STATUS, time, &result);
|
||||
sci_close(dev);
|
||||
if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) {
|
||||
pr_err("ACPI call to get KBD backlight status failed\n");
|
||||
return -EIO;
|
||||
} else if (result == HCI_NOT_SUPPORTED) {
|
||||
pr_info("Keyboard backlight status not supported\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum led_brightness toshiba_kbd_backlight_get(struct led_classdev *cdev)
|
||||
{
|
||||
struct toshiba_acpi_dev *dev = container_of(cdev,
|
||||
struct toshiba_acpi_dev, kbd_led);
|
||||
u32 state, result;
|
||||
acpi_status status;
|
||||
|
||||
/* Check the keyboard backlight state */
|
||||
status = hci_read1(dev, HCI_KBD_ILLUMINATION, &state, &result);
|
||||
if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) {
|
||||
pr_err("ACPI call to get the keyboard backlight failed\n");
|
||||
return LED_OFF;
|
||||
} else if (result == HCI_NOT_SUPPORTED) {
|
||||
pr_info("Keyboard backlight not supported\n");
|
||||
return LED_OFF;
|
||||
}
|
||||
|
||||
return state ? LED_FULL : LED_OFF;
|
||||
}
|
||||
|
||||
static void toshiba_kbd_backlight_set(struct led_classdev *cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct toshiba_acpi_dev *dev = container_of(cdev,
|
||||
struct toshiba_acpi_dev, kbd_led);
|
||||
u32 state, result;
|
||||
acpi_status status;
|
||||
|
||||
/* Set the keyboard backlight state */
|
||||
state = brightness ? 1 : 0;
|
||||
status = hci_write1(dev, HCI_KBD_ILLUMINATION, state, &result);
|
||||
if (ACPI_FAILURE(status) || result == SCI_INPUT_DATA_ERROR) {
|
||||
pr_err("ACPI call to set KBD Illumination mode failed\n");
|
||||
return;
|
||||
} else if (result == HCI_NOT_SUPPORTED) {
|
||||
pr_info("Keyboard backlight not supported\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Bluetooth rfkill handlers */
|
||||
|
||||
|
@ -956,6 +1050,117 @@ static const struct backlight_ops toshiba_backlight_data = {
|
|||
.get_brightness = get_lcd_brightness,
|
||||
.update_status = set_lcd_status,
|
||||
};
|
||||
|
||||
/*
|
||||
* Sysfs files
|
||||
*/
|
||||
|
||||
static ssize_t toshiba_kbd_bl_mode_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
|
||||
int mode = -1;
|
||||
int time = -1;
|
||||
|
||||
if (sscanf(buf, "%i", &mode) != 1 && (mode != 2 || mode != 1))
|
||||
return -EINVAL;
|
||||
|
||||
/* Set the Keyboard Backlight Mode where:
|
||||
* Mode - Auto (2) | FN-Z (1)
|
||||
* Auto - KBD backlight turns off automatically in given time
|
||||
* FN-Z - KBD backlight "toggles" when hotkey pressed
|
||||
*/
|
||||
if (mode != -1 && toshiba->kbd_mode != mode) {
|
||||
time = toshiba->kbd_time << HCI_MISC_SHIFT;
|
||||
time = time + toshiba->kbd_mode;
|
||||
if (toshiba_kbd_illum_status_set(toshiba, time) < 0)
|
||||
return -EIO;
|
||||
toshiba->kbd_mode = mode;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t toshiba_kbd_bl_mode_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
|
||||
u32 time;
|
||||
|
||||
if (toshiba_kbd_illum_status_get(toshiba, &time) < 0)
|
||||
return -EIO;
|
||||
|
||||
return sprintf(buf, "%i\n", time & 0x07);
|
||||
}
|
||||
|
||||
static ssize_t toshiba_kbd_bl_timeout_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
|
||||
int time = -1;
|
||||
|
||||
if (sscanf(buf, "%i", &time) != 1 && (time < 0 || time > 60))
|
||||
return -EINVAL;
|
||||
|
||||
/* Set the Keyboard Backlight Timeout: 0-60 seconds */
|
||||
if (time != -1 && toshiba->kbd_time != time) {
|
||||
time = time << HCI_MISC_SHIFT;
|
||||
time = (toshiba->kbd_mode == SCI_KBD_MODE_AUTO) ?
|
||||
time + 1 : time + 2;
|
||||
if (toshiba_kbd_illum_status_set(toshiba, time) < 0)
|
||||
return -EIO;
|
||||
toshiba->kbd_time = time >> HCI_MISC_SHIFT;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t toshiba_kbd_bl_timeout_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct toshiba_acpi_dev *toshiba = dev_get_drvdata(dev);
|
||||
u32 time;
|
||||
|
||||
if (toshiba_kbd_illum_status_get(toshiba, &time) < 0)
|
||||
return -EIO;
|
||||
|
||||
return sprintf(buf, "%i\n", time >> HCI_MISC_SHIFT);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(kbd_backlight_mode, S_IRUGO | S_IWUSR,
|
||||
toshiba_kbd_bl_mode_show, toshiba_kbd_bl_mode_store);
|
||||
static DEVICE_ATTR(kbd_backlight_timeout, S_IRUGO | S_IWUSR,
|
||||
toshiba_kbd_bl_timeout_show, toshiba_kbd_bl_timeout_store);
|
||||
|
||||
static struct attribute *toshiba_attributes[] = {
|
||||
&dev_attr_kbd_backlight_mode.attr,
|
||||
&dev_attr_kbd_backlight_timeout.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static umode_t toshiba_sysfs_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int idx)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct toshiba_acpi_dev *drv = dev_get_drvdata(dev);
|
||||
bool exists = true;
|
||||
|
||||
if (attr == &dev_attr_kbd_backlight_mode.attr)
|
||||
exists = (drv->kbd_illum_supported) ? true : false;
|
||||
else if (attr == &dev_attr_kbd_backlight_timeout.attr)
|
||||
exists = (drv->kbd_mode == SCI_KBD_MODE_AUTO) ? true : false;
|
||||
|
||||
return exists ? attr->mode : 0;
|
||||
}
|
||||
|
||||
static struct attribute_group toshiba_attr_group = {
|
||||
.is_visible = toshiba_sysfs_is_visible,
|
||||
.attrs = toshiba_attributes,
|
||||
};
|
||||
|
||||
static bool toshiba_acpi_i8042_filter(unsigned char data, unsigned char str,
|
||||
struct serio *port)
|
||||
|
@ -1158,6 +1363,10 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev)
|
|||
struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
|
||||
|
||||
remove_toshiba_proc_entries(dev);
|
||||
|
||||
if (dev->sysfs_created)
|
||||
sysfs_remove_group(&dev->acpi_dev->dev.kobj,
|
||||
&toshiba_attr_group);
|
||||
|
||||
if (dev->ntfy_supported) {
|
||||
i8042_remove_filter(toshiba_acpi_i8042_filter);
|
||||
|
@ -1179,6 +1388,9 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev)
|
|||
|
||||
if (dev->illumination_supported)
|
||||
led_classdev_unregister(&dev->led_dev);
|
||||
|
||||
if (dev->kbd_led_registered)
|
||||
led_classdev_unregister(&dev->kbd_led);
|
||||
|
||||
if (toshiba_acpi)
|
||||
toshiba_acpi = NULL;
|
||||
|
@ -1225,6 +1437,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
|
|||
dev->acpi_dev = acpi_dev;
|
||||
dev->method_hci = hci_method;
|
||||
acpi_dev->driver_data = dev;
|
||||
dev_set_drvdata(&acpi_dev->dev, dev);
|
||||
|
||||
if (toshiba_acpi_setup_keyboard(dev))
|
||||
pr_info("Unable to activate hotkeys\n");
|
||||
|
@ -1264,6 +1477,25 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
|
|||
if (!led_classdev_register(&acpi_dev->dev, &dev->led_dev))
|
||||
dev->illumination_supported = 1;
|
||||
}
|
||||
|
||||
ret = toshiba_kbd_illum_status_get(dev, &dummy);
|
||||
if (!ret) {
|
||||
dev->kbd_time = dummy >> HCI_MISC_SHIFT;
|
||||
dev->kbd_mode = dummy & 0x07;
|
||||
}
|
||||
dev->kbd_illum_supported = !ret;
|
||||
/*
|
||||
* Only register the LED if KBD illumination is supported
|
||||
* and the keyboard backlight operation mode is set to FN-Z
|
||||
*/
|
||||
if (dev->kbd_illum_supported && dev->kbd_mode == SCI_KBD_MODE_FNZ) {
|
||||
dev->kbd_led.name = "toshiba::kbd_backlight";
|
||||
dev->kbd_led.max_brightness = 1;
|
||||
dev->kbd_led.brightness_set = toshiba_kbd_backlight_set;
|
||||
dev->kbd_led.brightness_get = toshiba_kbd_backlight_get;
|
||||
if (!led_classdev_register(&dev->acpi_dev->dev, &dev->kbd_led))
|
||||
dev->kbd_led_registered = 1;
|
||||
}
|
||||
|
||||
/* Determine whether or not BIOS supports fan and video interfaces */
|
||||
|
||||
|
@ -1273,6 +1505,14 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
|
|||
ret = get_fan_status(dev, &dummy);
|
||||
dev->fan_supported = !ret;
|
||||
|
||||
ret = sysfs_create_group(&dev->acpi_dev->dev.kobj,
|
||||
&toshiba_attr_group);
|
||||
if (ret) {
|
||||
dev->sysfs_created = 0;
|
||||
goto error;
|
||||
}
|
||||
dev->sysfs_created = !ret;
|
||||
|
||||
create_toshiba_proc_entries(dev);
|
||||
|
||||
toshiba_acpi = dev;
|
||||
|
|
Loading…
Reference in New Issue