GPIO子系统(2)leds

        gpio子系统之leds,主要用于GPIO类型led的开关控制。

配置

CONFIG_LEDS_GPIO=y

drivers/leds/Makefile
	obj-$(CONFIG_LEDS_GPIO)         += leds-gpio.o
	
drivers/leds/Kconfig
	config LEDS_GPIO
		tristate "LED Support for GPIO connected LEDs"
		depends on LEDS_CLASS
		depends on GPIOLIB || COMPILE_TEST
		help
		  This option enables support for the LEDs connected to GPIO
		  outputs. To be useful the particular board must have LEDs
		  and they must be connected to the GPIO lines.  The LEDs must be
		  defined as platform devices and/or OpenFirmware platform devices.
		  The code to use these bindings can be selected below.

DTS

	leds {
		compatible = "gpio-leds";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_pcieb>;
		led1: user1 {
			label = "user1";
			gpios = <&gpio4 18 GPIO_ACTIVE_LOW>; 
			default-state = "on";
			linux,default-trigger = "heartbeat";
		};
	};

	pinctrl_pcieb: pcieagrp{ 
		fsl,pins = < 
			SC_P_EMMC0_RESET_B_LSIO_GPIO4_IO18     0x00000021
		>; 
	};

驱动

        使用platform虚拟总线的方式,提供probe和shutdown函数。

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);

        驱动匹配字符串:gpio-leds

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

        probe函数直接调用gpio_leds_create函数。

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) {
        ...
	} else {
		priv = gpio_leds_create(pdev);
		if (IS_ERR(priv))
			return PTR_ERR(priv);
	}

	platform_set_drvdata(pdev, priv);

	return 0;
}

        1、device_get_child_node_count函数获取子节点个数,并申请内存空间,包含gpio_leds_priv和gpio_led_data。

        2、device_for_each_child_node宏定义是重点,解析每个子节点的信息,主要包含label,gpios,linux,default-trigger,default-state信息。

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;
		struct device_node *np = to_of_node(child);

		ret = fwnode_property_read_string(child, "label", &led.name);
		if (ret && IS_ENABLED(CONFIG_OF) && np)
			led.name = np->name;
		if (!led.name) {
			fwnode_handle_put(child);
			return ERR_PTR(-EINVAL);
		}

		led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
							     GPIOD_ASIS,
							     led.name);
		if (IS_ERR(led.gpiod)) {
			fwnode_handle_put(child);
			return ERR_CAST(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, np, NULL);
		if (ret < 0) {
			fwnode_handle_put(child);
			return ERR_PTR(ret);
		}
		led_dat->cdev.dev->of_node = np;
		priv->num_leds++;
	}

	return priv;
}

        把dts中读出的子节点信息赋值到gpio_led_data中,gpiod_direction_output函数设置GPIO输出方向并根据状态设置对应高低电平。

static int create_gpio_led(const struct gpio_led *template,
	struct gpio_led_data *led_dat, struct device *parent,
	struct device_node *np, gpio_blink_set_t blink_set)
{
	int ret, state;

	led_dat->gpiod = template->gpiod;

	led_dat->cdev.name = template->name;
	led_dat->cdev.default_trigger = template->default_trigger;
	led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
	if (!led_dat->can_sleep)
		led_dat->cdev.brightness_set = gpio_led_set;
	else
		led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking;
	led_dat->blinking = 0;
	if (blink_set) {
		led_dat->platform_gpio_blink_set = blink_set;
		led_dat->cdev.blink_set = gpio_blink_set;
	}
	if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
		state = gpiod_get_value_cansleep(led_dat->gpiod);
		if (state < 0)
			return state;
	} else {
		state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
	}
	led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
	if (!template->retain_state_suspended)
		led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
	if (template->panic_indicator)
		led_dat->cdev.flags |= LED_PANIC_INDICATOR;
	if (template->retain_state_shutdown)
		led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;

	ret = gpiod_direction_output(led_dat->gpiod, state);
	if (ret < 0)
		return ret;

	return devm_of_led_classdev_register(parent, np, &led_dat->cdev);
}

        调用devm_of_led_classdev_register函数进行classdev的注册,这里进入到led-class层,通过device_create_with_groups函数创建/sys/class/leds下面的设备,还会创建brightness和trigger的class。

int devm_of_led_classdev_register(struct device *parent,
				  struct device_node *np,
				  struct led_classdev *led_cdev)
{
	struct led_classdev **dr;
	int rc;

	dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
	if (!dr)
		return -ENOMEM;

	rc = of_led_classdev_register(parent, np, led_cdev);
	if (rc) {
		devres_free(dr);
		return rc;
	}

	*dr = led_cdev;
	devres_add(parent, dr);

	return 0;
}
EXPORT_SYMBOL_GPL(devm_of_led_classdev_register);

led-class

       1、leds类属性的定义与初始化。文件:drivers/leds/led-class.c

subsys_initcall(leds_init);

static int __init leds_init(void)
{
	leds_class = class_create(THIS_MODULE, "leds");
	if (IS_ERR(leds_class))
		return PTR_ERR(leds_class);
	leds_class->pm = &leds_class_dev_pm_ops;
	leds_class->dev_groups = led_groups;
	return 0;
}

        2、leds_class->dev_groups 指定了leds设备类的类属性,包括trigger,brightness,max_brightness。类属性将被sysfs以文件的形式导出至/sys/class/leds/xxx目录下,用户空间通过对这些文件的访问来操作硬件设备。

static const struct attribute_group *led_groups[] = {
	&led_group,
	&led_trigger_group,
	NULL,
};

static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
static struct attribute *led_trigger_attrs[] = {
	&dev_attr_trigger.attr,
	NULL,
};
static const struct attribute_group led_trigger_group = {
	.attrs = led_trigger_attrs,
};

static struct attribute *led_class_attrs[] = {
	&dev_attr_brightness.attr,
	&dev_attr_max_brightness.attr,
	NULL,
};

static const struct attribute_group led_group = {
	.attrs = led_class_attrs,
};

        3、定义了brightness、max_brightness、trigger属性相关的echo和cat操作对应的show和store函数。当用户在文件系统下读写LED设备的属性文件时,就会调用这些属性文件的show和store方法,从而操作硬件。

static ssize_t brightness_show(struct device *dev,
		struct device_attribute *attr, char *buf)
static ssize_t brightness_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t size)
{
static DEVICE_ATTR_RW(brightness);

static ssize_t max_brightness_show(struct device *dev,
		struct device_attribute *attr, char *buf)
static DEVICE_ATTR_RO(max_brightness);

static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);

测试

root@genvict_imx8qxp:~# ls /sys/class/leds/user1
brightness  device  invert  max_brightness  power  subsystem  trigger  uevent
root@genvict_imx8qxp:~# cat /sys/class/leds/user1/brightness 
0
root@genvict_imx8qxp:~# cat /sys/class/leds/user1/max_brightness 
255
root@genvict_imx8qxp:~# cat /sys/class/leds/user1/trigger        
none rc-feedback bluetooth-power rfkill-any kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock mmc0 mmc1 [heartbeat] cpu cpu0 cpu1 cpu2 cpu3 default-on hci0-power rfkill0 rfkill1 

        波形怪怪的 

 pinctrl设置了GPIO功能后,方向和拉高拉低没有影响,GPIO_ACTIVE_LOW低电平有效,默认状态on,那么GPIO输出默认低电平,brightness默认就会on状态到255。

#define LEDS_GPIO_DEFSTATE_OFF		0
#define LEDS_GPIO_DEFSTATE_ON		1
#define LEDS_GPIO_DEFSTATE_KEEP		2

enum led_brightness {
	LED_OFF		= 0,
	LED_ON		= 1,
	LED_HALF	= 127,
	LED_FULL	= 255,
};

state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;

猜你喜欢

转载自blog.csdn.net/TSZ0000/article/details/124968342
今日推荐