blob: 5b6f18b188012dcc94b19c9156ee54b42650473b [file] [log] [blame]
David Woodhouse58ac7aa2010-08-10 23:44:05 +01001/*
Ike Panhca4b5a272010-12-13 18:00:48 +08002 * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras
David Woodhouse58ac7aa2010-08-10 23:44:05 +01003 *
4 * Copyright © 2010 Intel Corporation
5 * Copyright © 2010 David Woodhouse <dwmw2@infradead.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 * 02110-1301, USA.
21 */
22
Joe Perches9ab23982011-03-29 15:21:43 -070023#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
24
David Woodhouse58ac7aa2010-08-10 23:44:05 +010025#include <linux/kernel.h>
26#include <linux/module.h>
27#include <linux/init.h>
28#include <linux/types.h>
Lv Zheng8b484632013-12-03 08:49:16 +080029#include <linux/acpi.h>
David Woodhouse58ac7aa2010-08-10 23:44:05 +010030#include <linux/rfkill.h>
Ike Panhc98ee6912010-12-13 18:00:15 +080031#include <linux/platform_device.h>
Ike Panhcf63409a2010-12-13 18:00:38 +080032#include <linux/input.h>
33#include <linux/input/sparse-keymap.h>
Ike Panhca4ecbb82011-06-30 19:50:52 +080034#include <linux/backlight.h>
35#include <linux/fb.h>
Ike Panhc773e3202011-09-06 02:32:52 +080036#include <linux/debugfs.h>
37#include <linux/seq_file.h>
Maxim Mikityanskiy07a4a4f2012-07-06 16:08:00 +080038#include <linux/i8042.h>
Hans de Goede85093f72014-05-13 16:00:28 +020039#include <linux/dmi.h>
Himangi Saraogib3facd72014-06-09 17:46:50 -040040#include <linux/device.h>
Hans de Goede26bff5f2015-06-16 16:28:04 +020041#include <acpi/video.h>
David Woodhouse58ac7aa2010-08-10 23:44:05 +010042
Ike Panhcc1f73652010-12-13 18:01:12 +080043#define IDEAPAD_RFKILL_DEV_NUM (3)
David Woodhouse58ac7aa2010-08-10 23:44:05 +010044
Hao Wei Teeade50292017-08-15 00:13:51 +080045#define BM_CONSERVATION_BIT (5)
46
Ike Panhc3371f482011-06-30 19:50:40 +080047#define CFG_BT_BIT (16)
48#define CFG_3G_BIT (17)
49#define CFG_WIFI_BIT (18)
Ike Panhca84511f2011-06-30 19:50:47 +080050#define CFG_CAMERA_BIT (19)
Ike Panhc3371f482011-06-30 19:50:40 +080051
Arnd Bergmann74caab92015-11-06 22:28:49 +010052#if IS_ENABLED(CONFIG_ACPI_WMI)
Arnd Bergmann2d98e0b2016-05-09 23:49:21 +020053static const char *const ideapad_wmi_fnesc_events[] = {
54 "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */
55 "56322276-8493-4CE8-A783-98C991274F5E", /* Yoga 700 */
56};
Arnd Bergmann74caab92015-11-06 22:28:49 +010057#endif
58
Ike Panhc2be1dc22011-09-06 02:31:53 +080059enum {
Hao Wei Teeade50292017-08-15 00:13:51 +080060 BMCMD_CONSERVATION_ON = 3,
61 BMCMD_CONSERVATION_OFF = 5,
62};
63
64enum {
Ike Panhc2be1dc22011-09-06 02:31:53 +080065 VPCCMD_R_VPC1 = 0x10,
66 VPCCMD_R_BL_MAX,
67 VPCCMD_R_BL,
68 VPCCMD_W_BL,
69 VPCCMD_R_WIFI,
70 VPCCMD_W_WIFI,
71 VPCCMD_R_BT,
72 VPCCMD_W_BT,
73 VPCCMD_R_BL_POWER,
74 VPCCMD_R_NOVO,
75 VPCCMD_R_VPC2,
76 VPCCMD_R_TOUCHPAD,
77 VPCCMD_W_TOUCHPAD,
78 VPCCMD_R_CAMERA,
79 VPCCMD_W_CAMERA,
80 VPCCMD_R_3G,
81 VPCCMD_W_3G,
82 VPCCMD_R_ODD, /* 0x21 */
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +080083 VPCCMD_W_FAN,
84 VPCCMD_R_RF,
Ike Panhc2be1dc22011-09-06 02:31:53 +080085 VPCCMD_W_RF,
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +080086 VPCCMD_R_FAN = 0x2B,
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +080087 VPCCMD_R_SPECIAL_BUTTONS = 0x31,
Ike Panhc2be1dc22011-09-06 02:31:53 +080088 VPCCMD_W_BL_POWER = 0x33,
89};
90
Zhang Rui331e0ea2013-09-25 20:39:49 +080091struct ideapad_rfk_priv {
92 int dev;
93 struct ideapad_private *priv;
94};
95
David Woodhousece326322010-08-11 17:59:35 +010096struct ideapad_private {
Zhang Rui469f6432013-09-25 20:39:47 +080097 struct acpi_device *adev;
Ike Panhcc1f73652010-12-13 18:01:12 +080098 struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM];
Zhang Rui331e0ea2013-09-25 20:39:49 +080099 struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM];
Ike Panhc98ee6912010-12-13 18:00:15 +0800100 struct platform_device *platform_device;
Ike Panhcf63409a2010-12-13 18:00:38 +0800101 struct input_dev *inputdev;
Ike Panhca4ecbb82011-06-30 19:50:52 +0800102 struct backlight_device *blightdev;
Ike Panhc773e3202011-09-06 02:32:52 +0800103 struct dentry *debug;
Ike Panhc3371f482011-06-30 19:50:40 +0800104 unsigned long cfg;
Hans de Goedece363c22014-06-23 16:45:51 +0200105 bool has_hw_rfkill_switch;
Arnd Bergmann2d98e0b2016-05-09 23:49:21 +0200106 const char *fnesc_guid;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100107};
108
Ike Panhcbfa97b72010-10-01 15:40:22 +0800109static bool no_bt_rfkill;
110module_param(no_bt_rfkill, bool, 0444);
111MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
112
Ike Panhc6a09f212010-10-01 15:38:46 +0800113/*
114 * ACPI Helpers
115 */
116#define IDEAPAD_EC_TIMEOUT (100) /* in ms */
117
118static int read_method_int(acpi_handle handle, const char *method, int *val)
119{
120 acpi_status status;
121 unsigned long long result;
122
123 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
124 if (ACPI_FAILURE(status)) {
125 *val = -1;
126 return -1;
Ike Panhc6a09f212010-10-01 15:38:46 +0800127 }
Jiaxun Yangba3a3382017-12-02 21:45:31 +0800128 *val = result;
129 return 0;
130
Ike Panhc6a09f212010-10-01 15:38:46 +0800131}
132
Hao Wei Teeade50292017-08-15 00:13:51 +0800133static int method_gbmd(acpi_handle handle, unsigned long *ret)
134{
135 int result, val;
136
137 result = read_method_int(handle, "GBMD", &val);
138 *ret = val;
139 return result;
140}
141
142static int method_sbmc(acpi_handle handle, int cmd)
143{
144 acpi_status status;
145
146 status = acpi_execute_simple_method(handle, "SBMC", cmd);
147 return ACPI_FAILURE(status) ? -1 : 0;
148}
149
Ike Panhc6a09f212010-10-01 15:38:46 +0800150static int method_vpcr(acpi_handle handle, int cmd, int *ret)
151{
152 acpi_status status;
153 unsigned long long result;
154 struct acpi_object_list params;
155 union acpi_object in_obj;
156
157 params.count = 1;
158 params.pointer = &in_obj;
159 in_obj.type = ACPI_TYPE_INTEGER;
160 in_obj.integer.value = cmd;
161
162 status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
163
164 if (ACPI_FAILURE(status)) {
165 *ret = -1;
166 return -1;
Ike Panhc6a09f212010-10-01 15:38:46 +0800167 }
Jiaxun Yangba3a3382017-12-02 21:45:31 +0800168 *ret = result;
169 return 0;
170
Ike Panhc6a09f212010-10-01 15:38:46 +0800171}
172
173static int method_vpcw(acpi_handle handle, int cmd, int data)
174{
175 struct acpi_object_list params;
176 union acpi_object in_obj[2];
177 acpi_status status;
178
179 params.count = 2;
180 params.pointer = in_obj;
181 in_obj[0].type = ACPI_TYPE_INTEGER;
182 in_obj[0].integer.value = cmd;
183 in_obj[1].type = ACPI_TYPE_INTEGER;
184 in_obj[1].integer.value = data;
185
186 status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
187 if (status != AE_OK)
188 return -1;
189 return 0;
190}
191
192static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
193{
194 int val;
195 unsigned long int end_jiffies;
196
197 if (method_vpcw(handle, 1, cmd))
198 return -1;
199
200 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
201 time_before(jiffies, end_jiffies);) {
202 schedule();
203 if (method_vpcr(handle, 1, &val))
204 return -1;
205 if (val == 0) {
206 if (method_vpcr(handle, 0, &val))
207 return -1;
208 *data = val;
209 return 0;
210 }
211 }
212 pr_err("timeout in read_ec_cmd\n");
213 return -1;
214}
215
216static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)
217{
218 int val;
219 unsigned long int end_jiffies;
220
221 if (method_vpcw(handle, 0, data))
222 return -1;
223 if (method_vpcw(handle, 1, cmd))
224 return -1;
225
226 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
227 time_before(jiffies, end_jiffies);) {
228 schedule();
229 if (method_vpcr(handle, 1, &val))
230 return -1;
231 if (val == 0)
232 return 0;
233 }
Jiaxun Yangf1395ed2017-12-02 21:45:32 +0800234 pr_err("timeout in %s\n", __func__);
Ike Panhc6a09f212010-10-01 15:38:46 +0800235 return -1;
236}
Ike Panhc6a09f212010-10-01 15:38:46 +0800237
Ike Panhca4b5a272010-12-13 18:00:48 +0800238/*
Ike Panhc773e3202011-09-06 02:32:52 +0800239 * debugfs
240 */
Ike Panhc773e3202011-09-06 02:32:52 +0800241static int debugfs_status_show(struct seq_file *s, void *data)
242{
Zhang Rui331e0ea2013-09-25 20:39:49 +0800243 struct ideapad_private *priv = s->private;
Ike Panhc773e3202011-09-06 02:32:52 +0800244 unsigned long value;
245
Zhang Rui331e0ea2013-09-25 20:39:49 +0800246 if (!priv)
247 return -EINVAL;
248
249 if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value))
Ike Panhc773e3202011-09-06 02:32:52 +0800250 seq_printf(s, "Backlight max:\t%lu\n", value);
Zhang Rui331e0ea2013-09-25 20:39:49 +0800251 if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value))
Ike Panhc773e3202011-09-06 02:32:52 +0800252 seq_printf(s, "Backlight now:\t%lu\n", value);
Zhang Rui331e0ea2013-09-25 20:39:49 +0800253 if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value))
Ike Panhc773e3202011-09-06 02:32:52 +0800254 seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off");
255 seq_printf(s, "=====================\n");
256
Zhang Rui331e0ea2013-09-25 20:39:49 +0800257 if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value))
Ike Panhc773e3202011-09-06 02:32:52 +0800258 seq_printf(s, "Radio status:\t%s(%lu)\n",
259 value ? "On" : "Off", value);
Zhang Rui331e0ea2013-09-25 20:39:49 +0800260 if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value))
Ike Panhc773e3202011-09-06 02:32:52 +0800261 seq_printf(s, "Wifi status:\t%s(%lu)\n",
262 value ? "On" : "Off", value);
Zhang Rui331e0ea2013-09-25 20:39:49 +0800263 if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value))
Ike Panhc773e3202011-09-06 02:32:52 +0800264 seq_printf(s, "BT status:\t%s(%lu)\n",
265 value ? "On" : "Off", value);
Zhang Rui331e0ea2013-09-25 20:39:49 +0800266 if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value))
Ike Panhc773e3202011-09-06 02:32:52 +0800267 seq_printf(s, "3G status:\t%s(%lu)\n",
268 value ? "On" : "Off", value);
269 seq_printf(s, "=====================\n");
270
Zhang Rui331e0ea2013-09-25 20:39:49 +0800271 if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value))
Ike Panhc773e3202011-09-06 02:32:52 +0800272 seq_printf(s, "Touchpad status:%s(%lu)\n",
273 value ? "On" : "Off", value);
Zhang Rui331e0ea2013-09-25 20:39:49 +0800274 if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value))
Ike Panhc773e3202011-09-06 02:32:52 +0800275 seq_printf(s, "Camera status:\t%s(%lu)\n",
276 value ? "On" : "Off", value);
Hao Wei Teeade50292017-08-15 00:13:51 +0800277 seq_puts(s, "=====================\n");
278
279 if (!method_gbmd(priv->adev->handle, &value)) {
280 seq_printf(s, "Conservation mode:\t%s(%lu)\n",
281 test_bit(BM_CONSERVATION_BIT, &value) ? "On" : "Off",
282 value);
283 }
Ike Panhc773e3202011-09-06 02:32:52 +0800284
285 return 0;
286}
Andy Shevchenko334c4ef2018-01-22 18:05:45 +0200287DEFINE_SHOW_ATTRIBUTE(debugfs_status);
Ike Panhc773e3202011-09-06 02:32:52 +0800288
289static int debugfs_cfg_show(struct seq_file *s, void *data)
290{
Zhang Rui331e0ea2013-09-25 20:39:49 +0800291 struct ideapad_private *priv = s->private;
292
293 if (!priv) {
Ike Panhc773e3202011-09-06 02:32:52 +0800294 seq_printf(s, "cfg: N/A\n");
295 } else {
296 seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ",
Zhang Rui331e0ea2013-09-25 20:39:49 +0800297 priv->cfg);
298 if (test_bit(CFG_BT_BIT, &priv->cfg))
Ike Panhc773e3202011-09-06 02:32:52 +0800299 seq_printf(s, "Bluetooth ");
Zhang Rui331e0ea2013-09-25 20:39:49 +0800300 if (test_bit(CFG_3G_BIT, &priv->cfg))
Ike Panhc773e3202011-09-06 02:32:52 +0800301 seq_printf(s, "3G ");
Zhang Rui331e0ea2013-09-25 20:39:49 +0800302 if (test_bit(CFG_WIFI_BIT, &priv->cfg))
Ike Panhc773e3202011-09-06 02:32:52 +0800303 seq_printf(s, "Wireless ");
Zhang Rui331e0ea2013-09-25 20:39:49 +0800304 if (test_bit(CFG_CAMERA_BIT, &priv->cfg))
Ike Panhc773e3202011-09-06 02:32:52 +0800305 seq_printf(s, "Camera ");
306 seq_printf(s, "\nGraphic: ");
Zhang Rui331e0ea2013-09-25 20:39:49 +0800307 switch ((priv->cfg)&0x700) {
Ike Panhc773e3202011-09-06 02:32:52 +0800308 case 0x100:
309 seq_printf(s, "Intel");
310 break;
311 case 0x200:
312 seq_printf(s, "ATI");
313 break;
314 case 0x300:
315 seq_printf(s, "Nvidia");
316 break;
317 case 0x400:
318 seq_printf(s, "Intel and ATI");
319 break;
320 case 0x500:
321 seq_printf(s, "Intel and Nvidia");
322 break;
323 }
324 seq_printf(s, "\n");
325 }
326 return 0;
327}
Andy Shevchenko334c4ef2018-01-22 18:05:45 +0200328DEFINE_SHOW_ATTRIBUTE(debugfs_cfg);
Ike Panhc773e3202011-09-06 02:32:52 +0800329
Greg Kroah-Hartmanb859f152012-12-21 13:18:33 -0800330static int ideapad_debugfs_init(struct ideapad_private *priv)
Ike Panhc773e3202011-09-06 02:32:52 +0800331{
332 struct dentry *node;
333
334 priv->debug = debugfs_create_dir("ideapad", NULL);
335 if (priv->debug == NULL) {
336 pr_err("failed to create debugfs directory");
337 goto errout;
338 }
339
Zhang Rui331e0ea2013-09-25 20:39:49 +0800340 node = debugfs_create_file("cfg", S_IRUGO, priv->debug, priv,
Ike Panhc773e3202011-09-06 02:32:52 +0800341 &debugfs_cfg_fops);
342 if (!node) {
343 pr_err("failed to create cfg in debugfs");
344 goto errout;
345 }
346
Zhang Rui331e0ea2013-09-25 20:39:49 +0800347 node = debugfs_create_file("status", S_IRUGO, priv->debug, priv,
Ike Panhc773e3202011-09-06 02:32:52 +0800348 &debugfs_status_fops);
349 if (!node) {
Ike Panhca5c38922012-05-03 17:38:35 +0800350 pr_err("failed to create status in debugfs");
Ike Panhc773e3202011-09-06 02:32:52 +0800351 goto errout;
352 }
353
354 return 0;
355
356errout:
357 return -ENOMEM;
358}
359
360static void ideapad_debugfs_exit(struct ideapad_private *priv)
361{
362 debugfs_remove_recursive(priv->debug);
363 priv->debug = NULL;
364}
365
366/*
Ike Panhc3371f482011-06-30 19:50:40 +0800367 * sysfs
Ike Panhca4b5a272010-12-13 18:00:48 +0800368 */
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100369static ssize_t show_ideapad_cam(struct device *dev,
370 struct device_attribute *attr,
371 char *buf)
372{
Ike Panhc26c81d52010-10-01 15:39:40 +0800373 unsigned long result;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800374 struct ideapad_private *priv = dev_get_drvdata(dev);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100375
Zhang Rui331e0ea2013-09-25 20:39:49 +0800376 if (read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result))
Ike Panhc26c81d52010-10-01 15:39:40 +0800377 return sprintf(buf, "-1\n");
378 return sprintf(buf, "%lu\n", result);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100379}
380
381static ssize_t store_ideapad_cam(struct device *dev,
382 struct device_attribute *attr,
383 const char *buf, size_t count)
384{
385 int ret, state;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800386 struct ideapad_private *priv = dev_get_drvdata(dev);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100387
388 if (!count)
389 return 0;
390 if (sscanf(buf, "%i", &state) != 1)
391 return -EINVAL;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800392 ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100393 if (ret < 0)
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +0800394 return -EIO;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100395 return count;
396}
397
398static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
399
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +0800400static ssize_t show_ideapad_fan(struct device *dev,
401 struct device_attribute *attr,
402 char *buf)
403{
404 unsigned long result;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800405 struct ideapad_private *priv = dev_get_drvdata(dev);
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +0800406
Zhang Rui331e0ea2013-09-25 20:39:49 +0800407 if (read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result))
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +0800408 return sprintf(buf, "-1\n");
409 return sprintf(buf, "%lu\n", result);
410}
411
412static ssize_t store_ideapad_fan(struct device *dev,
413 struct device_attribute *attr,
414 const char *buf, size_t count)
415{
416 int ret, state;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800417 struct ideapad_private *priv = dev_get_drvdata(dev);
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +0800418
419 if (!count)
420 return 0;
421 if (sscanf(buf, "%i", &state) != 1)
422 return -EINVAL;
423 if (state < 0 || state > 4 || state == 3)
424 return -EINVAL;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800425 ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state);
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +0800426 if (ret < 0)
427 return -EIO;
428 return count;
429}
430
431static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan);
432
Ritesh Raj Sarraf36ac0d432017-02-18 00:17:56 +0530433static ssize_t touchpad_show(struct device *dev,
434 struct device_attribute *attr,
435 char *buf)
436{
437 struct ideapad_private *priv = dev_get_drvdata(dev);
438 unsigned long result;
439
440 if (read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result))
441 return sprintf(buf, "-1\n");
442 return sprintf(buf, "%lu\n", result);
443}
444
Arnd Bergmann46936fd2017-05-22 15:07:03 +0200445/* Switch to RO for now: It might be revisited in the future */
446static ssize_t __maybe_unused touchpad_store(struct device *dev,
447 struct device_attribute *attr,
448 const char *buf, size_t count)
Ritesh Raj Sarraf36ac0d432017-02-18 00:17:56 +0530449{
450 struct ideapad_private *priv = dev_get_drvdata(dev);
451 bool state;
452 int ret;
453
454 ret = kstrtobool(buf, &state);
455 if (ret)
456 return ret;
457
458 ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state);
459 if (ret < 0)
460 return -EIO;
461 return count;
462}
463
Andy Shevchenko7f363142017-05-07 14:28:30 +0300464static DEVICE_ATTR_RO(touchpad);
Ritesh Raj Sarraf36ac0d432017-02-18 00:17:56 +0530465
Hao Wei Teeade50292017-08-15 00:13:51 +0800466static ssize_t conservation_mode_show(struct device *dev,
467 struct device_attribute *attr,
468 char *buf)
469{
470 struct ideapad_private *priv = dev_get_drvdata(dev);
471 unsigned long result;
472
473 if (method_gbmd(priv->adev->handle, &result))
474 return sprintf(buf, "-1\n");
475 return sprintf(buf, "%u\n", test_bit(BM_CONSERVATION_BIT, &result));
476}
477
478static ssize_t conservation_mode_store(struct device *dev,
479 struct device_attribute *attr,
480 const char *buf, size_t count)
481{
482 struct ideapad_private *priv = dev_get_drvdata(dev);
483 bool state;
484 int ret;
485
486 ret = kstrtobool(buf, &state);
487 if (ret)
488 return ret;
489
490 ret = method_sbmc(priv->adev->handle, state ?
491 BMCMD_CONSERVATION_ON :
492 BMCMD_CONSERVATION_OFF);
493 if (ret < 0)
494 return -EIO;
495 return count;
496}
497
498static DEVICE_ATTR_RW(conservation_mode);
499
Ike Panhc3371f482011-06-30 19:50:40 +0800500static struct attribute *ideapad_attributes[] = {
501 &dev_attr_camera_power.attr,
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +0800502 &dev_attr_fan_mode.attr,
Ritesh Raj Sarraf36ac0d432017-02-18 00:17:56 +0530503 &dev_attr_touchpad.attr,
Hao Wei Teeade50292017-08-15 00:13:51 +0800504 &dev_attr_conservation_mode.attr,
Ike Panhc3371f482011-06-30 19:50:40 +0800505 NULL
506};
507
Al Viro587a1f12011-07-23 23:11:19 -0400508static umode_t ideapad_is_visible(struct kobject *kobj,
Ike Panhca84511f2011-06-30 19:50:47 +0800509 struct attribute *attr,
510 int idx)
511{
512 struct device *dev = container_of(kobj, struct device, kobj);
513 struct ideapad_private *priv = dev_get_drvdata(dev);
514 bool supported;
515
516 if (attr == &dev_attr_camera_power.attr)
517 supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg));
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +0800518 else if (attr == &dev_attr_fan_mode.attr) {
519 unsigned long value;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800520 supported = !read_ec_data(priv->adev->handle, VPCCMD_R_FAN,
521 &value);
Hao Wei Teeade50292017-08-15 00:13:51 +0800522 } else if (attr == &dev_attr_conservation_mode.attr) {
523 supported = acpi_has_method(priv->adev->handle, "GBMD") &&
524 acpi_has_method(priv->adev->handle, "SBMC");
Maxim Mikityanskiy0c7bbeb92012-07-06 16:08:11 +0800525 } else
Ike Panhca84511f2011-06-30 19:50:47 +0800526 supported = true;
527
528 return supported ? attr->mode : 0;
529}
530
Mathias Krause49458e82014-07-16 19:43:15 +0200531static const struct attribute_group ideapad_attribute_group = {
Ike Panhca84511f2011-06-30 19:50:47 +0800532 .is_visible = ideapad_is_visible,
Ike Panhc3371f482011-06-30 19:50:40 +0800533 .attrs = ideapad_attributes
534};
535
Ike Panhca4b5a272010-12-13 18:00:48 +0800536/*
537 * Rfkill
538 */
Ike Panhcc1f73652010-12-13 18:01:12 +0800539struct ideapad_rfk_data {
540 char *name;
541 int cfgbit;
542 int opcode;
543 int type;
544};
545
Mathias Krauseb3d94d72014-08-28 13:02:49 +0200546static const struct ideapad_rfk_data ideapad_rfk_data[] = {
Ike Panhc2be1dc22011-09-06 02:31:53 +0800547 { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN },
548 { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH },
549 { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN },
Ike Panhcc1f73652010-12-13 18:01:12 +0800550};
551
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100552static int ideapad_rfk_set(void *data, bool blocked)
553{
Zhang Rui331e0ea2013-09-25 20:39:49 +0800554 struct ideapad_rfk_priv *priv = data;
Arnd Bergmann4b200b42015-06-13 15:23:33 +0200555 int opcode = ideapad_rfk_data[priv->dev].opcode;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100556
Arnd Bergmann4b200b42015-06-13 15:23:33 +0200557 return write_ec_cmd(priv->priv->adev->handle, opcode, !blocked);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100558}
559
Bhumika Goyal3d59dfc2017-06-09 11:38:18 +0530560static const struct rfkill_ops ideapad_rfk_ops = {
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100561 .set_block = ideapad_rfk_set,
562};
563
Ike Panhc923de842011-09-06 02:32:01 +0800564static void ideapad_sync_rfk_state(struct ideapad_private *priv)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100565{
Hans de Goedece363c22014-06-23 16:45:51 +0200566 unsigned long hw_blocked = 0;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100567 int i;
568
Hans de Goedece363c22014-06-23 16:45:51 +0200569 if (priv->has_hw_rfkill_switch) {
570 if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked))
571 return;
572 hw_blocked = !hw_blocked;
573 }
Ike Panhc2b7266b2010-10-01 15:39:49 +0800574
Ike Panhcc1f73652010-12-13 18:01:12 +0800575 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
David Woodhousece326322010-08-11 17:59:35 +0100576 if (priv->rfk[i])
577 rfkill_set_hw_state(priv->rfk[i], hw_blocked);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100578}
579
Zhang Rui75a11f12013-09-25 20:39:48 +0800580static int ideapad_register_rfkill(struct ideapad_private *priv, int dev)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100581{
582 int ret;
Ike Panhc2b7266b2010-10-01 15:39:49 +0800583 unsigned long sw_blocked;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100584
Ike Panhcbfa97b72010-10-01 15:40:22 +0800585 if (no_bt_rfkill &&
586 (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) {
587 /* Force to enable bluetooth when no_bt_rfkill=1 */
Zhang Rui331e0ea2013-09-25 20:39:49 +0800588 write_ec_cmd(priv->adev->handle,
Ike Panhcbfa97b72010-10-01 15:40:22 +0800589 ideapad_rfk_data[dev].opcode, 1);
590 return 0;
591 }
Zhang Rui331e0ea2013-09-25 20:39:49 +0800592 priv->rfk_priv[dev].dev = dev;
593 priv->rfk_priv[dev].priv = priv;
Ike Panhcbfa97b72010-10-01 15:40:22 +0800594
Zhang Rui75a11f12013-09-25 20:39:48 +0800595 priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev].name,
Zhang Ruib5c37b72013-09-25 20:39:50 +0800596 &priv->platform_device->dev,
Zhang Rui75a11f12013-09-25 20:39:48 +0800597 ideapad_rfk_data[dev].type,
598 &ideapad_rfk_ops,
Zhang Rui331e0ea2013-09-25 20:39:49 +0800599 &priv->rfk_priv[dev]);
David Woodhousece326322010-08-11 17:59:35 +0100600 if (!priv->rfk[dev])
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100601 return -ENOMEM;
602
Zhang Rui331e0ea2013-09-25 20:39:49 +0800603 if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1,
Ike Panhc2b7266b2010-10-01 15:39:49 +0800604 &sw_blocked)) {
605 rfkill_init_sw_state(priv->rfk[dev], 0);
606 } else {
607 sw_blocked = !sw_blocked;
608 rfkill_init_sw_state(priv->rfk[dev], sw_blocked);
609 }
610
David Woodhousece326322010-08-11 17:59:35 +0100611 ret = rfkill_register(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100612 if (ret) {
David Woodhousece326322010-08-11 17:59:35 +0100613 rfkill_destroy(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100614 return ret;
615 }
616 return 0;
617}
618
Zhang Rui75a11f12013-09-25 20:39:48 +0800619static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100620{
David Woodhousece326322010-08-11 17:59:35 +0100621 if (!priv->rfk[dev])
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100622 return;
623
David Woodhousece326322010-08-11 17:59:35 +0100624 rfkill_unregister(priv->rfk[dev]);
625 rfkill_destroy(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100626}
627
Ike Panhc98ee6912010-12-13 18:00:15 +0800628/*
629 * Platform device
630 */
Zhang Ruib5c37b72013-09-25 20:39:50 +0800631static int ideapad_sysfs_init(struct ideapad_private *priv)
Ike Panhc98ee6912010-12-13 18:00:15 +0800632{
Zhang Ruib5c37b72013-09-25 20:39:50 +0800633 return sysfs_create_group(&priv->platform_device->dev.kobj,
Ike Panhcc9f718d2010-12-13 18:00:27 +0800634 &ideapad_attribute_group);
Ike Panhc98ee6912010-12-13 18:00:15 +0800635}
636
Zhang Ruib5c37b72013-09-25 20:39:50 +0800637static void ideapad_sysfs_exit(struct ideapad_private *priv)
Ike Panhc98ee6912010-12-13 18:00:15 +0800638{
Ike Panhc8693ae82010-12-13 18:01:01 +0800639 sysfs_remove_group(&priv->platform_device->dev.kobj,
Ike Panhcc9f718d2010-12-13 18:00:27 +0800640 &ideapad_attribute_group);
Ike Panhc98ee6912010-12-13 18:00:15 +0800641}
Ike Panhc98ee6912010-12-13 18:00:15 +0800642
Ike Panhcf63409a2010-12-13 18:00:38 +0800643/*
644 * input device
645 */
646static const struct key_entry ideapad_keymap[] = {
Ike Panhcf43d9ec2011-09-06 02:32:10 +0800647 { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } },
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +0800648 { KE_KEY, 7, { KEY_CAMERA } },
Alex Hung48f67d62016-06-06 09:46:11 +0800649 { KE_KEY, 8, { KEY_MICMUTE } },
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +0800650 { KE_KEY, 11, { KEY_F16 } },
Ike Panhcf43d9ec2011-09-06 02:32:10 +0800651 { KE_KEY, 13, { KEY_WLAN } },
652 { KE_KEY, 16, { KEY_PROG1 } },
653 { KE_KEY, 17, { KEY_PROG2 } },
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +0800654 { KE_KEY, 64, { KEY_PROG3 } },
655 { KE_KEY, 65, { KEY_PROG4 } },
Maxim Mikityanskiy07a4a4f2012-07-06 16:08:00 +0800656 { KE_KEY, 66, { KEY_TOUCHPAD_OFF } },
657 { KE_KEY, 67, { KEY_TOUCHPAD_ON } },
Arnd Bergmann74caab92015-11-06 22:28:49 +0100658 { KE_KEY, 128, { KEY_ESC } },
659
Ike Panhcf63409a2010-12-13 18:00:38 +0800660 { KE_END, 0 },
661};
662
Greg Kroah-Hartmanb859f152012-12-21 13:18:33 -0800663static int ideapad_input_init(struct ideapad_private *priv)
Ike Panhcf63409a2010-12-13 18:00:38 +0800664{
665 struct input_dev *inputdev;
666 int error;
667
668 inputdev = input_allocate_device();
Joe Perchesb222cca2013-10-23 12:14:52 -0700669 if (!inputdev)
Ike Panhcf63409a2010-12-13 18:00:38 +0800670 return -ENOMEM;
Ike Panhcf63409a2010-12-13 18:00:38 +0800671
672 inputdev->name = "Ideapad extra buttons";
673 inputdev->phys = "ideapad/input0";
674 inputdev->id.bustype = BUS_HOST;
Ike Panhc8693ae82010-12-13 18:01:01 +0800675 inputdev->dev.parent = &priv->platform_device->dev;
Ike Panhcf63409a2010-12-13 18:00:38 +0800676
677 error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL);
678 if (error) {
679 pr_err("Unable to setup input device keymap\n");
680 goto err_free_dev;
681 }
682
683 error = input_register_device(inputdev);
684 if (error) {
685 pr_err("Unable to register input device\n");
Michał Kępieńc973d4b2017-03-09 13:11:44 +0100686 goto err_free_dev;
Ike Panhcf63409a2010-12-13 18:00:38 +0800687 }
688
Ike Panhc8693ae82010-12-13 18:01:01 +0800689 priv->inputdev = inputdev;
Ike Panhcf63409a2010-12-13 18:00:38 +0800690 return 0;
691
Ike Panhcf63409a2010-12-13 18:00:38 +0800692err_free_dev:
693 input_free_device(inputdev);
694 return error;
695}
696
Axel Lin7451a552011-07-27 15:27:34 +0800697static void ideapad_input_exit(struct ideapad_private *priv)
Ike Panhcf63409a2010-12-13 18:00:38 +0800698{
Ike Panhc8693ae82010-12-13 18:01:01 +0800699 input_unregister_device(priv->inputdev);
700 priv->inputdev = NULL;
Ike Panhcf63409a2010-12-13 18:00:38 +0800701}
702
Ike Panhc8693ae82010-12-13 18:01:01 +0800703static void ideapad_input_report(struct ideapad_private *priv,
704 unsigned long scancode)
Ike Panhcf63409a2010-12-13 18:00:38 +0800705{
Ike Panhc8693ae82010-12-13 18:01:01 +0800706 sparse_keymap_report_event(priv->inputdev, scancode, 1, true);
Ike Panhcf63409a2010-12-13 18:00:38 +0800707}
Ike Panhcf63409a2010-12-13 18:00:38 +0800708
Ike Panhcf43d9ec2011-09-06 02:32:10 +0800709static void ideapad_input_novokey(struct ideapad_private *priv)
710{
711 unsigned long long_pressed;
712
Zhang Rui331e0ea2013-09-25 20:39:49 +0800713 if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed))
Ike Panhcf43d9ec2011-09-06 02:32:10 +0800714 return;
715 if (long_pressed)
716 ideapad_input_report(priv, 17);
717 else
718 ideapad_input_report(priv, 16);
719}
720
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +0800721static void ideapad_check_special_buttons(struct ideapad_private *priv)
722{
723 unsigned long bit, value;
724
Zhang Rui331e0ea2013-09-25 20:39:49 +0800725 read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value);
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +0800726
727 for (bit = 0; bit < 16; bit++) {
728 if (test_bit(bit, &value)) {
729 switch (bit) {
Maxim Mikityanskiya1ec56e2013-03-20 12:34:17 +0200730 case 0: /* Z580 */
731 case 6: /* Z570 */
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +0800732 /* Thermal Management button */
733 ideapad_input_report(priv, 65);
734 break;
735 case 1:
736 /* OneKey Theater button */
737 ideapad_input_report(priv, 64);
738 break;
Maxim Mikityanskiya1ec56e2013-03-20 12:34:17 +0200739 default:
740 pr_info("Unknown special button: %lu\n", bit);
741 break;
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +0800742 }
743 }
744 }
745}
746
Ike Panhca4b5a272010-12-13 18:00:48 +0800747/*
Ike Panhca4ecbb82011-06-30 19:50:52 +0800748 * backlight
749 */
750static int ideapad_backlight_get_brightness(struct backlight_device *blightdev)
751{
Zhang Rui331e0ea2013-09-25 20:39:49 +0800752 struct ideapad_private *priv = bl_get_data(blightdev);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800753 unsigned long now;
754
Zhang Rui331e0ea2013-09-25 20:39:49 +0800755 if (!priv)
756 return -EINVAL;
757
758 if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800759 return -EIO;
760 return now;
761}
762
763static int ideapad_backlight_update_status(struct backlight_device *blightdev)
764{
Zhang Rui331e0ea2013-09-25 20:39:49 +0800765 struct ideapad_private *priv = bl_get_data(blightdev);
766
767 if (!priv)
768 return -EINVAL;
769
770 if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL,
Ike Panhc2be1dc22011-09-06 02:31:53 +0800771 blightdev->props.brightness))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800772 return -EIO;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800773 if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER,
Ike Panhca4ecbb82011-06-30 19:50:52 +0800774 blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1))
775 return -EIO;
776
777 return 0;
778}
779
780static const struct backlight_ops ideapad_backlight_ops = {
781 .get_brightness = ideapad_backlight_get_brightness,
782 .update_status = ideapad_backlight_update_status,
783};
784
785static int ideapad_backlight_init(struct ideapad_private *priv)
786{
787 struct backlight_device *blightdev;
788 struct backlight_properties props;
789 unsigned long max, now, power;
790
Zhang Rui331e0ea2013-09-25 20:39:49 +0800791 if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800792 return -EIO;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800793 if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800794 return -EIO;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800795 if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800796 return -EIO;
797
798 memset(&props, 0, sizeof(struct backlight_properties));
799 props.max_brightness = max;
800 props.type = BACKLIGHT_PLATFORM;
801 blightdev = backlight_device_register("ideapad",
802 &priv->platform_device->dev,
803 priv,
804 &ideapad_backlight_ops,
805 &props);
806 if (IS_ERR(blightdev)) {
807 pr_err("Could not register backlight device\n");
808 return PTR_ERR(blightdev);
809 }
810
811 priv->blightdev = blightdev;
812 blightdev->props.brightness = now;
813 blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
814 backlight_update_status(blightdev);
815
816 return 0;
817}
818
819static void ideapad_backlight_exit(struct ideapad_private *priv)
820{
Markus Elfring00981812014-11-24 20:30:29 +0100821 backlight_device_unregister(priv->blightdev);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800822 priv->blightdev = NULL;
823}
824
825static void ideapad_backlight_notify_power(struct ideapad_private *priv)
826{
827 unsigned long power;
828 struct backlight_device *blightdev = priv->blightdev;
829
Rene Bollfordd4afc772011-10-23 09:56:42 +0200830 if (!blightdev)
831 return;
Zhang Rui331e0ea2013-09-25 20:39:49 +0800832 if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power))
Ike Panhca4ecbb82011-06-30 19:50:52 +0800833 return;
834 blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
835}
836
837static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
838{
839 unsigned long now;
840
841 /* if we control brightness via acpi video driver */
842 if (priv->blightdev == NULL) {
Zhang Rui331e0ea2013-09-25 20:39:49 +0800843 read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800844 return;
845 }
846
847 backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY);
848}
849
850/*
Ike Panhca4b5a272010-12-13 18:00:48 +0800851 * module init/exit
852 */
Zhang Rui75a11f12013-09-25 20:39:48 +0800853static void ideapad_sync_touchpad_state(struct ideapad_private *priv)
Maxim Mikityanskiy07a4a4f2012-07-06 16:08:00 +0800854{
Maxim Mikityanskiy07a4a4f2012-07-06 16:08:00 +0800855 unsigned long value;
856
857 /* Without reading from EC touchpad LED doesn't switch state */
Zhang Rui75a11f12013-09-25 20:39:48 +0800858 if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) {
Maxim Mikityanskiy07a4a4f2012-07-06 16:08:00 +0800859 /* Some IdeaPads don't really turn off touchpad - they only
860 * switch the LED state. We (de)activate KBC AUX port to turn
861 * touchpad off and on. We send KEY_TOUCHPAD_OFF and
862 * KEY_TOUCHPAD_ON to not to get out of sync with LED */
863 unsigned char param;
864 i8042_command(&param, value ? I8042_CMD_AUX_ENABLE :
865 I8042_CMD_AUX_DISABLE);
866 ideapad_input_report(priv, value ? 67 : 66);
867 }
868}
869
Zhang Ruib5c37b72013-09-25 20:39:50 +0800870static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100871{
Zhang Ruib5c37b72013-09-25 20:39:50 +0800872 struct ideapad_private *priv = data;
Ike Panhc8e7d3542010-10-01 15:39:05 +0800873 unsigned long vpc1, vpc2, vpc_bit;
874
Ike Panhc2be1dc22011-09-06 02:31:53 +0800875 if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))
Ike Panhc8e7d3542010-10-01 15:39:05 +0800876 return;
Ike Panhc2be1dc22011-09-06 02:31:53 +0800877 if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2))
Ike Panhc8e7d3542010-10-01 15:39:05 +0800878 return;
879
880 vpc1 = (vpc2 << 8) | vpc1;
881 for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
882 if (test_bit(vpc_bit, &vpc1)) {
Ike Panhca4ecbb82011-06-30 19:50:52 +0800883 switch (vpc_bit) {
884 case 9:
Ike Panhc923de842011-09-06 02:32:01 +0800885 ideapad_sync_rfk_state(priv);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800886 break;
Ike Panhc20a769c2012-05-03 17:38:46 +0800887 case 13:
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +0800888 case 11:
Alex Hung48f67d62016-06-06 09:46:11 +0800889 case 8:
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +0800890 case 7:
Ike Panhc20a769c2012-05-03 17:38:46 +0800891 case 6:
892 ideapad_input_report(priv, vpc_bit);
893 break;
Maxim Mikityanskiy07a4a4f2012-07-06 16:08:00 +0800894 case 5:
Zhang Rui75a11f12013-09-25 20:39:48 +0800895 ideapad_sync_touchpad_state(priv);
Maxim Mikityanskiy07a4a4f2012-07-06 16:08:00 +0800896 break;
Ike Panhca4ecbb82011-06-30 19:50:52 +0800897 case 4:
898 ideapad_backlight_notify_brightness(priv);
899 break;
Ike Panhcf43d9ec2011-09-06 02:32:10 +0800900 case 3:
901 ideapad_input_novokey(priv);
902 break;
Ike Panhca4ecbb82011-06-30 19:50:52 +0800903 case 2:
904 ideapad_backlight_notify_power(priv);
905 break;
Maxim Mikityanskiy296f9fe2012-07-06 16:07:50 +0800906 case 0:
907 ideapad_check_special_buttons(priv);
908 break;
Hao Wei Tee3cfd9562017-05-22 18:38:55 +0800909 case 1:
910 /* Some IdeaPads report event 1 every ~20
911 * seconds while on battery power; some
912 * report this when changing to/from tablet
913 * mode. Squelch this event.
914 */
915 break;
Ike Panhca4ecbb82011-06-30 19:50:52 +0800916 default:
Ike Panhc20a769c2012-05-03 17:38:46 +0800917 pr_info("Unknown event: %lu\n", vpc_bit);
Ike Panhca4ecbb82011-06-30 19:50:52 +0800918 }
Ike Panhc8e7d3542010-10-01 15:39:05 +0800919 }
920 }
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100921}
922
Arnd Bergmann74caab92015-11-06 22:28:49 +0100923#if IS_ENABLED(CONFIG_ACPI_WMI)
924static void ideapad_wmi_notify(u32 value, void *context)
925{
926 switch (value) {
927 case 128:
928 ideapad_input_report(context, value);
929 break;
930 default:
931 pr_info("Unknown WMI event %u\n", value);
932 }
933}
934#endif
935
Hans de Goedece363c22014-06-23 16:45:51 +0200936/*
937 * Some ideapads don't have a hardware rfkill switch, reading VPCCMD_R_RF
938 * always results in 0 on these models, causing ideapad_laptop to wrongly
939 * report all radios as hardware-blocked.
940 */
Mathias Krauseb3d94d72014-08-28 13:02:49 +0200941static const struct dmi_system_id no_hw_rfkill_list[] = {
Hans de Goede85093f72014-05-13 16:00:28 +0200942 {
Jiaxun Yangae7c8cb2017-12-02 21:45:34 +0800943 .ident = "Lenovo RESCUER R720-15IKBN",
944 .matches = {
945 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
946 DMI_MATCH(DMI_BOARD_NAME, "80WW"),
947 },
948 },
949 {
Philippe Coval9b071a42015-05-02 15:14:08 +0200950 .ident = "Lenovo G40-30",
951 .matches = {
952 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
953 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G40-30"),
954 },
955 },
956 {
Dmitry Tunin4fa9dab2015-01-18 15:44:40 +0300957 .ident = "Lenovo G50-30",
958 .matches = {
959 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
960 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"),
961 },
962 },
963 {
Yang Jiaxun710c0592017-07-04 14:39:19 +0000964 .ident = "Lenovo V310-14IKB",
965 .matches = {
966 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
967 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-14IKB"),
968 },
969 },
970 {
971 .ident = "Lenovo V310-14ISK",
972 .matches = {
973 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
974 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-14ISK"),
975 },
976 },
977 {
978 .ident = "Lenovo V310-15IKB",
979 .matches = {
980 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
981 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-15IKB"),
982 },
983 },
984 {
Andy Shevchenkoccc71792017-02-21 20:53:48 +0100985 .ident = "Lenovo V310-15ISK",
986 .matches = {
Andy Shevchenko5e8f42a2017-07-04 18:34:39 +0300987 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
988 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-15ISK"),
Andy Shevchenkoccc71792017-02-21 20:53:48 +0100989 },
990 },
991 {
Sven Eckelmann0df4b802017-07-01 08:20:18 +0200992 .ident = "Lenovo V510-15IKB",
993 .matches = {
994 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
995 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V510-15IKB"),
996 },
997 },
998 {
Yang Jiaxun710c0592017-07-04 14:39:19 +0000999 .ident = "Lenovo ideapad 300-15IBR",
1000 .matches = {
1001 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1002 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300-15IBR"),
1003 },
1004 },
1005 {
1006 .ident = "Lenovo ideapad 300-15IKB",
1007 .matches = {
1008 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1009 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300-15IKB"),
1010 },
1011 },
1012 {
1013 .ident = "Lenovo ideapad 300S-11IBR",
1014 .matches = {
1015 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1016 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300S-11BR"),
1017 },
1018 },
1019 {
1020 .ident = "Lenovo ideapad 310-15ABR",
1021 .matches = {
1022 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1023 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15ABR"),
1024 },
1025 },
1026 {
1027 .ident = "Lenovo ideapad 310-15IAP",
1028 .matches = {
1029 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1030 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15IAP"),
1031 },
1032 },
1033 {
Sven Rebhan1f3bc532017-02-21 20:53:48 +01001034 .ident = "Lenovo ideapad 310-15IKB",
1035 .matches = {
Andy Shevchenko5e8f42a2017-07-04 18:34:39 +03001036 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1037 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15IKB"),
Sven Rebhan1f3bc532017-02-21 20:53:48 +01001038 },
1039 },
1040 {
Yang Jiaxun710c0592017-07-04 14:39:19 +00001041 .ident = "Lenovo ideapad 310-15ISK",
1042 .matches = {
1043 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1044 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15ISK"),
1045 },
1046 },
1047 {
1048 .ident = "Lenovo ideapad Y700-14ISK",
1049 .matches = {
1050 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1051 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-14ISK"),
1052 },
1053 },
1054 {
velemase2970462016-12-06 22:17:43 +03001055 .ident = "Lenovo ideapad Y700-15ACZ",
1056 .matches = {
1057 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1058 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-15ACZ"),
1059 },
1060 },
1061 {
John Dahlstrom4db96752016-02-27 00:09:58 -06001062 .ident = "Lenovo ideapad Y700-15ISK",
1063 .matches = {
1064 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1065 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-15ISK"),
1066 },
1067 },
1068 {
1069 .ident = "Lenovo ideapad Y700 Touch-15ISK",
1070 .matches = {
1071 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1072 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700 Touch-15ISK"),
1073 },
1074 },
1075 {
Josh Boyeredde3162015-12-09 21:12:52 -05001076 .ident = "Lenovo ideapad Y700-17ISK",
1077 .matches = {
1078 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1079 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-17ISK"),
1080 },
1081 },
1082 {
Olle Liljenzin5d9f40b2017-06-18 13:09:31 +02001083 .ident = "Lenovo Legion Y520-15IKBN",
1084 .matches = {
1085 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1086 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y520-15IKBN"),
1087 },
1088 },
1089 {
Olle Liljenzin28d71ae2018-01-07 20:53:12 +01001090 .ident = "Lenovo Legion Y720-15IKB",
1091 .matches = {
1092 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1093 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y720-15IKB"),
1094 },
1095 },
1096 {
Olle Liljenzinb2f2fe22017-06-18 14:37:58 +02001097 .ident = "Lenovo Legion Y720-15IKBN",
1098 .matches = {
1099 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1100 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y720-15IKBN"),
1101 },
1102 },
1103 {
Hans de Goedece363c22014-06-23 16:45:51 +02001104 .ident = "Lenovo Yoga 2 11 / 13 / Pro",
Hans de Goede85093f72014-05-13 16:00:28 +02001105 .matches = {
1106 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
Hans de Goedece363c22014-06-23 16:45:51 +02001107 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 2"),
Hans de Goede85093f72014-05-13 16:00:28 +02001108 },
1109 },
Stephan Mueller725c7f62014-10-27 04:09:50 +01001110 {
Sebastian Krzyszkowiak6d212b82015-07-19 01:10:21 +02001111 .ident = "Lenovo Yoga 2 11 / 13 / Pro",
1112 .matches = {
1113 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1114 DMI_MATCH(DMI_BOARD_NAME, "Yoga2"),
1115 },
1116 },
1117 {
Arnd Bergmannc789fff2015-11-06 23:26:59 +01001118 .ident = "Lenovo Yoga 3 1170 / 1470",
1119 .matches = {
1120 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1121 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 3"),
1122 },
1123 },
1124 {
Stephan Mueller725c7f62014-10-27 04:09:50 +01001125 .ident = "Lenovo Yoga 3 Pro 1370",
1126 .matches = {
1127 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
Arnd Bergmannc789fff2015-11-06 23:26:59 +01001128 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 3"),
Stephan Mueller725c7f62014-10-27 04:09:50 +01001129 },
1130 },
Hans de Goedef71c8822015-11-09 17:09:05 +01001131 {
Josh Boyer6b31de32016-01-24 10:46:42 -05001132 .ident = "Lenovo Yoga 700",
1133 .matches = {
1134 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1135 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 700"),
1136 },
1137 },
1138 {
Hans de Goedef71c8822015-11-09 17:09:05 +01001139 .ident = "Lenovo Yoga 900",
1140 .matches = {
1141 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1142 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 900"),
1143 },
1144 },
Brian Masney40c30bb2016-10-11 19:28:02 -04001145 {
Mika Westerberg446647d2016-10-19 13:27:40 +03001146 .ident = "Lenovo Yoga 900",
1147 .matches = {
1148 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1149 DMI_MATCH(DMI_BOARD_NAME, "VIUU4"),
1150 },
1151 },
1152 {
Brian Masney40c30bb2016-10-11 19:28:02 -04001153 .ident = "Lenovo YOGA 910-13IKB",
1154 .matches = {
1155 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1156 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 910-13IKB"),
1157 },
1158 },
Philipp Hugb231669c2017-10-24 16:32:22 +02001159 {
1160 .ident = "Lenovo YOGA 920-13IKB",
1161 .matches = {
1162 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
1163 DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 920-13IKB"),
1164 },
1165 },
Hans de Goede85093f72014-05-13 16:00:28 +02001166 {}
1167};
1168
Zhang Ruib5c37b72013-09-25 20:39:50 +08001169static int ideapad_acpi_add(struct platform_device *pdev)
1170{
1171 int ret, i;
1172 int cfg;
1173 struct ideapad_private *priv;
1174 struct acpi_device *adev;
1175
1176 ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev);
1177 if (ret)
1178 return -ENODEV;
1179
1180 if (read_method_int(adev->handle, "_CFG", &cfg))
1181 return -ENODEV;
1182
Himangi Saraogib3facd72014-06-09 17:46:50 -04001183 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
Zhang Ruib5c37b72013-09-25 20:39:50 +08001184 if (!priv)
1185 return -ENOMEM;
1186
1187 dev_set_drvdata(&pdev->dev, priv);
1188 priv->cfg = cfg;
1189 priv->adev = adev;
1190 priv->platform_device = pdev;
Hans de Goedece363c22014-06-23 16:45:51 +02001191 priv->has_hw_rfkill_switch = !dmi_check_system(no_hw_rfkill_list);
Zhang Ruib5c37b72013-09-25 20:39:50 +08001192
1193 ret = ideapad_sysfs_init(priv);
1194 if (ret)
Himangi Saraogib3facd72014-06-09 17:46:50 -04001195 return ret;
Zhang Ruib5c37b72013-09-25 20:39:50 +08001196
1197 ret = ideapad_debugfs_init(priv);
1198 if (ret)
1199 goto debugfs_failed;
1200
1201 ret = ideapad_input_init(priv);
1202 if (ret)
1203 goto input_failed;
1204
Hans de Goedece363c22014-06-23 16:45:51 +02001205 /*
1206 * On some models without a hw-switch (the yoga 2 13 at least)
1207 * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work.
1208 */
1209 if (!priv->has_hw_rfkill_switch)
1210 write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1);
1211
1212 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
1213 if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg))
1214 ideapad_register_rfkill(priv, i);
1215
Zhang Ruib5c37b72013-09-25 20:39:50 +08001216 ideapad_sync_rfk_state(priv);
1217 ideapad_sync_touchpad_state(priv);
1218
Hans de Goede26bff5f2015-06-16 16:28:04 +02001219 if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
Zhang Ruib5c37b72013-09-25 20:39:50 +08001220 ret = ideapad_backlight_init(priv);
1221 if (ret && ret != -ENODEV)
1222 goto backlight_failed;
1223 }
1224 ret = acpi_install_notify_handler(adev->handle,
1225 ACPI_DEVICE_NOTIFY, ideapad_acpi_notify, priv);
1226 if (ret)
1227 goto notification_failed;
Arnd Bergmann2d98e0b2016-05-09 23:49:21 +02001228
Arnd Bergmann74caab92015-11-06 22:28:49 +01001229#if IS_ENABLED(CONFIG_ACPI_WMI)
Arnd Bergmann2d98e0b2016-05-09 23:49:21 +02001230 for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) {
1231 ret = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i],
1232 ideapad_wmi_notify, priv);
1233 if (ret == AE_OK) {
1234 priv->fnesc_guid = ideapad_wmi_fnesc_events[i];
1235 break;
1236 }
1237 }
Arnd Bergmann74caab92015-11-06 22:28:49 +01001238 if (ret != AE_OK && ret != AE_NOT_EXIST)
1239 goto notification_failed_wmi;
1240#endif
Zhang Ruib5c37b72013-09-25 20:39:50 +08001241
1242 return 0;
Arnd Bergmann74caab92015-11-06 22:28:49 +01001243#if IS_ENABLED(CONFIG_ACPI_WMI)
1244notification_failed_wmi:
1245 acpi_remove_notify_handler(priv->adev->handle,
1246 ACPI_DEVICE_NOTIFY, ideapad_acpi_notify);
1247#endif
Zhang Ruib5c37b72013-09-25 20:39:50 +08001248notification_failed:
1249 ideapad_backlight_exit(priv);
1250backlight_failed:
1251 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
1252 ideapad_unregister_rfkill(priv, i);
1253 ideapad_input_exit(priv);
1254input_failed:
1255 ideapad_debugfs_exit(priv);
1256debugfs_failed:
1257 ideapad_sysfs_exit(priv);
Zhang Ruib5c37b72013-09-25 20:39:50 +08001258 return ret;
1259}
1260
1261static int ideapad_acpi_remove(struct platform_device *pdev)
1262{
1263 struct ideapad_private *priv = dev_get_drvdata(&pdev->dev);
1264 int i;
1265
Arnd Bergmann74caab92015-11-06 22:28:49 +01001266#if IS_ENABLED(CONFIG_ACPI_WMI)
Arnd Bergmann2d98e0b2016-05-09 23:49:21 +02001267 if (priv->fnesc_guid)
1268 wmi_remove_notify_handler(priv->fnesc_guid);
Arnd Bergmann74caab92015-11-06 22:28:49 +01001269#endif
Zhang Ruib5c37b72013-09-25 20:39:50 +08001270 acpi_remove_notify_handler(priv->adev->handle,
1271 ACPI_DEVICE_NOTIFY, ideapad_acpi_notify);
1272 ideapad_backlight_exit(priv);
1273 for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
1274 ideapad_unregister_rfkill(priv, i);
1275 ideapad_input_exit(priv);
1276 ideapad_debugfs_exit(priv);
1277 ideapad_sysfs_exit(priv);
1278 dev_set_drvdata(&pdev->dev, NULL);
Zhang Ruib5c37b72013-09-25 20:39:50 +08001279
1280 return 0;
1281}
1282
Zhang Rui11fa8da2013-09-25 20:39:46 +08001283#ifdef CONFIG_PM_SLEEP
Maxim Mikityanskiy07a4a4f2012-07-06 16:08:00 +08001284static int ideapad_acpi_resume(struct device *device)
1285{
Zhang Rui75a11f12013-09-25 20:39:48 +08001286 struct ideapad_private *priv;
1287
1288 if (!device)
1289 return -EINVAL;
1290 priv = dev_get_drvdata(device);
1291
1292 ideapad_sync_rfk_state(priv);
1293 ideapad_sync_touchpad_state(priv);
Maxim Mikityanskiy07a4a4f2012-07-06 16:08:00 +08001294 return 0;
1295}
Zhang Ruib5c37b72013-09-25 20:39:50 +08001296#endif
Maxim Mikityanskiy07a4a4f2012-07-06 16:08:00 +08001297static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume);
1298
Zhang Ruib5c37b72013-09-25 20:39:50 +08001299static const struct acpi_device_id ideapad_device_ids[] = {
1300 { "VPC2004", 0},
1301 { "", 0},
David Woodhouse58ac7aa2010-08-10 23:44:05 +01001302};
Zhang Ruib5c37b72013-09-25 20:39:50 +08001303MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
1304
1305static struct platform_driver ideapad_acpi_driver = {
1306 .probe = ideapad_acpi_add,
1307 .remove = ideapad_acpi_remove,
1308 .driver = {
1309 .name = "ideapad_acpi",
Zhang Ruib5c37b72013-09-25 20:39:50 +08001310 .pm = &ideapad_pm,
1311 .acpi_match_table = ACPI_PTR(ideapad_device_ids),
1312 },
1313};
1314
1315module_platform_driver(ideapad_acpi_driver);
David Woodhouse58ac7aa2010-08-10 23:44:05 +01001316
1317MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
1318MODULE_DESCRIPTION("IdeaPad ACPI Extras");
1319MODULE_LICENSE("GPL");