嵌入式Linux驱动笔记(二十一)------GPIO和Pinctrl子系统的分析和思考

2023-05-16

你好!这里是风筝的博客,

欢迎和我一起交流。


好久都没有写东西了,最近来广州某公司实习,顺便记录下吧。
吐槽下,因为是二级保密单位,公司里电脑不给联网,贼难受。。。。。。

不过第一次接触真正的产品开发,正式的工程项目,还是有很多值得我学习的地方的。
公司用的是联芯的一套方案,分配电脑后,师傅给了我一个简单的任务:给一台手机(Android6.0)移植光线&距离传感器驱动和充电呼吸灯芯片的驱动。
因为是移植,所以,,,,,,,,我觉得我可以叫设备树工程师…
任务很简单,但是我却对代码陷入了沉思。

当我拿到一份代码方案时,最简单的,操作GPIO。
当我最初学习的时候,用的是S3C2440,从网上很多资料还是知道GPIO的宏,如:S3C2410_GPF(4)

在我使用全志 H3的芯片,大部分我都是直接操作寄存器了,而且网上也有资料可循。
现在我在公司接触的这块芯片,网上根本没有资料,资料就是厂家提供的说明文档,那以操作GPIO为例,我该怎么操作一个GPIO呢?

其实有经验的人都知道:模仿!
看工程下别人写的代码就知道了,这算是一种取巧捷径,那我们现在是思考,该怎么做呢?
因为公司是完全要求保密的,代码我也拷不出来,我就以我手上的sun8i-h3代码分析吧:
首先,打开手册,查找芯片手册里关于GPIO的章节:
address
这里我们可以看出GPIO的基地址是:0x01C20800
像我公司里用的芯片,是普通GPIO有一个基地址,可复用的GPIO又是另一个基地址,所以有gpio节点和pinctrl节点。
但是现在我们sun8i-h3都放在一个基地址了,所以gpio、pinctrl描述的是同一个节点。
我们去内核搜索一下,grep一下0x01C20800这个地址即可:
pio
接着我们可以去找下他的compatible属性,搜索一下 “&pio”很快就找到:
compatible
compatible属性是:“allwinner,sun8i-h3-pinctrl”
那么可以安心去找他的代码了:

static const struct sunxi_pinctrl_desc sun8i_h3_pinctrl_data = {
	.pins = sun8i_h3_pins,//数组内容放在pins字段
	.npins = ARRAY_SIZE(sun8i_h3_pins),
	.irq_banks = 2,
	.irq_read_needs_mux = true
};

static int sun8i_h3_pinctrl_probe(struct platform_device *pdev)
{
	return sunxi_pinctrl_init(pdev,
				  &sun8i_h3_pinctrl_data);
}

static const struct of_device_id sun8i_h3_pinctrl_match[] = {
	{ .compatible = "allwinner,sun8i-h3-pinctrl", },
	{}
};

static struct platform_driver sun8i_h3_pinctrl_driver = {
	.probe	= sun8i_h3_pinctrl_probe,
	.driver	= {
		.name		= "sun8i-h3-pinctrl",
		.of_match_table	= sun8i_h3_pinctrl_match,
	},
};

这里面有个数组需要注意一下,那就是:sun8i_h3_pins

static const struct sunxi_desc_pin sun8i_h3_pins[] = {
	SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 0),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart2"),		/* TX */
		  SUNXI_FUNCTION(0x3, "jtag"),		/* MS */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 0)),	/* PA_EINT0 */
	SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 1),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart2"),		/* RX */
		  SUNXI_FUNCTION(0x3, "jtag"),		/* CK */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 1)),	/* PA_EINT1 */
	SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 2),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart2"),		/* RTS */
		  SUNXI_FUNCTION(0x3, "jtag"),		/* DO */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 2)),	/* PA_EINT2 */
	SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 3),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart2"),		/* CTS */
		  SUNXI_FUNCTION(0x3, "jtag"),		/* DI */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 3)),	/* PA_EINT3 */
	SUNXI_PIN(SUNXI_PINCTRL_PIN(A, 4),
		  SUNXI_FUNCTION(0x0, "gpio_in"),
		  SUNXI_FUNCTION(0x1, "gpio_out"),
		  SUNXI_FUNCTION(0x2, "uart0"),		/* TX */
		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 4)),	/* PA_EINT4 */
	//......		  
}

看到那些"管脚映射结构体"数组里的参数,A0、A1…是不是觉得有有那么一丝丝熟悉?
里面的参数可以解读为:以A6为例
配置0x0为输入,0x1为输出,0x2为复用为串口,0x6为中断线配置,和芯片手册上是完全对应的!
进入probe函数看,挑一些重点的来写吧:

int sunxi_pinctrl_init_with_variant(struct platform_device *pdev,
				    const struct sunxi_pinctrl_desc *desc,
				    unsigned long variant)
{
	pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL);
	//......
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	pctl->membase = devm_ioremap_resource(&pdev->dev, res);//基地址,也就是0x01C20800 
	pctl->dev = &pdev->dev;
	pctl->desc = desc;
	pctl->variant = variant;
	//......
	ret = sunxi_pinctrl_build_state(pdev);//重要!和pinctrl子系统有关,懒得分析了,后面给链接,讲得挺好的
	//......
	for (i = 0, pin_idx = 0; i < pctl->desc->npins; i++) {//取出数组内容
		const struct sunxi_desc_pin *pin = pctl->desc->pins + i;//数组内容
		if (pin->variant && !(pctl->variant & pin->variant))
			continue;

		pins[pin_idx++] = pin->pin;
	}
	//......
	pctrl_desc->name = dev_name(&pdev->dev);
	pctrl_desc->owner = THIS_MODULE;
	pctrl_desc->pins = pins;//里面就是之前数组里的内容
	pctrl_desc->npins = pctl->ngroups;
	pctrl_desc->confops = &sunxi_pconf_ops;//和pinctrl里pinctrl_select_state函数使用有关,即(pinctrl_select_state)
	pctrl_desc->pctlops = &sunxi_pctrl_ops;//和pinctrl里pinctrl_select_state函数使用有关
	pctrl_desc->pmxops =  &sunxi_pmx_ops;//和pinctrl里pinctrl_select_state函数使用有关

	pctl->pctl_dev = devm_pinctrl_register(&pdev->dev, pctrl_desc, pctl);//和pinctrl有关,注册
	//......
	last_pin = pctl->desc->pins[pctl->desc->npins - 1].pin.number;
	pctl->chip->owner = THIS_MODULE;
	pctl->chip->request = gpiochip_generic_request,
	pctl->chip->free = gpiochip_generic_free,
	pctl->chip->direction_input = sunxi_pinctrl_gpio_direction_input,
	pctl->chip->direction_output = sunxi_pinctrl_gpio_direction_output,
	pctl->chip->get = sunxi_pinctrl_gpio_get,
	pctl->chip->set = sunxi_pinctrl_gpio_set,
	pctl->chip->of_xlate = sunxi_pinctrl_gpio_of_xlate,
	pctl->chip->to_irq = sunxi_pinctrl_gpio_to_irq,
	pctl->chip->of_gpio_n_cells = 3,//cells参数个数
	pctl->chip->can_sleep = false,
	pctl->chip->ngpio = round_up(last_pin, PINS_PER_BANK) -
			    pctl->desc->pin_base;
	pctl->chip->label = dev_name(&pdev->dev);
	pctl->chip->parent = &pdev->dev;
	pctl->chip->base = pctl->desc->pin_base;

	ret = gpiochip_add_data(pctl->chip, pctl);

	for (i = 0; i < pctl->desc->npins; i++) {
		const struct sunxi_desc_pin *pin = pctl->desc->pins + i;

		ret = gpiochip_add_pin_range(pctl->chip, dev_name(&pdev->dev),
					     pin->pin.number - pctl->desc->pin_base,
					     pin->pin.number, 1);//建立动态链表chip->pin_ranges,node为gpio_pin_range,其成员range包含pin脚基地址。
		if (ret)
			goto gpiochip_error;
	}
	//后面就是一些和irq相关的东西了
}

可以看出,这个函数,一开始
a)把GPIO的基地址取出来,放到pctl->membase里。

b)sunxi_pinctrl_build_state函数,这是和pinctrl子系统有关的,里面差不多是:将pctl->desc的子项数据(即sunxi_pinctrl_desc数据)复制到pctl相应子项(sunxi_pinctrl结构),文章末尾我会放一篇文章链接,讲的还不错,看那个即可。

c)把之前描述GPIO的数组放到pctrl_desc结构体中,以及填充confops、pctlops和pmxops三个字段,这三个字段就厉害了,当我们使用pinctrl_select_state(pinctrl的API)函数时就是调用的这些!

d)填充pctl->chip里面的各个字段,其中字段:direction_input、direction_output和get、set字段是我们所熟悉的,当我们使用GPIO的函数,如:GPIO_SET_VALUE或者GPIO_GET_VALUE时就是调用到里面的回调函数!还有注意of_xlate这个字段,我们在获取设备树节点时也要用到!而且其中of_gpio_n_cells字段也让我们知道,设备树里描述GPIO的属性是三个参数。

e)调用gpiochip_add_data,其实如果我们不是从设备树里通过compatible属性来反推代码的话,也可以直接搜索这个函数,因为每个平台都会调用这个函数来添加GPIO,这里就是GPIO驱动将通过chip来与pinctrl联系(bsp工程师通过gpiochip_add将gpio chip添加到gpio子系统中)

举个例子,当我们要从设备树获取GPIO时,API为:
of_get_named_gpio_flags函数

int of_get_named_gpio_flags(struct device_node *np, const char *list_name,
			    int index, enum of_gpio_flags *flags)
{
	struct gpio_desc *desc;

	desc = of_get_named_gpiod_flags(np, list_name, index, flags);//获取描述

	if (IS_ERR(desc))
		return PTR_ERR(desc);
	else
		return desc_to_gpio(desc);//这里才是通过描述得到具体的GPIO
}

其中

struct gpio_desc *of_get_named_gpiod_flags(struct device_node *np,
		     const char *propname, int index, enum of_gpio_flags *flags)
{
	ret = of_parse_phandle_with_args(np, propname, "#gpio-cells", index,
					 &gpiospec);//获取参数,存放在gpiospec里
	chip = of_find_gpiochip_by_xlate(&gpiospec);
	desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags);
	return desc;
}

重点就在of_xlate_and_get_gpiod_flags函数里:

static struct gpio_desc *of_xlate_and_get_gpiod_flags(struct gpio_chip *chip,
					struct of_phandle_args *gpiospec,
					enum of_gpio_flags *flags)
{
	int ret;

	if (chip->of_gpio_n_cells != gpiospec->args_count)
		return ERR_PTR(-EINVAL);

	ret = chip->of_xlate(chip, gpiospec, flags);
	if (ret < 0)
		return ERR_PTR(ret);

	return gpiochip_get_desc(chip, ret);
}

可以看到,这里就是会调用到我们之前说的of_xlate回调函数了,即sunxi_pinctrl_gpio_of_xlate函数:

static int sunxi_pinctrl_gpio_of_xlate(struct gpio_chip *gc,
				const struct of_phandle_args *gpiospec,
				u32 *flags)
{
	int pin, base;

	base = PINS_PER_BANK * gpiospec->args[0];
	pin = base + gpiospec->args[1];

	if (pin > gc->ngpio)
		return -EINVAL;

	if (flags)
		*flags = gpiospec->args[2];

	return pin;
}

可以简单看出,我们的设备树里GPIO的属性有三个参数,第一个是base,第二个是pin引脚,第三个是flag标志了。
看了下设备树,也确实是这样写的:
a10
设备树这里描述的就是A10了,从芯片文档我们可知,分为好几组IO口,第0组就是A。也就是base是0,pin是10,flags是GPIO_ACTIVE_HIGH。

再者,of_get_named_gpiod_flags函数的返回值为gpio_desc结构体,会通过调用desc_to_gpio函数通过gpio_desc得到具体的GPIO:

int desc_to_gpio(const struct gpio_desc *desc)
{
	//gdev->base在gpiochip_add_data函数填充
	return desc->gdev->base + (desc - &desc->gdev->descs[0]);
}

###同理
    我们在设置某个GPIO为高时:

static inline void gpio_set_value(unsigned int gpio, int value)
{
	__gpio_set_value(gpio, value);
}

__gpio_set_value
    -->gpiod_set_raw_value
        -->_gpiod_set_raw_value
            -->chip->set(chip, gpio_chip_hwgpio(desc), value);//调用set回调函数

gpio_chip_hwgpio:获取该gpio号对应于该chip的offset,由于chip的起始号不一定就是开始与系统全局desc的起点,需要这个函数转换一下
其中set回调函数就是sunxi_pinctrl_gpio_set函数了:

static void sunxi_pinctrl_gpio_set(struct gpio_chip *chip,
				unsigned offset, int value)
{
	struct sunxi_pinctrl *pctl = gpiochip_get_data(chip);
	u32 reg = sunxi_data_reg(offset);
	u8 index = sunxi_data_offset(offset);
	unsigned long flags;
	u32 regval;

	raw_spin_lock_irqsave(&pctl->lock, flags);

	regval = readl(pctl->membase + reg);

	if (value)
		regval |= BIT(index);
	else
		regval &= ~(BIT(index));

	writel(regval, pctl->membase + reg);

	raw_spin_unlock_irqrestore(&pctl->lock, flags);
}

里面基本就是:
先读取GPIO寄存器的值
修改值
再写回去!
其中读写的地址为(pctl->membase + reg),这里pctl->membase就是我们文章一开始分析时获取到的0x01C20800 地址了。

.

关于什么使用pinctrl,有这么几个API:

struct pinctrl *devm_pinctrl_get(struct device *dev)

pinctrl_lookup_state //寻找一个pin的配置

pinctrl_select_state // 设置选择一个pin的配置

.


关于pinctrl的文章,可以看看这个:
链接:
gpio子系统和pinctrl子系统(一)
gpio子系统和pinctrl子系统(二)
gpio子系统和pinctrl子系统(三)

Linux内核中提供了pinctrl子系统,目的是为了统一各SoC厂商的pin脚管理,避免各SoC厂商各自实现相同的pin脚管理子系统,减少SoC厂商系统移植工作量。
功能:
  1. 管理系统中所有可以控制的pin。在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin。
  2. 管理这些pin的复用(Multiplexing)。对于SOC而言,其引脚除了配置成普通GPIO之外,若干个引脚还可以组成一个pin group,形成特定的功能。例如pin number是{ 0, 8, 16, 24 }这四个引脚组合形成一个pin group,提供SPI的功能。pin control subsystem要管理所有的pin group。
  3. 配置这些pin的特性。例如配置该引脚上的pull-up/down电阻,配置drive strength等。
  4. 与GPIO子系统的交互
  5. 实现pin中断

关于为什么引进pinctrl,看这篇文章:Pinctrl基础简介

即:Gpiolib方式的缺点在于:当同一套代码对应多个board设计时,需要在board--gpiomux.c文件中加宏进行区分。对于不同平台项目,在board-msm8974-gpiomux.c文件中添加了很多宏控。
pinctrl方式可以避免代码中的这种冗余代码,它将board--gpiomux.c文件中的配置信息移到-pinctrl.dtsi;这样,针对不同project的board设计,分别在各自project的-pinctrl.dtsi中定义各自的gpio配置信息。
.
gpio子系统让驱动工程师不用关心底层gpio chip的具体实现,让bsp工程师不用关心上层驱动工程师的使用方式。pinctrl子系统帮我们管理了pin信息,包括了pin的mux和conf,同时也透明的处理了与gpio子系统的关联以及设备模型的关联。

了解更多,请点击:蜗窝科技

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

嵌入式Linux驱动笔记(二十一)------GPIO和Pinctrl子系统的分析和思考 的相关文章

  • python之字符串切片为列表

    函数名说明A replace old new count 将字符串A里的old替换为new xff0c 替换次数为counta join A 将字符串序列A之间插入字符aA split sep xff0c count 将字符串A切片输出为列
  • 用python实现最简单简单的计算器

    直接上代码 xff1a 密码是123 过程 体验 收获及自我评价 xff0c 本人在活动的具体工作可以输入1000字 import os 让解释器停住 xff0c 引用os模块 print 34 欢迎使用计算程序 34 print 34 请
  • Android 一键分享功能

    之前在做项目时遇到这么个需求 xff0c 就是用户点击Menu或者一个按钮可以把文字分享到各大微博例如新浪微博 腾讯 人人 开心 校内等 现在我给大家演示一下 xff08 一 xff09 先建一个工程文件ShareDemo xff08 二
  • android listview单击事件

    今天我们来学习下listview 单击事件 xff0c 这在开发中是经常用的组件之一 1 新建一个项目 xff0c 名为ListViewDemo 2 布置布局文件main xml lt xml version 61 34 1 0 34 en
  • android 焦点事件

    今天介绍下android中的焦点事件 xff0c 之前在项目有用到过 这块不是很难 xff0c 大家跟着过一遍吧 xff0c 用到的时候直接把我下面这段代码拷贝过去就ok了 1 建一个工程 xff0c 名为TestFocus 2 在布局文件
  • android 绘图、自定义组件

    我们在开发当中很多时候都需要自定义组件 xff0c 通过自定义组件 xff0c 可以随心所欲定制酷炫的效果 下面将演示自定义绘图组件 我们要绘制一个红色的线条 1 建立工程文件 xff0c 名为TouchDemo 2 布局文件 lt xml
  • SVN常用命令详解

    命令的使用 1 检出 svn co http 路径 目录或文件的全路径 本地目录全路径 username 用户名 password 密码svn co svn 路径 目录或文件的全路径 本地目录全路径 username用户名 password
  • androidstudio 拆包时设置dex方法个数

    前言 Android应用程序 xff0c 最终发布成一个apk xff0c 安装到手机上 apk文件随便用一个解压缩文件打开 xff0c 可以看到里面有一个classes dex文件 xff0c 这就是之前工程中所有的代码 xff0c 以及
  • hash算法原理详解

    散列表 它是基于快速存取的角度设计的 xff0c 也是一种典型的 空间换时间 的做法 顾名思义 xff0c 该数据结构可以理解为一个线性表 xff0c 但是其中的元素不是紧密排列的 xff0c 而是可能存在空隙 散列表 xff08 Hash
  • Android Studio 打包时 Signature Version 选择 V1 V2 说明

    问题描述 v1和v2 Android 7 0中引入了APK Signature Scheme v2 xff0c v1是jar Signature来自JDK V1 xff1a 应该是通过ZIP条目进行验证 xff0c 这样APK 签署后可进行
  • Android 多Dex分包机制

    问题引入 随着项目工程越来越庞大 xff0c 代码的方法数不断增长到一定程度 xff0c 就出现Android 低版本系统应用无法安装的情况 那么这是哪里出错了 xff1f Android系统对安装包有哪些限制 xff1f 前一阵子 xff
  • 安装openstack时碰到的错误

    文章目录 目录安装keystone identity时遇到的错误信息glance验证操作错误信息计算节点安装openstack nova compute找不到包openstack nova compute安装后服务起不来openstack
  • Linux什么时候能代替Windows?现在有什么困难?2022新一年Linux目标

    我举出一个身边例子 xff1a 昨天 xff0c 我的一个亲戚的电脑坏了 xff0c 是一个七八年前配置笔记本电脑 xff0c 找我修 xff0c 我本来想安装Windows10 xff0c 但是想到现在我电脑的主系统都是Linux了 xf
  • apt-get update error解决方法

    apt get update error Unable to lock the administration directory var lib dpkg is another proc root 64 ony an opt devstac
  • PVE安装win10并开启远程桌面

    接上一篇 一 win10安装镜像最新版下载 下载地址 xff1a https next itellyou cn 现在的win10最新版时22h2 文件名为zh cn windows 10 business editions version
  • Win10相机打开后显示灰色的原因(仅适用于联想笔记本)

    症状描述 xff1a 打开相机 xff0c 显示灰色 xff0c 中间有一个相机带斜杠的图标 我先说解决方法 xff0c 再吐槽 xff1a 如果网上的方法都不行 xff0c 就检查自己的笔记本是否安装了联想电脑管家 xff0c 如果有 x
  • Visual Studio 2015 tools for Unity 安装使用

    P1 xff1a 安装 微软好像把这个工具整合在Visual studio 2015一起了 xff0c 没找到单独的链接 CSDN链接 xff1a http download csdn net detail devilsomezeng 95
  • VMware部署Debian系统

    前面在手机和平板上安装了UserLAnd软件 xff0c 初步实现了随身携带Linux系统的小目标 但是前面也提到了目前存在一个小问题 xff0c 那就是没有办法远程登录 xff0c 简单调整了一下还没解决 xff0c 看来还是要简单学习一
  • Openstack关于Regions和Availability Zones

    声明 xff1a 本博客欢迎转发 xff0c 但请保留原作者信息 内容系本人学习 研究和总结 xff0c 如有雷同 xff0c 实属荣幸 xff01 原文地址 xff1a http blog csdn net gtt116 在AWS中有Re
  • Manjaro 安装搜狗输入法

    经历了长时间搜索和实践 xff0c 我终于安装好了搜狗输入法 xff0c 基本套路就还是按照大多数博客介绍的命令行装的 xff1a sudo pacman S fcitx im sudo pacman S fcitx configtool

随机推荐