diff --git a/soc/swr-mstr-ctrl.c b/soc/swr-mstr-ctrl.c index 8f1ad0822550..03d6c27b811d 100644 --- a/soc/swr-mstr-ctrl.c +++ b/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) { @@ -974,6 +977,7 @@ static int swrm_slvdev_datapath_control(struct swr_master *master, bool enable) pr_err("%s: swrm is null\n", __func__); return -EFAULT; } + mutex_lock(&swrm->mlock); bank = get_inactive_bank_num(swrm); @@ -993,7 +997,7 @@ static int swrm_slvdev_datapath_control(struct swr_master *master, bool enable) return -EINVAL; } swr_master_write(swrm, SWR_MSTR_RX_SWRM_CPU_INTERRUPT_EN, - SWRM_INTERRUPT_STATUS_MASK); + SWRM_INTERRUPT_STATUS_MASK); /* apply the new port config*/ swrm_apply_port_config(master); } else { @@ -1254,7 +1258,7 @@ static int swrm_check_slave_change_status(struct swr_mstr_ctrl *swrm, static irqreturn_t swr_mstr_interrupt(int irq, void *dev) { struct swr_mstr_ctrl *swrm = dev; - u32 value, intr_sts, intr_mask; + u32 value, intr_sts, intr_sts_masked; u32 temp = 0; u32 status, chg_sts, i; u8 devnum = 0; @@ -1262,17 +1266,20 @@ 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); mutex_unlock(&swrm->reslock); intr_sts = swr_master_read(swrm, SWRM_INTERRUPT_STATUS); - intr_mask = swr_master_read(swrm, SWR_MSTR_RX_SWRM_CPU_INTERRUPT_EN); - intr_sts &= intr_mask; + intr_sts_masked = intr_sts & swrm->intr_mask; handle_irq: for (i = 0; i < SWRM_INTERRUPT_MAX; i++) { - value = intr_sts & (1 << i); + value = intr_sts_masked & (1 << i); if (!value) continue; @@ -1364,16 +1371,16 @@ handle_irq: break; case SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION: dev_err_ratelimited(swrm->dev, "SWR Port collision detected\n"); - intr_mask &= ~SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION; + swrm->intr_mask &= ~SWRM_INTERRUPT_STATUS_DOUT_PORT_COLLISION; swr_master_write(swrm, - SWR_MSTR_RX_SWRM_CPU_INTERRUPT_EN, intr_mask); + SWR_MSTR_RX_SWRM_CPU_INTERRUPT_EN, swrm->intr_mask); break; case SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH: dev_dbg(swrm->dev, "SWR read enable valid mismatch\n"); - intr_mask &= + swrm->intr_mask &= ~SWRM_INTERRUPT_STATUS_READ_EN_RD_VALID_MISMATCH; swr_master_write(swrm, - SWR_MSTR_RX_SWRM_CPU_INTERRUPT_EN, intr_mask); + SWR_MSTR_RX_SWRM_CPU_INTERRUPT_EN, swrm->intr_mask); break; case SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED: complete(&swrm->broadcast); @@ -1401,9 +1408,9 @@ handle_irq: swr_master_write(swrm, SWRM_INTERRUPT_CLEAR, 0x0); intr_sts = swr_master_read(swrm, SWRM_INTERRUPT_STATUS); - intr_sts &= intr_mask; + intr_sts_masked = intr_sts & swrm->intr_mask; - if (intr_sts) { + if (intr_sts_masked) { dev_dbg(swrm->dev, "%s: new interrupt received\n", __func__); goto handle_irq; } @@ -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) @@ -1610,12 +1630,13 @@ static int swrm_master_init(struct swr_mstr_ctrl *swrm) reg[len] = SWRM_INTERRUPT_CLEAR; value[len++] = 0xFFFFFFFF; + swrm->intr_mask = SWRM_INTERRUPT_STATUS_MASK; /* Mask soundwire interrupts */ reg[len] = SWRM_INTERRUPT_MASK_ADDR; - value[len++] = 0x1FFFD; + value[len++] = swrm->intr_mask; reg[len] = SWR_MSTR_RX_SWRM_CPU_INTERRUPT_EN; - value[len++] = SWRM_INTERRUPT_STATUS_MASK; + value[len++] = swrm->intr_mask; swr_master_bulk_write(swrm, reg, value, len); @@ -1639,6 +1660,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 +1767,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 +1864,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 +1970,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 +1989,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 +1999,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 +2336,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 +2432,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 +2520,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 */ diff --git a/soc/swr-mstr-ctrl.h b/soc/swr-mstr-ctrl.h index 13b85f42200e..8582c51ea96c 100644 --- a/soc/swr-mstr-ctrl.h +++ b/soc/swr-mstr-ctrl.h @@ -7,6 +7,8 @@ #define _SWR_WCD_CTRL_H #include #include +#include +#include #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,11 @@ 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; + u32 intr_mask; }; #endif /* _SWR_WCD_CTRL_H */