2021-11-28 22:00:30 +03:00
// SPDX-License-Identifier: GPL-2.0
/* WMI driver for Lenovo Yoga Book YB1-X90* / -X91* tablets */
# include <linux/acpi.h>
2021-11-28 22:00:31 +03:00
# include <linux/gpio/consumer.h>
# include <linux/gpio/machine.h>
# include <linux/interrupt.h>
2021-11-28 22:00:30 +03:00
# include <linux/module.h>
# include <linux/leds.h>
# include <linux/wmi.h>
# include <linux/workqueue.h>
# define YB_MBTN_EVENT_GUID "243FEC1D-1963-41C1-8100-06A9D82A94B4"
# define YB_KBD_BL_DEFAULT 128
/* flags */
enum {
YB_KBD_IS_ON ,
YB_DIGITIZER_IS_ON ,
YB_DIGITIZER_MODE ,
2021-11-28 22:00:31 +03:00
YB_TABLET_MODE ,
2021-11-28 22:00:30 +03:00
YB_SUSPENDED ,
} ;
struct yogabook_wmi {
2023-04-30 19:57:55 +03:00
struct device * dev ;
2021-11-28 22:00:30 +03:00
struct acpi_device * kbd_adev ;
struct acpi_device * dig_adev ;
struct device * kbd_dev ;
struct device * dig_dev ;
2023-04-30 19:57:57 +03:00
struct led_classdev * pen_led ;
2021-11-28 22:00:31 +03:00
struct gpio_desc * backside_hall_gpio ;
2023-04-30 19:58:00 +03:00
int ( * set_kbd_backlight ) ( struct yogabook_wmi * data , uint8_t level ) ;
2021-11-28 22:00:31 +03:00
int backside_hall_irq ;
2021-11-28 22:00:30 +03:00
struct work_struct work ;
struct led_classdev kbd_bl_led ;
unsigned long flags ;
uint8_t brightness ;
} ;
/*
* To control keyboard backlight , call the method KBLC ( ) of the TCS1 ACPI
* device ( Goodix touchpad acts as virtual sensor keyboard ) .
*/
2023-04-30 19:57:55 +03:00
static int yogabook_wmi_set_kbd_backlight ( struct yogabook_wmi * data ,
2021-11-28 22:00:30 +03:00
uint8_t level )
{
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER , NULL } ;
struct acpi_object_list input ;
union acpi_object param ;
acpi_status status ;
2023-04-30 19:57:55 +03:00
dev_dbg ( data - > dev , " Set KBLC level to %u \n " , level ) ;
2021-11-28 22:00:30 +03:00
2023-04-30 19:57:59 +03:00
/* Ensure keyboard touchpad is on before we call KBLC() */
acpi_device_set_power ( data - > kbd_adev , ACPI_STATE_D0 ) ;
2021-11-28 22:00:30 +03:00
input . count = 1 ;
input . pointer = & param ;
param . type = ACPI_TYPE_INTEGER ;
param . integer . value = 255 - level ;
status = acpi_evaluate_object ( acpi_device_handle ( data - > kbd_adev ) , " KBLC " ,
& input , & output ) ;
if ( ACPI_FAILURE ( status ) ) {
2023-04-30 19:57:55 +03:00
dev_err ( data - > dev , " Failed to call KBLC method: 0x%x \n " , status ) ;
2021-11-28 22:00:30 +03:00
return status ;
}
kfree ( output . pointer ) ;
return 0 ;
}
static void yogabook_wmi_work ( struct work_struct * work )
{
struct yogabook_wmi * data = container_of ( work , struct yogabook_wmi , work ) ;
bool kbd_on , digitizer_on ;
int r ;
if ( test_bit ( YB_SUSPENDED , & data - > flags ) )
return ;
2021-11-28 22:00:31 +03:00
if ( test_bit ( YB_TABLET_MODE , & data - > flags ) ) {
kbd_on = false ;
digitizer_on = false ;
} else if ( test_bit ( YB_DIGITIZER_MODE , & data - > flags ) ) {
2021-11-28 22:00:30 +03:00
digitizer_on = true ;
kbd_on = false ;
} else {
kbd_on = true ;
digitizer_on = false ;
}
if ( ! kbd_on & & test_bit ( YB_KBD_IS_ON , & data - > flags ) ) {
/*
* Must be done before releasing the keyboard touchscreen driver ,
* so that the keyboard touchscreen dev is still in D0 .
*/
2023-04-30 19:58:00 +03:00
data - > set_kbd_backlight ( data , 0 ) ;
2021-11-28 22:00:30 +03:00
device_release_driver ( data - > kbd_dev ) ;
clear_bit ( YB_KBD_IS_ON , & data - > flags ) ;
}
if ( ! digitizer_on & & test_bit ( YB_DIGITIZER_IS_ON , & data - > flags ) ) {
2023-04-30 19:57:57 +03:00
led_set_brightness ( data - > pen_led , LED_OFF ) ;
2021-11-28 22:00:30 +03:00
device_release_driver ( data - > dig_dev ) ;
clear_bit ( YB_DIGITIZER_IS_ON , & data - > flags ) ;
}
if ( kbd_on & & ! test_bit ( YB_KBD_IS_ON , & data - > flags ) ) {
r = device_reprobe ( data - > kbd_dev ) ;
if ( r )
2023-04-30 19:57:55 +03:00
dev_warn ( data - > dev , " Reprobe of keyboard touchscreen failed: %d \n " , r ) ;
2021-11-28 22:00:30 +03:00
2023-04-30 19:58:00 +03:00
data - > set_kbd_backlight ( data , data - > brightness ) ;
2021-11-28 22:00:30 +03:00
set_bit ( YB_KBD_IS_ON , & data - > flags ) ;
}
if ( digitizer_on & & ! test_bit ( YB_DIGITIZER_IS_ON , & data - > flags ) ) {
r = device_reprobe ( data - > dig_dev ) ;
if ( r )
2023-04-30 19:57:55 +03:00
dev_warn ( data - > dev , " Reprobe of digitizer failed: %d \n " , r ) ;
2021-11-28 22:00:30 +03:00
2023-04-30 19:57:57 +03:00
led_set_brightness ( data - > pen_led , LED_FULL ) ;
2021-11-28 22:00:30 +03:00
set_bit ( YB_DIGITIZER_IS_ON , & data - > flags ) ;
}
}
2023-04-30 19:58:01 +03:00
static void yogabook_toggle_digitizer_mode ( struct yogabook_wmi * data )
2021-11-28 22:00:30 +03:00
{
if ( test_bit ( YB_SUSPENDED , & data - > flags ) )
return ;
if ( test_bit ( YB_DIGITIZER_MODE , & data - > flags ) )
clear_bit ( YB_DIGITIZER_MODE , & data - > flags ) ;
else
set_bit ( YB_DIGITIZER_MODE , & data - > flags ) ;
/*
* We are called from the ACPI core and the driver [ un ] binding which is
* done also needs ACPI functions , use a workqueue to avoid deadlocking .
*/
schedule_work ( & data - > work ) ;
}
2023-04-30 19:58:01 +03:00
static void yogabook_wmi_notify ( struct wmi_device * wdev , union acpi_object * dummy )
{
yogabook_toggle_digitizer_mode ( dev_get_drvdata ( & wdev - > dev ) ) ;
}
2021-11-28 22:00:31 +03:00
static irqreturn_t yogabook_backside_hall_irq ( int irq , void * _data )
{
struct yogabook_wmi * data = _data ;
if ( gpiod_get_value ( data - > backside_hall_gpio ) )
set_bit ( YB_TABLET_MODE , & data - > flags ) ;
else
clear_bit ( YB_TABLET_MODE , & data - > flags ) ;
schedule_work ( & data - > work ) ;
return IRQ_HANDLED ;
}
2021-11-28 22:00:30 +03:00
static enum led_brightness kbd_brightness_get ( struct led_classdev * cdev )
{
struct yogabook_wmi * data =
container_of ( cdev , struct yogabook_wmi , kbd_bl_led ) ;
return data - > brightness ;
}
static int kbd_brightness_set ( struct led_classdev * cdev ,
enum led_brightness value )
{
struct yogabook_wmi * data =
container_of ( cdev , struct yogabook_wmi , kbd_bl_led ) ;
if ( ( value < 0 ) | | ( value > 255 ) )
return - EINVAL ;
data - > brightness = value ;
2023-04-30 19:57:59 +03:00
if ( ! test_bit ( YB_KBD_IS_ON , & data - > flags ) )
2021-11-28 22:00:30 +03:00
return 0 ;
2023-04-30 19:58:00 +03:00
return data - > set_kbd_backlight ( data , data - > brightness ) ;
2021-11-28 22:00:30 +03:00
}
2021-11-28 22:00:31 +03:00
static struct gpiod_lookup_table yogabook_wmi_gpios = {
2023-04-30 19:57:57 +03:00
. table = {
2021-11-28 22:00:31 +03:00
GPIO_LOOKUP ( " INT33FF:02 " , 18 , " backside_hall_sw " , GPIO_ACTIVE_LOW ) ,
{ }
} ,
} ;
2023-04-30 19:57:57 +03:00
static struct led_lookup_data yogabook_pen_led = {
. provider = " platform::indicator " ,
. con_id = " pen-icon-led " ,
} ;
2023-04-30 19:57:58 +03:00
static int yogabook_probe ( struct device * dev , struct yogabook_wmi * data ,
const char * kbd_bl_led_name )
2021-11-28 22:00:30 +03:00
{
int r ;
2023-04-30 19:57:56 +03:00
data - > dev = dev ;
2021-11-28 22:00:30 +03:00
data - > brightness = YB_KBD_BL_DEFAULT ;
set_bit ( YB_KBD_IS_ON , & data - > flags ) ;
set_bit ( YB_DIGITIZER_IS_ON , & data - > flags ) ;
2023-04-30 19:57:50 +03:00
INIT_WORK ( & data - > work , yogabook_wmi_work ) ;
2021-11-28 22:00:30 +03:00
2023-04-30 19:57:57 +03:00
yogabook_pen_led . dev_id = dev_name ( dev ) ;
led_add_lookup ( & yogabook_pen_led ) ;
data - > pen_led = devm_led_get ( dev , " pen-icon-led " ) ;
led_remove_lookup ( & yogabook_pen_led ) ;
2023-04-30 19:57:58 +03:00
if ( IS_ERR ( data - > pen_led ) )
return dev_err_probe ( dev , PTR_ERR ( data - > pen_led ) , " Getting pen icon LED \n " ) ;
2023-04-30 19:57:57 +03:00
yogabook_wmi_gpios . dev_id = dev_name ( dev ) ;
2021-11-28 22:00:31 +03:00
gpiod_add_lookup_table ( & yogabook_wmi_gpios ) ;
2023-04-30 19:57:56 +03:00
data - > backside_hall_gpio = devm_gpiod_get ( dev , " backside_hall_sw " , GPIOD_IN ) ;
2023-04-30 19:57:53 +03:00
gpiod_remove_lookup_table ( & yogabook_wmi_gpios ) ;
2021-11-28 22:00:31 +03:00
2023-04-30 19:57:58 +03:00
if ( IS_ERR ( data - > backside_hall_gpio ) )
return dev_err_probe ( dev , PTR_ERR ( data - > backside_hall_gpio ) ,
" Getting backside_hall_sw GPIO \n " ) ;
2021-11-28 22:00:31 +03:00
r = gpiod_to_irq ( data - > backside_hall_gpio ) ;
2023-04-30 19:57:58 +03:00
if ( r < 0 )
return dev_err_probe ( dev , r , " Getting backside_hall_sw IRQ \n " ) ;
2021-11-28 22:00:31 +03:00
data - > backside_hall_irq = r ;
2023-04-30 19:57:52 +03:00
/* Set default brightness before enabling the IRQ */
2023-04-30 19:58:00 +03:00
data - > set_kbd_backlight ( data , YB_KBD_BL_DEFAULT ) ;
2023-04-30 19:57:52 +03:00
2023-04-30 19:57:50 +03:00
r = request_irq ( data - > backside_hall_irq , yogabook_backside_hall_irq ,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING ,
" backside_hall_sw " , data ) ;
2023-04-30 19:57:58 +03:00
if ( r )
return dev_err_probe ( dev , r , " Requesting backside_hall_sw IRQ \n " ) ;
2021-11-28 22:00:31 +03:00
2021-11-28 22:00:30 +03:00
schedule_work ( & data - > work ) ;
2023-04-30 19:57:58 +03:00
data - > kbd_bl_led . name = kbd_bl_led_name ;
2021-11-28 22:00:30 +03:00
data - > kbd_bl_led . brightness_set_blocking = kbd_brightness_set ;
data - > kbd_bl_led . brightness_get = kbd_brightness_get ;
data - > kbd_bl_led . max_brightness = 255 ;
2023-04-30 19:57:56 +03:00
r = devm_led_classdev_register ( dev , & data - > kbd_bl_led ) ;
2021-11-28 22:00:30 +03:00
if ( r < 0 ) {
2023-04-30 19:57:56 +03:00
dev_err_probe ( dev , r , " Registering backlight LED device \n " ) ;
2023-04-30 19:57:50 +03:00
goto error_free_irq ;
2021-11-28 22:00:30 +03:00
}
2023-04-30 19:57:56 +03:00
dev_set_drvdata ( dev , data ) ;
2021-11-28 22:00:30 +03:00
return 0 ;
2023-04-30 19:57:50 +03:00
error_free_irq :
free_irq ( data - > backside_hall_irq , data ) ;
cancel_work_sync ( & data - > work ) ;
2023-04-30 19:57:58 +03:00
return r ;
}
static int yogabook_wmi_probe ( struct wmi_device * wdev , const void * context )
{
struct device * dev = & wdev - > dev ;
struct yogabook_wmi * data ;
int r ;
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( data = = NULL )
return - ENOMEM ;
data - > kbd_adev = acpi_dev_get_first_match_dev ( " GDIX1001 " , NULL , - 1 ) ;
if ( ! data - > kbd_adev )
return dev_err_probe ( dev , - ENODEV , " Cannot find the touchpad device in ACPI tables \n " ) ;
data - > dig_adev = acpi_dev_get_first_match_dev ( " WCOM0019 " , NULL , - 1 ) ;
if ( ! data - > dig_adev ) {
r = dev_err_probe ( dev , - ENODEV , " Cannot find the digitizer device in ACPI tables \n " ) ;
goto error_put_devs ;
}
data - > kbd_dev = get_device ( acpi_get_first_physical_node ( data - > kbd_adev ) ) ;
if ( ! data - > kbd_dev | | ! data - > kbd_dev - > driver ) {
r = - EPROBE_DEFER ;
goto error_put_devs ;
}
data - > dig_dev = get_device ( acpi_get_first_physical_node ( data - > dig_adev ) ) ;
if ( ! data - > dig_dev | | ! data - > dig_dev - > driver ) {
r = - EPROBE_DEFER ;
goto error_put_devs ;
}
2023-04-30 19:58:00 +03:00
data - > set_kbd_backlight = yogabook_wmi_set_kbd_backlight ;
2023-04-30 19:57:58 +03:00
r = yogabook_probe ( dev , data , " ybwmi::kbd_backlight " ) ;
if ( r )
goto error_put_devs ;
return 0 ;
2021-11-28 22:00:30 +03:00
error_put_devs :
put_device ( data - > dig_dev ) ;
put_device ( data - > kbd_dev ) ;
acpi_dev_put ( data - > dig_adev ) ;
acpi_dev_put ( data - > kbd_adev ) ;
return r ;
}
2023-04-30 19:57:58 +03:00
static void yogabook_remove ( struct yogabook_wmi * data )
2021-11-28 22:00:30 +03:00
{
2023-04-30 19:57:51 +03:00
int r = 0 ;
2021-11-28 22:00:30 +03:00
2023-04-30 19:57:50 +03:00
free_irq ( data - > backside_hall_irq , data ) ;
cancel_work_sync ( & data - > work ) ;
2023-04-30 19:57:51 +03:00
if ( ! test_bit ( YB_KBD_IS_ON , & data - > flags ) )
r | = device_reprobe ( data - > kbd_dev ) ;
if ( ! test_bit ( YB_DIGITIZER_IS_ON , & data - > flags ) )
r | = device_reprobe ( data - > dig_dev ) ;
if ( r )
2023-04-30 19:57:55 +03:00
dev_warn ( data - > dev , " Reprobe of devices failed \n " ) ;
2023-04-30 19:57:58 +03:00
}
static void yogabook_wmi_remove ( struct wmi_device * wdev )
{
struct yogabook_wmi * data = dev_get_drvdata ( & wdev - > dev ) ;
yogabook_remove ( data ) ;
2023-04-30 19:57:51 +03:00
2021-11-28 22:00:30 +03:00
put_device ( data - > dig_dev ) ;
put_device ( data - > kbd_dev ) ;
acpi_dev_put ( data - > dig_adev ) ;
acpi_dev_put ( data - > kbd_adev ) ;
}
2023-04-30 19:57:54 +03:00
static int yogabook_suspend ( struct device * dev )
2021-11-28 22:00:30 +03:00
{
struct yogabook_wmi * data = dev_get_drvdata ( dev ) ;
set_bit ( YB_SUSPENDED , & data - > flags ) ;
flush_work ( & data - > work ) ;
return 0 ;
}
2023-04-30 19:57:54 +03:00
static int yogabook_resume ( struct device * dev )
2021-11-28 22:00:30 +03:00
{
struct yogabook_wmi * data = dev_get_drvdata ( dev ) ;
2023-04-30 19:57:59 +03:00
if ( test_bit ( YB_KBD_IS_ON , & data - > flags ) )
2023-04-30 19:58:00 +03:00
data - > set_kbd_backlight ( data , data - > brightness ) ;
2021-11-28 22:00:30 +03:00
clear_bit ( YB_SUSPENDED , & data - > flags ) ;
2021-11-28 22:00:31 +03:00
/* Check for YB_TABLET_MODE changes made during suspend */
schedule_work ( & data - > work ) ;
2021-11-28 22:00:30 +03:00
return 0 ;
}
static const struct wmi_device_id yogabook_wmi_id_table [ ] = {
{
. guid_string = YB_MBTN_EVENT_GUID ,
} ,
{ } /* Terminating entry */
} ;
2023-04-30 19:57:54 +03:00
static DEFINE_SIMPLE_DEV_PM_OPS ( yogabook_pm_ops , yogabook_suspend , yogabook_resume ) ;
2021-11-28 22:00:30 +03:00
static struct wmi_driver yogabook_wmi_driver = {
. driver = {
. name = " yogabook-wmi " ,
2023-04-30 19:57:54 +03:00
. pm = pm_sleep_ptr ( & yogabook_pm_ops ) ,
2021-11-28 22:00:30 +03:00
} ,
. no_notify_data = true ,
. id_table = yogabook_wmi_id_table ,
. probe = yogabook_wmi_probe ,
. remove = yogabook_wmi_remove ,
. notify = yogabook_wmi_notify ,
} ;
module_wmi_driver ( yogabook_wmi_driver ) ;
MODULE_DEVICE_TABLE ( wmi , yogabook_wmi_id_table ) ;
MODULE_AUTHOR ( " Yauhen Kharuzhy " ) ;
MODULE_DESCRIPTION ( " Lenovo Yoga Book WMI driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;