blob: ae744caf47262ede90545bccfcdeaf7fd203b810 [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
37static struct rfkill *ideapad_rfkill[5];
38
39static const char *ideapad_rfk_names[] = {
40 "ideapad_camera", "ideapad_wlan", "ideapad_bluetooth", "ideapad_3g", "ideapad_rfkill"
41};
42static const int ideapad_rfk_types[] = {
43 0, RFKILL_TYPE_WLAN, RFKILL_TYPE_BLUETOOTH, RFKILL_TYPE_WWAN, RFKILL_TYPE_WLAN
44};
45
46static int ideapad_dev_exists(int device)
47{
48 acpi_status status;
49 union acpi_object in_param;
50 struct acpi_object_list input = { 1, &in_param };
51 struct acpi_buffer output;
52 union acpi_object out_obj;
53
54 output.length = sizeof(out_obj);
55 output.pointer = &out_obj;
56
57 in_param.type = ACPI_TYPE_INTEGER;
58 in_param.integer.value = device + 1;
59
60 status = acpi_evaluate_object(NULL, "\\_SB_.DECN", &input, &output);
61 if (ACPI_FAILURE(status)) {
62 printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method failed %d. Is this an IdeaPAD?\n", status);
63 return -ENODEV;
64 }
65 if (out_obj.type != ACPI_TYPE_INTEGER) {
66 printk(KERN_WARNING "IdeaPAD \\_SB_.DECN method returned unexpected type\n");
67 return -ENODEV;
68 }
69 return out_obj.integer.value;
70}
71
72static int ideapad_dev_get_state(int device)
73{
74 acpi_status status;
75 union acpi_object in_param;
76 struct acpi_object_list input = { 1, &in_param };
77 struct acpi_buffer output;
78 union acpi_object out_obj;
79
80 output.length = sizeof(out_obj);
81 output.pointer = &out_obj;
82
83 in_param.type = ACPI_TYPE_INTEGER;
84 in_param.integer.value = device + 1;
85
86 status = acpi_evaluate_object(NULL, "\\_SB_.GECN", &input, &output);
87 if (ACPI_FAILURE(status)) {
88 printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method failed %d\n", status);
89 return -ENODEV;
90 }
91 if (out_obj.type != ACPI_TYPE_INTEGER) {
92 printk(KERN_WARNING "IdeaPAD \\_SB_.GECN method returned unexpected type\n");
93 return -ENODEV;
94 }
95 return out_obj.integer.value;
96}
97
98static int ideapad_dev_set_state(int device, int state)
99{
100 acpi_status status;
101 union acpi_object in_params[2];
102 struct acpi_object_list input = { 2, in_params };
103
104 in_params[0].type = ACPI_TYPE_INTEGER;
105 in_params[0].integer.value = device + 1;
106 in_params[1].type = ACPI_TYPE_INTEGER;
107 in_params[1].integer.value = state;
108
109 status = acpi_evaluate_object(NULL, "\\_SB_.SECN", &input, NULL);
110 if (ACPI_FAILURE(status)) {
111 printk(KERN_WARNING "IdeaPAD \\_SB_.SECN method failed %d\n", status);
112 return -ENODEV;
113 }
114 return 0;
115}
116static ssize_t show_ideapad_cam(struct device *dev,
117 struct device_attribute *attr,
118 char *buf)
119{
120 int state = ideapad_dev_get_state(IDEAPAD_DEV_CAMERA);
121 if (state < 0)
122 return state;
123
124 return sprintf(buf, "%d\n", state);
125}
126
127static ssize_t store_ideapad_cam(struct device *dev,
128 struct device_attribute *attr,
129 const char *buf, size_t count)
130{
131 int ret, state;
132
133 if (!count)
134 return 0;
135 if (sscanf(buf, "%i", &state) != 1)
136 return -EINVAL;
137 ret = ideapad_dev_set_state(IDEAPAD_DEV_CAMERA, state);
138 if (ret < 0)
139 return ret;
140 return count;
141}
142
143static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam);
144
145static int ideapad_rfk_set(void *data, bool blocked)
146{
147 int device = (unsigned long)data;
148
149 if (device == IDEAPAD_DEV_KILLSW)
150 return -EINVAL;
151 return ideapad_dev_set_state(device, !blocked);
152}
153
154static struct rfkill_ops ideapad_rfk_ops = {
155 .set_block = ideapad_rfk_set,
156};
157
158static void ideapad_sync_rfk_state(void)
159{
160 int hw_blocked = !ideapad_dev_get_state(IDEAPAD_DEV_KILLSW);
161 int i;
162
163 rfkill_set_hw_state(ideapad_rfkill[IDEAPAD_DEV_KILLSW], hw_blocked);
164 for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
165 if (ideapad_rfkill[i])
166 rfkill_set_hw_state(ideapad_rfkill[i], hw_blocked);
167 if (hw_blocked)
168 return;
169
170 for (i = IDEAPAD_DEV_WLAN; i < IDEAPAD_DEV_KILLSW; i++)
171 if (ideapad_rfkill[i])
172 rfkill_set_sw_state(ideapad_rfkill[i], !ideapad_dev_get_state(i));
173}
174
175static int ideapad_register_rfkill(struct acpi_device *device, int dev)
176{
177 int ret;
178
179 ideapad_rfkill[dev] = rfkill_alloc(ideapad_rfk_names[dev], &device->dev,
180 ideapad_rfk_types[dev], &ideapad_rfk_ops,
181 (void *)(long)dev);
182 if (!ideapad_rfkill[dev])
183 return -ENOMEM;
184
185 ret = rfkill_register(ideapad_rfkill[dev]);
186 if (ret) {
187 rfkill_destroy(ideapad_rfkill[dev]);
188 return ret;
189 }
190 return 0;
191}
192
193static void ideapad_unregister_rfkill(int dev)
194{
195 if (!ideapad_rfkill[dev])
196 return;
197
198 rfkill_unregister(ideapad_rfkill[dev]);
199 rfkill_destroy(ideapad_rfkill[dev]);
200}
201
202static const struct acpi_device_id ideapad_device_ids[] = {
203 { "VPC2004", 0},
204 { "", 0},
205};
206MODULE_DEVICE_TABLE(acpi, ideapad_device_ids);
207
208static int ideapad_acpi_add(struct acpi_device *device)
209{
210 int i;
211 int devs_present[5];
212
213 for (i = IDEAPAD_DEV_CAMERA; i < IDEAPAD_DEV_KILLSW; i++) {
214 devs_present[i] = ideapad_dev_exists(i);
215 if (devs_present[i] < 0)
216 return devs_present[i];
217 }
218
219 /* The hardware switch is always present */
220 devs_present[IDEAPAD_DEV_KILLSW] = 1;
221
222 if (devs_present[IDEAPAD_DEV_CAMERA]) {
223 int ret = device_create_file(&device->dev, &dev_attr_camera_power);
224 if (ret)
225 return ret;
226 }
227
228 for (i = IDEAPAD_DEV_WLAN; i <= IDEAPAD_DEV_KILLSW; i++) {
229 if (!devs_present[i])
230 continue;
231
232 ideapad_register_rfkill(device, i);
233 }
234 ideapad_sync_rfk_state();
235 return 0;
236}
237
238static int ideapad_acpi_remove(struct acpi_device *device, int type)
239{
240 int i;
241 device_remove_file(&device->dev, &dev_attr_camera_power);
242 for (i = 0; i < 5; i++)
243 ideapad_unregister_rfkill(i);
244 return 0;
245}
246
247static void ideapad_acpi_notify(struct acpi_device *device, u32 event)
248{
249 ideapad_sync_rfk_state();
250}
251
252static struct acpi_driver ideapad_acpi_driver = {
253 .name = "ideapad_acpi",
254 .class = "IdeaPad",
255 .ids = ideapad_device_ids,
256 .ops.add = ideapad_acpi_add,
257 .ops.remove = ideapad_acpi_remove,
258 .ops.notify = ideapad_acpi_notify,
259 .owner = THIS_MODULE,
260};
261
262
263static int __init ideapad_acpi_module_init(void)
264{
265 acpi_bus_register_driver(&ideapad_acpi_driver);
266
267 return 0;
268}
269
270
271static void __exit ideapad_acpi_module_exit(void)
272{
273 acpi_bus_unregister_driver(&ideapad_acpi_driver);
274
275}
276
277MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
278MODULE_DESCRIPTION("IdeaPad ACPI Extras");
279MODULE_LICENSE("GPL");
280
281module_init(ideapad_acpi_module_init);
282module_exit(ideapad_acpi_module_exit);