Commit 0c842b551063c5f7382ac9b457992f3b34972801
Committed by
Dan Williams
1 parent
86eb5fb611
Exists in
master
and in
4 other branches
dma40: cyclic xfer support
Support cyclic transfers, which are useful for ALSA drivers. Acked-by: Per Forlin <per.forlin@stericsson.com> Acked-by: Jonas Aaberg <jonas.aberg@stericsson.com> Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com> Signed-off-by: Linus Walleij <linus.walleij@stericsson.com> Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Showing 3 changed files with 167 additions and 49 deletions Side-by-side Diff
drivers/dma/ste_dma40.c
... | ... | @@ -115,6 +115,7 @@ |
115 | 115 | struct list_head node; |
116 | 116 | |
117 | 117 | bool is_in_client_list; |
118 | + bool cyclic; | |
118 | 119 | }; |
119 | 120 | |
120 | 121 | /** |
121 | 122 | |
122 | 123 | |
123 | 124 | |
124 | 125 | |
125 | 126 | |
126 | 127 | |
... | ... | @@ -527,18 +528,46 @@ |
527 | 528 | struct d40_log_lli_bidir *lli = &desc->lli_log; |
528 | 529 | int lli_current = desc->lli_current; |
529 | 530 | int lli_len = desc->lli_len; |
531 | + bool cyclic = desc->cyclic; | |
530 | 532 | int curr_lcla = -EINVAL; |
533 | + int first_lcla = 0; | |
534 | + bool linkback; | |
531 | 535 | |
532 | - if (lli_len - lli_current > 1) | |
536 | + /* | |
537 | + * We may have partially running cyclic transfers, in case we did't get | |
538 | + * enough LCLA entries. | |
539 | + */ | |
540 | + linkback = cyclic && lli_current == 0; | |
541 | + | |
542 | + /* | |
543 | + * For linkback, we need one LCLA even with only one link, because we | |
544 | + * can't link back to the one in LCPA space | |
545 | + */ | |
546 | + if (linkback || (lli_len - lli_current > 1)) { | |
533 | 547 | curr_lcla = d40_lcla_alloc_one(chan, desc); |
548 | + first_lcla = curr_lcla; | |
549 | + } | |
534 | 550 | |
535 | - d40_log_lli_lcpa_write(chan->lcpa, | |
536 | - &lli->dst[lli_current], | |
537 | - &lli->src[lli_current], | |
538 | - curr_lcla); | |
551 | + /* | |
552 | + * For linkback, we normally load the LCPA in the loop since we need to | |
553 | + * link it to the second LCLA and not the first. However, if we | |
554 | + * couldn't even get a first LCLA, then we have to run in LCPA and | |
555 | + * reload manually. | |
556 | + */ | |
557 | + if (!linkback || curr_lcla == -EINVAL) { | |
558 | + unsigned int flags = 0; | |
539 | 559 | |
540 | - lli_current++; | |
560 | + if (curr_lcla == -EINVAL) | |
561 | + flags |= LLI_TERM_INT; | |
541 | 562 | |
563 | + d40_log_lli_lcpa_write(chan->lcpa, | |
564 | + &lli->dst[lli_current], | |
565 | + &lli->src[lli_current], | |
566 | + curr_lcla, | |
567 | + flags); | |
568 | + lli_current++; | |
569 | + } | |
570 | + | |
542 | 571 | if (curr_lcla < 0) |
543 | 572 | goto out; |
544 | 573 | |
545 | 574 | |
546 | 575 | |
547 | 576 | |
... | ... | @@ -546,17 +575,33 @@ |
546 | 575 | unsigned int lcla_offset = chan->phy_chan->num * 1024 + |
547 | 576 | 8 * curr_lcla * 2; |
548 | 577 | struct d40_log_lli *lcla = pool->base + lcla_offset; |
578 | + unsigned int flags = 0; | |
549 | 579 | int next_lcla; |
550 | 580 | |
551 | 581 | if (lli_current + 1 < lli_len) |
552 | 582 | next_lcla = d40_lcla_alloc_one(chan, desc); |
553 | 583 | else |
554 | - next_lcla = -EINVAL; | |
584 | + next_lcla = linkback ? first_lcla : -EINVAL; | |
555 | 585 | |
586 | + if (cyclic || next_lcla == -EINVAL) | |
587 | + flags |= LLI_TERM_INT; | |
588 | + | |
589 | + if (linkback && curr_lcla == first_lcla) { | |
590 | + /* First link goes in both LCPA and LCLA */ | |
591 | + d40_log_lli_lcpa_write(chan->lcpa, | |
592 | + &lli->dst[lli_current], | |
593 | + &lli->src[lli_current], | |
594 | + next_lcla, flags); | |
595 | + } | |
596 | + | |
597 | + /* | |
598 | + * One unused LCLA in the cyclic case if the very first | |
599 | + * next_lcla fails... | |
600 | + */ | |
556 | 601 | d40_log_lli_lcla_write(lcla, |
557 | 602 | &lli->dst[lli_current], |
558 | 603 | &lli->src[lli_current], |
559 | - next_lcla); | |
604 | + next_lcla, flags); | |
560 | 605 | |
561 | 606 | dma_sync_single_range_for_device(chan->base->dev, |
562 | 607 | pool->dma_addr, lcla_offset, |
... | ... | @@ -565,7 +610,7 @@ |
565 | 610 | |
566 | 611 | curr_lcla = next_lcla; |
567 | 612 | |
568 | - if (curr_lcla == -EINVAL) { | |
613 | + if (curr_lcla == -EINVAL || curr_lcla == first_lcla) { | |
569 | 614 | lli_current++; |
570 | 615 | break; |
571 | 616 | } |
572 | 617 | |
573 | 618 | |
574 | 619 | |
... | ... | @@ -1074,18 +1119,37 @@ |
1074 | 1119 | if (d40d == NULL) |
1075 | 1120 | return; |
1076 | 1121 | |
1077 | - d40_lcla_free_all(d40c, d40d); | |
1122 | + if (d40d->cyclic) { | |
1123 | + /* | |
1124 | + * If this was a paritially loaded list, we need to reloaded | |
1125 | + * it, and only when the list is completed. We need to check | |
1126 | + * for done because the interrupt will hit for every link, and | |
1127 | + * not just the last one. | |
1128 | + */ | |
1129 | + if (d40d->lli_current < d40d->lli_len | |
1130 | + && !d40_tx_is_linked(d40c) | |
1131 | + && !d40_residue(d40c)) { | |
1132 | + d40_lcla_free_all(d40c, d40d); | |
1133 | + d40_desc_load(d40c, d40d); | |
1134 | + (void) d40_start(d40c); | |
1078 | 1135 | |
1079 | - if (d40d->lli_current < d40d->lli_len) { | |
1080 | - d40_desc_load(d40c, d40d); | |
1081 | - /* Start dma job */ | |
1082 | - (void) d40_start(d40c); | |
1083 | - return; | |
1084 | - } | |
1136 | + if (d40d->lli_current == d40d->lli_len) | |
1137 | + d40d->lli_current = 0; | |
1138 | + } | |
1139 | + } else { | |
1140 | + d40_lcla_free_all(d40c, d40d); | |
1085 | 1141 | |
1086 | - if (d40_queue_start(d40c) == NULL) | |
1087 | - d40c->busy = false; | |
1142 | + if (d40d->lli_current < d40d->lli_len) { | |
1143 | + d40_desc_load(d40c, d40d); | |
1144 | + /* Start dma job */ | |
1145 | + (void) d40_start(d40c); | |
1146 | + return; | |
1147 | + } | |
1088 | 1148 | |
1149 | + if (d40_queue_start(d40c) == NULL) | |
1150 | + d40c->busy = false; | |
1151 | + } | |
1152 | + | |
1089 | 1153 | d40c->pending_tx++; |
1090 | 1154 | tasklet_schedule(&d40c->tasklet); |
1091 | 1155 | |
1092 | 1156 | |
... | ... | @@ -1103,11 +1167,11 @@ |
1103 | 1167 | |
1104 | 1168 | /* Get first active entry from list */ |
1105 | 1169 | d40d = d40_first_active_get(d40c); |
1106 | - | |
1107 | 1170 | if (d40d == NULL) |
1108 | 1171 | goto err; |
1109 | 1172 | |
1110 | - d40c->completed = d40d->txd.cookie; | |
1173 | + if (!d40d->cyclic) | |
1174 | + d40c->completed = d40d->txd.cookie; | |
1111 | 1175 | |
1112 | 1176 | /* |
1113 | 1177 | * If terminating a channel pending_tx is set to zero. |
1114 | 1178 | |
... | ... | @@ -1122,16 +1186,18 @@ |
1122 | 1186 | callback = d40d->txd.callback; |
1123 | 1187 | callback_param = d40d->txd.callback_param; |
1124 | 1188 | |
1125 | - if (async_tx_test_ack(&d40d->txd)) { | |
1126 | - d40_pool_lli_free(d40c, d40d); | |
1127 | - d40_desc_remove(d40d); | |
1128 | - d40_desc_free(d40c, d40d); | |
1129 | - } else { | |
1130 | - if (!d40d->is_in_client_list) { | |
1189 | + if (!d40d->cyclic) { | |
1190 | + if (async_tx_test_ack(&d40d->txd)) { | |
1191 | + d40_pool_lli_free(d40c, d40d); | |
1131 | 1192 | d40_desc_remove(d40d); |
1132 | - d40_lcla_free_all(d40c, d40d); | |
1133 | - list_add_tail(&d40d->node, &d40c->client); | |
1134 | - d40d->is_in_client_list = true; | |
1193 | + d40_desc_free(d40c, d40d); | |
1194 | + } else { | |
1195 | + if (!d40d->is_in_client_list) { | |
1196 | + d40_desc_remove(d40d); | |
1197 | + d40_lcla_free_all(d40c, d40d); | |
1198 | + list_add_tail(&d40d->node, &d40c->client); | |
1199 | + d40d->is_in_client_list = true; | |
1200 | + } | |
1135 | 1201 | } |
1136 | 1202 | } |
1137 | 1203 | |
1138 | 1204 | |
1139 | 1205 | |
1140 | 1206 | |
... | ... | @@ -1694,19 +1760,23 @@ |
1694 | 1760 | struct stedma40_chan_cfg *cfg = &chan->dma_cfg; |
1695 | 1761 | struct stedma40_half_channel_info *src_info = &cfg->src_info; |
1696 | 1762 | struct stedma40_half_channel_info *dst_info = &cfg->dst_info; |
1763 | + unsigned long flags = 0; | |
1697 | 1764 | int ret; |
1698 | 1765 | |
1766 | + if (desc->cyclic) | |
1767 | + flags |= LLI_CYCLIC | LLI_TERM_INT; | |
1768 | + | |
1699 | 1769 | ret = d40_phy_sg_to_lli(sg_src, sg_len, src_dev_addr, |
1700 | 1770 | desc->lli_phy.src, |
1701 | 1771 | virt_to_phys(desc->lli_phy.src), |
1702 | 1772 | chan->src_def_cfg, |
1703 | - src_info, dst_info); | |
1773 | + src_info, dst_info, flags); | |
1704 | 1774 | |
1705 | 1775 | ret = d40_phy_sg_to_lli(sg_dst, sg_len, dst_dev_addr, |
1706 | 1776 | desc->lli_phy.dst, |
1707 | 1777 | virt_to_phys(desc->lli_phy.dst), |
1708 | 1778 | chan->dst_def_cfg, |
1709 | - dst_info, src_info); | |
1779 | + dst_info, src_info, flags); | |
1710 | 1780 | |
1711 | 1781 | dma_sync_single_for_device(chan->base->dev, desc->lli_pool.dma_addr, |
1712 | 1782 | desc->lli_pool.size, DMA_TO_DEVICE); |
1713 | 1783 | |
... | ... | @@ -1789,12 +1859,16 @@ |
1789 | 1859 | return NULL; |
1790 | 1860 | } |
1791 | 1861 | |
1862 | + | |
1792 | 1863 | spin_lock_irqsave(&chan->lock, flags); |
1793 | 1864 | |
1794 | 1865 | desc = d40_prep_desc(chan, sg_src, sg_len, dma_flags); |
1795 | 1866 | if (desc == NULL) |
1796 | 1867 | goto err; |
1797 | 1868 | |
1869 | + if (sg_next(&sg_src[sg_len - 1]) == sg_src) | |
1870 | + desc->cyclic = true; | |
1871 | + | |
1798 | 1872 | if (direction != DMA_NONE) { |
1799 | 1873 | dma_addr_t dev_addr = d40_get_dev_addr(chan, direction); |
1800 | 1874 | |
... | ... | @@ -2007,6 +2081,36 @@ |
2007 | 2081 | return d40_prep_sg(chan, sgl, sgl, sg_len, direction, dma_flags); |
2008 | 2082 | } |
2009 | 2083 | |
2084 | +static struct dma_async_tx_descriptor * | |
2085 | +dma40_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr, | |
2086 | + size_t buf_len, size_t period_len, | |
2087 | + enum dma_data_direction direction) | |
2088 | +{ | |
2089 | + unsigned int periods = buf_len / period_len; | |
2090 | + struct dma_async_tx_descriptor *txd; | |
2091 | + struct scatterlist *sg; | |
2092 | + int i; | |
2093 | + | |
2094 | + sg = kcalloc(periods + 1, sizeof(struct scatterlist), GFP_KERNEL); | |
2095 | + for (i = 0; i < periods; i++) { | |
2096 | + sg_dma_address(&sg[i]) = dma_addr; | |
2097 | + sg_dma_len(&sg[i]) = period_len; | |
2098 | + dma_addr += period_len; | |
2099 | + } | |
2100 | + | |
2101 | + sg[periods].offset = 0; | |
2102 | + sg[periods].length = 0; | |
2103 | + sg[periods].page_link = | |
2104 | + ((unsigned long)sg | 0x01) & ~0x02; | |
2105 | + | |
2106 | + txd = d40_prep_sg(chan, sg, sg, periods, direction, | |
2107 | + DMA_PREP_INTERRUPT); | |
2108 | + | |
2109 | + kfree(sg); | |
2110 | + | |
2111 | + return txd; | |
2112 | +} | |
2113 | + | |
2010 | 2114 | static enum dma_status d40_tx_status(struct dma_chan *chan, |
2011 | 2115 | dma_cookie_t cookie, |
2012 | 2116 | struct dma_tx_state *txstate) |
... | ... | @@ -2264,6 +2368,9 @@ |
2264 | 2368 | if (dma_has_cap(DMA_SG, dev->cap_mask)) |
2265 | 2369 | dev->device_prep_dma_sg = d40_prep_memcpy_sg; |
2266 | 2370 | |
2371 | + if (dma_has_cap(DMA_CYCLIC, dev->cap_mask)) | |
2372 | + dev->device_prep_dma_cyclic = dma40_prep_dma_cyclic; | |
2373 | + | |
2267 | 2374 | dev->device_alloc_chan_resources = d40_alloc_chan_resources; |
2268 | 2375 | dev->device_free_chan_resources = d40_free_chan_resources; |
2269 | 2376 | dev->device_issue_pending = d40_issue_pending; |
... | ... | @@ -2282,6 +2389,7 @@ |
2282 | 2389 | |
2283 | 2390 | dma_cap_zero(base->dma_slave.cap_mask); |
2284 | 2391 | dma_cap_set(DMA_SLAVE, base->dma_slave.cap_mask); |
2392 | + dma_cap_set(DMA_CYCLIC, base->dma_slave.cap_mask); | |
2285 | 2393 | |
2286 | 2394 | d40_ops_init(base, &base->dma_slave); |
2287 | 2395 | |
2288 | 2396 | |
... | ... | @@ -2316,9 +2424,9 @@ |
2316 | 2424 | dma_cap_set(DMA_SLAVE, base->dma_both.cap_mask); |
2317 | 2425 | dma_cap_set(DMA_MEMCPY, base->dma_both.cap_mask); |
2318 | 2426 | dma_cap_set(DMA_SG, base->dma_both.cap_mask); |
2427 | + dma_cap_set(DMA_CYCLIC, base->dma_slave.cap_mask); | |
2319 | 2428 | |
2320 | 2429 | d40_ops_init(base, &base->dma_both); |
2321 | - | |
2322 | 2430 | err = dma_async_device_register(&base->dma_both); |
2323 | 2431 | |
2324 | 2432 | if (err) { |
drivers/dma/ste_dma40_ll.c
... | ... | @@ -202,13 +202,15 @@ |
202 | 202 | |
203 | 203 | static struct d40_phy_lli * |
204 | 204 | d40_phy_buf_to_lli(struct d40_phy_lli *lli, dma_addr_t addr, u32 size, |
205 | - dma_addr_t lli_phys, u32 reg_cfg, | |
205 | + dma_addr_t lli_phys, dma_addr_t first_phys, u32 reg_cfg, | |
206 | 206 | struct stedma40_half_channel_info *info, |
207 | 207 | struct stedma40_half_channel_info *otherinfo, |
208 | 208 | unsigned long flags) |
209 | 209 | { |
210 | + bool lastlink = flags & LLI_LAST_LINK; | |
210 | 211 | bool addr_inc = flags & LLI_ADDR_INC; |
211 | 212 | bool term_int = flags & LLI_TERM_INT; |
213 | + bool cyclic = flags & LLI_CYCLIC; | |
212 | 214 | int err; |
213 | 215 | dma_addr_t next = lli_phys; |
214 | 216 | int size_rest = size; |
215 | 217 | |
... | ... | @@ -226,10 +228,12 @@ |
226 | 228 | otherinfo->data_width); |
227 | 229 | size_rest -= size_seg; |
228 | 230 | |
229 | - if (term_int && size_rest == 0) { | |
230 | - next = 0; | |
231 | + if (size_rest == 0 && term_int) | |
231 | 232 | flags |= LLI_TERM_INT; |
232 | - } else | |
233 | + | |
234 | + if (size_rest == 0 && lastlink) | |
235 | + next = cyclic ? first_phys : 0; | |
236 | + else | |
233 | 237 | next = ALIGN(next + sizeof(struct d40_phy_lli), |
234 | 238 | D40_LLI_ALIGN); |
235 | 239 | |
236 | 240 | |
... | ... | @@ -257,14 +261,14 @@ |
257 | 261 | dma_addr_t lli_phys, |
258 | 262 | u32 reg_cfg, |
259 | 263 | struct stedma40_half_channel_info *info, |
260 | - struct stedma40_half_channel_info *otherinfo) | |
264 | + struct stedma40_half_channel_info *otherinfo, | |
265 | + unsigned long flags) | |
261 | 266 | { |
262 | 267 | int total_size = 0; |
263 | 268 | int i; |
264 | 269 | struct scatterlist *current_sg = sg; |
265 | 270 | struct d40_phy_lli *lli = lli_sg; |
266 | 271 | dma_addr_t l_phys = lli_phys; |
267 | - unsigned long flags = 0; | |
268 | 272 | |
269 | 273 | if (!target) |
270 | 274 | flags |= LLI_ADDR_INC; |
271 | 275 | |
... | ... | @@ -277,12 +281,12 @@ |
277 | 281 | total_size += sg_dma_len(current_sg); |
278 | 282 | |
279 | 283 | if (i == sg_len - 1) |
280 | - flags |= LLI_TERM_INT; | |
284 | + flags |= LLI_TERM_INT | LLI_LAST_LINK; | |
281 | 285 | |
282 | 286 | l_phys = ALIGN(lli_phys + (lli - lli_sg) * |
283 | 287 | sizeof(struct d40_phy_lli), D40_LLI_ALIGN); |
284 | 288 | |
285 | - lli = d40_phy_buf_to_lli(lli, dst, len, l_phys, | |
289 | + lli = d40_phy_buf_to_lli(lli, dst, len, l_phys, lli_phys, | |
286 | 290 | reg_cfg, info, otherinfo, flags); |
287 | 291 | |
288 | 292 | if (lli == NULL) |
289 | 293 | |
290 | 294 | |
... | ... | @@ -297,15 +301,18 @@ |
297 | 301 | |
298 | 302 | static void d40_log_lli_link(struct d40_log_lli *lli_dst, |
299 | 303 | struct d40_log_lli *lli_src, |
300 | - int next) | |
304 | + int next, unsigned int flags) | |
301 | 305 | { |
306 | + bool interrupt = flags & LLI_TERM_INT; | |
302 | 307 | u32 slos = 0; |
303 | 308 | u32 dlos = 0; |
304 | 309 | |
305 | 310 | if (next != -EINVAL) { |
306 | 311 | slos = next * 2; |
307 | 312 | dlos = next * 2 + 1; |
308 | - } else { | |
313 | + } | |
314 | + | |
315 | + if (interrupt) { | |
309 | 316 | lli_dst->lcsp13 |= D40_MEM_LCSP1_SCFG_TIM_MASK; |
310 | 317 | lli_dst->lcsp13 |= D40_MEM_LCSP3_DTCP_MASK; |
311 | 318 | } |
312 | 319 | |
... | ... | @@ -320,9 +327,9 @@ |
320 | 327 | void d40_log_lli_lcpa_write(struct d40_log_lli_full *lcpa, |
321 | 328 | struct d40_log_lli *lli_dst, |
322 | 329 | struct d40_log_lli *lli_src, |
323 | - int next) | |
330 | + int next, unsigned int flags) | |
324 | 331 | { |
325 | - d40_log_lli_link(lli_dst, lli_src, next); | |
332 | + d40_log_lli_link(lli_dst, lli_src, next, flags); | |
326 | 333 | |
327 | 334 | writel(lli_src->lcsp02, &lcpa[0].lcsp0); |
328 | 335 | writel(lli_src->lcsp13, &lcpa[0].lcsp1); |
329 | 336 | |
... | ... | @@ -333,9 +340,9 @@ |
333 | 340 | void d40_log_lli_lcla_write(struct d40_log_lli *lcla, |
334 | 341 | struct d40_log_lli *lli_dst, |
335 | 342 | struct d40_log_lli *lli_src, |
336 | - int next) | |
343 | + int next, unsigned int flags) | |
337 | 344 | { |
338 | - d40_log_lli_link(lli_dst, lli_src, next); | |
345 | + d40_log_lli_link(lli_dst, lli_src, next, flags); | |
339 | 346 | |
340 | 347 | writel(lli_src->lcsp02, &lcla[0].lcsp02); |
341 | 348 | writel(lli_src->lcsp13, &lcla[0].lcsp13); |
drivers/dma/ste_dma40_ll.h
... | ... | @@ -296,6 +296,8 @@ |
296 | 296 | enum d40_lli_flags { |
297 | 297 | LLI_ADDR_INC = 1 << 0, |
298 | 298 | LLI_TERM_INT = 1 << 1, |
299 | + LLI_CYCLIC = 1 << 2, | |
300 | + LLI_LAST_LINK = 1 << 3, | |
299 | 301 | }; |
300 | 302 | |
301 | 303 | void d40_phy_cfg(struct stedma40_chan_cfg *cfg, |
... | ... | @@ -314,7 +316,8 @@ |
314 | 316 | dma_addr_t lli_phys, |
315 | 317 | u32 reg_cfg, |
316 | 318 | struct stedma40_half_channel_info *info, |
317 | - struct stedma40_half_channel_info *otherinfo); | |
319 | + struct stedma40_half_channel_info *otherinfo, | |
320 | + unsigned long flags); | |
318 | 321 | |
319 | 322 | /* Logical channels */ |
320 | 323 | |
321 | 324 | |
... | ... | @@ -328,12 +331,12 @@ |
328 | 331 | void d40_log_lli_lcpa_write(struct d40_log_lli_full *lcpa, |
329 | 332 | struct d40_log_lli *lli_dst, |
330 | 333 | struct d40_log_lli *lli_src, |
331 | - int next); | |
334 | + int next, unsigned int flags); | |
332 | 335 | |
333 | 336 | void d40_log_lli_lcla_write(struct d40_log_lli *lcla, |
334 | 337 | struct d40_log_lli *lli_dst, |
335 | 338 | struct d40_log_lli *lli_src, |
336 | - int next); | |
339 | + int next, unsigned int flags); | |
337 | 340 | |
338 | 341 | #endif /* STE_DMA40_LLI_H */ |