1*c474e9f2SBenson Leung // SPDX-License-Identifier: GPL-2.0 24574d1d6SBenson Leung // Driver to detect Tablet Mode for ChromeOS convertible. 34574d1d6SBenson Leung // 44574d1d6SBenson Leung // Copyright (C) 2017 Google, Inc. 54574d1d6SBenson Leung // Author: Gwendal Grignou <gwendal@chromium.org> 6*c474e9f2SBenson Leung // 7*c474e9f2SBenson Leung // On Chromebook using ACPI, this device listens for notification 8*c474e9f2SBenson Leung // from GOOG0006 and issue method TBMC to retrieve the status. 9*c474e9f2SBenson Leung // 10*c474e9f2SBenson Leung // GOOG0006 issues the notification when it receives EC_HOST_EVENT_MODE_CHANGE 11*c474e9f2SBenson Leung // from the EC. 12*c474e9f2SBenson Leung // Method TBMC reads EC_ACPI_MEM_DEVICE_ORIENTATION byte from the shared 13*c474e9f2SBenson Leung // memory region. 14b418f741SGwendal Grignou 15b418f741SGwendal Grignou #include <linux/acpi.h> 16b418f741SGwendal Grignou #include <linux/input.h> 17b418f741SGwendal Grignou #include <linux/io.h> 18b418f741SGwendal Grignou #include <linux/module.h> 19b418f741SGwendal Grignou #include <linux/printk.h> 20b418f741SGwendal Grignou 21b418f741SGwendal Grignou #define DRV_NAME "chromeos_tbmc" 22b418f741SGwendal Grignou #define ACPI_DRV_NAME "GOOG0006" 23b418f741SGwendal Grignou 24b418f741SGwendal Grignou static int chromeos_tbmc_query_switch(struct acpi_device *adev, 25b418f741SGwendal Grignou struct input_dev *idev) 26b418f741SGwendal Grignou { 27b418f741SGwendal Grignou unsigned long long state; 28b418f741SGwendal Grignou acpi_status status; 29b418f741SGwendal Grignou 30b418f741SGwendal Grignou status = acpi_evaluate_integer(adev->handle, "TBMC", NULL, &state); 31b418f741SGwendal Grignou if (ACPI_FAILURE(status)) 32b418f741SGwendal Grignou return -ENODEV; 33b418f741SGwendal Grignou 34b418f741SGwendal Grignou /* input layer checks if event is redundant */ 35b418f741SGwendal Grignou input_report_switch(idev, SW_TABLET_MODE, state); 36b418f741SGwendal Grignou input_sync(idev); 37b418f741SGwendal Grignou 38b418f741SGwendal Grignou return 0; 39b418f741SGwendal Grignou } 40b418f741SGwendal Grignou 41b418f741SGwendal Grignou static __maybe_unused int chromeos_tbmc_resume(struct device *dev) 42b418f741SGwendal Grignou { 43b418f741SGwendal Grignou struct acpi_device *adev = to_acpi_device(dev); 44b418f741SGwendal Grignou 45b418f741SGwendal Grignou return chromeos_tbmc_query_switch(adev, adev->driver_data); 46b418f741SGwendal Grignou } 47b418f741SGwendal Grignou 48b418f741SGwendal Grignou static void chromeos_tbmc_notify(struct acpi_device *adev, u32 event) 49b418f741SGwendal Grignou { 50b418f741SGwendal Grignou switch (event) { 51b418f741SGwendal Grignou case 0x80: 52b418f741SGwendal Grignou chromeos_tbmc_query_switch(adev, adev->driver_data); 53b418f741SGwendal Grignou break; 54b418f741SGwendal Grignou default: 55b418f741SGwendal Grignou dev_err(&adev->dev, "Unexpected event: 0x%08X\n", event); 56b418f741SGwendal Grignou } 57b418f741SGwendal Grignou } 58b418f741SGwendal Grignou 59b418f741SGwendal Grignou static int chromeos_tbmc_open(struct input_dev *idev) 60b418f741SGwendal Grignou { 61b418f741SGwendal Grignou struct acpi_device *adev = input_get_drvdata(idev); 62b418f741SGwendal Grignou 63b418f741SGwendal Grignou return chromeos_tbmc_query_switch(adev, idev); 64b418f741SGwendal Grignou } 65b418f741SGwendal Grignou 66b418f741SGwendal Grignou static int chromeos_tbmc_add(struct acpi_device *adev) 67b418f741SGwendal Grignou { 68b418f741SGwendal Grignou struct input_dev *idev; 69b418f741SGwendal Grignou struct device *dev = &adev->dev; 70b418f741SGwendal Grignou int ret; 71b418f741SGwendal Grignou 72b418f741SGwendal Grignou idev = devm_input_allocate_device(dev); 73b418f741SGwendal Grignou if (!idev) 74b418f741SGwendal Grignou return -ENOMEM; 75b418f741SGwendal Grignou 76b418f741SGwendal Grignou idev->name = "Tablet Mode Switch"; 77b418f741SGwendal Grignou idev->phys = acpi_device_hid(adev); 78b418f741SGwendal Grignou 79b418f741SGwendal Grignou idev->id.bustype = BUS_HOST; 80b418f741SGwendal Grignou idev->id.version = 1; 81b418f741SGwendal Grignou idev->id.product = 0; 82b418f741SGwendal Grignou idev->open = chromeos_tbmc_open; 83b418f741SGwendal Grignou 84b418f741SGwendal Grignou input_set_drvdata(idev, adev); 85b418f741SGwendal Grignou adev->driver_data = idev; 86b418f741SGwendal Grignou 87b418f741SGwendal Grignou input_set_capability(idev, EV_SW, SW_TABLET_MODE); 88b418f741SGwendal Grignou ret = input_register_device(idev); 89b418f741SGwendal Grignou if (ret) { 90b418f741SGwendal Grignou dev_err(dev, "cannot register input device\n"); 91b418f741SGwendal Grignou return ret; 92b418f741SGwendal Grignou } 93b418f741SGwendal Grignou return 0; 94b418f741SGwendal Grignou } 95b418f741SGwendal Grignou 96b418f741SGwendal Grignou static const struct acpi_device_id chromeos_tbmc_acpi_device_ids[] = { 97b418f741SGwendal Grignou { ACPI_DRV_NAME, 0 }, 98b418f741SGwendal Grignou { } 99b418f741SGwendal Grignou }; 100b418f741SGwendal Grignou MODULE_DEVICE_TABLE(acpi, chromeos_tbmc_acpi_device_ids); 101b418f741SGwendal Grignou 102b418f741SGwendal Grignou static const SIMPLE_DEV_PM_OPS(chromeos_tbmc_pm_ops, NULL, 103b418f741SGwendal Grignou chromeos_tbmc_resume); 104b418f741SGwendal Grignou 105b418f741SGwendal Grignou static struct acpi_driver chromeos_tbmc_driver = { 106b418f741SGwendal Grignou .name = DRV_NAME, 107b418f741SGwendal Grignou .class = DRV_NAME, 108b418f741SGwendal Grignou .ids = chromeos_tbmc_acpi_device_ids, 109b418f741SGwendal Grignou .ops = { 110b418f741SGwendal Grignou .add = chromeos_tbmc_add, 111b418f741SGwendal Grignou .notify = chromeos_tbmc_notify, 112b418f741SGwendal Grignou }, 113b418f741SGwendal Grignou .drv.pm = &chromeos_tbmc_pm_ops, 114b418f741SGwendal Grignou }; 115b418f741SGwendal Grignou 116b418f741SGwendal Grignou module_acpi_driver(chromeos_tbmc_driver); 117b418f741SGwendal Grignou 118b418f741SGwendal Grignou MODULE_LICENSE("GPL v2"); 119b418f741SGwendal Grignou MODULE_DESCRIPTION("ChromeOS ACPI tablet switch driver"); 120