1
2
3
4
5
6
7
8
9
10
11
12
13
14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
15
16#include <linux/module.h>
17#include <linux/kernel.h>
18#include <linux/init.h>
19#include <linux/platform_device.h>
20#include <linux/backlight.h>
21#include <linux/err.h>
22#include <linux/dmi.h>
23#include <linux/io.h>
24#include <linux/rfkill.h>
25#include <linux/power_supply.h>
26#include <linux/acpi.h>
27#include <linux/mm.h>
28#include <linux/i8042.h>
29#include <linux/slab.h>
30#include <linux/debugfs.h>
31#include <linux/seq_file.h>
32#include "../../firmware/dcdbas.h"
33
34#define BRIGHTNESS_TOKEN 0x7d
35
36
37
38
39struct calling_interface_buffer {
40 u16 class;
41 u16 select;
42 volatile u32 input[4];
43 volatile u32 output[4];
44} __packed;
45
46struct calling_interface_token {
47 u16 tokenID;
48 u16 location;
49 union {
50 u16 value;
51 u16 stringlength;
52 };
53};
54
55struct calling_interface_structure {
56 struct dmi_header header;
57 u16 cmdIOAddress;
58 u8 cmdIOCode;
59 u32 supportedCmds;
60 struct calling_interface_token tokens[];
61} __packed;
62
63static int da_command_address;
64static int da_command_code;
65static int da_num_tokens;
66static struct calling_interface_token *da_tokens;
67
68static struct platform_driver platform_driver = {
69 .driver = {
70 .name = "dell-laptop",
71 .owner = THIS_MODULE,
72 }
73};
74
75static struct platform_device *platform_device;
76static struct backlight_device *dell_backlight_device;
77static struct rfkill *wifi_rfkill;
78static struct rfkill *bluetooth_rfkill;
79static struct rfkill *wwan_rfkill;
80
81static const struct dmi_system_id __initdata dell_device_table[] = {
82 {
83 .ident = "Dell laptop",
84 .matches = {
85 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
86 DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
87 },
88 },
89 {
90 .matches = {
91 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
92 DMI_MATCH(DMI_CHASSIS_TYPE, "9"),
93 },
94 },
95 {
96 .ident = "Dell Computer Corporation",
97 .matches = {
98 DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
99 DMI_MATCH(DMI_CHASSIS_TYPE, "8"),
100 },
101 },
102 { }
103};
104
105static struct dmi_system_id __devinitdata dell_blacklist[] = {
106
107 {
108 .ident = "Dell Mini 9",
109 .matches = {
110 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
111 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 910"),
112 },
113 },
114 {
115 .ident = "Dell Mini 10",
116 .matches = {
117 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
118 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1010"),
119 },
120 },
121 {
122 .ident = "Dell Mini 10v",
123 .matches = {
124 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
125 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1011"),
126 },
127 },
128 {
129 .ident = "Dell Mini 1012",
130 .matches = {
131 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
132 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"),
133 },
134 },
135 {
136 .ident = "Dell Inspiron 11z",
137 .matches = {
138 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
139 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1110"),
140 },
141 },
142 {
143 .ident = "Dell Mini 12",
144 .matches = {
145 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
146 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1210"),
147 },
148 },
149 {}
150};
151
152static struct calling_interface_buffer *buffer;
153static struct page *bufferpage;
154static DEFINE_MUTEX(buffer_mutex);
155
156static int hwswitch_state;
157
158static void get_buffer(void)
159{
160 mutex_lock(&buffer_mutex);
161 memset(buffer, 0, sizeof(struct calling_interface_buffer));
162}
163
164static void release_buffer(void)
165{
166 mutex_unlock(&buffer_mutex);
167}
168
169static void __init parse_da_table(const struct dmi_header *dm)
170{
171
172 int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1;
173 struct calling_interface_structure *table =
174 container_of(dm, struct calling_interface_structure, header);
175
176
177
178
179 if (dm->length < 17)
180 return;
181
182 da_command_address = table->cmdIOAddress;
183 da_command_code = table->cmdIOCode;
184
185 da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) *
186 sizeof(struct calling_interface_token),
187 GFP_KERNEL);
188
189 if (!da_tokens)
190 return;
191
192 memcpy(da_tokens+da_num_tokens, table->tokens,
193 sizeof(struct calling_interface_token) * tokens);
194
195 da_num_tokens += tokens;
196}
197
198static void __init find_tokens(const struct dmi_header *dm, void *dummy)
199{
200 switch (dm->type) {
201 case 0xd4:
202 break;
203 case 0xd5:
204 break;
205 case 0xd6:
206 break;
207 case 0xda:
208 parse_da_table(dm);
209 break;
210 }
211}
212
213static int find_token_location(int tokenid)
214{
215 int i;
216 for (i = 0; i < da_num_tokens; i++) {
217 if (da_tokens[i].tokenID == tokenid)
218 return da_tokens[i].location;
219 }
220
221 return -1;
222}
223
224static struct calling_interface_buffer *
225dell_send_request(struct calling_interface_buffer *buffer, int class,
226 int select)
227{
228 struct smi_cmd command;
229
230 command.magic = SMI_CMD_MAGIC;
231 command.command_address = da_command_address;
232 command.command_code = da_command_code;
233 command.ebx = virt_to_phys(buffer);
234 command.ecx = 0x42534931;
235
236 buffer->class = class;
237 buffer->select = select;
238
239 dcdbas_smi_request(&command);
240
241 return buffer;
242}
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284static int dell_rfkill_set(void *data, bool blocked)
285{
286 int disable = blocked ? 1 : 0;
287 unsigned long radio = (unsigned long)data;
288 int hwswitch_bit = (unsigned long)data - 1;
289 int ret = 0;
290
291 get_buffer();
292 dell_send_request(buffer, 17, 11);
293
294
295
296 if ((hwswitch_state & BIT(hwswitch_bit)) &&
297 !(buffer->output[1] & BIT(16))) {
298 ret = -EINVAL;
299 goto out;
300 }
301
302 buffer->input[0] = (1 | (radio<<8) | (disable << 16));
303 dell_send_request(buffer, 17, 11);
304
305out:
306 release_buffer();
307 return ret;
308}
309
310static void dell_rfkill_query(struct rfkill *rfkill, void *data)
311{
312 int status;
313 int bit = (unsigned long)data + 16;
314 int hwswitch_bit = (unsigned long)data - 1;
315
316 get_buffer();
317 dell_send_request(buffer, 17, 11);
318 status = buffer->output[1];
319 release_buffer();
320
321 rfkill_set_sw_state(rfkill, !!(status & BIT(bit)));
322
323 if (hwswitch_state & (BIT(hwswitch_bit)))
324 rfkill_set_hw_state(rfkill, !(status & BIT(16)));
325}
326
327static const struct rfkill_ops dell_rfkill_ops = {
328 .set_block = dell_rfkill_set,
329 .query = dell_rfkill_query,
330};
331
332static struct dentry *dell_laptop_dir;
333
334static int dell_debugfs_show(struct seq_file *s, void *data)
335{
336 int status;
337
338 get_buffer();
339 dell_send_request(buffer, 17, 11);
340 status = buffer->output[1];
341 release_buffer();
342
343 seq_printf(s, "status:\t0x%X\n", status);
344 seq_printf(s, "Bit 0 : Hardware switch supported: %lu\n",
345 status & BIT(0));
346 seq_printf(s, "Bit 1 : Wifi locator supported: %lu\n",
347 (status & BIT(1)) >> 1);
348 seq_printf(s, "Bit 2 : Wifi is supported: %lu\n",
349 (status & BIT(2)) >> 2);
350 seq_printf(s, "Bit 3 : Bluetooth is supported: %lu\n",
351 (status & BIT(3)) >> 3);
352 seq_printf(s, "Bit 4 : WWAN is supported: %lu\n",
353 (status & BIT(4)) >> 4);
354 seq_printf(s, "Bit 5 : Wireless keyboard supported: %lu\n",
355 (status & BIT(5)) >> 5);
356 seq_printf(s, "Bit 8 : Wifi is installed: %lu\n",
357 (status & BIT(8)) >> 8);
358 seq_printf(s, "Bit 9 : Bluetooth is installed: %lu\n",
359 (status & BIT(9)) >> 9);
360 seq_printf(s, "Bit 10: WWAN is installed: %lu\n",
361 (status & BIT(10)) >> 10);
362 seq_printf(s, "Bit 16: Hardware switch is on: %lu\n",
363 (status & BIT(16)) >> 16);
364 seq_printf(s, "Bit 17: Wifi is blocked: %lu\n",
365 (status & BIT(17)) >> 17);
366 seq_printf(s, "Bit 18: Bluetooth is blocked: %lu\n",
367 (status & BIT(18)) >> 18);
368 seq_printf(s, "Bit 19: WWAN is blocked: %lu\n",
369 (status & BIT(19)) >> 19);
370
371 seq_printf(s, "\nhwswitch_state:\t0x%X\n", hwswitch_state);
372 seq_printf(s, "Bit 0 : Wifi controlled by switch: %lu\n",
373 hwswitch_state & BIT(0));
374 seq_printf(s, "Bit 1 : Bluetooth controlled by switch: %lu\n",
375 (hwswitch_state & BIT(1)) >> 1);
376 seq_printf(s, "Bit 2 : WWAN controlled by switch: %lu\n",
377 (hwswitch_state & BIT(2)) >> 2);
378 seq_printf(s, "Bit 7 : Wireless switch config locked: %lu\n",
379 (hwswitch_state & BIT(7)) >> 7);
380 seq_printf(s, "Bit 8 : Wifi locator enabled: %lu\n",
381 (hwswitch_state & BIT(8)) >> 8);
382 seq_printf(s, "Bit 15: Wifi locator setting locked: %lu\n",
383 (hwswitch_state & BIT(15)) >> 15);
384
385 return 0;
386}
387
388static int dell_debugfs_open(struct inode *inode, struct file *file)
389{
390 return single_open(file, dell_debugfs_show, inode->i_private);
391}
392
393static const struct file_operations dell_debugfs_fops = {
394 .owner = THIS_MODULE,
395 .open = dell_debugfs_open,
396 .read = seq_read,
397 .llseek = seq_lseek,
398 .release = single_release,
399};
400
401static void dell_update_rfkill(struct work_struct *ignored)
402{
403 if (wifi_rfkill)
404 dell_rfkill_query(wifi_rfkill, (void *)1);
405 if (bluetooth_rfkill)
406 dell_rfkill_query(bluetooth_rfkill, (void *)2);
407 if (wwan_rfkill)
408 dell_rfkill_query(wwan_rfkill, (void *)3);
409}
410static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill);
411
412
413static int __init dell_setup_rfkill(void)
414{
415 int status;
416 int ret;
417
418 if (dmi_check_system(dell_blacklist)) {
419 pr_info("Blacklisted hardware detected - not enabling rfkill\n");
420 return 0;
421 }
422
423 get_buffer();
424 dell_send_request(buffer, 17, 11);
425 status = buffer->output[1];
426 buffer->input[0] = 0x2;
427 dell_send_request(buffer, 17, 11);
428 hwswitch_state = buffer->output[1];
429 release_buffer();
430
431 if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) {
432 wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev,
433 RFKILL_TYPE_WLAN,
434 &dell_rfkill_ops, (void *) 1);
435 if (!wifi_rfkill) {
436 ret = -ENOMEM;
437 goto err_wifi;
438 }
439 ret = rfkill_register(wifi_rfkill);
440 if (ret)
441 goto err_wifi;
442 }
443
444 if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) {
445 bluetooth_rfkill = rfkill_alloc("dell-bluetooth",
446 &platform_device->dev,
447 RFKILL_TYPE_BLUETOOTH,
448 &dell_rfkill_ops, (void *) 2);
449 if (!bluetooth_rfkill) {
450 ret = -ENOMEM;
451 goto err_bluetooth;
452 }
453 ret = rfkill_register(bluetooth_rfkill);
454 if (ret)
455 goto err_bluetooth;
456 }
457
458 if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) {
459 wwan_rfkill = rfkill_alloc("dell-wwan",
460 &platform_device->dev,
461 RFKILL_TYPE_WWAN,
462 &dell_rfkill_ops, (void *) 3);
463 if (!wwan_rfkill) {
464 ret = -ENOMEM;
465 goto err_wwan;
466 }
467 ret = rfkill_register(wwan_rfkill);
468 if (ret)
469 goto err_wwan;
470 }
471
472 return 0;
473err_wwan:
474 rfkill_destroy(wwan_rfkill);
475 if (bluetooth_rfkill)
476 rfkill_unregister(bluetooth_rfkill);
477err_bluetooth:
478 rfkill_destroy(bluetooth_rfkill);
479 if (wifi_rfkill)
480 rfkill_unregister(wifi_rfkill);
481err_wifi:
482 rfkill_destroy(wifi_rfkill);
483
484 return ret;
485}
486
487static void dell_cleanup_rfkill(void)
488{
489 if (wifi_rfkill) {
490 rfkill_unregister(wifi_rfkill);
491 rfkill_destroy(wifi_rfkill);
492 }
493 if (bluetooth_rfkill) {
494 rfkill_unregister(bluetooth_rfkill);
495 rfkill_destroy(bluetooth_rfkill);
496 }
497 if (wwan_rfkill) {
498 rfkill_unregister(wwan_rfkill);
499 rfkill_destroy(wwan_rfkill);
500 }
501}
502
503static int dell_send_intensity(struct backlight_device *bd)
504{
505 int ret = 0;
506
507 get_buffer();
508 buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
509 buffer->input[1] = bd->props.brightness;
510
511 if (buffer->input[0] == -1) {
512 ret = -ENODEV;
513 goto out;
514 }
515
516 if (power_supply_is_system_supplied() > 0)
517 dell_send_request(buffer, 1, 2);
518 else
519 dell_send_request(buffer, 1, 1);
520
521out:
522 release_buffer();
523 return 0;
524}
525
526static int dell_get_intensity(struct backlight_device *bd)
527{
528 int ret = 0;
529
530 get_buffer();
531 buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
532
533 if (buffer->input[0] == -1) {
534 ret = -ENODEV;
535 goto out;
536 }
537
538 if (power_supply_is_system_supplied() > 0)
539 dell_send_request(buffer, 0, 2);
540 else
541 dell_send_request(buffer, 0, 1);
542
543 ret = buffer->output[1];
544
545out:
546 release_buffer();
547 return ret;
548}
549
550static const struct backlight_ops dell_ops = {
551 .get_brightness = dell_get_intensity,
552 .update_status = dell_send_intensity,
553};
554
555static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str,
556 struct serio *port)
557{
558 static bool extended;
559
560 if (str & 0x20)
561 return false;
562
563 if (unlikely(data == 0xe0)) {
564 extended = true;
565 return false;
566 } else if (unlikely(extended)) {
567 switch (data) {
568 case 0x8:
569 schedule_delayed_work(&dell_rfkill_work,
570 round_jiffies_relative(HZ));
571 break;
572 }
573 extended = false;
574 }
575
576 return false;
577}
578
579static int __init dell_init(void)
580{
581 int max_intensity = 0;
582 int ret;
583
584 if (!dmi_check_system(dell_device_table))
585 return -ENODEV;
586
587 dmi_walk(find_tokens, NULL);
588
589 if (!da_tokens) {
590 pr_info("Unable to find dmi tokens\n");
591 return -ENODEV;
592 }
593
594 ret = platform_driver_register(&platform_driver);
595 if (ret)
596 goto fail_platform_driver;
597 platform_device = platform_device_alloc("dell-laptop", -1);
598 if (!platform_device) {
599 ret = -ENOMEM;
600 goto fail_platform_device1;
601 }
602 ret = platform_device_add(platform_device);
603 if (ret)
604 goto fail_platform_device2;
605
606
607
608
609
610 bufferpage = alloc_page(GFP_KERNEL | GFP_DMA32);
611
612 if (!bufferpage)
613 goto fail_buffer;
614 buffer = page_address(bufferpage);
615
616 ret = dell_setup_rfkill();
617
618 if (ret) {
619 pr_warn("Unable to setup rfkill\n");
620 goto fail_rfkill;
621 }
622
623 ret = i8042_install_filter(dell_laptop_i8042_filter);
624 if (ret) {
625 pr_warn("Unable to install key filter\n");
626 goto fail_filter;
627 }
628
629 dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL);
630 if (dell_laptop_dir != NULL)
631 debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL,
632 &dell_debugfs_fops);
633
634#ifdef CONFIG_ACPI
635
636
637
638 if (acpi_video_backlight_support())
639 return 0;
640#endif
641
642 get_buffer();
643 buffer->input[0] = find_token_location(BRIGHTNESS_TOKEN);
644 if (buffer->input[0] != -1) {
645 dell_send_request(buffer, 0, 2);
646 max_intensity = buffer->output[3];
647 }
648 release_buffer();
649
650 if (max_intensity) {
651 struct backlight_properties props;
652 memset(&props, 0, sizeof(struct backlight_properties));
653 props.type = BACKLIGHT_PLATFORM;
654 props.max_brightness = max_intensity;
655 dell_backlight_device = backlight_device_register("dell_backlight",
656 &platform_device->dev,
657 NULL,
658 &dell_ops,
659 &props);
660
661 if (IS_ERR(dell_backlight_device)) {
662 ret = PTR_ERR(dell_backlight_device);
663 dell_backlight_device = NULL;
664 goto fail_backlight;
665 }
666
667 dell_backlight_device->props.brightness =
668 dell_get_intensity(dell_backlight_device);
669 backlight_update_status(dell_backlight_device);
670 }
671
672 return 0;
673
674fail_backlight:
675 i8042_remove_filter(dell_laptop_i8042_filter);
676 cancel_delayed_work_sync(&dell_rfkill_work);
677fail_filter:
678 dell_cleanup_rfkill();
679fail_rfkill:
680 free_page((unsigned long)bufferpage);
681fail_buffer:
682 platform_device_del(platform_device);
683fail_platform_device2:
684 platform_device_put(platform_device);
685fail_platform_device1:
686 platform_driver_unregister(&platform_driver);
687fail_platform_driver:
688 kfree(da_tokens);
689 return ret;
690}
691
692static void __exit dell_exit(void)
693{
694 debugfs_remove_recursive(dell_laptop_dir);
695 i8042_remove_filter(dell_laptop_i8042_filter);
696 cancel_delayed_work_sync(&dell_rfkill_work);
697 backlight_device_unregister(dell_backlight_device);
698 dell_cleanup_rfkill();
699 if (platform_device) {
700 platform_device_unregister(platform_device);
701 platform_driver_unregister(&platform_driver);
702 }
703 kfree(da_tokens);
704 free_page((unsigned long)buffer);
705}
706
707module_init(dell_init);
708module_exit(dell_exit);
709
710MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
711MODULE_DESCRIPTION("Dell laptop driver");
712MODULE_LICENSE("GPL");
713MODULE_ALIAS("dmi:*svnDellInc.:*:ct8:*");
714MODULE_ALIAS("dmi:*svnDellInc.:*:ct9:*");
715MODULE_ALIAS("dmi:*svnDellComputerCorporation.:*:ct8:*");
716