Skip to content

Commit e97dc74

Browse files
pcercueivinodkoul
authored andcommitted
dmaengine: axi-dmac: Add support for scatter-gather transfers
Implement support for scatter-gather transfers. Build a chain of hardware descriptors, each one corresponding to a segment of the transfer, and linked to the next one. The hardware will transfer the chain and only fire interrupts when the whole chain has been transferred. Support for scatter-gather is automatically enabled when the driver detects that the hardware supports it, by writing then reading the AXI_DMAC_REG_SG_ADDRESS register. If not available, the driver will fall back to standard DMA transfers. Signed-off-by: Paul Cercueil <paul@crapouillou.net> Link: https://lore.kernel.org/r/20231215131313.23840-4-paul@crapouillou.net Signed-off-by: Vinod Koul <vkoul@kernel.org>
1 parent 3f8fd25 commit e97dc74

1 file changed

Lines changed: 93 additions & 42 deletions

File tree

drivers/dma/dma-axi-dmac.c

Lines changed: 93 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,13 @@
8181
#define AXI_DMAC_REG_CURRENT_DEST_ADDR 0x438
8282
#define AXI_DMAC_REG_PARTIAL_XFER_LEN 0x44c
8383
#define AXI_DMAC_REG_PARTIAL_XFER_ID 0x450
84+
#define AXI_DMAC_REG_CURRENT_SG_ID 0x454
85+
#define AXI_DMAC_REG_SG_ADDRESS 0x47c
86+
#define AXI_DMAC_REG_SG_ADDRESS_HIGH 0x4bc
8487

8588
#define AXI_DMAC_CTRL_ENABLE BIT(0)
8689
#define AXI_DMAC_CTRL_PAUSE BIT(1)
90+
#define AXI_DMAC_CTRL_ENABLE_SG BIT(2)
8791

8892
#define AXI_DMAC_IRQ_SOT BIT(0)
8993
#define AXI_DMAC_IRQ_EOT BIT(1)
@@ -97,12 +101,16 @@
97101
/* The maximum ID allocated by the hardware is 31 */
98102
#define AXI_DMAC_SG_UNUSED 32U
99103

104+
/* Flags for axi_dmac_hw_desc.flags */
105+
#define AXI_DMAC_HW_FLAG_LAST BIT(0)
106+
#define AXI_DMAC_HW_FLAG_IRQ BIT(1)
107+
100108
struct axi_dmac_hw_desc {
101109
u32 flags;
102110
u32 id;
103111
u64 dest_addr;
104112
u64 src_addr;
105-
u64 __unused;
113+
u64 next_sg_addr;
106114
u32 y_len;
107115
u32 x_len;
108116
u32 src_stride;
@@ -150,6 +158,7 @@ struct axi_dmac_chan {
150158
bool hw_partial_xfer;
151159
bool hw_cyclic;
152160
bool hw_2d;
161+
bool hw_sg;
153162
};
154163

155164
struct axi_dmac {
@@ -224,9 +233,11 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan)
224233
unsigned int flags = 0;
225234
unsigned int val;
226235

227-
val = axi_dmac_read(dmac, AXI_DMAC_REG_START_TRANSFER);
228-
if (val) /* Queue is full, wait for the next SOT IRQ */
229-
return;
236+
if (!chan->hw_sg) {
237+
val = axi_dmac_read(dmac, AXI_DMAC_REG_START_TRANSFER);
238+
if (val) /* Queue is full, wait for the next SOT IRQ */
239+
return;
240+
}
230241

231242
desc = chan->next_desc;
232243

@@ -245,9 +256,10 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan)
245256
return;
246257
}
247258

248-
desc->num_submitted++;
249-
if (desc->num_submitted == desc->num_sgs ||
250-
desc->have_partial_xfer) {
259+
if (chan->hw_sg) {
260+
chan->next_desc = NULL;
261+
} else if (++desc->num_submitted == desc->num_sgs ||
262+
desc->have_partial_xfer) {
251263
if (desc->cyclic)
252264
desc->num_submitted = 0; /* Start again */
253265
else
@@ -259,14 +271,16 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan)
259271

260272
sg->hw->id = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_ID);
261273

262-
if (axi_dmac_dest_is_mem(chan)) {
263-
axi_dmac_write(dmac, AXI_DMAC_REG_DEST_ADDRESS, sg->hw->dest_addr);
264-
axi_dmac_write(dmac, AXI_DMAC_REG_DEST_STRIDE, sg->hw->dst_stride);
265-
}
274+
if (!chan->hw_sg) {
275+
if (axi_dmac_dest_is_mem(chan)) {
276+
axi_dmac_write(dmac, AXI_DMAC_REG_DEST_ADDRESS, sg->hw->dest_addr);
277+
axi_dmac_write(dmac, AXI_DMAC_REG_DEST_STRIDE, sg->hw->dst_stride);
278+
}
266279

267-
if (axi_dmac_src_is_mem(chan)) {
268-
axi_dmac_write(dmac, AXI_DMAC_REG_SRC_ADDRESS, sg->hw->src_addr);
269-
axi_dmac_write(dmac, AXI_DMAC_REG_SRC_STRIDE, sg->hw->src_stride);
280+
if (axi_dmac_src_is_mem(chan)) {
281+
axi_dmac_write(dmac, AXI_DMAC_REG_SRC_ADDRESS, sg->hw->src_addr);
282+
axi_dmac_write(dmac, AXI_DMAC_REG_SRC_STRIDE, sg->hw->src_stride);
283+
}
270284
}
271285

272286
/*
@@ -281,8 +295,14 @@ static void axi_dmac_start_transfer(struct axi_dmac_chan *chan)
281295
if (chan->hw_partial_xfer)
282296
flags |= AXI_DMAC_FLAG_PARTIAL_REPORT;
283297

284-
axi_dmac_write(dmac, AXI_DMAC_REG_X_LENGTH, sg->hw->x_len);
285-
axi_dmac_write(dmac, AXI_DMAC_REG_Y_LENGTH, sg->hw->y_len);
298+
if (chan->hw_sg) {
299+
axi_dmac_write(dmac, AXI_DMAC_REG_SG_ADDRESS, (u32)sg->hw_phys);
300+
axi_dmac_write(dmac, AXI_DMAC_REG_SG_ADDRESS_HIGH,
301+
(u64)sg->hw_phys >> 32);
302+
} else {
303+
axi_dmac_write(dmac, AXI_DMAC_REG_X_LENGTH, sg->hw->x_len);
304+
axi_dmac_write(dmac, AXI_DMAC_REG_Y_LENGTH, sg->hw->y_len);
305+
}
286306
axi_dmac_write(dmac, AXI_DMAC_REG_FLAGS, flags);
287307
axi_dmac_write(dmac, AXI_DMAC_REG_START_TRANSFER, 1);
288308
}
@@ -359,6 +379,9 @@ static void axi_dmac_compute_residue(struct axi_dmac_chan *chan,
359379
rslt->result = DMA_TRANS_NOERROR;
360380
rslt->residue = 0;
361381

382+
if (chan->hw_sg)
383+
return;
384+
362385
/*
363386
* We get here if the last completed segment is partial, which
364387
* means we can compute the residue from that segment onwards
@@ -385,36 +408,46 @@ static bool axi_dmac_transfer_done(struct axi_dmac_chan *chan,
385408
(completed_transfers & AXI_DMAC_FLAG_PARTIAL_XFER_DONE))
386409
axi_dmac_dequeue_partial_xfers(chan);
387410

388-
do {
389-
sg = &active->sg[active->num_completed];
390-
if (sg->hw->id == AXI_DMAC_SG_UNUSED) /* Not yet submitted */
391-
break;
392-
if (!(BIT(sg->hw->id) & completed_transfers))
393-
break;
394-
active->num_completed++;
395-
sg->hw->id = AXI_DMAC_SG_UNUSED;
396-
if (sg->schedule_when_free) {
397-
sg->schedule_when_free = false;
398-
start_next = true;
411+
if (chan->hw_sg) {
412+
if (active->cyclic) {
413+
vchan_cyclic_callback(&active->vdesc);
414+
} else {
415+
list_del(&active->vdesc.node);
416+
vchan_cookie_complete(&active->vdesc);
417+
active = axi_dmac_active_desc(chan);
399418
}
419+
} else {
420+
do {
421+
sg = &active->sg[active->num_completed];
422+
if (sg->hw->id == AXI_DMAC_SG_UNUSED) /* Not yet submitted */
423+
break;
424+
if (!(BIT(sg->hw->id) & completed_transfers))
425+
break;
426+
active->num_completed++;
427+
sg->hw->id = AXI_DMAC_SG_UNUSED;
428+
if (sg->schedule_when_free) {
429+
sg->schedule_when_free = false;
430+
start_next = true;
431+
}
400432

401-
if (sg->partial_len)
402-
axi_dmac_compute_residue(chan, active);
433+
if (sg->partial_len)
434+
axi_dmac_compute_residue(chan, active);
403435

404-
if (active->cyclic)
405-
vchan_cyclic_callback(&active->vdesc);
436+
if (active->cyclic)
437+
vchan_cyclic_callback(&active->vdesc);
406438

407-
if (active->num_completed == active->num_sgs ||
408-
sg->partial_len) {
409-
if (active->cyclic) {
410-
active->num_completed = 0; /* wrap around */
411-
} else {
412-
list_del(&active->vdesc.node);
413-
vchan_cookie_complete(&active->vdesc);
414-
active = axi_dmac_active_desc(chan);
439+
if (active->num_completed == active->num_sgs ||
440+
sg->partial_len) {
441+
if (active->cyclic) {
442+
active->num_completed = 0; /* wrap around */
443+
} else {
444+
list_del(&active->vdesc.node);
445+
vchan_cookie_complete(&active->vdesc);
446+
active = axi_dmac_active_desc(chan);
447+
}
415448
}
416-
}
417-
} while (active);
449+
} while (active);
450+
}
418451

419452
return start_next;
420453
}
@@ -478,8 +511,12 @@ static void axi_dmac_issue_pending(struct dma_chan *c)
478511
struct axi_dmac_chan *chan = to_axi_dmac_chan(c);
479512
struct axi_dmac *dmac = chan_to_axi_dmac(chan);
480513
unsigned long flags;
514+
u32 ctrl = AXI_DMAC_CTRL_ENABLE;
515+
516+
if (chan->hw_sg)
517+
ctrl |= AXI_DMAC_CTRL_ENABLE_SG;
481518

482-
axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, AXI_DMAC_CTRL_ENABLE);
519+
axi_dmac_write(dmac, AXI_DMAC_REG_CTRL, ctrl);
483520

484521
spin_lock_irqsave(&chan->vchan.lock, flags);
485522
if (vchan_issue_pending(&chan->vchan))
@@ -516,8 +553,14 @@ axi_dmac_alloc_desc(struct axi_dmac_chan *chan, unsigned int num_sgs)
516553

517554
hws[i].id = AXI_DMAC_SG_UNUSED;
518555
hws[i].flags = 0;
556+
557+
/* Link hardware descriptors */
558+
hws[i].next_sg_addr = hw_phys + (i + 1) * sizeof(*hws);
519559
}
520560

561+
/* The last hardware descriptor will trigger an interrupt */
562+
desc->sg[num_sgs - 1].hw->flags = AXI_DMAC_HW_FLAG_LAST | AXI_DMAC_HW_FLAG_IRQ;
563+
521564
return desc;
522565
}
523566

@@ -753,6 +796,9 @@ static bool axi_dmac_regmap_rdwr(struct device *dev, unsigned int reg)
753796
case AXI_DMAC_REG_CURRENT_DEST_ADDR:
754797
case AXI_DMAC_REG_PARTIAL_XFER_LEN:
755798
case AXI_DMAC_REG_PARTIAL_XFER_ID:
799+
case AXI_DMAC_REG_CURRENT_SG_ID:
800+
case AXI_DMAC_REG_SG_ADDRESS:
801+
case AXI_DMAC_REG_SG_ADDRESS_HIGH:
756802
return true;
757803
default:
758804
return false;
@@ -905,6 +951,10 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)
905951
if (axi_dmac_read(dmac, AXI_DMAC_REG_FLAGS) == AXI_DMAC_FLAG_CYCLIC)
906952
chan->hw_cyclic = true;
907953

954+
axi_dmac_write(dmac, AXI_DMAC_REG_SG_ADDRESS, 0xffffffff);
955+
if (axi_dmac_read(dmac, AXI_DMAC_REG_SG_ADDRESS))
956+
chan->hw_sg = true;
957+
908958
axi_dmac_write(dmac, AXI_DMAC_REG_Y_LENGTH, 1);
909959
if (axi_dmac_read(dmac, AXI_DMAC_REG_Y_LENGTH) == 1)
910960
chan->hw_2d = true;
@@ -1005,6 +1055,7 @@ static int axi_dmac_probe(struct platform_device *pdev)
10051055
dma_dev->dst_addr_widths = BIT(dmac->chan.dest_width);
10061056
dma_dev->directions = BIT(dmac->chan.direction);
10071057
dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
1058+
dma_dev->max_sg_burst = 31; /* 31 SGs maximum in one burst */
10081059
INIT_LIST_HEAD(&dma_dev->channels);
10091060

10101061
dmac->chan.vchan.desc_free = axi_dmac_desc_free;

0 commit comments

Comments
 (0)