1*e82882cdSGergo Koteles // SPDX-License-Identifier: GPL-2.0-or-later 2*e82882cdSGergo Koteles /* 3*e82882cdSGergo Koteles * lenovo-ymc.c - Lenovo Yoga Mode Control driver 4*e82882cdSGergo Koteles * 5*e82882cdSGergo Koteles * Copyright © 2022 Gergo Koteles <soyer@irl.hu> 6*e82882cdSGergo Koteles */ 7*e82882cdSGergo Koteles 8*e82882cdSGergo Koteles #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 9*e82882cdSGergo Koteles 10*e82882cdSGergo Koteles #include <linux/acpi.h> 11*e82882cdSGergo Koteles #include <linux/dmi.h> 12*e82882cdSGergo Koteles #include <linux/input.h> 13*e82882cdSGergo Koteles #include <linux/input/sparse-keymap.h> 14*e82882cdSGergo Koteles #include <linux/wmi.h> 15*e82882cdSGergo Koteles #include "ideapad-laptop.h" 16*e82882cdSGergo Koteles 17*e82882cdSGergo Koteles #define LENOVO_YMC_EVENT_GUID "06129D99-6083-4164-81AD-F092F9D773A6" 18*e82882cdSGergo Koteles #define LENOVO_YMC_QUERY_GUID "09B0EE6E-C3FD-4243-8DA1-7911FF80BB8C" 19*e82882cdSGergo Koteles 20*e82882cdSGergo Koteles #define LENOVO_YMC_QUERY_INSTANCE 0 21*e82882cdSGergo Koteles #define LENOVO_YMC_QUERY_METHOD 0x01 22*e82882cdSGergo Koteles 23*e82882cdSGergo Koteles static bool ec_trigger __read_mostly; 24*e82882cdSGergo Koteles module_param(ec_trigger, bool, 0444); 25*e82882cdSGergo Koteles MODULE_PARM_DESC(ec_trigger, "Enable EC triggering work-around to force emitting tablet mode events"); 26*e82882cdSGergo Koteles 27*e82882cdSGergo Koteles static const struct dmi_system_id ec_trigger_quirk_dmi_table[] = { 28*e82882cdSGergo Koteles { 29*e82882cdSGergo Koteles /* Lenovo Yoga 7 14ARB7 */ 30*e82882cdSGergo Koteles .matches = { 31*e82882cdSGergo Koteles DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 32*e82882cdSGergo Koteles DMI_MATCH(DMI_PRODUCT_NAME, "82QF"), 33*e82882cdSGergo Koteles }, 34*e82882cdSGergo Koteles }, 35*e82882cdSGergo Koteles { } 36*e82882cdSGergo Koteles }; 37*e82882cdSGergo Koteles 38*e82882cdSGergo Koteles struct lenovo_ymc_private { 39*e82882cdSGergo Koteles struct input_dev *input_dev; 40*e82882cdSGergo Koteles struct acpi_device *ec_acpi_dev; 41*e82882cdSGergo Koteles }; 42*e82882cdSGergo Koteles 43*e82882cdSGergo Koteles static void lenovo_ymc_trigger_ec(struct wmi_device *wdev, struct lenovo_ymc_private *priv) 44*e82882cdSGergo Koteles { 45*e82882cdSGergo Koteles int err; 46*e82882cdSGergo Koteles 47*e82882cdSGergo Koteles if (!priv->ec_acpi_dev) 48*e82882cdSGergo Koteles return; 49*e82882cdSGergo Koteles 50*e82882cdSGergo Koteles err = write_ec_cmd(priv->ec_acpi_dev->handle, VPCCMD_W_YMC, 1); 51*e82882cdSGergo Koteles if (err) 52*e82882cdSGergo Koteles dev_warn(&wdev->dev, "Could not write YMC: %d\n", err); 53*e82882cdSGergo Koteles } 54*e82882cdSGergo Koteles 55*e82882cdSGergo Koteles static const struct key_entry lenovo_ymc_keymap[] = { 56*e82882cdSGergo Koteles /* Laptop */ 57*e82882cdSGergo Koteles { KE_SW, 0x01, { .sw = { SW_TABLET_MODE, 0 } } }, 58*e82882cdSGergo Koteles /* Tablet */ 59*e82882cdSGergo Koteles { KE_SW, 0x02, { .sw = { SW_TABLET_MODE, 1 } } }, 60*e82882cdSGergo Koteles /* Drawing Board */ 61*e82882cdSGergo Koteles { KE_SW, 0x03, { .sw = { SW_TABLET_MODE, 1 } } }, 62*e82882cdSGergo Koteles /* Tent */ 63*e82882cdSGergo Koteles { KE_SW, 0x04, { .sw = { SW_TABLET_MODE, 1 } } }, 64*e82882cdSGergo Koteles { KE_END }, 65*e82882cdSGergo Koteles }; 66*e82882cdSGergo Koteles 67*e82882cdSGergo Koteles static void lenovo_ymc_notify(struct wmi_device *wdev, union acpi_object *data) 68*e82882cdSGergo Koteles { 69*e82882cdSGergo Koteles struct lenovo_ymc_private *priv = dev_get_drvdata(&wdev->dev); 70*e82882cdSGergo Koteles u32 input_val = 0; 71*e82882cdSGergo Koteles struct acpi_buffer input = { sizeof(input_val), &input_val }; 72*e82882cdSGergo Koteles struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 73*e82882cdSGergo Koteles union acpi_object *obj; 74*e82882cdSGergo Koteles acpi_status status; 75*e82882cdSGergo Koteles int code; 76*e82882cdSGergo Koteles 77*e82882cdSGergo Koteles status = wmi_evaluate_method(LENOVO_YMC_QUERY_GUID, 78*e82882cdSGergo Koteles LENOVO_YMC_QUERY_INSTANCE, 79*e82882cdSGergo Koteles LENOVO_YMC_QUERY_METHOD, 80*e82882cdSGergo Koteles &input, &output); 81*e82882cdSGergo Koteles 82*e82882cdSGergo Koteles if (ACPI_FAILURE(status)) { 83*e82882cdSGergo Koteles dev_warn(&wdev->dev, 84*e82882cdSGergo Koteles "Failed to evaluate query method: %s\n", 85*e82882cdSGergo Koteles acpi_format_exception(status)); 86*e82882cdSGergo Koteles return; 87*e82882cdSGergo Koteles } 88*e82882cdSGergo Koteles 89*e82882cdSGergo Koteles obj = output.pointer; 90*e82882cdSGergo Koteles 91*e82882cdSGergo Koteles if (obj->type != ACPI_TYPE_INTEGER) { 92*e82882cdSGergo Koteles dev_warn(&wdev->dev, 93*e82882cdSGergo Koteles "WMI event data is not an integer\n"); 94*e82882cdSGergo Koteles goto free_obj; 95*e82882cdSGergo Koteles } 96*e82882cdSGergo Koteles code = obj->integer.value; 97*e82882cdSGergo Koteles 98*e82882cdSGergo Koteles if (!sparse_keymap_report_event(priv->input_dev, code, 1, true)) 99*e82882cdSGergo Koteles dev_warn(&wdev->dev, "Unknown key %d pressed\n", code); 100*e82882cdSGergo Koteles 101*e82882cdSGergo Koteles free_obj: 102*e82882cdSGergo Koteles kfree(obj); 103*e82882cdSGergo Koteles lenovo_ymc_trigger_ec(wdev, priv); 104*e82882cdSGergo Koteles } 105*e82882cdSGergo Koteles 106*e82882cdSGergo Koteles static void acpi_dev_put_helper(void *p) { acpi_dev_put(p); } 107*e82882cdSGergo Koteles 108*e82882cdSGergo Koteles static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) 109*e82882cdSGergo Koteles { 110*e82882cdSGergo Koteles struct lenovo_ymc_private *priv; 111*e82882cdSGergo Koteles struct input_dev *input_dev; 112*e82882cdSGergo Koteles int err; 113*e82882cdSGergo Koteles 114*e82882cdSGergo Koteles ec_trigger |= dmi_check_system(ec_trigger_quirk_dmi_table); 115*e82882cdSGergo Koteles 116*e82882cdSGergo Koteles priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); 117*e82882cdSGergo Koteles if (!priv) 118*e82882cdSGergo Koteles return -ENOMEM; 119*e82882cdSGergo Koteles 120*e82882cdSGergo Koteles if (ec_trigger) { 121*e82882cdSGergo Koteles pr_debug("Lenovo YMC enable EC triggering.\n"); 122*e82882cdSGergo Koteles priv->ec_acpi_dev = acpi_dev_get_first_match_dev("VPC2004", NULL, -1); 123*e82882cdSGergo Koteles 124*e82882cdSGergo Koteles if (!priv->ec_acpi_dev) { 125*e82882cdSGergo Koteles dev_err(&wdev->dev, "Could not find EC ACPI device.\n"); 126*e82882cdSGergo Koteles return -ENODEV; 127*e82882cdSGergo Koteles } 128*e82882cdSGergo Koteles err = devm_add_action_or_reset(&wdev->dev, 129*e82882cdSGergo Koteles acpi_dev_put_helper, priv->ec_acpi_dev); 130*e82882cdSGergo Koteles if (err) { 131*e82882cdSGergo Koteles dev_err(&wdev->dev, 132*e82882cdSGergo Koteles "Could not clean up EC ACPI device: %d\n", err); 133*e82882cdSGergo Koteles return err; 134*e82882cdSGergo Koteles } 135*e82882cdSGergo Koteles } 136*e82882cdSGergo Koteles 137*e82882cdSGergo Koteles input_dev = devm_input_allocate_device(&wdev->dev); 138*e82882cdSGergo Koteles if (!input_dev) 139*e82882cdSGergo Koteles return -ENOMEM; 140*e82882cdSGergo Koteles 141*e82882cdSGergo Koteles input_dev->name = "Lenovo Yoga Tablet Mode Control switch"; 142*e82882cdSGergo Koteles input_dev->phys = LENOVO_YMC_EVENT_GUID "/input0"; 143*e82882cdSGergo Koteles input_dev->id.bustype = BUS_HOST; 144*e82882cdSGergo Koteles input_dev->dev.parent = &wdev->dev; 145*e82882cdSGergo Koteles err = sparse_keymap_setup(input_dev, lenovo_ymc_keymap, NULL); 146*e82882cdSGergo Koteles if (err) { 147*e82882cdSGergo Koteles dev_err(&wdev->dev, 148*e82882cdSGergo Koteles "Could not set up input device keymap: %d\n", err); 149*e82882cdSGergo Koteles return err; 150*e82882cdSGergo Koteles } 151*e82882cdSGergo Koteles 152*e82882cdSGergo Koteles err = input_register_device(input_dev); 153*e82882cdSGergo Koteles if (err) { 154*e82882cdSGergo Koteles dev_err(&wdev->dev, 155*e82882cdSGergo Koteles "Could not register input device: %d\n", err); 156*e82882cdSGergo Koteles return err; 157*e82882cdSGergo Koteles } 158*e82882cdSGergo Koteles 159*e82882cdSGergo Koteles priv->input_dev = input_dev; 160*e82882cdSGergo Koteles dev_set_drvdata(&wdev->dev, priv); 161*e82882cdSGergo Koteles 162*e82882cdSGergo Koteles /* Report the state for the first time on probe */ 163*e82882cdSGergo Koteles lenovo_ymc_trigger_ec(wdev, priv); 164*e82882cdSGergo Koteles lenovo_ymc_notify(wdev, NULL); 165*e82882cdSGergo Koteles return 0; 166*e82882cdSGergo Koteles } 167*e82882cdSGergo Koteles 168*e82882cdSGergo Koteles static const struct wmi_device_id lenovo_ymc_wmi_id_table[] = { 169*e82882cdSGergo Koteles { .guid_string = LENOVO_YMC_EVENT_GUID }, 170*e82882cdSGergo Koteles { } 171*e82882cdSGergo Koteles }; 172*e82882cdSGergo Koteles MODULE_DEVICE_TABLE(wmi, lenovo_ymc_wmi_id_table); 173*e82882cdSGergo Koteles 174*e82882cdSGergo Koteles static struct wmi_driver lenovo_ymc_driver = { 175*e82882cdSGergo Koteles .driver = { 176*e82882cdSGergo Koteles .name = "lenovo-ymc", 177*e82882cdSGergo Koteles }, 178*e82882cdSGergo Koteles .id_table = lenovo_ymc_wmi_id_table, 179*e82882cdSGergo Koteles .probe = lenovo_ymc_probe, 180*e82882cdSGergo Koteles .notify = lenovo_ymc_notify, 181*e82882cdSGergo Koteles }; 182*e82882cdSGergo Koteles 183*e82882cdSGergo Koteles module_wmi_driver(lenovo_ymc_driver); 184*e82882cdSGergo Koteles 185*e82882cdSGergo Koteles MODULE_AUTHOR("Gergo Koteles <soyer@irl.hu>"); 186*e82882cdSGergo Koteles MODULE_DESCRIPTION("Lenovo Yoga Mode Control driver"); 187*e82882cdSGergo Koteles MODULE_LICENSE("GPL"); 188