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 155*a27a1e35SHans de Goede static bool allow_v4_dytc; 156*a27a1e35SHans de Goede module_param(allow_v4_dytc, bool, 0444); 157*a27a1e35SHans de Goede MODULE_PARM_DESC(allow_v4_dytc, "Enable DYTC version 4 platform-profile support."); 158*a27a1e35SHans de Goede 15957ac3b05SIke Panhc /* 16057ac3b05SIke Panhc * ACPI Helpers 16157ac3b05SIke Panhc */ 16265c7713aSBarnabás Pőcze #define IDEAPAD_EC_TIMEOUT 200 /* in ms */ 16357ac3b05SIke Panhc 164ff36b0d9SBarnabás Pőcze static int eval_int(acpi_handle handle, const char *name, unsigned long *res) 16557ac3b05SIke Panhc { 16657ac3b05SIke Panhc unsigned long long result; 167ade50296SHao Wei Tee acpi_status status; 168ade50296SHao Wei Tee 169ff36b0d9SBarnabás Pőcze status = acpi_evaluate_integer(handle, (char *)name, NULL, &result); 170ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 171ff36b0d9SBarnabás Pőcze return -EIO; 17265c7713aSBarnabás Pőcze 173ff36b0d9SBarnabás Pőcze *res = result; 17465c7713aSBarnabás Pőcze 175ff36b0d9SBarnabás Pőcze return 0; 176ff36b0d9SBarnabás Pőcze } 177ff36b0d9SBarnabás Pőcze 178ff36b0d9SBarnabás Pőcze static int exec_simple_method(acpi_handle handle, const char *name, unsigned long arg) 179ff36b0d9SBarnabás Pőcze { 180ff36b0d9SBarnabás Pőcze acpi_status status = acpi_execute_simple_method(handle, (char *)name, arg); 181ff36b0d9SBarnabás Pőcze 1827be193e3SBarnabás Pőcze return ACPI_FAILURE(status) ? -EIO : 0; 183ade50296SHao Wei Tee } 184ade50296SHao Wei Tee 185ff36b0d9SBarnabás Pőcze static int eval_gbmd(acpi_handle handle, unsigned long *res) 186eabe5339SJiaxun Yang { 187ff36b0d9SBarnabás Pőcze return eval_int(handle, "GBMD", res); 188ff36b0d9SBarnabás Pőcze } 189ff36b0d9SBarnabás Pőcze 190b09aaa3fSBarnabás Pőcze static int exec_sbmc(acpi_handle handle, unsigned long arg) 191ff36b0d9SBarnabás Pőcze { 192b09aaa3fSBarnabás Pőcze return exec_simple_method(handle, "SBMC", arg); 193ff36b0d9SBarnabás Pőcze } 194ff36b0d9SBarnabás Pőcze 195ff36b0d9SBarnabás Pőcze static int eval_hals(acpi_handle handle, unsigned long *res) 196ff36b0d9SBarnabás Pőcze { 197ff36b0d9SBarnabás Pőcze return eval_int(handle, "HALS", res); 198ff36b0d9SBarnabás Pőcze } 199ff36b0d9SBarnabás Pőcze 200ff36b0d9SBarnabás Pőcze static int exec_sals(acpi_handle handle, unsigned long arg) 201ff36b0d9SBarnabás Pőcze { 202ff36b0d9SBarnabás Pőcze return exec_simple_method(handle, "SALS", arg); 203ff36b0d9SBarnabás Pőcze } 204ff36b0d9SBarnabás Pőcze 205ff36b0d9SBarnabás Pőcze static int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long arg, unsigned long *res) 206ff36b0d9SBarnabás Pőcze { 207eabe5339SJiaxun Yang struct acpi_object_list params; 208ff36b0d9SBarnabás Pőcze unsigned long long result; 209eabe5339SJiaxun Yang union acpi_object in_obj; 210ff36b0d9SBarnabás Pőcze acpi_status status; 211eabe5339SJiaxun Yang 212eabe5339SJiaxun Yang params.count = 1; 213eabe5339SJiaxun Yang params.pointer = &in_obj; 214eabe5339SJiaxun Yang in_obj.type = ACPI_TYPE_INTEGER; 215ff36b0d9SBarnabás Pőcze in_obj.integer.value = arg; 216eabe5339SJiaxun Yang 217ff36b0d9SBarnabás Pőcze status = acpi_evaluate_integer(handle, (char *)name, ¶ms, &result); 218ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 2197be193e3SBarnabás Pőcze return -EIO; 220ff36b0d9SBarnabás Pőcze 221ff36b0d9SBarnabás Pőcze if (res) 222ff36b0d9SBarnabás Pőcze *res = result; 223ff36b0d9SBarnabás Pőcze 224eabe5339SJiaxun Yang return 0; 225eabe5339SJiaxun Yang } 226eabe5339SJiaxun Yang 227ff36b0d9SBarnabás Pőcze static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res) 22857ac3b05SIke Panhc { 229ff36b0d9SBarnabás Pőcze return eval_int_with_arg(handle, "DYTC", cmd, res); 23057ac3b05SIke Panhc } 23157ac3b05SIke Panhc 232ff36b0d9SBarnabás Pőcze static int eval_vpcr(acpi_handle handle, unsigned long cmd, unsigned long *res) 233ff36b0d9SBarnabás Pőcze { 234ff36b0d9SBarnabás Pőcze return eval_int_with_arg(handle, "VPCR", cmd, res); 235ff36b0d9SBarnabás Pőcze } 236ff36b0d9SBarnabás Pőcze 237ff36b0d9SBarnabás Pőcze static int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data) 23857ac3b05SIke Panhc { 23957ac3b05SIke Panhc struct acpi_object_list params; 24057ac3b05SIke Panhc union acpi_object in_obj[2]; 24157ac3b05SIke Panhc acpi_status status; 24257ac3b05SIke Panhc 24357ac3b05SIke Panhc params.count = 2; 24457ac3b05SIke Panhc params.pointer = in_obj; 24557ac3b05SIke Panhc in_obj[0].type = ACPI_TYPE_INTEGER; 24657ac3b05SIke Panhc in_obj[0].integer.value = cmd; 24757ac3b05SIke Panhc in_obj[1].type = ACPI_TYPE_INTEGER; 24857ac3b05SIke Panhc in_obj[1].integer.value = data; 24957ac3b05SIke Panhc 25057ac3b05SIke Panhc status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); 251ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 2527be193e3SBarnabás Pőcze return -EIO; 25365c7713aSBarnabás Pőcze 25457ac3b05SIke Panhc return 0; 25557ac3b05SIke Panhc } 25657ac3b05SIke Panhc 257ff36b0d9SBarnabás Pőcze static int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *data) 25857ac3b05SIke Panhc { 259ff36b0d9SBarnabás Pőcze unsigned long end_jiffies, val; 260ff36b0d9SBarnabás Pőcze int err; 26157ac3b05SIke Panhc 262ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 1, cmd); 2637be193e3SBarnabás Pőcze if (err) 2647be193e3SBarnabás Pőcze return err; 26557ac3b05SIke Panhc 26640e0447dSBarnabás Pőcze end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; 26740e0447dSBarnabás Pőcze 26840e0447dSBarnabás Pőcze while (time_before(jiffies, end_jiffies)) { 26957ac3b05SIke Panhc schedule(); 27065c7713aSBarnabás Pőcze 271ff36b0d9SBarnabás Pőcze err = eval_vpcr(handle, 1, &val); 2727be193e3SBarnabás Pőcze if (err) 2737be193e3SBarnabás Pőcze return err; 27465c7713aSBarnabás Pőcze 275ff36b0d9SBarnabás Pőcze if (val == 0) 276ff36b0d9SBarnabás Pőcze return eval_vpcr(handle, 0, data); 27757ac3b05SIke Panhc } 27865c7713aSBarnabás Pőcze 279654324c4SBarnabás Pőcze acpi_handle_err(handle, "timeout in %s\n", __func__); 28065c7713aSBarnabás Pőcze 2817be193e3SBarnabás Pőcze return -ETIMEDOUT; 28257ac3b05SIke Panhc } 28357ac3b05SIke Panhc 284ff36b0d9SBarnabás Pőcze static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long data) 28557ac3b05SIke Panhc { 286ff36b0d9SBarnabás Pőcze unsigned long end_jiffies, val; 287ff36b0d9SBarnabás Pőcze int err; 28857ac3b05SIke Panhc 289ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 0, data); 2907be193e3SBarnabás Pőcze if (err) 2917be193e3SBarnabás Pőcze return err; 29265c7713aSBarnabás Pőcze 293ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 1, cmd); 2947be193e3SBarnabás Pőcze if (err) 2957be193e3SBarnabás Pőcze return err; 29657ac3b05SIke Panhc 29740e0447dSBarnabás Pőcze end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; 29840e0447dSBarnabás Pőcze 29940e0447dSBarnabás Pőcze while (time_before(jiffies, end_jiffies)) { 30057ac3b05SIke Panhc schedule(); 30165c7713aSBarnabás Pőcze 302ff36b0d9SBarnabás Pőcze err = eval_vpcr(handle, 1, &val); 3037be193e3SBarnabás Pőcze if (err) 3047be193e3SBarnabás Pőcze return err; 30565c7713aSBarnabás Pőcze 30657ac3b05SIke Panhc if (val == 0) 30757ac3b05SIke Panhc return 0; 30857ac3b05SIke Panhc } 30965c7713aSBarnabás Pőcze 310654324c4SBarnabás Pőcze acpi_handle_err(handle, "timeout in %s\n", __func__); 31165c7713aSBarnabás Pőcze 3127be193e3SBarnabás Pőcze return -ETIMEDOUT; 31357ac3b05SIke Panhc } 31457ac3b05SIke Panhc 315a4b5a279SIke Panhc /* 316773e3206SIke Panhc * debugfs 317773e3206SIke Panhc */ 318773e3206SIke Panhc static int debugfs_status_show(struct seq_file *s, void *data) 319773e3206SIke Panhc { 320331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 321773e3206SIke Panhc unsigned long value; 322773e3206SIke Panhc 323331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) 3247553390dSBarnabás Pőcze seq_printf(s, "Backlight max: %lu\n", value); 325331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) 3267553390dSBarnabás Pőcze seq_printf(s, "Backlight now: %lu\n", value); 327331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) 3287553390dSBarnabás Pőcze seq_printf(s, "BL power value: %s (%lu)\n", value ? "on" : "off", value); 32965c7713aSBarnabás Pőcze 330ade50296SHao Wei Tee seq_puts(s, "=====================\n"); 331ade50296SHao Wei Tee 3327553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) 3337553390dSBarnabás Pőcze seq_printf(s, "Radio status: %s (%lu)\n", value ? "on" : "off", value); 3347553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) 3357553390dSBarnabás Pőcze seq_printf(s, "Wifi status: %s (%lu)\n", value ? "on" : "off", value); 3367553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) 3377553390dSBarnabás Pőcze seq_printf(s, "BT status: %s (%lu)\n", value ? "on" : "off", value); 3387553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) 3397553390dSBarnabás Pőcze seq_printf(s, "3G status: %s (%lu)\n", value ? "on" : "off", value); 34065c7713aSBarnabás Pőcze 3417553390dSBarnabás Pőcze seq_puts(s, "=====================\n"); 3427553390dSBarnabás Pőcze 3437553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) 3447553390dSBarnabás Pőcze seq_printf(s, "Touchpad status: %s (%lu)\n", value ? "on" : "off", value); 3457553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) 3467553390dSBarnabás Pőcze seq_printf(s, "Camera status: %s (%lu)\n", value ? "on" : "off", value); 34765c7713aSBarnabás Pőcze 3487553390dSBarnabás Pőcze seq_puts(s, "=====================\n"); 3497553390dSBarnabás Pőcze 3507553390dSBarnabás Pőcze if (!eval_gbmd(priv->adev->handle, &value)) 3517553390dSBarnabás Pőcze seq_printf(s, "GBMD: %#010lx\n", value); 3527553390dSBarnabás Pőcze if (!eval_hals(priv->adev->handle, &value)) 3537553390dSBarnabás Pőcze seq_printf(s, "HALS: %#010lx\n", value); 354773e3206SIke Panhc 355773e3206SIke Panhc return 0; 356773e3206SIke Panhc } 357334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_status); 358773e3206SIke Panhc 359773e3206SIke Panhc static int debugfs_cfg_show(struct seq_file *s, void *data) 360773e3206SIke Panhc { 361331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 362331e0ea2SZhang Rui 36318227424SBarnabás Pőcze seq_printf(s, "_CFG: %#010lx\n\n", priv->cfg); 36418227424SBarnabás Pőcze 36518227424SBarnabás Pőcze seq_puts(s, "Capabilities:"); 3660b765671SBarnabás Pőcze if (test_bit(CFG_CAP_BT_BIT, &priv->cfg)) 36718227424SBarnabás Pőcze seq_puts(s, " bluetooth"); 3680b765671SBarnabás Pőcze if (test_bit(CFG_CAP_3G_BIT, &priv->cfg)) 36918227424SBarnabás Pőcze seq_puts(s, " 3G"); 3700b765671SBarnabás Pőcze if (test_bit(CFG_CAP_WIFI_BIT, &priv->cfg)) 37118227424SBarnabás Pőcze seq_puts(s, " wifi"); 3720b765671SBarnabás Pőcze if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg)) 37318227424SBarnabás Pőcze seq_puts(s, " camera"); 374b3ed1b7fSBarnabás Pőcze if (test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg)) 37518227424SBarnabás Pőcze seq_puts(s, " touchpad"); 37618227424SBarnabás Pőcze seq_puts(s, "\n"); 37718227424SBarnabás Pőcze 37818227424SBarnabás Pőcze seq_puts(s, "Graphics: "); 37918227424SBarnabás Pőcze switch (priv->cfg & 0x700) { 380773e3206SIke Panhc case 0x100: 38118227424SBarnabás Pőcze seq_puts(s, "Intel"); 382773e3206SIke Panhc break; 383773e3206SIke Panhc case 0x200: 38418227424SBarnabás Pőcze seq_puts(s, "ATI"); 385773e3206SIke Panhc break; 386773e3206SIke Panhc case 0x300: 38718227424SBarnabás Pőcze seq_puts(s, "Nvidia"); 388773e3206SIke Panhc break; 389773e3206SIke Panhc case 0x400: 39018227424SBarnabás Pőcze seq_puts(s, "Intel and ATI"); 391773e3206SIke Panhc break; 392773e3206SIke Panhc case 0x500: 39318227424SBarnabás Pőcze seq_puts(s, "Intel and Nvidia"); 394773e3206SIke Panhc break; 395773e3206SIke Panhc } 39618227424SBarnabás Pőcze seq_puts(s, "\n"); 397e1a39a44SBarnabás Pőcze 398773e3206SIke Panhc return 0; 399773e3206SIke Panhc } 400334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_cfg); 401773e3206SIke Panhc 40217f1bf38SGreg Kroah-Hartman static void ideapad_debugfs_init(struct ideapad_private *priv) 403773e3206SIke Panhc { 40417f1bf38SGreg Kroah-Hartman struct dentry *dir; 405773e3206SIke Panhc 40617f1bf38SGreg Kroah-Hartman dir = debugfs_create_dir("ideapad", NULL); 40717f1bf38SGreg Kroah-Hartman priv->debug = dir; 408773e3206SIke Panhc 40965c7713aSBarnabás Pőcze debugfs_create_file("cfg", 0444, dir, priv, &debugfs_cfg_fops); 41065c7713aSBarnabás Pőcze debugfs_create_file("status", 0444, dir, priv, &debugfs_status_fops); 411773e3206SIke Panhc } 412773e3206SIke Panhc 413773e3206SIke Panhc static void ideapad_debugfs_exit(struct ideapad_private *priv) 414773e3206SIke Panhc { 415773e3206SIke Panhc debugfs_remove_recursive(priv->debug); 416773e3206SIke Panhc priv->debug = NULL; 417773e3206SIke Panhc } 418773e3206SIke Panhc 419773e3206SIke Panhc /* 4203371f481SIke Panhc * sysfs 421a4b5a279SIke Panhc */ 42265c7713aSBarnabás Pőcze static ssize_t camera_power_show(struct device *dev, 42357ac3b05SIke Panhc struct device_attribute *attr, 42457ac3b05SIke Panhc char *buf) 42557ac3b05SIke Panhc { 426331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 42765c7713aSBarnabás Pőcze unsigned long result; 428c81f2410SBarnabás Pőcze int err; 42957ac3b05SIke Panhc 430c81f2410SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result); 431c81f2410SBarnabás Pőcze if (err) 432c81f2410SBarnabás Pőcze return err; 43365c7713aSBarnabás Pőcze 43400641c08SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!result); 43557ac3b05SIke Panhc } 43657ac3b05SIke Panhc 43765c7713aSBarnabás Pőcze static ssize_t camera_power_store(struct device *dev, 43857ac3b05SIke Panhc struct device_attribute *attr, 43957ac3b05SIke Panhc const char *buf, size_t count) 44057ac3b05SIke Panhc { 441331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 44200641c08SBarnabás Pőcze bool state; 443c81f2410SBarnabás Pőcze int err; 4440c7bbeb9SMaxim Mikityanskiy 44565c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 446c81f2410SBarnabás Pőcze if (err) 447c81f2410SBarnabás Pőcze return err; 4480c7bbeb9SMaxim Mikityanskiy 44965c7713aSBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); 45065c7713aSBarnabás Pőcze if (err) 45165c7713aSBarnabás Pőcze return err; 4520c7bbeb9SMaxim Mikityanskiy 4530c7bbeb9SMaxim Mikityanskiy return count; 4540c7bbeb9SMaxim Mikityanskiy } 4550c7bbeb9SMaxim Mikityanskiy 45665c7713aSBarnabás Pőcze static DEVICE_ATTR_RW(camera_power); 45736ac0d43SRitesh Raj Sarraf 458ade50296SHao Wei Tee static ssize_t conservation_mode_show(struct device *dev, 459ade50296SHao Wei Tee struct device_attribute *attr, 460ade50296SHao Wei Tee char *buf) 461ade50296SHao Wei Tee { 462ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 463ade50296SHao Wei Tee unsigned long result; 464c81f2410SBarnabás Pőcze int err; 465ade50296SHao Wei Tee 466ff36b0d9SBarnabás Pőcze err = eval_gbmd(priv->adev->handle, &result); 467c81f2410SBarnabás Pőcze if (err) 468c81f2410SBarnabás Pőcze return err; 46965c7713aSBarnabás Pőcze 4700b765671SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); 471ade50296SHao Wei Tee } 472ade50296SHao Wei Tee 473ade50296SHao Wei Tee static ssize_t conservation_mode_store(struct device *dev, 474ade50296SHao Wei Tee struct device_attribute *attr, 475ade50296SHao Wei Tee const char *buf, size_t count) 476ade50296SHao Wei Tee { 477ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 478ade50296SHao Wei Tee bool state; 47965c7713aSBarnabás Pőcze int err; 480ade50296SHao Wei Tee 48165c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 48265c7713aSBarnabás Pőcze if (err) 48365c7713aSBarnabás Pőcze return err; 484ade50296SHao Wei Tee 485b09aaa3fSBarnabás Pőcze err = exec_sbmc(priv->adev->handle, state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF); 48665c7713aSBarnabás Pőcze if (err) 48765c7713aSBarnabás Pőcze return err; 48865c7713aSBarnabás Pőcze 489ade50296SHao Wei Tee return count; 490ade50296SHao Wei Tee } 491ade50296SHao Wei Tee 492ade50296SHao Wei Tee static DEVICE_ATTR_RW(conservation_mode); 493ade50296SHao Wei Tee 49465c7713aSBarnabás Pőcze static ssize_t fan_mode_show(struct device *dev, 49565c7713aSBarnabás Pőcze struct device_attribute *attr, 49665c7713aSBarnabás Pőcze char *buf) 49765c7713aSBarnabás Pőcze { 49865c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 49965c7713aSBarnabás Pőcze unsigned long result; 50065c7713aSBarnabás Pőcze int err; 50165c7713aSBarnabás Pőcze 50265c7713aSBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result); 50365c7713aSBarnabás Pőcze if (err) 50465c7713aSBarnabás Pőcze return err; 50565c7713aSBarnabás Pőcze 50665c7713aSBarnabás Pőcze return sysfs_emit(buf, "%lu\n", result); 50765c7713aSBarnabás Pőcze } 50865c7713aSBarnabás Pőcze 50965c7713aSBarnabás Pőcze static ssize_t fan_mode_store(struct device *dev, 51065c7713aSBarnabás Pőcze struct device_attribute *attr, 51165c7713aSBarnabás Pőcze const char *buf, size_t count) 51265c7713aSBarnabás Pőcze { 51365c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 51465c7713aSBarnabás Pőcze unsigned int state; 51565c7713aSBarnabás Pőcze int err; 51665c7713aSBarnabás Pőcze 51765c7713aSBarnabás Pőcze err = kstrtouint(buf, 0, &state); 51865c7713aSBarnabás Pőcze if (err) 51965c7713aSBarnabás Pőcze return err; 52065c7713aSBarnabás Pőcze 52165c7713aSBarnabás Pőcze if (state > 4 || state == 3) 52265c7713aSBarnabás Pőcze return -EINVAL; 52365c7713aSBarnabás Pőcze 52465c7713aSBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); 52565c7713aSBarnabás Pőcze if (err) 52665c7713aSBarnabás Pőcze return err; 52765c7713aSBarnabás Pőcze 52865c7713aSBarnabás Pőcze return count; 52965c7713aSBarnabás Pőcze } 53065c7713aSBarnabás Pőcze 53165c7713aSBarnabás Pőcze static DEVICE_ATTR_RW(fan_mode); 53265c7713aSBarnabás Pőcze 53340760717SOleg Keri static ssize_t fn_lock_show(struct device *dev, 53440760717SOleg Keri struct device_attribute *attr, 53540760717SOleg Keri char *buf) 53640760717SOleg Keri { 53740760717SOleg Keri struct ideapad_private *priv = dev_get_drvdata(dev); 538ff36b0d9SBarnabás Pőcze unsigned long hals; 53965c7713aSBarnabás Pőcze int err; 54040760717SOleg Keri 54165c7713aSBarnabás Pőcze err = eval_hals(priv->adev->handle, &hals); 54265c7713aSBarnabás Pőcze if (err) 54365c7713aSBarnabás Pőcze return err; 54440760717SOleg Keri 545ff36b0d9SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); 54640760717SOleg Keri } 54740760717SOleg Keri 54840760717SOleg Keri static ssize_t fn_lock_store(struct device *dev, 54940760717SOleg Keri struct device_attribute *attr, 55040760717SOleg Keri const char *buf, size_t count) 55140760717SOleg Keri { 55240760717SOleg Keri struct ideapad_private *priv = dev_get_drvdata(dev); 55340760717SOleg Keri bool state; 55465c7713aSBarnabás Pőcze int err; 55540760717SOleg Keri 55665c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 55765c7713aSBarnabás Pőcze if (err) 55865c7713aSBarnabás Pőcze return err; 55940760717SOleg Keri 56065c7713aSBarnabás Pőcze err = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); 56165c7713aSBarnabás Pőcze if (err) 56265c7713aSBarnabás Pőcze return err; 56365c7713aSBarnabás Pőcze 56440760717SOleg Keri return count; 56540760717SOleg Keri } 56640760717SOleg Keri 56740760717SOleg Keri static DEVICE_ATTR_RW(fn_lock); 56840760717SOleg Keri 56965c7713aSBarnabás Pőcze static ssize_t touchpad_show(struct device *dev, 57065c7713aSBarnabás Pőcze struct device_attribute *attr, 57165c7713aSBarnabás Pőcze char *buf) 57265c7713aSBarnabás Pőcze { 57365c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 57465c7713aSBarnabás Pőcze unsigned long result; 57565c7713aSBarnabás Pőcze int err; 57665c7713aSBarnabás Pőcze 57765c7713aSBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result); 57865c7713aSBarnabás Pőcze if (err) 57965c7713aSBarnabás Pőcze return err; 58065c7713aSBarnabás Pőcze 58165c7713aSBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!result); 58265c7713aSBarnabás Pőcze } 58365c7713aSBarnabás Pőcze 58465c7713aSBarnabás Pőcze static ssize_t touchpad_store(struct device *dev, 58565c7713aSBarnabás Pőcze struct device_attribute *attr, 58665c7713aSBarnabás Pőcze const char *buf, size_t count) 58765c7713aSBarnabás Pőcze { 58865c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 58965c7713aSBarnabás Pőcze bool state; 59065c7713aSBarnabás Pőcze int err; 59165c7713aSBarnabás Pőcze 59265c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 59365c7713aSBarnabás Pőcze if (err) 59465c7713aSBarnabás Pőcze return err; 59565c7713aSBarnabás Pőcze 59665c7713aSBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); 59765c7713aSBarnabás Pőcze if (err) 59865c7713aSBarnabás Pőcze return err; 59965c7713aSBarnabás Pőcze 60065c7713aSBarnabás Pőcze return count; 60165c7713aSBarnabás Pőcze } 60265c7713aSBarnabás Pőcze 60365c7713aSBarnabás Pőcze static DEVICE_ATTR_RW(touchpad); 60440760717SOleg Keri 6056b49dea4SBarnabás Pőcze static ssize_t usb_charging_show(struct device *dev, 6066b49dea4SBarnabás Pőcze struct device_attribute *attr, 6076b49dea4SBarnabás Pőcze char *buf) 6086b49dea4SBarnabás Pőcze { 6096b49dea4SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 6106b49dea4SBarnabás Pőcze unsigned long hals; 6116b49dea4SBarnabás Pőcze int err; 6126b49dea4SBarnabás Pőcze 6136b49dea4SBarnabás Pőcze err = eval_hals(priv->adev->handle, &hals); 6146b49dea4SBarnabás Pőcze if (err) 6156b49dea4SBarnabás Pőcze return err; 6166b49dea4SBarnabás Pőcze 6176b49dea4SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(HALS_USB_CHARGING_STATE_BIT, &hals)); 6186b49dea4SBarnabás Pőcze } 6196b49dea4SBarnabás Pőcze 6206b49dea4SBarnabás Pőcze static ssize_t usb_charging_store(struct device *dev, 6216b49dea4SBarnabás Pőcze struct device_attribute *attr, 6226b49dea4SBarnabás Pőcze const char *buf, size_t count) 6236b49dea4SBarnabás Pőcze { 6246b49dea4SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 6256b49dea4SBarnabás Pőcze bool state; 6266b49dea4SBarnabás Pőcze int err; 6276b49dea4SBarnabás Pőcze 6286b49dea4SBarnabás Pőcze err = kstrtobool(buf, &state); 6296b49dea4SBarnabás Pőcze if (err) 6306b49dea4SBarnabás Pőcze return err; 6316b49dea4SBarnabás Pőcze 6326b49dea4SBarnabás Pőcze err = exec_sals(priv->adev->handle, state ? SALS_USB_CHARGING_ON : SALS_USB_CHARGING_OFF); 6336b49dea4SBarnabás Pőcze if (err) 6346b49dea4SBarnabás Pőcze return err; 6356b49dea4SBarnabás Pőcze 6366b49dea4SBarnabás Pőcze return count; 6376b49dea4SBarnabás Pőcze } 6386b49dea4SBarnabás Pőcze 6396b49dea4SBarnabás Pőcze static DEVICE_ATTR_RW(usb_charging); 6406b49dea4SBarnabás Pőcze 6413371f481SIke Panhc static struct attribute *ideapad_attributes[] = { 6423371f481SIke Panhc &dev_attr_camera_power.attr, 643ade50296SHao Wei Tee &dev_attr_conservation_mode.attr, 64465c7713aSBarnabás Pőcze &dev_attr_fan_mode.attr, 64540760717SOleg Keri &dev_attr_fn_lock.attr, 64665c7713aSBarnabás Pőcze &dev_attr_touchpad.attr, 6476b49dea4SBarnabás Pőcze &dev_attr_usb_charging.attr, 6483371f481SIke Panhc NULL 6493371f481SIke Panhc }; 6503371f481SIke Panhc 651587a1f16SAl Viro static umode_t ideapad_is_visible(struct kobject *kobj, 652a84511f7SIke Panhc struct attribute *attr, 653a84511f7SIke Panhc int idx) 654a84511f7SIke Panhc { 655708086b2SBarnabás Pőcze struct device *dev = kobj_to_dev(kobj); 656a84511f7SIke Panhc struct ideapad_private *priv = dev_get_drvdata(dev); 6571c59de4aSBarnabás Pőcze bool supported = true; 658a84511f7SIke Panhc 659a84511f7SIke Panhc if (attr == &dev_attr_camera_power.attr) 6600b765671SBarnabás Pőcze supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg); 6611c59de4aSBarnabás Pőcze else if (attr == &dev_attr_conservation_mode.attr) 6621c59de4aSBarnabás Pőcze supported = priv->features.conservation_mode; 6631c59de4aSBarnabás Pőcze else if (attr == &dev_attr_fan_mode.attr) 6641c59de4aSBarnabás Pőcze supported = priv->features.fan_mode; 6651c59de4aSBarnabás Pőcze else if (attr == &dev_attr_fn_lock.attr) 6661c59de4aSBarnabás Pőcze supported = priv->features.fn_lock; 6671c59de4aSBarnabás Pőcze else if (attr == &dev_attr_touchpad.attr) 668b3ed1b7fSBarnabás Pőcze supported = priv->features.touchpad_ctrl_via_ec && 669b3ed1b7fSBarnabás Pőcze test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg); 6706b49dea4SBarnabás Pőcze else if (attr == &dev_attr_usb_charging.attr) 6716b49dea4SBarnabás Pőcze supported = priv->features.usb_charging; 672a84511f7SIke Panhc 673a84511f7SIke Panhc return supported ? attr->mode : 0; 674a84511f7SIke Panhc } 675a84511f7SIke Panhc 67649458e83SMathias Krause static const struct attribute_group ideapad_attribute_group = { 677a84511f7SIke Panhc .is_visible = ideapad_is_visible, 6783371f481SIke Panhc .attrs = ideapad_attributes 6793371f481SIke Panhc }; 6803371f481SIke Panhc 681a4b5a279SIke Panhc /* 682eabe5339SJiaxun Yang * DYTC Platform profile 683eabe5339SJiaxun Yang */ 684eabe5339SJiaxun Yang #define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ 685eabe5339SJiaxun Yang #define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ 686eabe5339SJiaxun Yang #define DYTC_CMD_GET 2 /* To get current IC function and mode */ 687eabe5339SJiaxun Yang #define DYTC_CMD_RESET 0x1ff /* To reset back to default */ 688eabe5339SJiaxun Yang 689eabe5339SJiaxun Yang #define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ 690eabe5339SJiaxun Yang #define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ 691eabe5339SJiaxun Yang #define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ 692eabe5339SJiaxun Yang 693eabe5339SJiaxun Yang #define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ 694eabe5339SJiaxun Yang #define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ 695eabe5339SJiaxun Yang 696eabe5339SJiaxun Yang #define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ 697eabe5339SJiaxun Yang #define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ 698eabe5339SJiaxun Yang #define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ 699eabe5339SJiaxun Yang 700eabe5339SJiaxun Yang #define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ 701eabe5339SJiaxun Yang #define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ 702eabe5339SJiaxun Yang #define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ 703eabe5339SJiaxun Yang 704eabe5339SJiaxun Yang #define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ 705eabe5339SJiaxun Yang #define DYTC_MODE_LOW_POWER 3 /* Low power mode aka quiet */ 706eabe5339SJiaxun Yang #define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ 707eabe5339SJiaxun Yang 708eabe5339SJiaxun Yang #define DYTC_SET_COMMAND(function, mode, on) \ 709eabe5339SJiaxun Yang (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ 710eabe5339SJiaxun Yang (mode) << DYTC_SET_MODE_BIT | \ 711eabe5339SJiaxun Yang (on) << DYTC_SET_VALID_BIT) 712eabe5339SJiaxun Yang 713eabe5339SJiaxun Yang #define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) 714eabe5339SJiaxun Yang 715eabe5339SJiaxun Yang #define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) 716eabe5339SJiaxun Yang 717eabe5339SJiaxun Yang static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) 718eabe5339SJiaxun Yang { 719eabe5339SJiaxun Yang switch (dytcmode) { 720eabe5339SJiaxun Yang case DYTC_MODE_LOW_POWER: 721eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_LOW_POWER; 722eabe5339SJiaxun Yang break; 723eabe5339SJiaxun Yang case DYTC_MODE_BALANCE: 724eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_BALANCED; 725eabe5339SJiaxun Yang break; 726eabe5339SJiaxun Yang case DYTC_MODE_PERFORM: 727eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_PERFORMANCE; 728eabe5339SJiaxun Yang break; 729eabe5339SJiaxun Yang default: /* Unknown mode */ 730eabe5339SJiaxun Yang return -EINVAL; 731eabe5339SJiaxun Yang } 73265c7713aSBarnabás Pőcze 733eabe5339SJiaxun Yang return 0; 734eabe5339SJiaxun Yang } 735eabe5339SJiaxun Yang 736eabe5339SJiaxun Yang static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) 737eabe5339SJiaxun Yang { 738eabe5339SJiaxun Yang switch (profile) { 739eabe5339SJiaxun Yang case PLATFORM_PROFILE_LOW_POWER: 740eabe5339SJiaxun Yang *perfmode = DYTC_MODE_LOW_POWER; 741eabe5339SJiaxun Yang break; 742eabe5339SJiaxun Yang case PLATFORM_PROFILE_BALANCED: 743eabe5339SJiaxun Yang *perfmode = DYTC_MODE_BALANCE; 744eabe5339SJiaxun Yang break; 745eabe5339SJiaxun Yang case PLATFORM_PROFILE_PERFORMANCE: 746eabe5339SJiaxun Yang *perfmode = DYTC_MODE_PERFORM; 747eabe5339SJiaxun Yang break; 748eabe5339SJiaxun Yang default: /* Unknown profile */ 749eabe5339SJiaxun Yang return -EOPNOTSUPP; 750eabe5339SJiaxun Yang } 75165c7713aSBarnabás Pőcze 752eabe5339SJiaxun Yang return 0; 753eabe5339SJiaxun Yang } 754eabe5339SJiaxun Yang 755eabe5339SJiaxun Yang /* 756eabe5339SJiaxun Yang * dytc_profile_get: Function to register with platform_profile 757eabe5339SJiaxun Yang * handler. Returns current platform profile. 758eabe5339SJiaxun Yang */ 75965c7713aSBarnabás Pőcze static int dytc_profile_get(struct platform_profile_handler *pprof, 760eabe5339SJiaxun Yang enum platform_profile_option *profile) 761eabe5339SJiaxun Yang { 76265c7713aSBarnabás Pőcze struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 763eabe5339SJiaxun Yang 764eabe5339SJiaxun Yang *profile = dytc->current_profile; 765eabe5339SJiaxun Yang return 0; 766eabe5339SJiaxun Yang } 767eabe5339SJiaxun Yang 768eabe5339SJiaxun Yang /* 769eabe5339SJiaxun Yang * Helper function - check if we are in CQL mode and if we are 770eabe5339SJiaxun Yang * - disable CQL, 771eabe5339SJiaxun Yang * - run the command 772eabe5339SJiaxun Yang * - enable CQL 773eabe5339SJiaxun Yang * If not in CQL mode, just run the command 774eabe5339SJiaxun Yang */ 77565c7713aSBarnabás Pőcze static int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, 77665c7713aSBarnabás Pőcze unsigned long *output) 777eabe5339SJiaxun Yang { 778ff36b0d9SBarnabás Pőcze int err, cmd_err, cur_funcmode; 779eabe5339SJiaxun Yang 780eabe5339SJiaxun Yang /* Determine if we are in CQL mode. This alters the commands we do */ 781ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_GET, output); 782eabe5339SJiaxun Yang if (err) 783eabe5339SJiaxun Yang return err; 784eabe5339SJiaxun Yang 785eabe5339SJiaxun Yang cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; 786eabe5339SJiaxun Yang /* Check if we're OK to return immediately */ 787ff36b0d9SBarnabás Pőcze if (cmd == DYTC_CMD_GET && cur_funcmode != DYTC_FUNCTION_CQL) 788eabe5339SJiaxun Yang return 0; 789eabe5339SJiaxun Yang 790eabe5339SJiaxun Yang if (cur_funcmode == DYTC_FUNCTION_CQL) { 791ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_DISABLE_CQL, NULL); 792eabe5339SJiaxun Yang if (err) 793eabe5339SJiaxun Yang return err; 794eabe5339SJiaxun Yang } 795eabe5339SJiaxun Yang 796ff36b0d9SBarnabás Pőcze cmd_err = eval_dytc(priv->adev->handle, cmd, output); 797eabe5339SJiaxun Yang /* Check return condition after we've restored CQL state */ 798eabe5339SJiaxun Yang 799eabe5339SJiaxun Yang if (cur_funcmode == DYTC_FUNCTION_CQL) { 800ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_ENABLE_CQL, NULL); 801eabe5339SJiaxun Yang if (err) 802eabe5339SJiaxun Yang return err; 803eabe5339SJiaxun Yang } 804eabe5339SJiaxun Yang 805eabe5339SJiaxun Yang return cmd_err; 806eabe5339SJiaxun Yang } 807eabe5339SJiaxun Yang 808eabe5339SJiaxun Yang /* 809eabe5339SJiaxun Yang * dytc_profile_set: Function to register with platform_profile 810eabe5339SJiaxun Yang * handler. Sets current platform profile. 811eabe5339SJiaxun Yang */ 81265c7713aSBarnabás Pőcze static int dytc_profile_set(struct platform_profile_handler *pprof, 813eabe5339SJiaxun Yang enum platform_profile_option profile) 814eabe5339SJiaxun Yang { 81565c7713aSBarnabás Pőcze struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 81665c7713aSBarnabás Pőcze struct ideapad_private *priv = dytc->priv; 817ff67dbd5SQiu Wenbo unsigned long output; 818eabe5339SJiaxun Yang int err; 819eabe5339SJiaxun Yang 820eabe5339SJiaxun Yang err = mutex_lock_interruptible(&dytc->mutex); 821eabe5339SJiaxun Yang if (err) 822eabe5339SJiaxun Yang return err; 823eabe5339SJiaxun Yang 824eabe5339SJiaxun Yang if (profile == PLATFORM_PROFILE_BALANCED) { 825eabe5339SJiaxun Yang /* To get back to balanced mode we just issue a reset command */ 826ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_RESET, NULL); 827eabe5339SJiaxun Yang if (err) 828eabe5339SJiaxun Yang goto unlock; 829eabe5339SJiaxun Yang } else { 830eabe5339SJiaxun Yang int perfmode; 831eabe5339SJiaxun Yang 832eabe5339SJiaxun Yang err = convert_profile_to_dytc(profile, &perfmode); 833eabe5339SJiaxun Yang if (err) 834eabe5339SJiaxun Yang goto unlock; 835eabe5339SJiaxun Yang 836eabe5339SJiaxun Yang /* Determine if we are in CQL mode. This alters the commands we do */ 83765c7713aSBarnabás Pőcze err = dytc_cql_command(priv, DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), 838ff67dbd5SQiu Wenbo &output); 839eabe5339SJiaxun Yang if (err) 840eabe5339SJiaxun Yang goto unlock; 841eabe5339SJiaxun Yang } 84265c7713aSBarnabás Pőcze 843eabe5339SJiaxun Yang /* Success - update current profile */ 844eabe5339SJiaxun Yang dytc->current_profile = profile; 84565c7713aSBarnabás Pőcze 846eabe5339SJiaxun Yang unlock: 847eabe5339SJiaxun Yang mutex_unlock(&dytc->mutex); 84865c7713aSBarnabás Pőcze 849eabe5339SJiaxun Yang return err; 850eabe5339SJiaxun Yang } 851eabe5339SJiaxun Yang 852eabe5339SJiaxun Yang static void dytc_profile_refresh(struct ideapad_private *priv) 853eabe5339SJiaxun Yang { 854eabe5339SJiaxun Yang enum platform_profile_option profile; 855ff36b0d9SBarnabás Pőcze unsigned long output; 856ff36b0d9SBarnabás Pőcze int err, perfmode; 857eabe5339SJiaxun Yang 858eabe5339SJiaxun Yang mutex_lock(&priv->dytc->mutex); 859eabe5339SJiaxun Yang err = dytc_cql_command(priv, DYTC_CMD_GET, &output); 860eabe5339SJiaxun Yang mutex_unlock(&priv->dytc->mutex); 861eabe5339SJiaxun Yang if (err) 862eabe5339SJiaxun Yang return; 863eabe5339SJiaxun Yang 864eabe5339SJiaxun Yang perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; 86565c7713aSBarnabás Pőcze 86665c7713aSBarnabás Pőcze if (convert_dytc_to_profile(perfmode, &profile)) 86765c7713aSBarnabás Pőcze return; 86865c7713aSBarnabás Pőcze 869eabe5339SJiaxun Yang if (profile != priv->dytc->current_profile) { 870eabe5339SJiaxun Yang priv->dytc->current_profile = profile; 871eabe5339SJiaxun Yang platform_profile_notify(); 872eabe5339SJiaxun Yang } 873eabe5339SJiaxun Yang } 874eabe5339SJiaxun Yang 875599482c5SKelly Anderson static const struct dmi_system_id ideapad_dytc_v4_allow_table[] = { 876599482c5SKelly Anderson { 877599482c5SKelly Anderson /* Ideapad 5 Pro 16ACH6 */ 878599482c5SKelly Anderson .ident = "LENOVO 82L5", 879599482c5SKelly Anderson .matches = { 880599482c5SKelly Anderson DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 881599482c5SKelly Anderson DMI_MATCH(DMI_PRODUCT_NAME, "82L5") 882599482c5SKelly Anderson } 883599482c5SKelly Anderson }, 884599482c5SKelly Anderson {} 885599482c5SKelly Anderson }; 886599482c5SKelly Anderson 887eabe5339SJiaxun Yang static int ideapad_dytc_profile_init(struct ideapad_private *priv) 888eabe5339SJiaxun Yang { 889ff36b0d9SBarnabás Pőcze int err, dytc_version; 890ff36b0d9SBarnabás Pőcze unsigned long output; 891eabe5339SJiaxun Yang 8921c59de4aSBarnabás Pőcze if (!priv->features.dytc) 8931c59de4aSBarnabás Pőcze return -ENODEV; 8941c59de4aSBarnabás Pőcze 895ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); 896eabe5339SJiaxun Yang /* For all other errors we can flag the failure */ 897eabe5339SJiaxun Yang if (err) 898eabe5339SJiaxun Yang return err; 899eabe5339SJiaxun Yang 900eabe5339SJiaxun Yang /* Check DYTC is enabled and supports mode setting */ 901599482c5SKelly Anderson if (!test_bit(DYTC_QUERY_ENABLE_BIT, &output)) { 902599482c5SKelly Anderson dev_info(&priv->platform_device->dev, "DYTC_QUERY_ENABLE_BIT returned false\n"); 903eabe5339SJiaxun Yang return -ENODEV; 904599482c5SKelly Anderson } 905eabe5339SJiaxun Yang 906eabe5339SJiaxun Yang dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; 907599482c5SKelly Anderson 908*a27a1e35SHans de Goede if (dytc_version < 4) { 909*a27a1e35SHans de Goede dev_info(&priv->platform_device->dev, "DYTC_VERSION < 4 is not supported\n"); 910eabe5339SJiaxun Yang return -ENODEV; 911599482c5SKelly Anderson } 912*a27a1e35SHans de Goede 913*a27a1e35SHans de Goede if (dytc_version < 5 && 914*a27a1e35SHans de Goede !(allow_v4_dytc || dmi_check_system(ideapad_dytc_v4_allow_table))) { 915*a27a1e35SHans de Goede dev_info(&priv->platform_device->dev, 916*a27a1e35SHans de Goede "DYTC_VERSION 4 support may not work. Pass ideapad_laptop.allow_v4_dytc=Y on the kernel commandline to enable\n"); 917*a27a1e35SHans de Goede return -ENODEV; 918599482c5SKelly Anderson } 919eabe5339SJiaxun Yang 92065c7713aSBarnabás Pőcze priv->dytc = kzalloc(sizeof(*priv->dytc), GFP_KERNEL); 921eabe5339SJiaxun Yang if (!priv->dytc) 922eabe5339SJiaxun Yang return -ENOMEM; 923eabe5339SJiaxun Yang 924eabe5339SJiaxun Yang mutex_init(&priv->dytc->mutex); 925eabe5339SJiaxun Yang 926eabe5339SJiaxun Yang priv->dytc->priv = priv; 927eabe5339SJiaxun Yang priv->dytc->pprof.profile_get = dytc_profile_get; 928eabe5339SJiaxun Yang priv->dytc->pprof.profile_set = dytc_profile_set; 929eabe5339SJiaxun Yang 930eabe5339SJiaxun Yang /* Setup supported modes */ 931eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices); 932eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices); 933eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices); 934eabe5339SJiaxun Yang 935eabe5339SJiaxun Yang /* Create platform_profile structure and register */ 936eabe5339SJiaxun Yang err = platform_profile_register(&priv->dytc->pprof); 937eabe5339SJiaxun Yang if (err) 93865c7713aSBarnabás Pőcze goto pp_reg_failed; 939eabe5339SJiaxun Yang 940eabe5339SJiaxun Yang /* Ensure initial values are correct */ 941eabe5339SJiaxun Yang dytc_profile_refresh(priv); 942eabe5339SJiaxun Yang 943eabe5339SJiaxun Yang return 0; 944eabe5339SJiaxun Yang 94565c7713aSBarnabás Pőcze pp_reg_failed: 946eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 947eabe5339SJiaxun Yang kfree(priv->dytc); 948eabe5339SJiaxun Yang priv->dytc = NULL; 94965c7713aSBarnabás Pőcze 950eabe5339SJiaxun Yang return err; 951eabe5339SJiaxun Yang } 952eabe5339SJiaxun Yang 953eabe5339SJiaxun Yang static void ideapad_dytc_profile_exit(struct ideapad_private *priv) 954eabe5339SJiaxun Yang { 955eabe5339SJiaxun Yang if (!priv->dytc) 956eabe5339SJiaxun Yang return; 957eabe5339SJiaxun Yang 958eabe5339SJiaxun Yang platform_profile_remove(); 959eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 960eabe5339SJiaxun Yang kfree(priv->dytc); 96165c7713aSBarnabás Pőcze 962eabe5339SJiaxun Yang priv->dytc = NULL; 963eabe5339SJiaxun Yang } 964eabe5339SJiaxun Yang 965eabe5339SJiaxun Yang /* 966a4b5a279SIke Panhc * Rfkill 967a4b5a279SIke Panhc */ 968c1f73658SIke Panhc struct ideapad_rfk_data { 969c1f73658SIke Panhc char *name; 970c1f73658SIke Panhc int cfgbit; 971c1f73658SIke Panhc int opcode; 972c1f73658SIke Panhc int type; 973c1f73658SIke Panhc }; 974c1f73658SIke Panhc 975b3d94d70SMathias Krause static const struct ideapad_rfk_data ideapad_rfk_data[] = { 9760b765671SBarnabás Pőcze { "ideapad_wlan", CFG_CAP_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, 9770b765671SBarnabás Pőcze { "ideapad_bluetooth", CFG_CAP_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, 9780b765671SBarnabás Pőcze { "ideapad_3g", CFG_CAP_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, 979c1f73658SIke Panhc }; 980c1f73658SIke Panhc 98157ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked) 98257ac3b05SIke Panhc { 983331e0ea2SZhang Rui struct ideapad_rfk_priv *priv = data; 9844b200b46SArnd Bergmann int opcode = ideapad_rfk_data[priv->dev].opcode; 98557ac3b05SIke Panhc 9864b200b46SArnd Bergmann return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked); 98757ac3b05SIke Panhc } 98857ac3b05SIke Panhc 9893d59dfcdSBhumika Goyal static const struct rfkill_ops ideapad_rfk_ops = { 99057ac3b05SIke Panhc .set_block = ideapad_rfk_set, 99157ac3b05SIke Panhc }; 99257ac3b05SIke Panhc 993923de84aSIke Panhc static void ideapad_sync_rfk_state(struct ideapad_private *priv) 99457ac3b05SIke Panhc { 995ce363c2bSHans de Goede unsigned long hw_blocked = 0; 99657ac3b05SIke Panhc int i; 99757ac3b05SIke Panhc 9981c59de4aSBarnabás Pőcze if (priv->features.hw_rfkill_switch) { 999331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) 100057ac3b05SIke Panhc return; 100157ac3b05SIke Panhc hw_blocked = !hw_blocked; 1002ce363c2bSHans de Goede } 100357ac3b05SIke Panhc 1004c1f73658SIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 100557ac3b05SIke Panhc if (priv->rfk[i]) 100657ac3b05SIke Panhc rfkill_set_hw_state(priv->rfk[i], hw_blocked); 100757ac3b05SIke Panhc } 100857ac3b05SIke Panhc 100975a11f11SZhang Rui static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) 101057ac3b05SIke Panhc { 101165c7713aSBarnabás Pőcze unsigned long rf_enabled; 101265c7713aSBarnabás Pőcze int err; 101357ac3b05SIke Panhc 101465c7713aSBarnabás Pőcze if (no_bt_rfkill && ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH) { 1015bfa97b7dSIke Panhc /* Force to enable bluetooth when no_bt_rfkill=1 */ 101665c7713aSBarnabás Pőcze write_ec_cmd(priv->adev->handle, ideapad_rfk_data[dev].opcode, 1); 1017bfa97b7dSIke Panhc return 0; 1018bfa97b7dSIke Panhc } 101965c7713aSBarnabás Pőcze 1020331e0ea2SZhang Rui priv->rfk_priv[dev].dev = dev; 1021331e0ea2SZhang Rui priv->rfk_priv[dev].priv = priv; 1022bfa97b7dSIke Panhc 102375a11f11SZhang Rui priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, 1024b5c37b79SZhang Rui &priv->platform_device->dev, 102575a11f11SZhang Rui ideapad_rfk_data[dev].type, 102675a11f11SZhang Rui &ideapad_rfk_ops, 1027331e0ea2SZhang Rui &priv->rfk_priv[dev]); 102857ac3b05SIke Panhc if (!priv->rfk[dev]) 102957ac3b05SIke Panhc return -ENOMEM; 103057ac3b05SIke Panhc 103165c7713aSBarnabás Pőcze err = read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode - 1, &rf_enabled); 103265c7713aSBarnabás Pőcze if (err) 103365c7713aSBarnabás Pőcze rf_enabled = 1; 103457ac3b05SIke Panhc 103565c7713aSBarnabás Pőcze rfkill_init_sw_state(priv->rfk[dev], !rf_enabled); 103665c7713aSBarnabás Pőcze 103765c7713aSBarnabás Pőcze err = rfkill_register(priv->rfk[dev]); 103865c7713aSBarnabás Pőcze if (err) 103957ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 104065c7713aSBarnabás Pőcze 104165c7713aSBarnabás Pőcze return err; 104257ac3b05SIke Panhc } 104357ac3b05SIke Panhc 104475a11f11SZhang Rui static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) 104557ac3b05SIke Panhc { 104657ac3b05SIke Panhc if (!priv->rfk[dev]) 104757ac3b05SIke Panhc return; 104857ac3b05SIke Panhc 104957ac3b05SIke Panhc rfkill_unregister(priv->rfk[dev]); 105057ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 105157ac3b05SIke Panhc } 105257ac3b05SIke Panhc 105398ee6919SIke Panhc /* 105498ee6919SIke Panhc * Platform device 105598ee6919SIke Panhc */ 1056b5c37b79SZhang Rui static int ideapad_sysfs_init(struct ideapad_private *priv) 105798ee6919SIke Panhc { 10588782d8d7SBarnabás Pőcze return device_add_group(&priv->platform_device->dev, 1059c9f718d0SIke Panhc &ideapad_attribute_group); 106098ee6919SIke Panhc } 106198ee6919SIke Panhc 1062b5c37b79SZhang Rui static void ideapad_sysfs_exit(struct ideapad_private *priv) 106398ee6919SIke Panhc { 10648782d8d7SBarnabás Pőcze device_remove_group(&priv->platform_device->dev, 1065c9f718d0SIke Panhc &ideapad_attribute_group); 106698ee6919SIke Panhc } 106798ee6919SIke Panhc 1068f63409aeSIke Panhc /* 1069f63409aeSIke Panhc * input device 1070f63409aeSIke Panhc */ 1071f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = { 1072f43d9ec0SIke Panhc { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, 1073296f9fe0SMaxim Mikityanskiy { KE_KEY, 7, { KEY_CAMERA } }, 107448f67d62SAlex Hung { KE_KEY, 8, { KEY_MICMUTE } }, 1075296f9fe0SMaxim Mikityanskiy { KE_KEY, 11, { KEY_F16 } }, 1076f43d9ec0SIke Panhc { KE_KEY, 13, { KEY_WLAN } }, 1077f43d9ec0SIke Panhc { KE_KEY, 16, { KEY_PROG1 } }, 1078f43d9ec0SIke Panhc { KE_KEY, 17, { KEY_PROG2 } }, 1079296f9fe0SMaxim Mikityanskiy { KE_KEY, 64, { KEY_PROG3 } }, 1080296f9fe0SMaxim Mikityanskiy { KE_KEY, 65, { KEY_PROG4 } }, 108107a4a4fcSMaxim Mikityanskiy { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, 108207a4a4fcSMaxim Mikityanskiy { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, 108374caab99SArnd Bergmann { KE_KEY, 128, { KEY_ESC } }, 108465c7713aSBarnabás Pőcze { KE_END }, 1085f63409aeSIke Panhc }; 1086f63409aeSIke Panhc 1087b859f159SGreg Kroah-Hartman static int ideapad_input_init(struct ideapad_private *priv) 1088f63409aeSIke Panhc { 1089f63409aeSIke Panhc struct input_dev *inputdev; 109065c7713aSBarnabás Pőcze int err; 1091f63409aeSIke Panhc 1092f63409aeSIke Panhc inputdev = input_allocate_device(); 1093b222cca6SJoe Perches if (!inputdev) 1094f63409aeSIke Panhc return -ENOMEM; 1095f63409aeSIke Panhc 1096f63409aeSIke Panhc inputdev->name = "Ideapad extra buttons"; 1097f63409aeSIke Panhc inputdev->phys = "ideapad/input0"; 1098f63409aeSIke Panhc inputdev->id.bustype = BUS_HOST; 10998693ae84SIke Panhc inputdev->dev.parent = &priv->platform_device->dev; 1100f63409aeSIke Panhc 110165c7713aSBarnabás Pőcze err = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); 110265c7713aSBarnabás Pőcze if (err) { 1103654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 110465c7713aSBarnabás Pőcze "Could not set up input device keymap: %d\n", err); 1105f63409aeSIke Panhc goto err_free_dev; 1106f63409aeSIke Panhc } 1107f63409aeSIke Panhc 110865c7713aSBarnabás Pőcze err = input_register_device(inputdev); 110965c7713aSBarnabás Pőcze if (err) { 1110654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 111165c7713aSBarnabás Pőcze "Could not register input device: %d\n", err); 1112c973d4b5SMichał Kępień goto err_free_dev; 1113f63409aeSIke Panhc } 1114f63409aeSIke Panhc 11158693ae84SIke Panhc priv->inputdev = inputdev; 111665c7713aSBarnabás Pőcze 1117f63409aeSIke Panhc return 0; 1118f63409aeSIke Panhc 1119f63409aeSIke Panhc err_free_dev: 1120f63409aeSIke Panhc input_free_device(inputdev); 112165c7713aSBarnabás Pőcze 112265c7713aSBarnabás Pőcze return err; 1123f63409aeSIke Panhc } 1124f63409aeSIke Panhc 11257451a55aSAxel Lin static void ideapad_input_exit(struct ideapad_private *priv) 1126f63409aeSIke Panhc { 11278693ae84SIke Panhc input_unregister_device(priv->inputdev); 11288693ae84SIke Panhc priv->inputdev = NULL; 1129f63409aeSIke Panhc } 1130f63409aeSIke Panhc 11318693ae84SIke Panhc static void ideapad_input_report(struct ideapad_private *priv, 11328693ae84SIke Panhc unsigned long scancode) 1133f63409aeSIke Panhc { 11348693ae84SIke Panhc sparse_keymap_report_event(priv->inputdev, scancode, 1, true); 1135f63409aeSIke Panhc } 1136f63409aeSIke Panhc 1137f43d9ec0SIke Panhc static void ideapad_input_novokey(struct ideapad_private *priv) 1138f43d9ec0SIke Panhc { 1139f43d9ec0SIke Panhc unsigned long long_pressed; 1140f43d9ec0SIke Panhc 1141331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) 1142f43d9ec0SIke Panhc return; 114365c7713aSBarnabás Pőcze 1144f43d9ec0SIke Panhc if (long_pressed) 1145f43d9ec0SIke Panhc ideapad_input_report(priv, 17); 1146f43d9ec0SIke Panhc else 1147f43d9ec0SIke Panhc ideapad_input_report(priv, 16); 1148f43d9ec0SIke Panhc } 1149f43d9ec0SIke Panhc 1150296f9fe0SMaxim Mikityanskiy static void ideapad_check_special_buttons(struct ideapad_private *priv) 1151296f9fe0SMaxim Mikityanskiy { 1152296f9fe0SMaxim Mikityanskiy unsigned long bit, value; 1153296f9fe0SMaxim Mikityanskiy 11547be193e3SBarnabás Pőcze if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) 11557be193e3SBarnabás Pőcze return; 1156296f9fe0SMaxim Mikityanskiy 11570c4915b6SBarnabás Pőcze for_each_set_bit (bit, &value, 16) { 1158296f9fe0SMaxim Mikityanskiy switch (bit) { 1159a1ec56edSMaxim Mikityanskiy case 6: /* Z570 */ 116065c7713aSBarnabás Pőcze case 0: /* Z580 */ 1161296f9fe0SMaxim Mikityanskiy /* Thermal Management button */ 1162296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 65); 1163296f9fe0SMaxim Mikityanskiy break; 1164296f9fe0SMaxim Mikityanskiy case 1: 1165296f9fe0SMaxim Mikityanskiy /* OneKey Theater button */ 1166296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 64); 1167296f9fe0SMaxim Mikityanskiy break; 1168a1ec56edSMaxim Mikityanskiy default: 1169654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1170654324c4SBarnabás Pőcze "Unknown special button: %lu\n", bit); 1171a1ec56edSMaxim Mikityanskiy break; 1172296f9fe0SMaxim Mikityanskiy } 1173296f9fe0SMaxim Mikityanskiy } 1174296f9fe0SMaxim Mikityanskiy } 1175296f9fe0SMaxim Mikityanskiy 1176a4b5a279SIke Panhc /* 1177a4ecbb8aSIke Panhc * backlight 1178a4ecbb8aSIke Panhc */ 1179a4ecbb8aSIke Panhc static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) 1180a4ecbb8aSIke Panhc { 1181331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 1182a4ecbb8aSIke Panhc unsigned long now; 11837be193e3SBarnabás Pőcze int err; 1184a4ecbb8aSIke Panhc 11857be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 11867be193e3SBarnabás Pőcze if (err) 11877be193e3SBarnabás Pőcze return err; 118865c7713aSBarnabás Pőcze 1189a4ecbb8aSIke Panhc return now; 1190a4ecbb8aSIke Panhc } 1191a4ecbb8aSIke Panhc 1192a4ecbb8aSIke Panhc static int ideapad_backlight_update_status(struct backlight_device *blightdev) 1193a4ecbb8aSIke Panhc { 1194331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 11957be193e3SBarnabás Pőcze int err; 1196331e0ea2SZhang Rui 11977be193e3SBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, 11987be193e3SBarnabás Pőcze blightdev->props.brightness); 11997be193e3SBarnabás Pőcze if (err) 12007be193e3SBarnabás Pőcze return err; 120165c7713aSBarnabás Pőcze 12027be193e3SBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, 12037be193e3SBarnabás Pőcze blightdev->props.power != FB_BLANK_POWERDOWN); 12047be193e3SBarnabás Pőcze if (err) 12057be193e3SBarnabás Pőcze return err; 1206a4ecbb8aSIke Panhc 1207a4ecbb8aSIke Panhc return 0; 1208a4ecbb8aSIke Panhc } 1209a4ecbb8aSIke Panhc 1210a4ecbb8aSIke Panhc static const struct backlight_ops ideapad_backlight_ops = { 1211a4ecbb8aSIke Panhc .get_brightness = ideapad_backlight_get_brightness, 1212a4ecbb8aSIke Panhc .update_status = ideapad_backlight_update_status, 1213a4ecbb8aSIke Panhc }; 1214a4ecbb8aSIke Panhc 1215a4ecbb8aSIke Panhc static int ideapad_backlight_init(struct ideapad_private *priv) 1216a4ecbb8aSIke Panhc { 1217a4ecbb8aSIke Panhc struct backlight_device *blightdev; 1218a4ecbb8aSIke Panhc struct backlight_properties props; 1219a4ecbb8aSIke Panhc unsigned long max, now, power; 12207be193e3SBarnabás Pőcze int err; 1221a4ecbb8aSIke Panhc 12227be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max); 12237be193e3SBarnabás Pőcze if (err) 12247be193e3SBarnabás Pőcze return err; 122565c7713aSBarnabás Pőcze 12267be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 12277be193e3SBarnabás Pőcze if (err) 12287be193e3SBarnabás Pőcze return err; 122965c7713aSBarnabás Pőcze 12307be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power); 12317be193e3SBarnabás Pőcze if (err) 12327be193e3SBarnabás Pőcze return err; 1233a4ecbb8aSIke Panhc 123465c7713aSBarnabás Pőcze memset(&props, 0, sizeof(props)); 123565c7713aSBarnabás Pőcze 1236a4ecbb8aSIke Panhc props.max_brightness = max; 1237a4ecbb8aSIke Panhc props.type = BACKLIGHT_PLATFORM; 123865c7713aSBarnabás Pőcze 1239a4ecbb8aSIke Panhc blightdev = backlight_device_register("ideapad", 1240a4ecbb8aSIke Panhc &priv->platform_device->dev, 1241a4ecbb8aSIke Panhc priv, 1242a4ecbb8aSIke Panhc &ideapad_backlight_ops, 1243a4ecbb8aSIke Panhc &props); 1244a4ecbb8aSIke Panhc if (IS_ERR(blightdev)) { 124565c7713aSBarnabás Pőcze err = PTR_ERR(blightdev); 1246654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 124765c7713aSBarnabás Pőcze "Could not register backlight device: %d\n", err); 124865c7713aSBarnabás Pőcze return err; 1249a4ecbb8aSIke Panhc } 1250a4ecbb8aSIke Panhc 1251a4ecbb8aSIke Panhc priv->blightdev = blightdev; 1252a4ecbb8aSIke Panhc blightdev->props.brightness = now; 1253a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 125465c7713aSBarnabás Pőcze 1255a4ecbb8aSIke Panhc backlight_update_status(blightdev); 1256a4ecbb8aSIke Panhc 1257a4ecbb8aSIke Panhc return 0; 1258a4ecbb8aSIke Panhc } 1259a4ecbb8aSIke Panhc 1260a4ecbb8aSIke Panhc static void ideapad_backlight_exit(struct ideapad_private *priv) 1261a4ecbb8aSIke Panhc { 1262a4ecbb8aSIke Panhc backlight_device_unregister(priv->blightdev); 1263a4ecbb8aSIke Panhc priv->blightdev = NULL; 1264a4ecbb8aSIke Panhc } 1265a4ecbb8aSIke Panhc 1266a4ecbb8aSIke Panhc static void ideapad_backlight_notify_power(struct ideapad_private *priv) 1267a4ecbb8aSIke Panhc { 1268a4ecbb8aSIke Panhc struct backlight_device *blightdev = priv->blightdev; 126965c7713aSBarnabás Pőcze unsigned long power; 1270a4ecbb8aSIke Panhc 1271d4afc775SRene Bollford if (!blightdev) 1272d4afc775SRene Bollford return; 127365c7713aSBarnabás Pőcze 1274331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) 1275a4ecbb8aSIke Panhc return; 127665c7713aSBarnabás Pőcze 1277a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 1278a4ecbb8aSIke Panhc } 1279a4ecbb8aSIke Panhc 1280a4ecbb8aSIke Panhc static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) 1281a4ecbb8aSIke Panhc { 1282a4ecbb8aSIke Panhc unsigned long now; 1283a4ecbb8aSIke Panhc 1284a4ecbb8aSIke Panhc /* if we control brightness via acpi video driver */ 128565c7713aSBarnabás Pőcze if (!priv->blightdev) 1286331e0ea2SZhang Rui read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 128765c7713aSBarnabás Pőcze else 1288a4ecbb8aSIke Panhc backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); 1289a4ecbb8aSIke Panhc } 1290a4ecbb8aSIke Panhc 1291a4ecbb8aSIke Panhc /* 1292503325f8SBarnabás Pőcze * keyboard backlight 1293503325f8SBarnabás Pőcze */ 1294503325f8SBarnabás Pőcze static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv) 1295503325f8SBarnabás Pőcze { 1296503325f8SBarnabás Pőcze unsigned long hals; 1297503325f8SBarnabás Pőcze int err; 1298503325f8SBarnabás Pőcze 1299503325f8SBarnabás Pőcze err = eval_hals(priv->adev->handle, &hals); 1300503325f8SBarnabás Pőcze if (err) 1301503325f8SBarnabás Pőcze return err; 1302503325f8SBarnabás Pőcze 1303503325f8SBarnabás Pőcze return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals); 1304503325f8SBarnabás Pőcze } 1305503325f8SBarnabás Pőcze 1306503325f8SBarnabás Pőcze static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev) 1307503325f8SBarnabás Pőcze { 1308503325f8SBarnabás Pőcze struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); 1309503325f8SBarnabás Pőcze 1310503325f8SBarnabás Pőcze return ideapad_kbd_bl_brightness_get(priv); 1311503325f8SBarnabás Pőcze } 1312503325f8SBarnabás Pőcze 1313503325f8SBarnabás Pőcze static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness) 1314503325f8SBarnabás Pőcze { 1315503325f8SBarnabás Pőcze int err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF); 1316503325f8SBarnabás Pőcze 1317503325f8SBarnabás Pőcze if (err) 1318503325f8SBarnabás Pőcze return err; 1319503325f8SBarnabás Pőcze 1320503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1321503325f8SBarnabás Pőcze 1322503325f8SBarnabás Pőcze return 0; 1323503325f8SBarnabás Pőcze } 1324503325f8SBarnabás Pőcze 1325503325f8SBarnabás Pőcze static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev, 1326503325f8SBarnabás Pőcze enum led_brightness brightness) 1327503325f8SBarnabás Pőcze { 1328503325f8SBarnabás Pőcze struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); 1329503325f8SBarnabás Pőcze 1330503325f8SBarnabás Pőcze return ideapad_kbd_bl_brightness_set(priv, brightness); 1331503325f8SBarnabás Pőcze } 1332503325f8SBarnabás Pőcze 1333503325f8SBarnabás Pőcze static void ideapad_kbd_bl_notify(struct ideapad_private *priv) 1334503325f8SBarnabás Pőcze { 1335503325f8SBarnabás Pőcze int brightness; 1336503325f8SBarnabás Pőcze 1337503325f8SBarnabás Pőcze if (!priv->kbd_bl.initialized) 1338503325f8SBarnabás Pőcze return; 1339503325f8SBarnabás Pőcze 1340503325f8SBarnabás Pőcze brightness = ideapad_kbd_bl_brightness_get(priv); 1341503325f8SBarnabás Pőcze if (brightness < 0) 1342503325f8SBarnabás Pőcze return; 1343503325f8SBarnabás Pőcze 1344503325f8SBarnabás Pőcze if (brightness == priv->kbd_bl.last_brightness) 1345503325f8SBarnabás Pőcze return; 1346503325f8SBarnabás Pőcze 1347503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1348503325f8SBarnabás Pőcze 1349503325f8SBarnabás Pőcze led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness); 1350503325f8SBarnabás Pőcze } 1351503325f8SBarnabás Pőcze 1352503325f8SBarnabás Pőcze static int ideapad_kbd_bl_init(struct ideapad_private *priv) 1353503325f8SBarnabás Pőcze { 1354503325f8SBarnabás Pőcze int brightness, err; 1355503325f8SBarnabás Pőcze 1356503325f8SBarnabás Pőcze if (!priv->features.kbd_bl) 1357503325f8SBarnabás Pőcze return -ENODEV; 1358503325f8SBarnabás Pőcze 1359503325f8SBarnabás Pőcze if (WARN_ON(priv->kbd_bl.initialized)) 1360503325f8SBarnabás Pőcze return -EEXIST; 1361503325f8SBarnabás Pőcze 1362503325f8SBarnabás Pőcze brightness = ideapad_kbd_bl_brightness_get(priv); 1363503325f8SBarnabás Pőcze if (brightness < 0) 1364503325f8SBarnabás Pőcze return brightness; 1365503325f8SBarnabás Pőcze 1366503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1367503325f8SBarnabás Pőcze 1368503325f8SBarnabás Pőcze priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT; 1369503325f8SBarnabás Pőcze priv->kbd_bl.led.max_brightness = 1; 1370503325f8SBarnabás Pőcze priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get; 1371503325f8SBarnabás Pőcze priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set; 1372503325f8SBarnabás Pőcze priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED; 1373503325f8SBarnabás Pőcze 1374503325f8SBarnabás Pőcze err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led); 1375503325f8SBarnabás Pőcze if (err) 1376503325f8SBarnabás Pőcze return err; 1377503325f8SBarnabás Pőcze 1378503325f8SBarnabás Pőcze priv->kbd_bl.initialized = true; 1379503325f8SBarnabás Pőcze 1380503325f8SBarnabás Pőcze return 0; 1381503325f8SBarnabás Pőcze } 1382503325f8SBarnabás Pőcze 1383503325f8SBarnabás Pőcze static void ideapad_kbd_bl_exit(struct ideapad_private *priv) 1384503325f8SBarnabás Pőcze { 1385503325f8SBarnabás Pőcze if (!priv->kbd_bl.initialized) 1386503325f8SBarnabás Pőcze return; 1387503325f8SBarnabás Pőcze 1388503325f8SBarnabás Pőcze priv->kbd_bl.initialized = false; 1389503325f8SBarnabás Pőcze 1390503325f8SBarnabás Pőcze led_classdev_unregister(&priv->kbd_bl.led); 1391503325f8SBarnabás Pőcze } 1392503325f8SBarnabás Pőcze 1393503325f8SBarnabás Pőcze /* 1394a4b5a279SIke Panhc * module init/exit 1395a4b5a279SIke Panhc */ 139675a11f11SZhang Rui static void ideapad_sync_touchpad_state(struct ideapad_private *priv) 139707a4a4fcSMaxim Mikityanskiy { 139807a4a4fcSMaxim Mikityanskiy unsigned long value; 139907a4a4fcSMaxim Mikityanskiy 14001c59de4aSBarnabás Pőcze if (!priv->features.touchpad_ctrl_via_ec) 1401d69cd7eeSJiaxun Yang return; 1402d69cd7eeSJiaxun Yang 140307a4a4fcSMaxim Mikityanskiy /* Without reading from EC touchpad LED doesn't switch state */ 140475a11f11SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { 140565c7713aSBarnabás Pőcze unsigned char param; 140665c7713aSBarnabás Pőcze /* 140765c7713aSBarnabás Pőcze * Some IdeaPads don't really turn off touchpad - they only 140807a4a4fcSMaxim Mikityanskiy * switch the LED state. We (de)activate KBC AUX port to turn 140907a4a4fcSMaxim Mikityanskiy * touchpad off and on. We send KEY_TOUCHPAD_OFF and 141065c7713aSBarnabás Pőcze * KEY_TOUCHPAD_ON to not to get out of sync with LED 141165c7713aSBarnabás Pőcze */ 141265c7713aSBarnabás Pőcze i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE); 141307a4a4fcSMaxim Mikityanskiy ideapad_input_report(priv, value ? 67 : 66); 1414c6795746SBarnabás Pőcze sysfs_notify(&priv->platform_device->dev.kobj, NULL, "touchpad"); 141507a4a4fcSMaxim Mikityanskiy } 141607a4a4fcSMaxim Mikityanskiy } 141707a4a4fcSMaxim Mikityanskiy 1418b5c37b79SZhang Rui static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) 141957ac3b05SIke Panhc { 1420b5c37b79SZhang Rui struct ideapad_private *priv = data; 14210c4915b6SBarnabás Pőcze unsigned long vpc1, vpc2, bit; 142257ac3b05SIke Panhc 14232be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) 142457ac3b05SIke Panhc return; 142565c7713aSBarnabás Pőcze 14262be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) 142757ac3b05SIke Panhc return; 142857ac3b05SIke Panhc 142957ac3b05SIke Panhc vpc1 = (vpc2 << 8) | vpc1; 14300c4915b6SBarnabás Pőcze 14310c4915b6SBarnabás Pőcze for_each_set_bit (bit, &vpc1, 16) { 14320c4915b6SBarnabás Pőcze switch (bit) { 143320a769c1SIke Panhc case 13: 1434296f9fe0SMaxim Mikityanskiy case 11: 143548f67d62SAlex Hung case 8: 1436296f9fe0SMaxim Mikityanskiy case 7: 143720a769c1SIke Panhc case 6: 14380c4915b6SBarnabás Pőcze ideapad_input_report(priv, bit); 143920a769c1SIke Panhc break; 1440ab66724aSHans de Goede case 10: 1441ab66724aSHans de Goede /* 1442ab66724aSHans de Goede * This event gets send on a Yoga 300-11IBR when the EC 1443ab66724aSHans de Goede * believes that the device has changed between laptop/ 1444ab66724aSHans de Goede * tent/stand/tablet mode. The EC relies on getting 1445ab66724aSHans de Goede * angle info from 2 accelerometers through a special 1446ab66724aSHans de Goede * windows service calling a DSM on the DUAL250E ACPI- 1447ab66724aSHans de Goede * device. Linux does not do this, making the laptop/ 1448ab66724aSHans de Goede * tent/stand/tablet mode info unreliable, so we simply 1449ab66724aSHans de Goede * ignore these events. 1450ab66724aSHans de Goede */ 1451ab66724aSHans de Goede break; 145265c7713aSBarnabás Pőcze case 9: 145365c7713aSBarnabás Pőcze ideapad_sync_rfk_state(priv); 145465c7713aSBarnabás Pőcze break; 145507a4a4fcSMaxim Mikityanskiy case 5: 145675a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 145707a4a4fcSMaxim Mikityanskiy break; 1458a4ecbb8aSIke Panhc case 4: 1459a4ecbb8aSIke Panhc ideapad_backlight_notify_brightness(priv); 1460a4ecbb8aSIke Panhc break; 1461f43d9ec0SIke Panhc case 3: 1462f43d9ec0SIke Panhc ideapad_input_novokey(priv); 1463f43d9ec0SIke Panhc break; 1464a4ecbb8aSIke Panhc case 2: 1465a4ecbb8aSIke Panhc ideapad_backlight_notify_power(priv); 1466a4ecbb8aSIke Panhc break; 14673cfd956bSHao Wei Tee case 1: 146865c7713aSBarnabás Pőcze /* 146965c7713aSBarnabás Pőcze * Some IdeaPads report event 1 every ~20 14703cfd956bSHao Wei Tee * seconds while on battery power; some 14713cfd956bSHao Wei Tee * report this when changing to/from tablet 1472503325f8SBarnabás Pőcze * mode; some report this when the keyboard 1473503325f8SBarnabás Pőcze * backlight has changed. 14743cfd956bSHao Wei Tee */ 1475503325f8SBarnabás Pőcze ideapad_kbd_bl_notify(priv); 14763cfd956bSHao Wei Tee break; 147765c7713aSBarnabás Pőcze case 0: 147865c7713aSBarnabás Pőcze ideapad_check_special_buttons(priv); 147965c7713aSBarnabás Pőcze break; 1480a4ecbb8aSIke Panhc default: 1481654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1482654324c4SBarnabás Pőcze "Unknown event: %lu\n", bit); 148357ac3b05SIke Panhc } 148457ac3b05SIke Panhc } 1485a4ecbb8aSIke Panhc } 148657ac3b05SIke Panhc 148774caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 148874caab99SArnd Bergmann static void ideapad_wmi_notify(u32 value, void *context) 148974caab99SArnd Bergmann { 1490654324c4SBarnabás Pőcze struct ideapad_private *priv = context; 14913ae86d2dSMeng Dong unsigned long result; 1492654324c4SBarnabás Pőcze 149374caab99SArnd Bergmann switch (value) { 149474caab99SArnd Bergmann case 128: 1495654324c4SBarnabás Pőcze ideapad_input_report(priv, value); 149674caab99SArnd Bergmann break; 14973ae86d2dSMeng Dong case 208: 14983ae86d2dSMeng Dong if (!eval_hals(priv->adev->handle, &result)) { 14993ae86d2dSMeng Dong bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result); 15003ae86d2dSMeng Dong 15013ae86d2dSMeng Dong exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); 15023ae86d2dSMeng Dong } 15033ae86d2dSMeng Dong break; 150474caab99SArnd Bergmann default: 1505654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1506654324c4SBarnabás Pőcze "Unknown WMI event: %u\n", value); 150774caab99SArnd Bergmann } 150874caab99SArnd Bergmann } 150974caab99SArnd Bergmann #endif 151074caab99SArnd Bergmann 1511ce363c2bSHans de Goede /* 15125105e78eSHans de Goede * Some ideapads have a hardware rfkill switch, but most do not have one. 15135105e78eSHans de Goede * Reading VPCCMD_R_RF always results in 0 on models without a hardware rfkill, 15145105e78eSHans de Goede * switch causing ideapad_laptop to wrongly report all radios as hw-blocked. 15155105e78eSHans de Goede * There used to be a long list of DMI ids for models without a hw rfkill 15165105e78eSHans de Goede * switch here, but that resulted in playing whack a mole. 15175105e78eSHans de Goede * More importantly wrongly reporting the wifi radio as hw-blocked, results in 15185105e78eSHans de Goede * non working wifi. Whereas not reporting it hw-blocked, when it actually is 15195105e78eSHans de Goede * hw-blocked results in an empty SSID list, which is a much more benign 15205105e78eSHans de Goede * failure mode. 15215105e78eSHans de Goede * So the default now is the much safer option of assuming there is no 15225105e78eSHans de Goede * hardware rfkill switch. This default also actually matches most hardware, 15235105e78eSHans de Goede * since having a hw rfkill switch is quite rare on modern hardware, so this 15245105e78eSHans de Goede * also leads to a much shorter list. 1525ce363c2bSHans de Goede */ 15265105e78eSHans de Goede static const struct dmi_system_id hw_rfkill_list[] = { 152785093f79SHans de Goede {} 152885093f79SHans de Goede }; 152985093f79SHans de Goede 15301c59de4aSBarnabás Pőcze static void ideapad_check_features(struct ideapad_private *priv) 15311c59de4aSBarnabás Pőcze { 15321c59de4aSBarnabás Pőcze acpi_handle handle = priv->adev->handle; 15331c59de4aSBarnabás Pőcze unsigned long val; 15341c59de4aSBarnabás Pőcze 15351c59de4aSBarnabás Pőcze priv->features.hw_rfkill_switch = dmi_check_system(hw_rfkill_list); 15361c59de4aSBarnabás Pőcze 15371c59de4aSBarnabás Pőcze /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ 15381c59de4aSBarnabás Pőcze priv->features.touchpad_ctrl_via_ec = !acpi_dev_present("ELAN0634", NULL, -1); 15391c59de4aSBarnabás Pőcze 15401c59de4aSBarnabás Pőcze if (!read_ec_data(handle, VPCCMD_R_FAN, &val)) 15411c59de4aSBarnabás Pőcze priv->features.fan_mode = true; 15421c59de4aSBarnabás Pőcze 15431c59de4aSBarnabás Pőcze if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) 15441c59de4aSBarnabás Pőcze priv->features.conservation_mode = true; 15451c59de4aSBarnabás Pőcze 15461c59de4aSBarnabás Pőcze if (acpi_has_method(handle, "DYTC")) 15471c59de4aSBarnabás Pőcze priv->features.dytc = true; 15481c59de4aSBarnabás Pőcze 1549392cbf0aSBarnabás Pőcze if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) { 1550392cbf0aSBarnabás Pőcze if (!eval_hals(handle, &val)) { 1551392cbf0aSBarnabás Pőcze if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val)) 15521c59de4aSBarnabás Pőcze priv->features.fn_lock = true; 1553503325f8SBarnabás Pőcze 1554503325f8SBarnabás Pőcze if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) 1555503325f8SBarnabás Pőcze priv->features.kbd_bl = true; 15566b49dea4SBarnabás Pőcze 15576b49dea4SBarnabás Pőcze if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val)) 15586b49dea4SBarnabás Pőcze priv->features.usb_charging = true; 15591c59de4aSBarnabás Pőcze } 1560392cbf0aSBarnabás Pőcze } 1561392cbf0aSBarnabás Pőcze } 15621c59de4aSBarnabás Pőcze 1563b5c37b79SZhang Rui static int ideapad_acpi_add(struct platform_device *pdev) 1564b5c37b79SZhang Rui { 1565043449e7SRafael J. Wysocki struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 1566b5c37b79SZhang Rui struct ideapad_private *priv; 1567803be832SBarnabás Pőcze acpi_status status; 1568ff36b0d9SBarnabás Pőcze unsigned long cfg; 156965c7713aSBarnabás Pőcze int err, i; 1570b5c37b79SZhang Rui 1571043449e7SRafael J. Wysocki if (!adev || eval_int(adev->handle, "_CFG", &cfg)) 1572b5c37b79SZhang Rui return -ENODEV; 1573b5c37b79SZhang Rui 1574b3facd7bSHimangi Saraogi priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 1575b5c37b79SZhang Rui if (!priv) 1576b5c37b79SZhang Rui return -ENOMEM; 1577b5c37b79SZhang Rui 1578b5c37b79SZhang Rui dev_set_drvdata(&pdev->dev, priv); 157965c7713aSBarnabás Pőcze 1580b5c37b79SZhang Rui priv->cfg = cfg; 1581b5c37b79SZhang Rui priv->adev = adev; 1582b5c37b79SZhang Rui priv->platform_device = pdev; 1583b5c37b79SZhang Rui 15841c59de4aSBarnabás Pőcze ideapad_check_features(priv); 1585d69cd7eeSJiaxun Yang 158665c7713aSBarnabás Pőcze err = ideapad_sysfs_init(priv); 158765c7713aSBarnabás Pőcze if (err) 158865c7713aSBarnabás Pőcze return err; 1589b5c37b79SZhang Rui 159017f1bf38SGreg Kroah-Hartman ideapad_debugfs_init(priv); 1591b5c37b79SZhang Rui 159265c7713aSBarnabás Pőcze err = ideapad_input_init(priv); 159365c7713aSBarnabás Pőcze if (err) 1594b5c37b79SZhang Rui goto input_failed; 1595b5c37b79SZhang Rui 1596503325f8SBarnabás Pőcze err = ideapad_kbd_bl_init(priv); 1597503325f8SBarnabás Pőcze if (err) { 1598503325f8SBarnabás Pőcze if (err != -ENODEV) 1599503325f8SBarnabás Pőcze dev_warn(&pdev->dev, "Could not set up keyboard backlight LED: %d\n", err); 1600503325f8SBarnabás Pőcze else 1601503325f8SBarnabás Pőcze dev_info(&pdev->dev, "Keyboard backlight control not available\n"); 1602503325f8SBarnabás Pőcze } 1603503325f8SBarnabás Pőcze 1604ce363c2bSHans de Goede /* 1605ce363c2bSHans de Goede * On some models without a hw-switch (the yoga 2 13 at least) 1606ce363c2bSHans de Goede * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. 1607ce363c2bSHans de Goede */ 16081c59de4aSBarnabás Pőcze if (!priv->features.hw_rfkill_switch) 1609ce363c2bSHans de Goede write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); 1610ce363c2bSHans de Goede 1611d69cd7eeSJiaxun Yang /* The same for Touchpad */ 16121c59de4aSBarnabás Pőcze if (!priv->features.touchpad_ctrl_via_ec) 1613d69cd7eeSJiaxun Yang write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, 1); 1614d69cd7eeSJiaxun Yang 161585093f79SHans de Goede for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1616b5c37b79SZhang Rui if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) 1617b5c37b79SZhang Rui ideapad_register_rfkill(priv, i); 1618ce363c2bSHans de Goede 1619b5c37b79SZhang Rui ideapad_sync_rfk_state(priv); 1620b5c37b79SZhang Rui ideapad_sync_touchpad_state(priv); 1621b5c37b79SZhang Rui 162265c7713aSBarnabás Pőcze err = ideapad_dytc_profile_init(priv); 162365c7713aSBarnabás Pőcze if (err) { 162465c7713aSBarnabás Pőcze if (err != -ENODEV) 162565c7713aSBarnabás Pőcze dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", err); 16261c59de4aSBarnabás Pőcze else 16271c59de4aSBarnabás Pőcze dev_info(&pdev->dev, "DYTC interface is not available\n"); 16281c59de4aSBarnabás Pőcze } 1629eabe5339SJiaxun Yang 163026bff5f0SHans de Goede if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { 163165c7713aSBarnabás Pőcze err = ideapad_backlight_init(priv); 163265c7713aSBarnabás Pőcze if (err && err != -ENODEV) 1633b5c37b79SZhang Rui goto backlight_failed; 1634b5c37b79SZhang Rui } 163565c7713aSBarnabás Pőcze 1636803be832SBarnabás Pőcze status = acpi_install_notify_handler(adev->handle, 1637803be832SBarnabás Pőcze ACPI_DEVICE_NOTIFY, 1638803be832SBarnabás Pőcze ideapad_acpi_notify, priv); 1639803be832SBarnabás Pőcze if (ACPI_FAILURE(status)) { 164065c7713aSBarnabás Pőcze err = -EIO; 1641b5c37b79SZhang Rui goto notification_failed; 1642803be832SBarnabás Pőcze } 16432d98e0b9SArnd Bergmann 164474caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 16452d98e0b9SArnd Bergmann for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { 1646803be832SBarnabás Pőcze status = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], 16472d98e0b9SArnd Bergmann ideapad_wmi_notify, priv); 1648803be832SBarnabás Pőcze if (ACPI_SUCCESS(status)) { 16492d98e0b9SArnd Bergmann priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; 16502d98e0b9SArnd Bergmann break; 16512d98e0b9SArnd Bergmann } 16522d98e0b9SArnd Bergmann } 165365c7713aSBarnabás Pőcze 1654803be832SBarnabás Pőcze if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) { 165565c7713aSBarnabás Pőcze err = -EIO; 165674caab99SArnd Bergmann goto notification_failed_wmi; 1657803be832SBarnabás Pőcze } 165874caab99SArnd Bergmann #endif 1659b5c37b79SZhang Rui 1660b5c37b79SZhang Rui return 0; 166165c7713aSBarnabás Pőcze 166274caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 166374caab99SArnd Bergmann notification_failed_wmi: 166474caab99SArnd Bergmann acpi_remove_notify_handler(priv->adev->handle, 166565c7713aSBarnabás Pőcze ACPI_DEVICE_NOTIFY, 166665c7713aSBarnabás Pőcze ideapad_acpi_notify); 166774caab99SArnd Bergmann #endif 166865c7713aSBarnabás Pőcze 1669b5c37b79SZhang Rui notification_failed: 1670b5c37b79SZhang Rui ideapad_backlight_exit(priv); 167165c7713aSBarnabás Pőcze 1672b5c37b79SZhang Rui backlight_failed: 1673caa315b8SBarnabás Pőcze ideapad_dytc_profile_exit(priv); 167465c7713aSBarnabás Pőcze 1675b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1676b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 167765c7713aSBarnabás Pőcze 1678503325f8SBarnabás Pőcze ideapad_kbd_bl_exit(priv); 1679b5c37b79SZhang Rui ideapad_input_exit(priv); 168065c7713aSBarnabás Pőcze 1681b5c37b79SZhang Rui input_failed: 1682b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1683b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 168465c7713aSBarnabás Pőcze 168565c7713aSBarnabás Pőcze return err; 1686b5c37b79SZhang Rui } 1687b5c37b79SZhang Rui 1688b5c37b79SZhang Rui static int ideapad_acpi_remove(struct platform_device *pdev) 1689b5c37b79SZhang Rui { 1690b5c37b79SZhang Rui struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); 1691b5c37b79SZhang Rui int i; 1692b5c37b79SZhang Rui 169374caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 16942d98e0b9SArnd Bergmann if (priv->fnesc_guid) 16952d98e0b9SArnd Bergmann wmi_remove_notify_handler(priv->fnesc_guid); 169674caab99SArnd Bergmann #endif 169765c7713aSBarnabás Pőcze 1698b5c37b79SZhang Rui acpi_remove_notify_handler(priv->adev->handle, 169965c7713aSBarnabás Pőcze ACPI_DEVICE_NOTIFY, 170065c7713aSBarnabás Pőcze ideapad_acpi_notify); 170165c7713aSBarnabás Pőcze 1702b5c37b79SZhang Rui ideapad_backlight_exit(priv); 1703eabe5339SJiaxun Yang ideapad_dytc_profile_exit(priv); 170465c7713aSBarnabás Pőcze 1705b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1706b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 170765c7713aSBarnabás Pőcze 1708503325f8SBarnabás Pőcze ideapad_kbd_bl_exit(priv); 1709b5c37b79SZhang Rui ideapad_input_exit(priv); 1710b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1711b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 1712b5c37b79SZhang Rui 1713b5c37b79SZhang Rui return 0; 1714b5c37b79SZhang Rui } 1715b5c37b79SZhang Rui 171611fa8da5SZhang Rui #ifdef CONFIG_PM_SLEEP 1717e1a39a44SBarnabás Pőcze static int ideapad_acpi_resume(struct device *dev) 171807a4a4fcSMaxim Mikityanskiy { 1719e1a39a44SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 172075a11f11SZhang Rui 172175a11f11SZhang Rui ideapad_sync_rfk_state(priv); 172275a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 1723eabe5339SJiaxun Yang 1724eabe5339SJiaxun Yang if (priv->dytc) 1725eabe5339SJiaxun Yang dytc_profile_refresh(priv); 1726eabe5339SJiaxun Yang 172707a4a4fcSMaxim Mikityanskiy return 0; 172807a4a4fcSMaxim Mikityanskiy } 1729b5c37b79SZhang Rui #endif 173007a4a4fcSMaxim Mikityanskiy static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); 173107a4a4fcSMaxim Mikityanskiy 1732b5c37b79SZhang Rui static const struct acpi_device_id ideapad_device_ids[] = { 1733b5c37b79SZhang Rui {"VPC2004", 0}, 1734b5c37b79SZhang Rui {"", 0}, 173557ac3b05SIke Panhc }; 1736b5c37b79SZhang Rui MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 1737b5c37b79SZhang Rui 1738b5c37b79SZhang Rui static struct platform_driver ideapad_acpi_driver = { 1739b5c37b79SZhang Rui .probe = ideapad_acpi_add, 1740b5c37b79SZhang Rui .remove = ideapad_acpi_remove, 1741b5c37b79SZhang Rui .driver = { 1742b5c37b79SZhang Rui .name = "ideapad_acpi", 1743b5c37b79SZhang Rui .pm = &ideapad_pm, 1744b5c37b79SZhang Rui .acpi_match_table = ACPI_PTR(ideapad_device_ids), 1745b5c37b79SZhang Rui }, 1746b5c37b79SZhang Rui }; 1747b5c37b79SZhang Rui 1748b5c37b79SZhang Rui module_platform_driver(ideapad_acpi_driver); 174957ac3b05SIke Panhc 175057ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 175157ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 175257ac3b05SIke Panhc MODULE_LICENSE("GPL"); 1753