ideapad: use return value of _CFG to tell if device exist or not
[linux-2.6.git] / drivers / platform / x86 / ideapad_acpi.c
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
37 struct ideapad_private {
38         struct rfkill *rfk[5];
39 };
40
41 static struct {
42         char *name;
43         int cfgbit;
44         int type;
45 } ideapad_rfk_data[] = {
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 }
51 };
52
53 /*
54  * ACPI Helpers
55  */
56 #define IDEAPAD_EC_TIMEOUT (100) /* in ms */
57
58 static 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
73 static 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
96 static 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
115 static 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
139 static 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
162 static 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
188 static 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 }
206 static 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
217 static 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;
227         ret = ideapad_dev_set_state(IDEAPAD_DEV_CAMERA, !!state);
228         if (ret < 0)
229                 return ret;
230         return count;
231 }
232
233 static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
234
235 static 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
244 static struct rfkill_ops ideapad_rfk_ops = {
245         .set_block = ideapad_rfk_set,
246 };
247
248 static void ideapad_sync_rfk_state(struct acpi_device *adevice)
249 {
250         struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
251         int hw_blocked = !ideapad_dev_get_state(IDEAPAD_DEV_KILLSW);
252         int i;
253
254         rfkill_set_hw_state(priv->rfk[IDEAPAD_DEV_KILLSW], hw_blocked);
255         for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
256                 if (priv->rfk[i])
257                         rfkill_set_hw_state(priv->rfk[i], hw_blocked);
258         if (hw_blocked)
259                 return;
260
261         for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
262                 if (priv->rfk[i])
263                         rfkill_set_sw_state(priv->rfk[i], !ideapad_dev_get_state(i));
264 }
265
266 static int ideapad_register_rfkill(struct acpi_device *adevice, int dev)
267 {
268         struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
269         int ret;
270
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])
275                 return -ENOMEM;
276
277         ret = rfkill_register(priv->rfk[dev]);
278         if (ret) {
279                 rfkill_destroy(priv->rfk[dev]);
280                 return ret;
281         }
282         return 0;
283 }
284
285 static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)
286 {
287         struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
288
289         if (!priv->rfk[dev])
290                 return;
291
292         rfkill_unregister(priv->rfk[dev]);
293         rfkill_destroy(priv->rfk[dev]);
294 }
295
296 static const struct acpi_device_id ideapad_device_ids[] = {
297         { "VPC2004", 0},
298         { "", 0},
299 };
300 MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
301
302 static int ideapad_acpi_add(struct acpi_device *adevice)
303 {
304         int i, cfg;
305         int devs_present[5];
306         struct ideapad_private *priv;
307
308         if (read_method_int(adevice->handle, "_CFG", &cfg))
309                 return -ENODEV;
310
311         for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) {
312                 if (test_bit(ideapad_rfk_data[i].cfgbit, (unsigned long *)&cfg))
313                         devs_present[i] = 1;
314                 else
315                         devs_present[i] = 0;
316         }
317
318         /* The hardware switch is always present */
319         devs_present[IDEAPAD_DEV_KILLSW] = 1;
320
321         priv = kzalloc(sizeof(*priv), GFP_KERNEL);
322         if (!priv)
323                 return -ENOMEM;
324
325         if (devs_present[IDEAPAD_DEV_CAMERA]) {
326                 int ret = device_create_file(&adevice->dev, &dev_attr_camera_power);
327                 if (ret) {
328                         kfree(priv);
329                         return ret;
330                 }
331         }
332
333         dev_set_drvdata(&adevice->dev, priv);
334         for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) {
335                 if (!devs_present[i])
336                         continue;
337
338                 ideapad_register_rfkill(adevice, i);
339         }
340         ideapad_sync_rfk_state(adevice);
341         return 0;
342 }
343
344 static int ideapad_acpi_remove(struct acpi_device *adevice, int type)
345 {
346         struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
347         int i;
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);
356         return 0;
357 }
358
359 static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event)
360 {
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         }
376 }
377
378 static 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
389 static int __init ideapad_acpi_module_init(void)
390 {
391         acpi_bus_register_driver(&ideapad_acpi_driver);
392
393         return 0;
394 }
395
396
397 static void __exit ideapad_acpi_module_exit(void)
398 {
399         acpi_bus_unregister_driver(&ideapad_acpi_driver);
400
401 }
402
403 MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
404 MODULE_DESCRIPTION("IdeaPad ACPI Extras");
405 MODULE_LICENSE("GPL");
406
407 module_init(ideapad_acpi_module_init);
408 module_exit(ideapad_acpi_module_exit);