3.14内核下GPIO使能(同时支持uboot阶段的配置)

整体简介

如何在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;
                    }
            }
    }
序号原引脚新引脚备注bootkernelapp
1GPIO0_12CHG_END_STAT充电完成检测管脚
2GPIO0_13CHG_ING_STAT正在充电检测管脚
3GPIO0_14CHG_END_LED充电完成指示灯
4GPIO0_15CHG_ING_LED正在充电指示灯

结构分析

U-Boot的GPIO系统设计简洁而高效,它采用三层架构来实现功能:

  1. 顶层接口层:这一层面向用户提供了一套通用的接口定义,但并不涉及具体的实现细节。实现工作需要根据具体的芯片型号来定制开发。

  2. 中间接口实现层:在这一层,我们利用特定硬件板的GPIO特性来具体实现顶层定义的接口。

  3. 底层芯片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 底层驱动初始化的部分达到同样效果。

在U-Boot中,可以使用GPIO API对GPIO口进行配置和操作。 首先,在U-Boot配置文件中,需要开启GPIO选项: ``` CONFIG_DM_GPIO=y ``` 然后,在U-Boot中可以使用以下API对GPIO进行操作: 1. `dm_gpio_lookup_name(const char *name, struct gpio_desc *desc)`:通过GPIO名称获取GPIO描述符,将GPIO描述符存储在`desc`中。 2. `dm_gpio_request(struct gpio_desc *desc, const char *label)`:申请GPIO,将GPIO描述符`desc`与一个字符串`label`关联起来。 3. `dm_gpio_set_dir(struct gpio_desc *desc, enum gpio_direction direction)`:设置GPIO方向,`direction`参数可以是`GPIO_DIRECTION_OUT`或`GPIO_DIRECTION_IN`。 4. `dm_gpio_set_value(struct gpio_desc *desc, int value)`:设置GPIO输出值,`value`参数可以是`0`或`1`。 5. `dm_gpio_get_value(struct gpio_desc *desc)`:获取GPIO输入值,返回值可以是`0`或`1`。 例如,下面是一个设置GPIO口为输出模式、输出高电平的示例: ``` #include <common.h> #include <dm.h> #include <gpio.h> int do_gpio_test(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { struct gpio_desc desc; int ret; ret = dm_gpio_lookup_name("GPIO0_0", &desc); if (ret) { printf("Failed to lookup GPIO0_0\n"); return CMD_RET_FAILURE; } ret = dm_gpio_request(&desc, "gpio_test"); if (ret) { printf("Failed to request GPIO0_0\n"); return CMD_RET_FAILURE; } ret = dm_gpio_set_dir(&desc, GPIO_DIRECTION_OUT); if (ret) { printf("Failed to set GPIO0_0 direction\n"); dm_gpio_free(&desc); return CMD_RET_FAILURE; } ret = dm_gpio_set_value(&desc, 1); if (ret) { printf("Failed to set GPIO0_0 value\n"); dm_gpio_free(&desc); return CMD_RET_FAILURE; } dm_gpio_free(&desc); return CMD_RET_SUCCESS; } ``` 在上面的示例中,`dm_gpio_lookup_name`函数获取GPIO描述符,`dm_gpio_request`函数申请GPIO,`dm_gpio_set_dir`函数设置GPIO方向,`dm_gpio_set_value`函数设置输出值,`dm_gpio_free`函数释放GPIO资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值