kk-dev #1
|
@ -424,18 +424,45 @@
|
|||
*/
|
||||
/*zhongjinrong added for led driver end >*/
|
||||
|
||||
aw20036_led@3a {
|
||||
aw20036_led: aw20036_led@3a {
|
||||
compatible = "awinic,aw20036_led";
|
||||
reg = <0x3a>;
|
||||
reset-gpio = <&pio 7 0>;
|
||||
irq-gpio = <&pio 6 0>;
|
||||
imax = <5>; /* 80mA */
|
||||
max-brightness = <255>;
|
||||
brightness = <80>;
|
||||
gamma-table = <
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2
|
||||
2 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5
|
||||
5 6 6 6 7 7 7 7 8 8 8 9 9 10 10 10
|
||||
11 11 11 12 12 13 13 13 14 14 15 15 16 16 17 17
|
||||
18 18 19 19 20 20 21 21 22 23 23 24 24 25 26 26
|
||||
27 28 28 29 30 30 31 32 32 33 34 35 35 36 37 38
|
||||
38 39 40 41 42 42 43 44 45 46 47 48 49 49 50 51
|
||||
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
||||
69 70 71 72 73 74 75 76 78 79 80 81 82 84 85 86
|
||||
87 89 90 91 92 94 95 96 98 99 100 102 103 104 106 107
|
||||
109 110 112 113 114 116 117 119 120 122 123 125 126 128 130 131
|
||||
133 134 136 138 139 141 143 144 146 148 149 151 153 154 156 158
|
||||
160 161 163 165 167 169 170 172 174 176 178 180 182 183 185 187
|
||||
189 191 193 195 197 199 201 203 205 207 209 211 213 215 218 220
|
||||
222 224 226 228 230 233 235 237 239 241 244 246 248 250 253 255
|
||||
>;
|
||||
brightness-levels = <
|
||||
0 40 42 44 46 48 50 52 54 56
|
||||
58 60 62 64 66 68 70 72 74 76
|
||||
78 80 82 84 86 88 90 92 94 98
|
||||
100 102 104 106 108 110 112 114 116 118
|
||||
120 122 124 126 128 130 132 134 136 138
|
||||
140 142 144 146 148 150 152 154 156 158
|
||||
160 162 164 166 168 170 172 174 176 178
|
||||
180 182 184 186 188 190 192 194 196 198
|
||||
200 202 204 206 208 210 212 214 216 218
|
||||
220 222 224 226 228 230 232 234 236 238
|
||||
255>;
|
||||
default-brightness-level = <100>;
|
||||
status = "okay";
|
||||
aw20036,led {
|
||||
aw20036,name = "aw20036_led";
|
||||
aw20036,imax = <1>;
|
||||
aw20036,brightness = <128>;
|
||||
aw20036,max_brightness = <255>;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ CONFIG_CGROUP_SCHED=y
|
|||
CONFIG_RT_GROUP_SCHED=y
|
||||
CONFIG_NAMESPACES=y
|
||||
#CONFIG_BLK_DEV_INITRD is not set
|
||||
CONFIG_LEDS_CLASS_AVS_UX=y
|
||||
# CONFIG_SYSCTL_SYSCALL is not set
|
||||
CONFIG_EMBEDDED=y
|
||||
# CONFIG_SLUB_DEBUG is not set
|
||||
|
|
|
@ -29,6 +29,17 @@ config LEDS_CLASS_FLASH
|
|||
for the flash related features of a LED device. It can be built
|
||||
as a module.
|
||||
|
||||
config LEDS_CLASS_AVS_UX
|
||||
tristate "Amazon Alexa Voice Service LED UX Class Support"
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
This option enables the amazon alexa voice service led sysfs class in
|
||||
/sys/class/leds.
|
||||
It wrapps LED Class and adds AVS ux LEDS specific sysfs attributes and
|
||||
kernel internal API to it. You'll need this to provide support for AVS
|
||||
releated features of a LED device. It can be built as a module.
|
||||
|
||||
|
||||
comment "LED drivers"
|
||||
|
||||
config LEDS_88PM860X
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
obj-$(CONFIG_NEW_LEDS) += led-core.o
|
||||
obj-$(CONFIG_LEDS_CLASS) += led-class.o
|
||||
obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
|
||||
obj-$(CONFIG_LEDS_CLASS_AVS_UX) += led-class-avsux.o leds-aw20036.o
|
||||
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
|
||||
|
||||
# LED Platform Drivers
|
||||
|
|
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
* 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");
|
|
@ -0,0 +1,207 @@
|
|||
#ifndef __AW20036_BOOTUP_H__
|
||||
#define __AW20036_BOOTUP_H__
|
||||
|
||||
struct led_avsux_pattern bootup_patterns[] = {
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0xFF, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0xEF, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0xFF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0xDF, 0x00, 0x00},
|
||||
{0xFF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0xEF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0xFF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0xCF, 0x00, 0x00},
|
||||
{0xEF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0xDF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0xEF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0xBF, 0x00, 0x00},
|
||||
{0xDF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0xCF, 0x00, 0x00},{0xFF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0xDF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0xAF, 0x00, 0x00},
|
||||
{0xCF, 0x00, 0x00},{0xFF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0xBF, 0x00, 0x00},{0xEF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0xCF, 0x00, 0x00},{0xFF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x9F, 0x00, 0x00},
|
||||
{0xBF, 0x00, 0x00},{0xEF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0xAF, 0x00, 0x00},{0xDF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0xBF, 0x00, 0x00},{0xEF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x8F, 0x00, 0x00},
|
||||
{0xAF, 0x00, 0x00},{0xDF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0x9F, 0x00, 0x00},{0xCF, 0x00, 0x00},{0xFF, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0xAF, 0x00, 0x00},{0xDF, 0x00, 0x00},{0x00, 0x00, 0x00},{0x7F, 0x00, 0x00},
|
||||
{0x9F, 0x00, 0x00},{0xCF, 0x00, 0x00},{0xFF, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0x8F, 0x00, 0x00},{0xBF, 0x00, 0x00},{0xEF, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x9F, 0x00, 0x00},{0xCF, 0x00, 0x00},{0xFF, 0x00, 0x00},{0x6F, 0x00, 0x00},
|
||||
{0x8F, 0x00, 0x00},{0xBF, 0x00, 0x00},{0xEF, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0x7F, 0x00, 0x00},{0xAF, 0x00, 0x00},{0xDF, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x8F, 0x00, 0x00},{0xBF, 0x00, 0x00},{0xEF, 0x00, 0x00},{0x5F, 0x00, 0x00},
|
||||
{0x7F, 0x00, 0x00},{0xAF, 0x00, 0x00},{0xDF, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0x6F, 0x00, 0x00},{0x9F, 0x00, 0x00},{0xCF, 0x00, 0x00},{0xFF, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x7F, 0x00, 0x00},{0xAF, 0x00, 0x00},{0xDF, 0x00, 0x00},{0x4F, 0x00, 0x00},
|
||||
{0x6F, 0x00, 0x00},{0x9F, 0x00, 0x00},{0xCF, 0x00, 0x00},{0xFF, 0x00, 0x00},
|
||||
{0x5F, 0x00, 0x00},{0x8F, 0x00, 0x00},{0xBF, 0x00, 0x00},{0xEF, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x6F, 0x00, 0x00},{0x9F, 0x00, 0x00},{0xCF, 0x00, 0x00},{0xFF, 0x00, 0x00},
|
||||
{0x5F, 0x00, 0x00},{0x8F, 0x00, 0x00},{0xBF, 0x00, 0x00},{0xEF, 0x00, 0x00},
|
||||
{0x4F, 0x00, 0x00},{0x7F, 0x00, 0x00},{0xAF, 0x00, 0x00},{0xEF, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x5F, 0x00, 0x00},{0x8F, 0x00, 0x00},{0xBF, 0x00, 0x00},{0xEF, 0x00, 0x00},
|
||||
{0x4F, 0x00, 0x00},{0x7F, 0x00, 0x00},{0xAF, 0x00, 0x00},{0xDF, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x6F, 0x00, 0x00},{0x9F, 0x00, 0x00},{0xCF, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x4F, 0x00, 0x00},{0x7F, 0x00, 0x00},{0xAF, 0x00, 0x00},{0xDF, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x6F, 0x00, 0x00},{0x9F, 0x00, 0x00},{0xCF, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x5F, 0x00, 0x00},{0x8F, 0x00, 0x00},{0xBF, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x6F, 0x00, 0x00},{0x9F, 0x00, 0x00},{0xCF, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x5F, 0x00, 0x00},{0x8F, 0x00, 0x00},{0xBF, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x4F, 0x00, 0x00},{0x7F, 0x00, 0x00},{0xAF, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x5F, 0x00, 0x00},{0x8F, 0x00, 0x00},{0xBF, 0x00, 0xBF},
|
||||
{0x00, 0x00, 0x00},{0x4F, 0x00, 0x00},{0x7F, 0x00, 0x00},{0xAF, 0x00, 0xAF},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x6F, 0x00, 0x00},{0x9F, 0x00, 0x9F}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x4F, 0x00, 0x00},{0x7F, 0x00, 0x00},{0xAF, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x6F, 0x00, 0x00},{0x9F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x5F, 0x00, 0x00},{0x8F, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x6F, 0x00, 0x00},{0x9F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x5F, 0x00, 0x00},{0x8F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x4F, 0x00, 0x00},{0x7F, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x5F, 0x00, 0x00},{0x8F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x4F, 0x00, 0x00},{0x7F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x6F, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x4F, 0x00, 0x00},{0x7F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x6F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x5F, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x6F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x5F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x4F, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x5F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x4F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x4F, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
{
|
||||
.duration = 50,
|
||||
.colors = {
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00},{0x00, 0x00, 0x00}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,436 @@
|
|||
/*
|
||||
* AW20036 LED UX driver for amazon alexa voice service
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/led-class-avsux.h>
|
||||
#include "leds-aw20036.h"
|
||||
#include "leds-aw20036-bootup.h"
|
||||
|
||||
#define AW20036_I2C_NAME "aw20036_led"
|
||||
#define AW20036_VERSION "v2.0.0"
|
||||
#define AW20036_RESET_GPIO_NAME "aw20036_reset_gpio"
|
||||
|
||||
struct led_avsux_animation *bootup_anime;
|
||||
extern int avsux_timer_flag;
|
||||
|
||||
static void aw20036_brightness_work(struct work_struct *work)
|
||||
{
|
||||
struct aw20036 *priv = container_of(work, struct aw20036, work);
|
||||
struct led_classdev_avsux *auled_cdev = &priv->aucdev;
|
||||
struct led_classdev *led_cdev = &auled_cdev->led_cdev;
|
||||
struct led_avsux_pattern *pattern = priv->cur_pattern;
|
||||
enum led_brightness value = led_cdev->brightness;
|
||||
int act_value = 0;
|
||||
int max = led_cdev->max_brightness;
|
||||
u8 red, green, blue;
|
||||
int i;
|
||||
#if 1
|
||||
if (value > 100)
|
||||
value = 100;
|
||||
#else
|
||||
if (value > max)
|
||||
value = max;
|
||||
#endif
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
|
||||
if (priv->levels) {
|
||||
act_value = priv->levels[value];
|
||||
}
|
||||
|
||||
/* Set fade current */
|
||||
regmap_write(priv->regmap, REG_PAGE, 0xC2);
|
||||
for (i = 0; i < 12; i++) {
|
||||
#if 1
|
||||
red = pattern->colors[i].red * act_value / max;
|
||||
green = pattern->colors[i].green * act_value / max;
|
||||
blue = pattern->colors[i].blue * act_value / max;
|
||||
#else
|
||||
red = pattern->colors[i].red * max / LED_FULL;
|
||||
green = pattern->colors[i].green * max / LED_FULL;
|
||||
blue = pattern->colors[i].blue * max / LED_FULL;
|
||||
#endif
|
||||
if (priv->gamma_table) {
|
||||
regmap_write(priv->regmap, 3*i, priv->gamma_table[red]);
|
||||
regmap_write(priv->regmap, 3*i + 1, priv->gamma_table[green]);
|
||||
regmap_write(priv->regmap, 3*i + 2, priv->gamma_table[blue]);
|
||||
} else {
|
||||
regmap_write(priv->regmap, 3*i, red);
|
||||
regmap_write(priv->regmap, 3*i + 1, green);
|
||||
regmap_write(priv->regmap, 3*i + 2, blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void aw20036_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct led_classdev_avsux *auled_cdev = lcdev_to_aucdev(led_cdev);
|
||||
struct led_avsux_animation *animation = auled_cdev->cur_anime;
|
||||
struct aw20036 *priv = container_of(auled_cdev, struct aw20036, aucdev);
|
||||
|
||||
/* Save current pattern */
|
||||
priv->cur_pattern = animation->cur_pattern;
|
||||
|
||||
schedule_work(&priv->work);
|
||||
}
|
||||
|
||||
static ssize_t aw20036_store_setting(struct class *class,
|
||||
struct class_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int value;
|
||||
struct aw20036 *priv =
|
||||
container_of(class, struct aw20036, cls);
|
||||
|
||||
if (kstrtoint(buf, 10, &value) || ((value != 0) && (value != 1)))
|
||||
return -EINVAL;
|
||||
|
||||
if (!strcmp(attr->attr.name, "reset-gpio")) {
|
||||
gpio_set_value_cansleep(priv->reset_gpio, value);
|
||||
} else {
|
||||
pr_err("Invalid write attr.\n");
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void aw20036_debug_class_release(struct class *cls)
|
||||
{
|
||||
pr_info("aw20036_debug_class_release...\n");
|
||||
}
|
||||
|
||||
static struct class_attribute aw20036_debug_class_attrs[] = {
|
||||
__ATTR(reset-gpio, 0644, NULL, aw20036_store_setting),
|
||||
__ATTR_NULL
|
||||
};
|
||||
|
||||
static int aw20036_debug_reset_gpio(struct device *dev, struct aw20036 *priv)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
priv->cls.name = devm_kzalloc(dev, 20, GFP_KERNEL);
|
||||
sprintf((char *)priv->cls.name, AW20036_RESET_GPIO_NAME);
|
||||
priv->cls.class_attrs = aw20036_debug_class_attrs;
|
||||
priv->cls.class_release = aw20036_debug_class_release;
|
||||
ret = class_register(&priv->cls);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "debug register class failed! (%d)\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aw20036_parse_dt(struct device *dev, struct aw20036 *aw20036,
|
||||
struct device_node *np)
|
||||
{
|
||||
int ret = 0, prop_size, length;
|
||||
struct property *prop;
|
||||
struct property *prop_level;
|
||||
|
||||
aw20036->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0);
|
||||
if (aw20036->reset_gpio < 0) {
|
||||
dev_err(dev,
|
||||
"%s: no reset gpio provided, will not HW reset device\n",
|
||||
__func__);
|
||||
return -1;
|
||||
} else {
|
||||
dev_info(dev, "%s: reset gpio provided ok\n", __func__);
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "max-brightness", &aw20036->max_brightness);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed read max brightness, use default!");
|
||||
aw20036->max_brightness = LED_FULL;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "brightness", &aw20036->brightness);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed read max brightness, use default!");
|
||||
aw20036->brightness = LED_FULL;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "imax", &aw20036->imax);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed read global max current, use default!");
|
||||
aw20036->imax = 3; /* 40mA */
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "gamma-table", &prop_size);
|
||||
if (prop) {
|
||||
aw20036->gamma_table = devm_kzalloc(dev, prop_size, GFP_KERNEL);
|
||||
if (!aw20036->gamma_table)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_property_read_u32_array(np, "gamma-table",
|
||||
aw20036->gamma_table,
|
||||
prop_size / sizeof(u32));
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Get LED gamma table failed!\n");
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
dev_info(dev, "No LED gamma table found!\n");
|
||||
aw20036->gamma_table = NULL;
|
||||
}
|
||||
|
||||
prop_level = of_find_property(np, "brightness-levels", &length);
|
||||
if (prop_level) {
|
||||
aw20036->levels = devm_kzalloc(dev, length, GFP_KERNEL);
|
||||
if (!aw20036->levels)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_property_read_u32_array(np, "brightness-levels",
|
||||
aw20036->levels,
|
||||
length / sizeof(u32));
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Get LED brightness-levels failed!\n");
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
dev_info(dev, "No LED brightness levels table found!\n");
|
||||
aw20036->levels = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw20036_hw_reset(struct aw20036 *aw20036) {
|
||||
if (aw20036 && gpio_is_valid(aw20036->reset_gpio)) {
|
||||
gpio_set_value_cansleep(aw20036->reset_gpio, 0);
|
||||
msleep(1);
|
||||
gpio_set_value_cansleep(aw20036->reset_gpio, 1);
|
||||
usleep_range(2000, 2500);
|
||||
} else {
|
||||
dev_err(aw20036->dev, "hw_reset...failed\n");
|
||||
}
|
||||
dev_info(aw20036->dev, "hw_reset...ok.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw20036_read_aw20036id(struct aw20036 *aw20036) {
|
||||
int ret = -1;
|
||||
unsigned char cnt = 0;
|
||||
unsigned int reg_val = 0;
|
||||
|
||||
regmap_write(aw20036->regmap, REG_PAGE, 0xC0);
|
||||
|
||||
while (cnt < AW_READ_CHIPID_RETRIES) {
|
||||
ret = regmap_read(aw20036->regmap, REG_CHIPID, ®_val);
|
||||
if (reg_val == AW20036_CHIPID) {
|
||||
dev_info(aw20036->dev, "This Chip is 'AW20036' REG_ID: '0x%x'\n",
|
||||
reg_val);
|
||||
return 0;
|
||||
} else if (ret < 0) {
|
||||
dev_err(aw20036->dev,
|
||||
"%s: failed to AW20036_REG_ID: %d\n", __func__,
|
||||
ret);
|
||||
return -EIO;
|
||||
} else {
|
||||
cnt++;
|
||||
dev_info(aw20036->dev,
|
||||
"This Chip read register REG_ID: 0x%x\n",
|
||||
reg_val);
|
||||
}
|
||||
msleep(AW_READ_CHIPID_RETRY_DELAY);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int aw20036_led_init(struct aw20036 *aw20036) {
|
||||
int i = 0;
|
||||
|
||||
regmap_write(aw20036->regmap, REG_PAGE, 0xC0);
|
||||
regmap_write(aw20036->regmap, REG_SWRST, 0x01);
|
||||
msleep(2);
|
||||
regmap_write(aw20036->regmap, REG_GCCR, (aw20036->imax << 4) | 0x8);
|
||||
/* 3x12 LEDs */
|
||||
regmap_write(aw20036->regmap, REG_SIZE, 0x2);
|
||||
/* Active mode */
|
||||
regmap_write(aw20036->regmap, REG_WORK_MODE, 0x00);
|
||||
|
||||
/* Set DIM current */
|
||||
regmap_write(aw20036->regmap, REG_PAGE, 0xC1);
|
||||
for (i = 0; i < 36; i+=3) {
|
||||
regmap_write(aw20036->regmap, i, 0x3f);
|
||||
regmap_write(aw20036->regmap, i + 1, 0x3f);
|
||||
regmap_write(aw20036->regmap, i + 2, 0x2f);
|
||||
}
|
||||
|
||||
/* Set fade current */
|
||||
regmap_write(aw20036->regmap, REG_PAGE, 0xC2);
|
||||
for (i = 0; i <= 0x23; i++)
|
||||
regmap_write(aw20036->regmap, i, 0);
|
||||
|
||||
dev_info(aw20036->dev, "aw20036_led_init...ok\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw20036_led_release(struct aw20036 *aw20036) {
|
||||
regmap_write(aw20036->regmap, REG_PAGE, 0xC0);
|
||||
regmap_write(aw20036->regmap, REG_SWRST, 0x01);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct regmap_config aw20036_regmap = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = 0xFF,
|
||||
.cache_type = REGCACHE_NONE,
|
||||
};
|
||||
|
||||
static int aw20036_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id) {
|
||||
struct aw20036 *aw20036 = NULL;
|
||||
struct device_node *np = i2c->dev.of_node;
|
||||
struct led_classdev *led_cdev;
|
||||
int ret = -1;
|
||||
|
||||
aw20036 = devm_kzalloc(&i2c->dev, sizeof(struct aw20036), GFP_KERNEL);
|
||||
if (aw20036 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = aw20036_debug_reset_gpio(&i2c->dev, aw20036);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
aw20036->i2c = i2c;
|
||||
aw20036->dev = &i2c->dev;
|
||||
|
||||
if (np) {
|
||||
ret = aw20036_parse_dt(&i2c->dev, aw20036, np);
|
||||
if (ret) {
|
||||
dev_err(&i2c->dev,
|
||||
"%s: failed to parse device tree node\n",
|
||||
__func__);
|
||||
return -EIO;
|
||||
}
|
||||
} else {
|
||||
aw20036->reset_gpio = -1;
|
||||
}
|
||||
|
||||
if (gpio_is_valid(aw20036->reset_gpio)) {
|
||||
ret = devm_gpio_request_one(&i2c->dev, aw20036->reset_gpio,
|
||||
GPIOF_OUT_INIT_LOW, "aw20036_rst");
|
||||
if (ret) {
|
||||
dev_err(&i2c->dev, "%s: rst request failed\n",
|
||||
__func__);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
aw20036->regmap = devm_regmap_init_i2c(i2c, &aw20036_regmap);
|
||||
if (IS_ERR(aw20036->regmap)) {
|
||||
ret = PTR_ERR(aw20036->regmap);
|
||||
dev_err(&i2c->dev,
|
||||
"Failed to allocate register map: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
aw20036_hw_reset(aw20036);
|
||||
ret = aw20036_read_aw20036id(aw20036);
|
||||
if (ret < 0) {
|
||||
dev_err(&i2c->dev, "%s: aw20036_read_aw20036id failed ret=%d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
aw20036_led_init(aw20036);
|
||||
|
||||
INIT_WORK(&aw20036->work, aw20036_brightness_work);
|
||||
|
||||
aw20036->aucdev.led_type = AVS_UX_TYPE_RADIAL,
|
||||
aw20036->aucdev.num_leds = 12,
|
||||
led_cdev = &aw20036->aucdev.led_cdev;
|
||||
led_cdev->name = AW20036_I2C_NAME;
|
||||
led_cdev->max_brightness = aw20036->max_brightness;
|
||||
led_cdev->brightness = aw20036->brightness;
|
||||
led_cdev->brightness_set = aw20036_brightness_set;
|
||||
led_cdev->flags |= LED_DEV_CAP_AVS_UX;
|
||||
|
||||
ret = led_classdev_avsux_register(&i2c->dev, &aw20036->aucdev);
|
||||
if (ret < 0) {
|
||||
dev_err(&i2c->dev, "Register avsux led failed:%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(i2c, aw20036);
|
||||
|
||||
/* Run bootup animation */
|
||||
avsux_timer_flag = 1;
|
||||
bootup_anime = led_avsux_create_animation("bootup", 0, 0, 0,
|
||||
bootup_patterns, ARRAY_SIZE(bootup_patterns));
|
||||
if (!IS_ERR(bootup_anime)) {
|
||||
printk("############### bootup ###############\n");
|
||||
led_avsux_start_animation(&aw20036->aucdev, bootup_anime);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aw20036_i2c_remove(struct i2c_client *i2c) {
|
||||
struct aw20036 *aw20036 = i2c_get_clientdata(i2c);
|
||||
|
||||
aw20036_led_release(aw20036);
|
||||
|
||||
if (gpio_is_valid(aw20036->reset_gpio)) {
|
||||
devm_gpio_free(&i2c->dev, aw20036->reset_gpio);
|
||||
}
|
||||
|
||||
led_classdev_avsux_unregister(&aw20036->aucdev);
|
||||
|
||||
if (!IS_ERR(bootup_anime))
|
||||
kfree(bootup_anime);
|
||||
|
||||
devm_kfree(&i2c->dev, aw20036);
|
||||
aw20036 = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id aw20036_i2c_id[] = {
|
||||
{AW20036_I2C_NAME, 0},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, aw20036_i2c_id);
|
||||
|
||||
static const struct of_device_id aw20036_dt_match[] = {
|
||||
{.compatible = "awinic,aw20036_led"},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct i2c_driver aw20036_i2c_driver = {
|
||||
.driver = {
|
||||
.name = AW20036_I2C_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(aw20036_dt_match),
|
||||
},
|
||||
.probe = aw20036_i2c_probe,
|
||||
.remove = aw20036_i2c_remove,
|
||||
.id_table = aw20036_i2c_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(aw20036_i2c_driver);
|
||||
|
||||
MODULE_DESCRIPTION("AW20036 LED Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,80 @@
|
|||
#ifndef __AW20036_H__
|
||||
#define __AW20036_H__
|
||||
#include <linux/leds.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/led-class-avsux.h>
|
||||
|
||||
#define REG_CHIPID 0x00
|
||||
#define REG_WORK_MODE 0x01
|
||||
#define REG_SWRST 0x02
|
||||
#define REG_GCCR 0x03
|
||||
#define REG_FCS 0x04
|
||||
#define REG_CLKSYS 0x05
|
||||
#define REG_FLTCFG1 0x09
|
||||
#define REG_FLTCFG2 0x0A
|
||||
#define REG_ISRFLT 0x0B
|
||||
#define REG_LEDON0 0x31
|
||||
#define REG_LEDON1 0x32
|
||||
#define REG_LEDON2 0x33
|
||||
#define REG_LEDON3 0x34
|
||||
#define REG_LEDON4 0x35
|
||||
#define REG_LEDON5 0x36
|
||||
#define REG_PATE 0x43
|
||||
#define REG_FADEH0 0x44
|
||||
#define REG_FADEH1 0x45
|
||||
#define REG_FADEH2 0x46
|
||||
#define REG_FADEL0 0x47
|
||||
#define REG_FADEL1 0x48
|
||||
#define REG_FADEL2 0x49
|
||||
#define REG_PAT0T0 0x4A
|
||||
#define REG_PAT0T1 0x4B
|
||||
#define REG_PAT0T2 0x4C
|
||||
#define REG_PAT0T3 0x4D
|
||||
#define REG_PAT1T0 0x4E
|
||||
#define REG_PAT1T1 0x4F
|
||||
#define REG_PAT1T2 0x50
|
||||
#define REG_PAT1T3 0x51
|
||||
#define REG_PAT2T0 0x52
|
||||
#define REG_PAT2T1 0x53
|
||||
#define REG_PAT2T2 0x54
|
||||
#define REG_PAT2T3 0x55
|
||||
#define REG_PAT0CFG 0x56
|
||||
#define REG_PAT1CFG 0x57
|
||||
#define REG_PAT2CFG 0x58
|
||||
#define REG_PATGO 0x59
|
||||
#define REG_SIZE 0x80
|
||||
#define REG_PAGE 0xF0
|
||||
|
||||
#define AW_I2C_RETRIES 2
|
||||
#define AW_I2C_RETRY_DELAY 1
|
||||
#define AW_READ_CHIPID_RETRIES 2
|
||||
#define AW_READ_CHIPID_RETRY_DELAY 1
|
||||
|
||||
#define AW20036_RSTR 0x01
|
||||
#define AW20036_CHIPID 0x18
|
||||
|
||||
struct aw20036 {
|
||||
struct i2c_client *i2c;
|
||||
struct device *dev;
|
||||
struct led_classdev_avsux aucdev;
|
||||
struct regmap *regmap;
|
||||
struct work_struct work;
|
||||
struct led_avsux_pattern *cur_pattern;
|
||||
struct class cls;
|
||||
unsigned max_brightness;
|
||||
unsigned brightness;
|
||||
unsigned imax;
|
||||
u32 *gamma_table;
|
||||
u32 *levels;
|
||||
int reset_gpio;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -16,11 +16,20 @@
|
|||
#include <linux/rwsem.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#ifdef CONFIG_LEDS_CLASS_AVS_UX
|
||||
extern int avsux_timer_flag;
|
||||
#endif
|
||||
|
||||
static inline void led_set_brightness_async(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
value = min(value, led_cdev->max_brightness);
|
||||
led_cdev->brightness = value;
|
||||
#ifdef CONFIG_LEDS_CLASS_AVS_UX
|
||||
if (!avsux_timer_flag){
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!(led_cdev->flags & LED_SUSPENDED))
|
||||
led_cdev->brightness_set(led_cdev, value);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#obj-y += cht8305.o
|
||||
#obj-y += cw2015_fuel_gauge_V5.0.o
|
||||
#obj-y += cw2015.o
|
||||
obj-y += leds-aw20036.o
|
||||
#obj-y += leds-aw20036.o
|
||||
obj-y += feier_gpioint.o
|
||||
#obj-y += spi-st7789v.o
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_AVS_UX_LED_CLASS_H__
|
||||
#define __LINUX_AVS_UX_LED_CLASS_H__
|
||||
|
||||
#include <linux/leds.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
struct device_node;
|
||||
struct led_classdev_avsux;
|
||||
|
||||
#define MAX_NUM_LEDS 12
|
||||
#define LED_AVS_UX_SYSFS_GROUPS_SIZE 5
|
||||
|
||||
enum led_avsux_type {
|
||||
AVS_UX_TYPE_SINGLE = 0,
|
||||
AVS_UX_TYPE_RADIAL,
|
||||
AVS_UX_TYPE_LINEAR,
|
||||
};
|
||||
|
||||
struct led_rgb_colors {
|
||||
u8 red;
|
||||
u8 green;
|
||||
u8 blue;
|
||||
};
|
||||
|
||||
struct led_avsux_pattern {
|
||||
struct list_head list;
|
||||
struct led_rgb_colors colors[MAX_NUM_LEDS];
|
||||
u32 duration;
|
||||
};
|
||||
|
||||
struct led_avsux_animation {
|
||||
char name[32];
|
||||
char anime_path[256];
|
||||
struct list_head list;
|
||||
struct list_head patterns;
|
||||
struct led_avsux_pattern *cur_pattern;
|
||||
int priority;
|
||||
u32 loop, pause;
|
||||
u32 count;
|
||||
};
|
||||
|
||||
struct led_avsux_ops {
|
||||
int (*pattern_set)(struct led_classdev_avsux *auled_cdev, struct led_avsux_pattern *pattern);
|
||||
};
|
||||
|
||||
struct led_classdev_avsux {
|
||||
struct led_classdev led_cdev;
|
||||
struct led_avsux_ops *ops;
|
||||
enum led_avsux_type led_type;
|
||||
int num_leds;
|
||||
struct hrtimer anime_timer;
|
||||
struct list_head animations;
|
||||
struct led_avsux_animation *cur_anime;
|
||||
|
||||
const struct attribute_group *sysfs_groups[LED_AVS_UX_SYSFS_GROUPS_SIZE];
|
||||
};
|
||||
|
||||
static inline struct led_classdev_avsux *lcdev_to_aucdev(
|
||||
struct led_classdev *lcdev)
|
||||
{
|
||||
return container_of(lcdev, struct led_classdev_avsux, led_cdev);
|
||||
}
|
||||
|
||||
extern int led_classdev_avsux_register(struct device *parent,
|
||||
struct led_classdev_avsux *auled_cdev);
|
||||
|
||||
extern void led_classdev_avsux_unregister(struct led_classdev_avsux *auled_cdev);
|
||||
|
||||
extern struct led_avsux_animation *led_avsux_create_animation(char *name, int priority,
|
||||
u32 loop, u32 pause, struct led_avsux_pattern *patterns,
|
||||
int num_pattern);
|
||||
extern void led_avsux_start_animation(struct led_classdev_avsux *auled_cdev, struct led_avsux_animation *animation);
|
||||
|
||||
#endif
|
|
@ -48,6 +48,7 @@ struct led_classdev {
|
|||
#define SET_BRIGHTNESS_ASYNC (1 << 21)
|
||||
#define SET_BRIGHTNESS_SYNC (1 << 22)
|
||||
#define LED_DEV_CAP_FLASH (1 << 23)
|
||||
#define LED_DEV_CAP_AVS_UX (1 << 24)
|
||||
|
||||
/* Set LED brightness level */
|
||||
/* Must not sleep, use a workqueue if needed */
|
||||
|
|
Loading…
Reference in New Issue