Prechádzať zdrojové kódy

soc: swr-mstr: Add wake lock support to prevent suspend

Add lock and unlock sleep functions to master to prevent
system suspend during interrupts or slave wakeup
requests.

Change-Id: Icb4ea354ec6e2925f83c2380f30ea8e37aca7449
Signed-off-by: Ramprasad Katkam <[email protected]>
Ramprasad Katkam 6 rokov pred
rodič
commit
5734987f61
2 zmenil súbory, kde vykonal 210 pridanie a 18 odobranie
  1. 197 18
      soc/swr-mstr-ctrl.c
  2. 13 0
      soc/swr-mstr-ctrl.h

+ 197 - 18
soc/swr-mstr-ctrl.c

@@ -26,7 +26,8 @@
 #include "swr-mstr-ctrl.h"
 #include "swrm_port_config.h"
 
-
+#define SWRM_SYSTEM_RESUME_TIMEOUT_MS 700
+#define SWRM_SYS_SUSPEND_WAIT 1
 #define SWR_BROADCAST_CMD_ID            0x0F
 #define SWR_AUTO_SUSPEND_DELAY          3 /* delay in sec */
 #define SWR_DEV_ID_MASK			0xFFFFFFFF
@@ -82,6 +83,8 @@ static struct dentry *debugfs_poke;
 static struct dentry *debugfs_reg_dump;
 static unsigned int read_data;
 
+static bool swrm_lock_sleep(struct swr_mstr_ctrl *swrm);
+static void swrm_unlock_sleep(struct swr_mstr_ctrl *swrm);
 
 static bool swrm_is_msm_variant(int val)
 {
@@ -1262,6 +1265,10 @@ static irqreturn_t swr_mstr_interrupt(int irq, void *dev)
 	struct swr_device *swr_dev;
 	struct swr_master *mstr = &swrm->master;
 
+	if (unlikely(swrm_lock_sleep(swrm) == false)) {
+		dev_err(swrm->dev, "%s Failed to hold suspend\n", __func__);
+		return IRQ_NONE;
+	}
 
 	mutex_lock(&swrm->reslock);
 	swrm_clk_request(swrm, true);
@@ -1411,6 +1418,7 @@ handle_irq:
 	mutex_lock(&swrm->reslock);
 	swrm_clk_request(swrm, false);
 	mutex_unlock(&swrm->reslock);
+	swrm_unlock_sleep(swrm);
 	return ret;
 }
 
@@ -1454,12 +1462,19 @@ static void swrm_wakeup_work(struct work_struct *work)
 	mutex_lock(&swrm->devlock);
 	if (!swrm->dev_up) {
 		mutex_unlock(&swrm->devlock);
-		return;
+		goto exit;
 	}
 	mutex_unlock(&swrm->devlock);
+	if (unlikely(swrm_lock_sleep(swrm) == false)) {
+		dev_err(swrm->dev, "%s Failed to hold suspend\n", __func__);
+		goto exit;
+	}
 	pm_runtime_get_sync(swrm->dev);
 	pm_runtime_mark_last_busy(swrm->dev);
 	pm_runtime_put_autosuspend(swrm->dev);
+	swrm_unlock_sleep(swrm);
+exit:
+	pm_relax(swrm->dev);
 }
 
 static int swrm_get_device_status(struct swr_mstr_ctrl *swrm, u8 devnum)
@@ -1545,6 +1560,10 @@ static void swrm_device_wakeup_vote(struct swr_master *mstr)
 			__func__);
 		return;
 	}
+	if (unlikely(swrm_lock_sleep(swrm) == false)) {
+		dev_err(swrm->dev, "%s Failed to hold suspend\n", __func__);
+		return;
+	}
 	pm_runtime_get_sync(swrm->dev);
 }
 
@@ -1559,6 +1578,7 @@ static void swrm_device_wakeup_unvote(struct swr_master *mstr)
 	}
 	pm_runtime_mark_last_busy(swrm->dev);
 	pm_runtime_put_autosuspend(swrm->dev);
+	swrm_unlock_sleep(swrm);
 }
 
 static int swrm_master_init(struct swr_mstr_ctrl *swrm)
@@ -1639,6 +1659,7 @@ static int swrm_event_notify(struct notifier_block *self,
 	case SWR_WAKE_IRQ_EVENT:
 		if (swrm->ipc_wakeup && !swrm->ipc_wakeup_triggered) {
 			swrm->ipc_wakeup_triggered = true;
+			pm_stay_awake(swrm->dev);
 			schedule_work(&swrm->wakeup_work);
 		}
 		break;
@@ -1745,6 +1766,21 @@ static int swrm_probe(struct platform_device *pdev)
 			&swrm->clk_stop_mode0_supp)) {
 		swrm->clk_stop_mode0_supp = FALSE;
 	}
+
+	ret = of_property_read_u32(swrm->dev->of_node, "qcom,swr-num-dev",
+				   &swrm->num_dev);
+	if (ret) {
+		dev_dbg(&pdev->dev, "%s: Looking up %s property failed\n",
+			__func__, "qcom,swr-num-dev");
+	} else {
+		if (swrm->num_dev > SWR_MAX_SLAVE_DEVICES) {
+			dev_err(&pdev->dev, "%s: num_dev %d > max limit %d\n",
+				__func__, swrm->num_dev, SWR_MAX_SLAVE_DEVICES);
+			ret = -EINVAL;
+			goto err_pdata_fail;
+		}
+	}
+
 	/* Parse soundwire port mapping */
 	ret = of_property_read_u32(pdev->dev.of_node, "qcom,swr-num-ports",
 				&num_ports);
@@ -1827,24 +1863,17 @@ static int swrm_probe(struct platform_device *pdev)
 	mutex_init(&swrm->iolock);
 	mutex_init(&swrm->clklock);
 	mutex_init(&swrm->devlock);
+	mutex_init(&swrm->pm_lock);
+	swrm->wlock_holders = 0;
+	swrm->pm_state = SWRM_PM_SLEEPABLE;
+	init_waitqueue_head(&swrm->pm_wq);
+	pm_qos_add_request(&swrm->pm_qos_req,
+			   PM_QOS_CPU_DMA_LATENCY,
+			   PM_QOS_DEFAULT_VALUE);
 
 	for (i = 0 ; i < SWR_MSTR_PORT_LEN; i++)
 		INIT_LIST_HEAD(&swrm->mport_cfg[i].port_req_list);
 
-	ret = of_property_read_u32(swrm->dev->of_node, "qcom,swr-num-dev",
-				   &swrm->num_dev);
-	if (ret) {
-		dev_dbg(&pdev->dev, "%s: Looking up %s property failed\n",
-			__func__, "qcom,swr-num-dev");
-	} else {
-		if (swrm->num_dev > SWR_MAX_SLAVE_DEVICES) {
-			dev_err(&pdev->dev, "%s: num_dev %d > max limit %d\n",
-				__func__, swrm->num_dev, SWR_MAX_SLAVE_DEVICES);
-			ret = -EINVAL;
-			goto err_pdata_fail;
-		}
-	}
-
 	if (swrm->reg_irq) {
 		ret = swrm->reg_irq(swrm->handle, swr_mstr_interrupt, swrm,
 			    SWR_IRQ_REGISTER);
@@ -1940,6 +1969,9 @@ err_irq_fail:
 	mutex_destroy(&swrm->force_down_lock);
 	mutex_destroy(&swrm->iolock);
 	mutex_destroy(&swrm->clklock);
+	mutex_destroy(&swrm->pm_lock);
+	pm_qos_remove_request(&swrm->pm_qos_req);
+
 err_pdata_fail:
 err_memory_fail:
 	return ret;
@@ -1956,7 +1988,7 @@ static int swrm_remove(struct platform_device *pdev)
 		free_irq(swrm->irq, swrm);
 	else if (swrm->wake_irq > 0)
 		free_irq(swrm->wake_irq, swrm);
-
+	cancel_work_sync(&swrm->wakeup_work);
 	pm_runtime_disable(&pdev->dev);
 	pm_runtime_set_suspended(&pdev->dev);
 	swr_unregister_master(&swrm->master);
@@ -1966,6 +1998,8 @@ static int swrm_remove(struct platform_device *pdev)
 	mutex_destroy(&swrm->iolock);
 	mutex_destroy(&swrm->clklock);
 	mutex_destroy(&swrm->force_down_lock);
+	mutex_destroy(&swrm->pm_lock);
+	pm_qos_remove_request(&swrm->pm_qos_req);
 	devm_kfree(&pdev->dev, swrm);
 	return 0;
 }
@@ -2301,6 +2335,94 @@ int swrm_wcd_notify(struct platform_device *pdev, u32 id, void *data)
 }
 EXPORT_SYMBOL(swrm_wcd_notify);
 
+/*
+ * swrm_pm_cmpxchg:
+ *      Check old state and exchange with pm new state
+ *      if old state matches with current state
+ *
+ * @swrm: pointer to wcd core resource
+ * @o: pm old state
+ * @n: pm new state
+ *
+ * Returns old state
+ */
+static enum swrm_pm_state swrm_pm_cmpxchg(
+				struct swr_mstr_ctrl *swrm,
+				enum swrm_pm_state o,
+				enum swrm_pm_state n)
+{
+	enum swrm_pm_state old;
+
+	if (!swrm)
+		return o;
+
+	mutex_lock(&swrm->pm_lock);
+	old = swrm->pm_state;
+	if (old == o)
+		swrm->pm_state = n;
+	mutex_unlock(&swrm->pm_lock);
+
+	return old;
+}
+
+static bool swrm_lock_sleep(struct swr_mstr_ctrl *swrm)
+{
+	enum swrm_pm_state os;
+
+	/*
+	 * swrm_{lock/unlock}_sleep will be called by swr irq handler
+	 * and slave wake up requests..
+	 *
+	 * If system didn't resume, we can simply return false so
+	 * IRQ handler can return without handling IRQ.
+	 */
+	mutex_lock(&swrm->pm_lock);
+	if (swrm->wlock_holders++ == 0) {
+		dev_dbg(swrm->dev, "%s: holding wake lock\n", __func__);
+		pm_qos_update_request(&swrm->pm_qos_req,
+					  msm_cpuidle_get_deep_idle_latency());
+		pm_stay_awake(swrm->dev);
+	}
+	mutex_unlock(&swrm->pm_lock);
+
+	if (!wait_event_timeout(swrm->pm_wq,
+				((os =  swrm_pm_cmpxchg(swrm,
+				  SWRM_PM_SLEEPABLE,
+				  SWRM_PM_AWAKE)) ==
+					SWRM_PM_SLEEPABLE ||
+					(os == SWRM_PM_AWAKE)),
+					msecs_to_jiffies(
+					SWRM_SYSTEM_RESUME_TIMEOUT_MS))) {
+		dev_err(swrm->dev, "%s: system didn't resume within %dms, s %d, w %d\n",
+			__func__, SWRM_SYSTEM_RESUME_TIMEOUT_MS, swrm->pm_state,
+				swrm->wlock_holders);
+		swrm_unlock_sleep(swrm);
+		return false;
+	}
+	wake_up_all(&swrm->pm_wq);
+	return true;
+}
+
+static void swrm_unlock_sleep(struct swr_mstr_ctrl *swrm)
+{
+	mutex_lock(&swrm->pm_lock);
+	if (--swrm->wlock_holders == 0) {
+		dev_dbg(swrm->dev, "%s: releasing wake lock pm_state %d -> %d\n",
+			 __func__, swrm->pm_state, SWRM_PM_SLEEPABLE);
+		/*
+		 * if swrm_lock_sleep failed, pm_state would be still
+		 * swrm_PM_ASLEEP, don't overwrite
+		 */
+		if (likely(swrm->pm_state == SWRM_PM_AWAKE))
+			swrm->pm_state = SWRM_PM_SLEEPABLE;
+		pm_qos_update_request(&swrm->pm_qos_req,
+				  PM_QOS_DEFAULT_VALUE);
+		pm_relax(swrm->dev);
+	}
+	mutex_unlock(&swrm->pm_lock);
+	wake_up_all(&swrm->pm_wq);
+}
+
 #ifdef CONFIG_PM_SLEEP
 static int swrm_suspend(struct device *dev)
 {
@@ -2309,7 +2431,49 @@ static int swrm_suspend(struct device *dev)
 	struct swr_mstr_ctrl *swrm = platform_get_drvdata(pdev);
 
 	dev_dbg(dev, "%s: system suspend, state: %d\n", __func__, swrm->state);
-	if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) {
+
+	mutex_lock(&swrm->pm_lock);
+
+	if (swrm->pm_state == SWRM_PM_SLEEPABLE) {
+		dev_dbg(swrm->dev, "%s: suspending system, state %d, wlock %d\n",
+			 __func__, swrm->pm_state,
+			swrm->wlock_holders);
+		swrm->pm_state = SWRM_PM_ASLEEP;
+	} else if (swrm->pm_state == SWRM_PM_AWAKE) {
+		/*
+		 * unlock to wait for pm_state == SWRM_PM_SLEEPABLE
+		 * then set to SWRM_PM_ASLEEP
+		 */
+		dev_dbg(swrm->dev, "%s: waiting to suspend system, state %d, wlock %d\n",
+			 __func__, swrm->pm_state,
+			 swrm->wlock_holders);
+		mutex_unlock(&swrm->pm_lock);
+		if (!(wait_event_timeout(swrm->pm_wq, swrm_pm_cmpxchg(
+					 swrm, SWRM_PM_SLEEPABLE,
+						 SWRM_PM_ASLEEP) ==
+						   SWRM_PM_SLEEPABLE,
+						   msecs_to_jiffies(
+						   SWRM_SYS_SUSPEND_WAIT)))) {
+			dev_dbg(swrm->dev, "%s: suspend failed state %d, wlock %d\n",
+				 __func__, swrm->pm_state,
+				 swrm->wlock_holders);
+			return -EBUSY;
+		} else {
+			dev_dbg(swrm->dev,
+				"%s: done, state %d, wlock %d\n",
+				__func__, swrm->pm_state,
+				swrm->wlock_holders);
+		}
+		mutex_lock(&swrm->pm_lock);
+	} else if (swrm->pm_state == SWRM_PM_ASLEEP) {
+		dev_dbg(swrm->dev, "%s: system is already suspended, state %d, wlock %d\n",
+			__func__, swrm->pm_state,
+			swrm->wlock_holders);
+	}
+
+	mutex_unlock(&swrm->pm_lock);
+
+	if ((!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev))) {
 		ret = swrm_runtime_suspend(dev);
 		if (!ret) {
 			/*
@@ -2355,6 +2519,21 @@ static int swrm_resume(struct device *dev)
 			pm_request_autosuspend(dev);
 		}
 	}
+	mutex_lock(&swrm->pm_lock);
+	if (swrm->pm_state == SWRM_PM_ASLEEP) {
+		dev_dbg(swrm->dev,
+			"%s: resuming system, state %d, wlock %d\n",
+			__func__, swrm->pm_state,
+			swrm->wlock_holders);
+		swrm->pm_state = SWRM_PM_SLEEPABLE;
+	} else {
+		dev_dbg(swrm->dev, "%s: system is already awake, state %d wlock %d\n",
+			__func__, swrm->pm_state,
+			swrm->wlock_holders);
+	}
+	mutex_unlock(&swrm->pm_lock);
+	wake_up_all(&swrm->pm_wq);
+
 	return ret;
 }
 #endif /* CONFIG_PM_SLEEP */

+ 13 - 0
soc/swr-mstr-ctrl.h

@@ -7,6 +7,8 @@
 #define _SWR_WCD_CTRL_H
 #include <linux/module.h>
 #include <soc/swr-wcd.h>
+#include <linux/pm_qos.h>
+#include <soc/qcom/pm.h>
 
 #define SWR_ROW_48		0
 #define SWR_ROW_50		1
@@ -35,6 +37,12 @@ enum {
 	SWR_MSTR_SSR,
 };
 
+enum swrm_pm_state {
+	SWRM_PM_SLEEPABLE,
+	SWRM_PM_AWAKE,
+	SWRM_PM_ASLEEP,
+};
+
 enum {
 	SWR_IRQ_FREE,
 	SWR_IRQ_REGISTER,
@@ -111,6 +119,7 @@ struct swr_mstr_ctrl {
 	struct mutex devlock;
 	struct mutex mlock;
 	struct mutex reslock;
+	struct mutex pm_lock;
 	u32 swrm_base_reg;
 	char __iomem *swrm_dig_base;
 	u8 rcmd_id;
@@ -149,6 +158,10 @@ struct swr_mstr_ctrl {
 	u32 ipc_wakeup;
 	bool dev_up;
 	bool ipc_wakeup_triggered;
+	struct pm_qos_request pm_qos_req;
+	enum swrm_pm_state pm_state;
+	wait_queue_head_t pm_wq;
+	int wlock_holders;
 };
 
 #endif /* _SWR_WCD_CTRL_H */