18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// Driver to detect Tablet Mode for ChromeOS convertible. 38c2ecf20Sopenharmony_ci// 48c2ecf20Sopenharmony_ci// Copyright (C) 2017 Google, Inc. 58c2ecf20Sopenharmony_ci// Author: Gwendal Grignou <gwendal@chromium.org> 68c2ecf20Sopenharmony_ci// 78c2ecf20Sopenharmony_ci// On Chromebook using ACPI, this device listens for notification 88c2ecf20Sopenharmony_ci// from GOOG0006 and issue method TBMC to retrieve the status. 98c2ecf20Sopenharmony_ci// 108c2ecf20Sopenharmony_ci// GOOG0006 issues the notification when it receives EC_HOST_EVENT_MODE_CHANGE 118c2ecf20Sopenharmony_ci// from the EC. 128c2ecf20Sopenharmony_ci// Method TBMC reads EC_ACPI_MEM_DEVICE_ORIENTATION byte from the shared 138c2ecf20Sopenharmony_ci// memory region. 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <linux/acpi.h> 168c2ecf20Sopenharmony_ci#include <linux/input.h> 178c2ecf20Sopenharmony_ci#include <linux/io.h> 188c2ecf20Sopenharmony_ci#include <linux/module.h> 198c2ecf20Sopenharmony_ci#include <linux/printk.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define DRV_NAME "chromeos_tbmc" 228c2ecf20Sopenharmony_ci#define ACPI_DRV_NAME "GOOG0006" 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistatic int chromeos_tbmc_query_switch(struct acpi_device *adev, 258c2ecf20Sopenharmony_ci struct input_dev *idev) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci unsigned long long state; 288c2ecf20Sopenharmony_ci acpi_status status; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(adev->handle, "TBMC", NULL, &state); 318c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 328c2ecf20Sopenharmony_ci return -ENODEV; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci /* input layer checks if event is redundant */ 358c2ecf20Sopenharmony_ci input_report_switch(idev, SW_TABLET_MODE, state); 368c2ecf20Sopenharmony_ci input_sync(idev); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci return 0; 398c2ecf20Sopenharmony_ci} 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic __maybe_unused int chromeos_tbmc_resume(struct device *dev) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci struct acpi_device *adev = to_acpi_device(dev); 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci return chromeos_tbmc_query_switch(adev, adev->driver_data); 468c2ecf20Sopenharmony_ci} 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic void chromeos_tbmc_notify(struct acpi_device *adev, u32 event) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci acpi_pm_wakeup_event(&adev->dev); 518c2ecf20Sopenharmony_ci switch (event) { 528c2ecf20Sopenharmony_ci case 0x80: 538c2ecf20Sopenharmony_ci chromeos_tbmc_query_switch(adev, adev->driver_data); 548c2ecf20Sopenharmony_ci break; 558c2ecf20Sopenharmony_ci default: 568c2ecf20Sopenharmony_ci dev_err(&adev->dev, "Unexpected event: 0x%08X\n", event); 578c2ecf20Sopenharmony_ci } 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic int chromeos_tbmc_open(struct input_dev *idev) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci struct acpi_device *adev = input_get_drvdata(idev); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci return chromeos_tbmc_query_switch(adev, idev); 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic int chromeos_tbmc_add(struct acpi_device *adev) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci struct input_dev *idev; 708c2ecf20Sopenharmony_ci struct device *dev = &adev->dev; 718c2ecf20Sopenharmony_ci int ret; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci idev = devm_input_allocate_device(dev); 748c2ecf20Sopenharmony_ci if (!idev) 758c2ecf20Sopenharmony_ci return -ENOMEM; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci idev->name = "Tablet Mode Switch"; 788c2ecf20Sopenharmony_ci idev->phys = acpi_device_hid(adev); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci idev->id.bustype = BUS_HOST; 818c2ecf20Sopenharmony_ci idev->id.version = 1; 828c2ecf20Sopenharmony_ci idev->id.product = 0; 838c2ecf20Sopenharmony_ci idev->open = chromeos_tbmc_open; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci input_set_drvdata(idev, adev); 868c2ecf20Sopenharmony_ci adev->driver_data = idev; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci input_set_capability(idev, EV_SW, SW_TABLET_MODE); 898c2ecf20Sopenharmony_ci ret = input_register_device(idev); 908c2ecf20Sopenharmony_ci if (ret) { 918c2ecf20Sopenharmony_ci dev_err(dev, "cannot register input device\n"); 928c2ecf20Sopenharmony_ci return ret; 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci device_init_wakeup(dev, true); 958c2ecf20Sopenharmony_ci return 0; 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic const struct acpi_device_id chromeos_tbmc_acpi_device_ids[] = { 998c2ecf20Sopenharmony_ci { ACPI_DRV_NAME, 0 }, 1008c2ecf20Sopenharmony_ci { } 1018c2ecf20Sopenharmony_ci}; 1028c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, chromeos_tbmc_acpi_device_ids); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(chromeos_tbmc_pm_ops, NULL, 1058c2ecf20Sopenharmony_ci chromeos_tbmc_resume); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_cistatic struct acpi_driver chromeos_tbmc_driver = { 1088c2ecf20Sopenharmony_ci .name = DRV_NAME, 1098c2ecf20Sopenharmony_ci .class = DRV_NAME, 1108c2ecf20Sopenharmony_ci .ids = chromeos_tbmc_acpi_device_ids, 1118c2ecf20Sopenharmony_ci .ops = { 1128c2ecf20Sopenharmony_ci .add = chromeos_tbmc_add, 1138c2ecf20Sopenharmony_ci .notify = chromeos_tbmc_notify, 1148c2ecf20Sopenharmony_ci }, 1158c2ecf20Sopenharmony_ci .drv.pm = &chromeos_tbmc_pm_ops, 1168c2ecf20Sopenharmony_ci}; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cimodule_acpi_driver(chromeos_tbmc_driver); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 1218c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ChromeOS ACPI tablet switch driver"); 122