avs_mtk_voice/src/kernel/linux/v4.4/drivers/leds/led-class-avsux.c

463 lines
12 KiB
C
Executable File

/*
* Amazon alexa voice service LED UX class interface
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/firmware.h>
#include <linux/led-class-avsux.h>
#include "leds.h"
int avsux_timer_flag = 0;
static void avsux_parse_rgb(const u32 rgb, struct led_rgb_colors *colors)
{
colors->red = rgb & 0xff; /*exchange r&b data of the patten,by eric 20220623 */
colors->green = (rgb >> 8) & 0xff;
colors->blue = (rgb >> 16) & 0xff;/*exchange r&b data of the patten,by eric 20220623 */
}
static u32 avsux_dump_rgb(struct led_rgb_colors *colors)
{
return (colors->red << 16) | (colors->green << 8) | colors->blue;
}
static int avsux_parse_pattern(struct led_avsux_animation *animation, char *buf, size_t len)
{
struct led_avsux_pattern *pattern;
char *line, *duration, *hex;
u32 rgb;
int idx;
int ret;
char *next_line = buf;
if (!buf)
return -1;
buf[len-1] = '\0';
while ((line = strsep(&next_line, "\r\n")) != NULL) {
if ((line - buf) >= len)
break;
if (*line == '\0' || *line == '#')
continue;
duration = strsep(&line, ":");
if (line) {
pattern = kzalloc(sizeof(struct led_avsux_pattern), GFP_KERNEL);
if (pattern) {
ret = kstrtou32(duration, 10, &pattern->duration);
if(ret < 0) return ret; // add by kk
idx = 0;
while ((hex = strsep(&line, ",")) != NULL) {
ret = kstrtou32(hex, 16, &rgb);
if(ret < 0) return ret; // add by kk
avsux_parse_rgb(rgb, &pattern->colors[idx++]);
if (idx >= MAX_NUM_LEDS)
break;
}
list_add_tail(&pattern->list, &animation->patterns);
}
}
}
return 0;
}
static void avsux_animation_free(struct led_avsux_animation *animation)
{
struct list_head *pos, *p;
struct led_avsux_pattern *pattern;
if (!animation)
return;
list_for_each_safe(pos, p, &animation->patterns) {
pattern = list_entry(pos, struct led_avsux_pattern, list);
list_del(pos);
kfree(pattern);
}
kfree(animation);
}
static int avsux_animation_reset(struct led_classdev_avsux *auled_cdev)
{
struct list_head *pos, *p;
struct led_avsux_animation *animation;
auled_cdev->cur_anime = NULL;
list_for_each_safe(pos, p, &auled_cdev->animations) {
animation = list_entry(pos, struct led_avsux_animation, list);
list_del(pos);
avsux_animation_free(animation);
}
return 0;
}
struct led_avsux_animation *led_avsux_create_animation(char *name, int priority,
u32 loop, u32 pause, struct led_avsux_pattern *patterns,
int num_pattern)
{
struct led_avsux_animation *animation;
int i;
animation = kzalloc(sizeof(struct led_avsux_animation), GFP_KERNEL);
if (!animation)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&animation->patterns);
strncpy(animation->name, name, 32);
animation->priority = priority;
animation->loop = loop;
animation->pause = pause;
for (i = 0; i < num_pattern; i++)
list_add_tail(&patterns[i].list, &animation->patterns);
return animation;
}
EXPORT_SYMBOL_GPL(led_avsux_create_animation);
void led_avsux_start_animation(struct led_classdev_avsux *auled_cdev,
struct led_avsux_animation *animation)
{
if (!auled_cdev || !animation)
return;
auled_cdev->cur_anime = animation;
animation->cur_pattern = list_first_entry(&animation->patterns,
struct led_avsux_pattern, list);
animation->count = animation->loop;
hrtimer_start(&auled_cdev->anime_timer, ktime_set(0, 0), HRTIMER_MODE_REL);
}
EXPORT_SYMBOL_GPL(led_avsux_start_animation);
static void avsux_animation_start(struct led_classdev_avsux *auled_cdev, const char *name)
{
struct led_avsux_animation *animation;
if (hrtimer_active(&auled_cdev->anime_timer))
hrtimer_cancel(&auled_cdev->anime_timer);
list_for_each_entry(animation, &auled_cdev->animations, list) {
if (!strcmp(animation->name, name)) {
avsux_timer_flag = 1;
led_avsux_start_animation(auled_cdev, animation);
}
}
}
static ssize_t avsux_animation_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_avsux *auled_cdev = lcdev_to_aucdev(led_cdev);
const struct firmware *fw;
ssize_t ret = -EINVAL;
struct led_avsux_animation *animation;
mutex_lock(&led_cdev->led_access);
if (led_sysfs_is_disabled(led_cdev)) {
ret = -EBUSY;
goto unlock;
}
if (!strncmp(buf, "reset", 5)) {
if (hrtimer_active(&auled_cdev->anime_timer))
hrtimer_cancel(&auled_cdev->anime_timer);
avsux_animation_reset(auled_cdev);
ret = size;
goto unlock;
}
animation = kzalloc(sizeof(struct led_avsux_animation), GFP_KERNEL);
if (!animation) {
ret = -ENOMEM;
goto unlock;
}
INIT_LIST_HEAD(&animation->patterns);
ret = sscanf(buf, "%s %s %d %d", animation->name, animation->anime_path,
&animation->loop, &animation->pause);
if (ret < 2) {
kfree(animation);
ret = -EINVAL;
goto unlock;
}
ret = request_firmware(&fw, animation->anime_path, dev);
if (ret) {
kfree(animation);
goto unlock;
}
ret = avsux_parse_pattern(animation, (char *)fw->data, fw->size);
if (ret == 0)
list_add_tail(&animation->list, &auled_cdev->animations);
release_firmware(fw);
ret = size;
unlock:
mutex_unlock(&led_cdev->led_access);
return ret;
}
static ssize_t avsux_animation_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_avsux *auled_cdev = lcdev_to_aucdev(led_cdev);
struct led_avsux_animation *animation;
ssize_t len = 0;
if (list_empty(&auled_cdev->animations))
return 0;
len += snprintf(buf + len, PAGE_SIZE - len, "Available animations:\n");
list_for_each_entry(animation, &auled_cdev->animations, list) {
len += snprintf(buf + len, PAGE_SIZE - len, "\t[%s]:[%s][%d][%d]\n",
animation->name, animation->anime_path,
animation->loop, animation->pause);
}
return len;
}
static DEVICE_ATTR_RW(avsux_animation);
static ssize_t avsux_select_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_avsux *auled_cdev = lcdev_to_aucdev(led_cdev);
ssize_t ret = -EINVAL;
char name[32];
mutex_lock(&led_cdev->led_access);
if (led_sysfs_is_disabled(led_cdev)) {
ret = -EBUSY;
goto unlock;
}
ret = sscanf(buf, "%s", name);
if (ret < 1) {
ret = -EINVAL;
goto unlock;
}
avsux_animation_start(auled_cdev, name);
ret = size;
unlock:
mutex_unlock(&led_cdev->led_access);
return ret;
}
static ssize_t avsux_select_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_avsux *auled_cdev = lcdev_to_aucdev(led_cdev);
struct led_avsux_animation *animation = auled_cdev->cur_anime;
struct led_avsux_pattern *pattern;
ssize_t len = 0;
if (!animation)
return 0;
len += snprintf(buf + len, PAGE_SIZE - len, "%s[%s][%d][%d]:\n",
animation->name, animation->anime_path,
animation->loop, animation->pause);
list_for_each_entry(pattern, &animation->patterns, list) {
len += snprintf(buf + len, PAGE_SIZE - len,
"\t%8d:%06X,%06X,%06X,%06X,%06X,%06X,%06X,%06X,%06X,%06X,%06X,%06X\n", pattern->duration,
avsux_dump_rgb(&pattern->colors[0]), avsux_dump_rgb(&pattern->colors[1]),
avsux_dump_rgb(&pattern->colors[2]), avsux_dump_rgb(&pattern->colors[3]),
avsux_dump_rgb(&pattern->colors[4]), avsux_dump_rgb(&pattern->colors[5]),
avsux_dump_rgb(&pattern->colors[6]), avsux_dump_rgb(&pattern->colors[7]),
avsux_dump_rgb(&pattern->colors[8]), avsux_dump_rgb(&pattern->colors[9]),
avsux_dump_rgb(&pattern->colors[10]), avsux_dump_rgb(&pattern->colors[11]));
}
return len;
}
static DEVICE_ATTR_RW(avsux_select);
static char *avsux_type_str(enum led_avsux_type type)
{
switch (type) {
case AVS_UX_TYPE_SINGLE:
return "single";
case AVS_UX_TYPE_RADIAL:
return "radial";
case AVS_UX_TYPE_LINEAR:
return "linear";
}
return "unknow";
}
static ssize_t avsux_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_avsux *auled_cdev = lcdev_to_aucdev(led_cdev);
ssize_t len = 0;
len += snprintf(buf + len, PAGE_SIZE - len, "INFO:\n");
len += snprintf(buf + len, PAGE_SIZE - len, "\tled name:[%s]\n",
led_cdev->name);
len += snprintf(buf + len, PAGE_SIZE - len, "\tled type:[%s]\n",
avsux_type_str(auled_cdev->led_type));
len += snprintf(buf + len, PAGE_SIZE - len, "\tled nums:[%d]\n",
auled_cdev->num_leds);
return len;
}
static DEVICE_ATTR_RO(avsux_info);
static struct attribute *led_avsux_select_attrs[] = {
&dev_attr_avsux_select.attr,
NULL,
};
static const struct attribute_group led_avsux_select_group = {
.attrs = led_avsux_select_attrs,
};
static struct attribute *led_avsux_animation_attrs[] = {
&dev_attr_avsux_animation.attr,
NULL,
};
static const struct attribute_group led_avsux_animation_group = {
.attrs = led_avsux_animation_attrs,
};
static struct attribute *led_avsux_info_attrs[] = {
&dev_attr_avsux_info.attr,
NULL,
};
static const struct attribute_group led_avsux_info_group = {
.attrs = led_avsux_info_attrs,
};
static void led_avsux_init_sysfs_groups(struct led_classdev_avsux *auled_cdev)
{
struct led_classdev *led_cdev = &auled_cdev->led_cdev;
const struct attribute_group **avsux_groups = auled_cdev->sysfs_groups;
int num_sysfs_groups = 0;
avsux_groups[num_sysfs_groups++] = &led_avsux_select_group;
avsux_groups[num_sysfs_groups++] = &led_avsux_animation_group;
avsux_groups[num_sysfs_groups++] = &led_avsux_info_group;
led_cdev->groups = avsux_groups;
}
static enum hrtimer_restart led_avsux_animation_timer(struct hrtimer *timer)
{
struct led_classdev_avsux *auled_cdev =
container_of(timer, struct led_classdev_avsux, anime_timer);
struct led_avsux_animation *animation = auled_cdev->cur_anime;
ktime_t interval = ms_to_ktime(animation->cur_pattern->duration);
auled_cdev->ops->pattern_set(auled_cdev, animation->cur_pattern);
if (list_is_last(&animation->cur_pattern->list, &animation->patterns)) {
animation->cur_pattern = list_first_entry(&animation->patterns, struct led_avsux_pattern, list);
if (animation->pause)
interval = ktime_add_ms(interval, animation->pause);
if (--animation->count == 0) {
avsux_timer_flag = 0;
printk("%s, HRTIMER_NORESTART\n", __func__);
return HRTIMER_NORESTART;
}
} else
animation->cur_pattern = list_next_entry(animation->cur_pattern, list);
hrtimer_forward_now(&auled_cdev->anime_timer, interval);
return HRTIMER_RESTART;
}
static int led_avsux_pattern_set_common(struct led_classdev_avsux *auled_cdev, struct led_avsux_pattern *pattern)
{
struct led_classdev *led_cdev = &auled_cdev->led_cdev;
//led_set_brightness(led_cdev, LED_FULL);
led_set_brightness(led_cdev, led_cdev->brightness);
return 0;
}
static struct led_avsux_ops led_avsux_common_ops = {
.pattern_set = led_avsux_pattern_set_common,
};
int led_classdev_avsux_register(struct device *parent,
struct led_classdev_avsux *auled_cdev)
{
struct led_classdev *led_cdev;
if (!auled_cdev)
return -EINVAL;
led_cdev = &auled_cdev->led_cdev;
if (led_cdev->flags & LED_DEV_CAP_AVS_UX) {
if (!auled_cdev->ops)
auled_cdev->ops = &led_avsux_common_ops;
INIT_LIST_HEAD(&auled_cdev->animations);
hrtimer_init(&auled_cdev->anime_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
auled_cdev->anime_timer.function = &led_avsux_animation_timer;
led_avsux_init_sysfs_groups(auled_cdev);
}
/* Register led class device */
return led_classdev_register(parent, led_cdev);
}
EXPORT_SYMBOL_GPL(led_classdev_avsux_register);
void led_classdev_avsux_unregister(struct led_classdev_avsux *auled_cdev)
{
if (!auled_cdev)
return;
led_classdev_unregister(&auled_cdev->led_cdev);
}
EXPORT_SYMBOL_GPL(led_classdev_avsux_unregister);
MODULE_AUTHOR("Jason <yzhuq@qq.com>");
MODULE_DESCRIPTION("Amazon alexa voice service LED UX class interface");
MODULE_LICENSE("GPL v2");