media: rockchip: rkcif: recover from a lost MIPI line
Subsystems (4)
cover media: rockchip: rkcif: recover from a lost MIPI line
The VICAP MIPI DMA ends a frame after the configured number of lines and the sensor frame start does not reset that count. A line dropped on the CSI-2 link therefore leaves every later buffer composed of the tail of one frame and the head of the next, with the Bayer phase flipped when the offset is odd, until the capture is restarted. This was seen on an RV1126 (same VICAP MIPI block as the RK3568) with an IMX415 after a night of streaming, and it can be provoked at will by shortening the D-PHY settle time for a second.
The series makes the capture notice the slip and recover, and makes the two blocks involved report what they see:
- patch 1 masks the MIPI interrupt sources that come out of reset enabled, which otherwise keep the line asserted until the kernel disables the interrupt;
- patch 2 replaces the subdev state mutex taken on the stop path, which the interrupt handler calls, with a spinlock around the shared interrupt registers;
- patch 3 enables and counts the VICAP error interrupts in debugfs;
- patch 4 reads the line counter at every frame start and restarts the stream through the existing stop and start paths once two frames in a row show an offset, as the runtime suspend in between is what resets the counter;
- patch 5 programs the DMA write priority the vendor driver uses;
- patch 6 lets the Synopsys CSI-2 receiver request its err1 and err2 interrupts and count the error classes in debugfs.
Tested on an RV1126 board (vendor DT, rkcif and dw-mipi-csi2rx with local RV1126 compatibles) with the fault injection described in patch 4; the RK3568 and RK3588 register layouts for the error bits and the line counter come from the vendor kernel and are untested here.
Daniel Golle (6):
media: rockchip: rkcif: mask MIPI interrupts when powered
media: rockchip: rkcif: fix the MIPI stop path locking
media: rockchip: rkcif: count the MIPI capture errors
media: rockchip: rkcif: restart the MIPI DMA when it slips
media: rockchip: rkcif: raise the MIPI DMA write priority
media: synopsys: csi2rx: handle the error interrupts
.../rockchip/rkcif/rkcif-capture-mipi.c | 210 ++++++++++++++++-- .../rockchip/rkcif/rkcif-capture-mipi.h | 2 + .../platform/rockchip/rkcif/rkcif-common.h | 23 ++ .../media/platform/rockchip/rkcif/rkcif-dev.c | 8 + .../platform/rockchip/rkcif/rkcif-regs.h | 2 + .../platform/rockchip/rkcif/rkcif-stream.c | 64 ++++++ .../platform/rockchip/rkcif/rkcif-stream.h | 2 + .../media/platform/synopsys/dw-mipi-csi2rx.c | 125 ++++++++++- 8 files changed, 409 insertions(+), 27 deletions(-)
base-commit: 24a19b953e2a3d0ea5a8c4951a0ab8f32720c907
Signature · 1 line
2.56.0
PATCH 1/6 media: rockchip: rkcif: mask MIPI interrupts when powered
The MIPI interrupt enable register comes out of every reset with all 27 sources enabled. Of those only the frame end bits are handled, so the line stays asserted at about a thousand interrupts per second while the block is powered. The kernel then reports the interrupt as unhandled and disables it, after which frame completion only happens from the spurious interrupt poll. The poll can also call the handler while the block is clock gated.
Clear the enable register and the status bits after the block has been reset and clocked, and after the reset in runtime suspend. Treat any status bit that the handler has read and cleared as handled.
--- .../rockchip/rkcif/rkcif-capture-mipi.c | 19 +++++++++++++++++++ .../rockchip/rkcif/rkcif-capture-mipi.h | 2 ++ .../media/platform/rockchip/rkcif/rkcif-dev.c | 3 +++ 3 files changed, 24 insertions(+)
drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +19 -0
@@ -810,6 +810,23 @@ static void rkcif_mipi_set_crop(struct rkcif_stream *stream, u16 left, u16 top)rkcif_mipi_stream_write (stream,RKCIF_MIPI_CROP_START , val); } +void rkcif_mipi_irq_mask (struct rkcif_device *rkcif) +{ +if (!rkcif->match_data->mipi) +return ; + +for (unsigned int i =0 ; i < rkcif->match_data->mipi->mipi_num; i++) { +struct rkcif_interface *interface = + &rkcif->interfaces[RKCIF_MIPI_BASE + i]; + +if (!interface->rkcif) +continue ; + +rkcif_mipi_write (interface,RKCIF_MIPI_INTEN ,0 ); +rkcif_mipi_write (interface,RKCIF_MIPI_INTSTAT , ~0 ); + } +} +irqreturn_t rkcif_mipi_isr (int irq,void *ctx) {struct device *dev = ctx;
@@ -823,6 +840,8 @@ irqreturn_t rkcif_mipi_isr(int irq, void *ctx) intstat =rkcif_mipi_read (interface,RKCIF_MIPI_INTSTAT );rkcif_mipi_write (interface,RKCIF_MIPI_INTSTAT , intstat); +if (intstat) + ret =IRQ_HANDLED ;for (unsigned int j =0 ; j < interface->streams_num; j++) {struct rkcif_stream *stream = &interface->streams[j];
drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h +2 -0
@@ -21,4 +21,6 @@ void rkcif_mipi_unregister(struct rkcif_device *rkcif);irqreturn_t rkcif_mipi_isr (int irq,void *ctx); +void rkcif_mipi_irq_mask (struct rkcif_device *rkcif); +#endif
drivers/media/platform/rockchip/rkcif/rkcif-dev.c +3 -0
@@ -281,6 +281,7 @@ static int rkcif_runtime_suspend(struct device *dev)reset_control_assert (rkcif->reset);udelay (5 );reset_control_deassert (rkcif->reset); +rkcif_mipi_irq_mask (rkcif);clk_bulk_disable_unprepare (rkcif->clks_num, rkcif->clks);
@@ -298,6 +299,8 @@ static int rkcif_runtime_resume(struct device *dev)return ret; } +rkcif_mipi_irq_mask (rkcif); +return 0 ; }
Signature · 1 line
2.56.0
Michael Riesch
maintainerRe: [PATCH 1/6] media: rockchip: rkcif: mask MIPI interrupts when powered
Hi Daniel,
Thanks for the patch.
Quoted 2 lines · Daniel Golle
The MIPI interrupt enable register comes out of every reset with all 27 sources enabled. Of those only the frame end bits are handled, so
That seems highly unusual. How did you analyze this behavior? Feel free to throw any patch over the fence that reproduces/displays it.
Quoted 9 lines
the line stays asserted at about a thousand interrupts per second while the block is powered. The kernel then reports the interrupt as unhandled and disables it, after which frame completion only happens from the spurious interrupt poll. The poll can also call the handler while the block is clock gated.
Clear the enable register and the status bits after the block has been reset and clocked, and after the reset in runtime suspend. Treat any status bit that the handler has read and cleared as handled.
Can we configure the interrupt in rkcif_mipi_start_streaming instead? This feels a bit more natural IMHO.
Best regards,
Michael
Quoted 78 lines
Assisted-by LLMSigned-off-by Daniel Golle <[email protected]>--- .../rockchip/rkcif/rkcif-capture-mipi.c | 19 +++++++++++++++++++ .../rockchip/rkcif/rkcif-capture-mipi.h | 2 ++ .../media/platform/rockchip/rkcif/rkcif-dev.c | 3 +++ 3 files changed, 24 insertions(+)drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +19
-0@@ -810,6 +810,23 @@ static void rkcif_mipi_set_crop(struct rkcif_stream *stream, u16 left, u16 top)rkcif_mipi_stream_write (stream,RKCIF_MIPI_CROP_START , val); } +void rkcif_mipi_irq_mask (struct rkcif_device *rkcif) +{ +if (!rkcif->match_data->mipi) +return ; + +for (unsigned int i =0 ; i < rkcif->match_data->mipi->mipi_num; i++) { +struct rkcif_interface *interface = + &rkcif->interfaces[RKCIF_MIPI_BASE + i]; + +if (!interface->rkcif) +continue ; + +rkcif_mipi_write (interface,RKCIF_MIPI_INTEN ,0 ); +rkcif_mipi_write (interface,RKCIF_MIPI_INTSTAT , ~0 ); + } +} +irqreturn_t rkcif_mipi_isr (int irq,void *ctx) {struct device *dev = ctx;@@ -823,6 +840,8 @@ irqreturn_t rkcif_mipi_isr(int irq, void *ctx) intstat =rkcif_mipi_read (interface,RKCIF_MIPI_INTSTAT );rkcif_mipi_write (interface,RKCIF_MIPI_INTSTAT , intstat); +if (intstat) + ret =IRQ_HANDLED ;for (unsigned int j =0 ; j < interface->streams_num; j++) {struct rkcif_stream *stream = &interface->streams[j];drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h +2
-0@@ -21,4 +21,6 @@ void rkcif_mipi_unregister(struct rkcif_device *rkcif);irqreturn_t rkcif_mipi_isr (int irq,void *ctx); +void rkcif_mipi_irq_mask (struct rkcif_device *rkcif); +#endif drivers/media/platform/rockchip/rkcif/rkcif-dev.c +3
-0@@ -281,6 +281,7 @@ static int rkcif_runtime_suspend(struct device *dev)reset_control_assert (rkcif->reset);udelay (5 );reset_control_deassert (rkcif->reset); +rkcif_mipi_irq_mask (rkcif);clk_bulk_disable_unprepare (rkcif->clks_num, rkcif->clks);@@ -298,6 +299,8 @@ static int rkcif_runtime_resume(struct device *dev)return ret; } +rkcif_mipi_irq_mask (rkcif); +return 0 ; }
PATCH 2/6 media: rockchip: rkcif: fix the MIPI stop path locking
In reply to Daniel Gollerkcif_mipi_stop_streaming() takes the subdev state mutex, and the interrupt handler calls it at the frame end that completes a stop. A mutex taken in hard interrupt context sleeps when it is contended, and CONFIG_DEBUG_ATOMIC_SLEEP reports the call on every stream stop. The state itself is not read on either path; the lock serialises the read-modify-write of the interrupt enable and status registers, which the streams of a MIPI block share.
Guard those registers with a spinlock per interface, taken with interrupts disabled, so that the handler and the process context paths exclude each other, and leave the subdev state alone.
--- .../rockchip/rkcif/rkcif-capture-mipi.c | 24 +++++++------------ .../platform/rockchip/rkcif/rkcif-common.h | 3 +++ 2 files changed, 12 insertions(+), 15 deletions(-)
drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +9 -15
@@ -721,17 +721,14 @@ static int rkcif_mipi_start_streaming(struct rkcif_stream *stream)struct rkcif_interface *interface = stream->interface;const struct rkcif_output_fmt *active_out_fmt;const struct rkcif_mipi_match_data *match_data; -struct v4l2_subdev_state *state;u32 ctrl0 =0 , ctrl1 =0 , int_temp =0 , int_mask =0 , vlw =0 ; +unsigned long flags;u16 height, width; -int ret = -EINVAL ; - - state =v4l2_subdev_lock_and_get_active_state (&interface->sd); active_out_fmt =rkcif_stream_find_output_fmt (stream, false, stream->pix.pixelformat);if (!active_out_fmt) - goto out; +return -EINVAL ; height = stream->pix.height; width = stream->pix.width;
@@ -746,6 +743,7 @@ static int rkcif_mipi_start_streaming(struct rkcif_stream *stream) int_mask |=RKCIF_MIPI_INT_FRAME0_END (stream->id); int_mask |=RKCIF_MIPI_INT_FRAME1_END (stream->id); +spin_lock_irqsave (&interface->irq_lock, flags); int_temp =rkcif_mipi_read (interface,RKCIF_MIPI_INTEN ); int_temp |= int_mask;rkcif_mipi_write (interface,RKCIF_MIPI_INTEN , int_temp);
@@ -753,6 +751,7 @@ static int rkcif_mipi_start_streaming(struct rkcif_stream *stream) int_temp =rkcif_mipi_read (interface,RKCIF_MIPI_INTSTAT ); int_temp &= ~int_mask;rkcif_mipi_write (interface,RKCIF_MIPI_INTSTAT , int_temp); +spin_unlock_irqrestore (&interface->irq_lock, flags);rkcif_mipi_stream_write (stream,RKCIF_MIPI_FRAME0_VLW_Y , vlw);rkcif_mipi_stream_write (stream,RKCIF_MIPI_FRAME1_VLW_Y , vlw);
@@ -769,26 +768,21 @@ static int rkcif_mipi_start_streaming(struct rkcif_stream *stream)*/ rkcif_mipi_write (interface,RKCIF_MIPI_CTRL ,RK3588_MIPI_CTRL_CAP_EN ); - ret =0 ; - -out: -v4l2_subdev_unlock_state (state); -return ret; +return 0 ; }static void rkcif_mipi_stop_streaming (struct rkcif_stream *stream) {struct rkcif_interface *interface = stream->interface; -struct v4l2_subdev_state *state;u32 int_temp =0 , int_mask =0 ; - - state =v4l2_subdev_lock_and_get_active_state (&interface->sd); +unsigned long flags;rkcif_mipi_stream_write (stream,RKCIF_MIPI_CTRL0 ,0 ); int_mask |=RKCIF_MIPI_INT_FRAME0_END (stream->id); int_mask |=RKCIF_MIPI_INT_FRAME1_END (stream->id); +spin_lock_irqsave (&interface->irq_lock, flags); int_temp =rkcif_mipi_read (interface,RKCIF_MIPI_INTEN ); int_temp &= ~int_mask;rkcif_mipi_write (interface,RKCIF_MIPI_INTEN , int_temp);
@@ -796,10 +790,9 @@ static void rkcif_mipi_stop_streaming(struct rkcif_stream *stream) int_temp =rkcif_mipi_read (interface,RKCIF_MIPI_INTSTAT ); int_temp &= ~int_mask;rkcif_mipi_write (interface,RKCIF_MIPI_INTSTAT , int_temp); +spin_unlock_irqrestore (&interface->irq_lock, flags); stream->stopping = false; - -v4l2_subdev_unlock_state (state); }static void rkcif_mipi_set_crop (struct rkcif_stream *stream,u16 left,u16 top)
@@ -881,6 +874,7 @@ int rkcif_mipi_register(struct rkcif_device *rkcif) interface->in_fmts_num =ARRAY_SIZE (mipi_in_fmts); interface->set_crop = rkcif_mipi_set_crop; interface->streams_num =0 ; +spin_lock_init (&interface->irq_lock); ret =rkcif_interface_register (rkcif, interface);if (ret)continue ;
drivers/media/platform/rockchip/rkcif/rkcif-common.h +3 -0
@@ -198,6 +198,9 @@ struct rkcif_interface {struct rkcif_dvp dvp; }; +/* interrupt enable and status are shared by the streams of a block */ +spinlock_t irq_lock; +void (*set_crop)(struct rkcif_stream *stream,u16 left,u16 top); };
Signature · 1 line
2.56.0
PATCH 3/6 media: rockchip: rkcif: count the MIPI capture errors
In reply to Daniel GolleThe VICAP MIPI block flags DMA FIFO overflows, a bandwidth shortage and an RX FIFO overflow in its interrupt status, but the driver never enables these sources and clears the status bits unread, so a capture that loses data does so silently.
Enable the error sources while a stream runs on the interface, count every class in a debugfs directory named after the interface and warn with the raw status under a rate limit. The RK3588 lays out the error bits differently and adds a per-ID size error; both layouts follow the vendor driver.
--- .../rockchip/rkcif/rkcif-capture-mipi.c | 94 +++++++++++++++++++ .../platform/rockchip/rkcif/rkcif-common.h | 12 +++ .../media/platform/rockchip/rkcif/rkcif-dev.c | 5 + .../platform/rockchip/rkcif/rkcif-regs.h | 2 + 4 files changed, 113 insertions(+)
drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +94 -0
@@ -7,6 +7,7 @@* Copyright (C) 2025 Collabora, Ltd. */ +#include <linux/debugfs.h> #include <linux/interrupt.h> #include <media/mipi-csi2.h>
@@ -44,6 +45,35 @@#define RKCIF_MIPI_INT_FRAME0_END (id) BIT(8 + (id) * 2 + 0)#define RKCIF_MIPI_INT_FRAME1_END (id) BIT(8 + (id) * 2 + 1) +#define RKCIF_MIPI_INT_ALL_END GENMASK(15, 8) + +#define RK3568_MIPI_INT_DMA_Y_FIFO_OVERFLOW BIT(16) +#define RK3568_MIPI_INT_DMA_UV_FIFO_OVERFLOW BIT(17) +#define RK3568_MIPI_INT_CONFIG_FIFO_OVERFLOW BIT(18) +#define RK3568_MIPI_INT_BANDWIDTH_LACK BIT(19) +#define RK3568_MIPI_INT_RX_FIFO_OVERFLOW BIT(20) + +#define RK3588_MIPI_INT_DMA_Y_FIFO_OVERFLOW BIT(16) +#define RK3588_MIPI_INT_DMA_UV_FIFO_OVERFLOW BIT(17) +#define RK3588_MIPI_INT_BANDWIDTH_LACK BIT(18) +#define RK3588_MIPI_INT_RX_FIFO_OVERFLOW BIT(19) +#define RK3588_MIPI_INT_SIZE_ERROR GENMASK(27, 24) + +static const struct rkcif_mipi_error rk3568_mipi_errors[] = { + {RK3568_MIPI_INT_DMA_Y_FIFO_OVERFLOW ,"dma_y_fifo_overflow" }, + {RK3568_MIPI_INT_DMA_UV_FIFO_OVERFLOW ,"dma_uv_fifo_overflow" }, + {RK3568_MIPI_INT_CONFIG_FIFO_OVERFLOW ,"config_fifo_overflow" }, + {RK3568_MIPI_INT_BANDWIDTH_LACK ,"bandwidth_lack" }, + {RK3568_MIPI_INT_RX_FIFO_OVERFLOW ,"rx_fifo_overflow" }, +}; + +static const struct rkcif_mipi_error rk3588_mipi_errors[] = { + {RK3588_MIPI_INT_DMA_Y_FIFO_OVERFLOW ,"dma_y_fifo_overflow" }, + {RK3588_MIPI_INT_DMA_UV_FIFO_OVERFLOW ,"dma_uv_fifo_overflow" }, + {RK3588_MIPI_INT_BANDWIDTH_LACK ,"bandwidth_lack" }, + {RK3588_MIPI_INT_RX_FIFO_OVERFLOW ,"rx_fifo_overflow" }, + {RK3588_MIPI_INT_SIZE_ERROR ,"size_error" }, +};static const struct rkcif_output_fmt mipi_out_fmts[] = {/* YUV formats */
@@ -420,10 +450,14 @@ rkcif_rk3568_mipi_ctrl0(struct rkcif_stream *stream,const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data = { .mipi_num =1 , .mipi_ctrl0 = rkcif_rk3568_mipi_ctrl0, + .errors = rk3568_mipi_errors, + .errors_num =ARRAY_SIZE (rk3568_mipi_errors), .regs = { [RKCIF_MIPI_CTRL ] =0x20 , [RKCIF_MIPI_INTEN ] =0xa4 , [RKCIF_MIPI_INTSTAT ] =0xa8 , + [RKCIF_MIPI_LINE_CNT_ID0_1 ] =0xb4 , + [RKCIF_MIPI_LINE_CNT_ID2_3 ] =0xb8 , }, .regs_id = { [RKCIF_ID0 ] = {
@@ -531,10 +565,14 @@ rkcif_rk3588_mipi_ctrl0(struct rkcif_stream *stream,const struct rkcif_mipi_match_data rkcif_rk3588_vicap_mipi_match_data = { .mipi_num =6 , .mipi_ctrl0 = rkcif_rk3588_mipi_ctrl0, + .errors = rk3588_mipi_errors, + .errors_num =ARRAY_SIZE (rk3588_mipi_errors), .regs = { [RKCIF_MIPI_CTRL ] =0x20 , [RKCIF_MIPI_INTEN ] =0x74 , [RKCIF_MIPI_INTSTAT ] =0x78 , + [RKCIF_MIPI_LINE_CNT_ID0_1 ] =0x84 , + [RKCIF_MIPI_LINE_CNT_ID2_3 ] =0x88 , }, .regs_id = { [RKCIF_ID0 ] = {
@@ -699,6 +737,53 @@ rkcif_mipi_stream_read(struct rkcif_stream *stream, unsigned int index)return readl (stream->rkcif->base_addr + addr); } +static u32 rkcif_mipi_error_mask (struct rkcif_interface *interface) +{ +const struct rkcif_mipi_match_data *match_data = + interface->rkcif->match_data->mipi; +unsigned int i; +u32 mask =0 ; + +for (i =0 ; i < match_data->errors_num; i++) + mask |= match_data->errors[i].mask; + +return mask; +} + +static void rkcif_mipi_handle_errors (struct rkcif_interface *interface, +u32 intstat) +{ +const struct rkcif_mipi_match_data *match_data = + interface->rkcif->match_data->mipi; +unsigned int i; + + intstat &=rkcif_mipi_error_mask (interface); +if (!intstat) +return ; + +for (i =0 ; i < match_data->errors_num; i++) +if (intstat & match_data->errors[i].mask) + interface->error_count[i]++; + +dev_warn_ratelimited (interface->rkcif->dev,"%s: capture errors %#x\n" , + interface->sd.name, intstat); +} + +static void rkcif_mipi_debugfs_init (struct rkcif_interface *interface) +{ +const struct rkcif_mipi_match_data *match_data = + interface->rkcif->match_data->mipi; +struct dentry *parent = interface->rkcif->debugfs_dir; +unsigned int i; + + interface->debugfs_dir =debugfs_create_dir (interface->sd.name, parent); + +for (i =0 ; i < match_data->errors_num; i++) +debugfs_create_ulong (match_data->errors[i].name,0444 , + interface->debugfs_dir, + &interface->error_count[i]); +} +static void rkcif_mipi_queue_buffer (struct rkcif_stream *stream,unsigned int index) {
@@ -742,6 +827,7 @@ static int rkcif_mipi_start_streaming(struct rkcif_stream *stream) int_mask |=RKCIF_MIPI_INT_FRAME0_END (stream->id); int_mask |=RKCIF_MIPI_INT_FRAME1_END (stream->id); + int_mask |=rkcif_mipi_error_mask (interface);spin_lock_irqsave (&interface->irq_lock, flags); int_temp =rkcif_mipi_read (interface,RKCIF_MIPI_INTEN );
@@ -785,6 +871,8 @@ static void rkcif_mipi_stop_streaming(struct rkcif_stream *stream)spin_lock_irqsave (&interface->irq_lock, flags); int_temp =rkcif_mipi_read (interface,RKCIF_MIPI_INTEN ); int_temp &= ~int_mask; +if (!(int_temp &RKCIF_MIPI_INT_ALL_END )) + int_temp &= ~rkcif_mipi_error_mask (interface);rkcif_mipi_write (interface,RKCIF_MIPI_INTEN , int_temp); int_temp =rkcif_mipi_read (interface,RKCIF_MIPI_INTSTAT );
@@ -836,6 +924,8 @@ irqreturn_t rkcif_mipi_isr(int irq, void *ctx)if (intstat) ret =IRQ_HANDLED ; +rkcif_mipi_handle_errors (interface, intstat); +for (unsigned int j =0 ; j < interface->streams_num; j++) {struct rkcif_stream *stream = &interface->streams[j];
@@ -879,6 +969,8 @@ int rkcif_mipi_register(struct rkcif_device *rkcif)if (ret)continue ; +rkcif_mipi_debugfs_init (interface); +for (unsigned int j =0 ; j <RKCIF_ID_MAX ; j++) {struct rkcif_stream *stream = &interface->streams[j];
@@ -906,6 +998,7 @@ int rkcif_mipi_register(struct rkcif_device *rkcif)for (unsigned int j =0 ; j < interface->streams_num; j++)rkcif_stream_unregister (&interface->streams[j]); +debugfs_remove_recursive (interface->debugfs_dir);rkcif_interface_unregister (interface); }return ret;
@@ -923,6 +1016,7 @@ void rkcif_mipi_unregister(struct rkcif_device *rkcif)for (unsigned int j =0 ; j < interface->streams_num; j++)rkcif_stream_unregister (&interface->streams[j]); +debugfs_remove_recursive (interface->debugfs_dir);rkcif_interface_unregister (interface); } }
drivers/media/platform/rockchip/rkcif/rkcif-common.h +12 -0
@@ -28,6 +28,7 @@#define RKCIF_DRIVER_NAME "rockchip-cif"#define RKCIF_CLK_MAX 5 +#define RKCIF_MIPI_ERRORS_MAX 5enum rkcif_format_type {RKCIF_FMT_TYPE_INVALID ,
@@ -198,18 +199,28 @@ struct rkcif_interface {struct rkcif_dvp dvp; }; +struct dentry *debugfs_dir; +unsigned long error_count[RKCIF_MIPI_ERRORS_MAX ]; +/* interrupt enable and status are shared by the streams of a block */ spinlock_t irq_lock;void (*set_crop)(struct rkcif_stream *stream,u16 left,u16 top); }; +struct rkcif_mipi_error { +u32 mask; +const char *name; +}; +struct rkcif_mipi_match_data {unsigned int mipi_num;unsigned int regs[RKCIF_MIPI_REGISTER_MAX ];unsigned int regs_id[RKCIF_ID_MAX ][RKCIF_MIPI_ID_REGISTER_MAX ];u32 (*mipi_ctrl0)(struct rkcif_stream *stream,const struct rkcif_output_fmt *active_out_fmt); +const struct rkcif_mipi_error *errors; +unsigned int errors_num;struct {unsigned int offset; } blocks[RKCIF_MIPI_MAX -RKCIF_MIPI_BASE ];
@@ -248,6 +259,7 @@ struct rkcif_device {struct media_device media_dev;struct v4l2_device v4l2_dev;struct v4l2_async_notifier notifier; + struct dentry *debugfs_dir; };#endif
drivers/media/platform/rockchip/rkcif/rkcif-dev.c +5 -0
@@ -10,6 +10,7 @@*/ #include <linux/clk.h> +#include <linux/debugfs.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/mfd/syscon.h>
@@ -231,6 +232,8 @@ static int rkcif_probe(struct platform_device *pdev)v4l2_async_nf_init (&rkcif->notifier, &rkcif->v4l2_dev); rkcif->notifier.ops = &rkcif_notifier_ops; + rkcif->debugfs_dir =debugfs_create_dir (dev_name (dev),NULL ); + ret =rkcif_register (rkcif);if (ret) {dev_err (dev,"failed to register media entities: %d\n" , ret);
@@ -246,6 +249,7 @@ static int rkcif_probe(struct platform_device *pdev) err_rkcif_unregister:rkcif_unregister (rkcif); err_notifier_cleanup: +debugfs_remove_recursive (rkcif->debugfs_dir);v4l2_async_nf_cleanup (&rkcif->notifier);media_device_unregister (&rkcif->media_dev); err_v4l2_dev_unregister:
@@ -262,6 +266,7 @@ static void rkcif_remove(struct platform_device *pdev)v4l2_async_nf_unregister (&rkcif->notifier);rkcif_unregister (rkcif); +debugfs_remove_recursive (rkcif->debugfs_dir);v4l2_async_nf_cleanup (&rkcif->notifier);media_device_unregister (&rkcif->media_dev);v4l2_device_unregister (&rkcif->v4l2_dev);
drivers/media/platform/rockchip/rkcif/rkcif-regs.h +2 -0
@@ -132,6 +132,8 @@ enum rkcif_mipi_register_index {RKCIF_MIPI_CTRL ,RKCIF_MIPI_INTEN ,RKCIF_MIPI_INTSTAT , +RKCIF_MIPI_LINE_CNT_ID0_1 , +RKCIF_MIPI_LINE_CNT_ID2_3 , RKCIF_MIPI_REGISTER_MAX };
Signature · 1 line
2.56.0
PATCH 4/6 media: rockchip: rkcif: restart the MIPI DMA when it slips
In reply to Daniel GolleThe MIPI DMA ends a frame after the configured number of lines and the sensor frame start does not reset that count. A line dropped on the CSI-2 link, which the receiver reports as a frame boundary or sequence error, therefore leaves every later buffer composed of the tail of one frame and the head of the next, with the Bayer phase flipped when the offset is odd. Observed on an RV1126 with an IMX415 after a night of streaming, reproducible on demand by shortening the D-PHY settle time.
Enable the frame start interrupts and read the line counter there: it is zero in the vertical blanking while the DMA is in step and holds the offset otherwise. Disabling and re-enabling the capture only pauses the counter, so once a second frame confirms the offset the stream is restarted through the existing stop and start paths, and the runtime suspend in between resets the block. With other streams keeping the block powered the restart is skipped and a warning given once; the restarts are counted in debugfs next to the error counters.
--- .../rockchip/rkcif/rkcif-capture-mipi.c | 53 +++++++++++++++ .../platform/rockchip/rkcif/rkcif-common.h | 8 +++ .../platform/rockchip/rkcif/rkcif-stream.c | 64 +++++++++++++++++++ .../platform/rockchip/rkcif/rkcif-stream.h | 2 + 4 files changed, 127 insertions(+)
drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +53 -0
@@ -43,10 +43,17 @@#define RKCIF_MIPI_CTRL0_VC_ID (id) ((id) << 8)#define RKCIF_MIPI_CTRL0_CAP_EN BIT(0) +#define RKCIF_MIPI_INT_FRAME0_START (id) BIT((id) * 2 + 0) +#define RKCIF_MIPI_INT_FRAME1_START (id) BIT((id) * 2 + 1)#define RKCIF_MIPI_INT_FRAME0_END (id) BIT(8 + (id) * 2 + 0)#define RKCIF_MIPI_INT_FRAME1_END (id) BIT(8 + (id) * 2 + 1)#define RKCIF_MIPI_INT_ALL_END GENMASK(15, 8) +#define RKCIF_MIPI_LINE_CNT_MASK GENMASK(13, 0) + +/* a slipped frame boundary persists, so a second frame confirms it */ +#define RKCIF_MIPI_MISALIGNED_FRAMES 2 +#define RK3568_MIPI_INT_DMA_Y_FIFO_OVERFLOW BIT(16)#define RK3568_MIPI_INT_DMA_UV_FIFO_OVERFLOW BIT(17)#define RK3568_MIPI_INT_CONFIG_FIFO_OVERFLOW BIT(18)
@@ -782,6 +789,41 @@ static void rkcif_mipi_debugfs_init(struct rkcif_interface *interface)debugfs_create_ulong (match_data->errors[i].name,0444 , interface->debugfs_dir, &interface->error_count[i]); +debugfs_create_ulong ("restarts" ,0444 , interface->debugfs_dir, + &interface->restarts); +} + +static u32 rkcif_mipi_line_cnt (struct rkcif_stream *stream) +{ +unsigned int index =RKCIF_MIPI_LINE_CNT_ID0_1 + stream->id /2 ; +u32 val =rkcif_mipi_read (stream->interface, index); + +return (val >> (stream->id %2 *16 )) &RKCIF_MIPI_LINE_CNT_MASK ; +} + +/* the DMA frame boundary is a line count that only a block reset resyncs */ +static void rkcif_mipi_check_alignment (struct rkcif_stream *stream) +{ +u32 line; + +if (!stream->streaming || stream->stopping || stream->restart_pending || + stream->realign_disabled) +return ; + + line =rkcif_mipi_line_cnt (stream); +if (!line) { + stream->misaligned_frames =0 ; +return ; + } + +if (++stream->misaligned_frames <RKCIF_MIPI_MISALIGNED_FRAMES ) +return ; + + stream->misaligned_frames =0 ; +dev_warn (stream->rkcif->dev, +"%s: frame starts at line %u, restarting the capture\n" , + stream->vdev.name, line); +rkcif_stream_restart (stream); } static void rkcif_mipi_queue_buffer(struct rkcif_stream *stream,
@@ -825,6 +867,8 @@ static int rkcif_mipi_start_streaming(struct rkcif_stream *stream) ctrl1 =RKCIF_XY_COORD (width, height); + int_mask |=RKCIF_MIPI_INT_FRAME0_START (stream->id); + int_mask |=RKCIF_MIPI_INT_FRAME1_START (stream->id); int_mask |=RKCIF_MIPI_INT_FRAME0_END (stream->id); int_mask |=RKCIF_MIPI_INT_FRAME1_END (stream->id); int_mask |=rkcif_mipi_error_mask (interface);
@@ -865,6 +909,8 @@ static void rkcif_mipi_stop_streaming(struct rkcif_stream *stream)rkcif_mipi_stream_write (stream,RKCIF_MIPI_CTRL0 ,0 ); + int_mask |=RKCIF_MIPI_INT_FRAME0_START (stream->id); + int_mask |=RKCIF_MIPI_INT_FRAME1_START (stream->id); int_mask |=RKCIF_MIPI_INT_FRAME0_END (stream->id); int_mask |=RKCIF_MIPI_INT_FRAME1_END (stream->id);
@@ -929,6 +975,10 @@ irqreturn_t rkcif_mipi_isr(int irq, void *ctx)for (unsigned int j =0 ; j < interface->streams_num; j++) {struct rkcif_stream *stream = &interface->streams[j]; +if (intstat &RKCIF_MIPI_INT_FRAME0_START (stream->id) || + intstat &RKCIF_MIPI_INT_FRAME1_START (stream->id)) +rkcif_mipi_check_alignment (stream); +if (intstat &RKCIF_MIPI_INT_FRAME0_END (stream->id) || intstat &RKCIF_MIPI_INT_FRAME1_END (stream->id)) { ret =IRQ_HANDLED ;
@@ -939,6 +989,9 @@ irqreturn_t rkcif_mipi_isr(int irq, void *ctx)continue ; } +if (stream->restart_pending) +continue ; +rkcif_stream_pingpong (stream); } }
drivers/media/platform/rockchip/rkcif/rkcif-common.h +8 -0
@@ -14,6 +14,7 @@#include <linux/clk.h> #include <linux/mutex.h> #include <linux/regmap.h> +#include <linux/workqueue.h> #include <media/media-device.h> #include <media/media-entity.h>
@@ -158,6 +159,12 @@ struct rkcif_stream {bool stopping;wait_queue_head_t wq_stopped; +bool streaming; +bool restart_pending; +bool realign_disabled; +unsigned int misaligned_frames; +struct work_struct restart_work; +/* queue of available buffers plus spinlock that protects it */ spinlock_t driver_queue_lock;struct list_head driver_queue;
@@ -201,6 +208,7 @@ struct rkcif_interface {struct dentry *debugfs_dir;unsigned long error_count[RKCIF_MIPI_ERRORS_MAX ]; +unsigned long restarts;/* interrupt enable and status are shared by the streams of a block */ spinlock_t irq_lock;
drivers/media/platform/rockchip/rkcif/rkcif-stream.c +64 -0
100 lines not on this page · raw message
drivers/media/platform/rockchip/rkcif/rkcif-stream.h +2 -0
@@ -20,6 +20,8 @@void rkcif_stream_pingpong (struct rkcif_stream *stream); +void rkcif_stream_restart (struct rkcif_stream *stream); +int rkcif_stream_register (struct rkcif_device *rkcif,struct rkcif_stream *stream);
Signature · 1 line
2.56.0
PATCH 5/6 media: rockchip: rkcif: raise the MIPI DMA write priority
In reply to Daniel GolleThe MIPI control register holds the DDR urgency controls of the write
FIFO: a pressure level and a hurry level, each with its enable, and the FIFO water line above which they apply. The driver sets only the water line enable bit.
Program the values the vendor driver uses, pressure and hurry at level three from a quarter-full FIFO, so that the capture raises its priority on the memory bus as the FIFO fills. The bit layout is the same on the RK3568, RV1126 and RK3588.
--- .../rockchip/rkcif/rkcif-capture-mipi.c | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-)
drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +13 -7
@@ -37,7 +37,12 @@#define RK3588_MIPI_CTRL0_CROP_EN BIT(4)#define RK3588_MIPI_CTRL0_PARSE (type) ((type) << 1) -#define RK3588_MIPI_CTRL_CAP_EN BIT(0) +#define RKCIF_MIPI_CTRL_PRESS_VALUE (x) ((x) << 13) +#define RKCIF_MIPI_CTRL_PRESS_ENABLE BIT(12) +#define RKCIF_MIPI_CTRL_HURRY_VALUE (x) ((x) << 5) +#define RKCIF_MIPI_CTRL_HURRY_ENABLE BIT(4) +#define RKCIF_MIPI_CTRL_WATER_LINE_25 (0x2 << 1) +#define RKCIF_MIPI_CTRL_WATER_LINE_ENABLE BIT(0)#define RKCIF_MIPI_CTRL0_DT_ID (id) ((id) << 10)#define RKCIF_MIPI_CTRL0_VC_ID (id) ((id) << 8)
@@ -891,12 +896,13 @@ static int rkcif_mipi_start_streaming(struct rkcif_stream *stream)rkcif_mipi_stream_write (stream,RKCIF_MIPI_CTRL1 , ctrl1);rkcif_mipi_stream_write (stream,RKCIF_MIPI_CTRL0 , ctrl0); -/* -* TODO: This bit has a different meaning on the RK3568, but it is -* set there by default anyway. While correct, this is not exactly -* nice and shall be reworked during the next refactoring. -*/ -rkcif_mipi_write (interface,RKCIF_MIPI_CTRL ,RK3588_MIPI_CTRL_CAP_EN ); +rkcif_mipi_write (interface,RKCIF_MIPI_CTRL , +RKCIF_MIPI_CTRL_PRESS_VALUE (3 ) | +RKCIF_MIPI_CTRL_PRESS_ENABLE | +RKCIF_MIPI_CTRL_HURRY_VALUE (3 ) | +RKCIF_MIPI_CTRL_HURRY_ENABLE | +RKCIF_MIPI_CTRL_WATER_LINE_25 | +RKCIF_MIPI_CTRL_WATER_LINE_ENABLE );return 0 ; }
Signature · 1 line
2.56.0
PATCH 6/6 media: synopsys: csi2rx: handle the error interrupts
In reply to Daniel GolleThe receiver reports lost synchronisation, frame boundary and sequence errors, CRC and ECC results and unknown data types in two clear-on-read registers with an interrupt line each. The driver masks all of them, so a link that drops packets, and with them image lines, leaves no trace.
Request the err1 and err2 interrupts where the firmware names them, unmask the sources while the receiver runs, count each class in debugfs and warn with the raw registers under a rate limit. The unrecognised data type source stays masked as in the vendor driver. The masks are written again in the runtime resume, which resets the block.
--- .../media/platform/synopsys/dw-mipi-csi2rx.c | 125 +++++++++++++++++- 1 file changed, 120 insertions(+), 5 deletions(-)
drivers/media/platform/synopsys/dw-mipi-csi2rx.c +120 -5
@@ -9,7 +9,9 @@#include <linux/bitfield.h> #include <linux/clk.h> +#include <linux/debugfs.h> #include <linux/delay.h> +#include <linux/interrupt.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/module.h>
@@ -54,6 +56,18 @@#define IPI_MEM_FLUSH_AUTO BIT(8) +#define ERR1_PHY_SOT_SYNC GENMASK(3, 0) +#define ERR1_FRAME_BOUNDARY GENMASK(7, 4) +#define ERR1_FRAME_SEQUENCE GENMASK(11, 8) +#define ERR1_FRAME_DATA GENMASK(15, 12) +#define ERR1_CRC GENMASK(28, 24) + +#define ERR2_PHY_ESCAPE GENMASK(3, 0) +#define ERR2_PHY_SOT GENMASK(7, 4) +#define ERR2_ECC_CORRECTED GENMASK(11, 8) +#define ERR2_DATA_TYPE GENMASK(15, 12) +#define ERR2_PHY_CODE BIT(24) +enum dw_mipi_csi2rx_regs_index {DW_MIPI_CSI2RX_N_LANES ,DW_MIPI_CSI2RX_RESETN ,
@@ -100,6 +114,25 @@ struct dw_mipi_csi2rx_format {u8 csi_dt; }; +struct dw_mipi_csi2rx_error { +enum dw_mipi_csi2rx_regs_index reg; +u32 mask; +const char *name; +}; + +static const struct dw_mipi_csi2rx_error dw_mipi_csi2rx_errors[] = { + {DW_MIPI_CSI2RX_ERR1 ,ERR1_PHY_SOT_SYNC ,"phy_sot_sync" }, + {DW_MIPI_CSI2RX_ERR1 ,ERR1_FRAME_BOUNDARY ,"frame_boundary" }, + {DW_MIPI_CSI2RX_ERR1 ,ERR1_FRAME_SEQUENCE ,"frame_sequence" }, + {DW_MIPI_CSI2RX_ERR1 ,ERR1_FRAME_DATA ,"frame_data" }, + {DW_MIPI_CSI2RX_ERR1 ,ERR1_CRC ,"crc" }, + {DW_MIPI_CSI2RX_ERR2 ,ERR2_PHY_ESCAPE ,"phy_escape" }, + {DW_MIPI_CSI2RX_ERR2 ,ERR2_PHY_SOT ,"phy_sot" }, + {DW_MIPI_CSI2RX_ERR2 ,ERR2_ECC_CORRECTED ,"ecc_corrected" }, + {DW_MIPI_CSI2RX_ERR2 ,ERR2_DATA_TYPE ,"data_type" }, + {DW_MIPI_CSI2RX_ERR2 ,ERR2_PHY_CODE ,"phy_code" }, +}; +struct dw_mipi_csi2rx_device {struct device *dev;
@@ -108,6 +141,9 @@ struct dw_mipi_csi2rx_device {unsigned int clks_num;struct phy *phy;struct reset_control *reset; +bool err_irqs; +struct dentry *debugfs_dir; +unsigned long error_count[ARRAY_SIZE (dw_mipi_csi2rx_errors)];const struct dw_mipi_csi2rx_format *formats;unsigned int formats_num;
@@ -343,6 +379,76 @@ static inline u32 dw_mipi_csi2rx_read(struct dw_mipi_csi2rx_device *csi2,return readl (dw_mipi_csi2rx_get_regaddr (csi2, index)); } +static void dw_mipi_csi2rx_mask_errors (struct dw_mipi_csi2rx_device *csi2, +u32 msk1,u32 msk2) +{ +if (dw_mipi_csi2rx_has_reg (csi2,DW_MIPI_CSI2RX_MSK1 )) +dw_mipi_csi2rx_write (csi2,DW_MIPI_CSI2RX_MSK1 , msk1); + +if (dw_mipi_csi2rx_has_reg (csi2,DW_MIPI_CSI2RX_MSK2 )) +dw_mipi_csi2rx_write (csi2,DW_MIPI_CSI2RX_MSK2 , msk2); +} + +static irqreturn_t dw_mipi_csi2rx_err_isr (int irq,void *ctx) +{ +struct dw_mipi_csi2rx_device *csi2 = ctx; +const struct dw_mipi_csi2rx_error *error; +unsigned int i; +u32 err[2 ]; + + err[0 ] =dw_mipi_csi2rx_read (csi2,DW_MIPI_CSI2RX_ERR1 ); + err[1 ] =dw_mipi_csi2rx_read (csi2,DW_MIPI_CSI2RX_ERR2 ); +if (!err[0 ] && !err[1 ]) +return IRQ_NONE ; + +for (i =0 ; i <ARRAY_SIZE (dw_mipi_csi2rx_errors); i++) { + error = &dw_mipi_csi2rx_errors[i]; +if (err[error->reg -DW_MIPI_CSI2RX_ERR1 ] & error->mask) + csi2->error_count[i]++; + } + +dev_warn_ratelimited (csi2->dev,"link errors %#x %#x\n" , err[0 ], + err[1 ]); + +return IRQ_HANDLED ; +} + +static int dw_mipi_csi2rx_request_irqs (struct dw_mipi_csi2rx_device *csi2, +struct platform_device *pdev) +{ +static const char *const names[] = {"err1" ,"err2" }; +unsigned int i; +int irq, ret; + +for (i =0 ; i <ARRAY_SIZE (names); i++) { + irq =platform_get_irq_byname_optional (pdev, names[i]); +if (irq == -ENXIO ) +continue ; +if (irq <0 ) +return irq; + + ret =devm_request_irq (&pdev->dev, irq, dw_mipi_csi2rx_err_isr, +0 ,dev_name (&pdev->dev), csi2); +if (ret) +return ret; + + csi2->err_irqs = true; + } + +return 0 ; +} + +static void dw_mipi_csi2rx_debugfs_init (struct dw_mipi_csi2rx_device *csi2) +{ +unsigned int i; + + csi2->debugfs_dir =debugfs_create_dir (dev_name (csi2->dev),NULL ); + +for (i =0 ; i <ARRAY_SIZE (dw_mipi_csi2rx_errors); i++) +debugfs_create_ulong (dw_mipi_csi2rx_errors[i].name,0444 , + csi2->debugfs_dir, &csi2->error_count[i]); +} +static const struct dw_mipi_csi2rx_format *dw_mipi_csi2rx_find_format (struct dw_mipi_csi2rx_device *csi2,u32 mbus_code) {
@@ -425,6 +531,9 @@ static int dw_mipi_csi2rx_start(struct dw_mipi_csi2rx_device *csi2)if (csi2->drvdata->dphy_deassert_reset) csi2->drvdata->dphy_deassert_reset (csi2); +if (csi2->err_irqs) +dw_mipi_csi2rx_mask_errors (csi2,0 ,ERR2_DATA_TYPE ); +dw_mipi_csi2rx_write (csi2,DW_MIPI_CSI2RX_RESETN ,1 );if (csi2->drvdata->ipi_enable)
@@ -439,11 +548,7 @@ static void dw_mipi_csi2rx_stop(struct dw_mipi_csi2rx_device *csi2)dw_mipi_csi2rx_write (csi2,DW_MIPI_CSI2RX_RESETN ,0 ); -if (dw_mipi_csi2rx_has_reg (csi2,DW_MIPI_CSI2RX_MSK1 )) -dw_mipi_csi2rx_write (csi2,DW_MIPI_CSI2RX_MSK1 , ~0 ); - -if (dw_mipi_csi2rx_has_reg (csi2,DW_MIPI_CSI2RX_MSK2 )) -dw_mipi_csi2rx_write (csi2,DW_MIPI_CSI2RX_MSK2 , ~0 ); +dw_mipi_csi2rx_mask_errors (csi2, ~0 , ~0 ); }static const struct media_entity_operations dw_mipi_csi2rx_media_ops = {
@@ -985,6 +1090,10 @@ static int dw_mipi_csi2rx_probe(struct platform_device *pdev)return dev_err_probe (dev,PTR_ERR (csi2->reset),"failed to get reset\n" ); + ret =dw_mipi_csi2rx_request_irqs (csi2, pdev); +if (ret) +return dev_err_probe (dev, ret,"failed to request irqs\n" ); + csi2->formats = formats; csi2->formats_num =ARRAY_SIZE (formats);
@@ -997,6 +1106,8 @@ static int dw_mipi_csi2rx_probe(struct platform_device *pdev)return dev_err_probe (dev, ret,"failed to initialize MIPI CSI-2 PHY\n" ); +dw_mipi_csi2rx_debugfs_init (csi2); + ret =dw_mipi_csi2rx_register (csi2);if (ret) goto err_phy_exit;
@@ -1004,6 +1115,7 @@ static int dw_mipi_csi2rx_probe(struct platform_device *pdev)return 0 ; err_phy_exit: +debugfs_remove_recursive (csi2->debugfs_dir);phy_exit (csi2->phy);return ret;
@@ -1014,6 +1126,7 @@ static void dw_mipi_csi2rx_remove(struct platform_device *pdev)struct dw_mipi_csi2rx_device *csi2 =platform_get_drvdata (pdev);dw_mipi_csi2rx_unregister (csi2); +debugfs_remove_recursive (csi2->debugfs_dir);phy_exit (csi2->phy); }
@@ -1041,6 +1154,8 @@ static int dw_mipi_csi2rx_runtime_resume(struct device *dev)return ret; } +dw_mipi_csi2rx_mask_errors (csi2, ~0 , ~0 ); +return 0 ; }
Signature · 1 line
2.56.0