基于 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");