blob: c87693c7dde032a55cfd386880395785a1ca0cab [file] [log] [blame]
David Woodhouse58ac7aa2010-08-10 23:44:05 +01001/*
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
David Woodhousece326322010-08-11 17:59:35 +010037struct ideapad_private {
38 struct rfkill *rfk[5];
David Woodhouse58ac7aa2010-08-10 23:44:05 +010039};
David Woodhousece326322010-08-11 17:59:35 +010040
41static struct {
42 char *name;
43 int type;
44} ideapad_rfk_data[] = {
45 /* camera has no rfkill */
46 { "ideapad_wlan", RFKILL_TYPE_WLAN },
47 { "ideapad_bluetooth", RFKILL_TYPE_BLUETOOTH },
48 { "ideapad_3g", RFKILL_TYPE_WWAN },
49 { "ideapad_killsw", RFKILL_TYPE_WLAN }
David Woodhouse58ac7aa2010-08-10 23:44:05 +010050};
51
Ike Panhc6a09f212010-10-01 15:38:46 +080052/*
53 * ACPI Helpers
54 */
55#define IDEAPAD_EC_TIMEOUT (100) /* in ms */
56
57static int read_method_int(acpi_handle handle, const char *method, int *val)
58{
59 acpi_status status;
60 unsigned long long result;
61
62 status = acpi_evaluate_integer(handle, (char *)method, NULL, &result);
63 if (ACPI_FAILURE(status)) {
64 *val = -1;
65 return -1;
66 } else {
67 *val = result;
68 return 0;
69 }
70}
71
72static int method_vpcr(acpi_handle handle, int cmd, int *ret)
73{
74 acpi_status status;
75 unsigned long long result;
76 struct acpi_object_list params;
77 union acpi_object in_obj;
78
79 params.count = 1;
80 params.pointer = &in_obj;
81 in_obj.type = ACPI_TYPE_INTEGER;
82 in_obj.integer.value = cmd;
83
84 status = acpi_evaluate_integer(handle, "VPCR", &params, &result);
85
86 if (ACPI_FAILURE(status)) {
87 *ret = -1;
88 return -1;
89 } else {
90 *ret = result;
91 return 0;
92 }
93}
94
95static int method_vpcw(acpi_handle handle, int cmd, int data)
96{
97 struct acpi_object_list params;
98 union acpi_object in_obj[2];
99 acpi_status status;
100
101 params.count = 2;
102 params.pointer = in_obj;
103 in_obj[0].type = ACPI_TYPE_INTEGER;
104 in_obj[0].integer.value = cmd;
105 in_obj[1].type = ACPI_TYPE_INTEGER;
106 in_obj[1].integer.value = data;
107
108 status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
109 if (status != AE_OK)
110 return -1;
111 return 0;
112}
113
114static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data)
115{
116 int val;
117 unsigned long int end_jiffies;
118
119 if (method_vpcw(handle, 1, cmd))
120 return -1;
121
122 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
123 time_before(jiffies, end_jiffies);) {
124 schedule();
125 if (method_vpcr(handle, 1, &val))
126 return -1;
127 if (val == 0) {
128 if (method_vpcr(handle, 0, &val))
129 return -1;
130 *data = val;
131 return 0;
132 }
133 }
134 pr_err("timeout in read_ec_cmd\n");
135 return -1;
136}
137
138static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data)
139{
140 int val;
141 unsigned long int end_jiffies;
142
143 if (method_vpcw(handle, 0, data))
144 return -1;
145 if (method_vpcw(handle, 1, cmd))
146 return -1;
147
148 for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1;
149 time_before(jiffies, end_jiffies);) {
150 schedule();
151 if (method_vpcr(handle, 1, &val))
152 return -1;
153 if (val == 0)
154 return 0;
155 }
156 pr_err("timeout in write_ec_cmd\n");
157 return -1;
158}
159/* the above is ACPI helpers */
160
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100161static int ideapad_dev_exists(int device)
162{
163 acpi_status status;
164 union acpi_object in_param;
165 struct acpi_object_list input = { 1, &in_param };
166 struct acpi_buffer output;
167 union acpi_object out_obj;
168
169 output.length = sizeof(out_obj);
170 output.pointer = &out_obj;
171
172 in_param.type = ACPI_TYPE_INTEGER;
173 in_param.integer.value = device + 1;
174
175 status = acpi_evaluate_object(NULL, "\\_SB_.DECN", &input, &output);
176 if (ACPI_FAILURE(status)) {
177 printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method failed %d. Is this an IdeaPAD?\n", status);
178 return -ENODEV;
179 }
180 if (out_obj.type != ACPI_TYPE_INTEGER) {
181 printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method returned unexpected type\n");
182 return -ENODEV;
183 }
184 return out_obj.integer.value;
185}
186
187static int ideapad_dev_get_state(int device)
188{
189 acpi_status status;
190 union acpi_object in_param;
191 struct acpi_object_list input = { 1, &in_param };
192 struct acpi_buffer output;
193 union acpi_object out_obj;
194
195 output.length = sizeof(out_obj);
196 output.pointer = &out_obj;
197
198 in_param.type = ACPI_TYPE_INTEGER;
199 in_param.integer.value = device + 1;
200
201 status = acpi_evaluate_object(NULL, "\\_SB_.GECN", &input, &output);
202 if (ACPI_FAILURE(status)) {
203 printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method failed %d\n", status);
204 return -ENODEV;
205 }
206 if (out_obj.type != ACPI_TYPE_INTEGER) {
207 printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method returned unexpected type\n");
208 return -ENODEV;
209 }
210 return out_obj.integer.value;
211}
212
213static int ideapad_dev_set_state(int device, int state)
214{
215 acpi_status status;
216 union acpi_object in_params[2];
217 struct acpi_object_list input = { 2, in_params };
218
219 in_params[0].type = ACPI_TYPE_INTEGER;
220 in_params[0].integer.value = device + 1;
221 in_params[1].type = ACPI_TYPE_INTEGER;
222 in_params[1].integer.value = state;
223
224 status = acpi_evaluate_object(NULL, "\\_SB_.SECN", &input, NULL);
225 if (ACPI_FAILURE(status)) {
226 printk(KERN_WARNING "IdeaPAD \\_SB_.SECN method failed %d\n", status);
227 return -ENODEV;
228 }
229 return 0;
230}
231static ssize_t show_ideapad_cam(struct device *dev,
232 struct device_attribute *attr,
233 char *buf)
234{
235 int state = ideapad_dev_get_state(IDEAPAD_DEV_CAMERA);
236 if (state < 0)
237 return state;
238
239 return sprintf(buf, "%d\n", state);
240}
241
242static ssize_t store_ideapad_cam(struct device *dev,
243 struct device_attribute *attr,
244 const char *buf, size_t count)
245{
246 int ret, state;
247
248 if (!count)
249 return 0;
250 if (sscanf(buf, "%i", &state) != 1)
251 return -EINVAL;
David Woodhouse2016e4a2010-08-11 17:59:51 +0100252 ret = ideapad_dev_set_state(IDEAPAD_DEV_CAMERA, !!state);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100253 if (ret < 0)
254 return ret;
255 return count;
256}
257
258static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
259
260static int ideapad_rfk_set(void *data, bool blocked)
261{
262 int device = (unsigned long)data;
263
264 if (device == IDEAPAD_DEV_KILLSW)
265 return -EINVAL;
266 return ideapad_dev_set_state(device, !blocked);
267}
268
269static struct rfkill_ops ideapad_rfk_ops = {
270 .set_block = ideapad_rfk_set,
271};
272
David Woodhousece326322010-08-11 17:59:35 +0100273static void ideapad_sync_rfk_state(struct acpi_device *adevice)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100274{
David Woodhousece326322010-08-11 17:59:35 +0100275 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100276 int hw_blocked = !ideapad_dev_get_state(IDEAPAD_DEV_KILLSW);
277 int i;
278
David Woodhousece326322010-08-11 17:59:35 +0100279 rfkill_set_hw_state(priv->rfk[IDEAPAD_DEV_KILLSW], hw_blocked);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100280 for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
David Woodhousece326322010-08-11 17:59:35 +0100281 if (priv->rfk[i])
282 rfkill_set_hw_state(priv->rfk[i], hw_blocked);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100283 if (hw_blocked)
284 return;
285
286 for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
David Woodhousece326322010-08-11 17:59:35 +0100287 if (priv->rfk[i])
288 rfkill_set_sw_state(priv->rfk[i], !ideapad_dev_get_state(i));
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100289}
290
David Woodhousece326322010-08-11 17:59:35 +0100291static int ideapad_register_rfkill(struct acpi_device *adevice, int dev)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100292{
David Woodhousece326322010-08-11 17:59:35 +0100293 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100294 int ret;
295
David Woodhousece326322010-08-11 17:59:35 +0100296 priv->rfk[dev] = rfkill_alloc(ideapad_rfk_data[dev-1].name, &adevice->dev,
297 ideapad_rfk_data[dev-1].type, &ideapad_rfk_ops,
298 (void *)(long)dev);
299 if (!priv->rfk[dev])
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100300 return -ENOMEM;
301
David Woodhousece326322010-08-11 17:59:35 +0100302 ret = rfkill_register(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100303 if (ret) {
David Woodhousece326322010-08-11 17:59:35 +0100304 rfkill_destroy(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100305 return ret;
306 }
307 return 0;
308}
309
David Woodhousece326322010-08-11 17:59:35 +0100310static void ideapad_unregister_rfkill(struct acpi_device *adevice, int dev)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100311{
David Woodhousece326322010-08-11 17:59:35 +0100312 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
313
314 if (!priv->rfk[dev])
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100315 return;
316
David Woodhousece326322010-08-11 17:59:35 +0100317 rfkill_unregister(priv->rfk[dev]);
318 rfkill_destroy(priv->rfk[dev]);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100319}
320
321static const struct acpi_device_id ideapad_device_ids[] = {
322 { "VPC2004", 0},
323 { "", 0},
324};
325MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
326
David Woodhousece326322010-08-11 17:59:35 +0100327static int ideapad_acpi_add(struct acpi_device *adevice)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100328{
329 int i;
330 int devs_present[5];
David Woodhousece326322010-08-11 17:59:35 +0100331 struct ideapad_private *priv;
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100332
333 for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) {
334 devs_present[i] = ideapad_dev_exists(i);
335 if (devs_present[i] < 0)
336 return devs_present[i];
337 }
338
339 /* The hardware switch is always present */
340 devs_present[IDEAPAD_DEV_KILLSW] = 1;
341
David Woodhousece326322010-08-11 17:59:35 +0100342 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
343 if (!priv)
344 return -ENOMEM;
345
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100346 if (devs_present[IDEAPAD_DEV_CAMERA]) {
David Woodhousece326322010-08-11 17:59:35 +0100347 int ret = device_create_file(&adevice->dev, &dev_attr_camera_power);
348 if (ret) {
349 kfree(priv);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100350 return ret;
David Woodhousece326322010-08-11 17:59:35 +0100351 }
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100352 }
353
David Woodhousece326322010-08-11 17:59:35 +0100354 dev_set_drvdata(&adevice->dev, priv);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100355 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) {
356 if (!devs_present[i])
357 continue;
358
David Woodhousece326322010-08-11 17:59:35 +0100359 ideapad_register_rfkill(adevice, i);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100360 }
David Woodhousece326322010-08-11 17:59:35 +0100361 ideapad_sync_rfk_state(adevice);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100362 return 0;
363}
364
David Woodhousece326322010-08-11 17:59:35 +0100365static int ideapad_acpi_remove(struct acpi_device *adevice, int type)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100366{
David Woodhousece326322010-08-11 17:59:35 +0100367 struct ideapad_private *priv = dev_get_drvdata(&adevice->dev);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100368 int i;
David Woodhousece326322010-08-11 17:59:35 +0100369
370 device_remove_file(&adevice->dev, &dev_attr_camera_power);
371
372 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++)
373 ideapad_unregister_rfkill(adevice, i);
374
375 dev_set_drvdata(&adevice->dev, NULL);
376 kfree(priv);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100377 return 0;
378}
379
David Woodhousece326322010-08-11 17:59:35 +0100380static void ideapad_acpi_notify(struct acpi_device *adevice, u32 event)
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100381{
David Woodhousece326322010-08-11 17:59:35 +0100382 ideapad_sync_rfk_state(adevice);
David Woodhouse58ac7aa2010-08-10 23:44:05 +0100383}
384
385static struct acpi_driver ideapad_acpi_driver = {
386 .name = "ideapad_acpi",
387 .class = "IdeaPad",
388 .ids = ideapad_device_ids,
389 .ops.add = ideapad_acpi_add,
390 .ops.remove = ideapad_acpi_remove,
391 .ops.notify = ideapad_acpi_notify,
392 .owner = THIS_MODULE,
393};
394
395
396static int __init ideapad_acpi_module_init(void)
397{
398 acpi_bus_register_driver(&ideapad_acpi_driver);
399
400 return 0;
401}
402
403
404static void __exit ideapad_acpi_module_exit(void)
405{
406 acpi_bus_unregister_driver(&ideapad_acpi_driver);
407
408}
409
410MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
411MODULE_DESCRIPTION("IdeaPad ACPI Extras");
412MODULE_LICENSE("GPL");
413
414module_init(ideapad_acpi_module_init);
415module_exit(ideapad_acpi_module_exit);