Merge tag 'platform-drivers-x86-v4.2-1' of git://git.infradead.org/users/dvhart/linux...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 2 Jul 2015 01:55:34 +0000 (18:55 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 2 Jul 2015 01:55:34 +0000 (18:55 -0700)
Pull x86 platform driver updates from Darren Hart:
 "Fairly routine update for platform-drivers-x86.

  Mostly fixes and cleanups, with a significant refactoring of toshiba*
  drivers.  Includes the addition of the dell-rbtn driver.

  Details:

  asus-wmi:
   - fan control

  dell*:
   - add Dell airplane mode switch driver

  ideapad-laptop:
   - platform rfkill fixes, and regression fix

  pvpanic:
   - handle missing _STA correctly

  toshiba*:
   - rafactor bluetooth support
   - haps documentation
   - driver cleanup

  other:
   - Use acpi_video_unregister_backlight instead of
     acpi_video_unregister in serveral drivers.
   - Orphan msi-wmi.

* tag 'platform-drivers-x86-v4.2-1' of git://git.infradead.org/users/dvhart/linux-platform-drivers-x86: (24 commits)
  MAINTAINERS: Orphan x86 driver msi-wmi
  ideapad: fix software rfkill setting
  dell-laptop: Use dell-rbtn instead i8042 filter when possible
  dell-rbtn: Export notifier for other kernel modules
  dell-rbtn: Dell Airplane Mode Switch driver
  samsung-laptop: Use acpi_video_unregister_backlight instead of acpi_video_unregister
  asus-wmi: Use acpi_video_unregister_backlight instead of acpi_video_unregister
  apple_gmux: Use acpi_video_unregister_backlight instead of acpi_video_unregister
  pvpanic: handle missing _STA correctly
  ideapad_laptop: Lenovo G50-30 fix rfkill reports wireless blocked
  asus-wmi: add fan control
  Documentation/ABI: Add file describing the sysfs entries for toshiba_haps
  toshiba_haps: Make use of DEVICE_ATTR_{RW, WO} macros
  toshiba_haps: Replace sscanf with kstrtoint
  toshiba_acpi: Bump driver version to 0.22
  toshiba_acpi: Remove TOS_FAILURE check from some functions
  toshiba_acpi: Comments cleanup
  toshiba_acpi: Rename hci_{read, write}1 functions
  toshiba_acpi: Remove no longer needed hci_{read, write}2 functions
  toshiba_bluetooth: Change BT status message to debug
  ...

13 files changed:
Documentation/ABI/testing/sysfs-driver-toshiba_haps [new file with mode: 0644]
MAINTAINERS
drivers/platform/x86/Kconfig
drivers/platform/x86/Makefile
drivers/platform/x86/asus-wmi.c
drivers/platform/x86/dell-laptop.c
drivers/platform/x86/dell-rbtn.c [new file with mode: 0644]
drivers/platform/x86/dell-rbtn.h [new file with mode: 0644]
drivers/platform/x86/ideapad-laptop.c
drivers/platform/x86/pvpanic.c
drivers/platform/x86/toshiba_acpi.c
drivers/platform/x86/toshiba_bluetooth.c
drivers/platform/x86/toshiba_haps.c

diff --git a/Documentation/ABI/testing/sysfs-driver-toshiba_haps b/Documentation/ABI/testing/sysfs-driver-toshiba_haps
new file mode 100644 (file)
index 0000000..a662370
--- /dev/null
@@ -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 <coproscefalo@gmail.com>
+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 <coproscefalo@gmail.com>
+Description:   This file turns off the built-in accelerometer for a few
+               seconds and then restore normal operation. Accepting 1 as the
+               only parameter.
index 058b0fb..7628df3 100644 (file)
@@ -3216,6 +3216,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 <pali.rohar@gmail.com>
+S:     Maintained
+F:     drivers/platform/x86/dell-rbtn.*
+
 DELL LAPTOP FREEFALL DRIVER
 M:     Pali Rohár <pali.rohar@gmail.com>
 S:     Maintained
@@ -6786,9 +6791,8 @@ S:        Maintained
 F:     drivers/platform/x86/msi-laptop.c
 
 MSI WMI SUPPORT
-M:     Anisse Astier <anisse@astier.eu>
 L:     platform-driver-x86@vger.kernel.org
-S:     Supported
+S:     Orphan
 F:     drivers/platform/x86/msi-wmi.c
 
 MSI001 MEDIA DRIVER
index 7137a07..1e2f57f 100644 (file)
@@ -141,6 +141,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"
@@ -622,7 +638,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
@@ -653,6 +668,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
index f82232b..b3e54ed 100644 (file)
@@ -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
index 6f8558f..efbc3f0 100644 (file)
@@ -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;
+
 /*
  * <platform>/    - 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;
@@ -1831,6 +2132,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;
index 01d0810..35de903 100644 (file)
@@ -33,6 +33,7 @@
 #include <linux/seq_file.h>
 #include <acpi/video.h>
 #include "../../firmware/dcdbas.h"
+#include "dell-rbtn.h"
 
 #define BRIGHTNESS_TOKEN 0x7d
 #define KBD_LED_OFF_TOKEN 0x01E1
@@ -643,6 +644,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;
@@ -719,10 +734,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;
@@ -745,6 +812,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);
@@ -1957,8 +2032,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);
@@ -1979,8 +2052,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) {
@@ -1991,7 +2062,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 <mjg@redhat.com>");
diff --git a/drivers/platform/x86/dell-rbtn.c b/drivers/platform/x86/dell-rbtn.c
new file mode 100644 (file)
index 0000000..cd410e3
--- /dev/null
@@ -0,0 +1,423 @@
+/*
+    Dell Airplane Mode Switch driver
+    Copyright (C) 2014-2015  Pali Rohár <pali.rohar@gmail.com>
+
+    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 <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/rfkill.h>
+#include <linux/input.h>
+
+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 = &param;
+
+       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,
+};
+
+
+/*
+ * 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
+ */
+
+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:
+               if (auto_remove_rfkill && rbtn_chain_head.head)
+                       ret = 0;
+               else
+                       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);
+               atomic_notifier_call_chain(&rbtn_chain_head, event, device);
+               break;
+       default:
+               break;
+       }
+}
+
+
+/*
+ * module functions
+ */
+
+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 <pali.rohar@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/dell-rbtn.h b/drivers/platform/x86/dell-rbtn.h
new file mode 100644 (file)
index 0000000..c59cc6b
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+    Dell Airplane Mode Switch driver
+    Copyright (C) 2014-2015  Pali Rohár <pali.rohar@gmail.com>
+
+    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
index bea0228..76b5738 100644 (file)
@@ -465,8 +465,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 = {
@@ -837,6 +838,13 @@ static const struct dmi_system_id no_hw_rfkill_list[] = {
                        DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G40-30"),
                },
        },
+       {
+               .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 = {
index 073a90a..fd86dab 100644 (file)
@@ -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,
index 59bf27e..3ad7b1f 100644 (file)
@@ -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 <linux/kernel.h>
@@ -41,7 +41,6 @@
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
 #include <linux/backlight.h>
-#include <linux/rfkill.h>
 #include <linux/input.h>
 #include <linux/input/sparse-keymap.h>
 #include <linux/leds.h>
@@ -82,7 +81,7 @@ MODULE_LICENSE("GPL");
 
 #define TCI_WORDS                      6
 
-/* operations */
+/* Operations */
 #define HCI_SET                                0xff00
 #define HCI_GET                                0xfe00
 #define SCI_OPEN                       0xf100
@@ -90,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
@@ -105,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
@@ -127,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
@@ -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;
@@ -330,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];
@@ -345,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];
@@ -359,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
  */
@@ -395,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;
        }
@@ -433,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;
        }
@@ -481,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) {
@@ -522,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;
 
@@ -625,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;
@@ -646,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;
@@ -703,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");
@@ -825,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;
@@ -839,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;
@@ -919,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) {
@@ -948,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) {
@@ -974,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 ||
@@ -1002,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) {
@@ -1194,121 +1165,31 @@ 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;
 }
 
-/* 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,
-};
-
+/* Transflective Backlight */
 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;
 }
@@ -1318,12 +1199,13 @@ 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;
 }
 
-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;
@@ -1341,7 +1223,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);
 
@@ -1396,7 +1278,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;
 }
 
@@ -1446,7 +1328,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;
 }
 
@@ -1531,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);
        }
@@ -1552,7 +1435,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;
 }
 
@@ -1592,7 +1475,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
@@ -1620,7 +1503,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;
@@ -1632,7 +1515,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");
@@ -1769,7 +1652,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)
@@ -2391,7 +2274,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)
@@ -2408,8 +2291,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");
 }
@@ -2490,7 +2373,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);
@@ -2502,7 +2385,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:
@@ -2579,7 +2462,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;
        }
@@ -2689,11 +2572,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)
@@ -2730,7 +2608,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)
@@ -2766,33 +2643,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;
@@ -2930,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;
 }
index 2498007..c5e4508 100644 (file)
  * 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 <linux/init.h>
 #include <linux/types.h>
 #include <linux/acpi.h>
+#include <linux/rfkill.h>
 
 #define BT_KILLSWITCH_MASK     0x01
 #define BT_PLUGGED_MASK                0x40
@@ -34,6 +29,15 @@ MODULE_AUTHOR("Jes Sorensen <Jes.Sorensen@gmail.com>");
 MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver");
 MODULE_LICENSE("GPL");
 
+struct toshiba_bluetooth_dev {
+       struct acpi_device *acpi_dev;
+       struct rfkill *rfk;
+
+       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);
@@ -95,41 +99,12 @@ static int toshiba_bluetooth_status(acpi_handle handle)
                return -ENXIO;
        }
 
-       pr_info("Bluetooth status %llu\n", status);
-
        return status;
 }
 
 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)) {
@@ -165,20 +140,102 @@ 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;
+
+       pr_debug("Bluetooth status %d killswitch %d plugged %d powered %d\n",
+                status, bt_dev->killswitch, bt_dev->plugged, bt_dev->powered);
+
+       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)
 {
-       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
 
 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 +244,54 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device)
 
        pr_info("Toshiba ACPI Bluetooth device driver\n");
 
-       /* Enable the BT device */
-       result = toshiba_bluetooth_enable(device->handle);
-       if (result)
+       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;
+       }
+
+       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;
 }
 
 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);
 }
 
index 65300b6..7f2afc6 100644 (file)
@@ -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;
@@ -95,15 +100,21 @@ 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,
                                      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 */
@@ -113,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,