xref: /linux/drivers/platform/chrome/chromeos_tbmc.c (revision c474e9f2be9992861d926eea3e5d4fb393cd6e2a)
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