[OHOS - STM32MP157] 1 GPIO驱动分析 原创 精华
[OHOS - STM32MP157] 1 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驱动加载
-
驱动加载入口
/* 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
【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引脚反向
函数名 | 入参 | 出参 | 返回值 | 功能 |
---|---|---|---|---|
Stm32Mp157GpioSetDir | cntlr:结构体指针,核心层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输出电平
函数名 | 入参 | 出参 | 返回值 | 功能 |
---|---|---|---|---|
Stm32Mp157GpioWrite | cntlr:结构体指针,核心层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)
自此,openHarmony操作硬件寄存器的基本流程已经介绍完毕,供阅读参考。
4 .openHarmony GPIO驱动使用
【补充】: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管脚设置中断,重复其他管脚会导致系统运行异常