// 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); }