Linux led_class子系统

LED子系统是Linux内核中有关LED的框架,开发者可以使用这个框架来进行LED设备的注册。

LED子系统程序在/driver/leds/led-class.c中

  • led_classdev结构体
    内核把LED设备的属性抽象成了一个结构体,开发人员需要填充这个结构体里面的的一些成员变量,然后注册到内核中。
struct led_classdev {
	const char		*name;
	enum led_brightness	 brightness;
	enum led_brightness	 max_brightness;
	int			 flags;

	/* Lower 16 bits reflect status */
#define LED_SUSPENDED		(1 << 0)
	/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME	(1 << 16)
#define LED_BLINK_ONESHOT	(1 << 17)
#define LED_BLINK_ONESHOT_STOP	(1 << 18)
#define LED_BLINK_INVERT	(1 << 19)
#define LED_SYSFS_DISABLE	(1 << 20)
#define SET_BRIGHTNESS_ASYNC	(1 << 21)
#define SET_BRIGHTNESS_SYNC	(1 << 22)
#define LED_DEV_CAP_FLASH	(1 << 23)

	/* Set LED brightness level */
	/* Must not sleep, use a workqueue if needed */
	void		(*brightness_set)(struct led_classdev *led_cdev,
					  enum led_brightness brightness);
	/*
	 * Set LED brightness level immediately - it can block the caller for
	 * the time required for accessing a LED device register.
	 */
	int		(*brightness_set_sync)(struct led_classdev *led_cdev,
					enum led_brightness brightness);
	/* Get LED brightness level */
	enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

	/*
	 * Activate hardware accelerated blink, delays are in milliseconds
	 * and if both are zero then a sensible default should be chosen.
	 * The call should adjust the timings in that case and if it can't
	 * match the values specified exactly.
	 * Deactivate blinking again when the brightness is set to a fixed
	 * value via the brightness_set() callback.
	 */
	int		(*blink_set)(struct led_classdev *led_cdev,
				     unsigned long *delay_on,
				     unsigned long *delay_off);

	struct device		*dev;
	const struct attribute_group	**groups;

	struct list_head	 node;			/* LED Device list */
	const char		*default_trigger;	/* Trigger to use */

	unsigned long		 blink_delay_on, blink_delay_off;
	struct timer_list	 blink_timer;
	int			 blink_brightness;
	void			(*flash_resume)(struct led_classdev *led_cdev);

	struct work_struct	set_brightness_work;
	int			delayed_set_value;

#ifdef CONFIG_LEDS_TRIGGERS
	/* Protects the trigger data below */
	struct rw_semaphore	 trigger_lock;

	struct led_trigger	*trigger;
	struct list_head	 trig_list;
	void			*trigger_data;
	/* true if activated - deactivate routine uses it to do cleanup */
	bool			activated;
#endif

	/* Ensures consistent access to the LED Flash Class device */
	struct mutex		led_access;
};

我们通常需要关注的以下几个成员变量
name:LED设备名字
brightness:LED的亮度
max_brightness:LED的最大亮度
brightness_set:设置LED的亮度函数
brightness_get:返回LED亮度函数

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

led_brightness是一个枚举,里面定义了3个亮度值。灭,半亮和全亮。

  • LED框架初始化
 static int __init leds_init(void)
{
	leds_class = class_create(THIS_MODULE, "leds");  // 创建led类
	if (IS_ERR(leds_class))
		return PTR_ERR(leds_class);
	leds_class->pm = &leds_class_dev_pm_ops;
	leds_class->dev_groups = led_groups;      // 在leds类中创建属性
	return 0;
}

首先内核会先调用leds_init函数来初始化led子系统。先创建一个叫名字叫leds的类,然后在leds类中创建文件属性,这些文件属性可以在后面动态地设置LED的亮灭。

  • LED设备注册
/**
 * led_classdev_register - register a new object of led_classdev class.
 * @parent: The device to register.
 * @led_cdev: the led_classdev structure for this device.
 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
	char name[64];
	int ret;

	ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));
	if (ret < 0)
		return ret;

	led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
				led_cdev, led_cdev->groups, "%s", name); // 创建led设备
	if (IS_ERR(led_cdev->dev))
		return PTR_ERR(led_cdev->dev);

	if (ret)
		dev_warn(parent, "Led %s renamed to %s due to name collision",
				led_cdev->name, dev_name(led_cdev->dev));

#ifdef CONFIG_LEDS_TRIGGERS
	init_rwsem(&led_cdev->trigger_lock);
#endif
	mutex_init(&led_cdev->led_access);
	/* add to the list of leds */
	down_write(&leds_list_lock);
	list_add_tail(&led_cdev->node, &leds_list); // 将led设备添加内核链表
	up_write(&leds_list_lock);

	if (!led_cdev->max_brightness)  // 判断是否设备LED最大亮度
		led_cdev->max_brightness = LED_FULL;

	led_cdev->flags |= SET_BRIGHTNESS_ASYNC;

	led_update_brightness(led_cdev);

	INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);

	setup_timer(&led_cdev->blink_timer, led_timer_function,
		    (unsigned long)led_cdev);

#ifdef CONFIG_LEDS_TRIGGERS
	led_trigger_set_default(led_cdev);
#endif

	dev_dbg(parent, "Registered led device: %s\n",
			led_cdev->name);

	return 0;
}

内核给驱动开发人员提供了一个LED设备注册函数,叫led_classdev_register。

  • LED设备属性文件
static ssize_t brightness_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t size)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	unsigned long state;
	ssize_t ret;

	mutex_lock(&led_cdev->led_access);

	if (led_sysfs_is_disabled(led_cdev)) {
		ret = -EBUSY;
		goto unlock;
	}

	ret = kstrtoul(buf, 10, &state);
	if (ret)
		goto unlock;

	if (state == LED_OFF)
		led_trigger_remove(led_cdev);
	led_set_brightness(led_cdev, state);

	ret = size;
unlock:
	mutex_unlock(&led_cdev->led_access);
	return ret;
}
static DEVICE_ATTR_RW(brightness);

static ssize_t max_brightness_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	return sprintf(buf, "%u\n", led_cdev->max_brightness);
}
static DEVICE_ATTR_RO(max_brightness);


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,
};

上面几行代码会在每个led设备下创建两个设备属性文件,分别是max_brightness和brightness,用户可以直接通过读max_brightness设备文件获取LED的最大亮度,通过读写brightness设备文件来控制LED灯的亮灭。

接下来就利用led子系统写一个驱动程序来控制开发板上的LED灯,在开始写程序时要先看一下内核是否支持led子系统。
首先进入内核的图形化配置界面,在
Device Drivers
–>LED Support
–>LED Class Support
看一下LED Class Support选项是否选中,如果没有选中,就需要选中该配置项并重新编译内核代码。
Linux led_class子系统_第1张图片
如果led子系统配置成功会在/sys/class目录下看到一个叫“leds”的类。
Linux led_class子系统_第2张图片

驱动程序

struct led_dev_type
{
	struct miscdevice led_misc;
	int gpio;
	struct device_node* nd;  // 设备树节点
	struct mutex ledmutex;
	struct led_classdev ledclasscdev;
};


struct led_dev_type ledcdev;


void led_brightness_set(struct led_classdev *led_cdev,enum led_brightness brightness)
{
	struct led_dev_type *pdata = container_of(led_cdev, struct led_dev_type, ledclasscdev);
	
	
	if(LED_OFF == brightness)  // 判断用户传递参数是否为0
	{
		gpio_set_value(pdata->gpio,1);  // LED灭
	}
	else
	{
		gpio_set_value(pdata->gpio,0);  // LED亮
	}
}


static int led_probe(struct platform_device *pdev)
{
	int ret = 0;
	DEBUG_SFLR();
	
	ledcdev.nd = pdev->dev.of_node;  // 获取设备树节点
	DEBUG_SFLR("ledcdev.nd -> name = %s\r\n",ledcdev.nd->name);
	
	ledcdev.gpio = of_get_named_gpio(ledcdev.nd,"led_gpio",0);  // 获取LED的GPIO号
	if(ledcdev.gpio < 0){
		ret = -1;
		goto end;
	}
	DEBUG_SFLR("ledcdev.gpio  = %d\r\n",ledcdev.gpio );
	
	
	if(gpio_is_valid(ledcdev.gpio))
	{
		ret = devm_gpio_request_one(&pdev->dev,ledcdev.gpio,GPIOF_OUT_INIT_HIGH,"atkgpioled");  // 申请GPIO
		if(ret < 0){
			ret = -1;
			goto end;
		}
	}
	else{
		DEBUG_SFLR("ledcdev.gpio is busy\r\n..." );
		ret = -1;
		goto end;
	}
	
	
	ledcdev.ledclasscdev.name = "imx6ull_led";  // 设置led设备名
	ledcdev.ledclasscdev.brightness_set  = led_brightness_set; // 设置LED控制看书
	devm_led_classdev_register(&pdev->dev,&ledcdev.ledclasscdev); // 注册LED设备
	platform_set_drvdata(pdev,&ledcdev);
end:
	return ret;
}

static int led_remove(struct platform_device *pdev)
{
	// remove函数负责卸载驱动
	struct led_dev_type *pdata = platform_get_drvdata(pdev);
	devm_led_classdev_unregister(&pdev->dev,&pdata->ledclasscdev);
	gpio_set_value(pdata->gpio,1);
	gpio_free(pdata->gpio);
	
	return 0;
}
static const struct of_device_id led_match[] = {
	{ .compatible = "fsl,imx6ull gpioled", },
	{ },
};


static struct platform_driver led_driver = {
	.probe		= led_probe,
	.remove     = led_remove,  // 驱动卸载时执行remove函数
	.driver		= {
		.name	= "gpioled",
		.of_match_table = led_match,  // 新版有设备树时通过compatible节点匹配
	},
};



static int __init led_init_module(void)
{
	
	return platform_driver_register(&led_driver);  // 利用platform总线注册驱动
}


static void __exit led_exit_module(void)
{
	platform_driver_unregister(&led_driver);
	
}

module_init(led_init_module);
module_exit(led_exit_module);


MODULE_DESCRIPTION("xxxxxx");
MODULE_AUTHOR("xxxxx");
MODULE_LICENSE("GPL");

在程序中先申请LED灯的GPIO口,然后设置LED设备的名字以及LED灯的控制函数,然后注册LED设备。将驱动程序编译并加载到内核。

在加载驱动前,在leds类中还没有一个叫"imx6ull_led"的设备。
在这里插入图片描述
当驱动加载成功后,再次查看leds类中的设备,就会发现多了一个叫"imx6ull_led"的设备
Linux led_class子系统_第3张图片
进入"imx6ull_led"这个文件会发现里面有很多属性文件,我们可以读写这些属性文件来测试LED。
在这里插入图片描述

在这里插入图片描述
读取max_brightness得到LED的最大亮度。

在这里插入图片描述
同时也可以往brightness里面写值控制LED的亮灭。

你可能感兴趣的:(c语言,linux)