pm_esp32.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. // Copyright 2016-2017 Espressif Systems (Shanghai) PTE LTD
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #include <stdlib.h>
  15. #include <stdbool.h>
  16. #include <string.h>
  17. #include <sys/param.h>
  18. #include "esp_attr.h"
  19. #include "esp_err.h"
  20. #include "esp_pm.h"
  21. #include "esp_log.h"
  22. #include "esp_crosscore_int.h"
  23. #include "soc/rtc.h"
  24. #include "freertos/FreeRTOS.h"
  25. #include "freertos/xtensa_timer.h"
  26. #include "xtensa/core-macros.h"
  27. #include "pm_impl.h"
  28. #include "pm_trace.h"
  29. #include "esp_timer_impl.h"
  30. #include "esp32/pm.h"
  31. /* CCOMPARE update timeout, in CPU cycles. Any value above ~600 cycles will work
  32. * for the purpose of detecting a deadlock.
  33. */
  34. #define CCOMPARE_UPDATE_TIMEOUT 1000000
  35. #ifdef CONFIG_PM_PROFILING
  36. #define WITH_PROFILING
  37. #endif
  38. static portMUX_TYPE s_switch_lock = portMUX_INITIALIZER_UNLOCKED;
  39. /* The following state variables are protected using s_switch_lock: */
  40. /* Current sleep mode; When switching, contains old mode until switch is complete */
  41. static pm_mode_t s_mode = PM_MODE_CPU_MAX;
  42. /* True when switch is in progress */
  43. static volatile bool s_is_switching;
  44. /* When switch is in progress, this is the mode we are switching into */
  45. static pm_mode_t s_new_mode = PM_MODE_CPU_MAX;
  46. /* Number of times each mode was locked */
  47. static size_t s_mode_lock_counts[PM_MODE_COUNT];
  48. /* Bit mask of locked modes. BIT(i) is set iff s_mode_lock_counts[i] > 0. */
  49. static uint32_t s_mode_mask;
  50. /* Divider and multiplier used to adjust (ccompare - ccount) duration.
  51. * Only set to non-zero values when switch is in progress.
  52. */
  53. static uint32_t s_ccount_div;
  54. static uint32_t s_ccount_mul;
  55. /* Indicates to the ISR hook that CCOMPARE needs to be updated on the given CPU.
  56. * Used in conjunction with cross-core interrupt to update CCOMPARE on the other CPU.
  57. */
  58. static volatile bool s_need_update_ccompare[portNUM_PROCESSORS];
  59. /* When no RTOS tasks are active, these locks are released to allow going into
  60. * a lower power mode. Used by ISR hook and idle hook.
  61. */
  62. static esp_pm_lock_handle_t s_rtos_lock_handle[portNUM_PROCESSORS];
  63. /* A flag indicating that Idle hook has run on a given CPU;
  64. * Next interrupt on the same CPU will take s_rtos_lock_handle.
  65. */
  66. static bool s_core_idle[portNUM_PROCESSORS];
  67. /* g_ticks_us defined in ROM for PRO CPU */
  68. extern uint32_t g_ticks_per_us_pro;
  69. /* Lookup table of CPU frequencies to be used in each mode.
  70. * Modified by esp_pm_configure.
  71. */
  72. rtc_cpu_freq_t s_cpu_freq_by_mode[PM_MODE_COUNT] = {
  73. [PM_MODE_LIGHT_SLEEP] = (rtc_cpu_freq_t) -1, /* unused */
  74. [PM_MODE_APB_MIN] = RTC_CPU_FREQ_XTAL,
  75. [PM_MODE_APB_MAX] = RTC_CPU_FREQ_80M,
  76. [PM_MODE_CPU_MAX] = RTC_CPU_FREQ_80M,
  77. };
  78. /* Lookup table of CPU ticks per microsecond for each RTC_CPU_FREQ_ value.
  79. * Essentially the same as returned by rtc_clk_cpu_freq_value(), but without
  80. * the function call. Not const because XTAL frequency is only known at run time.
  81. */
  82. static uint32_t s_cpu_freq_to_ticks[] = {
  83. [RTC_CPU_FREQ_XTAL] = 0, /* This is set by esp_pm_impl_init */
  84. [RTC_CPU_FREQ_80M] = 80,
  85. [RTC_CPU_FREQ_160M] = 160,
  86. [RTC_CPU_FREQ_240M] = 240,
  87. [RTC_CPU_FREQ_2M] = 2
  88. };
  89. /* Lookup table of names for each RTC_CPU_FREQ_ value. Used for logging only. */
  90. static const char* s_freq_names[] __attribute__((unused)) = {
  91. [RTC_CPU_FREQ_XTAL] = "XTAL",
  92. [RTC_CPU_FREQ_80M] = "80",
  93. [RTC_CPU_FREQ_160M] = "160",
  94. [RTC_CPU_FREQ_240M] = "240",
  95. [RTC_CPU_FREQ_2M] = "2"
  96. };
  97. /* Whether automatic light sleep is enabled. Currently always false */
  98. static bool s_light_sleep_en = false;
  99. #ifdef WITH_PROFILING
  100. /* Time, in microseconds, spent so far in each mode */
  101. static pm_time_t s_time_in_mode[PM_MODE_COUNT];
  102. /* Timestamp, in microseconds, when the mode switch last happened */
  103. static pm_time_t s_last_mode_change_time;
  104. /* User-readable mode names, used by esp_pm_impl_dump_stats */
  105. static const char* s_mode_names[] = {
  106. "SLEEP",
  107. "APB_MIN",
  108. "APB_MAX",
  109. "CPU_MAX"
  110. };
  111. #endif // WITH_PROFILING
  112. static const char* TAG = "pm_esp32";
  113. static void update_ccompare();
  114. static void do_switch(pm_mode_t new_mode);
  115. static void leave_idle();
  116. static void on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us);
  117. pm_mode_t esp_pm_impl_get_mode(esp_pm_lock_type_t type, int arg)
  118. {
  119. (void) arg;
  120. if (type == ESP_PM_CPU_FREQ_MAX) {
  121. return PM_MODE_CPU_MAX;
  122. } else if (type == ESP_PM_APB_FREQ_MAX) {
  123. return PM_MODE_APB_MAX;
  124. } else if (type == ESP_PM_NO_LIGHT_SLEEP) {
  125. return PM_MODE_APB_MIN;
  126. } else {
  127. // unsupported mode
  128. abort();
  129. }
  130. }
  131. esp_err_t esp_pm_configure(const void* vconfig)
  132. {
  133. #ifndef CONFIG_PM_ENABLE
  134. return ESP_ERR_NOT_SUPPORTED;
  135. #endif
  136. const esp_pm_config_esp32_t* config = (const esp_pm_config_esp32_t*) vconfig;
  137. if (config->light_sleep_enable) {
  138. return ESP_ERR_NOT_SUPPORTED;
  139. }
  140. rtc_cpu_freq_t min_freq = config->min_cpu_freq;
  141. rtc_cpu_freq_t max_freq = config->max_cpu_freq;
  142. rtc_cpu_freq_t apb_max_freq; /* CPU frequency in APB_MAX mode */
  143. if (max_freq == RTC_CPU_FREQ_240M) {
  144. /* We can't switch between 240 and 80/160 without disabling PLL,
  145. * so use 240MHz CPU frequency when 80MHz APB frequency is requested.
  146. */
  147. apb_max_freq = RTC_CPU_FREQ_240M;
  148. } else {
  149. /* Otherwise (max CPU frequency is 80MHz or 160MHz), can use 80MHz
  150. * CPU frequency when 80MHz APB frequency is requested.
  151. */
  152. apb_max_freq = RTC_CPU_FREQ_80M;
  153. }
  154. apb_max_freq = MAX(apb_max_freq, min_freq);
  155. ESP_LOGI(TAG, "Frequency switching config: "
  156. "CPU_MAX: %s, APB_MAX: %s, APB_MIN: %s, Light sleep: %s",
  157. s_freq_names[max_freq],
  158. s_freq_names[apb_max_freq],
  159. s_freq_names[min_freq],
  160. config->light_sleep_enable ? "ENABLED" : "DISABLED");
  161. portENTER_CRITICAL(&s_switch_lock);
  162. s_cpu_freq_by_mode[PM_MODE_CPU_MAX] = max_freq;
  163. s_cpu_freq_by_mode[PM_MODE_APB_MAX] = apb_max_freq;
  164. s_cpu_freq_by_mode[PM_MODE_APB_MIN] = min_freq;
  165. s_light_sleep_en = config->light_sleep_enable;
  166. portEXIT_CRITICAL(&s_switch_lock);
  167. return ESP_OK;
  168. }
  169. static pm_mode_t IRAM_ATTR get_lowest_allowed_mode()
  170. {
  171. /* TODO: optimize using ffs/clz */
  172. if (s_mode_mask >= BIT(PM_MODE_CPU_MAX)) {
  173. return PM_MODE_CPU_MAX;
  174. } else if (s_mode_mask >= BIT(PM_MODE_APB_MAX)) {
  175. return PM_MODE_APB_MAX;
  176. } else if (s_mode_mask >= BIT(PM_MODE_APB_MIN) || !s_light_sleep_en) {
  177. return PM_MODE_APB_MIN;
  178. } else {
  179. return PM_MODE_LIGHT_SLEEP;
  180. }
  181. }
  182. void IRAM_ATTR esp_pm_impl_switch_mode(pm_mode_t mode,
  183. pm_mode_switch_t lock_or_unlock, pm_time_t now)
  184. {
  185. bool need_switch = false;
  186. uint32_t mode_mask = BIT(mode);
  187. portENTER_CRITICAL(&s_switch_lock);
  188. uint32_t count;
  189. if (lock_or_unlock == MODE_LOCK) {
  190. count = ++s_mode_lock_counts[mode];
  191. } else {
  192. count = s_mode_lock_counts[mode]--;
  193. }
  194. if (count == 1) {
  195. if (lock_or_unlock == MODE_LOCK) {
  196. s_mode_mask |= mode_mask;
  197. } else {
  198. s_mode_mask &= ~mode_mask;
  199. }
  200. need_switch = true;
  201. }
  202. pm_mode_t new_mode = s_mode;
  203. if (need_switch) {
  204. new_mode = get_lowest_allowed_mode();
  205. #ifdef WITH_PROFILING
  206. if (s_last_mode_change_time != 0) {
  207. pm_time_t diff = now - s_last_mode_change_time;
  208. s_time_in_mode[s_mode] += diff;
  209. }
  210. s_last_mode_change_time = now;
  211. #endif // WITH_PROFILING
  212. }
  213. portEXIT_CRITICAL(&s_switch_lock);
  214. if (need_switch && new_mode != s_mode) {
  215. do_switch(new_mode);
  216. }
  217. }
  218. /**
  219. * @brief Update clock dividers in esp_timer and FreeRTOS, and adjust CCOMPARE
  220. * values on both CPUs.
  221. * @param old_ticks_per_us old CPU frequency
  222. * @param ticks_per_us new CPU frequency
  223. */
  224. static void IRAM_ATTR on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us)
  225. {
  226. uint32_t old_apb_ticks_per_us = MIN(old_ticks_per_us, 80);
  227. uint32_t apb_ticks_per_us = MIN(ticks_per_us, 80);
  228. /* Update APB frequency value used by the timer */
  229. if (old_apb_ticks_per_us != apb_ticks_per_us) {
  230. esp_timer_impl_update_apb_freq(apb_ticks_per_us);
  231. }
  232. /* Calculate new tick divisor */
  233. _xt_tick_divisor = ticks_per_us * 1000000 / XT_TICK_PER_SEC;
  234. int core_id = xPortGetCoreID();
  235. if (s_rtos_lock_handle[core_id] != NULL) {
  236. ESP_PM_TRACE_ENTER(CCOMPARE_UPDATE, core_id);
  237. /* ccount_div and ccount_mul are used in esp_pm_impl_update_ccompare
  238. * to calculate new CCOMPARE value.
  239. */
  240. s_ccount_div = old_ticks_per_us;
  241. s_ccount_mul = ticks_per_us;
  242. /* Update CCOMPARE value on this CPU */
  243. update_ccompare();
  244. #if portNUM_PROCESSORS == 2
  245. /* Send interrupt to the other CPU to update CCOMPARE value */
  246. int other_core_id = (core_id == 0) ? 1 : 0;
  247. s_need_update_ccompare[other_core_id] = true;
  248. esp_crosscore_int_send_freq_switch(other_core_id);
  249. int timeout = 0;
  250. while (s_need_update_ccompare[other_core_id]) {
  251. if (++timeout == CCOMPARE_UPDATE_TIMEOUT) {
  252. assert(false && "failed to update CCOMPARE, possible deadlock");
  253. }
  254. }
  255. #endif // portNUM_PROCESSORS == 2
  256. s_ccount_mul = 0;
  257. s_ccount_div = 0;
  258. ESP_PM_TRACE_EXIT(CCOMPARE_UPDATE, core_id);
  259. }
  260. }
  261. /**
  262. * Perform the switch to new power mode.
  263. * Currently only changes the CPU frequency and adjusts clock dividers.
  264. * No light sleep yet.
  265. * @param new_mode mode to switch to
  266. */
  267. static void IRAM_ATTR do_switch(pm_mode_t new_mode)
  268. {
  269. const int core_id = xPortGetCoreID();
  270. do {
  271. portENTER_CRITICAL_ISR(&s_switch_lock);
  272. if (!s_is_switching) {
  273. break;
  274. }
  275. if (s_new_mode <= new_mode) {
  276. portEXIT_CRITICAL_ISR(&s_switch_lock);
  277. return;
  278. }
  279. if (s_need_update_ccompare[core_id]) {
  280. s_need_update_ccompare[core_id] = false;
  281. }
  282. portEXIT_CRITICAL_ISR(&s_switch_lock);
  283. } while (true);
  284. s_new_mode = new_mode;
  285. s_is_switching = true;
  286. portEXIT_CRITICAL_ISR(&s_switch_lock);
  287. rtc_cpu_freq_t old_freq = s_cpu_freq_by_mode[s_mode];
  288. rtc_cpu_freq_t new_freq = s_cpu_freq_by_mode[new_mode];
  289. if (new_freq != old_freq) {
  290. uint32_t old_ticks_per_us = g_ticks_per_us_pro;
  291. uint32_t new_ticks_per_us = s_cpu_freq_to_ticks[new_freq];
  292. bool switch_down = new_ticks_per_us < old_ticks_per_us;
  293. ESP_PM_TRACE_ENTER(FREQ_SWITCH, core_id);
  294. if (switch_down) {
  295. on_freq_update(old_ticks_per_us, new_ticks_per_us);
  296. }
  297. rtc_clk_cpu_freq_set_fast(new_freq);
  298. if (!switch_down) {
  299. on_freq_update(old_ticks_per_us, new_ticks_per_us);
  300. }
  301. ESP_PM_TRACE_EXIT(FREQ_SWITCH, core_id);
  302. }
  303. portENTER_CRITICAL_ISR(&s_switch_lock);
  304. s_mode = new_mode;
  305. s_is_switching = false;
  306. portEXIT_CRITICAL_ISR(&s_switch_lock);
  307. }
  308. /**
  309. * @brief Calculate new CCOMPARE value based on s_ccount_{mul,div}
  310. *
  311. * Adjusts CCOMPARE value so that the interrupt happens at the same time as it
  312. * would happen without the frequency change.
  313. * Assumes that the new_frequency = old_frequency * s_ccount_mul / s_ccount_div.
  314. */
  315. static void IRAM_ATTR update_ccompare()
  316. {
  317. const uint32_t ccompare_min_cycles_in_future = 1000;
  318. uint32_t ccount = XTHAL_GET_CCOUNT();
  319. uint32_t ccompare = XTHAL_GET_CCOMPARE(XT_TIMER_INDEX);
  320. if ((ccompare - ccompare_min_cycles_in_future) - ccount < UINT32_MAX / 2) {
  321. uint32_t diff = ccompare - ccount;
  322. uint32_t diff_scaled = (diff * s_ccount_mul + s_ccount_div - 1) / s_ccount_div;
  323. if (diff_scaled < _xt_tick_divisor) {
  324. uint32_t new_ccompare = ccount + diff_scaled;
  325. XTHAL_SET_CCOMPARE(XT_TIMER_INDEX, new_ccompare);
  326. }
  327. }
  328. }
  329. static void IRAM_ATTR leave_idle()
  330. {
  331. int core_id = xPortGetCoreID();
  332. if (s_core_idle[core_id]) {
  333. // TODO: possible optimization: raise frequency here first
  334. esp_pm_lock_acquire(s_rtos_lock_handle[core_id]);
  335. s_core_idle[core_id] = false;
  336. }
  337. }
  338. void esp_pm_impl_idle_hook()
  339. {
  340. int core_id = xPortGetCoreID();
  341. uint32_t state = portENTER_CRITICAL_NESTED();
  342. if (!s_core_idle[core_id]) {
  343. esp_pm_lock_release(s_rtos_lock_handle[core_id]);
  344. s_core_idle[core_id] = true;
  345. }
  346. portEXIT_CRITICAL_NESTED(state);
  347. ESP_PM_TRACE_ENTER(IDLE, core_id);
  348. }
  349. void IRAM_ATTR esp_pm_impl_isr_hook()
  350. {
  351. int core_id = xPortGetCoreID();
  352. ESP_PM_TRACE_ENTER(ISR_HOOK, core_id);
  353. #if portNUM_PROCESSORS == 2
  354. if (s_need_update_ccompare[core_id]) {
  355. update_ccompare();
  356. s_need_update_ccompare[core_id] = false;
  357. } else {
  358. leave_idle();
  359. }
  360. #else
  361. leave_idle();
  362. #endif // portNUM_PROCESSORS == 2
  363. ESP_PM_TRACE_EXIT(ISR_HOOK, core_id);
  364. }
  365. #ifdef WITH_PROFILING
  366. void esp_pm_impl_dump_stats(FILE* out)
  367. {
  368. pm_time_t time_in_mode[PM_MODE_COUNT];
  369. portENTER_CRITICAL_ISR(&s_switch_lock);
  370. memcpy(time_in_mode, s_time_in_mode, sizeof(time_in_mode));
  371. pm_time_t last_mode_change_time = s_last_mode_change_time;
  372. pm_mode_t cur_mode = s_mode;
  373. pm_time_t now = pm_get_time();
  374. portEXIT_CRITICAL_ISR(&s_switch_lock);
  375. time_in_mode[cur_mode] += now - last_mode_change_time;
  376. for (int i = 0; i < PM_MODE_COUNT; ++i) {
  377. fprintf(out, "%8s %12lld %2d%%\n",
  378. s_mode_names[i],
  379. time_in_mode[i],
  380. (int) (time_in_mode[i] * 100 / now));
  381. }
  382. }
  383. #endif // WITH_PROFILING
  384. void esp_pm_impl_init()
  385. {
  386. s_cpu_freq_to_ticks[RTC_CPU_FREQ_XTAL] = rtc_clk_xtal_freq_get();
  387. #ifdef CONFIG_PM_TRACE
  388. esp_pm_trace_init();
  389. #endif
  390. ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos0",
  391. &s_rtos_lock_handle[0]));
  392. ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[0]));
  393. #if portNUM_PROCESSORS == 2
  394. ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos1",
  395. &s_rtos_lock_handle[1]));
  396. ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[1]));
  397. #endif // portNUM_PROCESSORS == 2
  398. }