-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements state machine to handle host & device sleep. Signed-off-by: M Chetan Kumar <m.chetan.kumar@intel.com> Signed-off-by: David S. Miller <davem@davemloft.net>
- Loading branch information
M Chetan Kumar
authored and
David S. Miller
committed
Jun 13, 2021
1 parent
9413491
commit be8c936
Showing
2 changed files
with
540 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,333 @@ | ||
// 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); | ||
} |
Oops, something went wrong.