| /* Copyright 2018 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "console.h" |
| #include "heci_client.h" |
| #include "registers.h" |
| #include "system_state.h" |
| |
| #ifdef SS_SUBSYSTEM_DEBUG |
| #define CPUTS(outstr) cputs(CC_LPC, outstr) |
| #define CPRINTS(format, args...) cprints(CC_LPC, format, ##args) |
| #define CPRINTF(format, args...) cprintf(CC_LPC, format, ##args) |
| #else |
| #define CPUTS(outstr) |
| #define CPRINTS(format, args...) |
| #define CPRINTF(format, args...) |
| #endif |
| |
| /* the following "define"s and structures are from host driver |
| * and they are slightly modified for look&feel purpose. |
| */ |
| #define SYSTEM_STATE_SUBSCRIBE 0x1 |
| #define SYSTEM_STATE_STATUS 0x2 |
| #define SYSTEM_STATE_QUERY_SUBSCRIBERS 0x3 |
| #define SYSTEM_STATE_STATE_CHANGE_REQ 0x4 |
| |
| #define SUSPEND_STATE_BIT BIT(1) /* suspend/resume */ |
| |
| /* Cached state of ISH's requested power rails when AP suspends */ |
| static uint32_t cached_vnn_request; |
| |
| struct ss_header { |
| uint32_t cmd; |
| uint32_t cmd_status; |
| } __packed; |
| |
| struct ss_query_subscribers { |
| struct ss_header hdr; |
| } __packed; |
| |
| struct ss_subscribe { |
| struct ss_header hdr; |
| uint32_t states; |
| } __packed; |
| |
| struct ss_status { |
| struct ss_header hdr; |
| uint32_t supported_states; |
| uint32_t states_status; |
| } __packed; |
| |
| /* change request from device but host doesn't support it */ |
| struct ss_state_change_req { |
| struct ss_header hdr; |
| uint32_t requested_states; |
| uint32_t states_status; |
| } __packed; |
| |
| /* |
| * TODO: For now, every HECI client with valid .suspend or .resume callback is |
| * automatically registered as client of system state subsystem. |
| * so MAX_SS_CLIENTS should be HECI_MAX_NUM_OF_CLIENTS. |
| * if an object wants to get system state event then it can embeds |
| * "struct ss_subsys_device" in it and calls ss_subsys_register_client() like |
| * HECI client. |
| */ |
| #define MAX_SS_CLIENTS HECI_MAX_NUM_OF_CLIENTS |
| |
| struct ss_subsystem_context { |
| uint32_t registered_state; |
| |
| int num_of_ss_client; |
| struct ss_subsys_device *clients[MAX_SS_CLIENTS]; |
| }; |
| |
| static struct ss_subsystem_context ss_subsys_ctx; |
| |
| int ss_subsys_register_client(struct ss_subsys_device *ss_device) |
| { |
| int handle; |
| |
| if (ss_subsys_ctx.num_of_ss_client == MAX_SS_CLIENTS) |
| return -1; |
| |
| if (ss_device->cbs->resume || ss_device->cbs->suspend) { |
| handle = ss_subsys_ctx.num_of_ss_client++; |
| ss_subsys_ctx.registered_state |= SUSPEND_STATE_BIT; |
| ss_subsys_ctx.clients[handle] = ss_device; |
| } else { |
| return -1; |
| } |
| |
| return handle; |
| } |
| |
| static int ss_subsys_suspend(void) |
| { |
| int i; |
| |
| for (i = ss_subsys_ctx.num_of_ss_client - 1; i >= 0; i--) { |
| if (ss_subsys_ctx.clients[i]->cbs->suspend) |
| ss_subsys_ctx.clients[i]->cbs->suspend( |
| ss_subsys_ctx.clients[i]); |
| } |
| |
| /* |
| * PMU_VNN_REQ is used by ISH FW to assert power requirements of ISH to |
| * PMC. The system won't enter S0ix if ISH is requesting any power |
| * rails. Setting a bit to 1 both sets and clears a requested value. |
| * Cache the value of request power so we can restore it on resume. |
| */ |
| if (IS_ENABLED(CHIP_FAMILY_ISH5)) { |
| cached_vnn_request = PMU_VNN_REQ; |
| PMU_VNN_REQ = cached_vnn_request; |
| } |
| return EC_SUCCESS; |
| } |
| |
| static int ss_subsys_resume(void) |
| { |
| int i; |
| |
| /* |
| * Restore VNN power request from before suspend. |
| */ |
| if (IS_ENABLED(CHIP_FAMILY_ISH5) && cached_vnn_request) { |
| /* Request all cached power rails that are not already on. */ |
| PMU_VNN_REQ = cached_vnn_request & ~PMU_VNN_REQ; |
| /* Wait for power request to get acknowledged */ |
| while (!(PMU_VNN_REQ_ACK & PMU_VNN_REQ_ACK_STATUS)) |
| continue; |
| } |
| |
| for (i = 0; i < ss_subsys_ctx.num_of_ss_client; i++) { |
| if (ss_subsys_ctx.clients[i]->cbs->resume) |
| ss_subsys_ctx.clients[i]->cbs->resume( |
| ss_subsys_ctx.clients[i]); |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| void heci_handle_system_state_msg(uint8_t *msg, const size_t length) |
| { |
| struct ss_header *hdr = (struct ss_header *)msg; |
| struct ss_subscribe subscribe; |
| struct ss_status *status; |
| |
| switch (hdr->cmd) { |
| case SYSTEM_STATE_QUERY_SUBSCRIBERS: |
| subscribe.hdr.cmd = SYSTEM_STATE_SUBSCRIBE; |
| subscribe.hdr.cmd_status = 0; |
| subscribe.states = ss_subsys_ctx.registered_state; |
| |
| heci_send_fixed_client_msg(HECI_FIXED_SYSTEM_STATE_ADDR, |
| (uint8_t *)&subscribe, |
| sizeof(subscribe)); |
| |
| break; |
| case SYSTEM_STATE_STATUS: |
| status = (struct ss_status *)msg; |
| if (status->supported_states & SUSPEND_STATE_BIT) { |
| if (status->states_status & SUSPEND_STATE_BIT) |
| ss_subsys_suspend(); |
| else |
| ss_subsys_resume(); |
| } |
| |
| break; |
| } |
| } |