树莓派linux led字符设备驱动( linux自带)

  树莓派的Linux 内核已经集成了LED 灯驱动。 Linux 内核的 LED 灯驱动采用 platform 框架。编译树莓派linux内核时,会输入配置命令构建配置linux内核,如:

cd linux
KERNEL=kernel7l
make bcm2711_defconfig

  linux内核的makefile会从arch/arm/configs 目录中寻找默认配置文件: bcm2711_defconfig,配置完成后会生成.config 文件。打开.config 文件,有“CONFIG_LEDS_GPIO=y”,说明配置了LED灯驱动。

#
# LED drivers
#
# CONFIG_LEDS_AN30259A is not set
# CONFIG_LEDS_BCM6328 is not set
# CONFIG_LEDS_BCM6358 is not set
# CONFIG_LEDS_CR0014114 is not set
# CONFIG_LEDS_LM3530 is not set
# CONFIG_LEDS_LM3532 is not set
# CONFIG_LEDS_LM3642 is not set
# CONFIG_LEDS_LM3692X is not set
CONFIG_LEDS_PCA9532=m
# CONFIG_LEDS_PCA9532_GPIO is not set
CONFIG_LEDS_GPIO=y

一、Linux 内核自带 LED 驱动

  打开/drivers/leds/Makefile 这个文件,有:

obj-$(CONFIG_LEDS_GPIO_REGISTER)	+= leds-gpio-register.o
obj-$(CONFIG_LEDS_GPIO)			+= leds-gpio.o
obj-$(CONFIG_LEDS_LP3944)		+= leds-lp3944.o

  说明如果定义了 CONFIG_LEDS_GPIO 的话就会编译 leds-gpio.c 这个文件,而在.config 文件中有“CONFIG_LEDS_GPIO=y”这一行,因此 leds-gpio.c 驱动文件会被编译进linux内核。
  打开LED 灯驱动文件/drivers/leds/leds-gpio.c,有如下所示内容:

static const struct of_device_id of_gpio_leds_match[] = {
    
    
	{
    
     .compatible = "gpio-leds", },
	{
    
    },
};

  LED 驱动的匹配表, compatible 内容为“gpio-leds”,因此设备树中的 LED 灯设备节点的 compatible 属性值也要为“gpio-leds”,否则设备和驱动匹配不成功,驱动就没法工作。

static struct platform_driver gpio_led_driver = {
    
    
	.probe		= gpio_led_probe,
	.shutdown	= gpio_led_shutdown,
	.driver		= {
    
    
		.name	= "leds-gpio",
		.of_match_table = of_gpio_leds_match,
	},
};

module_platform_driver(gpio_led_driver);

  platform_driver 驱动结构体变量,可以看出, Linux 内核自带的 LED 驱动采用了 platform 框架。 probe 函数为 gpio_led_probe,当驱动和设备匹配成功以后 gpio_led_probe 函数就会执行。驱动名字为“leds-gpio”,因此会在/sys/bus/platform/drivers 目录下存在一个名为“leds-gpio”的文件。
  当驱动和设备匹配以后 gpio_led_probe 函数就会执行,此函数主要是从设备树中获取 LED灯的 GPIO 信息。gpio_led_probe内容为:

static int gpio_led_probe(struct platform_device *pdev)
{
    
    
	struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
	struct gpio_leds_priv *priv;
	int i, ret = 0;

	if (pdata && pdata->num_leds) {
    
    /* 非设备树方式 */
		priv = devm_kzalloc(&pdev->dev,
				sizeof_gpio_leds_priv(pdata->num_leds),
					GFP_KERNEL);
		if (!priv)
			return -ENOMEM;

		priv->num_leds = pdata->num_leds;
		for (i = 0; i < priv->num_leds; i++) {
    
    
			const struct gpio_led *template = &pdata->leds[i];
			struct gpio_led_data *led_dat = &priv->leds[i];

			if (template->gpiod)
				led_dat->gpiod = template->gpiod;
			else
				led_dat->gpiod =
					gpio_led_get_gpiod(&pdev->dev,
							   i, template);
			if (IS_ERR(led_dat->gpiod)) {
    
    
				dev_info(&pdev->dev, "Skipping unavailable LED gpio %d (%s)\n",
					 template->gpio, template->name);
				continue;
			}

			ret = create_gpio_led(template, led_dat,
					      &pdev->dev, NULL,
					      pdata->gpio_blink_set);
			if (ret < 0)
				return ret;
		}
	} else {
    
    /* 采用设备树 */
		priv = gpio_leds_create(pdev);
		if (IS_ERR(priv))
			return PTR_ERR(priv);
	}

	platform_set_drvdata(pdev, priv);

	return 0;
}

  如果使用设备树的话,会使用 gpio_leds_create 函数从设备树中提取设备信息,获取到的 LED 灯 GPIO 信息保存在返回值中,gpio_leds_create 函数内容如下:

static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
    
    
	struct device *dev = &pdev->dev;
	struct fwnode_handle *child;
	struct gpio_leds_priv *priv;
	int count, ret;

	count = device_get_child_node_count(dev);
	if (!count)
		return ERR_PTR(-ENODEV);

	priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL);
	if (!priv)
		return ERR_PTR(-ENOMEM);

	device_for_each_child_node(dev, child) {
    
    
		struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
		struct gpio_led led = {
    
    };
		const char *state = NULL;

		/*
		 * Acquire gpiod from DT with uninitialized label, which
		 * will be updated after LED class device is registered,
		 * Only then the final LED name is known.
		 */
		led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
							     GPIOD_ASIS,
							     NULL);
		if (IS_ERR(led.gpiod)) {
    
    
			fwnode_handle_put(child);
			return ERR_CAST(led.gpiod);
		}

		led_dat->gpiod = led.gpiod;

		fwnode_property_read_string(child, "linux,default-trigger",
					    &led.default_trigger);

		if (!fwnode_property_read_string(child, "default-state",
						 &state)) {
    
    
			if (!strcmp(state, "keep"))
				led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
			else if (!strcmp(state, "on"))
				led.default_state = LEDS_GPIO_DEFSTATE_ON;
			else
				led.default_state = LEDS_GPIO_DEFSTATE_OFF;
		}

		if (fwnode_property_present(child, "retain-state-suspended"))
			led.retain_state_suspended = 1;
		if (fwnode_property_present(child, "retain-state-shutdown"))
			led.retain_state_shutdown = 1;
		if (fwnode_property_present(child, "panic-indicator"))
			led.panic_indicator = 1;

		ret = create_gpio_led(&led, led_dat, dev, child, NULL);
		if (ret < 0) {
    
    
			fwnode_handle_put(child);
			return ERR_PTR(ret);
		}
		/* Set gpiod label to match the corresponding LED name. */
		gpiod_set_consumer_name(led_dat->gpiod,
					led_dat->cdev.dev->kobj.name);
		priv->num_leds++;
	}

	return priv;
}

  device_get_child_node_count 函数统计子节点数量,一般在在设备树中创建一个节点表示 LED 灯,然后在这个节点下面为每个 LED 灯创建一个子节点。因此子节点数量也是 LED 灯的数量。

二、树莓派4b的设备树节点

  在linux内核的.config 配置文件文件中有

#
# Other Architectures
#
CONFIG_ARCH_BCM2835=y
# CONFIG_ARCH_BCM_53573 is not set
# CONFIG_ARCH_BCM_63XX is not set
# CONFIG_ARCH_BRCMSTB is not set

  说明会编译“CONFIG_ARCH_BCM2835”配置下的设备树文件。
  在 arch/arm/boot/dts/Makefile文件中有如下内容:

dtb-$(CONFIG_ARCH_BCM2835) += \
	bcm2708-rpi-b.dtb \
	bcm2708-rpi-b-rev1.dtb \
	bcm2708-rpi-b-plus.dtb \
	bcm2708-rpi-cm.dtb \
	bcm2708-rpi-zero.dtb \
	bcm2708-rpi-zero-w.dtb \
	bcm2709-rpi-2-b.dtb \
	bcm2710-rpi-2-b.dtb \
	bcm2710-rpi-3-b.dtb \
	bcm2710-rpi-3-b-plus.dtb \
	bcm2711-rpi-4-b.dtb \
	bcm2710-rpi-cm3.dtb \
	bcm2711-rpi-cm4.dtb
	..........
	dtb-$(CONFIG_ARCH_BCM2835) += \
	bcm2835-rpi-b.dtb \
	bcm2835-rpi-a.dtb \
	bcm2835-rpi-b-rev2.dtb \
	bcm2835-rpi-b-plus.dtb \
	bcm2835-rpi-a-plus.dtb \
	bcm2835-rpi-cm1-io1.dtb \
	bcm2836-rpi-2-b.dtb \
	bcm2837-rpi-3-a-plus.dtb \
	bcm2837-rpi-3-b.dtb \
	bcm2837-rpi-3-b-plus.dtb \
	bcm2837-rpi-cm3-io3.dtb \
	bcm2711-rpi-4-b.dtb \
	bcm2835-rpi-zero.dtb \
	bcm2835-rpi-zero-w.dtb
	........
	# Enable fixups to support overlays on BCM2835 platforms
ifeq ($(CONFIG_ARCH_BCM2835),y)
	DTC_FLAGS ?= -@
endif

  编译设备树的时候会将这些.dts文件 编译为二进制的.dtb文件。如果使用uboot 启动linux内核,则uboot 中会使用 bootz 或 bootm命令向 Linux 内核传递二进制设备树文件(.dtb))。树莓派的bootloader会根据板子选择加载具体的设备树文件。
  在arch/arm/boot/dts/bcm2711-rpi-4-b.dts文件中有:

#include "bcm2711.dtsi"
#include "bcm2835-rpi.dtsi"
......
	leds {
    
    
		act {
    
    
			gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
		};

		pwr {
    
    
			label = "PWR";
			gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
			default-state = "keep";
			linux,default-trigger = "default-on";
		};
	};
......
&leds {
    
    
	act_led: act {
    
    
		label = "led0";
		linux,default-trigger = "mmc0";
		gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
	};

	pwr_led: pwr {
    
    
		label = "led1";
		linux,default-trigger = "default-on";
		gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
	};
};

  在arch/arm/boot/dts/bcm2835-rpi.dtsi文件中有:

	leds {
    
    
		compatible = "gpio-leds";

		act {
    
    
			label = "ACT";
			default-state = "keep";
			linux,default-trigger = "heartbeat";
		};
	};

  说明:
  ①、创建了一个节点leds表示 LED 灯设备。
  ②、 leds节点的 compatible 属性值为“gpio-leds”。
  ③、 leds节点的两个子节点,都有一个 label 属性“led0”和“led0”, label 属性一般表示LED 灯的名字。
  ④、这两个子节点都有 gpios 属性值,表示此 LED 所使用的 GPIO 引脚。
  ⑤、这两个子节点都设置“linux,default-trigger”属性值,也就是设置 LED 灯的默认功能。

三、运行测试

  启动树莓派以后查看/sys/bus/platform/devices/leds目录。证明在platform总线上加载了节点leds。在/sys/bus/platform/devices/leds目录下进入子目录leds。在 leds 目录下有两个个名为“led0”和“led1”的子目录,这两个子目录的名字就是设备树中两个子节点的 label 属性值。
  同时在目录sys/devices/platform/leds中也有相同的内容。
  测试时通过/sys/class/leds/led[LED_ID]/trigger文件进行配置。其中[LED_ID]需要替换为 0(代表 ACT LED)或 1(代表 PWR LED)。

猜你喜欢

转载自blog.csdn.net/xxxx123041/article/details/120461068