diff options
author | Per Forlin <per.forlin@stericsson.com> | 2010-12-20 12:31:38 -0500 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2011-01-04 20:20:43 -0500 |
commit | d49278e3351b34870cbffffc5067348a318e7b06 (patch) | |
tree | a8569e9a24286a0bd885f96e27342e4de2fdecbb /drivers/dma/ste_dma40_ll.c | |
parent | e8a7e48bb248a1196484d3f8afa53bded2b24e71 (diff) |
dmaengine: dma40: Add support to split up large elements
The maximum transfer size of the stedma40 is (64k-1) x data-width.
If the transfer size of one element exceeds this limit
the job is split up and sent as linked transfer.
Signed-off-by: Per Forlin <per.forlin@linaro.org>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Diffstat (limited to 'drivers/dma/ste_dma40_ll.c')
-rw-r--r-- | drivers/dma/ste_dma40_ll.c | 246 |
1 files changed, 172 insertions, 74 deletions
diff --git a/drivers/dma/ste_dma40_ll.c b/drivers/dma/ste_dma40_ll.c index 8557cb88b255..0b096a38322d 100644 --- a/drivers/dma/ste_dma40_ll.c +++ b/drivers/dma/ste_dma40_ll.c | |||
@@ -1,6 +1,6 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (C) ST-Ericsson SA 2007-2010 | 2 | * Copyright (C) ST-Ericsson SA 2007-2010 |
3 | * Author: Per Friden <per.friden@stericsson.com> for ST-Ericsson | 3 | * Author: Per Forlin <per.forlin@stericsson.com> for ST-Ericsson |
4 | * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson | 4 | * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson |
5 | * License terms: GNU General Public License (GPL) version 2 | 5 | * License terms: GNU General Public License (GPL) version 2 |
6 | */ | 6 | */ |
@@ -122,15 +122,15 @@ void d40_phy_cfg(struct stedma40_chan_cfg *cfg, | |||
122 | *dst_cfg = dst; | 122 | *dst_cfg = dst; |
123 | } | 123 | } |
124 | 124 | ||
125 | int d40_phy_fill_lli(struct d40_phy_lli *lli, | 125 | static int d40_phy_fill_lli(struct d40_phy_lli *lli, |
126 | dma_addr_t data, | 126 | dma_addr_t data, |
127 | u32 data_size, | 127 | u32 data_size, |
128 | int psize, | 128 | int psize, |
129 | dma_addr_t next_lli, | 129 | dma_addr_t next_lli, |
130 | u32 reg_cfg, | 130 | u32 reg_cfg, |
131 | bool term_int, | 131 | bool term_int, |
132 | u32 data_width, | 132 | u32 data_width, |
133 | bool is_device) | 133 | bool is_device) |
134 | { | 134 | { |
135 | int num_elems; | 135 | int num_elems; |
136 | 136 | ||
@@ -139,13 +139,6 @@ int d40_phy_fill_lli(struct d40_phy_lli *lli, | |||
139 | else | 139 | else |
140 | num_elems = 2 << psize; | 140 | num_elems = 2 << psize; |
141 | 141 | ||
142 | /* | ||
143 | * Size is 16bit. data_width is 8, 16, 32 or 64 bit | ||
144 | * Block large than 64 KiB must be split. | ||
145 | */ | ||
146 | if (data_size > (0xffff << data_width)) | ||
147 | return -EINVAL; | ||
148 | |||
149 | /* Must be aligned */ | 142 | /* Must be aligned */ |
150 | if (!IS_ALIGNED(data, 0x1 << data_width)) | 143 | if (!IS_ALIGNED(data, 0x1 << data_width)) |
151 | return -EINVAL; | 144 | return -EINVAL; |
@@ -187,55 +180,118 @@ int d40_phy_fill_lli(struct d40_phy_lli *lli, | |||
187 | return 0; | 180 | return 0; |
188 | } | 181 | } |
189 | 182 | ||
183 | static int d40_seg_size(int size, int data_width1, int data_width2) | ||
184 | { | ||
185 | u32 max_w = max(data_width1, data_width2); | ||
186 | u32 min_w = min(data_width1, data_width2); | ||
187 | u32 seg_max = ALIGN(STEDMA40_MAX_SEG_SIZE << min_w, 1 << max_w); | ||
188 | |||
189 | if (seg_max > STEDMA40_MAX_SEG_SIZE) | ||
190 | seg_max -= (1 << max_w); | ||
191 | |||
192 | if (size <= seg_max) | ||
193 | return size; | ||
194 | |||
195 | if (size <= 2 * seg_max) | ||
196 | return ALIGN(size / 2, 1 << max_w); | ||
197 | |||
198 | return seg_max; | ||
199 | } | ||
200 | |||
201 | struct d40_phy_lli *d40_phy_buf_to_lli(struct d40_phy_lli *lli, | ||
202 | dma_addr_t addr, | ||
203 | u32 size, | ||
204 | int psize, | ||
205 | dma_addr_t lli_phys, | ||
206 | u32 reg_cfg, | ||
207 | bool term_int, | ||
208 | u32 data_width1, | ||
209 | u32 data_width2, | ||
210 | bool is_device) | ||
211 | { | ||
212 | int err; | ||
213 | dma_addr_t next = lli_phys; | ||
214 | int size_rest = size; | ||
215 | int size_seg = 0; | ||
216 | |||
217 | do { | ||
218 | size_seg = d40_seg_size(size_rest, data_width1, data_width2); | ||
219 | size_rest -= size_seg; | ||
220 | |||
221 | if (term_int && size_rest == 0) | ||
222 | next = 0; | ||
223 | else | ||
224 | next = ALIGN(next + sizeof(struct d40_phy_lli), | ||
225 | D40_LLI_ALIGN); | ||
226 | |||
227 | err = d40_phy_fill_lli(lli, | ||
228 | addr, | ||
229 | size_seg, | ||
230 | psize, | ||
231 | next, | ||
232 | reg_cfg, | ||
233 | !next, | ||
234 | data_width1, | ||
235 | is_device); | ||
236 | |||
237 | if (err) | ||
238 | goto err; | ||
239 | |||
240 | lli++; | ||
241 | if (!is_device) | ||
242 | addr += size_seg; | ||
243 | } while (size_rest); | ||
244 | |||
245 | return lli; | ||
246 | |||
247 | err: | ||
248 | return NULL; | ||
249 | } | ||
250 | |||
190 | int d40_phy_sg_to_lli(struct scatterlist *sg, | 251 | int d40_phy_sg_to_lli(struct scatterlist *sg, |
191 | int sg_len, | 252 | int sg_len, |
192 | dma_addr_t target, | 253 | dma_addr_t target, |
193 | struct d40_phy_lli *lli, | 254 | struct d40_phy_lli *lli_sg, |
194 | dma_addr_t lli_phys, | 255 | dma_addr_t lli_phys, |
195 | u32 reg_cfg, | 256 | u32 reg_cfg, |
196 | u32 data_width, | 257 | u32 data_width1, |
258 | u32 data_width2, | ||
197 | int psize) | 259 | int psize) |
198 | { | 260 | { |
199 | int total_size = 0; | 261 | int total_size = 0; |
200 | int i; | 262 | int i; |
201 | struct scatterlist *current_sg = sg; | 263 | struct scatterlist *current_sg = sg; |
202 | dma_addr_t next_lli_phys; | ||
203 | dma_addr_t dst; | 264 | dma_addr_t dst; |
204 | int err = 0; | 265 | struct d40_phy_lli *lli = lli_sg; |
266 | dma_addr_t l_phys = lli_phys; | ||
205 | 267 | ||
206 | for_each_sg(sg, current_sg, sg_len, i) { | 268 | for_each_sg(sg, current_sg, sg_len, i) { |
207 | 269 | ||
208 | total_size += sg_dma_len(current_sg); | 270 | total_size += sg_dma_len(current_sg); |
209 | 271 | ||
210 | /* If this scatter list entry is the last one, no next link */ | ||
211 | if (sg_len - 1 == i) | ||
212 | next_lli_phys = 0; | ||
213 | else | ||
214 | next_lli_phys = ALIGN(lli_phys + (i + 1) * | ||
215 | sizeof(struct d40_phy_lli), | ||
216 | D40_LLI_ALIGN); | ||
217 | |||
218 | if (target) | 272 | if (target) |
219 | dst = target; | 273 | dst = target; |
220 | else | 274 | else |
221 | dst = sg_phys(current_sg); | 275 | dst = sg_phys(current_sg); |
222 | 276 | ||
223 | err = d40_phy_fill_lli(&lli[i], | 277 | l_phys = ALIGN(lli_phys + (lli - lli_sg) * |
224 | dst, | 278 | sizeof(struct d40_phy_lli), D40_LLI_ALIGN); |
225 | sg_dma_len(current_sg), | 279 | |
226 | psize, | 280 | lli = d40_phy_buf_to_lli(lli, |
227 | next_lli_phys, | 281 | dst, |
228 | reg_cfg, | 282 | sg_dma_len(current_sg), |
229 | !next_lli_phys, | 283 | psize, |
230 | data_width, | 284 | l_phys, |
231 | target == dst); | 285 | reg_cfg, |
232 | if (err) | 286 | sg_len - 1 == i, |
233 | goto err; | 287 | data_width1, |
288 | data_width2, | ||
289 | target == dst); | ||
290 | if (lli == NULL) | ||
291 | return -EINVAL; | ||
234 | } | 292 | } |
235 | 293 | ||
236 | return total_size; | 294 | return total_size; |
237 | err: | ||
238 | return err; | ||
239 | } | 295 | } |
240 | 296 | ||
241 | 297 | ||
@@ -315,17 +371,20 @@ void d40_log_lli_lcla_write(struct d40_log_lli *lcla, | |||
315 | writel(lli_dst->lcsp13, &lcla[1].lcsp13); | 371 | writel(lli_dst->lcsp13, &lcla[1].lcsp13); |
316 | } | 372 | } |
317 | 373 | ||
318 | void d40_log_fill_lli(struct d40_log_lli *lli, | 374 | static void d40_log_fill_lli(struct d40_log_lli *lli, |
319 | dma_addr_t data, u32 data_size, | 375 | dma_addr_t data, u32 data_size, |
320 | u32 reg_cfg, | 376 | u32 reg_cfg, |
321 | u32 data_width, | 377 | u32 data_width, |
322 | bool addr_inc) | 378 | bool addr_inc) |
323 | { | 379 | { |
324 | lli->lcsp13 = reg_cfg; | 380 | lli->lcsp13 = reg_cfg; |
325 | 381 | ||
326 | /* The number of elements to transfer */ | 382 | /* The number of elements to transfer */ |
327 | lli->lcsp02 = ((data_size >> data_width) << | 383 | lli->lcsp02 = ((data_size >> data_width) << |
328 | D40_MEM_LCSP0_ECNT_POS) & D40_MEM_LCSP0_ECNT_MASK; | 384 | D40_MEM_LCSP0_ECNT_POS) & D40_MEM_LCSP0_ECNT_MASK; |
385 | |||
386 | BUG_ON((data_size >> data_width) > STEDMA40_MAX_SEG_SIZE); | ||
387 | |||
329 | /* 16 LSBs address of the current element */ | 388 | /* 16 LSBs address of the current element */ |
330 | lli->lcsp02 |= data & D40_MEM_LCSP0_SPTR_MASK; | 389 | lli->lcsp02 |= data & D40_MEM_LCSP0_SPTR_MASK; |
331 | /* 16 MSBs address of the current element */ | 390 | /* 16 MSBs address of the current element */ |
@@ -348,55 +407,94 @@ int d40_log_sg_to_dev(struct scatterlist *sg, | |||
348 | int total_size = 0; | 407 | int total_size = 0; |
349 | struct scatterlist *current_sg = sg; | 408 | struct scatterlist *current_sg = sg; |
350 | int i; | 409 | int i; |
410 | struct d40_log_lli *lli_src = lli->src; | ||
411 | struct d40_log_lli *lli_dst = lli->dst; | ||
351 | 412 | ||
352 | for_each_sg(sg, current_sg, sg_len, i) { | 413 | for_each_sg(sg, current_sg, sg_len, i) { |
353 | total_size += sg_dma_len(current_sg); | 414 | total_size += sg_dma_len(current_sg); |
354 | 415 | ||
355 | if (direction == DMA_TO_DEVICE) { | 416 | if (direction == DMA_TO_DEVICE) { |
356 | d40_log_fill_lli(&lli->src[i], | 417 | lli_src = |
357 | sg_phys(current_sg), | 418 | d40_log_buf_to_lli(lli_src, |
358 | sg_dma_len(current_sg), | 419 | sg_phys(current_sg), |
359 | lcsp->lcsp1, src_data_width, | 420 | sg_dma_len(current_sg), |
360 | true); | 421 | lcsp->lcsp1, src_data_width, |
361 | d40_log_fill_lli(&lli->dst[i], | 422 | dst_data_width, |
362 | dev_addr, | 423 | true); |
363 | sg_dma_len(current_sg), | 424 | lli_dst = |
364 | lcsp->lcsp3, dst_data_width, | 425 | d40_log_buf_to_lli(lli_dst, |
365 | false); | 426 | dev_addr, |
427 | sg_dma_len(current_sg), | ||
428 | lcsp->lcsp3, dst_data_width, | ||
429 | src_data_width, | ||
430 | false); | ||
366 | } else { | 431 | } else { |
367 | d40_log_fill_lli(&lli->dst[i], | 432 | lli_dst = |
368 | sg_phys(current_sg), | 433 | d40_log_buf_to_lli(lli_dst, |
369 | sg_dma_len(current_sg), | 434 | sg_phys(current_sg), |
370 | lcsp->lcsp3, dst_data_width, | 435 | sg_dma_len(current_sg), |
371 | true); | 436 | lcsp->lcsp3, dst_data_width, |
372 | d40_log_fill_lli(&lli->src[i], | 437 | src_data_width, |
373 | dev_addr, | 438 | true); |
374 | sg_dma_len(current_sg), | 439 | lli_src = |
375 | lcsp->lcsp1, src_data_width, | 440 | d40_log_buf_to_lli(lli_src, |
376 | false); | 441 | dev_addr, |
442 | sg_dma_len(current_sg), | ||
443 | lcsp->lcsp1, src_data_width, | ||
444 | dst_data_width, | ||
445 | false); | ||
377 | } | 446 | } |
378 | } | 447 | } |
379 | return total_size; | 448 | return total_size; |
380 | } | 449 | } |
381 | 450 | ||
451 | struct d40_log_lli *d40_log_buf_to_lli(struct d40_log_lli *lli_sg, | ||
452 | dma_addr_t addr, | ||
453 | int size, | ||
454 | u32 lcsp13, /* src or dst*/ | ||
455 | u32 data_width1, | ||
456 | u32 data_width2, | ||
457 | bool addr_inc) | ||
458 | { | ||
459 | struct d40_log_lli *lli = lli_sg; | ||
460 | int size_rest = size; | ||
461 | int size_seg = 0; | ||
462 | |||
463 | do { | ||
464 | size_seg = d40_seg_size(size_rest, data_width1, data_width2); | ||
465 | size_rest -= size_seg; | ||
466 | |||
467 | d40_log_fill_lli(lli, | ||
468 | addr, | ||
469 | size_seg, | ||
470 | lcsp13, data_width1, | ||
471 | addr_inc); | ||
472 | if (addr_inc) | ||
473 | addr += size_seg; | ||
474 | lli++; | ||
475 | } while (size_rest); | ||
476 | |||
477 | return lli; | ||
478 | } | ||
479 | |||
382 | int d40_log_sg_to_lli(struct scatterlist *sg, | 480 | int d40_log_sg_to_lli(struct scatterlist *sg, |
383 | int sg_len, | 481 | int sg_len, |
384 | struct d40_log_lli *lli_sg, | 482 | struct d40_log_lli *lli_sg, |
385 | u32 lcsp13, /* src or dst*/ | 483 | u32 lcsp13, /* src or dst*/ |
386 | u32 data_width) | 484 | u32 data_width1, u32 data_width2) |
387 | { | 485 | { |
388 | int total_size = 0; | 486 | int total_size = 0; |
389 | struct scatterlist *current_sg = sg; | 487 | struct scatterlist *current_sg = sg; |
390 | int i; | 488 | int i; |
489 | struct d40_log_lli *lli = lli_sg; | ||
391 | 490 | ||
392 | for_each_sg(sg, current_sg, sg_len, i) { | 491 | for_each_sg(sg, current_sg, sg_len, i) { |
393 | total_size += sg_dma_len(current_sg); | 492 | total_size += sg_dma_len(current_sg); |
394 | 493 | lli = d40_log_buf_to_lli(lli, | |
395 | d40_log_fill_lli(&lli_sg[i], | 494 | sg_phys(current_sg), |
396 | sg_phys(current_sg), | 495 | sg_dma_len(current_sg), |
397 | sg_dma_len(current_sg), | 496 | lcsp13, |
398 | lcsp13, data_width, | 497 | data_width1, data_width2, true); |
399 | true); | ||
400 | } | 498 | } |
401 | return total_size; | 499 | return total_size; |
402 | } | 500 | } |