基于 RK3308 的灯效开发

更新时间:2021-04-22 10:23:03下载pdf

涂鸦 AVS SDK LED 灯效驱动框架基于 Linux 的 LED Class 扩展而来。涂鸦在标准的 Linux 的 LED 驱动之上新增了一个 AVSUX Class,其实现了大部分的 AVS 灯效处理逻辑,大大的降低了灯效驱动开发的复杂度,您只需实现简单的 LED 亮度设置接口即可实现 AVS 的灯效控制。

驱动框架

在进行驱动移植之前,您需要了解驱动框架中两个重要的数据结构:

  • struct led_avsux_animation:对应一个完整的 LED 灯效动画,每个动画由若干个帧组成。
  • struct led_avsux_pattern:对应 LED 灯效动画中的一帧,驱动以帧为单位进行灯效更新。

LED 驱动移植与开发

在拿到 LED 芯片原厂的驱动后,您可以按如下步骤进行移植:

  • 将附件中的linux/drivers/leds/led-class-avsux.cinclude/linux/led-class-avsux.h 拷贝到对应的内核目录下。

  • 修改 include/linux/leds.h头文件,添加 LED_DEV_CAP_AVS_UX

    +++ b/include/linux/leds.h @@ -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 */

    在 Makefile 和 Kconfig 中添加 AVSUX Class 驱动框架支持。

    +++ b/drivers/leds/Makefile @@ -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 obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o +++ b/drivers/leds/Kconfig @@ -29,6 +29,16 @@ 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 the AVS related features of a LED device. It can be built + as a module. + comment "LED drivers"
  • 完成上面的步骤后,就可以使用涂鸦 AVS LED 驱动框架开发灯效驱动了。

    LED 驱动开发非常简单,您只需要实现 brightness_set 接口,并调用 led_classdev_avsux_register 函数注册到驱动框架即可。

    一个典型的 LED 亮度设置接口如下:

    static void led_avs_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 led_avsux_pattern *pattern = animation->cur_pattern; int max = led_cdev->max_brightness; u8 red, green, blue; int i; if (value > max) value = max; /* 设置每个灯的 RGB 颜色 */ for (i = 0; i < auled_cdev->num_leds; i++) { red = pattern->colors[i].red * value / max; green = pattern->colors[i].green * value / max; blue = pattern->colors[i].blue * value / max; led_avs_set_rgb(i, red, green, blue); } }
  • 然后注册驱动到涂鸦 AVS LED 驱动框架,这里以一个 I2C 接口的芯片为例。

    static int led_avs_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct led_avs_priv *avs = NULL; struct device_node *np = i2c->dev.of_node; struct led_classdev *led_cdev; int ret = -1; avs = devm_kzalloc(&i2c->dev, sizeof(*avs), GFP_KERNEL); if (avs == NULL) return -ENOMEM; avs->i2c = i2c; avs->dev = &i2c->dev; if (np) { ret = avs_parse_dt(&i2c->dev, aw20036, np); if (ret) { dev_err(&i2c->dev, "%s: failed to parse device tree node\n", __func__); return -EIO; } } else { avs->reset_gpio = -1; } if (gpio_is_valid(avs->reset_gpio)) { ret = devm_gpio_request_one(&i2c->dev, avs->reset_gpio, GPIOF_OUT_INIT_LOW, "avs_rst"); if (ret) { dev_err(&i2c->dev, "%s: rst request failed\n", __func__); return -EIO; } } avs->regmap = devm_regmap_init_i2c(i2c, &avs_regmap); if (IS_ERR(avs->regmap)) { ret = PTR_ERR(avs->regmap); dev_err(&i2c->dev, "Failed to allocate register map: %d\n", ret); return ret; } avs_hw_reset(avs); ret = avs_check_chip_id(avs); if (ret < 0) { dev_err(&i2c->dev, "%s: check chip id failed ret=%d\n", __func__, ret); } avs_led_init(avs); /* 设置灯的类型和个数 */ avs->aucdev.led_type = AVS_UX_TYPE_RADIAL, avs->aucdev.num_leds = 12, led_cdev = &aw20036->aucdev.led_cdev; led_cdev->name = AVS_I2C_NAME; led_cdev->max_brightness = MAX_BRIGHTNESS; /* 通过max brightnees来调整整体的亮度 */ led_cdev->brightness_set = led_avs_brightness_set; led_cdev->flags |= LED_DEV_CAP_AVS_UX; /* 指定 AVS 灯效的支持 */ ret = led_classdev_avsux_register(&i2c->dev, &avs->aucdev); if (ret < 0) { dev_err(&i2c->dev, "Register avsux led failed:%d\n", ret); return ret; } i2c_set_clientdata(i2c, avs); return 0; }
  • 这里有几个地方需要注意:

    • LED 类型目前共有三种

      说明
      S_UX_TYPE_SINGLE
      S_UX_TYPE_RADIAL
      S_UX_TYPE_LINEAR
    • 驱动框架默认最多支持12个LED,如需支持更多的LED,请修改头文件 led-class-avsux.h 中的宏定义

    #define MAX_NUM_LEDS 12

示例

对于使用PWM或GPIO驱动的单灯灯阵,涂鸦提供了一个通用驱动。

将附件中的 linux/drivers/leds/leds-pwm-avsux.c 拷贝到内核的对应目录,并修改 Makefile

然后在设备树中使能pwm,并添加如下节点,pwm根据实际情况修改:

pwmleds { compatible = "avs-pwm-leds"; status = "okay"; avs-red { label = "red"; pwms = <&pwm0 0 10000000 0>; max-brightness = <255>; }; avs-green { label = "green"; pwms = <&pwm1 0 10000000 0>; max-brightness = <255>; }; avs-blue { label = "blue"; pwms = <&pwm2 0 10000000 0>; max-brightness = <255>; }; };

如果是使用gpio来模拟pwm,将附件的 linux/drivers/pwm/pwm-gpio.c 拷贝到内核的对应目录,并修改 Makefile。然后在设备树中添加如下的内容,pwm根据实际情况修改:

pwm0: pwm-gpio-r { compatible = "pwm-gpio"; #pwm-cells = <3>; pwm-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>; }; pwm1: pwm-gpio-g { compatible = "pwm-gpio"; #pwm-cells = <3>; pwm-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>; }; pwm2: pwm-gpio-b { compatible = "pwm-gpio"; #pwm-cells = <3>; pwm-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>; };

另外, 在附件中涂鸦提供了 aw20036 的驱动,您可以参考并进行移植。

测试灯效驱动

驱动编译加载后,会在 sysfs 下生成如下两个属性节点:

  • /sys/class/leds/avs-pwm-led/avsux_animation
  • /sys/class/leds/avs-pwm-led/avsux_select

先准备一个灯效文件 test.aniamtaion ,内容如下:

20:100000,100000,100000,100000,100000,100000,100000,100000,100000,100000,100000,100000 20:200000,200000,200000,200000,200000,200000,200000,200000,200000,200000,200000,200000 20:300000,300000,300000,300000,300000,300000,300000,300000,300000,300000,300000,300000 20:400000,400000,400000,400000,400000,400000,400000,400000,400000,400000,400000,400000 20:500000,500000,500000,500000,500000,500000,500000,500000,500000,500000,500000,500000 20:600000,600000,600000,600000,600000,600000,600000,600000,600000,600000,600000,600000 20:700000,700000,700000,700000,700000,700000,700000,700000,700000,700000,700000,700000 20:800000,800000,800000,800000,800000,800000,800000,800000,800000,800000,800000,800000 20:900000,900000,900000,900000,900000,900000,900000,900000,900000,900000,900000,900000 20:A00000,A00000,A00000,A00000,A00000,A00000,A00000,A00000,A00000,A00000,A00000,A00000 20:B00000,B00000,B00000,B00000,B00000,B00000,B00000,B00000,B00000,B00000,B00000,B00000 20:C00000,C00000,C00000,C00000,C00000,C00000,C00000,C00000,C00000,C00000,C00000,C00000

将文件拷贝到目标板的 /lib/firmware/ 目录下。

加载灯效到驱动中:

/ # echo "test test.animation" > /sys/class/leds/avs-pwm-led/avsux_animation

选择并使能灯效:

/ # echo "test" > /sys/class/leds/avs-pwm-led/avsux_select

此时就能看到红色灯从暗到亮的闪烁。

AVS 灯效动画文件

.animation文件描述了一个完整的灯效动画,其中每一行为一帧,对应一个 pattern。具体的格式如下:

其中 duration 表示这一帧的显示时长(单位为毫秒)。
HEX_XX 为对应灯的十六进制 RGB 值,每个灯之间以 , 分隔。需要注意的是,大于LED个数的值都会被驱动忽略。

附件的 led_animation 目录中已经定义了AVS用到的所有灯效,目前支持单灯、环灯和线灯,客户可以根据需要修改或添加新的灯效。

LED HAL

除了 AVSUX Class 驱动框架外,涂鸦还提供了 libLedAnimation 库用以实现更复杂的 LED 逻辑和状态机状态管理。

提供的 API 如下:

/** * LedAnimationInit() - 灯效初始化函数 * @conf: 灯效配置文件路径,为NULL时默认从/etc/led_config.json加载 */ ledHandle_t LedAnimationInit(const char *conf); /** * LedAnimationUpdate() - 灯效更新函数 * @handle: LedAnimationInit()返回的句柄 * @animation: 配置文件中定义的动画名称 */ int LedAnimationUpdate(ledHandle_t* handle, const char* animation); int LedAnimationRelease(ledHandle_t* handle);

灯效配置文件 /etc/led_config.json 的语法如下:

device 说明
device LED驱动的sysfs路径
type LED的类型
animations 支持的灯效

而每个灯效又包含如下几个属性:

属性 说明
fw_path 灯效动画文件路径(/lib/firnware下的相对路径)
priority 灯效优先级,高优先级的灯效可以打断覆盖低优先级的灯效
loop 灯效动画的循环次数,0 表示无限循环
pause 两次循环间的暂停时间
{ "device": "/sys/class/leds/avs-pwm-led", "type": "single" "animations": { "idle": { "fw_path": "led_animation/single/idle.animation", "priority": "1", "loop": "0", "pause":"0" }, "bootup": { "fw_path": "led_animation/single/bootup.animation", "priority": "0", "loop": "0", "pause":"0" }, "listening_start": { "fw_path": "led_animation/single/active-waking.animation", "priority": "0", "loop": "0", "pause":"0" }, ......