| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (C) 2020-21 Intel Corporation.
- */
- #include "iosm_ipc_protocol.h"
- /* Timeout value in MS for the PM to wait for device to reach active state */
- #define IPC_PM_ACTIVE_TIMEOUT_MS (500)
- /* Note that here "active" has the value 1, as compared to the enums
- * ipc_mem_host_pm_state or ipc_mem_dev_pm_state, where "active" is 0
- */
- #define IPC_PM_SLEEP (0)
- #define CONSUME_STATE (0)
- #define IPC_PM_ACTIVE (1)
- void ipc_pm_signal_hpda_doorbell(struct iosm_pm *ipc_pm, u32 identifier,
- bool host_slp_check)
- {
- if (host_slp_check && ipc_pm->host_pm_state != IPC_MEM_HOST_PM_ACTIVE &&
- ipc_pm->host_pm_state != IPC_MEM_HOST_PM_ACTIVE_WAIT) {
- ipc_pm->pending_hpda_update = true;
- dev_dbg(ipc_pm->dev,
- "Pend HPDA update set. Host PM_State: %d identifier:%d",
- ipc_pm->host_pm_state, identifier);
- return;
- }
- if (!ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_IRQ, true)) {
- ipc_pm->pending_hpda_update = true;
- dev_dbg(ipc_pm->dev, "Pending HPDA update set. identifier:%d",
- identifier);
- return;
- }
- ipc_pm->pending_hpda_update = false;
- /* Trigger the irq towards CP */
- ipc_cp_irq_hpda_update(ipc_pm->pcie, identifier);
- ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_IRQ, false);
- }
- /* Wake up the device if it is in low power mode. */
- static bool ipc_pm_link_activate(struct iosm_pm *ipc_pm)
- {
- if (ipc_pm->cp_state == IPC_MEM_DEV_PM_ACTIVE)
- return true;
- if (ipc_pm->cp_state == IPC_MEM_DEV_PM_SLEEP) {
- if (ipc_pm->ap_state == IPC_MEM_DEV_PM_SLEEP) {
- /* Wake up the device. */
- ipc_cp_irq_sleep_control(ipc_pm->pcie,
- IPC_MEM_DEV_PM_WAKEUP);
- ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE_WAIT;
- goto not_active;
- }
- if (ipc_pm->ap_state == IPC_MEM_DEV_PM_ACTIVE_WAIT)
- goto not_active;
- return true;
- }
- not_active:
- /* link is not ready */
- return false;
- }
- bool ipc_pm_wait_for_device_active(struct iosm_pm *ipc_pm)
- {
- bool ret_val = false;
- if (ipc_pm->ap_state != IPC_MEM_DEV_PM_ACTIVE) {
- /* Complete all memory stores before setting bit */
- smp_mb__before_atomic();
- /* Wait for IPC_PM_ACTIVE_TIMEOUT_MS for Device sleep state
- * machine to enter ACTIVE state.
- */
- set_bit(0, &ipc_pm->host_sleep_pend);
- /* Complete all memory stores after setting bit */
- smp_mb__after_atomic();
- if (!wait_for_completion_interruptible_timeout
- (&ipc_pm->host_sleep_complete,
- msecs_to_jiffies(IPC_PM_ACTIVE_TIMEOUT_MS))) {
- dev_err(ipc_pm->dev,
- "PM timeout. Expected State:%d. Actual: %d",
- IPC_MEM_DEV_PM_ACTIVE, ipc_pm->ap_state);
- goto active_timeout;
- }
- }
- ret_val = true;
- active_timeout:
- /* Complete all memory stores before clearing bit */
- smp_mb__before_atomic();
- /* Reset the atomic variable in any case as device sleep
- * state machine change is no longer of interest.
- */
- clear_bit(0, &ipc_pm->host_sleep_pend);
- /* Complete all memory stores after clearing bit */
- smp_mb__after_atomic();
- return ret_val;
- }
- static void ipc_pm_on_link_sleep(struct iosm_pm *ipc_pm)
- {
- /* pending sleep ack and all conditions are cleared
- * -> signal SLEEP__ACK to CP
- */
- ipc_pm->cp_state = IPC_MEM_DEV_PM_SLEEP;
- ipc_pm->ap_state = IPC_MEM_DEV_PM_SLEEP;
- ipc_cp_irq_sleep_control(ipc_pm->pcie, IPC_MEM_DEV_PM_SLEEP);
- }
- static void ipc_pm_on_link_wake(struct iosm_pm *ipc_pm, bool ack)
- {
- ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE;
- if (ack) {
- ipc_pm->cp_state = IPC_MEM_DEV_PM_ACTIVE;
- ipc_cp_irq_sleep_control(ipc_pm->pcie, IPC_MEM_DEV_PM_ACTIVE);
- /* check the consume state !!! */
- if (test_bit(CONSUME_STATE, &ipc_pm->host_sleep_pend))
- complete(&ipc_pm->host_sleep_complete);
- }
- /* Check for pending HPDA update.
- * Pending HP update could be because of sending message was
- * put on hold due to Device sleep state or due to TD update
- * which could be because of Device Sleep and Host Sleep
- * states.
- */
- if (ipc_pm->pending_hpda_update &&
- ipc_pm->host_pm_state == IPC_MEM_HOST_PM_ACTIVE)
- ipc_pm_signal_hpda_doorbell(ipc_pm, IPC_HP_PM_TRIGGER, true);
- }
- bool ipc_pm_trigger(struct iosm_pm *ipc_pm, enum ipc_pm_unit unit, bool active)
- {
- union ipc_pm_cond old_cond;
- union ipc_pm_cond new_cond;
- bool link_active;
- /* Save the current D3 state. */
- new_cond = ipc_pm->pm_cond;
- old_cond = ipc_pm->pm_cond;
- /* Calculate the power state only in the runtime phase. */
- switch (unit) {
- case IPC_PM_UNIT_IRQ: /* CP irq */
- new_cond.irq = active;
- break;
- case IPC_PM_UNIT_LINK: /* Device link state. */
- new_cond.link = active;
- break;
- case IPC_PM_UNIT_HS: /* Host sleep trigger requires Link. */
- new_cond.hs = active;
- break;
- default:
- break;
- }
- /* Something changed ? */
- if (old_cond.raw == new_cond.raw) {
- /* Stay in the current PM state. */
- link_active = old_cond.link == IPC_PM_ACTIVE;
- goto ret;
- }
- ipc_pm->pm_cond = new_cond;
- if (new_cond.link)
- ipc_pm_on_link_wake(ipc_pm, unit == IPC_PM_UNIT_LINK);
- else if (unit == IPC_PM_UNIT_LINK)
- ipc_pm_on_link_sleep(ipc_pm);
- if (old_cond.link == IPC_PM_SLEEP && new_cond.raw) {
- link_active = ipc_pm_link_activate(ipc_pm);
- goto ret;
- }
- link_active = old_cond.link == IPC_PM_ACTIVE;
- ret:
- return link_active;
- }
- bool ipc_pm_prepare_host_sleep(struct iosm_pm *ipc_pm)
- {
- /* suspend not allowed if host_pm_state is not IPC_MEM_HOST_PM_ACTIVE */
- if (ipc_pm->host_pm_state != IPC_MEM_HOST_PM_ACTIVE) {
- dev_err(ipc_pm->dev, "host_pm_state=%d\tExpected to be: %d",
- ipc_pm->host_pm_state, IPC_MEM_HOST_PM_ACTIVE);
- return false;
- }
- ipc_pm->host_pm_state = IPC_MEM_HOST_PM_SLEEP_WAIT_D3;
- return true;
- }
- bool ipc_pm_prepare_host_active(struct iosm_pm *ipc_pm)
- {
- if (ipc_pm->host_pm_state != IPC_MEM_HOST_PM_SLEEP) {
- dev_err(ipc_pm->dev, "host_pm_state=%d\tExpected to be: %d",
- ipc_pm->host_pm_state, IPC_MEM_HOST_PM_SLEEP);
- return false;
- }
- /* Sending Sleep Exit message to CP. Update the state */
- ipc_pm->host_pm_state = IPC_MEM_HOST_PM_ACTIVE_WAIT;
- return true;
- }
- void ipc_pm_set_s2idle_sleep(struct iosm_pm *ipc_pm, bool sleep)
- {
- if (sleep) {
- ipc_pm->ap_state = IPC_MEM_DEV_PM_SLEEP;
- ipc_pm->cp_state = IPC_MEM_DEV_PM_SLEEP;
- ipc_pm->device_sleep_notification = IPC_MEM_DEV_PM_SLEEP;
- } else {
- ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE;
- ipc_pm->cp_state = IPC_MEM_DEV_PM_ACTIVE;
- ipc_pm->device_sleep_notification = IPC_MEM_DEV_PM_ACTIVE;
- ipc_pm->pm_cond.link = IPC_PM_ACTIVE;
- }
- }
- bool ipc_pm_dev_slp_notification(struct iosm_pm *ipc_pm, u32 cp_pm_req)
- {
- if (cp_pm_req == ipc_pm->device_sleep_notification)
- return false;
- ipc_pm->device_sleep_notification = cp_pm_req;
- /* Evaluate the PM request. */
- switch (ipc_pm->cp_state) {
- case IPC_MEM_DEV_PM_ACTIVE:
- switch (cp_pm_req) {
- case IPC_MEM_DEV_PM_ACTIVE:
- break;
- case IPC_MEM_DEV_PM_SLEEP:
- /* Inform the PM that the device link can go down. */
- ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_LINK, false);
- return true;
- default:
- dev_err(ipc_pm->dev,
- "loc-pm=%d active: confused req-pm=%d",
- ipc_pm->cp_state, cp_pm_req);
- break;
- }
- break;
- case IPC_MEM_DEV_PM_SLEEP:
- switch (cp_pm_req) {
- case IPC_MEM_DEV_PM_ACTIVE:
- /* Inform the PM that the device link is active. */
- ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_LINK, true);
- break;
- case IPC_MEM_DEV_PM_SLEEP:
- break;
- default:
- dev_err(ipc_pm->dev,
- "loc-pm=%d sleep: confused req-pm=%d",
- ipc_pm->cp_state, cp_pm_req);
- break;
- }
- break;
- default:
- dev_err(ipc_pm->dev, "confused loc-pm=%d, req-pm=%d",
- ipc_pm->cp_state, cp_pm_req);
- break;
- }
- return false;
- }
- void ipc_pm_init(struct iosm_protocol *ipc_protocol)
- {
- struct iosm_imem *ipc_imem = ipc_protocol->imem;
- struct iosm_pm *ipc_pm = &ipc_protocol->pm;
- ipc_pm->pcie = ipc_imem->pcie;
- ipc_pm->dev = ipc_imem->dev;
- ipc_pm->pm_cond.irq = IPC_PM_SLEEP;
- ipc_pm->pm_cond.hs = IPC_PM_SLEEP;
- ipc_pm->pm_cond.link = IPC_PM_ACTIVE;
- ipc_pm->cp_state = IPC_MEM_DEV_PM_ACTIVE;
- ipc_pm->ap_state = IPC_MEM_DEV_PM_ACTIVE;
- ipc_pm->host_pm_state = IPC_MEM_HOST_PM_ACTIVE;
- /* Create generic wait-for-completion handler for Host Sleep
- * and device sleep coordination.
- */
- init_completion(&ipc_pm->host_sleep_complete);
- /* Complete all memory stores before clearing bit */
- smp_mb__before_atomic();
- clear_bit(0, &ipc_pm->host_sleep_pend);
- /* Complete all memory stores after clearing bit */
- smp_mb__after_atomic();
- }
- void ipc_pm_deinit(struct iosm_protocol *proto)
- {
- struct iosm_pm *ipc_pm = &proto->pm;
- complete(&ipc_pm->host_sleep_complete);
- }
|