-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtabletmod.c
More file actions
328 lines (285 loc) · 9.55 KB
/
tabletmod.c
File metadata and controls
328 lines (285 loc) · 9.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
// SPDX-License-Identifier: GPL-2.0
/*
* tabletmod.c: Detects that a 2-in-1 hybrid laptop is in tablet mode
* and disables the internal keyboard and trackpad accordingly.
*
* (C) Copyright 2019 Thomas Venriès
* Author: Thomas Venriès <thomas@cryd.io>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/kernel.h> // abs()
#include <linux/module.h>
#include <linux/dmi.h>
#include <linux/iio/iio.h>
#include <linux/device.h> // bus_find_device_by_name()
#include <linux/vt_kern.h> // kd_disable()
#include <linux/input/mousedev.h> // mousedev_disable()
#include <linux/limits.h> // PATH_MAX macro
#include <acpi/button.h> // acpi_lid_open()
#define __debug_variable debug
#define LOG(level, format, arg...) \
do { \
if (__debug_variable) \
pr_##level("Tabletmod: %s(): " format "\n", __func__, \
##arg); \
} while (0)
#define INFO(format, arg...) LOG(info, format, ##arg)
#define ERR(format, arg...) LOG(err, format, ##arg)
#define DBG(format, arg...) LOG(info, format, ##arg)
#define DEFERRED_TASK_DELAY 1000
#define SCHEDULE_DELAYED_WORK(_work) \
do { \
if (!schedule_delayed_work( \
_work, msecs_to_jiffies(DEFERRED_TASK_DELAY))) { \
pr_warn("%s(): work is already on a queue\n", \
__func__); \
} \
} while (0)
static bool debug __read_mostly;
static struct delayed_work accels_work;
static bool inputs_disabled = false;
struct tabletmod_devs {
char accels[2][PATH_MAX];
char inputs[2][PATH_MAX];
};
static struct tabletmod_devs system_config;
static const struct tabletmod_devs system_configs[] __initconst = {
{
/* Internal keyboard's and touchpad's physical names */
.accels = { "iio:device0", "iio:device1" },
/* Keyboard and lid's accelerometer physical names */
.inputs = { "isa0060/serio0/input0", "i2c-SYNA3602:00" }
},
};
static const struct dmi_system_id tabletmod_machines[] __initconst = {
{
.ident = "Ordissimo Julia 2",
.matches = {
DMI_MATCH(DMI_PRODUCT_FAMILY, "Ordissimo"),
DMI_MATCH(DMI_PRODUCT_NAME, "Julia 2"),
},
.driver_data = (void *) &system_configs[0]
},
};
static struct accel_handler {
struct iio_dev *dev;
int raw_data[3];
bool is_tablet;
} ts_hdlr, kb_hdlr;
/*
* Reads the raw values (Ax, Ay and Az) from a given accelerometer.
*/
static int tabletmod_read_accel(struct accel_handler *accel)
{
struct iio_chan_spec const *chans;
int i, val2;
struct iio_dev *indio_dev = accel->dev;
if (!indio_dev || !indio_dev->channels) {
DBG("device not found");
return -ENODEV;
}
/* FIXME: Selects only the `IIO_ACCEL` channels and calls read_raw()
* function.
*/
chans = indio_dev->channels;
/*
* Let's assume that the number of channels is based on the following
* scheme: num_channels = N * IIO_ACCEL + IIO_TIMESTAMP
* where:
* - `IIO_ACCEL`: an axis data
* - `IIO_TIMESTAMP`: a delay data.
* and `IIO_ACCEL` channels are leading the channel list
*
* So, the first `num_channels - 1` element(s) of
* the `struct iio_chan_spec` list are the axis data we need to read.
*/
for (i = 0; i < (indio_dev->num_channels - 1); ++i) {
indio_dev->info->read_raw(indio_dev, chans++,
&accel->raw_data[i], &val2,
IIO_CHAN_INFO_RAW);
}
DBG("%s: (%d;%d;%d)", indio_dev->name, accel->raw_data[0],
accel->raw_data[1], accel->raw_data[2]);
return 0;
}
/*
* Finds a industrial I/O device (or accelerometer in our case) by its name.
*/
static struct iio_dev *tabletmod_find_iio_by_name(const char *name)
{
struct iio_dev *indio_dev;
struct device *dev;
dev = bus_find_device_by_name(&iio_bus_type, NULL, name);
if (!dev)
return NULL;
indio_dev = container_of(dev, struct iio_dev, dev);
if (!indio_dev)
return NULL;
/* TODO: A IIO device must have at least 2 or 3 x IIO_ACCEL-type
* channels. Otherwise, we return NULL.
*/
return indio_dev;
}
/*
* Verifies if the devices specified in the system config exist.
*/
static int tabletmod_check_devices(const struct dmi_system_id *dmi)
{
struct tabletmod_devs *tab_devs = dmi->driver_data;
ts_hdlr.dev = tabletmod_find_iio_by_name(tab_devs->accels[0]);
if (!ts_hdlr.dev) {
DBG("device %s is missing", tab_devs->accels[0]);
return -ENODEV;
}
kb_hdlr.dev = tabletmod_find_iio_by_name(tab_devs->accels[1]);
if (!kb_hdlr.dev) {
DBG("device %s is missing", tab_devs->accels[1]);
return -ENODEV;
}
return 0;
}
/*
* Disables both the 2-in-1 laptop's keyboard and trackpad.
*/
static void tabletmod_disable_inputs(bool disabled)
{
if (disabled == inputs_disabled)
return;
INFO("%s inputs", (disabled) ? "disabled" : "enabled");
/*
* TODO: We should access the internal keyboard and trackpad
* input devices without the need of patching the kernel code base:
*/
kd_disable(disabled, system_config.inputs[0]);
mousedev_disable(disabled, system_config.inputs[1]);
inputs_disabled = disabled;
}
/*
* Detects that the 2-in-1 laptop's touchscreen is in tablet position.
*/
static inline bool detect_tabletmode_touchscreen(struct accel_handler *accel)
{
/* XZ Rotation: forward */
return (accel->raw_data[1] < 0 && accel->raw_data[2] < 500)
/* XY Rotation: left or right */
|| (accel->raw_data[1] < 320 && (accel->raw_data[0] > 380
|| accel->raw_data[0] < -380))
/* XYZ Rotation: forward-left */
|| (accel->raw_data[0] > -150 && accel->raw_data[1] < 250
&& accel->raw_data[2] > 360);
}
/*
* Detects that the laptop's keyboard is in tablet position.
*/
static inline bool detect_tabletmode_keyboard(struct accel_handler *accel)
{ /* XZ Rotation: forward */
return (accel->raw_data[0] < 0 && accel->raw_data[2] > -400)
/* XZ Rotation: backward */
|| (accel->raw_data[0] < 410 && accel->raw_data[2] > 280)
/* YZ Rotation: right */
|| (accel->raw_data[1] > -445 && accel->raw_data[2] > -260)
/* YZ Rotation: left */
|| (accel->raw_data[1] > 480 && accel->raw_data[2] > -230)
/* XYZ Rotation: forward-left */
|| (accel->raw_data[0] > 220 && accel->raw_data[1] < -350
&& accel->raw_data[2] > -335);
}
static u16 distance(int a, int b)
{
if (a > 0 && b > 0)
return max(a, b) - min(a, b);
else if (a < 0 && b < 0)
return abs(a - b);
else if (a == b)
return 0;
return abs(a) + abs(b);
}
static inline bool detect_tabletmode_parallel(struct accel_handler *accel1,
struct accel_handler *accel2)
{
/*
* When the touchscreen is folded back at 360° and facing the ground
* it is the same position than a close laptop put on the desk.
* That's why if the lid is open, we consider a range of position
* as tablet mode.
*/
return acpi_lid_open() &&
distance(accel1->raw_data[2], accel2->raw_data[2]) < 100;
}
/*
* Delayed task function which disables the input devices if it detects that
* the laptop is in tablet mode.
*/
static void tabletmod_handler(struct work_struct *work)
{
if (tabletmod_read_accel(&ts_hdlr) != 0
|| tabletmod_read_accel(&kb_hdlr) != 0) {
ERR("cannot read from one or both accelerometers");
goto schedule;
}
tabletmod_disable_inputs(
detect_tabletmode_parallel(&ts_hdlr, &kb_hdlr) ||
detect_tabletmode_touchscreen(&ts_hdlr) ||
detect_tabletmode_keyboard(&kb_hdlr));
schedule:
SCHEDULE_DELAYED_WORK(&accels_work);
}
static int __init tabletmod_init(void)
{
const struct dmi_system_id *dmi;
int ret;
/* Identify the machine and verify the required devices are present */
ret = dmi_check_system(tabletmod_machines);
/* We expect a unique profile per machine */
if (ret != 1) {
ERR("expects a unique machine profile, but found %d.", ret);
return 0; // Avoid kernel error
}
dmi = dmi_first_match(tabletmod_machines);
if (tabletmod_check_devices(dmi) != 0) {
ERR("some devices are missing");
return -ENODEV;
}
/* Store the system config after checking */
system_config = *(struct tabletmod_devs *)dmi->driver_data;
INIT_DELAYED_WORK(&accels_work, tabletmod_handler);
INFO("scheduling work...");
SCHEDULE_DELAYED_WORK(&accels_work);
return 0;
}
static void __exit tabletmod_exit(void)
{
INFO("canceling work...");
cancel_delayed_work_sync(&accels_work);
}
module_init(tabletmod_init);
module_exit(tabletmod_exit);
/*
* The idea is to load kernel modules automatically based on the DMI system
* identification information of the BIOS. The DMI system information can
* be used to identify a platform.
*
* We use the `MODULE_ALIAS(_dmi_)` macro to autoload this module
* according to a `_dmi_` expression.
*
* For consistency, use the same DMI information than those
* from the `tabletmod_machines[]` array above.
*
*/
MODULE_ALIAS("dmi:*:svnOrdissimo:pnJulia2:*:rvnOrdissimo:rnOrdissimo:*:");
/*
* We have to load this driver after the accelerometer's drivers.
* Without it, we cannot get the iio device via the kernel iio's api.
* We need to add a soft dependencie to accelerometer's drivers
*/
MODULE_SOFTDEP("pre: kxcjk_1013");
MODULE_AUTHOR("Thomas Venriès <thomas@cryd.io>");
MODULE_VERSION("0.1");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Detect the tablet mode from accelerometers and disable inputs accordingly");
module_param(debug, bool, 0644);
MODULE_PARM_DESC(debug, "Enable debug messages");