From f745a934058733522ee64e3f8e4e20cc633e7857 Mon Sep 17 00:00:00 2001 From: Wenping Zhang Date: Wed, 3 May 2017 17:21:03 +0800 Subject: [PATCH] leds: add multi-control for leds's trigger. userspace can control all leds by one ioctl through file node: /dev/led_multi_ctrl. Change-Id: I10ac19b86b46b3dc9a88809f1be5ebc95398212c Signed-off-by: Wenping Zhang --- drivers/leds/led-triggers.c | 20 + drivers/leds/leds-multi.h | 63 +++ drivers/leds/leds.h | 7 + drivers/leds/trigger/Kconfig | 6 + drivers/leds/trigger/Makefile | 1 + drivers/leds/trigger/ledtrig-multi-control.c | 526 +++++++++++++++++++ 6 files changed, 623 insertions(+) create mode 100644 drivers/leds/leds-multi.h create mode 100644 drivers/leds/trigger/ledtrig-multi-control.c diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index e8b1120f486d..a712ab14fd21 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -175,6 +175,26 @@ void led_trigger_set_default(struct led_classdev *led_cdev) } EXPORT_SYMBOL_GPL(led_trigger_set_default); +#ifdef CONFIG_LEDS_TRIGGER_MULTI_CTRL +void led_trigger_set_by_name(struct led_classdev *led_cdev, char *trig_name) +{ + struct led_trigger *trig; + + if (!trig_name) + return; + + down_read(&triggers_list_lock); + down_write(&led_cdev->trigger_lock); + list_for_each_entry(trig, &trigger_list, next_trig) { + if (!strcmp(trig_name, trig->name)) + led_trigger_set(led_cdev, trig); + } + up_write(&led_cdev->trigger_lock); + up_read(&triggers_list_lock); +} +EXPORT_SYMBOL_GPL(led_trigger_set_by_name); +#endif + void led_trigger_rename_static(const char *name, struct led_trigger *trig) { /* new name must be on a temporary string to prevent races */ diff --git a/drivers/leds/leds-multi.h b/drivers/leds/leds-multi.h new file mode 100644 index 000000000000..f4f1b71aaf89 --- /dev/null +++ b/drivers/leds/leds-multi.h @@ -0,0 +1,63 @@ +/* + * LED MULTI-CONTROL + * + * Copyright 2017 Allen Zhang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#ifndef __LEDS_MULTI_H_INCLUDED +#define __LEDS_MULTI_H_INCLUDED + +enum { + TRIG_NONE = 0, + TRIG_DEF_ON, + TRIG_TIMER, + TRIG_ONESHOT, + TRIG_MAX, +}; + +struct led_ctrl_data { + u32 trigger; + /* the delay time(ms) of triggering a trigger */ + u32 delayed_trigger_ms; + u32 brightness; + u32 delay_on; + u32 delay_off; +} __packed; + +struct led_ctrl_scroll_data { + u64 init_bitmap; + /* the shift bits on every scrolling time*/ + u32 shifts; + u32 shift_delay_ms; +} __packed; + +struct led_ctrl_breath_data { + u64 background_bitmap; + u64 breath_bitmap; + u32 change_delay_ms; + u32 breath_steps; +} __packed; + +#define MAX_LEDS_NUMBER 64 + +#define LEDS_MULTI_CTRL_IOCTL_MAGIC 'z' + +#define LEDS_MULTI_CTRL_IOCTL_MULTI_SET \ + _IOW(LEDS_MULTI_CTRL_IOCTL_MAGIC, 0x01, struct led_ctrl_data*) +#define LEDS_MULTI_CTRL_IOCTL_GET_LED_NUMBER \ + _IOR(LEDS_MULTI_CTRL_IOCTL_MAGIC, 0x02, int) +#define LEDS_MULTI_CTRL_IOCTL_MULTI_SET_SCROLL \ + _IOW(LEDS_MULTI_CTRL_IOCTL_MAGIC, 0x03, struct led_ctrl_scroll_data*) +#define LEDS_MULTI_CTRL_IOCTL_MULTI_SET_BREATH \ + _IOW(LEDS_MULTI_CTRL_IOCTL_MAGIC, 0x04, struct led_ctrl_breath_data*) + +int led_multi_control_register(struct led_classdev *led_cdev); +int led_multi_control_unregister(struct led_classdev *led_cdev); +int led_multi_control_init(struct device *dev); +int led_multi_control_exit(struct device *dev); + +#endif /* __LEDS_MULTI_H_INCLUDED */ diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h index 4238fbc31d35..c51bf835b1ac 100644 --- a/drivers/leds/leds.h +++ b/drivers/leds/leds.h @@ -50,4 +50,11 @@ void led_stop_software_blink(struct led_classdev *led_cdev); extern struct rw_semaphore leds_list_lock; extern struct list_head leds_list; +#ifdef CONFIG_LEDS_TRIGGER_MULTI_CTRL +void led_trigger_set_by_name(struct led_classdev *led_cdev, char *trig_name); +#else +static inline void led_trigger_set_by_name(struct led_classdev *led_cdev, + char *trig_name) {} +#endif + #endif /* __LEDS_H_INCLUDED */ diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index 5bda6a9b56bb..ec49afee7c68 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -108,4 +108,10 @@ config LEDS_TRIGGER_CAMERA This enables direct flash/torch on/off by the driver, kernel space. If unsure, say Y. +config LEDS_TRIGGER_MULTI_CTRL + tristate "LED Multi-Control Trigger" + depends on LEDS_TRIGGERS && LEDS_TRIGGER_DEFAULT_ON && LEDS_TRIGGER_TIMER && LEDS_TRIGGER_ONESHOT + help + This allows userspace control all the leds at one time by ioctl operation. + endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index 1abf48dacf7e..49e4e6e8271a 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_LEDS_TRIGGER_CPU) += ledtrig-cpu.o obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o +obj-$(CONFIG_LEDS_TRIGGER_MULTI_CTRL) += ledtrig-multi-control.o diff --git a/drivers/leds/trigger/ledtrig-multi-control.c b/drivers/leds/trigger/ledtrig-multi-control.c new file mode 100644 index 000000000000..95283f152071 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-multi-control.c @@ -0,0 +1,526 @@ +/* + * LED Kernel Multi-Control Trigger + * + * Control multi leds at one time using ioctl from userspace. + * + * Copyright 2017 Allen Zhang + * + * Based on Richard Purdie's ledtrig-timer.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" +#include "../leds-multi.h" + +struct multi_ctrl_data { + struct led_ctrl_data *data; + struct led_classdev *led_cdev; + struct delayed_work delay_trig_work; + struct list_head node; + struct led_ctrl_data old_data; +}; + +struct multi_ctrl_scroll_data { + struct led_ctrl_scroll_data *data; + volatile bool data_valid; + struct delayed_work scroll_work; +}; + +struct multi_ctrl_breath_data { + struct led_ctrl_breath_data *data; + volatile bool data_valid; + struct delayed_work breath_work; +}; + +enum leds_mode { + LEDS_MODE_MULTI_SET = 0, + LEDS_MODE_MULTI_SCROLL, + LEDS_MODE_MULTI_BREATH, + LEDS_MODE_MULTI_INVALID = 0xff, +}; + +#define bits(nr) ((u64)1 << (nr)) + +static DECLARE_RWSEM(multi_leds_list_lock); +static LIST_HEAD(multi_leds_list); + +static int led_num; +static struct miscdevice multi_ctrl_miscdev; +static struct multi_ctrl_scroll_data *multi_ctrl_scroll_info; +static struct multi_ctrl_breath_data *multi_ctrl_breath_info; +static char *mult_ctrl_trigger[TRIG_MAX] = { + "none", + "default-on", + "timer", + "oneshot", +}; + +static struct led_ctrl_data leds_data[MAX_LEDS_NUMBER]; +static struct led_ctrl_scroll_data leds_scroll_data; +static struct led_ctrl_breath_data leds_breath_data; +static enum leds_mode leds_pre_mode = LEDS_MODE_MULTI_INVALID; + +static u64 multi_ctrl_calc_next_scroll_bitmap(u64 cur_bitmap) +{ + u64 bitmap = cur_bitmap << leds_scroll_data.shifts; + u64 ret_bitmap = bitmap & (bits(led_num) - 1); + + if (bitmap > (bits(led_num) - 1)) + ret_bitmap |= bitmap >> led_num; + else + ret_bitmap = bitmap; + + return ret_bitmap; +} + +static void multi_ctrl_scroll_work_fn(struct work_struct *ws) +{ + struct multi_ctrl_data *ctrl_data; + int bit = 0; + static u64 update_bits = ~0; + + down_read(&multi_leds_list_lock); + if (!multi_ctrl_scroll_info->data_valid) { + update_bits = bits(led_num) - 1; + multi_ctrl_scroll_info->data_valid = true; + pr_info("%s, new scroll work is queued\n", __func__); + } + if (unlikely(update_bits > (bits(led_num) - 1))) { + pr_warn("%s,update_bits is exceed the max led numbers!\n", + __func__); + update_bits = bits(led_num) - 1; + } + + if (leds_pre_mode != LEDS_MODE_MULTI_SCROLL) { + update_bits = bits(led_num) - 1; + leds_pre_mode = LEDS_MODE_MULTI_SCROLL; + } + + list_for_each_entry(ctrl_data, &multi_leds_list, node) { + struct led_classdev *led_cdev = ctrl_data->led_cdev; + + if (bit >= led_num) { + dev_err(led_cdev->dev, "exceed the max number of muti_leds_list\n"); + break; + } + + cancel_delayed_work_sync(&ctrl_data->delay_trig_work); + + /* only change the led status when bits updated */ + if (update_bits & bits(bit)) { + if (leds_scroll_data.init_bitmap & bits(bit)) { + led_trigger_set_by_name(led_cdev, + mult_ctrl_trigger[TRIG_DEF_ON]); + } else { + led_trigger_remove(led_cdev); + } + } + + bit++; + } + update_bits = leds_scroll_data.init_bitmap; + leds_scroll_data.init_bitmap = multi_ctrl_calc_next_scroll_bitmap(update_bits); + update_bits ^= leds_scroll_data.init_bitmap; + up_read(&multi_leds_list_lock); + + schedule_delayed_work(&multi_ctrl_scroll_info->scroll_work, + msecs_to_jiffies(leds_scroll_data.shift_delay_ms)); +} + +static void multi_ctrl_breath_work_fn(struct work_struct *ws) +{ + struct multi_ctrl_data *ctrl_data; + int bit = 0; + u32 bri_every_step; + static u32 pre_brightness = LED_HALF; + static u64 pre_bg_bitmap; + static u64 pre_br_bitmap; + static u64 pre_black_bitmap; + + down_read(&multi_leds_list_lock); + if (!multi_ctrl_breath_info->data_valid) { + pre_bg_bitmap = 0; + pre_br_bitmap = 0; + pre_black_bitmap = 0; + multi_ctrl_breath_info->data_valid = true; + pr_info("%s, new breath work is queued\n", __func__); + } + + bri_every_step = LED_FULL / leds_breath_data.breath_steps; + list_for_each_entry(ctrl_data, &multi_leds_list, node) { + struct led_classdev *led_cdev = ctrl_data->led_cdev; + + if (bit >= led_num) { + dev_err(led_cdev->dev, "exceed the max number of muti_leds_list\n"); + break; + } + + cancel_delayed_work_sync(&ctrl_data->delay_trig_work); + + if (pre_bg_bitmap == 0) { + if (leds_breath_data.background_bitmap & bits(bit)) { + led_trigger_set_by_name(led_cdev, + mult_ctrl_trigger[TRIG_DEF_ON]); + } + } + + if (leds_breath_data.breath_bitmap & bits(bit)) { + /* only update the leds indicated by bitmap*/ + led_cdev->brightness = + (pre_brightness + bri_every_step) % LED_FULL; + if (unlikely(pre_br_bitmap == 0)) + led_trigger_set_by_name(led_cdev, + mult_ctrl_trigger[TRIG_DEF_ON]); + else + led_set_brightness_async(led_cdev, + led_cdev->brightness); + } + + if (pre_black_bitmap == 0) { + if ((~(leds_breath_data.background_bitmap | + leds_breath_data.breath_bitmap)) & + bits(bit)) + led_trigger_remove(led_cdev); + } + bit++; + } + pre_brightness = (pre_brightness + bri_every_step) % LED_FULL; + pre_bg_bitmap = leds_breath_data.background_bitmap; + pre_br_bitmap = leds_breath_data.breath_bitmap; + pre_black_bitmap = ~(pre_bg_bitmap | pre_br_bitmap); + leds_pre_mode = LEDS_MODE_MULTI_BREATH; + up_read(&multi_leds_list_lock); + + schedule_delayed_work(&multi_ctrl_breath_info->breath_work, + msecs_to_jiffies(leds_breath_data.change_delay_ms)); +} + +static void multi_ctrl_delay_trig_func(struct work_struct *ws) +{ + struct multi_ctrl_data *ctrl_data = + container_of(ws, struct multi_ctrl_data, delay_trig_work.work); + struct led_classdev *led_cdev = ctrl_data->led_cdev; + struct led_ctrl_data *led_data = ctrl_data->data; + + /* set brightness*/ + if (led_data->brightness == LED_OFF) { + led_trigger_remove(led_cdev); + return; + } + /* set delay_on and delay_off */ + led_cdev->blink_delay_off = led_data->delay_off; + led_cdev->blink_delay_on = led_data->delay_on; + led_cdev->brightness = led_data->brightness; + + led_trigger_set_by_name(led_cdev, mult_ctrl_trigger[led_data->trigger]); + + if (led_data->trigger == TRIG_ONESHOT) + led_blink_set_oneshot(led_cdev, + &led_cdev->blink_delay_on, + &led_cdev->blink_delay_off, 0); +} + +static int multi_ctrl_set_led(struct multi_ctrl_data *ctrl_data) +{ + struct led_ctrl_data *led_data = ctrl_data->data; + struct led_classdev *led_cdev = ctrl_data->led_cdev; + + if (!led_data || led_data->trigger >= TRIG_MAX) + return -EINVAL; + + if (led_data->delayed_trigger_ms && + (led_data->trigger == TRIG_TIMER || + led_data->trigger == TRIG_ONESHOT)) { + schedule_delayed_work(&ctrl_data->delay_trig_work, + msecs_to_jiffies(led_data->delayed_trigger_ms)); + } else { + /* set brightness*/ + if (led_data->brightness == LED_OFF || + led_data->trigger == TRIG_NONE) { + led_trigger_remove(led_cdev); + return 0; + } + /* set delay_on and delay_off */ + led_cdev->blink_delay_off = led_data->delay_off; + led_cdev->blink_delay_on = led_data->delay_on; + led_cdev->brightness = led_data->brightness; + + led_trigger_set_by_name(led_cdev, + mult_ctrl_trigger[led_data->trigger]); + + if (led_data->trigger == TRIG_ONESHOT) + led_blink_set_oneshot(led_cdev, + &led_cdev->blink_delay_on, + &led_cdev->blink_delay_off, 0); + } + + return 0; +} + +static void cancel_all_work_sync(void) +{ + multi_ctrl_scroll_info->data_valid = false; + multi_ctrl_breath_info->data_valid = false; + cancel_delayed_work_sync(&multi_ctrl_scroll_info->scroll_work); + cancel_delayed_work_sync(&multi_ctrl_breath_info->breath_work); +} + +static int multi_ctrl_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int multi_ctrl_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static long multi_ctrl_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int i = 0; + int ret = 0; + + cancel_all_work_sync(); + + switch (cmd) { + case LEDS_MULTI_CTRL_IOCTL_MULTI_SET: + { + struct led_ctrl_data *argp = (struct led_ctrl_data *)arg; + struct multi_ctrl_data *ctrl_data; + bool mode_changed = false; + + down_read(&multi_leds_list_lock); + + if (leds_pre_mode != LEDS_MODE_MULTI_SET) { + leds_pre_mode = LEDS_MODE_MULTI_SET; + mode_changed = true; + } + + if (copy_from_user(leds_data, argp, + sizeof(struct led_ctrl_data) * led_num)) { + pr_err("%s, copy from user failed\n", __func__); + up_read(&multi_leds_list_lock); + ret = -EFAULT; + break; + } + list_for_each_entry(ctrl_data, &multi_leds_list, node) { + struct led_classdev *led_cdev = ctrl_data->led_cdev; + + cancel_delayed_work_sync(&ctrl_data->delay_trig_work); + + if (i >= led_num) { + dev_err(led_cdev->dev, "exceed the max number of muti_leds_list\n"); + break; + } + ctrl_data->data = &leds_data[i++]; + if (!memcmp(&ctrl_data->old_data, ctrl_data->data, + sizeof(struct led_ctrl_data)) && + !mode_changed) { + continue; + } + multi_ctrl_set_led(ctrl_data); + memcpy(&ctrl_data->old_data, ctrl_data->data, + sizeof(struct led_ctrl_data)); + } + up_read(&multi_leds_list_lock); + break; + } + case LEDS_MULTI_CTRL_IOCTL_GET_LED_NUMBER: + { + int __user *p = (int __user *)arg; + + ret = put_user(led_num, p); + break; + } + case LEDS_MULTI_CTRL_IOCTL_MULTI_SET_SCROLL: + { + if (led_num > sizeof(leds_scroll_data.init_bitmap) * 8) { + pr_err("registered leds is exeeded the size of scroll bitmap!\n"); + ret = -EINVAL; + break; + } + + if (copy_from_user(&leds_scroll_data, + (struct led_ctrl_scroll_data *)arg, + sizeof(struct led_ctrl_scroll_data))) { + pr_err("%s, set scroll mode, copy from user failed\n", + __func__); + ret = -EFAULT; + break; + } + schedule_delayed_work(&multi_ctrl_scroll_info->scroll_work, 0); + break; + } + case LEDS_MULTI_CTRL_IOCTL_MULTI_SET_BREATH: + { + if (led_num > sizeof(leds_breath_data.breath_bitmap) * 8) { + pr_err("registered leds is exeeded the size of breath bitmap!\n"); + ret = -EINVAL; + break; + } + + /* + * background color bitmap will be set to default-on mode, + * so we can't set a bit in background bits if it's one of + * bit in breath color bitmap. + */ + leds_breath_data.background_bitmap &= + ~leds_breath_data.breath_bitmap; + if (copy_from_user(&leds_breath_data, + (struct led_ctrl_breath_data *)arg, + sizeof(leds_breath_data))) { + pr_err("%s, set breath mode, copy from user failed\n", + __func__); + ret = -EFAULT; + break; + } + schedule_delayed_work(&multi_ctrl_breath_info->breath_work, 0); + break; + } + default: + break; + } + + return ret; +} + +static const struct file_operations multi_ctrl_ops = { + .owner = THIS_MODULE, + .open = multi_ctrl_open, + .release = multi_ctrl_release, + .unlocked_ioctl = multi_ctrl_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = multi_ctrl_ioctl, +#endif +}; + +int led_multi_control_register(struct led_classdev *led_cdev) +{ + struct multi_ctrl_data *data; + + if (led_num++ >= MAX_LEDS_NUMBER) + return -EINVAL; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(led_cdev->dev, "malloc multi_ctrl_data failed\n"); + return -ENOMEM; + } + + data->led_cdev = led_cdev; + data->old_data.brightness = led_cdev->brightness; + data->old_data.delay_off = led_cdev->blink_delay_off; + data->old_data.delay_on = led_cdev->blink_delay_on; + + down_write(&multi_leds_list_lock); + list_add_tail(&data->node, &multi_leds_list); + up_write(&multi_leds_list_lock); + + INIT_DELAYED_WORK(&data->delay_trig_work, + multi_ctrl_delay_trig_func); + + return 0; +} + +int led_multi_control_unregister(struct led_classdev *cdev) +{ + struct multi_ctrl_data *ctrl_data; + + if (led_num-- < 0) + return -EINVAL; + + down_write(&multi_leds_list_lock); + list_for_each_entry(ctrl_data, &multi_leds_list, node) { + if (ctrl_data->led_cdev == cdev) { + cancel_delayed_work_sync(&ctrl_data->delay_trig_work); + list_del(&ctrl_data->node); + break; + } + } + up_write(&multi_leds_list_lock); + kfree(ctrl_data); + + return 0; +} + +int led_multi_control_init(struct device *dev) +{ + int ret; + + multi_ctrl_scroll_info = + kzalloc(sizeof(*multi_ctrl_scroll_info), GFP_KERNEL); + if (!multi_ctrl_scroll_info) { + pr_err("malloc multi_ctrl_scroll_info failed\n"); + return -ENOMEM; + } + multi_ctrl_breath_info = + kzalloc(sizeof(*multi_ctrl_breath_info), GFP_KERNEL); + if (!multi_ctrl_breath_info) { + pr_err("malloc leds_breath_info failed\n"); + ret = -ENOMEM; + goto breath_info_malloc_err; + } + + multi_ctrl_miscdev.fops = &multi_ctrl_ops; + multi_ctrl_miscdev.parent = dev; + multi_ctrl_miscdev.name = "led_multi_ctrl"; + ret = misc_register(&multi_ctrl_miscdev); + if (ret < 0) { + pr_err("Can't register misc dev for led multi-control.\n"); + goto miscdev_register_err; + } + led_num = 0; + + multi_ctrl_scroll_info->data = &leds_scroll_data; + INIT_DELAYED_WORK(&multi_ctrl_scroll_info->scroll_work, + multi_ctrl_scroll_work_fn); + multi_ctrl_breath_info->data = &leds_breath_data; + INIT_DELAYED_WORK(&multi_ctrl_breath_info->breath_work, + multi_ctrl_breath_work_fn); + + return 0; + +miscdev_register_err: + kfree(multi_ctrl_breath_info); + multi_ctrl_breath_info = NULL; + +breath_info_malloc_err: + kfree(multi_ctrl_scroll_info); + multi_ctrl_scroll_info = NULL; + + return ret; +} + +int led_multi_control_exit(struct device *dev) +{ + misc_deregister(&multi_ctrl_miscdev); + cancel_delayed_work_sync(&multi_ctrl_scroll_info->scroll_work); + cancel_delayed_work_sync(&multi_ctrl_breath_info->breath_work); + if (multi_ctrl_scroll_info) { + kfree(multi_ctrl_scroll_info); + multi_ctrl_scroll_info = NULL; + } + if (multi_ctrl_breath_info) { + kfree(multi_ctrl_breath_info); + multi_ctrl_breath_info = NULL; + } + + return 0; +} + +MODULE_AUTHOR("Allen.zhang "); +MODULE_DESCRIPTION("Multi-Contorl LED trigger"); +MODULE_LICENSE("GPL");