157ac3b05SIke Panhc /* 2a4b5a279SIke Panhc * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras 357ac3b05SIke Panhc * 457ac3b05SIke Panhc * Copyright © 2010 Intel Corporation 557ac3b05SIke Panhc * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> 657ac3b05SIke Panhc * 757ac3b05SIke Panhc * This program is free software; you can redistribute it and/or modify 857ac3b05SIke Panhc * it under the terms of the GNU General Public License as published by 957ac3b05SIke Panhc * the Free Software Foundation; either version 2 of the License, or 1057ac3b05SIke Panhc * (at your option) any later version. 1157ac3b05SIke Panhc * 1257ac3b05SIke Panhc * This program is distributed in the hope that it will be useful, 1357ac3b05SIke Panhc * but WITHOUT ANY WARRANTY; without even the implied warranty of 1457ac3b05SIke Panhc * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1557ac3b05SIke Panhc * GNU General Public License for more details. 1657ac3b05SIke Panhc * 1757ac3b05SIke Panhc * You should have received a copy of the GNU General Public License 1857ac3b05SIke Panhc * along with this program; if not, write to the Free Software 1957ac3b05SIke Panhc * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2057ac3b05SIke Panhc * 02110-1301, USA. 2157ac3b05SIke Panhc */ 2257ac3b05SIke Panhc 239ab23989SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 249ab23989SJoe Perches 2557ac3b05SIke Panhc #include <linux/kernel.h> 2657ac3b05SIke Panhc #include <linux/module.h> 2757ac3b05SIke Panhc #include <linux/init.h> 2857ac3b05SIke Panhc #include <linux/types.h> 298b48463fSLv Zheng #include <linux/acpi.h> 3057ac3b05SIke Panhc #include <linux/rfkill.h> 3198ee6919SIke Panhc #include <linux/platform_device.h> 32f63409aeSIke Panhc #include <linux/input.h> 33f63409aeSIke Panhc #include <linux/input/sparse-keymap.h> 34a4ecbb8aSIke Panhc #include <linux/backlight.h> 35a4ecbb8aSIke Panhc #include <linux/fb.h> 36773e3206SIke Panhc #include <linux/debugfs.h> 37773e3206SIke Panhc #include <linux/seq_file.h> 3807a4a4fcSMaxim Mikityanskiy #include <linux/i8042.h> 3985093f79SHans de Goede #include <linux/dmi.h> 40b3facd7bSHimangi Saraogi #include <linux/device.h> 4126bff5f0SHans de Goede #include <acpi/video.h> 4257ac3b05SIke Panhc 43c1f73658SIke Panhc #define IDEAPAD_RFKILL_DEV_NUM (3) 4457ac3b05SIke Panhc 45ade50296SHao Wei Tee #define BM_CONSERVATION_BIT (5) 46ade50296SHao Wei Tee 473371f481SIke Panhc #define CFG_BT_BIT (16) 483371f481SIke Panhc #define CFG_3G_BIT (17) 493371f481SIke Panhc #define CFG_WIFI_BIT (18) 50a84511f7SIke Panhc #define CFG_CAMERA_BIT (19) 513371f481SIke Panhc 5274caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 532d98e0b9SArnd Bergmann static const char *const ideapad_wmi_fnesc_events[] = { 542d98e0b9SArnd Bergmann "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */ 552d98e0b9SArnd Bergmann "56322276-8493-4CE8-A783-98C991274F5E", /* Yoga 700 */ 562d98e0b9SArnd Bergmann }; 5774caab99SArnd Bergmann #endif 5874caab99SArnd Bergmann 592be1dc21SIke Panhc enum { 60ade50296SHao Wei Tee BMCMD_CONSERVATION_ON = 3, 61ade50296SHao Wei Tee BMCMD_CONSERVATION_OFF = 5, 62ade50296SHao Wei Tee }; 63ade50296SHao Wei Tee 64ade50296SHao Wei Tee enum { 652be1dc21SIke Panhc VPCCMD_R_VPC1 = 0x10, 662be1dc21SIke Panhc VPCCMD_R_BL_MAX, 672be1dc21SIke Panhc VPCCMD_R_BL, 682be1dc21SIke Panhc VPCCMD_W_BL, 692be1dc21SIke Panhc VPCCMD_R_WIFI, 702be1dc21SIke Panhc VPCCMD_W_WIFI, 712be1dc21SIke Panhc VPCCMD_R_BT, 722be1dc21SIke Panhc VPCCMD_W_BT, 732be1dc21SIke Panhc VPCCMD_R_BL_POWER, 742be1dc21SIke Panhc VPCCMD_R_NOVO, 752be1dc21SIke Panhc VPCCMD_R_VPC2, 762be1dc21SIke Panhc VPCCMD_R_TOUCHPAD, 772be1dc21SIke Panhc VPCCMD_W_TOUCHPAD, 782be1dc21SIke Panhc VPCCMD_R_CAMERA, 792be1dc21SIke Panhc VPCCMD_W_CAMERA, 802be1dc21SIke Panhc VPCCMD_R_3G, 812be1dc21SIke Panhc VPCCMD_W_3G, 822be1dc21SIke Panhc VPCCMD_R_ODD, /* 0x21 */ 830c7bbeb9SMaxim Mikityanskiy VPCCMD_W_FAN, 840c7bbeb9SMaxim Mikityanskiy VPCCMD_R_RF, 852be1dc21SIke Panhc VPCCMD_W_RF, 860c7bbeb9SMaxim Mikityanskiy VPCCMD_R_FAN = 0x2B, 87296f9fe0SMaxim Mikityanskiy VPCCMD_R_SPECIAL_BUTTONS = 0x31, 882be1dc21SIke Panhc VPCCMD_W_BL_POWER = 0x33, 892be1dc21SIke Panhc }; 902be1dc21SIke Panhc 91331e0ea2SZhang Rui struct ideapad_rfk_priv { 92331e0ea2SZhang Rui int dev; 93331e0ea2SZhang Rui struct ideapad_private *priv; 94331e0ea2SZhang Rui }; 95331e0ea2SZhang Rui 9657ac3b05SIke Panhc struct ideapad_private { 97469f6434SZhang Rui struct acpi_device *adev; 98c1f73658SIke Panhc struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; 99331e0ea2SZhang Rui struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM]; 10098ee6919SIke Panhc struct platform_device *platform_device; 101f63409aeSIke Panhc struct input_dev *inputdev; 102a4ecbb8aSIke Panhc struct backlight_device *blightdev; 103773e3206SIke Panhc struct dentry *debug; 1043371f481SIke Panhc unsigned long cfg; 105ce363c2bSHans de Goede bool has_hw_rfkill_switch; 1062d98e0b9SArnd Bergmann const char *fnesc_guid; 10757ac3b05SIke Panhc }; 10857ac3b05SIke Panhc 109bfa97b7dSIke Panhc static bool no_bt_rfkill; 110bfa97b7dSIke Panhc module_param(no_bt_rfkill, bool, 0444); 111bfa97b7dSIke Panhc MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); 112bfa97b7dSIke Panhc 11357ac3b05SIke Panhc /* 11457ac3b05SIke Panhc * ACPI Helpers 11557ac3b05SIke Panhc */ 11657ac3b05SIke Panhc #define IDEAPAD_EC_TIMEOUT (100) /* in ms */ 11757ac3b05SIke Panhc 11857ac3b05SIke Panhc static int read_method_int(acpi_handle handle, const char *method, int *val) 11957ac3b05SIke Panhc { 12057ac3b05SIke Panhc acpi_status status; 12157ac3b05SIke Panhc unsigned long long result; 12257ac3b05SIke Panhc 12357ac3b05SIke Panhc status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); 12457ac3b05SIke Panhc if (ACPI_FAILURE(status)) { 12557ac3b05SIke Panhc *val = -1; 12657ac3b05SIke Panhc return -1; 127ba3a3387SJiaxun Yang } 12857ac3b05SIke Panhc *val = result; 12957ac3b05SIke Panhc return 0; 130ba3a3387SJiaxun Yang 13157ac3b05SIke Panhc } 13257ac3b05SIke Panhc 133ade50296SHao Wei Tee static int method_gbmd(acpi_handle handle, unsigned long *ret) 134ade50296SHao Wei Tee { 135ade50296SHao Wei Tee int result, val; 136ade50296SHao Wei Tee 137ade50296SHao Wei Tee result = read_method_int(handle, "GBMD", &val); 138ade50296SHao Wei Tee *ret = val; 139ade50296SHao Wei Tee return result; 140ade50296SHao Wei Tee } 141ade50296SHao Wei Tee 142ade50296SHao Wei Tee static int method_sbmc(acpi_handle handle, int cmd) 143ade50296SHao Wei Tee { 144ade50296SHao Wei Tee acpi_status status; 145ade50296SHao Wei Tee 146ade50296SHao Wei Tee status = acpi_execute_simple_method(handle, "SBMC", cmd); 147ade50296SHao Wei Tee return ACPI_FAILURE(status) ? -1 : 0; 148ade50296SHao Wei Tee } 149ade50296SHao Wei Tee 15057ac3b05SIke Panhc static int method_vpcr(acpi_handle handle, int cmd, int *ret) 15157ac3b05SIke Panhc { 15257ac3b05SIke Panhc acpi_status status; 15357ac3b05SIke Panhc unsigned long long result; 15457ac3b05SIke Panhc struct acpi_object_list params; 15557ac3b05SIke Panhc union acpi_object in_obj; 15657ac3b05SIke Panhc 15757ac3b05SIke Panhc params.count = 1; 15857ac3b05SIke Panhc params.pointer = &in_obj; 15957ac3b05SIke Panhc in_obj.type = ACPI_TYPE_INTEGER; 16057ac3b05SIke Panhc in_obj.integer.value = cmd; 16157ac3b05SIke Panhc 16257ac3b05SIke Panhc status = acpi_evaluate_integer(handle, "VPCR", ¶ms, &result); 16357ac3b05SIke Panhc 16457ac3b05SIke Panhc if (ACPI_FAILURE(status)) { 16557ac3b05SIke Panhc *ret = -1; 16657ac3b05SIke Panhc return -1; 167ba3a3387SJiaxun Yang } 16857ac3b05SIke Panhc *ret = result; 16957ac3b05SIke Panhc return 0; 170ba3a3387SJiaxun Yang 17157ac3b05SIke Panhc } 17257ac3b05SIke Panhc 17357ac3b05SIke Panhc static int method_vpcw(acpi_handle handle, int cmd, int data) 17457ac3b05SIke Panhc { 17557ac3b05SIke Panhc struct acpi_object_list params; 17657ac3b05SIke Panhc union acpi_object in_obj[2]; 17757ac3b05SIke Panhc acpi_status status; 17857ac3b05SIke Panhc 17957ac3b05SIke Panhc params.count = 2; 18057ac3b05SIke Panhc params.pointer = in_obj; 18157ac3b05SIke Panhc in_obj[0].type = ACPI_TYPE_INTEGER; 18257ac3b05SIke Panhc in_obj[0].integer.value = cmd; 18357ac3b05SIke Panhc in_obj[1].type = ACPI_TYPE_INTEGER; 18457ac3b05SIke Panhc in_obj[1].integer.value = data; 18557ac3b05SIke Panhc 18657ac3b05SIke Panhc status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); 18757ac3b05SIke Panhc if (status != AE_OK) 18857ac3b05SIke Panhc return -1; 18957ac3b05SIke Panhc return 0; 19057ac3b05SIke Panhc } 19157ac3b05SIke Panhc 19257ac3b05SIke Panhc static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) 19357ac3b05SIke Panhc { 19457ac3b05SIke Panhc int val; 19557ac3b05SIke Panhc unsigned long int end_jiffies; 19657ac3b05SIke Panhc 19757ac3b05SIke Panhc if (method_vpcw(handle, 1, cmd)) 19857ac3b05SIke Panhc return -1; 19957ac3b05SIke Panhc 20057ac3b05SIke Panhc for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 20157ac3b05SIke Panhc time_before(jiffies, end_jiffies);) { 20257ac3b05SIke Panhc schedule(); 20357ac3b05SIke Panhc if (method_vpcr(handle, 1, &val)) 20457ac3b05SIke Panhc return -1; 20557ac3b05SIke Panhc if (val == 0) { 20657ac3b05SIke Panhc if (method_vpcr(handle, 0, &val)) 20757ac3b05SIke Panhc return -1; 20857ac3b05SIke Panhc *data = val; 20957ac3b05SIke Panhc return 0; 21057ac3b05SIke Panhc } 21157ac3b05SIke Panhc } 21257ac3b05SIke Panhc pr_err("timeout in read_ec_cmd\n"); 21357ac3b05SIke Panhc return -1; 21457ac3b05SIke Panhc } 21557ac3b05SIke Panhc 21657ac3b05SIke Panhc static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) 21757ac3b05SIke Panhc { 21857ac3b05SIke Panhc int val; 21957ac3b05SIke Panhc unsigned long int end_jiffies; 22057ac3b05SIke Panhc 22157ac3b05SIke Panhc if (method_vpcw(handle, 0, data)) 22257ac3b05SIke Panhc return -1; 22357ac3b05SIke Panhc if (method_vpcw(handle, 1, cmd)) 22457ac3b05SIke Panhc return -1; 22557ac3b05SIke Panhc 22657ac3b05SIke Panhc for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; 22757ac3b05SIke Panhc time_before(jiffies, end_jiffies);) { 22857ac3b05SIke Panhc schedule(); 22957ac3b05SIke Panhc if (method_vpcr(handle, 1, &val)) 23057ac3b05SIke Panhc return -1; 23157ac3b05SIke Panhc if (val == 0) 23257ac3b05SIke Panhc return 0; 23357ac3b05SIke Panhc } 234f1395edbSJiaxun Yang pr_err("timeout in %s\n", __func__); 23557ac3b05SIke Panhc return -1; 23657ac3b05SIke Panhc } 23757ac3b05SIke Panhc 238a4b5a279SIke Panhc /* 239773e3206SIke Panhc * debugfs 240773e3206SIke Panhc */ 241773e3206SIke Panhc static int debugfs_status_show(struct seq_file *s, void *data) 242773e3206SIke Panhc { 243331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 244773e3206SIke Panhc unsigned long value; 245773e3206SIke Panhc 246331e0ea2SZhang Rui if (!priv) 247331e0ea2SZhang Rui return -EINVAL; 248331e0ea2SZhang Rui 249331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) 250773e3206SIke Panhc seq_printf(s, "Backlight max:\t%lu\n", value); 251331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) 252773e3206SIke Panhc seq_printf(s, "Backlight now:\t%lu\n", value); 253331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) 254773e3206SIke Panhc seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off"); 255773e3206SIke Panhc seq_printf(s, "=====================\n"); 256773e3206SIke Panhc 257331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) 258773e3206SIke Panhc seq_printf(s, "Radio status:\t%s(%lu)\n", 259773e3206SIke Panhc value ? "On" : "Off", value); 260331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) 261773e3206SIke Panhc seq_printf(s, "Wifi status:\t%s(%lu)\n", 262773e3206SIke Panhc value ? "On" : "Off", value); 263331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) 264773e3206SIke Panhc seq_printf(s, "BT status:\t%s(%lu)\n", 265773e3206SIke Panhc value ? "On" : "Off", value); 266331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) 267773e3206SIke Panhc seq_printf(s, "3G status:\t%s(%lu)\n", 268773e3206SIke Panhc value ? "On" : "Off", value); 269773e3206SIke Panhc seq_printf(s, "=====================\n"); 270773e3206SIke Panhc 271331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) 272773e3206SIke Panhc seq_printf(s, "Touchpad status:%s(%lu)\n", 273773e3206SIke Panhc value ? "On" : "Off", value); 274331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) 275773e3206SIke Panhc seq_printf(s, "Camera status:\t%s(%lu)\n", 276773e3206SIke Panhc value ? "On" : "Off", value); 277ade50296SHao Wei Tee seq_puts(s, "=====================\n"); 278ade50296SHao Wei Tee 279ade50296SHao Wei Tee if (!method_gbmd(priv->adev->handle, &value)) { 280ade50296SHao Wei Tee seq_printf(s, "Conservation mode:\t%s(%lu)\n", 281ade50296SHao Wei Tee test_bit(BM_CONSERVATION_BIT, &value) ? "On" : "Off", 282ade50296SHao Wei Tee value); 283ade50296SHao Wei Tee } 284773e3206SIke Panhc 285773e3206SIke Panhc return 0; 286773e3206SIke Panhc } 287773e3206SIke Panhc 288773e3206SIke Panhc static int debugfs_status_open(struct inode *inode, struct file *file) 289773e3206SIke Panhc { 290331e0ea2SZhang Rui return single_open(file, debugfs_status_show, inode->i_private); 291773e3206SIke Panhc } 292773e3206SIke Panhc 293773e3206SIke Panhc static const struct file_operations debugfs_status_fops = { 294773e3206SIke Panhc .owner = THIS_MODULE, 295773e3206SIke Panhc .open = debugfs_status_open, 296773e3206SIke Panhc .read = seq_read, 297773e3206SIke Panhc .llseek = seq_lseek, 298773e3206SIke Panhc .release = single_release, 299773e3206SIke Panhc }; 300773e3206SIke Panhc 301773e3206SIke Panhc static int debugfs_cfg_show(struct seq_file *s, void *data) 302773e3206SIke Panhc { 303331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 304331e0ea2SZhang Rui 305331e0ea2SZhang Rui if (!priv) { 306773e3206SIke Panhc seq_printf(s, "cfg: N/A\n"); 307773e3206SIke Panhc } else { 308773e3206SIke Panhc seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", 309331e0ea2SZhang Rui priv->cfg); 310331e0ea2SZhang Rui if (test_bit(CFG_BT_BIT, &priv->cfg)) 311773e3206SIke Panhc seq_printf(s, "Bluetooth "); 312331e0ea2SZhang Rui if (test_bit(CFG_3G_BIT, &priv->cfg)) 313773e3206SIke Panhc seq_printf(s, "3G "); 314331e0ea2SZhang Rui if (test_bit(CFG_WIFI_BIT, &priv->cfg)) 315773e3206SIke Panhc seq_printf(s, "Wireless "); 316331e0ea2SZhang Rui if (test_bit(CFG_CAMERA_BIT, &priv->cfg)) 317773e3206SIke Panhc seq_printf(s, "Camera "); 318773e3206SIke Panhc seq_printf(s, "\nGraphic: "); 319331e0ea2SZhang Rui switch ((priv->cfg)&0x700) { 320773e3206SIke Panhc case 0x100: 321773e3206SIke Panhc seq_printf(s, "Intel"); 322773e3206SIke Panhc break; 323773e3206SIke Panhc case 0x200: 324773e3206SIke Panhc seq_printf(s, "ATI"); 325773e3206SIke Panhc break; 326773e3206SIke Panhc case 0x300: 327773e3206SIke Panhc seq_printf(s, "Nvidia"); 328773e3206SIke Panhc break; 329773e3206SIke Panhc case 0x400: 330773e3206SIke Panhc seq_printf(s, "Intel and ATI"); 331773e3206SIke Panhc break; 332773e3206SIke Panhc case 0x500: 333773e3206SIke Panhc seq_printf(s, "Intel and Nvidia"); 334773e3206SIke Panhc break; 335773e3206SIke Panhc } 336773e3206SIke Panhc seq_printf(s, "\n"); 337773e3206SIke Panhc } 338773e3206SIke Panhc return 0; 339773e3206SIke Panhc } 340773e3206SIke Panhc 341773e3206SIke Panhc static int debugfs_cfg_open(struct inode *inode, struct file *file) 342773e3206SIke Panhc { 343331e0ea2SZhang Rui return single_open(file, debugfs_cfg_show, inode->i_private); 344773e3206SIke Panhc } 345773e3206SIke Panhc 346773e3206SIke Panhc static const struct file_operations debugfs_cfg_fops = { 347773e3206SIke Panhc .owner = THIS_MODULE, 348773e3206SIke Panhc .open = debugfs_cfg_open, 349773e3206SIke Panhc .read = seq_read, 350773e3206SIke Panhc .llseek = seq_lseek, 351773e3206SIke Panhc .release = single_release, 352773e3206SIke Panhc }; 353773e3206SIke Panhc 354b859f159SGreg Kroah-Hartman static int ideapad_debugfs_init(struct ideapad_private *priv) 355773e3206SIke Panhc { 356773e3206SIke Panhc struct dentry *node; 357773e3206SIke Panhc 358773e3206SIke Panhc priv->debug = debugfs_create_dir("ideapad", NULL); 359773e3206SIke Panhc if (priv->debug == NULL) { 360773e3206SIke Panhc pr_err("failed to create debugfs directory"); 361773e3206SIke Panhc goto errout; 362773e3206SIke Panhc } 363773e3206SIke Panhc 364331e0ea2SZhang Rui node = debugfs_create_file("cfg", S_IRUGO, priv->debug, priv, 365773e3206SIke Panhc &debugfs_cfg_fops); 366773e3206SIke Panhc if (!node) { 367773e3206SIke Panhc pr_err("failed to create cfg in debugfs"); 368773e3206SIke Panhc goto errout; 369773e3206SIke Panhc } 370773e3206SIke Panhc 371331e0ea2SZhang Rui node = debugfs_create_file("status", S_IRUGO, priv->debug, priv, 372773e3206SIke Panhc &debugfs_status_fops); 373773e3206SIke Panhc if (!node) { 374a5c3892fSIke Panhc pr_err("failed to create status in debugfs"); 375773e3206SIke Panhc goto errout; 376773e3206SIke Panhc } 377773e3206SIke Panhc 378773e3206SIke Panhc return 0; 379773e3206SIke Panhc 380773e3206SIke Panhc errout: 381773e3206SIke Panhc return -ENOMEM; 382773e3206SIke Panhc } 383773e3206SIke Panhc 384773e3206SIke Panhc static void ideapad_debugfs_exit(struct ideapad_private *priv) 385773e3206SIke Panhc { 386773e3206SIke Panhc debugfs_remove_recursive(priv->debug); 387773e3206SIke Panhc priv->debug = NULL; 388773e3206SIke Panhc } 389773e3206SIke Panhc 390773e3206SIke Panhc /* 3913371f481SIke Panhc * sysfs 392a4b5a279SIke Panhc */ 39357ac3b05SIke Panhc static ssize_t show_ideapad_cam(struct device *dev, 39457ac3b05SIke Panhc struct device_attribute *attr, 39557ac3b05SIke Panhc char *buf) 39657ac3b05SIke Panhc { 39757ac3b05SIke Panhc unsigned long result; 398331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 39957ac3b05SIke Panhc 400331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result)) 40157ac3b05SIke Panhc return sprintf(buf, "-1\n"); 40257ac3b05SIke Panhc return sprintf(buf, "%lu\n", result); 40357ac3b05SIke Panhc } 40457ac3b05SIke Panhc 40557ac3b05SIke Panhc static ssize_t store_ideapad_cam(struct device *dev, 40657ac3b05SIke Panhc struct device_attribute *attr, 40757ac3b05SIke Panhc const char *buf, size_t count) 40857ac3b05SIke Panhc { 40957ac3b05SIke Panhc int ret, state; 410331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 41157ac3b05SIke Panhc 41257ac3b05SIke Panhc if (!count) 41357ac3b05SIke Panhc return 0; 41457ac3b05SIke Panhc if (sscanf(buf, "%i", &state) != 1) 41557ac3b05SIke Panhc return -EINVAL; 416331e0ea2SZhang Rui ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); 41757ac3b05SIke Panhc if (ret < 0) 4180c7bbeb9SMaxim Mikityanskiy return -EIO; 41957ac3b05SIke Panhc return count; 42057ac3b05SIke Panhc } 42157ac3b05SIke Panhc 42257ac3b05SIke Panhc static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); 42357ac3b05SIke Panhc 4240c7bbeb9SMaxim Mikityanskiy static ssize_t show_ideapad_fan(struct device *dev, 4250c7bbeb9SMaxim Mikityanskiy struct device_attribute *attr, 4260c7bbeb9SMaxim Mikityanskiy char *buf) 4270c7bbeb9SMaxim Mikityanskiy { 4280c7bbeb9SMaxim Mikityanskiy unsigned long result; 429331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 4300c7bbeb9SMaxim Mikityanskiy 431331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result)) 4320c7bbeb9SMaxim Mikityanskiy return sprintf(buf, "-1\n"); 4330c7bbeb9SMaxim Mikityanskiy return sprintf(buf, "%lu\n", result); 4340c7bbeb9SMaxim Mikityanskiy } 4350c7bbeb9SMaxim Mikityanskiy 4360c7bbeb9SMaxim Mikityanskiy static ssize_t store_ideapad_fan(struct device *dev, 4370c7bbeb9SMaxim Mikityanskiy struct device_attribute *attr, 4380c7bbeb9SMaxim Mikityanskiy const char *buf, size_t count) 4390c7bbeb9SMaxim Mikityanskiy { 4400c7bbeb9SMaxim Mikityanskiy int ret, state; 441331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 4420c7bbeb9SMaxim Mikityanskiy 4430c7bbeb9SMaxim Mikityanskiy if (!count) 4440c7bbeb9SMaxim Mikityanskiy return 0; 4450c7bbeb9SMaxim Mikityanskiy if (sscanf(buf, "%i", &state) != 1) 4460c7bbeb9SMaxim Mikityanskiy return -EINVAL; 4470c7bbeb9SMaxim Mikityanskiy if (state < 0 || state > 4 || state == 3) 4480c7bbeb9SMaxim Mikityanskiy return -EINVAL; 449331e0ea2SZhang Rui ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); 4500c7bbeb9SMaxim Mikityanskiy if (ret < 0) 4510c7bbeb9SMaxim Mikityanskiy return -EIO; 4520c7bbeb9SMaxim Mikityanskiy return count; 4530c7bbeb9SMaxim Mikityanskiy } 4540c7bbeb9SMaxim Mikityanskiy 4550c7bbeb9SMaxim Mikityanskiy static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan); 4560c7bbeb9SMaxim Mikityanskiy 45736ac0d43SRitesh Raj Sarraf static ssize_t touchpad_show(struct device *dev, 45836ac0d43SRitesh Raj Sarraf struct device_attribute *attr, 45936ac0d43SRitesh Raj Sarraf char *buf) 46036ac0d43SRitesh Raj Sarraf { 46136ac0d43SRitesh Raj Sarraf struct ideapad_private *priv = dev_get_drvdata(dev); 46236ac0d43SRitesh Raj Sarraf unsigned long result; 46336ac0d43SRitesh Raj Sarraf 46436ac0d43SRitesh Raj Sarraf if (read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result)) 46536ac0d43SRitesh Raj Sarraf return sprintf(buf, "-1\n"); 46636ac0d43SRitesh Raj Sarraf return sprintf(buf, "%lu\n", result); 46736ac0d43SRitesh Raj Sarraf } 46836ac0d43SRitesh Raj Sarraf 46946936fd6SArnd Bergmann /* Switch to RO for now: It might be revisited in the future */ 47046936fd6SArnd Bergmann static ssize_t __maybe_unused touchpad_store(struct device *dev, 47136ac0d43SRitesh Raj Sarraf struct device_attribute *attr, 47236ac0d43SRitesh Raj Sarraf const char *buf, size_t count) 47336ac0d43SRitesh Raj Sarraf { 47436ac0d43SRitesh Raj Sarraf struct ideapad_private *priv = dev_get_drvdata(dev); 47536ac0d43SRitesh Raj Sarraf bool state; 47636ac0d43SRitesh Raj Sarraf int ret; 47736ac0d43SRitesh Raj Sarraf 47836ac0d43SRitesh Raj Sarraf ret = kstrtobool(buf, &state); 47936ac0d43SRitesh Raj Sarraf if (ret) 48036ac0d43SRitesh Raj Sarraf return ret; 48136ac0d43SRitesh Raj Sarraf 48236ac0d43SRitesh Raj Sarraf ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); 48336ac0d43SRitesh Raj Sarraf if (ret < 0) 48436ac0d43SRitesh Raj Sarraf return -EIO; 48536ac0d43SRitesh Raj Sarraf return count; 48636ac0d43SRitesh Raj Sarraf } 48736ac0d43SRitesh Raj Sarraf 4887f363145SAndy Shevchenko static DEVICE_ATTR_RO(touchpad); 48936ac0d43SRitesh Raj Sarraf 490ade50296SHao Wei Tee static ssize_t conservation_mode_show(struct device *dev, 491ade50296SHao Wei Tee struct device_attribute *attr, 492ade50296SHao Wei Tee char *buf) 493ade50296SHao Wei Tee { 494ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 495ade50296SHao Wei Tee unsigned long result; 496ade50296SHao Wei Tee 497ade50296SHao Wei Tee if (method_gbmd(priv->adev->handle, &result)) 498ade50296SHao Wei Tee return sprintf(buf, "-1\n"); 499ade50296SHao Wei Tee return sprintf(buf, "%u\n", test_bit(BM_CONSERVATION_BIT, &result)); 500ade50296SHao Wei Tee } 501ade50296SHao Wei Tee 502ade50296SHao Wei Tee static ssize_t conservation_mode_store(struct device *dev, 503ade50296SHao Wei Tee struct device_attribute *attr, 504ade50296SHao Wei Tee const char *buf, size_t count) 505ade50296SHao Wei Tee { 506ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 507ade50296SHao Wei Tee bool state; 508ade50296SHao Wei Tee int ret; 509ade50296SHao Wei Tee 510ade50296SHao Wei Tee ret = kstrtobool(buf, &state); 511ade50296SHao Wei Tee if (ret) 512ade50296SHao Wei Tee return ret; 513ade50296SHao Wei Tee 514ade50296SHao Wei Tee ret = method_sbmc(priv->adev->handle, state ? 515ade50296SHao Wei Tee BMCMD_CONSERVATION_ON : 516ade50296SHao Wei Tee BMCMD_CONSERVATION_OFF); 517ade50296SHao Wei Tee if (ret < 0) 518ade50296SHao Wei Tee return -EIO; 519ade50296SHao Wei Tee return count; 520ade50296SHao Wei Tee } 521ade50296SHao Wei Tee 522ade50296SHao Wei Tee static DEVICE_ATTR_RW(conservation_mode); 523ade50296SHao Wei Tee 5243371f481SIke Panhc static struct attribute *ideapad_attributes[] = { 5253371f481SIke Panhc &dev_attr_camera_power.attr, 5260c7bbeb9SMaxim Mikityanskiy &dev_attr_fan_mode.attr, 52736ac0d43SRitesh Raj Sarraf &dev_attr_touchpad.attr, 528ade50296SHao Wei Tee &dev_attr_conservation_mode.attr, 5293371f481SIke Panhc NULL 5303371f481SIke Panhc }; 5313371f481SIke Panhc 532587a1f16SAl Viro static umode_t ideapad_is_visible(struct kobject *kobj, 533a84511f7SIke Panhc struct attribute *attr, 534a84511f7SIke Panhc int idx) 535a84511f7SIke Panhc { 536a84511f7SIke Panhc struct device *dev = container_of(kobj, struct device, kobj); 537a84511f7SIke Panhc struct ideapad_private *priv = dev_get_drvdata(dev); 538a84511f7SIke Panhc bool supported; 539a84511f7SIke Panhc 540a84511f7SIke Panhc if (attr == &dev_attr_camera_power.attr) 541a84511f7SIke Panhc supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg)); 5420c7bbeb9SMaxim Mikityanskiy else if (attr == &dev_attr_fan_mode.attr) { 5430c7bbeb9SMaxim Mikityanskiy unsigned long value; 544331e0ea2SZhang Rui supported = !read_ec_data(priv->adev->handle, VPCCMD_R_FAN, 545331e0ea2SZhang Rui &value); 546ade50296SHao Wei Tee } else if (attr == &dev_attr_conservation_mode.attr) { 547ade50296SHao Wei Tee supported = acpi_has_method(priv->adev->handle, "GBMD") && 548ade50296SHao Wei Tee acpi_has_method(priv->adev->handle, "SBMC"); 5490c7bbeb9SMaxim Mikityanskiy } else 550a84511f7SIke Panhc supported = true; 551a84511f7SIke Panhc 552a84511f7SIke Panhc return supported ? attr->mode : 0; 553a84511f7SIke Panhc } 554a84511f7SIke Panhc 55549458e83SMathias Krause static const struct attribute_group ideapad_attribute_group = { 556a84511f7SIke Panhc .is_visible = ideapad_is_visible, 5573371f481SIke Panhc .attrs = ideapad_attributes 5583371f481SIke Panhc }; 5593371f481SIke Panhc 560a4b5a279SIke Panhc /* 561a4b5a279SIke Panhc * Rfkill 562a4b5a279SIke Panhc */ 563c1f73658SIke Panhc struct ideapad_rfk_data { 564c1f73658SIke Panhc char *name; 565c1f73658SIke Panhc int cfgbit; 566c1f73658SIke Panhc int opcode; 567c1f73658SIke Panhc int type; 568c1f73658SIke Panhc }; 569c1f73658SIke Panhc 570b3d94d70SMathias Krause static const struct ideapad_rfk_data ideapad_rfk_data[] = { 5712be1dc21SIke Panhc { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, 5722be1dc21SIke Panhc { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, 5732be1dc21SIke Panhc { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, 574c1f73658SIke Panhc }; 575c1f73658SIke Panhc 57657ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked) 57757ac3b05SIke Panhc { 578331e0ea2SZhang Rui struct ideapad_rfk_priv *priv = data; 5794b200b46SArnd Bergmann int opcode = ideapad_rfk_data[priv->dev].opcode; 58057ac3b05SIke Panhc 5814b200b46SArnd Bergmann return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked); 58257ac3b05SIke Panhc } 58357ac3b05SIke Panhc 5843d59dfcdSBhumika Goyal static const struct rfkill_ops ideapad_rfk_ops = { 58557ac3b05SIke Panhc .set_block = ideapad_rfk_set, 58657ac3b05SIke Panhc }; 58757ac3b05SIke Panhc 588923de84aSIke Panhc static void ideapad_sync_rfk_state(struct ideapad_private *priv) 58957ac3b05SIke Panhc { 590ce363c2bSHans de Goede unsigned long hw_blocked = 0; 59157ac3b05SIke Panhc int i; 59257ac3b05SIke Panhc 593ce363c2bSHans de Goede if (priv->has_hw_rfkill_switch) { 594331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) 59557ac3b05SIke Panhc return; 59657ac3b05SIke Panhc hw_blocked = !hw_blocked; 597ce363c2bSHans de Goede } 59857ac3b05SIke Panhc 599c1f73658SIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 60057ac3b05SIke Panhc if (priv->rfk[i]) 60157ac3b05SIke Panhc rfkill_set_hw_state(priv->rfk[i], hw_blocked); 60257ac3b05SIke Panhc } 60357ac3b05SIke Panhc 60475a11f11SZhang Rui static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) 60557ac3b05SIke Panhc { 60657ac3b05SIke Panhc int ret; 60757ac3b05SIke Panhc unsigned long sw_blocked; 60857ac3b05SIke Panhc 609bfa97b7dSIke Panhc if (no_bt_rfkill && 610bfa97b7dSIke Panhc (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { 611bfa97b7dSIke Panhc /* Force to enable bluetooth when no_bt_rfkill=1 */ 612331e0ea2SZhang Rui write_ec_cmd(priv->adev->handle, 613bfa97b7dSIke Panhc ideapad_rfk_data[dev].opcode, 1); 614bfa97b7dSIke Panhc return 0; 615bfa97b7dSIke Panhc } 616331e0ea2SZhang Rui priv->rfk_priv[dev].dev = dev; 617331e0ea2SZhang Rui priv->rfk_priv[dev].priv = priv; 618bfa97b7dSIke Panhc 61975a11f11SZhang Rui priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, 620b5c37b79SZhang Rui &priv->platform_device->dev, 62175a11f11SZhang Rui ideapad_rfk_data[dev].type, 62275a11f11SZhang Rui &ideapad_rfk_ops, 623331e0ea2SZhang Rui &priv->rfk_priv[dev]); 62457ac3b05SIke Panhc if (!priv->rfk[dev]) 62557ac3b05SIke Panhc return -ENOMEM; 62657ac3b05SIke Panhc 627331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1, 62857ac3b05SIke Panhc &sw_blocked)) { 62957ac3b05SIke Panhc rfkill_init_sw_state(priv->rfk[dev], 0); 63057ac3b05SIke Panhc } else { 63157ac3b05SIke Panhc sw_blocked = !sw_blocked; 63257ac3b05SIke Panhc rfkill_init_sw_state(priv->rfk[dev], sw_blocked); 63357ac3b05SIke Panhc } 63457ac3b05SIke Panhc 63557ac3b05SIke Panhc ret = rfkill_register(priv->rfk[dev]); 63657ac3b05SIke Panhc if (ret) { 63757ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 63857ac3b05SIke Panhc return ret; 63957ac3b05SIke Panhc } 64057ac3b05SIke Panhc return 0; 64157ac3b05SIke Panhc } 64257ac3b05SIke Panhc 64375a11f11SZhang Rui static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) 64457ac3b05SIke Panhc { 64557ac3b05SIke Panhc if (!priv->rfk[dev]) 64657ac3b05SIke Panhc return; 64757ac3b05SIke Panhc 64857ac3b05SIke Panhc rfkill_unregister(priv->rfk[dev]); 64957ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 65057ac3b05SIke Panhc } 65157ac3b05SIke Panhc 65298ee6919SIke Panhc /* 65398ee6919SIke Panhc * Platform device 65498ee6919SIke Panhc */ 655b5c37b79SZhang Rui static int ideapad_sysfs_init(struct ideapad_private *priv) 65698ee6919SIke Panhc { 657b5c37b79SZhang Rui return sysfs_create_group(&priv->platform_device->dev.kobj, 658c9f718d0SIke Panhc &ideapad_attribute_group); 65998ee6919SIke Panhc } 66098ee6919SIke Panhc 661b5c37b79SZhang Rui static void ideapad_sysfs_exit(struct ideapad_private *priv) 66298ee6919SIke Panhc { 6638693ae84SIke Panhc sysfs_remove_group(&priv->platform_device->dev.kobj, 664c9f718d0SIke Panhc &ideapad_attribute_group); 66598ee6919SIke Panhc } 66698ee6919SIke Panhc 667f63409aeSIke Panhc /* 668f63409aeSIke Panhc * input device 669f63409aeSIke Panhc */ 670f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = { 671f43d9ec0SIke Panhc { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, 672296f9fe0SMaxim Mikityanskiy { KE_KEY, 7, { KEY_CAMERA } }, 67348f67d62SAlex Hung { KE_KEY, 8, { KEY_MICMUTE } }, 674296f9fe0SMaxim Mikityanskiy { KE_KEY, 11, { KEY_F16 } }, 675f43d9ec0SIke Panhc { KE_KEY, 13, { KEY_WLAN } }, 676f43d9ec0SIke Panhc { KE_KEY, 16, { KEY_PROG1 } }, 677f43d9ec0SIke Panhc { KE_KEY, 17, { KEY_PROG2 } }, 678296f9fe0SMaxim Mikityanskiy { KE_KEY, 64, { KEY_PROG3 } }, 679296f9fe0SMaxim Mikityanskiy { KE_KEY, 65, { KEY_PROG4 } }, 68007a4a4fcSMaxim Mikityanskiy { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, 68107a4a4fcSMaxim Mikityanskiy { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, 68274caab99SArnd Bergmann { KE_KEY, 128, { KEY_ESC } }, 68374caab99SArnd Bergmann 684f63409aeSIke Panhc { KE_END, 0 }, 685f63409aeSIke Panhc }; 686f63409aeSIke Panhc 687b859f159SGreg Kroah-Hartman static int ideapad_input_init(struct ideapad_private *priv) 688f63409aeSIke Panhc { 689f63409aeSIke Panhc struct input_dev *inputdev; 690f63409aeSIke Panhc int error; 691f63409aeSIke Panhc 692f63409aeSIke Panhc inputdev = input_allocate_device(); 693b222cca6SJoe Perches if (!inputdev) 694f63409aeSIke Panhc return -ENOMEM; 695f63409aeSIke Panhc 696f63409aeSIke Panhc inputdev->name = "Ideapad extra buttons"; 697f63409aeSIke Panhc inputdev->phys = "ideapad/input0"; 698f63409aeSIke Panhc inputdev->id.bustype = BUS_HOST; 6998693ae84SIke Panhc inputdev->dev.parent = &priv->platform_device->dev; 700f63409aeSIke Panhc 701f63409aeSIke Panhc error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); 702f63409aeSIke Panhc if (error) { 703f63409aeSIke Panhc pr_err("Unable to setup input device keymap\n"); 704f63409aeSIke Panhc goto err_free_dev; 705f63409aeSIke Panhc } 706f63409aeSIke Panhc 707f63409aeSIke Panhc error = input_register_device(inputdev); 708f63409aeSIke Panhc if (error) { 709f63409aeSIke Panhc pr_err("Unable to register input device\n"); 710c973d4b5SMichał Kępień goto err_free_dev; 711f63409aeSIke Panhc } 712f63409aeSIke Panhc 7138693ae84SIke Panhc priv->inputdev = inputdev; 714f63409aeSIke Panhc return 0; 715f63409aeSIke Panhc 716f63409aeSIke Panhc err_free_dev: 717f63409aeSIke Panhc input_free_device(inputdev); 718f63409aeSIke Panhc return error; 719f63409aeSIke Panhc } 720f63409aeSIke Panhc 7217451a55aSAxel Lin static void ideapad_input_exit(struct ideapad_private *priv) 722f63409aeSIke Panhc { 7238693ae84SIke Panhc input_unregister_device(priv->inputdev); 7248693ae84SIke Panhc priv->inputdev = NULL; 725f63409aeSIke Panhc } 726f63409aeSIke Panhc 7278693ae84SIke Panhc static void ideapad_input_report(struct ideapad_private *priv, 7288693ae84SIke Panhc unsigned long scancode) 729f63409aeSIke Panhc { 7308693ae84SIke Panhc sparse_keymap_report_event(priv->inputdev, scancode, 1, true); 731f63409aeSIke Panhc } 732f63409aeSIke Panhc 733f43d9ec0SIke Panhc static void ideapad_input_novokey(struct ideapad_private *priv) 734f43d9ec0SIke Panhc { 735f43d9ec0SIke Panhc unsigned long long_pressed; 736f43d9ec0SIke Panhc 737331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) 738f43d9ec0SIke Panhc return; 739f43d9ec0SIke Panhc if (long_pressed) 740f43d9ec0SIke Panhc ideapad_input_report(priv, 17); 741f43d9ec0SIke Panhc else 742f43d9ec0SIke Panhc ideapad_input_report(priv, 16); 743f43d9ec0SIke Panhc } 744f43d9ec0SIke Panhc 745296f9fe0SMaxim Mikityanskiy static void ideapad_check_special_buttons(struct ideapad_private *priv) 746296f9fe0SMaxim Mikityanskiy { 747296f9fe0SMaxim Mikityanskiy unsigned long bit, value; 748296f9fe0SMaxim Mikityanskiy 749331e0ea2SZhang Rui read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value); 750296f9fe0SMaxim Mikityanskiy 751296f9fe0SMaxim Mikityanskiy for (bit = 0; bit < 16; bit++) { 752296f9fe0SMaxim Mikityanskiy if (test_bit(bit, &value)) { 753296f9fe0SMaxim Mikityanskiy switch (bit) { 754a1ec56edSMaxim Mikityanskiy case 0: /* Z580 */ 755a1ec56edSMaxim Mikityanskiy case 6: /* Z570 */ 756296f9fe0SMaxim Mikityanskiy /* Thermal Management button */ 757296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 65); 758296f9fe0SMaxim Mikityanskiy break; 759296f9fe0SMaxim Mikityanskiy case 1: 760296f9fe0SMaxim Mikityanskiy /* OneKey Theater button */ 761296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 64); 762296f9fe0SMaxim Mikityanskiy break; 763a1ec56edSMaxim Mikityanskiy default: 764a1ec56edSMaxim Mikityanskiy pr_info("Unknown special button: %lu\n", bit); 765a1ec56edSMaxim Mikityanskiy break; 766296f9fe0SMaxim Mikityanskiy } 767296f9fe0SMaxim Mikityanskiy } 768296f9fe0SMaxim Mikityanskiy } 769296f9fe0SMaxim Mikityanskiy } 770296f9fe0SMaxim Mikityanskiy 771a4b5a279SIke Panhc /* 772a4ecbb8aSIke Panhc * backlight 773a4ecbb8aSIke Panhc */ 774a4ecbb8aSIke Panhc static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) 775a4ecbb8aSIke Panhc { 776331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 777a4ecbb8aSIke Panhc unsigned long now; 778a4ecbb8aSIke Panhc 779331e0ea2SZhang Rui if (!priv) 780331e0ea2SZhang Rui return -EINVAL; 781331e0ea2SZhang Rui 782331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) 783a4ecbb8aSIke Panhc return -EIO; 784a4ecbb8aSIke Panhc return now; 785a4ecbb8aSIke Panhc } 786a4ecbb8aSIke Panhc 787a4ecbb8aSIke Panhc static int ideapad_backlight_update_status(struct backlight_device *blightdev) 788a4ecbb8aSIke Panhc { 789331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 790331e0ea2SZhang Rui 791331e0ea2SZhang Rui if (!priv) 792331e0ea2SZhang Rui return -EINVAL; 793331e0ea2SZhang Rui 794331e0ea2SZhang Rui if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, 7952be1dc21SIke Panhc blightdev->props.brightness)) 796a4ecbb8aSIke Panhc return -EIO; 797331e0ea2SZhang Rui if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, 798a4ecbb8aSIke Panhc blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1)) 799a4ecbb8aSIke Panhc return -EIO; 800a4ecbb8aSIke Panhc 801a4ecbb8aSIke Panhc return 0; 802a4ecbb8aSIke Panhc } 803a4ecbb8aSIke Panhc 804a4ecbb8aSIke Panhc static const struct backlight_ops ideapad_backlight_ops = { 805a4ecbb8aSIke Panhc .get_brightness = ideapad_backlight_get_brightness, 806a4ecbb8aSIke Panhc .update_status = ideapad_backlight_update_status, 807a4ecbb8aSIke Panhc }; 808a4ecbb8aSIke Panhc 809a4ecbb8aSIke Panhc static int ideapad_backlight_init(struct ideapad_private *priv) 810a4ecbb8aSIke Panhc { 811a4ecbb8aSIke Panhc struct backlight_device *blightdev; 812a4ecbb8aSIke Panhc struct backlight_properties props; 813a4ecbb8aSIke Panhc unsigned long max, now, power; 814a4ecbb8aSIke Panhc 815331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max)) 816a4ecbb8aSIke Panhc return -EIO; 817331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) 818a4ecbb8aSIke Panhc return -EIO; 819331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) 820a4ecbb8aSIke Panhc return -EIO; 821a4ecbb8aSIke Panhc 822a4ecbb8aSIke Panhc memset(&props, 0, sizeof(struct backlight_properties)); 823a4ecbb8aSIke Panhc props.max_brightness = max; 824a4ecbb8aSIke Panhc props.type = BACKLIGHT_PLATFORM; 825a4ecbb8aSIke Panhc blightdev = backlight_device_register("ideapad", 826a4ecbb8aSIke Panhc &priv->platform_device->dev, 827a4ecbb8aSIke Panhc priv, 828a4ecbb8aSIke Panhc &ideapad_backlight_ops, 829a4ecbb8aSIke Panhc &props); 830a4ecbb8aSIke Panhc if (IS_ERR(blightdev)) { 831a4ecbb8aSIke Panhc pr_err("Could not register backlight device\n"); 832a4ecbb8aSIke Panhc return PTR_ERR(blightdev); 833a4ecbb8aSIke Panhc } 834a4ecbb8aSIke Panhc 835a4ecbb8aSIke Panhc priv->blightdev = blightdev; 836a4ecbb8aSIke Panhc blightdev->props.brightness = now; 837a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 838a4ecbb8aSIke Panhc backlight_update_status(blightdev); 839a4ecbb8aSIke Panhc 840a4ecbb8aSIke Panhc return 0; 841a4ecbb8aSIke Panhc } 842a4ecbb8aSIke Panhc 843a4ecbb8aSIke Panhc static void ideapad_backlight_exit(struct ideapad_private *priv) 844a4ecbb8aSIke Panhc { 845a4ecbb8aSIke Panhc backlight_device_unregister(priv->blightdev); 846a4ecbb8aSIke Panhc priv->blightdev = NULL; 847a4ecbb8aSIke Panhc } 848a4ecbb8aSIke Panhc 849a4ecbb8aSIke Panhc static void ideapad_backlight_notify_power(struct ideapad_private *priv) 850a4ecbb8aSIke Panhc { 851a4ecbb8aSIke Panhc unsigned long power; 852a4ecbb8aSIke Panhc struct backlight_device *blightdev = priv->blightdev; 853a4ecbb8aSIke Panhc 854d4afc775SRene Bollford if (!blightdev) 855d4afc775SRene Bollford return; 856331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) 857a4ecbb8aSIke Panhc return; 858a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 859a4ecbb8aSIke Panhc } 860a4ecbb8aSIke Panhc 861a4ecbb8aSIke Panhc static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) 862a4ecbb8aSIke Panhc { 863a4ecbb8aSIke Panhc unsigned long now; 864a4ecbb8aSIke Panhc 865a4ecbb8aSIke Panhc /* if we control brightness via acpi video driver */ 866a4ecbb8aSIke Panhc if (priv->blightdev == NULL) { 867331e0ea2SZhang Rui read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 868a4ecbb8aSIke Panhc return; 869a4ecbb8aSIke Panhc } 870a4ecbb8aSIke Panhc 871a4ecbb8aSIke Panhc backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); 872a4ecbb8aSIke Panhc } 873a4ecbb8aSIke Panhc 874a4ecbb8aSIke Panhc /* 875a4b5a279SIke Panhc * module init/exit 876a4b5a279SIke Panhc */ 87775a11f11SZhang Rui static void ideapad_sync_touchpad_state(struct ideapad_private *priv) 87807a4a4fcSMaxim Mikityanskiy { 87907a4a4fcSMaxim Mikityanskiy unsigned long value; 88007a4a4fcSMaxim Mikityanskiy 88107a4a4fcSMaxim Mikityanskiy /* Without reading from EC touchpad LED doesn't switch state */ 88275a11f11SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { 88307a4a4fcSMaxim Mikityanskiy /* Some IdeaPads don't really turn off touchpad - they only 88407a4a4fcSMaxim Mikityanskiy * switch the LED state. We (de)activate KBC AUX port to turn 88507a4a4fcSMaxim Mikityanskiy * touchpad off and on. We send KEY_TOUCHPAD_OFF and 88607a4a4fcSMaxim Mikityanskiy * KEY_TOUCHPAD_ON to not to get out of sync with LED */ 88707a4a4fcSMaxim Mikityanskiy unsigned char param; 88807a4a4fcSMaxim Mikityanskiy i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : 88907a4a4fcSMaxim Mikityanskiy I8042_CMD_AUX_DISABLE); 89007a4a4fcSMaxim Mikityanskiy ideapad_input_report(priv, value ? 67 : 66); 89107a4a4fcSMaxim Mikityanskiy } 89207a4a4fcSMaxim Mikityanskiy } 89307a4a4fcSMaxim Mikityanskiy 894b5c37b79SZhang Rui static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) 89557ac3b05SIke Panhc { 896b5c37b79SZhang Rui struct ideapad_private *priv = data; 89757ac3b05SIke Panhc unsigned long vpc1, vpc2, vpc_bit; 89857ac3b05SIke Panhc 8992be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) 90057ac3b05SIke Panhc return; 9012be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) 90257ac3b05SIke Panhc return; 90357ac3b05SIke Panhc 90457ac3b05SIke Panhc vpc1 = (vpc2 << 8) | vpc1; 90557ac3b05SIke Panhc for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) { 90657ac3b05SIke Panhc if (test_bit(vpc_bit, &vpc1)) { 907a4ecbb8aSIke Panhc switch (vpc_bit) { 908a4ecbb8aSIke Panhc case 9: 909923de84aSIke Panhc ideapad_sync_rfk_state(priv); 910a4ecbb8aSIke Panhc break; 91120a769c1SIke Panhc case 13: 912296f9fe0SMaxim Mikityanskiy case 11: 91348f67d62SAlex Hung case 8: 914296f9fe0SMaxim Mikityanskiy case 7: 91520a769c1SIke Panhc case 6: 91620a769c1SIke Panhc ideapad_input_report(priv, vpc_bit); 91720a769c1SIke Panhc break; 91807a4a4fcSMaxim Mikityanskiy case 5: 91975a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 92007a4a4fcSMaxim Mikityanskiy break; 921a4ecbb8aSIke Panhc case 4: 922a4ecbb8aSIke Panhc ideapad_backlight_notify_brightness(priv); 923a4ecbb8aSIke Panhc break; 924f43d9ec0SIke Panhc case 3: 925f43d9ec0SIke Panhc ideapad_input_novokey(priv); 926f43d9ec0SIke Panhc break; 927a4ecbb8aSIke Panhc case 2: 928a4ecbb8aSIke Panhc ideapad_backlight_notify_power(priv); 929a4ecbb8aSIke Panhc break; 930296f9fe0SMaxim Mikityanskiy case 0: 931296f9fe0SMaxim Mikityanskiy ideapad_check_special_buttons(priv); 932296f9fe0SMaxim Mikityanskiy break; 9333cfd956bSHao Wei Tee case 1: 9343cfd956bSHao Wei Tee /* Some IdeaPads report event 1 every ~20 9353cfd956bSHao Wei Tee * seconds while on battery power; some 9363cfd956bSHao Wei Tee * report this when changing to/from tablet 9373cfd956bSHao Wei Tee * mode. Squelch this event. 9383cfd956bSHao Wei Tee */ 9393cfd956bSHao Wei Tee break; 940a4ecbb8aSIke Panhc default: 94120a769c1SIke Panhc pr_info("Unknown event: %lu\n", vpc_bit); 94257ac3b05SIke Panhc } 94357ac3b05SIke Panhc } 94457ac3b05SIke Panhc } 945a4ecbb8aSIke Panhc } 94657ac3b05SIke Panhc 94774caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 94874caab99SArnd Bergmann static void ideapad_wmi_notify(u32 value, void *context) 94974caab99SArnd Bergmann { 95074caab99SArnd Bergmann switch (value) { 95174caab99SArnd Bergmann case 128: 95274caab99SArnd Bergmann ideapad_input_report(context, value); 95374caab99SArnd Bergmann break; 95474caab99SArnd Bergmann default: 95574caab99SArnd Bergmann pr_info("Unknown WMI event %u\n", value); 95674caab99SArnd Bergmann } 95774caab99SArnd Bergmann } 95874caab99SArnd Bergmann #endif 95974caab99SArnd Bergmann 960ce363c2bSHans de Goede /* 961ce363c2bSHans de Goede * Some ideapads don't have a hardware rfkill switch, reading VPCCMD_R_RF 962ce363c2bSHans de Goede * always results in 0 on these models, causing ideapad_laptop to wrongly 963ce363c2bSHans de Goede * report all radios as hardware-blocked. 964ce363c2bSHans de Goede */ 965b3d94d70SMathias Krause static const struct dmi_system_id no_hw_rfkill_list[] = { 96685093f79SHans de Goede { 9679b071a43SPhilippe Coval .ident = "Lenovo G40-30", 9689b071a43SPhilippe Coval .matches = { 9699b071a43SPhilippe Coval DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 9709b071a43SPhilippe Coval DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G40-30"), 9719b071a43SPhilippe Coval }, 9729b071a43SPhilippe Coval }, 9739b071a43SPhilippe Coval { 9744fa9dabcSDmitry Tunin .ident = "Lenovo G50-30", 9754fa9dabcSDmitry Tunin .matches = { 9764fa9dabcSDmitry Tunin DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 9774fa9dabcSDmitry Tunin DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"), 9784fa9dabcSDmitry Tunin }, 9794fa9dabcSDmitry Tunin }, 9804fa9dabcSDmitry Tunin { 981710c059cSYang Jiaxun .ident = "Lenovo V310-14IKB", 982710c059cSYang Jiaxun .matches = { 983710c059cSYang Jiaxun DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 984710c059cSYang Jiaxun DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-14IKB"), 985710c059cSYang Jiaxun }, 986710c059cSYang Jiaxun }, 987710c059cSYang Jiaxun { 988710c059cSYang Jiaxun .ident = "Lenovo V310-14ISK", 989710c059cSYang Jiaxun .matches = { 990710c059cSYang Jiaxun DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 991710c059cSYang Jiaxun DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-14ISK"), 992710c059cSYang Jiaxun }, 993710c059cSYang Jiaxun }, 994710c059cSYang Jiaxun { 995710c059cSYang Jiaxun .ident = "Lenovo V310-15IKB", 996710c059cSYang Jiaxun .matches = { 997710c059cSYang Jiaxun DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 998710c059cSYang Jiaxun DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-15IKB"), 999710c059cSYang Jiaxun }, 1000710c059cSYang Jiaxun }, 1001710c059cSYang Jiaxun { 1002ccc7179fSAndy Shevchenko .ident = "Lenovo V310-15ISK", 1003ccc7179fSAndy Shevchenko .matches = { 1004ccc7179fSAndy Shevchenko DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1005ccc7179fSAndy Shevchenko DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-15ISK"), 1006ccc7179fSAndy Shevchenko }, 1007ccc7179fSAndy Shevchenko }, 1008ccc7179fSAndy Shevchenko { 10090df4b805SSven Eckelmann .ident = "Lenovo V510-15IKB", 10100df4b805SSven Eckelmann .matches = { 10110df4b805SSven Eckelmann DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 10120df4b805SSven Eckelmann DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V510-15IKB"), 10130df4b805SSven Eckelmann }, 10140df4b805SSven Eckelmann }, 10150df4b805SSven Eckelmann { 1016710c059cSYang Jiaxun .ident = "Lenovo ideapad 300-15IBR", 1017710c059cSYang Jiaxun .matches = { 1018710c059cSYang Jiaxun DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1019710c059cSYang Jiaxun DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300-15IBR"), 1020710c059cSYang Jiaxun }, 1021710c059cSYang Jiaxun }, 1022710c059cSYang Jiaxun { 1023710c059cSYang Jiaxun .ident = "Lenovo ideapad 300-15IKB", 1024710c059cSYang Jiaxun .matches = { 1025710c059cSYang Jiaxun DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1026710c059cSYang Jiaxun DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300-15IKB"), 1027710c059cSYang Jiaxun }, 1028710c059cSYang Jiaxun }, 1029710c059cSYang Jiaxun { 1030710c059cSYang Jiaxun .ident = "Lenovo ideapad 300S-11IBR", 1031710c059cSYang Jiaxun .matches = { 1032710c059cSYang Jiaxun DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1033710c059cSYang Jiaxun DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300S-11BR"), 1034710c059cSYang Jiaxun }, 1035710c059cSYang Jiaxun }, 1036710c059cSYang Jiaxun { 1037710c059cSYang Jiaxun .ident = "Lenovo ideapad 310-15ABR", 1038710c059cSYang Jiaxun .matches = { 1039710c059cSYang Jiaxun DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1040710c059cSYang Jiaxun DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15ABR"), 1041710c059cSYang Jiaxun }, 1042710c059cSYang Jiaxun }, 1043710c059cSYang Jiaxun { 1044710c059cSYang Jiaxun .ident = "Lenovo ideapad 310-15IAP", 1045710c059cSYang Jiaxun .matches = { 1046710c059cSYang Jiaxun DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1047710c059cSYang Jiaxun DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15IAP"), 1048710c059cSYang Jiaxun }, 1049710c059cSYang Jiaxun }, 1050710c059cSYang Jiaxun { 10511f3bc53dSSven Rebhan .ident = "Lenovo ideapad 310-15IKB", 10521f3bc53dSSven Rebhan .matches = { 10531f3bc53dSSven Rebhan DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 10541f3bc53dSSven Rebhan DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15IKB"), 10551f3bc53dSSven Rebhan }, 10561f3bc53dSSven Rebhan }, 10571f3bc53dSSven Rebhan { 1058710c059cSYang Jiaxun .ident = "Lenovo ideapad 310-15ISK", 1059710c059cSYang Jiaxun .matches = { 1060710c059cSYang Jiaxun DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1061710c059cSYang Jiaxun DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15ISK"), 1062710c059cSYang Jiaxun }, 1063710c059cSYang Jiaxun }, 1064710c059cSYang Jiaxun { 1065710c059cSYang Jiaxun .ident = "Lenovo ideapad Y700-14ISK", 1066710c059cSYang Jiaxun .matches = { 1067710c059cSYang Jiaxun DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1068710c059cSYang Jiaxun DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-14ISK"), 1069710c059cSYang Jiaxun }, 1070710c059cSYang Jiaxun }, 1071710c059cSYang Jiaxun { 1072e2970468Svelemas .ident = "Lenovo ideapad Y700-15ACZ", 1073e2970468Svelemas .matches = { 1074e2970468Svelemas DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1075e2970468Svelemas DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-15ACZ"), 1076e2970468Svelemas }, 1077e2970468Svelemas }, 1078e2970468Svelemas { 10794db9675dSJohn Dahlstrom .ident = "Lenovo ideapad Y700-15ISK", 10804db9675dSJohn Dahlstrom .matches = { 10814db9675dSJohn Dahlstrom DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 10824db9675dSJohn Dahlstrom DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-15ISK"), 10834db9675dSJohn Dahlstrom }, 10844db9675dSJohn Dahlstrom }, 10854db9675dSJohn Dahlstrom { 10864db9675dSJohn Dahlstrom .ident = "Lenovo ideapad Y700 Touch-15ISK", 10874db9675dSJohn Dahlstrom .matches = { 10884db9675dSJohn Dahlstrom DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 10894db9675dSJohn Dahlstrom DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700 Touch-15ISK"), 10904db9675dSJohn Dahlstrom }, 10914db9675dSJohn Dahlstrom }, 10924db9675dSJohn Dahlstrom { 1093edde316aSJosh Boyer .ident = "Lenovo ideapad Y700-17ISK", 1094edde316aSJosh Boyer .matches = { 1095edde316aSJosh Boyer DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1096edde316aSJosh Boyer DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-17ISK"), 1097edde316aSJosh Boyer }, 1098edde316aSJosh Boyer }, 1099edde316aSJosh Boyer { 11005d9f40b5SOlle Liljenzin .ident = "Lenovo Legion Y520-15IKBN", 11015d9f40b5SOlle Liljenzin .matches = { 11025d9f40b5SOlle Liljenzin DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 11035d9f40b5SOlle Liljenzin DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y520-15IKBN"), 11045d9f40b5SOlle Liljenzin }, 11055d9f40b5SOlle Liljenzin }, 11065d9f40b5SOlle Liljenzin { 1107b2f2fe20SOlle Liljenzin .ident = "Lenovo Legion Y720-15IKBN", 1108b2f2fe20SOlle Liljenzin .matches = { 1109b2f2fe20SOlle Liljenzin DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1110b2f2fe20SOlle Liljenzin DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y720-15IKBN"), 1111b2f2fe20SOlle Liljenzin }, 1112b2f2fe20SOlle Liljenzin }, 1113b2f2fe20SOlle Liljenzin { 1114ce363c2bSHans de Goede .ident = "Lenovo Yoga 2 11 / 13 / Pro", 111585093f79SHans de Goede .matches = { 111685093f79SHans de Goede DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1117ce363c2bSHans de Goede DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 2"), 111885093f79SHans de Goede }, 111985093f79SHans de Goede }, 1120725c7f61SStephan Mueller { 11216d212b8aSSebastian Krzyszkowiak .ident = "Lenovo Yoga 2 11 / 13 / Pro", 11226d212b8aSSebastian Krzyszkowiak .matches = { 11236d212b8aSSebastian Krzyszkowiak DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 11246d212b8aSSebastian Krzyszkowiak DMI_MATCH(DMI_BOARD_NAME, "Yoga2"), 11256d212b8aSSebastian Krzyszkowiak }, 11266d212b8aSSebastian Krzyszkowiak }, 11276d212b8aSSebastian Krzyszkowiak { 1128c789fffcSArnd Bergmann .ident = "Lenovo Yoga 3 1170 / 1470", 1129c789fffcSArnd Bergmann .matches = { 1130c789fffcSArnd Bergmann DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1131c789fffcSArnd Bergmann DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 3"), 1132c789fffcSArnd Bergmann }, 1133c789fffcSArnd Bergmann }, 1134c789fffcSArnd Bergmann { 1135725c7f61SStephan Mueller .ident = "Lenovo Yoga 3 Pro 1370", 1136725c7f61SStephan Mueller .matches = { 1137725c7f61SStephan Mueller DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1138c789fffcSArnd Bergmann DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 3"), 1139725c7f61SStephan Mueller }, 1140725c7f61SStephan Mueller }, 1141f71c882dSHans de Goede { 11426b31de3eSJosh Boyer .ident = "Lenovo Yoga 700", 11436b31de3eSJosh Boyer .matches = { 11446b31de3eSJosh Boyer DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 11456b31de3eSJosh Boyer DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 700"), 11466b31de3eSJosh Boyer }, 11476b31de3eSJosh Boyer }, 11486b31de3eSJosh Boyer { 1149f71c882dSHans de Goede .ident = "Lenovo Yoga 900", 1150f71c882dSHans de Goede .matches = { 1151f71c882dSHans de Goede DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1152f71c882dSHans de Goede DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 900"), 1153f71c882dSHans de Goede }, 1154f71c882dSHans de Goede }, 115540c30bbfSBrian Masney { 1156446647d4SMika Westerberg .ident = "Lenovo Yoga 900", 1157446647d4SMika Westerberg .matches = { 1158446647d4SMika Westerberg DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1159446647d4SMika Westerberg DMI_MATCH(DMI_BOARD_NAME, "VIUU4"), 1160446647d4SMika Westerberg }, 1161446647d4SMika Westerberg }, 1162446647d4SMika Westerberg { 116340c30bbfSBrian Masney .ident = "Lenovo YOGA 910-13IKB", 116440c30bbfSBrian Masney .matches = { 116540c30bbfSBrian Masney DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 116640c30bbfSBrian Masney DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 910-13IKB"), 116740c30bbfSBrian Masney }, 116840c30bbfSBrian Masney }, 1169b231669cSPhilipp Hug { 1170b231669cSPhilipp Hug .ident = "Lenovo YOGA 920-13IKB", 1171b231669cSPhilipp Hug .matches = { 1172b231669cSPhilipp Hug DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1173b231669cSPhilipp Hug DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 920-13IKB"), 1174b231669cSPhilipp Hug }, 1175b231669cSPhilipp Hug }, 117685093f79SHans de Goede {} 117785093f79SHans de Goede }; 117885093f79SHans de Goede 1179b5c37b79SZhang Rui static int ideapad_acpi_add(struct platform_device *pdev) 1180b5c37b79SZhang Rui { 1181b5c37b79SZhang Rui int ret, i; 1182b5c37b79SZhang Rui int cfg; 1183b5c37b79SZhang Rui struct ideapad_private *priv; 1184b5c37b79SZhang Rui struct acpi_device *adev; 1185b5c37b79SZhang Rui 1186b5c37b79SZhang Rui ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); 1187b5c37b79SZhang Rui if (ret) 1188b5c37b79SZhang Rui return -ENODEV; 1189b5c37b79SZhang Rui 1190b5c37b79SZhang Rui if (read_method_int(adev->handle, "_CFG", &cfg)) 1191b5c37b79SZhang Rui return -ENODEV; 1192b5c37b79SZhang Rui 1193b3facd7bSHimangi Saraogi priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 1194b5c37b79SZhang Rui if (!priv) 1195b5c37b79SZhang Rui return -ENOMEM; 1196b5c37b79SZhang Rui 1197b5c37b79SZhang Rui dev_set_drvdata(&pdev->dev, priv); 1198b5c37b79SZhang Rui priv->cfg = cfg; 1199b5c37b79SZhang Rui priv->adev = adev; 1200b5c37b79SZhang Rui priv->platform_device = pdev; 1201ce363c2bSHans de Goede priv->has_hw_rfkill_switch = !dmi_check_system(no_hw_rfkill_list); 1202b5c37b79SZhang Rui 1203b5c37b79SZhang Rui ret = ideapad_sysfs_init(priv); 1204b5c37b79SZhang Rui if (ret) 1205b3facd7bSHimangi Saraogi return ret; 1206b5c37b79SZhang Rui 1207b5c37b79SZhang Rui ret = ideapad_debugfs_init(priv); 1208b5c37b79SZhang Rui if (ret) 1209b5c37b79SZhang Rui goto debugfs_failed; 1210b5c37b79SZhang Rui 1211b5c37b79SZhang Rui ret = ideapad_input_init(priv); 1212b5c37b79SZhang Rui if (ret) 1213b5c37b79SZhang Rui goto input_failed; 1214b5c37b79SZhang Rui 1215ce363c2bSHans de Goede /* 1216ce363c2bSHans de Goede * On some models without a hw-switch (the yoga 2 13 at least) 1217ce363c2bSHans de Goede * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. 1218ce363c2bSHans de Goede */ 1219ce363c2bSHans de Goede if (!priv->has_hw_rfkill_switch) 1220ce363c2bSHans de Goede write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); 1221ce363c2bSHans de Goede 122285093f79SHans de Goede for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1223b5c37b79SZhang Rui if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) 1224b5c37b79SZhang Rui ideapad_register_rfkill(priv, i); 1225ce363c2bSHans de Goede 1226b5c37b79SZhang Rui ideapad_sync_rfk_state(priv); 1227b5c37b79SZhang Rui ideapad_sync_touchpad_state(priv); 1228b5c37b79SZhang Rui 122926bff5f0SHans de Goede if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { 1230b5c37b79SZhang Rui ret = ideapad_backlight_init(priv); 1231b5c37b79SZhang Rui if (ret && ret != -ENODEV) 1232b5c37b79SZhang Rui goto backlight_failed; 1233b5c37b79SZhang Rui } 1234b5c37b79SZhang Rui ret = acpi_install_notify_handler(adev->handle, 1235b5c37b79SZhang Rui ACPI_DEVICE_NOTIFY, ideapad_acpi_notify, priv); 1236b5c37b79SZhang Rui if (ret) 1237b5c37b79SZhang Rui goto notification_failed; 12382d98e0b9SArnd Bergmann 123974caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 12402d98e0b9SArnd Bergmann for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { 12412d98e0b9SArnd Bergmann ret = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], 12422d98e0b9SArnd Bergmann ideapad_wmi_notify, priv); 12432d98e0b9SArnd Bergmann if (ret == AE_OK) { 12442d98e0b9SArnd Bergmann priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; 12452d98e0b9SArnd Bergmann break; 12462d98e0b9SArnd Bergmann } 12472d98e0b9SArnd Bergmann } 124874caab99SArnd Bergmann if (ret != AE_OK && ret != AE_NOT_EXIST) 124974caab99SArnd Bergmann goto notification_failed_wmi; 125074caab99SArnd Bergmann #endif 1251b5c37b79SZhang Rui 1252b5c37b79SZhang Rui return 0; 125374caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 125474caab99SArnd Bergmann notification_failed_wmi: 125574caab99SArnd Bergmann acpi_remove_notify_handler(priv->adev->handle, 125674caab99SArnd Bergmann ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); 125774caab99SArnd Bergmann #endif 1258b5c37b79SZhang Rui notification_failed: 1259b5c37b79SZhang Rui ideapad_backlight_exit(priv); 1260b5c37b79SZhang Rui backlight_failed: 1261b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1262b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 1263b5c37b79SZhang Rui ideapad_input_exit(priv); 1264b5c37b79SZhang Rui input_failed: 1265b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1266b5c37b79SZhang Rui debugfs_failed: 1267b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 1268b5c37b79SZhang Rui return ret; 1269b5c37b79SZhang Rui } 1270b5c37b79SZhang Rui 1271b5c37b79SZhang Rui static int ideapad_acpi_remove(struct platform_device *pdev) 1272b5c37b79SZhang Rui { 1273b5c37b79SZhang Rui struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); 1274b5c37b79SZhang Rui int i; 1275b5c37b79SZhang Rui 127674caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 12772d98e0b9SArnd Bergmann if (priv->fnesc_guid) 12782d98e0b9SArnd Bergmann wmi_remove_notify_handler(priv->fnesc_guid); 127974caab99SArnd Bergmann #endif 1280b5c37b79SZhang Rui acpi_remove_notify_handler(priv->adev->handle, 1281b5c37b79SZhang Rui ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); 1282b5c37b79SZhang Rui ideapad_backlight_exit(priv); 1283b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1284b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 1285b5c37b79SZhang Rui ideapad_input_exit(priv); 1286b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1287b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 1288b5c37b79SZhang Rui dev_set_drvdata(&pdev->dev, NULL); 1289b5c37b79SZhang Rui 1290b5c37b79SZhang Rui return 0; 1291b5c37b79SZhang Rui } 1292b5c37b79SZhang Rui 129311fa8da5SZhang Rui #ifdef CONFIG_PM_SLEEP 129407a4a4fcSMaxim Mikityanskiy static int ideapad_acpi_resume(struct device *device) 129507a4a4fcSMaxim Mikityanskiy { 129675a11f11SZhang Rui struct ideapad_private *priv; 129775a11f11SZhang Rui 129875a11f11SZhang Rui if (!device) 129975a11f11SZhang Rui return -EINVAL; 130075a11f11SZhang Rui priv = dev_get_drvdata(device); 130175a11f11SZhang Rui 130275a11f11SZhang Rui ideapad_sync_rfk_state(priv); 130375a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 130407a4a4fcSMaxim Mikityanskiy return 0; 130507a4a4fcSMaxim Mikityanskiy } 1306b5c37b79SZhang Rui #endif 130707a4a4fcSMaxim Mikityanskiy static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); 130807a4a4fcSMaxim Mikityanskiy 1309b5c37b79SZhang Rui static const struct acpi_device_id ideapad_device_ids[] = { 1310b5c37b79SZhang Rui { "VPC2004", 0}, 1311b5c37b79SZhang Rui { "", 0}, 131257ac3b05SIke Panhc }; 1313b5c37b79SZhang Rui MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 1314b5c37b79SZhang Rui 1315b5c37b79SZhang Rui static struct platform_driver ideapad_acpi_driver = { 1316b5c37b79SZhang Rui .probe = ideapad_acpi_add, 1317b5c37b79SZhang Rui .remove = ideapad_acpi_remove, 1318b5c37b79SZhang Rui .driver = { 1319b5c37b79SZhang Rui .name = "ideapad_acpi", 1320b5c37b79SZhang Rui .pm = &ideapad_pm, 1321b5c37b79SZhang Rui .acpi_match_table = ACPI_PTR(ideapad_device_ids), 1322b5c37b79SZhang Rui }, 1323b5c37b79SZhang Rui }; 1324b5c37b79SZhang Rui 1325b5c37b79SZhang Rui module_platform_driver(ideapad_acpi_driver); 132657ac3b05SIke Panhc 132757ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 132857ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 132957ac3b05SIke Panhc MODULE_LICENSE("GPL"); 1330