diff --git a/ti-linux-kernel-6.12.35+git-ti-rt/arch/arm64/boot/dts/ti/k3-am62a-main.dtsi b/ti-linux-kernel-6.12.35+git-ti-rt/arch/arm64/boot/dts/ti/k3-am62a-main.dtsi
index 915516410..cfb6753af 100644
--- a/ti-linux-kernel-6.12.35+git-ti-rt/arch/arm64/boot/dts/ti/k3-am62a-main.dtsi
+++ b/ti-linux-kernel-6.12.35+git-ti-rt/arch/arm64/boot/dts/ti/k3-am62a-main.dtsi
@@ -50,10 +50,9 @@ facet_irqs@0 {
 	};
 
 	csi2rx_err_irqs@0 {
-		status = "okay";
+		status = "disabled";
 		compatible = "generic-uio";
-		// CSI2RX_ERR_IRQ_NUM = 175 （From TI AM62x TRM）
-		// IRQ_NUM - 32 => 175 - 32 => 143
+		/* IRQ now routed to cdns-csi2rx bridge — see cdns_csi2rx0 node. */
 		interrupts = <GIC_SPI 143 IRQ_TYPE_EDGE_RISING>;
 		interrupt-names = "csi2rx_err_irqs";
 		interrupt-parent = <&gic500>;
@@ -1104,6 +1103,14 @@ ti_csi2rx0: ticsi2rx@30102000 {
 		cdns_csi2rx0: csi-bridge@30101000 {
 			compatible = "ti,j721e-csi2rx", "cdns,csi2rx";
 			reg = <0x00 0x30101000 0x00 0x1000>;
+			/*
+			 * CSI2RX_ERR_IRQ_NUM = 175 (AM62x TRM), GIC sees it as
+			 * SPI 143 (= 175 - 32). Routed here (not generic-uio)
+			 * so the cdns driver ISR can push protocol-layer errors
+			 * into the j721e-csi2rx err_tbl for diagnostics.
+			 */
+			interrupts = <GIC_SPI 143 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "error_irq";
 			clocks = <&k3_clks 182 0>, <&k3_clks 182 3>, <&k3_clks 182 0>,
 				<&k3_clks 182 0>, <&k3_clks 182 4>, <&k3_clks 182 4>;
 			clock-names = "sys_clk", "p_clk", "pixel_if0_clk",
diff --git a/ti-linux-kernel-6.12.35+git-ti-rt/drivers/dma/dmaengine.h b/ti-linux-kernel-6.12.35+git-ti-rt/drivers/dma/dmaengine.h
index 53f16d3f0..b719d344d 100644
--- a/ti-linux-kernel-6.12.35+git-ti-rt/drivers/dma/dmaengine.h
+++ b/ti-linux-kernel-6.12.35+git-ti-rt/drivers/dma/dmaengine.h
@@ -134,7 +134,8 @@ dmaengine_desc_callback_invoke(struct dmaengine_desc_callback *cb,
 {
 	struct dmaengine_result dummy_result = {
 		.result = DMA_TRANS_NOERROR,
-		.residue = 0
+		.residue = 0,
+		.status = 0,
 	};
 
 	if (cb->callback_result) {
diff --git a/ti-linux-kernel-6.12.35+git-ti-rt/drivers/dma/ti/k3-udma-common.c b/ti-linux-kernel-6.12.35+git-ti-rt/drivers/dma/ti/k3-udma-common.c
index 415f243aa..2431e263b 100644
--- a/ti-linux-kernel-6.12.35+git-ti-rt/drivers/dma/ti/k3-udma-common.c
+++ b/ti-linux-kernel-6.12.35+git-ti-rt/drivers/dma/ti/k3-udma-common.c
@@ -6,6 +6,7 @@
 
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/bitfield.h>
 #include <linux/delay.h>
 #include <linux/dmaengine.h>
 #include <linux/dma-mapping.h>
@@ -1969,6 +1970,99 @@ void udma_synchronize(struct dma_chan *chan)
 	udma_reset_rings(uc);
 }
 
+static const char * const udma_tr_resp_status_type_str[] = {
+	[CPPI5_TR_RESPONSE_STATUS_NONE]			= "NONE",
+	[CPPI5_TR_RESPONSE_STATUS_TRANSFER_ERR]		= "TRANSFER_ERR",
+	[CPPI5_TR_RESPONSE_STATUS_ABORTED_ERR]		= "ABORTED_ERR",
+	[CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ERR]	= "SUBMISSION_ERR",
+	[CPPI5_TR_RESPONSE_STATUS_UNSUPPORTED_ERR]	= "UNSUPPORTED_ERR",
+	[CPPI5_TR_RESPONSE_STATUS_TRANSFER_EXCEPTION]	= "TRANSFER_EXCEPTION",
+	[CPPI5_TR_RESPONSE_STATUS__TEARDOWN_FLUSH]	= "TEARDOWN_FLUSH",
+};
+
+static const char * const udma_tr_resp_submission_info_str[] = {
+	[CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ICNT0]	= "ICNT0 was 0",
+	[CPPI5_TR_RESPONSE_STATUS_SUBMISSION_FIFO_FULL]	= "channel FIFO full",
+	[CPPI5_TR_RESPONSE_STATUS_SUBMISSION_OWN]	= "channel not owned",
+};
+
+static const char *udma_tr_status_type_name(u8 type)
+{
+	if (type < CPPI5_TR_RESPONSE_STATUS_MAX &&
+	    udma_tr_resp_status_type_str[type])
+		return udma_tr_resp_status_type_str[type];
+	return "unknown";
+}
+
+/*
+ * Dump the full TR descriptor contents when the TR response reports an
+ * error, to aid debugging of e.g. submission errors.
+ */
+static void udma_dump_tr_error(struct udma_chan *uc, struct udma_desc *d)
+{
+	struct udma_hwdesc *hwdesc = &d->hwdesc[0];
+	struct cppi5_desc_hdr_t *hdr = hwdesc->cppi5_desc_vaddr;
+	unsigned int tr_count, tr_size, i;
+
+	if (cppi5_desc_get_type(hwdesc->cppi5_desc_vaddr) !=
+	    CPPI5_INFO0_DESC_TYPE_VAL_TR)
+		return;
+
+	tr_count = (hdr->pkt_info0 & CPPI5_INFO0_TRDESC_LASTIDX_MASK) + 1;
+	tr_size = 16 << ((hdr->pkt_info1 & CPPI5_INFO1_TRDESC_RECSIZE_MASK) >>
+			 CPPI5_INFO1_TRDESC_RECSIZE_SHIFT);
+
+	dev_err(uc->ud->dev,
+		"chan%d TR error: dir=%u pkt_mode=%u state=%u terminated=%u tr_count=%u tr_size=%u residue=%u\n",
+		uc->id, uc->config.dir, uc->config.pkt_mode, uc->state,
+		d->terminated, tr_count, tr_size, d->residue);
+
+	for (i = 0; i < tr_count; i++) {
+		struct cppi5_tr_resp_t *resp = &hwdesc->tr_resp_base[i];
+		u8 type = FIELD_GET(CPPI5_TR_RESPONSE_STATUS_TYPE_MASK,
+				    resp->status);
+		u8 info = FIELD_GET(CPPI5_TR_RESPONSE_STATUS_INFO_MASK,
+				    resp->status);
+		const char *info_str = "";
+
+		if (type == CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ERR &&
+		    info < CPPI5_TR_RESPONSE_STATUS_SUBMISSION_MAX &&
+		    udma_tr_resp_submission_info_str[info])
+			info_str = udma_tr_resp_submission_info_str[info];
+
+		dev_err(uc->ud->dev,
+			"  resp[%u]: status=0x%02x type=%u(%s) info=%u(%s) cmd_id=0x%02x cfg=0x%02x\n",
+			i, resp->status, type, udma_tr_status_type_name(type),
+			info, info_str, resp->cmd_id, resp->flags);
+	}
+
+	for (i = 0; i < tr_count; i++) {
+		struct cppi5_tr_type0_t *tr =
+			(void *)((u8 *)hwdesc->tr_req_base + i * tr_size);
+
+		if (tr_size == sizeof(struct cppi5_tr_type15_t)) {
+			struct cppi5_tr_type15_t *tr15 = (void *)tr;
+
+			dev_err(uc->ud->dev,
+				"  tr[%u]: type=%u flags=0x%08x icnt=%u,%u,%u,%u addr=0x%llx dim=%d,%d,%d daddr=0x%llx dicnt=%u,%u,%u,%u ddim=%d,%d,%d\n",
+				i, (unsigned int)(tr15->flags &
+						  CPPI5_TR_TYPE_MASK),
+				tr15->flags,
+				tr15->icnt0, tr15->icnt1, tr15->icnt2,
+				tr15->icnt3, tr15->addr, tr15->dim1, tr15->dim2,
+				tr15->dim3, tr15->daddr, tr15->dicnt0,
+				tr15->dicnt1, tr15->dicnt2, tr15->dicnt3,
+				tr15->ddim1, tr15->ddim2, tr15->ddim3);
+		} else {
+			dev_err(uc->ud->dev,
+				"  tr[%u]: type=%u flags=0x%08x icnt0=%u addr=0x%llx\n",
+				i, (unsigned int)(tr->flags &
+						  CPPI5_TR_TYPE_MASK), tr->flags,
+				tr->icnt0, tr->addr);
+		}
+	}
+}
+
 void udma_desc_pre_callback(struct virt_dma_chan *vc,
 				   struct virt_dma_desc *vd,
 				   struct dmaengine_result *result)
@@ -1997,14 +2091,18 @@ void udma_desc_pre_callback(struct virt_dma_chan *vc,
 				result->result = DMA_TRANS_ABORTED;
 			else
 				result->result = DMA_TRANS_NOERROR;
+			result->status = 0;
 		} else {
 			result->residue = 0;
 			/* Propagate TR Response errors to the client */
 			status = d->hwdesc[0].tr_resp_base->status;
-			if (status)
+			result->status = status;
+			if (status) {
 				result->result = DMA_TRANS_ABORTED;
-			else
+				udma_dump_tr_error(uc, d);
+			} else {
 				result->result = DMA_TRANS_NOERROR;
+			}
 		}
 	}
 }
diff --git a/ti-linux-kernel-6.12.35+git-ti-rt/drivers/media/platform/cadence/cdns-csi2rx.c b/ti-linux-kernel-6.12.35+git-ti-rt/drivers/media/platform/cadence/cdns-csi2rx.c
index e130c1659..233d5d246 100644
--- a/ti-linux-kernel-6.12.35+git-ti-rt/drivers/media/platform/cadence/cdns-csi2rx.c
+++ b/ti-linux-kernel-6.12.35+git-ti-rt/drivers/media/platform/cadence/cdns-csi2rx.c
@@ -7,10 +7,12 @@
 #define DEBUG
 #define VERBOSE_DEBUG
 
+#include <linux/bitfield.h>
 #include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/io.h>
 #include <linux/iopoll.h>
+#include <linux/ktime.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_graph.h>
@@ -34,6 +36,12 @@
 #define CSI2RX_STATIC_CFG_DLANE_MAP(llane, plane)	((plane) << (16 + (llane) * 4))
 #define CSI2RX_STATIC_CFG_LANES_MASK			GENMASK(11, 8)
 
+#define CSI2RX_ERROR_BYPASS_CFG_REG		0x010
+#define CSI2RX_ERROR_BYPASS_CRC_EN		BIT(0)
+#define CSI2RX_ERROR_BYPASS_ECC_EN		BIT(1)
+#define CSI2RX_ERROR_BYPASS_DATA_ID_EN	BIT(2)
+#define CSI2RX_ERROR_BYPASS_ALL_EN		GENMASK(2, 0)
+
 #define CSI2RX_DPHY_LANE_CTRL_REG		0x40
 #define CSI2RX_DPHY_CL_RST			BIT(16)
 #define CSI2RX_DPHY_DL_RST(i)			BIT((i) + 12)
@@ -119,6 +127,18 @@ static const struct csi2rx_event csi2rx_events[] = {
 
 #define CSI2RX_NUM_EVENTS		ARRAY_SIZE(csi2rx_events)
 
+struct csi2rx_priv;
+typedef void (*cdns_csi2rx_error_subscriber_t)(struct csi2rx_priv *priv,
+					       u32 irq_status,
+					       ktime_t timestamp,
+					       void *data);
+
+/* Public subscriber API (exported for shim drivers) */
+void cdns_csi2rx_set_error_subscriber(struct v4l2_subdev *subdev,
+				      cdns_csi2rx_error_subscriber_t cb,
+				      void *data);
+void cdns_csi2rx_clear_error_subscriber(struct v4l2_subdev *subdev);
+
 struct csi2rx_priv {
 	struct device			*dev;
 	unsigned int			count;
@@ -155,6 +175,15 @@ struct csi2rx_priv {
 	/* Remote source */
 	struct v4l2_subdev		*source_subdev;
 	int				source_pad;
+
+	/*
+	 * Optional subscriber for protocol-layer error status. A downstream
+	 * driver (typically the TI CSI2RX shim) may register here to get a
+	 * push on every IRQ with the raw ERROR_IRQS register snapshot, BEFORE
+	 * the status register is cleared. NULL = no subscriber.
+	 */
+	cdns_csi2rx_error_subscriber_t	error_subscriber;
+	void				*error_subscriber_data;
 };
 
 static const struct csi2rx_fmt formats[] = {
@@ -218,9 +247,8 @@ static void csi2rx_configure_error_irq_mask(void __iomem *base,
 	writel(error_irq_mask, base + CSI2RX_ERROR_IRQS_MASK_REG);
 }
 
-static irqreturn_t csi2rx_irq_handler(int irq, void *dev_id)
+static u32 csi2rx_clear_error_irqs(struct csi2rx_priv *csi2rx)
 {
-	struct csi2rx_priv *csi2rx = dev_id;
 	int i;
 	u32 error_status;
 
@@ -231,6 +259,25 @@ static irqreturn_t csi2rx_irq_handler(int irq, void *dev_id)
 			csi2rx->events[i]++;
 
 	writel(error_status, csi2rx->base + CSI2RX_ERROR_IRQS_REG);
+	return error_status;
+}
+
+static irqreturn_t csi2rx_irq_handler(int irq, void *dev_id)
+{
+	struct csi2rx_priv *csi2rx = dev_id;
+	u32 error_status;
+	ktime_t ts;
+
+	/*
+	 * Snapshot ktime BEFORE clearing, so the subscriber sees the true
+	 * hardware time, not the time after software work.
+	 */
+	ts = ktime_get();
+	error_status = csi2rx_clear_error_irqs(csi2rx);
+
+	if (csi2rx->error_subscriber && error_status)
+		csi2rx->error_subscriber(csi2rx, error_status, ts,
+					 csi2rx->error_subscriber_data);
 
 	return IRQ_HANDLED;
 }
@@ -280,6 +327,26 @@ struct csi2rx_priv *v4l2_subdev_to_csi2rx(struct v4l2_subdev *subdev)
 	return container_of(subdev, struct csi2rx_priv, subdev);
 }
 
+void cdns_csi2rx_set_error_subscriber(struct v4l2_subdev *subdev,
+					cdns_csi2rx_error_subscriber_t cb,
+					void *data)
+{
+	struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+
+	csi2rx->error_subscriber = cb;
+	csi2rx->error_subscriber_data = data;
+}
+EXPORT_SYMBOL_GPL(cdns_csi2rx_set_error_subscriber);
+
+void cdns_csi2rx_clear_error_subscriber(struct v4l2_subdev *subdev)
+{
+	struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+
+	csi2rx->error_subscriber = NULL;
+	csi2rx->error_subscriber_data = NULL;
+}
+EXPORT_SYMBOL_GPL(cdns_csi2rx_clear_error_subscriber);
+
 static void csi2rx_reset(struct csi2rx_priv *csi2rx)
 {
 	unsigned int i;
@@ -397,6 +464,13 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
 
 	writel(reg, csi2rx->base + CSI2RX_STATIC_CFG_REG);
 
+	// clear error interrupts
+	csi2rx_clear_error_irqs(csi2rx);
+
+	// enable all error bypasses
+	writel(CSI2RX_ERROR_BYPASS_ALL_EN,
+		csi2rx->base + CSI2RX_ERROR_BYPASS_CFG_REG);
+
 	/* Enable DPHY clk and data lanes. */
 	if (csi2rx->dphy) {
 		reg = CSI2RX_DPHY_CL_EN | CSI2RX_DPHY_CL_RST;
@@ -1146,20 +1220,20 @@ static int csi2rx_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_cleanup;
 
-	// csi2rx->error_irq = platform_get_irq_byname_optional(pdev, "error_irq");
-
-	// if (csi2rx->error_irq < 0) {
-	// 	dev_dbg(csi2rx->dev, "Optional interrupt not defined, proceeding without it\n");
-	// } else {
-	// 	ret = devm_request_irq(csi2rx->dev, csi2rx->error_irq,
-	// 			       csi2rx_irq_handler, 0,
-	// 			       dev_name(&pdev->dev), csi2rx);
-	// 	if (ret) {
-	// 		dev_err(csi2rx->dev,
-	// 			"Unable to request interrupt: %d\n", ret);
-	// 		goto err_cleanup;
-	// 	}
-	// }
+	csi2rx->error_irq = platform_get_irq_byname_optional(pdev, "error_irq");
+
+	if (csi2rx->error_irq < 0) {
+		dev_dbg(csi2rx->dev, "Optional interrupt not defined, proceeding without it\n");
+	} else {
+		ret = devm_request_irq(csi2rx->dev, csi2rx->error_irq,
+				       csi2rx_irq_handler, 0,
+				       dev_name(&pdev->dev), csi2rx);
+		if (ret) {
+			dev_err(csi2rx->dev,
+				"Unable to request interrupt: %d\n", ret);
+			goto err_cleanup;
+		}
+	}
 
 	ret = v4l2_subdev_init_finalize(&csi2rx->subdev);
 	if (ret)
diff --git a/ti-linux-kernel-6.12.35+git-ti-rt/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/ti-linux-kernel-6.12.35+git-ti-rt/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index 595e5d974..004aac7ad 100644
--- a/ti-linux-kernel-6.12.35+git-ti-rt/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/ti-linux-kernel-6.12.35+git-ti-rt/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -12,7 +12,10 @@
 
 #include <linux/bitfield.h>
 #include <linux/dmaengine.h>
+#include <linux/dma/ti-cppi5.h>
 #include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
@@ -46,6 +49,40 @@
 #define SHIM_PSI_CFG0_SRC_TAG		GENMASK(15, 0)
 #define SHIM_PSI_CFG0_DST_TAG		GENMASK(31, 16)
 
+/*
+ * Mirrors of Cadence CSI-2 RX core registers (cdns-csi2rx.c).
+ * Duplicated here so we can snapshot them from the TI shim driver
+ * on error without crossing driver boundaries at runtime.
+ */
+#define CDNS_CSI2RX_DEVICE_CFG		0x000
+#define CDNS_CSI2RX_STATIC_CFG		0x008
+#define CDNS_CSI2RX_ERROR_IRQS		0x028
+#define CDNS_CSI2RX_ERROR_IRQS_MASK	0x02C
+#define CDNS_CSI2RX_DPHY_LANE_CTRL	0x040
+#define CDNS_CSI2RX_STREAM_CTRL(n)	((((n) + 1) * 0x100) + 0x000)
+#define CDNS_CSI2RX_STREAM_STATUS(n)	((((n) + 1) * 0x100) + 0x004)
+#define CDNS_CSI2RX_STREAM_DATA_CFG(n)	((((n) + 1) * 0x100) + 0x008)
+#define CDNS_CSI2RX_STREAM_CFG(n)	((((n) + 1) * 0x100) + 0x00c)
+
+/*
+ * Indices into csi2rx_err_entry::regs[] — names live side-by-side in
+ * csi2rx_err_reg_names[] for sysfs rendering.
+ */
+enum ti_csi2rx_err_reg_idx {
+	REG_SHIM_CNTL = 0,
+	REG_SHIM_DMACNTX,		/* offset computed from ctx->idx */
+	REG_SHIM_PSI_CFG0,		/* offset computed from ctx->idx */
+	REG_CDNS_STATIC_CFG,
+	REG_CDNS_ERROR_IRQS,
+	REG_CDNS_ERROR_IRQS_MASK,
+	REG_CDNS_DPHY_LANE_CTRL,
+	REG_CDNS_STREAM_CTRL,		/* for stream ctx->stream */
+	REG_CDNS_STREAM_STATUS,
+	REG_CDNS_STREAM_CFG,
+	REG_CDNS_STREAM_DATA_CFG,
+	CSI2RX_ERR_N_REGS
+};
+
 #define TI_CSI2RX_MAX_PIX_PER_CLK	4
 #define PSIL_WORD_SIZE_BYTES		16
 #define TI_CSI2RX_MAX_CTX		32
@@ -67,6 +104,37 @@
 #define DRAIN_BUFFER_SIZE		SZ_32K
 #define DRAIN_TIMEOUT_MS		250
 
+#define CSI2RX_ERR_TBL_SIZE		64
+
+/* Error-record kind discriminator */
+enum ti_csi2rx_err_kind {
+	CSI2RX_ERR_KIND_FRAME,	/* DMA TR error from a normal frame buffer */
+	CSI2RX_ERR_KIND_DRAIN,	/* DMA TR error from the drain DMA path */
+	CSI2RX_ERR_KIND_HW_IRQ,	/* Protocol-layer error from cdns CSI-2 RX ISR */
+};
+
+/* DMA TR error record for debug */
+struct csi2rx_err_entry {
+	s64 timestamp_ns;	/* monotonic clock when callback fired */
+	s64 delta_t1_ns;	/* interval from previous error callback (ns) */
+	s64 delta_t2_ns;	/* interval from two error callbacks ago (ns) */
+	u64 addr;		/* DMA buffer address used for this TR */
+	u32 size_bytes;		/* DMA transfer size for this TR */
+	u32 sequence;		/* frame sequence at error time */
+	u32 stream;		/* CSI stream index (used to pick stream regs) */
+	u32 hw_irq_status;	/* ERROR_IRQS snapshot (only for KIND_HW_IRQ) */
+	u8  kind;		/* enum ti_csi2rx_err_kind */
+	u8  ctx_idx;		/* CSI DMA context index */
+	u8  status;		/* TR Response status byte */
+	u8  status_type;	/* decoded TR Response status type */
+	u8  status_info;	/* decoded TR Response status info */
+	u8  result;		/* dmaengine_tx_result value */
+	u8  dma_state;		/* enum ti_csi2rx_dma_state */
+	u8  n_regs;		/* number of valid entries in regs[] */
+	u32 residue;		/* untransferred bytes */
+	u32 regs[CSI2RX_ERR_N_REGS];	/* snapshot of shim + cdns core regs */
+};
+
 struct ti_csi2rx_fmt {
 	u32				fourcc;	/* Four character code. */
 	u32				code;	/* Mbus code. */
@@ -121,6 +189,13 @@ struct ti_csi2rx_ctx {
 struct ti_csi2rx_dev {
 	struct device			*dev;
 	void __iomem			*shim;
+	/*
+	 * Optional handle to the Cadence CSI-2 RX core registers. Used only
+	 * for read-only diagnostics on error (snapshot of ERROR_IRQS, DPHY
+	 * state, stream status). May be NULL if the cdns node cannot be
+	 * mapped (i.e. on platforms without a sibling cdns,csi2rx).
+	 */
+	void __iomem			*cdns;
 	struct mutex			mutex; /* To serialize ioctls. */
 	unsigned int			enable_count;
 	bool					enable_multi_stream;
@@ -142,6 +217,16 @@ struct ti_csi2rx_dev {
 		size_t			len;
 	} drain;
 	struct completion drain_complete;
+
+	/* DMA TR error/interrupt debug table */
+	struct csi2rx_err_entry	err_tbl[CSI2RX_ERR_TBL_SIZE];
+	u32			err_tbl_head;
+	u32			err_tbl_count;
+	spinlock_t		err_tbl_lock;
+	s64			last_irq_ns;	/* timestamp of last interrupt */
+	s64			prev_irq_ns;	/* timestamp of 2nd-to-last interrupt */
+	s64			last_hw_irq_ns;	/* separate chain for hw-irq kind */
+	s64			prev_hw_irq_ns;
 };
 
 static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
@@ -334,7 +419,17 @@ static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
 extern int cdns_csi2rx_negotiate_ppc(struct v4l2_subdev *subdev,
 				     unsigned int pad, u8 *ppc);
 
-/* Forward declaration needed by ti_csi2rx_dma_callback. */
+struct csi2rx_priv;
+typedef void (*cdns_csi2rx_error_subscriber_t)(struct csi2rx_priv *priv,
+					       u32 irq_status,
+					       ktime_t timestamp,
+					       void *data);
+extern void cdns_csi2rx_set_error_subscriber(struct v4l2_subdev *subdev,
+					     cdns_csi2rx_error_subscriber_t cb,
+					     void *data);
+extern void cdns_csi2rx_clear_error_subscriber(struct v4l2_subdev *subdev);
+
+/* Forward declarations needed by ti_csi2rx_dma_callback_result. */
 static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
 			       struct ti_csi2rx_buffer *buf);
 
@@ -342,6 +437,151 @@ static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
 static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx);
 static int ti_csi2rx_dma_submit_pending(struct ti_csi2rx_ctx *ctx);
 
+static void ti_csi2rx_snapshot_regs(struct ti_csi2rx_dev *csi, u32 ctx_idx,
+				    u32 stream_idx, u32 *regs)
+{
+	regs[REG_SHIM_CNTL]           = readl(csi->shim + SHIM_CNTL);
+	regs[REG_SHIM_DMACNTX]        = readl(csi->shim + SHIM_DMACNTX(ctx_idx));
+	regs[REG_SHIM_PSI_CFG0]       = readl(csi->shim + SHIM_PSI_CFG0(ctx_idx));
+
+	if (csi->cdns) {
+		regs[REG_CDNS_STATIC_CFG]      = readl(csi->cdns + CDNS_CSI2RX_STATIC_CFG);
+		regs[REG_CDNS_ERROR_IRQS]      = readl(csi->cdns + CDNS_CSI2RX_ERROR_IRQS);
+		regs[REG_CDNS_ERROR_IRQS_MASK] = readl(csi->cdns + CDNS_CSI2RX_ERROR_IRQS_MASK);
+		regs[REG_CDNS_DPHY_LANE_CTRL]  = readl(csi->cdns + CDNS_CSI2RX_DPHY_LANE_CTRL);
+		regs[REG_CDNS_STREAM_CTRL]     = readl(csi->cdns + CDNS_CSI2RX_STREAM_CTRL(stream_idx));
+		regs[REG_CDNS_STREAM_STATUS]   = readl(csi->cdns + CDNS_CSI2RX_STREAM_STATUS(stream_idx));
+		regs[REG_CDNS_STREAM_CFG]      = readl(csi->cdns + CDNS_CSI2RX_STREAM_CFG(stream_idx));
+		regs[REG_CDNS_STREAM_DATA_CFG] = readl(csi->cdns + CDNS_CSI2RX_STREAM_DATA_CFG(stream_idx));
+	} else {
+		regs[REG_CDNS_STATIC_CFG]      = 0xffffffff;
+		regs[REG_CDNS_ERROR_IRQS]      = 0xffffffff;
+		regs[REG_CDNS_ERROR_IRQS_MASK] = 0xffffffff;
+		regs[REG_CDNS_DPHY_LANE_CTRL]  = 0xffffffff;
+		regs[REG_CDNS_STREAM_CTRL]     = 0xffffffff;
+		regs[REG_CDNS_STREAM_STATUS]   = 0xffffffff;
+		regs[REG_CDNS_STREAM_CFG]      = 0xffffffff;
+		regs[REG_CDNS_STREAM_DATA_CFG] = 0xffffffff;
+	}
+}
+
+/*
+ * Push-callback from the cadence CSI-2 RX ISR. Runs in hard-IRQ context.
+ * Records the protocol-layer error bitmap (which was about to be W1C'd
+ * by the cadence driver) into our shared err_tbl.
+ */
+static void ti_csi2rx_hw_irq_callback(struct csi2rx_priv *priv,
+				      u32 irq_status, ktime_t timestamp,
+				      void *data)
+{
+	struct ti_csi2rx_dev *csi = data;
+	struct csi2rx_err_entry *entry;
+	unsigned long flags;
+	s64 now, dt1, dt2, prev, last;
+
+	now = ktime_to_ns(timestamp);
+
+	spin_lock_irqsave(&csi->err_tbl_lock, flags);
+	prev = csi->prev_hw_irq_ns;
+	last = csi->last_hw_irq_ns;
+	entry = &csi->err_tbl[csi->err_tbl_head];
+	csi->err_tbl_head = (csi->err_tbl_head + 1) % CSI2RX_ERR_TBL_SIZE;
+	if (csi->err_tbl_count < CSI2RX_ERR_TBL_SIZE)
+		csi->err_tbl_count++;
+	csi->prev_hw_irq_ns = last;
+	csi->last_hw_irq_ns = now;
+	dt1 = last ? (now - last) : 0;
+	dt2 = prev ? (last - prev) : 0;
+
+	entry->kind           = CSI2RX_ERR_KIND_HW_IRQ;
+	entry->hw_irq_status  = irq_status;
+	entry->timestamp_ns   = now;
+	entry->delta_t1_ns    = dt1;
+	entry->delta_t2_ns    = dt2;
+	entry->ctx_idx        = 0xff;
+	entry->stream         = 0xff;
+	entry->sequence       = 0;
+	entry->addr           = 0;
+	entry->size_bytes     = 0;
+	entry->status         = 0;
+	entry->status_type    = 0;
+	entry->status_info    = 0;
+	entry->result         = 0;
+	entry->dma_state      = 0;
+	entry->residue        = 0;
+
+	ti_csi2rx_snapshot_regs(csi, 0, 0, entry->regs);
+	entry->n_regs         = CSI2RX_ERR_N_REGS;
+
+	spin_unlock_irqrestore(&csi->err_tbl_lock, flags);
+}
+
+static void ti_csi2rx_record_err(struct ti_csi2rx_ctx *ctx,
+				 const struct dmaengine_result *result,
+				 dma_addr_t addr, u32 size_bytes,
+				 enum ti_csi2rx_err_kind kind)
+{
+	struct ti_csi2rx_dev *csi = ctx->csi;
+	struct csi2rx_err_entry *entry;
+	unsigned long flags;
+	s64 now, dt1, dt2, prev, last;
+	u32 stream;
+
+	if (!result->status)
+		return;
+
+	now = ktime_get_ns();
+	stream = ctx->stream;
+
+	spin_lock_irqsave(&csi->err_tbl_lock, flags);
+	prev = csi->prev_irq_ns;
+	last = csi->last_irq_ns;
+	entry = &csi->err_tbl[csi->err_tbl_head];
+	csi->err_tbl_head = (csi->err_tbl_head + 1) % CSI2RX_ERR_TBL_SIZE;
+	if (csi->err_tbl_count < CSI2RX_ERR_TBL_SIZE)
+		csi->err_tbl_count++;
+	csi->prev_irq_ns = last;
+	csi->last_irq_ns = now;
+	dt1 = last ? (now - last) : 0;
+	dt2 = prev ? (last - prev) : 0;
+
+	entry->kind         = kind;
+	entry->hw_irq_status = 0;
+	entry->timestamp_ns = now;
+	entry->delta_t1_ns  = dt1;
+	entry->delta_t2_ns  = dt2;
+	entry->addr         = addr;
+	entry->size_bytes   = size_bytes;
+	entry->sequence     = ctx->sequence;
+	entry->ctx_idx      = ctx->idx;
+	entry->status       = result->status;
+	entry->status_type  = FIELD_GET(CPPI5_TR_RESPONSE_STATUS_TYPE_MASK,
+					result->status);
+	entry->status_info  = FIELD_GET(CPPI5_TR_RESPONSE_STATUS_INFO_MASK,
+					result->status);
+	entry->result       = result->result;
+	entry->dma_state    = ctx->dma.state;
+	entry->residue      = result->residue;
+	ti_csi2rx_snapshot_regs(csi, ctx->idx, stream, entry->regs);
+	entry->n_regs       = CSI2RX_ERR_N_REGS;
+	spin_unlock_irqrestore(&csi->err_tbl_lock, flags);
+}
+
+/* Decode the status info field for TR submission errors */
+static const char *ti_csi2rx_submission_err_str(u8 info)
+{
+	switch (info) {
+	case CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ICNT0:
+		return "ICNT0 was 0";
+	case CPPI5_TR_RESPONSE_STATUS_SUBMISSION_FIFO_FULL:
+		return "channel FIFO full";
+	case CPPI5_TR_RESPONSE_STATUS_SUBMISSION_OWN:
+		return "channel not owned";
+	default:
+		return "unknown";
+	}
+}
+
 static const struct ti_csi2rx_fmt *find_format_by_fourcc(u32 pixelformat)
 {
 	unsigned int i;
@@ -545,6 +785,13 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
 	if (ret)
 		return ret;
 
+	/*
+	 * Subscribe to protocol-layer errors from the cdns CSI-2 RX core.
+	 * The source subdev here IS the cdns CSI-2 RX bridge.
+	 */
+	cdns_csi2rx_set_error_subscriber(csi->source,
+					 ti_csi2rx_hw_irq_callback, csi);
+
 	/* Create and link video nodes for all DMA contexts */
 	for (i = 0; i < csi->num_ctx; i++) {
 		struct ti_csi2rx_ctx *ctx = &csi->ctx[i];
@@ -741,6 +988,41 @@ static void ti_csi2rx_drain_callback(void *param)
 	spin_unlock_irqrestore(&dma->lock, flags);
 }
 
+/*
+ * Drain transfers use this result-aware callback so that TR errors on the
+ * drain path (which otherwise go unnoticed) are logged, then the normal
+ * drain handling runs unchanged.
+ */
+static void ti_csi2rx_drain_callback_result(void *param,
+					    const struct dmaengine_result *result)
+{
+	struct ti_csi2rx_ctx *ctx = param;
+
+	if (result)
+		ti_csi2rx_record_err(ctx, result, ctx->csi->drain.paddr,
+				     ctx->csi->drain.len, CSI2RX_ERR_KIND_DRAIN);
+
+	if (result &&
+	    (result->result != DMA_TRANS_NOERROR || result->residue)) {
+		u8 type = FIELD_GET(CPPI5_TR_RESPONSE_STATUS_TYPE_MASK,
+				    result->status);
+		u8 info = FIELD_GET(CPPI5_TR_RESPONSE_STATUS_INFO_MASK,
+				    result->status);
+
+		dev_err(ctx->csi->dev,
+			"DMA drain TR error ctx %u: result=%u status=0x%02x (type=%u info=%u%s%s) residue=%u\n",
+			ctx->idx, result->result, result->status,
+			type, info,
+			type == CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ERR ?
+				" " : "",
+			type == CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ERR ?
+				ti_csi2rx_submission_err_str(info) : "",
+			result->residue);
+	}
+
+	ti_csi2rx_drain_callback(param);
+}
+
 /*
  * Drain the stale data left at the PSI-L endpoint.
  *
@@ -767,7 +1049,7 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
 		goto out;
 	}
 
-	desc->callback = ti_csi2rx_drain_callback;
+	desc->callback_result = ti_csi2rx_drain_callback_result;
 	desc->callback_param = ctx;
 
 	cookie = dmaengine_submit(desc);
@@ -802,25 +1084,82 @@ static int ti_csi2rx_dma_submit_pending(struct ti_csi2rx_ctx *ctx)
 	return ret;
 }
 
-static void ti_csi2rx_dma_callback(void *param)
+static void ti_csi2rx_dma_callback_result(void *param,
+					  const struct dmaengine_result *result)
 {
 	struct ti_csi2rx_buffer *buf = param;
 	struct ti_csi2rx_ctx *ctx = buf->ctx;
 	struct ti_csi2rx_dma *dma = &ctx->dma;
+	struct dmaengine_result ok_result = {
+		.result = DMA_TRANS_NOERROR,
+		.residue = 0,
+		.status = 0,
+	};
 	unsigned long flags;
+	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
+	bool drop_buf = false;
+
+	if (!result)
+		result = &ok_result;
+
+	ti_csi2rx_record_err(ctx, result,
+			     vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0),
+			     ctx->v_fmt.fmt.pix.sizeimage, CSI2RX_ERR_KIND_FRAME);
+
+	if (result->result != DMA_TRANS_NOERROR || result->residue) {
+		dma_addr_t addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf,
+								0);
+		u8 type = FIELD_GET(CPPI5_TR_RESPONSE_STATUS_TYPE_MASK,
+				    result->status);
+		u8 info = FIELD_GET(CPPI5_TR_RESPONSE_STATUS_INFO_MASK,
+				    result->status);
+		u32 snap[CSI2RX_ERR_N_REGS];
+
+		ti_csi2rx_snapshot_regs(ctx->csi, ctx->idx, ctx->stream, snap);
+
+		dev_err(ctx->csi->dev,
+			"DMA TR error ctx %u stream %u seq %u: result=%u status=0x%02x (type=%u info=%u%s%s) residue=%u sizeimage=%u addr=%pad dma_state=%u err_irqs=0x%08x dphy_lane_ctrl=0x%08x shim_dmacntx=0x%08x stream_status=0x%08x\n",
+			ctx->idx, ctx->stream, ctx->sequence,
+			result->result, result->status,
+			type, info,
+			type == CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ERR ?
+				" " : "",
+			type == CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ERR ?
+				ti_csi2rx_submission_err_str(info) : "",
+			result->residue,
+			ctx->v_fmt.fmt.pix.sizeimage,
+			&addr, ctx->dma.state,
+			snap[REG_CDNS_ERROR_IRQS],
+			snap[REG_CDNS_DPHY_LANE_CTRL],
+			snap[REG_SHIM_DMACNTX],
+			snap[REG_CDNS_STREAM_STATUS]);
+		ctx->ng_frame_cnt++;
+		if (type == CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ERR) {
+			drop_buf = true;
+			dev_warn(ctx->csi->dev,
+				 "Discard invalid frame for ctx %u seq %u and recycle buffer to DMA queue\n",
+				 ctx->idx, ctx->sequence);
+		} else {
+			state = VB2_BUF_STATE_ERROR;
+		}
+	} else {
+		ctx->ok_frame_cnt++;
+	}
 
-	/*
-	 * TODO: Derive the sequence number from the CSI2RX frame number
-	 * hardware monitor registers.
-	 */
-	buf->vb.vb2_buf.timestamp = ktime_get_ns();
-	buf->vb.sequence = ctx->sequence++;
+	if (!drop_buf) {
+		/*
+		 * TODO: Derive the sequence number from the CSI2RX frame number
+		 * hardware monitor registers.
+		 */
+		buf->vb.vb2_buf.timestamp = ktime_get_ns();
+		buf->vb.sequence = ctx->sequence++;
+	}
 
 	spin_lock_irqsave(&dma->lock, flags);
-
 	WARN_ON(!list_is_first(&buf->list, &dma->submitted));
-	vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
 	list_del(&buf->list);
+	if (drop_buf)
+		list_add(&buf->list, &dma->queue);
 
 	ti_csi2rx_dma_submit_pending(ctx);
 
@@ -830,6 +1169,9 @@ static void ti_csi2rx_dma_callback(void *param)
 				"DMA drain failed on one of the transactions\n");
 	}
 	spin_unlock_irqrestore(&dma->lock, flags);
+
+	if (!drop_buf)
+		vb2_buffer_done(&buf->vb.vb2_buf, state);
 }
 
 static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
@@ -848,7 +1190,7 @@ static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
 	if (!desc)
 		return -EIO;
 
-	desc->callback = ti_csi2rx_dma_callback;
+	desc->callback_result = ti_csi2rx_dma_callback_result;
 	desc->callback_param = buf;
 
 	cookie = dmaengine_submit(desc);
@@ -1013,7 +1355,7 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 		ret = -EIO;
 	spin_unlock_irqrestore(&dma->lock, flags);
 	if (ret)
-		return ret;
+		goto err;
 
 	ret = video_device_pipeline_start(&ctx->vdev, &csi->pipe);
 	if (ret)
@@ -1319,6 +1661,8 @@ static void ti_csi2rx_cleanup_v4l2(struct ti_csi2rx_dev *csi)
 
 static void ti_csi2rx_cleanup_notifier(struct ti_csi2rx_dev *csi)
 {
+	if (csi->source)
+		cdns_csi2rx_clear_error_subscriber(csi->source);
 	v4l2_async_nf_unregister(&csi->notifier);
 	v4l2_async_nf_cleanup(&csi->notifier);
 }
@@ -1736,6 +2080,134 @@ static const struct dev_pm_ops ti_csi2rx_pm_ops = {
 };
 #endif /* CONFIG_PM */
 
+static const char * const csi2rx_err_reg_names[CSI2RX_ERR_N_REGS] = {
+	[REG_SHIM_CNTL]           = "SHIM_CNTL",
+	[REG_SHIM_DMACNTX]        = "SHIM_DMACNTX",
+	[REG_SHIM_PSI_CFG0]       = "SHIM_PSI_CFG0",
+	[REG_CDNS_STATIC_CFG]     = "CDNS_STATIC_CFG",
+	[REG_CDNS_ERROR_IRQS]     = "CDNS_ERROR_IRQS",
+	[REG_CDNS_ERROR_IRQS_MASK]= "CDNS_ERROR_IRQS_MASK",
+	[REG_CDNS_DPHY_LANE_CTRL] = "CDNS_DPHY_LANE_CTRL",
+	[REG_CDNS_STREAM_CTRL]    = "CDNS_STREAM_CTRL",
+	[REG_CDNS_STREAM_STATUS]  = "CDNS_STREAM_STATUS",
+	[REG_CDNS_STREAM_CFG]     = "CDNS_STREAM_CFG",
+	[REG_CDNS_STREAM_DATA_CFG]= "CDNS_STREAM_DATA_CFG",
+};
+
+/*
+ * sysfs: dump the DMA TR error debug table
+ */
+static ssize_t dma_tr_log_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
+	struct csi2rx_err_entry *snapshot;
+	u32 count, head, start, i;
+	unsigned long flags;
+	int len = 0;
+
+	snapshot = kcalloc(CSI2RX_ERR_TBL_SIZE, sizeof(*snapshot), GFP_KERNEL);
+	if (!snapshot)
+		return scnprintf(buf, PAGE_SIZE, "(oom)\n");
+
+	len += scnprintf(buf + len, PAGE_SIZE - len,
+		"Only entries with non-zero TR response status are recorded.\n");
+
+	spin_lock_irqsave(&csi->err_tbl_lock, flags);
+	count = csi->err_tbl_count;
+	head  = csi->err_tbl_head;
+	memcpy(snapshot, csi->err_tbl, CSI2RX_ERR_TBL_SIZE * sizeof(*snapshot));
+	spin_unlock_irqrestore(&csi->err_tbl_lock, flags);
+
+	if (!count) {
+		len += scnprintf(buf + len, PAGE_SIZE - len, "(empty)\n");
+		goto out;
+	}
+
+	start = (count < CSI2RX_ERR_TBL_SIZE) ? 0 : head;
+
+	for (i = 0; i < count && len < PAGE_SIZE - 1024; i++) {
+		u32 idx = (start + i) % CSI2RX_ERR_TBL_SIZE;
+		struct csi2rx_err_entry *e = &snapshot[idx];
+		const char *info_str = "";
+		const char *kind_str;
+		u32 r;
+
+		switch (e->kind) {
+		case CSI2RX_ERR_KIND_DRAIN:  kind_str = "drain";  break;
+		case CSI2RX_ERR_KIND_HW_IRQ:  kind_str = "hw_irq"; break;
+		default:			  kind_str = "frame";  break;
+		}
+
+		if (e->status_type == CPPI5_TR_RESPONSE_STATUS_SUBMISSION_ERR)
+			info_str = ti_csi2rx_submission_err_str(e->status_info);
+
+		len += scnprintf(buf + len, PAGE_SIZE - len,
+			"[%u] kind=%s ctx=%u stream=%u seq=%u ts=%lld dt1=%lld dt2=%lld\n",
+			i, kind_str,
+			e->ctx_idx, e->stream, e->sequence,
+			e->timestamp_ns,
+			e->delta_t1_ns,
+			e->delta_t2_ns);
+
+		if (e->kind == CSI2RX_ERR_KIND_HW_IRQ) {
+			len += scnprintf(buf + len, PAGE_SIZE - len,
+				"    hw_irq_status=0x%08x\n", e->hw_irq_status);
+		} else {
+			len += scnprintf(buf + len, PAGE_SIZE - len,
+				"    status=0x%02x type=%u info=%u(%s) result=%u residue=%u addr=0x%llx bytes=%u dma_state=%u\n",
+				e->status, e->status_type, e->status_info, info_str,
+				e->result, e->residue, e->addr, e->size_bytes,
+				e->dma_state);
+		}
+		if (e->n_regs) {
+			len += scnprintf(buf + len, PAGE_SIZE - len, "    regs:");
+			for (r = 0; r < e->n_regs && r < CSI2RX_ERR_N_REGS; r++)
+				len += scnprintf(buf + len, PAGE_SIZE - len,
+						 " %s=0x%08x",
+						 csi2rx_err_reg_names[r],
+						 e->regs[r]);
+			len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
+		}
+	}
+
+out:
+	kfree(snapshot);
+	return len;
+}
+static DEVICE_ATTR_RO(dma_tr_log);
+
+static ssize_t dma_tr_log_clear_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&csi->err_tbl_lock, flags);
+	csi->err_tbl_head  = 0;
+	csi->err_tbl_count = 0;
+	csi->last_irq_ns   = 0;
+	csi->prev_irq_ns   = 0;
+	spin_unlock_irqrestore(&csi->err_tbl_lock, flags);
+
+	return count;
+}
+static DEVICE_ATTR_WO(dma_tr_log_clear);
+
+static struct attribute *ti_csi2rx_attrs[] = {
+	&dev_attr_dma_tr_log.attr,
+	&dev_attr_dma_tr_log_clear.attr,
+	NULL,
+};
+static const struct attribute_group ti_csi2rx_attr_group = {
+	.attrs = ti_csi2rx_attrs,
+};
+static const struct attribute_group *ti_csi2rx_attr_groups[] = {
+	&ti_csi2rx_attr_group,
+	NULL,
+};
+
 static int ti_csi2rx_probe(struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
@@ -1755,6 +2227,28 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	/*
+	 * Try to map the sibling Cadence CSI-2 RX core registers for error
+	 * diagnostics. Failure here is non-fatal — we just skip core reg
+	 * snapshots and keep going with shim-only dumps.
+	 */
+	{
+		struct device_node *cdns_np;
+
+		cdns_np = of_find_compatible_node(NULL, NULL, "cdns,csi2rx");
+		if (cdns_np) {
+			csi->cdns = of_iomap(cdns_np, 0);
+			if (!csi->cdns)
+				dev_warn(csi->dev,
+					 "Failed to map cdns,csi2rx core for diagnostics\n");
+			else
+				dev_info(csi->dev,
+					 "Mapped cdns,csi2rx core at %pOF for error diagnostics\n",
+					 cdns_np);
+			of_node_put(cdns_np);
+		}
+	}
+
 	csi->drain.len = DRAIN_BUFFER_SIZE;
 	csi->drain.vaddr = dma_alloc_coherent(csi->dev, csi->drain.len,
 					      &csi->drain.paddr,
@@ -1779,6 +2273,13 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
 	}
 
 	mutex_init(&csi->mutex);
+	spin_lock_init(&csi->err_tbl_lock);
+
+	ret = devm_device_add_group(csi->dev, ti_csi2rx_attr_groups[0]);
+	if (ret) {
+		dev_err(csi->dev, "Failed to add sysfs group: %d\n", ret);
+		goto err_v4l2;
+	}
 
 	ret = ti_csi2rx_v4l2_init(csi);
 	if (ret)
@@ -1826,6 +2327,8 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
 	mutex_destroy(&csi->mutex);
 	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
 			  csi->drain.paddr);
+	if (csi->cdns)
+		iounmap(csi->cdns);
 	return ret;
 }
 
@@ -1843,6 +2346,8 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
 	mutex_destroy(&csi->mutex);
 	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
 			  csi->drain.paddr);
+	if (csi->cdns)
+		iounmap(csi->cdns);
 
 	pm_runtime_disable(&pdev->dev);
 	pm_runtime_set_suspended(&pdev->dev);
diff --git a/ti-linux-kernel-6.12.35+git-ti-rt/include/linux/dmaengine.h b/ti-linux-kernel-6.12.35+git-ti-rt/include/linux/dmaengine.h
index b137fdb56..38d49455c 100644
--- a/ti-linux-kernel-6.12.35+git-ti-rt/include/linux/dmaengine.h
+++ b/ti-linux-kernel-6.12.35+git-ti-rt/include/linux/dmaengine.h
@@ -552,6 +552,7 @@ enum dmaengine_tx_result {
 struct dmaengine_result {
 	enum dmaengine_tx_result result;
 	u32 residue;
+	u8 status;		/* DMA controller-specific status byte */
 };
 
 typedef void (*dma_async_tx_callback_result)(void *dma_async_param,
