diff options
author | David Woodhouse <David.Woodhouse@intel.com> | 2010-08-10 18:44:05 -0400 |
---|---|---|
committer | David Woodhouse <David.Woodhouse@intel.com> | 2010-08-10 19:01:21 -0400 |
commit | 58ac7aa0c308d8b7e38e92840de59da7a39834d8 (patch) | |
tree | 21db24925bb7ea42531a2394f6adfda171d833a9 /drivers | |
parent | f6cec0ae58c17522a7bc4e2f39dae19f199ab534 (diff) |
Add Lenovo ideapad driver
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/platform/x86/Kconfig | 7 | ||||
-rw-r--r-- | drivers/platform/x86/Makefile | 1 | ||||
-rw-r--r-- | drivers/platform/x86/ideapad_acpi.c | 282 |
3 files changed, 290 insertions, 0 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 79baa6368f79..044f430f3b43 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig | |||
@@ -219,6 +219,13 @@ config SONYPI_COMPAT | |||
219 | ---help--- | 219 | ---help--- |
220 | Build the sonypi driver compatibility code into the sony-laptop driver. | 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 | config THINKPAD_ACPI | 229 | config THINKPAD_ACPI |
223 | tristate "ThinkPad ACPI Laptop Extras" | 230 | tristate "ThinkPad ACPI Laptop Extras" |
224 | depends on ACPI | 231 | depends on ACPI |
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 4744c7744ffa..85fb2b84f57e 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile | |||
@@ -15,6 +15,7 @@ obj-$(CONFIG_ACERHDF) += acerhdf.o | |||
15 | obj-$(CONFIG_HP_WMI) += hp-wmi.o | 15 | obj-$(CONFIG_HP_WMI) += hp-wmi.o |
16 | obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o | 16 | obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o |
17 | obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o | 17 | obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o |
18 | obj-$(CONFIG_IDEAPAD_ACPI) += ideapad_acpi.o | ||
18 | obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o | 19 | obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o |
19 | obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o | 20 | obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o |
20 | obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o | 21 | obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o |
diff --git a/drivers/platform/x86/ideapad_acpi.c b/drivers/platform/x86/ideapad_acpi.c new file mode 100644 index 000000000000..ae744caf4726 --- /dev/null +++ b/drivers/platform/x86/ideapad_acpi.c | |||
@@ -0,0 +1,282 @@ | |||
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); | ||