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