| // SPDX-License-Identifier: GPL-2.0 | |
| // Driver to detect Tablet Mode for ChromeOS convertible. | |
| // | |
| // Copyright (C) 2017 Google, Inc. | |
| // Author: Gwendal Grignou <gwendal@chromium.org> | |
| // | |
| // On Chromebook using ACPI, this device listens for notification | |
| // from GOOG0006 and issue method TBMC to retrieve the status. | |
| // | |
| // GOOG0006 issues the notification when it receives EC_HOST_EVENT_MODE_CHANGE | |
| // from the EC. | |
| // Method TBMC reads EC_ACPI_MEM_DEVICE_ORIENTATION byte from the shared | |
| // memory region. | |
| #include <linux/acpi.h> | |
| #include <linux/input.h> | |
| #include <linux/io.h> | |
| #include <linux/module.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/printk.h> | |
| #define DRV_NAME "chromeos_tbmc" | |
| #define ACPI_DRV_NAME "GOOG0006" | |
| static int chromeos_tbmc_query_switch(struct acpi_device *adev, | |
| struct input_dev *idev) | |
| { | |
| unsigned long long state; | |
| acpi_status status; | |
| status = acpi_evaluate_integer(adev->handle, "TBMC", NULL, &state); | |
| if (ACPI_FAILURE(status)) | |
| return -ENODEV; | |
| /* input layer checks if event is redundant */ | |
| input_report_switch(idev, SW_TABLET_MODE, state); | |
| input_sync(idev); | |
| return 0; | |
| } | |
| static __maybe_unused int chromeos_tbmc_resume(struct device *dev) | |
| { | |
| return chromeos_tbmc_query_switch(ACPI_COMPANION(dev), dev_get_drvdata(dev)); | |
| } | |
| static void chromeos_tbmc_notify(acpi_handle handle, u32 event, void *data) | |
| { | |
| struct device *dev = data; | |
| acpi_pm_wakeup_event(dev); | |
| switch (event) { | |
| case 0x80: | |
| chromeos_tbmc_query_switch(ACPI_COMPANION(dev), dev_get_drvdata(dev)); | |
| break; | |
| default: | |
| dev_err(dev, "Unexpected event: 0x%08X\n", event); | |
| } | |
| } | |
| static int chromeos_tbmc_open(struct input_dev *idev) | |
| { | |
| struct acpi_device *adev = input_get_drvdata(idev); | |
| return chromeos_tbmc_query_switch(adev, idev); | |
| } | |
| static int chromeos_tbmc_probe(struct platform_device *pdev) | |
| { | |
| struct input_dev *idev; | |
| struct device *dev = &pdev->dev; | |
| struct acpi_device *adev; | |
| int ret; | |
| adev = ACPI_COMPANION(dev); | |
| if (!adev) | |
| return -ENODEV; | |
| idev = devm_input_allocate_device(dev); | |
| if (!idev) | |
| return -ENOMEM; | |
| idev->name = "Tablet Mode Switch"; | |
| idev->phys = acpi_device_hid(adev); | |
| idev->id.bustype = BUS_HOST; | |
| idev->id.version = 1; | |
| idev->id.product = 0; | |
| idev->open = chromeos_tbmc_open; | |
| input_set_drvdata(idev, adev); | |
| platform_set_drvdata(pdev, idev); | |
| input_set_capability(idev, EV_SW, SW_TABLET_MODE); | |
| ret = input_register_device(idev); | |
| if (ret) { | |
| dev_err(dev, "cannot register input device\n"); | |
| return ret; | |
| } | |
| device_init_wakeup(dev, true); | |
| ret = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, | |
| chromeos_tbmc_notify, dev); | |
| if (ret) { | |
| dev_err(dev, "cannot install ACPI notify handler\n"); | |
| device_init_wakeup(dev, false); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static void chromeos_tbmc_remove(struct platform_device *pdev) | |
| { | |
| acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), | |
| ACPI_DEVICE_NOTIFY, chromeos_tbmc_notify); | |
| device_init_wakeup(&pdev->dev, false); | |
| } | |
| static const struct acpi_device_id chromeos_tbmc_acpi_device_ids[] = { | |
| { ACPI_DRV_NAME, 0 }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(acpi, chromeos_tbmc_acpi_device_ids); | |
| static SIMPLE_DEV_PM_OPS(chromeos_tbmc_pm_ops, NULL, | |
| chromeos_tbmc_resume); | |
| static struct platform_driver chromeos_tbmc_driver = { | |
| .probe = chromeos_tbmc_probe, | |
| .remove = chromeos_tbmc_remove, | |
| .driver = { | |
| .name = DRV_NAME, | |
| .acpi_match_table = chromeos_tbmc_acpi_device_ids, | |
| .pm = &chromeos_tbmc_pm_ops, | |
| }, | |
| }; | |
| module_platform_driver(chromeos_tbmc_driver); | |
| MODULE_LICENSE("GPL v2"); | |
| MODULE_DESCRIPTION("ChromeOS ACPI tablet switch driver"); |