Add driver specifically for the LID device

This commit is contained in:
Bastien Nocera 2016-07-19 14:04:44 +02:00
parent 47f491acca
commit 4fb17af00d
5 changed files with 527 additions and 0 deletions

View File

@ -0,0 +1,41 @@
From a3df5bf1d383e45c54875355556bc8b3bb2889c5 Mon Sep 17 00:00:00 2001
From: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Date: Tue, 31 May 2016 10:34:45 +0200
Subject: [PATCH 1/2] WIP: acpi/button: remove pointer to old lid_sysfs on
unbind
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
drivers/acpi/button.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c
index 5c3b091..e413599 100644
--- a/drivers/acpi/button.c
+++ b/drivers/acpi/button.c
@@ -188,9 +188,11 @@ done:
remove_dev_dir:
remove_proc_entry(acpi_device_bid(device),
acpi_lid_dir);
+ acpi_lid_dir = NULL;
acpi_device_dir(device) = NULL;
remove_lid_dir:
remove_proc_entry(ACPI_BUTTON_SUBCLASS_LID, acpi_button_dir);
+ acpi_button_dir = NULL;
remove_button_dir:
remove_proc_entry(ACPI_BUTTON_CLASS, acpi_root_dir);
goto done;
@@ -207,8 +209,10 @@ static int acpi_button_remove_fs(struct acpi_device *device)
acpi_device_dir(device));
remove_proc_entry(acpi_device_bid(device),
acpi_lid_dir);
+ acpi_lid_dir = NULL;
acpi_device_dir(device) = NULL;
remove_proc_entry(ACPI_BUTTON_SUBCLASS_LID, acpi_button_dir);
+ acpi_button_dir = NULL;
remove_proc_entry(ACPI_BUTTON_CLASS, acpi_root_dir);
return 0;
--
2.7.4

View File

@ -0,0 +1,108 @@
From 72023d2508fdd513a92e9dd25d1cc0999a0f32ee Mon Sep 17 00:00:00 2001
From: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Date: Fri, 27 May 2016 17:13:50 +0200
Subject: [PATCH] WIP: i2c-hid enabled S0 S3
---
drivers/hid/i2c-hid/i2c-hid.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c
index b3ec4f2..d0b1355 100644
--- a/drivers/hid/i2c-hid/i2c-hid.c
+++ b/drivers/hid/i2c-hid/i2c-hid.c
@@ -154,6 +154,8 @@ struct i2c_hid {
struct mutex reset_lock;
};
+static int i2c_hid_acpi_power(struct i2c_client *client, int lvl);
+
static int __i2c_hid_command(struct i2c_client *client,
const struct i2c_hid_cmd *command, u8 reportID,
u8 reportType, u8 *args, int args_len,
@@ -245,6 +247,7 @@ static int i2c_hid_get_report(struct i2c_client *client, u8 reportType,
i2c_hid_dbg(ihid, "%s\n", __func__);
+ pm_runtime_get_sync(&client->dev);
if (reportID >= 0x0F) {
args[args_len++] = reportID;
reportID = 0x0F;
@@ -261,6 +264,7 @@ static int i2c_hid_get_report(struct i2c_client *client, u8 reportType,
return ret;
}
+ pm_runtime_put(&client->dev);
return 0;
}
@@ -584,6 +588,9 @@ static int i2c_hid_get_raw_report(struct hid_device *hid,
ret_count = ihid->rawbuf[0] | (ihid->rawbuf[1] << 8);
+ i2c_hid_dbg(ihid, "report (len=%d): %*ph\n",
+ (int)ask_count, (int)ask_count, ihid->rawbuf);
+
if (ret_count <= 2)
return 0;
@@ -789,6 +796,8 @@ static int i2c_hid_power(struct hid_device *hid, int lvl)
pm_runtime_put(&client->dev);
break;
}
+
+ i2c_hid_acpi_power(client, lvl);
return 0;
}
@@ -909,12 +918,38 @@ static const struct acpi_device_id i2c_hid_acpi_match[] = {
{ },
};
MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match);
+
+static int i2c_hid_acpi_power(struct i2c_client *client, int lvl)
+{
+ int error = 0;
+
+ pr_err("%s lvl: %d %s:%d\n", __func__, lvl, __FILE__, __LINE__);
+
+ switch (lvl) {
+ case PM_HINT_FULLON:
+ error = acpi_bus_set_power(ACPI_HANDLE(&client->dev), ACPI_STATE_D0);
+ break;
+ case PM_HINT_NORMAL:
+ error = acpi_bus_set_power(ACPI_HANDLE(&client->dev), ACPI_STATE_D3);
+ break;
+ }
+ if (error)
+ dev_warn(&client->dev, "%s: error changing power state: %d\n", __func__, error);
+
+ return 0;
+}
+
#else
static inline int i2c_hid_acpi_pdata(struct i2c_client *client,
struct i2c_hid_platform_data *pdata)
{
return -ENODEV;
}
+
+static inline int i2c_hid_acpi_power(struct i2c_client *client, int lvl)
+{
+ return 0;
+}
#endif
#ifdef CONFIG_OF
@@ -1004,6 +1039,8 @@ static int i2c_hid_probe(struct i2c_client *client,
ihid->client = client;
+ i2c_hid_acpi_power(client, PM_HINT_FULLON);
+
hidRegister = ihid->pdata.hid_descriptor_address;
ihid->wHIDDescRegister = cpu_to_le16(hidRegister);
--
2.7.4

View File

@ -0,0 +1,373 @@
From 59131c2cc6f95e7517d73b2e8eefa890284ddf08 Mon Sep 17 00:00:00 2001
From: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Date: Fri, 3 Jun 2016 18:41:02 +0200
Subject: [PATCH 2/2] WIP: add custom surface3 platform device for controlling
LID and hotplug
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
drivers/platform/x86/Kconfig | 12 ++
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/surface3-wmi.c | 315 ++++++++++++++++++++++++++++++++++++
3 files changed, 328 insertions(+)
create mode 100644 drivers/platform/x86/surface3-wmi.c
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 3ec0025..376999b 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -363,6 +363,18 @@ config IDEAPAD_LAPTOP
This is a driver for Lenovo IdeaPad netbooks contains drivers for
rfkill switch, hotkey, fan control and backlight control.
+config SURFACE3_WMI
+ tristate "Surface 3 WMI Driver"
+ depends on ACPI_WMI
+ depends on DMI
+ depends on INPUT
+ depends on SPI
+ ---help---
+ Say Y here if you have a Surface 3.
+
+ To compile this driver as a module, choose M here: the module will
+ be called surface3-wmi.
+
config THINKPAD_ACPI
tristate "ThinkPad ACPI Laptop Extras"
depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 9b11b40..7324919 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o
obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o
obj-$(CONFIG_ACPI_WMI) += wmi.o
obj-$(CONFIG_MSI_WMI) += msi-wmi.o
+obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o
# toshiba_acpi must link after wmi to ensure that wmi devices are found
diff --git a/drivers/platform/x86/surface3-wmi.c b/drivers/platform/x86/surface3-wmi.c
new file mode 100644
index 0000000..92f7a38
--- /dev/null
+++ b/drivers/platform/x86/surface3-wmi.c
@@ -0,0 +1,315 @@
+/*
+ * Driver for the LID cover switch of the Surface 3
+ *
+ * Copyright (c) 2016 Red Hat Inc.
+ */
+
+/*
+ * 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; version 2 of the License.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>");
+MODULE_DESCRIPTION("Surface 3 platform driver");
+MODULE_LICENSE("GPL");
+
+#define ACPI_BUTTON_HID_LID "PNP0C0D"
+#define SPI_CTL_OBJ_NAME "SPI"
+#define SPI_TS_OBJ_NAME "NTRG"
+
+#define SURFACE3_LID_GUID "F7CC25EC-D20B-404C-8903-0ED4359C18AE"
+
+MODULE_ALIAS("wmi:" SURFACE3_LID_GUID);
+
+static const struct dmi_system_id surface3_dmi_table[] = {
+#if defined(CONFIG_X86)
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"),
+ },
+ },
+#endif
+ { }
+};
+
+struct surface3_wmi {
+ struct platform_device *spi_master_pdev;
+ struct acpi_device *spi_master_adev;
+ struct acpi_device *touchscreen_adev;
+ struct acpi_device *pnp0c0d_adev;
+ struct acpi_hotplug_context hp;
+ struct input_dev *input;
+};
+
+static struct platform_device *s3_wmi_pdev;
+
+static struct surface3_wmi s3_wmi;
+
+static DEFINE_MUTEX(s3_wmi_lock);
+
+static int s3_wmi_query_block(const char *guid, int instance, int *ret)
+{
+ acpi_status status;
+ union acpi_object *obj;
+
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+
+ mutex_lock(&s3_wmi_lock);
+ status = wmi_query_block(guid, instance, &output);
+
+ obj = output.pointer;
+
+ if (!obj || obj->type != ACPI_TYPE_INTEGER) {
+ if (obj) {
+ pr_err("query block returned object "
+ "type: %d - buffer length:%d\n", obj->type,
+ obj->type == ACPI_TYPE_BUFFER ?
+ obj->buffer.length : 0);
+ }
+ kfree(obj);
+ return -EINVAL;
+ }
+ *ret = obj->integer.value;
+ kfree(obj);
+ mutex_unlock(&s3_wmi_lock);
+ return 0;
+}
+
+static inline int s3_wmi_query_lid(int *ret)
+{
+ return s3_wmi_query_block(SURFACE3_LID_GUID, 0, ret);
+}
+
+static int s3_wmi_send_lid_state(int *return_value)
+{
+ int ret, lid_sw;
+
+ ret = s3_wmi_query_lid(&lid_sw);
+ if (ret)
+ return ret;
+
+ input_report_switch(s3_wmi.input, SW_LID, lid_sw);
+ input_sync(s3_wmi.input);
+
+ if (return_value)
+ *return_value = lid_sw;
+
+ return 0;
+}
+
+static int s3_wmi_hp_notify(struct acpi_device *adev, u32 value)
+{
+ int ret, lid_sw;
+ ret = s3_wmi_send_lid_state(&lid_sw);
+ if (ret)
+ return ret;
+
+ if (lid_sw) {
+ platform_device_unregister(s3_wmi.spi_master_pdev);
+ acpi_bus_trim(s3_wmi.spi_master_adev);
+ } else {
+ acpi_bus_scan(s3_wmi.spi_master_adev->handle);
+ }
+
+ return 0;
+}
+
+static acpi_status s3_wmi_attach_spi_device(acpi_handle handle,
+ u32 level,
+ void *data,
+ void **return_value)
+{
+ struct acpi_device *adev, **ts_adev;
+
+ if (acpi_bus_get_device(handle, &adev))
+ return AE_OK;
+
+ ts_adev = data;
+
+ if (strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME,
+ strlen(SPI_TS_OBJ_NAME)))
+ return AE_OK;
+
+ if (*ts_adev) {
+ pr_err("duplicate entry %s\n", SPI_TS_OBJ_NAME);
+ return AE_OK;
+ }
+
+ *ts_adev = adev;
+
+ return AE_OK;
+}
+
+static int s3_wmi_check_platform_device(struct device *dev, void *data)
+{
+ struct acpi_device *adev, *ts_adev;
+ acpi_handle handle;
+ acpi_status status;
+
+ /* ignore non ACPI devices */
+ handle = ACPI_HANDLE(dev);
+ if (!handle || acpi_bus_get_device(handle, &adev))
+ return 0;
+
+ /* check for LID ACPI switch */
+ if (!strcmp(ACPI_BUTTON_HID_LID, acpi_device_hid(adev))) {
+ s3_wmi.pnp0c0d_adev = adev;
+ return 0;
+ }
+
+ /* ignore non SPI controllers */
+ if (strncmp(acpi_device_bid(adev), SPI_CTL_OBJ_NAME,
+ strlen(SPI_CTL_OBJ_NAME)))
+ return 0;
+
+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1,
+ s3_wmi_attach_spi_device, NULL,
+ &ts_adev, NULL);
+ if (ACPI_FAILURE(status))
+ dev_warn(dev, "failed to enumerate SPI slaves\n");
+
+ if (!ts_adev)
+ return 0;
+
+ s3_wmi.spi_master_pdev = to_platform_device(dev);
+ s3_wmi.spi_master_adev = adev;
+ s3_wmi.touchscreen_adev = ts_adev;
+
+ return 0;
+}
+
+static int s3_wmi_create_and_register_input(struct platform_device *pdev)
+{
+ struct input_dev *input;
+ int error;
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = "Lid Switch";
+ input->phys = "button/input0";
+ input->id.bustype = BUS_HOST;
+ input->id.product = 0x0005;
+
+ input_set_capability(input, EV_SW, SW_LID);
+
+ error = input_register_device(input);
+ if (error)
+ goto out_err;
+
+ s3_wmi.input = input;
+
+ return 0;
+ out_err:
+ input_free_device(s3_wmi.input);
+ return error;
+}
+
+static int __init s3_wmi_probe(struct platform_device *pdev)
+{
+ int error;
+
+ if (!dmi_check_system(surface3_dmi_table))
+ return -ENODEV;
+
+ memset(&s3_wmi, 0, sizeof(s3_wmi));
+
+ bus_for_each_dev(&platform_bus_type, NULL, NULL,
+ s3_wmi_check_platform_device);
+
+ if (!s3_wmi.touchscreen_adev)
+ return -ENODEV;
+
+ acpi_bus_trim(s3_wmi.pnp0c0d_adev);
+
+ error = s3_wmi_create_and_register_input(pdev);
+ if (error)
+ goto restore_acpi_lid;
+
+ acpi_initialize_hp_context(s3_wmi.touchscreen_adev, &s3_wmi.hp,
+ s3_wmi_hp_notify, NULL);
+
+ s3_wmi_send_lid_state(NULL);
+
+ return 0;
+
+ restore_acpi_lid:
+ acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle);
+ return error;
+}
+
+static int s3_wmi_remove(struct platform_device *device)
+{
+ /* remove the hotplug context from the acpi device */
+ s3_wmi.touchscreen_adev->hp = NULL;
+
+ /* reinstall the actual PNPC0C0D LID default handle */
+ acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3_wmi_resume(struct device *dev)
+{
+ s3_wmi_send_lid_state(NULL);
+ return 0;
+}
+#endif
+static SIMPLE_DEV_PM_OPS(s3_wmi_pm, NULL, s3_wmi_resume);
+
+static struct platform_driver s3_wmi_driver = {
+ .driver = {
+ .name = "surface3-wmi",
+ .pm = &s3_wmi_pm,
+ },
+ .remove = s3_wmi_remove,
+};
+
+static int __init s3_wmi_init(void)
+{
+ int error;
+
+ s3_wmi_pdev = platform_device_alloc("surface3-wmi", -1);
+ if (!s3_wmi_pdev)
+ return -ENOMEM;
+
+ error = platform_device_add(s3_wmi_pdev);
+ if (error)
+ goto err_device_put;
+
+ error = platform_driver_probe(&s3_wmi_driver, s3_wmi_probe);
+ if (error)
+ goto err_device_del;
+
+ pr_info("Surface 3 WMI Extras loaded\n");
+ return 0;
+
+ err_device_del:
+ platform_device_del(s3_wmi_pdev);
+ err_device_put:
+ platform_device_put(s3_wmi_pdev);
+ return error;
+}
+
+static void __exit s3_wmi_exit(void)
+{
+ platform_device_unregister(s3_wmi_pdev);
+ platform_driver_unregister(&s3_wmi_driver);
+}
+
+module_init(s3_wmi_init);
+module_exit(s3_wmi_exit);
--
2.7.4

View File

@ -250,6 +250,7 @@ CONFIG_PANASONIC_LAPTOP=m
CONFIG_SAMSUNG_LAPTOP=m
CONFIG_SONY_LAPTOP=m
CONFIG_TOPSTAR_LAPTOP=m
CONFIG_SURFACE3_WMI=m
CONFIG_ACPI_WMI=m
CONFIG_ACER_WMI=m

View File

@ -651,6 +651,10 @@ Patch913: 0002-power-surface3_power-Improve-battery-capacity-report.patch
Patch914: 0001-gpiolib-acpi-make-sure-we-trigger-the-events-at-leas.patch
Patch915: 0001-WIP-acpi-button-remove-pointer-to-old-lid_sysfs-on-u.patch
Patch916: 0001-WIP-i2c-hid-enabled-S0-S3.patch
Patch917: 0002-WIP-add-custom-surface3-platform-device-for-controll.patch
# END OF PATCH DEFINITIONS
%endif