diff options
author | Nicolas Ferre <nicolas.ferre@atmel.com> | 2011-05-06 13:56:52 -0400 |
---|---|---|
committer | Vinod Koul <vinod.koul@intel.com> | 2011-05-09 02:12:54 -0400 |
commit | 23b5e3ad68a3c26a6a36039ea907997664aedcab (patch) | |
tree | 4aeeb9c081c05255615eb3fdd97268f58e6cc653 /drivers/dma | |
parent | 543aabc7d295bfe2489f184259395e3467520d48 (diff) |
dmaengine: at_hdmac: implement pause and resume in atc_control
Pause and resume controls are useful for audio devices. This also returns
correct status from atc_tx_status() in case chan is paused.
Idea from dw_dmac patch by Linus Walleij.
Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
Diffstat (limited to 'drivers/dma')
-rw-r--r-- | drivers/dma/at_hdmac.c | 97 | ||||
-rw-r--r-- | drivers/dma/at_hdmac_regs.h | 1 |
2 files changed, 71 insertions, 27 deletions
diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c index ba0b5ec4e4c2..5968245e1e6a 100644 --- a/drivers/dma/at_hdmac.c +++ b/drivers/dma/at_hdmac.c | |||
@@ -508,7 +508,8 @@ static irqreturn_t at_dma_interrupt(int irq, void *dev_id) | |||
508 | if (pending & (AT_DMA_BTC(i) | AT_DMA_ERR(i))) { | 508 | if (pending & (AT_DMA_BTC(i) | AT_DMA_ERR(i))) { |
509 | if (pending & AT_DMA_ERR(i)) { | 509 | if (pending & AT_DMA_ERR(i)) { |
510 | /* Disable channel on AHB error */ | 510 | /* Disable channel on AHB error */ |
511 | dma_writel(atdma, CHDR, atchan->mask); | 511 | dma_writel(atdma, CHDR, |
512 | AT_DMA_RES(i) | atchan->mask); | ||
512 | /* Give information to tasklet */ | 513 | /* Give information to tasklet */ |
513 | set_bit(ATC_IS_ERROR, &atchan->status); | 514 | set_bit(ATC_IS_ERROR, &atchan->status); |
514 | } | 515 | } |
@@ -952,39 +953,78 @@ static int atc_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, | |||
952 | { | 953 | { |
953 | struct at_dma_chan *atchan = to_at_dma_chan(chan); | 954 | struct at_dma_chan *atchan = to_at_dma_chan(chan); |
954 | struct at_dma *atdma = to_at_dma(chan->device); | 955 | struct at_dma *atdma = to_at_dma(chan->device); |
955 | struct at_desc *desc, *_desc; | 956 | int chan_id = atchan->chan_common.chan_id; |
957 | |||
956 | LIST_HEAD(list); | 958 | LIST_HEAD(list); |
957 | 959 | ||
958 | /* Only supports DMA_TERMINATE_ALL */ | 960 | dev_vdbg(chan2dev(chan), "atc_control (%d)\n", cmd); |
959 | if (cmd != DMA_TERMINATE_ALL) | ||
960 | return -ENXIO; | ||
961 | 961 | ||
962 | /* | 962 | if (cmd == DMA_PAUSE) { |
963 | * This is only called when something went wrong elsewhere, so | 963 | int pause_timeout = 1000; |
964 | * we don't really care about the data. Just disable the | ||
965 | * channel. We still have to poll the channel enable bit due | ||
966 | * to AHB/HSB limitations. | ||
967 | */ | ||
968 | spin_lock_bh(&atchan->lock); | ||
969 | 964 | ||
970 | dma_writel(atdma, CHDR, atchan->mask); | 965 | spin_lock_bh(&atchan->lock); |
971 | 966 | ||
972 | /* confirm that this channel is disabled */ | 967 | dma_writel(atdma, CHER, AT_DMA_SUSP(chan_id)); |
973 | while (dma_readl(atdma, CHSR) & atchan->mask) | 968 | |
974 | cpu_relax(); | 969 | /* wait for FIFO to be empty */ |
970 | while (!(dma_readl(atdma, CHSR) & AT_DMA_EMPT(chan_id))) { | ||
971 | if (pause_timeout-- > 0) { | ||
972 | /* the FIFO can only drain if the peripheral | ||
973 | * is still requesting data: | ||
974 | * -> timeout if it is not the case. */ | ||
975 | dma_writel(atdma, CHDR, AT_DMA_RES(chan_id)); | ||
976 | spin_unlock_bh(&atchan->lock); | ||
977 | return -ETIMEDOUT; | ||
978 | } | ||
979 | cpu_relax(); | ||
980 | } | ||
975 | 981 | ||
976 | /* active_list entries will end up before queued entries */ | 982 | set_bit(ATC_IS_PAUSED, &atchan->status); |
977 | list_splice_init(&atchan->queue, &list); | ||
978 | list_splice_init(&atchan->active_list, &list); | ||
979 | 983 | ||
980 | /* Flush all pending and queued descriptors */ | 984 | spin_unlock_bh(&atchan->lock); |
981 | list_for_each_entry_safe(desc, _desc, &list, desc_node) | 985 | } else if (cmd == DMA_RESUME) { |
982 | atc_chain_complete(atchan, desc); | 986 | if (!test_bit(ATC_IS_PAUSED, &atchan->status)) |
987 | return 0; | ||
983 | 988 | ||
984 | /* if channel dedicated to cyclic operations, free it */ | 989 | spin_lock_bh(&atchan->lock); |
985 | clear_bit(ATC_IS_CYCLIC, &atchan->status); | ||
986 | 990 | ||
987 | spin_unlock_bh(&atchan->lock); | 991 | dma_writel(atdma, CHDR, AT_DMA_RES(chan_id)); |
992 | clear_bit(ATC_IS_PAUSED, &atchan->status); | ||
993 | |||
994 | spin_unlock_bh(&atchan->lock); | ||
995 | } else if (cmd == DMA_TERMINATE_ALL) { | ||
996 | struct at_desc *desc, *_desc; | ||
997 | /* | ||
998 | * This is only called when something went wrong elsewhere, so | ||
999 | * we don't really care about the data. Just disable the | ||
1000 | * channel. We still have to poll the channel enable bit due | ||
1001 | * to AHB/HSB limitations. | ||
1002 | */ | ||
1003 | spin_lock_bh(&atchan->lock); | ||
1004 | |||
1005 | /* disabling channel: must also remove suspend state */ | ||
1006 | dma_writel(atdma, CHDR, AT_DMA_RES(chan_id) | atchan->mask); | ||
1007 | |||
1008 | /* confirm that this channel is disabled */ | ||
1009 | while (dma_readl(atdma, CHSR) & atchan->mask) | ||
1010 | cpu_relax(); | ||
1011 | |||
1012 | /* active_list entries will end up before queued entries */ | ||
1013 | list_splice_init(&atchan->queue, &list); | ||
1014 | list_splice_init(&atchan->active_list, &list); | ||
1015 | |||
1016 | /* Flush all pending and queued descriptors */ | ||
1017 | list_for_each_entry_safe(desc, _desc, &list, desc_node) | ||
1018 | atc_chain_complete(atchan, desc); | ||
1019 | |||
1020 | clear_bit(ATC_IS_PAUSED, &atchan->status); | ||
1021 | /* if channel dedicated to cyclic operations, free it */ | ||
1022 | clear_bit(ATC_IS_CYCLIC, &atchan->status); | ||
1023 | |||
1024 | spin_unlock_bh(&atchan->lock); | ||
1025 | } else { | ||
1026 | return -ENXIO; | ||
1027 | } | ||
988 | 1028 | ||
989 | return 0; | 1029 | return 0; |
990 | } | 1030 | } |
@@ -1032,8 +1072,11 @@ atc_tx_status(struct dma_chan *chan, | |||
1032 | else | 1072 | else |
1033 | dma_set_tx_state(txstate, last_complete, last_used, 0); | 1073 | dma_set_tx_state(txstate, last_complete, last_used, 0); |
1034 | 1074 | ||
1035 | dev_vdbg(chan2dev(chan), "tx_status: %d (d%d, u%d)\n", | 1075 | if (test_bit(ATC_IS_PAUSED, &atchan->status)) |
1036 | cookie, last_complete ? last_complete : 0, | 1076 | ret = DMA_PAUSED; |
1077 | |||
1078 | dev_vdbg(chan2dev(chan), "tx_status %d: cookie = %d (d%d, u%d)\n", | ||
1079 | ret, cookie, last_complete ? last_complete : 0, | ||
1037 | last_used ? last_used : 0); | 1080 | last_used ? last_used : 0); |
1038 | 1081 | ||
1039 | return ret; | 1082 | return ret; |
diff --git a/drivers/dma/at_hdmac_regs.h b/drivers/dma/at_hdmac_regs.h index ae3056df4f4b..087dbf1dd39c 100644 --- a/drivers/dma/at_hdmac_regs.h +++ b/drivers/dma/at_hdmac_regs.h | |||
@@ -191,6 +191,7 @@ txd_to_at_desc(struct dma_async_tx_descriptor *txd) | |||
191 | */ | 191 | */ |
192 | enum atc_status { | 192 | enum atc_status { |
193 | ATC_IS_ERROR = 0, | 193 | ATC_IS_ERROR = 0, |
194 | ATC_IS_PAUSED = 1, | ||
194 | ATC_IS_CYCLIC = 24, | 195 | ATC_IS_CYCLIC = 24, |
195 | }; | 196 | }; |
196 | 197 | ||