ideapad: use return value of _CFG to tell if device exist or not
[deliverable/linux.git] / drivers / platform / x86 / ideapad_acpi.c
CommitLineData
58ac7aa0
DW
1/*
2 * ideapad_acpi.c - Lenovo IdeaPad ACPI Extras
3 *
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
23#include <linux/kernel.h>
24#include <linux/module.h>
25#include <linux/init.h>
26#include <linux/types.h>
27#include <acpi/acpi_bus.h>
28#include <acpi/acpi_drivers.h>
29#include <linux/rfkill.h>
30
31#define IDEAPAD_DEV_CAMERA 0
32#define IDEAPAD_DEV_WLAN 1
33#define IDEAPAD_DEV_BLUETOOTH 2
34#define IDEAPAD_DEV_3G 3
35#define IDEAPAD_DEV_KILLSW 4
36
ce326329
DW
37struct ideapad_private {
38 struct rfkill *rfk[5];
58ac7aa0 39};
ce326329
DW
40
41static struct {
42 char *name;
dfa7f6fe 43 int cfgbit;
ce326329
DW
44 int type;
45} ideapad_rfk_data[] = {
dfa7f6fe
IP
46 { "ideapad_camera", 19, NUM_RFKILL_TYPES },
47 { "ideapad_wlan", 18, RFKILL_TYPE_WLAN },
48 { "ideapad_bluetooth", 16, RFKILL_TYPE_BLUETOOTH },
49 { "ideapad_3g", 17, RFKILL_TYPE_WWAN },
50 { "ideapad_killsw", 0, RFKILL_TYPE_WLAN }
58ac7aa0
DW
51};
52
6a09f21d
IP
53/*
54 * ACPI Helpers
55 */
56#define IDEAPAD_EC_TIMEOUT (100) /* in ms */
57
58static int read_method_int(acpi_handle handle, const char *method, int *val)
59{
60 acpi_status status;
61 unsigned long long result;
62
63 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
64 if (ACPI_FAILURE(status)) {
65 *val = -1;
66 return -1;
67 } else {
68 *val = result;
69 return 0;
70 }
71}
72
73static int method_vpcr(acpi_handle handle, int cmd, int *ret)
74{
75 acpi_status status;
76 unsigned long long result;
77 struct acpi_object_list params;
78 union acpi_object in_obj;
79
80 params.count = 1;
81 params.pointer = &in_obj;
82 in_obj.type = ACPI_TYPE_INTEGER;
83 in_obj.integer.value = cmd;
84
85 status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
86
87 if (ACPI_FAILURE(status)) {
88 *ret = -1;
89 return -1;
90 } else {
91 *ret = result;
92 return 0;
93 }
94}
95
96static int method_vpcw(acpi_handle handle, int cmd, int data)
97{
98 struct acpi_object_list params;
99 union acpi_object in_obj[2];
100 acpi_status status;
101
102 params.count = 2;
103 params.pointer = in_obj;
104 in_obj[0].type = ACPI_TYPE_INTEGER;
105 in_obj[0].integer.value = cmd;
106 in_obj[1].type = ACPI_TYPE_INTEGER;
107 in_obj[1].integer.value = data;
108
109 status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
110 if (status != AE_OK)
111 return -1;
112 return 0;
113}
114
115static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
116{
117 int val;
118 unsigned long int end_jiffies;
119
120 if (method_vpcw(handle, 1, cmd))
121 return -1;
122
123 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
124 time_before(jiffies, end_jiffies);) {
125 schedule();
126 if (method_vpcr(handle, 1, &val))
127 return -1;
128 if (val == 0) {
129 if (method_vpcr(handle, 0, &val))
130 return -1;
131 *data = val;
132 return 0;
133 }
134 }
135 pr_err("timeout in read_ec_cmd\n");
136 return -1;
137}
138
139static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)
140{
141 int val;
142 unsigned long int end_jiffies;
143
144 if (method_vpcw(handle, 0, data))
145 return -1;
146 if (method_vpcw(handle, 1, cmd))
147 return -1;
148
149 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
150 time_before(jiffies, end_jiffies);) {
151 schedule();
152 if (method_vpcr(handle, 1, &val))
153 return -1;
154 if (val == 0)
155 return 0;
156 }
157 pr_err("timeout in write_ec_cmd\n");
158 return -1;
159}
160/* the above is ACPI helpers */
161
58ac7aa0
DW
162static int ideapad_dev_get_state(int device)
163{
164 acpi_status status;
165 union acpi_object in_param;
166 struct acpi_object_list input = { 1, &in_param };
167 struct acpi_buffer output;
168 union acpi_object out_obj;
169
170 output.length = sizeof(out_obj);
171 output.pointer = &out_obj;
172
173 in_param.type = ACPI_TYPE_INTEGER;
174 in_param.integer.value = device + 1;
175
176 status = acpi_evaluate_object(NULL, "\\_SB_.GECN", &input, &output);
177 if (ACPI_FAILURE(status)) {
178 printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method failed %d\n", status);
179 return -ENODEV;
180 }
181 if (out_obj.type != ACPI_TYPE_INTEGER) {
182 printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method returned unexpected type\n");
183 return -ENODEV;
184 }
185 return out_obj.integer.value;
186}
187
188static int ideapad_dev_set_state(int device, int state)
189{
190 acpi_status status;
191 union acpi_object in_params[2];
192 struct acpi_object_list input = { 2, in_params };
193
194 in_params[0].type = ACPI_TYPE_INTEGER;
195 in_params[0].integer.value = device + 1;
196 in_params[1].type = ACPI_TYPE_INTEGER;
197 in_params[1].integer.value = state;
198
199 status = acpi_evaluate_object(NULL, "\\_SB_.SECN", &input, NULL);
200 if (ACPI_FAILURE(status)) {
201 printk(KERN_WARNING "IdeaPAD \\_SB_.SECN method failed %d\n", status);
202 return -ENODEV;
203 }
204 return 0;
205}
206static ssize_t show_ideapad_cam(struct device *dev,
207 struct device_attribute *attr,
208 char *buf)
209{
210 int state = ideapad_dev_get_state(IDEAPAD_DEV_CAMERA);
211 if (state < 0)
212 return state;
213
214 return sprintf(buf, "%d\n", state);
215}
216
217static ssize_t store_ideapad_cam(struct device *dev,
218 struct device_attribute *attr,
219 const char *buf, size_t count)
220{
221 int ret, state;
222
223 if (!count)
224 return 0;
225 if (sscanf(buf, "%i", &state) != 1)
226 return -EINVAL;
2016e4a0 227 ret = ideapad_dev_set_state(IDEAPAD_DEV_CAMERA, !!state);
58ac7aa0
DW
228 if (ret < 0)
229 return ret;
230 return count;
231}
232
233static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
234
235static int ideapad_rfk_set(void *data, bool blocked)
236{
237 int device = (unsigned long)data;
238
239 if (device == IDEAPAD_DEV_KILLSW)
240 return -EINVAL;
241 return ideapad_dev_set_state(device, !blocked);
242}
243
244static struct rfkill_ops ideapad_rfk_ops = {
245 .set_block = ideapad_rfk_set,
246};
247
ce326329 248static void ideapad_sync_rfk_state(struct acpi_device *adevice)
58ac7aa0 249{
ce326329 250 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
58ac7aa0
DW
251 int hw_blocked = !ideapad_dev_get_state(IDEAPAD_DEV_KILLSW);
252 int i;
253
ce326329 254 rfkill_set_hw_state(priv->rfk[IDEAPAD_DEV_KILLSW], hw_blocked);
58ac7aa0 255 for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
ce326329
DW
256 if (priv->rfk[i])
257 rfkill_set_hw_state(priv->rfk[i], hw_blocked);
58ac7aa0
DW
258 if (hw_blocked)
259 return;
260
261 for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
ce326329
DW
262 if (priv->rfk[i])
263 rfkill_set_sw_state(priv->rfk[i], !ideapad_dev_get_state(i));
58ac7aa0
DW
264}
265
ce326329 266static int ideapad_register_rfkill(struct acpi_device *adevice, int dev)
58ac7aa0 267{
ce326329 268 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
58ac7aa0
DW
269 int ret;
270
ce326329
DW
271 priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev-1].name, &adevice->dev,
272 ideapad_rfk_data[dev-1].type, &ideapad_rfk_ops,
273 (void *)(long)dev);
274 if (!priv->rfk[dev])
58ac7aa0
DW
275 return -ENOMEM;
276
ce326329 277 ret = rfkill_register(priv->rfk[dev]);
58ac7aa0 278 if (ret) {
ce326329 279 rfkill_destroy(priv->rfk[dev]);
58ac7aa0
DW
280 return ret;
281 }
282 return 0;
283}
284
ce326329 285static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)
58ac7aa0 286{
ce326329
DW
287 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
288
289 if (!priv->rfk[dev])
58ac7aa0
DW
290 return;
291
ce326329
DW
292 rfkill_unregister(priv->rfk[dev]);
293 rfkill_destroy(priv->rfk[dev]);
58ac7aa0
DW
294}
295
296static const struct acpi_device_id ideapad_device_ids[] = {
297 { "VPC2004", 0},
298 { "", 0},
299};
300MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
301
ce326329 302static int ideapad_acpi_add(struct acpi_device *adevice)
58ac7aa0 303{
6f8371c0 304 int i, cfg;
58ac7aa0 305 int devs_present[5];
ce326329 306 struct ideapad_private *priv;
58ac7aa0 307
6f8371c0
IP
308 if (read_method_int(adevice->handle, "_CFG", &cfg))
309 return -ENODEV;
310
58ac7aa0 311 for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) {
dfa7f6fe
IP
312 if (test_bit(ideapad_rfk_data[i].cfgbit, (unsigned long *)&cfg))
313 devs_present[i] = 1;
314 else
315 devs_present[i] = 0;
58ac7aa0
DW
316 }
317
318 /* The hardware switch is always present */
319 devs_present[IDEAPAD_DEV_KILLSW] = 1;
320
ce326329
DW
321 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
322 if (!priv)
323 return -ENOMEM;
324
58ac7aa0 325 if (devs_present[IDEAPAD_DEV_CAMERA]) {
ce326329
DW
326 int ret = device_create_file(&adevice->dev, &dev_attr_camera_power);
327 if (ret) {
328 kfree(priv);
58ac7aa0 329 return ret;
ce326329 330 }
58ac7aa0
DW
331 }
332
ce326329 333 dev_set_drvdata(&adevice->dev, priv);
58ac7aa0
DW
334 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) {
335 if (!devs_present[i])
336 continue;
337
ce326329 338 ideapad_register_rfkill(adevice, i);
58ac7aa0 339 }
ce326329 340 ideapad_sync_rfk_state(adevice);
58ac7aa0
DW
341 return 0;
342}
343
ce326329 344static int ideapad_acpi_remove(struct acpi_device *adevice, int type)
58ac7aa0 345{
ce326329 346 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
58ac7aa0 347 int i;
ce326329
DW
348
349 device_remove_file(&adevice->dev, &dev_attr_camera_power);
350
351 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++)
352 ideapad_unregister_rfkill(adevice, i);
353
354 dev_set_drvdata(&adevice->dev, NULL);
355 kfree(priv);
58ac7aa0
DW
356 return 0;
357}
358
ce326329 359static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event)
58ac7aa0 360{
8e7d3543
IP
361 acpi_handle handle = adevice->handle;
362 unsigned long vpc1, vpc2, vpc_bit;
363
364 if (read_ec_data(handle, 0x10, &vpc1))
365 return;
366 if (read_ec_data(handle, 0x1A, &vpc2))
367 return;
368
369 vpc1 = (vpc2 << 8) | vpc1;
370 for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
371 if (test_bit(vpc_bit, &vpc1)) {
372 if (vpc_bit == 9)
373 ideapad_sync_rfk_state(adevice);
374 }
375 }
58ac7aa0
DW
376}
377
378static struct acpi_driver ideapad_acpi_driver = {
379 .name = "ideapad_acpi",
380 .class = "IdeaPad",
381 .ids = ideapad_device_ids,
382 .ops.add = ideapad_acpi_add,
383 .ops.remove = ideapad_acpi_remove,
384 .ops.notify = ideapad_acpi_notify,
385 .owner = THIS_MODULE,
386};
387
388
389static int __init ideapad_acpi_module_init(void)
390{
391 acpi_bus_register_driver(&ideapad_acpi_driver);
392
393 return 0;
394}
395
396
397static void __exit ideapad_acpi_module_exit(void)
398{
399 acpi_bus_unregister_driver(&ideapad_acpi_driver);
400
401}
402
403MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
404MODULE_DESCRIPTION("IdeaPad ACPI Extras");
405MODULE_LICENSE("GPL");
406
407module_init(ideapad_acpi_module_init);
408module_exit(ideapad_acpi_module_exit);
This page took 0.083554 seconds and 5 git commands to generate.