AM335x WakeUp&Sleep

Posted by Jason Blog on April 28, 2017

最近在调试AM335X的休眠唤醒功能,可算是把我折腾惨了.

我们的需求是外部点火信号即ACC能唤醒睡眠中的系统,我们调试的时候用的是GPIO按键模拟ACC的方式.

第一个坑

首先根据AM335X的RM-25.2-Integration介绍,只有BANK0的GPIO才能配置成唤醒源.

With four GPIO modules, the device allows for a maximum of 128 GPIO pins. (The exact number available varies as a function of the device configuration and pin muxing.) GPIO0 is in the Wakeup domain and may be used to wakeup the device via external sources. GPIO[1:3] are located in the peripheral domain.

即GPIO0可以作为Wakeup domain.而GPIO[1:3]则是peripheral domain.

在调试的时候,查看唤醒源.

cat /sys/kernel/debug/wakeup_sources
name            active_count    event_count     hit_count       active_since         
gpio-keys       0               0               0              0                    
am33xx-rtc      0               0    			0				0			

可以看见gpio-keys和rtc是配置成了唤醒源的,而且当按下按键的时候,系统是能够检测到的,说明GPIO相关的配置是没有问题的.

第二个坑

由于调了两天,始终毫无进展,所以我们换了上一版本的底板,发现居然能正常唤醒.经过对比,开始以为或许是因为新板子采用了特殊的电源管理,休眠的时候对GPIO有影响,最后发现不是,呵呵.最终”元凶”是4G模块,当系统进入休眠后,4G模块任然占有USB总线.最后在休眠之前先把4G模块电关掉,就能正常睡眠和唤醒了,呵呵!

电源管理这部分有空在写篇博客好好分析下.不过可以先看看AM335X休眠的一个关键函数.


static int am33xx_pm_suspend(void)
{
	int state, ret = 0;

	struct omap_hwmod *gpmc_oh, *usb_oh, *gpio1_oh, *rtc_oh;

	usb_oh		= omap_hwmod_lookup("usb_otg_hs");
	gpmc_oh		= omap_hwmod_lookup("gpmc");
	gpio1_oh	= omap_hwmod_lookup("gpio1");	/* WKUP domain GPIO */
	rtc_oh		= omap_hwmod_lookup("rtc");

	omap_hwmod_enable(usb_oh);
	omap_hwmod_enable(gpmc_oh);
	
	/*
	 * Keep USB module enabled during standby
	 * to enable USB remote wakeup
	 * Note: This will result in hard-coding USB state
	 * during standby
	 */

	if (suspend_state != PM_SUSPEND_STANDBY)
	   omap_hwmod_idle(usb_oh);

	omap_hwmod_idle(gpmc_oh);

	/*
	 * Keep RTC module enabled during standby
	 * for PG2.x to enable wakeup from RTC.
	 */
	if ((omap_rev() >= AM335X_REV_ES2_0) &&
		(suspend_state == PM_SUSPEND_STANDBY)){
		omap_hwmod_enable(rtc_oh);
	}

	/*
	 * Disable the GPIO module. This ensure that
	 * only sWAKEUP interrupts to Cortex-M3 get generated
	 *
	 * XXX: EVM_SK uses a GPIO0 pin for VTP control
	 * in suspend and hence we can't do this for EVM_SK
	 * alone. The side-effect of this is that GPIO wakeup
	 * might have issues. Refer to commit 672639b for the
	 * details
	 */
	/*
	 * Keep GPIO0 module enabled during standby to
	 * support wakeup via GPIO0 keys.
	 */
	if ((suspend_cfg_param_list[EVM_ID] != EVM_SK) && (suspend_state != PM_SUSPEND_STANDBY)){
		omap_hwmod_idle(gpio1_oh);
	}

	/*
	 * Update Suspend_State value to be used in sleep33xx.S to keep
	 * GPIO0 module enabled during standby for EVM-SK
	 */
	if (suspend_state == PM_SUSPEND_STANDBY)
		suspend_cfg_param_list[SUSPEND_STATE] = PM_STANDBY;
	else
		suspend_cfg_param_list[SUSPEND_STATE] = PM_DS0;

#if 0  /*by szf*/
	/*
	 * Keep Touchscreen module enabled during standby
	 * to enable wakeup from standby.
	 */

	if (suspend_state == PM_SUSPEND_STANDBY)
		writel(0x2, AM33XX_CM_WKUP_ADC_TSC_CLKCTRL);
#endif

	if (gfx_l3_clkdm && gfx_l4ls_clkdm) {
		clkdm_sleep(gfx_l3_clkdm);
		clkdm_sleep(gfx_l4ls_clkdm);
	}

	/* Try to put GFX to sleep */
	if (gfx_pwrdm)
		pwrdm_set_next_pwrst(gfx_pwrdm, PWRDM_POWER_OFF);
	else
		pr_err("Could not program GFX to low power state\n");

	omap3_intc_suspend();

	am33xx_standby_setup(suspend_state);

	writel(0x0, AM33XX_CM_MPU_MPU_CLKCTRL);

	ret = cpu_suspend(0, am33xx_do_sram_idle);

	writel(0x2, AM33XX_CM_MPU_MPU_CLKCTRL);

	if (gfx_pwrdm) {
		state = pwrdm_read_pwrst(gfx_pwrdm);
		if (state != PWRDM_POWER_OFF)
			pr_err("GFX domain did not transition to low power state\n");
		else
			pr_info("GFX domain entered low power state\n");
	}

	/* XXX: Why do we need to wakeup the clockdomains? */
	if(gfx_l3_clkdm && gfx_l4ls_clkdm) {
		clkdm_wakeup(gfx_l3_clkdm);
		clkdm_wakeup(gfx_l4ls_clkdm);
	}

	/*
	 * Touchscreen module was enabled during standby
	 * Disable it here.
	 */
	if (suspend_state == PM_SUSPEND_STANDBY)
		writel(0x0, AM33XX_CM_WKUP_ADC_TSC_CLKCTRL);

	/*
	 * Put USB module to idle on resume from standby
	 */

	if (suspend_state == PM_SUSPEND_STANDBY)
		omap_hwmod_idle(usb_oh);

	/*
	 * Put RTC module to idle on resume from standby
	 * for PG2.x.
	 */
	if ((omap_rev() >= AM335X_REV_ES2_0) && (suspend_state == PM_SUSPEND_STANDBY))
		omap_hwmod_idle(rtc_oh);

	ret = am33xx_verify_lp_state(ret);

	/*
	 * Enable the GPIO module. Once the driver is
	 * fully adapted to runtime PM this will go away
	 */
	/*suspend_enter
	 * During standby, GPIO was not disabled. Hence no
	 * need to enable it here.
	 */
	if ((suspend_cfg_param_list[EVM_ID] != EVM_SK) &&
			(suspend_state != PM_SUSPEND_STANDBY))
		omap_hwmod_enable(gpio1_oh);

	return ret;
}

这个函数就是系统进入休眠之前所做的相关工作(板级相关的),不同的板子有不同的实现.可以看见里面把USB和GPIO还有触摸屏作为了唤醒源,具体实现,以后再分析.

第三个坑

是关于I2C设备的休眠问题,我们板子原来设计是可以单独开关I2C设备(PCA9555和BMI160),为了实现更低功耗,在休眠前就把这两个设备的电关掉,其实这样做是不对的,I2C设备的休眠唤醒应该在驱动中加,而且I2C设备是不建议单独开关电的.如果在休眠之前就把I2C设备电关掉,那在进入休眠的时候I2C总线会扫描设备,这样就会出错.