Linux gpio-leds heartbeat 心跳灯 驱动分析

  • Post author:
  • Post category:linux


通常产品会留一个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 的 显示极性,不需要修改驱动代码 !



版权声明:本文为u013443294原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。