diff --git a/README.md b/README.md index 4fa9d5a..03e059f 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,19 @@ Led subsystem allows us to control the leds on the laptop including the keyboard - 2: Half - 3: Full +- `/sys/class/leds/msiacpi::kbd_backlight/available_kbd_bl_modes` + - Description: This entry reports all supported keyboard backlight modes. + - Access: Read + - Valid values: Newline separated list of strings. + +- `/sys/class/leds/msiacpi::kbd_backlight/kbd_bl_mode` + - Description: This entry allows switching the keyboard backlight mode. It allows control on when to use the keyboard backlight. + - Access: Read, Write + - Valid values: + - Values reported by `/sys/class/leds/msiacpi::kbd_backlight/available_kbd_bl_modes`. Some of the possible values: + - always: always keep the keyboard backlight enabled. + - timeout: automatically disable the keyboard backlight after 10s from last interaction with they keyboard or touchpad. + ### Debug mode You can use module *parameters* to get direct read-write access to the EC or force-load a configuration diff --git a/ec_memory_configuration.h b/ec_memory_configuration.h index ff9503f..4e238d0 100644 --- a/ec_memory_configuration.h +++ b/ec_memory_configuration.h @@ -74,7 +74,7 @@ struct msi_ec_led_conf { #define MSI_EC_KBD_BL_STATE_MASK 0x3 struct msi_ec_kbd_bl_conf { int bl_mode_address; - int bl_modes[2]; + struct msi_ec_mode bl_modes[5]; int max_mode; int bl_state_address; diff --git a/msi-ec.c b/msi-ec.c index 2461809..68c5fdf 100644 --- a/msi-ec.c +++ b/msi-ec.c @@ -54,6 +54,9 @@ static DEFINE_MUTEX(ec_set_bit_mutex); #define FM_BASIC_NAME "basic" #define FM_ADVANCED_NAME "advanced" +#define KBD_BL_ALWAYS_NAME "always" +#define KBD_BL_TIMEOUT_NAME "timeout" + /* **************** Gen 1 - WMI1 **************** */ static const char *ALLOWED_FW_G1_0[] __initconst = { @@ -118,7 +121,11 @@ static struct msi_ec_conf CONF_G1_0 __initdata = { }, .kbd_bl = { .bl_mode_address = 0x2c, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xf3, .state_base_value = 0x80, @@ -201,7 +208,11 @@ static struct msi_ec_conf CONF_G1_1 __initdata = { }, .kbd_bl = { .bl_mode_address = 0x2c, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xf3, .state_base_value = 0x80, @@ -272,7 +283,11 @@ static struct msi_ec_conf CONF_G1_2 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = MSI_EC_ADDR_UNSUPP, // RGB .state_base_value = 0x80, @@ -356,7 +371,11 @@ static struct msi_ec_conf CONF_G1_3 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = MSI_EC_ADDR_UNSUPP, // RGB .state_base_value = 0x80, @@ -426,7 +445,11 @@ static struct msi_ec_conf CONF_G1_4 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNKNOWN, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xf3, .state_base_value = 0x80, @@ -499,7 +522,11 @@ static struct msi_ec_conf CONF_G1_5 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xf3, .state_base_value = 0x80, @@ -576,7 +603,11 @@ static struct msi_ec_conf CONF_G1_6 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNKNOWN, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xf3, .state_base_value = 0x80, @@ -659,7 +690,11 @@ static struct msi_ec_conf CONF_G1_7 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xf3, .state_base_value = 0x80, @@ -725,7 +760,11 @@ static struct msi_ec_conf CONF_G1_8 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xf3, .state_base_value = 0x80, @@ -797,7 +836,11 @@ static struct msi_ec_conf CONF_G1_9 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = MSI_EC_ADDR_UNSUPP, .state_base_value = 0x80, @@ -864,7 +907,11 @@ static struct msi_ec_conf CONF_G1_10 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = MSI_EC_ADDR_UNSUPP, // RGB .state_base_value = 0x80, @@ -938,7 +985,11 @@ static struct msi_ec_conf CONF_G1_11 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNKNOWN, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xf3, .state_base_value = 0x80, @@ -1006,7 +1057,11 @@ static struct msi_ec_conf CONF_G1_13 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xF3, .state_base_value = 0x80, @@ -1087,7 +1142,11 @@ static struct msi_ec_conf CONF_G2_0 __initdata = { }, .kbd_bl = { .bl_mode_address = 0x2c, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xd3, .state_base_value = 0x80, @@ -1208,7 +1267,11 @@ static struct msi_ec_conf CONF_G2_1 __initdata = { }, .kbd_bl = { .bl_mode_address = 0x2c, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xd3, // mix of single and RGB .state_base_value = 0x80, @@ -1297,7 +1360,9 @@ static struct msi_ec_conf CONF_G2_2 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { }, + .bl_modes = { + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = MSI_EC_ADDR_UNSUPP, .state_base_value = 0x80, @@ -1387,7 +1452,11 @@ static struct msi_ec_conf CONF_G2_3 __initdata = { }, .kbd_bl = { .bl_mode_address = 0x2c, - .bl_modes = { 0x00, 0x08 }, // 00 - on, 08 - 10 sec auto off + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, // 00 - on, 08 - 10 sec auto off .max_mode = 1, .bl_state_address = 0xd3, .state_base_value = 0x80, @@ -1456,7 +1525,11 @@ static struct msi_ec_conf CONF_G2_4 __initdata = { }, .kbd_bl = { .bl_mode_address = 0x2c, - .bl_modes = { 0x00, 0x08 }, // 00 - on, 08 - 10 sec auto off + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, // 00 - on, 08 - 10 sec auto off .max_mode = 1, .bl_state_address = 0xd3, .state_base_value = 0x80, @@ -1527,7 +1600,11 @@ static struct msi_ec_conf CONF_G2_5 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = MSI_EC_ADDR_UNSUPP, .state_base_value = 0x80, @@ -1609,7 +1686,9 @@ static struct msi_ec_conf CONF_G2_6 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { }, + .bl_modes = { + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = 0xd3, .state_base_value = 0x80, @@ -1747,7 +1826,11 @@ static struct msi_ec_conf CONF_G2_10 __initdata = { }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, - .bl_modes = { 0x00, 0x08 }, + .bl_modes = { + { KBD_BL_ALWAYS_NAME, 0x00 }, + { KBD_BL_TIMEOUT_NAME, 0x08 }, + MSI_EC_MODE_NULL + }, .max_mode = 1, .bl_state_address = MSI_EC_ADDR_UNSUPP, .state_base_value = 0x80, @@ -2778,6 +2861,105 @@ static int kbd_bl_sysfs_set(struct led_classdev *led_cdev, return ec_write(conf.kbd_bl.bl_state_address, wdata); } +static ssize_t available_kbd_bl_modes_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + int result = 0; + int count = 0; + + for (int i = 0; conf.kbd_bl.bl_modes[i].name; i++) { + // NULL entries have NULL name + + result = sysfs_emit_at(buf, count, "%s\n", conf.kbd_bl.bl_modes[i].name); + if (result < 0) + return result; + count += result; + } + + return count; +} + +static ssize_t kbd_bl_mode_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + u8 rdata; + int result; + + result = ec_read(conf.kbd_bl.bl_mode_address, &rdata); + if (result < 0) + return result; + + for (int i = 0; conf.kbd_bl.bl_modes[i].name; i++) { + // NULL entries have NULL name + + if (rdata == conf.kbd_bl.bl_modes[i].value) { + return sysfs_emit(buf, "%s\n", conf.kbd_bl.bl_modes[i].name); + } + } + + return sysfs_emit(buf, "%s (%i)\n", "unknown", rdata); +} + +static ssize_t kbd_bl_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int result; + + for (int i = 0; conf.kbd_bl.bl_modes[i].name; i++) { + // NULL entries have NULL name + + if (sysfs_streq(conf.kbd_bl.bl_modes[i].name, buf)) { + result = ec_write(conf.kbd_bl.bl_mode_address, + conf.kbd_bl.bl_modes[i].value); + if (result < 0) + return result; + + return count; + } + } + + return -EINVAL; +} + +static DEVICE_ATTR_RO(available_kbd_bl_modes); +static DEVICE_ATTR_RW(kbd_bl_mode); + +static struct attribute *kbd_bl_attrs[] = { + &dev_attr_available_kbd_bl_modes.attr, + &dev_attr_kbd_bl_mode.attr, + NULL +}; + +static umode_t kbd_bl_is_visible(struct kobject *kobj, + struct attribute *attr, + int idx) +{ + int address; + + if (!conf_loaded) + return 0; + + if (attr == &dev_attr_available_kbd_bl_modes.attr || + attr == &dev_attr_kbd_bl_mode.attr) + address = conf.kbd_bl.bl_mode_address; + + else + return attr->mode; + + return address == MSI_EC_ADDR_UNSUPP ? 0 : attr->mode; +} + +static const struct attribute_group kbd_bl_group = { + .attrs = kbd_bl_attrs, + .is_visible = kbd_bl_is_visible, +}; + +static const struct attribute_group *kbd_bl_groups[] = { + &kbd_bl_group, + NULL, +}; + static struct led_classdev micmute_led_cdev = { .name = "platform::micmute", .max_brightness = 1, @@ -2798,6 +2980,7 @@ static struct led_classdev msiacpi_led_kbdlight = { .flags = LED_BRIGHT_HW_CHANGED, .brightness_set_blocking = &kbd_bl_sysfs_set, .brightness_get = &kbd_bl_sysfs_get, + .groups = kbd_bl_groups, }; // ============================================================ //