通常产品会留一个LED 作为系统运行指示灯,Linux 自带leds-gpio 驱动,使用以来非常简单。
首先在设备树中配置led 引脚和工作模式
leds {
compatible = "gpio-leds";
work {
gpios = <&gpio8 1 GPIO_ACTIVE_LOW>;
label = "led2-0";
linux,default-trigger = "heartbeat";
pinctrl-names = "default";
pinctrl-0 = <&work_led>;
};
power {
gpios = <&gpio8 2 GPIO_ACTIVE_LOW>;
label = "led2-1";
linux,default-trigger = "default-on";
pinctrl-names = "default";
pinctrl-0 = <&power_led>;
};
};
然后在内核中使能led 驱动:
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
编译烧写到系统启动就可以看到LED闪烁了。
但笔者在工作中发现硬件工程师画的板子,LED有时候是高点平点亮,有时候是低电平点亮
那怎样才能统一风格,让LED按照心跳频率显示呢?
查看源码 kernel\drivers\leds\trigger\ledtrig-heartbeat.c
static void led_heartbeat_function(struct timer_list *t)
36{
37 struct heartbeat_trig_data *heartbeat_data =
38 from_timer(heartbeat_data, t, timer);
39 struct led_classdev *led_cdev;
40 unsigned long brightness = LED_OFF;
41 unsigned long delay = 0;
42
43 led_cdev = heartbeat_data->led_cdev;
44
45 if (unlikely(panic_heartbeats)) {
46 led_set_brightness_nosleep(led_cdev, LED_OFF);
47 return;
48 }
49
50 if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags))
51 led_cdev->blink_brightness = led_cdev->new_blink_brightness;
52
53 /* acts like an actual heart beat -- ie thump-thump-pause... */
54 switch (heartbeat_data->phase) {
55 case 0:
56 /*
57 * The hyperbolic function below modifies the
58 * heartbeat period length in dependency of the
59 * current (1min) load. It goes through the points
60 * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
61 */
62 heartbeat_data->period = 300 +
63 (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
64 heartbeat_data->period =
65 msecs_to_jiffies(heartbeat_data->period);
66 delay = msecs_to_jiffies(70);
67 heartbeat_data->phase++;
68 if (!heartbeat_data->invert)
69 brightness = led_cdev->blink_brightness;
70 break;
71 case 1:
72 delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
73 heartbeat_data->phase++;
74 if (heartbeat_data->invert)
75 brightness = led_cdev->blink_brightness;
76 break;
77 case 2:
78 delay = msecs_to_jiffies(70);
79 heartbeat_data->phase++;
80 if (!heartbeat_data->invert)
81 brightness = led_cdev->blink_brightness;
82 break;
83 default:
84 delay = heartbeat_data->period - heartbeat_data->period / 4 -
85 msecs_to_jiffies(70);
86 heartbeat_data->phase = 0;
87 if (heartbeat_data->invert)
88 brightness = led_cdev->blink_brightness;
89 break;
90 }
91
92 led_set_brightness_nosleep(led_cdev, brightness);
93 mod_timer(&heartbeat_data->timer, jiffies + delay);
94}
95
由源码可知,当延时时间到时设置 LED 亮度 brightness = led_cdev->max_brightness;
Linux系统默认灌电流点亮LED,即SOC引脚 低电平点亮,我们可以修改
led_set_brightness(led_cdev, LED_FULL);
led_set_brightness_async(led_cdev, LED_OFF); 来达到目标
但不急着动手,我们继续看源码:
static ssize_t led_invert_show(struct device *dev,
89 struct device_attribute *attr, char *buf)
90{
91 struct led_classdev *led_cdev = dev_get_drvdata(dev);
92 struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
93
94 return sprintf(buf, "%u\n", heartbeat_data->invert);
95}
96
97static ssize_t led_invert_store(struct device *dev,
98 struct device_attribute *attr, const char *buf, size_t size)
99{
100 struct led_classdev *led_cdev = dev_get_drvdata(dev);
101 struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
102 unsigned long state;
103 int ret;
104
105 ret = kstrtoul(buf, 0, &state);
106 if (ret)
107 return ret;
108
109 heartbeat_data->invert = !!state;
110
111 return size;
112}
驱动提供了 Sysfs 文件系统 访问
heartbeat_data
->
invert
变量,
echo 1 > /sys/devices/platform/leds/leds/led2-0/invert
就可以改变led 的 显示极性,不需要修改驱动代码 !