From 4d47bc80314f4997d377eaf921d9ea2510a1b696 Mon Sep 17 00:00:00 2001 From: Phil Elwell Date: Mon, 13 Jul 2020 10:33:19 +0100 Subject: [PATCH 0235/1085] leds: Add the actpwr trigger The actpwr trigger is a meta trigger that cycles between an inverted mmc0 and default-on. It is written in a way that could fairly easily be generalised to support alternative sets of source triggers. Signed-off-by: Phil Elwell --- drivers/leds/trigger/Kconfig | 11 ++ drivers/leds/trigger/Makefile | 1 + drivers/leds/trigger/ledtrig-actpwr.c | 190 ++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 drivers/leds/trigger/ledtrig-actpwr.c --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -162,4 +162,15 @@ config LEDS_TRIGGER_TTY When build as a module this driver will be called ledtrig-tty. +config LEDS_TRIGGER_ACTPWR + tristate "ACT/PWR Input Trigger" + depends on LEDS_TRIGGERS + help + This trigger is intended for platforms that have one software- + controllable LED and no dedicated activity or power LEDs, hence the + need to make the one LED perform both functions. It cycles between + default-on and an inverted mmc0 every 500ms, guaranteeing that it is + on for at least half of the time. + If unsure, say N. + endif # LEDS_TRIGGERS --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += led obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o obj-$(CONFIG_LEDS_TRIGGER_AUDIO) += ledtrig-audio.o obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o +obj-$(CONFIG_LEDS_TRIGGER_ACTPWR) += ledtrig-actpwr.o --- /dev/null +++ b/drivers/leds/trigger/ledtrig-actpwr.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Activity/power trigger + * + * Copyright (C) 2020 Raspberry Pi (Trading) Ltd. + * + * Based on Atsushi Nemoto's ledtrig-heartbeat.c, although there may be + * nothing left of the original now. + */ + +#include +#include +#include +#include +#include +#include "../leds.h" + +enum { + TRIG_ACT, + TRIG_PWR, + + TRIG_COUNT +}; + +struct actpwr_trig_src { + const char *name; + int interval; + bool invert; +}; + +struct actpwr_vled { + struct led_classdev cdev; + struct actpwr_trig_data *parent; + enum led_brightness value; + unsigned int interval; + bool invert; +}; + +struct actpwr_trig_data { + struct led_trigger trig; + struct actpwr_vled virt_leds[TRIG_COUNT]; + struct actpwr_vled *active; + struct timer_list timer; + int next_active; +}; + +static int actpwr_trig_activate(struct led_classdev *led_cdev); +static void actpwr_trig_deactivate(struct led_classdev *led_cdev); + +static const struct actpwr_trig_src actpwr_trig_sources[TRIG_COUNT] = { + [TRIG_ACT] = { "mmc0", 500, true }, + [TRIG_PWR] = { "default-on", 500, false }, +}; + +static struct actpwr_trig_data actpwr_data = { + { + .name = "actpwr", + .activate = actpwr_trig_activate, + .deactivate = actpwr_trig_deactivate, + } +}; + +static void actpwr_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct actpwr_vled *vled = container_of(led_cdev, struct actpwr_vled, + cdev); + struct actpwr_trig_data *trig = vled->parent; + + if (vled->invert) + value = !value; + vled->value = value; + + if (vled == trig->active) + led_trigger_event(&trig->trig, value); +} + +static int actpwr_brightness_set_blocking(struct led_classdev *led_cdev, + enum led_brightness value) +{ + actpwr_brightness_set(led_cdev, value); + return 0; +} + +static enum led_brightness actpwr_brightness_get(struct led_classdev *led_cdev) +{ + struct actpwr_vled *vled = container_of(led_cdev, struct actpwr_vled, + cdev); + + return vled->value; +} + +static void actpwr_trig_cycle(struct timer_list *t) +{ + struct actpwr_trig_data *trig = &actpwr_data; + struct actpwr_vled *active; + + active = &trig->virt_leds[trig->next_active]; + trig->active = active; + trig->next_active = (trig->next_active + 1) % TRIG_COUNT; + + led_trigger_event(&trig->trig, active->value); + + mod_timer(&trig->timer, jiffies + msecs_to_jiffies(active->interval)); +} + +static int actpwr_trig_activate(struct led_classdev *led_cdev) +{ + struct actpwr_trig_data *trig = &actpwr_data; + + /* Start the timer if this is the first LED */ + if (!trig->active) + actpwr_trig_cycle(&trig->timer); + else + led_set_brightness_nosleep(led_cdev, trig->active->value); + + return 0; +} + +static void actpwr_trig_deactivate(struct led_classdev *led_cdev) +{ + struct actpwr_trig_data *trig = &actpwr_data; + + if (list_empty(&trig->trig.led_cdevs)) { + del_timer_sync(&trig->timer); + trig->active = NULL; + } +} + +static int __init actpwr_trig_init(void) +{ + struct actpwr_trig_data *trig = &actpwr_data; + int ret = 0; + int i; + + timer_setup(&trig->timer, actpwr_trig_cycle, 0); + + /* Register one "LED" for each source trigger */ + for (i = 0; i < TRIG_COUNT; i++) + { + struct actpwr_vled *vled = &trig->virt_leds[i]; + struct led_classdev *cdev = &vled->cdev; + const struct actpwr_trig_src *src = &actpwr_trig_sources[i]; + + vled->parent = trig; + vled->interval = src->interval; + vled->invert = src->invert; + cdev->name = src->name; + cdev->brightness_set = actpwr_brightness_set; + cdev->brightness_set_blocking = actpwr_brightness_set_blocking; + cdev->brightness_get = actpwr_brightness_get; + cdev->default_trigger = src->name; + ret = led_classdev_register(NULL, cdev); + if (ret) + goto error_classdev; + } + + ret = led_trigger_register(&trig->trig); + if (ret) + goto error_classdev; + + return 0; + +error_classdev: + while (i > 0) + { + i--; + led_classdev_unregister(&trig->virt_leds[i].cdev); + } + + return ret; +} + +static void __exit actpwr_trig_exit(void) +{ + int i; + + led_trigger_unregister(&actpwr_data.trig); + for (i = 0; i < TRIG_COUNT; i++) + { + led_classdev_unregister(&actpwr_data.virt_leds[i].cdev); + } +} + +module_init(actpwr_trig_init); +module_exit(actpwr_trig_exit); + +MODULE_AUTHOR("Phil Elwell "); +MODULE_DESCRIPTION("ACT/PWR LED trigger"); +MODULE_LICENSE("GPL v2");