电子技术应用|技术阅读
登录|注册

您现在的位置是:电子技术应用 > 技术阅读 > 【BearPi-HM Micro】OpenHarmony GPIO驱动分析

【BearPi-HM Micro】OpenHarmony GPIO驱动分析

1 . STM32MP157GPIO寄存器

【参考资料】dm00327659-stm32mp157-advanced-armbased-32bit-mpus-stmicroelectronics.pdf

寄存器组地址

【P159 Memory map and register boundary addresses Table 9. Register boundary addresses】

PA0为例:由上表可知,GPIOA基地址为0x50002000,记住备用。

【以输出模式为例】查看芯片GPIO使用方法:

【P1064 I/O pin alternate function multiplexer and mapping 】: 找到如下描述:

【配置PA0为高速推挽上拉输出高电平模式】

下面看GPIO寄存器组:

【P1072 GPIO registers】

【GPIO port mode register (GPIOx_MODER) 】端口模式配置寄存器

由上图可知,该寄存器每两位控制一个IO口,如PA0配置为输出模式,即将该寄存器MODER0[1:0]位配置为01即可,该寄存器地址为GPIOA基地址 + 寄存器偏移地址,由上面得到的GPIOA基地址为0x50002000,由上图得到端口模式配置寄存器偏移地址为0x00,故我们最后需要的操作就是将0x50002000+0x00地址的低两位设置为01(输出)即可。

【GPIO port output type register (GPIOx_OTYPER) 】输出类型配置寄存器

配置详情同上,最后需要的操作就是将0x50002000+0x04地址的低一位设置为0(推挽输出)。

【GPIO port pull-up/pull-down register (GPIOx_PUPDR) 】端口上下拉配置寄存器

配置详情同上,最后需要的操作就是将0x50002000+0x0C地址的低两位设置为01(上拉)。

【GPIO port output data register (GPIOx_ODR) 】端口输出数据配置寄存器

配置详情同上,最后需要的操作就是将0x50002000+0x14地址的低一位设置为1(高电平)。

自此,芯片数据手册分析完毕,下面是openHarmony与具体soc平台GPIO的相关开发介绍。

2 .openHarmony驱动加载

驱动框架详情参考官方文档 | OpenHarmony[1]

驱动加载入口

/* HdfDriverEntry definition */
struct HdfDriverEntry g_GpioDriverEntry = {
.moduleVersion = 1,
.moduleName = "HDF_PLATFORM_GPIO",
.Bind = GpioDriverBind,
.Init = GpioDriverInit,
.Release = GpioDriverRelease,
};

/* Init HdfDriverEntry */
HDF_INIT(g_GpioDriverEntry);

驱动加载时会执行Bind方法和Init方法,详情参考官方文档,本文只做GPIO分析,不做驱动框架层面分析。

bind方法,GPIO驱动里面不做具体操作:

static int32_t GpioDriverBind(struct HdfDeviceObject *device)
{
(void)device;
return HDF_SUCCESS;
}

Init方法,获取hcs文件具体配置信息,根据该信息初始化硬件驱动:

device_gpio :: device { device0 :: deviceNode { policy = 0; priority = 10; permission = 0644; moduleName = "HDF_PLATFORM_GPIO"; serviceName = "HDF_PLATFORM_GPIO"; deviceMatchAttr = "st_stm32mp157_gpio"; } } gpio_config { controller_0x50002000 { match_attr = "st_stm32mp157_gpio"; groupNum = 11; bitNum = 16; gpioRegBase = 0x50002000; gpioRegStep = 0x1000; irqRegBase = 0x5000D000; irqRegStep = 0x400; } }
static int32_t GpioDriverInit(struct HdfDeviceObject *device){
int32_t ret; struct Stm32GpioCntlr *stm32gpio = &g_Stm32GpioCntlr;
HDF_LOGD("%s: Enter", __func__); if (device == NULL || device->property == NULL) { HDF_LOGE("%s: device or property NULL!", __func__); return HDF_ERR_INVALID_OBJECT; } //获取属性数据 ret = Stm32GpioReadDrs(stm32gpio, device->property); //读取hcs配置属性, /***************************分析时复制到此*************************************** 补充插入GPIO描述结构 struct Stm32GpioCntlr { struct GpioCntlr cntlr; // HDF驱动框架父对象 volatile unsigned char *regBase; //GPIO寄存器基地址 :为映射后的虚拟地址 EXTI_TypeDef *exitBase; uint32_t gpioPhyBase; uint32_t gpioRegStep; uint32_t irqPhyBase; uint32_t iqrRegStep; uint16_t groupNum; uint16_t bitNum; struct GpioGroup *groups; } ******函数调用传入设备描述对象结构体以及设备节点属性,该函数实现数据解析,类似设备树解析 static int32_t Stm32GpioReadDrs(struct Stm32GpioCntlr *stm32gpio, const struct DeviceResourceNode *node) { int32_t ret; struct DeviceResourceIface *drsOps = NULL;
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); if (drsOps == NULL || drsOps->GetUint32 == NULL) { HDF_LOGE("%s: invalid drs ops fail!", __func__); return HDF_FAILURE; }
ret = drsOps->GetUint32(node, "gpioRegBase", &stm32gpio->gpioPhyBase, 0); //获取GPIO寄存器基地址0x50002000,该地址是基于上面数据手册得到的GPIOA的寄存器基地址(实际物理地址) if (ret != HDF_SUCCESS) { HDF_LOGE("%s: read regBase fail!", __func__); return ret; }
ret = drsOps->GetUint32(node, "gpioRegStep", &stm32gpio->gpioRegStep, 0); //由于STM32MP157GPIO组并非连续地址,查阅手册后的到GPIO端口之间地址间距为0x1000 if (ret != HDF_SUCCESS) { HDF_LOGE("%s: read gpioRegStep fail!", __func__); return ret; }
ret = drsOps->GetUint16(node, "groupNum", &stm32gpio->groupNum, 0); //获取GPIO分组数,STM32MP157分组为A,B,C,D,E,F,G,H,I,J,Z共11组 if (ret != HDF_SUCCESS) { HDF_LOGE("%s: read groupNum fail!", __func__); return ret; }
ret = drsOps->GetUint16(node, "bitNum", &stm32gpio->bitNum, 0); //获取每组GPIO数量,STM32MP157一组IO数量为16 if (ret != HDF_SUCCESS) { HDF_LOGE("%s: read bitNum fail!", __func__); return ret; }
ret = drsOps->GetUint32(node, "irqRegBase", &stm32gpio->irqPhyBase, 0); //获取中断寄存器基地址 if (ret != HDF_SUCCESS) { HDF_LOGE("%s: read regBase fail!", __func__); return ret; }
ret = drsOps->GetUint32(node, "irqRegStep", &stm32gpio->iqrRegStep, 0); //获取中断寄存器地址间距 if (ret != HDF_SUCCESS) { HDF_LOGE("%s: read gpioRegStep fail!", __func__); return ret; } return HDF_SUCCESS; } ***********************************************************/ if (ret != HDF_SUCCESS) { HDF_LOGE("%s: get gpio device resource fail:%d", __func__, ret); return ret; }
if (stm32gpio->groupNum > GROUP_MAX || stm32gpio->groupNum <= 0 || stm32gpio->bitNum > BIT_MAX || stm32gpio->bitNum <= 0) { HDF_LOGE("%s: invalid groupNum:%u or bitNum:%u", __func__, stm32gpio->groupNum, stm32gpio->bitNum); return HDF_ERR_INVALID_PARAM; } //寄存器地址映射,MMU操作 stm32gpio->regBase = OsalIoRemap(stm32gpio->gpioPhyBase, stm32gpio->groupNum * stm32gpio->gpioRegStep); if (stm32gpio->regBase == NULL) { HDF_LOGE("%s: err remap phy:0x%x", __func__, stm32gpio->gpioPhyBase); return HDF_ERR_IO; } /* OsalIoRemap: remap registers */ stm32gpio->exitBase = OsalIoRemap(stm32gpio->irqPhyBase, stm32gpio->iqrRegStep); if (stm32gpio->exitBase == NULL) { dprintf("%s: OsalIoRemap fail!", __func__); return -1; }
ret = InitGpioCntlrMem(stm32gpio); if (ret != HDF_SUCCESS) { HDF_LOGE("%s: err init cntlr mem:%d", __func__, ret); OsalIoUnmap((void *)stm32gpio->regBase); stm32gpio->regBase = NULL; return ret; } stm32gpio->cntlr.count = stm32gpio->groupNum * stm32gpio->bitNum; stm32gpio->cntlr.priv = (void *)device->property; stm32gpio->cntlr.device = device; stm32gpio->cntlr.ops = &g_GpioMethod; ret = GpioCntlrAdd(&stm32gpio->cntlr); if (ret != HDF_SUCCESS) { HDF_LOGE("%s: err add controller: %d", __func__, ret); return ret; } HDF_LOGE("%s: dev service:%s init success!", __func__, HdfDeviceGetServiceName(device)); return ret;}


上述为openHarmony初始化GPIO的方法简单介绍,详细情况请仔细阅读源码,自此,OpenHarmony已经得到STM32MP157芯片的GPIO外设基本信息,我们继续往下分析。

3 .openHarmony驱动GPIO

参考官方文档:GPIO | OpenHarmony[2]

【openHarmony对外提供的GPIO驱动函数】


struct GpioMethod { int32_t (*request)(struct GpioCntlr *cntlr, uint16_t local);// 【可选】 int32_t (*release)(struct GpioCntlr *cntlr, uint16_t local);// 【可选】 int32_t (*write)(struct GpioCntlr *cntlr, uint16_t local, uint16_t val); int32_t (*read)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *val); int32_t (*setDir)(struct GpioCntlr *cntlr, uint16_t local, uint16_t dir); int32_t (*getDir)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *dir); int32_t (*toIrq)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *irq);// 【可选】 int32_t (*setIrq)(struct GpioCntlr *cntlr, uint16_t local, uint16_t mode, GpioIrqFunc func, void *arg); int32_t (*unsetIrq)(struct GpioCntlr *cntlr, uint16_t local); int32_t (*enableIrq)(struct GpioCntlr *cntlr, uint16_t local); int32_t (*disableIrq)(struct GpioCntlr *cntlr, uint16_t local);}



STM32MP157驱动函数实例化:

struct GpioMethod g_GpioMethod = { .request = NULL, .release = NULL, .write = Stm32Mp157GpioWrite, .read = Stm32Mp157GpioRead, .setDir = Stm32Mp157GpioSetDir, .getDir = Stm32Mp157GpioGetDir, .toIrq = NULL, .setIrq = Stm32Mp157GpioSetIrq, .unsetIrq = Stm32Mp157GpioUnsetIrq, .enableIrq = Stm32Mp157GpioEnableIrq, .disableIrq = Stm32Mp157GpioDisableIrq,};

【Stm32Mp157GpioSetDir】设置GPIO引脚反向

函数名入参出参返回值功能
Stm32Mp157GpioSetDircntlr:结构体指针,核心层GPIO控制器;local:uint16_t,GPIO端口标识号 ;dir:uint16_t,管脚方向传入值;HDF_STATUS相关状态设置GPIO引脚输入/输出方向
static int32_t Stm32Mp157GpioSetDir(struct GpioCntlr *cntlr, uint16_t gpio, uint16_t dir){ int32_t ret; uint32_t irqSave; unsigned int val; volatile unsigned char *addr = NULL;
unsigned int bitNum = Stm32ToBitNum(gpio); //将GPIO端口号转换为位号 如 0 = PA0 -> 0 struct GpioGroup *group = NULL;
ret = Stm32GetGroupByGpioNum(cntlr, gpio, &group); //将GPIO端口号转换为组号 如 1 = PA1 -> 0 if (ret != HDF_SUCCESS) { HDF_LOGE("Stm32GetGroupByGpioNum failed\n"); return ret; }
if (OsalSpinLockIrqSave(&group->lock, &irqSave) != HDF_SUCCESS) { HDF_LOGE("OsalSpinLockIrqSave failed\n"); return HDF_ERR_DEVICE_BUSY; } /** ************************************补充宏定义说明********************************** #define STM32MP15X_GPIO_DIR(base) ((base) + 0x00) 基地址+偏移地址 #define STM32MP15X_GPIO_DATA(base) ((base) + 0x18) #define STM32MP15X_GPIO_IDR(base) ((base) + 0x10) *****/ addr = STM32MP15X_GPIO_DIR(group->regBase); //获取GPIO寄存器地址,此处为虚拟地址 val = OSAL_READL(addr); //读取原来寄存器的值 if (dir == GPIO_DIR_IN) { val &= ~(0X3 << (bitNum*2)); /* bit0:1 清零 */ // 位操作 } else if (dir == GPIO_DIR_OUT) { val &= ~(0X3 << (bitNum*2)); /* bit0:1 清零 */ val |= (0X1 << (bitNum*2)); /* bit0:1 设置 01 */ } OSAL_WRITEL(val, addr); // 将运算后的值写入寄存器,配置完成 (void)OsalSpinUnlockIrqRestore(&group->lock, &irqSave); return HDF_SUCCESS;}

【Stm32Mp157GpioWrite】设置GPIO输出电平

函数名入参出参返回值功能
Stm32Mp157GpioWritecntlr:结构体指针,核心层GPIO控制器;local:uint16_t,GPIO端口标识号 ;val:uint16_t,电平传入值;HDF_STATUS相关状态GPIO引脚写入电平值
static int32_t Stm32Mp157GpioWrite(struct GpioCntlr *cntlr, uint16_t gpio, uint16_t val){ int32_t ret; uint32_t irqSave; unsigned int valCur; unsigned int bitNum = Stm32ToBitNum(gpio); volatile unsigned char *addr = NULL; struct GpioGroup *group = NULL;
ret = Stm32GetGroupByGpioNum(cntlr, gpio, &group); if (ret != HDF_SUCCESS) { return ret; } if (OsalSpinLockIrqSave(&group->lock, &irqSave) != HDF_SUCCESS) { return HDF_ERR_DEVICE_BUSY; } /** ************************************补充宏定义说明********************************** #define STM32MP15X_GPIO_DIR(base) ((base) + 0x00) 基地址+偏移地址 #define STM32MP15X_GPIO_DATA(base) ((base) + 0x18) 基地址+偏移地址 #define STM32MP15X_GPIO_IDR(base) ((base) + 0x10) *****/ addr = STM32MP15X_GPIO_DATA(group->regBase);//获取输出数据寄存器地址 valCur = OSAL_READL(addr); if (val == GPIO_VAL_LOW) { valCur &= ~(0x1 << bitNum); valCur |= (0x1 << (bitNum+16)); } else { valCur |= (0x1 << bitNum); } OSAL_WRITEL(valCur, addr); (void)OsalSpinUnlockIrqRestore(&group->lock, &irqSave);
return HDF_SUCCESS;}

其他函数请自行阅读源码。

device/st/drivers/gpio · 小熊派开源社区/BearPi-HM_Micro_small - 码云 - 开源中国 (gitee.com)[3]

自此,openHarmony操作硬件寄存器的基本流程已经介绍完毕,供阅读参考。

4 .openHarmony GPIO驱动使用

参阅官方GPIO | OpenHarmony[4]

【补充】:STM32MP157 GPIO管脚号计算 PXY = = [X] * 16 + Y

其中  [X]: [A] = 0, [B] = 1, [C] = 2, [D] = 3, [E] = 4,[F] = 5,  [G] = 6, [H] = 7, [I] = 8, [J] = 9, [K] = 10, [Z] = 11如 : PA0 = 0 * 16 + 0 = 0 ; PC10 = 2 * 16 + 10 = 42

【Tips】:GPIO驱动使用时,仅能支持一个GPIO管脚设置中断,重复其他管脚会导致系统运行异常

References

[1] 驱动框架详情参考官方文档 | OpenHarmony: https://docs.openharmony.cn/pages/0004010000/#简介
[2] 参考官方文档:GPIO | OpenHarmony: https://docs.openharmony.cn/pages/0004010101/#概述
[3] device/st/drivers/gpio · 小熊派开源社区/BearPi-HM_Micro_small - 码云 - 开源中国 (gitee.com): https://gitee.com/bearpi/bearpi-hm_micro_small/tree/master/device/st/drivers/gpio
[4] 参阅官方GPIO | OpenHarmony: https://docs.openharmony.cn/pages/0004010200/#概述