理论部分:
编写思路:
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 目录下。
Pinctrl 子节点的样式如下:
在设备树中添加 GPIO 信息
先查看电路原理图确定所用引脚,再在设备树中指定:添加 ”[name]-gpios” 属性,指定使用的是哪一个 GPIO Controller 里的哪一个引脚,还有其他 Flag 信息,比如 GPIO_ACTIVE_LOW 等。具体需要多少个 cell 来描述一个引脚,需要查看设备树中这个 GPIO Controller 节点里的“ #gpio-cells ”属性值,也 可以查看内核文档。
在驱动代码中调用 GPIO 子系统函数访问设备的gpio
实践部分:
创建pinctl sever节点:
修改imx6ull-myboard.dts,利用节点生成工具配置引脚后将生成的对应代码片段替换到dts文件内对应位置,节点内容如下:
pinctrl_gpioled: ledgrp{fsl,pins = <MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x10b0>;
};
- MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 表示将该io复用为GPIO
- 0x10b0 表示对PAD寄存器的配置值,具体含义为如下,
/*寄存器SW_PAD_SNVS_TAMPER3设置IO属性*bit 16:0 HYS关闭*bit [15:14]: 00 默认下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驱动能力*bit [0]: 0 低转换率
Pinctrl节点示例:
创建pinctrl client节点并添加GPIO信息:
在根节点下创建名为gpioled的LED节点,内容如下:
/*pinctrl led*/
gpioled {compatible = "myboard,gpioled";pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpioled>;led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;status = "okay";
};
- pinctrl-0 设置 LED所使用的PIN对应的pinctrl节点
- led-gpio 指定了LED所使用的GPIO,这里是GPIO5的IO03,低电平有效
注意事项:
因为我的开发板使用的设备树文件(imx6ull-myboard.dts)是从NXP官方提供的设备树文件(imx6ull-14x14-evk.dts)上修改而来的,可能某些引脚的配置与自己的开发板不一样,需要检查一下是否有使用冲突。
本次添加的这个MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03与文件中的其它引脚没有出现冲突,因此无需修改。
修改LED驱动文件
重要步骤:
第 1 步 定义、注册一个 platform_driver ;
第 2 步 在它的 probe 函数里:
a) 根据 platform_device 的设备树信息确定 GPIO : gpiod_get
b)创建设备节点
c)注册一个 file_operations 结构体register_chrdev()
file_operarions 中使用 GPIO 子系统的函数操作 GPIO: .open()、.write() (也就是gpiod_direction_output、 gpiod_set_value)
全部代码如下:
/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;#define MIN(a, b) (a < b ? a : b)void led_class_create_device(int minor)
{device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/100ask_led0,1,... */
}
void led_class_destroy_device(int minor)
{device_destroy(led_class, MKDEV(major, minor));
}
void register_led_operations(struct led_operations *opr)
{p_led_opr = opr;
}EXPORT_SYMBOL(led_class_create_device);
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);/* 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;
}/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;char status;struct inode *inode = file_inode(file);int minor = iminor(inode);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(&status, buf, 1);/* 根据次设备号和status控制LED */p_led_opr->ctl(minor, status);return 1;
}static int led_drv_open (struct inode *node, struct file *file)
{int minor = iminor(node);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 根据次设备号初始化LED */p_led_opr->init(minor);return 0;
}static int led_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 2. 定义自己的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. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */led_class = class_create(THIS_MODULE, "100ask_led_class");err = PTR_ERR(led_class);if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "led");return -1;}return 0;
}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit led_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);class_destroy(led_class);unregister_chrdev(major, "100ask_led");
}/* 7. 其他完善:提供设备信息,自动创建设备节点 */module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");