diff options
Diffstat (limited to 'drivers/misc/dell-laptop.c')
-rw-r--r-- | drivers/misc/dell-laptop.c | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/drivers/misc/dell-laptop.c b/drivers/misc/dell-laptop.c new file mode 100644 index 000000000000..4d33a2068b7a --- /dev/null +++ b/drivers/misc/dell-laptop.c | |||
@@ -0,0 +1,436 @@ | |||
1 | /* | ||
2 | * Driver for Dell laptop extras | ||
3 | * | ||
4 | * Copyright (c) Red Hat <mjg@redhat.com> | ||
5 | * | ||
6 | * Based on documentation in the libsmbios package, Copyright (C) 2005 Dell | ||
7 | * Inc. | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | */ | ||
13 | |||
14 | #include <linux/module.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/init.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/backlight.h> | ||
19 | #include <linux/err.h> | ||
20 | #include <linux/dmi.h> | ||
21 | #include <linux/io.h> | ||
22 | #include <linux/rfkill.h> | ||
23 | #include <linux/power_supply.h> | ||
24 | #include <linux/acpi.h> | ||
25 | #include "../firmware/dcdbas.h" | ||
26 | |||
27 | #define BRIGHTNESS_TOKEN 0x7d | ||
28 | |||
29 | /* This structure will be modified by the firmware when we enter | ||
30 | * system management mode, hence the volatiles */ | ||
31 | |||
32 | struct calling_interface_buffer { | ||
33 | u16 class; | ||
34 | u16 select; | ||
35 | volatile u32 input[4]; | ||
36 | volatile u32 output[4]; | ||
37 | } __packed; | ||
38 | |||
39 | struct calling_interface_token { | ||
40 | u16 tokenID; | ||
41 | u16 location; | ||
42 | union { | ||
43 | u16 value; | ||
44 | u16 stringlength; | ||
45 | }; | ||
46 | }; | ||
47 | |||
48 | struct calling_interface_structure { | ||
49 | struct dmi_header header; | ||
50 | u16 cmdIOAddress; | ||
51 | u8 cmdIOCode; | ||
52 | u32 supportedCmds; | ||
53 | struct calling_interface_token tokens[]; | ||
54 | } __packed; | ||
55 | |||
56 | static int da_command_address; | ||
57 | static int da_command_code; | ||
58 | static int da_num_tokens; | ||
59 | static struct calling_interface_token *da_tokens; | ||
60 | |||
61 | static struct backlight_device *dell_backlight_device; | ||
62 | static struct rfkill *wifi_rfkill; | ||
63 | static struct rfkill *bluetooth_rfkill; | ||
64 | static struct rfkill *wwan_rfkill; | ||
65 | |||
66 | static const struct dmi_system_id __initdata dell_device_table[] = { | ||
67 | { | ||
68 | .ident = "Dell laptop", | ||
69 | .matches = { | ||
70 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | ||
71 | DMI_MATCH(DMI_CHASSIS_TYPE, "8"), | ||
72 | }, | ||
73 | }, | ||
74 | { } | ||
75 | }; | ||
76 | |||
77 | static void parse_da_table(const struct dmi_header *dm) | ||
78 | { | ||
79 | /* Final token is a terminator, so we don't want to copy it */ | ||
80 | int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1; | ||
81 | struct calling_interface_structure *table = | ||
82 | container_of(dm, struct calling_interface_structure, header); | ||
83 | |||
84 | /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least | ||
85 | 6 bytes of entry */ | ||
86 | |||
87 | if (dm->length < 17) | ||
88 | return; | ||
89 | |||
90 | da_command_address = table->cmdIOAddress; | ||
91 | da_command_code = table->cmdIOCode; | ||
92 | |||
93 | da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * | ||
94 | sizeof(struct calling_interface_token), | ||
95 | GFP_KERNEL); | ||
96 | |||
97 | if (!da_tokens) | ||
98 | return; | ||
99 | |||
100 | memcpy(da_tokens+da_num_tokens, table->tokens, | ||
101 | sizeof(struct calling_interface_token) * tokens); | ||
102 | |||
103 | da_num_tokens += tokens; | ||
104 | } | ||
105 | |||
106 | static void find_tokens(const struct dmi_header *dm) | ||
107 | { | ||
108 | switch (dm->type) { | ||
109 | case 0xd4: /* Indexed IO */ | ||
110 | break; | ||
111 | case 0xd5: /* Protected Area Type 1 */ | ||
112 | break; | ||
113 | case 0xd6: /* Protected Area Type 2 */ | ||
114 | break; | ||
115 | case 0xda: /* Calling interface */ | ||
116 | parse_da_table(dm); | ||
117 | break; | ||
118 | } | ||
119 | } | ||
120 | |||
121 | static int find_token_location(int tokenid) | ||
122 | { | ||
123 | int i; | ||
124 | for (i = 0; i < da_num_tokens; i++) { | ||
125 | if (da_tokens[i].tokenID == tokenid) | ||
126 | return da_tokens[i].location; | ||
127 | } | ||
128 | |||
129 | return -1; | ||
130 | } | ||
131 | |||
132 | static struct calling_interface_buffer * | ||
133 | dell_send_request(struct calling_interface_buffer *buffer, int class, | ||
134 | int select) | ||
135 | { | ||
136 | struct smi_cmd command; | ||
137 | |||
138 | command.magic = SMI_CMD_MAGIC; | ||
139 | command.command_address = da_command_address; | ||
140 | command.command_code = da_command_code; | ||
141 | command.ebx = virt_to_phys(buffer); | ||
142 | command.ecx = 0x42534931; | ||
143 | |||
144 | buffer->class = class; | ||
145 | buffer->select = select; | ||
146 | |||
147 | dcdbas_smi_request(&command); | ||
148 | |||
149 | return buffer; | ||
150 | } | ||
151 | |||
152 | /* Derived from information in DellWirelessCtl.cpp: | ||
153 | Class 17, select 11 is radio control. It returns an array of 32-bit values. | ||
154 | |||
155 | result[0]: return code | ||
156 | result[1]: | ||
157 | Bit 0: Hardware switch supported | ||
158 | Bit 1: Wifi locator supported | ||
159 | Bit 2: Wifi is supported | ||
160 | Bit 3: Bluetooth is supported | ||
161 | Bit 4: WWAN is supported | ||
162 | Bit 5: Wireless keyboard supported | ||
163 | Bits 6-7: Reserved | ||
164 | Bit 8: Wifi is installed | ||
165 | Bit 9: Bluetooth is installed | ||
166 | Bit 10: WWAN is installed | ||
167 | Bits 11-15: Reserved | ||
168 | Bit 16: Hardware switch is on | ||
169 | Bit 17: Wifi is blocked | ||
170 | Bit 18: Bluetooth is blocked | ||
171 | Bit 19: WWAN is blocked | ||
172 | Bits 20-31: Reserved | ||
173 | result[2]: NVRAM size in bytes | ||
174 | result[3]: NVRAM format version number | ||
175 | */ | ||
176 | |||
177 | static int dell_rfkill_set(int radio, enum rfkill_state state) | ||
178 | { | ||
179 | struct calling_interface_buffer buffer; | ||
180 | int disable = (state == RFKILL_STATE_UNBLOCKED) ? 0 : 1; | ||
181 | |||
182 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | ||
183 | buffer.input[0] = (1 | (radio<<8) | (disable << 16)); | ||
184 | dell_send_request(&buffer, 17, 11); | ||
185 | |||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | static int dell_wifi_set(void *data, enum rfkill_state state) | ||
190 | { | ||
191 | return dell_rfkill_set(1, state); | ||
192 | } | ||
193 | |||
194 | static int dell_bluetooth_set(void *data, enum rfkill_state state) | ||
195 | { | ||
196 | return dell_rfkill_set(2, state); | ||
197 | } | ||
198 | |||
199 | static int dell_wwan_set(void *data, enum rfkill_state state) | ||
200 | { | ||
201 | return dell_rfkill_set(3, state); | ||
202 | } | ||
203 | |||
204 | static int dell_rfkill_get(int bit, enum rfkill_state *state) | ||
205 | { | ||
206 | struct calling_interface_buffer buffer; | ||
207 | int status; | ||
208 | int new_state = RFKILL_STATE_HARD_BLOCKED; | ||
209 | |||
210 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | ||
211 | dell_send_request(&buffer, 17, 11); | ||
212 | status = buffer.output[1]; | ||
213 | |||
214 | if (status & (1<<16)) | ||
215 | new_state = RFKILL_STATE_SOFT_BLOCKED; | ||
216 | |||
217 | if (status & (1<<bit)) | ||
218 | *state = new_state; | ||
219 | else | ||
220 | *state = RFKILL_STATE_UNBLOCKED; | ||
221 | |||
222 | return 0; | ||
223 | } | ||
224 | |||
225 | static int dell_wifi_get(void *data, enum rfkill_state *state) | ||
226 | { | ||
227 | return dell_rfkill_get(17, state); | ||
228 | } | ||
229 | |||
230 | static int dell_bluetooth_get(void *data, enum rfkill_state *state) | ||
231 | { | ||
232 | return dell_rfkill_get(18, state); | ||
233 | } | ||
234 | |||
235 | static int dell_wwan_get(void *data, enum rfkill_state *state) | ||
236 | { | ||
237 | return dell_rfkill_get(19, state); | ||
238 | } | ||
239 | |||
240 | static int dell_setup_rfkill(void) | ||
241 | { | ||
242 | struct calling_interface_buffer buffer; | ||
243 | int status; | ||
244 | int ret; | ||
245 | |||
246 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | ||
247 | dell_send_request(&buffer, 17, 11); | ||
248 | status = buffer.output[1]; | ||
249 | |||
250 | if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) { | ||
251 | wifi_rfkill = rfkill_allocate(NULL, RFKILL_TYPE_WLAN); | ||
252 | if (!wifi_rfkill) | ||
253 | goto err_wifi; | ||
254 | wifi_rfkill->name = "dell-wifi"; | ||
255 | wifi_rfkill->toggle_radio = dell_wifi_set; | ||
256 | wifi_rfkill->get_state = dell_wifi_get; | ||
257 | ret = rfkill_register(wifi_rfkill); | ||
258 | if (ret) | ||
259 | goto err_wifi; | ||
260 | } | ||
261 | |||
262 | if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) { | ||
263 | bluetooth_rfkill = rfkill_allocate(NULL, RFKILL_TYPE_BLUETOOTH); | ||
264 | if (!bluetooth_rfkill) | ||
265 | goto err_bluetooth; | ||
266 | bluetooth_rfkill->name = "dell-bluetooth"; | ||
267 | bluetooth_rfkill->toggle_radio = dell_bluetooth_set; | ||
268 | bluetooth_rfkill->get_state = dell_bluetooth_get; | ||
269 | ret = rfkill_register(bluetooth_rfkill); | ||
270 | if (ret) | ||
271 | goto err_bluetooth; | ||
272 | } | ||
273 | |||
274 | if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) { | ||
275 | wwan_rfkill = rfkill_allocate(NULL, RFKILL_TYPE_WWAN); | ||
276 | if (!wwan_rfkill) | ||
277 | goto err_wwan; | ||
278 | wwan_rfkill->name = "dell-wwan"; | ||
279 | wwan_rfkill->toggle_radio = dell_wwan_set; | ||
280 | wwan_rfkill->get_state = dell_wwan_get; | ||
281 | ret = rfkill_register(wwan_rfkill); | ||
282 | if (ret) | ||
283 | goto err_wwan; | ||
284 | } | ||
285 | |||
286 | return 0; | ||
287 | err_wwan: | ||
288 | if (wwan_rfkill) | ||
289 | rfkill_free(wwan_rfkill); | ||
290 | if (bluetooth_rfkill) { | ||
291 | rfkill_unregister(bluetooth_rfkill); | ||
292 | bluetooth_rfkill = NULL; | ||
293 | } | ||
294 | err_bluetooth: | ||
295 | if (bluetooth_rfkill) | ||
296 | rfkill_free(bluetooth_rfkill); | ||
297 | if (wifi_rfkill) { | ||
298 | rfkill_unregister(wifi_rfkill); | ||
299 | wifi_rfkill = NULL; | ||
300 | } | ||
301 | err_wifi: | ||
302 | if (wifi_rfkill) | ||
303 | rfkill_free(wifi_rfkill); | ||
304 | |||
305 | return ret; | ||
306 | } | ||
307 | |||
308 | static int dell_send_intensity(struct backlight_device *bd) | ||
309 | { | ||
310 | struct calling_interface_buffer buffer; | ||
311 | |||
312 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | ||
313 | buffer.input[0] = find_token_location(BRIGHTNESS_TOKEN); | ||
314 | buffer.input[1] = bd->props.brightness; | ||
315 | |||
316 | if (buffer.input[0] == -1) | ||
317 | return -ENODEV; | ||
318 | |||
319 | if (power_supply_is_system_supplied() > 0) | ||
320 | dell_send_request(&buffer, 1, 2); | ||
321 | else | ||
322 | dell_send_request(&buffer, 1, 1); | ||
323 | |||
324 | return 0; | ||
325 | } | ||
326 | |||
327 | static int dell_get_intensity(struct backlight_device *bd) | ||
328 | { | ||
329 | struct calling_interface_buffer buffer; | ||
330 | |||
331 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | ||
332 | buffer.input[0] = find_token_location(BRIGHTNESS_TOKEN); | ||
333 | |||
334 | if (buffer.input[0] == -1) | ||
335 | return -ENODEV; | ||
336 | |||
337 | if (power_supply_is_system_supplied() > 0) | ||
338 | dell_send_request(&buffer, 0, 2); | ||
339 | else | ||
340 | dell_send_request(&buffer, 0, 1); | ||
341 | |||
342 | return buffer.output[1]; | ||
343 | } | ||
344 | |||
345 | static struct backlight_ops dell_ops = { | ||
346 | .get_brightness = dell_get_intensity, | ||
347 | .update_status = dell_send_intensity, | ||
348 | }; | ||
349 | |||
350 | static int __init dell_init(void) | ||
351 | { | ||
352 | struct calling_interface_buffer buffer; | ||
353 | int max_intensity = 0; | ||
354 | int ret; | ||
355 | |||
356 | if (!dmi_check_system(dell_device_table)) | ||
357 | return -ENODEV; | ||
358 | |||
359 | dmi_walk(find_tokens); | ||
360 | |||
361 | if (!da_tokens) { | ||
362 | printk(KERN_INFO "dell-laptop: Unable to find dmi tokens\n"); | ||
363 | return -ENODEV; | ||
364 | } | ||
365 | |||
366 | ret = dell_setup_rfkill(); | ||
367 | |||
368 | if (ret) { | ||
369 | printk(KERN_WARNING "dell-laptop: Unable to setup rfkill\n"); | ||
370 | goto out; | ||
371 | } | ||
372 | |||
373 | #ifdef CONFIG_ACPI | ||
374 | /* In the event of an ACPI backlight being available, don't | ||
375 | * register the platform controller. | ||
376 | */ | ||
377 | if (acpi_video_backlight_support()) | ||
378 | return 0; | ||
379 | #endif | ||
380 | |||
381 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | ||
382 | buffer.input[0] = find_token_location(BRIGHTNESS_TOKEN); | ||
383 | |||
384 | if (buffer.input[0] != -1) { | ||
385 | dell_send_request(&buffer, 0, 2); | ||
386 | max_intensity = buffer.output[3]; | ||
387 | } | ||
388 | |||
389 | if (max_intensity) { | ||
390 | dell_backlight_device = backlight_device_register( | ||
391 | "dell_backlight", | ||
392 | NULL, NULL, | ||
393 | &dell_ops); | ||
394 | |||
395 | if (IS_ERR(dell_backlight_device)) { | ||
396 | ret = PTR_ERR(dell_backlight_device); | ||
397 | dell_backlight_device = NULL; | ||
398 | goto out; | ||
399 | } | ||
400 | |||
401 | dell_backlight_device->props.max_brightness = max_intensity; | ||
402 | dell_backlight_device->props.brightness = | ||
403 | dell_get_intensity(dell_backlight_device); | ||
404 | backlight_update_status(dell_backlight_device); | ||
405 | } | ||
406 | |||
407 | return 0; | ||
408 | out: | ||
409 | if (wifi_rfkill) | ||
410 | rfkill_unregister(wifi_rfkill); | ||
411 | if (bluetooth_rfkill) | ||
412 | rfkill_unregister(bluetooth_rfkill); | ||
413 | if (wwan_rfkill) | ||
414 | rfkill_unregister(wwan_rfkill); | ||
415 | kfree(da_tokens); | ||
416 | return ret; | ||
417 | } | ||
418 | |||
419 | static void __exit dell_exit(void) | ||
420 | { | ||
421 | backlight_device_unregister(dell_backlight_device); | ||
422 | if (wifi_rfkill) | ||
423 | rfkill_unregister(wifi_rfkill); | ||
424 | if (bluetooth_rfkill) | ||
425 | rfkill_unregister(bluetooth_rfkill); | ||
426 | if (wwan_rfkill) | ||
427 | rfkill_unregister(wwan_rfkill); | ||
428 | } | ||
429 | |||
430 | module_init(dell_init); | ||
431 | module_exit(dell_exit); | ||
432 | |||
433 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); | ||
434 | MODULE_DESCRIPTION("Dell laptop driver"); | ||
435 | MODULE_LICENSE("GPL"); | ||
436 | MODULE_ALIAS("dmi:*svnDellInc.:*:ct8:*"); | ||