1 /*
2 * Arm SCP/MCP Software
3 * Copyright (c) 2015-2023, Arm Limited and Contributors. All rights reserved.
4 *
5 * SPDX-License-Identifier: BSD-3-Clause
6 *
7 * Description:
8 * System Power Support.
9 */
10
11 #include <mod_power_domain.h>
12 #include <mod_system_power.h>
13
14 #include <fwk_assert.h>
15 #include <fwk_id.h>
16 #include <fwk_interrupt.h>
17 #include <fwk_log.h>
18 #include <fwk_mm.h>
19 #include <fwk_module.h>
20 #include <fwk_module_idx.h>
21 #include <fwk_status.h>
22
23 #include <stdbool.h>
24 #include <stdint.h>
25
26 /* SoC wakeup composite state */
27 #define MOD_SYSTEM_POWER_SOC_WAKEUP_STATE \
28 MOD_PD_COMPOSITE_STATE( \
29 (unsigned int)MOD_PD_LEVEL_2, \
30 0U, \
31 (unsigned int)MOD_PD_STATE_ON, \
32 (unsigned int)MOD_PD_STATE_ON, \
33 (unsigned int)MOD_PD_STATE_ON)
34
35 /* Element context */
36 struct system_power_dev_ctx {
37 /* Element configuration data pointer */
38 const struct mod_system_power_dev_config *config;
39
40 /* Power domain driver API pointer */
41 const struct mod_pd_driver_api *sys_ppu_api;
42 };
43
44 /* Module context */
45 struct mod_system_power_ctx {
46 /* System power element context table */
47 struct system_power_dev_ctx *dev_ctx_table;
48
49 /* Number of elements */
50 unsigned int dev_count;
51
52 /* Pointer to array of extended PPU power domain driver APIs */
53 const struct mod_pd_driver_api **ext_ppu_apis;
54
55 /* Power domain module restricted API pointer */
56 const struct mod_pd_restricted_api *pd_restricted_api;
57
58 /* Power domain module driver input API pointer */
59 const struct mod_pd_driver_input_api *pd_driver_input_api;
60
61 /* Driver API pointer */
62 const struct mod_system_power_driver_api *driver_api;
63
64 /* Power domain module identifier of the system power domain */
65 fwk_id_t mod_pd_system_id;
66
67 /* Current system-level power state */
68 unsigned int state;
69
70 /* Requested power state */
71 unsigned int requested_state;
72
73 /* Pointer to module config */
74 const struct mod_system_power_config *config;
75
76 /*
77 * Identifier of the power domain of the last standing core before system
78 * suspend.
79 */
80 fwk_id_t last_core_pd_id;
81 };
82
83 static struct mod_system_power_ctx system_power_ctx;
84
85 /*
86 * Static helpers
87 */
88
ext_ppus_set_state(enum mod_pd_state state)89 static void ext_ppus_set_state(enum mod_pd_state state)
90 {
91 unsigned int i;
92 int status;
93
94 for (i = 0; i < system_power_ctx.config->ext_ppus_count; i++) {
95 status = system_power_ctx.ext_ppu_apis[i]->set_state(
96 system_power_ctx.config->ext_ppus[i].ppu_id, state);
97 if (status != FWK_SUCCESS) {
98 FWK_LOG_DEBUG("[SYS-POW] ext-ppu%i %s @%d", i, __func__, __LINE__);
99 }
100 }
101 }
102
ext_ppus_shutdown(enum mod_pd_system_shutdown system_shutdown)103 static void ext_ppus_shutdown(enum mod_pd_system_shutdown system_shutdown)
104 {
105 unsigned int i;
106 const struct mod_pd_driver_api *api;
107 fwk_id_t ppu_id;
108 int status;
109
110 /* Shutdown external PPUs */
111 for (i = 0; i < system_power_ctx.config->ext_ppus_count; i++) {
112 api = system_power_ctx.ext_ppu_apis[i];
113 ppu_id = system_power_ctx.config->ext_ppus[i].ppu_id;
114
115 if (api->shutdown != NULL) {
116 status = api->shutdown(ppu_id, system_shutdown);
117 } else {
118 status = api->set_state(ppu_id, MOD_PD_STATE_OFF);
119 }
120 if (status != FWK_SUCCESS) {
121 FWK_LOG_DEBUG("[SYS-POW] ext-ppu%i %s @%d", i, __func__, __LINE__);
122 }
123 }
124 }
125
set_system_power_state(unsigned int state)126 static int set_system_power_state(unsigned int state)
127 {
128 int status;
129 unsigned int i;
130 struct system_power_dev_ctx *dev_ctx;
131 const uint8_t *sys_state_table;
132
133 for (i = 0; i < system_power_ctx.dev_count; i++) {
134 dev_ctx = &system_power_ctx.dev_ctx_table[i];
135
136 sys_state_table = dev_ctx->config->sys_state_table;
137
138 status = dev_ctx->sys_ppu_api->set_state(dev_ctx->config->sys_ppu_id,
139 sys_state_table[state]);
140 if (status != FWK_SUCCESS) {
141 return status;
142 }
143 }
144
145 return FWK_SUCCESS;
146 }
147
shutdown_system_power_ppus(enum mod_pd_system_shutdown system_shutdown)148 static int shutdown_system_power_ppus(
149 enum mod_pd_system_shutdown system_shutdown)
150 {
151 unsigned int i;
152 struct system_power_dev_ctx *dev_ctx;
153 const struct mod_pd_driver_api *api;
154 fwk_id_t ppu_id;
155 unsigned int state;
156 int status;
157
158 for (i = 0; i < system_power_ctx.dev_count; i++) {
159 dev_ctx = &system_power_ctx.dev_ctx_table[i];
160
161 api = dev_ctx->sys_ppu_api;
162 ppu_id = dev_ctx->config->sys_ppu_id;
163
164 if (api->shutdown != NULL) {
165 status = api->shutdown(ppu_id, system_shutdown);
166 } else {
167 state = dev_ctx->config->sys_state_table[MOD_PD_STATE_OFF];
168
169 status = api->set_state(ppu_id, state);
170 }
171 if (status != FWK_SUCCESS) {
172 return status;
173 }
174 }
175
176 return FWK_SUCCESS;
177 }
178
disable_all_irqs(void)179 static int disable_all_irqs(void)
180 {
181 int status = FWK_SUCCESS;
182
183 if (system_power_ctx.config->soc_wakeup_irq != FWK_INTERRUPT_NONE) {
184 status = fwk_interrupt_disable(system_power_ctx.config->soc_wakeup_irq);
185 if (status != FWK_SUCCESS) {
186 return FWK_E_DEVICE;
187 }
188 }
189
190 if (system_power_ctx.driver_api->platform_interrupts != NULL) {
191 status = system_power_ctx.driver_api->platform_interrupts(
192 MOD_SYSTEM_POWER_PLATFORM_INTERRUPT_CMD_DISABLE);
193 if (status != FWK_SUCCESS) {
194 status = FWK_E_DEVICE;
195 }
196 }
197
198 return status;
199 }
200
shutdown(fwk_id_t pd_id,enum mod_pd_system_shutdown system_shutdown)201 static int shutdown(
202 fwk_id_t pd_id,
203 enum mod_pd_system_shutdown system_shutdown)
204 {
205 int status;
206
207 status = disable_all_irqs();
208 if (status != FWK_SUCCESS) {
209 return status;
210 }
211
212 /* Shutdown external PPUs */
213 ext_ppus_shutdown(system_shutdown);
214
215 system_power_ctx.requested_state = (unsigned int)MOD_PD_STATE_OFF;
216
217 /* Shutdown system PPUs */
218 status = shutdown_system_power_ppus(system_shutdown);
219 if (status != FWK_SUCCESS) {
220 return status;
221 }
222
223 return FWK_SUCCESS;
224 }
225
226 /*
227 * Functions fulfilling the Power Domain module's driver API
228 */
229
system_power_set_state(fwk_id_t pd_id,unsigned int state)230 static int system_power_set_state(fwk_id_t pd_id, unsigned int state)
231 {
232 int status;
233 unsigned int soc_wakeup_irq;
234
235 if (!fwk_expect(state < MOD_SYSTEM_POWER_POWER_STATE_COUNT)) {
236 return FWK_E_PARAM;
237 }
238
239 soc_wakeup_irq = system_power_ctx.config->soc_wakeup_irq;
240
241 system_power_ctx.requested_state = state;
242
243 switch (state) {
244 case (unsigned int)MOD_PD_STATE_ON:
245 status = disable_all_irqs();
246 if (status != FWK_SUCCESS) {
247 return status;
248 }
249
250 status = set_system_power_state(state);
251 if (status != FWK_SUCCESS) {
252 return status;
253 }
254
255 ext_ppus_set_state(MOD_PD_STATE_ON);
256
257 break;
258
259 case (unsigned int)MOD_SYSTEM_POWER_POWER_STATE_SLEEP0:
260 ext_ppus_set_state(MOD_PD_STATE_OFF);
261
262 status = fwk_interrupt_clear_pending(soc_wakeup_irq);
263 if (status != FWK_SUCCESS) {
264 return status;
265 }
266
267 if (system_power_ctx.driver_api->platform_interrupts != NULL) {
268 status =
269 system_power_ctx.driver_api->platform_interrupts(
270 MOD_SYSTEM_POWER_PLATFORM_INTERRUPT_CMD_CLEAR_PENDING);
271 if (status != FWK_SUCCESS) {
272 return FWK_E_DEVICE;
273 }
274 }
275
276 status = set_system_power_state(state);
277 if (status != FWK_SUCCESS) {
278 return status;
279 }
280
281 /* Store the identifier of the power domain of the last standing core */
282 status = system_power_ctx.pd_driver_input_api->get_last_core_pd_id(
283 &system_power_ctx.last_core_pd_id);
284 if (status != FWK_SUCCESS) {
285 return status;
286 }
287
288 status = fwk_interrupt_enable(soc_wakeup_irq);
289 if (status != FWK_SUCCESS) {
290 return status;
291 }
292
293 if (system_power_ctx.driver_api->platform_interrupts != NULL) {
294 status = system_power_ctx.driver_api->platform_interrupts(
295 MOD_SYSTEM_POWER_PLATFORM_INTERRUPT_CMD_ENABLE);
296 if (status != FWK_SUCCESS) {
297 return FWK_E_DEVICE;
298 }
299 }
300
301 break;
302
303 case (unsigned int)MOD_PD_STATE_OFF:
304 status = disable_all_irqs();
305 if (status != FWK_SUCCESS) {
306 return status;
307 }
308
309 ext_ppus_set_state(MOD_PD_STATE_OFF);
310
311 status = set_system_power_state(state);
312 if (status != FWK_SUCCESS) {
313 return status;
314 }
315
316 break;
317
318 default:
319 return FWK_E_SUPPORT;
320 }
321
322 return FWK_SUCCESS;
323 }
324
system_power_get_state(fwk_id_t pd_id,unsigned int * state)325 static int system_power_get_state(fwk_id_t pd_id, unsigned int *state)
326 {
327 *state = system_power_ctx.state;
328
329 return FWK_SUCCESS;
330 }
331
system_power_reset(fwk_id_t pd_id)332 static int system_power_reset(fwk_id_t pd_id)
333 {
334 return FWK_E_SUPPORT;
335 }
336
system_power_shutdown(fwk_id_t pd_id,enum mod_pd_system_shutdown system_shutdown)337 static int system_power_shutdown(fwk_id_t pd_id,
338 enum mod_pd_system_shutdown system_shutdown)
339 {
340 int status;
341
342 status = shutdown(pd_id, system_shutdown);
343 if (status != FWK_SUCCESS) {
344 return status;
345 }
346
347 return system_power_ctx.driver_api->system_shutdown(system_shutdown);
348 }
349
soc_wakeup_handler(void)350 static void soc_wakeup_handler(void)
351 {
352 int status;
353 uint32_t state = MOD_SYSTEM_POWER_SOC_WAKEUP_STATE;
354
355 status = disable_all_irqs();
356 if (status != FWK_SUCCESS) {
357 fwk_trap();
358 }
359
360 status = system_power_ctx.pd_restricted_api->set_state(
361 system_power_ctx.last_core_pd_id, false, state);
362 fwk_check(status == FWK_SUCCESS);
363 }
364
365 static const struct mod_pd_driver_api system_power_power_domain_driver_api = {
366 .set_state = system_power_set_state,
367 .get_state = system_power_get_state,
368 .reset = system_power_reset,
369 .shutdown = system_power_shutdown
370 };
371
372 /*
373 * Functions fulfilling the Power Domain module's driver input API
374 */
375
system_power_report_power_state_transition(fwk_id_t dev_id,unsigned int state)376 static int system_power_report_power_state_transition(fwk_id_t dev_id,
377 unsigned int state)
378 {
379 static unsigned int sys_ppu_transition_count = 0;
380
381 sys_ppu_transition_count++;
382
383 if (sys_ppu_transition_count < system_power_ctx.dev_count) {
384 return FWK_SUCCESS;
385 }
386
387 system_power_ctx.state = system_power_ctx.requested_state;
388
389 sys_ppu_transition_count = 0;
390
391 return system_power_ctx.pd_driver_input_api->report_power_state_transition(
392 system_power_ctx.mod_pd_system_id, system_power_ctx.state);
393 }
394
395 static const struct mod_pd_driver_input_api
396 system_power_power_domain_driver_input_api = {
397 .report_power_state_transition = system_power_report_power_state_transition
398 };
399
400 /*
401 * Functions fulfilling the framework's module interface
402 */
403
system_power_mod_init(fwk_id_t module_id,unsigned int element_count,const void * data)404 static int system_power_mod_init(fwk_id_t module_id,
405 unsigned int element_count,
406 const void *data)
407 {
408 const struct mod_system_power_config *config;
409
410 fwk_assert(data != NULL);
411 fwk_check(element_count > 0);
412
413 system_power_ctx.config = config = data;
414 system_power_ctx.mod_pd_system_id = FWK_ID_NONE;
415 system_power_ctx.dev_count = element_count;
416
417 system_power_ctx.dev_ctx_table =
418 fwk_mm_calloc(element_count, sizeof(struct system_power_dev_ctx));
419
420 if (system_power_ctx.config->ext_ppus_count > 0) {
421 system_power_ctx.ext_ppu_apis = fwk_mm_calloc(
422 system_power_ctx.config->ext_ppus_count,
423 sizeof(system_power_ctx.ext_ppu_apis[0]));
424 }
425
426 if (system_power_ctx.config->soc_wakeup_irq != FWK_INTERRUPT_NONE) {
427 return fwk_interrupt_set_isr(system_power_ctx.config->soc_wakeup_irq,
428 soc_wakeup_handler);
429 }
430
431 return FWK_SUCCESS;
432 }
433
system_power_mod_element_init(fwk_id_t element_id,unsigned int unused,const void * data)434 static int system_power_mod_element_init(fwk_id_t element_id,
435 unsigned int unused,
436 const void *data)
437 {
438 struct system_power_dev_ctx *dev_ctx;
439
440 fwk_assert(data != NULL);
441
442 dev_ctx =
443 system_power_ctx.dev_ctx_table + fwk_id_get_element_idx(element_id);
444
445 dev_ctx->config = data;
446
447 /* Ensure a system state table is provided */
448 if (dev_ctx->config->sys_state_table == NULL) {
449 return FWK_E_DATA;
450 }
451
452 return FWK_SUCCESS;
453 }
454
system_power_bind(fwk_id_t id,unsigned int round)455 static int system_power_bind(fwk_id_t id, unsigned int round)
456 {
457 int status;
458 unsigned int i;
459 const struct mod_system_power_config *config;
460 struct system_power_dev_ctx *dev_ctx;
461
462 if (round == 1) {
463 if (!fwk_id_is_type(id, FWK_ID_TYPE_MODULE)) {
464 return FWK_SUCCESS;
465 }
466
467 /*
468 * During the first round of binding, the power domain module should
469 * have bound to the power domain driver API provided by the present
470 * module. Bind back to the power domain driver input API provided by
471 * the system_power_ctx.mod_pd_system_id power domain module element to
472 * report power state transitions of the system power domains.
473 */
474 return fwk_module_bind(
475 system_power_ctx.mod_pd_system_id,
476 mod_pd_api_id_driver_input,
477 &system_power_ctx.pd_driver_input_api);
478 }
479
480 if (fwk_id_is_type(id, FWK_ID_TYPE_MODULE)) {
481
482 config = system_power_ctx.config;
483
484 for (i = 0; i < config->ext_ppus_count; i++) {
485 status = fwk_module_bind(
486 config->ext_ppus[i].ppu_id,
487 config->ext_ppus[i].api_id,
488 &system_power_ctx.ext_ppu_apis[i]);
489 if (status != FWK_SUCCESS) {
490 return status;
491 }
492 }
493
494 status = fwk_module_bind(config->driver_id,
495 config->driver_api_id,
496 &system_power_ctx.driver_api);
497 if (status != FWK_SUCCESS) {
498 return status;
499 }
500
501 return fwk_module_bind(
502 fwk_module_id_power_domain,
503 mod_pd_api_id_restricted,
504 &system_power_ctx.pd_restricted_api);
505 }
506
507 dev_ctx = system_power_ctx.dev_ctx_table + fwk_id_get_element_idx(id);
508
509 return fwk_module_bind(dev_ctx->config->sys_ppu_id,
510 dev_ctx->config->api_id,
511 &dev_ctx->sys_ppu_api);
512 }
513
system_power_process_bind_request(fwk_id_t requester_id,fwk_id_t pd_id,fwk_id_t api_id,const void ** api)514 static int system_power_process_bind_request(fwk_id_t requester_id,
515 fwk_id_t pd_id,
516 fwk_id_t api_id,
517 const void **api)
518 {
519 unsigned int dev_idx;
520 struct system_power_dev_ctx *dev_ctx;
521
522 if (fwk_id_is_equal(api_id, mod_system_power_api_id_pd_driver)) {
523 if (!fwk_id_is_equal(
524 fwk_id_build_module_id(requester_id),
525 fwk_module_id_power_domain)) {
526 return FWK_E_ACCESS;
527 }
528
529 *api = &system_power_power_domain_driver_api;
530 system_power_ctx.mod_pd_system_id = requester_id;
531 } else {
532 for (dev_idx = 0; dev_idx < system_power_ctx.dev_count; dev_idx++) {
533 dev_ctx = &system_power_ctx.dev_ctx_table[dev_idx];
534
535 /*
536 * If requester_id refers to a system PPU configured by any one of
537 * our elements, break when dev_idx reaches that element.
538 */
539 if (fwk_id_is_equal(requester_id, dev_ctx->config->sys_ppu_id)) {
540 break;
541 }
542 }
543 if (dev_idx >= system_power_ctx.dev_count) {
544 /* Requester_id does not refer to any configured system PPU */
545 return FWK_E_ACCESS;
546 }
547
548 *api = &system_power_power_domain_driver_input_api;
549 }
550
551 return FWK_SUCCESS;
552 }
553
system_power_start(fwk_id_t id)554 static int system_power_start(fwk_id_t id)
555 {
556 int status;
557
558 if (system_power_ctx.driver_api->platform_interrupts != NULL) {
559 status = system_power_ctx.driver_api->platform_interrupts(
560 MOD_SYSTEM_POWER_PLATFORM_INTERRUPT_CMD_INIT);
561 if (status != FWK_SUCCESS) {
562 return status;
563 }
564 }
565
566 /* Configure initial power state */
567 system_power_ctx.state =
568 (unsigned int)system_power_ctx.config->initial_system_power_state;
569
570 return FWK_SUCCESS;
571 }
572
573 const struct fwk_module module_system_power = {
574 .type = FWK_MODULE_TYPE_DRIVER,
575 .api_count = (unsigned int)MOD_SYSTEM_POWER_API_COUNT,
576 .init = system_power_mod_init,
577 .element_init = system_power_mod_element_init,
578 .bind = system_power_bind,
579 .start = system_power_start,
580 .process_bind_request = system_power_process_bind_request,
581 };
582