From 2b74103547b4dfaac10cd55464cd2bcc26022a66 Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Sun, 3 May 2015 17:42:05 -0600 Subject: toshiba_acpi: Remove bluetooth rfkill code This patch removes all bluetooth rfkill related code residing in the toshiba_acpi driver. Separate patches will add (and adapt) the code to toshiba_bluetooth (where it belongs). Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/Kconfig | 1 - drivers/platform/x86/toshiba_acpi.c | 124 ------------------------------------ 2 files changed, 125 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index f9f205cb1f11..822171cb50d5 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -612,7 +612,6 @@ config ACPI_TOSHIBA select NEW_LEDS depends on BACKLIGHT_CLASS_DEVICE depends on INPUT - depends on RFKILL || RFKILL = n depends on SERIO_I8042 || SERIO_I8042 = n depends on ACPI_VIDEO || ACPI_VIDEO = n select INPUT_POLLDEV diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 9956b9902bb4..8ee7c24fb7eb 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -165,7 +164,6 @@ MODULE_LICENSE("GPL"); struct toshiba_acpi_dev { struct acpi_device *acpi_dev; const char *method_hci; - struct rfkill *bt_rfk; struct input_dev *hotkey_dev; struct work_struct hotkey_work; struct backlight_device *backlight_dev; @@ -202,8 +200,6 @@ struct toshiba_acpi_dev { unsigned int panel_power_on_supported:1; unsigned int usb_three_supported:1; unsigned int sysfs_created:1; - - struct mutex mutex; }; static struct toshiba_acpi_dev *toshiba_acpi; @@ -1212,97 +1208,6 @@ static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev, return 0; } -/* Bluetooth rfkill handlers */ - -static u32 hci_get_bt_present(struct toshiba_acpi_dev *dev, bool *present) -{ - u32 hci_result; - u32 value, value2; - - value = 0; - value2 = 0; - hci_result = hci_read2(dev, HCI_WIRELESS, &value, &value2); - if (hci_result == TOS_SUCCESS) - *present = (value & HCI_WIRELESS_BT_PRESENT) ? true : false; - - return hci_result; -} - -static u32 hci_get_radio_state(struct toshiba_acpi_dev *dev, bool *radio_state) -{ - u32 hci_result; - u32 value, value2; - - value = 0; - value2 = 0x0001; - hci_result = hci_read2(dev, HCI_WIRELESS, &value, &value2); - - *radio_state = value & HCI_WIRELESS_KILL_SWITCH; - return hci_result; -} - -static int bt_rfkill_set_block(void *data, bool blocked) -{ - struct toshiba_acpi_dev *dev = data; - u32 result1, result2; - u32 value; - int err; - bool radio_state; - - value = (blocked == false); - - mutex_lock(&dev->mutex); - if (hci_get_radio_state(dev, &radio_state) != TOS_SUCCESS) { - err = -EIO; - goto out; - } - - if (!radio_state) { - err = 0; - goto out; - } - - result1 = hci_write2(dev, HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER); - result2 = hci_write2(dev, HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH); - - if (result1 != TOS_SUCCESS || result2 != TOS_SUCCESS) - err = -EIO; - else - err = 0; - out: - mutex_unlock(&dev->mutex); - return err; -} - -static void bt_rfkill_poll(struct rfkill *rfkill, void *data) -{ - bool new_rfk_state; - bool value; - u32 hci_result; - struct toshiba_acpi_dev *dev = data; - - mutex_lock(&dev->mutex); - - hci_result = hci_get_radio_state(dev, &value); - if (hci_result != TOS_SUCCESS) { - /* Can't do anything useful */ - mutex_unlock(&dev->mutex); - return; - } - - new_rfk_state = value; - - mutex_unlock(&dev->mutex); - - if (rfkill_set_hw_state(rfkill, !new_rfk_state)) - bt_rfkill_set_block(data, true); -} - -static const struct rfkill_ops toshiba_rfk_ops = { - .set_block = bt_rfkill_set_block, - .poll = bt_rfkill_poll, -}; - static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, bool *enabled) { u32 hci_result; @@ -2692,11 +2597,6 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev) sparse_keymap_free(dev->hotkey_dev); } - if (dev->bt_rfk) { - rfkill_unregister(dev->bt_rfk); - rfkill_destroy(dev->bt_rfk); - } - backlight_device_unregister(dev->backlight_dev); if (dev->illumination_supported) @@ -2733,7 +2633,6 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) const char *hci_method; u32 special_functions; u32 dummy; - bool bt_present; int ret = 0; if (toshiba_acpi) @@ -2769,33 +2668,10 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev) if (toshiba_acpi_setup_keyboard(dev)) pr_info("Unable to activate hotkeys\n"); - mutex_init(&dev->mutex); - ret = toshiba_acpi_setup_backlight(dev); if (ret) goto error; - /* Register rfkill switch for Bluetooth */ - if (hci_get_bt_present(dev, &bt_present) == TOS_SUCCESS && bt_present) { - dev->bt_rfk = rfkill_alloc("Toshiba Bluetooth", - &acpi_dev->dev, - RFKILL_TYPE_BLUETOOTH, - &toshiba_rfk_ops, - dev); - if (!dev->bt_rfk) { - pr_err("unable to allocate rfkill device\n"); - ret = -ENOMEM; - goto error; - } - - ret = rfkill_register(dev->bt_rfk); - if (ret) { - pr_err("unable to register rfkill device\n"); - rfkill_destroy(dev->bt_rfk); - goto error; - } - } - if (toshiba_illumination_available(dev)) { dev->led_dev.name = "toshiba::illumination"; dev->led_dev.max_brightness = 1; -- cgit v1.2.3 From 84c0691e514539900d0f90b1e4442ce49664da5a Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Sun, 3 May 2015 17:42:06 -0600 Subject: toshiba_bluetooth: Add a container struct named toshiba_bluetooth_dev This patch adds a struct named toshiba_bluetooth_dev, which will be used to contain the acpi_device struct and bluetooth status booleans. This struct will also be used by later patches to store the rfkill struct as well. Also, a helper function named toshiba_bluetooth_sync_status was added to be also used by upcomming patches. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/toshiba_bluetooth.c | 47 +++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index 249800763362..a619ba67b9d4 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -34,6 +34,14 @@ MODULE_AUTHOR("Jes Sorensen "); MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver"); MODULE_LICENSE("GPL"); +struct toshiba_bluetooth_dev { + struct acpi_device *acpi_dev; + + bool killswitch; + bool plugged; + bool powered; +}; + static int toshiba_bt_rfkill_add(struct acpi_device *device); static int toshiba_bt_rfkill_remove(struct acpi_device *device); static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event); @@ -165,6 +173,25 @@ static int toshiba_bluetooth_disable(acpi_handle handle) return 0; } +/* Helper function */ +static int toshiba_bluetooth_sync_status(struct toshiba_bluetooth_dev *bt_dev) +{ + int status; + + status = toshiba_bluetooth_status(bt_dev->acpi_dev->handle); + if (status < 0) { + pr_err("Could not sync bluetooth device status\n"); + return status; + } + + bt_dev->killswitch = (status & BT_KILLSWITCH_MASK) ? true : false; + bt_dev->plugged = (status & BT_PLUGGED_MASK) ? true : false; + bt_dev->powered = (status & BT_POWER_MASK) ? true : false; + + return 0; +} + +/* ACPI driver functions */ static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) { toshiba_bluetooth_enable(device->handle); @@ -179,6 +206,7 @@ static int toshiba_bt_resume(struct device *dev) static int toshiba_bt_rfkill_add(struct acpi_device *device) { + struct toshiba_bluetooth_dev *bt_dev; int result; result = toshiba_bluetooth_present(device->handle); @@ -187,17 +215,34 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device) pr_info("Toshiba ACPI Bluetooth device driver\n"); + bt_dev = kzalloc(sizeof(*bt_dev), GFP_KERNEL); + if (!bt_dev) + return -ENOMEM; + bt_dev->acpi_dev = device; + device->driver_data = bt_dev; + dev_set_drvdata(&device->dev, bt_dev); + + result = toshiba_bluetooth_sync_status(bt_dev); + if (result) { + kfree(bt_dev); + return result; + } + /* Enable the BT device */ result = toshiba_bluetooth_enable(device->handle); if (result) - return result; + kfree(bt_dev); return result; } static int toshiba_bt_rfkill_remove(struct acpi_device *device) { + struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device); + /* clean up */ + kfree(bt_dev); + return toshiba_bluetooth_disable(device->handle); } -- cgit v1.2.3 From 7ee8cd3319d5e57b1b5e2b348f078af44e67a577 Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Sun, 3 May 2015 17:42:07 -0600 Subject: toshiba_bluetooth: Add RFKill handler functions This patch adds RFKill handler functions to the driver, allowing it to register and update the rfkill switch status. Also, a comment block was moved from the header to the poll function, as it explains why we need to poll the killswitch on older devices. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/toshiba_bluetooth.c | 77 ++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 822171cb50d5..399085d79015 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -642,6 +642,7 @@ config ACPI_TOSHIBA config TOSHIBA_BT_RFKILL tristate "Toshiba Bluetooth RFKill switch support" depends on ACPI + depends on RFKILL || RFKILL = n ---help--- This driver adds support for Bluetooth events for the RFKill switch on modern Toshiba laptops with full ACPI support and diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index a619ba67b9d4..a3b2d3883dd6 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -10,12 +10,6 @@ * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. - * - * Note the Toshiba Bluetooth RFKill switch seems to be a strange - * fish. It only provides a BT event when the switch is flipped to - * the 'on' position. When flipping it to 'off', the USB device is - * simply pulled away underneath us, without any BT event being - * delivered. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -25,6 +19,7 @@ #include #include #include +#include #define BT_KILLSWITCH_MASK 0x01 #define BT_PLUGGED_MASK 0x40 @@ -36,6 +31,7 @@ MODULE_LICENSE("GPL"); struct toshiba_bluetooth_dev { struct acpi_device *acpi_dev; + struct rfkill *rfk; bool killswitch; bool plugged; @@ -191,6 +187,49 @@ static int toshiba_bluetooth_sync_status(struct toshiba_bluetooth_dev *bt_dev) return 0; } +/* RFKill handlers */ +static int bt_rfkill_set_block(void *data, bool blocked) +{ + struct toshiba_bluetooth_dev *bt_dev = data; + int ret; + + ret = toshiba_bluetooth_sync_status(bt_dev); + if (ret) + return ret; + + if (!bt_dev->killswitch) + return 0; + + if (blocked) + ret = toshiba_bluetooth_disable(bt_dev->acpi_dev->handle); + else + ret = toshiba_bluetooth_enable(bt_dev->acpi_dev->handle); + + return ret; +} + +static void bt_rfkill_poll(struct rfkill *rfkill, void *data) +{ + struct toshiba_bluetooth_dev *bt_dev = data; + + if (toshiba_bluetooth_sync_status(bt_dev)) + return; + + /* + * Note the Toshiba Bluetooth RFKill switch seems to be a strange + * fish. It only provides a BT event when the switch is flipped to + * the 'on' position. When flipping it to 'off', the USB device is + * simply pulled away underneath us, without any BT event being + * delivered. + */ + rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch); +} + +static const struct rfkill_ops rfk_ops = { + .set_block = bt_rfkill_set_block, + .poll = bt_rfkill_poll, +}; + /* ACPI driver functions */ static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) { @@ -228,10 +267,25 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device) return result; } - /* Enable the BT device */ - result = toshiba_bluetooth_enable(device->handle); - if (result) + bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth", + &device->dev, + RFKILL_TYPE_BLUETOOTH, + &rfk_ops, + bt_dev); + if (!bt_dev->rfk) { + pr_err("Unable to allocate rfkill device\n"); + kfree(bt_dev); + return -ENOMEM; + } + + rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch); + + result = rfkill_register(bt_dev->rfk); + if (result) { + pr_err("Unable to register rfkill device\n"); + rfkill_destroy(bt_dev->rfk); kfree(bt_dev); + } return result; } @@ -241,6 +295,11 @@ static int toshiba_bt_rfkill_remove(struct acpi_device *device) struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device); /* clean up */ + if (bt_dev->rfk) { + rfkill_unregister(bt_dev->rfk); + rfkill_destroy(bt_dev->rfk); + } + kfree(bt_dev); return toshiba_bluetooth_disable(device->handle); -- cgit v1.2.3 From d85b11b1a4bee3146540111ef15b5ca1eeb71645 Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Sun, 3 May 2015 17:42:08 -0600 Subject: toshiba_bluetooth: Adapt *_enable, *_notify and *_resume functions to rfkill This patch adapts toshiba_bluetooth_enable, toshiba_bt_rfkill_notify and toshiba_bt_resume functions to rfkill. The *_enable function was cleaned from code that the rfkill code now provides, and the other two functions were modified to update the rfkill switch status, as they were only calling toshiba_bluetooth_enable. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/toshiba_bluetooth.c | 47 ++++++++++++-------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index a3b2d3883dd6..9867ccdf564f 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -107,33 +107,6 @@ static int toshiba_bluetooth_status(acpi_handle handle) static int toshiba_bluetooth_enable(acpi_handle handle) { acpi_status result; - bool killswitch; - bool powered; - bool plugged; - int status; - - /* - * Query ACPI to verify RFKill switch is set to 'on'. - * If not, we return silently, no need to report it as - * an error. - */ - status = toshiba_bluetooth_status(handle); - if (status < 0) - return status; - - killswitch = (status & BT_KILLSWITCH_MASK) ? true : false; - powered = (status & BT_POWER_MASK) ? true : false; - plugged = (status & BT_PLUGGED_MASK) ? true : false; - - if (!killswitch) - return 0; - /* - * This check ensures to only enable the device if it is powered - * off or detached, as some recent devices somehow pass the killswitch - * test, causing a loop enabling/disabling the device, see bug 93911. - */ - if (powered || plugged) - return 0; result = acpi_evaluate_object(handle, "AUSB", NULL, NULL); if (ACPI_FAILURE(result)) { @@ -233,13 +206,29 @@ static const struct rfkill_ops rfk_ops = { /* ACPI driver functions */ static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event) { - toshiba_bluetooth_enable(device->handle); + struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device); + + if (toshiba_bluetooth_sync_status(bt_dev)) + return; + + rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch); } #ifdef CONFIG_PM_SLEEP static int toshiba_bt_resume(struct device *dev) { - return toshiba_bluetooth_enable(to_acpi_device(dev)->handle); + struct toshiba_bluetooth_dev *bt_dev; + int ret; + + bt_dev = acpi_driver_data(to_acpi_device(dev)); + + ret = toshiba_bluetooth_sync_status(bt_dev); + if (ret) + return ret; + + rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch); + + return 0; } #endif -- cgit v1.2.3 From 8798df8845c74d3433997d026abf7ba013a7fcdd Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Sun, 3 May 2015 17:42:09 -0600 Subject: toshiba_bluetooth: Change BT status message to debug The function toshiba_bluetooth_status is currently printing the status of the device whenever it is queried, but since the introduction of the rfkill poll code, this value will get printed everytime the poll occurs. This patch removes the status message from the *_status function, and adds a debug message to the *_sync_status function printing the bluetooth device raw status, killswitch, plug and power states of the device as well. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/toshiba_bluetooth.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index 9867ccdf564f..c5e45089ac51 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -99,8 +99,6 @@ static int toshiba_bluetooth_status(acpi_handle handle) return -ENXIO; } - pr_info("Bluetooth status %llu\n", status); - return status; } @@ -157,6 +155,9 @@ static int toshiba_bluetooth_sync_status(struct toshiba_bluetooth_dev *bt_dev) bt_dev->plugged = (status & BT_PLUGGED_MASK) ? true : false; bt_dev->powered = (status & BT_POWER_MASK) ? true : false; + pr_debug("Bluetooth status %d killswitch %d plugged %d powered %d\n", + status, bt_dev->killswitch, bt_dev->plugged, bt_dev->powered); + return 0; } -- cgit v1.2.3 From 3b8760009d41a861ebd048ab0c1a25e85f80b385 Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Wed, 6 May 2015 09:35:09 -0600 Subject: toshiba_acpi: Remove no longer needed hci_{read, write}2 functions This patch removes the hci_{read, write}2 functions from the driver, and the toshiba_hotkey_event_type_get function was adapted to use the tci_raw function. The hci_write2 function was only used by the bluetooth rfkill code, but since its removal, it was causing build warnings, and the hci_read2 function was only used by the toshiba_hotkey_event_type_get function. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/toshiba_acpi.c | 39 +++++++------------------------------ 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 8ee7c24fb7eb..e59376220f00 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -355,31 +355,6 @@ static u32 hci_read1(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1) return out[0]; } -static u32 hci_write2(struct toshiba_acpi_dev *dev, u32 reg, u32 in1, u32 in2) -{ - u32 in[TCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 }; - u32 out[TCI_WORDS]; - acpi_status status = tci_raw(dev, in, out); - - return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE; -} - -static u32 hci_read2(struct toshiba_acpi_dev *dev, - u32 reg, u32 *out1, u32 *out2) -{ - u32 in[TCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 }; - u32 out[TCI_WORDS]; - acpi_status status = tci_raw(dev, in, out); - - if (ACPI_FAILURE(status)) - return TOS_FAILURE; - - *out1 = out[2]; - *out2 = out[3]; - - return out[0]; -} - /* * Common sci tasks */ @@ -1190,20 +1165,20 @@ static int toshiba_usb_three_set(struct toshiba_acpi_dev *dev, u32 state) static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev, u32 *type) { - u32 val1 = 0x03; - u32 val2 = 0; - u32 result; + u32 in[TCI_WORDS] = { HCI_GET, HCI_SYSTEM_INFO, 0x03, 0, 0, 0 }; + u32 out[TCI_WORDS]; + acpi_status status; - result = hci_read2(dev, HCI_SYSTEM_INFO, &val1, &val2); - if (result == TOS_FAILURE) { + status = tci_raw(dev, in, out); + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get System type failed\n"); return -EIO; - } else if (result == TOS_NOT_SUPPORTED) { + } else if (out[0] == TOS_NOT_SUPPORTED) { pr_info("System type not supported\n"); return -ENODEV; } - *type = val2; + *type = out[3]; return 0; } -- cgit v1.2.3 From d37782bd7bc67af37cd415027e298d3d5c7d56ad Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Wed, 6 May 2015 09:35:10 -0600 Subject: toshiba_acpi: Rename hci_{read, write}1 functions This patch simply renames the hci_{read, write}1 functions to hci_{read, write}. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/toshiba_acpi.c | 44 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index e59376220f00..19efb054317d 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -326,13 +326,13 @@ static acpi_status tci_raw(struct toshiba_acpi_dev *dev, } /* - * Common hci tasks (get or set one or two value) + * Common hci tasks * * In addition to the ACPI status, the HCI system returns a result which * may be useful (such as "not supported"). */ -static u32 hci_write1(struct toshiba_acpi_dev *dev, u32 reg, u32 in1) +static u32 hci_write(struct toshiba_acpi_dev *dev, u32 reg, u32 in1) { u32 in[TCI_WORDS] = { HCI_SET, reg, in1, 0, 0, 0 }; u32 out[TCI_WORDS]; @@ -341,7 +341,7 @@ static u32 hci_write1(struct toshiba_acpi_dev *dev, u32 reg, u32 in1) return ACPI_SUCCESS(status) ? out[0] : TOS_FAILURE; } -static u32 hci_read1(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1) +static u32 hci_read(struct toshiba_acpi_dev *dev, u32 reg, u32 *out1) { u32 in[TCI_WORDS] = { HCI_GET, reg, 0, 0, 0, 0 }; u32 out[TCI_WORDS]; @@ -596,7 +596,7 @@ static enum led_brightness toshiba_kbd_backlight_get(struct led_classdev *cdev) u32 state, result; /* Check the keyboard backlight state */ - result = hci_read1(dev, HCI_KBD_ILLUMINATION, &state); + result = hci_read(dev, HCI_KBD_ILLUMINATION, &state); if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { pr_err("ACPI call to get the keyboard backlight failed\n"); return LED_OFF; @@ -617,7 +617,7 @@ static void toshiba_kbd_backlight_set(struct led_classdev *cdev, /* Set the keyboard backlight state */ state = brightness ? 1 : 0; - result = hci_write1(dev, HCI_KBD_ILLUMINATION, state); + result = hci_write(dev, HCI_KBD_ILLUMINATION, state); if (result == TOS_FAILURE || result == TOS_INPUT_DATA_ERROR) { pr_err("ACPI call to set KBD Illumination mode failed\n"); return; @@ -1188,7 +1188,7 @@ static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, bool *enabled) u32 hci_result; u32 status; - hci_result = hci_read1(dev, HCI_TR_BACKLIGHT, &status); + hci_result = hci_read(dev, HCI_TR_BACKLIGHT, &status); *enabled = !status; return hci_result == TOS_SUCCESS ? 0 : -EIO; } @@ -1198,7 +1198,7 @@ static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, bool enable) u32 hci_result; u32 value = !enable; - hci_result = hci_write1(dev, HCI_TR_BACKLIGHT, value); + hci_result = hci_write(dev, HCI_TR_BACKLIGHT, value); return hci_result == TOS_SUCCESS ? 0 : -EIO; } @@ -1221,7 +1221,7 @@ static int __get_lcd_brightness(struct toshiba_acpi_dev *dev) brightness++; } - hci_result = hci_read1(dev, HCI_LCD_BRIGHTNESS, &value); + hci_result = hci_read(dev, HCI_LCD_BRIGHTNESS, &value); if (hci_result == TOS_SUCCESS) return brightness + (value >> HCI_LCD_BRIGHTNESS_SHIFT); @@ -1276,7 +1276,7 @@ static int set_lcd_brightness(struct toshiba_acpi_dev *dev, int value) } value = value << HCI_LCD_BRIGHTNESS_SHIFT; - hci_result = hci_write1(dev, HCI_LCD_BRIGHTNESS, value); + hci_result = hci_write(dev, HCI_LCD_BRIGHTNESS, value); return hci_result == TOS_SUCCESS ? 0 : -EIO; } @@ -1326,7 +1326,7 @@ static int get_video_status(struct toshiba_acpi_dev *dev, u32 *status) { u32 hci_result; - hci_result = hci_read1(dev, HCI_VIDEO_OUT, status); + hci_result = hci_read(dev, HCI_VIDEO_OUT, status); return hci_result == TOS_SUCCESS ? 0 : -EIO; } @@ -1432,7 +1432,7 @@ static int get_fan_status(struct toshiba_acpi_dev *dev, u32 *status) { u32 hci_result; - hci_result = hci_read1(dev, HCI_FAN, status); + hci_result = hci_read(dev, HCI_FAN, status); return hci_result == TOS_SUCCESS ? 0 : -EIO; } @@ -1472,7 +1472,7 @@ static ssize_t fan_proc_write(struct file *file, const char __user *buf, if (sscanf(cmd, " force_on : %i", &value) == 1 && value >= 0 && value <= 1) { - hci_result = hci_write1(dev, HCI_FAN, value); + hci_result = hci_write(dev, HCI_FAN, value); if (hci_result == TOS_SUCCESS) dev->force_fan = value; else @@ -1500,7 +1500,7 @@ static int keys_proc_show(struct seq_file *m, void *v) u32 value; if (!dev->key_event_valid && dev->system_event_supported) { - hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value); + hci_result = hci_read(dev, HCI_SYSTEM_EVENT, &value); if (hci_result == TOS_SUCCESS) { dev->key_event_valid = 1; dev->last_key_event = value; @@ -1512,7 +1512,7 @@ static int keys_proc_show(struct seq_file *m, void *v) * some machines where system events sporadically * become disabled. */ - hci_result = hci_write1(dev, HCI_SYSTEM_EVENT, 1); + hci_result = hci_write(dev, HCI_SYSTEM_EVENT, 1); pr_notice("Re-enabled hotkeys\n"); } else { pr_err("Error reading hotkey status\n"); @@ -1649,7 +1649,7 @@ static ssize_t fan_store(struct device *dev, if (state != 0 && state != 1) return -EINVAL; - result = hci_write1(toshiba, HCI_FAN, state); + result = hci_write(toshiba, HCI_FAN, state); if (result == TOS_FAILURE) return -EIO; else if (result == TOS_NOT_SUPPORTED) @@ -2271,7 +2271,7 @@ static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev) if (ACPI_FAILURE(status)) return -ENODEV; - result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE); + result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE); if (result == TOS_FAILURE) return -EIO; else if (result == TOS_NOT_SUPPORTED) @@ -2288,8 +2288,8 @@ static void toshiba_acpi_enable_special_functions(struct toshiba_acpi_dev *dev) * Re-activate the hotkeys, but this time, we are using the * "Special Functions" mode. */ - result = hci_write1(dev, HCI_HOTKEY_EVENT, - HCI_HOTKEY_SPECIAL_FUNCTIONS); + result = hci_write(dev, HCI_HOTKEY_EVENT, + HCI_HOTKEY_SPECIAL_FUNCTIONS); if (result != TOS_SUCCESS) pr_err("Could not enable the Special Function mode\n"); } @@ -2370,7 +2370,7 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev) toshiba_acpi_report_hotkey(dev, scancode); } else if (dev->system_event_supported) { do { - hci_result = hci_read1(dev, HCI_SYSTEM_EVENT, &value); + hci_result = hci_read(dev, HCI_SYSTEM_EVENT, &value); switch (hci_result) { case TOS_SUCCESS: toshiba_acpi_report_hotkey(dev, (int)value); @@ -2382,7 +2382,7 @@ static void toshiba_acpi_process_hotkeys(struct toshiba_acpi_dev *dev) * sporadically become disabled. */ hci_result = - hci_write1(dev, HCI_SYSTEM_EVENT, 1); + hci_write(dev, HCI_SYSTEM_EVENT, 1); pr_notice("Re-enabled hotkeys\n"); /* Fall through */ default: @@ -2459,7 +2459,7 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) if (acpi_has_method(dev->acpi_dev->handle, "INFO")) dev->info_supported = 1; else { - hci_result = hci_write1(dev, HCI_SYSTEM_EVENT, 1); + hci_result = hci_write(dev, HCI_SYSTEM_EVENT, 1); if (hci_result == TOS_SUCCESS) dev->system_event_supported = 1; } @@ -2784,7 +2784,7 @@ static int toshiba_acpi_suspend(struct device *device) u32 result; if (dev->hotkey_dev) - result = hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE); + result = hci_write(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE); return 0; } -- cgit v1.2.3 From 3f75bbe9162986bc42a553795f703917a8aadf89 Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Wed, 6 May 2015 09:35:11 -0600 Subject: toshiba_acpi: Comments cleanup This patch simply does some misc cleanup to comments, mainly capitalizes some left over comments from a previous clean up and adds some comments at the beginning of some feature function calls, as well as some misc changes to some comments. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/toshiba_acpi.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 19efb054317d..8a9dacc36114 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -81,7 +81,7 @@ MODULE_LICENSE("GPL"); #define TCI_WORDS 6 -/* operations */ +/* Operations */ #define HCI_SET 0xff00 #define HCI_GET 0xfe00 #define SCI_OPEN 0xf100 @@ -89,7 +89,7 @@ MODULE_LICENSE("GPL"); #define SCI_GET 0xf300 #define SCI_SET 0xf400 -/* return codes */ +/* Return codes */ #define TOS_SUCCESS 0x0000 #define TOS_OPEN_CLOSE_OK 0x0044 #define TOS_FAILURE 0x1000 @@ -104,7 +104,7 @@ MODULE_LICENSE("GPL"); #define TOS_NOT_INITIALIZED 0x8d50 #define TOS_NOT_INSTALLED 0x8e00 -/* registers */ +/* Registers */ #define HCI_FAN 0x0004 #define HCI_TR_BACKLIGHT 0x0005 #define HCI_SYSTEM_EVENT 0x0016 @@ -126,7 +126,7 @@ MODULE_LICENSE("GPL"); #define SCI_TOUCHPAD 0x050e #define SCI_KBD_FUNCTION_KEYS 0x0522 -/* field definitions */ +/* Field definitions */ #define HCI_ACCEL_MASK 0x7fff #define HCI_HOTKEY_DISABLE 0x0b #define HCI_HOTKEY_ENABLE 0x09 @@ -493,7 +493,7 @@ static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev) struct toshiba_acpi_dev, led_dev); u32 state, result; - /* First request : initialize communication. */ + /* First request : initialize communication. */ if (!sci_open(dev)) return LED_OFF; @@ -1183,6 +1183,7 @@ static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev, return 0; } +/* Transflective Backlight */ static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, bool *enabled) { u32 hci_result; @@ -1202,8 +1203,9 @@ static int set_tr_backlight_status(struct toshiba_acpi_dev *dev, bool enable) return hci_result == TOS_SUCCESS ? 0 : -EIO; } -static struct proc_dir_entry *toshiba_proc_dir /*= 0*/; +static struct proc_dir_entry *toshiba_proc_dir; +/* LCD Brightness */ static int __get_lcd_brightness(struct toshiba_acpi_dev *dev) { u32 hci_result; @@ -1411,7 +1413,8 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf, _set_bit(&new_video_out, HCI_VIDEO_OUT_TV, tv_out); /* * To avoid unnecessary video disruption, only write the new - * video setting if something changed. */ + * video setting if something changed. + */ if (new_video_out != video_out) ret = write_acpi_int(METHOD_VIDEO_OUT, new_video_out); } -- cgit v1.2.3 From 8baec45da1181572c0f8dc40c421c3f7ebecb0ab Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Wed, 6 May 2015 09:35:12 -0600 Subject: toshiba_acpi: Remove TOS_FAILURE check from some functions This patch removes the check for TOS_FAILURE whenever we are using the tci_raw function call, as that code is only returned by the {hci, sci}_{read, write} functions and never by the tci_raw, and thus making that check irrelevant. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/toshiba_acpi.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 8a9dacc36114..26671923b994 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -366,7 +366,7 @@ static int sci_open(struct toshiba_acpi_dev *dev) acpi_status status; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to open SCI failed\n"); return 0; } @@ -404,7 +404,7 @@ static void sci_close(struct toshiba_acpi_dev *dev) acpi_status status; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to close SCI failed\n"); return; } @@ -452,7 +452,7 @@ static int toshiba_illumination_available(struct toshiba_acpi_dev *dev) status = tci_raw(dev, in, out); sci_close(dev); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to query Illumination support failed\n"); return 0; } else if (out[0] == TOS_NOT_SUPPORTED) { @@ -674,7 +674,7 @@ static int toshiba_eco_mode_available(struct toshiba_acpi_dev *dev) u32 out[TCI_WORDS]; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get ECO led failed\n"); } else if (out[0] == TOS_NOT_INSTALLED) { pr_info("ECO led not installed"); @@ -796,7 +796,7 @@ static void toshiba_usb_sleep_charge_available(struct toshiba_acpi_dev *dev) return; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get USB Sleep and Charge mode failed\n"); sci_close(dev); return; @@ -810,7 +810,7 @@ static void toshiba_usb_sleep_charge_available(struct toshiba_acpi_dev *dev) in[5] = SCI_USB_CHARGE_BAT_LVL; status = tci_raw(dev, in, out); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get USB Sleep and Charge mode failed\n"); sci_close(dev); return; @@ -890,7 +890,7 @@ static int toshiba_sleep_functions_status_get(struct toshiba_acpi_dev *dev, in[5] = SCI_USB_CHARGE_BAT_LVL; status = tci_raw(dev, in, out); sci_close(dev); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get USB S&C battery level failed\n"); return -EIO; } else if (out[0] == TOS_NOT_SUPPORTED) { @@ -919,7 +919,7 @@ static int toshiba_sleep_functions_status_set(struct toshiba_acpi_dev *dev, in[5] = SCI_USB_CHARGE_BAT_LVL; status = tci_raw(dev, in, out); sci_close(dev); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to set USB S&C battery level failed\n"); return -EIO; } else if (out[0] == TOS_NOT_SUPPORTED) { @@ -945,7 +945,7 @@ static int toshiba_usb_rapid_charge_get(struct toshiba_acpi_dev *dev, in[5] = SCI_USB_CHARGE_RAPID_DSP; status = tci_raw(dev, in, out); sci_close(dev); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to get USB Rapid Charge failed\n"); return -EIO; } else if (out[0] == TOS_NOT_SUPPORTED || @@ -973,7 +973,7 @@ static int toshiba_usb_rapid_charge_set(struct toshiba_acpi_dev *dev, in[5] = SCI_USB_CHARGE_RAPID_DSP; status = tci_raw(dev, in, out); sci_close(dev); - if (ACPI_FAILURE(status) || out[0] == TOS_FAILURE) { + if (ACPI_FAILURE(status)) { pr_err("ACPI call to set USB Rapid Charge failed\n"); return -EIO; } else if (out[0] == TOS_NOT_SUPPORTED) { -- cgit v1.2.3 From 63ba3e28fd7302905052fc1b5fdcdd6bcd38a999 Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Wed, 6 May 2015 09:35:13 -0600 Subject: toshiba_acpi: Bump driver version to 0.22 This patch simply bumps the driver version to 0.22, as significant changes were made to the driver, such as cleanups, updated events, keymap handling, fixes and the bluetooth rfkill code removal. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/toshiba_acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 26671923b994..58d611946b7b 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -31,7 +31,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define TOSHIBA_ACPI_VERSION "0.21" +#define TOSHIBA_ACPI_VERSION "0.22" #define PROC_INTERFACE_VERSION 1 #include -- cgit v1.2.3 From a882003714ec94a3108b47ea5c0cf2a6ec1f3e98 Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Thu, 26 Feb 2015 10:58:30 -0700 Subject: toshiba_haps: Replace sscanf with kstrtoint This patch simply replaces the use of sscanf with kstrtoint returning the error code in case that something went bad. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/toshiba_haps.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index 65300b6a84b9..db6cc53f8c55 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -78,15 +78,20 @@ static ssize_t protection_level_store(struct device *dev, const char *buf, size_t count) { struct toshiba_haps_dev *haps = dev_get_drvdata(dev); - int level, ret; - - if (sscanf(buf, "%d", &level) != 1 || level < 0 || level > 3) - return -EINVAL; + int level; + int ret; - /* Set the sensor level. - * Acceptable levels are: + ret = kstrtoint(buf, 0, &level); + if (ret) + return ret; + /* + * Check for supported levels, which can be: * 0 - Disabled | 1 - Low | 2 - Medium | 3 - High */ + if (level < 0 || level > 3) + return -EINVAL; + + /* Set the sensor level */ ret = toshiba_haps_protection_level(haps->acpi_dev->handle, level); if (ret != 0) return ret; @@ -101,9 +106,14 @@ static ssize_t reset_protection_store(struct device *dev, const char *buf, size_t count) { struct toshiba_haps_dev *haps = dev_get_drvdata(dev); - int reset, ret; + int reset; + int ret; - if (sscanf(buf, "%d", &reset) != 1 || reset != 1) + ret = kstrtoint(buf, 0, &reset); + if (ret) + return ret; + /* The only accepted value is 1 */ + if (reset != 1) return -EINVAL; /* Reset the protection interface */ -- cgit v1.2.3 From 7dd62c0178ceae1d2bf5d8b88941314ec6eeca0a Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Thu, 26 Feb 2015 10:58:31 -0700 Subject: toshiba_haps: Make use of DEVICE_ATTR_{RW, WO} macros This patch makes use of DEVICE_ATTR_{RW, WO} macros, simplifying device attributes creation. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- drivers/platform/x86/toshiba_haps.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index db6cc53f8c55..7f2afc6b5eb9 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -100,6 +100,7 @@ static ssize_t protection_level_store(struct device *dev, return count; } +static DEVICE_ATTR_RW(protection_level); static ssize_t reset_protection_store(struct device *dev, struct device_attribute *attr, @@ -123,10 +124,7 @@ static ssize_t reset_protection_store(struct device *dev, return count; } - -static DEVICE_ATTR(protection_level, S_IRUGO | S_IWUSR, - protection_level_show, protection_level_store); -static DEVICE_ATTR(reset_protection, S_IWUSR, NULL, reset_protection_store); +static DEVICE_ATTR_WO(reset_protection); static struct attribute *haps_attributes[] = { &dev_attr_protection_level.attr, -- cgit v1.2.3 From fe88e379e89baf9cb0486caececc744fccbe9cb6 Mon Sep 17 00:00:00 2001 From: Azael Avalos Date: Thu, 26 Feb 2015 10:59:33 -0700 Subject: Documentation/ABI: Add file describing the sysfs entries for toshiba_haps This patch adds a new file describing the sysfs entries for the toshiba_haps driver. Signed-off-by: Azael Avalos Signed-off-by: Darren Hart --- Documentation/ABI/testing/sysfs-driver-toshiba_haps | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-toshiba_haps diff --git a/Documentation/ABI/testing/sysfs-driver-toshiba_haps b/Documentation/ABI/testing/sysfs-driver-toshiba_haps new file mode 100644 index 000000000000..a662370b4dbf --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-toshiba_haps @@ -0,0 +1,20 @@ +What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS620A:00/protection_level +Date: August 16, 2014 +KernelVersion: 3.17 +Contact: Azael Avalos +Description: This file controls the built-in accelerometer protection level, + valid values are: + * 0 -> Disabled + * 1 -> Low + * 2 -> Medium + * 3 -> High + The default potection value is set to 2 (Medium). +Users: KToshiba + +What: /sys/devices/LNXSYSTM:00/LNXSYBUS:00/TOS620A:00/reset_protection +Date: August 16, 2014 +KernelVersion: 3.17 +Contact: Azael Avalos +Description: This file turns off the built-in accelerometer for a few + seconds and then restore normal operation. Accepting 1 as the + only parameter. -- cgit v1.2.3 From 53e755c21afc230dd93fddda5a2db9609e6f07d0 Mon Sep 17 00:00:00 2001 From: Kast Bernd Date: Wed, 13 May 2015 16:24:16 +0200 Subject: asus-wmi: add fan control This patch is partially based on Felipe Contrera's earlier patch, that was discussed here: https://lkml.org/lkml/2013/10/8/800 Some problems of that patch are solved, now: 1) The main obstacle for the earlier patch seemed to be the use of virt_to_phys, which is accepted, now 2) random memory corruption occurred on my notebook, thus DMA-able memory is allocated now, which solves this problem 3) hwmon interface is used instead of the thermal interface, as a hwmon device is already set up by this driver and seemed more appropriate than the thermal interface 4) Calling the ACPI-functions was modularized thus it's possible to call some multifunctions easily, now (by using asus_wmi_evaluate_method_agfn). Unfortunately the WMI doesn't support controlling both fans on a dual-fan notebook because of an restriction in the acpi-method "SFNS", that is callable through the wmi. If "SFNV" would be called directly even dual fan configurations could be controlled, but not by using wmi. Speed readings only work on auto-mode, thus "-1" will be reported in manual mode. Additionally the speed readings are reported as hundreds of RPM thus they are not too precise. This patch is tested only on one notebook (N551JK) but a similar module, that contained some code to try to control the second fan also, was reported to work on an UX32VD, at least for the first fan. As Felipe already mentioned the low-level functions are described here: http://forum.notebookreview.com/threads/fan-control-on-asus-prime-ux31-ux31a-ux32a-ux32vd.705656/ Signed-off-by: Kast Bernd Acked-by: Corentin Chary Cc: Corentin Chary Cc: Darren Hart Cc: Matthew Garrett Cc: Rafael J. Wysocki Signed-off-by: Darren Hart --- drivers/platform/x86/asus-wmi.c | 344 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 323 insertions(+), 21 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 7543a56e0f45..9b82364e0f3c 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -78,6 +78,7 @@ MODULE_LICENSE("GPL"); #define ASUS_WMI_METHODID_GPID 0x44495047 /* Get Panel ID?? (Resol) */ #define ASUS_WMI_METHODID_QMOD 0x444F4D51 /* Quiet MODe */ #define ASUS_WMI_METHODID_SPLV 0x4C425053 /* Set Panel Light Value */ +#define ASUS_WMI_METHODID_AGFN 0x4E464741 /* FaN? */ #define ASUS_WMI_METHODID_SFUN 0x4E554653 /* FUNCtionalities */ #define ASUS_WMI_METHODID_SDSP 0x50534453 /* Set DiSPlay output */ #define ASUS_WMI_METHODID_GDSP 0x50534447 /* Get DiSPlay output */ @@ -150,11 +151,37 @@ MODULE_LICENSE("GPL"); #define ASUS_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00 +#define ASUS_FAN_DESC "cpu_fan" +#define ASUS_FAN_MFUN 0x13 +#define ASUS_FAN_SFUN_READ 0x06 +#define ASUS_FAN_SFUN_WRITE 0x07 +#define ASUS_FAN_CTRL_MANUAL 1 +#define ASUS_FAN_CTRL_AUTO 2 + struct bios_args { u32 arg0; u32 arg1; } __packed; +/* + * Struct that's used for all methods called via AGFN. Naming is + * identically to the AML code. + */ +struct agfn_args { + u16 mfun; /* probably "Multi-function" to be called */ + u16 sfun; /* probably "Sub-function" to be called */ + u16 len; /* size of the hole struct, including subfunction fields */ + u8 stas; /* not used by now */ + u8 err; /* zero on success */ +} __packed; + +/* struct used for calling fan read and write methods */ +struct fan_args { + struct agfn_args agfn; /* common fields */ + u8 fan; /* fan number: 0: set auto mode 1: 1st fan */ + u32 speed; /* read: RPM/100 - write: 0-255 */ +} __packed; + /* * / - debugfs root directory * dev_id - current dev_id @@ -204,6 +231,10 @@ struct asus_wmi { struct asus_rfkill gps; struct asus_rfkill uwb; + bool asus_hwmon_fan_manual_mode; + int asus_hwmon_num_fans; + int asus_hwmon_pwm; + struct hotplug_slot *hotplug_slot; struct mutex hotplug_lock; struct mutex wmi_lock; @@ -294,6 +325,36 @@ exit: return 0; } +static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args) +{ + struct acpi_buffer input; + u64 phys_addr; + u32 retval; + u32 status = -1; + + /* + * Copy to dma capable address otherwise memory corruption occurs as + * bios has to be able to access it. + */ + input.pointer = kzalloc(args.length, GFP_DMA | GFP_KERNEL); + input.length = args.length; + if (!input.pointer) + return -ENOMEM; + phys_addr = virt_to_phys(input.pointer); + memcpy(input.pointer, args.pointer, args.length); + + status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_AGFN, + phys_addr, 0, &retval); + if (!status) + memcpy(args.pointer, input.pointer, args.length); + + kfree(input.pointer); + if (status) + return -ENXIO; + + return retval; +} + static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval) { return asus_wmi_evaluate_method(asus->dsts_id, dev_id, 0, retval); @@ -1022,35 +1083,228 @@ exit: /* * Hwmon device */ -static ssize_t asus_hwmon_pwm1(struct device *dev, - struct device_attribute *attr, - char *buf) +static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan, + int *speed) +{ + struct fan_args args = { + .agfn.len = sizeof(args), + .agfn.mfun = ASUS_FAN_MFUN, + .agfn.sfun = ASUS_FAN_SFUN_READ, + .fan = fan, + .speed = 0, + }; + struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; + int status; + + if (fan != 1) + return -EINVAL; + + status = asus_wmi_evaluate_method_agfn(input); + + if (status || args.agfn.err) + return -ENXIO; + + if (speed) + *speed = args.speed; + + return 0; +} + +static int asus_hwmon_agfn_fan_speed_write(struct asus_wmi *asus, int fan, + int *speed) +{ + struct fan_args args = { + .agfn.len = sizeof(args), + .agfn.mfun = ASUS_FAN_MFUN, + .agfn.sfun = ASUS_FAN_SFUN_WRITE, + .fan = fan, + .speed = speed ? *speed : 0, + }; + struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; + int status; + + /* 1: for setting 1st fan's speed 0: setting auto mode */ + if (fan != 1 && fan != 0) + return -EINVAL; + + status = asus_wmi_evaluate_method_agfn(input); + + if (status || args.agfn.err) + return -ENXIO; + + if (speed && fan == 1) + asus->asus_hwmon_pwm = *speed; + + return 0; +} + +/* + * Check if we can read the speed of one fan. If true we assume we can also + * control it. + */ +static int asus_hwmon_get_fan_number(struct asus_wmi *asus, int *num_fans) +{ + int status; + int speed = 0; + + *num_fans = 0; + + status = asus_hwmon_agfn_fan_speed_read(asus, 1, &speed); + if (!status) + *num_fans = 1; + + return 0; +} + +static int asus_hwmon_fan_set_auto(struct asus_wmi *asus) +{ + int status; + + status = asus_hwmon_agfn_fan_speed_write(asus, 0, NULL); + if (status) + return -ENXIO; + + asus->asus_hwmon_fan_manual_mode = false; + + return 0; +} + +static int asus_hwmon_fan_rpm_show(struct device *dev, int fan) { struct asus_wmi *asus = dev_get_drvdata(dev); - u32 value; + int value; + int ret; + + /* no speed readable on manual mode */ + if (asus->asus_hwmon_fan_manual_mode) + return -ENXIO; + + ret = asus_hwmon_agfn_fan_speed_read(asus, fan+1, &value); + if (ret) { + pr_warn("reading fan speed failed: %d\n", ret); + return -ENXIO; + } + + return value; +} + +static void asus_hwmon_pwm_show(struct asus_wmi *asus, int fan, int *value) +{ int err; - err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, &value); + if (asus->asus_hwmon_pwm >= 0) { + *value = asus->asus_hwmon_pwm; + return; + } + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, value); if (err < 0) - return err; + return; - value &= 0xFF; - - if (value == 1) /* Low Speed */ - value = 85; - else if (value == 2) - value = 170; - else if (value == 3) - value = 255; - else if (value != 0) { - pr_err("Unknown fan speed %#x\n", value); - value = -1; + *value &= 0xFF; + + if (*value == 1) /* Low Speed */ + *value = 85; + else if (*value == 2) + *value = 170; + else if (*value == 3) + *value = 255; + else if (*value) { + pr_err("Unknown fan speed %#x\n", *value); + *value = -1; } +} + +static ssize_t pwm1_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + int value; + + asus_hwmon_pwm_show(asus, 0, &value); return sprintf(buf, "%d\n", value); } +static ssize_t pwm1_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { + struct asus_wmi *asus = dev_get_drvdata(dev); + int value; + int state; + int ret; + + ret = kstrtouint(buf, 10, &value); + + if (ret) + return ret; + + value = clamp(value, 0, 255); + + state = asus_hwmon_agfn_fan_speed_write(asus, 1, &value); + if (state) + pr_warn("Setting fan speed failed: %d\n", state); + else + asus->asus_hwmon_fan_manual_mode = true; + + return count; +} + +static ssize_t fan1_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int value = asus_hwmon_fan_rpm_show(dev, 0); + + return sprintf(buf, "%d\n", value < 0 ? -1 : value*100); + +} + +static ssize_t pwm1_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + + if (asus->asus_hwmon_fan_manual_mode) + return sprintf(buf, "%d\n", ASUS_FAN_CTRL_MANUAL); + + return sprintf(buf, "%d\n", ASUS_FAN_CTRL_AUTO); +} + +static ssize_t pwm1_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + int status = 0; + int state; + int ret; + + ret = kstrtouint(buf, 10, &state); + + if (ret) + return ret; + + if (state == ASUS_FAN_CTRL_MANUAL) + asus->asus_hwmon_fan_manual_mode = true; + else + status = asus_hwmon_fan_set_auto(asus); + + if (status) + return status; + + return count; +} + +static ssize_t fan1_label_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", ASUS_FAN_DESC); +} + static ssize_t asus_hwmon_temp1(struct device *dev, struct device_attribute *attr, char *buf) @@ -1069,11 +1323,21 @@ static ssize_t asus_hwmon_temp1(struct device *dev, return sprintf(buf, "%d\n", value); } -static DEVICE_ATTR(pwm1, S_IRUGO, asus_hwmon_pwm1, NULL); +/* Fan1 */ +static DEVICE_ATTR_RW(pwm1); +static DEVICE_ATTR_RW(pwm1_enable); +static DEVICE_ATTR_RO(fan1_input); +static DEVICE_ATTR_RO(fan1_label); + +/* Temperature */ static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL); static struct attribute *hwmon_attributes[] = { &dev_attr_pwm1.attr, + &dev_attr_pwm1_enable.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_label.attr, + &dev_attr_temp1_input.attr, NULL }; @@ -1084,19 +1348,28 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, struct device *dev = container_of(kobj, struct device, kobj); struct platform_device *pdev = to_platform_device(dev->parent); struct asus_wmi *asus = platform_get_drvdata(pdev); - bool ok = true; int dev_id = -1; + int fan_attr = -1; u32 value = ASUS_WMI_UNSUPPORTED_METHOD; + bool ok = true; if (attr == &dev_attr_pwm1.attr) dev_id = ASUS_WMI_DEVID_FAN_CTRL; else if (attr == &dev_attr_temp1_input.attr) dev_id = ASUS_WMI_DEVID_THERMAL_CTRL; + + if (attr == &dev_attr_fan1_input.attr + || attr == &dev_attr_fan1_label.attr + || attr == &dev_attr_pwm1.attr + || attr == &dev_attr_pwm1_enable.attr) { + fan_attr = 1; + } + if (dev_id != -1) { int err = asus_wmi_get_devstate(asus, dev_id, &value); - if (err < 0) + if (err < 0 && fan_attr == -1) return 0; /* can't return negative here */ } @@ -1112,10 +1385,16 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, if (value == ASUS_WMI_UNSUPPORTED_METHOD || value & 0xFFF80000 || (!asus->sfun && !(value & ASUS_WMI_DSTS_PRESENCE_BIT))) ok = false; + else + ok = fan_attr <= asus->asus_hwmon_num_fans; } else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) { /* If value is zero, something is clearly wrong */ - if (value == 0) + if (!value) ok = false; + } else if (fan_attr <= asus->asus_hwmon_num_fans && fan_attr != -1) { + ok = true; + } else { + ok = false; } return ok ? attr->mode : 0; @@ -1723,6 +2002,25 @@ error_debugfs: return -ENOMEM; } +static int asus_wmi_fan_init(struct asus_wmi *asus) +{ + int status; + + asus->asus_hwmon_pwm = -1; + asus->asus_hwmon_num_fans = -1; + asus->asus_hwmon_fan_manual_mode = false; + + status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans); + if (status) { + asus->asus_hwmon_num_fans = 0; + pr_warn("Could not determine number of fans: %d\n", status); + return -ENXIO; + } + + pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans); + return 0; +} + /* * WMI Driver */ @@ -1756,6 +2054,9 @@ static int asus_wmi_add(struct platform_device *pdev) if (err) goto fail_input; + err = asus_wmi_fan_init(asus); /* probably no problems on error */ + asus_hwmon_fan_set_auto(asus); + err = asus_wmi_hwmon_init(asus); if (err) goto fail_hwmon; @@ -1832,6 +2133,7 @@ static int asus_wmi_remove(struct platform_device *device) asus_wmi_rfkill_exit(asus); asus_wmi_debugfs_exit(asus); asus_wmi_platform_exit(asus); + asus_hwmon_fan_set_auto(asus); kfree(asus); return 0; -- cgit v1.2.3 From 4fa9dabcffc8e16601307d3d56b58c68d9716ba4 Mon Sep 17 00:00:00 2001 From: Dmitry Tunin Date: Sun, 18 Jan 2015 15:44:40 +0300 Subject: ideapad_laptop: Lenovo G50-30 fix rfkill reports wireless blocked Lenovo G30-50 does not have a hardware wireless switch and wireless is always blocked. BugLink: https://bugs.launchpad.net/bugs/1397021 Signed-off-by: Dmitry Tunin Cc: stable@vger.kernel.org Signed-off-by: Philippe Coval [dvhart@linux.intel.com: Reordered dmi id per Phillippe's later version] Signed-off-by: Darren Hart --- drivers/platform/x86/ideapad-laptop.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index b3d419a84723..1db3d2176d56 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -829,6 +829,13 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) * report all radios as hardware-blocked. */ static const struct dmi_system_id no_hw_rfkill_list[] = { + { + .ident = "Lenovo G50-30", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"), + }, + }, { .ident = "Lenovo Yoga 2 11 / 13 / Pro", .matches = { -- cgit v1.2.3 From 55cd3f01d631b0a3a022393bac234b9a643301f6 Mon Sep 17 00:00:00 2001 From: Radim Krčmář Date: Fri, 29 May 2015 22:18:52 +0200 Subject: pvpanic: handle missing _STA correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pvpanic was not properly detected when _STA was missing. ACPI 6.0 April 2015, 6.3.7 _STA (Status) If a device object (including the processor object) does not have an _STA object, then OSPM assumes that all of the above bits are set (i.e., the device is present, enabled, shown in the UI, and functioning). Not adhering to the specification made pvpanic dormant under QEMU 2.3. The original patch used acpi_bus_get_status_handle, which was not being exported, so module build blew up; switch to acpi_bus_get_status and use the status it populates. Populated status is a bitfield so we can make the code self-documenting. We do not check 'present' because 'enabled' has to be false in that case by specification. Older QEMUs set 0xff to status and newer ones do 0xb. Suggested-by: Igor Mammedov Signed-off-by: Radim Krčmář Reviewed-by: Igor Mammedov [dvhart@linux.intel.com: Merge acpi_bug_get_status fix to avoid bisect breakage] Signed-off-by: Darren Hart --- drivers/platform/x86/pvpanic.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/pvpanic.c b/drivers/platform/x86/pvpanic.c index 073a90a63dbc..fd86daba7ffd 100644 --- a/drivers/platform/x86/pvpanic.c +++ b/drivers/platform/x86/pvpanic.c @@ -92,13 +92,13 @@ pvpanic_walk_resources(struct acpi_resource *res, void *context) static int pvpanic_add(struct acpi_device *device) { - acpi_status status; - u64 ret; + int ret; - status = acpi_evaluate_integer(device->handle, "_STA", NULL, - &ret); + ret = acpi_bus_get_status(device); + if (ret < 0) + return ret; - if (ACPI_FAILURE(status) || (ret & 0x0B) != 0x0B) + if (!device->status.enabled || !device->status.functional) return -ENODEV; acpi_walk_resources(device->handle, METHOD_NAME__CRS, -- cgit v1.2.3 From 85eaa5fb829a4e4f0806912fc3136c87c5ee9225 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 1 Jun 2015 11:25:06 +0200 Subject: apple_gmux: Use acpi_video_unregister_backlight instead of acpi_video_unregister acpi_video_unregister() not only unregisters the acpi-video backlight interface but also unregisters the acpi video bus event listener, causing e.g. brightness hotkey presses to no longer generate keypress events. The unregistering of the acpi video bus event listener usually is undesirable, which by itself is a good reason to switch to acpi_video_unregister_backlight(). Another problem with using acpi_video_unregister() rather then using acpi_video_unregister_backlight() is that on systems with an intel video opregion (most systems) whether or not the acpi video bus event listener actually gets unregistered depends on module load ordering: Scenario a: 1) acpi/video.ko gets loaded (*), does not do acpi_video_register as there is an intel opregion. 2) intel.ko gets loaded, calls acpi_video_register() which registers both the listener and the acpi backlight interface 3) apple-gmux.ko gets loaded, calls acpi_video_unregister() causing both the listener and the acpi backlight interface to unregister Scenario b: 1) acpi/video.ko gets loaded (*), does not do acpi_video_register as there is an intel opregion. 2) apple-gmux.ko gets loaded, calls acpi_video_dmi_promote_vendor(), calls acpi_video_unregister(), which is a nop since acpi_video_register has not yet been called 2) intel.ko gets loaded, calls acpi_video_register() which registers the listener, but does not register the acpi backlight interface due to the call to the preciding call to acpi_video_dmi_promote_vendor() *) acpi/video.ko always loads first as both other modules depend on it. So we end up with or without an acpi video bus event listener depending on module load ordering, not good. Switching to using acpi_video_unregister_backlight() means that independ of ordering we will always have an acpi video bus event listener fixing this. Note that this commit means that systems without an intel video opregion, and systems which were hitting scenario a wrt module load ordering, are now getting an acpi video bus event listener while before they were not! On some systems this may cause the brightness hotkeys to start generating keypresses while before they were not (good), while on other systems this may cause the brightness hotkeys to generate multiple keypress events for a single press (not so good). Since on most systems the acpi video bus is the canonical source for brightness events I believe that the latter case will needs to be handled on a case by case basis by filtering out the duplicate keypresses at the other source for them. Cc: Seth Forshee Signed-off-by: Hans de Goede Signed-off-by: Darren Hart --- drivers/platform/x86/apple-gmux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index 6808715003f6..45032cebde71 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -551,7 +551,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) * Disable the other backlight choices. */ acpi_video_dmi_promote_vendor(); - acpi_video_unregister(); + acpi_video_unregister_backlight(); apple_bl_unregister(); gmux_data->power_state = VGA_SWITCHEROO_ON; -- cgit v1.2.3 From 5f770658747ee1f5730c9cb853e8957d7ba22e1b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 1 Jun 2015 11:25:07 +0200 Subject: asus-wmi: Use acpi_video_unregister_backlight instead of acpi_video_unregister acpi_video_unregister() not only unregisters the acpi-video backlight interface but also unregisters the acpi video bus event listener, causing e.g. brightness hotkey presses to no longer generate keypress events. The unregistering of the acpi video bus event listener usually is undesirable, which by itself is a good reason to switch to acpi_video_unregister_backlight(). Another problem with using acpi_video_unregister() rather then using acpi_video_unregister_backlight() is that on systems with an intel video opregion (most systems) and a wmi_backlight_power quirk, whether or not the acpi video bus event listener actually gets unregistered depends on module load ordering: Scenario a: 1) acpi/video.ko gets loaded (*), does not do acpi_video_register as there is an intel opregion. 2) intel.ko gets loaded, calls acpi_video_register() which registers both the listener and the acpi backlight interface 3) asus-wmi.ko gets loaded, calls acpi_video_unregister() causing both the listener and the acpi backlight interface to unregister Scenario b: 1) acpi/video.ko gets loaded (*), does not do acpi_video_register as there is an intel opregion. 2) asus-wmi.ko gets loaded, calls acpi_video_dmi_promote_vendor(), calls acpi_video_unregister(), which is a nop since acpi_video_register has not yet been called 2) intel.ko gets loaded, calls acpi_video_register() which registers the listener, but does not register the acpi backlight interface due to the call to the preciding call to acpi_video_dmi_promote_vendor() *) acpi/video.ko always loads first as both other modules depend on it. So we end up with or without an acpi video bus event listener depending on module load ordering, not good. Switching to using acpi_video_unregister_backlight() means that independ of ordering we will always have an acpi video bus event listener fixing this. Note that this commit means that systems without an intel video opregion, and systems which were hitting scenario a wrt module load ordering, are now getting an acpi video bus event listener while before they were not! On some systems this may cause the brightness hotkeys to start generating keypresses while before they were not (good), while on other systems this may cause the brightness hotkeys to generate multiple keypress events for a single press (not so good). Since on most systems the acpi video bus is the canonical source for brightness events I believe that the latter case will needs to be handled on a case by case basis by filtering out the duplicate keypresses at the other source for them. Cc: acpi4asus-user@lists.sourceforge.net Signed-off-by: Hans de Goede Acked-by: Corentin Chary --- drivers/platform/x86/asus-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 9b82364e0f3c..75de0feca13b 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -2078,7 +2078,7 @@ static int asus_wmi_add(struct platform_device *pdev) acpi_video_dmi_promote_vendor(); if (!acpi_video_backlight_support()) { pr_info("Disabling ACPI video driver\n"); - acpi_video_unregister(); + acpi_video_unregister_backlight(); err = asus_wmi_backlight_init(asus); if (err && err != -ENODEV) goto fail_backlight; -- cgit v1.2.3 From 9330dcdd918a5724af8321127a19a1b171df6bab Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 1 Jun 2015 11:25:08 +0200 Subject: samsung-laptop: Use acpi_video_unregister_backlight instead of acpi_video_unregister acpi_video_unregister() not only unregisters the acpi-video backlight interface but also unregisters the acpi video bus event listener, causing e.g. brightness hotkey presses to no longer generate keypress events. The unregistering of the acpi video bus event listener usually is undesirable, which by itself is a good reason to switch to acpi_video_unregister_backlight(). Another problem with using acpi_video_unregister() rather then using acpi_video_unregister_backlight() is that on systems with an intel video opregion (most systems) and a broken_acpi_video quirk, whether or not the acpi video bus event listener actually gets unregistered depends on module load ordering: Scenario a: 1) acpi/video.ko gets loaded (*), does not do acpi_video_register as there is an intel opregion. 2) intel.ko gets loaded, calls acpi_video_register() which registers both the listener and the acpi backlight interface 3) samsung-laptop.ko gets loaded, calls acpi_video_unregister() causing both the listener and the acpi backlight interface to unregister Scenario b: 1) acpi/video.ko gets loaded (*), does not do acpi_video_register as there is an intel opregion. 2) samsung-laptop.ko gets loaded, calls acpi_video_dmi_promote_vendor(), calls acpi_video_unregister(), which is a nop since acpi_video_register has not yet been called 2) intel.ko gets loaded, calls acpi_video_register() which registers the listener, but does not register the acpi backlight interface due to the call to the preciding call to acpi_video_dmi_promote_vendor() *) acpi/video.ko always loads first as both other modules depend on it. So we end up with or without an acpi video bus event listener depending on module load ordering, not good. Switching to using acpi_video_unregister_backlight() means that independ of ordering we will always have an acpi video bus event listener fixing this. Note that this commit means that systems without an intel video opregion, and systems which were hitting scenario a wrt module load ordering, are now getting an acpi video bus event listener while before they were not! On some systems this may cause the brightness hotkeys to start generating keypresses while before they were not (good), while on other systems this may cause the brightness hotkeys to generate multiple keypress events for a single press (not so good). Since on most systems the acpi video bus is the canonical source for brightness events I believe that the latter case will needs to be handled on a case by case basis by filtering out the duplicate keypresses at the other source for them. Signed-off-by: Hans de Goede Acked-by: Corentin Chary --- drivers/platform/x86/samsung-laptop.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index 9e701b2256f9..0df03e2c1e91 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -1730,14 +1730,14 @@ static int __init samsung_init(void) samsung->handle_backlight = false; } else if (samsung->quirks->broken_acpi_video) { pr_info("Disabling ACPI video driver\n"); - acpi_video_unregister(); + acpi_video_unregister_backlight(); } if (samsung->quirks->use_native_backlight) { pr_info("Using native backlight driver\n"); /* Tell acpi-video to not handle the backlight */ acpi_video_dmi_promote_vendor(); - acpi_video_unregister(); + acpi_video_unregister_backlight(); /* And also do not handle it ourselves */ samsung->handle_backlight = false; } -- cgit v1.2.3 From 817a5cdb40c8115eafe631b8e1de37cf8fe9fab8 Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Sat, 6 Jun 2015 10:23:28 +0200 Subject: dell-rbtn: Dell Airplane Mode Switch driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is an ACPI driver for Dell laptops which receive HW slider radio switch or hotkey toggle wifi button events. It exports rfkill device dell-rbtn (which provide correct hard rfkill state) or hotkey input device. Alex Hung is author of original hotkey input device code. Signed-off-by: Pali Rohár Tested-by: Gabriele Mazzotta Cc: Alex Hung [fengguang.wu@intel.com: rbtn_ops can be static] Signed-off-by: Fengguang Wu [dvhart@linux.intel.com: Correct multi-line comment formatting] Signed-off-by: Darren Hart --- MAINTAINERS | 5 + drivers/platform/x86/Kconfig | 16 ++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/dell-rbtn.c | 332 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 354 insertions(+) create mode 100644 drivers/platform/x86/dell-rbtn.c diff --git a/MAINTAINERS b/MAINTAINERS index 2e5bbc0d68b2..5808066e1677 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3071,6 +3071,11 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/dell-laptop.c +DELL LAPTOP RBTN DRIVER +M: Pali Rohár +S: Maintained +F: drivers/platform/x86/dell-rbtn.* + DELL LAPTOP FREEFALL DRIVER M: Pali Rohár S: Maintained diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 399085d79015..bca0aee22978 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -138,6 +138,22 @@ config DELL_SMO8800 To compile this driver as a module, choose M here: the module will be called dell-smo8800. +config DELL_RBTN + tristate "Dell Airplane Mode Switch driver" + depends on ACPI + depends on INPUT + depends on RFKILL + ---help--- + Say Y here if you want to support Dell Airplane Mode Switch ACPI + device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN. + This driver register rfkill device or input hotkey device depending + on hardware type (hw switch slider or keyboard toggle button). For + rfkill devices it receive HW switch events and set correct hard + rfkill state. + + To compile this driver as a module, choose M here: the module will + be called dell-rbtn. + config FUJITSU_LAPTOP tristate "Fujitsu Laptop Extras" diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index f82232b1fc4d..b3e54ed863c3 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o +obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_HP_ACCEL) += hp_accel.o diff --git a/drivers/platform/x86/dell-rbtn.c b/drivers/platform/x86/dell-rbtn.c new file mode 100644 index 000000000000..1c19fff6d565 --- /dev/null +++ b/drivers/platform/x86/dell-rbtn.c @@ -0,0 +1,332 @@ +/* + Dell Airplane Mode Switch driver + Copyright (C) 2014-2015 Pali Rohár + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#include +#include +#include +#include + +enum rbtn_type { + RBTN_UNKNOWN, + RBTN_TOGGLE, + RBTN_SLIDER, +}; + +struct rbtn_data { + enum rbtn_type type; + struct rfkill *rfkill; + struct input_dev *input_dev; +}; + + +/* + * acpi functions + */ + +static enum rbtn_type rbtn_check(struct acpi_device *device) +{ + unsigned long long output; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output); + if (ACPI_FAILURE(status)) + return RBTN_UNKNOWN; + + switch (output) { + case 0: + case 1: + return RBTN_TOGGLE; + case 2: + case 3: + return RBTN_SLIDER; + default: + return RBTN_UNKNOWN; + } +} + +static int rbtn_get(struct acpi_device *device) +{ + unsigned long long output; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output); + if (ACPI_FAILURE(status)) + return -EINVAL; + + return !output; +} + +static int rbtn_acquire(struct acpi_device *device, bool enable) +{ + struct acpi_object_list input; + union acpi_object param; + acpi_status status; + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = enable; + input.count = 1; + input.pointer = ¶m; + + status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL); + if (ACPI_FAILURE(status)) + return -EINVAL; + + return 0; +} + + +/* + * rfkill device + */ + +static void rbtn_rfkill_query(struct rfkill *rfkill, void *data) +{ + struct acpi_device *device = data; + int state; + + state = rbtn_get(device); + if (state < 0) + return; + + rfkill_set_states(rfkill, state, state); +} + +static int rbtn_rfkill_set_block(void *data, bool blocked) +{ + /* NOTE: setting soft rfkill state is not supported */ + return -EINVAL; +} + +static struct rfkill_ops rbtn_ops = { + .query = rbtn_rfkill_query, + .set_block = rbtn_rfkill_set_block, +}; + +static int rbtn_rfkill_init(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + int ret; + + if (rbtn_data->rfkill) + return 0; + + /* + * NOTE: rbtn controls all radio devices, not only WLAN + * but rfkill interface does not support "ANY" type + * so "WLAN" type is used + */ + rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev, + RFKILL_TYPE_WLAN, &rbtn_ops, device); + if (!rbtn_data->rfkill) + return -ENOMEM; + + ret = rfkill_register(rbtn_data->rfkill); + if (ret) { + rfkill_destroy(rbtn_data->rfkill); + rbtn_data->rfkill = NULL; + return ret; + } + + return 0; +} + +static void rbtn_rfkill_exit(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + if (!rbtn_data->rfkill) + return; + + rfkill_unregister(rbtn_data->rfkill); + rfkill_destroy(rbtn_data->rfkill); + rbtn_data->rfkill = NULL; +} + +static void rbtn_rfkill_event(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + if (rbtn_data->rfkill) + rbtn_rfkill_query(rbtn_data->rfkill, device); +} + + +/* + * input device + */ + +static int rbtn_input_init(struct rbtn_data *rbtn_data) +{ + int ret; + + rbtn_data->input_dev = input_allocate_device(); + if (!rbtn_data->input_dev) + return -ENOMEM; + + rbtn_data->input_dev->name = "DELL Wireless hotkeys"; + rbtn_data->input_dev->phys = "dellabce/input0"; + rbtn_data->input_dev->id.bustype = BUS_HOST; + rbtn_data->input_dev->evbit[0] = BIT(EV_KEY); + set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit); + + ret = input_register_device(rbtn_data->input_dev); + if (ret) { + input_free_device(rbtn_data->input_dev); + rbtn_data->input_dev = NULL; + return ret; + } + + return 0; +} + +static void rbtn_input_exit(struct rbtn_data *rbtn_data) +{ + input_unregister_device(rbtn_data->input_dev); + rbtn_data->input_dev = NULL; +} + +static void rbtn_input_event(struct rbtn_data *rbtn_data) +{ + input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1); + input_sync(rbtn_data->input_dev); + input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0); + input_sync(rbtn_data->input_dev); +} + + +/* + * acpi driver + */ + +static int rbtn_add(struct acpi_device *device); +static int rbtn_remove(struct acpi_device *device); +static void rbtn_notify(struct acpi_device *device, u32 event); + +static const struct acpi_device_id rbtn_ids[] = { + { "DELRBTN", 0 }, + { "DELLABCE", 0 }, + { "", 0 }, +}; + +static struct acpi_driver rbtn_driver = { + .name = "dell-rbtn", + .ids = rbtn_ids, + .ops = { + .add = rbtn_add, + .remove = rbtn_remove, + .notify = rbtn_notify, + }, + .owner = THIS_MODULE, +}; + + +/* + * acpi driver functions + */ + +static int rbtn_add(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data; + enum rbtn_type type; + int ret = 0; + + type = rbtn_check(device); + if (type == RBTN_UNKNOWN) { + dev_info(&device->dev, "Unknown device type\n"); + return -EINVAL; + } + + ret = rbtn_acquire(device, true); + if (ret < 0) { + dev_err(&device->dev, "Cannot enable device\n"); + return ret; + } + + rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL); + if (!rbtn_data) + return -ENOMEM; + + rbtn_data->type = type; + device->driver_data = rbtn_data; + + switch (rbtn_data->type) { + case RBTN_TOGGLE: + ret = rbtn_input_init(rbtn_data); + break; + case RBTN_SLIDER: + ret = rbtn_rfkill_init(device); + break; + default: + ret = -EINVAL; + } + + return ret; + +} + +static int rbtn_remove(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + switch (rbtn_data->type) { + case RBTN_TOGGLE: + rbtn_input_exit(rbtn_data); + break; + case RBTN_SLIDER: + rbtn_rfkill_exit(device); + break; + default: + break; + } + + rbtn_acquire(device, false); + device->driver_data = NULL; + + return 0; +} + +static void rbtn_notify(struct acpi_device *device, u32 event) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + if (event != 0x80) { + dev_info(&device->dev, "Received unknown event (0x%x)\n", + event); + return; + } + + switch (rbtn_data->type) { + case RBTN_TOGGLE: + rbtn_input_event(rbtn_data); + break; + case RBTN_SLIDER: + rbtn_rfkill_event(device); + break; + default: + break; + } +} + + +/* + * module functions + */ + +module_acpi_driver(rbtn_driver); + +MODULE_DEVICE_TABLE(acpi, rbtn_ids); +MODULE_DESCRIPTION("Dell Airplane Mode Switch driver"); +MODULE_AUTHOR("Pali Rohár "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From b05ffc95f9ed986534b67538e239e9c4ba254b55 Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Sat, 6 Jun 2015 10:23:29 +0200 Subject: dell-rbtn: Export notifier for other kernel modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch exports notifier functions so other modules can receive HW switch events. By default when some module register notifier, dell-rbtn driver automatically remove rfkill interfaces from system (it is expected that other module will use events for other rfkill interface). This behaviour can be changed with new module parameter "auto_remove_rfkill". This patch is designed for dell-laptop module for receiving those events. Signed-off-by: Pali Rohár Tested-by: Gabriele Mazzotta [dvhart@linux.intel.com: Cleanup MODULE_PARM_DESC formatting and grammar] Signed-off-by: Darren Hart --- drivers/platform/x86/dell-rbtn.c | 93 +++++++++++++++++++++++++++++++++++++++- drivers/platform/x86/dell-rbtn.h | 24 +++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/x86/dell-rbtn.h diff --git a/drivers/platform/x86/dell-rbtn.c b/drivers/platform/x86/dell-rbtn.c index 1c19fff6d565..cd410e392550 100644 --- a/drivers/platform/x86/dell-rbtn.c +++ b/drivers/platform/x86/dell-rbtn.c @@ -232,6 +232,86 @@ static struct acpi_driver rbtn_driver = { }; +/* + * notifier export functions + */ + +static bool auto_remove_rfkill = true; + +static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head); + +static int rbtn_inc_count(struct device *dev, void *data) +{ + struct acpi_device *device = to_acpi_device(dev); + struct rbtn_data *rbtn_data = device->driver_data; + int *count = data; + + if (rbtn_data->type == RBTN_SLIDER) + (*count)++; + + return 0; +} + +static int rbtn_switch_dev(struct device *dev, void *data) +{ + struct acpi_device *device = to_acpi_device(dev); + struct rbtn_data *rbtn_data = device->driver_data; + bool enable = data; + + if (rbtn_data->type != RBTN_SLIDER) + return 0; + + if (enable) + rbtn_rfkill_init(device); + else + rbtn_rfkill_exit(device); + + return 0; +} + +int dell_rbtn_notifier_register(struct notifier_block *nb) +{ + bool first; + int count; + int ret; + + count = 0; + ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count, + rbtn_inc_count); + if (ret || count == 0) + return -ENODEV; + + first = !rbtn_chain_head.head; + + ret = atomic_notifier_chain_register(&rbtn_chain_head, nb); + if (ret != 0) + return ret; + + if (auto_remove_rfkill && first) + ret = driver_for_each_device(&rbtn_driver.drv, NULL, + (void *)false, rbtn_switch_dev); + + return ret; +} +EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register); + +int dell_rbtn_notifier_unregister(struct notifier_block *nb) +{ + int ret; + + ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb); + if (ret != 0) + return ret; + + if (auto_remove_rfkill && !rbtn_chain_head.head) + ret = driver_for_each_device(&rbtn_driver.drv, NULL, + (void *)true, rbtn_switch_dev); + + return ret; +} +EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister); + + /* * acpi driver functions */ @@ -266,7 +346,10 @@ static int rbtn_add(struct acpi_device *device) ret = rbtn_input_init(rbtn_data); break; case RBTN_SLIDER: - ret = rbtn_rfkill_init(device); + if (auto_remove_rfkill && rbtn_chain_head.head) + ret = 0; + else + ret = rbtn_rfkill_init(device); break; default: ret = -EINVAL; @@ -313,6 +396,7 @@ static void rbtn_notify(struct acpi_device *device, u32 event) break; case RBTN_SLIDER: rbtn_rfkill_event(device); + atomic_notifier_call_chain(&rbtn_chain_head, event, device); break; default: break; @@ -326,6 +410,13 @@ static void rbtn_notify(struct acpi_device *device, u32 event) module_acpi_driver(rbtn_driver); +module_param(auto_remove_rfkill, bool, 0444); + +MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when " + "other modules start receiving events " + "from this module and re-add them when " + "the last module stops receiving events " + "(default true)"); MODULE_DEVICE_TABLE(acpi, rbtn_ids); MODULE_DESCRIPTION("Dell Airplane Mode Switch driver"); MODULE_AUTHOR("Pali Rohár "); diff --git a/drivers/platform/x86/dell-rbtn.h b/drivers/platform/x86/dell-rbtn.h new file mode 100644 index 000000000000..c59cc6b8ec2b --- /dev/null +++ b/drivers/platform/x86/dell-rbtn.h @@ -0,0 +1,24 @@ +/* + Dell Airplane Mode Switch driver + Copyright (C) 2014-2015 Pali Rohár + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#ifndef _DELL_RBTN_H_ +#define _DELL_RBTN_H_ + +struct notifier_block; + +int dell_rbtn_notifier_register(struct notifier_block *nb); +int dell_rbtn_notifier_unregister(struct notifier_block *nb); + +#endif -- cgit v1.2.3 From f8358578e2f23bd82d0454c17676bdb28a40664a Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Sat, 6 Jun 2015 10:23:30 +0200 Subject: dell-laptop: Use dell-rbtn instead i8042 filter when possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now module dell-laptop registered rfkill device which used i8042 filter function for receiving HW switch rfkill events (handling special keycode). But for some dell laptops there is native ACPI driver dell-rbtn which can receive rfkill events (without i8042 hooks). So this patch will combine best from both sides. It will use native ACPI driver dell-rbtn for receiving events and dell-laptop SMBIOS interface for enabling or disabling radio devices. If ACPI driver or device will not be available fallback to i8042 filter function will be used. Patch also changes module_init() to late_initcall() to ensure that init function will be called after initializing dell-rbtn.c driver. Signed-off-by: Pali Rohár Tested-by: Gabriele Mazzotta Signed-off-by: Darren Hart --- drivers/platform/x86/dell-laptop.c | 94 ++++++++++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index d688d806a8a5..83e3d7f9d923 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -32,6 +32,7 @@ #include #include #include "../../firmware/dcdbas.h" +#include "dell-rbtn.h" #define BRIGHTNESS_TOKEN 0x7d #define KBD_LED_OFF_TOKEN 0x01E1 @@ -642,6 +643,20 @@ static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, return false; } +static int (*dell_rbtn_notifier_register_func)(struct notifier_block *); +static int (*dell_rbtn_notifier_unregister_func)(struct notifier_block *); + +static int dell_laptop_rbtn_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + schedule_delayed_work(&dell_rfkill_work, 0); + return NOTIFY_OK; +} + +static struct notifier_block dell_laptop_rbtn_notifier = { + .notifier_call = dell_laptop_rbtn_notifier_call, +}; + static int __init dell_setup_rfkill(void) { int status, ret, whitelisted; @@ -718,10 +733,62 @@ static int __init dell_setup_rfkill(void) goto err_wwan; } - ret = i8042_install_filter(dell_laptop_i8042_filter); - if (ret) { - pr_warn("Unable to install key filter\n"); + /* + * Dell Airplane Mode Switch driver (dell-rbtn) supports ACPI devices + * which can receive events from HW slider switch. + * + * Dell SMBIOS on whitelisted models supports controlling radio devices + * but does not support receiving HW button switch events. We can use + * i8042 filter hook function to receive keyboard data and handle + * keycode for HW button. + * + * So if it is possible we will use Dell Airplane Mode Switch ACPI + * driver for receiving HW events and Dell SMBIOS for setting rfkill + * states. If ACPI driver or device is not available we will fallback to + * i8042 filter hook function. + * + * To prevent duplicate rfkill devices which control and do same thing, + * dell-rbtn driver will automatically remove its own rfkill devices + * once function dell_rbtn_notifier_register() is called. + */ + + dell_rbtn_notifier_register_func = + symbol_request(dell_rbtn_notifier_register); + if (dell_rbtn_notifier_register_func) { + dell_rbtn_notifier_unregister_func = + symbol_request(dell_rbtn_notifier_unregister); + if (!dell_rbtn_notifier_unregister_func) { + symbol_put(dell_rbtn_notifier_register); + dell_rbtn_notifier_register_func = NULL; + } + } + + if (dell_rbtn_notifier_register_func) { + ret = dell_rbtn_notifier_register_func( + &dell_laptop_rbtn_notifier); + symbol_put(dell_rbtn_notifier_register); + dell_rbtn_notifier_register_func = NULL; + if (ret != 0) { + symbol_put(dell_rbtn_notifier_unregister); + dell_rbtn_notifier_unregister_func = NULL; + } + } else { + pr_info("Symbols from dell-rbtn acpi driver are not available\n"); + ret = -ENODEV; + } + + if (ret == 0) { + pr_info("Using dell-rbtn acpi driver for receiving events\n"); + } else if (ret != -ENODEV) { + pr_warn("Unable to register dell rbtn notifier\n"); goto err_filter; + } else { + ret = i8042_install_filter(dell_laptop_i8042_filter); + if (ret) { + pr_warn("Unable to install key filter\n"); + goto err_filter; + } + pr_info("Using i8042 filter function for receiving events\n"); } return 0; @@ -744,6 +811,14 @@ err_wifi: static void dell_cleanup_rfkill(void) { + if (dell_rbtn_notifier_unregister_func) { + dell_rbtn_notifier_unregister_func(&dell_laptop_rbtn_notifier); + symbol_put(dell_rbtn_notifier_unregister); + dell_rbtn_notifier_unregister_func = NULL; + } else { + i8042_remove_filter(dell_laptop_i8042_filter); + } + cancel_delayed_work_sync(&dell_rfkill_work); if (wifi_rfkill) { rfkill_unregister(wifi_rfkill); rfkill_destroy(wifi_rfkill); @@ -1961,8 +2036,6 @@ static int __init dell_init(void) return 0; fail_backlight: - i8042_remove_filter(dell_laptop_i8042_filter); - cancel_delayed_work_sync(&dell_rfkill_work); dell_cleanup_rfkill(); fail_rfkill: free_page((unsigned long)bufferpage); @@ -1983,8 +2056,6 @@ static void __exit dell_exit(void) if (quirks && quirks->touchpad_led) touchpad_led_exit(); kbd_led_exit(); - i8042_remove_filter(dell_laptop_i8042_filter); - cancel_delayed_work_sync(&dell_rfkill_work); backlight_device_unregister(dell_backlight_device); dell_cleanup_rfkill(); if (platform_device) { @@ -1995,7 +2066,14 @@ static void __exit dell_exit(void) free_page((unsigned long)buffer); } -module_init(dell_init); +/* dell-rbtn.c driver export functions which will not work correctly (and could + * cause kernel crash) if they are called before dell-rbtn.c init code. This is + * not problem when dell-rbtn.c is compiled as external module. When both files + * (dell-rbtn.c and dell-laptop.c) are compiled statically into kernel, then we + * need to ensure that dell_init() will be called after initializing dell-rbtn. + * This can be achieved by late_initcall() instead module_init(). + */ +late_initcall(dell_init); module_exit(dell_exit); MODULE_AUTHOR("Matthew Garrett "); -- cgit v1.2.3 From 4b200b4604bec3388426159f1656109d19fadf6e Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Sat, 13 Jun 2015 15:23:33 +0200 Subject: ideapad: fix software rfkill setting This fixes a several year old regression that I found while trying to get the Yoga 3 11 to work. The ideapad_rfk_set function is meant to send a command to the embedded controller through ACPI, but as of c1f73658ed, it sends the index of the rfkill device instead of the command, and ignores the opcode field. This changes it back to the original behavior, which indeed flips the rfkill state as seen in the debugfs interface. Signed-off-by: Arnd Bergmann Fixes: c1f73658ed ("ideapad: pass ideapad_priv as argument (part 2)") Cc: stable@vger.kernel.org # v2.6.38+ Signed-off-by: Darren Hart --- drivers/platform/x86/ideapad-laptop.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 1db3d2176d56..1bd34dbfe73a 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -464,8 +464,9 @@ static const struct ideapad_rfk_data ideapad_rfk_data[] = { static int ideapad_rfk_set(void *data, bool blocked) { struct ideapad_rfk_priv *priv = data; + int opcode = ideapad_rfk_data[priv->dev].opcode; - return write_ec_cmd(priv->priv->adev->handle, priv->dev, !blocked); + return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked); } static struct rfkill_ops ideapad_rfk_ops = { -- cgit v1.2.3 From 5ee7041e5bc0fe8ba04a554dc2f9a18f709bc005 Mon Sep 17 00:00:00 2001 From: Anisse Astier Date: Sun, 14 Jun 2015 22:32:23 +0200 Subject: MAINTAINERS: Orphan x86 driver msi-wmi I haven't touched the code in a long time, and I don't have access to the hardware anymore to test any changes to this driver. Signed-off-by: Anisse Astier Signed-off-by: Darren Hart --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 5808066e1677..4188e84bd257 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6538,9 +6538,8 @@ S: Maintained F: drivers/platform/x86/msi-laptop.c MSI WMI SUPPORT -M: Anisse Astier L: platform-driver-x86@vger.kernel.org -S: Supported +S: Orphan F: drivers/platform/x86/msi-wmi.c MSI001 MEDIA DRIVER -- cgit v1.2.3