pm_esp32.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  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. * Initialized by esp_pm_impl_init and modified by esp_pm_configure.
  71. */
  72. rtc_cpu_freq_t s_cpu_freq_by_mode[PM_MODE_COUNT];
  73. /* Lookup table of CPU ticks per microsecond for each RTC_CPU_FREQ_ value.
  74. * Essentially the same as returned by rtc_clk_cpu_freq_value(), but without
  75. * the function call. Not const because XTAL frequency is only known at run time.
  76. */
  77. static uint32_t s_cpu_freq_to_ticks[] = {
  78. [RTC_CPU_FREQ_XTAL] = 0, /* This is set by esp_pm_impl_init */
  79. [RTC_CPU_FREQ_80M] = 80,
  80. [RTC_CPU_FREQ_160M] = 160,
  81. [RTC_CPU_FREQ_240M] = 240,
  82. [RTC_CPU_FREQ_2M] = 2
  83. };
  84. /* Lookup table of names for each RTC_CPU_FREQ_ value. Used for logging only. */
  85. static const char* s_freq_names[] __attribute__((unused)) = {
  86. [RTC_CPU_FREQ_XTAL] = "XTAL",
  87. [RTC_CPU_FREQ_80M] = "80",
  88. [RTC_CPU_FREQ_160M] = "160",
  89. [RTC_CPU_FREQ_240M] = "240",
  90. [RTC_CPU_FREQ_2M] = "2"
  91. };
  92. /* Whether automatic light sleep is enabled. Currently always false */
  93. static bool s_light_sleep_en = false;
  94. #ifdef WITH_PROFILING
  95. /* Time, in microseconds, spent so far in each mode */
  96. static pm_time_t s_time_in_mode[PM_MODE_COUNT];
  97. /* Timestamp, in microseconds, when the mode switch last happened */
  98. static pm_time_t s_last_mode_change_time;
  99. /* User-readable mode names, used by esp_pm_impl_dump_stats */
  100. static const char* s_mode_names[] = {
  101. "SLEEP",
  102. "APB_MIN",
  103. "APB_MAX",
  104. "CPU_MAX"
  105. };
  106. #endif // WITH_PROFILING
  107. static const char* TAG = "pm_esp32";
  108. static void update_ccompare();
  109. static void do_switch(pm_mode_t new_mode);
  110. static void leave_idle();
  111. static void on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us);
  112. pm_mode_t esp_pm_impl_get_mode(esp_pm_lock_type_t type, int arg)
  113. {
  114. (void) arg;
  115. if (type == ESP_PM_CPU_FREQ_MAX) {
  116. return PM_MODE_CPU_MAX;
  117. } else if (type == ESP_PM_APB_FREQ_MAX) {
  118. return PM_MODE_APB_MAX;
  119. } else if (type == ESP_PM_NO_LIGHT_SLEEP) {
  120. return PM_MODE_APB_MIN;
  121. } else {
  122. // unsupported mode
  123. abort();
  124. }
  125. }
  126. esp_err_t esp_pm_configure(const void* vconfig)
  127. {
  128. #ifndef CONFIG_PM_ENABLE
  129. return ESP_ERR_NOT_SUPPORTED;
  130. #endif
  131. const esp_pm_config_esp32_t* config = (const esp_pm_config_esp32_t*) vconfig;
  132. if (config->light_sleep_enable) {
  133. return ESP_ERR_NOT_SUPPORTED;
  134. }
  135. rtc_cpu_freq_t min_freq = config->min_cpu_freq;
  136. rtc_cpu_freq_t max_freq = config->max_cpu_freq;
  137. rtc_cpu_freq_t apb_max_freq; /* CPU frequency in APB_MAX mode */
  138. if (max_freq == RTC_CPU_FREQ_240M) {
  139. /* We can't switch between 240 and 80/160 without disabling PLL,
  140. * so use 240MHz CPU frequency when 80MHz APB frequency is requested.
  141. */
  142. apb_max_freq = RTC_CPU_FREQ_240M;
  143. } else {
  144. /* Otherwise (max CPU frequency is 80MHz or 160MHz), can use 80MHz
  145. * CPU frequency when 80MHz APB frequency is requested.
  146. */
  147. apb_max_freq = RTC_CPU_FREQ_80M;
  148. }
  149. apb_max_freq = MAX(apb_max_freq, min_freq);
  150. ESP_LOGI(TAG, "Frequency switching config: "
  151. "CPU_MAX: %s, APB_MAX: %s, APB_MIN: %s, Light sleep: %s",
  152. s_freq_names[max_freq],
  153. s_freq_names[apb_max_freq],
  154. s_freq_names[min_freq],
  155. config->light_sleep_enable ? "ENABLED" : "DISABLED");
  156. portENTER_CRITICAL(&s_switch_lock);
  157. s_cpu_freq_by_mode[PM_MODE_CPU_MAX] = max_freq;
  158. s_cpu_freq_by_mode[PM_MODE_APB_MAX] = apb_max_freq;
  159. s_cpu_freq_by_mode[PM_MODE_APB_MIN] = min_freq;
  160. s_cpu_freq_by_mode[PM_MODE_LIGHT_SLEEP] = min_freq;
  161. s_light_sleep_en = config->light_sleep_enable;
  162. portEXIT_CRITICAL(&s_switch_lock);
  163. return ESP_OK;
  164. }
  165. static pm_mode_t IRAM_ATTR get_lowest_allowed_mode()
  166. {
  167. /* TODO: optimize using ffs/clz */
  168. if (s_mode_mask >= BIT(PM_MODE_CPU_MAX)) {
  169. return PM_MODE_CPU_MAX;
  170. } else if (s_mode_mask >= BIT(PM_MODE_APB_MAX)) {
  171. return PM_MODE_APB_MAX;
  172. } else if (s_mode_mask >= BIT(PM_MODE_APB_MIN) || !s_light_sleep_en) {
  173. return PM_MODE_APB_MIN;
  174. } else {
  175. return PM_MODE_LIGHT_SLEEP;
  176. }
  177. }
  178. void IRAM_ATTR esp_pm_impl_switch_mode(pm_mode_t mode,
  179. pm_mode_switch_t lock_or_unlock, pm_time_t now)
  180. {
  181. bool need_switch = false;
  182. uint32_t mode_mask = BIT(mode);
  183. portENTER_CRITICAL(&s_switch_lock);
  184. uint32_t count;
  185. if (lock_or_unlock == MODE_LOCK) {
  186. count = ++s_mode_lock_counts[mode];
  187. } else {
  188. count = s_mode_lock_counts[mode]--;
  189. }
  190. if (count == 1) {
  191. if (lock_or_unlock == MODE_LOCK) {
  192. s_mode_mask |= mode_mask;
  193. } else {
  194. s_mode_mask &= ~mode_mask;
  195. }
  196. need_switch = true;
  197. }
  198. pm_mode_t new_mode = s_mode;
  199. if (need_switch) {
  200. new_mode = get_lowest_allowed_mode();
  201. #ifdef WITH_PROFILING
  202. if (s_last_mode_change_time != 0) {
  203. pm_time_t diff = now - s_last_mode_change_time;
  204. s_time_in_mode[s_mode] += diff;
  205. }
  206. s_last_mode_change_time = now;
  207. #endif // WITH_PROFILING
  208. }
  209. portEXIT_CRITICAL(&s_switch_lock);
  210. if (need_switch && new_mode != s_mode) {
  211. do_switch(new_mode);
  212. }
  213. }
  214. /**
  215. * @brief Update clock dividers in esp_timer and FreeRTOS, and adjust CCOMPARE
  216. * values on both CPUs.
  217. * @param old_ticks_per_us old CPU frequency
  218. * @param ticks_per_us new CPU frequency
  219. */
  220. static void IRAM_ATTR on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us)
  221. {
  222. uint32_t old_apb_ticks_per_us = MIN(old_ticks_per_us, 80);
  223. uint32_t apb_ticks_per_us = MIN(ticks_per_us, 80);
  224. /* Update APB frequency value used by the timer */
  225. if (old_apb_ticks_per_us != apb_ticks_per_us) {
  226. esp_timer_impl_update_apb_freq(apb_ticks_per_us);
  227. }
  228. /* Calculate new tick divisor */
  229. _xt_tick_divisor = ticks_per_us * 1000000 / XT_TICK_PER_SEC;
  230. int core_id = xPortGetCoreID();
  231. if (s_rtos_lock_handle[core_id] != NULL) {
  232. ESP_PM_TRACE_ENTER(CCOMPARE_UPDATE, core_id);
  233. /* ccount_div and ccount_mul are used in esp_pm_impl_update_ccompare
  234. * to calculate new CCOMPARE value.
  235. */
  236. s_ccount_div = old_ticks_per_us;
  237. s_ccount_mul = ticks_per_us;
  238. /* Update CCOMPARE value on this CPU */
  239. update_ccompare();
  240. #if portNUM_PROCESSORS == 2
  241. /* Send interrupt to the other CPU to update CCOMPARE value */
  242. int other_core_id = (core_id == 0) ? 1 : 0;
  243. s_need_update_ccompare[other_core_id] = true;
  244. esp_crosscore_int_send_freq_switch(other_core_id);
  245. int timeout = 0;
  246. while (s_need_update_ccompare[other_core_id]) {
  247. if (++timeout == CCOMPARE_UPDATE_TIMEOUT) {
  248. assert(false && "failed to update CCOMPARE, possible deadlock");
  249. }
  250. }
  251. #endif // portNUM_PROCESSORS == 2
  252. s_ccount_mul = 0;
  253. s_ccount_div = 0;
  254. ESP_PM_TRACE_EXIT(CCOMPARE_UPDATE, core_id);
  255. }
  256. }
  257. /**
  258. * Perform the switch to new power mode.
  259. * Currently only changes the CPU frequency and adjusts clock dividers.
  260. * No light sleep yet.
  261. * @param new_mode mode to switch to
  262. */
  263. static void IRAM_ATTR do_switch(pm_mode_t new_mode)
  264. {
  265. const int core_id = xPortGetCoreID();
  266. do {
  267. portENTER_CRITICAL_ISR(&s_switch_lock);
  268. if (!s_is_switching) {
  269. break;
  270. }
  271. if (s_new_mode <= new_mode) {
  272. portEXIT_CRITICAL_ISR(&s_switch_lock);
  273. return;
  274. }
  275. if (s_need_update_ccompare[core_id]) {
  276. s_need_update_ccompare[core_id] = false;
  277. }
  278. portEXIT_CRITICAL_ISR(&s_switch_lock);
  279. } while (true);
  280. s_new_mode = new_mode;
  281. s_is_switching = true;
  282. portEXIT_CRITICAL_ISR(&s_switch_lock);
  283. rtc_cpu_freq_t old_freq = s_cpu_freq_by_mode[s_mode];
  284. rtc_cpu_freq_t new_freq = s_cpu_freq_by_mode[new_mode];
  285. if (new_freq != old_freq) {
  286. uint32_t old_ticks_per_us = g_ticks_per_us_pro;
  287. uint32_t new_ticks_per_us = s_cpu_freq_to_ticks[new_freq];
  288. bool switch_down = new_ticks_per_us < old_ticks_per_us;
  289. ESP_PM_TRACE_ENTER(FREQ_SWITCH, core_id);
  290. if (switch_down) {
  291. on_freq_update(old_ticks_per_us, new_ticks_per_us);
  292. }
  293. rtc_clk_cpu_freq_set_fast(new_freq);
  294. if (!switch_down) {
  295. on_freq_update(old_ticks_per_us, new_ticks_per_us);
  296. }
  297. ESP_PM_TRACE_EXIT(FREQ_SWITCH, core_id);
  298. }
  299. portENTER_CRITICAL_ISR(&s_switch_lock);
  300. s_mode = new_mode;
  301. s_is_switching = false;
  302. portEXIT_CRITICAL_ISR(&s_switch_lock);
  303. }
  304. /**
  305. * @brief Calculate new CCOMPARE value based on s_ccount_{mul,div}
  306. *
  307. * Adjusts CCOMPARE value so that the interrupt happens at the same time as it
  308. * would happen without the frequency change.
  309. * Assumes that the new_frequency = old_frequency * s_ccount_mul / s_ccount_div.
  310. */
  311. static void IRAM_ATTR update_ccompare()
  312. {
  313. const uint32_t ccompare_min_cycles_in_future = 1000;
  314. uint32_t ccount = XTHAL_GET_CCOUNT();
  315. uint32_t ccompare = XTHAL_GET_CCOMPARE(XT_TIMER_INDEX);
  316. if ((ccompare - ccompare_min_cycles_in_future) - ccount < UINT32_MAX / 2) {
  317. uint32_t diff = ccompare - ccount;
  318. uint32_t diff_scaled = (diff * s_ccount_mul + s_ccount_div - 1) / s_ccount_div;
  319. if (diff_scaled < _xt_tick_divisor) {
  320. uint32_t new_ccompare = ccount + diff_scaled;
  321. XTHAL_SET_CCOMPARE(XT_TIMER_INDEX, new_ccompare);
  322. }
  323. }
  324. }
  325. static void IRAM_ATTR leave_idle()
  326. {
  327. int core_id = xPortGetCoreID();
  328. if (s_core_idle[core_id]) {
  329. // TODO: possible optimization: raise frequency here first
  330. esp_pm_lock_acquire(s_rtos_lock_handle[core_id]);
  331. s_core_idle[core_id] = false;
  332. }
  333. }
  334. void esp_pm_impl_idle_hook()
  335. {
  336. int core_id = xPortGetCoreID();
  337. uint32_t state = portENTER_CRITICAL_NESTED();
  338. if (!s_core_idle[core_id]) {
  339. esp_pm_lock_release(s_rtos_lock_handle[core_id]);
  340. s_core_idle[core_id] = true;
  341. }
  342. portEXIT_CRITICAL_NESTED(state);
  343. ESP_PM_TRACE_ENTER(IDLE, core_id);
  344. }
  345. void IRAM_ATTR esp_pm_impl_isr_hook()
  346. {
  347. int core_id = xPortGetCoreID();
  348. ESP_PM_TRACE_ENTER(ISR_HOOK, core_id);
  349. #if portNUM_PROCESSORS == 2
  350. if (s_need_update_ccompare[core_id]) {
  351. update_ccompare();
  352. s_need_update_ccompare[core_id] = false;
  353. } else {
  354. leave_idle();
  355. }
  356. #else
  357. leave_idle();
  358. #endif // portNUM_PROCESSORS == 2
  359. ESP_PM_TRACE_EXIT(ISR_HOOK, core_id);
  360. }
  361. #ifdef WITH_PROFILING
  362. void esp_pm_impl_dump_stats(FILE* out)
  363. {
  364. pm_time_t time_in_mode[PM_MODE_COUNT];
  365. portENTER_CRITICAL_ISR(&s_switch_lock);
  366. memcpy(time_in_mode, s_time_in_mode, sizeof(time_in_mode));
  367. pm_time_t last_mode_change_time = s_last_mode_change_time;
  368. pm_mode_t cur_mode = s_mode;
  369. pm_time_t now = pm_get_time();
  370. portEXIT_CRITICAL_ISR(&s_switch_lock);
  371. time_in_mode[cur_mode] += now - last_mode_change_time;
  372. fprintf(out, "Mode stats:\n");
  373. for (int i = 0; i < PM_MODE_COUNT; ++i) {
  374. if (i == PM_MODE_LIGHT_SLEEP && !s_light_sleep_en) {
  375. /* don't display light sleep mode if it's not enabled */
  376. continue;
  377. }
  378. fprintf(out, "%8s %6s %12lld %2d%%\n",
  379. s_mode_names[i],
  380. s_freq_names[s_cpu_freq_by_mode[i]],
  381. time_in_mode[i],
  382. (int) (time_in_mode[i] * 100 / now));
  383. }
  384. }
  385. #endif // WITH_PROFILING
  386. void esp_pm_impl_init()
  387. {
  388. s_cpu_freq_to_ticks[RTC_CPU_FREQ_XTAL] = rtc_clk_xtal_freq_get();
  389. #ifdef CONFIG_PM_TRACE
  390. esp_pm_trace_init();
  391. #endif
  392. ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos0",
  393. &s_rtos_lock_handle[0]));
  394. ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[0]));
  395. #if portNUM_PROCESSORS == 2
  396. ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "rtos1",
  397. &s_rtos_lock_handle[1]));
  398. ESP_ERROR_CHECK(esp_pm_lock_acquire(s_rtos_lock_handle[1]));
  399. /* Configure all modes to use the default CPU frequency.
  400. * This will be modified later by a call to esp_pm_configure.
  401. */
  402. rtc_cpu_freq_t default_freq;
  403. assert(rtc_clk_cpu_freq_from_mhz(CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ, &default_freq));
  404. for (size_t i = 0; i < PM_MODE_COUNT; ++i) {
  405. s_cpu_freq_by_mode[i] = default_freq;
  406. }
  407. #endif // portNUM_PROCESSORS == 2
  408. }