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>
147d38f034SBarnabás Pőcze #include <linux/debugfs.h>
157d38f034SBarnabás Pőcze #include <linux/device.h>
167d38f034SBarnabás Pőcze #include <linux/dmi.h>
177d38f034SBarnabás Pőcze #include <linux/fb.h>
187d38f034SBarnabás Pőcze #include <linux/i8042.h>
197d38f034SBarnabás Pőcze #include <linux/init.h>
20f63409aeSIke Panhc #include <linux/input.h>
21f63409aeSIke Panhc #include <linux/input/sparse-keymap.h>
2240e0447dSBarnabás Pőcze #include <linux/jiffies.h>
237d38f034SBarnabás Pőcze #include <linux/kernel.h>
247d38f034SBarnabás Pőcze #include <linux/module.h>
257d38f034SBarnabás Pőcze #include <linux/platform_device.h>
267d38f034SBarnabás Pőcze #include <linux/platform_profile.h>
277d38f034SBarnabás Pőcze #include <linux/rfkill.h>
28773e3206SIke Panhc #include <linux/seq_file.h>
29d6b50889SBarnabás Pőcze #include <linux/sysfs.h>
307d38f034SBarnabás Pőcze #include <linux/types.h>
317d38f034SBarnabás Pőcze 
3226bff5f0SHans de Goede #include <acpi/video.h>
3357ac3b05SIke Panhc 
34c1f73658SIke Panhc #define IDEAPAD_RFKILL_DEV_NUM	(3)
3557ac3b05SIke Panhc 
3674caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI)
372d98e0b9SArnd Bergmann static const char *const ideapad_wmi_fnesc_events[] = {
382d98e0b9SArnd Bergmann 	"26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */
392d98e0b9SArnd Bergmann 	"56322276-8493-4CE8-A783-98C991274F5E", /* Yoga 700 */
402d98e0b9SArnd Bergmann };
4174caab99SArnd Bergmann #endif
4274caab99SArnd Bergmann 
432be1dc21SIke Panhc enum {
440b765671SBarnabás Pőcze 	CFG_CAP_BT_BIT       = 16,
450b765671SBarnabás Pőcze 	CFG_CAP_3G_BIT       = 17,
460b765671SBarnabás Pőcze 	CFG_CAP_WIFI_BIT     = 18,
470b765671SBarnabás Pőcze 	CFG_CAP_CAM_BIT      = 19,
48b3ed1b7fSBarnabás Pőcze 	CFG_CAP_TOUCHPAD_BIT = 30,
490b765671SBarnabás Pőcze };
500b765671SBarnabás Pőcze 
510b765671SBarnabás Pőcze enum {
520b765671SBarnabás Pőcze 	GBMD_CONSERVATION_STATE_BIT = 5,
530b765671SBarnabás Pőcze };
540b765671SBarnabás Pőcze 
550b765671SBarnabás Pőcze enum {
560b765671SBarnabás Pőcze 	SMBC_CONSERVATION_ON  = 3,
570b765671SBarnabás Pőcze 	SMBC_CONSERVATION_OFF = 5,
580b765671SBarnabás Pőcze };
590b765671SBarnabás Pőcze 
600b765671SBarnabás Pőcze enum {
61392cbf0aSBarnabás Pőcze 	HALS_FNLOCK_SUPPORT_BIT  = 9,
620b765671SBarnabás Pőcze 	HALS_FNLOCK_STATE_BIT    = 10,
63392cbf0aSBarnabás Pőcze 	HALS_HOTKEYS_PRIMARY_BIT = 11,
640b765671SBarnabás Pőcze };
650b765671SBarnabás Pőcze 
660b765671SBarnabás Pőcze enum {
670b765671SBarnabás Pőcze 	SALS_FNLOCK_ON  = 0xe,
680b765671SBarnabás Pőcze 	SALS_FNLOCK_OFF = 0xf,
69ade50296SHao Wei Tee };
70ade50296SHao Wei Tee 
71ade50296SHao Wei Tee enum {
722be1dc21SIke Panhc 	VPCCMD_R_VPC1 = 0x10,
732be1dc21SIke Panhc 	VPCCMD_R_BL_MAX,
742be1dc21SIke Panhc 	VPCCMD_R_BL,
752be1dc21SIke Panhc 	VPCCMD_W_BL,
762be1dc21SIke Panhc 	VPCCMD_R_WIFI,
772be1dc21SIke Panhc 	VPCCMD_W_WIFI,
782be1dc21SIke Panhc 	VPCCMD_R_BT,
792be1dc21SIke Panhc 	VPCCMD_W_BT,
802be1dc21SIke Panhc 	VPCCMD_R_BL_POWER,
812be1dc21SIke Panhc 	VPCCMD_R_NOVO,
822be1dc21SIke Panhc 	VPCCMD_R_VPC2,
832be1dc21SIke Panhc 	VPCCMD_R_TOUCHPAD,
842be1dc21SIke Panhc 	VPCCMD_W_TOUCHPAD,
852be1dc21SIke Panhc 	VPCCMD_R_CAMERA,
862be1dc21SIke Panhc 	VPCCMD_W_CAMERA,
872be1dc21SIke Panhc 	VPCCMD_R_3G,
882be1dc21SIke Panhc 	VPCCMD_W_3G,
892be1dc21SIke Panhc 	VPCCMD_R_ODD, /* 0x21 */
900c7bbeb9SMaxim Mikityanskiy 	VPCCMD_W_FAN,
910c7bbeb9SMaxim Mikityanskiy 	VPCCMD_R_RF,
922be1dc21SIke Panhc 	VPCCMD_W_RF,
930c7bbeb9SMaxim Mikityanskiy 	VPCCMD_R_FAN = 0x2B,
94296f9fe0SMaxim Mikityanskiy 	VPCCMD_R_SPECIAL_BUTTONS = 0x31,
952be1dc21SIke Panhc 	VPCCMD_W_BL_POWER = 0x33,
962be1dc21SIke Panhc };
972be1dc21SIke Panhc 
98eabe5339SJiaxun Yang struct ideapad_dytc_priv {
99eabe5339SJiaxun Yang 	enum platform_profile_option current_profile;
100eabe5339SJiaxun Yang 	struct platform_profile_handler pprof;
101eabe5339SJiaxun Yang 	struct mutex mutex;
102eabe5339SJiaxun Yang 	struct ideapad_private *priv;
103eabe5339SJiaxun Yang };
104eabe5339SJiaxun Yang 
105331e0ea2SZhang Rui struct ideapad_rfk_priv {
106331e0ea2SZhang Rui 	int dev;
107331e0ea2SZhang Rui 	struct ideapad_private *priv;
108331e0ea2SZhang Rui };
109331e0ea2SZhang Rui 
11057ac3b05SIke Panhc struct ideapad_private {
111469f6434SZhang Rui 	struct acpi_device *adev;
112c1f73658SIke Panhc 	struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM];
113331e0ea2SZhang Rui 	struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM];
11498ee6919SIke Panhc 	struct platform_device *platform_device;
115f63409aeSIke Panhc 	struct input_dev *inputdev;
116a4ecbb8aSIke Panhc 	struct backlight_device *blightdev;
117eabe5339SJiaxun Yang 	struct ideapad_dytc_priv *dytc;
118773e3206SIke Panhc 	struct dentry *debug;
1193371f481SIke Panhc 	unsigned long cfg;
1202d98e0b9SArnd Bergmann 	const char *fnesc_guid;
1211c59de4aSBarnabás Pőcze 	struct {
1221c59de4aSBarnabás Pőcze 		bool conservation_mode    : 1;
1231c59de4aSBarnabás Pőcze 		bool dytc                 : 1;
1241c59de4aSBarnabás Pőcze 		bool fan_mode             : 1;
1251c59de4aSBarnabás Pőcze 		bool fn_lock              : 1;
1261c59de4aSBarnabás Pőcze 		bool hw_rfkill_switch     : 1;
1271c59de4aSBarnabás Pőcze 		bool touchpad_ctrl_via_ec : 1;
1281c59de4aSBarnabás Pőcze 	} features;
12957ac3b05SIke Panhc };
13057ac3b05SIke Panhc 
131bfa97b7dSIke Panhc static bool no_bt_rfkill;
132bfa97b7dSIke Panhc module_param(no_bt_rfkill, bool, 0444);
133bfa97b7dSIke Panhc MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
134bfa97b7dSIke Panhc 
13557ac3b05SIke Panhc /*
13657ac3b05SIke Panhc  * ACPI Helpers
13757ac3b05SIke Panhc  */
138ed5b9ba7SAaron Ma #define IDEAPAD_EC_TIMEOUT (200) /* in ms */
13957ac3b05SIke Panhc 
140ff36b0d9SBarnabás Pőcze static int eval_int(acpi_handle handle, const char *name, unsigned long *res)
14157ac3b05SIke Panhc {
14257ac3b05SIke Panhc 	unsigned long long result;
143ade50296SHao Wei Tee 	acpi_status status;
144ade50296SHao Wei Tee 
145ff36b0d9SBarnabás Pőcze 	status = acpi_evaluate_integer(handle, (char *)name, NULL, &result);
146ff36b0d9SBarnabás Pőcze 	if (ACPI_FAILURE(status))
147ff36b0d9SBarnabás Pőcze 		return -EIO;
148ff36b0d9SBarnabás Pőcze 	*res = result;
149ff36b0d9SBarnabás Pőcze 	return 0;
150ff36b0d9SBarnabás Pőcze }
151ff36b0d9SBarnabás Pőcze 
152ff36b0d9SBarnabás Pőcze static int exec_simple_method(acpi_handle handle, const char *name, unsigned long arg)
153ff36b0d9SBarnabás Pőcze {
154ff36b0d9SBarnabás Pőcze 	acpi_status status = acpi_execute_simple_method(handle, (char *)name, arg);
155ff36b0d9SBarnabás Pőcze 
1567be193e3SBarnabás Pőcze 	return ACPI_FAILURE(status) ? -EIO : 0;
157ade50296SHao Wei Tee }
158ade50296SHao Wei Tee 
159ff36b0d9SBarnabás Pőcze static int eval_gbmd(acpi_handle handle, unsigned long *res)
160eabe5339SJiaxun Yang {
161ff36b0d9SBarnabás Pőcze 	return eval_int(handle, "GBMD", res);
162ff36b0d9SBarnabás Pőcze }
163ff36b0d9SBarnabás Pőcze 
164ff36b0d9SBarnabás Pőcze static int exec_smbc(acpi_handle handle, unsigned long arg)
165ff36b0d9SBarnabás Pőcze {
166ff36b0d9SBarnabás Pőcze 	return exec_simple_method(handle, "SMBC", arg);
167ff36b0d9SBarnabás Pőcze }
168ff36b0d9SBarnabás Pőcze 
169ff36b0d9SBarnabás Pőcze static int eval_hals(acpi_handle handle, unsigned long *res)
170ff36b0d9SBarnabás Pőcze {
171ff36b0d9SBarnabás Pőcze 	return eval_int(handle, "HALS", res);
172ff36b0d9SBarnabás Pőcze }
173ff36b0d9SBarnabás Pőcze 
174ff36b0d9SBarnabás Pőcze static int exec_sals(acpi_handle handle, unsigned long arg)
175ff36b0d9SBarnabás Pőcze {
176ff36b0d9SBarnabás Pőcze 	return exec_simple_method(handle, "SALS", arg);
177ff36b0d9SBarnabás Pőcze }
178ff36b0d9SBarnabás Pőcze 
179ff36b0d9SBarnabás Pőcze static int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long arg, unsigned long *res)
180ff36b0d9SBarnabás Pőcze {
181eabe5339SJiaxun Yang 	struct acpi_object_list params;
182ff36b0d9SBarnabás Pőcze 	unsigned long long result;
183eabe5339SJiaxun Yang 	union acpi_object in_obj;
184ff36b0d9SBarnabás Pőcze 	acpi_status status;
185eabe5339SJiaxun Yang 
186eabe5339SJiaxun Yang 	params.count = 1;
187eabe5339SJiaxun Yang 	params.pointer = &in_obj;
188eabe5339SJiaxun Yang 	in_obj.type = ACPI_TYPE_INTEGER;
189ff36b0d9SBarnabás Pőcze 	in_obj.integer.value = arg;
190eabe5339SJiaxun Yang 
191ff36b0d9SBarnabás Pőcze 	status = acpi_evaluate_integer(handle, (char *)name, &params, &result);
192ff36b0d9SBarnabás Pőcze 	if (ACPI_FAILURE(status))
1937be193e3SBarnabás Pőcze 		return -EIO;
194ff36b0d9SBarnabás Pőcze 
195ff36b0d9SBarnabás Pőcze 	if (res)
196ff36b0d9SBarnabás Pőcze 		*res = result;
197ff36b0d9SBarnabás Pőcze 
198eabe5339SJiaxun Yang 	return 0;
199eabe5339SJiaxun Yang }
200eabe5339SJiaxun Yang 
201ff36b0d9SBarnabás Pőcze static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res)
20257ac3b05SIke Panhc {
203ff36b0d9SBarnabás Pőcze 	return eval_int_with_arg(handle, "DYTC", cmd, res);
20457ac3b05SIke Panhc }
20557ac3b05SIke Panhc 
206ff36b0d9SBarnabás Pőcze static int eval_vpcr(acpi_handle handle, unsigned long cmd, unsigned long *res)
207ff36b0d9SBarnabás Pőcze {
208ff36b0d9SBarnabás Pőcze 	return eval_int_with_arg(handle, "VPCR", cmd, res);
209ff36b0d9SBarnabás Pőcze }
210ff36b0d9SBarnabás Pőcze 
211ff36b0d9SBarnabás Pőcze static int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data)
21257ac3b05SIke Panhc {
21357ac3b05SIke Panhc 	struct acpi_object_list params;
21457ac3b05SIke Panhc 	union acpi_object in_obj[2];
21557ac3b05SIke Panhc 	acpi_status status;
21657ac3b05SIke Panhc 
21757ac3b05SIke Panhc 	params.count = 2;
21857ac3b05SIke Panhc 	params.pointer = in_obj;
21957ac3b05SIke Panhc 	in_obj[0].type = ACPI_TYPE_INTEGER;
22057ac3b05SIke Panhc 	in_obj[0].integer.value = cmd;
22157ac3b05SIke Panhc 	in_obj[1].type = ACPI_TYPE_INTEGER;
22257ac3b05SIke Panhc 	in_obj[1].integer.value = data;
22357ac3b05SIke Panhc 
22457ac3b05SIke Panhc 	status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
225ff36b0d9SBarnabás Pőcze 	if (ACPI_FAILURE(status))
2267be193e3SBarnabás Pőcze 		return -EIO;
22757ac3b05SIke Panhc 	return 0;
22857ac3b05SIke Panhc }
22957ac3b05SIke Panhc 
230ff36b0d9SBarnabás Pőcze static int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *data)
23157ac3b05SIke Panhc {
232ff36b0d9SBarnabás Pőcze 	unsigned long end_jiffies, val;
233ff36b0d9SBarnabás Pőcze 	int err;
23457ac3b05SIke Panhc 
235ff36b0d9SBarnabás Pőcze 	err = eval_vpcw(handle, 1, cmd);
2367be193e3SBarnabás Pőcze 	if (err)
2377be193e3SBarnabás Pőcze 		return err;
23857ac3b05SIke Panhc 
23940e0447dSBarnabás Pőcze 	end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1;
24040e0447dSBarnabás Pőcze 
24140e0447dSBarnabás Pőcze 	while (time_before(jiffies, end_jiffies)) {
24257ac3b05SIke Panhc 		schedule();
243ff36b0d9SBarnabás Pőcze 		err = eval_vpcr(handle, 1, &val);
2447be193e3SBarnabás Pőcze 		if (err)
2457be193e3SBarnabás Pőcze 			return err;
246ff36b0d9SBarnabás Pőcze 		if (val == 0)
247ff36b0d9SBarnabás Pőcze 			return eval_vpcr(handle, 0, data);
24857ac3b05SIke Panhc 	}
249654324c4SBarnabás Pőcze 	acpi_handle_err(handle, "timeout in %s\n", __func__);
2507be193e3SBarnabás Pőcze 	return -ETIMEDOUT;
25157ac3b05SIke Panhc }
25257ac3b05SIke Panhc 
253ff36b0d9SBarnabás Pőcze static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long data)
25457ac3b05SIke Panhc {
255ff36b0d9SBarnabás Pőcze 	unsigned long end_jiffies, val;
256ff36b0d9SBarnabás Pőcze 	int err;
25757ac3b05SIke Panhc 
258ff36b0d9SBarnabás Pőcze 	err = eval_vpcw(handle, 0, data);
2597be193e3SBarnabás Pőcze 	if (err)
2607be193e3SBarnabás Pőcze 		return err;
261ff36b0d9SBarnabás Pőcze 	err = eval_vpcw(handle, 1, cmd);
2627be193e3SBarnabás Pőcze 	if (err)
2637be193e3SBarnabás Pőcze 		return err;
26457ac3b05SIke Panhc 
26540e0447dSBarnabás Pőcze 	end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1;
26640e0447dSBarnabás Pőcze 
26740e0447dSBarnabás Pőcze 	while (time_before(jiffies, end_jiffies)) {
26857ac3b05SIke Panhc 		schedule();
269ff36b0d9SBarnabás Pőcze 		err = eval_vpcr(handle, 1, &val);
2707be193e3SBarnabás Pőcze 		if (err)
2717be193e3SBarnabás Pőcze 			return err;
27257ac3b05SIke Panhc 		if (val == 0)
27357ac3b05SIke Panhc 			return 0;
27457ac3b05SIke Panhc 	}
275654324c4SBarnabás Pőcze 	acpi_handle_err(handle, "timeout in %s\n", __func__);
2767be193e3SBarnabás Pőcze 	return -ETIMEDOUT;
27757ac3b05SIke Panhc }
27857ac3b05SIke Panhc 
279a4b5a279SIke Panhc /*
280773e3206SIke Panhc  * debugfs
281773e3206SIke Panhc  */
282773e3206SIke Panhc static int debugfs_status_show(struct seq_file *s, void *data)
283773e3206SIke Panhc {
284331e0ea2SZhang Rui 	struct ideapad_private *priv = s->private;
285773e3206SIke Panhc 	unsigned long value;
286773e3206SIke Panhc 
287331e0ea2SZhang Rui 	if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value))
288*7553390dSBarnabás Pőcze 		seq_printf(s, "Backlight max:  %lu\n", value);
289331e0ea2SZhang Rui 	if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value))
290*7553390dSBarnabás Pőcze 		seq_printf(s, "Backlight now:  %lu\n", value);
291331e0ea2SZhang Rui 	if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value))
292*7553390dSBarnabás Pőcze 		seq_printf(s, "BL power value: %s (%lu)\n", value ? "on" : "off", value);
293ade50296SHao Wei Tee 	seq_puts(s, "=====================\n");
294ade50296SHao Wei Tee 
295*7553390dSBarnabás Pőcze 	if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value))
296*7553390dSBarnabás Pőcze 		seq_printf(s, "Radio status: %s (%lu)\n", value ? "on" : "off", value);
297*7553390dSBarnabás Pőcze 	if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value))
298*7553390dSBarnabás Pőcze 		seq_printf(s, "Wifi status:  %s (%lu)\n", value ? "on" : "off", value);
299*7553390dSBarnabás Pőcze 	if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value))
300*7553390dSBarnabás Pőcze 		seq_printf(s, "BT status:    %s (%lu)\n", value ? "on" : "off", value);
301*7553390dSBarnabás Pőcze 	if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value))
302*7553390dSBarnabás Pőcze 		seq_printf(s, "3G status:    %s (%lu)\n", value ? "on" : "off", value);
303*7553390dSBarnabás Pőcze 	seq_puts(s, "=====================\n");
304*7553390dSBarnabás Pőcze 
305*7553390dSBarnabás Pőcze 	if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value))
306*7553390dSBarnabás Pőcze 		seq_printf(s, "Touchpad status: %s (%lu)\n", value ? "on" : "off", value);
307*7553390dSBarnabás Pőcze 	if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value))
308*7553390dSBarnabás Pőcze 		seq_printf(s, "Camera status:   %s (%lu)\n", value ? "on" : "off", value);
309*7553390dSBarnabás Pőcze 	seq_puts(s, "=====================\n");
310*7553390dSBarnabás Pőcze 
311*7553390dSBarnabás Pőcze 	if (!eval_gbmd(priv->adev->handle, &value))
312*7553390dSBarnabás Pőcze 		seq_printf(s, "GBMD: %#010lx\n", value);
313*7553390dSBarnabás Pőcze 	if (!eval_hals(priv->adev->handle, &value))
314*7553390dSBarnabás Pőcze 		seq_printf(s, "HALS: %#010lx\n", value);
315773e3206SIke Panhc 
316773e3206SIke Panhc 	return 0;
317773e3206SIke Panhc }
318334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_status);
319773e3206SIke Panhc 
320773e3206SIke Panhc static int debugfs_cfg_show(struct seq_file *s, void *data)
321773e3206SIke Panhc {
322331e0ea2SZhang Rui 	struct ideapad_private *priv = s->private;
323331e0ea2SZhang Rui 
324773e3206SIke Panhc 	seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ",
325331e0ea2SZhang Rui 		   priv->cfg);
3260b765671SBarnabás Pőcze 	if (test_bit(CFG_CAP_BT_BIT, &priv->cfg))
327773e3206SIke Panhc 		seq_printf(s, "Bluetooth ");
3280b765671SBarnabás Pőcze 	if (test_bit(CFG_CAP_3G_BIT, &priv->cfg))
329773e3206SIke Panhc 		seq_printf(s, "3G ");
3300b765671SBarnabás Pőcze 	if (test_bit(CFG_CAP_WIFI_BIT, &priv->cfg))
331773e3206SIke Panhc 		seq_printf(s, "Wireless ");
3320b765671SBarnabás Pőcze 	if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg))
333773e3206SIke Panhc 		seq_printf(s, "Camera ");
334b3ed1b7fSBarnabás Pőcze 	if (test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg))
335b3ed1b7fSBarnabás Pőcze 		seq_printf(s, "Touchpad ");
336773e3206SIke Panhc 	seq_printf(s, "\nGraphic: ");
337331e0ea2SZhang Rui 	switch ((priv->cfg)&0x700) {
338773e3206SIke Panhc 	case 0x100:
339773e3206SIke Panhc 		seq_printf(s, "Intel");
340773e3206SIke Panhc 		break;
341773e3206SIke Panhc 	case 0x200:
342773e3206SIke Panhc 		seq_printf(s, "ATI");
343773e3206SIke Panhc 		break;
344773e3206SIke Panhc 	case 0x300:
345773e3206SIke Panhc 		seq_printf(s, "Nvidia");
346773e3206SIke Panhc 		break;
347773e3206SIke Panhc 	case 0x400:
348773e3206SIke Panhc 		seq_printf(s, "Intel and ATI");
349773e3206SIke Panhc 		break;
350773e3206SIke Panhc 	case 0x500:
351773e3206SIke Panhc 		seq_printf(s, "Intel and Nvidia");
352773e3206SIke Panhc 		break;
353773e3206SIke Panhc 	}
354773e3206SIke Panhc 	seq_printf(s, "\n");
355e1a39a44SBarnabás Pőcze 
356773e3206SIke Panhc 	return 0;
357773e3206SIke Panhc }
358334c4efdSAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(debugfs_cfg);
359773e3206SIke Panhc 
36017f1bf38SGreg Kroah-Hartman static void ideapad_debugfs_init(struct ideapad_private *priv)
361773e3206SIke Panhc {
36217f1bf38SGreg Kroah-Hartman 	struct dentry *dir;
363773e3206SIke Panhc 
36417f1bf38SGreg Kroah-Hartman 	dir = debugfs_create_dir("ideapad", NULL);
36517f1bf38SGreg Kroah-Hartman 	priv->debug = dir;
366773e3206SIke Panhc 
36717f1bf38SGreg Kroah-Hartman 	debugfs_create_file("cfg", S_IRUGO, dir, priv, &debugfs_cfg_fops);
36817f1bf38SGreg Kroah-Hartman 	debugfs_create_file("status", S_IRUGO, dir, priv, &debugfs_status_fops);
369773e3206SIke Panhc }
370773e3206SIke Panhc 
371773e3206SIke Panhc static void ideapad_debugfs_exit(struct ideapad_private *priv)
372773e3206SIke Panhc {
373773e3206SIke Panhc 	debugfs_remove_recursive(priv->debug);
374773e3206SIke Panhc 	priv->debug = NULL;
375773e3206SIke Panhc }
376773e3206SIke Panhc 
377773e3206SIke Panhc /*
3783371f481SIke Panhc  * sysfs
379a4b5a279SIke Panhc  */
38057ac3b05SIke Panhc static ssize_t show_ideapad_cam(struct device *dev,
38157ac3b05SIke Panhc 				struct device_attribute *attr,
38257ac3b05SIke Panhc 				char *buf)
38357ac3b05SIke Panhc {
38457ac3b05SIke Panhc 	unsigned long result;
385331e0ea2SZhang Rui 	struct ideapad_private *priv = dev_get_drvdata(dev);
386c81f2410SBarnabás Pőcze 	int err;
38757ac3b05SIke Panhc 
388c81f2410SBarnabás Pőcze 	err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result);
389c81f2410SBarnabás Pőcze 	if (err)
390c81f2410SBarnabás Pőcze 		return err;
39100641c08SBarnabás Pőcze 	return sysfs_emit(buf, "%d\n", !!result);
39257ac3b05SIke Panhc }
39357ac3b05SIke Panhc 
39457ac3b05SIke Panhc static ssize_t store_ideapad_cam(struct device *dev,
39557ac3b05SIke Panhc 				 struct device_attribute *attr,
39657ac3b05SIke Panhc 				 const char *buf, size_t count)
39757ac3b05SIke Panhc {
398331e0ea2SZhang Rui 	struct ideapad_private *priv = dev_get_drvdata(dev);
39900641c08SBarnabás Pőcze 	bool state;
40000641c08SBarnabás Pőcze 	int ret;
40157ac3b05SIke Panhc 
40200641c08SBarnabás Pőcze 	ret = kstrtobool(buf, &state);
40300641c08SBarnabás Pőcze 	if (ret)
40400641c08SBarnabás Pőcze 		return ret;
405331e0ea2SZhang Rui 	ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state);
4067be193e3SBarnabás Pőcze 	if (ret)
4077be193e3SBarnabás Pőcze 		return ret;
40857ac3b05SIke Panhc 	return count;
40957ac3b05SIke Panhc }
41057ac3b05SIke Panhc 
41157ac3b05SIke Panhc static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
41257ac3b05SIke Panhc 
4130c7bbeb9SMaxim Mikityanskiy static ssize_t show_ideapad_fan(struct device *dev,
4140c7bbeb9SMaxim Mikityanskiy 				struct device_attribute *attr,
4150c7bbeb9SMaxim Mikityanskiy 				char *buf)
4160c7bbeb9SMaxim Mikityanskiy {
4170c7bbeb9SMaxim Mikityanskiy 	unsigned long result;
418331e0ea2SZhang Rui 	struct ideapad_private *priv = dev_get_drvdata(dev);
419c81f2410SBarnabás Pőcze 	int err;
4200c7bbeb9SMaxim Mikityanskiy 
421c81f2410SBarnabás Pőcze 	err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result);
422c81f2410SBarnabás Pőcze 	if (err)
423c81f2410SBarnabás Pőcze 		return err;
424d6b50889SBarnabás Pőcze 	return sysfs_emit(buf, "%lu\n", result);
4250c7bbeb9SMaxim Mikityanskiy }
4260c7bbeb9SMaxim Mikityanskiy 
4270c7bbeb9SMaxim Mikityanskiy static ssize_t store_ideapad_fan(struct device *dev,
4280c7bbeb9SMaxim Mikityanskiy 				 struct device_attribute *attr,
4290c7bbeb9SMaxim Mikityanskiy 				 const char *buf, size_t count)
4300c7bbeb9SMaxim Mikityanskiy {
431331e0ea2SZhang Rui 	struct ideapad_private *priv = dev_get_drvdata(dev);
43200641c08SBarnabás Pőcze 	unsigned int state;
43300641c08SBarnabás Pőcze 	int ret;
4340c7bbeb9SMaxim Mikityanskiy 
43500641c08SBarnabás Pőcze 	ret = kstrtouint(buf, 0, &state);
43600641c08SBarnabás Pőcze 	if (ret)
43700641c08SBarnabás Pőcze 		return ret;
43800641c08SBarnabás Pőcze 	if (state > 4 || state == 3)
4390c7bbeb9SMaxim Mikityanskiy 		return -EINVAL;
440331e0ea2SZhang Rui 	ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state);
4417be193e3SBarnabás Pőcze 	if (ret)
4427be193e3SBarnabás Pőcze 		return ret;
4430c7bbeb9SMaxim Mikityanskiy 	return count;
4440c7bbeb9SMaxim Mikityanskiy }
4450c7bbeb9SMaxim Mikityanskiy 
4460c7bbeb9SMaxim Mikityanskiy static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan);
4470c7bbeb9SMaxim Mikityanskiy 
44836ac0d43SRitesh Raj Sarraf static ssize_t touchpad_show(struct device *dev,
44936ac0d43SRitesh Raj Sarraf 			     struct device_attribute *attr,
45036ac0d43SRitesh Raj Sarraf 			     char *buf)
45136ac0d43SRitesh Raj Sarraf {
45236ac0d43SRitesh Raj Sarraf 	struct ideapad_private *priv = dev_get_drvdata(dev);
45336ac0d43SRitesh Raj Sarraf 	unsigned long result;
454c81f2410SBarnabás Pőcze 	int err;
45536ac0d43SRitesh Raj Sarraf 
456c81f2410SBarnabás Pőcze 	err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result);
457c81f2410SBarnabás Pőcze 	if (err)
458c81f2410SBarnabás Pőcze 		return err;
45900641c08SBarnabás Pőcze 	return sysfs_emit(buf, "%d\n", !!result);
46036ac0d43SRitesh Raj Sarraf }
46136ac0d43SRitesh Raj Sarraf 
46246936fd6SArnd Bergmann /* Switch to RO for now: It might be revisited in the future */
46346936fd6SArnd Bergmann static ssize_t __maybe_unused touchpad_store(struct device *dev,
46436ac0d43SRitesh Raj Sarraf 					     struct device_attribute *attr,
46536ac0d43SRitesh Raj Sarraf 					     const char *buf, size_t count)
46636ac0d43SRitesh Raj Sarraf {
46736ac0d43SRitesh Raj Sarraf 	struct ideapad_private *priv = dev_get_drvdata(dev);
46836ac0d43SRitesh Raj Sarraf 	bool state;
46936ac0d43SRitesh Raj Sarraf 	int ret;
47036ac0d43SRitesh Raj Sarraf 
47136ac0d43SRitesh Raj Sarraf 	ret = kstrtobool(buf, &state);
47236ac0d43SRitesh Raj Sarraf 	if (ret)
47336ac0d43SRitesh Raj Sarraf 		return ret;
47436ac0d43SRitesh Raj Sarraf 
47536ac0d43SRitesh Raj Sarraf 	ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state);
4767be193e3SBarnabás Pőcze 	if (ret)
4777be193e3SBarnabás Pőcze 		return ret;
47836ac0d43SRitesh Raj Sarraf 	return count;
47936ac0d43SRitesh Raj Sarraf }
48036ac0d43SRitesh Raj Sarraf 
4817f363145SAndy Shevchenko static DEVICE_ATTR_RO(touchpad);
48236ac0d43SRitesh Raj Sarraf 
483ade50296SHao Wei Tee static ssize_t conservation_mode_show(struct device *dev,
484ade50296SHao Wei Tee 				struct device_attribute *attr,
485ade50296SHao Wei Tee 				char *buf)
486ade50296SHao Wei Tee {
487ade50296SHao Wei Tee 	struct ideapad_private *priv = dev_get_drvdata(dev);
488ade50296SHao Wei Tee 	unsigned long result;
489c81f2410SBarnabás Pőcze 	int err;
490ade50296SHao Wei Tee 
491ff36b0d9SBarnabás Pőcze 	err = eval_gbmd(priv->adev->handle, &result);
492c81f2410SBarnabás Pőcze 	if (err)
493c81f2410SBarnabás Pőcze 		return err;
4940b765671SBarnabás Pőcze 	return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result));
495ade50296SHao Wei Tee }
496ade50296SHao Wei Tee 
497ade50296SHao Wei Tee static ssize_t conservation_mode_store(struct device *dev,
498ade50296SHao Wei Tee 				 struct device_attribute *attr,
499ade50296SHao Wei Tee 				 const char *buf, size_t count)
500ade50296SHao Wei Tee {
501ade50296SHao Wei Tee 	struct ideapad_private *priv = dev_get_drvdata(dev);
502ade50296SHao Wei Tee 	bool state;
503ade50296SHao Wei Tee 	int ret;
504ade50296SHao Wei Tee 
505ade50296SHao Wei Tee 	ret = kstrtobool(buf, &state);
506ade50296SHao Wei Tee 	if (ret)
507ade50296SHao Wei Tee 		return ret;
508ade50296SHao Wei Tee 
509ff36b0d9SBarnabás Pőcze 	ret = exec_smbc(priv->adev->handle, state ? SMBC_CONSERVATION_ON : SMBC_CONSERVATION_OFF);
5107be193e3SBarnabás Pőcze 	if (ret)
5117be193e3SBarnabás Pőcze 		return ret;
512ade50296SHao Wei Tee 	return count;
513ade50296SHao Wei Tee }
514ade50296SHao Wei Tee 
515ade50296SHao Wei Tee static DEVICE_ATTR_RW(conservation_mode);
516ade50296SHao Wei Tee 
51740760717SOleg Keri static ssize_t fn_lock_show(struct device *dev,
51840760717SOleg Keri 			    struct device_attribute *attr,
51940760717SOleg Keri 			    char *buf)
52040760717SOleg Keri {
52140760717SOleg Keri 	struct ideapad_private *priv = dev_get_drvdata(dev);
522ff36b0d9SBarnabás Pőcze 	unsigned long hals;
523ff36b0d9SBarnabás Pőcze 	int fail = eval_hals(priv->adev->handle, &hals);
52440760717SOleg Keri 
52540760717SOleg Keri 	if (fail)
526c81f2410SBarnabás Pőcze 		return fail;
52740760717SOleg Keri 
528ff36b0d9SBarnabás Pőcze 	return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals));
52940760717SOleg Keri }
53040760717SOleg Keri 
53140760717SOleg Keri static ssize_t fn_lock_store(struct device *dev,
53240760717SOleg Keri 			     struct device_attribute *attr,
53340760717SOleg Keri 			     const char *buf, size_t count)
53440760717SOleg Keri {
53540760717SOleg Keri 	struct ideapad_private *priv = dev_get_drvdata(dev);
53640760717SOleg Keri 	bool state;
53740760717SOleg Keri 	int ret;
53840760717SOleg Keri 
53940760717SOleg Keri 	ret = kstrtobool(buf, &state);
54040760717SOleg Keri 	if (ret)
54140760717SOleg Keri 		return ret;
54240760717SOleg Keri 
543ff36b0d9SBarnabás Pőcze 	ret = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
5447be193e3SBarnabás Pőcze 	if (ret)
5457be193e3SBarnabás Pőcze 		return ret;
54640760717SOleg Keri 	return count;
54740760717SOleg Keri }
54840760717SOleg Keri 
54940760717SOleg Keri static DEVICE_ATTR_RW(fn_lock);
55040760717SOleg Keri 
55140760717SOleg Keri 
5523371f481SIke Panhc static struct attribute *ideapad_attributes[] = {
5533371f481SIke Panhc 	&dev_attr_camera_power.attr,
5540c7bbeb9SMaxim Mikityanskiy 	&dev_attr_fan_mode.attr,
55536ac0d43SRitesh Raj Sarraf 	&dev_attr_touchpad.attr,
556ade50296SHao Wei Tee 	&dev_attr_conservation_mode.attr,
55740760717SOleg Keri 	&dev_attr_fn_lock.attr,
5583371f481SIke Panhc 	NULL
5593371f481SIke Panhc };
5603371f481SIke Panhc 
561587a1f16SAl Viro static umode_t ideapad_is_visible(struct kobject *kobj,
562a84511f7SIke Panhc 				 struct attribute *attr,
563a84511f7SIke Panhc 				 int idx)
564a84511f7SIke Panhc {
565708086b2SBarnabás Pőcze 	struct device *dev = kobj_to_dev(kobj);
566a84511f7SIke Panhc 	struct ideapad_private *priv = dev_get_drvdata(dev);
5671c59de4aSBarnabás Pőcze 	bool supported = true;
568a84511f7SIke Panhc 
569a84511f7SIke Panhc 	if (attr == &dev_attr_camera_power.attr)
5700b765671SBarnabás Pőcze 		supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg);
5711c59de4aSBarnabás Pőcze 	else if (attr == &dev_attr_conservation_mode.attr)
5721c59de4aSBarnabás Pőcze 		supported = priv->features.conservation_mode;
5731c59de4aSBarnabás Pőcze 	else if (attr == &dev_attr_fan_mode.attr)
5741c59de4aSBarnabás Pőcze 		supported = priv->features.fan_mode;
5751c59de4aSBarnabás Pőcze 	else if (attr == &dev_attr_fn_lock.attr)
5761c59de4aSBarnabás Pőcze 		supported = priv->features.fn_lock;
5771c59de4aSBarnabás Pőcze 	else if (attr == &dev_attr_touchpad.attr)
578b3ed1b7fSBarnabás Pőcze 		supported = priv->features.touchpad_ctrl_via_ec &&
579b3ed1b7fSBarnabás Pőcze 			    test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg);
580a84511f7SIke Panhc 
581a84511f7SIke Panhc 	return supported ? attr->mode : 0;
582a84511f7SIke Panhc }
583a84511f7SIke Panhc 
58449458e83SMathias Krause static const struct attribute_group ideapad_attribute_group = {
585a84511f7SIke Panhc 	.is_visible = ideapad_is_visible,
5863371f481SIke Panhc 	.attrs = ideapad_attributes
5873371f481SIke Panhc };
5883371f481SIke Panhc 
589a4b5a279SIke Panhc /*
590eabe5339SJiaxun Yang  * DYTC Platform profile
591eabe5339SJiaxun Yang  */
592eabe5339SJiaxun Yang #define DYTC_CMD_QUERY        0 /* To get DYTC status - enable/revision */
593eabe5339SJiaxun Yang #define DYTC_CMD_SET          1 /* To enable/disable IC function mode */
594eabe5339SJiaxun Yang #define DYTC_CMD_GET          2 /* To get current IC function and mode */
595eabe5339SJiaxun Yang #define DYTC_CMD_RESET    0x1ff /* To reset back to default */
596eabe5339SJiaxun Yang 
597eabe5339SJiaxun Yang #define DYTC_QUERY_ENABLE_BIT 8  /* Bit        8 - 0 = disabled, 1 = enabled */
598eabe5339SJiaxun Yang #define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */
599eabe5339SJiaxun Yang #define DYTC_QUERY_REV_BIT    28 /* Bits 28 - 31 - revision */
600eabe5339SJiaxun Yang 
601eabe5339SJiaxun Yang #define DYTC_GET_FUNCTION_BIT 8  /* Bits  8-11 - function setting */
602eabe5339SJiaxun Yang #define DYTC_GET_MODE_BIT     12 /* Bits 12-15 - mode setting */
603eabe5339SJiaxun Yang 
604eabe5339SJiaxun Yang #define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */
605eabe5339SJiaxun Yang #define DYTC_SET_MODE_BIT     16 /* Bits 16-19 - mode setting */
606eabe5339SJiaxun Yang #define DYTC_SET_VALID_BIT    20 /* Bit     20 - 1 = on, 0 = off */
607eabe5339SJiaxun Yang 
608eabe5339SJiaxun Yang #define DYTC_FUNCTION_STD     0  /* Function = 0, standard mode */
609eabe5339SJiaxun Yang #define DYTC_FUNCTION_CQL     1  /* Function = 1, lap mode */
610eabe5339SJiaxun Yang #define DYTC_FUNCTION_MMC     11 /* Function = 11, desk mode */
611eabe5339SJiaxun Yang 
612eabe5339SJiaxun Yang #define DYTC_MODE_PERFORM     2  /* High power mode aka performance */
613eabe5339SJiaxun Yang #define DYTC_MODE_LOW_POWER       3  /* Low power mode aka quiet */
614eabe5339SJiaxun Yang #define DYTC_MODE_BALANCE   0xF  /* Default mode aka balanced */
615eabe5339SJiaxun Yang 
616eabe5339SJiaxun Yang #define DYTC_SET_COMMAND(function, mode, on) \
617eabe5339SJiaxun Yang 	(DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \
618eabe5339SJiaxun Yang 	 (mode) << DYTC_SET_MODE_BIT | \
619eabe5339SJiaxun Yang 	 (on) << DYTC_SET_VALID_BIT)
620eabe5339SJiaxun Yang 
621eabe5339SJiaxun Yang #define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0)
622eabe5339SJiaxun Yang 
623eabe5339SJiaxun Yang #define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1)
624eabe5339SJiaxun Yang 
625eabe5339SJiaxun Yang static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile)
626eabe5339SJiaxun Yang {
627eabe5339SJiaxun Yang 	switch (dytcmode) {
628eabe5339SJiaxun Yang 	case DYTC_MODE_LOW_POWER:
629eabe5339SJiaxun Yang 		*profile = PLATFORM_PROFILE_LOW_POWER;
630eabe5339SJiaxun Yang 		break;
631eabe5339SJiaxun Yang 	case DYTC_MODE_BALANCE:
632eabe5339SJiaxun Yang 		*profile =  PLATFORM_PROFILE_BALANCED;
633eabe5339SJiaxun Yang 		break;
634eabe5339SJiaxun Yang 	case DYTC_MODE_PERFORM:
635eabe5339SJiaxun Yang 		*profile =  PLATFORM_PROFILE_PERFORMANCE;
636eabe5339SJiaxun Yang 		break;
637eabe5339SJiaxun Yang 	default: /* Unknown mode */
638eabe5339SJiaxun Yang 		return -EINVAL;
639eabe5339SJiaxun Yang 	}
640eabe5339SJiaxun Yang 	return 0;
641eabe5339SJiaxun Yang }
642eabe5339SJiaxun Yang 
643eabe5339SJiaxun Yang static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode)
644eabe5339SJiaxun Yang {
645eabe5339SJiaxun Yang 	switch (profile) {
646eabe5339SJiaxun Yang 	case PLATFORM_PROFILE_LOW_POWER:
647eabe5339SJiaxun Yang 		*perfmode = DYTC_MODE_LOW_POWER;
648eabe5339SJiaxun Yang 		break;
649eabe5339SJiaxun Yang 	case PLATFORM_PROFILE_BALANCED:
650eabe5339SJiaxun Yang 		*perfmode = DYTC_MODE_BALANCE;
651eabe5339SJiaxun Yang 		break;
652eabe5339SJiaxun Yang 	case PLATFORM_PROFILE_PERFORMANCE:
653eabe5339SJiaxun Yang 		*perfmode = DYTC_MODE_PERFORM;
654eabe5339SJiaxun Yang 		break;
655eabe5339SJiaxun Yang 	default: /* Unknown profile */
656eabe5339SJiaxun Yang 		return -EOPNOTSUPP;
657eabe5339SJiaxun Yang 	}
658eabe5339SJiaxun Yang 	return 0;
659eabe5339SJiaxun Yang }
660eabe5339SJiaxun Yang 
661eabe5339SJiaxun Yang /*
662eabe5339SJiaxun Yang  * dytc_profile_get: Function to register with platform_profile
663eabe5339SJiaxun Yang  * handler. Returns current platform profile.
664eabe5339SJiaxun Yang  */
665eabe5339SJiaxun Yang int dytc_profile_get(struct platform_profile_handler *pprof,
666eabe5339SJiaxun Yang 			enum platform_profile_option *profile)
667eabe5339SJiaxun Yang {
668eabe5339SJiaxun Yang 	struct ideapad_dytc_priv *dytc;
669eabe5339SJiaxun Yang 
670eabe5339SJiaxun Yang 	dytc = container_of(pprof, struct ideapad_dytc_priv, pprof);
671eabe5339SJiaxun Yang 	*profile = dytc->current_profile;
672eabe5339SJiaxun Yang 	return 0;
673eabe5339SJiaxun Yang }
674eabe5339SJiaxun Yang 
675eabe5339SJiaxun Yang /*
676eabe5339SJiaxun Yang  * Helper function - check if we are in CQL mode and if we are
677eabe5339SJiaxun Yang  *  -  disable CQL,
678eabe5339SJiaxun Yang  *  - run the command
679eabe5339SJiaxun Yang  *  - enable CQL
680eabe5339SJiaxun Yang  *  If not in CQL mode, just run the command
681eabe5339SJiaxun Yang  */
682ff36b0d9SBarnabás Pőcze int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, unsigned long *output)
683eabe5339SJiaxun Yang {
684ff36b0d9SBarnabás Pőcze 	int err, cmd_err, cur_funcmode;
685eabe5339SJiaxun Yang 
686eabe5339SJiaxun Yang 	/* Determine if we are in CQL mode. This alters the commands we do */
687ff36b0d9SBarnabás Pőcze 	err = eval_dytc(priv->adev->handle, DYTC_CMD_GET, output);
688eabe5339SJiaxun Yang 	if (err)
689eabe5339SJiaxun Yang 		return err;
690eabe5339SJiaxun Yang 
691eabe5339SJiaxun Yang 	cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF;
692eabe5339SJiaxun Yang 	/* Check if we're OK to return immediately */
693ff36b0d9SBarnabás Pőcze 	if (cmd == DYTC_CMD_GET && cur_funcmode != DYTC_FUNCTION_CQL)
694eabe5339SJiaxun Yang 		return 0;
695eabe5339SJiaxun Yang 
696eabe5339SJiaxun Yang 	if (cur_funcmode == DYTC_FUNCTION_CQL) {
697ff36b0d9SBarnabás Pőcze 		err = eval_dytc(priv->adev->handle, DYTC_DISABLE_CQL, NULL);
698eabe5339SJiaxun Yang 		if (err)
699eabe5339SJiaxun Yang 			return err;
700eabe5339SJiaxun Yang 	}
701eabe5339SJiaxun Yang 
702ff36b0d9SBarnabás Pőcze 	cmd_err = eval_dytc(priv->adev->handle, cmd, output);
703eabe5339SJiaxun Yang 	/* Check return condition after we've restored CQL state */
704eabe5339SJiaxun Yang 
705eabe5339SJiaxun Yang 	if (cur_funcmode == DYTC_FUNCTION_CQL) {
706ff36b0d9SBarnabás Pőcze 		err = eval_dytc(priv->adev->handle, DYTC_ENABLE_CQL, NULL);
707eabe5339SJiaxun Yang 		if (err)
708eabe5339SJiaxun Yang 			return err;
709eabe5339SJiaxun Yang 	}
710eabe5339SJiaxun Yang 
711eabe5339SJiaxun Yang 	return cmd_err;
712eabe5339SJiaxun Yang }
713eabe5339SJiaxun Yang 
714eabe5339SJiaxun Yang /*
715eabe5339SJiaxun Yang  * dytc_profile_set: Function to register with platform_profile
716eabe5339SJiaxun Yang  * handler. Sets current platform profile.
717eabe5339SJiaxun Yang  */
718eabe5339SJiaxun Yang int dytc_profile_set(struct platform_profile_handler *pprof,
719eabe5339SJiaxun Yang 			enum platform_profile_option profile)
720eabe5339SJiaxun Yang {
721eabe5339SJiaxun Yang 	struct ideapad_dytc_priv *dytc;
722eabe5339SJiaxun Yang 	struct ideapad_private *priv;
723eabe5339SJiaxun Yang 	int err;
724eabe5339SJiaxun Yang 
725eabe5339SJiaxun Yang 	dytc = container_of(pprof, struct ideapad_dytc_priv, pprof);
726eabe5339SJiaxun Yang 	priv = dytc->priv;
727eabe5339SJiaxun Yang 
728eabe5339SJiaxun Yang 	err = mutex_lock_interruptible(&dytc->mutex);
729eabe5339SJiaxun Yang 	if (err)
730eabe5339SJiaxun Yang 		return err;
731eabe5339SJiaxun Yang 
732eabe5339SJiaxun Yang 	if (profile == PLATFORM_PROFILE_BALANCED) {
733eabe5339SJiaxun Yang 		/* To get back to balanced mode we just issue a reset command */
734ff36b0d9SBarnabás Pőcze 		err = eval_dytc(priv->adev->handle, DYTC_CMD_RESET, NULL);
735eabe5339SJiaxun Yang 		if (err)
736eabe5339SJiaxun Yang 			goto unlock;
737eabe5339SJiaxun Yang 	} else {
738eabe5339SJiaxun Yang 		int perfmode;
739eabe5339SJiaxun Yang 
740eabe5339SJiaxun Yang 		err = convert_profile_to_dytc(profile, &perfmode);
741eabe5339SJiaxun Yang 		if (err)
742eabe5339SJiaxun Yang 			goto unlock;
743eabe5339SJiaxun Yang 
744eabe5339SJiaxun Yang 		/* Determine if we are in CQL mode. This alters the commands we do */
745eabe5339SJiaxun Yang 		err = dytc_cql_command(priv,
746eabe5339SJiaxun Yang 				DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1),
747ff36b0d9SBarnabás Pőcze 				NULL);
748eabe5339SJiaxun Yang 		if (err)
749eabe5339SJiaxun Yang 			goto unlock;
750eabe5339SJiaxun Yang 	}
751eabe5339SJiaxun Yang 	/* Success - update current profile */
752eabe5339SJiaxun Yang 	dytc->current_profile = profile;
753eabe5339SJiaxun Yang unlock:
754eabe5339SJiaxun Yang 	mutex_unlock(&dytc->mutex);
755eabe5339SJiaxun Yang 	return err;
756eabe5339SJiaxun Yang }
757eabe5339SJiaxun Yang 
758eabe5339SJiaxun Yang static void dytc_profile_refresh(struct ideapad_private *priv)
759eabe5339SJiaxun Yang {
760eabe5339SJiaxun Yang 	enum platform_profile_option profile;
761ff36b0d9SBarnabás Pőcze 	unsigned long output;
762ff36b0d9SBarnabás Pőcze 	int err, perfmode;
763eabe5339SJiaxun Yang 
764eabe5339SJiaxun Yang 	mutex_lock(&priv->dytc->mutex);
765eabe5339SJiaxun Yang 	err = dytc_cql_command(priv, DYTC_CMD_GET, &output);
766eabe5339SJiaxun Yang 	mutex_unlock(&priv->dytc->mutex);
767eabe5339SJiaxun Yang 	if (err)
768eabe5339SJiaxun Yang 		return;
769eabe5339SJiaxun Yang 
770eabe5339SJiaxun Yang 	perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF;
771eabe5339SJiaxun Yang 	convert_dytc_to_profile(perfmode, &profile);
772eabe5339SJiaxun Yang 	if (profile != priv->dytc->current_profile) {
773eabe5339SJiaxun Yang 		priv->dytc->current_profile = profile;
774eabe5339SJiaxun Yang 		platform_profile_notify();
775eabe5339SJiaxun Yang 	}
776eabe5339SJiaxun Yang }
777eabe5339SJiaxun Yang 
778eabe5339SJiaxun Yang static int ideapad_dytc_profile_init(struct ideapad_private *priv)
779eabe5339SJiaxun Yang {
780ff36b0d9SBarnabás Pőcze 	int err, dytc_version;
781ff36b0d9SBarnabás Pőcze 	unsigned long output;
782eabe5339SJiaxun Yang 
7831c59de4aSBarnabás Pőcze 	if (!priv->features.dytc)
7841c59de4aSBarnabás Pőcze 		return -ENODEV;
7851c59de4aSBarnabás Pőcze 
786ff36b0d9SBarnabás Pőcze 	err = eval_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output);
787eabe5339SJiaxun Yang 	/* For all other errors we can flag the failure */
788eabe5339SJiaxun Yang 	if (err)
789eabe5339SJiaxun Yang 		return err;
790eabe5339SJiaxun Yang 
791eabe5339SJiaxun Yang 	/* Check DYTC is enabled and supports mode setting */
792eabe5339SJiaxun Yang 	if (!(output & BIT(DYTC_QUERY_ENABLE_BIT)))
793eabe5339SJiaxun Yang 		return -ENODEV;
794eabe5339SJiaxun Yang 
795eabe5339SJiaxun Yang 	dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF;
796eabe5339SJiaxun Yang 	if (dytc_version < 5)
797eabe5339SJiaxun Yang 		return -ENODEV;
798eabe5339SJiaxun Yang 
799eabe5339SJiaxun Yang 	priv->dytc = kzalloc(sizeof(struct ideapad_dytc_priv), GFP_KERNEL);
800eabe5339SJiaxun Yang 	if (!priv->dytc)
801eabe5339SJiaxun Yang 		return -ENOMEM;
802eabe5339SJiaxun Yang 
803eabe5339SJiaxun Yang 	mutex_init(&priv->dytc->mutex);
804eabe5339SJiaxun Yang 
805eabe5339SJiaxun Yang 	priv->dytc->priv = priv;
806eabe5339SJiaxun Yang 	priv->dytc->pprof.profile_get = dytc_profile_get;
807eabe5339SJiaxun Yang 	priv->dytc->pprof.profile_set = dytc_profile_set;
808eabe5339SJiaxun Yang 
809eabe5339SJiaxun Yang 	/* Setup supported modes */
810eabe5339SJiaxun Yang 	set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices);
811eabe5339SJiaxun Yang 	set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices);
812eabe5339SJiaxun Yang 	set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices);
813eabe5339SJiaxun Yang 
814eabe5339SJiaxun Yang 	/* Create platform_profile structure and register */
815eabe5339SJiaxun Yang 	err = platform_profile_register(&priv->dytc->pprof);
816eabe5339SJiaxun Yang 	if (err)
817eabe5339SJiaxun Yang 		goto mutex_destroy;
818eabe5339SJiaxun Yang 
819eabe5339SJiaxun Yang 	/* Ensure initial values are correct */
820eabe5339SJiaxun Yang 	dytc_profile_refresh(priv);
821eabe5339SJiaxun Yang 
822eabe5339SJiaxun Yang 	return 0;
823eabe5339SJiaxun Yang 
824eabe5339SJiaxun Yang mutex_destroy:
825eabe5339SJiaxun Yang 	mutex_destroy(&priv->dytc->mutex);
826eabe5339SJiaxun Yang 	kfree(priv->dytc);
827eabe5339SJiaxun Yang 	priv->dytc = NULL;
828eabe5339SJiaxun Yang 	return err;
829eabe5339SJiaxun Yang }
830eabe5339SJiaxun Yang 
831eabe5339SJiaxun Yang static void ideapad_dytc_profile_exit(struct ideapad_private *priv)
832eabe5339SJiaxun Yang {
833eabe5339SJiaxun Yang 	if (!priv->dytc)
834eabe5339SJiaxun Yang 		return;
835eabe5339SJiaxun Yang 
836eabe5339SJiaxun Yang 	platform_profile_remove();
837eabe5339SJiaxun Yang 	mutex_destroy(&priv->dytc->mutex);
838eabe5339SJiaxun Yang 	kfree(priv->dytc);
839eabe5339SJiaxun Yang 	priv->dytc = NULL;
840eabe5339SJiaxun Yang }
841eabe5339SJiaxun Yang 
842eabe5339SJiaxun Yang /*
843a4b5a279SIke Panhc  * Rfkill
844a4b5a279SIke Panhc  */
845c1f73658SIke Panhc struct ideapad_rfk_data {
846c1f73658SIke Panhc 	char *name;
847c1f73658SIke Panhc 	int cfgbit;
848c1f73658SIke Panhc 	int opcode;
849c1f73658SIke Panhc 	int type;
850c1f73658SIke Panhc };
851c1f73658SIke Panhc 
852b3d94d70SMathias Krause static const struct ideapad_rfk_data ideapad_rfk_data[] = {
8530b765671SBarnabás Pőcze 	{ "ideapad_wlan",      CFG_CAP_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN },
8540b765671SBarnabás Pőcze 	{ "ideapad_bluetooth", CFG_CAP_BT_BIT,   VPCCMD_W_BT,   RFKILL_TYPE_BLUETOOTH },
8550b765671SBarnabás Pőcze 	{ "ideapad_3g",        CFG_CAP_3G_BIT,   VPCCMD_W_3G,   RFKILL_TYPE_WWAN },
856c1f73658SIke Panhc };
857c1f73658SIke Panhc 
85857ac3b05SIke Panhc static int ideapad_rfk_set(void *data, bool blocked)
85957ac3b05SIke Panhc {
860331e0ea2SZhang Rui 	struct ideapad_rfk_priv *priv = data;
8614b200b46SArnd Bergmann 	int opcode = ideapad_rfk_data[priv->dev].opcode;
86257ac3b05SIke Panhc 
8634b200b46SArnd Bergmann 	return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked);
86457ac3b05SIke Panhc }
86557ac3b05SIke Panhc 
8663d59dfcdSBhumika Goyal static const struct rfkill_ops ideapad_rfk_ops = {
86757ac3b05SIke Panhc 	.set_block = ideapad_rfk_set,
86857ac3b05SIke Panhc };
86957ac3b05SIke Panhc 
870923de84aSIke Panhc static void ideapad_sync_rfk_state(struct ideapad_private *priv)
87157ac3b05SIke Panhc {
872ce363c2bSHans de Goede 	unsigned long hw_blocked = 0;
87357ac3b05SIke Panhc 	int i;
87457ac3b05SIke Panhc 
8751c59de4aSBarnabás Pőcze 	if (priv->features.hw_rfkill_switch) {
876331e0ea2SZhang Rui 		if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked))
87757ac3b05SIke Panhc 			return;
87857ac3b05SIke Panhc 		hw_blocked = !hw_blocked;
879ce363c2bSHans de Goede 	}
88057ac3b05SIke Panhc 
881c1f73658SIke Panhc 	for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
88257ac3b05SIke Panhc 		if (priv->rfk[i])
88357ac3b05SIke Panhc 			rfkill_set_hw_state(priv->rfk[i], hw_blocked);
88457ac3b05SIke Panhc }
88557ac3b05SIke Panhc 
88675a11f11SZhang Rui static int ideapad_register_rfkill(struct ideapad_private *priv, int dev)
88757ac3b05SIke Panhc {
88857ac3b05SIke Panhc 	int ret;
88957ac3b05SIke Panhc 	unsigned long sw_blocked;
89057ac3b05SIke Panhc 
891bfa97b7dSIke Panhc 	if (no_bt_rfkill &&
892bfa97b7dSIke Panhc 	    (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) {
893bfa97b7dSIke Panhc 		/* Force to enable bluetooth when no_bt_rfkill=1 */
894331e0ea2SZhang Rui 		write_ec_cmd(priv->adev->handle,
895bfa97b7dSIke Panhc 			     ideapad_rfk_data[dev].opcode, 1);
896bfa97b7dSIke Panhc 		return 0;
897bfa97b7dSIke Panhc 	}
898331e0ea2SZhang Rui 	priv->rfk_priv[dev].dev = dev;
899331e0ea2SZhang Rui 	priv->rfk_priv[dev].priv = priv;
900bfa97b7dSIke Panhc 
90175a11f11SZhang Rui 	priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name,
902b5c37b79SZhang Rui 				      &priv->platform_device->dev,
90375a11f11SZhang Rui 				      ideapad_rfk_data[dev].type,
90475a11f11SZhang Rui 				      &ideapad_rfk_ops,
905331e0ea2SZhang Rui 				      &priv->rfk_priv[dev]);
90657ac3b05SIke Panhc 	if (!priv->rfk[dev])
90757ac3b05SIke Panhc 		return -ENOMEM;
90857ac3b05SIke Panhc 
909331e0ea2SZhang Rui 	if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1,
91057ac3b05SIke Panhc 			 &sw_blocked)) {
91157ac3b05SIke Panhc 		rfkill_init_sw_state(priv->rfk[dev], 0);
91257ac3b05SIke Panhc 	} else {
91357ac3b05SIke Panhc 		sw_blocked = !sw_blocked;
91457ac3b05SIke Panhc 		rfkill_init_sw_state(priv->rfk[dev], sw_blocked);
91557ac3b05SIke Panhc 	}
91657ac3b05SIke Panhc 
91757ac3b05SIke Panhc 	ret = rfkill_register(priv->rfk[dev]);
91857ac3b05SIke Panhc 	if (ret) {
91957ac3b05SIke Panhc 		rfkill_destroy(priv->rfk[dev]);
92057ac3b05SIke Panhc 		return ret;
92157ac3b05SIke Panhc 	}
92257ac3b05SIke Panhc 	return 0;
92357ac3b05SIke Panhc }
92457ac3b05SIke Panhc 
92575a11f11SZhang Rui static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev)
92657ac3b05SIke Panhc {
92757ac3b05SIke Panhc 	if (!priv->rfk[dev])
92857ac3b05SIke Panhc 		return;
92957ac3b05SIke Panhc 
93057ac3b05SIke Panhc 	rfkill_unregister(priv->rfk[dev]);
93157ac3b05SIke Panhc 	rfkill_destroy(priv->rfk[dev]);
93257ac3b05SIke Panhc }
93357ac3b05SIke Panhc 
93498ee6919SIke Panhc /*
93598ee6919SIke Panhc  * Platform device
93698ee6919SIke Panhc  */
937b5c37b79SZhang Rui static int ideapad_sysfs_init(struct ideapad_private *priv)
93898ee6919SIke Panhc {
9398782d8d7SBarnabás Pőcze 	return device_add_group(&priv->platform_device->dev,
940c9f718d0SIke Panhc 				&ideapad_attribute_group);
94198ee6919SIke Panhc }
94298ee6919SIke Panhc 
943b5c37b79SZhang Rui static void ideapad_sysfs_exit(struct ideapad_private *priv)
94498ee6919SIke Panhc {
9458782d8d7SBarnabás Pőcze 	device_remove_group(&priv->platform_device->dev,
946c9f718d0SIke Panhc 			    &ideapad_attribute_group);
94798ee6919SIke Panhc }
94898ee6919SIke Panhc 
949f63409aeSIke Panhc /*
950f63409aeSIke Panhc  * input device
951f63409aeSIke Panhc  */
952f63409aeSIke Panhc static const struct key_entry ideapad_keymap[] = {
953f43d9ec0SIke Panhc 	{ KE_KEY, 6,  { KEY_SWITCHVIDEOMODE } },
954296f9fe0SMaxim Mikityanskiy 	{ KE_KEY, 7,  { KEY_CAMERA } },
95548f67d62SAlex Hung 	{ KE_KEY, 8,  { KEY_MICMUTE } },
956296f9fe0SMaxim Mikityanskiy 	{ KE_KEY, 11, { KEY_F16 } },
957f43d9ec0SIke Panhc 	{ KE_KEY, 13, { KEY_WLAN } },
958f43d9ec0SIke Panhc 	{ KE_KEY, 16, { KEY_PROG1 } },
959f43d9ec0SIke Panhc 	{ KE_KEY, 17, { KEY_PROG2 } },
960296f9fe0SMaxim Mikityanskiy 	{ KE_KEY, 64, { KEY_PROG3 } },
961296f9fe0SMaxim Mikityanskiy 	{ KE_KEY, 65, { KEY_PROG4 } },
96207a4a4fcSMaxim Mikityanskiy 	{ KE_KEY, 66, { KEY_TOUCHPAD_OFF } },
96307a4a4fcSMaxim Mikityanskiy 	{ KE_KEY, 67, { KEY_TOUCHPAD_ON } },
96474caab99SArnd Bergmann 	{ KE_KEY, 128, { KEY_ESC } },
96574caab99SArnd Bergmann 
966f63409aeSIke Panhc 	{ KE_END, 0 },
967f63409aeSIke Panhc };
968f63409aeSIke Panhc 
969b859f159SGreg Kroah-Hartman static int ideapad_input_init(struct ideapad_private *priv)
970f63409aeSIke Panhc {
971f63409aeSIke Panhc 	struct input_dev *inputdev;
972f63409aeSIke Panhc 	int error;
973f63409aeSIke Panhc 
974f63409aeSIke Panhc 	inputdev = input_allocate_device();
975b222cca6SJoe Perches 	if (!inputdev)
976f63409aeSIke Panhc 		return -ENOMEM;
977f63409aeSIke Panhc 
978f63409aeSIke Panhc 	inputdev->name = "Ideapad extra buttons";
979f63409aeSIke Panhc 	inputdev->phys = "ideapad/input0";
980f63409aeSIke Panhc 	inputdev->id.bustype = BUS_HOST;
9818693ae84SIke Panhc 	inputdev->dev.parent = &priv->platform_device->dev;
982f63409aeSIke Panhc 
983f63409aeSIke Panhc 	error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL);
984f63409aeSIke Panhc 	if (error) {
985654324c4SBarnabás Pőcze 		dev_err(&priv->platform_device->dev,
986654324c4SBarnabás Pőcze 			"Unable to setup input device keymap\n");
987f63409aeSIke Panhc 		goto err_free_dev;
988f63409aeSIke Panhc 	}
989f63409aeSIke Panhc 
990f63409aeSIke Panhc 	error = input_register_device(inputdev);
991f63409aeSIke Panhc 	if (error) {
992654324c4SBarnabás Pőcze 		dev_err(&priv->platform_device->dev,
993654324c4SBarnabás Pőcze 			"Unable to register input device\n");
994c973d4b5SMichał Kępień 		goto err_free_dev;
995f63409aeSIke Panhc 	}
996f63409aeSIke Panhc 
9978693ae84SIke Panhc 	priv->inputdev = inputdev;
998f63409aeSIke Panhc 	return 0;
999f63409aeSIke Panhc 
1000f63409aeSIke Panhc err_free_dev:
1001f63409aeSIke Panhc 	input_free_device(inputdev);
1002f63409aeSIke Panhc 	return error;
1003f63409aeSIke Panhc }
1004f63409aeSIke Panhc 
10057451a55aSAxel Lin static void ideapad_input_exit(struct ideapad_private *priv)
1006f63409aeSIke Panhc {
10078693ae84SIke Panhc 	input_unregister_device(priv->inputdev);
10088693ae84SIke Panhc 	priv->inputdev = NULL;
1009f63409aeSIke Panhc }
1010f63409aeSIke Panhc 
10118693ae84SIke Panhc static void ideapad_input_report(struct ideapad_private *priv,
10128693ae84SIke Panhc 				 unsigned long scancode)
1013f63409aeSIke Panhc {
10148693ae84SIke Panhc 	sparse_keymap_report_event(priv->inputdev, scancode, 1, true);
1015f63409aeSIke Panhc }
1016f63409aeSIke Panhc 
1017f43d9ec0SIke Panhc static void ideapad_input_novokey(struct ideapad_private *priv)
1018f43d9ec0SIke Panhc {
1019f43d9ec0SIke Panhc 	unsigned long long_pressed;
1020f43d9ec0SIke Panhc 
1021331e0ea2SZhang Rui 	if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed))
1022f43d9ec0SIke Panhc 		return;
1023f43d9ec0SIke Panhc 	if (long_pressed)
1024f43d9ec0SIke Panhc 		ideapad_input_report(priv, 17);
1025f43d9ec0SIke Panhc 	else
1026f43d9ec0SIke Panhc 		ideapad_input_report(priv, 16);
1027f43d9ec0SIke Panhc }
1028f43d9ec0SIke Panhc 
1029296f9fe0SMaxim Mikityanskiy static void ideapad_check_special_buttons(struct ideapad_private *priv)
1030296f9fe0SMaxim Mikityanskiy {
1031296f9fe0SMaxim Mikityanskiy 	unsigned long bit, value;
1032296f9fe0SMaxim Mikityanskiy 
10337be193e3SBarnabás Pőcze 	if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value))
10347be193e3SBarnabás Pőcze 		return;
1035296f9fe0SMaxim Mikityanskiy 
10360c4915b6SBarnabás Pőcze 	for_each_set_bit (bit, &value, 16) {
1037296f9fe0SMaxim Mikityanskiy 		switch (bit) {
1038a1ec56edSMaxim Mikityanskiy 		case 0:	/* Z580 */
1039a1ec56edSMaxim Mikityanskiy 		case 6:	/* Z570 */
1040296f9fe0SMaxim Mikityanskiy 			/* Thermal Management button */
1041296f9fe0SMaxim Mikityanskiy 			ideapad_input_report(priv, 65);
1042296f9fe0SMaxim Mikityanskiy 			break;
1043296f9fe0SMaxim Mikityanskiy 		case 1:
1044296f9fe0SMaxim Mikityanskiy 			/* OneKey Theater button */
1045296f9fe0SMaxim Mikityanskiy 			ideapad_input_report(priv, 64);
1046296f9fe0SMaxim Mikityanskiy 			break;
1047a1ec56edSMaxim Mikityanskiy 		default:
1048654324c4SBarnabás Pőcze 			dev_info(&priv->platform_device->dev,
1049654324c4SBarnabás Pőcze 				 "Unknown special button: %lu\n", bit);
1050a1ec56edSMaxim Mikityanskiy 			break;
1051296f9fe0SMaxim Mikityanskiy 		}
1052296f9fe0SMaxim Mikityanskiy 	}
1053296f9fe0SMaxim Mikityanskiy }
1054296f9fe0SMaxim Mikityanskiy 
1055a4b5a279SIke Panhc /*
1056a4ecbb8aSIke Panhc  * backlight
1057a4ecbb8aSIke Panhc  */
1058a4ecbb8aSIke Panhc static int ideapad_backlight_get_brightness(struct backlight_device *blightdev)
1059a4ecbb8aSIke Panhc {
1060331e0ea2SZhang Rui 	struct ideapad_private *priv = bl_get_data(blightdev);
1061a4ecbb8aSIke Panhc 	unsigned long now;
10627be193e3SBarnabás Pőcze 	int err;
1063a4ecbb8aSIke Panhc 
10647be193e3SBarnabás Pőcze 	err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now);
10657be193e3SBarnabás Pőcze 	if (err)
10667be193e3SBarnabás Pőcze 		return err;
1067a4ecbb8aSIke Panhc 	return now;
1068a4ecbb8aSIke Panhc }
1069a4ecbb8aSIke Panhc 
1070a4ecbb8aSIke Panhc static int ideapad_backlight_update_status(struct backlight_device *blightdev)
1071a4ecbb8aSIke Panhc {
1072331e0ea2SZhang Rui 	struct ideapad_private *priv = bl_get_data(blightdev);
10737be193e3SBarnabás Pőcze 	int err;
1074331e0ea2SZhang Rui 
10757be193e3SBarnabás Pőcze 	err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL,
10767be193e3SBarnabás Pőcze 			   blightdev->props.brightness);
10777be193e3SBarnabás Pőcze 	if (err)
10787be193e3SBarnabás Pőcze 		return err;
10797be193e3SBarnabás Pőcze 	err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER,
10807be193e3SBarnabás Pőcze 			   blightdev->props.power != FB_BLANK_POWERDOWN);
10817be193e3SBarnabás Pőcze 	if (err)
10827be193e3SBarnabás Pőcze 		return err;
1083a4ecbb8aSIke Panhc 
1084a4ecbb8aSIke Panhc 	return 0;
1085a4ecbb8aSIke Panhc }
1086a4ecbb8aSIke Panhc 
1087a4ecbb8aSIke Panhc static const struct backlight_ops ideapad_backlight_ops = {
1088a4ecbb8aSIke Panhc 	.get_brightness = ideapad_backlight_get_brightness,
1089a4ecbb8aSIke Panhc 	.update_status = ideapad_backlight_update_status,
1090a4ecbb8aSIke Panhc };
1091a4ecbb8aSIke Panhc 
1092a4ecbb8aSIke Panhc static int ideapad_backlight_init(struct ideapad_private *priv)
1093a4ecbb8aSIke Panhc {
1094a4ecbb8aSIke Panhc 	struct backlight_device *blightdev;
1095a4ecbb8aSIke Panhc 	struct backlight_properties props;
1096a4ecbb8aSIke Panhc 	unsigned long max, now, power;
10977be193e3SBarnabás Pőcze 	int err;
1098a4ecbb8aSIke Panhc 
10997be193e3SBarnabás Pőcze 	err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max);
11007be193e3SBarnabás Pőcze 	if (err)
11017be193e3SBarnabás Pőcze 		return err;
11027be193e3SBarnabás Pőcze 	err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now);
11037be193e3SBarnabás Pőcze 	if (err)
11047be193e3SBarnabás Pőcze 		return err;
11057be193e3SBarnabás Pőcze 	err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power);
11067be193e3SBarnabás Pőcze 	if (err)
11077be193e3SBarnabás Pőcze 		return err;
1108a4ecbb8aSIke Panhc 
1109a4ecbb8aSIke Panhc 	memset(&props, 0, sizeof(struct backlight_properties));
1110a4ecbb8aSIke Panhc 	props.max_brightness = max;
1111a4ecbb8aSIke Panhc 	props.type = BACKLIGHT_PLATFORM;
1112a4ecbb8aSIke Panhc 	blightdev = backlight_device_register("ideapad",
1113a4ecbb8aSIke Panhc 					      &priv->platform_device->dev,
1114a4ecbb8aSIke Panhc 					      priv,
1115a4ecbb8aSIke Panhc 					      &ideapad_backlight_ops,
1116a4ecbb8aSIke Panhc 					      &props);
1117a4ecbb8aSIke Panhc 	if (IS_ERR(blightdev)) {
1118654324c4SBarnabás Pőcze 		dev_err(&priv->platform_device->dev,
1119654324c4SBarnabás Pőcze 			"Could not register backlight device\n");
1120a4ecbb8aSIke Panhc 		return PTR_ERR(blightdev);
1121a4ecbb8aSIke Panhc 	}
1122a4ecbb8aSIke Panhc 
1123a4ecbb8aSIke Panhc 	priv->blightdev = blightdev;
1124a4ecbb8aSIke Panhc 	blightdev->props.brightness = now;
1125a4ecbb8aSIke Panhc 	blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
1126a4ecbb8aSIke Panhc 	backlight_update_status(blightdev);
1127a4ecbb8aSIke Panhc 
1128a4ecbb8aSIke Panhc 	return 0;
1129a4ecbb8aSIke Panhc }
1130a4ecbb8aSIke Panhc 
1131a4ecbb8aSIke Panhc static void ideapad_backlight_exit(struct ideapad_private *priv)
1132a4ecbb8aSIke Panhc {
1133a4ecbb8aSIke Panhc 	backlight_device_unregister(priv->blightdev);
1134a4ecbb8aSIke Panhc 	priv->blightdev = NULL;
1135a4ecbb8aSIke Panhc }
1136a4ecbb8aSIke Panhc 
1137a4ecbb8aSIke Panhc static void ideapad_backlight_notify_power(struct ideapad_private *priv)
1138a4ecbb8aSIke Panhc {
1139a4ecbb8aSIke Panhc 	unsigned long power;
1140a4ecbb8aSIke Panhc 	struct backlight_device *blightdev = priv->blightdev;
1141a4ecbb8aSIke Panhc 
1142d4afc775SRene Bollford 	if (!blightdev)
1143d4afc775SRene Bollford 		return;
1144331e0ea2SZhang Rui 	if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power))
1145a4ecbb8aSIke Panhc 		return;
1146a4ecbb8aSIke Panhc 	blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
1147a4ecbb8aSIke Panhc }
1148a4ecbb8aSIke Panhc 
1149a4ecbb8aSIke Panhc static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
1150a4ecbb8aSIke Panhc {
1151a4ecbb8aSIke Panhc 	unsigned long now;
1152a4ecbb8aSIke Panhc 
1153a4ecbb8aSIke Panhc 	/* if we control brightness via acpi video driver */
1154a4ecbb8aSIke Panhc 	if (priv->blightdev == NULL) {
1155331e0ea2SZhang Rui 		read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now);
1156a4ecbb8aSIke Panhc 		return;
1157a4ecbb8aSIke Panhc 	}
1158a4ecbb8aSIke Panhc 
1159a4ecbb8aSIke Panhc 	backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY);
1160a4ecbb8aSIke Panhc }
1161a4ecbb8aSIke Panhc 
1162a4ecbb8aSIke Panhc /*
1163a4b5a279SIke Panhc  * module init/exit
1164a4b5a279SIke Panhc  */
116575a11f11SZhang Rui static void ideapad_sync_touchpad_state(struct ideapad_private *priv)
116607a4a4fcSMaxim Mikityanskiy {
116707a4a4fcSMaxim Mikityanskiy 	unsigned long value;
116807a4a4fcSMaxim Mikityanskiy 
11691c59de4aSBarnabás Pőcze 	if (!priv->features.touchpad_ctrl_via_ec)
1170d69cd7eeSJiaxun Yang 		return;
1171d69cd7eeSJiaxun Yang 
117207a4a4fcSMaxim Mikityanskiy 	/* Without reading from EC touchpad LED doesn't switch state */
117375a11f11SZhang Rui 	if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) {
117407a4a4fcSMaxim Mikityanskiy 		/* Some IdeaPads don't really turn off touchpad - they only
117507a4a4fcSMaxim Mikityanskiy 		 * switch the LED state. We (de)activate KBC AUX port to turn
117607a4a4fcSMaxim Mikityanskiy 		 * touchpad off and on. We send KEY_TOUCHPAD_OFF and
117707a4a4fcSMaxim Mikityanskiy 		 * KEY_TOUCHPAD_ON to not to get out of sync with LED */
117807a4a4fcSMaxim Mikityanskiy 		unsigned char param;
117907a4a4fcSMaxim Mikityanskiy 		i8042_command(&param, value ? I8042_CMD_AUX_ENABLE :
118007a4a4fcSMaxim Mikityanskiy 			      I8042_CMD_AUX_DISABLE);
118107a4a4fcSMaxim Mikityanskiy 		ideapad_input_report(priv, value ? 67 : 66);
118207a4a4fcSMaxim Mikityanskiy 	}
118307a4a4fcSMaxim Mikityanskiy }
118407a4a4fcSMaxim Mikityanskiy 
1185b5c37b79SZhang Rui static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
118657ac3b05SIke Panhc {
1187b5c37b79SZhang Rui 	struct ideapad_private *priv = data;
11880c4915b6SBarnabás Pőcze 	unsigned long vpc1, vpc2, bit;
118957ac3b05SIke Panhc 
11902be1dc21SIke Panhc 	if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))
119157ac3b05SIke Panhc 		return;
11922be1dc21SIke Panhc 	if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2))
119357ac3b05SIke Panhc 		return;
119457ac3b05SIke Panhc 
119557ac3b05SIke Panhc 	vpc1 = (vpc2 << 8) | vpc1;
11960c4915b6SBarnabás Pőcze 
11970c4915b6SBarnabás Pőcze 	for_each_set_bit (bit, &vpc1, 16) {
11980c4915b6SBarnabás Pőcze 		switch (bit) {
1199a4ecbb8aSIke Panhc 		case 9:
1200923de84aSIke Panhc 			ideapad_sync_rfk_state(priv);
1201a4ecbb8aSIke Panhc 			break;
120220a769c1SIke Panhc 		case 13:
1203296f9fe0SMaxim Mikityanskiy 		case 11:
120448f67d62SAlex Hung 		case 8:
1205296f9fe0SMaxim Mikityanskiy 		case 7:
120620a769c1SIke Panhc 		case 6:
12070c4915b6SBarnabás Pőcze 			ideapad_input_report(priv, bit);
120820a769c1SIke Panhc 			break;
120907a4a4fcSMaxim Mikityanskiy 		case 5:
121075a11f11SZhang Rui 			ideapad_sync_touchpad_state(priv);
121107a4a4fcSMaxim Mikityanskiy 			break;
1212a4ecbb8aSIke Panhc 		case 4:
1213a4ecbb8aSIke Panhc 			ideapad_backlight_notify_brightness(priv);
1214a4ecbb8aSIke Panhc 			break;
1215f43d9ec0SIke Panhc 		case 3:
1216f43d9ec0SIke Panhc 			ideapad_input_novokey(priv);
1217f43d9ec0SIke Panhc 			break;
1218a4ecbb8aSIke Panhc 		case 2:
1219a4ecbb8aSIke Panhc 			ideapad_backlight_notify_power(priv);
1220a4ecbb8aSIke Panhc 			break;
1221296f9fe0SMaxim Mikityanskiy 		case 0:
1222296f9fe0SMaxim Mikityanskiy 			ideapad_check_special_buttons(priv);
1223296f9fe0SMaxim Mikityanskiy 			break;
12243cfd956bSHao Wei Tee 		case 1:
12253cfd956bSHao Wei Tee 			/* Some IdeaPads report event 1 every ~20
12263cfd956bSHao Wei Tee 			 * seconds while on battery power; some
12273cfd956bSHao Wei Tee 			 * report this when changing to/from tablet
12283cfd956bSHao Wei Tee 			 * mode. Squelch this event.
12293cfd956bSHao Wei Tee 			 */
12303cfd956bSHao Wei Tee 			break;
1231a4ecbb8aSIke Panhc 		default:
1232654324c4SBarnabás Pőcze 			dev_info(&priv->platform_device->dev,
1233654324c4SBarnabás Pőcze 				 "Unknown event: %lu\n", bit);
123457ac3b05SIke Panhc 		}
123557ac3b05SIke Panhc 	}
1236a4ecbb8aSIke Panhc }
123757ac3b05SIke Panhc 
123874caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI)
123974caab99SArnd Bergmann static void ideapad_wmi_notify(u32 value, void *context)
124074caab99SArnd Bergmann {
1241654324c4SBarnabás Pőcze 	struct ideapad_private *priv = context;
1242654324c4SBarnabás Pőcze 
124374caab99SArnd Bergmann 	switch (value) {
124474caab99SArnd Bergmann 	case 128:
1245654324c4SBarnabás Pőcze 		ideapad_input_report(priv, value);
124674caab99SArnd Bergmann 		break;
124774caab99SArnd Bergmann 	default:
1248654324c4SBarnabás Pőcze 		dev_info(&priv->platform_device->dev,
1249654324c4SBarnabás Pőcze 			 "Unknown WMI event: %u\n", value);
125074caab99SArnd Bergmann 	}
125174caab99SArnd Bergmann }
125274caab99SArnd Bergmann #endif
125374caab99SArnd Bergmann 
1254ce363c2bSHans de Goede /*
12555105e78eSHans de Goede  * Some ideapads have a hardware rfkill switch, but most do not have one.
12565105e78eSHans de Goede  * Reading VPCCMD_R_RF always results in 0 on models without a hardware rfkill,
12575105e78eSHans de Goede  * switch causing ideapad_laptop to wrongly report all radios as hw-blocked.
12585105e78eSHans de Goede  * There used to be a long list of DMI ids for models without a hw rfkill
12595105e78eSHans de Goede  * switch here, but that resulted in playing whack a mole.
12605105e78eSHans de Goede  * More importantly wrongly reporting the wifi radio as hw-blocked, results in
12615105e78eSHans de Goede  * non working wifi. Whereas not reporting it hw-blocked, when it actually is
12625105e78eSHans de Goede  * hw-blocked results in an empty SSID list, which is a much more benign
12635105e78eSHans de Goede  * failure mode.
12645105e78eSHans de Goede  * So the default now is the much safer option of assuming there is no
12655105e78eSHans de Goede  * hardware rfkill switch. This default also actually matches most hardware,
12665105e78eSHans de Goede  * since having a hw rfkill switch is quite rare on modern hardware, so this
12675105e78eSHans de Goede  * also leads to a much shorter list.
1268ce363c2bSHans de Goede  */
12695105e78eSHans de Goede static const struct dmi_system_id hw_rfkill_list[] = {
127085093f79SHans de Goede 	{}
127185093f79SHans de Goede };
127285093f79SHans de Goede 
12731c59de4aSBarnabás Pőcze static void ideapad_check_features(struct ideapad_private *priv)
12741c59de4aSBarnabás Pőcze {
12751c59de4aSBarnabás Pőcze 	acpi_handle handle = priv->adev->handle;
12761c59de4aSBarnabás Pőcze 	unsigned long val;
12771c59de4aSBarnabás Pőcze 
12781c59de4aSBarnabás Pőcze 	priv->features.hw_rfkill_switch = dmi_check_system(hw_rfkill_list);
12791c59de4aSBarnabás Pőcze 
12801c59de4aSBarnabás Pőcze 	/* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */
12811c59de4aSBarnabás Pőcze 	priv->features.touchpad_ctrl_via_ec = !acpi_dev_present("ELAN0634", NULL, -1);
12821c59de4aSBarnabás Pőcze 
12831c59de4aSBarnabás Pőcze 	if (!read_ec_data(handle, VPCCMD_R_FAN, &val))
12841c59de4aSBarnabás Pőcze 		priv->features.fan_mode = true;
12851c59de4aSBarnabás Pőcze 
12861c59de4aSBarnabás Pőcze 	if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC"))
12871c59de4aSBarnabás Pőcze 		priv->features.conservation_mode = true;
12881c59de4aSBarnabás Pőcze 
12891c59de4aSBarnabás Pőcze 	if (acpi_has_method(handle, "DYTC"))
12901c59de4aSBarnabás Pőcze 		priv->features.dytc = true;
12911c59de4aSBarnabás Pőcze 
1292392cbf0aSBarnabás Pőcze 	if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) {
1293392cbf0aSBarnabás Pőcze 		if (!eval_hals(handle, &val)) {
1294392cbf0aSBarnabás Pőcze 			if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val))
12951c59de4aSBarnabás Pőcze 				priv->features.fn_lock = true;
12961c59de4aSBarnabás Pőcze 		}
1297392cbf0aSBarnabás Pőcze 	}
1298392cbf0aSBarnabás Pőcze }
12991c59de4aSBarnabás Pőcze 
1300b5c37b79SZhang Rui static int ideapad_acpi_add(struct platform_device *pdev)
1301b5c37b79SZhang Rui {
1302b5c37b79SZhang Rui 	int ret, i;
1303b5c37b79SZhang Rui 	struct ideapad_private *priv;
1304b5c37b79SZhang Rui 	struct acpi_device *adev;
1305803be832SBarnabás Pőcze 	acpi_status status;
1306ff36b0d9SBarnabás Pőcze 	unsigned long cfg;
1307b5c37b79SZhang Rui 
1308b5c37b79SZhang Rui 	ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev);
1309b5c37b79SZhang Rui 	if (ret)
1310b5c37b79SZhang Rui 		return -ENODEV;
1311b5c37b79SZhang Rui 
1312ff36b0d9SBarnabás Pőcze 	if (eval_int(adev->handle, "_CFG", &cfg))
1313b5c37b79SZhang Rui 		return -ENODEV;
1314b5c37b79SZhang Rui 
1315b3facd7bSHimangi Saraogi 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
1316b5c37b79SZhang Rui 	if (!priv)
1317b5c37b79SZhang Rui 		return -ENOMEM;
1318b5c37b79SZhang Rui 
1319b5c37b79SZhang Rui 	dev_set_drvdata(&pdev->dev, priv);
1320b5c37b79SZhang Rui 	priv->cfg = cfg;
1321b5c37b79SZhang Rui 	priv->adev = adev;
1322b5c37b79SZhang Rui 	priv->platform_device = pdev;
1323b5c37b79SZhang Rui 
13241c59de4aSBarnabás Pőcze 	ideapad_check_features(priv);
1325d69cd7eeSJiaxun Yang 
1326b5c37b79SZhang Rui 	ret = ideapad_sysfs_init(priv);
1327b5c37b79SZhang Rui 	if (ret)
1328b3facd7bSHimangi Saraogi 		return ret;
1329b5c37b79SZhang Rui 
133017f1bf38SGreg Kroah-Hartman 	ideapad_debugfs_init(priv);
1331b5c37b79SZhang Rui 
1332b5c37b79SZhang Rui 	ret = ideapad_input_init(priv);
1333b5c37b79SZhang Rui 	if (ret)
1334b5c37b79SZhang Rui 		goto input_failed;
1335b5c37b79SZhang Rui 
1336ce363c2bSHans de Goede 	/*
1337ce363c2bSHans de Goede 	 * On some models without a hw-switch (the yoga 2 13 at least)
1338ce363c2bSHans de Goede 	 * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work.
1339ce363c2bSHans de Goede 	 */
13401c59de4aSBarnabás Pőcze 	if (!priv->features.hw_rfkill_switch)
1341ce363c2bSHans de Goede 		write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1);
1342ce363c2bSHans de Goede 
1343d69cd7eeSJiaxun Yang 	/* The same for Touchpad */
13441c59de4aSBarnabás Pőcze 	if (!priv->features.touchpad_ctrl_via_ec)
1345d69cd7eeSJiaxun Yang 		write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, 1);
1346d69cd7eeSJiaxun Yang 
134785093f79SHans de Goede 	for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
1348b5c37b79SZhang Rui 		if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg))
1349b5c37b79SZhang Rui 			ideapad_register_rfkill(priv, i);
1350ce363c2bSHans de Goede 
1351b5c37b79SZhang Rui 	ideapad_sync_rfk_state(priv);
1352b5c37b79SZhang Rui 	ideapad_sync_touchpad_state(priv);
1353b5c37b79SZhang Rui 
13541c59de4aSBarnabás Pőcze 	ret = ideapad_dytc_profile_init(priv);
13551c59de4aSBarnabás Pőcze 	if (ret) {
13561c59de4aSBarnabás Pőcze 		if (ret != -ENODEV)
13571c59de4aSBarnabás Pőcze 			dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", ret);
13581c59de4aSBarnabás Pőcze 		else
13591c59de4aSBarnabás Pőcze 			dev_info(&pdev->dev, "DYTC interface is not available\n");
13601c59de4aSBarnabás Pőcze 	}
1361eabe5339SJiaxun Yang 
136226bff5f0SHans de Goede 	if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
1363b5c37b79SZhang Rui 		ret = ideapad_backlight_init(priv);
1364b5c37b79SZhang Rui 		if (ret && ret != -ENODEV)
1365b5c37b79SZhang Rui 			goto backlight_failed;
1366b5c37b79SZhang Rui 	}
1367803be832SBarnabás Pőcze 	status = acpi_install_notify_handler(adev->handle,
1368803be832SBarnabás Pőcze 					     ACPI_DEVICE_NOTIFY,
1369803be832SBarnabás Pőcze 					     ideapad_acpi_notify, priv);
1370803be832SBarnabás Pőcze 	if (ACPI_FAILURE(status)) {
1371803be832SBarnabás Pőcze 		ret = -EIO;
1372b5c37b79SZhang Rui 		goto notification_failed;
1373803be832SBarnabás Pőcze 	}
13742d98e0b9SArnd Bergmann 
137574caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI)
13762d98e0b9SArnd Bergmann 	for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) {
1377803be832SBarnabás Pőcze 		status = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i],
13782d98e0b9SArnd Bergmann 						    ideapad_wmi_notify, priv);
1379803be832SBarnabás Pőcze 		if (ACPI_SUCCESS(status)) {
13802d98e0b9SArnd Bergmann 			priv->fnesc_guid = ideapad_wmi_fnesc_events[i];
13812d98e0b9SArnd Bergmann 			break;
13822d98e0b9SArnd Bergmann 		}
13832d98e0b9SArnd Bergmann 	}
1384803be832SBarnabás Pőcze 	if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) {
1385803be832SBarnabás Pőcze 		ret = -EIO;
138674caab99SArnd Bergmann 		goto notification_failed_wmi;
1387803be832SBarnabás Pőcze 	}
138874caab99SArnd Bergmann #endif
1389b5c37b79SZhang Rui 
1390b5c37b79SZhang Rui 	return 0;
139174caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI)
139274caab99SArnd Bergmann notification_failed_wmi:
139374caab99SArnd Bergmann 	acpi_remove_notify_handler(priv->adev->handle,
139474caab99SArnd Bergmann 		ACPI_DEVICE_NOTIFY, ideapad_acpi_notify);
139574caab99SArnd Bergmann #endif
1396b5c37b79SZhang Rui notification_failed:
1397b5c37b79SZhang Rui 	ideapad_backlight_exit(priv);
1398b5c37b79SZhang Rui backlight_failed:
1399caa315b8SBarnabás Pőcze 	ideapad_dytc_profile_exit(priv);
1400b5c37b79SZhang Rui 	for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
1401b5c37b79SZhang Rui 		ideapad_unregister_rfkill(priv, i);
1402b5c37b79SZhang Rui 	ideapad_input_exit(priv);
1403b5c37b79SZhang Rui input_failed:
1404b5c37b79SZhang Rui 	ideapad_debugfs_exit(priv);
1405b5c37b79SZhang Rui 	ideapad_sysfs_exit(priv);
1406b5c37b79SZhang Rui 	return ret;
1407b5c37b79SZhang Rui }
1408b5c37b79SZhang Rui 
1409b5c37b79SZhang Rui static int ideapad_acpi_remove(struct platform_device *pdev)
1410b5c37b79SZhang Rui {
1411b5c37b79SZhang Rui 	struct ideapad_private *priv = dev_get_drvdata(&pdev->dev);
1412b5c37b79SZhang Rui 	int i;
1413b5c37b79SZhang Rui 
141474caab99SArnd Bergmann #if IS_ENABLED(CONFIG_ACPI_WMI)
14152d98e0b9SArnd Bergmann 	if (priv->fnesc_guid)
14162d98e0b9SArnd Bergmann 		wmi_remove_notify_handler(priv->fnesc_guid);
141774caab99SArnd Bergmann #endif
1418b5c37b79SZhang Rui 	acpi_remove_notify_handler(priv->adev->handle,
1419b5c37b79SZhang Rui 		ACPI_DEVICE_NOTIFY, ideapad_acpi_notify);
1420b5c37b79SZhang Rui 	ideapad_backlight_exit(priv);
1421eabe5339SJiaxun Yang 	ideapad_dytc_profile_exit(priv);
1422b5c37b79SZhang Rui 	for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
1423b5c37b79SZhang Rui 		ideapad_unregister_rfkill(priv, i);
1424b5c37b79SZhang Rui 	ideapad_input_exit(priv);
1425b5c37b79SZhang Rui 	ideapad_debugfs_exit(priv);
1426b5c37b79SZhang Rui 	ideapad_sysfs_exit(priv);
1427b5c37b79SZhang Rui 
1428b5c37b79SZhang Rui 	return 0;
1429b5c37b79SZhang Rui }
1430b5c37b79SZhang Rui 
143111fa8da5SZhang Rui #ifdef CONFIG_PM_SLEEP
1432e1a39a44SBarnabás Pőcze static int ideapad_acpi_resume(struct device *dev)
143307a4a4fcSMaxim Mikityanskiy {
1434e1a39a44SBarnabás Pőcze 	struct ideapad_private *priv = dev_get_drvdata(dev);
143575a11f11SZhang Rui 
143675a11f11SZhang Rui 	ideapad_sync_rfk_state(priv);
143775a11f11SZhang Rui 	ideapad_sync_touchpad_state(priv);
1438eabe5339SJiaxun Yang 
1439eabe5339SJiaxun Yang 	if (priv->dytc)
1440eabe5339SJiaxun Yang 		dytc_profile_refresh(priv);
1441eabe5339SJiaxun Yang 
144207a4a4fcSMaxim Mikityanskiy 	return 0;
144307a4a4fcSMaxim Mikityanskiy }
1444b5c37b79SZhang Rui #endif
144507a4a4fcSMaxim Mikityanskiy static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume);
144607a4a4fcSMaxim Mikityanskiy 
1447b5c37b79SZhang Rui static const struct acpi_device_id ideapad_device_ids[] = {
1448b5c37b79SZhang Rui 	{ "VPC2004", 0},
1449b5c37b79SZhang Rui 	{ "", 0},
145057ac3b05SIke Panhc };
1451b5c37b79SZhang Rui MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
1452b5c37b79SZhang Rui 
1453b5c37b79SZhang Rui static struct platform_driver ideapad_acpi_driver = {
1454b5c37b79SZhang Rui 	.probe = ideapad_acpi_add,
1455b5c37b79SZhang Rui 	.remove = ideapad_acpi_remove,
1456b5c37b79SZhang Rui 	.driver = {
1457b5c37b79SZhang Rui 		.name   = "ideapad_acpi",
1458b5c37b79SZhang Rui 		.pm     = &ideapad_pm,
1459b5c37b79SZhang Rui 		.acpi_match_table = ACPI_PTR(ideapad_device_ids),
1460b5c37b79SZhang Rui 	},
1461b5c37b79SZhang Rui };
1462b5c37b79SZhang Rui 
1463b5c37b79SZhang Rui module_platform_driver(ideapad_acpi_driver);
146457ac3b05SIke Panhc 
146557ac3b05SIke Panhc MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
146657ac3b05SIke Panhc MODULE_DESCRIPTION("IdeaPad ACPI Extras");
146757ac3b05SIke Panhc MODULE_LICENSE("GPL");
1468