162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for the LID cover switch of the Surface 3 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2016 Red Hat Inc. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/acpi.h> 1462306a36Sopenharmony_ci#include <linux/dmi.h> 1562306a36Sopenharmony_ci#include <linux/input.h> 1662306a36Sopenharmony_ci#include <linux/mutex.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci#include <linux/spi/spi.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ciMODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>"); 2162306a36Sopenharmony_ciMODULE_DESCRIPTION("Surface 3 platform driver"); 2262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define ACPI_BUTTON_HID_LID "PNP0C0D" 2562306a36Sopenharmony_ci#define SPI_CTL_OBJ_NAME "SPI" 2662306a36Sopenharmony_ci#define SPI_TS_OBJ_NAME "NTRG" 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define SURFACE3_LID_GUID "F7CC25EC-D20B-404C-8903-0ED4359C18AE" 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ciMODULE_ALIAS("wmi:" SURFACE3_LID_GUID); 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic const struct dmi_system_id surface3_dmi_table[] = { 3362306a36Sopenharmony_ci#if defined(CONFIG_X86) 3462306a36Sopenharmony_ci { 3562306a36Sopenharmony_ci .matches = { 3662306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), 3762306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), 3862306a36Sopenharmony_ci }, 3962306a36Sopenharmony_ci }, 4062306a36Sopenharmony_ci#endif 4162306a36Sopenharmony_ci { } 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistruct surface3_wmi { 4562306a36Sopenharmony_ci struct acpi_device *touchscreen_adev; 4662306a36Sopenharmony_ci struct acpi_device *pnp0c0d_adev; 4762306a36Sopenharmony_ci struct acpi_hotplug_context hp; 4862306a36Sopenharmony_ci struct input_dev *input; 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic struct platform_device *s3_wmi_pdev; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic struct surface3_wmi s3_wmi; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic DEFINE_MUTEX(s3_wmi_lock); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic int s3_wmi_query_block(const char *guid, int instance, int *ret) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 6062306a36Sopenharmony_ci union acpi_object *obj = NULL; 6162306a36Sopenharmony_ci acpi_status status; 6262306a36Sopenharmony_ci int error = 0; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci mutex_lock(&s3_wmi_lock); 6562306a36Sopenharmony_ci status = wmi_query_block(guid, instance, &output); 6662306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 6762306a36Sopenharmony_ci error = -EIO; 6862306a36Sopenharmony_ci goto out_free_unlock; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci obj = output.pointer; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci if (!obj || obj->type != ACPI_TYPE_INTEGER) { 7462306a36Sopenharmony_ci if (obj) { 7562306a36Sopenharmony_ci pr_err("query block returned object type: %d - buffer length:%d\n", 7662306a36Sopenharmony_ci obj->type, 7762306a36Sopenharmony_ci obj->type == ACPI_TYPE_BUFFER ? 7862306a36Sopenharmony_ci obj->buffer.length : 0); 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci error = -EINVAL; 8162306a36Sopenharmony_ci goto out_free_unlock; 8262306a36Sopenharmony_ci } 8362306a36Sopenharmony_ci *ret = obj->integer.value; 8462306a36Sopenharmony_ci out_free_unlock: 8562306a36Sopenharmony_ci kfree(obj); 8662306a36Sopenharmony_ci mutex_unlock(&s3_wmi_lock); 8762306a36Sopenharmony_ci return error; 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic inline int s3_wmi_query_lid(int *ret) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci return s3_wmi_query_block(SURFACE3_LID_GUID, 0, ret); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic int s3_wmi_send_lid_state(void) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci int ret, lid_sw; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci ret = s3_wmi_query_lid(&lid_sw); 10062306a36Sopenharmony_ci if (ret) 10162306a36Sopenharmony_ci return ret; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci input_report_switch(s3_wmi.input, SW_LID, lid_sw); 10462306a36Sopenharmony_ci input_sync(s3_wmi.input); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci return 0; 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic int s3_wmi_hp_notify(struct acpi_device *adev, u32 value) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci return s3_wmi_send_lid_state(); 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic acpi_status s3_wmi_attach_spi_device(acpi_handle handle, 11562306a36Sopenharmony_ci u32 level, 11662306a36Sopenharmony_ci void *data, 11762306a36Sopenharmony_ci void **return_value) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci struct acpi_device *adev = acpi_fetch_acpi_dev(handle); 12062306a36Sopenharmony_ci struct acpi_device **ts_adev = data; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci if (!adev || strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, 12362306a36Sopenharmony_ci strlen(SPI_TS_OBJ_NAME))) 12462306a36Sopenharmony_ci return AE_OK; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci if (*ts_adev) { 12762306a36Sopenharmony_ci pr_err("duplicate entry %s\n", SPI_TS_OBJ_NAME); 12862306a36Sopenharmony_ci return AE_OK; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci *ts_adev = adev; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci return AE_OK; 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic int s3_wmi_check_platform_device(struct device *dev, void *data) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(dev); 13962306a36Sopenharmony_ci struct acpi_device *ts_adev = NULL; 14062306a36Sopenharmony_ci acpi_status status; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci /* ignore non ACPI devices */ 14362306a36Sopenharmony_ci if (!adev) 14462306a36Sopenharmony_ci return 0; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci /* check for LID ACPI switch */ 14762306a36Sopenharmony_ci if (!strcmp(ACPI_BUTTON_HID_LID, acpi_device_hid(adev))) { 14862306a36Sopenharmony_ci s3_wmi.pnp0c0d_adev = adev; 14962306a36Sopenharmony_ci return 0; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci /* ignore non SPI controllers */ 15362306a36Sopenharmony_ci if (strncmp(acpi_device_bid(adev), SPI_CTL_OBJ_NAME, 15462306a36Sopenharmony_ci strlen(SPI_CTL_OBJ_NAME))) 15562306a36Sopenharmony_ci return 0; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci status = acpi_walk_namespace(ACPI_TYPE_DEVICE, adev->handle, 1, 15862306a36Sopenharmony_ci s3_wmi_attach_spi_device, NULL, 15962306a36Sopenharmony_ci &ts_adev, NULL); 16062306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 16162306a36Sopenharmony_ci dev_warn(dev, "failed to enumerate SPI slaves\n"); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci if (!ts_adev) 16462306a36Sopenharmony_ci return 0; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci s3_wmi.touchscreen_adev = ts_adev; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci return 0; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic int s3_wmi_create_and_register_input(struct platform_device *pdev) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci struct input_dev *input; 17462306a36Sopenharmony_ci int error; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci input = devm_input_allocate_device(&pdev->dev); 17762306a36Sopenharmony_ci if (!input) 17862306a36Sopenharmony_ci return -ENOMEM; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci input->name = "Lid Switch"; 18162306a36Sopenharmony_ci input->phys = "button/input0"; 18262306a36Sopenharmony_ci input->id.bustype = BUS_HOST; 18362306a36Sopenharmony_ci input->id.product = 0x0005; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci input_set_capability(input, EV_SW, SW_LID); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci error = input_register_device(input); 18862306a36Sopenharmony_ci if (error) 18962306a36Sopenharmony_ci return error; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci s3_wmi.input = input; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci return 0; 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic int __init s3_wmi_probe(struct platform_device *pdev) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci int error; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (!dmi_check_system(surface3_dmi_table)) 20162306a36Sopenharmony_ci return -ENODEV; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci memset(&s3_wmi, 0, sizeof(s3_wmi)); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci bus_for_each_dev(&platform_bus_type, NULL, NULL, 20662306a36Sopenharmony_ci s3_wmi_check_platform_device); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci if (!s3_wmi.touchscreen_adev) 20962306a36Sopenharmony_ci return -ENODEV; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci acpi_bus_trim(s3_wmi.pnp0c0d_adev); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci error = s3_wmi_create_and_register_input(pdev); 21462306a36Sopenharmony_ci if (error) 21562306a36Sopenharmony_ci goto restore_acpi_lid; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci acpi_initialize_hp_context(s3_wmi.touchscreen_adev, &s3_wmi.hp, 21862306a36Sopenharmony_ci s3_wmi_hp_notify, NULL); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci s3_wmi_send_lid_state(); 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci return 0; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci restore_acpi_lid: 22562306a36Sopenharmony_ci acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); 22662306a36Sopenharmony_ci return error; 22762306a36Sopenharmony_ci} 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_cistatic int s3_wmi_remove(struct platform_device *device) 23062306a36Sopenharmony_ci{ 23162306a36Sopenharmony_ci /* remove the hotplug context from the acpi device */ 23262306a36Sopenharmony_ci s3_wmi.touchscreen_adev->hp = NULL; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci /* reinstall the actual PNPC0C0D LID default handle */ 23562306a36Sopenharmony_ci acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); 23662306a36Sopenharmony_ci return 0; 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic int __maybe_unused s3_wmi_resume(struct device *dev) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci s3_wmi_send_lid_state(); 24262306a36Sopenharmony_ci return 0; 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(s3_wmi_pm, NULL, s3_wmi_resume); 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_cistatic struct platform_driver s3_wmi_driver = { 24762306a36Sopenharmony_ci .driver = { 24862306a36Sopenharmony_ci .name = "surface3-wmi", 24962306a36Sopenharmony_ci .pm = &s3_wmi_pm, 25062306a36Sopenharmony_ci }, 25162306a36Sopenharmony_ci .remove = s3_wmi_remove, 25262306a36Sopenharmony_ci}; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cistatic int __init s3_wmi_init(void) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci int error; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci s3_wmi_pdev = platform_device_alloc("surface3-wmi", -1); 25962306a36Sopenharmony_ci if (!s3_wmi_pdev) 26062306a36Sopenharmony_ci return -ENOMEM; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci error = platform_device_add(s3_wmi_pdev); 26362306a36Sopenharmony_ci if (error) 26462306a36Sopenharmony_ci goto err_device_put; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci error = platform_driver_probe(&s3_wmi_driver, s3_wmi_probe); 26762306a36Sopenharmony_ci if (error) 26862306a36Sopenharmony_ci goto err_device_del; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci pr_info("Surface 3 WMI Extras loaded\n"); 27162306a36Sopenharmony_ci return 0; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci err_device_del: 27462306a36Sopenharmony_ci platform_device_del(s3_wmi_pdev); 27562306a36Sopenharmony_ci err_device_put: 27662306a36Sopenharmony_ci platform_device_put(s3_wmi_pdev); 27762306a36Sopenharmony_ci return error; 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cistatic void __exit s3_wmi_exit(void) 28162306a36Sopenharmony_ci{ 28262306a36Sopenharmony_ci platform_device_unregister(s3_wmi_pdev); 28362306a36Sopenharmony_ci platform_driver_unregister(&s3_wmi_driver); 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_cimodule_init(s3_wmi_init); 28762306a36Sopenharmony_cimodule_exit(s3_wmi_exit); 288