qcacld-3.0: Add hif apis to prevent allow runtime pm

Support pm_runtime put/get/prevent/allow apis in hif layer.

Change-Id: I66be45de4177eeff3678b9ebd874c9bbde3cad22
CRs-Fixed: 935300
This commit is contained in:
Houston Hoffman 2015-11-02 16:15:02 -08:00 • committed by Akash Patel
commit d13fa735b2
3 changed files with 554 additions and 10 deletions

View file

@ -642,6 +642,47 @@ CDF_STATUS hif_enable(void *hif_ctx, struct device *dev, void *bdev,
enum hif_enable_type type);
void hif_disable(void *hif_ctx, enum hif_disable_type type);
void hif_enable_power_gating(void *hif_ctx);
#ifdef FEATURE_RUNTIME_PM
struct hif_pm_runtime_lock;
int hif_pm_runtime_get(void *hif_ctx);
int hif_pm_runtime_put(void *hif_ctx);
struct hif_pm_runtime_lock *hif_runtime_lock_init(const char *name);
void hif_runtime_lock_deinit(struct hif_pm_runtime_lock *lock);
int hif_pm_runtime_prevent_suspend(void *ol_sc,
struct hif_pm_runtime_lock *lock);
int hif_pm_runtime_allow_suspend(void *ol_sc,
struct hif_pm_runtime_lock *lock);
int hif_pm_runtime_prevent_suspend_timeout(void *ol_sc,
struct hif_pm_runtime_lock *lock, unsigned int delay);
#else
struct hif_pm_runtime_lock {
const char *name;
};
static inline int hif_pm_runtime_get(void *hif_ctx)
{ return 0; }
static inline int hif_pm_runtime_put(void *hif_ctx)
{ return 0; }
static inline struct hif_pm_runtime_lock *hif_runtime_lock_init(
const char *name)
{ return NULL; }
static inline void hif_runtime_lock_deinit(struct hif_pm_runtime_lock *lock)
{}
static inline int hif_pm_runtime_prevent_suspend(void *ol_sc,
struct hif_pm_runtime_lock *lock)
{ return 0; }
static inline int hif_pm_runtime_allow_suspend(void *ol_sc,
struct hif_pm_runtime_lock *lock)
{ return 0; }
static inline int hif_pm_runtime_prevent_suspend_timeout(void *ol_sc,
struct hif_pm_runtime_lock *lock, unsigned int delay)
{ return 0; }
#endif
void hif_runtime_pm_set_state_inprogress(void);
void hif_runtime_pm_set_state_on(void);
void hif_enable_power_management(void *hif_ctx);
int hif_bus_resume(void);
int hif_bus_suspend(void);

View file

@ -992,16 +992,7 @@ static inline void hif_runtime_pm_debugfs_remove(struct hif_pci_softc *sc)
}
#endif
/**
* hif_pm_runtime_lock_timeout_fn() - callback the runtime lock timeout
* @data: calback data that is the pci context
*
* if runtime locks are aquired with a timeout, this function releases
* the locks when the latest runtime lock expires.
*
* dummy implementation until lock acquisition is implemented.
*/
void hif_pm_runtime_lock_timeout_fn(unsigned long data) {}
static void hif_pm_runtime_lock_timeout_fn(unsigned long data);
/**
* hif_pm_runtime_start(): start the runtime pm
@ -2564,3 +2555,460 @@ int hif_get_target_type(struct ol_softc *ol_sc, struct device *dev,
return hif_get_device_type(id->device, revision_id,
hif_type, target_type);
}
#ifdef FEATURE_RUNTIME_PM
/**
* hif_pm_runtime_get() - do a get opperation on the device
*
* A get opperation will prevent a runtime suspend untill a
* corresponding put is done. This api should be used when sending
* data.
*
* CONTRARY TO THE REGULAR RUNTIME PM, WHEN THE BUS IS SUSPENDED,
* THIS API WILL ONLY REQUEST THE RESUME AND NOT TO A GET!!!
*
* return: success if the bus is up and a get has been issued
* otherwise an error code.
*/
int hif_pm_runtime_get(void *hif_ctx)
{
struct ol_softc *scn = hif_ctx;
struct hif_pci_softc *sc;
int ret;
int pm_state;
if (NULL == scn) {
HIF_ERROR("%s: Could not do runtime get, scn is null",
__func__);
return -EFAULT;
}
sc = scn->hif_sc;
pm_state = cdf_atomic_read(&sc->pm_state);
if (pm_state == HIF_PM_RUNTIME_STATE_ON ||
pm_state == HIF_PM_RUNTIME_STATE_NONE) {
sc->pm_stats.runtime_get++;
ret = __hif_pm_runtime_get(sc->dev);
/* Get can return 1 if the device is already active, just return
* success in that case
*/
if (ret > 0)
ret = 0;
if (ret)
hif_pm_runtime_put(hif_ctx);
if (ret && ret != -EINPROGRESS) {
sc->pm_stats.runtime_get_err++;
HIF_ERROR("%s: Runtime Get PM Error in pm_state:%d ret: %d",
__func__, cdf_atomic_read(&sc->pm_state), ret);
}
return ret;
}
sc->pm_stats.request_resume++;
sc->pm_stats.last_resume_caller = (void *)_RET_IP_;
ret = hif_pm_request_resume(sc->dev);
return -EAGAIN;
}
/**
* hif_pm_runtime_put() - do a put opperation on the device
*
* A put opperation will allow a runtime suspend after a corresponding
* get was done. This api should be used when sending data.
*
* This api will return a failure if runtime pm is stopped
* This api will return failure if it would decrement the usage count below 0.
*
* return: CDF_STATUS_SUCCESS if the put is performed
*/
int hif_pm_runtime_put(void *hif_ctx)
{
struct ol_softc *scn = (struct ol_softc *)hif_ctx;
struct hif_pci_softc *sc;
int pm_state, usage_count;
unsigned long flags;
char *error = NULL;
if (NULL == scn) {
HIF_ERROR("%s: Could not do runtime put, scn is null",
__func__);
return -EFAULT;
}
sc = scn->hif_sc;
usage_count = atomic_read(&sc->dev->power.usage_count);
if (usage_count == 1) {
pm_state = cdf_atomic_read(&sc->pm_state);
if (pm_state == HIF_PM_RUNTIME_STATE_NONE)
error = "Ignoring unexpected put when runtime pm is disabled";
} else if (usage_count == 0) {
error = "PUT Without a Get Operation";
}
if (error) {
spin_lock_irqsave(&sc->runtime_lock, flags);
hif_pci_runtime_pm_warn(sc, error);
spin_unlock_irqrestore(&sc->runtime_lock, flags);
return -EINVAL;
}
sc->pm_stats.runtime_put++;
hif_pm_runtime_mark_last_busy(sc->dev);
hif_pm_runtime_put_auto(sc->dev);
return 0;
}
/**
* __hif_pm_runtime_prevent_suspend() - prevent runtime suspend for a protocol reason
* @hif_sc: pci context
* @lock: runtime_pm lock being acquired
*
* Return 0 if successful.
*/
static int __hif_pm_runtime_prevent_suspend(struct hif_pci_softc
*hif_sc, struct hif_pm_runtime_lock *lock)
{
int ret = 0;
/*
* We shouldn't be setting context->timeout to zero here when
* context is active as we will have a case where Timeout API's
* for the same context called back to back.
* eg: echo "1=T:10:T:20" > /d/cnss_runtime_pm
* Set context->timeout to zero in hif_pm_runtime_prevent_suspend
* API to ensure the timeout version is no more active and
* list entry of this context will be deleted during allow suspend.
*/
if (lock->active)
return 0;
ret = __hif_pm_runtime_get(hif_sc->dev);
/**
* The ret can be -EINPROGRESS, if Runtime status is RPM_RESUMING or
* RPM_SUSPENDING. Any other negative value is an error.
* We shouldn't be do runtime_put here as in later point allow
* suspend gets called with the the context and there the usage count
* is decremented, so suspend will be prevented.
*/
if (ret < 0 && ret != -EINPROGRESS) {
hif_sc->pm_stats.runtime_get_err++;
hif_pci_runtime_pm_warn(hif_sc,
"Prevent Suspend Runtime PM Error");
}
hif_sc->prevent_suspend_cnt++;
lock->active = true;
list_add_tail(&lock->list, &hif_sc->prevent_suspend_list);
hif_sc->pm_stats.prevent_suspend++;
HIF_ERROR("%s: in pm_state:%d ret: %d", __func__,
cdf_atomic_read(&hif_sc->pm_state), ret);
return ret;
}
static int __hif_pm_runtime_allow_suspend(struct hif_pci_softc *hif_sc,
struct hif_pm_runtime_lock *lock)
{
int ret = 0;
int usage_count;
if (hif_sc->prevent_suspend_cnt == 0)
return ret;
if (!lock->active)
return ret;
usage_count = atomic_read(&hif_sc->dev->power.usage_count);
/*
* During Driver unload, platform driver increments the usage
* count to prevent any runtime suspend getting called.
* So during driver load in HIF_PM_RUNTIME_STATE_NONE state the
* usage_count should be one. Ideally this shouldn't happen as
* context->active should be active for allow suspend to happen
* Handling this case here to prevent any failures.
*/
if ((cdf_atomic_read(&hif_sc->pm_state) == HIF_PM_RUNTIME_STATE_NONE
&& usage_count == 1) || usage_count == 0) {
hif_pci_runtime_pm_warn(hif_sc,
"Allow without a prevent suspend");
return -EINVAL;
}
list_del(&lock->list);
hif_sc->prevent_suspend_cnt--;
lock->active = false;
lock->timeout = 0;
hif_pm_runtime_mark_last_busy(hif_sc->dev);
ret = hif_pm_runtime_put_auto(hif_sc->dev);
HIF_ERROR("%s: in pm_state:%d ret: %d", __func__,
cdf_atomic_read(&hif_sc->pm_state), ret);
hif_sc->pm_stats.allow_suspend++;
return ret;
}
/**
* hif_pm_runtime_lock_timeout_fn() - callback the runtime lock timeout
* @data: calback data that is the pci context
*
* if runtime locks are aquired with a timeout, this function releases
* the locks when the last runtime lock expires.
*
* dummy implementation until lock acquisition is implemented.
*/
static void hif_pm_runtime_lock_timeout_fn(unsigned long data)
{
struct hif_pci_softc *hif_sc = (struct hif_pci_softc *)data;
unsigned long flags;
unsigned long timer_expires;
struct hif_pm_runtime_lock *context, *temp;
spin_lock_irqsave(&hif_sc->runtime_lock, flags);
timer_expires = hif_sc->runtime_timer_expires;
/* Make sure we are not called too early, this should take care of
* following case
*
* CPU0 CPU1 (timeout function)
* ---- ----------------------
* spin_lock_irq
* timeout function called
*
* mod_timer()
*
* spin_unlock_irq
* spin_lock_irq
*/
if (timer_expires > 0 && !time_after(timer_expires, jiffies)) {
hif_sc->runtime_timer_expires = 0;
list_for_each_entry_safe(context, temp,
&hif_sc->prevent_suspend_list, list) {
if (context->timeout) {
__hif_pm_runtime_allow_suspend(hif_sc, context);
hif_sc->pm_stats.allow_suspend_timeout++;
}
}
}
spin_unlock_irqrestore(&hif_sc->runtime_lock, flags);
}
int hif_pm_runtime_prevent_suspend(void *ol_sc,
struct hif_pm_runtime_lock *data)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
struct hif_pci_softc *hif_sc = sc->hif_sc;
struct hif_pm_runtime_lock *context = data;
unsigned long flags;
if (!sc->enable_runtime_pm)
return 0;
if (!context)
return -EINVAL;
spin_lock_irqsave(&hif_sc->runtime_lock, flags);
context->timeout = 0;
__hif_pm_runtime_prevent_suspend(hif_sc, context);
spin_unlock_irqrestore(&hif_sc->runtime_lock, flags);
return 0;
}
int hif_pm_runtime_allow_suspend(void *ol_sc, struct hif_pm_runtime_lock *data)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
struct hif_pci_softc *hif_sc = sc->hif_sc;
struct hif_pm_runtime_lock *context = data;
unsigned long flags;
if (!sc->enable_runtime_pm)
return 0;
if (!context)
return -EINVAL;
spin_lock_irqsave(&hif_sc->runtime_lock, flags);
__hif_pm_runtime_allow_suspend(hif_sc, context);
/* The list can be empty as well in cases where
* we have one context in the list and the allow
* suspend came before the timer expires and we delete
* context above from the list.
* When list is empty prevent_suspend count will be zero.
*/
if (hif_sc->prevent_suspend_cnt == 0 &&
hif_sc->runtime_timer_expires > 0) {
del_timer(&hif_sc->runtime_timer);
hif_sc->runtime_timer_expires = 0;
}
spin_unlock_irqrestore(&hif_sc->runtime_lock, flags);
return 0;
}
/**
* hif_pm_runtime_prevent_suspend_timeout() - Prevent runtime suspend timeout
* @ol_sc: HIF context
* @lock: which lock is being acquired
* @delay: Timeout in milliseconds
*
* Prevent runtime suspend with a timeout after which runtime suspend would be
* allowed. This API uses a single timer to allow the suspend and timer is
* modified if the timeout is changed before timer fires.
* If the timeout is less than autosuspend_delay then use mark_last_busy instead
* of starting the timer.
*
* It is wise to try not to use this API and correct the design if possible.
*
* Return: 0 on success and negative error code on failure
*/
int hif_pm_runtime_prevent_suspend_timeout(void *ol_sc,
struct hif_pm_runtime_lock *lock, unsigned int delay)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
struct hif_pci_softc *hif_sc = sc->hif_sc;
int ret = 0;
unsigned long expires;
unsigned long flags;
struct hif_pm_runtime_lock *context = lock;
if (cds_is_load_unload_in_progress()) {
HIF_ERROR("%s: Load/unload in progress, ignore!",
__func__);
return -EINVAL;
}
if (cds_is_logp_in_progress()) {
HIF_ERROR("%s: LOGP in progress, ignore!", __func__);
return -EINVAL;
}
if (!sc->enable_runtime_pm)
return 0;
if (!context)
return -EINVAL;
/*
* Don't use internal timer if the timeout is less than auto suspend
* delay.
*/
if (delay <= hif_sc->dev->power.autosuspend_delay) {
hif_pm_request_resume(hif_sc->dev);
hif_pm_runtime_mark_last_busy(hif_sc->dev);
return ret;
}
expires = jiffies + msecs_to_jiffies(delay);
expires += !expires;
spin_lock_irqsave(&hif_sc->runtime_lock, flags);
context->timeout = delay;
ret = __hif_pm_runtime_prevent_suspend(hif_sc, context);
hif_sc->pm_stats.prevent_suspend_timeout++;
/* Modify the timer only if new timeout is after already configured
* timeout
*/
if (time_after(expires, hif_sc->runtime_timer_expires)) {
mod_timer(&hif_sc->runtime_timer, expires);
hif_sc->runtime_timer_expires = expires;
}
spin_unlock_irqrestore(&hif_sc->runtime_lock, flags);
HIF_ERROR("%s: pm_state: %d delay: %dms ret: %d\n", __func__,
cdf_atomic_read(&hif_sc->pm_state), delay, ret);
return ret;
}
/**
* hif_runtime_lock_init() - API to initialize Runtime PM context
* @name: Context name
*
* This API initalizes the Runtime PM context of the caller and
* return the pointer.
*
* Return: void *
*/
struct hif_pm_runtime_lock *hif_runtime_lock_init(const char *name)
{
struct hif_pm_runtime_lock *context;
context = cdf_mem_malloc(sizeof(*context));
if (!context) {
HIF_ERROR("%s: No memory for Runtime PM wakelock context\n",
__func__);
return NULL;
}
context->name = name ? name : "Default";
return context;
}
/**
* hif_runtime_lock_deinit() - This API frees the runtime pm ctx
* @data: Runtime PM context
*
* Return: void
*/
void hif_runtime_lock_deinit(struct hif_pm_runtime_lock *data)
{
unsigned long flags;
struct hif_pm_runtime_lock *context = data;
struct ol_softc *scn = cds_get_context(CDF_MODULE_ID_HIF);
struct hif_pci_softc *sc;
if (!scn)
return;
sc = scn->hif_sc;
if (!sc)
return;
if (!context)
return;
/*
* Ensure to delete the context list entry and reduce the usage count
* before freeing the context if context is active.
*/
spin_lock_irqsave(&sc->runtime_lock, flags);
__hif_pm_runtime_allow_suspend(sc, context);
spin_unlock_irqrestore(&sc->runtime_lock, flags);
cdf_mem_free(context);
}
#endif /* FEATURE_RUNTIME_PM */

View file

@ -173,4 +173,59 @@ void hif_pci_cancel_deferred_target_sleep(struct ol_softc *scn);
#endif
#define HIF_CE_DRAIN_WAIT_CNT 20
#ifdef FEATURE_RUNTIME_PM
#include <linux/pm_runtime.h>
#ifdef WLAN_OPEN_SOURCE
static inline int hif_pm_request_resume(struct device *dev)
{
return pm_request_resume(dev);
}
static inline int __hif_pm_runtime_get(struct device *dev)
{
return pm_runtime_get(dev);
}
static inline int hif_pm_runtime_put_auto(struct device *dev)
{
return pm_runtime_put_autosuspend(dev);
}
static inline void hif_pm_runtime_mark_last_busy(struct device *dev)
{
pm_runtime_mark_last_busy(dev);
}
static inline int hif_pm_runtime_resume(struct device *dev)
{
return pm_runtime_resume(dev);
}
#else
static inline int hif_pm_request_resume(struct device *dev)
{
return cnss_pm_runtime_request(dev, CNSS_PM_REQUEST_RESUME);
}
static inline int __hif_pm_runtime_get(struct device *dev)
{
return cnss_pm_runtime_request(dev, CNSS_PM_RUNTIME_GET);
}
static inline int hif_pm_runtime_put_auto(struct device *dev)
{
return cnss_pm_runtime_request(dev, CNSS_PM_RUNTIME_PUT_AUTO);
}
static inline void hif_pm_runtime_mark_last_busy(struct device *dev)
{
cnss_pm_runtime_request(dev, CNSS_PM_RUNTIME_MARK_LAST_BUSY);
}
static inline int hif_pm_runtime_resume(struct device *dev)
{
return cnss_pm_runtime_request(dev, CNSS_PM_RUNTIME_RESUME);
}
#endif /* WLAN_OPEN_SOURCE */
#endif /* FEATURE_RUNTIME_PM */
#endif /* __ATH_PCI_H__ */