Commit 58ac7aa0c308d8b7e38e92840de59da7a39834d8
1 parent
f6cec0ae58
Exists in
master
and in
7 other branches
Add Lenovo ideapad driver
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
Showing 3 changed files with 290 additions and 0 deletions Side-by-side Diff
drivers/platform/x86/Kconfig
... | ... | @@ -219,6 +219,13 @@ |
219 | 219 | ---help--- |
220 | 220 | Build the sonypi driver compatibility code into the sony-laptop driver. |
221 | 221 | |
222 | +config IDEAPAD_ACPI | |
223 | + tristate "Lenovo IdeaPad ACPI Laptop Extras" | |
224 | + depends on ACPI | |
225 | + depends on RFKILL | |
226 | + help | |
227 | + This is a driver for the rfkill switches on Lenovo IdeaPad netbooks. | |
228 | + | |
222 | 229 | config THINKPAD_ACPI |
223 | 230 | tristate "ThinkPad ACPI Laptop Extras" |
224 | 231 | depends on ACPI |
drivers/platform/x86/Makefile
... | ... | @@ -15,6 +15,7 @@ |
15 | 15 | obj-$(CONFIG_HP_WMI) += hp-wmi.o |
16 | 16 | obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o |
17 | 17 | obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o |
18 | +obj-$(CONFIG_IDEAPAD_ACPI) += ideapad_acpi.o | |
18 | 19 | obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o |
19 | 20 | obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o |
20 | 21 | obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o |
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 | +static struct rfkill *ideapad_rfkill[5]; | |
38 | + | |
39 | +static const char *ideapad_rfk_names[] = { | |
40 | + "ideapad_camera", "ideapad_wlan", "ideapad_bluetooth", "ideapad_3g", "ideapad_rfkill" | |
41 | +}; | |
42 | +static const int ideapad_rfk_types[] = { | |
43 | + 0, RFKILL_TYPE_WLAN, RFKILL_TYPE_BLUETOOTH, RFKILL_TYPE_WWAN, RFKILL_TYPE_WLAN | |
44 | +}; | |
45 | + | |
46 | +static 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 | + | |
72 | +static 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 | + | |
98 | +static 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 | +} | |
116 | +static 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 | + | |
127 | +static 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 | + | |
143 | +static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); | |
144 | + | |
145 | +static 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 | + | |
154 | +static struct rfkill_ops ideapad_rfk_ops = { | |
155 | + .set_block = ideapad_rfk_set, | |
156 | +}; | |
157 | + | |
158 | +static 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 | + | |
175 | +static 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 | + | |
193 | +static 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 | + | |
202 | +static const struct acpi_device_id ideapad_device_ids[] = { | |
203 | + { "VPC2004", 0}, | |
204 | + { "", 0}, | |
205 | +}; | |
206 | +MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); | |
207 | + | |
208 | +static 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 | + | |
238 | +static 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 | + | |
247 | +static void ideapad_acpi_notify(struct acpi_device *device, u32 event) | |
248 | +{ | |
249 | + ideapad_sync_rfk_state(); | |
250 | +} | |
251 | + | |
252 | +static 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 | + | |
263 | +static int __init ideapad_acpi_module_init(void) | |
264 | +{ | |
265 | + acpi_bus_register_driver(&ideapad_acpi_driver); | |
266 | + | |
267 | + return 0; | |
268 | +} | |
269 | + | |
270 | + | |
271 | +static void __exit ideapad_acpi_module_exit(void) | |
272 | +{ | |
273 | + acpi_bus_unregister_driver(&ideapad_acpi_driver); | |
274 | + | |
275 | +} | |
276 | + | |
277 | +MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); | |
278 | +MODULE_DESCRIPTION("IdeaPad ACPI Extras"); | |
279 | +MODULE_LICENSE("GPL"); | |
280 | + | |
281 | +module_init(ideapad_acpi_module_init); | |
282 | +module_exit(ideapad_acpi_module_exit); |