imx6ul linux4.1.15 LED驱动配置及heartbeat源码分析

武, 军/ 十月 24, 2017/ Linux, MPU

(1)查看内核配置
wujun@wj-vBox:~/freescale/linux-imx$ cat arch/arm/configs/imx_v7_defconfig | grep LEDS
CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_GPIO=y
CONFIG_LEDS_TRIGGERS=y
CONFIG_LEDS_TRIGGER_TIMER=y
CONFIG_LEDS_TRIGGER_ONESHOT=y
CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_LEDS_TRIGGER_BACKLIGHT=y
CONFIG_LEDS_TRIGGER_GPIO=y

已经开启了LED并且使能了几种触发模式:timer oneshot heartbeat backlight gpio

(2)设备树增加LED相关设定
wujun@wj-vBox:~/freescale/linux-imx$ vi arch/arm/boot/dts/imx6ul-14×14-evk.dts
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_leds>;

led0: user {
label = “user”;
gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;
default-state = “off”;
};

led1: cpu {
label = "cpu";
gpios = <&gpio5 2 GPIO_ACTIVE_LOW>;
default-state = "on";
linux,default-trigger = "heartbeat";
};
};

pinctrl放在 &iomuxc下
pinctrl_leds: ledgrp {
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x1b0b0
MX6UL_PAD_SNVS_TAMPER2__GPIO5_IO02 0x1b0b0
>;
};

共设定两个灯,
一个是GPIO5 IO01 用户灯
一个是GPIO5 IO02 CPU心跳灯

(3)查找驱动
wujun@wj-vBox:~/freescale/linux-imx$ grep -rn “gpio-leds” drivers
drivers/leds/leds-gpio.c:237: { .compatible = “gpio-leds”, },
触发方式源码在trigger目录下
wujun@wj-vBox:~/freescale/linux-imx/drivers/leds$ ls trigger/
built-in.o ledtrig-cpu.c ledtrig-heartbeat.c ledtrig-timer.o
Kconfig ledtrig-cpu.o ledtrig-heartbeat.o ledtrig-transient.c
ledtrig-backlight.c ledtrig-default-on.c ledtrig-ide-disk.c ledtrig-transient.o
ledtrig-backlight.o ledtrig-default-on.o ledtrig-oneshot.c Makefile
ledtrig-camera.c ledtrig-gpio.c ledtrig-oneshot.o modules.builtin
ledtrig-camera.o ledtrig-gpio.o ledtrig-timer.c modules.order
这些的编译时通过内核配置项来确定的。

(4)启动系统
通过查看LED的trigger文件,就能知道当前LED支持的触发器和目前设定的触发器
root@imx6ulevk:~# cat /sys/class/leds/user/trigger
[none] rc-feedback nand-disk mmc0 mmc1 timer oneshot heartbeat backlight gpio
root@imx6ulevk:~# cat /sys/class/leds/cpu/trigger
none rc-feedback nand-disk mmc0 mmc1 timer oneshot [heartbeat] backlight gpio

user led没有设定trigger
cpu led设定为heartbeat trigger

设置触发器也很简单,使用echo将触发器名称写入trigger文件即可。
写入的字符串一定是trigger文件已经存在的,否则会提示参数非法。
这里将user灯也设定为心跳灯。
root@imx6ulevk:~# echo heartbeat > /sys/class/leds/user/trigger
此时user灯与cpu灯同时闪烁。不过不是同步的。

再次查看用户灯的触发方式
root@imx6ulevk:~# cat /sys/class/leds/user/trigger
none rc-feedback nand-disk mmc0 mmc1 timer oneshot [heartbeat] backlight gpio
已经设定成心跳灯了。

(5)心跳灯控制源码
因为比较好奇心跳灯闪的频率,查看了驱动源码。
wujun@wj-vBox:~/freescale/linux-imx/drivers/leds$ vi trigger/ledtrig-heartbeat.c
找到了控制闪灯时序的代码。

static void led_heartbeat_function(unsigned long data)
{
struct led_classdev *led_cdev = (struct led_classdev *) data;
struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
unsigned long brightness = LED_OFF;
unsigned long delay = 0;

if (unlikely(panic_heartbeats)) {
led_set_brightness(led_cdev, LED_OFF);
return;
}

/* acts like an actual heart beat — ie thump-thump-pause… */
switch (heartbeat_data->phase) {
case 0:
/*
* The hyperbolic function below modifies the
* heartbeat period length in dependency of the
* current (1min) load. It goes through the points
* f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
*/
heartbeat_data->period = 300 +
(6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
heartbeat_data->period =
msecs_to_jiffies(heartbeat_data->period);
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
brightness = led_cdev->max_brightness;
break;
case 1:
delay = heartbeat_data->period / 4 – msecs_to_jiffies(70);
heartbeat_data->phase++;
break;
case 2:
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
brightness = led_cdev->max_brightness;
break;
default:
delay = heartbeat_data->period – heartbeat_data->period / 4 –
msecs_to_jiffies(70);
heartbeat_data->phase = 0;
break;
}

led_set_brightness_async(led_cdev, brightness);
mod_timer(&heartbeat_data->timer, jiffies + delay);
}

代码比较简单,不过里面有个细节
/* acts like an actual heart beat — ie thump-thump-pause… */
这个意思是说,模拟人的心跳,从这点上看,写这段代码的工程师是具有追求极致的精神。心跳灯都要模拟心跳的频率。
另外一个点就是周期的计算方法。
/*
* The hyperbolic function below modifies the
* heartbeat period length in dependency of the
* current (1min) load. It goes through the points
* f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
*/
heartbeat_data->period = 300 +
(6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
从代码的意思上看,是根据当前的系统load来决定周期。
如果系统load是0,周期就是1260,其他类推。
300 + (6270 << 11) / (5 * 0) + (7 << 11) = 300 + 6270 / 7 = 300 + 960 = 1260;

其中FSHIFT可以从下面路径找到定义
wujun@wj-vBox:~/freescale/linux-imx/include/linux$ grep -rn “FSHIFT” .
./sched.h:153:#define FSHIFT 11 /* nr of bits of precision */
这个是因为linux内核避免使用浮点运算所以用整数来计算。

linux内核定义了unsigned long avenrun[3];三个长整型数据。
wujun@wj-vBox:~/freescale/linux-imx$ vi ./kernel/sched/proc.c +61
61 unsigned long avenrun[3];
在32位的cpu上,long对应的就是32bit,其中低11位用于存放负载的小数部分,高21位用于存放整数部分。
之所以是三个变量,是因为内核要记录cpu的load有三个变量,分别是1min,5min和15min。
运行top的时候能够看到这三个变量的值。
load average: 0.00, 0.02, 0.00

运行uptime也可以看到
wujun@wj-vBox:~/freescale/linux-imx$ uptime
11:44:08 up 21:23, 1 user, load average: 0.00, 0.02, 0.00

这三个值是从/proc/loadavg中获得的。
wujun@wj-vBox:~/freescale/linux-imx$ cat /proc/loadavg
0.00 0.02 0.00 1/167 5038

得出结论是在cpu load为0的情况下,闪灯周期是1260ms。
总共闪两次,每次亮70ms,两次亮之间的时间差是周期的1/4.
也就是在cpu load = 0情况下:
|70 |       |70 |
| 415ms |
| 415ms | 415ms | 415ms | 415ms | 70|

(6)最后用示波器看一下波形是否如预期。
因为GPIO的驱动能力有限,一般LED控制都是选择灌电流。这样逻辑上是反向的,低电平亮,高电平灭。
上图

从图上看大致相同,总体感觉是开发板时钟偏快。