Merge branch 'bkl/procfs' of git://git.kernel.org/pub/scm/linux/kernel/git/frederic...
[linux-2.6-block.git] / drivers / platform / x86 / dell-laptop.c
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 <linux/mm.h>
26 #include <linux/i8042.h>
27 #include <linux/slab.h>
28 #include "../../firmware/dcdbas.h"
29
30 #define BRIGHTNESS_TOKEN 0x7d
31
32 /* This structure will be modified by the firmware when we enter
33  * system management mode, hence the volatiles */
34
35 struct calling_interface_buffer {
36         u16 class;
37         u16 select;
38         volatile u32 input[4];
39         volatile u32 output[4];
40 } __packed;
41
42 struct calling_interface_token {
43         u16 tokenID;
44         u16 location;
45         union {
46                 u16 value;
47                 u16 stringlength;
48         };
49 };
50
51 struct calling_interface_structure {
52         struct dmi_header header;
53         u16 cmdIOAddress;
54         u8 cmdIOCode;
55         u32 supportedCmds;
56         struct calling_interface_token tokens[];
57 } __packed;
58
59 static int da_command_address;
60 static int da_command_code;
61 static int da_num_tokens;
62 static struct calling_interface_token *da_tokens;
63
64 static struct platform_driver platform_driver = {
65         .driver = {
66                 .name = "dell-laptop",
67                 .owner = THIS_MODULE,
68         }
69 };
70
71 static struct platform_device *platform_device;
72 static struct backlight_device *dell_backlight_device;
73 static struct rfkill *wifi_rfkill;
74 static struct rfkill *bluetooth_rfkill;
75 static struct rfkill *wwan_rfkill;
76
77 static const struct dmi_system_id __initdata dell_device_table[] = {
78         {
79                 .ident = "Dell laptop",
80                 .matches = {
81                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
82                         DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
83                 },
84         },
85         {
86                 .ident = "Dell Computer Corporation",
87                 .matches = {
88                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
89                         DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
90                 },
91         },
92         { }
93 };
94
95 static struct dmi_system_id __devinitdata dell_blacklist[] = {
96         /* Supported by compal-laptop */
97         {
98                 .ident = "Dell Mini 9",
99                 .matches = {
100                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
101                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 910"),
102                 },
103         },
104         {
105                 .ident = "Dell Mini 10",
106                 .matches = {
107                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
108                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1010"),
109                 },
110         },
111         {
112                 .ident = "Dell Mini 10v",
113                 .matches = {
114                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
115                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1011"),
116                 },
117         },
118         {
119                 .ident = "Dell Inspiron 11z",
120                 .matches = {
121                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
122                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1110"),
123                 },
124         },
125         {
126                 .ident = "Dell Mini 12",
127                 .matches = {
128                         DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
129                         DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1210"),
130                 },
131         },
132         {}
133 };
134
135 static struct calling_interface_buffer *buffer;
136 static struct page *bufferpage;
137 static DEFINE_MUTEX(buffer_mutex);
138
139 static int hwswitch_state;
140
141 static void get_buffer(void)
142 {
143         mutex_lock(&buffer_mutex);
144         memset(buffer, 0, sizeof(struct calling_interface_buffer));
145 }
146
147 static void release_buffer(void)
148 {
149         mutex_unlock(&buffer_mutex);
150 }
151
152 static void __init parse_da_table(const struct dmi_header *dm)
153 {
154         /* Final token is a terminator, so we don't want to copy it */
155         int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1;
156         struct calling_interface_structure *table =
157                 container_of(dm, struct calling_interface_structure, header);
158
159         /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least
160            6 bytes of entry */
161
162         if (dm->length < 17)
163                 return;
164
165         da_command_address = table->cmdIOAddress;
166         da_command_code = table->cmdIOCode;
167
168         da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) *
169                              sizeof(struct calling_interface_token),
170                              GFP_KERNEL);
171
172         if (!da_tokens)
173                 return;
174
175         memcpy(da_tokens+da_num_tokens, table->tokens,
176                sizeof(struct calling_interface_token) * tokens);
177
178         da_num_tokens += tokens;
179 }
180
181 static void __init find_tokens(const struct dmi_header *dm, void *dummy)
182 {
183         switch (dm->type) {
184         case 0xd4: /* Indexed IO */
185                 break;
186         case 0xd5: /* Protected Area Type 1 */
187                 break;
188         case 0xd6: /* Protected Area Type 2 */
189                 break;
190         case 0xda: /* Calling interface */
191                 parse_da_table(dm);
192                 break;
193         }
194 }
195
196 static int find_token_location(int tokenid)
197 {
198         int i;
199         for (i = 0; i < da_num_tokens; i++) {
200                 if (da_tokens[i].tokenID == tokenid)
201                         return da_tokens[i].location;
202         }
203
204         return -1;
205 }
206
207 static struct calling_interface_buffer *
208 dell_send_request(struct calling_interface_buffer *buffer, int class,
209                   int select)
210 {
211         struct smi_cmd command;
212
213         command.magic = SMI_CMD_MAGIC;
214         command.command_address = da_command_address;
215         command.command_code = da_command_code;
216         command.ebx = virt_to_phys(buffer);
217         command.ecx = 0x42534931;
218
219         buffer->class = class;
220         buffer->select = select;
221
222         dcdbas_smi_request(&command);
223
224         return buffer;
225 }
226
227 /* Derived from information in DellWirelessCtl.cpp:
228    Class 17, select 11 is radio control. It returns an array of 32-bit values.
229
230    Input byte 0 = 0: Wireless information
231
232    result[0]: return code
233    result[1]:
234      Bit 0:      Hardware switch supported
235      Bit 1:      Wifi locator supported
236      Bit 2:      Wifi is supported
237      Bit 3:      Bluetooth is supported
238      Bit 4:      WWAN is supported
239      Bit 5:      Wireless keyboard supported
240      Bits 6-7:   Reserved
241      Bit 8:      Wifi is installed
242      Bit 9:      Bluetooth is installed
243      Bit 10:     WWAN is installed
244      Bits 11-15: Reserved
245      Bit 16:     Hardware switch is on
246      Bit 17:     Wifi is blocked
247      Bit 18:     Bluetooth is blocked
248      Bit 19:     WWAN is blocked
249      Bits 20-31: Reserved
250    result[2]: NVRAM size in bytes
251    result[3]: NVRAM format version number
252
253    Input byte 0 = 2: Wireless switch configuration
254    result[0]: return code
255    result[1]:
256      Bit 0:      Wifi controlled by switch
257      Bit 1:      Bluetooth controlled by switch
258      Bit 2:      WWAN controlled by switch
259      Bits 3-6:   Reserved
260      Bit 7:      Wireless switch config locked
261      Bit 8:      Wifi locator enabled
262      Bits 9-14:  Reserved
263      Bit 15:     Wifi locator setting locked
264      Bits 16-31: Reserved
265 */
266
267 static int dell_rfkill_set(void *data, bool blocked)
268 {
269         int disable = blocked ? 1 : 0;
270         unsigned long radio = (unsigned long)data;
271         int hwswitch_bit = (unsigned long)data - 1;
272         int ret = 0;
273
274         get_buffer();
275         dell_send_request(buffer, 17, 11);
276
277         /* If the hardware switch controls this radio, and the hardware
278            switch is disabled, don't allow changing the software state */
279         if ((hwswitch_state & BIT(hwswitch_bit)) &&
280             !(buffer->output[1] & BIT(16))) {
281                 ret = -EINVAL;
282                 goto out;
283         }
284
285         buffer->input[0] = (1 | (radio<<8) | (disable << 16));
286         dell_send_request(buffer, 17, 11);
287
288 out:
289         release_buffer();
290         return ret;
291 }
292
293 static void dell_rfkill_query(struct rfkill *rfkill, void *data)
294 {
295         int status;
296         int bit = (unsigned long)data + 16;
297         int hwswitch_bit = (unsigned long)data - 1;
298
299         get_buffer();
300         dell_send_request(buffer, 17, 11);
301         status = buffer->output[1];
302         release_buffer();
303
304         rfkill_set_sw_state(rfkill, !!(status & BIT(bit)));
305
306         if (hwswitch_state & (BIT(hwswitch_bit)))
307                 rfkill_set_hw_state(rfkill, !(status & BIT(16)));
308 }
309
310 static const struct rfkill_ops dell_rfkill_ops = {
311         .set_block = dell_rfkill_set,
312         .query = dell_rfkill_query,
313 };
314
315 static void dell_update_rfkill(struct work_struct *ignored)
316 {
317         if (wifi_rfkill)
318                 dell_rfkill_query(wifi_rfkill, (void *)1);
319         if (bluetooth_rfkill)
320                 dell_rfkill_query(bluetooth_rfkill, (void *)2);
321         if (wwan_rfkill)
322                 dell_rfkill_query(wwan_rfkill, (void *)3);
323 }
324 static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill);
325
326
327 static int __init dell_setup_rfkill(void)
328 {
329         int status;
330         int ret;
331
332         if (dmi_check_system(dell_blacklist)) {
333                 printk(KERN_INFO "dell-laptop: Blacklisted hardware detected - "
334                                 "not enabling rfkill\n");
335                 return 0;
336         }
337
338         get_buffer();
339         dell_send_request(buffer, 17, 11);
340         status = buffer->output[1];
341         buffer->input[0] = 0x2;
342         dell_send_request(buffer, 17, 11);
343         hwswitch_state = buffer->output[1];
344         release_buffer();
345
346         if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) {
347                 wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev,
348                                            RFKILL_TYPE_WLAN,
349                                            &dell_rfkill_ops, (void *) 1);
350                 if (!wifi_rfkill) {
351                         ret = -ENOMEM;
352                         goto err_wifi;
353                 }
354                 ret = rfkill_register(wifi_rfkill);
355                 if (ret)
356                         goto err_wifi;
357         }
358
359         if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) {
360                 bluetooth_rfkill = rfkill_alloc("dell-bluetooth",
361                                                 &platform_device->dev,
362                                                 RFKILL_TYPE_BLUETOOTH,
363                                                 &dell_rfkill_ops, (void *) 2);
364                 if (!bluetooth_rfkill) {
365                         ret = -ENOMEM;
366                         goto err_bluetooth;
367                 }
368                 ret = rfkill_register(bluetooth_rfkill);
369                 if (ret)
370                         goto err_bluetooth;
371         }
372
373         if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) {
374                 wwan_rfkill = rfkill_alloc("dell-wwan",
375                                            &platform_device->dev,
376                                            RFKILL_TYPE_WWAN,
377                                            &dell_rfkill_ops, (void *) 3);
378                 if (!wwan_rfkill) {
379                         ret = -ENOMEM;
380                         goto err_wwan;
381                 }
382                 ret = rfkill_register(wwan_rfkill);
383                 if (ret)
384                         goto err_wwan;
385         }
386
387         return 0;
388 err_wwan:
389         rfkill_destroy(wwan_rfkill);
390         if (bluetooth_rfkill)
391                 rfkill_unregister(bluetooth_rfkill);
392 err_bluetooth:
393         rfkill_destroy(bluetooth_rfkill);
394         if (wifi_rfkill)
395                 rfkill_unregister(wifi_rfkill);
396 err_wifi:
397         rfkill_destroy(wifi_rfkill);
398
399         return ret;
400 }
401
402 static void dell_cleanup_rfkill(void)
403 {
404         if (wifi_rfkill) {
405                 rfkill_unregister(wifi_rfkill);
406                 rfkill_destroy(wifi_rfkill);
407         }
408         if (bluetooth_rfkill) {
409                 rfkill_unregister(bluetooth_rfkill);
410                 rfkill_destroy(bluetooth_rfkill);
411         }
412         if (wwan_rfkill) {
413                 rfkill_unregister(wwan_rfkill);
414                 rfkill_destroy(wwan_rfkill);
415         }
416 }
417
418 static int dell_send_intensity(struct backlight_device *bd)
419 {
420         int ret = 0;
421
422         get_buffer();
423         buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
424         buffer->input[1] = bd->props.brightness;
425
426         if (buffer->input[0] == -1) {
427                 ret = -ENODEV;
428                 goto out;
429         }
430
431         if (power_supply_is_system_supplied() > 0)
432                 dell_send_request(buffer, 1, 2);
433         else
434                 dell_send_request(buffer, 1, 1);
435
436 out:
437         release_buffer();
438         return 0;
439 }
440
441 static int dell_get_intensity(struct backlight_device *bd)
442 {
443         int ret = 0;
444
445         get_buffer();
446         buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
447
448         if (buffer->input[0] == -1) {
449                 ret = -ENODEV;
450                 goto out;
451         }
452
453         if (power_supply_is_system_supplied() > 0)
454                 dell_send_request(buffer, 0, 2);
455         else
456                 dell_send_request(buffer, 0, 1);
457
458 out:
459         release_buffer();
460         if (ret)
461                 return ret;
462         return buffer->output[1];
463 }
464
465 static struct backlight_ops dell_ops = {
466         .get_brightness = dell_get_intensity,
467         .update_status  = dell_send_intensity,
468 };
469
470 bool dell_laptop_i8042_filter(unsigned char data, unsigned char str,
471                               struct serio *port)
472 {
473         static bool extended;
474
475         if (str & 0x20)
476                 return false;
477
478         if (unlikely(data == 0xe0)) {
479                 extended = true;
480                 return false;
481         } else if (unlikely(extended)) {
482                 switch (data) {
483                 case 0x8:
484                         schedule_delayed_work(&dell_rfkill_work,
485                                               round_jiffies_relative(HZ));
486                         break;
487                 }
488                 extended = false;
489         }
490
491         return false;
492 }
493
494 static int __init dell_init(void)
495 {
496         int max_intensity = 0;
497         int ret;
498
499         if (!dmi_check_system(dell_device_table))
500                 return -ENODEV;
501
502         dmi_walk(find_tokens, NULL);
503
504         if (!da_tokens)  {
505                 printk(KERN_INFO "dell-laptop: Unable to find dmi tokens\n");
506                 return -ENODEV;
507         }
508
509         ret = platform_driver_register(&platform_driver);
510         if (ret)
511                 goto fail_platform_driver;
512         platform_device = platform_device_alloc("dell-laptop", -1);
513         if (!platform_device) {
514                 ret = -ENOMEM;
515                 goto fail_platform_device1;
516         }
517         ret = platform_device_add(platform_device);
518         if (ret)
519                 goto fail_platform_device2;
520
521         /*
522          * Allocate buffer below 4GB for SMI data--only 32-bit physical addr
523          * is passed to SMI handler.
524          */
525         bufferpage = alloc_page(GFP_KERNEL | GFP_DMA32);
526
527         if (!bufferpage)
528                 goto fail_buffer;
529         buffer = page_address(bufferpage);
530         mutex_init(&buffer_mutex);
531
532         ret = dell_setup_rfkill();
533
534         if (ret) {
535                 printk(KERN_WARNING "dell-laptop: Unable to setup rfkill\n");
536                 goto fail_rfkill;
537         }
538
539         ret = i8042_install_filter(dell_laptop_i8042_filter);
540         if (ret) {
541                 printk(KERN_WARNING
542                        "dell-laptop: Unable to install key filter\n");
543                 goto fail_filter;
544         }
545
546 #ifdef CONFIG_ACPI
547         /* In the event of an ACPI backlight being available, don't
548          * register the platform controller.
549          */
550         if (acpi_video_backlight_support())
551                 return 0;
552 #endif
553
554         get_buffer();
555         buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
556         if (buffer->input[0] != -1) {
557                 dell_send_request(buffer, 0, 2);
558                 max_intensity = buffer->output[3];
559         }
560         release_buffer();
561
562         if (max_intensity) {
563                 struct backlight_properties props;
564                 memset(&props, 0, sizeof(struct backlight_properties));
565                 props.max_brightness = max_intensity;
566                 dell_backlight_device = backlight_device_register("dell_backlight",
567                                                                   &platform_device->dev,
568                                                                   NULL,
569                                                                   &dell_ops,
570                                                                   &props);
571
572                 if (IS_ERR(dell_backlight_device)) {
573                         ret = PTR_ERR(dell_backlight_device);
574                         dell_backlight_device = NULL;
575                         goto fail_backlight;
576                 }
577
578                 dell_backlight_device->props.brightness =
579                         dell_get_intensity(dell_backlight_device);
580                 backlight_update_status(dell_backlight_device);
581         }
582
583         return 0;
584
585 fail_backlight:
586         i8042_remove_filter(dell_laptop_i8042_filter);
587         cancel_delayed_work_sync(&dell_rfkill_work);
588 fail_filter:
589         dell_cleanup_rfkill();
590 fail_rfkill:
591         free_page((unsigned long)bufferpage);
592 fail_buffer:
593         platform_device_del(platform_device);
594 fail_platform_device2:
595         platform_device_put(platform_device);
596 fail_platform_device1:
597         platform_driver_unregister(&platform_driver);
598 fail_platform_driver:
599         kfree(da_tokens);
600         return ret;
601 }
602
603 static void __exit dell_exit(void)
604 {
605         i8042_remove_filter(dell_laptop_i8042_filter);
606         cancel_delayed_work_sync(&dell_rfkill_work);
607         backlight_device_unregister(dell_backlight_device);
608         dell_cleanup_rfkill();
609         if (platform_device) {
610                 platform_device_unregister(platform_device);
611                 platform_driver_unregister(&platform_driver);
612         }
613         kfree(da_tokens);
614         free_page((unsigned long)buffer);
615 }
616
617 module_init(dell_init);
618 module_exit(dell_exit);
619
620 MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
621 MODULE_DESCRIPTION("Dell laptop driver");
622 MODULE_LICENSE("GPL");
623 MODULE_ALIAS("dmi:*svnDellInc.:*:ct8:*");
624 MODULE_ALIAS("dmi:*svnDellComputerCorporation.:*:ct8:*");