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;
/*
#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];
}
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", µvolt);
/* 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