dmaengine: at_hdmac: improve power management routines
Save/restore dma controller state across a suspend-resume sequence. The prepare() function will wait for the non-cyclic channels to become idle. It also deals with cyclic operations with the start at next period while resuming. Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Signed-off-by: Vinod Koul <vinod.koul@intel.com>
This commit is contained in:
parent
d8cb04b070
commit
c0ba594737
|
@ -1385,27 +1385,113 @@ static void at_dma_shutdown(struct platform_device *pdev)
|
||||||
clk_disable(atdma->clk);
|
clk_disable(atdma->clk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int at_dma_prepare(struct device *dev)
|
||||||
|
{
|
||||||
|
struct platform_device *pdev = to_platform_device(dev);
|
||||||
|
struct at_dma *atdma = platform_get_drvdata(pdev);
|
||||||
|
struct dma_chan *chan, *_chan;
|
||||||
|
|
||||||
|
list_for_each_entry_safe(chan, _chan, &atdma->dma_common.channels,
|
||||||
|
device_node) {
|
||||||
|
struct at_dma_chan *atchan = to_at_dma_chan(chan);
|
||||||
|
/* wait for transaction completion (except in cyclic case) */
|
||||||
|
if (atc_chan_is_enabled(atchan) &&
|
||||||
|
!test_bit(ATC_IS_CYCLIC, &atchan->status))
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void atc_suspend_cyclic(struct at_dma_chan *atchan)
|
||||||
|
{
|
||||||
|
struct dma_chan *chan = &atchan->chan_common;
|
||||||
|
|
||||||
|
/* Channel should be paused by user
|
||||||
|
* do it anyway even if it is not done already */
|
||||||
|
if (!test_bit(ATC_IS_PAUSED, &atchan->status)) {
|
||||||
|
dev_warn(chan2dev(chan),
|
||||||
|
"cyclic channel not paused, should be done by channel user\n");
|
||||||
|
atc_control(chan, DMA_PAUSE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* now preserve additional data for cyclic operations */
|
||||||
|
/* next descriptor address in the cyclic list */
|
||||||
|
atchan->save_dscr = channel_readl(atchan, DSCR);
|
||||||
|
|
||||||
|
vdbg_dump_regs(atchan);
|
||||||
|
}
|
||||||
|
|
||||||
static int at_dma_suspend_noirq(struct device *dev)
|
static int at_dma_suspend_noirq(struct device *dev)
|
||||||
{
|
{
|
||||||
struct platform_device *pdev = to_platform_device(dev);
|
struct platform_device *pdev = to_platform_device(dev);
|
||||||
struct at_dma *atdma = platform_get_drvdata(pdev);
|
struct at_dma *atdma = platform_get_drvdata(pdev);
|
||||||
|
struct dma_chan *chan, *_chan;
|
||||||
|
|
||||||
at_dma_off(platform_get_drvdata(pdev));
|
/* preserve data */
|
||||||
|
list_for_each_entry_safe(chan, _chan, &atdma->dma_common.channels,
|
||||||
|
device_node) {
|
||||||
|
struct at_dma_chan *atchan = to_at_dma_chan(chan);
|
||||||
|
|
||||||
|
if (test_bit(ATC_IS_CYCLIC, &atchan->status))
|
||||||
|
atc_suspend_cyclic(atchan);
|
||||||
|
atchan->save_cfg = channel_readl(atchan, CFG);
|
||||||
|
}
|
||||||
|
atdma->save_imr = dma_readl(atdma, EBCIMR);
|
||||||
|
|
||||||
|
/* disable DMA controller */
|
||||||
|
at_dma_off(atdma);
|
||||||
clk_disable(atdma->clk);
|
clk_disable(atdma->clk);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void atc_resume_cyclic(struct at_dma_chan *atchan)
|
||||||
|
{
|
||||||
|
struct at_dma *atdma = to_at_dma(atchan->chan_common.device);
|
||||||
|
|
||||||
|
/* restore channel status for cyclic descriptors list:
|
||||||
|
* next descriptor in the cyclic list at the time of suspend */
|
||||||
|
channel_writel(atchan, SADDR, 0);
|
||||||
|
channel_writel(atchan, DADDR, 0);
|
||||||
|
channel_writel(atchan, CTRLA, 0);
|
||||||
|
channel_writel(atchan, CTRLB, 0);
|
||||||
|
channel_writel(atchan, DSCR, atchan->save_dscr);
|
||||||
|
dma_writel(atdma, CHER, atchan->mask);
|
||||||
|
|
||||||
|
/* channel pause status should be removed by channel user
|
||||||
|
* We cannot take the initiative to do it here */
|
||||||
|
|
||||||
|
vdbg_dump_regs(atchan);
|
||||||
|
}
|
||||||
|
|
||||||
static int at_dma_resume_noirq(struct device *dev)
|
static int at_dma_resume_noirq(struct device *dev)
|
||||||
{
|
{
|
||||||
struct platform_device *pdev = to_platform_device(dev);
|
struct platform_device *pdev = to_platform_device(dev);
|
||||||
struct at_dma *atdma = platform_get_drvdata(pdev);
|
struct at_dma *atdma = platform_get_drvdata(pdev);
|
||||||
|
struct dma_chan *chan, *_chan;
|
||||||
|
|
||||||
|
/* bring back DMA controller */
|
||||||
clk_enable(atdma->clk);
|
clk_enable(atdma->clk);
|
||||||
dma_writel(atdma, EN, AT_DMA_ENABLE);
|
dma_writel(atdma, EN, AT_DMA_ENABLE);
|
||||||
|
|
||||||
|
/* clear any pending interrupt */
|
||||||
|
while (dma_readl(atdma, EBCISR))
|
||||||
|
cpu_relax();
|
||||||
|
|
||||||
|
/* restore saved data */
|
||||||
|
dma_writel(atdma, EBCIER, atdma->save_imr);
|
||||||
|
list_for_each_entry_safe(chan, _chan, &atdma->dma_common.channels,
|
||||||
|
device_node) {
|
||||||
|
struct at_dma_chan *atchan = to_at_dma_chan(chan);
|
||||||
|
|
||||||
|
channel_writel(atchan, CFG, atchan->save_cfg);
|
||||||
|
if (test_bit(ATC_IS_CYCLIC, &atchan->status))
|
||||||
|
atc_resume_cyclic(atchan);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct dev_pm_ops at_dma_dev_pm_ops = {
|
static const struct dev_pm_ops at_dma_dev_pm_ops = {
|
||||||
|
.prepare = at_dma_prepare,
|
||||||
.suspend_noirq = at_dma_suspend_noirq,
|
.suspend_noirq = at_dma_suspend_noirq,
|
||||||
.resume_noirq = at_dma_resume_noirq,
|
.resume_noirq = at_dma_resume_noirq,
|
||||||
};
|
};
|
||||||
|
|
|
@ -204,6 +204,9 @@ enum atc_status {
|
||||||
* @status: transmit status information from irq/prep* functions
|
* @status: transmit status information from irq/prep* functions
|
||||||
* to tasklet (use atomic operations)
|
* to tasklet (use atomic operations)
|
||||||
* @tasklet: bottom half to finish transaction work
|
* @tasklet: bottom half to finish transaction work
|
||||||
|
* @save_cfg: configuration register that is saved on suspend/resume cycle
|
||||||
|
* @save_dscr: for cyclic operations, preserve next descriptor address in
|
||||||
|
* the cyclic list on suspend/resume cycle
|
||||||
* @lock: serializes enqueue/dequeue operations to descriptors lists
|
* @lock: serializes enqueue/dequeue operations to descriptors lists
|
||||||
* @completed_cookie: identifier for the most recently completed operation
|
* @completed_cookie: identifier for the most recently completed operation
|
||||||
* @active_list: list of descriptors dmaengine is being running on
|
* @active_list: list of descriptors dmaengine is being running on
|
||||||
|
@ -218,6 +221,8 @@ struct at_dma_chan {
|
||||||
u8 mask;
|
u8 mask;
|
||||||
unsigned long status;
|
unsigned long status;
|
||||||
struct tasklet_struct tasklet;
|
struct tasklet_struct tasklet;
|
||||||
|
u32 save_cfg;
|
||||||
|
u32 save_dscr;
|
||||||
|
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
|
|
||||||
|
@ -248,6 +253,7 @@ static inline struct at_dma_chan *to_at_dma_chan(struct dma_chan *dchan)
|
||||||
* @chan_common: common dmaengine dma_device object members
|
* @chan_common: common dmaengine dma_device object members
|
||||||
* @ch_regs: memory mapped register base
|
* @ch_regs: memory mapped register base
|
||||||
* @clk: dma controller clock
|
* @clk: dma controller clock
|
||||||
|
* @save_imr: interrupt mask register that is saved on suspend/resume cycle
|
||||||
* @all_chan_mask: all channels availlable in a mask
|
* @all_chan_mask: all channels availlable in a mask
|
||||||
* @dma_desc_pool: base of DMA descriptor region (DMA address)
|
* @dma_desc_pool: base of DMA descriptor region (DMA address)
|
||||||
* @chan: channels table to store at_dma_chan structures
|
* @chan: channels table to store at_dma_chan structures
|
||||||
|
@ -256,6 +262,7 @@ struct at_dma {
|
||||||
struct dma_device dma_common;
|
struct dma_device dma_common;
|
||||||
void __iomem *regs;
|
void __iomem *regs;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
|
u32 save_imr;
|
||||||
|
|
||||||
u8 all_chan_mask;
|
u8 all_chan_mask;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue