Browse Source

qcacld-3.0: Add runtime pm initialization

Add runtime pm initialization, including the creation
of a debugfs entry.

Change-Id: Ib057feaf36a04bb525a731e236187c134449c5f2
CRs-Fixed: 935300
Houston Hoffman 9 years ago
parent
commit
62aa58dc7a
4 changed files with 389 additions and 1 deletions
  1. 1 0
      core/hdd/src/wlan_hdd_driver_ops.c
  2. 1 0
      core/hif/inc/hif.h
  3. 325 0
      core/hif/src/pcie/if_pci.c
  4. 62 1
      core/hif/src/pcie/if_pci.h

+ 1 - 0
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);

+ 1 - 0
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);

+ 325 - 0
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 <linux/debugfs.h>
+#include <linux/seq_file.h>
 #include <osapi_linux.h>
 #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;
 }

+ 62 - 1
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);