* enum rz_dmac_chan_status: RZ DMAC channel status
* @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks
* @RZ_DMAC_CHAN_STATUS_CYCLIC: Channel is cyclic
+ * @RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL: Channel is paused through driver internal logic
*/
enum rz_dmac_chan_status {
RZ_DMAC_CHAN_STATUS_PAUSED,
RZ_DMAC_CHAN_STATUS_CYCLIC,
+ RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL,
};
struct rz_dmac_chan {
u32 chctrl;
int mid_rid;
+ struct {
+ u32 nxla;
+ } pm_state;
+
struct list_head ld_free;
struct {
return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED));
}
+static int rz_dmac_device_pause_internal(struct rz_dmac_chan *channel)
+{
+ lockdep_assert_held(&channel->vc.lock);
+
+ /* Skip channels explicitly paused by consummers or disabled. */
+ if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED) ||
+ !rz_dmac_chan_is_enabled(channel))
+ return 0;
+
+ return rz_dmac_device_pause_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL));
+}
+
static int rz_dmac_device_resume_set(struct rz_dmac_chan *channel,
unsigned long clear_bitmask)
{
- int ret = 0;
u32 val;
+ int ret;
lockdep_assert_held(&channel->vc.lock);
- /* Do not check CHSTAT_SUS but rely on HW capabilities. */
+ /*
+ * We can be:
+ *
+ * 1/ after the channel was paused by a consummer and now it
+ * needs to be resummed
+ * 2/ after the channel was paused internally (as a result of
+ * a system suspend with power loss or not)
+ * 3/ after the channel was paused by a consummer, the system
+ * went through a system suspend (with power loss or not)
+ * and the consummer wants to resume the channel
+ *
+ * To cover all the above cases we set both CLRSUS and SETEN.
+ *
+ * In case 1/ setting SETEN while the channel is still enabled
+ * is harmless for the controller.
+ *
+ * In case 2/ the channel is disabled when calling this function
+ * and setting CLRSUS is harmless for the controller as the
+ * channel is disabled anyway.
+ *
+ * In case 3/ the channel is disabled/enabled if the system
+ * went though a suspend with power loss/or not and setting
+ * CLRSUS/SETEN is harmless for the controller as the channel
+ * is enabled/disabled anyway.
+ */
+
+ rz_dmac_ch_writel(channel, CHCTRL_CLRSUS | CHCTRL_SETEN, CHCTRL, 1);
- rz_dmac_ch_writel(channel, CHCTRL_CLRSUS, CHCTRL, 1);
ret = read_poll_timeout_atomic(rz_dmac_ch_readl, val,
- !(val & CHSTAT_SUS), 1, 1024, false,
- channel, CHSTAT, 1);
+ ((val & (CHSTAT_SUS | CHSTAT_EN)) == CHSTAT_EN),
+ 1, 1024, false, channel, CHSTAT, 1);
channel->status &= ~clear_bitmask;
return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED));
}
+static int rz_dmac_device_resume_internal(struct rz_dmac_chan *channel)
+{
+ lockdep_assert_held(&channel->vc.lock);
+
+ if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL)))
+ return 0;
+
+ return rz_dmac_device_resume_set(channel, BIT(RZ_DMAC_CHAN_STATUS_PAUSED_INTERNAL));
+}
+
/*
* -----------------------------------------------------------------------------
* IRQ handling
pm_runtime_disable(&pdev->dev);
}
+static void rz_dmac_suspend_recover(struct rz_dmac *dmac)
+{
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED(dmac->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return;
+
+ for (unsigned int i = 0; i < dmac->n_channels; i++) {
+ struct rz_dmac_chan *channel = &dmac->channels[i];
+
+ guard(spinlock_irqsave)(&channel->vc.lock);
+
+ if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
+ continue;
+
+ rz_dmac_device_resume_internal(channel);
+ }
+}
+
+static int rz_dmac_suspend(struct device *dev)
+{
+ struct rz_dmac *dmac = dev_get_drvdata(dev);
+ int ret = 0;
+
+ for (unsigned int i = 0; i < dmac->n_channels; i++) {
+ struct rz_dmac_chan *channel = &dmac->channels[i];
+
+ guard(spinlock_irqsave)(&channel->vc.lock);
+
+ if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
+ continue;
+
+ ret = rz_dmac_device_pause_internal(channel);
+ if (ret) {
+ dev_err(dev, "Failed to suspend channel %s\n",
+ dma_chan_name(&channel->vc.chan));
+ break;
+ }
+
+ channel->pm_state.nxla = rz_dmac_ch_readl(channel, NXLA, 1);
+ }
+
+ if (ret)
+ goto suspend_recover;
+
+ ret = reset_control_assert(dmac->rstc);
+ if (ret)
+ goto suspend_recover;
+
+ ret = pm_runtime_put_sync(dev);
+ if (ret < 0)
+ goto reset_deassert;
+
+ return 0;
+
+reset_deassert:
+ reset_control_deassert(dmac->rstc);
+suspend_recover:
+ rz_dmac_suspend_recover(dmac);
+ return ret;
+}
+
+static int rz_dmac_resume(struct device *dev)
+{
+ struct rz_dmac *dmac = dev_get_drvdata(dev);
+ int errors = 0, ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = reset_control_deassert(dmac->rstc);
+ if (ret) {
+ /*
+ * Do not put runtime PM here and keep the same state as in
+ * probe. As subsequent suspend/resume cycles may follow, leave
+ * the runtime PM as is, here, to avoid imbalances.
+ */
+ return ret;
+ }
+
+ rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_0_7_COMMON_BASE + DCTRL);
+ rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_8_15_COMMON_BASE + DCTRL);
+
+ for (unsigned int i = 0; i < dmac->n_channels; i++) {
+ struct rz_dmac_chan *channel = &dmac->channels[i];
+
+ guard(spinlock_irqsave)(&channel->vc.lock);
+
+ rz_dmac_disable_hw(&dmac->channels[i]);
+
+ if (!(channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)))
+ continue;
+
+ rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid);
+
+ rz_dmac_ch_writel(channel, channel->pm_state.nxla, NXLA, 1);
+ rz_dmac_ch_writel(channel, channel->chcfg, CHCFG, 1);
+ rz_dmac_ch_writel(channel, CHCTRL_SWRST, CHCTRL, 1);
+ rz_dmac_ch_writel(channel, channel->chctrl, CHCTRL, 1);
+
+ ret = rz_dmac_device_resume_internal(channel);
+ if (ret) {
+ errors = ret;
+ dev_err(dev, "Failed to resume channel %s, ret=%d\n",
+ dma_chan_name(&channel->vc.chan), ret);
+ }
+ }
+
+ return errors ? : 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(rz_dmac_pm_ops, rz_dmac_suspend, rz_dmac_resume);
+
static const struct rz_dmac_info rz_dmac_v2h_info = {
.icu_register_dma_req = rzv2h_icu_register_dma_req,
.default_dma_req_no = RZV2H_ICU_DMAC_REQ_NO_DEFAULT,
.driver = {
.name = "rz-dmac",
.of_match_table = of_rz_dmac_match,
+ .pm = pm_ptr(&rz_dmac_pm_ops),
},
.probe = rz_dmac_probe,
.remove = rz_dmac_remove,