|
@@ -52,6 +52,8 @@
|
|
|
#include <linux/input/sparse-keymap.h>
|
|
|
#include <linux/leds.h>
|
|
|
#include <linux/slab.h>
|
|
|
+#include <linux/workqueue.h>
|
|
|
+#include <linux/i8042.h>
|
|
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
|
@@ -61,6 +63,9 @@ MODULE_AUTHOR("John Belmonte");
|
|
|
MODULE_DESCRIPTION("Toshiba Laptop ACPI Extras Driver");
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
+/* Scan code for Fn key on TOS1900 models */
|
|
|
+#define TOS1900_FN_SCAN 0x6e
|
|
|
+
|
|
|
/* Toshiba ACPI method paths */
|
|
|
#define METHOD_VIDEO_OUT "\\_SB_.VALX.DSSX"
|
|
|
|
|
@@ -95,6 +100,8 @@ MODULE_LICENSE("GPL");
|
|
|
#define HCI_WIRELESS 0x0056
|
|
|
|
|
|
/* field definitions */
|
|
|
+#define HCI_HOTKEY_DISABLE 0x0b
|
|
|
+#define HCI_HOTKEY_ENABLE 0x09
|
|
|
#define HCI_LCD_BRIGHTNESS_BITS 3
|
|
|
#define HCI_LCD_BRIGHTNESS_SHIFT (16-HCI_LCD_BRIGHTNESS_BITS)
|
|
|
#define HCI_LCD_BRIGHTNESS_LEVELS (1 << HCI_LCD_BRIGHTNESS_BITS)
|
|
@@ -111,6 +118,7 @@ struct toshiba_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;
|
|
|
struct led_classdev led_dev;
|
|
|
|
|
@@ -122,10 +130,14 @@ struct toshiba_acpi_dev {
|
|
|
unsigned int video_supported:1;
|
|
|
unsigned int fan_supported:1;
|
|
|
unsigned int system_event_supported:1;
|
|
|
+ unsigned int ntfy_supported:1;
|
|
|
+ unsigned int info_supported:1;
|
|
|
|
|
|
struct mutex mutex;
|
|
|
};
|
|
|
|
|
|
+static struct toshiba_acpi_dev *toshiba_acpi;
|
|
|
+
|
|
|
static const struct acpi_device_id toshiba_device_ids[] = {
|
|
|
{"TOS6200", 0},
|
|
|
{"TOS6208", 0},
|
|
@@ -847,10 +859,78 @@ static const struct backlight_ops toshiba_backlight_data = {
|
|
|
.update_status = set_lcd_status,
|
|
|
};
|
|
|
|
|
|
+static bool toshiba_acpi_i8042_filter(unsigned char data, unsigned char str,
|
|
|
+ struct serio *port)
|
|
|
+{
|
|
|
+ if (str & 0x20)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (unlikely(data == 0xe0))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if ((data & 0x7f) == TOS1900_FN_SCAN) {
|
|
|
+ schedule_work(&toshiba_acpi->hotkey_work);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static void toshiba_acpi_hotkey_work(struct work_struct *work)
|
|
|
+{
|
|
|
+ acpi_handle ec_handle = ec_get_handle();
|
|
|
+ acpi_status status;
|
|
|
+
|
|
|
+ if (!ec_handle)
|
|
|
+ return;
|
|
|
+
|
|
|
+ status = acpi_evaluate_object(ec_handle, "NTFY", NULL, NULL);
|
|
|
+ if (ACPI_FAILURE(status))
|
|
|
+ pr_err("ACPI NTFY method execution failed\n");
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Returns hotkey scancode, or < 0 on failure.
|
|
|
+ */
|
|
|
+static int toshiba_acpi_query_hotkey(struct toshiba_acpi_dev *dev)
|
|
|
+{
|
|
|
+ struct acpi_buffer buf;
|
|
|
+ union acpi_object out_obj;
|
|
|
+ acpi_status status;
|
|
|
+
|
|
|
+ buf.pointer = &out_obj;
|
|
|
+ buf.length = sizeof(out_obj);
|
|
|
+
|
|
|
+ status = acpi_evaluate_object(dev->acpi_dev->handle, "INFO",
|
|
|
+ NULL, &buf);
|
|
|
+ if (ACPI_FAILURE(status) || out_obj.type != ACPI_TYPE_INTEGER) {
|
|
|
+ pr_err("ACPI INFO method execution failed\n");
|
|
|
+ return -EIO;
|
|
|
+ }
|
|
|
+
|
|
|
+ return out_obj.integer.value;
|
|
|
+}
|
|
|
+
|
|
|
+static void toshiba_acpi_report_hotkey(struct toshiba_acpi_dev *dev,
|
|
|
+ int scancode)
|
|
|
+{
|
|
|
+ if (scancode == 0x100)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /* act on key press; ignore key release */
|
|
|
+ if (scancode & 0x80)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (!sparse_keymap_report_event(dev->hotkey_dev, scancode, 1, true))
|
|
|
+ pr_info("Unknown key %x\n", scancode);
|
|
|
+}
|
|
|
+
|
|
|
static int __devinit toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
|
|
|
{
|
|
|
acpi_status status;
|
|
|
+ acpi_handle ec_handle, handle;
|
|
|
int error;
|
|
|
+ u32 hci_result;
|
|
|
|
|
|
dev->hotkey_dev = input_allocate_device();
|
|
|
if (!dev->hotkey_dev) {
|
|
@@ -866,21 +946,67 @@ static int __devinit toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
|
|
|
if (error)
|
|
|
goto err_free_dev;
|
|
|
|
|
|
+ /*
|
|
|
+ * For some machines the SCI responsible for providing hotkey
|
|
|
+ * notification doesn't fire. We can trigger the notification
|
|
|
+ * whenever the Fn key is pressed using the NTFY method, if
|
|
|
+ * supported, so if it's present set up an i8042 key filter
|
|
|
+ * for this purpose.
|
|
|
+ */
|
|
|
+ status = AE_ERROR;
|
|
|
+ ec_handle = ec_get_handle();
|
|
|
+ if (ec_handle)
|
|
|
+ status = acpi_get_handle(ec_handle, "NTFY", &handle);
|
|
|
+
|
|
|
+ if (ACPI_SUCCESS(status)) {
|
|
|
+ INIT_WORK(&dev->hotkey_work, toshiba_acpi_hotkey_work);
|
|
|
+
|
|
|
+ error = i8042_install_filter(toshiba_acpi_i8042_filter);
|
|
|
+ if (error) {
|
|
|
+ pr_err("Error installing key filter\n");
|
|
|
+ goto err_free_keymap;
|
|
|
+ }
|
|
|
+
|
|
|
+ dev->ntfy_supported = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Determine hotkey query interface. Prefer using the INFO
|
|
|
+ * method when it is available.
|
|
|
+ */
|
|
|
+ status = acpi_get_handle(dev->acpi_dev->handle, "INFO", &handle);
|
|
|
+ if (ACPI_SUCCESS(status)) {
|
|
|
+ dev->info_supported = 1;
|
|
|
+ } else {
|
|
|
+ hci_write1(dev, HCI_SYSTEM_EVENT, 1, &hci_result);
|
|
|
+ if (hci_result == HCI_SUCCESS)
|
|
|
+ dev->system_event_supported = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!dev->info_supported && !dev->system_event_supported) {
|
|
|
+ pr_warn("No hotkey query interface found\n");
|
|
|
+ goto err_remove_filter;
|
|
|
+ }
|
|
|
+
|
|
|
status = acpi_evaluate_object(dev->acpi_dev->handle, "ENAB", NULL, NULL);
|
|
|
if (ACPI_FAILURE(status)) {
|
|
|
pr_info("Unable to enable hotkeys\n");
|
|
|
error = -ENODEV;
|
|
|
- goto err_free_keymap;
|
|
|
+ goto err_remove_filter;
|
|
|
}
|
|
|
|
|
|
error = input_register_device(dev->hotkey_dev);
|
|
|
if (error) {
|
|
|
pr_info("Unable to register input device\n");
|
|
|
- goto err_free_keymap;
|
|
|
+ goto err_remove_filter;
|
|
|
}
|
|
|
|
|
|
+ hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE, &hci_result);
|
|
|
return 0;
|
|
|
|
|
|
+ err_remove_filter:
|
|
|
+ if (dev->ntfy_supported)
|
|
|
+ i8042_remove_filter(toshiba_acpi_i8042_filter);
|
|
|
err_free_keymap:
|
|
|
sparse_keymap_free(dev->hotkey_dev);
|
|
|
err_free_dev:
|
|
@@ -895,6 +1021,11 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev, int type)
|
|
|
|
|
|
remove_toshiba_proc_entries(dev);
|
|
|
|
|
|
+ if (dev->ntfy_supported) {
|
|
|
+ i8042_remove_filter(toshiba_acpi_i8042_filter);
|
|
|
+ cancel_work_sync(&dev->hotkey_work);
|
|
|
+ }
|
|
|
+
|
|
|
if (dev->hotkey_dev) {
|
|
|
input_unregister_device(dev->hotkey_dev);
|
|
|
sparse_keymap_free(dev->hotkey_dev);
|
|
@@ -911,6 +1042,9 @@ static int toshiba_acpi_remove(struct acpi_device *acpi_dev, int type)
|
|
|
if (dev->illumination_supported)
|
|
|
led_classdev_unregister(&dev->led_dev);
|
|
|
|
|
|
+ if (toshiba_acpi)
|
|
|
+ toshiba_acpi = NULL;
|
|
|
+
|
|
|
kfree(dev);
|
|
|
|
|
|
return 0;
|
|
@@ -936,12 +1070,14 @@ static int __devinit toshiba_acpi_add(struct acpi_device *acpi_dev)
|
|
|
{
|
|
|
struct toshiba_acpi_dev *dev;
|
|
|
const char *hci_method;
|
|
|
- u32 hci_result;
|
|
|
u32 dummy;
|
|
|
bool bt_present;
|
|
|
int ret = 0;
|
|
|
struct backlight_properties props;
|
|
|
|
|
|
+ if (toshiba_acpi)
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
pr_info("Toshiba Laptop ACPI Extras version %s\n",
|
|
|
TOSHIBA_ACPI_VERSION);
|
|
|
|
|
@@ -963,11 +1099,6 @@ static int __devinit toshiba_acpi_add(struct acpi_device *acpi_dev)
|
|
|
|
|
|
mutex_init(&dev->mutex);
|
|
|
|
|
|
- /* enable event fifo */
|
|
|
- hci_write1(dev, HCI_SYSTEM_EVENT, 1, &hci_result);
|
|
|
- if (hci_result == HCI_SUCCESS)
|
|
|
- dev->system_event_supported = 1;
|
|
|
-
|
|
|
props.type = BACKLIGHT_PLATFORM;
|
|
|
props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1;
|
|
|
dev->backlight_dev = backlight_device_register("toshiba",
|
|
@@ -1024,6 +1155,8 @@ static int __devinit toshiba_acpi_add(struct acpi_device *acpi_dev)
|
|
|
|
|
|
create_toshiba_proc_entries(dev);
|
|
|
|
|
|
+ toshiba_acpi = dev;
|
|
|
+
|
|
|
return 0;
|
|
|
|
|
|
error:
|
|
@@ -1036,40 +1169,64 @@ static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
|
|
|
struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
|
|
|
u32 hci_result, value;
|
|
|
int retries = 3;
|
|
|
+ int scancode;
|
|
|
|
|
|
- if (!dev->system_event_supported || event != 0x80)
|
|
|
+ if (event != 0x80)
|
|
|
return;
|
|
|
|
|
|
- do {
|
|
|
- hci_read1(dev, HCI_SYSTEM_EVENT, &value, &hci_result);
|
|
|
- switch (hci_result) {
|
|
|
- case HCI_SUCCESS:
|
|
|
- if (value == 0x100)
|
|
|
- continue;
|
|
|
- /* act on key press; ignore key release */
|
|
|
- if (value & 0x80)
|
|
|
- continue;
|
|
|
-
|
|
|
- if (!sparse_keymap_report_event(dev->hotkey_dev,
|
|
|
- value, 1, true)) {
|
|
|
- pr_info("Unknown key %x\n",
|
|
|
- value);
|
|
|
+ if (dev->info_supported) {
|
|
|
+ scancode = toshiba_acpi_query_hotkey(dev);
|
|
|
+ if (scancode < 0)
|
|
|
+ pr_err("Failed to query hotkey event\n");
|
|
|
+ else if (scancode != 0)
|
|
|
+ toshiba_acpi_report_hotkey(dev, scancode);
|
|
|
+ } else if (dev->system_event_supported) {
|
|
|
+ do {
|
|
|
+ hci_read1(dev, HCI_SYSTEM_EVENT, &value, &hci_result);
|
|
|
+ switch (hci_result) {
|
|
|
+ case HCI_SUCCESS:
|
|
|
+ toshiba_acpi_report_hotkey(dev, (int)value);
|
|
|
+ break;
|
|
|
+ case HCI_NOT_SUPPORTED:
|
|
|
+ /*
|
|
|
+ * This is a workaround for an unresolved
|
|
|
+ * issue on some machines where system events
|
|
|
+ * sporadically become disabled.
|
|
|
+ */
|
|
|
+ hci_write1(dev, HCI_SYSTEM_EVENT, 1,
|
|
|
+ &hci_result);
|
|
|
+ pr_notice("Re-enabled hotkeys\n");
|
|
|
+ /* fall through */
|
|
|
+ default:
|
|
|
+ retries--;
|
|
|
+ break;
|
|
|
}
|
|
|
- break;
|
|
|
- case HCI_NOT_SUPPORTED:
|
|
|
- /* This is a workaround for an unresolved issue on
|
|
|
- * some machines where system events sporadically
|
|
|
- * become disabled. */
|
|
|
- hci_write1(dev, HCI_SYSTEM_EVENT, 1, &hci_result);
|
|
|
- pr_notice("Re-enabled hotkeys\n");
|
|
|
- /* fall through */
|
|
|
- default:
|
|
|
- retries--;
|
|
|
- break;
|
|
|
- }
|
|
|
- } while (retries && hci_result != HCI_EMPTY);
|
|
|
+ } while (retries && hci_result != HCI_EMPTY);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+static int toshiba_acpi_suspend(struct acpi_device *acpi_dev,
|
|
|
+ pm_message_t state)
|
|
|
+{
|
|
|
+ struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
|
|
|
+ u32 result;
|
|
|
+
|
|
|
+ if (dev->hotkey_dev)
|
|
|
+ hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_DISABLE, &result);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int toshiba_acpi_resume(struct acpi_device *acpi_dev)
|
|
|
+{
|
|
|
+ struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
|
|
|
+ u32 result;
|
|
|
+
|
|
|
+ if (dev->hotkey_dev)
|
|
|
+ hci_write1(dev, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE, &result);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
static struct acpi_driver toshiba_acpi_driver = {
|
|
|
.name = "Toshiba ACPI driver",
|
|
@@ -1080,6 +1237,8 @@ static struct acpi_driver toshiba_acpi_driver = {
|
|
|
.add = toshiba_acpi_add,
|
|
|
.remove = toshiba_acpi_remove,
|
|
|
.notify = toshiba_acpi_notify,
|
|
|
+ .suspend = toshiba_acpi_suspend,
|
|
|
+ .resume = toshiba_acpi_resume,
|
|
|
},
|
|
|
};
|
|
|
|