#define AXI_DMAC_REG_STATUS            0x430
 #define AXI_DMAC_REG_CURRENT_SRC_ADDR  0x434
 #define AXI_DMAC_REG_CURRENT_DEST_ADDR 0x438
+#define AXI_DMAC_REG_PARTIAL_XFER_LEN  0x44c
+#define AXI_DMAC_REG_PARTIAL_XFER_ID   0x450
 
 #define AXI_DMAC_CTRL_ENABLE           BIT(0)
 #define AXI_DMAC_CTRL_PAUSE            BIT(1)
 
 #define AXI_DMAC_FLAG_CYCLIC           BIT(0)
 #define AXI_DMAC_FLAG_LAST             BIT(1)
+#define AXI_DMAC_FLAG_PARTIAL_REPORT   BIT(2)
+
+#define AXI_DMAC_FLAG_PARTIAL_XFER_DONE BIT(31)
 
 /* The maximum ID allocated by the hardware is 31 */
 #define AXI_DMAC_SG_UNUSED 32U
        unsigned int dest_stride;
        unsigned int src_stride;
        unsigned int id;
+       unsigned int partial_len;
        bool schedule_when_free;
 };
 
        unsigned int address_align_mask;
        unsigned int length_align_mask;
 
+       bool hw_partial_xfer;
        bool hw_cyclic;
        bool hw_2d;
 };
                desc->num_sgs == 1)
                flags |= AXI_DMAC_FLAG_CYCLIC;
 
+       if (chan->hw_partial_xfer)
+               flags |= AXI_DMAC_FLAG_PARTIAL_REPORT;
+
        axi_dmac_write(dmac, AXI_DMAC_REG_X_LENGTH, sg->x_len - 1);
        axi_dmac_write(dmac, AXI_DMAC_REG_Y_LENGTH, sg->y_len - 1);
        axi_dmac_write(dmac, AXI_DMAC_REG_FLAGS, flags);
                struct axi_dmac_desc, vdesc.node);
 }
 
+static inline unsigned int axi_dmac_total_sg_bytes(struct axi_dmac_chan *chan,
+       struct axi_dmac_sg *sg)
+{
+       if (chan->hw_2d)
+               return sg->x_len * sg->y_len;
+       else
+               return sg->x_len;
+}
+
+static void axi_dmac_dequeue_partial_xfers(struct axi_dmac_chan *chan)
+{
+       struct axi_dmac *dmac = chan_to_axi_dmac(chan);
+       struct axi_dmac_desc *desc;
+       struct axi_dmac_sg *sg;
+       u32 xfer_done, len, id, i;
+       bool found_sg;
+
+       do {
+               len = axi_dmac_read(dmac, AXI_DMAC_REG_PARTIAL_XFER_LEN);
+               id  = axi_dmac_read(dmac, AXI_DMAC_REG_PARTIAL_XFER_ID);
+
+               found_sg = false;
+               list_for_each_entry(desc, &chan->active_descs, vdesc.node) {
+                       for (i = 0; i < desc->num_sgs; i++) {
+                               sg = &desc->sg[i];
+                               if (sg->id == AXI_DMAC_SG_UNUSED)
+                                       continue;
+                               if (sg->id == id) {
+                                       sg->partial_len = len;
+                                       found_sg = true;
+                                       break;
+                               }
+                       }
+                       if (found_sg)
+                               break;
+               }
+
+               if (found_sg) {
+                       dev_dbg(dmac->dma_dev.dev,
+                               "Found partial segment id=%u, len=%u\n",
+                               id, len);
+               } else {
+                       dev_warn(dmac->dma_dev.dev,
+                                "Not found partial segment id=%u, len=%u\n",
+                                id, len);
+               }
+
+               /* Check if we have any more partial transfers */
+               xfer_done = axi_dmac_read(dmac, AXI_DMAC_REG_TRANSFER_DONE);
+               xfer_done = !(xfer_done & AXI_DMAC_FLAG_PARTIAL_XFER_DONE);
+
+       } while (!xfer_done);
+}
+
+static void axi_dmac_compute_residue(struct axi_dmac_chan *chan,
+       struct axi_dmac_desc *active)
+{
+       struct dmaengine_result *rslt = &active->vdesc.tx_result;
+       unsigned int start = active->num_completed - 1;
+       struct axi_dmac_sg *sg;
+       unsigned int i, total;
+
+       rslt->result = DMA_TRANS_NOERROR;
+       rslt->residue = 0;
+
+       /*
+        * We get here if the last completed segment is partial, which
+        * means we can compute the residue from that segment onwards
+        */
+       for (i = start; i < active->num_sgs; i++) {
+               sg = &active->sg[i];
+               total = axi_dmac_total_sg_bytes(chan, sg);
+               rslt->residue += (total - sg->partial_len);
+       }
+}
+
 static bool axi_dmac_transfer_done(struct axi_dmac_chan *chan,
        unsigned int completed_transfers)
 {
        if (!active)
                return false;
 
+       if (chan->hw_partial_xfer &&
+           (completed_transfers & AXI_DMAC_FLAG_PARTIAL_XFER_DONE))
+               axi_dmac_dequeue_partial_xfers(chan);
+
        do {
                sg = &active->sg[active->num_completed];
                if (sg->id == AXI_DMAC_SG_UNUSED) /* Not yet submitted */
                        start_next = true;
                }
 
+               if (sg->partial_len)
+                       axi_dmac_compute_residue(chan, active);
+
                if (active->cyclic)
                        vchan_cyclic_callback(&active->vdesc);
 
-               if (active->num_completed == active->num_sgs) {
+               if (active->num_completed == active->num_sgs ||
+                   sg->partial_len) {
                        if (active->cyclic) {
                                active->num_completed = 0; /* wrap around */
                        } else {
                return -ENODEV;
        }
 
+       if (version >= ADI_AXI_PCORE_VER(4, 2, 'a'))
+               chan->hw_partial_xfer = true;
+
        if (version >= ADI_AXI_PCORE_VER(4, 1, 'a')) {
                axi_dmac_write(dmac, AXI_DMAC_REG_X_LENGTH, 0x00);
                chan->length_align_mask =