1 // SPDX-License-Identifier: GPL-2.0
3 * Platform driver for Lenovo Yoga Book YB1-X90F/L tablets (Android model)
4 * WMI driver for Lenovo Yoga Book YB1-X91F/L tablets (Windows model)
6 * The keyboard half of the YB1 models can function as both a capacitive
7 * touch keyboard or as a Wacom digitizer, but not at the same time.
9 * This driver takes care of switching between the 2 functions.
11 * Copyright 2023 Hans de Goede <hansg@kernel.org>
14 #include <linux/acpi.h>
15 #include <linux/gpio/consumer.h>
16 #include <linux/gpio/machine.h>
17 #include <linux/i2c.h>
18 #include <linux/interrupt.h>
19 #include <linux/leds.h>
20 #include <linux/module.h>
21 #include <linux/platform_device.h>
22 #include <linux/pwm.h>
23 #include <linux/wmi.h>
24 #include <linux/workqueue.h>
26 #define YB_MBTN_EVENT_GUID "243FEC1D-1963-41C1-8100-06A9D82A94B4"
28 #define YB_KBD_BL_DEFAULT 128
29 #define YB_KBD_BL_MAX 255
30 #define YB_KBD_BL_PWM_PERIOD 13333
32 #define YB_PDEV_NAME "yogabook-touch-kbd-digitizer-switch"
43 struct yogabook_data
{
45 struct acpi_device
*kbd_adev
;
46 struct acpi_device
*dig_adev
;
47 struct device
*kbd_dev
;
48 struct device
*dig_dev
;
49 struct led_classdev
*pen_led
;
50 struct gpio_desc
*pen_touch_event
;
51 struct gpio_desc
*kbd_bl_led_enable
;
52 struct gpio_desc
*backside_hall_gpio
;
53 struct pwm_device
*kbd_bl_pwm
;
54 int (*set_kbd_backlight
)(struct yogabook_data
*data
, uint8_t level
);
56 int backside_hall_irq
;
57 struct work_struct work
;
58 struct led_classdev kbd_bl_led
;
63 static void yogabook_work(struct work_struct
*work
)
65 struct yogabook_data
*data
= container_of(work
, struct yogabook_data
, work
);
66 bool kbd_on
, digitizer_on
;
69 if (test_bit(YB_SUSPENDED
, &data
->flags
))
72 if (test_bit(YB_TABLET_MODE
, &data
->flags
)) {
75 } else if (test_bit(YB_DIGITIZER_MODE
, &data
->flags
)) {
83 if (!kbd_on
&& test_bit(YB_KBD_IS_ON
, &data
->flags
)) {
85 * Must be done before releasing the keyboard touchscreen driver,
86 * so that the keyboard touchscreen dev is still in D0.
88 data
->set_kbd_backlight(data
, 0);
89 device_release_driver(data
->kbd_dev
);
90 clear_bit(YB_KBD_IS_ON
, &data
->flags
);
93 if (!digitizer_on
&& test_bit(YB_DIGITIZER_IS_ON
, &data
->flags
)) {
94 led_set_brightness(data
->pen_led
, LED_OFF
);
95 device_release_driver(data
->dig_dev
);
96 clear_bit(YB_DIGITIZER_IS_ON
, &data
->flags
);
99 if (kbd_on
&& !test_bit(YB_KBD_IS_ON
, &data
->flags
)) {
100 r
= device_reprobe(data
->kbd_dev
);
102 dev_warn(data
->dev
, "Reprobe of keyboard touchscreen failed: %d\n", r
);
104 data
->set_kbd_backlight(data
, data
->brightness
);
105 set_bit(YB_KBD_IS_ON
, &data
->flags
);
108 if (digitizer_on
&& !test_bit(YB_DIGITIZER_IS_ON
, &data
->flags
)) {
109 r
= device_reprobe(data
->dig_dev
);
111 dev_warn(data
->dev
, "Reprobe of digitizer failed: %d\n", r
);
113 led_set_brightness(data
->pen_led
, LED_FULL
);
114 set_bit(YB_DIGITIZER_IS_ON
, &data
->flags
);
118 static void yogabook_toggle_digitizer_mode(struct yogabook_data
*data
)
120 if (test_bit(YB_SUSPENDED
, &data
->flags
))
123 if (test_bit(YB_DIGITIZER_MODE
, &data
->flags
))
124 clear_bit(YB_DIGITIZER_MODE
, &data
->flags
);
126 set_bit(YB_DIGITIZER_MODE
, &data
->flags
);
129 * We are called from the ACPI core and the driver [un]binding which is
130 * done also needs ACPI functions, use a workqueue to avoid deadlocking.
132 schedule_work(&data
->work
);
135 static irqreturn_t
yogabook_backside_hall_irq(int irq
, void *_data
)
137 struct yogabook_data
*data
= _data
;
139 if (gpiod_get_value(data
->backside_hall_gpio
))
140 set_bit(YB_TABLET_MODE
, &data
->flags
);
142 clear_bit(YB_TABLET_MODE
, &data
->flags
);
144 schedule_work(&data
->work
);
149 #define kbd_led_to_yogabook(cdev) container_of(cdev, struct yogabook_data, kbd_bl_led)
151 static enum led_brightness
kbd_brightness_get(struct led_classdev
*cdev
)
153 struct yogabook_data
*data
= kbd_led_to_yogabook(cdev
);
155 return data
->brightness
;
158 static int kbd_brightness_set(struct led_classdev
*cdev
,
159 enum led_brightness value
)
161 struct yogabook_data
*data
= kbd_led_to_yogabook(cdev
);
163 if ((value
< 0) || (value
> YB_KBD_BL_MAX
))
166 data
->brightness
= value
;
168 if (!test_bit(YB_KBD_IS_ON
, &data
->flags
))
171 return data
->set_kbd_backlight(data
, data
->brightness
);
174 static struct gpiod_lookup_table yogabook_gpios
= {
176 GPIO_LOOKUP("INT33FF:02", 18, "backside_hall_sw", GPIO_ACTIVE_LOW
),
181 static struct led_lookup_data yogabook_pen_led
= {
182 .provider
= "platform::indicator",
183 .con_id
= "pen-icon-led",
186 static int yogabook_probe(struct device
*dev
, struct yogabook_data
*data
,
187 const char *kbd_bl_led_name
)
192 data
->brightness
= YB_KBD_BL_DEFAULT
;
193 set_bit(YB_KBD_IS_ON
, &data
->flags
);
194 set_bit(YB_DIGITIZER_IS_ON
, &data
->flags
);
195 INIT_WORK(&data
->work
, yogabook_work
);
197 yogabook_pen_led
.dev_id
= dev_name(dev
);
198 led_add_lookup(&yogabook_pen_led
);
199 data
->pen_led
= devm_led_get(dev
, "pen-icon-led");
200 led_remove_lookup(&yogabook_pen_led
);
202 if (IS_ERR(data
->pen_led
))
203 return dev_err_probe(dev
, PTR_ERR(data
->pen_led
), "Getting pen icon LED\n");
205 yogabook_gpios
.dev_id
= dev_name(dev
);
206 gpiod_add_lookup_table(&yogabook_gpios
);
207 data
->backside_hall_gpio
= devm_gpiod_get(dev
, "backside_hall_sw", GPIOD_IN
);
208 gpiod_remove_lookup_table(&yogabook_gpios
);
210 if (IS_ERR(data
->backside_hall_gpio
))
211 return dev_err_probe(dev
, PTR_ERR(data
->backside_hall_gpio
),
212 "Getting backside_hall_sw GPIO\n");
214 r
= gpiod_to_irq(data
->backside_hall_gpio
);
216 return dev_err_probe(dev
, r
, "Getting backside_hall_sw IRQ\n");
218 data
->backside_hall_irq
= r
;
220 /* Set default brightness before enabling the IRQ */
221 data
->set_kbd_backlight(data
, YB_KBD_BL_DEFAULT
);
223 r
= request_irq(data
->backside_hall_irq
, yogabook_backside_hall_irq
,
224 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
225 "backside_hall_sw", data
);
227 return dev_err_probe(dev
, r
, "Requesting backside_hall_sw IRQ\n");
229 schedule_work(&data
->work
);
231 data
->kbd_bl_led
.name
= kbd_bl_led_name
;
232 data
->kbd_bl_led
.brightness_set_blocking
= kbd_brightness_set
;
233 data
->kbd_bl_led
.brightness_get
= kbd_brightness_get
;
234 data
->kbd_bl_led
.max_brightness
= YB_KBD_BL_MAX
;
236 r
= devm_led_classdev_register(dev
, &data
->kbd_bl_led
);
238 dev_err_probe(dev
, r
, "Registering backlight LED device\n");
242 dev_set_drvdata(dev
, data
);
246 free_irq(data
->backside_hall_irq
, data
);
247 cancel_work_sync(&data
->work
);
251 static void yogabook_remove(struct yogabook_data
*data
)
255 free_irq(data
->backside_hall_irq
, data
);
256 cancel_work_sync(&data
->work
);
258 if (!test_bit(YB_KBD_IS_ON
, &data
->flags
))
259 r
|= device_reprobe(data
->kbd_dev
);
261 if (!test_bit(YB_DIGITIZER_IS_ON
, &data
->flags
))
262 r
|= device_reprobe(data
->dig_dev
);
265 dev_warn(data
->dev
, "Reprobe of devices failed\n");
268 static int yogabook_suspend(struct device
*dev
)
270 struct yogabook_data
*data
= dev_get_drvdata(dev
);
272 set_bit(YB_SUSPENDED
, &data
->flags
);
273 flush_work(&data
->work
);
275 if (test_bit(YB_KBD_IS_ON
, &data
->flags
))
276 data
->set_kbd_backlight(data
, 0);
281 static int yogabook_resume(struct device
*dev
)
283 struct yogabook_data
*data
= dev_get_drvdata(dev
);
285 if (test_bit(YB_KBD_IS_ON
, &data
->flags
))
286 data
->set_kbd_backlight(data
, data
->brightness
);
288 clear_bit(YB_SUSPENDED
, &data
->flags
);
290 /* Check for YB_TABLET_MODE changes made during suspend */
291 schedule_work(&data
->work
);
296 static DEFINE_SIMPLE_DEV_PM_OPS(yogabook_pm_ops
, yogabook_suspend
, yogabook_resume
);
298 /********** WMI driver code **********/
301 * To control keyboard backlight, call the method KBLC() of the TCS1 ACPI
302 * device (Goodix touchpad acts as virtual sensor keyboard).
304 static int yogabook_wmi_set_kbd_backlight(struct yogabook_data
*data
,
307 struct acpi_buffer output
= { ACPI_ALLOCATE_BUFFER
, NULL
};
308 struct acpi_object_list input
;
309 union acpi_object param
;
312 dev_dbg(data
->dev
, "Set KBLC level to %u\n", level
);
314 /* Ensure keyboard touchpad is on before we call KBLC() */
315 acpi_device_set_power(data
->kbd_adev
, ACPI_STATE_D0
);
318 input
.pointer
= ¶m
;
320 param
.type
= ACPI_TYPE_INTEGER
;
321 param
.integer
.value
= YB_KBD_BL_MAX
- level
;
323 status
= acpi_evaluate_object(acpi_device_handle(data
->kbd_adev
), "KBLC",
325 if (ACPI_FAILURE(status
)) {
326 dev_err(data
->dev
, "Failed to call KBLC method: 0x%x\n", status
);
330 kfree(output
.pointer
);
334 static int yogabook_wmi_probe(struct wmi_device
*wdev
, const void *context
)
336 struct device
*dev
= &wdev
->dev
;
337 struct yogabook_data
*data
;
340 data
= devm_kzalloc(dev
, sizeof(*data
), GFP_KERNEL
);
344 data
->kbd_adev
= acpi_dev_get_first_match_dev("GDIX1001", NULL
, -1);
346 return dev_err_probe(dev
, -ENODEV
, "Cannot find the touchpad device in ACPI tables\n");
348 data
->dig_adev
= acpi_dev_get_first_match_dev("WCOM0019", NULL
, -1);
349 if (!data
->dig_adev
) {
350 r
= dev_err_probe(dev
, -ENODEV
, "Cannot find the digitizer device in ACPI tables\n");
354 data
->kbd_dev
= get_device(acpi_get_first_physical_node(data
->kbd_adev
));
355 if (!data
->kbd_dev
|| !data
->kbd_dev
->driver
) {
360 data
->dig_dev
= get_device(acpi_get_first_physical_node(data
->dig_adev
));
361 if (!data
->dig_dev
|| !data
->dig_dev
->driver
) {
366 data
->set_kbd_backlight
= yogabook_wmi_set_kbd_backlight
;
368 r
= yogabook_probe(dev
, data
, "ybwmi::kbd_backlight");
375 put_device(data
->dig_dev
);
376 put_device(data
->kbd_dev
);
377 acpi_dev_put(data
->dig_adev
);
378 acpi_dev_put(data
->kbd_adev
);
382 static void yogabook_wmi_remove(struct wmi_device
*wdev
)
384 struct yogabook_data
*data
= dev_get_drvdata(&wdev
->dev
);
386 yogabook_remove(data
);
388 put_device(data
->dig_dev
);
389 put_device(data
->kbd_dev
);
390 acpi_dev_put(data
->dig_adev
);
391 acpi_dev_put(data
->kbd_adev
);
394 static void yogabook_wmi_notify(struct wmi_device
*wdev
, union acpi_object
*dummy
)
396 yogabook_toggle_digitizer_mode(dev_get_drvdata(&wdev
->dev
));
399 static const struct wmi_device_id yogabook_wmi_id_table
[] = {
401 .guid_string
= YB_MBTN_EVENT_GUID
,
403 { } /* Terminating entry */
405 MODULE_DEVICE_TABLE(wmi
, yogabook_wmi_id_table
);
407 static struct wmi_driver yogabook_wmi_driver
= {
409 .name
= "yogabook-wmi",
410 .pm
= pm_sleep_ptr(&yogabook_pm_ops
),
412 .no_notify_data
= true,
413 .id_table
= yogabook_wmi_id_table
,
414 .probe
= yogabook_wmi_probe
,
415 .remove
= yogabook_wmi_remove
,
416 .notify
= yogabook_wmi_notify
,
419 /********** platform driver code **********/
421 static struct gpiod_lookup_table yogabook_pdev_gpios
= {
422 .dev_id
= YB_PDEV_NAME
,
424 GPIO_LOOKUP("INT33FF:00", 95, "pen_touch_event", GPIO_ACTIVE_HIGH
),
425 GPIO_LOOKUP("INT33FF:03", 52, "enable_keyboard_led", GPIO_ACTIVE_HIGH
),
430 static int yogabook_pdev_set_kbd_backlight(struct yogabook_data
*data
, u8 level
)
432 struct pwm_state state
= {
433 .period
= YB_KBD_BL_PWM_PERIOD
,
434 .duty_cycle
= YB_KBD_BL_PWM_PERIOD
* level
/ YB_KBD_BL_MAX
,
438 pwm_apply_might_sleep(data
->kbd_bl_pwm
, &state
);
439 gpiod_set_value(data
->kbd_bl_led_enable
, level
? 1 : 0);
443 static irqreturn_t
yogabook_pen_touch_irq(int irq
, void *data
)
445 yogabook_toggle_digitizer_mode(data
);
449 static int yogabook_pdev_probe(struct platform_device
*pdev
)
451 struct device
*dev
= &pdev
->dev
;
452 struct yogabook_data
*data
;
455 data
= devm_kzalloc(dev
, sizeof(*data
), GFP_KERNEL
);
459 data
->kbd_dev
= bus_find_device_by_name(&i2c_bus_type
, NULL
, "i2c-goodix_ts");
460 if (!data
->kbd_dev
|| !data
->kbd_dev
->driver
) {
465 data
->dig_dev
= bus_find_device_by_name(&i2c_bus_type
, NULL
, "i2c-wacom");
466 if (!data
->dig_dev
|| !data
->dig_dev
->driver
) {
471 gpiod_add_lookup_table(&yogabook_pdev_gpios
);
472 data
->pen_touch_event
= devm_gpiod_get(dev
, "pen_touch_event", GPIOD_IN
);
473 data
->kbd_bl_led_enable
= devm_gpiod_get(dev
, "enable_keyboard_led", GPIOD_OUT_HIGH
);
474 gpiod_remove_lookup_table(&yogabook_pdev_gpios
);
476 if (IS_ERR(data
->pen_touch_event
)) {
477 r
= dev_err_probe(dev
, PTR_ERR(data
->pen_touch_event
),
478 "Getting pen_touch_event GPIO\n");
482 if (IS_ERR(data
->kbd_bl_led_enable
)) {
483 r
= dev_err_probe(dev
, PTR_ERR(data
->kbd_bl_led_enable
),
484 "Getting enable_keyboard_led GPIO\n");
488 data
->kbd_bl_pwm
= devm_pwm_get(dev
, "pwm_soc_lpss_2");
489 if (IS_ERR(data
->kbd_bl_pwm
)) {
490 r
= dev_err_probe(dev
, PTR_ERR(data
->kbd_bl_pwm
),
491 "Getting keyboard backlight PWM\n");
495 r
= gpiod_to_irq(data
->pen_touch_event
);
497 dev_err_probe(dev
, r
, "Getting pen_touch_event IRQ\n");
500 data
->pen_touch_irq
= r
;
502 r
= request_irq(data
->pen_touch_irq
, yogabook_pen_touch_irq
, IRQF_TRIGGER_FALLING
,
503 "pen_touch_event", data
);
505 dev_err_probe(dev
, r
, "Requesting pen_touch_event IRQ\n");
509 data
->set_kbd_backlight
= yogabook_pdev_set_kbd_backlight
;
511 r
= yogabook_probe(dev
, data
, "yogabook::kbd_backlight");
518 free_irq(data
->pen_touch_irq
, data
);
519 cancel_work_sync(&data
->work
);
521 put_device(data
->dig_dev
);
522 put_device(data
->kbd_dev
);
526 static void yogabook_pdev_remove(struct platform_device
*pdev
)
528 struct yogabook_data
*data
= platform_get_drvdata(pdev
);
530 yogabook_remove(data
);
531 free_irq(data
->pen_touch_irq
, data
);
532 cancel_work_sync(&data
->work
);
533 put_device(data
->dig_dev
);
534 put_device(data
->kbd_dev
);
537 static struct platform_driver yogabook_pdev_driver
= {
538 .probe
= yogabook_pdev_probe
,
539 .remove
= yogabook_pdev_remove
,
541 .name
= YB_PDEV_NAME
,
542 .pm
= pm_sleep_ptr(&yogabook_pm_ops
),
546 static int __init
yogabook_module_init(void)
550 r
= wmi_driver_register(&yogabook_wmi_driver
);
554 r
= platform_driver_register(&yogabook_pdev_driver
);
556 wmi_driver_unregister(&yogabook_wmi_driver
);
561 static void __exit
yogabook_module_exit(void)
563 platform_driver_unregister(&yogabook_pdev_driver
);
564 wmi_driver_unregister(&yogabook_wmi_driver
);
567 module_init(yogabook_module_init
);
568 module_exit(yogabook_module_exit
);
570 MODULE_ALIAS("platform:" YB_PDEV_NAME
);
571 MODULE_AUTHOR("Yauhen Kharuzhy");
572 MODULE_DESCRIPTION("Lenovo Yoga Book driver");
573 MODULE_LICENSE("GPL v2");