Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * lenovo-ymc.c - Lenovo Yoga Mode Control driver
4 *
5 * Copyright © 2022 Gergo Koteles <soyer@irl.hu>
6 */
7
8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9
10#include <linux/acpi.h>
11#include <linux/dmi.h>
12#include <linux/input.h>
13#include <linux/input/sparse-keymap.h>
14#include <linux/wmi.h>
15#include "ideapad-laptop.h"
16
17#define LENOVO_YMC_EVENT_GUID "06129D99-6083-4164-81AD-F092F9D773A6"
18#define LENOVO_YMC_QUERY_GUID "09B0EE6E-C3FD-4243-8DA1-7911FF80BB8C"
19
20#define LENOVO_YMC_QUERY_INSTANCE 0
21#define LENOVO_YMC_QUERY_METHOD 0x01
22
23static bool force;
24module_param(force, bool, 0444);
25MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type");
26
27static const struct dmi_system_id allowed_chasis_types_dmi_table[] = {
28 {
29 .matches = {
30 DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */),
31 },
32 },
33 {
34 .matches = {
35 DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "32" /* Detachable */),
36 },
37 },
38 { }
39};
40
41struct lenovo_ymc_private {
42 struct input_dev *input_dev;
43};
44
45static const struct key_entry lenovo_ymc_keymap[] = {
46 /* Laptop */
47 { KE_SW, 0x01, { .sw = { SW_TABLET_MODE, 0 } } },
48 /* Tablet */
49 { KE_SW, 0x02, { .sw = { SW_TABLET_MODE, 1 } } },
50 /* Drawing Board */
51 { KE_SW, 0x03, { .sw = { SW_TABLET_MODE, 1 } } },
52 /* Tent */
53 { KE_SW, 0x04, { .sw = { SW_TABLET_MODE, 1 } } },
54 { KE_END },
55};
56
57static void lenovo_ymc_notify(struct wmi_device *wdev, union acpi_object *data)
58{
59 struct lenovo_ymc_private *priv = dev_get_drvdata(&wdev->dev);
60 u32 input_val = 0;
61 struct acpi_buffer input = { sizeof(input_val), &input_val };
62 struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
63 union acpi_object *obj;
64 acpi_status status;
65 int code;
66
67 status = wmi_evaluate_method(LENOVO_YMC_QUERY_GUID,
68 LENOVO_YMC_QUERY_INSTANCE,
69 LENOVO_YMC_QUERY_METHOD,
70 &input, &output);
71
72 if (ACPI_FAILURE(status)) {
73 dev_warn(&wdev->dev,
74 "Failed to evaluate query method: %s\n",
75 acpi_format_exception(status));
76 return;
77 }
78
79 obj = output.pointer;
80
81 if (obj->type != ACPI_TYPE_INTEGER) {
82 dev_warn(&wdev->dev,
83 "WMI event data is not an integer\n");
84 goto free_obj;
85 }
86 code = obj->integer.value;
87
88 if (!sparse_keymap_report_event(priv->input_dev, code, 1, true))
89 dev_warn(&wdev->dev, "Unknown key %d pressed\n", code);
90
91free_obj:
92 kfree(obj);
93 ideapad_laptop_call_notifier(IDEAPAD_LAPTOP_YMC_EVENT, &code);
94}
95
96static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx)
97{
98 struct lenovo_ymc_private *priv;
99 struct input_dev *input_dev;
100 int err;
101
102 if (!dmi_check_system(allowed_chasis_types_dmi_table)) {
103 if (force)
104 dev_info(&wdev->dev, "Force loading Lenovo YMC support\n");
105 else
106 return -ENODEV;
107 }
108
109 priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
110 if (!priv)
111 return -ENOMEM;
112
113 input_dev = devm_input_allocate_device(&wdev->dev);
114 if (!input_dev)
115 return -ENOMEM;
116
117 input_dev->name = "Lenovo Yoga Tablet Mode Control switch";
118 input_dev->phys = LENOVO_YMC_EVENT_GUID "/input0";
119 input_dev->id.bustype = BUS_HOST;
120 input_dev->dev.parent = &wdev->dev;
121 err = sparse_keymap_setup(input_dev, lenovo_ymc_keymap, NULL);
122 if (err) {
123 dev_err(&wdev->dev,
124 "Could not set up input device keymap: %d\n", err);
125 return err;
126 }
127
128 err = input_register_device(input_dev);
129 if (err) {
130 dev_err(&wdev->dev,
131 "Could not register input device: %d\n", err);
132 return err;
133 }
134
135 priv->input_dev = input_dev;
136 dev_set_drvdata(&wdev->dev, priv);
137
138 /* Report the state for the first time on probe */
139 lenovo_ymc_notify(wdev, NULL);
140 return 0;
141}
142
143static const struct wmi_device_id lenovo_ymc_wmi_id_table[] = {
144 { .guid_string = LENOVO_YMC_EVENT_GUID },
145 { }
146};
147MODULE_DEVICE_TABLE(wmi, lenovo_ymc_wmi_id_table);
148
149static struct wmi_driver lenovo_ymc_driver = {
150 .driver = {
151 .name = "lenovo-ymc",
152 },
153 .id_table = lenovo_ymc_wmi_id_table,
154 .probe = lenovo_ymc_probe,
155 .notify = lenovo_ymc_notify,
156};
157
158module_wmi_driver(lenovo_ymc_driver);
159
160MODULE_AUTHOR("Gergo Koteles <soyer@irl.hu>");
161MODULE_DESCRIPTION("Lenovo Yoga Mode Control driver");
162MODULE_LICENSE("GPL");
163MODULE_IMPORT_NS(IDEAPAD_LAPTOP);