diff options
author | Damien.Horsley <Damien.Horsley@imgtec.com> | 2015-12-10 10:07:23 -0500 |
---|---|---|
committer | Vinod Koul <vinod.koul@intel.com> | 2015-12-18 00:42:29 -0500 |
commit | 0c328de77148ddccaa7a2c31f5751e4d443c213b (patch) | |
tree | 9f44db2ab2fd46006f971b7c88a13cdbd24e7227 /drivers/dma/img-mdc-dma.c | |
parent | 4fa2d09c1ae879c2ee2760ab419a4f97026dd97b (diff) |
dmaengine: mdc: Correct terminate_all handling
Use of the CANCEL bit in mdc_terminate_all creates an
additional 'command done' to appear in the registers (in
addition to an interrupt).
In addition, there is a potential race between
mdc_terminate_all and the irq handler if a transfer
completes at the same time as the terminate all (presently
this results in an inappropriate warning).
To handle these issues, any outstanding 'command done'
events are cleared during mdc_terminate_all and the irq
handler takes no action when there are no new 'command done'
events.
Signed-off-by: Damien.Horsley <Damien.Horsley@imgtec.com>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
Diffstat (limited to 'drivers/dma/img-mdc-dma.c')
-rw-r--r-- | drivers/dma/img-mdc-dma.c | 77 |
1 files changed, 51 insertions, 26 deletions
diff --git a/drivers/dma/img-mdc-dma.c b/drivers/dma/img-mdc-dma.c index 42ae58d1e303..a4c53be482cf 100644 --- a/drivers/dma/img-mdc-dma.c +++ b/drivers/dma/img-mdc-dma.c | |||
@@ -651,6 +651,48 @@ static enum dma_status mdc_tx_status(struct dma_chan *chan, | |||
651 | return ret; | 651 | return ret; |
652 | } | 652 | } |
653 | 653 | ||
654 | static unsigned int mdc_get_new_events(struct mdc_chan *mchan) | ||
655 | { | ||
656 | u32 val, processed, done1, done2; | ||
657 | unsigned int ret; | ||
658 | |||
659 | val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); | ||
660 | processed = (val >> MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) & | ||
661 | MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK; | ||
662 | /* | ||
663 | * CMDS_DONE may have incremented between reading CMDS_PROCESSED | ||
664 | * and clearing INT_ACTIVE. Re-read CMDS_PROCESSED to ensure we | ||
665 | * didn't miss a command completion. | ||
666 | */ | ||
667 | do { | ||
668 | val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); | ||
669 | |||
670 | done1 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) & | ||
671 | MDC_CMDS_PROCESSED_CMDS_DONE_MASK; | ||
672 | |||
673 | val &= ~((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK << | ||
674 | MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) | | ||
675 | MDC_CMDS_PROCESSED_INT_ACTIVE); | ||
676 | |||
677 | val |= done1 << MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT; | ||
678 | |||
679 | mdc_chan_writel(mchan, val, MDC_CMDS_PROCESSED); | ||
680 | |||
681 | val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); | ||
682 | |||
683 | done2 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) & | ||
684 | MDC_CMDS_PROCESSED_CMDS_DONE_MASK; | ||
685 | } while (done1 != done2); | ||
686 | |||
687 | if (done1 >= processed) | ||
688 | ret = done1 - processed; | ||
689 | else | ||
690 | ret = ((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK + 1) - | ||
691 | processed) + done1; | ||
692 | |||
693 | return ret; | ||
694 | } | ||
695 | |||
654 | static int mdc_terminate_all(struct dma_chan *chan) | 696 | static int mdc_terminate_all(struct dma_chan *chan) |
655 | { | 697 | { |
656 | struct mdc_chan *mchan = to_mdc_chan(chan); | 698 | struct mdc_chan *mchan = to_mdc_chan(chan); |
@@ -667,6 +709,8 @@ static int mdc_terminate_all(struct dma_chan *chan) | |||
667 | mchan->desc = NULL; | 709 | mchan->desc = NULL; |
668 | vchan_get_all_descriptors(&mchan->vc, &head); | 710 | vchan_get_all_descriptors(&mchan->vc, &head); |
669 | 711 | ||
712 | mdc_get_new_events(mchan); | ||
713 | |||
670 | spin_unlock_irqrestore(&mchan->vc.lock, flags); | 714 | spin_unlock_irqrestore(&mchan->vc.lock, flags); |
671 | 715 | ||
672 | if (mdesc) | 716 | if (mdesc) |
@@ -703,35 +747,17 @@ static irqreturn_t mdc_chan_irq(int irq, void *dev_id) | |||
703 | { | 747 | { |
704 | struct mdc_chan *mchan = (struct mdc_chan *)dev_id; | 748 | struct mdc_chan *mchan = (struct mdc_chan *)dev_id; |
705 | struct mdc_tx_desc *mdesc; | 749 | struct mdc_tx_desc *mdesc; |
706 | u32 val, processed, done1, done2; | 750 | unsigned int i, new_events; |
707 | unsigned int i; | ||
708 | 751 | ||
709 | spin_lock(&mchan->vc.lock); | 752 | spin_lock(&mchan->vc.lock); |
710 | 753 | ||
711 | val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); | ||
712 | processed = (val >> MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) & | ||
713 | MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK; | ||
714 | /* | ||
715 | * CMDS_DONE may have incremented between reading CMDS_PROCESSED | ||
716 | * and clearing INT_ACTIVE. Re-read CMDS_PROCESSED to ensure we | ||
717 | * didn't miss a command completion. | ||
718 | */ | ||
719 | do { | ||
720 | val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); | ||
721 | done1 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) & | ||
722 | MDC_CMDS_PROCESSED_CMDS_DONE_MASK; | ||
723 | val &= ~((MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK << | ||
724 | MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT) | | ||
725 | MDC_CMDS_PROCESSED_INT_ACTIVE); | ||
726 | val |= done1 << MDC_CMDS_PROCESSED_CMDS_PROCESSED_SHIFT; | ||
727 | mdc_chan_writel(mchan, val, MDC_CMDS_PROCESSED); | ||
728 | val = mdc_chan_readl(mchan, MDC_CMDS_PROCESSED); | ||
729 | done2 = (val >> MDC_CMDS_PROCESSED_CMDS_DONE_SHIFT) & | ||
730 | MDC_CMDS_PROCESSED_CMDS_DONE_MASK; | ||
731 | } while (done1 != done2); | ||
732 | |||
733 | dev_dbg(mdma2dev(mchan->mdma), "IRQ on channel %d\n", mchan->chan_nr); | 754 | dev_dbg(mdma2dev(mchan->mdma), "IRQ on channel %d\n", mchan->chan_nr); |
734 | 755 | ||
756 | new_events = mdc_get_new_events(mchan); | ||
757 | |||
758 | if (!new_events) | ||
759 | goto out; | ||
760 | |||
735 | mdesc = mchan->desc; | 761 | mdesc = mchan->desc; |
736 | if (!mdesc) { | 762 | if (!mdesc) { |
737 | dev_warn(mdma2dev(mchan->mdma), | 763 | dev_warn(mdma2dev(mchan->mdma), |
@@ -740,8 +766,7 @@ static irqreturn_t mdc_chan_irq(int irq, void *dev_id) | |||
740 | goto out; | 766 | goto out; |
741 | } | 767 | } |
742 | 768 | ||
743 | for (i = processed; i != done1; | 769 | for (i = 0; i < new_events; i++) { |
744 | i = (i + 1) % (MDC_CMDS_PROCESSED_CMDS_PROCESSED_MASK + 1)) { | ||
745 | /* | 770 | /* |
746 | * The first interrupt in a transfer indicates that the | 771 | * The first interrupt in a transfer indicates that the |
747 | * command list has been loaded, not that a command has | 772 | * command list has been loaded, not that a command has |