理论部分:
编写思路:
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");