| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Driver for the Analog Devices AXI-DMAC core |
|---|
| 3 | 4 | * |
|---|
| 4 | | - * Copyright 2013-2015 Analog Devices Inc. |
|---|
| 5 | + * Copyright 2013-2019 Analog Devices Inc. |
|---|
| 5 | 6 | * Author: Lars-Peter Clausen <lars@metafoo.de> |
|---|
| 6 | | - * |
|---|
| 7 | | - * Licensed under the GPL-2. |
|---|
| 8 | 7 | */ |
|---|
| 9 | 8 | |
|---|
| 9 | +#include <linux/bitfield.h> |
|---|
| 10 | 10 | #include <linux/clk.h> |
|---|
| 11 | 11 | #include <linux/device.h> |
|---|
| 12 | 12 | #include <linux/dma-mapping.h> |
|---|
| .. | .. |
|---|
| 19 | 19 | #include <linux/of.h> |
|---|
| 20 | 20 | #include <linux/of_dma.h> |
|---|
| 21 | 21 | #include <linux/platform_device.h> |
|---|
| 22 | +#include <linux/regmap.h> |
|---|
| 22 | 23 | #include <linux/slab.h> |
|---|
| 24 | +#include <linux/fpga/adi-axi-common.h> |
|---|
| 23 | 25 | |
|---|
| 24 | 26 | #include <dt-bindings/dma/axi-dmac.h> |
|---|
| 25 | 27 | |
|---|
| .. | .. |
|---|
| 44 | 46 | * there is no address than can or needs to be configured for the device side. |
|---|
| 45 | 47 | */ |
|---|
| 46 | 48 | |
|---|
| 49 | +#define AXI_DMAC_REG_INTERFACE_DESC 0x10 |
|---|
| 50 | +#define AXI_DMAC_DMA_SRC_TYPE_MSK GENMASK(13, 12) |
|---|
| 51 | +#define AXI_DMAC_DMA_SRC_TYPE_GET(x) FIELD_GET(AXI_DMAC_DMA_SRC_TYPE_MSK, x) |
|---|
| 52 | +#define AXI_DMAC_DMA_SRC_WIDTH_MSK GENMASK(11, 8) |
|---|
| 53 | +#define AXI_DMAC_DMA_SRC_WIDTH_GET(x) FIELD_GET(AXI_DMAC_DMA_SRC_WIDTH_MSK, x) |
|---|
| 54 | +#define AXI_DMAC_DMA_DST_TYPE_MSK GENMASK(5, 4) |
|---|
| 55 | +#define AXI_DMAC_DMA_DST_TYPE_GET(x) FIELD_GET(AXI_DMAC_DMA_DST_TYPE_MSK, x) |
|---|
| 56 | +#define AXI_DMAC_DMA_DST_WIDTH_MSK GENMASK(3, 0) |
|---|
| 57 | +#define AXI_DMAC_DMA_DST_WIDTH_GET(x) FIELD_GET(AXI_DMAC_DMA_DST_WIDTH_MSK, x) |
|---|
| 58 | + |
|---|
| 47 | 59 | #define AXI_DMAC_REG_IRQ_MASK 0x80 |
|---|
| 48 | 60 | #define AXI_DMAC_REG_IRQ_PENDING 0x84 |
|---|
| 49 | 61 | #define AXI_DMAC_REG_IRQ_SOURCE 0x88 |
|---|
| .. | .. |
|---|
| 63 | 75 | #define AXI_DMAC_REG_STATUS 0x430 |
|---|
| 64 | 76 | #define AXI_DMAC_REG_CURRENT_SRC_ADDR 0x434 |
|---|
| 65 | 77 | #define AXI_DMAC_REG_CURRENT_DEST_ADDR 0x438 |
|---|
| 78 | +#define AXI_DMAC_REG_PARTIAL_XFER_LEN 0x44c |
|---|
| 79 | +#define AXI_DMAC_REG_PARTIAL_XFER_ID 0x450 |
|---|
| 66 | 80 | |
|---|
| 67 | 81 | #define AXI_DMAC_CTRL_ENABLE BIT(0) |
|---|
| 68 | 82 | #define AXI_DMAC_CTRL_PAUSE BIT(1) |
|---|
| .. | .. |
|---|
| 71 | 85 | #define AXI_DMAC_IRQ_EOT BIT(1) |
|---|
| 72 | 86 | |
|---|
| 73 | 87 | #define AXI_DMAC_FLAG_CYCLIC BIT(0) |
|---|
| 88 | +#define AXI_DMAC_FLAG_LAST BIT(1) |
|---|
| 89 | +#define AXI_DMAC_FLAG_PARTIAL_REPORT BIT(2) |
|---|
| 90 | + |
|---|
| 91 | +#define AXI_DMAC_FLAG_PARTIAL_XFER_DONE BIT(31) |
|---|
| 74 | 92 | |
|---|
| 75 | 93 | /* The maximum ID allocated by the hardware is 31 */ |
|---|
| 76 | 94 | #define AXI_DMAC_SG_UNUSED 32U |
|---|
| .. | .. |
|---|
| 83 | 101 | unsigned int dest_stride; |
|---|
| 84 | 102 | unsigned int src_stride; |
|---|
| 85 | 103 | unsigned int id; |
|---|
| 104 | + unsigned int partial_len; |
|---|
| 86 | 105 | bool schedule_when_free; |
|---|
| 87 | 106 | }; |
|---|
| 88 | 107 | |
|---|
| 89 | 108 | struct axi_dmac_desc { |
|---|
| 90 | 109 | struct virt_dma_desc vdesc; |
|---|
| 91 | 110 | bool cyclic; |
|---|
| 111 | + bool have_partial_xfer; |
|---|
| 92 | 112 | |
|---|
| 93 | 113 | unsigned int num_submitted; |
|---|
| 94 | 114 | unsigned int num_completed; |
|---|
| .. | .. |
|---|
| 109 | 129 | unsigned int dest_type; |
|---|
| 110 | 130 | |
|---|
| 111 | 131 | unsigned int max_length; |
|---|
| 112 | | - unsigned int align_mask; |
|---|
| 132 | + unsigned int address_align_mask; |
|---|
| 133 | + unsigned int length_align_mask; |
|---|
| 113 | 134 | |
|---|
| 135 | + bool hw_partial_xfer; |
|---|
| 114 | 136 | bool hw_cyclic; |
|---|
| 115 | 137 | bool hw_2d; |
|---|
| 116 | 138 | }; |
|---|
| .. | .. |
|---|
| 123 | 145 | |
|---|
| 124 | 146 | struct dma_device dma_dev; |
|---|
| 125 | 147 | struct axi_dmac_chan chan; |
|---|
| 126 | | - |
|---|
| 127 | | - struct device_dma_parameters dma_parms; |
|---|
| 128 | 148 | }; |
|---|
| 129 | 149 | |
|---|
| 130 | 150 | static struct axi_dmac *chan_to_axi_dmac(struct axi_dmac_chan *chan) |
|---|
| .. | .. |
|---|
| 166 | 186 | |
|---|
| 167 | 187 | static bool axi_dmac_check_len(struct axi_dmac_chan *chan, unsigned int len) |
|---|
| 168 | 188 | { |
|---|
| 169 | | - if (len == 0 || len > chan->max_length) |
|---|
| 189 | + if (len == 0) |
|---|
| 170 | 190 | return false; |
|---|
| 171 | | - if ((len & chan->align_mask) != 0) /* Not aligned */ |
|---|
| 191 | + if ((len & chan->length_align_mask) != 0) /* Not aligned */ |
|---|
| 172 | 192 | return false; |
|---|
| 173 | 193 | return true; |
|---|
| 174 | 194 | } |
|---|
| 175 | 195 | |
|---|
| 176 | 196 | static bool axi_dmac_check_addr(struct axi_dmac_chan *chan, dma_addr_t addr) |
|---|
| 177 | 197 | { |
|---|
| 178 | | - if ((addr & chan->align_mask) != 0) /* Not aligned */ |
|---|
| 198 | + if ((addr & chan->address_align_mask) != 0) /* Not aligned */ |
|---|
| 179 | 199 | return false; |
|---|
| 180 | 200 | return true; |
|---|
| 181 | 201 | } |
|---|
| .. | .. |
|---|
| 211 | 231 | } |
|---|
| 212 | 232 | |
|---|
| 213 | 233 | desc->num_submitted++; |
|---|
| 214 | | - if (desc->num_submitted == desc->num_sgs) { |
|---|
| 234 | + if (desc->num_submitted == desc->num_sgs || |
|---|
| 235 | + desc->have_partial_xfer) { |
|---|
| 215 | 236 | if (desc->cyclic) |
|---|
| 216 | 237 | desc->num_submitted = 0; /* Start again */ |
|---|
| 217 | 238 | else |
|---|
| 218 | 239 | chan->next_desc = NULL; |
|---|
| 240 | + flags |= AXI_DMAC_FLAG_LAST; |
|---|
| 219 | 241 | } else { |
|---|
| 220 | 242 | chan->next_desc = desc; |
|---|
| 221 | 243 | } |
|---|
| .. | .. |
|---|
| 241 | 263 | desc->num_sgs == 1) |
|---|
| 242 | 264 | flags |= AXI_DMAC_FLAG_CYCLIC; |
|---|
| 243 | 265 | |
|---|
| 266 | + if (chan->hw_partial_xfer) |
|---|
| 267 | + flags |= AXI_DMAC_FLAG_PARTIAL_REPORT; |
|---|
| 268 | + |
|---|
| 244 | 269 | axi_dmac_write(dmac, AXI_DMAC_REG_X_LENGTH, sg->x_len - 1); |
|---|
| 245 | 270 | axi_dmac_write(dmac, AXI_DMAC_REG_Y_LENGTH, sg->y_len - 1); |
|---|
| 246 | 271 | axi_dmac_write(dmac, AXI_DMAC_REG_FLAGS, flags); |
|---|
| .. | .. |
|---|
| 253 | 278 | struct axi_dmac_desc, vdesc.node); |
|---|
| 254 | 279 | } |
|---|
| 255 | 280 | |
|---|
| 281 | +static inline unsigned int axi_dmac_total_sg_bytes(struct axi_dmac_chan *chan, |
|---|
| 282 | + struct axi_dmac_sg *sg) |
|---|
| 283 | +{ |
|---|
| 284 | + if (chan->hw_2d) |
|---|
| 285 | + return sg->x_len * sg->y_len; |
|---|
| 286 | + else |
|---|
| 287 | + return sg->x_len; |
|---|
| 288 | +} |
|---|
| 289 | + |
|---|
| 290 | +static void axi_dmac_dequeue_partial_xfers(struct axi_dmac_chan *chan) |
|---|
| 291 | +{ |
|---|
| 292 | + struct axi_dmac *dmac = chan_to_axi_dmac(chan); |
|---|
| 293 | + struct axi_dmac_desc *desc; |
|---|
| 294 | + struct axi_dmac_sg *sg; |
|---|
| 295 | + u32 xfer_done, len, id, i; |
|---|
| 296 | + bool found_sg; |
|---|
| 297 | + |
|---|
| 298 | + do { |
|---|
| 299 | + len = axi_dmac_read(dmac, AXI_DMAC_REG_PARTIAL_XFER_LEN); |
|---|
| 300 | + id = axi_dmac_read(dmac, AXI_DMAC_REG_PARTIAL_XFER_ID); |
|---|
| 301 | + |
|---|
| 302 | + found_sg = false; |
|---|
| 303 | + list_for_each_entry(desc, &chan->active_descs, vdesc.node) { |
|---|
| 304 | + for (i = 0; i < desc->num_sgs; i++) { |
|---|
| 305 | + sg = &desc->sg[i]; |
|---|
| 306 | + if (sg->id == AXI_DMAC_SG_UNUSED) |
|---|
| 307 | + continue; |
|---|
| 308 | + if (sg->id == id) { |
|---|
| 309 | + desc->have_partial_xfer = true; |
|---|
| 310 | + sg->partial_len = len; |
|---|
| 311 | + found_sg = true; |
|---|
| 312 | + break; |
|---|
| 313 | + } |
|---|
| 314 | + } |
|---|
| 315 | + if (found_sg) |
|---|
| 316 | + break; |
|---|
| 317 | + } |
|---|
| 318 | + |
|---|
| 319 | + if (found_sg) { |
|---|
| 320 | + dev_dbg(dmac->dma_dev.dev, |
|---|
| 321 | + "Found partial segment id=%u, len=%u\n", |
|---|
| 322 | + id, len); |
|---|
| 323 | + } else { |
|---|
| 324 | + dev_warn(dmac->dma_dev.dev, |
|---|
| 325 | + "Not found partial segment id=%u, len=%u\n", |
|---|
| 326 | + id, len); |
|---|
| 327 | + } |
|---|
| 328 | + |
|---|
| 329 | + /* Check if we have any more partial transfers */ |
|---|
| 330 | + xfer_done = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_DONE); |
|---|
| 331 | + xfer_done = !(xfer_done & AXI_DMAC_FLAG_PARTIAL_XFER_DONE); |
|---|
| 332 | + |
|---|
| 333 | + } while (!xfer_done); |
|---|
| 334 | +} |
|---|
| 335 | + |
|---|
| 336 | +static void axi_dmac_compute_residue(struct axi_dmac_chan *chan, |
|---|
| 337 | + struct axi_dmac_desc *active) |
|---|
| 338 | +{ |
|---|
| 339 | + struct dmaengine_result *rslt = &active->vdesc.tx_result; |
|---|
| 340 | + unsigned int start = active->num_completed - 1; |
|---|
| 341 | + struct axi_dmac_sg *sg; |
|---|
| 342 | + unsigned int i, total; |
|---|
| 343 | + |
|---|
| 344 | + rslt->result = DMA_TRANS_NOERROR; |
|---|
| 345 | + rslt->residue = 0; |
|---|
| 346 | + |
|---|
| 347 | + /* |
|---|
| 348 | + * We get here if the last completed segment is partial, which |
|---|
| 349 | + * means we can compute the residue from that segment onwards |
|---|
| 350 | + */ |
|---|
| 351 | + for (i = start; i < active->num_sgs; i++) { |
|---|
| 352 | + sg = &active->sg[i]; |
|---|
| 353 | + total = axi_dmac_total_sg_bytes(chan, sg); |
|---|
| 354 | + rslt->residue += (total - sg->partial_len); |
|---|
| 355 | + } |
|---|
| 356 | +} |
|---|
| 357 | + |
|---|
| 256 | 358 | static bool axi_dmac_transfer_done(struct axi_dmac_chan *chan, |
|---|
| 257 | 359 | unsigned int completed_transfers) |
|---|
| 258 | 360 | { |
|---|
| .. | .. |
|---|
| 263 | 365 | active = axi_dmac_active_desc(chan); |
|---|
| 264 | 366 | if (!active) |
|---|
| 265 | 367 | return false; |
|---|
| 368 | + |
|---|
| 369 | + if (chan->hw_partial_xfer && |
|---|
| 370 | + (completed_transfers & AXI_DMAC_FLAG_PARTIAL_XFER_DONE)) |
|---|
| 371 | + axi_dmac_dequeue_partial_xfers(chan); |
|---|
| 266 | 372 | |
|---|
| 267 | 373 | do { |
|---|
| 268 | 374 | sg = &active->sg[active->num_completed]; |
|---|
| .. | .. |
|---|
| 277 | 383 | start_next = true; |
|---|
| 278 | 384 | } |
|---|
| 279 | 385 | |
|---|
| 386 | + if (sg->partial_len) |
|---|
| 387 | + axi_dmac_compute_residue(chan, active); |
|---|
| 388 | + |
|---|
| 280 | 389 | if (active->cyclic) |
|---|
| 281 | 390 | vchan_cyclic_callback(&active->vdesc); |
|---|
| 282 | 391 | |
|---|
| 283 | | - if (active->num_completed == active->num_sgs) { |
|---|
| 392 | + if (active->num_completed == active->num_sgs || |
|---|
| 393 | + sg->partial_len) { |
|---|
| 284 | 394 | if (active->cyclic) { |
|---|
| 285 | 395 | active->num_completed = 0; /* wrap around */ |
|---|
| 286 | 396 | } else { |
|---|
| .. | .. |
|---|
| 367 | 477 | struct axi_dmac_desc *desc; |
|---|
| 368 | 478 | unsigned int i; |
|---|
| 369 | 479 | |
|---|
| 370 | | - desc = kzalloc(sizeof(struct axi_dmac_desc) + |
|---|
| 371 | | - sizeof(struct axi_dmac_sg) * num_sgs, GFP_NOWAIT); |
|---|
| 480 | + desc = kzalloc(struct_size(desc, sg, num_sgs), GFP_NOWAIT); |
|---|
| 372 | 481 | if (!desc) |
|---|
| 373 | 482 | return NULL; |
|---|
| 374 | 483 | |
|---|
| .. | .. |
|---|
| 380 | 489 | return desc; |
|---|
| 381 | 490 | } |
|---|
| 382 | 491 | |
|---|
| 492 | +static struct axi_dmac_sg *axi_dmac_fill_linear_sg(struct axi_dmac_chan *chan, |
|---|
| 493 | + enum dma_transfer_direction direction, dma_addr_t addr, |
|---|
| 494 | + unsigned int num_periods, unsigned int period_len, |
|---|
| 495 | + struct axi_dmac_sg *sg) |
|---|
| 496 | +{ |
|---|
| 497 | + unsigned int num_segments, i; |
|---|
| 498 | + unsigned int segment_size; |
|---|
| 499 | + unsigned int len; |
|---|
| 500 | + |
|---|
| 501 | + /* Split into multiple equally sized segments if necessary */ |
|---|
| 502 | + num_segments = DIV_ROUND_UP(period_len, chan->max_length); |
|---|
| 503 | + segment_size = DIV_ROUND_UP(period_len, num_segments); |
|---|
| 504 | + /* Take care of alignment */ |
|---|
| 505 | + segment_size = ((segment_size - 1) | chan->length_align_mask) + 1; |
|---|
| 506 | + |
|---|
| 507 | + for (i = 0; i < num_periods; i++) { |
|---|
| 508 | + len = period_len; |
|---|
| 509 | + |
|---|
| 510 | + while (len > segment_size) { |
|---|
| 511 | + if (direction == DMA_DEV_TO_MEM) |
|---|
| 512 | + sg->dest_addr = addr; |
|---|
| 513 | + else |
|---|
| 514 | + sg->src_addr = addr; |
|---|
| 515 | + sg->x_len = segment_size; |
|---|
| 516 | + sg->y_len = 1; |
|---|
| 517 | + sg++; |
|---|
| 518 | + addr += segment_size; |
|---|
| 519 | + len -= segment_size; |
|---|
| 520 | + } |
|---|
| 521 | + |
|---|
| 522 | + if (direction == DMA_DEV_TO_MEM) |
|---|
| 523 | + sg->dest_addr = addr; |
|---|
| 524 | + else |
|---|
| 525 | + sg->src_addr = addr; |
|---|
| 526 | + sg->x_len = len; |
|---|
| 527 | + sg->y_len = 1; |
|---|
| 528 | + sg++; |
|---|
| 529 | + addr += len; |
|---|
| 530 | + } |
|---|
| 531 | + |
|---|
| 532 | + return sg; |
|---|
| 533 | +} |
|---|
| 534 | + |
|---|
| 383 | 535 | static struct dma_async_tx_descriptor *axi_dmac_prep_slave_sg( |
|---|
| 384 | 536 | struct dma_chan *c, struct scatterlist *sgl, |
|---|
| 385 | 537 | unsigned int sg_len, enum dma_transfer_direction direction, |
|---|
| .. | .. |
|---|
| 387 | 539 | { |
|---|
| 388 | 540 | struct axi_dmac_chan *chan = to_axi_dmac_chan(c); |
|---|
| 389 | 541 | struct axi_dmac_desc *desc; |
|---|
| 542 | + struct axi_dmac_sg *dsg; |
|---|
| 390 | 543 | struct scatterlist *sg; |
|---|
| 544 | + unsigned int num_sgs; |
|---|
| 391 | 545 | unsigned int i; |
|---|
| 392 | 546 | |
|---|
| 393 | 547 | if (direction != chan->direction) |
|---|
| 394 | 548 | return NULL; |
|---|
| 395 | 549 | |
|---|
| 396 | | - desc = axi_dmac_alloc_desc(sg_len); |
|---|
| 550 | + num_sgs = 0; |
|---|
| 551 | + for_each_sg(sgl, sg, sg_len, i) |
|---|
| 552 | + num_sgs += DIV_ROUND_UP(sg_dma_len(sg), chan->max_length); |
|---|
| 553 | + |
|---|
| 554 | + desc = axi_dmac_alloc_desc(num_sgs); |
|---|
| 397 | 555 | if (!desc) |
|---|
| 398 | 556 | return NULL; |
|---|
| 557 | + |
|---|
| 558 | + dsg = desc->sg; |
|---|
| 399 | 559 | |
|---|
| 400 | 560 | for_each_sg(sgl, sg, sg_len, i) { |
|---|
| 401 | 561 | if (!axi_dmac_check_addr(chan, sg_dma_address(sg)) || |
|---|
| .. | .. |
|---|
| 404 | 564 | return NULL; |
|---|
| 405 | 565 | } |
|---|
| 406 | 566 | |
|---|
| 407 | | - if (direction == DMA_DEV_TO_MEM) |
|---|
| 408 | | - desc->sg[i].dest_addr = sg_dma_address(sg); |
|---|
| 409 | | - else |
|---|
| 410 | | - desc->sg[i].src_addr = sg_dma_address(sg); |
|---|
| 411 | | - desc->sg[i].x_len = sg_dma_len(sg); |
|---|
| 412 | | - desc->sg[i].y_len = 1; |
|---|
| 567 | + dsg = axi_dmac_fill_linear_sg(chan, direction, sg_dma_address(sg), 1, |
|---|
| 568 | + sg_dma_len(sg), dsg); |
|---|
| 413 | 569 | } |
|---|
| 414 | 570 | |
|---|
| 415 | 571 | desc->cyclic = false; |
|---|
| .. | .. |
|---|
| 424 | 580 | { |
|---|
| 425 | 581 | struct axi_dmac_chan *chan = to_axi_dmac_chan(c); |
|---|
| 426 | 582 | struct axi_dmac_desc *desc; |
|---|
| 427 | | - unsigned int num_periods, i; |
|---|
| 583 | + unsigned int num_periods, num_segments; |
|---|
| 428 | 584 | |
|---|
| 429 | 585 | if (direction != chan->direction) |
|---|
| 430 | 586 | return NULL; |
|---|
| .. | .. |
|---|
| 437 | 593 | return NULL; |
|---|
| 438 | 594 | |
|---|
| 439 | 595 | num_periods = buf_len / period_len; |
|---|
| 596 | + num_segments = DIV_ROUND_UP(period_len, chan->max_length); |
|---|
| 440 | 597 | |
|---|
| 441 | | - desc = axi_dmac_alloc_desc(num_periods); |
|---|
| 598 | + desc = axi_dmac_alloc_desc(num_periods * num_segments); |
|---|
| 442 | 599 | if (!desc) |
|---|
| 443 | 600 | return NULL; |
|---|
| 444 | 601 | |
|---|
| 445 | | - for (i = 0; i < num_periods; i++) { |
|---|
| 446 | | - if (direction == DMA_DEV_TO_MEM) |
|---|
| 447 | | - desc->sg[i].dest_addr = buf_addr; |
|---|
| 448 | | - else |
|---|
| 449 | | - desc->sg[i].src_addr = buf_addr; |
|---|
| 450 | | - desc->sg[i].x_len = period_len; |
|---|
| 451 | | - desc->sg[i].y_len = 1; |
|---|
| 452 | | - buf_addr += period_len; |
|---|
| 453 | | - } |
|---|
| 602 | + axi_dmac_fill_linear_sg(chan, direction, buf_addr, num_periods, |
|---|
| 603 | + period_len, desc->sg); |
|---|
| 454 | 604 | |
|---|
| 455 | 605 | desc->cyclic = true; |
|---|
| 456 | 606 | |
|---|
| .. | .. |
|---|
| 522 | 672 | desc->sg[0].y_len = 1; |
|---|
| 523 | 673 | } |
|---|
| 524 | 674 | |
|---|
| 675 | + if (flags & DMA_CYCLIC) |
|---|
| 676 | + desc->cyclic = true; |
|---|
| 677 | + |
|---|
| 525 | 678 | return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); |
|---|
| 526 | 679 | } |
|---|
| 527 | 680 | |
|---|
| .. | .. |
|---|
| 533 | 686 | static void axi_dmac_desc_free(struct virt_dma_desc *vdesc) |
|---|
| 534 | 687 | { |
|---|
| 535 | 688 | kfree(container_of(vdesc, struct axi_dmac_desc, vdesc)); |
|---|
| 689 | +} |
|---|
| 690 | + |
|---|
| 691 | +static bool axi_dmac_regmap_rdwr(struct device *dev, unsigned int reg) |
|---|
| 692 | +{ |
|---|
| 693 | + switch (reg) { |
|---|
| 694 | + case AXI_DMAC_REG_IRQ_MASK: |
|---|
| 695 | + case AXI_DMAC_REG_IRQ_SOURCE: |
|---|
| 696 | + case AXI_DMAC_REG_IRQ_PENDING: |
|---|
| 697 | + case AXI_DMAC_REG_CTRL: |
|---|
| 698 | + case AXI_DMAC_REG_TRANSFER_ID: |
|---|
| 699 | + case AXI_DMAC_REG_START_TRANSFER: |
|---|
| 700 | + case AXI_DMAC_REG_FLAGS: |
|---|
| 701 | + case AXI_DMAC_REG_DEST_ADDRESS: |
|---|
| 702 | + case AXI_DMAC_REG_SRC_ADDRESS: |
|---|
| 703 | + case AXI_DMAC_REG_X_LENGTH: |
|---|
| 704 | + case AXI_DMAC_REG_Y_LENGTH: |
|---|
| 705 | + case AXI_DMAC_REG_DEST_STRIDE: |
|---|
| 706 | + case AXI_DMAC_REG_SRC_STRIDE: |
|---|
| 707 | + case AXI_DMAC_REG_TRANSFER_DONE: |
|---|
| 708 | + case AXI_DMAC_REG_ACTIVE_TRANSFER_ID: |
|---|
| 709 | + case AXI_DMAC_REG_STATUS: |
|---|
| 710 | + case AXI_DMAC_REG_CURRENT_SRC_ADDR: |
|---|
| 711 | + case AXI_DMAC_REG_CURRENT_DEST_ADDR: |
|---|
| 712 | + case AXI_DMAC_REG_PARTIAL_XFER_LEN: |
|---|
| 713 | + case AXI_DMAC_REG_PARTIAL_XFER_ID: |
|---|
| 714 | + return true; |
|---|
| 715 | + default: |
|---|
| 716 | + return false; |
|---|
| 717 | + } |
|---|
| 718 | +} |
|---|
| 719 | + |
|---|
| 720 | +static const struct regmap_config axi_dmac_regmap_config = { |
|---|
| 721 | + .reg_bits = 32, |
|---|
| 722 | + .val_bits = 32, |
|---|
| 723 | + .reg_stride = 4, |
|---|
| 724 | + .max_register = AXI_DMAC_REG_PARTIAL_XFER_ID, |
|---|
| 725 | + .readable_reg = axi_dmac_regmap_rdwr, |
|---|
| 726 | + .writeable_reg = axi_dmac_regmap_rdwr, |
|---|
| 727 | +}; |
|---|
| 728 | + |
|---|
| 729 | +static void axi_dmac_adjust_chan_params(struct axi_dmac_chan *chan) |
|---|
| 730 | +{ |
|---|
| 731 | + chan->address_align_mask = max(chan->dest_width, chan->src_width) - 1; |
|---|
| 732 | + |
|---|
| 733 | + if (axi_dmac_dest_is_mem(chan) && axi_dmac_src_is_mem(chan)) |
|---|
| 734 | + chan->direction = DMA_MEM_TO_MEM; |
|---|
| 735 | + else if (!axi_dmac_dest_is_mem(chan) && axi_dmac_src_is_mem(chan)) |
|---|
| 736 | + chan->direction = DMA_MEM_TO_DEV; |
|---|
| 737 | + else if (axi_dmac_dest_is_mem(chan) && !axi_dmac_src_is_mem(chan)) |
|---|
| 738 | + chan->direction = DMA_DEV_TO_MEM; |
|---|
| 739 | + else |
|---|
| 740 | + chan->direction = DMA_DEV_TO_DEV; |
|---|
| 536 | 741 | } |
|---|
| 537 | 742 | |
|---|
| 538 | 743 | /* |
|---|
| .. | .. |
|---|
| 578 | 783 | return ret; |
|---|
| 579 | 784 | chan->dest_width = val / 8; |
|---|
| 580 | 785 | |
|---|
| 581 | | - ret = of_property_read_u32(of_chan, "adi,length-width", &val); |
|---|
| 582 | | - if (ret) |
|---|
| 583 | | - return ret; |
|---|
| 786 | + axi_dmac_adjust_chan_params(chan); |
|---|
| 584 | 787 | |
|---|
| 585 | | - if (val >= 32) |
|---|
| 586 | | - chan->max_length = UINT_MAX; |
|---|
| 587 | | - else |
|---|
| 588 | | - chan->max_length = (1ULL << val) - 1; |
|---|
| 788 | + return 0; |
|---|
| 789 | +} |
|---|
| 589 | 790 | |
|---|
| 590 | | - chan->align_mask = max(chan->dest_width, chan->src_width) - 1; |
|---|
| 791 | +static int axi_dmac_parse_dt(struct device *dev, struct axi_dmac *dmac) |
|---|
| 792 | +{ |
|---|
| 793 | + struct device_node *of_channels, *of_chan; |
|---|
| 794 | + int ret; |
|---|
| 591 | 795 | |
|---|
| 592 | | - if (axi_dmac_dest_is_mem(chan) && axi_dmac_src_is_mem(chan)) |
|---|
| 593 | | - chan->direction = DMA_MEM_TO_MEM; |
|---|
| 594 | | - else if (!axi_dmac_dest_is_mem(chan) && axi_dmac_src_is_mem(chan)) |
|---|
| 595 | | - chan->direction = DMA_MEM_TO_DEV; |
|---|
| 596 | | - else if (axi_dmac_dest_is_mem(chan) && !axi_dmac_src_is_mem(chan)) |
|---|
| 597 | | - chan->direction = DMA_DEV_TO_MEM; |
|---|
| 598 | | - else |
|---|
| 599 | | - chan->direction = DMA_DEV_TO_DEV; |
|---|
| 796 | + of_channels = of_get_child_by_name(dev->of_node, "adi,channels"); |
|---|
| 797 | + if (of_channels == NULL) |
|---|
| 798 | + return -ENODEV; |
|---|
| 600 | 799 | |
|---|
| 601 | | - chan->hw_cyclic = of_property_read_bool(of_chan, "adi,cyclic"); |
|---|
| 602 | | - chan->hw_2d = of_property_read_bool(of_chan, "adi,2d"); |
|---|
| 800 | + for_each_child_of_node(of_channels, of_chan) { |
|---|
| 801 | + ret = axi_dmac_parse_chan_dt(of_chan, &dmac->chan); |
|---|
| 802 | + if (ret) { |
|---|
| 803 | + of_node_put(of_chan); |
|---|
| 804 | + of_node_put(of_channels); |
|---|
| 805 | + return -EINVAL; |
|---|
| 806 | + } |
|---|
| 807 | + } |
|---|
| 808 | + of_node_put(of_channels); |
|---|
| 809 | + |
|---|
| 810 | + return 0; |
|---|
| 811 | +} |
|---|
| 812 | + |
|---|
| 813 | +static int axi_dmac_read_chan_config(struct device *dev, struct axi_dmac *dmac) |
|---|
| 814 | +{ |
|---|
| 815 | + struct axi_dmac_chan *chan = &dmac->chan; |
|---|
| 816 | + unsigned int val, desc; |
|---|
| 817 | + |
|---|
| 818 | + desc = axi_dmac_read(dmac, AXI_DMAC_REG_INTERFACE_DESC); |
|---|
| 819 | + if (desc == 0) { |
|---|
| 820 | + dev_err(dev, "DMA interface register reads zero\n"); |
|---|
| 821 | + return -EFAULT; |
|---|
| 822 | + } |
|---|
| 823 | + |
|---|
| 824 | + val = AXI_DMAC_DMA_SRC_TYPE_GET(desc); |
|---|
| 825 | + if (val > AXI_DMAC_BUS_TYPE_FIFO) { |
|---|
| 826 | + dev_err(dev, "Invalid source bus type read: %d\n", val); |
|---|
| 827 | + return -EINVAL; |
|---|
| 828 | + } |
|---|
| 829 | + chan->src_type = val; |
|---|
| 830 | + |
|---|
| 831 | + val = AXI_DMAC_DMA_DST_TYPE_GET(desc); |
|---|
| 832 | + if (val > AXI_DMAC_BUS_TYPE_FIFO) { |
|---|
| 833 | + dev_err(dev, "Invalid destination bus type read: %d\n", val); |
|---|
| 834 | + return -EINVAL; |
|---|
| 835 | + } |
|---|
| 836 | + chan->dest_type = val; |
|---|
| 837 | + |
|---|
| 838 | + val = AXI_DMAC_DMA_SRC_WIDTH_GET(desc); |
|---|
| 839 | + if (val == 0) { |
|---|
| 840 | + dev_err(dev, "Source bus width is zero\n"); |
|---|
| 841 | + return -EINVAL; |
|---|
| 842 | + } |
|---|
| 843 | + /* widths are stored in log2 */ |
|---|
| 844 | + chan->src_width = 1 << val; |
|---|
| 845 | + |
|---|
| 846 | + val = AXI_DMAC_DMA_DST_WIDTH_GET(desc); |
|---|
| 847 | + if (val == 0) { |
|---|
| 848 | + dev_err(dev, "Destination bus width is zero\n"); |
|---|
| 849 | + return -EINVAL; |
|---|
| 850 | + } |
|---|
| 851 | + chan->dest_width = 1 << val; |
|---|
| 852 | + |
|---|
| 853 | + axi_dmac_adjust_chan_params(chan); |
|---|
| 854 | + |
|---|
| 855 | + return 0; |
|---|
| 856 | +} |
|---|
| 857 | + |
|---|
| 858 | +static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version) |
|---|
| 859 | +{ |
|---|
| 860 | + struct axi_dmac_chan *chan = &dmac->chan; |
|---|
| 861 | + |
|---|
| 862 | + axi_dmac_write(dmac, AXI_DMAC_REG_FLAGS, AXI_DMAC_FLAG_CYCLIC); |
|---|
| 863 | + if (axi_dmac_read(dmac, AXI_DMAC_REG_FLAGS) == AXI_DMAC_FLAG_CYCLIC) |
|---|
| 864 | + chan->hw_cyclic = true; |
|---|
| 865 | + |
|---|
| 866 | + axi_dmac_write(dmac, AXI_DMAC_REG_Y_LENGTH, 1); |
|---|
| 867 | + if (axi_dmac_read(dmac, AXI_DMAC_REG_Y_LENGTH) == 1) |
|---|
| 868 | + chan->hw_2d = true; |
|---|
| 869 | + |
|---|
| 870 | + axi_dmac_write(dmac, AXI_DMAC_REG_X_LENGTH, 0xffffffff); |
|---|
| 871 | + chan->max_length = axi_dmac_read(dmac, AXI_DMAC_REG_X_LENGTH); |
|---|
| 872 | + if (chan->max_length != UINT_MAX) |
|---|
| 873 | + chan->max_length++; |
|---|
| 874 | + |
|---|
| 875 | + axi_dmac_write(dmac, AXI_DMAC_REG_DEST_ADDRESS, 0xffffffff); |
|---|
| 876 | + if (axi_dmac_read(dmac, AXI_DMAC_REG_DEST_ADDRESS) == 0 && |
|---|
| 877 | + chan->dest_type == AXI_DMAC_BUS_TYPE_AXI_MM) { |
|---|
| 878 | + dev_err(dmac->dma_dev.dev, |
|---|
| 879 | + "Destination memory-mapped interface not supported."); |
|---|
| 880 | + return -ENODEV; |
|---|
| 881 | + } |
|---|
| 882 | + |
|---|
| 883 | + axi_dmac_write(dmac, AXI_DMAC_REG_SRC_ADDRESS, 0xffffffff); |
|---|
| 884 | + if (axi_dmac_read(dmac, AXI_DMAC_REG_SRC_ADDRESS) == 0 && |
|---|
| 885 | + chan->src_type == AXI_DMAC_BUS_TYPE_AXI_MM) { |
|---|
| 886 | + dev_err(dmac->dma_dev.dev, |
|---|
| 887 | + "Source memory-mapped interface not supported."); |
|---|
| 888 | + return -ENODEV; |
|---|
| 889 | + } |
|---|
| 890 | + |
|---|
| 891 | + if (version >= ADI_AXI_PCORE_VER(4, 2, 'a')) |
|---|
| 892 | + chan->hw_partial_xfer = true; |
|---|
| 893 | + |
|---|
| 894 | + if (version >= ADI_AXI_PCORE_VER(4, 1, 'a')) { |
|---|
| 895 | + axi_dmac_write(dmac, AXI_DMAC_REG_X_LENGTH, 0x00); |
|---|
| 896 | + chan->length_align_mask = |
|---|
| 897 | + axi_dmac_read(dmac, AXI_DMAC_REG_X_LENGTH); |
|---|
| 898 | + } else { |
|---|
| 899 | + chan->length_align_mask = chan->address_align_mask; |
|---|
| 900 | + } |
|---|
| 603 | 901 | |
|---|
| 604 | 902 | return 0; |
|---|
| 605 | 903 | } |
|---|
| 606 | 904 | |
|---|
| 607 | 905 | static int axi_dmac_probe(struct platform_device *pdev) |
|---|
| 608 | 906 | { |
|---|
| 609 | | - struct device_node *of_channels, *of_chan; |
|---|
| 610 | 907 | struct dma_device *dma_dev; |
|---|
| 611 | 908 | struct axi_dmac *dmac; |
|---|
| 612 | 909 | struct resource *res; |
|---|
| 910 | + struct regmap *regmap; |
|---|
| 911 | + unsigned int version; |
|---|
| 613 | 912 | int ret; |
|---|
| 614 | 913 | |
|---|
| 615 | 914 | dmac = devm_kzalloc(&pdev->dev, sizeof(*dmac), GFP_KERNEL); |
|---|
| .. | .. |
|---|
| 631 | 930 | if (IS_ERR(dmac->clk)) |
|---|
| 632 | 931 | return PTR_ERR(dmac->clk); |
|---|
| 633 | 932 | |
|---|
| 933 | + ret = clk_prepare_enable(dmac->clk); |
|---|
| 934 | + if (ret < 0) |
|---|
| 935 | + return ret; |
|---|
| 936 | + |
|---|
| 937 | + version = axi_dmac_read(dmac, ADI_AXI_REG_VERSION); |
|---|
| 938 | + |
|---|
| 939 | + if (version >= ADI_AXI_PCORE_VER(4, 3, 'a')) |
|---|
| 940 | + ret = axi_dmac_read_chan_config(&pdev->dev, dmac); |
|---|
| 941 | + else |
|---|
| 942 | + ret = axi_dmac_parse_dt(&pdev->dev, dmac); |
|---|
| 943 | + |
|---|
| 944 | + if (ret < 0) |
|---|
| 945 | + goto err_clk_disable; |
|---|
| 946 | + |
|---|
| 634 | 947 | INIT_LIST_HEAD(&dmac->chan.active_descs); |
|---|
| 635 | 948 | |
|---|
| 636 | | - of_channels = of_get_child_by_name(pdev->dev.of_node, "adi,channels"); |
|---|
| 637 | | - if (of_channels == NULL) |
|---|
| 638 | | - return -ENODEV; |
|---|
| 639 | | - |
|---|
| 640 | | - for_each_child_of_node(of_channels, of_chan) { |
|---|
| 641 | | - ret = axi_dmac_parse_chan_dt(of_chan, &dmac->chan); |
|---|
| 642 | | - if (ret) { |
|---|
| 643 | | - of_node_put(of_chan); |
|---|
| 644 | | - of_node_put(of_channels); |
|---|
| 645 | | - return -EINVAL; |
|---|
| 646 | | - } |
|---|
| 647 | | - } |
|---|
| 648 | | - of_node_put(of_channels); |
|---|
| 649 | | - |
|---|
| 650 | | - pdev->dev.dma_parms = &dmac->dma_parms; |
|---|
| 651 | | - dma_set_max_seg_size(&pdev->dev, dmac->chan.max_length); |
|---|
| 949 | + dma_set_max_seg_size(&pdev->dev, UINT_MAX); |
|---|
| 652 | 950 | |
|---|
| 653 | 951 | dma_dev = &dmac->dma_dev; |
|---|
| 654 | 952 | dma_cap_set(DMA_SLAVE, dma_dev->cap_mask); |
|---|
| 655 | 953 | dma_cap_set(DMA_CYCLIC, dma_dev->cap_mask); |
|---|
| 954 | + dma_cap_set(DMA_INTERLEAVE, dma_dev->cap_mask); |
|---|
| 656 | 955 | dma_dev->device_free_chan_resources = axi_dmac_free_chan_resources; |
|---|
| 657 | 956 | dma_dev->device_tx_status = dma_cookie_status; |
|---|
| 658 | 957 | dma_dev->device_issue_pending = axi_dmac_issue_pending; |
|---|
| .. | .. |
|---|
| 672 | 971 | dmac->chan.vchan.desc_free = axi_dmac_desc_free; |
|---|
| 673 | 972 | vchan_init(&dmac->chan.vchan, dma_dev); |
|---|
| 674 | 973 | |
|---|
| 675 | | - ret = clk_prepare_enable(dmac->clk); |
|---|
| 676 | | - if (ret < 0) |
|---|
| 677 | | - return ret; |
|---|
| 974 | + ret = axi_dmac_detect_caps(dmac, version); |
|---|
| 975 | + if (ret) |
|---|
| 976 | + goto err_clk_disable; |
|---|
| 977 | + |
|---|
| 978 | + dma_dev->copy_align = (dmac->chan.address_align_mask + 1); |
|---|
| 678 | 979 | |
|---|
| 679 | 980 | axi_dmac_write(dmac, AXI_DMAC_REG_IRQ_MASK, 0x00); |
|---|
| 680 | 981 | |
|---|
| .. | .. |
|---|
| 694 | 995 | |
|---|
| 695 | 996 | platform_set_drvdata(pdev, dmac); |
|---|
| 696 | 997 | |
|---|
| 998 | + regmap = devm_regmap_init_mmio(&pdev->dev, dmac->base, |
|---|
| 999 | + &axi_dmac_regmap_config); |
|---|
| 1000 | + if (IS_ERR(regmap)) { |
|---|
| 1001 | + ret = PTR_ERR(regmap); |
|---|
| 1002 | + goto err_free_irq; |
|---|
| 1003 | + } |
|---|
| 1004 | + |
|---|
| 697 | 1005 | return 0; |
|---|
| 698 | 1006 | |
|---|
| 1007 | +err_free_irq: |
|---|
| 1008 | + free_irq(dmac->irq, dmac); |
|---|
| 699 | 1009 | err_unregister_of: |
|---|
| 700 | 1010 | of_dma_controller_free(pdev->dev.of_node); |
|---|
| 701 | 1011 | err_unregister_device: |
|---|