ideapad: use EC command to control camera
[linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git] / drivers / platform / x86 / ideapad_acpi.c
blobe45fc50b93dc8d858d9b3a2f3d7de38e572ce04a
1 /*
2 * ideapad_acpi.c - Lenovo IdeaPad ACPI Extras
4 * Copyright © 2010 Intel Corporation
5 * Copyright © 2010 David Woodhouse <dwmw2@infradead.org>
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.
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.
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.
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>
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
37 struct ideapad_private {
38 acpi_handle handle;
39 struct rfkill *rfk[5];
42 static struct {
43 char *name;
44 int cfgbit;
45 int type;
46 } ideapad_rfk_data[] = {
47 { "ideapad_camera", 19, NUM_RFKILL_TYPES },
48 { "ideapad_wlan", 18, RFKILL_TYPE_WLAN },
49 { "ideapad_bluetooth", 16, RFKILL_TYPE_BLUETOOTH },
50 { "ideapad_3g", 17, RFKILL_TYPE_WWAN },
51 { "ideapad_killsw", 0, RFKILL_TYPE_WLAN }
55 * ACPI Helpers
57 #define IDEAPAD_EC_TIMEOUT (100) /* in ms */
59 static int read_method_int(acpi_handle handle, const char *method, int *val)
61 acpi_status status;
62 unsigned long long result;
64 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
65 if (ACPI_FAILURE(status)) {
66 *val = -1;
67 return -1;
68 } else {
69 *val = result;
70 return 0;
74 static int method_vpcr(acpi_handle handle, int cmd, int *ret)
76 acpi_status status;
77 unsigned long long result;
78 struct acpi_object_list params;
79 union acpi_object in_obj;
81 params.count = 1;
82 params.pointer = &in_obj;
83 in_obj.type = ACPI_TYPE_INTEGER;
84 in_obj.integer.value = cmd;
86 status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
88 if (ACPI_FAILURE(status)) {
89 *ret = -1;
90 return -1;
91 } else {
92 *ret = result;
93 return 0;
97 static int method_vpcw(acpi_handle handle, int cmd, int data)
99 struct acpi_object_list params;
100 union acpi_object in_obj[2];
101 acpi_status status;
103 params.count = 2;
104 params.pointer = in_obj;
105 in_obj[0].type = ACPI_TYPE_INTEGER;
106 in_obj[0].integer.value = cmd;
107 in_obj[1].type = ACPI_TYPE_INTEGER;
108 in_obj[1].integer.value = data;
110 status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
111 if (status != AE_OK)
112 return -1;
113 return 0;
116 static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
118 int val;
119 unsigned long int end_jiffies;
121 if (method_vpcw(handle, 1, cmd))
122 return -1;
124 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
125 time_before(jiffies, end_jiffies);) {
126 schedule();
127 if (method_vpcr(handle, 1, &val))
128 return -1;
129 if (val == 0) {
130 if (method_vpcr(handle, 0, &val))
131 return -1;
132 *data = val;
133 return 0;
136 pr_err("timeout in read_ec_cmd\n");
137 return -1;
140 static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)
142 int val;
143 unsigned long int end_jiffies;
145 if (method_vpcw(handle, 0, data))
146 return -1;
147 if (method_vpcw(handle, 1, cmd))
148 return -1;
150 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
151 time_before(jiffies, end_jiffies);) {
152 schedule();
153 if (method_vpcr(handle, 1, &val))
154 return -1;
155 if (val == 0)
156 return 0;
158 pr_err("timeout in write_ec_cmd\n");
159 return -1;
161 /* the above is ACPI helpers */
163 static int ideapad_dev_get_state(int device)
165 acpi_status status;
166 union acpi_object in_param;
167 struct acpi_object_list input = { 1, &in_param };
168 struct acpi_buffer output;
169 union acpi_object out_obj;
171 output.length = sizeof(out_obj);
172 output.pointer = &out_obj;
174 in_param.type = ACPI_TYPE_INTEGER;
175 in_param.integer.value = device + 1;
177 status = acpi_evaluate_object(NULL, "\\_SB_.GECN", &input, &output);
178 if (ACPI_FAILURE(status)) {
179 printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method failed %d\n", status);
180 return -ENODEV;
182 if (out_obj.type != ACPI_TYPE_INTEGER) {
183 printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method returned unexpected type\n");
184 return -ENODEV;
186 return out_obj.integer.value;
189 static int ideapad_dev_set_state(int device, int state)
191 acpi_status status;
192 union acpi_object in_params[2];
193 struct acpi_object_list input = { 2, in_params };
195 in_params[0].type = ACPI_TYPE_INTEGER;
196 in_params[0].integer.value = device + 1;
197 in_params[1].type = ACPI_TYPE_INTEGER;
198 in_params[1].integer.value = state;
200 status = acpi_evaluate_object(NULL, "\\_SB_.SECN", &input, NULL);
201 if (ACPI_FAILURE(status)) {
202 printk(KERN_WARNING "IdeaPAD \\_SB_.SECN method failed %d\n", status);
203 return -ENODEV;
205 return 0;
207 static ssize_t show_ideapad_cam(struct device *dev,
208 struct device_attribute *attr,
209 char *buf)
211 struct ideapad_private *priv = dev_get_drvdata(dev);
212 acpi_handle handle = priv->handle;
213 unsigned long result;
215 if (read_ec_data(handle, 0x1D, &result))
216 return sprintf(buf, "-1\n");
217 return sprintf(buf, "%lu\n", result);
220 static ssize_t store_ideapad_cam(struct device *dev,
221 struct device_attribute *attr,
222 const char *buf, size_t count)
224 struct ideapad_private *priv = dev_get_drvdata(dev);
225 acpi_handle handle = priv->handle;
226 int ret, state;
228 if (!count)
229 return 0;
230 if (sscanf(buf, "%i", &state) != 1)
231 return -EINVAL;
232 ret = write_ec_cmd(handle, 0x1E, state);
233 if (ret < 0)
234 return ret;
235 return count;
238 static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
240 static int ideapad_rfk_set(void *data, bool blocked)
242 int device = (unsigned long)data;
244 if (device == IDEAPAD_DEV_KILLSW)
245 return -EINVAL;
246 return ideapad_dev_set_state(device, !blocked);
249 static struct rfkill_ops ideapad_rfk_ops = {
250 .set_block = ideapad_rfk_set,
253 static void ideapad_sync_rfk_state(struct acpi_device *adevice)
255 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
256 int hw_blocked = !ideapad_dev_get_state(IDEAPAD_DEV_KILLSW);
257 int i;
259 rfkill_set_hw_state(priv->rfk[IDEAPAD_DEV_KILLSW], hw_blocked);
260 for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
261 if (priv->rfk[i])
262 rfkill_set_hw_state(priv->rfk[i], hw_blocked);
263 if (hw_blocked)
264 return;
266 for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
267 if (priv->rfk[i])
268 rfkill_set_sw_state(priv->rfk[i], !ideapad_dev_get_state(i));
271 static int ideapad_register_rfkill(struct acpi_device *adevice, int dev)
273 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
274 int ret;
276 priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev-1].name, &adevice->dev,
277 ideapad_rfk_data[dev-1].type, &ideapad_rfk_ops,
278 (void *)(long)dev);
279 if (!priv->rfk[dev])
280 return -ENOMEM;
282 ret = rfkill_register(priv->rfk[dev]);
283 if (ret) {
284 rfkill_destroy(priv->rfk[dev]);
285 return ret;
287 return 0;
290 static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)
292 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
294 if (!priv->rfk[dev])
295 return;
297 rfkill_unregister(priv->rfk[dev]);
298 rfkill_destroy(priv->rfk[dev]);
301 static const struct acpi_device_id ideapad_device_ids[] = {
302 { "VPC2004", 0},
303 { "", 0},
305 MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
307 static int ideapad_acpi_add(struct acpi_device *adevice)
309 int i, cfg;
310 int devs_present[5];
311 struct ideapad_private *priv;
313 if (read_method_int(adevice->handle, "_CFG", &cfg))
314 return -ENODEV;
316 for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) {
317 if (test_bit(ideapad_rfk_data[i].cfgbit, (unsigned long *)&cfg))
318 devs_present[i] = 1;
319 else
320 devs_present[i] = 0;
323 /* The hardware switch is always present */
324 devs_present[IDEAPAD_DEV_KILLSW] = 1;
326 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
327 if (!priv)
328 return -ENOMEM;
330 if (devs_present[IDEAPAD_DEV_CAMERA]) {
331 int ret = device_create_file(&adevice->dev, &dev_attr_camera_power);
332 if (ret) {
333 kfree(priv);
334 return ret;
338 priv->handle = adevice->handle;
339 dev_set_drvdata(&adevice->dev, priv);
340 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) {
341 if (!devs_present[i])
342 continue;
344 ideapad_register_rfkill(adevice, i);
346 ideapad_sync_rfk_state(adevice);
347 return 0;
350 static int ideapad_acpi_remove(struct acpi_device *adevice, int type)
352 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
353 int i;
355 device_remove_file(&adevice->dev, &dev_attr_camera_power);
357 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++)
358 ideapad_unregister_rfkill(adevice, i);
360 dev_set_drvdata(&adevice->dev, NULL);
361 kfree(priv);
362 return 0;
365 static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event)
367 acpi_handle handle = adevice->handle;
368 unsigned long vpc1, vpc2, vpc_bit;
370 if (read_ec_data(handle, 0x10, &vpc1))
371 return;
372 if (read_ec_data(handle, 0x1A, &vpc2))
373 return;
375 vpc1 = (vpc2 << 8) | vpc1;
376 for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) {
377 if (test_bit(vpc_bit, &vpc1)) {
378 if (vpc_bit == 9)
379 ideapad_sync_rfk_state(adevice);
384 static struct acpi_driver ideapad_acpi_driver = {
385 .name = "ideapad_acpi",
386 .class = "IdeaPad",
387 .ids = ideapad_device_ids,
388 .ops.add = ideapad_acpi_add,
389 .ops.remove = ideapad_acpi_remove,
390 .ops.notify = ideapad_acpi_notify,
391 .owner = THIS_MODULE,
395 static int __init ideapad_acpi_module_init(void)
397 acpi_bus_register_driver(&ideapad_acpi_driver);
399 return 0;
403 static void __exit ideapad_acpi_module_exit(void)
405 acpi_bus_unregister_driver(&ideapad_acpi_driver);
409 MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
410 MODULE_DESCRIPTION("IdeaPad ACPI Extras");
411 MODULE_LICENSE("GPL");
413 module_init(ideapad_acpi_module_init);
414 module_exit(ideapad_acpi_module_exit);