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