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