基于 GPIO 子系统的 LED 驱动程序

基于 GPIO 子系统的 LED 驱动程序

在这里插入图片描述

GPIO的地位跟其他模块,比如 I2C、 UART 的地方是一样的,要使用某个引脚,需要先把引脚配置为 GPIO 功能,这要使用 Pinctrl 子系统,只需要在设备树里指定就可以。在驱动代码上不需要我们做任何事情。

GPIO 本身需要确定引脚,这也需要在设备树里指定。设备树节点会被内核转换为 platform_device,对应的,驱动代码中要注册一个 platform_driver,在 probe 函数中:获取引脚、注册 file_operations

在 file_operations 中:设置方向、读值/写值。 下图就是一个设备树的例子:
在这里插入图片描述

在设备树中添加Pinctrl 信息

有些芯片提供了设备树生成工具,在GUI 界面中选择引脚功能和配置信息,就可以自动生成 Pinctrl子节点。把它复制到你的设备树文件中,然后在 client device节点中引用就可以了。

有些芯片只提供文档,那就去阅读文档,一般在内核源码目录 Documentation\devicetree\bindings\pinctrl 下面,保存该厂家的文档,如果连文档都没有,那只能参考内核源码中的设备树文件,在内核源码目录 arch/arm/boot/dts 目录下。

最后一步,网络搜索。

在设备树中添加 GPIO 信息

先查看电路原理图确定所用引脚,再在设备树中指定:添加 ”[name]-gpios” 属性,指定使用的是哪一个 GPIO Controller 里的哪一个引脚,还有其他 Flag 信息,比如 GPIO_ACTIVE_LOW 等。具体需要多少个 cell 来描述一个引脚,需要查看设备树中这个 GPIO Controller 节点里的 “ #gpio-cells” 属性值,也可以查看内核文档。示例如下:
在这里插入图片描述

代码操作如下

在实际操作过程中也许会碰到意外的问题,现场演示如何解决。

  • 第1步 定义、注册一个 platform_driver

  • 第2步 在它的 probe 函数里:

    • 根据 platform_device 的设备树信息确定 GPIO: gpiod_get
    • 定义、注册一个 file_operations 结构体
    • file_operarions 中使用 GPIO 子系统的函数操作 GPIO:

好处在于这些代码对所有的板子都是完全一样的。

注册 platform_driver

// 资源
static const struct of_device_id winter_leds[] = {
	{ .compatible = "winter, led_drv"},
	{},
};
 
// 定义platform_driver
static struct platform_driver chip_demo_gpio_driver = {
	.probe = chip_demo_gpio_probe,
	.remove = chip_demo_gpio_remove,
	.driver = {
		.name = "winter_led",
		.of_match_table = winter_leds,
	},
};
 
// 入口函数中注册platform_driver
static int __init led_init(void)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 注册platform_driver结构体
	err = platform_driver_register(&chip_demo_gpio_driver);
	return err;
}
 
// 出口函数
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	platform_driver_unregister(&chip_demo_gpio_driver);
}
 
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

在 probe 函数中获得 GPIO

核心代码是如下,它从该设备(对应设备树中的设备节点)获取名为“ led”的引脚。在设备树中,必定有一属性名为 “ led-gpios”“ ledgpio”

/* 	  从platform_device获得GPIO
 *    把file_operations结构体告诉内核:注册驱动程序
 */
int chip_demo_gpio_probe (struct platform_device* pdev)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 设备树中定义 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 -1;
	}
 
	// 注册file_operations结构体
	major = register_chrdev(0, "winter_led", &led_drv);					// /proc/devices/winter_led
	// 注册结点
	led_class = class_create(THIS_MODULE, "winter_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, "winter_led%d", 0); /* /dev/100ask_led0 */
	return 0;
}

在 open 函数中调用 GPIO 函数设置引脚方向-输出

// 实现对应的open/read等函数
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;
}

在 write 函数中调用 GPIO 函数设置引脚值:

// 用户向内核中写数据
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__);
	copy_from_user(&status, buf, 1);
	// 根据次设备号和status控制LED
	gpiod_set_value(led_gpio, status);
 
	return 1;
}

释放 GPIO:

gpiod_put(led_gpio);

完整代码如下

#include <linux/module.h>
#include <linux/platform_device.h>
 
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
 
 
 
// 1确定主设备号
static int major = 0;
static struct class* led_class;
static struct gpio_desc *led_gpio;
 
 
// 实现对应的open/read等函数
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;
}
// 从内核中读数据到用户
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;
}
// 用户向内核中写数据
ssize_t led_drv_write (struct file* file, const char __user* buf, size_t size, loff_t* offset)
{
	char status;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	copy_from_user(&status, buf, 1);
	// 根据次设备号和status控制LED
	gpiod_set_value(led_gpio, status);
 
	return 1;
}
 
int led_drv_close (struct inode* noed, 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,
};
 
 
/* 	  从platform_device获得GPIO
 *    把file_operations结构体告诉内核:注册驱动程序
 */
int chip_demo_gpio_probe (struct platform_device* pdev)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 设备树中定义 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 -1;
	}
 
	// 注册file_operations结构体
	major register_chrdev(0, "winter_led", &led_drv);					// /proc/devices/winter_led
	// 注册结点
	led_class = class_create(THIS_MODULE, "winter_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, "winter_led%d", 0); /* /dev/100ask_led0 */
	return 0;
}
 
 
 
int chip_demo_gpio_remove (struct platform_device* pdev)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "winter_led");
	gpiod_put(led_gpio);
	return 0;
}
 
 
// 资源
static const struct of_device_id winter_leds[] = {
	{ .compatible = "winter,led_drv"},
	{},
};
 
// 定义platform_driver
static struct platform_driver chip_demo_gpio_driver = {
	.probe = chip_demo_gpio_probe,
	.remove = chip_demo_gpio_remove,
	.driver = {
		.name = "winter_led",
		.of_match_table = winter_leds,
	},
};
 
// 入口函数中注册platform_driver
static int __init led_init(void)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 注册platform_driver结构体
	err = platform_driver_register(&chip_demo_gpio_driver);
	return err;
}
 
// 出口函数
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	platform_driver_unregister(&chip_demo_gpio_driver);
}
 
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值