dmaengine: at_xdmac: add runtime pm support

Add runtime PM support which involves disabling/enabling controller's
clocks on runtime PM suspend/resume ops. The runtime suspend/resume is
done based on the work submitted to the controller: runtime resume is
happening on at_xdmac_start_xfer() and runtime suspend on
at_xdmac_tasklet().

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
Link: https://lore.kernel.org/r/20221117131547.293044-2-claudiu.beznea@microchip.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
Claudiu Beznea 2022-11-17 15:15:45 +02:00 коммит произвёл Vinod Koul
Родитель 610b573e51
Коммит 650b0e990c
1 изменённых файлов: 203 добавлений и 16 удалений

Просмотреть файл

@ -21,6 +21,7 @@
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include "dmaengine.h"
@ -240,6 +241,7 @@ struct at_xdmac_chan {
struct at_xdmac {
struct dma_device dma;
void __iomem *regs;
struct device *dev;
int irq;
struct clk *clk;
u32 save_gim;
@ -361,13 +363,65 @@ MODULE_PARM_DESC(init_nr_desc_per_channel,
"initial descriptors per channel (default: 64)");
static void at_xdmac_runtime_suspend_descriptors(struct at_xdmac_chan *atchan)
{
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
struct at_xdmac_desc *desc, *_desc;
list_for_each_entry_safe(desc, _desc, &atchan->xfers_list, xfer_node) {
if (!desc->active_xfer)
continue;
pm_runtime_mark_last_busy(atxdmac->dev);
pm_runtime_put_autosuspend(atxdmac->dev);
}
}
static int at_xdmac_runtime_resume_descriptors(struct at_xdmac_chan *atchan)
{
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
struct at_xdmac_desc *desc, *_desc;
int ret;
list_for_each_entry_safe(desc, _desc, &atchan->xfers_list, xfer_node) {
if (!desc->active_xfer)
continue;
ret = pm_runtime_resume_and_get(atxdmac->dev);
if (ret < 0)
return ret;
}
return 0;
}
static bool at_xdmac_chan_is_enabled(struct at_xdmac_chan *atchan)
{
return at_xdmac_chan_read(atchan, AT_XDMAC_GS) & atchan->mask;
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
int ret;
ret = pm_runtime_resume_and_get(atxdmac->dev);
if (ret < 0)
return false;
ret = !!(at_xdmac_chan_read(atchan, AT_XDMAC_GS) & atchan->mask);
pm_runtime_mark_last_busy(atxdmac->dev);
pm_runtime_put_autosuspend(atxdmac->dev);
return ret;
}
static void at_xdmac_off(struct at_xdmac *atxdmac)
{
struct dma_chan *chan, *_chan;
struct at_xdmac_chan *atchan;
int ret;
ret = pm_runtime_resume_and_get(atxdmac->dev);
if (ret < 0)
return;
at_xdmac_write(atxdmac, AT_XDMAC_GD, -1L);
/* Wait that all chans are disabled. */
@ -375,6 +429,18 @@ static void at_xdmac_off(struct at_xdmac *atxdmac)
cpu_relax();
at_xdmac_write(atxdmac, AT_XDMAC_GID, -1L);
/* Decrement runtime PM ref counter for each active descriptor. */
if (!list_empty(&atxdmac->dma.channels)) {
list_for_each_entry_safe(chan, _chan, &atxdmac->dma.channels,
device_node) {
atchan = to_at_xdmac_chan(chan);
at_xdmac_runtime_suspend_descriptors(atchan);
}
}
pm_runtime_mark_last_busy(atxdmac->dev);
pm_runtime_put_autosuspend(atxdmac->dev);
}
/* Call with lock hold. */
@ -383,6 +449,11 @@ static void at_xdmac_start_xfer(struct at_xdmac_chan *atchan,
{
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
u32 reg;
int ret;
ret = pm_runtime_resume_and_get(atxdmac->dev);
if (ret < 0)
return;
dev_vdbg(chan2dev(&atchan->chan), "%s: desc 0x%p\n", __func__, first);
@ -1463,7 +1534,7 @@ at_xdmac_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
struct at_xdmac_desc *desc, *_desc, *iter;
struct list_head *descs_list;
enum dma_status ret;
int residue, retry;
int residue, retry, pm_status;
u32 cur_nda, check_nda, cur_ubc, mask, value;
u8 dwidth = 0;
unsigned long flags;
@ -1473,6 +1544,10 @@ at_xdmac_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
if (ret == DMA_COMPLETE || !txstate)
return ret;
pm_status = pm_runtime_resume_and_get(atxdmac->dev);
if (pm_status < 0)
return DMA_ERROR;
spin_lock_irqsave(&atchan->lock, flags);
desc = list_first_entry(&atchan->xfers_list, struct at_xdmac_desc, xfer_node);
@ -1590,6 +1665,8 @@ at_xdmac_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
spin_unlock:
spin_unlock_irqrestore(&atchan->lock, flags);
pm_runtime_mark_last_busy(atxdmac->dev);
pm_runtime_put_autosuspend(atxdmac->dev);
return ret;
}
@ -1636,6 +1713,11 @@ static void at_xdmac_handle_error(struct at_xdmac_chan *atchan)
{
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
struct at_xdmac_desc *bad_desc;
int ret;
ret = pm_runtime_resume_and_get(atxdmac->dev);
if (ret < 0)
return;
/*
* The descriptor currently at the head of the active list is
@ -1665,12 +1747,16 @@ static void at_xdmac_handle_error(struct at_xdmac_chan *atchan)
__func__, &bad_desc->lld.mbr_sa, &bad_desc->lld.mbr_da,
bad_desc->lld.mbr_ubc);
pm_runtime_mark_last_busy(atxdmac->dev);
pm_runtime_put_autosuspend(atxdmac->dev);
/* Then continue with usual descriptor management */
}
static void at_xdmac_tasklet(struct tasklet_struct *t)
{
struct at_xdmac_chan *atchan = from_tasklet(atchan, t, tasklet);
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
struct at_xdmac_desc *desc;
struct dma_async_tx_descriptor *txd;
u32 error_mask;
@ -1720,6 +1806,13 @@ static void at_xdmac_tasklet(struct tasklet_struct *t)
list_splice_tail_init(&desc->descs_list, &atchan->free_descs_list);
at_xdmac_advance_work(atchan);
spin_unlock_irq(&atchan->lock);
/*
* Decrement runtime PM ref counter incremented in
* at_xdmac_start_xfer().
*/
pm_runtime_mark_last_busy(atxdmac->dev);
pm_runtime_put_autosuspend(atxdmac->dev);
}
static irqreturn_t at_xdmac_interrupt(int irq, void *dev_id)
@ -1811,19 +1904,31 @@ static int at_xdmac_device_pause(struct dma_chan *chan)
struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
unsigned long flags;
int ret;
dev_dbg(chan2dev(chan), "%s\n", __func__);
if (test_and_set_bit(AT_XDMAC_CHAN_IS_PAUSED, &atchan->status))
return 0;
ret = pm_runtime_resume_and_get(atxdmac->dev);
if (ret < 0)
return ret;
spin_lock_irqsave(&atchan->lock, flags);
at_xdmac_write(atxdmac, atxdmac->layout->grws, atchan->mask);
while (at_xdmac_chan_read(atchan, AT_XDMAC_CC)
& (AT_XDMAC_CC_WRIP | AT_XDMAC_CC_RDIP))
cpu_relax();
/* Decrement runtime PM ref counter for each active descriptor. */
at_xdmac_runtime_suspend_descriptors(atchan);
spin_unlock_irqrestore(&atchan->lock, flags);
pm_runtime_mark_last_busy(atxdmac->dev);
pm_runtime_put_autosuspend(atxdmac->dev);
return 0;
}
@ -1832,20 +1937,32 @@ static int at_xdmac_device_resume(struct dma_chan *chan)
struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
unsigned long flags;
int ret;
dev_dbg(chan2dev(chan), "%s\n", __func__);
ret = pm_runtime_resume_and_get(atxdmac->dev);
if (ret < 0)
return ret;
spin_lock_irqsave(&atchan->lock, flags);
if (!at_xdmac_chan_is_paused(atchan)) {
spin_unlock_irqrestore(&atchan->lock, flags);
return 0;
}
if (!at_xdmac_chan_is_paused(atchan))
goto unlock;
/* Increment runtime PM ref counter for each active descriptor. */
ret = at_xdmac_runtime_resume_descriptors(atchan);
if (ret < 0)
goto unlock;
at_xdmac_write(atxdmac, atxdmac->layout->grwr, atchan->mask);
clear_bit(AT_XDMAC_CHAN_IS_PAUSED, &atchan->status);
spin_unlock_irqrestore(&atchan->lock, flags);
return 0;
unlock:
spin_unlock_irqrestore(&atchan->lock, flags);
pm_runtime_mark_last_busy(atxdmac->dev);
pm_runtime_put_autosuspend(atxdmac->dev);
return ret;
}
static int at_xdmac_device_terminate_all(struct dma_chan *chan)
@ -1854,9 +1971,14 @@ static int at_xdmac_device_terminate_all(struct dma_chan *chan)
struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
unsigned long flags;
int ret;
dev_dbg(chan2dev(chan), "%s\n", __func__);
ret = pm_runtime_resume_and_get(atxdmac->dev);
if (ret < 0)
return ret;
spin_lock_irqsave(&atchan->lock, flags);
at_xdmac_write(atxdmac, AT_XDMAC_GD, atchan->mask);
while (at_xdmac_read(atxdmac, AT_XDMAC_GS) & atchan->mask)
@ -1867,12 +1989,24 @@ static int at_xdmac_device_terminate_all(struct dma_chan *chan)
list_del(&desc->xfer_node);
list_splice_tail_init(&desc->descs_list,
&atchan->free_descs_list);
/*
* We incremented the runtime PM reference count on
* at_xdmac_start_xfer() for this descriptor. Now it's time
* to release it.
*/
if (desc->active_xfer) {
pm_runtime_put_autosuspend(atxdmac->dev);
pm_runtime_mark_last_busy(atxdmac->dev);
}
}
clear_bit(AT_XDMAC_CHAN_IS_PAUSED, &atchan->status);
clear_bit(AT_XDMAC_CHAN_IS_CYCLIC, &atchan->status);
spin_unlock_irqrestore(&atchan->lock, flags);
pm_runtime_mark_last_busy(atxdmac->dev);
pm_runtime_put_autosuspend(atxdmac->dev);
return 0;
}
@ -1974,6 +2108,11 @@ static int __maybe_unused atmel_xdmac_suspend(struct device *dev)
{
struct at_xdmac *atxdmac = dev_get_drvdata(dev);
struct dma_chan *chan, *_chan;
int ret;
ret = pm_runtime_resume_and_get(atxdmac->dev);
if (ret < 0)
return ret;
list_for_each_entry_safe(chan, _chan, &atxdmac->dma.channels, device_node) {
struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
@ -1986,12 +2125,13 @@ static int __maybe_unused atmel_xdmac_suspend(struct device *dev)
atchan->save_cnda = at_xdmac_chan_read(atchan, AT_XDMAC_CNDA);
atchan->save_cndc = at_xdmac_chan_read(atchan, AT_XDMAC_CNDC);
}
at_xdmac_runtime_suspend_descriptors(atchan);
}
atxdmac->save_gim = at_xdmac_read(atxdmac, AT_XDMAC_GIM);
at_xdmac_off(atxdmac);
clk_disable_unprepare(atxdmac->clk);
return 0;
return pm_runtime_force_suspend(atxdmac->dev);
}
static int __maybe_unused atmel_xdmac_resume(struct device *dev)
@ -2003,8 +2143,8 @@ static int __maybe_unused atmel_xdmac_resume(struct device *dev)
int i;
int ret;
ret = clk_prepare_enable(atxdmac->clk);
if (ret)
ret = pm_runtime_force_resume(atxdmac->dev);
if (ret < 0)
return ret;
at_xdmac_axi_config(pdev);
@ -2019,6 +2159,11 @@ static int __maybe_unused atmel_xdmac_resume(struct device *dev)
at_xdmac_write(atxdmac, AT_XDMAC_GIE, atxdmac->save_gim);
list_for_each_entry_safe(chan, _chan, &atxdmac->dma.channels, device_node) {
atchan = to_at_xdmac_chan(chan);
ret = at_xdmac_runtime_resume_descriptors(atchan);
if (ret < 0)
return ret;
at_xdmac_chan_write(atchan, AT_XDMAC_CC, atchan->save_cc);
if (at_xdmac_chan_is_cyclic(atchan)) {
if (at_xdmac_chan_is_paused(atchan))
@ -2030,9 +2175,29 @@ static int __maybe_unused atmel_xdmac_resume(struct device *dev)
at_xdmac_write(atxdmac, AT_XDMAC_GE, atchan->mask);
}
}
pm_runtime_mark_last_busy(atxdmac->dev);
pm_runtime_put_autosuspend(atxdmac->dev);
return 0;
}
static int __maybe_unused atmel_xdmac_runtime_suspend(struct device *dev)
{
struct at_xdmac *atxdmac = dev_get_drvdata(dev);
clk_disable(atxdmac->clk);
return 0;
}
static int __maybe_unused atmel_xdmac_runtime_resume(struct device *dev)
{
struct at_xdmac *atxdmac = dev_get_drvdata(dev);
return clk_enable(atxdmac->clk);
}
static int at_xdmac_probe(struct platform_device *pdev)
{
struct at_xdmac *atxdmac;
@ -2071,6 +2236,7 @@ static int at_xdmac_probe(struct platform_device *pdev)
atxdmac->regs = base;
atxdmac->irq = irq;
atxdmac->dev = &pdev->dev;
atxdmac->layout = of_device_get_match_data(&pdev->dev);
if (!atxdmac->layout)
@ -2135,11 +2301,20 @@ static int at_xdmac_probe(struct platform_device *pdev)
atxdmac->dma.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
atxdmac->dma.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
/* Disable all chans and interrupts. */
at_xdmac_off(atxdmac);
platform_set_drvdata(pdev, atxdmac);
pm_runtime_set_autosuspend_delay(&pdev->dev, 500);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_noresume(&pdev->dev);
/* Init channels. */
INIT_LIST_HEAD(&atxdmac->dma.channels);
/* Disable all chans and interrupts. */
at_xdmac_off(atxdmac);
for (i = 0; i < nr_channels; i++) {
struct at_xdmac_chan *atchan = &atxdmac->chan[i];
@ -2159,12 +2334,11 @@ static int at_xdmac_probe(struct platform_device *pdev)
while (at_xdmac_chan_read(atchan, AT_XDMAC_CIS))
cpu_relax();
}
platform_set_drvdata(pdev, atxdmac);
ret = dma_async_device_register(&atxdmac->dma);
if (ret) {
dev_err(&pdev->dev, "fail to register DMA engine device\n");
goto err_clk_disable;
goto err_pm_disable;
}
ret = of_dma_controller_register(pdev->dev.of_node,
@ -2179,10 +2353,18 @@ static int at_xdmac_probe(struct platform_device *pdev)
at_xdmac_axi_config(pdev);
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_put_autosuspend(&pdev->dev);
return 0;
err_dma_unregister:
dma_async_device_unregister(&atxdmac->dma);
err_pm_disable:
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
pm_runtime_dont_use_autosuspend(&pdev->dev);
err_clk_disable:
clk_disable_unprepare(atxdmac->clk);
err_free_irq:
@ -2198,6 +2380,9 @@ static int at_xdmac_remove(struct platform_device *pdev)
at_xdmac_off(atxdmac);
of_dma_controller_free(pdev->dev.of_node);
dma_async_device_unregister(&atxdmac->dma);
pm_runtime_disable(atxdmac->dev);
pm_runtime_set_suspended(&pdev->dev);
pm_runtime_dont_use_autosuspend(&pdev->dev);
clk_disable_unprepare(atxdmac->clk);
free_irq(atxdmac->irq, atxdmac);
@ -2215,6 +2400,8 @@ static int at_xdmac_remove(struct platform_device *pdev)
static const struct dev_pm_ops __maybe_unused atmel_xdmac_dev_pm_ops = {
.prepare = atmel_xdmac_prepare,
SET_LATE_SYSTEM_SLEEP_PM_OPS(atmel_xdmac_suspend, atmel_xdmac_resume)
SET_RUNTIME_PM_OPS(atmel_xdmac_runtime_suspend,
atmel_xdmac_runtime_resume, NULL)
};
static const struct of_device_id atmel_xdmac_dt_ids[] = {