整体简介
如何在uboot下实现GPIO配置,并在最终Linux启动完成之后的阶段生效
本文编写耗费1小时,阅读本文需要25分钟时间。
环境准备
Linux kernelversion :3.14.43
board: TL-am4379-gp-evm
boot: origin-2017.4._tlv1.4
需求描述
由于为产品新增了充电检测功能,那么经过简单的整理,需要对以下几个GPIO引脚进行配置。
在uboot的启动过程中,我们会在充电的情况下启动boot并显示一张图片在LCD上。主要逻辑如下:
if(power==in)
{
boot_start();
GPIO_INIT();
{
set GPIO0_12 input
set GPIO0_13 input
set GPIO0_14 output
set GPIO0_15 output
}
if(GPIO0_12==ON)
{
goto Init_completed.
set GPIO0_14 HIGH
show_img( Charging completion );
while(button==IN && timer > 3min)
{
boot_kernel;
}
}
if(GPIO0_13==ON)
{
goto Init_completed.
set GPIO0_15 HIGH
show_img( Charging );
while(button==IN && timer > 3min)
{
boot_kernel;
}
}
}
序号 | 原引脚 | 新引脚 | 备注 | boot | kernel | app |
---|---|---|---|---|---|---|
1 | 无 | GPIO0_12 | CHG_END_STAT充电完成检测管脚 | √ | — | √ |
2 | 无 | GPIO0_13 | CHG_ING_STAT正在充电检测管脚 | √ | — | √ |
3 | 无 | GPIO0_14 | CHG_END_LED充电完成指示灯 | √ | — | √ |
4 | 无 | GPIO0_15 | CHG_ING_LED正在充电指示灯 | √ | — | √ |
结构分析
U-Boot的GPIO系统设计简洁而高效,它采用三层架构来实现功能:
-
顶层接口层:这一层面向用户提供了一套通用的接口定义,但并不涉及具体的实现细节。实现工作需要根据具体的芯片型号来定制开发。
-
中间接口实现层:在这一层,我们利用特定硬件板的GPIO特性来具体实现顶层定义的接口。
-
底层芯片GPIO实现层:这是最接近硬件的一层,直接负责实现与具体芯片GPIO相关的操作。
在实际工程应用中,我们通常需要直接操作硬件寄存器,并根据需要查找相应的寄存器地址。这样的设计既保证了系统的灵活性,又能满足不同硬件平台的需求。
uboot源码编译
1.1 安装 U-Boot 源代码
1.2 清理 U-Boot
进入 U-Boot 的安装目录"media/tl437x/u-boot"。执行以下命令:
cd /media/tl437x/u-boot/
make CROSS_COMPILE=arm-linux-gnueabihf- distclean
1.3 配置 U-Boot
执行以下命令进行 U-Boot 配置:
make CROSS_COMPILE=arm-linux-gnueabihf- am43xx_evm_config
1.4 编译 U-Boot
执行以下命令进行 U-Boot 编译:
make CROSS_COMPILE=arm-linux-gnueabihf- -j4
1.5 烧写 U-Boot
由于uboot在nand上启动,那么需要我们启动filesystem之后,运行flashboot脚本
之后是拷贝MLO文件和uboot.img文件到烧写的缓存目录下,笔者这里将个人所使用的目录原样给出,在实践时应当注意改成自己的路径。
cp mlo /run/media/mmcblk0p1/
cp u-boot.img /run/media/mmcblk0p1/
接下来通过 dd指令 和 flash_eraseall指令 对MLO和uboot.img进行烧写
flash_eraseall /dev/mtd0
flash_eraseall /dev/mtd1
flash_eraseall /dev/mtd2
flash_eraseall /dev/mtd3
dd if=/run/media/mmcblk0p1/MLO of=/dev/mtdblock0
dd if=/run/media/mmcblk0p1/MLO of=/dev/mtdblock1
dd if=/run/media/mmcblk0p1/MLO of=/dev/mtdblock2
dd if=/run/media/mmcblk0p1/MLO of=/dev/mtdblock3
flash_eraseall /dev/mtd5
dd if=/run/media/mmcblk0p1/u-boot.img of=/dev/mtdblock5
uboot源码分析
uboot的第一阶段做了哪些工作
在U-Boot的第一阶段,初始化过程涵盖了多个关键步骤,为系统的后续启动奠定了基础。首先,构建异常向量表是确保系统能够正确响应中断和异常的基础。接着,将CPU设置为SVC模式,为操作系统的运行提供了必要的执行环境。此外,关闭看门狗计时器是为了防止在启动过程中不必要的系统重置。开发板供电置锁确保了电源的稳定供应,为系统的持续运行提供了保障。时钟初始化为系统提供了精确的时间基准,而DDR初始化则为内存管理提供了必要的硬件支持。
串口初始化并打印"OK"是验证系统基本功能正常运行的一个简单测试。随后,重定位操作确保了代码和数据在正确的内存地址中执行和存储。建立映射表并开启MMU(内存管理单元)为更复杂的内存管理和虚拟内存提供了支持。最终,通过跳转到第二阶段的入口点,即ldr pc, =main指令,完成了第一阶段的初始化工作,为系统引导的下一阶段做好了准备。在lowlevel_init.S中,除了上述步骤,还额外执行了检查复位状态、IO恢复、开发板供电锁存、tzpc初始化,并在完成这些步骤后分别打印了’O’和’K’,作为系统初始化进度的指示。这些步骤共同确保了系统在启动过程中的稳定性和可靠性。
对于Uboot中具体的改动需求,我们可以将IO的业务逻辑部分写在boot的第二阶段,对应的是board.c这个文件,这样的做法是比较符合规范,也更好移植。
其中,start_armboot() 函数的作用是:第二阶段主要初始化剩下的硬件,主要是外部硬件如iNand和网卡,以及uboot中CLI的支持等。
进一步地,我们的目标应该是start_armboot下的 device_init() 函数中,这里的设备指的就是板上的硬件设备。
在/u-boot/arch/arm/include/asm/arch-am33xx/gpio.h中,有
#define AM33XX_GPIO0_BASE 0x44E07000
#define AM33XX_GPIO1_BASE 0x4804C000
#define AM33XX_GPIO2_BASE 0x481AC000
#define AM33XX_GPIO3_BASE 0x481AE000
#define AM33XX_GPIO4_BASE 0x48320000
#define AM33XX_GPIO5_BASE 0x48322000
根据 AM437x_Technical Reference Manual.pdf (TL开发板的参考手册)
GPIO5 0x4832_2000 ~ 0x4832_2FFF size:4KB Description:GPIO5_Registers
A38h CTRL_CONF_GPIO5_8 Section 7.3.1.194
A3Ch CTRL_CONF_GPIO5_9 Section 7.3.1.195
A40h CTRL_CONF_GPIO5_10 Section 7.3.1.196
A44h CTRL_CONF_GPIO5_11 Section 7.3.1.197
A48h CTRL_CONF_GPIO5_12 Section 7.3.1.198
A4Ch CTRL_CONF_GPIO5_13 Section 7.3.1.199
在跳转宏AM33XX_GPIO5_BASE定义处的时候,我们找到了am437x平台的源码。
如下这段代码存放在src/board/ti/am43xx/board.c下
我们将参考这部分代码进行对GPIO的设置
static void enable_vtt_regulator(void)
{
u32 temp;
/* enable module */
//#define writel(v,addr) __readwrite_bug("writel")
writel(GPIO_CTRL_ENABLEMODULE, AM33XX_GPIO5_BASE + OMAP_GPIO_CTRL);
/* enable output for GPIO5_7 */
writel(GPIO_SETDATAOUT(7),
AM33XX_GPIO5_BASE + OMAP_GPIO_SETDATAOUT);
temp = readl(AM33XX_GPIO5_BASE + OMAP_GPIO_OE);
temp = temp & ~(GPIO_OE_ENABLE(7));
writel(temp, AM33XX_GPIO5_BASE + OMAP_GPIO_OE);
}
这里的writel(),作用是:往内存映射的 I/O 空间上写数据,wirtel() I/O 上写入 32 位数据 (4字节)。
查找代码中的 Could not probe the EEPROM
puts("Debug in src/…/board.c late_init );
printf("Debug in src/…43xx/board.c read e2prom ");
经修改,在board.c中,vtt_regulator的初始化中暂时附带这个GPIO的输出操作
在board.c中的vtt_regulator函数中添加以下几句。
/* enable output for GPIO5_14 & GPIO5_15 */
puts("[Debug] Enable output for GPIO5_14 & GPIO5_15 For powerLED");
writel(GPIO_SETDATAOUT(14),
AM33XX_GPIO5_BASE + OMAP_GPIO_SETDATAOUT);
temp = readl(AM33XX_GPIO5_BASE + OMAP_GPIO_OE);
temp = temp & ~(GPIO_OE_ENABLE(14));
writel(temp, AM33XX_GPIO5_BASE + OMAP_GPIO_OE);
writel(GPIO_SETDATAOUT(15),
AM33XX_GPIO5_BASE + OMAP_GPIO_SETDATAOUT);
temp = readl(AM33XX_GPIO5_BASE + OMAP_GPIO_OE);
temp = temp & ~(GPIO_OE_ENABLE(15));
writel(temp, AM33XX_GPIO5_BASE + OMAP_GPIO_OE);
经过考虑,认为在uboot阶段,这个GPIO防止后续反转,将在late_init阶段进行GPIO的初始化设置。
检测IO
关于输入怎么写,我们可以关注一下Tronlong在gpio_direction_output的使用样例,与官方的代码风格对齐。
/* LED_SOM_D1 is indicator for U-BOOT to work on TL BOARD */
ret = gpio_direction_output(GPIO_LED_SOM_D1, 1);
我们跟踪了这个gpio_direction_output(),找到了其在omap_gpio.c中的原型。
并在其中找到了关于将IO引脚设成输入的方法。
看看别人用GPIO的方法
实现
首先在borad.c中添加各个管脚的宏定义,尽可能规范化一些
#define GPIO_LED_SOM_D1 (5*32+9)
/*Added to support THIS-PROJECT GPIO Group enable. */
下面是设置GPIO为input的函数原型
/**
* Set gpio direction as input
*/
int gpio_direction_input(unsigned gpio)
{
const struct gpio_bank *bank;
if (check_gpio(gpio) < 0)
return -1;
bank = get_gpio_bank(gpio);
_set_gpio_direction(bank, get_gpio_index(gpio), 1);
return 0;
}
拉高GPIO5_9以支持启动
我们要求在系统上电的512ms以内,完成一个引脚的拉高。
基于此,我们需要对添加的位置,结合代码进行分析。
首先,重提一下uboot的三个阶段。
在创龙的uboot启动过程中,与在x210上分散加载启动略有不同
在相同的SRAM启动过程中走完BL0之后,AM4379的boot首先是由一个轻量级uboot——SPL完成对uboot的引导,之后再进行BL1/BL2的。
那么如何在512ms内完成对GPIO的初始化呢?这里列出了三种不同的方案:
1、我们在SPL阶段完成这部分工作
2、我们在start.S到lowlevel_init阶段完成这部分工作
3、我们在启动第二阶段完成这部分工作。
结合源码分析一下:
第1种方案:实验过程中,在SPL下添加GPIO输出的时候会直接导致系统跑死。其原因是因为在GPIO设置输出之前没有对pinmux进行预配置。这部分工作在lowlevel_init中完成。
第2种方案:在串口第二次初始化之后对GPIO进行修改。
第3种方法:在板级初始化靠前的位置进行初始化。
笔者在实验中发现,在板级初始化中修改GPIO的时间约为200ms
这里出现了一个插曲:开始实验的时候没有任何打印信息出现就发生了系统挂起的问题,实际原因是由于BL0在寻找Uboot的存放位置耗费了时间。因此,在固化了系统到NandFlash之后,这个问题就得以解决了。
下面是修改的地方:
vi board/ti/am43xx/board.c
注释掉LED_SOM_D1相关的引脚拉高
这里是因为开发板官方占用了这个GPIO用作LED输出,现已经舍弃,即将其代码注释掉。
添加LT2950_KIL管脚的宏定义
#define GPIO_LT2950_KIL (5*32+9)
在比较靠前的地方进行GPIO拉高
static int read_eeprom(struct am43xx_board_id *header)
{
int ret;
//Added by Kaye. Set GPIO5_9 HIGH to support booting in THIS-PROJECT.
ret = gpio_direction_output(GPIO_LT2950_KIL, 1);
if (ret < 0)
printf("%s: gpio %d request failed %d\n", __func__,
GPIO_LT2950_KIL, ret);
printf("[DEBUG] pull 5-9 to HIGH,ret=%d\n",ret);
...
}
添加input检测引脚
每个mach针对自己的硬件结构实现了gpio_direction_input()函数
我们只管拿来用即可。
vi board/ti/am43xx/board.c
新增宏定义
//input
#define GPIO_PWR_SW_CHECK (5*32+0)
#define GPIO_LT2950_INT (5*32+1)
#define GPIO_CHG_END_STAT (5*32+12)
#define GPIO_CHG_ING_STAT (5*32+13)
//output
#define GPIO_LT2950_KIL (5*32+9)
#define GPIO_CHG_END_LED (5*32+14)
#define GPIO_CHG_ING_LED (5*32+15)
添加init函数
int ret;
//Added by Kaye. Set GPIO5_9 HIGH to support booting in THIS-PROJECT.
ret = gpio_direction_output(GPIO_LT2950_KIL, 1);
if (ret < 0)
printf("%s: gpio %d request failed %d\n", __func__,
GPIO_LT2950_KIL, ret);
printf("[DEBUG] pull 5-9 to HIGH,ret=%d\n",ret);
/*GPIO direction config */
//set LED output, pull them LOW
ret = gpio_direction_output(GPIO_CHG_END_LED, 0);
if (ret < 0)
printf("%s: gpio %d request failed %d\n", __func__,
GPIO_CHG_END_LED, ret);
ret = gpio_direction_output(GPIO_CHG_ING_LED, 0);
if (ret < 0)
printf("%s: gpio %d request failed %d\n", __func__,
GPIO_CHG_ING_LED, ret);
//set Charging detect IO.
ret = gpio_direction_input(GPIO_CHG_ING_STAT);
if (ret < 0)
printf("%s: gpio %d request failed %d\n", __func__,
GPIO_CHG_ING_STAT, ret);
ret = gpio_direction_input(GPIO_CHG_END_STAT);
if (ret < 0)
printf("%s: gpio %d request failed %d\n", __func__,
GPIO_CHG_END_STAT, ret);
//set PWR_SW_CHECK 开机按键按下检测管脚
ret = gpio_direction_input(GPIO_PWR_SW_CHECK);
if (ret < 0)
printf("%s: gpio %d request failed %d\n", __func__,
GPIO_PWR_SW_CHECK, ret);
//Added end. 2018/07/26
在dts中支持GPIO输出类的配置
在文件系统启动之后,可以添加如下命令对几个GPIO进行配置,通常在rcS下
# Create GPIO devices in /sys/class/gpio
echo 12 > export
echo 13 > export
echo 14 > export
echo 15 > export
经过验证后发现未能生效,应该是在dts里没有对新增的几个GPIO设备进行匹配
我们先在设备树添加2个LED驱动支持
led@12 {
label = "user-CAM_TRI";
gpios = <&gpio3 0 GPIO_ACTIVE_HIGH>; //D16 GPIO3_0 CAM_TRI
default-state = "off";
};
led@13 {
label = "user-CAM_TRI";
gpios = <&gpio3 0 GPIO_ACTIVE_HIGH>; //D16 GPIO3_0 CAM_TRI
default-state = "off";
};
led@14 {
label = "wifi-power";
gpios = <&gpio5 2 GPIO_ACTIVE_HIGH>; // WIFI模块供电使能(高有效)
default-state = "on";
};
led@15 {
label = "camera_power";
gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>; // CAM_PWR_EN相机供电使能(高有效)
default-state = "on";
};
/*Added by Kaye, 2018-07-26*/
在am437x对应的CPU手册“AM437x Sitara Processors.pdf”中搜索你需要添加的gpio管脚名称,例如:gpio0_2。
可见该管脚在芯片手册中的名称为mcasp0_axr1,为多功能复用管脚。gpio0_2为其第九个功能。所以在设备树中要将属性改为MUX_MODE9。
接着在am437x对应的CPU手册“AM437x_Technical Reference Manual.pdf”中搜索“mcasp0_axr1”。
芯片手册所在路径:
创龙AM437x光盘资料\datasheet\CPU\AM437x_Technical Reference Manual.pdf
可见其对应多功能服用配置寄存器CTRL_CONF_MCASP0_AXR1的偏移地址为0x9A8h,计算得其偏移地址为0x9A8h-0x800h=0x1A8h。
所以在设备树中做出如下修改:
实际更改如下:
//added by Kaye --20180726
0x180 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpio0_14.gpio0_14 */
0x184 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpio0_15.gpio0_15 */
0x144 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpio0_29.gpio0_29 */
0x228 (PIN_OUTPUT_PULLUP | MUX_MODE9) /* gpio5_2.gpio5_2 */
//added by Kaye end --20180726
因0x144的冲突,需要注释掉:
// Comment out by Kaye -- GPIO 5_2 used by WIFI_PWR_ENABLE
// uart5_pins: uart5_pins {
// pinctrl-single,pins = <
// 0x108 (PIN_INPUT | MUX_MODE3) /* mii1_col.uart5_rxd */
// 0x144 (PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* rmii1_refclk.uart5_txd */
// >;
// };
// Comment out END.
同样的,还有
// Comment out by kaye -- GPIO 5_2 used by charging LED
// uart1_pins: uart1_pins {
// pinctrl-single,pins = <
// 0x180 (PIN_INPUT | MUX_MODE0) /* uart1_rxd.uart1_rxd */
// 0x184 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* uart1_txd.uart1_txd */
// >;
// };
// Comment out END.
同样的,还有
// 0x228 (PIN_OUTPUT_PULLUP | MUX_MODE6) //H25 PWM4A led
// 0x228 (PIN_INPUT_PULLUP | MUX_MODE7) //gpio5_2
保存设备树文件,编译设备树并添加到启动设备中
这里值得注意的是,在tl437x平台上的mmc启动时,一定要将mmc中boot分区以及filesytem下的boot目录各自的dtb文件进行替换,否则可能会发生编译后无变化的奇怪现象。
经对brightness的写入测试,可支持对该引脚的控制
后记
Linux内核启动后,会把整个GPIO模块初始化,可以修改 Linux 底层驱动初始化的部分达到同样效果。