Commit 44fe8440 authored by Claudiu Beznea's avatar Claudiu Beznea Committed by Vinod Koul

dmaengine: at_xdmac: do not resume channels paused by consumers

In case there are DMA channels not paused by consumers in suspend
process (valid on AT91 SoCs for serial driver when no_console_suspend) the
driver pauses them (using at_xdmac_device_pause() which is also the same
function called by dmaengine_pause()) and then in the resume process the
driver resumes them calling at_xdmac_device_resume() which is the same
function called by dmaengine_resume()). This is good for DMA channels
not paused by consumers but for drivers that calls
dmaengine_pause()/dmaegine_resume() on suspend/resume path this may lead to
DMA channel being enabled before the IP is enabled. For IPs that needs
strict ordering with regards to DMA channel enablement this will lead to
wrong behavior. To fix this add a new set of functions
at_xdmac_device_pause_internal()/at_xdmac_device_resume_internal() to be
called only on suspend/resume.

Fixes: e1f7c9ee ("dmaengine: at_xdmac: creation of the atmel eXtended DMA Controller driver")
Signed-off-by: default avatarClaudiu Beznea <claudiu.beznea@microchip.com>
Link: https://lore.kernel.org/r/20230214151827.1050280-4-claudiu.beznea@microchip.comSigned-off-by: default avatarVinod Koul <vkoul@kernel.org>
parent e53957e1
...@@ -187,6 +187,7 @@ ...@@ -187,6 +187,7 @@
enum atc_status { enum atc_status {
AT_XDMAC_CHAN_IS_CYCLIC = 0, AT_XDMAC_CHAN_IS_CYCLIC = 0,
AT_XDMAC_CHAN_IS_PAUSED, AT_XDMAC_CHAN_IS_PAUSED,
AT_XDMAC_CHAN_IS_PAUSED_INTERNAL,
}; };
struct at_xdmac_layout { struct at_xdmac_layout {
...@@ -347,6 +348,11 @@ static inline int at_xdmac_chan_is_paused(struct at_xdmac_chan *atchan) ...@@ -347,6 +348,11 @@ static inline int at_xdmac_chan_is_paused(struct at_xdmac_chan *atchan)
return test_bit(AT_XDMAC_CHAN_IS_PAUSED, &atchan->status); return test_bit(AT_XDMAC_CHAN_IS_PAUSED, &atchan->status);
} }
static inline int at_xdmac_chan_is_paused_internal(struct at_xdmac_chan *atchan)
{
return test_bit(AT_XDMAC_CHAN_IS_PAUSED_INTERNAL, &atchan->status);
}
static inline bool at_xdmac_chan_is_peripheral_xfer(u32 cfg) static inline bool at_xdmac_chan_is_peripheral_xfer(u32 cfg)
{ {
return cfg & AT_XDMAC_CC_TYPE_PER_TRAN; return cfg & AT_XDMAC_CC_TYPE_PER_TRAN;
...@@ -1898,6 +1904,26 @@ static int at_xdmac_device_config(struct dma_chan *chan, ...@@ -1898,6 +1904,26 @@ static int at_xdmac_device_config(struct dma_chan *chan,
return ret; return ret;
} }
static void at_xdmac_device_pause_set(struct at_xdmac *atxdmac,
struct at_xdmac_chan *atchan)
{
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();
}
static void at_xdmac_device_pause_internal(struct at_xdmac_chan *atchan)
{
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
unsigned long flags;
spin_lock_irqsave(&atchan->lock, flags);
set_bit(AT_XDMAC_CHAN_IS_PAUSED_INTERNAL, &atchan->status);
at_xdmac_device_pause_set(atxdmac, atchan);
spin_unlock_irqrestore(&atchan->lock, flags);
}
static int at_xdmac_device_pause(struct dma_chan *chan) static int at_xdmac_device_pause(struct dma_chan *chan)
{ {
struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan); struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
...@@ -1915,11 +1941,8 @@ static int at_xdmac_device_pause(struct dma_chan *chan) ...@@ -1915,11 +1941,8 @@ static int at_xdmac_device_pause(struct dma_chan *chan)
return ret; return ret;
spin_lock_irqsave(&atchan->lock, flags); 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();
at_xdmac_device_pause_set(atxdmac, atchan);
/* Decrement runtime PM ref counter for each active descriptor. */ /* Decrement runtime PM ref counter for each active descriptor. */
at_xdmac_runtime_suspend_descriptors(atchan); at_xdmac_runtime_suspend_descriptors(atchan);
...@@ -1931,6 +1954,17 @@ static int at_xdmac_device_pause(struct dma_chan *chan) ...@@ -1931,6 +1954,17 @@ static int at_xdmac_device_pause(struct dma_chan *chan)
return 0; return 0;
} }
static void at_xdmac_device_resume_internal(struct at_xdmac_chan *atchan)
{
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
unsigned long flags;
spin_lock_irqsave(&atchan->lock, flags);
at_xdmac_write(atxdmac, atxdmac->layout->grwr, atchan->mask);
clear_bit(AT_XDMAC_CHAN_IS_PAUSED_INTERNAL, &atchan->status);
spin_unlock_irqrestore(&atchan->lock, flags);
}
static int at_xdmac_device_resume(struct dma_chan *chan) static int at_xdmac_device_resume(struct dma_chan *chan)
{ {
struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan); struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
...@@ -2119,7 +2153,7 @@ static int __maybe_unused atmel_xdmac_suspend(struct device *dev) ...@@ -2119,7 +2153,7 @@ static int __maybe_unused atmel_xdmac_suspend(struct device *dev)
atchan->save_cc = at_xdmac_chan_read(atchan, AT_XDMAC_CC); atchan->save_cc = at_xdmac_chan_read(atchan, AT_XDMAC_CC);
if (at_xdmac_chan_is_cyclic(atchan)) { if (at_xdmac_chan_is_cyclic(atchan)) {
if (!at_xdmac_chan_is_paused(atchan)) { if (!at_xdmac_chan_is_paused(atchan)) {
at_xdmac_device_pause(chan); at_xdmac_device_pause_internal(atchan);
at_xdmac_runtime_suspend_descriptors(atchan); at_xdmac_runtime_suspend_descriptors(atchan);
} }
atchan->save_cim = at_xdmac_chan_read(atchan, AT_XDMAC_CIM); atchan->save_cim = at_xdmac_chan_read(atchan, AT_XDMAC_CIM);
...@@ -2167,11 +2201,15 @@ static int __maybe_unused atmel_xdmac_resume(struct device *dev) ...@@ -2167,11 +2201,15 @@ static int __maybe_unused atmel_xdmac_resume(struct device *dev)
at_xdmac_chan_write(atchan, AT_XDMAC_CC, atchan->save_cc); at_xdmac_chan_write(atchan, AT_XDMAC_CC, atchan->save_cc);
if (at_xdmac_chan_is_cyclic(atchan)) { if (at_xdmac_chan_is_cyclic(atchan)) {
if (at_xdmac_chan_is_paused(atchan)) { /*
* Resume only channels not explicitly paused by
* consumers.
*/
if (at_xdmac_chan_is_paused_internal(atchan)) {
ret = at_xdmac_runtime_resume_descriptors(atchan); ret = at_xdmac_runtime_resume_descriptors(atchan);
if (ret < 0) if (ret < 0)
return ret; return ret;
at_xdmac_device_resume(chan); at_xdmac_device_resume_internal(atchan);
} }
at_xdmac_chan_write(atchan, AT_XDMAC_CNDA, atchan->save_cnda); at_xdmac_chan_write(atchan, AT_XDMAC_CNDA, atchan->save_cnda);
at_xdmac_chan_write(atchan, AT_XDMAC_CNDC, atchan->save_cndc); at_xdmac_chan_write(atchan, AT_XDMAC_CNDC, atchan->save_cndc);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment