18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Driver for the LID cover switch of the Surface 3 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2016 Red Hat Inc. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/acpi.h> 148c2ecf20Sopenharmony_ci#include <linux/dmi.h> 158c2ecf20Sopenharmony_ci#include <linux/input.h> 168c2ecf20Sopenharmony_ci#include <linux/mutex.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci#include <linux/spi/spi.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ciMODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>"); 218c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Surface 3 platform driver"); 228c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define ACPI_BUTTON_HID_LID "PNP0C0D" 258c2ecf20Sopenharmony_ci#define SPI_CTL_OBJ_NAME "SPI" 268c2ecf20Sopenharmony_ci#define SPI_TS_OBJ_NAME "NTRG" 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define SURFACE3_LID_GUID "F7CC25EC-D20B-404C-8903-0ED4359C18AE" 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ciMODULE_ALIAS("wmi:" SURFACE3_LID_GUID); 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic const struct dmi_system_id surface3_dmi_table[] = { 338c2ecf20Sopenharmony_ci#if defined(CONFIG_X86) 348c2ecf20Sopenharmony_ci { 358c2ecf20Sopenharmony_ci .matches = { 368c2ecf20Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), 378c2ecf20Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), 388c2ecf20Sopenharmony_ci }, 398c2ecf20Sopenharmony_ci }, 408c2ecf20Sopenharmony_ci#endif 418c2ecf20Sopenharmony_ci { } 428c2ecf20Sopenharmony_ci}; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistruct surface3_wmi { 458c2ecf20Sopenharmony_ci struct acpi_device *touchscreen_adev; 468c2ecf20Sopenharmony_ci struct acpi_device *pnp0c0d_adev; 478c2ecf20Sopenharmony_ci struct acpi_hotplug_context hp; 488c2ecf20Sopenharmony_ci struct input_dev *input; 498c2ecf20Sopenharmony_ci}; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic struct platform_device *s3_wmi_pdev; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic struct surface3_wmi s3_wmi; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(s3_wmi_lock); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic int s3_wmi_query_block(const char *guid, int instance, int *ret) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 608c2ecf20Sopenharmony_ci acpi_status status; 618c2ecf20Sopenharmony_ci union acpi_object *obj; 628c2ecf20Sopenharmony_ci int error = 0; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci mutex_lock(&s3_wmi_lock); 658c2ecf20Sopenharmony_ci status = wmi_query_block(guid, instance, &output); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci obj = output.pointer; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci if (!obj || obj->type != ACPI_TYPE_INTEGER) { 708c2ecf20Sopenharmony_ci if (obj) { 718c2ecf20Sopenharmony_ci pr_err("query block returned object type: %d - buffer length:%d\n", 728c2ecf20Sopenharmony_ci obj->type, 738c2ecf20Sopenharmony_ci obj->type == ACPI_TYPE_BUFFER ? 748c2ecf20Sopenharmony_ci obj->buffer.length : 0); 758c2ecf20Sopenharmony_ci } 768c2ecf20Sopenharmony_ci error = -EINVAL; 778c2ecf20Sopenharmony_ci goto out_free_unlock; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci *ret = obj->integer.value; 808c2ecf20Sopenharmony_ci out_free_unlock: 818c2ecf20Sopenharmony_ci kfree(obj); 828c2ecf20Sopenharmony_ci mutex_unlock(&s3_wmi_lock); 838c2ecf20Sopenharmony_ci return error; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic inline int s3_wmi_query_lid(int *ret) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci return s3_wmi_query_block(SURFACE3_LID_GUID, 0, ret); 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic int s3_wmi_send_lid_state(void) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci int ret, lid_sw; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci ret = s3_wmi_query_lid(&lid_sw); 968c2ecf20Sopenharmony_ci if (ret) 978c2ecf20Sopenharmony_ci return ret; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci input_report_switch(s3_wmi.input, SW_LID, lid_sw); 1008c2ecf20Sopenharmony_ci input_sync(s3_wmi.input); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci return 0; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_cistatic int s3_wmi_hp_notify(struct acpi_device *adev, u32 value) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci return s3_wmi_send_lid_state(); 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic acpi_status s3_wmi_attach_spi_device(acpi_handle handle, 1118c2ecf20Sopenharmony_ci u32 level, 1128c2ecf20Sopenharmony_ci void *data, 1138c2ecf20Sopenharmony_ci void **return_value) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct acpi_device *adev, **ts_adev; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (acpi_bus_get_device(handle, &adev)) 1188c2ecf20Sopenharmony_ci return AE_OK; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci ts_adev = data; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci if (strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, 1238c2ecf20Sopenharmony_ci strlen(SPI_TS_OBJ_NAME))) 1248c2ecf20Sopenharmony_ci return AE_OK; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci if (*ts_adev) { 1278c2ecf20Sopenharmony_ci pr_err("duplicate entry %s\n", SPI_TS_OBJ_NAME); 1288c2ecf20Sopenharmony_ci return AE_OK; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci *ts_adev = adev; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci return AE_OK; 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_cistatic int s3_wmi_check_platform_device(struct device *dev, void *data) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci struct acpi_device *adev, *ts_adev = NULL; 1398c2ecf20Sopenharmony_ci acpi_handle handle; 1408c2ecf20Sopenharmony_ci acpi_status status; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* ignore non ACPI devices */ 1438c2ecf20Sopenharmony_ci handle = ACPI_HANDLE(dev); 1448c2ecf20Sopenharmony_ci if (!handle || acpi_bus_get_device(handle, &adev)) 1458c2ecf20Sopenharmony_ci return 0; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci /* check for LID ACPI switch */ 1488c2ecf20Sopenharmony_ci if (!strcmp(ACPI_BUTTON_HID_LID, acpi_device_hid(adev))) { 1498c2ecf20Sopenharmony_ci s3_wmi.pnp0c0d_adev = adev; 1508c2ecf20Sopenharmony_ci return 0; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci /* ignore non SPI controllers */ 1548c2ecf20Sopenharmony_ci if (strncmp(acpi_device_bid(adev), SPI_CTL_OBJ_NAME, 1558c2ecf20Sopenharmony_ci strlen(SPI_CTL_OBJ_NAME))) 1568c2ecf20Sopenharmony_ci return 0; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, 1598c2ecf20Sopenharmony_ci s3_wmi_attach_spi_device, NULL, 1608c2ecf20Sopenharmony_ci &ts_adev, NULL); 1618c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 1628c2ecf20Sopenharmony_ci dev_warn(dev, "failed to enumerate SPI slaves\n"); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci if (!ts_adev) 1658c2ecf20Sopenharmony_ci return 0; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci s3_wmi.touchscreen_adev = ts_adev; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci return 0; 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic int s3_wmi_create_and_register_input(struct platform_device *pdev) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci struct input_dev *input; 1758c2ecf20Sopenharmony_ci int error; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci input = devm_input_allocate_device(&pdev->dev); 1788c2ecf20Sopenharmony_ci if (!input) 1798c2ecf20Sopenharmony_ci return -ENOMEM; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci input->name = "Lid Switch"; 1828c2ecf20Sopenharmony_ci input->phys = "button/input0"; 1838c2ecf20Sopenharmony_ci input->id.bustype = BUS_HOST; 1848c2ecf20Sopenharmony_ci input->id.product = 0x0005; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci input_set_capability(input, EV_SW, SW_LID); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci error = input_register_device(input); 1898c2ecf20Sopenharmony_ci if (error) 1908c2ecf20Sopenharmony_ci goto out_err; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci s3_wmi.input = input; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci return 0; 1958c2ecf20Sopenharmony_ci out_err: 1968c2ecf20Sopenharmony_ci input_free_device(s3_wmi.input); 1978c2ecf20Sopenharmony_ci return error; 1988c2ecf20Sopenharmony_ci} 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_cistatic int __init s3_wmi_probe(struct platform_device *pdev) 2018c2ecf20Sopenharmony_ci{ 2028c2ecf20Sopenharmony_ci int error; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci if (!dmi_check_system(surface3_dmi_table)) 2058c2ecf20Sopenharmony_ci return -ENODEV; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci memset(&s3_wmi, 0, sizeof(s3_wmi)); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci bus_for_each_dev(&platform_bus_type, NULL, NULL, 2108c2ecf20Sopenharmony_ci s3_wmi_check_platform_device); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci if (!s3_wmi.touchscreen_adev) 2138c2ecf20Sopenharmony_ci return -ENODEV; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci acpi_bus_trim(s3_wmi.pnp0c0d_adev); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci error = s3_wmi_create_and_register_input(pdev); 2188c2ecf20Sopenharmony_ci if (error) 2198c2ecf20Sopenharmony_ci goto restore_acpi_lid; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci acpi_initialize_hp_context(s3_wmi.touchscreen_adev, &s3_wmi.hp, 2228c2ecf20Sopenharmony_ci s3_wmi_hp_notify, NULL); 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci s3_wmi_send_lid_state(); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci return 0; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci restore_acpi_lid: 2298c2ecf20Sopenharmony_ci acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); 2308c2ecf20Sopenharmony_ci return error; 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_cistatic int s3_wmi_remove(struct platform_device *device) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci /* remove the hotplug context from the acpi device */ 2368c2ecf20Sopenharmony_ci s3_wmi.touchscreen_adev->hp = NULL; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci /* reinstall the actual PNPC0C0D LID default handle */ 2398c2ecf20Sopenharmony_ci acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); 2408c2ecf20Sopenharmony_ci return 0; 2418c2ecf20Sopenharmony_ci} 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_cistatic int __maybe_unused s3_wmi_resume(struct device *dev) 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci s3_wmi_send_lid_state(); 2468c2ecf20Sopenharmony_ci return 0; 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(s3_wmi_pm, NULL, s3_wmi_resume); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_cistatic struct platform_driver s3_wmi_driver = { 2518c2ecf20Sopenharmony_ci .driver = { 2528c2ecf20Sopenharmony_ci .name = "surface3-wmi", 2538c2ecf20Sopenharmony_ci .pm = &s3_wmi_pm, 2548c2ecf20Sopenharmony_ci }, 2558c2ecf20Sopenharmony_ci .remove = s3_wmi_remove, 2568c2ecf20Sopenharmony_ci}; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_cistatic int __init s3_wmi_init(void) 2598c2ecf20Sopenharmony_ci{ 2608c2ecf20Sopenharmony_ci int error; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci s3_wmi_pdev = platform_device_alloc("surface3-wmi", -1); 2638c2ecf20Sopenharmony_ci if (!s3_wmi_pdev) 2648c2ecf20Sopenharmony_ci return -ENOMEM; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci error = platform_device_add(s3_wmi_pdev); 2678c2ecf20Sopenharmony_ci if (error) 2688c2ecf20Sopenharmony_ci goto err_device_put; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci error = platform_driver_probe(&s3_wmi_driver, s3_wmi_probe); 2718c2ecf20Sopenharmony_ci if (error) 2728c2ecf20Sopenharmony_ci goto err_device_del; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci pr_info("Surface 3 WMI Extras loaded\n"); 2758c2ecf20Sopenharmony_ci return 0; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci err_device_del: 2788c2ecf20Sopenharmony_ci platform_device_del(s3_wmi_pdev); 2798c2ecf20Sopenharmony_ci err_device_put: 2808c2ecf20Sopenharmony_ci platform_device_put(s3_wmi_pdev); 2818c2ecf20Sopenharmony_ci return error; 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic void __exit s3_wmi_exit(void) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci platform_device_unregister(s3_wmi_pdev); 2878c2ecf20Sopenharmony_ci platform_driver_unregister(&s3_wmi_driver); 2888c2ecf20Sopenharmony_ci} 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_cimodule_init(s3_wmi_init); 2918c2ecf20Sopenharmony_cimodule_exit(s3_wmi_exit); 292