summaryrefslogtreecommitdiff
path: root/drivers/platform/x86/toshiba_acpi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/toshiba_acpi.c')
-rw-r--r--drivers/platform/x86/toshiba_acpi.c246
1 files changed, 238 insertions, 8 deletions
diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
index c01302989ee4..73833079bac8 100644
--- a/drivers/platform/x86/toshiba_acpi.c
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -51,6 +51,7 @@
#include <linux/dmi.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
+#include <linux/rfkill.h>
#include <linux/toshiba.h>
#include <acpi/video.h>
@@ -114,6 +115,7 @@ MODULE_LICENSE("GPL");
#define HCI_VIDEO_OUT 0x001c
#define HCI_HOTKEY_EVENT 0x001e
#define HCI_LCD_BRIGHTNESS 0x002a
+#define HCI_WIRELESS 0x0056
#define HCI_ACCELEROMETER 0x006d
#define HCI_KBD_ILLUMINATION 0x0095
#define HCI_ECO_MODE 0x0097
@@ -148,6 +150,10 @@ MODULE_LICENSE("GPL");
#define SCI_KBD_MODE_ON 0x8
#define SCI_KBD_MODE_OFF 0x10
#define SCI_KBD_TIME_MAX 0x3c001a
+#define HCI_WIRELESS_STATUS 0x1
+#define HCI_WIRELESS_WWAN 0x3
+#define HCI_WIRELESS_WWAN_STATUS 0x2000
+#define HCI_WIRELESS_WWAN_POWER 0x4000
#define SCI_USB_CHARGE_MODE_MASK 0xff
#define SCI_USB_CHARGE_DISABLED 0x00
#define SCI_USB_CHARGE_ALTERNATE 0x09
@@ -169,6 +175,7 @@ struct toshiba_acpi_dev {
struct led_classdev kbd_led;
struct led_classdev eco_led;
struct miscdevice miscdev;
+ struct rfkill *wwan_rfk;
int force_fan;
int last_key_event;
@@ -197,12 +204,15 @@ struct toshiba_acpi_dev {
unsigned int kbd_function_keys_supported:1;
unsigned int panel_power_on_supported:1;
unsigned int usb_three_supported:1;
+ unsigned int wwan_supported:1;
unsigned int sysfs_created:1;
unsigned int special_functions;
+ bool kbd_event_generated;
bool kbd_led_registered;
bool illumination_led_registered;
bool eco_led_registered;
+ bool killswitch;
};
static struct toshiba_acpi_dev *toshiba_acpi;
@@ -516,6 +526,7 @@ static void toshiba_kbd_illum_available(struct toshiba_acpi_dev *dev)
dev->kbd_illum_supported = 0;
dev->kbd_led_registered = false;
+ dev->kbd_event_generated = false;
if (!sci_open(dev))
return;
@@ -1085,6 +1096,104 @@ static int toshiba_hotkey_event_type_get(struct toshiba_acpi_dev *dev,
return -EIO;
}
+/* Wireless status (RFKill, WLAN, BT, WWAN) */
+static int toshiba_wireless_status(struct toshiba_acpi_dev *dev)
+{
+ u32 in[TCI_WORDS] = { HCI_GET, HCI_WIRELESS, 0, 0, 0, 0 };
+ u32 out[TCI_WORDS];
+ acpi_status status;
+
+ in[3] = HCI_WIRELESS_STATUS;
+ status = tci_raw(dev, in, out);
+
+ if (ACPI_FAILURE(status)) {
+ pr_err("ACPI call to get Wireless status failed\n");
+ return -EIO;
+ }
+
+ if (out[0] == TOS_NOT_SUPPORTED)
+ return -ENODEV;
+
+ if (out[0] != TOS_SUCCESS)
+ return -EIO;
+
+ dev->killswitch = !!(out[2] & HCI_WIRELESS_STATUS);
+
+ return 0;
+}
+
+/* WWAN */
+static void toshiba_wwan_available(struct toshiba_acpi_dev *dev)
+{
+ u32 in[TCI_WORDS] = { HCI_GET, HCI_WIRELESS, 0, 0, 0, 0 };
+ u32 out[TCI_WORDS];
+ acpi_status status;
+
+ dev->wwan_supported = 0;
+
+ /*
+ * WWAN support can be queried by setting the in[3] value to
+ * HCI_WIRELESS_WWAN (0x03).
+ *
+ * If supported, out[0] contains TOS_SUCCESS and out[2] contains
+ * HCI_WIRELESS_WWAN_STATUS (0x2000).
+ *
+ * If not supported, out[0] contains TOS_INPUT_DATA_ERROR (0x8300)
+ * or TOS_NOT_SUPPORTED (0x8000).
+ */
+ in[3] = HCI_WIRELESS_WWAN;
+ status = tci_raw(dev, in, out);
+
+ if (ACPI_FAILURE(status)) {
+ pr_err("ACPI call to get WWAN status failed\n");
+ return;
+ }
+
+ if (out[0] != TOS_SUCCESS)
+ return;
+
+ dev->wwan_supported = (out[2] == HCI_WIRELESS_WWAN_STATUS);
+}
+
+static int toshiba_wwan_set(struct toshiba_acpi_dev *dev, u32 state)
+{
+ u32 in[TCI_WORDS] = { HCI_SET, HCI_WIRELESS, state, 0, 0, 0 };
+ u32 out[TCI_WORDS];
+ acpi_status status;
+
+ in[3] = HCI_WIRELESS_WWAN_STATUS;
+ status = tci_raw(dev, in, out);
+
+ if (ACPI_FAILURE(status)) {
+ pr_err("ACPI call to set WWAN status failed\n");
+ return -EIO;
+ }
+
+ if (out[0] == TOS_NOT_SUPPORTED)
+ return -ENODEV;
+
+ if (out[0] != TOS_SUCCESS)
+ return -EIO;
+
+ /*
+ * Some devices only need to call HCI_WIRELESS_WWAN_STATUS to
+ * (de)activate the device, but some others need the
+ * HCI_WIRELESS_WWAN_POWER call as well.
+ */
+ in[3] = HCI_WIRELESS_WWAN_POWER;
+ status = tci_raw(dev, in, out);
+
+ if (ACPI_FAILURE(status)) {
+ pr_err("ACPI call to set WWAN power failed\n");
+ return -EIO;
+ }
+
+ if (out[0] == TOS_NOT_SUPPORTED)
+ return -ENODEV;
+
+ return out[0] == TOS_SUCCESS ? 0 : -EIO;
+}
+
/* Transflective Backlight */
static int get_tr_backlight_status(struct toshiba_acpi_dev *dev, u32 *status)
{
@@ -1535,6 +1644,11 @@ static const struct backlight_ops toshiba_backlight_data = {
.update_status = set_lcd_status,
};
+/* Keyboard backlight work */
+static void toshiba_acpi_kbd_bl_work(struct work_struct *work);
+
+static DECLARE_WORK(kbd_bl_work, toshiba_acpi_kbd_bl_work);
+
/*
* Sysfs files
*/
@@ -1634,6 +1748,24 @@ static ssize_t kbd_backlight_mode_store(struct device *dev,
return ret;
toshiba->kbd_mode = mode;
+
+ /*
+ * Some laptop models with the second generation backlit
+ * keyboard (type 2) do not generate the keyboard backlight
+ * changed event (0x92), and thus, the driver will never update
+ * the sysfs entries.
+ *
+ * The event is generated right when changing the keyboard
+ * backlight mode and the *notify function will set the
+ * kbd_event_generated to true.
+ *
+ * In case the event is not generated, schedule the keyboard
+ * backlight work to update the sysfs entries and emulate the
+ * event via genetlink.
+ */
+ if (toshiba->kbd_type == 2 &&
+ !toshiba_acpi->kbd_event_generated)
+ schedule_work(&kbd_bl_work);
}
return count;
@@ -2166,6 +2298,21 @@ static struct attribute_group toshiba_attr_group = {
.attrs = toshiba_attributes,
};
+static void toshiba_acpi_kbd_bl_work(struct work_struct *work)
+{
+ struct acpi_device *acpi_dev = toshiba_acpi->acpi_dev;
+
+ /* Update the sysfs entries */
+ if (sysfs_update_group(&acpi_dev->dev.kobj,
+ &toshiba_attr_group))
+ pr_err("Unable to update sysfs entries\n");
+
+ /* Emulate the keyboard backlight event */
+ acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class,
+ dev_name(&acpi_dev->dev),
+ 0x92, 0);
+}
+
/*
* Misc device
*/
@@ -2242,6 +2389,67 @@ static const struct file_operations toshiba_acpi_fops = {
};
/*
+ * WWAN RFKill handlers
+ */
+static int toshiba_acpi_wwan_set_block(void *data, bool blocked)
+{
+ struct toshiba_acpi_dev *dev = data;
+ int ret;
+
+ ret = toshiba_wireless_status(dev);
+ if (ret)
+ return ret;
+
+ if (!dev->killswitch)
+ return 0;
+
+ return toshiba_wwan_set(dev, !blocked);
+}
+
+static void toshiba_acpi_wwan_poll(struct rfkill *rfkill, void *data)
+{
+ struct toshiba_acpi_dev *dev = data;
+
+ if (toshiba_wireless_status(dev))
+ return;
+
+ rfkill_set_hw_state(dev->wwan_rfk, !dev->killswitch);
+}
+
+static const struct rfkill_ops wwan_rfk_ops = {
+ .set_block = toshiba_acpi_wwan_set_block,
+ .poll = toshiba_acpi_wwan_poll,
+};
+
+static int toshiba_acpi_setup_wwan_rfkill(struct toshiba_acpi_dev *dev)
+{
+ int ret = toshiba_wireless_status(dev);
+
+ if (ret)
+ return ret;
+
+ dev->wwan_rfk = rfkill_alloc("Toshiba WWAN",
+ &dev->acpi_dev->dev,
+ RFKILL_TYPE_WWAN,
+ &wwan_rfk_ops,
+ dev);
+ if (!dev->wwan_rfk) {
+ pr_err("Unable to allocate WWAN rfkill device\n");
+ return -ENOMEM;
+ }
+
+ rfkill_set_hw_state(dev->wwan_rfk, !dev->killswitch);
+
+ ret = rfkill_register(dev->wwan_rfk);
+ if (ret) {
+ pr_err("Unable to register WWAN rfkill device\n");
+ rfkill_destroy(dev->wwan_rfk);
+ }
+
+ return ret;
+}
+
+/*
* Hotkeys
*/
static int toshiba_acpi_enable_hotkeys(struct toshiba_acpi_dev *dev)
@@ -2484,6 +2692,14 @@ static int toshiba_acpi_setup_backlight(struct toshiba_acpi_dev *dev)
brightness = __get_lcd_brightness(dev);
if (brightness < 0)
return 0;
+ /*
+ * If transflective backlight is supported and the brightness is zero
+ * (lowest brightness level), the set_lcd_brightness function will
+ * activate the transflective backlight, making the LCD appear to be
+ * turned off, simply increment the brightness level to avoid that.
+ */
+ if (dev->tr_backlight_supported && brightness == 0)
+ brightness++;
ret = set_lcd_brightness(dev, brightness);
if (ret) {
pr_debug("Backlight method is read-only, disabling backlight support\n");
@@ -2561,6 +2777,8 @@ static void print_supported_features(struct toshiba_acpi_dev *dev)
pr_cont(" panel-power-on");
if (dev->usb_three_supported)
pr_cont(" usb3");
+ if (dev->wwan_supported)
+ pr_cont(" wwan");
pr_cont("\n");
}
@@ -2598,6 +2816,11 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev)
if (dev->eco_led_registered)
led_classdev_unregister(&dev->eco_led);
+ if (dev->wwan_rfk) {
+ rfkill_unregister(dev->wwan_rfk);
+ rfkill_destroy(dev->wwan_rfk);
+ }
+
if (toshiba_acpi)
toshiba_acpi = NULL;
@@ -2736,6 +2959,10 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
ret = get_fan_status(dev, &dummy);
dev->fan_supported = !ret;
+ toshiba_wwan_available(dev);
+ if (dev->wwan_supported)
+ toshiba_acpi_setup_wwan_rfkill(dev);
+
print_supported_features(dev);
ret = sysfs_create_group(&dev->acpi_dev->dev.kobj,
@@ -2760,7 +2987,6 @@ error:
static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
{
struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
- int ret;
switch (event) {
case 0x80: /* Hotkeys and some system events */
@@ -2790,10 +3016,10 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
pr_info("SATA power event received %x\n", event);
break;
case 0x92: /* Keyboard backlight mode changed */
+ toshiba_acpi->kbd_event_generated = true;
/* Update sysfs entries */
- ret = sysfs_update_group(&acpi_dev->dev.kobj,
- &toshiba_attr_group);
- if (ret)
+ if (sysfs_update_group(&acpi_dev->dev.kobj,
+ &toshiba_attr_group))
pr_err("Unable to update sysfs entries\n");
break;
case 0x85: /* Unknown */
@@ -2808,7 +3034,8 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class,
dev_name(&acpi_dev->dev),
- event, 0);
+ event, (event == 0x80) ?
+ dev->last_key_event : 0);
}
#ifdef CONFIG_PM_SLEEP
@@ -2832,12 +3059,15 @@ static int toshiba_acpi_resume(struct device *device)
struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device));
if (dev->hotkey_dev) {
- int error = toshiba_acpi_enable_hotkeys(dev);
-
- if (error)
+ if (toshiba_acpi_enable_hotkeys(dev))
pr_info("Unable to re-enable hotkeys\n");
}
+ if (dev->wwan_rfk) {
+ if (!toshiba_wireless_status(dev))
+ rfkill_set_hw_state(dev->wwan_rfk, !dev->killswitch);
+ }
+
return 0;
}
#endif