BSP/SDK 新手小白的笔记---pinctrl 子系统的理解分享--pinctrl_dev 的生成和注册--- 关联rk3588

前言

CPU是通过读写寄存器来控制外设(除ddr等内存以外的全部设备),pad是芯片与外界沟通封装好的焊点,pin是pcb通道的出入口,pinctrl实际控制的是pad,所以pinctrl子系统极其重要。

一、pad的本质,与“跳线”的区别,pinctrl的具体作用。

物理跳线:
比如说需要将gpio3 的pin 口与一个led灯相连,要不然手动焊线要不然就pcb规划贴片好。这样通过控制gpio就能控制led灯。
逻辑跳线:
pad和“跳线”的目的是一样的,包括寄存器其实也是一种逻辑跳线,比如
在这里插入图片描述
上面图是i.mx6ull的数据手册的一个32为寄存器说明,他0到3位是MUX_MODE即复用模式,如果设置不同的值这个pad就产生不同功能,其实就走不同的功能的电路被“跳”到这个pad上。和物理跳线不同的是可以使用cpu命令逻辑上的“跳线”。

二、pinctrl 框架

1、没有pinctrl子系统之前如何写驱动

如下摘抄正点原子的i.mx6ull开发板的教学代码:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: led.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: LED驱动文件。
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/1/30 左忠凯创建
***************************************************************/
#define LED_MAJOR		200		/* 主设备号 */
#define LED_NAME		"led" 	/* 设备名字 */

#define LEDOFF 	0				/* 关灯 */
#define LEDON 	1				/* 开灯 */
 
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE				(0X020C406C)	
#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)
#define GPIO1_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);	/* 关闭LED灯 */
	}
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 设备操作函数 */
static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init led_init(void)
{
	int retvalue = 0;
	u32 val = 0;

	/* 初始化LED */
	/* 1、寄存器地址映射 */
  	IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
	SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
  	SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
	GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
	GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

	/* 2、使能GPIO1时钟 */
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);	/* 清楚以前的设置 */
	val |= (3 << 26);	/* 设置新值 */
	writel(val, IMX6U_CCM_CCGR1);

	/* 3、设置GPIO1_IO03的复用功能,将其复用为
	 *    GPIO1_IO03,最后设置IO属性。
	 */
	writel(5, SW_MUX_GPIO1_IO03);
	
	/*寄存器SW_PAD_GPIO1_IO03设置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 低转换率
	 */
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	/* 4、设置GPIO1_IO03为输出功能 */
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);	/* 清除以前的设置 */
	val |= (1 << 3);	/* 设置为输出 */
	writel(val, GPIO1_GDIR);

	/* 5、默认关闭LED */
	val = readl(GPIO1_DR);
	val |= (1 << 3);	
	writel(val, GPIO1_DR);

	/* 6、注册字符设备驱动 */
	retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
	if(retvalue < 0){
		printk("register chrdev failed!\r\n");
		return -EIO;
	}
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{
	/* 取消映射 */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	/* 注销字符设备驱动 */
	unregister_chrdev(LED_MAJOR, LED_NAME);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

如上开发步骤主要步骤:
1,找到pad、gpio的全部寄存器
2,将物理寄存器转为虚拟机地址。
3,对应的文件系统如字符的文件系统对应的_operations结构体函数对寄存器对应虚拟地址的进行操作。
会产生那些问题:
1,可能多个程序读写寄存器产生冲突。
2,移植性差。

2、pinctrl子系统框架,注册流程分享。

如下是pinctrl子系统的流程:

内核核心初始化 - pinctrl 核心框架初始化
         |
设备树解析 - 解析 pinctrl 节点  如RK3588 
         |
平台设备创建 - 自动创建 platform_device
         |
驱动探测 -rockchip_pinctrl_probe() 执行硬件初始化
         |
pinctrl 注册 - 注册到 pinctrl 核心框架
         |
sysfs 创建 - 自动创建虚拟文件系统接口
        |
默认配置应用 - 根据设备树配置默认引脚状态

如上体现了分层,解耦的思想
**第一如上流程图,是使用设备树把硬件描述和驱动分离,也是平台驱动的其中之一概念,驱动和设备设备树匹配成功后会将对应的设备树节点信息当成参数传入驱动处理。如下

dts文件
/ {
    compatible = "board-name";
    
    led {
        compatible = "gpio-leds";
        status = "okay";
        
        user-led {
            label = "user-led";
            gpios = <&gpio0 12 0>;  // 使用GPIO0的第12个引脚
            default-state = "off";
        };
    };
};
对应驱动:
// 在驱动中匹配设备树节点
static const struct of_device_id led_ids[] = {
    { .compatible = "gpio-leds" },
    { }
};

// 获取GPIO并控制
struct gpio_desc *led_gpio;
led_gpio = gpio_get(&pdev->dev, "user-led");
gpiod_direction_output(led_gpio, 1);  // 点亮LED

要是不了解如果可以话推荐看一下我主页关于设备树文章分享:
在这里插入图片描述

**第二把驱动逻辑即pinctl子系统平台(具体怎么做,实际读写寄存器)和驱动配置(配合设备树往子系统注册pad,告诉“驱动逻辑”即pintcrl 子系统硬件有那些怎么找到这些硬件)进行分离,不再是驱动直接读写寄存器。如本小节开头,

驱动探测 -rockchip_pinctrl_probe() 执行硬件初始化
         |
pinctrl 注册 - 注册到 pinctrl 核心框架

如上在整体流程中芯片厂商的工程师就是把pintrl的相关信息注册到内核的pinctrl 核心框架。大致流程如下:

rockchip_pinctrl_probe()
    ├── 分配 info 结构
    ├── 获取 rk3588_pinctrl_data
    ├── devm_platform_ioremap_resource()  // 映射寄存器
    ├── rockchip_pinctrl_parse_dt()       // 解析设备树
    ├── devm_pinctrl_register()           // 注册 pinctrl
    ├── rockchip_gpiolib_register()       // 注册 GPIO
    └── rockchip_pinctrl_create_debugfs() // 调试接口

具体的pinctrl-rockchip.c文件中probe函数:

static int rockchip_pinctrl_probe(struct platform_device *pdev)
{
	struct rockchip_pinctrl *info;
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node, *node;
	struct rockchip_pin_ctrl *ctrl;
	struct resource *res;
	void __iomem *base;
	int ret;

	if (!dev->of_node)
		return dev_err_probe(dev, -ENODEV, "device tree node not found\n");

	info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
	if (!info)
		return -ENOMEM;

	info->dev = dev;

	ctrl = rockchip_pinctrl_get_soc_data(info, pdev);
	if (!ctrl)
		return dev_err_probe(dev, -EINVAL, "driver data not available\n");
	info->ctrl = ctrl;

	node = of_parse_phandle(np, "rockchip,grf", 0);
	if (node) {
		info->regmap_base = syscon_node_to_regmap(node);
		of_node_put(node);
		if (IS_ERR(info->regmap_base))
			return PTR_ERR(info->regmap_base);
	} else {
		base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
		if (IS_ERR(base))
			return PTR_ERR(base);

		rockchip_regmap_config.max_register = resource_size(res) - 4;
		rockchip_regmap_config.name = "rockchip,pinctrl";
		info->regmap_base =
			devm_regmap_init_mmio(dev, base, &rockchip_regmap_config);

		/* to check for the old dt-bindings */
		info->reg_size = resource_size(res);

		/* Honor the old binding, with pull registers as 2nd resource */
		if (ctrl->type == RK3188 && info->reg_size < 0x200) {
			base = devm_platform_get_and_ioremap_resource(pdev, 1, &res);
			if (IS_ERR(base))
				return PTR_ERR(base);

			rockchip_regmap_config.max_register = resource_size(res) - 4;
			rockchip_regmap_config.name = "rockchip,pinctrl-pull";
			info->regmap_pull =
				devm_regmap_init_mmio(dev, base, &rockchip_regmap_config);
		}
	}

	/* try to find the optional reference to the pmu syscon */
	node = of_parse_phandle(np, "rockchip,pmu", 0);
	if (node) {
		info->regmap_pmu = syscon_node_to_regmap(node);
		of_node_put(node);
		if (IS_ERR(info->regmap_pmu))
			return PTR_ERR(info->regmap_pmu);
	}

	if (IS_ENABLED(CONFIG_CPU_RK3308) && ctrl->type == RK3308) {
		ret = rk3308_soc_data_init(info);
		if (ret)
			return ret;
	}

	ret = rockchip_pinctrl_register(pdev, info);
	if (ret)
		return ret;

	platform_set_drvdata(pdev, info);
	g_pctldev = info->pctl_dev;

	ret = of_platform_populate(np, NULL, NULL, &pdev->dev);
	if (ret)
		return dev_err_probe(dev, ret, "failed to register gpio device\n");

	dev_info(dev, "probed %s\n", dev_name(dev));

	return 0;
}

其中struct rockchip_pinctrl *info 就这个围绕运行的核心,函数大部分在初始化这个结构体体然后运行 ret = rockchip_pinctrl_register(pdev, info);结构体rockchip_pinctrl 定义如下:

struct rockchip_pinctrl {
	struct regmap			*regmap_base;
	int				reg_size;
	struct regmap			*regmap_pull;
	struct regmap			*regmap_pmu;
	struct device			*dev;
	struct rockchip_pin_ctrl	*ctrl;
	struct pinctrl_desc		pctl;
	struct pinctrl_dev		*pctl_dev;
	struct rockchip_pin_group	*groups;
	unsigned int			ngroups;
	struct rockchip_pmx_func	*functions;
	unsigned int			nfunctions;
};

rockchip_pinctrl_register(pdev, info)内容如下:

static int rockchip_pinctrl_register(struct platform_device *pdev,
					struct rockchip_pinctrl *info)
{
	struct pinctrl_desc *ctrldesc = &info->pctl;
	struct pinctrl_pin_desc *pindesc, *pdesc;
	struct rockchip_pin_bank *pin_bank;
	struct device *dev = &pdev->dev;
	int pin, bank, ret;
	int k;

	ctrldesc->name = "rockchip-pinctrl";
	ctrldesc->owner = THIS_MODULE;
	ctrldesc->pctlops = &rockchip_pctrl_ops;
	ctrldesc->pmxops = &rockchip_pmx_ops;
	ctrldesc->confops = &rockchip_pinconf_ops;

	pindesc = devm_kcalloc(dev, info->ctrl->nr_pins, sizeof(*pindesc), GFP_KERNEL);
	if (!pindesc)
		return -ENOMEM;

	ctrldesc->pins = pindesc;
	ctrldesc->npins = info->ctrl->nr_pins;

	pdesc = pindesc;
	for (bank = 0, k = 0; bank < info->ctrl->nr_banks; bank++) {
		pin_bank = &info->ctrl->pin_banks[bank];
		for (pin = 0; pin < pin_bank->nr_pins; pin++, k++) {
			pdesc->number = k;
			pdesc->name = kasprintf(GFP_KERNEL, "%s-%d",
						pin_bank->name, pin);
			pdesc++;
		}

		INIT_LIST_HEAD(&pin_bank->deferred_pins);
		mutex_init(&pin_bank->deferred_lock);
	}

	ret = rockchip_pinctrl_parse_dt(pdev, info);
	if (ret)
		return ret;

	info->pctl_dev = devm_pinctrl_register(dev, ctrldesc, info);
	if (IS_ERR(info->pctl_dev))
		return dev_err_probe(dev, PTR_ERR(info->pctl_dev), "could not register pinctrl driver\n");

	return 0;
}

如上代码可以主要目的是运行函数devm_pinctrl_register(dev, ctrldesc, info); 其中ctrldesc 就是rockchip_pinctrl *info的pctl加上一些操作函数的结构体,我们再看devm_pinctrl_register(dev, ctrldesc, info)具体内容;

struct pinctrl_dev *devm_pinctrl_register(struct device *dev,
					  struct pinctrl_desc *pctldesc,
					  void *driver_data)
{
	struct pinctrl_dev **ptr, *pctldev;

	ptr = devres_alloc(devm_pinctrl_dev_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return ERR_PTR(-ENOMEM);

	pctldev = pinctrl_register(pctldesc, dev, driver_data);
	if (IS_ERR(pctldev)) {
		devres_free(ptr);
		return pctldev;
	}

	*ptr = pctldev;
	devres_add(dev, ptr);

	return pctldev;
}

可以看出devm_pinctrl_register就是在pinctrl_register(pctldesc, dev, driver_data)基础上加上主要加上自动资源管理:设备卸载时自动调用 pinctrl_unregister等(即 自动释放分配内存等,否者需要在卸载驱动的代码需要加上pinctrl_unregister托管方式),下面是 pinctrl_register(pctldesc, dev, driver_data)具体代码:

struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
				    struct device *dev, void *driver_data)
{
	struct pinctrl_dev *pctldev;
	int error;

	pctldev = pinctrl_init_controller(pctldesc, dev, driver_data);
	if (IS_ERR(pctldev))
		return pctldev;

	error = pinctrl_enable(pctldev);
	if (error)
		return ERR_PTR(error);

	return pctldev;
}

跳转pinctrl_init_controller(pctldesc, dev, driver_data)

static struct pinctrl_dev *
pinctrl_init_controller(struct pinctrl_desc *pctldesc, struct device *dev,
			void *driver_data)
{
	struct pinctrl_dev *pctldev;
	int ret;

	if (!pctldesc)
		return ERR_PTR(-EINVAL);
	if (!pctldesc->name)
		return ERR_PTR(-EINVAL);

	pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);
	if (!pctldev)
		return ERR_PTR(-ENOMEM);

	/* Initialize pin control device struct */
	pctldev->owner = pctldesc->owner;
	pctldev->desc = pctldesc;
	pctldev->driver_data = driver_data;
	INIT_RADIX_TREE(&pctldev->pin_desc_tree, GFP_KERNEL);
#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
	INIT_RADIX_TREE(&pctldev->pin_group_tree, GFP_KERNEL);
#endif
#ifdef CONFIG_GENERIC_PINMUX_FUNCTIONS
	INIT_RADIX_TREE(&pctldev->pin_function_tree, GFP_KERNEL);
#endif
	INIT_LIST_HEAD(&pctldev->gpio_ranges);
	INIT_LIST_HEAD(&pctldev->node);
	pctldev->dev = dev;
	mutex_init(&pctldev->mutex);

	/* check core ops for sanity */
	ret = pinctrl_check_ops(pctldev);
	if (ret) {
		dev_err(dev, "pinctrl ops lacks necessary functions\n");
		goto out_err;
	}

	/* If we're implementing pinmuxing, check the ops for sanity */
	if (pctldesc->pmxops) {
		ret = pinmux_check_ops(pctldev);
		if (ret)
			goto out_err;
	}

	/* If we're implementing pinconfig, check the ops for sanity */
	if (pctldesc->confops) {
		ret = pinconf_check_ops(pctldev);
		if (ret)
			goto out_err;
	}

	/* Register all the pins */
	dev_dbg(dev, "try to register %d pins ...\n",  pctldesc->npins);
	ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);
	if (ret) {
		dev_err(dev, "error during pin registration\n");
		pinctrl_free_pindescs(pctldev, pctldesc->pins,
				      pctldesc->npins);
		goto out_err;
	}

	return pctldev;

out_err:
	mutex_destroy(&pctldev->mutex);
	kfree(pctldev);
	return ERR_PTR(ret);
}

如上代码可以看出实际上面的全部过程就是将厂商芯片的pad三个相关信息注册到pinctrl平台
pinctrl_desc *pctldesc:pinctrl的标准描述有多少pad 操作函数等
struct device *dev:平台设备结构体的device,
void *driver_data:就是厂商的私有数据需要通过pinctrl_desc *pctldesc具体操作函数处理;

总结

上述主要分享的是bsp开发工程师注册pinctr的大致流程。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值