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; 139*81a5603aSArnav Rawat bool set_fn_lock_led : 1; 1401c59de4aSBarnabás Pőcze bool hw_rfkill_switch : 1; 141503325f8SBarnabás Pőcze bool kbd_bl : 1; 1421c59de4aSBarnabás Pőcze bool touchpad_ctrl_via_ec : 1; 1436b49dea4SBarnabás Pőcze bool usb_charging : 1; 1441c59de4aSBarnabás Pőcze } features; 145503325f8SBarnabás Pőcze struct { 146503325f8SBarnabás Pőcze bool initialized; 147503325f8SBarnabás Pőcze struct led_classdev led; 148503325f8SBarnabás Pőcze unsigned int last_brightness; 149503325f8SBarnabás Pőcze } kbd_bl; 15057ac3b05SIke Panhc }; 15157ac3b05SIke Panhc 152bfa97b7dSIke Panhc static bool no_bt_rfkill; 153bfa97b7dSIke Panhc module_param(no_bt_rfkill, bool, 0444); 154bfa97b7dSIke Panhc MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); 155bfa97b7dSIke Panhc 156a27a1e35SHans de Goede static bool allow_v4_dytc; 157a27a1e35SHans de Goede module_param(allow_v4_dytc, bool, 0444); 158a27a1e35SHans de Goede MODULE_PARM_DESC(allow_v4_dytc, "Enable DYTC version 4 platform-profile support."); 159a27a1e35SHans de Goede 16057ac3b05SIke Panhc /* 16157ac3b05SIke Panhc * ACPI Helpers 16257ac3b05SIke Panhc */ 16365c7713aSBarnabás Pőcze #define IDEAPAD_EC_TIMEOUT 200 /* in ms */ 16457ac3b05SIke Panhc 165ff36b0d9SBarnabás Pőcze static int eval_int(acpi_handle handle, const char *name, unsigned long *res) 16657ac3b05SIke Panhc { 16757ac3b05SIke Panhc unsigned long long result; 168ade50296SHao Wei Tee acpi_status status; 169ade50296SHao Wei Tee 170ff36b0d9SBarnabás Pőcze status = acpi_evaluate_integer(handle, (char *)name, NULL, &result); 171ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 172ff36b0d9SBarnabás Pőcze return -EIO; 17365c7713aSBarnabás Pőcze 174ff36b0d9SBarnabás Pőcze *res = result; 17565c7713aSBarnabás Pőcze 176ff36b0d9SBarnabás Pőcze return 0; 177ff36b0d9SBarnabás Pőcze } 178ff36b0d9SBarnabás Pőcze 179ff36b0d9SBarnabás Pőcze static int exec_simple_method(acpi_handle handle, const char *name, unsigned long arg) 180ff36b0d9SBarnabás Pőcze { 181ff36b0d9SBarnabás Pőcze acpi_status status = acpi_execute_simple_method(handle, (char *)name, arg); 182ff36b0d9SBarnabás Pőcze 1837be193e3SBarnabás Pőcze return ACPI_FAILURE(status) ? -EIO : 0; 184ade50296SHao Wei Tee } 185ade50296SHao Wei Tee 186ff36b0d9SBarnabás Pőcze static int eval_gbmd(acpi_handle handle, unsigned long *res) 187eabe5339SJiaxun Yang { 188ff36b0d9SBarnabás Pőcze return eval_int(handle, "GBMD", res); 189ff36b0d9SBarnabás Pőcze } 190ff36b0d9SBarnabás Pőcze 191b09aaa3fSBarnabás Pőcze static int exec_sbmc(acpi_handle handle, unsigned long arg) 192ff36b0d9SBarnabás Pőcze { 193b09aaa3fSBarnabás Pőcze return exec_simple_method(handle, "SBMC", arg); 194ff36b0d9SBarnabás Pőcze } 195ff36b0d9SBarnabás Pőcze 196ff36b0d9SBarnabás Pőcze static int eval_hals(acpi_handle handle, unsigned long *res) 197ff36b0d9SBarnabás Pőcze { 198ff36b0d9SBarnabás Pőcze return eval_int(handle, "HALS", res); 199ff36b0d9SBarnabás Pőcze } 200ff36b0d9SBarnabás Pőcze 201ff36b0d9SBarnabás Pőcze static int exec_sals(acpi_handle handle, unsigned long arg) 202ff36b0d9SBarnabás Pőcze { 203ff36b0d9SBarnabás Pőcze return exec_simple_method(handle, "SALS", arg); 204ff36b0d9SBarnabás Pőcze } 205ff36b0d9SBarnabás Pőcze 206ff36b0d9SBarnabás Pőcze static int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long arg, unsigned long *res) 207ff36b0d9SBarnabás Pőcze { 208eabe5339SJiaxun Yang struct acpi_object_list params; 209ff36b0d9SBarnabás Pőcze unsigned long long result; 210eabe5339SJiaxun Yang union acpi_object in_obj; 211ff36b0d9SBarnabás Pőcze acpi_status status; 212eabe5339SJiaxun Yang 213eabe5339SJiaxun Yang params.count = 1; 214eabe5339SJiaxun Yang params.pointer = &in_obj; 215eabe5339SJiaxun Yang in_obj.type = ACPI_TYPE_INTEGER; 216ff36b0d9SBarnabás Pőcze in_obj.integer.value = arg; 217eabe5339SJiaxun Yang 218ff36b0d9SBarnabás Pőcze status = acpi_evaluate_integer(handle, (char *)name, ¶ms, &result); 219ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 2207be193e3SBarnabás Pőcze return -EIO; 221ff36b0d9SBarnabás Pőcze 222ff36b0d9SBarnabás Pőcze if (res) 223ff36b0d9SBarnabás Pőcze *res = result; 224ff36b0d9SBarnabás Pőcze 225eabe5339SJiaxun Yang return 0; 226eabe5339SJiaxun Yang } 227eabe5339SJiaxun Yang 228ff36b0d9SBarnabás Pőcze static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res) 22957ac3b05SIke Panhc { 230ff36b0d9SBarnabás Pőcze return eval_int_with_arg(handle, "DYTC", cmd, res); 23157ac3b05SIke Panhc } 23257ac3b05SIke Panhc 233ff36b0d9SBarnabás Pőcze static int eval_vpcr(acpi_handle handle, unsigned long cmd, unsigned long *res) 234ff36b0d9SBarnabás Pőcze { 235ff36b0d9SBarnabás Pőcze return eval_int_with_arg(handle, "VPCR", cmd, res); 236ff36b0d9SBarnabás Pőcze } 237ff36b0d9SBarnabás Pőcze 238ff36b0d9SBarnabás Pőcze static int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data) 23957ac3b05SIke Panhc { 24057ac3b05SIke Panhc struct acpi_object_list params; 24157ac3b05SIke Panhc union acpi_object in_obj[2]; 24257ac3b05SIke Panhc acpi_status status; 24357ac3b05SIke Panhc 24457ac3b05SIke Panhc params.count = 2; 24557ac3b05SIke Panhc params.pointer = in_obj; 24657ac3b05SIke Panhc in_obj[0].type = ACPI_TYPE_INTEGER; 24757ac3b05SIke Panhc in_obj[0].integer.value = cmd; 24857ac3b05SIke Panhc in_obj[1].type = ACPI_TYPE_INTEGER; 24957ac3b05SIke Panhc in_obj[1].integer.value = data; 25057ac3b05SIke Panhc 25157ac3b05SIke Panhc status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); 252ff36b0d9SBarnabás Pőcze if (ACPI_FAILURE(status)) 2537be193e3SBarnabás Pőcze return -EIO; 25465c7713aSBarnabás Pőcze 25557ac3b05SIke Panhc return 0; 25657ac3b05SIke Panhc } 25757ac3b05SIke Panhc 258ff36b0d9SBarnabás Pőcze static int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *data) 25957ac3b05SIke Panhc { 260ff36b0d9SBarnabás Pőcze unsigned long end_jiffies, val; 261ff36b0d9SBarnabás Pőcze int err; 26257ac3b05SIke Panhc 263ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 1, cmd); 2647be193e3SBarnabás Pőcze if (err) 2657be193e3SBarnabás Pőcze return err; 26657ac3b05SIke Panhc 26740e0447dSBarnabás Pőcze end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; 26840e0447dSBarnabás Pőcze 26940e0447dSBarnabás Pőcze while (time_before(jiffies, end_jiffies)) { 27057ac3b05SIke Panhc schedule(); 27165c7713aSBarnabás Pőcze 272ff36b0d9SBarnabás Pőcze err = eval_vpcr(handle, 1, &val); 2737be193e3SBarnabás Pőcze if (err) 2747be193e3SBarnabás Pőcze return err; 27565c7713aSBarnabás Pőcze 276ff36b0d9SBarnabás Pőcze if (val == 0) 277ff36b0d9SBarnabás Pőcze return eval_vpcr(handle, 0, data); 27857ac3b05SIke Panhc } 27965c7713aSBarnabás Pőcze 280654324c4SBarnabás Pőcze acpi_handle_err(handle, "timeout in %s\n", __func__); 28165c7713aSBarnabás Pőcze 2827be193e3SBarnabás Pőcze return -ETIMEDOUT; 28357ac3b05SIke Panhc } 28457ac3b05SIke Panhc 285ff36b0d9SBarnabás Pőcze static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long data) 28657ac3b05SIke Panhc { 287ff36b0d9SBarnabás Pőcze unsigned long end_jiffies, val; 288ff36b0d9SBarnabás Pőcze int err; 28957ac3b05SIke Panhc 290ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 0, data); 2917be193e3SBarnabás Pőcze if (err) 2927be193e3SBarnabás Pőcze return err; 29365c7713aSBarnabás Pőcze 294ff36b0d9SBarnabás Pőcze err = eval_vpcw(handle, 1, cmd); 2957be193e3SBarnabás Pőcze if (err) 2967be193e3SBarnabás Pőcze return err; 29757ac3b05SIke Panhc 29840e0447dSBarnabás Pőcze end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; 29940e0447dSBarnabás Pőcze 30040e0447dSBarnabás Pőcze while (time_before(jiffies, end_jiffies)) { 30157ac3b05SIke Panhc schedule(); 30265c7713aSBarnabás Pőcze 303ff36b0d9SBarnabás Pőcze err = eval_vpcr(handle, 1, &val); 3047be193e3SBarnabás Pőcze if (err) 3057be193e3SBarnabás Pőcze return err; 30665c7713aSBarnabás Pőcze 30757ac3b05SIke Panhc if (val == 0) 30857ac3b05SIke Panhc return 0; 30957ac3b05SIke Panhc } 31065c7713aSBarnabás Pőcze 311654324c4SBarnabás Pőcze acpi_handle_err(handle, "timeout in %s\n", __func__); 31265c7713aSBarnabás Pőcze 3137be193e3SBarnabás Pőcze return -ETIMEDOUT; 31457ac3b05SIke Panhc } 31557ac3b05SIke Panhc 316a4b5a279SIke Panhc /* 317773e3206SIke Panhc * debugfs 318773e3206SIke Panhc */ 319773e3206SIke Panhc static int debugfs_status_show(struct seq_file *s, void *data) 320773e3206SIke Panhc { 321331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 322773e3206SIke Panhc unsigned long value; 323773e3206SIke Panhc 324331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) 3257553390dSBarnabás Pőcze seq_printf(s, "Backlight max: %lu\n", value); 326331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) 3277553390dSBarnabás Pőcze seq_printf(s, "Backlight now: %lu\n", value); 328331e0ea2SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) 3297553390dSBarnabás Pőcze seq_printf(s, "BL power value: %s (%lu)\n", value ? "on" : "off", value); 33065c7713aSBarnabás Pőcze 331ade50296SHao Wei Tee seq_puts(s, "=====================\n"); 332ade50296SHao Wei Tee 3337553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) 3347553390dSBarnabás Pőcze seq_printf(s, "Radio status: %s (%lu)\n", value ? "on" : "off", value); 3357553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) 3367553390dSBarnabás Pőcze seq_printf(s, "Wifi status: %s (%lu)\n", value ? "on" : "off", value); 3377553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) 3387553390dSBarnabás Pőcze seq_printf(s, "BT status: %s (%lu)\n", value ? "on" : "off", value); 3397553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) 3407553390dSBarnabás Pőcze seq_printf(s, "3G status: %s (%lu)\n", value ? "on" : "off", value); 34165c7713aSBarnabás Pőcze 3427553390dSBarnabás Pőcze seq_puts(s, "=====================\n"); 3437553390dSBarnabás Pőcze 3447553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) 3457553390dSBarnabás Pőcze seq_printf(s, "Touchpad status: %s (%lu)\n", value ? "on" : "off", value); 3467553390dSBarnabás Pőcze if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) 3477553390dSBarnabás Pőcze seq_printf(s, "Camera status: %s (%lu)\n", value ? "on" : "off", value); 34865c7713aSBarnabás Pőcze 3497553390dSBarnabás Pőcze seq_puts(s, "=====================\n"); 3507553390dSBarnabás Pőcze 3517553390dSBarnabás Pőcze if (!eval_gbmd(priv->adev->handle, &value)) 3527553390dSBarnabás Pőcze seq_printf(s, "GBMD: %#010lx\n", value); 3537553390dSBarnabás Pőcze if (!eval_hals(priv->adev->handle, &value)) 3547553390dSBarnabás Pőcze seq_printf(s, "HALS: %#010lx\n", value); 355773e3206SIke Panhc 356773e3206SIke Panhc return 0; 357773e3206SIke Panhc } 358334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_status); 359773e3206SIke Panhc 360773e3206SIke Panhc static int debugfs_cfg_show(struct seq_file *s, void *data) 361773e3206SIke Panhc { 362331e0ea2SZhang Rui struct ideapad_private *priv = s->private; 363331e0ea2SZhang Rui 36418227424SBarnabás Pőcze seq_printf(s, "_CFG: %#010lx\n\n", priv->cfg); 36518227424SBarnabás Pőcze 36618227424SBarnabás Pőcze seq_puts(s, "Capabilities:"); 3670b765671SBarnabás Pőcze if (test_bit(CFG_CAP_BT_BIT, &priv->cfg)) 36818227424SBarnabás Pőcze seq_puts(s, " bluetooth"); 3690b765671SBarnabás Pőcze if (test_bit(CFG_CAP_3G_BIT, &priv->cfg)) 37018227424SBarnabás Pőcze seq_puts(s, " 3G"); 3710b765671SBarnabás Pőcze if (test_bit(CFG_CAP_WIFI_BIT, &priv->cfg)) 37218227424SBarnabás Pőcze seq_puts(s, " wifi"); 3730b765671SBarnabás Pőcze if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg)) 37418227424SBarnabás Pőcze seq_puts(s, " camera"); 375b3ed1b7fSBarnabás Pőcze if (test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg)) 37618227424SBarnabás Pőcze seq_puts(s, " touchpad"); 37718227424SBarnabás Pőcze seq_puts(s, "\n"); 37818227424SBarnabás Pőcze 37918227424SBarnabás Pőcze seq_puts(s, "Graphics: "); 38018227424SBarnabás Pőcze switch (priv->cfg & 0x700) { 381773e3206SIke Panhc case 0x100: 38218227424SBarnabás Pőcze seq_puts(s, "Intel"); 383773e3206SIke Panhc break; 384773e3206SIke Panhc case 0x200: 38518227424SBarnabás Pőcze seq_puts(s, "ATI"); 386773e3206SIke Panhc break; 387773e3206SIke Panhc case 0x300: 38818227424SBarnabás Pőcze seq_puts(s, "Nvidia"); 389773e3206SIke Panhc break; 390773e3206SIke Panhc case 0x400: 39118227424SBarnabás Pőcze seq_puts(s, "Intel and ATI"); 392773e3206SIke Panhc break; 393773e3206SIke Panhc case 0x500: 39418227424SBarnabás Pőcze seq_puts(s, "Intel and Nvidia"); 395773e3206SIke Panhc break; 396773e3206SIke Panhc } 39718227424SBarnabás Pőcze seq_puts(s, "\n"); 398e1a39a44SBarnabás Pőcze 399773e3206SIke Panhc return 0; 400773e3206SIke Panhc } 401334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_cfg); 402773e3206SIke Panhc 40317f1bf38SGreg Kroah-Hartman static void ideapad_debugfs_init(struct ideapad_private *priv) 404773e3206SIke Panhc { 40517f1bf38SGreg Kroah-Hartman struct dentry *dir; 406773e3206SIke Panhc 40717f1bf38SGreg Kroah-Hartman dir = debugfs_create_dir("ideapad", NULL); 40817f1bf38SGreg Kroah-Hartman priv->debug = dir; 409773e3206SIke Panhc 41065c7713aSBarnabás Pőcze debugfs_create_file("cfg", 0444, dir, priv, &debugfs_cfg_fops); 41165c7713aSBarnabás Pőcze debugfs_create_file("status", 0444, dir, priv, &debugfs_status_fops); 412773e3206SIke Panhc } 413773e3206SIke Panhc 414773e3206SIke Panhc static void ideapad_debugfs_exit(struct ideapad_private *priv) 415773e3206SIke Panhc { 416773e3206SIke Panhc debugfs_remove_recursive(priv->debug); 417773e3206SIke Panhc priv->debug = NULL; 418773e3206SIke Panhc } 419773e3206SIke Panhc 420773e3206SIke Panhc /* 4213371f481SIke Panhc * sysfs 422a4b5a279SIke Panhc */ 42365c7713aSBarnabás Pőcze static ssize_t camera_power_show(struct device *dev, 42457ac3b05SIke Panhc struct device_attribute *attr, 42557ac3b05SIke Panhc char *buf) 42657ac3b05SIke Panhc { 427331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 42865c7713aSBarnabás Pőcze unsigned long result; 429c81f2410SBarnabás Pőcze int err; 43057ac3b05SIke Panhc 431c81f2410SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result); 432c81f2410SBarnabás Pőcze if (err) 433c81f2410SBarnabás Pőcze return err; 43465c7713aSBarnabás Pőcze 43500641c08SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!result); 43657ac3b05SIke Panhc } 43757ac3b05SIke Panhc 43865c7713aSBarnabás Pőcze static ssize_t camera_power_store(struct device *dev, 43957ac3b05SIke Panhc struct device_attribute *attr, 44057ac3b05SIke Panhc const char *buf, size_t count) 44157ac3b05SIke Panhc { 442331e0ea2SZhang Rui struct ideapad_private *priv = dev_get_drvdata(dev); 44300641c08SBarnabás Pőcze bool state; 444c81f2410SBarnabás Pőcze int err; 4450c7bbeb9SMaxim Mikityanskiy 44665c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 447c81f2410SBarnabás Pőcze if (err) 448c81f2410SBarnabás Pőcze return err; 4490c7bbeb9SMaxim Mikityanskiy 45065c7713aSBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); 45165c7713aSBarnabás Pőcze if (err) 45265c7713aSBarnabás Pőcze return err; 4530c7bbeb9SMaxim Mikityanskiy 4540c7bbeb9SMaxim Mikityanskiy return count; 4550c7bbeb9SMaxim Mikityanskiy } 4560c7bbeb9SMaxim Mikityanskiy 45765c7713aSBarnabás Pőcze static DEVICE_ATTR_RW(camera_power); 45836ac0d43SRitesh Raj Sarraf 459ade50296SHao Wei Tee static ssize_t conservation_mode_show(struct device *dev, 460ade50296SHao Wei Tee struct device_attribute *attr, 461ade50296SHao Wei Tee char *buf) 462ade50296SHao Wei Tee { 463ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 464ade50296SHao Wei Tee unsigned long result; 465c81f2410SBarnabás Pőcze int err; 466ade50296SHao Wei Tee 467ff36b0d9SBarnabás Pőcze err = eval_gbmd(priv->adev->handle, &result); 468c81f2410SBarnabás Pőcze if (err) 469c81f2410SBarnabás Pőcze return err; 47065c7713aSBarnabás Pőcze 4710b765671SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); 472ade50296SHao Wei Tee } 473ade50296SHao Wei Tee 474ade50296SHao Wei Tee static ssize_t conservation_mode_store(struct device *dev, 475ade50296SHao Wei Tee struct device_attribute *attr, 476ade50296SHao Wei Tee const char *buf, size_t count) 477ade50296SHao Wei Tee { 478ade50296SHao Wei Tee struct ideapad_private *priv = dev_get_drvdata(dev); 479ade50296SHao Wei Tee bool state; 48065c7713aSBarnabás Pőcze int err; 481ade50296SHao Wei Tee 48265c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 48365c7713aSBarnabás Pőcze if (err) 48465c7713aSBarnabás Pőcze return err; 485ade50296SHao Wei Tee 486b09aaa3fSBarnabás Pőcze err = exec_sbmc(priv->adev->handle, state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF); 48765c7713aSBarnabás Pőcze if (err) 48865c7713aSBarnabás Pőcze return err; 48965c7713aSBarnabás Pőcze 490ade50296SHao Wei Tee return count; 491ade50296SHao Wei Tee } 492ade50296SHao Wei Tee 493ade50296SHao Wei Tee static DEVICE_ATTR_RW(conservation_mode); 494ade50296SHao Wei Tee 49565c7713aSBarnabás Pőcze static ssize_t fan_mode_show(struct device *dev, 49665c7713aSBarnabás Pőcze struct device_attribute *attr, 49765c7713aSBarnabás Pőcze char *buf) 49865c7713aSBarnabás Pőcze { 49965c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 50065c7713aSBarnabás Pőcze unsigned long result; 50165c7713aSBarnabás Pőcze int err; 50265c7713aSBarnabás Pőcze 50365c7713aSBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result); 50465c7713aSBarnabás Pőcze if (err) 50565c7713aSBarnabás Pőcze return err; 50665c7713aSBarnabás Pőcze 50765c7713aSBarnabás Pőcze return sysfs_emit(buf, "%lu\n", result); 50865c7713aSBarnabás Pőcze } 50965c7713aSBarnabás Pőcze 51065c7713aSBarnabás Pőcze static ssize_t fan_mode_store(struct device *dev, 51165c7713aSBarnabás Pőcze struct device_attribute *attr, 51265c7713aSBarnabás Pőcze const char *buf, size_t count) 51365c7713aSBarnabás Pőcze { 51465c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 51565c7713aSBarnabás Pőcze unsigned int state; 51665c7713aSBarnabás Pőcze int err; 51765c7713aSBarnabás Pőcze 51865c7713aSBarnabás Pőcze err = kstrtouint(buf, 0, &state); 51965c7713aSBarnabás Pőcze if (err) 52065c7713aSBarnabás Pőcze return err; 52165c7713aSBarnabás Pőcze 52265c7713aSBarnabás Pőcze if (state > 4 || state == 3) 52365c7713aSBarnabás Pőcze return -EINVAL; 52465c7713aSBarnabás Pőcze 52565c7713aSBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); 52665c7713aSBarnabás Pőcze if (err) 52765c7713aSBarnabás Pőcze return err; 52865c7713aSBarnabás Pőcze 52965c7713aSBarnabás Pőcze return count; 53065c7713aSBarnabás Pőcze } 53165c7713aSBarnabás Pőcze 53265c7713aSBarnabás Pőcze static DEVICE_ATTR_RW(fan_mode); 53365c7713aSBarnabás Pőcze 53440760717SOleg Keri static ssize_t fn_lock_show(struct device *dev, 53540760717SOleg Keri struct device_attribute *attr, 53640760717SOleg Keri char *buf) 53740760717SOleg Keri { 53840760717SOleg Keri struct ideapad_private *priv = dev_get_drvdata(dev); 539ff36b0d9SBarnabás Pőcze unsigned long hals; 54065c7713aSBarnabás Pőcze int err; 54140760717SOleg Keri 54265c7713aSBarnabás Pőcze err = eval_hals(priv->adev->handle, &hals); 54365c7713aSBarnabás Pőcze if (err) 54465c7713aSBarnabás Pőcze return err; 54540760717SOleg Keri 546ff36b0d9SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); 54740760717SOleg Keri } 54840760717SOleg Keri 54940760717SOleg Keri static ssize_t fn_lock_store(struct device *dev, 55040760717SOleg Keri struct device_attribute *attr, 55140760717SOleg Keri const char *buf, size_t count) 55240760717SOleg Keri { 55340760717SOleg Keri struct ideapad_private *priv = dev_get_drvdata(dev); 55440760717SOleg Keri bool state; 55565c7713aSBarnabás Pőcze int err; 55640760717SOleg Keri 55765c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 55865c7713aSBarnabás Pőcze if (err) 55965c7713aSBarnabás Pőcze return err; 56040760717SOleg Keri 56165c7713aSBarnabás Pőcze err = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); 56265c7713aSBarnabás Pőcze if (err) 56365c7713aSBarnabás Pőcze return err; 56465c7713aSBarnabás Pőcze 56540760717SOleg Keri return count; 56640760717SOleg Keri } 56740760717SOleg Keri 56840760717SOleg Keri static DEVICE_ATTR_RW(fn_lock); 56940760717SOleg Keri 57065c7713aSBarnabás Pőcze static ssize_t touchpad_show(struct device *dev, 57165c7713aSBarnabás Pőcze struct device_attribute *attr, 57265c7713aSBarnabás Pőcze char *buf) 57365c7713aSBarnabás Pőcze { 57465c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 57565c7713aSBarnabás Pőcze unsigned long result; 57665c7713aSBarnabás Pőcze int err; 57765c7713aSBarnabás Pőcze 57865c7713aSBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result); 57965c7713aSBarnabás Pőcze if (err) 58065c7713aSBarnabás Pőcze return err; 58165c7713aSBarnabás Pőcze 58265c7713aSBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!result); 58365c7713aSBarnabás Pőcze } 58465c7713aSBarnabás Pőcze 58565c7713aSBarnabás Pőcze static ssize_t touchpad_store(struct device *dev, 58665c7713aSBarnabás Pőcze struct device_attribute *attr, 58765c7713aSBarnabás Pőcze const char *buf, size_t count) 58865c7713aSBarnabás Pőcze { 58965c7713aSBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 59065c7713aSBarnabás Pőcze bool state; 59165c7713aSBarnabás Pőcze int err; 59265c7713aSBarnabás Pőcze 59365c7713aSBarnabás Pőcze err = kstrtobool(buf, &state); 59465c7713aSBarnabás Pőcze if (err) 59565c7713aSBarnabás Pőcze return err; 59665c7713aSBarnabás Pőcze 59765c7713aSBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); 59865c7713aSBarnabás Pőcze if (err) 59965c7713aSBarnabás Pőcze return err; 60065c7713aSBarnabás Pőcze 60165c7713aSBarnabás Pőcze return count; 60265c7713aSBarnabás Pőcze } 60365c7713aSBarnabás Pőcze 60465c7713aSBarnabás Pőcze static DEVICE_ATTR_RW(touchpad); 60540760717SOleg Keri 6066b49dea4SBarnabás Pőcze static ssize_t usb_charging_show(struct device *dev, 6076b49dea4SBarnabás Pőcze struct device_attribute *attr, 6086b49dea4SBarnabás Pőcze char *buf) 6096b49dea4SBarnabás Pőcze { 6106b49dea4SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 6116b49dea4SBarnabás Pőcze unsigned long hals; 6126b49dea4SBarnabás Pőcze int err; 6136b49dea4SBarnabás Pőcze 6146b49dea4SBarnabás Pőcze err = eval_hals(priv->adev->handle, &hals); 6156b49dea4SBarnabás Pőcze if (err) 6166b49dea4SBarnabás Pőcze return err; 6176b49dea4SBarnabás Pőcze 6186b49dea4SBarnabás Pőcze return sysfs_emit(buf, "%d\n", !!test_bit(HALS_USB_CHARGING_STATE_BIT, &hals)); 6196b49dea4SBarnabás Pőcze } 6206b49dea4SBarnabás Pőcze 6216b49dea4SBarnabás Pőcze static ssize_t usb_charging_store(struct device *dev, 6226b49dea4SBarnabás Pőcze struct device_attribute *attr, 6236b49dea4SBarnabás Pőcze const char *buf, size_t count) 6246b49dea4SBarnabás Pőcze { 6256b49dea4SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 6266b49dea4SBarnabás Pőcze bool state; 6276b49dea4SBarnabás Pőcze int err; 6286b49dea4SBarnabás Pőcze 6296b49dea4SBarnabás Pőcze err = kstrtobool(buf, &state); 6306b49dea4SBarnabás Pőcze if (err) 6316b49dea4SBarnabás Pőcze return err; 6326b49dea4SBarnabás Pőcze 6336b49dea4SBarnabás Pőcze err = exec_sals(priv->adev->handle, state ? SALS_USB_CHARGING_ON : SALS_USB_CHARGING_OFF); 6346b49dea4SBarnabás Pőcze if (err) 6356b49dea4SBarnabás Pőcze return err; 6366b49dea4SBarnabás Pőcze 6376b49dea4SBarnabás Pőcze return count; 6386b49dea4SBarnabás Pőcze } 6396b49dea4SBarnabás Pőcze 6406b49dea4SBarnabás Pőcze static DEVICE_ATTR_RW(usb_charging); 6416b49dea4SBarnabás Pőcze 6423371f481SIke Panhc static struct attribute *ideapad_attributes[] = { 6433371f481SIke Panhc &dev_attr_camera_power.attr, 644ade50296SHao Wei Tee &dev_attr_conservation_mode.attr, 64565c7713aSBarnabás Pőcze &dev_attr_fan_mode.attr, 64640760717SOleg Keri &dev_attr_fn_lock.attr, 64765c7713aSBarnabás Pőcze &dev_attr_touchpad.attr, 6486b49dea4SBarnabás Pőcze &dev_attr_usb_charging.attr, 6493371f481SIke Panhc NULL 6503371f481SIke Panhc }; 6513371f481SIke Panhc 652587a1f16SAl Viro static umode_t ideapad_is_visible(struct kobject *kobj, 653a84511f7SIke Panhc struct attribute *attr, 654a84511f7SIke Panhc int idx) 655a84511f7SIke Panhc { 656708086b2SBarnabás Pőcze struct device *dev = kobj_to_dev(kobj); 657a84511f7SIke Panhc struct ideapad_private *priv = dev_get_drvdata(dev); 6581c59de4aSBarnabás Pőcze bool supported = true; 659a84511f7SIke Panhc 660a84511f7SIke Panhc if (attr == &dev_attr_camera_power.attr) 6610b765671SBarnabás Pőcze supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg); 6621c59de4aSBarnabás Pőcze else if (attr == &dev_attr_conservation_mode.attr) 6631c59de4aSBarnabás Pőcze supported = priv->features.conservation_mode; 6641c59de4aSBarnabás Pőcze else if (attr == &dev_attr_fan_mode.attr) 6651c59de4aSBarnabás Pőcze supported = priv->features.fan_mode; 6661c59de4aSBarnabás Pőcze else if (attr == &dev_attr_fn_lock.attr) 6671c59de4aSBarnabás Pőcze supported = priv->features.fn_lock; 6681c59de4aSBarnabás Pőcze else if (attr == &dev_attr_touchpad.attr) 669b3ed1b7fSBarnabás Pőcze supported = priv->features.touchpad_ctrl_via_ec && 670b3ed1b7fSBarnabás Pőcze test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg); 6716b49dea4SBarnabás Pőcze else if (attr == &dev_attr_usb_charging.attr) 6726b49dea4SBarnabás Pőcze supported = priv->features.usb_charging; 673a84511f7SIke Panhc 674a84511f7SIke Panhc return supported ? attr->mode : 0; 675a84511f7SIke Panhc } 676a84511f7SIke Panhc 67749458e83SMathias Krause static const struct attribute_group ideapad_attribute_group = { 678a84511f7SIke Panhc .is_visible = ideapad_is_visible, 6793371f481SIke Panhc .attrs = ideapad_attributes 6803371f481SIke Panhc }; 6813371f481SIke Panhc 682a4b5a279SIke Panhc /* 683eabe5339SJiaxun Yang * DYTC Platform profile 684eabe5339SJiaxun Yang */ 685eabe5339SJiaxun Yang #define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ 686eabe5339SJiaxun Yang #define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ 687eabe5339SJiaxun Yang #define DYTC_CMD_GET 2 /* To get current IC function and mode */ 688eabe5339SJiaxun Yang #define DYTC_CMD_RESET 0x1ff /* To reset back to default */ 689eabe5339SJiaxun Yang 690eabe5339SJiaxun Yang #define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ 691eabe5339SJiaxun Yang #define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ 692eabe5339SJiaxun Yang #define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ 693eabe5339SJiaxun Yang 694eabe5339SJiaxun Yang #define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ 695eabe5339SJiaxun Yang #define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ 696eabe5339SJiaxun Yang 697eabe5339SJiaxun Yang #define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ 698eabe5339SJiaxun Yang #define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ 699eabe5339SJiaxun Yang #define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ 700eabe5339SJiaxun Yang 701eabe5339SJiaxun Yang #define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ 702eabe5339SJiaxun Yang #define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ 703eabe5339SJiaxun Yang #define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ 704eabe5339SJiaxun Yang 705eabe5339SJiaxun Yang #define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ 706eabe5339SJiaxun Yang #define DYTC_MODE_LOW_POWER 3 /* Low power mode aka quiet */ 707eabe5339SJiaxun Yang #define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ 708eabe5339SJiaxun Yang 709eabe5339SJiaxun Yang #define DYTC_SET_COMMAND(function, mode, on) \ 710eabe5339SJiaxun Yang (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ 711eabe5339SJiaxun Yang (mode) << DYTC_SET_MODE_BIT | \ 712eabe5339SJiaxun Yang (on) << DYTC_SET_VALID_BIT) 713eabe5339SJiaxun Yang 714eabe5339SJiaxun Yang #define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) 715eabe5339SJiaxun Yang 716eabe5339SJiaxun Yang #define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) 717eabe5339SJiaxun Yang 718eabe5339SJiaxun Yang static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) 719eabe5339SJiaxun Yang { 720eabe5339SJiaxun Yang switch (dytcmode) { 721eabe5339SJiaxun Yang case DYTC_MODE_LOW_POWER: 722eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_LOW_POWER; 723eabe5339SJiaxun Yang break; 724eabe5339SJiaxun Yang case DYTC_MODE_BALANCE: 725eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_BALANCED; 726eabe5339SJiaxun Yang break; 727eabe5339SJiaxun Yang case DYTC_MODE_PERFORM: 728eabe5339SJiaxun Yang *profile = PLATFORM_PROFILE_PERFORMANCE; 729eabe5339SJiaxun Yang break; 730eabe5339SJiaxun Yang default: /* Unknown mode */ 731eabe5339SJiaxun Yang return -EINVAL; 732eabe5339SJiaxun Yang } 73365c7713aSBarnabás Pőcze 734eabe5339SJiaxun Yang return 0; 735eabe5339SJiaxun Yang } 736eabe5339SJiaxun Yang 737eabe5339SJiaxun Yang static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) 738eabe5339SJiaxun Yang { 739eabe5339SJiaxun Yang switch (profile) { 740eabe5339SJiaxun Yang case PLATFORM_PROFILE_LOW_POWER: 741eabe5339SJiaxun Yang *perfmode = DYTC_MODE_LOW_POWER; 742eabe5339SJiaxun Yang break; 743eabe5339SJiaxun Yang case PLATFORM_PROFILE_BALANCED: 744eabe5339SJiaxun Yang *perfmode = DYTC_MODE_BALANCE; 745eabe5339SJiaxun Yang break; 746eabe5339SJiaxun Yang case PLATFORM_PROFILE_PERFORMANCE: 747eabe5339SJiaxun Yang *perfmode = DYTC_MODE_PERFORM; 748eabe5339SJiaxun Yang break; 749eabe5339SJiaxun Yang default: /* Unknown profile */ 750eabe5339SJiaxun Yang return -EOPNOTSUPP; 751eabe5339SJiaxun Yang } 75265c7713aSBarnabás Pőcze 753eabe5339SJiaxun Yang return 0; 754eabe5339SJiaxun Yang } 755eabe5339SJiaxun Yang 756eabe5339SJiaxun Yang /* 757eabe5339SJiaxun Yang * dytc_profile_get: Function to register with platform_profile 758eabe5339SJiaxun Yang * handler. Returns current platform profile. 759eabe5339SJiaxun Yang */ 76065c7713aSBarnabás Pőcze static int dytc_profile_get(struct platform_profile_handler *pprof, 761eabe5339SJiaxun Yang enum platform_profile_option *profile) 762eabe5339SJiaxun Yang { 76365c7713aSBarnabás Pőcze struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 764eabe5339SJiaxun Yang 765eabe5339SJiaxun Yang *profile = dytc->current_profile; 766eabe5339SJiaxun Yang return 0; 767eabe5339SJiaxun Yang } 768eabe5339SJiaxun Yang 769eabe5339SJiaxun Yang /* 770eabe5339SJiaxun Yang * Helper function - check if we are in CQL mode and if we are 771eabe5339SJiaxun Yang * - disable CQL, 772eabe5339SJiaxun Yang * - run the command 773eabe5339SJiaxun Yang * - enable CQL 774eabe5339SJiaxun Yang * If not in CQL mode, just run the command 775eabe5339SJiaxun Yang */ 77665c7713aSBarnabás Pőcze static int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, 77765c7713aSBarnabás Pőcze unsigned long *output) 778eabe5339SJiaxun Yang { 779ff36b0d9SBarnabás Pőcze int err, cmd_err, cur_funcmode; 780eabe5339SJiaxun Yang 781eabe5339SJiaxun Yang /* Determine if we are in CQL mode. This alters the commands we do */ 782ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_GET, output); 783eabe5339SJiaxun Yang if (err) 784eabe5339SJiaxun Yang return err; 785eabe5339SJiaxun Yang 786eabe5339SJiaxun Yang cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; 787eabe5339SJiaxun Yang /* Check if we're OK to return immediately */ 788ff36b0d9SBarnabás Pőcze if (cmd == DYTC_CMD_GET && cur_funcmode != DYTC_FUNCTION_CQL) 789eabe5339SJiaxun Yang return 0; 790eabe5339SJiaxun Yang 791eabe5339SJiaxun Yang if (cur_funcmode == DYTC_FUNCTION_CQL) { 792ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_DISABLE_CQL, NULL); 793eabe5339SJiaxun Yang if (err) 794eabe5339SJiaxun Yang return err; 795eabe5339SJiaxun Yang } 796eabe5339SJiaxun Yang 797ff36b0d9SBarnabás Pőcze cmd_err = eval_dytc(priv->adev->handle, cmd, output); 798eabe5339SJiaxun Yang /* Check return condition after we've restored CQL state */ 799eabe5339SJiaxun Yang 800eabe5339SJiaxun Yang if (cur_funcmode == DYTC_FUNCTION_CQL) { 801ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_ENABLE_CQL, NULL); 802eabe5339SJiaxun Yang if (err) 803eabe5339SJiaxun Yang return err; 804eabe5339SJiaxun Yang } 805eabe5339SJiaxun Yang 806eabe5339SJiaxun Yang return cmd_err; 807eabe5339SJiaxun Yang } 808eabe5339SJiaxun Yang 809eabe5339SJiaxun Yang /* 810eabe5339SJiaxun Yang * dytc_profile_set: Function to register with platform_profile 811eabe5339SJiaxun Yang * handler. Sets current platform profile. 812eabe5339SJiaxun Yang */ 81365c7713aSBarnabás Pőcze static int dytc_profile_set(struct platform_profile_handler *pprof, 814eabe5339SJiaxun Yang enum platform_profile_option profile) 815eabe5339SJiaxun Yang { 81665c7713aSBarnabás Pőcze struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); 81765c7713aSBarnabás Pőcze struct ideapad_private *priv = dytc->priv; 818ff67dbd5SQiu Wenbo unsigned long output; 819eabe5339SJiaxun Yang int err; 820eabe5339SJiaxun Yang 821eabe5339SJiaxun Yang err = mutex_lock_interruptible(&dytc->mutex); 822eabe5339SJiaxun Yang if (err) 823eabe5339SJiaxun Yang return err; 824eabe5339SJiaxun Yang 825eabe5339SJiaxun Yang if (profile == PLATFORM_PROFILE_BALANCED) { 826eabe5339SJiaxun Yang /* To get back to balanced mode we just issue a reset command */ 827ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_RESET, NULL); 828eabe5339SJiaxun Yang if (err) 829eabe5339SJiaxun Yang goto unlock; 830eabe5339SJiaxun Yang } else { 831eabe5339SJiaxun Yang int perfmode; 832eabe5339SJiaxun Yang 833eabe5339SJiaxun Yang err = convert_profile_to_dytc(profile, &perfmode); 834eabe5339SJiaxun Yang if (err) 835eabe5339SJiaxun Yang goto unlock; 836eabe5339SJiaxun Yang 837eabe5339SJiaxun Yang /* Determine if we are in CQL mode. This alters the commands we do */ 83865c7713aSBarnabás Pőcze err = dytc_cql_command(priv, DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), 839ff67dbd5SQiu Wenbo &output); 840eabe5339SJiaxun Yang if (err) 841eabe5339SJiaxun Yang goto unlock; 842eabe5339SJiaxun Yang } 84365c7713aSBarnabás Pőcze 844eabe5339SJiaxun Yang /* Success - update current profile */ 845eabe5339SJiaxun Yang dytc->current_profile = profile; 84665c7713aSBarnabás Pőcze 847eabe5339SJiaxun Yang unlock: 848eabe5339SJiaxun Yang mutex_unlock(&dytc->mutex); 84965c7713aSBarnabás Pőcze 850eabe5339SJiaxun Yang return err; 851eabe5339SJiaxun Yang } 852eabe5339SJiaxun Yang 853eabe5339SJiaxun Yang static void dytc_profile_refresh(struct ideapad_private *priv) 854eabe5339SJiaxun Yang { 855eabe5339SJiaxun Yang enum platform_profile_option profile; 856ff36b0d9SBarnabás Pőcze unsigned long output; 857ff36b0d9SBarnabás Pőcze int err, perfmode; 858eabe5339SJiaxun Yang 859eabe5339SJiaxun Yang mutex_lock(&priv->dytc->mutex); 860eabe5339SJiaxun Yang err = dytc_cql_command(priv, DYTC_CMD_GET, &output); 861eabe5339SJiaxun Yang mutex_unlock(&priv->dytc->mutex); 862eabe5339SJiaxun Yang if (err) 863eabe5339SJiaxun Yang return; 864eabe5339SJiaxun Yang 865eabe5339SJiaxun Yang perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; 86665c7713aSBarnabás Pőcze 86765c7713aSBarnabás Pőcze if (convert_dytc_to_profile(perfmode, &profile)) 86865c7713aSBarnabás Pőcze return; 86965c7713aSBarnabás Pőcze 870eabe5339SJiaxun Yang if (profile != priv->dytc->current_profile) { 871eabe5339SJiaxun Yang priv->dytc->current_profile = profile; 872eabe5339SJiaxun Yang platform_profile_notify(); 873eabe5339SJiaxun Yang } 874eabe5339SJiaxun Yang } 875eabe5339SJiaxun Yang 876599482c5SKelly Anderson static const struct dmi_system_id ideapad_dytc_v4_allow_table[] = { 877599482c5SKelly Anderson { 878599482c5SKelly Anderson /* Ideapad 5 Pro 16ACH6 */ 879599482c5SKelly Anderson .matches = { 880599482c5SKelly Anderson DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 881599482c5SKelly Anderson DMI_MATCH(DMI_PRODUCT_NAME, "82L5") 882599482c5SKelly Anderson } 883599482c5SKelly Anderson }, 8848853e8ceSHans de Goede { 8858853e8ceSHans de Goede /* Ideapad 5 15ITL05 */ 8868853e8ceSHans de Goede .matches = { 8878853e8ceSHans de Goede DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 8888853e8ceSHans de Goede DMI_MATCH(DMI_PRODUCT_VERSION, "IdeaPad 5 15ITL05") 8898853e8ceSHans de Goede } 8908853e8ceSHans de Goede }, 891599482c5SKelly Anderson {} 892599482c5SKelly Anderson }; 893599482c5SKelly Anderson 894eabe5339SJiaxun Yang static int ideapad_dytc_profile_init(struct ideapad_private *priv) 895eabe5339SJiaxun Yang { 896ff36b0d9SBarnabás Pőcze int err, dytc_version; 897ff36b0d9SBarnabás Pőcze unsigned long output; 898eabe5339SJiaxun Yang 8991c59de4aSBarnabás Pőcze if (!priv->features.dytc) 9001c59de4aSBarnabás Pőcze return -ENODEV; 9011c59de4aSBarnabás Pőcze 902ff36b0d9SBarnabás Pőcze err = eval_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); 903eabe5339SJiaxun Yang /* For all other errors we can flag the failure */ 904eabe5339SJiaxun Yang if (err) 905eabe5339SJiaxun Yang return err; 906eabe5339SJiaxun Yang 907eabe5339SJiaxun Yang /* Check DYTC is enabled and supports mode setting */ 908599482c5SKelly Anderson if (!test_bit(DYTC_QUERY_ENABLE_BIT, &output)) { 909599482c5SKelly Anderson dev_info(&priv->platform_device->dev, "DYTC_QUERY_ENABLE_BIT returned false\n"); 910eabe5339SJiaxun Yang return -ENODEV; 911599482c5SKelly Anderson } 912eabe5339SJiaxun Yang 913eabe5339SJiaxun Yang dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; 914599482c5SKelly Anderson 915a27a1e35SHans de Goede if (dytc_version < 4) { 916a27a1e35SHans de Goede dev_info(&priv->platform_device->dev, "DYTC_VERSION < 4 is not supported\n"); 917eabe5339SJiaxun Yang return -ENODEV; 918599482c5SKelly Anderson } 919a27a1e35SHans de Goede 920a27a1e35SHans de Goede if (dytc_version < 5 && 921a27a1e35SHans de Goede !(allow_v4_dytc || dmi_check_system(ideapad_dytc_v4_allow_table))) { 922a27a1e35SHans de Goede dev_info(&priv->platform_device->dev, 923a27a1e35SHans de Goede "DYTC_VERSION 4 support may not work. Pass ideapad_laptop.allow_v4_dytc=Y on the kernel commandline to enable\n"); 924a27a1e35SHans de Goede return -ENODEV; 925599482c5SKelly Anderson } 926eabe5339SJiaxun Yang 92765c7713aSBarnabás Pőcze priv->dytc = kzalloc(sizeof(*priv->dytc), GFP_KERNEL); 928eabe5339SJiaxun Yang if (!priv->dytc) 929eabe5339SJiaxun Yang return -ENOMEM; 930eabe5339SJiaxun Yang 931eabe5339SJiaxun Yang mutex_init(&priv->dytc->mutex); 932eabe5339SJiaxun Yang 933eabe5339SJiaxun Yang priv->dytc->priv = priv; 934eabe5339SJiaxun Yang priv->dytc->pprof.profile_get = dytc_profile_get; 935eabe5339SJiaxun Yang priv->dytc->pprof.profile_set = dytc_profile_set; 936eabe5339SJiaxun Yang 937eabe5339SJiaxun Yang /* Setup supported modes */ 938eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices); 939eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices); 940eabe5339SJiaxun Yang set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices); 941eabe5339SJiaxun Yang 942eabe5339SJiaxun Yang /* Create platform_profile structure and register */ 943eabe5339SJiaxun Yang err = platform_profile_register(&priv->dytc->pprof); 944eabe5339SJiaxun Yang if (err) 94565c7713aSBarnabás Pőcze goto pp_reg_failed; 946eabe5339SJiaxun Yang 947eabe5339SJiaxun Yang /* Ensure initial values are correct */ 948eabe5339SJiaxun Yang dytc_profile_refresh(priv); 949eabe5339SJiaxun Yang 950eabe5339SJiaxun Yang return 0; 951eabe5339SJiaxun Yang 95265c7713aSBarnabás Pőcze pp_reg_failed: 953eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 954eabe5339SJiaxun Yang kfree(priv->dytc); 955eabe5339SJiaxun Yang priv->dytc = NULL; 95665c7713aSBarnabás Pőcze 957eabe5339SJiaxun Yang return err; 958eabe5339SJiaxun Yang } 959eabe5339SJiaxun Yang 960eabe5339SJiaxun Yang static void ideapad_dytc_profile_exit(struct ideapad_private *priv) 961eabe5339SJiaxun Yang { 962eabe5339SJiaxun Yang if (!priv->dytc) 963eabe5339SJiaxun Yang return; 964eabe5339SJiaxun Yang 965eabe5339SJiaxun Yang platform_profile_remove(); 966eabe5339SJiaxun Yang mutex_destroy(&priv->dytc->mutex); 967eabe5339SJiaxun Yang kfree(priv->dytc); 96865c7713aSBarnabás Pőcze 969eabe5339SJiaxun Yang priv->dytc = NULL; 970eabe5339SJiaxun Yang } 971eabe5339SJiaxun Yang 972eabe5339SJiaxun Yang /* 973a4b5a279SIke Panhc * Rfkill 974a4b5a279SIke Panhc */ 975c1f73658SIke Panhc struct ideapad_rfk_data { 976c1f73658SIke Panhc char *name; 977c1f73658SIke Panhc int cfgbit; 978c1f73658SIke Panhc int opcode; 979c1f73658SIke Panhc int type; 980c1f73658SIke Panhc }; 981c1f73658SIke Panhc 982b3d94d70SMathias Krause static const struct ideapad_rfk_data ideapad_rfk_data[] = { 9830b765671SBarnabás Pőcze { "ideapad_wlan", CFG_CAP_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, 9840b765671SBarnabás Pőcze { "ideapad_bluetooth", CFG_CAP_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, 9850b765671SBarnabás Pőcze { "ideapad_3g", CFG_CAP_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, 986c1f73658SIke Panhc }; 987c1f73658SIke Panhc 98857ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked) 98957ac3b05SIke Panhc { 990331e0ea2SZhang Rui struct ideapad_rfk_priv *priv = data; 9914b200b46SArnd Bergmann int opcode = ideapad_rfk_data[priv->dev].opcode; 99257ac3b05SIke Panhc 9934b200b46SArnd Bergmann return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked); 99457ac3b05SIke Panhc } 99557ac3b05SIke Panhc 9963d59dfcdSBhumika Goyal static const struct rfkill_ops ideapad_rfk_ops = { 99757ac3b05SIke Panhc .set_block = ideapad_rfk_set, 99857ac3b05SIke Panhc }; 99957ac3b05SIke Panhc 1000923de84aSIke Panhc static void ideapad_sync_rfk_state(struct ideapad_private *priv) 100157ac3b05SIke Panhc { 1002ce363c2bSHans de Goede unsigned long hw_blocked = 0; 100357ac3b05SIke Panhc int i; 100457ac3b05SIke Panhc 10051c59de4aSBarnabás Pőcze if (priv->features.hw_rfkill_switch) { 1006331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) 100757ac3b05SIke Panhc return; 100857ac3b05SIke Panhc hw_blocked = !hw_blocked; 1009ce363c2bSHans de Goede } 101057ac3b05SIke Panhc 1011c1f73658SIke Panhc for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 101257ac3b05SIke Panhc if (priv->rfk[i]) 101357ac3b05SIke Panhc rfkill_set_hw_state(priv->rfk[i], hw_blocked); 101457ac3b05SIke Panhc } 101557ac3b05SIke Panhc 101675a11f11SZhang Rui static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) 101757ac3b05SIke Panhc { 101865c7713aSBarnabás Pőcze unsigned long rf_enabled; 101965c7713aSBarnabás Pőcze int err; 102057ac3b05SIke Panhc 102165c7713aSBarnabás Pőcze if (no_bt_rfkill && ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH) { 1022bfa97b7dSIke Panhc /* Force to enable bluetooth when no_bt_rfkill=1 */ 102365c7713aSBarnabás Pőcze write_ec_cmd(priv->adev->handle, ideapad_rfk_data[dev].opcode, 1); 1024bfa97b7dSIke Panhc return 0; 1025bfa97b7dSIke Panhc } 102665c7713aSBarnabás Pőcze 1027331e0ea2SZhang Rui priv->rfk_priv[dev].dev = dev; 1028331e0ea2SZhang Rui priv->rfk_priv[dev].priv = priv; 1029bfa97b7dSIke Panhc 103075a11f11SZhang Rui priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name, 1031b5c37b79SZhang Rui &priv->platform_device->dev, 103275a11f11SZhang Rui ideapad_rfk_data[dev].type, 103375a11f11SZhang Rui &ideapad_rfk_ops, 1034331e0ea2SZhang Rui &priv->rfk_priv[dev]); 103557ac3b05SIke Panhc if (!priv->rfk[dev]) 103657ac3b05SIke Panhc return -ENOMEM; 103757ac3b05SIke Panhc 103865c7713aSBarnabás Pőcze err = read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode - 1, &rf_enabled); 103965c7713aSBarnabás Pőcze if (err) 104065c7713aSBarnabás Pőcze rf_enabled = 1; 104157ac3b05SIke Panhc 104265c7713aSBarnabás Pőcze rfkill_init_sw_state(priv->rfk[dev], !rf_enabled); 104365c7713aSBarnabás Pőcze 104465c7713aSBarnabás Pőcze err = rfkill_register(priv->rfk[dev]); 104565c7713aSBarnabás Pőcze if (err) 104657ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 104765c7713aSBarnabás Pőcze 104865c7713aSBarnabás Pőcze return err; 104957ac3b05SIke Panhc } 105057ac3b05SIke Panhc 105175a11f11SZhang Rui static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) 105257ac3b05SIke Panhc { 105357ac3b05SIke Panhc if (!priv->rfk[dev]) 105457ac3b05SIke Panhc return; 105557ac3b05SIke Panhc 105657ac3b05SIke Panhc rfkill_unregister(priv->rfk[dev]); 105757ac3b05SIke Panhc rfkill_destroy(priv->rfk[dev]); 105857ac3b05SIke Panhc } 105957ac3b05SIke Panhc 106098ee6919SIke Panhc /* 106198ee6919SIke Panhc * Platform device 106298ee6919SIke Panhc */ 1063b5c37b79SZhang Rui static int ideapad_sysfs_init(struct ideapad_private *priv) 106498ee6919SIke Panhc { 10658782d8d7SBarnabás Pőcze return device_add_group(&priv->platform_device->dev, 1066c9f718d0SIke Panhc &ideapad_attribute_group); 106798ee6919SIke Panhc } 106898ee6919SIke Panhc 1069b5c37b79SZhang Rui static void ideapad_sysfs_exit(struct ideapad_private *priv) 107098ee6919SIke Panhc { 10718782d8d7SBarnabás Pőcze device_remove_group(&priv->platform_device->dev, 1072c9f718d0SIke Panhc &ideapad_attribute_group); 107398ee6919SIke Panhc } 107498ee6919SIke Panhc 1075f63409aeSIke Panhc /* 1076f63409aeSIke Panhc * input device 1077f63409aeSIke Panhc */ 1078f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = { 1079f43d9ec0SIke Panhc { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, 1080296f9fe0SMaxim Mikityanskiy { KE_KEY, 7, { KEY_CAMERA } }, 108148f67d62SAlex Hung { KE_KEY, 8, { KEY_MICMUTE } }, 1082296f9fe0SMaxim Mikityanskiy { KE_KEY, 11, { KEY_F16 } }, 1083f43d9ec0SIke Panhc { KE_KEY, 13, { KEY_WLAN } }, 1084f43d9ec0SIke Panhc { KE_KEY, 16, { KEY_PROG1 } }, 1085f43d9ec0SIke Panhc { KE_KEY, 17, { KEY_PROG2 } }, 1086296f9fe0SMaxim Mikityanskiy { KE_KEY, 64, { KEY_PROG3 } }, 1087296f9fe0SMaxim Mikityanskiy { KE_KEY, 65, { KEY_PROG4 } }, 108807a4a4fcSMaxim Mikityanskiy { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, 108907a4a4fcSMaxim Mikityanskiy { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, 109074caab99SArnd Bergmann { KE_KEY, 128, { KEY_ESC } }, 109165c7713aSBarnabás Pőcze { KE_END }, 1092f63409aeSIke Panhc }; 1093f63409aeSIke Panhc 1094b859f159SGreg Kroah-Hartman static int ideapad_input_init(struct ideapad_private *priv) 1095f63409aeSIke Panhc { 1096f63409aeSIke Panhc struct input_dev *inputdev; 109765c7713aSBarnabás Pőcze int err; 1098f63409aeSIke Panhc 1099f63409aeSIke Panhc inputdev = input_allocate_device(); 1100b222cca6SJoe Perches if (!inputdev) 1101f63409aeSIke Panhc return -ENOMEM; 1102f63409aeSIke Panhc 1103f63409aeSIke Panhc inputdev->name = "Ideapad extra buttons"; 1104f63409aeSIke Panhc inputdev->phys = "ideapad/input0"; 1105f63409aeSIke Panhc inputdev->id.bustype = BUS_HOST; 11068693ae84SIke Panhc inputdev->dev.parent = &priv->platform_device->dev; 1107f63409aeSIke Panhc 110865c7713aSBarnabás Pőcze err = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); 110965c7713aSBarnabás Pőcze if (err) { 1110654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 111165c7713aSBarnabás Pőcze "Could not set up input device keymap: %d\n", err); 1112f63409aeSIke Panhc goto err_free_dev; 1113f63409aeSIke Panhc } 1114f63409aeSIke Panhc 111565c7713aSBarnabás Pőcze err = input_register_device(inputdev); 111665c7713aSBarnabás Pőcze if (err) { 1117654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 111865c7713aSBarnabás Pőcze "Could not register input device: %d\n", err); 1119c973d4b5SMichał Kępień goto err_free_dev; 1120f63409aeSIke Panhc } 1121f63409aeSIke Panhc 11228693ae84SIke Panhc priv->inputdev = inputdev; 112365c7713aSBarnabás Pőcze 1124f63409aeSIke Panhc return 0; 1125f63409aeSIke Panhc 1126f63409aeSIke Panhc err_free_dev: 1127f63409aeSIke Panhc input_free_device(inputdev); 112865c7713aSBarnabás Pőcze 112965c7713aSBarnabás Pőcze return err; 1130f63409aeSIke Panhc } 1131f63409aeSIke Panhc 11327451a55aSAxel Lin static void ideapad_input_exit(struct ideapad_private *priv) 1133f63409aeSIke Panhc { 11348693ae84SIke Panhc input_unregister_device(priv->inputdev); 11358693ae84SIke Panhc priv->inputdev = NULL; 1136f63409aeSIke Panhc } 1137f63409aeSIke Panhc 11388693ae84SIke Panhc static void ideapad_input_report(struct ideapad_private *priv, 11398693ae84SIke Panhc unsigned long scancode) 1140f63409aeSIke Panhc { 11418693ae84SIke Panhc sparse_keymap_report_event(priv->inputdev, scancode, 1, true); 1142f63409aeSIke Panhc } 1143f63409aeSIke Panhc 1144f43d9ec0SIke Panhc static void ideapad_input_novokey(struct ideapad_private *priv) 1145f43d9ec0SIke Panhc { 1146f43d9ec0SIke Panhc unsigned long long_pressed; 1147f43d9ec0SIke Panhc 1148331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) 1149f43d9ec0SIke Panhc return; 115065c7713aSBarnabás Pőcze 1151f43d9ec0SIke Panhc if (long_pressed) 1152f43d9ec0SIke Panhc ideapad_input_report(priv, 17); 1153f43d9ec0SIke Panhc else 1154f43d9ec0SIke Panhc ideapad_input_report(priv, 16); 1155f43d9ec0SIke Panhc } 1156f43d9ec0SIke Panhc 1157296f9fe0SMaxim Mikityanskiy static void ideapad_check_special_buttons(struct ideapad_private *priv) 1158296f9fe0SMaxim Mikityanskiy { 1159296f9fe0SMaxim Mikityanskiy unsigned long bit, value; 1160296f9fe0SMaxim Mikityanskiy 11617be193e3SBarnabás Pőcze if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) 11627be193e3SBarnabás Pőcze return; 1163296f9fe0SMaxim Mikityanskiy 11640c4915b6SBarnabás Pőcze for_each_set_bit (bit, &value, 16) { 1165296f9fe0SMaxim Mikityanskiy switch (bit) { 1166a1ec56edSMaxim Mikityanskiy case 6: /* Z570 */ 116765c7713aSBarnabás Pőcze case 0: /* Z580 */ 1168296f9fe0SMaxim Mikityanskiy /* Thermal Management button */ 1169296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 65); 1170296f9fe0SMaxim Mikityanskiy break; 1171296f9fe0SMaxim Mikityanskiy case 1: 1172296f9fe0SMaxim Mikityanskiy /* OneKey Theater button */ 1173296f9fe0SMaxim Mikityanskiy ideapad_input_report(priv, 64); 1174296f9fe0SMaxim Mikityanskiy break; 1175a1ec56edSMaxim Mikityanskiy default: 1176654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1177654324c4SBarnabás Pőcze "Unknown special button: %lu\n", bit); 1178a1ec56edSMaxim Mikityanskiy break; 1179296f9fe0SMaxim Mikityanskiy } 1180296f9fe0SMaxim Mikityanskiy } 1181296f9fe0SMaxim Mikityanskiy } 1182296f9fe0SMaxim Mikityanskiy 1183a4b5a279SIke Panhc /* 1184a4ecbb8aSIke Panhc * backlight 1185a4ecbb8aSIke Panhc */ 1186a4ecbb8aSIke Panhc static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) 1187a4ecbb8aSIke Panhc { 1188331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 1189a4ecbb8aSIke Panhc unsigned long now; 11907be193e3SBarnabás Pőcze int err; 1191a4ecbb8aSIke Panhc 11927be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 11937be193e3SBarnabás Pőcze if (err) 11947be193e3SBarnabás Pőcze return err; 119565c7713aSBarnabás Pőcze 1196a4ecbb8aSIke Panhc return now; 1197a4ecbb8aSIke Panhc } 1198a4ecbb8aSIke Panhc 1199a4ecbb8aSIke Panhc static int ideapad_backlight_update_status(struct backlight_device *blightdev) 1200a4ecbb8aSIke Panhc { 1201331e0ea2SZhang Rui struct ideapad_private *priv = bl_get_data(blightdev); 12027be193e3SBarnabás Pőcze int err; 1203331e0ea2SZhang Rui 12047be193e3SBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, 12057be193e3SBarnabás Pőcze blightdev->props.brightness); 12067be193e3SBarnabás Pőcze if (err) 12077be193e3SBarnabás Pőcze return err; 120865c7713aSBarnabás Pőcze 12097be193e3SBarnabás Pőcze err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, 12107be193e3SBarnabás Pőcze blightdev->props.power != FB_BLANK_POWERDOWN); 12117be193e3SBarnabás Pőcze if (err) 12127be193e3SBarnabás Pőcze return err; 1213a4ecbb8aSIke Panhc 1214a4ecbb8aSIke Panhc return 0; 1215a4ecbb8aSIke Panhc } 1216a4ecbb8aSIke Panhc 1217a4ecbb8aSIke Panhc static const struct backlight_ops ideapad_backlight_ops = { 1218a4ecbb8aSIke Panhc .get_brightness = ideapad_backlight_get_brightness, 1219a4ecbb8aSIke Panhc .update_status = ideapad_backlight_update_status, 1220a4ecbb8aSIke Panhc }; 1221a4ecbb8aSIke Panhc 1222a4ecbb8aSIke Panhc static int ideapad_backlight_init(struct ideapad_private *priv) 1223a4ecbb8aSIke Panhc { 1224a4ecbb8aSIke Panhc struct backlight_device *blightdev; 1225a4ecbb8aSIke Panhc struct backlight_properties props; 1226a4ecbb8aSIke Panhc unsigned long max, now, power; 12277be193e3SBarnabás Pőcze int err; 1228a4ecbb8aSIke Panhc 12297be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max); 12307be193e3SBarnabás Pőcze if (err) 12317be193e3SBarnabás Pőcze return err; 123265c7713aSBarnabás Pőcze 12337be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 12347be193e3SBarnabás Pőcze if (err) 12357be193e3SBarnabás Pőcze return err; 123665c7713aSBarnabás Pőcze 12377be193e3SBarnabás Pőcze err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power); 12387be193e3SBarnabás Pőcze if (err) 12397be193e3SBarnabás Pőcze return err; 1240a4ecbb8aSIke Panhc 124165c7713aSBarnabás Pőcze memset(&props, 0, sizeof(props)); 124265c7713aSBarnabás Pőcze 1243a4ecbb8aSIke Panhc props.max_brightness = max; 1244a4ecbb8aSIke Panhc props.type = BACKLIGHT_PLATFORM; 124565c7713aSBarnabás Pőcze 1246a4ecbb8aSIke Panhc blightdev = backlight_device_register("ideapad", 1247a4ecbb8aSIke Panhc &priv->platform_device->dev, 1248a4ecbb8aSIke Panhc priv, 1249a4ecbb8aSIke Panhc &ideapad_backlight_ops, 1250a4ecbb8aSIke Panhc &props); 1251a4ecbb8aSIke Panhc if (IS_ERR(blightdev)) { 125265c7713aSBarnabás Pőcze err = PTR_ERR(blightdev); 1253654324c4SBarnabás Pőcze dev_err(&priv->platform_device->dev, 125465c7713aSBarnabás Pőcze "Could not register backlight device: %d\n", err); 125565c7713aSBarnabás Pőcze return err; 1256a4ecbb8aSIke Panhc } 1257a4ecbb8aSIke Panhc 1258a4ecbb8aSIke Panhc priv->blightdev = blightdev; 1259a4ecbb8aSIke Panhc blightdev->props.brightness = now; 1260a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 126165c7713aSBarnabás Pőcze 1262a4ecbb8aSIke Panhc backlight_update_status(blightdev); 1263a4ecbb8aSIke Panhc 1264a4ecbb8aSIke Panhc return 0; 1265a4ecbb8aSIke Panhc } 1266a4ecbb8aSIke Panhc 1267a4ecbb8aSIke Panhc static void ideapad_backlight_exit(struct ideapad_private *priv) 1268a4ecbb8aSIke Panhc { 1269a4ecbb8aSIke Panhc backlight_device_unregister(priv->blightdev); 1270a4ecbb8aSIke Panhc priv->blightdev = NULL; 1271a4ecbb8aSIke Panhc } 1272a4ecbb8aSIke Panhc 1273a4ecbb8aSIke Panhc static void ideapad_backlight_notify_power(struct ideapad_private *priv) 1274a4ecbb8aSIke Panhc { 1275a4ecbb8aSIke Panhc struct backlight_device *blightdev = priv->blightdev; 127665c7713aSBarnabás Pőcze unsigned long power; 1277a4ecbb8aSIke Panhc 1278d4afc775SRene Bollford if (!blightdev) 1279d4afc775SRene Bollford return; 128065c7713aSBarnabás Pőcze 1281331e0ea2SZhang Rui if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) 1282a4ecbb8aSIke Panhc return; 128365c7713aSBarnabás Pőcze 1284a4ecbb8aSIke Panhc blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; 1285a4ecbb8aSIke Panhc } 1286a4ecbb8aSIke Panhc 1287a4ecbb8aSIke Panhc static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) 1288a4ecbb8aSIke Panhc { 1289a4ecbb8aSIke Panhc unsigned long now; 1290a4ecbb8aSIke Panhc 1291a4ecbb8aSIke Panhc /* if we control brightness via acpi video driver */ 129265c7713aSBarnabás Pőcze if (!priv->blightdev) 1293331e0ea2SZhang Rui read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); 129465c7713aSBarnabás Pőcze else 1295a4ecbb8aSIke Panhc backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); 1296a4ecbb8aSIke Panhc } 1297a4ecbb8aSIke Panhc 1298a4ecbb8aSIke Panhc /* 1299503325f8SBarnabás Pőcze * keyboard backlight 1300503325f8SBarnabás Pőcze */ 1301503325f8SBarnabás Pőcze static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv) 1302503325f8SBarnabás Pőcze { 1303503325f8SBarnabás Pőcze unsigned long hals; 1304503325f8SBarnabás Pőcze int err; 1305503325f8SBarnabás Pőcze 1306503325f8SBarnabás Pőcze err = eval_hals(priv->adev->handle, &hals); 1307503325f8SBarnabás Pőcze if (err) 1308503325f8SBarnabás Pőcze return err; 1309503325f8SBarnabás Pőcze 1310503325f8SBarnabás Pőcze return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals); 1311503325f8SBarnabás Pőcze } 1312503325f8SBarnabás Pőcze 1313503325f8SBarnabás Pőcze static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev) 1314503325f8SBarnabás Pőcze { 1315503325f8SBarnabás Pőcze struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); 1316503325f8SBarnabás Pőcze 1317503325f8SBarnabás Pőcze return ideapad_kbd_bl_brightness_get(priv); 1318503325f8SBarnabás Pőcze } 1319503325f8SBarnabás Pőcze 1320503325f8SBarnabás Pőcze static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness) 1321503325f8SBarnabás Pőcze { 1322503325f8SBarnabás Pőcze int err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF); 1323503325f8SBarnabás Pőcze 1324503325f8SBarnabás Pőcze if (err) 1325503325f8SBarnabás Pőcze return err; 1326503325f8SBarnabás Pőcze 1327503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1328503325f8SBarnabás Pőcze 1329503325f8SBarnabás Pőcze return 0; 1330503325f8SBarnabás Pőcze } 1331503325f8SBarnabás Pőcze 1332503325f8SBarnabás Pőcze static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev, 1333503325f8SBarnabás Pőcze enum led_brightness brightness) 1334503325f8SBarnabás Pőcze { 1335503325f8SBarnabás Pőcze struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); 1336503325f8SBarnabás Pőcze 1337503325f8SBarnabás Pőcze return ideapad_kbd_bl_brightness_set(priv, brightness); 1338503325f8SBarnabás Pőcze } 1339503325f8SBarnabás Pőcze 1340503325f8SBarnabás Pőcze static void ideapad_kbd_bl_notify(struct ideapad_private *priv) 1341503325f8SBarnabás Pőcze { 1342503325f8SBarnabás Pőcze int brightness; 1343503325f8SBarnabás Pőcze 1344503325f8SBarnabás Pőcze if (!priv->kbd_bl.initialized) 1345503325f8SBarnabás Pőcze return; 1346503325f8SBarnabás Pőcze 1347503325f8SBarnabás Pőcze brightness = ideapad_kbd_bl_brightness_get(priv); 1348503325f8SBarnabás Pőcze if (brightness < 0) 1349503325f8SBarnabás Pőcze return; 1350503325f8SBarnabás Pőcze 1351503325f8SBarnabás Pőcze if (brightness == priv->kbd_bl.last_brightness) 1352503325f8SBarnabás Pőcze return; 1353503325f8SBarnabás Pőcze 1354503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1355503325f8SBarnabás Pőcze 1356503325f8SBarnabás Pőcze led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness); 1357503325f8SBarnabás Pőcze } 1358503325f8SBarnabás Pőcze 1359503325f8SBarnabás Pőcze static int ideapad_kbd_bl_init(struct ideapad_private *priv) 1360503325f8SBarnabás Pőcze { 1361503325f8SBarnabás Pőcze int brightness, err; 1362503325f8SBarnabás Pőcze 1363503325f8SBarnabás Pőcze if (!priv->features.kbd_bl) 1364503325f8SBarnabás Pőcze return -ENODEV; 1365503325f8SBarnabás Pőcze 1366503325f8SBarnabás Pőcze if (WARN_ON(priv->kbd_bl.initialized)) 1367503325f8SBarnabás Pőcze return -EEXIST; 1368503325f8SBarnabás Pőcze 1369503325f8SBarnabás Pőcze brightness = ideapad_kbd_bl_brightness_get(priv); 1370503325f8SBarnabás Pőcze if (brightness < 0) 1371503325f8SBarnabás Pőcze return brightness; 1372503325f8SBarnabás Pőcze 1373503325f8SBarnabás Pőcze priv->kbd_bl.last_brightness = brightness; 1374503325f8SBarnabás Pőcze 1375503325f8SBarnabás Pőcze priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT; 1376503325f8SBarnabás Pőcze priv->kbd_bl.led.max_brightness = 1; 1377503325f8SBarnabás Pőcze priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get; 1378503325f8SBarnabás Pőcze priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set; 1379503325f8SBarnabás Pőcze priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED; 1380503325f8SBarnabás Pőcze 1381503325f8SBarnabás Pőcze err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led); 1382503325f8SBarnabás Pőcze if (err) 1383503325f8SBarnabás Pőcze return err; 1384503325f8SBarnabás Pőcze 1385503325f8SBarnabás Pőcze priv->kbd_bl.initialized = true; 1386503325f8SBarnabás Pőcze 1387503325f8SBarnabás Pőcze return 0; 1388503325f8SBarnabás Pőcze } 1389503325f8SBarnabás Pőcze 1390503325f8SBarnabás Pőcze static void ideapad_kbd_bl_exit(struct ideapad_private *priv) 1391503325f8SBarnabás Pőcze { 1392503325f8SBarnabás Pőcze if (!priv->kbd_bl.initialized) 1393503325f8SBarnabás Pőcze return; 1394503325f8SBarnabás Pőcze 1395503325f8SBarnabás Pőcze priv->kbd_bl.initialized = false; 1396503325f8SBarnabás Pőcze 1397503325f8SBarnabás Pőcze led_classdev_unregister(&priv->kbd_bl.led); 1398503325f8SBarnabás Pőcze } 1399503325f8SBarnabás Pőcze 1400503325f8SBarnabás Pőcze /* 1401a4b5a279SIke Panhc * module init/exit 1402a4b5a279SIke Panhc */ 140375a11f11SZhang Rui static void ideapad_sync_touchpad_state(struct ideapad_private *priv) 140407a4a4fcSMaxim Mikityanskiy { 140507a4a4fcSMaxim Mikityanskiy unsigned long value; 140607a4a4fcSMaxim Mikityanskiy 14071c59de4aSBarnabás Pőcze if (!priv->features.touchpad_ctrl_via_ec) 1408d69cd7eeSJiaxun Yang return; 1409d69cd7eeSJiaxun Yang 141007a4a4fcSMaxim Mikityanskiy /* Without reading from EC touchpad LED doesn't switch state */ 141175a11f11SZhang Rui if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { 141265c7713aSBarnabás Pőcze unsigned char param; 141365c7713aSBarnabás Pőcze /* 141465c7713aSBarnabás Pőcze * Some IdeaPads don't really turn off touchpad - they only 141507a4a4fcSMaxim Mikityanskiy * switch the LED state. We (de)activate KBC AUX port to turn 141607a4a4fcSMaxim Mikityanskiy * touchpad off and on. We send KEY_TOUCHPAD_OFF and 141765c7713aSBarnabás Pőcze * KEY_TOUCHPAD_ON to not to get out of sync with LED 141865c7713aSBarnabás Pőcze */ 141965c7713aSBarnabás Pőcze i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE); 142007a4a4fcSMaxim Mikityanskiy ideapad_input_report(priv, value ? 67 : 66); 1421c6795746SBarnabás Pőcze sysfs_notify(&priv->platform_device->dev.kobj, NULL, "touchpad"); 142207a4a4fcSMaxim Mikityanskiy } 142307a4a4fcSMaxim Mikityanskiy } 142407a4a4fcSMaxim Mikityanskiy 1425b5c37b79SZhang Rui static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) 142657ac3b05SIke Panhc { 1427b5c37b79SZhang Rui struct ideapad_private *priv = data; 14280c4915b6SBarnabás Pőcze unsigned long vpc1, vpc2, bit; 142957ac3b05SIke Panhc 14302be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) 143157ac3b05SIke Panhc return; 143265c7713aSBarnabás Pőcze 14332be1dc21SIke Panhc if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) 143457ac3b05SIke Panhc return; 143557ac3b05SIke Panhc 143657ac3b05SIke Panhc vpc1 = (vpc2 << 8) | vpc1; 14370c4915b6SBarnabás Pőcze 14380c4915b6SBarnabás Pőcze for_each_set_bit (bit, &vpc1, 16) { 14390c4915b6SBarnabás Pőcze switch (bit) { 144020a769c1SIke Panhc case 13: 1441296f9fe0SMaxim Mikityanskiy case 11: 144248f67d62SAlex Hung case 8: 1443296f9fe0SMaxim Mikityanskiy case 7: 144420a769c1SIke Panhc case 6: 14450c4915b6SBarnabás Pőcze ideapad_input_report(priv, bit); 144620a769c1SIke Panhc break; 1447ab66724aSHans de Goede case 10: 1448ab66724aSHans de Goede /* 1449ab66724aSHans de Goede * This event gets send on a Yoga 300-11IBR when the EC 1450ab66724aSHans de Goede * believes that the device has changed between laptop/ 1451ab66724aSHans de Goede * tent/stand/tablet mode. The EC relies on getting 1452ab66724aSHans de Goede * angle info from 2 accelerometers through a special 1453ab66724aSHans de Goede * windows service calling a DSM on the DUAL250E ACPI- 1454ab66724aSHans de Goede * device. Linux does not do this, making the laptop/ 1455ab66724aSHans de Goede * tent/stand/tablet mode info unreliable, so we simply 1456ab66724aSHans de Goede * ignore these events. 1457ab66724aSHans de Goede */ 1458ab66724aSHans de Goede break; 145965c7713aSBarnabás Pőcze case 9: 146065c7713aSBarnabás Pőcze ideapad_sync_rfk_state(priv); 146165c7713aSBarnabás Pőcze break; 146207a4a4fcSMaxim Mikityanskiy case 5: 146375a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 146407a4a4fcSMaxim Mikityanskiy break; 1465a4ecbb8aSIke Panhc case 4: 1466a4ecbb8aSIke Panhc ideapad_backlight_notify_brightness(priv); 1467a4ecbb8aSIke Panhc break; 1468f43d9ec0SIke Panhc case 3: 1469f43d9ec0SIke Panhc ideapad_input_novokey(priv); 1470f43d9ec0SIke Panhc break; 1471a4ecbb8aSIke Panhc case 2: 1472a4ecbb8aSIke Panhc ideapad_backlight_notify_power(priv); 1473a4ecbb8aSIke Panhc break; 14743cfd956bSHao Wei Tee case 1: 147565c7713aSBarnabás Pőcze /* 147665c7713aSBarnabás Pőcze * Some IdeaPads report event 1 every ~20 14773cfd956bSHao Wei Tee * seconds while on battery power; some 14783cfd956bSHao Wei Tee * report this when changing to/from tablet 1479503325f8SBarnabás Pőcze * mode; some report this when the keyboard 1480503325f8SBarnabás Pőcze * backlight has changed. 14813cfd956bSHao Wei Tee */ 1482503325f8SBarnabás Pőcze ideapad_kbd_bl_notify(priv); 14833cfd956bSHao Wei Tee break; 148465c7713aSBarnabás Pőcze case 0: 148565c7713aSBarnabás Pőcze ideapad_check_special_buttons(priv); 148665c7713aSBarnabás Pőcze break; 1487a4ecbb8aSIke Panhc default: 1488654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1489654324c4SBarnabás Pőcze "Unknown event: %lu\n", bit); 149057ac3b05SIke Panhc } 149157ac3b05SIke Panhc } 1492a4ecbb8aSIke Panhc } 149357ac3b05SIke Panhc 149474caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 149574caab99SArnd Bergmann static void ideapad_wmi_notify(u32 value, void *context) 149674caab99SArnd Bergmann { 1497654324c4SBarnabás Pőcze struct ideapad_private *priv = context; 14983ae86d2dSMeng Dong unsigned long result; 1499654324c4SBarnabás Pőcze 150074caab99SArnd Bergmann switch (value) { 150174caab99SArnd Bergmann case 128: 1502654324c4SBarnabás Pőcze ideapad_input_report(priv, value); 150374caab99SArnd Bergmann break; 15043ae86d2dSMeng Dong case 208: 1505*81a5603aSArnav Rawat if (!priv->features.set_fn_lock_led) 1506*81a5603aSArnav Rawat break; 1507*81a5603aSArnav Rawat 15083ae86d2dSMeng Dong if (!eval_hals(priv->adev->handle, &result)) { 15093ae86d2dSMeng Dong bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result); 15103ae86d2dSMeng Dong 15113ae86d2dSMeng Dong exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); 15123ae86d2dSMeng Dong } 15133ae86d2dSMeng Dong break; 151474caab99SArnd Bergmann default: 1515654324c4SBarnabás Pőcze dev_info(&priv->platform_device->dev, 1516654324c4SBarnabás Pőcze "Unknown WMI event: %u\n", value); 151774caab99SArnd Bergmann } 151874caab99SArnd Bergmann } 151974caab99SArnd Bergmann #endif 152074caab99SArnd Bergmann 1521*81a5603aSArnav Rawat /* On some models we need to call exec_sals(SALS_FNLOCK_ON/OFF) to set the LED */ 1522*81a5603aSArnav Rawat static const struct dmi_system_id set_fn_lock_led_list[] = { 1523*81a5603aSArnav Rawat { 1524*81a5603aSArnav Rawat /* https://bugzilla.kernel.org/show_bug.cgi?id=212671 */ 1525*81a5603aSArnav Rawat .matches = { 1526*81a5603aSArnav Rawat DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1527*81a5603aSArnav Rawat DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Legion R7000P2020H"), 1528*81a5603aSArnav Rawat } 1529*81a5603aSArnav Rawat }, 1530*81a5603aSArnav Rawat {} 1531*81a5603aSArnav Rawat }; 1532*81a5603aSArnav Rawat 1533ce363c2bSHans de Goede /* 15345105e78eSHans de Goede * Some ideapads have a hardware rfkill switch, but most do not have one. 15355105e78eSHans de Goede * Reading VPCCMD_R_RF always results in 0 on models without a hardware rfkill, 15365105e78eSHans de Goede * switch causing ideapad_laptop to wrongly report all radios as hw-blocked. 15375105e78eSHans de Goede * There used to be a long list of DMI ids for models without a hw rfkill 15385105e78eSHans de Goede * switch here, but that resulted in playing whack a mole. 15395105e78eSHans de Goede * More importantly wrongly reporting the wifi radio as hw-blocked, results in 15405105e78eSHans de Goede * non working wifi. Whereas not reporting it hw-blocked, when it actually is 15415105e78eSHans de Goede * hw-blocked results in an empty SSID list, which is a much more benign 15425105e78eSHans de Goede * failure mode. 15435105e78eSHans de Goede * So the default now is the much safer option of assuming there is no 15445105e78eSHans de Goede * hardware rfkill switch. This default also actually matches most hardware, 15455105e78eSHans de Goede * since having a hw rfkill switch is quite rare on modern hardware, so this 15465105e78eSHans de Goede * also leads to a much shorter list. 1547ce363c2bSHans de Goede */ 15485105e78eSHans de Goede static const struct dmi_system_id hw_rfkill_list[] = { 154985093f79SHans de Goede {} 155085093f79SHans de Goede }; 155185093f79SHans de Goede 1552a231224aSManyi Li static const struct dmi_system_id no_touchpad_switch_list[] = { 1553a231224aSManyi Li { 1554a231224aSManyi Li .ident = "Lenovo Yoga 3 Pro 1370", 1555a231224aSManyi Li .matches = { 1556a231224aSManyi Li DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1557a231224aSManyi Li DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 3"), 1558a231224aSManyi Li }, 1559a231224aSManyi Li }, 1560a231224aSManyi Li { 1561a231224aSManyi Li .ident = "ZhaoYang K4e-IML", 1562a231224aSManyi Li .matches = { 1563a231224aSManyi Li DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 1564a231224aSManyi Li DMI_MATCH(DMI_PRODUCT_VERSION, "ZhaoYang K4e-IML"), 1565a231224aSManyi Li }, 1566a231224aSManyi Li }, 1567a231224aSManyi Li {} 1568a231224aSManyi Li }; 1569a231224aSManyi Li 15701c59de4aSBarnabás Pőcze static void ideapad_check_features(struct ideapad_private *priv) 15711c59de4aSBarnabás Pőcze { 15721c59de4aSBarnabás Pőcze acpi_handle handle = priv->adev->handle; 15731c59de4aSBarnabás Pőcze unsigned long val; 15741c59de4aSBarnabás Pőcze 1575*81a5603aSArnav Rawat priv->features.set_fn_lock_led = dmi_check_system(set_fn_lock_led_list); 15761c59de4aSBarnabás Pőcze priv->features.hw_rfkill_switch = dmi_check_system(hw_rfkill_list); 15771c59de4aSBarnabás Pőcze 15781c59de4aSBarnabás Pőcze /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ 1579a231224aSManyi Li if (acpi_dev_present("ELAN0634", NULL, -1)) 1580a231224aSManyi Li priv->features.touchpad_ctrl_via_ec = 0; 1581a231224aSManyi Li else if (dmi_check_system(no_touchpad_switch_list)) 1582a231224aSManyi Li priv->features.touchpad_ctrl_via_ec = 0; 1583a231224aSManyi Li else 1584a231224aSManyi Li priv->features.touchpad_ctrl_via_ec = 1; 15851c59de4aSBarnabás Pőcze 15861c59de4aSBarnabás Pőcze if (!read_ec_data(handle, VPCCMD_R_FAN, &val)) 15871c59de4aSBarnabás Pőcze priv->features.fan_mode = true; 15881c59de4aSBarnabás Pőcze 15891c59de4aSBarnabás Pőcze if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) 15901c59de4aSBarnabás Pőcze priv->features.conservation_mode = true; 15911c59de4aSBarnabás Pőcze 15921c59de4aSBarnabás Pőcze if (acpi_has_method(handle, "DYTC")) 15931c59de4aSBarnabás Pőcze priv->features.dytc = true; 15941c59de4aSBarnabás Pőcze 1595392cbf0aSBarnabás Pőcze if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) { 1596392cbf0aSBarnabás Pőcze if (!eval_hals(handle, &val)) { 1597392cbf0aSBarnabás Pőcze if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val)) 15981c59de4aSBarnabás Pőcze priv->features.fn_lock = true; 1599503325f8SBarnabás Pőcze 1600503325f8SBarnabás Pőcze if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) 1601503325f8SBarnabás Pőcze priv->features.kbd_bl = true; 16026b49dea4SBarnabás Pőcze 16036b49dea4SBarnabás Pőcze if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val)) 16046b49dea4SBarnabás Pőcze priv->features.usb_charging = true; 16051c59de4aSBarnabás Pőcze } 1606392cbf0aSBarnabás Pőcze } 1607392cbf0aSBarnabás Pőcze } 16081c59de4aSBarnabás Pőcze 1609b5c37b79SZhang Rui static int ideapad_acpi_add(struct platform_device *pdev) 1610b5c37b79SZhang Rui { 1611043449e7SRafael J. Wysocki struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 1612b5c37b79SZhang Rui struct ideapad_private *priv; 1613803be832SBarnabás Pőcze acpi_status status; 1614ff36b0d9SBarnabás Pőcze unsigned long cfg; 161565c7713aSBarnabás Pőcze int err, i; 1616b5c37b79SZhang Rui 1617043449e7SRafael J. Wysocki if (!adev || eval_int(adev->handle, "_CFG", &cfg)) 1618b5c37b79SZhang Rui return -ENODEV; 1619b5c37b79SZhang Rui 1620b3facd7bSHimangi Saraogi priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 1621b5c37b79SZhang Rui if (!priv) 1622b5c37b79SZhang Rui return -ENOMEM; 1623b5c37b79SZhang Rui 1624b5c37b79SZhang Rui dev_set_drvdata(&pdev->dev, priv); 162565c7713aSBarnabás Pőcze 1626b5c37b79SZhang Rui priv->cfg = cfg; 1627b5c37b79SZhang Rui priv->adev = adev; 1628b5c37b79SZhang Rui priv->platform_device = pdev; 1629b5c37b79SZhang Rui 16301c59de4aSBarnabás Pőcze ideapad_check_features(priv); 1631d69cd7eeSJiaxun Yang 163265c7713aSBarnabás Pőcze err = ideapad_sysfs_init(priv); 163365c7713aSBarnabás Pőcze if (err) 163465c7713aSBarnabás Pőcze return err; 1635b5c37b79SZhang Rui 163617f1bf38SGreg Kroah-Hartman ideapad_debugfs_init(priv); 1637b5c37b79SZhang Rui 163865c7713aSBarnabás Pőcze err = ideapad_input_init(priv); 163965c7713aSBarnabás Pőcze if (err) 1640b5c37b79SZhang Rui goto input_failed; 1641b5c37b79SZhang Rui 1642503325f8SBarnabás Pőcze err = ideapad_kbd_bl_init(priv); 1643503325f8SBarnabás Pőcze if (err) { 1644503325f8SBarnabás Pőcze if (err != -ENODEV) 1645503325f8SBarnabás Pőcze dev_warn(&pdev->dev, "Could not set up keyboard backlight LED: %d\n", err); 1646503325f8SBarnabás Pőcze else 1647503325f8SBarnabás Pőcze dev_info(&pdev->dev, "Keyboard backlight control not available\n"); 1648503325f8SBarnabás Pőcze } 1649503325f8SBarnabás Pőcze 1650ce363c2bSHans de Goede /* 1651ce363c2bSHans de Goede * On some models without a hw-switch (the yoga 2 13 at least) 1652ce363c2bSHans de Goede * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. 1653ce363c2bSHans de Goede */ 16541c59de4aSBarnabás Pőcze if (!priv->features.hw_rfkill_switch) 1655ce363c2bSHans de Goede write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); 1656ce363c2bSHans de Goede 1657d69cd7eeSJiaxun Yang /* The same for Touchpad */ 16581c59de4aSBarnabás Pőcze if (!priv->features.touchpad_ctrl_via_ec) 1659d69cd7eeSJiaxun Yang write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, 1); 1660d69cd7eeSJiaxun Yang 166185093f79SHans de Goede for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1662b5c37b79SZhang Rui if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) 1663b5c37b79SZhang Rui ideapad_register_rfkill(priv, i); 1664ce363c2bSHans de Goede 1665b5c37b79SZhang Rui ideapad_sync_rfk_state(priv); 1666b5c37b79SZhang Rui ideapad_sync_touchpad_state(priv); 1667b5c37b79SZhang Rui 166865c7713aSBarnabás Pőcze err = ideapad_dytc_profile_init(priv); 166965c7713aSBarnabás Pőcze if (err) { 167065c7713aSBarnabás Pőcze if (err != -ENODEV) 167165c7713aSBarnabás Pőcze dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", err); 16721c59de4aSBarnabás Pőcze else 16731c59de4aSBarnabás Pőcze dev_info(&pdev->dev, "DYTC interface is not available\n"); 16741c59de4aSBarnabás Pőcze } 1675eabe5339SJiaxun Yang 167626bff5f0SHans de Goede if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { 167765c7713aSBarnabás Pőcze err = ideapad_backlight_init(priv); 167865c7713aSBarnabás Pőcze if (err && err != -ENODEV) 1679b5c37b79SZhang Rui goto backlight_failed; 1680b5c37b79SZhang Rui } 168165c7713aSBarnabás Pőcze 1682803be832SBarnabás Pőcze status = acpi_install_notify_handler(adev->handle, 1683803be832SBarnabás Pőcze ACPI_DEVICE_NOTIFY, 1684803be832SBarnabás Pőcze ideapad_acpi_notify, priv); 1685803be832SBarnabás Pőcze if (ACPI_FAILURE(status)) { 168665c7713aSBarnabás Pőcze err = -EIO; 1687b5c37b79SZhang Rui goto notification_failed; 1688803be832SBarnabás Pőcze } 16892d98e0b9SArnd Bergmann 169074caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 16912d98e0b9SArnd Bergmann for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { 1692803be832SBarnabás Pőcze status = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], 16932d98e0b9SArnd Bergmann ideapad_wmi_notify, priv); 1694803be832SBarnabás Pőcze if (ACPI_SUCCESS(status)) { 16952d98e0b9SArnd Bergmann priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; 16962d98e0b9SArnd Bergmann break; 16972d98e0b9SArnd Bergmann } 16982d98e0b9SArnd Bergmann } 169965c7713aSBarnabás Pőcze 1700803be832SBarnabás Pőcze if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) { 170165c7713aSBarnabás Pőcze err = -EIO; 170274caab99SArnd Bergmann goto notification_failed_wmi; 1703803be832SBarnabás Pőcze } 170474caab99SArnd Bergmann #endif 1705b5c37b79SZhang Rui 1706b5c37b79SZhang Rui return 0; 170765c7713aSBarnabás Pőcze 170874caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 170974caab99SArnd Bergmann notification_failed_wmi: 171074caab99SArnd Bergmann acpi_remove_notify_handler(priv->adev->handle, 171165c7713aSBarnabás Pőcze ACPI_DEVICE_NOTIFY, 171265c7713aSBarnabás Pőcze ideapad_acpi_notify); 171374caab99SArnd Bergmann #endif 171465c7713aSBarnabás Pőcze 1715b5c37b79SZhang Rui notification_failed: 1716b5c37b79SZhang Rui ideapad_backlight_exit(priv); 171765c7713aSBarnabás Pőcze 1718b5c37b79SZhang Rui backlight_failed: 1719caa315b8SBarnabás Pőcze ideapad_dytc_profile_exit(priv); 172065c7713aSBarnabás Pőcze 1721b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1722b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 172365c7713aSBarnabás Pőcze 1724503325f8SBarnabás Pőcze ideapad_kbd_bl_exit(priv); 1725b5c37b79SZhang Rui ideapad_input_exit(priv); 172665c7713aSBarnabás Pőcze 1727b5c37b79SZhang Rui input_failed: 1728b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1729b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 173065c7713aSBarnabás Pőcze 173165c7713aSBarnabás Pőcze return err; 1732b5c37b79SZhang Rui } 1733b5c37b79SZhang Rui 1734b5c37b79SZhang Rui static int ideapad_acpi_remove(struct platform_device *pdev) 1735b5c37b79SZhang Rui { 1736b5c37b79SZhang Rui struct ideapad_private *priv = dev_get_drvdata(&pdev->dev); 1737b5c37b79SZhang Rui int i; 1738b5c37b79SZhang Rui 173974caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI) 17402d98e0b9SArnd Bergmann if (priv->fnesc_guid) 17412d98e0b9SArnd Bergmann wmi_remove_notify_handler(priv->fnesc_guid); 174274caab99SArnd Bergmann #endif 174365c7713aSBarnabás Pőcze 1744b5c37b79SZhang Rui acpi_remove_notify_handler(priv->adev->handle, 174565c7713aSBarnabás Pőcze ACPI_DEVICE_NOTIFY, 174665c7713aSBarnabás Pőcze ideapad_acpi_notify); 174765c7713aSBarnabás Pőcze 1748b5c37b79SZhang Rui ideapad_backlight_exit(priv); 1749eabe5339SJiaxun Yang ideapad_dytc_profile_exit(priv); 175065c7713aSBarnabás Pőcze 1751b5c37b79SZhang Rui for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) 1752b5c37b79SZhang Rui ideapad_unregister_rfkill(priv, i); 175365c7713aSBarnabás Pőcze 1754503325f8SBarnabás Pőcze ideapad_kbd_bl_exit(priv); 1755b5c37b79SZhang Rui ideapad_input_exit(priv); 1756b5c37b79SZhang Rui ideapad_debugfs_exit(priv); 1757b5c37b79SZhang Rui ideapad_sysfs_exit(priv); 1758b5c37b79SZhang Rui 1759b5c37b79SZhang Rui return 0; 1760b5c37b79SZhang Rui } 1761b5c37b79SZhang Rui 176211fa8da5SZhang Rui #ifdef CONFIG_PM_SLEEP 1763e1a39a44SBarnabás Pőcze static int ideapad_acpi_resume(struct device *dev) 176407a4a4fcSMaxim Mikityanskiy { 1765e1a39a44SBarnabás Pőcze struct ideapad_private *priv = dev_get_drvdata(dev); 176675a11f11SZhang Rui 176775a11f11SZhang Rui ideapad_sync_rfk_state(priv); 176875a11f11SZhang Rui ideapad_sync_touchpad_state(priv); 1769eabe5339SJiaxun Yang 1770eabe5339SJiaxun Yang if (priv->dytc) 1771eabe5339SJiaxun Yang dytc_profile_refresh(priv); 1772eabe5339SJiaxun Yang 177307a4a4fcSMaxim Mikityanskiy return 0; 177407a4a4fcSMaxim Mikityanskiy } 1775b5c37b79SZhang Rui #endif 177607a4a4fcSMaxim Mikityanskiy static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); 177707a4a4fcSMaxim Mikityanskiy 1778b5c37b79SZhang Rui static const struct acpi_device_id ideapad_device_ids[] = { 1779b5c37b79SZhang Rui {"VPC2004", 0}, 1780b5c37b79SZhang Rui {"", 0}, 178157ac3b05SIke Panhc }; 1782b5c37b79SZhang Rui MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); 1783b5c37b79SZhang Rui 1784b5c37b79SZhang Rui static struct platform_driver ideapad_acpi_driver = { 1785b5c37b79SZhang Rui .probe = ideapad_acpi_add, 1786b5c37b79SZhang Rui .remove = ideapad_acpi_remove, 1787b5c37b79SZhang Rui .driver = { 1788b5c37b79SZhang Rui .name = "ideapad_acpi", 1789b5c37b79SZhang Rui .pm = &ideapad_pm, 1790b5c37b79SZhang Rui .acpi_match_table = ACPI_PTR(ideapad_device_ids), 1791b5c37b79SZhang Rui }, 1792b5c37b79SZhang Rui }; 1793b5c37b79SZhang Rui 1794b5c37b79SZhang Rui module_platform_driver(ideapad_acpi_driver); 179557ac3b05SIke Panhc 179657ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 179757ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras"); 179857ac3b05SIke Panhc MODULE_LICENSE("GPL"); 1799