Skip to content

Commit 94683cb

Browse files
committed
bcm2835-dma: fix cyclic DMA setup
The current implementation is broken in several ways: The number of frames was calculated wrong, which results in one frame less than requested. eg when requesting a 96000 bytes buffer split into 4 periods of 24000 bytes the code would allocate 3 frames: the first two with 24000 bytes size and the third one with 48000 bytes. This not only results in the cyclic callback being called at other times than requested but can also lead to frames larger than what the DMA hardware can support. eg when requesting a 192000 bytes buffer split into 4 periods of 48000 bytes each (well within the limit of what the DMA hardware supports) it would allocate 2 48000 byte frames and one 96000 byte frame - the latter larger than what the hardware supports. Frames larger than the hardware limit are the reason for clicking playback and the previous fix of using a smaller limit for cyclic transfers only worked around the underlying bug. Another issue is the way the code tried to handle period sizes larger than what the hardware supports. It results in cyclic callbacks not at the requested period length but in callbacks at the hardware limit boundaries. The simple fix for these issues is to reject requests that can't be handled by the hardware. The maximum period length in bcm2835-i2s has been fixed (set to 64k-4 bytes, the limit of the lite DMA channels) so the cyclic DMA setup code should never be called with period lenght larger than the hardware limit, thus period splitting is not neccessary at all. Signed-off-by: Matthias Reichl <[email protected]>
1 parent e3e5f0a commit 94683cb

File tree

1 file changed

+23
-17
lines changed

1 file changed

+23
-17
lines changed

drivers/dma/bcm2835-dma.c

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,6 @@ struct bcm2835_desc {
144144
*/
145145
#define MAX_LITE_TRANSFER (SZ_64K - 4)
146146

147-
/*
148-
* Transfers larger than 32k cause issues with the bcm2708-i2s driver,
149-
* so limit transfer size to 32k as bcm2708-dmaengine did.
150-
*/
151-
#define MAX_CYCLIC_LITE_TRANSFER SZ_32K
152-
153147
static inline struct bcm2835_dmadev *to_bcm2835_dma_dev(struct dma_device *d)
154148
{
155149
return container_of(d, struct bcm2835_dmadev, ddev);
@@ -385,6 +379,15 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic(
385379
unsigned int frame, max_size;
386380
int i;
387381

382+
if (!buf_len || !period_len)
383+
return NULL;
384+
385+
if (buf_len % period_len) {
386+
dev_err(chan->device->dev,
387+
"Buffer length should be a multiple of period\n");
388+
return NULL;
389+
}
390+
388391
/* Grab configuration */
389392
if (!is_slave_direction(direction)) {
390393
dev_err(chan->device->dev, "%s: bad direction?\n", __func__);
@@ -410,19 +413,26 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic(
410413
return NULL;
411414
}
412415

416+
if (c->ch >= 8) /* LITE channel */
417+
max_size = MAX_LITE_TRANSFER;
418+
else
419+
max_size = MAX_NORMAL_TRANSFER;
420+
421+
if (period_len > max_size) {
422+
dev_err(chan->device->dev,
423+
"Period length %d larger than maximum %d\n",
424+
period_len, max_size);
425+
return NULL;
426+
}
427+
413428
/* Now allocate and setup the descriptor. */
414429
d = kzalloc(sizeof(*d), GFP_NOWAIT);
415430
if (!d)
416431
return NULL;
417432

418433
d->c = c;
419434
d->dir = direction;
420-
if (c->ch >= 8) /* LITE channel */
421-
max_size = MAX_CYCLIC_LITE_TRANSFER;
422-
else
423-
max_size = MAX_NORMAL_TRANSFER;
424-
period_len = min(period_len, max_size);
425-
d->frames = (buf_len - 1) / (period_len + 1);
435+
d->frames = buf_len / period_len;
426436

427437
d->cb_list = kcalloc(d->frames, sizeof(*d->cb_list), GFP_KERNEL);
428438
if (!d->cb_list) {
@@ -470,11 +480,7 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic(
470480
BCM2835_DMA_PER_MAP(c->dreq);
471481

472482
/* Length of a frame */
473-
if (frame != d->frames - 1)
474-
control_block->length = period_len;
475-
else
476-
control_block->length = buf_len - (d->frames - 1) *
477-
period_len;
483+
control_block->length = period_len;
478484
d->size += control_block->length;
479485

480486
/*

0 commit comments

Comments
 (0)