text: /* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include "clock.h"
  #include "common.h"
  #include "console.h"
  #include "hooks.h"
  #include "hwtimer.h"
  #include "init_chip.h"
  #include "rdd.h"
  #include "registers.h"
  #include "system.h"
  #include "task.h"
  #include "timer.h"
  #include "util.h"
  
  #define CPRINTS(format, args...) cprints(CC_USB, format, ## args)
  
  /* What to do when we're just waiting */
  static enum {
  	DONT_KNOW,
  	IDLE_WFI,
  	IDLE_SLEEP,
  	IDLE_DEEP_SLEEP,
  	NUM_CHOICES
  } idle_action;
  
  #define EVENT_MIN 500
  
  static int idle_default;
  
  static const char const *idle_name[] = {
  	"invalid",
  	"wfi",
  	"sleep",
  	"deep sleep",
  };
  BUILD_ASSERT(ARRAY_SIZE(idle_name) == NUM_CHOICES);
  
  static int command_idle(int argc, char **argv)
  {
  	int i;
  
  	if (argc > 1) {
  		if (!strncasecmp("c", argv[1], 1)) {
  			GREG32(PMU, PWRDN_SCRATCH17) = 0;
  		} else if (console_is_restricted()) {
  			ccprintf("Console is locked, cannot set idle state\n");
  			return EC_ERROR_INVAL;
  		} else {
  			for (i = 1; i < ARRAY_SIZE(idle_name); i++)
  				if (!strncasecmp(idle_name[i], argv[1], 1)) {
  					idle_action = i;
  					break;
  				}
  		}
  	}
  
  	ccprintf("idle action: %s\n", idle_name[idle_action]);
  	ccprintf("deep sleep count: %u\n", GREG32(PMU, PWRDN_SCRATCH17));
  
  	return EC_SUCCESS;
  }
  DECLARE_SAFE_CONSOLE_COMMAND(idle, command_idle,
  			     "[w|s|d|c]",
  			     "Set idle action: wfi, sleep, deep sleep or "
  			     "Clear the deep sleep count");
  
  static int utmi_wakeup_is_enabled(void)
  {
  #ifdef CONFIG_RDD
  	return is_utmi_wakeup_allowed();
  #endif
  	return 1;
  }
  
  static void prepare_to_sleep(void)
  {
  	/* No task switching! */
  	interrupt_disable();
  
  	/* Enable all possible internal wake sources */
  	GR_PMU_EXITPD_MASK =
  		GC_PMU_EXITPD_MASK_PIN_PD_EXIT_MASK |
  		GC_PMU_EXITPD_MASK_RDD0_PD_EXIT_TIMER_MASK |
  		GC_PMU_EXITPD_MASK_RBOX_WAKEUP_MASK |
  		GC_PMU_EXITPD_MASK_TIMELS0_PD_EXIT_TIMER0_MASK |
  		GC_PMU_EXITPD_MASK_TIMELS0_PD_EXIT_TIMER1_MASK;
  
  	/* Clear the RBOX wakeup signal and status bits */
  	GREG32(RBOX, WAKEUP) = GC_RBOX_WAKEUP_CLEAR_MASK;
  	/* Wake on RBOX interrupts */
  	GREG32(RBOX, WAKEUP) = GC_RBOX_WAKEUP_ENABLE_MASK;
  
  	if (utmi_wakeup_is_enabled() && idle_action != IDLE_DEEP_SLEEP)
  		GR_PMU_EXITPD_MASK |=
  			GC_PMU_EXITPD_MASK_UTMI_SUSPEND_N_MASK;
  
  	/* Which rails should we turn off? */
  	GR_PMU_LOW_POWER_DIS =
  		GC_PMU_LOW_POWER_DIS_VDDIOF_MASK |
  		GC_PMU_LOW_POWER_DIS_VDDXO_MASK |
  		GC_PMU_LOW_POWER_DIS_JTR_RC_MASK;
  
  	/*
  	 * Deep sleep should only be enabled when the AP is off otherwise the
  	 * TPM state will lost.
  	 *
  	 * TODO(crosbug.com/p/55747): Enable deep sleep when the AP is shut
  	 * down. Currently deep sleep is only enabled through the console.
  	 */
  	if (idle_action == IDLE_DEEP_SLEEP) {
  		/* Clear upcoming events. They don't matter in deep sleep */
  		__hw_clock_event_clear();
  
  		/* Configure pins for deep sleep */
  		board_configure_deep_sleep_wakepins();
  
  		/* Make sure the usb clock is enabled */
  		clock_enable_module(MODULE_USB, 1);
  		/*
  		 * Preserve some state prior to deep sleep. Pretty much all we
  		 * need is the device address, since everything else can be
  		 * reinitialized on resume.
  		 */
  		GREG32(PMU, PWRDN_SCRATCH18) = GR_USB_DCFG;
  
  		/* Increment the deep sleep count */
  		GREG32(PMU, PWRDN_SCRATCH17) =
  			GREG32(PMU, PWRDN_SCRATCH17) + 1;
  
  		/* Latch the pinmux values */
  		GREG32(PINMUX, HOLD) = 1;
  
  		/* Clamp the USB pins and shut the PHY down. We have to do this
  		 * in three separate steps, or Bad Things happen. */
  		GWRITE_FIELD(USB, PCGCCTL, PWRCLMP, 1);
  		GWRITE_FIELD(USB, PCGCCTL, RSTPDWNMODULE, 1);
  		GWRITE_FIELD(USB, PCGCCTL, STOPPCLK, 1);
  
  		/* Shut down one more power rail for deep sleep */
  		GR_PMU_LOW_POWER_DIS |=
  			GC_PMU_LOW_POWER_DIS_VDDL_MASK;
  	}
  
  	/* The next "wfi" will trigger it */
  	GR_PMU_LOW_POWER_DIS |= GC_PMU_LOW_POWER_DIS_START_MASK;
  }
  
  /* This is for normal sleep only. Deep sleep resumes with a warm boot. */
  static void resume_from_sleep(void)
  {
  	/* Prevent accidental reentry */
  	GR_PMU_LOW_POWER_DIS = 0;
  
  	/* Allow task switching again */
  	interrupt_enable();
  }
  
  
  /* The time in the future at which sleeping will be allowed. */
  static timestamp_t next_sleep_time;
  
  /* Update the future sleep time. */
  void delay_sleep_by(uint32_t us)
  {
  	timestamp_t tmp = get_time();
  
  	tmp.val += us;
  	if (tmp.val > next_sleep_time.val)
  		next_sleep_time = tmp;
  }
  
  /* Wait a good long time after any console input, in case there's more. */
  void clock_refresh_console_in_use(void)
  {
  	delay_sleep_by(10 * SECOND);
  }
  
  void disable_deep_sleep(void)
  {
  	idle_action = idle_default;
  }
  
  void enable_deep_sleep(void)
  {
  	idle_action = IDLE_DEEP_SLEEP;
  }
  
  static void idle_init(void)
  {
  	/*
  	 * If bus obfuscation is enabled disable sleep.
  	 */
  	if ((GR_FUSE(OBFUSCATION_EN) == 5) ||
  	    (GR_FUSE(FW_DEFINED_BROM_APPLYSEC) & (1 << 3)) ||
  	    (runlevel_is_high() && GREAD(GLOBALSEC, OBFS_SW_EN))) {
  		CPRINTS("bus obfuscation enabled disabling sleep");
  		idle_default = IDLE_WFI;
  	} else {
  		idle_default = IDLE_SLEEP;
  	}
  }
  DECLARE_HOOK(HOOK_INIT, idle_init, HOOK_PRIO_DEFAULT - 1);
  
  /* Custom idle task, executed when no tasks are ready to be scheduled. */
  void __idle(void)
  {
  	int sleep_ok, sleep_delay_passed, next_evt_us;
  
  	/*
  	 * On init or resume from deep sleep set the idle action to default. If
  	 * it should be something else it will be determined during runtime.
  	 *
  	 * Before changing idle_action check that it is not already set. It is
  	 * possible that HOOK_CHIPSET_RESUME or SHUTDOWN were triggered before
  	 * this and set the idle_action.
  	 */
  	if (!idle_action)
  		idle_action = idle_default;
  
  	/* Disable sleep for 20 seconds after init */
  	delay_sleep_by(20 * SECOND);
  
  	while (1) {
  
  		/* Anyone still busy? */
  		sleep_ok = DEEP_SLEEP_ALLOWED;
  
  		/* Wait a bit, just in case */
  		sleep_delay_passed = timestamp_expired(next_sleep_time, 0);
  
  		/* Don't enable sleep if there is about to be an event */
  		next_evt_us = __hw_clock_event_get() - __hw_clock_source_read();
  
  		/* If it hasn't yet been long enough, check again when it is */
  		if (!sleep_delay_passed)
  			timer_arm(next_sleep_time, TASK_ID_IDLE);
  
  		/* We're allowed to sleep now, so set it up. */
  		if (sleep_ok && sleep_delay_passed && next_evt_us > EVENT_MIN)
  			if (idle_action != IDLE_WFI)
  				prepare_to_sleep();
  
  		/* Wait for the next irq event. This stops the CPU clock and
  		 * may trigger sleep or deep sleep if enabled. */
  		asm("wfi");
  
  		/*
  		 * Note: After resuming from normal sleep we should clear
  		 * PMU_LOW_POWER_DIS to prevent sleeping again by accident.
  		 * Normal sleep eventually resumes here after the waking
  		 * interrupt has been handled, but since all the other tasks
  		 * will get a chance to run first it might be some time before
  		 * that happens. If we find ourselves going back into sleep
  		 * unexpectedly, that might be why.
  		 */
  		resume_from_sleep();
  	}
  }
  