| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 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> | 
| Len Brown | cad7312 | 2009-01-09 17:23:38 -0500 | [diff] [blame] | 25 | #include "../../firmware/dcdbas.h" | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 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 |  | 
| Jean Delvare | e7a19c5 | 2009-03-30 21:46:44 +0200 | [diff] [blame] | 106 | static void find_tokens(const struct dmi_header *dm, void *dummy) | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 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 |  | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 177 | static int dell_rfkill_set(void *data, bool blocked) | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 178 | { | 
|  | 179 | struct calling_interface_buffer buffer; | 
| Johannes Berg | 624f0de | 2009-06-15 16:26:47 +0200 | [diff] [blame] | 180 | int disable = blocked ? 1 : 0; | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 181 | unsigned long radio = (unsigned long)data; | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 182 |  | 
|  | 183 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | 
|  | 184 | buffer.input[0] = (1 | (radio<<8) | (disable << 16)); | 
|  | 185 | dell_send_request(&buffer, 17, 11); | 
|  | 186 |  | 
|  | 187 | return 0; | 
|  | 188 | } | 
|  | 189 |  | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 190 | static void dell_rfkill_query(struct rfkill *rfkill, void *data) | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 191 | { | 
|  | 192 | struct calling_interface_buffer buffer; | 
|  | 193 | int status; | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 194 | int bit = (unsigned long)data + 16; | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 195 |  | 
|  | 196 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | 
|  | 197 | dell_send_request(&buffer, 17, 11); | 
|  | 198 | status = buffer.output[1]; | 
|  | 199 |  | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 200 | if (status & BIT(bit)) | 
|  | 201 | rfkill_set_hw_state(rfkill, !!(status & BIT(16))); | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 202 | } | 
|  | 203 |  | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 204 | static const struct rfkill_ops dell_rfkill_ops = { | 
|  | 205 | .set_block = dell_rfkill_set, | 
|  | 206 | .query = dell_rfkill_query, | 
|  | 207 | }; | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 208 |  | 
|  | 209 | static int dell_setup_rfkill(void) | 
|  | 210 | { | 
|  | 211 | struct calling_interface_buffer buffer; | 
|  | 212 | int status; | 
|  | 213 | int ret; | 
|  | 214 |  | 
|  | 215 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | 
|  | 216 | dell_send_request(&buffer, 17, 11); | 
|  | 217 | status = buffer.output[1]; | 
|  | 218 |  | 
|  | 219 | if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) { | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 220 | wifi_rfkill = rfkill_alloc("dell-wifi", NULL, RFKILL_TYPE_WLAN, | 
|  | 221 | &dell_rfkill_ops, (void *) 1); | 
|  | 222 | if (!wifi_rfkill) { | 
|  | 223 | ret = -ENOMEM; | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 224 | goto err_wifi; | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 225 | } | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 226 | ret = rfkill_register(wifi_rfkill); | 
|  | 227 | if (ret) | 
|  | 228 | goto err_wifi; | 
|  | 229 | } | 
|  | 230 |  | 
|  | 231 | if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) { | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 232 | bluetooth_rfkill = rfkill_alloc("dell-bluetooth", NULL, | 
|  | 233 | RFKILL_TYPE_BLUETOOTH, | 
|  | 234 | &dell_rfkill_ops, (void *) 2); | 
|  | 235 | if (!bluetooth_rfkill) { | 
|  | 236 | ret = -ENOMEM; | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 237 | goto err_bluetooth; | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 238 | } | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 239 | ret = rfkill_register(bluetooth_rfkill); | 
|  | 240 | if (ret) | 
|  | 241 | goto err_bluetooth; | 
|  | 242 | } | 
|  | 243 |  | 
|  | 244 | if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) { | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 245 | wwan_rfkill = rfkill_alloc("dell-wwan", NULL, RFKILL_TYPE_WWAN, | 
|  | 246 | &dell_rfkill_ops, (void *) 3); | 
|  | 247 | if (!wwan_rfkill) { | 
|  | 248 | ret = -ENOMEM; | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 249 | goto err_wwan; | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 250 | } | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 251 | ret = rfkill_register(wwan_rfkill); | 
|  | 252 | if (ret) | 
|  | 253 | goto err_wwan; | 
|  | 254 | } | 
|  | 255 |  | 
|  | 256 | return 0; | 
|  | 257 | err_wwan: | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 258 | rfkill_destroy(wwan_rfkill); | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 259 | if (bluetooth_rfkill) | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 260 | rfkill_unregister(bluetooth_rfkill); | 
|  | 261 | err_bluetooth: | 
|  | 262 | rfkill_destroy(bluetooth_rfkill); | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 263 | if (wifi_rfkill) | 
| Johannes Berg | 19d337d | 2009-06-02 13:01:37 +0200 | [diff] [blame] | 264 | rfkill_unregister(wifi_rfkill); | 
|  | 265 | err_wifi: | 
|  | 266 | rfkill_destroy(wifi_rfkill); | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 267 |  | 
|  | 268 | return ret; | 
|  | 269 | } | 
|  | 270 |  | 
|  | 271 | static int dell_send_intensity(struct backlight_device *bd) | 
|  | 272 | { | 
|  | 273 | struct calling_interface_buffer buffer; | 
|  | 274 |  | 
|  | 275 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | 
|  | 276 | buffer.input[0] = find_token_location(BRIGHTNESS_TOKEN); | 
|  | 277 | buffer.input[1] = bd->props.brightness; | 
|  | 278 |  | 
|  | 279 | if (buffer.input[0] == -1) | 
|  | 280 | return -ENODEV; | 
|  | 281 |  | 
|  | 282 | if (power_supply_is_system_supplied() > 0) | 
|  | 283 | dell_send_request(&buffer, 1, 2); | 
|  | 284 | else | 
|  | 285 | dell_send_request(&buffer, 1, 1); | 
|  | 286 |  | 
|  | 287 | return 0; | 
|  | 288 | } | 
|  | 289 |  | 
|  | 290 | static int dell_get_intensity(struct backlight_device *bd) | 
|  | 291 | { | 
|  | 292 | struct calling_interface_buffer buffer; | 
|  | 293 |  | 
|  | 294 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | 
|  | 295 | buffer.input[0] = find_token_location(BRIGHTNESS_TOKEN); | 
|  | 296 |  | 
|  | 297 | if (buffer.input[0] == -1) | 
|  | 298 | return -ENODEV; | 
|  | 299 |  | 
|  | 300 | if (power_supply_is_system_supplied() > 0) | 
|  | 301 | dell_send_request(&buffer, 0, 2); | 
|  | 302 | else | 
|  | 303 | dell_send_request(&buffer, 0, 1); | 
|  | 304 |  | 
|  | 305 | return buffer.output[1]; | 
|  | 306 | } | 
|  | 307 |  | 
|  | 308 | static struct backlight_ops dell_ops = { | 
|  | 309 | .get_brightness = dell_get_intensity, | 
|  | 310 | .update_status  = dell_send_intensity, | 
|  | 311 | }; | 
|  | 312 |  | 
|  | 313 | static int __init dell_init(void) | 
|  | 314 | { | 
|  | 315 | struct calling_interface_buffer buffer; | 
|  | 316 | int max_intensity = 0; | 
|  | 317 | int ret; | 
|  | 318 |  | 
|  | 319 | if (!dmi_check_system(dell_device_table)) | 
|  | 320 | return -ENODEV; | 
|  | 321 |  | 
| Jean Delvare | e7a19c5 | 2009-03-30 21:46:44 +0200 | [diff] [blame] | 322 | dmi_walk(find_tokens, NULL); | 
| Matthew Garrett | ad8f07c | 2009-01-07 18:08:56 -0800 | [diff] [blame] | 323 |  | 
|  | 324 | if (!da_tokens)  { | 
|  | 325 | printk(KERN_INFO "dell-laptop: Unable to find dmi tokens\n"); | 
|  | 326 | return -ENODEV; | 
|  | 327 | } | 
|  | 328 |  | 
|  | 329 | ret = dell_setup_rfkill(); | 
|  | 330 |  | 
|  | 331 | if (ret) { | 
|  | 332 | printk(KERN_WARNING "dell-laptop: Unable to setup rfkill\n"); | 
|  | 333 | goto out; | 
|  | 334 | } | 
|  | 335 |  | 
|  | 336 | #ifdef CONFIG_ACPI | 
|  | 337 | /* In the event of an ACPI backlight being available, don't | 
|  | 338 | * register the platform controller. | 
|  | 339 | */ | 
|  | 340 | if (acpi_video_backlight_support()) | 
|  | 341 | return 0; | 
|  | 342 | #endif | 
|  | 343 |  | 
|  | 344 | memset(&buffer, 0, sizeof(struct calling_interface_buffer)); | 
|  | 345 | buffer.input[0] = find_token_location(BRIGHTNESS_TOKEN); | 
|  | 346 |  | 
|  | 347 | if (buffer.input[0] != -1) { | 
|  | 348 | dell_send_request(&buffer, 0, 2); | 
|  | 349 | max_intensity = buffer.output[3]; | 
|  | 350 | } | 
|  | 351 |  | 
|  | 352 | if (max_intensity) { | 
|  | 353 | dell_backlight_device = backlight_device_register( | 
|  | 354 | "dell_backlight", | 
|  | 355 | NULL, NULL, | 
|  | 356 | &dell_ops); | 
|  | 357 |  | 
|  | 358 | if (IS_ERR(dell_backlight_device)) { | 
|  | 359 | ret = PTR_ERR(dell_backlight_device); | 
|  | 360 | dell_backlight_device = NULL; | 
|  | 361 | goto out; | 
|  | 362 | } | 
|  | 363 |  | 
|  | 364 | dell_backlight_device->props.max_brightness = max_intensity; | 
|  | 365 | dell_backlight_device->props.brightness = | 
|  | 366 | dell_get_intensity(dell_backlight_device); | 
|  | 367 | backlight_update_status(dell_backlight_device); | 
|  | 368 | } | 
|  | 369 |  | 
|  | 370 | return 0; | 
|  | 371 | out: | 
|  | 372 | if (wifi_rfkill) | 
|  | 373 | rfkill_unregister(wifi_rfkill); | 
|  | 374 | if (bluetooth_rfkill) | 
|  | 375 | rfkill_unregister(bluetooth_rfkill); | 
|  | 376 | if (wwan_rfkill) | 
|  | 377 | rfkill_unregister(wwan_rfkill); | 
|  | 378 | kfree(da_tokens); | 
|  | 379 | return ret; | 
|  | 380 | } | 
|  | 381 |  | 
|  | 382 | static void __exit dell_exit(void) | 
|  | 383 | { | 
|  | 384 | backlight_device_unregister(dell_backlight_device); | 
|  | 385 | if (wifi_rfkill) | 
|  | 386 | rfkill_unregister(wifi_rfkill); | 
|  | 387 | if (bluetooth_rfkill) | 
|  | 388 | rfkill_unregister(bluetooth_rfkill); | 
|  | 389 | if (wwan_rfkill) | 
|  | 390 | rfkill_unregister(wwan_rfkill); | 
|  | 391 | } | 
|  | 392 |  | 
|  | 393 | module_init(dell_init); | 
|  | 394 | module_exit(dell_exit); | 
|  | 395 |  | 
|  | 396 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); | 
|  | 397 | MODULE_DESCRIPTION("Dell laptop driver"); | 
|  | 398 | MODULE_LICENSE("GPL"); | 
|  | 399 | MODULE_ALIAS("dmi:*svnDellInc.:*:ct8:*"); |