xref: /linux/drivers/platform/chrome/chromeos_tbmc.c (revision 976e3645923bdd2fe7893aae33fd7a21098bfb28)
1c474e9f2SBenson 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>
6c474e9f2SBenson Leung //
7c474e9f2SBenson Leung // On Chromebook using ACPI, this device listens for notification
8c474e9f2SBenson Leung // from GOOG0006 and issue method TBMC to retrieve the status.
9c474e9f2SBenson Leung //
10c474e9f2SBenson Leung // GOOG0006 issues the notification when it receives EC_HOST_EVENT_MODE_CHANGE
11c474e9f2SBenson Leung // from the EC.
12c474e9f2SBenson Leung // Method TBMC reads EC_ACPI_MEM_DEVICE_ORIENTATION byte from the shared
13c474e9f2SBenson 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 
chromeos_tbmc_query_switch(struct acpi_device * adev,struct input_dev * idev)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 
chromeos_tbmc_resume(struct device * dev)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 
chromeos_tbmc_notify(struct acpi_device * adev,u32 event)48b418f741SGwendal Grignou static void chromeos_tbmc_notify(struct acpi_device *adev, u32 event)
49b418f741SGwendal Grignou {
50*0144c00eSRavi Chandra Sadineni 	acpi_pm_wakeup_event(&adev->dev);
51b418f741SGwendal Grignou 	switch (event) {
52b418f741SGwendal Grignou 	case 0x80:
53b418f741SGwendal Grignou 		chromeos_tbmc_query_switch(adev, adev->driver_data);
54b418f741SGwendal Grignou 		break;
55b418f741SGwendal Grignou 	default:
56b418f741SGwendal Grignou 		dev_err(&adev->dev, "Unexpected event: 0x%08X\n", event);
57b418f741SGwendal Grignou 	}
58b418f741SGwendal Grignou }
59b418f741SGwendal Grignou 
chromeos_tbmc_open(struct input_dev * idev)60b418f741SGwendal Grignou static int chromeos_tbmc_open(struct input_dev *idev)
61b418f741SGwendal Grignou {
62b418f741SGwendal Grignou 	struct acpi_device *adev = input_get_drvdata(idev);
63b418f741SGwendal Grignou 
64b418f741SGwendal Grignou 	return chromeos_tbmc_query_switch(adev, idev);
65b418f741SGwendal Grignou }
66b418f741SGwendal Grignou 
chromeos_tbmc_add(struct acpi_device * adev)67b418f741SGwendal Grignou static int chromeos_tbmc_add(struct acpi_device *adev)
68b418f741SGwendal Grignou {
69b418f741SGwendal Grignou 	struct input_dev *idev;
70b418f741SGwendal Grignou 	struct device *dev = &adev->dev;
71b418f741SGwendal Grignou 	int ret;
72b418f741SGwendal Grignou 
73b418f741SGwendal Grignou 	idev = devm_input_allocate_device(dev);
74b418f741SGwendal Grignou 	if (!idev)
75b418f741SGwendal Grignou 		return -ENOMEM;
76b418f741SGwendal Grignou 
77b418f741SGwendal Grignou 	idev->name = "Tablet Mode Switch";
78b418f741SGwendal Grignou 	idev->phys = acpi_device_hid(adev);
79b418f741SGwendal Grignou 
80b418f741SGwendal Grignou 	idev->id.bustype = BUS_HOST;
81b418f741SGwendal Grignou 	idev->id.version = 1;
82b418f741SGwendal Grignou 	idev->id.product = 0;
83b418f741SGwendal Grignou 	idev->open = chromeos_tbmc_open;
84b418f741SGwendal Grignou 
85b418f741SGwendal Grignou 	input_set_drvdata(idev, adev);
86b418f741SGwendal Grignou 	adev->driver_data = idev;
87b418f741SGwendal Grignou 
88b418f741SGwendal Grignou 	input_set_capability(idev, EV_SW, SW_TABLET_MODE);
89b418f741SGwendal Grignou 	ret = input_register_device(idev);
90b418f741SGwendal Grignou 	if (ret) {
91b418f741SGwendal Grignou 		dev_err(dev, "cannot register input device\n");
92b418f741SGwendal Grignou 		return ret;
93b418f741SGwendal Grignou 	}
94*0144c00eSRavi Chandra Sadineni 	device_init_wakeup(dev, true);
95b418f741SGwendal Grignou 	return 0;
96b418f741SGwendal Grignou }
97b418f741SGwendal Grignou 
98b418f741SGwendal Grignou static const struct acpi_device_id chromeos_tbmc_acpi_device_ids[] = {
99b418f741SGwendal Grignou 	{ ACPI_DRV_NAME, 0 },
100b418f741SGwendal Grignou 	{ }
101b418f741SGwendal Grignou };
102b418f741SGwendal Grignou MODULE_DEVICE_TABLE(acpi, chromeos_tbmc_acpi_device_ids);
103b418f741SGwendal Grignou 
104bc3f4b5cSNathan Chancellor static SIMPLE_DEV_PM_OPS(chromeos_tbmc_pm_ops, NULL,
105b418f741SGwendal Grignou 		chromeos_tbmc_resume);
106b418f741SGwendal Grignou 
107b418f741SGwendal Grignou static struct acpi_driver chromeos_tbmc_driver = {
108b418f741SGwendal Grignou 	.name = DRV_NAME,
109b418f741SGwendal Grignou 	.class = DRV_NAME,
110b418f741SGwendal Grignou 	.ids = chromeos_tbmc_acpi_device_ids,
111b418f741SGwendal Grignou 	.ops = {
112b418f741SGwendal Grignou 		.add = chromeos_tbmc_add,
113b418f741SGwendal Grignou 		.notify = chromeos_tbmc_notify,
114b418f741SGwendal Grignou 	},
115b418f741SGwendal Grignou 	.drv.pm = &chromeos_tbmc_pm_ops,
116b418f741SGwendal Grignou };
117b418f741SGwendal Grignou 
118b418f741SGwendal Grignou module_acpi_driver(chromeos_tbmc_driver);
119b418f741SGwendal Grignou 
120b418f741SGwendal Grignou MODULE_LICENSE("GPL v2");
121b418f741SGwendal Grignou MODULE_DESCRIPTION("ChromeOS ACPI tablet switch driver");
122