RK3326 RESET按键进入loader

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

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


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

  1. uboot何时会检测Recovery动作

  2. 检测条件

    首先第一点,分析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模式检测
  1. 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链表,驱动任务结束。

  1. boot_mode.c
    download模式检测时,调用key_read(KEY_VOLUMEUP)获取按键状态,检测的重点就是该函数。
    当检测到KEY_VOLUMEUP是KEY_PRESS_DOWN或者KEY_PRESS_LONG_DOWN时,才会进入download loader模式,否则正常启动。
  2. 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->adcvaladcval
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_DOWNKEY_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
  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值