18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Copyright (C) 2010 Dell Inc. 38c2ecf20Sopenharmony_ci * Louis Davis <louis_davis@dell.com> 48c2ecf20Sopenharmony_ci * Jim Dailey <jim_dailey@dell.com> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 78c2ecf20Sopenharmony_ci * it under the terms of the GNU General Public License as 88c2ecf20Sopenharmony_ci * published by the Free Software Foundation. 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/acpi.h> 138c2ecf20Sopenharmony_ci#include <linux/leds.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ciMODULE_AUTHOR("Louis Davis/Jim Dailey"); 188c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Dell LED Control Driver"); 198c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396" 228c2ecf20Sopenharmony_ciMODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* Error Result Codes: */ 258c2ecf20Sopenharmony_ci#define INVALID_DEVICE_ID 250 268c2ecf20Sopenharmony_ci#define INVALID_PARAMETER 251 278c2ecf20Sopenharmony_ci#define INVALID_BUFFER 252 288c2ecf20Sopenharmony_ci#define INTERFACE_ERROR 253 298c2ecf20Sopenharmony_ci#define UNSUPPORTED_COMMAND 254 308c2ecf20Sopenharmony_ci#define UNSPECIFIED_ERROR 255 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci/* Device ID */ 338c2ecf20Sopenharmony_ci#define DEVICE_ID_PANEL_BACK 1 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/* LED Commands */ 368c2ecf20Sopenharmony_ci#define CMD_LED_ON 16 378c2ecf20Sopenharmony_ci#define CMD_LED_OFF 17 388c2ecf20Sopenharmony_ci#define CMD_LED_BLINK 18 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistruct bios_args { 418c2ecf20Sopenharmony_ci unsigned char length; 428c2ecf20Sopenharmony_ci unsigned char result_code; 438c2ecf20Sopenharmony_ci unsigned char device_id; 448c2ecf20Sopenharmony_ci unsigned char command; 458c2ecf20Sopenharmony_ci unsigned char on_time; 468c2ecf20Sopenharmony_ci unsigned char off_time; 478c2ecf20Sopenharmony_ci}; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic int dell_led_perform_fn(u8 length, u8 result_code, u8 device_id, 508c2ecf20Sopenharmony_ci u8 command, u8 on_time, u8 off_time) 518c2ecf20Sopenharmony_ci{ 528c2ecf20Sopenharmony_ci struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 538c2ecf20Sopenharmony_ci struct bios_args *bios_return; 548c2ecf20Sopenharmony_ci struct acpi_buffer input; 558c2ecf20Sopenharmony_ci union acpi_object *obj; 568c2ecf20Sopenharmony_ci acpi_status status; 578c2ecf20Sopenharmony_ci u8 return_code; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci struct bios_args args = { 608c2ecf20Sopenharmony_ci .length = length, 618c2ecf20Sopenharmony_ci .result_code = result_code, 628c2ecf20Sopenharmony_ci .device_id = device_id, 638c2ecf20Sopenharmony_ci .command = command, 648c2ecf20Sopenharmony_ci .on_time = on_time, 658c2ecf20Sopenharmony_ci .off_time = off_time 668c2ecf20Sopenharmony_ci }; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci input.length = sizeof(struct bios_args); 698c2ecf20Sopenharmony_ci input.pointer = &args; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 0, 1, &input, &output); 728c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 738c2ecf20Sopenharmony_ci return status; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci obj = output.pointer; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci if (!obj) 788c2ecf20Sopenharmony_ci return -EINVAL; 798c2ecf20Sopenharmony_ci if (obj->type != ACPI_TYPE_BUFFER) { 808c2ecf20Sopenharmony_ci kfree(obj); 818c2ecf20Sopenharmony_ci return -EINVAL; 828c2ecf20Sopenharmony_ci } 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci bios_return = ((struct bios_args *)obj->buffer.pointer); 858c2ecf20Sopenharmony_ci return_code = bios_return->result_code; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci kfree(obj); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return return_code; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic int led_on(void) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci return dell_led_perform_fn(3, /* Length of command */ 958c2ecf20Sopenharmony_ci INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ 968c2ecf20Sopenharmony_ci DEVICE_ID_PANEL_BACK, /* Device ID */ 978c2ecf20Sopenharmony_ci CMD_LED_ON, /* Command */ 988c2ecf20Sopenharmony_ci 0, /* not used */ 998c2ecf20Sopenharmony_ci 0); /* not used */ 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic int led_off(void) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci return dell_led_perform_fn(3, /* Length of command */ 1058c2ecf20Sopenharmony_ci INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ 1068c2ecf20Sopenharmony_ci DEVICE_ID_PANEL_BACK, /* Device ID */ 1078c2ecf20Sopenharmony_ci CMD_LED_OFF, /* Command */ 1088c2ecf20Sopenharmony_ci 0, /* not used */ 1098c2ecf20Sopenharmony_ci 0); /* not used */ 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic int led_blink(unsigned char on_eighths, unsigned char off_eighths) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci return dell_led_perform_fn(5, /* Length of command */ 1158c2ecf20Sopenharmony_ci INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ 1168c2ecf20Sopenharmony_ci DEVICE_ID_PANEL_BACK, /* Device ID */ 1178c2ecf20Sopenharmony_ci CMD_LED_BLINK, /* Command */ 1188c2ecf20Sopenharmony_ci on_eighths, /* blink on in eigths of a second */ 1198c2ecf20Sopenharmony_ci off_eighths); /* blink off in eights of a second */ 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic void dell_led_set(struct led_classdev *led_cdev, 1238c2ecf20Sopenharmony_ci enum led_brightness value) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci if (value == LED_OFF) 1268c2ecf20Sopenharmony_ci led_off(); 1278c2ecf20Sopenharmony_ci else 1288c2ecf20Sopenharmony_ci led_on(); 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_cistatic int dell_led_blink(struct led_classdev *led_cdev, 1328c2ecf20Sopenharmony_ci unsigned long *delay_on, unsigned long *delay_off) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci unsigned long on_eighths; 1358c2ecf20Sopenharmony_ci unsigned long off_eighths; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci /* 1388c2ecf20Sopenharmony_ci * The Dell LED delay is based on 125ms intervals. 1398c2ecf20Sopenharmony_ci * Need to round up to next interval. 1408c2ecf20Sopenharmony_ci */ 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci on_eighths = DIV_ROUND_UP(*delay_on, 125); 1438c2ecf20Sopenharmony_ci on_eighths = clamp_t(unsigned long, on_eighths, 1, 255); 1448c2ecf20Sopenharmony_ci *delay_on = on_eighths * 125; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci off_eighths = DIV_ROUND_UP(*delay_off, 125); 1478c2ecf20Sopenharmony_ci off_eighths = clamp_t(unsigned long, off_eighths, 1, 255); 1488c2ecf20Sopenharmony_ci *delay_off = off_eighths * 125; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci led_blink(on_eighths, off_eighths); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci return 0; 1538c2ecf20Sopenharmony_ci} 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic struct led_classdev dell_led = { 1568c2ecf20Sopenharmony_ci .name = "dell::lid", 1578c2ecf20Sopenharmony_ci .brightness = LED_OFF, 1588c2ecf20Sopenharmony_ci .max_brightness = 1, 1598c2ecf20Sopenharmony_ci .brightness_set = dell_led_set, 1608c2ecf20Sopenharmony_ci .blink_set = dell_led_blink, 1618c2ecf20Sopenharmony_ci .flags = LED_CORE_SUSPENDRESUME, 1628c2ecf20Sopenharmony_ci}; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic int __init dell_led_init(void) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci int error = 0; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (!wmi_has_guid(DELL_LED_BIOS_GUID)) 1698c2ecf20Sopenharmony_ci return -ENODEV; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci error = led_off(); 1728c2ecf20Sopenharmony_ci if (error != 0) 1738c2ecf20Sopenharmony_ci return -ENODEV; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci return led_classdev_register(NULL, &dell_led); 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic void __exit dell_led_exit(void) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci led_classdev_unregister(&dell_led); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci led_off(); 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_cimodule_init(dell_led_init); 1868c2ecf20Sopenharmony_cimodule_exit(dell_led_exit); 187