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 */ 44*3ae86d2dSMeng 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 871eabe5339SJiaxun Yang static int ideapad_dytc_profile_init(struct ideapad_private *priv) 872eabe5339SJiaxun Yang { 873ff36b0d9SBarnabás Pőcze int err, dytc_version; 874ff36b0d9SBarnabás Pőcze unsigned long output; 875eabe5339SJiaxun Yang 8761c59de4aSBarnabás Pőcze if (!priv->features.dytc) 8771c59de4aSBarnabás Pőcze return -ENODEV; 8781c59de4aSBarnabás Pőcze 879ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); 880eabe5339SJiaxun Yang /* For all other errors we can flag the failure */ 881eabe5339SJiaxun Yang if (err) 882eabe5339SJiaxun Yang return err; 883eabe5339SJiaxun Yang 884eabe5339SJiaxun Yang /* Check DYTC is enabled and supports mode setting */ 88565c7713aSBarnabás Pőcze if (!test_bit(DYTC_QUERY_ENABLE_BIT, &output)) 886eabe5339SJiaxun Yang return -ENODEV; 887eabe5339SJiaxun Yang 888eabe5339SJiaxun Yang dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; 889eabe5339SJiaxun Yang if (dytc_version < 5) 890eabe5339SJiaxun Yang return -ENODEV; 891eabe5339SJiaxun Yang 89265c7713aSBarnabás Pőcze priv->dytc = kzalloc(sizeof(*priv->dytc), GFP_KERNEL); 893eabe5339SJiaxun Yang if (!priv->dytc) 894eabe5339SJiaxun Yang return -ENOMEM; 895eabe5339SJiaxun Yang 896eabe5339SJiaxun Yang mutex_init(&priv->dytc->mutex); 897eabe5339SJiaxun Yang 898eabe5339SJiaxun Yang priv->dytc->priv = priv; 899eabe5339SJiaxun Yang priv->dytc->pprof.profile_get = dytc_profile_get; 900eabe5339SJiaxun Yang priv->dytc->pprof.profile_set = dytc_profile_set; 901eabe5339SJiaxun Yang 902eabe5339SJiaxun Yang /* Setup supported modes */ 903eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices); 904eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices); 905eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices); 906eabe5339SJiaxun Yang 907eabe5339SJiaxun Yang /* Create platform_profile structure and register */ 908eabe5339SJiaxun Yang err = platform_profile_register(&priv->dytc->pprof); 909eabe5339SJiaxun Yang if (err) 91065c7713aSBarnabás Pőcze goto pp_reg_failed; 911eabe5339SJiaxun Yang 912eabe5339SJiaxun Yang /* Ensure initial values are correct */ 913eabe5339SJiaxun Yang dytc_profile_refresh(priv); 914eabe5339SJiaxun Yang 915eabe5339SJiaxun Yang return 0; 916eabe5339SJiaxun Yang 91765c7713aSBarnabás Pőcze pp_reg_failed: 918eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 919eabe5339SJiaxun Yang kfree(priv->dytc); 920eabe5339SJiaxun Yang priv->dytc = NULL; 92165c7713aSBarnabás Pőcze 922eabe5339SJiaxun Yang return err; 923eabe5339SJiaxun Yang } 924eabe5339SJiaxun Yang 925eabe5339SJiaxun Yang static void ideapad_dytc_profile_exit(struct ideapad_private *priv) 926eabe5339SJiaxun Yang { 927eabe5339SJiaxun Yang if (!priv->dytc) 928eabe5339SJiaxun Yang return; 929eabe5339SJiaxun Yang 930eabe5339SJiaxun Yang platform_profile_remove(); 931eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 932eabe5339SJiaxun Yang kfree(priv->dytc); 93365c7713aSBarnabás Pőcze 934eabe5339SJiaxun Yang priv->dytc = NULL; 935eabe5339SJiaxun Yang } 936eabe5339SJiaxun Yang 937eabe5339SJiaxun Yang /* 938a4b5a279SIke Panhc * Rfkill 939a4b5a279SIke Panhc */ 940c1f73658SIke Panhc struct ideapad_rfk_data { 941c1f73658SIke Panhc char *name; 942c1f73658SIke Panhc int cfgbit; 943c1f73658SIke Panhc int opcode; 944c1f73658SIke Panhc int type; 945c1f73658SIke Panhc }; 946c1f73658SIke Panhc 947b3d94d70SMathias Krause static const struct ideapad_rfk_data ideapad_rfk_data[] = { 9480b765671SBarnabás Pőcze { "ideapad_wlan", CFG_CAP_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, 9490b765671SBarnabás Pőcze { "ideapad_bluetooth", CFG_CAP_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, 9500b765671SBarnabás Pőcze { "ideapad_3g", CFG_CAP_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, 951c1f73658SIke Panhc }; 952c1f73658SIke Panhc 95357ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked) 95457ac3b05SIke Panhc { 955331e0ea2SZhang Rui struct ideapad_rfk_priv *priv = data; 9564b200b46SArnd Bergmann int opcode = ideapad_rfk_data[priv->dev].opcode; 95757ac3b05SIke Panhc 9584b200b46SArnd Bergmann return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked); 95957ac3b05SIke Panhc } 96057ac3b05SIke Panhc 9613d59dfcdSBhumika Goyal static const struct rfkill_ops ideapad_rfk_ops = { 96257ac3b05SIke Panhc .set_block = ideapad_rfk_set, 96357ac3b05SIke Panhc }; 96457ac3b05SIke Panhc 965923de84aSIke Panhc static void ideapad_sync_rfk_state(struct ideapad_private *priv) 96657ac3b05SIke Panhc { 967ce363c2bSHans de Goede unsigned long hw_blocked = 0; 96857ac3b05SIke Panhc int i; 96957ac3b05SIke Panhc 9701c59de4aSBarnabás Pőcze if (priv->features.hw_rfkill_switch) { 971331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) 97257ac3b05SIke Panhc return; 97357ac3b05SIke Panhc hw_blocked = !hw_blocked; 974ce363c2bSHans de Goede } 97557ac3b05SIke Panhc 976c1f73658SIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 97757ac3b05SIke Panhc if (priv->rfk[i]) 97857ac3b05SIke Panhc rfkill_set_hw_state(priv->rfk[i], hw_blocked); 97957ac3b05SIke Panhc } 98057ac3b05SIke Panhc 98175a11f11SZhang Rui static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) 98257ac3b05SIke Panhc { 98365c7713aSBarnabás Pőcze unsigned long rf_enabled; 98465c7713aSBarnabás Pőcze int err; 98557ac3b05SIke Panhc 98665c7713aSBarnabás Pőcze if (no_bt_rfkill && ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH) { 987bfa97b7dSIke Panhc /* Force to enable bluetooth when no_bt_rfkill=1 */ 98865c7713aSBarnabás Pőcze write_ec_cmd(priv->adev->handle, ideapad_rfk_data[dev].opcode, 1); 989bfa97b7dSIke Panhc return 0; 990bfa97b7dSIke Panhc } 99165c7713aSBarnabás Pőcze 992331e0ea2SZhang Rui priv->rfk_priv[dev].dev = dev; 993331e0ea2SZhang Rui priv->rfk_priv[dev].priv = priv; 994bfa97b7dSIke Panhc 99575a11f11SZhang Rui priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, 996b5c37b79SZhang Rui &priv->platform_device->dev, 99775a11f11SZhang Rui ideapad_rfk_data[dev].type, 99875a11f11SZhang Rui &ideapad_rfk_ops, 999331e0ea2SZhang Rui &priv->rfk_priv[dev]); 100057ac3b05SIke Panhc if (!priv->rfk[dev]) 100157ac3b05SIke Panhc return -ENOMEM; 100257ac3b05SIke Panhc 100365c7713aSBarnabás Pőcze err = read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode - 1, &rf_enabled); 100465c7713aSBarnabás Pőcze if (err) 100565c7713aSBarnabás Pőcze rf_enabled = 1; 100657ac3b05SIke Panhc 100765c7713aSBarnabás Pőcze rfkill_init_sw_state(priv->rfk[dev], !rf_enabled); 100865c7713aSBarnabás Pőcze 100965c7713aSBarnabás Pőcze err = rfkill_register(priv->rfk[dev]); 101065c7713aSBarnabás Pőcze if (err) 101157ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 101265c7713aSBarnabás Pőcze 101365c7713aSBarnabás Pőcze return err; 101457ac3b05SIke Panhc } 101557ac3b05SIke Panhc 101675a11f11SZhang Rui static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) 101757ac3b05SIke Panhc { 101857ac3b05SIke Panhc if (!priv->rfk[dev]) 101957ac3b05SIke Panhc return; 102057ac3b05SIke Panhc 102157ac3b05SIke Panhc rfkill_unregister(priv->rfk[dev]); 102257ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 102357ac3b05SIke Panhc } 102457ac3b05SIke Panhc 102598ee6919SIke Panhc /* 102698ee6919SIke Panhc * Platform device 102798ee6919SIke Panhc */ 1028b5c37b79SZhang Rui static int ideapad_sysfs_init(struct ideapad_private *priv) 102998ee6919SIke Panhc { 10308782d8d7SBarnabás Pőcze return device_add_group(&priv->platform_device->dev, 1031c9f718d0SIke Panhc &ideapad_attribute_group); 103298ee6919SIke Panhc } 103398ee6919SIke Panhc 1034b5c37b79SZhang Rui static void ideapad_sysfs_exit(struct ideapad_private *priv) 103598ee6919SIke Panhc { 10368782d8d7SBarnabás Pőcze device_remove_group(&priv->platform_device->dev, 1037c9f718d0SIke Panhc &ideapad_attribute_group); 103898ee6919SIke Panhc } 103998ee6919SIke Panhc 1040f63409aeSIke Panhc /* 1041f63409aeSIke Panhc * input device 1042f63409aeSIke Panhc */ 1043f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = { 1044f43d9ec0SIke Panhc { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, 1045296f9fe0SMaxim Mikityanskiy { KE_KEY, 7, { KEY_CAMERA } }, 104648f67d62SAlex Hung { KE_KEY, 8, { KEY_MICMUTE } }, 1047296f9fe0SMaxim Mikityanskiy { KE_KEY, 11, { KEY_F16 } }, 1048f43d9ec0SIke Panhc { KE_KEY, 13, { KEY_WLAN } }, 1049f43d9ec0SIke Panhc { KE_KEY, 16, { KEY_PROG1 } }, 1050f43d9ec0SIke Panhc { KE_KEY, 17, { KEY_PROG2 } }, 1051296f9fe0SMaxim Mikityanskiy { KE_KEY, 64, { KEY_PROG3 } }, 1052296f9fe0SMaxim Mikityanskiy { KE_KEY, 65, { KEY_PROG4 } }, 105307a4a4fcSMaxim Mikityanskiy { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, 105407a4a4fcSMaxim Mikityanskiy { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, 105574caab99SArnd Bergmann { KE_KEY, 128, { KEY_ESC } }, 105665c7713aSBarnabás Pőcze { KE_END }, 1057f63409aeSIke Panhc }; 1058f63409aeSIke Panhc 1059b859f159SGreg Kroah-Hartman static int ideapad_input_init(struct ideapad_private *priv) 1060f63409aeSIke Panhc { 1061f63409aeSIke Panhc struct input_dev *inputdev; 106265c7713aSBarnabás Pőcze int err; 1063f63409aeSIke Panhc 1064f63409aeSIke Panhc inputdev = input_allocate_device(); 1065b222cca6SJoe Perches if (!inputdev) 1066f63409aeSIke Panhc return -ENOMEM; 1067f63409aeSIke Panhc 1068f63409aeSIke Panhc inputdev->name = "Ideapad extra buttons"; 1069f63409aeSIke Panhc inputdev->phys = "ideapad/input0"; 1070f63409aeSIke Panhc inputdev->id.bustype = BUS_HOST; 10718693ae84SIke Panhc inputdev->dev.parent = &priv->platform_device->dev; 1072f63409aeSIke Panhc 107365c7713aSBarnabás Pőcze err = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); 107465c7713aSBarnabás Pőcze if (err) { 1075654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 107665c7713aSBarnabás Pőcze "Could not set up input device keymap: %d\n", err); 1077f63409aeSIke Panhc goto err_free_dev; 1078f63409aeSIke Panhc } 1079f63409aeSIke Panhc 108065c7713aSBarnabás Pőcze err = input_register_device(inputdev); 108165c7713aSBarnabás Pőcze if (err) { 1082654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 108365c7713aSBarnabás Pőcze "Could not register input device: %d\n", err); 1084c973d4b5SMichał Kępień goto err_free_dev; 1085f63409aeSIke Panhc } 1086f63409aeSIke Panhc 10878693ae84SIke Panhc priv->inputdev = inputdev; 108865c7713aSBarnabás Pőcze 1089f63409aeSIke Panhc return 0; 1090f63409aeSIke Panhc 1091f63409aeSIke Panhc err_free_dev: 1092f63409aeSIke Panhc input_free_device(inputdev); 109365c7713aSBarnabás Pőcze 109465c7713aSBarnabás Pőcze return err; 1095f63409aeSIke Panhc } 1096f63409aeSIke Panhc 10977451a55aSAxel Lin static void ideapad_input_exit(struct ideapad_private *priv) 1098f63409aeSIke Panhc { 10998693ae84SIke Panhc input_unregister_device(priv->inputdev); 11008693ae84SIke Panhc priv->inputdev = NULL; 1101f63409aeSIke Panhc } 1102f63409aeSIke Panhc 11038693ae84SIke Panhc static void ideapad_input_report(struct ideapad_private *priv, 11048693ae84SIke Panhc unsigned long scancode) 1105f63409aeSIke Panhc { 11068693ae84SIke Panhc sparse_keymap_report_event(priv->inputdev, scancode, 1, true); 1107f63409aeSIke Panhc } 1108f63409aeSIke Panhc 1109f43d9ec0SIke Panhc static void ideapad_input_novokey(struct ideapad_private *priv) 1110f43d9ec0SIke Panhc { 1111f43d9ec0SIke Panhc unsigned long long_pressed; 1112f43d9ec0SIke Panhc 1113331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) 1114f43d9ec0SIke Panhc return; 111565c7713aSBarnabás Pőcze 1116f43d9ec0SIke Panhc if (long_pressed) 1117f43d9ec0SIke Panhc ideapad_input_report(priv, 17); 1118f43d9ec0SIke Panhc else 1119f43d9ec0SIke Panhc ideapad_input_report(priv, 16); 1120f43d9ec0SIke Panhc } 1121f43d9ec0SIke Panhc 1122296f9fe0SMaxim Mikityanskiy static void ideapad_check_special_buttons(struct ideapad_private *priv) 1123296f9fe0SMaxim Mikityanskiy { 1124296f9fe0SMaxim Mikityanskiy unsigned long bit, value; 1125296f9fe0SMaxim Mikityanskiy 11267be193e3SBarnabás Pőcze if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) 11277be193e3SBarnabás Pőcze return; 1128296f9fe0SMaxim Mikityanskiy 11290c4915b6SBarnabás Pőcze for_each_set_bit (bit, &value, 16) { 1130296f9fe0SMaxim Mikityanskiy switch (bit) { 1131a1ec56edSMaxim Mikityanskiy case 6: /* Z570 */ 113265c7713aSBarnabás Pőcze case 0: /* Z580 */ 1133296f9fe0SMaxim Mikityanskiy /* Thermal Management button */ 1134296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 65); 1135296f9fe0SMaxim Mikityanskiy break; 1136296f9fe0SMaxim Mikityanskiy case 1: 1137296f9fe0SMaxim Mikityanskiy /* OneKey Theater button */ 1138296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 64); 1139296f9fe0SMaxim Mikityanskiy break; 1140a1ec56edSMaxim Mikityanskiy default: 1141654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1142654324c4SBarnabás Pőcze "Unknown special button: %lu\n", bit); 1143a1ec56edSMaxim Mikityanskiy break; 1144296f9fe0SMaxim Mikityanskiy } 1145296f9fe0SMaxim Mikityanskiy } 1146296f9fe0SMaxim Mikityanskiy } 1147296f9fe0SMaxim Mikityanskiy 1148a4b5a279SIke Panhc /* 1149a4ecbb8aSIke Panhc * backlight 1150a4ecbb8aSIke Panhc */ 1151a4ecbb8aSIke Panhc static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) 1152a4ecbb8aSIke Panhc { 1153331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 1154a4ecbb8aSIke Panhc unsigned long now; 11557be193e3SBarnabás Pőcze int err; 1156a4ecbb8aSIke Panhc 11577be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 11587be193e3SBarnabás Pőcze if (err) 11597be193e3SBarnabás Pőcze return err; 116065c7713aSBarnabás Pőcze 1161a4ecbb8aSIke Panhc return now; 1162a4ecbb8aSIke Panhc } 1163a4ecbb8aSIke Panhc 1164a4ecbb8aSIke Panhc static int ideapad_backlight_update_status(struct backlight_device *blightdev) 1165a4ecbb8aSIke Panhc { 1166331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 11677be193e3SBarnabás Pőcze int err; 1168331e0ea2SZhang Rui 11697be193e3SBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, 11707be193e3SBarnabás Pőcze blightdev->props.brightness); 11717be193e3SBarnabás Pőcze if (err) 11727be193e3SBarnabás Pőcze return err; 117365c7713aSBarnabás Pőcze 11747be193e3SBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, 11757be193e3SBarnabás Pőcze blightdev->props.power != FB_BLANK_POWERDOWN); 11767be193e3SBarnabás Pőcze if (err) 11777be193e3SBarnabás Pőcze return err; 1178a4ecbb8aSIke Panhc 1179a4ecbb8aSIke Panhc return 0; 1180a4ecbb8aSIke Panhc } 1181a4ecbb8aSIke Panhc 1182a4ecbb8aSIke Panhc static const struct backlight_ops ideapad_backlight_ops = { 1183a4ecbb8aSIke Panhc .get_brightness = ideapad_backlight_get_brightness, 1184a4ecbb8aSIke Panhc .update_status = ideapad_backlight_update_status, 1185a4ecbb8aSIke Panhc }; 1186a4ecbb8aSIke Panhc 1187a4ecbb8aSIke Panhc static int ideapad_backlight_init(struct ideapad_private *priv) 1188a4ecbb8aSIke Panhc { 1189a4ecbb8aSIke Panhc struct backlight_device *blightdev; 1190a4ecbb8aSIke Panhc struct backlight_properties props; 1191a4ecbb8aSIke Panhc unsigned long max, now, power; 11927be193e3SBarnabás Pőcze int err; 1193a4ecbb8aSIke Panhc 11947be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max); 11957be193e3SBarnabás Pőcze if (err) 11967be193e3SBarnabás Pőcze return err; 119765c7713aSBarnabás Pőcze 11987be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 11997be193e3SBarnabás Pőcze if (err) 12007be193e3SBarnabás Pőcze return err; 120165c7713aSBarnabás Pőcze 12027be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power); 12037be193e3SBarnabás Pőcze if (err) 12047be193e3SBarnabás Pőcze return err; 1205a4ecbb8aSIke Panhc 120665c7713aSBarnabás Pőcze memset(&props, 0, sizeof(props)); 120765c7713aSBarnabás Pőcze 1208a4ecbb8aSIke Panhc props.max_brightness = max; 1209a4ecbb8aSIke Panhc props.type = BACKLIGHT_PLATFORM; 121065c7713aSBarnabás Pőcze 1211a4ecbb8aSIke Panhc blightdev = backlight_device_register("ideapad", 1212a4ecbb8aSIke Panhc &priv->platform_device->dev, 1213a4ecbb8aSIke Panhc priv, 1214a4ecbb8aSIke Panhc &ideapad_backlight_ops, 1215a4ecbb8aSIke Panhc &props); 1216a4ecbb8aSIke Panhc if (IS_ERR(blightdev)) { 121765c7713aSBarnabás Pőcze err = PTR_ERR(blightdev); 1218654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 121965c7713aSBarnabás Pőcze "Could not register backlight device: %d\n", err); 122065c7713aSBarnabás Pőcze return err; 1221a4ecbb8aSIke Panhc } 1222a4ecbb8aSIke Panhc 1223a4ecbb8aSIke Panhc priv->blightdev = blightdev; 1224a4ecbb8aSIke Panhc blightdev->props.brightness = now; 1225a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 122665c7713aSBarnabás Pőcze 1227a4ecbb8aSIke Panhc backlight_update_status(blightdev); 1228a4ecbb8aSIke Panhc 1229a4ecbb8aSIke Panhc return 0; 1230a4ecbb8aSIke Panhc } 1231a4ecbb8aSIke Panhc 1232a4ecbb8aSIke Panhc static void ideapad_backlight_exit(struct ideapad_private *priv) 1233a4ecbb8aSIke Panhc { 1234a4ecbb8aSIke Panhc backlight_device_unregister(priv->blightdev); 1235a4ecbb8aSIke Panhc priv->blightdev = NULL; 1236a4ecbb8aSIke Panhc } 1237a4ecbb8aSIke Panhc 1238a4ecbb8aSIke Panhc static void ideapad_backlight_notify_power(struct ideapad_private *priv) 1239a4ecbb8aSIke Panhc { 1240a4ecbb8aSIke Panhc struct backlight_device *blightdev = priv->blightdev; 124165c7713aSBarnabás Pőcze unsigned long power; 1242a4ecbb8aSIke Panhc 1243d4afc775SRene Bollford if (!blightdev) 1244d4afc775SRene Bollford return; 124565c7713aSBarnabás Pőcze 1246331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) 1247a4ecbb8aSIke Panhc return; 124865c7713aSBarnabás Pőcze 1249a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 1250a4ecbb8aSIke Panhc } 1251a4ecbb8aSIke Panhc 1252a4ecbb8aSIke Panhc static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) 1253a4ecbb8aSIke Panhc { 1254a4ecbb8aSIke Panhc unsigned long now; 1255a4ecbb8aSIke Panhc 1256a4ecbb8aSIke Panhc /* if we control brightness via acpi video driver */ 125765c7713aSBarnabás Pőcze if (!priv->blightdev) 1258331e0ea2SZhang Rui read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 125965c7713aSBarnabás Pőcze else 1260a4ecbb8aSIke Panhc backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); 1261a4ecbb8aSIke Panhc } 1262a4ecbb8aSIke Panhc 1263a4ecbb8aSIke Panhc /* 1264503325f8SBarnabás Pőcze * keyboard backlight 1265503325f8SBarnabás Pőcze */ 1266503325f8SBarnabás Pőcze static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv) 1267503325f8SBarnabás Pőcze { 1268503325f8SBarnabás Pőcze unsigned long hals; 1269503325f8SBarnabás Pőcze int err; 1270503325f8SBarnabás Pőcze 1271503325f8SBarnabás Pőcze err = eval_hals(priv->adev->handle, &hals); 1272503325f8SBarnabás Pőcze if (err) 1273503325f8SBarnabás Pőcze return err; 1274503325f8SBarnabás Pőcze 1275503325f8SBarnabás Pőcze return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals); 1276503325f8SBarnabás Pőcze } 1277503325f8SBarnabás Pőcze 1278503325f8SBarnabás Pőcze static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev) 1279503325f8SBarnabás Pőcze { 1280503325f8SBarnabás Pőcze struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); 1281503325f8SBarnabás Pőcze 1282503325f8SBarnabás Pőcze return ideapad_kbd_bl_brightness_get(priv); 1283503325f8SBarnabás Pőcze } 1284503325f8SBarnabás Pőcze 1285503325f8SBarnabás Pőcze static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness) 1286503325f8SBarnabás Pőcze { 1287503325f8SBarnabás Pőcze int err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF); 1288503325f8SBarnabás Pőcze 1289503325f8SBarnabás Pőcze if (err) 1290503325f8SBarnabás Pőcze return err; 1291503325f8SBarnabás Pőcze 1292503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1293503325f8SBarnabás Pőcze 1294503325f8SBarnabás Pőcze return 0; 1295503325f8SBarnabás Pőcze } 1296503325f8SBarnabás Pőcze 1297503325f8SBarnabás Pőcze static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev, 1298503325f8SBarnabás Pőcze enum led_brightness brightness) 1299503325f8SBarnabás Pőcze { 1300503325f8SBarnabás Pőcze struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); 1301503325f8SBarnabás Pőcze 1302503325f8SBarnabás Pőcze return ideapad_kbd_bl_brightness_set(priv, brightness); 1303503325f8SBarnabás Pőcze } 1304503325f8SBarnabás Pőcze 1305503325f8SBarnabás Pőcze static void ideapad_kbd_bl_notify(struct ideapad_private *priv) 1306503325f8SBarnabás Pőcze { 1307503325f8SBarnabás Pőcze int brightness; 1308503325f8SBarnabás Pőcze 1309503325f8SBarnabás Pőcze if (!priv->kbd_bl.initialized) 1310503325f8SBarnabás Pőcze return; 1311503325f8SBarnabás Pőcze 1312503325f8SBarnabás Pőcze brightness = ideapad_kbd_bl_brightness_get(priv); 1313503325f8SBarnabás Pőcze if (brightness < 0) 1314503325f8SBarnabás Pőcze return; 1315503325f8SBarnabás Pőcze 1316503325f8SBarnabás Pőcze if (brightness == priv->kbd_bl.last_brightness) 1317503325f8SBarnabás Pőcze return; 1318503325f8SBarnabás Pőcze 1319503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1320503325f8SBarnabás Pőcze 1321503325f8SBarnabás Pőcze led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness); 1322503325f8SBarnabás Pőcze } 1323503325f8SBarnabás Pőcze 1324503325f8SBarnabás Pőcze static int ideapad_kbd_bl_init(struct ideapad_private *priv) 1325503325f8SBarnabás Pőcze { 1326503325f8SBarnabás Pőcze int brightness, err; 1327503325f8SBarnabás Pőcze 1328503325f8SBarnabás Pőcze if (!priv->features.kbd_bl) 1329503325f8SBarnabás Pőcze return -ENODEV; 1330503325f8SBarnabás Pőcze 1331503325f8SBarnabás Pőcze if (WARN_ON(priv->kbd_bl.initialized)) 1332503325f8SBarnabás Pőcze return -EEXIST; 1333503325f8SBarnabás Pőcze 1334503325f8SBarnabás Pőcze brightness = ideapad_kbd_bl_brightness_get(priv); 1335503325f8SBarnabás Pőcze if (brightness < 0) 1336503325f8SBarnabás Pőcze return brightness; 1337503325f8SBarnabás Pőcze 1338503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1339503325f8SBarnabás Pőcze 1340503325f8SBarnabás Pőcze priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT; 1341503325f8SBarnabás Pőcze priv->kbd_bl.led.max_brightness = 1; 1342503325f8SBarnabás Pőcze priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get; 1343503325f8SBarnabás Pőcze priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set; 1344503325f8SBarnabás Pőcze priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED; 1345503325f8SBarnabás Pőcze 1346503325f8SBarnabás Pőcze err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led); 1347503325f8SBarnabás Pőcze if (err) 1348503325f8SBarnabás Pőcze return err; 1349503325f8SBarnabás Pőcze 1350503325f8SBarnabás Pőcze priv->kbd_bl.initialized = true; 1351503325f8SBarnabás Pőcze 1352503325f8SBarnabás Pőcze return 0; 1353503325f8SBarnabás Pőcze } 1354503325f8SBarnabás Pőcze 1355503325f8SBarnabás Pőcze static void ideapad_kbd_bl_exit(struct ideapad_private *priv) 1356503325f8SBarnabás Pőcze { 1357503325f8SBarnabás Pőcze if (!priv->kbd_bl.initialized) 1358503325f8SBarnabás Pőcze return; 1359503325f8SBarnabás Pőcze 1360503325f8SBarnabás Pőcze priv->kbd_bl.initialized = false; 1361503325f8SBarnabás Pőcze 1362503325f8SBarnabás Pőcze led_classdev_unregister(&priv->kbd_bl.led); 1363503325f8SBarnabás Pőcze } 1364503325f8SBarnabás Pőcze 1365503325f8SBarnabás Pőcze /* 1366a4b5a279SIke Panhc * module init/exit 1367a4b5a279SIke Panhc */ 136875a11f11SZhang Rui static void ideapad_sync_touchpad_state(struct ideapad_private *priv) 136907a4a4fcSMaxim Mikityanskiy { 137007a4a4fcSMaxim Mikityanskiy unsigned long value; 137107a4a4fcSMaxim Mikityanskiy 13721c59de4aSBarnabás Pőcze if (!priv->features.touchpad_ctrl_via_ec) 1373d69cd7eeSJiaxun Yang return; 1374d69cd7eeSJiaxun Yang 137507a4a4fcSMaxim Mikityanskiy /* Without reading from EC touchpad LED doesn't switch state */ 137675a11f11SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { 137765c7713aSBarnabás Pőcze unsigned char param; 137865c7713aSBarnabás Pőcze /* 137965c7713aSBarnabás Pőcze * Some IdeaPads don't really turn off touchpad - they only 138007a4a4fcSMaxim Mikityanskiy * switch the LED state. We (de)activate KBC AUX port to turn 138107a4a4fcSMaxim Mikityanskiy * touchpad off and on. We send KEY_TOUCHPAD_OFF and 138265c7713aSBarnabás Pőcze * KEY_TOUCHPAD_ON to not to get out of sync with LED 138365c7713aSBarnabás Pőcze */ 138465c7713aSBarnabás Pőcze i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE); 138507a4a4fcSMaxim Mikityanskiy ideapad_input_report(priv, value ? 67 : 66); 1386c6795746SBarnabás Pőcze sysfs_notify(&priv->platform_device->dev.kobj, NULL, "touchpad"); 138707a4a4fcSMaxim Mikityanskiy } 138807a4a4fcSMaxim Mikityanskiy } 138907a4a4fcSMaxim Mikityanskiy 1390b5c37b79SZhang Rui static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) 139157ac3b05SIke Panhc { 1392b5c37b79SZhang Rui struct ideapad_private *priv = data; 13930c4915b6SBarnabás Pőcze unsigned long vpc1, vpc2, bit; 139457ac3b05SIke Panhc 13952be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) 139657ac3b05SIke Panhc return; 139765c7713aSBarnabás Pőcze 13982be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) 139957ac3b05SIke Panhc return; 140057ac3b05SIke Panhc 140157ac3b05SIke Panhc vpc1 = (vpc2 << 8) | vpc1; 14020c4915b6SBarnabás Pőcze 14030c4915b6SBarnabás Pőcze for_each_set_bit (bit, &vpc1, 16) { 14040c4915b6SBarnabás Pőcze switch (bit) { 140520a769c1SIke Panhc case 13: 1406296f9fe0SMaxim Mikityanskiy case 11: 140748f67d62SAlex Hung case 8: 1408296f9fe0SMaxim Mikityanskiy case 7: 140920a769c1SIke Panhc case 6: 14100c4915b6SBarnabás Pőcze ideapad_input_report(priv, bit); 141120a769c1SIke Panhc break; 1412ab66724aSHans de Goede case 10: 1413ab66724aSHans de Goede /* 1414ab66724aSHans de Goede * This event gets send on a Yoga 300-11IBR when the EC 1415ab66724aSHans de Goede * believes that the device has changed between laptop/ 1416ab66724aSHans de Goede * tent/stand/tablet mode. The EC relies on getting 1417ab66724aSHans de Goede * angle info from 2 accelerometers through a special 1418ab66724aSHans de Goede * windows service calling a DSM on the DUAL250E ACPI- 1419ab66724aSHans de Goede * device. Linux does not do this, making the laptop/ 1420ab66724aSHans de Goede * tent/stand/tablet mode info unreliable, so we simply 1421ab66724aSHans de Goede * ignore these events. 1422ab66724aSHans de Goede */ 1423ab66724aSHans de Goede break; 142465c7713aSBarnabás Pőcze case 9: 142565c7713aSBarnabás Pőcze ideapad_sync_rfk_state(priv); 142665c7713aSBarnabás Pőcze break; 142707a4a4fcSMaxim Mikityanskiy case 5: 142875a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 142907a4a4fcSMaxim Mikityanskiy break; 1430a4ecbb8aSIke Panhc case 4: 1431a4ecbb8aSIke Panhc ideapad_backlight_notify_brightness(priv); 1432a4ecbb8aSIke Panhc break; 1433f43d9ec0SIke Panhc case 3: 1434f43d9ec0SIke Panhc ideapad_input_novokey(priv); 1435f43d9ec0SIke Panhc break; 1436a4ecbb8aSIke Panhc case 2: 1437a4ecbb8aSIke Panhc ideapad_backlight_notify_power(priv); 1438a4ecbb8aSIke Panhc break; 14393cfd956bSHao Wei Tee case 1: 144065c7713aSBarnabás Pőcze /* 144165c7713aSBarnabás Pőcze * Some IdeaPads report event 1 every ~20 14423cfd956bSHao Wei Tee * seconds while on battery power; some 14433cfd956bSHao Wei Tee * report this when changing to/from tablet 1444503325f8SBarnabás Pőcze * mode; some report this when the keyboard 1445503325f8SBarnabás Pőcze * backlight has changed. 14463cfd956bSHao Wei Tee */ 1447503325f8SBarnabás Pőcze ideapad_kbd_bl_notify(priv); 14483cfd956bSHao Wei Tee break; 144965c7713aSBarnabás Pőcze case 0: 145065c7713aSBarnabás Pőcze ideapad_check_special_buttons(priv); 145165c7713aSBarnabás Pőcze break; 1452a4ecbb8aSIke Panhc default: 1453654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1454654324c4SBarnabás Pőcze "Unknown event: %lu\n", bit); 145557ac3b05SIke Panhc } 145657ac3b05SIke Panhc } 1457a4ecbb8aSIke Panhc } 145857ac3b05SIke Panhc 145974caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 146074caab99SArnd Bergmann static void ideapad_wmi_notify(u32 value, void *context) 146174caab99SArnd Bergmann { 1462654324c4SBarnabás Pőcze struct ideapad_private *priv = context; 1463*3ae86d2dSMeng Dong unsigned long result; 1464654324c4SBarnabás Pőcze 146574caab99SArnd Bergmann switch (value) { 146674caab99SArnd Bergmann case 128: 1467654324c4SBarnabás Pőcze ideapad_input_report(priv, value); 146874caab99SArnd Bergmann break; 1469*3ae86d2dSMeng Dong case 208: 1470*3ae86d2dSMeng Dong if (!eval_hals(priv->adev->handle, &result)) { 1471*3ae86d2dSMeng Dong bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result); 1472*3ae86d2dSMeng Dong 1473*3ae86d2dSMeng Dong exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); 1474*3ae86d2dSMeng Dong } 1475*3ae86d2dSMeng Dong break; 147674caab99SArnd Bergmann default: 1477654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1478654324c4SBarnabás Pőcze "Unknown WMI event: %u\n", value); 147974caab99SArnd Bergmann } 148074caab99SArnd Bergmann } 148174caab99SArnd Bergmann #endif 148274caab99SArnd Bergmann 1483ce363c2bSHans de Goede /* 14845105e78eSHans de Goede * Some ideapads have a hardware rfkill switch, but most do not have one. 14855105e78eSHans de Goede * Reading VPCCMD_R_RF always results in 0 on models without a hardware rfkill, 14865105e78eSHans de Goede * switch causing ideapad_laptop to wrongly report all radios as hw-blocked. 14875105e78eSHans de Goede * There used to be a long list of DMI ids for models without a hw rfkill 14885105e78eSHans de Goede * switch here, but that resulted in playing whack a mole. 14895105e78eSHans de Goede * More importantly wrongly reporting the wifi radio as hw-blocked, results in 14905105e78eSHans de Goede * non working wifi. Whereas not reporting it hw-blocked, when it actually is 14915105e78eSHans de Goede * hw-blocked results in an empty SSID list, which is a much more benign 14925105e78eSHans de Goede * failure mode. 14935105e78eSHans de Goede * So the default now is the much safer option of assuming there is no 14945105e78eSHans de Goede * hardware rfkill switch. This default also actually matches most hardware, 14955105e78eSHans de Goede * since having a hw rfkill switch is quite rare on modern hardware, so this 14965105e78eSHans de Goede * also leads to a much shorter list. 1497ce363c2bSHans de Goede */ 14985105e78eSHans de Goede static const struct dmi_system_id hw_rfkill_list[] = { 149985093f79SHans de Goede {} 150085093f79SHans de Goede }; 150185093f79SHans de Goede 15021c59de4aSBarnabás Pőcze static void ideapad_check_features(struct ideapad_private *priv) 15031c59de4aSBarnabás Pőcze { 15041c59de4aSBarnabás Pőcze acpi_handle handle = priv->adev->handle; 15051c59de4aSBarnabás Pőcze unsigned long val; 15061c59de4aSBarnabás Pőcze 15071c59de4aSBarnabás Pőcze priv->features.hw_rfkill_switch = dmi_check_system(hw_rfkill_list); 15081c59de4aSBarnabás Pőcze 15091c59de4aSBarnabás Pőcze /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ 15101c59de4aSBarnabás Pőcze priv->features.touchpad_ctrl_via_ec = !acpi_dev_present("ELAN0634", NULL, -1); 15111c59de4aSBarnabás Pőcze 15121c59de4aSBarnabás Pőcze if (!read_ec_data(handle, VPCCMD_R_FAN, &val)) 15131c59de4aSBarnabás Pőcze priv->features.fan_mode = true; 15141c59de4aSBarnabás Pőcze 15151c59de4aSBarnabás Pőcze if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) 15161c59de4aSBarnabás Pőcze priv->features.conservation_mode = true; 15171c59de4aSBarnabás Pőcze 15181c59de4aSBarnabás Pőcze if (acpi_has_method(handle, "DYTC")) 15191c59de4aSBarnabás Pőcze priv->features.dytc = true; 15201c59de4aSBarnabás Pőcze 1521392cbf0aSBarnabás Pőcze if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) { 1522392cbf0aSBarnabás Pőcze if (!eval_hals(handle, &val)) { 1523392cbf0aSBarnabás Pőcze if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val)) 15241c59de4aSBarnabás Pőcze priv->features.fn_lock = true; 1525503325f8SBarnabás Pőcze 1526503325f8SBarnabás Pőcze if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) 1527503325f8SBarnabás Pőcze priv->features.kbd_bl = true; 15286b49dea4SBarnabás Pőcze 15296b49dea4SBarnabás Pőcze if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val)) 15306b49dea4SBarnabás Pőcze priv->features.usb_charging = true; 15311c59de4aSBarnabás Pőcze } 1532392cbf0aSBarnabás Pőcze } 1533392cbf0aSBarnabás Pőcze } 15341c59de4aSBarnabás Pőcze 1535b5c37b79SZhang Rui static int ideapad_acpi_add(struct platform_device *pdev) 1536b5c37b79SZhang Rui { 1537b5c37b79SZhang Rui struct ideapad_private *priv; 1538b5c37b79SZhang Rui struct acpi_device *adev; 1539803be832SBarnabás Pőcze acpi_status status; 1540ff36b0d9SBarnabás Pőcze unsigned long cfg; 154165c7713aSBarnabás Pőcze int err, i; 1542b5c37b79SZhang Rui 154365c7713aSBarnabás Pőcze err = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); 154465c7713aSBarnabás Pőcze if (err) 1545b5c37b79SZhang Rui return -ENODEV; 1546b5c37b79SZhang Rui 1547ff36b0d9SBarnabás Pőcze if (eval_int(adev->handle, "_CFG", &cfg)) 1548b5c37b79SZhang Rui return -ENODEV; 1549b5c37b79SZhang Rui 1550b3facd7bSHimangi Saraogi priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 1551b5c37b79SZhang Rui if (!priv) 1552b5c37b79SZhang Rui return -ENOMEM; 1553b5c37b79SZhang Rui 1554b5c37b79SZhang Rui dev_set_drvdata(&pdev->dev, priv); 155565c7713aSBarnabás Pőcze 1556b5c37b79SZhang Rui priv->cfg = cfg; 1557b5c37b79SZhang Rui priv->adev = adev; 1558b5c37b79SZhang Rui priv->platform_device = pdev; 1559b5c37b79SZhang Rui 15601c59de4aSBarnabás Pőcze ideapad_check_features(priv); 1561d69cd7eeSJiaxun Yang 156265c7713aSBarnabás Pőcze err = ideapad_sysfs_init(priv); 156365c7713aSBarnabás Pőcze if (err) 156465c7713aSBarnabás Pőcze return err; 1565b5c37b79SZhang Rui 156617f1bf38SGreg Kroah-Hartman ideapad_debugfs_init(priv); 1567b5c37b79SZhang Rui 156865c7713aSBarnabás Pőcze err = ideapad_input_init(priv); 156965c7713aSBarnabás Pőcze if (err) 1570b5c37b79SZhang Rui goto input_failed; 1571b5c37b79SZhang Rui 1572503325f8SBarnabás Pőcze err = ideapad_kbd_bl_init(priv); 1573503325f8SBarnabás Pőcze if (err) { 1574503325f8SBarnabás Pőcze if (err != -ENODEV) 1575503325f8SBarnabás Pőcze dev_warn(&pdev->dev, "Could not set up keyboard backlight LED: %d\n", err); 1576503325f8SBarnabás Pőcze else 1577503325f8SBarnabás Pőcze dev_info(&pdev->dev, "Keyboard backlight control not available\n"); 1578503325f8SBarnabás Pőcze } 1579503325f8SBarnabás Pőcze 1580ce363c2bSHans de Goede /* 1581ce363c2bSHans de Goede * On some models without a hw-switch (the yoga 2 13 at least) 1582ce363c2bSHans de Goede * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. 1583ce363c2bSHans de Goede */ 15841c59de4aSBarnabás Pőcze if (!priv->features.hw_rfkill_switch) 1585ce363c2bSHans de Goede write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); 1586ce363c2bSHans de Goede 1587d69cd7eeSJiaxun Yang /* The same for Touchpad */ 15881c59de4aSBarnabás Pőcze if (!priv->features.touchpad_ctrl_via_ec) 1589d69cd7eeSJiaxun Yang write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, 1); 1590d69cd7eeSJiaxun Yang 159185093f79SHans de Goede for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1592b5c37b79SZhang Rui if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) 1593b5c37b79SZhang Rui ideapad_register_rfkill(priv, i); 1594ce363c2bSHans de Goede 1595b5c37b79SZhang Rui ideapad_sync_rfk_state(priv); 1596b5c37b79SZhang Rui ideapad_sync_touchpad_state(priv); 1597b5c37b79SZhang Rui 159865c7713aSBarnabás Pőcze err = ideapad_dytc_profile_init(priv); 159965c7713aSBarnabás Pőcze if (err) { 160065c7713aSBarnabás Pőcze if (err != -ENODEV) 160165c7713aSBarnabás Pőcze dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", err); 16021c59de4aSBarnabás Pőcze else 16031c59de4aSBarnabás Pőcze dev_info(&pdev->dev, "DYTC interface is not available\n"); 16041c59de4aSBarnabás Pőcze } 1605eabe5339SJiaxun Yang 160626bff5f0SHans de Goede if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { 160765c7713aSBarnabás Pőcze err = ideapad_backlight_init(priv); 160865c7713aSBarnabás Pőcze if (err && err != -ENODEV) 1609b5c37b79SZhang Rui goto backlight_failed; 1610b5c37b79SZhang Rui } 161165c7713aSBarnabás Pőcze 1612803be832SBarnabás Pőcze status = acpi_install_notify_handler(adev->handle, 1613803be832SBarnabás Pőcze ACPI_DEVICE_NOTIFY, 1614803be832SBarnabás Pőcze ideapad_acpi_notify, priv); 1615803be832SBarnabás Pőcze if (ACPI_FAILURE(status)) { 161665c7713aSBarnabás Pőcze err = -EIO; 1617b5c37b79SZhang Rui goto notification_failed; 1618803be832SBarnabás Pőcze } 16192d98e0b9SArnd Bergmann 162074caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 16212d98e0b9SArnd Bergmann for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { 1622803be832SBarnabás Pőcze status = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], 16232d98e0b9SArnd Bergmann ideapad_wmi_notify, priv); 1624803be832SBarnabás Pőcze if (ACPI_SUCCESS(status)) { 16252d98e0b9SArnd Bergmann priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; 16262d98e0b9SArnd Bergmann break; 16272d98e0b9SArnd Bergmann } 16282d98e0b9SArnd Bergmann } 162965c7713aSBarnabás Pőcze 1630803be832SBarnabás Pőcze if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) { 163165c7713aSBarnabás Pőcze err = -EIO; 163274caab99SArnd Bergmann goto notification_failed_wmi; 1633803be832SBarnabás Pőcze } 163474caab99SArnd Bergmann #endif 1635b5c37b79SZhang Rui 1636b5c37b79SZhang Rui return 0; 163765c7713aSBarnabás Pőcze 163874caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 163974caab99SArnd Bergmann notification_failed_wmi: 164074caab99SArnd Bergmann acpi_remove_notify_handler(priv->adev->handle, 164165c7713aSBarnabás Pőcze ACPI_DEVICE_NOTIFY, 164265c7713aSBarnabás Pőcze ideapad_acpi_notify); 164374caab99SArnd Bergmann #endif 164465c7713aSBarnabás Pőcze 1645b5c37b79SZhang Rui notification_failed: 1646b5c37b79SZhang Rui ideapad_backlight_exit(priv); 164765c7713aSBarnabás Pőcze 1648b5c37b79SZhang Rui backlight_failed: 1649caa315b8SBarnabás Pőcze ideapad_dytc_profile_exit(priv); 165065c7713aSBarnabás Pőcze 1651b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1652b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 165365c7713aSBarnabás Pőcze 1654503325f8SBarnabás Pőcze ideapad_kbd_bl_exit(priv); 1655b5c37b79SZhang Rui ideapad_input_exit(priv); 165665c7713aSBarnabás Pőcze 1657b5c37b79SZhang Rui input_failed: 1658b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1659b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 166065c7713aSBarnabás Pőcze 166165c7713aSBarnabás Pőcze return err; 1662b5c37b79SZhang Rui } 1663b5c37b79SZhang Rui 1664b5c37b79SZhang Rui static int ideapad_acpi_remove(struct platform_device *pdev) 1665b5c37b79SZhang Rui { 1666b5c37b79SZhang Rui struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); 1667b5c37b79SZhang Rui int i; 1668b5c37b79SZhang Rui 166974caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 16702d98e0b9SArnd Bergmann if (priv->fnesc_guid) 16712d98e0b9SArnd Bergmann wmi_remove_notify_handler(priv->fnesc_guid); 167274caab99SArnd Bergmann #endif 167365c7713aSBarnabás Pőcze 1674b5c37b79SZhang Rui acpi_remove_notify_handler(priv->adev->handle, 167565c7713aSBarnabás Pőcze ACPI_DEVICE_NOTIFY, 167665c7713aSBarnabás Pőcze ideapad_acpi_notify); 167765c7713aSBarnabás Pőcze 1678b5c37b79SZhang Rui ideapad_backlight_exit(priv); 1679eabe5339SJiaxun Yang ideapad_dytc_profile_exit(priv); 168065c7713aSBarnabás Pőcze 1681b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1682b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 168365c7713aSBarnabás Pőcze 1684503325f8SBarnabás Pőcze ideapad_kbd_bl_exit(priv); 1685b5c37b79SZhang Rui ideapad_input_exit(priv); 1686b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1687b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 1688b5c37b79SZhang Rui 1689b5c37b79SZhang Rui return 0; 1690b5c37b79SZhang Rui } 1691b5c37b79SZhang Rui 169211fa8da5SZhang Rui #ifdef CONFIG_PM_SLEEP 1693e1a39a44SBarnabás Pőcze static int ideapad_acpi_resume(struct device *dev) 169407a4a4fcSMaxim Mikityanskiy { 1695e1a39a44SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 169675a11f11SZhang Rui 169775a11f11SZhang Rui ideapad_sync_rfk_state(priv); 169875a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 1699eabe5339SJiaxun Yang 1700eabe5339SJiaxun Yang if (priv->dytc) 1701eabe5339SJiaxun Yang dytc_profile_refresh(priv); 1702eabe5339SJiaxun Yang 170307a4a4fcSMaxim Mikityanskiy return 0; 170407a4a4fcSMaxim Mikityanskiy } 1705b5c37b79SZhang Rui #endif 170607a4a4fcSMaxim Mikityanskiy static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); 170707a4a4fcSMaxim Mikityanskiy 1708b5c37b79SZhang Rui static const struct acpi_device_id ideapad_device_ids[] = { 1709b5c37b79SZhang Rui {"VPC2004", 0}, 1710b5c37b79SZhang Rui {"", 0}, 171157ac3b05SIke Panhc }; 1712b5c37b79SZhang Rui MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 1713b5c37b79SZhang Rui 1714b5c37b79SZhang Rui static struct platform_driver ideapad_acpi_driver = { 1715b5c37b79SZhang Rui .probe = ideapad_acpi_add, 1716b5c37b79SZhang Rui .remove = ideapad_acpi_remove, 1717b5c37b79SZhang Rui .driver = { 1718b5c37b79SZhang Rui .name = "ideapad_acpi", 1719b5c37b79SZhang Rui .pm = &ideapad_pm, 1720b5c37b79SZhang Rui .acpi_match_table = ACPI_PTR(ideapad_device_ids), 1721b5c37b79SZhang Rui }, 1722b5c37b79SZhang Rui }; 1723b5c37b79SZhang Rui 1724b5c37b79SZhang Rui module_platform_driver(ideapad_acpi_driver); 172557ac3b05SIke Panhc 172657ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 172757ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 172857ac3b05SIke Panhc MODULE_LICENSE("GPL"); 1729