1*f1e1ea51SMario Limonciello /* 2*f1e1ea51SMario Limonciello * Copyright (C) 2010 Dell Inc. 3*f1e1ea51SMario Limonciello * Louis Davis <louis_davis@dell.com> 4*f1e1ea51SMario Limonciello * Jim Dailey <jim_dailey@dell.com> 5*f1e1ea51SMario Limonciello * 6*f1e1ea51SMario Limonciello * This program is free software; you can redistribute it and/or modify 7*f1e1ea51SMario Limonciello * it under the terms of the GNU General Public License as 8*f1e1ea51SMario Limonciello * published by the Free Software Foundation. 9*f1e1ea51SMario Limonciello * 10*f1e1ea51SMario Limonciello */ 11*f1e1ea51SMario Limonciello 12*f1e1ea51SMario Limonciello #include <linux/acpi.h> 13*f1e1ea51SMario Limonciello #include <linux/leds.h> 14*f1e1ea51SMario Limonciello #include <linux/slab.h> 15*f1e1ea51SMario Limonciello #include <linux/module.h> 16*f1e1ea51SMario Limonciello 17*f1e1ea51SMario Limonciello MODULE_AUTHOR("Louis Davis/Jim Dailey"); 18*f1e1ea51SMario Limonciello MODULE_DESCRIPTION("Dell LED Control Driver"); 19*f1e1ea51SMario Limonciello MODULE_LICENSE("GPL"); 20*f1e1ea51SMario Limonciello 21*f1e1ea51SMario Limonciello #define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396" 22*f1e1ea51SMario Limonciello MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); 23*f1e1ea51SMario Limonciello 24*f1e1ea51SMario Limonciello /* Error Result Codes: */ 25*f1e1ea51SMario Limonciello #define INVALID_DEVICE_ID 250 26*f1e1ea51SMario Limonciello #define INVALID_PARAMETER 251 27*f1e1ea51SMario Limonciello #define INVALID_BUFFER 252 28*f1e1ea51SMario Limonciello #define INTERFACE_ERROR 253 29*f1e1ea51SMario Limonciello #define UNSUPPORTED_COMMAND 254 30*f1e1ea51SMario Limonciello #define UNSPECIFIED_ERROR 255 31*f1e1ea51SMario Limonciello 32*f1e1ea51SMario Limonciello /* Device ID */ 33*f1e1ea51SMario Limonciello #define DEVICE_ID_PANEL_BACK 1 34*f1e1ea51SMario Limonciello 35*f1e1ea51SMario Limonciello /* LED Commands */ 36*f1e1ea51SMario Limonciello #define CMD_LED_ON 16 37*f1e1ea51SMario Limonciello #define CMD_LED_OFF 17 38*f1e1ea51SMario Limonciello #define CMD_LED_BLINK 18 39*f1e1ea51SMario Limonciello 40*f1e1ea51SMario Limonciello struct bios_args { 41*f1e1ea51SMario Limonciello unsigned char length; 42*f1e1ea51SMario Limonciello unsigned char result_code; 43*f1e1ea51SMario Limonciello unsigned char device_id; 44*f1e1ea51SMario Limonciello unsigned char command; 45*f1e1ea51SMario Limonciello unsigned char on_time; 46*f1e1ea51SMario Limonciello unsigned char off_time; 47*f1e1ea51SMario Limonciello }; 48*f1e1ea51SMario Limonciello 49*f1e1ea51SMario Limonciello static int dell_led_perform_fn(u8 length, u8 result_code, u8 device_id, 50*f1e1ea51SMario Limonciello u8 command, u8 on_time, u8 off_time) 51*f1e1ea51SMario Limonciello { 52*f1e1ea51SMario Limonciello struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; 53*f1e1ea51SMario Limonciello struct bios_args *bios_return; 54*f1e1ea51SMario Limonciello struct acpi_buffer input; 55*f1e1ea51SMario Limonciello union acpi_object *obj; 56*f1e1ea51SMario Limonciello acpi_status status; 57*f1e1ea51SMario Limonciello u8 return_code; 58*f1e1ea51SMario Limonciello 59*f1e1ea51SMario Limonciello struct bios_args args = { 60*f1e1ea51SMario Limonciello .length = length, 61*f1e1ea51SMario Limonciello .result_code = result_code, 62*f1e1ea51SMario Limonciello .device_id = device_id, 63*f1e1ea51SMario Limonciello .command = command, 64*f1e1ea51SMario Limonciello .on_time = on_time, 65*f1e1ea51SMario Limonciello .off_time = off_time 66*f1e1ea51SMario Limonciello }; 67*f1e1ea51SMario Limonciello 68*f1e1ea51SMario Limonciello input.length = sizeof(struct bios_args); 69*f1e1ea51SMario Limonciello input.pointer = &args; 70*f1e1ea51SMario Limonciello 71*f1e1ea51SMario Limonciello status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 0, 1, &input, &output); 72*f1e1ea51SMario Limonciello if (ACPI_FAILURE(status)) 73*f1e1ea51SMario Limonciello return status; 74*f1e1ea51SMario Limonciello 75*f1e1ea51SMario Limonciello obj = output.pointer; 76*f1e1ea51SMario Limonciello 77*f1e1ea51SMario Limonciello if (!obj) 78*f1e1ea51SMario Limonciello return -EINVAL; 79*f1e1ea51SMario Limonciello if (obj->type != ACPI_TYPE_BUFFER) { 80*f1e1ea51SMario Limonciello kfree(obj); 81*f1e1ea51SMario Limonciello return -EINVAL; 82*f1e1ea51SMario Limonciello } 83*f1e1ea51SMario Limonciello 84*f1e1ea51SMario Limonciello bios_return = ((struct bios_args *)obj->buffer.pointer); 85*f1e1ea51SMario Limonciello return_code = bios_return->result_code; 86*f1e1ea51SMario Limonciello 87*f1e1ea51SMario Limonciello kfree(obj); 88*f1e1ea51SMario Limonciello 89*f1e1ea51SMario Limonciello return return_code; 90*f1e1ea51SMario Limonciello } 91*f1e1ea51SMario Limonciello 92*f1e1ea51SMario Limonciello static int led_on(void) 93*f1e1ea51SMario Limonciello { 94*f1e1ea51SMario Limonciello return dell_led_perform_fn(3, /* Length of command */ 95*f1e1ea51SMario Limonciello INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ 96*f1e1ea51SMario Limonciello DEVICE_ID_PANEL_BACK, /* Device ID */ 97*f1e1ea51SMario Limonciello CMD_LED_ON, /* Command */ 98*f1e1ea51SMario Limonciello 0, /* not used */ 99*f1e1ea51SMario Limonciello 0); /* not used */ 100*f1e1ea51SMario Limonciello } 101*f1e1ea51SMario Limonciello 102*f1e1ea51SMario Limonciello static int led_off(void) 103*f1e1ea51SMario Limonciello { 104*f1e1ea51SMario Limonciello return dell_led_perform_fn(3, /* Length of command */ 105*f1e1ea51SMario Limonciello INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ 106*f1e1ea51SMario Limonciello DEVICE_ID_PANEL_BACK, /* Device ID */ 107*f1e1ea51SMario Limonciello CMD_LED_OFF, /* Command */ 108*f1e1ea51SMario Limonciello 0, /* not used */ 109*f1e1ea51SMario Limonciello 0); /* not used */ 110*f1e1ea51SMario Limonciello } 111*f1e1ea51SMario Limonciello 112*f1e1ea51SMario Limonciello static int led_blink(unsigned char on_eighths, unsigned char off_eighths) 113*f1e1ea51SMario Limonciello { 114*f1e1ea51SMario Limonciello return dell_led_perform_fn(5, /* Length of command */ 115*f1e1ea51SMario Limonciello INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ 116*f1e1ea51SMario Limonciello DEVICE_ID_PANEL_BACK, /* Device ID */ 117*f1e1ea51SMario Limonciello CMD_LED_BLINK, /* Command */ 118*f1e1ea51SMario Limonciello on_eighths, /* blink on in eigths of a second */ 119*f1e1ea51SMario Limonciello off_eighths); /* blink off in eights of a second */ 120*f1e1ea51SMario Limonciello } 121*f1e1ea51SMario Limonciello 122*f1e1ea51SMario Limonciello static void dell_led_set(struct led_classdev *led_cdev, 123*f1e1ea51SMario Limonciello enum led_brightness value) 124*f1e1ea51SMario Limonciello { 125*f1e1ea51SMario Limonciello if (value == LED_OFF) 126*f1e1ea51SMario Limonciello led_off(); 127*f1e1ea51SMario Limonciello else 128*f1e1ea51SMario Limonciello led_on(); 129*f1e1ea51SMario Limonciello } 130*f1e1ea51SMario Limonciello 131*f1e1ea51SMario Limonciello static int dell_led_blink(struct led_classdev *led_cdev, 132*f1e1ea51SMario Limonciello unsigned long *delay_on, unsigned long *delay_off) 133*f1e1ea51SMario Limonciello { 134*f1e1ea51SMario Limonciello unsigned long on_eighths; 135*f1e1ea51SMario Limonciello unsigned long off_eighths; 136*f1e1ea51SMario Limonciello 137*f1e1ea51SMario Limonciello /* 138*f1e1ea51SMario Limonciello * The Dell LED delay is based on 125ms intervals. 139*f1e1ea51SMario Limonciello * Need to round up to next interval. 140*f1e1ea51SMario Limonciello */ 141*f1e1ea51SMario Limonciello 142*f1e1ea51SMario Limonciello on_eighths = DIV_ROUND_UP(*delay_on, 125); 143*f1e1ea51SMario Limonciello on_eighths = clamp_t(unsigned long, on_eighths, 1, 255); 144*f1e1ea51SMario Limonciello *delay_on = on_eighths * 125; 145*f1e1ea51SMario Limonciello 146*f1e1ea51SMario Limonciello off_eighths = DIV_ROUND_UP(*delay_off, 125); 147*f1e1ea51SMario Limonciello off_eighths = clamp_t(unsigned long, off_eighths, 1, 255); 148*f1e1ea51SMario Limonciello *delay_off = off_eighths * 125; 149*f1e1ea51SMario Limonciello 150*f1e1ea51SMario Limonciello led_blink(on_eighths, off_eighths); 151*f1e1ea51SMario Limonciello 152*f1e1ea51SMario Limonciello return 0; 153*f1e1ea51SMario Limonciello } 154*f1e1ea51SMario Limonciello 155*f1e1ea51SMario Limonciello static struct led_classdev dell_led = { 156*f1e1ea51SMario Limonciello .name = "dell::lid", 157*f1e1ea51SMario Limonciello .brightness = LED_OFF, 158*f1e1ea51SMario Limonciello .max_brightness = 1, 159*f1e1ea51SMario Limonciello .brightness_set = dell_led_set, 160*f1e1ea51SMario Limonciello .blink_set = dell_led_blink, 161*f1e1ea51SMario Limonciello .flags = LED_CORE_SUSPENDRESUME, 162*f1e1ea51SMario Limonciello }; 163*f1e1ea51SMario Limonciello 164*f1e1ea51SMario Limonciello static int __init dell_led_init(void) 165*f1e1ea51SMario Limonciello { 166*f1e1ea51SMario Limonciello int error = 0; 167*f1e1ea51SMario Limonciello 168*f1e1ea51SMario Limonciello if (!wmi_has_guid(DELL_LED_BIOS_GUID)) 169*f1e1ea51SMario Limonciello return -ENODEV; 170*f1e1ea51SMario Limonciello 171*f1e1ea51SMario Limonciello error = led_off(); 172*f1e1ea51SMario Limonciello if (error != 0) 173*f1e1ea51SMario Limonciello return -ENODEV; 174*f1e1ea51SMario Limonciello 175*f1e1ea51SMario Limonciello return led_classdev_register(NULL, &dell_led); 176*f1e1ea51SMario Limonciello } 177*f1e1ea51SMario Limonciello 178*f1e1ea51SMario Limonciello static void __exit dell_led_exit(void) 179*f1e1ea51SMario Limonciello { 180*f1e1ea51SMario Limonciello led_classdev_unregister(&dell_led); 181*f1e1ea51SMario Limonciello 182*f1e1ea51SMario Limonciello led_off(); 183*f1e1ea51SMario Limonciello } 184*f1e1ea51SMario Limonciello 185*f1e1ea51SMario Limonciello module_init(dell_led_init); 186*f1e1ea51SMario Limonciello module_exit(dell_led_exit); 187