#define pr_fmt(fmt) "[hui][led] " fmt #include "events.h" #include #include "hui_internal.h" #include #include #include #include "leds.h" #include "hui_internal.h" #include "log.h" #define BLINK_CODE(...) \ ({ \ struct blink_code ___code = { .params = { 0, 0 }, \ .color = led->current_color, \ __VA_ARGS__ }; \ ___code; \ }) #define BLINK_CODE_SKIP BLINK_CODE(.type = blink_invalid) #define COLOR(name) \ static inline struct blink_code set_current_color_##name( \ struct led *led) \ { \ led->current_color = \ led_lookup_color(led, led_color_name_##name, true); \ return BLINK_CODE_SKIP; \ } #include "../def/colors.h" static u8 __leds_max_location; static inline u8 leds_max_location(void) { return __leds_max_location; } static bool __led_all_dimmable; static inline bool leds_all_dimmable(void) { return __led_all_dimmable; } #define OVERLAYS(...) (led_overlay_t[]){ __VA_ARGS__ NULL } #define OVERLAY(_name_) _name_ #include "../def/leds.c" static LIST_HEAD(leds); static struct kset *led_kset; enum ambient_light_dim_modes { AMBIENT_DIMMING_OFF, AMBIENT_DIMMING_ON = 1 }; const struct led_type *lookup_led_type(const char *name) { int i; for (i = 0; i < ARRAY_SIZE(led_types); i++) { if (sysfs_streq(led_types[i].name, name)) { return led_types + i; } } return NULL; } struct led_color led_lookup_color(const struct led *led, enum led_color_name name, bool allow_fallback) { if (!allow_fallback || led_color_valid(led->obj.colors[name])) return led->obj.colors[name]; return hui_kobj->obj.colors[name]; } struct led_color led_color_premultiply(struct led_color c) { u32 i; struct led_color res; for (i = 0; i < 3; i++) { u32 t = c.c[i]; t *= c.brightness; t /= 0xFF; res.c[i] = t; } res.brightness = 0xFF; return res; } /* * This function scales the brightness 0-100 into the brightness min/max range (0-255). * * Other color components will be left unchanged. */ static struct led_color led_dim_brightness(const struct led *led, struct led_color color, u8 brightness) { u32 t, t2; struct led_color res = color; // First calculate our effective min brightness t = color.brightness; t *= led->brightness_min; t /= 255; // Now calculate the variable part t2 = color.brightness; t2 *= led->brightness_max - led->brightness_min; t2 /= 255; // Now Change it according to the specified brightness // This is from 0-100 t2 *= brightness; t2 /= 100; t += t2; res.brightness = t; return res; } static void update_leds_all_dimmable(void) { struct led *led; bool all_dimmable = true; list_for_each_entry(led, &leds, list) { if (led->options.location == 0) continue; if (!led->dimmable) all_dimmable = false; } __led_all_dimmable = all_dimmable; } /* * This function will iterate over all overlays in a led and call each in turn. * As soon as one returns an valid blink_code, the evaluation is stopped and * this new code is stored. */ static void led_calculate(struct led *led) { struct blink_code code = BLINK_CODE_SKIP; led_overlay_t overlay; int j; pr_debug("Update led <%s> with type <%s>\n", led->name, led->type->name); // Set default color to white led->current_color.brightness = 0xFF; led->current_color.c[0] = 0xFF; led->current_color.c[1] = 0xFF; led->current_color.c[2] = 0xFF; for (j = 0, overlay = led->type->overlays[j]; overlay != NULL; j++, overlay = led->type->overlays[j]) { pr_debug(" -> consult %ps\n", overlay); code = overlay(led); if (code.type != blink_invalid) { led->current_code = code; led->current_code_source = overlay; break; } } } void leds_update(void) { struct led *led; lockdep_assert_held(&hui_update_mutex); list_for_each_entry(led, &leds, list) { struct blink_code old_code = led->current_code; if (led->forced_code.type != blink_invalid) { pr_debug("Force led <%s>\n", led->name); led->current_code = led->forced_code; led->current_code_source = NULL; } else led_calculate(led); if (!blink_code_equals(old_code, led->current_code)) hui_log_led(led); } } /* * The brightness calculation is done as extra step, as this may change quite * often, when using auto dimming. * * This way we can recalculate the brightness without consulting all the * overlays again. Those would just calculate the same result. * * This function also calls the output driver. If the driver needs * pre-multiplied colors, it can use #led_color_premultiply. */ void leds_update_output(void) { struct led *led; lockdep_assert_held(&hui_update_mutex); list_for_each_entry(led, &leds, list) { struct blink_code code = led->current_code; u8 brightness = 100; if (led->dimmable) { u8 factor = event_get_value(dim_modus) == AMBIENT_DIMMING_ON ? (hui_kobj->ambient_light) : 100; brightness = (hui_kobj->brightness * factor) / 100; } code.color = led_dim_brightness( led, code.color, brightness); led->ops->out(code, led->ops_ctx); } } void *avm_hui_add_led(const char *name, const char *type_name, struct device_node *node, const struct avm_hui_led_ops *ops, void *ctx) { int err; const struct led_type *type; struct led *led; type = lookup_led_type(type_name); if (!type) { type = lookup_led_type("unknown"); pr_err("Unknown led type %s for led %s\n", type_name, name); } led = kzalloc(sizeof(struct led), GFP_KERNEL); if (!led) return ERR_PTR(-ENOMEM); led->obj.kobj.kset = led_kset; led->name = name; led->type = type; led->ops = ops; led->ops_ctx = ctx; led->forced_code.type = blink_invalid; led->brightness_min = 0; led->brightness_max = 255; // First copy the original options and then use the dt node to update // them led->options = type->options; if (node) { u32 temp; if (!of_property_read_u32(node, "location", &temp)) led->options.location = temp; led->dimmable = of_property_read_bool(node, "dimmable"); if (!of_property_read_u32_index(node, "brightness-range", 0, &temp)) led->brightness_min = temp; if (!of_property_read_u32_index(node, "brightness-range", 1, &temp)) led->brightness_max = temp; colored_kobject_init_from_dt(&led->obj, node); } err = kobject_init_and_add(&led->obj.kobj, &led_ktype, NULL, "%s", name); if (err) { return ERR_PTR(err); } err = sysfs_create_group(&led->obj.kobj, &colored_attribute_group); if (err) { kobject_put(&led->obj.kobj); return ERR_PTR(err); } mutex_lock(&hui_update_mutex); list_add_tail(&led->list, &leds); if (led->options.location > __leds_max_location) __leds_max_location = led->options.location; update_leds_all_dimmable(); mutex_unlock(&hui_update_mutex); hui_schedule_update(); return led; } EXPORT_SYMBOL(avm_hui_add_led); void avm_hui_remove_led(void *handle) { struct led *led = handle; if (!led || IS_ERR(led)) return; mutex_lock(&hui_update_mutex); list_del(&led->list); mutex_unlock(&hui_update_mutex); kfree(led); } EXPORT_SYMBOL(avm_hui_remove_led); #if IS_ENABLED(CONFIG_AVM_FLASH_UPDATE) static bool hui_is_bare = false; /* * This is called, when the linux kernel is stopped and the flash_update * module assumed total control. * * Therefore we do not actually need to disable the timer. We just turn off all * possible leds. * * This is called from an atomic context. */ void led_event_disable_timer(void) { struct led *led; hui_is_bare = true; list_for_each_entry (led, &leds, list) { struct led_color color = { 0 }; if (!led->ops->raw_out) continue; led->ops->raw_out(color, led->ops_ctx); } } EXPORT_SYMBOL(led_event_disable_timer); int hui_bare_handle_event(enum _led_event event, int val) { struct led *led; if (!hui_is_bare) return 0; list_for_each_entry (led, &leds, list) { struct led_color color = { 0 }; if (!led->ops->raw_out) continue; if (!(led->options.flags & LED_FLAG_BARE_UPDATE)) continue; if (val) color = led_lookup_color(led, led_color_name_normal, true); led->ops->raw_out(color, led->ops_ctx); } return 1; } #endif static void proc_led_state(struct seq_file *seq, void *context) { struct led *led; mutex_lock(&hui_update_mutex); list_for_each_entry (led, &leds, list) { seq_printf(seq, "%s (%s): %d (%d, %d, #%02X%02X%02X) by %pf\n", led->name, led->type->name, led->current_code.type, led->current_code.params[0], led->current_code.params[1], led->current_code.color.c[0], led->current_code.color.c[1], led->current_code.color.c[2], led->current_code_source); } mutex_unlock(&hui_update_mutex); } int leds_init(void) { led_kset = kset_create_and_add("led", NULL, &hui_kobj->obj.kobj); if (!led_kset) return -ENOMEM; add_simple_proc_file("avm/hui/led_state", NULL, proc_led_state, NULL); return 0; } void leds_exit(void) { remove_simple_proc_file("avm/hui/led_state"); }