在了解上一章节引脚权限相关操作后,我将抛开官方描述,从入手调试角度详细介绍模块中的GPIO是什么、怎么操作等等。另外,最后将以LCD的mipi数据获取方式介绍AP与BP的交流。
一、GPIO
之前讲述一些概念时我都以引脚来代替GPIO,一是因为引脚的概念大家在大学都学过了解过,二是抛开概念不谈,其实GPIO和pin脚是一样的,只不过主动可控的pin脚叫GPIO,而被动受影响的pin脚就是pin,这里需要注意的是我们在高通的代码中只能操作高通模块的GPIO,一些外接芯片的引脚都是受控的,所以无法直接控制。
上图展示了部分845模块的GPIO,一个845模块有一百多个GPIO,其中可以分为普通可控GPIO,sensor GPIO,在qupv组中的GPIO和PMIC的GPIO等。从输出方向又可分为输入与输出,下面主要介绍可控GPIO与QUPV组中的GPIO的一些操作。
(1)普通可控GPIO
在kernel开发中一些对GPIO的操作主要是拉高与拉低,当然还有其他的比如获取高低电平状态设置输入输出等等。下面以点亮一颗单色LED灯为例,介绍相关操作,假设LED直连模块的GPIO50脚并且没有其他设备占用该脚,那么逻辑上当GPIO50为高电平LED就亮,相反为低电平就会熄灭。
如果没有时序或者功能限制,我们可以在任意一个可运行的驱动中添加拉高拉低状态,下面以在9611驱动中添加为例:
AP侧对GPIO的操作:
先在驱动添加相关库文件并宏定义LED GPIO为50,一些GPIO操作的接口函数都在这里面:
#include <Linux/of_gpio.h>
#include <Linux/gpio.h>
#define LED_GPIO 50
在操作GPIO的函数中加入以下语句:
static int lt9611_probe(..., ...){
...
...
int ret;
if(gpio_is_valid(LED_GPIO)){ //判断GPIO50是否有效
gpio_free(LED_GPIO) //释放GPIO50
ret = gpio_request(LED_GPIO,"TEST_LED_GPIO"){ //获取GPIO50
if(ret){
pr_err("%s:led gpio request failed\n",__func__);
goto LED_REQ_ERROR;
}
ret = gpio_direction_output(LED_GPIO, 1); //拉高GPIO50
/*ret = gpio_direction_output(LED_GPIO, 0); 拉低GPIO50*/
msleep(10);
if(ret){
pr_err("%s:led gpio set high value failed\n",__func__);
gpto LED_OUT_ERROR;
}
}
}
LED_REQ_ERROR:
gpio_free(LED_GPIO);
LED_OUT_ERROR:
gpio_free(LED_GPIO);
...
...
return ret;
}
以上是拉高/拉低一个脚的最规范的写法,当定义好一个GPIO后首先需要判断该脚是否合法有效,如果无效则需要释放该脚,第二步是要去获取GPIO50的操作权限,当获取后相当于你已经占有了GPIO50,(当有另一个线程要来获取同样的脚时会失败,只有当我释放掉后其他线程才能获取)。当获取成功后第三步需要利用gpio_direction_output接口来拉高/拉低GPIO50,并且延长一定时间,如果拉高/拉低失败,则进入error释放GPIO50。按照上述程序执行后LED会在系统运行到该驱动时常亮,如果是拉低就会常灭。
在AP侧配置一个引脚为输入引脚的示例如下:
int ret;
if(gpio_is_valid(LED_GPIO)){
}
ret = gpio_request(LED_GPIO, "TEST_LED_GPIO");
if(ret){
pr_err("%s:request led_gpio failed\n",__func__);
goto LED_REQ_ERROR;
}
ret = gpio_direction_input(LED_GPIO);
if(ret){
pr_err("%s:set led_gpio input failed\n",__func__);
goto LED_INP_ERROR;
}
msleep(10);
LED_INP_ERROR:
gpio_free(LED_GPIO);
LED_REQ_ERROR:
gpop_free(LED_GPIO);
和配置成输出脚一样,只是最后一句变成了gpio_direction_input,一般配置成输入脚后当有中断产生引脚会自动拉高拉低;
关于GPIO的其他操作,之后实战调试会接触。
需要注意以上代码中的其他语句:pr_err为打印日志信息的语句,一般在debug的时候用得很多,所以调试时需要多加此类语句定位错误,后面的__func__指的是该语句所在函数的名称,一般用来辅助定位错误。
BP侧对GPIO的操作:
一般关于LCD/HDMI OUT的引脚拉高都会在MDPPlatformLib.c这个文件(文件位置之前写过),和AP侧一样只是接口换了,下面直接上代码。
先包含库文件及定义引脚:
#include <Protocol/EFITlmm.h>
#define LED_GPIO 50
在对GPIO操作的函数中加入如下语句:(可以在文件搜索GPIO)
/* Setup LED_GPIO Pin */
if (EFI_SUCCESS != TLMMProtocol->ConfigGpio((UINT32)EFI_GPIO_CFG(LED_GPIO, 0, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_2MA), TLMM_GPIO_ENABLE))
{
DEBUG((EFI_D_ERROR, "DisplayDxe: Configure GPIO %d for LED_GPIO Failed!\n", LED_GPIO));
}
/* Set LED_GPIO HIGH */
if (EFI_SUCCESS != TLMMProtocol->GpioOut((UINT32)EFI_GPIO_CFG(LED_GPIO, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), GPIO_HIGH_VALUE))
{
DEBUG((EFI_D_ERROR, "DisplayDxe: LED_GPIO HIGH failed!\n"));
}
MDP_OSAL_DELAYUS(100);
/* Pull LED_GPIO Low */
if (EFI_SUCCESS != TLMMProtocol->GpioOut((UINT32)EFI_GPIO_CFG(LED_GPIO, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), GPIO_LOW_VALUE))
{
DEBUG((EFI_D_ERROR, "DisplayDxe: Pull LED_GPIO Low Failed!\n"));
}
MDP_OSAL_DELAYUS(100);
以拉高再拉低为例,首先需要使能需要拉高的脚,和AP侧的获取脚的权限是一样的道理,之后再通过改变接口函数的GPIO_HIGH_VLAUE和GPIO_LOW_VALUE改变引脚高低电平,也可以通过其中的GPIO_OUTPUT和GPIO_INPUT来改变GPIO方向。下面的代码为配置一个输入引脚:
if (EFI_SUCCESS != TLMMProtocol->ConfigGpio((UINT32)EFI_GPIO_CFG(LT9211_IRQ, 0, GPIO_INPUT, GPIO_NO_PULL, GPIO_2MA), TLMM_GPIO_ENABLE))
{
DEBUG((EFI_D_ERROR, "DisplayDxe: Configure GPIO %d for LT9211 LT9211_IRQ line Failed!\n", LT9211_IRQ));
}
if (EFI_SUCCESS != TLMMProtocol->GpioIn((UINT32)EFI_GPIO_CFG(LT9211_IRQ, 0, GPIO_INPUT, GPIO_NO_PULL, GPIO_2MA), pGpioValue))
{
DEBUG((EFI_D_ERROR, "DisplayDxe: Get display GPIO(%d) value failed with status()!\n", uGpioNum));
}
MDP_OSAL_DELAYUS(30);
我们常常需要把中断脚配置成输入脚,所以就不拿LED的GPIO举例,上述代码是将9211的中断脚配置成输入脚,同样是使能加配置输入加延时,请仔细研究接口参数,注意输入与输出的区别。
还有一种情况比较特殊,就是需要一开机就拉高某个GPIO,这里需要在BP侧该文件中配置:
sdm845_Android10_BP\boot_images\QcomPkg\SDM845Pkg\Settings\TLMM\loader\TLMMChipset.xml
在这配置相应的GPIO就可以,如果需要开机默认拉高,把对应GPIO那一行改成
PIN_OUTPUT | PIN_PULL_UP | PIN_OUT_HIGH | PIN_PRG_YES就可以
以上是一个普通的GPIO口拉高的过程,当然如果有功能或者时序限制,只需要找到对应驱动或者对应功能函数加上上面的语句即可。
(2)在QUPV组中的GPIO
这种GPIO一般用作I2C/SPI/UART通信,如果需要将此类GPIO修改为普通GPIO,请参考上一篇文章关于BP修改权限的操作。修改好权限之后需要编译trustzone烧写生效,其他操作与第一个可控GPIO操作一样。
配置最多的还是I2C的GPIO了,上一篇文章具体讲了该怎么找某组GPIO对应哪个QUPV,当调试后I2C通信失败,通常可以采取给权限将这两个脚循环一定次数拉高拉低来判断电路是否正常,或者可以用示波器量SDA/SCL脚是否有波形输出。
接下来讲一下如何配置I2C(顺序可变):
第一:在BP侧将这两个脚配置成I2C,即上一片文档中讲的权限问题,如果没有配置的话需要自己配置一行语句,最后编译。(一般都已经配好,有时权限不对)
第二:在设备树配置正确的根节点:
在第一篇文章中讲过设备树的一些配置,这里如果需要配置成I2C需要写成如上图的根节点形式,即&qupv3_se0_i2c,另外根据外设芯片的数据手册再配置对应I2C地址,上图9211地址为3b。具体该节点配在哪个设备树需要根据不同平台定。
第三:在pinctrl文件中添加对应se组的GPIO
按照此格式进行配置即可,具体问题具体分析;修改对应GPIO和对应的se组就可以。
第四:在驱动中添加I2C的设备注册读写等操作,这一步基本驱动中都有写,只需要分析一两篇驱动了解I2C的注册过程即可。
这一部分之所以在可控GPIO之后讲,是因为只有把芯片的一些GPIO按数据手册规定配置好地址写对,I2C才可以通。
(3)sensor专用GPIO
这种GPIO是sensor调试的同学经常接触到的,但是在LCD/HDMI OUT调试的过程中没接触过,之前在调试PWM时需要的一个脚为sensor专用脚,把相关的thermal服务去掉即可,请查看相关提交记录。
(4)PMIC中的LDO
PMIC中的GPIO也接触的比较少,在调试背光的时候如果背光供电是PMIC就需要接触。在AP侧的操作之后的背光调试示例中会介绍,这里先介绍一些最基本的开机拉高拉低的操作,即在BP操作PMIC的GPIO。
开机默认拉高:
文件位置:sdm845_Android10_BP\aop_proc\core\pmic\pm\config\sdm845\pm_config_target.c
根据不同的数组名找到对应PMIC,如何根据后面的注释找到对应PMIC的LDO,在前面的参数中按照你的需求修改就可以了,如下图:
每个参数的意思,请自行搜索。这里第二个参数表示开机默认状态。还可以通过以下文件修改LDO的状态,文件位置为:sdm845_Android10_BP\boot_images\QcomPkg\Library\PmicLib\target\sdm845_pm8998_pmi8998\system\src\pm_sbl_boot_oem.c,添加如下代码
pm_err_flag_type
pm_driver_post_init(void){
...
...
err_flag |= pm_ldo_sw_enable(0,PM_LDO_19,PM_ON);//enable ldo19
...
...
}
pm_ldo_sw_enable第一个参数为PMIC的index,这个需要根据不同项目决定,第二个参数代表哪一个LDO,最后是其状态。
上述为PMIC的LDO开关,我也默认为GPIO的操作了,毕竟都是能上电的。
(5)PMIC中的GPIO
和普通GPIO在BP拉高拉低差不多,关于LCD/HDMI OUT的PMIC GPIO也需要在MDDPlatformLib.c中配置。
第一, 先是获取GPIO的一些协议,注意如果需要用到PMIC的GPIO还需要先在该文件夹的.inf文件添加以下内容(一般已经配好):
[Protocols]
gQcomPmicGpioProtocolGuid
第二在MDPPlatformLib中找到PMIC的函数,进行以下操作:
#include <Protocol/EFIPmicGpio.h> //在文件头部包含库函数
EFI_QCOM_PMIC_GPIO_PROTOCOL *PmicGpioProtocol = NULL;
EFI_QCOM_PMIC_LPG_PROTOCOL *PmicLpgProtocol = NULL;
if (EFI_SUCCESS != gBS->LocateProtocol(&gQcomPmicGpioProtocolGuid, NULL, (VOID **)&PmicGpioProtocol))
{
DEBUG(( EFI_D_ERROR, "PCIe_ConfigPMICGPIO: PMIC Locate Protocol failed\r\n"));
}
Status = PmicGpioProtocol->ConfigDigitalOutput(
1,
EFI_PM_GPIO_5,
EFI_PM_GPIO_OUT_BUFFER_CONFIG_CMOS,
EFI_PM_GPIO_VIN0,
EFI_PM_GPIO_SOURCE_DTEST2,
EFI_PM_GPIO_OUT_BUFFER_OFF,
TRUE);
if EFI_ERROR (Status)
{
DEBUG(( EFI_D_ERROR, "LJH:PCIe_ConfigPMICGPIO: ConfigDigitalOutput EFI_PM_GPIO_5 failed\r\n"));
}
然后利用PMICGpioProtocol的接口配置PMIC的GPIO,上述代码配置的是index为1的PMIC的GPIO5,并设置电压为VIN0.,是否拉高取决于最后一个参数是TRUE还是FALSE,PMIC的index还是根据不同项目决定,其他的按照自己需求修改即可。
二、MIPI参数AP与BP的交流过程
这一小节的内容比较简单,但是是LCD/HDMI OUT调试者必须要知道的,之前讲过AP与BP都会配置MIPI的数据,那他们在不同阶段被获取的方式是如何?
简单描述:当你按下开机键,机器先在BP中寻找MIPI数据,如果找不到就会去AP匹配对应的MIPI数据,如果还是没有就不会显示。
开机的时候会执行这里,按照程序先去该文件头部找对应的XML配置:
或者是去这个位置找对应的XML文件:QCS8250_LA1.1_BP\boot_images\QcomPkg\Settings\Panel
如果两个地方都没有,那根据我们在MDDPlatformLib.c文件中这样的一条配置:
去AP的设备树匹配对应的MIPI参数,如这里的9611如果在上述两种情况都没有匹配到的话,在设备树文件中有这样的一个文件:
打开文件后可以发现:
这里方框中的名字与MDDPlatformLib.c中方框的名字是一样的,所以BP就是通过这种方式去寻找AP的设备树获取MIPI数据。
三、总结
本篇文章主要讲各种不同类型的GPIO拉高拉低以及设置输入输出的方法,其中普通GPIO与QUPV组中的GPIO运用最多,可以多看接口函数的.h文件了解其含义。最后简单描述了一下MIPI在AP与BP交流的过程,之后会有屏幕调试的实战讲解来更深刻理解MIPI。