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 = ; 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 = ; + 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 #include +#include #include #include #include @@ -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 #include #include #include #include +#include #include #include #include @@ -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 #include +#include #include +#include +#include #include #include #include @@ -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,