瑞芯微Rockchip开发者社区
直播中

王玉兰

7年用户 1277经验值
私信 关注
[经验]

RK3326 RESET按键使其开机进入Loader模式

Platform:Android 8.1
SoC:RK3326
UBOOT:U-Boot 2017.09

需求:RESET和Recovery按键接在一起,RESET可以复用为Recovery脚,使其开机能进入Loader模式。

开机进入系统后,RESET为复位键。
开机前uboot按键检测阶段,RESET为Recovery键。
uboot何时会检测Recovery动作

检测条件

首先第一点,分析uboot启动代码,这里从board.c入手,硬件初始化完后执行board_late_init(),在文件arch/arm/mach-rockchip/board.c中:

int board_late_init(void)
{
#if (CONFIG_ROCKCHIP_BOOT_MODE_REG > 0)
setup_boot_mode();
#endif

#ifdef CONFIG_DM_CHARGE_DISPLAY
charge_display();
#endif

#ifdef CONFIG_DRM_ROCKCHIP
rockchip_show_logo();
#endif
rockchip_set_serialno();

soc_clk_dump();

return rk_board_late_init();

}

CONFIG_ROCKCHIP_BOOT_MODE_REG定义为0xff010200,接下来执行boot模式检测。

在boot_mode.c中:

int setup_boot_mode(void)
{
int boot_mode = BOOT_MODE_NORMAL;
char env_preboot[256] = {0};

devtype_num_envset();
rockchip_dnl_mode_check();

}

rockchip_dnl_mode_check()检测download行为,条件为真时执行download loader模式

void rockchip_dnl_mode_check(void)
{
if (rockchip_dnl_key_pressed()) {
if (rockchip_u2phy_vbus_detect()) {
printf("download key pressed, entering download mode...\n");
/* If failed, we fall back to bootrom download mode */
run_command_list("rockusb 0 {devtype} {devnum}", -1, 0);
set_back_to_bootrom_dnl_flag();
do_reset(NULL, 0, 0, NULL);
} else {
printf("recovery key pressed, entering recovery mode!\n");
env_set("reboot_mode", "recovery");
}
}
}

主要函数为rockchip_dnl_key_pressed(void)

__weak int rockchip_dnl_key_pressed(void)
{
int keyval = false;

/*

  • This is a generic interface to read key
    */
    #if defined(CONFIG_DM_KEY)
    keyval = key_read(KEY_VOLUMEUP);

    return key_is_pressed(keyval);

#elif defined(CONFIG_ADC)
const void *blob = gd->fdt_blob;
unsigned int val;
int channel = 1;
int node;
int ret;
u32 chns[2];

node = fdt_node_offset_by_compatible(blob, 0, "adc-keys");
if (node >= 0) {
       if (!fdtdec_get_int_array(blob, node, "io-channels", chns, 2))
	       channel = chns[1];
}

/*读取当前channel的键值*/
ret = adc_channel_single_shot("saradc", channel, &val);
if (ret) {
	printf("%s adc_channel_single_shot fail! ret=%d\n", __func__, ret);
	return false;
}

if ((val >= KEY_DOWN_MIN_VAL) && (val <= KEY_DOWN_MAX_VAL))
	return true;
else
	return false;

#endif

return keyval;

}

一、CONFIG_DM_KEY

由于平台默认开启了CONFIG_DM_KEY,因此会调用key-uclass,这里涉及以下及部分代码:

adc_key.c ------> 读取dts里面的adc配置
key-uclass.c ----> 提供api读取判断按键状态
boot_mode.c ----> download模式检测

adc_key.c

要使用按键检测,平台默认为判断KEY_VOLUMEUP按键,因此dts需要配置如下:

adc-keys {
status = "okay";
compatible = "adc-keys";
io-channels = <&saradc 2>;
io-channel-names = "buttons";
poll-interval = <100>;
keyup-threshold-microvolt = <1800000>;

vol-up-key {
linux,code = <KEY_VOLUMEUP>;
label = "volume up";
press-threshold-microvolt = <40>;
};
};

注:press-threshold-microvolt可以灵活配置,最终目的是通过key-uclass的键值域检测

dts配置好后,adc_key驱动会读取并将该key信息加入由key-uclass维护的key_list链表,驱动任务结束。

boot_mode.c

download模式检测时,调用key_read(KEY_VOLUMEUP)获取按键状态,检测的重点就是该函数。

当检测到KEY_VOLUMEUP是KEY_PRESS_DOWN或者KEY_PRESS_LONG_DOWN时,才会进入download loader模式,否则正常启动。

key-uclass.c

现在来看看按键状态检测的重点:key_read()

int key_read(int code)
{
struct udevice *dev;
struct input_key *key;
static int initialized;
unsigned int adcval;
int keyval = KEY_NOT_EXIST;
int found = 0, ret;

/* Initialize all key drivers */
if (!initialized) {
for (uclass_first_device(UCLASS_KEY, &dev);
dev;
uclass_next_device(&dev)) {
debug("%s: dev.name = %s\n", func, dev->name);
;
}
}

/* Search on the key list */
list_for_each_entry(key, &key_list, link) {
if (key->code == code) {
found = 1;
break;
}
}
if (!found)
goto out;

/* Is a adc key? /
if (key->type & ADC_KEY) {
ret = adc_channel_single_shot("saradc", key->channel, &adcval);
if (ret)
printf("%s: failed to read saradc, ret=%d\n",
key->name, ret);
else
keyval = key_read_adc_simple_event(key, adcval);
/
Is a gpio key? /
} else if (key->type & GPIO_KEY) {
/
All pwrkey must register as an interrupt event */
if (key->code == KEY_POWER) {
keyval = key_read_gpio_interrupt_event(key);
} else {
keyval = key_read_gpio_simple_event(key);
}
} else {
printf("%s: invalid key type!\n", func);
}

debug("%s: key.name=%s, code=%d, keyval=%d\n",
func, key->name, key->code, keyval);
out:
return keyval;
}

首先,会从链表key_list中查找是否有KEY_VOLUMEUP按键,若dts中没有配置KEY_VOLUMEUP按键,程序返回KEY_NOT_EXIST;若dts中有配置KEY_VOLUMEUP按键,在adc_key初始化时已经将按键信息假如到链表key_list中,这里found就会置1,程序继续执行。

因为配置的是adc按键类型,先获取当前adc键值然后调用key_read_adc_simple_event()来真正的检测按键状态:

/*

What's simple and complex event mean?

simple event: key press down or none;

complext event: key press down, long down or none;
*/
static int key_read_adc_simple_event(struct input_key *key, unsigned int adcval)
{
int max, min, margin = 30;
int keyval;

/* Get min, max */
max = key->adcval + margin;
if (key->adcval > margin)
min = key->adcval - margin;
else
min = 0;

debug("%s: %s: val=%d, max=%d, min=%d, adcval=%d\n",
func, key->name, key->adcval, max, min, adcval);

/* Check */
if ((adcval <= max) && (adcval >= min)) {
keyval = KEY_PRESS_DOWN;
debug("%s key pressed..\n", key->name);
} else {
keyval = KEY_PRESS_NONE;
}

return keyval;
}

有两个值:key->adcval和adcval

key->adcval是从dts中获取到的按键press-threshold-microvolt信息,经过以下处理:

ofnode_read_u32(node, "press-threshold-microvolt", &microvolt);
/* Convert microvolt to adc value */
key->adcval = microvolt / (key->vref / 1024);

adcval是读取到的当前adc键值

先根据key->adcval得出按键检测域的最大值和最小值,再通过当前adc键值adcval和这两个值对比,若在区间内,则认为按键是按下的,返回按键状态KEY_PRESS_DOWN。若不在区间内,则返回按键状态KEY_PRESS_NONE。

因此,key_read(KEY_VOLUMEUP)有两种状态(GPIO按键暂不讨论):
KEY_PRESS_DOWN和KEY_PRESS_NONE

最后在boot_mode中,调用key_is_pressed(keyval)

int key_is_pressed(int keyval)
{
return (keyval == KEY_PRESS_DOWN || keyval == KEY_PRESS_LONG_DOWN);
}

如果按键是KEY_PRESS_DOWN或KEY_PRESS_LONG_DOWN(按键为GPIO类型)时,返回true

注:由于硬件原因,RESET开机时的adc值为36,因此需要修改默认的margin为40,否则也检测不到按键按下。

二、CONFIG_ADC

uboot当中还有另外一种方式检测download模式,使用该方式首先需要关闭CONFIG_DM_KEY,默认是开启的。

该方式检测更简单,通过对比adc键值是否在KEY_DOWN_MIN_VAL和KEY_DOWN_MAX_VAL区间内来判断按键是否按下,并且不会判断按键code,代码如下:

#define KEY_DOWN_MIN_VAL 0
#define KEY_DOWN_MAX_VAL 30

if ((val >= KEY_DOWN_MIN_VAL) && (val <= KEY_DOWN_MAX_VAL))
return true;
else
return false;

因此,当键值val在区间KEY_DOWN_MIN_VAL和KEY_DOWN_MAX_VAL中时返回true,否则返回false。

注:同样需要修改KEY_DOWN_MAX_VAL为40,但是dts中可以不用配置按键code,只需要配置adc需要检测的channel即可。

三、总结

两种方式都可以满足RESET复用为Recovery使用,各有各的特点。
相同点:

需要使能adc-keys
根据硬件需求修改MAX为40
不同点:

CONFIG_ADC不需要添加按键KEY_VOLUMEUP

原作者:HelloBirthday

更多回帖

发帖
×
20
完善资料,
赚取积分