pinctrl与gpio子系统下的字符设备驱动框架
点亮Linux驱动开发路上的第一个灯一文中将与外设有关的寄存器信息,定义到驱动代码中,直接操作寄存器来控制外设。缺点是当芯片的寄存器发了变动,就要对底层的驱动进行重写。
设备树下的字符设备驱动框架一文中将与外设有关的寄存器信息,写到了设备树文件中,通过设备树API函数获取外设信息。当外设的信息有变化时,只需要修改设备树文件即可,无需修改底层驱动,提高了驱动代码的复用能力,但仍需要直接操作寄存器来控制外设。
本文介绍的pinctrl和gpio子系统实现了对寄存器的操作,我们只需要使用子系统提供的API函数即可,而无需再直接操作寄存器了。
1. pinctrl与gpio子系统介绍
1.1 pinctrl子系统
pinctrl子系统就是管理PIN引脚的一个系统,在设备树里设置PIN的配置信息,pinctrl会根据提供的信息来配置PIN功能。其主要工作内容如下:
打开设备树文件imx6ull-andyxi-emmc.dts,其中iomuxc节点就是I.MX6ULL的外设节点。代码中的pinctrl_hog_1子节点就是和热插拔有关的PIN集合
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
>;
};
......
};
};
下面以UART1_RTS_B这个PIN为例,介绍如何添加PIN的配置信息:
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
前半部分:设置UART1_RTS_B引脚的复用功能
//MX6UL_PAD_UART1_RTS_B__GPIO1_IO19宏定义在文件arch/arm/boot/dts/imx6ul-pinfunc.h中
#define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS 0x0090 0x031C 0x0620 0x0 0x3
#define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS 0x0090 0x031C 0x0000 0x0 0x0
#define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER 0x0090 0x031C 0x0000 0x1 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B 0x0090 0x031C 0x0668 0x2 0x1
#define MX6UL_PAD_UART1_RTS_B__CSI_DATA05 0x0090 0x031C 0x04CC 0x3 0x1
#define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT 0x0090 0x031C 0x0000 0x4 0x0
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B 0x0090 0x031C 0x0674 0x8 0x2
上面代码中共有8个UART1_RTS_B引脚的宏定义,分别对应该引脚的8个复用IO。宏定义的值,被分为了5段,每段的值都有具体的含义:
后半部分:此值就是conf_reg寄存器的值,用于设置UART1_RTS_B引脚的电气特性
1.2 gpio子系统
gpio子系统就是管理gpio功能的一个系统,其作用是初始化gpio,并提供对外的API接口。使用gpio子系统后,就无需自己操作寄存器,通过调用相关API函数即可实现对gpio的控制。
仍以热插拔节点为例,pinctrl子系统已经将UART1_RTS_B复用为GPIO1_IO19,并设置好了电气属性。驱动程序通过读取其高低电平来判断SD卡有没有插入。
那驱动程序怎么知道CD引脚连接的是GPIO1_IO19呢 ?这就需要设备树来告诉驱动了,在设备树的SD卡节点下添加一个描述CD引脚的属性:
&usdhc1 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc1>;
pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
pinctrl-3 = <&pinctrl_hog_1>;
cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
keep-power-in-suspend;
enable-sdio-wakeup;
vmmc-supply = <®_sd1_vmmc>;
status = "okay";
};
上面代码中pinctrl-3指定了CD引脚的pinctrl信息,cd-gpios属性描述了CD引脚使用哪个IO,属性值共有三个,具体含义如下:
设置好设备树后就可使用gpio子系统提供的API函数来操作指定的GPIO,gpio子系统向开发人员屏蔽了具体的读写寄存器过程。下图中有常用的API函数介,此外还有pinctrl和gpio子系统的使用模板:
**2. **pinctrl与gpio子系统字符设备驱动框架
下图为pinctrl与gpio子系统下的字符设备驱动框架:
接下来根据上面的框架图,以驱动LED (GPIO1_IO03) 为例,分步介绍具体的代码编写流程
2.1 修改设备树文件
添加pinctrl节点:在iomuxc节点的imx6ul-evk子节点下创建pinctrl_led节点,复用GPIO1_IO03
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
//MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 复用GPIO1_IO03
//0x10B0 设置pin的电气特性
添加LED设备节点:在根节点下创建LED设备节点,指定对应的pinctrl节点,指定所使用的GPIO
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "alpha-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
检查PIN是否冲突:检查pinctrl和设备节点中指定的引脚有没有被别的外设占用
//检查GPIO_IO03这个PIN有没有被其他的pinctrl节点使用
pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
//GPIO_IO03被pinctrl_tsc节点占用,因此需要屏蔽掉
/* MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0 */
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
>;
};
//检查"gpio1 3"有没有被其他设备节点占用
&tsc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc>;
//"gpio1 3"被tsc设备节点占用,因此需要屏蔽掉
/* xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; */
measure-delay-time = <0xffff>;
pre-charge-time = <0xfff>;
status = "okay";
};
编译设备树:使用make dtbs命令编译设备树,并使用该设备树启动Linux系统
#在内核根目录下
make dtbs #编译设备树
#启动Linux系统后
cd /proc/device-tree #查看"gpioled"节点是否存在
2.2 编写驱动程序
创建驱动程序文件gpioled.c,添加如下代码
宏定义及设备结构体定义
struct gpioled_dev{
dev_t devid; //设备号
struct cdev cdev; //cdev字符设备
struct class *class; //类
struct device *device; //设备
int major; //主设备号
int minor; //次设备号
struct device_node *nd; //设备节点
int led_gpio; //所使用的gpio编号
};
struct gpioled_dev gpioled; //定义led设备
编写设备操作函数:write函数中,直接使用gpio子系统提供的API函数操作GPIO
static ssize_t led_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0){
printk("kernel write failed!\\r\\n");
return -EFAULT;
}
ledstat = databuf[0];
if(ledstat == LEDON){
gpio_set_value(dev->led_gpio, 0);
}else if(ledstat == LEDOFF){
gpio_set_value(dev->led_gpio, 1);
}
return 0;
}
驱动入口函数中:获取GPIO编号后初始化
/* 设置 LED 所使用的 GPIO */
/* 1、获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
printk("gpioled node cant not found!\\r\\n");
return -EINVAL;
} else {
printk("gpioled node has been found!\\r\\n");
}
/* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(gpioled.led_gpio < 0) {
printk("can't get led-gpio");
return -EINVAL;
}
printk("led-gpio num = %d\\r\\n", gpioled.led_gpio);
/* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\\r\\n");
}
驱动入口函数中:注册字符设备驱动,代码与Linux点灯中一样
驱动出口函数中:注销设备驱动,删除类和设备,代码可参考Linux点灯一文
2.3 编写测试程序
实现操作驱动文件对外设进行控制的功能。创建测试程序文件gpioledApp.c,代码内容与Linux点灯一文中的测试程序代码一致,此处不再赘述
2.4 编译测试
编译驱动程序:当前目录下创建Makefile文件,并make编译
KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := gpioled.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译测试程序:无需内核参与,直接编译即可
arm-linux-gnueabihf-gcc gpioledApp.c -o gpioledApp
运行测试:启动开发板后,加载驱动模块,之后使用应用程序测试驱动是否正常工作
depmod #第一次加载驱动的时候需要运行此命令
modprobe gpioled.ko #加载驱动
./gpioledApp /dev/gpioled 1 #打开LED灯
./gpioledApp /dev/gpioled 0 #关闭LED灯
rmmod gpioled.ko #卸载驱动模块
全部0条评论
快来发表一下你的评论吧 !