Linux 中利用设备树点灯

系列文章目录

第一章 Linux 中内核与驱动程序
第二章 Linux 设备驱动编写 (misc)
第三章 Linux 设备驱动编写及设备节点自动生成 (cdev)
第四章 Linux 平台总线platform与设备树
第五章 Linux 设备树中pinctrl与gpio(lichee nano pi)
第六章 Linux ioctl接口应用
第七章 Linux 中利用设备树点灯
————————————————



前言

前一节中介绍了pinctrl子系统和gpio子系统,但是仍然感觉梦比,在此进行一个小实验来学习怎么使用。仍然是点灯,因此需要先利用pinctrl子系统将对应引脚设置为gpio,然后再利用gpio子系统控制输入输出。
然后在具体介绍一下platform模型,以及一些gpio函数。


一、设备树文件修改

1.pinctrl设置

本例采用韦东山的qume开发板,其芯片为nxp的imx6ul。首先在板子对应的内核树文件中找到iomuxc或者iomuxc_snvs,具体可以看原理图上的引脚编号。(这里仅说明过程)
在这里插入图片描述
这里面有好多已经定义好的pinctrl配置,仿照其形式添加需要的字段。本文在这里添加的是pinctrl_test。其中的宏定义可以在imx6ul-pinfunc.h中查看。

在这里插入图片描述

2.gpio设置

设置好pinctrl属性,需要在设备树中再添加设置节点并且激活配置,这里需要将gpio的属性也设置好。
在根节点中,添加子节点leds,利用红框中的两行,激活上一节中的配置,然后配置对应引脚的gpio属性。如此就配置好设备树了。接下来编写驱动程序,具体而言就是drive文件。

在这里插入图片描述

二、platform平台总线模型

1、platform平台总线模型介绍

之前章节也提到过:平台总线模型,将驱动程序分成了driver.c和device.c两部分。其中,前者是与驱动相关的代码;后者是与硬件相关的描述,如一些寄存器和其他的硬件资源。两者会进行匹配,成功后进到probe函数。 在probe函数我们可以拿到device里面描述的硬件资源,然后在probe里面注册杂项设备和字符设备,这样就可以完成一个以平台总线模型设计的驱动。
如此一来,就有引入一个结构体struct platform_driver,其内容如下:

static struct platform_driver my_driver = {
    .probe      = my_probe,		//注册platform_driver时,匹配成功调用prob函数
    .remove     = my_remove,	//卸载platform_driver时调用该函数
    .driver     = {
        .name   = "XXXX",
        .of_match_table = YYYY,	//YYYY为of_device_id结构体,声明.compatible属性
    },
};

其中注册 platform_driver 的函数是platform_driver_register(&my_driver )。卸载 platform_driver 的函数是platform_driver_unregister(&my_driver )。其中,在驱动的入口函数module_init(入口函数)里面注册,出口函数module_exit(出口函数)里面卸载。拿硬件信息和注册字符设备驱动和杂项设备驱动的代码都放在prob函数里面了。

2、drive文件编写

其中device部分就是设备树文件。那么剩下的需要编写的就是driver.c部分了。drive文件编写流程:

/*部分代码-来自韦东山*。
/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
static struct gpio_desc *led_gpio;	//硬件资源

/* 3. 实现对应的open/read/write等函数,填入file_operations结构 */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/****** 根据次设备号和status控制LED ******/
	gpiod_set_value(led_gpio, status);
	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据次设备号初始化LED */
	gpiod_direction_output(led_gpio, 0);
	return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 定义自己的file_operations结构体 */
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

/* 4. 从platform_device获得GPIO
 *    把file_operations结构体告诉内核:注册驱动程序
 */
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 4.1 拿硬件资源,设备树中定义 led-gpios=<...>;*/
    led_gpio = gpiod_get(&pdev->dev, "led", 0);
	if (IS_ERR(led_gpio)) {
		dev_err(&pdev->dev, "Failed to get GPIO for led\n");
		return PTR_ERR(led_gpio);
	}
    
	/* 4.2 注册file_operations 	*/
	major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */

	led_class = class_create(THIS_MODULE, "100ask_led_class");
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led");
		gpiod_put(led_gpio);
		return PTR_ERR(led_class);
	}
	
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */  
    return 0;
}

static int chip_demo_gpio_remove(struct platform_device *pdev)
{
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "100ask_led");
	gpiod_put(led_gpio);
    return 0;
}

static const struct of_device_id ask100_leds[] = {
    { .compatible = "100ask,leddrv" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "100ask_led",
        .of_match_table = ask100_leds,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init led_init(void)
{
    int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    err = platform_driver_register(&chip_demo_gpio_driver); 
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数卸载platform_driver
 */
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    platform_driver_unregister(&chip_demo_gpio_driver);
}

/* 7. 其他完善:提供设备信息,自动创建设备节点 */
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

这里的流程同之前的相同不过有几个函数gpiod_getgpiod_put等需要注意。这个下次再总结具体不在叙述,重点在于pinctrl和gpio子系统的设置。
总的来说,pinctrl和gpio子系统不算是坏事,对于不同的板子只需要修改设备树文件就可以复用driver.c文件,如此一来就可以到处点灯了。
在这里插入图片描述

附录

附一张法师的图。
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

请叫我7plus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值