116216333SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 257ac3b05SIke Panhc /* 3a4b5a279SIke Panhc * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras 457ac3b05SIke Panhc * 557ac3b05SIke Panhc * Copyright © 2010 Intel Corporation 657ac3b05SIke Panhc * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> 757ac3b05SIke Panhc */ 857ac3b05SIke Panhc 99ab23989SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 109ab23989SJoe Perches 118b48463fSLv Zheng #include <linux/acpi.h> 127d38f034SBarnabás Pőcze #include <linux/backlight.h> 130c4915b6SBarnabás Pőcze #include <linux/bitops.h> 14503325f8SBarnabás Pőcze #include <linux/bug.h> 157d38f034SBarnabás Pőcze #include <linux/debugfs.h> 167d38f034SBarnabás Pőcze #include <linux/device.h> 177d38f034SBarnabás Pőcze #include <linux/dmi.h> 187d38f034SBarnabás Pőcze #include <linux/fb.h> 197d38f034SBarnabás Pőcze #include <linux/i8042.h> 207d38f034SBarnabás Pőcze #include <linux/init.h> 21f63409aeSIke Panhc #include <linux/input.h> 22f63409aeSIke Panhc #include <linux/input/sparse-keymap.h> 2340e0447dSBarnabás Pőcze #include <linux/jiffies.h> 247d38f034SBarnabás Pőcze #include <linux/kernel.h> 25503325f8SBarnabás Pőcze #include <linux/leds.h> 267d38f034SBarnabás Pőcze #include <linux/module.h> 277d38f034SBarnabás Pőcze #include <linux/platform_device.h> 287d38f034SBarnabás Pőcze #include <linux/platform_profile.h> 297d38f034SBarnabás Pőcze #include <linux/rfkill.h> 30773e3206SIke Panhc #include <linux/seq_file.h> 31d6b50889SBarnabás Pőcze #include <linux/sysfs.h> 327d38f034SBarnabás Pőcze #include <linux/types.h> 337d38f034SBarnabás Pőcze 3426bff5f0SHans de Goede #include <acpi/video.h> 3557ac3b05SIke Panhc 36503325f8SBarnabás Pőcze #include <dt-bindings/leds/common.h> 37503325f8SBarnabás Pőcze 3865c7713aSBarnabás Pőcze #define IDEAPAD_RFKILL_DEV_NUM 3 3957ac3b05SIke Panhc 4074caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 412d98e0b9SArnd Bergmann static const char *const ideapad_wmi_fnesc_events[] = { 422d98e0b9SArnd Bergmann "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */ 432d98e0b9SArnd Bergmann "56322276-8493-4CE8-A783-98C991274F5E", /* Yoga 700 */ 443ae86d2dSMeng Dong "8FC0DE0C-B4E4-43FD-B0F3-8871711C1294", /* Legion 5 */ 452d98e0b9SArnd Bergmann }; 4674caab99SArnd Bergmann #endif 4774caab99SArnd Bergmann 482be1dc21SIke Panhc enum { 490b765671SBarnabás Pőcze CFG_CAP_BT_BIT = 16, 500b765671SBarnabás Pőcze CFG_CAP_3G_BIT = 17, 510b765671SBarnabás Pőcze CFG_CAP_WIFI_BIT = 18, 520b765671SBarnabás Pőcze CFG_CAP_CAM_BIT = 19, 53b3ed1b7fSBarnabás Pőcze CFG_CAP_TOUCHPAD_BIT = 30, 540b765671SBarnabás Pőcze }; 550b765671SBarnabás Pőcze 560b765671SBarnabás Pőcze enum { 570b765671SBarnabás Pőcze GBMD_CONSERVATION_STATE_BIT = 5, 580b765671SBarnabás Pőcze }; 590b765671SBarnabás Pőcze 600b765671SBarnabás Pőcze enum { 61b09aaa3fSBarnabás Pőcze SBMC_CONSERVATION_ON = 3, 62b09aaa3fSBarnabás Pőcze SBMC_CONSERVATION_OFF = 5, 630b765671SBarnabás Pőcze }; 640b765671SBarnabás Pőcze 650b765671SBarnabás Pőcze enum { 66503325f8SBarnabás Pőcze HALS_KBD_BL_SUPPORT_BIT = 4, 67503325f8SBarnabás Pőcze HALS_KBD_BL_STATE_BIT = 5, 686b49dea4SBarnabás Pőcze HALS_USB_CHARGING_SUPPORT_BIT = 6, 696b49dea4SBarnabás Pőcze HALS_USB_CHARGING_STATE_BIT = 7, 70392cbf0aSBarnabás Pőcze HALS_FNLOCK_SUPPORT_BIT = 9, 710b765671SBarnabás Pőcze HALS_FNLOCK_STATE_BIT = 10, 72392cbf0aSBarnabás Pőcze HALS_HOTKEYS_PRIMARY_BIT = 11, 730b765671SBarnabás Pőcze }; 740b765671SBarnabás Pőcze 750b765671SBarnabás Pőcze enum { 76503325f8SBarnabás Pőcze SALS_KBD_BL_ON = 0x8, 77503325f8SBarnabás Pőcze SALS_KBD_BL_OFF = 0x9, 786b49dea4SBarnabás Pőcze SALS_USB_CHARGING_ON = 0xa, 796b49dea4SBarnabás Pőcze SALS_USB_CHARGING_OFF = 0xb, 800b765671SBarnabás Pőcze SALS_FNLOCK_ON = 0xe, 810b765671SBarnabás Pőcze SALS_FNLOCK_OFF = 0xf, 82ade50296SHao Wei Tee }; 83ade50296SHao Wei Tee 84ade50296SHao Wei Tee enum { 852be1dc21SIke Panhc VPCCMD_R_VPC1 = 0x10, 862be1dc21SIke Panhc VPCCMD_R_BL_MAX, 872be1dc21SIke Panhc VPCCMD_R_BL, 882be1dc21SIke Panhc VPCCMD_W_BL, 892be1dc21SIke Panhc VPCCMD_R_WIFI, 902be1dc21SIke Panhc VPCCMD_W_WIFI, 912be1dc21SIke Panhc VPCCMD_R_BT, 922be1dc21SIke Panhc VPCCMD_W_BT, 932be1dc21SIke Panhc VPCCMD_R_BL_POWER, 942be1dc21SIke Panhc VPCCMD_R_NOVO, 952be1dc21SIke Panhc VPCCMD_R_VPC2, 962be1dc21SIke Panhc VPCCMD_R_TOUCHPAD, 972be1dc21SIke Panhc VPCCMD_W_TOUCHPAD, 982be1dc21SIke Panhc VPCCMD_R_CAMERA, 992be1dc21SIke Panhc VPCCMD_W_CAMERA, 1002be1dc21SIke Panhc VPCCMD_R_3G, 1012be1dc21SIke Panhc VPCCMD_W_3G, 1022be1dc21SIke Panhc VPCCMD_R_ODD, /* 0x21 */ 1030c7bbeb9SMaxim Mikityanskiy VPCCMD_W_FAN, 1040c7bbeb9SMaxim Mikityanskiy VPCCMD_R_RF, 1052be1dc21SIke Panhc VPCCMD_W_RF, 1060c7bbeb9SMaxim Mikityanskiy VPCCMD_R_FAN = 0x2B, 107296f9fe0SMaxim Mikityanskiy VPCCMD_R_SPECIAL_BUTTONS = 0x31, 1082be1dc21SIke Panhc VPCCMD_W_BL_POWER = 0x33, 1092be1dc21SIke Panhc }; 1102be1dc21SIke Panhc 111eabe5339SJiaxun Yang struct ideapad_dytc_priv { 112eabe5339SJiaxun Yang enum platform_profile_option current_profile; 113eabe5339SJiaxun Yang struct platform_profile_handler pprof; 11465c7713aSBarnabás Pőcze struct mutex mutex; /* protects the DYTC interface */ 115eabe5339SJiaxun Yang struct ideapad_private *priv; 116eabe5339SJiaxun Yang }; 117eabe5339SJiaxun Yang 118331e0ea2SZhang Rui struct ideapad_rfk_priv { 119331e0ea2SZhang Rui int dev; 120331e0ea2SZhang Rui struct ideapad_private *priv; 121331e0ea2SZhang Rui }; 122331e0ea2SZhang Rui 12357ac3b05SIke Panhc struct ideapad_private { 124469f6434SZhang Rui struct acpi_device *adev; 125c1f73658SIke Panhc struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM]; 126331e0ea2SZhang Rui struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM]; 12798ee6919SIke Panhc struct platform_device *platform_device; 128f63409aeSIke Panhc struct input_dev *inputdev; 129a4ecbb8aSIke Panhc struct backlight_device *blightdev; 130eabe5339SJiaxun Yang struct ideapad_dytc_priv *dytc; 131773e3206SIke Panhc struct dentry *debug; 1323371f481SIke Panhc unsigned long cfg; 1332d98e0b9SArnd Bergmann const char *fnesc_guid; 1341c59de4aSBarnabás Pőcze struct { 1351c59de4aSBarnabás Pőcze bool conservation_mode : 1; 1361c59de4aSBarnabás Pőcze bool dytc : 1; 1371c59de4aSBarnabás Pőcze bool fan_mode : 1; 1381c59de4aSBarnabás Pőcze bool fn_lock : 1; 1391c59de4aSBarnabás Pőcze bool hw_rfkill_switch : 1; 140503325f8SBarnabás Pőcze bool kbd_bl : 1; 1411c59de4aSBarnabás Pőcze bool touchpad_ctrl_via_ec : 1; 1426b49dea4SBarnabás Pőcze bool usb_charging : 1; 1431c59de4aSBarnabás Pőcze } features; 144503325f8SBarnabás Pőcze struct { 145503325f8SBarnabás Pőcze bool initialized; 146503325f8SBarnabás Pőcze struct led_classdev led; 147503325f8SBarnabás Pőcze unsigned int last_brightness; 148503325f8SBarnabás Pőcze } kbd_bl; 14957ac3b05SIke Panhc }; 15057ac3b05SIke Panhc 151bfa97b7dSIke Panhc static bool no_bt_rfkill; 152bfa97b7dSIke Panhc module_param(no_bt_rfkill, bool, 0444); 153bfa97b7dSIke Panhc MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); 154bfa97b7dSIke Panhc 15557ac3b05SIke Panhc /* 15657ac3b05SIke Panhc * ACPI Helpers 15757ac3b05SIke Panhc */ 15865c7713aSBarnabás Pőcze #define IDEAPAD_EC_TIMEOUT 200 /* in ms */ 15957ac3b05SIke Panhc 160ff36b0d9SBarnabás Pőcze static int eval_int(acpi_handle handle, const char *name, unsigned long *res) 16157ac3b05SIke Panhc { 16257ac3b05SIke Panhc unsigned long long result; 163ade50296SHao Wei Tee acpi_status status; 164ade50296SHao Wei Tee 165ff36b0d9SBarnabás Pőcze status = acpi_evaluate_integer(handle, (char *)name, NULL, &result); 166ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 167ff36b0d9SBarnabás Pőcze return -EIO; 16865c7713aSBarnabás Pőcze 169ff36b0d9SBarnabás Pőcze *res = result; 17065c7713aSBarnabás Pőcze 171ff36b0d9SBarnabás Pőcze return 0; 172ff36b0d9SBarnabás Pőcze } 173ff36b0d9SBarnabás Pőcze 174ff36b0d9SBarnabás Pőcze static int exec_simple_method(acpi_handle handle, const char *name, unsigned long arg) 175ff36b0d9SBarnabás Pőcze { 176ff36b0d9SBarnabás Pőcze acpi_status status = acpi_execute_simple_method(handle, (char *)name, arg); 177ff36b0d9SBarnabás Pőcze 1787be193e3SBarnabás Pőcze return ACPI_FAILURE(status) ? -EIO : 0; 179ade50296SHao Wei Tee } 180ade50296SHao Wei Tee 181ff36b0d9SBarnabás Pőcze static int eval_gbmd(acpi_handle handle, unsigned long *res) 182eabe5339SJiaxun Yang { 183ff36b0d9SBarnabás Pőcze return eval_int(handle, "GBMD", res); 184ff36b0d9SBarnabás Pőcze } 185ff36b0d9SBarnabás Pőcze 186b09aaa3fSBarnabás Pőcze static int exec_sbmc(acpi_handle handle, unsigned long arg) 187ff36b0d9SBarnabás Pőcze { 188b09aaa3fSBarnabás Pőcze return exec_simple_method(handle, "SBMC", arg); 189ff36b0d9SBarnabás Pőcze } 190ff36b0d9SBarnabás Pőcze 191ff36b0d9SBarnabás Pőcze static int eval_hals(acpi_handle handle, unsigned long *res) 192ff36b0d9SBarnabás Pőcze { 193ff36b0d9SBarnabás Pőcze return eval_int(handle, "HALS", res); 194ff36b0d9SBarnabás Pőcze } 195ff36b0d9SBarnabás Pőcze 196ff36b0d9SBarnabás Pőcze static int exec_sals(acpi_handle handle, unsigned long arg) 197ff36b0d9SBarnabás Pőcze { 198ff36b0d9SBarnabás Pőcze return exec_simple_method(handle, "SALS", arg); 199ff36b0d9SBarnabás Pőcze } 200ff36b0d9SBarnabás Pőcze 201ff36b0d9SBarnabás Pőcze static int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long arg, unsigned long *res) 202ff36b0d9SBarnabás Pőcze { 203eabe5339SJiaxun Yang struct acpi_object_list params; 204ff36b0d9SBarnabás Pőcze unsigned long long result; 205eabe5339SJiaxun Yang union acpi_object in_obj; 206ff36b0d9SBarnabás Pőcze acpi_status status; 207eabe5339SJiaxun Yang 208eabe5339SJiaxun Yang params.count = 1; 209eabe5339SJiaxun Yang params.pointer = &in_obj; 210eabe5339SJiaxun Yang in_obj.type = ACPI_TYPE_INTEGER; 211ff36b0d9SBarnabás Pőcze in_obj.integer.value = arg; 212eabe5339SJiaxun Yang 213ff36b0d9SBarnabás Pőcze status = acpi_evaluate_integer(handle, (char *)name, ¶ms, &result); 214ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 2157be193e3SBarnabás Pőcze return -EIO; 216ff36b0d9SBarnabás Pőcze 217ff36b0d9SBarnabás Pőcze if (res) 218ff36b0d9SBarnabás Pőcze *res = result; 219ff36b0d9SBarnabás Pőcze 220eabe5339SJiaxun Yang return 0; 221eabe5339SJiaxun Yang } 222eabe5339SJiaxun Yang 223ff36b0d9SBarnabás Pőcze static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res) 22457ac3b05SIke Panhc { 225ff36b0d9SBarnabás Pőcze return eval_int_with_arg(handle, "DYTC", cmd, res); 22657ac3b05SIke Panhc } 22757ac3b05SIke Panhc 228ff36b0d9SBarnabás Pőcze static int eval_vpcr(acpi_handle handle, unsigned long cmd, unsigned long *res) 229ff36b0d9SBarnabás Pőcze { 230ff36b0d9SBarnabás Pőcze return eval_int_with_arg(handle, "VPCR", cmd, res); 231ff36b0d9SBarnabás Pőcze } 232ff36b0d9SBarnabás Pőcze 233ff36b0d9SBarnabás Pőcze static int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data) 23457ac3b05SIke Panhc { 23557ac3b05SIke Panhc struct acpi_object_list params; 23657ac3b05SIke Panhc union acpi_object in_obj[2]; 23757ac3b05SIke Panhc acpi_status status; 23857ac3b05SIke Panhc 23957ac3b05SIke Panhc params.count = 2; 24057ac3b05SIke Panhc params.pointer = in_obj; 24157ac3b05SIke Panhc in_obj[0].type = ACPI_TYPE_INTEGER; 24257ac3b05SIke Panhc in_obj[0].integer.value = cmd; 24357ac3b05SIke Panhc in_obj[1].type = ACPI_TYPE_INTEGER; 24457ac3b05SIke Panhc in_obj[1].integer.value = data; 24557ac3b05SIke Panhc 24657ac3b05SIke Panhc status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); 247ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 2487be193e3SBarnabás Pőcze return -EIO; 24965c7713aSBarnabás Pőcze 25057ac3b05SIke Panhc return 0; 25157ac3b05SIke Panhc } 25257ac3b05SIke Panhc 253ff36b0d9SBarnabás Pőcze static int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *data) 25457ac3b05SIke Panhc { 255ff36b0d9SBarnabás Pőcze unsigned long end_jiffies, val; 256ff36b0d9SBarnabás Pőcze int err; 25757ac3b05SIke Panhc 258ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 1, cmd); 2597be193e3SBarnabás Pőcze if (err) 2607be193e3SBarnabás Pőcze return err; 26157ac3b05SIke Panhc 26240e0447dSBarnabás Pőcze end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; 26340e0447dSBarnabás Pőcze 26440e0447dSBarnabás Pőcze while (time_before(jiffies, end_jiffies)) { 26557ac3b05SIke Panhc schedule(); 26665c7713aSBarnabás Pőcze 267ff36b0d9SBarnabás Pőcze err = eval_vpcr(handle, 1, &val); 2687be193e3SBarnabás Pőcze if (err) 2697be193e3SBarnabás Pőcze return err; 27065c7713aSBarnabás Pőcze 271ff36b0d9SBarnabás Pőcze if (val == 0) 272ff36b0d9SBarnabás Pőcze return eval_vpcr(handle, 0, data); 27357ac3b05SIke Panhc } 27465c7713aSBarnabás Pőcze 275654324c4SBarnabás Pőcze acpi_handle_err(handle, "timeout in %s\n", __func__); 27665c7713aSBarnabás Pőcze 2777be193e3SBarnabás Pőcze return -ETIMEDOUT; 27857ac3b05SIke Panhc } 27957ac3b05SIke Panhc 280ff36b0d9SBarnabás Pőcze static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long data) 28157ac3b05SIke Panhc { 282ff36b0d9SBarnabás Pőcze unsigned long end_jiffies, val; 283ff36b0d9SBarnabás Pőcze int err; 28457ac3b05SIke Panhc 285ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 0, data); 2867be193e3SBarnabás Pőcze if (err) 2877be193e3SBarnabás Pőcze return err; 28865c7713aSBarnabás Pőcze 289ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 1, cmd); 2907be193e3SBarnabás Pőcze if (err) 2917be193e3SBarnabás Pőcze return err; 29257ac3b05SIke Panhc 29340e0447dSBarnabás Pőcze end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; 29440e0447dSBarnabás Pőcze 29540e0447dSBarnabás Pőcze while (time_before(jiffies, end_jiffies)) { 29657ac3b05SIke Panhc schedule(); 29765c7713aSBarnabás Pőcze 298ff36b0d9SBarnabás Pőcze err = eval_vpcr(handle, 1, &val); 2997be193e3SBarnabás Pőcze if (err) 3007be193e3SBarnabás Pőcze return err; 30165c7713aSBarnabás Pőcze 30257ac3b05SIke Panhc if (val == 0) 30357ac3b05SIke Panhc return 0; 30457ac3b05SIke Panhc } 30565c7713aSBarnabás Pőcze 306654324c4SBarnabás Pőcze acpi_handle_err(handle, "timeout in %s\n", __func__); 30765c7713aSBarnabás Pőcze 3087be193e3SBarnabás Pőcze return -ETIMEDOUT; 30957ac3b05SIke Panhc } 31057ac3b05SIke Panhc 311a4b5a279SIke Panhc /* 312773e3206SIke Panhc * debugfs 313773e3206SIke Panhc */ 314773e3206SIke Panhc static int debugfs_status_show(struct seq_file *s, void *data) 315773e3206SIke Panhc { 316331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 317773e3206SIke Panhc unsigned long value; 318773e3206SIke Panhc 319331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) 3207553390dSBarnabás Pőcze seq_printf(s, "Backlight max: %lu\n", value); 321331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) 3227553390dSBarnabás Pőcze seq_printf(s, "Backlight now: %lu\n", value); 323331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) 3247553390dSBarnabás Pőcze seq_printf(s, "BL power value: %s (%lu)\n", value ? "on" : "off", value); 32565c7713aSBarnabás Pőcze 326ade50296SHao Wei Tee seq_puts(s, "=====================\n"); 327ade50296SHao Wei Tee 3287553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) 3297553390dSBarnabás Pőcze seq_printf(s, "Radio status: %s (%lu)\n", value ? "on" : "off", value); 3307553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) 3317553390dSBarnabás Pőcze seq_printf(s, "Wifi status: %s (%lu)\n", value ? "on" : "off", value); 3327553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) 3337553390dSBarnabás Pőcze seq_printf(s, "BT status: %s (%lu)\n", value ? "on" : "off", value); 3347553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) 3357553390dSBarnabás Pőcze seq_printf(s, "3G status: %s (%lu)\n", value ? "on" : "off", value); 33665c7713aSBarnabás Pőcze 3377553390dSBarnabás Pőcze seq_puts(s, "=====================\n"); 3387553390dSBarnabás Pőcze 3397553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) 3407553390dSBarnabás Pőcze seq_printf(s, "Touchpad status: %s (%lu)\n", value ? "on" : "off", value); 3417553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) 3427553390dSBarnabás Pőcze seq_printf(s, "Camera status: %s (%lu)\n", value ? "on" : "off", value); 34365c7713aSBarnabás Pőcze 3447553390dSBarnabás Pőcze seq_puts(s, "=====================\n"); 3457553390dSBarnabás Pőcze 3467553390dSBarnabás Pőcze if (!eval_gbmd(priv->adev->handle, &value)) 3477553390dSBarnabás Pőcze seq_printf(s, "GBMD: %#010lx\n", value); 3487553390dSBarnabás Pőcze if (!eval_hals(priv->adev->handle, &value)) 3497553390dSBarnabás Pőcze seq_printf(s, "HALS: %#010lx\n", value); 350773e3206SIke Panhc 351773e3206SIke Panhc return 0; 352773e3206SIke Panhc } 353334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_status); 354773e3206SIke Panhc 355773e3206SIke Panhc static int debugfs_cfg_show(struct seq_file *s, void *data) 356773e3206SIke Panhc { 357331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 358331e0ea2SZhang Rui 35918227424SBarnabás Pőcze seq_printf(s, "_CFG: %#010lx\n\n", priv->cfg); 36018227424SBarnabás Pőcze 36118227424SBarnabás Pőcze seq_puts(s, "Capabilities:"); 3620b765671SBarnabás Pőcze if (test_bit(CFG_CAP_BT_BIT, &priv->cfg)) 36318227424SBarnabás Pőcze seq_puts(s, " bluetooth"); 3640b765671SBarnabás Pőcze if (test_bit(CFG_CAP_3G_BIT, &priv->cfg)) 36518227424SBarnabás Pőcze seq_puts(s, " 3G"); 3660b765671SBarnabás Pőcze if (test_bit(CFG_CAP_WIFI_BIT, &priv->cfg)) 36718227424SBarnabás Pőcze seq_puts(s, " wifi"); 3680b765671SBarnabás Pőcze if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg)) 36918227424SBarnabás Pőcze seq_puts(s, " camera"); 370b3ed1b7fSBarnabás Pőcze if (test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg)) 37118227424SBarnabás Pőcze seq_puts(s, " touchpad"); 37218227424SBarnabás Pőcze seq_puts(s, "\n"); 37318227424SBarnabás Pőcze 37418227424SBarnabás Pőcze seq_puts(s, "Graphics: "); 37518227424SBarnabás Pőcze switch (priv->cfg & 0x700) { 376773e3206SIke Panhc case 0x100: 37718227424SBarnabás Pőcze seq_puts(s, "Intel"); 378773e3206SIke Panhc break; 379773e3206SIke Panhc case 0x200: 38018227424SBarnabás Pőcze seq_puts(s, "ATI"); 381773e3206SIke Panhc break; 382773e3206SIke Panhc case 0x300: 38318227424SBarnabás Pőcze seq_puts(s, "Nvidia"); 384773e3206SIke Panhc break; 385773e3206SIke Panhc case 0x400: 38618227424SBarnabás Pőcze seq_puts(s, "Intel and ATI"); 387773e3206SIke Panhc break; 388773e3206SIke Panhc case 0x500: 38918227424SBarnabás Pőcze seq_puts(s, "Intel and Nvidia"); 390773e3206SIke Panhc break; 391773e3206SIke Panhc } 39218227424SBarnabás Pőcze seq_puts(s, "\n"); 393e1a39a44SBarnabás Pőcze 394773e3206SIke Panhc return 0; 395773e3206SIke Panhc } 396334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_cfg); 397773e3206SIke Panhc 39817f1bf38SGreg Kroah-Hartman static void ideapad_debugfs_init(struct ideapad_private *priv) 399773e3206SIke Panhc { 40017f1bf38SGreg Kroah-Hartman struct dentry *dir; 401773e3206SIke Panhc 40217f1bf38SGreg Kroah-Hartman dir = debugfs_create_dir("ideapad", NULL); 40317f1bf38SGreg Kroah-Hartman priv->debug = dir; 404773e3206SIke Panhc 40565c7713aSBarnabás Pőcze debugfs_create_file("cfg", 0444, dir, priv, &debugfs_cfg_fops); 40665c7713aSBarnabás Pőcze debugfs_create_file("status", 0444, dir, priv, &debugfs_status_fops); 407773e3206SIke Panhc } 408773e3206SIke Panhc 409773e3206SIke Panhc static void ideapad_debugfs_exit(struct ideapad_private *priv) 410773e3206SIke Panhc { 411773e3206SIke Panhc debugfs_remove_recursive(priv->debug); 412773e3206SIke Panhc priv->debug = NULL; 413773e3206SIke Panhc } 414773e3206SIke Panhc 415773e3206SIke Panhc /* 4163371f481SIke Panhc * sysfs 417a4b5a279SIke Panhc */ 41865c7713aSBarnabás Pőcze static ssize_t camera_power_show(struct device *dev, 41957ac3b05SIke Panhc struct device_attribute *attr, 42057ac3b05SIke Panhc char *buf) 42157ac3b05SIke Panhc { 422331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 42365c7713aSBarnabás Pőcze unsigned long result; 424c81f2410SBarnabás Pőcze int err; 42557ac3b05SIke Panhc 426c81f2410SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result); 427c81f2410SBarnabás Pőcze if (err) 428c81f2410SBarnabás Pőcze return err; 42965c7713aSBarnabás Pőcze 43000641c08SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!result); 43157ac3b05SIke Panhc } 43257ac3b05SIke Panhc 43365c7713aSBarnabás Pőcze static ssize_t camera_power_store(struct device *dev, 43457ac3b05SIke Panhc struct device_attribute *attr, 43557ac3b05SIke Panhc const char *buf, size_t count) 43657ac3b05SIke Panhc { 437331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 43800641c08SBarnabás Pőcze bool state; 439c81f2410SBarnabás Pőcze int err; 4400c7bbeb9SMaxim Mikityanskiy 44165c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 442c81f2410SBarnabás Pőcze if (err) 443c81f2410SBarnabás Pőcze return err; 4440c7bbeb9SMaxim Mikityanskiy 44565c7713aSBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); 44665c7713aSBarnabás Pőcze if (err) 44765c7713aSBarnabás Pőcze return err; 4480c7bbeb9SMaxim Mikityanskiy 4490c7bbeb9SMaxim Mikityanskiy return count; 4500c7bbeb9SMaxim Mikityanskiy } 4510c7bbeb9SMaxim Mikityanskiy 45265c7713aSBarnabás Pőcze static DEVICE_ATTR_RW(camera_power); 45336ac0d43SRitesh Raj Sarraf 454ade50296SHao Wei Tee static ssize_t conservation_mode_show(struct device *dev, 455ade50296SHao Wei Tee struct device_attribute *attr, 456ade50296SHao Wei Tee char *buf) 457ade50296SHao Wei Tee { 458ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 459ade50296SHao Wei Tee unsigned long result; 460c81f2410SBarnabás Pőcze int err; 461ade50296SHao Wei Tee 462ff36b0d9SBarnabás Pőcze err = eval_gbmd(priv->adev->handle, &result); 463c81f2410SBarnabás Pőcze if (err) 464c81f2410SBarnabás Pőcze return err; 46565c7713aSBarnabás Pőcze 4660b765671SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); 467ade50296SHao Wei Tee } 468ade50296SHao Wei Tee 469ade50296SHao Wei Tee static ssize_t conservation_mode_store(struct device *dev, 470ade50296SHao Wei Tee struct device_attribute *attr, 471ade50296SHao Wei Tee const char *buf, size_t count) 472ade50296SHao Wei Tee { 473ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 474ade50296SHao Wei Tee bool state; 47565c7713aSBarnabás Pőcze int err; 476ade50296SHao Wei Tee 47765c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 47865c7713aSBarnabás Pőcze if (err) 47965c7713aSBarnabás Pőcze return err; 480ade50296SHao Wei Tee 481b09aaa3fSBarnabás Pőcze err = exec_sbmc(priv->adev->handle, state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF); 48265c7713aSBarnabás Pőcze if (err) 48365c7713aSBarnabás Pőcze return err; 48465c7713aSBarnabás Pőcze 485ade50296SHao Wei Tee return count; 486ade50296SHao Wei Tee } 487ade50296SHao Wei Tee 488ade50296SHao Wei Tee static DEVICE_ATTR_RW(conservation_mode); 489ade50296SHao Wei Tee 49065c7713aSBarnabás Pőcze static ssize_t fan_mode_show(struct device *dev, 49165c7713aSBarnabás Pőcze struct device_attribute *attr, 49265c7713aSBarnabás Pőcze char *buf) 49365c7713aSBarnabás Pőcze { 49465c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 49565c7713aSBarnabás Pőcze unsigned long result; 49665c7713aSBarnabás Pőcze int err; 49765c7713aSBarnabás Pőcze 49865c7713aSBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result); 49965c7713aSBarnabás Pőcze if (err) 50065c7713aSBarnabás Pőcze return err; 50165c7713aSBarnabás Pőcze 50265c7713aSBarnabás Pőcze return sysfs_emit(buf, "%lu\n", result); 50365c7713aSBarnabás Pőcze } 50465c7713aSBarnabás Pőcze 50565c7713aSBarnabás Pőcze static ssize_t fan_mode_store(struct device *dev, 50665c7713aSBarnabás Pőcze struct device_attribute *attr, 50765c7713aSBarnabás Pőcze const char *buf, size_t count) 50865c7713aSBarnabás Pőcze { 50965c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 51065c7713aSBarnabás Pőcze unsigned int state; 51165c7713aSBarnabás Pőcze int err; 51265c7713aSBarnabás Pőcze 51365c7713aSBarnabás Pőcze err = kstrtouint(buf, 0, &state); 51465c7713aSBarnabás Pőcze if (err) 51565c7713aSBarnabás Pőcze return err; 51665c7713aSBarnabás Pőcze 51765c7713aSBarnabás Pőcze if (state > 4 || state == 3) 51865c7713aSBarnabás Pőcze return -EINVAL; 51965c7713aSBarnabás Pőcze 52065c7713aSBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); 52165c7713aSBarnabás Pőcze if (err) 52265c7713aSBarnabás Pőcze return err; 52365c7713aSBarnabás Pőcze 52465c7713aSBarnabás Pőcze return count; 52565c7713aSBarnabás Pőcze } 52665c7713aSBarnabás Pőcze 52765c7713aSBarnabás Pőcze static DEVICE_ATTR_RW(fan_mode); 52865c7713aSBarnabás Pőcze 52940760717SOleg Keri static ssize_t fn_lock_show(struct device *dev, 53040760717SOleg Keri struct device_attribute *attr, 53140760717SOleg Keri char *buf) 53240760717SOleg Keri { 53340760717SOleg Keri struct ideapad_private *priv = dev_get_drvdata(dev); 534ff36b0d9SBarnabás Pőcze unsigned long hals; 53565c7713aSBarnabás Pőcze int err; 53640760717SOleg Keri 53765c7713aSBarnabás Pőcze err = eval_hals(priv->adev->handle, &hals); 53865c7713aSBarnabás Pőcze if (err) 53965c7713aSBarnabás Pőcze return err; 54040760717SOleg Keri 541ff36b0d9SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); 54240760717SOleg Keri } 54340760717SOleg Keri 54440760717SOleg Keri static ssize_t fn_lock_store(struct device *dev, 54540760717SOleg Keri struct device_attribute *attr, 54640760717SOleg Keri const char *buf, size_t count) 54740760717SOleg Keri { 54840760717SOleg Keri struct ideapad_private *priv = dev_get_drvdata(dev); 54940760717SOleg Keri bool state; 55065c7713aSBarnabás Pőcze int err; 55140760717SOleg Keri 55265c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 55365c7713aSBarnabás Pőcze if (err) 55465c7713aSBarnabás Pőcze return err; 55540760717SOleg Keri 55665c7713aSBarnabás Pőcze err = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); 55765c7713aSBarnabás Pőcze if (err) 55865c7713aSBarnabás Pőcze return err; 55965c7713aSBarnabás Pőcze 56040760717SOleg Keri return count; 56140760717SOleg Keri } 56240760717SOleg Keri 56340760717SOleg Keri static DEVICE_ATTR_RW(fn_lock); 56440760717SOleg Keri 56565c7713aSBarnabás Pőcze static ssize_t touchpad_show(struct device *dev, 56665c7713aSBarnabás Pőcze struct device_attribute *attr, 56765c7713aSBarnabás Pőcze char *buf) 56865c7713aSBarnabás Pőcze { 56965c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 57065c7713aSBarnabás Pőcze unsigned long result; 57165c7713aSBarnabás Pőcze int err; 57265c7713aSBarnabás Pőcze 57365c7713aSBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result); 57465c7713aSBarnabás Pőcze if (err) 57565c7713aSBarnabás Pőcze return err; 57665c7713aSBarnabás Pőcze 57765c7713aSBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!result); 57865c7713aSBarnabás Pőcze } 57965c7713aSBarnabás Pőcze 58065c7713aSBarnabás Pőcze static ssize_t touchpad_store(struct device *dev, 58165c7713aSBarnabás Pőcze struct device_attribute *attr, 58265c7713aSBarnabás Pőcze const char *buf, size_t count) 58365c7713aSBarnabás Pőcze { 58465c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 58565c7713aSBarnabás Pőcze bool state; 58665c7713aSBarnabás Pőcze int err; 58765c7713aSBarnabás Pőcze 58865c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 58965c7713aSBarnabás Pőcze if (err) 59065c7713aSBarnabás Pőcze return err; 59165c7713aSBarnabás Pőcze 59265c7713aSBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); 59365c7713aSBarnabás Pőcze if (err) 59465c7713aSBarnabás Pőcze return err; 59565c7713aSBarnabás Pőcze 59665c7713aSBarnabás Pőcze return count; 59765c7713aSBarnabás Pőcze } 59865c7713aSBarnabás Pőcze 59965c7713aSBarnabás Pőcze static DEVICE_ATTR_RW(touchpad); 60040760717SOleg Keri 6016b49dea4SBarnabás Pőcze static ssize_t usb_charging_show(struct device *dev, 6026b49dea4SBarnabás Pőcze struct device_attribute *attr, 6036b49dea4SBarnabás Pőcze char *buf) 6046b49dea4SBarnabás Pőcze { 6056b49dea4SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 6066b49dea4SBarnabás Pőcze unsigned long hals; 6076b49dea4SBarnabás Pőcze int err; 6086b49dea4SBarnabás Pőcze 6096b49dea4SBarnabás Pőcze err = eval_hals(priv->adev->handle, &hals); 6106b49dea4SBarnabás Pőcze if (err) 6116b49dea4SBarnabás Pőcze return err; 6126b49dea4SBarnabás Pőcze 6136b49dea4SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(HALS_USB_CHARGING_STATE_BIT, &hals)); 6146b49dea4SBarnabás Pőcze } 6156b49dea4SBarnabás Pőcze 6166b49dea4SBarnabás Pőcze static ssize_t usb_charging_store(struct device *dev, 6176b49dea4SBarnabás Pőcze struct device_attribute *attr, 6186b49dea4SBarnabás Pőcze const char *buf, size_t count) 6196b49dea4SBarnabás Pőcze { 6206b49dea4SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 6216b49dea4SBarnabás Pőcze bool state; 6226b49dea4SBarnabás Pőcze int err; 6236b49dea4SBarnabás Pőcze 6246b49dea4SBarnabás Pőcze err = kstrtobool(buf, &state); 6256b49dea4SBarnabás Pőcze if (err) 6266b49dea4SBarnabás Pőcze return err; 6276b49dea4SBarnabás Pőcze 6286b49dea4SBarnabás Pőcze err = exec_sals(priv->adev->handle, state ? SALS_USB_CHARGING_ON : SALS_USB_CHARGING_OFF); 6296b49dea4SBarnabás Pőcze if (err) 6306b49dea4SBarnabás Pőcze return err; 6316b49dea4SBarnabás Pőcze 6326b49dea4SBarnabás Pőcze return count; 6336b49dea4SBarnabás Pőcze } 6346b49dea4SBarnabás Pőcze 6356b49dea4SBarnabás Pőcze static DEVICE_ATTR_RW(usb_charging); 6366b49dea4SBarnabás Pőcze 6373371f481SIke Panhc static struct attribute *ideapad_attributes[] = { 6383371f481SIke Panhc &dev_attr_camera_power.attr, 639ade50296SHao Wei Tee &dev_attr_conservation_mode.attr, 64065c7713aSBarnabás Pőcze &dev_attr_fan_mode.attr, 64140760717SOleg Keri &dev_attr_fn_lock.attr, 64265c7713aSBarnabás Pőcze &dev_attr_touchpad.attr, 6436b49dea4SBarnabás Pőcze &dev_attr_usb_charging.attr, 6443371f481SIke Panhc NULL 6453371f481SIke Panhc }; 6463371f481SIke Panhc 647587a1f16SAl Viro static umode_t ideapad_is_visible(struct kobject *kobj, 648a84511f7SIke Panhc struct attribute *attr, 649a84511f7SIke Panhc int idx) 650a84511f7SIke Panhc { 651708086b2SBarnabás Pőcze struct device *dev = kobj_to_dev(kobj); 652a84511f7SIke Panhc struct ideapad_private *priv = dev_get_drvdata(dev); 6531c59de4aSBarnabás Pőcze bool supported = true; 654a84511f7SIke Panhc 655a84511f7SIke Panhc if (attr == &dev_attr_camera_power.attr) 6560b765671SBarnabás Pőcze supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg); 6571c59de4aSBarnabás Pőcze else if (attr == &dev_attr_conservation_mode.attr) 6581c59de4aSBarnabás Pőcze supported = priv->features.conservation_mode; 6591c59de4aSBarnabás Pőcze else if (attr == &dev_attr_fan_mode.attr) 6601c59de4aSBarnabás Pőcze supported = priv->features.fan_mode; 6611c59de4aSBarnabás Pőcze else if (attr == &dev_attr_fn_lock.attr) 6621c59de4aSBarnabás Pőcze supported = priv->features.fn_lock; 6631c59de4aSBarnabás Pőcze else if (attr == &dev_attr_touchpad.attr) 664b3ed1b7fSBarnabás Pőcze supported = priv->features.touchpad_ctrl_via_ec && 665b3ed1b7fSBarnabás Pőcze test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg); 6666b49dea4SBarnabás Pőcze else if (attr == &dev_attr_usb_charging.attr) 6676b49dea4SBarnabás Pőcze supported = priv->features.usb_charging; 668a84511f7SIke Panhc 669a84511f7SIke Panhc return supported ? attr->mode : 0; 670a84511f7SIke Panhc } 671a84511f7SIke Panhc 67249458e83SMathias Krause static const struct attribute_group ideapad_attribute_group = { 673a84511f7SIke Panhc .is_visible = ideapad_is_visible, 6743371f481SIke Panhc .attrs = ideapad_attributes 6753371f481SIke Panhc }; 6763371f481SIke Panhc 677a4b5a279SIke Panhc /* 678eabe5339SJiaxun Yang * DYTC Platform profile 679eabe5339SJiaxun Yang */ 680eabe5339SJiaxun Yang #define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ 681eabe5339SJiaxun Yang #define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ 682eabe5339SJiaxun Yang #define DYTC_CMD_GET 2 /* To get current IC function and mode */ 683eabe5339SJiaxun Yang #define DYTC_CMD_RESET 0x1ff /* To reset back to default */ 684eabe5339SJiaxun Yang 685eabe5339SJiaxun Yang #define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ 686eabe5339SJiaxun Yang #define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ 687eabe5339SJiaxun Yang #define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ 688eabe5339SJiaxun Yang 689eabe5339SJiaxun Yang #define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ 690eabe5339SJiaxun Yang #define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ 691eabe5339SJiaxun Yang 692eabe5339SJiaxun Yang #define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ 693eabe5339SJiaxun Yang #define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ 694eabe5339SJiaxun Yang #define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ 695eabe5339SJiaxun Yang 696eabe5339SJiaxun Yang #define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ 697eabe5339SJiaxun Yang #define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ 698eabe5339SJiaxun Yang #define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ 699eabe5339SJiaxun Yang 700eabe5339SJiaxun Yang #define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ 701eabe5339SJiaxun Yang #define DYTC_MODE_LOW_POWER 3 /* Low power mode aka quiet */ 702eabe5339SJiaxun Yang #define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ 703eabe5339SJiaxun Yang 704eabe5339SJiaxun Yang #define DYTC_SET_COMMAND(function, mode, on) \ 705eabe5339SJiaxun Yang (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ 706eabe5339SJiaxun Yang (mode) << DYTC_SET_MODE_BIT | \ 707eabe5339SJiaxun Yang (on) << DYTC_SET_VALID_BIT) 708eabe5339SJiaxun Yang 709eabe5339SJiaxun Yang #define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) 710eabe5339SJiaxun Yang 711eabe5339SJiaxun Yang #define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) 712eabe5339SJiaxun Yang 713eabe5339SJiaxun Yang static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) 714eabe5339SJiaxun Yang { 715eabe5339SJiaxun Yang switch (dytcmode) { 716eabe5339SJiaxun Yang case DYTC_MODE_LOW_POWER: 717eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_LOW_POWER; 718eabe5339SJiaxun Yang break; 719eabe5339SJiaxun Yang case DYTC_MODE_BALANCE: 720eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_BALANCED; 721eabe5339SJiaxun Yang break; 722eabe5339SJiaxun Yang case DYTC_MODE_PERFORM: 723eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_PERFORMANCE; 724eabe5339SJiaxun Yang break; 725eabe5339SJiaxun Yang default: /* Unknown mode */ 726eabe5339SJiaxun Yang return -EINVAL; 727eabe5339SJiaxun Yang } 72865c7713aSBarnabás Pőcze 729eabe5339SJiaxun Yang return 0; 730eabe5339SJiaxun Yang } 731eabe5339SJiaxun Yang 732eabe5339SJiaxun Yang static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) 733eabe5339SJiaxun Yang { 734eabe5339SJiaxun Yang switch (profile) { 735eabe5339SJiaxun Yang case PLATFORM_PROFILE_LOW_POWER: 736eabe5339SJiaxun Yang *perfmode = DYTC_MODE_LOW_POWER; 737eabe5339SJiaxun Yang break; 738eabe5339SJiaxun Yang case PLATFORM_PROFILE_BALANCED: 739eabe5339SJiaxun Yang *perfmode = DYTC_MODE_BALANCE; 740eabe5339SJiaxun Yang break; 741eabe5339SJiaxun Yang case PLATFORM_PROFILE_PERFORMANCE: 742eabe5339SJiaxun Yang *perfmode = DYTC_MODE_PERFORM; 743eabe5339SJiaxun Yang break; 744eabe5339SJiaxun Yang default: /* Unknown profile */ 745eabe5339SJiaxun Yang return -EOPNOTSUPP; 746eabe5339SJiaxun Yang } 74765c7713aSBarnabás Pőcze 748eabe5339SJiaxun Yang return 0; 749eabe5339SJiaxun Yang } 750eabe5339SJiaxun Yang 751eabe5339SJiaxun Yang /* 752eabe5339SJiaxun Yang * dytc_profile_get: Function to register with platform_profile 753eabe5339SJiaxun Yang * handler. Returns current platform profile. 754eabe5339SJiaxun Yang */ 75565c7713aSBarnabás Pőcze static int dytc_profile_get(struct platform_profile_handler *pprof, 756eabe5339SJiaxun Yang enum platform_profile_option *profile) 757eabe5339SJiaxun Yang { 75865c7713aSBarnabás Pőcze struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 759eabe5339SJiaxun Yang 760eabe5339SJiaxun Yang *profile = dytc->current_profile; 761eabe5339SJiaxun Yang return 0; 762eabe5339SJiaxun Yang } 763eabe5339SJiaxun Yang 764eabe5339SJiaxun Yang /* 765eabe5339SJiaxun Yang * Helper function - check if we are in CQL mode and if we are 766eabe5339SJiaxun Yang * - disable CQL, 767eabe5339SJiaxun Yang * - run the command 768eabe5339SJiaxun Yang * - enable CQL 769eabe5339SJiaxun Yang * If not in CQL mode, just run the command 770eabe5339SJiaxun Yang */ 77165c7713aSBarnabás Pőcze static int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, 77265c7713aSBarnabás Pőcze unsigned long *output) 773eabe5339SJiaxun Yang { 774ff36b0d9SBarnabás Pőcze int err, cmd_err, cur_funcmode; 775eabe5339SJiaxun Yang 776eabe5339SJiaxun Yang /* Determine if we are in CQL mode. This alters the commands we do */ 777ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_GET, output); 778eabe5339SJiaxun Yang if (err) 779eabe5339SJiaxun Yang return err; 780eabe5339SJiaxun Yang 781eabe5339SJiaxun Yang cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; 782eabe5339SJiaxun Yang /* Check if we're OK to return immediately */ 783ff36b0d9SBarnabás Pőcze if (cmd == DYTC_CMD_GET && cur_funcmode != DYTC_FUNCTION_CQL) 784eabe5339SJiaxun Yang return 0; 785eabe5339SJiaxun Yang 786eabe5339SJiaxun Yang if (cur_funcmode == DYTC_FUNCTION_CQL) { 787ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_DISABLE_CQL, NULL); 788eabe5339SJiaxun Yang if (err) 789eabe5339SJiaxun Yang return err; 790eabe5339SJiaxun Yang } 791eabe5339SJiaxun Yang 792ff36b0d9SBarnabás Pőcze cmd_err = eval_dytc(priv->adev->handle, cmd, output); 793eabe5339SJiaxun Yang /* Check return condition after we've restored CQL state */ 794eabe5339SJiaxun Yang 795eabe5339SJiaxun Yang if (cur_funcmode == DYTC_FUNCTION_CQL) { 796ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_ENABLE_CQL, NULL); 797eabe5339SJiaxun Yang if (err) 798eabe5339SJiaxun Yang return err; 799eabe5339SJiaxun Yang } 800eabe5339SJiaxun Yang 801eabe5339SJiaxun Yang return cmd_err; 802eabe5339SJiaxun Yang } 803eabe5339SJiaxun Yang 804eabe5339SJiaxun Yang /* 805eabe5339SJiaxun Yang * dytc_profile_set: Function to register with platform_profile 806eabe5339SJiaxun Yang * handler. Sets current platform profile. 807eabe5339SJiaxun Yang */ 80865c7713aSBarnabás Pőcze static int dytc_profile_set(struct platform_profile_handler *pprof, 809eabe5339SJiaxun Yang enum platform_profile_option profile) 810eabe5339SJiaxun Yang { 81165c7713aSBarnabás Pőcze struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 81265c7713aSBarnabás Pőcze struct ideapad_private *priv = dytc->priv; 813ff67dbd5SQiu Wenbo unsigned long output; 814eabe5339SJiaxun Yang int err; 815eabe5339SJiaxun Yang 816eabe5339SJiaxun Yang err = mutex_lock_interruptible(&dytc->mutex); 817eabe5339SJiaxun Yang if (err) 818eabe5339SJiaxun Yang return err; 819eabe5339SJiaxun Yang 820eabe5339SJiaxun Yang if (profile == PLATFORM_PROFILE_BALANCED) { 821eabe5339SJiaxun Yang /* To get back to balanced mode we just issue a reset command */ 822ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_RESET, NULL); 823eabe5339SJiaxun Yang if (err) 824eabe5339SJiaxun Yang goto unlock; 825eabe5339SJiaxun Yang } else { 826eabe5339SJiaxun Yang int perfmode; 827eabe5339SJiaxun Yang 828eabe5339SJiaxun Yang err = convert_profile_to_dytc(profile, &perfmode); 829eabe5339SJiaxun Yang if (err) 830eabe5339SJiaxun Yang goto unlock; 831eabe5339SJiaxun Yang 832eabe5339SJiaxun Yang /* Determine if we are in CQL mode. This alters the commands we do */ 83365c7713aSBarnabás Pőcze err = dytc_cql_command(priv, DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), 834ff67dbd5SQiu Wenbo &output); 835eabe5339SJiaxun Yang if (err) 836eabe5339SJiaxun Yang goto unlock; 837eabe5339SJiaxun Yang } 83865c7713aSBarnabás Pőcze 839eabe5339SJiaxun Yang /* Success - update current profile */ 840eabe5339SJiaxun Yang dytc->current_profile = profile; 84165c7713aSBarnabás Pőcze 842eabe5339SJiaxun Yang unlock: 843eabe5339SJiaxun Yang mutex_unlock(&dytc->mutex); 84465c7713aSBarnabás Pőcze 845eabe5339SJiaxun Yang return err; 846eabe5339SJiaxun Yang } 847eabe5339SJiaxun Yang 848eabe5339SJiaxun Yang static void dytc_profile_refresh(struct ideapad_private *priv) 849eabe5339SJiaxun Yang { 850eabe5339SJiaxun Yang enum platform_profile_option profile; 851ff36b0d9SBarnabás Pőcze unsigned long output; 852ff36b0d9SBarnabás Pőcze int err, perfmode; 853eabe5339SJiaxun Yang 854eabe5339SJiaxun Yang mutex_lock(&priv->dytc->mutex); 855eabe5339SJiaxun Yang err = dytc_cql_command(priv, DYTC_CMD_GET, &output); 856eabe5339SJiaxun Yang mutex_unlock(&priv->dytc->mutex); 857eabe5339SJiaxun Yang if (err) 858eabe5339SJiaxun Yang return; 859eabe5339SJiaxun Yang 860eabe5339SJiaxun Yang perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; 86165c7713aSBarnabás Pőcze 86265c7713aSBarnabás Pőcze if (convert_dytc_to_profile(perfmode, &profile)) 86365c7713aSBarnabás Pőcze return; 86465c7713aSBarnabás Pőcze 865eabe5339SJiaxun Yang if (profile != priv->dytc->current_profile) { 866eabe5339SJiaxun Yang priv->dytc->current_profile = profile; 867eabe5339SJiaxun Yang platform_profile_notify(); 868eabe5339SJiaxun Yang } 869eabe5339SJiaxun Yang } 870eabe5339SJiaxun Yang 871599482c5SKelly Anderson static const struct dmi_system_id ideapad_dytc_v4_allow_table[] = { 872599482c5SKelly Anderson { 873599482c5SKelly Anderson /* Ideapad 5 Pro 16ACH6 */ 874599482c5SKelly Anderson .ident = "LENOVO 82L5", 875599482c5SKelly Anderson .matches = { 876599482c5SKelly Anderson DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 877599482c5SKelly Anderson DMI_MATCH(DMI_PRODUCT_NAME, "82L5") 878599482c5SKelly Anderson } 879599482c5SKelly Anderson }, 880599482c5SKelly Anderson {} 881599482c5SKelly Anderson }; 882599482c5SKelly Anderson 883eabe5339SJiaxun Yang static int ideapad_dytc_profile_init(struct ideapad_private *priv) 884eabe5339SJiaxun Yang { 885ff36b0d9SBarnabás Pőcze int err, dytc_version; 886ff36b0d9SBarnabás Pőcze unsigned long output; 887eabe5339SJiaxun Yang 8881c59de4aSBarnabás Pőcze if (!priv->features.dytc) 8891c59de4aSBarnabás Pőcze return -ENODEV; 8901c59de4aSBarnabás Pőcze 891ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); 892eabe5339SJiaxun Yang /* For all other errors we can flag the failure */ 893eabe5339SJiaxun Yang if (err) 894eabe5339SJiaxun Yang return err; 895eabe5339SJiaxun Yang 896eabe5339SJiaxun Yang /* Check DYTC is enabled and supports mode setting */ 897599482c5SKelly Anderson if (!test_bit(DYTC_QUERY_ENABLE_BIT, &output)) { 898599482c5SKelly Anderson dev_info(&priv->platform_device->dev, "DYTC_QUERY_ENABLE_BIT returned false\n"); 899eabe5339SJiaxun Yang return -ENODEV; 900599482c5SKelly Anderson } 901eabe5339SJiaxun Yang 902eabe5339SJiaxun Yang dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; 903599482c5SKelly Anderson 904599482c5SKelly Anderson if (dytc_version < 5) { 905599482c5SKelly Anderson if (dytc_version < 4 || !dmi_check_system(ideapad_dytc_v4_allow_table)) { 906599482c5SKelly Anderson dev_info(&priv->platform_device->dev, 907599482c5SKelly Anderson "DYTC_VERSION is less than 4 or is not allowed: %d\n", 908599482c5SKelly Anderson dytc_version); 909eabe5339SJiaxun Yang return -ENODEV; 910599482c5SKelly Anderson } 911599482c5SKelly Anderson } 912eabe5339SJiaxun Yang 91365c7713aSBarnabás Pőcze priv->dytc = kzalloc(sizeof(*priv->dytc), GFP_KERNEL); 914eabe5339SJiaxun Yang if (!priv->dytc) 915eabe5339SJiaxun Yang return -ENOMEM; 916eabe5339SJiaxun Yang 917eabe5339SJiaxun Yang mutex_init(&priv->dytc->mutex); 918eabe5339SJiaxun Yang 919eabe5339SJiaxun Yang priv->dytc->priv = priv; 920eabe5339SJiaxun Yang priv->dytc->pprof.profile_get = dytc_profile_get; 921eabe5339SJiaxun Yang priv->dytc->pprof.profile_set = dytc_profile_set; 922eabe5339SJiaxun Yang 923eabe5339SJiaxun Yang /* Setup supported modes */ 924eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices); 925eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices); 926eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices); 927eabe5339SJiaxun Yang 928eabe5339SJiaxun Yang /* Create platform_profile structure and register */ 929eabe5339SJiaxun Yang err = platform_profile_register(&priv->dytc->pprof); 930eabe5339SJiaxun Yang if (err) 93165c7713aSBarnabás Pőcze goto pp_reg_failed; 932eabe5339SJiaxun Yang 933eabe5339SJiaxun Yang /* Ensure initial values are correct */ 934eabe5339SJiaxun Yang dytc_profile_refresh(priv); 935eabe5339SJiaxun Yang 936eabe5339SJiaxun Yang return 0; 937eabe5339SJiaxun Yang 93865c7713aSBarnabás Pőcze pp_reg_failed: 939eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 940eabe5339SJiaxun Yang kfree(priv->dytc); 941eabe5339SJiaxun Yang priv->dytc = NULL; 94265c7713aSBarnabás Pőcze 943eabe5339SJiaxun Yang return err; 944eabe5339SJiaxun Yang } 945eabe5339SJiaxun Yang 946eabe5339SJiaxun Yang static void ideapad_dytc_profile_exit(struct ideapad_private *priv) 947eabe5339SJiaxun Yang { 948eabe5339SJiaxun Yang if (!priv->dytc) 949eabe5339SJiaxun Yang return; 950eabe5339SJiaxun Yang 951eabe5339SJiaxun Yang platform_profile_remove(); 952eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 953eabe5339SJiaxun Yang kfree(priv->dytc); 95465c7713aSBarnabás Pőcze 955eabe5339SJiaxun Yang priv->dytc = NULL; 956eabe5339SJiaxun Yang } 957eabe5339SJiaxun Yang 958eabe5339SJiaxun Yang /* 959a4b5a279SIke Panhc * Rfkill 960a4b5a279SIke Panhc */ 961c1f73658SIke Panhc struct ideapad_rfk_data { 962c1f73658SIke Panhc char *name; 963c1f73658SIke Panhc int cfgbit; 964c1f73658SIke Panhc int opcode; 965c1f73658SIke Panhc int type; 966c1f73658SIke Panhc }; 967c1f73658SIke Panhc 968b3d94d70SMathias Krause static const struct ideapad_rfk_data ideapad_rfk_data[] = { 9690b765671SBarnabás Pőcze { "ideapad_wlan", CFG_CAP_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, 9700b765671SBarnabás Pőcze { "ideapad_bluetooth", CFG_CAP_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, 9710b765671SBarnabás Pőcze { "ideapad_3g", CFG_CAP_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, 972c1f73658SIke Panhc }; 973c1f73658SIke Panhc 97457ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked) 97557ac3b05SIke Panhc { 976331e0ea2SZhang Rui struct ideapad_rfk_priv *priv = data; 9774b200b46SArnd Bergmann int opcode = ideapad_rfk_data[priv->dev].opcode; 97857ac3b05SIke Panhc 9794b200b46SArnd Bergmann return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked); 98057ac3b05SIke Panhc } 98157ac3b05SIke Panhc 9823d59dfcdSBhumika Goyal static const struct rfkill_ops ideapad_rfk_ops = { 98357ac3b05SIke Panhc .set_block = ideapad_rfk_set, 98457ac3b05SIke Panhc }; 98557ac3b05SIke Panhc 986923de84aSIke Panhc static void ideapad_sync_rfk_state(struct ideapad_private *priv) 98757ac3b05SIke Panhc { 988ce363c2bSHans de Goede unsigned long hw_blocked = 0; 98957ac3b05SIke Panhc int i; 99057ac3b05SIke Panhc 9911c59de4aSBarnabás Pőcze if (priv->features.hw_rfkill_switch) { 992331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) 99357ac3b05SIke Panhc return; 99457ac3b05SIke Panhc hw_blocked = !hw_blocked; 995ce363c2bSHans de Goede } 99657ac3b05SIke Panhc 997c1f73658SIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 99857ac3b05SIke Panhc if (priv->rfk[i]) 99957ac3b05SIke Panhc rfkill_set_hw_state(priv->rfk[i], hw_blocked); 100057ac3b05SIke Panhc } 100157ac3b05SIke Panhc 100275a11f11SZhang Rui static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) 100357ac3b05SIke Panhc { 100465c7713aSBarnabás Pőcze unsigned long rf_enabled; 100565c7713aSBarnabás Pőcze int err; 100657ac3b05SIke Panhc 100765c7713aSBarnabás Pőcze if (no_bt_rfkill && ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH) { 1008bfa97b7dSIke Panhc /* Force to enable bluetooth when no_bt_rfkill=1 */ 100965c7713aSBarnabás Pőcze write_ec_cmd(priv->adev->handle, ideapad_rfk_data[dev].opcode, 1); 1010bfa97b7dSIke Panhc return 0; 1011bfa97b7dSIke Panhc } 101265c7713aSBarnabás Pőcze 1013331e0ea2SZhang Rui priv->rfk_priv[dev].dev = dev; 1014331e0ea2SZhang Rui priv->rfk_priv[dev].priv = priv; 1015bfa97b7dSIke Panhc 101675a11f11SZhang Rui priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, 1017b5c37b79SZhang Rui &priv->platform_device->dev, 101875a11f11SZhang Rui ideapad_rfk_data[dev].type, 101975a11f11SZhang Rui &ideapad_rfk_ops, 1020331e0ea2SZhang Rui &priv->rfk_priv[dev]); 102157ac3b05SIke Panhc if (!priv->rfk[dev]) 102257ac3b05SIke Panhc return -ENOMEM; 102357ac3b05SIke Panhc 102465c7713aSBarnabás Pőcze err = read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode - 1, &rf_enabled); 102565c7713aSBarnabás Pőcze if (err) 102665c7713aSBarnabás Pőcze rf_enabled = 1; 102757ac3b05SIke Panhc 102865c7713aSBarnabás Pőcze rfkill_init_sw_state(priv->rfk[dev], !rf_enabled); 102965c7713aSBarnabás Pőcze 103065c7713aSBarnabás Pőcze err = rfkill_register(priv->rfk[dev]); 103165c7713aSBarnabás Pőcze if (err) 103257ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 103365c7713aSBarnabás Pőcze 103465c7713aSBarnabás Pőcze return err; 103557ac3b05SIke Panhc } 103657ac3b05SIke Panhc 103775a11f11SZhang Rui static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) 103857ac3b05SIke Panhc { 103957ac3b05SIke Panhc if (!priv->rfk[dev]) 104057ac3b05SIke Panhc return; 104157ac3b05SIke Panhc 104257ac3b05SIke Panhc rfkill_unregister(priv->rfk[dev]); 104357ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 104457ac3b05SIke Panhc } 104557ac3b05SIke Panhc 104698ee6919SIke Panhc /* 104798ee6919SIke Panhc * Platform device 104898ee6919SIke Panhc */ 1049b5c37b79SZhang Rui static int ideapad_sysfs_init(struct ideapad_private *priv) 105098ee6919SIke Panhc { 10518782d8d7SBarnabás Pőcze return device_add_group(&priv->platform_device->dev, 1052c9f718d0SIke Panhc &ideapad_attribute_group); 105398ee6919SIke Panhc } 105498ee6919SIke Panhc 1055b5c37b79SZhang Rui static void ideapad_sysfs_exit(struct ideapad_private *priv) 105698ee6919SIke Panhc { 10578782d8d7SBarnabás Pőcze device_remove_group(&priv->platform_device->dev, 1058c9f718d0SIke Panhc &ideapad_attribute_group); 105998ee6919SIke Panhc } 106098ee6919SIke Panhc 1061f63409aeSIke Panhc /* 1062f63409aeSIke Panhc * input device 1063f63409aeSIke Panhc */ 1064f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = { 1065f43d9ec0SIke Panhc { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, 1066296f9fe0SMaxim Mikityanskiy { KE_KEY, 7, { KEY_CAMERA } }, 106748f67d62SAlex Hung { KE_KEY, 8, { KEY_MICMUTE } }, 1068296f9fe0SMaxim Mikityanskiy { KE_KEY, 11, { KEY_F16 } }, 1069f43d9ec0SIke Panhc { KE_KEY, 13, { KEY_WLAN } }, 1070f43d9ec0SIke Panhc { KE_KEY, 16, { KEY_PROG1 } }, 1071f43d9ec0SIke Panhc { KE_KEY, 17, { KEY_PROG2 } }, 1072296f9fe0SMaxim Mikityanskiy { KE_KEY, 64, { KEY_PROG3 } }, 1073296f9fe0SMaxim Mikityanskiy { KE_KEY, 65, { KEY_PROG4 } }, 107407a4a4fcSMaxim Mikityanskiy { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, 107507a4a4fcSMaxim Mikityanskiy { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, 107674caab99SArnd Bergmann { KE_KEY, 128, { KEY_ESC } }, 107765c7713aSBarnabás Pőcze { KE_END }, 1078f63409aeSIke Panhc }; 1079f63409aeSIke Panhc 1080b859f159SGreg Kroah-Hartman static int ideapad_input_init(struct ideapad_private *priv) 1081f63409aeSIke Panhc { 1082f63409aeSIke Panhc struct input_dev *inputdev; 108365c7713aSBarnabás Pőcze int err; 1084f63409aeSIke Panhc 1085f63409aeSIke Panhc inputdev = input_allocate_device(); 1086b222cca6SJoe Perches if (!inputdev) 1087f63409aeSIke Panhc return -ENOMEM; 1088f63409aeSIke Panhc 1089f63409aeSIke Panhc inputdev->name = "Ideapad extra buttons"; 1090f63409aeSIke Panhc inputdev->phys = "ideapad/input0"; 1091f63409aeSIke Panhc inputdev->id.bustype = BUS_HOST; 10928693ae84SIke Panhc inputdev->dev.parent = &priv->platform_device->dev; 1093f63409aeSIke Panhc 109465c7713aSBarnabás Pőcze err = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); 109565c7713aSBarnabás Pőcze if (err) { 1096654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 109765c7713aSBarnabás Pőcze "Could not set up input device keymap: %d\n", err); 1098f63409aeSIke Panhc goto err_free_dev; 1099f63409aeSIke Panhc } 1100f63409aeSIke Panhc 110165c7713aSBarnabás Pőcze err = input_register_device(inputdev); 110265c7713aSBarnabás Pőcze if (err) { 1103654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 110465c7713aSBarnabás Pőcze "Could not register input device: %d\n", err); 1105c973d4b5SMichał Kępień goto err_free_dev; 1106f63409aeSIke Panhc } 1107f63409aeSIke Panhc 11088693ae84SIke Panhc priv->inputdev = inputdev; 110965c7713aSBarnabás Pőcze 1110f63409aeSIke Panhc return 0; 1111f63409aeSIke Panhc 1112f63409aeSIke Panhc err_free_dev: 1113f63409aeSIke Panhc input_free_device(inputdev); 111465c7713aSBarnabás Pőcze 111565c7713aSBarnabás Pőcze return err; 1116f63409aeSIke Panhc } 1117f63409aeSIke Panhc 11187451a55aSAxel Lin static void ideapad_input_exit(struct ideapad_private *priv) 1119f63409aeSIke Panhc { 11208693ae84SIke Panhc input_unregister_device(priv->inputdev); 11218693ae84SIke Panhc priv->inputdev = NULL; 1122f63409aeSIke Panhc } 1123f63409aeSIke Panhc 11248693ae84SIke Panhc static void ideapad_input_report(struct ideapad_private *priv, 11258693ae84SIke Panhc unsigned long scancode) 1126f63409aeSIke Panhc { 11278693ae84SIke Panhc sparse_keymap_report_event(priv->inputdev, scancode, 1, true); 1128f63409aeSIke Panhc } 1129f63409aeSIke Panhc 1130f43d9ec0SIke Panhc static void ideapad_input_novokey(struct ideapad_private *priv) 1131f43d9ec0SIke Panhc { 1132f43d9ec0SIke Panhc unsigned long long_pressed; 1133f43d9ec0SIke Panhc 1134331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) 1135f43d9ec0SIke Panhc return; 113665c7713aSBarnabás Pőcze 1137f43d9ec0SIke Panhc if (long_pressed) 1138f43d9ec0SIke Panhc ideapad_input_report(priv, 17); 1139f43d9ec0SIke Panhc else 1140f43d9ec0SIke Panhc ideapad_input_report(priv, 16); 1141f43d9ec0SIke Panhc } 1142f43d9ec0SIke Panhc 1143296f9fe0SMaxim Mikityanskiy static void ideapad_check_special_buttons(struct ideapad_private *priv) 1144296f9fe0SMaxim Mikityanskiy { 1145296f9fe0SMaxim Mikityanskiy unsigned long bit, value; 1146296f9fe0SMaxim Mikityanskiy 11477be193e3SBarnabás Pőcze if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) 11487be193e3SBarnabás Pőcze return; 1149296f9fe0SMaxim Mikityanskiy 11500c4915b6SBarnabás Pőcze for_each_set_bit (bit, &value, 16) { 1151296f9fe0SMaxim Mikityanskiy switch (bit) { 1152a1ec56edSMaxim Mikityanskiy case 6: /* Z570 */ 115365c7713aSBarnabás Pőcze case 0: /* Z580 */ 1154296f9fe0SMaxim Mikityanskiy /* Thermal Management button */ 1155296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 65); 1156296f9fe0SMaxim Mikityanskiy break; 1157296f9fe0SMaxim Mikityanskiy case 1: 1158296f9fe0SMaxim Mikityanskiy /* OneKey Theater button */ 1159296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 64); 1160296f9fe0SMaxim Mikityanskiy break; 1161a1ec56edSMaxim Mikityanskiy default: 1162654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1163654324c4SBarnabás Pőcze "Unknown special button: %lu\n", bit); 1164a1ec56edSMaxim Mikityanskiy break; 1165296f9fe0SMaxim Mikityanskiy } 1166296f9fe0SMaxim Mikityanskiy } 1167296f9fe0SMaxim Mikityanskiy } 1168296f9fe0SMaxim Mikityanskiy 1169a4b5a279SIke Panhc /* 1170a4ecbb8aSIke Panhc * backlight 1171a4ecbb8aSIke Panhc */ 1172a4ecbb8aSIke Panhc static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) 1173a4ecbb8aSIke Panhc { 1174331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 1175a4ecbb8aSIke Panhc unsigned long now; 11767be193e3SBarnabás Pőcze int err; 1177a4ecbb8aSIke Panhc 11787be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 11797be193e3SBarnabás Pőcze if (err) 11807be193e3SBarnabás Pőcze return err; 118165c7713aSBarnabás Pőcze 1182a4ecbb8aSIke Panhc return now; 1183a4ecbb8aSIke Panhc } 1184a4ecbb8aSIke Panhc 1185a4ecbb8aSIke Panhc static int ideapad_backlight_update_status(struct backlight_device *blightdev) 1186a4ecbb8aSIke Panhc { 1187331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 11887be193e3SBarnabás Pőcze int err; 1189331e0ea2SZhang Rui 11907be193e3SBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, 11917be193e3SBarnabás Pőcze blightdev->props.brightness); 11927be193e3SBarnabás Pőcze if (err) 11937be193e3SBarnabás Pőcze return err; 119465c7713aSBarnabás Pőcze 11957be193e3SBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, 11967be193e3SBarnabás Pőcze blightdev->props.power != FB_BLANK_POWERDOWN); 11977be193e3SBarnabás Pőcze if (err) 11987be193e3SBarnabás Pőcze return err; 1199a4ecbb8aSIke Panhc 1200a4ecbb8aSIke Panhc return 0; 1201a4ecbb8aSIke Panhc } 1202a4ecbb8aSIke Panhc 1203a4ecbb8aSIke Panhc static const struct backlight_ops ideapad_backlight_ops = { 1204a4ecbb8aSIke Panhc .get_brightness = ideapad_backlight_get_brightness, 1205a4ecbb8aSIke Panhc .update_status = ideapad_backlight_update_status, 1206a4ecbb8aSIke Panhc }; 1207a4ecbb8aSIke Panhc 1208a4ecbb8aSIke Panhc static int ideapad_backlight_init(struct ideapad_private *priv) 1209a4ecbb8aSIke Panhc { 1210a4ecbb8aSIke Panhc struct backlight_device *blightdev; 1211a4ecbb8aSIke Panhc struct backlight_properties props; 1212a4ecbb8aSIke Panhc unsigned long max, now, power; 12137be193e3SBarnabás Pőcze int err; 1214a4ecbb8aSIke Panhc 12157be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max); 12167be193e3SBarnabás Pőcze if (err) 12177be193e3SBarnabás Pőcze return err; 121865c7713aSBarnabás Pőcze 12197be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 12207be193e3SBarnabás Pőcze if (err) 12217be193e3SBarnabás Pőcze return err; 122265c7713aSBarnabás Pőcze 12237be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power); 12247be193e3SBarnabás Pőcze if (err) 12257be193e3SBarnabás Pőcze return err; 1226a4ecbb8aSIke Panhc 122765c7713aSBarnabás Pőcze memset(&props, 0, sizeof(props)); 122865c7713aSBarnabás Pőcze 1229a4ecbb8aSIke Panhc props.max_brightness = max; 1230a4ecbb8aSIke Panhc props.type = BACKLIGHT_PLATFORM; 123165c7713aSBarnabás Pőcze 1232a4ecbb8aSIke Panhc blightdev = backlight_device_register("ideapad", 1233a4ecbb8aSIke Panhc &priv->platform_device->dev, 1234a4ecbb8aSIke Panhc priv, 1235a4ecbb8aSIke Panhc &ideapad_backlight_ops, 1236a4ecbb8aSIke Panhc &props); 1237a4ecbb8aSIke Panhc if (IS_ERR(blightdev)) { 123865c7713aSBarnabás Pőcze err = PTR_ERR(blightdev); 1239654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 124065c7713aSBarnabás Pőcze "Could not register backlight device: %d\n", err); 124165c7713aSBarnabás Pőcze return err; 1242a4ecbb8aSIke Panhc } 1243a4ecbb8aSIke Panhc 1244a4ecbb8aSIke Panhc priv->blightdev = blightdev; 1245a4ecbb8aSIke Panhc blightdev->props.brightness = now; 1246a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 124765c7713aSBarnabás Pőcze 1248a4ecbb8aSIke Panhc backlight_update_status(blightdev); 1249a4ecbb8aSIke Panhc 1250a4ecbb8aSIke Panhc return 0; 1251a4ecbb8aSIke Panhc } 1252a4ecbb8aSIke Panhc 1253a4ecbb8aSIke Panhc static void ideapad_backlight_exit(struct ideapad_private *priv) 1254a4ecbb8aSIke Panhc { 1255a4ecbb8aSIke Panhc backlight_device_unregister(priv->blightdev); 1256a4ecbb8aSIke Panhc priv->blightdev = NULL; 1257a4ecbb8aSIke Panhc } 1258a4ecbb8aSIke Panhc 1259a4ecbb8aSIke Panhc static void ideapad_backlight_notify_power(struct ideapad_private *priv) 1260a4ecbb8aSIke Panhc { 1261a4ecbb8aSIke Panhc struct backlight_device *blightdev = priv->blightdev; 126265c7713aSBarnabás Pőcze unsigned long power; 1263a4ecbb8aSIke Panhc 1264d4afc775SRene Bollford if (!blightdev) 1265d4afc775SRene Bollford return; 126665c7713aSBarnabás Pőcze 1267331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) 1268a4ecbb8aSIke Panhc return; 126965c7713aSBarnabás Pőcze 1270a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 1271a4ecbb8aSIke Panhc } 1272a4ecbb8aSIke Panhc 1273a4ecbb8aSIke Panhc static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) 1274a4ecbb8aSIke Panhc { 1275a4ecbb8aSIke Panhc unsigned long now; 1276a4ecbb8aSIke Panhc 1277a4ecbb8aSIke Panhc /* if we control brightness via acpi video driver */ 127865c7713aSBarnabás Pőcze if (!priv->blightdev) 1279331e0ea2SZhang Rui read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 128065c7713aSBarnabás Pőcze else 1281a4ecbb8aSIke Panhc backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); 1282a4ecbb8aSIke Panhc } 1283a4ecbb8aSIke Panhc 1284a4ecbb8aSIke Panhc /* 1285503325f8SBarnabás Pőcze * keyboard backlight 1286503325f8SBarnabás Pőcze */ 1287503325f8SBarnabás Pőcze static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv) 1288503325f8SBarnabás Pőcze { 1289503325f8SBarnabás Pőcze unsigned long hals; 1290503325f8SBarnabás Pőcze int err; 1291503325f8SBarnabás Pőcze 1292503325f8SBarnabás Pőcze err = eval_hals(priv->adev->handle, &hals); 1293503325f8SBarnabás Pőcze if (err) 1294503325f8SBarnabás Pőcze return err; 1295503325f8SBarnabás Pőcze 1296503325f8SBarnabás Pőcze return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals); 1297503325f8SBarnabás Pőcze } 1298503325f8SBarnabás Pőcze 1299503325f8SBarnabás Pőcze static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev) 1300503325f8SBarnabás Pőcze { 1301503325f8SBarnabás Pőcze struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); 1302503325f8SBarnabás Pőcze 1303503325f8SBarnabás Pőcze return ideapad_kbd_bl_brightness_get(priv); 1304503325f8SBarnabás Pőcze } 1305503325f8SBarnabás Pőcze 1306503325f8SBarnabás Pőcze static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness) 1307503325f8SBarnabás Pőcze { 1308503325f8SBarnabás Pőcze int err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF); 1309503325f8SBarnabás Pőcze 1310503325f8SBarnabás Pőcze if (err) 1311503325f8SBarnabás Pőcze return err; 1312503325f8SBarnabás Pőcze 1313503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1314503325f8SBarnabás Pőcze 1315503325f8SBarnabás Pőcze return 0; 1316503325f8SBarnabás Pőcze } 1317503325f8SBarnabás Pőcze 1318503325f8SBarnabás Pőcze static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev, 1319503325f8SBarnabás Pőcze enum led_brightness brightness) 1320503325f8SBarnabás Pőcze { 1321503325f8SBarnabás Pőcze struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); 1322503325f8SBarnabás Pőcze 1323503325f8SBarnabás Pőcze return ideapad_kbd_bl_brightness_set(priv, brightness); 1324503325f8SBarnabás Pőcze } 1325503325f8SBarnabás Pőcze 1326503325f8SBarnabás Pőcze static void ideapad_kbd_bl_notify(struct ideapad_private *priv) 1327503325f8SBarnabás Pőcze { 1328503325f8SBarnabás Pőcze int brightness; 1329503325f8SBarnabás Pőcze 1330503325f8SBarnabás Pőcze if (!priv->kbd_bl.initialized) 1331503325f8SBarnabás Pőcze return; 1332503325f8SBarnabás Pőcze 1333503325f8SBarnabás Pőcze brightness = ideapad_kbd_bl_brightness_get(priv); 1334503325f8SBarnabás Pőcze if (brightness < 0) 1335503325f8SBarnabás Pőcze return; 1336503325f8SBarnabás Pőcze 1337503325f8SBarnabás Pőcze if (brightness == priv->kbd_bl.last_brightness) 1338503325f8SBarnabás Pőcze return; 1339503325f8SBarnabás Pőcze 1340503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1341503325f8SBarnabás Pőcze 1342503325f8SBarnabás Pőcze led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness); 1343503325f8SBarnabás Pőcze } 1344503325f8SBarnabás Pőcze 1345503325f8SBarnabás Pőcze static int ideapad_kbd_bl_init(struct ideapad_private *priv) 1346503325f8SBarnabás Pőcze { 1347503325f8SBarnabás Pőcze int brightness, err; 1348503325f8SBarnabás Pőcze 1349503325f8SBarnabás Pőcze if (!priv->features.kbd_bl) 1350503325f8SBarnabás Pőcze return -ENODEV; 1351503325f8SBarnabás Pőcze 1352503325f8SBarnabás Pőcze if (WARN_ON(priv->kbd_bl.initialized)) 1353503325f8SBarnabás Pőcze return -EEXIST; 1354503325f8SBarnabás Pőcze 1355503325f8SBarnabás Pőcze brightness = ideapad_kbd_bl_brightness_get(priv); 1356503325f8SBarnabás Pőcze if (brightness < 0) 1357503325f8SBarnabás Pőcze return brightness; 1358503325f8SBarnabás Pőcze 1359503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1360503325f8SBarnabás Pőcze 1361503325f8SBarnabás Pőcze priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT; 1362503325f8SBarnabás Pőcze priv->kbd_bl.led.max_brightness = 1; 1363503325f8SBarnabás Pőcze priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get; 1364503325f8SBarnabás Pőcze priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set; 1365503325f8SBarnabás Pőcze priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED; 1366503325f8SBarnabás Pőcze 1367503325f8SBarnabás Pőcze err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led); 1368503325f8SBarnabás Pőcze if (err) 1369503325f8SBarnabás Pőcze return err; 1370503325f8SBarnabás Pőcze 1371503325f8SBarnabás Pőcze priv->kbd_bl.initialized = true; 1372503325f8SBarnabás Pőcze 1373503325f8SBarnabás Pőcze return 0; 1374503325f8SBarnabás Pőcze } 1375503325f8SBarnabás Pőcze 1376503325f8SBarnabás Pőcze static void ideapad_kbd_bl_exit(struct ideapad_private *priv) 1377503325f8SBarnabás Pőcze { 1378503325f8SBarnabás Pőcze if (!priv->kbd_bl.initialized) 1379503325f8SBarnabás Pőcze return; 1380503325f8SBarnabás Pőcze 1381503325f8SBarnabás Pőcze priv->kbd_bl.initialized = false; 1382503325f8SBarnabás Pőcze 1383503325f8SBarnabás Pőcze led_classdev_unregister(&priv->kbd_bl.led); 1384503325f8SBarnabás Pőcze } 1385503325f8SBarnabás Pőcze 1386503325f8SBarnabás Pőcze /* 1387a4b5a279SIke Panhc * module init/exit 1388a4b5a279SIke Panhc */ 138975a11f11SZhang Rui static void ideapad_sync_touchpad_state(struct ideapad_private *priv) 139007a4a4fcSMaxim Mikityanskiy { 139107a4a4fcSMaxim Mikityanskiy unsigned long value; 139207a4a4fcSMaxim Mikityanskiy 13931c59de4aSBarnabás Pőcze if (!priv->features.touchpad_ctrl_via_ec) 1394d69cd7eeSJiaxun Yang return; 1395d69cd7eeSJiaxun Yang 139607a4a4fcSMaxim Mikityanskiy /* Without reading from EC touchpad LED doesn't switch state */ 139775a11f11SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { 139865c7713aSBarnabás Pőcze unsigned char param; 139965c7713aSBarnabás Pőcze /* 140065c7713aSBarnabás Pőcze * Some IdeaPads don't really turn off touchpad - they only 140107a4a4fcSMaxim Mikityanskiy * switch the LED state. We (de)activate KBC AUX port to turn 140207a4a4fcSMaxim Mikityanskiy * touchpad off and on. We send KEY_TOUCHPAD_OFF and 140365c7713aSBarnabás Pőcze * KEY_TOUCHPAD_ON to not to get out of sync with LED 140465c7713aSBarnabás Pőcze */ 140565c7713aSBarnabás Pőcze i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE); 140607a4a4fcSMaxim Mikityanskiy ideapad_input_report(priv, value ? 67 : 66); 1407c6795746SBarnabás Pőcze sysfs_notify(&priv->platform_device->dev.kobj, NULL, "touchpad"); 140807a4a4fcSMaxim Mikityanskiy } 140907a4a4fcSMaxim Mikityanskiy } 141007a4a4fcSMaxim Mikityanskiy 1411b5c37b79SZhang Rui static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) 141257ac3b05SIke Panhc { 1413b5c37b79SZhang Rui struct ideapad_private *priv = data; 14140c4915b6SBarnabás Pőcze unsigned long vpc1, vpc2, bit; 141557ac3b05SIke Panhc 14162be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) 141757ac3b05SIke Panhc return; 141865c7713aSBarnabás Pőcze 14192be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) 142057ac3b05SIke Panhc return; 142157ac3b05SIke Panhc 142257ac3b05SIke Panhc vpc1 = (vpc2 << 8) | vpc1; 14230c4915b6SBarnabás Pőcze 14240c4915b6SBarnabás Pőcze for_each_set_bit (bit, &vpc1, 16) { 14250c4915b6SBarnabás Pőcze switch (bit) { 142620a769c1SIke Panhc case 13: 1427296f9fe0SMaxim Mikityanskiy case 11: 142848f67d62SAlex Hung case 8: 1429296f9fe0SMaxim Mikityanskiy case 7: 143020a769c1SIke Panhc case 6: 14310c4915b6SBarnabás Pőcze ideapad_input_report(priv, bit); 143220a769c1SIke Panhc break; 1433ab66724aSHans de Goede case 10: 1434ab66724aSHans de Goede /* 1435ab66724aSHans de Goede * This event gets send on a Yoga 300-11IBR when the EC 1436ab66724aSHans de Goede * believes that the device has changed between laptop/ 1437ab66724aSHans de Goede * tent/stand/tablet mode. The EC relies on getting 1438ab66724aSHans de Goede * angle info from 2 accelerometers through a special 1439ab66724aSHans de Goede * windows service calling a DSM on the DUAL250E ACPI- 1440ab66724aSHans de Goede * device. Linux does not do this, making the laptop/ 1441ab66724aSHans de Goede * tent/stand/tablet mode info unreliable, so we simply 1442ab66724aSHans de Goede * ignore these events. 1443ab66724aSHans de Goede */ 1444ab66724aSHans de Goede break; 144565c7713aSBarnabás Pőcze case 9: 144665c7713aSBarnabás Pőcze ideapad_sync_rfk_state(priv); 144765c7713aSBarnabás Pőcze break; 144807a4a4fcSMaxim Mikityanskiy case 5: 144975a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 145007a4a4fcSMaxim Mikityanskiy break; 1451a4ecbb8aSIke Panhc case 4: 1452a4ecbb8aSIke Panhc ideapad_backlight_notify_brightness(priv); 1453a4ecbb8aSIke Panhc break; 1454f43d9ec0SIke Panhc case 3: 1455f43d9ec0SIke Panhc ideapad_input_novokey(priv); 1456f43d9ec0SIke Panhc break; 1457a4ecbb8aSIke Panhc case 2: 1458a4ecbb8aSIke Panhc ideapad_backlight_notify_power(priv); 1459a4ecbb8aSIke Panhc break; 14603cfd956bSHao Wei Tee case 1: 146165c7713aSBarnabás Pőcze /* 146265c7713aSBarnabás Pőcze * Some IdeaPads report event 1 every ~20 14633cfd956bSHao Wei Tee * seconds while on battery power; some 14643cfd956bSHao Wei Tee * report this when changing to/from tablet 1465503325f8SBarnabás Pőcze * mode; some report this when the keyboard 1466503325f8SBarnabás Pőcze * backlight has changed. 14673cfd956bSHao Wei Tee */ 1468503325f8SBarnabás Pőcze ideapad_kbd_bl_notify(priv); 14693cfd956bSHao Wei Tee break; 147065c7713aSBarnabás Pőcze case 0: 147165c7713aSBarnabás Pőcze ideapad_check_special_buttons(priv); 147265c7713aSBarnabás Pőcze break; 1473a4ecbb8aSIke Panhc default: 1474654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1475654324c4SBarnabás Pőcze "Unknown event: %lu\n", bit); 147657ac3b05SIke Panhc } 147757ac3b05SIke Panhc } 1478a4ecbb8aSIke Panhc } 147957ac3b05SIke Panhc 148074caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 148174caab99SArnd Bergmann static void ideapad_wmi_notify(u32 value, void *context) 148274caab99SArnd Bergmann { 1483654324c4SBarnabás Pőcze struct ideapad_private *priv = context; 14843ae86d2dSMeng Dong unsigned long result; 1485654324c4SBarnabás Pőcze 148674caab99SArnd Bergmann switch (value) { 148774caab99SArnd Bergmann case 128: 1488654324c4SBarnabás Pőcze ideapad_input_report(priv, value); 148974caab99SArnd Bergmann break; 14903ae86d2dSMeng Dong case 208: 14913ae86d2dSMeng Dong if (!eval_hals(priv->adev->handle, &result)) { 14923ae86d2dSMeng Dong bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result); 14933ae86d2dSMeng Dong 14943ae86d2dSMeng Dong exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); 14953ae86d2dSMeng Dong } 14963ae86d2dSMeng Dong break; 149774caab99SArnd Bergmann default: 1498654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1499654324c4SBarnabás Pőcze "Unknown WMI event: %u\n", value); 150074caab99SArnd Bergmann } 150174caab99SArnd Bergmann } 150274caab99SArnd Bergmann #endif 150374caab99SArnd Bergmann 1504ce363c2bSHans de Goede /* 15055105e78eSHans de Goede * Some ideapads have a hardware rfkill switch, but most do not have one. 15065105e78eSHans de Goede * Reading VPCCMD_R_RF always results in 0 on models without a hardware rfkill, 15075105e78eSHans de Goede * switch causing ideapad_laptop to wrongly report all radios as hw-blocked. 15085105e78eSHans de Goede * There used to be a long list of DMI ids for models without a hw rfkill 15095105e78eSHans de Goede * switch here, but that resulted in playing whack a mole. 15105105e78eSHans de Goede * More importantly wrongly reporting the wifi radio as hw-blocked, results in 15115105e78eSHans de Goede * non working wifi. Whereas not reporting it hw-blocked, when it actually is 15125105e78eSHans de Goede * hw-blocked results in an empty SSID list, which is a much more benign 15135105e78eSHans de Goede * failure mode. 15145105e78eSHans de Goede * So the default now is the much safer option of assuming there is no 15155105e78eSHans de Goede * hardware rfkill switch. This default also actually matches most hardware, 15165105e78eSHans de Goede * since having a hw rfkill switch is quite rare on modern hardware, so this 15175105e78eSHans de Goede * also leads to a much shorter list. 1518ce363c2bSHans de Goede */ 15195105e78eSHans de Goede static const struct dmi_system_id hw_rfkill_list[] = { 152085093f79SHans de Goede {} 152185093f79SHans de Goede }; 152285093f79SHans de Goede 15231c59de4aSBarnabás Pőcze static void ideapad_check_features(struct ideapad_private *priv) 15241c59de4aSBarnabás Pőcze { 15251c59de4aSBarnabás Pőcze acpi_handle handle = priv->adev->handle; 15261c59de4aSBarnabás Pőcze unsigned long val; 15271c59de4aSBarnabás Pőcze 15281c59de4aSBarnabás Pőcze priv->features.hw_rfkill_switch = dmi_check_system(hw_rfkill_list); 15291c59de4aSBarnabás Pőcze 15301c59de4aSBarnabás Pőcze /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ 15311c59de4aSBarnabás Pőcze priv->features.touchpad_ctrl_via_ec = !acpi_dev_present("ELAN0634", NULL, -1); 15321c59de4aSBarnabás Pőcze 15331c59de4aSBarnabás Pőcze if (!read_ec_data(handle, VPCCMD_R_FAN, &val)) 15341c59de4aSBarnabás Pőcze priv->features.fan_mode = true; 15351c59de4aSBarnabás Pőcze 15361c59de4aSBarnabás Pőcze if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) 15371c59de4aSBarnabás Pőcze priv->features.conservation_mode = true; 15381c59de4aSBarnabás Pőcze 15391c59de4aSBarnabás Pőcze if (acpi_has_method(handle, "DYTC")) 15401c59de4aSBarnabás Pőcze priv->features.dytc = true; 15411c59de4aSBarnabás Pőcze 1542392cbf0aSBarnabás Pőcze if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) { 1543392cbf0aSBarnabás Pőcze if (!eval_hals(handle, &val)) { 1544392cbf0aSBarnabás Pőcze if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val)) 15451c59de4aSBarnabás Pőcze priv->features.fn_lock = true; 1546503325f8SBarnabás Pőcze 1547503325f8SBarnabás Pőcze if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) 1548503325f8SBarnabás Pőcze priv->features.kbd_bl = true; 15496b49dea4SBarnabás Pőcze 15506b49dea4SBarnabás Pőcze if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val)) 15516b49dea4SBarnabás Pőcze priv->features.usb_charging = true; 15521c59de4aSBarnabás Pőcze } 1553392cbf0aSBarnabás Pőcze } 1554392cbf0aSBarnabás Pőcze } 15551c59de4aSBarnabás Pőcze 1556b5c37b79SZhang Rui static int ideapad_acpi_add(struct platform_device *pdev) 1557b5c37b79SZhang Rui { 1558*043449e7SRafael J. Wysocki struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 1559b5c37b79SZhang Rui struct ideapad_private *priv; 1560803be832SBarnabás Pőcze acpi_status status; 1561ff36b0d9SBarnabás Pőcze unsigned long cfg; 156265c7713aSBarnabás Pőcze int err, i; 1563b5c37b79SZhang Rui 1564*043449e7SRafael J. Wysocki if (!adev || eval_int(adev->handle, "_CFG", &cfg)) 1565b5c37b79SZhang Rui return -ENODEV; 1566b5c37b79SZhang Rui 1567b3facd7bSHimangi Saraogi priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 1568b5c37b79SZhang Rui if (!priv) 1569b5c37b79SZhang Rui return -ENOMEM; 1570b5c37b79SZhang Rui 1571b5c37b79SZhang Rui dev_set_drvdata(&pdev->dev, priv); 157265c7713aSBarnabás Pőcze 1573b5c37b79SZhang Rui priv->cfg = cfg; 1574b5c37b79SZhang Rui priv->adev = adev; 1575b5c37b79SZhang Rui priv->platform_device = pdev; 1576b5c37b79SZhang Rui 15771c59de4aSBarnabás Pőcze ideapad_check_features(priv); 1578d69cd7eeSJiaxun Yang 157965c7713aSBarnabás Pőcze err = ideapad_sysfs_init(priv); 158065c7713aSBarnabás Pőcze if (err) 158165c7713aSBarnabás Pőcze return err; 1582b5c37b79SZhang Rui 158317f1bf38SGreg Kroah-Hartman ideapad_debugfs_init(priv); 1584b5c37b79SZhang Rui 158565c7713aSBarnabás Pőcze err = ideapad_input_init(priv); 158665c7713aSBarnabás Pőcze if (err) 1587b5c37b79SZhang Rui goto input_failed; 1588b5c37b79SZhang Rui 1589503325f8SBarnabás Pőcze err = ideapad_kbd_bl_init(priv); 1590503325f8SBarnabás Pőcze if (err) { 1591503325f8SBarnabás Pőcze if (err != -ENODEV) 1592503325f8SBarnabás Pőcze dev_warn(&pdev->dev, "Could not set up keyboard backlight LED: %d\n", err); 1593503325f8SBarnabás Pőcze else 1594503325f8SBarnabás Pőcze dev_info(&pdev->dev, "Keyboard backlight control not available\n"); 1595503325f8SBarnabás Pőcze } 1596503325f8SBarnabás Pőcze 1597ce363c2bSHans de Goede /* 1598ce363c2bSHans de Goede * On some models without a hw-switch (the yoga 2 13 at least) 1599ce363c2bSHans de Goede * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. 1600ce363c2bSHans de Goede */ 16011c59de4aSBarnabás Pőcze if (!priv->features.hw_rfkill_switch) 1602ce363c2bSHans de Goede write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); 1603ce363c2bSHans de Goede 1604d69cd7eeSJiaxun Yang /* The same for Touchpad */ 16051c59de4aSBarnabás Pőcze if (!priv->features.touchpad_ctrl_via_ec) 1606d69cd7eeSJiaxun Yang write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, 1); 1607d69cd7eeSJiaxun Yang 160885093f79SHans de Goede for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1609b5c37b79SZhang Rui if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) 1610b5c37b79SZhang Rui ideapad_register_rfkill(priv, i); 1611ce363c2bSHans de Goede 1612b5c37b79SZhang Rui ideapad_sync_rfk_state(priv); 1613b5c37b79SZhang Rui ideapad_sync_touchpad_state(priv); 1614b5c37b79SZhang Rui 161565c7713aSBarnabás Pőcze err = ideapad_dytc_profile_init(priv); 161665c7713aSBarnabás Pőcze if (err) { 161765c7713aSBarnabás Pőcze if (err != -ENODEV) 161865c7713aSBarnabás Pőcze dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", err); 16191c59de4aSBarnabás Pőcze else 16201c59de4aSBarnabás Pőcze dev_info(&pdev->dev, "DYTC interface is not available\n"); 16211c59de4aSBarnabás Pőcze } 1622eabe5339SJiaxun Yang 162326bff5f0SHans de Goede if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { 162465c7713aSBarnabás Pőcze err = ideapad_backlight_init(priv); 162565c7713aSBarnabás Pőcze if (err && err != -ENODEV) 1626b5c37b79SZhang Rui goto backlight_failed; 1627b5c37b79SZhang Rui } 162865c7713aSBarnabás Pőcze 1629803be832SBarnabás Pőcze status = acpi_install_notify_handler(adev->handle, 1630803be832SBarnabás Pőcze ACPI_DEVICE_NOTIFY, 1631803be832SBarnabás Pőcze ideapad_acpi_notify, priv); 1632803be832SBarnabás Pőcze if (ACPI_FAILURE(status)) { 163365c7713aSBarnabás Pőcze err = -EIO; 1634b5c37b79SZhang Rui goto notification_failed; 1635803be832SBarnabás Pőcze } 16362d98e0b9SArnd Bergmann 163774caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 16382d98e0b9SArnd Bergmann for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { 1639803be832SBarnabás Pőcze status = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], 16402d98e0b9SArnd Bergmann ideapad_wmi_notify, priv); 1641803be832SBarnabás Pőcze if (ACPI_SUCCESS(status)) { 16422d98e0b9SArnd Bergmann priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; 16432d98e0b9SArnd Bergmann break; 16442d98e0b9SArnd Bergmann } 16452d98e0b9SArnd Bergmann } 164665c7713aSBarnabás Pőcze 1647803be832SBarnabás Pőcze if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) { 164865c7713aSBarnabás Pőcze err = -EIO; 164974caab99SArnd Bergmann goto notification_failed_wmi; 1650803be832SBarnabás Pőcze } 165174caab99SArnd Bergmann #endif 1652b5c37b79SZhang Rui 1653b5c37b79SZhang Rui return 0; 165465c7713aSBarnabás Pőcze 165574caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 165674caab99SArnd Bergmann notification_failed_wmi: 165774caab99SArnd Bergmann acpi_remove_notify_handler(priv->adev->handle, 165865c7713aSBarnabás Pőcze ACPI_DEVICE_NOTIFY, 165965c7713aSBarnabás Pőcze ideapad_acpi_notify); 166074caab99SArnd Bergmann #endif 166165c7713aSBarnabás Pőcze 1662b5c37b79SZhang Rui notification_failed: 1663b5c37b79SZhang Rui ideapad_backlight_exit(priv); 166465c7713aSBarnabás Pőcze 1665b5c37b79SZhang Rui backlight_failed: 1666caa315b8SBarnabás Pőcze ideapad_dytc_profile_exit(priv); 166765c7713aSBarnabás Pőcze 1668b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1669b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 167065c7713aSBarnabás Pőcze 1671503325f8SBarnabás Pőcze ideapad_kbd_bl_exit(priv); 1672b5c37b79SZhang Rui ideapad_input_exit(priv); 167365c7713aSBarnabás Pőcze 1674b5c37b79SZhang Rui input_failed: 1675b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1676b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 167765c7713aSBarnabás Pőcze 167865c7713aSBarnabás Pőcze return err; 1679b5c37b79SZhang Rui } 1680b5c37b79SZhang Rui 1681b5c37b79SZhang Rui static int ideapad_acpi_remove(struct platform_device *pdev) 1682b5c37b79SZhang Rui { 1683b5c37b79SZhang Rui struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); 1684b5c37b79SZhang Rui int i; 1685b5c37b79SZhang Rui 168674caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 16872d98e0b9SArnd Bergmann if (priv->fnesc_guid) 16882d98e0b9SArnd Bergmann wmi_remove_notify_handler(priv->fnesc_guid); 168974caab99SArnd Bergmann #endif 169065c7713aSBarnabás Pőcze 1691b5c37b79SZhang Rui acpi_remove_notify_handler(priv->adev->handle, 169265c7713aSBarnabás Pőcze ACPI_DEVICE_NOTIFY, 169365c7713aSBarnabás Pőcze ideapad_acpi_notify); 169465c7713aSBarnabás Pőcze 1695b5c37b79SZhang Rui ideapad_backlight_exit(priv); 1696eabe5339SJiaxun Yang ideapad_dytc_profile_exit(priv); 169765c7713aSBarnabás Pőcze 1698b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1699b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 170065c7713aSBarnabás Pőcze 1701503325f8SBarnabás Pőcze ideapad_kbd_bl_exit(priv); 1702b5c37b79SZhang Rui ideapad_input_exit(priv); 1703b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1704b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 1705b5c37b79SZhang Rui 1706b5c37b79SZhang Rui return 0; 1707b5c37b79SZhang Rui } 1708b5c37b79SZhang Rui 170911fa8da5SZhang Rui #ifdef CONFIG_PM_SLEEP 1710e1a39a44SBarnabás Pőcze static int ideapad_acpi_resume(struct device *dev) 171107a4a4fcSMaxim Mikityanskiy { 1712e1a39a44SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 171375a11f11SZhang Rui 171475a11f11SZhang Rui ideapad_sync_rfk_state(priv); 171575a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 1716eabe5339SJiaxun Yang 1717eabe5339SJiaxun Yang if (priv->dytc) 1718eabe5339SJiaxun Yang dytc_profile_refresh(priv); 1719eabe5339SJiaxun Yang 172007a4a4fcSMaxim Mikityanskiy return 0; 172107a4a4fcSMaxim Mikityanskiy } 1722b5c37b79SZhang Rui #endif 172307a4a4fcSMaxim Mikityanskiy static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); 172407a4a4fcSMaxim Mikityanskiy 1725b5c37b79SZhang Rui static const struct acpi_device_id ideapad_device_ids[] = { 1726b5c37b79SZhang Rui {"VPC2004", 0}, 1727b5c37b79SZhang Rui {"", 0}, 172857ac3b05SIke Panhc }; 1729b5c37b79SZhang Rui MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 1730b5c37b79SZhang Rui 1731b5c37b79SZhang Rui static struct platform_driver ideapad_acpi_driver = { 1732b5c37b79SZhang Rui .probe = ideapad_acpi_add, 1733b5c37b79SZhang Rui .remove = ideapad_acpi_remove, 1734b5c37b79SZhang Rui .driver = { 1735b5c37b79SZhang Rui .name = "ideapad_acpi", 1736b5c37b79SZhang Rui .pm = &ideapad_pm, 1737b5c37b79SZhang Rui .acpi_match_table = ACPI_PTR(ideapad_device_ids), 1738b5c37b79SZhang Rui }, 1739b5c37b79SZhang Rui }; 1740b5c37b79SZhang Rui 1741b5c37b79SZhang Rui module_platform_driver(ideapad_acpi_driver); 174257ac3b05SIke Panhc 174357ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 174457ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 174557ac3b05SIke Panhc MODULE_LICENSE("GPL"); 1746