diff --git a/core/hdd/src/wlan_hdd_driver_ops.c b/core/hdd/src/wlan_hdd_driver_ops.c index 2308a0d6ed..39cd2111e8 100644 --- a/core/hdd/src/wlan_hdd_driver_ops.c +++ b/core/hdd/src/wlan_hdd_driver_ops.c @@ -247,6 +247,7 @@ static int wlan_hdd_probe(struct device *dev, void *bdev, const hif_bus_id *bid, if (ret) goto err_hif_close; + hif_enable_power_management(hif_ctx); if (reinit) { cds_set_recovery_in_progress(false); diff --git a/core/hif/inc/hif.h b/core/hif/inc/hif.h index cc7a66fd7f..b1ff3d9302 100644 --- a/core/hif/inc/hif.h +++ b/core/hif/inc/hif.h @@ -642,6 +642,7 @@ 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); +void hif_enable_power_management(void *hif_ctx); int hif_bus_resume(void); int hif_bus_suspend(void); void hif_vote_link_down(void); diff --git a/core/hif/src/pcie/if_pci.c b/core/hif/src/pcie/if_pci.c index 21cec32177..15e8ccb94c 100644 --- a/core/hif/src/pcie/if_pci.c +++ b/core/hif/src/pcie/if_pci.c @@ -43,6 +43,8 @@ #include "bmi_msg.h" /* TARGET_TYPE_ */ #include "regtable.h" #include "ol_fw.h" +#include +#include #include #include "cds_api.h" #include "cdf_status.h" @@ -804,6 +806,326 @@ end: cdf_atomic_dec(&scn->active_tasklet_cnt); } +#ifdef FEATURE_RUNTIME_PM +#define HIF_PCI_RUNTIME_PM_STATS(_s, _sc, _name) \ + seq_printf(_s, "%30s: %u\n", #_name, _sc->pm_stats._name) + +/** + * hif_pci_runtime_pm_warn() - Runtime PM Debugging API + * @sc: hif_pci_softc context + * @msg: log message + * + * log runtime pm stats when something seems off. + * + * Return: void + */ +void hif_pci_runtime_pm_warn(struct hif_pci_softc *sc, const char *msg) +{ + struct hif_pm_runtime_lock *ctx; + + HIF_ERROR("%s: usage_count: %d, pm_state: %d, prevent_suspend_cnt: %d", + msg, atomic_read(&sc->dev->power.usage_count), + atomic_read(&sc->pm_state), + sc->prevent_suspend_cnt); + + HIF_ERROR("runtime_status: %d, runtime_error: %d, disable_depth: %d autosuspend_delay: %d", + sc->dev->power.runtime_status, + sc->dev->power.runtime_error, + sc->dev->power.disable_depth, + sc->dev->power.autosuspend_delay); + + HIF_ERROR("runtime_get: %u, runtime_put: %u, request_resume: %u", + sc->pm_stats.runtime_get, sc->pm_stats.runtime_put, + sc->pm_stats.request_resume); + + HIF_ERROR("allow_suspend: %u, prevent_suspend: %u", + sc->pm_stats.allow_suspend, + sc->pm_stats.prevent_suspend); + + HIF_ERROR("prevent_suspend_timeout: %u, allow_suspend_timeout: %u", + sc->pm_stats.prevent_suspend_timeout, + sc->pm_stats.allow_suspend_timeout); + + HIF_ERROR("Suspended: %u, resumed: %u count", + sc->pm_stats.suspended, + sc->pm_stats.resumed); + + HIF_ERROR("suspend_err: %u, runtime_get_err: %u", + sc->pm_stats.suspend_err, + sc->pm_stats.runtime_get_err); + + HIF_ERROR("Active Wakeup Sources preventing Runtime Suspend: "); + + list_for_each_entry(ctx, &sc->prevent_suspend_list, list) { + HIF_ERROR("source %s; timeout %d ms", ctx->name, ctx->timeout); + } + + WARN_ON(1); +} + +/** + * hif_pci_pm_runtime_debugfs_show(): show debug stats for runtimepm + * @s: file to print to + * @data: unused + * + * debugging tool added to the debug fs for displaying runtimepm stats + * + * Return: 0 + */ +static int hif_pci_pm_runtime_debugfs_show(struct seq_file *s, void *data) +{ + struct hif_pci_softc *sc = s->private; + static const char * const autopm_state[] = {"NONE", "ON", "INPROGRESS", + "SUSPENDED"}; + unsigned int msecs_age; + int pm_state = atomic_read(&sc->pm_state); + unsigned long timer_expires, flags; + struct hif_pm_runtime_lock *ctx; + + seq_printf(s, "%30s: %s\n", "Runtime PM state", + autopm_state[pm_state]); + seq_printf(s, "%30s: %pf\n", "Last Resume Caller", + sc->pm_stats.last_resume_caller); + + if (pm_state == HIF_PM_RUNTIME_STATE_SUSPENDED) { + msecs_age = jiffies_to_msecs( + jiffies - sc->pm_stats.suspend_jiffies); + seq_printf(s, "%30s: %d.%03ds\n", "Suspended Since", + msecs_age / 1000, msecs_age % 1000); + } + + seq_printf(s, "%30s: %d\n", "PM Usage count", + atomic_read(&sc->dev->power.usage_count)); + + seq_printf(s, "%30s: %u\n", "prevent_suspend_cnt", + sc->prevent_suspend_cnt); + + HIF_PCI_RUNTIME_PM_STATS(s, sc, suspended); + HIF_PCI_RUNTIME_PM_STATS(s, sc, suspend_err); + HIF_PCI_RUNTIME_PM_STATS(s, sc, resumed); + HIF_PCI_RUNTIME_PM_STATS(s, sc, runtime_get); + HIF_PCI_RUNTIME_PM_STATS(s, sc, runtime_put); + HIF_PCI_RUNTIME_PM_STATS(s, sc, request_resume); + HIF_PCI_RUNTIME_PM_STATS(s, sc, prevent_suspend); + HIF_PCI_RUNTIME_PM_STATS(s, sc, allow_suspend); + HIF_PCI_RUNTIME_PM_STATS(s, sc, prevent_suspend_timeout); + HIF_PCI_RUNTIME_PM_STATS(s, sc, allow_suspend_timeout); + HIF_PCI_RUNTIME_PM_STATS(s, sc, runtime_get_err); + + timer_expires = sc->runtime_timer_expires; + if (timer_expires > 0) { + msecs_age = jiffies_to_msecs(timer_expires - jiffies); + seq_printf(s, "%30s: %d.%03ds\n", "Prevent suspend timeout", + msecs_age / 1000, msecs_age % 1000); + } + + spin_lock_irqsave(&sc->runtime_lock, flags); + if (list_empty(&sc->prevent_suspend_list)) { + spin_unlock_irqrestore(&sc->runtime_lock, flags); + return 0; + } + + seq_printf(s, "%30s: ", "Active Wakeup_Sources"); + list_for_each_entry(ctx, &sc->prevent_suspend_list, list) { + seq_printf(s, "%s", ctx->name); + if (ctx->timeout) + seq_printf(s, "(%d ms)", ctx->timeout); + seq_puts(s, " "); + } + seq_puts(s, "\n"); + spin_unlock_irqrestore(&sc->runtime_lock, flags); + + return 0; +} +#undef HIF_PCI_RUNTIME_PM_STATS + +/** + * hif_pci_autopm_open() - open a debug fs file to access the runtime pm stats + * @inode + * @file + * + * Return: linux error code of single_open. + */ +static int hif_pci_runtime_pm_open(struct inode *inode, struct file *file) +{ + return single_open(file, hif_pci_pm_runtime_debugfs_show, + inode->i_private); +} + +#ifdef WLAN_OPEN_SOURCE +static const struct file_operations hif_pci_runtime_pm_fops = { + .owner = THIS_MODULE, + .open = hif_pci_runtime_pm_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +/** + * hif_runtime_pm_debugfs_create() - creates runtimepm debugfs entry + * @sc: pci context + * + * creates a debugfs entry to debug the runtime pm feature. + */ +static void hif_runtime_pm_debugfs_create(struct hif_pci_softc *sc) +{ + sc->pm_dentry = debugfs_create_file("cnss_runtime_pm", + S_IRUSR, NULL, sc, + &hif_pci_runtime_pm_fops); +} +/** + * hif_runtime_pm_debugfs_remove() - removes runtimepm debugfs entry + * @sc: pci context + * + * removes the debugfs entry to debug the runtime pm feature. + */ +static void hif_runtime_pm_debugfs_remove(struct hif_pci_softc *sc) +{ + debugfs_remove(sc->pm_dentry); +} +#else +static inline void hif_runtime_pm_debugfs_create(struct hif_pci_softc *sc) +{ +} +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) {} + +/** + * hif_pm_runtime_start(): start the runtime pm + * @sc: pci context + * + * After this call, runtime pm will be active. + */ +static void hif_pm_runtime_start(struct hif_pci_softc *sc) +{ + struct ol_softc *ol_sc; + + ol_sc = sc->ol_sc; + + if (!ol_sc->enable_runtime_pm) { + HIF_INFO("%s: RUNTIME PM is disabled in ini\n", __func__); + return; + } + + if (cds_get_conparam() == CDF_FTM_MODE || + WLAN_IS_EPPING_ENABLED(cds_get_conparam())) { + HIF_INFO("%s: RUNTIME PM is disabled for FTM/EPPING mode\n", + __func__); + return; + } + + setup_timer(&sc->runtime_timer, hif_pm_runtime_lock_timeout_fn, + (unsigned long)sc); + + HIF_INFO("%s: Enabling RUNTIME PM, Delay: %d ms", __func__, + ol_sc->runtime_pm_delay); + + cnss_runtime_init(sc->dev, ol_sc->runtime_pm_delay); + cdf_atomic_set(&sc->pm_state, HIF_PM_RUNTIME_STATE_ON); + hif_runtime_pm_debugfs_create(sc); +} + +/** + * hif_pm_runtime_stop(): stop runtime pm + * @sc: pci context + * + * Turns off runtime pm and frees corresponding resources + * that were acquired by hif_runtime_pm_start(). + */ +static void hif_pm_runtime_stop(struct hif_pci_softc *sc) +{ + struct ol_softc *ol_sc = sc->ol_sc; + + if (!ol_sc->enable_runtime_pm) + return; + + if (cds_get_conparam() == CDF_FTM_MODE || + WLAN_IS_EPPING_ENABLED(cds_get_conparam())) + return; + + cnss_runtime_exit(sc->dev); + cnss_pm_runtime_request(sc->dev, CNSS_PM_RUNTIME_RESUME); + + cdf_atomic_set(&sc->pm_state, HIF_PM_RUNTIME_STATE_NONE); + + hif_runtime_pm_debugfs_remove(sc); + del_timer_sync(&sc->runtime_timer); + /* doesn't wait for penting trafic unlike cld-2.0 */ +} + +/** + * hif_pm_runtime_open(): initialize runtime pm + * @sc: pci data structure + * + * Early initialization + */ +static void hif_pm_runtime_open(struct hif_pci_softc *sc) +{ + spin_lock_init(&sc->runtime_lock); + + cdf_atomic_init(&sc->pm_state); + cdf_atomic_set(&sc->pm_state, HIF_PM_RUNTIME_STATE_NONE); + INIT_LIST_HEAD(&sc->prevent_suspend_list); +} + +/** + * hif_pm_runtime_close(): close runtime pm + * @sc: pci bus handle + * + * ensure runtime_pm is stopped before closing the driver + */ +static void hif_pm_runtime_close(struct hif_pci_softc *sc) +{ + if (cdf_atomic_read(&sc->pm_state) == HIF_PM_RUNTIME_STATE_NONE) + return; + else + hif_pm_runtime_stop(sc); +} + + +#else + +static void hif_pm_runtime_close(struct hif_pci_softc *sc) {} +static void hif_pm_runtime_open(struct hif_pci_softc *sc) {} +static void hif_pm_runtime_start(struct hif_pci_softc *sc) {} +#endif + +/** + * hif_enable_power_management(): enable power management + * @hif_ctx: hif context + * + * Currently only does runtime pm. Eventually this function could + * consolidate other power state features such as only letting + * the soc sleep after the driver finishes loading and re-enabling + * aspm (hif_enable_power_gating). + */ +void hif_enable_power_management(void *hif_ctx) +{ + struct hif_pci_softc *pci_ctx; + + if (hif_ctx == NULL) { + HIF_ERROR("%s, hif_ctx null", __func__); + return; + } + + pci_ctx = ((struct ol_softc *)hif_ctx)->hif_sc; + + hif_pm_runtime_start(pci_ctx); +} + #define ATH_PCI_PROBE_RETRY_MAX 3 /** * hif_bus_open(): hif_bus_open @@ -824,6 +1146,7 @@ CDF_STATUS hif_bus_open(struct ol_softc *ol_sc, enum ath_hal_bus_type bus_type) ol_sc->hif_sc = (void *)sc; sc->ol_sc = ol_sc; ol_sc->bus_type = bus_type; + hif_pm_runtime_open(sc); cdf_spinlock_init(&ol_sc->irq_lock); @@ -846,6 +1169,8 @@ void hif_bus_close(struct ol_softc *ol_sc) sc = ol_sc->hif_sc; if (sc == NULL) return; + + hif_pm_runtime_close(sc); cdf_mem_free(sc); ol_sc->hif_sc = NULL; } diff --git a/core/hif/src/pcie/if_pci.h b/core/hif/src/pcie/if_pci.h index b2f5bd6c20..d49c53e671 100644 --- a/core/hif/src/pcie/if_pci.h +++ b/core/hif/src/pcie/if_pci.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2015 The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2016 The Linux Foundation. All rights reserved. * * Previously licensed under the ISC license by Qualcomm Atheros, Inc. * @@ -50,6 +50,54 @@ struct hif_tasklet_entry { uint8_t id; /* 0 - 9: maps to CE, 10: fw */ void *hif_handler; /* struct hif_pci_softc */ }; + +/** + * enum hif_pm_runtime_state - Driver States for Runtime Power Management + * HIF_PM_RUNTIME_STATE_NONE: runtime pm is off + * HIF_PM_RUNTIME_STATE_ON: runtime pm is active and link is active + * HIF_PM_RUNTIME_STATE_INPROGRESS: a runtime suspend or resume is in progress + * HIF_PM_RUNTIME_STATE_SUSPENDED: the driver is runtime suspended + */ +enum hif_pm_runtime_state { + HIF_PM_RUNTIME_STATE_NONE, + HIF_PM_RUNTIME_STATE_ON, + HIF_PM_RUNTIME_STATE_INPROGRESS, + HIF_PM_RUNTIME_STATE_SUSPENDED, +}; + +#ifdef FEATURE_RUNTIME_PM + +/** + * struct hif_pm_runtime_lock - data structure for preventing runtime suspend + * @list - global list of runtime locks + * @active - true if this lock is preventing suspend + * @name - character string for tracking this lock + */ +struct hif_pm_runtime_lock { + struct list_head list; + bool active; + uint32_t timeout; + const char *name; +}; + +/* Debugging stats for Runtime PM */ +struct hif_pci_pm_stats { + u32 suspended; + u32 suspend_err; + u32 resumed; + u32 runtime_get; + u32 runtime_put; + u32 request_resume; + u32 allow_suspend; + u32 prevent_suspend; + u32 prevent_suspend_timeout; + u32 allow_suspend_timeout; + u32 runtime_get_err; + void *last_resume_caller; + unsigned long suspend_jiffies; +}; +#endif + struct hif_pci_softc { void __iomem *mem; /* PCI address. */ /* For efficiency, should be first in struct */ @@ -69,6 +117,19 @@ struct hif_pci_softc { cdf_dma_addr_t soc_pcie_bar0; struct hif_tasklet_entry tasklet_entries[HIF_MAX_TASKLET_NUM]; bool pci_enabled; +#ifdef FEATURE_RUNTIME_PM + atomic_t pm_state; + uint32_t prevent_suspend_cnt; + struct hif_pci_pm_stats pm_stats; + struct work_struct pm_work; + spinlock_t runtime_lock; + struct timer_list runtime_timer; + struct list_head prevent_suspend_list; + unsigned long runtime_timer_expires; +#ifdef WLAN_OPEN_SOURCE + struct dentry *pm_dentry; +#endif +#endif }; bool hif_pci_targ_is_present(struct ol_softc *scn, void *__iomem *mem);