pinctrl子系统初始化RGB灯

文章目录

  • 系列文章目录
  • 前言
  • 正文
    • 引入
    • iomuxc节点:pinctrl子系统初窥
      • iomuxc节点
      • 节点引脚配置方式
      • 节点配置信息记录
    • pinctrl子系统实验:RGB灯引脚初始化
      • platform设备引脚初始化
      • RGB灯引脚状态初始化
        • iomuxc节点添加引脚配置信息
        • rgb_led节点添加引脚状态
    • 代码示例
  • 总结

系列文章目录

Linux字符设备驱动详解
Linux字符设备驱动详解二(使用设备驱动模型)
Linux字符设备驱动详解三(使用class)
Linux字符设备驱动详解四(使用自属的xbus驱动总线)
Linux字符设备驱动详解五(使用platform虚拟平台总线)
Linux字符设备驱动详解六(设备树实现RGB灯驱动)
Linux字符设备驱动详解七(“插件“设备树实现RGB灯驱动)

前言

通过前面的七篇文章,我们学习到在控制一个硬件外设的时候,需要获取外设所使用的的GPIO,然后去查询芯片手册相关的寄存器,然后通过控制寄存器才能控制相应的外设。使用直接操作寄存器的方法编写驱动,很明显这样子的开发方式效率很低,而且写程序也很麻烦,于是引入pinctrl子系统来解决这个问题。

正文

引入

ARM SoC提供了十分丰富的硬件接口,而接口物理上的表现就是一个个的pin(或者叫做pad, finger等)。为了实现丰富的硬件功能,SoC的pin需要实现复用功能,即单独的pin需要提供不同功能,例如,pin0既可以作为GPIO,可以也用于i2c的SCL,通过pin相关的复用寄存器来切换不同的功能。除此之外,软件还可以通过寄存器配置pin相关的电气特性,例如,上拉/下拉、驱动能力、开漏等。

Linux kernel 3.0之前的内核,对于pin的功能配置都是通过目标板的配置文件(arch/arm/mach-*)来初始化的,这种配置方式比较繁琐,十分容易出现问题(例如,pin的功能配置冲突)。所以,Linux kernel 3.0之后,实现了DT的板级配置信息管理机制,大大改善了对于pin的配置方式,随之一起实现的就是pinctrl子系统。

pinctrl子系统主要负责以下功能:
1、枚举、命名通过板级DTS配置的所有pin;
2、对于pin实现复用功能;
3、配置pin的电器特性,例如,上拉/下拉、驱动能力、开漏等。

进一步理解,无论是哪种芯片,都有类似下图的结构:
pinctrl子系统初始化RGB灯_第1张图片
要想让pinA、B用于GPIO,需要设置IOMUX让它们连接到GPIO模块,要想让pinA、B用于I2C,需要设置IOMUX让它们连接到I2C模块。

所以GPIO、I2C应该是并列的关系,它们能够使用之前,需要设置IOMUX。有时候并不仅仅是设置IOMUX,还要配置引脚,比如上拉、下拉、开漏等等。

现在的芯片动辄几百个引脚,在使用到GPIO功能时,让你一个引脚一个引脚去找对应的寄存器很明显不行。术业有专攻,这些累活就让芯片厂家的BSP工程师。作为驱动工程师需要在他们的基础上开发。

所以,要把引脚的复用、配置抽出来,做成Pinctrl子系统,给GPIO、I2C等模块使用。BSP工程师要做什么?如下图:
pinctrl子系统初始化RGB灯_第2张图片
等BSP工程师在GPIO子系统、Pinctrl子系统中把自家芯片的支持加进去后,我们就可以非常方便地使用这些引脚了。

GPIO模块在图中跟I2C不是并列的吗?干嘛在讲Pinctrl时还把GPIO子系统拉进来?大多数的芯片,没有单独的IOMUX模块,引脚的复用、配置等等,就是在GPIO模块内部实现的。在硬件上GPIO和Pinctrl是如此密切相关,在软件上它们的关系也非常密切。所以这2个子系统一般都一起讲解。

关于这两个子系统之间的关系,这两个子系统是软件上面的概念,属于Linux内核的一部分。但最终要用起来,都是要与实际硬件挂钩,具体理解可以参照下图:
pinctrl子系统初始化RGB灯_第3张图片
我们要操控一个引脚需要配置两个模块的寄存器:GPIO模块及IOMUXC模块。IOMUXC模块是用来配置引脚功能及一些引脚参数(引脚速率、上下拉等);GPIO模块用于配置引脚的输入输出等。其中,pinctrl子系统管理的是IOMUXC模块;gpio子系统管理的是GPIO模块。

所以综上来说,在嵌入式Linux开发中,像前面笔记中的那几种led驱动方式(与寄存器打交道)基本上是用不上的。Linux内核提供了pinctrl 和 gpio 子系统用于引脚的驱动,这样我们可以避免与寄存器打交道。

iomuxc节点:pinctrl子系统初窥

  • 汇总所需引脚的配置信息
  • pinctrl子系统预存iomux节点信息

iomuxc节点

imx6ull.dtsi

iomuxc: iomuxc@20e0000 {
     
				compatible = "fsl,imx6ul-iomuxc";
				reg = <0x20e0000 0x4000>;
			};
  • compatible:与pinctrl子系统的平台驱动做匹配
  • reg:引脚配置寄存器的基地址

imx6ull-seeed-npi.dts

&iomuxc {
     
	pinctrl-names = "default""init","sleep";
	pinctrl-0 = <&pinctrl_hog_1>;
	pinctrl-1 =<&xxx>;
	pinctrl-2 =<&yyy>;
...
	pinctrl_uart1: uart1grp {
     
		fsl,pins = <
			MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
			MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
		>;
	};
...
}

节点引脚配置方式

  • pinctrl-names:定义引脚状态
  • pinctrl-0:定义第0种状态需要使用到的引脚,可引用其他节点标识
  • pinctrl-1:定义第1种状态需要使用到的引脚,以此类推

节点配置信息记录

fsl,pins

  • 结合imx6ull的pinctrl子系统驱动使用
  • 以该属性来标识引脚的配置信息

fsl,pins属性值
一个宏+一个十六进制数

MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1

宏定义原型
imx6ull.dtsi ->#include “imx6ull-pinfunc.h”->#include “imx6ul-pinfunc.h”

#define MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x0084 0x0310 0x0000 0 0

宏值含义

<mux_reg conf_reg input_reg mux_mode input_val>
 0x0084    0x0310    0x0000     0x0      0x0

mux_reg:引脚复用设置寄存器
pinctrl子系统初始化RGB灯_第4张图片
conf_reg:引脚属性设置寄存器
pinctrl子系统初始化RGB灯_第5张图片input_reg:引脚输入设置寄存器
pinctrl子系统初始化RGB灯_第6张图片

  • 引脚需要输入功能时设置
  • mux_mode:复用寄存器设置值
  • 设置引脚复用
  • input_value:输入寄存器设置值
    • 设置引脚输入特性

十六进制数
属性寄存器设置值

  • 特性复杂,独立设置

pinctrl子系统实验:RGB灯引脚初始化

platform设备引脚初始化

注册平台设备或者平台驱动

RGB灯引脚状态初始化

相比较前两篇文章,即相比较设备树和插件设备树,通过pinctrl子系统点灯,传统设备树imx6ull-speed-npi.dts文件多了以下两部分

iomuxc节点添加引脚配置信息

pinctrl_rgb_led:rgb_led{
     
                    fsl,pins = <
                            MX6UL_PAD_GPIO1_IO04__GPIO1_IO04    0x000010B1
                            MX6UL_PAD_CSI_HSYNC__GPIO4_IO20     0x000010B1
                            MX6UL_PAD_CSI_VSYNC__GPIO4_IO19     0x000010B1
                    >;
            };

rgb_led节点添加引脚状态

pinctrl-names = "default";
 pinctrl-0 = <&pinctrl_rgb_led>;

代码示例

以野火代码为例
dts_led.c文件删去了引脚配置初始化相关的代码,如下(实际只能删去红色框中代码,全删对本文测试无影响),之所以删去是因为初始化的部分工作我们通过iomuxc节点和pinctrl子系统机制帮助我们完成了。
pinctrl子系统初始化RGB灯_第7张图片
dts_led.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

/*------------------字符设备内容----------------------*/
#define DEV_NAME "rgb_led"
#define DEV_CNT (1)

/*定义 led 资源结构体,保存获取得到的节点信息以及转换后的虚拟寄存器地址*/
struct led_resource
{
     
	struct device_node *device_node; //rgb_led_red的设备树节点
	void __iomem *virtual_CCM_CCGR;
	void __iomem *virtual_IOMUXC_SW_MUX_CTL_PAD;
	void __iomem *virtual_IOMUXC_SW_PAD_CTL_PAD;
	void __iomem *virtual_DR;
	void __iomem *virtual_GDIR;
};

static dev_t led_devno;					 //定义字符设备的设备号
static struct cdev led_chr_dev;			 //定义字符设备结构体chr_dev
struct class *class_led;				 //保存创建的类
struct device *device;					 // 保存创建的设备
struct device_node *rgb_led_device_node; //rgb_led的设备树节点结构体

/*定义 R G B 三个灯的led_resource 结构体,保存获取得到的节点信息*/
struct led_resource led_red;
struct led_resource led_green;
struct led_resource led_blue;

/*字符设备操作函数集,open函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
     
	printk("\n open form driver \n");
	return 0;
}

/*字符设备操作函数集,write函数*/
static ssize_t led_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
     

	int ret,error;
	unsigned int register_data = 0; //暂存读取得到的寄存器数据
	unsigned char receive_data[10]; //用于保存接收到的数据
	unsigned int write_data; //用于保存接收到的数据

	if(cnt>10)
			cnt =10;

	error = copy_from_user(receive_data, buf, cnt);
	if (error < 0)
	{
     
		return -1;
	}

	ret = kstrtoint(receive_data, 16, &write_data);
	if (ret) {
     
		return -1;
        }

	/*设置 GPIO1_04 输出电平*/
	if (write_data & 0x04)
	{
     
		register_data = ioread32(led_red.virtual_DR);
		register_data &= ~(0x01 << 4);
		iowrite32(register_data, led_red.virtual_DR); // GPIO1_04引脚输出低电平,红灯亮
	}
	else
	{
     
		register_data = ioread32(led_red.virtual_DR);
		register_data |= (0x01 << 4);
		iowrite32(register_data, led_red.virtual_DR); // GPIO1_04引脚输出高电平,红灯灭
	}

	/*设置 GPIO4_20 输出电平*/
	if (write_data & 0x02)
	{
     
		register_data = ioread32(led_green.virtual_DR);
		register_data &= ~(0x01 << 20);
		iowrite32(register_data, led_green.virtual_DR); // GPIO4_20引脚输出低电平,绿灯亮
	}
	else
	{
     
		register_data = ioread32(led_green.virtual_DR);
		register_data |= (0x01 << 20);
		iowrite32(register_data, led_green.virtual_DR); // GPIO4_20引脚输出高电平,绿灯灭
	}

	/*设置 GPIO4_19 输出电平*/
	if (write_data & 0x01)
	{
     
		register_data = ioread32(led_blue.virtual_DR);
		register_data &= ~(0x01 << 19);
		iowrite32(register_data, led_blue.virtual_DR); //GPIO4_19引脚输出低电平,蓝灯亮
	}
	else
	{
     
		register_data = ioread32(led_blue.virtual_DR);
		register_data |= (0x01 << 19);
		iowrite32(register_data, led_blue.virtual_DR); //GPIO4_19引脚输出高电平,蓝灯灭
	}

	return cnt;
}

/*字符设备操作函数集*/
static struct file_operations led_chr_dev_fops =
	{
     
		.owner = THIS_MODULE,
		.open = led_chr_dev_open,
		.write = led_chr_dev_write,
};

/*----------------平台驱动函数集-----------------*/
static int led_probe(struct platform_device *pdv)
{
     

	int ret = -1; //保存错误状态码
	unsigned int register_data = 0;

	printk(KERN_ALERT "\t  match successed  \n");

	/*获取rgb_led的设备树节点*/
	rgb_led_device_node = of_find_node_by_path("/rgb_led");
	if (rgb_led_device_node == NULL)
	{
     
		printk(KERN_ERR "\t  get rgb_led failed!  \n");
		return -1;
	}

	/*获取rgb_led节点的红灯子节点*/
	led_red.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_red");
	if (led_red.device_node == NULL)
	{
     
		printk(KERN_ERR "\n get rgb_led_red_device_node failed ! \n");
		return -1;
	}


	/*获取 reg 属性并转化为虚拟地址*/
	led_red.virtual_CCM_CCGR = of_iomap(led_red.device_node, 0);
	led_red.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_red.device_node, 1);
	led_red.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_red.device_node, 2);
	led_red.virtual_DR = of_iomap(led_red.device_node, 3);
	led_red.virtual_GDIR = of_iomap(led_red.device_node, 4);


	/*获取rgb_led节点的绿灯子节点*/
	led_green.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_green");
	if (led_green.device_node == NULL)
	{
     
		printk(KERN_ERR "\n get rgb_led_green_device_node failed ! \n");
		return -1;
	}

	/*获取 reg 属性并转化为虚拟地址*/
	led_green.virtual_CCM_CCGR = of_iomap(led_green.device_node, 0);
	led_green.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_green.device_node, 1);
	led_green.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_green.device_node, 2);
	led_green.virtual_DR = of_iomap(led_green.device_node, 3);
	led_green.virtual_GDIR = of_iomap(led_green.device_node, 4);

	
	/*获取rgb_led节点的蓝灯子节点*/
	led_blue.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_blue");
	if (led_blue.device_node == NULL)
	{
     
		printk(KERN_ERR "\n get rgb_led_blue_device_node failed ! \n");
		return -1;
	}

	/*获取 reg 属性并转化为虚拟地址*/
	led_blue.virtual_CCM_CCGR = of_iomap(led_blue.device_node, 0);
	led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_blue.device_node, 1);
	led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_blue.device_node, 2);
	led_blue.virtual_DR = of_iomap(led_blue.device_node, 3);
	led_blue.virtual_GDIR = of_iomap(led_blue.device_node, 4);


	/*---------------------注册 字符设备部分-----------------*/

	//第一步
	//采用动态分配的方式,获取设备编号,次设备号为0,
	//设备名称为rgb-leds,可通过命令cat  /proc/devices查看
	//DEV_CNT为1,当前只申请一个设备编号
	ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
	if (ret < 0)
	{
     
		printk("fail to alloc led_devno\n");
		goto alloc_err;
	}
	//第二步
	//关联字符设备结构体cdev与文件操作结构体file_operations
	led_chr_dev.owner = THIS_MODULE;
	cdev_init(&led_chr_dev, &led_chr_dev_fops);
	//第三步
	//添加设备至cdev_map散列表中
	ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);
	if (ret < 0)
	{
     
		printk("fail to add cdev\n");
		goto add_err;
	}

	//第四步
	/*创建类 */
	class_led = class_create(THIS_MODULE, DEV_NAME);

	/*创建设备*/
	device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);

	return 0;

add_err:
	//添加设备失败时,需要注销设备号
	unregister_chrdev_region(led_devno, DEV_CNT);
	printk("\n error! \n");
alloc_err:

	return -1;
}

static const struct of_device_id rgb_led[] = {
     
	{
     .compatible = "fire,rgb_led"},
	{
     /* sentinel */}};

/*定义平台设备结构体*/
struct platform_driver led_platform_driver = {
     
	.probe = led_probe,
	.driver = {
     
		.name = "rgb-leds-platform",
		.owner = THIS_MODULE,
		.of_match_table = rgb_led,
	}};

/*
*驱动初始化函数
*/
static int __init led_platform_driver_init(void)
{
     
	int DriverState;
	DriverState = platform_driver_register(&led_platform_driver);
	printk(KERN_ALERT "\tDriverState is %d\n", DriverState);
	return 0;
}

/*
*驱动注销函数
*/
static void __exit led_platform_driver_exit(void)
{
     
	/*取消物理地址映射到虚拟地址*/
	iounmap(led_green.virtual_CCM_CCGR);
	iounmap(led_green.virtual_IOMUXC_SW_MUX_CTL_PAD);
	iounmap(led_green.virtual_IOMUXC_SW_PAD_CTL_PAD);
	iounmap(led_green.virtual_DR);
	iounmap(led_green.virtual_GDIR);

	iounmap(led_red.virtual_CCM_CCGR);
	iounmap(led_red.virtual_IOMUXC_SW_MUX_CTL_PAD);
	iounmap(led_red.virtual_IOMUXC_SW_PAD_CTL_PAD);
	iounmap(led_red.virtual_DR);
	iounmap(led_red.virtual_GDIR);

	iounmap(led_blue.virtual_CCM_CCGR);
	iounmap(led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD);
	iounmap(led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD);
	iounmap(led_blue.virtual_DR);
	iounmap(led_blue.virtual_GDIR);

	/*删除设备*/
	device_destroy(class_led, led_devno);		  //清除设备
	class_destroy(class_led);					  //清除类
	cdev_del(&led_chr_dev);						  //清除设备号
	unregister_chrdev_region(led_devno, DEV_CNT); //取消注册字符设备

	/*注销字符设备*/
	platform_driver_unregister(&led_platform_driver);

	printk(KERN_ALERT "led_platform_driver exit!\n");
}

module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);

MODULE_LICENSE("GPL");

/**/

总结

同样的,将传统设备树imx6ull-speed-npi.dts编译为rgb.dtbo插件设备树,将其加载到内核中,加载完成后就使用以下命令查看设备树,此时可以在设备数中看到新增了rgb_led节点。

ls /sys/firmware/devicetree/base
或者
ls /proc/device-tree

再编译dts_led.c源文件为dto.led.ko内核模块并加载进内核。这时就有了/dev/rgb_led节点,最后向/dev/rgb_led节点写入数据就能控制rgb灯了。

sudo sh -c "ecoh '1' >/dev/rgb_led"
亮蓝灯
sudo sh -c "ecoh '2' >/dev/rgb_led"
亮绿灯
sudo sh -c "ecoh '4' >/dev/rgb_led"
亮红灯
sudo sh -c "ecoh '7' >/dev/rgb_led"
全亮

参考:
【深度】GPIO和Pinctrl子系统的使用–韦东山
【Linux笔记】Pinctrl子系统与GPIO子系统–嵌入式大杂烩
Linux内核之pinctrl子系统
L2.Pinctrl子系统

你可能感兴趣的:(Linux,#,驱动开发,嵌入式,linux,驱动开发)