理论部分:
pinctrl
子系统重点是设置
PIN(
有的
SOC
叫做
PAD)
的复用和电气属性,如果 pinctrl
子系统将一个
PIN
复用为
GPIO
的话,那么接下来就要用到
gpio
子系统了。gpio
子系统顾名思义,就是用于初始化
GPIO
并且提供相应的
API
函数,比如设置
GPIO为输入输出,读取 GPIO
的值等。
gpio
子系统的主要目的就是方便驱动开发者使用
gpio
,驱动开发者在设备树中添加 gpio
相关信息,然后就可以在驱动程序中使用
gpio
子系统提供的
API函数来操作 GPIO
,
Linux
内核向驱动开发者屏蔽掉了
GPIO
的设置过程,极大的方便了驱动开发者使用 GPIO
。
编写思路:
GPIO
的地位跟其他模块,比如
I2C
、
UART
的地方是一样的,要使用某个引脚,需要先把引脚配置为 GPIO
功能,这要使用
Pinctrl
子系统,只需要在设备 树里指定就可以。在驱动代码上不需要我们做任何事情。 GPIO 本身需要确定引脚,这也需要在设备树里指定。
设备树节点会被内核转换为
platform_device
。 对应的,驱动代码中要注册一个 platform_driver
,在
probe
函数中:获得引脚、注册 file_operations。在
file_operations
中:设置方向、读值
/
写值。
在设备树中添加 Pinctrl 信息:
有些芯片提供了
1)设备树生成工具,在
GUI
界面中选择引脚功能和配置信息, 就可以自动生成 Pinctrl
子结点。把它复制到你的设备树文件中,再在
client device 结点中引用就可以。
2)有 些 芯 片 只 提 供 文 档 , 那 就 去 阅 读 文 档 , 一 般 在 内 核 源 码的绑定目 录 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");