一、Pinctl子系统
借助pinctl设置引脚的复用与电气属性的设置。我们之前的实验是直接操作相应的寄存器,但是这种配置比较繁琐,且容易出现问题。为了解决,引入pinctl子系统:获取设备树的引脚信息,通过获取的引脚信息来设置复用及电气特性。
在参数手册中,IOMUXC部分中有如下三种控制器的寄存器。在设备树当中分别存储了控制器的首地址。
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
gpr: iomuxc-gpr@020e4000 {
compatible = "fsl,imx6ul-iomuxc-gpr",
"fsl,imx6q-iomuxc-gpr", "syscon";
reg = <0x020e4000 0x4000>;
};
iomuxc_snvs: iomuxc-snvs@02290000 {
compatible = "fsl,imx6ull-iomuxc-snvs";
reg = <0x02290000 0x10000>;
};
我举一个使用GPIO01_04的LED接口的例子。在imx6ull-14x14-ddr3-arm2.dts文件中有一个结点定义到了。那么问题来了,它是什么呢,其实在该文件的头文件中跟踪,可以跟踪到imx6ul-pinfunc.c中有定义。
它代表五个值,分别:
寄存器的根节点的偏移地址,
电气设置的偏移地址,
输入寄存器的寄存器,
复用模式,
输入寄存器中填写的值。
pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
>;
};
/*<mux_reg conf_reg input_reg mux_mode input_val>*/
#define MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x006C 0x02F8 0x0000 0x5 0x0
1.pincrtl驱动
找到pincrtl子系统的驱动程序,它是厂商做的部分。设备树如何找到驱动呢?通过compatible属性将设备树与.c驱动文件进行匹配。因此我们可以进行全局搜索字符串的方法来找到pinctl驱动程序。
匹配以后,执行probe函数。probe执行与之匹配设备的函数。
二、GPIO子系统
使用GPIO的框架。作为GPIO功能就要使用GPIO的子系统。我们以前的程序使用了大量手动映射寄存器,很是麻烦,使用gpio框架的子系统后,提供了很多操作gpio的函数,对于开发方便了很多。
1、设置GPIO
就拿SD卡的结点做例子。
其中**cd-gpios = <&gpio1 1 GPIO_ACTIVE_LOW>; **这一行很重要。描述SD卡的CD引脚的gpio1_1的IO口,GPIO_ACTIVE_LOW = 1<<0代表低电平有效。
&usdhc1 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc1>;
pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
/*cd-gpios = <&gpio1 1 GPIO_ACTIVE_LOW>;*/
keep-power-in-suspend;
enable-sdio-wakeup;
vmmc-supply = <®_wifi_vmmc>;
status = "okay";
};
2、如何使用cd-gpios信息
使用gpio相关的of函数。
1.获取设备结点
2.获取gpio编号, of_get_named_gpio函数。
3.请求编号的gpio gpio_request
4.设置gpio输入输出, gpio_dierction_input/gpio_dierction_output
5.如果是输入,gpio,通过gpio_get_value读取GPIO ;如果是输出,通过gpio_set_value设置GPIO
6.最后释放 gpio_free。
3、GPIO驱动
在drivers/gpio-xxx.c文件下对应的最底层的部分。
gpiolib-xxx.c文件是中间文件用于底层与应用层文件进行交互。
现在暂且不去分析了。
三、写驱动
1.设备树修改
写入pinctl的信息以及gpio子系统的信息。
其中重要的是pinctrl-0信息,led-gpios的信息。其实分别就是pinctrl子系统与gpio子系统了。
gpioled{
compatible = "yifan,gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpioled>;
led-gpios = <&gpio1 4 GPIO_ACTIVE_LOW>, /* red */
<&gpio4 20 GPIO_ACTIVE_LOW>, /* green */
<&gpio4 19 GPIO_ACTIVE_LOW>; /* blue */
status = "okay";
};
};
pinctrl_gpioled:ledgrp{
fsl,pins = <
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x10b0
MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x10b0
MX6UL_PAD_CSI_VSYNC__GPIO4_IO19 0x10b0
>;
};
2.搭建驱动框架
不详细介绍了,总之就是注册注销模块。构建fops结构体,填充指针函数。
3.使用GPIO的of函数
1.首先根据路径获取设备结点,我觉得这样方便一些。
2.根据设备树获取led对应的编号。
3.根据led编号申请IO。
4.设置IO为输出,并且赋初值。
/*设备结构体*/
struct gpio_led{
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
struct cdev cdev; /*字符设备*/
struct class *class; /*设备的类*/
struct device *device;/*设备*/
struct device_node *nd;/*设备结点*/
int led_gpio[3]; /*IO的编号*/
char *name[10]; /*IO申请名称*/
};
struct gpio_led led;
/* 1.获取设备结点 */
led.nd = of_find_node_by_path("/gpioled");
if(led.nd == NULL){
ret = -EINVAL;
goto failed_findnd;
}
/* 2.获取Led对应的GPIO */
for(i = 0 ; i < 3 ; i++ ){
led.led_gpio[i] = of_get_named_gpio(led.nd , "led-gpios" , i);
if(led.led_gpio[i] < 0){
printk("led.led_gpio%d < 0\r\n" ,i );
ret = -EINVAL;
goto failed_findnd;
}
printk("led.led_gpio[%d] = %d\r\n" , i ,led.led_gpio[i] );
/* 3.申请IO */
led.name[i]="---------";
sprintf(led.name[i], "led-gpios");
printk("%s\r\n" , led.name[i]);
ret = gpio_request(led.led_gpio[i], led.name[i]);
if (ret) {
printk("failed to request GPIO for LED\n");
ret = -EINVAL;
goto failed_findnd;
}
/* 4.使用IO,设置为输出 */
ret = gpio_direction_output(led.led_gpio[i] ,1 );
if(ret < 0){
goto failed_setio;
}
//printk("led.led_gpio[%d] is close\r\n" , i);
/* 5.输出电平 ,点亮LED灯 */
gpio_set_value(led.led_gpio[i] , 0);
}
return 0;
failed_setio:
for(i = 0; i< 3 ; i++)
gpio_free(led.led_gpio[i]);
printk("failed_request\r\n");
failed_findnd:
device_destroy(led.class , led.devid);
printk("failed_findnd\r\n");
failed_device:
class_destroy(led.class);
printk("failed_device\r\n");
failed_class:
cdev_del(&led.cdev);
printk("failed_class\r\n");
failed_cdev:
unregister_chrdev_region(led.devid , LED_CNT);
printk("failed_cdev\r\n");
failed_devid:
return ret;
4.应用接口函数
与以往的程序相差无几。只需要使用gpio_set_value,原型如下。
static inline void gpio_set_value(unsigned gpio, int value);
\