From c4e620eccbef76aa5564ebb295e23d6540e27215 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Thu, 5 Jun 2025 08:57:35 +0200 Subject: [PATCH 001/307] media: dvb-core: dmxdevfilter must always flush bufs Currently the buffers are being filled until full, which works fine for the transport stream, but not when reading sections, those have to be returned to userspace immediately, otherwise dvbv5-scan will just wait forever. Add a 'flush' argument to dvb_vb2_fill_buffer to indicate whether the buffer must be flushed or wait until it is full. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/dvb-core/dmxdev.c | 8 ++++---- drivers/media/dvb-core/dvb_vb2.c | 5 +++-- include/media/dvb_vb2.h | 6 ++++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c index 8c6f5aafda1d..17184b367490 100644 --- a/drivers/media/dvb-core/dmxdev.c +++ b/drivers/media/dvb-core/dmxdev.c @@ -397,11 +397,11 @@ static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len, if (dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx)) { ret = dvb_vb2_fill_buffer(&dmxdevfilter->vb2_ctx, buffer1, buffer1_len, - buffer_flags); + buffer_flags, true); if (ret == buffer1_len) ret = dvb_vb2_fill_buffer(&dmxdevfilter->vb2_ctx, buffer2, buffer2_len, - buffer_flags); + buffer_flags, true); } else { ret = dvb_dmxdev_buffer_write(&dmxdevfilter->buffer, buffer1, buffer1_len); @@ -452,10 +452,10 @@ static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len, if (dvb_vb2_is_streaming(ctx)) { ret = dvb_vb2_fill_buffer(ctx, buffer1, buffer1_len, - buffer_flags); + buffer_flags, false); if (ret == buffer1_len) ret = dvb_vb2_fill_buffer(ctx, buffer2, buffer2_len, - buffer_flags); + buffer_flags, false); } else { if (buffer->error) { spin_unlock(&dmxdevfilter->dev->lock); diff --git a/drivers/media/dvb-core/dvb_vb2.c b/drivers/media/dvb-core/dvb_vb2.c index 29edaaff7a5c..7444bbc2f24d 100644 --- a/drivers/media/dvb-core/dvb_vb2.c +++ b/drivers/media/dvb-core/dvb_vb2.c @@ -249,7 +249,8 @@ int dvb_vb2_is_streaming(struct dvb_vb2_ctx *ctx) int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx, const unsigned char *src, int len, - enum dmx_buffer_flags *buffer_flags) + enum dmx_buffer_flags *buffer_flags, + bool flush) { unsigned long flags = 0; void *vbuf = NULL; @@ -306,7 +307,7 @@ int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx, } } - if (ctx->nonblocking && ctx->buf) { + if (flush && ctx->buf) { vb2_set_plane_payload(&ctx->buf->vb, 0, ll); vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_DONE); list_del(&ctx->buf->list); diff --git a/include/media/dvb_vb2.h b/include/media/dvb_vb2.h index 8cb88452cd6c..0fbbfc65157e 100644 --- a/include/media/dvb_vb2.h +++ b/include/media/dvb_vb2.h @@ -124,7 +124,7 @@ static inline int dvb_vb2_release(struct dvb_vb2_ctx *ctx) return 0; }; #define dvb_vb2_is_streaming(ctx) (0) -#define dvb_vb2_fill_buffer(ctx, file, wait, flags) (0) +#define dvb_vb2_fill_buffer(ctx, file, wait, flags, flush) (0) static inline __poll_t dvb_vb2_poll(struct dvb_vb2_ctx *ctx, struct file *file, @@ -166,10 +166,12 @@ int dvb_vb2_is_streaming(struct dvb_vb2_ctx *ctx); * @buffer_flags: * pointer to buffer flags as defined by &enum dmx_buffer_flags. * can be NULL. + * @flush: flush the buffer, even if it isn't full. */ int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx, const unsigned char *src, int len, - enum dmx_buffer_flags *buffer_flags); + enum dmx_buffer_flags *buffer_flags, + bool flush); /** * dvb_vb2_poll - Wrapper to vb2_core_streamon() for Digital TV From 37fd4a0306b5309849274b03a6bf2020ed3af95c Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Thu, 5 Jun 2025 08:57:36 +0200 Subject: [PATCH 002/307] media: dvb-core/dmxdev: drop locks around mmap() vb2 no longer requires locking around mmap since commit f035eb4e976e ("[media] videobuf2: fix lockdep warning"). Since the streaming I/O mode for DVB support is by default off, and the dvb utilities were never updated with streaming support, and we never had regression tests for this streaming mode, this was never noticed before. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/dvb-core/dmxdev.c | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c index 17184b367490..1cc0e9ecc56a 100644 --- a/drivers/media/dvb-core/dmxdev.c +++ b/drivers/media/dvb-core/dmxdev.c @@ -1217,24 +1217,11 @@ static int dvb_demux_mmap(struct file *file, struct vm_area_struct *vma) { struct dmxdev_filter *dmxdevfilter = file->private_data; struct dmxdev *dmxdev = dmxdevfilter->dev; - int ret; if (!dmxdev->may_do_mmap) return -ENOTTY; - if (mutex_lock_interruptible(&dmxdev->mutex)) - return -ERESTARTSYS; - - if (mutex_lock_interruptible(&dmxdevfilter->mutex)) { - mutex_unlock(&dmxdev->mutex); - return -ERESTARTSYS; - } - ret = dvb_vb2_mmap(&dmxdevfilter->vb2_ctx, vma); - - mutex_unlock(&dmxdevfilter->mutex); - mutex_unlock(&dmxdev->mutex); - - return ret; + return dvb_vb2_mmap(&dmxdevfilter->vb2_ctx, vma); } #endif @@ -1367,7 +1354,6 @@ static int dvb_dvr_mmap(struct file *file, struct vm_area_struct *vma) { struct dvb_device *dvbdev = file->private_data; struct dmxdev *dmxdev = dvbdev->priv; - int ret; if (!dmxdev->may_do_mmap) return -ENOTTY; @@ -1375,12 +1361,7 @@ static int dvb_dvr_mmap(struct file *file, struct vm_area_struct *vma) if (dmxdev->exit) return -ENODEV; - if (mutex_lock_interruptible(&dmxdev->mutex)) - return -ERESTARTSYS; - - ret = dvb_vb2_mmap(&dmxdev->dvr_vb2_ctx, vma); - mutex_unlock(&dmxdev->mutex); - return ret; + return dvb_vb2_mmap(&dmxdev->dvr_vb2_ctx, vma); } #endif From cbb5cd440d06a87c2c8c5819c5ad5c2a73021687 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Thu, 5 Jun 2025 08:57:37 +0200 Subject: [PATCH 003/307] media: dvb-core: dvb_vb2: drop wait_prepare/finish callbacks Since commit 88785982a19d ("media: vb2: use lock if wait_prepare/finish are NULL") it is no longer needed to set the wait_prepare/finish vb2_ops callbacks as long as the lock field in vb2_queue is set. Set the queue lock to &ctx->mutex, which makes it possible to drop the wait_prepare/finish callbacks. This simplifies the code and this is a step towards the goal of deleting these callbacks. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/dvb-core/dmxdev.c | 15 +++++++++++- drivers/media/dvb-core/dvb_vb2.c | 40 +++++++++----------------------- include/media/dvb_vb2.h | 11 ++++----- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c index 1cc0e9ecc56a..c946c8ea6e39 100644 --- a/drivers/media/dvb-core/dmxdev.c +++ b/drivers/media/dvb-core/dmxdev.c @@ -171,6 +171,7 @@ static int dvb_dvr_open(struct inode *inode, struct file *file) dvb_ringbuffer_init(&dmxdev->dvr_buffer, mem, DVR_BUFFER_SIZE); if (dmxdev->may_do_mmap) dvb_vb2_init(&dmxdev->dvr_vb2_ctx, "dvr", + &dmxdev->mutex, file->f_flags & O_NONBLOCK); dvbdev->readers--; } @@ -815,9 +816,21 @@ static int dvb_demux_open(struct inode *inode, struct file *file) dmxdev->may_do_mmap = 0; #endif + /* + * The mutex passed to dvb_vb2_init is unlocked when a buffer + * is in a blocking wait. However, dmxdevfilter has two mutexes: + * dmxdevfilter->mutex and dmxdev->mutex. So this will not work. + * The solution would be to support unlocking two mutexes in vb2, + * but since this problem has been here since the beginning and + * nobody ever complained, we leave it as-is rather than adding + * that second mutex pointer to vb2. + * + * In the unlikely event that someone complains about this, then + * this comment will hopefully help. + */ dvb_ringbuffer_init(&dmxdevfilter->buffer, NULL, 8192); dvb_vb2_init(&dmxdevfilter->vb2_ctx, "demux_filter", - file->f_flags & O_NONBLOCK); + &dmxdevfilter->mutex, file->f_flags & O_NONBLOCK); dmxdevfilter->type = DMXDEV_TYPE_NONE; dvb_dmxdev_filter_state_set(dmxdevfilter, DMXDEV_STATE_ALLOCATED); timer_setup(&dmxdevfilter->timer, dvb_dmxdev_filter_timeout, 0); diff --git a/drivers/media/dvb-core/dvb_vb2.c b/drivers/media/dvb-core/dvb_vb2.c index 7444bbc2f24d..672b0efdca21 100644 --- a/drivers/media/dvb-core/dvb_vb2.c +++ b/drivers/media/dvb-core/dvb_vb2.c @@ -103,31 +103,12 @@ static void _stop_streaming(struct vb2_queue *vq) spin_unlock_irqrestore(&ctx->slock, flags); } -static void _dmxdev_lock(struct vb2_queue *vq) -{ - struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vq); - - mutex_lock(&ctx->mutex); - dprintk(3, "[%s]\n", ctx->name); -} - -static void _dmxdev_unlock(struct vb2_queue *vq) -{ - struct dvb_vb2_ctx *ctx = vb2_get_drv_priv(vq); - - if (mutex_is_locked(&ctx->mutex)) - mutex_unlock(&ctx->mutex); - dprintk(3, "[%s]\n", ctx->name); -} - static const struct vb2_ops dvb_vb2_qops = { .queue_setup = _queue_setup, .buf_prepare = _buffer_prepare, .buf_queue = _buffer_queue, .start_streaming = _start_streaming, .stop_streaming = _stop_streaming, - .wait_prepare = _dmxdev_unlock, - .wait_finish = _dmxdev_lock, }; static void _fill_dmx_buffer(struct vb2_buffer *vb, void *pb) @@ -158,9 +139,10 @@ static const struct vb2_buf_ops dvb_vb2_buf_ops = { }; /* - * Videobuf operations + * vb2 operations */ -int dvb_vb2_init(struct dvb_vb2_ctx *ctx, const char *name, int nonblocking) +int dvb_vb2_init(struct dvb_vb2_ctx *ctx, const char *name, + struct mutex *mutex, int nonblocking) { struct vb2_queue *q = &ctx->vb_q; int ret; @@ -175,14 +157,7 @@ int dvb_vb2_init(struct dvb_vb2_ctx *ctx, const char *name, int nonblocking) q->ops = &dvb_vb2_qops; q->mem_ops = &vb2_vmalloc_memops; q->buf_ops = &dvb_vb2_buf_ops; - ret = vb2_core_queue_init(q); - if (ret) { - ctx->state = DVB_VB2_STATE_NONE; - dprintk(1, "[%s] errno=%d\n", ctx->name, ret); - return ret; - } - - mutex_init(&ctx->mutex); + q->lock = mutex; spin_lock_init(&ctx->slock); INIT_LIST_HEAD(&ctx->dvb_q); @@ -190,6 +165,13 @@ int dvb_vb2_init(struct dvb_vb2_ctx *ctx, const char *name, int nonblocking) ctx->nonblocking = nonblocking; ctx->state = DVB_VB2_STATE_INIT; + ret = vb2_core_queue_init(q); + if (ret) { + ctx->state = DVB_VB2_STATE_NONE; + dprintk(1, "[%s] errno=%d\n", ctx->name, ret); + return ret; + } + dprintk(3, "[%s]\n", ctx->name); return 0; diff --git a/include/media/dvb_vb2.h b/include/media/dvb_vb2.h index 0fbbfc65157e..8932396d2c99 100644 --- a/include/media/dvb_vb2.h +++ b/include/media/dvb_vb2.h @@ -72,8 +72,6 @@ struct dvb_buffer { /** * struct dvb_vb2_ctx - control struct for VB2 handler * @vb_q: pointer to &struct vb2_queue with videobuf2 queue. - * @mutex: mutex to serialize vb2 operations. Used by - * vb2 core %wait_prepare and %wait_finish operations. * @slock: spin lock used to protect buffer filling at dvb_vb2.c. * @dvb_q: List of buffers that are not filled yet. * @buf: Pointer to the buffer that are currently being filled. @@ -96,7 +94,6 @@ struct dvb_buffer { */ struct dvb_vb2_ctx { struct vb2_queue vb_q; - struct mutex mutex; spinlock_t slock; struct list_head dvb_q; struct dvb_buffer *buf; @@ -114,8 +111,8 @@ struct dvb_vb2_ctx { }; #ifndef CONFIG_DVB_MMAP -static inline int dvb_vb2_init(struct dvb_vb2_ctx *ctx, - const char *name, int non_blocking) +static inline int dvb_vb2_init(struct dvb_vb2_ctx *ctx, const char *name, + struct mutex *mutex, int non_blocking) { return 0; }; @@ -138,10 +135,12 @@ static inline __poll_t dvb_vb2_poll(struct dvb_vb2_ctx *ctx, * * @ctx: control struct for VB2 handler * @name: name for the VB2 handler + * @mutex: pointer to the mutex that serializes vb2 ioctls * @non_blocking: * if not zero, it means that the device is at non-blocking mode */ -int dvb_vb2_init(struct dvb_vb2_ctx *ctx, const char *name, int non_blocking); +int dvb_vb2_init(struct dvb_vb2_ctx *ctx, const char *name, + struct mutex *mutex, int non_blocking); /** * dvb_vb2_release - Releases the VB2 handler allocated resources and From d0730006dac2922bcd3cd16818516ddd3ffb7302 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Thu, 5 Jun 2025 08:57:38 +0200 Subject: [PATCH 004/307] media: vb2: remove vb2_ops_wait_prepare/finish helpers Since vb2 now relies on the presence of the vb2_queue lock field and there are no more drivers that use these helpers, it is safe to drop them. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/driver-api/media/v4l2-dev.rst | 8 ++++---- .../media/common/videobuf2/videobuf2-v4l2.c | 14 -------------- include/media/videobuf2-v4l2.h | 18 ------------------ 3 files changed, 4 insertions(+), 36 deletions(-) diff --git a/Documentation/driver-api/media/v4l2-dev.rst b/Documentation/driver-api/media/v4l2-dev.rst index d5cb19b21a9f..dd239ad42051 100644 --- a/Documentation/driver-api/media/v4l2-dev.rst +++ b/Documentation/driver-api/media/v4l2-dev.rst @@ -157,10 +157,10 @@ changing the e.g. exposure of the webcam. Of course, you can always do all the locking yourself by leaving both lock pointers at ``NULL``. -In the case of :ref:`videobuf2 ` you will need to implement the -``wait_prepare()`` and ``wait_finish()`` callbacks to unlock/lock if applicable. -If you use the ``queue->lock`` pointer, then you can use the helper functions -:c:func:`vb2_ops_wait_prepare` and :c:func:`vb2_ops_wait_finish`. +In the case of :ref:`videobuf2 ` you must set the ``queue->lock`` +pointer to the lock you use to serialize the queuing ioctls. This ensures that +that lock is released while waiting in ``VIDIOC_DQBUF`` for a buffer to arrive, +and it is retaken afterwards. The implementation of a hotplug disconnect should also take the lock from :c:type:`video_device` before calling v4l2_device_disconnect. If you are also diff --git a/drivers/media/common/videobuf2/videobuf2-v4l2.c b/drivers/media/common/videobuf2/videobuf2-v4l2.c index 83862d57b126..4baded4fd3b8 100644 --- a/drivers/media/common/videobuf2/videobuf2-v4l2.c +++ b/drivers/media/common/videobuf2/videobuf2-v4l2.c @@ -1302,20 +1302,6 @@ void vb2_video_unregister_device(struct video_device *vdev) } EXPORT_SYMBOL_GPL(vb2_video_unregister_device); -/* vb2_ops helpers. Only use if vq->lock is non-NULL. */ - -void vb2_ops_wait_prepare(struct vb2_queue *vq) -{ - mutex_unlock(vq->lock); -} -EXPORT_SYMBOL_GPL(vb2_ops_wait_prepare); - -void vb2_ops_wait_finish(struct vb2_queue *vq) -{ - mutex_lock(vq->lock); -} -EXPORT_SYMBOL_GPL(vb2_ops_wait_finish); - /* * Note that this function is called during validation time and * thus the req_queue_mutex is held to ensure no request objects diff --git a/include/media/videobuf2-v4l2.h b/include/media/videobuf2-v4l2.h index 77ce8238ab30..71d2864fb235 100644 --- a/include/media/videobuf2-v4l2.h +++ b/include/media/videobuf2-v4l2.h @@ -367,24 +367,6 @@ unsigned long vb2_fop_get_unmapped_area(struct file *file, unsigned long addr, */ void vb2_video_unregister_device(struct video_device *vdev); -/** - * vb2_ops_wait_prepare - helper function to lock a struct &vb2_queue - * - * @vq: pointer to &struct vb2_queue - * - * ..note:: only use if vq->lock is non-NULL. - */ -void vb2_ops_wait_prepare(struct vb2_queue *vq); - -/** - * vb2_ops_wait_finish - helper function to unlock a struct &vb2_queue - * - * @vq: pointer to &struct vb2_queue - * - * ..note:: only use if vq->lock is non-NULL. - */ -void vb2_ops_wait_finish(struct vb2_queue *vq); - struct media_request; int vb2_request_validate(struct media_request *req); void vb2_request_queue(struct media_request *req); From b70886ff5833cf499e77af77d2324ce8f68b60ce Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Thu, 5 Jun 2025 08:57:39 +0200 Subject: [PATCH 005/307] media: vb2: drop wait_prepare/finish callbacks Drop the wait_prepare/finish callbacks. Instead require that the vb2_queue lock field is always set and use that lock when waiting for buffers to arrive. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- .../userspace-api/media/conf_nitpick.py | 2 - .../media/common/videobuf2/videobuf2-core.c | 49 ++++--------------- include/media/videobuf2-core.h | 23 ++------- 3 files changed, 15 insertions(+), 59 deletions(-) diff --git a/Documentation/userspace-api/media/conf_nitpick.py b/Documentation/userspace-api/media/conf_nitpick.py index 0a8e236d07ab..445a29c01d1b 100644 --- a/Documentation/userspace-api/media/conf_nitpick.py +++ b/Documentation/userspace-api/media/conf_nitpick.py @@ -42,8 +42,6 @@ nitpick_ignore = [ ("c:func", "struct fd_set"), ("c:func", "struct pollfd"), ("c:func", "usb_make_path"), - ("c:func", "wait_finish"), - ("c:func", "wait_prepare"), ("c:func", "write"), ("c:type", "atomic_t"), diff --git a/drivers/media/common/videobuf2/videobuf2-core.c b/drivers/media/common/videobuf2/videobuf2-core.c index 2df566f409b6..2d1f253b4929 100644 --- a/drivers/media/common/videobuf2/videobuf2-core.c +++ b/drivers/media/common/videobuf2/videobuf2-core.c @@ -605,8 +605,7 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int start, unsigned i */ if (vb2_get_num_buffers(q)) { bool unbalanced = q->cnt_start_streaming != q->cnt_stop_streaming || - q->cnt_prepare_streaming != q->cnt_unprepare_streaming || - q->cnt_wait_prepare != q->cnt_wait_finish; + q->cnt_prepare_streaming != q->cnt_unprepare_streaming; if (unbalanced) { pr_info("unbalanced counters for queue %p:\n", q); @@ -617,13 +616,8 @@ static void __vb2_queue_free(struct vb2_queue *q, unsigned int start, unsigned i if (q->cnt_prepare_streaming != q->cnt_unprepare_streaming) pr_info(" prepare_streaming: %u unprepare_streaming: %u\n", q->cnt_prepare_streaming, q->cnt_unprepare_streaming); - if (q->cnt_wait_prepare != q->cnt_wait_finish) - pr_info(" wait_prepare: %u wait_finish: %u\n", - q->cnt_wait_prepare, q->cnt_wait_finish); } q->cnt_queue_setup = 0; - q->cnt_wait_prepare = 0; - q->cnt_wait_finish = 0; q->cnt_prepare_streaming = 0; q->cnt_start_streaming = 0; q->cnt_stop_streaming = 0; @@ -2037,10 +2031,7 @@ static int __vb2_wait_for_done_vb(struct vb2_queue *q, int nonblocking) * become ready or for streamoff. Driver's lock is released to * allow streamoff or qbuf to be called while waiting. */ - if (q->ops->wait_prepare) - call_void_qop(q, wait_prepare, q); - else if (q->lock) - mutex_unlock(q->lock); + mutex_unlock(q->lock); /* * All locks have been released, it is safe to sleep now. @@ -2050,10 +2041,7 @@ static int __vb2_wait_for_done_vb(struct vb2_queue *q, int nonblocking) !list_empty(&q->done_list) || !q->streaming || q->error); - if (q->ops->wait_finish) - call_void_qop(q, wait_finish, q); - else if (q->lock) - mutex_lock(q->lock); + mutex_lock(q->lock); q->waiting_in_dqbuf = 0; /* @@ -2653,12 +2641,8 @@ int vb2_core_queue_init(struct vb2_queue *q) if (WARN_ON(q->min_reqbufs_allocation > q->max_num_buffers)) return -EINVAL; - /* Either both or none are set */ - if (WARN_ON(!q->ops->wait_prepare ^ !q->ops->wait_finish)) - return -EINVAL; - - /* Warn if q->lock is NULL and no custom wait_prepare is provided */ - if (WARN_ON(!q->lock && !q->ops->wait_prepare)) + /* Warn if q->lock is NULL */ + if (WARN_ON(!q->lock)) return -EINVAL; INIT_LIST_HEAD(&q->queued_list); @@ -3220,17 +3204,10 @@ static int vb2_thread(void *data) continue; prequeue--; } else { - if (!threadio->stop) { - if (q->ops->wait_finish) - call_void_qop(q, wait_finish, q); - else if (q->lock) - mutex_lock(q->lock); + mutex_lock(q->lock); + if (!threadio->stop) ret = vb2_core_dqbuf(q, &index, NULL, 0); - if (q->ops->wait_prepare) - call_void_qop(q, wait_prepare, q); - else if (q->lock) - mutex_unlock(q->lock); - } + mutex_unlock(q->lock); dprintk(q, 5, "file io: vb2_dqbuf result: %d\n", ret); if (!ret) vb = vb2_get_buffer(q, index); @@ -3245,15 +3222,9 @@ static int vb2_thread(void *data) if (copy_timestamp) vb->timestamp = ktime_get_ns(); if (!threadio->stop) { - if (q->ops->wait_finish) - call_void_qop(q, wait_finish, q); - else if (q->lock) - mutex_lock(q->lock); + mutex_lock(q->lock); ret = vb2_core_qbuf(q, vb, NULL, NULL); - if (q->ops->wait_prepare) - call_void_qop(q, wait_prepare, q); - else if (q->lock) - mutex_unlock(q->lock); + mutex_unlock(q->lock); } if (ret || threadio->stop) break; diff --git a/include/media/videobuf2-core.h b/include/media/videobuf2-core.h index 9b02aeba4108..4424d481d7f7 100644 --- a/include/media/videobuf2-core.h +++ b/include/media/videobuf2-core.h @@ -351,13 +351,6 @@ struct vb2_buffer { * \*num_buffers are being allocated additionally to * the buffers already allocated. If either \*num_planes * or the requested sizes are invalid callback must return %-EINVAL. - * @wait_prepare: release any locks taken while calling vb2 functions; - * it is called before an ioctl needs to wait for a new - * buffer to arrive; required to avoid a deadlock in - * blocking access type. - * @wait_finish: reacquire all locks released in the previous callback; - * required to continue operation after sleeping while - * waiting for a new buffer to arrive. * @buf_out_validate: called when the output buffer is prepared or queued * to a request; drivers can use this to validate * userspace-provided information; this is required only @@ -436,9 +429,6 @@ struct vb2_ops { unsigned int *num_buffers, unsigned int *num_planes, unsigned int sizes[], struct device *alloc_devs[]); - void (*wait_prepare)(struct vb2_queue *q); - void (*wait_finish)(struct vb2_queue *q); - int (*buf_out_validate)(struct vb2_buffer *vb); int (*buf_init)(struct vb2_buffer *vb); int (*buf_prepare)(struct vb2_buffer *vb); @@ -521,10 +511,10 @@ struct vb2_buf_ops { * @non_coherent_mem: when set queue will attempt to allocate buffers using * non-coherent memory. * @lock: pointer to a mutex that protects the &struct vb2_queue. The - * driver can set this to a mutex to let the v4l2 core serialize - * the queuing ioctls. If the driver wants to handle locking - * itself, then this should be set to NULL. This lock is not used - * by the videobuf2 core API. + * driver must set this to a mutex to let the v4l2 core serialize + * the queuing ioctls. This lock is used when waiting for a new + * buffer to arrive: the lock is released, we wait for the new + * buffer, and then retaken. * @owner: The filehandle that 'owns' the buffers, i.e. the filehandle * that called reqbufs, create_buffers or started fileio. * This field is not used by the videobuf2 core API, but it allows @@ -680,8 +670,6 @@ struct vb2_queue { * called. Used to check for unbalanced ops. */ u32 cnt_queue_setup; - u32 cnt_wait_prepare; - u32 cnt_wait_finish; u32 cnt_prepare_streaming; u32 cnt_start_streaming; u32 cnt_stop_streaming; @@ -766,8 +754,7 @@ void vb2_discard_done(struct vb2_queue *q); * @q: pointer to &struct vb2_queue with videobuf2 queue. * * This function will wait until all buffers that have been given to the driver - * by &vb2_ops->buf_queue are given back to vb2 with vb2_buffer_done(). It - * doesn't call &vb2_ops->wait_prepare/&vb2_ops->wait_finish pair. + * by &vb2_ops->buf_queue are given back to vb2 with vb2_buffer_done(). * It is intended to be called with all locks taken, for example from * &vb2_ops->stop_streaming callback. */ From 85883141d3bea47e5bce688cc300800da7b1ea61 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Tue, 16 Sep 2025 19:10:55 +0300 Subject: [PATCH 006/307] media: uvcvideo: Replace dev_dbg() with uvc_dbg() The uvcvideo driver uses a uvc_dbg() macro that supports enabling debug message categories selectively, and prints a KERN_DEBUG message. The macro is used through the driver, but one direct dev_dbg() call creeped in. Replace it with uvc_dbg(). Signed-off-by: Laurent Pinchart Reviewed-by: Ricardo Ribalda Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_ctrl.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index 2905505c240c..d58bd888f0bf 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -2929,8 +2929,7 @@ int uvc_ctrl_restore_values(struct uvc_device *dev) if (!ctrl->initialized || !ctrl->modified || (ctrl->info.flags & UVC_CTRL_FLAG_RESTORE) == 0) continue; - dev_dbg(&dev->intf->dev, - "restoring control %pUl/%u/%u\n", + uvc_dbg(dev, CONTROL, "restoring control %pUl/%u/%u\n", ctrl->info.entity, ctrl->info.index, ctrl->info.selector); ctrl->dirty = 1; From 4238bd6dc6ba36f44d89a60338223d5a4f708cbf Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 19 Nov 2025 20:17:36 +0000 Subject: [PATCH 007/307] media: uvcvideo: Fix support for V4L2_CTRL_FLAG_HAS_WHICH_MIN_MAX The VIDIOC_G_EXT_CTRLS with which V4L2_CTRL_WHICH_(MIN|MAX)_VAL can only work for controls that have previously announced support for it. This patch fixes the following v4l2-compliance error: info: checking extended control 'User Controls' (0x00980001) fail: v4l2-test-controls.cpp(980): ret != EINVAL (got 13) test VIDIOC_G/S/TRY_EXT_CTRLS: FAIL Fixes: 39d2c891c96e ("media: uvcvideo: support V4L2_CTRL_WHICH_MIN/MAX_VAL") Cc: stable@vger.kernel.org Signed-off-by: Ricardo Ribalda Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_ctrl.c | 14 ++++++++++++-- drivers/media/usb/uvc/uvc_v4l2.c | 10 ++++++---- drivers/media/usb/uvc/uvcvideo.h | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index d58bd888f0bf..ec5aaeb99928 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -1432,7 +1432,7 @@ static bool uvc_ctrl_is_readable(u32 which, struct uvc_control *ctrl, * auto_exposure=1, exposure_time_absolute=251. */ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, - const struct v4l2_ext_controls *ctrls, + const struct v4l2_ext_controls *ctrls, u32 which, unsigned long ioctl) { struct uvc_control_mapping *master_map = NULL; @@ -1442,14 +1442,24 @@ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, s32 val; int ret; int i; + /* + * There is no need to check the ioctl, all the ioctls except + * VIDIOC_G_EXT_CTRLS use which=V4L2_CTRL_WHICH_CUR_VAL. + */ + bool is_which_min_max = which == V4L2_CTRL_WHICH_MIN_VAL || + which == V4L2_CTRL_WHICH_MAX_VAL; if (__uvc_query_v4l2_class(chain, v4l2_id, 0) >= 0) - return -EACCES; + return is_which_min_max ? -EINVAL : -EACCES; ctrl = uvc_find_control(chain, v4l2_id, &mapping); if (!ctrl) return -EINVAL; + if ((!(ctrl->info.flags & UVC_CTRL_FLAG_GET_MIN) || + !(ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX)) && is_which_min_max) + return -EINVAL; + if (ioctl == VIDIOC_G_EXT_CTRLS) return uvc_ctrl_is_readable(ctrls->which, ctrl, mapping); diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 9e4a251eca88..30c160daed8c 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -765,14 +765,15 @@ static int uvc_ioctl_query_ext_ctrl(struct file *file, void *priv, static int uvc_ctrl_check_access(struct uvc_video_chain *chain, struct v4l2_ext_controls *ctrls, - unsigned long ioctl) + u32 which, unsigned long ioctl) { struct v4l2_ext_control *ctrl = ctrls->controls; unsigned int i; int ret = 0; for (i = 0; i < ctrls->count; ++ctrl, ++i) { - ret = uvc_ctrl_is_accessible(chain, ctrl->id, ctrls, ioctl); + ret = uvc_ctrl_is_accessible(chain, ctrl->id, ctrls, which, + ioctl); if (ret) break; } @@ -806,7 +807,7 @@ static int uvc_ioctl_g_ext_ctrls(struct file *file, void *priv, which = V4L2_CTRL_WHICH_CUR_VAL; } - ret = uvc_ctrl_check_access(chain, ctrls, VIDIOC_G_EXT_CTRLS); + ret = uvc_ctrl_check_access(chain, ctrls, which, VIDIOC_G_EXT_CTRLS); if (ret < 0) return ret; @@ -840,7 +841,8 @@ static int uvc_ioctl_s_try_ext_ctrls(struct uvc_fh *handle, if (!ctrls->count) return 0; - ret = uvc_ctrl_check_access(chain, ctrls, ioctl); + ret = uvc_ctrl_check_access(chain, ctrls, V4L2_CTRL_WHICH_CUR_VAL, + ioctl); if (ret < 0) return ret; diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index ed7bad31f75c..d583425893a5 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -786,7 +786,7 @@ int uvc_ctrl_get(struct uvc_video_chain *chain, u32 which, struct v4l2_ext_control *xctrl); int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl); int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, - const struct v4l2_ext_controls *ctrls, + const struct v4l2_ext_controls *ctrls, u32 which, unsigned long ioctl); int uvc_xu_ctrl_query(struct uvc_video_chain *chain, From f0487a90b2c50d4021c578a809144d800a703676 Mon Sep 17 00:00:00 2001 From: John Bauer Date: Mon, 1 Dec 2025 17:06:53 +0100 Subject: [PATCH 008/307] media: uvcvideo: UVC minimum relative pan/tilt/zoom speed fix. In Video4Linux, a negative value in V4L2_CID_ZOOM_CONTINUOUS, V4L2_CID_PAN_SPEED and V4L2_CID_TILT_SPEED indicates a movement in the "opposite" direction to the standard direction. Currently, we were using -UVC_GET_MIN as the negative value, which resulted in the camera moving in the slowest possible speed. Quirk the driver to return -UVC_GET_MAX for the affected controls. Note that the get/set function from the mapping cannot be used for this, because we need to use the information from GET_MAX for GET_MIN and hacking uvc_ctrl_populate_cache seems like a worse alternative. Tested on OBSBOT Tiny 2. lsusb -v: Bus 008 Device 002: ID 3564:fef8 Remo Tech Co., Ltd. OBSBOT Tiny 2 Negotiated speed: SuperSpeed (5Gbps) Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 3.10 bDeviceClass 239 Miscellaneous Device bDeviceSubClass 2 [unknown] bDeviceProtocol 1 Interface Association bMaxPacketSize0 9 idVendor 0x3564 Remo Tech Co., Ltd. idProduct 0xfef8 OBSBOT Tiny 2 bcdDevice 4.09 iManufacturer 1 Remo Tech Co., Ltd. iProduct 2 OBSBOT Tiny 2 iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x04fc bNumInterfaces 4 bConfigurationValue 1 iConfiguration 4 OBSBOT Multifunction bmAttributes 0xc0 Self Powered MaxPower 0mA Interface Association: bLength 8 bDescriptorType 11 bFirstInterface 0 bInterfaceCount 2 bFunctionClass 14 Video bFunctionSubClass 3 Video Interface Collection bFunctionProtocol 0 iFunction 5 OBSBOT Tiny 2 StreamCamera Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 14 Video bInterfaceSubClass 1 Video Control bInterfaceProtocol 0 iInterface 5 OBSBOT Tiny 2 StreamCamera VideoControl Interface Descriptor: bLength 13 bDescriptorType 36 bDescriptorSubtype 1 (HEADER) bcdUVC 1.00 wTotalLength 0x0050 dwClockFrequency 48.000000MHz bInCollection 1 baInterfaceNr( 0) 1 VideoControl Interface Descriptor: bLength 18 bDescriptorType 36 bDescriptorSubtype 2 (INPUT_TERMINAL) bTerminalID 1 wTerminalType 0x0201 Camera Sensor bAssocTerminal 0 iTerminal 0 wObjectiveFocalLengthMin 0 wObjectiveFocalLengthMax 0 wOcularFocalLength 0 bControlSize 3 bmControls 0x00023e3e Auto-Exposure Mode Auto-Exposure Priority Exposure Time (Absolute) Exposure Time (Relative) Focus (Absolute) Zoom (Absolute) Zoom (Relative) PanTilt (Absolute) PanTilt (Relative) Roll (Absolute) Focus, Auto VideoControl Interface Descriptor: bLength 11 bDescriptorType 36 bDescriptorSubtype 5 (PROCESSING_UNIT) Warning: Descriptor too short bUnitID 3 bSourceID 1 wMaxMultiplier 400 bControlSize 2 bmControls 0x0000f7df Brightness Contrast Hue Saturation Sharpness White Balance Temperature White Balance Component Backlight Compensation Gain Power Line Frequency White Balance Temperature, Auto White Balance Component, Auto Digital Multiplier Digital Multiplier Limit iProcessing 0 bmVideoStandards 0x1d None PAL - 625/50 SECAM - 625/50 NTSC - 625/50 VideoControl Interface Descriptor: bLength 29 bDescriptorType 36 bDescriptorSubtype 6 (EXTENSION_UNIT) bUnitID 2 guidExtensionCode {9a1e7291-6843-4683-6d92-39bc7906ee49} bNumControls 19 bNrInPins 1 baSourceID( 0) 3 bControlSize 4 bmControls( 0) 0xff bmControls( 1) 0xff bmControls( 2) 0x04 bmControls( 3) 0x00 iExtension 0 VideoControl Interface Descriptor: bLength 9 bDescriptorType 36 bDescriptorSubtype 3 (OUTPUT_TERMINAL) bTerminalID 7 wTerminalType 0x0101 USB Streaming bAssocTerminal 0 bSourceID 2 iTerminal 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0010 1x 16 bytes bInterval 8 bMaxBurst 0 VideoControl Endpoint Descriptor: bLength 5 bDescriptorType 37 bDescriptorSubtype 3 (EP_INTERRUPT) wMaxTransferSize 16 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 14 Video bInterfaceSubClass 2 Video Streaming bInterfaceProtocol 0 iInterface 6 Video Streaming VideoStreaming Interface Descriptor: bLength 16 bDescriptorType 36 bDescriptorSubtype 1 (INPUT_HEADER) bNumFormats 3 wTotalLength 0x03f8 bEndpointAddress 0x81 EP 1 IN bmInfo 0 bTerminalLink 7 bStillCaptureMethod 0 bTriggerSupport 0 bTriggerUsage 0 bControlSize 1 bmaControls( 0) 4 bmaControls( 1) 0 bmaControls( 2) 4 VideoStreaming Interface Descriptor: bLength 11 bDescriptorType 36 bDescriptorSubtype 6 (FORMAT_MJPEG) bFormatIndex 1 bNumFrameDescriptors 6 bFlags 0 Fixed-size samples: No bDefaultFrameIndex 1 bAspectRatioX 0 bAspectRatioY 0 bmInterlaceFlags 0x00 Interlaced stream or variable: No Fields per frame: 1 fields Field 1 first: No Field pattern: Field 1 only bCopyProtect 0 VideoStreaming Interface Descriptor: bLength 62 bDescriptorType 36 bDescriptorSubtype 7 (FRAME_MJPEG) bFrameIndex 1 bmCapabilities 0x00 Still image unsupported wWidth 1920 wHeight 1080 dwMinBitRate 995328000 dwMaxBitRate 1990656000 dwMaxVideoFrameBufferSize 4147200 dwDefaultFrameInterval 333333 bFrameIntervalType 9 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 166833 dwFrameInterval( 2) 200000 dwFrameInterval( 3) 333333 dwFrameInterval( 4) 333666 dwFrameInterval( 5) 400000 dwFrameInterval( 6) 416666 dwFrameInterval( 7) 500000 dwFrameInterval( 8) 666666 VideoStreaming Interface Descriptor: bLength 50 bDescriptorType 36 bDescriptorSubtype 7 (FRAME_MJPEG) bFrameIndex 2 bmCapabilities 0x00 Still image unsupported wWidth 3840 wHeight 2160 dwMinBitRate 1327104000 dwMaxBitRate 1327104000 dwMaxVideoFrameBufferSize 16588800 dwDefaultFrameInterval 333333 bFrameIntervalType 6 dwFrameInterval( 0) 333333 dwFrameInterval( 1) 333666 dwFrameInterval( 2) 400000 dwFrameInterval( 3) 416666 dwFrameInterval( 4) 500000 dwFrameInterval( 5) 666666 VideoStreaming Interface Descriptor: bLength 62 bDescriptorType 36 bDescriptorSubtype 7 (FRAME_MJPEG) bFrameIndex 3 bmCapabilities 0x00 Still image unsupported wWidth 1280 wHeight 720 dwMinBitRate 221184000 dwMaxBitRate 884736000 dwMaxVideoFrameBufferSize 1843200 dwDefaultFrameInterval 333333 bFrameIntervalType 9 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 166833 dwFrameInterval( 2) 200000 dwFrameInterval( 3) 333333 dwFrameInterval( 4) 333666 dwFrameInterval( 5) 400000 dwFrameInterval( 6) 416666 dwFrameInterval( 7) 500000 dwFrameInterval( 8) 666666 VideoStreaming Interface Descriptor: bLength 50 bDescriptorType 36 bDescriptorSubtype 7 (FRAME_MJPEG) bFrameIndex 4 bmCapabilities 0x00 Still image unsupported wWidth 1280 wHeight 960 dwMinBitRate 294912000 dwMaxBitRate 1179648000 dwMaxVideoFrameBufferSize 2457600 dwDefaultFrameInterval 333333 bFrameIntervalType 6 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 333333 dwFrameInterval( 2) 400000 dwFrameInterval( 3) 416666 dwFrameInterval( 4) 500000 dwFrameInterval( 5) 666666 VideoStreaming Interface Descriptor: bLength 50 bDescriptorType 36 bDescriptorSubtype 7 (FRAME_MJPEG) bFrameIndex 5 bmCapabilities 0x00 Still image unsupported wWidth 1920 wHeight 1440 dwMinBitRate 663552000 dwMaxBitRate 1327104000 dwMaxVideoFrameBufferSize 5529600 dwDefaultFrameInterval 333333 bFrameIntervalType 6 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 333333 dwFrameInterval( 2) 400000 dwFrameInterval( 3) 416666 dwFrameInterval( 4) 500000 dwFrameInterval( 5) 666666 VideoStreaming Interface Descriptor: bLength 46 bDescriptorType 36 bDescriptorSubtype 7 (FRAME_MJPEG) bFrameIndex 6 bmCapabilities 0x00 Still image unsupported wWidth 4000 wHeight 3000 dwMinBitRate 1920000000 dwMaxBitRate 1920000000 dwMaxVideoFrameBufferSize 24000000 dwDefaultFrameInterval 333333 bFrameIntervalType 5 dwFrameInterval( 0) 333333 dwFrameInterval( 1) 400000 dwFrameInterval( 2) 416666 dwFrameInterval( 3) 500000 dwFrameInterval( 4) 666666 VideoStreaming Interface Descriptor: bLength 6 bDescriptorType 36 bDescriptorSubtype 13 (COLORFORMAT) bColorPrimaries 1 (BT.709,sRGB) bTransferCharacteristics 1 (BT.709) bMatrixCoefficients 4 (SMPTE 170M (BT.601)) VideoStreaming Interface Descriptor: bLength 27 bDescriptorType 36 bDescriptorSubtype 4 (FORMAT_UNCOMPRESSED) bFormatIndex 2 bNumFrameDescriptors 7 guidFormat {32595559-0000-0010-8000-00aa00389b71} bBitsPerPixel 16 bDefaultFrameIndex 5 bAspectRatioX 0 bAspectRatioY 0 bmInterlaceFlags 0x00 Interlaced stream or variable: No Fields per frame: 2 fields Field 1 first: No Field pattern: Field 1 only bCopyProtect 0 VideoStreaming Interface Descriptor: bLength 50 bDescriptorType 36 bDescriptorSubtype 5 (FRAME_UNCOMPRESSED) bFrameIndex 1 bmCapabilities 0x00 Still image unsupported wWidth 640 wHeight 360 dwMinBitRate 55296000 dwMaxBitRate 221184000 dwMaxVideoFrameBufferSize 460800 dwDefaultFrameInterval 333333 bFrameIntervalType 6 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 333333 dwFrameInterval( 2) 400000 dwFrameInterval( 3) 416666 dwFrameInterval( 4) 500000 dwFrameInterval( 5) 666666 VideoStreaming Interface Descriptor: bLength 50 bDescriptorType 36 bDescriptorSubtype 5 (FRAME_UNCOMPRESSED) bFrameIndex 2 bmCapabilities 0x00 Still image unsupported wWidth 640 wHeight 480 dwMinBitRate 73728000 dwMaxBitRate 294912000 dwMaxVideoFrameBufferSize 614400 dwDefaultFrameInterval 333333 bFrameIntervalType 6 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 333333 dwFrameInterval( 2) 400000 dwFrameInterval( 3) 416666 dwFrameInterval( 4) 500000 dwFrameInterval( 5) 666666 VideoStreaming Interface Descriptor: bLength 50 bDescriptorType 36 bDescriptorSubtype 5 (FRAME_UNCOMPRESSED) bFrameIndex 3 bmCapabilities 0x00 Still image unsupported wWidth 1280 wHeight 720 dwMinBitRate 221184000 dwMaxBitRate 884736000 dwMaxVideoFrameBufferSize 1843200 dwDefaultFrameInterval 333333 bFrameIntervalType 6 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 333333 dwFrameInterval( 2) 400000 dwFrameInterval( 3) 416666 dwFrameInterval( 4) 500000 dwFrameInterval( 5) 666666 VideoStreaming Interface Descriptor: bLength 50 bDescriptorType 36 bDescriptorSubtype 5 (FRAME_UNCOMPRESSED) bFrameIndex 4 bmCapabilities 0x00 Still image unsupported wWidth 1280 wHeight 960 dwMinBitRate 294912000 dwMaxBitRate 1179648000 dwMaxVideoFrameBufferSize 2457600 dwDefaultFrameInterval 333333 bFrameIntervalType 6 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 333333 dwFrameInterval( 2) 400000 dwFrameInterval( 3) 416666 dwFrameInterval( 4) 500000 dwFrameInterval( 5) 666666 VideoStreaming Interface Descriptor: bLength 46 bDescriptorType 36 bDescriptorSubtype 5 (FRAME_UNCOMPRESSED) bFrameIndex 5 bmCapabilities 0x00 Still image unsupported wWidth 1920 wHeight 1080 dwMinBitRate 497664000 dwMaxBitRate 995328000 dwMaxVideoFrameBufferSize 4147200 dwDefaultFrameInterval 333333 bFrameIntervalType 5 dwFrameInterval( 0) 333333 dwFrameInterval( 1) 400000 dwFrameInterval( 2) 416666 dwFrameInterval( 3) 500000 dwFrameInterval( 4) 666666 VideoStreaming Interface Descriptor: bLength 46 bDescriptorType 36 bDescriptorSubtype 5 (FRAME_UNCOMPRESSED) bFrameIndex 6 bmCapabilities 0x00 Still image unsupported wWidth 1920 wHeight 1440 dwMinBitRate 663552000 dwMaxBitRate 1327104000 dwMaxVideoFrameBufferSize 5529600 dwDefaultFrameInterval 333333 bFrameIntervalType 5 dwFrameInterval( 0) 333333 dwFrameInterval( 1) 400000 dwFrameInterval( 2) 416666 dwFrameInterval( 3) 500000 dwFrameInterval( 4) 666666 VideoStreaming Interface Descriptor: bLength 30 bDescriptorType 36 bDescriptorSubtype 5 (FRAME_UNCOMPRESSED) bFrameIndex 7 bmCapabilities 0x00 Still image unsupported wWidth 3840 wHeight 2160 dwMinBitRate 1990656000 dwMaxBitRate 1990656000 dwMaxVideoFrameBufferSize 16588800 dwDefaultFrameInterval 666666 bFrameIntervalType 1 dwFrameInterval( 0) 666666 VideoStreaming Interface Descriptor: bLength 6 bDescriptorType 36 bDescriptorSubtype 13 (COLORFORMAT) bColorPrimaries 1 (BT.709,sRGB) bTransferCharacteristics 1 (BT.709) bMatrixCoefficients 4 (SMPTE 170M (BT.601)) VideoStreaming Interface Descriptor: bLength 28 bDescriptorType 36 bDescriptorSubtype 16 (FORMAT_FRAME_BASED) bFormatIndex 3 bNumFrameDescriptors 5 guidFormat {34363248-0000-0010-8000-00aa00389b71} bBitsPerPixel 16 bDefaultFrameIndex 1 bAspectRatioX 0 bAspectRatioY 0 bmInterlaceFlags 0x00 Interlaced stream or variable: No Fields per frame: 2 fields Field 1 first: No Field pattern: Field 1 only bCopyProtect 0 bVariableSize 1 VideoStreaming Interface Descriptor: bLength 62 bDescriptorType 36 bDescriptorSubtype 17 (FRAME_FRAME_BASED) bFrameIndex 1 bmCapabilities 0x00 Still image unsupported wWidth 1920 wHeight 1080 dwMinBitRate 497664000 dwMaxBitRate 1990656000 dwDefaultFrameInterval 333333 bFrameIntervalType 9 dwBytesPerLine 0 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 166833 dwFrameInterval( 2) 200000 dwFrameInterval( 3) 333333 dwFrameInterval( 4) 333666 dwFrameInterval( 5) 400000 dwFrameInterval( 6) 416666 dwFrameInterval( 7) 500000 dwFrameInterval( 8) 666666 VideoStreaming Interface Descriptor: bLength 50 bDescriptorType 36 bDescriptorSubtype 17 (FRAME_FRAME_BASED) bFrameIndex 2 bmCapabilities 0x00 Still image unsupported wWidth 3840 wHeight 2160 dwMinBitRate 1327104000 dwMaxBitRate 1327104000 dwDefaultFrameInterval 333333 bFrameIntervalType 6 dwBytesPerLine 0 dwFrameInterval( 0) 333333 dwFrameInterval( 1) 333666 dwFrameInterval( 2) 400000 dwFrameInterval( 3) 416666 dwFrameInterval( 4) 500000 dwFrameInterval( 5) 666666 VideoStreaming Interface Descriptor: bLength 62 bDescriptorType 36 bDescriptorSubtype 17 (FRAME_FRAME_BASED) bFrameIndex 4 bmCapabilities 0x00 Still image unsupported wWidth 1280 wHeight 720 dwMinBitRate 221184000 dwMaxBitRate 884736000 dwDefaultFrameInterval 333333 bFrameIntervalType 9 dwBytesPerLine 0 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 166833 dwFrameInterval( 2) 200000 dwFrameInterval( 3) 333333 dwFrameInterval( 4) 333666 dwFrameInterval( 5) 400000 dwFrameInterval( 6) 416666 dwFrameInterval( 7) 500000 dwFrameInterval( 8) 666666 VideoStreaming Interface Descriptor: bLength 50 bDescriptorType 36 bDescriptorSubtype 17 (FRAME_FRAME_BASED) bFrameIndex 5 bmCapabilities 0x00 Still image unsupported wWidth 1280 wHeight 960 dwMinBitRate 294912000 dwMaxBitRate 1179648000 dwDefaultFrameInterval 333333 bFrameIntervalType 6 dwBytesPerLine 0 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 333333 dwFrameInterval( 2) 400000 dwFrameInterval( 3) 416666 dwFrameInterval( 4) 500000 dwFrameInterval( 5) 666666 VideoStreaming Interface Descriptor: bLength 50 bDescriptorType 36 bDescriptorSubtype 17 (FRAME_FRAME_BASED) bFrameIndex 6 bmCapabilities 0x00 Still image unsupported wWidth 1920 wHeight 1440 dwMinBitRate 663552000 dwMaxBitRate 1327104000 dwDefaultFrameInterval 333333 bFrameIntervalType 6 dwBytesPerLine 0 dwFrameInterval( 0) 166666 dwFrameInterval( 1) 333333 dwFrameInterval( 2) 400000 dwFrameInterval( 3) 416666 dwFrameInterval( 4) 500000 dwFrameInterval( 5) 666666 VideoStreaming Interface Descriptor: bLength 6 bDescriptorType 36 bDescriptorSubtype 13 (COLORFORMAT) bColorPrimaries 1 (BT.709,sRGB) bTransferCharacteristics 1 (BT.709) bMatrixCoefficients 4 (SMPTE 170M (BT.601)) Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 13 Interface Association: bLength 8 bDescriptorType 11 bFirstInterface 2 bInterfaceCount 2 bFunctionClass 1 Audio bFunctionSubClass 0 [unknown] bFunctionProtocol 0 iFunction 8 OBSBOT Tiny2 Audio Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 0 bInterfaceClass 1 Audio bInterfaceSubClass 1 Control Device bInterfaceProtocol 0 iInterface 8 OBSBOT Tiny2 Audio AudioControl Interface Descriptor: bLength 9 bDescriptorType 36 bDescriptorSubtype 1 (HEADER) bcdADC 1.00 wTotalLength 0x0027 bInCollection 1 baInterfaceNr(0) 3 AudioControl Interface Descriptor: bLength 12 bDescriptorType 36 bDescriptorSubtype 2 (INPUT_TERMINAL) bTerminalID 3 wTerminalType 0x0201 Microphone bAssocTerminal 0 bNrChannels 2 wChannelConfig 0x0003 Left Front (L) Right Front (R) iChannelNames 13 Capture Channels iTerminal 12 OBSBOT Tiny2 Microphone AudioControl Interface Descriptor: bLength 9 bDescriptorType 36 bDescriptorSubtype 3 (OUTPUT_TERMINAL) bTerminalID 4 wTerminalType 0x0101 USB Streaming bAssocTerminal 0 bSourceID 5 iTerminal 14 Capture Output terminal AudioControl Interface Descriptor: bLength 9 bDescriptorType 36 bDescriptorSubtype 6 (FEATURE_UNIT) bUnitID 5 bSourceID 3 bControlSize 2 bmaControls(0) 0x0003 Mute Control Volume Control iFeature 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 3 bAlternateSetting 0 bNumEndpoints 0 bInterfaceClass 1 Audio bInterfaceSubClass 2 Streaming bInterfaceProtocol 0 iInterface 17 OBSBOT Tiny2 Microphone Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 3 bAlternateSetting 1 bNumEndpoints 1 bInterfaceClass 1 Audio bInterfaceSubClass 2 Streaming bInterfaceProtocol 0 iInterface 18 Capture Active AudioStreaming Interface Descriptor: bLength 7 bDescriptorType 36 bDescriptorSubtype 1 (AS_GENERAL) bTerminalLink 4 bDelay 1 frames wFormatTag 0x0001 PCM AudioStreaming Interface Descriptor: bLength 11 bDescriptorType 36 bDescriptorSubtype 2 (FORMAT_TYPE) bFormatType 1 (FORMAT_TYPE_I) bNrChannels 2 bSubframeSize 2 bBitResolution 16 bSamFreqType 1 Discrete tSamFreq[ 0] 48000 Endpoint Descriptor: bLength 9 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 5 Transfer Type Isochronous Synch Type Asynchronous Usage Type Data wMaxPacketSize 0x00c0 1x 192 bytes bInterval 4 bRefresh 0 bSynchAddress 0 bMaxBurst 0 AudioStreaming Endpoint Descriptor: bLength 7 bDescriptorType 37 bDescriptorSubtype 1 (EP_GENERAL) bmAttributes 0x01 Sampling Frequency bLockDelayUnits 0 Undefined wLockDelay 0x0000 Binary Object Store Descriptor: bLength 5 bDescriptorType 15 wTotalLength 0x0016 bNumDeviceCaps 2 USB 2.0 Extension Device Capability: bLength 7 bDescriptorType 16 bDevCapabilityType 2 bmAttributes 0x00000006 BESL Link Power Management (LPM) Supported SuperSpeed USB Device Capability: bLength 10 bDescriptorType 16 bDevCapabilityType 3 bmAttributes 0x00 wSpeedsSupported 0x000f Device can operate at Low Speed (1Mbps) Device can operate at Full Speed (12Mbps) Device can operate at High Speed (480Mbps) Device can operate at SuperSpeed (5Gbps) bFunctionalitySupport 1 Lowest fully-functional device speed is Full Speed (12Mbps) bU1DevExitLat 1 micro seconds bU2DevExitLat 500 micro seconds Device Status: 0x0001 Self Powered Signed-off-by: John Bauer Signed-off-by: Gergo Koteles Reviewed-by: Ricardo Ribalda Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_ctrl.c | 62 ++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index ec5aaeb99928..f0f6f8454d9c 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -483,6 +483,7 @@ static int uvc_ctrl_get_zoom(struct uvc_control_mapping *mapping, u8 query, return 0; case UVC_GET_MIN: + /* Not used, we use -UVC_GET_MAX */ case UVC_GET_MAX: case UVC_GET_RES: case UVC_GET_DEF: @@ -526,8 +527,7 @@ static int uvc_ctrl_get_rel_speed(struct uvc_control_mapping *mapping, *out = (sign == 0) ? 0 : (sign > 0 ? value : -value); return 0; case UVC_GET_MIN: - *out = -value; - return 0; + /* Not used, we use -UVC_GET_MAX */ case UVC_GET_MAX: case UVC_GET_RES: case UVC_GET_DEF: @@ -1527,6 +1527,17 @@ static u32 uvc_get_ctrl_bitmap(struct uvc_control *ctrl, return ~0; } +static bool uvc_ctrl_is_relative_ptz(__u32 ctrl_id) +{ + switch (ctrl_id) { + case V4L2_CID_ZOOM_CONTINUOUS: + case V4L2_CID_PAN_SPEED: + case V4L2_CID_TILT_SPEED: + return true; + } + return false; +} + /* * Maximum retry count to avoid spurious errors with controls. Increasing this * value does no seem to produce better results in the tested hardware. @@ -1586,18 +1597,32 @@ static int __uvc_queryctrl_boundaries(struct uvc_video_chain *chain, break; } - if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MIN) - v4l2_ctrl->minimum = uvc_mapping_get_s32(mapping, UVC_GET_MIN, - uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN)); - else - v4l2_ctrl->minimum = 0; - if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX) v4l2_ctrl->maximum = uvc_mapping_get_s32(mapping, UVC_GET_MAX, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX)); else v4l2_ctrl->maximum = 0; + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MIN) { + /* + * For relative PTZ controls, UVC_GET_MIN for + * b(Pan|Tilt|Zoom)Speed returns the minimum speed of the + * movement in direction specified in the sign field. + * See in USB Device Class Definition for Video Devices: + * 4.2.2.1.13 Zoom (Relative) Control + * 4.2.2.1.15 PanTilt (Relative) Control + * + * For minimum value, use maximum speed but in negative direction. + */ + if (uvc_ctrl_is_relative_ptz(v4l2_ctrl->id)) + v4l2_ctrl->minimum = -v4l2_ctrl->maximum; + else + v4l2_ctrl->minimum = uvc_mapping_get_s32(mapping, + UVC_GET_MIN, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN)); + } else { + v4l2_ctrl->minimum = 0; + } + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES) v4l2_ctrl->step = uvc_mapping_get_s32(mapping, UVC_GET_RES, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES)); @@ -2459,6 +2484,7 @@ int uvc_ctrl_get(struct uvc_video_chain *chain, u32 which, static int uvc_ctrl_clamp(struct uvc_video_chain *chain, struct uvc_control *ctrl, + u32 v4l2_id, struct uvc_control_mapping *mapping, s32 *value_in_out) { @@ -2476,10 +2502,24 @@ static int uvc_ctrl_clamp(struct uvc_video_chain *chain, return ret; } - min = uvc_mapping_get_s32(mapping, UVC_GET_MIN, - uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN)); max = uvc_mapping_get_s32(mapping, UVC_GET_MAX, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX)); + /* + * For relative PTZ controls, UVC_GET_MIN for + * b(Pan|Tilt|Zoom)Speed returns the minimum speed of the + * movement in direction specified in the sign field. + * See in USB Device Class Definition for Video Devices: + * 4.2.2.1.13 Zoom (Relative) Control + * 4.2.2.1.15 PanTilt (Relative) Control + * + * For minimum value, use maximum speed but in negative direction. + */ + if (uvc_ctrl_is_relative_ptz(v4l2_id)) + min = -max; + else + min = uvc_mapping_get_s32(mapping, UVC_GET_MIN, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN)); + step = uvc_mapping_get_s32(mapping, UVC_GET_RES, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES)); if (step == 0) @@ -2593,7 +2633,7 @@ int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl) if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) return -EACCES; - ret = uvc_ctrl_clamp(chain, ctrl, mapping, &xctrl->value); + ret = uvc_ctrl_clamp(chain, ctrl, xctrl->id, mapping, &xctrl->value); if (ret) return ret; /* From 27ccc44a511b0cd76dc607e2a4893b876192ee1b Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 19 Nov 2025 19:37:10 +0000 Subject: [PATCH 009/307] media: uvcvideo: Remove nodrop parameter We announced the deprecation intentions one year ago in the commit commit 40ed9e9b2808 ("media: uvcvideo: Announce the user our deprecation intentions"). We have not hear any complains, lets remove the nodrop parameter. Reviewed-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_driver.c | 19 ------------------- drivers/media/usb/uvc/uvc_queue.c | 25 ------------------------- drivers/media/usb/uvc/uvcvideo.h | 1 - 3 files changed, 45 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index ee4f54d68349..71563d8f4bcf 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -32,7 +32,6 @@ unsigned int uvc_clock_param = CLOCK_MONOTONIC; unsigned int uvc_hw_timestamps_param; -unsigned int uvc_no_drop_param = 1; static unsigned int uvc_quirks_param = -1; unsigned int uvc_dbg_param; unsigned int uvc_timeout_param = UVC_CTRL_STREAMING_TIMEOUT; @@ -2468,24 +2467,6 @@ MODULE_PARM_DESC(clock, "Video buffers timestamp clock"); module_param_named(hwtimestamps, uvc_hw_timestamps_param, uint, 0644); MODULE_PARM_DESC(hwtimestamps, "Use hardware timestamps"); -static int param_set_nodrop(const char *val, const struct kernel_param *kp) -{ - pr_warn_once("uvcvideo: " - DEPRECATED - "nodrop parameter will be eventually removed.\n"); - return param_set_bool(val, kp); -} - -static const struct kernel_param_ops param_ops_nodrop = { - .set = param_set_nodrop, - .get = param_get_uint, -}; - -param_check_uint(nodrop, &uvc_no_drop_param); -module_param_cb(nodrop, ¶m_ops_nodrop, &uvc_no_drop_param, 0644); -__MODULE_PARM_TYPE(nodrop, "uint"); -MODULE_PARM_DESC(nodrop, "Don't drop incomplete frames"); - module_param_named(quirks, uvc_quirks_param, uint, 0644); MODULE_PARM_DESC(quirks, "Forced device quirks"); module_param_named(trace, uvc_dbg_param, uint, 0644); diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c index 790184c9843d..3bc54456b4d9 100644 --- a/drivers/media/usb/uvc/uvc_queue.c +++ b/drivers/media/usb/uvc/uvc_queue.c @@ -331,34 +331,9 @@ struct uvc_buffer *uvc_queue_get_current_buffer(struct uvc_video_queue *queue) return nextbuf; } -/* - * uvc_queue_buffer_requeue: Requeue a buffer on our internal irqqueue - * - * Reuse a buffer through our internal queue without the need to 'prepare'. - * The buffer will be returned to userspace through the uvc_buffer_queue call if - * the device has been disconnected. - */ -static void uvc_queue_buffer_requeue(struct uvc_video_queue *queue, - struct uvc_buffer *buf) -{ - buf->error = 0; - buf->state = UVC_BUF_STATE_QUEUED; - buf->bytesused = 0; - vb2_set_plane_payload(&buf->buf.vb2_buf, 0, 0); - - uvc_buffer_queue(&buf->buf.vb2_buf); -} - static void uvc_queue_buffer_complete(struct kref *ref) { struct uvc_buffer *buf = container_of(ref, struct uvc_buffer, ref); - struct vb2_buffer *vb = &buf->buf.vb2_buf; - struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue); - - if (buf->error && !uvc_no_drop_param) { - uvc_queue_buffer_requeue(queue, buf); - return; - } buf->state = buf->error ? UVC_BUF_STATE_ERROR : UVC_BUF_STATE_DONE; vb2_set_plane_payload(&buf->buf.vb2_buf, 0, buf->bytesused); diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index d583425893a5..911016047687 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -659,7 +659,6 @@ static inline struct uvc_fh *to_uvc_fh(struct file *filp) #define UVC_WARN_XU_GET_RES 2 extern unsigned int uvc_clock_param; -extern unsigned int uvc_no_drop_param; extern unsigned int uvc_dbg_param; extern unsigned int uvc_timeout_param; extern unsigned int uvc_hw_timestamps_param; From ee5b462b97162dbb6c536e18a37b3048f6520019 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 19 Nov 2025 19:37:13 +0000 Subject: [PATCH 010/307] media: uvcvideo: Document how to format GUIDs Manual formatting the GUIDs can lead to errors, document a programmatically way to format the GUIDs from lsusb into something that the driver can use. Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- include/linux/usb/uvc.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/linux/usb/uvc.h b/include/linux/usb/uvc.h index 22e0dab0809e..ea92ac623a45 100644 --- a/include/linux/usb/uvc.h +++ b/include/linux/usb/uvc.h @@ -10,6 +10,14 @@ /* ------------------------------------------------------------------------ * GUIDs + * + * The GUID returned by lsusb can be converted to this format with the + * following python snippet: + * + * import uuid + * id = "{01234567-89ab-cdef-0123-456789abcdef}" + * le = uuid.UUID(id).bytes_le + * print("{" + ", ".join([f"0x{b:02x}" for b in le]) + "}") */ #define UVC_GUID_UVC_CAMERA \ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ From e0ac618fff090ea8fd4423f1e3c7eb7d34d8b184 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 30 Apr 2025 09:12:27 +0200 Subject: [PATCH 011/307] media: omap3isp: configure entity functions For the various subdevices, set the function field. This fixes v4l2-compliance errors: $ v4l2-compliance -M0 v4l2-compliance 1.33.0-5410, 32 bits, 64-bit time_t v4l2-compliance SHA: c12c89c5bd70 2025-10-05 09:58:42 Compliance test for omap3isp device /dev/media0: Media Driver Info: Driver name : omap3isp Model : TI OMAP3 ISP Serial : Bus info : platform:480bc000.isp Media version : 6.17.0 Hardware revision: 0x000000f0 (240) Driver version : 6.17.0 Required ioctls: test MEDIA_IOC_DEVICE_INFO: OK test invalid ioctls: OK Allow for multiple opens: test second /dev/media0 open: OK test MEDIA_IOC_DEVICE_INFO: OK test for unlimited opens: OK Media Controller ioctls: fail: v4l2-test-media.cpp(108): function == MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN fail: v4l2-test-media.cpp(196): checkFunction(ent.function, true) test MEDIA_IOC_G_TOPOLOGY: FAIL fail: v4l2-test-media.cpp(398): num_data_links != num_links test MEDIA_IOC_ENUM_ENTITIES/LINKS: FAIL test MEDIA_IOC_SETUP_LINK: OK Total for omap3isp device /dev/media0: 8, Succeeded: 6, Failed: 2, Warnings: 0 Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/ispccdc.c | 1 + drivers/media/platform/ti/omap3isp/ispccp2.c | 1 + drivers/media/platform/ti/omap3isp/ispcsi2.c | 1 + drivers/media/platform/ti/omap3isp/isppreview.c | 1 + drivers/media/platform/ti/omap3isp/ispresizer.c | 1 + drivers/media/platform/ti/omap3isp/ispstat.c | 1 + 6 files changed, 6 insertions(+) diff --git a/drivers/media/platform/ti/omap3isp/ispccdc.c b/drivers/media/platform/ti/omap3isp/ispccdc.c index 55ee14e8b449..9dbf06ac058d 100644 --- a/drivers/media/platform/ti/omap3isp/ispccdc.c +++ b/drivers/media/platform/ti/omap3isp/ispccdc.c @@ -2675,6 +2675,7 @@ static int ccdc_init_entities(struct isp_ccdc_device *ccdc) pads[CCDC_PAD_SOURCE_OF].flags = MEDIA_PAD_FL_SOURCE; me->ops = &ccdc_media_ops; + me->function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_ENC_CONV; ret = media_entity_pads_init(me, CCDC_PADS_NUM, pads); if (ret < 0) return ret; diff --git a/drivers/media/platform/ti/omap3isp/ispccp2.c b/drivers/media/platform/ti/omap3isp/ispccp2.c index 1204ee221c9e..f4147c79639c 100644 --- a/drivers/media/platform/ti/omap3isp/ispccp2.c +++ b/drivers/media/platform/ti/omap3isp/ispccp2.c @@ -1086,6 +1086,7 @@ static int ccp2_init_entities(struct isp_ccp2_device *ccp2) pads[CCP2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; me->ops = &ccp2_media_ops; + me->function = MEDIA_ENT_F_VID_IF_BRIDGE; ret = media_entity_pads_init(me, CCP2_PADS_NUM, pads); if (ret < 0) return ret; diff --git a/drivers/media/platform/ti/omap3isp/ispcsi2.c b/drivers/media/platform/ti/omap3isp/ispcsi2.c index ae574e1b6528..f227042b61b6 100644 --- a/drivers/media/platform/ti/omap3isp/ispcsi2.c +++ b/drivers/media/platform/ti/omap3isp/ispcsi2.c @@ -1251,6 +1251,7 @@ static int csi2_init_entities(struct isp_csi2_device *csi2) | MEDIA_PAD_FL_MUST_CONNECT; me->ops = &csi2_media_ops; + me->function = MEDIA_ENT_F_VID_IF_BRIDGE; ret = media_entity_pads_init(me, CSI2_PADS_NUM, pads); if (ret < 0) return ret; diff --git a/drivers/media/platform/ti/omap3isp/isppreview.c b/drivers/media/platform/ti/omap3isp/isppreview.c index e383a57654de..26f7167d1f4f 100644 --- a/drivers/media/platform/ti/omap3isp/isppreview.c +++ b/drivers/media/platform/ti/omap3isp/isppreview.c @@ -2294,6 +2294,7 @@ static int preview_init_entities(struct isp_prev_device *prev) pads[PREV_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; me->ops = &preview_media_ops; + me->function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; ret = media_entity_pads_init(me, PREV_PADS_NUM, pads); if (ret < 0) goto error_handler_free; diff --git a/drivers/media/platform/ti/omap3isp/ispresizer.c b/drivers/media/platform/ti/omap3isp/ispresizer.c index 87d821b02e5c..5dff48489394 100644 --- a/drivers/media/platform/ti/omap3isp/ispresizer.c +++ b/drivers/media/platform/ti/omap3isp/ispresizer.c @@ -1738,6 +1738,7 @@ static int resizer_init_entities(struct isp_res_device *res) pads[RESZ_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; me->ops = &resizer_media_ops; + me->function = MEDIA_ENT_F_PROC_VIDEO_SCALER; ret = media_entity_pads_init(me, RESZ_PADS_NUM, pads); if (ret < 0) return ret; diff --git a/drivers/media/platform/ti/omap3isp/ispstat.c b/drivers/media/platform/ti/omap3isp/ispstat.c index 07bd62a93d99..64bc71d830c4 100644 --- a/drivers/media/platform/ti/omap3isp/ispstat.c +++ b/drivers/media/platform/ti/omap3isp/ispstat.c @@ -1037,6 +1037,7 @@ static int isp_stat_init_entities(struct ispstat *stat, const char *name, stat->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; me->ops = NULL; + me->function = MEDIA_ENT_F_PROC_VIDEO_STATISTICS; return media_entity_pads_init(me, 1, &stat->pad); } From a03c909815639875f214c9d680839d48fa7cb29f Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 30 Apr 2025 09:17:33 +0200 Subject: [PATCH 012/307] media: omap3isp: add V4L2_CAP_IO_MC and don't set bus_info Since this is a media-centric device set the V4L2_CAP_IO_MC capability. Also don't set bus_info, leave that to the v4l2 core. This fixes v4l2-compliance errors: test MC information (see 'Media Driver Info' above): OK fail: v4l2-compliance.cpp(661): missing bus_info prefix ('media') Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/ispvideo.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/ti/omap3isp/ispvideo.c b/drivers/media/platform/ti/omap3isp/ispvideo.c index 0e7f0bf2b346..46609045e2c8 100644 --- a/drivers/media/platform/ti/omap3isp/ispvideo.c +++ b/drivers/media/platform/ti/omap3isp/ispvideo.c @@ -645,11 +645,9 @@ isp_video_querycap(struct file *file, void *fh, struct v4l2_capability *cap) strscpy(cap->driver, ISP_VIDEO_DRIVER_NAME, sizeof(cap->driver)); strscpy(cap->card, video->video.name, sizeof(cap->card)); - strscpy(cap->bus_info, "media", sizeof(cap->bus_info)); cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT - | V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS; - + | V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS | V4L2_CAP_IO_MC; return 0; } @@ -1451,10 +1449,10 @@ int omap3isp_video_init(struct isp_video *video, const char *name) video->video.ioctl_ops = &isp_video_ioctl_ops; if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) video->video.device_caps = V4L2_CAP_VIDEO_CAPTURE - | V4L2_CAP_STREAMING; + | V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; else video->video.device_caps = V4L2_CAP_VIDEO_OUTPUT - | V4L2_CAP_STREAMING; + | V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; video->pipe.stream_state = ISP_PIPELINE_STREAM_STOPPED; From 44c03802a5191626996ee9db4bac090b164ca340 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Fri, 17 Oct 2025 15:26:40 +0200 Subject: [PATCH 013/307] media: omap3isp: isp_video_mbus_to_pix/pix_to_mbus fixes The isp_video_mbus_to_pix/pix_to_mbus functions did not take the last empty entry { 0, } of the formats array into account. As a result, isp_video_mbus_to_pix would accept code 0 and isp_video_pix_to_mbus would select code 0 if no match was found. Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/ispvideo.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/ti/omap3isp/ispvideo.c b/drivers/media/platform/ti/omap3isp/ispvideo.c index 46609045e2c8..864d38140b87 100644 --- a/drivers/media/platform/ti/omap3isp/ispvideo.c +++ b/drivers/media/platform/ti/omap3isp/ispvideo.c @@ -148,12 +148,12 @@ static unsigned int isp_video_mbus_to_pix(const struct isp_video *video, pix->width = mbus->width; pix->height = mbus->height; - for (i = 0; i < ARRAY_SIZE(formats); ++i) { + for (i = 0; i < ARRAY_SIZE(formats) - 1; ++i) { if (formats[i].code == mbus->code) break; } - if (WARN_ON(i == ARRAY_SIZE(formats))) + if (WARN_ON(i == ARRAY_SIZE(formats) - 1)) return 0; min_bpl = pix->width * formats[i].bpp; @@ -191,7 +191,7 @@ static void isp_video_pix_to_mbus(const struct v4l2_pix_format *pix, /* Skip the last format in the loop so that it will be selected if no * match is found. */ - for (i = 0; i < ARRAY_SIZE(formats) - 1; ++i) { + for (i = 0; i < ARRAY_SIZE(formats) - 2; ++i) { if (formats[i].pixelformat == pix->pixelformat) break; } From 5e72c0c94e420bc56373f80ff9313534ab017020 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 30 Apr 2025 09:27:50 +0200 Subject: [PATCH 014/307] media: omap3isp: implement enum_fmt_vid_cap/out Add missing ioctls. This makes v4l2-compliance happier: fail: v4l2-test-formats.cpp(516): pixelformat 59565955 (UYVY) for buftype 1 not reported by ENUM_FMT test VIDIOC_G_FMT: FAIL fail: v4l2-test-formats.cpp(516): pixelformat 59565955 (UYVY) for buftype 1 not reported by ENUM_FMT test VIDIOC_TRY_FMT: FAIL fail: v4l2-test-formats.cpp(516): pixelformat 56595559 (YUYV) for buftype 1 not reported by ENUM_FMT test VIDIOC_S_FMT: FAIL Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/ispvideo.c | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/ti/omap3isp/ispvideo.c b/drivers/media/platform/ti/omap3isp/ispvideo.c index 864d38140b87..5ce8736ca5bd 100644 --- a/drivers/media/platform/ti/omap3isp/ispvideo.c +++ b/drivers/media/platform/ti/omap3isp/ispvideo.c @@ -35,6 +35,10 @@ /* * NOTE: When adding new media bus codes, always remember to add * corresponding in-memory formats to the table below!!! + * + * If there are multiple entries with the same pixelformat but + * different media bus codes, then keep those together. Otherwise + * isp_video_enum_format() cannot detect duplicate pixelformats. */ static struct isp_format_info formats[] = { { MEDIA_BUS_FMT_Y8_1X8, MEDIA_BUS_FMT_Y8_1X8, @@ -97,12 +101,12 @@ static struct isp_format_info formats[] = { { MEDIA_BUS_FMT_UYVY8_1X16, MEDIA_BUS_FMT_UYVY8_1X16, MEDIA_BUS_FMT_UYVY8_1X16, 0, V4L2_PIX_FMT_UYVY, 16, 2, }, - { MEDIA_BUS_FMT_YUYV8_1X16, MEDIA_BUS_FMT_YUYV8_1X16, - MEDIA_BUS_FMT_YUYV8_1X16, 0, - V4L2_PIX_FMT_YUYV, 16, 2, }, { MEDIA_BUS_FMT_UYVY8_2X8, MEDIA_BUS_FMT_UYVY8_2X8, MEDIA_BUS_FMT_UYVY8_2X8, 0, V4L2_PIX_FMT_UYVY, 8, 2, }, + { MEDIA_BUS_FMT_YUYV8_1X16, MEDIA_BUS_FMT_YUYV8_1X16, + MEDIA_BUS_FMT_YUYV8_1X16, 0, + V4L2_PIX_FMT_YUYV, 16, 2, }, { MEDIA_BUS_FMT_YUYV8_2X8, MEDIA_BUS_FMT_YUYV8_2X8, MEDIA_BUS_FMT_YUYV8_2X8, 0, V4L2_PIX_FMT_YUYV, 8, 2, }, @@ -652,6 +656,33 @@ isp_video_querycap(struct file *file, void *fh, struct v4l2_capability *cap) return 0; } +static int +isp_video_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct isp_video *video = video_drvdata(file); + unsigned int i, j; + + if (f->type != video->type) + return -EINVAL; + + for (i = 0, j = 0; i < ARRAY_SIZE(formats); i++) { + /* Weed out duplicate pixelformats with different mbus codes */ + if (!f->mbus_code && i && + formats[i - 1].pixelformat == formats[i].pixelformat) + continue; + if (f->mbus_code && formats[i].code != f->mbus_code) + continue; + + if (j == f->index) { + f->pixelformat = formats[i].pixelformat; + return 0; + } + j++; + } + + return -EINVAL; +} + static int isp_video_get_format(struct file *file, void *fh, struct v4l2_format *format) { @@ -1258,9 +1289,11 @@ isp_video_s_input(struct file *file, void *fh, unsigned int input) static const struct v4l2_ioctl_ops isp_video_ioctl_ops = { .vidioc_querycap = isp_video_querycap, + .vidioc_enum_fmt_vid_cap = isp_video_enum_format, .vidioc_g_fmt_vid_cap = isp_video_get_format, .vidioc_s_fmt_vid_cap = isp_video_set_format, .vidioc_try_fmt_vid_cap = isp_video_try_format, + .vidioc_enum_fmt_vid_out = isp_video_enum_format, .vidioc_g_fmt_vid_out = isp_video_get_format, .vidioc_s_fmt_vid_out = isp_video_set_format, .vidioc_try_fmt_vid_out = isp_video_try_format, From 2c422fd1a431feb0d8918b11ff37ec613e111d66 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 30 Apr 2025 09:14:24 +0200 Subject: [PATCH 015/307] media: omap3isp: use V4L2_COLORSPACE_SRGB instead of _JPEG JPEG colorspace should generally not be used unless it is actually dealing with JPG data. This fixes v4l2-compliance errors: fail: v4l2-test-formats.cpp(416): pixelformat != V4L2_PIX_FMT_JPEG && pixelformat != V4L2_PIX_FMT_MJPEG && colorspace == V4L2_COLORSPACE_JPEG fail: v4l2-test-formats.cpp(521): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization) test VIDIOC_TRY_FMT: FAIL Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/isppreview.c | 2 +- drivers/media/platform/ti/omap3isp/ispresizer.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/ti/omap3isp/isppreview.c b/drivers/media/platform/ti/omap3isp/isppreview.c index 26f7167d1f4f..9992db782870 100644 --- a/drivers/media/platform/ti/omap3isp/isppreview.c +++ b/drivers/media/platform/ti/omap3isp/isppreview.c @@ -1796,7 +1796,7 @@ static void preview_try_format(struct isp_prev_device *prev, fmt->width = crop->width; fmt->height = crop->height; - fmt->colorspace = V4L2_COLORSPACE_JPEG; + fmt->colorspace = V4L2_COLORSPACE_SRGB; break; } diff --git a/drivers/media/platform/ti/omap3isp/ispresizer.c b/drivers/media/platform/ti/omap3isp/ispresizer.c index 5dff48489394..ad0127f5b5cb 100644 --- a/drivers/media/platform/ti/omap3isp/ispresizer.c +++ b/drivers/media/platform/ti/omap3isp/ispresizer.c @@ -1405,7 +1405,7 @@ static void resizer_try_format(struct isp_res_device *res, break; } - fmt->colorspace = V4L2_COLORSPACE_JPEG; + fmt->colorspace = V4L2_COLORSPACE_SRGB; fmt->field = V4L2_FIELD_NONE; } From 7575b8dfa91f82fcb34ffd5568ff415ac4685794 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 30 Apr 2025 09:21:53 +0200 Subject: [PATCH 016/307] media: omap3isp: set initial format Initialize the v4l2_format to a default. Empty formats are not allowed in V4L2, so this fixes v4l2-compliance issues: fail: v4l2-test-formats.cpp(514): !pix.width || !pix.height test VIDIOC_G_FMT: FAIL Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/ispvideo.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/media/platform/ti/omap3isp/ispvideo.c b/drivers/media/platform/ti/omap3isp/ispvideo.c index 5ce8736ca5bd..c52312b39598 100644 --- a/drivers/media/platform/ti/omap3isp/ispvideo.c +++ b/drivers/media/platform/ti/omap3isp/ispvideo.c @@ -1319,6 +1319,7 @@ static const struct v4l2_ioctl_ops isp_video_ioctl_ops = { static int isp_video_open(struct file *file) { struct isp_video *video = video_drvdata(file); + struct v4l2_mbus_framefmt fmt; struct isp_video_fh *handle; struct vb2_queue *queue; int ret = 0; @@ -1361,6 +1362,13 @@ static int isp_video_open(struct file *file) memset(&handle->format, 0, sizeof(handle->format)); handle->format.type = video->type; + handle->format.fmt.pix.width = 720; + handle->format.fmt.pix.height = 480; + handle->format.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; + handle->format.fmt.pix.field = V4L2_FIELD_NONE; + handle->format.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + isp_video_pix_to_mbus(&handle->format.fmt.pix, &fmt); + isp_video_mbus_to_pix(video, &fmt, &handle->format.fmt.pix); handle->timeperframe.denominator = 1; handle->video = video; From 93ee7d61dc311cfa382cf3461fc7647e0ff62bff Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 30 Apr 2025 09:29:21 +0200 Subject: [PATCH 017/307] media: omap3isp: rework isp_video_try/set_format isp_video_set_format now calls isp_video_try_format first, ensuring consistent behavior and removing duplicate code in both functions. This fixes an v4l2-compliance error: fail: v4l2-test-formats.cpp(519): !pix.sizeimage test VIDIOC_S_FMT: FAIL Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/ispvideo.c | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/drivers/media/platform/ti/omap3isp/ispvideo.c b/drivers/media/platform/ti/omap3isp/ispvideo.c index c52312b39598..adea39b6d930 100644 --- a/drivers/media/platform/ti/omap3isp/ispvideo.c +++ b/drivers/media/platform/ti/omap3isp/ispvideo.c @@ -700,11 +700,15 @@ isp_video_get_format(struct file *file, void *fh, struct v4l2_format *format) } static int -isp_video_set_format(struct file *file, void *fh, struct v4l2_format *format) +isp_video_try_format(struct file *file, void *fh, struct v4l2_format *format) { - struct isp_video_fh *vfh = file_to_isp_video_fh(file); struct isp_video *video = video_drvdata(file); - struct v4l2_mbus_framefmt fmt; + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_subdev *subdev; + u32 pad; + int ret; if (format->type != video->type) return -EINVAL; @@ -744,32 +748,11 @@ isp_video_set_format(struct file *file, void *fh, struct v4l2_format *format) break; } - /* Fill the bytesperline and sizeimage fields by converting to media bus - * format and back to pixel format. - */ - isp_video_pix_to_mbus(&format->fmt.pix, &fmt); - isp_video_mbus_to_pix(video, &fmt, &format->fmt.pix); - - mutex_lock(&video->mutex); - vfh->format = *format; - mutex_unlock(&video->mutex); - - return 0; -} - -static int -isp_video_try_format(struct file *file, void *fh, struct v4l2_format *format) -{ - struct isp_video *video = video_drvdata(file); - struct v4l2_subdev_format fmt = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - }; - struct v4l2_subdev *subdev; - u32 pad; - int ret; - - if (format->type != video->type) - return -EINVAL; + if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + isp_video_pix_to_mbus(&format->fmt.pix, &fmt.format); + isp_video_mbus_to_pix(video, &fmt.format, &format->fmt.pix); + return 0; + } subdev = isp_video_remote_subdev(video, &pad); if (subdev == NULL) @@ -786,6 +769,24 @@ isp_video_try_format(struct file *file, void *fh, struct v4l2_format *format) return 0; } +static int +isp_video_set_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct isp_video_fh *vfh = file_to_isp_video_fh(file); + struct isp_video *video = video_drvdata(file); + int ret; + + ret = isp_video_try_format(file, fh, format); + if (ret) + return ret; + + mutex_lock(&video->mutex); + vfh->format = *format; + mutex_unlock(&video->mutex); + + return 0; +} + static int isp_video_get_selection(struct file *file, void *fh, struct v4l2_selection *sel) { From 5c1f16ec600c22202a144c2a6a9ddb3678310e63 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 30 Apr 2025 09:27:50 +0200 Subject: [PATCH 018/307] media: omap3isp: implement create/prepare_bufs Add missing ioctls. This makes v4l2-compliance happier: warn: v4l2-test-buffers.cpp(813): VIDIOC_CREATE_BUFS not supported test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/ispvideo.c | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/ti/omap3isp/ispvideo.c b/drivers/media/platform/ti/omap3isp/ispvideo.c index adea39b6d930..ac170ef4fa01 100644 --- a/drivers/media/platform/ti/omap3isp/ispvideo.c +++ b/drivers/media/platform/ti/omap3isp/ispvideo.c @@ -329,6 +329,13 @@ static int isp_video_queue_setup(struct vb2_queue *queue, struct isp_video_fh *vfh = vb2_get_drv_priv(queue); struct isp_video *video = vfh->video; + if (*num_planes) { + if (*num_planes != 1) + return -EINVAL; + if (sizes[0] < vfh->format.fmt.pix.sizeimage) + return -EINVAL; + return 0; + } *num_planes = 1; sizes[0] = vfh->format.fmt.pix.sizeimage; @@ -344,6 +351,7 @@ static int isp_video_buffer_prepare(struct vb2_buffer *buf) { struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(buf); struct isp_video_fh *vfh = vb2_get_drv_priv(buf->vb2_queue); + unsigned int size = vfh->format.fmt.pix.sizeimage; struct isp_buffer *buffer = to_isp_buffer(vbuf); struct isp_video *video = vfh->video; dma_addr_t addr; @@ -364,8 +372,13 @@ static int isp_video_buffer_prepare(struct vb2_buffer *buf) return -EINVAL; } - vb2_set_plane_payload(&buffer->vb.vb2_buf, 0, - vfh->format.fmt.pix.sizeimage); + if (vb2_plane_size(&buffer->vb.vb2_buf, 0) < size) { + dev_dbg(video->isp->dev, + "data will not fit into plane (%lu < %u)\n", + vb2_plane_size(&buffer->vb.vb2_buf, 0), size); + return -EINVAL; + } + vb2_set_plane_payload(&buffer->vb.vb2_buf, 0, size); buffer->dma = addr; return 0; @@ -935,6 +948,20 @@ isp_video_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *rb) return ret; } +static int +isp_video_create_bufs(struct file *file, void *fh, struct v4l2_create_buffers *p) +{ + struct isp_video_fh *vfh = file_to_isp_video_fh(file); + struct isp_video *video = video_drvdata(file); + int ret; + + mutex_lock(&video->queue_lock); + ret = vb2_create_bufs(&vfh->queue, p); + mutex_unlock(&video->queue_lock); + + return ret; +} + static int isp_video_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) { @@ -949,6 +976,20 @@ isp_video_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) return ret; } +static int +isp_video_prepare_buf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct isp_video_fh *vfh = file_to_isp_video_fh(file); + struct isp_video *video = video_drvdata(file); + int ret; + + mutex_lock(&video->queue_lock); + ret = vb2_prepare_buf(&vfh->queue, video->video.v4l2_dev->mdev, b); + mutex_unlock(&video->queue_lock); + + return ret; +} + static int isp_video_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) { @@ -1303,7 +1344,9 @@ static const struct v4l2_ioctl_ops isp_video_ioctl_ops = { .vidioc_g_parm = isp_video_get_param, .vidioc_s_parm = isp_video_set_param, .vidioc_reqbufs = isp_video_reqbufs, + .vidioc_create_bufs = isp_video_create_bufs, .vidioc_querybuf = isp_video_querybuf, + .vidioc_prepare_buf = isp_video_prepare_buf, .vidioc_qbuf = isp_video_qbuf, .vidioc_dqbuf = isp_video_dqbuf, .vidioc_streamon = isp_video_streamon, From 335d7956f739d70f78ae803cb618d85e4444fb36 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 7 Oct 2025 14:05:59 +0200 Subject: [PATCH 019/307] media: omap3isp: better VIDIOC_G/S_PARM handling Fix various v4l2-compliance errors relating to timeperframe. VIDIOC_G/S_PARM is only supported for Video Output, so disable these ioctls for Capture devices. Ensure numerator and denominator are never 0. Set missing V4L2_CAP_TIMEPERFRAME capability for VIDIOC_S_PARM. v4l2-compliance: fail: v4l2-test-formats.cpp(1388): out->timeperframe.numerator == 0 || out->timeperframe.denominator == 0 test VIDIOC_G/S_PARM: FAIL Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/ispvideo.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/ti/omap3isp/ispvideo.c b/drivers/media/platform/ti/omap3isp/ispvideo.c index ac170ef4fa01..86cb27b6ca4e 100644 --- a/drivers/media/platform/ti/omap3isp/ispvideo.c +++ b/drivers/media/platform/ti/omap3isp/ispvideo.c @@ -928,7 +928,10 @@ isp_video_set_param(struct file *file, void *fh, struct v4l2_streamparm *a) if (a->parm.output.timeperframe.denominator == 0) a->parm.output.timeperframe.denominator = 1; + if (a->parm.output.timeperframe.numerator == 0) + a->parm.output.timeperframe.numerator = 1; + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; vfh->timeperframe = a->parm.output.timeperframe; return 0; @@ -1413,6 +1416,7 @@ static int isp_video_open(struct file *file) handle->format.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; isp_video_pix_to_mbus(&handle->format.fmt.pix, &fmt); isp_video_mbus_to_pix(video, &fmt, &handle->format.fmt.pix); + handle->timeperframe.numerator = 1; handle->timeperframe.denominator = 1; handle->video = video; @@ -1532,12 +1536,15 @@ int omap3isp_video_init(struct isp_video *video, const char *name) video->video.vfl_type = VFL_TYPE_VIDEO; video->video.release = video_device_release_empty; video->video.ioctl_ops = &isp_video_ioctl_ops; - if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { video->video.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; - else + v4l2_disable_ioctl(&video->video, VIDIOC_S_PARM); + v4l2_disable_ioctl(&video->video, VIDIOC_G_PARM); + } else { video->video.device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; + } video->pipe.stream_state = ISP_PIPELINE_STREAM_STOPPED; From 44c383116d3587662661e94e75842d92fb3a4bee Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 7 Oct 2025 16:18:28 +0200 Subject: [PATCH 020/307] media: omap3isp: support ctrl events for isppreview The preview subdev device was missing V4L2_SUBDEV_FL_HAS_EVENTS, and that prevented VIDIOC_SUBSCRIBE_EVENT from working. Fixes a v4l2-compliance error: fail: v4l2-test-controls.cpp(1128): subscribe event for control 'User Controls' failed test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: FAIL Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/isppreview.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/ti/omap3isp/isppreview.c b/drivers/media/platform/ti/omap3isp/isppreview.c index 9992db782870..3e9e213c6d8a 100644 --- a/drivers/media/platform/ti/omap3isp/isppreview.c +++ b/drivers/media/platform/ti/omap3isp/isppreview.c @@ -2277,7 +2277,7 @@ static int preview_init_entities(struct isp_prev_device *prev) strscpy(sd->name, "OMAP3 ISP preview", sizeof(sd->name)); sd->grp_id = 1 << 16; /* group ID for isp subdevs */ v4l2_set_subdevdata(sd, prev); - sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE; v4l2_ctrl_handler_init(&prev->ctrls, 2); v4l2_ctrl_new_std(&prev->ctrls, &preview_ctrl_ops, V4L2_CID_BRIGHTNESS, From ccd797968922e5e6d0e59aa4883c432e5c906a4b Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 7 Oct 2025 17:06:43 +0200 Subject: [PATCH 021/307] media: omap3isp: ispccp2: always clamp in ccp2_try_format() If ccp2->input == CCP2_INPUT_NONE, then try_format didn't clamp the width and height. This can happen with v4l2-compliance tests. Always clamp. This fixes this v4l2-compliance error: fail: v4l2-test-subdevs.cpp(171): fse.max_width == ~0U || fse.max_height == ~0U fail: v4l2-test-subdevs.cpp(270): ret && ret != ENOTTY test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: FAIL Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- drivers/media/platform/ti/omap3isp/ispccp2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/ti/omap3isp/ispccp2.c b/drivers/media/platform/ti/omap3isp/ispccp2.c index f4147c79639c..d668111b44f4 100644 --- a/drivers/media/platform/ti/omap3isp/ispccp2.c +++ b/drivers/media/platform/ti/omap3isp/ispccp2.c @@ -658,7 +658,7 @@ static void ccp2_try_format(struct isp_ccp2_device *ccp2, fmt->height = clamp_t(u32, fmt->height, ISPCCP2_DAT_SIZE_MIN, ISPCCP2_DAT_SIZE_MAX); - } else if (ccp2->input == CCP2_INPUT_MEMORY) { + } else { fmt->width = clamp_t(u32, fmt->width, ISPCCP2_LCM_HSIZE_COUNT_MIN, ISPCCP2_LCM_HSIZE_COUNT_MAX); From 17e1e1641f74a89824d4de3aa38c78daa5686cc1 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 7 Oct 2025 17:09:18 +0200 Subject: [PATCH 022/307] media: omap3isp: isppreview: always clamp in preview_try_format() If prev->input != PREVIEW_INPUT_MEMORY the width and height weren't clamped. Just always clamp. This fixes a v4l2-compliance error: fail: v4l2-test-subdevs.cpp(171): fse.max_width == ~0U || fse.max_height == ~0U fail: v4l2-test-subdevs.cpp(270): ret && ret != ENOTTY test Try VIDIOC_SUBDEV_ENUM_MBUS_CODE/FRAME_SIZE/FRAME_INTERVAL: FAIL Signed-off-by: Hans Verkuil Acked-by: Sakari Ailus --- .../media/platform/ti/omap3isp/isppreview.c | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/media/platform/ti/omap3isp/isppreview.c b/drivers/media/platform/ti/omap3isp/isppreview.c index 3e9e213c6d8a..3f3b5bd9cdc7 100644 --- a/drivers/media/platform/ti/omap3isp/isppreview.c +++ b/drivers/media/platform/ti/omap3isp/isppreview.c @@ -1742,22 +1742,17 @@ static void preview_try_format(struct isp_prev_device *prev, switch (pad) { case PREV_PAD_SINK: - /* When reading data from the CCDC, the input size has already - * been mangled by the CCDC output pad so it can be accepted - * as-is. - * - * When reading data from memory, clamp the requested width and - * height. The TRM doesn't specify a minimum input height, make + /* + * Clamp the requested width and height. + * The TRM doesn't specify a minimum input height, make * sure we got enough lines to enable the noise filter and color * filter array interpolation. */ - if (prev->input == PREVIEW_INPUT_MEMORY) { - fmt->width = clamp_t(u32, fmt->width, PREV_MIN_IN_WIDTH, - preview_max_out_width(prev)); - fmt->height = clamp_t(u32, fmt->height, - PREV_MIN_IN_HEIGHT, - PREV_MAX_IN_HEIGHT); - } + fmt->width = clamp_t(u32, fmt->width, PREV_MIN_IN_WIDTH, + preview_max_out_width(prev)); + fmt->height = clamp_t(u32, fmt->height, + PREV_MIN_IN_HEIGHT, + PREV_MAX_IN_HEIGHT); fmt->colorspace = V4L2_COLORSPACE_SRGB; From 88e935de7cf8795d7a6a51385db87ecb361a7050 Mon Sep 17 00:00:00 2001 From: Irui Wang Date: Sun, 7 Sep 2025 17:35:56 +0800 Subject: [PATCH 023/307] media: mediatek: encoder: Fix uninitialized scalar variable issue UNINIT checker finds some instances of variables that are used without being initialized, for example using the uninitialized value enc_result.is_key_frm can result in unpredictable behavior, so initialize these variables after declaring. Fixes: 4e855a6efa54 ("[media] vcodec: mediatek: Add Mediatek V4L2 Video Encoder Driver") Cc: stable@vger.kernel.org Signed-off-by: Irui Wang Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c b/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c index 6faf3f659e75..b3a0a1d8b7a8 100644 --- a/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c +++ b/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c @@ -850,7 +850,7 @@ static void vb2ops_venc_buf_queue(struct vb2_buffer *vb) static int vb2ops_venc_start_streaming(struct vb2_queue *q, unsigned int count) { struct mtk_vcodec_enc_ctx *ctx = vb2_get_drv_priv(q); - struct venc_enc_param param; + struct venc_enc_param param = { }; int ret; int i; @@ -1004,7 +1004,7 @@ static int mtk_venc_encode_header(void *priv) int ret; struct vb2_v4l2_buffer *src_buf, *dst_buf; struct mtk_vcodec_mem bs_buf; - struct venc_done_result enc_result; + struct venc_done_result enc_result = { }; dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); if (!dst_buf) { @@ -1125,7 +1125,7 @@ static void mtk_venc_worker(struct work_struct *work) struct vb2_v4l2_buffer *src_buf, *dst_buf; struct venc_frm_buf frm_buf; struct mtk_vcodec_mem bs_buf; - struct venc_done_result enc_result; + struct venc_done_result enc_result = { }; int ret, i; /* check dst_buf, dst_buf may be removed in device_run From e4f63453fd37d79501676c6f32790179909b5042 Mon Sep 17 00:00:00 2001 From: Qianfeng Rong Date: Sun, 7 Sep 2025 17:35:57 +0800 Subject: [PATCH 024/307] media: mediatek: vcodec: use = { } instead of memset() Based on testing and recommendations by David Lechner et al. [1][2], using = { } to initialize a structure or array is the preferred way to do this in the kernel. Converts memset() to = { }, thereby: - Eliminating the risk of sizeof() mismatches. - Simplifying the code. [1]: https://lore.kernel.org/linux-iio/202505090942.48EBF01B@keescook/ [2]: https://lore.kernel.org/lkml/20250614151844.50524610@jic23-huawei/ Signed-off-by: Qianfeng Rong Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../mediatek/vcodec/decoder/vdec/vdec_vp9_if.c | 3 +-- .../mediatek/vcodec/decoder/vdec_vpu_if.c | 12 ++++-------- .../mediatek/vcodec/encoder/mtk_vcodec_enc.c | 6 ++---- .../mediatek/vcodec/encoder/venc_vpu_if.c | 15 +++++---------- 4 files changed, 12 insertions(+), 24 deletions(-) diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_if.c index eb3354192853..80554b2c26c0 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_if.c +++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_if.c @@ -548,10 +548,9 @@ static bool vp9_wait_dec_end(struct vdec_vp9_inst *inst) static struct vdec_vp9_inst *vp9_alloc_inst(struct mtk_vcodec_dec_ctx *ctx) { int result; - struct mtk_vcodec_mem mem; + struct mtk_vcodec_mem mem = { }; struct vdec_vp9_inst *inst; - memset(&mem, 0, sizeof(mem)); mem.size = sizeof(struct vdec_vp9_inst); result = mtk_vcodec_mem_alloc(ctx, &mem); if (result) diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec_vpu_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec_vpu_if.c index 40b97f114cf6..b35759a0b353 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/vdec_vpu_if.c +++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec_vpu_if.c @@ -182,12 +182,11 @@ static int vcodec_vpu_send_msg(struct vdec_vpu_inst *vpu, void *msg, int len) static int vcodec_send_ap_ipi(struct vdec_vpu_inst *vpu, unsigned int msg_id) { - struct vdec_ap_ipi_cmd msg; + struct vdec_ap_ipi_cmd msg = { }; int err = 0; mtk_vdec_debug(vpu->ctx, "+ id=%X", msg_id); - memset(&msg, 0, sizeof(msg)); msg.msg_id = msg_id; if (vpu->fw_abi_version < 2) msg.vpu_inst_addr = vpu->inst_addr; @@ -202,7 +201,7 @@ static int vcodec_send_ap_ipi(struct vdec_vpu_inst *vpu, unsigned int msg_id) int vpu_dec_init(struct vdec_vpu_inst *vpu) { - struct vdec_ap_ipi_init msg; + struct vdec_ap_ipi_init msg = { }; int err; init_waitqueue_head(&vpu->wq); @@ -226,7 +225,6 @@ int vpu_dec_init(struct vdec_vpu_inst *vpu) } } - memset(&msg, 0, sizeof(msg)); msg.msg_id = AP_IPIMSG_DEC_INIT; msg.ap_inst_addr = (unsigned long)vpu; msg.codec_type = vpu->codec_type; @@ -246,7 +244,7 @@ int vpu_dec_init(struct vdec_vpu_inst *vpu) int vpu_dec_start(struct vdec_vpu_inst *vpu, uint32_t *data, unsigned int len) { - struct vdec_ap_ipi_dec_start msg; + struct vdec_ap_ipi_dec_start msg = { }; int i; int err = 0; @@ -255,7 +253,6 @@ int vpu_dec_start(struct vdec_vpu_inst *vpu, uint32_t *data, unsigned int len) return -EINVAL; } - memset(&msg, 0, sizeof(msg)); msg.msg_id = AP_IPIMSG_DEC_START; if (vpu->fw_abi_version < 2) msg.vpu_inst_addr = vpu->inst_addr; @@ -274,7 +271,7 @@ int vpu_dec_start(struct vdec_vpu_inst *vpu, uint32_t *data, unsigned int len) int vpu_dec_get_param(struct vdec_vpu_inst *vpu, uint32_t *data, unsigned int len, unsigned int param_type) { - struct vdec_ap_ipi_get_param msg; + struct vdec_ap_ipi_get_param msg = { }; int err; if (len > ARRAY_SIZE(msg.data)) { @@ -282,7 +279,6 @@ int vpu_dec_get_param(struct vdec_vpu_inst *vpu, uint32_t *data, return -EINVAL; } - memset(&msg, 0, sizeof(msg)); msg.msg_id = AP_IPIMSG_DEC_GET_PARAM; msg.inst_id = vpu->inst_id; memcpy(msg.data, data, sizeof(unsigned int) * len); diff --git a/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c b/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c index b3a0a1d8b7a8..0d4e94463685 100644 --- a/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c +++ b/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c @@ -1049,7 +1049,7 @@ static int mtk_venc_encode_header(void *priv) static int mtk_venc_param_change(struct mtk_vcodec_enc_ctx *ctx) { - struct venc_enc_param enc_prm; + struct venc_enc_param enc_prm = { }; struct vb2_v4l2_buffer *vb2_v4l2 = v4l2_m2m_next_src_buf(ctx->m2m_ctx); struct mtk_video_enc_buf *mtk_buf; int ret = 0; @@ -1060,7 +1060,6 @@ static int mtk_venc_param_change(struct mtk_vcodec_enc_ctx *ctx) mtk_buf = container_of(vb2_v4l2, struct mtk_video_enc_buf, m2m_buf.vb); - memset(&enc_prm, 0, sizeof(enc_prm)); if (mtk_buf->param_change == MTK_ENCODE_PARAM_NONE) return 0; @@ -1123,7 +1122,7 @@ static void mtk_venc_worker(struct work_struct *work) struct mtk_vcodec_enc_ctx *ctx = container_of(work, struct mtk_vcodec_enc_ctx, encode_work); struct vb2_v4l2_buffer *src_buf, *dst_buf; - struct venc_frm_buf frm_buf; + struct venc_frm_buf frm_buf = { }; struct mtk_vcodec_mem bs_buf; struct venc_done_result enc_result = { }; int ret, i; @@ -1153,7 +1152,6 @@ static void mtk_venc_worker(struct work_struct *work) return; } - memset(&frm_buf, 0, sizeof(frm_buf)); for (i = 0; i < src_buf->vb2_buf.num_planes ; i++) { frm_buf.fb_addr[i].dma_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, i); diff --git a/drivers/media/platform/mediatek/vcodec/encoder/venc_vpu_if.c b/drivers/media/platform/mediatek/vcodec/encoder/venc_vpu_if.c index 3c229b1f6b21..0c825aa7224d 100644 --- a/drivers/media/platform/mediatek/vcodec/encoder/venc_vpu_if.c +++ b/drivers/media/platform/mediatek/vcodec/encoder/venc_vpu_if.c @@ -133,7 +133,7 @@ static int vpu_enc_send_msg(struct venc_vpu_inst *vpu, void *msg, int vpu_enc_init(struct venc_vpu_inst *vpu) { int status; - struct venc_ap_ipi_msg_init out; + struct venc_ap_ipi_msg_init out = { }; init_waitqueue_head(&vpu->wq_hd); vpu->signaled = 0; @@ -149,7 +149,6 @@ int vpu_enc_init(struct venc_vpu_inst *vpu) return -EINVAL; } - memset(&out, 0, sizeof(out)); out.msg_id = AP_IPIMSG_ENC_INIT; out.venc_inst = (unsigned long)vpu; if (vpu_enc_send_msg(vpu, &out, sizeof(out))) { @@ -192,11 +191,10 @@ int vpu_enc_set_param(struct venc_vpu_inst *vpu, size_t msg_size = is_ext ? sizeof(struct venc_ap_ipi_msg_set_param_ext) : sizeof(struct venc_ap_ipi_msg_set_param); - struct venc_ap_ipi_msg_set_param_ext out; + struct venc_ap_ipi_msg_set_param_ext out = { }; mtk_venc_debug(vpu->ctx, "id %d ->", id); - memset(&out, 0, sizeof(out)); out.base.msg_id = AP_IPIMSG_ENC_SET_PARAM; out.base.vpu_inst_addr = vpu->inst_addr; out.base.param_id = id; @@ -259,11 +257,10 @@ static int vpu_enc_encode_32bits(struct venc_vpu_inst *vpu, size_t msg_size = is_ext ? sizeof(struct venc_ap_ipi_msg_enc_ext) : sizeof(struct venc_ap_ipi_msg_enc); - struct venc_ap_ipi_msg_enc_ext out; + struct venc_ap_ipi_msg_enc_ext out = { }; mtk_venc_debug(vpu->ctx, "bs_mode %d ->", bs_mode); - memset(&out, 0, sizeof(out)); out.base.msg_id = AP_IPIMSG_ENC_ENCODE; out.base.vpu_inst_addr = vpu->inst_addr; out.base.bs_mode = bs_mode; @@ -303,12 +300,11 @@ static int vpu_enc_encode_34bits(struct venc_vpu_inst *vpu, struct mtk_vcodec_mem *bs_buf, struct venc_frame_info *frame_info) { - struct venc_ap_ipi_msg_enc_ext_34 out; + struct venc_ap_ipi_msg_enc_ext_34 out = { }; size_t msg_size = sizeof(struct venc_ap_ipi_msg_enc_ext_34); mtk_venc_debug(vpu->ctx, "bs_mode %d ->", bs_mode); - memset(&out, 0, sizeof(out)); out.msg_id = AP_IPIMSG_ENC_ENCODE; out.vpu_inst_addr = vpu->inst_addr; out.bs_mode = bs_mode; @@ -368,9 +364,8 @@ int vpu_enc_encode(struct venc_vpu_inst *vpu, unsigned int bs_mode, int vpu_enc_deinit(struct venc_vpu_inst *vpu) { - struct venc_ap_ipi_msg_deinit out; + struct venc_ap_ipi_msg_deinit out = { }; - memset(&out, 0, sizeof(out)); out.msg_id = AP_IPIMSG_ENC_DEINIT; out.vpu_inst_addr = vpu->inst_addr; if (vpu_enc_send_msg(vpu, &out, sizeof(out))) { From 8beb7e982fae2f7b37018e522d8b864691bbf4d3 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 28 Oct 2025 11:04:54 +0100 Subject: [PATCH 025/307] media: mediatek: amend vpu_get_plat_device() documentation Add a comment to the vpu_get_plat_device() documentation to make it clear that the VPU platform device is returned with an incremented reference count (which needs to be dropped after use). Signed-off-by: Johan Hovold Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/mediatek/vpu/mtk_vpu.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/media/platform/mediatek/vpu/mtk_vpu.h b/drivers/media/platform/mediatek/vpu/mtk_vpu.h index da05f3e74081..3951547e9ec5 100644 --- a/drivers/media/platform/mediatek/vpu/mtk_vpu.h +++ b/drivers/media/platform/mediatek/vpu/mtk_vpu.h @@ -119,8 +119,7 @@ int vpu_ipi_send(struct platform_device *pdev, * @pdev: the platform device of the module requesting VPU platform * device for using VPU API. * - * Return: Return NULL if it is failed. - * otherwise it is VPU's platform device + * Return: a reference to the VPU's platform device, or NULL on failure. **/ struct platform_device *vpu_get_plat_device(struct platform_device *pdev); From 8a8a3232abac5b972058a5f2cb3e33199d2a8648 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 8 Oct 2025 16:55:03 +0800 Subject: [PATCH 026/307] media: mtk-mdp: Fix error handling in probe function Add mtk_mdp_unregister_m2m_device() on the error handling path to prevent resource leak. Add check for the return value of vpu_get_plat_device() to prevent null pointer dereference. And vpu_get_plat_device() increases the reference count of the returned platform device. Add platform_device_put() to prevent reference leak. Fixes: c8eb2d7e8202 ("[media] media: Add Mediatek MDP Driver") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/mediatek/mdp/mtk_mdp_core.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c b/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c index 80fdc6ff57e0..f78fa30f1864 100644 --- a/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c @@ -194,11 +194,17 @@ static int mtk_mdp_probe(struct platform_device *pdev) } mdp->vpu_dev = vpu_get_plat_device(pdev); + if (!mdp->vpu_dev) { + dev_err(&pdev->dev, "Failed to get vpu device\n"); + ret = -ENODEV; + goto err_vpu_get_dev; + } + ret = vpu_wdt_reg_handler(mdp->vpu_dev, mtk_mdp_reset_handler, mdp, VPU_RST_MDP); if (ret) { dev_err(&pdev->dev, "Failed to register reset handler\n"); - goto err_m2m_register; + goto err_reg_handler; } platform_set_drvdata(pdev, mdp); @@ -206,7 +212,7 @@ static int mtk_mdp_probe(struct platform_device *pdev) ret = vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32)); if (ret) { dev_err(&pdev->dev, "Failed to set vb2 dma mag seg size\n"); - goto err_m2m_register; + goto err_reg_handler; } pm_runtime_enable(dev); @@ -214,6 +220,12 @@ static int mtk_mdp_probe(struct platform_device *pdev) return 0; +err_reg_handler: + platform_device_put(mdp->vpu_dev); + +err_vpu_get_dev: + mtk_mdp_unregister_m2m_device(mdp); + err_m2m_register: v4l2_device_unregister(&mdp->v4l2_dev); From f128bab57b8018e526b7eda854ca20069863af47 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 8 Oct 2025 17:01:56 +0800 Subject: [PATCH 027/307] media: mtk-mdp: Fix a reference leak bug in mtk_mdp_remove() In mtk_mdp_probe(), vpu_get_plat_device() increases the reference count of the returned platform device. Add platform_device_put() to prevent reference leak. Fixes: c8eb2d7e8202 ("[media] media: Add Mediatek MDP Driver") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/mediatek/mdp/mtk_mdp_core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c b/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c index f78fa30f1864..8432833814f3 100644 --- a/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c @@ -254,6 +254,7 @@ static void mtk_mdp_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); vb2_dma_contig_clear_max_seg_size(&pdev->dev); + platform_device_put(mdp->vpu_dev); mtk_mdp_unregister_m2m_device(mdp); v4l2_device_unregister(&mdp->v4l2_dev); From 88fe301896596da34847871dbdfe261ed1148676 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 17 Oct 2025 17:58:37 +0200 Subject: [PATCH 028/307] media: dt-bindings: nxp,imx8-jpeg: Document optional SRAM support Document optional phandle to mmio-sram, which can describe an SRAM region used for descriptor storage instead of regular DRAM region. Use of SRAM instead of DRAM for descriptor storage may improve bus access pattern and performance. Reviewed-by: Frank Li Signed-off-by: Marek Vasut Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml b/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml index b5aca3d2cc5c..18cc6315a821 100644 --- a/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml +++ b/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml @@ -55,6 +55,12 @@ properties: minItems: 1 # Wrapper and all slots maxItems: 5 # Wrapper and 4 slots + sram: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Optional phandle to a reserved on-chip SRAM regions. The SRAM can + be used for descriptor storage, which may improve bus utilization. + required: - compatible - reg From 241e75fe6b9db206e8df14d305ea7e42fd3a2846 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 17 Oct 2025 17:58:38 +0200 Subject: [PATCH 029/307] media: imx-jpeg: Add support for descriptor allocation from SRAM Add support for optional allocation of bitstream descriptors from SRAM instead of DRAM. In case the encoder/decoder DT node contains 'sram' property which points to 'mmio-sram', the driver will attempt to use the SRAM instead of DRAM for descriptor allocation, which might improve performance. This also helps on i.MX95 rev.A with sporadic SLOTn_STATUS IMG_RD_ERR bit 11 being triggered during JPEG encoding, which is caused by a bug fixed on later SoC revisions. The bug occurs less often when using the SRAM for descriptor storage, but is not entirely mitigated. The following pipeline triggers the bug when descriptors get allocated from DRAM, the pipeline often hangs after a few seconds and the encoder driver indicates "timeout, cancel it": gst-launch-1.0 videotestsrc ! \ video/x-raw,width=256,height=256,format=YUY2 ! \ queue ! v4l2jpegenc ! queue ! fakesink Tested-by: Ming Qian Signed-off-by: Marek Vasut Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/nxp/imx-jpeg/mxc-jpeg.c | 70 +++++++++++-------- .../media/platform/nxp/imx-jpeg/mxc-jpeg.h | 1 + 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c index 9e4a813489c0..b558700d1d96 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -783,32 +784,40 @@ static int mxc_get_free_slot(struct mxc_jpeg_slot_data *slot_data) return -1; } +static void mxc_jpeg_free(struct mxc_jpeg_dev *jpeg, size_t size, void *addr, dma_addr_t handle) +{ + if (jpeg->sram_pool) + gen_pool_free(jpeg->sram_pool, (unsigned long)addr, size); + else + dma_free_coherent(jpeg->dev, size, addr, handle); +} + static void mxc_jpeg_free_slot_data(struct mxc_jpeg_dev *jpeg) { /* free descriptor for decoding/encoding phase */ - dma_free_coherent(jpeg->dev, sizeof(struct mxc_jpeg_desc), - jpeg->slot_data.desc, - jpeg->slot_data.desc_handle); + mxc_jpeg_free(jpeg, sizeof(struct mxc_jpeg_desc), + jpeg->slot_data.desc, + jpeg->slot_data.desc_handle); jpeg->slot_data.desc = NULL; jpeg->slot_data.desc_handle = 0; /* free descriptor for encoder configuration phase / decoder DHT */ - dma_free_coherent(jpeg->dev, sizeof(struct mxc_jpeg_desc), - jpeg->slot_data.cfg_desc, - jpeg->slot_data.cfg_desc_handle); + mxc_jpeg_free(jpeg, sizeof(struct mxc_jpeg_desc), + jpeg->slot_data.cfg_desc, + jpeg->slot_data.cfg_desc_handle); jpeg->slot_data.cfg_desc_handle = 0; jpeg->slot_data.cfg_desc = NULL; /* free configuration stream */ - dma_free_coherent(jpeg->dev, MXC_JPEG_MAX_CFG_STREAM, - jpeg->slot_data.cfg_stream_vaddr, - jpeg->slot_data.cfg_stream_handle); + mxc_jpeg_free(jpeg, MXC_JPEG_MAX_CFG_STREAM, + jpeg->slot_data.cfg_stream_vaddr, + jpeg->slot_data.cfg_stream_handle); jpeg->slot_data.cfg_stream_vaddr = NULL; jpeg->slot_data.cfg_stream_handle = 0; - dma_free_coherent(jpeg->dev, jpeg->slot_data.cfg_dec_size, - jpeg->slot_data.cfg_dec_vaddr, - jpeg->slot_data.cfg_dec_daddr); + mxc_jpeg_free(jpeg, jpeg->slot_data.cfg_dec_size, + jpeg->slot_data.cfg_dec_vaddr, + jpeg->slot_data.cfg_dec_daddr); jpeg->slot_data.cfg_dec_size = 0; jpeg->slot_data.cfg_dec_vaddr = NULL; jpeg->slot_data.cfg_dec_daddr = 0; @@ -816,6 +825,15 @@ static void mxc_jpeg_free_slot_data(struct mxc_jpeg_dev *jpeg) jpeg->slot_data.used = false; } +static struct mxc_jpeg_desc *mxc_jpeg_alloc(struct mxc_jpeg_dev *jpeg, size_t size, + dma_addr_t *handle) +{ + if (jpeg->sram_pool) + return gen_pool_dma_zalloc(jpeg->sram_pool, size, handle); + else + return dma_alloc_coherent(jpeg->dev, size, handle, GFP_ATOMIC); +} + static bool mxc_jpeg_alloc_slot_data(struct mxc_jpeg_dev *jpeg) { struct mxc_jpeg_desc *desc; @@ -826,37 +844,29 @@ static bool mxc_jpeg_alloc_slot_data(struct mxc_jpeg_dev *jpeg) goto skip_alloc; /* already allocated, reuse it */ /* allocate descriptor for decoding/encoding phase */ - desc = dma_alloc_coherent(jpeg->dev, - sizeof(struct mxc_jpeg_desc), - &jpeg->slot_data.desc_handle, - GFP_ATOMIC); + desc = mxc_jpeg_alloc(jpeg, sizeof(struct mxc_jpeg_desc), + &jpeg->slot_data.desc_handle); if (!desc) goto err; jpeg->slot_data.desc = desc; /* allocate descriptor for configuration phase (encoder only) */ - cfg_desc = dma_alloc_coherent(jpeg->dev, - sizeof(struct mxc_jpeg_desc), - &jpeg->slot_data.cfg_desc_handle, - GFP_ATOMIC); + cfg_desc = mxc_jpeg_alloc(jpeg, sizeof(struct mxc_jpeg_desc), + &jpeg->slot_data.cfg_desc_handle); if (!cfg_desc) goto err; jpeg->slot_data.cfg_desc = cfg_desc; /* allocate configuration stream */ - cfg_stm = dma_alloc_coherent(jpeg->dev, - MXC_JPEG_MAX_CFG_STREAM, - &jpeg->slot_data.cfg_stream_handle, - GFP_ATOMIC); + cfg_stm = mxc_jpeg_alloc(jpeg, MXC_JPEG_MAX_CFG_STREAM, + &jpeg->slot_data.cfg_stream_handle); if (!cfg_stm) goto err; jpeg->slot_data.cfg_stream_vaddr = cfg_stm; jpeg->slot_data.cfg_dec_size = MXC_JPEG_PATTERN_WIDTH * MXC_JPEG_PATTERN_HEIGHT * 2; - jpeg->slot_data.cfg_dec_vaddr = dma_alloc_coherent(jpeg->dev, - jpeg->slot_data.cfg_dec_size, - &jpeg->slot_data.cfg_dec_daddr, - GFP_ATOMIC); + jpeg->slot_data.cfg_dec_vaddr = mxc_jpeg_alloc(jpeg, jpeg->slot_data.cfg_dec_size, + &jpeg->slot_data.cfg_dec_daddr); if (!jpeg->slot_data.cfg_dec_vaddr) goto err; @@ -2884,6 +2894,10 @@ static int mxc_jpeg_probe(struct platform_device *pdev) jpeg->dev = dev; jpeg->mode = mode; + /* SRAM pool is optional */ + jpeg->sram_pool = of_gen_pool_get(pdev->dev.of_node, "sram", 0); + dev_info(dev, "Using DMA descriptor pool in %cRAM\n", jpeg->sram_pool ? 'S' : 'D'); + /* Get clocks */ ret = devm_clk_bulk_get_all(&pdev->dev, &jpeg->clks); if (ret < 0) { diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h index 44e46face6d1..9c5b4f053ded 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h @@ -141,6 +141,7 @@ struct mxc_jpeg_dev { int num_domains; struct device **pd_dev; struct device_link **pd_link; + struct gen_pool *sram_pool; }; /** From b4e26c6fc1b3c225caf80d4a95c6f9fcbe959e17 Mon Sep 17 00:00:00 2001 From: Brandon Brnich Date: Tue, 21 Oct 2025 15:46:17 -0500 Subject: [PATCH 030/307] media: chips-media: wave5: Fix conditional in start_streaming When STREAMON(CAP) is called after STREAMON(OUT), the driver was failing to switch states from VPU_INST_STATE_OPEN to VPU_INST_STATE_INIT_SEQ and VPU_INST_STATE_PIC_RUN because the capture queue streaming boolean had not yet been set to true. This led to a hang in the encoder since the state was stuck in VPU_INST_STATE_OPEN. During the second call to start_streaming, the sequence initialization and frame buffer allocation should occur. Signed-off-by: Brandon Brnich Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c index 9bfaa9fb3ceb..bca881a0a161 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c @@ -1367,7 +1367,8 @@ static int wave5_vpu_enc_start_streaming(struct vb2_queue *q, unsigned int count if (ret) goto return_buffers; } - if (inst->state == VPU_INST_STATE_OPEN && m2m_ctx->cap_q_ctx.q.streaming) { + if (inst->state == VPU_INST_STATE_OPEN && + (m2m_ctx->cap_q_ctx.q.streaming || q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) { ret = initialize_sequence(inst); if (ret) { dev_warn(inst->dev->dev, "Sequence not found: %d\n", ret); From 5da0380de41439ed64ed9a5218850db38544e315 Mon Sep 17 00:00:00 2001 From: Brandon Brnich Date: Tue, 21 Oct 2025 15:46:18 -0500 Subject: [PATCH 031/307] media: chips-media: wave5: Process ready frames when CMD_STOP sent to Encoder CMD_STOP being sent to encoder before last job is executed by device_run can lead to an occasional dropped frame. Ensure that remaining ready buffers are drained by making a call to v4l2_m2m_try_schedule. Signed-off-by: Brandon Brnich Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c index bca881a0a161..8e3dd9e34abc 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c @@ -649,6 +649,8 @@ static int wave5_vpu_enc_encoder_cmd(struct file *file, void *fh, struct v4l2_en m2m_ctx->last_src_buf = v4l2_m2m_last_src_buf(m2m_ctx); m2m_ctx->is_draining = true; + + v4l2_m2m_try_schedule(m2m_ctx); break; case V4L2_ENC_CMD_START: break; From 9cf4452e824c1e2d41c9c0b13cc8a32a0a7dec38 Mon Sep 17 00:00:00 2001 From: Xulin Sun Date: Thu, 4 Dec 2025 17:41:51 +0800 Subject: [PATCH 032/307] media: chips-media: wave5: Fix PM runtime usage count underflow Replace pm_runtime_put_sync() with pm_runtime_dont_use_autosuspend() in the remove path to properly pair with pm_runtime_use_autosuspend() from probe. This allows pm_runtime_disable() to handle reference count cleanup correctly regardless of current suspend state. The driver calls pm_runtime_put_sync() unconditionally in remove, but the device may already be suspended due to autosuspend configured in probe. When autosuspend has already suspended the device, the usage count is 0, and pm_runtime_put_sync() decrements it to -1. This causes the following warning on module unload: ------------[ cut here ]------------ WARNING: CPU: 1 PID: 963 at kernel/kthread.c:1430 kthread_destroy_worker+0x84/0x98 ... vdec 30210000.video-codec: Runtime PM usage count underflow! Fixes: 9707a6254a8a ("media: chips-media: wave5: Add the v4l2 layer") Cc: stable@vger.kernel.org Signed-off-by: Xulin Sun Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/chips-media/wave5/wave5-vpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu.c b/drivers/media/platform/chips-media/wave5/wave5-vpu.c index e1715d3f43b0..23aa3ab51a0e 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu.c @@ -356,7 +356,7 @@ static void wave5_vpu_remove(struct platform_device *pdev) hrtimer_cancel(&dev->hrtimer); } - pm_runtime_put_sync(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_disable(&pdev->dev); mutex_destroy(&dev->dev_lock); From 5a0c122e834b2f7f029526422c71be922960bf03 Mon Sep 17 00:00:00 2001 From: Xulin Sun Date: Thu, 4 Dec 2025 17:41:52 +0800 Subject: [PATCH 033/307] media: chips-media: wave5: Fix kthread worker destruction in polling mode Fix the cleanup order in polling mode (irq < 0) to prevent kernel warnings during module removal. Cancel the hrtimer before destroying the kthread worker to ensure work queues are empty. In polling mode, the driver uses hrtimer to periodically trigger wave5_vpu_timer_callback() which queues work via kthread_queue_work(). The kthread_destroy_worker() function validates that both work queues are empty with WARN_ON(!list_empty(&worker->work_list)) and WARN_ON(!list_empty(&worker->delayed_work_list)). The original code called kthread_destroy_worker() before hrtimer_cancel(), creating a race condition where the timer could fire during worker destruction and queue new work, triggering the WARN_ON. This causes the following warning on every module unload in polling mode: ------------[ cut here ]------------ WARNING: CPU: 2 PID: 1034 at kernel/kthread.c:1430 kthread_destroy_worker+0x84/0x98 Modules linked in: wave5(-) rpmsg_ctrl rpmsg_char ... Call trace: kthread_destroy_worker+0x84/0x98 wave5_vpu_remove+0xc8/0xe0 [wave5] platform_remove+0x30/0x58 ... ---[ end trace 0000000000000000 ]--- Fixes: ed7276ed2fd0 ("media: chips-media: wave5: Add hrtimer based polling support") Cc: stable@vger.kernel.org Signed-off-by: Xulin Sun Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/chips-media/wave5/wave5-vpu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu.c b/drivers/media/platform/chips-media/wave5/wave5-vpu.c index 23aa3ab51a0e..0bcd48df49d0 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu.c @@ -352,8 +352,9 @@ static void wave5_vpu_remove(struct platform_device *pdev) struct vpu_device *dev = dev_get_drvdata(&pdev->dev); if (dev->irq < 0) { - kthread_destroy_worker(dev->worker); hrtimer_cancel(&dev->hrtimer); + kthread_cancel_work_sync(&dev->work); + kthread_destroy_worker(dev->worker); } pm_runtime_dont_use_autosuspend(&pdev->dev); From b74cedac643b02aefa7da881b58a3792859d9748 Mon Sep 17 00:00:00 2001 From: Xulin Sun Date: Thu, 4 Dec 2025 17:41:53 +0800 Subject: [PATCH 034/307] media: chips-media: wave5: Fix device cleanup order to prevent kernel panic Move video device unregistration to the beginning of the remove function to ensure all video operations are stopped before cleaning up the worker thread and disabling PM runtime. This prevents hardware register access after the device has been powered down. In polling mode, the hrtimer periodically triggers wave5_vpu_timer_callback() which queues work to the kthread worker. The worker executes wave5_vpu_irq_work_fn() which reads hardware registers via wave5_vdi_read_register(). The original cleanup order disabled PM runtime and powered down hardware before unregistering video devices. When autosuspend triggers and powers off the hardware, the video devices are still registered and the worker thread can still be triggered by the hrtimer, causing it to attempt reading registers from powered-off hardware. This results in a bus error (synchronous external abort) and kernel panic. This causes random kernel panics during encoding operations: Internal error: synchronous external abort: 0000000096000010 [#1] PREEMPT SMP Modules linked in: wave5 rpmsg_ctrl rpmsg_char ... CPU: 0 UID: 0 PID: 1520 Comm: vpu_irq_thread Tainted: G M W pc : wave5_vdi_read_register+0x10/0x38 [wave5] lr : wave5_vpu_irq_work_fn+0x28/0x60 [wave5] Call trace: wave5_vdi_read_register+0x10/0x38 [wave5] kthread_worker_fn+0xd8/0x238 kthread+0x104/0x120 ret_from_fork+0x10/0x20 Code: aa1e03e9 d503201f f9416800 8b214000 (b9400000) ---[ end trace 0000000000000000 ]--- Kernel panic - not syncing: synchronous external abort: Fatal exception Fixes: 9707a6254a8a ("media: chips-media: wave5: Add the v4l2 layer") Cc: stable@vger.kernel.org Signed-off-by: Xulin Sun Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/chips-media/wave5/wave5-vpu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu.c b/drivers/media/platform/chips-media/wave5/wave5-vpu.c index 0bcd48df49d0..77d6c934d0b9 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu.c @@ -351,6 +351,10 @@ static void wave5_vpu_remove(struct platform_device *pdev) { struct vpu_device *dev = dev_get_drvdata(&pdev->dev); + wave5_vpu_enc_unregister_device(dev); + wave5_vpu_dec_unregister_device(dev); + v4l2_device_unregister(&dev->v4l2_dev); + if (dev->irq < 0) { hrtimer_cancel(&dev->hrtimer); kthread_cancel_work_sync(&dev->work); @@ -364,9 +368,6 @@ static void wave5_vpu_remove(struct platform_device *pdev) mutex_destroy(&dev->hw_lock); reset_control_assert(dev->resets); clk_bulk_disable_unprepare(dev->num_clks, dev->clks); - wave5_vpu_enc_unregister_device(dev); - wave5_vpu_dec_unregister_device(dev); - v4l2_device_unregister(&dev->v4l2_dev); wave5_vdi_release(&pdev->dev); ida_destroy(&dev->inst_ida); } From cbb9c0d50e471483cced55f5b7db4569dcd959a6 Mon Sep 17 00:00:00 2001 From: Jackson Lee Date: Wed, 19 Nov 2025 15:25:46 +0900 Subject: [PATCH 035/307] media: chips-media: wave5: Fix SError of kernel panic when closed SError of kernel panic rarely happened while testing fluster. The root cause was to enter suspend mode because timeout of autosuspend delay happened. [ 48.834439] SError Interrupt on CPU0, code 0x00000000bf000000 -- SError [ 48.834455] CPU: 0 UID: 0 PID: 1067 Comm: v4l2h265dec0:sr Not tainted 6.12.9-gc9e21a1ebd75-dirty #7 [ 48.834461] Hardware name: ti Texas Instruments J721S2 EVM/Texas Instruments J721S2 EVM, BIOS 2025.01-00345-gbaf3aaa8ecfa 01/01/2025 [ 48.834464] pstate: 20000005 (nzCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 48.834468] pc : wave5_dec_clr_disp_flag+0x40/0x80 [wave5] [ 48.834488] lr : wave5_dec_clr_disp_flag+0x40/0x80 [wave5] [ 48.834495] sp : ffff8000856e3a30 [ 48.834497] x29: ffff8000856e3a30 x28: ffff0008093f6010 x27: ffff000809158130 [ 48.834504] x26: 0000000000000000 x25: ffff00080b625000 x24: ffff000804a9ba80 [ 48.834509] x23: ffff000802343028 x22: ffff000809158150 x21: ffff000802218000 [ 48.834513] x20: ffff0008093f6000 x19: ffff0008093f6000 x18: 0000000000000000 [ 48.834518] x17: 0000000000000000 x16: 0000000000000000 x15: 0000ffff74009618 [ 48.834523] x14: 000000010000000c x13: 0000000000000000 x12: 0000000000000000 [ 48.834527] x11: ffffffffffffffff x10: ffffffffffffffff x9 : ffff000802343028 [ 48.834532] x8 : ffff00080b6252a0 x7 : 0000000000000038 x6 : 0000000000000000 [ 48.834536] x5 : ffff00080b625060 x4 : 0000000000000000 x3 : 0000000000000000 [ 48.834541] x2 : 0000000000000000 x1 : ffff800084bf0118 x0 : ffff800084bf0000 [ 48.834547] Kernel panic - not syncing: Asynchronous SError Interrupt [ 48.834549] CPU: 0 UID: 0 PID: 1067 Comm: v4l2h265dec0:sr Not tainted 6.12.9-gc9e21a1ebd75-dirty #7 [ 48.834554] Hardware name: ti Texas Instruments J721S2 EVM/Texas Instruments J721S2 EVM, BIOS 2025.01-00345-gbaf3aaa8ecfa 01/01/2025 [ 48.834556] Call trace: [ 48.834559] dump_backtrace+0x94/0xec [ 48.834574] show_stack+0x18/0x24 [ 48.834579] dump_stack_lvl+0x38/0x90 [ 48.834585] dump_stack+0x18/0x24 [ 48.834588] panic+0x35c/0x3e0 [ 48.834592] nmi_panic+0x40/0x8c [ 48.834595] arm64_serror_panic+0x64/0x70 [ 48.834598] do_serror+0x3c/0x78 [ 48.834601] el1h_64_error_handler+0x34/0x4c [ 48.834605] el1h_64_error+0x64/0x68 [ 48.834608] wave5_dec_clr_disp_flag+0x40/0x80 [wave5] [ 48.834615] wave5_vpu_dec_clr_disp_flag+0x54/0x80 [wave5] [ 48.834622] wave5_vpu_dec_buf_queue+0x19c/0x1a0 [wave5] [ 48.834628] __enqueue_in_driver+0x3c/0x74 [videobuf2_common] [ 48.834639] vb2_core_qbuf+0x508/0x61c [videobuf2_common] [ 48.834646] vb2_qbuf+0xa4/0x168 [videobuf2_v4l2] [ 48.834656] v4l2_m2m_qbuf+0x80/0x238 [v4l2_mem2mem] [ 48.834666] v4l2_m2m_ioctl_qbuf+0x18/0x24 [v4l2_mem2mem] [ 48.834673] v4l_qbuf+0x48/0x5c [videodev] [ 48.834704] __video_do_ioctl+0x180/0x3f0 [videodev] [ 48.834725] video_usercopy+0x2ec/0x68c [videodev] [ 48.834745] video_ioctl2+0x18/0x24 [videodev] [ 48.834766] v4l2_ioctl+0x40/0x60 [videodev] [ 48.834786] __arm64_sys_ioctl+0xa8/0xec [ 48.834793] invoke_syscall+0x44/0x100 [ 48.834800] el0_svc_common.constprop.0+0xc0/0xe0 [ 48.834804] do_el0_svc+0x1c/0x28 [ 48.834809] el0_svc+0x30/0xd0 [ 48.834813] el0t_64_sync_handler+0xc0/0xc4 [ 48.834816] el0t_64_sync+0x190/0x194 [ 48.834820] SMP: stopping secondary CPUs [ 48.834831] Kernel Offset: disabled [ 48.834833] CPU features: 0x08,00002002,80200000,4200421b [ 48.834837] Memory Limit: none [ 49.161404] ---[ end Kernel panic - not syncing: Asynchronous SError Interrupt ]--- Fixes: 2092b3833487 ("media: chips-media: wave5: Support runtime suspend/resume") Cc: stable@vger.kernel.org Signed-off-by: Jackson Lee Signed-off-by: Nas Chung Reviewed-by: Nicolas Dufresne Tested-by: Brandon Brnich Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../platform/chips-media/wave5/wave5-vpu-dec.c | 5 ++--- .../platform/chips-media/wave5/wave5-vpu-enc.c | 3 --- .../media/platform/chips-media/wave5/wave5-vpu.c | 2 +- .../platform/chips-media/wave5/wave5-vpuapi.c | 15 --------------- 4 files changed, 3 insertions(+), 22 deletions(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c index e3038c18ca36..528387c6ecf6 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c @@ -1243,6 +1243,7 @@ static void wave5_vpu_dec_buf_queue_dst(struct vb2_buffer *vb) struct vpu_instance *inst = vb2_get_drv_priv(vb->vb2_queue); struct v4l2_m2m_ctx *m2m_ctx = inst->v4l2_fh.m2m_ctx; + pm_runtime_resume_and_get(inst->dev->dev); vbuf->sequence = inst->queued_dst_buf_num++; if (inst->state == VPU_INST_STATE_PIC_RUN) { @@ -1275,6 +1276,7 @@ static void wave5_vpu_dec_buf_queue_dst(struct vb2_buffer *vb) } else { v4l2_m2m_buf_queue(m2m_ctx, vbuf); } + pm_runtime_put_autosuspend(inst->dev->dev); } static void wave5_vpu_dec_buf_queue(struct vb2_buffer *vb) @@ -1825,9 +1827,6 @@ static int wave5_vpu_open_dec(struct file *filp) if (ret) goto cleanup_inst; - if (list_empty(&dev->instances)) - pm_runtime_use_autosuspend(inst->dev->dev); - list_add_tail(&inst->list, &dev->instances); mutex_unlock(&dev->dev_lock); diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c index 8e3dd9e34abc..3be79dda3a2e 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c @@ -1771,9 +1771,6 @@ static int wave5_vpu_open_enc(struct file *filp) if (ret) goto cleanup_inst; - if (list_empty(&dev->instances)) - pm_runtime_use_autosuspend(inst->dev->dev); - list_add_tail(&inst->list, &dev->instances); mutex_unlock(&dev->dev_lock); diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu.c b/drivers/media/platform/chips-media/wave5/wave5-vpu.c index 77d6c934d0b9..0026f5840362 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu.c @@ -322,7 +322,7 @@ static int wave5_vpu_probe(struct platform_device *pdev) dev_info(&pdev->dev, "Product Code: 0x%x\n", dev->product_code); dev_info(&pdev->dev, "Firmware Revision: %u\n", fw_revision); - pm_runtime_set_autosuspend_delay(&pdev->dev, 100); + pm_runtime_set_autosuspend_delay(&pdev->dev, 500); pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_enable(&pdev->dev); wave5_vpu_sleep_wake(&pdev->dev, true, NULL, 0); diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c index e5e879a13e8b..e94d6ebc9f81 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c @@ -207,8 +207,6 @@ int wave5_vpu_dec_close(struct vpu_instance *inst, u32 *fail_res) int retry = 0; struct vpu_device *vpu_dev = inst->dev; int i; - int inst_count = 0; - struct vpu_instance *inst_elm; *fail_res = 0; if (!inst->codec_info) @@ -250,11 +248,6 @@ int wave5_vpu_dec_close(struct vpu_instance *inst, u32 *fail_res) wave5_vdi_free_dma_memory(vpu_dev, &p_dec_info->vb_task); - list_for_each_entry(inst_elm, &vpu_dev->instances, list) - inst_count++; - if (inst_count == 1) - pm_runtime_dont_use_autosuspend(vpu_dev->dev); - unlock_and_return: mutex_unlock(&vpu_dev->hw_lock); pm_runtime_put_sync(inst->dev->dev); @@ -720,8 +713,6 @@ int wave5_vpu_enc_close(struct vpu_instance *inst, u32 *fail_res) int ret; int retry = 0; struct vpu_device *vpu_dev = inst->dev; - int inst_count = 0; - struct vpu_instance *inst_elm; *fail_res = 0; if (!inst->codec_info) @@ -764,12 +755,6 @@ int wave5_vpu_enc_close(struct vpu_instance *inst, u32 *fail_res) } wave5_vdi_free_dma_memory(vpu_dev, &p_enc_info->vb_task); - - list_for_each_entry(inst_elm, &vpu_dev->instances, list) - inst_count++; - if (inst_count == 1) - pm_runtime_dont_use_autosuspend(vpu_dev->dev); - mutex_unlock(&vpu_dev->hw_lock); pm_runtime_put_sync(inst->dev->dev); From e66ff2b08e4ee1c4d3b84f24818e5bcc178cc3a4 Mon Sep 17 00:00:00 2001 From: Jackson Lee Date: Wed, 19 Nov 2025 15:25:47 +0900 Subject: [PATCH 036/307] media: chips-media: wave5: Fix Null reference while testing fluster When multi instances are created/destroyed, many interrupts happens and structures for decoder are removed. "struct vpu_instance" this structure is shared for all flow in the decoder, so if the structure is not protected by lock, Null dereference could happens sometimes. IRQ Handler was spilt to two phases and Lock was added as well. Fixes: 9707a6254a8a ("media: chips-media: wave5: Add the v4l2 layer") Cc: stable@vger.kernel.org Signed-off-by: Jackson Lee Signed-off-by: Nas Chung Tested-by: Brandon Brnich Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../platform/chips-media/wave5/wave5-helper.c | 28 +++++- .../platform/chips-media/wave5/wave5-helper.h | 1 + .../chips-media/wave5/wave5-vpu-dec.c | 5 + .../chips-media/wave5/wave5-vpu-enc.c | 5 + .../platform/chips-media/wave5/wave5-vpu.c | 99 ++++++++++++++++--- .../platform/chips-media/wave5/wave5-vpuapi.h | 6 ++ 6 files changed, 132 insertions(+), 12 deletions(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-helper.c b/drivers/media/platform/chips-media/wave5/wave5-helper.c index f03ad9c0de22..53a0ac068c2e 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-helper.c +++ b/drivers/media/platform/chips-media/wave5/wave5-helper.c @@ -27,6 +27,11 @@ const char *state_to_str(enum vpu_instance_state state) } } +int wave5_kfifo_alloc(struct vpu_instance *inst) +{ + return kfifo_alloc(&inst->irq_status, 16 * sizeof(int), GFP_KERNEL); +} + void wave5_cleanup_instance(struct vpu_instance *inst, struct file *filp) { int i; @@ -49,7 +54,7 @@ void wave5_cleanup_instance(struct vpu_instance *inst, struct file *filp) v4l2_fh_del(&inst->v4l2_fh, filp); v4l2_fh_exit(&inst->v4l2_fh); } - list_del_init(&inst->list); + kfifo_free(&inst->irq_status); ida_free(&inst->dev->inst_ida, inst->id); kfree(inst->codec_info); kfree(inst); @@ -61,8 +66,29 @@ int wave5_vpu_release_device(struct file *filp, { struct vpu_instance *inst = file_to_vpu_inst(filp); int ret = 0; + unsigned long flags; v4l2_m2m_ctx_release(inst->v4l2_fh.m2m_ctx); + /* + * To prevent Null reference exception, the existing irq handler were + * separated to two modules. + * One is to queue interrupt reason into the irq handler, + * the other is irq_thread to call the wave5_vpu_dec_finish_decode + * to get decoded frame. + * The list of instances should be protected between all flow of the + * decoding process, but to protect the list in the irq_handler, spin lock + * should be used, and mutex should be used in the irq_thread because spin lock + * is not able to be used because mutex is already being used + * in the wave5_vpu_dec_finish_decode. + * So the spin lock and mutex were used to protect the list in the release function. + */ + ret = mutex_lock_interruptible(&inst->dev->irq_lock); + if (ret) + return ret; + spin_lock_irqsave(&inst->dev->irq_spinlock, flags); + list_del_init(&inst->list); + spin_unlock_irqrestore(&inst->dev->irq_spinlock, flags); + mutex_unlock(&inst->dev->irq_lock); if (inst->state != VPU_INST_STATE_NONE) { u32 fail_res; diff --git a/drivers/media/platform/chips-media/wave5/wave5-helper.h b/drivers/media/platform/chips-media/wave5/wave5-helper.h index 976a402e426f..d61fdbda359d 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-helper.h +++ b/drivers/media/platform/chips-media/wave5/wave5-helper.h @@ -33,4 +33,5 @@ void wave5_update_pix_fmt(struct v4l2_pix_format_mplane *pix_mp, unsigned int width, unsigned int height, const struct v4l2_frmsize_stepwise *frmsize); +int wave5_kfifo_alloc(struct vpu_instance *inst); #endif diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c index 528387c6ecf6..b10396fa2379 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c @@ -1808,6 +1808,11 @@ static int wave5_vpu_open_dec(struct file *filp) inst->xfer_func = V4L2_XFER_FUNC_DEFAULT; init_completion(&inst->irq_done); + ret = wave5_kfifo_alloc(inst); + if (ret) { + dev_err(inst->dev->dev, "failed to allocate fifo\n"); + goto cleanup_inst; + } inst->id = ida_alloc(&inst->dev->inst_ida, GFP_KERNEL); if (inst->id < 0) { diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c index 3be79dda3a2e..e69bef5d1b52 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c @@ -1757,6 +1757,11 @@ static int wave5_vpu_open_enc(struct file *filp) inst->frame_rate = 30; init_completion(&inst->irq_done); + ret = wave5_kfifo_alloc(inst); + if (ret) { + dev_err(inst->dev->dev, "failed to allocate fifo\n"); + goto cleanup_inst; + } inst->id = ida_alloc(&inst->dev->inst_ida, GFP_KERNEL); if (inst->id < 0) { diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu.c b/drivers/media/platform/chips-media/wave5/wave5-vpu.c index 0026f5840362..3216b4997644 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu.c @@ -51,8 +51,11 @@ static void wave5_vpu_handle_irq(void *dev_id) u32 seq_done; u32 cmd_done; u32 irq_reason; - struct vpu_instance *inst; + u32 irq_subreason; + struct vpu_instance *inst, *tmp; struct vpu_device *dev = dev_id; + int val; + unsigned long flags; irq_reason = wave5_vdi_read_register(dev, W5_VPU_VINT_REASON); seq_done = wave5_vdi_read_register(dev, W5_RET_SEQ_DONE_INSTANCE_INFO); @@ -60,7 +63,8 @@ static void wave5_vpu_handle_irq(void *dev_id) wave5_vdi_write_register(dev, W5_VPU_VINT_REASON_CLR, irq_reason); wave5_vdi_write_register(dev, W5_VPU_VINT_CLEAR, 0x1); - list_for_each_entry(inst, &dev->instances, list) { + spin_lock_irqsave(&dev->irq_spinlock, flags); + list_for_each_entry_safe(inst, tmp, &dev->instances, list) { if (irq_reason & BIT(INT_WAVE5_INIT_SEQ) || irq_reason & BIT(INT_WAVE5_ENC_SET_PARAM)) { @@ -82,22 +86,54 @@ static void wave5_vpu_handle_irq(void *dev_id) irq_reason & BIT(INT_WAVE5_ENC_PIC)) { if (cmd_done & BIT(inst->id)) { cmd_done &= ~BIT(inst->id); - wave5_vdi_write_register(dev, W5_RET_QUEUE_CMD_DONE_INST, - cmd_done); - inst->ops->finish_process(inst); + if (dev->irq >= 0) { + irq_subreason = + wave5_vdi_read_register(dev, W5_VPU_VINT_REASON); + if (!(irq_subreason & BIT(INT_WAVE5_DEC_PIC))) + wave5_vdi_write_register(dev, + W5_RET_QUEUE_CMD_DONE_INST, + cmd_done); + } + val = BIT(INT_WAVE5_DEC_PIC); + kfifo_in(&inst->irq_status, &val, sizeof(int)); } } - - wave5_vpu_clear_interrupt(inst, irq_reason); } + spin_unlock_irqrestore(&dev->irq_spinlock, flags); + + if (dev->irq < 0) + up(&dev->irq_sem); +} + +static irqreturn_t wave5_vpu_irq(int irq, void *dev_id) +{ + struct vpu_device *dev = dev_id; + + if (wave5_vdi_read_register(dev, W5_VPU_VPU_INT_STS)) { + wave5_vpu_handle_irq(dev); + return IRQ_WAKE_THREAD; + } + + return IRQ_HANDLED; } static irqreturn_t wave5_vpu_irq_thread(int irq, void *dev_id) { struct vpu_device *dev = dev_id; + struct vpu_instance *inst, *tmp; + int irq_status, ret; - if (wave5_vdi_read_register(dev, W5_VPU_VPU_INT_STS)) - wave5_vpu_handle_irq(dev); + mutex_lock(&dev->irq_lock); + list_for_each_entry_safe(inst, tmp, &dev->instances, list) { + while (kfifo_len(&inst->irq_status)) { + ret = kfifo_out(&inst->irq_status, &irq_status, sizeof(int)); + if (!ret) + break; + + inst->ops->finish_process(inst); + } + } + mutex_unlock(&dev->irq_lock); return IRQ_HANDLED; } @@ -121,6 +157,35 @@ static enum hrtimer_restart wave5_vpu_timer_callback(struct hrtimer *timer) return HRTIMER_RESTART; } +static int irq_thread(void *data) +{ + struct vpu_device *dev = (struct vpu_device *)data; + struct vpu_instance *inst, *tmp; + int irq_status, ret; + + while (!kthread_should_stop()) { + if (down_interruptible(&dev->irq_sem)) + continue; + + if (kthread_should_stop()) + break; + + mutex_lock(&dev->irq_lock); + list_for_each_entry_safe(inst, tmp, &dev->instances, list) { + while (kfifo_len(&inst->irq_status)) { + ret = kfifo_out(&inst->irq_status, &irq_status, sizeof(int)); + if (!ret) + break; + + inst->ops->finish_process(inst); + } + } + mutex_unlock(&dev->irq_lock); + } + + return 0; +} + static int wave5_vpu_load_firmware(struct device *dev, const char *fw_name, u32 *revision) { @@ -224,6 +289,8 @@ static int wave5_vpu_probe(struct platform_device *pdev) mutex_init(&dev->dev_lock); mutex_init(&dev->hw_lock); + mutex_init(&dev->irq_lock); + spin_lock_init(&dev->irq_spinlock); dev_set_drvdata(&pdev->dev, dev); dev->dev = &pdev->dev; @@ -266,9 +333,13 @@ static int wave5_vpu_probe(struct platform_device *pdev) } dev->product = wave5_vpu_get_product_id(dev); + INIT_LIST_HEAD(&dev->instances); + dev->irq = platform_get_irq(pdev, 0); if (dev->irq < 0) { dev_err(&pdev->dev, "failed to get irq resource, falling back to polling\n"); + sema_init(&dev->irq_sem, 1); + dev->irq_thread = kthread_run(irq_thread, dev, "irq thread"); hrtimer_setup(&dev->hrtimer, &wave5_vpu_timer_callback, CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED); dev->worker = kthread_run_worker(0, "vpu_irq_thread"); @@ -280,7 +351,7 @@ static int wave5_vpu_probe(struct platform_device *pdev) dev->vpu_poll_interval = vpu_poll_interval; kthread_init_work(&dev->work, wave5_vpu_irq_work_fn); } else { - ret = devm_request_threaded_irq(&pdev->dev, dev->irq, NULL, + ret = devm_request_threaded_irq(&pdev->dev, dev->irq, wave5_vpu_irq, wave5_vpu_irq_thread, IRQF_ONESHOT, "vpu_irq", dev); if (ret) { dev_err(&pdev->dev, "Register interrupt handler, fail: %d\n", ret); @@ -288,7 +359,6 @@ static int wave5_vpu_probe(struct platform_device *pdev) } } - INIT_LIST_HEAD(&dev->instances); ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); if (ret) { dev_err(&pdev->dev, "v4l2_device_register, fail: %d\n", ret); @@ -356,6 +426,12 @@ static void wave5_vpu_remove(struct platform_device *pdev) v4l2_device_unregister(&dev->v4l2_dev); if (dev->irq < 0) { + if (dev->irq_thread) { + kthread_stop(dev->irq_thread); + up(&dev->irq_sem); + dev->irq_thread = NULL; + } + hrtimer_cancel(&dev->hrtimer); kthread_cancel_work_sync(&dev->work); kthread_destroy_worker(dev->worker); @@ -366,6 +442,7 @@ static void wave5_vpu_remove(struct platform_device *pdev) mutex_destroy(&dev->dev_lock); mutex_destroy(&dev->hw_lock); + mutex_destroy(&dev->irq_lock); reset_control_assert(dev->resets); clk_bulk_disable_unprepare(dev->num_clks, dev->clks); wave5_vdi_release(&pdev->dev); diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.h b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.h index 45615c15beca..bc101397204d 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.h +++ b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.h @@ -8,6 +8,7 @@ #ifndef VPUAPI_H_INCLUDED #define VPUAPI_H_INCLUDED +#include #include #include #include @@ -747,6 +748,7 @@ struct vpu_device { struct video_device *video_dev_enc; struct mutex dev_lock; /* lock for the src, dst v4l2 queues */ struct mutex hw_lock; /* lock hw configurations */ + struct mutex irq_lock; int irq; enum product_id product; struct vpu_attr attr; @@ -764,7 +766,10 @@ struct vpu_device { struct kthread_worker *worker; int vpu_poll_interval; int num_clks; + struct task_struct *irq_thread; + struct semaphore irq_sem; /* signal to irq_thread when interrupt happens*/ struct reset_control *resets; + spinlock_t irq_spinlock; /* protect instances list */ }; struct vpu_instance; @@ -788,6 +793,7 @@ struct vpu_instance { enum v4l2_ycbcr_encoding ycbcr_enc; enum v4l2_quantization quantization; + struct kfifo irq_status; enum vpu_instance_state state; enum vpu_instance_type type; const struct vpu_instance_ops *ops; From a07ce1e22d6ec437029ad1868836573376655a0d Mon Sep 17 00:00:00 2001 From: Jackson Lee Date: Wed, 19 Nov 2025 15:25:48 +0900 Subject: [PATCH 037/307] media: chips-media: wave5: Add WARN_ON to check if dec_output_info is NULL The dec_output_info should not be a null pointer, WARN_ON around it to indicates a driver issue. Signed-off-by: Jackson Lee Signed-off-by: Nas Chung Tested-by: Brandon Brnich Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/chips-media/wave5/wave5-vpuapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c index e94d6ebc9f81..5b10f9f49b9f 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c @@ -485,7 +485,7 @@ int wave5_vpu_dec_get_output_info(struct vpu_instance *inst, struct dec_output_i struct vpu_device *vpu_dev = inst->dev; struct dec_output_info *disp_info; - if (!info) + if (WARN_ON(!info)) return -EINVAL; p_dec_info = &inst->codec_info->dec_info; From a176ac5e701f1ba30843c0d7e0663a761bf9a61a Mon Sep 17 00:00:00 2001 From: Jackson Lee Date: Wed, 19 Nov 2025 15:25:49 +0900 Subject: [PATCH 038/307] media: chips-media: wave5: Improve performance of decoder The current decoding method was to wait until each frame was decoded after feeding a bitstream. As a result, performance was low and Wave5 could not achieve max pixel processing rate. Update driver to use an asynchronous approach for decoding and feeding a bitstream in order to achieve full capabilities of the device. WAVE5 supports command-queueing to maximize performance by pipelining internal commands and by hiding wait cycle taken to receive a command from Host processor. Instead of waiting for each command to be executed before sending the next command, Host processor just places all the commands in the command-queue and goes on doing other things while the commands in the queue are processed by VPU. While Host processor handles its own tasks, it can receive VPU interrupt request (IRQ). In this case, host processor can simply exit interrupt service routine (ISR) without accessing to host interface to read the result of the command reported by VPU. After host processor completed its tasks, host processor can read the command result when host processor needs the reports and does response processing. To achieve this goal, the device_run() calls v4l2_m2m_job_finish so that next command can be sent to VPU continuously, if there is any result, then irq is triggered and gets decoded frames and returns them to upper layer. Theses processes work independently each other without waiting a decoded frame. Signed-off-by: Jackson Lee Signed-off-by: Nas Chung Tested-by: Brandon Brnich Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../platform/chips-media/wave5/wave5-hw.c | 2 +- .../chips-media/wave5/wave5-vpu-dec.c | 180 +++++++++++++----- .../platform/chips-media/wave5/wave5-vpu.h | 2 +- .../platform/chips-media/wave5/wave5-vpuapi.c | 53 ++++-- .../platform/chips-media/wave5/wave5-vpuapi.h | 6 + .../chips-media/wave5/wave5-vpuconfig.h | 1 + 6 files changed, 178 insertions(+), 66 deletions(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-hw.c b/drivers/media/platform/chips-media/wave5/wave5-hw.c index d94cf84c3ee5..687ce6ccf3ae 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-hw.c +++ b/drivers/media/platform/chips-media/wave5/wave5-hw.c @@ -102,7 +102,7 @@ static void _wave5_print_reg_err(struct vpu_device *vpu_dev, u32 reg_fail_reason dev_dbg(dev, "%s: queueing failure: 0x%x\n", func, reg_val); break; case WAVE5_SYSERR_RESULT_NOT_READY: - dev_err(dev, "%s: result not ready: 0x%x\n", func, reg_fail_reason); + dev_dbg(dev, "%s: result not ready: 0x%x\n", func, reg_fail_reason); break; case WAVE5_SYSERR_ACCESS_VIOLATION_HW: dev_err(dev, "%s: access violation: 0x%x\n", func, reg_fail_reason); diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c index b10396fa2379..e75770912e21 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c @@ -136,6 +136,18 @@ valid_state_switch: return 0; } +static int set_instance_state(struct vpu_instance *inst, enum vpu_instance_state state) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&inst->state_spinlock, flags); + ret = switch_state(inst, state); + spin_unlock_irqrestore(&inst->state_spinlock, flags); + + return ret; +} + static int wave5_vpu_dec_set_eos_on_firmware(struct vpu_instance *inst) { int ret; @@ -227,7 +239,7 @@ static int start_decode(struct vpu_instance *inst, u32 *fail_res) src_buf = v4l2_m2m_src_buf_remove(m2m_ctx); if (src_buf) v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); - switch_state(inst, VPU_INST_STATE_STOP); + set_instance_state(inst, VPU_INST_STATE_STOP); dev_dbg(inst->dev->dev, "%s: pic run failed / finish job", __func__); v4l2_m2m_job_finish(inst->v4l2_m2m_dev, m2m_ctx); @@ -268,6 +280,7 @@ static void send_eos_event(struct vpu_instance *inst) v4l2_event_queue_fh(&inst->v4l2_fh, &vpu_event_eos); inst->eos = false; + inst->sent_eos = true; } static int handle_dynamic_resolution_change(struct vpu_instance *inst) @@ -347,13 +360,12 @@ static void wave5_vpu_dec_finish_decode(struct vpu_instance *inst) struct vb2_v4l2_buffer *dec_buf = NULL; struct vb2_v4l2_buffer *disp_buf = NULL; struct vb2_queue *dst_vq = v4l2_m2m_get_dst_vq(m2m_ctx); - struct queue_status_info q_status; dev_dbg(inst->dev->dev, "%s: Fetch output info from firmware.", __func__); ret = wave5_vpu_dec_get_output_info(inst, &dec_info); if (ret) { - dev_warn(inst->dev->dev, "%s: could not get output info.", __func__); + dev_dbg(inst->dev->dev, "%s: could not get output info.", __func__); v4l2_m2m_job_finish(inst->v4l2_m2m_dev, m2m_ctx); return; } @@ -442,18 +454,17 @@ static void wave5_vpu_dec_finish_decode(struct vpu_instance *inst) spin_unlock_irqrestore(&inst->state_spinlock, flags); } - /* - * During a resolution change and while draining, the firmware may flush - * the reorder queue regardless of having a matching decoding operation - * pending. Only terminate the job if there are no more IRQ coming. - */ - wave5_vpu_dec_give_command(inst, DEC_GET_QUEUE_STATUS, &q_status); - if (q_status.report_queue_count == 0 && - (q_status.instance_queue_count == 0 || dec_info.sequence_changed)) { - dev_dbg(inst->dev->dev, "%s: finishing job.\n", __func__); - pm_runtime_put_autosuspend(inst->dev->dev); - v4l2_m2m_job_finish(inst->v4l2_m2m_dev, m2m_ctx); + if (inst->sent_eos && + v4l2_m2m_get_curr_priv(inst->v4l2_m2m_dev)) { + struct queue_status_info q_status; + + wave5_vpu_dec_give_command(inst, DEC_GET_QUEUE_STATUS, &q_status); + if (q_status.report_queue_count == 0 && + q_status.instance_queue_count == 0) + v4l2_m2m_job_finish(inst->v4l2_m2m_dev, m2m_ctx); } + + inst->queuing_fail = false; } static int wave5_vpu_dec_querycap(struct file *file, void *fh, struct v4l2_capability *cap) @@ -1142,11 +1153,30 @@ static int write_to_ringbuffer(struct vpu_instance *inst, void *buffer, size_t b return 0; } +static struct vpu_src_buffer *inst_src_buf_remove(struct vpu_instance *inst) +{ + struct vpu_src_buffer *b; + int ret; + + ret = mutex_lock_interruptible(&inst->feed_lock); + if (ret) + return NULL; + + if (list_empty(&inst->avail_src_bufs)) { + mutex_unlock(&inst->feed_lock); + return NULL; + } + b = list_first_entry(&inst->avail_src_bufs, struct vpu_src_buffer, list); + list_del_init(&b->list); + mutex_unlock(&inst->feed_lock); + return b; +} + static int fill_ringbuffer(struct vpu_instance *inst) { struct v4l2_m2m_ctx *m2m_ctx = inst->v4l2_fh.m2m_ctx; - struct v4l2_m2m_buffer *buf, *n; - int ret; + struct vpu_src_buffer *vpu_buf; + int ret = 0; if (m2m_ctx->last_src_buf) { struct vpu_src_buffer *vpu_buf = wave5_to_vpu_src_buf(m2m_ctx->last_src_buf); @@ -1157,9 +1187,8 @@ static int fill_ringbuffer(struct vpu_instance *inst) } } - v4l2_m2m_for_each_src_buf_safe(m2m_ctx, buf, n) { - struct vb2_v4l2_buffer *vbuf = &buf->vb; - struct vpu_src_buffer *vpu_buf = wave5_to_vpu_src_buf(vbuf); + while ((vpu_buf = inst_src_buf_remove(inst)) != NULL) { + struct vb2_v4l2_buffer *vbuf = &vpu_buf->v4l2_m2m_buf.vb; struct vpu_buf *ring_buffer = &inst->bitstream_vbuf; size_t src_size = vb2_get_plane_payload(&vbuf->vb2_buf, 0); void *src_buf = vb2_plane_vaddr(&vbuf->vb2_buf, 0); @@ -1219,9 +1248,12 @@ static int fill_ringbuffer(struct vpu_instance *inst) dev_dbg(inst->dev->dev, "last src buffer written to the ring buffer\n"); break; } + + inst->queuing_num++; + break; } - return 0; + return ret; } static void wave5_vpu_dec_buf_queue_src(struct vb2_buffer *vb) @@ -1230,10 +1262,16 @@ static void wave5_vpu_dec_buf_queue_src(struct vb2_buffer *vb) struct v4l2_m2m_ctx *m2m_ctx = inst->v4l2_fh.m2m_ctx; struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct vpu_src_buffer *vpu_buf = wave5_to_vpu_src_buf(vbuf); + int ret; vpu_buf->consumed = false; vbuf->sequence = inst->queued_src_buf_num++; - + ret = mutex_lock_interruptible(&inst->feed_lock); + if (ret) + return; + INIT_LIST_HEAD(&vpu_buf->list); + list_add_tail(&vpu_buf->list, &inst->avail_src_bufs); + mutex_unlock(&inst->feed_lock); v4l2_m2m_buf_queue(m2m_ctx, vbuf); } @@ -1288,10 +1326,13 @@ static void wave5_vpu_dec_buf_queue(struct vb2_buffer *vb) __func__, vb->type, vb->index, vb2_plane_size(&vbuf->vb2_buf, 0), vb2_plane_size(&vbuf->vb2_buf, 1), vb2_plane_size(&vbuf->vb2_buf, 2)); - if (vb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + if (vb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { wave5_vpu_dec_buf_queue_src(vb); - else if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + if (inst->empty_queue) + inst->empty_queue = false; + } else if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { wave5_vpu_dec_buf_queue_dst(vb); + } } static int wave5_vpu_dec_allocate_ring_buffer(struct vpu_instance *inst) @@ -1385,6 +1426,12 @@ static int streamoff_output(struct vb2_queue *q) dma_addr_t new_rd_ptr; struct dec_output_info dec_info; unsigned int i; + struct vpu_src_buffer *vpu_buf; + + inst->retry = false; + inst->queuing_num = 0; + while ((vpu_buf = inst_src_buf_remove(inst)) != NULL) + ; for (i = 0; i < v4l2_m2m_num_dst_bufs_ready(m2m_ctx); i++) { ret = wave5_vpu_dec_set_disp_flag(inst, i); @@ -1470,21 +1517,21 @@ static void wave5_vpu_dec_stop_streaming(struct vb2_queue *q) { struct vpu_instance *inst = vb2_get_drv_priv(q); struct v4l2_m2m_ctx *m2m_ctx = inst->v4l2_fh.m2m_ctx; + bool check_cmd = TRUE; dev_dbg(inst->dev->dev, "%s: type: %u\n", __func__, q->type); pm_runtime_resume_and_get(inst->dev->dev); - + inst->empty_queue = true; while (check_cmd) { struct queue_status_info q_status; struct dec_output_info dec_output_info; wave5_vpu_dec_give_command(inst, DEC_GET_QUEUE_STATUS, &q_status); - - if (q_status.report_queue_count == 0) - break; - - if (wave5_vpu_wait_interrupt(inst, VPU_DEC_TIMEOUT) < 0) + if ((inst->state == VPU_INST_STATE_STOP || + inst->state == VPU_INST_STATE_INIT_SEQ || + q_status.instance_queue_count == 0) && + q_status.report_queue_count == 0) break; if (wave5_vpu_dec_get_output_info(inst, &dec_output_info)) @@ -1498,6 +1545,8 @@ static void wave5_vpu_dec_stop_streaming(struct vb2_queue *q) else streamoff_capture(q); + inst->empty_queue = false; + inst->sent_eos = false; pm_runtime_put_autosuspend(inst->dev->dev); } @@ -1579,10 +1628,18 @@ static void wave5_vpu_dec_device_run(void *priv) dev_dbg(inst->dev->dev, "%s: Fill the ring buffer with new bitstream data", __func__); pm_runtime_resume_and_get(inst->dev->dev); - ret = fill_ringbuffer(inst); - if (ret) { - dev_warn(inst->dev->dev, "Filling ring buffer failed\n"); - goto finish_job_and_return; + if (!inst->retry) { + ret = fill_ringbuffer(inst); + if (ret < 0) { + dev_warn(inst->dev->dev, "Filling ring buffer failed\n"); + goto finish_job_and_return; + } else if (!inst->eos && + inst->queuing_num == 0 && + inst->state == VPU_INST_STATE_PIC_RUN) { + dev_dbg(inst->dev->dev, "%s: no bitstream for feeding, so skip ", __func__); + inst->empty_queue = true; + goto finish_job_and_return; + } } switch (inst->state) { @@ -1607,7 +1664,7 @@ static void wave5_vpu_dec_device_run(void *priv) } spin_unlock_irqrestore(&inst->state_spinlock, flags); } else { - switch_state(inst, VPU_INST_STATE_INIT_SEQ); + set_instance_state(inst, VPU_INST_STATE_INIT_SEQ); } break; @@ -1618,8 +1675,7 @@ static void wave5_vpu_dec_device_run(void *priv) * we had a chance to switch, which leads to an invalid state * change. */ - switch_state(inst, VPU_INST_STATE_PIC_RUN); - + set_instance_state(inst, VPU_INST_STATE_PIC_RUN); /* * During DRC, the picture decoding remains pending, so just leave the job * active until this decode operation completes. @@ -1633,14 +1689,12 @@ static void wave5_vpu_dec_device_run(void *priv) ret = wave5_prepare_fb(inst); if (ret) { dev_warn(inst->dev->dev, "Framebuffer preparation, fail: %d\n", ret); - switch_state(inst, VPU_INST_STATE_STOP); + set_instance_state(inst, VPU_INST_STATE_STOP); break; } - if (q_status.instance_queue_count) { - dev_dbg(inst->dev->dev, "%s: leave with active job", __func__); - return; - } + if (q_status.instance_queue_count) + goto finish_job_and_return; fallthrough; case VPU_INST_STATE_PIC_RUN: @@ -1649,28 +1703,45 @@ static void wave5_vpu_dec_device_run(void *priv) dev_err(inst->dev->dev, "Frame decoding on m2m context (%p), fail: %d (result: %d)\n", m2m_ctx, ret, fail_res); - break; + goto finish_job_and_return; + } + + if (fail_res == WAVE5_SYSERR_QUEUEING_FAIL) { + inst->retry = true; + inst->queuing_fail = true; + } else { + inst->retry = false; + if (!inst->eos) + inst->queuing_num--; } - /* Return so that we leave this job active */ - dev_dbg(inst->dev->dev, "%s: leave with active job", __func__); - return; - default: - WARN(1, "Execution of a job in state %s illegal.\n", state_to_str(inst->state)); break; + default: + dev_dbg(inst->dev->dev, "Execution of a job in state %s illegal.\n", + state_to_str(inst->state)); } finish_job_and_return: dev_dbg(inst->dev->dev, "%s: leave and finish job", __func__); pm_runtime_put_autosuspend(inst->dev->dev); - v4l2_m2m_job_finish(inst->v4l2_m2m_dev, m2m_ctx); + /* + * After receiving CMD_STOP, there is no input, but we have to run device_run + * to send DEC_PIC command until display index == -1, so job_finish was always + * called in the device_run to archive it, the logic was very wasteful + * in power and CPU time. + * If EOS is passed, device_run will not call job_finish no more, it is called + * only if HW is idle status in order to reduce overhead. + */ + if (!inst->sent_eos) + v4l2_m2m_job_finish(inst->v4l2_m2m_dev, m2m_ctx); } static void wave5_vpu_dec_job_abort(void *priv) { struct vpu_instance *inst = priv; + struct v4l2_m2m_ctx *m2m_ctx = inst->v4l2_fh.m2m_ctx; int ret; - ret = switch_state(inst, VPU_INST_STATE_STOP); + ret = set_instance_state(inst, VPU_INST_STATE_STOP); if (ret) return; @@ -1678,6 +1749,8 @@ static void wave5_vpu_dec_job_abort(void *priv) if (ret) dev_warn(inst->dev->dev, "Setting EOS for the bitstream, fail: %d\n", ret); + + v4l2_m2m_job_finish(inst->v4l2_m2m_dev, m2m_ctx); } static int wave5_vpu_dec_job_ready(void *priv) @@ -1713,10 +1786,15 @@ static int wave5_vpu_dec_job_ready(void *priv) "No capture buffer ready to decode!\n"); break; } else if (!wave5_is_draining_or_eos(inst) && - !v4l2_m2m_num_src_bufs_ready(m2m_ctx)) { + (!v4l2_m2m_num_src_bufs_ready(m2m_ctx) || + inst->empty_queue)) { dev_dbg(inst->dev->dev, "No bitstream data to decode!\n"); break; + } else if (inst->state == VPU_INST_STATE_PIC_RUN && + !wave5_is_draining_or_eos(inst) && + inst->queuing_fail) { + break; } ret = 1; break; @@ -1753,6 +1831,8 @@ static int wave5_vpu_open_dec(struct file *filp) inst->ops = &wave5_vpu_dec_inst_ops; spin_lock_init(&inst->state_spinlock); + mutex_init(&inst->feed_lock); + INIT_LIST_HEAD(&inst->avail_src_bufs); inst->codec_info = kzalloc(sizeof(*inst->codec_info), GFP_KERNEL); if (!inst->codec_info) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu.h b/drivers/media/platform/chips-media/wave5/wave5-vpu.h index 5943bdaa9c4c..99c3be882192 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu.h +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu.h @@ -22,8 +22,8 @@ struct vpu_src_buffer { struct v4l2_m2m_buffer v4l2_m2m_buf; - struct list_head list; bool consumed; + struct list_head list; }; struct vpu_dst_buffer { diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c index 5b10f9f49b9f..d26ffc942219 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.c @@ -52,11 +52,12 @@ int wave5_vpu_init_with_bitcode(struct device *dev, u8 *bitcode, size_t size) int wave5_vpu_flush_instance(struct vpu_instance *inst) { int ret = 0; + int mutex_ret = 0; int retry = 0; - ret = mutex_lock_interruptible(&inst->dev->hw_lock); - if (ret) - return ret; + mutex_ret = mutex_lock_interruptible(&inst->dev->hw_lock); + if (mutex_ret) + return mutex_ret; do { /* * Repeat the FLUSH command until the firmware reports that the @@ -80,11 +81,16 @@ int wave5_vpu_flush_instance(struct vpu_instance *inst) mutex_unlock(&inst->dev->hw_lock); wave5_vpu_dec_get_output_info(inst, &dec_info); - ret = mutex_lock_interruptible(&inst->dev->hw_lock); - if (ret) - return ret; - if (dec_info.index_frame_display > 0) + mutex_ret = mutex_lock_interruptible(&inst->dev->hw_lock); + if (mutex_ret) + return mutex_ret; + if (dec_info.index_frame_display >= 0) { + mutex_unlock(&inst->dev->hw_lock); wave5_vpu_dec_set_disp_flag(inst, dec_info.index_frame_display); + mutex_ret = mutex_lock_interruptible(&inst->dev->hw_lock); + if (mutex_ret) + return mutex_ret; + } } } while (ret != 0); mutex_unlock(&inst->dev->hw_lock); @@ -207,6 +213,8 @@ int wave5_vpu_dec_close(struct vpu_instance *inst, u32 *fail_res) int retry = 0; struct vpu_device *vpu_dev = inst->dev; int i; + struct dec_output_info dec_info; + int ret_mutex; *fail_res = 0; if (!inst->codec_info) @@ -214,10 +222,10 @@ int wave5_vpu_dec_close(struct vpu_instance *inst, u32 *fail_res) pm_runtime_resume_and_get(inst->dev->dev); - ret = mutex_lock_interruptible(&vpu_dev->hw_lock); - if (ret) { + ret_mutex = mutex_lock_interruptible(&vpu_dev->hw_lock); + if (ret_mutex) { pm_runtime_put_sync(inst->dev->dev); - return ret; + return ret_mutex; } do { @@ -227,11 +235,26 @@ int wave5_vpu_dec_close(struct vpu_instance *inst, u32 *fail_res) goto unlock_and_return; } - if (*fail_res == WAVE5_SYSERR_VPU_STILL_RUNNING && - retry++ >= MAX_FIRMWARE_CALL_RETRY) { + if (ret == 0) + break; + + if (*fail_res != WAVE5_SYSERR_VPU_STILL_RUNNING) { + dev_warn(inst->dev->dev, "dec_finish_seq timed out\n"); + goto unlock_and_return; + } + + if (retry++ >= MAX_FIRMWARE_CALL_RETRY) { ret = -ETIMEDOUT; goto unlock_and_return; } + + mutex_unlock(&vpu_dev->hw_lock); + wave5_vpu_dec_get_output_info(inst, &dec_info); + ret_mutex = mutex_lock_interruptible(&vpu_dev->hw_lock); + if (ret_mutex) { + pm_runtime_put_sync(inst->dev->dev); + return ret_mutex; + } } while (ret != 0); dev_dbg(inst->dev->dev, "%s: dec_finish_seq complete\n", __func__); @@ -248,6 +271,8 @@ int wave5_vpu_dec_close(struct vpu_instance *inst, u32 *fail_res) wave5_vdi_free_dma_memory(vpu_dev, &p_dec_info->vb_task); + mutex_destroy(&inst->feed_lock); + unlock_and_return: mutex_unlock(&vpu_dev->hw_lock); pm_runtime_put_sync(inst->dev->dev); @@ -460,11 +485,11 @@ int wave5_vpu_dec_set_rd_ptr(struct vpu_instance *inst, dma_addr_t addr, int upd dma_addr_t wave5_vpu_dec_get_rd_ptr(struct vpu_instance *inst) { int ret; - dma_addr_t rd_ptr; + dma_addr_t rd_ptr = 0; ret = mutex_lock_interruptible(&inst->dev->hw_lock); if (ret) - return ret; + return rd_ptr; rd_ptr = wave5_dec_get_rd_ptr(inst); diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.h b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.h index bc101397204d..c64135769869 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpuapi.h +++ b/drivers/media/platform/chips-media/wave5/wave5-vpuapi.h @@ -818,6 +818,12 @@ struct vpu_instance { bool cbcr_interleave; bool nv21; bool eos; + bool sent_eos; /* check if EOS is sent to application */ + bool retry; /* retry to feed bitstream if failure reason is WAVE5_SYSERR_QUEUEING_FAIL*/ + int queuing_num; /* count of bitstream queued */ + struct mutex feed_lock; /* lock for feeding bitstream buffers */ + bool queuing_fail; /* if there is the queuing failure */ + bool empty_queue; struct vpu_buf bitstream_vbuf; dma_addr_t last_rd_ptr; size_t remaining_consumed_bytes; diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpuconfig.h b/drivers/media/platform/chips-media/wave5/wave5-vpuconfig.h index 1ea9f5f31499..4ebd48d5550e 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpuconfig.h +++ b/drivers/media/platform/chips-media/wave5/wave5-vpuconfig.h @@ -59,6 +59,7 @@ // application specific configuration #define VPU_ENC_TIMEOUT 60000 #define VPU_DEC_TIMEOUT 60000 +#define VPU_DEC_STOP_TIMEOUT 10 // for WAVE encoder #define USE_SRC_PRP_AXI 0 From a519e21e32398459ba357e67b541402f7295ee1b Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Tue, 11 Nov 2025 14:57:06 +0000 Subject: [PATCH 039/307] media: chips-media: wave5: Fix memory leak on codec_info allocation failure In wave5_vpu_open_enc() and wave5_vpu_open_dec(), a vpu instance is allocated via kzalloc(). If the subsequent allocation for inst->codec_info fails, the functions return -ENOMEM without freeing the previously allocated instance, causing a memory leak. Fix this by calling kfree() on the instance in this error path to ensure it is properly released. Fixes: 9707a6254a8a6 ("media: chips-media: wave5: Add the v4l2 layer") Signed-off-by: Zilin Guan Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c | 4 +++- drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c index e75770912e21..8917542b993c 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-dec.c @@ -1835,8 +1835,10 @@ static int wave5_vpu_open_dec(struct file *filp) INIT_LIST_HEAD(&inst->avail_src_bufs); inst->codec_info = kzalloc(sizeof(*inst->codec_info), GFP_KERNEL); - if (!inst->codec_info) + if (!inst->codec_info) { + kfree(inst); return -ENOMEM; + } v4l2_fh_init(&inst->v4l2_fh, vdev); v4l2_fh_add(&inst->v4l2_fh, filp); diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c index e69bef5d1b52..24fc0d0d3f4a 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu-enc.c @@ -1581,8 +1581,10 @@ static int wave5_vpu_open_enc(struct file *filp) inst->ops = &wave5_vpu_enc_inst_ops; inst->codec_info = kzalloc(sizeof(*inst->codec_info), GFP_KERNEL); - if (!inst->codec_info) + if (!inst->codec_info) { + kfree(inst); return -ENOMEM; + } v4l2_fh_init(&inst->v4l2_fh, vdev); v4l2_fh_add(&inst->v4l2_fh, filp); From c789a7f40288c19004f786a6da67c3733d38c6af Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Fri, 28 Nov 2025 14:16:12 -0500 Subject: [PATCH 040/307] media: mc: add manual request completion By default when the last request object is completed, the whole request completes as well. But sometimes you want to delay this completion to an arbitrary point in time so add a manual complete mode for this. In req_queue the driver marks the request for manual completion by calling media_request_mark_manual_completion, and when the driver wants to manually complete the request it calls media_request_manual_complete(). Signed-off-by: Hans Verkuil Signed-off-by: Nicolas Dufresne --- drivers/media/mc/mc-request.c | 41 +++++++++++++++++++++++++++++++++-- include/media/media-request.h | 38 +++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/drivers/media/mc/mc-request.c b/drivers/media/mc/mc-request.c index 2ac9ac0a740b..70713445c5af 100644 --- a/drivers/media/mc/mc-request.c +++ b/drivers/media/mc/mc-request.c @@ -54,6 +54,7 @@ static void media_request_clean(struct media_request *req) req->access_count = 0; WARN_ON(req->num_incomplete_objects); req->num_incomplete_objects = 0; + req->manual_completion = false; wake_up_interruptible_all(&req->poll_wait); } @@ -298,6 +299,7 @@ int media_request_alloc(struct media_device *mdev, int *alloc_fd) req->mdev = mdev; req->state = MEDIA_REQUEST_STATE_IDLE; req->num_incomplete_objects = 0; + req->manual_completion = false; kref_init(&req->kref); INIT_LIST_HEAD(&req->objects); spin_lock_init(&req->lock); @@ -449,7 +451,7 @@ void media_request_object_unbind(struct media_request_object *obj) req->num_incomplete_objects--; if (req->state == MEDIA_REQUEST_STATE_QUEUED && - !req->num_incomplete_objects) { + !req->num_incomplete_objects && !req->manual_completion) { req->state = MEDIA_REQUEST_STATE_COMPLETE; completed = true; wake_up_interruptible_all(&req->poll_wait); @@ -478,7 +480,7 @@ void media_request_object_complete(struct media_request_object *obj) WARN_ON(req->state != MEDIA_REQUEST_STATE_QUEUED)) goto unlock; - if (!--req->num_incomplete_objects) { + if (!--req->num_incomplete_objects && !req->manual_completion) { req->state = MEDIA_REQUEST_STATE_COMPLETE; wake_up_interruptible_all(&req->poll_wait); completed = true; @@ -489,3 +491,38 @@ unlock: media_request_put(req); } EXPORT_SYMBOL_GPL(media_request_object_complete); + +void media_request_manual_complete(struct media_request *req) +{ + bool completed = false; + unsigned long flags; + + if (WARN_ON_ONCE(!req)) + return; + + spin_lock_irqsave(&req->lock, flags); + + if (WARN_ON_ONCE(!req->manual_completion)) + goto unlock; + + if (WARN_ON_ONCE(req->state != MEDIA_REQUEST_STATE_QUEUED)) + goto unlock; + + req->manual_completion = false; + /* + * It is expected that all other objects in this request are + * completed when this function is called. WARN if that is + * not the case. + */ + if (!WARN_ON(req->num_incomplete_objects)) { + req->state = MEDIA_REQUEST_STATE_COMPLETE; + wake_up_interruptible_all(&req->poll_wait); + completed = true; + } + +unlock: + spin_unlock_irqrestore(&req->lock, flags); + if (completed) + media_request_put(req); +} +EXPORT_SYMBOL_GPL(media_request_manual_complete); diff --git a/include/media/media-request.h b/include/media/media-request.h index bb500b2f9da4..3256fcf3709d 100644 --- a/include/media/media-request.h +++ b/include/media/media-request.h @@ -56,6 +56,9 @@ struct media_request_object; * @access_count: count the number of request accesses that are in progress * @objects: List of @struct media_request_object request objects * @num_incomplete_objects: The number of incomplete objects in the request + * @manual_completion: if true, then the request won't be marked as completed + * when @num_incomplete_objects reaches 0. Call media_request_manual_complete() + * to complete the request after @num_incomplete_objects == 0. * @poll_wait: Wait queue for poll * @lock: Serializes access to this struct */ @@ -68,6 +71,7 @@ struct media_request { unsigned int access_count; struct list_head objects; unsigned int num_incomplete_objects; + bool manual_completion; wait_queue_head_t poll_wait; spinlock_t lock; }; @@ -218,6 +222,38 @@ media_request_get_by_fd(struct media_device *mdev, int request_fd); int media_request_alloc(struct media_device *mdev, int *alloc_fd); +/** + * media_request_mark_manual_completion - Enable manual completion + * + * @req: The request + * + * Mark that the request has to be manually completed by calling + * media_request_manual_complete(). + * + * This function shall be called in the req_queue callback. + */ +static inline void +media_request_mark_manual_completion(struct media_request *req) +{ + req->manual_completion = true; +} + +/** + * media_request_manual_complete - Mark the request as completed + * + * @req: The request + * + * This function completes a request that was marked for manual completion by an + * earlier call to media_request_mark_manual_completion(). The request's + * @manual_completion field is reset to false. + * + * All objects contained in the request must have been completed previously. It + * is an error to call this function otherwise. If such an error occurred, the + * function will WARN and the object completion will be delayed until + * @num_incomplete_objects is 0. + */ +void media_request_manual_complete(struct media_request *req); + #else static inline void media_request_get(struct media_request *req) @@ -336,7 +372,7 @@ void media_request_object_init(struct media_request_object *obj); * @req: The media request * @ops: The object ops for this object * @priv: A driver-specific priv pointer associated with this object - * @is_buffer: Set to true if the object a buffer object. + * @is_buffer: Set to true if the object is a buffer object. * @obj: The object * * Bind this object to the request and set the ops and priv values of From d14d93f940763655c23c9a0012648ec42a06f482 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Fri, 28 Nov 2025 14:16:13 -0500 Subject: [PATCH 041/307] media: vicodec: add support for manual completion Manually complete the requests: this tests the manual completion code. Signed-off-by: Hans Verkuil Signed-off-by: Nicolas Dufresne --- .../media/test-drivers/vicodec/vicodec-core.c | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/media/test-drivers/vicodec/vicodec-core.c b/drivers/media/test-drivers/vicodec/vicodec-core.c index a7ab668ce70b..be846f711969 100644 --- a/drivers/media/test-drivers/vicodec/vicodec-core.c +++ b/drivers/media/test-drivers/vicodec/vicodec-core.c @@ -448,8 +448,10 @@ static void device_run(void *priv) ctx->comp_magic_cnt = 0; ctx->comp_has_frame = false; spin_unlock(ctx->lock); - if (ctx->is_stateless && src_req) + if (ctx->is_stateless && src_req) { v4l2_ctrl_request_complete(src_req, &ctx->hdl); + media_request_manual_complete(src_req); + } if (ctx->is_enc) v4l2_m2m_job_finish(dev->stateful_enc.m2m_dev, ctx->fh.m2m_ctx); @@ -1518,8 +1520,12 @@ static void vicodec_return_bufs(struct vb2_queue *q, u32 state) vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); if (vbuf == NULL) return; - v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req, - &ctx->hdl); + if (ctx->is_stateless && V4L2_TYPE_IS_OUTPUT(q->type)) { + struct media_request *req = vbuf->vb2_buf.req_obj.req; + + v4l2_ctrl_request_complete(req, &ctx->hdl); + media_request_manual_complete(req); + } spin_lock(ctx->lock); v4l2_m2m_buf_done(vbuf, state); spin_unlock(ctx->lock); @@ -1672,6 +1678,7 @@ static void vicodec_buf_request_complete(struct vb2_buffer *vb) struct vicodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl); + media_request_manual_complete(vb->req_obj.req); } @@ -2002,6 +2009,12 @@ static int vicodec_request_validate(struct media_request *req) return vb2_request_validate(req); } +static void vicodec_request_queue(struct media_request *req) +{ + media_request_mark_manual_completion(req); + v4l2_m2m_request_queue(req); +} + static const struct v4l2_file_operations vicodec_fops = { .owner = THIS_MODULE, .open = vicodec_open, @@ -2022,7 +2035,7 @@ static const struct video_device vicodec_videodev = { static const struct media_device_ops vicodec_m2m_media_ops = { .req_validate = vicodec_request_validate, - .req_queue = v4l2_m2m_request_queue, + .req_queue = vicodec_request_queue, }; static const struct v4l2_m2m_ops m2m_ops = { From de9f0c2a1ce3d97561d5cf4678b80e13943647fd Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Fri, 28 Nov 2025 14:16:14 -0500 Subject: [PATCH 042/307] media: mc: add debugfs node to keep track of requests Keep track of the number of requests and request objects of a media device. Helps to verify that all request-related memory is freed. Signed-off-by: Hans Verkuil Signed-off-by: Nicolas Dufresne --- drivers/media/mc/mc-device.c | 30 ++++++++++++++++++++++++++++++ drivers/media/mc/mc-devnode.c | 5 +++++ drivers/media/mc/mc-request.c | 6 ++++++ include/media/media-device.h | 9 +++++++++ include/media/media-devnode.h | 4 ++++ include/media/media-request.h | 2 ++ 6 files changed, 56 insertions(+) diff --git a/drivers/media/mc/mc-device.c b/drivers/media/mc/mc-device.c index c0dd4ae57227..5a4465b290f0 100644 --- a/drivers/media/mc/mc-device.c +++ b/drivers/media/mc/mc-device.c @@ -679,6 +679,23 @@ void media_device_unregister_entity(struct media_entity *entity) } EXPORT_SYMBOL_GPL(media_device_unregister_entity); +#ifdef CONFIG_DEBUG_FS +/* + * Log the state of media requests. Very useful for debugging. + */ +static int media_device_requests(struct seq_file *file, void *priv) +{ + struct media_device *dev = dev_get_drvdata(file->private); + + seq_printf(file, "number of requests: %d\n", + atomic_read(&dev->num_requests)); + seq_printf(file, "number of request objects: %d\n", + atomic_read(&dev->num_request_objects)); + + return 0; +} +#endif + void media_device_init(struct media_device *mdev) { INIT_LIST_HEAD(&mdev->entities); @@ -697,6 +714,9 @@ void media_device_init(struct media_device *mdev) media_set_bus_info(mdev->bus_info, sizeof(mdev->bus_info), mdev->dev); + atomic_set(&mdev->num_requests, 0); + atomic_set(&mdev->num_request_objects, 0); + dev_dbg(mdev->dev, "Media device initialized\n"); } EXPORT_SYMBOL_GPL(media_device_init); @@ -748,6 +768,15 @@ int __must_check __media_device_register(struct media_device *mdev, dev_dbg(mdev->dev, "Media device registered\n"); +#ifdef CONFIG_DEBUG_FS + if (!media_debugfs_root) + media_debugfs_root = debugfs_create_dir("media", NULL); + mdev->media_dir = debugfs_create_dir(dev_name(&devnode->dev), + media_debugfs_root); + debugfs_create_devm_seqfile(&devnode->dev, "requests", + mdev->media_dir, media_device_requests); +#endif + return 0; } EXPORT_SYMBOL_GPL(__media_device_register); @@ -824,6 +853,7 @@ void media_device_unregister(struct media_device *mdev) dev_dbg(mdev->dev, "Media device unregistered\n"); + debugfs_remove_recursive(mdev->media_dir); device_remove_file(&mdev->devnode->dev, &dev_attr_model); media_devnode_unregister(mdev->devnode); /* devnode free is handled in media_devnode_*() */ diff --git a/drivers/media/mc/mc-devnode.c b/drivers/media/mc/mc-devnode.c index 6daa7aa99442..51260a7d6921 100644 --- a/drivers/media/mc/mc-devnode.c +++ b/drivers/media/mc/mc-devnode.c @@ -45,6 +45,9 @@ static dev_t media_dev_t; static DEFINE_MUTEX(media_devnode_lock); static DECLARE_BITMAP(media_devnode_nums, MEDIA_NUM_DEVICES); +/* debugfs */ +struct dentry *media_debugfs_root; + /* Called when the last user of the media device exits. */ static void media_devnode_release(struct device *cd) { @@ -231,6 +234,7 @@ int __must_check media_devnode_register(struct media_device *mdev, if (devnode->parent) devnode->dev.parent = devnode->parent; dev_set_name(&devnode->dev, "media%d", devnode->minor); + dev_set_drvdata(&devnode->dev, mdev); device_initialize(&devnode->dev); /* Part 2: Initialize the character device */ @@ -309,6 +313,7 @@ static int __init media_devnode_init(void) static void __exit media_devnode_exit(void) { + debugfs_remove_recursive(media_debugfs_root); bus_unregister(&media_bus_type); unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES); } diff --git a/drivers/media/mc/mc-request.c b/drivers/media/mc/mc-request.c index 70713445c5af..dbea4da5235b 100644 --- a/drivers/media/mc/mc-request.c +++ b/drivers/media/mc/mc-request.c @@ -75,6 +75,7 @@ static void media_request_release(struct kref *kref) mdev->ops->req_free(req); else kfree(req); + atomic_dec(&mdev->num_requests); } void media_request_put(struct media_request *req) @@ -321,6 +322,7 @@ int media_request_alloc(struct media_device *mdev, int *alloc_fd) snprintf(req->debug_str, sizeof(req->debug_str), "%u:%d", atomic_inc_return(&mdev->request_id), *alloc_fd); + atomic_inc(&mdev->num_requests); dev_dbg(mdev->dev, "request: allocated %s\n", req->debug_str); return 0; @@ -339,10 +341,12 @@ static void media_request_object_release(struct kref *kref) struct media_request_object *obj = container_of(kref, struct media_request_object, kref); struct media_request *req = obj->req; + struct media_device *mdev = obj->mdev; if (WARN_ON(req)) media_request_object_unbind(obj); obj->ops->release(obj); + atomic_dec(&mdev->num_request_objects); } struct media_request_object * @@ -407,6 +411,7 @@ int media_request_object_bind(struct media_request *req, obj->req = req; obj->ops = ops; obj->priv = priv; + obj->mdev = req->mdev; if (is_buffer) list_add_tail(&obj->list, &req->objects); @@ -414,6 +419,7 @@ int media_request_object_bind(struct media_request *req, list_add(&obj->list, &req->objects); req->num_incomplete_objects++; ret = 0; + atomic_inc(&obj->mdev->num_request_objects); unlock: spin_unlock_irqrestore(&req->lock, flags); diff --git a/include/media/media-device.h b/include/media/media-device.h index 53d2a16a70b0..749c327e3c58 100644 --- a/include/media/media-device.h +++ b/include/media/media-device.h @@ -11,6 +11,7 @@ #ifndef _MEDIA_DEVICE_H #define _MEDIA_DEVICE_H +#include #include #include #include @@ -106,6 +107,9 @@ struct media_device_ops { * @ops: Operation handler callbacks * @req_queue_mutex: Serialise the MEDIA_REQUEST_IOC_QUEUE ioctl w.r.t. * other operations that stop or start streaming. + * @num_requests: number of associated requests + * @num_request_objects: number of associated request objects + * @media_dir: DebugFS media directory * @request_id: Used to generate unique request IDs * * This structure represents an abstract high-level media device. It allows easy @@ -179,6 +183,11 @@ struct media_device { const struct media_device_ops *ops; struct mutex req_queue_mutex; + atomic_t num_requests; + atomic_t num_request_objects; + + /* debugfs */ + struct dentry *media_dir; atomic_t request_id; }; diff --git a/include/media/media-devnode.h b/include/media/media-devnode.h index d27c1c646c28..dbcabeffcb57 100644 --- a/include/media/media-devnode.h +++ b/include/media/media-devnode.h @@ -20,9 +20,13 @@ #include #include #include +#include struct media_device; +/* debugfs top-level media directory */ +extern struct dentry *media_debugfs_root; + /* * Flag to mark the media_devnode struct as registered. Drivers must not touch * this flag directly, it will be set and cleared by media_devnode_register and diff --git a/include/media/media-request.h b/include/media/media-request.h index 3256fcf3709d..43ed18c11b51 100644 --- a/include/media/media-request.h +++ b/include/media/media-request.h @@ -292,6 +292,7 @@ struct media_request_object_ops { * struct media_request_object - An opaque object that belongs to a media * request * + * @mdev: Media device this object belongs to * @ops: object's operations * @priv: object's priv pointer * @req: the request this object belongs to (can be NULL) @@ -303,6 +304,7 @@ struct media_request_object_ops { * another struct that contains the actual data for this request object. */ struct media_request_object { + struct media_device *mdev; const struct media_request_object_ops *ops; void *priv; struct media_request *req; From 1b78aae6313f4131610f5023ae34f2c7502ceeab Mon Sep 17 00:00:00 2001 From: Sebastian Fricke Date: Fri, 28 Nov 2025 14:16:15 -0500 Subject: [PATCH 043/307] media: mediatek: vcodec: Implement manual request completion Rework how requests are completed in the MediaTek VCodec driver, by implementing the new manual request completion feature, which allows to keep a request open while allowing to add new bitstream data. This is useful in this case, because the hardware has a LAT and a core decode work, after the LAT decode the bitstream isn't required anymore so the source buffer can be set done and the request stays open until the core decode work finishes. Signed-off-by: Sebastian Fricke Co-developed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../mediatek/vcodec/decoder/mtk_vcodec_dec.c | 4 +- .../vcodec/decoder/mtk_vcodec_dec_drv.h | 17 +++ .../vcodec/decoder/mtk_vcodec_dec_stateless.c | 113 ++++++++++++------ 3 files changed, 98 insertions(+), 36 deletions(-) diff --git a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec.c b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec.c index 1f32ba11a18c..d76e891f784b 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec.c +++ b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec.c @@ -882,8 +882,10 @@ void vb2ops_vdec_stop_streaming(struct vb2_queue *q) src_buf->vb2_buf.req_obj.req; v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); - if (req) + if (req) { v4l2_ctrl_request_complete(req, &ctx->ctrl_hdl); + media_request_manual_complete(req); + } } } return; diff --git a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.h b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.h index 9d68808e8f9c..c9d27534c63e 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.h +++ b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_drv.h @@ -7,6 +7,8 @@ #ifndef _MTK_VCODEC_DEC_DRV_H_ #define _MTK_VCODEC_DEC_DRV_H_ +#include + #include "../common/mtk_vcodec_cmn_drv.h" #include "../common/mtk_vcodec_dbgfs.h" #include "../common/mtk_vcodec_fw_priv.h" @@ -128,6 +130,16 @@ struct mtk_vcodec_dec_pdata { bool uses_stateless_api; }; +/** + * struct mtk_vcodec_dec_request - Media request private data. + * @refcount: Used to ensure we don't complete the request too soon + * @req: Media Request structure + */ +struct mtk_vcodec_dec_request { + struct kref refcount; + struct media_request req; +}; + /** * struct mtk_vcodec_dec_ctx - Context (instance) private data. * @@ -324,6 +336,11 @@ static inline struct mtk_vcodec_dec_ctx *ctrl_to_dec_ctx(struct v4l2_ctrl *ctrl) return container_of(ctrl->handler, struct mtk_vcodec_dec_ctx, ctrl_hdl); } +static inline struct mtk_vcodec_dec_request *req_to_dec_req(struct media_request *req) +{ + return container_of(req, struct mtk_vcodec_dec_request, req); +} + /* Wake up context wait_queue */ static inline void wake_up_dec_ctx(struct mtk_vcodec_dec_ctx *ctx, unsigned int reason, unsigned int hw_id) diff --git a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_stateless.c b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_stateless.c index d873159b9b30..6598a9e160b7 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_stateless.c +++ b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_stateless.c @@ -242,10 +242,18 @@ static const struct v4l2_frmsize_stepwise stepwise_fhd = { .step_height = 16 }; -static void mtk_vdec_stateless_cap_to_disp(struct mtk_vcodec_dec_ctx *ctx, int error, - struct media_request *src_buf_req) +static void mtk_vcodec_dec_request_release(struct kref *ref) { - struct vb2_v4l2_buffer *vb2_dst; + struct mtk_vcodec_dec_request *req = + container_of(ref, struct mtk_vcodec_dec_request, refcount); + media_request_manual_complete(&req->req); +} + +static void mtk_vdec_stateless_cap_to_disp(struct mtk_vcodec_dec_ctx *ctx, int error, + struct media_request *mreq) +{ + struct mtk_vcodec_dec_request *req = req_to_dec_req(mreq); + struct vb2_v4l2_buffer *dst_buf; enum vb2_buffer_state state; if (error) @@ -253,17 +261,9 @@ static void mtk_vdec_stateless_cap_to_disp(struct mtk_vcodec_dec_ctx *ctx, int e else state = VB2_BUF_STATE_DONE; - vb2_dst = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); - if (vb2_dst) { - v4l2_m2m_buf_done(vb2_dst, state); - mtk_v4l2_vdec_dbg(2, ctx, "free frame buffer id:%d to done list", - vb2_dst->vb2_buf.index); - } else { - mtk_v4l2_vdec_err(ctx, "dst buffer is NULL"); - } - - if (src_buf_req) - v4l2_ctrl_request_complete(src_buf_req, &ctx->ctrl_hdl); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + v4l2_m2m_buf_done(dst_buf, state); + kref_put(&req->refcount, mtk_vcodec_dec_request_release); } static struct vdec_fb *vdec_get_cap_buffer(struct mtk_vcodec_dec_ctx *ctx) @@ -306,6 +306,7 @@ static void vb2ops_vdec_buf_request_complete(struct vb2_buffer *vb) struct mtk_vcodec_dec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->ctrl_hdl); + media_request_manual_complete(vb->req_obj.req); } static void mtk_vdec_worker(struct work_struct *work) @@ -317,8 +318,9 @@ static void mtk_vdec_worker(struct work_struct *work) struct vb2_buffer *vb2_src; struct mtk_vcodec_mem *bs_src; struct mtk_video_dec_buf *dec_buf_src; - struct media_request *src_buf_req; - enum vb2_buffer_state state; + struct media_request *mreq; + struct mtk_vcodec_dec_request *req; + enum vb2_buffer_state buf_state; bool res_chg = false; int ret; @@ -350,14 +352,26 @@ static void mtk_vdec_worker(struct work_struct *work) mtk_v4l2_vdec_dbg(3, ctx, "[%d] Bitstream VA=%p DMA=%pad Size=%zx vb=%p", ctx->id, bs_src->va, &bs_src->dma_addr, bs_src->size, vb2_src); /* Apply request controls. */ - src_buf_req = vb2_src->req_obj.req; - if (src_buf_req) - v4l2_ctrl_request_setup(src_buf_req, &ctx->ctrl_hdl); - else + mreq = vb2_src->req_obj.req; + if (WARN_ON(!mreq)) { + v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); mtk_v4l2_vdec_err(ctx, "vb2 buffer media request is NULL"); + return; + } + + v4l2_ctrl_request_setup(mreq, &ctx->ctrl_hdl); + + /* Keep a reference so that if the processing completes before this function + * ends, we won't accidently update a freshly queued request. + */ + req = req_to_dec_req(mreq); + kref_get(&req->refcount); ret = vdec_if_decode(ctx, bs_src, NULL, &res_chg); - if (ret && ret != -EAGAIN) { + if (ret == -EAGAIN) + goto done; + + if (ret) { mtk_v4l2_vdec_err(ctx, "[%d] decode src_buf[%d] sz=0x%zx pts=%llu ret=%d res_chg=%d", ctx->id, vb2_src->index, bs_src->size, @@ -367,21 +381,23 @@ static void mtk_vdec_worker(struct work_struct *work) dec_buf_src->error = true; mutex_unlock(&ctx->lock); } + + buf_state = VB2_BUF_STATE_ERROR; + } else { + buf_state = VB2_BUF_STATE_DONE; } - state = ret ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE; - if (!IS_VDEC_LAT_ARCH(dev->vdec_pdata->hw_arch) || - ctx->current_codec == V4L2_PIX_FMT_VP8_FRAME) { - v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, state); - if (src_buf_req) - v4l2_ctrl_request_complete(src_buf_req, &ctx->ctrl_hdl); - } else { - if (ret != -EAGAIN) { - v4l2_m2m_src_buf_remove(ctx->m2m_ctx); - v4l2_m2m_buf_done(vb2_v4l2_src, state); - } - v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); - } + v4l2_ctrl_request_complete(mreq, &ctx->ctrl_hdl); + v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + v4l2_m2m_buf_done(vb2_v4l2_src, buf_state); + + if (ret || !IS_VDEC_LAT_ARCH(dev->vdec_pdata->hw_arch) || + ctx->current_codec == V4L2_PIX_FMT_VP8_FRAME) + mtk_vdec_stateless_cap_to_disp(ctx, ret, mreq); + +done: + kref_put(&req->refcount, mtk_vcodec_dec_request_release); + v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); } static void vb2ops_vdec_stateless_buf_queue(struct vb2_buffer *vb) @@ -709,6 +725,22 @@ static int mtk_vcodec_dec_ctrls_setup(struct mtk_vcodec_dec_ctx *ctx) return 0; } +static struct media_request *fops_media_request_alloc(struct media_device *mdev) +{ + struct mtk_vcodec_dec_request *req; + + req = kzalloc(sizeof(*req), GFP_KERNEL); + + return &req->req; +} + +static void fops_media_request_free(struct media_request *mreq) +{ + struct mtk_vcodec_dec_request *req = req_to_dec_req(mreq); + + kfree(req); +} + static int fops_media_request_validate(struct media_request *mreq) { const unsigned int buffer_cnt = vb2_request_buffer_cnt(mreq); @@ -729,9 +761,20 @@ static int fops_media_request_validate(struct media_request *mreq) return vb2_request_validate(mreq); } +static void fops_media_request_queue(struct media_request *mreq) +{ + struct mtk_vcodec_dec_request *req = req_to_dec_req(mreq); + + media_request_mark_manual_completion(mreq); + kref_init(&req->refcount); + v4l2_m2m_request_queue(mreq); +} + const struct media_device_ops mtk_vcodec_media_ops = { + .req_alloc = fops_media_request_alloc, + .req_free = fops_media_request_free, .req_validate = fops_media_request_validate, - .req_queue = v4l2_m2m_request_queue, + .req_queue = fops_media_request_queue, }; static void mtk_vcodec_add_formats(unsigned int fourcc, From 3e92d7e4935084ecdbdc88880cc4688618ae1557 Mon Sep 17 00:00:00 2001 From: Nicolas Dufresne Date: Fri, 28 Nov 2025 14:16:16 -0500 Subject: [PATCH 044/307] media: mediatek: vcodec: Don't try to decode 422/444 VP9 This is not supported by the hardware and trying to decode these leads to LAT timeout errors. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../mediatek/vcodec/decoder/mtk_vcodec_dec_stateless.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_stateless.c b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_stateless.c index 6598a9e160b7..8e1cf16a168a 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_stateless.c +++ b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_stateless.c @@ -518,6 +518,12 @@ static int mtk_vdec_s_ctrl(struct v4l2_ctrl *ctrl) mtk_v4l2_vdec_err(ctx, "VP9: bit_depth:%d", frame->bit_depth); return -EINVAL; } + + if (!(frame->flags & V4L2_VP9_FRAME_FLAG_X_SUBSAMPLING) || + !(frame->flags & V4L2_VP9_FRAME_FLAG_Y_SUBSAMPLING)) { + mtk_v4l2_vdec_err(ctx, "VP9: only 420 subsampling is supported"); + return -EINVAL; + } break; case V4L2_CID_STATELESS_AV1_SEQUENCE: seq = (struct v4l2_ctrl_av1_sequence *)hdr_ctrl->p_new.p; From db6b97a4f8041e479be9ef4b8b07022636c96f50 Mon Sep 17 00:00:00 2001 From: Nicolas Dufresne Date: Fri, 5 Dec 2025 09:54:24 +0800 Subject: [PATCH 045/307] media: v4l2-mem2mem: Add a kref to the v4l2_m2m_dev structure Adding a reference count to the v4l2_m2m_dev structure allow safely sharing it across multiple hardware nodes. This can be used to prevent running jobs concurrently on m2m cores that have some internal resource sharing. Signed-off-by: Ming Qian Reviewed-by: Frank Li Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil [hverkuil: fix typos in v4l2_m2m_put documentation] --- drivers/media/v4l2-core/v4l2-mem2mem.c | 23 +++++++++++++++++++++++ include/media/v4l2-mem2mem.h | 21 +++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c index fec93c1a9231..ae0de54d4c3e 100644 --- a/drivers/media/v4l2-core/v4l2-mem2mem.c +++ b/drivers/media/v4l2-core/v4l2-mem2mem.c @@ -90,6 +90,7 @@ static const char * const m2m_entity_name[] = { * @job_work: worker to run queued jobs. * @job_queue_flags: flags of the queue status, %QUEUE_PAUSED. * @m2m_ops: driver callbacks + * @kref: device reference count */ struct v4l2_m2m_dev { struct v4l2_m2m_ctx *curr_ctx; @@ -109,6 +110,8 @@ struct v4l2_m2m_dev { unsigned long job_queue_flags; const struct v4l2_m2m_ops *m2m_ops; + + struct kref kref; }; static struct v4l2_m2m_queue_ctx *get_queue_ctx(struct v4l2_m2m_ctx *m2m_ctx, @@ -1200,6 +1203,7 @@ struct v4l2_m2m_dev *v4l2_m2m_init(const struct v4l2_m2m_ops *m2m_ops) INIT_LIST_HEAD(&m2m_dev->job_queue); spin_lock_init(&m2m_dev->job_spinlock); INIT_WORK(&m2m_dev->job_work, v4l2_m2m_device_run_work); + kref_init(&m2m_dev->kref); return m2m_dev; } @@ -1211,6 +1215,25 @@ void v4l2_m2m_release(struct v4l2_m2m_dev *m2m_dev) } EXPORT_SYMBOL_GPL(v4l2_m2m_release); +void v4l2_m2m_get(struct v4l2_m2m_dev *m2m_dev) +{ + kref_get(&m2m_dev->kref); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_get); + +static void v4l2_m2m_release_from_kref(struct kref *kref) +{ + struct v4l2_m2m_dev *m2m_dev = container_of(kref, struct v4l2_m2m_dev, kref); + + v4l2_m2m_release(m2m_dev); +} + +void v4l2_m2m_put(struct v4l2_m2m_dev *m2m_dev) +{ + kref_put(&m2m_dev->kref, v4l2_m2m_release_from_kref); +} +EXPORT_SYMBOL_GPL(v4l2_m2m_put); + struct v4l2_m2m_ctx *v4l2_m2m_ctx_init(struct v4l2_m2m_dev *m2m_dev, void *drv_priv, int (*queue_init)(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)) diff --git a/include/media/v4l2-mem2mem.h b/include/media/v4l2-mem2mem.h index bf6a09a04dcf..31de25d792b9 100644 --- a/include/media/v4l2-mem2mem.h +++ b/include/media/v4l2-mem2mem.h @@ -547,6 +547,27 @@ v4l2_m2m_register_media_controller(struct v4l2_m2m_dev *m2m_dev, */ void v4l2_m2m_release(struct v4l2_m2m_dev *m2m_dev); +/** + * v4l2_m2m_get() - take a reference to the m2m_dev structure + * + * @m2m_dev: opaque pointer to the internal data to handle M2M context + * + * This is used to share the M2M device across multiple devices. This + * can be used to avoid scheduling two hardware nodes concurrently. + */ +void v4l2_m2m_get(struct v4l2_m2m_dev *m2m_dev); + +/** + * v4l2_m2m_put() - remove a reference to the m2m_dev structure + * + * @m2m_dev: opaque pointer to the internal data to handle M2M context + * + * Once the M2M device has no more references, v4l2_m2m_release() will be + * called automatically. Users of this method should never call + * v4l2_m2m_release() directly. See v4l2_m2m_get() for more details. + */ +void v4l2_m2m_put(struct v4l2_m2m_dev *m2m_dev); + /** * v4l2_m2m_ctx_init() - allocate and initialize a m2m context * From e0203ddf9af7c8e170e1e99ce83b4dc07f0cd765 Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Fri, 5 Dec 2025 09:54:26 +0800 Subject: [PATCH 046/307] media: verisilicon: Avoid G2 bus error while decoding H.264 and HEVC For the i.MX8MQ platform, there is a hardware limitation: the g1 VPU and g2 VPU cannot decode simultaneously; otherwise, it will cause below bus error and produce corrupted pictures, even potentially lead to system hang. [ 110.527986] hantro-vpu 38310000.video-codec: frame decode timed out. [ 110.583517] hantro-vpu 38310000.video-codec: bus error detected. Therefore, it is necessary to ensure that g1 and g2 operate alternately. This allows for successful multi-instance decoding of H.264 and HEVC. To achieve this, g1 and g2 share the same v4l2_m2m_dev, and then the v4l2_m2m_dev can handle the scheduling. Fixes: cb5dd5a0fa518 ("media: hantro: Introduce G2/HEVC decoder") Cc: stable@vger.kernel.org Signed-off-by: Ming Qian Reviewed-by: Frank Li Co-developed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/verisilicon/hantro.h | 2 + .../media/platform/verisilicon/hantro_drv.c | 42 +++++++++++++++++-- .../media/platform/verisilicon/imx8m_vpu_hw.c | 8 ++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/verisilicon/hantro.h b/drivers/media/platform/verisilicon/hantro.h index e0fdc4535b2d..0353de154a1e 100644 --- a/drivers/media/platform/verisilicon/hantro.h +++ b/drivers/media/platform/verisilicon/hantro.h @@ -77,6 +77,7 @@ struct hantro_irq { * @double_buffer: core needs double buffering * @legacy_regs: core uses legacy register set * @late_postproc: postproc must be set up at the end of the job + * @shared_devices: an array of device ids that cannot run concurrently */ struct hantro_variant { unsigned int enc_offset; @@ -101,6 +102,7 @@ struct hantro_variant { unsigned int double_buffer : 1; unsigned int legacy_regs : 1; unsigned int late_postproc : 1; + const struct of_device_id *shared_devices; }; /** diff --git a/drivers/media/platform/verisilicon/hantro_drv.c b/drivers/media/platform/verisilicon/hantro_drv.c index 60b95b5d8565..94f58f4e4a4e 100644 --- a/drivers/media/platform/verisilicon/hantro_drv.c +++ b/drivers/media/platform/verisilicon/hantro_drv.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -1035,6 +1036,41 @@ static int hantro_disable_multicore(struct hantro_dev *vpu) return 0; } +static struct v4l2_m2m_dev *hantro_get_v4l2_m2m_dev(struct hantro_dev *vpu) +{ + struct device_node *node; + struct hantro_dev *shared_vpu; + + if (!vpu->variant || !vpu->variant->shared_devices) + goto init_new_m2m_dev; + + for_each_matching_node(node, vpu->variant->shared_devices) { + struct platform_device *pdev; + struct v4l2_m2m_dev *m2m_dev; + + pdev = of_find_device_by_node(node); + if (!pdev) + continue; + + shared_vpu = platform_get_drvdata(pdev); + if (IS_ERR_OR_NULL(shared_vpu) || shared_vpu == vpu) { + platform_device_put(pdev); + continue; + } + + v4l2_m2m_get(shared_vpu->m2m_dev); + m2m_dev = shared_vpu->m2m_dev; + platform_device_put(pdev); + + of_node_put(node); + + return m2m_dev; + } + +init_new_m2m_dev: + return v4l2_m2m_init(&vpu_m2m_ops); +} + static int hantro_probe(struct platform_device *pdev) { const struct of_device_id *match; @@ -1186,7 +1222,7 @@ static int hantro_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, vpu); - vpu->m2m_dev = v4l2_m2m_init(&vpu_m2m_ops); + vpu->m2m_dev = hantro_get_v4l2_m2m_dev(vpu); if (IS_ERR(vpu->m2m_dev)) { v4l2_err(&vpu->v4l2_dev, "Failed to init mem2mem device\n"); ret = PTR_ERR(vpu->m2m_dev); @@ -1225,7 +1261,7 @@ err_rm_enc_func: hantro_remove_enc_func(vpu); err_m2m_rel: media_device_cleanup(&vpu->mdev); - v4l2_m2m_release(vpu->m2m_dev); + v4l2_m2m_put(vpu->m2m_dev); err_v4l2_unreg: v4l2_device_unregister(&vpu->v4l2_dev); err_clk_unprepare: @@ -1248,7 +1284,7 @@ static void hantro_remove(struct platform_device *pdev) hantro_remove_dec_func(vpu); hantro_remove_enc_func(vpu); media_device_cleanup(&vpu->mdev); - v4l2_m2m_release(vpu->m2m_dev); + v4l2_m2m_put(vpu->m2m_dev); v4l2_device_unregister(&vpu->v4l2_dev); clk_bulk_unprepare(vpu->variant->num_clocks, vpu->clocks); reset_control_assert(vpu->resets); diff --git a/drivers/media/platform/verisilicon/imx8m_vpu_hw.c b/drivers/media/platform/verisilicon/imx8m_vpu_hw.c index 5be0e2e76882..6f8e43b7f157 100644 --- a/drivers/media/platform/verisilicon/imx8m_vpu_hw.c +++ b/drivers/media/platform/verisilicon/imx8m_vpu_hw.c @@ -343,6 +343,12 @@ const struct hantro_variant imx8mq_vpu_variant = { .num_regs = ARRAY_SIZE(imx8mq_reg_names) }; +static const struct of_device_id imx8mq_vpu_shared_resources[] __initconst = { + { .compatible = "nxp,imx8mq-vpu-g1", }, + { .compatible = "nxp,imx8mq-vpu-g2", }, + { /* sentinel */ } +}; + const struct hantro_variant imx8mq_vpu_g1_variant = { .dec_fmts = imx8m_vpu_dec_fmts, .num_dec_fmts = ARRAY_SIZE(imx8m_vpu_dec_fmts), @@ -356,6 +362,7 @@ const struct hantro_variant imx8mq_vpu_g1_variant = { .num_irqs = ARRAY_SIZE(imx8mq_irqs), .clk_names = imx8mq_g1_clk_names, .num_clocks = ARRAY_SIZE(imx8mq_g1_clk_names), + .shared_devices = imx8mq_vpu_shared_resources, }; const struct hantro_variant imx8mq_vpu_g2_variant = { @@ -371,6 +378,7 @@ const struct hantro_variant imx8mq_vpu_g2_variant = { .num_irqs = ARRAY_SIZE(imx8mq_g2_irqs), .clk_names = imx8mq_g2_clk_names, .num_clocks = ARRAY_SIZE(imx8mq_g2_clk_names), + .shared_devices = imx8mq_vpu_shared_resources, }; const struct hantro_variant imx8mm_vpu_g1_variant = { From e0f99b810e1181374370f91cd996d761549e147f Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Tue, 9 Dec 2025 11:34:01 +0100 Subject: [PATCH 047/307] media: verisilicon: AV1: Fix enable cdef computation If all the fields of the CDEF parameters are zero (which is the default), then av1_enable_cdef register needs to be unset (despite the V4L2_AV1_SEQUENCE_FLAG_ENABLE_CDEF possibly being set). Signed-off-by: Benjamin Gaignard Fixes: 727a400686a2c ("media: verisilicon: Add Rockchip AV1 decoder") Cc: stable@vger.kernel.org Reported-by: Jianfeng Liu Closes: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/4786 Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil [hverkuil: dropped Link tag since it just duplicated the Closes: URL] --- .../platform/verisilicon/rockchip_vpu981_hw_av1_dec.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c index e4703bb6be7c..f4f7cb45b1f1 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c @@ -1396,8 +1396,16 @@ static void rockchip_vpu981_av1_dec_set_cdef(struct hantro_ctx *ctx) u16 luma_sec_strength = 0; u32 chroma_pri_strength = 0; u16 chroma_sec_strength = 0; + bool enable_cdef; int i; + enable_cdef = !(cdef->bits == 0 && + cdef->damping_minus_3 == 0 && + cdef->y_pri_strength[0] == 0 && + cdef->y_sec_strength[0] == 0 && + cdef->uv_pri_strength[0] == 0 && + cdef->uv_sec_strength[0] == 0); + hantro_reg_write(vpu, &av1_enable_cdef, enable_cdef); hantro_reg_write(vpu, &av1_cdef_bits, cdef->bits); hantro_reg_write(vpu, &av1_cdef_damping, cdef->damping_minus_3); @@ -1953,8 +1961,6 @@ static void rockchip_vpu981_av1_dec_set_parameters(struct hantro_ctx *ctx) !!(ctrls->frame->flags & V4L2_AV1_FRAME_FLAG_SHOW_FRAME)); hantro_reg_write(vpu, &av1_switchable_motion_mode, !!(ctrls->frame->flags & V4L2_AV1_FRAME_FLAG_IS_MOTION_MODE_SWITCHABLE)); - hantro_reg_write(vpu, &av1_enable_cdef, - !!(ctrls->sequence->flags & V4L2_AV1_SEQUENCE_FLAG_ENABLE_CDEF)); hantro_reg_write(vpu, &av1_allow_masked_compound, !!(ctrls->sequence->flags & V4L2_AV1_SEQUENCE_FLAG_ENABLE_MASKED_COMPOUND)); From cb3f945c012ab152fd2323e0df34c2b640071738 Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Tue, 9 Dec 2025 11:34:17 +0100 Subject: [PATCH 048/307] media: verisilicon: AV1: Fix tx mode bit setting AV1 specification describes 3 possibles tx modes: 4x4 only, largest and select. The hardware allows 5 possibles tx modes: 4x4 only, 8x8, 16x16, 32x32 and select. Since the both aren't exactly matching we need to add a mapping function to set the correct mode on hardware. Signed-off-by: Benjamin Gaignard Fixes: 727a400686a2c ("media: verisilicon: Add Rockchip AV1 decoder") Cc: stable@vger.kernel.org Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../verisilicon/rockchip_vpu981_hw_av1_dec.c | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c index f4f7cb45b1f1..f52b8208e6b9 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c @@ -72,6 +72,14 @@ : AV1_DIV_ROUND_UP_POW2((_value_), (_n_))); \ }) +enum rockchip_av1_tx_mode { + ROCKCHIP_AV1_TX_MODE_ONLY_4X4 = 0, + ROCKCHIP_AV1_TX_MODE_8X8 = 1, + ROCKCHIP_AV1_TX_MODE_16x16 = 2, + ROCKCHIP_AV1_TX_MODE_32x32 = 3, + ROCKCHIP_AV1_TX_MODE_SELECT = 4, +}; + struct rockchip_av1_film_grain { u8 scaling_lut_y[256]; u8 scaling_lut_cb[256]; @@ -1935,11 +1943,26 @@ static void rockchip_vpu981_av1_dec_set_reference_frames(struct hantro_ctx *ctx) rockchip_vpu981_av1_dec_set_other_frames(ctx); } +static int rockchip_vpu981_av1_get_hardware_tx_mode(enum v4l2_av1_tx_mode tx_mode) +{ + switch (tx_mode) { + case V4L2_AV1_TX_MODE_ONLY_4X4: + return ROCKCHIP_AV1_TX_MODE_ONLY_4X4; + case V4L2_AV1_TX_MODE_LARGEST: + return ROCKCHIP_AV1_TX_MODE_32x32; + case V4L2_AV1_TX_MODE_SELECT: + return ROCKCHIP_AV1_TX_MODE_SELECT; + } + + return ROCKCHIP_AV1_TX_MODE_32x32; +} + static void rockchip_vpu981_av1_dec_set_parameters(struct hantro_ctx *ctx) { struct hantro_dev *vpu = ctx->dev; struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + int tx_mode; hantro_reg_write(vpu, &av1_skip_mode, !!(ctrls->frame->flags & V4L2_AV1_FRAME_FLAG_SKIP_MODE_PRESENT)); @@ -2005,7 +2028,9 @@ static void rockchip_vpu981_av1_dec_set_parameters(struct hantro_ctx *ctx) !!(ctrls->frame->flags & V4L2_AV1_FRAME_FLAG_ALLOW_HIGH_PRECISION_MV)); hantro_reg_write(vpu, &av1_comp_pred_mode, (ctrls->frame->flags & V4L2_AV1_FRAME_FLAG_REFERENCE_SELECT) ? 2 : 0); - hantro_reg_write(vpu, &av1_transform_mode, (ctrls->frame->tx_mode == 1) ? 3 : 4); + + tx_mode = rockchip_vpu981_av1_get_hardware_tx_mode(ctrls->frame->tx_mode); + hantro_reg_write(vpu, &av1_transform_mode, tx_mode); hantro_reg_write(vpu, &av1_max_cb_size, (ctrls->sequence->flags & V4L2_AV1_SEQUENCE_FLAG_USE_128X128_SUPERBLOCK) ? 7 : 6); From 8e30a27f4aab0a0482630f4283ca2a994fd78d1d Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Fri, 7 Nov 2025 15:13:03 +0100 Subject: [PATCH 049/307] media: platform: mtk-mdp3: add WQ_PERCPU to alloc_workqueue users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently if a user enqueues a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistency cannot be addressed without refactoring the API. alloc_workqueue() treats all queues as per-CPU by default, while unbound workqueues must opt-in via WQ_UNBOUND. This default is suboptimal: most workloads benefit from unbound queues, allowing the scheduler to place worker threads where they’re needed and reducing noise when CPUs are isolated. This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") This change adds a new WQ_PERCPU flag to explicitly request alloc_workqueue() to be per-cpu when WQ_UNBOUND has not been specified. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. Once migration is complete, WQ_UNBOUND can be removed and unbound will become the implicit default. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c index 6d26d4aa1eef..f20d2ed1f2bb 100644 --- a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c @@ -268,14 +268,16 @@ static int mdp_probe(struct platform_device *pdev) goto err_free_mutex; } - mdp->job_wq = alloc_workqueue(MDP_MODULE_NAME, WQ_FREEZABLE, 0); + mdp->job_wq = alloc_workqueue(MDP_MODULE_NAME, + WQ_FREEZABLE | WQ_PERCPU, 0); if (!mdp->job_wq) { dev_err(dev, "Unable to create job workqueue\n"); ret = -ENOMEM; goto err_deinit_comp; } - mdp->clock_wq = alloc_workqueue(MDP_MODULE_NAME "-clock", WQ_FREEZABLE, + mdp->clock_wq = alloc_workqueue(MDP_MODULE_NAME "-clock", + WQ_FREEZABLE | WQ_PERCPU, 0); if (!mdp->clock_wq) { dev_err(dev, "Unable to create clock workqueue\n"); From 336367fab9b96db7b0ee174443c426fc6c53b912 Mon Sep 17 00:00:00 2001 From: Brandon Brnich Date: Tue, 16 Dec 2025 15:34:13 -0600 Subject: [PATCH 050/307] media: chips-media: wave5: Fix Potential Probe Resource Leak After kthread creation during probe sequence, a handful of other failures could occur. If this were to happen, the kthread is never explicitly deleted which results in a resource leak. Add explicit cleanup of this resource. Signed-off-by: Brandon Brnich Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/chips-media/wave5/wave5-vpu.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/chips-media/wave5/wave5-vpu.c b/drivers/media/platform/chips-media/wave5/wave5-vpu.c index 3216b4997644..76d57c6b636a 100644 --- a/drivers/media/platform/chips-media/wave5/wave5-vpu.c +++ b/drivers/media/platform/chips-media/wave5/wave5-vpu.c @@ -362,7 +362,7 @@ static int wave5_vpu_probe(struct platform_device *pdev) ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); if (ret) { dev_err(&pdev->dev, "v4l2_device_register, fail: %d\n", ret); - goto err_vdi_release; + goto err_irq_release; } if (match_data->flags & WAVE5_IS_DEC) { @@ -407,7 +407,15 @@ err_dec_unreg: wave5_vpu_dec_unregister_device(dev); err_v4l2_unregister: v4l2_device_unregister(&dev->v4l2_dev); +err_irq_release: + if (dev->irq < 0) + kthread_destroy_worker(dev->worker); err_vdi_release: + if (dev->irq_thread) { + kthread_stop(dev->irq_thread); + up(&dev->irq_sem); + dev->irq_thread = NULL; + } wave5_vdi_release(&pdev->dev); err_clk_dis: clk_bulk_disable_unprepare(dev->num_clks, dev->clks); From 59cbe9436de67c5592467587dd39a4879c002d4d Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 4 Dec 2025 06:11:33 +0200 Subject: [PATCH 051/307] dt-bindings: media: i2c: Add Samsung S5KJN1 image sensor Add device tree bindings documentation for Samsung S5KJN1 image sensor. Reviewed-by: Rob Herring (Arm) Signed-off-by: Vladimir Zapolskiy Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../bindings/media/i2c/samsung,s5kjn1.yaml | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/samsung,s5kjn1.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/samsung,s5kjn1.yaml b/Documentation/devicetree/bindings/media/i2c/samsung,s5kjn1.yaml new file mode 100644 index 000000000000..8f368ae044b4 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/samsung,s5kjn1.yaml @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/samsung,s5kjn1.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung S5KJN1 Image Sensor + +description: + Samsung S5KJN1 (ISOCELL JN1) image sensor is a 50MP image sensor. + The sensor is controlled over a serial camera control bus protocol, + the widest supported output image frame size is 8160x6144 at 10 frames + per second, data output format is RAW10 transferred over 4-lane + MIPI D-PHY interface. + +maintainers: + - Vladimir Zapolskiy + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: samsung,s5kjn1 + + reg: + maxItems: 1 + + clocks: + description: MCLK supply clock. + maxItems: 1 + + reset-gpios: + description: Active low GPIO connected to RESET pad of the sensor. + maxItems: 1 + + afvdd-supply: + description: Autofocus actuator voltage supply, 2.8-3.0 volts. + + vdda-supply: + description: Analogue voltage supply, 2.8 volts. + + vddd-supply: + description: Digital core voltage supply, 1.05 volts. + + vddio-supply: + description: Digital I/O voltage supply, 1.8 volts. + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + required: + - link-frequencies + +required: + - compatible + - reg + - port + +unevaluatedProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@56 { + compatible = "samsung,s5kjn1"; + reg = <0x56>; + clocks = <&camera_mclk 0>; + assigned-clocks = <&camera_mclk 0>; + assigned-clock-rates = <24000000>; + reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; + vdda-supply = <&vreg_2p8>; + vddd-supply = <&vreg_1p05>; + vddio-supply = <&vreg_1p8>; + + port { + endpoint { + link-frequencies = /bits/ 64 <700000000>; + remote-endpoint = <&mipi_csi2_ep>; + }; + }; + }; + }; +... From e38fd0933c759bfd51aafbee07f11e8d69da4366 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 4 Dec 2025 06:11:34 +0200 Subject: [PATCH 052/307] media: i2c: add Samsung S5KJN1 image sensor device driver Samsung S5KJN1 is a 50MP image sensor, it produces Bayer GRBG (2x2) frames in RAW10 output format, the maximum supported output resolution is 8160x6144 at 10 frames per second. Tested-by: Neil Armstrong # on SM8650-QRD Signed-off-by: Vladimir Zapolskiy Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 8 + drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/s5kjn1.c | 1487 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1506 insertions(+) create mode 100644 drivers/media/i2c/s5kjn1.c diff --git a/MAINTAINERS b/MAINTAINERS index 5b11839cba9d..e451855c50bc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23270,6 +23270,14 @@ L: linux-media@vger.kernel.org S: Supported F: drivers/media/i2c/s5k5baf.c +SAMSUNG S5KJN1 CAMERA DRIVER +M: Vladimir Zapolskiy +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/samsung,s5kjn1.yaml +F: drivers/media/i2c/s5kjn1.c + SAMSUNG S5P Security SubSystem (SSS) DRIVER M: Krzysztof Kozlowski M: Vladimir Zapolskiy diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 4b4db8c4f496..89bc7c153058 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -755,6 +755,16 @@ config VIDEO_S5K6A3 This is a V4L2 sensor driver for Samsung S5K6A3 raw camera sensor. +config VIDEO_S5KJN1 + tristate "Samsung S5KJN1 sensor support" + select V4L2_CCI_I2C + help + This is a V4L2 sensor driver for Samsung S5KJN1 50MP raw + camera sensor. + + To compile this driver as a module, choose M here: the + module will be called s5kjn1. + config VIDEO_VD55G1 tristate "ST VD55G1 sensor support" select V4L2_CCI_I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index c5f17602454f..f86e2622c0d7 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -127,6 +127,7 @@ obj-$(CONFIG_VIDEO_RJ54N1) += rj54n1cb0c.o obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/ obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o +obj-$(CONFIG_VIDEO_S5KJN1) += s5kjn1.o obj-$(CONFIG_VIDEO_SAA6588) += saa6588.o obj-$(CONFIG_VIDEO_SAA6752HS) += saa6752hs.o obj-$(CONFIG_VIDEO_SAA7110) += saa7110.o diff --git a/drivers/media/i2c/s5kjn1.c b/drivers/media/i2c/s5kjn1.c new file mode 100644 index 000000000000..a707cb740556 --- /dev/null +++ b/drivers/media/i2c/s5kjn1.c @@ -0,0 +1,1487 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Linaro Ltd + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define S5KJN1_LINK_FREQ_700MHZ (700ULL * HZ_PER_MHZ) +#define S5KJN1_MCLK_FREQ_24MHZ (24 * HZ_PER_MHZ) +#define S5KJN1_DATA_LANES 4 + +/* Register map is similar to MIPI CCS compliant camera sensors */ +#define S5KJN1_REG_CHIP_ID CCI_REG16(0x0000) +#define S5KJN1_CHIP_ID 0x38e1 + +#define S5KJN1_REG_CTRL_MODE CCI_REG8(0x0100) +#define S5KJN1_MODE_STREAMING BIT(0) + +#define S5KJN1_REG_ORIENTATION CCI_REG8(0x0101) +#define S5KJN1_VFLIP BIT(1) +#define S5KJN1_HFLIP BIT(0) + +#define S5KJN1_REG_EXPOSURE CCI_REG16(0x0202) +#define S5KJN1_EXPOSURE_MIN 8 +#define S5KJN1_EXPOSURE_STEP 1 + +#define S5KJN1_REG_AGAIN CCI_REG16(0x0204) +#define S5KJN1_AGAIN_MIN 1 +#define S5KJN1_AGAIN_MAX 64 +#define S5KJN1_AGAIN_STEP 1 +#define S5KJN1_AGAIN_DEFAULT 6 +#define S5KJN1_AGAIN_SHIFT 5 + +#define S5KJN1_REG_VTS CCI_REG16(0x0340) +#define S5KJN1_VTS_MAX 0xffff + +#define S5KJN1_REG_HTS CCI_REG16(0x0342) +#define S5KJN1_REG_X_ADDR_START CCI_REG16(0x0344) +#define S5KJN1_REG_Y_ADDR_START CCI_REG16(0x0346) +#define S5KJN1_REG_X_ADDR_END CCI_REG16(0x0348) +#define S5KJN1_REG_Y_ADDR_END CCI_REG16(0x034a) +#define S5KJN1_REG_X_OUTPUT_SIZE CCI_REG16(0x034c) +#define S5KJN1_REG_Y_OUTPUT_SIZE CCI_REG16(0x034e) + +#define S5KJN1_REG_TEST_PATTERN CCI_REG16(0x0600) + +#define to_s5kjn1(_sd) container_of(_sd, struct s5kjn1, sd) + +static const s64 s5kjn1_link_freq_menu[] = { + S5KJN1_LINK_FREQ_700MHZ, +}; + +/* List of supported formats to cover horizontal and vertical flip controls */ +static const u32 s5kjn1_mbus_formats[] = { + MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, +}; + +struct s5kjn1_reg_list { + const struct cci_reg_sequence *regs; + unsigned int num_regs; +}; + +struct s5kjn1_mode { + u32 width; /* Frame width in pixels */ + u32 height; /* Frame height in pixels */ + u32 hts; /* Horizontal timing size */ + u32 vts; /* Default vertical timing size */ + u32 exposure; /* Default exposure value */ + u32 exposure_margin; /* Exposure margin */ + + const struct s5kjn1_reg_list reg_list; /* Sensor register setting */ +}; + +static const char * const s5kjn1_test_pattern_menu[] = { + "Disabled", + "Solid color", + "Color bars", + "Fade to grey color bars", + "PN9", +}; + +struct s5kjn1 { + struct device *dev; + struct regmap *regmap; + struct clk *mclk; + struct gpio_desc *reset_gpio; + struct regulator *afvdd; /* Autofocus actuator power */ + struct regulator *vdda; /* Analog power */ + struct regulator *vddd; /* Digital core power */ + struct regulator *vddio; /* Digital I/O power */ + + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + + const struct s5kjn1_mode *mode; +}; + +static const struct cci_reg_sequence init_array_setting[] = { + { CCI_REG16(0x6028), 0x2400 }, + { CCI_REG16(0x602a), 0x1354 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x6f12), 0x7017 }, + { CCI_REG16(0x602a), 0x13b2 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x1236 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x1a0a }, + { CCI_REG16(0x6f12), 0x4c0a }, + { CCI_REG16(0x602a), 0x2210 }, + { CCI_REG16(0x6f12), 0x3401 }, + { CCI_REG16(0x602a), 0x2176 }, + { CCI_REG16(0x6f12), 0x6400 }, + { CCI_REG16(0x602a), 0x222e }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x602a), 0x06b6 }, + { CCI_REG16(0x6f12), 0x0a00 }, + { CCI_REG16(0x602a), 0x06bc }, + { CCI_REG16(0x6f12), 0x1001 }, + { CCI_REG16(0x602a), 0x2140 }, + { CCI_REG16(0x6f12), 0x0101 }, + { CCI_REG16(0x602a), 0x1a0e }, + { CCI_REG16(0x6f12), 0x9600 }, + { CCI_REG16(0x6028), 0x4000 }, + { CCI_REG16(0xf44e), 0x0011 }, + { CCI_REG16(0xf44c), 0x0b0b }, + { CCI_REG16(0xf44a), 0x0006 }, + { CCI_REG16(0x0118), 0x0002 }, + { CCI_REG16(0x011a), 0x0001 }, +}; + +static const struct cci_reg_sequence s5kjn1_4080x3072_30fps_mode[] = { + { CCI_REG16(0x6028), 0x2400 }, + { CCI_REG16(0x602a), 0x1a28 }, + { CCI_REG16(0x6f12), 0x4c00 }, + { CCI_REG16(0x602a), 0x065a }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x139e }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x139c }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x13a0 }, + { CCI_REG16(0x6f12), 0x0a00 }, + { CCI_REG16(0x6f12), 0x0120 }, + { CCI_REG16(0x602a), 0x2072 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x1a64 }, + { CCI_REG16(0x6f12), 0x0301 }, + { CCI_REG16(0x6f12), 0xff00 }, + { CCI_REG16(0x602a), 0x19e6 }, + { CCI_REG16(0x6f12), 0x0200 }, + { CCI_REG16(0x602a), 0x1a30 }, + { CCI_REG16(0x6f12), 0x3401 }, + { CCI_REG16(0x602a), 0x19fc }, + { CCI_REG16(0x6f12), 0x0b00 }, + { CCI_REG16(0x602a), 0x19f4 }, + { CCI_REG16(0x6f12), 0x0606 }, + { CCI_REG16(0x602a), 0x19f8 }, + { CCI_REG16(0x6f12), 0x1010 }, + { CCI_REG16(0x602a), 0x1b26 }, + { CCI_REG16(0x6f12), 0x6f80 }, + { CCI_REG16(0x6f12), 0xa060 }, + { CCI_REG16(0x602a), 0x1a3c }, + { CCI_REG16(0x6f12), 0x6207 }, + { CCI_REG16(0x602a), 0x1a48 }, + { CCI_REG16(0x6f12), 0x6207 }, + { CCI_REG16(0x602a), 0x1444 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x602a), 0x144c }, + { CCI_REG16(0x6f12), 0x3f00 }, + { CCI_REG16(0x6f12), 0x3f00 }, + { CCI_REG16(0x602a), 0x7f6c }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x6f12), 0x2f00 }, + { CCI_REG16(0x6f12), 0xfa00 }, + { CCI_REG16(0x6f12), 0x2400 }, + { CCI_REG16(0x6f12), 0xe500 }, + { CCI_REG16(0x602a), 0x0650 }, + { CCI_REG16(0x6f12), 0x0600 }, + { CCI_REG16(0x602a), 0x0654 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x1a46 }, + { CCI_REG16(0x6f12), 0x8a00 }, + { CCI_REG16(0x602a), 0x1a52 }, + { CCI_REG16(0x6f12), 0xbf00 }, + { CCI_REG16(0x602a), 0x0674 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x602a), 0x0668 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x602a), 0x0684 }, + { CCI_REG16(0x6f12), 0x4001 }, + { CCI_REG16(0x602a), 0x0688 }, + { CCI_REG16(0x6f12), 0x4001 }, + { CCI_REG16(0x602a), 0x147c }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x1480 }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x19f6 }, + { CCI_REG16(0x6f12), 0x0904 }, + { CCI_REG16(0x602a), 0x0812 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x1a02 }, + { CCI_REG16(0x6f12), 0x1800 }, + { CCI_REG16(0x602a), 0x2148 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x2042 }, + { CCI_REG16(0x6f12), 0x1a00 }, + { CCI_REG16(0x602a), 0x0874 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x09c0 }, + { CCI_REG16(0x6f12), 0x2008 }, + { CCI_REG16(0x602a), 0x09c4 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x602a), 0x19fe }, + { CCI_REG16(0x6f12), 0x0e1c }, + { CCI_REG16(0x602a), 0x4d92 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x84c8 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x4d94 }, + { CCI_REG16(0x6f12), 0x0005 }, + { CCI_REG16(0x6f12), 0x000a }, + { CCI_REG16(0x6f12), 0x0010 }, + { CCI_REG16(0x6f12), 0x0810 }, + { CCI_REG16(0x6f12), 0x000a }, + { CCI_REG16(0x6f12), 0x0040 }, + { CCI_REG16(0x6f12), 0x0810 }, + { CCI_REG16(0x6f12), 0x0810 }, + { CCI_REG16(0x6f12), 0x8002 }, + { CCI_REG16(0x6f12), 0xfd03 }, + { CCI_REG16(0x6f12), 0x0010 }, + { CCI_REG16(0x6f12), 0x1510 }, + { CCI_REG16(0x602a), 0x3570 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x3574 }, + { CCI_REG16(0x6f12), 0x1201 }, + { CCI_REG16(0x602a), 0x21e4 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x602a), 0x21ec }, + { CCI_REG16(0x6f12), 0x1f04 }, + { CCI_REG16(0x602a), 0x2080 }, + { CCI_REG16(0x6f12), 0x0101 }, + { CCI_REG16(0x6f12), 0xff00 }, + { CCI_REG16(0x6f12), 0x7f01 }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x6f12), 0x8001 }, + { CCI_REG16(0x6f12), 0xd244 }, + { CCI_REG16(0x6f12), 0xd244 }, + { CCI_REG16(0x6f12), 0x14f4 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x20ba }, + { CCI_REG16(0x6f12), 0x141c }, + { CCI_REG16(0x6f12), 0x111c }, + { CCI_REG16(0x6f12), 0x54f4 }, + { CCI_REG16(0x602a), 0x120e }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x212e }, + { CCI_REG16(0x6f12), 0x0200 }, + { CCI_REG16(0x602a), 0x13ae }, + { CCI_REG16(0x6f12), 0x0101 }, + { CCI_REG16(0x602a), 0x0718 }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x602a), 0x0710 }, + { CCI_REG16(0x6f12), 0x0002 }, + { CCI_REG16(0x6f12), 0x0804 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x1b5c }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x0786 }, + { CCI_REG16(0x6f12), 0x7701 }, + { CCI_REG16(0x602a), 0x2022 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x602a), 0x1360 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x1376 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x6f12), 0x6038 }, + { CCI_REG16(0x6f12), 0x7038 }, + { CCI_REG16(0x6f12), 0x8038 }, + { CCI_REG16(0x602a), 0x1386 }, + { CCI_REG16(0x6f12), 0x0b00 }, + { CCI_REG16(0x602a), 0x06fa }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x4a94 }, + { CCI_REG16(0x6f12), 0x0900 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0300 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0300 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0900 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x0a76 }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x0aee }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x0b66 }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x0bde }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x0be8 }, + { CCI_REG16(0x6f12), 0x3000 }, + { CCI_REG16(0x6f12), 0x3000 }, + { CCI_REG16(0x602a), 0x0c56 }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x0c60 }, + { CCI_REG16(0x6f12), 0x3000 }, + { CCI_REG16(0x6f12), 0x3000 }, + { CCI_REG16(0x602a), 0x0cb6 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x0cf2 }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x602a), 0x0cf0 }, + { CCI_REG16(0x6f12), 0x0101 }, + { CCI_REG16(0x602a), 0x11b8 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x11f6 }, + { CCI_REG16(0x6f12), 0x0020 }, + { CCI_REG16(0x602a), 0x4a74 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0xd8ff }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0xd8ff }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x218e }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x2268 }, + { CCI_REG16(0x6f12), 0xf279 }, + { CCI_REG16(0x602a), 0x5006 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x500e }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x4e70 }, + { CCI_REG16(0x6f12), 0x2062 }, + { CCI_REG16(0x6f12), 0x5501 }, + { CCI_REG16(0x602a), 0x06dc }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6028), 0x4000 }, + { CCI_REG16(0xf46a), 0xae80 }, + { S5KJN1_REG_X_ADDR_START, 0x0000 }, + { S5KJN1_REG_Y_ADDR_START, 0x0000 }, + { S5KJN1_REG_X_ADDR_END, 0x1fff }, + { S5KJN1_REG_Y_ADDR_END, 0x181f }, + { S5KJN1_REG_X_OUTPUT_SIZE, 0x0ff0 }, + { S5KJN1_REG_Y_OUTPUT_SIZE, 0x0c00 }, + { CCI_REG16(0x0350), 0x0008 }, + { CCI_REG16(0x0352), 0x0008 }, + { CCI_REG16(0x0900), 0x0122 }, + { CCI_REG16(0x0380), 0x0002 }, + { CCI_REG16(0x0382), 0x0002 }, + { CCI_REG16(0x0384), 0x0002 }, + { CCI_REG16(0x0386), 0x0002 }, + { CCI_REG16(0x0110), 0x1002 }, + { CCI_REG16(0x0114), 0x0301 }, + { CCI_REG16(0x0116), 0x3000 }, + + /* Clock settings */ + { CCI_REG16(0x0136), 0x1800 }, + { CCI_REG16(0x013e), 0x0000 }, + { CCI_REG16(0x0300), 0x0006 }, + { CCI_REG16(0x0302), 0x0001 }, + { CCI_REG16(0x0304), 0x0004 }, + { CCI_REG16(0x0306), 0x008c }, + { CCI_REG16(0x0308), 0x0008 }, + { CCI_REG16(0x030a), 0x0001 }, + { CCI_REG16(0x030c), 0x0000 }, + { CCI_REG16(0x030e), 0x0004 }, + { CCI_REG16(0x0310), 0x0092 }, + { CCI_REG16(0x0312), 0x0000 }, + + { CCI_REG16(0x080e), 0x0000 }, + { S5KJN1_REG_VTS, 0x10c0 }, + { S5KJN1_REG_HTS, 0x1100 }, + { CCI_REG16(0x0702), 0x0000 }, + { S5KJN1_REG_EXPOSURE, 0x0100 }, + { CCI_REG16(0x0200), 0x0100 }, + { CCI_REG16(0x0d00), 0x0101 }, + { CCI_REG16(0x0d02), 0x0101 }, + { CCI_REG16(0x0d04), 0x0102 }, + { CCI_REG16(0x6226), 0x0000 }, + { CCI_REG16(0x0816), 0x1c00 }, +}; + +static const struct cci_reg_sequence s5kjn1_8160x6144_10fps_mode[] = { + { CCI_REG16(0x6028), 0x2400 }, + { CCI_REG16(0x602a), 0x1a28 }, + { CCI_REG16(0x6f12), 0x4c00 }, + { CCI_REG16(0x602a), 0x065a }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x139e }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x602a), 0x139c }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x13a0 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x6f12), 0x0120 }, + { CCI_REG16(0x602a), 0x2072 }, + { CCI_REG16(0x6f12), 0x0101 }, + { CCI_REG16(0x602a), 0x1a64 }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x19e6 }, + { CCI_REG16(0x6f12), 0x0200 }, + { CCI_REG16(0x602a), 0x1a30 }, + { CCI_REG16(0x6f12), 0x3403 }, + { CCI_REG16(0x602a), 0x19fc }, + { CCI_REG16(0x6f12), 0x0700 }, + { CCI_REG16(0x602a), 0x19f4 }, + { CCI_REG16(0x6f12), 0x0707 }, + { CCI_REG16(0x602a), 0x19f8 }, + { CCI_REG16(0x6f12), 0x0b0b }, + { CCI_REG16(0x602a), 0x1b26 }, + { CCI_REG16(0x6f12), 0x6f80 }, + { CCI_REG16(0x6f12), 0xa060 }, + { CCI_REG16(0x602a), 0x1a3c }, + { CCI_REG16(0x6f12), 0x8207 }, + { CCI_REG16(0x602a), 0x1a48 }, + { CCI_REG16(0x6f12), 0x8207 }, + { CCI_REG16(0x602a), 0x1444 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x602a), 0x144c }, + { CCI_REG16(0x6f12), 0x3f00 }, + { CCI_REG16(0x6f12), 0x3f00 }, + { CCI_REG16(0x602a), 0x7f6c }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x6f12), 0x2f00 }, + { CCI_REG16(0x6f12), 0xfa00 }, + { CCI_REG16(0x6f12), 0x2400 }, + { CCI_REG16(0x6f12), 0xe500 }, + { CCI_REG16(0x602a), 0x0650 }, + { CCI_REG16(0x6f12), 0x0600 }, + { CCI_REG16(0x602a), 0x0654 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x1a46 }, + { CCI_REG16(0x6f12), 0x8500 }, + { CCI_REG16(0x602a), 0x1a52 }, + { CCI_REG16(0x6f12), 0x9800 }, + { CCI_REG16(0x602a), 0x0674 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x602a), 0x0668 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x602a), 0x0684 }, + { CCI_REG16(0x6f12), 0x4001 }, + { CCI_REG16(0x602a), 0x0688 }, + { CCI_REG16(0x6f12), 0x4001 }, + { CCI_REG16(0x602a), 0x147c }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x602a), 0x1480 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x602a), 0x19f6 }, + { CCI_REG16(0x6f12), 0x0404 }, + { CCI_REG16(0x602a), 0x0812 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x1a02 }, + { CCI_REG16(0x6f12), 0x1800 }, + { CCI_REG16(0x602a), 0x2148 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x2042 }, + { CCI_REG16(0x6f12), 0x1a00 }, + { CCI_REG16(0x602a), 0x0874 }, + { CCI_REG16(0x6f12), 0x0106 }, + { CCI_REG16(0x602a), 0x09c0 }, + { CCI_REG16(0x6f12), 0x4000 }, + { CCI_REG16(0x602a), 0x09c4 }, + { CCI_REG16(0x6f12), 0x4000 }, + { CCI_REG16(0x602a), 0x19fe }, + { CCI_REG16(0x6f12), 0x0c1c }, + { CCI_REG16(0x602a), 0x4d92 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x84c8 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x4d94 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x3570 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x3574 }, + { CCI_REG16(0x6f12), 0x7306 }, + { CCI_REG16(0x602a), 0x21e4 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x602a), 0x21ec }, + { CCI_REG16(0x6f12), 0x6902 }, + { CCI_REG16(0x602a), 0x2080 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x6f12), 0xff00 }, + { CCI_REG16(0x6f12), 0x0002 }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x6f12), 0x0002 }, + { CCI_REG16(0x6f12), 0xd244 }, + { CCI_REG16(0x6f12), 0xd244 }, + { CCI_REG16(0x6f12), 0x14f4 }, + { CCI_REG16(0x6f12), 0x101c }, + { CCI_REG16(0x6f12), 0x0d1c }, + { CCI_REG16(0x6f12), 0x54f4 }, + { CCI_REG16(0x602a), 0x20ba }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x120e }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x212e }, + { CCI_REG16(0x6f12), 0x0200 }, + { CCI_REG16(0x602a), 0x13ae }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x0718 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x0710 }, + { CCI_REG16(0x6f12), 0x0010 }, + { CCI_REG16(0x6f12), 0x0201 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x602a), 0x1b5c }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x0786 }, + { CCI_REG16(0x6f12), 0x1401 }, + { CCI_REG16(0x602a), 0x2022 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x6f12), 0x0500 }, + { CCI_REG16(0x602a), 0x1360 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x1376 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x6038 }, + { CCI_REG16(0x6f12), 0x7038 }, + { CCI_REG16(0x6f12), 0x8038 }, + { CCI_REG16(0x602a), 0x1386 }, + { CCI_REG16(0x6f12), 0x0b00 }, + { CCI_REG16(0x602a), 0x06fa }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x4a94 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x6f12), 0x0400 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x6f12), 0x0800 }, + { CCI_REG16(0x602a), 0x0a76 }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x0aee }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x0b66 }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x0bde }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x0be8 }, + { CCI_REG16(0x6f12), 0x5000 }, + { CCI_REG16(0x6f12), 0x5000 }, + { CCI_REG16(0x602a), 0x0c56 }, + { CCI_REG16(0x6f12), 0x1000 }, + { CCI_REG16(0x602a), 0x0c60 }, + { CCI_REG16(0x6f12), 0x5000 }, + { CCI_REG16(0x6f12), 0x5000 }, + { CCI_REG16(0x602a), 0x0cb6 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x0cf2 }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x602a), 0x0cf0 }, + { CCI_REG16(0x6f12), 0x0101 }, + { CCI_REG16(0x602a), 0x11b8 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x11f6 }, + { CCI_REG16(0x6f12), 0x0010 }, + { CCI_REG16(0x602a), 0x4a74 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x218e }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x2268 }, + { CCI_REG16(0x6f12), 0xf279 }, + { CCI_REG16(0x602a), 0x5006 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x500e }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x4e70 }, + { CCI_REG16(0x6f12), 0x2062 }, + { CCI_REG16(0x6f12), 0x5501 }, + { CCI_REG16(0x602a), 0x06dc }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6028), 0x4000 }, + { CCI_REG16(0xf46a), 0xae80 }, + { S5KJN1_REG_X_ADDR_START, 0x0000 }, + { S5KJN1_REG_Y_ADDR_START, 0x0000 }, + { S5KJN1_REG_X_ADDR_END, 0x1fff }, + { S5KJN1_REG_Y_ADDR_END, 0x181f }, + { S5KJN1_REG_X_OUTPUT_SIZE, 0x1fe0 }, + { S5KJN1_REG_Y_OUTPUT_SIZE, 0x1800 }, + { CCI_REG16(0x0350), 0x0010 }, + { CCI_REG16(0x0352), 0x0010 }, + { CCI_REG16(0x0900), 0x0111 }, + { CCI_REG16(0x0380), 0x0001 }, + { CCI_REG16(0x0382), 0x0001 }, + { CCI_REG16(0x0384), 0x0001 }, + { CCI_REG16(0x0386), 0x0001 }, + { CCI_REG16(0x0110), 0x1002 }, + { CCI_REG16(0x0114), 0x0300 }, + { CCI_REG16(0x0116), 0x3000 }, + + /* Clock settings */ + { CCI_REG16(0x0136), 0x1800 }, + { CCI_REG16(0x013e), 0x0000 }, + { CCI_REG16(0x0300), 0x0006 }, + { CCI_REG16(0x0302), 0x0001 }, + { CCI_REG16(0x0304), 0x0004 }, + { CCI_REG16(0x0306), 0x008c }, + { CCI_REG16(0x0308), 0x0008 }, + { CCI_REG16(0x030a), 0x0001 }, + { CCI_REG16(0x030c), 0x0000 }, + { CCI_REG16(0x030e), 0x0004 }, + { CCI_REG16(0x0310), 0x0074 }, + { CCI_REG16(0x0312), 0x0000 }, + + { CCI_REG16(0x080e), 0x0000 }, + { S5KJN1_REG_VTS, 0x1900 }, + { S5KJN1_REG_HTS, 0x21f0 }, + { CCI_REG16(0x0702), 0x0000 }, + { S5KJN1_REG_EXPOSURE, 0x0100 }, + { CCI_REG16(0x0200), 0x0100 }, + { CCI_REG16(0x0d00), 0x0100 }, + { CCI_REG16(0x0d02), 0x0001 }, + { CCI_REG16(0x0d04), 0x0002 }, + { CCI_REG16(0x6226), 0x0000 }, +}; + +static const struct s5kjn1_mode s5kjn1_supported_modes[] = { + { + .width = 4080, + .height = 3072, + .hts = 4352, + .vts = 4288, + .exposure = 3840, + .exposure_margin = 22, + .reg_list = { + .regs = s5kjn1_4080x3072_30fps_mode, + .num_regs = ARRAY_SIZE(s5kjn1_4080x3072_30fps_mode), + }, + }, + { + .width = 8160, + .height = 6144, + .hts = 8688, + .vts = 6400, + .exposure = 6144, + .exposure_margin = 44, + .reg_list = { + .regs = s5kjn1_8160x6144_10fps_mode, + .num_regs = ARRAY_SIZE(s5kjn1_8160x6144_10fps_mode), + }, + }, +}; + +static int s5kjn1_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s5kjn1 *s5kjn1 = container_of(ctrl->handler, struct s5kjn1, + ctrl_handler); + const struct s5kjn1_mode *mode = s5kjn1->mode; + s64 exposure_max; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + exposure_max = mode->height + ctrl->val - mode->exposure_margin; + __v4l2_ctrl_modify_range(s5kjn1->exposure, + s5kjn1->exposure->minimum, + exposure_max, + s5kjn1->exposure->step, + s5kjn1->exposure->default_value); + break; + } + + /* V4L2 controls are applied, when sensor is powered up for streaming */ + if (!pm_runtime_get_if_active(s5kjn1->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = cci_write(s5kjn1->regmap, S5KJN1_REG_AGAIN, + ctrl->val << S5KJN1_AGAIN_SHIFT, NULL); + break; + case V4L2_CID_EXPOSURE: + ret = cci_write(s5kjn1->regmap, S5KJN1_REG_EXPOSURE, + ctrl->val, NULL); + break; + case V4L2_CID_VBLANK: + ret = cci_write(s5kjn1->regmap, S5KJN1_REG_VTS, + ctrl->val + mode->height, NULL); + break; + case V4L2_CID_VFLIP: + case V4L2_CID_HFLIP: + ret = cci_write(s5kjn1->regmap, S5KJN1_REG_ORIENTATION, + (s5kjn1->vflip->val ? S5KJN1_VFLIP : 0) | + (s5kjn1->hflip->val ? S5KJN1_HFLIP : 0), NULL); + break; + case V4L2_CID_TEST_PATTERN: + ret = cci_write(s5kjn1->regmap, S5KJN1_REG_TEST_PATTERN, + ctrl->val, NULL); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(s5kjn1->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops s5kjn1_ctrl_ops = { + .s_ctrl = s5kjn1_set_ctrl, +}; + +static inline u64 s5kjn1_freq_to_pixel_rate(const u64 freq) +{ + return div_u64(freq * 2 * S5KJN1_DATA_LANES, 10); +} + +static int s5kjn1_init_controls(struct s5kjn1 *s5kjn1) +{ + struct v4l2_ctrl_handler *ctrl_hdlr = &s5kjn1->ctrl_handler; + const struct s5kjn1_mode *mode = s5kjn1->mode; + s64 pixel_rate, hblank, vblank, exposure_max; + struct v4l2_fwnode_device_properties props; + int ret; + + v4l2_ctrl_handler_init(ctrl_hdlr, 9); + + s5kjn1->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &s5kjn1_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(s5kjn1_link_freq_menu) - 1, + 0, s5kjn1_link_freq_menu); + if (s5kjn1->link_freq) + s5kjn1->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate = s5kjn1_freq_to_pixel_rate(s5kjn1_link_freq_menu[0]); + s5kjn1->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &s5kjn1_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 0, pixel_rate, 1, pixel_rate); + + hblank = mode->hts - mode->width; + s5kjn1->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &s5kjn1_ctrl_ops, + V4L2_CID_HBLANK, hblank, + hblank, 1, hblank); + if (s5kjn1->hblank) + s5kjn1->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank = mode->vts - mode->height; + s5kjn1->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &s5kjn1_ctrl_ops, + V4L2_CID_VBLANK, vblank, + S5KJN1_VTS_MAX - mode->height, 1, + vblank); + + v4l2_ctrl_new_std(ctrl_hdlr, &s5kjn1_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + S5KJN1_AGAIN_MIN, S5KJN1_AGAIN_MAX, + S5KJN1_AGAIN_STEP, S5KJN1_AGAIN_DEFAULT); + + exposure_max = mode->vts - mode->exposure_margin; + s5kjn1->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &s5kjn1_ctrl_ops, + V4L2_CID_EXPOSURE, + S5KJN1_EXPOSURE_MIN, + exposure_max, + S5KJN1_EXPOSURE_STEP, + mode->exposure); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &s5kjn1_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(s5kjn1_test_pattern_menu) - 1, + 0, 0, s5kjn1_test_pattern_menu); + + s5kjn1->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &s5kjn1_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (s5kjn1->hflip) + s5kjn1->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + s5kjn1->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &s5kjn1_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (s5kjn1->vflip) + s5kjn1->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + ret = v4l2_fwnode_device_parse(s5kjn1->dev, &props); + if (ret) + goto error_free_hdlr; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &s5kjn1_ctrl_ops, + &props); + if (ret) + goto error_free_hdlr; + + s5kjn1->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error_free_hdlr: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int s5kjn1_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct s5kjn1 *s5kjn1 = to_s5kjn1(sd); + const struct s5kjn1_reg_list *reg_list = &s5kjn1->mode->reg_list; + int ret; + + ret = pm_runtime_resume_and_get(s5kjn1->dev); + if (ret) + return ret; + + /* Page pointer */ + cci_write(s5kjn1->regmap, CCI_REG16(0x6028), 0x4000, &ret); + + /* Set version */ + cci_write(s5kjn1->regmap, CCI_REG16(0x0000), 0x0003, &ret); + cci_write(s5kjn1->regmap, CCI_REG16(0x0000), S5KJN1_CHIP_ID, &ret); + cci_write(s5kjn1->regmap, CCI_REG16(0x001e), 0x0007, &ret); + cci_write(s5kjn1->regmap, CCI_REG16(0x6028), 0x4000, &ret); + cci_write(s5kjn1->regmap, CCI_REG16(0x6010), 0x0001, &ret); + if (ret) + goto error; + + usleep_range(5 * USEC_PER_MSEC, 6 * USEC_PER_MSEC); + + cci_write(s5kjn1->regmap, CCI_REG16(0x6226), 0x0001, &ret); + if (ret) + goto error; + + usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC); + + /* Sensor init settings */ + cci_multi_reg_write(s5kjn1->regmap, init_array_setting, + ARRAY_SIZE(init_array_setting), &ret); + cci_multi_reg_write(s5kjn1->regmap, reg_list->regs, + reg_list->num_regs, &ret); + if (ret) + goto error; + + ret = __v4l2_ctrl_handler_setup(s5kjn1->sd.ctrl_handler); + + cci_write(s5kjn1->regmap, S5KJN1_REG_CTRL_MODE, + S5KJN1_MODE_STREAMING, &ret); + if (ret) + goto error; + + return 0; + +error: + dev_err(s5kjn1->dev, "failed to start streaming: %d\n", ret); + pm_runtime_put_autosuspend(s5kjn1->dev); + + return ret; +} + +static int s5kjn1_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct s5kjn1 *s5kjn1 = to_s5kjn1(sd); + int ret; + + ret = cci_write(s5kjn1->regmap, S5KJN1_REG_CTRL_MODE, 0x0, NULL); + if (ret) + dev_err(s5kjn1->dev, "failed to stop streaming: %d\n", ret); + + pm_runtime_put_autosuspend(s5kjn1->dev); + + return ret; +} + +static u32 s5kjn1_get_format_code(struct s5kjn1 *s5kjn1) +{ + unsigned int i; + + i = (s5kjn1->vflip->val ? 2 : 0) | (s5kjn1->hflip->val ? 1 : 0); + + return s5kjn1_mbus_formats[i]; +} + +static void s5kjn1_update_pad_format(struct s5kjn1 *s5kjn1, + const struct s5kjn1_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->code = s5kjn1_get_format_code(s5kjn1); + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; +} + +static int s5kjn1_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct s5kjn1 *s5kjn1 = to_s5kjn1(sd); + s64 hblank, vblank, exposure_max; + const struct s5kjn1_mode *mode; + + mode = v4l2_find_nearest_size(s5kjn1_supported_modes, + ARRAY_SIZE(s5kjn1_supported_modes), + width, height, + fmt->format.width, fmt->format.height); + + s5kjn1_update_pad_format(s5kjn1, mode, &fmt->format); + + /* Format code could be updated with respect to flip controls */ + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY || s5kjn1->mode == mode) + goto set_format; + + /* Update limits and set FPS and exposure to default values */ + hblank = mode->hts - mode->width; + __v4l2_ctrl_modify_range(s5kjn1->hblank, hblank, hblank, 1, hblank); + + vblank = mode->vts - mode->height; + __v4l2_ctrl_modify_range(s5kjn1->vblank, vblank, + S5KJN1_VTS_MAX - mode->height, 1, vblank); + __v4l2_ctrl_s_ctrl(s5kjn1->vblank, vblank); + + exposure_max = mode->vts - mode->exposure_margin; + __v4l2_ctrl_modify_range(s5kjn1->exposure, S5KJN1_EXPOSURE_MIN, + exposure_max, S5KJN1_EXPOSURE_STEP, + mode->exposure); + __v4l2_ctrl_s_ctrl(s5kjn1->exposure, mode->exposure); + + if (s5kjn1->sd.ctrl_handler->error) + return s5kjn1->sd.ctrl_handler->error; + + s5kjn1->mode = mode; + +set_format: + *v4l2_subdev_state_get_format(state, 0) = fmt->format; + + return 0; +} + +static int s5kjn1_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct s5kjn1 *s5kjn1 = to_s5kjn1(sd); + + /* Media bus code index is constant, but code formats are not */ + if (code->index > 0) + return -EINVAL; + + code->code = s5kjn1_get_format_code(s5kjn1); + + return 0; +} + +static int s5kjn1_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct s5kjn1 *s5kjn1 = to_s5kjn1(sd); + + if (fse->index >= ARRAY_SIZE(s5kjn1_supported_modes)) + return -EINVAL; + + if (fse->code != s5kjn1_get_format_code(s5kjn1)) + return -EINVAL; + + fse->min_width = s5kjn1_supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = s5kjn1_supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int s5kjn1_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct s5kjn1 *s5kjn1 = to_s5kjn1(sd); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = s5kjn1->mode->width; + sel->r.height = s5kjn1->mode->width; + return 0; + default: + return -EINVAL; + } + + return 0; +} + +static int s5kjn1_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct s5kjn1 *s5kjn1 = to_s5kjn1(sd); + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = 0, + .format = { + /* Media bus code depends on current flip controls */ + .width = s5kjn1->mode->width, + .height = s5kjn1->mode->height, + }, + }; + + s5kjn1_set_pad_format(sd, state, &fmt); + + return 0; +} + +static const struct v4l2_subdev_video_ops s5kjn1_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops s5kjn1_pad_ops = { + .set_fmt = s5kjn1_set_pad_format, + .get_fmt = v4l2_subdev_get_fmt, + .get_selection = s5kjn1_get_selection, + .enum_mbus_code = s5kjn1_enum_mbus_code, + .enum_frame_size = s5kjn1_enum_frame_size, + .enable_streams = s5kjn1_enable_streams, + .disable_streams = s5kjn1_disable_streams, +}; + +static const struct v4l2_subdev_ops s5kjn1_subdev_ops = { + .video = &s5kjn1_video_ops, + .pad = &s5kjn1_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops s5kjn1_internal_ops = { + .init_state = s5kjn1_init_state, +}; + +static const struct media_entity_operations s5kjn1_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int s5kjn1_identify_sensor(struct s5kjn1 *s5kjn1) +{ + u64 val; + int ret; + + ret = cci_read(s5kjn1->regmap, S5KJN1_REG_CHIP_ID, &val, NULL); + if (ret) { + dev_err(s5kjn1->dev, "failed to read chip id: %d\n", ret); + return ret; + } + + if (val != S5KJN1_CHIP_ID) { + dev_err(s5kjn1->dev, "chip id mismatch: %x!=%llx\n", + S5KJN1_CHIP_ID, val); + return -ENODEV; + } + + return 0; +} + +static int s5kjn1_check_hwcfg(struct s5kjn1 *s5kjn1) +{ + struct fwnode_handle *fwnode = dev_fwnode(s5kjn1->dev), *ep; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus = { + .mipi_csi2 = { + .num_data_lanes = S5KJN1_DATA_LANES, + }, + }, + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + unsigned long freq_bitmap; + int ret; + + if (!fwnode) + return -ENODEV; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != S5KJN1_DATA_LANES) { + dev_err(s5kjn1->dev, "Invalid number of data lanes: %u\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto endpoint_free; + } + + ret = v4l2_link_freq_to_bitmap(s5kjn1->dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + s5kjn1_link_freq_menu, + ARRAY_SIZE(s5kjn1_link_freq_menu), + &freq_bitmap); + +endpoint_free: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int s5kjn1_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct s5kjn1 *s5kjn1 = to_s5kjn1(sd); + int ret; + + if (s5kjn1->vddd) { + ret = regulator_enable(s5kjn1->vddd); + if (ret) + return ret; + + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + } + + if (s5kjn1->vdda) { + ret = regulator_enable(s5kjn1->vdda); + if (ret) + goto disable_vddd; + } + + if (s5kjn1->vddio) { + ret = regulator_enable(s5kjn1->vddio); + if (ret) + goto disable_vdda; + } + + if (s5kjn1->afvdd) { + ret = regulator_enable(s5kjn1->afvdd); + if (ret) + goto disable_vddio; + } + + ret = clk_prepare_enable(s5kjn1->mclk); + if (ret) + goto disable_regulators; + + gpiod_set_value_cansleep(s5kjn1->reset_gpio, 0); + usleep_range(10 * USEC_PER_MSEC, 15 * USEC_PER_MSEC); + + return 0; + +disable_regulators: + if (s5kjn1->afvdd) + regulator_disable(s5kjn1->afvdd); + +disable_vddio: + if (s5kjn1->vddio) + regulator_disable(s5kjn1->vddio); + +disable_vdda: + if (s5kjn1->vdda) + regulator_disable(s5kjn1->vdda); + +disable_vddd: + if (s5kjn1->vddd) { + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + regulator_disable(s5kjn1->vddd); + } + + return ret; +} + +static int s5kjn1_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct s5kjn1 *s5kjn1 = to_s5kjn1(sd); + + gpiod_set_value_cansleep(s5kjn1->reset_gpio, 1); + + clk_disable_unprepare(s5kjn1->mclk); + + if (s5kjn1->afvdd) + regulator_disable(s5kjn1->afvdd); + + if (s5kjn1->vddio) + regulator_disable(s5kjn1->vddio); + + if (s5kjn1->vdda) + regulator_disable(s5kjn1->vdda); + + if (s5kjn1->vddd) { + usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC); + regulator_disable(s5kjn1->vddd); + } + + return 0; +} + +static int s5kjn1_probe(struct i2c_client *client) +{ + struct s5kjn1 *s5kjn1; + unsigned long freq; + int ret; + + s5kjn1 = devm_kzalloc(&client->dev, sizeof(*s5kjn1), GFP_KERNEL); + if (!s5kjn1) + return -ENOMEM; + + s5kjn1->dev = &client->dev; + v4l2_i2c_subdev_init(&s5kjn1->sd, client, &s5kjn1_subdev_ops); + + s5kjn1->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(s5kjn1->regmap)) + return dev_err_probe(s5kjn1->dev, PTR_ERR(s5kjn1->regmap), + "failed to init CCI\n"); + + s5kjn1->mclk = devm_v4l2_sensor_clk_get(s5kjn1->dev, NULL); + if (IS_ERR(s5kjn1->mclk)) + return dev_err_probe(s5kjn1->dev, PTR_ERR(s5kjn1->mclk), + "failed to get MCLK clock\n"); + + freq = clk_get_rate(s5kjn1->mclk); + if (freq != S5KJN1_MCLK_FREQ_24MHZ) + return dev_err_probe(s5kjn1->dev, -EINVAL, + "MCLK clock frequency %lu is not supported\n", + freq); + + ret = s5kjn1_check_hwcfg(s5kjn1); + if (ret) + return dev_err_probe(s5kjn1->dev, ret, + "failed to check HW configuration\n"); + + s5kjn1->reset_gpio = devm_gpiod_get_optional(s5kjn1->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(s5kjn1->reset_gpio)) + return dev_err_probe(s5kjn1->dev, PTR_ERR(s5kjn1->reset_gpio), + "cannot get reset GPIO\n"); + + s5kjn1->afvdd = devm_regulator_get_optional(s5kjn1->dev, "afvdd"); + if (IS_ERR(s5kjn1->afvdd)) { + ret = PTR_ERR(s5kjn1->afvdd); + if (ret != -ENODEV) { + return dev_err_probe(s5kjn1->dev, ret, + "Failed to get 'afvdd' regulator\n"); + } + + s5kjn1->afvdd = NULL; + } + + s5kjn1->vdda = devm_regulator_get_optional(s5kjn1->dev, "vdda"); + if (IS_ERR(s5kjn1->vdda)) { + ret = PTR_ERR(s5kjn1->vdda); + if (ret != -ENODEV) { + return dev_err_probe(s5kjn1->dev, ret, + "Failed to get 'vdda' regulator\n"); + } + + s5kjn1->vdda = NULL; + } + + s5kjn1->vddd = devm_regulator_get_optional(s5kjn1->dev, "vddd"); + if (IS_ERR(s5kjn1->vddd)) { + ret = PTR_ERR(s5kjn1->vddd); + if (ret != -ENODEV) { + return dev_err_probe(s5kjn1->dev, ret, + "Failed to get 'vddd' regulator\n"); + } + + s5kjn1->vddd = NULL; + } + + s5kjn1->vddio = devm_regulator_get_optional(s5kjn1->dev, "vddio"); + if (IS_ERR(s5kjn1->vddio)) { + ret = PTR_ERR(s5kjn1->vddio); + if (ret != -ENODEV) { + return dev_err_probe(s5kjn1->dev, ret, + "Failed to get 'vddio' regulator\n"); + } + + s5kjn1->vddio = NULL; + } + + /* The sensor must be powered on to read the CHIP_ID register */ + ret = s5kjn1_power_on(s5kjn1->dev); + if (ret) + return ret; + + ret = s5kjn1_identify_sensor(s5kjn1); + if (ret) { + dev_err_probe(s5kjn1->dev, ret, "failed to find sensor\n"); + goto power_off; + } + + s5kjn1->mode = &s5kjn1_supported_modes[0]; + ret = s5kjn1_init_controls(s5kjn1); + if (ret) { + dev_err_probe(s5kjn1->dev, ret, "failed to init controls\n"); + goto power_off; + } + + s5kjn1->sd.state_lock = s5kjn1->ctrl_handler.lock; + s5kjn1->sd.internal_ops = &s5kjn1_internal_ops; + s5kjn1->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + s5kjn1->sd.entity.ops = &s5kjn1_subdev_entity_ops; + s5kjn1->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + s5kjn1->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&s5kjn1->sd.entity, 1, &s5kjn1->pad); + if (ret) { + dev_err_probe(s5kjn1->dev, ret, + "failed to init media entity pads\n"); + goto v4l2_ctrl_handler_free; + } + + ret = v4l2_subdev_init_finalize(&s5kjn1->sd); + if (ret < 0) { + dev_err_probe(s5kjn1->dev, ret, + "failed to init media entity pads\n"); + goto media_entity_cleanup; + } + + pm_runtime_set_active(s5kjn1->dev); + pm_runtime_enable(s5kjn1->dev); + + ret = v4l2_async_register_subdev_sensor(&s5kjn1->sd); + if (ret < 0) { + dev_err_probe(s5kjn1->dev, ret, + "failed to register V4L2 subdev\n"); + goto subdev_cleanup; + } + + pm_runtime_set_autosuspend_delay(s5kjn1->dev, 1000); + pm_runtime_use_autosuspend(s5kjn1->dev); + pm_runtime_idle(s5kjn1->dev); + + return 0; + +subdev_cleanup: + v4l2_subdev_cleanup(&s5kjn1->sd); + pm_runtime_disable(s5kjn1->dev); + pm_runtime_set_suspended(s5kjn1->dev); + +media_entity_cleanup: + media_entity_cleanup(&s5kjn1->sd.entity); + +v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(s5kjn1->sd.ctrl_handler); + +power_off: + s5kjn1_power_off(s5kjn1->dev); + + return ret; +} + +static void s5kjn1_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct s5kjn1 *s5kjn1 = to_s5kjn1(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(s5kjn1->dev); + + if (!pm_runtime_status_suspended(s5kjn1->dev)) { + s5kjn1_power_off(s5kjn1->dev); + pm_runtime_set_suspended(s5kjn1->dev); + } +} + +static const struct dev_pm_ops s5kjn1_pm_ops = { + SET_RUNTIME_PM_OPS(s5kjn1_power_off, s5kjn1_power_on, NULL) +}; + +static const struct of_device_id s5kjn1_of_match[] = { + { .compatible = "samsung,s5kjn1" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s5kjn1_of_match); + +static struct i2c_driver s5kjn1_i2c_driver = { + .driver = { + .name = "s5kjn1", + .pm = &s5kjn1_pm_ops, + .of_match_table = s5kjn1_of_match, + }, + .probe = s5kjn1_probe, + .remove = s5kjn1_remove, +}; + +module_i2c_driver(s5kjn1_i2c_driver); + +MODULE_AUTHOR("Vladimir Zapolskiy "); +MODULE_DESCRIPTION("Samsung S5KJN1 image sensor driver"); +MODULE_LICENSE("GPL"); From d855e1279b2734d1567ddf8fa1a0c62180dd3cbb Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Wed, 3 Dec 2025 06:02:39 +0200 Subject: [PATCH 053/307] dt-bindings: media: i2c: Add Samsung S5K3M5 image sensor Add device tree bindings documentation for Samsung S5K3M5 image sensor. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Vladimir Zapolskiy Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../bindings/media/i2c/samsung,s5k3m5.yaml | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/samsung,s5k3m5.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/samsung,s5k3m5.yaml b/Documentation/devicetree/bindings/media/i2c/samsung,s5k3m5.yaml new file mode 100644 index 000000000000..434f15f64bcd --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/samsung,s5k3m5.yaml @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/samsung,s5k3m5.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung S5K3M5 Image Sensor + +description: + Samsung S5K3M5 (ISOCELL 3M5) image sensor is a 13MP image sensor. + The sensor is controlled over a serial camera control bus protocol, + the widest supported output image frame size is 4208x3120 at 30 frames + per second, data output format is RAW10 transferred over 4-lane + MIPI D-PHY interface. + +maintainers: + - Vladimir Zapolskiy + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: samsung,s5k3m5 + + reg: + maxItems: 1 + + clocks: + description: MCLK supply clock. + maxItems: 1 + + reset-gpios: + description: Active low GPIO connected to RESET pad of the sensor. + maxItems: 1 + + afvdd-supply: + description: Autofocus actuator voltage supply, 2.8-3.0 volts. + + vdda-supply: + description: Analogue voltage supply, 2.8 volts. + + vddd-supply: + description: Digital core voltage supply, 1.05 volts. + + vddio-supply: + description: Digital I/O voltage supply, 2.8 or 1.8 volts. + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + required: + - link-frequencies + +required: + - compatible + - reg + - port + +unevaluatedProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@10 { + compatible = "samsung,s5k3m5"; + reg = <0x10>; + clocks = <&camera_mclk 0>; + assigned-clocks = <&camera_mclk 0>; + assigned-clock-rates = <24000000>; + reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; + vdda-supply = <&vreg_2p8>; + vddd-supply = <&vreg_1p05>; + vddio-supply = <&vreg_1p8>; + + port { + endpoint { + link-frequencies = /bits/ 64 <602500000>; + remote-endpoint = <&mipi_csi2_ep>; + }; + }; + }; + }; +... From 7d402666094ce25d2b52da679b8d7210cde01a34 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Wed, 3 Dec 2025 06:02:40 +0200 Subject: [PATCH 054/307] media: i2c: Add Samsung S5K3M5 13MP camera sensor driver Samsung S5K3M5 (ISOCELL 3M5) is a 13MP image sensor, it produces Bayer GRBG (2x2) frames in RAW10 output format, the maximum supported output resolution is 4208x3120 at 30 frames per second. Tested-by: Neil Armstrong # on SM8550-QRD Signed-off-by: Vladimir Zapolskiy Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 8 + drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/s5k3m5.c | 1377 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1396 insertions(+) create mode 100644 drivers/media/i2c/s5k3m5.c diff --git a/MAINTAINERS b/MAINTAINERS index e451855c50bc..55e0e1963c95 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23263,6 +23263,14 @@ S: Supported F: Documentation/devicetree/bindings/media/samsung,s5c73m3.yaml F: drivers/media/i2c/s5c73m3/* +SAMSUNG S5K3M5 CAMERA DRIVER +M: Vladimir Zapolskiy +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/samsung,s5k3m5.yaml +F: drivers/media/i2c/s5k3m5.c + SAMSUNG S5K5BAF CAMERA DRIVER M: Sylwester Nawrocki M: Andrzej Hajda diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 89bc7c153058..4af63bc0b810 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -743,6 +743,16 @@ config VIDEO_S5C73M3 This is a V4L2 sensor driver for Samsung S5C73M3 8 Mpixel camera. +config VIDEO_S5K3M5 + tristate "Samsung S5K3M5 sensor support" + select V4L2_CCI_I2C + help + This is a V4L2 sensor driver for Samsung S5K3M5 13MP raw + camera sensor. + + To compile this driver as a module, choose M here: the + module will be called s5k3m5. + config VIDEO_S5K5BAF tristate "Samsung S5K5BAF sensor support" help diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index f86e2622c0d7..8400c3dfe626 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -125,6 +125,7 @@ obj-$(CONFIG_VIDEO_RDACM20) += rdacm20.o obj-$(CONFIG_VIDEO_RDACM21) += rdacm21.o obj-$(CONFIG_VIDEO_RJ54N1) += rj54n1cb0c.o obj-$(CONFIG_VIDEO_S5C73M3) += s5c73m3/ +obj-$(CONFIG_VIDEO_S5K3M5) += s5k3m5.o obj-$(CONFIG_VIDEO_S5K5BAF) += s5k5baf.o obj-$(CONFIG_VIDEO_S5K6A3) += s5k6a3.o obj-$(CONFIG_VIDEO_S5KJN1) += s5kjn1.o diff --git a/drivers/media/i2c/s5k3m5.c b/drivers/media/i2c/s5k3m5.c new file mode 100644 index 000000000000..c591b580d2e7 --- /dev/null +++ b/drivers/media/i2c/s5k3m5.c @@ -0,0 +1,1377 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Linaro Ltd + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define S5K3M5_LINK_FREQ_602P5MHZ (602500ULL * HZ_PER_KHZ) +#define S5K3M5_MCLK_FREQ_24MHZ (24 * HZ_PER_MHZ) +#define S5K3M5_DATA_LANES 4 + +/* Register map is similar to MIPI CCS compliant camera sensors */ +#define S5K3M5_REG_CHIP_ID CCI_REG16(0x0000) +#define S5K3M5_CHIP_ID 0x30d5 + +/* Both streaming and flipping settings are controlled at the same time */ +#define S5K3M5_REG_CTRL_MODE CCI_REG16(0x0100) +#define S5K3M5_MODE_STREAMING BIT(8) +#define S5K3M5_VFLIP BIT(1) +#define S5K3M5_HFLIP BIT(0) + +#define S5K3M5_REG_EXPOSURE CCI_REG16(0x0202) +#define S5K3M5_EXPOSURE_MIN 8 +#define S5K3M5_EXPOSURE_STEP 1 +#define S5K3M5_EXPOSURE_MARGIN 4 + +#define S5K3M5_REG_AGAIN CCI_REG16(0x0204) +#define S5K3M5_AGAIN_MIN 1 +#define S5K3M5_AGAIN_MAX 16 +#define S5K3M5_AGAIN_STEP 1 +#define S5K3M5_AGAIN_DEFAULT 1 +#define S5K3M5_AGAIN_SHIFT 5 + +#define S5K3M5_REG_VTS CCI_REG16(0x0340) +#define S5K3M5_VTS_MAX 0xffff + +#define S5K3M5_REG_HTS CCI_REG16(0x0342) +#define S5K3M5_REG_X_ADDR_START CCI_REG16(0x0344) +#define S5K3M5_REG_Y_ADDR_START CCI_REG16(0x0346) +#define S5K3M5_REG_X_ADDR_END CCI_REG16(0x0348) +#define S5K3M5_REG_Y_ADDR_END CCI_REG16(0x034a) +#define S5K3M5_REG_X_OUTPUT_SIZE CCI_REG16(0x034c) +#define S5K3M5_REG_Y_OUTPUT_SIZE CCI_REG16(0x034e) + +#define S5K3M5_REG_TEST_PATTERN CCI_REG16(0x0600) + +#define to_s5k3m5(_sd) container_of(_sd, struct s5k3m5, sd) + +static const s64 s5k3m5_link_freq_menu[] = { + S5K3M5_LINK_FREQ_602P5MHZ, +}; + +/* List of supported formats to cover horizontal and vertical flip controls */ +static const u32 s5k3m5_mbus_formats[] = { + MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, +}; + +struct s5k3m5_reg_list { + const struct cci_reg_sequence *regs; + unsigned int num_regs; +}; + +struct s5k3m5_mode { + u32 width; /* Frame width in pixels */ + u32 height; /* Frame height in pixels */ + u32 hts; /* Horizontal timing size */ + u32 vts; /* Default vertical timing size */ + u32 exposure; /* Default exposure value */ + + const struct s5k3m5_reg_list reg_list; /* Sensor register setting */ +}; + +static const char * const s5k3m5_test_pattern_menu[] = { + "Disabled", + "Solid colour", + "Colour bars", + "Fade to grey colour bars", + "PN9", +}; + +static const char * const s5k3m5_supply_names[] = { + "afvdd", /* Autofocus power */ + "vdda", /* Analog power */ + "vddd", /* Digital core power */ + "vddio", /* Digital I/O power */ +}; + +#define S5K3M5_NUM_SUPPLIES ARRAY_SIZE(s5k3m5_supply_names) + +struct s5k3m5 { + struct device *dev; + struct regmap *regmap; + struct clk *mclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[S5K3M5_NUM_SUPPLIES]; + + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + + const struct s5k3m5_mode *mode; +}; + +static const struct cci_reg_sequence burst_array_setting[] = { + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0549 }, + { CCI_REG16(0x6f12), 0x0448 }, + { CCI_REG16(0x6f12), 0x054a }, + { CCI_REG16(0x6f12), 0xc1f8 }, + { CCI_REG16(0x6f12), 0xc804 }, + { CCI_REG16(0x6f12), 0x101a }, + { CCI_REG16(0x6f12), 0xa1f8 }, + { CCI_REG16(0x6f12), 0xcc04 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x1bb9 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x4210 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x2e50 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x7000 }, + { CCI_REG16(0x6f12), 0x10b5 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x4ff9 }, + { CCI_REG16(0x6f12), 0x9949 }, + { CCI_REG16(0x6f12), 0x0120 }, + { CCI_REG16(0x6f12), 0x0880 }, + { CCI_REG16(0x6f12), 0x10bd }, + { CCI_REG16(0x6f12), 0x2de9 }, + { CCI_REG16(0x6f12), 0xf041 }, + { CCI_REG16(0x6f12), 0x974c }, + { CCI_REG16(0x6f12), 0x954f }, + { CCI_REG16(0x6f12), 0x0026 }, + { CCI_REG16(0x6f12), 0xb4f8 }, + { CCI_REG16(0x6f12), 0x6a52 }, + { CCI_REG16(0x6f12), 0x3888 }, + { CCI_REG16(0x6f12), 0x08b1 }, + { CCI_REG16(0x6f12), 0xa4f8 }, + { CCI_REG16(0x6f12), 0x6a62 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x43f9 }, + { CCI_REG16(0x6f12), 0x3e80 }, + { CCI_REG16(0x6f12), 0xa4f8 }, + { CCI_REG16(0x6f12), 0x6a52 }, + { CCI_REG16(0x6f12), 0xbde8 }, + { CCI_REG16(0x6f12), 0xf081 }, + { CCI_REG16(0x6f12), 0x2de9 }, + { CCI_REG16(0x6f12), 0xf041 }, + { CCI_REG16(0x6f12), 0x0746 }, + { CCI_REG16(0x6f12), 0x8c48 }, + { CCI_REG16(0x6f12), 0x0e46 }, + { CCI_REG16(0x6f12), 0x0022 }, + { CCI_REG16(0x6f12), 0x4068 }, + { CCI_REG16(0x6f12), 0x84b2 }, + { CCI_REG16(0x6f12), 0x050c }, + { CCI_REG16(0x6f12), 0x2146 }, + { CCI_REG16(0x6f12), 0x2846 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x36f9 }, + { CCI_REG16(0x6f12), 0x3146 }, + { CCI_REG16(0x6f12), 0x3846 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x37f9 }, + { CCI_REG16(0x6f12), 0x874f }, + { CCI_REG16(0x6f12), 0x4df2 }, + { CCI_REG16(0x6f12), 0x0c26 }, + { CCI_REG16(0x6f12), 0x4ff4 }, + { CCI_REG16(0x6f12), 0x8061 }, + { CCI_REG16(0x6f12), 0x3a78 }, + { CCI_REG16(0x6f12), 0x3046 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x29f9 }, + { CCI_REG16(0x6f12), 0x7878 }, + { CCI_REG16(0x6f12), 0xb8b3 }, + { CCI_REG16(0x6f12), 0x0022 }, + { CCI_REG16(0x6f12), 0x8021 }, + { CCI_REG16(0x6f12), 0x3046 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x22f9 }, + { CCI_REG16(0x6f12), 0x8048 }, + { CCI_REG16(0x6f12), 0x0088 }, + { CCI_REG16(0x6f12), 0x804b }, + { CCI_REG16(0x6f12), 0xa3f8 }, + { CCI_REG16(0x6f12), 0x5c02 }, + { CCI_REG16(0x6f12), 0x7e48 }, + { CCI_REG16(0x6f12), 0x001d }, + { CCI_REG16(0x6f12), 0x0088 }, + { CCI_REG16(0x6f12), 0xa3f8 }, + { CCI_REG16(0x6f12), 0x5e02 }, + { CCI_REG16(0x6f12), 0xb3f8 }, + { CCI_REG16(0x6f12), 0x5c02 }, + { CCI_REG16(0x6f12), 0xb3f8 }, + { CCI_REG16(0x6f12), 0x5e12 }, + { CCI_REG16(0x6f12), 0x4218 }, + { CCI_REG16(0x6f12), 0x02d0 }, + { CCI_REG16(0x6f12), 0x8002 }, + { CCI_REG16(0x6f12), 0xb0fb }, + { CCI_REG16(0x6f12), 0xf2f2 }, + { CCI_REG16(0x6f12), 0x91b2 }, + { CCI_REG16(0x6f12), 0x784a }, + { CCI_REG16(0x6f12), 0xa3f8 }, + { CCI_REG16(0x6f12), 0x6012 }, + { CCI_REG16(0x6f12), 0xb2f8 }, + { CCI_REG16(0x6f12), 0x1602 }, + { CCI_REG16(0x6f12), 0xb2f8 }, + { CCI_REG16(0x6f12), 0x1422 }, + { CCI_REG16(0x6f12), 0xa3f8 }, + { CCI_REG16(0x6f12), 0x9805 }, + { CCI_REG16(0x6f12), 0xa3f8 }, + { CCI_REG16(0x6f12), 0x9a25 }, + { CCI_REG16(0x6f12), 0x8018 }, + { CCI_REG16(0x6f12), 0x04d0 }, + { CCI_REG16(0x6f12), 0x9202 }, + { CCI_REG16(0x6f12), 0xb2fb }, + { CCI_REG16(0x6f12), 0xf0f0 }, + { CCI_REG16(0x6f12), 0xa3f8 }, + { CCI_REG16(0x6f12), 0x9c05 }, + { CCI_REG16(0x6f12), 0xb3f8 }, + { CCI_REG16(0x6f12), 0x9c05 }, + { CCI_REG16(0x6f12), 0x0a18 }, + { CCI_REG16(0x6f12), 0x01fb }, + { CCI_REG16(0x6f12), 0x1020 }, + { CCI_REG16(0x6f12), 0x40f3 }, + { CCI_REG16(0x6f12), 0x9510 }, + { CCI_REG16(0x6f12), 0x1028 }, + { CCI_REG16(0x6f12), 0x06dc }, + { CCI_REG16(0x6f12), 0x0028 }, + { CCI_REG16(0x6f12), 0x05da }, + { CCI_REG16(0x6f12), 0x0020 }, + { CCI_REG16(0x6f12), 0x03e0 }, + { CCI_REG16(0x6f12), 0xffe7 }, + { CCI_REG16(0x6f12), 0x0122 }, + { CCI_REG16(0x6f12), 0xc5e7 }, + { CCI_REG16(0x6f12), 0x1020 }, + { CCI_REG16(0x6f12), 0x6849 }, + { CCI_REG16(0x6f12), 0x0880 }, + { CCI_REG16(0x6f12), 0x2146 }, + { CCI_REG16(0x6f12), 0x2846 }, + { CCI_REG16(0x6f12), 0xbde8 }, + { CCI_REG16(0x6f12), 0xf041 }, + { CCI_REG16(0x6f12), 0x0122 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0xe2b8 }, + { CCI_REG16(0x6f12), 0xf0b5 }, + { CCI_REG16(0x6f12), 0x644c }, + { CCI_REG16(0x6f12), 0xdde9 }, + { CCI_REG16(0x6f12), 0x0565 }, + { CCI_REG16(0x6f12), 0x08b1 }, + { CCI_REG16(0x6f12), 0x2788 }, + { CCI_REG16(0x6f12), 0x0760 }, + { CCI_REG16(0x6f12), 0x09b1 }, + { CCI_REG16(0x6f12), 0x6088 }, + { CCI_REG16(0x6f12), 0x0860 }, + { CCI_REG16(0x6f12), 0x12b1 }, + { CCI_REG16(0x6f12), 0xa088 }, + { CCI_REG16(0x6f12), 0x401c }, + { CCI_REG16(0x6f12), 0x1060 }, + { CCI_REG16(0x6f12), 0x0bb1 }, + { CCI_REG16(0x6f12), 0xe088 }, + { CCI_REG16(0x6f12), 0x1860 }, + { CCI_REG16(0x6f12), 0x0eb1 }, + { CCI_REG16(0x6f12), 0xa07b }, + { CCI_REG16(0x6f12), 0x3060 }, + { CCI_REG16(0x6f12), 0x002d }, + { CCI_REG16(0x6f12), 0x01d0 }, + { CCI_REG16(0x6f12), 0xe07b }, + { CCI_REG16(0x6f12), 0x2860 }, + { CCI_REG16(0x6f12), 0xf0bd }, + { CCI_REG16(0x6f12), 0x70b5 }, + { CCI_REG16(0x6f12), 0x0646 }, + { CCI_REG16(0x6f12), 0x5048 }, + { CCI_REG16(0x6f12), 0x0022 }, + { CCI_REG16(0x6f12), 0x8068 }, + { CCI_REG16(0x6f12), 0x84b2 }, + { CCI_REG16(0x6f12), 0x050c }, + { CCI_REG16(0x6f12), 0x2146 }, + { CCI_REG16(0x6f12), 0x2846 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0xbef8 }, + { CCI_REG16(0x6f12), 0x3046 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0xc5f8 }, + { CCI_REG16(0x6f12), 0x5248 }, + { CCI_REG16(0x6f12), 0x0368 }, + { CCI_REG16(0x6f12), 0xb3f8 }, + { CCI_REG16(0x6f12), 0x7401 }, + { CCI_REG16(0x6f12), 0x010a }, + { CCI_REG16(0x6f12), 0x5048 }, + { CCI_REG16(0x6f12), 0x4268 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x5010 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x7511 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x5210 }, + { CCI_REG16(0x6f12), 0xb3f8 }, + { CCI_REG16(0x6f12), 0x7811 }, + { CCI_REG16(0x6f12), 0x090a }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x5810 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x7911 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x5a10 }, + { CCI_REG16(0x6f12), 0x33f8 }, + { CCI_REG16(0x6f12), 0xf01f }, + { CCI_REG16(0x6f12), 0x0068 }, + { CCI_REG16(0x6f12), 0x090a }, + { CCI_REG16(0x6f12), 0x00f8 }, + { CCI_REG16(0x6f12), 0xce1f }, + { CCI_REG16(0x6f12), 0x5978 }, + { CCI_REG16(0x6f12), 0x8170 }, + { CCI_REG16(0x6f12), 0x5988 }, + { CCI_REG16(0x6f12), 0x090a }, + { CCI_REG16(0x6f12), 0x0171 }, + { CCI_REG16(0x6f12), 0xd978 }, + { CCI_REG16(0x6f12), 0x8171 }, + { CCI_REG16(0x6f12), 0x988c }, + { CCI_REG16(0x6f12), 0x000a }, + { CCI_REG16(0x6f12), 0x9074 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x2500 }, + { CCI_REG16(0x6f12), 0x1075 }, + { CCI_REG16(0x6f12), 0xd88c }, + { CCI_REG16(0x6f12), 0x000a }, + { CCI_REG16(0x6f12), 0x9075 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x2700 }, + { CCI_REG16(0x6f12), 0x1076 }, + { CCI_REG16(0x6f12), 0xb3f8 }, + { CCI_REG16(0x6f12), 0xb000 }, + { CCI_REG16(0x6f12), 0x000a }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x7e00 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0xb100 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x8000 }, + { CCI_REG16(0x6f12), 0x2f48 }, + { CCI_REG16(0x6f12), 0x90f8 }, + { CCI_REG16(0x6f12), 0xb313 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x8210 }, + { CCI_REG16(0x6f12), 0x90f8 }, + { CCI_REG16(0x6f12), 0xb103 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x8400 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0xb400 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x8600 }, + { CCI_REG16(0x6f12), 0x0020 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x8800 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x6211 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x9610 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x0112 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0x9e10 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x0212 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0xa010 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0xa200 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0xa400 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x0512 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0xa610 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x0612 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0xa810 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x0712 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0xaa10 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0xac00 }, + { CCI_REG16(0x6f12), 0x5a20 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0xad00 }, + { CCI_REG16(0x6f12), 0x93f8 }, + { CCI_REG16(0x6f12), 0x0902 }, + { CCI_REG16(0x6f12), 0x82f8 }, + { CCI_REG16(0x6f12), 0xae00 }, + { CCI_REG16(0x6f12), 0x2146 }, + { CCI_REG16(0x6f12), 0x2846 }, + { CCI_REG16(0x6f12), 0xbde8 }, + { CCI_REG16(0x6f12), 0x7040 }, + { CCI_REG16(0x6f12), 0x0122 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x47b8 }, + { CCI_REG16(0x6f12), 0x10b5 }, + { CCI_REG16(0x6f12), 0x0f4c }, + { CCI_REG16(0x6f12), 0x0020 }, + { CCI_REG16(0x6f12), 0x2080 }, + { CCI_REG16(0x6f12), 0xaff2 }, + { CCI_REG16(0x6f12), 0x3321 }, + { CCI_REG16(0x6f12), 0x1748 }, + { CCI_REG16(0x6f12), 0x0161 }, + { CCI_REG16(0x6f12), 0xaff2 }, + { CCI_REG16(0x6f12), 0x2d21 }, + { CCI_REG16(0x6f12), 0x0022 }, + { CCI_REG16(0x6f12), 0x8161 }, + { CCI_REG16(0x6f12), 0xaff2 }, + { CCI_REG16(0x6f12), 0x1121 }, + { CCI_REG16(0x6f12), 0x1448 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x45f8 }, + { CCI_REG16(0x6f12), 0x0022 }, + { CCI_REG16(0x6f12), 0xaff2 }, + { CCI_REG16(0x6f12), 0x2911 }, + { CCI_REG16(0x6f12), 0x6060 }, + { CCI_REG16(0x6f12), 0x1148 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x3ef8 }, + { CCI_REG16(0x6f12), 0x0022 }, + { CCI_REG16(0x6f12), 0xaff2 }, + { CCI_REG16(0x6f12), 0x6b11 }, + { CCI_REG16(0x6f12), 0xa060 }, + { CCI_REG16(0x6f12), 0x0f48 }, + { CCI_REG16(0x6f12), 0x00f0 }, + { CCI_REG16(0x6f12), 0x37f8 }, + { CCI_REG16(0x6f12), 0xe060 }, + { CCI_REG16(0x6f12), 0x10bd }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x4200 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x2e50 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x41d0 }, + { CCI_REG16(0x6f12), 0x4000 }, + { CCI_REG16(0x6f12), 0x9404 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x38e0 }, + { CCI_REG16(0x6f12), 0x4000 }, + { CCI_REG16(0x6f12), 0xd000 }, + { CCI_REG16(0x6f12), 0x4000 }, + { CCI_REG16(0x6f12), 0xa410 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x2c66 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x0890 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x3620 }, + { CCI_REG16(0x6f12), 0x2000 }, + { CCI_REG16(0x6f12), 0x0840 }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x6f12), 0x020d }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x67cd }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x3ae1 }, + { CCI_REG16(0x6f12), 0x45f6 }, + { CCI_REG16(0x6f12), 0x250c }, + { CCI_REG16(0x6f12), 0xc0f2 }, + { CCI_REG16(0x6f12), 0x000c }, + { CCI_REG16(0x6f12), 0x6047 }, + { CCI_REG16(0x6f12), 0x45f6 }, + { CCI_REG16(0x6f12), 0xf31c }, + { CCI_REG16(0x6f12), 0xc0f2 }, + { CCI_REG16(0x6f12), 0x000c }, + { CCI_REG16(0x6f12), 0x6047 }, + { CCI_REG16(0x6f12), 0x4af2 }, + { CCI_REG16(0x6f12), 0xd74c }, + { CCI_REG16(0x6f12), 0xc0f2 }, + { CCI_REG16(0x6f12), 0x000c }, + { CCI_REG16(0x6f12), 0x6047 }, + { CCI_REG16(0x6f12), 0x40f2 }, + { CCI_REG16(0x6f12), 0x0d2c }, + { CCI_REG16(0x6f12), 0xc0f2 }, + { CCI_REG16(0x6f12), 0x010c }, + { CCI_REG16(0x6f12), 0x6047 }, + { CCI_REG16(0x6f12), 0x46f2 }, + { CCI_REG16(0x6f12), 0xcd7c }, + { CCI_REG16(0x6f12), 0xc0f2 }, + { CCI_REG16(0x6f12), 0x000c }, + { CCI_REG16(0x6f12), 0x6047 }, + { CCI_REG16(0x6f12), 0x4af6 }, + { CCI_REG16(0x6f12), 0xe75c }, + { CCI_REG16(0x6f12), 0xc0f2 }, + { CCI_REG16(0x6f12), 0x000c }, + { CCI_REG16(0x6f12), 0x6047 }, + { CCI_REG16(0x6f12), 0x30d5 }, + { CCI_REG16(0x6f12), 0x00fc }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x000e }, +}; + +static const struct cci_reg_sequence init_array_setting[] = { + { CCI_REG16(0x602a), 0x41d0 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x1662 }, + { CCI_REG16(0x6f12), 0x1e00 }, + { CCI_REG16(0x602a), 0x1c9a }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x0ff2 }, + { CCI_REG16(0x6f12), 0x0020 }, + { CCI_REG16(0x602a), 0x0ef6 }, + { CCI_REG16(0x6f12), 0x0100 }, + { CCI_REG16(0x602a), 0x23b2 }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x602a), 0x0fe4 }, + { CCI_REG16(0x6f12), 0x0107 }, + { CCI_REG16(0x6f12), 0x07d0 }, + { CCI_REG16(0x602a), 0x12f8 }, + { CCI_REG16(0x6f12), 0x3d09 }, + { CCI_REG16(0x602a), 0x0e18 }, + { CCI_REG16(0x6f12), 0x0040 }, + { CCI_REG16(0x602a), 0x1066 }, + { CCI_REG16(0x6f12), 0x000c }, + { CCI_REG16(0x602a), 0x13de }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x602a), 0x12f2 }, + { CCI_REG16(0x6f12), 0x0f0f }, + { CCI_REG16(0x602a), 0x13dc }, + { CCI_REG16(0x6f12), 0x806f }, + { CCI_REG16(0xf46e), 0x00c3 }, + { CCI_REG16(0xf46c), 0xbfa0 }, + { CCI_REG16(0xf44a), 0x0007 }, + { CCI_REG16(0xf456), 0x000a }, + { CCI_REG16(0x6028), 0x2000 }, + { CCI_REG16(0x602a), 0x12f6 }, + { CCI_REG16(0x6f12), 0x7008 }, + { CCI_REG16(0x0bc6), 0x0000 }, + { CCI_REG16(0x0b36), 0x0001 }, +}; + +static const struct cci_reg_sequence s5k3m5_4208x3120_30fps_mode[] = { + { CCI_REG16(0x6214), 0x7971 }, + { CCI_REG16(0x6218), 0x7150 }, + { S5K3M5_REG_X_ADDR_START, 0x0000 }, + { S5K3M5_REG_Y_ADDR_START, 0x0008 }, + { S5K3M5_REG_X_ADDR_END, 0x1077 }, + { S5K3M5_REG_Y_ADDR_END, 0x0c37 }, + { S5K3M5_REG_X_OUTPUT_SIZE, 0x1070 }, + { S5K3M5_REG_Y_OUTPUT_SIZE, 0x0c30 }, + { S5K3M5_REG_VTS, 0x0cf2 }, + { S5K3M5_REG_HTS, 0x12f0 }, + + { CCI_REG16(0x0900), 0x0011 }, + { CCI_REG16(0x0380), 0x0001 }, + { CCI_REG16(0x0382), 0x0001 }, + { CCI_REG16(0x0384), 0x0001 }, + { CCI_REG16(0x0386), 0x0001 }, + { CCI_REG16(0x0402), 0x1010 }, + { CCI_REG16(0x0404), 0x1000 }, + { CCI_REG16(0x0350), 0x0008 }, + { CCI_REG16(0x0352), 0x0000 }, + + /* Clock settings */ + { CCI_REG16(0x0136), 0x1800 }, + { CCI_REG16(0x013e), 0x0000 }, + { CCI_REG16(0x0300), 0x0008 }, + { CCI_REG16(0x0302), 0x0001 }, + { CCI_REG16(0x0304), 0x0006 }, + { CCI_REG16(0x0306), 0x00f1 }, + { CCI_REG16(0x0308), 0x0008 }, + { CCI_REG16(0x030a), 0x0001 }, + { CCI_REG16(0x030c), 0x0000 }, + { CCI_REG16(0x030e), 0x0003 }, + { CCI_REG16(0x0310), 0x005a }, + { CCI_REG16(0x0312), 0x0000 }, + + { CCI_REG16(0x0b06), 0x0101 }, + { CCI_REG16(0x6028), 0x2000 }, + { CCI_REG16(0x602a), 0x1ff6 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x021e), 0x0000 }, + { CCI_REG16(0x0202), 0x0100 }, + { CCI_REG16(0x0204), 0x0020 }, + { CCI_REG16(0x0d00), 0x0100 }, + { CCI_REG16(0x0d02), 0x0001 }, + { CCI_REG16(0x0114), 0x0301 }, + { CCI_REG16(0x0d06), 0x0208 }, + { CCI_REG16(0x0d08), 0x0300 }, + { CCI_REG16(0x6028), 0x2000 }, + { CCI_REG16(0x602a), 0x0f10 }, + { CCI_REG16(0x6f12), 0x0003 }, + { CCI_REG16(0x602a), 0x0f12 }, + { CCI_REG16(0x6f12), 0x0200 }, + { CCI_REG16(0x602a), 0x2bc0 }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x0b30), 0x0000 }, + { CCI_REG16(0x0b32), 0x0000 }, + { CCI_REG16(0x0b34), 0x0001 }, + { CCI_REG16(0x0804), 0x0200 }, + { CCI_REG16(0x0810), 0x0020 }, +}; + +static const struct cci_reg_sequence s5k3m5_2104x1184_60fps_mode[] = { + { CCI_REG16(0x6214), 0x7971 }, + { CCI_REG16(0x6218), 0x7150 }, + { S5K3M5_REG_X_ADDR_START, 0x0000 }, + { S5K3M5_REG_Y_ADDR_START, 0x0180 }, + { S5K3M5_REG_X_ADDR_END, 0x1077 }, + { S5K3M5_REG_Y_ADDR_END, 0x0abf }, + { S5K3M5_REG_X_OUTPUT_SIZE, 0x0838 }, + { S5K3M5_REG_Y_OUTPUT_SIZE, 0x04a0 }, + { S5K3M5_REG_VTS, 0x0658 }, + { S5K3M5_REG_HTS, 0x1340 }, + + { CCI_REG16(0x0900), 0x0112 }, + { CCI_REG16(0x0380), 0x0001 }, + { CCI_REG16(0x0382), 0x0001 }, + { CCI_REG16(0x0384), 0x0001 }, + { CCI_REG16(0x0386), 0x0003 }, + { CCI_REG16(0x0402), 0x1010 }, + { CCI_REG16(0x0404), 0x2000 }, + { CCI_REG16(0x0350), 0x0004 }, + { CCI_REG16(0x0352), 0x0000 }, + + /* Clock settings */ + { CCI_REG16(0x0136), 0x1800 }, + { CCI_REG16(0x013e), 0x0000 }, + { CCI_REG16(0x0300), 0x0008 }, + { CCI_REG16(0x0302), 0x0001 }, + { CCI_REG16(0x0304), 0x0006 }, + { CCI_REG16(0x0306), 0x00f1 }, + { CCI_REG16(0x0308), 0x0008 }, + { CCI_REG16(0x030a), 0x0001 }, + { CCI_REG16(0x030c), 0x0000 }, + { CCI_REG16(0x030e), 0x0003 }, + { CCI_REG16(0x0310), 0x0063 }, + { CCI_REG16(0x0312), 0x0001 }, + + { CCI_REG16(0x0b06), 0x0101 }, + { CCI_REG16(0x6028), 0x2000 }, + { CCI_REG16(0x602a), 0x1ff6 }, + { CCI_REG16(0x6f12), 0x0000 }, + { CCI_REG16(0x021e), 0x0000 }, + { CCI_REG16(0x0202), 0x0100 }, + { CCI_REG16(0x0204), 0x0020 }, + { CCI_REG16(0x0d00), 0x0101 }, + { CCI_REG16(0x0d02), 0x0101 }, + { CCI_REG16(0x0114), 0x0301 }, + { CCI_REG16(0x0d06), 0x0208 }, + { CCI_REG16(0x0d08), 0x0300 }, + { CCI_REG16(0x6028), 0x2000 }, + { CCI_REG16(0x602a), 0x0f10 }, + { CCI_REG16(0x6f12), 0x0003 }, + { CCI_REG16(0x602a), 0x0f12 }, + { CCI_REG16(0x6f12), 0x0200 }, + { CCI_REG16(0x602a), 0x2bc0 }, + { CCI_REG16(0x6f12), 0x0001 }, + { CCI_REG16(0x0b30), 0x0000 }, + { CCI_REG16(0x0b32), 0x0000 }, + { CCI_REG16(0x0b34), 0x0001 }, + { CCI_REG16(0x0804), 0x0200 }, + { CCI_REG16(0x0810), 0x0020 }, +}; + +static const struct s5k3m5_mode s5k3m5_supported_modes[] = { + { + .width = 4208, + .height = 3120, + .hts = 4848, + .vts = 3314, + .exposure = 256, + .reg_list = { + .regs = s5k3m5_4208x3120_30fps_mode, + .num_regs = ARRAY_SIZE(s5k3m5_4208x3120_30fps_mode), + }, + }, + { + .width = 2104, + .height = 1184, + .hts = 4928, + .vts = 1624, + .exposure = 256, + .reg_list = { + .regs = s5k3m5_2104x1184_60fps_mode, + .num_regs = ARRAY_SIZE(s5k3m5_2104x1184_60fps_mode), + }, + }, +}; + +static int s5k3m5_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s5k3m5 *s5k3m5 = container_of(ctrl->handler, struct s5k3m5, + ctrl_handler); + const struct s5k3m5_mode *mode = s5k3m5->mode; + s64 exposure_max; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + /* The orientation settings are applied along with streaming */ + return 0; + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + exposure_max = mode->height + ctrl->val - S5K3M5_EXPOSURE_MARGIN; + __v4l2_ctrl_modify_range(s5k3m5->exposure, + s5k3m5->exposure->minimum, + exposure_max, + s5k3m5->exposure->step, + s5k3m5->exposure->default_value); + break; + } + + /* V4L2 controls are applied, when sensor is powered up for streaming */ + if (!pm_runtime_get_if_active(s5k3m5->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = cci_write(s5k3m5->regmap, S5K3M5_REG_AGAIN, + ctrl->val << S5K3M5_AGAIN_SHIFT, NULL); + break; + case V4L2_CID_EXPOSURE: + ret = cci_write(s5k3m5->regmap, S5K3M5_REG_EXPOSURE, + ctrl->val, NULL); + break; + case V4L2_CID_VBLANK: + ret = cci_write(s5k3m5->regmap, S5K3M5_REG_VTS, + ctrl->val + mode->height, NULL); + break; + case V4L2_CID_TEST_PATTERN: + ret = cci_write(s5k3m5->regmap, S5K3M5_REG_TEST_PATTERN, + ctrl->val, NULL); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(s5k3m5->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops s5k3m5_ctrl_ops = { + .s_ctrl = s5k3m5_set_ctrl, +}; + +static inline u64 s5k3m5_freq_to_pixel_rate(const u64 freq) +{ + return div_u64(freq * 2 * S5K3M5_DATA_LANES, 10); +} + +static int s5k3m5_init_controls(struct s5k3m5 *s5k3m5) +{ + struct v4l2_ctrl_handler *ctrl_hdlr = &s5k3m5->ctrl_handler; + const struct s5k3m5_mode *mode = s5k3m5->mode; + s64 pixel_rate, hblank, vblank, exposure_max; + struct v4l2_fwnode_device_properties props; + int ret; + + v4l2_ctrl_handler_init(ctrl_hdlr, 9); + + s5k3m5->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &s5k3m5_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(s5k3m5_link_freq_menu) - 1, + 0, s5k3m5_link_freq_menu); + if (s5k3m5->link_freq) + s5k3m5->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate = s5k3m5_freq_to_pixel_rate(s5k3m5_link_freq_menu[0]); + s5k3m5->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &s5k3m5_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 0, pixel_rate, 1, pixel_rate); + + hblank = mode->hts - mode->width; + s5k3m5->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &s5k3m5_ctrl_ops, + V4L2_CID_HBLANK, hblank, + hblank, 1, hblank); + if (s5k3m5->hblank) + s5k3m5->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank = mode->vts - mode->height; + s5k3m5->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &s5k3m5_ctrl_ops, + V4L2_CID_VBLANK, vblank, + S5K3M5_VTS_MAX - mode->height, 1, + vblank); + + v4l2_ctrl_new_std(ctrl_hdlr, &s5k3m5_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + S5K3M5_AGAIN_MIN, S5K3M5_AGAIN_MAX, + S5K3M5_AGAIN_STEP, S5K3M5_AGAIN_DEFAULT); + + exposure_max = mode->vts - S5K3M5_EXPOSURE_MARGIN; + s5k3m5->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &s5k3m5_ctrl_ops, + V4L2_CID_EXPOSURE, + S5K3M5_EXPOSURE_MIN, + exposure_max, + S5K3M5_EXPOSURE_STEP, + mode->exposure); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &s5k3m5_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(s5k3m5_test_pattern_menu) - 1, + 0, 0, s5k3m5_test_pattern_menu); + + s5k3m5->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &s5k3m5_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (s5k3m5->hflip) + s5k3m5->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + s5k3m5->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &s5k3m5_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (s5k3m5->vflip) + s5k3m5->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + ret = v4l2_fwnode_device_parse(s5k3m5->dev, &props); + if (ret) + goto error_free_hdlr; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &s5k3m5_ctrl_ops, + &props); + if (ret) + goto error_free_hdlr; + + s5k3m5->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error_free_hdlr: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int s5k3m5_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct s5k3m5 *s5k3m5 = to_s5k3m5(sd); + const struct s5k3m5_reg_list *reg_list = &s5k3m5->mode->reg_list; + int ret; + + ret = pm_runtime_resume_and_get(s5k3m5->dev); + if (ret) + return ret; + + /* Page pointer */ + cci_write(s5k3m5->regmap, CCI_REG16(0x6028), 0x4000, &ret); + + /* Set version */ + cci_write(s5k3m5->regmap, CCI_REG16(0x0000), 0x0006, &ret); + cci_write(s5k3m5->regmap, CCI_REG16(0x0000), S5K3M5_CHIP_ID, &ret); + cci_write(s5k3m5->regmap, CCI_REG16(0x6214), 0x7971, &ret); + cci_write(s5k3m5->regmap, CCI_REG16(0x6218), 0x7150, &ret); + if (ret) + goto error; + + usleep_range(4 * USEC_PER_MSEC, 5 * USEC_PER_MSEC); + + cci_write(s5k3m5->regmap, CCI_REG16(0x0a02), 0x7800, &ret); + cci_write(s5k3m5->regmap, CCI_REG16(0x6028), 0x2000, &ret); + cci_write(s5k3m5->regmap, CCI_REG16(0x602a), 0x3eac, &ret); + + /* Sensor init settings */ + cci_multi_reg_write(s5k3m5->regmap, burst_array_setting, + ARRAY_SIZE(burst_array_setting), &ret); + cci_multi_reg_write(s5k3m5->regmap, init_array_setting, + ARRAY_SIZE(init_array_setting), &ret); + cci_multi_reg_write(s5k3m5->regmap, reg_list->regs, + reg_list->num_regs, &ret); + if (ret) + goto error; + + ret = __v4l2_ctrl_handler_setup(s5k3m5->sd.ctrl_handler); + + cci_write(s5k3m5->regmap, S5K3M5_REG_CTRL_MODE, + S5K3M5_MODE_STREAMING | + (s5k3m5->vflip->val ? S5K3M5_VFLIP : 0) | + (s5k3m5->hflip->val ? S5K3M5_HFLIP : 0), &ret); + if (ret) + goto error; + + return 0; + +error: + dev_err(s5k3m5->dev, "failed to start streaming: %d\n", ret); + pm_runtime_put_autosuspend(s5k3m5->dev); + + return ret; +} + +static int s5k3m5_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct s5k3m5 *s5k3m5 = to_s5k3m5(sd); + int ret; + + ret = cci_write(s5k3m5->regmap, S5K3M5_REG_CTRL_MODE, 0, NULL); + if (ret) + dev_err(s5k3m5->dev, "failed to stop streaming: %d\n", ret); + + pm_runtime_put_autosuspend(s5k3m5->dev); + + return ret; +} + +static u32 s5k3m5_get_format_code(struct s5k3m5 *s5k3m5) +{ + unsigned int i; + + i = (s5k3m5->vflip->val ? 2 : 0) | (s5k3m5->hflip->val ? 1 : 0); + + return s5k3m5_mbus_formats[i]; +} + +static void s5k3m5_update_pad_format(struct s5k3m5 *s5k3m5, + const struct s5k3m5_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->code = s5k3m5_get_format_code(s5k3m5); + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; +} + +static int s5k3m5_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct s5k3m5 *s5k3m5 = to_s5k3m5(sd); + s64 hblank, vblank, exposure_max; + const struct s5k3m5_mode *mode; + + mode = v4l2_find_nearest_size(s5k3m5_supported_modes, + ARRAY_SIZE(s5k3m5_supported_modes), + width, height, + fmt->format.width, fmt->format.height); + + s5k3m5_update_pad_format(s5k3m5, mode, &fmt->format); + + /* Format code could be updated with respect to flip controls */ + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY || s5k3m5->mode == mode) + goto set_format; + + /* Update limits and set FPS and exposure to default values */ + hblank = mode->hts - mode->width; + __v4l2_ctrl_modify_range(s5k3m5->hblank, hblank, hblank, 1, hblank); + + vblank = mode->vts - mode->height; + __v4l2_ctrl_modify_range(s5k3m5->vblank, vblank, + S5K3M5_VTS_MAX - mode->height, 1, vblank); + __v4l2_ctrl_s_ctrl(s5k3m5->vblank, vblank); + + exposure_max = mode->vts - S5K3M5_EXPOSURE_MARGIN; + __v4l2_ctrl_modify_range(s5k3m5->exposure, S5K3M5_EXPOSURE_MIN, + exposure_max, S5K3M5_EXPOSURE_STEP, + mode->exposure); + __v4l2_ctrl_s_ctrl(s5k3m5->exposure, mode->exposure); + + if (s5k3m5->sd.ctrl_handler->error) + return s5k3m5->sd.ctrl_handler->error; + + s5k3m5->mode = mode; + +set_format: + *v4l2_subdev_state_get_format(state, 0) = fmt->format; + + return 0; +} + +static int s5k3m5_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct s5k3m5 *s5k3m5 = to_s5k3m5(sd); + + /* Media bus code index is constant, but code formats are not */ + if (code->index > 0) + return -EINVAL; + + code->code = s5k3m5_get_format_code(s5k3m5); + + return 0; +} + +static int s5k3m5_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct s5k3m5 *s5k3m5 = to_s5k3m5(sd); + + if (fse->index >= ARRAY_SIZE(s5k3m5_supported_modes)) + return -EINVAL; + + if (fse->code != s5k3m5_get_format_code(s5k3m5)) + return -EINVAL; + + fse->min_width = s5k3m5_supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = s5k3m5_supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int s5k3m5_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct s5k3m5 *s5k3m5 = to_s5k3m5(sd); + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = s5k3m5->mode->width; + sel->r.height = s5k3m5->mode->width; + return 0; + default: + return -EINVAL; + } + + return 0; +} + +static int s5k3m5_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct s5k3m5 *s5k3m5 = to_s5k3m5(sd); + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = 0, + .format = { + /* Media bus code depends on current flip controls */ + .width = s5k3m5->mode->width, + .height = s5k3m5->mode->height, + }, + }; + + s5k3m5_set_pad_format(sd, state, &fmt); + + return 0; +} + +static const struct v4l2_subdev_video_ops s5k3m5_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops s5k3m5_pad_ops = { + .set_fmt = s5k3m5_set_pad_format, + .get_fmt = v4l2_subdev_get_fmt, + .get_selection = s5k3m5_get_selection, + .enum_mbus_code = s5k3m5_enum_mbus_code, + .enum_frame_size = s5k3m5_enum_frame_size, + .enable_streams = s5k3m5_enable_streams, + .disable_streams = s5k3m5_disable_streams, +}; + +static const struct v4l2_subdev_ops s5k3m5_subdev_ops = { + .video = &s5k3m5_video_ops, + .pad = &s5k3m5_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops s5k3m5_internal_ops = { + .init_state = s5k3m5_init_state, +}; + +static const struct media_entity_operations s5k3m5_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int s5k3m5_identify_sensor(struct s5k3m5 *s5k3m5) +{ + u64 val; + int ret; + + ret = cci_read(s5k3m5->regmap, S5K3M5_REG_CHIP_ID, &val, NULL); + if (ret) { + dev_err(s5k3m5->dev, "failed to read chip id: %d\n", ret); + return ret; + } + + if (val != S5K3M5_CHIP_ID) { + dev_err(s5k3m5->dev, "chip id mismatch: %x!=%llx\n", + S5K3M5_CHIP_ID, val); + return -ENODEV; + } + + return 0; +} + +static int s5k3m5_check_hwcfg(struct s5k3m5 *s5k3m5) +{ + struct fwnode_handle *fwnode = dev_fwnode(s5k3m5->dev), *ep; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus = { + .mipi_csi2 = { + .num_data_lanes = S5K3M5_DATA_LANES, + }, + }, + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + unsigned long freq_bitmap; + int ret; + + if (!fwnode) + return -ENODEV; + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != S5K3M5_DATA_LANES) { + dev_err(s5k3m5->dev, "Invalid number of data lanes: %u\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto endpoint_free; + } + + ret = v4l2_link_freq_to_bitmap(s5k3m5->dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + s5k3m5_link_freq_menu, + ARRAY_SIZE(s5k3m5_link_freq_menu), + &freq_bitmap); + +endpoint_free: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int s5k3m5_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct s5k3m5 *s5k3m5 = to_s5k3m5(sd); + int ret; + + ret = regulator_bulk_enable(S5K3M5_NUM_SUPPLIES, s5k3m5->supplies); + if (ret) + return ret; + + ret = clk_prepare_enable(s5k3m5->mclk); + if (ret) + goto disable_regulators; + + gpiod_set_value_cansleep(s5k3m5->reset_gpio, 0); + usleep_range(10 * USEC_PER_MSEC, 15 * USEC_PER_MSEC); + + return 0; + +disable_regulators: + regulator_bulk_disable(S5K3M5_NUM_SUPPLIES, s5k3m5->supplies); + + return ret; +} + +static int s5k3m5_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct s5k3m5 *s5k3m5 = to_s5k3m5(sd); + + gpiod_set_value_cansleep(s5k3m5->reset_gpio, 1); + + clk_disable_unprepare(s5k3m5->mclk); + + regulator_bulk_disable(S5K3M5_NUM_SUPPLIES, s5k3m5->supplies); + + return 0; +} + +static int s5k3m5_probe(struct i2c_client *client) +{ + struct s5k3m5 *s5k3m5; + unsigned long freq; + unsigned int i; + int ret; + + s5k3m5 = devm_kzalloc(&client->dev, sizeof(*s5k3m5), GFP_KERNEL); + if (!s5k3m5) + return -ENOMEM; + + s5k3m5->dev = &client->dev; + v4l2_i2c_subdev_init(&s5k3m5->sd, client, &s5k3m5_subdev_ops); + + s5k3m5->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(s5k3m5->regmap)) + return dev_err_probe(s5k3m5->dev, PTR_ERR(s5k3m5->regmap), + "failed to init CCI\n"); + + s5k3m5->mclk = devm_v4l2_sensor_clk_get(s5k3m5->dev, NULL); + if (IS_ERR(s5k3m5->mclk)) + return dev_err_probe(s5k3m5->dev, PTR_ERR(s5k3m5->mclk), + "failed to get MCLK clock\n"); + + freq = clk_get_rate(s5k3m5->mclk); + if (freq != S5K3M5_MCLK_FREQ_24MHZ) + return dev_err_probe(s5k3m5->dev, -EINVAL, + "MCLK clock frequency %lu is not supported\n", + freq); + + ret = s5k3m5_check_hwcfg(s5k3m5); + if (ret) + return dev_err_probe(s5k3m5->dev, ret, + "failed to check HW configuration\n"); + + s5k3m5->reset_gpio = devm_gpiod_get_optional(s5k3m5->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(s5k3m5->reset_gpio)) + return dev_err_probe(s5k3m5->dev, PTR_ERR(s5k3m5->reset_gpio), + "cannot get reset GPIO\n"); + + for (i = 0; i < S5K3M5_NUM_SUPPLIES; i++) + s5k3m5->supplies[i].supply = s5k3m5_supply_names[i]; + + ret = devm_regulator_bulk_get(s5k3m5->dev, S5K3M5_NUM_SUPPLIES, + s5k3m5->supplies); + if (ret) + return dev_err_probe(s5k3m5->dev, ret, + "failed to get supply regulators\n"); + + /* The sensor must be powered on to read the CHIP_ID register */ + ret = s5k3m5_power_on(s5k3m5->dev); + if (ret) + return ret; + + ret = s5k3m5_identify_sensor(s5k3m5); + if (ret) { + dev_err_probe(s5k3m5->dev, ret, "failed to find sensor\n"); + goto power_off; + } + + s5k3m5->mode = &s5k3m5_supported_modes[0]; + ret = s5k3m5_init_controls(s5k3m5); + if (ret) { + dev_err_probe(s5k3m5->dev, ret, "failed to init controls\n"); + goto power_off; + } + + s5k3m5->sd.state_lock = s5k3m5->ctrl_handler.lock; + s5k3m5->sd.internal_ops = &s5k3m5_internal_ops; + s5k3m5->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + s5k3m5->sd.entity.ops = &s5k3m5_subdev_entity_ops; + s5k3m5->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + s5k3m5->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&s5k3m5->sd.entity, 1, &s5k3m5->pad); + if (ret) { + dev_err_probe(s5k3m5->dev, ret, + "failed to init media entity pads\n"); + goto v4l2_ctrl_handler_free; + } + + ret = v4l2_subdev_init_finalize(&s5k3m5->sd); + if (ret < 0) { + dev_err_probe(s5k3m5->dev, ret, + "failed to init media entity pads\n"); + goto media_entity_cleanup; + } + + pm_runtime_set_active(s5k3m5->dev); + pm_runtime_enable(s5k3m5->dev); + + ret = v4l2_async_register_subdev_sensor(&s5k3m5->sd); + if (ret < 0) { + dev_err_probe(s5k3m5->dev, ret, + "failed to register V4L2 subdev\n"); + goto subdev_cleanup; + } + + pm_runtime_set_autosuspend_delay(s5k3m5->dev, 1000); + pm_runtime_use_autosuspend(s5k3m5->dev); + pm_runtime_idle(s5k3m5->dev); + + return 0; + +subdev_cleanup: + v4l2_subdev_cleanup(&s5k3m5->sd); + pm_runtime_disable(s5k3m5->dev); + pm_runtime_set_suspended(s5k3m5->dev); + +media_entity_cleanup: + media_entity_cleanup(&s5k3m5->sd.entity); + +v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(s5k3m5->sd.ctrl_handler); + +power_off: + s5k3m5_power_off(s5k3m5->dev); + + return ret; +} + +static void s5k3m5_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct s5k3m5 *s5k3m5 = to_s5k3m5(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(s5k3m5->dev); + + if (!pm_runtime_status_suspended(s5k3m5->dev)) { + s5k3m5_power_off(s5k3m5->dev); + pm_runtime_set_suspended(s5k3m5->dev); + } +} + +static const struct dev_pm_ops s5k3m5_pm_ops = { + SET_RUNTIME_PM_OPS(s5k3m5_power_off, s5k3m5_power_on, NULL) +}; + +static const struct of_device_id s5k3m5_of_match[] = { + { .compatible = "samsung,s5k3m5" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s5k3m5_of_match); + +static struct i2c_driver s5k3m5_i2c_driver = { + .driver = { + .name = "s5k3m5", + .pm = &s5k3m5_pm_ops, + .of_match_table = s5k3m5_of_match, + }, + .probe = s5k3m5_probe, + .remove = s5k3m5_remove, +}; + +module_i2c_driver(s5k3m5_i2c_driver); + +MODULE_AUTHOR("Vladimir Zapolskiy "); +MODULE_DESCRIPTION("Samsung S5K3M5 image sensor driver"); +MODULE_LICENSE("GPL"); From 555db89381c008c9b2e9b33bc0087961da575bfd Mon Sep 17 00:00:00 2001 From: Elgin Perumbilly Date: Mon, 5 Jan 2026 15:04:13 +0530 Subject: [PATCH 055/307] dt-bindings: media: i2c: Add os05b10 sensor Add bindings for Omnivision OS05B10 sensor. Add MAINTAINERS entry for Omnivision OS05B10 binding documentation Signed-off-by: Elgin Perumbilly Signed-off-by: Himanshu Bhavani Reviewed-by: Vladimir Zapolskiy Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../bindings/media/i2c/ovti,os05b10.yaml | 103 ++++++++++++++++++ MAINTAINERS | 7 ++ 2 files changed, 110 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,os05b10.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,os05b10.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,os05b10.yaml new file mode 100644 index 000000000000..b76771d81851 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,os05b10.yaml @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ovti,os05b10.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: OmniVision OS05B10 Image Sensor + +maintainers: + - Elgin Perumbilly + +description: + The OmniVision OS05B10 is a 5MP (2592x1944) color CMOS image sensor controlled + through an I2C-compatible SCCB bus. it outputs RAW10/RAW12 format and uses a + 1/2.78" optical format. + +properties: + compatible: + const: ovti,os05b10 + + reg: + maxItems: 1 + + clocks: + items: + - description: XCLK clock + + avdd-supply: + description: Analog Domain Power Supply (2.8v) + + dovdd-supply: + description: I/O Domain Power Supply (1.8v) + + dvdd-supply: + description: Digital Domain Power Supply (1.2v) + + reset-gpios: + maxItems: 1 + description: Reset Pin GPIO Control (active low) + + port: + description: MIPI CSI-2 transmitter port + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + oneOf: + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + - items: + - const: 1 + - const: 2 + required: + - data-lanes + - link-frequencies + +required: + - compatible + - reg + - clocks + - avdd-supply + - dovdd-supply + - dvdd-supply + - port + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera-sensor@36 { + compatible = "ovti,os05b10"; + reg = <0x36>; + clocks = <&os05b10_clk>; + reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; + + avdd-supply = <&os05b10_avdd_2v8>; + dvdd-supply = <&os05b10_dvdd_1v2>; + dovdd-supply = <&os05b10_dovdd_1v8>; + + port { + cam_out: endpoint { + remote-endpoint = <&mipi_in_cam>; + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <600000000>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 55e0e1963c95..34433602e972 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19232,6 +19232,13 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/ovti,og0ve1b.yaml F: drivers/media/i2c/og0ve1b.c +OMNIVISION OS05B10 SENSOR DRIVER +M: Himanshu Bhavani +M: Elgin Perumbilly +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/ovti,os05b10.yaml + OMNIVISION OV01A10 SENSOR DRIVER M: Bingbu Cao L: linux-media@vger.kernel.org From 3aa9296a23ec41a8424e9a2346eea59fb6cb7d8c Mon Sep 17 00:00:00 2001 From: Himanshu Bhavani Date: Mon, 5 Jan 2026 15:04:14 +0530 Subject: [PATCH 056/307] media: i2c: add os05b10 image sensor driver Add a v4l2 subdevice driver for the Omnivision OS05B10 sensor. The Omnivision OS05B10 image sensor with an active array size of 2592 x 1944. The following features are supported: - Manual exposure an gain control support - vblank/hblank control support - Supported resolution: 2592 x 1944 @ 60fps (SBGGR10) Co-developed-by: Elgin Perumbilly Signed-off-by: Elgin Perumbilly Signed-off-by: Himanshu Bhavani Reviewed-by: Vladimir Zapolskiy Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 1 + drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/os05b10.c | 1135 +++++++++++++++++++++++++++++++++++ 4 files changed, 1147 insertions(+) create mode 100644 drivers/media/i2c/os05b10.c diff --git a/MAINTAINERS b/MAINTAINERS index 34433602e972..752ccaea38b0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19238,6 +19238,7 @@ M: Elgin Perumbilly L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/ovti,os05b10.yaml +F: drivers/media/i2c/os05b10.c OMNIVISION OV01A10 SENSOR DRIVER M: Bingbu Cao diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 4af63bc0b810..6dcae0aa98ee 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -371,6 +371,16 @@ config VIDEO_OG0VE1B To compile this driver as a module, choose M here: the module will be called og0ve1b. +config VIDEO_OS05B10 + tristate "OmniVision OS05B10 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for Omnivision + OS05B10 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called os05b10. + config VIDEO_OV01A10 tristate "OmniVision OV01A10 sensor support" help diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 8400c3dfe626..a3a6396df3c4 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -84,6 +84,7 @@ obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o obj-$(CONFIG_VIDEO_OG0VE1B) += og0ve1b.o +obj-$(CONFIG_VIDEO_OS05B10) += os05b10.o obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o diff --git a/drivers/media/i2c/os05b10.c b/drivers/media/i2c/os05b10.c new file mode 100644 index 000000000000..8912d54ea55a --- /dev/null +++ b/drivers/media/i2c/os05b10.c @@ -0,0 +1,1135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 Support for the os05b10 + * + * Copyright (C) 2025 Silicon Signals Pvt. Ltd. + * + * Inspired from imx219, ov2735 camera drivers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define OS05B10_XCLK_FREQ (24 * HZ_PER_MHZ) + +#define OS05B10_REG_CHIP_ID CCI_REG24(0x300a) +#define OS05B10_CHIP_ID 0x530641 + +#define OS05B10_REG_CTRL_MODE CCI_REG8(0x0100) +#define OS05B10_MODE_STANDBY 0x00 +#define OS05B10_MODE_STREAMING 0x01 + +#define OS05B10_REG_EXPOSURE CCI_REG24(0x3500) +#define OS05B10_EXPOSURE_MIN 2 +#define OS05B10_EXPOSURE_STEP 1 +#define OS05B10_EXPOSURE_MARGIN 8 + +#define OS05B10_REG_ANALOG_GAIN CCI_REG16(0x3508) +#define OS05B10_ANALOG_GAIN_MIN 0x80 +#define OS05B10_ANALOG_GAIN_MAX 0x7C0 +#define OS05B10_ANALOG_GAIN_STEP 1 +#define OS05B10_ANALOG_GAIN_DEFAULT 0x80 + +#define OS05B10_REG_HTS CCI_REG16(0x380c) + +#define OS05B10_REG_VTS CCI_REG16(0x380e) +#define OS05B10_VTS_MAX 0x7fff + +#define OS05B10_LINK_FREQ_600MHZ (600 * HZ_PER_MHZ) + +static const struct v4l2_rect os05b10_native_area = { + .top = 0, + .left = 0, + .width = 2608, + .height = 1960, +}; + +static const struct v4l2_rect os05b10_active_area = { + .top = 8, + .left = 8, + .width = 2592, + .height = 1944, +}; + +static const char * const os05b10_supply_name[] = { + "avdd", /* Analog supply */ + "dovdd", /* Digital IO */ + "dvdd", /* Digital core */ +}; + +static const struct cci_reg_sequence os05b10_common_regs[] = { + { CCI_REG8(0x0301), 0x44 }, + { CCI_REG8(0x0303), 0x02 }, + { CCI_REG8(0x0305), 0x32 }, + { CCI_REG8(0x0306), 0x00 }, + { CCI_REG8(0x0325), 0x3b }, + { CCI_REG8(0x3002), 0x21 }, + { CCI_REG8(0x3016), 0x72 }, + { CCI_REG8(0x301e), 0xb4 }, + { CCI_REG8(0x301f), 0xd0 }, + { CCI_REG8(0x3021), 0x03 }, + { CCI_REG8(0x3022), 0x01 }, + { CCI_REG8(0x3107), 0xa1 }, + { CCI_REG8(0x3108), 0x7d }, + { CCI_REG8(0x3109), 0xfc }, + { CCI_REG8(0x3503), 0x88 }, + { CCI_REG8(0x350a), 0x04 }, + { CCI_REG8(0x350b), 0x00 }, + { CCI_REG8(0x350c), 0x00 }, + { CCI_REG8(0x350d), 0x80 }, + { CCI_REG8(0x350e), 0x04 }, + { CCI_REG8(0x350f), 0x00 }, + { CCI_REG8(0x3510), 0x00 }, + { CCI_REG8(0x3511), 0x00 }, + { CCI_REG8(0x3512), 0x20 }, + { CCI_REG8(0x3600), 0x4d }, + { CCI_REG8(0x3601), 0x08 }, + { CCI_REG8(0x3610), 0x87 }, + { CCI_REG8(0x3611), 0x24 }, + { CCI_REG8(0x3614), 0x4c }, + { CCI_REG8(0x3620), 0x0c }, + { CCI_REG8(0x3632), 0x80 }, + { CCI_REG8(0x3633), 0x00 }, + { CCI_REG8(0x3636), 0xcc }, + { CCI_REG8(0x3637), 0x27 }, + { CCI_REG8(0x3660), 0x00 }, + { CCI_REG8(0x3662), 0x10 }, + { CCI_REG8(0x3665), 0x00 }, + { CCI_REG8(0x3666), 0x00 }, + { CCI_REG8(0x366a), 0x14 }, + { CCI_REG8(0x3670), 0x0b }, + { CCI_REG8(0x3671), 0x0b }, + { CCI_REG8(0x3672), 0x0b }, + { CCI_REG8(0x3673), 0x0b }, + { CCI_REG8(0x3678), 0x2b }, + { CCI_REG8(0x367a), 0x11 }, + { CCI_REG8(0x367b), 0x11 }, + { CCI_REG8(0x367c), 0x11 }, + { CCI_REG8(0x367d), 0x11 }, + { CCI_REG8(0x3681), 0xff }, + { CCI_REG8(0x3682), 0x86 }, + { CCI_REG8(0x3683), 0x44 }, + { CCI_REG8(0x3684), 0x24 }, + { CCI_REG8(0x3685), 0x00 }, + { CCI_REG8(0x368a), 0x00 }, + { CCI_REG8(0x368d), 0x2b }, + { CCI_REG8(0x368e), 0x2b }, + { CCI_REG8(0x3690), 0x00 }, + { CCI_REG8(0x3691), 0x0b }, + { CCI_REG8(0x3692), 0x0b }, + { CCI_REG8(0x3693), 0x0b }, + { CCI_REG8(0x3694), 0x0b }, + { CCI_REG8(0x369d), 0x68 }, + { CCI_REG8(0x369e), 0x34 }, + { CCI_REG8(0x369f), 0x1b }, + { CCI_REG8(0x36a0), 0x0f }, + { CCI_REG8(0x36a1), 0x77 }, + { CCI_REG8(0x36b0), 0x30 }, + { CCI_REG8(0x36b2), 0x00 }, + { CCI_REG8(0x36b3), 0x00 }, + { CCI_REG8(0x36b4), 0x00 }, + { CCI_REG8(0x36b5), 0x00 }, + { CCI_REG8(0x36b6), 0x00 }, + { CCI_REG8(0x36b7), 0x00 }, + { CCI_REG8(0x36b8), 0x00 }, + { CCI_REG8(0x36b9), 0x00 }, + { CCI_REG8(0x36ba), 0x00 }, + { CCI_REG8(0x36bb), 0x00 }, + { CCI_REG8(0x36bc), 0x00 }, + { CCI_REG8(0x36bd), 0x00 }, + { CCI_REG8(0x36be), 0x00 }, + { CCI_REG8(0x36bf), 0x00 }, + { CCI_REG8(0x36c0), 0x01 }, + { CCI_REG8(0x36c1), 0x00 }, + { CCI_REG8(0x36c2), 0x00 }, + { CCI_REG8(0x36c3), 0x00 }, + { CCI_REG8(0x36c4), 0x00 }, + { CCI_REG8(0x36c5), 0x00 }, + { CCI_REG8(0x36c6), 0x00 }, + { CCI_REG8(0x36c7), 0x00 }, + { CCI_REG8(0x36c8), 0x00 }, + { CCI_REG8(0x36c9), 0x00 }, + { CCI_REG8(0x36ca), 0x0e }, + { CCI_REG8(0x36cb), 0x0e }, + { CCI_REG8(0x36cc), 0x0e }, + { CCI_REG8(0x36cd), 0x0e }, + { CCI_REG8(0x36ce), 0x0c }, + { CCI_REG8(0x36cf), 0x0c }, + { CCI_REG8(0x36d0), 0x0c }, + { CCI_REG8(0x36d1), 0x0c }, + { CCI_REG8(0x36d2), 0x00 }, + { CCI_REG8(0x36d3), 0x08 }, + { CCI_REG8(0x36d4), 0x10 }, + { CCI_REG8(0x36d5), 0x10 }, + { CCI_REG8(0x36d6), 0x00 }, + { CCI_REG8(0x36d7), 0x08 }, + { CCI_REG8(0x36d8), 0x10 }, + { CCI_REG8(0x36d9), 0x10 }, + { CCI_REG8(0x3701), 0x1d }, + { CCI_REG8(0x3703), 0x2a }, + { CCI_REG8(0x3704), 0x05 }, + { CCI_REG8(0x3709), 0x57 }, + { CCI_REG8(0x370b), 0x63 }, + { CCI_REG8(0x3706), 0x28 }, + { CCI_REG8(0x370a), 0x00 }, + { CCI_REG8(0x370b), 0x63 }, + { CCI_REG8(0x370e), 0x0c }, + { CCI_REG8(0x370f), 0x1c }, + { CCI_REG8(0x3710), 0x00 }, + { CCI_REG8(0x3713), 0x00 }, + { CCI_REG8(0x3714), 0x24 }, + { CCI_REG8(0x3716), 0x24 }, + { CCI_REG8(0x371a), 0x1e }, + { CCI_REG8(0x3724), 0x09 }, + { CCI_REG8(0x3725), 0xb2 }, + { CCI_REG8(0x372b), 0x54 }, + { CCI_REG8(0x3730), 0xe1 }, + { CCI_REG8(0x3735), 0x80 }, + { CCI_REG8(0x3739), 0x10 }, + { CCI_REG8(0x373f), 0xb0 }, + { CCI_REG8(0x3740), 0x28 }, + { CCI_REG8(0x3741), 0x21 }, + { CCI_REG8(0x3742), 0x21 }, + { CCI_REG8(0x3743), 0x21 }, + { CCI_REG8(0x3744), 0x63 }, + { CCI_REG8(0x3745), 0x5a }, + { CCI_REG8(0x3746), 0x5a }, + { CCI_REG8(0x3747), 0x5a }, + { CCI_REG8(0x3748), 0x00 }, + { CCI_REG8(0x3749), 0x00 }, + { CCI_REG8(0x374a), 0x00 }, + { CCI_REG8(0x374b), 0x00 }, + { CCI_REG8(0x3756), 0x00 }, + { CCI_REG8(0x3757), 0x0e }, + { CCI_REG8(0x375d), 0x84 }, + { CCI_REG8(0x3760), 0x11 }, + { CCI_REG8(0x3767), 0x08 }, + { CCI_REG8(0x376f), 0x42 }, + { CCI_REG8(0x3771), 0x00 }, + { CCI_REG8(0x3773), 0x01 }, + { CCI_REG8(0x3774), 0x02 }, + { CCI_REG8(0x3775), 0x12 }, + { CCI_REG8(0x3776), 0x02 }, + { CCI_REG8(0x377b), 0x40 }, + { CCI_REG8(0x377c), 0x00 }, + { CCI_REG8(0x377d), 0x0c }, + { CCI_REG8(0x3782), 0x02 }, + { CCI_REG8(0x3787), 0x24 }, + { CCI_REG8(0x378a), 0x01 }, + { CCI_REG8(0x378d), 0x00 }, + { CCI_REG8(0x3790), 0x1f }, + { CCI_REG8(0x3791), 0x58 }, + { CCI_REG8(0x3795), 0x24 }, + { CCI_REG8(0x3796), 0x01 }, + { CCI_REG8(0x3798), 0x40 }, + { CCI_REG8(0x379c), 0x00 }, + { CCI_REG8(0x379d), 0x00 }, + { CCI_REG8(0x379e), 0x00 }, + { CCI_REG8(0x379f), 0x01 }, + { CCI_REG8(0x37a1), 0x10 }, + { CCI_REG8(0x37a6), 0x00 }, + { CCI_REG8(0x37ab), 0x0e }, + { CCI_REG8(0x37ac), 0xa0 }, + { CCI_REG8(0x37be), 0x0a }, + { CCI_REG8(0x37bf), 0x05 }, + { CCI_REG8(0x37bb), 0x02 }, + { CCI_REG8(0x37bf), 0x05 }, + { CCI_REG8(0x37c2), 0x04 }, + { CCI_REG8(0x37c4), 0x11 }, + { CCI_REG8(0x37c5), 0x80 }, + { CCI_REG8(0x37c6), 0x14 }, + { CCI_REG8(0x37c7), 0x08 }, + { CCI_REG8(0x37c8), 0x42 }, + { CCI_REG8(0x37cd), 0x17 }, + { CCI_REG8(0x37ce), 0x01 }, + { CCI_REG8(0x37d8), 0x02 }, + { CCI_REG8(0x37d9), 0x08 }, + { CCI_REG8(0x37dc), 0x01 }, + { CCI_REG8(0x37e0), 0x0c }, + { CCI_REG8(0x37e1), 0x20 }, + { CCI_REG8(0x37e2), 0x10 }, + { CCI_REG8(0x37e3), 0x04 }, + { CCI_REG8(0x37e4), 0x28 }, + { CCI_REG8(0x37e5), 0x02 }, + { CCI_REG8(0x37ef), 0x00 }, + { CCI_REG8(0x37f4), 0x00 }, + { CCI_REG8(0x37f5), 0x00 }, + { CCI_REG8(0x37f6), 0x00 }, + { CCI_REG8(0x37f7), 0x00 }, + { CCI_REG8(0x3800), 0x01 }, + { CCI_REG8(0x3801), 0x30 }, + { CCI_REG8(0x3802), 0x00 }, + { CCI_REG8(0x3803), 0x00 }, + { CCI_REG8(0x3804), 0x0b }, + { CCI_REG8(0x3805), 0x5f }, + { CCI_REG8(0x3806), 0x07 }, + { CCI_REG8(0x3807), 0xa7 }, + { CCI_REG8(0x3808), 0x0a }, + { CCI_REG8(0x3809), 0x20 }, + { CCI_REG8(0x380a), 0x07 }, + { CCI_REG8(0x380b), 0x98 }, + { CCI_REG8(0x380c), 0x06 }, + { CCI_REG8(0x380d), 0xd0 }, + { CCI_REG8(0x3810), 0x00 }, + { CCI_REG8(0x3811), 0x08 }, + { CCI_REG8(0x3812), 0x00 }, + { CCI_REG8(0x3813), 0x08 }, + { CCI_REG8(0x3814), 0x01 }, + { CCI_REG8(0x3815), 0x01 }, + { CCI_REG8(0x3816), 0x01 }, + { CCI_REG8(0x3817), 0x01 }, + { CCI_REG8(0x3818), 0x00 }, + { CCI_REG8(0x3819), 0x00 }, + { CCI_REG8(0x381a), 0x00 }, + { CCI_REG8(0x381b), 0x01 }, + { CCI_REG8(0x3820), 0x88 }, + { CCI_REG8(0x3821), 0x00 }, + { CCI_REG8(0x3822), 0x12 }, + { CCI_REG8(0x3823), 0x08 }, + { CCI_REG8(0x3824), 0x00 }, + { CCI_REG8(0x3825), 0x20 }, + { CCI_REG8(0x3826), 0x00 }, + { CCI_REG8(0x3827), 0x08 }, + { CCI_REG8(0x3829), 0x03 }, + { CCI_REG8(0x382a), 0x00 }, + { CCI_REG8(0x382b), 0x00 }, + { CCI_REG8(0x3832), 0x08 }, + { CCI_REG8(0x3838), 0x00 }, + { CCI_REG8(0x3839), 0x00 }, + { CCI_REG8(0x383a), 0x00 }, + { CCI_REG8(0x383b), 0x00 }, + { CCI_REG8(0x383d), 0x01 }, + { CCI_REG8(0x383e), 0x00 }, + { CCI_REG8(0x383f), 0x00 }, + { CCI_REG8(0x3843), 0x00 }, + { CCI_REG8(0x3880), 0x16 }, + { CCI_REG8(0x3881), 0x00 }, + { CCI_REG8(0x3882), 0x08 }, + { CCI_REG8(0x389a), 0x00 }, + { CCI_REG8(0x389b), 0x00 }, + { CCI_REG8(0x38a2), 0x02 }, + { CCI_REG8(0x38a3), 0x02 }, + { CCI_REG8(0x38a4), 0x02 }, + { CCI_REG8(0x38a5), 0x02 }, + { CCI_REG8(0x38a7), 0x04 }, + { CCI_REG8(0x38b8), 0x02 }, + { CCI_REG8(0x3c80), 0x3e }, + { CCI_REG8(0x3c86), 0x01 }, + { CCI_REG8(0x3c87), 0x02 }, + { CCI_REG8(0x389c), 0x00 }, + { CCI_REG8(0x3ca2), 0x0c }, + { CCI_REG8(0x3d85), 0x1b }, + { CCI_REG8(0x3d8c), 0x01 }, + { CCI_REG8(0x3d8d), 0xe2 }, + { CCI_REG8(0x3f00), 0xcb }, + { CCI_REG8(0x3f03), 0x08 }, + { CCI_REG8(0x3f9e), 0x07 }, + { CCI_REG8(0x3f9f), 0x04 }, + { CCI_REG8(0x4000), 0xf3 }, + { CCI_REG8(0x4002), 0x00 }, + { CCI_REG8(0x4003), 0x40 }, + { CCI_REG8(0x4008), 0x02 }, + { CCI_REG8(0x4009), 0x0d }, + { CCI_REG8(0x400a), 0x01 }, + { CCI_REG8(0x400b), 0x00 }, + { CCI_REG8(0x4040), 0x00 }, + { CCI_REG8(0x4041), 0x07 }, + { CCI_REG8(0x4090), 0x14 }, + { CCI_REG8(0x40b0), 0x01 }, + { CCI_REG8(0x40b1), 0x01 }, + { CCI_REG8(0x40b2), 0x30 }, + { CCI_REG8(0x40b3), 0x04 }, + { CCI_REG8(0x40b4), 0xe8 }, + { CCI_REG8(0x40b5), 0x01 }, + { CCI_REG8(0x40b7), 0x07 }, + { CCI_REG8(0x40b8), 0xff }, + { CCI_REG8(0x40b9), 0x00 }, + { CCI_REG8(0x40ba), 0x00 }, + { CCI_REG8(0x4300), 0xff }, + { CCI_REG8(0x4301), 0x00 }, + { CCI_REG8(0x4302), 0x0f }, + { CCI_REG8(0x4303), 0x20 }, + { CCI_REG8(0x4304), 0x20 }, + { CCI_REG8(0x4305), 0x83 }, + { CCI_REG8(0x4306), 0x21 }, + { CCI_REG8(0x430d), 0x00 }, + { CCI_REG8(0x4505), 0xc4 }, + { CCI_REG8(0x4506), 0x00 }, + { CCI_REG8(0x4507), 0x60 }, + { CCI_REG8(0x4803), 0x00 }, + { CCI_REG8(0x4809), 0x8e }, + { CCI_REG8(0x480e), 0x00 }, + { CCI_REG8(0x4813), 0x00 }, + { CCI_REG8(0x4814), 0x2a }, + { CCI_REG8(0x481b), 0x40 }, + { CCI_REG8(0x481f), 0x30 }, + { CCI_REG8(0x4825), 0x34 }, + { CCI_REG8(0x4829), 0x64 }, + { CCI_REG8(0x4837), 0x12 }, + { CCI_REG8(0x484b), 0x07 }, + { CCI_REG8(0x4883), 0x36 }, + { CCI_REG8(0x4885), 0x03 }, + { CCI_REG8(0x488b), 0x00 }, + { CCI_REG8(0x4d06), 0x01 }, + { CCI_REG8(0x4e00), 0x2a }, + { CCI_REG8(0x4e0d), 0x00 }, + { CCI_REG8(0x5000), 0xf9 }, + { CCI_REG8(0x5001), 0x09 }, + { CCI_REG8(0x5004), 0x00 }, + { CCI_REG8(0x5005), 0x0e }, + { CCI_REG8(0x5036), 0x00 }, + { CCI_REG8(0x5080), 0x04 }, + { CCI_REG8(0x5082), 0x00 }, + { CCI_REG8(0x5180), 0x00 }, + { CCI_REG8(0x5181), 0x10 }, + { CCI_REG8(0x5182), 0x01 }, + { CCI_REG8(0x5183), 0xdf }, + { CCI_REG8(0x5184), 0x02 }, + { CCI_REG8(0x5185), 0x6c }, + { CCI_REG8(0x5189), 0x48 }, + { CCI_REG8(0x520a), 0x03 }, + { CCI_REG8(0x520b), 0x0f }, + { CCI_REG8(0x520c), 0x3f }, + { CCI_REG8(0x580b), 0x03 }, + { CCI_REG8(0x580d), 0x00 }, + { CCI_REG8(0x580f), 0x00 }, + { CCI_REG8(0x5820), 0x00 }, + { CCI_REG8(0x5821), 0x00 }, + { CCI_REG8(0x3222), 0x03 }, + { CCI_REG8(0x3208), 0x06 }, + { CCI_REG8(0x3701), 0x1d }, + { CCI_REG8(0x37ab), 0x01 }, + { CCI_REG8(0x3790), 0x21 }, + { CCI_REG8(0x38be), 0x00 }, + { CCI_REG8(0x3791), 0x5a }, + { CCI_REG8(0x37bf), 0x1c }, + { CCI_REG8(0x3610), 0x37 }, + { CCI_REG8(0x3208), 0x16 }, + { CCI_REG8(0x3208), 0x07 }, + { CCI_REG8(0x3701), 0x1d }, + { CCI_REG8(0x37ab), 0x0e }, + { CCI_REG8(0x3790), 0x21 }, + { CCI_REG8(0x38be), 0x00 }, + { CCI_REG8(0x3791), 0x5a }, + { CCI_REG8(0x37bf), 0x0a }, + { CCI_REG8(0x3610), 0x87 }, + { CCI_REG8(0x3208), 0x17 }, + { CCI_REG8(0x3208), 0x08 }, + { CCI_REG8(0x3701), 0x1d }, + { CCI_REG8(0x37ab), 0x0e }, + { CCI_REG8(0x3790), 0x21 }, + { CCI_REG8(0x38be), 0x00 }, + { CCI_REG8(0x3791), 0x5a }, + { CCI_REG8(0x37bf), 0x0a }, + { CCI_REG8(0x3610), 0x87 }, + { CCI_REG8(0x3208), 0x18 }, + { CCI_REG8(0x3208), 0x09 }, + { CCI_REG8(0x3701), 0x1d }, + { CCI_REG8(0x37ab), 0x0e }, + { CCI_REG8(0x3790), 0x28 }, + { CCI_REG8(0x38be), 0x00 }, + { CCI_REG8(0x3791), 0x63 }, + { CCI_REG8(0x37bf), 0x0a }, + { CCI_REG8(0x3610), 0x87 }, + { CCI_REG8(0x3208), 0x19 }, +}; + +struct os05b10 { + struct device *dev; + struct regmap *cci; + struct v4l2_subdev sd; + struct media_pad pad; + struct clk *xclk; + struct i2c_client *client; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(os05b10_supply_name)]; + + /* V4L2 Controls */ + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *gain; + struct v4l2_ctrl *exposure; + + u32 link_freq_index; + u32 data_lanes; +}; + +struct os05b10_mode { + u32 width; + u32 height; + u32 vts; + u32 hts; + u32 exp; + u8 bpp; +}; + +static const struct os05b10_mode supported_modes_10bit[] = { + { + .width = 2592, + .height = 1944, + .vts = 2006, + .hts = 1744, + .exp = 1944, + .bpp = 10, + }, +}; + +static const s64 link_frequencies[] = { + OS05B10_LINK_FREQ_600MHZ, +}; + +static const u32 os05b10_mbus_codes[] = { + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static inline struct os05b10 *to_os05b10(struct v4l2_subdev *sd) +{ + return container_of_const(sd, struct os05b10, sd); +}; + +static int os05b10_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct os05b10 *os05b10 = container_of_const(ctrl->handler, + struct os05b10, handler); + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + int vmax, ret; + + state = v4l2_subdev_get_locked_active_state(&os05b10->sd); + fmt = v4l2_subdev_state_get_format(state, 0); + + if (ctrl->id == V4L2_CID_VBLANK) { + /* Honour the VBLANK limits when setting exposure. */ + s64 max = fmt->height + ctrl->val - OS05B10_EXPOSURE_MARGIN; + + ret = __v4l2_ctrl_modify_range(os05b10->exposure, + os05b10->exposure->minimum, max, + os05b10->exposure->step, + os05b10->exposure->default_value); + if (ret) + return ret; + } + + if (pm_runtime_get_if_in_use(os05b10->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_VBLANK: + vmax = fmt->height + ctrl->val; + ret = cci_write(os05b10->cci, OS05B10_REG_VTS, vmax, NULL); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = cci_write(os05b10->cci, OS05B10_REG_ANALOG_GAIN, + ctrl->val, NULL); + break; + case V4L2_CID_EXPOSURE: + ret = cci_write(os05b10->cci, OS05B10_REG_EXPOSURE, + ctrl->val, NULL); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(os05b10->dev); + + return ret; +} + +static int os05b10_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(os05b10_mbus_codes)) + return -EINVAL; + + code->code = os05b10_mbus_codes[code->index]; + + return 0; +} + +static int os05b10_set_framing_limits(struct os05b10 *os05b10, + const struct os05b10_mode *mode) +{ + u32 hblank, vblank, vblank_max, max_exp; + int ret; + + hblank = mode->hts - mode->width; + ret = __v4l2_ctrl_modify_range(os05b10->hblank, hblank, hblank, 1, + hblank); + if (ret) + return ret; + + vblank = mode->vts - mode->height; + vblank_max = OS05B10_VTS_MAX - mode->height; + ret = __v4l2_ctrl_modify_range(os05b10->vblank, 0, vblank_max, 1, + vblank); + if (ret) + return ret; + + max_exp = mode->vts - OS05B10_EXPOSURE_MARGIN; + return __v4l2_ctrl_modify_range(os05b10->exposure, + OS05B10_EXPOSURE_MIN, max_exp, + OS05B10_EXPOSURE_STEP, mode->exp); +} + +static int os05b10_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + const struct os05b10_mode *mode = &supported_modes_10bit[0]; + struct os05b10 *os05b10 = to_os05b10(sd); + struct v4l2_mbus_framefmt *format; + int ret; + + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; + + format = v4l2_subdev_state_get_format(sd_state, 0); + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ret = os05b10_set_framing_limits(os05b10, mode); + if (ret) + return ret; + } + + *format = fmt->format; + + return 0; +} + +static int os05b10_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r = os05b10_native_area; + return 0; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r = os05b10_active_area; + return 0; + default: + return -EINVAL; + } +} + +static int os05b10_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes_10bit)) + return -EINVAL; + + fse->min_width = supported_modes_10bit[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes_10bit[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int os05b10_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct os05b10 *os05b10 = to_os05b10(sd); + int ret; + + ret = pm_runtime_resume_and_get(os05b10->dev); + if (ret < 0) + return ret; + + /* Write common registers */ + ret = cci_multi_reg_write(os05b10->cci, os05b10_common_regs, + ARRAY_SIZE(os05b10_common_regs), NULL); + if (ret) { + dev_err(os05b10->dev, "failed to write common registers\n"); + goto err_rpm_put; + } + + /* Apply customized user controls */ + ret = __v4l2_ctrl_handler_setup(os05b10->sd.ctrl_handler); + if (ret) + goto err_rpm_put; + + /* Stream ON */ + ret = cci_write(os05b10->cci, OS05B10_REG_CTRL_MODE, + OS05B10_MODE_STREAMING, NULL); + if (ret) + goto err_rpm_put; + + return 0; + +err_rpm_put: + pm_runtime_put(os05b10->dev); + + return ret; +} + +static int os05b10_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct os05b10 *os05b10 = to_os05b10(sd); + int ret; + + ret = cci_write(os05b10->cci, OS05B10_REG_CTRL_MODE, + OS05B10_MODE_STANDBY, NULL); + if (ret) + dev_err(os05b10->dev, "failed to set stream off\n"); + + pm_runtime_put(os05b10->dev); + + return ret; +} + +static int os05b10_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *format; + const struct os05b10_mode *mode; + + /* Initialize try_fmt */ + format = v4l2_subdev_state_get_format(state, 0); + + mode = &supported_modes_10bit[0]; + format->code = MEDIA_BUS_FMT_SBGGR10_1X10; + + /* Update image pad formate */ + format->width = mode->width; + format->height = mode->height; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_RAW; + format->quantization = V4L2_QUANTIZATION_FULL_RANGE; + format->xfer_func = V4L2_XFER_FUNC_NONE; + + return 0; +} + +static const struct v4l2_subdev_video_ops os05b10_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops os05b10_pad_ops = { + .enum_mbus_code = os05b10_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = os05b10_set_pad_format, + .get_selection = os05b10_get_selection, + .enum_frame_size = os05b10_enum_frame_size, + .enable_streams = os05b10_enable_streams, + .disable_streams = os05b10_disable_streams, +}; + +static const struct v4l2_subdev_internal_ops os05b10_internal_ops = { + .init_state = os05b10_init_state, +}; + +static const struct v4l2_subdev_ops os05b10_subdev_ops = { + .video = &os05b10_video_ops, + .pad = &os05b10_pad_ops, +}; + +static const struct v4l2_ctrl_ops os05b10_ctrl_ops = { + .s_ctrl = os05b10_set_ctrl, +}; + +static int os05b10_identify_module(struct os05b10 *os05b10) +{ + int ret; + u64 val; + + ret = cci_read(os05b10->cci, OS05B10_REG_CHIP_ID, &val, NULL); + if (ret) + return dev_err_probe(os05b10->dev, ret, + "failed to read chip id %x\n", + OS05B10_CHIP_ID); + + if (val != OS05B10_CHIP_ID) + return dev_err_probe(os05b10->dev, -ENODEV, + "chip id mismatch: %x!=%llx\n", + OS05B10_CHIP_ID, val); + + return 0; +} + +static int os05b10_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct os05b10 *os05b10 = to_os05b10(sd); + unsigned long delay_us; + int ret; + + /* Enable power rails */ + ret = regulator_bulk_enable(ARRAY_SIZE(os05b10_supply_name), + os05b10->supplies); + if (ret) { + dev_err(os05b10->dev, "failed to enable regulators\n"); + return ret; + } + + /* Enable xclk */ + ret = clk_prepare_enable(os05b10->xclk); + if (ret) { + dev_err(os05b10->dev, "failed to enable clock\n"); + goto err_regulator_off; + } + + gpiod_set_value_cansleep(os05b10->reset_gpio, 0); + + /* Delay T1 */ + fsleep(5 * USEC_PER_MSEC); + + /* Delay T2 (8192 cycles before SCCB/I2C access) */ + delay_us = DIV_ROUND_UP(8192, OS05B10_XCLK_FREQ / 1000 / 1000); + usleep_range(delay_us, delay_us * 2); + + return 0; + +err_regulator_off: + regulator_bulk_disable(ARRAY_SIZE(os05b10_supply_name), + os05b10->supplies); + + return ret; +} + +static int os05b10_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct os05b10 *os05b10 = to_os05b10(sd); + + gpiod_set_value_cansleep(os05b10->reset_gpio, 1); + + regulator_bulk_disable(ARRAY_SIZE(os05b10_supply_name), + os05b10->supplies); + clk_disable_unprepare(os05b10->xclk); + + return 0; +} + +static int os05b10_parse_endpoint(struct os05b10 *os05b10) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + unsigned long link_freq_bitmap; + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(os05b10->dev), 0, 0, 0); + if (!ep) { + dev_err(os05b10->dev, "Failed to get next endpoint\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { + ret = dev_err_probe(os05b10->dev, -EINVAL, + "only 4 data lanes are supported\n"); + goto error_out; + } + + os05b10->data_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; + + ret = v4l2_link_freq_to_bitmap(os05b10->dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + link_frequencies, + ARRAY_SIZE(link_frequencies), + &link_freq_bitmap); + if (ret) { + dev_err(os05b10->dev, "only 600MHz frequency is available\n"); + goto error_out; + } + + os05b10->link_freq_index = __ffs(link_freq_bitmap); + +error_out: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static u64 os05b10_pixel_rate(struct os05b10 *os05b10, + const struct os05b10_mode *mode) +{ + u64 link_freq = link_frequencies[os05b10->link_freq_index]; + const unsigned int lanes = os05b10->data_lanes; + u64 numerator = link_freq * 2 * lanes; + unsigned int bpp = mode->bpp; + + do_div(numerator, bpp); + + dev_info(os05b10->dev, + "link_freq=%llu bpp=%u lanes=%u pixel_rate=%llu\n", + link_freq, bpp, lanes, numerator); + + return numerator; +} + +static int os05b10_init_controls(struct os05b10 *os05b10) +{ + const struct os05b10_mode *mode = &supported_modes_10bit[0]; + u64 hblank_def, vblank_def, exp_max, pixel_rate; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + int ret; + + ctrl_hdlr = &os05b10->handler; + v4l2_ctrl_handler_init(ctrl_hdlr, 8); + + pixel_rate = os05b10_pixel_rate(os05b10, mode); + v4l2_ctrl_new_std(ctrl_hdlr, &os05b10_ctrl_ops, V4L2_CID_PIXEL_RATE, + pixel_rate, pixel_rate, 1, pixel_rate); + + os05b10->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &os05b10_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_frequencies) - 1, + os05b10->link_freq_index, + link_frequencies); + + if (os05b10->link_freq) + os05b10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + hblank_def = mode->hts - mode->width; + os05b10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK, + hblank_def, hblank_def, + 1, hblank_def); + if (os05b10->hblank) + os05b10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank_def = mode->vts - mode->height; + os05b10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os05b10_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + OS05B10_VTS_MAX - mode->height, + 1, vblank_def); + + exp_max = mode->vts - OS05B10_EXPOSURE_MARGIN; + os05b10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &os05b10_ctrl_ops, + V4L2_CID_EXPOSURE, + OS05B10_EXPOSURE_MIN, + exp_max, OS05B10_EXPOSURE_STEP, + mode->exp); + + os05b10->gain = v4l2_ctrl_new_std(ctrl_hdlr, &os05b10_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, + OS05B10_ANALOG_GAIN_MIN, + OS05B10_ANALOG_GAIN_MAX, + OS05B10_ANALOG_GAIN_STEP, + OS05B10_ANALOG_GAIN_DEFAULT); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(os05b10->dev, "control init failed (%d)\n", ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(os05b10->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &os05b10_ctrl_ops, + &props); + if (ret) + goto error; + + os05b10->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int os05b10_probe(struct i2c_client *client) +{ + struct os05b10 *os05b10; + unsigned int xclk_freq; + unsigned int i; + int ret; + + os05b10 = devm_kzalloc(&client->dev, sizeof(*os05b10), GFP_KERNEL); + if (!os05b10) + return -ENOMEM; + + os05b10->client = client; + os05b10->dev = &client->dev; + + v4l2_i2c_subdev_init(&os05b10->sd, client, &os05b10_subdev_ops); + + os05b10->cci = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(os05b10->cci)) + return dev_err_probe(os05b10->dev, PTR_ERR(os05b10->cci), + "failed to initialize CCI\n"); + + os05b10->xclk = devm_v4l2_sensor_clk_get(os05b10->dev, NULL); + if (IS_ERR(os05b10->xclk)) + return dev_err_probe(os05b10->dev, PTR_ERR(os05b10->xclk), + "failed to get xclk\n"); + + xclk_freq = clk_get_rate(os05b10->xclk); + if (xclk_freq != OS05B10_XCLK_FREQ) + return dev_err_probe(os05b10->dev, -EINVAL, + "xclk frequency not supported: %d Hz\n", + xclk_freq); + + for (i = 0; i < ARRAY_SIZE(os05b10_supply_name); i++) + os05b10->supplies[i].supply = os05b10_supply_name[i]; + + ret = devm_regulator_bulk_get(os05b10->dev, + ARRAY_SIZE(os05b10_supply_name), + os05b10->supplies); + if (ret) + return dev_err_probe(os05b10->dev, ret, + "failed to get regulators\n"); + + ret = os05b10_parse_endpoint(os05b10); + if (ret) + return dev_err_probe(os05b10->dev, ret, + "failed to parse endpoint configuration\n"); + + os05b10->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(os05b10->reset_gpio)) + return dev_err_probe(os05b10->dev, PTR_ERR(os05b10->reset_gpio), + "failed to get reset GPIO\n"); + + ret = os05b10_power_on(os05b10->dev); + if (ret) + return ret; + + ret = os05b10_identify_module(os05b10); + if (ret) + goto error_power_off; + + /* This needs the pm runtime to be registered. */ + ret = os05b10_init_controls(os05b10); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + os05b10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + os05b10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + os05b10->sd.internal_ops = &os05b10_internal_ops; + os05b10->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&os05b10->sd.entity, 1, &os05b10->pad); + if (ret) { + dev_err_probe(os05b10->dev, ret, + "failed to init entity pads\n"); + goto error_handler_free; + } + + os05b10->sd.state_lock = os05b10->handler.lock; + ret = v4l2_subdev_init_finalize(&os05b10->sd); + if (ret < 0) { + dev_err_probe(os05b10->dev, ret, "subdev init error\n"); + goto error_media_entity; + } + + pm_runtime_set_active(os05b10->dev); + pm_runtime_enable(os05b10->dev); + + ret = v4l2_async_register_subdev_sensor(&os05b10->sd); + if (ret < 0) { + dev_err_probe(os05b10->dev, ret, + "failed to register os05b10 sub-device\n"); + goto error_subdev_cleanup; + } + + pm_runtime_idle(os05b10->dev); + + return 0; + +error_subdev_cleanup: + v4l2_subdev_cleanup(&os05b10->sd); + pm_runtime_disable(os05b10->dev); + pm_runtime_set_suspended(os05b10->dev); + +error_media_entity: + media_entity_cleanup(&os05b10->sd.entity); + +error_handler_free: + v4l2_ctrl_handler_free(os05b10->sd.ctrl_handler); + +error_power_off: + os05b10_power_off(os05b10->dev); + + return ret; +} + +static void os05b10_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct os05b10 *os05b10 = to_os05b10(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(&os05b10->sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(os05b10->sd.ctrl_handler); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + os05b10_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } +} + +static DEFINE_RUNTIME_DEV_PM_OPS(os05b10_pm_ops, os05b10_power_off, + os05b10_power_on, NULL); + +static const struct of_device_id os05b10_id[] = { + { .compatible = "ovti,os05b10" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, os05b10_id); + +static struct i2c_driver os05b10_driver = { + .driver = { + .name = "os05b10", + .pm = pm_ptr(&os05b10_pm_ops), + .of_match_table = os05b10_id, + }, + .probe = os05b10_probe, + .remove = os05b10_remove, +}; + +module_i2c_driver(os05b10_driver); + +MODULE_DESCRIPTION("OS05B10 Camera Sensor Driver"); +MODULE_AUTHOR("Himanshu Bhavani "); +MODULE_AUTHOR("Elgin Perumbilly "); +MODULE_LICENSE("GPL"); From 8f1fd70fc7c3cad2faf0a1adde5e4399ecfae691 Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Wed, 17 Dec 2025 11:02:21 +0800 Subject: [PATCH 057/307] media: docs: dev-decoder: Trigger dynamic source change for colorspace If colorspace changes, the client needs to renegotiate the pipeline, otherwise the decoded frame may not be displayed correctly. So add colorspace as a trigger parameter for dynamic resolution change. Signed-off-by: Ming Qian Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- Documentation/userspace-api/media/v4l/dev-decoder.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/userspace-api/media/v4l/dev-decoder.rst b/Documentation/userspace-api/media/v4l/dev-decoder.rst index eb662ced0ab4..2beb6ba1b3c2 100644 --- a/Documentation/userspace-api/media/v4l/dev-decoder.rst +++ b/Documentation/userspace-api/media/v4l/dev-decoder.rst @@ -933,7 +933,10 @@ reflected by corresponding queries): * the minimum number of buffers needed for decoding, -* bit-depth of the bitstream has been changed. +* bit-depth of the bitstream has been changed, + +* colorspace of the bitstream has been changed, but it doesn't require + buffer reallocation. Whenever that happens, the decoder must proceed as follows: From d85f3207d75df6d7a08be6526b15ff398668206c Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Wed, 17 Dec 2025 11:02:22 +0800 Subject: [PATCH 058/307] media: amphion: Clear last_buffer_dequeued flag for DEC_CMD_START The V4L2_DEC_CMD_START command may be used to handle the dynamic source change, which will triggers an implicit decoder drain. The last_buffer_dequeued flag is set in the implicit decoder drain, so driver need to clear it to continue the following decoding flow. Signed-off-by: Ming Qian Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/amphion/vdec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/amphion/vdec.c b/drivers/media/platform/amphion/vdec.c index c0d2aabb9e0e..f25dbcebdccf 100644 --- a/drivers/media/platform/amphion/vdec.c +++ b/drivers/media/platform/amphion/vdec.c @@ -724,6 +724,7 @@ static int vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd switch (cmd->cmd) { case V4L2_DEC_CMD_START: vdec_cmd_start(inst); + vb2_clear_last_buffer_dequeued(v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx)); break; case V4L2_DEC_CMD_STOP: vdec_cmd_stop(inst); From bb22847d1134723d6ed67ef0aaeb44c6af53e3da Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Wed, 17 Dec 2025 11:02:23 +0800 Subject: [PATCH 059/307] media: amphion: Trigger source change if colorspace changed After encountering a colorspace change in the stream, the decoder sends a V4L2_EVENT_SOURCE_CHANGE event with changes set to V4L2_EVENT_SRC_CH_RESOLUTION. Then the client can detect and handle the colorspace change without any buffer reallocation Signed-off-by: Ming Qian Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/amphion/vdec.c | 61 +++++++++++++++-------- drivers/media/platform/amphion/vpu_v4l2.c | 1 - 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/drivers/media/platform/amphion/vdec.c b/drivers/media/platform/amphion/vdec.c index f25dbcebdccf..beeaf75c76b4 100644 --- a/drivers/media/platform/amphion/vdec.c +++ b/drivers/media/platform/amphion/vdec.c @@ -950,7 +950,7 @@ static void vdec_stop_done(struct vpu_inst *inst) vpu_inst_unlock(inst); } -static bool vdec_check_source_change(struct vpu_inst *inst) +static bool vdec_check_source_change(struct vpu_inst *inst, struct vpu_dec_codec_info *hdr) { struct vdec_t *vdec = inst->priv; const struct vpu_format *sibling; @@ -962,26 +962,35 @@ static bool vdec_check_source_change(struct vpu_inst *inst) return false; sibling = vpu_helper_find_sibling(inst, inst->cap_format.type, inst->cap_format.pixfmt); - if (sibling && vdec->codec_info.pixfmt == sibling->pixfmt) - vdec->codec_info.pixfmt = inst->cap_format.pixfmt; + if (sibling && hdr->pixfmt == sibling->pixfmt) + hdr->pixfmt = inst->cap_format.pixfmt; if (!vb2_is_streaming(v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx))) return true; - if (inst->cap_format.pixfmt != vdec->codec_info.pixfmt) + if (inst->cap_format.pixfmt != hdr->pixfmt) return true; - if (inst->cap_format.width != vdec->codec_info.decoded_width) + if (inst->cap_format.width != hdr->decoded_width) return true; - if (inst->cap_format.height != vdec->codec_info.decoded_height) + if (inst->cap_format.height != hdr->decoded_height) return true; if (vpu_get_num_buffers(inst, inst->cap_format.type) < inst->min_buffer_cap) return true; - if (inst->crop.left != vdec->codec_info.offset_x) + if (inst->crop.left != hdr->offset_x) return true; - if (inst->crop.top != vdec->codec_info.offset_y) + if (inst->crop.top != hdr->offset_y) return true; - if (inst->crop.width != vdec->codec_info.width) + if (inst->crop.width != hdr->width) return true; - if (inst->crop.height != vdec->codec_info.height) + if (inst->crop.height != hdr->height) + return true; + if (!hdr->progressive) + return true; + + if (vdec->seq_hdr_found && + (hdr->color_primaries != vdec->codec_info.color_primaries || + hdr->transfer_chars != vdec->codec_info.transfer_chars || + hdr->matrix_coeffs != vdec->codec_info.matrix_coeffs || + hdr->full_range != vdec->codec_info.full_range)) return true; return false; @@ -1333,20 +1342,25 @@ static void vdec_event_seq_hdr(struct vpu_inst *inst, struct vpu_dec_codec_info struct vdec_t *vdec = inst->priv; vpu_inst_lock(inst); - memcpy(&vdec->codec_info, hdr, sizeof(vdec->codec_info)); - vpu_trace(inst->dev, "[%d] %d x %d, crop : (%d, %d) %d x %d, %d, %d\n", + vpu_trace(inst->dev, + "[%d] %d x %d, crop : (%d, %d) %d x %d, %d, %d, colorspace: %d, %d, %d, %d\n", inst->id, - vdec->codec_info.decoded_width, - vdec->codec_info.decoded_height, - vdec->codec_info.offset_x, - vdec->codec_info.offset_y, - vdec->codec_info.width, - vdec->codec_info.height, + hdr->decoded_width, + hdr->decoded_height, + hdr->offset_x, + hdr->offset_y, + hdr->width, + hdr->height, hdr->num_ref_frms, - hdr->num_dpb_frms); + hdr->num_dpb_frms, + hdr->color_primaries, + hdr->transfer_chars, + hdr->matrix_coeffs, + hdr->full_range); inst->min_buffer_cap = hdr->num_ref_frms + hdr->num_dpb_frms; - vdec->is_source_changed = vdec_check_source_change(inst); + vdec->is_source_changed = vdec_check_source_change(inst, hdr); + memcpy(&vdec->codec_info, hdr, sizeof(vdec->codec_info)); vdec_init_fmt(inst); vdec_init_crop(inst); vdec_init_mbi(inst); @@ -1375,7 +1389,12 @@ static void vdec_event_resolution_change(struct vpu_inst *inst) { struct vdec_t *vdec = inst->priv; - vpu_trace(inst->dev, "[%d]\n", inst->id); + vpu_trace(inst->dev, "[%d] input : %d, decoded : %d, display : %d, sequence : %d\n", + inst->id, + vdec->params.frame_count, + vdec->decoded_frame_count, + vdec->display_frame_count, + vdec->sequence); vpu_inst_lock(inst); vdec->seq_tag = vdec->codec_info.tag; vdec_clear_fs(&vdec->mbi); diff --git a/drivers/media/platform/amphion/vpu_v4l2.c b/drivers/media/platform/amphion/vpu_v4l2.c index 47dff9a35bb4..121165a7184f 100644 --- a/drivers/media/platform/amphion/vpu_v4l2.c +++ b/drivers/media/platform/amphion/vpu_v4l2.c @@ -102,7 +102,6 @@ static int vpu_notify_eos(struct vpu_inst *inst) int vpu_notify_source_change(struct vpu_inst *inst) { static const struct v4l2_event ev = { - .id = 0, .type = V4L2_EVENT_SOURCE_CHANGE, .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION }; From d79c2165a48e87fc21136c323cb7822da69f8691 Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Mon, 22 Dec 2025 16:49:10 +0800 Subject: [PATCH 060/307] media: amphion: Use kmalloc instead of vmalloc Replace vmalloc/vfree with kmalloc/kfree for allocating small driver structures (vpu_inst, vdec_t, venc_t, vpu_cmd_t, and frame objects). vmalloc() is designed for large memory allocations and incurs unnecessary overhead for small objects due to virtual memory mapping. kmalloc() is more appropriate as it allocates physically contiguous memory with lower overhead. ftrace measurements of vpu_alloc_cmd() show significant improvement: Before (vmalloc): 35-72 us (avg ~45.7 us) After (kmalloc): 11-26 us (avg ~16.8 us) This reduces allocation time by approximately 63%. No functional changes are intended. Signed-off-by: Ming Qian Reviewed-by: Nicolas Dufresne Reviewed-by: Frank Li Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/amphion/vdec.c | 16 +++++++--------- drivers/media/platform/amphion/venc.c | 20 +++++++++----------- drivers/media/platform/amphion/vpu_cmds.c | 15 +++++++-------- drivers/media/platform/amphion/vpu_core.c | 11 ++++------- 4 files changed, 27 insertions(+), 35 deletions(-) diff --git a/drivers/media/platform/amphion/vdec.c b/drivers/media/platform/amphion/vdec.c index beeaf75c76b4..adf53b49020e 100644 --- a/drivers/media/platform/amphion/vdec.c +++ b/drivers/media/platform/amphion/vdec.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -17,7 +16,6 @@ #include #include #include -#include #include "vpu.h" #include "vpu_defs.h" #include "vpu_core.h" @@ -1662,9 +1660,9 @@ static void vdec_cleanup(struct vpu_inst *inst) vdec->slots = NULL; vdec->slot_count = 0; } - vfree(vdec); + kfree(vdec); inst->priv = NULL; - vfree(inst); + kfree(inst); } static void vdec_init_params(struct vdec_t *vdec) @@ -1929,13 +1927,13 @@ static int vdec_open(struct file *file) struct vdec_t *vdec; int ret; - inst = vzalloc(sizeof(*inst)); + inst = kzalloc(sizeof(*inst), GFP_KERNEL); if (!inst) return -ENOMEM; - vdec = vzalloc(sizeof(*vdec)); + vdec = kzalloc(sizeof(*vdec), GFP_KERNEL); if (!vdec) { - vfree(inst); + kfree(inst); return -ENOMEM; } @@ -1943,8 +1941,8 @@ static int vdec_open(struct file *file) sizeof(*vdec->slots), GFP_KERNEL | __GFP_ZERO); if (!vdec->slots) { - vfree(vdec); - vfree(inst); + kfree(vdec); + kfree(inst); return -ENOMEM; } vdec->slot_count = VDEC_SLOT_CNT_DFT; diff --git a/drivers/media/platform/amphion/venc.c b/drivers/media/platform/amphion/venc.c index aced76401b69..9e5cbc2b0d3f 100644 --- a/drivers/media/platform/amphion/venc.c +++ b/drivers/media/platform/amphion/venc.c @@ -13,14 +13,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include "vpu.h" #include "vpu_defs.h" #include "vpu_core.h" @@ -844,7 +842,7 @@ static int venc_get_encoded_frames(struct vpu_inst *inst) v4l2_m2m_dst_buf_remove(inst->fh.m2m_ctx))) break; list_del_init(&frame->list); - vfree(frame); + kfree(frame); } return 0; @@ -860,7 +858,7 @@ static int venc_frame_encoded(struct vpu_inst *inst, void *arg) if (!info) return -EINVAL; venc = inst->priv; - frame = vzalloc(sizeof(*frame)); + frame = kzalloc(sizeof(*frame), GFP_KERNEL); if (!frame) return -ENOMEM; @@ -912,9 +910,9 @@ static void venc_cleanup(struct vpu_inst *inst) return; venc = inst->priv; - vfree(venc); + kfree(venc); inst->priv = NULL; - vfree(inst); + kfree(inst); } static int venc_start_session(struct vpu_inst *inst, u32 type) @@ -1067,7 +1065,7 @@ static void venc_cleanup_frames(struct venc_t *venc) list_for_each_entry_safe(frame, tmp, &venc->frames, list) { list_del_init(&frame->list); - vfree(frame); + kfree(frame); } } @@ -1151,7 +1149,7 @@ static int venc_process_capture(struct vpu_inst *inst, struct vb2_buffer *vb) return ret; list_del_init(&frame->list); - vfree(frame); + kfree(frame); return 0; } @@ -1309,13 +1307,13 @@ static int venc_open(struct file *file) struct venc_t *venc; int ret; - inst = vzalloc(sizeof(*inst)); + inst = kzalloc(sizeof(*inst), GFP_KERNEL); if (!inst) return -ENOMEM; - venc = vzalloc(sizeof(*venc)); + venc = kzalloc(sizeof(*venc), GFP_KERNEL); if (!venc) { - vfree(inst); + kfree(inst); return -ENOMEM; } diff --git a/drivers/media/platform/amphion/vpu_cmds.c b/drivers/media/platform/amphion/vpu_cmds.c index 5695f5c1cb3e..ab69412e0aa7 100644 --- a/drivers/media/platform/amphion/vpu_cmds.c +++ b/drivers/media/platform/amphion/vpu_cmds.c @@ -13,7 +13,6 @@ #include #include #include -#include #include "vpu.h" #include "vpu_defs.h" #include "vpu_cmds.h" @@ -84,13 +83,13 @@ static struct vpu_cmd_t *vpu_alloc_cmd(struct vpu_inst *inst, u32 id, void *data int i; int ret; - cmd = vzalloc(sizeof(*cmd)); + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); if (!cmd) return NULL; - cmd->pkt = vzalloc(sizeof(*cmd->pkt)); + cmd->pkt = kzalloc(sizeof(*cmd->pkt), GFP_KERNEL); if (!cmd->pkt) { - vfree(cmd); + kfree(cmd); return NULL; } @@ -98,8 +97,8 @@ static struct vpu_cmd_t *vpu_alloc_cmd(struct vpu_inst *inst, u32 id, void *data ret = vpu_iface_pack_cmd(inst->core, cmd->pkt, inst->id, id, data); if (ret) { dev_err(inst->dev, "iface pack cmd %s fail\n", vpu_id_name(id)); - vfree(cmd->pkt); - vfree(cmd); + kfree(cmd->pkt); + kfree(cmd); return NULL; } for (i = 0; i < ARRAY_SIZE(vpu_cmd_requests); i++) { @@ -118,8 +117,8 @@ static void vpu_free_cmd(struct vpu_cmd_t *cmd) return; if (cmd->last_response_cmd) atomic_long_set(cmd->last_response_cmd, cmd->key); - vfree(cmd->pkt); - vfree(cmd); + kfree(cmd->pkt); + kfree(cmd); } static int vpu_session_process_cmd(struct vpu_inst *inst, struct vpu_cmd_t *cmd) diff --git a/drivers/media/platform/amphion/vpu_core.c b/drivers/media/platform/amphion/vpu_core.c index 168f0514851e..85cc4a14f8ed 100644 --- a/drivers/media/platform/amphion/vpu_core.c +++ b/drivers/media/platform/amphion/vpu_core.c @@ -17,7 +17,6 @@ #include #include #include -#include #include "vpu.h" #include "vpu_defs.h" #include "vpu_core.h" @@ -265,7 +264,7 @@ static int vpu_core_register(struct device *dev, struct vpu_core *core) INIT_WORK(&core->msg_work, vpu_msg_run_work); INIT_DELAYED_WORK(&core->msg_delayed_work, vpu_msg_delayed_work); buffer_size = roundup_pow_of_two(VPU_MSG_BUFFER_SIZE); - core->msg_buffer = vzalloc(buffer_size); + core->msg_buffer = kzalloc(buffer_size, GFP_KERNEL); if (!core->msg_buffer) { dev_err(core->dev, "failed allocate buffer for fifo\n"); ret = -ENOMEM; @@ -282,10 +281,8 @@ static int vpu_core_register(struct device *dev, struct vpu_core *core) return 0; error: - if (core->msg_buffer) { - vfree(core->msg_buffer); - core->msg_buffer = NULL; - } + kfree(core->msg_buffer); + core->msg_buffer = NULL; if (core->workqueue) { destroy_workqueue(core->workqueue); core->workqueue = NULL; @@ -308,7 +305,7 @@ static int vpu_core_unregister(struct device *dev, struct vpu_core *core) vpu_core_put_vpu(core); core->vpu = NULL; - vfree(core->msg_buffer); + kfree(core->msg_buffer); core->msg_buffer = NULL; if (core->workqueue) { From 5633ec763a2a18cef6c5ac9250e4f4b8786e7999 Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Tue, 23 Dec 2025 14:27:52 +0800 Subject: [PATCH 061/307] media: amphion: Drop min_queued_buffers assignment The min_queued_buffers field controls when start_streaming() is called by the vb2 core (it delays the callback until at least N buffers are queued). Setting it to 1 affects the timing of start_streaming(), which breaks the seek flow in decoder scenarios and causes test failures. The current driver implementation does not rely on this minimum buffer requirement and handles streaming start correctly with the default value of 0, so remove these assignments. Fixes: 3cd084519c6f ("media: amphion: add vpu v4l2 m2m support") Cc: stable@vger.kernel.org Signed-off-by: Ming Qian Reviewed-by: Nicolas Dufresne Reviewed-by: Frank Li Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/amphion/vpu_v4l2.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/media/platform/amphion/vpu_v4l2.c b/drivers/media/platform/amphion/vpu_v4l2.c index 121165a7184f..64fc88d89ccc 100644 --- a/drivers/media/platform/amphion/vpu_v4l2.c +++ b/drivers/media/platform/amphion/vpu_v4l2.c @@ -669,7 +669,6 @@ static int vpu_m2m_queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_q src_vq->mem_ops = &vb2_vmalloc_memops; src_vq->drv_priv = inst; src_vq->buf_struct_size = sizeof(struct vpu_vb2_buffer); - src_vq->min_queued_buffers = 1; src_vq->dev = inst->vpu->dev; src_vq->lock = &inst->lock; ret = vb2_queue_init(src_vq); @@ -686,7 +685,6 @@ static int vpu_m2m_queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_q dst_vq->mem_ops = &vb2_vmalloc_memops; dst_vq->drv_priv = inst; dst_vq->buf_struct_size = sizeof(struct vpu_vb2_buffer); - dst_vq->min_queued_buffers = 1; dst_vq->dev = inst->vpu->dev; dst_vq->lock = &inst->lock; ret = vb2_queue_init(dst_vq); From 81f8e0e6a2e115df9274d0289779f8fca694479c Mon Sep 17 00:00:00 2001 From: Alper Ak Date: Sat, 27 Dec 2025 11:40:37 +0300 Subject: [PATCH 062/307] media: rockchip: rga: Fix possible ERR_PTR dereference in rga_buf_init() rga_get_frame() can return ERR_PTR(-EINVAL) when buffer type is unsupported or invalid. rga_buf_init() does not check the return value and unconditionally dereferences the pointer when accessing f->size. Add proper ERR_PTR checking and return the error to prevent dereferencing an invalid pointer. Fixes: 6040702ade23 ("media: rockchip: rga: allocate DMA descriptors per buffer") Cc: stable@vger.kernel.org Signed-off-by: Alper Ak Reviewed-by: Michael Tretter Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/rockchip/rga/rga-buf.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/platform/rockchip/rga/rga-buf.c b/drivers/media/platform/rockchip/rga/rga-buf.c index 730bdf98565a..bb575873f2b2 100644 --- a/drivers/media/platform/rockchip/rga/rga-buf.c +++ b/drivers/media/platform/rockchip/rga/rga-buf.c @@ -80,6 +80,9 @@ static int rga_buf_init(struct vb2_buffer *vb) struct rga_frame *f = rga_get_frame(ctx, vb->vb2_queue->type); size_t n_desc = 0; + if (IS_ERR(f)) + return PTR_ERR(f); + n_desc = DIV_ROUND_UP(f->size, PAGE_SIZE); rbuf->n_desc = n_desc; From c5760e35d3a4b80c5e557a65d78b3bf6139f9419 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 8 Jan 2026 16:23:21 +0100 Subject: [PATCH 063/307] media: mediatek: vcodec: Discard pm_runtime_put() return value Printing error messages on pm_runtime_put() returning negative values is not particularly useful. Returning an error code from pm_runtime_put() merely means that it has not queued up a work item to check whether or not the device can be suspended and there are many perfectly valid situations in which that can happen, like after writing "on" to the devices' runtime PM "control" attribute in sysfs for one example. Accordingly, update mtk_vcodec_enc_pw_off() and mtk_vcodec_dec_pw_off() to simply discard the return value of pm_runtime_put(). This will facilitate a planned change of the pm_runtime_put() return type to void in the future. Signed-off-by: Rafael J. Wysocki Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../platform/mediatek/vcodec/decoder/mtk_vcodec_dec_pm.c | 6 +----- .../platform/mediatek/vcodec/encoder/mtk_vcodec_enc_pm.c | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_pm.c b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_pm.c index aefd3e9e3061..f6f03c61c643 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_pm.c +++ b/drivers/media/platform/mediatek/vcodec/decoder/mtk_vcodec_dec_pm.c @@ -67,11 +67,7 @@ static int mtk_vcodec_dec_pw_on(struct mtk_vcodec_pm *pm) static void mtk_vcodec_dec_pw_off(struct mtk_vcodec_pm *pm) { - int ret; - - ret = pm_runtime_put(pm->dev); - if (ret && ret != -EAGAIN) - dev_err(pm->dev, "pm_runtime_put fail %d", ret); + pm_runtime_put(pm->dev); } static void mtk_vcodec_dec_clock_on(struct mtk_vcodec_pm *pm) diff --git a/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_pm.c b/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_pm.c index 1a2b14a3e219..edf9133c0673 100644 --- a/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_pm.c +++ b/drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc_pm.c @@ -71,11 +71,7 @@ int mtk_vcodec_enc_pw_on(struct mtk_vcodec_pm *pm) void mtk_vcodec_enc_pw_off(struct mtk_vcodec_pm *pm) { - int ret; - - ret = pm_runtime_put(pm->dev); - if (ret && ret != -EAGAIN) - dev_err(pm->dev, "pm_runtime_put fail %d", ret); + pm_runtime_put(pm->dev); } void mtk_vcodec_enc_clock_on(struct mtk_vcodec_pm *pm) From 1c1b79f40ee4444fa1ac96079751608b724c6b2b Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Thu, 8 Jan 2026 14:29:46 +0100 Subject: [PATCH 064/307] media: verisilicon: AV1: Set IDR flag for intra_only frame type Intra_only frame could be considered as a key frame so Instantaneous Decoding Refresh (IDR) flag must be set of the both case and not only for key frames. Signed-off-by: Benjamin Gaignard Reported-by: Jianfeng Liu Fixes: 727a400686a2c ("media: verisilicon: Add Rockchip AV1 decoder") Cc: stable@vger.kernel.org Reviewed-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c index f52b8208e6b9..500e94bcb029 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c @@ -2018,7 +2018,7 @@ static void rockchip_vpu981_av1_dec_set_parameters(struct hantro_ctx *ctx) !!(ctrls->frame->quantization.flags & V4L2_AV1_QUANTIZATION_FLAG_DELTA_Q_PRESENT)); - hantro_reg_write(vpu, &av1_idr_pic_e, !ctrls->frame->frame_type); + hantro_reg_write(vpu, &av1_idr_pic_e, IS_INTRA(ctrls->frame->frame_type)); hantro_reg_write(vpu, &av1_quant_base_qindex, ctrls->frame->quantization.base_q_idx); hantro_reg_write(vpu, &av1_bit_depth_y_minus8, ctx->bit_depth - 8); hantro_reg_write(vpu, &av1_bit_depth_c_minus8, ctx->bit_depth - 8); From 56a8676456e1eb6882ec90e3544b3622979c0a11 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Wed, 29 Oct 2025 12:06:23 +0100 Subject: [PATCH 065/307] MAINTAINERS: adjust file entry in MEDIATEK MDP DRIVER Commit fbf97d6c1dd4 ("dt-bindings: media: Convert MediaTek mt8173-mdp bindings to DT schema") renames mediatek-mdp.txt to mediatek,mt8173-mdp.yaml as part of this dt-binding conversion, but misses to adjust the file entry in MEDIATEK MDP DRIVER. Adjust the file entry after the conversion. Signed-off-by: Lukas Bulwahn Reviewed-by: Hans Verkuil Signed-off-by: Hans Verkuil --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 752ccaea38b0..c1a8c25eec83 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16033,7 +16033,7 @@ M: Minghsiu Tsai M: Houlong Wei M: Andrew-CT Chen S: Supported -F: Documentation/devicetree/bindings/media/mediatek-mdp.txt +F: Documentation/devicetree/bindings/media/mediatek,mt8173-mdp.yaml F: drivers/media/platform/mediatek/mdp/ F: drivers/media/platform/mediatek/vpu/ From ceff18405f6bd7cf87bd4f47648d902ca9fc0e01 Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Fri, 31 Oct 2025 22:26:45 +0530 Subject: [PATCH 066/307] media: saa6588: Remove dprintk macro and use v4l2_info() The existing 'dprintk' macro used an unwrapped 'if' statement which was flagged by checkpatch, but instead of wrapping it, the debug handling can be simplified. This patch removes the 'dprintk' macro entirely and replaces all its usages with v4l2_info() helper. The unused 'PREFIX' macro is also removed. Signed-off-by: Shrikant Raskar Signed-off-by: Hans Verkuil --- drivers/media/i2c/saa6588.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/drivers/media/i2c/saa6588.c b/drivers/media/i2c/saa6588.c index fb09e4560d8a..90ae4121a68a 100644 --- a/drivers/media/i2c/saa6588.c +++ b/drivers/media/i2c/saa6588.c @@ -49,8 +49,6 @@ MODULE_LICENSE("GPL"); /* ---------------------------------------------------------------------- */ #define UNSET (-1U) -#define PREFIX "saa6588: " -#define dprintk if (debug) printk struct saa6588 { struct v4l2_subdev sd; @@ -144,14 +142,14 @@ static bool block_from_buf(struct saa6588 *s, unsigned char *buf) if (s->rd_index == s->wr_index) { if (debug > 2) - dprintk(PREFIX "Read: buffer empty.\n"); + v4l2_info(&s->sd, "Read: buffer empty.\n"); return false; } if (debug > 2) { - dprintk(PREFIX "Read: "); + v4l2_info(&s->sd, "Read: "); for (i = s->rd_index; i < s->rd_index + 3; i++) - dprintk("0x%02x ", s->buffer[i]); + v4l2_info(&s->sd, "0x%02x ", s->buffer[i]); } memcpy(buf, &s->buffer[s->rd_index], 3); @@ -162,7 +160,7 @@ static bool block_from_buf(struct saa6588 *s, unsigned char *buf) s->block_count--; if (debug > 2) - dprintk("%d blocks total.\n", s->block_count); + v4l2_info(&s->sd, "%d blocks total.\n", s->block_count); return true; } @@ -222,11 +220,11 @@ static void block_to_buf(struct saa6588 *s, unsigned char *blockbuf) unsigned int i; if (debug > 3) - dprintk(PREFIX "New block: "); + v4l2_info(&s->sd, "New block: "); for (i = 0; i < 3; ++i) { if (debug > 3) - dprintk("0x%02x ", blockbuf[i]); + v4l2_info(&s->sd, "0x%02x ", blockbuf[i]); s->buffer[s->wr_index] = blockbuf[i]; s->wr_index++; } @@ -242,7 +240,7 @@ static void block_to_buf(struct saa6588 *s, unsigned char *blockbuf) s->block_count++; if (debug > 3) - dprintk("%d blocks total.\n", s->block_count); + v4l2_info(&s->sd, "%d blocks total.\n", s->block_count); } static void saa6588_i2c_poll(struct saa6588 *s) @@ -257,7 +255,7 @@ static void saa6588_i2c_poll(struct saa6588 *s) SAA6588 returns garbage otherwise. */ if (6 != i2c_master_recv(client, &tmpbuf[0], 6)) { if (debug > 1) - dprintk(PREFIX "read error!\n"); + v4l2_info(&s->sd, "read error!\n"); return; } @@ -267,7 +265,7 @@ static void saa6588_i2c_poll(struct saa6588 *s) blocknum = tmpbuf[0] >> 5; if (blocknum == s->last_blocknum) { if (debug > 3) - dprintk("Saw block %d again.\n", blocknum); + v4l2_info(&s->sd, "Saw block %d again.\n", blocknum); return; } @@ -370,12 +368,13 @@ static void saa6588_configure(struct saa6588 *s) break; } - dprintk(PREFIX "writing: 0w=0x%02x 1w=0x%02x 2w=0x%02x\n", - buf[0], buf[1], buf[2]); + if (debug) + v4l2_info(&s->sd, "writing: 0w=0x%02x 1w=0x%02x 2w=0x%02x\n", + buf[0], buf[1], buf[2]); rc = i2c_master_send(client, buf, 3); if (rc != 3) - printk(PREFIX "i2c i/o error: rc == %d (should be 3)\n", rc); + v4l2_info(&s->sd, "i2c i/o error: rc == %d (should be 3)\n", rc); } /* ---------------------------------------------------------------------- */ From 42bf68dfef7e737933674bd56e1159950247b094 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Sat, 1 Nov 2025 12:08:00 +0300 Subject: [PATCH 067/307] media: v4l2-core: simplify v4l2_m2m_register_entity() Avoid 'strlen()'/'kmalloc()'/'snprintf()' sequence by using the convenient 'kasprintf()' in 'v4l2_m2m_register_entity()'. Signed-off-by: Dmitry Antipov Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-mem2mem.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c index ae0de54d4c3e..b661f483dad3 100644 --- a/drivers/media/v4l2-core/v4l2-mem2mem.c +++ b/drivers/media/v4l2-core/v4l2-mem2mem.c @@ -1037,8 +1037,6 @@ static int v4l2_m2m_register_entity(struct media_device *mdev, { struct media_entity *entity; struct media_pad *pads; - char *name; - unsigned int len; int num_pads; int ret; @@ -1071,12 +1069,10 @@ static int v4l2_m2m_register_entity(struct media_device *mdev, entity->info.dev.major = VIDEO_MAJOR; entity->info.dev.minor = vdev->minor; } - len = strlen(vdev->name) + 2 + strlen(m2m_entity_name[type]); - name = kmalloc(len, GFP_KERNEL); - if (!name) + entity->name = kasprintf(GFP_KERNEL, "%s-%s", vdev->name, + m2m_entity_name[type]); + if (!entity->name) return -ENOMEM; - snprintf(name, len, "%s-%s", vdev->name, m2m_entity_name[type]); - entity->name = name; entity->function = function; ret = media_entity_pads_init(entity, num_pads, pads); From 43e5302d22334f1183dec3e0d5d8007eefe2817c Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Fri, 14 Nov 2025 09:12:57 +0000 Subject: [PATCH 068/307] media: tegra-video: Fix memory leak in __tegra_channel_try_format() The state object allocated by __v4l2_subdev_state_alloc() must be freed with __v4l2_subdev_state_free() when it is no longer needed. In __tegra_channel_try_format(), two error paths return directly after v4l2_subdev_call() fails, without freeing the allocated 'sd_state' object. This violates the requirement and causes a memory leak. Fix this by introducing a cleanup label and using goto statements in the error paths to ensure that __v4l2_subdev_state_free() is always called before the function returns. Fixes: 56f64b82356b7 ("media: tegra-video: Use zero crop settings if subdev has no get_selection") Fixes: 1ebaeb09830f3 ("media: tegra-video: Add support for external sensor capture") Cc: stable@vger.kernel.org Signed-off-by: Zilin Guan Signed-off-by: Hans Verkuil --- drivers/staging/media/tegra-video/vi.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c index c9276ff76157..14b327afe045 100644 --- a/drivers/staging/media/tegra-video/vi.c +++ b/drivers/staging/media/tegra-video/vi.c @@ -438,7 +438,7 @@ static int __tegra_channel_try_format(struct tegra_vi_channel *chan, .target = V4L2_SEL_TGT_CROP_BOUNDS, }; struct v4l2_rect *try_crop; - int ret; + int ret = 0; subdev = tegra_channel_get_remote_source_subdev(chan); if (!subdev) @@ -482,8 +482,10 @@ static int __tegra_channel_try_format(struct tegra_vi_channel *chan, } else { ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, &sdsel); - if (ret) - return -EINVAL; + if (ret) { + ret = -EINVAL; + goto out_free; + } try_crop->width = sdsel.r.width; try_crop->height = sdsel.r.height; @@ -495,14 +497,15 @@ static int __tegra_channel_try_format(struct tegra_vi_channel *chan, ret = v4l2_subdev_call(subdev, pad, set_fmt, sd_state, &fmt); if (ret < 0) - return ret; + goto out_free; v4l2_fill_pix_format(pix, &fmt.format); chan->vi->ops->vi_fmt_align(pix, fmtinfo->bpp); +out_free: __v4l2_subdev_state_free(sd_state); - return 0; + return ret; } static int tegra_channel_try_format(struct file *file, void *fh, From e83f8dd668eaf18f408169e1006f61dc5ebaa00e Mon Sep 17 00:00:00 2001 From: Jammy Huang Date: Mon, 24 Nov 2025 11:05:14 +0800 Subject: [PATCH 069/307] media: aspeed: Fix dram hang at res-change Dram hang could happen in the steps below: 1. start capture/compression 2. out-of-lock watchdog raise irq because of res-change. 3. aspeed_video_irq_res_change do clk-off At step3, capture/compression could be not accomplished yet. If clk-off in the middle of video operation, dram controller could hang at ast2500. Use reset rather than clk-off/on to avoid this problem. Signed-off-by: Jammy Huang Signed-off-by: Hans Verkuil --- drivers/media/platform/aspeed/aspeed-video.c | 22 +++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/aspeed/aspeed-video.c b/drivers/media/platform/aspeed/aspeed-video.c index b83e43245277..41cb96f60110 100644 --- a/drivers/media/platform/aspeed/aspeed-video.c +++ b/drivers/media/platform/aspeed/aspeed-video.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -310,6 +311,7 @@ struct aspeed_video { void __iomem *base; struct clk *eclk; struct clk *vclk; + struct reset_control *reset; struct device *dev; struct v4l2_ctrl_handler ctrl_handler; @@ -720,6 +722,13 @@ static void aspeed_video_on(struct aspeed_video *video) set_bit(VIDEO_CLOCKS_ON, &video->flags); } +static void aspeed_video_reset(struct aspeed_video *v) +{ + reset_control_assert(v->reset); + usleep_range(100, 150); + reset_control_deassert(v->reset); +} + static void aspeed_video_bufs_done(struct aspeed_video *video, enum vb2_buffer_state state) { @@ -742,7 +751,9 @@ static void aspeed_video_irq_res_change(struct aspeed_video *video, ulong delay) video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; - aspeed_video_off(video); + aspeed_video_write(video, VE_INTERRUPT_CTRL, 0); + aspeed_video_write(video, VE_INTERRUPT_STATUS, 0xffffffff); + aspeed_video_reset(video); aspeed_video_bufs_done(video, VB2_BUF_STATE_ERROR); schedule_delayed_work(&video->res_work, delay); @@ -1984,8 +1995,7 @@ static void aspeed_video_stop_streaming(struct vb2_queue *q) * Need to force stop any DMA and try and get HW into a good * state for future calls to start streaming again. */ - aspeed_video_off(video); - aspeed_video_on(video); + aspeed_video_reset(video); aspeed_video_init_regs(video); @@ -2230,6 +2240,12 @@ static int aspeed_video_init(struct aspeed_video *video) } dev_info(video->dev, "irq %d\n", irq); + video->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(video->reset)) { + dev_err(dev, "Unable to get reset\n"); + return PTR_ERR(video->reset); + } + video->eclk = devm_clk_get(dev, "eclk"); if (IS_ERR(video->eclk)) { dev_err(dev, "Unable to get ECLK\n"); From 8e2421e1977ab594011849bbfe028f81ed2ab886 Mon Sep 17 00:00:00 2001 From: Michael Tretter Date: Tue, 25 Nov 2025 15:29:54 +0100 Subject: [PATCH 070/307] media: dt-bindings: adi,adv7180: add VPP and CSI register maps Different variants of the ADV7280 chip have up to three register maps. The availability of the CSI and VPP register maps depends on the chip variant. The address of the additional register maps depends on the board design and other chips on the I2C but. They may be programmed via registers in the main register map. Allow to specify the addresses of the VPP and CSI register maps in the device tree to solve I2C address conflicts on a board level. The CSI and VPP register maps are always optional to allow backwards compatibility with existing device trees which may rely on the default address. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Michael Tretter Signed-off-by: Hans Verkuil --- .../bindings/media/i2c/adi,adv7180.yaml | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/media/i2c/adi,adv7180.yaml b/Documentation/devicetree/bindings/media/i2c/adi,adv7180.yaml index dee8ce7cb7ba..5f8f3b3dea76 100644 --- a/Documentation/devicetree/bindings/media/i2c/adi,adv7180.yaml +++ b/Documentation/devicetree/bindings/media/i2c/adi,adv7180.yaml @@ -30,7 +30,27 @@ properties: - adi,adv7282-m reg: - maxItems: 1 + minItems: 1 + items: + - description: main register map + - description: VPP or CSI register map + - description: CSI register map + description: + The ADV7180 family may have up to three register maps. All chips have + the main register map. The availability of the CSI and VPP register maps + depends on the chip variant. + + The addresses of the CSI and VPP register maps are programmable by + software. They depend on the board layout and other devices on the I2C + bus and are determined by the hardware designer to avoid address + conflicts on the I2C bus. + + reg-names: + minItems: 1 + items: + - const: main + - enum: [ csi, vpp ] + - const: csi powerdown-gpios: maxItems: 1 @@ -138,6 +158,62 @@ allOf: required: - ports + - if: + properties: + compatible: + contains: + enum: + - adi,adv7180 + - adi,adv7180cp + - adi,adv7180st + - adi,adv7182 + then: + properties: + reg: + maxItems: 1 + + reg-names: + maxItems: 1 + + - if: + properties: + compatible: + contains: + enum: + - adi,adv7281 + - adi,adv7281-m + - adi,adv7281-ma + then: + properties: + reg: + minItems: 1 + maxItems: 2 + + reg-names: + minItems: 1 + items: + - const: main + - const: csi + + - if: + properties: + compatible: + contains: + enum: + - adi,adv7280 + - adi,adv7282 + then: + properties: + reg: + minItems: 1 + maxItems: 2 + + reg-names: + minItems: 1 + items: + - const: main + - const: vpp + examples: - | i2c { @@ -187,3 +263,22 @@ examples: }; }; }; + + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + composite-in@20 { + compatible = "adi,adv7280-m"; + reg = <0x20>, <0x42>, <0x44>; + reg-names = "main", "vpp", "csi"; + + port { + adv7280_out: endpoint { + bus-width = <8>; + remote-endpoint = <&vin1ep>; + }; + }; + }; + }; From 57b96d4a23c7088127fbd228750bd0b6a1686169 Mon Sep 17 00:00:00 2001 From: Thorsten Schmelzer Date: Tue, 25 Nov 2025 15:29:55 +0100 Subject: [PATCH 071/307] media: adv7180: add support for ancillary devices Depending on other devices on the i2c bus, using a non-default address for the CSI and VPP devices may be necessary. Replace calls to i2c_new_dummy_device with i2c_new_ancillary_device, which can directly use an i2c address from the device tree. Program the actual addresses of the sub-devices when configuring the chip. Signed-off-by: Thorsten Schmelzer Signed-off-by: Michael Tretter Signed-off-by: Hans Verkuil --- drivers/media/i2c/adv7180.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c index 378f4e6af12c..4152f2049a6d 100644 --- a/drivers/media/i2c/adv7180.c +++ b/drivers/media/i2c/adv7180.c @@ -1066,13 +1066,13 @@ static int adv7180_select_input(struct adv7180_state *state, unsigned int input) static int adv7182_init(struct adv7180_state *state) { - if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) + if (state->csi_client) adv7180_write(state, ADV7180_REG_CSI_SLAVE_ADDR, - ADV7180_DEFAULT_CSI_I2C_ADDR << 1); + state->csi_client->addr << 1); - if (state->chip_info->flags & ADV7180_FLAG_I2P) + if (state->vpp_client) adv7180_write(state, ADV7180_REG_VPP_SLAVE_ADDR, - ADV7180_DEFAULT_VPP_I2C_ADDR << 1); + state->vpp_client->addr << 1); if (state->chip_info->flags & ADV7180_FLAG_V2) { /* ADI recommended writes for improved video quality */ @@ -1443,15 +1443,17 @@ static int adv7180_probe(struct i2c_client *client) state->force_bt656_4 = true; if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) { - state->csi_client = i2c_new_dummy_device(client->adapter, - ADV7180_DEFAULT_CSI_I2C_ADDR); + state->csi_client = + i2c_new_ancillary_device(client, "csi", + ADV7180_DEFAULT_CSI_I2C_ADDR); if (IS_ERR(state->csi_client)) return PTR_ERR(state->csi_client); } if (state->chip_info->flags & ADV7180_FLAG_I2P) { - state->vpp_client = i2c_new_dummy_device(client->adapter, - ADV7180_DEFAULT_VPP_I2C_ADDR); + state->vpp_client = + i2c_new_ancillary_device(client, "vpp", + ADV7180_DEFAULT_VPP_I2C_ADDR); if (IS_ERR(state->vpp_client)) { ret = PTR_ERR(state->vpp_client); goto err_unregister_csi_client; From 2473b46c480d53e6b20d29b4d497a88747e3e280 Mon Sep 17 00:00:00 2001 From: Thorsten Schmelzer Date: Tue, 25 Nov 2025 15:29:56 +0100 Subject: [PATCH 072/307] media: adv7180: implement g_register and s_register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The g_register and s_register callbacks are useful for debugging the adv7180. Implement the callbacks to expose the register debugging to userspace. Signed-off-by: Thorsten Schmelzer Reviewed-by: Niklas Söderlund Signed-off-by: Michael Tretter Signed-off-by: Hans Verkuil --- drivers/media/i2c/adv7180.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c index 4152f2049a6d..d289cbc2eefd 100644 --- a/drivers/media/i2c/adv7180.c +++ b/drivers/media/i2c/adv7180.c @@ -969,6 +969,32 @@ static int adv7180_subscribe_event(struct v4l2_subdev *sd, } } +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int adv7180_g_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct adv7180_state *state = to_state(sd); + int ret; + + ret = adv7180_read(state, reg->reg); + if (ret < 0) + return ret; + + reg->val = ret; + reg->size = 1; + + return 0; +} + +static int adv7180_s_register(struct v4l2_subdev *sd, + const struct v4l2_dbg_register *reg) +{ + struct adv7180_state *state = to_state(sd); + + return adv7180_write(state, reg->reg, reg->val); +} +#endif + static const struct v4l2_subdev_video_ops adv7180_video_ops = { .s_std = adv7180_s_std, .g_std = adv7180_g_std, @@ -982,6 +1008,10 @@ static const struct v4l2_subdev_video_ops adv7180_video_ops = { static const struct v4l2_subdev_core_ops adv7180_core_ops = { .subscribe_event = adv7180_subscribe_event, .unsubscribe_event = v4l2_event_subdev_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = adv7180_g_register, + .s_register = adv7180_s_register, +#endif }; static const struct v4l2_subdev_pad_ops adv7180_pad_ops = { From 90289b67c5c1d4c18784059b27460d292e16d208 Mon Sep 17 00:00:00 2001 From: Thorsten Schmelzer Date: Tue, 25 Nov 2025 15:29:57 +0100 Subject: [PATCH 073/307] media: adv7180: fix frame interval in progressive mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ADV7280-M may internally convert interlaced video input to progressive video. If this mode is enabled, the ADV7280-M delivers progressive video frames at the field rate of 50 fields per second (PAL) or 60 fields per second (NTSC). Fix the reported frame interval if progressive video is enabled. Signed-off-by: Thorsten Schmelzer Reviewed-by: Niklas Söderlund Signed-off-by: Michael Tretter Signed-off-by: Hans Verkuil --- drivers/media/i2c/adv7180.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c index d289cbc2eefd..669b0b3165b1 100644 --- a/drivers/media/i2c/adv7180.c +++ b/drivers/media/i2c/adv7180.c @@ -507,6 +507,13 @@ static int adv7180_get_frame_interval(struct v4l2_subdev *sd, fi->interval.denominator = 25; } + /* + * If the de-interlacer is active, the chip produces full video frames + * at the field rate. + */ + if (state->field == V4L2_FIELD_NONE) + fi->interval.denominator *= 2; + return 0; } From b8bf939d77c0cd01118e953bbf554e0fa15e9006 Mon Sep 17 00:00:00 2001 From: Shaurya Rane Date: Thu, 27 Nov 2025 00:34:10 +0530 Subject: [PATCH 074/307] media: radio-keene: fix memory leak in error path Fix a memory leak in usb_keene_probe(). The v4l2 control handler is initialized and controls are added, but if v4l2_device_register() or video_register_device() fails afterward, the handler was never freed, leaking memory. Add v4l2_ctrl_handler_free() call in the err_v4l2 error path to ensure the control handler is properly freed for all error paths after it is initialized. Reported-by: syzbot+a41b73dce23962a74c72@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a41b73dce23962a74c72 Fixes: 1bf20c3a0c61 ("[media] radio-keene: add a driver for the Keene FM Transmitter") Cc: stable@vger.kernel.org Signed-off-by: Shaurya Rane Signed-off-by: Hans Verkuil --- drivers/media/radio/radio-keene.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/radio/radio-keene.c b/drivers/media/radio/radio-keene.c index f3b57f0cb1ec..c133305fd019 100644 --- a/drivers/media/radio/radio-keene.c +++ b/drivers/media/radio/radio-keene.c @@ -338,7 +338,6 @@ static int usb_keene_probe(struct usb_interface *intf, if (hdl->error) { retval = hdl->error; - v4l2_ctrl_handler_free(hdl); goto err_v4l2; } retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); @@ -384,6 +383,7 @@ static int usb_keene_probe(struct usb_interface *intf, err_vdev: v4l2_device_unregister(&radio->v4l2_dev); err_v4l2: + v4l2_ctrl_handler_free(&radio->hdl); kfree(radio->buffer); kfree(radio); err: From dbc527d980f7ba8559de38f8c1e4158c71a78915 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 10 Dec 2025 10:53:48 +0800 Subject: [PATCH 075/307] media: cx88: Add missing unmap in snd_cx88_hw_params() In error path, add cx88_alsa_dma_unmap() to release resource acquired by cx88_alsa_dma_map(). Fixes: b2c75abde0de ("[media] cx88: drop videobuf abuse in cx88-alsa") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Signed-off-by: Hans Verkuil --- drivers/media/pci/cx88/cx88-alsa.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/media/pci/cx88/cx88-alsa.c b/drivers/media/pci/cx88/cx88-alsa.c index 29fb1311e443..4e574d8390b4 100644 --- a/drivers/media/pci/cx88/cx88-alsa.c +++ b/drivers/media/pci/cx88/cx88-alsa.c @@ -483,8 +483,10 @@ static int snd_cx88_hw_params(struct snd_pcm_substream *substream, ret = cx88_risc_databuffer(chip->pci, &buf->risc, buf->sglist, chip->period_size, chip->num_periods, 1); - if (ret < 0) + if (ret < 0) { + cx88_alsa_dma_unmap(chip); goto error; + } /* Loop back to start of program */ buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); From 141c81849fab2ad4d6e3fdaff7cbaa873e8b5eb2 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 10 Dec 2025 11:02:17 +0800 Subject: [PATCH 076/307] media: cx23885: Add missing unmap in snd_cx23885_hw_params() In error path, add cx23885_alsa_dma_unmap() to release the resource acquired by cx23885_alsa_dma_map(). Fixes: 9529a4b0cf49 ("[media] cx23885: drop videobuf abuse in cx23885-alsa") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Signed-off-by: Hans Verkuil --- drivers/media/pci/cx23885/cx23885-alsa.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/media/pci/cx23885/cx23885-alsa.c b/drivers/media/pci/cx23885/cx23885-alsa.c index 25dc8d4dc5b7..717fc6c9ef21 100644 --- a/drivers/media/pci/cx23885/cx23885-alsa.c +++ b/drivers/media/pci/cx23885/cx23885-alsa.c @@ -392,8 +392,10 @@ static int snd_cx23885_hw_params(struct snd_pcm_substream *substream, ret = cx23885_risc_databuffer(chip->pci, &buf->risc, buf->sglist, chip->period_size, chip->num_periods, 1); - if (ret < 0) + if (ret < 0) { + cx23885_alsa_dma_unmap(chip); goto error; + } /* Loop back to start of program */ buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP|RISC_IRQ1|RISC_CNT_INC); From 863f50d583445c3c8b28a0fc4bb9c18fd9656f41 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Wed, 10 Dec 2025 16:52:30 +0800 Subject: [PATCH 077/307] media: cx25821: Add missing unmap in snd_cx25821_hw_params() In error path, add cx25821_alsa_dma_unmap() to release the resource acquired by cx25821_alsa_dma_map() Fixes: 8d8e6d6005de ("[media] cx28521: drop videobuf abuse in cx25821-alsa") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Signed-off-by: Hans Verkuil --- drivers/media/pci/cx25821/cx25821-alsa.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/pci/cx25821/cx25821-alsa.c b/drivers/media/pci/cx25821/cx25821-alsa.c index a42f0c03a7ca..f463365163b7 100644 --- a/drivers/media/pci/cx25821/cx25821-alsa.c +++ b/drivers/media/pci/cx25821/cx25821-alsa.c @@ -535,6 +535,7 @@ static int snd_cx25821_hw_params(struct snd_pcm_substream *substream, chip->period_size, chip->num_periods, 1); if (ret < 0) { pr_info("DEBUG: ERROR after cx25821_risc_databuffer_audio()\n"); + cx25821_alsa_dma_unmap(chip); goto error; } From a813338d910b49375018ebe960f912ca7672f88c Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Wed, 10 Dec 2025 19:00:06 +0300 Subject: [PATCH 078/307] media: synopsys: hdmirx: Detect broken interrupt Downstream version of RK3588 U-Boot uses customized TF-A that remaps HDMIRX hardware interrupt, routing it via firmware that isn't supported by upstream driver. Detect broken interrupt and print a clarifying error message about a need to use open-source TF-A with this driver. Reviewed-by: Sebastian Reichel Signed-off-by: Dmitry Osipenko Signed-off-by: Hans Verkuil --- .../platform/synopsys/hdmirx/snps_hdmirx.c | 90 ++++++++++++++++++- .../platform/synopsys/hdmirx/snps_hdmirx.h | 2 + 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c index c3007e09bc9f..9cceffa4ce25 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c @@ -132,12 +132,14 @@ struct snps_hdmirx_dev { struct delayed_work delayed_work_hotplug; struct delayed_work delayed_work_res_change; struct hdmirx_cec *cec; + struct mutex phy_rw_lock; /* to protect phy r/w configuration */ struct mutex stream_lock; /* to lock video stream capture */ struct mutex work_lock; /* to lock the critical section of hotplug event */ struct reset_control_bulk_data resets[HDMIRX_NUM_RST]; struct clk_bulk_data *clks; struct regmap *grf; struct regmap *vo1_grf; + struct completion cr_read_done; struct completion cr_write_done; struct completion timer_base_lock; struct completion avi_pkt_rcv; @@ -796,11 +798,50 @@ static int wait_reg_bit_status(struct snps_hdmirx_dev *hdmirx_dev, u32 reg, return 0; } +static int hdmirx_phy_register_read(struct snps_hdmirx_dev *hdmirx_dev, + u32 phy_reg, u32 *val) +{ + struct device *dev = hdmirx_dev->dev; + u32 status; + + guard(mutex)(&hdmirx_dev->phy_rw_lock); + + reinit_completion(&hdmirx_dev->cr_read_done); + /* clear irq status */ + hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_2_INT_CLEAR, 0xffffffff); + /* en irq */ + hdmirx_update_bits(hdmirx_dev, MAINUNIT_2_INT_MASK_N, + PHYCREG_CR_READ_DONE, PHYCREG_CR_READ_DONE); + /* write phy reg addr */ + hdmirx_writel(hdmirx_dev, PHYCREG_CONFIG1, phy_reg); + /* config read enable */ + hdmirx_writel(hdmirx_dev, PHYCREG_CONTROL, PHYCREG_CR_PARA_READ_P); + + if (!wait_for_completion_timeout(&hdmirx_dev->cr_read_done, + msecs_to_jiffies(20))) { + dev_err(dev, "%s wait cr read done failed\n", __func__); + return -ETIMEDOUT; + } + + /* read phy reg value */ + status = hdmirx_readl(hdmirx_dev, PHYCREG_STATUS); + if (!(status & PHYCREG_CR_PARA_DATAVALID)) { + dev_err(dev, "%s cr read failed\n", __func__); + return -EINVAL; + } + + *val = status & PHYCREG_CR_PARA_RD_DATA_MASK; + + return 0; +} + static int hdmirx_phy_register_write(struct snps_hdmirx_dev *hdmirx_dev, u32 phy_reg, u32 val) { struct device *dev = hdmirx_dev->dev; + guard(mutex)(&hdmirx_dev->phy_rw_lock); + reinit_completion(&hdmirx_dev->cr_write_done); /* clear irq status */ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_2_INT_CLEAR, 0xffffffff); @@ -1814,6 +1855,13 @@ static void mainunit_2_int_handler(struct snps_hdmirx_dev *hdmirx_dev, *handled = true; } + if (status & PHYCREG_CR_READ_DONE) { + hdmirx_update_bits(hdmirx_dev, MAINUNIT_2_INT_MASK_N, + PHYCREG_CR_READ_DONE, 0); + complete(&hdmirx_dev->cr_read_done); + *handled = true; + } + if (status & TMDSVALID_STABLE_CHG) { process_signal_change(hdmirx_dev); v4l2_dbg(2, debug, v4l2_dev, "%s: TMDSVALID_STABLE_CHG\n", __func__); @@ -2313,18 +2361,51 @@ static void hdmirx_disable_all_interrupts(struct snps_hdmirx_dev *hdmirx_dev) hdmirx_clear_interrupt(hdmirx_dev, CEC_INT_CLEAR, 0xffffffff); } -static void hdmirx_init(struct snps_hdmirx_dev *hdmirx_dev) +static int hdmirx_detect_broken_interrupt(struct snps_hdmirx_dev *hdmirx_dev) { + int ret; + u32 val; + + enable_irq(hdmirx_dev->hdmi_irq); + + hdmirx_writel(hdmirx_dev, PHYCREG_CONFIG0, 0x3); + + ret = hdmirx_phy_register_read(hdmirx_dev, + HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_FSM_CONFIG, + &val); + + disable_irq(hdmirx_dev->hdmi_irq); + + return ret; +} + +static int hdmirx_init(struct snps_hdmirx_dev *hdmirx_dev) +{ + int ret; + hdmirx_update_bits(hdmirx_dev, PHY_CONFIG, PHY_RESET | PHY_PDDQ, 0); regmap_write(hdmirx_dev->vo1_grf, VO1_GRF_VO1_CON2, (HDMIRX_SDAIN_MSK | HDMIRX_SCLIN_MSK) | ((HDMIRX_SDAIN_MSK | HDMIRX_SCLIN_MSK) << 16)); + + /* + * RK3588 downstream version of TF-A remaps HDMIRX interrupt and + * requires use of a vendor-specific FW API that we don't support + * in this driver. + */ + ret = hdmirx_detect_broken_interrupt(hdmirx_dev); + if (ret) + dev_err_probe(hdmirx_dev->dev, ret, + "interrupt not functioning, open-source TF-A is required by this driver\n"); + /* * Some interrupts are enabled by default, so we disable * all interrupts and clear interrupts status first. */ hdmirx_disable_all_interrupts(hdmirx_dev); + + return ret; } /* hdmi-4k-300mhz EDID produced by v4l2-ctl tool */ @@ -2606,10 +2687,12 @@ static int hdmirx_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(hdmirx_dev->regs), "failed to remap regs resource\n"); + mutex_init(&hdmirx_dev->phy_rw_lock); mutex_init(&hdmirx_dev->stream_lock); mutex_init(&hdmirx_dev->work_lock); spin_lock_init(&hdmirx_dev->rst_lock); + init_completion(&hdmirx_dev->cr_read_done); init_completion(&hdmirx_dev->cr_write_done); init_completion(&hdmirx_dev->timer_base_lock); init_completion(&hdmirx_dev->avi_pkt_rcv); @@ -2623,7 +2706,10 @@ static int hdmirx_probe(struct platform_device *pdev) hdmirx_dev->timings = cea640x480; hdmirx_enable(dev); - hdmirx_init(hdmirx_dev); + + ret = hdmirx_init(hdmirx_dev); + if (ret) + goto err_pm; v4l2_dev = &hdmirx_dev->v4l2_dev; strscpy(v4l2_dev->name, dev_name(dev), sizeof(v4l2_dev->name)); diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h index b13f58e31944..31b887e94e87 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h @@ -116,6 +116,8 @@ #define PHYCREG_CR_PARA_WRITE_P BIT(1) #define PHYCREG_CR_PARA_READ_P BIT(0) #define PHYCREG_STATUS 0x00f4 +#define PHYCREG_CR_PARA_DATAVALID BIT(24) +#define PHYCREG_CR_PARA_RD_DATA_MASK GENMASK(15, 0) #define MAINUNIT_STATUS 0x0150 #define TMDSVALID_STABLE_ST BIT(1) From 0fdf6323c35a134f206dcad5babb4ff488552076 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 11 Dec 2025 19:00:35 -0800 Subject: [PATCH 079/307] media: solo6x10: Check for out of bounds chip_id Clang with CONFIG_UBSAN_SHIFT=y noticed a condition where a signed type (literal "1" is an "int") could end up being shifted beyond 32 bits, so instrumentation was added (and due to the double is_tw286x() call seen via inlining), Clang decides the second one must now be undefined behavior and elides the rest of the function[1]. This is a known problem with Clang (that is still being worked on), but we can avoid the entire problem by actually checking the existing max chip ID, and now there is no runtime instrumentation added at all since everything is known to be within bounds. Additionally use an unsigned value for the shift to remove the instrumentation even without the explicit bounds checking. Link: https://github.com/ClangBuiltLinux/linux/issues/2144 [1] Suggested-by: Nathan Chancellor Signed-off-by: Kees Cook Signed-off-by: Hans Verkuil [hverkuil: fix checkpatch warning for is_tw286x] --- drivers/media/pci/solo6x10/solo6x10-tw28.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/media/pci/solo6x10/solo6x10-tw28.c b/drivers/media/pci/solo6x10/solo6x10-tw28.c index 1b7c22a9bc94..8f53946c6792 100644 --- a/drivers/media/pci/solo6x10/solo6x10-tw28.c +++ b/drivers/media/pci/solo6x10/solo6x10-tw28.c @@ -166,7 +166,7 @@ static const u8 tbl_tw2865_pal_template[] = { 0x64, 0x51, 0x40, 0xaf, 0xFF, 0xF0, 0x00, 0xC0, }; -#define is_tw286x(__solo, __id) (!(__solo->tw2815 & (1 << __id))) +#define is_tw286x(__solo, __id) (!((__solo)->tw2815 & (1U << (__id)))) static u8 tw_readbyte(struct solo_dev *solo_dev, int chip_id, u8 tw6x_off, u8 tw_off) @@ -686,6 +686,9 @@ int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, chip_num = ch / 4; ch %= 4; + if (chip_num >= TW_NUM_CHIP) + return -EINVAL; + if (val > 255 || val < 0) return -ERANGE; @@ -758,6 +761,9 @@ int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, chip_num = ch / 4; ch %= 4; + if (chip_num >= TW_NUM_CHIP) + return -EINVAL; + switch (ctrl) { case V4L2_CID_SHARPNESS: /* Only 286x has sharpness */ From 7422eca89d60d85b698e85b4627ba1d9bf49b1c0 Mon Sep 17 00:00:00 2001 From: Preyas Sharma Date: Sat, 20 Dec 2025 09:55:19 +0000 Subject: [PATCH 080/307] staging: media: av7110: use usleep_range in av7110_hw.c Use usleep_range instead of msleep to fix checkpatch warnings. Signed-off-by: Preyas Sharma Signed-off-by: Hans Verkuil [hverkuil: added commit message] --- drivers/staging/media/av7110/av7110_hw.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/staging/media/av7110/av7110_hw.c b/drivers/staging/media/av7110/av7110_hw.c index bf8e6dca40e5..49ce295771e4 100644 --- a/drivers/staging/media/av7110/av7110_hw.c +++ b/drivers/staging/media/av7110/av7110_hw.c @@ -312,7 +312,7 @@ int av7110_wait_msgstate(struct av7110 *av7110, u16 flags) pr_err("%s(): timeout waiting for MSGSTATE %04x\n", __func__, stat & flags); return -ETIMEDOUT; } - msleep(1); + usleep_range(1000, 2000); } return 0; } @@ -343,7 +343,7 @@ static int __av7110_send_fw_cmd(struct av7110 *av7110, u16 *buf, int length) av7110->arm_errors++; return -ETIMEDOUT; } - msleep(1); + usleep_range(1000, 2000); } if (FW_VERSION(av7110->arm_app) <= 0x261f) @@ -359,7 +359,7 @@ static int __av7110_send_fw_cmd(struct av7110 *av7110, u16 *buf, int length) pr_err("%s(): timeout waiting for HANDSHAKE_REG\n", __func__); return -ETIMEDOUT; } - msleep(1); + usleep_range(1000, 2000); } #endif @@ -405,7 +405,7 @@ static int __av7110_send_fw_cmd(struct av7110 *av7110, u16 *buf, int length) av7110->arm_errors++; return -ETIMEDOUT; } - msleep(1); + usleep_range(1000, 2000); } } @@ -433,7 +433,7 @@ static int __av7110_send_fw_cmd(struct av7110 *av7110, u16 *buf, int length) __func__, (buf[0] >> 8) & 0xff); return -ETIMEDOUT; } - msleep(1); + usleep_range(1000, 2000); } stat = rdebi(av7110, DEBINOSWAP, MSGSTATE, 0, 2); @@ -559,7 +559,7 @@ int av7110_fw_request(struct av7110 *av7110, u16 *request_buf, return -ETIMEDOUT; } #ifdef _NOHANDSHAKE - msleep(1); + usleep_range(1000, 2000); #endif } @@ -574,7 +574,7 @@ int av7110_fw_request(struct av7110 *av7110, u16 *request_buf, mutex_unlock(&av7110->dcomlock); return -ETIMEDOUT; } - msleep(1); + usleep_range(1000, 2000); } #endif @@ -719,7 +719,7 @@ static int FlushText(struct av7110 *av7110) mutex_unlock(&av7110->dcomlock); return -ETIMEDOUT; } - msleep(1); + usleep_range(1000, 2000); } mutex_unlock(&av7110->dcomlock); return 0; @@ -745,7 +745,7 @@ static int WriteText(struct av7110 *av7110, u8 win, u16 x, u16 y, char *buf) mutex_unlock(&av7110->dcomlock); return -ETIMEDOUT; } - msleep(1); + usleep_range(1000, 2000); } #ifndef _NOHANDSHAKE start = jiffies; @@ -758,7 +758,7 @@ static int WriteText(struct av7110 *av7110, u8 win, u16 x, u16 y, char *buf) mutex_unlock(&av7110->dcomlock); return -ETIMEDOUT; } - msleep(1); + usleep_range(1000, 2000); } #endif for (i = 0; i < length / 2; i++) From 63bf2f71712c458239f44bb25cf581a06f9770dc Mon Sep 17 00:00:00 2001 From: Preyas Sharma Date: Sat, 20 Dec 2025 09:55:21 +0000 Subject: [PATCH 081/307] staging: media: av7110: use usleep_range in sp8870.c Use usleep_range instead of udelay to fix checkpatch warning. Signed-off-by: Preyas Sharma Signed-off-by: Hans Verkuil [hverkuil: added commit message] --- drivers/staging/media/av7110/sp8870.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/media/av7110/sp8870.c b/drivers/staging/media/av7110/sp8870.c index 0c813860f5b2..93bf47a62e38 100644 --- a/drivers/staging/media/av7110/sp8870.c +++ b/drivers/staging/media/av7110/sp8870.c @@ -496,7 +496,7 @@ static int sp8870_set_frontend(struct dvb_frontend *fe) dprintk("delay = %i usec\n", check_count * 10); break; } - udelay(10); + usleep_range(10, 20); } if (valid) break; From a8333c8262aed2aedf608c18edd39cf5342680a7 Mon Sep 17 00:00:00 2001 From: Szymon Wilczek Date: Sat, 20 Dec 2025 19:24:19 +0100 Subject: [PATCH 082/307] media: pvrusb2: fix URB leak in pvr2_send_request_ex When pvr2_send_request_ex() submits a write URB successfully but fails to submit the read URB (e.g. returns -ENOMEM), it returns immediately without waiting for the write URB to complete. Since the driver reuses the same URB structure, a subsequent call to pvr2_send_request_ex() attempts to submit the still-active write URB, triggering a 'URB submitted while active' warning in usb_submit_urb(). Fix this by ensuring the write URB is unlinked and waited upon if the read URB submission fails. Reported-by: syzbot+405dcd13121ff75a9e16@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=405dcd13121ff75a9e16 Signed-off-by: Szymon Wilczek Acked-by: Mike Isely Signed-off-by: Hans Verkuil --- drivers/media/usb/pvrusb2/pvrusb2-hdw.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c index b32bb906a9de..5807734ae26c 100644 --- a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c +++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c @@ -3709,6 +3709,11 @@ status); "Failed to submit read-control URB status=%d", status); hdw->ctl_read_pend_flag = 0; + if (hdw->ctl_write_pend_flag) { + usb_unlink_urb(hdw->ctl_write_urb); + while (hdw->ctl_write_pend_flag) + wait_for_completion(&hdw->ctl_done); + } goto done; } } From 9cea16fea47e5553f51d10957677ff735b1eff03 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Tue, 23 Dec 2025 11:18:13 +0530 Subject: [PATCH 083/307] media: i2c/tw9903: Fix potential memory leak in tw9903_probe() In one of the error paths in tw9903_probe(), the memory allocated in v4l2_ctrl_handler_init() and v4l2_ctrl_new_std() is not freed. Fix that by calling v4l2_ctrl_handler_free() on the handler in that error path. Cc: stable@vger.kernel.org Fixes: 0890ec19c65d ("[media] tw9903: add new tw9903 video decoder") Signed-off-by: Abdun Nihaal Signed-off-by: Hans Verkuil --- drivers/media/i2c/tw9903.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/i2c/tw9903.c b/drivers/media/i2c/tw9903.c index b996a05e56f2..c3eafd5d5dc8 100644 --- a/drivers/media/i2c/tw9903.c +++ b/drivers/media/i2c/tw9903.c @@ -228,6 +228,7 @@ static int tw9903_probe(struct i2c_client *client) if (write_regs(sd, initial_registers) < 0) { v4l2_err(client, "error initializing TW9903\n"); + v4l2_ctrl_handler_free(hdl); return -EINVAL; } From cad237b6c875fbee5d353a2b289e98d240d17ec8 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Tue, 23 Dec 2025 11:19:01 +0530 Subject: [PATCH 084/307] media: i2c/tw9906: Fix potential memory leak in tw9906_probe() In one of the error paths in tw9906_probe(), the memory allocated in v4l2_ctrl_handler_init() and v4l2_ctrl_new_std() is not freed. Fix that by calling v4l2_ctrl_handler_free() on the handler in that error path. Cc: stable@vger.kernel.org Fixes: a000e9a02b58 ("[media] tw9906: add Techwell tw9906 video decoder") Signed-off-by: Abdun Nihaal Signed-off-by: Hans Verkuil --- drivers/media/i2c/tw9906.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/i2c/tw9906.c b/drivers/media/i2c/tw9906.c index 6220f4fddbab..0ab43fe42d7f 100644 --- a/drivers/media/i2c/tw9906.c +++ b/drivers/media/i2c/tw9906.c @@ -196,6 +196,7 @@ static int tw9906_probe(struct i2c_client *client) if (write_regs(sd, initial_registers) < 0) { v4l2_err(client, "error initializing TW9906\n"); + v4l2_ctrl_handler_free(hdl); return -EINVAL; } From ee138a2cfc425edb3e3890ee0c9a1d395b6b08f1 Mon Sep 17 00:00:00 2001 From: Sun Jian Date: Fri, 2 Jan 2026 19:45:00 +0800 Subject: [PATCH 085/307] staging: media: tegra-video: move tegra210_csi_soc declaration to csi.h Sparse warns that tegra210_csi_soc is not declared in tegra210.c. The symbol is referenced from csi.c, so it must remain global. Move the declaration to csi.h so users see it via the header and avoid extern declarations in .c files. SPARSE: drivers/staging/media/tegra-video/tegra210.c:1214:28: warning: symbol 'tegra210_csi_soc' was not declared. Should it be static? No functional change intended. Suggested-by: Luca Ceresoli Signed-off-by: Sun Jian Signed-off-by: Hans Verkuil --- drivers/staging/media/tegra-video/csi.c | 4 ---- drivers/staging/media/tegra-video/csi.h | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/staging/media/tegra-video/csi.c b/drivers/staging/media/tegra-video/csi.c index 604185c00a1a..3c3f6e3fd1ec 100644 --- a/drivers/staging/media/tegra-video/csi.c +++ b/drivers/staging/media/tegra-video/csi.c @@ -835,10 +835,6 @@ static void tegra_csi_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } -#if defined(CONFIG_ARCH_TEGRA_210_SOC) -extern const struct tegra_csi_soc tegra210_csi_soc; -#endif - static const struct of_device_id tegra_csi_of_id_table[] = { #if defined(CONFIG_ARCH_TEGRA_210_SOC) { .compatible = "nvidia,tegra210-csi", .data = &tegra210_csi_soc }, diff --git a/drivers/staging/media/tegra-video/csi.h b/drivers/staging/media/tegra-video/csi.h index 3e6e5ee1bb1e..609c5952e050 100644 --- a/drivers/staging/media/tegra-video/csi.h +++ b/drivers/staging/media/tegra-video/csi.h @@ -130,6 +130,10 @@ struct tegra_csi_soc { unsigned int tpg_frmrate_table_size; }; +#if defined(CONFIG_ARCH_TEGRA_210_SOC) +extern const struct tegra_csi_soc tegra210_csi_soc; +#endif + /** * struct tegra_csi - NVIDIA Tegra CSI device structure * From 597f8851c9343aad7c070818f20c61aab5fb74f0 Mon Sep 17 00:00:00 2001 From: Sun Jian Date: Fri, 2 Jan 2026 19:45:01 +0800 Subject: [PATCH 086/307] staging: media: tegra-video: move tegra20_vip_soc declaration to vip.h tegra20_vip_soc is shared across translation units but is currently declared via an extern in vip.c. Move the declaration to vip.h so users get it via the header and we avoid extern declarations in .c files, matching the pattern already used by tegra20_vi_soc and tegra210_vi_soc. Suggested-by: Luca Ceresoli Signed-off-by: Sun Jian Signed-off-by: Hans Verkuil --- drivers/staging/media/tegra-video/vip.c | 4 ---- drivers/staging/media/tegra-video/vip.h | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/staging/media/tegra-video/vip.c b/drivers/staging/media/tegra-video/vip.c index 5ec717f3afd5..80cd3b113125 100644 --- a/drivers/staging/media/tegra-video/vip.c +++ b/drivers/staging/media/tegra-video/vip.c @@ -263,10 +263,6 @@ static void tegra_vip_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } -#if defined(CONFIG_ARCH_TEGRA_2x_SOC) -extern const struct tegra_vip_soc tegra20_vip_soc; -#endif - static const struct of_device_id tegra_vip_of_id_table[] = { #if defined(CONFIG_ARCH_TEGRA_2x_SOC) { .compatible = "nvidia,tegra20-vip", .data = &tegra20_vip_soc }, diff --git a/drivers/staging/media/tegra-video/vip.h b/drivers/staging/media/tegra-video/vip.h index 32ceaaccbba2..fdded00447e4 100644 --- a/drivers/staging/media/tegra-video/vip.h +++ b/drivers/staging/media/tegra-video/vip.h @@ -50,6 +50,10 @@ struct tegra_vip_soc { const struct tegra_vip_ops *ops; }; +#if defined(CONFIG_ARCH_TEGRA_2x_SOC) +extern const struct tegra_vip_soc tegra20_vip_soc; +#endif + /** * struct tegra_vip - NVIDIA Tegra VIP device structure * From 68cd8ac994cac38a305200f638b30e13c690753b Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Sat, 3 Jan 2026 15:46:47 +0800 Subject: [PATCH 087/307] media: cx25821: Fix a resource leak in cx25821_dev_setup() Add release_mem_region() if ioremap() fails to release the memory region obtained by cx25821_get_resources(). Signed-off-by: Haoxiang Li Signed-off-by: Hans Verkuil --- drivers/media/pci/cx25821/cx25821-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/pci/cx25821/cx25821-core.c b/drivers/media/pci/cx25821/cx25821-core.c index 6627fa9166d3..a7336be44474 100644 --- a/drivers/media/pci/cx25821/cx25821-core.c +++ b/drivers/media/pci/cx25821/cx25821-core.c @@ -908,6 +908,7 @@ static int cx25821_dev_setup(struct cx25821_dev *dev) if (!dev->lmmio) { CX25821_ERR("ioremap failed, maybe increasing __VMALLOC_RESERVE in page.h\n"); + release_mem_region(dev->base_io_addr, pci_resource_len(dev->pci, 0)); cx25821_iounmap(dev); return -ENOMEM; } From 17526c7e69d07395e9d39794aacba42dcb02ff49 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 13 Jan 2026 13:29:46 +0000 Subject: [PATCH 088/307] Revert "media: uvcvideo: Remove nodrop parameter" This reverts commit 27ccc44a511b0cd76dc607e2a4893b876192ee1b. A user has reported that the Zoom Video Conferencing tool is not capable of handling invalid frames. Due to this, he has to continue using this parameter. We will keep working with Zoom so they can handle invalid frames correctly, but for this cycle the safest option is to revert this parameter drop. Fixes: 27ccc44a511b ("media: uvcvideo: Remove nodrop parameter") Reported-by: Ralf Jung Closes: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1121718 Closes: https://lore.kernel.org/linux-media/uboug5ectzm4s32yfgopjbcxq2uhsoc4kluaby7a4b7nzfjave@boco7oocnftr/ Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_driver.c | 19 +++++++++++++++++++ drivers/media/usb/uvc/uvc_queue.c | 25 +++++++++++++++++++++++++ drivers/media/usb/uvc/uvcvideo.h | 1 + 3 files changed, 45 insertions(+) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 71563d8f4bcf..ee4f54d68349 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -32,6 +32,7 @@ unsigned int uvc_clock_param = CLOCK_MONOTONIC; unsigned int uvc_hw_timestamps_param; +unsigned int uvc_no_drop_param = 1; static unsigned int uvc_quirks_param = -1; unsigned int uvc_dbg_param; unsigned int uvc_timeout_param = UVC_CTRL_STREAMING_TIMEOUT; @@ -2467,6 +2468,24 @@ MODULE_PARM_DESC(clock, "Video buffers timestamp clock"); module_param_named(hwtimestamps, uvc_hw_timestamps_param, uint, 0644); MODULE_PARM_DESC(hwtimestamps, "Use hardware timestamps"); +static int param_set_nodrop(const char *val, const struct kernel_param *kp) +{ + pr_warn_once("uvcvideo: " + DEPRECATED + "nodrop parameter will be eventually removed.\n"); + return param_set_bool(val, kp); +} + +static const struct kernel_param_ops param_ops_nodrop = { + .set = param_set_nodrop, + .get = param_get_uint, +}; + +param_check_uint(nodrop, &uvc_no_drop_param); +module_param_cb(nodrop, ¶m_ops_nodrop, &uvc_no_drop_param, 0644); +__MODULE_PARM_TYPE(nodrop, "uint"); +MODULE_PARM_DESC(nodrop, "Don't drop incomplete frames"); + module_param_named(quirks, uvc_quirks_param, uint, 0644); MODULE_PARM_DESC(quirks, "Forced device quirks"); module_param_named(trace, uvc_dbg_param, uint, 0644); diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c index 3bc54456b4d9..681a74ed09fb 100644 --- a/drivers/media/usb/uvc/uvc_queue.c +++ b/drivers/media/usb/uvc/uvc_queue.c @@ -331,9 +331,34 @@ struct uvc_buffer *uvc_queue_get_current_buffer(struct uvc_video_queue *queue) return nextbuf; } +/* + * uvc_queue_buffer_requeue: Requeue a buffer on our internal irqqueue + * + * Reuse a buffer through our internal queue without the need to 'prepare'. + * The buffer will be returned to userspace through the uvc_buffer_queue call if + * the device has been disconnected. + */ +static void uvc_queue_buffer_requeue(struct uvc_video_queue *queue, + struct uvc_buffer *buf) +{ + buf->error = 0; + buf->state = UVC_BUF_STATE_QUEUED; + buf->bytesused = 0; + vb2_set_plane_payload(&buf->buf.vb2_buf, 0, 0); + + uvc_buffer_queue(&buf->buf.vb2_buf); +} + static void uvc_queue_buffer_complete(struct kref *ref) { struct uvc_buffer *buf = container_of(ref, struct uvc_buffer, ref); + struct vb2_buffer *vb = &buf->buf.vb2_buf; + struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue); + + if (buf->error && !uvc_no_drop_param) { + uvc_queue_buffer_requeue(queue, buf); + return; + } buf->state = buf->error ? UVC_BUF_STATE_ERROR : UVC_BUF_STATE_DONE; vb2_set_plane_payload(&buf->buf.vb2_buf, 0, buf->bytesused); diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 911016047687..d583425893a5 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -659,6 +659,7 @@ static inline struct uvc_fh *to_uvc_fh(struct file *filp) #define UVC_WARN_XU_GET_RES 2 extern unsigned int uvc_clock_param; +extern unsigned int uvc_no_drop_param; extern unsigned int uvc_dbg_param; extern unsigned int uvc_timeout_param; extern unsigned int uvc_hw_timestamps_param; From 406fc2e9ca65e0df345ebf4ce95aa87cb6416f35 Mon Sep 17 00:00:00 2001 From: Deepa Guthyappa Madivalara Date: Wed, 10 Dec 2025 10:59:04 -0800 Subject: [PATCH 089/307] media: uapi: videodev2: Add support for AV1 stateful decoder Introduce a new pixel format, V4L2_PIX_FMT_AV1, to the Video4Linux2(V4L2) API. This format is intended for AV1 bitstreams in stateful decoding/encoding workflows. The fourcc code 'AV10' is used to distinguish this format from the existing V4L2_PIX_FMT_AV1_FRAME, which is used for stateless AV1 decoder implementation. Reviewed-by: Bryan O'Donoghue Reviewed-by: Nicolas Dufresne Reviewed-by: Hans Verkuil Signed-off-by: Deepa Guthyappa Madivalara Tested-by: Val Packett Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../userspace-api/media/v4l/pixfmt-compressed.rst | 8 ++++++++ include/uapi/linux/videodev2.h | 1 + 2 files changed, 9 insertions(+) diff --git a/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst b/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst index c7efb0465db6..235f955d3cd5 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst @@ -275,6 +275,14 @@ Compressed Formats of macroblocks to decode a full corresponding frame to the matching capture buffer. + * .. _V4L2-PIX-FMT-AV1: + + - ``V4L2_PIX_FMT_AV1`` + - 'AV01' + - AV1 compressed video frame. This format is adapted for implementing AV1 + pipeline. The decoder implements stateful video decoder and expects one + temporal unit per buffer in OBU stream format. + The encoder generates one Temporal Unit per buffer. .. raw:: latex \normalsize diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index add08188f068..848e86617d5c 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -775,6 +775,7 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_H264_SLICE v4l2_fourcc('S', '2', '6', '4') /* H264 parsed slices */ #define V4L2_PIX_FMT_HEVC_SLICE v4l2_fourcc('S', '2', '6', '5') /* HEVC parsed slices */ #define V4L2_PIX_FMT_AV1_FRAME v4l2_fourcc('A', 'V', '1', 'F') /* AV1 parsed frame */ +#define V4L2_PIX_FMT_AV1 v4l2_fourcc('A', 'V', '0', '1') /* AV1 */ #define V4L2_PIX_FMT_SPK v4l2_fourcc('S', 'P', 'K', '0') /* Sorenson Spark */ #define V4L2_PIX_FMT_RV30 v4l2_fourcc('R', 'V', '3', '0') /* RealVideo 8 */ #define V4L2_PIX_FMT_RV40 v4l2_fourcc('R', 'V', '4', '0') /* RealVideo 9 & 10 */ From 151096542e937e7336dc0d50192b855f5b490e22 Mon Sep 17 00:00:00 2001 From: Deepa Guthyappa Madivalara Date: Wed, 10 Dec 2025 10:59:05 -0800 Subject: [PATCH 090/307] media: v4l2: Add description for V4L2_PIX_FMT_AV1 in v4l_fill_fmtdesc() Add a descriptive string for the AV1 pixel format to v4l_fill_fmtdesc(), enabling proper reporting of AV1 support via VIDIOC_ENUM_FMT. Reviewed-by: Bryan O'Donoghue Reviewed-by: Nicolas Dufresne Reviewed-by: Hans Verkuil Signed-off-by: Deepa Guthyappa Madivalara Tested-by: Val Packett Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ioctl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 98512ea4cc5b..37d33d4a363d 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1544,6 +1544,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_QC10C: descr = "QCOM Compressed 10-bit Format"; break; case V4L2_PIX_FMT_AJPG: descr = "Aspeed JPEG"; break; case V4L2_PIX_FMT_AV1_FRAME: descr = "AV1 Frame"; break; + case V4L2_PIX_FMT_AV1: descr = "AV1 OBU Stream"; break; case V4L2_PIX_FMT_MT2110T: descr = "Mediatek 10bit Tile Mode"; break; case V4L2_PIX_FMT_MT2110R: descr = "Mediatek 10bit Raster Mode"; break; case V4L2_PIX_FMT_HEXTILE: descr = "Hextile Compressed Format"; break; From 10ac84abd48186b4c20719a7985c8df30d3b5f7a Mon Sep 17 00:00:00 2001 From: Deepa Guthyappa Madivalara Date: Wed, 10 Dec 2025 10:59:06 -0800 Subject: [PATCH 091/307] media: iris: Add support for AV1 format in iris decoder Extend iris decoder driver to support format V4L2_PIX_FMT_AV1. This change updates the format enumeration (VIDIOC_ENUM_FMT) and allows setting AV1 format via VIDIOC_S_FMT for gen2 and beyond. Gen1 iris hardware decoder does not support AV1 format. Reviewed-by: Bryan O'Donoghue Reviewed-by: Dikshita Agarwal Signed-off-by: Deepa Guthyappa Madivalara Tested-by: Val Packett Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../qcom/iris/iris_hfi_gen2_defines.h | 1 + .../media/platform/qcom/iris/iris_instance.h | 1 + .../platform/qcom/iris/iris_platform_common.h | 2 ++ .../platform/qcom/iris/iris_platform_gen1.c | 22 ++++++++++++++- .../platform/qcom/iris/iris_platform_gen2.c | 28 ++++++++++++++++++- drivers/media/platform/qcom/iris/iris_vdec.c | 23 +++------------ 6 files changed, 56 insertions(+), 21 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h index 1b6a4dbac828..3d56f257bc56 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h @@ -139,6 +139,7 @@ enum hfi_codec_type { HFI_CODEC_DECODE_HEVC = 3, HFI_CODEC_ENCODE_HEVC = 4, HFI_CODEC_DECODE_VP9 = 5, + HFI_CODEC_DECODE_AV1 = 7, }; enum hfi_picture_type { diff --git a/drivers/media/platform/qcom/iris/iris_instance.h b/drivers/media/platform/qcom/iris/iris_instance.h index 62fbb30691ff..7e4f5f1dd892 100644 --- a/drivers/media/platform/qcom/iris/iris_instance.h +++ b/drivers/media/platform/qcom/iris/iris_instance.h @@ -19,6 +19,7 @@ enum iris_fmt_type_out { IRIS_FMT_H264, IRIS_FMT_HEVC, IRIS_FMT_VP9, + IRIS_FMT_AV1, }; enum iris_fmt_type_cap { diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index 8d8cdb56a3c7..6e8e762bd5cf 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -217,6 +217,8 @@ struct iris_platform_data { u64 dma_mask; const char *fwname; u32 pas_id; + struct iris_fmt *inst_iris_fmts; + u32 inst_iris_fmts_size; struct platform_inst_caps *inst_caps; const struct platform_inst_fw_cap *inst_fw_caps_dec; u32 inst_fw_caps_dec_size; diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen1.c b/drivers/media/platform/qcom/iris/iris_platform_gen1.c index 34cbeb8f52e2..712515b0f329 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen1.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen1.c @@ -11,6 +11,7 @@ #include "iris_hfi_gen1_defines.h" #include "iris_vpu_buffer.h" #include "iris_vpu_common.h" +#include "iris_instance.h" #include "iris_platform_sc7280.h" @@ -19,7 +20,22 @@ #define BITRATE_PEAK_DEFAULT (BITRATE_DEFAULT * 2) #define BITRATE_STEP 100 -static const struct platform_inst_fw_cap inst_fw_cap_sm8250_dec[] = { +static struct iris_fmt platform_fmts_sm8250_dec[] = { + [IRIS_FMT_H264] = { + .pixfmt = V4L2_PIX_FMT_H264, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, + [IRIS_FMT_HEVC] = { + .pixfmt = V4L2_PIX_FMT_HEVC, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, + [IRIS_FMT_VP9] = { + .pixfmt = V4L2_PIX_FMT_VP9, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, +}; + +static struct platform_inst_fw_cap inst_fw_cap_sm8250_dec[] = { { .cap_id = PIPE, /* .max, .min and .value are set via platform data */ @@ -337,6 +353,8 @@ const struct iris_platform_data sm8250_data = { .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu-1.0/venus.mbn", .pas_id = IRIS_PAS_ID, + .inst_iris_fmts = platform_fmts_sm8250_dec, + .inst_iris_fmts_size = ARRAY_SIZE(platform_fmts_sm8250_dec), .inst_caps = &platform_inst_cap_sm8250, .inst_fw_caps_dec = inst_fw_cap_sm8250_dec, .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8250_dec), @@ -386,6 +404,8 @@ const struct iris_platform_data sc7280_data = { .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu/vpu20_p1.mbn", .pas_id = IRIS_PAS_ID, + .inst_iris_fmts = platform_fmts_sm8250_dec, + .inst_iris_fmts_size = ARRAY_SIZE(platform_fmts_sm8250_dec), .inst_caps = &platform_inst_cap_sm8250, .inst_fw_caps_dec = inst_fw_cap_sm8250_dec, .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8250_dec), diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index c1989240c248..010f6b804e0a 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -19,6 +19,25 @@ #define VIDEO_ARCH_LX 1 #define BITRATE_MAX 245000000 +static struct iris_fmt platform_fmts_sm8550_dec[] = { + [IRIS_FMT_H264] = { + .pixfmt = V4L2_PIX_FMT_H264, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, + [IRIS_FMT_HEVC] = { + .pixfmt = V4L2_PIX_FMT_HEVC, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, + [IRIS_FMT_VP9] = { + .pixfmt = V4L2_PIX_FMT_VP9, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, + [IRIS_FMT_AV1] = { + .pixfmt = V4L2_PIX_FMT_AV1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, +}; + static const struct platform_inst_fw_cap inst_fw_cap_sm8550_dec[] = { { .cap_id = PROFILE_H264, @@ -759,6 +778,8 @@ const struct iris_platform_data sm8550_data = { .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu/vpu30_p4.mbn", .pas_id = IRIS_PAS_ID, + .inst_iris_fmts = platform_fmts_sm8550_dec, + .inst_iris_fmts_size = ARRAY_SIZE(platform_fmts_sm8550_dec), .inst_caps = &platform_inst_cap_sm8550, .inst_fw_caps_dec = inst_fw_cap_sm8550_dec, .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8550_dec), @@ -809,7 +830,6 @@ const struct iris_platform_data sm8550_data = { .dec_output_prop_vp9 = sm8550_vdec_subscribe_output_properties_vp9, .dec_output_prop_vp9_size = ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_vp9), - .dec_ip_int_buf_tbl = sm8550_dec_ip_int_buf_tbl, .dec_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_ip_int_buf_tbl), .dec_op_int_buf_tbl = sm8550_dec_op_int_buf_tbl, @@ -851,6 +871,8 @@ const struct iris_platform_data sm8650_data = { .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu/vpu33_p4.mbn", .pas_id = IRIS_PAS_ID, + .inst_iris_fmts = platform_fmts_sm8550_dec, + .inst_iris_fmts_size = ARRAY_SIZE(platform_fmts_sm8550_dec), .inst_caps = &platform_inst_cap_sm8550, .inst_fw_caps_dec = inst_fw_cap_sm8550_dec, .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8550_dec), @@ -933,6 +955,8 @@ const struct iris_platform_data sm8750_data = { .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu/vpu35_p4.mbn", .pas_id = IRIS_PAS_ID, + .inst_iris_fmts = platform_fmts_sm8550_dec, + .inst_iris_fmts_size = ARRAY_SIZE(platform_fmts_sm8550_dec), .inst_caps = &platform_inst_cap_sm8550, .inst_fw_caps_dec = inst_fw_cap_sm8550_dec, .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8550_dec), @@ -1019,6 +1043,8 @@ const struct iris_platform_data qcs8300_data = { .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu/vpu30_p4_s6.mbn", .pas_id = IRIS_PAS_ID, + .inst_iris_fmts = platform_fmts_sm8550_dec, + .inst_iris_fmts_size = ARRAY_SIZE(platform_fmts_sm8550_dec), .inst_caps = &platform_inst_cap_qcs8300, .inst_fw_caps_dec = inst_fw_cap_sm8550_dec, .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8550_dec), diff --git a/drivers/media/platform/qcom/iris/iris_vdec.c b/drivers/media/platform/qcom/iris/iris_vdec.c index 69ffe52590d3..bf7b9c621cb7 100644 --- a/drivers/media/platform/qcom/iris/iris_vdec.c +++ b/drivers/media/platform/qcom/iris/iris_vdec.c @@ -67,21 +67,6 @@ void iris_vdec_inst_deinit(struct iris_inst *inst) kfree(inst->fmt_src); } -static const struct iris_fmt iris_vdec_formats_out[] = { - [IRIS_FMT_H264] = { - .pixfmt = V4L2_PIX_FMT_H264, - .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - }, - [IRIS_FMT_HEVC] = { - .pixfmt = V4L2_PIX_FMT_HEVC, - .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - }, - [IRIS_FMT_VP9] = { - .pixfmt = V4L2_PIX_FMT_VP9, - .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - }, -}; - static const struct iris_fmt iris_vdec_formats_cap[] = { [IRIS_FMT_NV12] = { .pixfmt = V4L2_PIX_FMT_NV12, @@ -101,8 +86,8 @@ find_format(struct iris_inst *inst, u32 pixfmt, u32 type) unsigned int i; switch (type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: - fmt = iris_vdec_formats_out; - size = ARRAY_SIZE(iris_vdec_formats_out); + fmt = inst->core->iris_platform_data->inst_iris_fmts; + size = inst->core->iris_platform_data->inst_iris_fmts_size; break; case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: fmt = iris_vdec_formats_cap; @@ -131,8 +116,8 @@ find_format_by_index(struct iris_inst *inst, u32 index, u32 type) switch (type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: - fmt = iris_vdec_formats_out; - size = ARRAY_SIZE(iris_vdec_formats_out); + fmt = inst->core->iris_platform_data->inst_iris_fmts; + size = inst->core->iris_platform_data->inst_iris_fmts_size; break; case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: fmt = iris_vdec_formats_cap; From 2af481a459a4740c402d2b3346bc4f7b013d74b4 Mon Sep 17 00:00:00 2001 From: Deepa Guthyappa Madivalara Date: Wed, 10 Dec 2025 10:59:07 -0800 Subject: [PATCH 092/307] media: iris: Define AV1-specific platform capabilities and properties Defining platform specific capabilities specific to AV1 decoder. Set and subscribe to manadatory properties to firmware for AV1. Reviewed-by: Bryan O'Donoghue Reviewed-by: Dikshita Agarwal Signed-off-by: Deepa Guthyappa Madivalara Tested-by: Val Packett Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../media/platform/qcom/iris/iris_buffer.h | 1 + drivers/media/platform/qcom/iris/iris_ctrls.c | 8 ++ .../platform/qcom/iris/iris_hfi_common.h | 3 + .../qcom/iris/iris_hfi_gen2_command.c | 85 ++++++++++- .../qcom/iris/iris_hfi_gen2_defines.h | 8 ++ .../qcom/iris/iris_hfi_gen2_response.c | 22 +++ .../platform/qcom/iris/iris_platform_common.h | 11 ++ .../platform/qcom/iris/iris_platform_gen2.c | 133 ++++++++++++++++++ drivers/media/platform/qcom/iris/iris_vidc.c | 1 + 9 files changed, 270 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_buffer.h b/drivers/media/platform/qcom/iris/iris_buffer.h index 325d30fce5c9..5ef365d9236c 100644 --- a/drivers/media/platform/qcom/iris/iris_buffer.h +++ b/drivers/media/platform/qcom/iris/iris_buffer.h @@ -42,6 +42,7 @@ enum iris_buffer_type { BUF_SCRATCH_1, BUF_SCRATCH_2, BUF_VPSS, + BUF_PARTIAL, BUF_TYPE_MAX, }; diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.c b/drivers/media/platform/qcom/iris/iris_ctrls.c index c0b3a09ad3e3..87f929442ec6 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.c +++ b/drivers/media/platform/qcom/iris/iris_ctrls.c @@ -98,6 +98,10 @@ static enum platform_inst_fw_cap_type iris_get_cap_id(u32 id) return B_FRAME_QP_H264; case V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP: return B_FRAME_QP_HEVC; + case V4L2_CID_MPEG_VIDEO_AV1_PROFILE: + return PROFILE_AV1; + case V4L2_CID_MPEG_VIDEO_AV1_LEVEL: + return LEVEL_AV1; default: return INST_FW_CAP_MAX; } @@ -185,6 +189,10 @@ static u32 iris_get_v4l2_id(enum platform_inst_fw_cap_type cap_id) return V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP; case B_FRAME_QP_HEVC: return V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP; + case PROFILE_AV1: + return V4L2_CID_MPEG_VIDEO_AV1_PROFILE; + case LEVEL_AV1: + return V4L2_CID_MPEG_VIDEO_AV1_LEVEL; default: return 0; } diff --git a/drivers/media/platform/qcom/iris/iris_hfi_common.h b/drivers/media/platform/qcom/iris/iris_hfi_common.h index b51471fb32c7..3edb5ae582b4 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_common.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_common.h @@ -141,6 +141,9 @@ struct hfi_subscription_params { u32 profile; u32 level; u32 tier; + u32 drap; + u32 film_grain; + u32 super_block; }; u32 iris_hfi_get_v4l2_color_primaries(u32 hfi_primaries); diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index f91295532099..b492d9dfddd3 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -10,6 +10,7 @@ #define UNSPECIFIED_COLOR_FORMAT 5 #define NUM_SYS_INIT_PACKETS 8 +#define NUM_COMV_AV1 18 #define SYS_INIT_PKT_SIZE (sizeof(struct iris_hfi_header) + \ NUM_SYS_INIT_PACKETS * (sizeof(struct iris_hfi_packet) + sizeof(u32))) @@ -121,6 +122,7 @@ static u32 iris_hfi_gen2_get_port_from_buf_type(struct iris_inst *inst, case BUF_COMV: case BUF_NON_COMV: case BUF_LINE: + case BUF_PARTIAL: return HFI_PORT_BITSTREAM; case BUF_OUTPUT: case BUF_DPB: @@ -380,6 +382,9 @@ static int iris_hfi_gen2_set_profile(struct iris_inst *inst, u32 plane) case V4L2_PIX_FMT_H264: profile = inst->fw_caps[PROFILE_H264].value; break; + case V4L2_PIX_FMT_AV1: + profile = inst->fw_caps[PROFILE_AV1].value; + break; } inst_hfi_gen2->src_subcr_params.profile = profile; @@ -409,6 +414,9 @@ static int iris_hfi_gen2_set_level(struct iris_inst *inst, u32 plane) case V4L2_PIX_FMT_H264: level = inst->fw_caps[LEVEL_H264].value; break; + case V4L2_PIX_FMT_AV1: + level = inst->fw_caps[LEVEL_AV1].value; + break; } inst_hfi_gen2->src_subcr_params.level = level; @@ -496,10 +504,12 @@ static int iris_hfi_gen2_set_linear_stride_scanline(struct iris_inst *inst, u32 static int iris_hfi_gen2_set_tier(struct iris_inst *inst, u32 plane) { - struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); u32 port = iris_hfi_gen2_get_port(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); u32 tier = inst->fw_caps[TIER].value; + tier = (inst->codec == V4L2_PIX_FMT_AV1) ? inst->fw_caps[TIER_AV1].value : + inst->fw_caps[TIER].value; inst_hfi_gen2->src_subcr_params.tier = tier; return iris_hfi_gen2_session_set_property(inst, @@ -525,6 +535,40 @@ static int iris_hfi_gen2_set_frame_rate(struct iris_inst *inst, u32 plane) sizeof(u32)); } +static int iris_hfi_gen2_set_film_grain(struct iris_inst *inst, u32 plane) +{ + u32 port = iris_hfi_gen2_get_port(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); + u32 film_grain = inst->fw_caps[FILM_GRAIN].value; + + inst_hfi_gen2->src_subcr_params.film_grain = film_grain; + + return iris_hfi_gen2_session_set_property(inst, + HFI_PROP_AV1_FILM_GRAIN_PRESENT, + HFI_HOST_FLAGS_NONE, + port, + HFI_PAYLOAD_U32_ENUM, + &film_grain, + sizeof(u32)); +} + +static int iris_hfi_gen2_set_super_block(struct iris_inst *inst, u32 plane) +{ + u32 port = iris_hfi_gen2_get_port(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); + u32 super_block = inst->fw_caps[SUPER_BLOCK].value; + + inst_hfi_gen2->src_subcr_params.super_block = super_block; + + return iris_hfi_gen2_session_set_property(inst, + HFI_PROP_AV1_SUPER_BLOCK_ENABLED, + HFI_HOST_FLAGS_NONE, + port, + HFI_PAYLOAD_U32_ENUM, + &super_block, + sizeof(u32)); +} + static int iris_hfi_gen2_session_set_config_params(struct iris_inst *inst, u32 plane) { const struct iris_platform_data *pdata = inst->core->iris_platform_data; @@ -548,6 +592,9 @@ static int iris_hfi_gen2_session_set_config_params(struct iris_inst *inst, u32 p {HFI_PROP_LINEAR_STRIDE_SCANLINE, iris_hfi_gen2_set_linear_stride_scanline }, {HFI_PROP_TIER, iris_hfi_gen2_set_tier }, {HFI_PROP_FRAME_RATE, iris_hfi_gen2_set_frame_rate }, + {HFI_PROP_AV1_FILM_GRAIN_PRESENT, iris_hfi_gen2_set_film_grain }, + {HFI_PROP_AV1_SUPER_BLOCK_ENABLED, iris_hfi_gen2_set_super_block }, + {HFI_PROP_OPB_ENABLE, iris_hfi_gen2_set_opb_enable }, }; if (inst->domain == DECODER) { @@ -561,6 +608,9 @@ static int iris_hfi_gen2_session_set_config_params(struct iris_inst *inst, u32 p } else if (inst->codec == V4L2_PIX_FMT_VP9) { config_params = pdata->dec_input_config_params_vp9; config_params_size = pdata->dec_input_config_params_vp9_size; + } else if (inst->codec == V4L2_PIX_FMT_AV1) { + config_params = pdata->dec_input_config_params_av1; + config_params_size = pdata->dec_input_config_params_av1_size; } else { return -EINVAL; } @@ -615,6 +665,9 @@ static int iris_hfi_gen2_session_set_codec(struct iris_inst *inst) break; case V4L2_PIX_FMT_VP9: codec = HFI_CODEC_DECODE_VP9; + break; + case V4L2_PIX_FMT_AV1: + codec = HFI_CODEC_DECODE_AV1; } iris_hfi_gen2_packet_session_property(inst, @@ -780,6 +833,11 @@ static int iris_hfi_gen2_subscribe_change_param(struct iris_inst *inst, u32 plan change_param_size = core->iris_platform_data->dec_input_config_params_vp9_size; break; + case V4L2_PIX_FMT_AV1: + change_param = core->iris_platform_data->dec_input_config_params_av1; + change_param_size = + core->iris_platform_data->dec_input_config_params_av1_size; + break; } payload[0] = HFI_MODE_PORT_SETTINGS_CHANGE; @@ -862,6 +920,16 @@ static int iris_hfi_gen2_subscribe_change_param(struct iris_inst *inst, u32 plan payload_size = sizeof(u32); payload_type = HFI_PAYLOAD_U32; break; + case HFI_PROP_AV1_FILM_GRAIN_PRESENT: + payload[0] = subsc_params.film_grain; + payload_size = sizeof(u32); + payload_type = HFI_PAYLOAD_U32; + break; + case HFI_PROP_AV1_SUPER_BLOCK_ENABLED: + payload[0] = subsc_params.super_block; + payload_size = sizeof(u32); + payload_type = HFI_PAYLOAD_U32; + break; default: prop_type = 0; ret = -EINVAL; @@ -917,6 +985,11 @@ static int iris_hfi_gen2_subscribe_property(struct iris_inst *inst, u32 plane) subscribe_prop_size = core->iris_platform_data->dec_output_prop_vp9_size; break; + case V4L2_PIX_FMT_AV1: + subcribe_prop = core->iris_platform_data->dec_output_prop_av1; + subscribe_prop_size = + core->iris_platform_data->dec_output_prop_av1_size; + break; } } @@ -1092,6 +1165,8 @@ static u32 iris_hfi_gen2_buf_type_from_driver(u32 domain, enum iris_buffer_type return HFI_BUFFER_ARP; case BUF_VPSS: return HFI_BUFFER_VPSS; + case BUF_PARTIAL: + return HFI_BUFFER_PARTIAL_DATA; default: return 0; } @@ -1104,7 +1179,13 @@ static int iris_set_num_comv(struct iris_inst *inst) u32 num_comv; caps = core->iris_platform_data->inst_caps; - num_comv = caps->num_comv; + + /* + * AV1 needs more comv buffers than other codecs. + * Update accordingly. + */ + num_comv = (inst->codec == V4L2_PIX_FMT_AV1) ? + NUM_COMV_AV1 : caps->num_comv; return core->hfi_ops->session_set_property(inst, HFI_PROP_COMV_BUFFER_COUNT, diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h index 3d56f257bc56..5f1f1a7eb91f 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h @@ -89,10 +89,18 @@ enum hfi_seq_header_mode { #define HFI_PROP_DEC_START_FROM_RAP_FRAME 0x03000169 #define HFI_PROP_NO_OUTPUT 0x0300016a #define HFI_PROP_BUFFER_MARK 0x0300016c +#define HFI_PROP_WORST_COMPRESSION_RATIO 0x03000174 +#define HFI_PROP_WORST_COMPLEXITY_FACTOR 0x03000175 #define HFI_PROP_RAW_RESOLUTION 0x03000178 #define HFI_PROP_TOTAL_PEAK_BITRATE 0x0300017C +#define HFI_PROP_AV1_FILM_GRAIN_PRESENT 0x03000180 +#define HFI_PROP_AV1_SUPER_BLOCK_ENABLED 0x03000181 +#define HFI_PROP_AV1_OP_POINT 0x03000182 #define HFI_PROP_OPB_ENABLE 0x03000184 +#define HFI_PROP_AV1_TILE_ROWS_COLUMNS 0x03000187 +#define HFI_PROP_AV1_DRAP_CONFIG 0x03000189 #define HFI_PROP_COMV_BUFFER_COUNT 0x03000193 +#define HFI_PROP_AV1_UNIFORM_TILE_SPACING 0x03000197 #define HFI_PROP_END 0x03FFFFFF #define HFI_SESSION_ERROR_BEGIN 0x04000000 diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c index 2f1f118eae4f..eb3373f0ad4a 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c @@ -54,6 +54,8 @@ static u32 iris_hfi_gen2_buf_type_to_driver(struct iris_inst *inst, return BUF_SCRATCH_2; case HFI_BUFFER_PERSIST: return BUF_PERSIST; + case HFI_BUFFER_PARTIAL_DATA: + return BUF_PARTIAL; default: return 0; } @@ -72,6 +74,7 @@ static bool iris_hfi_gen2_is_valid_hfi_buffer_type(u32 buffer_type) case HFI_BUFFER_DPB: case HFI_BUFFER_PERSIST: case HFI_BUFFER_VPSS: + case HFI_BUFFER_PARTIAL_DATA: return true; default: return false; @@ -596,6 +599,10 @@ static void iris_hfi_gen2_read_input_subcr_params(struct iris_inst *inst) inst->fw_caps[PROFILE_H264].value = subsc_params.profile; inst->fw_caps[LEVEL_H264].value = subsc_params.level; break; + case V4L2_PIX_FMT_AV1: + inst->fw_caps[PROFILE_AV1].value = subsc_params.profile; + inst->fw_caps[LEVEL_AV1].value = subsc_params.level; + break; } inst->fw_caps[POC].value = subsc_params.pic_order_cnt; @@ -608,6 +615,11 @@ static void iris_hfi_gen2_read_input_subcr_params(struct iris_inst *inst) iris_inst_change_state(inst, IRIS_INST_ERROR); } + if (inst->codec == V4L2_PIX_FMT_AV1) { + inst->fw_caps[FILM_GRAIN].value = subsc_params.film_grain; + inst->fw_caps[SUPER_BLOCK].value = subsc_params.super_block; + } + inst->fw_min_count = subsc_params.fw_min_count; inst->buffers[BUF_OUTPUT].min_count = iris_vpu_buf_count(inst, BUF_OUTPUT); inst->buffers[BUF_OUTPUT].size = pixmp_op->plane_fmt[0].sizeimage; @@ -711,6 +723,12 @@ static int iris_hfi_gen2_handle_session_property(struct iris_inst *inst, case HFI_PROP_NO_OUTPUT: inst_hfi_gen2->hfi_frame_info.no_output = 1; break; + case HFI_PROP_AV1_FILM_GRAIN_PRESENT: + inst_hfi_gen2->src_subcr_params.film_grain = pkt->payload[0]; + break; + case HFI_PROP_AV1_SUPER_BLOCK_ENABLED: + inst_hfi_gen2->src_subcr_params.super_block = pkt->payload[0]; + break; case HFI_PROP_QUALITY_MODE: case HFI_PROP_STAGE: case HFI_PROP_PIPE: @@ -841,6 +859,10 @@ static void iris_hfi_gen2_init_src_change_param(struct iris_inst *inst) subsc_params->profile = inst->fw_caps[PROFILE_H264].value; subsc_params->level = inst->fw_caps[LEVEL_H264].value; break; + case V4L2_PIX_FMT_AV1: + subsc_params->profile = inst->fw_caps[PROFILE_AV1].value; + subsc_params->level = inst->fw_caps[LEVEL_AV1].value; + break; } subsc_params->pic_order_cnt = inst->fw_caps[POC].value; diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index 6e8e762bd5cf..14a2f4dbe38a 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -103,6 +103,13 @@ enum platform_inst_fw_cap_type { LEVEL_H264, LEVEL_HEVC, LEVEL_VP9, + PROFILE_AV1, + LEVEL_AV1, + TIER_AV1, + DRAP, + FILM_GRAIN, + SUPER_BLOCK, + ENH_LAYER_COUNT, INPUT_BUF_HOST_MAX_COUNT, OUTPUT_BUF_HOST_MAX_COUNT, STAGE, @@ -241,6 +248,8 @@ struct iris_platform_data { unsigned int dec_input_config_params_hevc_size; const u32 *dec_input_config_params_vp9; unsigned int dec_input_config_params_vp9_size; + const u32 *dec_input_config_params_av1; + unsigned int dec_input_config_params_av1_size; const u32 *dec_output_config_params; unsigned int dec_output_config_params_size; const u32 *enc_input_config_params; @@ -255,6 +264,8 @@ struct iris_platform_data { unsigned int dec_output_prop_hevc_size; const u32 *dec_output_prop_vp9; unsigned int dec_output_prop_vp9_size; + const u32 *dec_output_prop_av1; + unsigned int dec_output_prop_av1_size; const u32 *dec_ip_int_buf_tbl; unsigned int dec_ip_int_buf_tbl_size; const u32 *dec_op_int_buf_tbl; diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index 010f6b804e0a..b2d8559dd2b8 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -64,6 +64,16 @@ static const struct platform_inst_fw_cap inst_fw_cap_sm8550_dec[] = { .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, .set = iris_set_u32_enum, }, + { + .cap_id = PROFILE_AV1, + .min = V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN, + .max = V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN), + .value = V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN, + .hfi_id = HFI_PROP_PROFILE, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, { .cap_id = PROFILE_VP9, .min = V4L2_MPEG_VIDEO_VP9_PROFILE_0, @@ -147,6 +157,33 @@ static const struct platform_inst_fw_cap inst_fw_cap_sm8550_dec[] = { .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, .set = iris_set_u32_enum, }, + { + .cap_id = LEVEL_AV1, + .min = V4L2_MPEG_VIDEO_AV1_LEVEL_2_0, + .max = V4L2_MPEG_VIDEO_AV1_LEVEL_6_1, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_2_0) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_2_1) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_2_2) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_2_3) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_3_0) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_3_1) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_3_2) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_3_3) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_4_0) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_4_1) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_4_2) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_4_3) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_5_0) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_5_1) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_5_2) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_5_3) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_6_0) | + BIT(V4L2_MPEG_VIDEO_AV1_LEVEL_6_1), + .value = V4L2_MPEG_VIDEO_AV1_LEVEL_6_1, + .hfi_id = HFI_PROP_LEVEL, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, { .cap_id = TIER, .min = V4L2_MPEG_VIDEO_HEVC_TIER_MAIN, @@ -158,6 +195,53 @@ static const struct platform_inst_fw_cap inst_fw_cap_sm8550_dec[] = { .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, .set = iris_set_u32_enum, }, + { + .cap_id = TIER_AV1, + .min = 0, + .max = 1, + .step_or_mask = 1, + .value = 0, + .hfi_id = HFI_PROP_TIER, + .flags = CAP_FLAG_INPUT_PORT, + .set = iris_set_u32, + }, + { + .cap_id = DRAP, + .min = 0, + .max = 1, + .step_or_mask = 1, + .value = 0, + .hfi_id = HFI_PROP_AV1_DRAP_CONFIG, + .flags = CAP_FLAG_INPUT_PORT, + .set = iris_set_u32, + }, + { + .cap_id = FILM_GRAIN, + .min = 0, + .max = 1, + .step_or_mask = 1, + .value = 0, + .hfi_id = HFI_PROP_AV1_FILM_GRAIN_PRESENT, + .flags = CAP_FLAG_VOLATILE, + }, + { + .cap_id = SUPER_BLOCK, + .min = 0, + .max = 1, + .step_or_mask = 1, + .value = 0, + .hfi_id = HFI_PROP_AV1_SUPER_BLOCK_ENABLED, + }, + { + .cap_id = ENH_LAYER_COUNT, + .min = 0, + .max = 1, + .step_or_mask = 1, + .value = 0, + .hfi_id = HFI_PROP_AV1_OP_POINT, + .flags = CAP_FLAG_INPUT_PORT, + .set = iris_set_u32, + }, { .cap_id = INPUT_BUF_HOST_MAX_COUNT, .min = DEFAULT_MAX_HOST_BUF_COUNT, @@ -699,6 +783,19 @@ static const u32 sm8550_vdec_input_config_param_vp9[] = { HFI_PROP_LEVEL, }; +static const u32 sm8550_vdec_input_config_param_av1[] = { + HFI_PROP_BITSTREAM_RESOLUTION, + HFI_PROP_CROP_OFFSETS, + HFI_PROP_LUMA_CHROMA_BIT_DEPTH, + HFI_PROP_BUFFER_FW_MIN_OUTPUT_COUNT, + HFI_PROP_PROFILE, + HFI_PROP_LEVEL, + HFI_PROP_TIER, + HFI_PROP_AV1_FILM_GRAIN_PRESENT, + HFI_PROP_AV1_SUPER_BLOCK_ENABLED, + HFI_PROP_SIGNAL_COLOR_INFO, +}; + static const u32 sm8550_venc_input_config_params[] = { HFI_PROP_COLOR_FORMAT, HFI_PROP_RAW_RESOLUTION, @@ -736,11 +833,18 @@ static const u32 sm8550_vdec_subscribe_output_properties_vp9[] = { HFI_PROP_PICTURE_TYPE, }; +static const u32 sm8550_vdec_subscribe_output_properties_av1[] = { + HFI_PROP_PICTURE_TYPE, + HFI_PROP_WORST_COMPRESSION_RATIO, + HFI_PROP_WORST_COMPLEXITY_FACTOR, +}; + static const u32 sm8550_dec_ip_int_buf_tbl[] = { BUF_BIN, BUF_COMV, BUF_NON_COMV, BUF_LINE, + BUF_PARTIAL, }; static const u32 sm8550_dec_op_int_buf_tbl[] = { @@ -805,6 +909,10 @@ const struct iris_platform_data sm8550_data = { sm8550_vdec_input_config_param_vp9, .dec_input_config_params_vp9_size = ARRAY_SIZE(sm8550_vdec_input_config_param_vp9), + .dec_input_config_params_av1 = + sm8550_vdec_input_config_param_av1, + .dec_input_config_params_av1_size = + ARRAY_SIZE(sm8550_vdec_input_config_param_av1), .dec_output_config_params = sm8550_vdec_output_config_params, .dec_output_config_params_size = @@ -830,6 +938,10 @@ const struct iris_platform_data sm8550_data = { .dec_output_prop_vp9 = sm8550_vdec_subscribe_output_properties_vp9, .dec_output_prop_vp9_size = ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_vp9), + .dec_output_prop_av1 = sm8550_vdec_subscribe_output_properties_av1, + .dec_output_prop_av1_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_av1), + .dec_ip_int_buf_tbl = sm8550_dec_ip_int_buf_tbl, .dec_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_ip_int_buf_tbl), .dec_op_int_buf_tbl = sm8550_dec_op_int_buf_tbl, @@ -898,6 +1010,10 @@ const struct iris_platform_data sm8650_data = { sm8550_vdec_input_config_param_vp9, .dec_input_config_params_vp9_size = ARRAY_SIZE(sm8550_vdec_input_config_param_vp9), + .dec_input_config_params_av1 = + sm8550_vdec_input_config_param_av1, + .dec_input_config_params_av1_size = + ARRAY_SIZE(sm8550_vdec_input_config_param_av1), .dec_output_config_params = sm8550_vdec_output_config_params, .dec_output_config_params_size = @@ -923,6 +1039,9 @@ const struct iris_platform_data sm8650_data = { .dec_output_prop_vp9 = sm8550_vdec_subscribe_output_properties_vp9, .dec_output_prop_vp9_size = ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_vp9), + .dec_output_prop_av1 = sm8550_vdec_subscribe_output_properties_av1, + .dec_output_prop_av1_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_av1), .dec_ip_int_buf_tbl = sm8550_dec_ip_int_buf_tbl, .dec_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_ip_int_buf_tbl), @@ -981,6 +1100,10 @@ const struct iris_platform_data sm8750_data = { sm8550_vdec_input_config_param_vp9, .dec_input_config_params_vp9_size = ARRAY_SIZE(sm8550_vdec_input_config_param_vp9), + .dec_input_config_params_av1 = + sm8550_vdec_input_config_param_av1, + .dec_input_config_params_av1_size = + ARRAY_SIZE(sm8550_vdec_input_config_param_av1), .dec_output_config_params = sm8550_vdec_output_config_params, .dec_output_config_params_size = @@ -1006,6 +1129,9 @@ const struct iris_platform_data sm8750_data = { .dec_output_prop_vp9 = sm8550_vdec_subscribe_output_properties_vp9, .dec_output_prop_vp9_size = ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_vp9), + .dec_output_prop_av1 = sm8550_vdec_subscribe_output_properties_av1, + .dec_output_prop_av1_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_av1), .dec_ip_int_buf_tbl = sm8550_dec_ip_int_buf_tbl, .dec_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_ip_int_buf_tbl), @@ -1070,6 +1196,10 @@ const struct iris_platform_data qcs8300_data = { sm8550_vdec_input_config_param_vp9, .dec_input_config_params_vp9_size = ARRAY_SIZE(sm8550_vdec_input_config_param_vp9), + .dec_input_config_params_av1 = + sm8550_vdec_input_config_param_av1, + .dec_input_config_params_av1_size = + ARRAY_SIZE(sm8550_vdec_input_config_param_av1), .dec_output_config_params = sm8550_vdec_output_config_params, .dec_output_config_params_size = @@ -1095,6 +1225,9 @@ const struct iris_platform_data qcs8300_data = { .dec_output_prop_vp9 = sm8550_vdec_subscribe_output_properties_vp9, .dec_output_prop_vp9_size = ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_vp9), + .dec_output_prop_av1 = sm8550_vdec_subscribe_output_properties_av1, + .dec_output_prop_av1_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_av1), .dec_ip_int_buf_tbl = sm8550_dec_ip_int_buf_tbl, .dec_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_ip_int_buf_tbl), diff --git a/drivers/media/platform/qcom/iris/iris_vidc.c b/drivers/media/platform/qcom/iris/iris_vidc.c index c9b881923ef1..dfd94f4a84a9 100644 --- a/drivers/media/platform/qcom/iris/iris_vidc.c +++ b/drivers/media/platform/qcom/iris/iris_vidc.c @@ -178,6 +178,7 @@ int iris_open(struct file *filp) INIT_LIST_HEAD(&inst->buffers[BUF_SCRATCH_1].list); INIT_LIST_HEAD(&inst->buffers[BUF_SCRATCH_2].list); INIT_LIST_HEAD(&inst->buffers[BUF_VPSS].list); + INIT_LIST_HEAD(&inst->buffers[BUF_PARTIAL].list); init_completion(&inst->completion); init_completion(&inst->flush_completion); From 819fcf0554388bb54b64184902cebfed5260437d Mon Sep 17 00:00:00 2001 From: Deepa Guthyappa Madivalara Date: Wed, 10 Dec 2025 10:59:08 -0800 Subject: [PATCH 093/307] media: iris: Add internal buffer calculation for AV1 decoder Implement internal buffer count and size calculations for AV1 decoder for all the buffer types required by the AV1 decoder, including BIN, COMV, PERSIST, LINE, and PARTIAL. This ensures the hardware decoder has properly allocated memory for AV1 decoding operations, enabling correct AV1 video playback. Reviewed-by: Dikshita Agarwal Signed-off-by: Deepa Guthyappa Madivalara Tested-by: Val Packett Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../media/platform/qcom/iris/iris_buffer.h | 1 + .../platform/qcom/iris/iris_vpu_buffer.c | 299 +++++++++++++++++- .../platform/qcom/iris/iris_vpu_buffer.h | 116 +++++++ 3 files changed, 412 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_buffer.h b/drivers/media/platform/qcom/iris/iris_buffer.h index 5ef365d9236c..75bb76776182 100644 --- a/drivers/media/platform/qcom/iris/iris_buffer.h +++ b/drivers/media/platform/qcom/iris/iris_buffer.h @@ -27,6 +27,7 @@ struct iris_inst; * @BUF_SCRATCH_1: buffer to store decoding/encoding context data for HW * @BUF_SCRATCH_2: buffer to store encoding context data for HW * @BUF_VPSS: buffer to store VPSS context data for HW + * @BUF_PARTIAL: buffer for AV1 IBC data * @BUF_TYPE_MAX: max buffer types */ enum iris_buffer_type { diff --git a/drivers/media/platform/qcom/iris/iris_vpu_buffer.c b/drivers/media/platform/qcom/iris/iris_vpu_buffer.c index 4463be05ce16..f4985790bae4 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_buffer.c +++ b/drivers/media/platform/qcom/iris/iris_vpu_buffer.c @@ -9,6 +9,17 @@ #include "iris_hfi_gen2_defines.h" #define HFI_MAX_COL_FRAME 6 +#define HFI_COLOR_FORMAT_YUV420_NV12_UBWC_Y_TILE_HEIGHT (8) +#define HFI_COLOR_FORMAT_YUV420_NV12_UBWC_Y_TILE_WIDTH (32) +#define HFI_COLOR_FORMAT_YUV420_NV12_UBWC_UV_TILE_HEIGHT (8) +#define HFI_COLOR_FORMAT_YUV420_NV12_UBWC_UV_TILE_WIDTH (16) +#define HFI_COLOR_FORMAT_YUV420_TP10_UBWC_Y_TILE_HEIGHT (4) +#define HFI_COLOR_FORMAT_YUV420_TP10_UBWC_Y_TILE_WIDTH (48) +#define HFI_COLOR_FORMAT_YUV420_TP10_UBWC_UV_TILE_HEIGHT (4) +#define HFI_COLOR_FORMAT_YUV420_TP10_UBWC_UV_TILE_WIDTH (24) +#define AV1D_SIZE_BSE_COL_MV_64x64 512 +#define AV1D_SIZE_BSE_COL_MV_128x128 2816 +#define UBWC_TILE_SIZE 256 #ifndef SYSTEM_LAL_TILE10 #define SYSTEM_LAL_TILE10 192 @@ -39,6 +50,31 @@ static u32 hfi_buffer_bin_h264d(u32 frame_width, u32 frame_height, u32 num_vpp_p return size_h264d_hw_bin_buffer(n_aligned_w, n_aligned_h, num_vpp_pipes); } +static u32 size_av1d_hw_bin_buffer(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) +{ + u32 size_yuv, size_bin_hdr, size_bin_res; + + size_yuv = ((frame_width * frame_height) <= BIN_BUFFER_THRESHOLD) ? + ((BIN_BUFFER_THRESHOLD * 3) >> 1) : + ((frame_width * frame_height * 3) >> 1); + size_bin_hdr = size_yuv * AV1_CABAC_HDR_RATIO_HD_TOT; + size_bin_res = size_yuv * AV1_CABAC_RES_RATIO_HD_TOT; + size_bin_hdr = ALIGN(size_bin_hdr / num_vpp_pipes, + DMA_ALIGNMENT) * num_vpp_pipes; + size_bin_res = ALIGN(size_bin_res / num_vpp_pipes, + DMA_ALIGNMENT) * num_vpp_pipes; + + return size_bin_hdr + size_bin_res; +} + +static u32 hfi_buffer_bin_av1d(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) +{ + u32 n_aligned_h = ALIGN(frame_height, 16); + u32 n_aligned_w = ALIGN(frame_width, 16); + + return size_av1d_hw_bin_buffer(n_aligned_w, n_aligned_h, num_vpp_pipes); +} + static u32 size_h265d_hw_bin_buffer(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) { u32 product = frame_width * frame_height; @@ -110,6 +146,26 @@ static u32 hfi_buffer_comv_h265d(u32 frame_width, u32 frame_height, u32 _comv_bu return (_size * (_comv_bufcount)) + 512; } +static u32 num_lcu(u32 frame_width, u32 frame_height, u32 lcu_size) +{ + return ((frame_width + lcu_size - 1) / lcu_size) * + ((frame_height + lcu_size - 1) / lcu_size); +} + +static u32 hfi_buffer_comv_av1d(u32 frame_width, u32 frame_height, u32 comv_bufcount) +{ + u32 size; + + size = 2 * ALIGN(max(num_lcu(frame_width, frame_height, 64) * + AV1D_SIZE_BSE_COL_MV_64x64, + num_lcu(frame_width, frame_height, 128) * + AV1D_SIZE_BSE_COL_MV_128x128), + DMA_ALIGNMENT); + size *= comv_bufcount; + + return size; +} + static u32 size_h264d_bse_cmd_buf(u32 frame_height) { u32 height = ALIGN(frame_height, 32); @@ -122,7 +178,7 @@ static u32 size_h265d_bse_cmd_buf(u32 frame_width, u32 frame_height) { u32 _size = ALIGN(((ALIGN(frame_width, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS) * (ALIGN(frame_height, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS)) * - NUM_HW_PIC_BUF, DMA_ALIGNMENT); + NUM_HW_PIC_BUF, DMA_ALIGNMENT); _size = min_t(u32, _size, H265D_MAX_SLICE + 1); _size = 2 * _size * SIZE_H265D_BSE_CMD_PER_BUF; @@ -174,6 +230,20 @@ static u32 hfi_buffer_persist_h264d(void) DMA_ALIGNMENT); } +static u32 hfi_buffer_persist_av1d(u32 max_width, u32 max_height, u32 total_ref_count) +{ + u32 comv_size, size; + + comv_size = hfi_buffer_comv_av1d(max_width, max_height, total_ref_count); + size = ALIGN((SIZE_AV1D_SEQUENCE_HEADER * 2 + SIZE_AV1D_METADATA + + AV1D_NUM_HW_PIC_BUF * (SIZE_AV1D_TILE_OFFSET + SIZE_AV1D_QM) + + AV1D_NUM_FRAME_HEADERS * (SIZE_AV1D_FRAME_HEADER + + 2 * SIZE_AV1D_PROB_TABLE) + comv_size + HDR10_HIST_EXTRADATA_SIZE + + SIZE_AV1D_METADATA * AV1D_NUM_HW_PIC_BUF), DMA_ALIGNMENT); + + return ALIGN(size, DMA_ALIGNMENT); +} + static u32 hfi_buffer_non_comv_h264d(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) { u32 size_bse = size_h264d_bse_cmd_buf(frame_height); @@ -459,6 +529,182 @@ static u32 hfi_buffer_line_h264d(u32 frame_width, u32 frame_height, return ALIGN((size + vpss_lb_size), DMA_ALIGNMENT); } +static u32 size_av1d_lb_opb_wr1_nv12_ubwc(u32 frame_width, u32 frame_height) +{ + u32 size, y_width, y_width_a = 128; + + y_width = ALIGN(frame_width, y_width_a); + + size = ((y_width + HFI_COLOR_FORMAT_YUV420_NV12_UBWC_Y_TILE_WIDTH - 1) / + HFI_COLOR_FORMAT_YUV420_NV12_UBWC_Y_TILE_WIDTH + + (AV1D_MAX_TILE_COLS - 1)); + return size * UBWC_TILE_SIZE; +} + +static u32 size_av1d_lb_opb_wr1_tp10_ubwc(u32 frame_width, u32 frame_height) +{ + u32 size, y_width, y_width_a = 256; + + y_width = ALIGN(frame_width, y_width_a); + + size = ((y_width + HFI_COLOR_FORMAT_YUV420_TP10_UBWC_Y_TILE_WIDTH - 1) / + HFI_COLOR_FORMAT_YUV420_TP10_UBWC_Y_TILE_WIDTH + + (AV1D_MAX_TILE_COLS - 1)); + + return size * UBWC_TILE_SIZE; +} + +static u32 hfi_buffer_line_av1d(u32 frame_width, u32 frame_height, + bool is_opb, u32 num_vpp_pipes) +{ + u32 size, vpss_lb_size, opbwrbufsize, opbwr8, opbwr10; + + size = ALIGN(size_av1d_lb_fe_top_data(frame_width, frame_height), + DMA_ALIGNMENT) + + ALIGN(size_av1d_lb_fe_top_ctrl(frame_width, frame_height), + DMA_ALIGNMENT) + + ALIGN(size_av1d_lb_fe_left_data(frame_width, frame_height), + DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(size_av1d_lb_fe_left_ctrl(frame_width, frame_height), + DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(size_av1d_lb_se_left_ctrl(frame_width, frame_height), + DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(size_av1d_lb_se_top_ctrl(frame_width, frame_height), + DMA_ALIGNMENT) + + ALIGN(size_av1d_lb_pe_top_data(frame_width, frame_height), + DMA_ALIGNMENT) + + ALIGN(size_av1d_lb_vsp_top(frame_width, frame_height), + DMA_ALIGNMENT) + + ALIGN(size_av1d_lb_recon_dma_metadata_wr + (frame_width, frame_height), DMA_ALIGNMENT) * 2 + + ALIGN(size_av1d_qp(frame_width, frame_height), DMA_ALIGNMENT); + opbwr8 = size_av1d_lb_opb_wr1_nv12_ubwc(frame_width, frame_height); + opbwr10 = size_av1d_lb_opb_wr1_tp10_ubwc(frame_width, frame_height); + opbwrbufsize = opbwr8 >= opbwr10 ? opbwr8 : opbwr10; + size = ALIGN((size + opbwrbufsize), DMA_ALIGNMENT); + if (is_opb) { + vpss_lb_size = size_vpss_lb(frame_width, frame_height); + size = ALIGN((size + vpss_lb_size) * 2, DMA_ALIGNMENT); + } + + return size; +} + +static u32 size_av1d_ibc_nv12_ubwc(u32 frame_width, u32 frame_height) +{ + u32 size; + u32 y_width_a = 128, y_height_a = 32; + u32 uv_width_a = 128, uv_height_a = 32; + u32 ybufsize, uvbufsize, y_width, y_height, uv_width, uv_height; + u32 y_meta_width_a = 64, y_meta_height_a = 16; + u32 uv_meta_width_a = 64, uv_meta_height_a = 16; + u32 meta_height, meta_stride, meta_size; + u32 tile_width_y = HFI_COLOR_FORMAT_YUV420_NV12_UBWC_Y_TILE_WIDTH; + u32 tile_height_y = HFI_COLOR_FORMAT_YUV420_NV12_UBWC_Y_TILE_HEIGHT; + u32 tile_width_uv = HFI_COLOR_FORMAT_YUV420_NV12_UBWC_UV_TILE_WIDTH; + u32 tile_height_uv = HFI_COLOR_FORMAT_YUV420_NV12_UBWC_UV_TILE_HEIGHT; + + y_width = ALIGN(frame_width, y_width_a); + y_height = ALIGN(frame_height, y_height_a); + uv_width = ALIGN(frame_width, uv_width_a); + uv_height = ALIGN(((frame_height + 1) >> 1), uv_height_a); + ybufsize = ALIGN((y_width * y_height), HFI_ALIGNMENT_4096); + uvbufsize = ALIGN(uv_width * uv_height, HFI_ALIGNMENT_4096); + size = ybufsize + uvbufsize; + meta_stride = ALIGN(((frame_width + (tile_width_y - 1)) / tile_width_y), + y_meta_width_a); + meta_height = ALIGN(((frame_height + (tile_height_y - 1)) / tile_height_y), + y_meta_height_a); + meta_size = ALIGN(meta_stride * meta_height, HFI_ALIGNMENT_4096); + size += meta_size; + meta_stride = ALIGN(((((frame_width + 1) >> 1) + (tile_width_uv - 1)) / + tile_width_uv), uv_meta_width_a); + meta_height = ALIGN(((((frame_height + 1) >> 1) + (tile_height_uv - 1)) / + tile_height_uv), uv_meta_height_a); + meta_size = ALIGN(meta_stride * meta_height, HFI_ALIGNMENT_4096); + size += meta_size; + + return size; +} + +static u32 hfi_yuv420_tp10_calc_y_stride(u32 frame_width, u32 stride_multiple) +{ + u32 stride; + + stride = ALIGN(frame_width, 192); + stride = ALIGN(stride * 4 / 3, stride_multiple); + + return stride; +} + +static u32 hfi_yuv420_tp10_calc_y_bufheight(u32 frame_height, u32 min_buf_height_multiple) +{ + return ALIGN(frame_height, min_buf_height_multiple); +} + +static u32 hfi_yuv420_tp10_calc_uv_stride(u32 frame_width, u32 stride_multiple) +{ + u32 stride; + + stride = ALIGN(frame_width, 192); + stride = ALIGN(stride * 4 / 3, stride_multiple); + + return stride; +} + +static u32 hfi_yuv420_tp10_calc_uv_bufheight(u32 frame_height, u32 min_buf_height_multiple) +{ + return ALIGN(((frame_height + 1) >> 1), min_buf_height_multiple); +} + +static u32 size_av1d_ibc_tp10_ubwc(u32 frame_width, u32 frame_height) +{ + u32 size; + u32 y_width_a = 256, y_height_a = 16, + uv_width_a = 256, uv_height_a = 16; + u32 ybufsize, uvbufsize, y_width, y_height, uv_width, uv_height; + u32 y_meta_width_a = 64, y_meta_height_a = 16, + uv_meta_width_a = 64, uv_meta_height_a = 16; + u32 meta_height, meta_stride, meta_size; + u32 tile_width_y = HFI_COLOR_FORMAT_YUV420_TP10_UBWC_Y_TILE_WIDTH; + u32 tile_height_y = HFI_COLOR_FORMAT_YUV420_TP10_UBWC_Y_TILE_HEIGHT; + u32 tile_width_uv = HFI_COLOR_FORMAT_YUV420_TP10_UBWC_UV_TILE_WIDTH; + u32 tile_height_uv = HFI_COLOR_FORMAT_YUV420_TP10_UBWC_UV_TILE_HEIGHT; + + y_width = hfi_yuv420_tp10_calc_y_stride(frame_width, y_width_a); + y_height = hfi_yuv420_tp10_calc_y_bufheight(frame_height, y_height_a); + uv_width = hfi_yuv420_tp10_calc_uv_stride(frame_width, uv_width_a); + uv_height = hfi_yuv420_tp10_calc_uv_bufheight(frame_height, uv_height_a); + ybufsize = ALIGN(y_width * y_height, HFI_ALIGNMENT_4096); + uvbufsize = ALIGN(uv_width * uv_height, HFI_ALIGNMENT_4096); + size = ybufsize + uvbufsize; + meta_stride = ALIGN(((frame_width + (tile_width_y - 1)) / tile_width_y), + y_meta_width_a); + meta_height = ALIGN(((frame_height + (tile_height_y - 1)) / tile_height_y), + y_meta_height_a); + meta_size = ALIGN(meta_stride * meta_height, HFI_ALIGNMENT_4096); + size += meta_size; + meta_stride = ALIGN(((((frame_width + 1) >> 1) + (tile_width_uv - 1)) / + tile_width_uv), uv_meta_width_a); + meta_height = ALIGN(((((frame_height + 1) >> 1) + (tile_height_uv - 1)) / + tile_height_uv), uv_meta_height_a); + meta_size = ALIGN(meta_stride * meta_height, HFI_ALIGNMENT_4096); + size += meta_size; + + return size; +} + +static u32 hfi_buffer_ibc_av1d(u32 frame_width, u32 frame_height) +{ + u32 size, ibc8, ibc10; + + ibc8 = size_av1d_ibc_nv12_ubwc(frame_width, frame_height); + ibc10 = size_av1d_ibc_tp10_ubwc(frame_width, frame_height); + size = ibc8 >= ibc10 ? ibc8 : ibc10; + + return ALIGN(size, DMA_ALIGNMENT); +} + static u32 iris_vpu_dec_bin_size(struct iris_inst *inst) { u32 num_vpp_pipes = inst->core->iris_platform_data->num_vpp_pipe; @@ -472,6 +718,8 @@ static u32 iris_vpu_dec_bin_size(struct iris_inst *inst) return hfi_buffer_bin_h265d(width, height, num_vpp_pipes); else if (inst->codec == V4L2_PIX_FMT_VP9) return hfi_buffer_bin_vp9d(width, height, num_vpp_pipes); + else if (inst->codec == V4L2_PIX_FMT_AV1) + return hfi_buffer_bin_av1d(width, height, num_vpp_pipes); return 0; } @@ -487,18 +735,34 @@ static u32 iris_vpu_dec_comv_size(struct iris_inst *inst) return hfi_buffer_comv_h264d(width, height, num_comv); else if (inst->codec == V4L2_PIX_FMT_HEVC) return hfi_buffer_comv_h265d(width, height, num_comv); + else if (inst->codec == V4L2_PIX_FMT_AV1) { + if (inst->fw_caps[DRAP].value) + return 0; + else + return hfi_buffer_comv_av1d(width, height, num_comv); + } return 0; } static u32 iris_vpu_dec_persist_size(struct iris_inst *inst) { + struct platform_inst_caps *caps; + if (inst->codec == V4L2_PIX_FMT_H264) return hfi_buffer_persist_h264d(); else if (inst->codec == V4L2_PIX_FMT_HEVC) return hfi_buffer_persist_h265d(0); else if (inst->codec == V4L2_PIX_FMT_VP9) return hfi_buffer_persist_vp9d(); + else if (inst->codec == V4L2_PIX_FMT_AV1) { + caps = inst->core->iris_platform_data->inst_caps; + if (inst->fw_caps[DRAP].value) + return hfi_buffer_persist_av1d(caps->max_frame_width, + caps->max_frame_height, 16); + else + return hfi_buffer_persist_av1d(0, 0, 0); + } return 0; } @@ -545,6 +809,8 @@ static u32 iris_vpu_dec_line_size(struct iris_inst *inst) else if (inst->codec == V4L2_PIX_FMT_VP9) return hfi_buffer_line_vp9d(width, height, out_min_count, is_opb, num_vpp_pipes); + else if (inst->codec == V4L2_PIX_FMT_AV1) + return hfi_buffer_line_av1d(width, height, is_opb, num_vpp_pipes); return 0; } @@ -653,6 +919,15 @@ static u32 iris_vpu_enc_bin_size(struct iris_inst *inst) num_vpp_pipes, inst->hfi_rc_type); } +static u32 iris_vpu_dec_partial_size(struct iris_inst *inst) +{ + struct v4l2_format *f = inst->fmt_src; + u32 height = f->fmt.pix_mp.height; + u32 width = f->fmt.pix_mp.width; + + return hfi_buffer_ibc_av1d(width, height); +} + static inline u32 hfi_buffer_comv_enc(u32 frame_width, u32 frame_height, u32 lcu_size, u32 num_recon, u32 standard) @@ -1414,7 +1689,9 @@ static int output_min_count(struct iris_inst *inst) /* fw_min_count > 0 indicates reconfig event has already arrived */ if (inst->fw_min_count) { - if (iris_split_mode_enabled(inst) && inst->codec == V4L2_PIX_FMT_VP9) + if (iris_split_mode_enabled(inst) && + (inst->codec == V4L2_PIX_FMT_VP9 || + inst->codec == V4L2_PIX_FMT_AV1)) return min_t(u32, 4, inst->fw_min_count); else return inst->fw_min_count; @@ -1422,6 +1699,8 @@ static int output_min_count(struct iris_inst *inst) if (inst->codec == V4L2_PIX_FMT_VP9) output_min_count = 9; + else if (inst->codec == V4L2_PIX_FMT_AV1) + output_min_count = 11; return output_min_count; } @@ -1444,6 +1723,7 @@ u32 iris_vpu_buf_size(struct iris_inst *inst, enum iris_buffer_type buffer_type) {BUF_PERSIST, iris_vpu_dec_persist_size }, {BUF_DPB, iris_vpu_dec_dpb_size }, {BUF_SCRATCH_1, iris_vpu_dec_scratch1_size }, + {BUF_PARTIAL, iris_vpu_dec_partial_size }, }; static const struct iris_vpu_buf_type_handle enc_internal_buf_type_handle[] = { @@ -1510,14 +1790,20 @@ static u32 internal_buffer_count(struct iris_inst *inst, buffer_type == BUF_PERSIST) { return 1; } else if (buffer_type == BUF_COMV || buffer_type == BUF_NON_COMV) { - if (inst->codec == V4L2_PIX_FMT_H264 || inst->codec == V4L2_PIX_FMT_HEVC) + if (inst->codec == V4L2_PIX_FMT_H264 || + inst->codec == V4L2_PIX_FMT_HEVC || + inst->codec == V4L2_PIX_FMT_AV1) return 1; } + return 0; } static inline int iris_vpu_dpb_count(struct iris_inst *inst) { + if (inst->codec == V4L2_PIX_FMT_AV1) + return 11; + if (iris_split_mode_enabled(inst)) { return inst->fw_min_count ? inst->fw_min_count : inst->buffers[BUF_OUTPUT].min_count; @@ -1536,9 +1822,13 @@ int iris_vpu_buf_count(struct iris_inst *inst, enum iris_buffer_type buffer_type return MIN_BUFFERS; else return output_min_count(inst); + case BUF_NON_COMV: + if (inst->codec == V4L2_PIX_FMT_AV1) + return 0; + else + return 1; case BUF_BIN: case BUF_COMV: - case BUF_NON_COMV: case BUF_LINE: case BUF_PERSIST: return internal_buffer_count(inst, buffer_type); @@ -1546,6 +1836,7 @@ int iris_vpu_buf_count(struct iris_inst *inst, enum iris_buffer_type buffer_type case BUF_SCRATCH_2: case BUF_VPSS: case BUF_ARP: + case BUF_PARTIAL: return 1; /* internal buffer count needed by firmware is 1 */ case BUF_DPB: return iris_vpu_dpb_count(inst); diff --git a/drivers/media/platform/qcom/iris/iris_vpu_buffer.h b/drivers/media/platform/qcom/iris/iris_vpu_buffer.h index 04f0b7400a1e..13c7199fcf35 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_buffer.h +++ b/drivers/media/platform/qcom/iris/iris_vpu_buffer.h @@ -11,6 +11,7 @@ struct iris_inst; #define MIN_BUFFERS 4 #define DMA_ALIGNMENT 256 +#define HFI_ALIGNMENT_4096 4096 #define NUM_HW_PIC_BUF 32 #define LCU_MAX_SIZE_PELS 64 @@ -81,6 +82,22 @@ struct iris_inst; #define MAX_PE_NBR_DATA_LCU64_LINE_BUFFER_SIZE 384 #define MAX_FE_NBR_DATA_LUMA_LINE_BUFFER_SIZE 640 +#define AV1_CABAC_HDR_RATIO_HD_TOT 2 +#define AV1_CABAC_RES_RATIO_HD_TOT 2 +#define AV1D_LCU_MAX_SIZE_PELS 128 +#define AV1D_LCU_MIN_SIZE_PELS 64 +#define AV1D_MAX_TILE_COLS 64 +#define MAX_PE_NBR_DATA_LCU32_LINE_BUFFER_SIZE 192 +#define MAX_PE_NBR_DATA_LCU16_LINE_BUFFER_SIZE 96 +#define AV1D_NUM_HW_PIC_BUF 16 +#define AV1D_NUM_FRAME_HEADERS 16 +#define SIZE_AV1D_SEQUENCE_HEADER 768 +#define SIZE_AV1D_METADATA 512 +#define SIZE_AV1D_FRAME_HEADER 1280 +#define SIZE_AV1D_TILE_OFFSET 65536 +#define SIZE_AV1D_QM 3328 +#define SIZE_AV1D_PROB_TABLE 22784 + #define SIZE_SLICE_CMD_BUFFER (ALIGN(20480, 256)) #define SIZE_SPS_PPS_SLICE_HDR (2048 + 4096) #define SIZE_BSE_SLICE_CMD_BUF ((((8192 << 2) + 7) & (~7)) * 3) @@ -101,6 +118,15 @@ struct iris_inst; #define NUM_MBS_4K (DIV_ROUND_UP(MAX_WIDTH, 16) * DIV_ROUND_UP(MAX_HEIGHT, 16)) #define NUM_MBS_720P (((ALIGN(1280, 16)) >> 4) * ((ALIGN(736, 16)) >> 4)) +#define BITS_PER_PIX 16 +#define NUM_LINES_LUMA 10 +#define NUM_LINES_CHROMA 6 +#define AV1D_LCU_MAX_SIZE_PELS 128 +#define AV1D_LCU_MIN_SIZE_PELS 64 +#define AV1D_MAX_TILE_COLS 64 +#define BITS_PER_CTRL_PACK 128 +#define NUM_CTRL_PACK_LCU 10 + static inline u32 size_h264d_lb_fe_top_data(u32 frame_width) { return MAX_FE_NBR_DATA_LUMA_LINE_BUFFER_SIZE * ALIGN(frame_width, 16) * 3; @@ -146,6 +172,96 @@ static inline u32 size_h264d_qp(u32 frame_width, u32 frame_height) return DIV_ROUND_UP(frame_width, 64) * DIV_ROUND_UP(frame_height, 64) * 128; } +static inline u32 size_av1d_lb_fe_top_data(u32 frame_width, u32 frame_height) +{ + return (ALIGN(frame_width, AV1D_LCU_MAX_SIZE_PELS) * + ((BITS_PER_PIX * NUM_LINES_LUMA) >> 3) + + ALIGN(frame_width, AV1D_LCU_MAX_SIZE_PELS) / 2 * + ((BITS_PER_PIX * NUM_LINES_CHROMA) >> 3) * 2); +} + +static inline u32 size_av1d_lb_fe_left_data(u32 frame_width, u32 frame_height) +{ + return (32 * (ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) + + ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / + AV1D_LCU_MIN_SIZE_PELS * 16) + + 16 * (ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / 2 + + ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / + AV1D_LCU_MIN_SIZE_PELS * 8) * 2 + + 24 * (ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) + + ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / + AV1D_LCU_MIN_SIZE_PELS * 16) + + 24 * (ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / 2 + + ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / + AV1D_LCU_MIN_SIZE_PELS * 12) * 2 + + 24 * (ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) + + ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / + AV1D_LCU_MIN_SIZE_PELS * 16) + + 16 * (ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) + + ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / + AV1D_LCU_MIN_SIZE_PELS * 16) + + 16 * (ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / 2 + + ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / + AV1D_LCU_MIN_SIZE_PELS * 12) * 2); +} + +static inline u32 size_av1d_lb_fe_top_ctrl(u32 frame_width, u32 frame_height) +{ + return (NUM_CTRL_PACK_LCU * ((frame_width + AV1D_LCU_MIN_SIZE_PELS - 1) / + AV1D_LCU_MIN_SIZE_PELS) * BITS_PER_CTRL_PACK / 8); +} + +static inline u32 size_av1d_lb_fe_left_ctrl(u32 frame_width, u32 frame_height) +{ + return (16 * ((ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / 16) + + (ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / + AV1D_LCU_MIN_SIZE_PELS)) + + 3 * 16 * (ALIGN(frame_height, AV1D_LCU_MAX_SIZE_PELS) / + AV1D_LCU_MIN_SIZE_PELS)); +} + +static inline u32 size_av1d_lb_se_top_ctrl(u32 frame_width, u32 frame_height) +{ + return (((frame_width + 7) / 8) * MAX_SE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE); +} + +static inline u32 size_av1d_lb_se_left_ctrl(u32 frame_width, u32 frame_height) +{ + return (max(((frame_height + 15) / 16) * + MAX_SE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE, + max(((frame_height + 31) / 32) * + MAX_SE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE, + ((frame_height + 63) / 64) * + MAX_SE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE))); +} + +static inline u32 size_av1d_lb_pe_top_data(u32 frame_width, u32 frame_height) +{ + return (max(((frame_width + 15) / 16) * + MAX_PE_NBR_DATA_LCU16_LINE_BUFFER_SIZE, + max(((frame_width + 31) / 32) * + MAX_PE_NBR_DATA_LCU32_LINE_BUFFER_SIZE, + ((frame_width + 63) / 64) * + MAX_PE_NBR_DATA_LCU64_LINE_BUFFER_SIZE))); +} + +static inline u32 size_av1d_lb_vsp_top(u32 frame_width, u32 frame_height) +{ + return (max(((frame_width + 63) / 64) * 1280, + ((frame_width + 127) / 128) * MAX_HEIGHT)); +} + +static inline u32 size_av1d_lb_recon_dma_metadata_wr(u32 frame_width, + u32 frame_height) +{ + return ((ALIGN(frame_height, 8) / (4 / 2)) * 64); +} + +static inline u32 size_av1d_qp(u32 frame_width, u32 frame_height) +{ + return size_h264d_qp(frame_width, frame_height); +} + u32 iris_vpu_buf_size(struct iris_inst *inst, enum iris_buffer_type buffer_type); u32 iris_vpu33_buf_size(struct iris_inst *inst, enum iris_buffer_type buffer_type); int iris_vpu_buf_count(struct iris_inst *inst, enum iris_buffer_type buffer_type); From ada20c3db0db4f2834d9515f6105111871f04a4d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:09 +0200 Subject: [PATCH 094/307] media: i2c: ov01a10: Fix the horizontal flip control During sensor calibration I noticed that with the hflip control set to false/disabled the image was mirrored. So it seems that the horizontal flip control is inverted and needs to be set to 1 to not flip (just like the similar problem recently fixed on the ov08x40 sensor). Invert the hflip control to fix the sensor mirroring by default. As the comment above the newly added OV01A10_MEDIA_BUS_FMT define explains the control being inverted also means that the native Bayer-order of the sensor actually is GBRG not BGGR, but so as to not break userspace the Bayer-order is kept at BGGR. Fixes: 0827b58dabff ("media: i2c: add ov01a10 image sensor driver") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 141cb6f75b55..e5df01f97978 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -75,6 +75,15 @@ #define OV01A10_REG_X_WIN 0x3811 #define OV01A10_REG_Y_WIN 0x3813 +/* + * The native ov01a10 bayer-pattern is GBRG, but there was a driver bug enabling + * hflip/mirroring by default resulting in BGGR. Because of this bug Intel's + * proprietary IPU6 userspace stack expects BGGR. So we report BGGR to not break + * userspace and fix things up by shifting the crop window-x coordinate by 1 + * when hflip is *disabled*. + */ +#define OV01A10_MEDIA_BUS_FMT MEDIA_BUS_FMT_SBGGR10_1X10 + struct ov01a10_reg { u16 address; u8 val; @@ -185,14 +194,14 @@ static const struct ov01a10_reg sensor_1280x800_setting[] = { {0x380e, 0x03}, {0x380f, 0x80}, {0x3810, 0x00}, - {0x3811, 0x08}, + {0x3811, 0x09}, {0x3812, 0x00}, {0x3813, 0x08}, {0x3814, 0x01}, {0x3815, 0x01}, {0x3816, 0x01}, {0x3817, 0x01}, - {0x3820, 0xa0}, + {0x3820, 0xa8}, {0x3822, 0x13}, {0x3832, 0x28}, {0x3833, 0x10}, @@ -411,7 +420,7 @@ static int ov01a10_set_hflip(struct ov01a10 *ov01a10, u32 hflip) int ret; u32 val, offset; - offset = hflip ? 0x9 : 0x8; + offset = hflip ? 0x8 : 0x9; ret = ov01a10_write_reg(ov01a10, OV01A10_REG_X_WIN, 1, offset); if (ret) return ret; @@ -420,8 +429,8 @@ static int ov01a10_set_hflip(struct ov01a10 *ov01a10, u32 hflip) if (ret) return ret; - val = hflip ? val | FIELD_PREP(OV01A10_HFLIP_MASK, 0x1) : - val & ~OV01A10_HFLIP_MASK; + val = hflip ? val & ~OV01A10_HFLIP_MASK : + val | FIELD_PREP(OV01A10_HFLIP_MASK, 0x1); return ov01a10_write_reg(ov01a10, OV01A10_REG_FORMAT1, 1, val); } @@ -610,7 +619,7 @@ static void ov01a10_update_pad_format(const struct ov01a10_mode *mode, { fmt->width = mode->width; fmt->height = mode->height; - fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; + fmt->code = OV01A10_MEDIA_BUS_FMT; fmt->field = V4L2_FIELD_NONE; fmt->colorspace = V4L2_COLORSPACE_RAW; } @@ -751,7 +760,7 @@ static int ov01a10_enum_mbus_code(struct v4l2_subdev *sd, if (code->index > 0) return -EINVAL; - code->code = MEDIA_BUS_FMT_SBGGR10_1X10; + code->code = OV01A10_MEDIA_BUS_FMT; return 0; } @@ -761,7 +770,7 @@ static int ov01a10_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_frame_size_enum *fse) { if (fse->index >= ARRAY_SIZE(supported_modes) || - fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) + fse->code != OV01A10_MEDIA_BUS_FMT) return -EINVAL; fse->min_width = supported_modes[fse->index].width; From 9c632eebf6af4cb7b0f85503fe1ebc5176ff0db1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:10 +0200 Subject: [PATCH 095/307] media: i2c: ov01a10: Fix reported pixel-rate value CSI lanes are double-clocked so with a single lane at 400MHZ the resulting pixel-rate for 10-bits pixels is 400 MHz * 2 / 10 = 80 MHz, not 40 MHz. This also matches with the observed frame-rate of 60 fps with the default vblank setting: 80000000 / (1488 * 896) = 60. Fixes: 0827b58dabff ("media: i2c: add ov01a10 image sensor driver") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index e5df01f97978..0b1a1ecfffd0 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -16,7 +16,7 @@ #include #define OV01A10_LINK_FREQ_400MHZ 400000000ULL -#define OV01A10_SCLK 40000000LL +#define OV01A10_SCLK 80000000LL #define OV01A10_DATA_LANES 1 #define OV01A10_REG_CHIP_ID 0x300a From 109e0feacaeca5ec2dd71d7d17c73232ce5cbddc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:11 +0200 Subject: [PATCH 096/307] media: i2c: ov01a10: Fix analogue gain range A analogue maximum gain of 0xffff / 65525 seems unlikely and testing indeed shows that the gain control wraps-around at 16383, so set the maximum gain to 0x3fff / 16383. The minimum gain of 0x100 is correct. Setting bits 8-11 to 0x0 results in the same gain values as setting these bits to 0x1, with bits 0-7 still increasing the gain when going from 0x000 - 0x0ff in the exact same range as when going from 0x100 - 0x1ff. Fixes: 0827b58dabff ("media: i2c: add ov01a10 image sensor driver") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait [Sakari Ailus: mention analogue gain and update the limit from 4096.] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 0b1a1ecfffd0..834ca46acb75 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -48,7 +48,7 @@ /* analog gain controls */ #define OV01A10_REG_ANALOG_GAIN 0x3508 #define OV01A10_ANAL_GAIN_MIN 0x100 -#define OV01A10_ANAL_GAIN_MAX 0xffff +#define OV01A10_ANAL_GAIN_MAX 0x3fff #define OV01A10_ANAL_GAIN_STEP 1 /* digital gain controls */ From 0dfec6e30c334364145d0acb38bb8c216b9a7a78 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:12 +0200 Subject: [PATCH 097/307] media: i2c: ov01a10: Add missing v4l2_subdev_cleanup() calls Add missing v4l2_subdev_cleanup() calls to cleanup after v4l2_subdev_init_finalize(). Fixes: 0827b58dabff ("media: i2c: add ov01a10 image sensor driver") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Reviewed-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 834ca46acb75..1e22df12989a 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -864,6 +864,7 @@ static void ov01a10_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); media_entity_cleanup(&sd->entity); v4l2_ctrl_handler_free(sd->ctrl_handler); @@ -934,6 +935,7 @@ static int ov01a10_probe(struct i2c_client *client) err_pm_disable: pm_runtime_disable(dev); pm_runtime_set_suspended(&client->dev); + v4l2_subdev_cleanup(&ov01a10->sd); err_media_entity_cleanup: media_entity_cleanup(&ov01a10->sd.entity); From f8563a375e7fba7c776eb591d4498be592c19098 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:13 +0200 Subject: [PATCH 098/307] media: i2c: ov01a10: Fix passing stream instead of pad to v4l2_subdev_state_get_format() The 2 argument version of v4l2_subdev_state_get_format() takes the pad as second argument, not the stream. Fixes: bc0e8d91feec ("media: v4l: subdev: Switch to stream-aware state functions") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 1e22df12989a..dd2b6d381175 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -731,7 +731,7 @@ static int ov01a10_set_format(struct v4l2_subdev *sd, h_blank); } - format = v4l2_subdev_state_get_format(sd_state, fmt->stream); + format = v4l2_subdev_state_get_format(sd_state, fmt->pad); *format = fmt->format; return 0; From 409fb57c1b3deada4b8e153eb6344afb3c2dfb9c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:14 +0200 Subject: [PATCH 099/307] media: i2c: ov01a10: Fix test-pattern disabling When the test-pattern control gets set to 0 (Disabled) 0 should be written to the test-pattern register, rather then doing nothing. Fixes: 0827b58dabff ("media: i2c: add ov01a10 image sensor driver") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Reviewed-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index dd2b6d381175..3ad516e4d369 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -249,9 +249,8 @@ static const struct ov01a10_reg sensor_1280x800_setting[] = { static const char * const ov01a10_test_pattern_menu[] = { "Disabled", "Color Bar", - "Top-Bottom Darker Color Bar", - "Right-Left Darker Color Bar", - "Color Bar type 4", + "Left-Right Darker Color Bar", + "Bottom-Top Darker Color Bar", }; static const s64 link_freq_menu_items[] = { @@ -406,10 +405,8 @@ static int ov01a10_update_digital_gain(struct ov01a10 *ov01a10, u32 d_gain) static int ov01a10_test_pattern(struct ov01a10 *ov01a10, u32 pattern) { - if (!pattern) - return 0; - - pattern = (pattern - 1) | OV01A10_TEST_PATTERN_ENABLE; + if (pattern) + pattern |= OV01A10_TEST_PATTERN_ENABLE; return ov01a10_write_reg(ov01a10, OV01A10_REG_TEST_PATTERN, 1, pattern); } From ea444f30f6e37048939a0e1056749d7afc190dd5 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:15 +0200 Subject: [PATCH 100/307] media: i2c: ov01a10: Change default vblank value to a vblank resulting in 30 fps The ov01a10 is quite a small sensor, which does not capture a lot of light, increase the default vblank so that the sensor runs at 30 fps by default, doubling the default exposure. Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 3ad516e4d369..4f04bb8688b8 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -34,7 +34,7 @@ /* vertical and horizontal timings */ #define OV01A10_REG_VTS 0x380e -#define OV01A10_VTS_DEF 0x0380 +#define OV01A10_VTS_DEF 0x0700 #define OV01A10_VTS_MIN 0x0380 #define OV01A10_VTS_MAX 0xffff #define OV01A10_HTS_DEF 1488 @@ -191,8 +191,8 @@ static const struct ov01a10_reg sensor_1280x800_setting[] = { {0x380b, 0x20}, {0x380c, 0x02}, {0x380d, 0xe8}, - {0x380e, 0x03}, - {0x380f, 0x80}, + {0x380e, 0x07}, + {0x380f, 0x00}, {0x3810, 0x00}, {0x3811, 0x09}, {0x3812, 0x00}, From 50b52b6d420e85f86a579ea197b569b31bc6de43 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:16 +0200 Subject: [PATCH 101/307] media: i2c: ov01a10: Convert to new CCI register access helpers Use the new comon CCI register access helpers to replace the private register access helpers in the ov01a10 driver. Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/Kconfig | 1 + drivers/media/i2c/ov01a10.c | 219 ++++++++++-------------------------- 2 files changed, 59 insertions(+), 161 deletions(-) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 6dcae0aa98ee..3121c0d947cd 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -383,6 +383,7 @@ config VIDEO_OS05B10 config VIDEO_OV01A10 tristate "OmniVision OV01A10 sensor support" + select V4L2_CCI_I2C help This is a Video4Linux2 sensor driver for the OmniVision OV01A10 camera. diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 4f04bb8688b8..34b7ab6eb286 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -10,7 +10,9 @@ #include #include #include +#include +#include #include #include #include @@ -19,10 +21,10 @@ #define OV01A10_SCLK 80000000LL #define OV01A10_DATA_LANES 1 -#define OV01A10_REG_CHIP_ID 0x300a +#define OV01A10_REG_CHIP_ID CCI_REG24(0x300a) #define OV01A10_CHIP_ID 0x560141 -#define OV01A10_REG_MODE_SELECT 0x0100 +#define OV01A10_REG_MODE_SELECT CCI_REG8(0x0100) #define OV01A10_MODE_STANDBY 0x00 #define OV01A10_MODE_STREAMING 0x01 @@ -33,47 +35,47 @@ #define OV01A10_ACITVE_HEIGHT 800 /* vertical and horizontal timings */ -#define OV01A10_REG_VTS 0x380e +#define OV01A10_REG_VTS CCI_REG16(0x380e) #define OV01A10_VTS_DEF 0x0700 #define OV01A10_VTS_MIN 0x0380 #define OV01A10_VTS_MAX 0xffff #define OV01A10_HTS_DEF 1488 /* exposure controls */ -#define OV01A10_REG_EXPOSURE 0x3501 +#define OV01A10_REG_EXPOSURE CCI_REG16(0x3501) #define OV01A10_EXPOSURE_MIN 4 #define OV01A10_EXPOSURE_MAX_MARGIN 8 #define OV01A10_EXPOSURE_STEP 1 /* analog gain controls */ -#define OV01A10_REG_ANALOG_GAIN 0x3508 +#define OV01A10_REG_ANALOG_GAIN CCI_REG16(0x3508) #define OV01A10_ANAL_GAIN_MIN 0x100 #define OV01A10_ANAL_GAIN_MAX 0x3fff #define OV01A10_ANAL_GAIN_STEP 1 /* digital gain controls */ -#define OV01A10_REG_DIGITAL_GAIN_B 0x350a -#define OV01A10_REG_DIGITAL_GAIN_GB 0x3510 -#define OV01A10_REG_DIGITAL_GAIN_GR 0x3513 -#define OV01A10_REG_DIGITAL_GAIN_R 0x3516 +#define OV01A10_REG_DIGITAL_GAIN_B CCI_REG24(0x350a) +#define OV01A10_REG_DIGITAL_GAIN_GB CCI_REG24(0x3510) +#define OV01A10_REG_DIGITAL_GAIN_GR CCI_REG24(0x3513) +#define OV01A10_REG_DIGITAL_GAIN_R CCI_REG24(0x3516) #define OV01A10_DGTL_GAIN_MIN 0 #define OV01A10_DGTL_GAIN_MAX 0x3ffff #define OV01A10_DGTL_GAIN_STEP 1 #define OV01A10_DGTL_GAIN_DEFAULT 1024 /* test pattern control */ -#define OV01A10_REG_TEST_PATTERN 0x4503 +#define OV01A10_REG_TEST_PATTERN CCI_REG8(0x4503) #define OV01A10_TEST_PATTERN_ENABLE BIT(7) #define OV01A10_LINK_FREQ_400MHZ_INDEX 0 /* flip and mirror control */ -#define OV01A10_REG_FORMAT1 0x3820 +#define OV01A10_REG_FORMAT1 CCI_REG8(0x3820) #define OV01A10_VFLIP_MASK BIT(4) #define OV01A10_HFLIP_MASK BIT(3) /* window offset */ -#define OV01A10_REG_X_WIN 0x3811 -#define OV01A10_REG_Y_WIN 0x3813 +#define OV01A10_REG_X_WIN CCI_REG16(0x3810) +#define OV01A10_REG_Y_WIN CCI_REG16(0x3812) /* * The native ov01a10 bayer-pattern is GBRG, but there was a driver bug enabling @@ -84,14 +86,9 @@ */ #define OV01A10_MEDIA_BUS_FMT MEDIA_BUS_FMT_SBGGR10_1X10 -struct ov01a10_reg { - u16 address; - u8 val; -}; - struct ov01a10_reg_list { u32 num_of_regs; - const struct ov01a10_reg *regs; + const struct reg_sequence *regs; }; struct ov01a10_link_freq_config { @@ -109,7 +106,7 @@ struct ov01a10_mode { const struct ov01a10_reg_list reg_list; }; -static const struct ov01a10_reg mipi_data_rate_720mbps[] = { +static const struct reg_sequence mipi_data_rate_720mbps[] = { {0x0103, 0x01}, {0x0302, 0x00}, {0x0303, 0x06}, @@ -125,7 +122,7 @@ static const struct ov01a10_reg mipi_data_rate_720mbps[] = { {0x0325, 0x68}, }; -static const struct ov01a10_reg sensor_1280x800_setting[] = { +static const struct reg_sequence sensor_1280x800_setting[] = { {0x3002, 0xa1}, {0x301e, 0xf0}, {0x3022, 0x01}, @@ -282,6 +279,7 @@ static const struct ov01a10_mode supported_modes[] = { }; struct ov01a10 { + struct regmap *regmap; struct v4l2_subdev sd; struct media_pad pad; struct v4l2_ctrl_handler ctrl_handler; @@ -301,104 +299,15 @@ static inline struct ov01a10 *to_ov01a10(struct v4l2_subdev *subdev) return container_of(subdev, struct ov01a10, sd); } -static int ov01a10_read_reg(struct ov01a10 *ov01a10, u16 reg, u16 len, u32 *val) -{ - struct i2c_client *client = v4l2_get_subdevdata(&ov01a10->sd); - struct i2c_msg msgs[2]; - u8 addr_buf[2]; - u8 data_buf[4] = {0}; - int ret = 0; - - if (len > sizeof(data_buf)) - return -EINVAL; - - put_unaligned_be16(reg, addr_buf); - msgs[0].addr = client->addr; - msgs[0].flags = 0; - msgs[0].len = sizeof(addr_buf); - msgs[0].buf = addr_buf; - msgs[1].addr = client->addr; - msgs[1].flags = I2C_M_RD; - msgs[1].len = len; - msgs[1].buf = &data_buf[sizeof(data_buf) - len]; - - ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); - - if (ret != ARRAY_SIZE(msgs)) - return ret < 0 ? ret : -EIO; - - *val = get_unaligned_be32(data_buf); - - return 0; -} - -static int ov01a10_write_reg(struct ov01a10 *ov01a10, u16 reg, u16 len, u32 val) -{ - struct i2c_client *client = v4l2_get_subdevdata(&ov01a10->sd); - u8 buf[6]; - int ret = 0; - - if (len > 4) - return -EINVAL; - - put_unaligned_be16(reg, buf); - put_unaligned_be32(val << 8 * (4 - len), buf + 2); - - ret = i2c_master_send(client, buf, len + 2); - if (ret != len + 2) - return ret < 0 ? ret : -EIO; - - return 0; -} - -static int ov01a10_write_reg_list(struct ov01a10 *ov01a10, - const struct ov01a10_reg_list *r_list) -{ - struct i2c_client *client = v4l2_get_subdevdata(&ov01a10->sd); - unsigned int i; - int ret = 0; - - for (i = 0; i < r_list->num_of_regs; i++) { - ret = ov01a10_write_reg(ov01a10, r_list->regs[i].address, 1, - r_list->regs[i].val); - if (ret) { - dev_err_ratelimited(&client->dev, - "write reg 0x%4.4x err = %d\n", - r_list->regs[i].address, ret); - return ret; - } - } - - return 0; -} - static int ov01a10_update_digital_gain(struct ov01a10 *ov01a10, u32 d_gain) { - struct i2c_client *client = v4l2_get_subdevdata(&ov01a10->sd); u32 real = d_gain << 6; int ret = 0; - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_DIGITAL_GAIN_B, 3, real); - if (ret) { - dev_err(&client->dev, "failed to set DIGITAL_GAIN_B\n"); - return ret; - } - - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_DIGITAL_GAIN_GB, 3, real); - if (ret) { - dev_err(&client->dev, "failed to set DIGITAL_GAIN_GB\n"); - return ret; - } - - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_DIGITAL_GAIN_GR, 3, real); - if (ret) { - dev_err(&client->dev, "failed to set DIGITAL_GAIN_GR\n"); - return ret; - } - - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_DIGITAL_GAIN_R, 3, real); - if (ret) - dev_err(&client->dev, "failed to set DIGITAL_GAIN_R\n"); + cci_write(ov01a10->regmap, OV01A10_REG_DIGITAL_GAIN_B, real, &ret); + cci_write(ov01a10->regmap, OV01A10_REG_DIGITAL_GAIN_GB, real, &ret); + cci_write(ov01a10->regmap, OV01A10_REG_DIGITAL_GAIN_GR, real, &ret); + cci_write(ov01a10->regmap, OV01A10_REG_DIGITAL_GAIN_R, real, &ret); return ret; } @@ -408,48 +317,39 @@ static int ov01a10_test_pattern(struct ov01a10 *ov01a10, u32 pattern) if (pattern) pattern |= OV01A10_TEST_PATTERN_ENABLE; - return ov01a10_write_reg(ov01a10, OV01A10_REG_TEST_PATTERN, 1, pattern); + return cci_write(ov01a10->regmap, OV01A10_REG_TEST_PATTERN, pattern, + NULL); } /* for vflip and hflip, use 0x9 as window offset to keep the bayer */ static int ov01a10_set_hflip(struct ov01a10 *ov01a10, u32 hflip) { - int ret; u32 val, offset; + int ret = 0; offset = hflip ? 0x8 : 0x9; - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_X_WIN, 1, offset); - if (ret) - return ret; + val = hflip ? 0 : FIELD_PREP(OV01A10_HFLIP_MASK, 0x1); - ret = ov01a10_read_reg(ov01a10, OV01A10_REG_FORMAT1, 1, &val); - if (ret) - return ret; + cci_write(ov01a10->regmap, OV01A10_REG_X_WIN, offset, &ret); + cci_update_bits(ov01a10->regmap, OV01A10_REG_FORMAT1, + OV01A10_HFLIP_MASK, val, &ret); - val = hflip ? val & ~OV01A10_HFLIP_MASK : - val | FIELD_PREP(OV01A10_HFLIP_MASK, 0x1); - - return ov01a10_write_reg(ov01a10, OV01A10_REG_FORMAT1, 1, val); + return ret; } static int ov01a10_set_vflip(struct ov01a10 *ov01a10, u32 vflip) { - int ret; u32 val, offset; + int ret = 0; offset = vflip ? 0x9 : 0x8; - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_Y_WIN, 1, offset); - if (ret) - return ret; + val = vflip ? FIELD_PREP(OV01A10_VFLIP_MASK, 0x1) : 0; - ret = ov01a10_read_reg(ov01a10, OV01A10_REG_FORMAT1, 1, &val); - if (ret) - return ret; + cci_write(ov01a10->regmap, OV01A10_REG_Y_WIN, offset, &ret); + cci_update_bits(ov01a10->regmap, OV01A10_REG_FORMAT1, + OV01A10_VFLIP_MASK, val, &ret); - val = vflip ? val | FIELD_PREP(OV01A10_VFLIP_MASK, 0x1) : - val & ~OV01A10_VFLIP_MASK; - - return ov01a10_write_reg(ov01a10, OV01A10_REG_FORMAT1, 1, val); + return ret; } static int ov01a10_set_ctrl(struct v4l2_ctrl *ctrl) @@ -474,8 +374,8 @@ static int ov01a10_set_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_ANALOGUE_GAIN: - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_ANALOG_GAIN, 2, - ctrl->val); + ret = cci_write(ov01a10->regmap, OV01A10_REG_ANALOG_GAIN, + ctrl->val, NULL); break; case V4L2_CID_DIGITAL_GAIN: @@ -483,13 +383,13 @@ static int ov01a10_set_ctrl(struct v4l2_ctrl *ctrl) break; case V4L2_CID_EXPOSURE: - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_EXPOSURE, 2, - ctrl->val); + ret = cci_write(ov01a10->regmap, OV01A10_REG_EXPOSURE, + ctrl->val, NULL); break; case V4L2_CID_VBLANK: - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_VTS, 2, - ov01a10->cur_mode->height + ctrl->val); + ret = cci_write(ov01a10->regmap, OV01A10_REG_VTS, + ov01a10->cur_mode->height + ctrl->val, NULL); break; case V4L2_CID_TEST_PATTERN: @@ -630,14 +530,16 @@ static int ov01a10_start_streaming(struct ov01a10 *ov01a10) link_freq_index = ov01a10->cur_mode->link_freq_index; reg_list = &link_freq_configs[link_freq_index].reg_list; - ret = ov01a10_write_reg_list(ov01a10, reg_list); + ret = regmap_multi_reg_write(ov01a10->regmap, reg_list->regs, + reg_list->num_of_regs); if (ret) { dev_err(&client->dev, "failed to set plls\n"); return ret; } reg_list = &ov01a10->cur_mode->reg_list; - ret = ov01a10_write_reg_list(ov01a10, reg_list); + ret = regmap_multi_reg_write(ov01a10->regmap, reg_list->regs, + reg_list->num_of_regs); if (ret) { dev_err(&client->dev, "failed to set mode\n"); return ret; @@ -647,23 +549,14 @@ static int ov01a10_start_streaming(struct ov01a10 *ov01a10) if (ret) return ret; - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_MODE_SELECT, 1, - OV01A10_MODE_STREAMING); - if (ret) - dev_err(&client->dev, "failed to start streaming\n"); - - return ret; + return cci_write(ov01a10->regmap, OV01A10_REG_MODE_SELECT, + OV01A10_MODE_STREAMING, NULL); } static void ov01a10_stop_streaming(struct ov01a10 *ov01a10) { - struct i2c_client *client = v4l2_get_subdevdata(&ov01a10->sd); - int ret = 0; - - ret = ov01a10_write_reg(ov01a10, OV01A10_REG_MODE_SELECT, 1, - OV01A10_MODE_STANDBY); - if (ret) - dev_err(&client->dev, "failed to stop streaming\n"); + cci_write(ov01a10->regmap, OV01A10_REG_MODE_SELECT, + OV01A10_MODE_STANDBY, NULL); } static int ov01a10_set_stream(struct v4l2_subdev *sd, int enable) @@ -841,14 +734,14 @@ static int ov01a10_identify_module(struct ov01a10 *ov01a10) { struct i2c_client *client = v4l2_get_subdevdata(&ov01a10->sd); int ret; - u32 val; + u64 val; - ret = ov01a10_read_reg(ov01a10, OV01A10_REG_CHIP_ID, 3, &val); + ret = cci_read(ov01a10->regmap, OV01A10_REG_CHIP_ID, &val, NULL); if (ret) return ret; if (val != OV01A10_CHIP_ID) { - dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + dev_err(&client->dev, "chip id mismatch: %x!=%llx\n", OV01A10_CHIP_ID, val); return -EIO; } @@ -879,6 +772,10 @@ static int ov01a10_probe(struct i2c_client *client) if (!ov01a10) return -ENOMEM; + ov01a10->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(ov01a10->regmap)) + return PTR_ERR(ov01a10->regmap); + v4l2_i2c_subdev_init(&ov01a10->sd, client, &ov01a10_subdev_ops); ov01a10->sd.internal_ops = &ov01a10_internal_ops; From f60e94ed1aa110e48c7798a2fc99022e3b03239b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:17 +0200 Subject: [PATCH 102/307] media: i2c: ov01a10: Remove overly verbose probe() error reporting Many of the functions called from ov01a10_probe() are expected to never fail and they should all already log some message if they fail. Remove the unnecessarily verbose dev_err[_probe]() calls from the error-exit paths in probe(). Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 34b7ab6eb286..f7aea9740a53 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -781,16 +781,13 @@ static int ov01a10_probe(struct i2c_client *client) ret = ov01a10_identify_module(ov01a10); if (ret) - return dev_err_probe(dev, ret, - "failed to find sensor\n"); + return ret; ov01a10->cur_mode = &supported_modes[0]; ret = ov01a10_init_controls(ov01a10); - if (ret) { - dev_err(dev, "failed to init controls: %d\n", ret); + if (ret) return ret; - } ov01a10->sd.state_lock = ov01a10->ctrl_handler.lock; ov01a10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; @@ -799,16 +796,12 @@ static int ov01a10_probe(struct i2c_client *client) ov01a10->pad.flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&ov01a10->sd.entity, 1, &ov01a10->pad); - if (ret) { - dev_err(dev, "Failed to init entity pads: %d\n", ret); + if (ret) goto err_handler_free; - } ret = v4l2_subdev_init_finalize(&ov01a10->sd); - if (ret) { - dev_err(dev, "Failed to allocate subdev state: %d\n", ret); + if (ret) goto err_media_entity_cleanup; - } /* * Device is already turned on by i2c-core with ACPI domain PM. @@ -819,10 +812,8 @@ static int ov01a10_probe(struct i2c_client *client) pm_runtime_idle(dev); ret = v4l2_async_register_subdev_sensor(&ov01a10->sd); - if (ret < 0) { - dev_err(dev, "Failed to register subdev: %d\n", ret); + if (ret) goto err_pm_disable; - } return 0; From ad134ffad2efcb490a75fa1720f9aa43d0f84fe0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:18 +0200 Subject: [PATCH 103/307] media: i2c: ov01a10: Store dev pointer in struct ov01a10 Now that the cci_* register access helpers are used we no longer need the i2c_client in various functions. Some code is still getting the client just to be able to get to the device pointer. Directly store a struct device *dev pointing to &client->dev inside struct ov01a10 to make the code simpler. This also fixes a mismatch of using dev vs &client->dev in the runtime_pm_*() calls in probe(). Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index f7aea9740a53..7677860c28ef 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -279,6 +279,7 @@ static const struct ov01a10_mode supported_modes[] = { }; struct ov01a10 { + struct device *dev; struct regmap *regmap; struct v4l2_subdev sd; struct media_pad pad; @@ -356,7 +357,6 @@ static int ov01a10_set_ctrl(struct v4l2_ctrl *ctrl) { struct ov01a10 *ov01a10 = container_of(ctrl->handler, struct ov01a10, ctrl_handler); - struct i2c_client *client = v4l2_get_subdevdata(&ov01a10->sd); s64 exposure_max; int ret = 0; @@ -369,7 +369,7 @@ static int ov01a10_set_ctrl(struct v4l2_ctrl *ctrl) exposure_max); } - if (!pm_runtime_get_if_in_use(&client->dev)) + if (!pm_runtime_get_if_in_use(ov01a10->dev)) return 0; switch (ctrl->id) { @@ -409,7 +409,7 @@ static int ov01a10_set_ctrl(struct v4l2_ctrl *ctrl) break; } - pm_runtime_put(&client->dev); + pm_runtime_put(ov01a10->dev); return ret; } @@ -420,7 +420,6 @@ static const struct v4l2_ctrl_ops ov01a10_ctrl_ops = { static int ov01a10_init_controls(struct ov01a10 *ov01a10) { - struct i2c_client *client = v4l2_get_subdevdata(&ov01a10->sd); struct v4l2_fwnode_device_properties props; u32 vblank_min, vblank_max, vblank_default; struct v4l2_ctrl_handler *ctrl_hdlr; @@ -429,7 +428,7 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) int ret = 0; int size; - ret = v4l2_fwnode_device_parse(&client->dev, &props); + ret = v4l2_fwnode_device_parse(ov01a10->dev, &props); if (ret) return ret; @@ -523,7 +522,6 @@ static void ov01a10_update_pad_format(const struct ov01a10_mode *mode, static int ov01a10_start_streaming(struct ov01a10 *ov01a10) { - struct i2c_client *client = v4l2_get_subdevdata(&ov01a10->sd); const struct ov01a10_reg_list *reg_list; int link_freq_index; int ret = 0; @@ -533,7 +531,7 @@ static int ov01a10_start_streaming(struct ov01a10 *ov01a10) ret = regmap_multi_reg_write(ov01a10->regmap, reg_list->regs, reg_list->num_of_regs); if (ret) { - dev_err(&client->dev, "failed to set plls\n"); + dev_err(ov01a10->dev, "failed to set plls\n"); return ret; } @@ -541,7 +539,7 @@ static int ov01a10_start_streaming(struct ov01a10 *ov01a10) ret = regmap_multi_reg_write(ov01a10->regmap, reg_list->regs, reg_list->num_of_regs); if (ret) { - dev_err(&client->dev, "failed to set mode\n"); + dev_err(ov01a10->dev, "failed to set mode\n"); return ret; } @@ -562,25 +560,24 @@ static void ov01a10_stop_streaming(struct ov01a10 *ov01a10) static int ov01a10_set_stream(struct v4l2_subdev *sd, int enable) { struct ov01a10 *ov01a10 = to_ov01a10(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); struct v4l2_subdev_state *state; int ret = 0; state = v4l2_subdev_lock_and_get_active_state(sd); if (enable) { - ret = pm_runtime_resume_and_get(&client->dev); + ret = pm_runtime_resume_and_get(ov01a10->dev); if (ret < 0) goto unlock; ret = ov01a10_start_streaming(ov01a10); if (ret) { - pm_runtime_put(&client->dev); + pm_runtime_put(ov01a10->dev); goto unlock; } } else { ov01a10_stop_streaming(ov01a10); - pm_runtime_put(&client->dev); + pm_runtime_put(ov01a10->dev); } unlock: @@ -732,7 +729,6 @@ static const struct media_entity_operations ov01a10_subdev_entity_ops = { static int ov01a10_identify_module(struct ov01a10 *ov01a10) { - struct i2c_client *client = v4l2_get_subdevdata(&ov01a10->sd); int ret; u64 val; @@ -741,7 +737,7 @@ static int ov01a10_identify_module(struct ov01a10 *ov01a10) return ret; if (val != OV01A10_CHIP_ID) { - dev_err(&client->dev, "chip id mismatch: %x!=%llx\n", + dev_err(ov01a10->dev, "chip id mismatch: %x!=%llx\n", OV01A10_CHIP_ID, val); return -EIO; } @@ -764,14 +760,15 @@ static void ov01a10_remove(struct i2c_client *client) static int ov01a10_probe(struct i2c_client *client) { - struct device *dev = &client->dev; struct ov01a10 *ov01a10; int ret = 0; - ov01a10 = devm_kzalloc(dev, sizeof(*ov01a10), GFP_KERNEL); + ov01a10 = devm_kzalloc(&client->dev, sizeof(*ov01a10), GFP_KERNEL); if (!ov01a10) return -ENOMEM; + ov01a10->dev = &client->dev; + ov01a10->regmap = devm_cci_regmap_init_i2c(client, 16); if (IS_ERR(ov01a10->regmap)) return PTR_ERR(ov01a10->regmap); @@ -808,8 +805,8 @@ static int ov01a10_probe(struct i2c_client *client) * Enable runtime PM and turn off the device. */ pm_runtime_set_active(&client->dev); - pm_runtime_enable(dev); - pm_runtime_idle(dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); ret = v4l2_async_register_subdev_sensor(&ov01a10->sd); if (ret) @@ -818,7 +815,7 @@ static int ov01a10_probe(struct i2c_client *client) return 0; err_pm_disable: - pm_runtime_disable(dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); v4l2_subdev_cleanup(&ov01a10->sd); From 6b0a5d40a36828f62fea6340f1691fee64df7f83 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:19 +0200 Subject: [PATCH 104/307] media: i2c: ov01a10: Add ov01a10_check_hwcfg() function Add a function to check that the number of mipi-lanes and there frequency are what the driver expects. Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 58 ++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 7677860c28ef..0ef4bbc93d66 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -293,6 +293,7 @@ struct ov01a10 { struct v4l2_ctrl *exposure; const struct ov01a10_mode *cur_mode; + u32 link_freq_index; }; static inline struct ov01a10 *to_ov01a10(struct v4l2_subdev *subdev) @@ -426,7 +427,6 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) const struct ov01a10_mode *cur_mode; s64 exposure_max, h_blank; int ret = 0; - int size; ret = v4l2_fwnode_device_parse(ov01a10->dev, &props); if (ret) @@ -438,12 +438,11 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) return ret; cur_mode = ov01a10->cur_mode; - size = ARRAY_SIZE(link_freq_menu_items); ov01a10->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &ov01a10_ctrl_ops, V4L2_CID_LINK_FREQ, - size - 1, 0, + ov01a10->link_freq_index, 0, link_freq_menu_items); if (ov01a10->link_freq) ov01a10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; @@ -745,6 +744,53 @@ static int ov01a10_identify_module(struct ov01a10 *ov01a10) return 0; } +static int ov01a10_check_hwcfg(struct ov01a10 *ov01a10) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep, *fwnode = dev_fwnode(ov01a10->dev); + unsigned long link_freq_bitmap; + int ret; + + /* + * Sometimes the fwnode graph is initialized by the bridge driver, + * wait for this. + */ + ep = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0, 0); + if (!ep) + return dev_err_probe(ov01a10->dev, -EPROBE_DEFER, + "waiting for fwnode graph endpoint\n"); + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return dev_err_probe(ov01a10->dev, ret, "parsing endpoint\n"); + + ret = v4l2_link_freq_to_bitmap(ov01a10->dev, + bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + link_freq_menu_items, + ARRAY_SIZE(link_freq_menu_items), + &link_freq_bitmap); + if (ret) + goto check_hwcfg_error; + + /* v4l2_link_freq_to_bitmap() guarantees at least 1 bit is set */ + ov01a10->link_freq_index = ffs(link_freq_bitmap) - 1; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV01A10_DATA_LANES) { + ret = dev_err_probe(ov01a10->dev, -EINVAL, + "number of CSI2 data lanes %u is not supported\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); + goto check_hwcfg_error; + } + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + return ret; +} + static void ov01a10_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); @@ -761,7 +807,7 @@ static void ov01a10_remove(struct i2c_client *client) static int ov01a10_probe(struct i2c_client *client) { struct ov01a10 *ov01a10; - int ret = 0; + int ret; ov01a10 = devm_kzalloc(&client->dev, sizeof(*ov01a10), GFP_KERNEL); if (!ov01a10) @@ -776,6 +822,10 @@ static int ov01a10_probe(struct i2c_client *client) v4l2_i2c_subdev_init(&ov01a10->sd, client, &ov01a10_subdev_ops); ov01a10->sd.internal_ops = &ov01a10_internal_ops; + ret = ov01a10_check_hwcfg(ov01a10); + if (ret) + return ret; + ret = ov01a10_identify_module(ov01a10); if (ret) return ret; From 4fbbdabb0c7db066eea17a4a2034816051c73888 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:20 +0200 Subject: [PATCH 105/307] media: i2c: ov01a10: Add power on/off sequencing support So far the ov01a10 driver has only been used on laptops with an IVSC chip where the IVSC chip controls the power on/off sequencing of the sensor. But there are also designs with an ov01a10 sensor where the kernel needs to directly take care of the power-sequencing, controlling clks, regulators and GPIOs. Add support for these designs. The 2 ms minimum reset assertion time is taken from other Omnivision sensor drivers like the ov5675. The 20 ms delay after reset de-assert comes from the out of tree ov01a1s driver. Signed-off-by: Hans de Goede Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 126 +++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 0ef4bbc93d66..fee24acef4b2 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -7,10 +7,14 @@ #include #include +#include +#include +#include #include #include #include #include +#include #include #include @@ -20,6 +24,7 @@ #define OV01A10_LINK_FREQ_400MHZ 400000000ULL #define OV01A10_SCLK 80000000LL #define OV01A10_DATA_LANES 1 +#define OV01A10_MCLK 19200000 #define OV01A10_REG_CHIP_ID CCI_REG24(0x300a) #define OV01A10_CHIP_ID 0x560141 @@ -278,6 +283,12 @@ static const struct ov01a10_mode supported_modes[] = { }, }; +static const char * const ov01a10_supply_names[] = { + "dovdd", /* Digital I/O power */ + "avdd", /* Analog power */ + "dvdd", /* Digital core power */ +}; + struct ov01a10 { struct device *dev; struct regmap *regmap; @@ -294,6 +305,11 @@ struct ov01a10 { const struct ov01a10_mode *cur_mode; u32 link_freq_index; + + struct clk *clk; + struct gpio_desc *reset; + struct gpio_desc *powerdown; + struct regulator_bulk_data supplies[ARRAY_SIZE(ov01a10_supply_names)]; }; static inline struct ov01a10 *to_ov01a10(struct v4l2_subdev *subdev) @@ -726,6 +742,92 @@ static const struct media_entity_operations ov01a10_subdev_entity_ops = { .link_validate = v4l2_subdev_link_validate, }; +static int ov01a10_get_pm_resources(struct ov01a10 *ov01a10) +{ + unsigned long freq; + int i, ret; + + ov01a10->clk = devm_v4l2_sensor_clk_get(ov01a10->dev, NULL); + if (IS_ERR(ov01a10->clk)) + return dev_err_probe(ov01a10->dev, PTR_ERR(ov01a10->clk), + "getting clock\n"); + + freq = clk_get_rate(ov01a10->clk); + if (freq != OV01A10_MCLK) + return dev_err_probe(ov01a10->dev, -EINVAL, + "external clock %lu is not supported", + freq); + + ov01a10->reset = devm_gpiod_get_optional(ov01a10->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(ov01a10->reset)) + return dev_err_probe(ov01a10->dev, PTR_ERR(ov01a10->reset), + "getting reset gpio\n"); + + ov01a10->powerdown = devm_gpiod_get_optional(ov01a10->dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(ov01a10->powerdown)) + return dev_err_probe(ov01a10->dev, PTR_ERR(ov01a10->powerdown), + "getting powerdown gpio\n"); + + for (i = 0; i < ARRAY_SIZE(ov01a10_supply_names); i++) + ov01a10->supplies[i].supply = ov01a10_supply_names[i]; + + ret = devm_regulator_bulk_get(ov01a10->dev, + ARRAY_SIZE(ov01a10_supply_names), + ov01a10->supplies); + if (ret) + return dev_err_probe(ov01a10->dev, ret, "getting regulators\n"); + + return 0; +} + +static int ov01a10_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov01a10 *ov01a10 = to_ov01a10(sd); + int ret; + + ret = clk_prepare_enable(ov01a10->clk); + if (ret) { + dev_err(dev, "Error enabling clk: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(ov01a10_supply_names), + ov01a10->supplies); + if (ret) { + dev_err(dev, "Error enabling regulators: %d\n", ret); + clk_disable_unprepare(ov01a10->clk); + return ret; + } + + if (ov01a10->reset || ov01a10->powerdown) { + /* Assert reset/powerdown for at least 2ms on back to back off-on */ + fsleep(2000); + gpiod_set_value_cansleep(ov01a10->powerdown, 0); + gpiod_set_value_cansleep(ov01a10->reset, 0); + fsleep(20000); + } + + return 0; +} + +static int ov01a10_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov01a10 *ov01a10 = to_ov01a10(sd); + + gpiod_set_value_cansleep(ov01a10->reset, 1); + gpiod_set_value_cansleep(ov01a10->powerdown, 1); + + regulator_bulk_disable(ARRAY_SIZE(ov01a10_supply_names), + ov01a10->supplies); + + clk_disable_unprepare(ov01a10->clk); + return 0; +} + static int ov01a10_identify_module(struct ov01a10 *ov01a10) { int ret; @@ -801,7 +903,10 @@ static void ov01a10_remove(struct i2c_client *client) v4l2_ctrl_handler_free(sd->ctrl_handler); pm_runtime_disable(&client->dev); - pm_runtime_set_suspended(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + ov01a10_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } } static int ov01a10_probe(struct i2c_client *client) @@ -826,15 +931,23 @@ static int ov01a10_probe(struct i2c_client *client) if (ret) return ret; - ret = ov01a10_identify_module(ov01a10); + ret = ov01a10_get_pm_resources(ov01a10); if (ret) return ret; + ret = ov01a10_power_on(&client->dev); + if (ret) + return ret; + + ret = ov01a10_identify_module(ov01a10); + if (ret) + goto err_power_off; + ov01a10->cur_mode = &supported_modes[0]; ret = ov01a10_init_controls(ov01a10); if (ret) - return ret; + goto err_power_off; ov01a10->sd.state_lock = ov01a10->ctrl_handler.lock; ov01a10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; @@ -875,9 +988,15 @@ err_media_entity_cleanup: err_handler_free: v4l2_ctrl_handler_free(ov01a10->sd.ctrl_handler); +err_power_off: + ov01a10_power_off(&client->dev); + return ret; } +static DEFINE_RUNTIME_DEV_PM_OPS(ov01a10_pm_ops, ov01a10_power_off, + ov01a10_power_on, NULL); + #ifdef CONFIG_ACPI static const struct acpi_device_id ov01a10_acpi_ids[] = { { "OVTI01A0" }, @@ -890,6 +1009,7 @@ MODULE_DEVICE_TABLE(acpi, ov01a10_acpi_ids); static struct i2c_driver ov01a10_i2c_driver = { .driver = { .name = "ov01a10", + .pm = pm_sleep_ptr(&ov01a10_pm_ops), .acpi_match_table = ACPI_PTR(ov01a10_acpi_ids), }, .probe = ov01a10_probe, From 82d1ab15cb16a249bc0995040837b198f18e0972 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:21 +0200 Subject: [PATCH 106/307] media: i2c: ov01a10: Don't update pixel_rate and link_freq from set_fmt The pixel_rate and link_freq never change, stop updating them on every set_fmt. Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index fee24acef4b2..747e91cd8828 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -619,8 +619,6 @@ static int ov01a10_set_format(struct v4l2_subdev *sd, if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { ov01a10->cur_mode = mode; - __v4l2_ctrl_s_ctrl(ov01a10->link_freq, mode->link_freq_index); - __v4l2_ctrl_s_ctrl_int64(ov01a10->pixel_rate, OV01A10_SCLK); vblank_def = mode->vts_def - mode->height; __v4l2_ctrl_modify_range(ov01a10->vblank, From 7f0912eaca4678b8fa86916024c11c84759245e8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:22 +0200 Subject: [PATCH 107/307] media: i2c: ov01a10: Move setting of ctrl->flags to after checking ctrl_hdlr->error Instead of checking successful creation of the link_freq and vblank controls, set their flags after checking ctrl_hdlr->error where it is guaranteed that the controls will exist. Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 747e91cd8828..6dcd982cf8eb 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -460,8 +460,6 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) V4L2_CID_LINK_FREQ, ov01a10->link_freq_index, 0, link_freq_menu_items); - if (ov01a10->link_freq) - ov01a10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ov01a10->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a10_ctrl_ops, V4L2_CID_PIXEL_RATE, 0, @@ -478,8 +476,6 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) ov01a10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a10_ctrl_ops, V4L2_CID_HBLANK, h_blank, h_blank, 1, h_blank); - if (ov01a10->hblank) - ov01a10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; v4l2_ctrl_new_std(ctrl_hdlr, &ov01a10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, OV01A10_ANAL_GAIN_MIN, OV01A10_ANAL_GAIN_MAX, @@ -516,6 +512,9 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) goto fail; } + ov01a10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + ov01a10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + ov01a10->sd.ctrl_handler = ctrl_hdlr; return 0; From 299b82078696ac14325a605b3d2727ff00d6e73e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:23 +0200 Subject: [PATCH 108/307] media: i2c: ov01a10: Use native and default for pixel-array size names According to the OV01A10 product-brief PDF the OV01A10 has an active pixel array size of 1296x816. In otherwords the native and active sizes are the same. Replace the (misspelled) ACTIVE defines for the default resolution of 1280x800 with DEFAULT to avoid giving the impression that the active pixel array size is only 1280x800. And replace PIXEL_ARRAY with NATIVE to make clear this is the native pixel array size / to match the V4L2_SEL_TGT_NATIVE_SIZE naming. Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 6dcd982cf8eb..3eb6445b8f00 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -34,10 +34,10 @@ #define OV01A10_MODE_STREAMING 0x01 /* pixel array */ -#define OV01A10_PIXEL_ARRAY_WIDTH 1296 -#define OV01A10_PIXEL_ARRAY_HEIGHT 816 -#define OV01A10_ACITVE_WIDTH 1280 -#define OV01A10_ACITVE_HEIGHT 800 +#define OV01A10_NATIVE_WIDTH 1296 +#define OV01A10_NATIVE_HEIGHT 816 +#define OV01A10_DEFAULT_WIDTH 1280 +#define OV01A10_DEFAULT_HEIGHT 800 /* vertical and horizontal timings */ #define OV01A10_REG_VTS CCI_REG16(0x380e) @@ -270,8 +270,8 @@ static const struct ov01a10_link_freq_config link_freq_configs[] = { static const struct ov01a10_mode supported_modes[] = { { - .width = OV01A10_ACITVE_WIDTH, - .height = OV01A10_ACITVE_HEIGHT, + .width = OV01A10_DEFAULT_WIDTH, + .height = OV01A10_DEFAULT_HEIGHT, .hts = OV01A10_HTS_DEF, .vts_def = OV01A10_VTS_DEF, .vts_min = OV01A10_VTS_MIN, @@ -642,8 +642,8 @@ static int ov01a10_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_format fmt = { .which = V4L2_SUBDEV_FORMAT_TRY, .format = { - .width = OV01A10_ACITVE_WIDTH, - .height = OV01A10_ACITVE_HEIGHT, + .width = OV01A10_DEFAULT_WIDTH, + .height = OV01A10_DEFAULT_HEIGHT, }, }; @@ -692,17 +692,17 @@ static int ov01a10_get_selection(struct v4l2_subdev *sd, case V4L2_SEL_TGT_CROP_BOUNDS: sel->r.top = 0; sel->r.left = 0; - sel->r.width = OV01A10_PIXEL_ARRAY_WIDTH; - sel->r.height = OV01A10_PIXEL_ARRAY_HEIGHT; + sel->r.width = OV01A10_NATIVE_WIDTH; + sel->r.height = OV01A10_NATIVE_HEIGHT; return 0; case V4L2_SEL_TGT_CROP: case V4L2_SEL_TGT_CROP_DEFAULT: - sel->r.top = (OV01A10_PIXEL_ARRAY_HEIGHT - - OV01A10_ACITVE_HEIGHT) / 2; - sel->r.left = (OV01A10_PIXEL_ARRAY_WIDTH - - OV01A10_ACITVE_WIDTH) / 2; - sel->r.width = OV01A10_ACITVE_WIDTH; - sel->r.height = OV01A10_ACITVE_HEIGHT; + sel->r.top = (OV01A10_NATIVE_HEIGHT - + OV01A10_DEFAULT_HEIGHT) / 2; + sel->r.left = (OV01A10_NATIVE_WIDTH - + OV01A10_DEFAULT_WIDTH) / 2; + sel->r.width = OV01A10_DEFAULT_WIDTH; + sel->r.height = OV01A10_DEFAULT_HEIGHT; return 0; } From 896f4e21af4f53719f841b647d084f4689d5ad6c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:24 +0200 Subject: [PATCH 109/307] media: i2c: ov01a10: Add cropping support / allow arbitrary sizes Remove the fixed mode list and add cropping support. The main reason for doing this is to allow libcamera to select 1292x812 instead of 1280x800 so that after the extra border which the CPU debayer code needs libcamera can output 1280x720 instead of 1276x720. This in turn allows google-meet to use 720p instead of it falling back to a pretty bad 360p. This has been tested on a Dell XPS 9320, with both libcamera as well as with Intel's out-of-tree psys driver + proprietary userspace stack. Libcamera asks for 1292x812 where as the Intel stack asks for 1280x800 and neither stack explicitly sets the crop-window. Hence the need for ov01a10_set_format() to adjust the crop-window if necessary. Note the differentiating between pattern_size and border_size is done in preparation for adding support for the monochrome OV01A1B model where coordinates still need to be aligned to a multiple of 2, but there will be no need for a border (border_size=0). Link: https://bugzilla.redhat.com/show_bug.cgi?id=2337593 Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 344 ++++++++++++++++++++++-------------- 1 file changed, 210 insertions(+), 134 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 3eb6445b8f00..349fd3d06df5 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -40,7 +40,6 @@ #define OV01A10_DEFAULT_HEIGHT 800 /* vertical and horizontal timings */ -#define OV01A10_REG_VTS CCI_REG16(0x380e) #define OV01A10_VTS_DEF 0x0700 #define OV01A10_VTS_MIN 0x0380 #define OV01A10_VTS_MAX 0xffff @@ -68,19 +67,26 @@ #define OV01A10_DGTL_GAIN_STEP 1 #define OV01A10_DGTL_GAIN_DEFAULT 1024 -/* test pattern control */ -#define OV01A10_REG_TEST_PATTERN CCI_REG8(0x4503) -#define OV01A10_TEST_PATTERN_ENABLE BIT(7) -#define OV01A10_LINK_FREQ_400MHZ_INDEX 0 +/* timing control */ +#define OV01A10_REG_X_ADDR_START CCI_REG16(0x3800) +#define OV01A10_REG_Y_ADDR_START CCI_REG16(0x3802) +#define OV01A10_REG_X_ADDR_END CCI_REG16(0x3804) +#define OV01A10_REG_Y_ADDR_END CCI_REG16(0x3806) +#define OV01A10_REG_X_OUTPUT_SIZE CCI_REG16(0x3808) +#define OV01A10_REG_Y_OUTPUT_SIZE CCI_REG16(0x380a) +#define OV01A10_REG_HTS CCI_REG16(0x380c) /* in units of 2 pixels */ +#define OV01A10_REG_VTS CCI_REG16(0x380e) +#define OV01A10_REG_X_WIN CCI_REG16(0x3810) +#define OV01A10_REG_Y_WIN CCI_REG16(0x3812) /* flip and mirror control */ #define OV01A10_REG_FORMAT1 CCI_REG8(0x3820) #define OV01A10_VFLIP_MASK BIT(4) #define OV01A10_HFLIP_MASK BIT(3) -/* window offset */ -#define OV01A10_REG_X_WIN CCI_REG16(0x3810) -#define OV01A10_REG_Y_WIN CCI_REG16(0x3812) +/* test pattern control */ +#define OV01A10_REG_TEST_PATTERN CCI_REG8(0x4503) +#define OV01A10_TEST_PATTERN_ENABLE BIT(7) /* * The native ov01a10 bayer-pattern is GBRG, but there was a driver bug enabling @@ -90,6 +96,7 @@ * when hflip is *disabled*. */ #define OV01A10_MEDIA_BUS_FMT MEDIA_BUS_FMT_SBGGR10_1X10 +#define OV01A10_BAYER_PATTERN_SIZE 2 /* 2x2 */ struct ov01a10_reg_list { u32 num_of_regs; @@ -100,17 +107,6 @@ struct ov01a10_link_freq_config { const struct ov01a10_reg_list reg_list; }; -struct ov01a10_mode { - u32 width; - u32 height; - u32 hts; - u32 vts_def; - u32 vts_min; - u32 link_freq_index; - - const struct ov01a10_reg_list reg_list; -}; - static const struct reg_sequence mipi_data_rate_720mbps[] = { {0x0103, 0x01}, {0x0302, 0x00}, @@ -127,7 +123,7 @@ static const struct reg_sequence mipi_data_rate_720mbps[] = { {0x0325, 0x68}, }; -static const struct reg_sequence sensor_1280x800_setting[] = { +static const struct reg_sequence ov01a10_global_setting[] = { {0x3002, 0xa1}, {0x301e, 0xf0}, {0x3022, 0x01}, @@ -179,26 +175,6 @@ static const struct reg_sequence sensor_1280x800_setting[] = { {0x37e4, 0x04}, {0x37e5, 0x03}, {0x37e6, 0x04}, - {0x3800, 0x00}, - {0x3801, 0x00}, - {0x3802, 0x00}, - {0x3803, 0x00}, - {0x3804, 0x05}, - {0x3805, 0x0f}, - {0x3806, 0x03}, - {0x3807, 0x2f}, - {0x3808, 0x05}, - {0x3809, 0x00}, - {0x380a, 0x03}, - {0x380b, 0x20}, - {0x380c, 0x02}, - {0x380d, 0xe8}, - {0x380e, 0x07}, - {0x380f, 0x00}, - {0x3810, 0x00}, - {0x3811, 0x09}, - {0x3812, 0x00}, - {0x3813, 0x08}, {0x3814, 0x01}, {0x3815, 0x01}, {0x3816, 0x01}, @@ -260,7 +236,7 @@ static const s64 link_freq_menu_items[] = { }; static const struct ov01a10_link_freq_config link_freq_configs[] = { - [OV01A10_LINK_FREQ_400MHZ_INDEX] = { + { .reg_list = { .num_of_regs = ARRAY_SIZE(mipi_data_rate_720mbps), .regs = mipi_data_rate_720mbps, @@ -268,19 +244,11 @@ static const struct ov01a10_link_freq_config link_freq_configs[] = { }, }; -static const struct ov01a10_mode supported_modes[] = { - { - .width = OV01A10_DEFAULT_WIDTH, - .height = OV01A10_DEFAULT_HEIGHT, - .hts = OV01A10_HTS_DEF, - .vts_def = OV01A10_VTS_DEF, - .vts_min = OV01A10_VTS_MIN, - .reg_list = { - .num_of_regs = ARRAY_SIZE(sensor_1280x800_setting), - .regs = sensor_1280x800_setting, - }, - .link_freq_index = OV01A10_LINK_FREQ_400MHZ_INDEX, - }, +static const struct v4l2_rect ov01a10_default_crop = { + .left = (OV01A10_NATIVE_WIDTH - OV01A10_DEFAULT_WIDTH) / 2, + .top = (OV01A10_NATIVE_HEIGHT - OV01A10_DEFAULT_HEIGHT) / 2, + .width = OV01A10_DEFAULT_WIDTH, + .height = OV01A10_DEFAULT_HEIGHT, }; static const char * const ov01a10_supply_names[] = { @@ -303,7 +271,6 @@ struct ov01a10 { struct v4l2_ctrl *hblank; struct v4l2_ctrl *exposure; - const struct ov01a10_mode *cur_mode; u32 link_freq_index; struct clk *clk; @@ -317,6 +284,22 @@ static inline struct ov01a10 *to_ov01a10(struct v4l2_subdev *subdev) return container_of(subdev, struct ov01a10, sd); } +static struct v4l2_mbus_framefmt *ov01a10_get_active_format(struct ov01a10 *ov01a10) +{ + struct v4l2_subdev_state *active_state = + v4l2_subdev_get_locked_active_state(&ov01a10->sd); + + return v4l2_subdev_state_get_format(active_state, 0); +} + +static struct v4l2_rect *ov01a10_get_active_crop(struct ov01a10 *ov01a10) +{ + struct v4l2_subdev_state *active_state = + v4l2_subdev_get_locked_active_state(&ov01a10->sd); + + return v4l2_subdev_state_get_crop(active_state, 0); +} + static int ov01a10_update_digital_gain(struct ov01a10 *ov01a10, u32 d_gain) { u32 real = d_gain << 6; @@ -339,13 +322,16 @@ static int ov01a10_test_pattern(struct ov01a10 *ov01a10, u32 pattern) NULL); } -/* for vflip and hflip, use 0x9 as window offset to keep the bayer */ static int ov01a10_set_hflip(struct ov01a10 *ov01a10, u32 hflip) { + struct v4l2_rect *crop = ov01a10_get_active_crop(ov01a10); u32 val, offset; int ret = 0; - offset = hflip ? 0x8 : 0x9; + offset = crop->left; + if (!hflip) + offset++; + val = hflip ? 0 : FIELD_PREP(OV01A10_HFLIP_MASK, 0x1); cci_write(ov01a10->regmap, OV01A10_REG_X_WIN, offset, &ret); @@ -357,10 +343,14 @@ static int ov01a10_set_hflip(struct ov01a10 *ov01a10, u32 hflip) static int ov01a10_set_vflip(struct ov01a10 *ov01a10, u32 vflip) { + struct v4l2_rect *crop = ov01a10_get_active_crop(ov01a10); u32 val, offset; int ret = 0; - offset = vflip ? 0x9 : 0x8; + offset = crop->top; + if (vflip) + offset++; + val = vflip ? FIELD_PREP(OV01A10_VFLIP_MASK, 0x1) : 0; cci_write(ov01a10->regmap, OV01A10_REG_Y_WIN, offset, &ret); @@ -374,12 +364,13 @@ static int ov01a10_set_ctrl(struct v4l2_ctrl *ctrl) { struct ov01a10 *ov01a10 = container_of(ctrl->handler, struct ov01a10, ctrl_handler); + struct v4l2_mbus_framefmt *fmt = ov01a10_get_active_format(ov01a10); s64 exposure_max; int ret = 0; if (ctrl->id == V4L2_CID_VBLANK) { - exposure_max = ov01a10->cur_mode->height + ctrl->val - - OV01A10_EXPOSURE_MAX_MARGIN; + exposure_max = fmt->height + ctrl->val - + OV01A10_EXPOSURE_MAX_MARGIN; __v4l2_ctrl_modify_range(ov01a10->exposure, ov01a10->exposure->minimum, exposure_max, ov01a10->exposure->step, @@ -406,7 +397,7 @@ static int ov01a10_set_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_VBLANK: ret = cci_write(ov01a10->regmap, OV01A10_REG_VTS, - ov01a10->cur_mode->height + ctrl->val, NULL); + fmt->height + ctrl->val, NULL); break; case V4L2_CID_TEST_PATTERN: @@ -440,7 +431,6 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) struct v4l2_fwnode_device_properties props; u32 vblank_min, vblank_max, vblank_default; struct v4l2_ctrl_handler *ctrl_hdlr; - const struct ov01a10_mode *cur_mode; s64 exposure_max, h_blank; int ret = 0; @@ -453,8 +443,6 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) if (ret) return ret; - cur_mode = ov01a10->cur_mode; - ov01a10->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &ov01a10_ctrl_ops, V4L2_CID_LINK_FREQ, @@ -465,14 +453,14 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) V4L2_CID_PIXEL_RATE, 0, OV01A10_SCLK, 1, OV01A10_SCLK); - vblank_min = cur_mode->vts_min - cur_mode->height; - vblank_max = OV01A10_VTS_MAX - cur_mode->height; - vblank_default = cur_mode->vts_def - cur_mode->height; + vblank_min = OV01A10_VTS_MIN - OV01A10_DEFAULT_HEIGHT; + vblank_max = OV01A10_VTS_MAX - OV01A10_DEFAULT_HEIGHT; + vblank_default = OV01A10_VTS_DEF - OV01A10_DEFAULT_HEIGHT; ov01a10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a10_ctrl_ops, V4L2_CID_VBLANK, vblank_min, vblank_max, 1, vblank_default); - h_blank = cur_mode->hts - cur_mode->width; + h_blank = OV01A10_HTS_DEF - OV01A10_DEFAULT_WIDTH; ov01a10->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a10_ctrl_ops, V4L2_CID_HBLANK, h_blank, h_blank, 1, h_blank); @@ -484,7 +472,7 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) OV01A10_DGTL_GAIN_MIN, OV01A10_DGTL_GAIN_MAX, OV01A10_DGTL_GAIN_STEP, OV01A10_DGTL_GAIN_DEFAULT); - exposure_max = cur_mode->vts_def - OV01A10_EXPOSURE_MAX_MARGIN; + exposure_max = OV01A10_VTS_DEF - OV01A10_EXPOSURE_MAX_MARGIN; ov01a10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a10_ctrl_ops, V4L2_CID_EXPOSURE, OV01A10_EXPOSURE_MIN, @@ -524,24 +512,48 @@ fail: return ret; } -static void ov01a10_update_pad_format(const struct ov01a10_mode *mode, - struct v4l2_mbus_framefmt *fmt) +static void ov01a10_fill_format(struct v4l2_mbus_framefmt *fmt, + unsigned int width, unsigned int height) { - fmt->width = mode->width; - fmt->height = mode->height; + memset(fmt, 0, sizeof(*fmt)); + fmt->width = width; + fmt->height = height; fmt->code = OV01A10_MEDIA_BUS_FMT; fmt->field = V4L2_FIELD_NONE; fmt->colorspace = V4L2_COLORSPACE_RAW; } +static int ov01a10_set_mode(struct ov01a10 *ov01a10) +{ + struct v4l2_mbus_framefmt *fmt = ov01a10_get_active_format(ov01a10); + int ret = 0; + + cci_write(ov01a10->regmap, OV01A10_REG_X_ADDR_START, 0, &ret); + cci_write(ov01a10->regmap, OV01A10_REG_Y_ADDR_START, 0, &ret); + cci_write(ov01a10->regmap, OV01A10_REG_X_ADDR_END, + OV01A10_NATIVE_WIDTH - 1, &ret); + cci_write(ov01a10->regmap, OV01A10_REG_Y_ADDR_END, + OV01A10_NATIVE_HEIGHT - 1, &ret); + cci_write(ov01a10->regmap, OV01A10_REG_X_OUTPUT_SIZE, + fmt->width, &ret); + cci_write(ov01a10->regmap, OV01A10_REG_Y_OUTPUT_SIZE, + fmt->height, &ret); + /* HTS register is in units of 2 pixels */ + cci_write(ov01a10->regmap, OV01A10_REG_HTS, + OV01A10_HTS_DEF / 2, &ret); + /* OV01A10_REG_VTS is set by vblank control */ + /* OV01A10_REG_X_WIN is set by hlip control */ + /* OV01A10_REG_Y_WIN is set by vflip control */ + + return ret; +} + static int ov01a10_start_streaming(struct ov01a10 *ov01a10) { const struct ov01a10_reg_list *reg_list; - int link_freq_index; - int ret = 0; + int ret; - link_freq_index = ov01a10->cur_mode->link_freq_index; - reg_list = &link_freq_configs[link_freq_index].reg_list; + reg_list = &link_freq_configs[ov01a10->link_freq_index].reg_list; ret = regmap_multi_reg_write(ov01a10->regmap, reg_list->regs, reg_list->num_of_regs); if (ret) { @@ -549,9 +561,14 @@ static int ov01a10_start_streaming(struct ov01a10 *ov01a10) return ret; } - reg_list = &ov01a10->cur_mode->reg_list; - ret = regmap_multi_reg_write(ov01a10->regmap, reg_list->regs, - reg_list->num_of_regs); + ret = regmap_multi_reg_write(ov01a10->regmap, ov01a10_global_setting, + ARRAY_SIZE(ov01a10_global_setting)); + if (ret) { + dev_err(ov01a10->dev, "failed to initialize sensor\n"); + return ret; + } + + ret = ov01a10_set_mode(ov01a10); if (ret) { dev_err(ov01a10->dev, "failed to set mode\n"); return ret; @@ -600,54 +617,64 @@ unlock: return ret; } +static void ov01a10_update_blank_ctrls(struct ov01a10 *ov01a10, + unsigned int width, unsigned int height) +{ + s32 hblank, vblank_def; + + vblank_def = OV01A10_VTS_DEF - height; + __v4l2_ctrl_modify_range(ov01a10->vblank, + OV01A10_VTS_MIN - height, + OV01A10_VTS_MAX - height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ov01a10->vblank, vblank_def); + + hblank = OV01A10_HTS_DEF - width; + __v4l2_ctrl_modify_range(ov01a10->hblank, hblank, hblank, 1, hblank); +} + static int ov01a10_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(sd_state, fmt->pad); + const int pattern_size = OV01A10_BAYER_PATTERN_SIZE; + const int border_size = OV01A10_BAYER_PATTERN_SIZE; struct ov01a10 *ov01a10 = to_ov01a10(sd); - const struct ov01a10_mode *mode; - struct v4l2_mbus_framefmt *format; - s32 vblank_def, h_blank; + unsigned int width, height; - mode = v4l2_find_nearest_size(supported_modes, - ARRAY_SIZE(supported_modes), width, - height, fmt->format.width, - fmt->format.height); + width = clamp_val(ALIGN(fmt->format.width, pattern_size), + pattern_size, + OV01A10_NATIVE_WIDTH - 2 * border_size); + height = clamp_val(ALIGN(fmt->format.height, pattern_size), + pattern_size, + OV01A10_NATIVE_HEIGHT - 2 * border_size); - ov01a10_update_pad_format(mode, &fmt->format); - - if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { - ov01a10->cur_mode = mode; - - vblank_def = mode->vts_def - mode->height; - __v4l2_ctrl_modify_range(ov01a10->vblank, - mode->vts_min - mode->height, - OV01A10_VTS_MAX - mode->height, 1, - vblank_def); - __v4l2_ctrl_s_ctrl(ov01a10->vblank, vblank_def); - h_blank = mode->hts - mode->width; - __v4l2_ctrl_modify_range(ov01a10->hblank, h_blank, h_blank, 1, - h_blank); + /* Center image for userspace which does not set the crop first */ + if (width != crop->width || height != crop->height) { + crop->left = ALIGN((OV01A10_NATIVE_WIDTH - width) / 2, + pattern_size); + crop->top = ALIGN((OV01A10_NATIVE_HEIGHT - height) / 2, + pattern_size); + crop->width = width; + crop->height = height; } - format = v4l2_subdev_state_get_format(sd_state, fmt->pad); - *format = fmt->format; + ov01a10_fill_format(&fmt->format, width, height); + *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + ov01a10_update_blank_ctrls(ov01a10, width, height); return 0; } static int ov01a10_init_state(struct v4l2_subdev *sd, - struct v4l2_subdev_state *state) + struct v4l2_subdev_state *sd_state) { - struct v4l2_subdev_format fmt = { - .which = V4L2_SUBDEV_FORMAT_TRY, - .format = { - .width = OV01A10_DEFAULT_WIDTH, - .height = OV01A10_DEFAULT_HEIGHT, - }, - }; - - ov01a10_set_format(sd, state, &fmt); + *v4l2_subdev_state_get_crop(sd_state, 0) = ov01a10_default_crop; + ov01a10_fill_format(v4l2_subdev_state_get_format(sd_state, 0), + OV01A10_DEFAULT_WIDTH, OV01A10_DEFAULT_HEIGHT); return 0; } @@ -668,14 +695,16 @@ static int ov01a10_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_size_enum *fse) { - if (fse->index >= ARRAY_SIZE(supported_modes) || - fse->code != OV01A10_MEDIA_BUS_FMT) + const int pattern_size = OV01A10_BAYER_PATTERN_SIZE; + const int border_size = OV01A10_BAYER_PATTERN_SIZE; + + if (fse->index) return -EINVAL; - fse->min_width = supported_modes[fse->index].width; - fse->max_width = fse->min_width; - fse->min_height = supported_modes[fse->index].height; - fse->max_height = fse->min_height; + fse->min_width = pattern_size; + fse->max_width = OV01A10_NATIVE_WIDTH - 2 * border_size; + fse->min_height = pattern_size; + fse->max_height = OV01A10_NATIVE_HEIGHT - 2 * border_size; return 0; } @@ -684,31 +713,79 @@ static int ov01a10_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { - if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) - return -EINVAL; + const int border_size = OV01A10_BAYER_PATTERN_SIZE; switch (sel->target) { - case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(state, sel->pad); + return 0; + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r = ov01a10_default_crop; + return 0; case V4L2_SEL_TGT_CROP_BOUNDS: - sel->r.top = 0; + /* Keep a border for hvflip shift to preserve bayer-pattern */ + sel->r.left = border_size; + sel->r.top = border_size; + sel->r.width = OV01A10_NATIVE_WIDTH - 2 * border_size; + sel->r.height = OV01A10_NATIVE_HEIGHT - 2 * border_size; + return 0; + case V4L2_SEL_TGT_NATIVE_SIZE: sel->r.left = 0; + sel->r.top = 0; sel->r.width = OV01A10_NATIVE_WIDTH; sel->r.height = OV01A10_NATIVE_HEIGHT; return 0; - case V4L2_SEL_TGT_CROP: - case V4L2_SEL_TGT_CROP_DEFAULT: - sel->r.top = (OV01A10_NATIVE_HEIGHT - - OV01A10_DEFAULT_HEIGHT) / 2; - sel->r.left = (OV01A10_NATIVE_WIDTH - - OV01A10_DEFAULT_WIDTH) / 2; - sel->r.width = OV01A10_DEFAULT_WIDTH; - sel->r.height = OV01A10_DEFAULT_HEIGHT; - return 0; } return -EINVAL; } +static int ov01a10_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + const int pattern_size = OV01A10_BAYER_PATTERN_SIZE; + const int border_size = OV01A10_BAYER_PATTERN_SIZE; + struct ov01a10 *ov01a10 = to_ov01a10(sd); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + struct v4l2_rect rect; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + /* + * Clamp the boundaries of the crop rectangle to the size of the sensor + * pixel array. Align to pattern-size to ensure pattern isn't disrupted. + */ + rect.left = clamp_val(ALIGN(sel->r.left, pattern_size), border_size, + OV01A10_NATIVE_WIDTH - 2 * border_size); + rect.top = clamp_val(ALIGN(sel->r.top, pattern_size), border_size, + OV01A10_NATIVE_HEIGHT - 2 * border_size); + rect.width = clamp_val(ALIGN(sel->r.width, pattern_size), pattern_size, + OV01A10_NATIVE_WIDTH - rect.left - border_size); + rect.height = clamp_val(ALIGN(sel->r.height, pattern_size), pattern_size, + OV01A10_NATIVE_HEIGHT - rect.top - border_size); + + crop = v4l2_subdev_state_get_crop(sd_state, sel->pad); + + /* Reset the output size if the crop rectangle size has changed */ + if (rect.width != crop->width || rect.height != crop->height) { + format = v4l2_subdev_state_get_format(sd_state, sel->pad); + format->width = rect.width; + format->height = rect.height; + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) + ov01a10_update_blank_ctrls(ov01a10, rect.width, + rect.height); + } + + *crop = rect; + sel->r = rect; + + return 0; +} + static const struct v4l2_subdev_core_ops ov01a10_core_ops = { .log_status = v4l2_ctrl_subdev_log_status, }; @@ -721,6 +798,7 @@ static const struct v4l2_subdev_pad_ops ov01a10_pad_ops = { .set_fmt = ov01a10_set_format, .get_fmt = v4l2_subdev_get_fmt, .get_selection = ov01a10_get_selection, + .set_selection = ov01a10_set_selection, .enum_mbus_code = ov01a10_enum_mbus_code, .enum_frame_size = ov01a10_enum_frame_size, }; @@ -940,8 +1018,6 @@ static int ov01a10_probe(struct i2c_client *client) if (ret) goto err_power_off; - ov01a10->cur_mode = &supported_modes[0]; - ret = ov01a10_init_controls(ov01a10); if (ret) goto err_power_off; From d4fdb4e6e05c46a2746b96811345f45c937c41c9 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:25 +0200 Subject: [PATCH 110/307] media: i2c: ov01a10: Remove struct ov01a10_reg_list After the conversion to the CCI register access helpers, struct ov01a10_reg_list is only used inside struct ov01a10_link_freq_config. Simplify things by embedding the ov01a10_reg_list members directly into struct ov01a10_link_freq_config. Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 349fd3d06df5..1af0af00df24 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -98,13 +98,9 @@ #define OV01A10_MEDIA_BUS_FMT MEDIA_BUS_FMT_SBGGR10_1X10 #define OV01A10_BAYER_PATTERN_SIZE 2 /* 2x2 */ -struct ov01a10_reg_list { - u32 num_of_regs; - const struct reg_sequence *regs; -}; - struct ov01a10_link_freq_config { - const struct ov01a10_reg_list reg_list; + const struct reg_sequence *regs; + int regs_len; }; static const struct reg_sequence mipi_data_rate_720mbps[] = { @@ -237,10 +233,8 @@ static const s64 link_freq_menu_items[] = { static const struct ov01a10_link_freq_config link_freq_configs[] = { { - .reg_list = { - .num_of_regs = ARRAY_SIZE(mipi_data_rate_720mbps), - .regs = mipi_data_rate_720mbps, - } + .regs = mipi_data_rate_720mbps, + .regs_len = ARRAY_SIZE(mipi_data_rate_720mbps), }, }; @@ -550,12 +544,12 @@ static int ov01a10_set_mode(struct ov01a10 *ov01a10) static int ov01a10_start_streaming(struct ov01a10 *ov01a10) { - const struct ov01a10_reg_list *reg_list; + const struct ov01a10_link_freq_config *freq_cfg; int ret; - reg_list = &link_freq_configs[ov01a10->link_freq_index].reg_list; - ret = regmap_multi_reg_write(ov01a10->regmap, reg_list->regs, - reg_list->num_of_regs); + freq_cfg = &link_freq_configs[ov01a10->link_freq_index]; + ret = regmap_multi_reg_write(ov01a10->regmap, freq_cfg->regs, + freq_cfg->regs_len); if (ret) { dev_err(ov01a10->dev, "failed to set plls\n"); return ret; From e8fefc67f2d9df05524b182ac9d3f3f712315e3b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:26 +0200 Subject: [PATCH 111/307] media: i2c: ov01a10: Replace exposure->min/step with direct define use The exposure minimum and step are constant use the defines for this instead of retrieving these from the exposure-control. Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 1af0af00df24..76c75da4d7c8 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -366,9 +366,8 @@ static int ov01a10_set_ctrl(struct v4l2_ctrl *ctrl) exposure_max = fmt->height + ctrl->val - OV01A10_EXPOSURE_MAX_MARGIN; __v4l2_ctrl_modify_range(ov01a10->exposure, - ov01a10->exposure->minimum, - exposure_max, ov01a10->exposure->step, - exposure_max); + OV01A10_EXPOSURE_MIN, exposure_max, + OV01A10_EXPOSURE_STEP, exposure_max); } if (!pm_runtime_get_if_in_use(ov01a10->dev)) From b9315439680f83caef233a6aea8a7126ecf02bcc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:27 +0200 Subject: [PATCH 112/307] media: i2c: ov01a10: Only set register 0x0305 once Stop setting register 0x0305 to one value from mipi_data_rate_720mbps only to override it with a different value from sensor_1280x800_setting. Instead directly set it to 0xf4. Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 76c75da4d7c8..2480925f9957 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -108,7 +108,7 @@ static const struct reg_sequence mipi_data_rate_720mbps[] = { {0x0302, 0x00}, {0x0303, 0x06}, {0x0304, 0x01}, - {0x0305, 0xe0}, + {0x0305, 0xf4}, {0x0306, 0x00}, {0x0308, 0x01}, {0x0309, 0x00}, @@ -216,7 +216,6 @@ static const struct reg_sequence ov01a10_global_setting[] = { {0x5200, 0x18}, {0x5004, 0x00}, {0x5080, 0x40}, - {0x0305, 0xf4}, {0x0325, 0xc2}, }; From a9aafc571a2aed699b1a88f2c30203e173838600 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:28 +0200 Subject: [PATCH 113/307] media: i2c: ov01a10: Remove values set by controls from global_setting[] Registers 0x3501 (exposure), 0x3508 (analogue-gain) and 0x4503 (test- pattern) are already set through __v4l2_ctrl_handler_setup() drop them from ov01a10_global_setting[]. Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 2480925f9957..1a02772bdf1c 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -123,11 +123,7 @@ static const struct reg_sequence ov01a10_global_setting[] = { {0x3002, 0xa1}, {0x301e, 0xf0}, {0x3022, 0x01}, - {0x3501, 0x03}, - {0x3502, 0x78}, {0x3504, 0x0c}, - {0x3508, 0x01}, - {0x3509, 0x00}, {0x3601, 0xc0}, {0x3603, 0x71}, {0x3610, 0x68}, @@ -197,7 +193,6 @@ static const struct reg_sequence ov01a10_global_setting[] = { {0x4300, 0xff}, {0x4301, 0x00}, {0x4302, 0x0f}, - {0x4503, 0x00}, {0x4601, 0x50}, {0x4800, 0x64}, {0x481f, 0x34}, From 12d3c5ffe298d99b4c0d87f8d381ae701bd71462 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:29 +0200 Subject: [PATCH 114/307] media: i2c: ov01a10: Add ov01a10_sensor_cfg struct Add a struct with some sensor variant (ov01a10 / ov01a1b / ov01a1s) specific settings. This is a preparation patch for adding support for the ov01a1s sensor which uses the same sensor with a different (RGBI) color-filter. Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 87 +++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 1a02772bdf1c..a909f1ff8a76 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -88,16 +88,6 @@ #define OV01A10_REG_TEST_PATTERN CCI_REG8(0x4503) #define OV01A10_TEST_PATTERN_ENABLE BIT(7) -/* - * The native ov01a10 bayer-pattern is GBRG, but there was a driver bug enabling - * hflip/mirroring by default resulting in BGGR. Because of this bug Intel's - * proprietary IPU6 userspace stack expects BGGR. So we report BGGR to not break - * userspace and fix things up by shifting the crop window-x coordinate by 1 - * when hflip is *disabled*. - */ -#define OV01A10_MEDIA_BUS_FMT MEDIA_BUS_FMT_SBGGR10_1X10 -#define OV01A10_BAYER_PATTERN_SIZE 2 /* 2x2 */ - struct ov01a10_link_freq_config { const struct reg_sequence *regs; int regs_len; @@ -245,9 +235,19 @@ static const char * const ov01a10_supply_names[] = { "dvdd", /* Digital core power */ }; +struct ov01a10_sensor_cfg { + const char *model; + u32 bus_fmt; + int pattern_size; + int border_size; + bool invert_hflip_shift; + bool invert_vflip_shift; +}; + struct ov01a10 { struct device *dev; struct regmap *regmap; + const struct ov01a10_sensor_cfg *cfg; struct v4l2_subdev sd; struct media_pad pad; struct v4l2_ctrl_handler ctrl_handler; @@ -310,14 +310,15 @@ static int ov01a10_test_pattern(struct ov01a10 *ov01a10, u32 pattern) NULL); } -static int ov01a10_set_hflip(struct ov01a10 *ov01a10, u32 hflip) +static int ov01a10_set_hflip(struct ov01a10 *ov01a10, bool hflip) { struct v4l2_rect *crop = ov01a10_get_active_crop(ov01a10); + const struct ov01a10_sensor_cfg *cfg = ov01a10->cfg; u32 val, offset; int ret = 0; offset = crop->left; - if (!hflip) + if ((hflip ^ cfg->invert_hflip_shift) && cfg->border_size) offset++; val = hflip ? 0 : FIELD_PREP(OV01A10_HFLIP_MASK, 0x1); @@ -329,14 +330,15 @@ static int ov01a10_set_hflip(struct ov01a10 *ov01a10, u32 hflip) return ret; } -static int ov01a10_set_vflip(struct ov01a10 *ov01a10, u32 vflip) +static int ov01a10_set_vflip(struct ov01a10 *ov01a10, bool vflip) { struct v4l2_rect *crop = ov01a10_get_active_crop(ov01a10); + const struct ov01a10_sensor_cfg *cfg = ov01a10->cfg; u32 val, offset; int ret = 0; offset = crop->top; - if (vflip) + if ((vflip ^ cfg->invert_vflip_shift) && cfg->border_size) offset++; val = vflip ? FIELD_PREP(OV01A10_VFLIP_MASK, 0x1) : 0; @@ -499,13 +501,14 @@ fail: return ret; } -static void ov01a10_fill_format(struct v4l2_mbus_framefmt *fmt, +static void ov01a10_fill_format(struct ov01a10 *ov01a10, + struct v4l2_mbus_framefmt *fmt, unsigned int width, unsigned int height) { memset(fmt, 0, sizeof(*fmt)); fmt->width = width; fmt->height = height; - fmt->code = OV01A10_MEDIA_BUS_FMT; + fmt->code = ov01a10->cfg->bus_fmt; fmt->field = V4L2_FIELD_NONE; fmt->colorspace = V4L2_COLORSPACE_RAW; } @@ -625,9 +628,9 @@ static int ov01a10_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_format *fmt) { struct v4l2_rect *crop = v4l2_subdev_state_get_crop(sd_state, fmt->pad); - const int pattern_size = OV01A10_BAYER_PATTERN_SIZE; - const int border_size = OV01A10_BAYER_PATTERN_SIZE; struct ov01a10 *ov01a10 = to_ov01a10(sd); + const int pattern_size = ov01a10->cfg->pattern_size; + const int border_size = ov01a10->cfg->border_size; unsigned int width, height; width = clamp_val(ALIGN(fmt->format.width, pattern_size), @@ -647,7 +650,7 @@ static int ov01a10_set_format(struct v4l2_subdev *sd, crop->height = height; } - ov01a10_fill_format(&fmt->format, width, height); + ov01a10_fill_format(ov01a10, &fmt->format, width, height); *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) @@ -659,8 +662,10 @@ static int ov01a10_set_format(struct v4l2_subdev *sd, static int ov01a10_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state) { + struct ov01a10 *ov01a10 = to_ov01a10(sd); + *v4l2_subdev_state_get_crop(sd_state, 0) = ov01a10_default_crop; - ov01a10_fill_format(v4l2_subdev_state_get_format(sd_state, 0), + ov01a10_fill_format(ov01a10, v4l2_subdev_state_get_format(sd_state, 0), OV01A10_DEFAULT_WIDTH, OV01A10_DEFAULT_HEIGHT); return 0; @@ -670,10 +675,12 @@ static int ov01a10_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) { + struct ov01a10 *ov01a10 = to_ov01a10(sd); + if (code->index > 0) return -EINVAL; - code->code = OV01A10_MEDIA_BUS_FMT; + code->code = ov01a10->cfg->bus_fmt; return 0; } @@ -682,8 +689,9 @@ static int ov01a10_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_size_enum *fse) { - const int pattern_size = OV01A10_BAYER_PATTERN_SIZE; - const int border_size = OV01A10_BAYER_PATTERN_SIZE; + struct ov01a10 *ov01a10 = to_ov01a10(sd); + const int pattern_size = ov01a10->cfg->pattern_size; + const int border_size = ov01a10->cfg->border_size; if (fse->index) return -EINVAL; @@ -700,7 +708,8 @@ static int ov01a10_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { - const int border_size = OV01A10_BAYER_PATTERN_SIZE; + struct ov01a10 *ov01a10 = to_ov01a10(sd); + const int border_size = ov01a10->cfg->border_size; switch (sel->target) { case V4L2_SEL_TGT_CROP: @@ -731,9 +740,9 @@ static int ov01a10_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { - const int pattern_size = OV01A10_BAYER_PATTERN_SIZE; - const int border_size = OV01A10_BAYER_PATTERN_SIZE; struct ov01a10 *ov01a10 = to_ov01a10(sd); + const int pattern_size = ov01a10->cfg->pattern_size; + const int border_size = ov01a10->cfg->border_size; struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; struct v4l2_rect rect; @@ -973,20 +982,28 @@ static void ov01a10_remove(struct i2c_client *client) static int ov01a10_probe(struct i2c_client *client) { + const struct ov01a10_sensor_cfg *cfg; struct ov01a10 *ov01a10; int ret; + cfg = device_get_match_data(&client->dev); + if (!cfg) + return -EINVAL; + ov01a10 = devm_kzalloc(&client->dev, sizeof(*ov01a10), GFP_KERNEL); if (!ov01a10) return -ENOMEM; ov01a10->dev = &client->dev; + ov01a10->cfg = cfg; ov01a10->regmap = devm_cci_regmap_init_i2c(client, 16); if (IS_ERR(ov01a10->regmap)) return PTR_ERR(ov01a10->regmap); v4l2_i2c_subdev_init(&ov01a10->sd, client, &ov01a10_subdev_ops); + /* Override driver->name with actual sensor model */ + v4l2_i2c_subdev_set_name(&ov01a10->sd, client, cfg->model, NULL); ov01a10->sd.internal_ops = &ov01a10_internal_ops; ret = ov01a10_check_hwcfg(ov01a10); @@ -1058,8 +1075,24 @@ static DEFINE_RUNTIME_DEV_PM_OPS(ov01a10_pm_ops, ov01a10_power_off, ov01a10_power_on, NULL); #ifdef CONFIG_ACPI +/* + * The native ov01a10 bayer-pattern is GBRG, but there was a driver bug enabling + * hflip/mirroring by default resulting in BGGR. Because of this bug Intel's + * proprietary IPU6 userspace stack expects BGGR. So we report BGGR to not break + * userspace and fix things up by shifting the crop window-x coordinate by 1 + * when hflip is *disabled*. + */ +static const struct ov01a10_sensor_cfg ov01a10_cfg = { + .model = "ov01a10", + .bus_fmt = MEDIA_BUS_FMT_SBGGR10_1X10, + .pattern_size = 2, /* 2x2 */ + .border_size = 2, + .invert_hflip_shift = true, + .invert_vflip_shift = false, +}; + static const struct acpi_device_id ov01a10_acpi_ids[] = { - { "OVTI01A0" }, + { "OVTI01A0", (uintptr_t)&ov01a10_cfg }, { } }; From 0c5ebba56b10cf6b09da6d90c7be8ad3b0c5c083 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:30 +0200 Subject: [PATCH 115/307] media: i2c: ov01a10: Optimize setting h/vflip values Since ov01a10_global_setting[] sets the initial format1 register value, there is no need to do a read-write-modify when setting the flip controls. Only write format1 when setting the flip-controls and remove the now unnecessary format1 register init from ov01a10_global_setting[]. Signed-off-by: Hans de Goede Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 41 +++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index a909f1ff8a76..6d245b638b2d 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -161,7 +161,6 @@ static const struct reg_sequence ov01a10_global_setting[] = { {0x3815, 0x01}, {0x3816, 0x01}, {0x3817, 0x01}, - {0x3820, 0xa8}, {0x3822, 0x13}, {0x3832, 0x28}, {0x3833, 0x10}, @@ -240,6 +239,7 @@ struct ov01a10_sensor_cfg { u32 bus_fmt; int pattern_size; int border_size; + u8 format1_base_val; bool invert_hflip_shift; bool invert_vflip_shift; }; @@ -258,6 +258,8 @@ struct ov01a10 { struct v4l2_ctrl *vblank; struct v4l2_ctrl *hblank; struct v4l2_ctrl *exposure; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; u32 link_freq_index; @@ -310,22 +312,33 @@ static int ov01a10_test_pattern(struct ov01a10 *ov01a10, u32 pattern) NULL); } +static void ov01a10_set_format1(struct ov01a10 *ov01a10, int *ret) +{ + u8 val = ov01a10->cfg->format1_base_val; + + /* hflip register bit is inverted */ + if (!ov01a10->hflip->val) + val |= FIELD_PREP(OV01A10_HFLIP_MASK, 0x1); + + if (ov01a10->vflip->val) + val |= FIELD_PREP(OV01A10_VFLIP_MASK, 0x1); + + cci_write(ov01a10->regmap, OV01A10_REG_FORMAT1, val, ret); +} + static int ov01a10_set_hflip(struct ov01a10 *ov01a10, bool hflip) { struct v4l2_rect *crop = ov01a10_get_active_crop(ov01a10); const struct ov01a10_sensor_cfg *cfg = ov01a10->cfg; - u32 val, offset; + u32 offset; int ret = 0; offset = crop->left; if ((hflip ^ cfg->invert_hflip_shift) && cfg->border_size) offset++; - val = hflip ? 0 : FIELD_PREP(OV01A10_HFLIP_MASK, 0x1); - cci_write(ov01a10->regmap, OV01A10_REG_X_WIN, offset, &ret); - cci_update_bits(ov01a10->regmap, OV01A10_REG_FORMAT1, - OV01A10_HFLIP_MASK, val, &ret); + ov01a10_set_format1(ov01a10, &ret); return ret; } @@ -334,18 +347,15 @@ static int ov01a10_set_vflip(struct ov01a10 *ov01a10, bool vflip) { struct v4l2_rect *crop = ov01a10_get_active_crop(ov01a10); const struct ov01a10_sensor_cfg *cfg = ov01a10->cfg; - u32 val, offset; + u32 offset; int ret = 0; offset = crop->top; if ((vflip ^ cfg->invert_vflip_shift) && cfg->border_size) offset++; - val = vflip ? FIELD_PREP(OV01A10_VFLIP_MASK, 0x1) : 0; - cci_write(ov01a10->regmap, OV01A10_REG_Y_WIN, offset, &ret); - cci_update_bits(ov01a10->regmap, OV01A10_REG_FORMAT1, - OV01A10_VFLIP_MASK, val, &ret); + ov01a10_set_format1(ov01a10, &ret); return ret; } @@ -474,10 +484,10 @@ static int ov01a10_init_controls(struct ov01a10 *ov01a10) ARRAY_SIZE(ov01a10_test_pattern_menu) - 1, 0, 0, ov01a10_test_pattern_menu); - v4l2_ctrl_new_std(ctrl_hdlr, &ov01a10_ctrl_ops, V4L2_CID_HFLIP, - 0, 1, 1, 0); - v4l2_ctrl_new_std(ctrl_hdlr, &ov01a10_ctrl_ops, V4L2_CID_VFLIP, - 0, 1, 1, 0); + ov01a10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a10_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + ov01a10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a10_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov01a10_ctrl_ops, &props); @@ -1087,6 +1097,7 @@ static const struct ov01a10_sensor_cfg ov01a10_cfg = { .bus_fmt = MEDIA_BUS_FMT_SBGGR10_1X10, .pattern_size = 2, /* 2x2 */ .border_size = 2, + .format1_base_val = 0xa0, .invert_hflip_shift = true, .invert_vflip_shift = false, }; From 36eab90d3f4f4c38d1e9635af40cdd40c7675471 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Oct 2025 19:40:31 +0200 Subject: [PATCH 116/307] media: i2c: ov01a10: Add ov01a1b support Add support for the ov01a1b model which is the exact same sensor as the ov01a10 without a color-filter. Note since there is no color-filter there is also no need to shift the crop-window when flipping, so the crop window set by userspace may cover the full sensor (border_size=0). Signed-off-by: Hans de Goede Reviewed-by: Mehdi Djait Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 6d245b638b2d..10227ae6fe42 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -1102,8 +1102,17 @@ static const struct ov01a10_sensor_cfg ov01a10_cfg = { .invert_vflip_shift = false, }; +static const struct ov01a10_sensor_cfg ov01a1b_cfg = { + .model = "ov01a1b", + .bus_fmt = MEDIA_BUS_FMT_Y10_1X10, + .pattern_size = 2, /* Keep coordinates aligned to a multiple of 2 */ + .border_size = 0, + .format1_base_val = 0xa0, +}; + static const struct acpi_device_id ov01a10_acpi_ids[] = { { "OVTI01A0", (uintptr_t)&ov01a10_cfg }, + { "OVTI01AB", (uintptr_t)&ov01a1b_cfg }, { } }; From 4011e67a989b828cbf885131933d322dfe80f33a Mon Sep 17 00:00:00 2001 From: Yemike Abhilash Chandra Date: Wed, 24 Dec 2025 15:50:24 +0530 Subject: [PATCH 117/307] media: ti: vpe: Re-introduce multi-instance and multi-client support vpdma_update_dma_addr() was originally added to support multi-instance and multi-client in vpdma. However it was dropped as it was unused. We are adding support for TI VIP. Some devices may have multiple VIP instances each with its own VPDMA engine. Within VIP, two slices can use a single VPDMA engine simultaneously. So support for multi instances and multiple clients is needed. Hence reintroduce multi-instance and multi-client support. Also add kernel doc and switch to GPL version of EXPORT_SYMBOL. This reverts commit 9314891df119442a6ec1518b3d872c330e2bf1a1. Signed-off-by: Yemike Abhilash Chandra Signed-off-by: Hans Verkuil --- drivers/media/platform/ti/vpe/vpdma.c | 48 +++++++++++++++++++++++++++ drivers/media/platform/ti/vpe/vpdma.h | 3 ++ 2 files changed, 51 insertions(+) diff --git a/drivers/media/platform/ti/vpe/vpdma.c b/drivers/media/platform/ti/vpe/vpdma.c index bb8a8bd7980c..29ee1918bc1c 100644 --- a/drivers/media/platform/ti/vpe/vpdma.c +++ b/drivers/media/platform/ti/vpe/vpdma.c @@ -552,6 +552,54 @@ EXPORT_SYMBOL(vpdma_submit_descs); static void dump_dtd(struct vpdma_dtd *dtd); +/** + * vpdma_update_dma_addr() - update DMA address in a descriptor + * @vpdma: VPDMA device context + * @list: vpdma desc list to which we add this descriptor + * @dma_addr: new DMA address to program into the descriptor + * @write_dtd: descriptor pointer used to compute write-back address + * @drop: if true, set the drop bit in the write descriptor + * @idx: index of the descriptor in the list to update + * + * Updates dma addresses of the descriptor at @idx in @list. + * This allows reusing an existing descriptor list with a new buffer + * address, instead of rebuilding the list, which is needed when + * multiple clients share the same VPDMA engine. The list buffer is + * unmapped before the update and remapped after. + */ +void vpdma_update_dma_addr(struct vpdma_data *vpdma, + struct vpdma_desc_list *list, + dma_addr_t dma_addr, + void *write_dtd, int drop, int idx) +{ + struct vpdma_dtd *dtd = list->buf.addr; + dma_addr_t write_desc_addr; + int offset; + + dtd += idx; + vpdma_unmap_desc_buf(vpdma, &list->buf); + + dtd->start_addr = dma_addr; + + /* Calculate write address from the offset of write_dtd from start + * of the list->buf + */ + offset = (void *)write_dtd - list->buf.addr; + write_desc_addr = list->buf.dma_addr + offset; + + if (drop) + dtd->desc_write_addr = dtd_desc_write_addr(write_desc_addr, + 1, 1, 0); + else + dtd->desc_write_addr = dtd_desc_write_addr(write_desc_addr, + 1, 0, 0); + + vpdma_map_desc_buf(vpdma, &list->buf); + + dump_dtd(dtd); +} +EXPORT_SYMBOL_GPL(vpdma_update_dma_addr); + void vpdma_set_max_size(struct vpdma_data *vpdma, int reg_addr, u32 width, u32 height) { diff --git a/drivers/media/platform/ti/vpe/vpdma.h b/drivers/media/platform/ti/vpe/vpdma.h index e4d7941c6207..5b3a0cd49a3c 100644 --- a/drivers/media/platform/ti/vpe/vpdma.h +++ b/drivers/media/platform/ti/vpe/vpdma.h @@ -222,6 +222,9 @@ void vpdma_free_desc_list(struct vpdma_desc_list *list); int vpdma_submit_descs(struct vpdma_data *vpdma, struct vpdma_desc_list *list, int list_num); bool vpdma_list_busy(struct vpdma_data *vpdma, int list_num); +void vpdma_update_dma_addr(struct vpdma_data *vpdma, + struct vpdma_desc_list *list, dma_addr_t dma_addr, + void *write_dtd, int drop, int idx); /* VPDMA hardware list funcs */ int vpdma_hwlist_alloc(struct vpdma_data *vpdma, void *priv); From 4c97845712874447702af83eb7db6e16d4259639 Mon Sep 17 00:00:00 2001 From: Yemike Abhilash Chandra Date: Wed, 24 Dec 2025 15:50:25 +0530 Subject: [PATCH 118/307] media: ti: vpe: Export vpdma_load_firmware() function Export vpdma_load_firmware() function which is needed by TI VIP to load the VPDMA firmware. Signed-off-by: Yemike Abhilash Chandra Signed-off-by: Hans Verkuil --- drivers/media/platform/ti/vpe/vpdma.c | 3 ++- drivers/media/platform/ti/vpe/vpdma.h | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/ti/vpe/vpdma.c b/drivers/media/platform/ti/vpe/vpdma.c index 29ee1918bc1c..573aa83f62eb 100644 --- a/drivers/media/platform/ti/vpe/vpdma.c +++ b/drivers/media/platform/ti/vpe/vpdma.c @@ -1135,7 +1135,7 @@ rel_fw: release_firmware(f); } -static int vpdma_load_firmware(struct vpdma_data *vpdma) +int vpdma_load_firmware(struct vpdma_data *vpdma) { int r; struct device *dev = &vpdma->pdev->dev; @@ -1152,6 +1152,7 @@ static int vpdma_load_firmware(struct vpdma_data *vpdma) return 0; } +EXPORT_SYMBOL_GPL(vpdma_load_firmware); int vpdma_create(struct platform_device *pdev, struct vpdma_data *vpdma, void (*cb)(struct platform_device *pdev)) diff --git a/drivers/media/platform/ti/vpe/vpdma.h b/drivers/media/platform/ti/vpe/vpdma.h index 5b3a0cd49a3c..1fc53fb33497 100644 --- a/drivers/media/platform/ti/vpe/vpdma.h +++ b/drivers/media/platform/ti/vpe/vpdma.h @@ -281,4 +281,7 @@ void vpdma_dump_regs(struct vpdma_data *vpdma); int vpdma_create(struct platform_device *pdev, struct vpdma_data *vpdma, void (*cb)(struct platform_device *pdev)); +/* load vpdma firmware*/ +int vpdma_load_firmware(struct vpdma_data *vpdma); + #endif From 7c0b084c04b69225ad38ac35a52047598af5bbbb Mon Sep 17 00:00:00 2001 From: Dale Farnsworth Date: Wed, 24 Dec 2025 15:50:26 +0530 Subject: [PATCH 119/307] dt-bindings: media: ti: vpe: Add support for Video Input Port Add device tree bindings for the Video Input Port. Video Input Port (VIP) can be found on devices such as DRA7xx and provides a parallel interface to a video source such as a sensor or TV decoder. Reviewed-by: Rob Herring (Arm) Signed-off-by: Dale Farnsworth Signed-off-by: Benoit Parrot Signed-off-by: Sukrut Bellary Signed-off-by: Yemike Abhilash Chandra Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/ti,vip.yaml | 152 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 153 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/ti,vip.yaml diff --git a/Documentation/devicetree/bindings/media/ti,vip.yaml b/Documentation/devicetree/bindings/media/ti,vip.yaml new file mode 100644 index 000000000000..e30cc461542b --- /dev/null +++ b/Documentation/devicetree/bindings/media/ti,vip.yaml @@ -0,0 +1,152 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2025 Texas Instruments Incorporated - http://www.ti.com/ +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/ti,vip.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments DRA7x Video Input Port (VIP) + +maintainers: + - Yemike Abhilash Chandra + +description: |- + Video Input Port (VIP) can be found on devices such as DRA7xx and + provides the system interface and the processing capability to + connect parallel image-sensor as well as BT.656/1120 capable encoder + chip to DRA7x device. + + Each VIP instance supports 2 independently configurable external + video input capture slices (Slice 0 and Slice 1) each providing + up to two video input ports (Port A and Port B). + +properties: + compatible: + enum: + - ti,dra7-vip + + reg: + maxItems: 1 + + interrupts: + items: + - description: IRQ index 0 is used for Slice0 interrupts + - description: IRQ index 1 is used for Slice1 interrupts + + ti,ctrl-module: + description: + Reference to the device control module that provides clock-edge + inversion control for VIP ports. These controls allow the + VIP to sample pixel data on the correct clock edge. + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + items: + - description: phandle to device control module + - description: offset to the CTRL_CORE_SMA_SW_1 register + - description: Bit field to slice 0 port A + - description: Bit field to slice 0 port B + - description: Bit field to slice 1 port A + - description: Bit field to slice 1 port B + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + patternProperties: + '^port@[0-3]$': + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: | + Each VIP instance supports 2 independently configurable external video + input capture slices (Slice 0 and Slice 1) each providing up to two video + input ports (Port A and Port B). These ports represent the following + port@0 -> Slice 0 Port A + port@1 -> Slice 0 Port B + port@2 -> Slice 1 Port A + port@3 -> Slice 1 Port B + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-width: + enum: [8, 16, 24] + default: 8 + +required: + - compatible + - reg + - interrupts + - ti,ctrl-module + - ports + +additionalProperties: false + +examples: + - | + #include + #include + + video@48970000 { + compatible = "ti,dra7-vip"; + reg = <0x48970000 0x1000>; + interrupts = , + ; + ti,ctrl-module = <&scm_conf 0x534 0x0 0x2 0x1 0x3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + vin1a: port@0 { + reg = <0>; + + vin1a_ep: endpoint { + remote-endpoint = <&camera1>; + hsync-active = <1>; + vsync-active = <1>; + pclk-sample = <0>; + bus-width = <8>; + }; + }; + + vin1b: port@1 { + reg = <1>; + + vin1b_ep: endpoint { + remote-endpoint = <&camera2>; + hsync-active = <1>; + vsync-active = <1>; + pclk-sample = <0>; + bus-width = <8>; + }; + }; + + vin2a: port@2 { + reg = <2>; + + vin2a_ep: endpoint { + remote-endpoint = <&camera3>; + hsync-active = <1>; + vsync-active = <1>; + pclk-sample = <0>; + bus-width = <16>; + }; + }; + + vin2b: port@3 { + reg = <3>; + + vin2b_ep: endpoint { + remote-endpoint = <&camera4>; + hsync-active = <1>; + vsync-active = <1>; + pclk-sample = <0>; + bus-width = <8>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index c1a8c25eec83..8fa4c1c961f1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26276,6 +26276,7 @@ S: Maintained W: http://linuxtv.org/ Q: http://patchwork.linuxtv.org/project/linux-media/list/ F: Documentation/devicetree/bindings/media/ti,cal.yaml +F: Documentation/devicetree/bindings/media/ti,vip.yaml F: Documentation/devicetree/bindings/media/ti,vpe.yaml F: drivers/media/platform/ti/cal/ F: drivers/media/platform/ti/vpe/ From fc2873aa4a21355593b55eb49ff534d31f81c584 Mon Sep 17 00:00:00 2001 From: Dale Farnsworth Date: Wed, 24 Dec 2025 15:50:27 +0530 Subject: [PATCH 120/307] media: ti: vpe: Add the VIP driver VIP stands for Video Input Port. It can be found on devices such as DRA7xx and provides a parallel interface to a video source such as a sensor or TV decoder. Each VIP can support two inputs (slices) and an SoC can be configured with a variable number of VIPs. Each slice can support two ports, each connected to its own sub-device. Signed-off-by: Dale Farnsworth Signed-off-by: Benoit Parrot Signed-off-by: Sukrut Bellary Signed-off-by: Yemike Abhilash Chandra Signed-off-by: Hans Verkuil --- drivers/media/platform/ti/Kconfig | 13 + drivers/media/platform/ti/vpe/Makefile | 2 + drivers/media/platform/ti/vpe/vip.c | 3673 ++++++++++++++++++++++++ drivers/media/platform/ti/vpe/vip.h | 717 +++++ 4 files changed, 4405 insertions(+) create mode 100644 drivers/media/platform/ti/vpe/vip.c create mode 100644 drivers/media/platform/ti/vpe/vip.h diff --git a/drivers/media/platform/ti/Kconfig b/drivers/media/platform/ti/Kconfig index 3bc4aa35887e..da33facf4467 100644 --- a/drivers/media/platform/ti/Kconfig +++ b/drivers/media/platform/ti/Kconfig @@ -41,6 +41,19 @@ config VIDEO_TI_CAL_MC default. Note that this behavior can be overridden via module parameter 'mc_api'. +config VIDEO_TI_VIP + tristate "TI Video Input Port" + depends on VIDEO_DEV + depends on SOC_DRA7XX || COMPILE_TEST + depends on HAS_DMA + select VIDEOBUF2_DMA_CONTIG + select VIDEO_TI_VPDMA + select VIDEO_TI_SC + select VIDEO_TI_CSC + help + Driver support for VIP module on certain TI SoC's + VIP = Video Input Port. + # Mem2mem drivers config VIDEO_TI_VPE diff --git a/drivers/media/platform/ti/vpe/Makefile b/drivers/media/platform/ti/vpe/Makefile index 3fadfe084f87..fbb0dec5a30e 100644 --- a/drivers/media/platform/ti/vpe/Makefile +++ b/drivers/media/platform/ti/vpe/Makefile @@ -3,10 +3,12 @@ obj-$(CONFIG_VIDEO_TI_VPE) += ti-vpe.o obj-$(CONFIG_VIDEO_TI_VPDMA) += ti-vpdma.o obj-$(CONFIG_VIDEO_TI_SC) += ti-sc.o obj-$(CONFIG_VIDEO_TI_CSC) += ti-csc.o +obj-$(CONFIG_VIDEO_TI_VIP) += ti-vip.o ti-vpe-y := vpe.o ti-vpdma-y := vpdma.o ti-sc-y := sc.o ti-csc-y := csc.o +ti-vip-y := vip.o ccflags-$(CONFIG_VIDEO_TI_VPE_DEBUG) += -DDEBUG diff --git a/drivers/media/platform/ti/vpe/vip.c b/drivers/media/platform/ti/vpe/vip.c new file mode 100644 index 000000000000..d4236ac6d867 --- /dev/null +++ b/drivers/media/platform/ti/vpe/vip.c @@ -0,0 +1,3673 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TI VIP capture driver + * + * Copyright (C) 2025 Texas Instruments Incorporated - http://www.ti.com/ + * David Griego, + * Dale Farnsworth, + * Yemike Abhilash Chandra, + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "vip.h" + +#define VIP_MODULE_NAME "vip" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-8)"); + +/* + * Minimum and maximum frame sizes + */ +#define MIN_W 128 +#define MIN_H 128 +#define MAX_W 2048 +#define MAX_H 1536 + +/* + * Required alignments + */ +#define S_ALIGN 0 /* multiple of 1 */ +#define H_ALIGN 1 /* multiple of 2 */ +#define W_ALIGN 1 /* multiple of 2 */ + +/* + * Need a descriptor entry for each of up to 15 outputs, + * and up to 2 control transfers. + */ +#define VIP_DESC_LIST_SIZE (17 * sizeof(struct vpdma_dtd)) + +/* + * port flag bits + */ +#define FLAG_INTERLACED BIT(4) +#define FLAG_MULT_PORT BIT(6) +#define FLAG_MULT_ANC BIT(7) + +#define VIP_VPDMA_FIFO_SIZE 2 +#define VIP_DROPQ_SIZE 3 + +/* + * Define indices into the srce_info tables + */ + +#define VIP_SRCE_MULT_PORT 0 +#define VIP_SRCE_MULT_ANC 1 +#define VIP_SRCE_LUMA 2 +#define VIP_SRCE_CHROMA 3 +#define VIP_SRCE_RGB 4 + +#define reg_read(dev, offset) ioread32((dev)->base + (offset)) +#define reg_write(dev, offset, val) iowrite32((val), (dev)->base + (offset)) + +#define GET_OFFSET_TOP(port, obj, reg) \ + ((obj)->base - (port)->dev->base + (reg)) + +#define VIP_SET_MMR_ADB_HDR(port, hdr, regs, offset_a) \ + VPDMA_SET_MMR_ADB_HDR((port)->mmr_adb, vip_mmr_adb, hdr, regs, offset_a) + +/* + * These represent the module resets bit for slice 1 + * Upon detecting slice2 we simply left shift by 1 + */ +#define VIP_DP_RST BIT(16) +#define VIP_CSC_RST BIT(20) +#define VIP_SC_RST BIT(22) + +#define VIP_PARSER_PORT(p) (VIP_PARSER_PORTA_0 + ((p) * 0x8U)) +#define VIP_PARSER_CROP_H_PORT(p) \ + (VIP_PARSER_PORTA_EXTRA4 + ((p) * 0x10U)) +#define VIP_PARSER_CROP_V_PORT(p) \ + (VIP_PARSER_PORTA_EXTRA5 + ((p) * 0x10U)) +#define VIP_PARSER_STOP_IMM_PORT(p) (VIP_PARSER_PORTA_EXTRA6 + ((p) * 0x4U)) + +#define PARSER_IRQ_MASK (VIP_PORTA_OUTPUT_FIFO_YUV | \ + VIP_PORTB_OUTPUT_FIFO_YUV) + +/* + * The srce_info structure contains per-srce data. + */ +struct vip_srce_info { + u8 base_channel; /* the VPDMA channel number */ + u8 vb_index; /* input frame f, f-1, f-2 index */ + u8 vb_part; /* identifies section of co-planar formats */ +}; + +static struct vip_srce_info srce_info[5] = { + [VIP_SRCE_MULT_PORT] = { + .base_channel = VIP1_CHAN_NUM_MULT_PORT_A_SRC0, + .vb_index = 0, + .vb_part = VIP_CHROMA, + }, + [VIP_SRCE_MULT_ANC] = { + .base_channel = VIP1_CHAN_NUM_MULT_ANC_A_SRC0, + .vb_index = 0, + .vb_part = VIP_LUMA, + }, + [VIP_SRCE_LUMA] = { + .base_channel = VIP1_CHAN_NUM_PORT_A_LUMA, + .vb_index = 1, + .vb_part = VIP_LUMA, + }, + [VIP_SRCE_CHROMA] = { + .base_channel = VIP1_CHAN_NUM_PORT_A_CHROMA, + .vb_index = 1, + .vb_part = VIP_CHROMA, + }, + [VIP_SRCE_RGB] = { + .base_channel = VIP1_CHAN_NUM_PORT_A_RGB, + .vb_part = VIP_LUMA, + }, +}; + +static struct vip_fmt vip_formats[VIP_MAX_ACTIVE_FMT] = { + { + .fourcc = V4L2_PIX_FMT_NV12, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .coplanar = 1, + .vpdma_fmt = { &vpdma_yuv_fmts[VPDMA_DATA_FMT_Y420], + &vpdma_yuv_fmts[VPDMA_DATA_FMT_C420], + }, + }, + { + .fourcc = V4L2_PIX_FMT_UYVY, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_yuv_fmts[VPDMA_DATA_FMT_CBY422], + }, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_yuv_fmts[VPDMA_DATA_FMT_YCB422], + }, + }, + { + .fourcc = V4L2_PIX_FMT_VYUY, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_yuv_fmts[VPDMA_DATA_FMT_CRY422], + }, + }, + { + .fourcc = V4L2_PIX_FMT_YVYU, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_yuv_fmts[VPDMA_DATA_FMT_YCR422], + }, + }, + { + .fourcc = V4L2_PIX_FMT_RGB24, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_RGB24], + }, + }, + { + .fourcc = V4L2_PIX_FMT_RGB32, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_ARGB32], + }, + }, + { + .fourcc = V4L2_PIX_FMT_BGR24, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_BGR24], + }, + }, + { + .fourcc = V4L2_PIX_FMT_BGR32, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_ABGR32], + }, + }, + { + .fourcc = V4L2_PIX_FMT_RGB24, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .coplanar = 0, + .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_RGB24], + }, + }, + { + .fourcc = V4L2_PIX_FMT_RGB32, + .code = MEDIA_BUS_FMT_ARGB8888_1X32, + .coplanar = 0, + .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_ARGB32], + }, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_raw_fmts[VPDMA_DATA_FMT_RAW8], + }, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_raw_fmts[VPDMA_DATA_FMT_RAW8], + }, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_raw_fmts[VPDMA_DATA_FMT_RAW8], + }, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .coplanar = 0, + .vpdma_fmt = { &vpdma_raw_fmts[VPDMA_DATA_FMT_RAW8], + }, + }, + { + /* V4L2 currently only defines one 16 bit variant */ + .fourcc = V4L2_PIX_FMT_SBGGR16, + .code = MEDIA_BUS_FMT_SBGGR16_1X16, + .coplanar = 0, + .vpdma_fmt = { &vpdma_raw_fmts[VPDMA_DATA_FMT_RAW16], + }, + }, +}; + +/* + * DMA address/data block for the shadow registers + */ +struct vip_mmr_adb { + struct vpdma_adb_hdr sc_hdr0; + u32 sc_regs0[7]; + u32 sc_pad0[1]; + struct vpdma_adb_hdr sc_hdr8; + u32 sc_regs8[6]; + u32 sc_pad8[2]; + struct vpdma_adb_hdr sc_hdr17; + u32 sc_regs17[9]; + u32 sc_pad17[3]; + struct vpdma_adb_hdr csc_hdr; + u32 csc_regs[6]; + u32 csc_pad[2]; +}; + +/* + * Function prototype declarations + */ +static int alloc_port(struct vip_dev *, int); +static void free_port(struct vip_port *); +static int vip_setup_parser(struct vip_port *port); +static int vip_setup_scaler(struct vip_stream *stream); +static void vip_enable_parser(struct vip_port *port, bool on); +static void vip_reset_parser(struct vip_port *port, bool on); +static void vip_parser_stop_imm(struct vip_port *port, bool on); +static void stop_dma(struct vip_stream *stream, bool clear_list); +static int vip_load_vpdma_list_fifo(struct vip_stream *stream); +static inline bool is_scaler_available(struct vip_port *port); +static inline bool allocate_scaler(struct vip_port *port); +static inline void free_scaler(struct vip_port *port); +static bool is_csc_available(struct vip_port *port); +static bool allocate_csc(struct vip_port *port, + enum vip_csc_state csc_direction); +static void free_csc(struct vip_port *port); + +/* initialize v4l2_format_info member in vip_formats array */ +static void vip_init_format_info(struct device *dev) +{ + struct vip_fmt *fmt; + int i; + + for (i = 0; i < ARRAY_SIZE(vip_formats); i++) { + fmt = &vip_formats[i]; + fmt->finfo = v4l2_format_info(fmt->fourcc); + } +} + +/* Print Four-character-code (FOURCC) */ +static char *fourcc_to_str(u32 fmt) +{ + static char code[5]; + + code[0] = (unsigned char)(fmt & 0xff); + code[1] = (unsigned char)((fmt >> 8) & 0xff); + code[2] = (unsigned char)((fmt >> 16) & 0xff); + code[3] = (unsigned char)((fmt >> 24) & 0xff); + code[4] = '\0'; + + return code; +} + +/* + * Find our format description corresponding to the passed v4l2_format + */ +static struct vip_fmt *find_port_format_by_pix(struct vip_port *port, + u32 pixelformat) +{ + struct vip_fmt *fmt; + unsigned int index; + + for (index = 0; index < port->num_active_fmt; index++) { + fmt = port->active_fmt[index]; + if (fmt->fourcc == pixelformat) + return fmt; + } + + return NULL; +} + +static struct vip_fmt *find_port_format_by_code(struct vip_port *port, + u32 code) +{ + struct vip_fmt *fmt; + unsigned int index; + + for (index = 0; index < port->num_active_fmt; index++) { + fmt = port->active_fmt[index]; + if (fmt->code == code) + return fmt; + } + + return NULL; +} + +inline struct vip_port *notifier_to_vip_port(struct v4l2_async_notifier *n) +{ + return container_of(n, struct vip_port, notifier); +} + +static bool vip_is_mbuscode_yuv(u32 code) +{ + return ((code & 0xff00) == 0x2000); +} + +static bool vip_is_mbuscode_rgb(u32 code) +{ + return ((code & 0xff00) == 0x1000); +} + +static bool vip_is_mbuscode_raw(u32 code) +{ + return ((code & 0xff00) == 0x3000); +} + +/* + * This is not an accurate conversion but it is only used to + * assess if color conversion is needed. + */ +static u32 vip_mbus_code_to_fourcc(u32 code) +{ + if (vip_is_mbuscode_rgb(code)) + return V4L2_PIX_FMT_RGB24; + + if (vip_is_mbuscode_yuv(code)) + return V4L2_PIX_FMT_UYVY; + + return V4L2_PIX_FMT_SBGGR8; +} + +static enum vip_csc_state +vip_csc_direction(u32 src_code, const struct v4l2_format_info *dfinfo) +{ + if (vip_is_mbuscode_yuv(src_code) && v4l2_is_format_rgb(dfinfo)) + return VIP_CSC_Y2R; + else if (vip_is_mbuscode_rgb(src_code) && v4l2_is_format_yuv(dfinfo)) + return VIP_CSC_R2Y; + else + return VIP_CSC_NA; +} + +/* + * Insert a masked field into a 32-bit field + */ +static void insert_field(u32 *valp, u32 field, u32 mask, int shift) +{ + u32 val = *valp; + + val &= ~(mask << shift); + val |= (field & mask) << shift; + *valp = val; +} + +/* + * Set the headers for all of the address/data block structures. + */ +static void init_adb_hdrs(struct vip_port *port) +{ + VIP_SET_MMR_ADB_HDR(port, sc_hdr0, sc_regs0, + GET_OFFSET_TOP(port, port->dev->sc, CFG_SC0)); + VIP_SET_MMR_ADB_HDR(port, sc_hdr8, sc_regs8, + GET_OFFSET_TOP(port, port->dev->sc, CFG_SC8)); + VIP_SET_MMR_ADB_HDR(port, sc_hdr17, sc_regs17, + GET_OFFSET_TOP(port, port->dev->sc, CFG_SC17)); + VIP_SET_MMR_ADB_HDR(port, csc_hdr, csc_regs, + GET_OFFSET_TOP(port, port->dev->csc, CSC_CSC00)); + +}; + +static void vip_module_toggle(struct vip_dev *dev, uint32_t module, bool on) +{ + u32 val = 0; + + val = reg_read(dev, VIP_CLK_RESET); + + if (dev->slice_id == VIP_SLICE2) + module <<= 1; + + if (on) + val |= module; + else + val &= ~module; + + reg_write(dev, VIP_CLK_RESET, val); +} + +/* + * Enable or disable the VIP clocks + */ +static void vip_set_clock_enable(struct vip_dev *dev, bool on) +{ + u32 val = 0; + + val = reg_read(dev, VIP_CLK_ENABLE); + if (on) { + val |= VIP_VPDMA_CLK_ENABLE; + if (dev->slice_id == VIP_SLICE1) + val |= VIP_VIP1_DATA_PATH_CLK_ENABLE; + else + val |= VIP_VIP2_DATA_PATH_CLK_ENABLE; + } else { + if (dev->slice_id == VIP_SLICE1) + val &= ~VIP_VIP1_DATA_PATH_CLK_ENABLE; + else + val &= ~VIP_VIP2_DATA_PATH_CLK_ENABLE; + + /* Both VIP are disabled then shutdown VPDMA also */ + if (!(val & (VIP_VIP1_DATA_PATH_CLK_ENABLE | + VIP_VIP2_DATA_PATH_CLK_ENABLE))) + val = 0; + } + + reg_write(dev, VIP_CLK_ENABLE, val); +} + +/* This helper function is used to enable the clock early on to + * enable vpdma firmware loading before the slice device are created + */ +static void vip_shared_set_clock_enable(struct vip_shared *shared, bool on) +{ + u32 val = 0; + + val = VIP_VIP1_DATA_PATH_CLK_ENABLE | VIP_VPDMA_CLK_ENABLE; + + reg_write(shared, VIP_CLK_ENABLE, val); +} + +static void vip_top_reset(struct vip_dev *dev) +{ + u32 val = 0; + + val = reg_read(dev, VIP_CLK_RESET); + + if (dev->slice_id == VIP_SLICE1) + insert_field(&val, 1, VIP_DATA_PATH_CLK_RESET_MASK, + VIP_VIP1_DATA_PATH_RESET_SHIFT); + else + insert_field(&val, 1, VIP_DATA_PATH_CLK_RESET_MASK, + VIP_VIP2_DATA_PATH_RESET_SHIFT); + + reg_write(dev, VIP_CLK_RESET, val); + + usleep_range(200, 250); + + val = reg_read(dev, VIP_CLK_RESET); + + if (dev->slice_id == VIP_SLICE1) + insert_field(&val, 0, VIP_DATA_PATH_CLK_RESET_MASK, + VIP_VIP1_DATA_PATH_RESET_SHIFT); + else + insert_field(&val, 0, VIP_DATA_PATH_CLK_RESET_MASK, + VIP_VIP2_DATA_PATH_RESET_SHIFT); + reg_write(dev, VIP_CLK_RESET, val); +} + +static void vip_top_vpdma_reset(struct vip_shared *shared) +{ + u32 val; + + val = reg_read(shared, VIP_CLK_RESET); + insert_field(&val, 1, VIP_VPDMA_CLK_RESET_MASK, + VIP_VPDMA_CLK_RESET_SHIFT); + reg_write(shared, VIP_CLK_RESET, val); + + usleep_range(200, 250); + + val = reg_read(shared, VIP_CLK_RESET); + insert_field(&val, 0, VIP_VPDMA_CLK_RESET_MASK, + VIP_VPDMA_CLK_RESET_SHIFT); + reg_write(shared, VIP_CLK_RESET, val); +} + +static void vip_set_pclk_invert(struct vip_port *port) +{ + struct vip_ctrl_module *ctrl = port->dev->syscon; + struct vip_dev *dev = port->dev; + u32 index; + /* + * When the VIP parser is configured to so that the pixel clock + * is to be sampled at falling edge, the pixel clock needs to be + * inverted before it is given to the VIP module. This is done + * by setting a bit in the CTRL_CORE_SMA_SW1 register. + */ + + index = 2 * port->dev->slice_id + port->port_id; + + v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: slice%d:port%d -> index: %d\n", __func__, + port->dev->slice_id, port->port_id, index); + + if (ctrl->syscon_pol) + regmap_update_bits(ctrl->syscon_pol, + ctrl->syscon_offset, + ctrl->syscon_bit_field[index], + ctrl->syscon_bit_field[index]); +} + +static void vip_set_data_interface(struct vip_port *port, + enum data_interface_modes mode) +{ + u32 val = 0; + + insert_field(&val, mode, VIP_DATA_INTERFACE_MODE_MASK, + VIP_DATA_INTERFACE_MODE_SHFT); + + reg_write(port->dev->parser, VIP_PARSER_MAIN_CFG, val); +} + +static void vip_set_slice_path(struct vip_dev *dev, + enum data_path_select data_path, u32 path_val) +{ + u32 val = 0; + int data_path_reg; + + data_path_reg = VIP_VIP1_DATA_PATH_SELECT + 4 * dev->slice_id; + + switch (data_path) { + case ALL_FIELDS_DATA_SELECT: + val |= path_val; + break; + case VIP_CSC_SRC_DATA_SELECT: + insert_field(&val, path_val, VIP_CSC_SRC_SELECT_MASK, + VIP_CSC_SRC_SELECT_SHFT); + break; + case VIP_SC_SRC_DATA_SELECT: + insert_field(&val, path_val, VIP_SC_SRC_SELECT_MASK, + VIP_SC_SRC_SELECT_SHFT); + break; + case VIP_RGB_SRC_DATA_SELECT: + val |= (path_val) ? VIP_RGB_SRC_SELECT : 0; + break; + case VIP_RGB_OUT_LO_DATA_SELECT: + val |= (path_val) ? VIP_RGB_OUT_LO_SRC_SELECT : 0; + break; + case VIP_RGB_OUT_HI_DATA_SELECT: + val |= (path_val) ? VIP_RGB_OUT_HI_SRC_SELECT : 0; + break; + case VIP_CHR_DS_1_SRC_DATA_SELECT: + insert_field(&val, path_val, VIP_DS1_SRC_SELECT_MASK, + VIP_DS1_SRC_SELECT_SHFT); + break; + case VIP_CHR_DS_2_SRC_DATA_SELECT: + insert_field(&val, path_val, VIP_DS2_SRC_SELECT_MASK, + VIP_DS2_SRC_SELECT_SHFT); + break; + case VIP_MULTI_CHANNEL_DATA_SELECT: + val |= (path_val) ? VIP_MULTI_CHANNEL_SELECT : 0; + break; + case VIP_CHR_DS_1_DATA_BYPASS: + val |= (path_val) ? VIP_DS1_BYPASS : 0; + break; + case VIP_CHR_DS_2_DATA_BYPASS: + val |= (path_val) ? VIP_DS2_BYPASS : 0; + break; + default: + v4l2_err(&dev->v4l2_dev, "%s: data_path 0x%x is not valid\n", + __func__, data_path); + return; + } + insert_field(&val, data_path, VIP_DATAPATH_SELECT_MASK, + VIP_DATAPATH_SELECT_SHFT); + reg_write(dev, data_path_reg, val); + v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: DATA_PATH_SELECT(%08X): %08X\n", __func__, + data_path_reg, reg_read(dev, data_path_reg)); +} + +/* + * Return the vip_stream structure for a given struct file + */ +static inline struct vip_stream *file2stream(struct file *file) +{ + return video_drvdata(file); +} + +/* + * Append a destination descriptor to the current descriptor list, + * setting up dma to the given srce. + */ +static int add_out_dtd(struct vip_stream *stream, int srce_type) +{ + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + struct vip_srce_info *sinfo = &srce_info[srce_type]; + struct v4l2_rect *c_rect = &port->c_rect; + struct vip_fmt *fmt = port->fmt; + int channel, plane = 0; + int max_width, max_height; + dma_addr_t dma_addr = 0; + u32 flags; + u32 width = stream->width; + + channel = sinfo->base_channel; + + switch (srce_type) { + case VIP_SRCE_MULT_PORT: + case VIP_SRCE_MULT_ANC: + if (port->port_id == VIP_PORTB) + channel += VIP_CHAN_MULT_PORTB_OFFSET; + channel += stream->stream_id; + flags = 0; + break; + case VIP_SRCE_CHROMA: + plane = 1; + fallthrough; + case VIP_SRCE_LUMA: + if (port->port_id == VIP_PORTB) { + if (port->scaler && !port->fmt->coplanar) + /* + * In this case Port A Chroma channel + * is used to carry Port B scaled YUV422 + */ + channel += 1; + else + channel += VIP_CHAN_YUV_PORTB_OFFSET; + } + flags = port->flags; + break; + case VIP_SRCE_RGB: + if (port->port_id == VIP_PORTB || + (port->port_id == VIP_PORTA && + port->csc == VIP_CSC_NA && + v4l2_is_format_rgb(port->fmt->finfo))) + /* + * RGB sensor only connect to Y_LO + * channel i.e. port B channel. + */ + channel += VIP_CHAN_RGB_PORTB_OFFSET; + flags = port->flags; + break; + default: + v4l2_err(&dev->v4l2_dev, "%s: srce_type 0x%x is not valid\n", + __func__, srce_type); + return -1; + } + + if (dev->slice_id == VIP_SLICE2) + channel += VIP_CHAN_VIP2_OFFSET; + + if (port->fmt->vpdma_fmt[0] == &vpdma_raw_fmts[VPDMA_DATA_FMT_RAW8]) { + /* + * Special case since we are faking a YUV422 16bit format + * to have the vpdma perform the needed byte swap + * we need to adjust the pixel width accordingly + * otherwise the parser will attempt to collect more pixels + * then available and the vpdma transfer will exceed the + * allocated frame buffer. + */ + width >>= 1; + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: 8 bit raw detected, adjusting width to %d\n", + __func__, width); + } + + /* + * Use VPDMA_MAX_SIZE1 or VPDMA_MAX_SIZE2 register for slice0/1 + */ + + if (dev->slice_id == VIP_SLICE1) { + vpdma_set_max_size(dev->shared->vpdma, VPDMA_MAX_SIZE1, + width, stream->height); + + max_width = MAX_OUT_WIDTH_REG1; + max_height = MAX_OUT_HEIGHT_REG1; + } else { + vpdma_set_max_size(dev->shared->vpdma, VPDMA_MAX_SIZE2, + width, stream->height); + + max_width = MAX_OUT_WIDTH_REG2; + max_height = MAX_OUT_HEIGHT_REG2; + } + + /* + * Mark this channel to be cleared while cleaning up resources + * This will make sure that an abort descriptor for this channel + * would be submitted to VPDMA causing any ongoing transaction to be + * aborted and cleanup the VPDMA FSM for this channel + */ + stream->vpdma_channels[channel] = 1; + + vpdma_rawchan_add_out_dtd(&stream->desc_list, c_rect->width, + stream->bytesperline, c_rect, + fmt->vpdma_fmt[plane], dma_addr, + max_width, max_height, channel, flags); + return 0; +} + +/* + * add_stream_dtds - prepares and starts DMA for pending transfers + */ +static void add_stream_dtds(struct vip_stream *stream) +{ + struct vip_port *port = stream->port; + int srce_type; + + if (port->flags & FLAG_MULT_PORT) + srce_type = VIP_SRCE_MULT_PORT; + else if (port->flags & FLAG_MULT_ANC) + srce_type = VIP_SRCE_MULT_ANC; + else if (v4l2_is_format_rgb(port->fmt->finfo)) + srce_type = VIP_SRCE_RGB; + else + srce_type = VIP_SRCE_LUMA; + + add_out_dtd(stream, srce_type); + + if (srce_type == VIP_SRCE_LUMA && port->fmt->coplanar) + add_out_dtd(stream, VIP_SRCE_CHROMA); +} + +static void enable_irqs(struct vip_dev *dev, int irq_num, int list_num) +{ + struct vip_parser_data *parser = dev->parser; + u32 reg_addr = VIP_INT0_ENABLE0_SET + + VIP_INTC_INTX_OFFSET * irq_num; + u32 irq_val = (1 << (list_num * 2)) | + (VIP_VIP1_PARSER_INT << (irq_num * 1)); + + /* Enable Parser Interrupt */ + reg_write(parser, VIP_PARSER_FIQ_MASK, (u32)~PARSER_IRQ_MASK); + + reg_write(dev->shared, reg_addr, irq_val); + + vpdma_enable_list_complete_irq(dev->shared->vpdma, + irq_num, list_num, true); +} + +static void disable_irqs(struct vip_dev *dev, int irq_num, int list_num) +{ + struct vip_parser_data *parser = dev->parser; + u32 reg_addr = VIP_INT0_ENABLE0_CLR + + VIP_INTC_INTX_OFFSET * irq_num; + u32 irq_val = (1 << (list_num * 2)) | + (VIP_VIP1_PARSER_INT << (irq_num * 1)); + + /* Disable all Parser Interrupt */ + reg_write(parser, VIP_PARSER_FIQ_MASK, 0xffffffff); + + reg_write(dev->shared, reg_addr, irq_val); + + vpdma_enable_list_complete_irq(dev->shared->vpdma, + irq_num, list_num, false); +} + +static void clear_irqs(struct vip_dev *dev, int irq_num, int list_num) +{ + struct vip_parser_data *parser = dev->parser; + u32 reg_addr = VIP_INT0_STATUS0_CLR + + VIP_INTC_INTX_OFFSET * irq_num; + u32 irq_val = (1 << (list_num * 2)) | + (VIP_VIP1_PARSER_INT << (irq_num * 1)); + + /* Clear all Parser Interrupt */ + reg_write(parser, VIP_PARSER_FIQ_CLR, 0xffffffff); + reg_write(parser, VIP_PARSER_FIQ_CLR, 0x0); + + reg_write(dev->shared, reg_addr, irq_val); + + vpdma_clear_list_stat(dev->shared->vpdma, irq_num, dev->slice_id); +} + +static void populate_desc_list(struct vip_stream *stream) +{ + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + + stream->desc_next = stream->desc_list.buf.addr; + add_stream_dtds(stream); + + vpdma_map_desc_buf(dev->shared->vpdma, &stream->desc_list.buf); +} + +/* + * start_dma - adds descriptors to the dma list and submits them. + * Should be called after a new vb is queued and on a vpdma list + * completion interrupt. + */ +static void start_dma(struct vip_stream *stream, struct vip_buffer *buf) +{ + struct vip_dev *dev = stream->port->dev; + struct vpdma_data *vpdma = dev->shared->vpdma; + int list_num = stream->list_num; + dma_addr_t dma_addr; + int drop_data; + + if (vpdma_list_busy(vpdma, list_num)) { + v4l2_err(&dev->v4l2_dev, "vpdma list busy, cannot post\n"); + return; /* nothing to do */ + } + + if (buf) { + dma_addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + drop_data = 0; + v4l2_dbg(4, debug, &dev->v4l2_dev, "%s: vb2 buf idx:%d, dma_addr:%pad\n", + __func__, buf->vb.vb2_buf.index, &dma_addr); + } else { + dma_addr = 0; + drop_data = 1; + v4l2_dbg(4, debug, &dev->v4l2_dev, "%s: dropped\n", __func__); + } + + vpdma_update_dma_addr(dev->shared->vpdma, &stream->desc_list, + dma_addr, stream->write_desc, drop_data, 0); + + if (stream->port->fmt->coplanar) { + dma_addr += stream->bytesperline * stream->height; + vpdma_update_dma_addr(dev->shared->vpdma, &stream->desc_list, + dma_addr, stream->write_desc + 1, + drop_data, 1); + } + + vpdma_submit_descs(dev->shared->vpdma, + &stream->desc_list, stream->list_num); +} + +static void vip_schedule_next_buffer(struct vip_stream *stream) +{ + struct vip_dev *dev = stream->port->dev; + struct vip_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + if (list_empty(&stream->vidq)) { + v4l2_dbg(4, debug, &dev->v4l2_dev, "Dropping frame\n"); + if (list_empty(&stream->dropq)) { + v4l2_err(&dev->v4l2_dev, "No dropq buffer left!"); + spin_unlock_irqrestore(&dev->slock, flags); + return; + } + buf = list_entry(stream->dropq.next, + struct vip_buffer, list); + + buf->drop = true; + list_move_tail(&buf->list, &stream->post_bufs); + buf = NULL; + } else { + buf = list_entry(stream->vidq.next, + struct vip_buffer, list); + buf->drop = false; + list_move_tail(&buf->list, &stream->post_bufs); + v4l2_dbg(4, debug, &dev->v4l2_dev, "added next buffer\n"); + } + + spin_unlock_irqrestore(&dev->slock, flags); + start_dma(stream, buf); +} + +static void vip_process_buffer_complete(struct vip_stream *stream) +{ + struct vip_dev *dev = stream->port->dev; + struct vip_buffer *buf; + struct vb2_v4l2_buffer *vb = NULL; + unsigned long flags, fld; + + buf = list_first_entry_or_null(&stream->post_bufs, struct vip_buffer, list); + + if (stream->port->flags & FLAG_INTERLACED) { + vpdma_unmap_desc_buf(dev->shared->vpdma, + &stream->desc_list.buf); + + fld = dtd_get_field(stream->write_desc); + stream->field = fld ? V4L2_FIELD_BOTTOM : V4L2_FIELD_TOP; + + vpdma_map_desc_buf(dev->shared->vpdma, &stream->desc_list.buf); + } + + if (buf) { + vb = &buf->vb; + vb->sequence = stream->sequence; + vb->vb2_buf.timestamp = ktime_get_ns(); + vb->field = V4L2_FIELD_NONE; + + if (buf->drop) { + spin_lock_irqsave(&dev->slock, flags); + list_move_tail(&buf->list, &stream->dropq); + spin_unlock_irqrestore(&dev->slock, flags); + } else { + spin_lock_irqsave(&dev->slock, flags); + list_del(&buf->list); + spin_unlock_irqrestore(&dev->slock, flags); + vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE); + } + } else { + v4l2_err(&dev->v4l2_dev, "%s: buf is null!!!\n", __func__); + return; + } + + stream->sequence++; +} + +static int vip_reset_vpdma(struct vip_stream *stream) +{ + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + struct vip_buffer *buf; + unsigned long flags; + + stop_dma(stream, false); + + spin_lock_irqsave(&dev->slock, flags); + /* requeue all active buffers in the opposite order */ + while (!list_empty(&stream->post_bufs)) { + buf = list_last_entry(&stream->post_bufs, + struct vip_buffer, list); + list_del(&buf->list); + if (buf->drop == 1) { + list_add_tail(&buf->list, &stream->dropq); + v4l2_dbg(4, debug, &dev->v4l2_dev, "requeueing drop buffer on dropq\n"); + } else { + list_add(&buf->list, &stream->vidq); + v4l2_dbg(4, debug, &dev->v4l2_dev, "requeueing vb2 buf idx:%d on vidq\n", + buf->vb.vb2_buf.index); + } + } + spin_unlock_irqrestore(&dev->slock, flags); + + /* Make sure the desc_list is unmapped */ + vpdma_unmap_desc_buf(dev->shared->vpdma, &stream->desc_list.buf); + + return 0; +} + +static void vip_overflow_recovery_work(struct work_struct *work) +{ + struct vip_stream *stream = container_of(work, struct vip_stream, + recovery_work); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + + v4l2_err(&dev->v4l2_dev, "%s: Port %c\n", __func__, + port->port_id == VIP_PORTA ? 'A' : 'B'); + + disable_irqs(dev, dev->slice_id, stream->list_num); + clear_irqs(dev, dev->slice_id, stream->list_num); + + /* 1. Set VIP_XTRA6_PORT_A[31:16] YUV_SRCNUM_STOP_IMMEDIATELY */ + /* 2. Set VIP_XTRA6_PORT_A[15:0] ANC_SRCNUM_STOP_IMMEDIATELY */ + vip_parser_stop_imm(port, 1); + + /* 3. Clear VIP_PORT_A[8] ENABLE */ + /* + * 4. Set VIP_PORT_A[7] CLR_ASYNC_FIFO_RD + * Set VIP_PORT_A[6] CLR_ASYNC_FIFO_WR + */ + vip_enable_parser(port, false); + + /* 5. Set VIP_PORT_A[23] SW_RESET */ + vip_reset_parser(port, 1); + + /* + * 6. Reset other VIP modules + * For each module used downstream of VIP_PARSER, write 1 to the + * bit location of the VIP_CLKC_RST register which is connected + * to VIP_PARSER + */ + vip_module_toggle(dev, VIP_DP_RST, true); + + usleep_range(200, 250); + + /* + * 7. Abort VPDMA channels + * Write to list attribute to stop list 0 + * Write to list address register location of abort list + * Write to list attribute register list 0 and size of abort list + */ + vip_reset_vpdma(stream); + + /* 8. Clear VIP_PORT_A[23] SW_RESET */ + vip_reset_parser(port, 0); + + /* + * 9. Un-reset other VIP modules + * For each module used downstream of VIP_PARSER, write 0 to + * the bit location of the VIP_CLKC_RST register which is + * connected to VIP_PARSER + */ + vip_module_toggle(dev, VIP_DP_RST, false); + + /* 10. (Delay) */ + /* 11. SC coeff downloaded (if VIP_SCALER is being used) */ + vip_setup_scaler(stream); + + /* 12. (Delay) */ + /* the above are not needed here yet */ + + populate_desc_list(stream); + stream->num_recovery++; + if (stream->num_recovery < 5) { + /* Reload the vpdma */ + vip_load_vpdma_list_fifo(stream); + + enable_irqs(dev, dev->slice_id, stream->list_num); + vip_schedule_next_buffer(stream); + + /* 13. Clear VIP_XTRA6_PORT_A[31:16] YUV_SRCNUM_STOP_IMM */ + /* 14. Clear VIP_XTRA6_PORT_A[15:0] ANC_SRCNUM_STOP_IMM */ + + vip_parser_stop_imm(port, 0); + + /* 15. Set VIP_PORT_A[8] ENABLE */ + /* + * 16. Clear VIP_PORT_A[7] CLR_ASYNC_FIFO_RD + * Clear VIP_PORT_A[6] CLR_ASYNC_FIFO_WR + */ + vip_enable_parser(port, true); + } else { + v4l2_err(&dev->v4l2_dev, "%s: num_recovery limit exceeded leaving disabled\n", + __func__); + } +} + +static void handle_parser_irqs(struct vip_dev *dev) +{ + struct vip_parser_data *parser = dev->parser; + struct vip_port *porta = dev->ports[VIP_PORTA]; + struct vip_port *portb = dev->ports[VIP_PORTB]; + struct vip_stream *stream = NULL; + u32 vip_irq_stat = reg_read(parser, VIP_PARSER_FIQ_STATUS); + int i; + + v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: FIQ_STATUS: 0x%08x\n", __func__, vip_irq_stat); + + /* Clear all Parser Interrupt */ + reg_write(parser, VIP_PARSER_FIQ_CLR, vip_irq_stat); + reg_write(parser, VIP_PARSER_FIQ_CLR, 0x0); + + #ifdef DEBUG + if (vip_irq_stat & VIP_PORTA_VDET) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTA_VDET\n"); + if (vip_irq_stat & VIP_PORTB_VDET) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTB_VDET\n"); + if (vip_irq_stat & VIP_PORTA_ASYNC_FIFO_OF) + v4l2_err(&dev->v4l2_dev, "VIP_PORTA_ASYNC_FIFO_OF\n"); + if (vip_irq_stat & VIP_PORTB_ASYNC_FIFO_OF) + v4l2_err(&dev->v4l2_dev, "VIP_PORTB_ASYNC_FIFO_OF\n"); + if (vip_irq_stat & VIP_PORTA_OUTPUT_FIFO_YUV) + v4l2_err(&dev->v4l2_dev, "VIP_PORTA_OUTPUT_FIFO_YUV\n"); + if (vip_irq_stat & VIP_PORTA_OUTPUT_FIFO_ANC) + v4l2_err(&dev->v4l2_dev, "VIP_PORTA_OUTPUT_FIFO_ANC\n"); + if (vip_irq_stat & VIP_PORTB_OUTPUT_FIFO_YUV) + v4l2_err(&dev->v4l2_dev, "VIP_PORTB_OUTPUT_FIFO_YUV\n"); + if (vip_irq_stat & VIP_PORTB_OUTPUT_FIFO_ANC) + v4l2_err(&dev->v4l2_dev, "VIP_PORTB_OUTPUT_FIFO_ANC\n"); + if (vip_irq_stat & VIP_PORTA_CONN) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTA_CONN\n"); + if (vip_irq_stat & VIP_PORTA_DISCONN) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTA_DISCONN\n"); + if (vip_irq_stat & VIP_PORTB_CONN) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTB_CONN\n"); + if (vip_irq_stat & VIP_PORTB_DISCONN) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTB_DISCONN\n"); + if (vip_irq_stat & VIP_PORTA_SRC0_SIZE) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTA_SRC0_SIZE\n"); + if (vip_irq_stat & VIP_PORTB_SRC0_SIZE) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTB_SRC0_SIZE\n"); + if (vip_irq_stat & VIP_PORTA_YUV_PROTO_VIOLATION) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTA_YUV_PROTO_VIOLATION\n"); + if (vip_irq_stat & VIP_PORTA_ANC_PROTO_VIOLATION) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTA_ANC_PROTO_VIOLATION\n"); + if (vip_irq_stat & VIP_PORTB_YUV_PROTO_VIOLATION) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTB_YUV_PROTO_VIOLATION\n"); + if (vip_irq_stat & VIP_PORTB_ANC_PROTO_VIOLATION) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTB_ANC_PROTO_VIOLATION\n"); + if (vip_irq_stat & VIP_PORTA_CFG_DISABLE_COMPLETE) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTA_CFG_DISABLE_COMPLETE\n"); + if (vip_irq_stat & VIP_PORTB_CFG_DISABLE_COMPLETE) + v4l2_dbg(3, debug, &dev->v4l2_dev, "VIP_PORTB_CFG_DISABLE_COMPLETE\n"); + #endif + + if (vip_irq_stat & (VIP_PORTA_ASYNC_FIFO_OF | + VIP_PORTA_OUTPUT_FIFO_YUV | + VIP_PORTA_OUTPUT_FIFO_ANC)) { + for (i = 0; i < VIP_CAP_STREAMS_PER_PORT; i++) { + if (porta->cap_streams[i] && + porta->cap_streams[i]->port->port_id == + porta->port_id) { + stream = porta->cap_streams[i]; + break; + } + } + if (stream) { + disable_irqs(dev, dev->slice_id, + stream->list_num); + schedule_work(&stream->recovery_work); + return; + } + } + if (vip_irq_stat & (VIP_PORTB_ASYNC_FIFO_OF | + VIP_PORTB_OUTPUT_FIFO_YUV | + VIP_PORTB_OUTPUT_FIFO_ANC)) { + for (i = 0; i < VIP_CAP_STREAMS_PER_PORT; i++) { + if (portb->cap_streams[i] && + portb->cap_streams[i]->port->port_id == + portb->port_id) { + stream = portb->cap_streams[i]; + break; + } + } + if (stream) { + disable_irqs(dev, dev->slice_id, + stream->list_num); + schedule_work(&stream->recovery_work); + return; + } + } +} + +static irqreturn_t vip_irq(int irq_vip, void *data) +{ + struct vip_dev *dev = (struct vip_dev *)data; + struct vpdma_data *vpdma; + struct vip_stream *stream; + int list_num; + int irq_num = dev->slice_id; + u32 irqst, irqst_saved, reg_addr; + + if (!dev->shared) + return IRQ_HANDLED; + + vpdma = dev->shared->vpdma; + reg_addr = VIP_INT0_STATUS0 + + VIP_INTC_INTX_OFFSET * irq_num; + irqst_saved = reg_read(dev->shared, reg_addr); + irqst = irqst_saved; + + v4l2_dbg(8, debug, &dev->v4l2_dev, "IRQ %d VIP_INT%d_STATUS0 0x%x\n", + irq_vip, irq_num, irqst); + if (irqst) { + if (irqst & (VIP_VIP1_PARSER_INT << (irq_num * 1))) { + irqst &= ~(VIP_VIP1_PARSER_INT << (irq_num * 1)); + handle_parser_irqs(dev); + } + + for (list_num = 0; irqst && (list_num < 8); list_num++) { + /* Check for LIST_COMPLETE IRQ */ + if (!(irqst & (1 << list_num * 2))) + continue; + + v4l2_dbg(8, debug, &dev->v4l2_dev, "IRQ %d: handling LIST%d_COMPLETE\n", + irq_num, list_num); + + stream = vpdma_hwlist_get_priv(vpdma, list_num); + if (!stream || stream->list_num != list_num) { + v4l2_err(&dev->v4l2_dev, "IRQ occurred for unused list"); + continue; + } + + vpdma_clear_list_stat(vpdma, irq_num, list_num); + + vip_process_buffer_complete(stream); + + vip_schedule_next_buffer(stream); + + irqst &= ~((1 << list_num * 2)); + } + } + + /* Acknowledge that we are done with all interrupts */ + reg_write(dev->shared, VIP_INTC_E0I, 1 << irq_num); + + /* Clear handled events from status register */ + reg_addr = VIP_INT0_STATUS0_CLR + + VIP_INTC_INTX_OFFSET * irq_num; + reg_write(dev->shared, reg_addr, irqst_saved); + + return IRQ_HANDLED; +} + +/* + * video ioctls + */ +static int vip_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, VIP_MODULE_NAME, sizeof(cap->driver)); + strscpy(cap->card, VIP_MODULE_NAME, sizeof(cap->card)); + return 0; +} + +static int vip_enuminput(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct vip_stream *stream = file2stream(file); + + if (inp->index) + return -EINVAL; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = stream->vfd->tvnorms; + sprintf(inp->name, "Camera %u", stream->vfd->num); + + return 0; +} + +static int vip_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int vip_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i != 0) + return -EINVAL; + return 0; +} + +static int vip_querystd(struct file *file, void *fh, v4l2_std_id *std) +{ + struct vip_stream *stream = file2stream(file); + struct vip_port *port = stream->port; + + *std = stream->vfd->tvnorms; + v4l2_subdev_call(port->subdev, video, querystd, std); + return 0; +} + +static int vip_g_std(struct file *file, void *fh, v4l2_std_id *std) +{ + struct vip_stream *stream = file2stream(file); + struct vip_port *port = stream->port; + + *std = stream->vfd->tvnorms; + v4l2_subdev_call(port->subdev, video, g_std, std); + + return 0; +} + +static int vip_s_std(struct file *file, void *fh, v4l2_std_id std) +{ + struct vip_stream *stream = file2stream(file); + struct vip_port *port = stream->port; + + if (stream->vfd->tvnorms == std) + return 0; + + if (!(std & stream->vfd->tvnorms)) + return -EINVAL; + + if (vb2_is_busy(&stream->vb_vidq)) + return -EBUSY; + + v4l2_subdev_call(port->subdev, video, s_std, std); + return 0; +} + +static int vip_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct vip_stream *stream = file2stream(file); + struct vip_port *port = stream->port; + struct vip_fmt *fmt; + + if (f->index >= port->num_active_fmt) + return -EINVAL; + + fmt = port->active_fmt[f->index]; + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int vip_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *f) +{ + struct vip_stream *stream = file2stream(file); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + struct vip_fmt *fmt; + int ret; + struct v4l2_subdev_frame_size_enum fse = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = 0, + }; + + fmt = find_port_format_by_pix(port, f->pixel_format); + if (!fmt) + return -EINVAL; + + fse.index = f->index; + fse.code = fmt->code; + ret = v4l2_subdev_call(port->subdev, pad, enum_frame_size, NULL, &fse); + if (ret) + return -EINVAL; + + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: index: %d code: %x W:[%d,%d] H:[%d,%d]\n", + __func__, fse.index, fse.code, fse.min_width, fse.max_width, + fse.min_height, fse.max_height); + + f->type = V4L2_FRMSIZE_TYPE_DISCRETE; + f->discrete.width = fse.max_width; + f->discrete.height = fse.max_height; + + return 0; +} + +static int vip_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *f) +{ + struct vip_stream *stream = file2stream(file); + struct vip_port *port = stream->port; + struct vip_fmt *fmt; + struct v4l2_subdev_frame_interval_enum fie = { + .index = f->index, + .width = f->width, + .height = f->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + fmt = find_port_format_by_pix(port, f->pixel_format); + if (!fmt) + return -EINVAL; + + fie.code = fmt->code; + ret = v4l2_subdev_call(port->subdev, pad, enum_frame_interval, + NULL, &fie); + if (ret) + return ret; + f->type = V4L2_FRMIVAL_TYPE_DISCRETE; + f->discrete = fie.interval; + + return 0; +} + +static int vip_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct vip_stream *stream = video_drvdata(file); + struct vip_port *port = stream->port; + + return v4l2_g_parm_cap(video_devdata(file), port->subdev, a); +} + +static int vip_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct vip_stream *stream = video_drvdata(file); + struct vip_port *port = stream->port; + + return v4l2_s_parm_cap(video_devdata(file), port->subdev, a); +} + +static int vip_calc_format_size(struct vip_port *port, + struct vip_fmt *fmt, + struct v4l2_format *f) +{ + enum v4l2_field *field; + unsigned int stride; + struct vip_dev *dev = port->dev; + + if (!fmt) { + v4l2_dbg(2, debug, &dev->v4l2_dev, + "no vip_fmt format provided!\n"); + return -EINVAL; + } + + field = &f->fmt.pix.field; + if (*field == V4L2_FIELD_ANY) + *field = V4L2_FIELD_NONE; + else if (V4L2_FIELD_NONE != *field && V4L2_FIELD_ALTERNATE != *field) + return -EINVAL; + + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, W_ALIGN, + &f->fmt.pix.height, MIN_H, MAX_H, H_ALIGN, + S_ALIGN); + + stride = f->fmt.pix.width * (fmt->vpdma_fmt[0]->depth >> 3); + f->fmt.pix.bytesperline = ALIGN(stride, VPDMA_STRIDE_ALIGN); + + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + if (fmt->coplanar) { + f->fmt.pix.sizeimage += f->fmt.pix.height * + f->fmt.pix.bytesperline * + fmt->vpdma_fmt[VIP_CHROMA]->depth >> 3; + } + + f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + + v4l2_dbg(3, debug, &dev->v4l2_dev, "calc_format_size: fourcc:%s size: %dx%d bpl:%d img_size:%d\n", + fourcc_to_str(f->fmt.pix.pixelformat), + f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); + + return 0; +} + +static inline bool vip_is_size_dma_aligned(u32 bpp, u32 width) +{ + return ((width * bpp) == ALIGN(width * bpp, VPDMA_STRIDE_ALIGN)); +} + +static int vip_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vip_stream *stream = file2stream(file); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + struct vip_fmt *fmt; + u32 best_width, best_height, largest_width, largest_height; + int ret, found; + enum vip_csc_state csc_direction; + struct v4l2_subdev_frame_size_enum fse = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = 0, + }; + + fmt = find_port_format_by_pix(port, f->fmt.pix.pixelformat); + if (!fmt) { + /* Just get the first one enumerated */ + fmt = port->active_fmt[0]; + f->fmt.pix.pixelformat = fmt->fourcc; + } + + csc_direction = vip_csc_direction(fmt->code, fmt->finfo); + if (csc_direction != VIP_CSC_NA) { + if (!is_csc_available(port)) { + v4l2_dbg(2, debug, &dev->v4l2_dev, + "CSC not available for Fourcc format (0x%08x).\n", + f->fmt.pix.pixelformat); + + /* Just get the first one enumerated */ + fmt = port->active_fmt[0]; + f->fmt.pix.pixelformat = fmt->fourcc; + /* re-evaluate the csc_direction here */ + csc_direction = vip_csc_direction(fmt->code, + fmt->finfo); + } else { + v4l2_dbg(3, debug, &dev->v4l2_dev, "CSC active on Port %c: going %s\n", + port->port_id == VIP_PORTA ? 'A' : 'B', + (csc_direction == VIP_CSC_Y2R) ? "Y2R" : "R2Y"); + } + } + + /* + * Given that sensors might support multiple mbus code we need + * to use the one that matches the requested pixel format + */ + port->try_mbus_framefmt = port->mbus_framefmt; + port->try_mbus_framefmt.code = fmt->code; + + /* check for/find a valid width/height */ + ret = 0; + found = false; + best_width = 0; + best_height = 0; + largest_width = 0; + largest_height = 0; + + fse.code = fmt->code; + for (fse.index = 0; ; fse.index++) { + u32 bpp = fmt->vpdma_fmt[0]->depth >> 3; + + ret = v4l2_subdev_call(port->subdev, pad, + enum_frame_size, NULL, &fse); + if (ret) + break; + + if (!vip_is_size_dma_aligned(bpp, fse.max_width)) + continue; + + if (fse.max_width >= largest_width && + fse.max_height >= largest_height) { + v4l2_dbg(3, debug, &dev->v4l2_dev, "try_fmt loop:%d found new larger: %dx%d\n", + fse.index, fse.max_width, fse.max_height); + largest_width = fse.max_width; + largest_height = fse.max_height; + } + + if (fse.max_width >= f->fmt.pix.width && + fse.max_height >= f->fmt.pix.height) { + v4l2_dbg(3, debug, &dev->v4l2_dev, "try_fmt loop:%d found at least larger: %dx%d\n", + fse.index, fse.max_width, fse.max_height); + + if (!best_width || + ((abs(best_width - f->fmt.pix.width) >= + abs(fse.max_width - f->fmt.pix.width)) && + (abs(best_height - f->fmt.pix.height) >= + abs(fse.max_height - f->fmt.pix.height)))) { + best_width = fse.max_width; + best_height = fse.max_height; + v4l2_dbg(3, debug, &dev->v4l2_dev, "try_fmt loop:%d found new best: %dx%d\n", + fse.index, fse.max_width, + fse.max_height); + } + } + + if (f->fmt.pix.width == fse.max_width && + f->fmt.pix.height == fse.max_height) { + found = true; + v4l2_dbg(3, debug, &dev->v4l2_dev, "try_fmt loop:%d found direct match: %dx%d\n", + fse.index, fse.max_width, + fse.max_height); + break; + } + + if (f->fmt.pix.width >= fse.min_width && + f->fmt.pix.width <= fse.max_width && + f->fmt.pix.height >= fse.min_height && + f->fmt.pix.height <= fse.max_height) { + found = true; + v4l2_dbg(3, debug, &dev->v4l2_dev, "try_fmt loop:%d found direct range match: %dx%d\n", + fse.index, fse.max_width, + fse.max_height); + break; + } + } + + if (found) { + port->try_mbus_framefmt.width = f->fmt.pix.width; + port->try_mbus_framefmt.height = f->fmt.pix.height; + /* No need to check for scaling */ + goto calc_size; + } else if (f->fmt.pix.width > largest_width) { + port->try_mbus_framefmt.width = largest_width; + port->try_mbus_framefmt.height = largest_height; + } else if (best_width) { + port->try_mbus_framefmt.width = best_width; + port->try_mbus_framefmt.height = best_height; + } else { + /* use existing values as default */ + } + + v4l2_dbg(3, debug, &dev->v4l2_dev, "try_fmt best subdev size: %dx%d\n", + port->try_mbus_framefmt.width, + port->try_mbus_framefmt.height); + + if (is_scaler_available(port) && + csc_direction != VIP_CSC_Y2R && + !vip_is_mbuscode_raw(fmt->code) && + f->fmt.pix.height <= port->try_mbus_framefmt.height && + port->try_mbus_framefmt.height <= SC_MAX_PIXEL_HEIGHT && + port->try_mbus_framefmt.width <= SC_MAX_PIXEL_WIDTH) { + /* + * Scaler is only accessible if the dst colorspace is YUV. + * As the input to the scaler must be in YUV mode only. + * + * Scaling up is allowed only horizontally. + */ + unsigned int hratio, vratio, width_align, height_align; + u32 bpp = fmt->vpdma_fmt[0]->depth >> 3; + + v4l2_dbg(3, debug, &dev->v4l2_dev, "Scaler active on Port %c: requesting %dx%d\n", + port->port_id == VIP_PORTA ? 'A' : 'B', + f->fmt.pix.width, f->fmt.pix.height); + + /* Just make sure everything is properly aligned */ + width_align = ALIGN(f->fmt.pix.width * bpp, VPDMA_STRIDE_ALIGN); + width_align /= bpp; + height_align = ALIGN(f->fmt.pix.height, 2); + + f->fmt.pix.width = width_align; + f->fmt.pix.height = height_align; + + hratio = f->fmt.pix.width * 1000 / + port->try_mbus_framefmt.width; + vratio = f->fmt.pix.height * 1000 / + port->try_mbus_framefmt.height; + if (hratio < 125) { + f->fmt.pix.width = port->try_mbus_framefmt.width / 8; + v4l2_dbg(3, debug, &dev->v4l2_dev, "Horizontal scaling ratio out of range adjusting -> %d\n", + f->fmt.pix.width); + } + + if (vratio < 188) { + f->fmt.pix.height = port->try_mbus_framefmt.height / 4; + v4l2_dbg(3, debug, &dev->v4l2_dev, "Vertical scaling ratio out of range adjusting -> %d\n", + f->fmt.pix.height); + } + v4l2_dbg(3, debug, &dev->v4l2_dev, "Scaler: got %dx%d\n", + f->fmt.pix.width, f->fmt.pix.height); + } else { + /* use existing values as default */ + f->fmt.pix.width = port->try_mbus_framefmt.width; + f->fmt.pix.height = port->try_mbus_framefmt.height; + } + +calc_size: + /* That we have a fmt calculate imagesize and bytesperline */ + return vip_calc_format_size(port, fmt, f); +} + +static int vip_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vip_stream *stream = file2stream(file); + struct vip_port *port = stream->port; + + /* Use last known values or defaults */ + f->fmt.pix.width = stream->width; + f->fmt.pix.height = stream->height; + f->fmt.pix.pixelformat = port->fmt->fourcc; + f->fmt.pix.field = stream->sup_field; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + f->fmt.pix.bytesperline = stream->bytesperline; + f->fmt.pix.sizeimage = stream->sizeimage; + + return 0; +} + +static int vip_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vip_stream *stream = file2stream(file); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + struct v4l2_subdev_format sfmt; + struct v4l2_mbus_framefmt *mf; + enum vip_csc_state csc_direction; + int ret; + + ret = vip_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + if (vb2_is_busy(&stream->vb_vidq)) { + v4l2_err(&dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + /* + * Check if we need the scaler or not + * + * Since on previous S_FMT call the scaler might have been + * allocated if it is not needed in this instance we will + * attempt to free it just in case. + * + * free_scaler() is harmless unless the current port + * allocated it. + */ + if (f->fmt.pix.width == port->try_mbus_framefmt.width && + f->fmt.pix.height == port->try_mbus_framefmt.height) + free_scaler(port); + else + allocate_scaler(port); + + port->fmt = find_port_format_by_pix(port, + f->fmt.pix.pixelformat); + stream->width = f->fmt.pix.width; + stream->height = f->fmt.pix.height; + stream->bytesperline = f->fmt.pix.bytesperline; + stream->sizeimage = f->fmt.pix.sizeimage; + stream->sup_field = f->fmt.pix.field; + stream->field = f->fmt.pix.field; + + port->c_rect.left = 0; + port->c_rect.top = 0; + port->c_rect.width = stream->width; + port->c_rect.height = stream->height; + + /* + * Check if we need the csc unit or not + * + * Since on previous S_FMT call, the csc might have been + * allocated if it is not needed in this instance we will + * attempt to free it just in case. + * + * free_csc() is harmless unless the current port + * allocated it. + */ + csc_direction = vip_csc_direction(port->fmt->code, port->fmt->finfo); + if (csc_direction == VIP_CSC_NA) + free_csc(port); + else + allocate_csc(port, csc_direction); + + if (stream->sup_field == V4L2_FIELD_ALTERNATE) + port->flags |= FLAG_INTERLACED; + else + port->flags &= ~FLAG_INTERLACED; + + memset(&sfmt, 0, sizeof(sfmt)); + mf = &sfmt.format; + v4l2_fill_mbus_format(mf, &f->fmt.pix, port->fmt->code); + /* Make sure to use the subdev size found in the try_fmt */ + mf->width = port->try_mbus_framefmt.width; + mf->height = port->try_mbus_framefmt.height; + + sfmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + sfmt.pad = 0; + ret = v4l2_subdev_call(port->subdev, pad, set_fmt, NULL, &sfmt); + if (ret) { + v4l2_dbg(1, debug, &dev->v4l2_dev, "set_fmt failed in subdev\n"); + return ret; + } + + /* Save it */ + port->mbus_framefmt = *mf; + + v4l2_dbg(3, debug, &dev->v4l2_dev, "s_fmt subdev fmt mbus_code: %04X size: %dx%d\n", + port->mbus_framefmt.code, + port->mbus_framefmt.width, port->mbus_framefmt.height); + + return 0; +} + +static void vip_unset_csc_y2r(struct vip_dev *dev, struct vip_port *port) +{ + if (port->port_id == VIP_PORTA) { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_RGB_SRC_DATA_SELECT, 0); + } else { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_LO_DATA_SELECT, 0); + } +} + +static void vip_unset_csc_r2y(struct vip_dev *dev, struct vip_port *port) +{ + if (port->port_id != VIP_PORTA) { + v4l2_err(&dev->v4l2_dev, "RGB sensor can only be on Port A\n"); + return; + } + + if (port->scaler && port->fmt->coplanar) { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else if (port->scaler) { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else if (port->fmt->coplanar) { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } +} + +static void vip_unset_rgb(struct vip_dev *dev, struct vip_port *port) +{ + if (port->port_id != VIP_PORTA) { + v4l2_err(&dev->v4l2_dev, "RGB sensor can only be on Port A\n"); + return; + } + + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_LO_DATA_SELECT, 0); +} + +static void vip_unset_yuv(struct vip_dev *dev, struct vip_port *port) +{ + if (port->scaler && port->fmt->coplanar) { + if (port->port_id == VIP_PORTA) { + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else { + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_2_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_LO_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 0); + } + } else if (port->scaler) { + if (port->port_id == VIP_PORTA) { + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else { + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_2_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_CHR_DS_2_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } + } else if (port->fmt->coplanar) { + if (port->port_id == VIP_PORTA) { + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else { + vip_set_slice_path(dev, VIP_CHR_DS_2_SRC_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_CHR_DS_2_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_LO_DATA_SELECT, 0); + } + } else { + /* + * We undo all data path setting except for the multi + * stream case. + * Because we cannot disrupt other on-going capture if only + * one stream is terminated the other might still be going + */ + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_RGB_OUT_LO_DATA_SELECT, 0); + } +} + +/* + * Does the exact opposite of set_fmt_params + * It makes sure the DataPath register is sane after tear down + */ +static void unset_fmt_params(struct vip_stream *stream) +{ + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + + stream->sequence = 0; + stream->field = V4L2_FIELD_TOP; + + /* Undo CSC Y2R routing */ + if (port->csc == VIP_CSC_Y2R) { + vip_unset_csc_y2r(dev, port); + /* Undo CSC R2Y routing */ + } else if (port->csc == VIP_CSC_R2Y) { + vip_unset_csc_r2y(dev, port); + /* Undo RGB output routing (no CSC) */ + } else if (v4l2_is_format_rgb(port->fmt->finfo)) { + vip_unset_rgb(dev, port); + /* Undo YUV routing with no CSC */ + } else { + vip_unset_yuv(dev, port); + } +} + +static void vip_config_csc_y2r(struct vip_dev *dev, struct vip_port *port) +{ + port->flags &= ~FLAG_MULT_PORT; + + /* Set alpha component in background color */ + vpdma_set_bg_color(dev->shared->vpdma, + (struct vpdma_data_format *) + port->fmt->vpdma_fmt[0], + 0xff); + + if (port->port_id == VIP_PORTA) { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_RGB_SRC_DATA_SELECT, 1); + } else { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 2); + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_LO_DATA_SELECT, 1); + } +} + +static void vip_config_csc_r2y(struct vip_dev *dev, struct vip_port *port) +{ + if (port->port_id != VIP_PORTA) { + v4l2_err(&dev->v4l2_dev, "RGB sensor can only be on Port A\n"); + return; + } + + port->flags &= ~FLAG_MULT_PORT; + + if (port->scaler && port->fmt->coplanar) { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 4); + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else if (port->scaler) { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 4); + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 1); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else if (port->fmt->coplanar) { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 4); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 2); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else { + vip_set_slice_path(dev, VIP_CSC_SRC_DATA_SELECT, 4); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 2); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 1); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } +} + +static void vip_config_rgb(struct vip_dev *dev, struct vip_port *port) +{ + if (port->port_id != VIP_PORTA) { + v4l2_err(&dev->v4l2_dev, "RGB sensor can only be on Port A\n"); + return; + } + + port->flags &= ~FLAG_MULT_PORT; + + /* Set alpha component in background color */ + vpdma_set_bg_color(dev->shared->vpdma, + (struct vpdma_data_format *) + port->fmt->vpdma_fmt[0], + 0xff); + + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_RGB_OUT_LO_DATA_SELECT, 1); +} + +static void vip_config_yuv(struct vip_dev *dev, struct vip_port *port) +{ + if (port->scaler && port->fmt->coplanar) { + port->flags &= ~FLAG_MULT_PORT; + if (port->port_id == VIP_PORTA) { + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 2); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else { + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 3); + vip_set_slice_path(dev, VIP_CHR_DS_2_SRC_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_LO_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 0); + } + } else if (port->scaler) { + port->flags &= ~FLAG_MULT_PORT; + if (port->port_id == VIP_PORTA) { + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 2); + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 1); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else { + vip_set_slice_path(dev, VIP_SC_SRC_DATA_SELECT, 3); + vip_set_slice_path(dev, VIP_CHR_DS_2_SRC_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 1); + vip_set_slice_path(dev, VIP_CHR_DS_2_DATA_BYPASS, 1); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } + } else if (port->fmt->coplanar) { + port->flags &= ~FLAG_MULT_PORT; + if (port->port_id == VIP_PORTA) { + vip_set_slice_path(dev, VIP_CHR_DS_1_SRC_DATA_SELECT, 3); + vip_set_slice_path(dev, VIP_CHR_DS_1_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_HI_DATA_SELECT, 0); + } else { + vip_set_slice_path(dev, VIP_CHR_DS_2_SRC_DATA_SELECT, 4); + vip_set_slice_path(dev, VIP_CHR_DS_2_DATA_BYPASS, 0); + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 0); + vip_set_slice_path(dev, VIP_RGB_OUT_LO_DATA_SELECT, 0); + } + } else { + port->flags |= FLAG_MULT_PORT; + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 1); + vip_set_slice_path(dev, VIP_RGB_OUT_LO_DATA_SELECT, 0); + } +} + +/* + * Set the registers that are modified when the video format changes. + */ +static void set_fmt_params(struct vip_stream *stream) +{ + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + + stream->sequence = 0; + stream->field = V4L2_FIELD_TOP; + + /* YUV input, RGB output using CSC (Y2R) */ + if (port->csc == VIP_CSC_Y2R) { + vip_config_csc_y2r(dev, port); + /* RGB input, YUV output using CSC (R2Y) */ + } else if (port->csc == VIP_CSC_R2Y) { + vip_config_csc_r2y(dev, port); + /* RGB output without CSC */ + } else if (v4l2_is_format_rgb(port->fmt->finfo)) { + vip_config_rgb(dev, port); + /* YUV output without CSC */ + } else { + vip_config_yuv(dev, port); + } +} + +static int vip_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + struct vip_stream *stream = file2stream(file); + + switch (s->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = 0; + s->r.top = 0; + s->r.width = stream->width; + s->r.height = stream->height; + return 0; + + case V4L2_SEL_TGT_CROP: + s->r = stream->port->c_rect; + return 0; + } + + return -EINVAL; +} + +static int enclosed_rectangle(struct v4l2_rect *a, struct v4l2_rect *b) +{ + if (a->left < b->left || a->top < b->top) + return 0; + if (a->left + a->width > b->left + b->width) + return 0; + if (a->top + a->height > b->top + b->height) + return 0; + + return 1; +} + +static int vip_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct vip_stream *stream = file2stream(file); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + struct v4l2_rect r = s->r; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (s->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + if (vb2_is_busy(&stream->vb_vidq)) + return -EBUSY; + + v4l_bound_align_image(&r.width, 0, stream->width, 0, + &r.height, 0, stream->height, 0, 0); + + r.left = clamp_t(unsigned int, r.left, 0, stream->width - r.width); + r.top = clamp_t(unsigned int, r.top, 0, stream->height - r.height); + + if (s->flags & V4L2_SEL_FLAG_LE && !enclosed_rectangle(&r, &s->r)) + return -ERANGE; + + if (s->flags & V4L2_SEL_FLAG_GE && !enclosed_rectangle(&s->r, &r)) + return -ERANGE; + + s->r = r; + stream->port->c_rect = r; + + v4l2_dbg(1, debug, &dev->v4l2_dev, "cropped (%d,%d)/%dx%d of %dx%d\n", + r.left, r.top, r.width, r.height, + stream->width, stream->height); + + return 0; +} + +static const struct v4l2_ioctl_ops vip_ioctl_ops = { + .vidioc_querycap = vip_querycap, + .vidioc_enum_input = vip_enuminput, + .vidioc_g_input = vip_g_input, + .vidioc_s_input = vip_s_input, + + .vidioc_querystd = vip_querystd, + .vidioc_g_std = vip_g_std, + .vidioc_s_std = vip_s_std, + + .vidioc_enum_fmt_vid_cap = vip_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vip_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vip_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vip_s_fmt_vid_cap, + + .vidioc_enum_frameintervals = vip_enum_frameintervals, + .vidioc_enum_framesizes = vip_enum_framesizes, + .vidioc_s_parm = vip_s_parm, + .vidioc_g_parm = vip_g_parm, + .vidioc_g_selection = vip_g_selection, + .vidioc_s_selection = vip_s_selection, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * Videobuf operations + */ +static int vip_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct vip_stream *stream = vb2_get_drv_priv(vq); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + unsigned int size = stream->sizeimage; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + v4l2_dbg(1, debug, &dev->v4l2_dev, "get %d buffer(s) of size %d each.\n", + *nbuffers, sizes[0]); + + return 0; +} + +static int vip_buf_prepare(struct vb2_buffer *vb) +{ + struct vip_stream *stream = vb2_get_drv_priv(vb->vb2_queue); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + + if (vb2_plane_size(vb, 0) < stream->sizeimage) { + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)stream->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, stream->sizeimage); + + return 0; +} + +static void vip_buf_queue(struct vb2_buffer *vb) +{ + struct vip_stream *stream = vb2_get_drv_priv(vb->vb2_queue); + struct vip_dev *dev = stream->port->dev; + struct vip_buffer *buf = container_of(vb, struct vip_buffer, + vb.vb2_buf); + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + list_add_tail(&buf->list, &stream->vidq); + spin_unlock_irqrestore(&dev->slock, flags); +} + +static void return_buffers(struct vb2_queue *vq, int state) +{ + struct vip_stream *stream = vb2_get_drv_priv(vq); + struct vip_dev *dev = stream->port->dev; + struct vip_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&dev->slock, flags); + + /* release all active buffers */ + while (!list_empty(&stream->post_bufs)) { + buf = list_entry(stream->post_bufs.next, + struct vip_buffer, list); + list_del(&buf->list); + if (buf->drop == 1) + list_add_tail(&buf->list, &stream->dropq); + else + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + while (!list_empty(&stream->vidq)) { + buf = list_entry(stream->vidq.next, struct vip_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + + INIT_LIST_HEAD(&stream->post_bufs); + INIT_LIST_HEAD(&stream->vidq); + + spin_unlock_irqrestore(&dev->slock, flags); +} + +static int vip_setup_scaler(struct vip_stream *stream) +{ + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + struct sc_data *sc = dev->sc; + struct csc_data *csc = dev->csc; + struct vpdma_data *vpdma = dev->shared->vpdma; + struct vip_mmr_adb *mmr_adb = port->mmr_adb.addr; + int list_num = stream->list_num; + int timeout = 500; + struct v4l2_format dst_f; + struct v4l2_format src_f; + + memset(&src_f, 0, sizeof(src_f)); + src_f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_fill_pix_format(&src_f.fmt.pix, &port->mbus_framefmt); + src_f.fmt.pix.pixelformat = vip_mbus_code_to_fourcc(port->fmt->code); + + dst_f = src_f; + dst_f.fmt.pix.pixelformat = port->fmt->fourcc; + dst_f.fmt.pix.width = stream->width; + dst_f.fmt.pix.height = stream->height; + + /* if scaler not associated with this port then skip */ + if (port->scaler) { + sc_set_hs_coeffs(sc, port->sc_coeff_h.addr, + port->mbus_framefmt.width, + port->c_rect.width); + sc_set_vs_coeffs(sc, port->sc_coeff_v.addr, + port->mbus_framefmt.height, + port->c_rect.height); + sc_config_scaler(sc, &mmr_adb->sc_regs0[0], + &mmr_adb->sc_regs8[0], &mmr_adb->sc_regs17[0], + port->mbus_framefmt.width, + port->mbus_framefmt.height, + port->c_rect.width, + port->c_rect.height); + port->load_mmrs = true; + } + + /* if csc not associated with this port then skip */ + if (port->csc) { + csc_set_coeff(csc, &mmr_adb->csc_regs[0], + &src_f, &dst_f); + + port->load_mmrs = true; + } + + /* If coeff are already loaded then skip */ + if (!sc->load_coeff_v && !sc->load_coeff_h && !port->load_mmrs) + return 0; + + if (vpdma_list_busy(vpdma, list_num)) { + v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: List %d is busy\n", + __func__, list_num); + } + + /* Make sure we start with a clean list */ + vpdma_reset_desc_list(&stream->desc_list); + + /* config descriptors */ + if (port->load_mmrs) { + vpdma_map_desc_buf(vpdma, &port->mmr_adb); + vpdma_add_cfd_adb(&stream->desc_list, CFD_MMR_CLIENT, + &port->mmr_adb); + + port->load_mmrs = false; + v4l2_dbg(3, debug, &dev->v4l2_dev, "Added mmr_adb config desc\n"); + } + + if (sc->loaded_coeff_h != port->sc_coeff_h.dma_addr || + sc->load_coeff_h) { + vpdma_map_desc_buf(vpdma, &port->sc_coeff_h); + vpdma_add_cfd_block(&stream->desc_list, + VIP_SLICE1_CFD_SC_CLIENT + dev->slice_id, + &port->sc_coeff_h, 0); + + sc->loaded_coeff_h = port->sc_coeff_h.dma_addr; + sc->load_coeff_h = false; + v4l2_dbg(3, debug, &dev->v4l2_dev, "Added sc_coeff_h config desc\n"); + } + + if (sc->loaded_coeff_v != port->sc_coeff_v.dma_addr || + sc->load_coeff_v) { + vpdma_map_desc_buf(vpdma, &port->sc_coeff_v); + vpdma_add_cfd_block(&stream->desc_list, + VIP_SLICE1_CFD_SC_CLIENT + dev->slice_id, + &port->sc_coeff_v, SC_COEF_SRAM_SIZE >> 4); + + sc->loaded_coeff_v = port->sc_coeff_v.dma_addr; + sc->load_coeff_v = false; + v4l2_dbg(3, debug, &dev->v4l2_dev, "Added sc_coeff_v config desc\n"); + } + v4l2_dbg(3, debug, stream, "CFD_SC_CLIENT %d slice_id: %d\n", + VIP_SLICE1_CFD_SC_CLIENT + dev->slice_id, dev->slice_id); + + vpdma_map_desc_buf(vpdma, &stream->desc_list.buf); + v4l2_dbg(3, debug, &dev->v4l2_dev, "Submitting desc on list# %d\n", list_num); + vpdma_submit_descs(vpdma, &stream->desc_list, list_num); + + while (vpdma_list_busy(vpdma, list_num) && timeout--) + usleep_range(1000, 1100); + + vpdma_unmap_desc_buf(dev->shared->vpdma, &port->mmr_adb); + vpdma_unmap_desc_buf(dev->shared->vpdma, &port->sc_coeff_h); + vpdma_unmap_desc_buf(dev->shared->vpdma, &port->sc_coeff_v); + vpdma_unmap_desc_buf(dev->shared->vpdma, &stream->desc_list.buf); + + vpdma_reset_desc_list(&stream->desc_list); + + if (timeout <= 0) { + v4l2_err(&dev->v4l2_dev, "Timed out setting up scaler through VPDMA list\n"); + return -EBUSY; + } + + return 0; +} + +static int vip_load_vpdma_list_fifo(struct vip_stream *stream) +{ + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + struct vpdma_data *vpdma = dev->shared->vpdma; + int list_num = stream->list_num; + struct vip_buffer *buf; + unsigned long flags; + int timeout, i; + + if (vpdma_list_busy(dev->shared->vpdma, stream->list_num)) + return -EBUSY; + + for (i = 0; i < VIP_VPDMA_FIFO_SIZE; i++) { + spin_lock_irqsave(&dev->slock, flags); + if (list_empty(&stream->vidq)) { + v4l2_err(&dev->v4l2_dev, "No buffer left!"); + spin_unlock_irqrestore(&dev->slock, flags); + return -EINVAL; + } + + buf = list_entry(stream->vidq.next, + struct vip_buffer, list); + buf->drop = false; + + list_move_tail(&buf->list, &stream->post_bufs); + spin_unlock_irqrestore(&dev->slock, flags); + + v4l2_dbg(2, debug, &dev->v4l2_dev, "%s: start_dma vb2 buf idx:%d\n", + __func__, buf->vb.vb2_buf.index); + start_dma(stream, buf); + + timeout = 500; + while (vpdma_list_busy(vpdma, list_num) && timeout--) + usleep_range(1000, 1100); + + if (timeout <= 0) { + v4l2_err(&dev->v4l2_dev, "Timed out loading VPDMA list fifo\n"); + return -EBUSY; + } + } + return 0; +} + +static int vip_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vip_stream *stream = vb2_get_drv_priv(vq); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + int ret; + + ret = vip_setup_scaler(stream); + if (ret) + goto err; + + /* + * Make sure the scaler is configured before the datapath is + * enabled. The scaler can only load the coefficient + * parameters when it is idle. If the scaler path is enabled + * and video data is being received then the VPDMA transfer will + * stall indefinitely. + */ + set_fmt_params(stream); + ret = vip_setup_parser(port); + if (ret) + goto err; + + if (port->subdev) { + ret = v4l2_subdev_call(port->subdev, video, s_stream, 1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "stream on failed in subdev\n"); + goto err; + } + } + + stream->sequence = 0; + stream->field = V4L2_FIELD_TOP; + populate_desc_list(stream); + + ret = vip_load_vpdma_list_fifo(stream); + if (ret) + goto err; + + stream->num_recovery = 0; + + clear_irqs(dev, dev->slice_id, stream->list_num); + enable_irqs(dev, dev->slice_id, stream->list_num); + vip_schedule_next_buffer(stream); + vip_parser_stop_imm(port, false); + vip_enable_parser(port, true); + + return 0; + +err: + return_buffers(vq, VB2_BUF_STATE_QUEUED); + return ret; +} + +/* + * Abort streaming and wait for last buffer + */ +static void vip_stop_streaming(struct vb2_queue *vq) +{ + struct vip_stream *stream = vb2_get_drv_priv(vq); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + int ret; + + vip_parser_stop_imm(port, true); + vip_enable_parser(port, false); + unset_fmt_params(stream); + + disable_irqs(dev, dev->slice_id, stream->list_num); + clear_irqs(dev, dev->slice_id, stream->list_num); + + if (port->subdev) { + ret = v4l2_subdev_call(port->subdev, video, s_stream, 0); + if (ret) + v4l2_err(&dev->v4l2_dev, "Failed to stop subdev stream"); + } + + stop_dma(stream, true); + + return_buffers(vq, VB2_BUF_STATE_ERROR); + + vpdma_unmap_desc_buf(dev->shared->vpdma, &stream->desc_list.buf); + vpdma_reset_desc_list(&stream->desc_list); +} + +static const struct vb2_ops vip_video_qops = { + .queue_setup = vip_queue_setup, + .buf_prepare = vip_buf_prepare, + .buf_queue = vip_buf_queue, + .start_streaming = vip_start_streaming, + .stop_streaming = vip_stop_streaming, +}; + +static int vip_init_dev(struct vip_dev *dev) +{ + if (dev->num_ports != 0) + goto done; + + vip_set_clock_enable(dev, 1); + vip_module_toggle(dev, VIP_SC_RST, false); + vip_module_toggle(dev, VIP_CSC_RST, false); +done: + dev->num_ports++; + + return 0; +} + +static inline bool is_scaler_available(struct vip_port *port) +{ + if (port->endpoint.bus_type == V4L2_MBUS_PARALLEL) + if (port->dev->sc_assigned == VIP_NOT_ASSIGNED || + port->dev->sc_assigned == port->port_id) + return true; + return false; +} + +static inline bool allocate_scaler(struct vip_port *port) +{ + if (is_scaler_available(port)) { + if (port->dev->sc_assigned == VIP_NOT_ASSIGNED || + port->dev->sc_assigned == port->port_id) { + port->dev->sc_assigned = port->port_id; + port->scaler = true; + return true; + } + } + return false; +} + +static inline void free_scaler(struct vip_port *port) +{ + if (port->dev->sc_assigned == port->port_id) { + port->dev->sc_assigned = VIP_NOT_ASSIGNED; + port->scaler = false; + } +} + +static bool is_csc_available(struct vip_port *port) +{ + if (port->endpoint.bus_type == V4L2_MBUS_PARALLEL) + if (port->dev->csc_assigned == VIP_NOT_ASSIGNED || + port->dev->csc_assigned == port->port_id) + return true; + return false; +} + +static bool allocate_csc(struct vip_port *port, + enum vip_csc_state csc_direction) +{ + struct vip_dev *dev = port->dev; + /* Is CSC needed? */ + if (csc_direction != VIP_CSC_NA) { + if (is_csc_available(port)) { + port->dev->csc_assigned = port->port_id; + port->csc = csc_direction; + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: csc allocated: dir: %d\n", + __func__, csc_direction); + return true; + } + } + return false; +} + +static void free_csc(struct vip_port *port) +{ + struct vip_dev *dev = port->dev; + + if (port->dev->csc_assigned == port->port_id) { + port->dev->csc_assigned = VIP_NOT_ASSIGNED; + port->csc = VIP_CSC_NA; + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: csc freed\n", + __func__); + } +} + +static int vip_init_port(struct vip_port *port) +{ + struct vip_dev *dev = port->dev; + int ret; + struct vip_fmt *fmt; + struct v4l2_subdev_format sd_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = 0, + }; + struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format; + + if (port->num_streams != 0) + goto done; + + ret = vip_init_dev(port->dev); + if (ret) + goto done; + + /* Get subdevice current frame format */ + ret = v4l2_subdev_call(port->subdev, pad, get_fmt, NULL, &sd_fmt); + if (ret) + v4l2_dbg(1, debug, &dev->v4l2_dev, "init_port get_fmt failed in subdev: (%d)\n", + ret); + + /* try to find one that matches */ + fmt = find_port_format_by_code(port, mbus_fmt->code); + if (!fmt) { + v4l2_dbg(1, debug, &dev->v4l2_dev, "subdev default mbus_fmt %04x is not matched.\n", + mbus_fmt->code); + /* if all else fails just pick the first one */ + fmt = port->active_fmt[0]; + + mbus_fmt->code = fmt->code; + sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + sd_fmt.pad = 0; + ret = v4l2_subdev_call(port->subdev, pad, set_fmt, + NULL, &sd_fmt); + if (ret) + v4l2_dbg(1, debug, &dev->v4l2_dev, "init_port set_fmt failed in subdev: (%d)\n", + ret); + } + + /* Assign current format */ + port->fmt = fmt; + port->mbus_framefmt = *mbus_fmt; + + v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: g_mbus_fmt subdev mbus_code: %04X fourcc:%s size: %dx%d\n", + __func__, fmt->code, + fourcc_to_str(fmt->fourcc), + mbus_fmt->width, mbus_fmt->height); + + if (mbus_fmt->field == V4L2_FIELD_ALTERNATE) + port->flags |= FLAG_INTERLACED; + else + port->flags &= ~FLAG_INTERLACED; + + port->c_rect.left = 0; + port->c_rect.top = 0; + port->c_rect.width = mbus_fmt->width; + port->c_rect.height = mbus_fmt->height; + + ret = vpdma_alloc_desc_buf(&port->sc_coeff_h, SC_COEF_SRAM_SIZE); + if (ret != 0) + return ret; + + ret = vpdma_alloc_desc_buf(&port->sc_coeff_v, SC_COEF_SRAM_SIZE); + if (ret != 0) + goto free_sc_h; + + ret = vpdma_alloc_desc_buf(&port->mmr_adb, sizeof(struct vip_mmr_adb)); + if (ret != 0) + goto free_sc_v; + + init_adb_hdrs(port); + + vip_enable_parser(port, false); +done: + port->num_streams++; + return 0; + +free_sc_v: + vpdma_free_desc_buf(&port->sc_coeff_v); +free_sc_h: + vpdma_free_desc_buf(&port->sc_coeff_h); + return ret; +} + +static int vip_init_stream(struct vip_stream *stream) +{ + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + struct vip_fmt *fmt; + struct v4l2_mbus_framefmt *mbus_fmt; + struct v4l2_format f; + int ret; + + ret = vip_init_port(port); + if (ret != 0) + return ret; + + fmt = port->fmt; + mbus_fmt = &port->mbus_framefmt; + + memset(&f, 0, sizeof(f)); + + /* Properly calculate the sizeimage and bytesperline values. */ + v4l2_fill_pix_format(&f.fmt.pix, mbus_fmt); + f.fmt.pix.pixelformat = fmt->fourcc; + ret = vip_calc_format_size(port, fmt, &f); + if (ret) + return ret; + + stream->width = f.fmt.pix.width; + stream->height = f.fmt.pix.height; + stream->sup_field = f.fmt.pix.field; + stream->bytesperline = f.fmt.pix.bytesperline; + stream->sizeimage = f.fmt.pix.sizeimage; + + v4l2_dbg(3, debug, &dev->v4l2_dev, "init_stream fourcc:%s size: %dx%d bpl:%d img_size:%d\n", + fourcc_to_str(f.fmt.pix.pixelformat), + f.fmt.pix.width, f.fmt.pix.height, + f.fmt.pix.bytesperline, f.fmt.pix.sizeimage); + v4l2_dbg(3, debug, &dev->v4l2_dev, "init_stream vpdma data type: 0x%02X\n", + port->fmt->vpdma_fmt[0]->data_type); + + ret = vpdma_create_desc_list(&stream->desc_list, VIP_DESC_LIST_SIZE, + VPDMA_LIST_TYPE_NORMAL); + + if (ret != 0) + return ret; + + stream->write_desc = (struct vpdma_dtd *)stream->desc_list.buf.addr + + 15; + + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: stream instance %pa\n", + __func__, &stream); + + return 0; +} + +static void vip_release_dev(struct vip_dev *dev) +{ + /* + * On last close, disable clocks to conserve power + */ + + if (--dev->num_ports == 0) { + /* reset the scaler module */ + vip_module_toggle(dev, VIP_SC_RST, true); + vip_module_toggle(dev, VIP_CSC_RST, true); + vip_set_clock_enable(dev, 0); + } +} + +static int vip_set_crop_parser(struct vip_port *port) +{ + struct vip_dev *dev = port->dev; + struct vip_parser_data *parser = dev->parser; + u32 hcrop = 0, vcrop = 0; + u32 width = port->mbus_framefmt.width; + + if (port->fmt->vpdma_fmt[0] == &vpdma_raw_fmts[VPDMA_DATA_FMT_RAW8]) { + /* + * Special case since we are faking a YUV422 16bit format + * to have the vpdma perform the needed byte swap + * we need to adjust the pixel width accordingly + * otherwise the parser will attempt to collect more pixels + * then available and the vpdma transfer will exceed the + * allocated frame buffer. + */ + width >>= 1; + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: 8 bit raw detected, adjusting width to %d\n", + __func__, width); + } + + /* + * Set Parser Crop parameters to source size otherwise + * scaler and colorspace converter will yield garbage. + */ + hcrop = VIP_ACT_BYPASS; + insert_field(&hcrop, 0, VIP_ACT_SKIP_NUMPIX_MASK, + VIP_ACT_SKIP_NUMPIX_SHFT); + insert_field(&hcrop, width, + VIP_ACT_USE_NUMPIX_MASK, VIP_ACT_USE_NUMPIX_SHFT); + reg_write(parser, VIP_PARSER_CROP_H_PORT(port->port_id), hcrop); + + insert_field(&vcrop, 0, VIP_ACT_SKIP_NUMLINES_MASK, + VIP_ACT_SKIP_NUMLINES_SHFT); + insert_field(&vcrop, port->mbus_framefmt.height, + VIP_ACT_USE_NUMLINES_MASK, VIP_ACT_USE_NUMLINES_SHFT); + reg_write(parser, VIP_PARSER_CROP_V_PORT(port->port_id), vcrop); + + return 0; +} + +static int vip_setup_parser(struct vip_port *port) +{ + struct vip_dev *dev = port->dev; + struct vip_parser_data *parser = dev->parser; + struct v4l2_fwnode_endpoint *endpoint = &port->endpoint; + int iface, sync_type; + u32 flags = 0, config0; + + /* Reset the port */ + vip_reset_parser(port, true); + usleep_range(200, 250); + vip_reset_parser(port, false); + + config0 = reg_read(parser, VIP_PARSER_PORT(port->port_id)); + + if (endpoint->bus_type == V4L2_MBUS_BT656) { + flags = endpoint->bus.parallel.flags; + iface = DUAL_8B_INTERFACE; + + /* + * Ideally, this should come from subdev + * port->fmt can be anything once CSC is enabled + */ + if (vip_is_mbuscode_rgb(port->fmt->code)) + sync_type = EMBEDDED_SYNC_SINGLE_RGB_OR_YUV444; + else + sync_type = EMBEDDED_SYNC_LINE_MULTIPLEXED_YUV422; + + } else if (endpoint->bus_type == V4L2_MBUS_PARALLEL) { + flags = endpoint->bus.parallel.flags; + + switch (endpoint->bus.parallel.bus_width) { + case 24: + iface = SINGLE_24B_INTERFACE; + break; + case 16: + iface = SINGLE_16B_INTERFACE; + break; + case 8: + default: + iface = DUAL_8B_INTERFACE; + } + + if (vip_is_mbuscode_rgb(port->fmt->code)) + sync_type = DISCRETE_SYNC_SINGLE_RGB_24B; + else + sync_type = DISCRETE_SYNC_SINGLE_YUV422; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + config0 |= VIP_HSYNC_POLARITY; + else if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + config0 &= ~VIP_HSYNC_POLARITY; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + config0 |= VIP_VSYNC_POLARITY; + else if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + config0 &= ~VIP_VSYNC_POLARITY; + + config0 &= ~VIP_USE_ACTVID_HSYNC_ONLY; + config0 |= VIP_ACTVID_POLARITY; + config0 |= VIP_DISCRETE_BASIC_MODE; + + } else { + v4l2_err(&dev->v4l2_dev, "Device doesn't support CSI2"); + return -EINVAL; + } + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) { + vip_set_pclk_invert(port); + config0 |= VIP_PIXCLK_EDGE_POLARITY; + } else { + config0 &= ~VIP_PIXCLK_EDGE_POLARITY; + } + + config0 |= ((sync_type & VIP_SYNC_TYPE_MASK) << VIP_SYNC_TYPE_SHFT); + + reg_write(parser, VIP_PARSER_PORT(port->port_id), config0); + + vip_set_data_interface(port, iface); + vip_set_crop_parser(port); + + return 0; +} + +static void vip_enable_parser(struct vip_port *port, bool on) +{ + u32 config0; + struct vip_dev *dev = port->dev; + struct vip_parser_data *parser = dev->parser; + + config0 = reg_read(parser, VIP_PARSER_PORT(port->port_id)); + + if (on) { + config0 |= VIP_PORT_ENABLE; + config0 &= ~(VIP_ASYNC_FIFO_RD | VIP_ASYNC_FIFO_WR); + } else { + config0 &= ~VIP_PORT_ENABLE; + config0 |= (VIP_ASYNC_FIFO_RD | VIP_ASYNC_FIFO_WR); + } + reg_write(parser, VIP_PARSER_PORT(port->port_id), config0); +} + +static void vip_reset_parser(struct vip_port *port, bool on) +{ + u32 config0; + struct vip_dev *dev = port->dev; + struct vip_parser_data *parser = dev->parser; + + config0 = reg_read(parser, VIP_PARSER_PORT(port->port_id)); + + if (on) + config0 |= VIP_SW_RESET; + else + config0 &= ~VIP_SW_RESET; + + reg_write(parser, VIP_PARSER_PORT(port->port_id), config0); +} + +static void vip_parser_stop_imm(struct vip_port *port, bool on) +{ + u32 config0; + struct vip_dev *dev = port->dev; + struct vip_parser_data *parser = dev->parser; + + config0 = reg_read(parser, VIP_PARSER_STOP_IMM_PORT(port->port_id)); + + if (on) + config0 = 0xffffffff; + else + config0 = 0; + + reg_write(parser, VIP_PARSER_STOP_IMM_PORT(port->port_id), config0); +} + +static void vip_release_stream(struct vip_stream *stream) +{ + struct vip_dev *dev = stream->port->dev; + + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: stream instance %pa\n", + __func__, &stream); + + vpdma_unmap_desc_buf(dev->shared->vpdma, &stream->desc_list.buf); + vpdma_free_desc_buf(&stream->desc_list.buf); + vpdma_free_desc_list(&stream->desc_list); +} + +static void vip_release_port(struct vip_port *port) +{ + struct vip_dev *dev = port->dev; + + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: port instance %pa\n", + __func__, &port); + + vpdma_free_desc_buf(&port->mmr_adb); + vpdma_free_desc_buf(&port->sc_coeff_h); + vpdma_free_desc_buf(&port->sc_coeff_v); +} + +static void stop_dma(struct vip_stream *stream, bool clear_list) +{ + struct vip_dev *dev = stream->port->dev; + int ch, size = 0; + + /* Create a list of channels to be cleared */ + for (ch = 0; ch < VPDMA_MAX_CHANNELS; ch++) { + if (stream->vpdma_channels[ch] == 1) { + stream->vpdma_channels_to_abort[size++] = ch; + v4l2_dbg(2, debug, &dev->v4l2_dev, "Clear channel no: %d\n", ch); + } + } + + /* Clear all the used channels for the list */ + vpdma_list_cleanup(dev->shared->vpdma, stream->list_num, + stream->vpdma_channels_to_abort, size); + + if (clear_list) + for (ch = 0; ch < VPDMA_MAX_CHANNELS; ch++) + stream->vpdma_channels[ch] = 0; +} + +static int vip_open(struct file *file) +{ + struct vip_stream *stream = video_drvdata(file); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + int ret = 0; + + mutex_lock(&dev->mutex); + + ret = v4l2_fh_open(file); + if (ret) { + v4l2_err(&dev->v4l2_dev, "v4l2_fh_open failed\n"); + goto unlock; + } + + /* + * If this is the first open file. + * Then initialize hw module. + */ + if (!v4l2_fh_is_singular_file(file)) + goto unlock; + + if (vip_init_stream(stream)) + ret = -ENODEV; +unlock: + mutex_unlock(&dev->mutex); + return ret; +} + +static int vip_release(struct file *file) +{ + struct vip_stream *stream = video_drvdata(file); + struct vip_port *port = stream->port; + struct vip_dev *dev = port->dev; + bool fh_singular; + int ret; + + mutex_lock(&dev->mutex); + + /* Save the singular status before we call the clean-up helper */ + fh_singular = v4l2_fh_is_singular_file(file); + + /* the release helper will cleanup any on-going streaming */ + ret = _vb2_fop_release(file, NULL); + + free_csc(port); + free_scaler(port); + + /* + * If this is the last open file. + * Then de-initialize hw module. + */ + if (fh_singular) { + vip_release_stream(stream); + + if (--port->num_streams == 0) { + vip_release_port(port); + vip_release_dev(port->dev); + } + } + + mutex_unlock(&dev->mutex); + + return ret; +} + +/* + * File operations + */ +static const struct v4l2_file_operations vip_fops = { + .owner = THIS_MODULE, + .open = vip_open, + .release = vip_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +static struct video_device vip_videodev = { + .name = VIP_MODULE_NAME, + .fops = &vip_fops, + .ioctl_ops = &vip_ioctl_ops, + .minor = -1, + .release = video_device_release, + .tvnorms = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM, + .device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE, +}; + +static int alloc_stream(struct vip_port *port, int stream_id, int vfl_type) +{ + struct vip_stream *stream; + struct vip_dev *dev = port->dev; + struct vb2_queue *q; + struct video_device *vfd; + struct vip_buffer *buf; + struct list_head *pos, *tmp; + int ret, i; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->port = port; + stream->stream_id = stream_id; + stream->vfl_type = vfl_type; + port->cap_streams[stream_id] = stream; + + stream->list_num = vpdma_hwlist_alloc(dev->shared->vpdma, stream); + if (stream->list_num < 0) { + v4l2_err(&dev->v4l2_dev, "Could not get VPDMA hwlist"); + ret = -ENODEV; + goto do_free_stream; + } + + INIT_LIST_HEAD(&stream->post_bufs); + + /* + * Initialize queue + */ + q = &stream->vb_vidq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->drv_priv = stream; + q->buf_struct_size = sizeof(struct vip_buffer); + q->ops = &vip_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->mutex; + q->min_queued_buffers = 2; + q->dev = dev->v4l2_dev.dev; + + ret = vb2_queue_init(q); + if (ret) + goto do_free_hwlist; + + INIT_WORK(&stream->recovery_work, vip_overflow_recovery_work); + + INIT_LIST_HEAD(&stream->vidq); + + /* Allocate/populate Drop queue entries */ + INIT_LIST_HEAD(&stream->dropq); + for (i = 0; i < VIP_DROPQ_SIZE; i++) { + buf = kzalloc(sizeof(*buf), GFP_ATOMIC); + if (!buf) { + ret = -ENOMEM; + goto do_free_dropq; + } + buf->drop = true; + list_add(&buf->list, &stream->dropq); + } + + vfd = video_device_alloc(); + if (!vfd) { + ret = -ENOMEM; + goto do_free_dropq; + } + *vfd = vip_videodev; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->queue = q; + + vfd->lock = &dev->mutex; + video_set_drvdata(vfd, stream); + stream->vfd = vfd; + + ret = video_register_device(vfd, vfl_type, -1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto do_free_vfd; + } + + v4l2_info(&dev->v4l2_dev, "device registered as %s\n", + video_device_node_name(vfd)); + return 0; + +do_free_vfd: + video_device_release(vfd); +do_free_dropq: + list_for_each_safe(pos, tmp, &stream->dropq) { + buf = list_entry(pos, + struct vip_buffer, list); + v4l2_dbg(1, debug, &dev->v4l2_dev, "dropq buffer\n"); + list_del(pos); + kfree(buf); + } +do_free_hwlist: + vpdma_hwlist_release(dev->shared->vpdma, stream->list_num); +do_free_stream: + kfree(stream); + return ret; +} + +static void free_stream(struct vip_stream *stream) +{ + struct vip_dev *dev; + struct vip_buffer *buf; + struct list_head *pos, *q; + + if (!stream) + return; + + dev = stream->port->dev; + /* Free up the Drop queue */ + list_for_each_safe(pos, q, &stream->dropq) { + buf = list_entry(pos, + struct vip_buffer, list); + v4l2_dbg(1, debug, &dev->v4l2_dev, "dropq buffer\n"); + list_del(pos); + kfree(buf); + } + + video_unregister_device(stream->vfd); + vpdma_hwlist_release(dev->shared->vpdma, stream->list_num); + stream->port->cap_streams[stream->stream_id] = NULL; + kfree(stream); +} + +static int get_subdev_active_format(struct vip_port *port, + struct v4l2_subdev *subdev) +{ + struct vip_fmt *fmt; + struct vip_dev *dev = port->dev; + struct v4l2_subdev_mbus_code_enum mbus_code; + int ret = 0; + unsigned int k, i, j; + enum vip_csc_state csc; + + /* Enumerate sub device formats and enable all matching local formats */ + port->num_active_fmt = 0; + for (k = 0, i = 0; (ret != -EINVAL); k++) { + memset(&mbus_code, 0, sizeof(mbus_code)); + mbus_code.index = k; + mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(subdev, pad, enum_mbus_code, + NULL, &mbus_code); + if (ret) + continue; + + v4l2_dbg(2, debug, &dev->v4l2_dev, + "subdev %s: code: %04x idx: %d\n", + subdev->name, mbus_code.code, k); + + for (j = 0; j < ARRAY_SIZE(vip_formats); j++) { + fmt = &vip_formats[j]; + if (mbus_code.code != fmt->code) + continue; + + /* + * When the port is configured for BT656 + * then none of the downstream unit can be used. + * So here we need to skip all format requiring + * either CSC or CHR_DS + */ + csc = vip_csc_direction(fmt->code, fmt->finfo); + if (port->endpoint.bus_type == V4L2_MBUS_BT656 && + (csc != VIP_CSC_NA || fmt->coplanar)) + continue; + + port->active_fmt[i] = fmt; + v4l2_dbg(2, debug, &dev->v4l2_dev, + "matched fourcc: %s: code: %04x idx: %d\n", + fourcc_to_str(fmt->fourcc), fmt->code, i); + port->num_active_fmt = ++i; + } + } + + if (i == 0) { + v4l2_err(&dev->v4l2_dev, "No suitable format reported by subdev %s\n", + subdev->name); + return -EINVAL; + } + return 0; +} + +static int alloc_port(struct vip_dev *dev, int id) +{ + struct vip_port *port; + + if (dev->ports[id]) + return -EINVAL; + + port = devm_kzalloc(&dev->pdev->dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + dev->ports[id] = port; + port->dev = dev; + port->port_id = id; + port->num_streams = 0; + return 0; +} + +static void free_port(struct vip_port *port) +{ + if (!port) + return; + + v4l2_async_nf_unregister(&port->notifier); + v4l2_async_nf_cleanup(&port->notifier); + free_stream(port->cap_streams[0]); +} + +static int get_field(u32 value, u32 mask, int shift) +{ + return (value & (mask << shift)) >> shift; +} + +static int vip_probe_complete(struct platform_device *pdev); +static void vip_vpdma_fw_cb(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "VPDMA firmware loaded\n"); + + if (pdev->dev.of_node) + vip_probe_complete(pdev); +} + +static int vip_create_streams(struct vip_port *port, + struct v4l2_subdev *subdev) +{ + int i; + + for (i = 0; i < VIP_CAP_STREAMS_PER_PORT; i++) + free_stream(port->cap_streams[i]); + + if (get_subdev_active_format(port, subdev)) + return -ENODEV; + + port->subdev = subdev; + + if (port->endpoint.bus_type == V4L2_MBUS_PARALLEL) { + port->flags |= FLAG_MULT_PORT; + port->num_streams_configured = 1; + alloc_stream(port, 0, VFL_TYPE_VIDEO); + } else if (port->endpoint.bus_type == V4L2_MBUS_BT656) { + port->flags |= FLAG_MULT_PORT; + port->num_streams_configured = 1; + alloc_stream(port, 0, VFL_TYPE_VIDEO); + } + return 0; +} + +static int vip_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asd) +{ + struct vip_port *port = notifier_to_vip_port(notifier); + int ret; + + if (port->subdev) { + v4l2_info(&port->dev->v4l2_dev, "Rejecting subdev %s (Already set!!)", + subdev->name); + return 0; + } + + v4l2_info(&port->dev->v4l2_dev, "Port %c: Using subdev %s for capture\n", + port->port_id == VIP_PORTA ? 'A' : 'B', subdev->name); + + ret = vip_create_streams(port, subdev); + if (ret) + return ret; + + return 0; +} + +static int vip_async_complete(struct v4l2_async_notifier *notifier) +{ + return 0; +} + +static const struct v4l2_async_notifier_operations vip_async_ops = { + .bound = vip_async_bound, + .complete = vip_async_complete, +}; + +static struct fwnode_handle * +fwnode_graph_get_next_endpoint_by_regs(const struct fwnode_handle *fwnode, + int port_reg, int reg) +{ + return of_fwnode_handle(of_graph_get_endpoint_by_regs(to_of_node(fwnode), + port_reg, reg)); +} + +static int vip_register_subdev_notify(struct vip_port *port, + struct fwnode_handle *ep) +{ + struct v4l2_async_notifier *notifier = &port->notifier; + struct fwnode_handle *subdev; + struct v4l2_fwnode_endpoint *vep; + struct v4l2_async_connection *asd; + int ret; + struct vip_dev *dev = port->dev; + + vep = &port->endpoint; + + subdev = fwnode_graph_get_remote_port_parent(ep); + if (!subdev) { + v4l2_dbg(3, debug, &dev->v4l2_dev, "can't get remote parent\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(ep, vep); + if (ret) { + v4l2_dbg(3, debug, &dev->v4l2_dev, "Failed to parse endpoint:\n"); + fwnode_handle_put(subdev); + return -EINVAL; + } + + v4l2_async_nf_init(notifier, &port->dev->shared->v4l2_dev); + + asd = v4l2_async_nf_add_fwnode(notifier, subdev, struct v4l2_async_connection); + if (IS_ERR(asd)) { + v4l2_dbg(1, debug, &dev->v4l2_dev, "Error adding asd\n"); + fwnode_handle_put(subdev); + v4l2_async_nf_cleanup(notifier); + return -EINVAL; + } + + notifier->ops = &vip_async_ops; + ret = v4l2_async_nf_register(notifier); + if (ret) { + v4l2_dbg(1, debug, &dev->v4l2_dev, "Error registering async notifier\n"); + v4l2_async_nf_cleanup(notifier); + ret = -EINVAL; + } + + return ret; +} + +static int vip_endpoint_scan(struct platform_device *pdev) +{ + struct device_node *parent = pdev->dev.of_node; + struct device_node *ep = NULL; + int count = 0, p; + + for (p = 0; p < (VIP_NUM_PORTS * VIP_NUM_SLICES); p++) { + ep = of_graph_get_endpoint_by_regs(parent, p, 0); + if (!ep) + continue; + + count++; + of_node_put(ep); + } + + return count; +} + +static int vip_probe_complete(struct platform_device *pdev) +{ + struct vip_shared *shared = platform_get_drvdata(pdev); + struct vip_ctrl_module *ctrl = NULL; + struct vip_port *port; + struct vip_dev *dev; + struct device_node *parent = pdev->dev.of_node; + struct fwnode_handle *ep = NULL; + unsigned int syscon_args[5]; + int ret, i, slice_id, port_id, p; + + /* Allocate ctrl before using it */ + ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + ctrl->syscon_pol = syscon_regmap_lookup_by_phandle_args(parent, "ti,ctrl-module", + 5, syscon_args); + + if (IS_ERR(ctrl->syscon_pol)) + return dev_err_probe(&pdev->dev, PTR_ERR(ctrl->syscon_pol), + "Failed to get ti,ctrl-module\n"); + + ctrl->syscon_offset = syscon_args[0]; + + for (i = 0; i < ARRAY_SIZE(ctrl->syscon_bit_field); i++) + ctrl->syscon_bit_field[i] = syscon_args[i + 1]; + + for (p = 0; p < (VIP_NUM_PORTS * VIP_NUM_SLICES); p++) { + ep = fwnode_graph_get_next_endpoint_by_regs(of_fwnode_handle(parent), + p, 0); + if (!ep) + continue; + + switch (p) { + case 0: + slice_id = VIP_SLICE1; + port_id = VIP_PORTA; + break; + case 1: + slice_id = VIP_SLICE2; + port_id = VIP_PORTA; + break; + case 2: + slice_id = VIP_SLICE1; + port_id = VIP_PORTB; + break; + case 3: + slice_id = VIP_SLICE2; + port_id = VIP_PORTB; + break; + default: + dev_err(&pdev->dev, "Unknown port reg=<%d>\n", p); + continue; + } + + ret = alloc_port(shared->devs[slice_id], port_id); + if (ret < 0) + continue; + + dev = shared->devs[slice_id]; + dev->syscon = ctrl; + port = dev->ports[port_id]; + + vip_register_subdev_notify(port, ep); + fwnode_handle_put(ep); + } + return 0; +} + +static int vip_probe_slice(struct platform_device *pdev, int slice) +{ + struct vip_shared *shared = platform_get_drvdata(pdev); + struct vip_dev *dev; + struct vip_parser_data *parser; + struct sc_data *sc; + struct csc_data *csc; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->irq = platform_get_irq(pdev, slice); + if (dev->irq < 0) + return dev->irq; + + ret = devm_request_irq(&pdev->dev, dev->irq, vip_irq, + 0, VIP_MODULE_NAME, dev); + if (ret < 0) + return -ENOMEM; + + spin_lock_init(&dev->slock); + mutex_init(&dev->mutex); + + dev->slice_id = slice; + dev->pdev = pdev; + dev->base = shared->base; + dev->v4l2_dev = shared->v4l2_dev; + + dev->shared = shared; + shared->devs[slice] = dev; + + vip_top_reset(dev); + vip_set_slice_path(dev, VIP_MULTI_CHANNEL_DATA_SELECT, 1); + + parser = devm_kzalloc(&pdev->dev, sizeof(*dev->parser), GFP_KERNEL); + if (!parser) + return PTR_ERR_OR_ZERO(parser); + + parser->base = dev->base + (slice ? VIP_SLICE1_PARSER : VIP_SLICE0_PARSER); + if (IS_ERR(parser->base)) + return PTR_ERR(parser->base); + + parser->pdev = pdev; + dev->parser = parser; + + dev->sc_assigned = VIP_NOT_ASSIGNED; + sc = devm_kzalloc(&pdev->dev, sizeof(*dev->sc), GFP_KERNEL); + if (!sc) + return PTR_ERR_OR_ZERO(sc); + + sc->base = dev->base + (slice ? VIP_SLICE1_SC : VIP_SLICE0_SC); + if (IS_ERR(sc->base)) + return PTR_ERR(sc->base); + + sc->pdev = pdev; + dev->sc = sc; + + dev->csc_assigned = VIP_NOT_ASSIGNED; + csc = devm_kzalloc(&pdev->dev, sizeof(*dev->csc), GFP_KERNEL); + if (!csc) + return PTR_ERR_OR_ZERO(csc); + + csc->base = dev->base + (slice ? VIP_SLICE1_CSC : VIP_SLICE0_CSC); + if (IS_ERR(csc->base)) + return PTR_ERR(csc->base); + + csc->pdev = pdev; + dev->csc = csc; + + return 0; +} + +static int vip_probe(struct platform_device *pdev) +{ + struct vip_shared *shared; + int ret, slice = VIP_SLICE1; + u32 tmp, pid; + + /* If there are no endpoint defined there is nothing to do */ + if (!vip_endpoint_scan(pdev)) { + dev_err(&pdev->dev, "%s: No sensor", __func__); + return -ENODEV; + } + + ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, + "32-bit consistent DMA enable failed\n"); + return ret; + } + + shared = devm_kzalloc(&pdev->dev, sizeof(*shared), GFP_KERNEL); + if (!shared) + return -ENOMEM; + + shared->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(shared->base)) + return PTR_ERR(shared->base); + + vip_init_format_info(&pdev->dev); + + pm_runtime_enable(&pdev->dev); + + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) + goto err_runtime_disable; + + /* Make sure H/W module has the right functionality */ + pid = reg_read(shared, VIP_PID); + tmp = get_field(pid, VIP_PID_FUNC_MASK, VIP_PID_FUNC_SHIFT); + + if (tmp != VIP_PID_FUNC) { + dev_info(&pdev->dev, "vip: unexpected PID function: 0x%x\n", + tmp); + ret = -ENODEV; + goto err_runtime_put; + } + + ret = v4l2_device_register(&pdev->dev, &shared->v4l2_dev); + if (ret) + goto err_runtime_put; + + /* enable clocks, so the firmware will load properly */ + vip_shared_set_clock_enable(shared, 1); + vip_top_vpdma_reset(shared); + + platform_set_drvdata(pdev, shared); + + v4l2_ctrl_handler_init(&shared->ctrl_handler, 11); + shared->v4l2_dev.ctrl_handler = &shared->ctrl_handler; + + for (slice = VIP_SLICE1; slice < VIP_NUM_SLICES; slice++) { + ret = vip_probe_slice(pdev, slice); + if (ret) { + dev_err(&pdev->dev, "Creating slice failed"); + goto err_dev_unreg; + } + } + + shared->vpdma = &shared->vpdma_data; + + shared->vpdma->pdev = pdev; + shared->vpdma->cb = vip_vpdma_fw_cb; + spin_lock_init(&shared->vpdma->lock); + + shared->vpdma->base = shared->base + VIP_VPDMA_BASE; + if (!shared->vpdma->base) { + dev_err(&pdev->dev, "failed to ioremap\n"); + ret = -ENOMEM; + goto err_dev_unreg; + } + + ret = vpdma_load_firmware(shared->vpdma); + if (ret) { + dev_err(&pdev->dev, "Creating VPDMA failed"); + goto err_dev_unreg; + } + + return 0; + +err_dev_unreg: + v4l2_ctrl_handler_free(&shared->ctrl_handler); + v4l2_device_unregister(&shared->v4l2_dev); +err_runtime_put: + pm_runtime_put_sync(&pdev->dev); +err_runtime_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static void vip_remove(struct platform_device *pdev) +{ + struct vip_shared *shared = platform_get_drvdata(pdev); + struct vip_dev *dev; + int slice; + + for (slice = 0; slice < VIP_NUM_SLICES; slice++) { + dev = shared->devs[slice]; + if (!dev) + continue; + + free_port(dev->ports[VIP_PORTA]); + free_port(dev->ports[VIP_PORTB]); + } + + v4l2_ctrl_handler_free(&shared->ctrl_handler); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); +} + +#if defined(CONFIG_OF) +static const struct of_device_id vip_of_match[] = { + { + .compatible = "ti,dra7-vip", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, vip_of_match); +#endif + +static struct platform_driver vip_pdrv = { + .probe = vip_probe, + .remove = vip_remove, + .driver = { + .name = VIP_MODULE_NAME, + .of_match_table = of_match_ptr(vip_of_match), + }, +}; + +module_platform_driver(vip_pdrv); + +MODULE_DESCRIPTION("TI VIP driver"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/ti/vpe/vip.h b/drivers/media/platform/ti/vpe/vip.h new file mode 100644 index 000000000000..20525369955d --- /dev/null +++ b/drivers/media/platform/ti/vpe/vip.h @@ -0,0 +1,717 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * TI VIP capture driver + * + * Copyright (C) 2025 Texas Instruments Incorpated - http://www.ti.com/ + * David Griego, + * Dale Farnsworth, + * Yemike Abhilash Chandra, + */ + +#ifndef __TI_VIP_H +#define __TI_VIP_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vpdma.h" +#include "vpdma_priv.h" +#include "sc.h" +#include "csc.h" + +#define VIP_INSTANCE1 1 +#define VIP_INSTANCE2 2 +#define VIP_INSTANCE3 3 + +#define VIP_SLICE1 0 +#define VIP_SLICE2 1 +#define VIP_NUM_SLICES 2 + +/* + * Additional client identifiers used for VPDMA configuration descriptors + */ +#define VIP_SLICE1_CFD_SC_CLIENT 7 +#define VIP_SLICE2_CFD_SC_CLIENT 8 + +#define VIP_PORTA 0 +#define VIP_PORTB 1 +#define VIP_NUM_PORTS 2 + +#define VIP_MAX_PLANES 2 +#define VIP_LUMA 0 +#define VIP_CHROMA 1 + +#define VIP_CAP_STREAMS_PER_PORT 16 +#define VIP_VBI_STREAMS_PER_PORT 16 + +#define VIP_MAX_SUBDEV 5 + +#define VPDMA_FIRMWARE "vpdma-1b8.bin" + +/* + * This value needs to be at least as large as the number of entry in + * vip_formats[]. + * When vip_formats[] is modified make sure to adjust this value also. + */ +#define VIP_MAX_ACTIVE_FMT 16 +/* + * Colorspace conversion unit can be in one of 3 modes: + * NA - Not Available on this port + * Y2R - Needed for YUV to RGB on this port + * R2Y - Needed for RGB to YUV on this port + */ +enum vip_csc_state { + VIP_CSC_NA = 0, + VIP_CSC_Y2R, + VIP_CSC_R2Y, +}; + +/* buffer for one video frame */ +struct vip_buffer { + /* common v4l buffer stuff */ + struct vb2_v4l2_buffer vb; + struct list_head list; + bool drop; +}; + +/* + * struct vip_fmt - VIP media bus format information + * @fourcc: V4L2 pixel format FCC identifier + * @code: V4L2 media bus format code + * @colorspace: V4L2 colorspace identifier + * @coplanar: 1 if unpacked Luma and Chroma, 0 otherwise (packed/interleaved) + * @vpdma_fmt: VPDMA data format per plane. + * @finfo: Cache v4l2_format_info for associated fourcc + */ +struct vip_fmt { + u32 fourcc; + u32 code; + u32 colorspace; + u8 coplanar; + const struct vpdma_data_format *vpdma_fmt[VIP_MAX_PLANES]; + const struct v4l2_format_info *finfo; +}; + +/* + * The vip_parser_data structures contains the memory mapped + * info to access the parser registers. + */ +struct vip_parser_data { + void __iomem *base; + + struct platform_device *pdev; +}; + +/* + * The vip_shared structure contains data that is shared by both + * the VIP1 and VIP2 slices. + */ +struct vip_shared { + struct list_head list; + void __iomem *base; + struct vpdma_data vpdma_data; + struct vpdma_data *vpdma; + struct v4l2_device v4l2_dev; + struct vip_dev *devs[VIP_NUM_SLICES]; + struct v4l2_ctrl_handler ctrl_handler; +}; + +struct vip_ctrl_module { + struct regmap *syscon_pol; + u32 syscon_offset; + u32 syscon_bit_field[4]; +}; + +/* + * There are two vip_dev structure, one for each vip slice: VIP1 & VIP2. + */ +struct vip_dev { + struct v4l2_device v4l2_dev; + struct platform_device *pdev; + struct vip_shared *shared; + struct vip_ctrl_module *syscon; + int instance_id; + int slice_id; + int num_ports; /* count of open ports */ + struct mutex mutex; + /* protects access to stream buffer queues */ + spinlock_t slock; + + int irq; + void __iomem *base; + + struct vip_port *ports[VIP_NUM_PORTS]; + + char name[16]; + /* parser data handle */ + struct vip_parser_data *parser; + /* scaler data handle */ + struct sc_data *sc; + /* scaler port assignation */ + int sc_assigned; + /* csc data handle */ + struct csc_data *csc; + /* csc port assignation */ + int csc_assigned; +}; + +/* + * There are two vip_port structures for each vip_dev, one for port A + * and one for port B. + */ +struct vip_port { + struct vip_dev *dev; + int port_id; + + unsigned int flags; + struct v4l2_rect c_rect; /* crop rectangle */ + struct v4l2_mbus_framefmt mbus_framefmt; + struct v4l2_mbus_framefmt try_mbus_framefmt; + + char name[16]; + struct vip_fmt *fmt; /* current format info */ + /* Number of channels/streams configured */ + int num_streams_configured; + int num_streams; /* count of open streams */ + struct vip_stream *cap_streams[VIP_CAP_STREAMS_PER_PORT]; + + struct v4l2_async_notifier notifier; + struct v4l2_subdev *subdev; + struct v4l2_fwnode_endpoint endpoint; + struct vip_fmt *active_fmt[VIP_MAX_ACTIVE_FMT]; + int num_active_fmt; + /* have new shadow reg values */ + bool load_mmrs; + /* shadow reg addr/data block */ + struct vpdma_buf mmr_adb; + /* h coeff buffer */ + struct vpdma_buf sc_coeff_h; + /* v coeff buffer */ + struct vpdma_buf sc_coeff_v; + /* Show if scaler resource is available on this port */ + bool scaler; + /* Show the csc resource state on this port */ + enum vip_csc_state csc; +}; + +/* + * When handling multiplexed video, there can be multiple streams for each + * port. The vip_stream structure holds per-stream data. + */ +struct vip_stream { + struct video_device *vfd; + struct vip_port *port; + int stream_id; + int list_num; + int vfl_type; + char name[16]; + struct work_struct recovery_work; + int num_recovery; + enum v4l2_field field; /* current field */ + unsigned int sequence; /* current frame/field seq */ + enum v4l2_field sup_field; /* supported field value */ + unsigned int width; /* frame width */ + unsigned int height; /* frame height */ + unsigned int bytesperline; /* bytes per line in memory */ + unsigned int sizeimage; /* image size in memory */ + struct list_head vidq; /* incoming vip_bufs queue */ + struct list_head dropq; /* drop vip_bufs queue */ + struct list_head post_bufs; /* vip_bufs to be DMAed */ + /* Maintain a list of used channels - Needed for VPDMA cleanup */ + int vpdma_channels[VPDMA_MAX_CHANNELS]; + int vpdma_channels_to_abort[VPDMA_MAX_CHANNELS]; + struct vpdma_desc_list desc_list; /* DMA descriptor list */ + struct vpdma_dtd *write_desc; + /* next unused desc_list addr */ + void *desc_next; + struct vb2_queue vb_vidq; +}; + +/* + * VIP Enumerations + */ +enum data_path_select { + ALL_FIELDS_DATA_SELECT = 0, + VIP_CSC_SRC_DATA_SELECT, + VIP_SC_SRC_DATA_SELECT, + VIP_RGB_SRC_DATA_SELECT, + VIP_RGB_OUT_LO_DATA_SELECT, + VIP_RGB_OUT_HI_DATA_SELECT, + VIP_CHR_DS_1_SRC_DATA_SELECT, + VIP_CHR_DS_2_SRC_DATA_SELECT, + VIP_MULTI_CHANNEL_DATA_SELECT, + VIP_CHR_DS_1_DATA_BYPASS, + VIP_CHR_DS_2_DATA_BYPASS, +}; + +enum data_interface_modes { + SINGLE_24B_INTERFACE = 0, + SINGLE_16B_INTERFACE = 1, + DUAL_8B_INTERFACE = 2, +}; + +enum sync_types { + EMBEDDED_SYNC_SINGLE_YUV422 = 0, + EMBEDDED_SYNC_2X_MULTIPLEXED_YUV422 = 1, + EMBEDDED_SYNC_4X_MULTIPLEXED_YUV422 = 2, + EMBEDDED_SYNC_LINE_MULTIPLEXED_YUV422 = 3, + DISCRETE_SYNC_SINGLE_YUV422 = 4, + EMBEDDED_SYNC_SINGLE_RGB_OR_YUV444 = 5, + DISCRETE_SYNC_SINGLE_RGB_24B = 10, +}; + +#define VIP_NOT_ASSIGNED -1 + +/* + * Register offsets and field selectors + */ +#define VIP_PID_FUNC 0xf02 + +#define VIP_PID 0x0000 +#define VIP_PID_MINOR_MASK 0x3f +#define VIP_PID_MINOR_SHIFT 0 +#define VIP_PID_CUSTOM_MASK 0x03 +#define VIP_PID_CUSTOM_SHIFT 6 +#define VIP_PID_MAJOR_MASK 0x07 +#define VIP_PID_MAJOR_SHIFT 8 +#define VIP_PID_RTL_MASK 0x1f +#define VIP_PID_RTL_SHIFT 11 +#define VIP_PID_FUNC_MASK 0xfff +#define VIP_PID_FUNC_SHIFT 16 +#define VIP_PID_SCHEME_MASK 0x03 +#define VIP_PID_SCHEME_SHIFT 30 + +#define VIP_SYSCONFIG 0x0010 +#define VIP_SYSCONFIG_IDLE_MASK 0x03 +#define VIP_SYSCONFIG_IDLE_SHIFT 2 +#define VIP_SYSCONFIG_STANDBY_MASK 0x03 +#define VIP_SYSCONFIG_STANDBY_SHIFT 4 +#define VIP_FORCE_IDLE_MODE 0 +#define VIP_NO_IDLE_MODE 1 +#define VIP_SMART_IDLE_MODE 2 +#define VIP_SMART_IDLE_WAKEUP_MODE 3 +#define VIP_FORCE_STANDBY_MODE 0 +#define VIP_NO_STANDBY_MODE 1 +#define VIP_SMART_STANDBY_MODE 2 +#define VIP_SMART_STANDBY_WAKEUP_MODE 3 + +#define VIP_INTC_INTX_OFFSET 0x0020 + +#define VIP_INT0_STATUS0_RAW_SET 0x0020 +#define VIP_INT0_STATUS0_RAW VIP_INT0_STATUS0_RAW_SET +#define VIP_INT0_STATUS0_CLR 0x0028 +#define VIP_INT0_STATUS0 VIP_INT0_STATUS0_CLR +#define VIP_INT0_ENABLE0_SET 0x0030 +#define VIP_INT0_ENABLE0 VIP_INT0_ENABLE0_SET +#define VIP_INT0_ENABLE0_CLR 0x0038 +#define VIP_INT0_LIST0_COMPLETE BIT(0) +#define VIP_INT0_LIST0_NOTIFY BIT(1) +#define VIP_INT0_LIST1_COMPLETE BIT(2) +#define VIP_INT0_LIST1_NOTIFY BIT(3) +#define VIP_INT0_LIST2_COMPLETE BIT(4) +#define VIP_INT0_LIST2_NOTIFY BIT(5) +#define VIP_INT0_LIST3_COMPLETE BIT(6) +#define VIP_INT0_LIST3_NOTIFY BIT(7) +#define VIP_INT0_LIST4_COMPLETE BIT(8) +#define VIP_INT0_LIST4_NOTIFY BIT(9) +#define VIP_INT0_LIST5_COMPLETE BIT(10) +#define VIP_INT0_LIST5_NOTIFY BIT(11) +#define VIP_INT0_LIST6_COMPLETE BIT(12) +#define VIP_INT0_LIST6_NOTIFY BIT(13) +#define VIP_INT0_LIST7_COMPLETE BIT(14) +#define VIP_INT0_LIST7_NOTIFY BIT(15) +#define VIP_INT0_DESCRIPTOR BIT(16) +#define VIP_VIP1_PARSER_INT BIT(20) +#define VIP_VIP2_PARSER_INT BIT(21) + +#define VIP_INT0_STATUS1_RAW_SET 0x0024 +#define VIP_INT0_STATUS1_RAW VIP_INT0_STATUS0_RAW_SET +#define VIP_INT0_STATUS1_CLR 0x002c +#define VIP_INT0_STATUS1 VIP_INT0_STATUS0_CLR +#define VIP_INT0_ENABLE1_SET 0x0034 +#define VIP_INT0_ENABLE1 VIP_INT0_ENABLE0_SET +#define VIP_INT0_ENABLE1_CLR 0x003c +#define VIP_INT0_ENABLE1_STAT 0x004c +#define VIP_INT0_CHANNEL_GROUP0 BIT(0) +#define VIP_INT0_CHANNEL_GROUP1 BIT(1) +#define VIP_INT0_CHANNEL_GROUP2 BIT(2) +#define VIP_INT0_CHANNEL_GROUP3 BIT(3) +#define VIP_INT0_CHANNEL_GROUP4 BIT(4) +#define VIP_INT0_CHANNEL_GROUP5 BIT(5) +#define VIP_INT0_CLIENT BIT(7) +#define VIP_VIP1_DS1_UV_ERROR_INT BIT(22) +#define VIP_VIP1_DS2_UV_ERROR_INT BIT(23) +#define VIP_VIP2_DS1_UV_ERROR_INT BIT(24) +#define VIP_VIP2_DS2_UV_ERROR_INT BIT(25) + +#define VIP_INTC_E0I 0x00a0 + +#define VIP_CLK_ENABLE 0x0100 +#define VIP_VPDMA_CLK_ENABLE BIT(0) +#define VIP_VIP1_DATA_PATH_CLK_ENABLE BIT(16) +#define VIP_VIP2_DATA_PATH_CLK_ENABLE BIT(17) + +#define VIP_CLK_RESET 0x0104 +#define VIP_VPDMA_RESET BIT(0) +#define VIP_VPDMA_CLK_RESET_MASK 0x1 +#define VIP_VPDMA_CLK_RESET_SHIFT 0 +#define VIP_DATA_PATH_CLK_RESET_MASK 0x1 +#define VIP_VIP1_DATA_PATH_RESET_SHIFT 16 +#define VIP_VIP2_DATA_PATH_RESET_SHIFT 17 +#define VIP_VIP1_DATA_PATH_RESET BIT(16) +#define VIP_VIP2_DATA_PATH_RESET BIT(17) +#define VIP_VIP1_PARSER_RESET BIT(18) +#define VIP_VIP2_PARSER_RESET BIT(19) +#define VIP_VIP1_CSC_RESET BIT(20) +#define VIP_VIP2_CSC_RESET BIT(21) +#define VIP_VIP1_SC_RESET BIT(22) +#define VIP_VIP2_SC_RESET BIT(23) +#define VIP_VIP1_DS1_RESET BIT(25) +#define VIP_VIP2_DS1_RESET BIT(26) +#define VIP_VIP1_DS2_RESET BIT(27) +#define VIP_VIP2_DS2_RESET BIT(28) +#define VIP_MAIN_RESET BIT(31) + +#define VIP_VIP1_DATA_PATH_SELECT 0x010c +#define VIP_VIP2_DATA_PATH_SELECT 0x0110 +#define VIP_CSC_SRC_SELECT_MASK 0x07 +#define VIP_CSC_SRC_SELECT_SHFT 0 +#define VIP_SC_SRC_SELECT_MASK 0x07 +#define VIP_SC_SRC_SELECT_SHFT 3 +#define VIP_RGB_SRC_SELECT BIT(6) +#define VIP_RGB_OUT_LO_SRC_SELECT BIT(7) +#define VIP_RGB_OUT_HI_SRC_SELECT BIT(8) +#define VIP_DS1_SRC_SELECT_MASK 0x07 +#define VIP_DS1_SRC_SELECT_SHFT 9 +#define VIP_DS2_SRC_SELECT_MASK 0x07 +#define VIP_DS2_SRC_SELECT_SHFT 12 +#define VIP_MULTI_CHANNEL_SELECT BIT(15) +#define VIP_DS1_BYPASS BIT(16) +#define VIP_DS2_BYPASS BIT(17) +#define VIP_TESTPORT_B_SELECT BIT(26) +#define VIP_TESTPORT_A_SELECT BIT(27) +#define VIP_DATAPATH_SELECT_MASK 0x0f +#define VIP_DATAPATH_SELECT_SHFT 28 + +#define VIP_PARSER_MAIN_CFG 0x0000 +#define VIP_DATA_INTERFACE_MODE_MASK 0x03 +#define VIP_DATA_INTERFACE_MODE_SHFT 0 +#define VIP_CLIP_BLANK BIT(4) +#define VIP_CLIP_ACTIVE BIT(5) + +#define VIP_SLICE0_PARSER 0x5500 +#define VIP_SLICE1_PARSER 0x5a00 +#define VIP_PARSER_PORTA_0 0x0004 +#define VIP_PARSER_PORTB_0 0x000c +#define VIP_SYNC_TYPE_MASK 0x0f +#define VIP_SYNC_TYPE_SHFT 0 +#define VIP_CTRL_CHANNEL_SEL_MASK 0x03 +#define VIP_CTRL_CHANNEL_SEL_SHFT 4 +#define VIP_ASYNC_FIFO_WR BIT(6) +#define VIP_ASYNC_FIFO_RD BIT(7) +#define VIP_PORT_ENABLE BIT(8) +#define VIP_FID_POLARITY BIT(9) +#define VIP_PIXCLK_EDGE_POLARITY BIT(10) +#define VIP_HSYNC_POLARITY BIT(11) +#define VIP_VSYNC_POLARITY BIT(12) +#define VIP_ACTVID_POLARITY BIT(13) +#define VIP_FID_DETECT_MODE BIT(14) +#define VIP_USE_ACTVID_HSYNC_ONLY BIT(15) +#define VIP_FID_SKEW_PRECOUNT_MASK 0x3f +#define VIP_FID_SKEW_PRECOUNT_SHFT 16 +#define VIP_DISCRETE_BASIC_MODE BIT(22) +#define VIP_SW_RESET BIT(23) +#define VIP_FID_SKEW_POSTCOUNT_MASK 0x3f +#define VIP_FID_SKEW_POSTCOUNT_SHFT 24 +#define VIP_ANALYZER_2X4X_SRCNUM_POS BIT(30) +#define VIP_ANALYZER_FVH_ERR_COR_EN BIT(31) + +#define VIP_PARSER_PORTA_1 0x0008 +#define VIP_PARSER_PORTB_1 0x0010 +#define VIP_SRC0_NUMLINES_MASK 0x0fff +#define VIP_SRC0_NUMLINES_SHFT 0 +#define VIP_ANC_CHAN_SEL_8B_MASK 0x03 +#define VIP_ANC_CHAN_SEL_8B_SHFT 13 +#define VIP_SRC0_NUMPIX_MASK 0x0fff +#define VIP_SRC0_NUMPIX_SHFT 16 +#define VIP_REPACK_SEL_MASK 0x07 +#define VIP_REPACK_SEL_SHFT 28 + +#define VIP_PARSER_FIQ_MASK 0x0014 +#define VIP_PARSER_FIQ_CLR 0x0018 +#define VIP_PARSER_FIQ_STATUS 0x001c +#define VIP_PORTA_VDET BIT(0) +#define VIP_PORTB_VDET BIT(1) +#define VIP_PORTA_ASYNC_FIFO_OF BIT(2) +#define VIP_PORTB_ASYNC_FIFO_OF BIT(3) +#define VIP_PORTA_OUTPUT_FIFO_YUV BIT(4) +#define VIP_PORTA_OUTPUT_FIFO_ANC BIT(6) +#define VIP_PORTB_OUTPUT_FIFO_YUV BIT(7) +#define VIP_PORTB_OUTPUT_FIFO_ANC BIT(9) +#define VIP_PORTA_CONN BIT(10) +#define VIP_PORTA_DISCONN BIT(11) +#define VIP_PORTB_CONN BIT(12) +#define VIP_PORTB_DISCONN BIT(13) +#define VIP_PORTA_SRC0_SIZE BIT(14) +#define VIP_PORTB_SRC0_SIZE BIT(15) +#define VIP_PORTA_YUV_PROTO_VIOLATION BIT(16) +#define VIP_PORTA_ANC_PROTO_VIOLATION BIT(17) +#define VIP_PORTB_YUV_PROTO_VIOLATION BIT(18) +#define VIP_PORTB_ANC_PROTO_VIOLATION BIT(19) +#define VIP_PORTA_CFG_DISABLE_COMPLETE BIT(20) +#define VIP_PORTB_CFG_DISABLE_COMPLETE BIT(21) + +#define VIP_PARSER_PORTA_SOURCE_FID 0x0020 +#define VIP_PARSER_PORTA_ENCODER_FID 0x0024 +#define VIP_PARSER_PORTB_SOURCE_FID 0x0028 +#define VIP_PARSER_PORTB_ENCODER_FID 0x002c + +#define VIP_PARSER_PORTA_SRC0_SIZE 0x0030 +#define VIP_PARSER_PORTB_SRC0_SIZE 0x0070 +#define VIP_SOURCE_HEIGHT_MASK 0x0fff +#define VIP_SOURCE_HEIGHT_SHFT 0 +#define VIP_SOURCE_WIDTH_MASK 0x0fff +#define VIP_SOURCE_WIDTH_SHFT 16 + +#define VIP_PARSER_PORTA_VDET_VEC 0x00b0 +#define VIP_PARSER_PORTB_VDET_VEC 0x00b4 + +#define VIP_PARSER_PORTA_EXTRA2 0x00b8 +#define VIP_PARSER_PORTB_EXTRA2 0x00c8 +#define VIP_ANC_SKIP_NUMPIX_MASK 0x0fff +#define VIP_ANC_SKIP_NUMPIX_SHFT 0 +#define VIP_ANC_BYPASS BIT(15) +#define VIP_ANC_USE_NUMPIX_MASK 0x0fff +#define VIP_ANC_USE_NUMPIX_SHFT 16 +#define VIP_ANC_TARGET_SRCNUM_MASK 0x0f +#define VIP_ANC_TARGET_SRCNUM_SHFT 28 + +#define VIP_PARSER_PORTA_EXTRA3 0x00bc +#define VIP_PARSER_PORTB_EXTRA3 0x00cc +#define VIP_ANC_SKIP_NUMLINES_MASK 0x0fff +#define VIP_ANC_SKIP_NUMLINES_SHFT 0 +#define VIP_ANC_USE_NUMLINES_MASK 0x0fff +#define VIP_ANC_USE_NUMLINES_SHFT 16 + +#define VIP_PARSER_PORTA_EXTRA4 0x00c0 +#define VIP_PARSER_PORTB_EXTRA4 0x00d0 +#define VIP_ACT_SKIP_NUMPIX_MASK 0x0fff +#define VIP_ACT_SKIP_NUMPIX_SHFT 0 +#define VIP_ACT_BYPASS BIT(15) +#define VIP_ACT_USE_NUMPIX_MASK 0x0fff +#define VIP_ACT_USE_NUMPIX_SHFT 16 +#define VIP_ACT_TARGET_SRCNUM_MASK 0x0f +#define VIP_ACT_TARGET_SRCNUM_SHFT 28 + +#define VIP_PARSER_PORTA_EXTRA5 0x00c4 +#define VIP_PARSER_PORTB_EXTRA5 0x00d4 +#define VIP_ACT_SKIP_NUMLINES_MASK 0x0fff +#define VIP_ACT_SKIP_NUMLINES_SHFT 0 +#define VIP_ACT_USE_NUMLINES_MASK 0x0fff +#define VIP_ACT_USE_NUMLINES_SHFT 16 + +#define VIP_PARSER_PORTA_EXTRA6 0x00d8 +#define VIP_PARSER_PORTB_EXTRA6 0x00dc +#define VIP_ANC_SRCNUM_STOP_IMM_SHFT 0 +#define VIP_YUV_SRCNUM_STOP_IMM_SHFT 16 + +#define VIP_SLICE0_CSC 0x5700 +#define VIP_SLICE1_CSC 0x5c00 +#define VIP_CSC_CSC00 0x0200 +#define VIP_CSC_A0_MASK 0x1fff +#define VIP_CSC_A0_SHFT 0 +#define VIP_CSC_B0_MASK 0x1fff +#define VIP_CSC_B0_SHFT 16 + +#define VIP_CSC_CSC01 0x0204 +#define VIP_CSC_C0_MASK 0x1fff +#define VIP_CSC_C0_SHFT 0 +#define VIP_CSC_A1_MASK 0x1fff +#define VIP_CSC_A1_SHFT 16 + +#define VIP_CSC_CSC02 0x0208 +#define VIP_CSC_B1_MASK 0x1fff +#define VIP_CSC_B1_SHFT 0 +#define VIP_CSC_C1_MASK 0x1fff +#define VIP_CSC_C1_SHFT 16 + +#define VIP_CSC_CSC03 0x020c +#define VIP_CSC_A2_MASK 0x1fff +#define VIP_CSC_A2_SHFT 0 +#define VIP_CSC_B2_MASK 0x1fff +#define VIP_CSC_B2_SHFT 16 + +#define VIP_CSC_CSC04 0x0210 +#define VIP_CSC_C2_MASK 0x1fff +#define VIP_CSC_C2_SHFT 0 +#define VIP_CSC_D0_MASK 0x0fff +#define VIP_CSC_D0_SHFT 16 + +#define VIP_CSC_CSC05 0x0214 +#define VIP_CSC_D1_MASK 0x0fff +#define VIP_CSC_D1_SHFT 0 +#define VIP_CSC_D2_MASK 0x0fff +#define VIP_CSC_D2_SHFT 16 +#define VIP_CSC_BYPASS BIT(28) + +#define VIP_SLICE0_SC 0x5800 +#define VIP_SLICE1_SC 0x5d00 +#define VIP_SC_MP_SC0 0x0300 +#define VIP_INTERLACE_O BIT(0) +#define VIP_LINEAR BIT(1) +#define VIP_SC_BYPASS BIT(2) +#define VIP_INVT_FID BIT(3) +#define VIP_USE_RAV BIT(4) +#define VIP_ENABLE_EV BIT(5) +#define VIP_AUTH_HS BIT(6) +#define VIP_DCM_2X BIT(7) +#define VIP_DCM_4X BIT(8) +#define VIP_HP_BYPASS BIT(9) +#define VIP_INTERLACE_I BIT(10) +#define VIP_ENABLE_SIN2_VER_INTP BIT(11) +#define VIP_Y_PK_EN BIT(14) +#define VIP_TRIM BIT(15) +#define VIP_SELFGEN_FID BIT(16) + +#define VIP_SC_MP_SC1 0x0304 +#define VIP_ROW_ACC_INC_MASK 0x07ffffff +#define VIP_ROW_ACC_INC_SHFT 0 + +#define VIP_SC_MP_SC2 0x0308 +#define VIP_ROW_ACC_OFFSET_MASK 0x0fffffff +#define VIP_ROW_ACC_OFFSET_SHFT 0 + +#define VIP_SC_MP_SC3 0x030c +#define VIP_ROW_ACC_OFFSET_B_MASK 0x0fffffff +#define VIP_ROW_ACC_OFFSET_B_SHFT 0 + +#define VIP_SC_MP_SC4 0x0310 +#define VIP_TAR_H_MASK 0x07ff +#define VIP_TAR_H_SHFT 0 +#define VIP_TAR_W_MASK 0x07ff +#define VIP_TAR_W_SHFT 12 +#define VIP_LIN_ACC_INC_U_MASK 0x07 +#define VIP_LIN_ACC_INC_U_SHFT 24 +#define VIP_NLIN_ACC_INIT_U_MASK 0x07 +#define VIP_NLIN_ACC_INIT_U_SHFT 28 + +#define VIP_SC_MP_SC5 0x0314 +#define VIP_SRC_H_MASK 0x03ff +#define VIP_SRC_H_SHFT 0 +#define VIP_SRC_W_MASK 0x07ff +#define VIP_SRC_W_SHFT 12 +#define VIP_NLIN_ACC_INC_U_MASK 0x07 +#define VIP_NLIN_ACC_INC_U_SHFT 24 + +#define VIP_SC_MP_SC6 0x0318 +#define VIP_ROW_ACC_INIT_RAV_MASK 0x03ff +#define VIP_ROW_ACC_INIT_RAV_SHFT 0 +#define VIP_ROW_ACC_INIT_RAV_B_MASK 0x03ff +#define VIP_ROW_ACC_INIT_RAV_B_SHFT 10 + +#define VIP_SC_MP_SC8 0x0320 +#define VIP_NLIN_LEFT_MASK 0x07ff +#define VIP_NLIN_LEFT_SHFT 0 +#define VIP_NLIN_RIGHT_MASK 0x07ff +#define VIP_NLIN_RIGHT_SHFT 12 + +#define VIP_SC_MP_SC9 0x0324 +#define VIP_LIN_ACC_INC VIP_SC_MP_SC9 + +#define VIP_SC_MP_SC10 0x0328 +#define VIP_NLIN_ACC_INIT VIP_SC_MP_SC10 + +#define VIP_SC_MP_SC11 0x032c +#define VIP_NLIN_ACC_INC VIP_SC_MP_SC11 + +#define VIP_SC_MP_SC12 0x0330 +#define VIP_COL_ACC_OFFSET_MASK 0x01ffffff +#define VIP_COL_ACC_OFFSET_SHFT 0 + +#define VIP_SC_MP_SC13 0x0334 +#define VIP_SC_FACTOR_RAV_MASK 0x03ff +#define VIP_SC_FACTOR_RAV_SHFT 0 +#define VIP_CHROMA_INTP_THR_MASK 0x03ff +#define VIP_CHROMA_INTP_THR_SHFT 12 +#define VIP_DELTA_CHROMA_THR_MASK 0x0f +#define VIP_DELTA_CHROMA_THR_SHFT 24 + +#define VIP_SC_MP_SC17 0x0344 +#define VIP_EV_THR_MASK 0x03ff +#define VIP_EV_THR_SHFT 12 +#define VIP_DELTA_LUMA_THR_MASK 0x0f +#define VIP_DELTA_LUMA_THR_SHFT 24 +#define VIP_DELTA_EV_THR_MASK 0x0f +#define VIP_DELTA_EV_THR_SHFT 28 + +#define VIP_SC_MP_SC18 0x0348 +#define VIP_HS_FACTOR_MASK 0x03ff +#define VIP_HS_FACTOR_SHFT 0 +#define VIP_CONF_DEFAULT_MASK 0x01ff +#define VIP_CONF_DEFAULT_SHFT 16 + +#define VIP_SC_MP_SC19 0x034c +#define VIP_HPF_COEFF0_MASK 0xff +#define VIP_HPF_COEFF0_SHFT 0 +#define VIP_HPF_COEFF1_MASK 0xff +#define VIP_HPF_COEFF1_SHFT 8 +#define VIP_HPF_COEFF2_MASK 0xff +#define VIP_HPF_COEFF2_SHFT 16 +#define VIP_HPF_COEFF3_MASK 0xff +#define VIP_HPF_COEFF3_SHFT 23 + +#define VIP_SC_MP_SC20 0x0350 +#define VIP_HPF_COEFF4_MASK 0xff +#define VIP_HPF_COEFF4_SHFT 0 +#define VIP_HPF_COEFF5_MASK 0xff +#define VIP_HPF_COEFF5_SHFT 8 +#define VIP_HPF_NORM_SHFT_MASK 0x07 +#define VIP_HPF_NORM_SHFT_SHFT 16 +#define VIP_NL_LIMIT_MASK 0x1ff +#define VIP_NL_LIMIT_SHFT 20 + +#define VIP_SC_MP_SC21 0x0354 +#define VIP_NL_LO_THR_MASK 0x01ff +#define VIP_NL_LO_THR_SHFT 0 +#define VIP_NL_LO_SLOPE_MASK 0xff +#define VIP_NL_LO_SLOPE_SHFT 16 + +#define VIP_SC_MP_SC22 0x0358 +#define VIP_NL_HI_THR_MASK 0x01ff +#define VIP_NL_HI_THR_SHFT 0 +#define VIP_NL_HI_SLOPE_SH_MASK 0x07 +#define VIP_NL_HI_SLOPE_SH_SHFT 16 + +#define VIP_SC_MP_SC23 0x035c +#define VIP_GRADIENT_THR_MASK 0x07ff +#define VIP_GRADIENT_THR_SHFT 0 +#define VIP_GRADIENT_THR_RANGE_MASK 0x0f +#define VIP_GRADIENT_THR_RANGE_SHFT 12 +#define VIP_MIN_GY_THR_MASK 0xff +#define VIP_MIN_GY_THR_SHFT 16 +#define VIP_MIN_GY_THR_RANGE_MASK 0x0f +#define VIP_MIN_GY_THR_RANGE_SHFT 28 + +#define VIP_SC_MP_SC24 0x0360 +#define VIP_ORG_H_MASK 0x07ff +#define VIP_ORG_H_SHFT 0 +#define VIP_ORG_W_MASK 0x07ff +#define VIP_ORG_W_SHFT 16 + +#define VIP_SC_MP_SC25 0x0364 +#define VIP_OFF_H_MASK 0x07ff +#define VIP_OFF_H_SHFT 0 +#define VIP_OFF_W_MASK 0x07ff +#define VIP_OFF_W_SHFT 16 + +#define VIP_VPDMA_BASE 0xd000 + +#endif From 555e882051a3a7ecc2bcee2b2047822249dcd074 Mon Sep 17 00:00:00 2001 From: Vikram Sharma Date: Fri, 7 Nov 2025 21:55:20 +0530 Subject: [PATCH 121/307] dt-bindings: media: qcom,qcs8300-camss: Add missing power supplies Add missing vdda-phy-supply and vdda-pll-supply in the (monaco)qcs8300 camss binding. While enabling imx412 sensor for qcs8300 we see a need to add these supplies which were missing in initial submission. Fixes: 634a2958fae30 ("media: dt-bindings: Add qcom,qcs8300-camss compatible") Cc: stable@vger.kernel.org Co-developed-by: Nihal Kumar Gupta Signed-off-by: Nihal Kumar Gupta Signed-off-by: Vikram Sharma Reviewed-by: Krzysztof Kozlowski Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../bindings/media/qcom,qcs8300-camss.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/devicetree/bindings/media/qcom,qcs8300-camss.yaml b/Documentation/devicetree/bindings/media/qcom,qcs8300-camss.yaml index 80a4540a22dc..e5f170aa4d9e 100644 --- a/Documentation/devicetree/bindings/media/qcom,qcs8300-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,qcs8300-camss.yaml @@ -120,6 +120,14 @@ properties: items: - const: top + vdda-phy-supply: + description: + Phandle to a 0.88V regulator supply to CSI PHYs. + + vdda-pll-supply: + description: + Phandle to 1.2V regulator supply to CSI PHYs pll block. + ports: $ref: /schemas/graph.yaml#/properties/ports @@ -160,6 +168,8 @@ required: - power-domains - power-domain-names - ports + - vdda-phy-supply + - vdda-pll-supply additionalProperties: false @@ -328,6 +338,9 @@ examples: power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>; power-domain-names = "top"; + vdda-phy-supply = <&vreg_l4a_0p88>; + vdda-pll-supply = <&vreg_l1c_1p2>; + ports { #address-cells = <1>; #size-cells = <0>; From 94d14ac1f2d385e57b651b3bef1026a7b8f9cf38 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Mon, 22 Dec 2025 15:16:05 +0100 Subject: [PATCH 122/307] dt-bindings: media: Correct camss supply description Usually, the supply is around 1.2 V, not 1.8 V, and also correct wording. Signed-off-by: David Heidelberg Reviewed-by: Bryan O'Donoghue Reviewed-by: Vladimir Zapolskiy Reviewed-by: Krzysztof Kozlowski Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/qcom,sa8775p-camss.yaml | 4 ++-- .../devicetree/bindings/media/qcom,sc7280-camss.yaml | 4 ++-- .../devicetree/bindings/media/qcom,sc8280xp-camss.yaml | 4 ++-- .../devicetree/bindings/media/qcom,sdm670-camss.yaml | 4 ++-- .../devicetree/bindings/media/qcom,sdm845-camss.yaml | 4 ++-- .../devicetree/bindings/media/qcom,sm8250-camss.yaml | 4 ++-- .../devicetree/bindings/media/qcom,sm8550-camss.yaml | 4 ++-- .../devicetree/bindings/media/qcom,x1e80100-camss.yaml | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Documentation/devicetree/bindings/media/qcom,sa8775p-camss.yaml b/Documentation/devicetree/bindings/media/qcom,sa8775p-camss.yaml index 019caa2b09c3..48f280e99809 100644 --- a/Documentation/devicetree/bindings/media/qcom,sa8775p-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,sa8775p-camss.yaml @@ -126,11 +126,11 @@ properties: vdda-phy-supply: description: - Phandle to a regulator supply to PHY core block. + 0.88V supply to CSIPHY IP blocks. vdda-pll-supply: description: - Phandle to 1.8V regulator supply to PHY refclk pll block. + 1.2V supply to CSIPHY IP blocks. ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/media/qcom,sc7280-camss.yaml b/Documentation/devicetree/bindings/media/qcom,sc7280-camss.yaml index ee35e3bc97ff..b1c54c5b01b2 100644 --- a/Documentation/devicetree/bindings/media/qcom,sc7280-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,sc7280-camss.yaml @@ -125,11 +125,11 @@ properties: vdda-phy-supply: description: - Phandle to a regulator supply to PHY core block. + 0.88V supply to CSIPHY IP blocks. vdda-pll-supply: description: - Phandle to 1.8V regulator supply to PHY refclk pll block. + 1.2V supply to CSIPHY IP blocks. ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/media/qcom,sc8280xp-camss.yaml b/Documentation/devicetree/bindings/media/qcom,sc8280xp-camss.yaml index c99fe4106eee..354130aba9fc 100644 --- a/Documentation/devicetree/bindings/media/qcom,sc8280xp-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,sc8280xp-camss.yaml @@ -264,11 +264,11 @@ properties: vdda-phy-supply: description: - Phandle to a regulator supply to PHY core block. + 0.88V supply to CSIPHY IP blocks. vdda-pll-supply: description: - Phandle to 1.8V regulator supply to PHY refclk pll block. + 1.2V supply to CSIPHY IP blocks. required: - clock-names diff --git a/Documentation/devicetree/bindings/media/qcom,sdm670-camss.yaml b/Documentation/devicetree/bindings/media/qcom,sdm670-camss.yaml index 35c40fe22376..46cc7fff1599 100644 --- a/Documentation/devicetree/bindings/media/qcom,sdm670-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,sdm670-camss.yaml @@ -91,11 +91,11 @@ properties: vdda-phy-supply: description: - Phandle to a regulator supply to PHY core block. + 0.88V supply to CSIPHY IP blocks. vdda-pll-supply: description: - Phandle to 1.8V regulator supply to PHY refclk pll block. + 1.2V supply to CSIPHY IP blocks. ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/media/qcom,sdm845-camss.yaml b/Documentation/devicetree/bindings/media/qcom,sdm845-camss.yaml index 82bf4689d330..be09cf3a3b3b 100644 --- a/Documentation/devicetree/bindings/media/qcom,sdm845-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,sdm845-camss.yaml @@ -207,11 +207,11 @@ properties: vdda-phy-supply: description: - Phandle to a regulator supply to PHY core block. + 0.88V supply to CSIPHY IP blocks. vdda-pll-supply: description: - Phandle to 1.8V regulator supply to PHY refclk pll block. + 1.2V supply to CSIPHY IP blocks. required: - clock-names diff --git a/Documentation/devicetree/bindings/media/qcom,sm8250-camss.yaml b/Documentation/devicetree/bindings/media/qcom,sm8250-camss.yaml index ebf68ff4ab96..a509d4bbcb4a 100644 --- a/Documentation/devicetree/bindings/media/qcom,sm8250-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,sm8250-camss.yaml @@ -296,11 +296,11 @@ properties: vdda-phy-supply: description: - Phandle to a regulator supply to PHY core block. + 0.88V supply to CSIPHY IP blocks. vdda-pll-supply: description: - Phandle to 1.8V regulator supply to PHY refclk pll block. + 1.2V supply to CSIPHY IP blocks. required: - clock-names diff --git a/Documentation/devicetree/bindings/media/qcom,sm8550-camss.yaml b/Documentation/devicetree/bindings/media/qcom,sm8550-camss.yaml index cd34f14916b4..4b9ab1352e91 100644 --- a/Documentation/devicetree/bindings/media/qcom,sm8550-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,sm8550-camss.yaml @@ -134,11 +134,11 @@ properties: vdda-phy-supply: description: - Phandle to a regulator supply to PHY core block. + 0.88V supply to CSIPHY IP blocks. vdda-pll-supply: description: - Phandle to 1.2V regulator supply to PHY refclk pll block. + 1.2V supply to CSIPHY IP blocks. ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml index b87a13479a4b..2d1662ef522b 100644 --- a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml @@ -120,11 +120,11 @@ properties: vdd-csiphy-0p8-supply: description: - Phandle to a 0.8V regulator supply to a PHY. + 0.8V supply to a PHY. vdd-csiphy-1p2-supply: description: - Phandle to 1.2V regulator supply to a PHY. + 1.2V supply to a PHY. ports: $ref: /schemas/graph.yaml#/properties/ports From 26efe43f2ecd11de15c430a5e5069a6fc1372a5c Mon Sep 17 00:00:00 2001 From: Hangxiang Ma Date: Sun, 30 Nov 2025 22:25:36 -0800 Subject: [PATCH 123/307] media: camss: csiphy: Make CSIPHY status macro cross-platform The current value of '0xb0' that represents the offset to the status registers within the common registers of the CSIPHY has been changed on the newer SOCs and it requires generalizing the macro using a new variable 'common_status_offset'. This variable is initialized in the csiphy_init() function. Signed-off-by: Hangxiang Ma Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 19 +++++++++++++------ .../media/platform/qcom/camss/camss-csiphy.h | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index 619abbf60781..d70d4f611798 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -46,7 +46,8 @@ #define CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE BIT(7) #define CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_COMMON_PWRDN_B BIT(0) #define CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_SHOW_REV_ID BIT(1) -#define CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(offset, n) ((offset) + 0xb0 + 0x4 * (n)) +#define CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(offset, common_status_offset, n) \ + ((offset) + (common_status_offset) + 0x4 * (n)) #define CSIPHY_DEFAULT_PARAMS 0 #define CSIPHY_LANE_ENABLE 1 @@ -810,13 +811,17 @@ static void csiphy_hw_version_read(struct csiphy_device *csiphy, CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 6)); hw_version = readl_relaxed(csiphy->base + - CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->offset, 12)); + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->offset, + regs->common_status_offset, 12)); hw_version |= readl_relaxed(csiphy->base + - CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->offset, 13)) << 8; + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->offset, + regs->common_status_offset, 13)) << 8; hw_version |= readl_relaxed(csiphy->base + - CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->offset, 14)) << 16; + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->offset, + regs->common_status_offset, 14)) << 16; hw_version |= readl_relaxed(csiphy->base + - CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->offset, 15)) << 24; + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->offset, + regs->common_status_offset, 15)) << 24; dev_dbg(dev, "CSIPHY 3PH HW Version = 0x%08x\n", hw_version); } @@ -845,7 +850,8 @@ static irqreturn_t csiphy_isr(int irq, void *dev) for (i = 0; i < 11; i++) { int c = i + 22; u8 val = readl_relaxed(csiphy->base + - CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->offset, i)); + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(regs->offset, + regs->common_status_offset, i)); writel_relaxed(val, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, c)); @@ -1086,6 +1092,7 @@ static int csiphy_init(struct csiphy_device *csiphy) csiphy->regs = regs; regs->offset = 0x800; + regs->common_status_offset = 0xb0; switch (csiphy->camss->res->version) { case CAMSS_845: diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.h b/drivers/media/platform/qcom/camss/camss-csiphy.h index 895f80003c44..2d5054819df7 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.h +++ b/drivers/media/platform/qcom/camss/camss-csiphy.h @@ -90,6 +90,7 @@ struct csiphy_device_regs { const struct csiphy_lane_regs *lane_regs; int lane_array_size; u32 offset; + u32 common_status_offset; }; struct csiphy_device { From 17bc0e288224f793a5a97408b7ce0b80c178bfaa Mon Sep 17 00:00:00 2001 From: Wenmeng Liu Date: Fri, 14 Nov 2025 16:26:49 +0800 Subject: [PATCH 124/307] media: qcom: camss: Add support for regulator init_load_uA in CSIPHY Some Qualcomm regulators are configured with initial mode as HPM (High Power Mode), which may lead to higher power consumption. To reduce power usage, it's preferable to set the initial mode to LPM (Low Power Mode). To ensure the regulator can switch from LPM to HPM when needed, this patch adds current load configuration for CAMSS CSIPHY. This allows the regulator framework to scale the mode dynamically based on the load requirement. The current default value for current is uninitialized or random. To address this, initial current values are added for the following platforms: MSM8916, MSM8939, MSM8953, MSM8996, QCM2290, SDM670, SM8250, SC7280, SM8550, SM8650, QCS8300, SA8775P and X1E80100. For SDM660, SDM845, SC8280XP the value is set to 0, indicating that no default current value is configured, the other values are derived from the power grid. Signed-off-by: Wenmeng Liu Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../media/platform/qcom/camss/camss-csid.c | 18 +- .../media/platform/qcom/camss/camss-csiphy.c | 19 +- drivers/media/platform/qcom/camss/camss.c | 328 ++++++++++++++---- drivers/media/platform/qcom/camss/camss.h | 2 +- 4 files changed, 265 insertions(+), 102 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csid.c b/drivers/media/platform/qcom/camss/camss-csid.c index 5284b5857368..ed1820488c98 100644 --- a/drivers/media/platform/qcom/camss/camss-csid.c +++ b/drivers/media/platform/qcom/camss/camss-csid.c @@ -1187,24 +1187,12 @@ int msm_csid_subdev_init(struct camss *camss, struct csid_device *csid, /* Regulator */ for (i = 0; i < ARRAY_SIZE(res->regulators); i++) { - if (res->regulators[i]) + if (res->regulators[i].supply) csid->num_supplies++; } - if (csid->num_supplies) { - csid->supplies = devm_kmalloc_array(camss->dev, - csid->num_supplies, - sizeof(*csid->supplies), - GFP_KERNEL); - if (!csid->supplies) - return -ENOMEM; - } - - for (i = 0; i < csid->num_supplies; i++) - csid->supplies[i].supply = res->regulators[i]; - - ret = devm_regulator_bulk_get(camss->dev, csid->num_supplies, - csid->supplies); + ret = devm_regulator_bulk_get_const(camss->dev, csid->num_supplies, + res->regulators, &csid->supplies); if (ret) return ret; diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c index a734fb7dde0a..62623393f414 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy.c @@ -695,24 +695,13 @@ int msm_csiphy_subdev_init(struct camss *camss, /* CSIPHY supplies */ for (i = 0; i < ARRAY_SIZE(res->regulators); i++) { - if (res->regulators[i]) + if (res->regulators[i].supply) csiphy->num_supplies++; } - if (csiphy->num_supplies) { - csiphy->supplies = devm_kmalloc_array(camss->dev, - csiphy->num_supplies, - sizeof(*csiphy->supplies), - GFP_KERNEL); - if (!csiphy->supplies) - return -ENOMEM; - } - - for (i = 0; i < csiphy->num_supplies; i++) - csiphy->supplies[i].supply = res->regulators[i]; - - ret = devm_regulator_bulk_get(camss->dev, csiphy->num_supplies, - csiphy->supplies); + if (csiphy->num_supplies > 0) + ret = devm_regulator_bulk_get_const(camss->dev, csiphy->num_supplies, + res->regulators, &csiphy->supplies); return ret; } diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index fcc2b2c3cba0..6cfb71fcd861 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -73,7 +73,9 @@ static const struct camss_subdev_resources csiphy_res_8x16[] = { static const struct camss_subdev_resources csid_res_8x16[] = { /* CSID0 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 40000 } + }, .clock = { "top_ahb", "ispif_ahb", "csi0_ahb", "ahb", "csi0", "csi0_phy", "csi0_pix", "csi0_rdi" }, .clock_rate = { { 0 }, @@ -95,7 +97,9 @@ static const struct camss_subdev_resources csid_res_8x16[] = { /* CSID1 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 40000 } + }, .clock = { "top_ahb", "ispif_ahb", "csi1_ahb", "ahb", "csi1", "csi1_phy", "csi1_pix", "csi1_rdi" }, .clock_rate = { { 0 }, @@ -157,7 +161,9 @@ static const struct camss_subdev_resources vfe_res_8x16[] = { static const struct camss_subdev_resources csiphy_res_8x39[] = { /* CSIPHY0 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 40000 } + }, .clock = { "top_ahb", "ispif_ahb", "ahb", "csiphy0_timer" }, .clock_rate = { { 0 }, { 40000000, 80000000 }, @@ -174,7 +180,9 @@ static const struct camss_subdev_resources csiphy_res_8x39[] = { /* CSIPHY1 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 40000 } + }, .clock = { "top_ahb", "ispif_ahb", "ahb", "csiphy1_timer" }, .clock_rate = { { 0 }, { 40000000, 80000000 }, @@ -300,7 +308,9 @@ static const struct camss_subdev_resources vfe_res_8x39[] = { static const struct camss_subdev_resources csid_res_8x53[] = { /* CSID0 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 9900 } + }, .clock = { "top_ahb", "ispif_ahb", "csi0_ahb", "ahb", "csi0", "csi0_phy", "csi0_pix", "csi0_rdi" }, .clock_rate = { { 0 }, @@ -323,7 +333,9 @@ static const struct camss_subdev_resources csid_res_8x53[] = { /* CSID1 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 9900 } + }, .clock = { "top_ahb", "ispif_ahb", "csi1_ahb", "ahb", "csi1", "csi1_phy", "csi1_pix", "csi1_rdi" }, .clock_rate = { { 0 }, @@ -346,7 +358,9 @@ static const struct camss_subdev_resources csid_res_8x53[] = { /* CSID2 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 9900 } + }, .clock = { "top_ahb", "ispif_ahb", "csi2_ahb", "ahb", "csi2", "csi2_phy", "csi2_pix", "csi2_rdi" }, .clock_rate = { { 0 }, @@ -507,7 +521,9 @@ static const struct camss_subdev_resources csiphy_res_8x96[] = { static const struct camss_subdev_resources csid_res_8x96[] = { /* CSID0 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 80160 } + }, .clock = { "top_ahb", "ispif_ahb", "csi0_ahb", "ahb", "csi0", "csi0_phy", "csi0_pix", "csi0_rdi" }, .clock_rate = { { 0 }, @@ -529,7 +545,9 @@ static const struct camss_subdev_resources csid_res_8x96[] = { /* CSID1 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 80160 } + }, .clock = { "top_ahb", "ispif_ahb", "csi1_ahb", "ahb", "csi1", "csi1_phy", "csi1_pix", "csi1_rdi" }, .clock_rate = { { 0 }, @@ -551,7 +569,9 @@ static const struct camss_subdev_resources csid_res_8x96[] = { /* CSID2 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 80160 } + }, .clock = { "top_ahb", "ispif_ahb", "csi2_ahb", "ahb", "csi2", "csi2_phy", "csi2_pix", "csi2_rdi" }, .clock_rate = { { 0 }, @@ -573,7 +593,9 @@ static const struct camss_subdev_resources csid_res_8x96[] = { /* CSID3 */ { - .regulators = { "vdda" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 80160 } + }, .clock = { "top_ahb", "ispif_ahb", "csi3_ahb", "ahb", "csi3", "csi3_phy", "csi3_pix", "csi3_rdi" }, .clock_rate = { { 0 }, @@ -661,7 +683,10 @@ static const struct camss_subdev_resources vfe_res_8x96[] = { static const struct camss_subdev_resources csiphy_res_2290[] = { /* CSIPHY0 */ { - .regulators = { "vdd-csiphy-1p2", "vdd-csiphy-1p8" }, + .regulators = { + { .supply = "vdd-csiphy-1p2", .init_load_uA = 26700 }, + { .supply = "vdd-csiphy-1p8", .init_load_uA = 2600 } + }, .clock = { "top_ahb", "ahb", "csiphy0", "csiphy0_timer" }, .clock_rate = { { 0 }, { 0 }, @@ -678,7 +703,10 @@ static const struct camss_subdev_resources csiphy_res_2290[] = { /* CSIPHY1 */ { - .regulators = { "vdd-csiphy-1p2", "vdd-csiphy-1p8" }, + .regulators = { + { .supply = "vdd-csiphy-1p2", .init_load_uA = 26700 }, + { .supply = "vdd-csiphy-1p8", .init_load_uA = 2600 } + }, .clock = { "top_ahb", "ahb", "csiphy1", "csiphy1_timer" }, .clock_rate = { { 0 }, { 0 }, @@ -854,7 +882,10 @@ static const struct camss_subdev_resources csiphy_res_660[] = { static const struct camss_subdev_resources csid_res_660[] = { /* CSID0 */ { - .regulators = { "vdda", "vdd_sec" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 0 }, + { .supply = "vdd_sec", .init_load_uA = 0 } + }, .clock = { "top_ahb", "ispif_ahb", "csi0_ahb", "ahb", "csi0", "csi0_phy", "csi0_pix", "csi0_rdi", "cphy_csid0" }, @@ -879,7 +910,10 @@ static const struct camss_subdev_resources csid_res_660[] = { /* CSID1 */ { - .regulators = { "vdda", "vdd_sec" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 0 }, + { .supply = "vdd_sec", .init_load_uA = 0 } + }, .clock = { "top_ahb", "ispif_ahb", "csi1_ahb", "ahb", "csi1", "csi1_phy", "csi1_pix", "csi1_rdi", "cphy_csid1" }, @@ -904,7 +938,10 @@ static const struct camss_subdev_resources csid_res_660[] = { /* CSID2 */ { - .regulators = { "vdda", "vdd_sec" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 0 }, + { .supply = "vdd_sec", .init_load_uA = 0 } + }, .clock = { "top_ahb", "ispif_ahb", "csi2_ahb", "ahb", "csi2", "csi2_phy", "csi2_pix", "csi2_rdi", "cphy_csid2" }, @@ -929,7 +966,10 @@ static const struct camss_subdev_resources csid_res_660[] = { /* CSID3 */ { - .regulators = { "vdda", "vdd_sec" }, + .regulators = { + { .supply = "vdda", .init_load_uA = 0 }, + { .supply = "vdd_sec", .init_load_uA = 0 } + }, .clock = { "top_ahb", "ispif_ahb", "csi3_ahb", "ahb", "csi3", "csi3_phy", "csi3_pix", "csi3_rdi", "cphy_csid3" }, @@ -1026,7 +1066,10 @@ static const struct camss_subdev_resources vfe_res_660[] = { static const struct camss_subdev_resources csiphy_res_670[] = { /* CSIPHY0 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 42800 }, + { .supply = "vdda-pll", .init_load_uA = 13900 } + }, .clock = { "soc_ahb", "cpas_ahb", "csiphy0", "csiphy0_timer" }, .clock_rate = { { 0 }, @@ -1044,7 +1087,10 @@ static const struct camss_subdev_resources csiphy_res_670[] = { /* CSIPHY1 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 42800 }, + { .supply = "vdda-pll", .init_load_uA = 13900 } + }, .clock = { "soc_ahb", "cpas_ahb", "csiphy1", "csiphy1_timer" }, .clock_rate = { { 0 }, @@ -1062,7 +1108,10 @@ static const struct camss_subdev_resources csiphy_res_670[] = { /* CSIPHY2 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 42800 }, + { .supply = "vdda-pll", .init_load_uA = 13900 } + }, .clock = { "soc_ahb", "cpas_ahb", "csiphy2", "csiphy2_timer" }, .clock_rate = { { 0 }, @@ -1302,7 +1351,10 @@ static const struct camss_subdev_resources csiphy_res_845[] = { static const struct camss_subdev_resources csid_res_845[] = { /* CSID0 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "cpas_ahb", "cphy_rx_src", "slow_ahb_src", "soc_ahb", "vfe0", "vfe0_src", "vfe0_cphy_rx", "csi0", @@ -1327,7 +1379,10 @@ static const struct camss_subdev_resources csid_res_845[] = { /* CSID1 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "cpas_ahb", "cphy_rx_src", "slow_ahb_src", "soc_ahb", "vfe1", "vfe1_src", "vfe1_cphy_rx", "csi1", @@ -1352,7 +1407,10 @@ static const struct camss_subdev_resources csid_res_845[] = { /* CSID2 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "cpas_ahb", "cphy_rx_src", "slow_ahb_src", "soc_ahb", "vfe_lite", "vfe_lite_src", "vfe_lite_cphy_rx", "csi2", @@ -1464,7 +1522,10 @@ static const struct camss_subdev_resources vfe_res_845[] = { static const struct camss_subdev_resources csiphy_res_8250[] = { /* CSIPHY0 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 17500 }, + { .supply = "vdda-pll", .init_load_uA = 10000 } + }, .clock = { "csiphy0", "csiphy0_timer" }, .clock_rate = { { 400000000 }, { 300000000 } }, @@ -1478,7 +1539,10 @@ static const struct camss_subdev_resources csiphy_res_8250[] = { }, /* CSIPHY1 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 17500 }, + { .supply = "vdda-pll", .init_load_uA = 10000 } + }, .clock = { "csiphy1", "csiphy1_timer" }, .clock_rate = { { 400000000 }, { 300000000 } }, @@ -1492,7 +1556,10 @@ static const struct camss_subdev_resources csiphy_res_8250[] = { }, /* CSIPHY2 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 17500 }, + { .supply = "vdda-pll", .init_load_uA = 10000 } + }, .clock = { "csiphy2", "csiphy2_timer" }, .clock_rate = { { 400000000 }, { 300000000 } }, @@ -1506,7 +1573,10 @@ static const struct camss_subdev_resources csiphy_res_8250[] = { }, /* CSIPHY3 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 17500 }, + { .supply = "vdda-pll", .init_load_uA = 10000 } + }, .clock = { "csiphy3", "csiphy3_timer" }, .clock_rate = { { 400000000 }, { 300000000 } }, @@ -1520,7 +1590,10 @@ static const struct camss_subdev_resources csiphy_res_8250[] = { }, /* CSIPHY4 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 17500 }, + { .supply = "vdda-pll", .init_load_uA = 10000 } + }, .clock = { "csiphy4", "csiphy4_timer" }, .clock_rate = { { 400000000 }, { 300000000 } }, @@ -1534,7 +1607,10 @@ static const struct camss_subdev_resources csiphy_res_8250[] = { }, /* CSIPHY5 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 17500 }, + { .supply = "vdda-pll", .init_load_uA = 10000 } + }, .clock = { "csiphy5", "csiphy5_timer" }, .clock_rate = { { 400000000 }, { 300000000 } }, @@ -1748,7 +1824,10 @@ static const struct resources_icc icc_res_sm8250[] = { static const struct camss_subdev_resources csiphy_res_7280[] = { /* CSIPHY0 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 16100 }, + { .supply = "vdda-pll", .init_load_uA = 9000 } + }, .clock = { "csiphy0", "csiphy0_timer" }, .clock_rate = { { 300000000, 400000000 }, @@ -1763,7 +1842,10 @@ static const struct camss_subdev_resources csiphy_res_7280[] = { }, /* CSIPHY1 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 16100 }, + { .supply = "vdda-pll", .init_load_uA = 9000 } + }, .clock = { "csiphy1", "csiphy1_timer" }, .clock_rate = { { 300000000, 400000000 }, @@ -1778,7 +1860,10 @@ static const struct camss_subdev_resources csiphy_res_7280[] = { }, /* CSIPHY2 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 16100 }, + { .supply = "vdda-pll", .init_load_uA = 9000 } + }, .clock = { "csiphy2", "csiphy2_timer" }, .clock_rate = { { 300000000, 400000000 }, @@ -1793,7 +1878,10 @@ static const struct camss_subdev_resources csiphy_res_7280[] = { }, /* CSIPHY3 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 16100 }, + { .supply = "vdda-pll", .init_load_uA = 9000 } + }, .clock = { "csiphy3", "csiphy3_timer" }, .clock_rate = { { 300000000, 400000000 }, @@ -1808,7 +1896,10 @@ static const struct camss_subdev_resources csiphy_res_7280[] = { }, /* CSIPHY4 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 16100 }, + { .supply = "vdda-pll", .init_load_uA = 9000 } + }, .clock = { "csiphy4", "csiphy4_timer" }, .clock_rate = { { 300000000, 400000000 }, @@ -2121,7 +2212,10 @@ static const struct camss_subdev_resources csiphy_res_sc8280xp[] = { static const struct camss_subdev_resources csid_res_sc8280xp[] = { /* CSID0 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "vfe0_csid", "vfe0_cphy_rx", "vfe0", "vfe0_axi" }, .clock_rate = { { 400000000, 480000000, 600000000 }, { 0 }, @@ -2137,7 +2231,10 @@ static const struct camss_subdev_resources csid_res_sc8280xp[] = { }, /* CSID1 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "vfe1_csid", "vfe1_cphy_rx", "vfe1", "vfe1_axi" }, .clock_rate = { { 400000000, 480000000, 600000000 }, { 0 }, @@ -2153,7 +2250,10 @@ static const struct camss_subdev_resources csid_res_sc8280xp[] = { }, /* CSID2 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "vfe2_csid", "vfe2_cphy_rx", "vfe2", "vfe2_axi" }, .clock_rate = { { 400000000, 480000000, 600000000 }, { 0 }, @@ -2169,7 +2269,10 @@ static const struct camss_subdev_resources csid_res_sc8280xp[] = { }, /* CSID3 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "vfe3_csid", "vfe3_cphy_rx", "vfe3", "vfe3_axi" }, .clock_rate = { { 400000000, 480000000, 600000000 }, { 0 }, @@ -2185,7 +2288,10 @@ static const struct camss_subdev_resources csid_res_sc8280xp[] = { }, /* CSID_LITE0 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "vfe_lite0_csid", "vfe_lite0_cphy_rx", "vfe_lite0" }, .clock_rate = { { 400000000, 480000000, 600000000 }, { 0 }, @@ -2201,7 +2307,10 @@ static const struct camss_subdev_resources csid_res_sc8280xp[] = { }, /* CSID_LITE1 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "vfe_lite1_csid", "vfe_lite1_cphy_rx", "vfe_lite1" }, .clock_rate = { { 400000000, 480000000, 600000000 }, { 0 }, @@ -2217,7 +2326,10 @@ static const struct camss_subdev_resources csid_res_sc8280xp[] = { }, /* CSID_LITE2 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "vfe_lite2_csid", "vfe_lite2_cphy_rx", "vfe_lite2" }, .clock_rate = { { 400000000, 480000000, 600000000 }, { 0 }, @@ -2233,7 +2345,10 @@ static const struct camss_subdev_resources csid_res_sc8280xp[] = { }, /* CSID_LITE3 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 0 }, + { .supply = "vdda-pll", .init_load_uA = 0 } + }, .clock = { "vfe_lite3_csid", "vfe_lite3_cphy_rx", "vfe_lite3" }, .clock_rate = { { 400000000, 480000000, 600000000 }, { 0 }, @@ -2434,7 +2549,10 @@ static const struct resources_icc icc_res_sc8280xp[] = { static const struct camss_subdev_resources csiphy_res_8550[] = { /* CSIPHY0 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 32200 }, + { .supply = "vdda-pll", .init_load_uA = 18000 } + }, .clock = { "csiphy0", "csiphy0_timer" }, .clock_rate = { { 400000000, 480000000 }, { 400000000 } }, @@ -2448,7 +2566,10 @@ static const struct camss_subdev_resources csiphy_res_8550[] = { }, /* CSIPHY1 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 32200 }, + { .supply = "vdda-pll", .init_load_uA = 18000 } + }, .clock = { "csiphy1", "csiphy1_timer" }, .clock_rate = { { 400000000, 480000000 }, { 400000000 } }, @@ -2462,7 +2583,10 @@ static const struct camss_subdev_resources csiphy_res_8550[] = { }, /* CSIPHY2 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 32200 }, + { .supply = "vdda-pll", .init_load_uA = 18000 } + }, .clock = { "csiphy2", "csiphy2_timer" }, .clock_rate = { { 400000000, 480000000 }, { 400000000 } }, @@ -2476,7 +2600,10 @@ static const struct camss_subdev_resources csiphy_res_8550[] = { }, /* CSIPHY3 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 32200 }, + { .supply = "vdda-pll", .init_load_uA = 18000 } + }, .clock = { "csiphy3", "csiphy3_timer" }, .clock_rate = { { 400000000, 480000000 }, { 400000000 } }, @@ -2490,7 +2617,10 @@ static const struct camss_subdev_resources csiphy_res_8550[] = { }, /* CSIPHY4 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 37900 }, + { .supply = "vdda-pll", .init_load_uA = 18600 } + }, .clock = { "csiphy4", "csiphy4_timer" }, .clock_rate = { { 400000000, 480000000 }, { 400000000 } }, @@ -2504,7 +2634,10 @@ static const struct camss_subdev_resources csiphy_res_8550[] = { }, /* CSIPHY5 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 32200 }, + { .supply = "vdda-pll", .init_load_uA = 18000 } + }, .clock = { "csiphy5", "csiphy5_timer" }, .clock_rate = { { 400000000, 480000000 }, { 400000000 } }, @@ -2518,7 +2651,10 @@ static const struct camss_subdev_resources csiphy_res_8550[] = { }, /* CSIPHY6 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 37900 }, + { .supply = "vdda-pll", .init_load_uA = 18600 } + }, .clock = { "csiphy6", "csiphy6_timer" }, .clock_rate = { { 400000000, 480000000 }, { 400000000 } }, @@ -2532,7 +2668,10 @@ static const struct camss_subdev_resources csiphy_res_8550[] = { }, /* CSIPHY7 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 32200 }, + { .supply = "vdda-pll", .init_load_uA = 18000 } + }, .clock = { "csiphy7", "csiphy7_timer" }, .clock_rate = { { 400000000, 480000000 }, { 400000000 } }, @@ -2763,7 +2902,10 @@ static const struct resources_icc icc_res_sm8550[] = { static const struct camss_subdev_resources csiphy_res_sm8650[] = { /* CSIPHY0 */ { - .regulators = { "vdd-csiphy01-0p9", "vdd-csiphy01-1p2", }, + .regulators = { + { .supply = "vdd-csiphy01-0p9", .init_load_uA = 88000 }, + { .supply = "vdd-csiphy01-1p2", .init_load_uA = 17800 }, + }, .clock = { "csiphy0", "csiphy0_timer" }, .clock_rate = { { 400000000 }, { 400000000 } }, @@ -2777,7 +2919,10 @@ static const struct camss_subdev_resources csiphy_res_sm8650[] = { }, /* CSIPHY1 */ { - .regulators = { "vdd-csiphy01-0p9", "vdd-csiphy01-1p2", }, + .regulators = { + { .supply = "vdd-csiphy01-0p9", .init_load_uA = 88000 }, + { .supply = "vdd-csiphy01-1p2", .init_load_uA = 17800 }, + }, .clock = { "csiphy1", "csiphy1_timer" }, .clock_rate = { { 400000000 }, { 400000000 } }, @@ -2791,7 +2936,10 @@ static const struct camss_subdev_resources csiphy_res_sm8650[] = { }, /* CSIPHY2 */ { - .regulators = { "vdd-csiphy24-0p9", "vdd-csiphy24-1p2", }, + .regulators = { + { .supply = "vdd-csiphy24-0p9", .init_load_uA = 147000 }, + { .supply = "vdd-csiphy24-1p2", .init_load_uA = 24400 }, + }, .clock = { "csiphy2", "csiphy2_timer" }, .clock_rate = { { 400000000 }, { 400000000 } }, @@ -2805,7 +2953,10 @@ static const struct camss_subdev_resources csiphy_res_sm8650[] = { }, /* CSIPHY3 */ { - .regulators = { "vdd-csiphy35-0p9", "vdd-csiphy35-1p2", }, + .regulators = { + { .supply = "vdd-csiphy35-0p9", .init_load_uA = 88000 }, + { .supply = "vdd-csiphy35-1p2", .init_load_uA = 17800 }, + }, .clock = { "csiphy3", "csiphy3_timer" }, .clock_rate = { { 400000000 }, { 400000000 } }, @@ -2819,7 +2970,10 @@ static const struct camss_subdev_resources csiphy_res_sm8650[] = { }, /* CSIPHY4 */ { - .regulators = { "vdd-csiphy24-0p9", "vdd-csiphy24-1p2", }, + .regulators = { + { .supply = "vdd-csiphy24-0p9", .init_load_uA = 147000 }, + { .supply = "vdd-csiphy24-1p2", .init_load_uA = 24400 }, + }, .clock = { "csiphy4", "csiphy4_timer" }, .clock_rate = { { 400000000 }, { 400000000 } }, @@ -2833,7 +2987,10 @@ static const struct camss_subdev_resources csiphy_res_sm8650[] = { }, /* CSIPHY5 */ { - .regulators = { "vdd-csiphy35-0p9", "vdd-csiphy35-1p2", }, + .regulators = { + { .supply = "vdd-csiphy35-0p9", .init_load_uA = 88000 }, + { .supply = "vdd-csiphy35-1p2", .init_load_uA = 17800 }, + }, .clock = { "csiphy5", "csiphy5_timer" }, .clock_rate = { { 400000000 }, { 400000000 } }, @@ -3074,7 +3231,10 @@ static const struct resources_icc icc_res_sm8650[] = { static const struct camss_subdev_resources csiphy_res_8300[] = { /* CSIPHY0 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 15900 }, + { .supply = "vdda-pll", .init_load_uA = 8900 } + }, .clock = { "csiphy_rx", "csiphy0", "csiphy0_timer" }, .clock_rate = { @@ -3092,7 +3252,10 @@ static const struct camss_subdev_resources csiphy_res_8300[] = { }, /* CSIPHY1 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 15900 }, + { .supply = "vdda-pll", .init_load_uA = 8900 } + }, .clock = { "csiphy_rx", "csiphy1", "csiphy1_timer" }, .clock_rate = { @@ -3110,7 +3273,10 @@ static const struct camss_subdev_resources csiphy_res_8300[] = { }, /* CSIPHY2 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 15900 }, + { .supply = "vdda-pll", .init_load_uA = 8900 } + }, .clock = { "csiphy_rx", "csiphy2", "csiphy2_timer" }, .clock_rate = { @@ -3131,7 +3297,10 @@ static const struct camss_subdev_resources csiphy_res_8300[] = { static const struct camss_subdev_resources csiphy_res_8775p[] = { /* CSIPHY0 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 15900 }, + { .supply = "vdda-pll", .init_load_uA = 8900 } + }, .clock = { "csiphy_rx", "csiphy0", "csiphy0_timer"}, .clock_rate = { { 400000000 }, @@ -3148,7 +3317,10 @@ static const struct camss_subdev_resources csiphy_res_8775p[] = { }, /* CSIPHY1 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 15900 }, + { .supply = "vdda-pll", .init_load_uA = 8900 } + }, .clock = { "csiphy_rx", "csiphy1", "csiphy1_timer"}, .clock_rate = { { 400000000 }, @@ -3165,7 +3337,10 @@ static const struct camss_subdev_resources csiphy_res_8775p[] = { }, /* CSIPHY2 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 15900 }, + { .supply = "vdda-pll", .init_load_uA = 8900 } + }, .clock = { "csiphy_rx", "csiphy2", "csiphy2_timer"}, .clock_rate = { { 400000000 }, @@ -3182,7 +3357,10 @@ static const struct camss_subdev_resources csiphy_res_8775p[] = { }, /* CSIPHY3 */ { - .regulators = { "vdda-phy", "vdda-pll" }, + .regulators = { + { .supply = "vdda-phy", .init_load_uA = 15900 }, + { .supply = "vdda-pll", .init_load_uA = 8900 } + }, .clock = { "csiphy_rx", "csiphy3", "csiphy3_timer"}, .clock_rate = { { 400000000 }, @@ -3535,8 +3713,10 @@ static const struct resources_icc icc_res_sa8775p[] = { static const struct camss_subdev_resources csiphy_res_x1e80100[] = { /* CSIPHY0 */ { - .regulators = { "vdd-csiphy-0p8", - "vdd-csiphy-1p2" }, + .regulators = { + { .supply = "vdd-csiphy-0p8", .init_load_uA = 105000 }, + { .supply = "vdd-csiphy-1p2", .init_load_uA = 58900 } + }, .clock = { "csiphy0", "csiphy0_timer" }, .clock_rate = { { 300000000, 400000000, 480000000 }, { 266666667, 400000000 } }, @@ -3550,8 +3730,10 @@ static const struct camss_subdev_resources csiphy_res_x1e80100[] = { }, /* CSIPHY1 */ { - .regulators = { "vdd-csiphy-0p8", - "vdd-csiphy-1p2" }, + .regulators = { + { .supply = "vdd-csiphy-0p8", .init_load_uA = 105000 }, + { .supply = "vdd-csiphy-1p2", .init_load_uA = 58900 } + }, .clock = { "csiphy1", "csiphy1_timer" }, .clock_rate = { { 300000000, 400000000, 480000000 }, { 266666667, 400000000 } }, @@ -3565,8 +3747,10 @@ static const struct camss_subdev_resources csiphy_res_x1e80100[] = { }, /* CSIPHY2 */ { - .regulators = { "vdd-csiphy-0p8", - "vdd-csiphy-1p2" }, + .regulators = { + { .supply = "vdd-csiphy-0p8", .init_load_uA = 105000 }, + { .supply = "vdd-csiphy-1p2", .init_load_uA = 58900 } + }, .clock = { "csiphy2", "csiphy2_timer" }, .clock_rate = { { 300000000, 400000000, 480000000 }, { 266666667, 400000000 } }, @@ -3580,8 +3764,10 @@ static const struct camss_subdev_resources csiphy_res_x1e80100[] = { }, /* CSIPHY4 */ { - .regulators = { "vdd-csiphy-0p8", - "vdd-csiphy-1p2" }, + .regulators = { + { .supply = "vdd-csiphy-0p8", .init_load_uA = 105000 }, + { .supply = "vdd-csiphy-1p2", .init_load_uA = 58900 } + }, .clock = { "csiphy4", "csiphy4_timer" }, .clock_rate = { { 300000000, 400000000, 480000000 }, { 266666667, 400000000 } }, diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h index 9d9a62640e25..e34f06b4e153 100644 --- a/drivers/media/platform/qcom/camss/camss.h +++ b/drivers/media/platform/qcom/camss/camss.h @@ -44,7 +44,7 @@ #define CAMSS_INIT_BUF_COUNT 2 struct camss_subdev_resources { - char *regulators[CAMSS_RES_MAX]; + struct regulator_bulk_data regulators[CAMSS_RES_MAX]; char *clock[CAMSS_RES_MAX]; char *clock_for_reset[CAMSS_RES_MAX]; u32 clock_rate[CAMSS_RES_MAX][CAMSS_RES_MAX]; From a89e490ba3551823511588b7b3828d67f8b82954 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Mon, 20 Oct 2025 17:02:27 +0300 Subject: [PATCH 125/307] media: qcom: camss: Do not enable cpas fast ahb clock for SM8550 VFE lite The clock is needed to stream images over a full VFE IP on SM8550 CAMSS, and it should not be enabled, when an image stream is routed over any of two lite VFE IPs on the SoC. Signed-off-by: Vladimir Zapolskiy Acked-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 6cfb71fcd861..9122568f9b0a 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -2843,12 +2843,11 @@ static const struct camss_subdev_resources vfe_res_8550[] = { /* VFE3 lite */ { .regulators = {}, - .clock = { "gcc_axi_hf", "cpas_ahb", "cpas_fast_ahb_clk", "vfe_lite_ahb", + .clock = { "gcc_axi_hf", "cpas_ahb", "vfe_lite_ahb", "vfe_lite", "cpas_ife_lite", "camnoc_axi" }, .clock_rate = { { 0 }, { 80000000 }, { 300000000, 400000000 }, - { 300000000, 400000000 }, { 400000000, 480000000 }, { 300000000, 400000000 }, { 300000000, 400000000 } }, @@ -2865,12 +2864,11 @@ static const struct camss_subdev_resources vfe_res_8550[] = { /* VFE4 lite */ { .regulators = {}, - .clock = { "gcc_axi_hf", "cpas_ahb", "cpas_fast_ahb_clk", "vfe_lite_ahb", + .clock = { "gcc_axi_hf", "cpas_ahb", "vfe_lite_ahb", "vfe_lite", "cpas_ife_lite", "camnoc_axi" }, .clock_rate = { { 0 }, { 80000000 }, { 300000000, 400000000 }, - { 300000000, 400000000 }, { 400000000, 480000000 }, { 300000000, 400000000 }, { 300000000, 400000000 } }, From 0b61ce8dc4a71ea7baa19275c655677c612dde6d Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Thu, 11 Dec 2025 14:59:39 +0100 Subject: [PATCH 126/307] media: qcom: camss: csid-340: Fix unused variables The CSID driver has some unused variables and function parameters that are no longer needed (due to refactoring). Clean up those unused elements: - Remove the `vc` parameter from `__csid_configure_rx()`. - Drop the unused `lane_cnt` variable. - Adjust call to `__csid_configure_rx()` accordingly. Signed-off-by: Loic Poulain Reviewed-by: Bryan O'Donoghue Reviewed-by: Vladimir Zapolskiy Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss-csid-340.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csid-340.c b/drivers/media/platform/qcom/camss/camss-csid-340.c index 22a30510fb79..2b50f9b96a34 100644 --- a/drivers/media/platform/qcom/camss/camss-csid-340.c +++ b/drivers/media/platform/qcom/camss/camss-csid-340.c @@ -55,8 +55,7 @@ #define CSID_RDI_CTRL_HALT_AT_FRAME_BOUNDARY 0 #define CSID_RDI_CTRL_RESUME_AT_FRAME_BOUNDARY 1 -static void __csid_configure_rx(struct csid_device *csid, - struct csid_phy_config *phy, int vc) +static void __csid_configure_rx(struct csid_device *csid, struct csid_phy_config *phy) { u32 val; @@ -81,13 +80,9 @@ static void __csid_configure_rdi_stream(struct csid_device *csid, u8 enable, u8 const struct csid_format_info *format = csid_get_fmt_entry(csid->res->formats->formats, csid->res->formats->nformats, input_format->code); - u8 lane_cnt = csid->phy.lane_cnt; u8 dt_id; u32 val; - if (!lane_cnt) - lane_cnt = 4; - /* * DT_ID is a two bit bitfield that is concatenated with * the four least significant bits of the five bit VC @@ -120,10 +115,11 @@ static void csid_configure_stream(struct csid_device *csid, u8 enable) { int i; + __csid_configure_rx(csid, &csid->phy); + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++) { if (csid->phy.en_vc & BIT(i)) { __csid_configure_rdi_stream(csid, enable, i); - __csid_configure_rx(csid, &csid->phy, i); __csid_ctrl_rdi(csid, enable, i); } } From d965919af524e68cb2ab1a685872050ad2ee933d Mon Sep 17 00:00:00 2001 From: Alper Ak Date: Mon, 29 Dec 2025 10:52:17 +0300 Subject: [PATCH 127/307] media: qcom: camss: vfe: Fix out-of-bounds access in vfe_isr_reg_update() vfe_isr() iterates using MSM_VFE_IMAGE_MASTERS_NUM(7) as the loop bound and passes the index to vfe_isr_reg_update(). However, vfe->line[] array is defined with VFE_LINE_NUM_MAX(4): struct vfe_line line[VFE_LINE_NUM_MAX]; When index is 4, 5, 6, the access to vfe->line[line_id] exceeds the array bounds and resulting in out-of-bounds memory access. Fix this by using separate loops for output lines and write masters. Fixes: 4edc8eae715c ("media: camss: Add initial support for VFE hardware version Titan 480") Signed-off-by: Alper Ak Cc: stable@vger.kernel.org Reviewed-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss-vfe-480.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-vfe-480.c b/drivers/media/platform/qcom/camss/camss-vfe-480.c index 4feea590a47b..d73f733fde04 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe-480.c +++ b/drivers/media/platform/qcom/camss/camss-vfe-480.c @@ -202,11 +202,13 @@ static irqreturn_t vfe_isr(int irq, void *dev) writel_relaxed(status, vfe->base + VFE_BUS_IRQ_CLEAR(0)); writel_relaxed(1, vfe->base + VFE_BUS_IRQ_CLEAR_GLOBAL); - /* Loop through all WMs IRQs */ - for (i = 0; i < MSM_VFE_IMAGE_MASTERS_NUM; i++) { + for (i = 0; i < MAX_VFE_OUTPUT_LINES; i++) { if (status & BUS_IRQ_MASK_0_RDI_RUP(vfe, i)) vfe_isr_reg_update(vfe, i); + } + /* Loop through all WMs IRQs */ + for (i = 0; i < MSM_VFE_IMAGE_MASTERS_NUM; i++) { if (status & BUS_IRQ_MASK_0_COMP_DONE(vfe, RDI_COMP_GROUP(i))) vfe_buf_done(vfe, i); } From eccf5fa8e3fef5f986dc42e0d15a56b74f5ee128 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 20 Nov 2025 02:46:03 +0200 Subject: [PATCH 128/307] media: qcom: camss: change internals of endpoint parsing to fwnode handling Since a few called V4L2 functions operate with fwnode arguments the change from OF device nodes to fwnodes brings a simplification to the code. The camss_parse_endpoint_node() function is called once by camss_probe(), and there is no use of knowing a number of asynchronously registered remote devices, so it makes sense to remove the related computation from the function. Tested-by: Loic Poulain Signed-off-by: Vladimir Zapolskiy Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss.c | 49 +++++++++++------------ 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 9122568f9b0a..8e0809202362 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4206,16 +4206,16 @@ static const struct parent_dev_ops vfe_parent_dev_ops = { }; /* - * camss_of_parse_endpoint_node - Parse port endpoint node - * @dev: Device - * @node: Device node to be parsed + * camss_parse_endpoint_node - Parse port endpoint node + * @dev: CAMSS device + * @ep: Device endpoint to be parsed * @csd: Parsed data from port endpoint node * * Return 0 on success or a negative error code on failure */ -static int camss_of_parse_endpoint_node(struct device *dev, - struct device_node *node, - struct camss_async_subdev *csd) +static int camss_parse_endpoint_node(struct device *dev, + struct fwnode_handle *ep, + struct camss_async_subdev *csd) { struct csiphy_lanes_cfg *lncfg = &csd->interface.csi2.lane_cfg; struct v4l2_mbus_config_mipi_csi2 *mipi_csi2; @@ -4223,7 +4223,7 @@ static int camss_of_parse_endpoint_node(struct device *dev, unsigned int i; int ret; - ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep); + ret = v4l2_fwnode_endpoint_parse(ep, &vep); if (ret) return ret; @@ -4258,49 +4258,46 @@ static int camss_of_parse_endpoint_node(struct device *dev, } /* - * camss_of_parse_ports - Parse ports node - * @dev: Device - * @notifier: v4l2_device notifier data + * camss_parse_ports - Parse ports node + * @dev: CAMSS device * - * Return number of "port" nodes found in "ports" node + * Return 0 on success or a negative error code on failure */ -static int camss_of_parse_ports(struct camss *camss) +static int camss_parse_ports(struct camss *camss) { struct device *dev = camss->dev; - struct device_node *node = NULL; - struct device_node *remote = NULL; - int ret, num_subdevs = 0; + struct fwnode_handle *fwnode = dev_fwnode(dev), *ep; + int ret; - for_each_endpoint_of_node(dev->of_node, node) { + fwnode_graph_for_each_endpoint(fwnode, ep) { struct camss_async_subdev *csd; + struct fwnode_handle *remote; - remote = of_graph_get_remote_port_parent(node); + remote = fwnode_graph_get_remote_port_parent(ep); if (!remote) { dev_err(dev, "Cannot get remote parent\n"); ret = -EINVAL; goto err_cleanup; } - csd = v4l2_async_nf_add_fwnode(&camss->notifier, - of_fwnode_handle(remote), + csd = v4l2_async_nf_add_fwnode(&camss->notifier, remote, struct camss_async_subdev); - of_node_put(remote); + fwnode_handle_put(remote); if (IS_ERR(csd)) { ret = PTR_ERR(csd); goto err_cleanup; } - ret = camss_of_parse_endpoint_node(dev, node, csd); + ret = camss_parse_endpoint_node(dev, ep, csd); if (ret < 0) goto err_cleanup; - - num_subdevs++; } - return num_subdevs; + return 0; err_cleanup: - of_node_put(node); + fwnode_handle_put(ep); + return ret; } @@ -4857,7 +4854,7 @@ static int camss_probe(struct platform_device *pdev) pm_runtime_enable(dev); - ret = camss_of_parse_ports(camss); + ret = camss_parse_ports(camss); if (ret < 0) goto err_v4l2_device_unregister; From 77a2ff40c51d170402f76e4e76640bfd4139a901 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 20 Nov 2025 02:46:04 +0200 Subject: [PATCH 129/307] media: qcom: camss: use a handy v4l2_async_nf_add_fwnode_remote() function Another code simplification makes parsing of remote endpoints easy. Tested-by: Loic Poulain Signed-off-by: Vladimir Zapolskiy Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 8e0809202362..7f44b60bcd72 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4271,18 +4271,9 @@ static int camss_parse_ports(struct camss *camss) fwnode_graph_for_each_endpoint(fwnode, ep) { struct camss_async_subdev *csd; - struct fwnode_handle *remote; - remote = fwnode_graph_get_remote_port_parent(ep); - if (!remote) { - dev_err(dev, "Cannot get remote parent\n"); - ret = -EINVAL; - goto err_cleanup; - } - - csd = v4l2_async_nf_add_fwnode(&camss->notifier, remote, - struct camss_async_subdev); - fwnode_handle_put(remote); + csd = v4l2_async_nf_add_fwnode_remote(&camss->notifier, ep, + typeof(*csd)); if (IS_ERR(csd)) { ret = PTR_ERR(csd); goto err_cleanup; From c88c5441f7ba030644b65dbd2dfbff811dbe19a3 Mon Sep 17 00:00:00 2001 From: Wenmeng Liu Date: Mon, 12 Jan 2026 16:04:52 +0800 Subject: [PATCH 130/307] dt-bindings: media: Add qcom,sm6150-camss Add bindings for the Camera Subsystem on the SM6150 SoC The SM6150 platform provides: - 2 x VFE (version 170), each with 3 RDI - 1 x VFE Lite (version 170), each with 4 RDI - 2 x CSID (version 170) - 1 x CSID Lite (version 170) - 3 x CSIPHY (version 2.0.0) - 1 x BPS (Bayer Processing Segment) - 1 x ICP (Imaging Control Processor) - 1 x IPE (Image Postprocessing Engine) - 1 x JPEG Encoder/Decoder - 1 x LRME (Low Resolution Motion Estimation) Reviewed-by: Vladimir Zapolskiy Signed-off-by: Wenmeng Liu Reviewed-by: Krzysztof Kozlowski Reviewed-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../bindings/media/qcom,sm6150-camss.yaml | 439 ++++++++++++++++++ 1 file changed, 439 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/qcom,sm6150-camss.yaml diff --git a/Documentation/devicetree/bindings/media/qcom,sm6150-camss.yaml b/Documentation/devicetree/bindings/media/qcom,sm6150-camss.yaml new file mode 100644 index 000000000000..ba7b0acb9128 --- /dev/null +++ b/Documentation/devicetree/bindings/media/qcom,sm6150-camss.yaml @@ -0,0 +1,439 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/qcom,sm6150-camss.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SM6150 Camera Subsystem (CAMSS) + +maintainers: + - Wenmeng Liu + +description: + This binding describes the camera subsystem hardware found on SM6150 + Qualcomm SoCs. It includes submodules such as CSIPHY (CSI Physical layer) + and CSID (CSI Decoder), which comply with the MIPI CSI2 protocol. + + The subsystem also integrates a set of real-time image processing engines + and their associated configuration modules, as well as non-real-time engines. + +properties: + compatible: + const: qcom,sm6150-camss + + reg: + items: + - description: Registers for CSID 0 + - description: Registers for CSID 1 + - description: Registers for CSID Lite + - description: Registers for CSIPHY 0 + - description: Registers for CSIPHY 1 + - description: Registers for CSIPHY 2 + - description: Registers for VFE 0 + - description: Registers for VFE 1 + - description: Registers for VFE Lite + - description: Registers for BPS (Bayer Processing Segment) + - description: Registers for CAMNOC + - description: Registers for CPAS CDM + - description: Registers for CPAS TOP + - description: Registers for ICP (Imaging Control Processor) CSR (Control and Status Registers) + - description: Registers for ICP QGIC (Qualcomm Generic Interrupt Controller) + - description: Registers for ICP SIERRA ((A5 subsystem communication)) + - description: Registers for IPE (Image Postprocessing Engine) 0 + - description: Registers for JPEG DMA + - description: Registers for JPEG ENC + - description: Registers for LRME (Low Resolution Motion Estimation) + + reg-names: + items: + - const: csid0 + - const: csid1 + - const: csid_lite + - const: csiphy0 + - const: csiphy1 + - const: csiphy2 + - const: vfe0 + - const: vfe1 + - const: vfe_lite + - const: bps + - const: camnoc + - const: cpas_cdm + - const: cpas_top + - const: icp_csr + - const: icp_qgic + - const: icp_sierra + - const: ipe0 + - const: jpeg_dma + - const: jpeg_enc + - const: lrme + + clocks: + maxItems: 33 + + clock-names: + items: + - const: gcc_ahb + - const: gcc_axi_hf + - const: camnoc_axi + - const: cpas_ahb + - const: csiphy0 + - const: csiphy0_timer + - const: csiphy1 + - const: csiphy1_timer + - const: csiphy2 + - const: csiphy2_timer + - const: soc_ahb + - const: vfe0 + - const: vfe0_axi + - const: vfe0_cphy_rx + - const: vfe0_csid + - const: vfe1 + - const: vfe1_axi + - const: vfe1_cphy_rx + - const: vfe1_csid + - const: vfe_lite + - const: vfe_lite_cphy_rx + - const: vfe_lite_csid + - const: bps + - const: bps_ahb + - const: bps_axi + - const: bps_areg + - const: icp + - const: ipe0 + - const: ipe0_ahb + - const: ipe0_areg + - const: ipe0_axi + - const: jpeg + - const: lrme + + interrupts: + maxItems: 15 + + interrupt-names: + items: + - const: csid0 + - const: csid1 + - const: csid_lite + - const: csiphy0 + - const: csiphy1 + - const: csiphy2 + - const: vfe0 + - const: vfe1 + - const: vfe_lite + - const: camnoc + - const: cdm + - const: icp + - const: jpeg_dma + - const: jpeg_enc + - const: lrme + + interconnects: + maxItems: 4 + + interconnect-names: + items: + - const: ahb + - const: hf_0 + - const: hf_1 + - const: sf_mnoc + + iommus: + items: + - description: Camera IFE 0 non-protected stream + - description: Camera IFE 1 non-protected stream + - description: Camera IFE 3 non-protected stream + - description: Camera CDM non-protected stream + - description: Camera LRME read non-protected stream + - description: Camera IPE 0 read non-protected stream + - description: Camera BPS read non-protected stream + - description: Camera IPE 0 write non-protected stream + - description: Camera BPS write non-protected stream + - description: Camera LRME write non-protected stream + - description: Camera JPEG read non-protected stream + - description: Camera JPEG write non-protected stream + - description: Camera ICP stream + + power-domains: + items: + - description: + IFE0 GDSC - Image Front End, Global Distributed Switch Controller. + - description: + IFE1 GDSC - Image Front End, Global Distributed Switch Controller. + - description: + Titan GDSC - Titan ISP Block, Global Distributed Switch Controller. + - description: + Titan BPS - Bayer Processing Segment, Global Distributed Switch Controller. + - description: + IPE GDSC - Image Postprocessing Engine, Global Distributed Switch Controller. + + power-domain-names: + items: + - const: ife0 + - const: ife1 + - const: top + - const: bps + - const: ipe + + vdd-csiphy-1p2-supply: + description: + Phandle to a 1.2V regulator supply to CSI PHYs. + + vdd-csiphy-1p8-supply: + description: + Phandle to 1.8V regulator supply to CSI PHYs pll block. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + description: + CSI input ports. + + patternProperties: + "^port@[0-2]$": + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + + description: + Input port for receiving CSI data from a CSIPHY. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + required: + - data-lanes + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - interrupts + - interrupt-names + - interconnects + - interconnect-names + - iommus + - power-domains + - power-domain-names + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + camss: isp@acb3000 { + compatible = "qcom,sm6150-camss"; + + reg = <0x0 0x0acb3000 0x0 0x1000>, + <0x0 0x0acba000 0x0 0x1000>, + <0x0 0x0acc8000 0x0 0x1000>, + <0x0 0x0ac65000 0x0 0x1000>, + <0x0 0x0ac66000 0x0 0x1000>, + <0x0 0x0ac67000 0x0 0x1000>, + <0x0 0x0acaf000 0x0 0x4000>, + <0x0 0x0acb6000 0x0 0x4000>, + <0x0 0x0acc4000 0x0 0x4000>, + <0x0 0x0ac6f000 0x0 0x3000>, + <0x0 0x0ac42000 0x0 0x5000>, + <0x0 0x0ac48000 0x0 0x1000>, + <0x0 0x0ac40000 0x0 0x1000>, + <0x0 0x0ac18000 0x0 0x3000>, + <0x0 0x0ac00000 0x0 0x6000>, + <0x0 0x0ac10000 0x0 0x8000>, + <0x0 0x0ac87000 0x0 0x3000>, + <0x0 0x0ac52000 0x0 0x4000>, + <0x0 0x0ac4e000 0x0 0x4000>, + <0x0 0x0ac6b000 0x0 0x0a00>; + reg-names = "csid0", + "csid1", + "csid_lite", + "csiphy0", + "csiphy1", + "csiphy2", + "vfe0", + "vfe1", + "vfe_lite", + "bps", + "camnoc", + "cpas_cdm", + "cpas_top", + "icp_csr", + "icp_qgic", + "icp_sierra", + "ipe0", + "jpeg_dma", + "jpeg_enc", + "lrme"; + + clocks = <&gcc GCC_CAMERA_AHB_CLK>, + <&gcc GCC_CAMERA_HF_AXI_CLK>, + <&camcc CAM_CC_CAMNOC_AXI_CLK>, + <&camcc CAM_CC_CPAS_AHB_CLK>, + <&camcc CAM_CC_CSIPHY0_CLK>, + <&camcc CAM_CC_CSI0PHYTIMER_CLK>, + <&camcc CAM_CC_CSIPHY1_CLK>, + <&camcc CAM_CC_CSI1PHYTIMER_CLK>, + <&camcc CAM_CC_CSIPHY2_CLK>, + <&camcc CAM_CC_CSI2PHYTIMER_CLK>, + <&camcc CAM_CC_SOC_AHB_CLK>, + <&camcc CAM_CC_IFE_0_CLK>, + <&camcc CAM_CC_IFE_0_AXI_CLK>, + <&camcc CAM_CC_IFE_0_CPHY_RX_CLK>, + <&camcc CAM_CC_IFE_0_CSID_CLK>, + <&camcc CAM_CC_IFE_1_CLK>, + <&camcc CAM_CC_IFE_1_AXI_CLK>, + <&camcc CAM_CC_IFE_1_CPHY_RX_CLK>, + <&camcc CAM_CC_IFE_1_CSID_CLK>, + <&camcc CAM_CC_IFE_LITE_CLK>, + <&camcc CAM_CC_IFE_LITE_CPHY_RX_CLK>, + <&camcc CAM_CC_IFE_LITE_CSID_CLK>, + <&camcc CAM_CC_BPS_CLK>, + <&camcc CAM_CC_BPS_AHB_CLK>, + <&camcc CAM_CC_BPS_AXI_CLK>, + <&camcc CAM_CC_BPS_AREG_CLK>, + <&camcc CAM_CC_ICP_CLK>, + <&camcc CAM_CC_IPE_0_CLK>, + <&camcc CAM_CC_IPE_0_AHB_CLK>, + <&camcc CAM_CC_IPE_0_AREG_CLK>, + <&camcc CAM_CC_IPE_0_AXI_CLK>, + <&camcc CAM_CC_JPEG_CLK>, + <&camcc CAM_CC_LRME_CLK>; + + clock-names = "gcc_ahb", + "gcc_axi_hf", + "camnoc_axi", + "cpas_ahb", + "csiphy0", + "csiphy0_timer", + "csiphy1", + "csiphy1_timer", + "csiphy2", + "csiphy2_timer", + "soc_ahb", + "vfe0", + "vfe0_axi", + "vfe0_cphy_rx", + "vfe0_csid", + "vfe1", + "vfe1_axi", + "vfe1_cphy_rx", + "vfe1_csid", + "vfe_lite", + "vfe_lite_cphy_rx", + "vfe_lite_csid", + "bps", + "bps_ahb", + "bps_axi", + "bps_areg", + "icp", + "ipe0", + "ipe0_ahb", + "ipe0_areg", + "ipe0_axi", + "jpeg", + "lrme"; + + interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY + &config_noc SLAVE_CAMERA_CFG QCOM_ICC_TAG_ACTIVE_ONLY>, + <&mmss_noc MASTER_CAMNOC_HF0 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&mmss_noc MASTER_CAMNOC_HF1 QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>, + <&mmss_noc MASTER_CAMNOC_SF QCOM_ICC_TAG_ALWAYS + &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>; + interconnect-names = "ahb", + "hf_0", + "hf_1", + "sf_mnoc"; + + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = "csid0", + "csid1", + "csid_lite", + "csiphy0", + "csiphy1", + "csiphy2", + "vfe0", + "vfe1", + "vfe_lite", + "camnoc", + "cdm", + "icp", + "jpeg_dma", + "jpeg_enc", + "lrme"; + + iommus = <&apps_smmu 0x0820 0x40>, + <&apps_smmu 0x0840 0x00>, + <&apps_smmu 0x0860 0x40>, + <&apps_smmu 0x0c00 0x00>, + <&apps_smmu 0x0cc0 0x00>, + <&apps_smmu 0x0c80 0x00>, + <&apps_smmu 0x0ca0 0x00>, + <&apps_smmu 0x0d00 0x00>, + <&apps_smmu 0x0d20 0x00>, + <&apps_smmu 0x0d40 0x00>, + <&apps_smmu 0x0d80 0x20>, + <&apps_smmu 0x0da0 0x20>, + <&apps_smmu 0x0de2 0x00>; + + power-domains = <&camcc IFE_0_GDSC>, + <&camcc IFE_1_GDSC>, + <&camcc TITAN_TOP_GDSC>, + <&camcc BPS_GDSC>, + <&camcc IPE_0_GDSC>; + power-domain-names = "ife0", + "ife1", + "top", + "bps", + "ipe"; + + vdd-csiphy-1p2-supply = <&vreg_l11a_1p2>; + vdd-csiphy-1p8-supply = <&vreg_l12a_1p8>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + csiphy_ep0: endpoint { + data-lanes = <0 1>; + remote-endpoint = <&sensor_ep>; + }; + }; + }; + }; + }; From 2759816f1682ca6a269064b7b9d50a65bc3afd5d Mon Sep 17 00:00:00 2001 From: Wenmeng Liu Date: Mon, 12 Jan 2026 16:04:53 +0800 Subject: [PATCH 131/307] media: qcom: camss: add support for SM6150 camss The camera subsystem for SM6150 which is based on Spectra 230. For SM6150: - VFE and CSID version: 170 (vfe170, csid170) - CSIPHY version: csiphy-v2.0.1 (14nm) Reviewed-by: Vladimir Zapolskiy Signed-off-by: Wenmeng Liu Reviewed-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../qcom/camss/camss-csiphy-3ph-1-0.c | 2 + drivers/media/platform/qcom/camss/camss-vfe.c | 2 + drivers/media/platform/qcom/camss/camss.c | 198 ++++++++++++++++++ drivers/media/platform/qcom/camss/camss.h | 1 + 4 files changed, 203 insertions(+) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index d70d4f611798..415483274552 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -1010,6 +1010,7 @@ static bool csiphy_is_gen2(u32 version) switch (version) { case CAMSS_2290: + case CAMSS_6150: case CAMSS_7280: case CAMSS_8250: case CAMSS_8280XP: @@ -1100,6 +1101,7 @@ static int csiphy_init(struct csiphy_device *csiphy) regs->lane_array_size = ARRAY_SIZE(lane_regs_sdm845); break; case CAMSS_2290: + case CAMSS_6150: regs->lane_regs = &lane_regs_qcm2290[0]; regs->lane_array_size = ARRAY_SIZE(lane_regs_qcm2290); break; diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c index 9c7ad8aa4058..5baf0e3d4bc4 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe.c +++ b/drivers/media/platform/qcom/camss/camss-vfe.c @@ -342,6 +342,7 @@ static u32 vfe_src_pad_code(struct vfe_line *line, u32 sink_code, break; case CAMSS_660: case CAMSS_2290: + case CAMSS_6150: case CAMSS_7280: case CAMSS_8x96: case CAMSS_8250: @@ -2001,6 +2002,7 @@ static int vfe_bpl_align(struct vfe_device *vfe) int ret = 8; switch (vfe->camss->res->version) { + case CAMSS_6150: case CAMSS_7280: case CAMSS_8250: case CAMSS_8280XP: diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 7f44b60bcd72..00b87fd9afbd 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -1519,6 +1519,190 @@ static const struct camss_subdev_resources vfe_res_845[] = { } }; +static const struct camss_subdev_resources csiphy_res_sm6150[] = { + /* CSIPHY0 */ + { + .regulators = { + { .supply = "vdd-csiphy-1p2", .init_load_uA = 35000 }, + { .supply = "vdd-csiphy-1p8", .init_load_uA = 5000 } + }, + .clock = { "csiphy0", "csiphy0_timer" }, + .clock_rate = { { 269333333, 384000000 }, + { 269333333 } }, + .reg = { "csiphy0" }, + .interrupt = { "csiphy0" }, + .csiphy = { + .id = 0, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY1 */ + { + .regulators = { + { .supply = "vdd-csiphy-1p2", .init_load_uA = 35000 }, + { .supply = "vdd-csiphy-1p8", .init_load_uA = 5000 } + }, + .clock = { "csiphy1", "csiphy1_timer" }, + .clock_rate = { { 269333333, 384000000 }, + { 269333333 } }, + .reg = { "csiphy1" }, + .interrupt = { "csiphy1" }, + .csiphy = { + .id = 1, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, + /* CSIPHY2 */ + { + .regulators = { + { .supply = "vdd-csiphy-1p2", .init_load_uA = 35000 }, + { .supply = "vdd-csiphy-1p8", .init_load_uA = 5000 } + }, + .clock = { "csiphy2", "csiphy2_timer" }, + .clock_rate = { { 269333333, 384000000 }, + { 269333333 } }, + .reg = { "csiphy2" }, + .interrupt = { "csiphy2" }, + .csiphy = { + .id = 2, + .hw_ops = &csiphy_ops_3ph_1_0, + .formats = &csiphy_formats_sdm845 + } + }, +}; + +static const struct camss_subdev_resources csid_res_sm6150[] = { + /* CSID0 */ + { + .regulators = {}, + .clock = { "vfe0_cphy_rx", "vfe0_csid" }, + .clock_rate = { { 269333333, 384000000 }, + { 320000000, 540000000 } }, + .reg = { "csid0" }, + .interrupt = { "csid0" }, + .csid = { + .is_lite = false, + .hw_ops = &csid_ops_gen2, + .parent_dev_ops = &vfe_parent_dev_ops, + .formats = &csid_formats_gen2 + } + }, + /* CSID1 */ + { + .regulators = {}, + .clock = { "vfe1_cphy_rx", "vfe1_csid" }, + .clock_rate = { { 269333333, 384000000 }, + { 320000000, 540000000 } }, + .reg = { "csid1" }, + .interrupt = { "csid1" }, + .csid = { + .is_lite = false, + .hw_ops = &csid_ops_gen2, + .parent_dev_ops = &vfe_parent_dev_ops, + .formats = &csid_formats_gen2 + } + }, + /* CSID2 */ + { + .regulators = {}, + .clock = { "vfe_lite_cphy_rx", "vfe_lite_csid" }, + .clock_rate = { { 269333333, 384000000 }, + { 320000000, 540000000 } }, + .reg = { "csid_lite" }, + .interrupt = { "csid_lite" }, + .csid = { + .is_lite = true, + .hw_ops = &csid_ops_gen2, + .parent_dev_ops = &vfe_parent_dev_ops, + .formats = &csid_formats_gen2 + } + }, +}; + +static const struct camss_subdev_resources vfe_res_sm6150[] = { + /* VFE0 */ + { + .regulators = {}, + .clock = { "gcc_axi_hf", "camnoc_axi", "cpas_ahb", "soc_ahb", + "vfe0", "vfe0_axi"}, + .clock_rate = { { 0 }, + { 0 }, + { 80000000 }, + { 37500000, 40000000 }, + { 360000000, 432000000, 540000000, 600000000 }, + { 265000000, 426000000 } }, + .reg = { "vfe0" }, + .interrupt = { "vfe0" }, + .vfe = { + .line_num = 3, + .is_lite = false, + .has_pd = true, + .pd_name = "ife0", + .hw_ops = &vfe_ops_170, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, + /* VFE1 */ + { + .regulators = {}, + .clock = { "gcc_axi_hf", "camnoc_axi", "cpas_ahb", "soc_ahb", + "vfe1", "vfe1_axi"}, + .clock_rate = { { 0 }, + { 0 }, + { 80000000 }, + { 37500000, 40000000 }, + { 360000000, 432000000, 540000000, 600000000 }, + { 265000000, 426000000 } }, + .reg = { "vfe1" }, + .interrupt = { "vfe1" }, + .vfe = { + .line_num = 3, + .is_lite = false, + .has_pd = true, + .pd_name = "ife1", + .hw_ops = &vfe_ops_170, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, + /* VFE2 */ + { + .regulators = {}, + .clock = { "gcc_axi_hf", "camnoc_axi", "cpas_ahb", "soc_ahb", + "vfe_lite" }, + .clock_rate = { { 0 }, + { 0 }, + { 80000000 }, + { 37500000, 40000000 }, + { 360000000, 432000000, 540000000, 600000000 } }, + .reg = { "vfe_lite" }, + .interrupt = { "vfe_lite" }, + .vfe = { + .line_num = 4, + .is_lite = true, + .hw_ops = &vfe_ops_170, + .formats_rdi = &vfe_formats_rdi_845, + .formats_pix = &vfe_formats_pix_845 + } + }, +}; + +static const struct resources_icc icc_res_sm6150[] = { + { + .name = "ahb", + .icc_bw_tbl.avg = 38400, + .icc_bw_tbl.peak = 76800, + }, + { + .name = "hf_0", + .icc_bw_tbl.avg = 2097152, + .icc_bw_tbl.peak = 2097152, + }, +}; + static const struct camss_subdev_resources csiphy_res_8250[] = { /* CSIPHY0 */ { @@ -5036,6 +5220,19 @@ static const struct camss_resources sdm845_resources = { .vfe_num = ARRAY_SIZE(vfe_res_845), }; +static const struct camss_resources sm6150_resources = { + .version = CAMSS_6150, + .pd_name = "top", + .csiphy_res = csiphy_res_sm6150, + .csid_res = csid_res_sm6150, + .vfe_res = vfe_res_sm6150, + .icc_res = icc_res_sm6150, + .icc_path_num = ARRAY_SIZE(icc_res_sm6150), + .csiphy_num = ARRAY_SIZE(csiphy_res_sm6150), + .csid_num = ARRAY_SIZE(csid_res_sm6150), + .vfe_num = ARRAY_SIZE(vfe_res_sm6150), +}; + static const struct camss_resources sm8250_resources = { .version = CAMSS_8250, .pd_name = "top", @@ -5131,6 +5328,7 @@ static const struct of_device_id camss_dt_match[] = { { .compatible = "qcom,sdm660-camss", .data = &sdm660_resources }, { .compatible = "qcom,sdm670-camss", .data = &sdm670_resources }, { .compatible = "qcom,sdm845-camss", .data = &sdm845_resources }, + { .compatible = "qcom,sm6150-camss", .data = &sm6150_resources }, { .compatible = "qcom,sm8250-camss", .data = &sm8250_resources }, { .compatible = "qcom,sm8550-camss", .data = &sm8550_resources }, { .compatible = "qcom,sm8650-camss", .data = &sm8650_resources }, diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h index e34f06b4e153..6d048414c919 100644 --- a/drivers/media/platform/qcom/camss/camss.h +++ b/drivers/media/platform/qcom/camss/camss.h @@ -80,6 +80,7 @@ enum pm_domain { enum camss_version { CAMSS_660, CAMSS_2290, + CAMSS_6150, CAMSS_7280, CAMSS_8x16, CAMSS_8x39, From e3d5436c7099881e3604826757c8d65acc2d94f3 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 11 Nov 2025 13:07:29 +0200 Subject: [PATCH 132/307] media: v4l2-ctrls: Set error v4l2_ctrl_new_fwnode_properties consistently The vast majority of the callers of v4l2_ctrl_new_fwnode_properties() do check the returned error code but the function does not set the control handler's error on failure. This will make error handling more complicated and prone for bugs. Always assign the control handler's error field on error in v4l2_ctrl_new_fwnode_properties(). Signed-off-by: Sakari Ailus Reviewed-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 3 ++- include/media/v4l2-ctrls.h | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 209bc05883bb..1d4b5859f0e2 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -2780,7 +2780,8 @@ int v4l2_ctrl_new_fwnode_properties(struct v4l2_ctrl_handler *hdl, orientation_ctrl = V4L2_CAMERA_ORIENTATION_EXTERNAL; break; default: - return -EINVAL; + hdl->error = -EINVAL; + return hdl->error; } if (!v4l2_ctrl_new_std_menu(hdl, ctrl_ops, V4L2_CID_CAMERA_ORIENTATION, diff --git a/include/media/v4l2-ctrls.h b/include/media/v4l2-ctrls.h index 31fc1bee3797..327976b14d50 100644 --- a/include/media/v4l2-ctrls.h +++ b/include/media/v4l2-ctrls.h @@ -1581,6 +1581,9 @@ int v4l2_ctrl_subdev_log_status(struct v4l2_subdev *sd); * not overwritten. Callers should register the controls they want to handle * themselves before calling this function. * + * This function will set the control handler's error field on failure, just as + * other functions adding controls to the handler. + * * Return: 0 on success, a negative error code on failure. */ int v4l2_ctrl_new_fwnode_properties(struct v4l2_ctrl_handler *hdl, From 37a5b80a94cc6ca3b6a5c2999a6ad008ca2e6277 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 12 Nov 2025 09:57:57 +0200 Subject: [PATCH 133/307] media: v4l2-fwnode: Allow passing NULL fwnode to endpoint parsers v4l2_fwnode_endpoint_parse() and v4l2_fwnode_endpoint_alloc_parse() take a fwnode as the first argument and leave it up to the caller to check a valid fwnode has been obtained through various means. Instead, add a check here so the callers won't need to do that anymore. Signed-off-by: Sakari Ailus Reviewed-by: Michael Riesch Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-fwnode.c | 3 +++ include/media/v4l2-fwnode.h | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c index cb153ce42c45..22ec702a4567 100644 --- a/drivers/media/v4l2-core/v4l2-fwnode.c +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -465,6 +465,9 @@ static int __v4l2_fwnode_endpoint_parse(struct fwnode_handle *fwnode, enum v4l2_mbus_type mbus_type; int rval; + if (!fwnode) + return -EINVAL; + pr_debug("===== begin parsing endpoint %pfw\n", fwnode); fwnode_property_read_u32(fwnode, "bus-type", &bus_type); diff --git a/include/media/v4l2-fwnode.h b/include/media/v4l2-fwnode.h index f7c57c776589..cd82e70ccbaa 100644 --- a/include/media/v4l2-fwnode.h +++ b/include/media/v4l2-fwnode.h @@ -182,7 +182,7 @@ enum v4l2_fwnode_bus_type { /** * v4l2_fwnode_endpoint_parse() - parse all fwnode node properties - * @fwnode: pointer to the endpoint's fwnode handle + * @fwnode: pointer to the endpoint's fwnode handle (may be NULL) * @vep: pointer to the V4L2 fwnode data structure * * This function parses the V4L2 fwnode endpoint specific parameters from the @@ -218,7 +218,7 @@ enum v4l2_fwnode_bus_type { * * Return: %0 on success or a negative error code on failure: * %-ENOMEM on memory allocation failure - * %-EINVAL on parsing failure + * %-EINVAL on parsing failure, including @fwnode == NULL * %-ENXIO on mismatching bus types */ int v4l2_fwnode_endpoint_parse(struct fwnode_handle *fwnode, @@ -236,7 +236,7 @@ void v4l2_fwnode_endpoint_free(struct v4l2_fwnode_endpoint *vep); /** * v4l2_fwnode_endpoint_alloc_parse() - parse all fwnode node properties - * @fwnode: pointer to the endpoint's fwnode handle + * @fwnode: pointer to the endpoint's fwnode handle (may be NULL) * @vep: pointer to the V4L2 fwnode data structure * * This function parses the V4L2 fwnode endpoint specific parameters from the @@ -276,7 +276,7 @@ void v4l2_fwnode_endpoint_free(struct v4l2_fwnode_endpoint *vep); * * Return: %0 on success or a negative error code on failure: * %-ENOMEM on memory allocation failure - * %-EINVAL on parsing failure + * %-EINVAL on parsing failure, including @fwnode == NULL * %-ENXIO on mismatching bus types */ int v4l2_fwnode_endpoint_alloc_parse(struct fwnode_handle *fwnode, From 679f0b7b6a409750a25754c8833e268e5fdde742 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 25 Nov 2025 00:24:48 +0200 Subject: [PATCH 134/307] media: ccs: Avoid possible division by zero Calculating maximum M for scaler configuration involves dividing by MIN_X_OUTPUT_SIZE limit register's value. Albeit the value is presumably non-zero, the driver was missing the check it in fact was. Fix this. Reported-by: Josh Poimboeuf Closes: https://lore.kernel.org/all/ahukd6b3wonye3zgtptvwzvrxldcruazs2exfvll6etjhmcxyj@vq3eh6pd375b/ Fixes: ccfc97bdb5ae ("[media] smiapp: Add driver") Cc: stable@vger.kernel.org # for 5.15 and later Signed-off-by: Sakari Ailus Reviewed-by: Nathan Chancellor Tested-by: Nathan Chancellor # build Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index f8523140784c..dadff8c50679 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -2346,7 +2346,7 @@ static void ccs_set_compose_scaler(struct v4l2_subdev *subdev, * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.height; max_m = crops[CCS_PAD_SINK]->width * CCS_LIM(sensor, SCALER_N_MIN) - / CCS_LIM(sensor, MIN_X_OUTPUT_SIZE); + / (CCS_LIM(sensor, MIN_X_OUTPUT_SIZE) ?: 1); a = clamp(a, CCS_LIM(sensor, SCALER_M_MIN), CCS_LIM(sensor, SCALER_M_MAX)); From 7345d6d356336c448d6b9230ed8704f39679fd12 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 21 Nov 2025 13:48:40 +0200 Subject: [PATCH 135/307] media: v4l2-async: Fix error handling on steps after finding a match Once an async connection is found to be matching with an fwnode, a sub-device may be registered (in case it wasn't already), its bound operation is called, ancillary links are created, the async connection is added to the sub-device's list of connections and removed from the global waiting connection list. Further on, the sub-device's possible own notifier is searched for possible additional matches. Fix these specific issues: - If v4l2_async_match_notify() failed before the sub-notifier handling, the async connection was unbound and its entry removed from the sub-device's async connection list. The latter part was also done in v4l2_async_match_notify(). - The async connection's sd field was only set after creating ancillary links in v4l2_async_match_notify(). It was however dereferenced in v4l2_async_unbind_subdev_one(), which was called on error path of v4l2_async_match_notify() failure. Signed-off-by: Sakari Ailus Tested-by: "Yew, Chang Ching" Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-async.c | 45 +++++++++++++++++++--------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index ee884a8221fb..1c08bba9ecb9 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -343,7 +343,6 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, struct v4l2_subdev *sd, struct v4l2_async_connection *asc) { - struct v4l2_async_notifier *subdev_notifier; bool registered = false; int ret; @@ -389,6 +388,25 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, dev_dbg(notifier_dev(notifier), "v4l2-async: %s bound (ret %d)\n", dev_name(sd->dev), ret); + return 0; + +err_call_unbind: + v4l2_async_nf_call_unbind(notifier, sd, asc); + list_del(&asc->asc_subdev_entry); + +err_unregister_subdev: + if (registered) + v4l2_device_unregister_subdev(sd); + + return ret; +} + +static int +v4l2_async_nf_try_subdev_notifier(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd) +{ + struct v4l2_async_notifier *subdev_notifier; + /* * See if the sub-device has a notifier. If not, return here. */ @@ -404,16 +422,6 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, subdev_notifier->parent = notifier; return v4l2_async_nf_try_all_subdevs(subdev_notifier); - -err_call_unbind: - v4l2_async_nf_call_unbind(notifier, sd, asc); - list_del(&asc->asc_subdev_entry); - -err_unregister_subdev: - if (registered) - v4l2_device_unregister_subdev(sd); - - return ret; } /* Test all async sub-devices in a notifier for a match. */ @@ -445,6 +453,10 @@ again: if (ret < 0) return ret; + ret = v4l2_async_nf_try_subdev_notifier(notifier, sd); + if (ret < 0) + return ret; + /* * v4l2_async_match_notify() may lead to registering a * new notifier and thus changing the async subdevs @@ -829,7 +841,11 @@ int __v4l2_async_register_subdev(struct v4l2_subdev *sd, struct module *module) ret = v4l2_async_match_notify(notifier, v4l2_dev, sd, asc); if (ret) - goto err_unbind; + goto err_unlock; + + ret = v4l2_async_nf_try_subdev_notifier(notifier, sd); + if (ret) + goto err_unbind_one; ret = v4l2_async_nf_try_complete(notifier); if (ret) @@ -853,9 +869,10 @@ err_unbind: if (subdev_notifier) v4l2_async_nf_unbind_all_subdevs(subdev_notifier); - if (asc) - v4l2_async_unbind_subdev_one(notifier, asc); +err_unbind_one: + v4l2_async_unbind_subdev_one(notifier, asc); +err_unlock: mutex_unlock(&list_lock); sd->owner = NULL; From c228fb1fc2559c83528cb6eb19ef4ad600d83126 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 6 Sep 2024 09:28:05 +0300 Subject: [PATCH 136/307] media: Documentation: Clean up figure titles Remove DocBook (?) conversion leftovers from figure titles, including obsolete numbering and highlighting. This aligns these titles with others. Signed-off-by: Sakari Ailus Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- Documentation/userspace-api/media/v4l/dev-raw-vbi.rst | 6 +++--- Documentation/userspace-api/media/v4l/dev-subdev.rst | 6 +++--- Documentation/userspace-api/media/v4l/subdev-formats.rst | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Documentation/userspace-api/media/v4l/dev-raw-vbi.rst b/Documentation/userspace-api/media/v4l/dev-raw-vbi.rst index 2bec20d87928..1f7bb8fd15e7 100644 --- a/Documentation/userspace-api/media/v4l/dev-raw-vbi.rst +++ b/Documentation/userspace-api/media/v4l/dev-raw-vbi.rst @@ -221,7 +221,7 @@ and always returns default parameters as :ref:`VIDIOC_G_FMT ` does :alt: vbi_hsync.svg :align: center - **Figure 4.1. Line synchronization** + Line synchronization .. _vbi-525: @@ -229,7 +229,7 @@ and always returns default parameters as :ref:`VIDIOC_G_FMT ` does :alt: vbi_525.svg :align: center - **Figure 4.2. ITU-R 525 line numbering (M/NTSC and M/PAL)** + ITU-R 525 line numbering (M/NTSC and M/PAL) .. _vbi-625: @@ -237,7 +237,7 @@ and always returns default parameters as :ref:`VIDIOC_G_FMT ` does :alt: vbi_625.svg :align: center - **Figure 4.3. ITU-R 625 line numbering** + ITU-R 625 line numbering Remember the VBI image format depends on the selected video standard, therefore the application must choose a new standard or query the diff --git a/Documentation/userspace-api/media/v4l/dev-subdev.rst b/Documentation/userspace-api/media/v4l/dev-subdev.rst index 2530170a56ae..c5554d172b14 100644 --- a/Documentation/userspace-api/media/v4l/dev-subdev.rst +++ b/Documentation/userspace-api/media/v4l/dev-subdev.rst @@ -460,7 +460,7 @@ selection will refer to the sink pad format dimensions instead. :alt: subdev-image-processing-crop.svg :align: center - **Figure 4.5. Image processing in subdevs: simple crop example** + Image processing in subdevs: simple crop example In the above example, the subdev supports cropping on its sink pad. To configure it, the user sets the media bus format on the subdev's sink @@ -477,7 +477,7 @@ pad. :alt: subdev-image-processing-scaling-multi-source.svg :align: center - **Figure 4.6. Image processing in subdevs: scaling with multiple sources** + Image processing in subdevs: scaling with multiple sources In this example, the subdev is capable of first cropping, then scaling and finally cropping for two source pads individually from the resulting @@ -493,7 +493,7 @@ an area at location specified by the source crop rectangle from it. :alt: subdev-image-processing-full.svg :align: center - **Figure 4.7. Image processing in subdevs: scaling and composition with multiple sinks and sources** + Image processing in subdevs: scaling and composition with multiple sinks and sources The subdev driver supports two sink pads and two source pads. The images from both of the sink pads are individually cropped, then scaled and diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst index cf970750dd4c..896177c5334f 100644 --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst @@ -2800,7 +2800,7 @@ be named ``MEDIA_BUS_FMT_SRGGB10_2X8_PADHI_LE``. :alt: bayer.svg :align: center - **Figure 4.8 Bayer Patterns** + Bayer Patterns The following table lists existing packed Bayer formats. The data organization is given as an example for the first pixel only. From 9369f001c8ab97e6ab5554d43c84dd75e713fd2c Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 15 Jul 2025 16:24:45 +0300 Subject: [PATCH 137/307] media: Documentation: Fix routing documentation flag references The routing flag indicating an active route is called V4L2_SUBDEV_ROUTE_FL_ACTIVE, not V4L2_SUBDEV_STREAM_FL_ACTIVE. Fix this. Signed-off-by: Sakari Ailus Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- Documentation/userspace-api/media/v4l/dev-subdev.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Documentation/userspace-api/media/v4l/dev-subdev.rst b/Documentation/userspace-api/media/v4l/dev-subdev.rst index c5554d172b14..142e2cd95062 100644 --- a/Documentation/userspace-api/media/v4l/dev-subdev.rst +++ b/Documentation/userspace-api/media/v4l/dev-subdev.rst @@ -578,15 +578,14 @@ Device types and routing setup Different kinds of sub-devices have differing behaviour for route activation, depending on the hardware. In all cases, however, only routes that have the -``V4L2_SUBDEV_STREAM_FL_ACTIVE`` flag set are active. +``V4L2_SUBDEV_ROUTE_FL_ACTIVE`` flag set are active. Devices generating the streams may allow enabling and disabling some of the routes or have a fixed routing configuration. If the routes can be disabled, not -declaring the routes (or declaring them without -``V4L2_SUBDEV_STREAM_FL_ACTIVE`` flag set) in ``VIDIOC_SUBDEV_S_ROUTING`` will -disable the routes. ``VIDIOC_SUBDEV_S_ROUTING`` will still return such routes -back to the user in the routes array, with the ``V4L2_SUBDEV_STREAM_FL_ACTIVE`` -flag unset. +declaring the routes (or declaring them without ``V4L2_SUBDEV_ROUTE_FL_ACTIVE`` +flag set) in ``VIDIOC_SUBDEV_S_ROUTING`` will disable the routes. +``VIDIOC_SUBDEV_S_ROUTING`` will still return such routes back to the user in +the routes array, with the ``V4L2_SUBDEV_ROUTE_FL_ACTIVE`` flag unset. Devices transporting the streams almost always have more configurability with respect to routing. Typically any route between the sub-device's sink and source From e87ebb4e4f71a3a51af67509280a0ba0c89083c4 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 25 Aug 2025 10:52:54 +0300 Subject: [PATCH 138/307] media: Documentation: Document -ENXIO for VIDIOC_SUBDEV_S_ROUTING Document that -ENXIO is returned when the user tries to set a routing configuration not supported by the hardware (or rather the driver). The documentation details the exact cases of this, besides -EINVAL that is already documented for VIDIOC_SUBDEV_S_ROUTING. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- .../userspace-api/media/v4l/vidioc-subdev-g-routing.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Documentation/userspace-api/media/v4l/vidioc-subdev-g-routing.rst b/Documentation/userspace-api/media/v4l/vidioc-subdev-g-routing.rst index 1cf795480602..6f66ca38589e 100644 --- a/Documentation/userspace-api/media/v4l/vidioc-subdev-g-routing.rst +++ b/Documentation/userspace-api/media/v4l/vidioc-subdev-g-routing.rst @@ -157,7 +157,14 @@ appropriately. The generic error codes are described at the EINVAL The sink or source pad identifiers reference a non-existing pad or reference pads of different types (ie. the sink_pad identifiers refers to a source - pad), or the ``which`` field has an unsupported value. + pad), the ``which`` field has an unsupported value, or, for + ``VIDIOC_SUBDEV_S_ROUTING``, the num_routes field set by the application is + larger than the len_routes field value. + +ENXIO + The application requested routes cannot be created or the state of + the specified routes cannot be modified. Only returned for + ``VIDIOC_SUBDEV_S_ROUTING``. E2BIG The application provided ``num_routes`` for ``VIDIOC_SUBDEV_S_ROUTING`` is From 04ec8572f8689f577781ba0229b5bb1671c520e0 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 25 Aug 2025 10:28:52 +0300 Subject: [PATCH 139/307] media: v4l2-subdev: Extend VIDIOC_SUBDEV_S_ROUTING error codes Return -ENXIO from routing configuration errors other than generic IOCTL argument validation. Signed-off-by: Sakari Ailus Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c | 2 +- drivers/media/platform/raspberrypi/rp1-cfe/csi2.c | 2 +- drivers/media/v4l2-core/v4l2-subdev.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c index ede6cc74c023..3c26cfef93d1 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c @@ -114,7 +114,7 @@ static int __mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd, "invalid route from memory input (%u) to pipe %u\n", route->sink_pad, route->source_pad - xbar->num_sinks); - return -EINVAL; + return -ENXIO; } } diff --git a/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c b/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c index 2c5b4d24b4e6..104908afbf41 100644 --- a/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c +++ b/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c @@ -492,7 +492,7 @@ static int csi2_set_routing(struct v4l2_subdev *sd, const struct v4l2_subdev_route *route = &routing->routes[i]; if (route->source_stream != 0) - return -EINVAL; + return -ENXIO; } ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 25e66bf18f5f..66842b975f91 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -2112,7 +2112,7 @@ int v4l2_subdev_routing_validate(struct v4l2_subdev *sd, { u32 *remote_pads = NULL; unsigned int i, j; - int ret = -EINVAL; + int ret = -ENXIO; if (disallow & (V4L2_SUBDEV_ROUTING_NO_STREAM_MIX | V4L2_SUBDEV_ROUTING_NO_MULTIPLEXING)) { From 02cab4a097b7b1094783a1634e2daaf0888d6e50 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 3 Oct 2023 12:48:22 +0300 Subject: [PATCH 140/307] media: ccs: No need to set streaming to false in power off Streaming will have been stopped before the sensor is powered off, and so sensor->streaming is also false already. Do not set it as part of the runtime suspend callback. Signed-off-by: Sakari Ailus Reviewed-by: Kieran Bingham Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index dadff8c50679..c7ed90d9473a 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -1704,7 +1704,6 @@ static int ccs_power_off(struct device *dev) usleep_range(5000, 5000); regulator_bulk_disable(ARRAY_SIZE(ccs_regulators), sensor->regulators); - sensor->streaming = false; return 0; } From de927934dd900207f0881e496bdf1d26fd371463 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 16 Apr 2024 11:09:57 +0300 Subject: [PATCH 141/307] media: ccs: Move ccs_pm_get_init function up Prepare for using ccs_pm_get_init from locations earlier than its the current place. Also add a missing newline while at it. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 73 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index c7ed90d9473a..6e102f48f135 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -1712,6 +1712,43 @@ static int ccs_power_off(struct device *dev) * Video stream management */ +static int ccs_pm_get_init(struct ccs_sensor *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + int rval; + + /* + * It can't use pm_runtime_resume_and_get() here, as the driver + * relies at the returned value to detect if the device was already + * active or not. + */ + rval = pm_runtime_get_sync(&client->dev); + if (rval < 0) + goto error; + + /* Device was already active, so don't set controls */ + if (rval == 1 && !sensor->handler_setup_needed) + return 0; + + sensor->handler_setup_needed = false; + + /* Restore V4L2 controls to the previously suspended device */ + rval = v4l2_ctrl_handler_setup(&sensor->pixel_array->ctrl_handler); + if (rval) + goto error; + + rval = v4l2_ctrl_handler_setup(&sensor->src->ctrl_handler); + if (rval) + goto error; + + /* Keep PM runtime usage_count incremented on success */ + return 0; + +error: + pm_runtime_put(&client->dev); + return rval; +} + static int ccs_start_streaming(struct ccs_sensor *sensor) { struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); @@ -1866,42 +1903,6 @@ out: * V4L2 subdev video operations */ -static int ccs_pm_get_init(struct ccs_sensor *sensor) -{ - struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); - int rval; - - /* - * It can't use pm_runtime_resume_and_get() here, as the driver - * relies at the returned value to detect if the device was already - * active or not. - */ - rval = pm_runtime_get_sync(&client->dev); - if (rval < 0) - goto error; - - /* Device was already active, so don't set controls */ - if (rval == 1 && !sensor->handler_setup_needed) - return 0; - - sensor->handler_setup_needed = false; - - /* Restore V4L2 controls to the previously suspended device */ - rval = v4l2_ctrl_handler_setup(&sensor->pixel_array->ctrl_handler); - if (rval) - goto error; - - rval = v4l2_ctrl_handler_setup(&sensor->src->ctrl_handler); - if (rval) - goto error; - - /* Keep PM runtime usage_count incremented on success */ - return 0; -error: - pm_runtime_put(&client->dev); - return rval; -} - static int ccs_set_stream(struct v4l2_subdev *subdev, int enable) { struct ccs_sensor *sensor = to_ccs_sensor(subdev); From 021baf99c259da2f0c705423dd5cb73082bbb052 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 16 Apr 2024 11:12:52 +0300 Subject: [PATCH 142/307] media: ccs: Rename out label of ccs_start_streaming In preparation for upcoming changes in the function, rename the out label as err_pm_put. The purpose of the label is changed to match its name in the next patch. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 6e102f48f135..97e627090073 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -1761,7 +1761,7 @@ static int ccs_start_streaming(struct ccs_sensor *sensor) (sensor->csi_format->width << 8) | sensor->csi_format->compressed); if (rval) - goto out; + goto err_pm_put; /* Binning configuration */ if (sensor->binning_horizontal == 1 && @@ -1774,38 +1774,38 @@ static int ccs_start_streaming(struct ccs_sensor *sensor) rval = ccs_write(sensor, BINNING_TYPE, binning_type); if (rval < 0) - goto out; + goto err_pm_put; binning_mode = 1; } rval = ccs_write(sensor, BINNING_MODE, binning_mode); if (rval < 0) - goto out; + goto err_pm_put; /* Set up PLL */ rval = ccs_pll_configure(sensor); if (rval) - goto out; + goto err_pm_put; /* Analog crop start coordinates */ rval = ccs_write(sensor, X_ADDR_START, sensor->pa_src.left); if (rval < 0) - goto out; + goto err_pm_put; rval = ccs_write(sensor, Y_ADDR_START, sensor->pa_src.top); if (rval < 0) - goto out; + goto err_pm_put; /* Analog crop end coordinates */ rval = ccs_write(sensor, X_ADDR_END, sensor->pa_src.left + sensor->pa_src.width - 1); if (rval < 0) - goto out; + goto err_pm_put; rval = ccs_write(sensor, Y_ADDR_END, sensor->pa_src.top + sensor->pa_src.height - 1); if (rval < 0) - goto out; + goto err_pm_put; /* * Output from pixel array, including blanking, is set using @@ -1818,22 +1818,22 @@ static int ccs_start_streaming(struct ccs_sensor *sensor) rval = ccs_write(sensor, DIGITAL_CROP_X_OFFSET, sensor->scaler_sink.left); if (rval < 0) - goto out; + goto err_pm_put; rval = ccs_write(sensor, DIGITAL_CROP_Y_OFFSET, sensor->scaler_sink.top); if (rval < 0) - goto out; + goto err_pm_put; rval = ccs_write(sensor, DIGITAL_CROP_IMAGE_WIDTH, sensor->scaler_sink.width); if (rval < 0) - goto out; + goto err_pm_put; rval = ccs_write(sensor, DIGITAL_CROP_IMAGE_HEIGHT, sensor->scaler_sink.height); if (rval < 0) - goto out; + goto err_pm_put; } /* Scaling */ @@ -1841,20 +1841,20 @@ static int ccs_start_streaming(struct ccs_sensor *sensor) != CCS_SCALING_CAPABILITY_NONE) { rval = ccs_write(sensor, SCALING_MODE, sensor->scaling_mode); if (rval < 0) - goto out; + goto err_pm_put; rval = ccs_write(sensor, SCALE_M, sensor->scale_m); if (rval < 0) - goto out; + goto err_pm_put; } /* Output size from sensor */ rval = ccs_write(sensor, X_OUTPUT_SIZE, sensor->src_src.width); if (rval < 0) - goto out; + goto err_pm_put; rval = ccs_write(sensor, Y_OUTPUT_SIZE, sensor->src_src.height); if (rval < 0) - goto out; + goto err_pm_put; if (CCS_LIM(sensor, FLASH_MODE_CAPABILITY) & (CCS_FLASH_MODE_CAPABILITY_SINGLE_STROBE | @@ -1863,18 +1863,18 @@ static int ccs_start_streaming(struct ccs_sensor *sensor) sensor->hwcfg.strobe_setup->trigger != 0) { rval = ccs_setup_flash_strobe(sensor); if (rval) - goto out; + goto err_pm_put; } rval = ccs_call_quirk(sensor, pre_streamon); if (rval) { dev_err(&client->dev, "pre_streamon quirks failed\n"); - goto out; + goto err_pm_put; } rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_STREAMING); -out: +err_pm_put: mutex_unlock(&sensor->mutex); return rval; From ee71368a5666cedb0c603d9eea3a6f0c079f88cc Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 5 Sep 2023 10:42:49 +0300 Subject: [PATCH 143/307] media: ccs: Move ccs_validate_csi_data_format up ccs_validate_csi_data_format() will soon be needed elsewhere, above its current location. Move it up. Signed-off-by: Sakari Ailus Reviewed-by: Julien Massot Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 97e627090073..c7b46d0fba49 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -1977,6 +1977,20 @@ static int ccs_post_streamoff(struct v4l2_subdev *subdev) return pm_runtime_put(&client->dev); } +static const struct ccs_csi_data_format +*ccs_validate_csi_data_format(struct ccs_sensor *sensor, u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) { + if (sensor->mbus_frame_fmts & (1 << i) && + ccs_csi_data_formats[i].code == code) + return &ccs_csi_data_formats[i]; + } + + return sensor->csi_format; +} + static int ccs_enum_mbus_code(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) @@ -2112,20 +2126,6 @@ static void ccs_propagate(struct v4l2_subdev *subdev, } } -static const struct ccs_csi_data_format -*ccs_validate_csi_data_format(struct ccs_sensor *sensor, u32 code) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) { - if (sensor->mbus_frame_fmts & (1 << i) && - ccs_csi_data_formats[i].code == code) - return &ccs_csi_data_formats[i]; - } - - return sensor->csi_format; -} - static int ccs_set_format_source(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) From eee13cbccacb6d0a3120c126b8544030905b069d Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Mon, 22 Dec 2025 13:45:25 +0530 Subject: [PATCH 144/307] media: i2c: ov5647: Initialize subdev before controls In ov5647_init_controls() we call v4l2_get_subdevdata, but it is initialized by v4l2_i2c_subdev_init() in the probe, which currently happens after init_controls(). This can result in a segfault if the error condition is hit, and we try to access i2c_client, so fix the order. Fixes: 4974c2f19fd8 ("media: ov5647: Support gain, exposure and AWB controls") Cc: stable@vger.kernel.org Suggested-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index e193fef4fced..f9fac858dc7b 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -1420,15 +1420,15 @@ static int ov5647_probe(struct i2c_client *client) sensor->mode = OV5647_DEFAULT_MODE; - ret = ov5647_init_controls(sensor); - if (ret) - goto mutex_destroy; - sd = &sensor->sd; v4l2_i2c_subdev_init(sd, client, &ov5647_subdev_ops); sd->internal_ops = &ov5647_subdev_internal_ops; sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + ret = ov5647_init_controls(sensor); + if (ret) + goto mutex_destroy; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad); From a4e62e597f21bb37db0ad13aca486094e9188167 Mon Sep 17 00:00:00 2001 From: David Plowman Date: Mon, 22 Dec 2025 13:45:26 +0530 Subject: [PATCH 145/307] media: i2c: ov5647: Correct pixel array offset The top offset in the pixel array is actually 6 (see page 3-1 of the OV5647 data sheet). Fixes: 14f70a3232aa ("media: ov5647: Add support for get_selection()") Cc: stable@vger.kernel.org Signed-off-by: David Plowman Reviewed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index f9fac858dc7b..d9e300406f58 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -69,7 +69,7 @@ #define OV5647_NATIVE_HEIGHT 1956U #define OV5647_PIXEL_ARRAY_LEFT 16U -#define OV5647_PIXEL_ARRAY_TOP 16U +#define OV5647_PIXEL_ARRAY_TOP 6U #define OV5647_PIXEL_ARRAY_WIDTH 2592U #define OV5647_PIXEL_ARRAY_HEIGHT 1944U From 1438248c5a82c86b4e1f0311c3bb827af747a8cf Mon Sep 17 00:00:00 2001 From: David Plowman Date: Mon, 22 Dec 2025 13:45:27 +0530 Subject: [PATCH 146/307] media: i2c: ov5647: Correct minimum VBLANK value Trial and error reveals that the minimum vblank value appears to be 24 (the OV5647 data sheet does not give any clues). This fixes streaming lock-ups in full resolution mode. Fixes: 2512c06441e3 ("media: ov5647: Support V4L2_CID_VBLANK control") Cc: stable@vger.kernel.org Signed-off-by: David Plowman Reviewed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index d9e300406f58..191954497e3d 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -73,7 +73,7 @@ #define OV5647_PIXEL_ARRAY_WIDTH 2592U #define OV5647_PIXEL_ARRAY_HEIGHT 1944U -#define OV5647_VBLANK_MIN 4 +#define OV5647_VBLANK_MIN 24 #define OV5647_VTS_MAX 32767 #define OV5647_EXPOSURE_MIN 4 From f007586b1e89dcea40168415d0422cb7a0fc31b1 Mon Sep 17 00:00:00 2001 From: David Plowman Date: Mon, 22 Dec 2025 13:45:28 +0530 Subject: [PATCH 147/307] media: i2c: ov5647: Sensor should report RAW color space As this sensor captures RAW bayer frames, the colorspace should be V4L2_COLORSPACE_RAW instead of SRGB. Fixes: a8df5af695a1 ("media: ov5647: Add SGGBR10_1X10 modes") Cc: stable@vger.kernel.org Signed-off-by: David Plowman Reviewed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 191954497e3d..c0f1121b025e 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -508,7 +508,7 @@ static const struct ov5647_mode ov5647_modes[] = { { .format = { .code = MEDIA_BUS_FMT_SBGGR10_1X10, - .colorspace = V4L2_COLORSPACE_SRGB, + .colorspace = V4L2_COLORSPACE_RAW, .field = V4L2_FIELD_NONE, .width = 2592, .height = 1944 @@ -529,7 +529,7 @@ static const struct ov5647_mode ov5647_modes[] = { { .format = { .code = MEDIA_BUS_FMT_SBGGR10_1X10, - .colorspace = V4L2_COLORSPACE_SRGB, + .colorspace = V4L2_COLORSPACE_RAW, .field = V4L2_FIELD_NONE, .width = 1920, .height = 1080 @@ -550,7 +550,7 @@ static const struct ov5647_mode ov5647_modes[] = { { .format = { .code = MEDIA_BUS_FMT_SBGGR10_1X10, - .colorspace = V4L2_COLORSPACE_SRGB, + .colorspace = V4L2_COLORSPACE_RAW, .field = V4L2_FIELD_NONE, .width = 1296, .height = 972 @@ -571,7 +571,7 @@ static const struct ov5647_mode ov5647_modes[] = { { .format = { .code = MEDIA_BUS_FMT_SBGGR10_1X10, - .colorspace = V4L2_COLORSPACE_SRGB, + .colorspace = V4L2_COLORSPACE_RAW, .field = V4L2_FIELD_NONE, .width = 640, .height = 480 From c063632b494b02e891442d10f17e37b7fcfab9b3 Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Mon, 22 Dec 2025 13:45:29 +0530 Subject: [PATCH 148/307] media: i2c: ov5647: Fix PIXEL_RATE value for VGA mode The pixel rate for VGA (640x480) mode is configured in the mode's table to be 58.333 MPix/s instead of 55 MPix/s, so fix it. Fixes: 911f4516ee2b ("media: ov5647: Support V4L2_CID_PIXEL_RATE") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/CAPY8ntA2TCf9FuB6Nk%2BOn%2By6N_PMuYPAOAr3Yx8YESwe4skWvw@mail.gmail.com/ Suggested-by: Dave Stevenson Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index c0f1121b025e..bf5b0bd8d6ac 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -582,7 +582,7 @@ static const struct ov5647_mode ov5647_modes[] = { .width = 2560, .height = 1920, }, - .pixel_rate = 55000000, + .pixel_rate = 58333000, .hts = 1852, .vts = 0x1f8, .reg_list = ov5647_640x480_10bpp, From d240430664adc0925042bd671a98668c5d399fe7 Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Mon, 22 Dec 2025 13:45:30 +0530 Subject: [PATCH 149/307] dt-bindings: media: ov5647: Add optional regulators The OV5647 camera sensor takes 3 voltage supplies. So define those in the bindings as optional regulators, to not break existing users. Acked-by: Krzysztof Kozlowski Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/i2c/ovti,ov5647.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml index a2abed06a099..9d3f7f1789cd 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml @@ -30,6 +30,15 @@ properties: description: Reference to the GPIO connected to the pwdn pin. Active high. maxItems: 1 + avdd-supply: + description: Analog voltage supply, 2.8 volts + + dvdd-supply: + description: Digital core voltage supply, 1.5 volts + + dovdd-supply: + description: Digital I/O voltage supply, 1.7 - 3.0 volts + port: $ref: /schemas/graph.yaml#/$defs/port-base additionalProperties: false From e7696f27f8df96ef6b4f72d288c0b2d63aef23c3 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Mon, 22 Dec 2025 13:45:31 +0530 Subject: [PATCH 150/307] media: i2c: ov5647: Add support for regulator control The driver supported using GPIOs to control the shutdown line, but no regulator control. Add regulator hooks. Signed-off-by: Dave Stevenson Reviewed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 43 +++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index bf5b0bd8d6ac..774d16335ebf 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +82,15 @@ #define OV5647_EXPOSURE_DEFAULT 1000 #define OV5647_EXPOSURE_MAX 65535 +/* regulator supplies */ +static const char * const ov5647_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +#define OV5647_NUM_SUPPLIES ARRAY_SIZE(ov5647_supply_names) + struct regval_list { u16 addr; u8 data; @@ -102,6 +112,7 @@ struct ov5647 { struct mutex lock; struct clk *xclk; struct gpio_desc *pwdn; + struct regulator_bulk_data supplies[OV5647_NUM_SUPPLIES]; bool clock_ncont; struct v4l2_ctrl_handler ctrls; const struct ov5647_mode *mode; @@ -777,11 +788,20 @@ static int ov5647_power_on(struct device *dev) dev_dbg(dev, "OV5647 power on\n"); - if (sensor->pwdn) { - gpiod_set_value_cansleep(sensor->pwdn, 0); - msleep(PWDN_ACTIVE_DELAY_MS); + ret = regulator_bulk_enable(OV5647_NUM_SUPPLIES, sensor->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; } + ret = gpiod_set_value_cansleep(sensor->pwdn, 0); + if (ret < 0) { + dev_err(dev, "pwdn gpio set value failed: %d\n", ret); + goto error_reg_disable; + } + + msleep(PWDN_ACTIVE_DELAY_MS); + ret = clk_prepare_enable(sensor->xclk); if (ret < 0) { dev_err(dev, "clk prepare enable failed\n"); @@ -808,6 +828,8 @@ error_clk_disable: clk_disable_unprepare(sensor->xclk); error_pwdn: gpiod_set_value_cansleep(sensor->pwdn, 1); +error_reg_disable: + regulator_bulk_disable(OV5647_NUM_SUPPLIES, sensor->supplies); return ret; } @@ -837,6 +859,7 @@ static int ov5647_power_off(struct device *dev) clk_disable_unprepare(sensor->xclk); gpiod_set_value_cansleep(sensor->pwdn, 1); + regulator_bulk_disable(OV5647_NUM_SUPPLIES, sensor->supplies); return 0; } @@ -1284,6 +1307,16 @@ static const struct v4l2_ctrl_ops ov5647_ctrl_ops = { .s_ctrl = ov5647_s_ctrl, }; +static int ov5647_configure_regulators(struct device *dev, + struct ov5647 *sensor) +{ + for (unsigned int i = 0; i < OV5647_NUM_SUPPLIES; i++) + sensor->supplies[i].supply = ov5647_supply_names[i]; + + return devm_regulator_bulk_get(dev, OV5647_NUM_SUPPLIES, + sensor->supplies); +} + static int ov5647_init_controls(struct ov5647 *sensor) { struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); @@ -1416,6 +1449,10 @@ static int ov5647_probe(struct i2c_client *client) return -EINVAL; } + ret = ov5647_configure_regulators(dev, sensor); + if (ret) + dev_err_probe(dev, ret, "Failed to get power regulators\n"); + mutex_init(&sensor->lock); sensor->mode = OV5647_DEFAULT_MODE; From 03f2af9ef431fff89ef2239bdd9ac30112a0fe9c Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Mon, 22 Dec 2025 13:45:32 +0530 Subject: [PATCH 151/307] dt-bindings: media: ov5647: Allow props from video-interface-devices Allow properties from video-interface-devices. The change is identical to commit 08fbd355be3d ("media: dt-bindings: sony,imx219: Allow props from video-interface-devices") Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml index 9d3f7f1789cd..2d7937a372a2 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml @@ -14,6 +14,9 @@ description: |- The OV5647 is a raw image sensor with MIPI CSI-2 and CCP2 image data interfaces and CCI (I2C compatible) control bus. +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + properties: compatible: const: ovti,ov5647 @@ -57,7 +60,7 @@ required: - clocks - port -additionalProperties: false +unevaluatedProperties: false examples: - | From 4885fb98350f76dd05c0f352925a53e2c20d390a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 22 Dec 2025 13:45:33 +0530 Subject: [PATCH 152/307] media: i2c: ov5647: Parse and register properties Parse device properties and register controls for them using the V4L2 fwnode properties helpers. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 774d16335ebf..6f07cac12c6c 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -1320,9 +1320,11 @@ static int ov5647_configure_regulators(struct device *dev, static int ov5647_init_controls(struct ov5647 *sensor) { struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + struct v4l2_fwnode_device_properties props; int hblank, exposure_max, exposure_def; + struct device *dev = &client->dev; - v4l2_ctrl_handler_init(&sensor->ctrls, 9); + v4l2_ctrl_handler_init(&sensor->ctrls, 11); v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, V4L2_CID_AUTOGAIN, 0, 1, 1, 0); @@ -1371,6 +1373,11 @@ static int ov5647_init_controls(struct ov5647 *sensor) ARRAY_SIZE(ov5647_test_pattern_menu) - 1, 0, 0, ov5647_test_pattern_menu); + v4l2_fwnode_device_parse(dev, &props); + + v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &ov5647_ctrl_ops, + &props); + if (sensor->ctrls.error) goto handler_free; From ebde37e5ef1a0b34c170118eaa22ff86c3ed5b40 Mon Sep 17 00:00:00 2001 From: David Plowman Date: Mon, 22 Dec 2025 13:45:34 +0530 Subject: [PATCH 153/307] media: i2c: ov5647: Support HFLIP and VFLIP Add missing controls for horizontal and vertical flipping. The sensor readout mirrors in the horizontal direction by default (if 0x3821[1] = 0) which can make things unnecessarily difficult for applications. The register table prior to this commit was setting that bit explicitly, to achieve a normally oriented image. Now that we have userspace controls for HFLIP, we keep the convention and report the non-mirrored image (with 0x3821[1] = 1) as horizontal_flip=0, and vice versa. Signed-off-by: David Plowman Reviewed-by: Jacopo Mondi Co-developed-by: Jai Luthra Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 81 ++++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 6f07cac12c6c..5bb80b943c8b 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -55,6 +55,8 @@ #define OV5647_REG_GAIN_LO 0x350b #define OV5647_REG_VTS_HI 0x380e #define OV5647_REG_VTS_LO 0x380f +#define OV5647_REG_TIMING_TC_V 0x3820 +#define OV5647_REG_TIMING_TC_H 0x3821 #define OV5647_REG_FRAME_OFF_NUMBER 0x4202 #define OV5647_REG_MIPI_CTRL00 0x4800 #define OV5647_REG_MIPI_CTRL14 0x4814 @@ -120,6 +122,8 @@ struct ov5647 { struct v4l2_ctrl *hblank; struct v4l2_ctrl *vblank; struct v4l2_ctrl *exposure; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; }; static inline struct ov5647 *to_sensor(struct v4l2_subdev *sd) @@ -161,7 +165,7 @@ static struct regval_list ov5647_2592x1944_10bpp[] = { {0x3036, 0x69}, {0x303c, 0x11}, {0x3106, 0xf5}, - {0x3821, 0x06}, + {0x3821, 0x02}, {0x3820, 0x00}, {0x3827, 0xec}, {0x370c, 0x03}, @@ -250,7 +254,7 @@ static struct regval_list ov5647_1080p30_10bpp[] = { {0x3036, 0x62}, {0x303c, 0x11}, {0x3106, 0xf5}, - {0x3821, 0x06}, + {0x3821, 0x02}, {0x3820, 0x00}, {0x3827, 0xec}, {0x370c, 0x03}, @@ -414,7 +418,7 @@ static struct regval_list ov5647_2x2binned_10bpp[] = { {0x4800, 0x24}, {0x3503, 0x03}, {0x3820, 0x41}, - {0x3821, 0x07}, + {0x3821, 0x03}, {0x350a, 0x00}, {0x350b, 0x10}, {0x3500, 0x00}, @@ -430,7 +434,7 @@ static struct regval_list ov5647_640x480_10bpp[] = { {0x3035, 0x11}, {0x3036, 0x46}, {0x303c, 0x11}, - {0x3821, 0x07}, + {0x3821, 0x03}, {0x3820, 0x41}, {0x370c, 0x03}, {0x3612, 0x59}, @@ -956,6 +960,26 @@ static const struct v4l2_subdev_video_ops ov5647_subdev_video_ops = { .s_stream = ov5647_s_stream, }; +/* + * This function returns the mbus code for the current settings of the HFLIP + * and VFLIP controls. + */ +static u32 ov5647_get_mbus_code(struct v4l2_subdev *sd) +{ + struct ov5647 *sensor = to_sensor(sd); + /* The control values are only 0 or 1. */ + int index = sensor->hflip->val | (sensor->vflip->val << 1); + + static const u32 codes[4] = { + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10 + }; + + return codes[index]; +} + static int ov5647_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) @@ -963,7 +987,7 @@ static int ov5647_enum_mbus_code(struct v4l2_subdev *sd, if (code->index > 0) return -EINVAL; - code->code = MEDIA_BUS_FMT_SBGGR10_1X10; + code->code = ov5647_get_mbus_code(sd); return 0; } @@ -974,7 +998,7 @@ static int ov5647_enum_frame_size(struct v4l2_subdev *sd, { const struct v4l2_mbus_framefmt *fmt; - if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10 || + if (fse->code != ov5647_get_mbus_code(sd) || fse->index >= ARRAY_SIZE(ov5647_modes)) return -EINVAL; @@ -1007,6 +1031,8 @@ static int ov5647_get_pad_fmt(struct v4l2_subdev *sd, } *fmt = *sensor_format; + /* The code we pass back must reflect the current h/vflips. */ + fmt->code = ov5647_get_mbus_code(sd); mutex_unlock(&sensor->lock); return 0; @@ -1054,6 +1080,8 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, exposure_def); } *fmt = mode->format; + /* The code we pass back must reflect the current h/vflips. */ + fmt->code = ov5647_get_mbus_code(sd); mutex_unlock(&sensor->lock); return 0; @@ -1229,6 +1257,31 @@ static int ov5647_s_exposure(struct v4l2_subdev *sd, u32 val) return ov5647_write(sd, OV5647_REG_EXP_LO, (val & 0xf) << 4); } +static int ov5647_s_flip(struct v4l2_subdev *sd, u16 reg, u32 ctrl_val) +{ + u8 reg_val; + int ret; + + /* + * TIMING TC REG20 (Vertical) and REG21 (Horizontal): + * - [2]: ISP mirror/flip + * - [1]: Sensor mirror/flip + * + * We only use sensor flip. + * + * Using ISP flip retains the BGGR pattern at the cost of changing the + * pixel array readout. This affects the selection rectangles in ways + * that are not very well documented, and would be tougher to deal with + * for applications compared to reading a different bayer pattern. + */ + ret = ov5647_read(sd, reg, ®_val); + if (ret) + return ret; + + return ov5647_write(sd, reg, ctrl_val ? reg_val | BIT(1) + : reg_val & ~BIT(1)); +} + static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) { struct ov5647 *sensor = container_of(ctrl->handler, @@ -1291,6 +1344,14 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) /* Read-only, but we adjust it based on mode. */ break; + case V4L2_CID_HFLIP: + /* There's an in-built hflip in the sensor, so account for that here. */ + ret = ov5647_s_flip(sd, OV5647_REG_TIMING_TC_H, !ctrl->val); + break; + case V4L2_CID_VFLIP: + ret = ov5647_s_flip(sd, OV5647_REG_TIMING_TC_V, ctrl->val); + break; + default: dev_info(&client->dev, "Control (id:0x%x, val:0x%x) not supported\n", @@ -1324,7 +1385,7 @@ static int ov5647_init_controls(struct ov5647 *sensor) int hblank, exposure_max, exposure_def; struct device *dev = &client->dev; - v4l2_ctrl_handler_init(&sensor->ctrls, 11); + v4l2_ctrl_handler_init(&sensor->ctrls, 13); v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, V4L2_CID_AUTOGAIN, 0, 1, 1, 0); @@ -1373,6 +1434,12 @@ static int ov5647_init_controls(struct ov5647 *sensor) ARRAY_SIZE(ov5647_test_pattern_menu) - 1, 0, 0, ov5647_test_pattern_menu); + sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_fwnode_device_parse(dev, &props); v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &ov5647_ctrl_ops, From b9ca9ef3f0471d8c7a4ef978304ebbbc6489e2e1 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Mon, 22 Dec 2025 13:45:35 +0530 Subject: [PATCH 154/307] media: i2c: ov5647: Use v4l2_async_register_subdev_sensor for lens binding v4l2_async_register_subdev doesn't bind in lens or flash drivers, but v4l2_async_register_subdev_sensor does. Switch to using v4l2_async_register_subdev_sensor. Signed-off-by: Dave Stevenson Reviewed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 5bb80b943c8b..d6accdd20ef5 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -1554,7 +1554,7 @@ static int ov5647_probe(struct i2c_client *client) if (ret < 0) goto power_off; - ret = v4l2_async_register_subdev(sd); + ret = v4l2_async_register_subdev_sensor(sd); if (ret < 0) goto power_off; From 87b2b6cef20885f45bbbc9412e30ca3f79f8245b Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Mon, 22 Dec 2025 13:45:36 +0530 Subject: [PATCH 155/307] media: i2c: ov5647: Add control of V4L2_CID_HBLANK The driver did expose V4L2_CID_HBLANK, but as a READ_ONLY control. The sensor only uses the HTS register to control the line length, so convert this control to read/write, with the appropriate ranges. Adopt the old fixed values as the minimum values permitted in each mode to avoid issues of it not streaming. This should allow exposure times up to ~3 seconds (up from ~1sec). Signed-off-by: Dave Stevenson Reviewed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index d6accdd20ef5..c89e13442e29 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -53,6 +53,8 @@ #define OV5647_REG_AEC_AGC 0x3503 #define OV5647_REG_GAIN_HI 0x350a #define OV5647_REG_GAIN_LO 0x350b +#define OV5647_REG_HTS_HI 0x380c +#define OV5647_REG_HTS_LO 0x380d #define OV5647_REG_VTS_HI 0x380e #define OV5647_REG_VTS_LO 0x380f #define OV5647_REG_TIMING_TC_V 0x3820 @@ -79,6 +81,8 @@ #define OV5647_VBLANK_MIN 24 #define OV5647_VTS_MAX 32767 +#define OV5647_HTS_MAX 0x1fff + #define OV5647_EXPOSURE_MIN 4 #define OV5647_EXPOSURE_STEP 1 #define OV5647_EXPOSURE_DEFAULT 1000 @@ -187,8 +191,6 @@ static struct regval_list ov5647_2592x1944_10bpp[] = { {0x3a19, 0xf8}, {0x3c01, 0x80}, {0x3b07, 0x0c}, - {0x380c, 0x0b}, - {0x380d, 0x1c}, {0x3814, 0x11}, {0x3815, 0x11}, {0x3708, 0x64}, @@ -276,8 +278,6 @@ static struct regval_list ov5647_1080p30_10bpp[] = { {0x3a19, 0xf8}, {0x3c01, 0x80}, {0x3b07, 0x0c}, - {0x380c, 0x09}, - {0x380d, 0x70}, {0x3814, 0x11}, {0x3815, 0x11}, {0x3708, 0x64}, @@ -375,8 +375,6 @@ static struct regval_list ov5647_2x2binned_10bpp[] = { {0x3809, 0x10}, {0x380a, 0x03}, {0x380b, 0xcc}, - {0x380c, 0x07}, - {0x380d, 0x68}, {0x3811, 0x0c}, {0x3813, 0x06}, {0x3814, 0x31}, @@ -450,8 +448,6 @@ static struct regval_list ov5647_640x480_10bpp[] = { {0x3a19, 0xf8}, {0x3c01, 0x80}, {0x3b07, 0x0c}, - {0x380c, 0x07}, - {0x380d, 0x3c}, {0x3814, 0x35}, {0x3815, 0x35}, {0x3708, 0x64}, @@ -1063,7 +1059,8 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, mode->pixel_rate, 1, mode->pixel_rate); hblank = mode->hts - mode->format.width; - __v4l2_ctrl_modify_range(sensor->hblank, hblank, hblank, 1, + __v4l2_ctrl_modify_range(sensor->hblank, hblank, + OV5647_HTS_MAX - mode->format.width, 1, hblank); vblank = mode->vts - mode->format.height; @@ -1333,6 +1330,10 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) ret = ov5647_write16(sd, OV5647_REG_VTS_HI, sensor->mode->format.height + ctrl->val); break; + case V4L2_CID_HBLANK: + ret = ov5647_write16(sd, OV5647_REG_HTS_HI, + sensor->mode->format.width + ctrl->val); + break; case V4L2_CID_TEST_PATTERN: ret = ov5647_write(sd, OV5647_REG_ISPCTRL3D, ov5647_test_pattern_val[ctrl->val]); @@ -1340,7 +1341,6 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) /* Read-only, but we adjust it based on mode. */ case V4L2_CID_PIXEL_RATE: - case V4L2_CID_HBLANK: /* Read-only, but we adjust it based on mode. */ break; @@ -1416,10 +1416,11 @@ static int ov5647_init_controls(struct ov5647 *sensor) sensor->mode->pixel_rate, 1, sensor->mode->pixel_rate); - /* By default, HBLANK is read only, but it does change per mode. */ hblank = sensor->mode->hts - sensor->mode->format.width; sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, - V4L2_CID_HBLANK, hblank, hblank, 1, + V4L2_CID_HBLANK, hblank, + OV5647_HTS_MAX - + sensor->mode->format.width, 1, hblank); sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, @@ -1449,7 +1450,6 @@ static int ov5647_init_controls(struct ov5647 *sensor) goto handler_free; sensor->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; - sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; sensor->sd.ctrl_handler = &sensor->ctrls; return 0; From 46e972383a998c2645b528b04188ea2fcfbb8e22 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Mon, 22 Dec 2025 13:45:37 +0530 Subject: [PATCH 156/307] media: i2c: ov5647: Tidy up mode registers to make the order common To make comparisons of the mode registers easier, put the registers for the binned and VGA modes in the same order as the others. Signed-off-by: Dave Stevenson Reviewed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index c89e13442e29..8142496ed959 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -343,6 +343,8 @@ static struct regval_list ov5647_2x2binned_10bpp[] = { {0x3036, 0x62}, {0x303c, 0x11}, {0x3106, 0xf5}, + {0x3821, 0x03}, + {0x3820, 0x41}, {0x3827, 0xec}, {0x370c, 0x03}, {0x3612, 0x59}, @@ -415,8 +417,6 @@ static struct regval_list ov5647_2x2binned_10bpp[] = { {0x4837, 0x16}, {0x4800, 0x24}, {0x3503, 0x03}, - {0x3820, 0x41}, - {0x3821, 0x03}, {0x350a, 0x00}, {0x350b, 0x10}, {0x3500, 0x00}, @@ -429,20 +429,27 @@ static struct regval_list ov5647_2x2binned_10bpp[] = { static struct regval_list ov5647_640x480_10bpp[] = { {0x0100, 0x00}, {0x0103, 0x01}, - {0x3035, 0x11}, + {0x3034, 0x1a}, + {0x3035, 0x21}, {0x3036, 0x46}, {0x303c, 0x11}, + {0x3106, 0xf5}, {0x3821, 0x03}, {0x3820, 0x41}, + {0x3827, 0xec}, {0x370c, 0x03}, {0x3612, 0x59}, {0x3618, 0x00}, {0x5000, 0x06}, {0x5003, 0x08}, {0x5a00, 0x08}, - {0x3000, 0xff}, - {0x3001, 0xff}, - {0x3002, 0xff}, + {0x3000, 0x00}, + {0x3001, 0x00}, + {0x3002, 0x00}, + {0x3016, 0x08}, + {0x3017, 0xe0}, + {0x3018, 0x44}, + {0x301c, 0xf8}, {0x301d, 0xf0}, {0x3a18, 0x00}, {0x3a19, 0xf8}, @@ -468,6 +475,7 @@ static struct regval_list ov5647_640x480_10bpp[] = { {0x3632, 0xe2}, {0x3633, 0x23}, {0x3634, 0x44}, + {0x3636, 0x06}, {0x3620, 0x64}, {0x3621, 0xe0}, {0x3600, 0x37}, @@ -496,19 +504,6 @@ static struct regval_list ov5647_640x480_10bpp[] = { {0x4001, 0x02}, {0x4004, 0x02}, {0x4000, 0x09}, - {0x3000, 0x00}, - {0x3001, 0x00}, - {0x3002, 0x00}, - {0x3017, 0xe0}, - {0x301c, 0xfc}, - {0x3636, 0x06}, - {0x3016, 0x08}, - {0x3827, 0xec}, - {0x3018, 0x44}, - {0x3035, 0x21}, - {0x3106, 0xf5}, - {0x3034, 0x1a}, - {0x301c, 0xf8}, {0x4800, 0x34}, {0x3503, 0x03}, {0x0100, 0x01}, From d06a6dc90675aaec1f5749765bdc37b64fdd031c Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Mon, 22 Dec 2025 13:45:38 +0530 Subject: [PATCH 157/307] media: i2c: ov5647: Separate out the common registers. There are many registers in common between all the modes. Pull those out into one common table. Signed-off-by: Dave Stevenson Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 257 ++++++++----------------------------- 1 file changed, 57 insertions(+), 200 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 8142496ed959..13b703f39b10 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -161,22 +161,16 @@ static const struct regval_list sensor_oe_enable_regs[] = { {0x3002, 0xe4}, }; -static struct regval_list ov5647_2592x1944_10bpp[] = { +static struct regval_list ov5647_common_regs[] = { {0x0100, 0x00}, {0x0103, 0x01}, {0x3034, 0x1a}, {0x3035, 0x21}, - {0x3036, 0x69}, {0x303c, 0x11}, {0x3106, 0xf5}, - {0x3821, 0x02}, - {0x3820, 0x00}, {0x3827, 0xec}, {0x370c, 0x03}, - {0x3612, 0x5b}, - {0x3618, 0x04}, {0x5000, 0x06}, - {0x5002, 0x41}, {0x5003, 0x08}, {0x5a00, 0x08}, {0x3000, 0x00}, @@ -191,14 +185,47 @@ static struct regval_list ov5647_2592x1944_10bpp[] = { {0x3a19, 0xf8}, {0x3c01, 0x80}, {0x3b07, 0x0c}, + {0x3630, 0x2e}, + {0x3632, 0xe2}, + {0x3633, 0x23}, + {0x3634, 0x44}, + {0x3636, 0x06}, + {0x3620, 0x64}, + {0x3621, 0xe0}, + {0x3600, 0x37}, + {0x3704, 0xa0}, + {0x3703, 0x5a}, + {0x3715, 0x78}, + {0x3717, 0x01}, + {0x3731, 0x02}, + {0x370b, 0x60}, + {0x3705, 0x1a}, + {0x3f05, 0x02}, + {0x3f06, 0x10}, + {0x3f01, 0x0a}, + {0x3a08, 0x01}, + {0x3a0f, 0x58}, + {0x3a10, 0x50}, + {0x3a1b, 0x58}, + {0x3a1e, 0x50}, + {0x3a11, 0x60}, + {0x3a1f, 0x28}, + {0x4001, 0x02}, + {0x4000, 0x09}, + {0x3503, 0x03}, +}; + +static struct regval_list ov5647_2592x1944_10bpp[] = { + {0x3036, 0x69}, + {0x3821, 0x02}, + {0x3820, 0x00}, + {0x3612, 0x5b}, + {0x3618, 0x04}, + {0x5002, 0x41}, {0x3814, 0x11}, {0x3815, 0x11}, {0x3708, 0x64}, {0x3709, 0x12}, - {0x3808, 0x0a}, - {0x3809, 0x20}, - {0x380a, 0x07}, - {0x380b, 0x98}, {0x3800, 0x00}, {0x3801, 0x00}, {0x3802, 0x00}, @@ -207,85 +234,34 @@ static struct regval_list ov5647_2592x1944_10bpp[] = { {0x3805, 0x3f}, {0x3806, 0x07}, {0x3807, 0xa3}, + {0x3808, 0x0a}, + {0x3809, 0x20}, + {0x380a, 0x07}, + {0x380b, 0x98}, {0x3811, 0x10}, {0x3813, 0x06}, - {0x3630, 0x2e}, - {0x3632, 0xe2}, - {0x3633, 0x23}, - {0x3634, 0x44}, - {0x3636, 0x06}, - {0x3620, 0x64}, - {0x3621, 0xe0}, - {0x3600, 0x37}, - {0x3704, 0xa0}, - {0x3703, 0x5a}, - {0x3715, 0x78}, - {0x3717, 0x01}, - {0x3731, 0x02}, - {0x370b, 0x60}, - {0x3705, 0x1a}, - {0x3f05, 0x02}, - {0x3f06, 0x10}, - {0x3f01, 0x0a}, - {0x3a08, 0x01}, {0x3a09, 0x28}, {0x3a0a, 0x00}, {0x3a0b, 0xf6}, {0x3a0d, 0x08}, {0x3a0e, 0x06}, - {0x3a0f, 0x58}, - {0x3a10, 0x50}, - {0x3a1b, 0x58}, - {0x3a1e, 0x50}, - {0x3a11, 0x60}, - {0x3a1f, 0x28}, - {0x4001, 0x02}, {0x4004, 0x04}, - {0x4000, 0x09}, {0x4837, 0x19}, {0x4800, 0x24}, - {0x3503, 0x03}, {0x0100, 0x01}, }; static struct regval_list ov5647_1080p30_10bpp[] = { - {0x0100, 0x00}, - {0x0103, 0x01}, - {0x3034, 0x1a}, - {0x3035, 0x21}, {0x3036, 0x62}, - {0x303c, 0x11}, - {0x3106, 0xf5}, {0x3821, 0x02}, {0x3820, 0x00}, - {0x3827, 0xec}, - {0x370c, 0x03}, {0x3612, 0x5b}, {0x3618, 0x04}, - {0x5000, 0x06}, {0x5002, 0x41}, - {0x5003, 0x08}, - {0x5a00, 0x08}, - {0x3000, 0x00}, - {0x3001, 0x00}, - {0x3002, 0x00}, - {0x3016, 0x08}, - {0x3017, 0xe0}, - {0x3018, 0x44}, - {0x301c, 0xf8}, - {0x301d, 0xf0}, - {0x3a18, 0x00}, - {0x3a19, 0xf8}, - {0x3c01, 0x80}, - {0x3b07, 0x0c}, {0x3814, 0x11}, {0x3815, 0x11}, {0x3708, 0x64}, {0x3709, 0x12}, - {0x3808, 0x07}, - {0x3809, 0x80}, - {0x380a, 0x04}, - {0x380b, 0x38}, {0x3800, 0x01}, {0x3801, 0x5c}, {0x3802, 0x01}, @@ -294,77 +270,30 @@ static struct regval_list ov5647_1080p30_10bpp[] = { {0x3805, 0xe3}, {0x3806, 0x05}, {0x3807, 0xf1}, + {0x3808, 0x07}, + {0x3809, 0x80}, + {0x380a, 0x04}, + {0x380b, 0x38}, {0x3811, 0x04}, {0x3813, 0x02}, - {0x3630, 0x2e}, - {0x3632, 0xe2}, - {0x3633, 0x23}, - {0x3634, 0x44}, - {0x3636, 0x06}, - {0x3620, 0x64}, - {0x3621, 0xe0}, - {0x3600, 0x37}, - {0x3704, 0xa0}, - {0x3703, 0x5a}, - {0x3715, 0x78}, - {0x3717, 0x01}, - {0x3731, 0x02}, - {0x370b, 0x60}, - {0x3705, 0x1a}, - {0x3f05, 0x02}, - {0x3f06, 0x10}, - {0x3f01, 0x0a}, - {0x3a08, 0x01}, {0x3a09, 0x4b}, {0x3a0a, 0x01}, {0x3a0b, 0x13}, {0x3a0d, 0x04}, {0x3a0e, 0x03}, - {0x3a0f, 0x58}, - {0x3a10, 0x50}, - {0x3a1b, 0x58}, - {0x3a1e, 0x50}, - {0x3a11, 0x60}, - {0x3a1f, 0x28}, - {0x4001, 0x02}, {0x4004, 0x04}, - {0x4000, 0x09}, {0x4837, 0x19}, {0x4800, 0x34}, - {0x3503, 0x03}, {0x0100, 0x01}, }; static struct regval_list ov5647_2x2binned_10bpp[] = { - {0x0100, 0x00}, - {0x0103, 0x01}, - {0x3034, 0x1a}, - {0x3035, 0x21}, {0x3036, 0x62}, - {0x303c, 0x11}, - {0x3106, 0xf5}, {0x3821, 0x03}, {0x3820, 0x41}, - {0x3827, 0xec}, - {0x370c, 0x03}, {0x3612, 0x59}, {0x3618, 0x00}, - {0x5000, 0x06}, {0x5002, 0x41}, - {0x5003, 0x08}, - {0x5a00, 0x08}, - {0x3000, 0x00}, - {0x3001, 0x00}, - {0x3002, 0x00}, - {0x3016, 0x08}, - {0x3017, 0xe0}, - {0x3018, 0x44}, - {0x301c, 0xf8}, - {0x301d, 0xf0}, - {0x3a18, 0x00}, - {0x3a19, 0xf8}, - {0x3c01, 0x80}, - {0x3b07, 0x0c}, {0x3800, 0x00}, {0x3801, 0x00}, {0x3802, 0x00}, @@ -381,42 +310,14 @@ static struct regval_list ov5647_2x2binned_10bpp[] = { {0x3813, 0x06}, {0x3814, 0x31}, {0x3815, 0x31}, - {0x3630, 0x2e}, - {0x3632, 0xe2}, - {0x3633, 0x23}, - {0x3634, 0x44}, - {0x3636, 0x06}, - {0x3620, 0x64}, - {0x3621, 0xe0}, - {0x3600, 0x37}, - {0x3704, 0xa0}, - {0x3703, 0x5a}, - {0x3715, 0x78}, - {0x3717, 0x01}, - {0x3731, 0x02}, - {0x370b, 0x60}, - {0x3705, 0x1a}, - {0x3f05, 0x02}, - {0x3f06, 0x10}, - {0x3f01, 0x0a}, - {0x3a08, 0x01}, {0x3a09, 0x28}, {0x3a0a, 0x00}, {0x3a0b, 0xf6}, {0x3a0d, 0x08}, {0x3a0e, 0x06}, - {0x3a0f, 0x58}, - {0x3a10, 0x50}, - {0x3a1b, 0x58}, - {0x3a1e, 0x50}, - {0x3a11, 0x60}, - {0x3a1f, 0x28}, - {0x4001, 0x02}, {0x4004, 0x04}, - {0x4000, 0x09}, {0x4837, 0x16}, {0x4800, 0x24}, - {0x3503, 0x03}, {0x350a, 0x00}, {0x350b, 0x10}, {0x3500, 0x00}, @@ -427,42 +328,15 @@ static struct regval_list ov5647_2x2binned_10bpp[] = { }; static struct regval_list ov5647_640x480_10bpp[] = { - {0x0100, 0x00}, - {0x0103, 0x01}, - {0x3034, 0x1a}, - {0x3035, 0x21}, {0x3036, 0x46}, - {0x303c, 0x11}, - {0x3106, 0xf5}, {0x3821, 0x03}, {0x3820, 0x41}, - {0x3827, 0xec}, - {0x370c, 0x03}, {0x3612, 0x59}, {0x3618, 0x00}, - {0x5000, 0x06}, - {0x5003, 0x08}, - {0x5a00, 0x08}, - {0x3000, 0x00}, - {0x3001, 0x00}, - {0x3002, 0x00}, - {0x3016, 0x08}, - {0x3017, 0xe0}, - {0x3018, 0x44}, - {0x301c, 0xf8}, - {0x301d, 0xf0}, - {0x3a18, 0x00}, - {0x3a19, 0xf8}, - {0x3c01, 0x80}, - {0x3b07, 0x0c}, {0x3814, 0x35}, {0x3815, 0x35}, {0x3708, 0x64}, {0x3709, 0x52}, - {0x3808, 0x02}, - {0x3809, 0x80}, - {0x380a, 0x01}, - {0x380b, 0xe0}, {0x3800, 0x00}, {0x3801, 0x10}, {0x3802, 0x00}, @@ -471,41 +345,17 @@ static struct regval_list ov5647_640x480_10bpp[] = { {0x3805, 0x2f}, {0x3806, 0x07}, {0x3807, 0x9f}, - {0x3630, 0x2e}, - {0x3632, 0xe2}, - {0x3633, 0x23}, - {0x3634, 0x44}, - {0x3636, 0x06}, - {0x3620, 0x64}, - {0x3621, 0xe0}, - {0x3600, 0x37}, - {0x3704, 0xa0}, - {0x3703, 0x5a}, - {0x3715, 0x78}, - {0x3717, 0x01}, - {0x3731, 0x02}, - {0x370b, 0x60}, - {0x3705, 0x1a}, - {0x3f05, 0x02}, - {0x3f06, 0x10}, - {0x3f01, 0x0a}, - {0x3a08, 0x01}, + {0x3808, 0x02}, + {0x3809, 0x80}, + {0x380a, 0x01}, + {0x380b, 0xe0}, {0x3a09, 0x2e}, {0x3a0a, 0x00}, {0x3a0b, 0xfb}, {0x3a0d, 0x02}, {0x3a0e, 0x01}, - {0x3a0f, 0x58}, - {0x3a10, 0x50}, - {0x3a1b, 0x58}, - {0x3a1e, 0x50}, - {0x3a11, 0x60}, - {0x3a1f, 0x28}, - {0x4001, 0x02}, {0x4004, 0x02}, - {0x4000, 0x09}, {0x4800, 0x34}, - {0x3503, 0x03}, {0x0100, 0x01}, }; @@ -701,6 +551,13 @@ static int ov5647_set_mode(struct v4l2_subdev *sd) if (ret < 0) return ret; + ret = ov5647_write_array(sd, ov5647_common_regs, + ARRAY_SIZE(ov5647_common_regs)); + if (ret < 0) { + dev_err(&client->dev, "write sensor common regs error\n"); + return ret; + } + ret = ov5647_write_array(sd, sensor->mode->reg_list, sensor->mode->num_regs); if (ret < 0) { From 8bc977e2c51b3fcfaf787915620efc62b4166bbf Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Mon, 22 Dec 2025 13:45:39 +0530 Subject: [PATCH 158/307] media: i2c: ov5647: Use the same PLL config for full, 1080p, and binned modes In order to simplify the driver slightly, use the same PLL configuration, and hence pixel rate and link frequency (to be added) for the full, 1080p, and binned modes. Signed-off-by: Dave Stevenson Reviewed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 13b703f39b10..d4a13e17692f 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -252,7 +252,7 @@ static struct regval_list ov5647_2592x1944_10bpp[] = { }; static struct regval_list ov5647_1080p30_10bpp[] = { - {0x3036, 0x62}, + {0x3036, 0x69}, {0x3821, 0x02}, {0x3820, 0x00}, {0x3612, 0x5b}, @@ -288,7 +288,7 @@ static struct regval_list ov5647_1080p30_10bpp[] = { }; static struct regval_list ov5647_2x2binned_10bpp[] = { - {0x3036, 0x62}, + {0x3036, 0x69}, {0x3821, 0x03}, {0x3820, 0x41}, {0x3612, 0x59}, @@ -396,7 +396,7 @@ static const struct ov5647_mode ov5647_modes[] = { .width = 1928, .height = 1080, }, - .pixel_rate = 81666700, + .pixel_rate = 87500000, .hts = 2416, .vts = 0x450, .reg_list = ov5647_1080p30_10bpp, @@ -417,7 +417,7 @@ static const struct ov5647_mode ov5647_modes[] = { .width = 2592, .height = 1944, }, - .pixel_rate = 81666700, + .pixel_rate = 87500000, .hts = 1896, .vts = 0x59b, .reg_list = ov5647_2x2binned_10bpp, From dda59680f24fdd490546830b0108dfb81cbc853b Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Mon, 22 Dec 2025 13:45:40 +0530 Subject: [PATCH 159/307] media: i2c: ov5647: Tidy up PIXEL_RATE control The pixel rate control is marked as read-only by the framework itself, so no need to mark it explicitly in the driver. Also, we can set the ops to NULL to avoid checking for it in the s_ctrl implementation. Suggested-by: Jacopo Mondi Reviewed-by: Dave Stevenson Reviewed-by: Jacopo Mondi Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index d4a13e17692f..7003c9922c5b 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -1190,12 +1190,6 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) ret = ov5647_write(sd, OV5647_REG_ISPCTRL3D, ov5647_test_pattern_val[ctrl->val]); break; - - /* Read-only, but we adjust it based on mode. */ - case V4L2_CID_PIXEL_RATE: - /* Read-only, but we adjust it based on mode. */ - break; - case V4L2_CID_HFLIP: /* There's an in-built hflip in the sensor, so account for that here. */ ret = ov5647_s_flip(sd, OV5647_REG_TIMING_TC_H, !ctrl->val); @@ -1262,7 +1256,7 @@ static int ov5647_init_controls(struct ov5647 *sensor) V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 32); /* By default, PIXEL_RATE is read only, but it does change per mode */ - sensor->pixel_rate = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, + sensor->pixel_rate = v4l2_ctrl_new_std(&sensor->ctrls, NULL, V4L2_CID_PIXEL_RATE, sensor->mode->pixel_rate, sensor->mode->pixel_rate, 1, @@ -1301,7 +1295,6 @@ static int ov5647_init_controls(struct ov5647 *sensor) if (sensor->ctrls.error) goto handler_free; - sensor->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; sensor->sd.ctrl_handler = &sensor->ctrls; return 0; From c6e115144b500039b9ece39c3aaf3e61e1f761af Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Mon, 22 Dec 2025 13:45:41 +0530 Subject: [PATCH 160/307] media: i2c: ov5647: Add V4L2_CID_LINK_FREQUENCY control The link frequency can vary between modes, so add it as a control. Signed-off-by: Dave Stevenson Signed-off-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 7003c9922c5b..1041732104fa 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -97,6 +97,13 @@ static const char * const ov5647_supply_names[] = { #define OV5647_NUM_SUPPLIES ARRAY_SIZE(ov5647_supply_names) +#define FREQ_INDEX_FULL 0 +#define FREQ_INDEX_VGA 1 +static const s64 ov5647_link_freqs[] = { + [FREQ_INDEX_FULL] = 218750000, + [FREQ_INDEX_VGA] = 145833300, +}; + struct regval_list { u16 addr; u8 data; @@ -106,6 +113,7 @@ struct ov5647_mode { struct v4l2_mbus_framefmt format; struct v4l2_rect crop; u64 pixel_rate; + unsigned int link_freq_index; int hts; int vts; const struct regval_list *reg_list; @@ -128,6 +136,7 @@ struct ov5647 { struct v4l2_ctrl *exposure; struct v4l2_ctrl *hflip; struct v4l2_ctrl *vflip; + struct v4l2_ctrl *link_freq; }; static inline struct ov5647 *to_sensor(struct v4l2_subdev *sd) @@ -376,6 +385,7 @@ static const struct ov5647_mode ov5647_modes[] = { .height = 1944 }, .pixel_rate = 87500000, + .link_freq_index = FREQ_INDEX_FULL, .hts = 2844, .vts = 0x7b0, .reg_list = ov5647_2592x1944_10bpp, @@ -397,6 +407,7 @@ static const struct ov5647_mode ov5647_modes[] = { .height = 1080, }, .pixel_rate = 87500000, + .link_freq_index = FREQ_INDEX_FULL, .hts = 2416, .vts = 0x450, .reg_list = ov5647_1080p30_10bpp, @@ -418,6 +429,7 @@ static const struct ov5647_mode ov5647_modes[] = { .height = 1944, }, .pixel_rate = 87500000, + .link_freq_index = FREQ_INDEX_FULL, .hts = 1896, .vts = 0x59b, .reg_list = ov5647_2x2binned_10bpp, @@ -439,6 +451,7 @@ static const struct ov5647_mode ov5647_modes[] = { .height = 1920, }, .pixel_rate = 58333000, + .link_freq_index = FREQ_INDEX_VGA, .hts = 1852, .vts = 0x1f8, .reg_list = ov5647_640x480_10bpp, @@ -927,6 +940,8 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, sensor->exposure->minimum, exposure_max, sensor->exposure->step, exposure_def); + + __v4l2_ctrl_s_ctrl(sensor->link_freq, mode->link_freq_index); } *fmt = mode->format; /* The code we pass back must reflect the current h/vflips. */ @@ -1231,7 +1246,7 @@ static int ov5647_init_controls(struct ov5647 *sensor) int hblank, exposure_max, exposure_def; struct device *dev = &client->dev; - v4l2_ctrl_handler_init(&sensor->ctrls, 13); + v4l2_ctrl_handler_init(&sensor->ctrls, 14); v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, V4L2_CID_AUTOGAIN, 0, 1, 1, 0); @@ -1287,6 +1302,14 @@ static int ov5647_init_controls(struct ov5647 *sensor) sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + sensor->link_freq = + v4l2_ctrl_new_int_menu(&sensor->ctrls, NULL, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(ov5647_link_freqs) - 1, + sensor->mode->link_freq_index, + ov5647_link_freqs); + if (sensor->link_freq) + sensor->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + v4l2_fwnode_device_parse(dev, &props); v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &ov5647_ctrl_ops, From acc483fad79ecd1f5fb0adfd605f276383b678cd Mon Sep 17 00:00:00 2001 From: Zixuan Dong Date: Mon, 15 Dec 2025 13:53:32 +0800 Subject: [PATCH 161/307] media: staging: atomisp: remove redundant OOM error messages The memory allocation functions (kvzalloc) already emit a stack dump on failure when GFP_KERNEL is used. Printing an extra error message is redundant and increases code size. This resolves the checkpatch warnings: WARNING: Possible unnecessary 'out of memory' message Signed-off-by: Zixuan Dong Reviewed-by: Andy Shevchenko Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/atomisp/pci/atomisp_cmd.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/staging/media/atomisp/pci/atomisp_cmd.c b/drivers/staging/media/atomisp/pci/atomisp_cmd.c index 3a4eb4f6d3be..a3cd9d3e9ce7 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_cmd.c +++ b/drivers/staging/media/atomisp/pci/atomisp_cmd.c @@ -2961,11 +2961,8 @@ int atomisp_set_parameters(struct video_device *vdev, * per-frame setting only works for the main output frame. */ param = kvzalloc(sizeof(*param), GFP_KERNEL); - if (!param) { - dev_err(asd->isp->dev, "%s: failed to alloc params buffer\n", - __func__); + if (!param) return -ENOMEM; - } css_param = ¶m->params; } From 4f84155e4a7e16ddfb3fb630d637a863829a0e80 Mon Sep 17 00:00:00 2001 From: Arjun Changla Date: Thu, 11 Dec 2025 05:26:51 +0000 Subject: [PATCH 162/307] staging: media: atomisp: fix trailing statement Fix checkpatch error "trailing statements should be on next line" by moving the return statement to a new line. Signed-off-by: Arjun Changla Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../atomisp/pci/isp/kernels/fc/fc_1.0/ia_css_formats.host.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/fc/fc_1.0/ia_css_formats.host.c b/drivers/staging/media/atomisp/pci/isp/kernels/fc/fc_1.0/ia_css_formats.host.c index 6a10d3545278..e422cca55022 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/fc/fc_1.0/ia_css_formats.host.c +++ b/drivers/staging/media/atomisp/pci/isp/kernels/fc/fc_1.0/ia_css_formats.host.c @@ -35,7 +35,8 @@ ia_css_formats_dump( const struct sh_css_isp_formats_params *formats, unsigned int level) { - if (!formats) return; + if (!formats) + return; ia_css_debug_dtrace(level, "\t%-32s = %d\n", "video_full_range_flag", formats->video_full_range_flag); } From d71bd243ef4849013a9ea6ba3a6359894435aacf Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Thu, 25 Dec 2025 13:03:07 -0800 Subject: [PATCH 163/307] media: dt-bindings: i2c: toshiba,et8ek8: Convert to DT schema Convert binding for toshiba,et8ek8 from TXT to YAML format. Update MAINTAINERS file accordingly. The binding references video-interface-devices.yaml at top level to inherit flash-leds property. Signed-off-by: Alex Tran Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../bindings/media/i2c/toshiba,et8ek8.txt | 55 ------------ .../bindings/media/i2c/toshiba,et8ek8.yaml | 87 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 88 insertions(+), 55 deletions(-) delete mode 100644 Documentation/devicetree/bindings/media/i2c/toshiba,et8ek8.txt create mode 100644 Documentation/devicetree/bindings/media/i2c/toshiba,et8ek8.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/toshiba,et8ek8.txt b/Documentation/devicetree/bindings/media/i2c/toshiba,et8ek8.txt deleted file mode 100644 index 8d8e40c56872..000000000000 --- a/Documentation/devicetree/bindings/media/i2c/toshiba,et8ek8.txt +++ /dev/null @@ -1,55 +0,0 @@ -Toshiba et8ek8 5MP sensor - -Toshiba et8ek8 5MP sensor is an image sensor found in Nokia N900 device - -More detailed documentation can be found in -Documentation/devicetree/bindings/media/video-interfaces.txt . - - -Mandatory properties --------------------- - -- compatible: "toshiba,et8ek8" -- reg: I2C address (0x3e, or an alternative address) -- vana-supply: Analogue voltage supply (VANA), 2.8 volts -- clocks: External clock to the sensor -- reset-gpios: XSHUTDOWN GPIO. The XSHUTDOWN signal is active low. The sensor - is in hardware standby mode when the signal is in the low state. - - -Optional properties -------------------- - -- flash-leds: See ../video-interfaces.txt -- lens-focus: See ../video-interfaces.txt - - -Endpoint node mandatory properties ----------------------------------- - -- remote-endpoint: A phandle to the bus receiver's endpoint node. - - -Example -------- - -&i2c3 { - clock-frequency = <400000>; - - cam1: camera@3e { - compatible = "toshiba,et8ek8"; - reg = <0x3e>; - vana-supply = <&vaux4>; - - clocks = <&isp 0>; - assigned-clocks = <&isp 0>; - assigned-clock-rates = <9600000>; - - reset-gpio = <&gpio4 6 GPIO_ACTIVE_HIGH>; /* 102 */ - port { - csi_cam1: endpoint { - remote-endpoint = <&csi_out1>; - }; - }; - }; -}; diff --git a/Documentation/devicetree/bindings/media/i2c/toshiba,et8ek8.yaml b/Documentation/devicetree/bindings/media/i2c/toshiba,et8ek8.yaml new file mode 100644 index 000000000000..f0186ae87de2 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/toshiba,et8ek8.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/toshiba,et8ek8.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Toshiba et8ek8 5MP sensor + +maintainers: + - Pavel Machek + - Sakari Ailus + +description: + Toshiba et8ek8 5MP sensor is an image sensor found in Nokia N900 device + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: toshiba,et8ek8 + + reg: + description: + I2C address (0x3e, or an alternative address) + maxItems: 1 + + vana-supply: + description: + Analogue voltage supply (VANA), 2.8 volts + + clocks: + maxItems: 1 + + reset-gpios: + description: + XSHUTDOWN GPIO. The XSHUTDOWN signal is active low. The sensor + is in hardware standby mode when the signal is in the low state. + maxItems: 1 + + flash-leds: + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + +required: + - compatible + - reg + - vana-supply + - clocks + - reset-gpios + - port + +unevaluatedProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@3e { + compatible = "toshiba,et8ek8"; + reg = <0x3e>; + vana-supply = <&vaux4>; + clocks = <&isp 0>; + assigned-clocks = <&isp 0>; + assigned-clock-rates = <9600000>; + reset-gpios = <&gpio4 6 GPIO_ACTIVE_HIGH>; + flash-leds = <&led>; + + port { + csi_cam1: endpoint { + remote-endpoint = <&csi_out1>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 8fa4c1c961f1..fc68ee0c68c0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18583,6 +18583,7 @@ M: Sakari Ailus L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/adi,ad5820.yaml +F: Documentation/devicetree/bindings/media/i2c/toshiba,et8ek8.yaml F: drivers/media/i2c/ad5820.c F: drivers/media/i2c/et8ek8 From c16b58a4557370efdcf99ab6f978cb676b391579 Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Thu, 25 Dec 2025 13:03:08 -0800 Subject: [PATCH 164/307] media: dt-bindings: ti,omap3isp: Convert to DT schema Convert binding for ti,omap3isp from TXT to YAML format. Signed-off-by: Alex Tran Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/ti,omap3isp.txt | 71 ------- .../bindings/media/ti,omap3isp.yaml | 189 ++++++++++++++++++ 2 files changed, 189 insertions(+), 71 deletions(-) delete mode 100644 Documentation/devicetree/bindings/media/ti,omap3isp.txt create mode 100644 Documentation/devicetree/bindings/media/ti,omap3isp.yaml diff --git a/Documentation/devicetree/bindings/media/ti,omap3isp.txt b/Documentation/devicetree/bindings/media/ti,omap3isp.txt deleted file mode 100644 index ac23de855641..000000000000 --- a/Documentation/devicetree/bindings/media/ti,omap3isp.txt +++ /dev/null @@ -1,71 +0,0 @@ -OMAP 3 ISP Device Tree bindings -=============================== - -The DT definitions can be found in include/dt-bindings/media/omap3-isp.h. - -Required properties -=================== - -compatible : must contain "ti,omap3-isp" - -reg : the two registers sets (physical address and length) for the - ISP. The first set contains the core ISP registers up to - the end of the SBL block. The second set contains the - CSI PHYs and receivers registers. -interrupts : the ISP interrupt specifier -iommus : phandle and IOMMU specifier for the IOMMU that serves the ISP -syscon : the phandle and register offset to the Complex I/O or CSI-PHY - register -ti,phy-type : 0 -- OMAP3ISP_PHY_TYPE_COMPLEX_IO (e.g. 3430) - 1 -- OMAP3ISP_PHY_TYPE_CSIPHY (e.g. 3630) -#clock-cells : Must be 1 --- the ISP provides two external clocks, - cam_xclka and cam_xclkb, at indices 0 and 1, - respectively. Please find more information on common - clock bindings in ../clock/clock-bindings.txt. - -Port nodes (optional) ---------------------- - -More documentation on these bindings is available in -video-interfaces.txt in the same directory. - -reg : The interface: - 0 - parallel (CCDC) - 1 - CSIPHY1 -- CSI2C / CCP2B on 3630; - CSI1 -- CSIb on 3430 - 2 - CSIPHY2 -- CSI2A / CCP2B on 3630; - CSI2 -- CSIa on 3430 - -Optional properties -=================== - -vdd-csiphy1-supply : voltage supply of the CSI-2 PHY 1 -vdd-csiphy2-supply : voltage supply of the CSI-2 PHY 2 - -Endpoint nodes --------------- - -lane-polarities : lane polarity (required on CSI-2) - 0 -- not inverted; 1 -- inverted -data-lanes : an array of data lanes from 1 to 3. The length can - be either 1 or 2. (required on CSI-2) -clock-lanes : the clock lane (from 1 to 3). (required on CSI-2) - - -Example -======= - - isp@480bc000 { - compatible = "ti,omap3-isp"; - reg = <0x480bc000 0x12fc - 0x480bd800 0x0600>; - interrupts = <24>; - iommus = <&mmu_isp>; - syscon = <&scm_conf 0x2f0>; - ti,phy-type = ; - #clock-cells = <1>; - ports { - #address-cells = <1>; - #size-cells = <0>; - }; - }; diff --git a/Documentation/devicetree/bindings/media/ti,omap3isp.yaml b/Documentation/devicetree/bindings/media/ti,omap3isp.yaml new file mode 100644 index 000000000000..7155fd3db505 --- /dev/null +++ b/Documentation/devicetree/bindings/media/ti,omap3isp.yaml @@ -0,0 +1,189 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/ti,omap3isp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments OMAP 3 Image Signal Processor (ISP) + +maintainers: + - Laurent Pinchart + - Sakari Ailus + +description: + The OMAP 3 ISP is an image signal processor present in OMAP 3 SoCs. + +properties: + compatible: + const: ti,omap3-isp + + reg: + items: + - description: Core ISP registers up to the end of the SBL block + - description: CSI PHYs and receivers registers + + interrupts: + maxItems: 1 + + iommus: + maxItems: 1 + + syscon: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: phandle to System Control Module + - description: register offset to Complex I/O or CSI-PHY register + description: + Phandle and register offset to the Complex I/O or CSI-PHY register + + ti,phy-type: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + description: + 0 - OMAP3ISP_PHY_TYPE_COMPLEX_IO (e.g. OMAP 3430) + 1 - OMAP3ISP_PHY_TYPE_CSIPHY (e.g. OMAP 3630) + + '#clock-cells': + const: 1 + description: + The ISP provides two external clocks, cam_xclka and cam_xclkb, + at indices 0 and 1 respectively. + + vdd-csiphy1-supply: + description: Voltage supply of the CSI-2 PHY 1 + + vdd-csiphy2-supply: + description: Voltage supply of the CSI-2 PHY 2 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: Parallel (CCDC) interface + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: | + CSIPHY1 interface: + OMAP 3630: CSI2C / CCP2B + OMAP 3430: CSI1 (CSIb) + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + lane-polarities: + minItems: 2 + maxItems: 3 + + data-lanes: + minItems: 1 + maxItems: 2 + items: + minimum: 1 + maximum: 3 + + clock-lanes: + minimum: 1 + maximum: 3 + + port@2: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: | + CSIPHY2 interface: + OMAP 3630: CSI2A / CCP2B + OMAP 3430: CSI2 (CSIa) + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + lane-polarities: + minItems: 2 + maxItems: 3 + + data-lanes: + minItems: 1 + maxItems: 2 + items: + minimum: 1 + maximum: 3 + + clock-lanes: + minimum: 1 + maximum: 3 + +required: + - compatible + - reg + - interrupts + - iommus + - syscon + - ti,phy-type + - '#clock-cells' + +additionalProperties: false + +examples: + - | + #include + + isp@480bc000 { + compatible = "ti,omap3-isp"; + reg = <0x480bc000 0x12fc>, + <0x480bd800 0x0600>; + interrupts = <24>; + iommus = <&mmu_isp>; + syscon = <&scm_conf 0x2f0>; + ti,phy-type = ; + #clock-cells = <1>; + vdd-csiphy1-supply = <&vaux2>; + vdd-csiphy2-supply = <&vaux2>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + parallel_ep: endpoint { + remote-endpoint = <¶llel>; + }; + }; + + port@1 { + reg = <1>; + csi1_ep: endpoint { + remote-endpoint = <&smia_1>; + clock-lanes = <1>; + data-lanes = <2>; + lane-polarities = <0 0>; + }; + }; + + port@2 { + reg = <2>; + csi2a_ep: endpoint { + remote-endpoint = <&smia_2>; + clock-lanes = <2>; + data-lanes = <1 3>; + lane-polarities = <1 1 1>; + }; + }; + }; + }; From d9d64cd17d0e4e1f40eb1a137429014a127a9953 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Mon, 22 Dec 2025 05:09:46 +0200 Subject: [PATCH 165/307] media: i2c: ov6211: make configurable vblank control of the sensor Configurable vertical blanking size control allows users to set a wanted image frame rate. Reviewed-by: Tarang Raval Signed-off-by: Vladimir Zapolskiy Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov6211.c | 55 +++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/drivers/media/i2c/ov6211.c b/drivers/media/i2c/ov6211.c index e3ac5ecf27d1..034d5d57d67e 100644 --- a/drivers/media/i2c/ov6211.c +++ b/drivers/media/i2c/ov6211.c @@ -41,6 +41,10 @@ #define OV6211_ANALOGUE_GAIN_STEP 1 #define OV6211_ANALOGUE_GAIN_DEFAULT 160 +/* Vertical timing size */ +#define OV6211_REG_VTS CCI_REG16(0x380e) +#define OV6211_VTS_MAX 0xffff + /* Test pattern */ #define OV6211_REG_PRE_ISP CCI_REG8(0x5e00) #define OV6211_TEST_PATTERN_ENABLE BIT(7) @@ -89,6 +93,8 @@ struct ov6211 { struct v4l2_subdev sd; struct media_pad pad; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; struct v4l2_ctrl_handler ctrl_handler; /* Saved register values */ @@ -167,8 +173,6 @@ static const struct cci_reg_sequence ov6211_400x400_120fps_mode[] = { { CCI_REG8(0x380b), 0x90 }, { CCI_REG8(0x380c), 0x05 }, /* horizontal timing size */ { CCI_REG8(0x380d), 0xf2 }, - { CCI_REG8(0x380e), 0x01 }, /* vertical timing size */ - { CCI_REG8(0x380f), 0xb6 }, { CCI_REG8(0x3810), 0x00 }, { CCI_REG8(0x3811), 0x04 }, { CCI_REG8(0x3812), 0x00 }, @@ -251,8 +255,25 @@ static int ov6211_set_ctrl(struct v4l2_ctrl *ctrl) { struct ov6211 *ov6211 = container_of(ctrl->handler, struct ov6211, ctrl_handler); + const struct ov6211_mode *mode = &supported_modes[0]; + s64 exposure_max; int ret; + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + exposure_max = ctrl->val + mode->height - + OV6211_EXPOSURE_MAX_MARGIN; + ret = __v4l2_ctrl_modify_range(ov6211->exposure, + ov6211->exposure->minimum, + exposure_max, + ov6211->exposure->step, + ov6211->exposure->default_value); + if (ret) + return ret; + } + /* V4L2 controls are applied, when sensor is powered up for streaming */ if (!pm_runtime_get_if_active(ov6211->dev)) return 0; @@ -266,6 +287,10 @@ static int ov6211_set_ctrl(struct v4l2_ctrl *ctrl) ret = cci_write(ov6211->regmap, OV6211_REG_EXPOSURE, ctrl->val << 4, NULL); break; + case V4L2_CID_VBLANK: + ret = cci_write(ov6211->regmap, OV6211_REG_VTS, + ctrl->val + mode->height, NULL); + break; case V4L2_CID_TEST_PATTERN: ret = ov6211_set_test_pattern(ov6211, ctrl->val); break; @@ -287,8 +312,8 @@ static int ov6211_init_controls(struct ov6211 *ov6211) { struct v4l2_ctrl_handler *ctrl_hdlr = &ov6211->ctrl_handler; const struct ov6211_mode *mode = &supported_modes[0]; + s64 exposure_max, pixel_rate, h_blank, v_blank; struct v4l2_fwnode_device_properties props; - s64 exposure_max, pixel_rate, h_blank; struct v4l2_ctrl *ctrl; int ret; @@ -311,24 +336,24 @@ static int ov6211_init_controls(struct ov6211 *ov6211) if (ctrl) ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; - ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &ov6211_ctrl_ops, V4L2_CID_VBLANK, - mode->vts - mode->height, - mode->vts - mode->height, 1, - mode->vts - mode->height); - if (ctrl) - ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + v_blank = mode->vts - mode->height; + ov6211->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov6211_ctrl_ops, + V4L2_CID_VBLANK, v_blank, + OV6211_VTS_MAX - mode->height, 1, + v_blank); v4l2_ctrl_new_std(ctrl_hdlr, &ov6211_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, OV6211_ANALOGUE_GAIN_MIN, OV6211_ANALOGUE_GAIN_MAX, OV6211_ANALOGUE_GAIN_STEP, OV6211_ANALOGUE_GAIN_DEFAULT); - exposure_max = (mode->vts - OV6211_EXPOSURE_MAX_MARGIN); - v4l2_ctrl_new_std(ctrl_hdlr, &ov6211_ctrl_ops, - V4L2_CID_EXPOSURE, - OV6211_EXPOSURE_MIN, exposure_max, - OV6211_EXPOSURE_STEP, - OV6211_EXPOSURE_DEFAULT); + exposure_max = mode->vts - OV6211_EXPOSURE_MAX_MARGIN; + ov6211->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov6211_ctrl_ops, + V4L2_CID_EXPOSURE, + OV6211_EXPOSURE_MIN, + exposure_max, + OV6211_EXPOSURE_STEP, + OV6211_EXPOSURE_DEFAULT); v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov6211_ctrl_ops, V4L2_CID_TEST_PATTERN, From 89bb7d3c404d45a26889104c7787fd03d032a29f Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Mon, 22 Dec 2025 05:09:47 +0200 Subject: [PATCH 166/307] media: i2c: og0ve1b: make configurable vblank control of the sensor Configurable vertical blanking size control allows users to set a wanted image frame rate. Reviewed-by: Tarang Raval Signed-off-by: Vladimir Zapolskiy Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/og0ve1b.c | 55 +++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/drivers/media/i2c/og0ve1b.c b/drivers/media/i2c/og0ve1b.c index 262d9df766fe..84a28cdcade1 100644 --- a/drivers/media/i2c/og0ve1b.c +++ b/drivers/media/i2c/og0ve1b.c @@ -41,6 +41,10 @@ #define OG0VE1B_ANALOGUE_GAIN_STEP 1 #define OG0VE1B_ANALOGUE_GAIN_DEFAULT 16 +/* Vertical timing size */ +#define OG0VE1B_REG_VTS CCI_REG16(0x380e) +#define OG0VE1B_VTS_MAX 0xffff + /* Test pattern */ #define OG0VE1B_REG_PRE_ISP CCI_REG8(0x5e00) #define OG0VE1B_TEST_PATTERN_ENABLE BIT(7) @@ -89,6 +93,8 @@ struct og0ve1b { struct v4l2_subdev sd; struct media_pad pad; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; struct v4l2_ctrl_handler ctrl_handler; /* Saved register value */ @@ -140,8 +146,6 @@ static const struct cci_reg_sequence og0ve1b_640x480_120fps_mode[] = { { CCI_REG8(0x380b), 0xe0 }, { CCI_REG8(0x380c), 0x03 }, /* horizontal timing size */ { CCI_REG8(0x380d), 0x18 }, - { CCI_REG8(0x380e), 0x02 }, /* vertical timing size */ - { CCI_REG8(0x380f), 0x38 }, { CCI_REG8(0x3811), 0x04 }, { CCI_REG8(0x3813), 0x04 }, { CCI_REG8(0x3814), 0x11 }, @@ -273,8 +277,25 @@ static int og0ve1b_set_ctrl(struct v4l2_ctrl *ctrl) { struct og0ve1b *og0ve1b = container_of(ctrl->handler, struct og0ve1b, ctrl_handler); + const struct og0ve1b_mode *mode = &supported_modes[0]; + s64 exposure_max; int ret; + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + exposure_max = ctrl->val + mode->height - + OG0VE1B_EXPOSURE_MAX_MARGIN; + ret = __v4l2_ctrl_modify_range(og0ve1b->exposure, + og0ve1b->exposure->minimum, + exposure_max, + og0ve1b->exposure->step, + og0ve1b->exposure->default_value); + if (ret) + return ret; + } + /* V4L2 controls are applied, when sensor is powered up for streaming */ if (!pm_runtime_get_if_active(og0ve1b->dev)) return 0; @@ -288,6 +309,10 @@ static int og0ve1b_set_ctrl(struct v4l2_ctrl *ctrl) ret = cci_write(og0ve1b->regmap, OG0VE1B_REG_EXPOSURE, ctrl->val << 4, NULL); break; + case V4L2_CID_VBLANK: + ret = cci_write(og0ve1b->regmap, OG0VE1B_REG_VTS, + ctrl->val + mode->height, NULL); + break; case V4L2_CID_TEST_PATTERN: ret = og0ve1b_enable_test_pattern(og0ve1b, ctrl->val); break; @@ -309,8 +334,8 @@ static int og0ve1b_init_controls(struct og0ve1b *og0ve1b) { struct v4l2_ctrl_handler *ctrl_hdlr = &og0ve1b->ctrl_handler; const struct og0ve1b_mode *mode = &supported_modes[0]; + s64 exposure_max, pixel_rate, h_blank, v_blank; struct v4l2_fwnode_device_properties props; - s64 exposure_max, pixel_rate, h_blank; struct v4l2_ctrl *ctrl; int ret; @@ -333,24 +358,24 @@ static int og0ve1b_init_controls(struct og0ve1b *og0ve1b) if (ctrl) ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; - ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &og0ve1b_ctrl_ops, V4L2_CID_VBLANK, - mode->vts - mode->height, - mode->vts - mode->height, 1, - mode->vts - mode->height); - if (ctrl) - ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + v_blank = mode->vts - mode->height; + og0ve1b->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &og0ve1b_ctrl_ops, + V4L2_CID_VBLANK, v_blank, + OG0VE1B_VTS_MAX - mode->height, 1, + v_blank); v4l2_ctrl_new_std(ctrl_hdlr, &og0ve1b_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, OG0VE1B_ANALOGUE_GAIN_MIN, OG0VE1B_ANALOGUE_GAIN_MAX, OG0VE1B_ANALOGUE_GAIN_STEP, OG0VE1B_ANALOGUE_GAIN_DEFAULT); - exposure_max = (mode->vts - OG0VE1B_EXPOSURE_MAX_MARGIN); - v4l2_ctrl_new_std(ctrl_hdlr, &og0ve1b_ctrl_ops, - V4L2_CID_EXPOSURE, - OG0VE1B_EXPOSURE_MIN, exposure_max, - OG0VE1B_EXPOSURE_STEP, - OG0VE1B_EXPOSURE_DEFAULT); + exposure_max = mode->vts - OG0VE1B_EXPOSURE_MAX_MARGIN; + og0ve1b->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &og0ve1b_ctrl_ops, + V4L2_CID_EXPOSURE, + OG0VE1B_EXPOSURE_MIN, + exposure_max, + OG0VE1B_EXPOSURE_STEP, + OG0VE1B_EXPOSURE_DEFAULT); v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &og0ve1b_ctrl_ops, V4L2_CID_TEST_PATTERN, From 31e5191aa11931b53e1242acef4f4375f00ca523 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 30 Dec 2025 19:22:02 +0200 Subject: [PATCH 167/307] media: ccs: Fix setting initial sub-device state Fix setting sub-device state for non-source sub-devices. Fixes: 5755be5f15d9 ("media: v4l2-subdev: Rename .init_cfg() operation to .init_state()") Cc: stable@vger.kernel.org # for v6.8 and later Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index c7b46d0fba49..3fd9ceaead21 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -2940,6 +2940,8 @@ static void ccs_cleanup(struct ccs_sensor *sensor) ccs_free_controls(sensor); } +static const struct v4l2_subdev_internal_ops ccs_internal_ops; + static int ccs_init_subdev(struct ccs_sensor *sensor, struct ccs_subdev *ssd, const char *name, unsigned short num_pads, u32 function, @@ -2952,8 +2954,10 @@ static int ccs_init_subdev(struct ccs_sensor *sensor, if (!ssd) return 0; - if (ssd != sensor->src) + if (ssd != sensor->src) { v4l2_subdev_init(&ssd->sd, &ccs_ops); + ssd->sd.internal_ops = &ccs_internal_ops; + } ssd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ssd->sd.entity.function = function; @@ -3062,6 +3066,10 @@ static const struct media_entity_operations ccs_entity_ops = { .link_validate = v4l2_subdev_link_validate, }; +static const struct v4l2_subdev_internal_ops ccs_internal_ops = { + .init_state = ccs_init_state, +}; + static const struct v4l2_subdev_internal_ops ccs_internal_src_ops = { .init_state = ccs_init_state, .registered = ccs_registered, From 7d2909830e69257843d8def3ea9f839891c118e3 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 29 Sep 2023 14:28:57 +0300 Subject: [PATCH 168/307] media: ccs: Use {enable,disable}_streams operations Switch from s_stream() video op to enable_streams() and disable_streams() pad operations. They are preferred and required for streams support. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 81 ++++++++++++++------------------ 1 file changed, 35 insertions(+), 46 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 3fd9ceaead21..623fc9770205 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -1709,7 +1709,7 @@ static int ccs_power_off(struct device *dev) } /* ----------------------------------------------------------------------------- - * Video stream management + * V4L2 subdev video operations */ static int ccs_pm_get_init(struct ccs_sensor *sensor) @@ -1733,11 +1733,11 @@ static int ccs_pm_get_init(struct ccs_sensor *sensor) sensor->handler_setup_needed = false; /* Restore V4L2 controls to the previously suspended device */ - rval = v4l2_ctrl_handler_setup(&sensor->pixel_array->ctrl_handler); + rval = __v4l2_ctrl_handler_setup(&sensor->pixel_array->ctrl_handler); if (rval) goto error; - rval = v4l2_ctrl_handler_setup(&sensor->src->ctrl_handler); + rval = __v4l2_ctrl_handler_setup(&sensor->src->ctrl_handler); if (rval) goto error; @@ -1749,13 +1749,21 @@ error: return rval; } -static int ccs_start_streaming(struct ccs_sensor *sensor) +static int ccs_enable_streams(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) { + struct ccs_sensor *sensor = to_ccs_sensor(subdev); struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); unsigned int binning_mode; int rval; - mutex_lock(&sensor->mutex); + if (pad != CCS_PAD_SRC) + return -EINVAL; + + rval = ccs_pm_get_init(sensor); + if (rval) + return rval; rval = ccs_write(sensor, CSI_DATA_FORMAT, (sensor->csi_format->width << 8) | @@ -1874,62 +1882,39 @@ static int ccs_start_streaming(struct ccs_sensor *sensor) rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_STREAMING); + sensor->streaming = true; + + return 0; + err_pm_put: - mutex_unlock(&sensor->mutex); + pm_runtime_put_autosuspend(&client->dev); return rval; } -static int ccs_stop_streaming(struct ccs_sensor *sensor) -{ - struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); - int rval; - - mutex_lock(&sensor->mutex); - rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_SOFTWARE_STANDBY); - if (rval) - goto out; - - rval = ccs_call_quirk(sensor, post_streamoff); - if (rval) - dev_err(&client->dev, "post_streamoff quirks failed\n"); - -out: - mutex_unlock(&sensor->mutex); - return rval; -} - -/* ----------------------------------------------------------------------------- - * V4L2 subdev video operations - */ - -static int ccs_set_stream(struct v4l2_subdev *subdev, int enable) +static int ccs_disable_streams(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) { struct ccs_sensor *sensor = to_ccs_sensor(subdev); struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); int rval; - if (!enable) { - ccs_stop_streaming(sensor); - sensor->streaming = false; - pm_runtime_put_autosuspend(&client->dev); + if (pad != CCS_PAD_SRC) + return -EINVAL; - return 0; - } - - rval = ccs_pm_get_init(sensor); + rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_SOFTWARE_STANDBY); if (rval) return rval; - sensor->streaming = true; + rval = ccs_call_quirk(sensor, post_streamoff); + if (rval) + dev_err(&client->dev, "post_streamoff quirks failed\n"); - rval = ccs_start_streaming(sensor); - if (rval < 0) { - sensor->streaming = false; - pm_runtime_put_autosuspend(&client->dev); - } + sensor->streaming = false; + pm_runtime_put_autosuspend(&client->dev); - return rval; + return 0; } static int ccs_pre_streamon(struct v4l2_subdev *subdev, u32 flags) @@ -1955,7 +1940,9 @@ static int ccs_pre_streamon(struct v4l2_subdev *subdev, u32 flags) } } + mutex_lock(&sensor->mutex); rval = ccs_pm_get_init(sensor); + mutex_unlock(&sensor->mutex); if (rval) return rval; @@ -3038,7 +3025,7 @@ static int ccs_init_state(struct v4l2_subdev *sd, } static const struct v4l2_subdev_video_ops ccs_video_ops = { - .s_stream = ccs_set_stream, + .s_stream = v4l2_subdev_s_stream_helper, .pre_streamon = ccs_pre_streamon, .post_streamoff = ccs_post_streamoff, }; @@ -3049,6 +3036,8 @@ static const struct v4l2_subdev_pad_ops ccs_pad_ops = { .set_fmt = ccs_set_format, .get_selection = ccs_get_selection, .set_selection = ccs_set_selection, + .enable_streams = ccs_enable_streams, + .disable_streams = ccs_disable_streams, }; static const struct v4l2_subdev_sensor_ops ccs_sensor_ops = { From 24b2208053d2262f554edc3fc721f1fdbac8ec7a Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 3 Oct 2023 12:46:19 +0300 Subject: [PATCH 169/307] media: ccs: Track streaming state With enable_streams and disable_streams, the driver for a device where streams are not independently started and stopped needs to maintain state information on streams that have been requested to be started. Do that now. In the future, a helper function in the framework is a desirable way to do this instead. Signed-off-by: Sakari Ailus Reviewed-by: Julien Massot Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 13 ++++++++++--- drivers/media/i2c/ccs/ccs.h | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 623fc9770205..a04763adad91 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -1761,6 +1761,11 @@ static int ccs_enable_streams(struct v4l2_subdev *subdev, if (pad != CCS_PAD_SRC) return -EINVAL; + if (sensor->streaming) { + sensor->streaming |= streams_mask; + return 0; + } + rval = ccs_pm_get_init(sensor); if (rval) return rval; @@ -1882,7 +1887,7 @@ static int ccs_enable_streams(struct v4l2_subdev *subdev, rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_STREAMING); - sensor->streaming = true; + sensor->streaming |= streams_mask; return 0; @@ -1903,6 +1908,10 @@ static int ccs_disable_streams(struct v4l2_subdev *subdev, if (pad != CCS_PAD_SRC) return -EINVAL; + sensor->streaming &= ~streams_mask; + if (sensor->streaming) + return 0; + rval = ccs_write(sensor, MODE_SELECT, CCS_MODE_SELECT_SOFTWARE_STANDBY); if (rval) return rval; @@ -1911,7 +1920,6 @@ static int ccs_disable_streams(struct v4l2_subdev *subdev, if (rval) dev_err(&client->dev, "post_streamoff quirks failed\n"); - sensor->streaming = false; pm_runtime_put_autosuspend(&client->dev); return 0; @@ -3522,7 +3530,6 @@ static int ccs_probe(struct i2c_client *client) goto out_cleanup; } - sensor->streaming = false; sensor->dev_init_done = true; sensor->handler_setup_needed = true; diff --git a/drivers/media/i2c/ccs/ccs.h b/drivers/media/i2c/ccs/ccs.h index 0726c4687f0f..518482758da6 100644 --- a/drivers/media/i2c/ccs/ccs.h +++ b/drivers/media/i2c/ccs/ccs.h @@ -238,7 +238,7 @@ struct ccs_sensor { u16 image_start; /* image data start line */ u16 visible_pixel_start; /* start pixel of the visible image */ - bool streaming; + u8 streaming; bool dev_init_done; bool handler_setup_needed; u8 compressed_min_bpp; From 290fb5a1b4209d3bf1ee95f3990dda08a9850177 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 5 Sep 2016 14:20:05 +0300 Subject: [PATCH 170/307] media: ccs: Support frame descriptors Provide information on the frame layout using frame descriptors. Signed-off-by: Sakari Ailus Reviewed-by: Julien Massot Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 74 ++++++++++++++++++++++++++++++-- drivers/media/i2c/ccs/ccs.h | 4 ++ 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index a04763adad91..d7038f5b3689 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -245,6 +246,33 @@ out_err: return ret; } +static u8 ccs_mipi_csi2_data_type(unsigned int bpp) +{ + switch (bpp) { + case 6: + return MIPI_CSI2_DT_RAW6; + case 7: + return MIPI_CSI2_DT_RAW7; + case 8: + return MIPI_CSI2_DT_RAW8; + case 10: + return MIPI_CSI2_DT_RAW10; + case 12: + return MIPI_CSI2_DT_RAW12; + case 14: + return MIPI_CSI2_DT_RAW14; + case 16: + return MIPI_CSI2_DT_RAW16; + case 20: + return MIPI_CSI2_DT_RAW20; + case 24: + return MIPI_CSI2_DT_RAW24; + default: + WARN_ON(1); + return 0; + } +} + static int ccs_read_frame_fmt(struct ccs_sensor *sensor) { struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); @@ -2074,10 +2102,11 @@ static void ccs_get_crop_compose(struct v4l2_subdev *subdev, if (crops) for (i = 0; i < subdev->entity.num_pads; i++) crops[i] = - v4l2_subdev_state_get_crop(sd_state, i); + v4l2_subdev_state_get_crop(sd_state, i, + CCS_STREAM_PIXEL); if (comps) - *comps = v4l2_subdev_state_get_compose(sd_state, - ssd->sink_pad); + *comps = v4l2_subdev_state_get_compose(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); } /* Changes require propagation only on sink pad. */ @@ -2110,7 +2139,8 @@ static void ccs_propagate(struct v4l2_subdev *subdev, fallthrough; case V4L2_SEL_TGT_COMPOSE: *crops[CCS_PAD_SRC] = *comp; - fmt = v4l2_subdev_state_get_format(sd_state, CCS_PAD_SRC); + fmt = v4l2_subdev_state_get_format(sd_state, CCS_PAD_SRC, + CCS_STREAM_PIXEL); fmt->width = comp->width; fmt->height = comp->height; if (which == V4L2_SUBDEV_FORMAT_ACTIVE && ssd == sensor->src) @@ -2625,6 +2655,41 @@ static int ccs_set_selection(struct v4l2_subdev *subdev, return ret; } +static int ccs_get_frame_desc(struct v4l2_subdev *subdev, unsigned int pad, + struct v4l2_mbus_frame_desc *desc) +{ + struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct v4l2_mbus_frame_desc_entry *entry = desc->entry; + struct v4l2_subdev_state *sd_state; + + switch (sensor->hwcfg.csi_signalling_mode) { + case CCS_CSI_SIGNALING_MODE_CSI_2_DPHY: + case CCS_CSI_SIGNALING_MODE_CSI_2_CPHY: + desc->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + break; + default: + /* FIXME: CCP2 support */ + return -EINVAL; + } + + sd_state = v4l2_subdev_lock_and_get_active_state(subdev); + if (!sd_state) + return -EINVAL; + + entry->pixelcode = sensor->csi_format->code; + entry->stream = CCS_STREAM_PIXEL; + entry->bus.csi2.dt = + sensor->csi_format->width == sensor->csi_format->compressed ? + ccs_mipi_csi2_data_type(sensor->csi_format->width) : + CCS_DEFAULT_COMPRESSED_DT; + entry++; + desc->num_entries++; + + v4l2_subdev_unlock_state(sd_state); + + return 0; +} + static int ccs_get_skip_frames(struct v4l2_subdev *subdev, u32 *frames) { struct ccs_sensor *sensor = to_ccs_sensor(subdev); @@ -3046,6 +3111,7 @@ static const struct v4l2_subdev_pad_ops ccs_pad_ops = { .set_selection = ccs_set_selection, .enable_streams = ccs_enable_streams, .disable_streams = ccs_disable_streams, + .get_frame_desc = ccs_get_frame_desc, }; static const struct v4l2_subdev_sensor_ops ccs_sensor_ops = { diff --git a/drivers/media/i2c/ccs/ccs.h b/drivers/media/i2c/ccs/ccs.h index 518482758da6..0bdb8cd3accb 100644 --- a/drivers/media/i2c/ccs/ccs.h +++ b/drivers/media/i2c/ccs/ccs.h @@ -48,6 +48,8 @@ #define CCS_COLOUR_COMPONENTS 4 +#define CCS_DEFAULT_COMPRESSED_DT MIPI_CSI2_DT_USER_DEFINED(0) + #define SMIAPP_NAME "smiapp" #define CCS_NAME "ccs" @@ -177,6 +179,8 @@ struct ccs_csi_data_format { #define CCS_PAD_SRC 1 #define CCS_PADS 2 +#define CCS_STREAM_PIXEL 0 + struct ccs_binning_subtype { u8 horizontal:4; u8 vertical:4; From 1a84924258b9554ad5b857d2729bb2f553b8fed1 Mon Sep 17 00:00:00 2001 From: Elgin Perumbilly Date: Mon, 5 Jan 2026 17:04:30 +0530 Subject: [PATCH 171/307] media: i2c: ov2735: request reset GPIO as initially asserted The reset GPIO must be requested in the asserted (HIGH) state to keep the sensor in standby during probe and power sequencing. Signed-off-by: Elgin Perumbilly Acked-by: Tarang Raval Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov2735.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov2735.c b/drivers/media/i2c/ov2735.c index b96600204141..dcb1add1fd9f 100644 --- a/drivers/media/i2c/ov2735.c +++ b/drivers/media/i2c/ov2735.c @@ -993,7 +993,7 @@ static int ov2735_probe(struct i2c_client *client) "failed to parse endpoint configuration\n"); ov2735->reset_gpio = devm_gpiod_get_optional(ov2735->dev, - "reset", GPIOD_OUT_LOW); + "reset", GPIOD_OUT_HIGH); if (IS_ERR(ov2735->reset_gpio)) return dev_err_probe(ov2735->dev, PTR_ERR(ov2735->reset_gpio), "failed to get reset GPIO\n"); From d57348c1bb8df069f992b3478fea642045712212 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:02:58 +0100 Subject: [PATCH 172/307] media: aptina-pll: Debug log p1 min and max values Make aptina_pll_calculate() debug log the calculated p1 min and max values, this makes it easier to see how the m, n and p1 values were chosen. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/aptina-pll.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/i2c/aptina-pll.c b/drivers/media/i2c/aptina-pll.c index b1f89bbf9d47..cd2ed4583c97 100644 --- a/drivers/media/i2c/aptina-pll.c +++ b/drivers/media/i2c/aptina-pll.c @@ -129,6 +129,8 @@ int aptina_pll_calculate(struct device *dev, p1_max = min(limits->p1_max, limits->out_clock_max * div / (pll->ext_clock * pll->m)); + dev_dbg(dev, "pll: p1 min %u max %u\n", p1_min, p1_max); + for (p1 = p1_max & ~1; p1 >= p1_min; p1 -= 2) { unsigned int mf_inc = p1 / gcd(div, p1); unsigned int mf_high; From 1df58b9ddaac468154083af6e1d7ddaf8769e964 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:02:59 +0100 Subject: [PATCH 173/307] media: mt9m114: Use aptina-PLL helper to get PLL values Before this change the driver used hardcoded PLL m, n and p values to achieve a 48MHz pixclock when used with an external clock with a frequency of 24 MHz. Use aptina_pll_calculate() to allow the driver to work with different external clock frequencies. The m, n, and p values will be unchanged with a 24 MHz extclk and this has also been tested with a 19.2 MHz clock where m gets increased from 32 to 40. Suggested-by: Laurent Pinchart Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede [Sakari Ailus: Fix capitalisation of "MHz".] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/Kconfig | 1 + drivers/media/i2c/mt9m114.c | 64 +++++++++++++++++++++++++++---------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 3121c0d947cd..371a08a53f7a 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -305,6 +305,7 @@ config VIDEO_MT9M111 config VIDEO_MT9M114 tristate "onsemi MT9M114 sensor support" select V4L2_CCI_I2C + select VIDEO_APTINA_PLL help This is a Video4Linux2 sensor-level driver for the onsemi MT9M114 camera. diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 51ebbe7ae996..2867a0ce80e0 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -32,6 +32,8 @@ #include #include +#include "aptina-pll.h" + /* Sysctl registers */ #define MT9M114_CHIP_ID CCI_REG16(0x0000) #define MT9M114_COMMAND_REGISTER CCI_REG16(0x0080) @@ -267,9 +269,9 @@ #define MT9M114_CAM_SYSCTL_PLL_ENABLE_VALUE BIT(0) #define MT9M114_CAM_SYSCTL_PLL_DISABLE_VALUE 0x00 #define MT9M114_CAM_SYSCTL_PLL_DIVIDER_M_N CCI_REG16(0xc980) -#define MT9M114_CAM_SYSCTL_PLL_DIVIDER_VALUE(m, n) (((n) << 8) | (m)) +#define MT9M114_CAM_SYSCTL_PLL_DIVIDER_VALUE(m, n) ((((n) - 1) << 8) | (m)) #define MT9M114_CAM_SYSCTL_PLL_DIVIDER_P CCI_REG16(0xc982) -#define MT9M114_CAM_SYSCTL_PLL_DIVIDER_P_VALUE(p) ((p) << 8) +#define MT9M114_CAM_SYSCTL_PLL_DIVIDER_P_VALUE(p) (((p) - 1) << 8) #define MT9M114_CAM_PORT_OUTPUT_CONTROL CCI_REG16(0xc984) #define MT9M114_CAM_PORT_PORT_SELECT_PARALLEL (0 << 0) #define MT9M114_CAM_PORT_PORT_SELECT_MIPI (1 << 0) @@ -330,7 +332,7 @@ * minimum values that have been seen in register lists are 303 and 38, use * them. * - * Set the default to achieve 1280x960 at 30fps. + * Set the default to achieve 1280x960 at 30fps with a 48 MHz pixclock. */ #define MT9M114_MIN_HBLANK 303 #define MT9M114_MIN_VBLANK 38 @@ -340,6 +342,8 @@ #define MT9M114_DEF_FRAME_RATE 30 #define MT9M114_MAX_FRAME_RATE 120 +#define MT9M114_DEF_PIXCLOCK 48000000 + #define MT9M114_PIXEL_ARRAY_WIDTH 1296U #define MT9M114_PIXEL_ARRAY_HEIGHT 976U @@ -384,11 +388,7 @@ struct mt9m114 { struct v4l2_fwnode_endpoint bus_cfg; bool bypass_pll; - struct { - unsigned int m; - unsigned int n; - unsigned int p; - } pll; + struct aptina_pll pll; unsigned int pixrate; bool streaming; @@ -758,7 +758,7 @@ static int mt9m114_initialize(struct mt9m114 *sensor) sensor->pll.n), &ret); cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_DIVIDER_P, - MT9M114_CAM_SYSCTL_PLL_DIVIDER_P_VALUE(sensor->pll.p), + MT9M114_CAM_SYSCTL_PLL_DIVIDER_P_VALUE(sensor->pll.p1), &ret); } @@ -2281,14 +2281,39 @@ static int mt9m114_verify_link_frequency(struct mt9m114 *sensor, return 0; } +/* + * Based on the docs the PLL is believed to have the following setup: + * + * +-----+ +-----+ +-----+ +-----+ +-----+ + * Fin --> | / N | --> | x M | --> | x 2 | --> | / P | --> | / 2 | --> + * +-----+ +-----+ +-----+ +-----+ +-----+ + * fBit fWord fSensor + * ext_clock int_clock out_clock pix_clock + * + * The MT9M114 docs give a max fBit rate of 768 MHz which translates to + * an out_clock_max of 384 MHz. + */ static int mt9m114_clk_init(struct mt9m114 *sensor) { + static const struct aptina_pll_limits limits = { + .ext_clock_min = 6000000, + .ext_clock_max = 54000000, + /* int_clock_* limits are not documented taken from mt9p031.c */ + .int_clock_min = 2000000, + .int_clock_max = 13500000, + /* out_clock_min is not documented, taken from mt9p031.c */ + .out_clock_min = 180000000, + .out_clock_max = 384000000, + .pix_clock_max = 48000000, + .n_min = 1, + .n_max = 64, + .m_min = 16, + .m_max = 192, + .p1_min = 8, + .p1_max = 8, + }; unsigned int pixrate; - - /* Hardcode the PLL multiplier and dividers to default settings. */ - sensor->pll.m = 32; - sensor->pll.n = 1; - sensor->pll.p = 7; + int ret; /* * Calculate the pixel rate and link frequency. The CSI-2 bus is clocked @@ -2308,8 +2333,15 @@ static int mt9m114_clk_init(struct mt9m114 *sensor) } /* Check if the PLL configuration fits the configured link frequency. */ - pixrate = clk_get_rate(sensor->clk) * sensor->pll.m - / ((sensor->pll.n + 1) * (sensor->pll.p + 1)); + sensor->pll.ext_clock = clk_get_rate(sensor->clk); + sensor->pll.pix_clock = MT9M114_DEF_PIXCLOCK; + + ret = aptina_pll_calculate(&sensor->client->dev, &limits, &sensor->pll); + if (ret) + return ret; + + pixrate = sensor->pll.ext_clock * sensor->pll.m + / (sensor->pll.n * sensor->pll.p1); if (mt9m114_verify_link_frequency(sensor, pixrate) == 0) { sensor->pixrate = pixrate; sensor->bypass_pll = false; From 71087c9b814994541154d667d88aac9682be63db Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:00 +0100 Subject: [PATCH 174/307] media: mt9m114: Lower minimum vblank value As the comment above the defines says, the minimum values are undocumented so the lowest values seen in register lists are used. The version of the mt9m114 driver shipped together with the atomisp code uses 21 for vblank in its register lists, lower MT9M114_MIN_VBLANK accordingly. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 2867a0ce80e0..0c536d71b01f 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -329,13 +329,13 @@ /* * The minimum amount of horizontal and vertical blanking is undocumented. The - * minimum values that have been seen in register lists are 303 and 38, use + * minimum values that have been seen in register lists are 303 and 21, use * them. * * Set the default to achieve 1280x960 at 30fps with a 48 MHz pixclock. */ #define MT9M114_MIN_HBLANK 303 -#define MT9M114_MIN_VBLANK 38 +#define MT9M114_MIN_VBLANK 21 #define MT9M114_DEF_HBLANK 323 #define MT9M114_DEF_VBLANK 39 From 0fa4baded5ef5ff51d96bee4fe233ad6aa35a833 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:01 +0100 Subject: [PATCH 175/307] media: mt9m114: Fix default hblank and vblank values The current default hblank and vblank values are based on reaching 30 fps with the pixel-array outputting 1280x960, but the default format for the pixel-array source pad and the isp sink pad is 1296x976, correct the default hblank and vblank values to take this into account. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 0c536d71b01f..0076723c5aa5 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -332,12 +332,13 @@ * minimum values that have been seen in register lists are 303 and 21, use * them. * - * Set the default to achieve 1280x960 at 30fps with a 48 MHz pixclock. + * Set the default to achieve full resolution (1296x976 analog crop + * rectangle, 1280x960 output size) at 30fps with a 48 MHz pixclock. */ #define MT9M114_MIN_HBLANK 303 #define MT9M114_MIN_VBLANK 21 -#define MT9M114_DEF_HBLANK 323 -#define MT9M114_DEF_VBLANK 39 +#define MT9M114_DEF_HBLANK 307 +#define MT9M114_DEF_VBLANK 23 #define MT9M114_DEF_FRAME_RATE 30 #define MT9M114_MAX_FRAME_RATE 120 From 432d109333afd674a416a061b98264bfcd5f0515 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:02 +0100 Subject: [PATCH 176/307] media: mt9m114: Tweak default hblank and vblank for more accurate fps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PLL gets programmed to achieve a 48 MHz pixelclock, with the current vblank + hblank defaults this results in a fps of: 48000000 / ((1296 + 307) * (976 + 23) = 29.974 fps Tweak the defaults to get closer to 30 fps: 48000000 / ((1296 + 308) * (976 + 21) = 30.015 fps This improves things from being 0.026 fps too low to 0.015 fps too high. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 0076723c5aa5..a45e6709d108 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -337,8 +337,8 @@ */ #define MT9M114_MIN_HBLANK 303 #define MT9M114_MIN_VBLANK 21 -#define MT9M114_DEF_HBLANK 307 -#define MT9M114_DEF_VBLANK 23 +#define MT9M114_DEF_HBLANK 308 +#define MT9M114_DEF_VBLANK 21 #define MT9M114_DEF_FRAME_RATE 30 #define MT9M114_MAX_FRAME_RATE 120 From 84359d0a5e3afce5e3e3b6562efadff690614d5b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:03 +0100 Subject: [PATCH 177/307] media: mt9m114: Avoid a reset low spike during probe() mt9m114_probe() requests the reset GPIO in output low state: sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); and then almost immediately afterwards calls mt9m114_power_on() which does: gpiod_set_value(sensor->reset, 1); fsleep(duration); gpiod_set_value(sensor->reset, 0); which means that if the reset pin was high before this code runs that it will very briefly be driven low because of passing GPIOD_OUT_LOW when requesting the GPIO only to be driven high again possibly directly after that. Such a very brief driving low of the reset pin may put the chip in a confused state. Request the GPIO in high (reset the chip) state instead to avoid this, turning the initial gpiod_set_value() in mt9m114_power_on() into a no-op. and the fsleep() ensures that it will stay high long enough to properly reset the chip. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index a45e6709d108..910eefa915c7 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -2467,7 +2467,7 @@ static int mt9m114_probe(struct i2c_client *client) goto error_ep_free; } - sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + sensor->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(sensor->reset)) { ret = PTR_ERR(sensor->reset); dev_err_probe(dev, ret, "Failed to get reset GPIO\n"); From d6b6a23bb487958e7e1540c0cb6aaccf82f136c9 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:04 +0100 Subject: [PATCH 178/307] media: mt9m114: Put sensor in reset on power down Put the sensor back in reset on power down. Putting the sensor in reset reduces power-consumption by putting all the data / ctrl pins in High-Z mode. This helps save power on designs where the regulators may need to stay on while the sensor is powered down. This also ensures that the sensor is properly reset on power up, since now the sensor will see a reset high to low transition after the regulators have been turned on. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 910eefa915c7..ba7b3e9857c5 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -2228,6 +2228,13 @@ error_regulator: static void mt9m114_power_off(struct mt9m114 *sensor) { + unsigned int duration; + + gpiod_set_value(sensor->reset, 1); + /* Power off takes 10 clock cycles. Double it to be safe. */ + duration = DIV_ROUND_UP(2 * 10 * 1000000, clk_get_rate(sensor->clk)); + fsleep(duration); + clk_disable_unprepare(sensor->clk); regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies); } From 633deb131082f41a3467f8a77abb9c95fb390008 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:05 +0100 Subject: [PATCH 179/307] media: mt9m114: Add and use mt9m114_ifp_get_border() helper function Normally the IFP removes a 4 pixel border all around its sink format size for demosaicing. But in RAW10 mode it does not do this. Add a new mt9m114_ifp_get_border() helper function to get the border size (4 or 0) and use this where applicable instead of hardcoding a border of 4 pixels everywhere. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 62 +++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index ba7b3e9857c5..0933a5236e3d 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -851,6 +851,18 @@ static int mt9m114_configure_pa(struct mt9m114 *sensor, return ret; } +/* + * For source pad formats other then RAW10 the IFP removes a 4 pixel border from + * its sink pad format size for demosaicing. + */ +static int mt9m114_ifp_get_border(struct v4l2_subdev_state *state) +{ + const struct v4l2_mbus_framefmt *format = + v4l2_subdev_state_get_format(state, 1); + + return format->code == MEDIA_BUS_FMT_SGRBG10_1X10 ? 0 : 4; +} + static int mt9m114_configure_ifp(struct mt9m114 *sensor, struct v4l2_subdev_state *state) { @@ -858,6 +870,7 @@ static int mt9m114_configure_ifp(struct mt9m114 *sensor, const struct v4l2_mbus_framefmt *format; const struct v4l2_rect *crop; const struct v4l2_rect *compose; + unsigned int border; u64 output_format; int ret = 0; @@ -872,15 +885,18 @@ static int mt9m114_configure_ifp(struct mt9m114 *sensor, return ret; /* - * Color pipeline (IFP) cropping and scaling. Subtract 4 from the left - * and top coordinates to compensate for the lines and columns removed - * by demosaicing that are taken into account in the crop rectangle but - * not in the hardware. + * Color pipeline (IFP) cropping and scaling. The crop window registers + * apply cropping after demosaicing, which itself consumes 4 pixels on + * each side of the image. The crop rectangle exposed to userspace + * includes that demosaicing border, subtract it from the left and top + * coordinates to configure the crop window. */ + border = mt9m114_ifp_get_border(state); + cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_XOFFSET, - crop->left - 4, &ret); + crop->left - border, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_YOFFSET, - crop->top - 4, &ret); + crop->top - border, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_WIDTH, crop->width, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_HEIGHT, @@ -1865,6 +1881,7 @@ static int mt9m114_ifp_get_selection(struct v4l2_subdev *sd, { const struct v4l2_mbus_framefmt *format; const struct v4l2_rect *crop; + unsigned int border; int ret = 0; /* Crop and compose are only supported on the sink pad. */ @@ -1879,15 +1896,17 @@ static int mt9m114_ifp_get_selection(struct v4l2_subdev *sd, case V4L2_SEL_TGT_CROP_DEFAULT: case V4L2_SEL_TGT_CROP_BOUNDS: /* - * The crop default and bounds are equal to the sink - * format size minus 4 pixels on each side for demosaicing. + * Crop defaults and bounds are equal to the sink format size. + * For source pad formats other then RAW10 this gets reduced + * by 4 pixels on each side for demosaicing. */ format = v4l2_subdev_state_get_format(state, 0); + border = mt9m114_ifp_get_border(state); - sel->r.left = 4; - sel->r.top = 4; - sel->r.width = format->width - 8; - sel->r.height = format->height - 8; + sel->r.left = border; + sel->r.top = border; + sel->r.width = format->width - 2 * border; + sel->r.height = format->height - 2 * border; break; case V4L2_SEL_TGT_COMPOSE: @@ -1922,6 +1941,7 @@ static int mt9m114_ifp_set_selection(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; struct v4l2_rect *compose; + unsigned int border; if (sel->target != V4L2_SEL_TGT_CROP && sel->target != V4L2_SEL_TGT_COMPOSE) @@ -1937,21 +1957,23 @@ static int mt9m114_ifp_set_selection(struct v4l2_subdev *sd, if (sel->target == V4L2_SEL_TGT_CROP) { /* - * Clamp the crop rectangle. Demosaicing removes 4 pixels on - * each side of the image. + * Clamp the crop rectangle. For source pad formats other then + * RAW10 demosaicing removes 4 pixels on each side of the image. */ - crop->left = clamp_t(unsigned int, ALIGN(sel->r.left, 2), 4, - format->width - 4 - + border = mt9m114_ifp_get_border(state); + + crop->left = clamp_t(unsigned int, ALIGN(sel->r.left, 2), border, + format->width - border - MT9M114_SCALER_CROPPED_INPUT_WIDTH); - crop->top = clamp_t(unsigned int, ALIGN(sel->r.top, 2), 4, - format->height - 4 - + crop->top = clamp_t(unsigned int, ALIGN(sel->r.top, 2), border, + format->height - border - MT9M114_SCALER_CROPPED_INPUT_HEIGHT); crop->width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), MT9M114_SCALER_CROPPED_INPUT_WIDTH, - format->width - 4 - crop->left); + format->width - border - crop->left); crop->height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), MT9M114_SCALER_CROPPED_INPUT_HEIGHT, - format->height - 4 - crop->top); + format->height - border - crop->top); sel->r = *crop; From 5e3c0bd3f864fec9f895bd42c172f56ac4cdeb63 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:06 +0100 Subject: [PATCH 180/307] media: mt9m114: Adjust IFP selections and source format when source format changes to/from RAW10 Changing the IFP source pad format to RAW10 means disabling the scaler, which means that the crop and compose rectangles must be reset to match the sink format size with no border. And when changing the source pad format back from RAW10 to another format which require demosaicing the crop and compose rectangles must be reset to the sink format size minus a 4 pixels border all around it. Also when changing the source pad format back from RAW10 to another format the colorspace, ycbcr_enc and quantization need to be updated too. Add a new mt9m114_ifp_update_sel_and_src_fmt() helper which resets all these taking the bordersize for the new source format into account and call this helper whenever the source pad format changes to/from RAW10. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 53 +++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 0933a5236e3d..6dd8e1cbe99e 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -1840,6 +1840,41 @@ static int mt9m114_ifp_enum_frameintervals(struct v4l2_subdev *sd, return 0; } +/* + * Helper function to update IFP crop, compose rectangles and source format + * when the pixel border size changes, which requires resetting these. + */ +static void mt9m114_ifp_update_sel_and_src_fmt(struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *src_format, *sink_format; + struct v4l2_rect *crop; + unsigned int border; + + sink_format = v4l2_subdev_state_get_format(state, 0); + src_format = v4l2_subdev_state_get_format(state, 1); + crop = v4l2_subdev_state_get_crop(state, 0); + border = mt9m114_ifp_get_border(state); + + crop->left = border; + crop->top = border; + crop->width = sink_format->width - 2 * border; + crop->height = sink_format->height - 2 * border; + *v4l2_subdev_state_get_compose(state, 0) = *crop; + + src_format->width = crop->width; + src_format->height = crop->height; + + if (src_format->code == MEDIA_BUS_FMT_SGRBG10_1X10) { + src_format->colorspace = V4L2_COLORSPACE_RAW; + src_format->ycbcr_enc = V4L2_YCBCR_ENC_601; + src_format->quantization = V4L2_QUANTIZATION_FULL_RANGE; + } else { + src_format->colorspace = V4L2_COLORSPACE_SRGB; + src_format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + src_format->quantization = V4L2_QUANTIZATION_DEFAULT; + } +} + static int mt9m114_ifp_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) @@ -1863,11 +1898,19 @@ static int mt9m114_ifp_set_fmt(struct v4l2_subdev *sd, /* Only the media bus code can be changed on the source pad. */ info = mt9m114_format_info(sensor, 1, fmt->format.code); - format->code = info->code; - - /* If the output format is RAW10, bypass the scaler. */ - if (format->code == MEDIA_BUS_FMT_SGRBG10_1X10) - *format = *v4l2_subdev_state_get_format(state, 0); + /* + * If the output format changes from/to RAW10 then the crop + * rectangle needs to be adjusted to add / remove the 4 pixel + * border used for demosaicing. And these changes then need to + * be propagated to the compose rectangle and source format. + */ + if ((format->code == MEDIA_BUS_FMT_SGRBG10_1X10) != + (info->code == MEDIA_BUS_FMT_SGRBG10_1X10)) { + format->code = info->code; + mt9m114_ifp_update_sel_and_src_fmt(state); + } else { + format->code = info->code; + } } fmt->format = *format; From df0090002c56b154ac543167fa92ba7b77f2b09f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:07 +0100 Subject: [PATCH 181/307] media: mt9m114: Update source pad selection and format when sink pad format changes Call mt9m114_ifp_update_sel_and_src_fmt() on sink pad format changes to propagate these downstream. This is necessary in 2 different scenarios: 1. When passing through RAW10 bypassing the scaler then any sink pad format changes must be propagated to the crop/compose selections and to the source pad format. 2. When the scaler is active, then the crop-rectangle cannot be bigger then the sink pad format minus a 4 pixel border all around. If the sink format change reduces the size then things also needs to be propagated downstream. Rather then adding extra code to check for these conditions, simply always propagate sink pad format changes downstream. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 6dd8e1cbe99e..c107e9f26618 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -1892,6 +1892,9 @@ static int mt9m114_ifp_set_fmt(struct v4l2_subdev *sd, format->height = clamp(ALIGN(fmt->format.height, 8), MT9M114_PIXEL_ARRAY_MIN_OUTPUT_HEIGHT, MT9M114_PIXEL_ARRAY_HEIGHT); + + /* Propagate changes downstream. */ + mt9m114_ifp_update_sel_and_src_fmt(state); } else { const struct mt9m114_format_info *info; From ceb485b3378bf59d3ed23726e9effff9c2d6792d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:08 +0100 Subject: [PATCH 182/307] media: mt9m114: Don't allow changing the IFP crop/compose selections when bypassing the scaler The scaler is bypassed when the ISP source/output pad's pixel-format is set to MEDIA_BUS_FMT_SGRBG10_1X10. Don't allow changing the IFP crop and/or compose selections when in this mode. Instead of returning -EINVAL simply return the current (noop) crop and compose rectangles. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index c107e9f26618..9743d83e9d0d 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -1984,7 +1984,7 @@ static int mt9m114_ifp_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { - struct v4l2_mbus_framefmt *format; + struct v4l2_mbus_framefmt *format, *src_format; struct v4l2_rect *crop; struct v4l2_rect *compose; unsigned int border; @@ -1997,8 +1997,16 @@ static int mt9m114_ifp_set_selection(struct v4l2_subdev *sd, if (sel->pad != 0) return -EINVAL; - format = v4l2_subdev_state_get_format(state, 0); crop = v4l2_subdev_state_get_crop(state, 0); + + /* Crop and compose cannot be changed when bypassing the scaler. */ + src_format = v4l2_subdev_state_get_format(state, 1); + if (src_format->code == MEDIA_BUS_FMT_SGRBG10_1X10) { + sel->r = *crop; + return 0; + } + + format = v4l2_subdev_state_get_format(state, 0); compose = v4l2_subdev_state_get_compose(state, 0); if (sel->target == V4L2_SEL_TGT_CROP) { @@ -2043,9 +2051,8 @@ static int mt9m114_ifp_set_selection(struct v4l2_subdev *sd, } /* Propagate the compose rectangle to the source format. */ - format = v4l2_subdev_state_get_format(state, 1); - format->width = compose->width; - format->height = compose->height; + src_format->width = compose->width; + src_format->height = compose->height; return 0; } From 46c96eec9a763813e4592d72ecb38a6a33113258 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:09 +0100 Subject: [PATCH 183/307] media: mt9m114: Drop start-, stop-streaming sequence from initialize Drop the start-, stop-streaming sequence from initialize. When streaming is started with a runtime-suspended sensor, mt9m114_start_streaming() will runtime-resume the sensor which calls mt9m114_initialize() immediately followed by calling mt9m114_set_state(ENTER_CONFIG_CHANGE). This results in the following state changes in quick succession: mt9m114_set_state(ENTER_CONFIG_CHANGE) -> transitions to STREAMING mt9m114_set_state(ENTER_SUSPEND) -> transitions to SUSPENDED mt9m114_set_state(ENTER_CONFIG_CHANGE) -> transitions to STREAMING these quick state changes confuses the CSI receiver on atomisp devices causing streaming to not work. Drop the state changes from mt9m114_initialize() and move the mt9m114_initialize() call to mt9m114_start_streaming() so that only a single mt9m114_set_state(ENTER_CONFIG_CHANGE) call is made when streaming is started with a runtime-suspend sensor. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 9743d83e9d0d..4be81fe3c827 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -789,14 +789,6 @@ static int mt9m114_initialize(struct mt9m114 *sensor) if (ret < 0) return ret; - ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_CONFIG_CHANGE); - if (ret < 0) - return ret; - - ret = mt9m114_set_state(sensor, MT9M114_SYS_STATE_ENTER_SUSPEND); - if (ret < 0) - return ret; - return 0; } @@ -967,6 +959,10 @@ static int mt9m114_start_streaming(struct mt9m114 *sensor, if (ret) return ret; + ret = mt9m114_initialize(sensor); + if (ret) + goto error; + ret = mt9m114_configure_ifp(sensor, ifp_state); if (ret) goto error; @@ -2318,19 +2314,8 @@ static int __maybe_unused mt9m114_runtime_resume(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); struct mt9m114 *sensor = ifp_to_mt9m114(sd); - int ret; - ret = mt9m114_power_on(sensor); - if (ret) - return ret; - - ret = mt9m114_initialize(sensor); - if (ret) { - mt9m114_power_off(sensor); - return ret; - } - - return 0; + return mt9m114_power_on(sensor); } static int __maybe_unused mt9m114_runtime_suspend(struct device *dev) @@ -2574,8 +2559,8 @@ static int mt9m114_probe(struct i2c_client *client) /* * Identify the sensor. The driver supports runtime PM, but needs to * work when runtime PM is disabled in the kernel. To that end, power - * the sensor on manually here, and initialize it after identification - * to reach the same state as if resumed through runtime PM. + * the sensor on manually here to reach the same state as if resumed + * through runtime PM. */ ret = mt9m114_power_on(sensor); if (ret < 0) { @@ -2587,10 +2572,6 @@ static int mt9m114_probe(struct i2c_client *client) if (ret < 0) goto error_power_off; - ret = mt9m114_initialize(sensor); - if (ret < 0) - goto error_power_off; - /* * Enable runtime PM with autosuspend. As the device has been powered * manually, mark it as active, and increase the usage count without From 437e1f6a960035166495a5117aacbc596115eeb6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:10 +0100 Subject: [PATCH 184/307] media: mt9m114: Return -EPROBE_DEFER if no endpoint is found With IPU# bridges, endpoints may only be created when the IPU bridge is initialized. This may happen after the sensor driver's first probe(). Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 4be81fe3c827..2c41169c0212 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -2460,11 +2460,17 @@ static int mt9m114_parse_dt(struct mt9m114 *sensor) struct fwnode_handle *ep; int ret; + /* + * On ACPI systems the fwnode graph can be initialized by a bridge + * driver, which may not have probed yet. Wait for this. + * + * TODO: Return an error once bridge driver code will have moved + * to the ACPI core. + */ ep = fwnode_graph_get_next_endpoint(fwnode, NULL); - if (!ep) { - dev_err(&sensor->client->dev, "No endpoint found\n"); - return -EINVAL; - } + if (!ep) + return dev_err_probe(&sensor->client->dev, -EPROBE_DEFER, + "waiting for fwnode graph endpoint\n"); sensor->bus_cfg.bus_type = V4L2_MBUS_UNKNOWN; ret = v4l2_fwnode_endpoint_alloc_parse(ep, &sensor->bus_cfg); From ac9f4a5e49a0e276a951983cd2c9c050e2f63799 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 30 Dec 2025 18:03:11 +0100 Subject: [PATCH 185/307] media: mt9m114: Add ACPI enumeration support Add support for the mt9m114 sensor being enumerated through ACPI using the INT33F0 HID as found on the Asus T100TA. Reviewed-by: Laurent Pinchart Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 2c41169c0212..16b0ace15813 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -2652,11 +2652,18 @@ static const struct of_device_id mt9m114_of_ids[] = { }; MODULE_DEVICE_TABLE(of, mt9m114_of_ids); +static const struct acpi_device_id mt9m114_acpi_ids[] = { + { "INT33F0" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(acpi, mt9m114_acpi_ids); + static struct i2c_driver mt9m114_driver = { .driver = { .name = "mt9m114", .pm = &mt9m114_pm_ops, .of_match_table = mt9m114_of_ids, + .acpi_match_table = mt9m114_acpi_ids, }, .probe = mt9m114_probe, .remove = mt9m114_remove, From 973e42fd5d2b397bff34f0c249014902dbf65912 Mon Sep 17 00:00:00 2001 From: Xiaolei Wang Date: Fri, 5 Dec 2025 15:19:18 +0800 Subject: [PATCH 186/307] media: i2c: ov5647: use our own mutex for the ctrl lock __v4l2_ctrl_handler_setup() and __v4l2_ctrl_modify_range() contains an assertion to verify that the v4l2_ctrl_handler::lock is held, as it should only be called when the lock has already been acquired. Therefore use our own mutex for the ctrl lock, otherwise a warning will be reported. Fixes: 4974c2f19fd8 ("media: ov5647: Support gain, exposure and AWB controls") Cc: stable@vger.kernel.org Signed-off-by: Xiaolei Wang [Sakari Ailus: Fix a minor conflict.] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 1041732104fa..6285e234962e 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -1248,6 +1248,8 @@ static int ov5647_init_controls(struct ov5647 *sensor) v4l2_ctrl_handler_init(&sensor->ctrls, 14); + sensor->ctrls.lock = &sensor->lock; + v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, V4L2_CID_AUTOGAIN, 0, 1, 1, 0); From 401aec35ac7bd04b4018a519257b945abb88e26c Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 10 Dec 2025 07:53:43 +0000 Subject: [PATCH 187/307] media: dw9714: Fix powerup sequence We have experienced seen multiple I2C errors while doing stress test on the module: dw9714 i2c-PRP0001:01: dw9714_vcm_resume I2C failure: -5 dw9714 i2c-PRP0001:01: I2C write fail Inspecting the powerup sequence we found that it does not match the documentation at: https://blog.arducam.com/downloads/DW9714A-DONGWOON(Autofocus_motor_manual).pdf """ (2) DW9714A requires waiting time of 12ms after power on. During this waiting time, the offset calibration of internal amplifier is operating for minimization of output offset current . """ This patch increases the powerup delay to follow the documentation. Fixes: 9d00ccabfbb5 ("media: i2c: dw9714: Fix occasional probe errors") Signed-off-by: Ricardo Ribalda Reviewed-by: Hans de Goede Tested-by: Neil Sun Reported-by: Naomi Huang Cc: stable@vger.kernel.org Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/dw9714.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/dw9714.c b/drivers/media/i2c/dw9714.c index 1e7ad355a388..3288de539452 100644 --- a/drivers/media/i2c/dw9714.c +++ b/drivers/media/i2c/dw9714.c @@ -149,7 +149,7 @@ static int dw9714_power_up(struct dw9714_device *dw9714_dev) gpiod_set_value_cansleep(dw9714_dev->powerdown_gpio, 0); - usleep_range(1000, 2000); + usleep_range(12000, 14000); return 0; } From 3e0fcc91277d5af114a58aaa68f34b44e8d8a411 Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Tue, 23 Dec 2025 15:22:58 +0800 Subject: [PATCH 188/307] media: ipu6: Fix typo and wrong constant in ipu6-mmu.c Fix two coding errors in ipu6-mmu.c: 1. Fix syntax error in page_table_dump() where the closing parenthesis and semicolon were swapped in the TBL_PHYS_ADDR macro call. 2. Fix incorrect loop bound in alloc_l2_pt(). When initializing L2 page table entries, the loop was incorrectly using ISP_L1PT_PTES instead of ISP_L2PT_PTES. Fixes: 9163d83573e4 ("media: intel/ipu6: add IPU6 DMA mapping API and MMU table") Cc: stable@vger.kernel.org Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-mmu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c index 6d1c0b90169d..85cc6d5b4dd1 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.c +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c @@ -102,7 +102,7 @@ static void page_table_dump(struct ipu6_mmu_info *mmu_info) if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) continue; - l2_phys = TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx];) + l2_phys = TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]); dev_dbg(mmu_info->dev, "l1 entry %u; iovas 0x%8.8x-0x%8.8x, at %pap\n", l1_idx, iova, iova + ISP_PAGE_SIZE, &l2_phys); @@ -248,7 +248,7 @@ static u32 *alloc_l2_pt(struct ipu6_mmu_info *mmu_info) dev_dbg(mmu_info->dev, "alloc_l2: get_zeroed_page() = %p\n", pt); - for (i = 0; i < ISP_L1PT_PTES; i++) + for (i = 0; i < ISP_L2PT_PTES; i++) pt[i] = mmu_info->dummy_page_pteval; return pt; From 6099f78e4c9223f4de4169d2fd1cded01279da1a Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Tue, 23 Dec 2025 15:22:59 +0800 Subject: [PATCH 189/307] media: ipu6: Fix RPM reference leak in probe error paths Several error paths in ipu6_pci_probe() were jumping directly to out_ipu6_bus_del_devices without releasing the runtime PM reference. Add pm_runtime_put_sync() before cleaning up other resources. Cc: Stable@vger.kernel.org Fixes: 25fedc021985 ("media: intel/ipu6: add Intel IPU6 PCI device driver") Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 1f4f20b9c94d..a2768f44017a 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -630,21 +630,21 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (ret) { dev_err_probe(&isp->pdev->dev, ret, "Failed to set MMU hardware\n"); - goto out_ipu6_bus_del_devices; + goto out_ipu6_rpm_put; } ret = ipu6_buttress_map_fw_image(isp->psys, isp->cpd_fw, &isp->psys->fw_sgt); if (ret) { dev_err_probe(&isp->pdev->dev, ret, "failed to map fw image\n"); - goto out_ipu6_bus_del_devices; + goto out_ipu6_rpm_put; } ret = ipu6_cpd_create_pkg_dir(isp->psys, isp->cpd_fw->data); if (ret) { dev_err_probe(&isp->pdev->dev, ret, "failed to create pkg dir\n"); - goto out_ipu6_bus_del_devices; + goto out_ipu6_rpm_put; } ret = devm_request_threaded_irq(dev, pdev->irq, ipu6_buttress_isr, @@ -652,7 +652,7 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) IRQF_SHARED, IPU6_NAME, isp); if (ret) { dev_err_probe(dev, ret, "Requesting irq failed\n"); - goto out_ipu6_bus_del_devices; + goto out_ipu6_rpm_put; } ret = ipu6_buttress_authenticate(isp); @@ -683,6 +683,8 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) out_free_irq: devm_free_irq(dev, pdev->irq, isp); +out_ipu6_rpm_put: + pm_runtime_put_sync(&isp->psys->auxdev.dev); out_ipu6_bus_del_devices: if (isp->psys) { ipu6_cpd_free_pkg_dir(isp->psys); From 9ad65684b9285c5d66fb417d50e91a25ef8c994d Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Tue, 23 Dec 2025 15:23:00 +0800 Subject: [PATCH 190/307] media: staging/ipu7: Ignore interrupts when device is suspended IPU7 devices have shared interrupts with others. In some case when IPU7 device is suspended, driver get unexpected interrupt and invalid irq status 0xffffffff from ISR_STATUS and PB LOCAL_STATUS registers as interrupt is triggered from other device on shared irq line. In order to avoid this issue use pm_runtime_get_if_active() to check if IPU7 device is resumed, ignore the invalid irq status and use synchronize_irq() in suspend. Cc: Stable@vger.kernel.org Fixes: b7fe4c0019b1 ("media: staging/ipu7: add Intel IPU7 PCI device driver") Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/ipu7-buttress.c | 17 ++++++++++++++++- drivers/staging/media/ipu7/ipu7.c | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/ipu7/ipu7-buttress.c b/drivers/staging/media/ipu7/ipu7-buttress.c index e5707f5e300b..40c6c8473357 100644 --- a/drivers/staging/media/ipu7/ipu7-buttress.c +++ b/drivers/staging/media/ipu7/ipu7-buttress.c @@ -342,14 +342,23 @@ irqreturn_t ipu_buttress_isr(int irq, void *isp_ptr) u32 disable_irqs = 0; u32 irq_status; unsigned int i; + int active; - pm_runtime_get_noresume(dev); + active = pm_runtime_get_if_active(dev); + if (active <= 0) + return IRQ_NONE; pb_irq = readl(isp->pb_base + INTERRUPT_STATUS); writel(pb_irq, isp->pb_base + INTERRUPT_STATUS); /* check btrs ATS, CFI and IMR errors, BIT(0) is unused for IPU */ pb_local_irq = readl(isp->pb_base + BTRS_LOCAL_INTERRUPT_MASK); + if (pb_local_irq == 0xffffffff) { + dev_warn_once(dev, "invalid PB irq status\n"); + pm_runtime_put_noidle(dev); + return IRQ_NONE; + } + if (pb_local_irq & ~BIT(0)) { dev_warn(dev, "PB interrupt status 0x%x local 0x%x\n", pb_irq, pb_local_irq); @@ -370,6 +379,12 @@ irqreturn_t ipu_buttress_isr(int irq, void *isp_ptr) return IRQ_NONE; } + if (irq_status == 0xffffffff) { + dev_warn_once(dev, "invalid irq status 0x%08x\n", irq_status); + pm_runtime_put_noidle(dev); + return IRQ_NONE; + } + do { writel(irq_status, isp->base + BUTTRESS_REG_IRQ_CLEAR); diff --git a/drivers/staging/media/ipu7/ipu7.c b/drivers/staging/media/ipu7/ipu7.c index 5cddc09c72bf..6c8c3eea44ac 100644 --- a/drivers/staging/media/ipu7/ipu7.c +++ b/drivers/staging/media/ipu7/ipu7.c @@ -2684,6 +2684,10 @@ static void ipu7_pci_reset_done(struct pci_dev *pdev) */ static int ipu7_suspend(struct device *dev) { + struct pci_dev *pdev = to_pci_dev(dev); + + synchronize_irq(pdev->irq); + return 0; } From 1433e6ccc25e9ea596683ab66e1c51f37fc7d491 Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Tue, 23 Dec 2025 15:23:01 +0800 Subject: [PATCH 191/307] media: staging/ipu7: Call synchronous RPM suspend in probe failure If firmware authentication failed during driver probe, driver call an asynchronous API to suspend the psys device but the bus device will be removed soon, thus runtime PM of bus device will be disabled soon, that will cancel the suspend request, so use synchronous suspend to make sure the runtime suspend before disabling its RPM. IPU7 hardware has constraints that the PSYS device must be powered off before ISYS, otherwise it will cause machine check error. Cc: Stable@vger.kernel.org Fixes: b7fe4c0019b1 ("media: staging/ipu7: add Intel IPU7 PCI device driver") Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/ipu7.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/media/ipu7/ipu7.c b/drivers/staging/media/ipu7/ipu7.c index 6c8c3eea44ac..fa5a1867626f 100644 --- a/drivers/staging/media/ipu7/ipu7.c +++ b/drivers/staging/media/ipu7/ipu7.c @@ -2620,7 +2620,7 @@ out_ipu_bus_del_devices: if (!IS_ERR_OR_NULL(isp->isys) && !IS_ERR_OR_NULL(isp->isys->mmu)) ipu7_mmu_cleanup(isp->isys->mmu); if (!IS_ERR_OR_NULL(isp->psys)) - pm_runtime_put(&isp->psys->auxdev.dev); + pm_runtime_put_sync(&isp->psys->auxdev.dev); ipu7_bus_del_devices(pdev); release_firmware(isp->cpd_fw); buttress_exit: From f7923e6bafcad686adb51cc100ba1860f8b43922 Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Tue, 23 Dec 2025 15:23:02 +0800 Subject: [PATCH 192/307] media: staging/ipu7: Update CDPHY register settings Some CPHY settings needs to updated according to the latest guide from SNPS. This patch program 45ohm for tuning resistance to fix CPHY problem and update the ITMINRX and GMODE for CPHY. Cc: Stable@vger.kernel.org Fixes: a516d36bdc3d ("media: staging/ipu7: add IPU7 input system device driver") Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/ipu7-isys-csi-phy.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c b/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c index 2d5717883518..3f15af3b4c79 100644 --- a/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c +++ b/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c @@ -124,6 +124,7 @@ static const struct cdr_fbk_cap_prog_params table7[] = { { 1350, 1589, 4 }, { 1590, 1949, 5 }, { 1950, 2499, 6 }, + { 2500, 3500, 7 }, { } }; @@ -838,9 +839,10 @@ static void ipu7_isys_cphy_config(struct ipu7_isys *isys, u8 id, u8 lanes, dwc_phy_write_mask(isys, id, reg + 0x400 * i, reset_thresh, 9, 11); + /* Tuning ITMINRX to 2 for CPHY */ reg = CORE_DIG_CLANE_0_RW_LP_0; for (i = 0; i < trios; i++) - dwc_phy_write_mask(isys, id, reg + 0x400 * i, 1, 12, 15); + dwc_phy_write_mask(isys, id, reg + 0x400 * i, 2, 12, 15); reg = CORE_DIG_CLANE_0_RW_LP_2; for (i = 0; i < trios; i++) @@ -860,7 +862,11 @@ static void ipu7_isys_cphy_config(struct ipu7_isys *isys, u8 id, u8 lanes, for (i = 0; i < (lanes + 1); i++) { reg = CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9 + 0x400 * i; dwc_phy_write_mask(isys, id, reg, 4U, 0, 2); - dwc_phy_write_mask(isys, id, reg, 0U, 3, 4); + /* Set GMODE to 2 when CPHY >= 1.5Gsps */ + if (mbps >= 1500) + dwc_phy_write_mask(isys, id, reg, 2U, 3, 4); + else + dwc_phy_write_mask(isys, id, reg, 0U, 3, 4); reg = CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_7 + 0x400 * i; dwc_phy_write_mask(isys, id, reg, cap_prog, 10, 12); @@ -930,8 +936,9 @@ static int ipu7_isys_phy_config(struct ipu7_isys *isys, u8 id, u8 lanes, 7, 12, 14); dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_7, 0, 8, 10); + /* resistance tuning: 1 for 45ohm, 0 for 50ohm */ dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_5, - 0, 8, 8); + 1, 8, 8); if (aggregation) phy_mode = isys->csi2[0].phy_mode; From 98cc19a353abc8b48b7d58fd7a455e09e7c3aba3 Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Tue, 23 Dec 2025 15:23:03 +0800 Subject: [PATCH 193/307] media: staging/ipu7: Fix the loop bound in l2 table alloc This patch fixes the incorrect loop bound in alloc_l2_pt(). When initializing L2 page table entries, the loop was incorrectly using ISP_L1PT_PTES instead of ISP_L2PT_PTES though the ISP_L1PT_PTES is equal to ISP_L2PT_PTES. Fixes: 71d81c25683a ("media: staging/ipu7: add IPU7 DMA APIs and MMU mapping") Cc: stable@vger.kernel.org Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/ipu7-mmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/media/ipu7/ipu7-mmu.c b/drivers/staging/media/ipu7/ipu7-mmu.c index ded1986eb8ba..ea35cce4830a 100644 --- a/drivers/staging/media/ipu7/ipu7-mmu.c +++ b/drivers/staging/media/ipu7/ipu7-mmu.c @@ -231,7 +231,7 @@ static u32 *alloc_l2_pt(struct ipu7_mmu_info *mmu_info) dev_dbg(mmu_info->dev, "alloc_l2: get_zeroed_page() = %p\n", pt); - for (i = 0; i < ISP_L1PT_PTES; i++) + for (i = 0; i < ISP_L2PT_PTES; i++) pt[i] = mmu_info->dummy_page_pteval; return pt; From 779bdaad2abf718fb8116839e818e58852874b4d Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 27 Nov 2025 14:14:22 +0200 Subject: [PATCH 194/307] media: ipu6: Ensure stream_mutex is acquired when dealing with node list The ipu6 isys driver maintains the list of video buffer queues related to a stream (in ipu6 context streams on the same CSI-2 virtual channel) and this list is modified through VIDIOC_STREAMON and VIDIOC_STREAMOFF IOCTLs. Ensure the common mutex is acquired when accessing the linked list, i.e. the isys device context's stream_mutex. Add a lockdep assert to ipu6_isys_get_buffer_list() and switch to guard() while at it as the error handling becomes more simple this way. Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys-queue.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index aa2cf7287477..8f05987cdb4e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -3,6 +3,7 @@ * Copyright (C) 2013--2024 Intel Corporation */ #include +#include #include #include #include @@ -201,6 +202,8 @@ static int buffer_list_get(struct ipu6_isys_stream *stream, unsigned long flags; unsigned long buf_flag = IPU6_ISYS_BUFFER_LIST_FL_INCOMING; + lockdep_assert_held(&stream->mutex); + bl->nbufs = 0; INIT_LIST_HEAD(&bl->head); @@ -294,9 +297,8 @@ static int ipu6_isys_stream_start(struct ipu6_isys_video *av, struct ipu6_isys_buffer_list __bl; int ret; - mutex_lock(&stream->isys->stream_mutex); + guard(mutex)(&stream->isys->stream_mutex); ret = ipu6_isys_video_set_streaming(av, 1, bl); - mutex_unlock(&stream->isys->stream_mutex); if (ret) goto out_requeue; @@ -637,10 +639,10 @@ static void stop_streaming(struct vb2_queue *q) mutex_lock(&av->isys->stream_mutex); if (stream->nr_streaming == stream->nr_queues && stream->streaming) ipu6_isys_video_set_streaming(av, 0, NULL); + list_del(&aq->node); mutex_unlock(&av->isys->stream_mutex); stream->nr_streaming--; - list_del(&aq->node); stream->streaming = 0; mutex_unlock(&stream->mutex); From 5b18b3816f7f0573c8f764f88d403250dd528ca5 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 19 Dec 2025 00:54:35 +0200 Subject: [PATCH 195/307] media: ipu6: Drop MMU hardware initialisation in probe() The MMU hardware is initialised and cleaned up in the runtime PM resume callbacks. Do not do this in probe(). Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index fc0ec0a4b8f5..226bfae21c16 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -1070,10 +1070,6 @@ static int isys_probe(struct auxiliary_device *auxdev, if (!isys->csi2) return -ENOMEM; - ret = ipu6_mmu_hw_init(adev->mmu); - if (ret) - return ret; - /* initial sensor type */ isys->sensor_type = isys->pdata->ipdata->sensor_type_start; @@ -1125,8 +1121,6 @@ static int isys_probe(struct auxiliary_device *auxdev, if (ret) goto free_fw_msg_bufs; - ipu6_mmu_hw_cleanup(adev->mmu); - return 0; free_fw_msg_bufs: @@ -1148,8 +1142,6 @@ release_firmware: mutex_destroy(&isys->mutex); mutex_destroy(&isys->stream_mutex); - ipu6_mmu_hw_cleanup(adev->mmu); - return ret; } From 312c74e6d360eaa871b66b417868c63728e5170b Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 19 Dec 2025 00:52:43 +0200 Subject: [PATCH 196/307] media: ipu6: Remove redundant driver data checks Both runtime PM resume and suspend callbacks check whether the driver's data is set for the device. This is done in probe(); drop the redundant checks. Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 226bfae21c16..ef01dcb635fa 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -857,9 +857,6 @@ static int isys_runtime_pm_resume(struct device *dev) unsigned long flags; int ret; - if (!isys) - return 0; - ret = ipu6_mmu_hw_init(adev->mmu); if (ret) return ret; @@ -884,13 +881,9 @@ static int isys_runtime_pm_resume(struct device *dev) static int isys_runtime_pm_suspend(struct device *dev) { struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); - struct ipu6_isys *isys; + struct ipu6_isys *isys = dev_get_drvdata(dev); unsigned long flags; - isys = dev_get_drvdata(dev); - if (!isys) - return 0; - spin_lock_irqsave(&isys->power_lock, flags); isys->power = 0; spin_unlock_irqrestore(&isys->power_lock, flags); From e5af254e446a9d33c4f256105580e4c1ad822fc0 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 3 Dec 2025 10:27:28 +0200 Subject: [PATCH 197/307] media: ipu6: Make symbols static Make isys_setup_hw and isys_isr static as they're only used in a single file. Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys.c | 4 ++-- drivers/media/pci/intel/ipu6/ipu6-isys.h | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index ef01dcb635fa..5a8becff65ae 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -269,7 +269,7 @@ fail: return ret; } -void isys_setup_hw(struct ipu6_isys *isys) +static void isys_setup_hw(struct ipu6_isys *isys) { void __iomem *base = isys->pdata->base; const u8 *thd = isys->pdata->ipdata->hw_variant.cdc_fifo_threshold; @@ -341,7 +341,7 @@ static void ipu6_isys_csi2_isr(struct ipu6_isys_csi2 *csi2) } } -irqreturn_t isys_isr(struct ipu6_bus_device *adev) +static irqreturn_t isys_isr(struct ipu6_bus_device *adev) { struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); void __iomem *base = isys->pdata->base; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h index 0e2c8b71edfc..7fb8cb820912 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -181,8 +181,6 @@ void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys); extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops; -void isys_setup_hw(struct ipu6_isys *isys); -irqreturn_t isys_isr(struct ipu6_bus_device *adev); void update_watermark_setting(struct ipu6_isys *isys); int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys, From b0087483682fb97ea57086875ff860d618a7182d Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 30 Oct 2025 12:01:48 +0200 Subject: [PATCH 198/307] media: ipu6: Remove redundant streaming start via buffer queueing The videobuf2 framework will ensure buffers are queued before streaming is started. Remove support for starting streaming via the buf_queue() callback. Signed-off-by: Sakari Ailus Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys-queue.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index 8f05987cdb4e..fdf41b3cf60e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -408,13 +408,6 @@ static void buf_queue(struct vb2_buffer *vb) ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl); ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); - if (!stream->streaming) { - ret = ipu6_isys_stream_start(av, &bl, true); - if (ret) - dev_err(dev, "stream start failed.\n"); - goto out; - } - /* * We must queue the buffers in the buffer list to the * appropriate video buffer queues BEFORE passing them to the From 04d59cc06af6eb297d2d4c85354da2d562ce83f9 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 7 Nov 2025 12:46:51 +0200 Subject: [PATCH 199/307] media: ipu6: Don't check pipeline in stream_start A pipeline exists when start_streaming has returned so the check for start_streaming_called is equivalent to having media_pipeline. Use vb2_start_streaming_called() to perform the check. Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys-queue.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index fdf41b3cf60e..dcad6aafee29 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -355,8 +355,6 @@ static void buf_queue(struct vb2_buffer *vb) vb2_buffer_to_ipu6_isys_video_buffer(vvb); struct ipu6_isys_buffer *ib = &ivb->ib; struct device *dev = &av->isys->adev->auxdev.dev; - struct media_pipeline *media_pipe = - media_entity_pipeline(&av->vdev.entity); struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; struct ipu6_isys_stream *stream = av->stream; struct ipu6_isys_buffer_list bl; @@ -374,8 +372,8 @@ static void buf_queue(struct vb2_buffer *vb) list_add(&ib->head, &aq->incoming); spin_unlock_irqrestore(&aq->lock, flags); - if (!media_pipe || !vb->vb2_queue->start_streaming_called) { - dev_dbg(dev, "media pipeline is not ready for %s\n", + if (!vb2_start_streaming_called(vb->vb2_queue)) { + dev_dbg(dev, "start_streaming hasn't been called yet on %s\n", av->vdev.name); return; } From 5925a92cc70d10c7d3124923c36da09b9c1a6eeb Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 18 Dec 2025 00:05:38 +0200 Subject: [PATCH 200/307] media: ipu6: Close firmware streams on streaming enable failure When enabling streaming fails, the stream is stopped in firmware but not closed. Do this to release resources on firmware side. Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys-video.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index dec8f5ffcfa5..919b77107cef 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -1066,6 +1066,7 @@ int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, out_media_entity_stop_streaming_firmware: stop_streaming_firmware(av); + close_streaming_firmware(av); return ret; } From 2b08b7007e55bd1793a58478d3ecea4fd95849a5 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 7 Jan 2026 23:55:31 +0200 Subject: [PATCH 201/307] media: ipu6: Always close firmware stream Close the firmware stream even when disabling a stream on an upstream sub-device fails. This allows the firmware to release resources related to a stream that is stopped in any case. Suggested-by: Bingbu Cao Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys-video.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index 919b77107cef..54d861aca008 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -1036,11 +1036,10 @@ int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, sd->name, r_pad->index, stream_mask); ret = v4l2_subdev_disable_streams(sd, r_pad->index, stream_mask); - if (ret) { + if (ret) dev_err(dev, "stream off %s failed with %d\n", sd->name, ret); - return ret; - } + close_streaming_firmware(av); } else { ret = start_stream_firmware(av, bl); From 6d9f46bc3038041ccc58fe4d4955da0347a7b56d Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 4 Nov 2025 11:53:55 +0200 Subject: [PATCH 202/307] media: ipu6: Drop error argument from ipu6_isys_stream_start() error argument for ipu6_isys_stream_start() is always false, remove the argument. The IPU6_ISYS_BUFFER_LIST_FL_SET_STATE buffer flag also becomes redundant as a result, remove it as well. Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys-queue.c | 12 +++--------- drivers/media/pci/intel/ipu6/ipu6-isys-queue.h | 1 - 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index dcad6aafee29..0e9f0025aeb3 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -132,9 +132,6 @@ void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, list_add_tail(&ib->head, &aq->incoming); spin_unlock_irqrestore(&aq->lock, flags); - if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_SET_STATE) - vb2_buffer_done(vb, state); - if (first) { dev_dbg(dev, "queue buf list %p flags %lx, s %d, %d bufs\n", @@ -290,7 +287,7 @@ ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, /* Start streaming for real. The buffer list must be available. */ static int ipu6_isys_stream_start(struct ipu6_isys_video *av, - struct ipu6_isys_buffer_list *bl, bool error) + struct ipu6_isys_buffer_list *bl) { struct ipu6_isys_stream *stream = av->stream; struct device *dev = &stream->isys->adev->auxdev.dev; @@ -336,10 +333,7 @@ static int ipu6_isys_stream_start(struct ipu6_isys_video *av, out_requeue: if (bl && bl->nbufs) ipu6_isys_buffer_list_queue(bl, - IPU6_ISYS_BUFFER_LIST_FL_INCOMING | - (error ? - IPU6_ISYS_BUFFER_LIST_FL_SET_STATE : - 0), error ? VB2_BUF_STATE_ERROR : + IPU6_ISYS_BUFFER_LIST_FL_INCOMING, VB2_BUF_STATE_QUEUED); flush_firmware_streamon_fail(stream); @@ -590,7 +584,7 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) goto out; } - ret = ipu6_isys_stream_start(av, bl, false); + ret = ipu6_isys_stream_start(av, bl); if (ret) goto out_stream_start; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h index 844dfda15ab6..dec1fed44dd2 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h @@ -39,7 +39,6 @@ struct ipu6_isys_video_buffer { #define IPU6_ISYS_BUFFER_LIST_FL_INCOMING BIT(0) #define IPU6_ISYS_BUFFER_LIST_FL_ACTIVE BIT(1) -#define IPU6_ISYS_BUFFER_LIST_FL_SET_STATE BIT(2) struct ipu6_isys_buffer_list { struct list_head head; From b613b2fedb299b54492921f6a81835aae9b11f36 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 25 Nov 2025 15:27:54 +0200 Subject: [PATCH 203/307] media: ipu6: Obtain remote pad using media_pad_remote_pad_unique() There's no reason to use media_entity_remote_source_pad_unique() as we know our pads. Use media_pad_remote_pad_unique() instead. Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c index 43a2a16a3c2a..7e539a0c6c92 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c @@ -88,7 +88,7 @@ s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2) if (!csi2) return -EINVAL; - src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); + src_pad = media_pad_remote_pad_unique(&csi2->asd.sd.entity.pads[CSI2_PAD_SINK]); if (IS_ERR(src_pad)) { dev_err(&csi2->isys->adev->auxdev.dev, "can't get source pad of %s (%pe)\n", From 7c305850912ea153808b95475ce5acd0f934891d Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 6 Nov 2025 11:56:40 +0200 Subject: [PATCH 204/307] media: ipu6: Obtain unique source pad from remote sub-device Obtain unique source pad from a remote sub-device, instead of the first one. This means that only one link may be active at stream start. There's no functional change in practice, unless multiple CSI-2 transmitters are directly connected to the receiver. Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys-video.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index 54d861aca008..1d58e321bda7 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -1204,10 +1204,10 @@ int ipu6_isys_setup_video(struct ipu6_isys_video *av, remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); asd = to_ipu6_isys_subdev(remote_sd); - source_pad = media_pad_remote_pad_first(&remote_pad->entity->pads[0]); - if (!source_pad) { + source_pad = media_pad_remote_pad_unique(&remote_pad->entity->pads[0]); + if (IS_ERR(source_pad)) { dev_dbg(dev, "No external source entity\n"); - return -ENODEV; + return PTR_ERR(source_pad); } *source_entity = source_pad->entity; From e660207a1d080c019959a0d98638b1efa7bfb700 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 7 Nov 2025 10:03:15 +0200 Subject: [PATCH 205/307] media: ipu6: Remove source_entity from struct ipu6_isys_stream Remove source_entity from struct ipu6_isys_stream and instead pass it on in function arguments. Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- .../media/pci/intel/ipu6/ipu6-isys-queue.c | 22 ++++++++-- .../media/pci/intel/ipu6/ipu6-isys-video.c | 42 +++++-------------- .../media/pci/intel/ipu6/ipu6-isys-video.h | 6 +-- 3 files changed, 31 insertions(+), 39 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index 0e9f0025aeb3..651ddab9ef14 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -532,14 +532,28 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); struct ipu6_isys_buffer_list __bl, *bl = NULL; struct ipu6_isys_stream *stream; - struct media_entity *source_entity = NULL; + struct media_pad *source_pad, *remote_pad; int nr_queues, ret; dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n", av->vdev.name, ipu6_isys_get_frame_width(av), ipu6_isys_get_frame_height(av), pfmt->css_pixelformat); - ret = ipu6_isys_setup_video(av, &source_entity, &nr_queues); + remote_pad = media_pad_remote_pad_unique(&av->pad); + if (IS_ERR(remote_pad)) { + dev_dbg(dev, "failed to get remote pad\n"); + ret = PTR_ERR(remote_pad); + goto out_return_buffers; + } + + source_pad = media_pad_remote_pad_unique(&remote_pad->entity->pads[0]); + if (IS_ERR(source_pad)) { + dev_dbg(dev, "No external source entity\n"); + ret = PTR_ERR(source_pad); + goto out_return_buffers; + } + + ret = ipu6_isys_setup_video(av, remote_pad, source_pad, &nr_queues); if (ret < 0) { dev_dbg(dev, "failed to setup video\n"); goto out_return_buffers; @@ -560,7 +574,7 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) stream = av->stream; mutex_lock(&stream->mutex); if (!stream->nr_streaming) { - ret = ipu6_isys_video_prepare_stream(av, source_entity, + ret = ipu6_isys_video_prepare_stream(av, source_pad->entity, nr_queues); if (ret) goto out_fw_close; @@ -571,7 +585,7 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) stream->nr_queues); list_add(&aq->node, &stream->queues); - ipu6_isys_configure_stream_watermark(av, true); + ipu6_isys_configure_stream_watermark(av, source_pad->entity); ipu6_isys_update_stream_watermark(av, true); if (stream->nr_streaming != stream->nr_queues) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index 1d58e321bda7..b035c7342a91 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -745,17 +745,16 @@ int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, stream->stream_source = stream->asd->source; csi2 = ipu6_isys_subdev_to_csi2(stream->asd); csi2->receiver_errors = 0; - stream->source_entity = source_entity; dev_dbg(&av->isys->adev->auxdev.dev, "prepare stream: external entity %s\n", - stream->source_entity->name); + source_entity->name); return 0; } void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, - bool state) + struct media_entity *source) { struct ipu6_isys *isys = av->isys; struct ipu6_isys_csi2 *csi2 = NULL; @@ -769,10 +768,7 @@ void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, u64 pixel_rate = 0; int ret; - if (!state) - return; - - esd = media_entity_to_v4l2_subdev(av->stream->source_entity); + esd = media_entity_to_v4l2_subdev(source); av->watermark.width = ipu6_isys_get_frame_width(av); av->watermark.height = ipu6_isys_get_frame_height(av); @@ -804,7 +800,7 @@ void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, iwake_watermark->force_iwake_disable = true; mutex_unlock(&iwake_watermark->mutex); dev_warn(dev, "unexpected pixel_rate from %s, disable iwake.\n", - av->stream->source_entity->name); + source->name); } } @@ -1011,9 +1007,6 @@ int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, dev_dbg(dev, "set stream: %d\n", state); - if (WARN(!stream->source_entity, "No source entity for stream\n")) - return -ENODEV; - sd = &stream->asd->sd; r_pad = media_pad_remote_pad_first(&av->pad); r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, r_pad->index); @@ -1179,7 +1172,8 @@ void ipu6_isys_fw_close(struct ipu6_isys *isys) } int ipu6_isys_setup_video(struct ipu6_isys_video *av, - struct media_entity **source_entity, int *nr_queues) + struct media_pad *remote_pad, + struct media_pad *source_pad, int *nr_queues) { const struct ipu6_isys_pixelformat *pfmt = ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); @@ -1188,30 +1182,14 @@ int ipu6_isys_setup_video(struct ipu6_isys_video *av, struct v4l2_subdev_route *route = NULL; struct v4l2_subdev_route *r; struct v4l2_subdev_state *state; - struct ipu6_isys_subdev *asd; - struct v4l2_subdev *remote_sd; + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(remote_pad->entity); + struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(remote_sd); struct media_pipeline *pipeline; - struct media_pad *source_pad, *remote_pad; int ret = -EINVAL; *nr_queues = 0; - remote_pad = media_pad_remote_pad_unique(&av->pad); - if (IS_ERR(remote_pad)) { - dev_dbg(dev, "failed to get remote pad\n"); - return PTR_ERR(remote_pad); - } - - remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); - asd = to_ipu6_isys_subdev(remote_sd); - source_pad = media_pad_remote_pad_unique(&remote_pad->entity->pads[0]); - if (IS_ERR(source_pad)) { - dev_dbg(dev, "No external source entity\n"); - return PTR_ERR(source_pad); - } - - *source_entity = source_pad->entity; - /* Find the root */ state = v4l2_subdev_lock_and_get_active_state(remote_sd); for_each_active_route(&state->routing, r) { @@ -1231,7 +1209,7 @@ int ipu6_isys_setup_video(struct ipu6_isys_video *av, ret = ipu6_isys_csi2_get_remote_desc(av->source_stream, to_ipu6_isys_csi2(asd), - *source_entity, &entry); + source_pad->entity, &entry); if (ret == -ENOIOCTLCMD) { av->vc = 0; av->dt = ipu6_isys_mbus_code_to_mipi(pfmt->code); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h index 1dd36f2a077e..2ff53315d7b9 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h @@ -43,7 +43,6 @@ struct sequence_info { */ struct ipu6_isys_stream { struct mutex mutex; - struct media_entity *source_entity; atomic_t sequence; unsigned int seq_index; struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF]; @@ -113,7 +112,8 @@ int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, int ipu6_isys_fw_open(struct ipu6_isys *isys); void ipu6_isys_fw_close(struct ipu6_isys *isys); int ipu6_isys_setup_video(struct ipu6_isys_video *av, - struct media_entity **source_entity, int *nr_queues); + struct media_pad *remote_pad, + struct media_pad *source_pad, int *nr_queues); int ipu6_isys_video_init(struct ipu6_isys_video *av); void ipu6_isys_video_cleanup(struct ipu6_isys_video *av); void ipu6_isys_put_stream(struct ipu6_isys_stream *stream); @@ -123,7 +123,7 @@ struct ipu6_isys_stream * ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc); void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, - bool state); + struct media_entity *source); void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state); u32 ipu6_isys_get_format(struct ipu6_isys_video *av); From 58410f62e25d5981fcca7e2e768e4e182ce2200e Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 10 Nov 2025 13:26:57 +0200 Subject: [PATCH 206/307] media: ipu6: Drop custom functions to obtain sd state information Drop the custom functions that are used to obtain information from the sub-device state. Signed-off-by: Sakari Ailus Reviewed-by: Bingbu Cao Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- .../media/pci/intel/ipu6/ipu6-isys-queue.c | 17 ++++++--- .../media/pci/intel/ipu6/ipu6-isys-subdev.c | 36 ------------------- .../media/pci/intel/ipu6/ipu6-isys-subdev.h | 4 --- .../media/pci/intel/ipu6/ipu6-isys-video.c | 33 +++++++---------- 4 files changed, 24 insertions(+), 66 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index 651ddab9ef14..c862de31af9c 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -420,7 +420,7 @@ out: static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq) { - struct v4l2_mbus_framefmt format; + struct v4l2_mbus_framefmt format, *__format; struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); struct device *dev = &av->isys->adev->auxdev.dev; struct media_pad *remote_pad = @@ -435,13 +435,20 @@ static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq) sd = media_entity_to_v4l2_subdev(remote_pad->entity); r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, remote_pad->index); - ret = ipu6_isys_get_stream_pad_fmt(sd, remote_pad->index, r_stream, - &format); + struct v4l2_subdev_state *state = + v4l2_subdev_lock_and_get_active_state(sd); - if (ret) { + __format = v4l2_subdev_state_get_format(state, remote_pad->index, + r_stream); + if (__format) + format = *__format; + + v4l2_subdev_unlock_state(state); + + if (!__format) { dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n", sd->entity.name, remote_pad->index, r_stream); - return ret; + return -EPIPE; } if (format.width != ipu6_isys_get_frame_width(av) || diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c index 869e7d4ba572..dbd6f76a066d 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c @@ -265,42 +265,6 @@ static int subdev_set_routing(struct v4l2_subdev *sd, return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); } -int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, - struct v4l2_mbus_framefmt *format) -{ - struct v4l2_mbus_framefmt *fmt; - struct v4l2_subdev_state *state; - - if (!sd || !format) - return -EINVAL; - - state = v4l2_subdev_lock_and_get_active_state(sd); - fmt = v4l2_subdev_state_get_format(state, pad, stream); - if (fmt) - *format = *fmt; - v4l2_subdev_unlock_state(state); - - return fmt ? 0 : -EINVAL; -} - -int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream, - struct v4l2_rect *crop) -{ - struct v4l2_subdev_state *state; - struct v4l2_rect *rect; - - if (!sd || !crop) - return -EINVAL; - - state = v4l2_subdev_lock_and_get_active_state(sd); - rect = v4l2_subdev_state_get_crop(state, pad, stream); - if (rect) - *crop = *rect; - v4l2_subdev_unlock_state(state); - - return rect ? 0 : -EINVAL; -} - u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad) { struct v4l2_subdev_state *state; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h index 268dfa01e903..35069099c364 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h @@ -38,10 +38,6 @@ int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_mbus_code_enum *code); u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad); -int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, - struct v4l2_mbus_framefmt *format); -int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream, - struct v4l2_rect *crop); int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, enum v4l2_subdev_format_whence which, diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index b035c7342a91..e9dab8a709ef 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -455,6 +455,7 @@ static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, { struct media_pad *src_pad = media_pad_remote_pad_first(&av->pad); struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(src_pad->entity); + struct v4l2_subdev_state *state = v4l2_subdev_get_locked_active_state(sd); struct ipu6_fw_isys_input_pin_info_abi *input_pin; struct ipu6_fw_isys_output_pin_info_abi *output_pin; struct ipu6_isys_stream *stream = av->stream; @@ -464,26 +465,13 @@ static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); struct v4l2_rect v4l2_crop; struct ipu6_isys *isys = av->isys; - struct device *dev = &isys->adev->auxdev.dev; int input_pins = cfg->nof_input_pins++; int output_pins; u32 src_stream; - int ret; src_stream = ipu6_isys_get_src_stream_by_src_pad(sd, src_pad->index); - ret = ipu6_isys_get_stream_pad_fmt(sd, src_pad->index, src_stream, - &fmt); - if (ret < 0) { - dev_err(dev, "can't get stream format (%d)\n", ret); - return ret; - } - - ret = ipu6_isys_get_stream_pad_crop(sd, src_pad->index, src_stream, - &v4l2_crop); - if (ret < 0) { - dev_err(dev, "can't get stream crop (%d)\n", ret); - return ret; - } + fmt = *v4l2_subdev_state_get_format(state, src_pad->index, src_stream); + v4l2_crop = *v4l2_subdev_state_get_crop(state, src_pad->index, src_stream); input_pin = &cfg->input_pins[input_pins]; input_pin->input_res.width = fmt.width; @@ -784,13 +772,16 @@ void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, csi2 = ipu6_isys_subdev_to_csi2(av->stream->asd); link_freq = ipu6_isys_csi2_get_link_freq(csi2); if (link_freq > 0) { + struct v4l2_subdev_state *state = + v4l2_subdev_lock_and_get_active_state(&csi2->asd.sd); + lanes = csi2->nlanes; - ret = ipu6_isys_get_stream_pad_fmt(&csi2->asd.sd, 0, - av->source_stream, &format); - if (!ret) { - bpp = ipu6_isys_mbus_code_to_bpp(format.code); - pixel_rate = mul_u64_u32_div(link_freq, lanes * 2, bpp); - } + format = *v4l2_subdev_state_get_format(state, 0, + av->source_stream); + bpp = ipu6_isys_mbus_code_to_bpp(format.code); + pixel_rate = mul_u64_u32_div(link_freq, lanes * 2, bpp); + + v4l2_subdev_unlock_state(state); } av->watermark.pixel_rate = pixel_rate; From dc6c52205bdaddf1dc259497a958402b35c01fe2 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 11 Dec 2025 14:20:22 +0200 Subject: [PATCH 207/307] media: ipu6: Always call video_device_pipeline_alloc_start() Even if a video device is part of a pipeline already, video_device_pipeline_alloc_start() handles that case gracefully. Don't explicitly differentiate between video_device_pipeline_start() and video_device_pipeline_alloc_start() based on the existence of a pipeline. Signed-off-by: Sakari Ailus Tested-by: Mehdi Djait # Dell XPS 9315 Reviewed-by: Mehdi Djait Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys-queue.c | 1 - drivers/media/pci/intel/ipu6/ipu6-isys-video.c | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index c862de31af9c..fabaed63df0c 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -427,7 +427,6 @@ static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq) media_pad_remote_pad_first(av->vdev.entity.pads); struct v4l2_subdev *sd; u32 r_stream, code; - int ret; if (!remote_pad) return -ENOTCONN; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index e9dab8a709ef..3ac48d2076da 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -1176,7 +1176,6 @@ int ipu6_isys_setup_video(struct ipu6_isys_video *av, struct v4l2_subdev *remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(remote_sd); - struct media_pipeline *pipeline; int ret = -EINVAL; *nr_queues = 0; @@ -1216,11 +1215,7 @@ int ipu6_isys_setup_video(struct ipu6_isys_video *av, return ret; } - pipeline = media_entity_pipeline(&av->vdev.entity); - if (!pipeline) - ret = video_device_pipeline_alloc_start(&av->vdev); - else - ret = video_device_pipeline_start(&av->vdev, pipeline); + ret = video_device_pipeline_alloc_start(&av->vdev); if (ret < 0) { dev_dbg(dev, "media pipeline start failed\n"); return ret; From 4f66f36388d5668c215f107a4e1ce1a707251ff5 Mon Sep 17 00:00:00 2001 From: Xiaolei Wang Date: Thu, 8 Jan 2026 13:21:57 +0800 Subject: [PATCH 208/307] media: i2c: ov5647: Convert to CCI register access helpers Use the new common CCI register access helpers to replace the private register access helpers in the ov5647 driver. This simplifies the driver by reducing the amount of code. Signed-off-by: Xiaolei Wang Reviewed-by: Tarang Raval Reviewed-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/Kconfig | 1 + drivers/media/i2c/ov5647.c | 396 +++++++++++-------------------------- 2 files changed, 114 insertions(+), 283 deletions(-) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 371a08a53f7a..5eb1e0e0a87a 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -541,6 +541,7 @@ config VIDEO_OV5645 config VIDEO_OV5647 tristate "OmniVision OV5647 sensor support" + select V4L2_CCI_I2C help This is a Video4Linux2 sensor driver for the OmniVision OV5647 camera. diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 6285e234962e..5d352d77d563 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -20,9 +20,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -42,29 +44,26 @@ #define MIPI_CTRL00_BUS_IDLE BIT(2) #define MIPI_CTRL00_CLOCK_LANE_DISABLE BIT(0) -#define OV5647_SW_STANDBY 0x0100 -#define OV5647_SW_RESET 0x0103 -#define OV5647_REG_CHIPID_H 0x300a -#define OV5647_REG_CHIPID_L 0x300b -#define OV5640_REG_PAD_OUT 0x300d -#define OV5647_REG_EXP_HI 0x3500 -#define OV5647_REG_EXP_MID 0x3501 -#define OV5647_REG_EXP_LO 0x3502 -#define OV5647_REG_AEC_AGC 0x3503 -#define OV5647_REG_GAIN_HI 0x350a -#define OV5647_REG_GAIN_LO 0x350b -#define OV5647_REG_HTS_HI 0x380c -#define OV5647_REG_HTS_LO 0x380d -#define OV5647_REG_VTS_HI 0x380e -#define OV5647_REG_VTS_LO 0x380f -#define OV5647_REG_TIMING_TC_V 0x3820 -#define OV5647_REG_TIMING_TC_H 0x3821 -#define OV5647_REG_FRAME_OFF_NUMBER 0x4202 -#define OV5647_REG_MIPI_CTRL00 0x4800 -#define OV5647_REG_MIPI_CTRL14 0x4814 -#define OV5647_REG_AWB 0x5001 -#define OV5647_REG_ISPCTRL3D 0x503d +#define OV5647_SW_STANDBY CCI_REG8(0x0100) +#define OV5647_SW_RESET CCI_REG8(0x0103) +#define OV5647_REG_CHIPID CCI_REG16(0x300a) +#define OV5640_REG_PAD_OUT CCI_REG8(0x300d) +#define OV5647_REG_EXPOSURE CCI_REG24(0x3500) +#define OV5647_REG_AEC_AGC CCI_REG8(0x3503) +#define OV5647_REG_GAIN CCI_REG16(0x350a) +#define OV5647_REG_HTS CCI_REG16(0x380c) +#define OV5647_REG_VTS CCI_REG16(0x380e) +#define OV5647_REG_TIMING_TC_V CCI_REG8(0x3820) +#define OV5647_REG_TIMING_TC_H CCI_REG8(0x3821) +#define OV5647_REG_FRAME_OFF_NUMBER CCI_REG8(0x4202) +#define OV5647_REG_MIPI_CTRL00 CCI_REG8(0x4800) +#define OV5647_REG_MIPI_CTRL14 CCI_REG8(0x4814) +#define OV5647_REG_MIPI_CTRL14_CHANNEL_MASK GENMASK(7, 6) +#define OV5647_REG_MIPI_CTRL14_CHANNEL_SHIFT 6 +#define OV5647_REG_AWB CCI_REG8(0x5001) +#define OV5647_REG_ISPCTRL3D CCI_REG8(0x503d) +#define OV5647_CHIP_ID 0x5647 #define REG_TERM 0xfffe #define VAL_TERM 0xfe #define REG_DLY 0xffff @@ -104,11 +103,6 @@ static const s64 ov5647_link_freqs[] = { [FREQ_INDEX_VGA] = 145833300, }; -struct regval_list { - u16 addr; - u8 data; -}; - struct ov5647_mode { struct v4l2_mbus_framefmt format; struct v4l2_rect crop; @@ -116,12 +110,13 @@ struct ov5647_mode { unsigned int link_freq_index; int hts; int vts; - const struct regval_list *reg_list; + const struct reg_sequence *reg_list; unsigned int num_regs; }; struct ov5647 { struct v4l2_subdev sd; + struct regmap *regmap; struct media_pad pad; struct mutex lock; struct clk *xclk; @@ -158,19 +153,19 @@ static const u8 ov5647_test_pattern_val[] = { 0x81, /* Random Data */ }; -static const struct regval_list sensor_oe_disable_regs[] = { +static const struct reg_sequence sensor_oe_disable_regs[] = { {0x3000, 0x00}, {0x3001, 0x00}, {0x3002, 0x00}, }; -static const struct regval_list sensor_oe_enable_regs[] = { +static const struct reg_sequence sensor_oe_enable_regs[] = { {0x3000, 0x0f}, {0x3001, 0xff}, {0x3002, 0xe4}, }; -static struct regval_list ov5647_common_regs[] = { +static const struct reg_sequence ov5647_common_regs[] = { {0x0100, 0x00}, {0x0103, 0x01}, {0x3034, 0x1a}, @@ -224,7 +219,7 @@ static struct regval_list ov5647_common_regs[] = { {0x3503, 0x03}, }; -static struct regval_list ov5647_2592x1944_10bpp[] = { +static const struct reg_sequence ov5647_2592x1944_10bpp[] = { {0x3036, 0x69}, {0x3821, 0x02}, {0x3820, 0x00}, @@ -260,7 +255,7 @@ static struct regval_list ov5647_2592x1944_10bpp[] = { {0x0100, 0x01}, }; -static struct regval_list ov5647_1080p30_10bpp[] = { +static const struct reg_sequence ov5647_1080p30_10bpp[] = { {0x3036, 0x69}, {0x3821, 0x02}, {0x3820, 0x00}, @@ -296,7 +291,7 @@ static struct regval_list ov5647_1080p30_10bpp[] = { {0x0100, 0x01}, }; -static struct regval_list ov5647_2x2binned_10bpp[] = { +static const struct reg_sequence ov5647_2x2binned_10bpp[] = { {0x3036, 0x69}, {0x3821, 0x03}, {0x3820, 0x41}, @@ -336,7 +331,7 @@ static struct regval_list ov5647_2x2binned_10bpp[] = { {0x0100, 0x01}, }; -static struct regval_list ov5647_640x480_10bpp[] = { +static const struct reg_sequence ov5647_640x480_10bpp[] = { {0x3036, 0x46}, {0x3821, 0x03}, {0x3820, 0x41}, @@ -463,116 +458,36 @@ static const struct ov5647_mode ov5647_modes[] = { #define OV5647_DEFAULT_MODE (&ov5647_modes[3]) #define OV5647_DEFAULT_FORMAT (ov5647_modes[3].format) -static int ov5647_write16(struct v4l2_subdev *sd, u16 reg, u16 val) -{ - unsigned char data[4] = { reg >> 8, reg & 0xff, val >> 8, val & 0xff}; - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret; - - ret = i2c_master_send(client, data, 4); - if (ret < 0) { - dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n", - __func__, reg); - return ret; - } - - return 0; -} - -static int ov5647_write(struct v4l2_subdev *sd, u16 reg, u8 val) -{ - unsigned char data[3] = { reg >> 8, reg & 0xff, val}; - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret; - - ret = i2c_master_send(client, data, 3); - if (ret < 0) { - dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n", - __func__, reg); - return ret; - } - - return 0; -} - -static int ov5647_read(struct v4l2_subdev *sd, u16 reg, u8 *val) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - u8 buf[2] = { reg >> 8, reg & 0xff }; - struct i2c_msg msg[2]; - int ret; - - msg[0].addr = client->addr; - msg[0].flags = client->flags; - msg[0].buf = buf; - msg[0].len = sizeof(buf); - - msg[1].addr = client->addr; - msg[1].flags = client->flags | I2C_M_RD; - msg[1].buf = buf; - msg[1].len = 1; - - ret = i2c_transfer(client->adapter, msg, 2); - if (ret != 2) { - dev_err(&client->dev, "%s: i2c read error, reg: %x = %d\n", - __func__, reg, ret); - return ret >= 0 ? -EINVAL : ret; - } - - *val = buf[0]; - - return 0; -} - -static int ov5647_write_array(struct v4l2_subdev *sd, - const struct regval_list *regs, int array_size) -{ - int i, ret; - - for (i = 0; i < array_size; i++) { - ret = ov5647_write(sd, regs[i].addr, regs[i].data); - if (ret < 0) - return ret; - } - - return 0; -} - static int ov5647_set_virtual_channel(struct v4l2_subdev *sd, int channel) { - u8 channel_id; - int ret; + struct ov5647 *sensor = to_sensor(sd); - ret = ov5647_read(sd, OV5647_REG_MIPI_CTRL14, &channel_id); - if (ret < 0) - return ret; - - channel_id &= ~(3 << 6); - - return ov5647_write(sd, OV5647_REG_MIPI_CTRL14, - channel_id | (channel << 6)); + return cci_update_bits(sensor->regmap, OV5647_REG_MIPI_CTRL14, + OV5647_REG_MIPI_CTRL14_CHANNEL_MASK, + channel << OV5647_REG_MIPI_CTRL14_CHANNEL_SHIFT, + NULL); } static int ov5647_set_mode(struct v4l2_subdev *sd) { struct i2c_client *client = v4l2_get_subdevdata(sd); struct ov5647 *sensor = to_sensor(sd); - u8 resetval, rdval; + u64 resetval, rdval; int ret; - ret = ov5647_read(sd, OV5647_SW_STANDBY, &rdval); + ret = cci_read(sensor->regmap, OV5647_SW_STANDBY, &rdval, NULL); if (ret < 0) return ret; - ret = ov5647_write_array(sd, ov5647_common_regs, - ARRAY_SIZE(ov5647_common_regs)); + ret = regmap_multi_reg_write(sensor->regmap, ov5647_common_regs, + ARRAY_SIZE(ov5647_common_regs)); if (ret < 0) { dev_err(&client->dev, "write sensor common regs error\n"); return ret; } - ret = ov5647_write_array(sd, sensor->mode->reg_list, - sensor->mode->num_regs); + ret = regmap_multi_reg_write(sensor->regmap, sensor->mode->reg_list, + sensor->mode->num_regs); if (ret < 0) { dev_err(&client->dev, "write sensor default regs error\n"); return ret; @@ -582,13 +497,13 @@ static int ov5647_set_mode(struct v4l2_subdev *sd) if (ret < 0) return ret; - ret = ov5647_read(sd, OV5647_SW_STANDBY, &resetval); + ret = cci_read(sensor->regmap, OV5647_SW_STANDBY, &resetval, NULL); if (ret < 0) return ret; if (!(resetval & 0x01)) { dev_err(&client->dev, "Device was in SW standby"); - ret = ov5647_write(sd, OV5647_SW_STANDBY, 0x01); + ret = cci_write(sensor->regmap, OV5647_SW_STANDBY, 0x01, NULL); if (ret < 0) return ret; } @@ -618,32 +533,25 @@ static int ov5647_stream_on(struct v4l2_subdev *sd) val |= MIPI_CTRL00_CLOCK_LANE_GATE | MIPI_CTRL00_LINE_SYNC_ENABLE; - ret = ov5647_write(sd, OV5647_REG_MIPI_CTRL00, val); - if (ret < 0) - return ret; + cci_write(sensor->regmap, OV5647_REG_MIPI_CTRL00, val, &ret); + cci_write(sensor->regmap, OV5647_REG_FRAME_OFF_NUMBER, 0x00, &ret); + cci_write(sensor->regmap, OV5640_REG_PAD_OUT, 0x00, &ret); - ret = ov5647_write(sd, OV5647_REG_FRAME_OFF_NUMBER, 0x00); - if (ret < 0) - return ret; - - return ov5647_write(sd, OV5640_REG_PAD_OUT, 0x00); + return ret; } static int ov5647_stream_off(struct v4l2_subdev *sd) { - int ret; + struct ov5647 *sensor = to_sensor(sd); + int ret = 0; - ret = ov5647_write(sd, OV5647_REG_MIPI_CTRL00, - MIPI_CTRL00_CLOCK_LANE_GATE | MIPI_CTRL00_BUS_IDLE | - MIPI_CTRL00_CLOCK_LANE_DISABLE); - if (ret < 0) - return ret; + cci_write(sensor->regmap, OV5647_REG_MIPI_CTRL00, + MIPI_CTRL00_CLOCK_LANE_GATE | MIPI_CTRL00_BUS_IDLE | + MIPI_CTRL00_CLOCK_LANE_DISABLE, &ret); + cci_write(sensor->regmap, OV5647_REG_FRAME_OFF_NUMBER, 0x0f, &ret); + cci_write(sensor->regmap, OV5640_REG_PAD_OUT, 0x01, &ret); - ret = ov5647_write(sd, OV5647_REG_FRAME_OFF_NUMBER, 0x0f); - if (ret < 0) - return ret; - - return ov5647_write(sd, OV5640_REG_PAD_OUT, 0x01); + return ret; } static int ov5647_power_on(struct device *dev) @@ -673,8 +581,8 @@ static int ov5647_power_on(struct device *dev) goto error_pwdn; } - ret = ov5647_write_array(&sensor->sd, sensor_oe_enable_regs, - ARRAY_SIZE(sensor_oe_enable_regs)); + ret = regmap_multi_reg_write(sensor->regmap, sensor_oe_enable_regs, + ARRAY_SIZE(sensor_oe_enable_regs)); if (ret < 0) { dev_err(dev, "write sensor_oe_enable_regs error\n"); goto error_clk_disable; @@ -702,23 +610,17 @@ error_reg_disable: static int ov5647_power_off(struct device *dev) { struct ov5647 *sensor = dev_get_drvdata(dev); - u8 rdval; int ret; dev_dbg(dev, "OV5647 power off\n"); - ret = ov5647_write_array(&sensor->sd, sensor_oe_disable_regs, - ARRAY_SIZE(sensor_oe_disable_regs)); + ret = regmap_multi_reg_write(sensor->regmap, sensor_oe_disable_regs, + ARRAY_SIZE(sensor_oe_disable_regs)); if (ret < 0) dev_dbg(dev, "disable oe failed\n"); /* Enter software standby */ - ret = ov5647_read(&sensor->sd, OV5647_SW_STANDBY, &rdval); - if (ret < 0) - dev_dbg(dev, "software standby failed\n"); - - rdval &= ~0x01; - ret = ov5647_write(&sensor->sd, OV5647_SW_STANDBY, rdval); + ret = cci_update_bits(sensor->regmap, OV5647_SW_STANDBY, 0x01, 0x00, NULL); if (ret < 0) dev_dbg(dev, "software standby failed\n"); @@ -733,10 +635,11 @@ static int ov5647_power_off(struct device *dev) static int ov5647_sensor_get_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) { + struct ov5647 *sensor = to_sensor(sd); int ret; - u8 val; + u64 val; - ret = ov5647_read(sd, reg->reg & 0xff, &val); + ret = cci_read(sensor->regmap, reg->reg & 0xff, &val, NULL); if (ret < 0) return ret; @@ -749,7 +652,9 @@ static int ov5647_sensor_get_register(struct v4l2_subdev *sd, static int ov5647_sensor_set_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) { - return ov5647_write(sd, reg->reg & 0xff, reg->val & 0xff); + struct ov5647 *sensor = to_sensor(sd); + + return cci_write(sensor->regmap, reg->reg & 0xff, reg->val & 0xff, NULL); } #endif @@ -1004,33 +909,27 @@ static const struct v4l2_subdev_ops ov5647_subdev_ops = { static int ov5647_detect(struct v4l2_subdev *sd) { + struct ov5647 *sensor = to_sensor(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); - u8 read; + u64 read; int ret; - ret = ov5647_write(sd, OV5647_SW_RESET, 0x01); + ret = cci_write(sensor->regmap, OV5647_SW_RESET, 0x01, NULL); if (ret < 0) return ret; - ret = ov5647_read(sd, OV5647_REG_CHIPID_H, &read); + ret = cci_read(sensor->regmap, OV5647_REG_CHIPID, &read, NULL); if (ret < 0) - return ret; + return dev_err_probe(&client->dev, ret, + "failed to read chip id %x\n", + OV5647_REG_CHIPID); - if (read != 0x56) { - dev_err(&client->dev, "ID High expected 0x56 got %x", read); + if (read != OV5647_CHIP_ID) { + dev_err(&client->dev, "Chip ID expected 0x5647 got 0x%llx", read); return -ENODEV; } - ret = ov5647_read(sd, OV5647_REG_CHIPID_L, &read); - if (ret < 0) - return ret; - - if (read != 0x47) { - dev_err(&client->dev, "ID Low expected 0x47 got %x", read); - return -ENODEV; - } - - return ov5647_write(sd, OV5647_SW_RESET, 0x00); + return cci_write(sensor->regmap, OV5647_SW_RESET, 0x00, NULL); } static int ov5647_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) @@ -1053,99 +952,6 @@ static const struct v4l2_subdev_internal_ops ov5647_subdev_internal_ops = { .open = ov5647_open, }; -static int ov5647_s_auto_white_balance(struct v4l2_subdev *sd, u32 val) -{ - return ov5647_write(sd, OV5647_REG_AWB, val ? 1 : 0); -} - -static int ov5647_s_autogain(struct v4l2_subdev *sd, u32 val) -{ - int ret; - u8 reg; - - /* Non-zero turns on AGC by clearing bit 1.*/ - ret = ov5647_read(sd, OV5647_REG_AEC_AGC, ®); - if (ret) - return ret; - - return ov5647_write(sd, OV5647_REG_AEC_AGC, val ? reg & ~BIT(1) - : reg | BIT(1)); -} - -static int ov5647_s_exposure_auto(struct v4l2_subdev *sd, u32 val) -{ - int ret; - u8 reg; - - /* - * Everything except V4L2_EXPOSURE_MANUAL turns on AEC by - * clearing bit 0. - */ - ret = ov5647_read(sd, OV5647_REG_AEC_AGC, ®); - if (ret) - return ret; - - return ov5647_write(sd, OV5647_REG_AEC_AGC, - val == V4L2_EXPOSURE_MANUAL ? reg | BIT(0) - : reg & ~BIT(0)); -} - -static int ov5647_s_analogue_gain(struct v4l2_subdev *sd, u32 val) -{ - int ret; - - /* 10 bits of gain, 2 in the high register. */ - ret = ov5647_write(sd, OV5647_REG_GAIN_HI, (val >> 8) & 3); - if (ret) - return ret; - - return ov5647_write(sd, OV5647_REG_GAIN_LO, val & 0xff); -} - -static int ov5647_s_exposure(struct v4l2_subdev *sd, u32 val) -{ - int ret; - - /* - * Sensor has 20 bits, but the bottom 4 bits are fractions of a line - * which we leave as zero (and don't receive in "val"). - */ - ret = ov5647_write(sd, OV5647_REG_EXP_HI, (val >> 12) & 0xf); - if (ret) - return ret; - - ret = ov5647_write(sd, OV5647_REG_EXP_MID, (val >> 4) & 0xff); - if (ret) - return ret; - - return ov5647_write(sd, OV5647_REG_EXP_LO, (val & 0xf) << 4); -} - -static int ov5647_s_flip(struct v4l2_subdev *sd, u16 reg, u32 ctrl_val) -{ - u8 reg_val; - int ret; - - /* - * TIMING TC REG20 (Vertical) and REG21 (Horizontal): - * - [2]: ISP mirror/flip - * - [1]: Sensor mirror/flip - * - * We only use sensor flip. - * - * Using ISP flip retains the BGGR pattern at the cost of changing the - * pixel array readout. This affects the selection rectangles in ways - * that are not very well documented, and would be tougher to deal with - * for applications compared to reading a different bayer pattern. - */ - ret = ov5647_read(sd, reg, ®_val); - if (ret) - return ret; - - return ov5647_write(sd, reg, ctrl_val ? reg_val | BIT(1) - : reg_val & ~BIT(1)); -} - static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) { struct ov5647 *sensor = container_of(ctrl->handler, @@ -1179,38 +985,55 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_AUTO_WHITE_BALANCE: - ret = ov5647_s_auto_white_balance(sd, ctrl->val); + ret = cci_write(sensor->regmap, OV5647_REG_AWB, + ctrl->val ? 1 : 0, NULL); break; case V4L2_CID_AUTOGAIN: - ret = ov5647_s_autogain(sd, ctrl->val); + /* Non-zero turns on AGC by clearing bit 1.*/ + return cci_update_bits(sensor->regmap, OV5647_REG_AEC_AGC, BIT(1), + ctrl->val ? 0 : BIT(1), NULL); break; case V4L2_CID_EXPOSURE_AUTO: - ret = ov5647_s_exposure_auto(sd, ctrl->val); + /* + * Everything except V4L2_EXPOSURE_MANUAL turns on AEC by + * clearing bit 0. + */ + return cci_update_bits(sensor->regmap, OV5647_REG_AEC_AGC, BIT(0), + ctrl->val == V4L2_EXPOSURE_MANUAL ? BIT(0) : 0, NULL); break; case V4L2_CID_ANALOGUE_GAIN: - ret = ov5647_s_analogue_gain(sd, ctrl->val); + /* 10 bits of gain, 2 in the high register. */ + return cci_write(sensor->regmap, OV5647_REG_GAIN, + ctrl->val & 0x3ff, NULL); break; case V4L2_CID_EXPOSURE: - ret = ov5647_s_exposure(sd, ctrl->val); + /* + * Sensor has 20 bits, but the bottom 4 bits are fractions of a line + * which we leave as zero (and don't receive in "val"). + */ + ret = cci_write(sensor->regmap, OV5647_REG_EXPOSURE, + ctrl->val << 4, NULL); break; case V4L2_CID_VBLANK: - ret = ov5647_write16(sd, OV5647_REG_VTS_HI, - sensor->mode->format.height + ctrl->val); + ret = cci_write(sensor->regmap, OV5647_REG_VTS, + sensor->mode->format.height + ctrl->val, NULL); break; case V4L2_CID_HBLANK: - ret = ov5647_write16(sd, OV5647_REG_HTS_HI, - sensor->mode->format.width + ctrl->val); + ret = cci_write(sensor->regmap, OV5647_REG_HTS, + sensor->mode->format.width + ctrl->val, &ret); break; case V4L2_CID_TEST_PATTERN: - ret = ov5647_write(sd, OV5647_REG_ISPCTRL3D, - ov5647_test_pattern_val[ctrl->val]); + ret = cci_write(sensor->regmap, OV5647_REG_ISPCTRL3D, + ov5647_test_pattern_val[ctrl->val], NULL); break; case V4L2_CID_HFLIP: /* There's an in-built hflip in the sensor, so account for that here. */ - ret = ov5647_s_flip(sd, OV5647_REG_TIMING_TC_H, !ctrl->val); + ret = cci_update_bits(sensor->regmap, OV5647_REG_TIMING_TC_H, BIT(1), + ctrl->val ? 0 : BIT(1), NULL); break; case V4L2_CID_VFLIP: - ret = ov5647_s_flip(sd, OV5647_REG_TIMING_TC_V, ctrl->val); + ret = cci_update_bits(sensor->regmap, OV5647_REG_TIMING_TC_V, BIT(1), + ctrl->val ? BIT(1) : 0, NULL); break; default: @@ -1416,6 +1239,13 @@ static int ov5647_probe(struct i2c_client *client) if (ret < 0) goto ctrl_handler_free; + sensor->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(sensor->regmap)) { + ret = dev_err_probe(dev, PTR_ERR(sensor->regmap), + "Failed to init CCI\n"); + goto entity_cleanup; + } + ret = ov5647_power_on(dev); if (ret) goto entity_cleanup; From ebcb6cafd10fe601bd7e55f1ce07c17c62f877be Mon Sep 17 00:00:00 2001 From: Xiaolei Wang Date: Thu, 8 Jan 2026 13:21:58 +0800 Subject: [PATCH 209/307] media: i2c: ov5647: Switch to using the sub-device state lock Switch to using the sub-device state lock and properly call v4l2_subdev_init_finalize() / v4l2_subdev_cleanup() on probe() / remove(). Signed-off-by: Xiaolei Wang Reviewed-by: Tarang Raval [Sakari Ailus: Remove now-redundant assignment of control handler lock.] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 42 ++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 5d352d77d563..4660c06de6b5 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -118,7 +118,6 @@ struct ov5647 { struct v4l2_subdev sd; struct regmap *regmap; struct media_pad pad; - struct mutex lock; struct clk *xclk; struct gpio_desc *pwdn; struct regulator_bulk_data supplies[OV5647_NUM_SUPPLIES]; @@ -686,10 +685,10 @@ __ov5647_get_pad_crop(struct ov5647 *ov5647, static int ov5647_s_stream(struct v4l2_subdev *sd, int enable) { struct i2c_client *client = v4l2_get_subdevdata(sd); - struct ov5647 *sensor = to_sensor(sd); + struct v4l2_subdev_state *state; int ret; - mutex_lock(&sensor->lock); + state = v4l2_subdev_lock_and_get_active_state(sd); if (enable) { ret = pm_runtime_resume_and_get(&client->dev); @@ -710,14 +709,14 @@ static int ov5647_s_stream(struct v4l2_subdev *sd, int enable) pm_runtime_put(&client->dev); } - mutex_unlock(&sensor->lock); + v4l2_subdev_unlock_state(state); return 0; error_pm: pm_runtime_put(&client->dev); error_unlock: - mutex_unlock(&sensor->lock); + v4l2_subdev_unlock_state(state); return ret; } @@ -785,7 +784,6 @@ static int ov5647_get_pad_fmt(struct v4l2_subdev *sd, const struct v4l2_mbus_framefmt *sensor_format; struct ov5647 *sensor = to_sensor(sd); - mutex_lock(&sensor->lock); switch (format->which) { case V4L2_SUBDEV_FORMAT_TRY: sensor_format = v4l2_subdev_state_get_format(sd_state, @@ -799,7 +797,6 @@ static int ov5647_get_pad_fmt(struct v4l2_subdev *sd, *fmt = *sensor_format; /* The code we pass back must reflect the current h/vflips. */ fmt->code = ov5647_get_mbus_code(sd); - mutex_unlock(&sensor->lock); return 0; } @@ -817,7 +814,6 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, fmt->width, fmt->height); /* Update the sensor mode and apply at it at streamon time. */ - mutex_lock(&sensor->lock); if (format->which == V4L2_SUBDEV_FORMAT_TRY) { *v4l2_subdev_state_get_format(sd_state, format->pad) = mode->format; } else { @@ -851,7 +847,6 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, *fmt = mode->format; /* The code we pass back must reflect the current h/vflips. */ fmt->code = ov5647_get_mbus_code(sd); - mutex_unlock(&sensor->lock); return 0; } @@ -864,10 +859,8 @@ static int ov5647_get_selection(struct v4l2_subdev *sd, case V4L2_SEL_TGT_CROP: { struct ov5647 *sensor = to_sensor(sd); - mutex_lock(&sensor->lock); sel->r = *__ov5647_get_pad_crop(sensor, sd_state, sel->pad, sel->which); - mutex_unlock(&sensor->lock); return 0; } @@ -960,9 +953,6 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl) struct i2c_client *client = v4l2_get_subdevdata(sd); int ret = 0; - - /* v4l2_ctrl_lock() locks our own mutex */ - if (ctrl->id == V4L2_CID_VBLANK) { int exposure_max, exposure_def; @@ -1071,8 +1061,6 @@ static int ov5647_init_controls(struct ov5647 *sensor) v4l2_ctrl_handler_init(&sensor->ctrls, 14); - sensor->ctrls.lock = &sensor->lock; - v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops, V4L2_CID_AUTOGAIN, 0, 1, 1, 0); @@ -1220,8 +1208,6 @@ static int ov5647_probe(struct i2c_client *client) if (ret) dev_err_probe(dev, ret, "Failed to get power regulators\n"); - mutex_init(&sensor->lock); - sensor->mode = OV5647_DEFAULT_MODE; sd = &sensor->sd; @@ -1231,7 +1217,7 @@ static int ov5647_probe(struct i2c_client *client) ret = ov5647_init_controls(sensor); if (ret) - goto mutex_destroy; + return ret; sensor->pad.flags = MEDIA_PAD_FL_SOURCE; sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; @@ -1254,27 +1240,35 @@ static int ov5647_probe(struct i2c_client *client) if (ret < 0) goto power_off; - ret = v4l2_async_register_subdev_sensor(sd); - if (ret < 0) + sd->state_lock = sensor->ctrls.lock; + ret = v4l2_subdev_init_finalize(sd); + if (ret < 0) { + ret = dev_err_probe(dev, ret, "failed to init subdev\n"); goto power_off; + } /* Enable runtime PM and turn off the device */ pm_runtime_set_active(dev); pm_runtime_enable(dev); + + ret = v4l2_async_register_subdev_sensor(sd); + if (ret < 0) + goto v4l2_subdev_cleanup; + pm_runtime_idle(dev); dev_dbg(dev, "OmniVision OV5647 camera driver probed\n"); return 0; +v4l2_subdev_cleanup: + v4l2_subdev_cleanup(sd); power_off: ov5647_power_off(dev); entity_cleanup: media_entity_cleanup(&sd->entity); ctrl_handler_free: v4l2_ctrl_handler_free(&sensor->ctrls); -mutex_destroy: - mutex_destroy(&sensor->lock); return ret; } @@ -1285,11 +1279,11 @@ static void ov5647_remove(struct i2c_client *client) struct ov5647 *sensor = to_sensor(sd); v4l2_async_unregister_subdev(&sensor->sd); + v4l2_subdev_cleanup(sd); media_entity_cleanup(&sensor->sd.entity); v4l2_ctrl_handler_free(&sensor->ctrls); v4l2_device_unregister_subdev(sd); pm_runtime_disable(&client->dev); - mutex_destroy(&sensor->lock); } static const struct dev_pm_ops ov5647_pm_ops = { From 98cf5594c2fbe07f47e861ceb942327681207fbe Mon Sep 17 00:00:00 2001 From: Xiaolei Wang Date: Thu, 8 Jan 2026 13:21:59 +0800 Subject: [PATCH 210/307] media: i2c: ov5647: switch to {enable,disable}_streams Switch from s_stream to enable_streams and disable_streams callbacks. Signed-off-by: Xiaolei Wang Reviewed-by: Laurent Pinchart Reviewed-by: Tarang Raval Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5647.c | 89 ++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 51 deletions(-) diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 4660c06de6b5..6a46ef7233ac 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -510,23 +510,42 @@ static int ov5647_set_mode(struct v4l2_subdev *sd) return 0; } -static int ov5647_stream_on(struct v4l2_subdev *sd) +static int ov5647_stream_stop(struct ov5647 *sensor) +{ + int ret = 0; + + cci_write(sensor->regmap, OV5647_REG_MIPI_CTRL00, + MIPI_CTRL00_CLOCK_LANE_GATE | MIPI_CTRL00_BUS_IDLE | + MIPI_CTRL00_CLOCK_LANE_DISABLE, &ret); + cci_write(sensor->regmap, OV5647_REG_FRAME_OFF_NUMBER, 0x0f, &ret); + cci_write(sensor->regmap, OV5640_REG_PAD_OUT, 0x01, &ret); + + return ret; +} + +static int ov5647_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) { struct i2c_client *client = v4l2_get_subdevdata(sd); struct ov5647 *sensor = to_sensor(sd); u8 val = MIPI_CTRL00_BUS_IDLE; int ret; + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + return ret; + ret = ov5647_set_mode(sd); if (ret) { dev_err(&client->dev, "Failed to program sensor mode: %d\n", ret); - return ret; + goto done; } /* Apply customized values from user when stream starts. */ ret = __v4l2_ctrl_handler_setup(sd->ctrl_handler); if (ret) - return ret; + goto done; if (sensor->clock_ncont) val |= MIPI_CTRL00_CLOCK_LANE_GATE | @@ -536,19 +555,24 @@ static int ov5647_stream_on(struct v4l2_subdev *sd) cci_write(sensor->regmap, OV5647_REG_FRAME_OFF_NUMBER, 0x00, &ret); cci_write(sensor->regmap, OV5640_REG_PAD_OUT, 0x00, &ret); +done: + if (ret) + pm_runtime_put(&client->dev); + return ret; } -static int ov5647_stream_off(struct v4l2_subdev *sd) +static int ov5647_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) { + struct i2c_client *client = v4l2_get_subdevdata(sd); struct ov5647 *sensor = to_sensor(sd); - int ret = 0; + int ret; - cci_write(sensor->regmap, OV5647_REG_MIPI_CTRL00, - MIPI_CTRL00_CLOCK_LANE_GATE | MIPI_CTRL00_BUS_IDLE | - MIPI_CTRL00_CLOCK_LANE_DISABLE, &ret); - cci_write(sensor->regmap, OV5647_REG_FRAME_OFF_NUMBER, 0x0f, &ret); - cci_write(sensor->regmap, OV5640_REG_PAD_OUT, 0x01, &ret); + ret = ov5647_stream_stop(sensor); + + pm_runtime_put(&client->dev); return ret; } @@ -588,7 +612,7 @@ static int ov5647_power_on(struct device *dev) } /* Stream off to coax lanes into LP-11 state. */ - ret = ov5647_stream_off(&sensor->sd); + ret = ov5647_stream_stop(sensor); if (ret < 0) { dev_err(dev, "camera not available, check power\n"); goto error_clk_disable; @@ -682,47 +706,8 @@ __ov5647_get_pad_crop(struct ov5647 *ov5647, return NULL; } -static int ov5647_s_stream(struct v4l2_subdev *sd, int enable) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - struct v4l2_subdev_state *state; - int ret; - - state = v4l2_subdev_lock_and_get_active_state(sd); - - if (enable) { - ret = pm_runtime_resume_and_get(&client->dev); - if (ret < 0) - goto error_unlock; - - ret = ov5647_stream_on(sd); - if (ret < 0) { - dev_err(&client->dev, "stream start failed: %d\n", ret); - goto error_pm; - } - } else { - ret = ov5647_stream_off(sd); - if (ret < 0) { - dev_err(&client->dev, "stream stop failed: %d\n", ret); - goto error_pm; - } - pm_runtime_put(&client->dev); - } - - v4l2_subdev_unlock_state(state); - - return 0; - -error_pm: - pm_runtime_put(&client->dev); -error_unlock: - v4l2_subdev_unlock_state(state); - - return ret; -} - static const struct v4l2_subdev_video_ops ov5647_subdev_video_ops = { - .s_stream = ov5647_s_stream, + .s_stream = v4l2_subdev_s_stream_helper, }; /* @@ -892,6 +877,8 @@ static const struct v4l2_subdev_pad_ops ov5647_subdev_pad_ops = { .set_fmt = ov5647_set_pad_fmt, .get_fmt = ov5647_get_pad_fmt, .get_selection = ov5647_get_selection, + .enable_streams = ov5647_enable_streams, + .disable_streams = ov5647_disable_streams, }; static const struct v4l2_subdev_ops ov5647_subdev_ops = { From bc6af7695a511b93f43e5ab4f872bcbccb580380 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 9 Jan 2026 10:53:40 +0200 Subject: [PATCH 211/307] media: ccs: Clean up ccs_get_hwcfg() a little Fix coding style issues and do other small cleanups in ccs_get_hwcfg(). Signed-off-by: Sakari Ailus Reviewed-by: David Heidelberg Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 33 +++++++++++++------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index d7038f5b3689..4508a561428a 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -3145,11 +3145,9 @@ static const struct v4l2_subdev_internal_ops ccs_internal_src_ops = { static int ccs_get_hwconfig(struct ccs_sensor *sensor, struct device *dev) { - struct ccs_hwconfig *hwcfg = &sensor->hwcfg; struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = V4L2_MBUS_UNKNOWN }; - struct fwnode_handle *ep; - struct fwnode_handle *fwnode = dev_fwnode(dev); - unsigned int i; + struct fwnode_handle *fwnode = dev_fwnode(dev), *ep; + struct ccs_hwconfig *hwcfg = &sensor->hwcfg; int rval; ep = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0, @@ -3176,9 +3174,9 @@ static int ccs_get_hwconfig(struct ccs_sensor *sensor, struct device *dev) break; case V4L2_MBUS_CSI1: case V4L2_MBUS_CCP2: - hwcfg->csi_signalling_mode = (bus_cfg.bus.mipi_csi1.strobe) ? - SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE : - SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK; + hwcfg->csi_signalling_mode = bus_cfg.bus.mipi_csi1.strobe ? + SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE : + SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK; hwcfg->lanes = 1; break; default: @@ -3187,11 +3185,7 @@ static int ccs_get_hwconfig(struct ccs_sensor *sensor, struct device *dev) goto out_err; } - rval = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", - &hwcfg->ext_clk); - - dev_dbg(dev, "clk %u, mode %u\n", hwcfg->ext_clk, - hwcfg->csi_signalling_mode); + dev_dbg(dev, "signalling mode: %u\n", hwcfg->csi_signalling_mode); if (!bus_cfg.nr_of_link_frequencies) { dev_warn(dev, "no link frequencies defined\n"); @@ -3199,23 +3193,22 @@ static int ccs_get_hwconfig(struct ccs_sensor *sensor, struct device *dev) goto out_err; } - hwcfg->op_sys_clock = devm_kcalloc( - dev, bus_cfg.nr_of_link_frequencies + 1 /* guardian */, - sizeof(*hwcfg->op_sys_clock), GFP_KERNEL); + hwcfg->op_sys_clock = + devm_kcalloc(dev, + bus_cfg.nr_of_link_frequencies + 1 /* guardian */, + sizeof(*hwcfg->op_sys_clock), GFP_KERNEL); if (!hwcfg->op_sys_clock) { rval = -ENOMEM; goto out_err; } - for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) { + for (unsigned int i = 0; i < bus_cfg.nr_of_link_frequencies; i++) { hwcfg->op_sys_clock[i] = bus_cfg.link_frequencies[i]; dev_dbg(dev, "freq %u: %lld\n", i, hwcfg->op_sys_clock[i]); } - v4l2_fwnode_endpoint_free(&bus_cfg); - fwnode_handle_put(ep); - - return 0; + fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &hwcfg->ext_clk); out_err: v4l2_fwnode_endpoint_free(&bus_cfg); From 08e47937637e7255509efa421260f46d212ddf96 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 9 Jan 2026 11:00:21 +0200 Subject: [PATCH 212/307] media: ccs: Use devm_v4l2_sensor_clk_get() Rely on devm_v4l2_sensor_clk_get() to obtain the sensor's external clock. Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 55 +++++++++++--------------------- 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 4508a561428a..6f2020e2f861 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -3296,47 +3296,30 @@ static int ccs_probe(struct i2c_client *client) return rval; } - sensor->ext_clk = devm_clk_get(&client->dev, NULL); - if (PTR_ERR(sensor->ext_clk) == -ENOENT) { - dev_info(&client->dev, "no clock defined, continuing...\n"); - sensor->ext_clk = NULL; - } else if (IS_ERR(sensor->ext_clk)) { - dev_err(&client->dev, "could not get clock (%pe)\n", - sensor->ext_clk); - return -EPROBE_DEFER; - } + sensor->ext_clk = devm_v4l2_sensor_clk_get(&client->dev, NULL); + if (IS_ERR(sensor->ext_clk)) + return dev_err_probe(&client->dev, PTR_ERR(sensor->ext_clk), + "could not get clock\n"); - if (sensor->ext_clk) { - if (sensor->hwcfg.ext_clk) { - unsigned long rate; + if (sensor->hwcfg.ext_clk) { + unsigned long rate; - rval = clk_set_rate(sensor->ext_clk, - sensor->hwcfg.ext_clk); - if (rval < 0) { - dev_err(&client->dev, - "unable to set clock freq to %u\n", - sensor->hwcfg.ext_clk); - return rval; - } + rval = clk_set_rate(sensor->ext_clk, sensor->hwcfg.ext_clk); + if (rval < 0) + return dev_err_probe(&client->dev, rval, + "unable to set clock freq to %u\n", + sensor->hwcfg.ext_clk); - rate = clk_get_rate(sensor->ext_clk); - if (rate != sensor->hwcfg.ext_clk) { - dev_err(&client->dev, - "can't set clock freq, asked for %u but got %lu\n", - sensor->hwcfg.ext_clk, rate); - return -EINVAL; - } - } else { - sensor->hwcfg.ext_clk = clk_get_rate(sensor->ext_clk); - dev_dbg(&client->dev, "obtained clock freq %u\n", - sensor->hwcfg.ext_clk); + rate = clk_get_rate(sensor->ext_clk); + if (rate != sensor->hwcfg.ext_clk) { + return dev_err_probe(&client->dev, -EINVAL, + "can't set clock freq, asked for %u but got %lu\n", + sensor->hwcfg.ext_clk, rate); } - } else if (sensor->hwcfg.ext_clk) { - dev_dbg(&client->dev, "assuming clock freq %u\n", - sensor->hwcfg.ext_clk); } else { - dev_err(&client->dev, "unable to obtain clock freq\n"); - return -EINVAL; + sensor->hwcfg.ext_clk = clk_get_rate(sensor->ext_clk); + dev_dbg(&client->dev, "obtained clock freq %u\n", + sensor->hwcfg.ext_clk); } if (!sensor->hwcfg.ext_clk) { From 3085977e734dab74adebb1dda195befce25addff Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Fri, 9 Jan 2026 18:57:07 +0100 Subject: [PATCH 213/307] media: ccs: Accommodate C-PHY into the calculation We need to set correct mode for PLL to calculate correct frequency. Signalling mode is known at this point, so use it for that. Fixes: 47b6eaf36eba ("media: ccs-pll: Differentiate between CSI-2 D-PHY and C-PHY") Reviewed-by: Mehdi Djait Signed-off-by: David Heidelberg [Sakari Ailus: Drop extra newline.] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 6f2020e2f861..bc832f5ccd29 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -3472,7 +3472,21 @@ static int ccs_probe(struct i2c_client *client) sensor->scale_m = CCS_LIM(sensor, SCALER_N_MIN); /* prepare PLL configuration input values */ - sensor->pll.bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY; + switch (sensor->hwcfg.csi_signalling_mode) { + case CCS_CSI_SIGNALING_MODE_CSI_2_CPHY: + sensor->pll.bus_type = CCS_PLL_BUS_TYPE_CSI2_CPHY; + break; + case CCS_CSI_SIGNALING_MODE_CSI_2_DPHY: + case SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_CLOCK: + case SMIAPP_CSI_SIGNALLING_MODE_CCP2_DATA_STROBE: + sensor->pll.bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY; + break; + default: + dev_err(&client->dev, "unsupported signalling mode %u\n", + sensor->hwcfg.csi_signalling_mode); + rval = -EINVAL; + goto out_cleanup; + } sensor->pll.csi2.lanes = sensor->hwcfg.lanes; if (CCS_LIM(sensor, CLOCK_CALCULATION) & CCS_CLOCK_CALCULATION_LANE_SPEED) { From 4d4135dd953395c311cd630d69658d7b8af9d39d Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 15 Dec 2025 10:19:30 -0500 Subject: [PATCH 214/307] MAINTAINERS: Promote Frank Li as i.MX7/8 media maintainer Move Rui Miguel Silva to reviewer since he does not have sufficient time to maintain i.MX7/8 media drivers. Frank Li has the hardware, complete documentation, and has participated actively in patch review. Promote him as the i.MX7/8 media drivers maintainer. Reviewed-by: Rui Miguel Silva Signed-off-by: Frank Li Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index fc68ee0c68c0..5560da0deb71 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15752,11 +15752,11 @@ F: include/linux/imx-media.h F: include/media/imx.h MEDIA DRIVERS FOR FREESCALE IMX7/8 -M: Rui Miguel Silva M: Laurent Pinchart +M: Frank Li M: Martin Kepplinger-Novakovic +R: Rui Miguel Silva R: Purism Kernel Team -R: Frank Li L: imx@lists.linux.dev L: linux-media@vger.kernel.org S: Maintained From 2f130245f2143fa8f4da77071f844911d2c69319 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Mon, 15 Dec 2025 13:10:19 +0100 Subject: [PATCH 215/307] media: stm32: dcmipp: avoid naming clock if only one is needed When DCMIPP requires only a single clock (kclk), avoid relying on its name to obtain it. The introduction of MP25 support added the mclk, which necessitated naming the first clock kclk. However, this breaks backward compatibility with existing MP13 device trees that do not specify clock names. Fixes: 686f27f7ea37 ("media: stm32: dcmipp: add core support for the stm32mp25") Signed-off-by: Alain Volmat Cc: Stable@vger.kernel.org # 6.14.x: 7f487562af49 media: stm32: dcmipp: correct ret type in dcmipp_graph_notify_bound Cc: Stable@vger.kernel.org # 6.14.x: c715dd62da30 media: stm32: dcmipp: add has_csi2 & needs_mclk in match data Cc: Stable@vger.kernel.org # 6.14.x: Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c index 1b7bae3266c8..49398d077764 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c @@ -526,7 +526,12 @@ static int dcmipp_probe(struct platform_device *pdev) return ret; } - kclk = devm_clk_get(&pdev->dev, "kclk"); + /* + * In case of the DCMIPP has only 1 clock (such as on MP13), the + * clock might not be named. + */ + kclk = devm_clk_get(&pdev->dev, + dcmipp->pipe_cfg->needs_mclk ? "kclk" : NULL); if (IS_ERR(kclk)) return dev_err_probe(&pdev->dev, PTR_ERR(kclk), "Unable to get kclk\n"); From 5152437f7610eb522fd233ab548a19d4601cbc2a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 30 Dec 2025 09:34:36 +0100 Subject: [PATCH 216/307] media: i2c: Add note to prevent buggy code re-use adv7604 and et8ek8 sensor drivers have mixed up logical and line level for reset/powerdown signal. They call it a reset signal (it indeed behaves like that), but drivers assert the reset to operate which is clearly incorrect and relies on wrong ACTIVE_HIGH flag in the DTS. People in discussions copy existing poor code and claim they can repeat same mistake, so add a note to prevent that. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/adv7604.c | 8 +++++++- drivers/media/i2c/et8ek8/et8ek8_driver.c | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index 516553fb17e9..67116a4ef134 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -3453,7 +3453,13 @@ static int configure_regmaps(struct adv76xx_state *state) static void adv76xx_reset(struct adv76xx_state *state) { if (state->reset_gpio) { - /* ADV76XX can be reset by a low reset pulse of minimum 5 ms. */ + /* + * Note: Misinterpretation of reset assertion - do not re-use + * this code. The reset pin is using incorrect (for a reset + * signal) logical level. + * + * ADV76XX can be reset by a low reset pulse of minimum 5 ms. + */ gpiod_set_value_cansleep(state->reset_gpio, 0); usleep_range(5000, 10000); gpiod_set_value_cansleep(state->reset_gpio, 1); diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c index 2cb7b718782b..50121c3e5b48 100644 --- a/drivers/media/i2c/et8ek8/et8ek8_driver.c +++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c @@ -835,6 +835,10 @@ static int et8ek8_power_on(struct et8ek8_sensor *sensor) udelay(10); /* I wish this is a good value */ + /* + * Note: Misinterpretation of reset assertion - do not re-use this code. + * The reset pin is using incorrect (for a reset signal) logical level. + */ gpiod_set_value(sensor->reset, 1); msleep(5000 * 1000 / sensor->xclk_freq + 1); /* Wait 5000 cycles */ From 222f1279edd9008ee35b62de156ddac84e31443c Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 19 Dec 2025 15:30:35 +0100 Subject: [PATCH 217/307] media: stm32: dcmipp: bytecap: clear all interrupts upon stream stop Ensure that there are no pending interrupts after we have stopped the pipeline. Indeed, it could happen that new interrupt has been generated during the stop_streaming processing hence clear them in order to avoid getting a new interrupt right from the start of a next start_streaming. Fixes: 28e0f3772296 ("media: stm32-dcmipp: STM32 DCMIPP camera interface driver") Cc: stable@vger.kernel.org Signed-off-by: Alain Volmat Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c index 1c1b6b48918e..b18e273ef4a3 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c @@ -512,6 +512,9 @@ static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) /* Disable pipe */ reg_clear(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN); + /* Clear any pending interrupts */ + reg_write(vcap, DCMIPP_CMFCR, DCMIPP_CMIER_P0ALL); + spin_lock_irq(&vcap->irqlock); /* Return all queued buffers to vb2 in ERROR state */ From 3363aa2640f1738ad7fc56ea56f5e0301ad97196 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 19 Dec 2025 15:30:36 +0100 Subject: [PATCH 218/307] media: stm32: dcmipp: byteproc: disable compose for all bayers Avoid possibility to perform compose on all frames which mbus code is within the bayer range or jpeg format. Fixes: 822c72eb1519 ("media: stm32: dcmipp: add bayer 10~14 bits formats") Cc: stable@vger.kernel.org Signed-off-by: Alain Volmat Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c index db76a02a1848..ec1d773d5ad1 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c @@ -130,11 +130,8 @@ static void dcmipp_byteproc_adjust_compose(struct v4l2_rect *r, r->left = 0; /* Compose is not possible for JPEG or Bayer formats */ - if (fmt->code == MEDIA_BUS_FMT_JPEG_1X8 || - fmt->code == MEDIA_BUS_FMT_SBGGR8_1X8 || - fmt->code == MEDIA_BUS_FMT_SGBRG8_1X8 || - fmt->code == MEDIA_BUS_FMT_SGRBG8_1X8 || - fmt->code == MEDIA_BUS_FMT_SRGGB8_1X8) { + if (fmt->code >= MEDIA_BUS_FMT_SBGGR8_1X8 && + fmt->code <= MEDIA_BUS_FMT_JPEG_1X8) { r->width = fmt->width; r->height = fmt->height; return; From f29f8bfcb0555801e22e29acd2c83c3d3ee9d20d Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 19 Dec 2025 15:30:37 +0100 Subject: [PATCH 219/307] media: stm32: dcmipp: Add RGB888 format support overall Add entries within several subdevs in order to handle RGB888 format. Signed-off-by: Alain Volmat Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c | 1 + .../media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c | 2 ++ drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c index b18e273ef4a3..6d42517817fd 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c @@ -57,6 +57,7 @@ struct dcmipp_bytecap_pix_map { static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pix_map_list[] = { PIXMAP_MBUS_PFMT(RGB565_2X8_LE, RGB565), PIXMAP_MBUS_PFMT(RGB565_1X16, RGB565), + PIXMAP_MBUS_PFMT(RGB888_1X24, RGB24), PIXMAP_MBUS_PFMT(YUYV8_2X8, YUYV), PIXMAP_MBUS_PFMT(YUYV8_1X16, YUYV), PIXMAP_MBUS_PFMT(YVYU8_2X8, YVYU), diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c index ec1d773d5ad1..dde28ed288cd 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c @@ -49,6 +49,8 @@ struct dcmipp_byteproc_pix_map { static const struct dcmipp_byteproc_pix_map dcmipp_byteproc_pix_map_list[] = { PIXMAP_MBUS_BPP(RGB565_2X8_LE, 2), PIXMAP_MBUS_BPP(RGB565_1X16, 2), + PIXMAP_MBUS_BPP(RGB888_3X8, 3), + PIXMAP_MBUS_BPP(RGB888_1X24, 3), PIXMAP_MBUS_BPP(YUYV8_2X8, 2), PIXMAP_MBUS_BPP(YUYV8_1X16, 2), PIXMAP_MBUS_BPP(YVYU8_2X8, 2), diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c index 7e5311b67d7e..c44d664f6a0b 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c @@ -19,6 +19,7 @@ #define DCMIPP_PRCR_FORMAT_SHIFT 16 #define DCMIPP_PRCR_FORMAT_YUV422 0x1e #define DCMIPP_PRCR_FORMAT_RGB565 0x22 +#define DCMIPP_PRCR_FORMAT_RGB888 0x24 #define DCMIPP_PRCR_FORMAT_RAW8 0x2a #define DCMIPP_PRCR_FORMAT_RAW10 0x2b #define DCMIPP_PRCR_FORMAT_RAW12 0x2c @@ -72,6 +73,9 @@ static const struct dcmipp_inp_pix_map dcmipp_inp_pix_map_list[] = { PIXMAP_SINK_SRC_PRCR_SWAP(RGB565_2X8_LE, RGB565_2X8_LE, RGB565, 1, MIPI_CSI2_DT_RGB565), PIXMAP_SINK_SRC_PRCR_SWAP(RGB565_2X8_BE, RGB565_2X8_LE, RGB565, 0, MIPI_CSI2_DT_RGB565), PIXMAP_SINK_SRC_PRCR_SWAP(RGB565_1X16, RGB565_1X16, RGB565, 0, MIPI_CSI2_DT_RGB565), + /* RGB888 */ + PIXMAP_SINK_SRC_PRCR_SWAP(RGB888_3X8, RGB888_3X8, RGB888, 0, MIPI_CSI2_DT_RGB888), + PIXMAP_SINK_SRC_PRCR_SWAP(RGB888_1X24, RGB888_1X24, RGB888, 0, MIPI_CSI2_DT_RGB888), /* YUV422 */ PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, YUYV8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_1X16, YUYV8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), From 0085da1315898ea27bcb323e456ca1d39d43e96a Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 19 Dec 2025 15:30:38 +0100 Subject: [PATCH 220/307] media: stm32: dcmipp: add Y10-Y12-Y14 in all subdevs DCMIPP can handle Y8 to Y14 input formats however until now only Y8 was handled. Add support for Y10-Y12-Y14 in all relevant subdevs. Signed-off-by: Alain Volmat Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c | 3 +++ .../media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c | 3 +++ drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c index 6d42517817fd..dcb791c56d4c 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c @@ -67,6 +67,9 @@ static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pix_map_list[] = { PIXMAP_MBUS_PFMT(VYUY8_2X8, VYUY), PIXMAP_MBUS_PFMT(VYUY8_1X16, VYUY), PIXMAP_MBUS_PFMT(Y8_1X8, GREY), + PIXMAP_MBUS_PFMT(Y10_1X10, Y10), + PIXMAP_MBUS_PFMT(Y12_1X12, Y12), + PIXMAP_MBUS_PFMT(Y14_1X14, Y14), PIXMAP_MBUS_PFMT(SBGGR8_1X8, SBGGR8), PIXMAP_MBUS_PFMT(SGBRG8_1X8, SGBRG8), PIXMAP_MBUS_PFMT(SGRBG8_1X8, SGRBG8), diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c index dde28ed288cd..f45a3027f8f3 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c @@ -60,6 +60,9 @@ static const struct dcmipp_byteproc_pix_map dcmipp_byteproc_pix_map_list[] = { PIXMAP_MBUS_BPP(VYUY8_2X8, 2), PIXMAP_MBUS_BPP(VYUY8_1X16, 2), PIXMAP_MBUS_BPP(Y8_1X8, 1), + PIXMAP_MBUS_BPP(Y10_1X10, 2), + PIXMAP_MBUS_BPP(Y12_1X12, 2), + PIXMAP_MBUS_BPP(Y14_1X14, 2), PIXMAP_MBUS_BPP(SBGGR8_1X8, 1), PIXMAP_MBUS_BPP(SGBRG8_1X8, 1), PIXMAP_MBUS_BPP(SGRBG8_1X8, 1), diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c index c44d664f6a0b..c4bc76909b1c 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c @@ -25,6 +25,9 @@ #define DCMIPP_PRCR_FORMAT_RAW12 0x2c #define DCMIPP_PRCR_FORMAT_RAW14 0x2d #define DCMIPP_PRCR_FORMAT_G8 0x4a +#define DCMIPP_PRCR_FORMAT_G10 0x4b +#define DCMIPP_PRCR_FORMAT_G12 0x4c +#define DCMIPP_PRCR_FORMAT_G14 0x4d #define DCMIPP_PRCR_FORMAT_BYTE_STREAM 0x5a #define DCMIPP_PRCR_ESS BIT(4) #define DCMIPP_PRCR_PCKPOL BIT(5) @@ -89,6 +92,9 @@ static const struct dcmipp_inp_pix_map dcmipp_inp_pix_map_list[] = { PIXMAP_SINK_SRC_PRCR_SWAP(VYUY8_1X16, VYUY8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), /* GREY */ PIXMAP_SINK_SRC_PRCR_SWAP(Y8_1X8, Y8_1X8, G8, 0, MIPI_CSI2_DT_RAW8), + PIXMAP_SINK_SRC_PRCR_SWAP(Y10_1X10, Y10_1X10, G10, 0, MIPI_CSI2_DT_RAW10), + PIXMAP_SINK_SRC_PRCR_SWAP(Y12_1X12, Y12_1X12, G12, 0, MIPI_CSI2_DT_RAW12), + PIXMAP_SINK_SRC_PRCR_SWAP(Y14_1X14, Y14_1X14, G14, 0, MIPI_CSI2_DT_RAW14), /* Raw Bayer */ PIXMAP_SINK_SRC_PRCR_SWAP(SBGGR8_1X8, SBGGR8_1X8, RAW8, 0, MIPI_CSI2_DT_RAW8), PIXMAP_SINK_SRC_PRCR_SWAP(SGBRG8_1X8, SGBRG8_1X8, RAW8, 0, MIPI_CSI2_DT_RAW8), From 8a71f1f8d91fb10d306857c0f198a940c1d22e06 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 19 Dec 2025 15:30:39 +0100 Subject: [PATCH 221/307] media: stm32: dcmipp: byteproc: only allow compose with 8/16bit fmts Since the byteproc is working at byte level, it is not possible to perform decimation with formats other than 8 or 16 bit per pixel. Ensure any other format won't be able to set a compose. Signed-off-by: Alain Volmat Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c index f45a3027f8f3..f9e4a3a9ef3f 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c @@ -131,6 +131,8 @@ static void dcmipp_byteproc_adjust_crop(struct v4l2_rect *r, static void dcmipp_byteproc_adjust_compose(struct v4l2_rect *r, const struct v4l2_mbus_framefmt *fmt) { + const struct dcmipp_byteproc_pix_map *vpix; + r->top = 0; r->left = 0; @@ -142,6 +144,14 @@ static void dcmipp_byteproc_adjust_compose(struct v4l2_rect *r, return; } + /* Prevent compose on formats which are not 1 or 2 bytes per pixel */ + vpix = dcmipp_byteproc_pix_map_by_code(fmt->code); + if (vpix->bpp != 1 && vpix->bpp != 2) { + r->width = fmt->width; + r->height = fmt->height; + return; + } + /* Adjust height - we can only perform 1/2 decimation */ if (r->height <= (fmt->height / 2)) r->height = fmt->height / 2; @@ -149,7 +159,7 @@ static void dcmipp_byteproc_adjust_compose(struct v4l2_rect *r, r->height = fmt->height; /* Adjust width /2 or /4 for 8bits formats and /2 for 16bits formats */ - if (fmt->code == MEDIA_BUS_FMT_Y8_1X8 && r->width <= (fmt->width / 4)) + if (vpix->bpp == 1 && r->width <= (fmt->width / 4)) r->width = fmt->width / 4; else if (r->width <= (fmt->width / 2)) r->width = fmt->width / 2; From 8e3c7238481bb36485935151ce4ee0dc4cb72dca Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 19 Dec 2025 15:30:40 +0100 Subject: [PATCH 222/307] media: stm32: dcmipp: bytecap: remove useless cmier variable The struct dcmipp_bytecap_device embeds a cmier variable which is actually only set once and then used in several places for interrupt mask or interrupt enable/disable. Use the DCMIPP_CMIER_P0ALL bitfield macro directly instead of using it through the cmier variable. There is also no need to mask again vcap->cmsr2 against enabled interrupts bit since this has already been done when storing the interrupt status into the cmsr2 variable. Signed-off-by: Alain Volmat Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../st/stm32/stm32-dcmipp/dcmipp-bytecap.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c index dcb791c56d4c..19e6b187be22 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c @@ -147,7 +147,6 @@ struct dcmipp_bytecap_device { void __iomem *regs; - u32 cmier; u32 cmsr2; struct { @@ -443,8 +442,7 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, dcmipp_start_capture(vcap, vcap->next); /* Enable interruptions */ - vcap->cmier |= DCMIPP_CMIER_P0ALL; - reg_set(vcap, DCMIPP_CMIER, vcap->cmier); + reg_set(vcap, DCMIPP_CMIER, DCMIPP_CMIER_P0ALL); vcap->state = DCMIPP_RUNNING; @@ -500,7 +498,7 @@ static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) media_pipeline_stop(vcap->vdev.entity.pads); /* Disable interruptions */ - reg_clear(vcap, DCMIPP_CMIER, vcap->cmier); + reg_clear(vcap, DCMIPP_CMIER, DCMIPP_CMIER_P0ALL); /* Stop capture */ reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ); @@ -749,23 +747,20 @@ static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg) struct dcmipp_bytecap_device *vcap = container_of(arg, struct dcmipp_bytecap_device, ved); size_t bytesused = 0; - u32 cmsr2; spin_lock_irq(&vcap->irqlock); - cmsr2 = vcap->cmsr2 & vcap->cmier; - /* * If we have an overrun, a frame-end will probably not be generated, * in that case the active buffer will be recycled as next buffer by * the VSYNC handler */ - if (cmsr2 & DCMIPP_CMSR2_P0OVRF) { + if (vcap->cmsr2 & DCMIPP_CMSR2_P0OVRF) { vcap->count.errors++; vcap->count.overrun++; } - if (cmsr2 & DCMIPP_CMSR2_P0FRAMEF) { + if (vcap->cmsr2 & DCMIPP_CMSR2_P0FRAMEF) { vcap->count.frame++; /* Read captured buffer size */ @@ -773,7 +768,7 @@ static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg) dcmipp_bytecap_process_frame(vcap, bytesused); } - if (cmsr2 & DCMIPP_CMSR2_P0VSYNCF) { + if (vcap->cmsr2 & DCMIPP_CMSR2_P0VSYNCF) { vcap->count.vsync++; if (vcap->state == DCMIPP_WAIT_FOR_BUFFER) { vcap->count.underrun++; @@ -804,7 +799,7 @@ static irqreturn_t dcmipp_bytecap_irq_callback(int irq, void *arg) container_of(arg, struct dcmipp_bytecap_device, ved); /* Store interrupt status register */ - vcap->cmsr2 = reg_read(vcap, DCMIPP_CMSR2) & vcap->cmier; + vcap->cmsr2 = reg_read(vcap, DCMIPP_CMSR2) & DCMIPP_CMIER_P0ALL; vcap->count.it++; /* Clear interrupt */ From 2eeefb22f9409ad24213184e967f63fe77f21194 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 14 Jan 2026 11:54:15 +0200 Subject: [PATCH 223/307] media: ipu6: Update e-mail addresses Remove Hongju's e-mail address that no longer works. Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys.c | 2 +- drivers/media/pci/intel/ipu6/ipu6.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 5a8becff65ae..c9cdeb7054d7 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -1358,7 +1358,7 @@ MODULE_AUTHOR("Sakari Ailus "); MODULE_AUTHOR("Tianshu Qiu "); MODULE_AUTHOR("Bingbu Cao "); MODULE_AUTHOR("Yunliang Ding "); -MODULE_AUTHOR("Hongju Wang "); +MODULE_AUTHOR("Hongju Wang"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Intel IPU6 input system driver"); MODULE_IMPORT_NS("INTEL_IPU6"); diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index a2768f44017a..24238f8311a6 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -843,6 +843,6 @@ MODULE_AUTHOR("Tianshu Qiu "); MODULE_AUTHOR("Bingbu Cao "); MODULE_AUTHOR("Qingwu Zhang "); MODULE_AUTHOR("Yunliang Ding "); -MODULE_AUTHOR("Hongju Wang "); +MODULE_AUTHOR("Hongju Wang"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Intel IPU6 PCI driver"); From 90002a0ba29ebda54853f2113e769d651f509309 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 12 Jan 2026 22:45:56 +0200 Subject: [PATCH 224/307] media: ov01a10: Fix indentation Fix indentation in ov01a10_get_pm_resources(). Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 10227ae6fe42..1aeba3df4cbf 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -846,7 +846,7 @@ static int ov01a10_get_pm_resources(struct ov01a10 *ov01a10) "getting reset gpio\n"); ov01a10->powerdown = devm_gpiod_get_optional(ov01a10->dev, "powerdown", - GPIOD_OUT_HIGH); + GPIOD_OUT_HIGH); if (IS_ERR(ov01a10->powerdown)) return dev_err_probe(ov01a10->dev, PTR_ERR(ov01a10->powerdown), "getting powerdown gpio\n"); From 91848c99ed6a98daf77f4cb7d44cf3f13bc6998f Mon Sep 17 00:00:00 2001 From: Mehdi Djait Date: Thu, 8 Jan 2026 14:57:38 +0100 Subject: [PATCH 225/307] media: i2c: ov01a10: Fix digital gain range Digital gain wraps-around at the maximum of 16838 / 0x3fff. Fix the maximum digital gain by setting it to 0x3fff. Signed-off-by: Mehdi Djait Reviewed-by: Hans de Goede Fixes: 0827b58dabff ("media: i2c: add ov01a10 image sensor driver") Cc: stable@vger.kernel.org Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov01a10.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 1aeba3df4cbf..8a29e5b4b6ba 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -63,7 +63,7 @@ #define OV01A10_REG_DIGITAL_GAIN_GR CCI_REG24(0x3513) #define OV01A10_REG_DIGITAL_GAIN_R CCI_REG24(0x3516) #define OV01A10_DGTL_GAIN_MIN 0 -#define OV01A10_DGTL_GAIN_MAX 0x3ffff +#define OV01A10_DGTL_GAIN_MAX 0x3fff #define OV01A10_DGTL_GAIN_STEP 1 #define OV01A10_DGTL_GAIN_DEFAULT 1024 From 0fc3caab96c9e713e897ff4cf3b42b1b409236d1 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 25 Sep 2023 11:23:08 +0300 Subject: [PATCH 226/307] media: ccs: Remove ccs_get_crop_compose helper As it's now easier to obtain the necessary information on crop and compose rectangles after moving to sub-device state, remove the ccs_get_crop_compose helper. Also remove the comp arguments of the compose goodness calculators and make related local variables and function arguments const where applicable. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 161 ++++++++++++++----------------- 1 file changed, 71 insertions(+), 90 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index bc832f5ccd29..5d931e1fb46d 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -2091,24 +2091,6 @@ static int ccs_get_format(struct v4l2_subdev *subdev, return rval; } -static void ccs_get_crop_compose(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *sd_state, - struct v4l2_rect **crops, - struct v4l2_rect **comps) -{ - struct ccs_subdev *ssd = to_ccs_subdev(subdev); - unsigned int i; - - if (crops) - for (i = 0; i < subdev->entity.num_pads; i++) - crops[i] = - v4l2_subdev_state_get_crop(sd_state, i, - CCS_STREAM_PIXEL); - if (comps) - *comps = v4l2_subdev_state_get_compose(sd_state, ssd->sink_pad, - CCS_STREAM_PIXEL); -} - /* Changes require propagation only on sink pad. */ static void ccs_propagate(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state, int which, @@ -2116,15 +2098,17 @@ static void ccs_propagate(struct v4l2_subdev *subdev, { struct ccs_sensor *sensor = to_ccs_sensor(subdev); struct ccs_subdev *ssd = to_ccs_subdev(subdev); - struct v4l2_rect *comp, *crops[CCS_PADS]; + struct v4l2_rect *comp, *crop; struct v4l2_mbus_framefmt *fmt; - ccs_get_crop_compose(subdev, sd_state, crops, &comp); - + comp = v4l2_subdev_state_get_compose(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); switch (target) { case V4L2_SEL_TGT_CROP: - comp->width = crops[CCS_PAD_SINK]->width; - comp->height = crops[CCS_PAD_SINK]->height; + crop = v4l2_subdev_state_get_crop(sd_state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + comp->width = crop->width; + comp->height = crop->height; if (which == V4L2_SUBDEV_FORMAT_ACTIVE) { if (ssd == sensor->scaler) { sensor->scale_m = CCS_LIM(sensor, SCALER_N_MIN); @@ -2138,13 +2122,15 @@ static void ccs_propagate(struct v4l2_subdev *subdev, } fallthrough; case V4L2_SEL_TGT_COMPOSE: - *crops[CCS_PAD_SRC] = *comp; + crop = v4l2_subdev_state_get_crop(sd_state, CCS_PAD_SRC, + CCS_STREAM_PIXEL); + *crop = *comp; fmt = v4l2_subdev_state_get_format(sd_state, CCS_PAD_SRC, CCS_STREAM_PIXEL); fmt->width = comp->width; fmt->height = comp->height; if (which == V4L2_SUBDEV_FORMAT_ACTIVE && ssd == sensor->src) - sensor->src_src = *crops[CCS_PAD_SRC]; + sensor->src_src = *crop; break; default: WARN_ON_ONCE(1); @@ -2210,7 +2196,7 @@ static int ccs_set_format(struct v4l2_subdev *subdev, { struct ccs_sensor *sensor = to_ccs_sensor(subdev); struct ccs_subdev *ssd = to_ccs_subdev(subdev); - struct v4l2_rect *crops[CCS_PADS]; + struct v4l2_rect *crop; mutex_lock(&sensor->mutex); @@ -2239,12 +2225,13 @@ static int ccs_set_format(struct v4l2_subdev *subdev, CCS_LIM(sensor, MIN_Y_OUTPUT_SIZE), CCS_LIM(sensor, MAX_Y_OUTPUT_SIZE)); - ccs_get_crop_compose(subdev, sd_state, crops, NULL); + crop = v4l2_subdev_state_get_crop(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); - crops[ssd->sink_pad]->left = 0; - crops[ssd->sink_pad]->top = 0; - crops[ssd->sink_pad]->width = fmt->format.width; - crops[ssd->sink_pad]->height = fmt->format.height; + crop->left = 0; + crop->top = 0; + crop->width = fmt->format.width; + crop->height = fmt->format.height; ccs_propagate(subdev, sd_state, fmt->which, V4L2_SEL_TGT_CROP); mutex_unlock(&sensor->mutex); @@ -2299,26 +2286,23 @@ static int scaling_goodness(struct v4l2_subdev *subdev, int w, int ask_w, static void ccs_set_compose_binner(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel, - struct v4l2_rect **crops, - struct v4l2_rect *comp) + const struct v4l2_rect *sink_crop) { struct ccs_sensor *sensor = to_ccs_sensor(subdev); unsigned int i; unsigned int binh = 1, binv = 1; - int best = scaling_goodness( - subdev, - crops[CCS_PAD_SINK]->width, sel->r.width, - crops[CCS_PAD_SINK]->height, sel->r.height, sel->flags); + int best = scaling_goodness(subdev, sink_crop->width, sel->r.width, + sink_crop->height, sel->r.height, + sel->flags); for (i = 0; i < sensor->nbinning_subtypes; i++) { - int this = scaling_goodness( - subdev, - crops[CCS_PAD_SINK]->width - / sensor->binning_subtypes[i].horizontal, - sel->r.width, - crops[CCS_PAD_SINK]->height - / sensor->binning_subtypes[i].vertical, - sel->r.height, sel->flags); + int this = scaling_goodness(subdev, + sink_crop->width + / sensor->binning_subtypes[i].horizontal, + sel->r.width, + sink_crop->height + / sensor->binning_subtypes[i].vertical, + sel->r.height, sel->flags); if (this > best) { binh = sensor->binning_subtypes[i].horizontal; @@ -2331,8 +2315,8 @@ static void ccs_set_compose_binner(struct v4l2_subdev *subdev, sensor->binning_horizontal = binh; } - sel->r.width = (crops[CCS_PAD_SINK]->width / binh) & ~1; - sel->r.height = (crops[CCS_PAD_SINK]->height / binv) & ~1; + sel->r.width = (sink_crop->width / binh) & ~1; + sel->r.height = (sink_crop->height / binv) & ~1; } /* @@ -2347,8 +2331,7 @@ static void ccs_set_compose_binner(struct v4l2_subdev *subdev, static void ccs_set_compose_scaler(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel, - struct v4l2_rect **crops, - struct v4l2_rect *comp) + const struct v4l2_rect *sink_crop) { struct i2c_client *client = v4l2_get_subdevdata(subdev); struct ccs_sensor *sensor = to_ccs_sensor(subdev); @@ -2360,16 +2343,12 @@ static void ccs_set_compose_scaler(struct v4l2_subdev *subdev, unsigned int i; int best = INT_MIN; - sel->r.width = min_t(unsigned int, sel->r.width, - crops[CCS_PAD_SINK]->width); - sel->r.height = min_t(unsigned int, sel->r.height, - crops[CCS_PAD_SINK]->height); + sel->r.width = min_t(unsigned int, sel->r.width, sink_crop->width); + sel->r.height = min_t(unsigned int, sel->r.height, sink_crop->height); - a = crops[CCS_PAD_SINK]->width - * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.width; - b = crops[CCS_PAD_SINK]->height - * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.height; - max_m = crops[CCS_PAD_SINK]->width + a = sink_crop->width * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.width; + b = sink_crop->height * CCS_LIM(sensor, SCALER_N_MIN) / sel->r.height; + max_m = sink_crop->width * CCS_LIM(sensor, SCALER_N_MIN) / (CCS_LIM(sensor, MIN_X_OUTPUT_SIZE) ?: 1); @@ -2401,14 +2380,12 @@ static void ccs_set_compose_scaler(struct v4l2_subdev *subdev, } for (i = 0; i < ntry; i++) { - int this = scaling_goodness( - subdev, - crops[CCS_PAD_SINK]->width - / try[i] * CCS_LIM(sensor, SCALER_N_MIN), - sel->r.width, - crops[CCS_PAD_SINK]->height, - sel->r.height, - sel->flags); + int this = scaling_goodness(subdev, + sink_crop->width + / try[i] + * CCS_LIM(sensor, SCALER_N_MIN), + sel->r.width, sink_crop->height, + sel->r.height, sel->flags); dev_dbg(&client->dev, "trying factor %u (%u)\n", try[i], i); @@ -2423,12 +2400,10 @@ static void ccs_set_compose_scaler(struct v4l2_subdev *subdev, continue; this = scaling_goodness( - subdev, crops[CCS_PAD_SINK]->width - / try[i] + subdev, sink_crop->width / try[i] * CCS_LIM(sensor, SCALER_N_MIN), sel->r.width, - crops[CCS_PAD_SINK]->height - / try[i] + sink_crop->height / try[i] * CCS_LIM(sensor, SCALER_N_MIN), sel->r.height, sel->flags); @@ -2440,18 +2415,13 @@ static void ccs_set_compose_scaler(struct v4l2_subdev *subdev, } } - sel->r.width = - (crops[CCS_PAD_SINK]->width - / scale_m - * CCS_LIM(sensor, SCALER_N_MIN)) & ~1; + sel->r.width = (sink_crop->width / scale_m + * CCS_LIM(sensor, SCALER_N_MIN)) & ~1; if (mode == SMIAPP_SCALING_MODE_BOTH) - sel->r.height = - (crops[CCS_PAD_SINK]->height - / scale_m - * CCS_LIM(sensor, SCALER_N_MIN)) - & ~1; + sel->r.height = (sink_crop->height / scale_m + * CCS_LIM(sensor, SCALER_N_MIN)) & ~1; else - sel->r.height = crops[CCS_PAD_SINK]->height; + sel->r.height = sink_crop->height; if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { sensor->scale_m = scale_m; @@ -2465,17 +2435,21 @@ static int ccs_set_compose(struct v4l2_subdev *subdev, { struct ccs_sensor *sensor = to_ccs_sensor(subdev); struct ccs_subdev *ssd = to_ccs_subdev(subdev); - struct v4l2_rect *comp, *crops[CCS_PADS]; + const struct v4l2_rect *sink_crop; + struct v4l2_rect *comp; - ccs_get_crop_compose(subdev, sd_state, crops, &comp); + sink_crop = v4l2_subdev_state_get_crop(sd_state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + comp = v4l2_subdev_state_get_compose(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); sel->r.top = 0; sel->r.left = 0; if (ssd == sensor->binner) - ccs_set_compose_binner(subdev, sd_state, sel, crops, comp); + ccs_set_compose_binner(subdev, sd_state, sel, sink_crop); else - ccs_set_compose_scaler(subdev, sd_state, sel, crops, comp); + ccs_set_compose_scaler(subdev, sd_state, sel, sink_crop); *comp = sel->r; ccs_propagate(subdev, sd_state, sel->which, V4L2_SEL_TGT_COMPOSE); @@ -2530,9 +2504,13 @@ static int ccs_set_crop(struct v4l2_subdev *subdev, { struct ccs_sensor *sensor = to_ccs_sensor(subdev); struct ccs_subdev *ssd = to_ccs_subdev(subdev); - struct v4l2_rect src_size = { 0 }, *crops[CCS_PADS], *comp; + struct v4l2_rect src_size = { 0 }, *crop; + const struct v4l2_rect *comp; - ccs_get_crop_compose(subdev, sd_state, crops, &comp); + crop = v4l2_subdev_state_get_crop(sd_state, sel->pad, + CCS_STREAM_PIXEL); + comp = v4l2_subdev_state_get_compose(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); if (sel->pad == ssd->sink_pad) { struct v4l2_mbus_framefmt *mfmt = @@ -2555,7 +2533,7 @@ static int ccs_set_crop(struct v4l2_subdev *subdev, sel->r.left = min_t(int, sel->r.left, src_size.width - sel->r.width); sel->r.top = min_t(int, sel->r.top, src_size.height - sel->r.height); - *crops[sel->pad] = sel->r; + *crop = sel->r; if (ssd != sensor->pixel_array && sel->pad == CCS_PAD_SINK) ccs_propagate(subdev, sd_state, sel->which, V4L2_SEL_TGT_CROP); @@ -2580,14 +2558,17 @@ static int ccs_get_selection(struct v4l2_subdev *subdev, { struct ccs_sensor *sensor = to_ccs_sensor(subdev); struct ccs_subdev *ssd = to_ccs_subdev(subdev); - struct v4l2_rect *comp, *crops[CCS_PADS]; + const struct v4l2_rect *crop, *comp; int ret; ret = ccs_sel_supported(subdev, sel); if (ret) return ret; - ccs_get_crop_compose(subdev, sd_state, crops, &comp); + crop = v4l2_subdev_state_get_crop(sd_state, sel->pad, + CCS_STREAM_PIXEL); + comp = v4l2_subdev_state_get_compose(sd_state, ssd->sink_pad, + CCS_STREAM_PIXEL); switch (sel->target) { case V4L2_SEL_TGT_CROP_BOUNDS: @@ -2607,7 +2588,7 @@ static int ccs_get_selection(struct v4l2_subdev *subdev, break; case V4L2_SEL_TGT_CROP: case V4L2_SEL_TGT_COMPOSE_BOUNDS: - sel->r = *crops[sel->pad]; + sel->r = *crop; break; case V4L2_SEL_TGT_COMPOSE: sel->r = *comp; From a88883d1209c3c759ebaa7886d9a7098d5ed1132 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 6 Sep 2023 11:04:31 +0300 Subject: [PATCH 227/307] media: ccs: Rely on sub-device state locking Rely on sub-device state locking to serialise access to driver's data structures. The driver-provided mutex is used as the state lock for all driver sub-devices. Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 146 +++++++++++++++---------------- drivers/media/i2c/ccs/ccs.h | 1 - 2 files changed, 70 insertions(+), 77 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 5d931e1fb46d..a08f06149097 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -541,12 +541,13 @@ static int ccs_pll_update(struct ccs_sensor *sensor) * */ -static void __ccs_update_exposure_limits(struct ccs_sensor *sensor) +static void __ccs_update_exposure_limits(struct ccs_sensor *sensor, + const struct v4l2_rect *pa_src) { struct v4l2_ctrl *ctrl = sensor->exposure; int max; - max = sensor->pa_src.height + sensor->vblank->val - + max = pa_src->height + sensor->vblank->val - CCS_LIM(sensor, COARSE_INTEGRATION_TIME_MAX_MARGIN); __v4l2_ctrl_modify_range(ctrl, ctrl->minimum, max, ctrl->step, max); @@ -649,12 +650,20 @@ static int ccs_set_ctrl(struct v4l2_ctrl *ctrl) container_of(ctrl->handler, struct ccs_subdev, ctrl_handler) ->sensor; struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); + struct v4l2_subdev_state *state; + const struct v4l2_rect *pa_src = NULL; int pm_status; u32 orient = 0; unsigned int i; int exposure; int rval; + if (ctrl->id == V4L2_CID_VBLANK || ctrl->id == V4L2_CID_HBLANK) { + state = v4l2_subdev_get_locked_active_state(&sensor->pixel_array->sd); + pa_src = v4l2_subdev_state_get_crop(state, CCS_PA_PAD_SRC, + CCS_STREAM_PIXEL); + } + switch (ctrl->id) { case V4L2_CID_HFLIP: case V4L2_CID_VFLIP: @@ -673,7 +682,7 @@ static int ccs_set_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_VBLANK: exposure = sensor->exposure->val; - __ccs_update_exposure_limits(sensor); + __ccs_update_exposure_limits(sensor, pa_src); if (exposure > sensor->exposure->maximum) { sensor->exposure->val = sensor->exposure->maximum; @@ -765,12 +774,12 @@ static int ccs_set_ctrl(struct v4l2_ctrl *ctrl) break; case V4L2_CID_VBLANK: rval = ccs_write(sensor, FRAME_LENGTH_LINES, - sensor->pa_src.height + ctrl->val); + pa_src->height + ctrl->val); break; case V4L2_CID_HBLANK: rval = ccs_write(sensor, LINE_LENGTH_PCK, - sensor->pa_src.width + ctrl->val); + pa_src->width + ctrl->val); break; case V4L2_CID_TEST_PATTERN: @@ -1223,7 +1232,8 @@ static int ccs_get_mbus_formats(struct ccs_sensor *sensor) return 0; } -static void ccs_update_blanking(struct ccs_sensor *sensor) +static void ccs_update_blanking(struct ccs_sensor *sensor, + const struct v4l2_rect *pa_src) { struct v4l2_ctrl *vblank = sensor->vblank; struct v4l2_ctrl *hblank = sensor->hblank; @@ -1246,21 +1256,26 @@ static void ccs_update_blanking(struct ccs_sensor *sensor) min = max_t(int, CCS_LIM(sensor, MIN_FRAME_BLANKING_LINES), - min_fll - sensor->pa_src.height); - max = max_fll - sensor->pa_src.height; + min_fll - pa_src->height); + max = max_fll - pa_src->height; __v4l2_ctrl_modify_range(vblank, min, max, vblank->step, min); - min = max_t(int, min_llp - sensor->pa_src.width, min_lbp); - max = max_llp - sensor->pa_src.width; + min = max_t(int, min_llp - pa_src->width, min_lbp); + max = max_llp - pa_src->width; __v4l2_ctrl_modify_range(hblank, min, max, hblank->step, min); - __ccs_update_exposure_limits(sensor); + __ccs_update_exposure_limits(sensor, pa_src); } static int ccs_pll_blanking_update(struct ccs_sensor *sensor) { + struct v4l2_subdev_state *state = + v4l2_subdev_get_locked_active_state(&sensor->pixel_array->sd); + const struct v4l2_rect *pa_src = + v4l2_subdev_state_get_crop(state, CCS_PA_PAD_SRC, + CCS_STREAM_PIXEL); struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); int rval; @@ -1269,15 +1284,15 @@ static int ccs_pll_blanking_update(struct ccs_sensor *sensor) return rval; /* Output from pixel array, including blanking */ - ccs_update_blanking(sensor); + ccs_update_blanking(sensor, pa_src); dev_dbg(&client->dev, "vblank\t\t%d\n", sensor->vblank->val); dev_dbg(&client->dev, "hblank\t\t%d\n", sensor->hblank->val); dev_dbg(&client->dev, "real timeperframe\t100/%d\n", sensor->pll.pixel_rate_pixel_array / - ((sensor->pa_src.width + sensor->hblank->val) * - (sensor->pa_src.height + sensor->vblank->val) / 100)); + ((pa_src->width + sensor->hblank->val) * + (pa_src->height + sensor->vblank->val) / 100)); return 0; } @@ -1782,6 +1797,16 @@ static int ccs_enable_streams(struct v4l2_subdev *subdev, u64 streams_mask) { struct ccs_sensor *sensor = to_ccs_sensor(subdev); + struct v4l2_subdev_state *pa_state = + v4l2_subdev_get_locked_active_state(&sensor->pixel_array->sd); + struct v4l2_subdev_state *src_state = + v4l2_subdev_get_locked_active_state(&sensor->src->sd); + const struct v4l2_rect *pa_src = + v4l2_subdev_state_get_crop(pa_state, CCS_PA_PAD_SRC, + CCS_STREAM_PIXEL); + const struct v4l2_rect *src_src = + v4l2_subdev_state_get_crop(src_state, CCS_PAD_SRC, + CCS_STREAM_PIXEL); struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); unsigned int binning_mode; int rval; @@ -1829,22 +1854,20 @@ static int ccs_enable_streams(struct v4l2_subdev *subdev, goto err_pm_put; /* Analog crop start coordinates */ - rval = ccs_write(sensor, X_ADDR_START, sensor->pa_src.left); + rval = ccs_write(sensor, X_ADDR_START, pa_src->left); if (rval < 0) goto err_pm_put; - rval = ccs_write(sensor, Y_ADDR_START, sensor->pa_src.top); + rval = ccs_write(sensor, Y_ADDR_START, pa_src->top); if (rval < 0) goto err_pm_put; /* Analog crop end coordinates */ - rval = ccs_write(sensor, X_ADDR_END, - sensor->pa_src.left + sensor->pa_src.width - 1); + rval = ccs_write(sensor, X_ADDR_END, pa_src->left + pa_src->width - 1); if (rval < 0) goto err_pm_put; - rval = ccs_write(sensor, Y_ADDR_END, - sensor->pa_src.top + sensor->pa_src.height - 1); + rval = ccs_write(sensor, Y_ADDR_END, pa_src->top + pa_src->height - 1); if (rval < 0) goto err_pm_put; @@ -1856,23 +1879,30 @@ static int ccs_enable_streams(struct v4l2_subdev *subdev, /* Digital crop */ if (CCS_LIM(sensor, DIGITAL_CROP_CAPABILITY) == CCS_DIGITAL_CROP_CAPABILITY_INPUT_CROP) { + struct v4l2_subdev_state *scaler_state = + v4l2_subdev_get_locked_active_state(&sensor->scaler->sd); + const struct v4l2_rect *scaler_sink = + v4l2_subdev_state_get_crop(scaler_state, + sensor->scaler->sink_pad, + CCS_STREAM_PIXEL); + rval = ccs_write(sensor, DIGITAL_CROP_X_OFFSET, - sensor->scaler_sink.left); + scaler_sink->left); if (rval < 0) goto err_pm_put; rval = ccs_write(sensor, DIGITAL_CROP_Y_OFFSET, - sensor->scaler_sink.top); + scaler_sink->top); if (rval < 0) goto err_pm_put; rval = ccs_write(sensor, DIGITAL_CROP_IMAGE_WIDTH, - sensor->scaler_sink.width); + scaler_sink->width); if (rval < 0) goto err_pm_put; rval = ccs_write(sensor, DIGITAL_CROP_IMAGE_HEIGHT, - sensor->scaler_sink.height); + scaler_sink->height); if (rval < 0) goto err_pm_put; } @@ -1890,10 +1920,10 @@ static int ccs_enable_streams(struct v4l2_subdev *subdev, } /* Output size from sensor */ - rval = ccs_write(sensor, X_OUTPUT_SIZE, sensor->src_src.width); + rval = ccs_write(sensor, X_OUTPUT_SIZE, src_src->width); if (rval < 0) goto err_pm_put; - rval = ccs_write(sensor, Y_OUTPUT_SIZE, sensor->src_src.height); + rval = ccs_write(sensor, Y_OUTPUT_SIZE, src_src->height); if (rval < 0) goto err_pm_put; @@ -2022,20 +2052,17 @@ static int ccs_enum_mbus_code(struct v4l2_subdev *subdev, struct ccs_sensor *sensor = to_ccs_sensor(subdev); unsigned int i; int idx = -1; - int rval = -EINVAL; - - mutex_lock(&sensor->mutex); dev_err(&client->dev, "subdev %s, pad %u, index %u\n", subdev->name, code->pad, code->index); if (subdev != &sensor->src->sd || code->pad != CCS_PAD_SRC) { if (code->index) - goto out; + return -EINVAL; code->code = sensor->internal_csi_format->code; - rval = 0; - goto out; + + return 0; } for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) { @@ -2046,18 +2073,14 @@ static int ccs_enum_mbus_code(struct v4l2_subdev *subdev, code->code = ccs_csi_data_formats[i].code; dev_err(&client->dev, "found index %u, i %u, code %x\n", code->index, i, code->code); - rval = 0; - break; + return 0; } } -out: - mutex_unlock(&sensor->mutex); - - return rval; + return -EINVAL; } -static u32 __ccs_get_mbus_code(struct v4l2_subdev *subdev, unsigned int pad) +static u32 ccs_get_mbus_code(struct v4l2_subdev *subdev, unsigned int pad) { struct ccs_sensor *sensor = to_ccs_sensor(subdev); @@ -2067,28 +2090,14 @@ static u32 __ccs_get_mbus_code(struct v4l2_subdev *subdev, unsigned int pad) return sensor->internal_csi_format->code; } -static int __ccs_get_format(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *fmt) -{ - fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad); - fmt->format.code = __ccs_get_mbus_code(subdev, fmt->pad); - - return 0; -} - static int ccs_get_format(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { - struct ccs_sensor *sensor = to_ccs_sensor(subdev); - int rval; + fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad); + fmt->format.code = ccs_get_mbus_code(subdev, fmt->pad); - mutex_lock(&sensor->mutex); - rval = __ccs_get_format(subdev, sd_state, fmt); - mutex_unlock(&sensor->mutex); - - return rval; + return 0; } /* Changes require propagation only on sink pad. */ @@ -2114,7 +2123,6 @@ static void ccs_propagate(struct v4l2_subdev *subdev, sensor->scale_m = CCS_LIM(sensor, SCALER_N_MIN); sensor->scaling_mode = CCS_SCALING_MODE_NO_SCALING; - sensor->scaler_sink = *comp; } else if (ssd == sensor->binner) { sensor->binning_horizontal = 1; sensor->binning_vertical = 1; @@ -2129,8 +2137,6 @@ static void ccs_propagate(struct v4l2_subdev *subdev, CCS_STREAM_PIXEL); fmt->width = comp->width; fmt->height = comp->height; - if (which == V4L2_SUBDEV_FORMAT_ACTIVE && ssd == sensor->src) - sensor->src_src = *crop; break; default: WARN_ON_ONCE(1); @@ -2149,7 +2155,7 @@ static int ccs_set_format_source(struct v4l2_subdev *subdev, unsigned int i; int rval; - rval = __ccs_get_format(subdev, sd_state, fmt); + rval = ccs_get_format(subdev, sd_state, fmt); if (rval) return rval; @@ -2198,20 +2204,17 @@ static int ccs_set_format(struct v4l2_subdev *subdev, struct ccs_subdev *ssd = to_ccs_subdev(subdev); struct v4l2_rect *crop; - mutex_lock(&sensor->mutex); - if (fmt->pad == ssd->source_pad) { int rval; rval = ccs_set_format_source(subdev, sd_state, fmt); - mutex_unlock(&sensor->mutex); - return rval; } /* Sink pad. Width and height are changeable here. */ - fmt->format.code = __ccs_get_mbus_code(subdev, fmt->pad); + fmt->format.code = ccs_get_mbus_code(subdev, fmt->pad); + fmt->format.width &= ~1; fmt->format.height &= ~1; fmt->format.field = V4L2_FIELD_NONE; @@ -2234,8 +2237,6 @@ static int ccs_set_format(struct v4l2_subdev *subdev, crop->height = fmt->format.height; ccs_propagate(subdev, sd_state, fmt->which, V4L2_SEL_TGT_CROP); - mutex_unlock(&sensor->mutex); - return 0; } @@ -2537,9 +2538,6 @@ static int ccs_set_crop(struct v4l2_subdev *subdev, if (ssd != sensor->pixel_array && sel->pad == CCS_PAD_SINK) ccs_propagate(subdev, sd_state, sel->which, V4L2_SEL_TGT_CROP); - else if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE && - ssd == sensor->pixel_array) - sensor->pa_src = sel->r; return 0; } @@ -2609,8 +2607,6 @@ static int ccs_set_selection(struct v4l2_subdev *subdev, if (ret) return ret; - mutex_lock(&sensor->mutex); - sel->r.left = max(0, sel->r.left & ~1); sel->r.top = max(0, sel->r.top & ~1); sel->r.width = CCS_ALIGN_DIM(sel->r.width, sel->flags); @@ -2632,7 +2628,6 @@ static int ccs_set_selection(struct v4l2_subdev *subdev, ret = -EINVAL; } - mutex_unlock(&sensor->mutex); return ret; } @@ -3002,6 +2997,7 @@ static int ccs_init_subdev(struct ccs_sensor *sensor, ssd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ssd->sd.entity.function = function; + ssd->sd.state_lock = &sensor->mutex; ssd->sensor = sensor; ssd->npads = num_pads; @@ -3059,10 +3055,8 @@ static int ccs_init_state(struct v4l2_subdev *sd, fmt->field = V4L2_FIELD_NONE; if (ssd == sensor->pixel_array) { - if (is_active) - sensor->pa_src = *crop; - mutex_unlock(&sensor->mutex); + return 0; } diff --git a/drivers/media/i2c/ccs/ccs.h b/drivers/media/i2c/ccs/ccs.h index 0bdb8cd3accb..e885063c89f0 100644 --- a/drivers/media/i2c/ccs/ccs.h +++ b/drivers/media/i2c/ccs/ccs.h @@ -225,7 +225,6 @@ struct ccs_sensor { u32 mbus_frame_fmts; const struct ccs_csi_data_format *csi_format; const struct ccs_csi_data_format *internal_csi_format; - struct v4l2_rect pa_src, scaler_sink, src_src; u32 default_mbus_frame_fmts; int default_pixel_order; struct ccs_data_container sdata, mdata; From ea3674c4465c11ab5181820405fcc9361f1d000a Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 16 Jan 2026 12:56:56 +0200 Subject: [PATCH 228/307] media: ccs: Switch to guard and scoped_guard Replace the use of mutex_{,un}lock() by guard() and scoped_guard() where it makes sense (i.e. everywhere). Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index a08f06149097..81f81cb2c354 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -2006,9 +2006,8 @@ static int ccs_pre_streamon(struct v4l2_subdev *subdev, u32 flags) } } - mutex_lock(&sensor->mutex); - rval = ccs_pm_get_init(sensor); - mutex_unlock(&sensor->mutex); + scoped_guard(mutex, &sensor->mutex) + rval = ccs_pm_get_init(sensor); if (rval) return rval; @@ -3045,7 +3044,7 @@ static int ccs_init_state(struct v4l2_subdev *sd, v4l2_subdev_state_get_crop(sd_state, pad); bool is_active = !sd->active_state || sd->active_state == sd_state; - mutex_lock(&sensor->mutex); + guard(mutex)(&sensor->mutex); ccs_get_native_size(ssd, crop); @@ -3054,11 +3053,8 @@ static int ccs_init_state(struct v4l2_subdev *sd, fmt->code = sensor->internal_csi_format->code; fmt->field = V4L2_FIELD_NONE; - if (ssd == sensor->pixel_array) { - mutex_unlock(&sensor->mutex); - + if (ssd == sensor->pixel_array) return 0; - } fmt = v4l2_subdev_state_get_format(sd_state, CCS_PAD_SRC); fmt->code = ssd == sensor->src ? @@ -3067,8 +3063,6 @@ static int ccs_init_state(struct v4l2_subdev *sd, ccs_propagate(sd, sd_state, is_active, V4L2_SEL_TGT_CROP); - mutex_unlock(&sensor->mutex); - return 0; } @@ -3553,9 +3547,8 @@ static int ccs_probe(struct i2c_client *client) goto out_cleanup; } - mutex_lock(&sensor->mutex); - rval = ccs_pll_blanking_update(sensor); - mutex_unlock(&sensor->mutex); + scoped_guard(mutex, &sensor->mutex) + rval = ccs_pll_blanking_update(sensor); if (rval) { dev_err(&client->dev, "update mode failed\n"); goto out_cleanup; From 7858ac7a8a6415eafb5ac027aaa9acf3ec78cc31 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 10 Oct 2023 22:53:04 +0300 Subject: [PATCH 229/307] media: ccs: Compute binning configuration from sub-device state Calculate binning configuration from sub-device state so the state related configuration can be removed from the driver's device context struct. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 56 +++++++++++++++++++------------- drivers/media/i2c/ccs/ccs.h | 3 -- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 81f81cb2c354..6d2b5eb50659 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -511,13 +511,36 @@ static int ccs_pll_try(struct ccs_sensor *sensor, struct ccs_pll *pll) return ccs_pll_calculate(&client->dev, &lim, pll); } +static void +ccs_get_binning(struct ccs_sensor *sensor, u8 *binning_mode, u8 *binh, u8 *binv) +{ + struct v4l2_subdev_state *state = + v4l2_subdev_get_locked_active_state(&sensor->binner->sd); + const struct v4l2_rect *sink_crop = + v4l2_subdev_state_get_crop(state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + const struct v4l2_rect *sink_comp = + v4l2_subdev_state_get_compose(state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + + if (binning_mode) + *binning_mode = sink_crop->width == sink_comp->width && + sink_crop->height == sink_comp->height ? 0 : 1; + + *binh = sink_crop->width / sink_comp->width; + *binv = sink_crop->height / sink_comp->height; +} + static int ccs_pll_update(struct ccs_sensor *sensor) { struct ccs_pll *pll = &sensor->pll; + u8 binh, binv; int rval; - pll->binning_horizontal = sensor->binning_horizontal; - pll->binning_vertical = sensor->binning_vertical; + ccs_get_binning(sensor, NULL, &binh, &binv); + + pll->binning_horizontal = binh; + pll->binning_vertical = binv; pll->link_freq = sensor->link_freq->qmenu_int[sensor->link_freq->val]; pll->scale_m = sensor->scale_m; @@ -1239,8 +1262,11 @@ static void ccs_update_blanking(struct ccs_sensor *sensor, struct v4l2_ctrl *hblank = sensor->hblank; u16 min_fll, max_fll, min_llp, max_llp, min_lbp; int min, max; + u8 binh, binv; - if (sensor->binning_vertical > 1 || sensor->binning_horizontal > 1) { + ccs_get_binning(sensor, NULL, &binh, &binv); + + if (binv > 1 || binh > 1) { min_fll = CCS_LIM(sensor, MIN_FRAME_LENGTH_LINES_BIN); max_fll = CCS_LIM(sensor, MAX_FRAME_LENGTH_LINES_BIN); min_llp = CCS_LIM(sensor, MIN_LINE_LENGTH_PCK_BIN); @@ -1808,7 +1834,7 @@ static int ccs_enable_streams(struct v4l2_subdev *subdev, v4l2_subdev_state_get_crop(src_state, CCS_PAD_SRC, CCS_STREAM_PIXEL); struct i2c_client *client = v4l2_get_subdevdata(&sensor->src->sd); - unsigned int binning_mode; + u8 binning_mode, binh, binv; int rval; if (pad != CCS_PAD_SRC) @@ -1830,19 +1856,12 @@ static int ccs_enable_streams(struct v4l2_subdev *subdev, goto err_pm_put; /* Binning configuration */ - if (sensor->binning_horizontal == 1 && - sensor->binning_vertical == 1) { - binning_mode = 0; - } else { - u8 binning_type = - (sensor->binning_horizontal << 4) - | sensor->binning_vertical; + ccs_get_binning(sensor, &binning_mode, &binh, &binv); - rval = ccs_write(sensor, BINNING_TYPE, binning_type); + if (binning_mode) { + rval = ccs_write(sensor, BINNING_TYPE, (binh << 4) | binv); if (rval < 0) goto err_pm_put; - - binning_mode = 1; } rval = ccs_write(sensor, BINNING_MODE, binning_mode); if (rval < 0) @@ -2122,9 +2141,6 @@ static void ccs_propagate(struct v4l2_subdev *subdev, sensor->scale_m = CCS_LIM(sensor, SCALER_N_MIN); sensor->scaling_mode = CCS_SCALING_MODE_NO_SCALING; - } else if (ssd == sensor->binner) { - sensor->binning_horizontal = 1; - sensor->binning_vertical = 1; } } fallthrough; @@ -2310,10 +2326,6 @@ static void ccs_set_compose_binner(struct v4l2_subdev *subdev, best = this; } } - if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { - sensor->binning_vertical = binv; - sensor->binning_horizontal = binh; - } sel->r.width = (sink_crop->width / binh) & ~1; sel->r.height = (sink_crop->height / binv) & ~1; @@ -3400,8 +3412,6 @@ static int ccs_probe(struct i2c_client *client) sensor->binning_subtypes[i].vertical); } } - sensor->binning_horizontal = 1; - sensor->binning_vertical = 1; if (device_create_file(&client->dev, &dev_attr_ident) != 0) { dev_err(&client->dev, "sysfs ident entry creation failed\n"); diff --git a/drivers/media/i2c/ccs/ccs.h b/drivers/media/i2c/ccs/ccs.h index e885063c89f0..62a4900686f8 100644 --- a/drivers/media/i2c/ccs/ccs.h +++ b/drivers/media/i2c/ccs/ccs.h @@ -229,9 +229,6 @@ struct ccs_sensor { int default_pixel_order; struct ccs_data_container sdata, mdata; - u8 binning_horizontal; - u8 binning_vertical; - u8 scale_m; u8 scaling_mode; From ce7b25c0170a0942b56e566b95bdc1e5efcc40f8 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 10 Oct 2023 22:57:00 +0300 Subject: [PATCH 230/307] media: ccs: Compute scaling configuration from sub-device state Compute scaling configuration from sub-device state instead of storing it to the driver's device context struct. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 59 ++++++++++++++++++++++---------- drivers/media/i2c/ccs/ccs.h | 3 -- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 6d2b5eb50659..4a680f3f34e5 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -531,19 +531,51 @@ ccs_get_binning(struct ccs_sensor *sensor, u8 *binning_mode, u8 *binh, u8 *binv) *binv = sink_crop->height / sink_comp->height; } +static void ccs_get_scaling(struct ccs_sensor *sensor, u8 *scaling_mode, + u8 *scale_m) +{ + struct v4l2_subdev_state *state = + v4l2_subdev_get_locked_active_state(&sensor->scaler->sd); + const struct v4l2_rect *sink_crop = + v4l2_subdev_state_get_crop(state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + const struct v4l2_rect *sink_comp = + v4l2_subdev_state_get_compose(state, CCS_PAD_SINK, + CCS_STREAM_PIXEL); + + *scale_m = sink_crop->width * CCS_LIM(sensor, SCALER_N_MIN) / + sink_comp->width; + + if (!scaling_mode) + return; + + if (sink_crop->width == sink_comp->width) + *scaling_mode = CCS_SCALING_MODE_NO_SCALING; + else if (sink_crop->height == sink_comp->height) + *scaling_mode = CCS_SCALING_MODE_HORIZONTAL; + else + *scaling_mode = SMIAPP_SCALING_MODE_BOTH; +} + static int ccs_pll_update(struct ccs_sensor *sensor) { struct ccs_pll *pll = &sensor->pll; u8 binh, binv; + u8 scale_m; int rval; ccs_get_binning(sensor, NULL, &binh, &binv); + if (sensor->scaler) + ccs_get_scaling(sensor, NULL, &scale_m); + else + scale_m = CCS_LIM(sensor, SCALER_N_MIN); + pll->binning_horizontal = binh; pll->binning_vertical = binv; pll->link_freq = sensor->link_freq->qmenu_int[sensor->link_freq->val]; - pll->scale_m = sensor->scale_m; + pll->scale_m = scale_m; pll->bits_per_pixel = sensor->csi_format->compressed; rval = ccs_pll_try(sensor, pll); @@ -1184,7 +1216,7 @@ static int ccs_get_mbus_formats(struct ccs_sensor *sensor) /* Figure out which BPP values can be used with which formats. */ pll->binning_horizontal = 1; pll->binning_vertical = 1; - pll->scale_m = sensor->scale_m; + pll->scale_m = CCS_LIM(sensor, SCALER_N_MIN); for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) { sensor->compressed_min_bpp = @@ -1929,11 +1961,15 @@ static int ccs_enable_streams(struct v4l2_subdev *subdev, /* Scaling */ if (CCS_LIM(sensor, SCALING_CAPABILITY) != CCS_SCALING_CAPABILITY_NONE) { - rval = ccs_write(sensor, SCALING_MODE, sensor->scaling_mode); + u8 scaling_mode, scale_m; + + ccs_get_scaling(sensor, &scaling_mode, &scale_m); + + rval = ccs_write(sensor, SCALING_MODE, scaling_mode); if (rval < 0) goto err_pm_put; - rval = ccs_write(sensor, SCALE_M, sensor->scale_m); + rval = ccs_write(sensor, SCALE_M, scale_m); if (rval < 0) goto err_pm_put; } @@ -2123,7 +2159,6 @@ static void ccs_propagate(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state, int which, int target) { - struct ccs_sensor *sensor = to_ccs_sensor(subdev); struct ccs_subdev *ssd = to_ccs_subdev(subdev); struct v4l2_rect *comp, *crop; struct v4l2_mbus_framefmt *fmt; @@ -2136,13 +2171,6 @@ static void ccs_propagate(struct v4l2_subdev *subdev, CCS_STREAM_PIXEL); comp->width = crop->width; comp->height = crop->height; - if (which == V4L2_SUBDEV_FORMAT_ACTIVE) { - if (ssd == sensor->scaler) { - sensor->scale_m = CCS_LIM(sensor, SCALER_N_MIN); - sensor->scaling_mode = - CCS_SCALING_MODE_NO_SCALING; - } - } fallthrough; case V4L2_SEL_TGT_COMPOSE: crop = v4l2_subdev_state_get_crop(sd_state, CCS_PAD_SRC, @@ -2434,11 +2462,6 @@ static void ccs_set_compose_scaler(struct v4l2_subdev *subdev, * CCS_LIM(sensor, SCALER_N_MIN)) & ~1; else sel->r.height = sink_crop->height; - - if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) { - sensor->scale_m = scale_m; - sensor->scaling_mode = mode; - } } /* We're only called on source pads. This function sets scaling. */ static int ccs_set_compose(struct v4l2_subdev *subdev, @@ -3448,8 +3471,6 @@ static int ccs_probe(struct i2c_client *client) sensor->pixel_array = &sensor->ssds[sensor->ssds_used]; sensor->ssds_used++; - sensor->scale_m = CCS_LIM(sensor, SCALER_N_MIN); - /* prepare PLL configuration input values */ switch (sensor->hwcfg.csi_signalling_mode) { case CCS_CSI_SIGNALING_MODE_CSI_2_CPHY: diff --git a/drivers/media/i2c/ccs/ccs.h b/drivers/media/i2c/ccs/ccs.h index 62a4900686f8..51915321ef44 100644 --- a/drivers/media/i2c/ccs/ccs.h +++ b/drivers/media/i2c/ccs/ccs.h @@ -229,9 +229,6 @@ struct ccs_sensor { int default_pixel_order; struct ccs_data_container sdata, mdata; - u8 scale_m; - u8 scaling_mode; - u8 frame_skip; u16 embedded_start; /* embedded data start line */ u16 embedded_end; From 026d02ea142deb56fe997df8d29417ca6cc3b862 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 11 Oct 2023 14:57:52 +0300 Subject: [PATCH 231/307] media: ccs: Remove which parameter from ccs_propagate ccs_propagate() no longer stores information in the driver's context struct. The which parameter can thus be removed. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 4a680f3f34e5..062c70b0a47d 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -2156,8 +2156,7 @@ static int ccs_get_format(struct v4l2_subdev *subdev, /* Changes require propagation only on sink pad. */ static void ccs_propagate(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *sd_state, int which, - int target) + struct v4l2_subdev_state *sd_state, int target) { struct ccs_subdev *ssd = to_ccs_subdev(subdev); struct v4l2_rect *comp, *crop; @@ -2278,7 +2277,7 @@ static int ccs_set_format(struct v4l2_subdev *subdev, crop->top = 0; crop->width = fmt->format.width; crop->height = fmt->format.height; - ccs_propagate(subdev, sd_state, fmt->which, V4L2_SEL_TGT_CROP); + ccs_propagate(subdev, sd_state, V4L2_SEL_TGT_CROP); return 0; } @@ -2487,7 +2486,7 @@ static int ccs_set_compose(struct v4l2_subdev *subdev, ccs_set_compose_scaler(subdev, sd_state, sel, sink_crop); *comp = sel->r; - ccs_propagate(subdev, sd_state, sel->which, V4L2_SEL_TGT_COMPOSE); + ccs_propagate(subdev, sd_state, V4L2_SEL_TGT_COMPOSE); if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) return ccs_pll_blanking_update(sensor); @@ -2571,7 +2570,7 @@ static int ccs_set_crop(struct v4l2_subdev *subdev, *crop = sel->r; if (ssd != sensor->pixel_array && sel->pad == CCS_PAD_SINK) - ccs_propagate(subdev, sd_state, sel->which, V4L2_SEL_TGT_CROP); + ccs_propagate(subdev, sd_state, V4L2_SEL_TGT_CROP); return 0; } @@ -3077,7 +3076,6 @@ static int ccs_init_state(struct v4l2_subdev *sd, v4l2_subdev_state_get_format(sd_state, pad); struct v4l2_rect *crop = v4l2_subdev_state_get_crop(sd_state, pad); - bool is_active = !sd->active_state || sd->active_state == sd_state; guard(mutex)(&sensor->mutex); @@ -3096,7 +3094,7 @@ static int ccs_init_state(struct v4l2_subdev *sd, sensor->csi_format->code : sensor->internal_csi_format->code; fmt->field = V4L2_FIELD_NONE; - ccs_propagate(sd, sd_state, is_active, V4L2_SEL_TGT_CROP); + ccs_propagate(sd, sd_state, V4L2_SEL_TGT_CROP); return 0; } From 0b216f7f172bfa10281dd2e7ef57292415b41849 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 7 Jan 2026 14:55:40 +0200 Subject: [PATCH 232/307] media: ccs: Make supported mbus code bitmask a u64 Prepare for adding more media bus codes. Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ccs/ccs-core.c | 9 +++++---- drivers/media/i2c/ccs/ccs.h | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 062c70b0a47d..9c7f340ca9a1 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -13,6 +13,7 @@ * Based on smia-sensor.c by Tuukka Toivonen */ +#include #include #include #include @@ -1209,7 +1210,7 @@ static int ccs_get_mbus_formats(struct ccs_sensor *sensor) dev_dbg(&client->dev, "jolly good! %u\n", j); - sensor->default_mbus_frame_fmts |= 1 << j; + sensor->default_mbus_frame_fmts |= BIT_U64(j); } } @@ -1242,7 +1243,7 @@ static int ccs_get_mbus_formats(struct ccs_sensor *sensor) f->compressed - sensor->compressed_min_bpp]; unsigned int j; - if (!(sensor->default_mbus_frame_fmts & 1 << i)) + if (!(sensor->default_mbus_frame_fmts & BIT_U64(i))) continue; pll->bits_per_pixel = f->compressed; @@ -2090,7 +2091,7 @@ static const struct ccs_csi_data_format unsigned int i; for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) { - if (sensor->mbus_frame_fmts & (1 << i) && + if (sensor->mbus_frame_fmts & BIT_U64(i) && ccs_csi_data_formats[i].code == code) return &ccs_csi_data_formats[i]; } @@ -2120,7 +2121,7 @@ static int ccs_enum_mbus_code(struct v4l2_subdev *subdev, } for (i = 0; i < ARRAY_SIZE(ccs_csi_data_formats); i++) { - if (sensor->mbus_frame_fmts & (1 << i)) + if (sensor->mbus_frame_fmts & BIT_U64(i)) idx++; if (idx == code->index) { diff --git a/drivers/media/i2c/ccs/ccs.h b/drivers/media/i2c/ccs/ccs.h index 51915321ef44..58952c5bf06b 100644 --- a/drivers/media/i2c/ccs/ccs.h +++ b/drivers/media/i2c/ccs/ccs.h @@ -222,10 +222,10 @@ struct ccs_sensor { void *ccs_limits; u8 nbinning_subtypes; struct ccs_binning_subtype binning_subtypes[CCS_LIM_BINNING_SUB_TYPE_MAX_N + 1]; - u32 mbus_frame_fmts; + u64 mbus_frame_fmts; const struct ccs_csi_data_format *csi_format; const struct ccs_csi_data_format *internal_csi_format; - u32 default_mbus_frame_fmts; + u64 default_mbus_frame_fmts; int default_pixel_order; struct ccs_data_container sdata, mdata; From 59b9548e50d2c5ea4209733f919a35675cd78f28 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sat, 19 Aug 2023 02:40:28 +0300 Subject: [PATCH 233/307] media: i2c: imx219: Inline imx219_update_pad_format() in its caller The imx219_update_pad_format() is short and called from a single place, in imx219_set_pad_format(). Inline the code in the caller to keep all format adjustments grouped in a single place and improve readability. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Reviewed-by: Dave Stevenson Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/imx219.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index bc55fe2a93b4..fee63bc106d9 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -792,21 +792,6 @@ static int imx219_disable_streams(struct v4l2_subdev *sd, return ret; } -static void imx219_update_pad_format(struct imx219 *imx219, - const struct imx219_mode *mode, - struct v4l2_mbus_framefmt *fmt, u32 code) -{ - /* Bayer order varies with flips */ - fmt->code = imx219_get_format_code(imx219, code); - fmt->width = mode->width; - fmt->height = mode->height; - fmt->field = V4L2_FIELD_NONE; - fmt->colorspace = V4L2_COLORSPACE_RAW; - fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; - fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; - fmt->xfer_func = V4L2_XFER_FUNC_NONE; -} - static int imx219_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_mbus_code_enum *code) @@ -858,12 +843,24 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, format = v4l2_subdev_state_get_format(state, 0); prev_line_len = format->width + imx219->hblank->val; + /* + * Adjust the requested format to match the closest mode. The Bayer + * order varies with flips. + */ mode = v4l2_find_nearest_size(supported_modes, ARRAY_SIZE(supported_modes), width, height, fmt->format.width, fmt->format.height); - imx219_update_pad_format(imx219, mode, &fmt->format, fmt->format.code); + fmt->format.code = imx219_get_format_code(imx219, fmt->format.code); + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_601; + fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; + *format = fmt->format; /* From d89ccbf3dde727d91a242a5a3f3b70a90579b057 Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 9 Dec 2025 23:44:36 +0100 Subject: [PATCH 234/307] media: v4l: ctrls: add a control for flash/strobe duration Add a V4L2_CID_FLASH_DURATION control to set the duration of a flash/strobe pulse. This controls the length of the flash/strobe pulse output by device (typically a camera sensor) and connected to the flash controller. This is different to the V4L2_CID_FLASH_TIMEOUT control, which is implemented by the flash controller and defines a limit after which the flash is "forcefully" turned off again. Reviewed-by: Laurent Pinchart Signed-off-by: Richard Leitner Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-defs.c | 1 + include/uapi/linux/v4l2-controls.h | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index ad41f65374e2..4848423205ff 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -1135,6 +1135,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_FLASH_FAULT: return "Faults"; case V4L2_CID_FLASH_CHARGE: return "Charge"; case V4L2_CID_FLASH_READY: return "Ready to Strobe"; + case V4L2_CID_FLASH_DURATION: return "Strobe Duration"; /* JPEG encoder controls */ /* Keep the order of the 'case's the same as in v4l2-controls.h! */ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index f84ed133a6c9..357845830fe9 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -1192,6 +1192,7 @@ enum v4l2_flash_strobe_source { #define V4L2_CID_FLASH_CHARGE (V4L2_CID_FLASH_CLASS_BASE + 11) #define V4L2_CID_FLASH_READY (V4L2_CID_FLASH_CLASS_BASE + 12) +#define V4L2_CID_FLASH_DURATION (V4L2_CID_FLASH_CLASS_BASE + 13) /* JPEG-class control IDs */ From 5be4154f6255d92d9d2ad5da658d7d33a655386f Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 9 Dec 2025 23:44:37 +0100 Subject: [PATCH 235/307] media: v4l: ctrls: add a control for enabling strobe output Add a control V4L2_CID_FLASH_STROBE_OE to en- or disable the strobe output of v4l2 devices (most likely sensors). Signed-off-by: Richard Leitner Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-defs.c | 2 ++ include/uapi/linux/v4l2-controls.h | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index 4848423205ff..765aeeec84fe 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -1136,6 +1136,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_FLASH_CHARGE: return "Charge"; case V4L2_CID_FLASH_READY: return "Ready to Strobe"; case V4L2_CID_FLASH_DURATION: return "Strobe Duration"; + case V4L2_CID_FLASH_STROBE_OE: return "Strobe Output Enable"; /* JPEG encoder controls */ /* Keep the order of the 'case's the same as in v4l2-controls.h! */ @@ -1282,6 +1283,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_FLASH_STROBE_STATUS: case V4L2_CID_FLASH_CHARGE: case V4L2_CID_FLASH_READY: + case V4L2_CID_FLASH_STROBE_OE: case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER: case V4L2_CID_MPEG_VIDEO_DECODER_SLICE_INTERFACE: case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE: diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 357845830fe9..572622e4535e 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -1193,6 +1193,7 @@ enum v4l2_flash_strobe_source { #define V4L2_CID_FLASH_CHARGE (V4L2_CID_FLASH_CLASS_BASE + 11) #define V4L2_CID_FLASH_READY (V4L2_CID_FLASH_CLASS_BASE + 12) #define V4L2_CID_FLASH_DURATION (V4L2_CID_FLASH_CLASS_BASE + 13) +#define V4L2_CID_FLASH_STROBE_OE (V4L2_CID_FLASH_CLASS_BASE + 14) /* JPEG-class control IDs */ From fa0e06f2fe00fa9e283a15d85e7542a4b72a14a7 Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 9 Dec 2025 23:44:38 +0100 Subject: [PATCH 236/307] Documentation: uAPI: media: add V4L2_CID_FLASH_{DURATION,STROBE_OE} Add the new strobe duration and hardware strobe output enable to v4l uAPI documentation. Additionally add labels for cross-referencing v4l controls. Signed-off-by: Richard Leitner Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../media/v4l/ext-ctrls-flash.rst | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-flash.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-flash.rst index bd024ab461a4..b7f45fc0a797 100644 --- a/Documentation/userspace-api/media/v4l/ext-ctrls-flash.rst +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-flash.rst @@ -58,6 +58,8 @@ Flash Control IDs ``V4L2_CID_FLASH_CLASS (class)`` The FLASH class descriptor. +.. _v4l2-cid-flash-led-mode: + ``V4L2_CID_FLASH_LED_MODE (menu)`` Defines the mode of the flash LED, the high-power white LED attached to the flash controller. Setting this control may not be possible in @@ -81,6 +83,8 @@ Flash Control IDs +.. _v4l2-cid-flash-strobe-source: + ``V4L2_CID_FLASH_STROBE_SOURCE (menu)`` Defines the source of the flash LED strobe. @@ -97,6 +101,12 @@ Flash Control IDs - The flash strobe is triggered by an external source. Typically this is a sensor, which makes it possible to synchronise the flash strobe start to exposure start. + This method of controlling flash LED strobe has two additional + prerequisites: the strobe source's :ref:`strobe output + ` must be enabled (if available) + and the flash controller's :ref:`flash LED mode + ` must be set to + ``V4L2_FLASH_LED_MODE_FLASH``. @@ -187,3 +197,35 @@ Flash Control IDs charged before strobing. LED flashes often require a cooldown period after strobe during which another strobe will not be possible. This is a read-only control. + +.. _v4l2-cid-flash-duration: + +``V4L2_CID_FLASH_DURATION (integer)`` + Duration of the flash strobe pulse generated by the strobe source, when + using external strobe. This control shall be implemented by the device + generating the hardware flash strobe signal, typically a camera sensor, + connected to a flash controller. + + The flash controllers :ref:`strobe source ` + must be configured to ``V4L2_FLASH_STROBE_SOURCE_EXTERNAL`` for this + mode of operation. For more details please also take a look at the + documentation there. + + The unit should be microseconds (µs) if possible. + +.. _v4l2-cid-flash-strobe-oe: + +``V4L2_CID_FLASH_STROBE_OE (boolean)`` + Enables the output of a hardware strobe signal from the strobe source, + when using external strobe. This control shall be implemented by the device + generating the hardware flash strobe signal, typically a camera sensor, + connected to a flash controller. + + Provided the signal generating device driver supports it, the length of the + strobe signal can be configured by adjusting its + :ref:`flash duration `. + + The flash controllers :ref:`strobe source ` + must be configured to ``V4L2_FLASH_STROBE_SOURCE_EXTERNAL`` for this + mode of operation. For more details please also take a look at the + documentation there. From 47bc0d294d66a7019f13aa65e8b32aee4532869e Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 9 Dec 2025 23:44:39 +0100 Subject: [PATCH 237/307] media: i2c: ov9282: add output enable register definitions Add #define's for the output enable registers (0x3004, 0x3005, 0x3006), also known as SC_CTRL_04, SC_CTRL_05, SC_CTRL_04. Use those register definitions instead of the raw values in the `common_regs` struct. All values are based on the OV9281 datasheet v1.53 (january 2019). Reviewed-by: Dave Stevenson Signed-off-by: Richard Leitner Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov9282.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index 3e24d88f603c..d7448728cdd1 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -37,6 +37,29 @@ #define OV9282_REG_ID 0x300a #define OV9282_ID 0x9281 +/* Output enable registers */ +#define OV9282_REG_OUTPUT_ENABLE4 0x3004 +#define OV9282_OUTPUT_ENABLE4_GPIO2 BIT(1) +#define OV9282_OUTPUT_ENABLE4_D9 BIT(0) + +#define OV9282_REG_OUTPUT_ENABLE5 0x3005 +#define OV9282_OUTPUT_ENABLE5_D8 BIT(7) +#define OV9282_OUTPUT_ENABLE5_D7 BIT(6) +#define OV9282_OUTPUT_ENABLE5_D6 BIT(5) +#define OV9282_OUTPUT_ENABLE5_D5 BIT(4) +#define OV9282_OUTPUT_ENABLE5_D4 BIT(3) +#define OV9282_OUTPUT_ENABLE5_D3 BIT(2) +#define OV9282_OUTPUT_ENABLE5_D2 BIT(1) +#define OV9282_OUTPUT_ENABLE5_D1 BIT(0) + +#define OV9282_REG_OUTPUT_ENABLE6 0x3006 +#define OV9282_OUTPUT_ENABLE6_D0 BIT(7) +#define OV9282_OUTPUT_ENABLE6_PCLK BIT(6) +#define OV9282_OUTPUT_ENABLE6_HREF BIT(5) +#define OV9282_OUTPUT_ENABLE6_STROBE BIT(3) +#define OV9282_OUTPUT_ENABLE6_ILPWM BIT(2) +#define OV9282_OUTPUT_ENABLE6_VSYNC BIT(1) + /* Exposure control */ #define OV9282_REG_EXPOSURE 0x3500 #define OV9282_EXPOSURE_MIN 1 @@ -213,9 +236,9 @@ static const struct ov9282_reg common_regs[] = { {0x0302, 0x32}, {0x030e, 0x02}, {0x3001, 0x00}, - {0x3004, 0x00}, - {0x3005, 0x00}, - {0x3006, 0x04}, + {OV9282_REG_OUTPUT_ENABLE4, 0x00}, + {OV9282_REG_OUTPUT_ENABLE5, 0x00}, + {OV9282_REG_OUTPUT_ENABLE6, OV9282_OUTPUT_ENABLE6_ILPWM}, {0x3011, 0x0a}, {0x3013, 0x18}, {0x301c, 0xf0}, From f007ad65d7246d6b616541a3c7685ec688ca6beb Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 9 Dec 2025 23:44:40 +0100 Subject: [PATCH 238/307] media: i2c: ov9282: add strobe output enable v4l2 control Add V4L2_CID_FLASH_STROBE_OE enable/disable support using the "strobe output enable" feature of the sensor. All values are based on the OV9281 datasheet v1.53 (january 2019) and tested using an ov9281 VisionComponents module. Signed-off-by: Richard Leitner Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov9282.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index d7448728cdd1..cd4d234d7056 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -670,6 +670,23 @@ static int ov9282_set_ctrl_vflip(struct ov9282 *ov9282, int value) current_val); } +static int ov9282_set_ctrl_flash_strobe_oe(struct ov9282 *ov9282, bool enable) +{ + u32 current_val; + int ret; + + ret = ov9282_read_reg(ov9282, OV9282_REG_OUTPUT_ENABLE6, 1, ¤t_val); + if (ret) + return ret; + + if (enable) + current_val |= OV9282_OUTPUT_ENABLE6_STROBE; + else + current_val &= ~OV9282_OUTPUT_ENABLE6_STROBE; + + return ov9282_write_reg(ov9282, OV9282_REG_OUTPUT_ENABLE6, 1, current_val); +} + /** * ov9282_set_ctrl() - Set subdevice control * @ctrl: pointer to v4l2_ctrl structure @@ -736,6 +753,9 @@ static int ov9282_set_ctrl(struct v4l2_ctrl *ctrl) ret = ov9282_write_reg(ov9282, OV9282_REG_TIMING_HTS, 2, (ctrl->val + ov9282->cur_mode->width) >> 1); break; + case V4L2_CID_FLASH_STROBE_OE: + ret = ov9282_set_ctrl_flash_strobe_oe(ov9282, ctrl->val); + break; default: dev_err(ov9282->dev, "Invalid control %d", ctrl->id); ret = -EINVAL; @@ -1325,7 +1345,7 @@ static int ov9282_init_controls(struct ov9282 *ov9282) u32 lpfr; int ret; - ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 11); if (ret) return ret; @@ -1390,6 +1410,10 @@ static int ov9282_init_controls(struct ov9282 *ov9282) OV9282_TIMING_HTS_MAX - mode->width, 1, hblank_min); + /* Flash/Strobe controls */ + v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, + V4L2_CID_FLASH_STROBE_OE, 0, 1, 1, 0); + ret = v4l2_fwnode_device_parse(ov9282->dev, &props); if (!ret) { /* Failure sets ctrl_hdlr->error, which we check afterwards anyway */ From 84ec7597ae1afb9430c55044f318d1d40f8ecef0 Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 9 Dec 2025 23:44:41 +0100 Subject: [PATCH 239/307] media: i2c: ov9282: add strobe_duration v4l2 control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add V4L2_CID_FLASH_DURATION support using the "strobe_frame_span" feature of the sensor. This is implemented by transforming the given µs value by an interpolated formula to a "span step width" value and writing it to register PWM_CTRL_25, PWM_CTRL_26, PWM_CTRL_27, PWM_CTRL_28 (0x3925, 0x3926, 0x3927, 0x3928). The maximum control value is set to the period of the current default framerate. All register values are based on the OV9281 datasheet v1.53 (jan 2019) and tested using an ov9281 VisionComponents module. Signed-off-by: Richard Leitner [Sakari Ailus: Remove redundant assignment of ret, declare ret as last.] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov9282.c | 45 +++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index cd4d234d7056..92da28f248a4 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -97,6 +97,10 @@ #define OV9282_REG_MIPI_CTRL00 0x4800 #define OV9282_GATED_CLOCK BIT(5) +/* Flash/Strobe control registers */ +#define OV9282_REG_STROBE_FRAME_SPAN 0x3925 +#define OV9282_STROBE_FRAME_SPAN_DEFAULT 0x0000001a + /* Input clock rate */ #define OV9282_INCLK_RATE 24000000 @@ -687,6 +691,39 @@ static int ov9282_set_ctrl_flash_strobe_oe(struct ov9282 *ov9282, bool enable) return ov9282_write_reg(ov9282, OV9282_REG_OUTPUT_ENABLE6, 1, current_val); } +static int ov9282_set_ctrl_flash_duration(struct ov9282 *ov9282, u32 value) +{ + /* + * Calculate "strobe_frame_span" increments from a given value (µs). + * This is quite tricky as "The step width of shift and span is + * programmable under system clock domain.", but it's not documented + * how to program this step width (at least in the datasheet available + * to the author at time of writing). + * The formula below is interpolated from different modes/framerates + * and should work quite well for most settings. + */ + u32 val = value * 192 / (ov9282->cur_mode->width + ov9282->hblank_ctrl->val); + int ret; + + ret = ov9282_write_reg(ov9282, OV9282_REG_STROBE_FRAME_SPAN, 1, + (val >> 24) & 0xff); + if (ret) + return ret; + + ret = ov9282_write_reg(ov9282, OV9282_REG_STROBE_FRAME_SPAN + 1, 1, + (val >> 16) & 0xff); + if (ret) + return ret; + + ret = ov9282_write_reg(ov9282, OV9282_REG_STROBE_FRAME_SPAN + 2, 1, + (val >> 8) & 0xff); + if (ret) + return ret; + + return ov9282_write_reg(ov9282, OV9282_REG_STROBE_FRAME_SPAN + 3, 1, + val & 0xff); +} + /** * ov9282_set_ctrl() - Set subdevice control * @ctrl: pointer to v4l2_ctrl structure @@ -756,6 +793,9 @@ static int ov9282_set_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_FLASH_STROBE_OE: ret = ov9282_set_ctrl_flash_strobe_oe(ov9282, ctrl->val); break; + case V4L2_CID_FLASH_DURATION: + ret = ov9282_set_ctrl_flash_duration(ov9282, ctrl->val); + break; default: dev_err(ov9282->dev, "Invalid control %d", ctrl->id); ret = -EINVAL; @@ -1345,7 +1385,7 @@ static int ov9282_init_controls(struct ov9282 *ov9282) u32 lpfr; int ret; - ret = v4l2_ctrl_handler_init(ctrl_hdlr, 11); + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 12); if (ret) return ret; @@ -1414,6 +1454,9 @@ static int ov9282_init_controls(struct ov9282 *ov9282) v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, V4L2_CID_FLASH_STROBE_OE, 0, 1, 1, 0); + v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, V4L2_CID_FLASH_DURATION, + 0, 13900, 1, 8); + ret = v4l2_fwnode_device_parse(ov9282->dev, &props); if (!ret) { /* Failure sets ctrl_hdlr->error, which we check afterwards anyway */ From b95d8058a2786a8b42e32f383f8df45097675d00 Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 9 Dec 2025 23:44:42 +0100 Subject: [PATCH 240/307] media: i2c: ov9282: implement try_ctrl for strobe_duration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As the granularity of the hardware supported values is lower than the control value, implement a try_ctrl() function for V4L2_CID_FLASH_DURATION. This function calculates the nearest possible µs strobe duration for the given value and returns it back to the caller. Signed-off-by: Richard Leitner Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov9282.c | 53 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index 92da28f248a4..3c74d65fe312 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -128,6 +129,8 @@ #define OV9282_REG_MIN 0x00 #define OV9282_REG_MAX 0xfffff +#define OV9282_STROBE_SPAN_FACTOR 192 + static const char * const ov9282_supply_names[] = { "avdd", /* Analog power */ "dovdd", /* Digital I/O power */ @@ -691,7 +694,7 @@ static int ov9282_set_ctrl_flash_strobe_oe(struct ov9282 *ov9282, bool enable) return ov9282_write_reg(ov9282, OV9282_REG_OUTPUT_ENABLE6, 1, current_val); } -static int ov9282_set_ctrl_flash_duration(struct ov9282 *ov9282, u32 value) +static u32 ov9282_us_to_flash_duration(struct ov9282 *ov9282, u32 value) { /* * Calculate "strobe_frame_span" increments from a given value (µs). @@ -702,7 +705,26 @@ static int ov9282_set_ctrl_flash_duration(struct ov9282 *ov9282, u32 value) * The formula below is interpolated from different modes/framerates * and should work quite well for most settings. */ - u32 val = value * 192 / (ov9282->cur_mode->width + ov9282->hblank_ctrl->val); + u32 frame_width = ov9282->cur_mode->width + ov9282->hblank_ctrl->val; + + return value * OV9282_STROBE_SPAN_FACTOR / frame_width; +} + +static u32 ov9282_flash_duration_to_us(struct ov9282 *ov9282, u32 value) +{ + /* + * Calculate back to microseconds from "strobe_frame_span" increments. + * As the calculation in ov9282_us_to_flash_duration uses an integer + * divison round up here. + */ + u32 frame_width = ov9282->cur_mode->width + ov9282->hblank_ctrl->val; + + return DIV_ROUND_UP(value * frame_width, OV9282_STROBE_SPAN_FACTOR); +} + +static int ov9282_set_ctrl_flash_duration(struct ov9282 *ov9282, u32 value) +{ + u32 val = ov9282_us_to_flash_duration(ov9282, value); int ret; ret = ov9282_write_reg(ov9282, OV9282_REG_STROBE_FRAME_SPAN, 1, @@ -806,9 +828,36 @@ static int ov9282_set_ctrl(struct v4l2_ctrl *ctrl) return ret; } +static int ov9282_try_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov9282 *ov9282 = + container_of_const(ctrl->handler, struct ov9282, ctrl_handler); + + if (ctrl->id == V4L2_CID_FLASH_DURATION) { + u32 us = ctrl->val; + u32 fd = ov9282_us_to_flash_duration(ov9282, us); + + /* get nearest strobe_duration value */ + u32 us0 = ov9282_flash_duration_to_us(ov9282, fd); + u32 us1 = ov9282_flash_duration_to_us(ov9282, fd + 1); + + if (abs(us1 - us) < abs(us - us0)) + ctrl->val = us1; + else + ctrl->val = us0; + + if (us != ctrl->val) + dev_dbg(ov9282->dev, "using next valid strobe_duration %u instead of %u\n", + ctrl->val, us); + } + + return 0; +} + /* V4l2 subdevice control ops*/ static const struct v4l2_ctrl_ops ov9282_ctrl_ops = { .s_ctrl = ov9282_set_ctrl, + .try_ctrl = ov9282_try_ctrl, }; /** From df60764ea52102f433b76cfa4fc873229e60e76c Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 9 Dec 2025 23:44:43 +0100 Subject: [PATCH 241/307] media: i2c: ov9282: dynamic flash_duration maximum This patch sets the current exposure time as maximum for the flash_duration control. As Flash/Strobes which are longer than the exposure time have no effect. Signed-off-by: Richard Leitner [Sakari Ailus: Some lines rewrapped.] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov9282.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index 3c74d65fe312..ded9b2044ff8 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -199,6 +199,7 @@ struct ov9282_mode { * @exp_ctrl: Pointer to exposure control * @again_ctrl: Pointer to analog gain control * @pixel_rate: Pointer to pixel rate control + * @flash_duration: Pointer to flash duration control * @vblank: Vertical blanking in lines * @noncontinuous_clock: Selection of CSI2 noncontinuous clock mode * @cur_mode: Pointer to current selected sensor mode @@ -221,6 +222,7 @@ struct ov9282 { struct v4l2_ctrl *again_ctrl; }; struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *flash_duration; u32 vblank; bool noncontinuous_clock; const struct ov9282_mode *cur_mode; @@ -612,6 +614,15 @@ static int ov9282_update_controls(struct ov9282 *ov9282, mode->vblank_max, 1, mode->vblank); } +static u32 ov9282_exposure_to_us(struct ov9282 *ov9282, u32 exposure) +{ + /* calculate exposure time in µs */ + u32 frame_width = ov9282->cur_mode->width + ov9282->hblank_ctrl->val; + u32 trow_us = frame_width / (ov9282->pixel_rate->val / 1000000UL); + + return exposure * trow_us; +} + /** * ov9282_update_exp_gain() - Set updated exposure and gain * @ov9282: pointer to ov9282 device @@ -623,9 +634,10 @@ static int ov9282_update_controls(struct ov9282 *ov9282, static int ov9282_update_exp_gain(struct ov9282 *ov9282, u32 exposure, u32 gain) { int ret; + u32 exposure_us = ov9282_exposure_to_us(ov9282, exposure); - dev_dbg(ov9282->dev, "Set exp %u, analog gain %u", - exposure, gain); + dev_dbg(ov9282->dev, "Set exp %u (~%u us), analog gain %u", + exposure, exposure_us, gain); ret = ov9282_write_reg(ov9282, OV9282_REG_HOLD, 1, 1); if (ret) @@ -636,6 +648,12 @@ static int ov9282_update_exp_gain(struct ov9282 *ov9282, u32 exposure, u32 gain) goto error_release_group_hold; ret = ov9282_write_reg(ov9282, OV9282_REG_AGAIN, 1, gain); + if (ret) + goto error_release_group_hold; + + ret = __v4l2_ctrl_modify_range(ov9282->flash_duration, + 0, exposure_us, 1, + OV9282_STROBE_FRAME_SPAN_DEFAULT); error_release_group_hold: ov9282_write_reg(ov9282, OV9282_REG_HOLD, 1, 0); @@ -1431,6 +1449,7 @@ static int ov9282_init_controls(struct ov9282 *ov9282) const struct ov9282_mode *mode = ov9282->cur_mode; struct v4l2_fwnode_device_properties props; u32 hblank_min; + u32 exposure_us; u32 lpfr; int ret; @@ -1503,8 +1522,11 @@ static int ov9282_init_controls(struct ov9282 *ov9282) v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, V4L2_CID_FLASH_STROBE_OE, 0, 1, 1, 0); - v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, V4L2_CID_FLASH_DURATION, - 0, 13900, 1, 8); + exposure_us = ov9282_exposure_to_us(ov9282, OV9282_EXPOSURE_DEFAULT); + ov9282->flash_duration = + v4l2_ctrl_new_std(ctrl_hdlr, &ov9282_ctrl_ops, + V4L2_CID_FLASH_DURATION, 0, exposure_us, 1, + OV9282_STROBE_FRAME_SPAN_DEFAULT); ret = v4l2_fwnode_device_parse(ov9282->dev, &props); if (!ret) { From f422140e5a17557af48b10ea76a8b29777ce4e4b Mon Sep 17 00:00:00 2001 From: Himanshu Bhavani Date: Thu, 8 Jan 2026 11:24:54 +0530 Subject: [PATCH 242/307] media: i2c: os05b10: Few minor improvements Return 0 from disable_streams, use div_u64() instead of do_div(), use dev_dbg() for a debug message and move loop variable definition into the loop itself. Signed-off-by: Himanshu Bhavani Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/os05b10.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/drivers/media/i2c/os05b10.c b/drivers/media/i2c/os05b10.c index 8912d54ea55a..e0453c988e4a 100644 --- a/drivers/media/i2c/os05b10.c +++ b/drivers/media/i2c/os05b10.c @@ -708,7 +708,7 @@ static int os05b10_disable_streams(struct v4l2_subdev *sd, pm_runtime_put(os05b10->dev); - return ret; + return 0; } static int os05b10_init_state(struct v4l2_subdev *sd, @@ -884,17 +884,13 @@ static u64 os05b10_pixel_rate(struct os05b10 *os05b10, const struct os05b10_mode *mode) { u64 link_freq = link_frequencies[os05b10->link_freq_index]; - const unsigned int lanes = os05b10->data_lanes; - u64 numerator = link_freq * 2 * lanes; - unsigned int bpp = mode->bpp; + u64 pixel_rate = div_u64(link_freq * 2 * os05b10->data_lanes, mode->bpp); - do_div(numerator, bpp); + dev_dbg(os05b10->dev, + "link_freq=%llu bpp=%u lanes=%u pixel_rate=%llu\n", + link_freq, mode->bpp, os05b10->data_lanes, pixel_rate); - dev_info(os05b10->dev, - "link_freq=%llu bpp=%u lanes=%u pixel_rate=%llu\n", - link_freq, bpp, lanes, numerator); - - return numerator; + return pixel_rate; } static int os05b10_init_controls(struct os05b10 *os05b10) @@ -977,7 +973,6 @@ static int os05b10_probe(struct i2c_client *client) { struct os05b10 *os05b10; unsigned int xclk_freq; - unsigned int i; int ret; os05b10 = devm_kzalloc(&client->dev, sizeof(*os05b10), GFP_KERNEL); @@ -1005,7 +1000,7 @@ static int os05b10_probe(struct i2c_client *client) "xclk frequency not supported: %d Hz\n", xclk_freq); - for (i = 0; i < ARRAY_SIZE(os05b10_supply_name); i++) + for (unsigned int i = 0; i < ARRAY_SIZE(os05b10_supply_name); i++) os05b10->supplies[i].supply = os05b10_supply_name[i]; ret = devm_regulator_bulk_get(os05b10->dev, From a20cf188abe299578d49d229a0cc28a1ef41e558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Fri, 16 Jan 2026 13:55:53 +0100 Subject: [PATCH 243/307] media: pci: mgb4: Add support for GMSL1 modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for GMSL1 modules. GMSL1 modules have 2 inputs, 2 outputs and require a separate FW (id #3). Signed-off-by: Martin Tůma Signed-off-by: Hans Verkuil --- drivers/media/pci/mgb4/mgb4_core.c | 22 ++++++-- drivers/media/pci/mgb4/mgb4_core.h | 9 ++-- drivers/media/pci/mgb4/mgb4_sysfs.h | 6 ++- drivers/media/pci/mgb4/mgb4_sysfs_in.c | 49 +++++++++++++---- drivers/media/pci/mgb4/mgb4_sysfs_out.c | 38 ++++++++++--- drivers/media/pci/mgb4/mgb4_vin.c | 72 ++++++++++++++++--------- drivers/media/pci/mgb4/mgb4_vout.c | 64 +++++++++++++++------- 7 files changed, 189 insertions(+), 71 deletions(-) diff --git a/drivers/media/pci/mgb4/mgb4_core.c b/drivers/media/pci/mgb4/mgb4_core.c index 3ce6b717ca32..a7cb8dc50b53 100644 --- a/drivers/media/pci/mgb4/mgb4_core.c +++ b/drivers/media/pci/mgb4/mgb4_core.c @@ -381,6 +381,18 @@ static int get_serial_number(struct mgb4_dev *mgbdev) return 0; } +static const char *module_type_str(struct mgb4_dev *mgbdev) +{ + if (MGB4_IS_FPDL3(mgbdev)) + return "FPDL3"; + else if (MGB4_IS_GMSL3(mgbdev)) + return "GMSL3"; + else if (MGB4_IS_GMSL1(mgbdev)) + return "GMSL1"; + else + return "UNKNOWN"; +} + static int get_module_version(struct mgb4_dev *mgbdev) { struct device *dev = &mgbdev->pdev->dev; @@ -402,19 +414,21 @@ static int get_module_version(struct mgb4_dev *mgbdev) } mgbdev->module_version = ~((u32)version) & 0xff; - if (!(MGB4_IS_FPDL3(mgbdev) || MGB4_IS_GMSL(mgbdev))) { + if (!(MGB4_IS_FPDL3(mgbdev) || + MGB4_IS_GMSL3(mgbdev) || + MGB4_IS_GMSL1(mgbdev))) { dev_err(dev, "unknown module type\n"); return -EINVAL; } fw_version = mgb4_read_reg(&mgbdev->video, 0xC4) >> 24; if ((MGB4_IS_FPDL3(mgbdev) && fw_version != 1) || - (MGB4_IS_GMSL(mgbdev) && fw_version != 2)) { + (MGB4_IS_GMSL3(mgbdev) && fw_version != 2) || + (MGB4_IS_GMSL1(mgbdev) && fw_version != 3)) { dev_err(dev, "module/firmware type mismatch\n"); return -EINVAL; } - dev_info(dev, "%s module detected\n", - MGB4_IS_FPDL3(mgbdev) ? "FPDL3" : "GMSL"); + dev_info(dev, "%s module detected\n", module_type_str(mgbdev)); return 0; } diff --git a/drivers/media/pci/mgb4/mgb4_core.h b/drivers/media/pci/mgb4/mgb4_core.h index cc24068400a2..06ef8bb44089 100644 --- a/drivers/media/pci/mgb4/mgb4_core.h +++ b/drivers/media/pci/mgb4/mgb4_core.h @@ -18,14 +18,17 @@ #define MGB4_VIN_DEVICES 2 #define MGB4_VOUT_DEVICES 2 -#define MGB4_IS_GMSL(mgbdev) \ +#define MGB4_IS_GMSL1(mgbdev) \ + (((mgbdev)->module_version >> 4) == 6) +#define MGB4_IS_GMSL3(mgbdev) \ ((((mgbdev)->module_version >> 4) >= 2) && \ (((mgbdev)->module_version >> 4) <= 4)) #define MGB4_IS_FPDL3(mgbdev) \ (((mgbdev)->module_version >> 4) == 1) #define MGB4_HAS_VOUT(mgbdev) \ - ((((mgbdev)->module_version >> 4) >= 1) && \ - (((mgbdev)->module_version >> 4) <= 3)) + (((((mgbdev)->module_version >> 4) >= 1) && \ + (((mgbdev)->module_version >> 4) <= 3)) || \ + ((((mgbdev)->module_version >> 4) == 6))) struct mgb4_dma_channel { struct dma_chan *chan; diff --git a/drivers/media/pci/mgb4/mgb4_sysfs.h b/drivers/media/pci/mgb4/mgb4_sysfs.h index 017d82c0624e..fa7fd232c2c2 100644 --- a/drivers/media/pci/mgb4/mgb4_sysfs.h +++ b/drivers/media/pci/mgb4/mgb4_sysfs.h @@ -11,8 +11,10 @@ extern struct attribute *mgb4_pci_attrs[]; extern struct attribute *mgb4_fpdl3_in_attrs[]; -extern struct attribute *mgb4_gmsl_in_attrs[]; +extern struct attribute *mgb4_gmsl3_in_attrs[]; +extern struct attribute *mgb4_gmsl1_in_attrs[]; extern struct attribute *mgb4_fpdl3_out_attrs[]; -extern struct attribute *mgb4_gmsl_out_attrs[]; +extern struct attribute *mgb4_gmsl3_out_attrs[]; +extern struct attribute *mgb4_gmsl1_out_attrs[]; #endif diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_in.c b/drivers/media/pci/mgb4/mgb4_sysfs_in.c index 9626fa59e3d3..4cd4addcd0a5 100644 --- a/drivers/media/pci/mgb4/mgb4_sysfs_in.c +++ b/drivers/media/pci/mgb4/mgb4_sysfs_in.c @@ -36,10 +36,13 @@ static ssize_t oldi_lane_width_show(struct device *dev, u32 config; int ret; - i2c_reg = MGB4_IS_GMSL(mgbdev) ? 0x1CE : 0x49; - i2c_mask = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x03; - i2c_single_val = MGB4_IS_GMSL(mgbdev) ? 0x00 : 0x02; - i2c_dual_val = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x00; + if (MGB4_IS_GMSL1(mgbdev)) + return sprintf(buf, "0\n"); + + i2c_reg = MGB4_IS_GMSL3(mgbdev) ? 0x1CE : 0x49; + i2c_mask = MGB4_IS_GMSL3(mgbdev) ? 0x0E : 0x03; + i2c_single_val = MGB4_IS_GMSL3(mgbdev) ? 0x00 : 0x02; + i2c_dual_val = MGB4_IS_GMSL3(mgbdev) ? 0x0E : 0x00; mutex_lock(&mgbdev->i2c_lock); ret = mgb4_i2c_read_byte(&vindev->deser, i2c_reg); @@ -79,21 +82,24 @@ static ssize_t oldi_lane_width_store(struct device *dev, if (ret) return ret; + if (MGB4_IS_GMSL1(mgbdev)) + return val ? -EINVAL : count; + switch (val) { case 0: /* single */ fpga_data = 0; - i2c_data = MGB4_IS_GMSL(mgbdev) ? 0x00 : 0x02; + i2c_data = MGB4_IS_GMSL3(mgbdev) ? 0x00 : 0x02; break; case 1: /* dual */ fpga_data = 1U << 9; - i2c_data = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x00; + i2c_data = MGB4_IS_GMSL3(mgbdev) ? 0x0E : 0x00; break; default: return -EINVAL; } - i2c_reg = MGB4_IS_GMSL(mgbdev) ? 0x1CE : 0x49; - i2c_mask = MGB4_IS_GMSL(mgbdev) ? 0x0E : 0x03; + i2c_reg = MGB4_IS_GMSL3(mgbdev) ? 0x1CE : 0x49; + i2c_mask = MGB4_IS_GMSL3(mgbdev) ? 0x0E : 0x03; mutex_lock(&mgbdev->i2c_lock); ret = mgb4_i2c_mask_byte(&vindev->deser, i2c_reg, i2c_mask, i2c_data); @@ -102,7 +108,7 @@ static ssize_t oldi_lane_width_store(struct device *dev, return -EIO; mgb4_mask_reg(&mgbdev->video, vindev->config->regs.config, 1U << 9, fpga_data); - if (MGB4_IS_GMSL(mgbdev)) { + if (MGB4_IS_GMSL3(mgbdev)) { /* reset input link */ mutex_lock(&mgbdev->i2c_lock); ret = mgb4_i2c_mask_byte(&vindev->deser, 0x10, 1U << 5, 1U << 5); @@ -745,7 +751,7 @@ struct attribute *mgb4_fpdl3_in_attrs[] = { NULL }; -struct attribute *mgb4_gmsl_in_attrs[] = { +struct attribute *mgb4_gmsl3_in_attrs[] = { &dev_attr_input_id.attr, &dev_attr_link_status.attr, &dev_attr_stream_status.attr, @@ -770,3 +776,26 @@ struct attribute *mgb4_gmsl_in_attrs[] = { &dev_attr_gmsl_fec.attr, NULL }; + +struct attribute *mgb4_gmsl1_in_attrs[] = { + &dev_attr_input_id.attr, + &dev_attr_link_status.attr, + &dev_attr_stream_status.attr, + &dev_attr_video_width.attr, + &dev_attr_video_height.attr, + &dev_attr_hsync_status.attr, + &dev_attr_vsync_status.attr, + &dev_attr_oldi_lane_width.attr, + &dev_attr_color_mapping.attr, + &dev_attr_hsync_gap_length.attr, + &dev_attr_vsync_gap_length.attr, + &dev_attr_pclk_frequency.attr, + &dev_attr_hsync_width.attr, + &dev_attr_vsync_width.attr, + &dev_attr_hback_porch.attr, + &dev_attr_hfront_porch.attr, + &dev_attr_vback_porch.attr, + &dev_attr_vfront_porch.attr, + &dev_attr_frequency_range.attr, + NULL +}; diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c index 573aa61c69d4..5769f3ca6c2f 100644 --- a/drivers/media/pci/mgb4/mgb4_sysfs_out.c +++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c @@ -665,6 +665,7 @@ static ssize_t pclk_frequency_store(struct device *dev, { struct video_device *vdev = to_video_device(dev); struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev); + struct mgb4_dev *mgbdev = voutdev->mgbdev; unsigned long val; int ret; unsigned int dp; @@ -679,14 +680,16 @@ static ssize_t pclk_frequency_store(struct device *dev, return -EBUSY; } - dp = (val > 50000) ? 1 : 0; + dp = (MGB4_IS_FPDL3(mgbdev) && val > 50000) ? 1 : 0; voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, val >> dp) << dp; - - mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config, - 0x10, dp << 4); - mutex_lock(&voutdev->mgbdev->i2c_lock); - ret = mgb4_i2c_mask_byte(&voutdev->ser, 0x4F, 1 << 6, ((~dp) & 1) << 6); - mutex_unlock(&voutdev->mgbdev->i2c_lock); + mgb4_mask_reg(&mgbdev->video, voutdev->config->regs.config, 0x10, + dp << 4); + if (MGB4_IS_FPDL3(mgbdev)) { + mutex_lock(&mgbdev->i2c_lock); + ret = mgb4_i2c_mask_byte(&voutdev->ser, 0x4F, 1 << 6, + ((~dp) & 1) << 6); + mutex_unlock(&mgbdev->i2c_lock); + } mutex_unlock(voutdev->vdev.lock); @@ -731,7 +734,7 @@ struct attribute *mgb4_fpdl3_out_attrs[] = { NULL }; -struct attribute *mgb4_gmsl_out_attrs[] = { +struct attribute *mgb4_gmsl3_out_attrs[] = { &dev_attr_output_id.attr, &dev_attr_video_source.attr, &dev_attr_display_width.attr, @@ -739,3 +742,22 @@ struct attribute *mgb4_gmsl_out_attrs[] = { &dev_attr_frame_rate.attr, NULL }; + +struct attribute *mgb4_gmsl1_out_attrs[] = { + &dev_attr_output_id.attr, + &dev_attr_video_source.attr, + &dev_attr_display_width.attr, + &dev_attr_display_height.attr, + &dev_attr_frame_rate.attr, + &dev_attr_hsync_polarity.attr, + &dev_attr_vsync_polarity.attr, + &dev_attr_de_polarity.attr, + &dev_attr_pclk_frequency.attr, + &dev_attr_hsync_width.attr, + &dev_attr_vsync_width.attr, + &dev_attr_hback_porch.attr, + &dev_attr_hfront_porch.attr, + &dev_attr_vback_porch.attr, + &dev_attr_vfront_porch.attr, + NULL +}; diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c index 4b38076486ff..be55491b52b2 100644 --- a/drivers/media/pci/mgb4/mgb4_vin.c +++ b/drivers/media/pci/mgb4/mgb4_vin.c @@ -32,7 +32,8 @@ #include "mgb4_vin.h" ATTRIBUTE_GROUPS(mgb4_fpdl3_in); -ATTRIBUTE_GROUPS(mgb4_gmsl_in); +ATTRIBUTE_GROUPS(mgb4_gmsl3_in); +ATTRIBUTE_GROUPS(mgb4_gmsl1_in); static const struct mgb4_vin_config vin_cfg[] = { {0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28, 0xE8}}, @@ -44,23 +45,31 @@ static const struct i2c_board_info fpdl3_deser_info[] = { {I2C_BOARD_INFO("deserializer2", 0x36)}, }; -static const struct i2c_board_info gmsl_deser_info[] = { +static const struct i2c_board_info gmsl3_deser_info[] = { {I2C_BOARD_INFO("deserializer1", 0x4C)}, {I2C_BOARD_INFO("deserializer2", 0x2A)}, }; +static const struct i2c_board_info gmsl1_deser_info[] = { + {I2C_BOARD_INFO("deserializer1", 0x2C)}, + {I2C_BOARD_INFO("deserializer2", 0x6C)}, +}; + static const struct mgb4_i2c_kv fpdl3_i2c[] = { {0x06, 0xFF, 0x04}, {0x07, 0xFF, 0x01}, {0x45, 0xFF, 0xE8}, {0x49, 0xFF, 0x00}, {0x34, 0xFF, 0x00}, {0x23, 0xFF, 0x00} }; -static const struct mgb4_i2c_kv gmsl_i2c[] = { +static const struct mgb4_i2c_kv gmsl3_i2c[] = { {0x01, 0x03, 0x03}, {0x300, 0x0C, 0x0C}, {0x03, 0xC0, 0xC0}, {0x1CE, 0x0E, 0x0E}, {0x11, 0x05, 0x00}, {0x05, 0xC0, 0x40}, {0x307, 0x0F, 0x00}, {0xA0, 0x03, 0x00}, {0x3E0, 0x07, 0x07}, {0x308, 0x01, 0x01}, {0x10, 0x20, 0x20}, {0x300, 0x40, 0x40} }; +static const struct mgb4_i2c_kv gmsl1_i2c[] = { +}; + static const struct v4l2_dv_timings_cap video_timings_cap = { .type = V4L2_DV_BT_656_1120, .bt = { @@ -796,22 +805,29 @@ static irqreturn_t err_handler(int irq, void *ctx) static int deser_init(struct mgb4_vin_dev *vindev, int id) { - int rv, addr_size; - size_t values_count; - const struct mgb4_i2c_kv *values; - const struct i2c_board_info *info; + int rv, addr_size = 0; + size_t count = 0; + const struct mgb4_i2c_kv *values = NULL; + const struct i2c_board_info *info = NULL; struct device *dev = &vindev->mgbdev->pdev->dev; - if (MGB4_IS_GMSL(vindev->mgbdev)) { - info = &gmsl_deser_info[id]; + if (MGB4_IS_GMSL3(vindev->mgbdev)) { + info = &gmsl3_deser_info[id]; addr_size = 16; - values = gmsl_i2c; - values_count = ARRAY_SIZE(gmsl_i2c); - } else { + values = gmsl3_i2c; + count = ARRAY_SIZE(gmsl3_i2c); + } else if (MGB4_IS_FPDL3(vindev->mgbdev)) { info = &fpdl3_deser_info[id]; addr_size = 8; values = fpdl3_i2c; - values_count = ARRAY_SIZE(fpdl3_i2c); + count = ARRAY_SIZE(fpdl3_i2c); + } else if (MGB4_IS_GMSL1(vindev->mgbdev)) { + info = &gmsl1_deser_info[id]; + addr_size = 8; + values = gmsl1_i2c; + count = ARRAY_SIZE(gmsl1_i2c); + } else { + return -EINVAL; } rv = mgb4_i2c_init(&vindev->deser, vindev->mgbdev->i2c_adap, info, @@ -820,7 +836,7 @@ static int deser_init(struct mgb4_vin_dev *vindev, int id) dev_err(dev, "failed to create deserializer\n"); return rv; } - rv = mgb4_i2c_configure(&vindev->deser, values, values_count); + rv = mgb4_i2c_configure(&vindev->deser, values, count); if (rv < 0) { dev_err(dev, "failed to configure deserializer\n"); goto err_i2c_dev; @@ -838,11 +854,12 @@ static void fpga_init(struct mgb4_vin_dev *vindev) { struct mgb4_regs *video = &vindev->mgbdev->video; const struct mgb4_vin_regs *regs = &vindev->config->regs; + int dp = MGB4_IS_GMSL1(vindev->mgbdev) ? 0 : 1; mgb4_write_reg(video, regs->config, 0x00000001); mgb4_write_reg(video, regs->sync, 0x03E80002); mgb4_write_reg(video, regs->padding, 0x00000000); - mgb4_write_reg(video, regs->config, 1U << 9); + mgb4_write_reg(video, regs->config, dp << 9); } static void create_debugfs(struct mgb4_vin_dev *vindev) @@ -890,10 +907,21 @@ static void create_debugfs(struct mgb4_vin_dev *vindev) #endif } +static const struct attribute_group **module_groups(struct mgb4_dev *mgbdev) +{ + if (MGB4_IS_FPDL3(mgbdev)) + return mgb4_fpdl3_in_groups; + else if (MGB4_IS_GMSL3(mgbdev)) + return mgb4_gmsl3_in_groups; + else if (MGB4_IS_GMSL1(mgbdev)) + return mgb4_gmsl1_in_groups; + else + return NULL; +} + struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int id) { int rv; - const struct attribute_group **groups; struct mgb4_vin_dev *vindev; struct pci_dev *pdev = mgbdev->pdev; struct device *dev = &pdev->dev; @@ -914,14 +942,13 @@ struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int id) INIT_WORK(&vindev->dma_work, dma_transfer); INIT_WORK(&vindev->err_work, signal_change); - /* IRQ callback */ + /* IRQ callbacks */ vin_irq = xdma_get_user_irq(mgbdev->xdev, vindev->config->vin_irq); rv = request_irq(vin_irq, vin_handler, 0, "mgb4-vin", vindev); if (rv) { dev_err(dev, "failed to register vin irq handler\n"); goto err_alloc; } - /* Error IRQ callback */ err_irq = xdma_get_user_irq(mgbdev->xdev, vindev->config->err_irq); rv = request_irq(err_irq, err_handler, 0, "mgb4-err", vindev); if (rv) { @@ -986,9 +1013,7 @@ struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int id) } /* Module sysfs attributes */ - groups = MGB4_IS_GMSL(mgbdev) - ? mgb4_gmsl_in_groups : mgb4_fpdl3_in_groups; - rv = device_add_groups(&vindev->vdev.dev, groups); + rv = device_add_groups(&vindev->vdev.dev, module_groups(mgbdev)); if (rv) { dev_err(dev, "failed to create sysfs attributes\n"); goto err_video_dev; @@ -1014,7 +1039,6 @@ err_alloc: void mgb4_vin_free(struct mgb4_vin_dev *vindev) { - const struct attribute_group **groups; int vin_irq = xdma_get_user_irq(vindev->mgbdev->xdev, vindev->config->vin_irq); int err_irq = xdma_get_user_irq(vindev->mgbdev->xdev, @@ -1025,9 +1049,7 @@ void mgb4_vin_free(struct mgb4_vin_dev *vindev) free_irq(vin_irq, vindev); free_irq(err_irq, vindev); - groups = MGB4_IS_GMSL(vindev->mgbdev) - ? mgb4_gmsl_in_groups : mgb4_fpdl3_in_groups; - device_remove_groups(&vindev->vdev.dev, groups); + device_remove_groups(&vindev->vdev.dev, module_groups(vindev->mgbdev)); mgb4_i2c_free(&vindev->deser); video_unregister_device(&vindev->vdev); diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c index fd93fbbaf755..44e9565d4d06 100644 --- a/drivers/media/pci/mgb4/mgb4_vout.c +++ b/drivers/media/pci/mgb4/mgb4_vout.c @@ -25,7 +25,8 @@ #include "mgb4_vout.h" ATTRIBUTE_GROUPS(mgb4_fpdl3_out); -ATTRIBUTE_GROUPS(mgb4_gmsl_out); +ATTRIBUTE_GROUPS(mgb4_gmsl3_out); +ATTRIBUTE_GROUPS(mgb4_gmsl1_out); static const struct mgb4_vout_config vout_cfg[] = { {0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7C, 0xE0}}, @@ -37,10 +38,18 @@ static const struct i2c_board_info fpdl3_ser_info[] = { {I2C_BOARD_INFO("serializer2", 0x16)}, }; +static const struct i2c_board_info gmsl1_ser_info[] = { + {I2C_BOARD_INFO("serializer1", 0x24)}, + {I2C_BOARD_INFO("serializer2", 0x22)}, +}; + static const struct mgb4_i2c_kv fpdl3_i2c[] = { {0x05, 0xFF, 0x04}, {0x06, 0xFF, 0x01}, {0xC2, 0xFF, 0x80} }; +static const struct mgb4_i2c_kv gmsl1_i2c[] = { +}; + static const struct v4l2_dv_timings_cap video_timings_cap = { .type = V4L2_DV_BT_656_1120, .bt = { @@ -634,12 +643,24 @@ static irqreturn_t handler(int irq, void *ctx) static int ser_init(struct mgb4_vout_dev *voutdev, int id) { - int rv; - const struct i2c_board_info *info = &fpdl3_ser_info[id]; struct mgb4_i2c_client *ser = &voutdev->ser; struct device *dev = &voutdev->mgbdev->pdev->dev; + const struct i2c_board_info *info = NULL; + const struct mgb4_i2c_kv *values = NULL; + size_t count = 0; + int rv; - if (MGB4_IS_GMSL(voutdev->mgbdev)) + if (MGB4_IS_FPDL3(voutdev->mgbdev)) { + info = &fpdl3_ser_info[id]; + values = fpdl3_i2c; + count = ARRAY_SIZE(fpdl3_i2c); + } else if (MGB4_IS_GMSL1(voutdev->mgbdev)) { + info = &gmsl1_ser_info[id]; + values = gmsl1_i2c; + count = ARRAY_SIZE(gmsl1_i2c); + } + + if (!info) return 0; rv = mgb4_i2c_init(ser, voutdev->mgbdev->i2c_adap, info, 8); @@ -647,7 +668,7 @@ static int ser_init(struct mgb4_vout_dev *voutdev, int id) dev_err(dev, "failed to create serializer\n"); return rv; } - rv = mgb4_i2c_configure(ser, fpdl3_i2c, ARRAY_SIZE(fpdl3_i2c)); + rv = mgb4_i2c_configure(ser, values, count); if (rv < 0) { dev_err(dev, "failed to configure serializer\n"); goto err_i2c_dev; @@ -665,18 +686,19 @@ static void fpga_init(struct mgb4_vout_dev *voutdev) { struct mgb4_regs *video = &voutdev->mgbdev->video; const struct mgb4_vout_regs *regs = &voutdev->config->regs; + int dp = MGB4_IS_GMSL1(voutdev->mgbdev) ? 0 : 1; + u32 source = (voutdev->config->id + MGB4_VIN_DEVICES) << 2; - mgb4_write_reg(video, regs->config, 0x00000011); + mgb4_write_reg(video, regs->config, 0x00000001); mgb4_write_reg(video, regs->resolution, (1280 << 16) | 640); mgb4_write_reg(video, regs->hsync, 0x00283232); mgb4_write_reg(video, regs->vsync, 0x40141F1E); mgb4_write_reg(video, regs->frame_limit, MGB4_HW_FREQ / 60); mgb4_write_reg(video, regs->padding, 0x00000000); - voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 61150 >> 1) << 1; + voutdev->freq = mgb4_cmt_set_vout_freq(voutdev, 61150 >> dp) << dp; - mgb4_write_reg(video, regs->config, - (voutdev->config->id + MGB4_VIN_DEVICES) << 2 | 1 << 4); + mgb4_write_reg(video, regs->config, source | dp << 4); } static void create_debugfs(struct mgb4_vout_dev *voutdev) @@ -720,10 +742,21 @@ static void create_debugfs(struct mgb4_vout_dev *voutdev) #endif } +static const struct attribute_group **module_groups(struct mgb4_dev *mgbdev) +{ + if (MGB4_IS_FPDL3(mgbdev)) + return mgb4_fpdl3_out_groups; + else if (MGB4_IS_GMSL3(mgbdev)) + return mgb4_gmsl3_out_groups; + else if (MGB4_IS_GMSL1(mgbdev)) + return mgb4_gmsl1_out_groups; + else + return NULL; +} + struct mgb4_vout_dev *mgb4_vout_create(struct mgb4_dev *mgbdev, int id) { int rv, irq; - const struct attribute_group **groups; struct mgb4_vout_dev *voutdev; struct pci_dev *pdev = mgbdev->pdev; struct device *dev = &pdev->dev; @@ -804,9 +837,7 @@ struct mgb4_vout_dev *mgb4_vout_create(struct mgb4_dev *mgbdev, int id) } /* Module sysfs attributes */ - groups = MGB4_IS_GMSL(mgbdev) - ? mgb4_gmsl_out_groups : mgb4_fpdl3_out_groups; - rv = device_add_groups(&voutdev->vdev.dev, groups); + rv = device_add_groups(&voutdev->vdev.dev, module_groups(mgbdev)); if (rv) { dev_err(dev, "failed to create sysfs attributes\n"); goto err_video_dev; @@ -830,15 +861,10 @@ err_alloc: void mgb4_vout_free(struct mgb4_vout_dev *voutdev) { - const struct attribute_group **groups; int irq = xdma_get_user_irq(voutdev->mgbdev->xdev, voutdev->config->irq); free_irq(irq, voutdev); - - groups = MGB4_IS_GMSL(voutdev->mgbdev) - ? mgb4_gmsl_out_groups : mgb4_fpdl3_out_groups; - device_remove_groups(&voutdev->vdev.dev, groups); - + device_remove_groups(&voutdev->vdev.dev, module_groups(voutdev->mgbdev)); mgb4_i2c_free(&voutdev->ser); video_unregister_device(&voutdev->vdev); v4l2_device_unregister(&voutdev->v4l2dev); From f67adf545f9e7db4731fb7721597557e2571f891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Fri, 16 Jan 2026 13:55:54 +0100 Subject: [PATCH 244/307] media: pci: mgb4: Add support for GMSL3 coaxial modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for GMSL3 modules with a coaxial interface, a variant of the already existing module with the id #4. The FW is the same as for all other GMSL3 modules. Signed-off-by: Martin Tůma Signed-off-by: Hans Verkuil --- drivers/media/pci/mgb4/mgb4_core.h | 7 +++++-- drivers/media/pci/mgb4/mgb4_vin.c | 27 +++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/drivers/media/pci/mgb4/mgb4_core.h b/drivers/media/pci/mgb4/mgb4_core.h index 06ef8bb44089..bfae75f30c7c 100644 --- a/drivers/media/pci/mgb4/mgb4_core.h +++ b/drivers/media/pci/mgb4/mgb4_core.h @@ -21,8 +21,11 @@ #define MGB4_IS_GMSL1(mgbdev) \ (((mgbdev)->module_version >> 4) == 6) #define MGB4_IS_GMSL3(mgbdev) \ - ((((mgbdev)->module_version >> 4) >= 2) && \ - (((mgbdev)->module_version >> 4) <= 4)) + (((((mgbdev)->module_version >> 4) >= 2) && \ + (((mgbdev)->module_version >> 4) <= 4)) || \ + (((mgbdev)->module_version >> 4) == 8)) +#define MGB4_IS_GMSL3C(mgbdev) \ + (((mgbdev)->module_version >> 4) == 8) #define MGB4_IS_FPDL3(mgbdev) \ (((mgbdev)->module_version >> 4) == 1) #define MGB4_HAS_VOUT(mgbdev) \ diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c index be55491b52b2..e782db79686f 100644 --- a/drivers/media/pci/mgb4/mgb4_vin.c +++ b/drivers/media/pci/mgb4/mgb4_vin.c @@ -50,6 +50,11 @@ static const struct i2c_board_info gmsl3_deser_info[] = { {I2C_BOARD_INFO("deserializer2", 0x2A)}, }; +static const struct i2c_board_info gmsl3c_deser_info[] = { + {I2C_BOARD_INFO("deserializer1", 0x6A)}, + {I2C_BOARD_INFO("deserializer2", 0x6C)}, +}; + static const struct i2c_board_info gmsl1_deser_info[] = { {I2C_BOARD_INFO("deserializer1", 0x2C)}, {I2C_BOARD_INFO("deserializer2", 0x6C)}, @@ -67,6 +72,13 @@ static const struct mgb4_i2c_kv gmsl3_i2c[] = { {0x308, 0x01, 0x01}, {0x10, 0x20, 0x20}, {0x300, 0x40, 0x40} }; +static const struct mgb4_i2c_kv gmsl3c_i2c[] = { + {0x01, 0x03, 0x02}, {0x300, 0x0C, 0x08}, {0x03, 0xC0, 0x00}, + {0x1CE, 0x0E, 0x0E}, {0x11, 0x05, 0x05}, {0x05, 0xC0, 0x40}, + {0x307, 0x0F, 0x00}, {0xA0, 0x03, 0x00}, {0x3E0, 0x07, 0x00}, + {0x308, 0x01, 0x00}, {0x10, 0x20, 0x20}, {0x300, 0x40, 0x40} +}; + static const struct mgb4_i2c_kv gmsl1_i2c[] = { }; @@ -812,10 +824,17 @@ static int deser_init(struct mgb4_vin_dev *vindev, int id) struct device *dev = &vindev->mgbdev->pdev->dev; if (MGB4_IS_GMSL3(vindev->mgbdev)) { - info = &gmsl3_deser_info[id]; - addr_size = 16; - values = gmsl3_i2c; - count = ARRAY_SIZE(gmsl3_i2c); + if (MGB4_IS_GMSL3C(vindev->mgbdev)) { + info = &gmsl3c_deser_info[id]; + addr_size = 16; + values = gmsl3c_i2c; + count = ARRAY_SIZE(gmsl3c_i2c); + } else { + info = &gmsl3_deser_info[id]; + addr_size = 16; + values = gmsl3_i2c; + count = ARRAY_SIZE(gmsl3_i2c); + } } else if (MGB4_IS_FPDL3(vindev->mgbdev)) { info = &fpdl3_deser_info[id]; addr_size = 8; From eb4ee870747c3a77a9c3c84d84efb64bd481013a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Fri, 16 Jan 2026 13:55:55 +0100 Subject: [PATCH 245/307] Documentation: admin-guide: media: mgb4: Add GMSL1 & GMSL3-coax modules info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the mgb4 GMSL1 and GMSL3-coax modules info. Signed-off-by: Martin Tůma Signed-off-by: Hans Verkuil --- Documentation/admin-guide/media/mgb4.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Documentation/admin-guide/media/mgb4.rst b/Documentation/admin-guide/media/mgb4.rst index 5ac69b833a7a..0a8a56e837f7 100644 --- a/Documentation/admin-guide/media/mgb4.rst +++ b/Documentation/admin-guide/media/mgb4.rst @@ -31,9 +31,11 @@ Global (PCI card) parameters | 0 - No module present | 1 - FPDL3 - | 2 - GMSL (one serializer, two daisy chained deserializers) - | 3 - GMSL (one serializer, two deserializers) - | 4 - GMSL (two deserializers with two daisy chain outputs) + | 2 - GMSL3 (one serializer, two daisy chained deserializers) + | 3 - GMSL3 (one serializer, two deserializers) + | 4 - GMSL3 (two deserializers with two daisy chain outputs) + | 6 - GMSL1 + | 8 - GMSL3 coax **module_version** (R): Module version number. Zero in case of a missing module. @@ -42,7 +44,8 @@ Global (PCI card) parameters Firmware type. | 1 - FPDL3 - | 2 - GMSL + | 2 - GMSL3 + | 3 - GMSL1 **fw_version** (R): Firmware version number. From 93ecd6ee95c38cb533fa25f48d3c1c8cb69f410f Mon Sep 17 00:00:00 2001 From: Renjiang Han Date: Thu, 11 Dec 2025 15:20:39 +0530 Subject: [PATCH 246/307] media: venus: vdec: fix error state assignment for zero bytesused When hfi_session_flush is issued, all queued buffers are returned to the V4L2 driver. Some of these buffers are not processed and have bytesused = 0. Currently, the driver marks such buffers as error even during drain operations, which can incorrectly flag EOS buffers. Only capture buffers with zero payload (and not EOS) should be marked with VB2_BUF_STATE_ERROR. The check is performed inside the non-EOS branch to ensure correct handling. Fixes: 51df3c81ba10b ("media: venus: vdec: Mark flushed buffers with error state") Signed-off-by: Renjiang Han Reviewed-by: Vikash Garodia Cc: stable@vger.kernel.org Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/vdec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/qcom/venus/vdec.c b/drivers/media/platform/qcom/venus/vdec.c index 4a6641fdffcf..d0bd2d86a31f 100644 --- a/drivers/media/platform/qcom/venus/vdec.c +++ b/drivers/media/platform/qcom/venus/vdec.c @@ -1440,10 +1440,10 @@ static void vdec_buf_done(struct venus_inst *inst, unsigned int buf_type, inst->drain_active = false; inst->codec_state = VENUS_DEC_STATE_STOPPED; } + } else { + if (!bytesused) + state = VB2_BUF_STATE_ERROR; } - - if (!bytesused) - state = VB2_BUF_STATE_ERROR; } else { vbuf->sequence = inst->sequence_out++; } From 63c072e2937e6c9995df1b6a28523ed2ae68d364 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Tue, 25 Nov 2025 11:04:19 +0530 Subject: [PATCH 247/307] media: venus: vdec: restrict EOS addr quirk to IRIS2 only On SM8250 (IRIS2) with firmware older than 1.0.087, the firmware could not handle a dummy device address for EOS buffers, so a NULL device address is sent instead. The existing check used IS_V6() alongside a firmware version gate: if (IS_V6(core) && is_fw_rev_or_older(core, 1, 0, 87)) fdata.device_addr = 0; else fdata.device_addr = 0xdeadb000; However, SC7280 which is also V6, uses a firmware string of the form "1.0.", which the version parser translates to 1.0.0. This unintentionally satisfies the `is_fw_rev_or_older(..., 1, 0, 87)` condition on SC7280. Combined with IS_V6() matching there as well, the quirk is incorrectly applied to SC7280, causing VP9 decode failures. Constrain the check to IRIS2 (SM8250) only, which is the only platform that needed this quirk, by replacing IS_V6() with IS_IRIS2(). This restores correct behavior on SC7280 (no forced NULL EOS buffer address). Fixes: 47f867cb1b63 ("media: venus: fix EOS handling in decoder stop command") Cc: stable@vger.kernel.org Reported-by: Mecid Closes: https://github.com/qualcomm-linux/kernel-topics/issues/222 Co-developed-by: Renjiang Han Signed-off-by: Renjiang Han Signed-off-by: Dikshita Agarwal Tested-by: Renjiang Han Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/vdec.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/venus/vdec.c b/drivers/media/platform/qcom/venus/vdec.c index d0bd2d86a31f..4cd69440e875 100644 --- a/drivers/media/platform/qcom/venus/vdec.c +++ b/drivers/media/platform/qcom/venus/vdec.c @@ -565,7 +565,13 @@ vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd) fdata.buffer_type = HFI_BUFFER_INPUT; fdata.flags |= HFI_BUFFERFLAG_EOS; - if (IS_V6(inst->core) && is_fw_rev_or_older(inst->core, 1, 0, 87)) + + /* Send NULL EOS addr for only IRIS2 (SM8250),for firmware <= 1.0.87. + * SC7280 also reports "1.0." parsed as 1.0.0; restricting to IRIS2 + * avoids misapplying this quirk and breaking VP9 decode on SC7280. + */ + + if (IS_IRIS2(inst->core) && is_fw_rev_or_older(inst->core, 1, 0, 87)) fdata.device_addr = 0; else fdata.device_addr = 0xdeadb000; From 66c744e28b69d931b142adb6b9f5e56ac9644f79 Mon Sep 17 00:00:00 2001 From: Jorge Ramirez-Ortiz Date: Wed, 26 Nov 2025 15:00:24 +0100 Subject: [PATCH 248/307] media: venus: assign unique bus_info strings for encoder and decoder The Venus encoder and decoder video devices currently report the same bus_info string ("platform:qcom-venus"). Assign unique bus_info identifiers by appending ":dec" and ":enc" to the parent device name. With this change v4l2-ctl will display two separate logical devices Signed-off-by: Jorge Ramirez-Ortiz Reviewed-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/vdec.c | 6 +++++- drivers/media/platform/qcom/venus/venc.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/venus/vdec.c b/drivers/media/platform/qcom/venus/vdec.c index 4cd69440e875..21ca4947a849 100644 --- a/drivers/media/platform/qcom/venus/vdec.c +++ b/drivers/media/platform/qcom/venus/vdec.c @@ -433,9 +433,13 @@ vdec_g_selection(struct file *file, void *fh, struct v4l2_selection *s) static int vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap) { + struct venus_inst *inst = to_inst(file); + struct venus_core *core = inst->core; + strscpy(cap->driver, "qcom-venus", sizeof(cap->driver)); strscpy(cap->card, "Qualcomm Venus video decoder", sizeof(cap->card)); - strscpy(cap->bus_info, "platform:qcom-venus", sizeof(cap->bus_info)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "plat:%s:dec", dev_name(core->dev)); return 0; } diff --git a/drivers/media/platform/qcom/venus/venc.c b/drivers/media/platform/qcom/venus/venc.c index b478b982a80d..0b5843ba536f 100644 --- a/drivers/media/platform/qcom/venus/venc.c +++ b/drivers/media/platform/qcom/venus/venc.c @@ -144,9 +144,13 @@ static int venc_v4l2_to_hfi(int id, int value) static int venc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) { + struct venus_inst *inst = to_inst(file); + struct venus_core *core = inst->core; + strscpy(cap->driver, "qcom-venus", sizeof(cap->driver)); strscpy(cap->card, "Qualcomm Venus video encoder", sizeof(cap->card)); - strscpy(cap->bus_info, "platform:qcom-venus", sizeof(cap->bus_info)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "plat:%s:enc", dev_name(core->dev)); return 0; } From 370e19042fb8ac68109f8bdb0fdd8118baf39318 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Mon, 29 Dec 2025 12:01:23 +0530 Subject: [PATCH 249/307] Revert "media: iris: Add sanity check for stop streaming" This reverts commit ad699fa78b59241c9d71a8cafb51525f3dab04d4. Revert the check that skipped stop_streaming when the instance was in IRIS_INST_ERROR, as it caused multiple regressions: 1. Buffers were not returned to vb2 when the instance was already in error state, triggering warnings in the vb2 core because buffer completion was skipped. 2. If a session failed early (e.g. unsupported configuration), the instance transitioned to IRIS_INST_ERROR. When userspace attempted to stop streaming for cleanup, stop_streaming was skipped due to the added check, preventing proper teardown and leaving the firmware in an inconsistent state. Fixes: ad699fa78b59 ("media: iris: Add sanity check for stop streaming") Signed-off-by: Dikshita Agarwal Reviewed-by: Vikash Garodia Cc: stable@vger.kernel.org Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_vb2.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_vb2.c b/drivers/media/platform/qcom/iris/iris_vb2.c index db8768d8a8f6..139b821f7952 100644 --- a/drivers/media/platform/qcom/iris/iris_vb2.c +++ b/drivers/media/platform/qcom/iris/iris_vb2.c @@ -231,8 +231,6 @@ void iris_vb2_stop_streaming(struct vb2_queue *q) return; mutex_lock(&inst->lock); - if (inst->state == IRIS_INST_ERROR) - goto exit; if (!V4L2_TYPE_IS_OUTPUT(q->type) && !V4L2_TYPE_IS_CAPTURE(q->type)) @@ -243,10 +241,10 @@ void iris_vb2_stop_streaming(struct vb2_queue *q) goto exit; exit: - if (ret) { - iris_helper_buffers_done(inst, q->type, VB2_BUF_STATE_ERROR); + iris_helper_buffers_done(inst, q->type, VB2_BUF_STATE_ERROR); + if (ret) iris_inst_change_state(inst, IRIS_INST_ERROR); - } + mutex_unlock(&inst->lock); } From 89f7cf35901138d9828d981ce64c131a3da6e867 Mon Sep 17 00:00:00 2001 From: Vishnu Reddy Date: Mon, 6 Oct 2025 14:48:19 +0530 Subject: [PATCH 250/307] media: iris: Fix ffmpeg corrupted frame error When the ffmpeg decoder is running, the driver receives the V4L2_BUF_FLAG_KEYFRAME flag in the input buffer. The driver then forwards this flag information to the firmware. The firmware, in turn, copies the input buffer flags directly into the output buffer flags. Upon receiving the output buffer from the firmware, the driver observes that the buffer contains the HFI_BUFFERFLAG_DATACORRUPT flag. The root cause is that both V4L2_BUF_FLAG_KEYFRAME and HFI_BUFFERFLAG_DATACORRUPT are the same value. As a result, the driver incorrectly interprets the output frame as corrupted, even though the frame is actually valid. This misinterpretation causes the driver to report an error and skip good frames, leading to missing frames in the final video output and triggering ffmpeg's "corrupt decoded frame" error. To resolve this issue, the input buffer flags should not be sent to the firmware during decoding, since the firmware does not require this information. Fixes: 17f2a485ca67 ("media: iris: implement vb2 ops for buf_queue and firmware response") Cc: stable@vger.kernel.org Signed-off-by: Vishnu Reddy Reviewed-by: Dikshita Agarwal Reviewed-by: Bryan O'Donoghue Reviewed-by: Vikash Garodia Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c index 52da7ef7bab0..b6261d186d21 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c @@ -282,7 +282,7 @@ static int iris_hfi_gen1_queue_input_buffer(struct iris_inst *inst, struct iris_ com_ip_pkt.shdr.session_id = inst->session_id; com_ip_pkt.time_stamp_hi = upper_32_bits(buf->timestamp); com_ip_pkt.time_stamp_lo = lower_32_bits(buf->timestamp); - com_ip_pkt.flags = buf->flags; + com_ip_pkt.flags = 0; com_ip_pkt.mark_target = 0; com_ip_pkt.mark_data = 0; com_ip_pkt.offset = buf->data_offset; From 71fe80364a6584f404556ac9a6a4aca4ab80fb5b Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 23 Dec 2025 10:02:58 +0000 Subject: [PATCH 251/307] media: iris: Fix fps calculation iris_venc_s_param() uses do_div to divide two 64 bits operators, this is wrong. Luckily for us, both of the operators fit in 32 bits, so we can use a normal division. Now that we are at it, mark the fps smaller than 1 as invalid, the code does not seem to handle them properly. The following cocci warning is fixed with this patch: ./platform/qcom/iris/iris_venc.c:378:1-7: WARNING: do_div() does a 64-by-32 division, please consider using div64_u64 instead Fixes: 4ff586ff28e3 ("media: iris: Add support for G/S_PARM for encoder video device") Reviewed-by: Dikshita Agarwal Cc: stable@vger.kernel.org Signed-off-by: Ricardo Ribalda Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_venc.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_venc.c b/drivers/media/platform/qcom/iris/iris_venc.c index 5830eba93c68..0ed5018f9fe3 100644 --- a/drivers/media/platform/qcom/iris/iris_venc.c +++ b/drivers/media/platform/qcom/iris/iris_venc.c @@ -382,8 +382,7 @@ int iris_venc_s_param(struct iris_inst *inst, struct v4l2_streamparm *s_parm) struct v4l2_fract *timeperframe = NULL; u32 default_rate = DEFAULT_FPS; bool is_frame_rate = false; - u64 us_per_frame, fps; - u32 max_rate; + u32 fps, max_rate; int ret = 0; @@ -405,23 +404,19 @@ int iris_venc_s_param(struct iris_inst *inst, struct v4l2_streamparm *s_parm) timeperframe->denominator = default_rate; } - us_per_frame = timeperframe->numerator * (u64)USEC_PER_SEC; - do_div(us_per_frame, timeperframe->denominator); - - if (!us_per_frame) + fps = timeperframe->denominator / timeperframe->numerator; + if (!fps) return -EINVAL; - fps = (u64)USEC_PER_SEC; - do_div(fps, us_per_frame); if (fps > max_rate) { ret = -ENOMEM; goto reset_rate; } if (is_frame_rate) - inst->frame_rate = (u32)fps; + inst->frame_rate = fps; else - inst->operating_rate = (u32)fps; + inst->operating_rate = fps; if ((s_parm->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && vb2_is_streaming(src_q)) || (s_parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && vb2_is_streaming(dst_q))) { From 4980721cb97d6c47700ab61a048ac8819cfeec87 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Thu, 25 Dec 2025 20:09:10 -0300 Subject: [PATCH 252/307] media: iris: use fallback size when S_FMT is called without width/height According to 4.5.1.5 of the M2M stateful decoder UAPI documentation, providing the width and the height to S_FMT is "required only if it cannot be parsed from the stream", otherwise they can be left as 0 and the S_FMT implementation is expected to return a valid placeholder resolution that would let REQBUFS succeed. iris was missing the fallback, so clients like rpi-ffmpeg wouldn't work. Fix by adding an explicit fallback to defaults. Fixes: b530b95de22c ("media: iris: implement s_fmt, g_fmt and try_fmt ioctls") Link: https://github.com/jc-kynesim/rpi-ffmpeg/issues/103 Reviewed-by: Dikshita Agarwal Signed-off-by: Val Packett Cc: stable@vger.kernel.org Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_vdec.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_vdec.c b/drivers/media/platform/qcom/iris/iris_vdec.c index bf7b9c621cb7..467d00044a2f 100644 --- a/drivers/media/platform/qcom/iris/iris_vdec.c +++ b/drivers/media/platform/qcom/iris/iris_vdec.c @@ -216,6 +216,14 @@ int iris_vdec_s_fmt(struct iris_inst *inst, struct v4l2_format *f) if (vb2_is_busy(q)) return -EBUSY; + /* Width and height are optional, so fall back to a valid placeholder + * resolution until the real one is decoded from the bitstream. + */ + if (f->fmt.pix_mp.width == 0 && f->fmt.pix_mp.height == 0) { + f->fmt.pix_mp.width = DEFAULT_WIDTH; + f->fmt.pix_mp.height = DEFAULT_HEIGHT; + } + iris_vdec_try_fmt(inst, f); switch (f->type) { From 8fc707d13df517222db12b465af4aa9df05c99e1 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Sun, 2 Nov 2025 09:10:19 +0530 Subject: [PATCH 253/307] media: iris: remove v4l2_m2m_ioctl_{de,en}coder_cmd API usage during STOP handling Currently v4l2_m2m_ioctl_{de,enc}coder_cmd is being invoked during STOP command handling. However, this is not required as the iris driver has its own drain and stop handling mechanism in place. Using the m2m command API in this context leads to incorrect behavior, where the LAST flag is prematurely attached to a capture buffer, when there are no buffers in m2m source queue. But, in this scenario even though the source buffers are returned to client, hardware might still need to process the pending capture buffers. Attaching LAST flag prematurely can result in the capture buffer being removed from the destination queue before the hardware has finished processing it, causing issues when the buffer is eventually returned by the hardware. To prevent this, remove the m2m API usage in stop handling. Fixes: d09100763bed ("media: iris: add support for drain sequence") Fixes: 75db90ae067d ("media: iris: Add support for drain sequence in encoder video device") Signed-off-by: Dikshita Agarwal Reviewed-by: Vikash Garodia Cc: stable@vger.kernel.org Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_vidc.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_vidc.c b/drivers/media/platform/qcom/iris/iris_vidc.c index dfd94f4a84a9..bd38d84c9cc7 100644 --- a/drivers/media/platform/qcom/iris/iris_vidc.c +++ b/drivers/media/platform/qcom/iris/iris_vidc.c @@ -573,9 +573,10 @@ static int iris_dec_cmd(struct file *filp, void *fh, mutex_lock(&inst->lock); - ret = v4l2_m2m_ioctl_decoder_cmd(filp, fh, dec); - if (ret) + if (dec->cmd != V4L2_DEC_CMD_STOP && dec->cmd != V4L2_DEC_CMD_START) { + ret = -EINVAL; goto unlock; + } if (inst->state == IRIS_INST_DEINIT) goto unlock; @@ -606,9 +607,10 @@ static int iris_enc_cmd(struct file *filp, void *fh, mutex_lock(&inst->lock); - ret = v4l2_m2m_ioctl_encoder_cmd(filp, fh, enc); - if (ret) + if (enc->cmd != V4L2_ENC_CMD_STOP && enc->cmd != V4L2_ENC_CMD_START) { + ret = -EINVAL; goto unlock; + } if (inst->state == IRIS_INST_DEINIT) goto unlock; From bbef55f414100853d5bcea56a41f8b171bac8fcb Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Thu, 18 Dec 2025 12:24:09 +0530 Subject: [PATCH 254/307] media: iris: Add missing platform data entries for SM8750 Two platform-data fields for SM8750 were missed: - get_vpu_buffer_size = iris_vpu33_buf_size Without this, the driver fails to allocate the required internal buffers, leading to basic decode/encode failures during session bring-up. - max_core_mbps = ((7680 * 4320) / 256) * 60 Without this capability exposed, capability checks are incomplete and v4l2-compliance for encoder fails. Fixes: a5925a2ce077 ("media: iris: add VPU33 specific encoding buffer calculation") Fixes: a6882431a138 ("media: iris: Add support for ENUM_FRAMESIZES/FRAMEINTERVALS for encoder") Cc: stable@vger.kernel.org Signed-off-by: Dikshita Agarwal Reviewed-by: Vikash Garodia Reviewed-by: Konrad Dybcio Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_platform_gen2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index b2d8559dd2b8..c932dc026616 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -1056,6 +1056,7 @@ const struct iris_platform_data sm8750_data = { .get_instance = iris_hfi_gen2_get_instance, .init_hfi_command_ops = iris_hfi_gen2_command_ops_init, .init_hfi_response_ops = iris_hfi_gen2_response_ops_init, + .get_vpu_buffer_size = iris_vpu33_buf_size, .vpu_ops = &iris_vpu35_ops, .set_preset_registers = iris_set_sm8550_preset_registers, .icc_tbl = sm8550_icc_table, @@ -1088,6 +1089,7 @@ const struct iris_platform_data sm8750_data = { .num_vpp_pipe = 4, .max_session_count = 16, .max_core_mbpf = NUM_MBS_8K * 2, + .max_core_mbps = ((7680 * 4320) / 256) * 60, .dec_input_config_params_default = sm8550_vdec_input_config_params_default, .dec_input_config_params_default_size = From 2d0bbd982dfdd67da488a772f7a8a1bdca7642bf Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Mon, 29 Dec 2025 12:01:20 +0530 Subject: [PATCH 255/307] media: iris: Add buffer to list only after successful allocation Move `list_add_tail()` to after `dma_alloc_attrs()` succeeds when creating internal buffers. Previously, the buffer was enqueued in `buffers->list` before the DMA allocation. If the allocation failed, the function returned `-ENOMEM` while leaving a partially initialized buffer in the list, which could lead to inconsistent state and potential leaks. By adding the buffer to the list only after `dma_alloc_attrs()` succeeds, we ensure the list contains only valid, fully initialized buffers. Fixes: 73702f45db81 ("media: iris: allocate, initialize and queue internal buffers") Reviewed-by: Bryan O'Donoghue Signed-off-by: Dikshita Agarwal Reviewed-by: Vikash Garodia Cc: stable@vger.kernel.org Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_buffer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_buffer.c b/drivers/media/platform/qcom/iris/iris_buffer.c index b89b1ee06cce..f1f003a787bf 100644 --- a/drivers/media/platform/qcom/iris/iris_buffer.c +++ b/drivers/media/platform/qcom/iris/iris_buffer.c @@ -351,12 +351,15 @@ static int iris_create_internal_buffer(struct iris_inst *inst, buffer->index = index; buffer->buffer_size = buffers->size; buffer->dma_attrs = DMA_ATTR_WRITE_COMBINE | DMA_ATTR_NO_KERNEL_MAPPING; - list_add_tail(&buffer->list, &buffers->list); buffer->kvaddr = dma_alloc_attrs(core->dev, buffer->buffer_size, &buffer->device_addr, GFP_KERNEL, buffer->dma_attrs); - if (!buffer->kvaddr) + if (!buffer->kvaddr) { + kfree(buffer); return -ENOMEM; + } + + list_add_tail(&buffer->list, &buffers->list); return 0; } From 811dbc546f47559dc9d2098c612acfd47e32479e Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Mon, 29 Dec 2025 12:01:21 +0530 Subject: [PATCH 256/307] media: iris: Skip resolution set on first IPSC The resolution property is not supposed to be set during reconfig. Existing iris_drc_pending(inst) check is insufficient, as it doesn't cover the first port setting change. Extend the conditional check to also skip resolution setting when the instance is in IRIS_INST_SUB_FIRST_IPSC. Fixes: caf205548769 ("media: iris: Avoid updating frame size to firmware during reconfig") Reviewed-by: Bryan O'Donoghue Signed-off-by: Dikshita Agarwal Reviewed-by: Vikash Garodia Cc: stable@vger.kernel.org Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c index b6261d186d21..1c107daca9e8 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c @@ -733,7 +733,7 @@ static int iris_hfi_gen1_set_resolution(struct iris_inst *inst, u32 plane) struct hfi_framesize fs; int ret; - if (!iris_drc_pending(inst)) { + if (!iris_drc_pending(inst) && !(inst->sub_state & IRIS_INST_SUB_FIRST_IPSC)) { fs.buffer_type = HFI_BUFFER_INPUT; fs.width = inst->fmt_src->fmt.pix_mp.width; fs.height = inst->fmt_src->fmt.pix_mp.height; From 1dabf00ee206eceb0f08a1fe5d1ce635f9064338 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Mon, 29 Dec 2025 12:01:22 +0530 Subject: [PATCH 257/307] media: iris: gen1: Destroy internal buffers after FW releases After the firmware releases internal buffers, the driver was not destroying them. This left stale allocations that were no longer used, especially across resolution changes where new buffers are allocated per the updated requirements. As a result, memory was wasted until session close. Destroy internal buffers once the release response is received from the firmware. Fixes: 73702f45db81 ("media: iris: allocate, initialize and queue internal buffers") Reviewed-by: Bryan O'Donoghue Signed-off-by: Dikshita Agarwal Reviewed-by: Vikash Garodia Cc: stable@vger.kernel.org Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c index 1c107daca9e8..11815f6f5bac 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c @@ -441,6 +441,8 @@ static int iris_hfi_gen1_session_unset_buffers(struct iris_inst *inst, struct ir goto exit; ret = iris_wait_for_session_response(inst, false); + if (!ret) + ret = iris_destroy_internal_buffer(inst, buf); exit: kfree(pkt); From 9aa8d63d09cfc44d879427cc5ba308012ca4ab8e Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Mon, 29 Dec 2025 12:01:24 +0530 Subject: [PATCH 258/307] media: iris: gen2: Add sanity check for session stop In iris_kill_session, inst->state is set to IRIS_INST_ERROR and session_close is executed, which will kfree(inst_hfi_gen2->packet). If stop_streaming is called afterward, it will cause a crash. Add a NULL check for inst_hfi_gen2->packet before sendling STOP packet to firmware to fix that. Fixes: 11712ce70f8e ("media: iris: implement vb2 streaming ops") Signed-off-by: Dikshita Agarwal Reviewed-by: Vikash Garodia Cc: stable@vger.kernel.org Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index b492d9dfddd3..c75c8b9398a2 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -1036,6 +1036,9 @@ static int iris_hfi_gen2_session_stop(struct iris_inst *inst, u32 plane) struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); int ret = 0; + if (!inst_hfi_gen2->packet) + return -EINVAL; + reinit_completion(&inst->completion); iris_hfi_gen2_packet_session_command(inst, From 2c73cfd0cfc44ffe331ccb81f6ac45fc399d9ddb Mon Sep 17 00:00:00 2001 From: Vishnu Reddy Date: Mon, 29 Dec 2025 12:01:25 +0530 Subject: [PATCH 259/307] media: iris: Prevent output buffer queuing before stream-on completes During normal playback, stream-on for input is followed by output, and only after input stream-on does actual streaming begin. However, when gst-play performs a seek, both input and output streams are stopped, and on restart, output stream-on occurs first. At this point, firmware has not yet started streaming. Queuing output buffers before the firmware begins streaming causes it to process buffers in an invalid state, leading to an error response. These buffers are returned to the driver as errors, forcing the driver into an error state and stopping playback. Fix this by deferring output buffer queuing until stream-on completes. Input buffers can still be queued before stream-on as required. Fixes: 92e007ca5ab6 ("media: iris: Add V4L2 streaming support for encoder video device") Signed-off-by: Vishnu Reddy Signed-off-by: Dikshita Agarwal Reviewed-by: Vikash Garodia Cc: stable@vger.kernel.org Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_vb2.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_vb2.c b/drivers/media/platform/qcom/iris/iris_vb2.c index 139b821f7952..bf0b8400996e 100644 --- a/drivers/media/platform/qcom/iris/iris_vb2.c +++ b/drivers/media/platform/qcom/iris/iris_vb2.c @@ -193,10 +193,14 @@ int iris_vb2_start_streaming(struct vb2_queue *q, unsigned int count) buf_type = iris_v4l2_type_to_driver(q->type); if (inst->domain == DECODER) { - if (inst->state == IRIS_INST_STREAMING) + if (buf_type == BUF_INPUT) + ret = iris_queue_deferred_buffers(inst, BUF_INPUT); + + if (!ret && inst->state == IRIS_INST_STREAMING) { ret = iris_queue_internal_deferred_buffers(inst, BUF_DPB); - if (!ret) - ret = iris_queue_deferred_buffers(inst, buf_type); + if (!ret) + ret = iris_queue_deferred_buffers(inst, BUF_OUTPUT); + } } else { if (inst->state == IRIS_INST_STREAMING) { ret = iris_queue_deferred_buffers(inst, BUF_INPUT); From 3f9b5c1ccd4484086b5ad51ed05c5d94ecaab7d3 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 23 Dec 2025 10:02:57 +0000 Subject: [PATCH 260/307] media: iris: Document difference in size during allocation As we get ready for kzalloc checking for invalid sizes, let's add documentation for the cases where the size is different but valid. This patch fixes this cocci warning: ./platform/qcom/iris/iris_hfi_gen2_command.c:1215:9-25: WARNING: casting value returned by memory allocation function to (struct iris_inst *) is useless. Signed-off-by: Ricardo Ribalda Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index c75c8b9398a2..df31f452b69b 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -1296,5 +1296,10 @@ void iris_hfi_gen2_command_ops_init(struct iris_core *core) struct iris_inst *iris_hfi_gen2_get_instance(void) { - return (struct iris_inst *)kzalloc(sizeof(struct iris_inst_hfi_gen2), GFP_KERNEL); + struct iris_inst_hfi_gen2 *out; + + /* The allocation is intentionally larger than struct iris_inst. */ + out = kzalloc(sizeof(*out), GFP_KERNEL); + + return &out->inst; } From 9adfb1ba701494aea216ee5c45ed9a1929398102 Mon Sep 17 00:00:00 2001 From: Vishnu Reddy Date: Mon, 10 Nov 2025 12:54:29 +0530 Subject: [PATCH 261/307] media: iris: Change psc properties message to debug level The message for attempting to set already configured input or output parameter subscribe change properties is not an error condition. Move it from error to debug level and make it more descriptive by indicating which specific input or output psc was already set. Signed-off-by: Vishnu Reddy Reviewed-by: Vikash Garodia Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index df31f452b69b..5db00f8a5e06 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -813,7 +813,7 @@ static int iris_hfi_gen2_subscribe_change_param(struct iris_inst *inst, u32 plan if ((V4L2_TYPE_IS_OUTPUT(plane) && inst_hfi_gen2->ipsc_properties_set) || (V4L2_TYPE_IS_CAPTURE(plane) && inst_hfi_gen2->opsc_properties_set)) { - dev_err(core->dev, "invalid plane\n"); + dev_dbg(core->dev, "%cPSC already set\n", V4L2_TYPE_IS_OUTPUT(plane) ? 'I' : 'O'); return 0; } From d9967fa37ca97aec5aea98d627107147cc02edbc Mon Sep 17 00:00:00 2001 From: Wangao Wang Date: Fri, 14 Nov 2025 11:08:58 +0800 Subject: [PATCH 262/307] media: qcom: iris: Improve format alignment for encoder Add members enc_raw_width, enc_raw_height to the struct iris_inst to support codec alignment requirements. HFI_PROP_RAW_RESOLUTION needs to be set to the actual YUV resolution. Reviewed-by: Bryan O'Donoghue Reviewed-by: Dikshita Agarwal Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Wangao Wang Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c | 3 +-- drivers/media/platform/qcom/iris/iris_instance.h | 4 ++++ drivers/media/platform/qcom/iris/iris_venc.c | 6 ++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index 5db00f8a5e06..91eca524d021 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -170,8 +170,7 @@ static int iris_hfi_gen2_session_set_property(struct iris_inst *inst, u32 packet static int iris_hfi_gen2_set_raw_resolution(struct iris_inst *inst, u32 plane) { - u32 resolution = inst->fmt_src->fmt.pix_mp.width << 16 | - inst->fmt_src->fmt.pix_mp.height; + u32 resolution = inst->enc_raw_width << 16 | inst->enc_raw_height; u32 port = iris_hfi_gen2_get_port(inst, plane); return iris_hfi_gen2_session_set_property(inst, diff --git a/drivers/media/platform/qcom/iris/iris_instance.h b/drivers/media/platform/qcom/iris/iris_instance.h index 7e4f5f1dd892..7f96962d7086 100644 --- a/drivers/media/platform/qcom/iris/iris_instance.h +++ b/drivers/media/platform/qcom/iris/iris_instance.h @@ -70,6 +70,8 @@ struct iris_fmt { * @frame_rate: frame rate of current instance * @operating_rate: operating rate of current instance * @hfi_rc_type: rate control type + * @enc_raw_width: source image width for encoder instance + * @enc_raw_height: source image height for encoder instance */ struct iris_inst { @@ -108,6 +110,8 @@ struct iris_inst { u32 frame_rate; u32 operating_rate; u32 hfi_rc_type; + u32 enc_raw_width; + u32 enc_raw_height; }; #endif diff --git a/drivers/media/platform/qcom/iris/iris_venc.c b/drivers/media/platform/qcom/iris/iris_venc.c index 0ed5018f9fe3..6a94c08652b7 100644 --- a/drivers/media/platform/qcom/iris/iris_venc.c +++ b/drivers/media/platform/qcom/iris/iris_venc.c @@ -68,6 +68,9 @@ int iris_venc_inst_init(struct iris_inst *inst) inst->operating_rate = DEFAULT_FPS; inst->frame_rate = DEFAULT_FPS; + inst->enc_raw_width = DEFAULT_WIDTH; + inst->enc_raw_height = DEFAULT_HEIGHT; + memcpy(&inst->fw_caps[0], &core->inst_fw_caps_enc[0], INST_FW_CAP_MAX * sizeof(struct platform_inst_fw_cap)); @@ -287,6 +290,9 @@ static int iris_venc_s_fmt_input(struct iris_inst *inst, struct v4l2_format *f) inst->buffers[BUF_INPUT].min_count = iris_vpu_buf_count(inst, BUF_INPUT); inst->buffers[BUF_INPUT].size = fmt->fmt.pix_mp.plane_fmt[0].sizeimage; + inst->enc_raw_width = f->fmt.pix_mp.width; + inst->enc_raw_height = f->fmt.pix_mp.height; + if (f->fmt.pix_mp.width != inst->crop.width || f->fmt.pix_mp.height != inst->crop.height) { inst->crop.top = 0; From 0708f305d6fabf9aca1d1c96d5a5b003df2d8bc9 Mon Sep 17 00:00:00 2001 From: Wangao Wang Date: Fri, 14 Nov 2025 11:08:59 +0800 Subject: [PATCH 263/307] media: qcom: iris: Improve crop_offset handling for encoder The setting of HFI_PROP_CROP_OFFSETS for the OUTPUT port is correct, but on the CAPTURE port it is used to inform the firmware about the region of interest, so crop_offset needs to be handled accordingly. Reviewed-by: Dikshita Agarwal Signed-off-by: Wangao Wang Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../qcom/iris/iris_hfi_gen2_command.c | 20 ++++++++++++++----- drivers/media/platform/qcom/iris/iris_venc.c | 4 ++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index 91eca524d021..c73019efaea6 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -217,7 +217,7 @@ static int iris_hfi_gen2_set_crop_offsets(struct iris_inst *inst, u32 plane) u32 port = iris_hfi_gen2_get_port(inst, plane); u32 bottom_offset, right_offset; u32 left_offset, top_offset; - u32 payload[2]; + u32 payload[2], codec_align; if (inst->domain == DECODER) { if (V4L2_TYPE_IS_OUTPUT(plane)) { @@ -232,10 +232,20 @@ static int iris_hfi_gen2_set_crop_offsets(struct iris_inst *inst, u32 plane) top_offset = inst->compose.top; } } else { - bottom_offset = (inst->fmt_src->fmt.pix_mp.height - inst->crop.height); - right_offset = (inst->fmt_src->fmt.pix_mp.width - inst->crop.width); - left_offset = inst->crop.left; - top_offset = inst->crop.top; + codec_align = inst->codec == V4L2_PIX_FMT_HEVC ? 32 : 16; + if (V4L2_TYPE_IS_OUTPUT(plane)) { + bottom_offset = (inst->enc_raw_height - inst->crop.height); + right_offset = (inst->enc_raw_width - inst->crop.width); + left_offset = inst->crop.left; + top_offset = inst->crop.top; + } else { + bottom_offset = (ALIGN(inst->fmt_dst->fmt.pix_mp.height, codec_align) - + inst->fmt_dst->fmt.pix_mp.height); + right_offset = (ALIGN(inst->fmt_dst->fmt.pix_mp.width, codec_align) - + inst->fmt_dst->fmt.pix_mp.width); + left_offset = 0; + top_offset = 0; + } } payload[0] = FIELD_PREP(GENMASK(31, 16), left_offset) | top_offset; diff --git a/drivers/media/platform/qcom/iris/iris_venc.c b/drivers/media/platform/qcom/iris/iris_venc.c index 6a94c08652b7..6628e9a3ab01 100644 --- a/drivers/media/platform/qcom/iris/iris_venc.c +++ b/drivers/media/platform/qcom/iris/iris_venc.c @@ -62,8 +62,8 @@ int iris_venc_inst_init(struct iris_inst *inst) inst->crop.left = 0; inst->crop.top = 0; - inst->crop.width = f->fmt.pix_mp.width; - inst->crop.height = f->fmt.pix_mp.height; + inst->crop.width = DEFAULT_WIDTH; + inst->crop.height = DEFAULT_HEIGHT; inst->operating_rate = DEFAULT_FPS; inst->frame_rate = DEFAULT_FPS; From 121d6d7a35110a5ce6892b1e4695cac9c0f3094b Mon Sep 17 00:00:00 2001 From: Wangao Wang Date: Fri, 14 Nov 2025 11:09:00 +0800 Subject: [PATCH 264/307] media: qcom: iris: Add scale support for encoder Add members enc_scale_width, enc_scale_height to the struct iris_inst to support scale requirements. Add output width and height settings in iris_venc_s_fmt_output to enable scaling functionality. Add VPSS buffer to platform data, which the scale function requires. Reviewed-by: Bryan O'Donoghue Reviewed-by: Dikshita Agarwal Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Wangao Wang Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../qcom/iris/iris_hfi_gen2_command.c | 12 +++++----- .../qcom/iris/iris_hfi_gen2_response.c | 2 ++ .../media/platform/qcom/iris/iris_instance.h | 4 ++++ .../platform/qcom/iris/iris_platform_gen2.c | 12 ++++++++++ drivers/media/platform/qcom/iris/iris_venc.c | 23 ++++++++++++++++++- .../platform/qcom/iris/iris_vpu_buffer.c | 9 ++++---- 6 files changed, 51 insertions(+), 11 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index c73019efaea6..16df96dbb594 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -196,8 +196,8 @@ static int iris_hfi_gen2_set_bitstream_resolution(struct iris_inst *inst, u32 pl payload_type = HFI_PAYLOAD_U32; } else { codec_align = inst->codec == V4L2_PIX_FMT_HEVC ? 32 : 16; - resolution = ALIGN(inst->fmt_dst->fmt.pix_mp.width, codec_align) << 16 | - ALIGN(inst->fmt_dst->fmt.pix_mp.height, codec_align); + resolution = ALIGN(inst->enc_scale_width, codec_align) << 16 | + ALIGN(inst->enc_scale_height, codec_align); inst_hfi_gen2->dst_subcr_params.bitstream_resolution = resolution; payload_type = HFI_PAYLOAD_32_PACKED; } @@ -239,10 +239,10 @@ static int iris_hfi_gen2_set_crop_offsets(struct iris_inst *inst, u32 plane) left_offset = inst->crop.left; top_offset = inst->crop.top; } else { - bottom_offset = (ALIGN(inst->fmt_dst->fmt.pix_mp.height, codec_align) - - inst->fmt_dst->fmt.pix_mp.height); - right_offset = (ALIGN(inst->fmt_dst->fmt.pix_mp.width, codec_align) - - inst->fmt_dst->fmt.pix_mp.width); + bottom_offset = (ALIGN(inst->enc_scale_height, codec_align) - + inst->enc_scale_height); + right_offset = (ALIGN(inst->enc_scale_width, codec_align) - + inst->enc_scale_width); left_offset = 0; top_offset = 0; } diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c index eb3373f0ad4a..8e19f61bbbf9 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c @@ -56,6 +56,8 @@ static u32 iris_hfi_gen2_buf_type_to_driver(struct iris_inst *inst, return BUF_PERSIST; case HFI_BUFFER_PARTIAL_DATA: return BUF_PARTIAL; + case HFI_BUFFER_VPSS: + return BUF_VPSS; default: return 0; } diff --git a/drivers/media/platform/qcom/iris/iris_instance.h b/drivers/media/platform/qcom/iris/iris_instance.h index 7f96962d7086..16965150f427 100644 --- a/drivers/media/platform/qcom/iris/iris_instance.h +++ b/drivers/media/platform/qcom/iris/iris_instance.h @@ -72,6 +72,8 @@ struct iris_fmt { * @hfi_rc_type: rate control type * @enc_raw_width: source image width for encoder instance * @enc_raw_height: source image height for encoder instance + * @enc_scale_width: scale width for encoder instance + * @enc_scale_height: scale height for encoder instance */ struct iris_inst { @@ -112,6 +114,8 @@ struct iris_inst { u32 hfi_rc_type; u32 enc_raw_width; u32 enc_raw_height; + u32 enc_scale_width; + u32 enc_scale_height; }; #endif diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index c932dc026616..39ab973aebce 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -851,6 +851,10 @@ static const u32 sm8550_dec_op_int_buf_tbl[] = { BUF_DPB, }; +static const u32 sm8550_enc_ip_int_buf_tbl[] = { + BUF_VPSS, +}; + static const u32 sm8550_enc_op_int_buf_tbl[] = { BUF_BIN, BUF_COMV, @@ -947,6 +951,8 @@ const struct iris_platform_data sm8550_data = { .dec_op_int_buf_tbl = sm8550_dec_op_int_buf_tbl, .dec_op_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_op_int_buf_tbl), + .enc_ip_int_buf_tbl = sm8550_enc_ip_int_buf_tbl, + .enc_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_enc_ip_int_buf_tbl), .enc_op_int_buf_tbl = sm8550_enc_op_int_buf_tbl, .enc_op_int_buf_tbl_size = ARRAY_SIZE(sm8550_enc_op_int_buf_tbl), }; @@ -1048,6 +1054,8 @@ const struct iris_platform_data sm8650_data = { .dec_op_int_buf_tbl = sm8550_dec_op_int_buf_tbl, .dec_op_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_op_int_buf_tbl), + .enc_ip_int_buf_tbl = sm8550_enc_ip_int_buf_tbl, + .enc_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_enc_ip_int_buf_tbl), .enc_op_int_buf_tbl = sm8550_enc_op_int_buf_tbl, .enc_op_int_buf_tbl_size = ARRAY_SIZE(sm8550_enc_op_int_buf_tbl), }; @@ -1140,6 +1148,8 @@ const struct iris_platform_data sm8750_data = { .dec_op_int_buf_tbl = sm8550_dec_op_int_buf_tbl, .dec_op_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_op_int_buf_tbl), + .enc_ip_int_buf_tbl = sm8550_enc_ip_int_buf_tbl, + .enc_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_enc_ip_int_buf_tbl), .enc_op_int_buf_tbl = sm8550_enc_op_int_buf_tbl, .enc_op_int_buf_tbl_size = ARRAY_SIZE(sm8550_enc_op_int_buf_tbl), }; @@ -1236,6 +1246,8 @@ const struct iris_platform_data qcs8300_data = { .dec_op_int_buf_tbl = sm8550_dec_op_int_buf_tbl, .dec_op_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_op_int_buf_tbl), + .enc_ip_int_buf_tbl = sm8550_enc_ip_int_buf_tbl, + .enc_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_enc_ip_int_buf_tbl), .enc_op_int_buf_tbl = sm8550_enc_op_int_buf_tbl, .enc_op_int_buf_tbl_size = ARRAY_SIZE(sm8550_enc_op_int_buf_tbl), }; diff --git a/drivers/media/platform/qcom/iris/iris_venc.c b/drivers/media/platform/qcom/iris/iris_venc.c index 6628e9a3ab01..6461d9c9d598 100644 --- a/drivers/media/platform/qcom/iris/iris_venc.c +++ b/drivers/media/platform/qcom/iris/iris_venc.c @@ -70,6 +70,8 @@ int iris_venc_inst_init(struct iris_inst *inst) inst->enc_raw_width = DEFAULT_WIDTH; inst->enc_raw_height = DEFAULT_HEIGHT; + inst->enc_scale_width = DEFAULT_WIDTH; + inst->enc_scale_height = DEFAULT_HEIGHT; memcpy(&inst->fw_caps[0], &core->inst_fw_caps_enc[0], INST_FW_CAP_MAX * sizeof(struct platform_inst_fw_cap)); @@ -226,15 +228,32 @@ int iris_venc_try_fmt(struct iris_inst *inst, struct v4l2_format *f) static int iris_venc_s_fmt_output(struct iris_inst *inst, struct v4l2_format *f) { + const struct iris_fmt *venc_fmt; struct v4l2_format *fmt; + u32 codec_align; iris_venc_try_fmt(inst, f); - if (!(find_format(inst, f->fmt.pix_mp.pixelformat, f->type))) + venc_fmt = find_format(inst, f->fmt.pix_mp.pixelformat, f->type); + if (!venc_fmt) return -EINVAL; + codec_align = venc_fmt->pixfmt == V4L2_PIX_FMT_HEVC ? 32 : 16; + fmt = inst->fmt_dst; fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + /* + * If output format size != input format size, + * it is considered a scaling case, + * and the scaled size needs to be saved. + */ + if (f->fmt.pix_mp.width != inst->fmt_src->fmt.pix_mp.width || + f->fmt.pix_mp.height != inst->fmt_src->fmt.pix_mp.height) { + inst->enc_scale_width = f->fmt.pix_mp.width; + inst->enc_scale_height = f->fmt.pix_mp.height; + fmt->fmt.pix_mp.width = ALIGN(f->fmt.pix_mp.width, codec_align); + fmt->fmt.pix_mp.height = ALIGN(f->fmt.pix_mp.height, codec_align); + } fmt->fmt.pix_mp.num_planes = 1; fmt->fmt.pix_mp.plane_fmt[0].bytesperline = 0; fmt->fmt.pix_mp.plane_fmt[0].sizeimage = iris_get_buffer_size(inst, BUF_OUTPUT); @@ -292,6 +311,8 @@ static int iris_venc_s_fmt_input(struct iris_inst *inst, struct v4l2_format *f) inst->enc_raw_width = f->fmt.pix_mp.width; inst->enc_raw_height = f->fmt.pix_mp.height; + inst->enc_scale_width = f->fmt.pix_mp.width; + inst->enc_scale_height = f->fmt.pix_mp.height; if (f->fmt.pix_mp.width != inst->crop.width || f->fmt.pix_mp.height != inst->crop.height) { diff --git a/drivers/media/platform/qcom/iris/iris_vpu_buffer.c b/drivers/media/platform/qcom/iris/iris_vpu_buffer.c index f4985790bae4..e110b139f003 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_buffer.c +++ b/drivers/media/platform/qcom/iris/iris_vpu_buffer.c @@ -1406,10 +1406,11 @@ static u32 iris_vpu_enc_arp_size(struct iris_inst *inst) inline bool is_scaling_enabled(struct iris_inst *inst) { - return inst->crop.left != inst->compose.left || - inst->crop.top != inst->compose.top || - inst->crop.width != inst->compose.width || - inst->crop.height != inst->compose.height; + struct v4l2_pix_format_mplane *dst_fmt = &inst->fmt_dst->fmt.pix_mp; + struct v4l2_pix_format_mplane *src_fmt = &inst->fmt_src->fmt.pix_mp; + + return dst_fmt->width != src_fmt->width || + dst_fmt->height != src_fmt->height; } static inline From 49a940f37de337dbbede5ac204cc9d7ebe22c59a Mon Sep 17 00:00:00 2001 From: Wangao Wang Date: Fri, 14 Nov 2025 11:09:01 +0800 Subject: [PATCH 265/307] media: qcom: iris: Add rotation support for encoder Add rotation control for encoder, enabling V4L2_CID_ROTATE and handling 90/180/270 degree rotation. Reviewed-by: Dikshita Agarwal Co-developed-by: Neil Armstrong Signed-off-by: Neil Armstrong Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Wangao Wang Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_ctrls.c | 34 +++++++++++++ drivers/media/platform/qcom/iris/iris_ctrls.h | 1 + .../qcom/iris/iris_hfi_gen2_command.c | 41 +++++++++++---- .../qcom/iris/iris_hfi_gen2_defines.h | 9 ++++ .../platform/qcom/iris/iris_platform_common.h | 1 + .../platform/qcom/iris/iris_platform_gen2.c | 10 ++++ drivers/media/platform/qcom/iris/iris_utils.c | 6 +++ drivers/media/platform/qcom/iris/iris_utils.h | 1 + .../platform/qcom/iris/iris_vpu_buffer.c | 51 +++++++++++-------- 9 files changed, 123 insertions(+), 31 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.c b/drivers/media/platform/qcom/iris/iris_ctrls.c index 87f929442ec6..faa0f75abfea 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.c +++ b/drivers/media/platform/qcom/iris/iris_ctrls.c @@ -102,6 +102,8 @@ static enum platform_inst_fw_cap_type iris_get_cap_id(u32 id) return PROFILE_AV1; case V4L2_CID_MPEG_VIDEO_AV1_LEVEL: return LEVEL_AV1; + case V4L2_CID_ROTATE: + return ROTATION; default: return INST_FW_CAP_MAX; } @@ -193,6 +195,8 @@ static u32 iris_get_v4l2_id(enum platform_inst_fw_cap_type cap_id) return V4L2_CID_MPEG_VIDEO_AV1_PROFILE; case LEVEL_AV1: return V4L2_CID_MPEG_VIDEO_AV1_LEVEL; + case ROTATION: + return V4L2_CID_ROTATE; default: return 0; } @@ -901,6 +905,36 @@ int iris_set_qp_range(struct iris_inst *inst, enum platform_inst_fw_cap_type cap &range, sizeof(range)); } +int iris_set_rotation(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id) +{ + const struct iris_hfi_command_ops *hfi_ops = inst->core->hfi_ops; + u32 hfi_id = inst->fw_caps[cap_id].hfi_id; + u32 hfi_val; + + switch (inst->fw_caps[cap_id].value) { + case 0: + hfi_val = HFI_ROTATION_NONE; + return 0; + case 90: + hfi_val = HFI_ROTATION_90; + break; + case 180: + hfi_val = HFI_ROTATION_180; + break; + case 270: + hfi_val = HFI_ROTATION_270; + break; + default: + return -EINVAL; + } + + return hfi_ops->session_set_property(inst, hfi_id, + HFI_HOST_FLAGS_NONE, + iris_get_port_info(inst, cap_id), + HFI_PAYLOAD_U32, + &hfi_val, sizeof(u32)); +} + int iris_set_properties(struct iris_inst *inst, u32 plane) { const struct iris_hfi_command_ops *hfi_ops = inst->core->hfi_ops; diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.h b/drivers/media/platform/qcom/iris/iris_ctrls.h index 30af333cc494..3ea0a00c7587 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.h +++ b/drivers/media/platform/qcom/iris/iris_ctrls.h @@ -32,6 +32,7 @@ int iris_set_min_qp(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_i int iris_set_max_qp(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); int iris_set_frame_qp(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); int iris_set_qp_range(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); +int iris_set_rotation(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); int iris_set_properties(struct iris_inst *inst, u32 plane); #endif diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index 16df96dbb594..715ec9575b90 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -182,22 +182,36 @@ static int iris_hfi_gen2_set_raw_resolution(struct iris_inst *inst, u32 plane) sizeof(u32)); } +static inline u32 iris_hfi_get_aligned_resolution(struct iris_inst *inst, u32 width, u32 height) +{ + u32 codec_align = inst->codec == V4L2_PIX_FMT_HEVC ? 32 : 16; + + return (ALIGN(width, codec_align) << 16 | ALIGN(height, codec_align)); +} + static int iris_hfi_gen2_set_bitstream_resolution(struct iris_inst *inst, u32 plane) { struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); u32 port = iris_hfi_gen2_get_port(inst, plane); enum hfi_packet_payload_info payload_type; - u32 resolution, codec_align; + u32 width, height; + u32 resolution; if (inst->domain == DECODER) { - resolution = inst->fmt_src->fmt.pix_mp.width << 16 | - inst->fmt_src->fmt.pix_mp.height; + width = inst->fmt_src->fmt.pix_mp.width; + height = inst->fmt_src->fmt.pix_mp.height; + resolution = iris_hfi_get_aligned_resolution(inst, width, height); inst_hfi_gen2->src_subcr_params.bitstream_resolution = resolution; payload_type = HFI_PAYLOAD_U32; } else { - codec_align = inst->codec == V4L2_PIX_FMT_HEVC ? 32 : 16; - resolution = ALIGN(inst->enc_scale_width, codec_align) << 16 | - ALIGN(inst->enc_scale_height, codec_align); + if (is_rotation_90_or_270(inst)) { + width = inst->enc_scale_height; + height = inst->enc_scale_width; + } else { + width = inst->enc_scale_width; + height = inst->enc_scale_height; + } + resolution = iris_hfi_get_aligned_resolution(inst, width, height); inst_hfi_gen2->dst_subcr_params.bitstream_resolution = resolution; payload_type = HFI_PAYLOAD_32_PACKED; } @@ -239,10 +253,17 @@ static int iris_hfi_gen2_set_crop_offsets(struct iris_inst *inst, u32 plane) left_offset = inst->crop.left; top_offset = inst->crop.top; } else { - bottom_offset = (ALIGN(inst->enc_scale_height, codec_align) - - inst->enc_scale_height); - right_offset = (ALIGN(inst->enc_scale_width, codec_align) - - inst->enc_scale_width); + if (is_rotation_90_or_270(inst)) { + bottom_offset = (ALIGN(inst->enc_scale_width, codec_align) - + inst->enc_scale_width); + right_offset = (ALIGN(inst->enc_scale_height, codec_align) - + inst->enc_scale_height); + } else { + bottom_offset = (ALIGN(inst->enc_scale_height, codec_align) - + inst->enc_scale_height); + right_offset = (ALIGN(inst->enc_scale_width, codec_align) - + inst->enc_scale_width); + } left_offset = 0; top_offset = 0; } diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h index 5f1f1a7eb91f..c3508514014c 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h @@ -83,6 +83,15 @@ enum hfi_seq_header_mode { }; #define HFI_PROP_SEQ_HEADER_MODE 0x03000149 + +enum hfi_rotation { + HFI_ROTATION_NONE = 0x00000000, + HFI_ROTATION_90 = 0x00000001, + HFI_ROTATION_180 = 0x00000002, + HFI_ROTATION_270 = 0x00000003, +}; + +#define HFI_PROP_ROTATION 0x0300014b #define HFI_PROP_SIGNAL_COLOR_INFO 0x03000155 #define HFI_PROP_PICTURE_TYPE 0x03000162 #define HFI_PROP_DEC_DEFAULT_HEADER 0x03000168 diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index 14a2f4dbe38a..e8fe8b9bd21e 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -150,6 +150,7 @@ enum platform_inst_fw_cap_type { P_FRAME_QP_HEVC, B_FRAME_QP_H264, B_FRAME_QP_HEVC, + ROTATION, INST_FW_CAP_MAX, }; diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index 39ab973aebce..3412a77a2c08 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -689,6 +689,16 @@ static const struct platform_inst_fw_cap inst_fw_cap_sm8550_enc[] = { .flags = CAP_FLAG_OUTPUT_PORT, .set = iris_set_u32, }, + { + .cap_id = ROTATION, + .min = 0, + .max = 270, + .step_or_mask = 90, + .value = 0, + .hfi_id = HFI_PROP_ROTATION, + .flags = CAP_FLAG_OUTPUT_PORT, + .set = iris_set_rotation, + }, }; static struct platform_inst_caps platform_inst_cap_sm8550 = { diff --git a/drivers/media/platform/qcom/iris/iris_utils.c b/drivers/media/platform/qcom/iris/iris_utils.c index e2f1131de431..cfc5b576ec56 100644 --- a/drivers/media/platform/qcom/iris/iris_utils.c +++ b/drivers/media/platform/qcom/iris/iris_utils.c @@ -125,3 +125,9 @@ int iris_check_core_mbps(struct iris_inst *inst) return 0; } + +bool is_rotation_90_or_270(struct iris_inst *inst) +{ + return inst->fw_caps[ROTATION].value == 90 || + inst->fw_caps[ROTATION].value == 270; +} diff --git a/drivers/media/platform/qcom/iris/iris_utils.h b/drivers/media/platform/qcom/iris/iris_utils.h index 75740181122f..b5705d156431 100644 --- a/drivers/media/platform/qcom/iris/iris_utils.h +++ b/drivers/media/platform/qcom/iris/iris_utils.h @@ -51,5 +51,6 @@ void iris_helper_buffers_done(struct iris_inst *inst, unsigned int type, int iris_wait_for_session_response(struct iris_inst *inst, bool is_flush); int iris_check_core_mbpf(struct iris_inst *inst); int iris_check_core_mbps(struct iris_inst *inst); +bool is_rotation_90_or_270(struct iris_inst *inst); #endif diff --git a/drivers/media/platform/qcom/iris/iris_vpu_buffer.c b/drivers/media/platform/qcom/iris/iris_vpu_buffer.c index e110b139f003..aced49495bb8 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_buffer.c +++ b/drivers/media/platform/qcom/iris/iris_vpu_buffer.c @@ -822,6 +822,22 @@ static u32 iris_vpu_dec_scratch1_size(struct iris_inst *inst) iris_vpu_dec_line_size(inst); } +static inline u32 iris_vpu_enc_get_bitstream_width(struct iris_inst *inst) +{ + if (is_rotation_90_or_270(inst)) + return inst->fmt_dst->fmt.pix_mp.height; + else + return inst->fmt_dst->fmt.pix_mp.width; +} + +static inline u32 iris_vpu_enc_get_bitstream_height(struct iris_inst *inst) +{ + if (is_rotation_90_or_270(inst)) + return inst->fmt_dst->fmt.pix_mp.width; + else + return inst->fmt_dst->fmt.pix_mp.height; +} + static inline u32 size_bin_bitstream_enc(u32 width, u32 height, u32 rc_type) { @@ -904,10 +920,9 @@ static inline u32 hfi_buffer_bin_enc(u32 width, u32 height, static u32 iris_vpu_enc_bin_size(struct iris_inst *inst) { u32 num_vpp_pipes = inst->core->iris_platform_data->num_vpp_pipe; + u32 height = iris_vpu_enc_get_bitstream_height(inst); + u32 width = iris_vpu_enc_get_bitstream_width(inst); u32 stage = inst->fw_caps[STAGE].value; - struct v4l2_format *f = inst->fmt_dst; - u32 height = f->fmt.pix_mp.height; - u32 width = f->fmt.pix_mp.width; u32 lcu_size; if (inst->codec == V4L2_PIX_FMT_HEVC) @@ -951,9 +966,8 @@ u32 hfi_buffer_comv_enc(u32 frame_width, u32 frame_height, u32 lcu_size, static u32 iris_vpu_enc_comv_size(struct iris_inst *inst) { - struct v4l2_format *f = inst->fmt_dst; - u32 height = f->fmt.pix_mp.height; - u32 width = f->fmt.pix_mp.width; + u32 height = iris_vpu_enc_get_bitstream_height(inst); + u32 width = iris_vpu_enc_get_bitstream_width(inst); u32 num_recon = 1; u32 lcu_size = 16; @@ -1233,9 +1247,8 @@ u32 hfi_buffer_non_comv_enc(u32 frame_width, u32 frame_height, static u32 iris_vpu_enc_non_comv_size(struct iris_inst *inst) { u32 num_vpp_pipes = inst->core->iris_platform_data->num_vpp_pipe; - struct v4l2_format *f = inst->fmt_dst; - u32 height = f->fmt.pix_mp.height; - u32 width = f->fmt.pix_mp.width; + u32 height = iris_vpu_enc_get_bitstream_height(inst); + u32 width = iris_vpu_enc_get_bitstream_width(inst); u32 lcu_size = 16; if (inst->codec == V4L2_PIX_FMT_HEVC) { @@ -1326,9 +1339,8 @@ u32 hfi_buffer_line_enc_vpu33(u32 frame_width, u32 frame_height, bool is_ten_bit static u32 iris_vpu_enc_line_size(struct iris_inst *inst) { u32 num_vpp_pipes = inst->core->iris_platform_data->num_vpp_pipe; - struct v4l2_format *f = inst->fmt_dst; - u32 height = f->fmt.pix_mp.height; - u32 width = f->fmt.pix_mp.width; + u32 height = iris_vpu_enc_get_bitstream_height(inst); + u32 width = iris_vpu_enc_get_bitstream_width(inst); u32 lcu_size = 16; if (inst->codec == V4L2_PIX_FMT_HEVC) { @@ -1344,9 +1356,8 @@ static u32 iris_vpu_enc_line_size(struct iris_inst *inst) static u32 iris_vpu33_enc_line_size(struct iris_inst *inst) { u32 num_vpp_pipes = inst->core->iris_platform_data->num_vpp_pipe; - struct v4l2_format *f = inst->fmt_dst; - u32 height = f->fmt.pix_mp.height; - u32 width = f->fmt.pix_mp.width; + u32 height = iris_vpu_enc_get_bitstream_height(inst); + u32 width = iris_vpu_enc_get_bitstream_width(inst); u32 lcu_size = 16; if (inst->codec == V4L2_PIX_FMT_HEVC) { @@ -1567,9 +1578,8 @@ static inline u32 hfi_buffer_scratch1_enc(u32 frame_width, u32 frame_height, static u32 iris_vpu_enc_scratch1_size(struct iris_inst *inst) { u32 num_vpp_pipes = inst->core->iris_platform_data->num_vpp_pipe; - struct v4l2_format *f = inst->fmt_dst; - u32 frame_height = f->fmt.pix_mp.height; - u32 frame_width = f->fmt.pix_mp.width; + u32 frame_height = iris_vpu_enc_get_bitstream_height(inst); + u32 frame_width = iris_vpu_enc_get_bitstream_width(inst); u32 num_ref = 1; u32 lcu_size; bool is_h265; @@ -1665,9 +1675,8 @@ static inline u32 hfi_buffer_scratch2_enc(u32 frame_width, u32 frame_height, static u32 iris_vpu_enc_scratch2_size(struct iris_inst *inst) { - struct v4l2_format *f = inst->fmt_dst; - u32 frame_width = f->fmt.pix_mp.width; - u32 frame_height = f->fmt.pix_mp.height; + u32 frame_height = iris_vpu_enc_get_bitstream_height(inst); + u32 frame_width = iris_vpu_enc_get_bitstream_width(inst); u32 num_ref = 1; return hfi_buffer_scratch2_enc(frame_width, frame_height, num_ref, From 874eca6d309530d9223fb33e1ad056861955f491 Mon Sep 17 00:00:00 2001 From: Wangao Wang Date: Fri, 14 Nov 2025 11:09:02 +0800 Subject: [PATCH 266/307] media: qcom: iris: Add flip support for encoder Add support for V4L2_CID_HFLIP and V4L2_CID_VFLIP controls in encoder. Reviewed-by: Dikshita Agarwal Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Wangao Wang Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_ctrls.c | 27 +++++++++++++++++++ drivers/media/platform/qcom/iris/iris_ctrls.h | 1 + .../qcom/iris/iris_hfi_gen2_defines.h | 8 ++++++ .../platform/qcom/iris/iris_platform_common.h | 2 ++ .../platform/qcom/iris/iris_platform_gen2.c | 22 +++++++++++++++ 5 files changed, 60 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.c b/drivers/media/platform/qcom/iris/iris_ctrls.c index faa0f75abfea..d7bd7f6dcc21 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.c +++ b/drivers/media/platform/qcom/iris/iris_ctrls.c @@ -104,6 +104,10 @@ static enum platform_inst_fw_cap_type iris_get_cap_id(u32 id) return LEVEL_AV1; case V4L2_CID_ROTATE: return ROTATION; + case V4L2_CID_HFLIP: + return HFLIP; + case V4L2_CID_VFLIP: + return VFLIP; default: return INST_FW_CAP_MAX; } @@ -197,6 +201,10 @@ static u32 iris_get_v4l2_id(enum platform_inst_fw_cap_type cap_id) return V4L2_CID_MPEG_VIDEO_AV1_LEVEL; case ROTATION: return V4L2_CID_ROTATE; + case HFLIP: + return V4L2_CID_HFLIP; + case VFLIP: + return V4L2_CID_VFLIP; default: return 0; } @@ -935,6 +943,25 @@ int iris_set_rotation(struct iris_inst *inst, enum platform_inst_fw_cap_type cap &hfi_val, sizeof(u32)); } +int iris_set_flip(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id) +{ + const struct iris_hfi_command_ops *hfi_ops = inst->core->hfi_ops; + u32 hfi_id = inst->fw_caps[cap_id].hfi_id; + u32 hfi_val = HFI_DISABLE_FLIP; + + if (inst->fw_caps[HFLIP].value) + hfi_val |= HFI_HORIZONTAL_FLIP; + + if (inst->fw_caps[VFLIP].value) + hfi_val |= HFI_VERTICAL_FLIP; + + return hfi_ops->session_set_property(inst, hfi_id, + HFI_HOST_FLAGS_NONE, + iris_get_port_info(inst, cap_id), + HFI_PAYLOAD_U32_ENUM, + &hfi_val, sizeof(u32)); +} + int iris_set_properties(struct iris_inst *inst, u32 plane) { const struct iris_hfi_command_ops *hfi_ops = inst->core->hfi_ops; diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.h b/drivers/media/platform/qcom/iris/iris_ctrls.h index 3ea0a00c7587..355a592049f3 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.h +++ b/drivers/media/platform/qcom/iris/iris_ctrls.h @@ -33,6 +33,7 @@ int iris_set_max_qp(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_i int iris_set_frame_qp(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); int iris_set_qp_range(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); int iris_set_rotation(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); +int iris_set_flip(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); int iris_set_properties(struct iris_inst *inst, u32 plane); #endif diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h index c3508514014c..864162364b31 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h @@ -92,6 +92,14 @@ enum hfi_rotation { }; #define HFI_PROP_ROTATION 0x0300014b + +enum hfi_flip { + HFI_DISABLE_FLIP = 0x00000000, + HFI_HORIZONTAL_FLIP = 0x00000001, + HFI_VERTICAL_FLIP = 0x00000002, +}; + +#define HFI_PROP_FLIP 0x0300014c #define HFI_PROP_SIGNAL_COLOR_INFO 0x03000155 #define HFI_PROP_PICTURE_TYPE 0x03000162 #define HFI_PROP_DEC_DEFAULT_HEADER 0x03000168 diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index e8fe8b9bd21e..b3bf25ed2dd0 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -151,6 +151,8 @@ enum platform_inst_fw_cap_type { B_FRAME_QP_H264, B_FRAME_QP_HEVC, ROTATION, + HFLIP, + VFLIP, INST_FW_CAP_MAX, }; diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index 3412a77a2c08..034c67341694 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -699,6 +699,28 @@ static const struct platform_inst_fw_cap inst_fw_cap_sm8550_enc[] = { .flags = CAP_FLAG_OUTPUT_PORT, .set = iris_set_rotation, }, + { + .cap_id = HFLIP, + .min = 0, + .max = 1, + .step_or_mask = 1, + .value = 0, + .hfi_id = HFI_PROP_FLIP, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_INPUT_PORT | + CAP_FLAG_DYNAMIC_ALLOWED, + .set = iris_set_flip, + }, + { + .cap_id = VFLIP, + .min = 0, + .max = 1, + .step_or_mask = 1, + .value = 0, + .hfi_id = HFI_PROP_FLIP, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_INPUT_PORT | + CAP_FLAG_DYNAMIC_ALLOWED, + .set = iris_set_flip, + }, }; static struct platform_inst_caps platform_inst_cap_sm8550 = { From 5ef0832c6e42dc67101629810c763625905cd026 Mon Sep 17 00:00:00 2001 From: Wangao Wang Date: Fri, 14 Nov 2025 11:09:03 +0800 Subject: [PATCH 267/307] media: qcom: iris: Add intra refresh support for encoder Add support for V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD and V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE controls. Reviewed-by: Dikshita Agarwal Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Wangao Wang Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_ctrls.c | 34 +++++++++++++++++++ drivers/media/platform/qcom/iris/iris_ctrls.h | 1 + .../qcom/iris/iris_hfi_gen2_defines.h | 2 ++ .../platform/qcom/iris/iris_platform_common.h | 2 ++ .../platform/qcom/iris/iris_platform_gen2.c | 19 +++++++++++ 5 files changed, 58 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.c b/drivers/media/platform/qcom/iris/iris_ctrls.c index d7bd7f6dcc21..3cec957580f5 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.c +++ b/drivers/media/platform/qcom/iris/iris_ctrls.c @@ -108,6 +108,10 @@ static enum platform_inst_fw_cap_type iris_get_cap_id(u32 id) return HFLIP; case V4L2_CID_VFLIP: return VFLIP; + case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE: + return IR_TYPE; + case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD: + return IR_PERIOD; default: return INST_FW_CAP_MAX; } @@ -205,6 +209,10 @@ static u32 iris_get_v4l2_id(enum platform_inst_fw_cap_type cap_id) return V4L2_CID_HFLIP; case VFLIP: return V4L2_CID_VFLIP; + case IR_TYPE: + return V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE; + case IR_PERIOD: + return V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD; default: return 0; } @@ -962,6 +970,32 @@ int iris_set_flip(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id) &hfi_val, sizeof(u32)); } +int iris_set_ir_period(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id) +{ + const struct iris_hfi_command_ops *hfi_ops = inst->core->hfi_ops; + struct vb2_queue *q = v4l2_m2m_get_dst_vq(inst->m2m_ctx); + u32 ir_period = inst->fw_caps[cap_id].value; + u32 ir_type = 0; + + if (inst->fw_caps[IR_TYPE].value == + V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_RANDOM) { + if (vb2_is_streaming(q)) + return 0; + ir_type = HFI_PROP_IR_RANDOM_PERIOD; + } else if (inst->fw_caps[IR_TYPE].value == + V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_CYCLIC) { + ir_type = HFI_PROP_IR_CYCLIC_PERIOD; + } else { + return -EINVAL; + } + + return hfi_ops->session_set_property(inst, ir_type, + HFI_HOST_FLAGS_NONE, + iris_get_port_info(inst, cap_id), + HFI_PAYLOAD_U32, + &ir_period, sizeof(u32)); +} + int iris_set_properties(struct iris_inst *inst, u32 plane) { const struct iris_hfi_command_ops *hfi_ops = inst->core->hfi_ops; diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.h b/drivers/media/platform/qcom/iris/iris_ctrls.h index 355a592049f3..9518803577bc 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.h +++ b/drivers/media/platform/qcom/iris/iris_ctrls.h @@ -34,6 +34,7 @@ int iris_set_frame_qp(struct iris_inst *inst, enum platform_inst_fw_cap_type cap int iris_set_qp_range(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); int iris_set_rotation(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); int iris_set_flip(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); +int iris_set_ir_period(struct iris_inst *inst, enum platform_inst_fw_cap_type cap_id); int iris_set_properties(struct iris_inst *inst, u32 plane); #endif diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h index 864162364b31..cecf771c55dd 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h @@ -70,6 +70,7 @@ enum hfi_rate_control { #define HFI_PROP_QP_PACKED 0x0300012e #define HFI_PROP_MIN_QP_PACKED 0x0300012f #define HFI_PROP_MAX_QP_PACKED 0x03000130 +#define HFI_PROP_IR_RANDOM_PERIOD 0x03000131 #define HFI_PROP_TOTAL_BITRATE 0x0300013b #define HFI_PROP_MAX_GOP_FRAMES 0x03000146 #define HFI_PROP_MAX_B_FRAMES 0x03000147 @@ -113,6 +114,7 @@ enum hfi_flip { #define HFI_PROP_AV1_FILM_GRAIN_PRESENT 0x03000180 #define HFI_PROP_AV1_SUPER_BLOCK_ENABLED 0x03000181 #define HFI_PROP_AV1_OP_POINT 0x03000182 +#define HFI_PROP_IR_CYCLIC_PERIOD 0x0300017E #define HFI_PROP_OPB_ENABLE 0x03000184 #define HFI_PROP_AV1_TILE_ROWS_COLUMNS 0x03000187 #define HFI_PROP_AV1_DRAP_CONFIG 0x03000189 diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index b3bf25ed2dd0..a24ed2d03a61 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -153,6 +153,8 @@ enum platform_inst_fw_cap_type { ROTATION, HFLIP, VFLIP, + IR_TYPE, + IR_PERIOD, INST_FW_CAP_MAX, }; diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index 034c67341694..753ebf0a42c5 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -721,6 +721,25 @@ static const struct platform_inst_fw_cap inst_fw_cap_sm8550_enc[] = { CAP_FLAG_DYNAMIC_ALLOWED, .set = iris_set_flip, }, + { + .cap_id = IR_TYPE, + .min = V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_RANDOM, + .max = V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_CYCLIC, + .step_or_mask = BIT(V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_RANDOM) | + BIT(V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_CYCLIC), + .value = V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_RANDOM, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + }, + { + .cap_id = IR_PERIOD, + .min = 0, + .max = INT_MAX, + .step_or_mask = 1, + .value = 0, + .flags = CAP_FLAG_OUTPUT_PORT | + CAP_FLAG_DYNAMIC_ALLOWED, + .set = iris_set_ir_period, + }, }; static struct platform_inst_caps platform_inst_cap_sm8550 = { From 8744df0e8946abee487342c97233e61ceb35a632 Mon Sep 17 00:00:00 2001 From: Vikash Garodia Date: Wed, 10 Dec 2025 18:05:59 +0530 Subject: [PATCH 268/307] media: iris: Add support for multiple clock sources vpu4 depends on more than one clock source. Thus far hardware versions up to vpu3x have been clocked by a single source. This adds support for multiple clocks by, - Adding a lookup table - Configuring OPP table for video device with different video clocks - Setting OPP for multiple clocks during dev_pm_opp_set_opp() This patch extends the support for multiple clocks in driver, which would be used in subsequent patch for kaanapali, when the platform data is prepared. Reviewed-by: Bryan O'Donoghue Co-developed-by: Vishnu Reddy Signed-off-by: Vishnu Reddy Signed-off-by: Vikash Garodia Reviewed-by: Dikshita Agarwal Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../platform/qcom/iris/iris_platform_common.h | 1 + .../platform/qcom/iris/iris_platform_gen1.c | 7 +++++++ .../platform/qcom/iris/iris_platform_gen2.c | 9 +++++++++ .../platform/qcom/iris/iris_platform_sc7280.h | 5 +++++ drivers/media/platform/qcom/iris/iris_power.c | 2 +- drivers/media/platform/qcom/iris/iris_probe.c | 20 ++++++++----------- .../media/platform/qcom/iris/iris_resources.c | 16 +++++++++++++-- .../media/platform/qcom/iris/iris_resources.h | 1 + .../platform/qcom/iris/iris_vpu_common.c | 4 ++-- 9 files changed, 48 insertions(+), 17 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index a24ed2d03a61..222bd7d629ce 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -221,6 +221,7 @@ struct iris_platform_data { const char * const *opp_pd_tbl; unsigned int opp_pd_tbl_size; const struct platform_clk_data *clk_tbl; + const char * const *opp_clk_tbl; unsigned int clk_tbl_size; const char * const *clk_rst_tbl; unsigned int clk_rst_tbl_size; diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen1.c b/drivers/media/platform/qcom/iris/iris_platform_gen1.c index 712515b0f329..7abd18be3549 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen1.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen1.c @@ -289,6 +289,11 @@ static const struct platform_clk_data sm8250_clk_table[] = { {IRIS_HW_CLK, "vcodec0_core" }, }; +static const char * const sm8250_opp_clk_table[] = { + "vcodec0_core", + NULL, +}; + static struct tz_cp_config tz_cp_config_sm8250 = { .cp_start = 0, .cp_size = 0x25800000, @@ -349,6 +354,7 @@ const struct iris_platform_data sm8250_data = { .opp_pd_tbl_size = ARRAY_SIZE(sm8250_opp_pd_table), .clk_tbl = sm8250_clk_table, .clk_tbl_size = ARRAY_SIZE(sm8250_clk_table), + .opp_clk_tbl = sm8250_opp_clk_table, /* Upper bound of DMA address range */ .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu-1.0/venus.mbn", @@ -400,6 +406,7 @@ const struct iris_platform_data sc7280_data = { .opp_pd_tbl_size = ARRAY_SIZE(sc7280_opp_pd_table), .clk_tbl = sc7280_clk_table, .clk_tbl_size = ARRAY_SIZE(sc7280_clk_table), + .opp_clk_tbl = sc7280_opp_clk_table, /* Upper bound of DMA address range */ .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu/vpu20_p1.mbn", diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index 753ebf0a42c5..cc242e56de7f 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -785,6 +785,11 @@ static const struct platform_clk_data sm8550_clk_table[] = { {IRIS_HW_CLK, "vcodec0_core" }, }; +static const char * const sm8550_opp_clk_table[] = { + "vcodec0_core", + NULL, +}; + static struct ubwc_config_data ubwc_config_sm8550 = { .max_channels = 8, .mal_length = 32, @@ -933,6 +938,7 @@ const struct iris_platform_data sm8550_data = { .opp_pd_tbl_size = ARRAY_SIZE(sm8550_opp_pd_table), .clk_tbl = sm8550_clk_table, .clk_tbl_size = ARRAY_SIZE(sm8550_clk_table), + .opp_clk_tbl = sm8550_opp_clk_table, /* Upper bound of DMA address range */ .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu/vpu30_p4.mbn", @@ -1036,6 +1042,7 @@ const struct iris_platform_data sm8650_data = { .opp_pd_tbl_size = ARRAY_SIZE(sm8550_opp_pd_table), .clk_tbl = sm8550_clk_table, .clk_tbl_size = ARRAY_SIZE(sm8550_clk_table), + .opp_clk_tbl = sm8550_opp_clk_table, /* Upper bound of DMA address range */ .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu/vpu33_p4.mbn", @@ -1130,6 +1137,7 @@ const struct iris_platform_data sm8750_data = { .opp_pd_tbl_size = ARRAY_SIZE(sm8550_opp_pd_table), .clk_tbl = sm8750_clk_table, .clk_tbl_size = ARRAY_SIZE(sm8750_clk_table), + .opp_clk_tbl = sm8550_opp_clk_table, /* Upper bound of DMA address range */ .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu/vpu35_p4.mbn", @@ -1228,6 +1236,7 @@ const struct iris_platform_data qcs8300_data = { .opp_pd_tbl_size = ARRAY_SIZE(sm8550_opp_pd_table), .clk_tbl = sm8550_clk_table, .clk_tbl_size = ARRAY_SIZE(sm8550_clk_table), + .opp_clk_tbl = sm8550_opp_clk_table, /* Upper bound of DMA address range */ .dma_mask = 0xe0000000 - 1, .fwname = "qcom/vpu/vpu30_p4_s6.mbn", diff --git a/drivers/media/platform/qcom/iris/iris_platform_sc7280.h b/drivers/media/platform/qcom/iris/iris_platform_sc7280.h index f1bef4d4bcfe..0ec8f334df67 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_sc7280.h +++ b/drivers/media/platform/qcom/iris/iris_platform_sc7280.h @@ -23,4 +23,9 @@ static const struct platform_clk_data sc7280_clk_table[] = { {IRIS_HW_AHB_CLK, "vcodec_bus" }, }; +static const char * const sc7280_opp_clk_table[] = { + "vcodec_core", + NULL, +}; + #endif diff --git a/drivers/media/platform/qcom/iris/iris_power.c b/drivers/media/platform/qcom/iris/iris_power.c index dbca42df0910..91aa21d4070e 100644 --- a/drivers/media/platform/qcom/iris/iris_power.c +++ b/drivers/media/platform/qcom/iris/iris_power.c @@ -91,7 +91,7 @@ static int iris_set_clocks(struct iris_inst *inst) } core->power.clk_freq = freq; - ret = dev_pm_opp_set_rate(core->dev, freq); + ret = iris_opp_set_rate(core->dev, freq); mutex_unlock(&core->lock); return ret; diff --git a/drivers/media/platform/qcom/iris/iris_probe.c b/drivers/media/platform/qcom/iris/iris_probe.c index 9bc9b34c2576..ddaacda523ec 100644 --- a/drivers/media/platform/qcom/iris/iris_probe.c +++ b/drivers/media/platform/qcom/iris/iris_probe.c @@ -40,8 +40,6 @@ static int iris_init_icc(struct iris_core *core) static int iris_init_power_domains(struct iris_core *core) { - const struct platform_clk_data *clk_tbl; - u32 clk_cnt, i; int ret; struct dev_pm_domain_attach_data iris_pd_data = { @@ -56,6 +54,11 @@ static int iris_init_power_domains(struct iris_core *core) .pd_flags = PD_FLAG_DEV_LINK_ON | PD_FLAG_REQUIRED_OPP, }; + struct dev_pm_opp_config iris_opp_clk_data = { + .clk_names = core->iris_platform_data->opp_clk_tbl, + .config_clks = dev_pm_opp_config_clks_simple, + }; + ret = devm_pm_domain_attach_list(core->dev, &iris_pd_data, &core->pmdomain_tbl); if (ret < 0) return ret; @@ -64,16 +67,9 @@ static int iris_init_power_domains(struct iris_core *core) if (ret < 0) return ret; - clk_tbl = core->iris_platform_data->clk_tbl; - clk_cnt = core->iris_platform_data->clk_tbl_size; - - for (i = 0; i < clk_cnt; i++) { - if (clk_tbl[i].clk_type == IRIS_HW_CLK) { - ret = devm_pm_opp_set_clkname(core->dev, clk_tbl[i].clk_name); - if (ret) - return ret; - } - } + ret = devm_pm_opp_set_config(core->dev, &iris_opp_clk_data); + if (ret) + return ret; return devm_pm_opp_of_add_table(core->dev); } diff --git a/drivers/media/platform/qcom/iris/iris_resources.c b/drivers/media/platform/qcom/iris/iris_resources.c index 164490c49c95..773f6548370a 100644 --- a/drivers/media/platform/qcom/iris/iris_resources.c +++ b/drivers/media/platform/qcom/iris/iris_resources.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -58,11 +59,22 @@ int iris_unset_icc_bw(struct iris_core *core) return icc_bulk_set_bw(core->icc_count, core->icc_tbl); } +int iris_opp_set_rate(struct device *dev, unsigned long freq) +{ + struct dev_pm_opp *opp __free(put_opp); + + opp = devfreq_recommended_opp(dev, &freq, 0); + if (IS_ERR(opp)) + return PTR_ERR(opp); + + return dev_pm_opp_set_opp(dev, opp); +} + int iris_enable_power_domains(struct iris_core *core, struct device *pd_dev) { int ret; - ret = dev_pm_opp_set_rate(core->dev, ULONG_MAX); + ret = iris_opp_set_rate(core->dev, ULONG_MAX); if (ret) return ret; @@ -77,7 +89,7 @@ int iris_disable_power_domains(struct iris_core *core, struct device *pd_dev) { int ret; - ret = dev_pm_opp_set_rate(core->dev, 0); + ret = iris_opp_set_rate(core->dev, 0); if (ret) return ret; diff --git a/drivers/media/platform/qcom/iris/iris_resources.h b/drivers/media/platform/qcom/iris/iris_resources.h index f723dfe5bd81..6bfbd2dc6db0 100644 --- a/drivers/media/platform/qcom/iris/iris_resources.h +++ b/drivers/media/platform/qcom/iris/iris_resources.h @@ -8,6 +8,7 @@ struct iris_core; +int iris_opp_set_rate(struct device *dev, unsigned long freq); int iris_enable_power_domains(struct iris_core *core, struct device *pd_dev); int iris_disable_power_domains(struct iris_core *core, struct device *pd_dev); int iris_unset_icc_bw(struct iris_core *core); diff --git a/drivers/media/platform/qcom/iris/iris_vpu_common.c b/drivers/media/platform/qcom/iris/iris_vpu_common.c index 515dd55a3377..fef192a2de48 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_common.c +++ b/drivers/media/platform/qcom/iris/iris_vpu_common.c @@ -270,7 +270,7 @@ void iris_vpu_power_off_hw(struct iris_core *core) void iris_vpu_power_off(struct iris_core *core) { - dev_pm_opp_set_rate(core->dev, 0); + iris_opp_set_rate(core->dev, 0); core->iris_platform_data->vpu_ops->power_off_hw(core); core->iris_platform_data->vpu_ops->power_off_controller(core); iris_unset_icc_bw(core); @@ -368,7 +368,7 @@ int iris_vpu_power_on(struct iris_core *core) freq = core->power.clk_freq ? core->power.clk_freq : (u32)ULONG_MAX; - dev_pm_opp_set_rate(core->dev, freq); + iris_opp_set_rate(core->dev, freq); core->iris_platform_data->set_preset_registers(core); From dff99dff6c3c4feadc28585dc16f741b0978e631 Mon Sep 17 00:00:00 2001 From: Vikash Garodia Date: Wed, 10 Dec 2025 18:06:00 +0530 Subject: [PATCH 269/307] media: iris: Add support for multiple TZ content protection(CP) configs vpu4 needs an additional configuration with respect to CP regions. Make the CP configuration as array such that the multiple configuration can be managed per platform. Reviewed-by: Konrad Dybcio Reviewed-by: Bryan O'Donoghue Co-developed-by: Vishnu Reddy Signed-off-by: Vishnu Reddy Signed-off-by: Vikash Garodia Reviewed-by: Dikshita Agarwal Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../media/platform/qcom/iris/iris_firmware.c | 23 ++++++++++-------- .../platform/qcom/iris/iris_platform_common.h | 3 ++- .../platform/qcom/iris/iris_platform_gen1.c | 18 ++++++++------ .../platform/qcom/iris/iris_platform_gen2.c | 24 ++++++++++++------- 4 files changed, 41 insertions(+), 27 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_firmware.c b/drivers/media/platform/qcom/iris/iris_firmware.c index 679444327ed7..5f408024e967 100644 --- a/drivers/media/platform/qcom/iris/iris_firmware.c +++ b/drivers/media/platform/qcom/iris/iris_firmware.c @@ -64,9 +64,9 @@ err_release_fw: int iris_fw_load(struct iris_core *core) { - struct tz_cp_config *cp_config = core->iris_platform_data->tz_cp_config_data; + const struct tz_cp_config *cp_config; const char *fwpath = NULL; - int ret; + int i, ret; ret = of_property_read_string_index(core->dev->of_node, "firmware-name", 0, &fwpath); @@ -85,14 +85,17 @@ int iris_fw_load(struct iris_core *core) return ret; } - ret = qcom_scm_mem_protect_video_var(cp_config->cp_start, - cp_config->cp_size, - cp_config->cp_nonpixel_start, - cp_config->cp_nonpixel_size); - if (ret) { - dev_err(core->dev, "protect memory failed\n"); - qcom_scm_pas_shutdown(core->iris_platform_data->pas_id); - return ret; + for (i = 0; i < core->iris_platform_data->tz_cp_config_data_size; i++) { + cp_config = &core->iris_platform_data->tz_cp_config_data[i]; + ret = qcom_scm_mem_protect_video_var(cp_config->cp_start, + cp_config->cp_size, + cp_config->cp_nonpixel_start, + cp_config->cp_nonpixel_size); + if (ret) { + dev_err(core->dev, "qcom_scm_mem_protect_video_var failed: %d\n", ret); + qcom_scm_pas_shutdown(core->iris_platform_data->pas_id); + return ret; + } } return ret; diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index 222bd7d629ce..f215abcd36eb 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -237,7 +237,8 @@ struct iris_platform_data { u32 inst_fw_caps_dec_size; const struct platform_inst_fw_cap *inst_fw_caps_enc; u32 inst_fw_caps_enc_size; - struct tz_cp_config *tz_cp_config_data; + const struct tz_cp_config *tz_cp_config_data; + u32 tz_cp_config_data_size; u32 core_arch; u32 hw_response_timeout; struct ubwc_config_data *ubwc_config; diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen1.c b/drivers/media/platform/qcom/iris/iris_platform_gen1.c index 7abd18be3549..df8e6bf9430e 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen1.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen1.c @@ -294,11 +294,13 @@ static const char * const sm8250_opp_clk_table[] = { NULL, }; -static struct tz_cp_config tz_cp_config_sm8250 = { - .cp_start = 0, - .cp_size = 0x25800000, - .cp_nonpixel_start = 0x01000000, - .cp_nonpixel_size = 0x24800000, +static const struct tz_cp_config tz_cp_config_sm8250[] = { + { + .cp_start = 0, + .cp_size = 0x25800000, + .cp_nonpixel_start = 0x01000000, + .cp_nonpixel_size = 0x24800000, + }, }; static const u32 sm8250_vdec_input_config_param_default[] = { @@ -366,7 +368,8 @@ const struct iris_platform_data sm8250_data = { .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8250_dec), .inst_fw_caps_enc = inst_fw_cap_sm8250_enc, .inst_fw_caps_enc_size = ARRAY_SIZE(inst_fw_cap_sm8250_enc), - .tz_cp_config_data = &tz_cp_config_sm8250, + .tz_cp_config_data = tz_cp_config_sm8250, + .tz_cp_config_data_size = ARRAY_SIZE(tz_cp_config_sm8250), .hw_response_timeout = HW_RESPONSE_TIMEOUT_VALUE, .num_vpp_pipe = 4, .max_session_count = 16, @@ -418,7 +421,8 @@ const struct iris_platform_data sc7280_data = { .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8250_dec), .inst_fw_caps_enc = inst_fw_cap_sm8250_enc, .inst_fw_caps_enc_size = ARRAY_SIZE(inst_fw_cap_sm8250_enc), - .tz_cp_config_data = &tz_cp_config_sm8250, + .tz_cp_config_data = tz_cp_config_sm8250, + .tz_cp_config_data_size = ARRAY_SIZE(tz_cp_config_sm8250), .hw_response_timeout = HW_RESPONSE_TIMEOUT_VALUE, .num_vpp_pipe = 1, .no_aon = true, diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index cc242e56de7f..5da90d47f9c6 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -800,11 +800,13 @@ static struct ubwc_config_data ubwc_config_sm8550 = { .bank_spreading = 1, }; -static struct tz_cp_config tz_cp_config_sm8550 = { - .cp_start = 0, - .cp_size = 0x25800000, - .cp_nonpixel_start = 0x01000000, - .cp_nonpixel_size = 0x24800000, +static const struct tz_cp_config tz_cp_config_sm8550[] = { + { + .cp_start = 0, + .cp_size = 0x25800000, + .cp_nonpixel_start = 0x01000000, + .cp_nonpixel_size = 0x24800000, + }, }; static const u32 sm8550_vdec_input_config_params_default[] = { @@ -950,7 +952,8 @@ const struct iris_platform_data sm8550_data = { .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8550_dec), .inst_fw_caps_enc = inst_fw_cap_sm8550_enc, .inst_fw_caps_enc_size = ARRAY_SIZE(inst_fw_cap_sm8550_enc), - .tz_cp_config_data = &tz_cp_config_sm8550, + .tz_cp_config_data = tz_cp_config_sm8550, + .tz_cp_config_data_size = ARRAY_SIZE(tz_cp_config_sm8550), .core_arch = VIDEO_ARCH_LX, .hw_response_timeout = HW_RESPONSE_TIMEOUT_VALUE, .ubwc_config = &ubwc_config_sm8550, @@ -1054,7 +1057,8 @@ const struct iris_platform_data sm8650_data = { .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8550_dec), .inst_fw_caps_enc = inst_fw_cap_sm8550_enc, .inst_fw_caps_enc_size = ARRAY_SIZE(inst_fw_cap_sm8550_enc), - .tz_cp_config_data = &tz_cp_config_sm8550, + .tz_cp_config_data = tz_cp_config_sm8550, + .tz_cp_config_data_size = ARRAY_SIZE(tz_cp_config_sm8550), .core_arch = VIDEO_ARCH_LX, .hw_response_timeout = HW_RESPONSE_TIMEOUT_VALUE, .ubwc_config = &ubwc_config_sm8550, @@ -1149,7 +1153,8 @@ const struct iris_platform_data sm8750_data = { .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8550_dec), .inst_fw_caps_enc = inst_fw_cap_sm8550_enc, .inst_fw_caps_enc_size = ARRAY_SIZE(inst_fw_cap_sm8550_enc), - .tz_cp_config_data = &tz_cp_config_sm8550, + .tz_cp_config_data = tz_cp_config_sm8550, + .tz_cp_config_data_size = ARRAY_SIZE(tz_cp_config_sm8550), .core_arch = VIDEO_ARCH_LX, .hw_response_timeout = HW_RESPONSE_TIMEOUT_VALUE, .ubwc_config = &ubwc_config_sm8550, @@ -1248,7 +1253,8 @@ const struct iris_platform_data qcs8300_data = { .inst_fw_caps_dec_size = ARRAY_SIZE(inst_fw_cap_sm8550_dec), .inst_fw_caps_enc = inst_fw_cap_sm8550_enc, .inst_fw_caps_enc_size = ARRAY_SIZE(inst_fw_cap_sm8550_enc), - .tz_cp_config_data = &tz_cp_config_sm8550, + .tz_cp_config_data = tz_cp_config_sm8550, + .tz_cp_config_data_size = ARRAY_SIZE(tz_cp_config_sm8550), .core_arch = VIDEO_ARCH_LX, .hw_response_timeout = HW_RESPONSE_TIMEOUT_VALUE, .ubwc_config = &ubwc_config_sm8550, From df816dce1b01ad761b20ff9568fbb5383e485bf9 Mon Sep 17 00:00:00 2001 From: Vikash Garodia Date: Wed, 10 Dec 2025 18:06:01 +0530 Subject: [PATCH 270/307] media: iris: Introduce buffer size calculations for vpu4 Introduces vp4 buffer size calculation for both encoder and decoder. Reuse the buffer size calculation which are common, while adding the vpu4 ones separately. Co-developed-by: Vishnu Reddy Signed-off-by: Vishnu Reddy Signed-off-by: Vikash Garodia Reviewed-by: Dikshita Agarwal Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../platform/qcom/iris/iris_vpu_buffer.c | 342 ++++++++++++++++++ .../platform/qcom/iris/iris_vpu_buffer.h | 24 ++ 2 files changed, 366 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_vpu_buffer.c b/drivers/media/platform/qcom/iris/iris_vpu_buffer.c index aced49495bb8..9270422c1601 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_buffer.c +++ b/drivers/media/platform/qcom/iris/iris_vpu_buffer.c @@ -1693,6 +1693,304 @@ static u32 iris_vpu_enc_vpss_size(struct iris_inst *inst) return hfi_buffer_vpss_enc(width, height, ds_enable, 0, 0); } +static inline u32 size_dpb_opb(u32 height, u32 lcu_size) +{ + u32 max_tile_height = ((height + lcu_size - 1) / lcu_size) * lcu_size + 8; + u32 dpb_opb = 3 * ((max_tile_height >> 3) * DMA_ALIGNMENT); + u32 num_luma_chrome_plane = 2; + + return ALIGN(dpb_opb, DMA_ALIGNMENT) * num_luma_chrome_plane; +} + +static u32 hfi_vpu4x_vp9d_lb_size(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) +{ + u32 vp9_top_lb, vp9_fe_left_lb, vp9_se_left_lb, dpb_opb, vp9d_qp, num_lcu_per_pipe; + u32 lcu_size = 64; + + vp9_top_lb = ALIGN(size_vp9d_lb_vsp_top(frame_width, frame_height), DMA_ALIGNMENT); + vp9_top_lb += ALIGN(size_vpxd_lb_se_top_ctrl(frame_width, frame_height), DMA_ALIGNMENT); + vp9_top_lb += max3(DIV_ROUND_UP(frame_width, BUFFER_ALIGNMENT_16_BYTES) * + MAX_PE_NBR_DATA_LCU16_LINE_BUFFER_SIZE, + DIV_ROUND_UP(frame_width, BUFFER_ALIGNMENT_32_BYTES) * + MAX_PE_NBR_DATA_LCU32_LINE_BUFFER_SIZE, + DIV_ROUND_UP(frame_width, BUFFER_ALIGNMENT_64_BYTES) * + MAX_PE_NBR_DATA_LCU64_LINE_BUFFER_SIZE); + vp9_top_lb = ALIGN(vp9_top_lb, DMA_ALIGNMENT); + vp9_top_lb += ALIGN((DMA_ALIGNMENT * DIV_ROUND_UP(frame_width, lcu_size)), + DMA_ALIGNMENT) * FE_TOP_CTRL_LINE_NUMBERS; + vp9_top_lb += ALIGN(DMA_ALIGNMENT * 8 * DIV_ROUND_UP(frame_width, lcu_size), + DMA_ALIGNMENT) * (FE_TOP_DATA_LUMA_LINE_NUMBERS + + FE_TOP_DATA_CHROMA_LINE_NUMBERS); + + num_lcu_per_pipe = (DIV_ROUND_UP(frame_height, lcu_size) / num_vpp_pipes) + + (DIV_ROUND_UP(frame_height, lcu_size) % num_vpp_pipes); + vp9_fe_left_lb = ALIGN((DMA_ALIGNMENT * num_lcu_per_pipe), DMA_ALIGNMENT) * + FE_LFT_CTRL_LINE_NUMBERS; + vp9_fe_left_lb += ((ALIGN((DMA_ALIGNMENT * 8 * num_lcu_per_pipe), DMA_ALIGNMENT) * + FE_LFT_DB_DATA_LINE_NUMBERS) + + ALIGN((DMA_ALIGNMENT * 3 * num_lcu_per_pipe), DMA_ALIGNMENT) + + ALIGN((DMA_ALIGNMENT * 4 * num_lcu_per_pipe), DMA_ALIGNMENT) + + (ALIGN((DMA_ALIGNMENT * 24 * num_lcu_per_pipe), DMA_ALIGNMENT) * + FE_LFT_LR_DATA_LINE_NUMBERS)); + vp9_fe_left_lb = vp9_fe_left_lb * num_vpp_pipes; + + vp9_se_left_lb = ALIGN(size_vpxd_lb_se_left_ctrl(frame_width, frame_height), + DMA_ALIGNMENT); + dpb_opb = size_dpb_opb(frame_height, lcu_size); + vp9d_qp = ALIGN(size_vp9d_qp(frame_width, frame_height), DMA_ALIGNMENT); + + return vp9_top_lb + vp9_fe_left_lb + (vp9_se_left_lb * num_vpp_pipes) + + (dpb_opb * num_vpp_pipes) + vp9d_qp; +} + +static u32 hfi_vpu4x_buffer_line_vp9d(u32 frame_width, u32 frame_height, u32 _yuv_bufcount_min, + bool is_opb, u32 num_vpp_pipes) +{ + u32 lb_size = hfi_vpu4x_vp9d_lb_size(frame_width, frame_height, num_vpp_pipes); + u32 dpb_obp_size = 0, lcu_size = 64; + + if (is_opb) + dpb_obp_size = size_dpb_opb(frame_height, lcu_size) * num_vpp_pipes; + + return lb_size + dpb_obp_size; +} + +static u32 iris_vpu4x_dec_line_size(struct iris_inst *inst) +{ + u32 num_vpp_pipes = inst->core->iris_platform_data->num_vpp_pipe; + u32 out_min_count = inst->buffers[BUF_OUTPUT].min_count; + struct v4l2_format *f = inst->fmt_src; + u32 height = f->fmt.pix_mp.height; + u32 width = f->fmt.pix_mp.width; + bool is_opb = false; + + if (iris_split_mode_enabled(inst)) + is_opb = true; + + if (inst->codec == V4L2_PIX_FMT_H264) + return hfi_buffer_line_h264d(width, height, is_opb, num_vpp_pipes); + else if (inst->codec == V4L2_PIX_FMT_HEVC) + return hfi_buffer_line_h265d(width, height, is_opb, num_vpp_pipes); + else if (inst->codec == V4L2_PIX_FMT_VP9) + return hfi_vpu4x_buffer_line_vp9d(width, height, out_min_count, is_opb, + num_vpp_pipes); + + return 0; +} + +static u32 hfi_vpu4x_buffer_persist_h265d(u32 rpu_enabled) +{ + return ALIGN((SIZE_SLIST_BUF_H265 * NUM_SLIST_BUF_H265 + H265_NUM_FRM_INFO * + H265_DISPLAY_BUF_SIZE + (H265_NUM_TILE * sizeof(u32)) + (NUM_HW_PIC_BUF * + (SIZE_SEI_USERDATA + SIZE_H265D_ARP + SIZE_THREE_DIMENSION_USERDATA)) + + rpu_enabled * NUM_HW_PIC_BUF * SIZE_DOLBY_RPU_METADATA), DMA_ALIGNMENT); +} + +static u32 hfi_vpu4x_buffer_persist_vp9d(void) +{ + return ALIGN(VP9_NUM_PROBABILITY_TABLE_BUF * VP9_PROB_TABLE_SIZE, DMA_ALIGNMENT) + + (ALIGN(hfi_iris3_vp9d_comv_size(), DMA_ALIGNMENT) * 2) + + ALIGN(MAX_SUPERFRAME_HEADER_LEN, DMA_ALIGNMENT) + + ALIGN(VP9_UDC_HEADER_BUF_SIZE, DMA_ALIGNMENT) + + ALIGN(VP9_NUM_FRAME_INFO_BUF * CCE_TILE_OFFSET_SIZE, DMA_ALIGNMENT) + + ALIGN(VP9_NUM_FRAME_INFO_BUF * VP9_FRAME_INFO_BUF_SIZE_VPU4X, DMA_ALIGNMENT) + + HDR10_HIST_EXTRADATA_SIZE; +} + +static u32 iris_vpu4x_dec_persist_size(struct iris_inst *inst) +{ + if (inst->codec == V4L2_PIX_FMT_H264) + return hfi_buffer_persist_h264d(); + else if (inst->codec == V4L2_PIX_FMT_HEVC) + return hfi_vpu4x_buffer_persist_h265d(0); + else if (inst->codec == V4L2_PIX_FMT_VP9) + return hfi_vpu4x_buffer_persist_vp9d(); + + return 0; +} + +static u32 size_se_lb(u32 standard, u32 num_vpp_pipes_enc, + u32 frame_width_coded, u32 frame_height_coded) +{ + u32 se_tlb_size = ALIGN(frame_width_coded, DMA_ALIGNMENT); + u32 se_llb_size = (standard == HFI_CODEC_ENCODE_HEVC) ? + ((frame_height_coded + BUFFER_ALIGNMENT_32_BYTES - 1) / + BUFFER_ALIGNMENT_32_BYTES) * LOG2_16 * LLB_UNIT_SIZE : + ((frame_height_coded + BUFFER_ALIGNMENT_16_BYTES - 1) / + BUFFER_ALIGNMENT_16_BYTES) * LOG2_32 * LLB_UNIT_SIZE; + + se_llb_size = ALIGN(se_llb_size, BUFFER_ALIGNMENT_32_BYTES); + + if (num_vpp_pipes_enc > 1) + se_llb_size = ALIGN(se_llb_size + BUFFER_ALIGNMENT_512_BYTES, + DMA_ALIGNMENT) * num_vpp_pipes_enc; + + return ALIGN(se_tlb_size + se_llb_size, DMA_ALIGNMENT); +} + +static u32 size_te_lb(bool is_ten_bit, u32 num_vpp_pipes_enc, u32 width_in_lcus, + u32 frame_height_coded, u32 frame_width_coded) +{ + u32 num_pixel_10_bit = 3, num_pixel_8_bit = 2, num_pixel_te_llb = 3; + u32 te_llb_col_rc_size = ALIGN(32 * width_in_lcus / num_vpp_pipes_enc, + DMA_ALIGNMENT) * num_vpp_pipes_enc; + u32 te_tlb_recon_data_size = ALIGN((is_ten_bit ? num_pixel_10_bit : num_pixel_8_bit) * + frame_width_coded, DMA_ALIGNMENT); + u32 te_llb_recon_data_size = ((1 + is_ten_bit) * num_pixel_te_llb * frame_height_coded + + num_vpp_pipes_enc - 1) / num_vpp_pipes_enc; + te_llb_recon_data_size = ALIGN(te_llb_recon_data_size, DMA_ALIGNMENT) * num_vpp_pipes_enc; + + return ALIGN(te_llb_recon_data_size + te_llb_col_rc_size + te_tlb_recon_data_size, + DMA_ALIGNMENT); +} + +static inline u32 calc_fe_tlb_size(u32 size_per_lcu, bool is_ten_bit) +{ + u32 num_pixels_fe_tlb_10_bit = 128, num_pixels_fe_tlb_8_bit = 64; + + return is_ten_bit ? (num_pixels_fe_tlb_10_bit * (size_per_lcu + 1)) : + (size_per_lcu * num_pixels_fe_tlb_8_bit); +} + +static u32 size_fe_lb(bool is_ten_bit, u32 standard, u32 num_vpp_pipes_enc, + u32 frame_height_coded, u32 frame_width_coded) +{ + u32 log2_lcu_size, num_cu_in_height_pipe, num_cu_in_width, + fb_llb_db_ctrl_size, fb_llb_db_luma_size, fb_llb_db_chroma_size, + fb_tlb_db_ctrl_size, fb_tlb_db_luma_size, fb_tlb_db_chroma_size, + fb_llb_sao_ctrl_size, fb_llb_sao_luma_size, fb_llb_sao_chroma_size, + fb_tlb_sao_ctrl_size, fb_tlb_sao_luma_size, fb_tlb_sao_chroma_size, + fb_lb_top_sdc_size, fb_lb_se_ctrl_size, fe_tlb_size, size_per_lcu; + + log2_lcu_size = (standard == HFI_CODEC_ENCODE_HEVC) ? 5 : 4; + num_cu_in_height_pipe = ((frame_height_coded >> log2_lcu_size) + num_vpp_pipes_enc - 1) / + num_vpp_pipes_enc; + num_cu_in_width = frame_width_coded >> log2_lcu_size; + + size_per_lcu = 2; + fe_tlb_size = calc_fe_tlb_size(size_per_lcu, 1); + fb_llb_db_ctrl_size = ALIGN(fe_tlb_size, DMA_ALIGNMENT) * num_cu_in_height_pipe; + fb_llb_db_ctrl_size = ALIGN(fb_llb_db_ctrl_size, DMA_ALIGNMENT) * num_vpp_pipes_enc; + + size_per_lcu = (1 << (log2_lcu_size - 3)); + fe_tlb_size = calc_fe_tlb_size(size_per_lcu, is_ten_bit); + fb_llb_db_luma_size = ALIGN(fe_tlb_size, DMA_ALIGNMENT) * num_cu_in_height_pipe; + fb_llb_db_luma_size = ALIGN(fb_llb_db_luma_size, DMA_ALIGNMENT) * num_vpp_pipes_enc; + + size_per_lcu = ((1 << (log2_lcu_size - 4)) * 2); + fe_tlb_size = calc_fe_tlb_size(size_per_lcu, is_ten_bit); + fb_llb_db_chroma_size = ALIGN(fe_tlb_size, DMA_ALIGNMENT) * num_cu_in_height_pipe; + fb_llb_db_chroma_size = ALIGN(fb_llb_db_chroma_size, DMA_ALIGNMENT) * num_vpp_pipes_enc; + + size_per_lcu = 1; + fe_tlb_size = calc_fe_tlb_size(size_per_lcu, 1); + fb_tlb_db_ctrl_size = ALIGN(fe_tlb_size, DMA_ALIGNMENT) * num_cu_in_width; + fb_llb_sao_ctrl_size = ALIGN(fe_tlb_size, DMA_ALIGNMENT) * num_cu_in_height_pipe; + fb_llb_sao_ctrl_size = fb_llb_sao_ctrl_size * num_vpp_pipes_enc; + fb_tlb_sao_ctrl_size = ALIGN(fe_tlb_size, DMA_ALIGNMENT) * num_cu_in_width; + + size_per_lcu = ((1 << (log2_lcu_size - 3)) + 1); + fe_tlb_size = calc_fe_tlb_size(size_per_lcu, is_ten_bit); + fb_tlb_db_luma_size = ALIGN(fe_tlb_size, DMA_ALIGNMENT) * num_cu_in_width; + + size_per_lcu = (2 * ((1 << (log2_lcu_size - 4)) + 1)); + fe_tlb_size = calc_fe_tlb_size(size_per_lcu, is_ten_bit); + fb_tlb_db_chroma_size = ALIGN(fe_tlb_size, DMA_ALIGNMENT) * num_cu_in_width; + + fb_llb_sao_luma_size = BUFFER_ALIGNMENT_256_BYTES * num_vpp_pipes_enc; + fb_llb_sao_chroma_size = BUFFER_ALIGNMENT_256_BYTES * num_vpp_pipes_enc; + fb_tlb_sao_luma_size = BUFFER_ALIGNMENT_256_BYTES; + fb_tlb_sao_chroma_size = BUFFER_ALIGNMENT_256_BYTES; + fb_lb_top_sdc_size = ALIGN((FE_SDC_DATA_PER_BLOCK * (frame_width_coded >> 5)), + DMA_ALIGNMENT); + fb_lb_se_ctrl_size = ALIGN((SE_CTRL_DATA_PER_BLOCK * (frame_width_coded >> 5)), + DMA_ALIGNMENT); + + return fb_llb_db_ctrl_size + fb_llb_db_luma_size + fb_llb_db_chroma_size + + fb_tlb_db_ctrl_size + fb_tlb_db_luma_size + fb_tlb_db_chroma_size + + fb_llb_sao_ctrl_size + fb_llb_sao_luma_size + fb_llb_sao_chroma_size + + fb_tlb_sao_ctrl_size + fb_tlb_sao_luma_size + fb_tlb_sao_chroma_size + + fb_lb_top_sdc_size + fb_lb_se_ctrl_size; +} + +static u32 size_md_lb(u32 standard, u32 frame_width_coded, + u32 frame_height_coded, u32 num_vpp_pipes_enc) +{ + u32 md_tlb_size = ALIGN(frame_width_coded, DMA_ALIGNMENT); + u32 md_llb_size = (standard == HFI_CODEC_ENCODE_HEVC) ? + ((frame_height_coded + BUFFER_ALIGNMENT_32_BYTES - 1) / + BUFFER_ALIGNMENT_32_BYTES) * LOG2_16 * LLB_UNIT_SIZE : + ((frame_height_coded + BUFFER_ALIGNMENT_16_BYTES - 1) / + BUFFER_ALIGNMENT_16_BYTES) * LOG2_32 * LLB_UNIT_SIZE; + + md_llb_size = ALIGN(md_llb_size, BUFFER_ALIGNMENT_32_BYTES); + + if (num_vpp_pipes_enc > 1) + md_llb_size = ALIGN(md_llb_size + BUFFER_ALIGNMENT_512_BYTES, + DMA_ALIGNMENT) * num_vpp_pipes_enc; + + md_llb_size = ALIGN(md_llb_size, DMA_ALIGNMENT); + + return ALIGN(md_tlb_size + md_llb_size, DMA_ALIGNMENT); +} + +static u32 size_dma_opb_lb(u32 num_vpp_pipes_enc, u32 frame_width_coded, + u32 frame_height_coded) +{ + u32 opb_packet_bytes = 128, opb_bpp = 128, opb_size_per_row = 6; + u32 dma_opb_wr_tlb_y_size = DIV_ROUND_UP(frame_width_coded, 16) * opb_packet_bytes; + u32 dma_opb_wr_tlb_uv_size = DIV_ROUND_UP(frame_width_coded, 16) * opb_packet_bytes; + u32 dma_opb_wr2_tlb_y_size = ALIGN((opb_bpp * opb_size_per_row * frame_height_coded / 8), + DMA_ALIGNMENT) * num_vpp_pipes_enc; + u32 dma_opb_wr2_tlb_uv_size = ALIGN((opb_bpp * opb_size_per_row * frame_height_coded / 8), + DMA_ALIGNMENT) * num_vpp_pipes_enc; + + dma_opb_wr2_tlb_y_size = max(dma_opb_wr2_tlb_y_size, dma_opb_wr_tlb_y_size << 1); + dma_opb_wr2_tlb_uv_size = max(dma_opb_wr2_tlb_uv_size, dma_opb_wr_tlb_uv_size << 1); + + return ALIGN(dma_opb_wr_tlb_y_size + dma_opb_wr_tlb_uv_size + dma_opb_wr2_tlb_y_size + + dma_opb_wr2_tlb_uv_size, DMA_ALIGNMENT); +} + +static u32 hfi_vpu4x_buffer_line_enc(u32 frame_width, u32 frame_height, + bool is_ten_bit, u32 num_vpp_pipes_enc, + u32 lcu_size, u32 standard) +{ + u32 width_in_lcus = (frame_width + lcu_size - 1) / lcu_size; + u32 height_in_lcus = (frame_height + lcu_size - 1) / lcu_size; + u32 frame_width_coded = width_in_lcus * lcu_size; + u32 frame_height_coded = height_in_lcus * lcu_size; + + u32 se_lb_size = size_se_lb(standard, num_vpp_pipes_enc, frame_width_coded, + frame_height_coded); + u32 te_lb_size = size_te_lb(is_ten_bit, num_vpp_pipes_enc, width_in_lcus, + frame_height_coded, frame_width_coded); + u32 fe_lb_size = size_fe_lb(is_ten_bit, standard, num_vpp_pipes_enc, frame_height_coded, + frame_width_coded); + u32 md_lb_size = size_md_lb(standard, frame_width_coded, frame_height_coded, + num_vpp_pipes_enc); + u32 dma_opb_lb_size = size_dma_opb_lb(num_vpp_pipes_enc, frame_width_coded, + frame_height_coded); + u32 dse_lb_size = ALIGN((256 + (16 * (frame_width_coded >> 4))), DMA_ALIGNMENT); + u32 size_vpss_lb_enc = size_vpss_line_buf_vpu33(num_vpp_pipes_enc, frame_width_coded, + frame_height_coded); + + return se_lb_size + te_lb_size + fe_lb_size + md_lb_size + dma_opb_lb_size + + dse_lb_size + size_vpss_lb_enc; +} + +static u32 iris_vpu4x_enc_line_size(struct iris_inst *inst) +{ + u32 num_vpp_pipes = inst->core->iris_platform_data->num_vpp_pipe; + u32 lcu_size = inst->codec == V4L2_PIX_FMT_HEVC ? 32 : 16; + struct v4l2_format *f = inst->fmt_dst; + u32 height = f->fmt.pix_mp.height; + u32 width = f->fmt.pix_mp.width; + + return hfi_vpu4x_buffer_line_enc(width, height, 0, num_vpp_pipes, + lcu_size, inst->codec); +} + static int output_min_count(struct iris_inst *inst) { int output_min_count = 4; @@ -1793,6 +2091,50 @@ u32 iris_vpu33_buf_size(struct iris_inst *inst, enum iris_buffer_type buffer_typ return size; } +u32 iris_vpu4x_buf_size(struct iris_inst *inst, enum iris_buffer_type buffer_type) +{ + const struct iris_vpu_buf_type_handle *buf_type_handle_arr = NULL; + u32 size = 0, buf_type_handle_size = 0, i; + + static const struct iris_vpu_buf_type_handle dec_internal_buf_type_handle[] = { + {BUF_BIN, iris_vpu_dec_bin_size }, + {BUF_COMV, iris_vpu_dec_comv_size }, + {BUF_NON_COMV, iris_vpu_dec_non_comv_size }, + {BUF_LINE, iris_vpu4x_dec_line_size }, + {BUF_PERSIST, iris_vpu4x_dec_persist_size }, + {BUF_DPB, iris_vpu_dec_dpb_size }, + {BUF_SCRATCH_1, iris_vpu_dec_scratch1_size }, + }; + + static const struct iris_vpu_buf_type_handle enc_internal_buf_type_handle[] = { + {BUF_BIN, iris_vpu_enc_bin_size }, + {BUF_COMV, iris_vpu_enc_comv_size }, + {BUF_NON_COMV, iris_vpu_enc_non_comv_size }, + {BUF_LINE, iris_vpu4x_enc_line_size }, + {BUF_ARP, iris_vpu_enc_arp_size }, + {BUF_VPSS, iris_vpu_enc_vpss_size }, + {BUF_SCRATCH_1, iris_vpu_enc_scratch1_size }, + {BUF_SCRATCH_2, iris_vpu_enc_scratch2_size }, + }; + + if (inst->domain == DECODER) { + buf_type_handle_size = ARRAY_SIZE(dec_internal_buf_type_handle); + buf_type_handle_arr = dec_internal_buf_type_handle; + } else if (inst->domain == ENCODER) { + buf_type_handle_size = ARRAY_SIZE(enc_internal_buf_type_handle); + buf_type_handle_arr = enc_internal_buf_type_handle; + } + + for (i = 0; i < buf_type_handle_size; i++) { + if (buf_type_handle_arr[i].type == buffer_type) { + size = buf_type_handle_arr[i].handle(inst); + break; + } + } + + return size; +} + static u32 internal_buffer_count(struct iris_inst *inst, enum iris_buffer_type buffer_type) { diff --git a/drivers/media/platform/qcom/iris/iris_vpu_buffer.h b/drivers/media/platform/qcom/iris/iris_vpu_buffer.h index 13c7199fcf35..12640eb5ed8c 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_buffer.h +++ b/drivers/media/platform/qcom/iris/iris_vpu_buffer.h @@ -48,7 +48,12 @@ struct iris_inst; #define VP9_NUM_PROBABILITY_TABLE_BUF (VP9_NUM_FRAME_INFO_BUF + 4) #define VP9_PROB_TABLE_SIZE (3840) #define VP9_FRAME_INFO_BUF_SIZE (6144) +#define VP9_FRAME_INFO_BUF_SIZE_VPU4X (6400) +#define BUFFER_ALIGNMENT_16_BYTES 16 #define BUFFER_ALIGNMENT_32_BYTES 32 +#define BUFFER_ALIGNMENT_64_BYTES 64 +#define BUFFER_ALIGNMENT_256_BYTES 256 +#define BUFFER_ALIGNMENT_512_BYTES 512 #define CCE_TILE_OFFSET_SIZE ALIGN(32 * 4 * 4, BUFFER_ALIGNMENT_32_BYTES) #define MAX_SUPERFRAME_HEADER_LEN (34) #define MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE 64 @@ -67,6 +72,8 @@ struct iris_inst; #define H265_CABAC_HDR_RATIO_HD_TOT 2 #define H265_CABAC_RES_RATIO_HD_TOT 2 #define SIZE_H265D_VPP_CMD_PER_BUF (256) +#define SIZE_THREE_DIMENSION_USERDATA 768 +#define SIZE_H265D_ARP 9728 #define VPX_DECODER_FRAME_CONCURENCY_LVL (2) #define VPX_DECODER_FRAME_BIN_HDR_BUDGET 1 @@ -77,6 +84,18 @@ struct iris_inst; #define SIZE_H264D_HW_PIC_T (BIT(11)) +#define FE_LFT_CTRL_LINE_NUMBERS 4 +#define FE_LFT_DB_DATA_LINE_NUMBERS 2 +#define FE_LFT_LR_DATA_LINE_NUMBERS 4 +#define FE_TOP_CTRL_LINE_NUMBERS 3 +#define FE_TOP_DATA_LUMA_LINE_NUMBERS 2 +#define FE_TOP_DATA_CHROMA_LINE_NUMBERS 3 +#define FE_SDC_DATA_PER_BLOCK 16 +#define SE_CTRL_DATA_PER_BLOCK 2020 + +#define MAX_PE_NBR_DATA_LCU16_LINE_BUFFER_SIZE 96 +#define MAX_PE_NBR_DATA_LCU32_LINE_BUFFER_SIZE 192 + #define MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE 64 #define MAX_SE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE 16 #define MAX_PE_NBR_DATA_LCU64_LINE_BUFFER_SIZE 384 @@ -113,6 +132,10 @@ struct iris_inst; #define HFI_BUFFER_ARP_ENC 204800 +#define LOG2_16 4 +#define LOG2_32 5 +#define LLB_UNIT_SIZE 16 + #define MAX_WIDTH 4096 #define MAX_HEIGHT 2304 #define NUM_MBS_4K (DIV_ROUND_UP(MAX_WIDTH, 16) * DIV_ROUND_UP(MAX_HEIGHT, 16)) @@ -264,6 +287,7 @@ static inline u32 size_av1d_qp(u32 frame_width, u32 frame_height) u32 iris_vpu_buf_size(struct iris_inst *inst, enum iris_buffer_type buffer_type); u32 iris_vpu33_buf_size(struct iris_inst *inst, enum iris_buffer_type buffer_type); +u32 iris_vpu4x_buf_size(struct iris_inst *inst, enum iris_buffer_type buffer_type); int iris_vpu_buf_count(struct iris_inst *inst, enum iris_buffer_type buffer_type); #endif From 2fc9b85dea50497f2264863d2f398bd1cfa5375b Mon Sep 17 00:00:00 2001 From: Vikash Garodia Date: Wed, 10 Dec 2025 18:06:02 +0530 Subject: [PATCH 271/307] media: iris: Move vpu register defines to common header file Some of vpu4 register defines are common with vpu3x. Move those into the common register defines header. This is done to reuse the defines for vpu4 in subsequent patch which enables the power sequence for vpu4. Reviewed-by: Bryan O'Donoghue Co-developed-by: Vishnu Reddy Signed-off-by: Vishnu Reddy Signed-off-by: Vikash Garodia Reviewed-by: Dikshita Agarwal Co-developed-by: Dmitry Baryshkov Signed-off-by: Dmitry Baryshkov Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_vpu3x.c | 42 ------------- .../platform/qcom/iris/iris_vpu_common.c | 43 ------------- .../qcom/iris/iris_vpu_register_defines.h | 61 +++++++++++++++++++ 3 files changed, 61 insertions(+), 85 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_vpu3x.c b/drivers/media/platform/qcom/iris/iris_vpu3x.c index 339776a0b467..cd53bcda3b3e 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu3x.c +++ b/drivers/media/platform/qcom/iris/iris_vpu3x.c @@ -11,48 +11,6 @@ #include "iris_vpu_common.h" #include "iris_vpu_register_defines.h" -#define WRAPPER_TZ_BASE_OFFS 0x000C0000 -#define AON_BASE_OFFS 0x000E0000 -#define AON_MVP_NOC_RESET 0x0001F000 - -#define WRAPPER_DEBUG_BRIDGE_LPI_CONTROL (WRAPPER_BASE_OFFS + 0x54) -#define WRAPPER_DEBUG_BRIDGE_LPI_STATUS (WRAPPER_BASE_OFFS + 0x58) -#define WRAPPER_IRIS_CPU_NOC_LPI_CONTROL (WRAPPER_BASE_OFFS + 0x5C) -#define REQ_POWER_DOWN_PREP BIT(0) -#define WRAPPER_IRIS_CPU_NOC_LPI_STATUS (WRAPPER_BASE_OFFS + 0x60) -#define NOC_LPI_STATUS_DONE BIT(0) /* Indicates the NOC handshake is complete */ -#define NOC_LPI_STATUS_DENY BIT(1) /* Indicates the NOC handshake is denied */ -#define NOC_LPI_STATUS_ACTIVE BIT(2) /* Indicates the NOC is active */ -#define WRAPPER_CORE_CLOCK_CONFIG (WRAPPER_BASE_OFFS + 0x88) -#define CORE_CLK_RUN 0x0 -/* VPU v3.5 */ -#define WRAPPER_IRIS_VCODEC_VPU_WRAPPER_SPARE_0 (WRAPPER_BASE_OFFS + 0x78) - -#define WRAPPER_TZ_CTL_AXI_CLOCK_CONFIG (WRAPPER_TZ_BASE_OFFS + 0x14) -#define CTL_AXI_CLK_HALT BIT(0) -#define CTL_CLK_HALT BIT(1) - -#define WRAPPER_TZ_QNS4PDXFIFO_RESET (WRAPPER_TZ_BASE_OFFS + 0x18) -#define RESET_HIGH BIT(0) - -#define CPU_CS_AHB_BRIDGE_SYNC_RESET (CPU_CS_BASE_OFFS + 0x160) -#define CORE_BRIDGE_SW_RESET BIT(0) -#define CORE_BRIDGE_HW_RESET_DISABLE BIT(1) - -#define CPU_CS_X2RPMH (CPU_CS_BASE_OFFS + 0x168) -#define MSK_SIGNAL_FROM_TENSILICA BIT(0) -#define MSK_CORE_POWER_ON BIT(1) - -#define AON_WRAPPER_MVP_NOC_RESET_REQ (AON_MVP_NOC_RESET + 0x000) -#define VIDEO_NOC_RESET_REQ (BIT(0) | BIT(1)) - -#define AON_WRAPPER_MVP_NOC_RESET_ACK (AON_MVP_NOC_RESET + 0x004) - -#define VCODEC_SS_IDLE_STATUSN (VCODEC_BASE_OFFS + 0x70) - -#define AON_WRAPPER_MVP_NOC_LPI_CONTROL (AON_BASE_OFFS) -#define AON_WRAPPER_MVP_NOC_LPI_STATUS (AON_BASE_OFFS + 0x4) - #define AON_WRAPPER_MVP_NOC_CORE_SW_RESET (AON_BASE_OFFS + 0x18) #define SW_RESET BIT(0) #define AON_WRAPPER_MVP_NOC_CORE_CLK_CONTROL (AON_BASE_OFFS + 0x20) diff --git a/drivers/media/platform/qcom/iris/iris_vpu_common.c b/drivers/media/platform/qcom/iris/iris_vpu_common.c index fef192a2de48..50242fc6b465 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_common.c +++ b/drivers/media/platform/qcom/iris/iris_vpu_common.c @@ -11,13 +11,6 @@ #include "iris_vpu_common.h" #include "iris_vpu_register_defines.h" -#define WRAPPER_TZ_BASE_OFFS 0x000C0000 -#define AON_BASE_OFFS 0x000E0000 - -#define CPU_IC_BASE_OFFS (CPU_BASE_OFFS) - -#define CPU_CS_A2HSOFTINTCLR (CPU_CS_BASE_OFFS + 0x1C) -#define CLEAR_XTENSA2HOST_INTR BIT(0) #define CTRL_INIT (CPU_CS_BASE_OFFS + 0x48) #define CTRL_STATUS (CPU_CS_BASE_OFFS + 0x4C) @@ -35,42 +28,6 @@ #define UC_REGION_ADDR (CPU_CS_BASE_OFFS + 0x64) #define UC_REGION_SIZE (CPU_CS_BASE_OFFS + 0x68) -#define CPU_CS_H2XSOFTINTEN (CPU_CS_BASE_OFFS + 0x148) -#define HOST2XTENSA_INTR_ENABLE BIT(0) - -#define CPU_CS_X2RPMH (CPU_CS_BASE_OFFS + 0x168) -#define MSK_SIGNAL_FROM_TENSILICA BIT(0) -#define MSK_CORE_POWER_ON BIT(1) - -#define CPU_IC_SOFTINT (CPU_IC_BASE_OFFS + 0x150) -#define CPU_IC_SOFTINT_H2A_SHFT 0x0 - -#define WRAPPER_INTR_STATUS (WRAPPER_BASE_OFFS + 0x0C) -#define WRAPPER_INTR_STATUS_A2HWD_BMSK BIT(3) -#define WRAPPER_INTR_STATUS_A2H_BMSK BIT(2) - -#define WRAPPER_INTR_MASK (WRAPPER_BASE_OFFS + 0x10) -#define WRAPPER_INTR_MASK_A2HWD_BMSK BIT(3) -#define WRAPPER_INTR_MASK_A2HCPU_BMSK BIT(2) - -#define WRAPPER_DEBUG_BRIDGE_LPI_CONTROL (WRAPPER_BASE_OFFS + 0x54) -#define WRAPPER_DEBUG_BRIDGE_LPI_STATUS (WRAPPER_BASE_OFFS + 0x58) -#define WRAPPER_IRIS_CPU_NOC_LPI_CONTROL (WRAPPER_BASE_OFFS + 0x5C) -#define WRAPPER_IRIS_CPU_NOC_LPI_STATUS (WRAPPER_BASE_OFFS + 0x60) - -#define WRAPPER_TZ_CPU_STATUS (WRAPPER_TZ_BASE_OFFS + 0x10) -#define WRAPPER_TZ_CTL_AXI_CLOCK_CONFIG (WRAPPER_TZ_BASE_OFFS + 0x14) -#define CTL_AXI_CLK_HALT BIT(0) -#define CTL_CLK_HALT BIT(1) - -#define WRAPPER_TZ_QNS4PDXFIFO_RESET (WRAPPER_TZ_BASE_OFFS + 0x18) -#define RESET_HIGH BIT(0) - -#define AON_WRAPPER_MVP_NOC_LPI_CONTROL (AON_BASE_OFFS) -#define REQ_POWER_DOWN_PREP BIT(0) - -#define AON_WRAPPER_MVP_NOC_LPI_STATUS (AON_BASE_OFFS + 0x4) - static void iris_vpu_interrupt_init(struct iris_core *core) { u32 mask_val; diff --git a/drivers/media/platform/qcom/iris/iris_vpu_register_defines.h b/drivers/media/platform/qcom/iris/iris_vpu_register_defines.h index fe8a39e5e5a3..72168b9ffa73 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_register_defines.h +++ b/drivers/media/platform/qcom/iris/iris_vpu_register_defines.h @@ -7,11 +7,72 @@ #define __IRIS_VPU_REGISTER_DEFINES_H__ #define VCODEC_BASE_OFFS 0x00000000 +#define AON_MVP_NOC_RESET 0x0001F000 #define CPU_BASE_OFFS 0x000A0000 #define WRAPPER_BASE_OFFS 0x000B0000 +#define WRAPPER_TZ_BASE_OFFS 0x000C0000 +#define AON_BASE_OFFS 0x000E0000 + +#define VCODEC_SS_IDLE_STATUSN (VCODEC_BASE_OFFS + 0x70) + +#define AON_WRAPPER_MVP_NOC_RESET_REQ (AON_MVP_NOC_RESET + 0x000) +#define VIDEO_NOC_RESET_REQ (BIT(0) | BIT(1)) + +#define AON_WRAPPER_MVP_NOC_RESET_ACK (AON_MVP_NOC_RESET + 0x004) #define CPU_CS_BASE_OFFS (CPU_BASE_OFFS) +#define CPU_IC_BASE_OFFS (CPU_BASE_OFFS) +#define CPU_CS_A2HSOFTINTCLR (CPU_CS_BASE_OFFS + 0x1C) +#define CLEAR_XTENSA2HOST_INTR BIT(0) + +#define CPU_CS_H2XSOFTINTEN (CPU_CS_BASE_OFFS + 0x148) +#define HOST2XTENSA_INTR_ENABLE BIT(0) + +#define CPU_IC_SOFTINT (CPU_IC_BASE_OFFS + 0x150) +#define CPU_IC_SOFTINT_H2A_SHFT 0x0 + +#define CPU_CS_AHB_BRIDGE_SYNC_RESET (CPU_CS_BASE_OFFS + 0x160) +#define CORE_BRIDGE_SW_RESET BIT(0) +#define CORE_BRIDGE_HW_RESET_DISABLE BIT(1) + +#define CPU_CS_X2RPMH (CPU_CS_BASE_OFFS + 0x168) +#define MSK_SIGNAL_FROM_TENSILICA BIT(0) +#define MSK_CORE_POWER_ON BIT(1) + +#define WRAPPER_INTR_STATUS (WRAPPER_BASE_OFFS + 0x0C) +#define WRAPPER_INTR_STATUS_A2HWD_BMSK BIT(3) +#define WRAPPER_INTR_STATUS_A2H_BMSK BIT(2) + +#define WRAPPER_INTR_MASK (WRAPPER_BASE_OFFS + 0x10) +#define WRAPPER_INTR_MASK_A2HWD_BMSK BIT(3) +#define WRAPPER_INTR_MASK_A2HCPU_BMSK BIT(2) + +#define WRAPPER_DEBUG_BRIDGE_LPI_CONTROL (WRAPPER_BASE_OFFS + 0x54) +#define WRAPPER_DEBUG_BRIDGE_LPI_STATUS (WRAPPER_BASE_OFFS + 0x58) +#define WRAPPER_IRIS_CPU_NOC_LPI_CONTROL (WRAPPER_BASE_OFFS + 0x5C) +#define REQ_POWER_DOWN_PREP BIT(0) + +#define WRAPPER_IRIS_CPU_NOC_LPI_STATUS (WRAPPER_BASE_OFFS + 0x60) +#define NOC_LPI_STATUS_DONE BIT(0) /* Indicates the NOC handshake is complete */ +#define NOC_LPI_STATUS_DENY BIT(1) /* Indicates the NOC handshake is denied */ +#define NOC_LPI_STATUS_ACTIVE BIT(2) /* Indicates the NOC is active */ + +#define WRAPPER_IRIS_VCODEC_VPU_WRAPPER_SPARE_0 (WRAPPER_BASE_OFFS + 0x78) #define WRAPPER_CORE_POWER_STATUS (WRAPPER_BASE_OFFS + 0x80) +#define WRAPPER_CORE_CLOCK_CONFIG (WRAPPER_BASE_OFFS + 0x88) +#define CORE_CLK_RUN 0x0 + +#define WRAPPER_TZ_CPU_STATUS (WRAPPER_TZ_BASE_OFFS + 0x10) + +#define WRAPPER_TZ_CTL_AXI_CLOCK_CONFIG (WRAPPER_TZ_BASE_OFFS + 0x14) +#define CTL_AXI_CLK_HALT BIT(0) +#define CTL_CLK_HALT BIT(1) + +#define WRAPPER_TZ_QNS4PDXFIFO_RESET (WRAPPER_TZ_BASE_OFFS + 0x18) +#define RESET_HIGH BIT(0) + +#define AON_WRAPPER_MVP_NOC_LPI_CONTROL (AON_BASE_OFFS) +#define AON_WRAPPER_MVP_NOC_LPI_STATUS (AON_BASE_OFFS + 0x4) #endif From 82b96a8d8d6ebedcda8ef2d051ddd0f43d8eee14 Mon Sep 17 00:00:00 2001 From: Vikash Garodia Date: Wed, 10 Dec 2025 18:06:03 +0530 Subject: [PATCH 272/307] media: iris: Move vpu35 specific api to common to use for vpu4 Some of the sequence and calculations for vpu4 is identical to vpu35, namely power sequence for vpu controller and the clock frequency calculation. Move those to common file that can be shared for both vpu35 and vpu4. This patch prepares for power sequence for vpu4 which is added in subsequent patch. Reviewed-by: Bryan O'Donoghue Co-developed-by: Vishnu Reddy Signed-off-by: Vishnu Reddy Signed-off-by: Vikash Garodia Reviewed-by: Dikshita Agarwal Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_vpu3x.c | 157 +----------------- .../platform/qcom/iris/iris_vpu_common.c | 141 ++++++++++++++++ .../platform/qcom/iris/iris_vpu_common.h | 4 + 3 files changed, 151 insertions(+), 151 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_vpu3x.c b/drivers/media/platform/qcom/iris/iris_vpu3x.c index cd53bcda3b3e..fe4423b951b1 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu3x.c +++ b/drivers/media/platform/qcom/iris/iris_vpu3x.c @@ -16,8 +16,6 @@ #define AON_WRAPPER_MVP_NOC_CORE_CLK_CONTROL (AON_BASE_OFFS + 0x20) #define NOC_HALT BIT(0) #define AON_WRAPPER_SPARE (AON_BASE_OFFS + 0x28) -#define AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL (AON_BASE_OFFS + 0x2C) -#define AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_STATUS (AON_BASE_OFFS + 0x30) static bool iris_vpu3x_hw_power_collapsed(struct iris_core *core) { @@ -262,155 +260,12 @@ static void iris_vpu35_power_off_hw(struct iris_core *core) iris_disable_unprepare_clock(core, IRIS_AXI_CLK); } -static int iris_vpu35_power_off_controller(struct iris_core *core) -{ - u32 clk_rst_tbl_size = core->iris_platform_data->clk_rst_tbl_size; - unsigned int count = 0; - u32 val = 0; - bool handshake_done, handshake_busy; - int ret; - - writel(MSK_SIGNAL_FROM_TENSILICA | MSK_CORE_POWER_ON, core->reg_base + CPU_CS_X2RPMH); - - writel(REQ_POWER_DOWN_PREP, core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_CONTROL); - - ret = readl_poll_timeout(core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_STATUS, - val, val & BIT(0), 200, 2000); - if (ret) - goto disable_power; - - writel(0, core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_CONTROL); - - /* Retry up to 1000 times as recommended by hardware documentation */ - do { - /* set MNoC to low power */ - writel(REQ_POWER_DOWN_PREP, core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL); - - udelay(15); - - val = readl(core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_STATUS); - - handshake_done = val & NOC_LPI_STATUS_DONE; - handshake_busy = val & (NOC_LPI_STATUS_DENY | NOC_LPI_STATUS_ACTIVE); - - if (handshake_done || !handshake_busy) - break; - - writel(0, core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL); - - udelay(15); - - } while (++count < 1000); - - if (!handshake_done && handshake_busy) - dev_err(core->dev, "LPI handshake timeout\n"); - - ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_STATUS, - val, val & BIT(0), 200, 2000); - if (ret) - goto disable_power; - - writel(0, core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL); - - writel(0, core->reg_base + WRAPPER_DEBUG_BRIDGE_LPI_CONTROL); - - ret = readl_poll_timeout(core->reg_base + WRAPPER_DEBUG_BRIDGE_LPI_STATUS, - val, val == 0, 200, 2000); - if (ret) - goto disable_power; - -disable_power: - iris_disable_unprepare_clock(core, IRIS_CTRL_CLK); - iris_disable_unprepare_clock(core, IRIS_CTRL_FREERUN_CLK); - iris_disable_unprepare_clock(core, IRIS_AXI1_CLK); - - iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]); - - reset_control_bulk_reset(clk_rst_tbl_size, core->resets); - - return 0; -} - -static int iris_vpu35_power_on_controller(struct iris_core *core) -{ - int ret; - - ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]); - if (ret) - return ret; - - ret = iris_prepare_enable_clock(core, IRIS_AXI1_CLK); - if (ret) - goto err_disable_power; - - ret = iris_prepare_enable_clock(core, IRIS_CTRL_FREERUN_CLK); - if (ret) - goto err_disable_axi1_clk; - - ret = iris_prepare_enable_clock(core, IRIS_CTRL_CLK); - if (ret) - goto err_disable_ctrl_free_clk; - - return 0; - -err_disable_ctrl_free_clk: - iris_disable_unprepare_clock(core, IRIS_CTRL_FREERUN_CLK); -err_disable_axi1_clk: - iris_disable_unprepare_clock(core, IRIS_AXI1_CLK); -err_disable_power: - iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]); - - return ret; -} - -static void iris_vpu35_program_bootup_registers(struct iris_core *core) -{ - writel(0x1, core->reg_base + WRAPPER_IRIS_VCODEC_VPU_WRAPPER_SPARE_0); -} - -static u64 iris_vpu3x_calculate_frequency(struct iris_inst *inst, size_t data_size) -{ - struct platform_inst_caps *caps = inst->core->iris_platform_data->inst_caps; - struct v4l2_format *inp_f = inst->fmt_src; - u32 height, width, mbs_per_second, mbpf; - u64 fw_cycles, fw_vpp_cycles; - u64 vsp_cycles, vpp_cycles; - u32 fps = DEFAULT_FPS; - - width = max(inp_f->fmt.pix_mp.width, inst->crop.width); - height = max(inp_f->fmt.pix_mp.height, inst->crop.height); - - mbpf = NUM_MBS_PER_FRAME(height, width); - mbs_per_second = mbpf * fps; - - fw_cycles = fps * caps->mb_cycles_fw; - fw_vpp_cycles = fps * caps->mb_cycles_fw_vpp; - - vpp_cycles = mult_frac(mbs_per_second, caps->mb_cycles_vpp, (u32)inst->fw_caps[PIPE].value); - /* 21 / 20 is minimum overhead factor */ - vpp_cycles += max(div_u64(vpp_cycles, 20), fw_vpp_cycles); - - /* 1.059 is multi-pipe overhead */ - if (inst->fw_caps[PIPE].value > 1) - vpp_cycles += div_u64(vpp_cycles * 59, 1000); - - vsp_cycles = fps * data_size * 8; - vsp_cycles = div_u64(vsp_cycles, 2); - /* VSP FW overhead 1.05 */ - vsp_cycles = div_u64(vsp_cycles * 21, 20); - - if (inst->fw_caps[STAGE].value == STAGE_1) - vsp_cycles = vsp_cycles * 3; - - return max3(vpp_cycles, vsp_cycles, fw_cycles); -} - const struct vpu_ops iris_vpu3_ops = { .power_off_hw = iris_vpu3_power_off_hardware, .power_on_hw = iris_vpu_power_on_hw, .power_off_controller = iris_vpu_power_off_controller, .power_on_controller = iris_vpu_power_on_controller, - .calc_freq = iris_vpu3x_calculate_frequency, + .calc_freq = iris_vpu3x_vpu4x_calculate_frequency, }; const struct vpu_ops iris_vpu33_ops = { @@ -418,14 +273,14 @@ const struct vpu_ops iris_vpu33_ops = { .power_on_hw = iris_vpu_power_on_hw, .power_off_controller = iris_vpu33_power_off_controller, .power_on_controller = iris_vpu_power_on_controller, - .calc_freq = iris_vpu3x_calculate_frequency, + .calc_freq = iris_vpu3x_vpu4x_calculate_frequency, }; const struct vpu_ops iris_vpu35_ops = { .power_off_hw = iris_vpu35_power_off_hw, .power_on_hw = iris_vpu35_power_on_hw, - .power_off_controller = iris_vpu35_power_off_controller, - .power_on_controller = iris_vpu35_power_on_controller, - .program_bootup_registers = iris_vpu35_program_bootup_registers, - .calc_freq = iris_vpu3x_calculate_frequency, + .power_off_controller = iris_vpu35_vpu4x_power_off_controller, + .power_on_controller = iris_vpu35_vpu4x_power_on_controller, + .program_bootup_registers = iris_vpu35_vpu4x_program_bootup_registers, + .calc_freq = iris_vpu3x_vpu4x_calculate_frequency, }; diff --git a/drivers/media/platform/qcom/iris/iris_vpu_common.c b/drivers/media/platform/qcom/iris/iris_vpu_common.c index 50242fc6b465..548e5f1727fd 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_common.c +++ b/drivers/media/platform/qcom/iris/iris_vpu_common.c @@ -8,9 +8,12 @@ #include #include "iris_core.h" +#include "iris_instance.h" #include "iris_vpu_common.h" #include "iris_vpu_register_defines.h" +#define AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL (AON_BASE_OFFS + 0x2C) +#define AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_STATUS (AON_BASE_OFFS + 0x30) #define CTRL_INIT (CPU_CS_BASE_OFFS + 0x48) #define CTRL_STATUS (CPU_CS_BASE_OFFS + 0x4C) @@ -305,6 +308,144 @@ err_disable_power: return ret; } +int iris_vpu35_vpu4x_power_off_controller(struct iris_core *core) +{ + u32 clk_rst_tbl_size = core->iris_platform_data->clk_rst_tbl_size; + bool handshake_done, handshake_busy; + u32 count = 0, val = 0; + int ret; + + writel(MSK_SIGNAL_FROM_TENSILICA | MSK_CORE_POWER_ON, core->reg_base + CPU_CS_X2RPMH); + + writel(REQ_POWER_DOWN_PREP, core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_CONTROL); + + ret = readl_poll_timeout(core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_STATUS, + val, val & BIT(0), 200, 2000); + if (ret) + goto disable_power; + + writel(0, core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_CONTROL); + + /* Retry up to 1000 times as recommended by hardware documentation */ + do { + /* set MNoC to low power */ + writel(REQ_POWER_DOWN_PREP, core->reg_base + + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL); + usleep_range(10, 20); + val = readl(core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_STATUS); + + handshake_done = val & NOC_LPI_STATUS_DONE; + handshake_busy = val & (NOC_LPI_STATUS_DENY | NOC_LPI_STATUS_ACTIVE); + + if (handshake_done || !handshake_busy) + break; + + writel(0, core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL); + usleep_range(10, 20); + + } while (++count < 1000); + + if (!handshake_done && handshake_busy) + dev_err(core->dev, "LPI handshake timeout\n"); + + ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_STATUS, + val, val & BIT(0), 200, 2000); + if (ret) + goto disable_power; + + writel(0, core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL); + + writel(0, core->reg_base + WRAPPER_DEBUG_BRIDGE_LPI_CONTROL); + + readl_poll_timeout(core->reg_base + WRAPPER_DEBUG_BRIDGE_LPI_STATUS, + val, val == 0, 200, 2000); + +disable_power: + iris_disable_unprepare_clock(core, IRIS_CTRL_CLK); + iris_disable_unprepare_clock(core, IRIS_CTRL_FREERUN_CLK); + iris_disable_unprepare_clock(core, IRIS_AXI1_CLK); + + iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]); + + reset_control_bulk_reset(clk_rst_tbl_size, core->resets); + + return 0; +} + +int iris_vpu35_vpu4x_power_on_controller(struct iris_core *core) +{ + int ret; + + ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]); + if (ret) + return ret; + + ret = iris_prepare_enable_clock(core, IRIS_AXI1_CLK); + if (ret) + goto err_disable_power; + + ret = iris_prepare_enable_clock(core, IRIS_CTRL_FREERUN_CLK); + if (ret) + goto err_disable_axi1_clk; + + ret = iris_prepare_enable_clock(core, IRIS_CTRL_CLK); + if (ret) + goto err_disable_ctrl_free_clk; + + return 0; + +err_disable_ctrl_free_clk: + iris_disable_unprepare_clock(core, IRIS_CTRL_FREERUN_CLK); +err_disable_axi1_clk: + iris_disable_unprepare_clock(core, IRIS_AXI1_CLK); +err_disable_power: + iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]); + + return ret; +} + +void iris_vpu35_vpu4x_program_bootup_registers(struct iris_core *core) +{ + writel(0x1, core->reg_base + WRAPPER_IRIS_VCODEC_VPU_WRAPPER_SPARE_0); +} + +u64 iris_vpu3x_vpu4x_calculate_frequency(struct iris_inst *inst, size_t data_size) +{ + struct platform_inst_caps *caps = inst->core->iris_platform_data->inst_caps; + struct v4l2_format *inp_f = inst->fmt_src; + u32 height, width, mbs_per_second, mbpf; + u64 fw_cycles, fw_vpp_cycles; + u64 vsp_cycles, vpp_cycles; + u32 fps = DEFAULT_FPS; + + width = max(inp_f->fmt.pix_mp.width, inst->crop.width); + height = max(inp_f->fmt.pix_mp.height, inst->crop.height); + + mbpf = NUM_MBS_PER_FRAME(height, width); + mbs_per_second = mbpf * fps; + + fw_cycles = fps * caps->mb_cycles_fw; + fw_vpp_cycles = fps * caps->mb_cycles_fw_vpp; + + vpp_cycles = mult_frac(mbs_per_second, caps->mb_cycles_vpp, (u32)inst->fw_caps[PIPE].value); + /* 21 / 20 is minimum overhead factor */ + vpp_cycles += max(div_u64(vpp_cycles, 20), fw_vpp_cycles); + + /* 1.059 is multi-pipe overhead */ + if (inst->fw_caps[PIPE].value > 1) + vpp_cycles += div_u64(vpp_cycles * 59, 1000); + + vsp_cycles = fps * data_size * 8; + vsp_cycles = div_u64(vsp_cycles, 2); + /* VSP FW overhead 1.05 */ + vsp_cycles = div_u64(vsp_cycles * 21, 20); + + if (inst->fw_caps[STAGE].value == STAGE_1) + vsp_cycles = vsp_cycles * 3; + + return max3(vpp_cycles, vsp_cycles, fw_cycles); +} + int iris_vpu_power_on(struct iris_core *core) { u32 freq; diff --git a/drivers/media/platform/qcom/iris/iris_vpu_common.h b/drivers/media/platform/qcom/iris/iris_vpu_common.h index d636e287457a..7cf4304604cc 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_common.h +++ b/drivers/media/platform/qcom/iris/iris_vpu_common.h @@ -33,5 +33,9 @@ int iris_vpu_power_on(struct iris_core *core); int iris_vpu_power_off_controller(struct iris_core *core); void iris_vpu_power_off_hw(struct iris_core *core); void iris_vpu_power_off(struct iris_core *core); +int iris_vpu35_vpu4x_power_off_controller(struct iris_core *core); +int iris_vpu35_vpu4x_power_on_controller(struct iris_core *core); +void iris_vpu35_vpu4x_program_bootup_registers(struct iris_core *core); +u64 iris_vpu3x_vpu4x_calculate_frequency(struct iris_inst *inst, size_t data_size); #endif From dde659d37036c6edf5eeef81274b43fe12dfad53 Mon Sep 17 00:00:00 2001 From: Vikash Garodia Date: Wed, 10 Dec 2025 18:06:04 +0530 Subject: [PATCH 273/307] media: iris: Introduce vpu ops for vpu4 with necessary hooks Add power sequence for vpu4 by reusing from previous generation wherever possible. Hook up vpu4 op with vpu4 specific implemtation or resue from earlier generation wherever feasible, like clock calculation in this case. Co-developed-by: Vishnu Reddy Signed-off-by: Vishnu Reddy Signed-off-by: Vikash Garodia Reviewed-by: Dikshita Agarwal Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/Makefile | 1 + .../platform/qcom/iris/iris_platform_common.h | 7 + drivers/media/platform/qcom/iris/iris_vpu4x.c | 369 ++++++++++++++++++ .../platform/qcom/iris/iris_vpu_common.h | 1 + 4 files changed, 378 insertions(+) create mode 100644 drivers/media/platform/qcom/iris/iris_vpu4x.c diff --git a/drivers/media/platform/qcom/iris/Makefile b/drivers/media/platform/qcom/iris/Makefile index fad3be044e5f..2abbd3aeb4af 100644 --- a/drivers/media/platform/qcom/iris/Makefile +++ b/drivers/media/platform/qcom/iris/Makefile @@ -22,6 +22,7 @@ qcom-iris-objs += iris_buffer.o \ iris_venc.o \ iris_vpu2.o \ iris_vpu3x.o \ + iris_vpu4x.o \ iris_vpu_buffer.o \ iris_vpu_common.o \ diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index f215abcd36eb..5a489917580e 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -57,6 +57,10 @@ enum platform_clk_type { IRIS_AXI1_CLK, IRIS_CTRL_FREERUN_CLK, IRIS_HW_FREERUN_CLK, + IRIS_BSE_HW_CLK, + IRIS_VPP0_HW_CLK, + IRIS_VPP1_HW_CLK, + IRIS_APV_HW_CLK, }; struct platform_clk_data { @@ -203,6 +207,9 @@ struct icc_vote_data { enum platform_pm_domain_type { IRIS_CTRL_POWER_DOMAIN, IRIS_HW_POWER_DOMAIN, + IRIS_VPP0_HW_POWER_DOMAIN, + IRIS_VPP1_HW_POWER_DOMAIN, + IRIS_APV_HW_POWER_DOMAIN, }; struct iris_platform_data { diff --git a/drivers/media/platform/qcom/iris/iris_vpu4x.c b/drivers/media/platform/qcom/iris/iris_vpu4x.c new file mode 100644 index 000000000000..a8db02ce5c5e --- /dev/null +++ b/drivers/media/platform/qcom/iris/iris_vpu4x.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include + +#include "iris_instance.h" +#include "iris_vpu_common.h" +#include "iris_vpu_register_defines.h" + +#define AON_WRAPPER_MVP_NOC_RESET_SYNCRST (AON_MVP_NOC_RESET + 0x08) +#define CPU_CS_APV_BRIDGE_SYNC_RESET (CPU_BASE_OFFS + 0x174) +#define MVP_NOC_RESET_REQ_MASK 0x70103 +#define VPU_IDLE_BITS 0x7103 +#define WRAPPER_EFUSE_MONITOR (WRAPPER_BASE_OFFS + 0x08) + +#define APV_CLK_HALT BIT(1) +#define CORE_CLK_HALT BIT(0) +#define CORE_PWR_ON BIT(1) +#define DISABLE_VIDEO_APV_BIT BIT(27) +#define DISABLE_VIDEO_VPP1_BIT BIT(28) +#define DISABLE_VIDEO_VPP0_BIT BIT(29) + +static int iris_vpu4x_genpd_set_hwmode(struct iris_core *core, bool hw_mode, u32 efuse_value) +{ + int ret; + + ret = dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN], hw_mode); + if (ret) + return ret; + + if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) { + ret = dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs + [IRIS_VPP0_HW_POWER_DOMAIN], hw_mode); + if (ret) + goto restore_hw_domain_mode; + } + + if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT)) { + ret = dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs + [IRIS_VPP1_HW_POWER_DOMAIN], hw_mode); + if (ret) + goto restore_vpp0_domain_mode; + } + + if (!(efuse_value & DISABLE_VIDEO_APV_BIT)) { + ret = dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs + [IRIS_APV_HW_POWER_DOMAIN], hw_mode); + if (ret) + goto restore_vpp1_domain_mode; + } + + return 0; + +restore_vpp1_domain_mode: + if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT)) + dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs[IRIS_VPP1_HW_POWER_DOMAIN], + !hw_mode); +restore_vpp0_domain_mode: + if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) + dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs[IRIS_VPP0_HW_POWER_DOMAIN], + !hw_mode); +restore_hw_domain_mode: + dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN], !hw_mode); + + return ret; +} + +static int iris_vpu4x_power_on_apv(struct iris_core *core) +{ + int ret; + + ret = iris_enable_power_domains(core, + core->pmdomain_tbl->pd_devs[IRIS_APV_HW_POWER_DOMAIN]); + if (ret) + return ret; + + ret = iris_prepare_enable_clock(core, IRIS_APV_HW_CLK); + if (ret) + goto disable_apv_hw_power_domain; + + return 0; + +disable_apv_hw_power_domain: + iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_APV_HW_POWER_DOMAIN]); + + return ret; +} + +static void iris_vpu4x_power_off_apv(struct iris_core *core) +{ + bool handshake_done, handshake_busy; + u32 value, count = 0; + int ret; + + value = readl(core->reg_base + WRAPPER_CORE_CLOCK_CONFIG); + + if (value & APV_CLK_HALT) + writel(0x0, core->reg_base + WRAPPER_CORE_CLOCK_CONFIG); + + do { + writel(REQ_POWER_DOWN_PREP, core->reg_base + AON_WRAPPER_MVP_NOC_LPI_CONTROL); + usleep_range(10, 20); + value = readl(core->reg_base + AON_WRAPPER_MVP_NOC_LPI_STATUS); + + handshake_done = value & NOC_LPI_STATUS_DONE; + handshake_busy = value & (NOC_LPI_STATUS_DENY | NOC_LPI_STATUS_ACTIVE); + + if (handshake_done || !handshake_busy) + break; + + writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_LPI_CONTROL); + usleep_range(10, 20); + + } while (++count < 1000); + + if (!handshake_done && handshake_busy) + dev_err(core->dev, "LPI handshake timeout\n"); + + writel(0x080200, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_REQ); + ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_NOC_RESET_ACK, + value, value & 0x080200, 200, 2000); + if (ret) + goto disable_clocks_and_power; + + writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_SYNCRST); + writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_REQ); + ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_NOC_RESET_ACK, + value, value == 0x0, 200, 2000); + if (ret) + goto disable_clocks_and_power; + + writel(CORE_BRIDGE_SW_RESET | CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + + CPU_CS_APV_BRIDGE_SYNC_RESET); + writel(CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + CPU_CS_APV_BRIDGE_SYNC_RESET); + writel(0x0, core->reg_base + CPU_CS_APV_BRIDGE_SYNC_RESET); + +disable_clocks_and_power: + iris_disable_unprepare_clock(core, IRIS_APV_HW_CLK); + iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_APV_HW_POWER_DOMAIN]); +} + +static void iris_vpu4x_ahb_sync_reset_apv(struct iris_core *core) +{ + writel(CORE_BRIDGE_SW_RESET | CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + + CPU_CS_APV_BRIDGE_SYNC_RESET); + writel(CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + CPU_CS_APV_BRIDGE_SYNC_RESET); + writel(0x0, core->reg_base + CPU_CS_APV_BRIDGE_SYNC_RESET); +} + +static void iris_vpu4x_ahb_sync_reset_hardware(struct iris_core *core) +{ + writel(CORE_BRIDGE_SW_RESET | CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + + CPU_CS_AHB_BRIDGE_SYNC_RESET); + writel(CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + CPU_CS_AHB_BRIDGE_SYNC_RESET); + writel(0x0, core->reg_base + CPU_CS_AHB_BRIDGE_SYNC_RESET); +} + +static int iris_vpu4x_enable_hardware_clocks(struct iris_core *core, u32 efuse_value) +{ + int ret; + + ret = iris_prepare_enable_clock(core, IRIS_AXI_CLK); + if (ret) + return ret; + + ret = iris_prepare_enable_clock(core, IRIS_HW_FREERUN_CLK); + if (ret) + goto disable_axi_clock; + + ret = iris_prepare_enable_clock(core, IRIS_HW_CLK); + if (ret) + goto disable_hw_free_run_clock; + + ret = iris_prepare_enable_clock(core, IRIS_BSE_HW_CLK); + if (ret) + goto disable_hw_clock; + + if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) { + ret = iris_prepare_enable_clock(core, IRIS_VPP0_HW_CLK); + if (ret) + goto disable_bse_hw_clock; + } + + if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT)) { + ret = iris_prepare_enable_clock(core, IRIS_VPP1_HW_CLK); + if (ret) + goto disable_vpp0_hw_clock; + } + + return 0; + +disable_vpp0_hw_clock: + if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) + iris_disable_unprepare_clock(core, IRIS_VPP0_HW_CLK); +disable_bse_hw_clock: + iris_disable_unprepare_clock(core, IRIS_BSE_HW_CLK); +disable_hw_clock: + iris_disable_unprepare_clock(core, IRIS_HW_CLK); +disable_hw_free_run_clock: + iris_disable_unprepare_clock(core, IRIS_HW_FREERUN_CLK); +disable_axi_clock: + iris_disable_unprepare_clock(core, IRIS_AXI_CLK); + + return ret; +} + +static void iris_vpu4x_disable_hardware_clocks(struct iris_core *core, u32 efuse_value) +{ + if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT)) + iris_disable_unprepare_clock(core, IRIS_VPP1_HW_CLK); + + if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) + iris_disable_unprepare_clock(core, IRIS_VPP0_HW_CLK); + + iris_disable_unprepare_clock(core, IRIS_BSE_HW_CLK); + iris_disable_unprepare_clock(core, IRIS_HW_CLK); + iris_disable_unprepare_clock(core, IRIS_HW_FREERUN_CLK); + iris_disable_unprepare_clock(core, IRIS_AXI_CLK); +} + +static int iris_vpu4x_power_on_hardware(struct iris_core *core) +{ + u32 efuse_value = readl(core->reg_base + WRAPPER_EFUSE_MONITOR); + int ret; + + ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN]); + if (ret) + return ret; + + if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) { + ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs + [IRIS_VPP0_HW_POWER_DOMAIN]); + if (ret) + goto disable_hw_power_domain; + } + + if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT)) { + ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs + [IRIS_VPP1_HW_POWER_DOMAIN]); + if (ret) + goto disable_vpp0_power_domain; + } + + ret = iris_vpu4x_enable_hardware_clocks(core, efuse_value); + if (ret) + goto disable_vpp1_power_domain; + + if (!(efuse_value & DISABLE_VIDEO_APV_BIT)) { + ret = iris_vpu4x_power_on_apv(core); + if (ret) + goto disable_hw_clocks; + + iris_vpu4x_ahb_sync_reset_apv(core); + } + + iris_vpu4x_ahb_sync_reset_hardware(core); + + ret = iris_vpu4x_genpd_set_hwmode(core, true, efuse_value); + if (ret) + goto disable_apv_power_domain; + + return 0; + +disable_apv_power_domain: + if (!(efuse_value & DISABLE_VIDEO_APV_BIT)) + iris_vpu4x_power_off_apv(core); +disable_hw_clocks: + iris_vpu4x_disable_hardware_clocks(core, efuse_value); +disable_vpp1_power_domain: + if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT)) + iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs + [IRIS_VPP1_HW_POWER_DOMAIN]); +disable_vpp0_power_domain: + if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) + iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs + [IRIS_VPP0_HW_POWER_DOMAIN]); +disable_hw_power_domain: + iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN]); + + return ret; +} + +static void iris_vpu4x_power_off_hardware(struct iris_core *core) +{ + u32 efuse_value = readl(core->reg_base + WRAPPER_EFUSE_MONITOR); + bool handshake_done, handshake_busy; + u32 value, count = 0; + int ret; + + iris_vpu4x_genpd_set_hwmode(core, false, efuse_value); + + if (!(efuse_value & DISABLE_VIDEO_APV_BIT)) + iris_vpu4x_power_off_apv(core); + + value = readl(core->reg_base + WRAPPER_CORE_POWER_STATUS); + + if (!(value & CORE_PWR_ON)) + goto disable_clocks_and_power; + + value = readl(core->reg_base + WRAPPER_CORE_CLOCK_CONFIG); + + if (value & CORE_CLK_HALT) + writel(0x0, core->reg_base + WRAPPER_CORE_CLOCK_CONFIG); + + readl_poll_timeout(core->reg_base + VCODEC_SS_IDLE_STATUSN, value, + value & VPU_IDLE_BITS, 2000, 20000); + + do { + writel(REQ_POWER_DOWN_PREP, core->reg_base + AON_WRAPPER_MVP_NOC_LPI_CONTROL); + usleep_range(10, 20); + value = readl(core->reg_base + AON_WRAPPER_MVP_NOC_LPI_STATUS); + + handshake_done = value & NOC_LPI_STATUS_DONE; + handshake_busy = value & (NOC_LPI_STATUS_DENY | NOC_LPI_STATUS_ACTIVE); + + if (handshake_done || !handshake_busy) + break; + + writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_LPI_CONTROL); + usleep_range(10, 20); + + } while (++count < 1000); + + if (!handshake_done && handshake_busy) + dev_err(core->dev, "LPI handshake timeout\n"); + + writel(MVP_NOC_RESET_REQ_MASK, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_REQ); + ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_NOC_RESET_ACK, + value, value & MVP_NOC_RESET_REQ_MASK, 200, 2000); + if (ret) + goto disable_clocks_and_power; + + writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_SYNCRST); + writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_REQ); + ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_NOC_RESET_ACK, + value, value == 0x0, 200, 2000); + if (ret) + goto disable_clocks_and_power; + + writel(CORE_BRIDGE_SW_RESET | CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + + CPU_CS_AHB_BRIDGE_SYNC_RESET); + writel(CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + CPU_CS_AHB_BRIDGE_SYNC_RESET); + writel(0x0, core->reg_base + CPU_CS_AHB_BRIDGE_SYNC_RESET); + +disable_clocks_and_power: + iris_vpu4x_disable_hardware_clocks(core, efuse_value); + + if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT)) + iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs + [IRIS_VPP1_HW_POWER_DOMAIN]); + + if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) + iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs + [IRIS_VPP0_HW_POWER_DOMAIN]); + + iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN]); +} + +const struct vpu_ops iris_vpu4x_ops = { + .power_off_hw = iris_vpu4x_power_off_hardware, + .power_on_hw = iris_vpu4x_power_on_hardware, + .power_off_controller = iris_vpu35_vpu4x_power_off_controller, + .power_on_controller = iris_vpu35_vpu4x_power_on_controller, + .program_bootup_registers = iris_vpu35_vpu4x_program_bootup_registers, + .calc_freq = iris_vpu3x_vpu4x_calculate_frequency, +}; diff --git a/drivers/media/platform/qcom/iris/iris_vpu_common.h b/drivers/media/platform/qcom/iris/iris_vpu_common.h index 7cf4304604cc..f6dffc613b82 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_common.h +++ b/drivers/media/platform/qcom/iris/iris_vpu_common.h @@ -12,6 +12,7 @@ extern const struct vpu_ops iris_vpu2_ops; extern const struct vpu_ops iris_vpu3_ops; extern const struct vpu_ops iris_vpu33_ops; extern const struct vpu_ops iris_vpu35_ops; +extern const struct vpu_ops iris_vpu4x_ops; struct vpu_ops { void (*power_off_hw)(struct iris_core *core); From ae8694393e13f563209fad681e5bc1b99d558646 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 3 Nov 2025 19:45:54 +0000 Subject: [PATCH 274/307] media: dt-bindings: media: renesas,fcp: Allow three clocks for RZ/V2N SoC Update the FCP DT schema to permit three clock inputs for the RZ/V2N SoC. The FCP block on this SoC requires three separate clocks, unlike other variants which use only one. Fixes: f42eddf44fbf ("media: dt-bindings: media: renesas,fcp: Document RZ/V2N SoC") Signed-off-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Acked-by: Conor Dooley Link: https://patch.msgid.link/20251103194554.54313-1-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- Documentation/devicetree/bindings/media/renesas,fcp.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/media/renesas,fcp.yaml b/Documentation/devicetree/bindings/media/renesas,fcp.yaml index cf92dfe69637..b5eff6fec8a9 100644 --- a/Documentation/devicetree/bindings/media/renesas,fcp.yaml +++ b/Documentation/devicetree/bindings/media/renesas,fcp.yaml @@ -77,6 +77,7 @@ allOf: - renesas,r9a07g043u-fcpvd - renesas,r9a07g044-fcpvd - renesas,r9a07g054-fcpvd + - renesas,r9a09g056-fcpvd - renesas,r9a09g057-fcpvd then: properties: From 23e1bfeff47658698673a7211dec0ced2a1a0a00 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 3 Nov 2025 01:44:38 +0200 Subject: [PATCH 275/307] media: imx8-isi: Drop unneeded module alias The imx8-isi driver has a module alias named "ISI". This is not required, as there is no reason to load this module through an alias: the device is probed through OF, and the module has never been named "ISI". Drop the alias. Suggested-by: Johan Hovold Reviewed-by: Johan Hovold Reviewed-by: Frank Li Link: https://patch.msgid.link/20251102234438.8022-1-laurent.pinchart@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c index c3d411ddf492..0f000582a1de 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c @@ -575,7 +575,6 @@ static struct platform_driver mxc_isi_driver = { }; module_platform_driver(mxc_isi_driver); -MODULE_ALIAS("ISI"); MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_DESCRIPTION("IMX8 Image Sensing Interface driver"); MODULE_LICENSE("GPL"); From bb0365f4f3423ac54f892a28da6588da8a67dd42 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 22 Dec 2025 21:01:55 +0100 Subject: [PATCH 276/307] media: rkisp1: Discard pm_runtime_put() return value Printing error messages on pm_runtime_put() returning negative values is not particularly useful. Returning an error code from pm_runtime_put() merely means that it has not queued up a work item to check whether or not the device can be suspended and there are many perfectly valid situations in which that can happen, like after writing "on" to the devices' runtime PM "control" attribute in sysfs for one example. Accordingly, update rkisp1_vb2_stop_streaming() to simply discard the return value of pm_runtime_put(). This will facilitate a planned change of the pm_runtime_put() return type to void in the future. Signed-off-by: Rafael J. Wysocki Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/2356323.iZASKD2KPV@rafael.j.wysocki Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c index 6dcefd144d5a..867cdddf9f89 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c @@ -1123,7 +1123,6 @@ static void rkisp1_vb2_stop_streaming(struct vb2_queue *queue) struct rkisp1_capture *cap = queue->drv_priv; struct rkisp1_vdev_node *node = &cap->vnode; struct rkisp1_device *rkisp1 = cap->rkisp1; - int ret; mutex_lock(&cap->rkisp1->stream_lock); @@ -1132,9 +1131,7 @@ static void rkisp1_vb2_stop_streaming(struct vb2_queue *queue) rkisp1_return_all_buffers(cap, VB2_BUF_STATE_ERROR); v4l2_pipeline_pm_put(&node->vdev.entity); - ret = pm_runtime_put(rkisp1->dev); - if (ret < 0) - dev_err(rkisp1->dev, "power down failed error:%d\n", ret); + pm_runtime_put(rkisp1->dev); rkisp1_dummy_buf_destroy(cap); From 5a50f2b61104d0d351b59ec179f67abab7870453 Mon Sep 17 00:00:00 2001 From: Rui Wang Date: Mon, 5 Jan 2026 12:11:42 -0500 Subject: [PATCH 277/307] media: rkisp1: Fix filter mode register configuration The rkisp1_flt_config() function performs an initial direct write to RKISP1_CIF_ISP_FILT_MODE without including the RKISP1_CIF_ISP_FLT_ENA bit, which clears the filter enable bit in the hardware. The subsequent read/modify/write sequence then reads back the register with the enable bit already cleared and cannot restore it, resulting in the filter being inadvertently disabled. Remove the redundant direct write. The read/modify/write sequence alone correctly preserves the existing enable bit state while updating the DNR mode and filter configuration bits. Signed-off-by: Rui Wang Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260105171142.147792-2-rui.wang@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/rockchip/rkisp1/rkisp1-params.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c index c9f88635224c..6442436a5e42 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c @@ -411,12 +411,6 @@ static void rkisp1_flt_config(struct rkisp1_params *params, rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_LUM_WEIGHT, arg->lum_weight); - rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_MODE, - (arg->mode ? RKISP1_CIF_ISP_FLT_MODE_DNR : 0) | - RKISP1_CIF_ISP_FLT_CHROMA_V_MODE(arg->chr_v_mode) | - RKISP1_CIF_ISP_FLT_CHROMA_H_MODE(arg->chr_h_mode) | - RKISP1_CIF_ISP_FLT_GREEN_STAGE1(arg->grn_stage1)); - /* avoid to override the old enable value */ filt_mode = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_FILT_MODE); filt_mode &= RKISP1_CIF_ISP_FLT_ENA; From 075282b2e68daffb6a49cb1eb6acc531a60ba74a Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 16 Jan 2026 11:29:19 -0500 Subject: [PATCH 278/307] media: nxp: use dev_err_probe() to simplify code Use dev_err_probe() to simplify the code. Drop the explicit error message after returning from imx8mq_mipi_csi_parse_dt(), as the error is already reported by this helper. No functional change. Reviewed-by: Laurent Pinchart Reviewed-by: Alexander Stein Signed-off-by: Frank Li Link: https://patch.msgid.link/20260116-cam_cleanup-v4-1-29ce01640443@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx-mipi-csis.c | 6 ++-- drivers/media/platform/nxp/imx7-media-csi.c | 14 +++----- drivers/media/platform/nxp/imx8mq-mipi-csi2.c | 33 ++++++++----------- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/drivers/media/platform/nxp/imx-mipi-csis.c b/drivers/media/platform/nxp/imx-mipi-csis.c index 088b2945aee3..ce93d868746f 100644 --- a/drivers/media/platform/nxp/imx-mipi-csis.c +++ b/drivers/media/platform/nxp/imx-mipi-csis.c @@ -1547,10 +1547,8 @@ static int mipi_csis_probe(struct platform_device *pdev) /* Now that the hardware is initialized, request the interrupt. */ ret = devm_request_irq(dev, irq, mipi_csis_irq_handler, 0, dev_name(dev), csis); - if (ret) { - dev_err(dev, "Interrupt request failed\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Interrupt request failed\n"); /* Initialize and register the subdev. */ ret = mipi_csis_subdev_init(csis); diff --git a/drivers/media/platform/nxp/imx7-media-csi.c b/drivers/media/platform/nxp/imx7-media-csi.c index 933a5f39f9f4..7ddc7ba06e3d 100644 --- a/drivers/media/platform/nxp/imx7-media-csi.c +++ b/drivers/media/platform/nxp/imx7-media-csi.c @@ -2218,11 +2218,9 @@ static int imx7_csi_probe(struct platform_device *pdev) /* Acquire resources and install interrupt handler. */ csi->mclk = devm_clk_get(&pdev->dev, "mclk"); - if (IS_ERR(csi->mclk)) { - ret = PTR_ERR(csi->mclk); - dev_err(dev, "Failed to get mclk: %d", ret); - return ret; - } + if (IS_ERR(csi->mclk)) + return dev_err_probe(dev, PTR_ERR(csi->mclk), + "Failed to get mclk\n"); csi->irq = platform_get_irq(pdev, 0); if (csi->irq < 0) @@ -2236,10 +2234,8 @@ static int imx7_csi_probe(struct platform_device *pdev) ret = devm_request_irq(dev, csi->irq, imx7_csi_irq_handler, 0, "csi", (void *)csi); - if (ret < 0) { - dev_err(dev, "Request CSI IRQ failed.\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Request CSI IRQ failed.\n"); /* Initialize all the media device infrastructure. */ ret = imx7_csi_media_init(csi); diff --git a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c index 371b4e81328c..00494789d9ba 100644 --- a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c +++ b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c @@ -951,10 +951,9 @@ static int imx8mq_mipi_csi_parse_dt(struct csi_state *state) int ret = 0; state->rst = devm_reset_control_array_get_exclusive(dev); - if (IS_ERR(state->rst)) { - dev_err(dev, "Failed to get reset: %pe\n", state->rst); - return PTR_ERR(state->rst); - } + if (IS_ERR(state->rst)) + return dev_err_probe(dev, PTR_ERR(state->rst), + "Failed to get reset\n"); if (state->pdata->use_reg_csr) { const struct regmap_config regmap_config = { @@ -977,24 +976,22 @@ static int imx8mq_mipi_csi_parse_dt(struct csi_state *state) ret = of_property_read_u32_array(np, "fsl,mipi-phy-gpr", out_val, ARRAY_SIZE(out_val)); - if (ret) { - dev_err(dev, "no fsl,mipi-phy-gpr property found: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "property %s not found\n", + "fsl,mipi-phy-gpr"); ph = *out_val; node = of_find_node_by_phandle(ph); - if (!node) { - dev_err(dev, "Error finding node by phandle\n"); - return -ENODEV; - } + if (!node) + return dev_err_probe(dev, -ENODEV, + "Error finding node by phandle\n"); + state->phy_gpr = syscon_node_to_regmap(node); of_node_put(node); - if (IS_ERR(state->phy_gpr)) { - dev_err(dev, "failed to get gpr regmap: %pe\n", state->phy_gpr); - return PTR_ERR(state->phy_gpr); - } + if (IS_ERR(state->phy_gpr)) + return dev_err_probe(dev, PTR_ERR(state->phy_gpr), + "failed to get gpr regmap\n"); state->phy_gpr_reg = out_val[1]; dev_dbg(dev, "phy gpr register set to 0x%x\n", state->phy_gpr_reg); @@ -1017,10 +1014,8 @@ static int imx8mq_mipi_csi_probe(struct platform_device *pdev) state->pdata = of_device_get_match_data(dev); ret = imx8mq_mipi_csi_parse_dt(state); - if (ret < 0) { - dev_err(dev, "Failed to parse device tree: %d\n", ret); + if (ret < 0) return ret; - } /* Acquire resources. */ state->regs = devm_platform_ioremap_resource(pdev, 0); From 078161dd44d6f848a62a473206d69025607736ec Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 16 Jan 2026 11:29:20 -0500 Subject: [PATCH 279/307] media: nxp: imx8-isi: use devm_pm_runtime_enable() to simplify code Use devm_pm_runtime_enable() to simplify code. Change to use dev_err_probe() because previous goto change to return. No functional change. Signed-off-by: Frank Li Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260116-cam_cleanup-v4-2-29ce01640443@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../media/platform/nxp/imx8-isi/imx8-isi-core.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c index 0f000582a1de..18146978bdf5 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c @@ -499,13 +499,14 @@ static int mxc_isi_probe(struct platform_device *pdev) dma_size = isi->pdata->has_36bit_dma ? 36 : 32; dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size)); - pm_runtime_enable(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; ret = mxc_isi_crossbar_init(isi); - if (ret) { - dev_err(dev, "Failed to initialize crossbar: %d\n", ret); - goto err_pm; - } + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize crossbar\n"); for (i = 0; i < isi->pdata->num_channels; ++i) { ret = mxc_isi_pipe_init(isi, i); @@ -528,8 +529,7 @@ static int mxc_isi_probe(struct platform_device *pdev) err_xbar: mxc_isi_crossbar_cleanup(&isi->crossbar); -err_pm: - pm_runtime_disable(isi->dev); + return ret; } @@ -548,8 +548,6 @@ static void mxc_isi_remove(struct platform_device *pdev) mxc_isi_crossbar_cleanup(&isi->crossbar); mxc_isi_v4l2_cleanup(isi); - - pm_runtime_disable(isi->dev); } static const struct of_device_id mxc_isi_of_match[] = { From e001b3b1cdd460eba59c7738dc43530d5781579b Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 16 Jan 2026 11:29:21 -0500 Subject: [PATCH 280/307] media: nxp: use cleanup __free(fwnode_handle) simplify code Use cleanup __free(fwnode_handle) simplify code. No functional change. Signed-off-by: Frank Li Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260116-cam_cleanup-v4-3-29ce01640443@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx-mipi-csis.c | 25 ++++++------------- drivers/media/platform/nxp/imx8mq-mipi-csi2.c | 25 ++++++------------- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/drivers/media/platform/nxp/imx-mipi-csis.c b/drivers/media/platform/nxp/imx-mipi-csis.c index ce93d868746f..9a43fd1eb0bc 100644 --- a/drivers/media/platform/nxp/imx-mipi-csis.c +++ b/drivers/media/platform/nxp/imx-mipi-csis.c @@ -12,6 +12,7 @@ * */ +#include #include #include #include @@ -1349,27 +1350,26 @@ static int mipi_csis_async_register(struct mipi_csis_device *csis) .bus_type = V4L2_MBUS_CSI2_DPHY, }; struct v4l2_async_connection *asd; - struct fwnode_handle *ep; unsigned int i; int ret; v4l2_async_subdev_nf_init(&csis->notifier, &csis->sd); - ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csis->dev), 0, 0, - FWNODE_GRAPH_ENDPOINT_NEXT); + struct fwnode_handle *ep __free(fwnode_handle) = + fwnode_graph_get_endpoint_by_id(dev_fwnode(csis->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); if (!ep) return -ENOTCONN; ret = v4l2_fwnode_endpoint_parse(ep, &vep); if (ret) - goto err_parse; + return ret; for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) { if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) { dev_err(csis->dev, "data lanes reordering is not supported"); - ret = -EINVAL; - goto err_parse; + return -EINVAL; } } @@ -1381,12 +1381,8 @@ static int mipi_csis_async_register(struct mipi_csis_device *csis) asd = v4l2_async_nf_add_fwnode_remote(&csis->notifier, ep, struct v4l2_async_connection); - if (IS_ERR(asd)) { - ret = PTR_ERR(asd); - goto err_parse; - } - - fwnode_handle_put(ep); + if (IS_ERR(asd)) + return PTR_ERR(asd); csis->notifier.ops = &mipi_csis_notify_ops; @@ -1395,11 +1391,6 @@ static int mipi_csis_async_register(struct mipi_csis_device *csis) return ret; return v4l2_async_register_subdev(&csis->sd); - -err_parse: - fwnode_handle_put(ep); - - return ret; } /* ----------------------------------------------------------------------------- diff --git a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c index 00494789d9ba..e111b1debd69 100644 --- a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c +++ b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -717,27 +718,26 @@ static int imx8mq_mipi_csi_async_register(struct csi_state *state) .bus_type = V4L2_MBUS_CSI2_DPHY, }; struct v4l2_async_connection *asd; - struct fwnode_handle *ep; unsigned int i; int ret; v4l2_async_subdev_nf_init(&state->notifier, &state->sd); - ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(state->dev), 0, 0, - FWNODE_GRAPH_ENDPOINT_NEXT); + struct fwnode_handle *ep __free(fwnode_handle) = + fwnode_graph_get_endpoint_by_id(dev_fwnode(state->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); if (!ep) return -ENOTCONN; ret = v4l2_fwnode_endpoint_parse(ep, &vep); if (ret) - goto err_parse; + return ret; for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) { if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) { dev_err(state->dev, "data lanes reordering is not supported"); - ret = -EINVAL; - goto err_parse; + return -EINVAL; } } @@ -749,12 +749,8 @@ static int imx8mq_mipi_csi_async_register(struct csi_state *state) asd = v4l2_async_nf_add_fwnode_remote(&state->notifier, ep, struct v4l2_async_connection); - if (IS_ERR(asd)) { - ret = PTR_ERR(asd); - goto err_parse; - } - - fwnode_handle_put(ep); + if (IS_ERR(asd)) + return PTR_ERR(asd); state->notifier.ops = &imx8mq_mipi_csi_notify_ops; @@ -763,11 +759,6 @@ static int imx8mq_mipi_csi_async_register(struct csi_state *state) return ret; return v4l2_async_register_subdev(&state->sd); - -err_parse: - fwnode_handle_put(ep); - - return ret; } /* ----------------------------------------------------------------------------- From 7ce92f358970242fba35e854a17663a44d26ccc9 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 16 Jan 2026 11:17:56 -0500 Subject: [PATCH 281/307] media: staging: media: imx6-mipi-csi2: replace spaces with tabs for alignment Replace spaces with tabs to align register value definitions, making it easier to add new entries and maintain consistent formatting. Also use a space between the type and field in struct csi2_dev. No functional change. Signed-off-by: Frank Li Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260116-stage-csi2-cleanup-v2-1-a56e9cb25196@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/staging/media/imx/imx6-mipi-csi2.c | 84 +++++++++++----------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/drivers/staging/media/imx/imx6-mipi-csi2.c b/drivers/staging/media/imx/imx6-mipi-csi2.c index dd8c7b3233bc..1113ea2a37f0 100644 --- a/drivers/staging/media/imx/imx6-mipi-csi2.c +++ b/drivers/staging/media/imx/imx6-mipi-csi2.c @@ -23,65 +23,65 @@ * there must be 5 pads: 1 input pad from sensor, and * the 4 virtual channel output pads */ -#define CSI2_SINK_PAD 0 -#define CSI2_NUM_SINK_PADS 1 -#define CSI2_NUM_SRC_PADS 4 -#define CSI2_NUM_PADS 5 +#define CSI2_SINK_PAD 0 +#define CSI2_NUM_SINK_PADS 1 +#define CSI2_NUM_SRC_PADS 4 +#define CSI2_NUM_PADS 5 /* * The default maximum bit-rate per lane in Mbps, if the * source subdev does not provide V4L2_CID_LINK_FREQ. */ -#define CSI2_DEFAULT_MAX_MBPS 849 +#define CSI2_DEFAULT_MAX_MBPS 849 struct csi2_dev { - struct device *dev; - struct v4l2_subdev sd; + struct device *dev; + struct v4l2_subdev sd; struct v4l2_async_notifier notifier; - struct media_pad pad[CSI2_NUM_PADS]; - struct clk *dphy_clk; - struct clk *pllref_clk; - struct clk *pix_clk; /* what is this? */ - void __iomem *base; + struct media_pad pad[CSI2_NUM_PADS]; + struct clk *dphy_clk; + struct clk *pllref_clk; + struct clk *pix_clk; /* what is this? */ + void __iomem *base; - struct v4l2_subdev *remote; - unsigned int remote_pad; - unsigned short data_lanes; + struct v4l2_subdev *remote; + unsigned int remote_pad; + unsigned short data_lanes; /* lock to protect all members below */ struct mutex lock; struct v4l2_mbus_framefmt format_mbus; - int stream_count; - struct v4l2_subdev *src_sd; - bool sink_linked[CSI2_NUM_SRC_PADS]; + int stream_count; + struct v4l2_subdev *src_sd; + bool sink_linked[CSI2_NUM_SRC_PADS]; }; #define DEVICE_NAME "imx6-mipi-csi2" /* Register offsets */ -#define CSI2_VERSION 0x000 -#define CSI2_N_LANES 0x004 -#define CSI2_PHY_SHUTDOWNZ 0x008 -#define CSI2_DPHY_RSTZ 0x00c -#define CSI2_RESETN 0x010 -#define CSI2_PHY_STATE 0x014 -#define PHY_STOPSTATEDATA_BIT 4 -#define PHY_STOPSTATEDATA(n) BIT(PHY_STOPSTATEDATA_BIT + (n)) -#define PHY_RXCLKACTIVEHS BIT(8) -#define PHY_RXULPSCLKNOT BIT(9) -#define PHY_STOPSTATECLK BIT(10) -#define CSI2_DATA_IDS_1 0x018 -#define CSI2_DATA_IDS_2 0x01c -#define CSI2_ERR1 0x020 -#define CSI2_ERR2 0x024 -#define CSI2_MSK1 0x028 -#define CSI2_MSK2 0x02c -#define CSI2_PHY_TST_CTRL0 0x030 +#define CSI2_VERSION 0x000 +#define CSI2_N_LANES 0x004 +#define CSI2_PHY_SHUTDOWNZ 0x008 +#define CSI2_DPHY_RSTZ 0x00c +#define CSI2_RESETN 0x010 +#define CSI2_PHY_STATE 0x014 +#define PHY_STOPSTATEDATA_BIT 4 +#define PHY_STOPSTATEDATA(n) BIT(PHY_STOPSTATEDATA_BIT + (n)) +#define PHY_RXCLKACTIVEHS BIT(8) +#define PHY_RXULPSCLKNOT BIT(9) +#define PHY_STOPSTATECLK BIT(10) +#define CSI2_DATA_IDS_1 0x018 +#define CSI2_DATA_IDS_2 0x01c +#define CSI2_ERR1 0x020 +#define CSI2_ERR2 0x024 +#define CSI2_MSK1 0x028 +#define CSI2_MSK2 0x02c +#define CSI2_PHY_TST_CTRL0 0x030 #define PHY_TESTCLR BIT(0) #define PHY_TESTCLK BIT(1) -#define CSI2_PHY_TST_CTRL1 0x034 +#define CSI2_PHY_TST_CTRL1 0x034 #define PHY_TESTEN BIT(16) /* * i.MX CSI2IPU Gasket registers follow. The CSI2IPU gasket is @@ -106,13 +106,13 @@ static inline struct csi2_dev *notifier_to_dev(struct v4l2_async_notifier *n) * reference manual is as follows: * * 1. Deassert presetn signal (global reset). - * It's not clear what this "global reset" signal is (maybe APB - * global reset), but in any case this step would be probably - * be carried out during driver load in csi2_probe(). + * It's not clear what this "global reset" signal is (maybe APB + * global reset), but in any case this step would be probably + * be carried out during driver load in csi2_probe(). * * 2. Configure MIPI Camera Sensor to put all Tx lanes in LP-11 state. - * This must be carried out by the MIPI sensor's s_power(ON) subdev - * op. + * This must be carried out by the MIPI sensor's s_power(ON) subdev + * op. * * 3. D-PHY initialization. * 4. CSI2 Controller programming (Set N_LANES, deassert PHY_SHUTDOWNZ, From 2da4207d0a7c7de28edde12fe9e3da574f877351 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 16 Jan 2026 11:17:57 -0500 Subject: [PATCH 282/307] media: staging: media: imx6-mipi-csi2: use devm_mutex_init() to simplify code Use devm_mutex_init() to simplify the code. No functional change. Reviewed-by: Philipp Zabel Signed-off-by: Frank Li Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260116-stage-csi2-cleanup-v2-2-a56e9cb25196@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/staging/media/imx/imx6-mipi-csi2.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/staging/media/imx/imx6-mipi-csi2.c b/drivers/staging/media/imx/imx6-mipi-csi2.c index 1113ea2a37f0..4f740170d2bb 100644 --- a/drivers/staging/media/imx/imx6-mipi-csi2.c +++ b/drivers/staging/media/imx/imx6-mipi-csi2.c @@ -777,12 +777,14 @@ static int csi2_probe(struct platform_device *pdev) if (!csi2->base) return -ENOMEM; - mutex_init(&csi2->lock); + ret = devm_mutex_init(&pdev->dev, &csi2->lock); + if (ret) + return ret; ret = clk_prepare_enable(csi2->pllref_clk); if (ret) { v4l2_err(&csi2->sd, "failed to enable pllref_clk\n"); - goto rmmutex; + return ret; } ret = clk_prepare_enable(csi2->dphy_clk); @@ -805,8 +807,6 @@ clean_notifier: clk_disable_unprepare(csi2->dphy_clk); pllref_off: clk_disable_unprepare(csi2->pllref_clk); -rmmutex: - mutex_destroy(&csi2->lock); return ret; } @@ -820,7 +820,6 @@ static void csi2_remove(struct platform_device *pdev) v4l2_async_unregister_subdev(sd); clk_disable_unprepare(csi2->dphy_clk); clk_disable_unprepare(csi2->pllref_clk); - mutex_destroy(&csi2->lock); media_entity_cleanup(&sd->entity); } From 6ee54e03d97bce5168b4ac87cdf7ac88b3f944a8 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 16 Jan 2026 11:18:00 -0500 Subject: [PATCH 283/307] media: staging: media: imx6-mipi-csi2: use devm_platform_ioremap_resource() simplify code Use devm_platform_ioremap_resource() simplify code. No functional change. Reviewed-by: Laurent Pinchart Reviewed-by: Philipp Zabel Signed-off-by: Frank Li Link: https://patch.msgid.link/20260116-stage-csi2-cleanup-v2-5-a56e9cb25196@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/staging/media/imx/imx6-mipi-csi2.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/drivers/staging/media/imx/imx6-mipi-csi2.c b/drivers/staging/media/imx/imx6-mipi-csi2.c index 4f740170d2bb..211f67fb92b5 100644 --- a/drivers/staging/media/imx/imx6-mipi-csi2.c +++ b/drivers/staging/media/imx/imx6-mipi-csi2.c @@ -719,7 +719,6 @@ err_parse: static int csi2_probe(struct platform_device *pdev) { struct csi2_dev *csi2; - struct resource *res; int i, ret; csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL); @@ -767,15 +766,9 @@ static int csi2_probe(struct platform_device *pdev) return PTR_ERR(csi2->pix_clk); } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - v4l2_err(&csi2->sd, "failed to get platform resources\n"); - return -ENODEV; - } - - csi2->base = devm_ioremap(&pdev->dev, res->start, PAGE_SIZE); - if (!csi2->base) - return -ENOMEM; + csi2->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi2->base)) + return PTR_ERR(csi2->base); ret = devm_mutex_init(&pdev->dev, &csi2->lock); if (ret) From a1da27d0c3e08d88980a3205fc61773ec6089666 Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Tue, 20 Jan 2026 13:22:27 +0100 Subject: [PATCH 284/307] media: dt-bindings: add rockchip mipi csi-2 receiver Add documentation for the Rockchip MIPI CSI-2 Receiver. Signed-off-by: Michael Riesch Reviewed-by: Krzysztof Kozlowski Signed-off-by: Michael Riesch Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../media/rockchip,rk3568-mipi-csi2.yaml | 141 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 147 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml new file mode 100644 index 000000000000..2c2bd87582eb --- /dev/null +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/rockchip,rk3568-mipi-csi2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip MIPI CSI-2 Receiver + +maintainers: + - Michael Riesch + +description: + The Rockchip MIPI CSI-2 Receiver is a CSI-2 bridge with one input port and + one output port. It receives the data with the help of an external MIPI PHY + (C-PHY or D-PHY) and passes it to the Rockchip Video Capture (VICAP) block. + +properties: + compatible: + enum: + - rockchip,rk3568-mipi-csi2 + + reg: + maxItems: 1 + + interrupts: + items: + - description: Interrupt that signals changes in CSI2HOST_ERR1. + - description: Interrupt that signals changes in CSI2HOST_ERR2. + + interrupt-names: + items: + - const: err1 + - const: err2 + + clocks: + maxItems: 1 + + phys: + maxItems: 1 + description: MIPI C-PHY or D-PHY. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: Input port node. Connect to e.g., a MIPI CSI-2 image sensor. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + data-lanes: + minItems: 1 + maxItems: 4 + + required: + - bus-type + - data-lanes + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Output port connected to a Rockchip VICAP port. + + required: + - port@0 + - port@1 + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + +required: + - compatible + - reg + - clocks + - phys + - ports + - power-domains + - resets + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + + soc { + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + csi: csi@fdfb0000 { + compatible = "rockchip,rk3568-mipi-csi2"; + reg = <0x0 0xfdfb0000 0x0 0x10000>; + interrupts = , + ; + interrupt-names = "err1", "err2"; + clocks = <&cru PCLK_CSI2HOST1>; + phys = <&csi_dphy>; + power-domains = <&power RK3568_PD_VI>; + resets = <&cru SRST_P_CSI2HOST1>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + csi_in: port@0 { + reg = <0>; + + csi_input: endpoint { + bus-type = ; + data-lanes = <1 2 3 4>; + remote-endpoint = <&imx415_output>; + }; + }; + + csi_out: port@1 { + reg = <1>; + + csi_output: endpoint { + remote-endpoint = <&vicap_mipi_input>; + }; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 5560da0deb71..1d1589feaf95 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25364,6 +25364,12 @@ S: Maintained F: drivers/i2c/busses/i2c-designware-amdisp.c F: include/linux/soc/amd/isp4_misc.h +SYNOPSYS DESIGNWARE MIPI CSI-2 RECEIVER DRIVER +M: Michael Riesch +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml + SYNOPSYS DESIGNWARE MMC/SD/SDIO DRIVER M: Jaehoon Chung M: Shawn Lin From 355a110040665e439af126e8ec9c3968e73f163c Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Tue, 20 Jan 2026 13:22:28 +0100 Subject: [PATCH 285/307] media: synopsys: add driver for the designware mipi csi-2 receiver The Synopsys DesignWare MIPI CSI-2 Receiver is a CSI-2 bridge with one input port and one output port. It receives the data with the help of an external MIPI PHY (C-PHY or D-PHY) and passes it to e.g., the Rockchip Video Capture (VICAP) block on recent Rockchip SoCs. Add a V4L2 subdevice driver for this unit. Signed-off-by: Michael Riesch Reviewed-by: Bryan O'Donoghue Reviewed-by: Mehdi Djait Signed-off-by: Michael Riesch [Sakari Ailus: Make sparse and smatch happy.] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 1 + drivers/media/platform/synopsys/Kconfig | 18 + drivers/media/platform/synopsys/Makefile | 2 + .../media/platform/synopsys/dw-mipi-csi2rx.c | 722 ++++++++++++++++++ 4 files changed, 743 insertions(+) create mode 100644 drivers/media/platform/synopsys/dw-mipi-csi2rx.c diff --git a/MAINTAINERS b/MAINTAINERS index 1d1589feaf95..62ccdc72384d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25369,6 +25369,7 @@ M: Michael Riesch L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml +F: drivers/media/platform/synopsys/dw-mipi-csi2rx.c SYNOPSYS DESIGNWARE MMC/SD/SDIO DRIVER M: Jaehoon Chung diff --git a/drivers/media/platform/synopsys/Kconfig b/drivers/media/platform/synopsys/Kconfig index 4fd521f78425..e798ec00b189 100644 --- a/drivers/media/platform/synopsys/Kconfig +++ b/drivers/media/platform/synopsys/Kconfig @@ -1,3 +1,21 @@ # SPDX-License-Identifier: GPL-2.0-only source "drivers/media/platform/synopsys/hdmirx/Kconfig" + +config VIDEO_DW_MIPI_CSI2RX + tristate "Synopsys DesignWare MIPI CSI-2 Receiver" + depends on VIDEO_DEV + depends on V4L_PLATFORM_DRIVERS + depends on PM && COMMON_CLK + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + The Synopsys DesignWare MIPI CSI-2 Receiver is a CSI-2 bridge with + one input port and one output port. It receives the data with the + help of an external MIPI PHY (C-PHY or D-PHY) and passes it to e.g., + the Rockchip Video Capture (VICAP) block on recent Rockchip SoCs. + This is a driver for this unit. + + To compile this driver as a module, choose M here: the module + will be called dw-mipi-csi2rx. diff --git a/drivers/media/platform/synopsys/Makefile b/drivers/media/platform/synopsys/Makefile index 3b12c574dd67..e0232ee23304 100644 --- a/drivers/media/platform/synopsys/Makefile +++ b/drivers/media/platform/synopsys/Makefile @@ -1,2 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y += hdmirx/ + +obj-$(CONFIG_VIDEO_DW_MIPI_CSI2RX) += dw-mipi-csi2rx.o diff --git a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c new file mode 100644 index 000000000000..170346ae1a59 --- /dev/null +++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c @@ -0,0 +1,722 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synopsys DesignWare MIPI CSI-2 Receiver Driver + * + * Copyright (C) 2019 Rockchip Electronics Co., Ltd. + * Copyright (C) 2025 Michael Riesch + * Copyright (C) 2026 Collabora, Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define DW_MIPI_CSI2RX_N_LANES 0x04 +#define DW_MIPI_CSI2RX_RESETN 0x10 +#define DW_MIPI_CSI2RX_PHY_STATE 0x14 +#define DW_MIPI_CSI2RX_ERR1 0x20 +#define DW_MIPI_CSI2RX_ERR2 0x24 +#define DW_MIPI_CSI2RX_MSK1 0x28 +#define DW_MIPI_CSI2RX_MSK2 0x2c +#define DW_MIPI_CSI2RX_CONTROL 0x40 + +#define SW_CPHY_EN(x) ((x) << 0) +#define SW_DSI_EN(x) ((x) << 4) +#define SW_DATATYPE_FS(x) ((x) << 8) +#define SW_DATATYPE_FE(x) ((x) << 14) +#define SW_DATATYPE_LS(x) ((x) << 20) +#define SW_DATATYPE_LE(x) ((x) << 26) + +#define DW_MIPI_CSI2RX_CLKS_MAX 1 + +enum { + DW_MIPI_CSI2RX_PAD_SINK, + DW_MIPI_CSI2RX_PAD_SRC, + DW_MIPI_CSI2RX_PAD_MAX, +}; + +struct dw_mipi_csi2rx_format { + u32 code; + u8 depth; + u8 csi_dt; +}; + +struct dw_mipi_csi2rx_device { + struct device *dev; + + void __iomem *base_addr; + struct clk_bulk_data *clks; + unsigned int clks_num; + struct phy *phy; + struct reset_control *reset; + + const struct dw_mipi_csi2rx_format *formats; + unsigned int formats_num; + + struct media_pad pads[DW_MIPI_CSI2RX_PAD_MAX]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev sd; + + enum v4l2_mbus_type bus_type; + u32 lanes_num; +}; + +static const struct v4l2_mbus_framefmt default_format = { + .width = 3840, + .height = 2160, + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_RAW, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_FULL_RANGE, + .xfer_func = V4L2_XFER_FUNC_NONE, +}; + +static const struct dw_mipi_csi2rx_format formats[] = { + /* YUV formats */ + { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + }, + { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + }, + { + .code = MEDIA_BUS_FMT_YVYU8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + }, + { + .code = MEDIA_BUS_FMT_VYUY8_1X16, + .depth = 16, + .csi_dt = MIPI_CSI2_DT_YUV422_8B, + }, + /* RGB formats */ + { + .code = MEDIA_BUS_FMT_RGB888_1X24, + .depth = 24, + .csi_dt = MIPI_CSI2_DT_RGB888, + }, + { + .code = MEDIA_BUS_FMT_BGR888_1X24, + .depth = 24, + .csi_dt = MIPI_CSI2_DT_RGB888, + }, + /* Bayer formats */ + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + }, + { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + }, + { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + }, + { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .depth = 8, + .csi_dt = MIPI_CSI2_DT_RAW8, + }, + { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + }, + { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + }, + { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + }, + { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .depth = 10, + .csi_dt = MIPI_CSI2_DT_RAW10, + }, + { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + }, + { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + }, + { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + }, + { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + }, +}; + +static inline struct dw_mipi_csi2rx_device *to_csi2(struct v4l2_subdev *sd) +{ + return container_of(sd, struct dw_mipi_csi2rx_device, sd); +} + +static inline void dw_mipi_csi2rx_write(struct dw_mipi_csi2rx_device *csi2, + unsigned int addr, u32 val) +{ + writel(val, csi2->base_addr + addr); +} + +static inline u32 dw_mipi_csi2rx_read(struct dw_mipi_csi2rx_device *csi2, + unsigned int addr) +{ + return readl(csi2->base_addr + addr); +} + +static const struct dw_mipi_csi2rx_format * +dw_mipi_csi2rx_find_format(struct dw_mipi_csi2rx_device *csi2, u32 mbus_code) +{ + WARN_ON(csi2->formats_num == 0); + + for (unsigned int i = 0; i < csi2->formats_num; i++) { + const struct dw_mipi_csi2rx_format *format = &csi2->formats[i]; + + if (format->code == mbus_code) + return format; + } + + return NULL; +} + +static int dw_mipi_csi2rx_start(struct dw_mipi_csi2rx_device *csi2) +{ + struct media_pad *source_pad; + union phy_configure_opts opts; + u32 lanes = csi2->lanes_num; + u32 control = 0; + s64 link_freq; + int ret; + + if (lanes < 1 || lanes > 4) + return -EINVAL; + + source_pad = media_pad_remote_pad_unique( + &csi2->pads[DW_MIPI_CSI2RX_PAD_SINK]); + if (IS_ERR(source_pad)) + return PTR_ERR(source_pad); + + /* set mult and div to 0, thus completely rely on V4L2_CID_LINK_FREQ */ + link_freq = v4l2_get_link_freq(source_pad, 0, 0); + if (link_freq < 0) + return link_freq; + + switch (csi2->bus_type) { + case V4L2_MBUS_CSI2_DPHY: + ret = phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, + lanes, &opts.mipi_dphy); + if (ret) + return ret; + + ret = phy_set_mode(csi2->phy, PHY_MODE_MIPI_DPHY); + if (ret) + return ret; + + ret = phy_configure(csi2->phy, &opts); + if (ret) + return ret; + + control |= SW_CPHY_EN(0); + break; + + case V4L2_MBUS_CSI2_CPHY: + /* TODO: implement CPHY configuration */ + return -EOPNOTSUPP; + default: + return -EINVAL; + } + + control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) | + SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03); + + dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_N_LANES, lanes - 1); + dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_CONTROL, control); + dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_RESETN, 1); + + return phy_power_on(csi2->phy); +} + +static void dw_mipi_csi2rx_stop(struct dw_mipi_csi2rx_device *csi2) +{ + phy_power_off(csi2->phy); + + dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_RESETN, 0); + dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_MSK1, ~0); + dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_MSK2, ~0); +} + +static const struct media_entity_operations dw_mipi_csi2rx_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int +dw_mipi_csi2rx_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct dw_mipi_csi2rx_device *csi2 = to_csi2(sd); + + switch (code->pad) { + case DW_MIPI_CSI2RX_PAD_SRC: + if (code->index) + return -EINVAL; + + code->code = + v4l2_subdev_state_get_format(sd_state, + DW_MIPI_CSI2RX_PAD_SINK)->code; + + return 0; + case DW_MIPI_CSI2RX_PAD_SINK: + if (code->index > csi2->formats_num) + return -EINVAL; + + code->code = csi2->formats[code->index].code; + return 0; + default: + return -EINVAL; + } +} + +static int dw_mipi_csi2rx_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct dw_mipi_csi2rx_device *csi2 = to_csi2(sd); + const struct dw_mipi_csi2rx_format *fmt; + struct v4l2_mbus_framefmt *sink, *src; + + /* the format on the source pad always matches the sink pad */ + if (format->pad == DW_MIPI_CSI2RX_PAD_SRC) + return v4l2_subdev_get_fmt(sd, state, format); + + sink = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!sink) + return -EINVAL; + + fmt = dw_mipi_csi2rx_find_format(csi2, format->format.code); + if (!fmt) + format->format = default_format; + + *sink = format->format; + + /* propagate the format to the source pad */ + src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!src) + return -EINVAL; + + *src = *sink; + + return 0; +} + +static int dw_mipi_csi2rx_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, + &default_format); +} + +static int dw_mipi_csi2rx_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct dw_mipi_csi2rx_device *csi2 = to_csi2(sd); + struct v4l2_subdev *remote_sd; + struct media_pad *sink_pad, *remote_pad; + struct device *dev = csi2->dev; + u64 mask; + int ret; + + sink_pad = &sd->entity.pads[DW_MIPI_CSI2RX_PAD_SINK]; + remote_pad = media_pad_remote_pad_first(sink_pad); + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + + mask = v4l2_subdev_state_xlate_streams(state, DW_MIPI_CSI2RX_PAD_SINK, + DW_MIPI_CSI2RX_PAD_SRC, + &streams_mask); + + ret = pm_runtime_resume_and_get(dev); + if (ret) + goto err; + + ret = dw_mipi_csi2rx_start(csi2); + if (ret) { + dev_err(dev, "failed to enable CSI hardware\n"); + goto err_pm_runtime_put; + } + + ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask); + if (ret) + goto err_csi_stop; + + return 0; + +err_csi_stop: + dw_mipi_csi2rx_stop(csi2); +err_pm_runtime_put: + pm_runtime_put(dev); +err: + return ret; +} + +static int dw_mipi_csi2rx_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct dw_mipi_csi2rx_device *csi2 = to_csi2(sd); + struct v4l2_subdev *remote_sd; + struct media_pad *sink_pad, *remote_pad; + struct device *dev = csi2->dev; + u64 mask; + int ret; + + sink_pad = &sd->entity.pads[DW_MIPI_CSI2RX_PAD_SINK]; + remote_pad = media_pad_remote_pad_first(sink_pad); + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + + mask = v4l2_subdev_state_xlate_streams(state, DW_MIPI_CSI2RX_PAD_SINK, + DW_MIPI_CSI2RX_PAD_SRC, + &streams_mask); + + ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); + + dw_mipi_csi2rx_stop(csi2); + + pm_runtime_put(dev); + + return ret; +} + +static const struct v4l2_subdev_pad_ops dw_mipi_csi2rx_pad_ops = { + .enum_mbus_code = dw_mipi_csi2rx_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = dw_mipi_csi2rx_set_fmt, + .set_routing = dw_mipi_csi2rx_set_routing, + .enable_streams = dw_mipi_csi2rx_enable_streams, + .disable_streams = dw_mipi_csi2rx_disable_streams, +}; + +static const struct v4l2_subdev_ops dw_mipi_csi2rx_ops = { + .pad = &dw_mipi_csi2rx_pad_ops, +}; + +static int dw_mipi_csi2rx_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = DW_MIPI_CSI2RX_PAD_SINK, + .sink_stream = 0, + .source_pad = DW_MIPI_CSI2RX_PAD_SRC, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + struct v4l2_subdev_krouting routing = { + .len_routes = ARRAY_SIZE(routes), + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return v4l2_subdev_set_routing_with_fmt(sd, state, &routing, + &default_format); +} + +static const struct v4l2_subdev_internal_ops dw_mipi_csi2rx_internal_ops = { + .init_state = dw_mipi_csi2rx_init_state, +}; + +static int dw_mipi_csi2rx_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asd) +{ + struct dw_mipi_csi2rx_device *csi2 = + container_of(notifier, struct dw_mipi_csi2rx_device, notifier); + struct media_pad *sink_pad = &csi2->pads[DW_MIPI_CSI2RX_PAD_SINK]; + int ret; + + ret = v4l2_create_fwnode_links_to_pad(sd, sink_pad, + MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(csi2->dev, "failed to link source pad of %s\n", + sd->name); + return ret; + } + + return 0; +} + +static const struct v4l2_async_notifier_operations dw_mipi_csi2rx_notifier_ops = { + .bound = dw_mipi_csi2rx_notifier_bound, +}; + +static int dw_mipi_csi2rx_register_notifier(struct dw_mipi_csi2rx_device *csi2) +{ + struct v4l2_async_connection *asd; + struct v4l2_async_notifier *ntf = &csi2->notifier; + struct v4l2_fwnode_endpoint vep; + struct v4l2_subdev *sd = &csi2->sd; + struct device *dev = csi2->dev; + int ret; + + struct fwnode_handle *ep __free(fwnode_handle) = + fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); + if (!ep) + return dev_err_probe(dev, -ENODEV, "failed to get endpoint\n"); + + vep.bus_type = V4L2_MBUS_UNKNOWN; + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + return dev_err_probe(dev, ret, "failed to parse endpoint\n"); + + if (vep.bus_type != V4L2_MBUS_CSI2_DPHY && + vep.bus_type != V4L2_MBUS_CSI2_CPHY) + return dev_err_probe(dev, -EINVAL, + "invalid bus type of endpoint\n"); + + csi2->bus_type = vep.bus_type; + csi2->lanes_num = vep.bus.mipi_csi2.num_data_lanes; + + v4l2_async_subdev_nf_init(ntf, sd); + ntf->ops = &dw_mipi_csi2rx_notifier_ops; + + asd = v4l2_async_nf_add_fwnode_remote(ntf, ep, + struct v4l2_async_connection); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto err_nf_cleanup; + } + + ret = v4l2_async_nf_register(ntf); + if (ret) { + ret = dev_err_probe(dev, ret, "failed to register notifier\n"); + goto err_nf_cleanup; + } + + return 0; + +err_nf_cleanup: + v4l2_async_nf_cleanup(ntf); + + return ret; +} + +static int dw_mipi_csi2rx_register(struct dw_mipi_csi2rx_device *csi2) +{ + struct media_pad *pads = csi2->pads; + struct v4l2_subdev *sd = &csi2->sd; + int ret; + + ret = dw_mipi_csi2rx_register_notifier(csi2); + if (ret) + goto err; + + v4l2_subdev_init(sd, &dw_mipi_csi2rx_ops); + sd->dev = csi2->dev; + sd->entity.ops = &dw_mipi_csi2rx_media_ops; + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + sd->internal_ops = &dw_mipi_csi2rx_internal_ops; + snprintf(sd->name, sizeof(sd->name), "dw-mipi-csi2rx %s", + dev_name(csi2->dev)); + + pads[DW_MIPI_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[DW_MIPI_CSI2RX_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, DW_MIPI_CSI2RX_PAD_MAX, pads); + if (ret) + goto err_notifier_unregister; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_entity_cleanup; + + ret = v4l2_async_register_subdev(sd); + if (ret) { + dev_err(sd->dev, "failed to register CSI-2 subdev\n"); + goto err_subdev_cleanup; + } + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(sd); +err_entity_cleanup: + media_entity_cleanup(&sd->entity); +err_notifier_unregister: + v4l2_async_nf_unregister(&csi2->notifier); + v4l2_async_nf_cleanup(&csi2->notifier); +err: + return ret; +} + +static void dw_mipi_csi2rx_unregister(struct dw_mipi_csi2rx_device *csi2) +{ + struct v4l2_subdev *sd = &csi2->sd; + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_async_nf_unregister(&csi2->notifier); + v4l2_async_nf_cleanup(&csi2->notifier); +} + +static const struct of_device_id dw_mipi_csi2rx_of_match[] = { + { + .compatible = "rockchip,rk3568-mipi-csi2", + }, + {} +}; +MODULE_DEVICE_TABLE(of, dw_mipi_csi2rx_of_match); + +static int dw_mipi_csi2rx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dw_mipi_csi2rx_device *csi2; + int ret; + + csi2 = devm_kzalloc(dev, sizeof(*csi2), GFP_KERNEL); + if (!csi2) + return -ENOMEM; + csi2->dev = dev; + dev_set_drvdata(dev, csi2); + + csi2->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi2->base_addr)) + return PTR_ERR(csi2->base_addr); + + ret = devm_clk_bulk_get_all(dev, &csi2->clks); + if (ret != DW_MIPI_CSI2RX_CLKS_MAX) + return dev_err_probe(dev, -ENODEV, "failed to get clocks\n"); + csi2->clks_num = ret; + + csi2->phy = devm_phy_get(dev, NULL); + if (IS_ERR(csi2->phy)) + return dev_err_probe(dev, PTR_ERR(csi2->phy), + "failed to get MIPI CSI-2 PHY\n"); + + csi2->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(csi2->reset)) + return dev_err_probe(dev, PTR_ERR(csi2->reset), + "failed to get reset\n"); + + csi2->formats = formats; + csi2->formats_num = ARRAY_SIZE(formats); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "failed to enable pm runtime\n"); + + ret = phy_init(csi2->phy); + if (ret) + return dev_err_probe(dev, ret, + "failed to initialize MIPI CSI-2 PHY\n"); + + ret = dw_mipi_csi2rx_register(csi2); + if (ret) + goto err_phy_exit; + + return 0; + +err_phy_exit: + phy_exit(csi2->phy); + + return ret; +} + +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); + phy_exit(csi2->phy); +} + +static int dw_mipi_csi2rx_runtime_suspend(struct device *dev) +{ + struct dw_mipi_csi2rx_device *csi2 = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(csi2->clks_num, csi2->clks); + + return 0; +} + +static int dw_mipi_csi2rx_runtime_resume(struct device *dev) +{ + struct dw_mipi_csi2rx_device *csi2 = dev_get_drvdata(dev); + int ret; + + reset_control_assert(csi2->reset); + udelay(5); + reset_control_deassert(csi2->reset); + + ret = clk_bulk_prepare_enable(csi2->clks_num, csi2->clks); + if (ret) { + dev_err(dev, "failed to enable clocks\n"); + return ret; + } + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(dw_mipi_csi2rx_pm_ops, + dw_mipi_csi2rx_runtime_suspend, + dw_mipi_csi2rx_runtime_resume, NULL); + +static struct platform_driver dw_mipi_csi2rx_drv = { + .driver = { + .name = "dw-mipi-csi2rx", + .of_match_table = dw_mipi_csi2rx_of_match, + .pm = pm_ptr(&dw_mipi_csi2rx_pm_ops), + }, + .probe = dw_mipi_csi2rx_probe, + .remove = dw_mipi_csi2rx_remove, +}; +module_platform_driver(dw_mipi_csi2rx_drv); + +MODULE_DESCRIPTION("Synopsys DesignWare MIPI CSI-2 Receiver platform driver"); +MODULE_LICENSE("GPL"); From a505ca2db89ad92a8d8d27fa68ebafb12e04a679 Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Wed, 14 Jan 2026 10:07:10 +0100 Subject: [PATCH 286/307] media: verisilicon: AV1: Fix tile info buffer size Each tile info is composed of: row_sb, col_sb, start_pos and end_pos (4 bytes each). So the total required memory is AV1_MAX_TILES * 16 bytes. Use the correct #define to allocate the buffer and avoid writing tile info in non-allocated memory. Signed-off-by: Benjamin Gaignard Fixes: 727a400686a2c ("media: verisilicon: Add Rockchip AV1 decoder") Cc: stable@vger.kernel.org Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c index 500e94bcb029..e4e21ad37323 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c @@ -381,12 +381,12 @@ int rockchip_vpu981_av1_dec_init(struct hantro_ctx *ctx) return -ENOMEM; av1_dec->global_model.size = GLOBAL_MODEL_SIZE; - av1_dec->tile_info.cpu = dma_alloc_coherent(vpu->dev, AV1_MAX_TILES, + av1_dec->tile_info.cpu = dma_alloc_coherent(vpu->dev, AV1_TILE_INFO_SIZE, &av1_dec->tile_info.dma, GFP_KERNEL); if (!av1_dec->tile_info.cpu) return -ENOMEM; - av1_dec->tile_info.size = AV1_MAX_TILES; + av1_dec->tile_info.size = AV1_TILE_INFO_SIZE; av1_dec->film_grain.cpu = dma_alloc_coherent(vpu->dev, ALIGN(sizeof(struct rockchip_av1_film_grain), 2048), From 8a5586d4ffb10751b869a02d757482fe0b3739f3 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:17 -0500 Subject: [PATCH 287/307] media: uapi: HEVC: Add v4l2_ctrl_hevc_ext_sps_[ls]t_rps controls Some hardware (e.g.: Rockchip's rk3588 hevc decoder) need to fully parse the slice header, which cannot be passed with the current controls. There is also no skip method similar as to what can be found in verisilicon HW. The SPS is therefore extended through these 2 new controls, providing the long and short term reference information from the slice header for HEVC decoding. These now controls are similar as what is passed in the Vulkan Video API with the StdVideoH265ShortTermRefPicSet and StdVideoH265LongTermRefPicsSps structures embedded in the SPS struct. Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/v4l/ext-ctrls-codec-stateless.rst | 120 ++++++++++++++++++ .../media/v4l/videodev2.h.rst.exceptions | 2 + .../media/v4l/vidioc-queryctrl.rst | 12 ++ 3 files changed, 134 insertions(+) diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst index 497ae74379f6..3b1e05c6eb13 100644 --- a/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst @@ -2959,6 +2959,126 @@ This structure contains all loop filter related parameters. See sections - 0x00000004 - +``V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS (struct)`` + Subset of the :c:type:`v4l2_ctrl_hevc_sps` control. + It extends it with the list of Long-term reference sets parameters. + These parameters are defined according to :ref:`hevc`. + They are described in section 7.4.3.2.1 "General sequence parameter set + RBSP semantics" of the specification. + This control is a dynamically sized 1-dimensional array. + The values in the array should be ignored when either + num_long_term_ref_pics_sps is 0 or the + V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT flag is not set in + :c:type:`v4l2_ctrl_hevc_sps`. + +.. c:type:: v4l2_ctrl_hevc_ext_sps_lt_rps + +.. cssclass:: longtable + +.. flat-table:: struct v4l2_ctrl_hevc_ext_sps_lt_rps + :header-rows: 0 + :stub-columns: 0 + :widths: 1 1 2 + + * - __u16 + - ``lt_ref_pic_poc_lsb_sps`` + - Long term reference picture order count as described in section 7.4.3.2.1 + "General sequence parameter set RBSP semantics" of the specification. + * - __u16 + - ``flags`` + - See :ref:`Extended Long-Term RPS Flags ` + +.. _hevc_ext_sps_lt_rps_flags: + +``Extended SPS Long-Term RPS Flags`` + +.. cssclass:: longtable + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 1 1 2 + + * - ``V4L2_HEVC_EXT_SPS_LT_RPS_FLAG_USED_LT`` + - 0x00000001 + - Specifies if the long-term reference picture is used 7.4.3.2.1 "General sequence parameter + set RBSP semantics" of the specification. + +``V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS (struct)`` + Subset of the :c:type:`v4l2_ctrl_hevc_sps` control. + It extends it with the list of Short-term reference sets parameters. + These parameters are defined according to :ref:`hevc`. + They are described in section 7.4.8 "Short-term reference picture set + semantics" of the specification. + This control is a dynamically sized 1-dimensional array. + The values in the array should be ignored when + num_short_term_ref_pic_sets is 0. + +.. c:type:: v4l2_ctrl_hevc_ext_sps_st_rps + +.. cssclass:: longtable + +.. flat-table:: struct v4l2_ctrl_hevc_ext_sps_st_rps + :header-rows: 0 + :stub-columns: 0 + :widths: 1 1 2 + + * - __u8 + - ``delta_idx_minus1`` + - Specifies the delta compare to the index. See details in section 7.4.8 "Short-term + reference picture set semantics" of the specification. + * - __u8 + - ``delta_rps_sign`` + - Sign of the delta as specified in section 7.4.8 "Short-term reference picture set + semantics" of the specification. + * - __u8 + - ``num_negative_pics`` + - Number of short-term RPS entries that have picture order count values less than the + picture order count value of the current picture. + * - __u8 + - ``num_positive_pics`` + - Number of short-term RPS entries that have picture order count values greater than the + picture order count value of the current picture. + * - __u32 + - ``used_by_curr_pic`` + - Bit i specifies if short-term RPS i is used by the current picture. + * - __u32 + - ``use_delta_flag`` + - Bit i specifies if short-term RPS i is included in the short-term RPS entries. + * - __u16 + - ``abs_delta_rps_minus1`` + - Absolute delta RPS as specified in section 7.4.8 "Short-term reference picture set + semantics" of the specification. + * - __u16 + - ``delta_poc_s0_minus1[16]`` + - Specifies the negative picture order count delta for the i-th entry in the short-term RPS. + See details in section 7.4.8 "Short-term reference picture set semantics" of the + specification. + * - __u16 + - ``delta_poc_s1_minus1[16]`` + - Specifies the positive picture order count delta for the i-th entry in the short-term RPS. + See details in section 7.4.8 "Short-term reference picture set semantics" of the + specification. + * - __u16 + - ``flags`` + - See :ref:`Extended Short-Term RPS Flags ` + +.. _hevc_ext_sps_st_rps_flags: + +``Extended SPS Short-Term RPS Flags`` + +.. cssclass:: longtable + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 1 1 2 + + * - ``V4L2_HEVC_EXT_SPS_ST_RPS_FLAG_INTER_REF_PIC_SET_PRED`` + - 0x00000001 + - Specifies if the short-term RPS is predicted from another short term RPS. See details in + section 7.4.8 "Short-term reference picture set semantics" of the specification. + .. _v4l2-codec-stateless-av1: ``V4L2_CID_STATELESS_AV1_SEQUENCE (struct)`` diff --git a/Documentation/userspace-api/media/v4l/videodev2.h.rst.exceptions b/Documentation/userspace-api/media/v4l/videodev2.h.rst.exceptions index c41693115db6..6182b4e2d2ee 100644 --- a/Documentation/userspace-api/media/v4l/videodev2.h.rst.exceptions +++ b/Documentation/userspace-api/media/v4l/videodev2.h.rst.exceptions @@ -150,6 +150,8 @@ replace symbol V4L2_CTRL_TYPE_H264_SCALING_MATRIX :c:type:`V4L.v4l2_ctrl_type` replace symbol V4L2_CTRL_TYPE_H264_PRED_WEIGHTS :c:type:`V4L.v4l2_ctrl_type` replace symbol V4L2_CTRL_TYPE_H264_SLICE_PARAMS :c:type:`V4L.v4l2_ctrl_type` replace symbol V4L2_CTRL_TYPE_H264_DECODE_PARAMS :c:type:`V4L.v4l2_ctrl_type` +replace symbol V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS :c:type:`V4L.v4l2_ctrl_type` +replace symbol V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS :c:type:`V4L.v4l2_ctrl_type` replace symbol V4L2_CTRL_TYPE_HEVC_SPS :c:type:`V4L.v4l2_ctrl_type` replace symbol V4L2_CTRL_TYPE_HEVC_PPS :c:type:`V4L.v4l2_ctrl_type` replace symbol V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS :c:type:`V4L.v4l2_ctrl_type` diff --git a/Documentation/userspace-api/media/v4l/vidioc-queryctrl.rst b/Documentation/userspace-api/media/v4l/vidioc-queryctrl.rst index c8baa9430c14..82c8b52e771c 100644 --- a/Documentation/userspace-api/media/v4l/vidioc-queryctrl.rst +++ b/Documentation/userspace-api/media/v4l/vidioc-queryctrl.rst @@ -531,6 +531,18 @@ See also the examples in :ref:`control`. - n/a - A struct :c:type:`v4l2_ctrl_hevc_decode_params`, containing HEVC decoding parameters for stateless video decoders. + * - ``V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS`` + - n/a + - n/a + - n/a + - A struct :c:type:`v4l2_ctrl_hevc_ext_sps_lt_rps`, containing HEVC + extended Long-Term RPS for stateless video decoders. + * - ``V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS`` + - n/a + - n/a + - n/a + - A struct :c:type:`v4l2_ctrl_hevc_ext_sps_st_rps`, containing HEVC + extended Short-Term RPS for stateless video decoders. * - ``V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR`` - n/a - n/a From fa05705107a40131a8335ad37817153709261738 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:18 -0500 Subject: [PATCH 288/307] media: v4l2-ctrls: Add hevc_ext_sps_[ls]t_rps controls The vdpu381 decoder found on newer Rockchip SoC need the information from the long term and short term ref pic sets from the SPS. So far, it wasn't included in the v4l2 API, so add it with new dynamic sized controls. Each element of the hevc_ext_sps_lt_rps array contains the long term ref pic set at that index. Each element of the hevc_ext_sps_st_rps contains the short term ref pic set at that index, as the raw data. It is the role of the drivers to calculate the reference sets values. Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 28 +++++++++++ drivers/media/v4l2-core/v4l2-ctrls-defs.c | 10 ++++ include/uapi/linux/v4l2-controls.h | 61 +++++++++++++++++++++++ include/uapi/linux/videodev2.h | 2 + 4 files changed, 101 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 1d4b5859f0e2..79a157975f70 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -424,6 +424,12 @@ void v4l2_ctrl_type_op_log(const struct v4l2_ctrl *ctrl) case V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS: pr_cont("HEVC_SLICE_PARAMS"); break; + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS: + pr_cont("HEVC_EXT_SPS_ST_RPS"); + break; + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS: + pr_cont("HEVC_EXT_SPS_LT_RPS"); + break; case V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX: pr_cont("HEVC_SCALING_MATRIX"); break; @@ -961,6 +967,8 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx, struct v4l2_ctrl_h264_pred_weights *p_h264_pred_weights; struct v4l2_ctrl_h264_slice_params *p_h264_slice_params; struct v4l2_ctrl_h264_decode_params *p_h264_dec_params; + struct v4l2_ctrl_hevc_ext_sps_lt_rps *p_hevc_lt_rps; + struct v4l2_ctrl_hevc_ext_sps_st_rps *p_hevc_st_rps; struct v4l2_ctrl_hevc_sps *p_hevc_sps; struct v4l2_ctrl_hevc_pps *p_hevc_pps; struct v4l2_ctrl_hdr10_mastering_display *p_hdr10_mastering; @@ -1254,6 +1262,20 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx, case V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS: break; + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS: + p_hevc_st_rps = p; + + if (p_hevc_st_rps->flags & ~V4L2_HEVC_EXT_SPS_ST_RPS_FLAG_INTER_REF_PIC_SET_PRED) + return -EINVAL; + break; + + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS: + p_hevc_lt_rps = p; + + if (p_hevc_lt_rps->flags & ~V4L2_HEVC_EXT_SPS_LT_RPS_FLAG_USED_LT) + return -EINVAL; + break; + case V4L2_CTRL_TYPE_HDR10_CLL_INFO: break; @@ -2006,6 +2028,12 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl, case V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS: elem_size = sizeof(struct v4l2_ctrl_hevc_slice_params); break; + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS: + elem_size = sizeof(struct v4l2_ctrl_hevc_ext_sps_st_rps); + break; + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS: + elem_size = sizeof(struct v4l2_ctrl_hevc_ext_sps_lt_rps); + break; case V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX: elem_size = sizeof(struct v4l2_ctrl_hevc_scaling_matrix); break; diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index 765aeeec84fe..551426c4cd01 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -1235,6 +1235,8 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_STATELESS_HEVC_DECODE_MODE: return "HEVC Decode Mode"; case V4L2_CID_STATELESS_HEVC_START_CODE: return "HEVC Start Code"; case V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS: return "HEVC Entry Point Offsets"; + case V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS: return "HEVC Short Term Ref Sets"; + case V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS: return "HEVC Long Term Ref Sets"; case V4L2_CID_STATELESS_AV1_SEQUENCE: return "AV1 Sequence Parameters"; case V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY: return "AV1 Tile Group Entry"; case V4L2_CID_STATELESS_AV1_FRAME: return "AV1 Frame Parameters"; @@ -1581,6 +1583,14 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, *type = V4L2_CTRL_TYPE_U32; *flags |= V4L2_CTRL_FLAG_DYNAMIC_ARRAY; break; + case V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS: + *type = V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS; + *flags |= V4L2_CTRL_FLAG_DYNAMIC_ARRAY; + break; + case V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS: + *type = V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS; + *flags |= V4L2_CTRL_FLAG_DYNAMIC_ARRAY; + break; case V4L2_CID_STATELESS_VP9_COMPRESSED_HDR: *type = V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR; break; diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 572622e4535e..68dd0c4e47b2 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -2101,6 +2101,8 @@ struct v4l2_ctrl_mpeg2_quantisation { #define V4L2_CID_STATELESS_HEVC_DECODE_MODE (V4L2_CID_CODEC_STATELESS_BASE + 405) #define V4L2_CID_STATELESS_HEVC_START_CODE (V4L2_CID_CODEC_STATELESS_BASE + 406) #define V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS (V4L2_CID_CODEC_STATELESS_BASE + 407) +#define V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS (V4L2_CID_CODEC_STATELESS_BASE + 408) +#define V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS (V4L2_CID_CODEC_STATELESS_BASE + 409) enum v4l2_stateless_hevc_decode_mode { V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, @@ -2556,6 +2558,65 @@ struct v4l2_ctrl_hevc_scaling_matrix { __u8 scaling_list_dc_coef_32x32[2]; }; +#define V4L2_HEVC_EXT_SPS_ST_RPS_FLAG_INTER_REF_PIC_SET_PRED 0x1 + +/* + * struct v4l2_ctrl_hevc_ext_sps_st_rps - HEVC short term RPS parameters + * + * Dynamic size 1-dimension array for short term RPS. The number of elements + * is v4l2_ctrl_hevc_sps::num_short_term_ref_pic_sets. It can contain up to 65 elements. + * + * @delta_idx_minus1: Specifies the delta compare to the index. See details in section 7.4.8 + * "Short-term reference picture set semantics" of the specification. + * @delta_rps_sign: Sign of the delta as specified in section 7.4.8 "Short-term reference picture + * set semantics" of the specification. + * @abs_delta_rps_minus1: Absolute delta RPS as specified in section 7.4.8 "Short-term reference + * picture set semantics" of the specification. + * @num_negative_pics: Number of short-term RPS entries that have picture order count values less + * than the picture order count value of the current picture. + * @num_positive_pics: Number of short-term RPS entries that have picture order count values + * greater than the picture order count value of the current picture. + * @used_by_curr_pic: Bit j specifies if short-term RPS j is used by the current picture. + * @use_delta_flag: Bit j equals to 1 specifies that the j-th entry in the source candidate + * short-term RPS is included in this candidate short-term RPS. + * @delta_poc_s0_minus1: Specifies the negative picture order count delta for the i-th entry in + * the short-term RPS. See details in section 7.4.8 "Short-term reference + * picture set semantics" of the specification. + * @delta_poc_s1_minus1: Specifies the positive picture order count delta for the i-th entry in + * the short-term RPS. See details in section 7.4.8 "Short-term reference + * picture set semantics" of the specification. + * @flags: See V4L2_HEVC_EXT_SPS_ST_RPS_FLAG_{} + */ +struct v4l2_ctrl_hevc_ext_sps_st_rps { + __u8 delta_idx_minus1; + __u8 delta_rps_sign; + __u8 num_negative_pics; + __u8 num_positive_pics; + __u32 used_by_curr_pic; + __u32 use_delta_flag; + __u16 abs_delta_rps_minus1; + __u16 delta_poc_s0_minus1[16]; + __u16 delta_poc_s1_minus1[16]; + __u16 flags; +}; + +#define V4L2_HEVC_EXT_SPS_LT_RPS_FLAG_USED_LT 0x1 + +/* + * struct v4l2_ctrl_hevc_ext_sps_lt_rps - HEVC long term RPS parameters + * + * Dynamic size 1-dimension array for long term RPS. The number of elements + * is v4l2_ctrl_hevc_sps::num_long_term_ref_pics_sps. It can contain up to 65 elements. + * + * @lt_ref_pic_poc_lsb_sps: picture order count modulo MaxPicOrderCntLsb of the i-th candidate + * long-term reference picture. + * @flags: See V4L2_HEVC_EXT_SPS_LT_RPS_FLAG_{} + */ +struct v4l2_ctrl_hevc_ext_sps_lt_rps { + __u16 lt_ref_pic_poc_lsb_sps; + __u16 flags; +}; + /* Stateless VP9 controls */ #define V4L2_VP9_LOOP_FILTER_FLAG_DELTA_ENABLED 0x1 diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 848e86617d5c..eda4492e40dc 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -1986,6 +1986,8 @@ enum v4l2_ctrl_type { V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS = 0x0272, V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX = 0x0273, V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS = 0x0274, + V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS = 0x0275, + V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS = 0x0276, V4L2_CTRL_TYPE_AV1_SEQUENCE = 0x280, V4L2_CTRL_TYPE_AV1_TILE_GROUP_ENTRY = 0x281, From 4cb9cd80b36ea977c468415eb2730ae4435505cc Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:19 -0500 Subject: [PATCH 289/307] media: visl: Add HEVC short and long term RPS sets Log the recently added v4l2 controls to set HEVC short and long term RPS sets with 2 new ftrace entries. Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/visl/visl-dec.c | 7 +++ drivers/media/test-drivers/visl/visl-dec.h | 3 + .../media/test-drivers/visl/visl-trace-hevc.h | 59 +++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/drivers/media/test-drivers/visl/visl-dec.c b/drivers/media/test-drivers/visl/visl-dec.c index d90b79de8384..6bbf93757047 100644 --- a/drivers/media/test-drivers/visl/visl-dec.c +++ b/drivers/media/test-drivers/visl/visl-dec.c @@ -547,6 +547,9 @@ static void visl_trace_ctrls(struct visl_ctx *ctx, struct visl_run *run) trace_v4l2_hevc_dpb_entry(&run->hevc.dpram->dpb[i]); trace_v4l2_hevc_pred_weight_table(&run->hevc.spram->pred_weight_table); + trace_v4l2_ctrl_hevc_ext_sps_lt_rps(run->hevc.rps_lt); + trace_v4l2_ctrl_hevc_ext_sps_st_rps(run->hevc.rps_st); + break; case VISL_CODEC_AV1: trace_v4l2_ctrl_av1_sequence(run->av1.seq); @@ -611,6 +614,10 @@ void visl_device_run(void *priv) run.hevc.spram = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SLICE_PARAMS); run.hevc.sm = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SCALING_MATRIX); run.hevc.dpram = visl_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_DECODE_PARAMS); + run.hevc.rps_lt = visl_find_control_data(ctx, + V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS); + run.hevc.rps_st = visl_find_control_data(ctx, + V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS); break; case VISL_CODEC_AV1: run.av1.seq = visl_find_control_data(ctx, V4L2_CID_STATELESS_AV1_SEQUENCE); diff --git a/drivers/media/test-drivers/visl/visl-dec.h b/drivers/media/test-drivers/visl/visl-dec.h index c2c2ef3a8798..6e7562e555bb 100644 --- a/drivers/media/test-drivers/visl/visl-dec.h +++ b/drivers/media/test-drivers/visl/visl-dec.h @@ -7,6 +7,7 @@ #ifndef _VISL_DEC_H_ #define _VISL_DEC_H_ +#include "linux/v4l2-controls.h" #include "visl.h" struct visl_fwht_run { @@ -43,6 +44,8 @@ struct visl_hevc_run { const struct v4l2_ctrl_hevc_slice_params *spram; const struct v4l2_ctrl_hevc_scaling_matrix *sm; const struct v4l2_ctrl_hevc_decode_params *dpram; + const struct v4l2_ctrl_hevc_ext_sps_lt_rps *rps_lt; + const struct v4l2_ctrl_hevc_ext_sps_st_rps *rps_st; }; struct visl_av1_run { diff --git a/drivers/media/test-drivers/visl/visl-trace-hevc.h b/drivers/media/test-drivers/visl/visl-trace-hevc.h index 837b8ec12e97..963914c463db 100644 --- a/drivers/media/test-drivers/visl/visl-trace-hevc.h +++ b/drivers/media/test-drivers/visl/visl-trace-hevc.h @@ -1,4 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0+ */ +#include "linux/v4l2-controls.h" #if !defined(_VISL_TRACE_HEVC_H_) || defined(TRACE_HEADER_MULTI_READ) #define _VISL_TRACE_HEVC_H_ @@ -343,6 +344,54 @@ DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_decode_params_tmpl, )) ); +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_ext_sps_lt_rps_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_ext_sps_lt_rps *lt), + TP_ARGS(lt), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_ext_sps_lt_rps, lt)), + TP_fast_assign(__entry->lt = *lt), + TP_printk("\nflags %s\n" + "lt_ref_pic_poc_lsb_sps %x\n", + __print_flags(__entry->lt.flags, "|", + {V4L2_HEVC_EXT_SPS_LT_RPS_FLAG_USED_LT, "USED_LT"} + ), + __entry->lt.lt_ref_pic_poc_lsb_sps + ) +) + +DECLARE_EVENT_CLASS(v4l2_ctrl_hevc_ext_sps_st_rps_tmpl, + TP_PROTO(const struct v4l2_ctrl_hevc_ext_sps_st_rps *st), + TP_ARGS(st), + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_hevc_ext_sps_st_rps, st)), + TP_fast_assign(__entry->st = *st), + TP_printk("\nflags %s\n" + "delta_idx_minus1: %u\n" + "delta_rps_sign: %u\n" + "abs_delta_rps_minus1: %u\n" + "num_negative_pics: %u\n" + "num_positive_pics: %u\n" + "used_by_curr_pic: %08x\n" + "use_delta_flag: %08x\n" + "delta_poc_s0_minus1: %s\n" + "delta_poc_s1_minus1: %s\n", + __print_flags(__entry->st.flags, "|", + {V4L2_HEVC_EXT_SPS_ST_RPS_FLAG_INTER_REF_PIC_SET_PRED, "INTER_REF_PIC_SET_PRED"} + ), + __entry->st.delta_idx_minus1, + __entry->st.delta_rps_sign, + __entry->st.abs_delta_rps_minus1, + __entry->st.num_negative_pics, + __entry->st.num_positive_pics, + __entry->st.used_by_curr_pic, + __entry->st.use_delta_flag, + __print_array(__entry->st.delta_poc_s0_minus1, + ARRAY_SIZE(__entry->st.delta_poc_s0_minus1), + sizeof(__entry->st.delta_poc_s0_minus1[0])), + __print_array(__entry->st.delta_poc_s1_minus1, + ARRAY_SIZE(__entry->st.delta_poc_s1_minus1), + sizeof(__entry->st.delta_poc_s1_minus1[0])) + ) +) + DECLARE_EVENT_CLASS(v4l2_hevc_dpb_entry_tmpl, TP_PROTO(const struct v4l2_hevc_dpb_entry *e), @@ -391,6 +440,16 @@ DEFINE_EVENT(v4l2_ctrl_hevc_decode_params_tmpl, v4l2_ctrl_hevc_decode_params, TP_ARGS(d) ); +DEFINE_EVENT(v4l2_ctrl_hevc_ext_sps_lt_rps_tmpl, v4l2_ctrl_hevc_ext_sps_lt_rps, + TP_PROTO(const struct v4l2_ctrl_hevc_ext_sps_lt_rps *lt), + TP_ARGS(lt) +); + +DEFINE_EVENT(v4l2_ctrl_hevc_ext_sps_st_rps_tmpl, v4l2_ctrl_hevc_ext_sps_st_rps, + TP_PROTO(const struct v4l2_ctrl_hevc_ext_sps_st_rps *st), + TP_ARGS(st) +); + DEFINE_EVENT(v4l2_hevc_dpb_entry_tmpl, v4l2_hevc_dpb_entry, TP_PROTO(const struct v4l2_hevc_dpb_entry *e), TP_ARGS(e) From dc6898981f744cf54c29313f5a399260933cdf66 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:20 -0500 Subject: [PATCH 290/307] media: rkvdec: Switch to using structs instead of writel In an effort to merge the rkvdec2 driver [1] with this one, switch from writel() calls to using structs to represent the register mappings. This is done in order to have all supported decoders use the same format in the future and ease reading of the code. Using structs also improves stability as the hardware is tested and validated downstream using a similar method. It was noticed, on decoders, that: - Some registers require to be writen in increasing order [2] - Some registers, even if unrelated, need to be written to their reset values (it was the case here for axi_ddr_[rw]data). Using structs can also help improving performance later when, e.g. multicore support is added on RK3588. Performance seems to be slightly improved, but at least, not made worse. Running fluster's JVT-AVC_V1 test suite with GStreamer on the Radxa ROCK PI 4 SE gives the following times: Before this patch: - --jobs 1: Ran 129/135 tests successfully in 77.167 secs - --jobs 6: Ran 129/135 tests successfully in 23.046 secs With this patch: - --jobs 1: Ran 129/135 tests successfully in 70.698 secs - --jobs 6: Ran 129/135 tests successfully in 22.917 secs This also shows that the fluster score hasn't changed. [1]: https://lore.kernel.org/all/20250325213303.826925-1-detlev.casanova@collabora.com/ [2]: https://lore.kernel.org/all/20200127143009.15677-5-andrzej.p@collabora.com/ Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../platform/rockchip/rkvdec/rkvdec-h264.c | 166 +++-- .../platform/rockchip/rkvdec/rkvdec-hevc.c | 64 +- .../platform/rockchip/rkvdec/rkvdec-regs.h | 586 ++++++++++++------ .../platform/rockchip/rkvdec/rkvdec-vp9.c | 230 +++---- .../media/platform/rockchip/rkvdec/rkvdec.c | 10 +- .../media/platform/rockchip/rkvdec/rkvdec.h | 1 + 6 files changed, 603 insertions(+), 454 deletions(-) diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c index d14b4d173448..68e20cb81a88 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c @@ -115,6 +115,7 @@ struct rkvdec_h264_run { struct rkvdec_h264_ctx { struct rkvdec_aux_buf priv_tbl; struct rkvdec_h264_reflists reflists; + struct rkvdec_regs regs; }; #define CABAC_ENTRY(ctxidx, idc0_m, idc0_n, idc1_m, idc1_n, \ @@ -841,45 +842,41 @@ static void assemble_hw_scaling_list(struct rkvdec_ctx *ctx, } /* - * dpb poc related registers table + * Set the ref POC in the correct register. + * + * The 32 registers are spread across 3 regions, each alternating top and bottom ref POCs: + * - 1: ref 0 to 14 contain top 0 to 7 and bottoms 0 to 6 + * - 2: ref 15 to 29 contain top 8 to 14 and bottoms 7 to 14 + * - 3: ref 30 and 31 which correspond to top 15 and bottom 15 respectively. */ -static const u32 poc_reg_tbl_top_field[16] = { - RKVDEC_REG_H264_POC_REFER0(0), - RKVDEC_REG_H264_POC_REFER0(2), - RKVDEC_REG_H264_POC_REFER0(4), - RKVDEC_REG_H264_POC_REFER0(6), - RKVDEC_REG_H264_POC_REFER0(8), - RKVDEC_REG_H264_POC_REFER0(10), - RKVDEC_REG_H264_POC_REFER0(12), - RKVDEC_REG_H264_POC_REFER0(14), - RKVDEC_REG_H264_POC_REFER1(1), - RKVDEC_REG_H264_POC_REFER1(3), - RKVDEC_REG_H264_POC_REFER1(5), - RKVDEC_REG_H264_POC_REFER1(7), - RKVDEC_REG_H264_POC_REFER1(9), - RKVDEC_REG_H264_POC_REFER1(11), - RKVDEC_REG_H264_POC_REFER1(13), - RKVDEC_REG_H264_POC_REFER2(0) -}; - -static const u32 poc_reg_tbl_bottom_field[16] = { - RKVDEC_REG_H264_POC_REFER0(1), - RKVDEC_REG_H264_POC_REFER0(3), - RKVDEC_REG_H264_POC_REFER0(5), - RKVDEC_REG_H264_POC_REFER0(7), - RKVDEC_REG_H264_POC_REFER0(9), - RKVDEC_REG_H264_POC_REFER0(11), - RKVDEC_REG_H264_POC_REFER0(13), - RKVDEC_REG_H264_POC_REFER1(0), - RKVDEC_REG_H264_POC_REFER1(2), - RKVDEC_REG_H264_POC_REFER1(4), - RKVDEC_REG_H264_POC_REFER1(6), - RKVDEC_REG_H264_POC_REFER1(8), - RKVDEC_REG_H264_POC_REFER1(10), - RKVDEC_REG_H264_POC_REFER1(12), - RKVDEC_REG_H264_POC_REFER1(14), - RKVDEC_REG_H264_POC_REFER2(1) -}; +static void set_poc_reg(struct rkvdec_regs *regs, uint32_t poc, int id, bool bottom) +{ + if (!bottom) { + switch (id) { + case 0 ... 7: + regs->h26x.ref0_14_poc[id * 2] = poc; + break; + case 8 ... 14: + regs->h26x.ref15_29_poc[(id - 8) * 2 + 1] = poc; + break; + case 15: + regs->h26x.ref30_poc = poc; + break; + } + } else { + switch (id) { + case 0 ... 6: + regs->h26x.ref0_14_poc[id * 2 + 1] = poc; + break; + case 7 ... 14: + regs->h26x.ref15_29_poc[(id - 7) * 2] = poc; + break; + case 15: + regs->h26x.ref31_poc = poc; + break; + } + } +} static void config_registers(struct rkvdec_ctx *ctx, struct rkvdec_h264_run *run) @@ -894,6 +891,7 @@ static void config_registers(struct rkvdec_ctx *ctx, struct vb2_v4l2_buffer *src_buf = run->base.bufs.src; struct vb2_v4l2_buffer *dst_buf = run->base.bufs.dst; const struct v4l2_format *f; + struct rkvdec_regs *regs = &h264_ctx->regs; dma_addr_t rlc_addr; dma_addr_t refer_addr; u32 rlc_len; @@ -903,10 +901,11 @@ static void config_registers(struct rkvdec_ctx *ctx, u32 yuv_virstride = 0; u32 offset; dma_addr_t dst_addr; - u32 reg, i; + u32 i; - reg = RKVDEC_MODE(RKVDEC_MODE_H264); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_SYSCTRL); + memset(regs, 0, sizeof(*regs)); + + regs->common.reg02.dec_mode = RKVDEC_MODE_H264; f = &ctx->decoded_fmt; dst_fmt = &f->fmt.pix_mp; @@ -921,39 +920,35 @@ static void config_registers(struct rkvdec_ctx *ctx, else if (sps->chroma_format_idc == 2) yuv_virstride = 2 * y_virstride; - reg = RKVDEC_Y_HOR_VIRSTRIDE(hor_virstride / 16) | - RKVDEC_UV_HOR_VIRSTRIDE(hor_virstride / 16) | - RKVDEC_SLICE_NUM_HIGHBIT | - RKVDEC_SLICE_NUM_LOWBITS(0x7ff); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_PICPAR); + regs->common.reg03.uv_hor_virstride = hor_virstride / 16; + regs->common.reg03.y_hor_virstride = hor_virstride / 16; + regs->common.reg03.slice_num_highbit = 1; + regs->common.reg03.slice_num_lowbits = 0x7ff; /* config rlc base address */ rlc_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); - writel_relaxed(rlc_addr, rkvdec->regs + RKVDEC_REG_STRM_RLC_BASE); - writel_relaxed(rlc_addr, rkvdec->regs + RKVDEC_REG_RLCWRITE_BASE); + regs->common.strm_rlc_base = rlc_addr; + regs->h26x.rlcwrite_base = rlc_addr; rlc_len = vb2_get_plane_payload(&src_buf->vb2_buf, 0); - reg = RKVDEC_STRM_LEN(rlc_len); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_STRM_LEN); + regs->common.stream_len = rlc_len; /* config cabac table */ offset = offsetof(struct rkvdec_h264_priv_tbl, cabac_table); - writel_relaxed(priv_start_addr + offset, - rkvdec->regs + RKVDEC_REG_CABACTBL_PROB_BASE); + regs->common.cabactbl_base = priv_start_addr + offset; /* config output base address */ dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); - writel_relaxed(dst_addr, rkvdec->regs + RKVDEC_REG_DECOUT_BASE); + regs->common.decout_base = dst_addr; - reg = RKVDEC_Y_VIRSTRIDE(y_virstride / 16); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_Y_VIRSTRIDE); + regs->common.reg08.y_virstride = y_virstride / 16; - reg = RKVDEC_YUV_VIRSTRIDE(yuv_virstride / 16); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_YUV_VIRSTRIDE); + regs->common.reg09.yuv_virstride = yuv_virstride / 16; /* config ref pic address & poc */ for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { struct vb2_buffer *vb_buf = run->ref_buf[i]; + struct ref_base *base; /* * If a DPB entry is unused or invalid, address of current destination @@ -963,54 +958,37 @@ static void config_registers(struct rkvdec_ctx *ctx, vb_buf = &dst_buf->vb2_buf; refer_addr = vb2_dma_contig_plane_dma_addr(vb_buf, 0); - if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE) - refer_addr |= RKVDEC_COLMV_USED_FLAG_REF; - if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_FIELD) - refer_addr |= RKVDEC_FIELD_REF; - - if (dpb[i].fields & V4L2_H264_TOP_FIELD_REF) - refer_addr |= RKVDEC_TOPFIELD_USED_REF; - if (dpb[i].fields & V4L2_H264_BOTTOM_FIELD_REF) - refer_addr |= RKVDEC_BOTFIELD_USED_REF; - - writel_relaxed(dpb[i].top_field_order_cnt, - rkvdec->regs + poc_reg_tbl_top_field[i]); - writel_relaxed(dpb[i].bottom_field_order_cnt, - rkvdec->regs + poc_reg_tbl_bottom_field[i]); - if (i < V4L2_H264_NUM_DPB_ENTRIES - 1) - writel_relaxed(refer_addr, - rkvdec->regs + RKVDEC_REG_H264_BASE_REFER(i)); + base = ®s->h26x.ref0_14_base[i]; else - writel_relaxed(refer_addr, - rkvdec->regs + RKVDEC_REG_H264_BASE_REFER15); + base = ®s->h26x.ref15_base; + + base->base_addr = refer_addr >> 4; + base->field_ref = !!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_FIELD); + base->colmv_use_flag_ref = !!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE); + base->topfield_used_ref = !!(dpb[i].fields & V4L2_H264_TOP_FIELD_REF); + base->botfield_used_ref = !!(dpb[i].fields & V4L2_H264_BOTTOM_FIELD_REF); + + set_poc_reg(regs, dpb[i].top_field_order_cnt, i, false); + set_poc_reg(regs, dpb[i].bottom_field_order_cnt, i, true); } - reg = RKVDEC_CUR_POC(dec_params->top_field_order_cnt); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_CUR_POC0); - - reg = RKVDEC_CUR_POC(dec_params->bottom_field_order_cnt); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_CUR_POC1); + regs->h26x.cur_poc = dec_params->top_field_order_cnt; + regs->h26x.cur_poc1 = dec_params->bottom_field_order_cnt; /* config hw pps address */ offset = offsetof(struct rkvdec_h264_priv_tbl, param_set); - writel_relaxed(priv_start_addr + offset, - rkvdec->regs + RKVDEC_REG_PPS_BASE); + regs->h26x.pps_base = priv_start_addr + offset; /* config hw rps address */ offset = offsetof(struct rkvdec_h264_priv_tbl, rps); - writel_relaxed(priv_start_addr + offset, - rkvdec->regs + RKVDEC_REG_RPS_BASE); - - reg = RKVDEC_AXI_DDR_RDATA(0); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_AXI_DDR_RDATA); - - reg = RKVDEC_AXI_DDR_WDATA(0); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_AXI_DDR_WDATA); + regs->h26x.rps_base = priv_start_addr + offset; offset = offsetof(struct rkvdec_h264_priv_tbl, err_info); - writel_relaxed(priv_start_addr + offset, - rkvdec->regs + RKVDEC_REG_H264_ERRINFO_BASE); + regs->h26x.errorinfo_base = priv_start_addr + offset; + + rkvdec_memcpy_toio(rkvdec->regs, regs, + MIN(sizeof(*regs), sizeof(u32) * rkvdec->variant->num_regs)); } #define RKVDEC_H264_MAX_DEPTH_IN_BYTES 2 @@ -1181,8 +1159,6 @@ static int rkvdec_h264_run(struct rkvdec_ctx *ctx) schedule_delayed_work(&rkvdec->watchdog_work, msecs_to_jiffies(2000)); - writel(0, rkvdec->regs + RKVDEC_REG_STRMD_ERR_EN); - writel(0, rkvdec->regs + RKVDEC_REG_H264_ERR_E); writel(1, rkvdec->regs + RKVDEC_REG_PREF_LUMA_CACHE_COMMAND); writel(1, rkvdec->regs + RKVDEC_REG_PREF_CHR_CACHE_COMMAND); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c index fc7e6a260b0a..2d9e0e947a6d 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c @@ -129,6 +129,7 @@ struct rkvdec_hevc_run { struct rkvdec_hevc_ctx { struct rkvdec_aux_buf priv_tbl; struct v4l2_ctrl_hevc_scaling_matrix scaling_matrix_cache; + struct rkvdec_regs regs; }; struct scaling_factor { @@ -548,6 +549,7 @@ static void config_registers(struct rkvdec_ctx *ctx, const struct v4l2_ctrl_hevc_slice_params *sl_params = &run->slices_params[0]; const struct v4l2_hevc_dpb_entry *dpb = decode_params->dpb; struct rkvdec_hevc_ctx *hevc_ctx = ctx->priv; + struct rkvdec_regs *regs = &hevc_ctx->regs; dma_addr_t priv_start_addr = hevc_ctx->priv_tbl.dma; const struct v4l2_pix_format_mplane *dst_fmt; struct vb2_v4l2_buffer *src_buf = run->base.bufs.src; @@ -564,8 +566,9 @@ static void config_registers(struct rkvdec_ctx *ctx, dma_addr_t dst_addr; u32 reg, i; - reg = RKVDEC_MODE(RKVDEC_MODE_HEVC); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_SYSCTRL); + memset(regs, 0, sizeof(*regs)); + + regs->common.reg02.dec_mode = RKVDEC_MODE_HEVC; f = &ctx->decoded_fmt; dst_fmt = &f->fmt.pix_mp; @@ -580,33 +583,27 @@ static void config_registers(struct rkvdec_ctx *ctx, else if (sps->chroma_format_idc == 2) yuv_virstride = 2 * y_virstride; - reg = RKVDEC_Y_HOR_VIRSTRIDE(hor_virstride / 16) | - RKVDEC_UV_HOR_VIRSTRIDE(hor_virstride / 16) | - RKVDEC_SLICE_NUM_LOWBITS(run->num_slices); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_PICPAR); + regs->common.reg03.slice_num_lowbits = run->num_slices; + regs->common.reg03.uv_hor_virstride = hor_virstride / 16; + regs->common.reg03.y_hor_virstride = hor_virstride / 16; /* config rlc base address */ rlc_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); - writel_relaxed(rlc_addr, rkvdec->regs + RKVDEC_REG_STRM_RLC_BASE); + regs->common.strm_rlc_base = rlc_addr; rlc_len = vb2_get_plane_payload(&src_buf->vb2_buf, 0); - reg = RKVDEC_STRM_LEN(round_up(rlc_len, 16) + 64); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_STRM_LEN); + regs->common.stream_len = round_up(rlc_len, 16) + 64; /* config cabac table */ offset = offsetof(struct rkvdec_hevc_priv_tbl, cabac_table); - writel_relaxed(priv_start_addr + offset, - rkvdec->regs + RKVDEC_REG_CABACTBL_PROB_BASE); + regs->common.cabactbl_base = priv_start_addr + offset; /* config output base address */ dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); - writel_relaxed(dst_addr, rkvdec->regs + RKVDEC_REG_DECOUT_BASE); + regs->common.decout_base = dst_addr; - reg = RKVDEC_Y_VIRSTRIDE(y_virstride / 16); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_Y_VIRSTRIDE); - - reg = RKVDEC_YUV_VIRSTRIDE(yuv_virstride / 16); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_YUV_VIRSTRIDE); + regs->common.reg08.y_virstride = y_virstride / 16; + regs->common.reg09.yuv_virstride = yuv_virstride / 16; /* config ref pic address */ for (i = 0; i < 15; i++) { @@ -620,33 +617,30 @@ static void config_registers(struct rkvdec_ctx *ctx, } refer_addr = vb2_dma_contig_plane_dma_addr(vb_buf, 0); - writel_relaxed(refer_addr | reg, - rkvdec->regs + RKVDEC_REG_H264_BASE_REFER(i)); - reg = RKVDEC_POC_REFER(i < decode_params->num_active_dpb_entries ? - dpb[i].pic_order_cnt_val : 0); - writel_relaxed(reg, - rkvdec->regs + RKVDEC_REG_H264_POC_REFER0(i)); + regs->h26x.ref0_14_base[i].base_addr = refer_addr >> 4; + regs->h26x.ref0_14_base[i].field_ref = !!(reg & 1); + regs->h26x.ref0_14_base[i].topfield_used_ref = !!(reg & 2); + regs->h26x.ref0_14_base[i].botfield_used_ref = !!(reg & 4); + regs->h26x.ref0_14_base[i].colmv_use_flag_ref = !!(reg & 8); + + regs->h26x.ref0_14_poc[i] = i < decode_params->num_active_dpb_entries + ? dpb[i].pic_order_cnt_val + : 0; } - reg = RKVDEC_CUR_POC(sl_params->slice_pic_order_cnt); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_CUR_POC0); + regs->h26x.cur_poc = sl_params->slice_pic_order_cnt; /* config hw pps address */ offset = offsetof(struct rkvdec_hevc_priv_tbl, param_set); - writel_relaxed(priv_start_addr + offset, - rkvdec->regs + RKVDEC_REG_PPS_BASE); + regs->h26x.pps_base = priv_start_addr + offset; /* config hw rps address */ offset = offsetof(struct rkvdec_hevc_priv_tbl, rps); - writel_relaxed(priv_start_addr + offset, - rkvdec->regs + RKVDEC_REG_RPS_BASE); + regs->h26x.rps_base = priv_start_addr + offset; - reg = RKVDEC_AXI_DDR_RDATA(0); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_AXI_DDR_RDATA); - - reg = RKVDEC_AXI_DDR_WDATA(0); - writel_relaxed(reg, rkvdec->regs + RKVDEC_REG_AXI_DDR_WDATA); + rkvdec_memcpy_toio(rkvdec->regs, regs, + MIN(sizeof(*regs), sizeof(u32) * rkvdec->variant->num_regs)); } #define RKVDEC_HEVC_MAX_DEPTH_IN_BYTES 2 @@ -784,8 +778,6 @@ static int rkvdec_hevc_run(struct rkvdec_ctx *ctx) schedule_delayed_work(&rkvdec->watchdog_work, msecs_to_jiffies(2000)); - writel(0, rkvdec->regs + RKVDEC_REG_STRMD_ERR_EN); - writel(0, rkvdec->regs + RKVDEC_REG_H264_ERR_E); writel(1, rkvdec->regs + RKVDEC_REG_PREF_LUMA_CACHE_COMMAND); writel(1, rkvdec->regs + RKVDEC_REG_PREF_CHR_CACHE_COMMAND); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-regs.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-regs.h index c627b6b6f53a..1af66c5f1c9b 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-regs.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-regs.h @@ -3,7 +3,12 @@ #ifndef RKVDEC_REGS_H_ #define RKVDEC_REGS_H_ -/* rkvcodec registers */ +#include + +/* + * REG_INTERRUPT is accessed via writel to enable the decoder after + * configuring it and clear interrupt strmd_error_status + */ #define RKVDEC_REG_INTERRUPT 0x004 #define RKVDEC_INTERRUPT_DEC_E BIT(0) #define RKVDEC_CONFIG_DEC_CLK_GATE_E BIT(1) @@ -30,198 +35,399 @@ #define RKVDEC_SOFTRESET_RDY BIT(22) #define RKVDEC_WR_DDR_ALIGN_EN BIT(23) -#define RKVDEC_REG_SYSCTRL 0x008 -#define RKVDEC_IN_ENDIAN BIT(0) -#define RKVDEC_IN_SWAP32_E BIT(1) -#define RKVDEC_IN_SWAP64_E BIT(2) -#define RKVDEC_STR_ENDIAN BIT(3) -#define RKVDEC_STR_SWAP32_E BIT(4) -#define RKVDEC_STR_SWAP64_E BIT(5) -#define RKVDEC_OUT_ENDIAN BIT(6) -#define RKVDEC_OUT_SWAP32_E BIT(7) -#define RKVDEC_OUT_CBCR_SWAP BIT(8) -#define RKVDEC_RLC_MODE_DIRECT_WRITE BIT(10) -#define RKVDEC_RLC_MODE BIT(11) -#define RKVDEC_STRM_START_BIT(x) (((x) & 0x7f) << 12) -#define RKVDEC_MODE(x) (((x) & 0x03) << 20) -#define RKVDEC_MODE_HEVC 0 -#define RKVDEC_MODE_H264 1 -#define RKVDEC_MODE_VP9 2 -#define RKVDEC_RPS_MODE BIT(24) -#define RKVDEC_STRM_MODE BIT(25) -#define RKVDEC_H264_STRM_LASTPKT BIT(26) -#define RKVDEC_H264_FIRSTSLICE_FLAG BIT(27) -#define RKVDEC_H264_FRAME_ORSLICE BIT(28) -#define RKVDEC_BUSPR_SLOT_DIS BIT(29) - -#define RKVDEC_REG_PICPAR 0x00C -#define RKVDEC_Y_HOR_VIRSTRIDE(x) ((x) & 0x1ff) -#define RKVDEC_SLICE_NUM_HIGHBIT BIT(11) -#define RKVDEC_UV_HOR_VIRSTRIDE(x) (((x) & 0x1ff) << 12) -#define RKVDEC_SLICE_NUM_LOWBITS(x) (((x) & 0x7ff) << 21) - -#define RKVDEC_REG_STRM_RLC_BASE 0x010 - -#define RKVDEC_REG_STRM_LEN 0x014 -#define RKVDEC_STRM_LEN(x) ((x) & 0x7ffffff) - -#define RKVDEC_REG_CABACTBL_PROB_BASE 0x018 -#define RKVDEC_REG_DECOUT_BASE 0x01C - -#define RKVDEC_REG_Y_VIRSTRIDE 0x020 -#define RKVDEC_Y_VIRSTRIDE(x) ((x) & 0xfffff) - -#define RKVDEC_REG_YUV_VIRSTRIDE 0x024 -#define RKVDEC_YUV_VIRSTRIDE(x) ((x) & 0x1fffff) -#define RKVDEC_REG_H264_BASE_REFER(i) (((i) * 0x04) + 0x028) - -#define RKVDEC_REG_H264_BASE_REFER15 0x0C0 -#define RKVDEC_FIELD_REF BIT(0) -#define RKVDEC_TOPFIELD_USED_REF BIT(1) -#define RKVDEC_BOTFIELD_USED_REF BIT(2) -#define RKVDEC_COLMV_USED_FLAG_REF BIT(3) - -#define RKVDEC_REG_VP9_LAST_FRAME_BASE 0x02c -#define RKVDEC_REG_VP9_GOLDEN_FRAME_BASE 0x030 -#define RKVDEC_REG_VP9_ALTREF_FRAME_BASE 0x034 - -#define RKVDEC_REG_VP9_CPRHEADER_OFFSET 0x028 -#define RKVDEC_VP9_CPRHEADER_OFFSET(x) ((x) & 0xffff) - -#define RKVDEC_REG_VP9_REFERLAST_BASE 0x02C -#define RKVDEC_REG_VP9_REFERGOLDEN_BASE 0x030 -#define RKVDEC_REG_VP9_REFERALFTER_BASE 0x034 - -#define RKVDEC_REG_VP9COUNT_BASE 0x038 -#define RKVDEC_VP9COUNT_UPDATE_EN BIT(0) - -#define RKVDEC_REG_VP9_SEGIDLAST_BASE 0x03C -#define RKVDEC_REG_VP9_SEGIDCUR_BASE 0x040 -#define RKVDEC_REG_VP9_FRAME_SIZE(i) ((i) * 0x04 + 0x044) -#define RKVDEC_VP9_FRAMEWIDTH(x) (((x) & 0xffff) << 0) -#define RKVDEC_VP9_FRAMEHEIGHT(x) (((x) & 0xffff) << 16) - -#define RKVDEC_VP9_SEGID_GRP(i) ((i) * 0x04 + 0x050) -#define RKVDEC_SEGID_ABS_DELTA(x) ((x) & 0x1) -#define RKVDEC_SEGID_FRAME_QP_DELTA_EN(x) (((x) & 0x1) << 1) -#define RKVDEC_SEGID_FRAME_QP_DELTA(x) (((x) & 0x1ff) << 2) -#define RKVDEC_SEGID_FRAME_LOOPFILTER_VALUE_EN(x) (((x) & 0x1) << 11) -#define RKVDEC_SEGID_FRAME_LOOPFILTER_VALUE(x) (((x) & 0x7f) << 12) -#define RKVDEC_SEGID_REFERINFO_EN(x) (((x) & 0x1) << 19) -#define RKVDEC_SEGID_REFERINFO(x) (((x) & 0x03) << 20) -#define RKVDEC_SEGID_FRAME_SKIP_EN(x) (((x) & 0x1) << 22) - -#define RKVDEC_VP9_CPRHEADER_CONFIG 0x070 -#define RKVDEC_VP9_TX_MODE(x) ((x) & 0x07) -#define RKVDEC_VP9_FRAME_REF_MODE(x) (((x) & 0x03) << 3) - -#define RKVDEC_VP9_REF_SCALE(i) ((i) * 0x04 + 0x074) -#define RKVDEC_VP9_REF_HOR_SCALE(x) ((x) & 0xffff) -#define RKVDEC_VP9_REF_VER_SCALE(x) (((x) & 0xffff) << 16) - -#define RKVDEC_VP9_REF_DELTAS_LASTFRAME 0x080 -#define RKVDEC_REF_DELTAS_LASTFRAME(pos, val) (((val) & 0x7f) << ((pos) * 7)) - -#define RKVDEC_VP9_INFO_LASTFRAME 0x084 -#define RKVDEC_MODE_DELTAS_LASTFRAME(pos, val) (((val) & 0x7f) << ((pos) * 7)) -#define RKVDEC_SEG_EN_LASTFRAME BIT(16) -#define RKVDEC_LAST_SHOW_FRAME BIT(17) -#define RKVDEC_LAST_INTRA_ONLY BIT(18) -#define RKVDEC_LAST_WIDHHEIGHT_EQCUR BIT(19) -#define RKVDEC_COLOR_SPACE_LASTKEYFRAME(x) (((x) & 0x07) << 20) - -#define RKVDEC_VP9_INTERCMD_BASE 0x088 - -#define RKVDEC_VP9_INTERCMD_NUM 0x08C -#define RKVDEC_INTERCMD_NUM(x) ((x) & 0xffffff) - -#define RKVDEC_VP9_LASTTILE_SIZE 0x090 -#define RKVDEC_LASTTILE_SIZE(x) ((x) & 0xffffff) - -#define RKVDEC_VP9_HOR_VIRSTRIDE(i) ((i) * 0x04 + 0x094) -#define RKVDEC_HOR_Y_VIRSTRIDE(x) ((x) & 0x1ff) -#define RKVDEC_HOR_UV_VIRSTRIDE(x) (((x) & 0x1ff) << 16) - -#define RKVDEC_REG_H264_POC_REFER0(i) (((i) * 0x04) + 0x064) -#define RKVDEC_REG_H264_POC_REFER1(i) (((i) * 0x04) + 0x0C4) -#define RKVDEC_REG_H264_POC_REFER2(i) (((i) * 0x04) + 0x120) -#define RKVDEC_POC_REFER(x) ((x) & 0xffffffff) - -#define RKVDEC_REG_CUR_POC0 0x0A0 -#define RKVDEC_REG_CUR_POC1 0x128 -#define RKVDEC_CUR_POC(x) ((x) & 0xffffffff) - -#define RKVDEC_REG_RLCWRITE_BASE 0x0A4 -#define RKVDEC_REG_PPS_BASE 0x0A8 -#define RKVDEC_REG_RPS_BASE 0x0AC - -#define RKVDEC_REG_STRMD_ERR_EN 0x0B0 -#define RKVDEC_STRMD_ERR_EN(x) ((x) & 0xffffffff) - -#define RKVDEC_REG_STRMD_ERR_STA 0x0B4 -#define RKVDEC_STRMD_ERR_STA(x) ((x) & 0xfffffff) -#define RKVDEC_COLMV_ERR_REF_PICIDX(x) (((x) & 0x0f) << 28) - -#define RKVDEC_REG_STRMD_ERR_CTU 0x0B8 -#define RKVDEC_STRMD_ERR_CTU(x) ((x) & 0xff) -#define RKVDEC_STRMD_ERR_CTU_YOFFSET(x) (((x) & 0xff) << 8) -#define RKVDEC_STRMFIFO_SPACE2FULL(x) (((x) & 0x7f) << 16) -#define RKVDEC_VP9_ERR_EN_CTU0 BIT(24) - -#define RKVDEC_REG_SAO_CTU_POS 0x0BC -#define RKVDEC_SAOWR_XOFFSET(x) ((x) & 0x1ff) -#define RKVDEC_SAOWR_YOFFSET(x) (((x) & 0x3ff) << 16) - -#define RKVDEC_VP9_LAST_FRAME_YSTRIDE 0x0C0 -#define RKVDEC_VP9_GOLDEN_FRAME_YSTRIDE 0x0C4 -#define RKVDEC_VP9_ALTREF_FRAME_YSTRIDE 0x0C8 -#define RKVDEC_VP9_REF_YSTRIDE(x) (((x) & 0xfffff) << 0) - -#define RKVDEC_VP9_LAST_FRAME_YUVSTRIDE 0x0CC -#define RKVDEC_VP9_REF_YUVSTRIDE(x) (((x) & 0x1fffff) << 0) - -#define RKVDEC_VP9_REF_COLMV_BASE 0x0D0 - -#define RKVDEC_REG_PERFORMANCE_CYCLE 0x100 -#define RKVDEC_PERFORMANCE_CYCLE(x) ((x) & 0xffffffff) - -#define RKVDEC_REG_AXI_DDR_RDATA 0x104 -#define RKVDEC_AXI_DDR_RDATA(x) ((x) & 0xffffffff) - -#define RKVDEC_REG_AXI_DDR_WDATA 0x108 -#define RKVDEC_AXI_DDR_WDATA(x) ((x) & 0xffffffff) - -#define RKVDEC_REG_FPGADEBUG_RESET 0x10C -#define RKVDEC_BUSIFD_RESETN BIT(0) -#define RKVDEC_CABAC_RESETN BIT(1) -#define RKVDEC_DEC_CTRL_RESETN BIT(2) -#define RKVDEC_TRANSD_RESETN BIT(3) -#define RKVDEC_INTRA_RESETN BIT(4) -#define RKVDEC_INTER_RESETN BIT(5) -#define RKVDEC_RECON_RESETN BIT(6) -#define RKVDEC_FILER_RESETN BIT(7) - -#define RKVDEC_REG_PERFORMANCE_SEL 0x110 -#define RKVDEC_PERF_SEL_CNT0(x) ((x) & 0x3f) -#define RKVDEC_PERF_SEL_CNT1(x) (((x) & 0x3f) << 8) -#define RKVDEC_PERF_SEL_CNT2(x) (((x) & 0x3f) << 16) - -#define RKVDEC_REG_PERFORMANCE_CNT(i) ((i) * 0x04 + 0x114) -#define RKVDEC_PERF_CNT(x) ((x) & 0xffffffff) - -#define RKVDEC_REG_H264_ERRINFO_BASE 0x12C - -#define RKVDEC_REG_H264_ERRINFO_NUM 0x130 -#define RKVDEC_SLICEDEC_NUM(x) ((x) & 0x3fff) -#define RKVDEC_STRMD_DECT_ERR_FLAG BIT(15) -#define RKVDEC_ERR_PKT_NUM(x) (((x) & 0x3fff) << 16) - -#define RKVDEC_REG_H264_ERR_E 0x134 -#define RKVDEC_H264_ERR_EN_HIGHBITS(x) ((x) & 0x3fffffff) - #define RKVDEC_REG_QOS_CTRL 0x18C +/* + * Cache configuration is not covered in the range of the register struct + */ #define RKVDEC_REG_PREF_LUMA_CACHE_COMMAND 0x410 #define RKVDEC_REG_PREF_CHR_CACHE_COMMAND 0x450 +/* + * Define the mode values + */ +#define RKVDEC_MODE_HEVC 0 +#define RKVDEC_MODE_H264 1 +#define RKVDEC_MODE_VP9 2 + +/* rkvcodec registers */ +struct rkvdec_common_regs { + struct rkvdec_id { + u32 minor_ver : 8; + u32 level : 1; + u32 dec_support : 3; + u32 profile : 1; + u32 reserved0 : 1; + u32 codec_flag : 1; + u32 reserved1 : 1; + u32 prod_num : 16; + } reg00; + + struct rkvdec_int { + u32 dec_e : 1; + u32 dec_clkgate_e : 1; + u32 dec_e_strmd_clkgate_dis : 1; + u32 timeout_mode : 1; + u32 dec_irq_dis : 1; + u32 dec_timeout_e : 1; + u32 buf_empty_en : 1; + u32 stmerror_waitdecfifo_empty : 1; + u32 dec_irq : 1; + u32 dec_irq_raw : 1; + u32 reserved2 : 2; + u32 dec_rdy_sta : 1; + u32 dec_bus_sta : 1; + u32 dec_error_sta : 1; + u32 dec_timeout_sta : 1; + u32 dec_empty_sta : 1; + u32 colmv_ref_error_sta : 1; + u32 cabu_end_sta : 1; + u32 h264orvp9_error_mode : 1; + u32 softrst_en_p : 1; + u32 force_softreset_valid : 1; + u32 softreset_rdy : 1; + u32 wr_ddr_align_en : 1; + u32 scl_down_en : 1; + u32 allow_not_wr_unref_bframe : 1; + u32 reserved1 : 6; + } reg01; + + struct rkvdec_sysctrl { + u32 in_endian : 1; + u32 in_swap32_e : 1; + u32 in_swap64_e : 1; + u32 str_endian : 1; + u32 str_swap32_e : 1; + u32 str_swap64_e : 1; + u32 out_endian : 1; + u32 out_swap32_e : 1; + u32 out_cbcr_swap : 1; + u32 reserved0 : 1; + u32 rlc_mode_direct_write : 1; + u32 rlc_mode : 1; + u32 strm_start_bit : 7; + u32 reserved1 : 1; + u32 dec_mode : 2; + u32 reserved2 : 2; + u32 rps_mode : 1; + u32 stream_mode : 1; + u32 stream_lastpacket : 1; + u32 firstslice_flag : 1; + u32 frame_orslice : 1; + u32 buspr_slot_disable : 1; + u32 colmv_mode : 1; + u32 ycacherd_prior : 1; + } reg02; + + struct rkvdec_picpar { + u32 y_hor_virstride : 9; + u32 reserved : 2; + u32 slice_num_highbit : 1; + u32 uv_hor_virstride : 9; + u32 slice_num_lowbits : 11; + } reg03; + + u32 strm_rlc_base; + u32 stream_len; + u32 cabactbl_base; + u32 decout_base; + + struct rkvdec_y_virstride { + u32 y_virstride : 20; + u32 reserved0 : 12; + } reg08; + + struct rkvdec_yuv_virstride { + u32 yuv_virstride : 21; + u32 reserved0 : 11; + } reg09; +} __packed; + +struct ref_base { + u32 field_ref : 1; + u32 topfield_used_ref : 1; + u32 botfield_used_ref : 1; + u32 colmv_use_flag_ref : 1; + u32 base_addr : 28; +}; + +struct rkvdec_h26x_regs { + struct ref_base ref0_14_base[15]; + u32 ref0_14_poc[15]; + + u32 cur_poc; + u32 rlcwrite_base; + u32 pps_base; + u32 rps_base; + + u32 strmd_error_e; + + struct { + u32 strmd_error_status : 28; + u32 colmv_error_ref_picidx : 4; + } reg45; + + struct { + u32 strmd_error_ctu_xoffset : 8; + u32 strmd_error_ctu_yoffset : 8; + u32 streamfifo_space2full : 7; + u32 reserved0 : 1; + u32 vp9_error_ctu0_en : 1; + u32 reserved1 : 7; + } reg46; + + struct { + u32 saowr_xoffet : 9; + u32 reserved0 : 7; + u32 saowr_yoffset : 10; + u32 reserved1 : 6; + } reg47; + + struct ref_base ref15_base; + + u32 ref15_29_poc[15]; + + u32 performance_cycle; + u32 axi_ddr_rdata; + u32 axi_ddr_wdata; + + struct { + u32 busifd_resetn : 1; + u32 cabac_resetn : 1; + u32 dec_ctrl_resetn : 1; + u32 transd_resetn : 1; + u32 intra_resetn : 1; + u32 inter_resetn : 1; + u32 recon_resetn : 1; + u32 filer_resetn : 1; + u32 reserved0 : 24; + } reg67; + + struct { + u32 perf_cnt0_sel : 6; + u32 reserved0 : 2; + u32 perf_cnt1_sel : 6; + u32 reserved1 : 2; + u32 perf_cnt2_sel : 6; + u32 reserved2 : 10; + } reg68; + + u32 perf_cnt0; + u32 perf_cnt1; + u32 perf_cnt2; + u32 ref30_poc; + u32 ref31_poc; + u32 cur_poc1; + u32 errorinfo_base; + + struct { + u32 slicedec_num : 14; + u32 reserved0 : 1; + u32 strmd_detect_error_flag : 1; + u32 error_packet_num : 14; + u32 reserved1 : 2; + } reg76; + + struct { + u32 error_en_highbits : 30; + u32 strmd_error_slice_en : 1; + u32 strmd_error_frame_en : 1; + } reg77; + + u32 colmv_cur_base; + u32 colmv_ref_base[16]; + u32 scanlist_addr; + u32 reg96_sd_decout_base; + u32 sd_y_virstride; + u32 sd_hor_stride; + u32 qos_ctrl; + u32 perf[8]; + u32 qos1; +} __packed; + +struct rkvdec_vp9_regs { + struct cprheader_offset { + u32 cprheader_offset : 16; + u32 reserved : 16; + } reg10; + + u32 refer_bases[3]; + u32 count_base; + u32 segidlast_base; + u32 segidcur_base; + + struct frame_sizes { + u32 framewidth : 16; + u32 frameheight : 16; + } reg17_19[3]; + + struct segid_grp { + u32 segid_abs_delta : 1; + u32 segid_frame_qp_delta_en : 1; + u32 segid_frame_qp_delta : 9; + u32 segid_frame_loopfilter_value_en : 1; + u32 segid_frame_loopfilter_value : 7; + u32 segid_referinfo_en : 1; + u32 segid_referinfo : 2; + u32 segid_frame_skip_en : 1; + u32 reserved : 9; + } reg20_27[8]; + + struct cprheader_config { + u32 tx_mode : 3; + u32 frame_reference_mode : 2; + u32 reserved : 27; + } reg28; + + struct ref_scale { + u32 ref_hor_scale : 16; + u32 ref_ver_scale : 16; + } reg29_31[3]; + + struct ref_deltas_lastframe { + u32 ref_deltas_lastframe0 : 7; + u32 ref_deltas_lastframe1 : 7; + u32 ref_deltas_lastframe2 : 7; + u32 ref_deltas_lastframe3 : 7; + u32 reserved : 4; + } reg32; + + struct info_lastframe { + u32 mode_deltas_lastframe0 : 7; + u32 mode_deltas_lastframe1 : 7; + u32 reserved0 : 2; + u32 segmentation_enable_lstframe : 1; + u32 last_show_frame : 1; + u32 last_intra_only : 1; + u32 last_widthheight_eqcur : 1; + u32 color_space_lastkeyframe : 3; + u32 reserved1 : 9; + } reg33; + + u32 intercmd_base; + + struct intercmd_num { + u32 intercmd_num : 24; + u32 reserved : 8; + } reg35; + + struct lasttile_size { + u32 lasttile_size : 24; + u32 reserved : 8; + } reg36; + + struct hor_virstride { + u32 y_hor_virstride : 9; + u32 reserved0 : 7; + u32 uv_hor_virstride : 9; + u32 reserved1 : 7; + } reg37_39[3]; + + u32 cur_poc; + + struct rlcwrite_base { + u32 reserved : 3; + u32 rlcwrite_base : 29; + } reg41; + + struct pps_base { + u32 reserved : 4; + u32 pps_base : 28; + } reg42; + + struct rps_base { + u32 reserved : 4; + u32 rps_base : 28; + } reg43; + + struct strmd_error_en { + u32 strmd_error_e : 28; + u32 reserved : 4; + } reg44; + + u32 vp9_error_info0; + + struct strmd_error_ctu { + u32 strmd_error_ctu_xoffset : 8; + u32 strmd_error_ctu_yoffset : 8; + u32 streamfifo_space2full : 7; + u32 reserved0 : 1; + u32 error_ctu0_en : 1; + u32 reserved1 : 7; + } reg46; + + struct sao_ctu_position { + u32 saowr_xoffet : 9; + u32 reserved0 : 7; + u32 saowr_yoffset : 10; + u32 reserved1 : 6; + } reg47; + + struct ystride { + u32 virstride : 20; + u32 reserved : 12; + } reg48_50[3]; + + struct lastref_yuvstride { + u32 lastref_yuv_virstride : 21; + u32 reserved : 11; + } reg51; + + u32 refcolmv_base; + + u32 reserved0[11]; + + u32 performance_cycle; + u32 axi_ddr_rdata; + u32 axi_ddr_wdata; + + struct fpgadebug_reset { + u32 busifd_resetn : 1; + u32 cabac_resetn : 1; + u32 dec_ctrl_resetn : 1; + u32 transd_resetn : 1; + u32 intra_resetn : 1; + u32 inter_resetn : 1; + u32 recon_resetn : 1; + u32 filer_resetn : 1; + u32 reserved : 24; + } reg67; + + struct performance_sel { + u32 perf_cnt0_sel : 6; + u32 reserved0 : 2; + u32 perf_cnt1_sel : 6; + u32 reserved1 : 2; + u32 perf_cnt2_sel : 6; + u32 reserved : 10; + } reg68; + + u32 perf_cnt0; + u32 perf_cnt1; + u32 perf_cnt2; + + u32 reserved1[3]; + + u32 vp9_error_info1; + + struct error_ctu1 { + u32 vp9_error_ctu1_x : 6; + u32 reserved0 : 2; + u32 vp9_error_ctu1_y : 6; + u32 reserved1 : 1; + u32 vp9_error_ctu1_en : 1; + u32 reserved2 : 16; + } reg76; + + u32 reserved2; +} __packed; + +struct rkvdec_regs { + struct rkvdec_common_regs common; + union { + struct rkvdec_h26x_regs h26x; + struct rkvdec_vp9_regs vp9; + }; +} __packed; + #endif /* RKVDEC_REGS_H_ */ diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c index b4bf01e839ef..ba51a7c2fe55 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c @@ -163,6 +163,7 @@ struct rkvdec_vp9_ctx { struct v4l2_vp9_frame_context frame_context[4]; struct rkvdec_vp9_frame_info cur; struct rkvdec_vp9_frame_info last; + struct rkvdec_regs regs; }; static void write_coeff_plane(const u8 coef[6][6][3], u8 *coeff_plane) @@ -347,38 +348,6 @@ static void init_probs(struct rkvdec_ctx *ctx, init_inter_probs(ctx, run); } -struct rkvdec_vp9_ref_reg { - u32 reg_frm_size; - u32 reg_hor_stride; - u32 reg_y_stride; - u32 reg_yuv_stride; - u32 reg_ref_base; -}; - -static struct rkvdec_vp9_ref_reg ref_regs[] = { - { - .reg_frm_size = RKVDEC_REG_VP9_FRAME_SIZE(0), - .reg_hor_stride = RKVDEC_VP9_HOR_VIRSTRIDE(0), - .reg_y_stride = RKVDEC_VP9_LAST_FRAME_YSTRIDE, - .reg_yuv_stride = RKVDEC_VP9_LAST_FRAME_YUVSTRIDE, - .reg_ref_base = RKVDEC_REG_VP9_LAST_FRAME_BASE, - }, - { - .reg_frm_size = RKVDEC_REG_VP9_FRAME_SIZE(1), - .reg_hor_stride = RKVDEC_VP9_HOR_VIRSTRIDE(1), - .reg_y_stride = RKVDEC_VP9_GOLDEN_FRAME_YSTRIDE, - .reg_yuv_stride = 0, - .reg_ref_base = RKVDEC_REG_VP9_GOLDEN_FRAME_BASE, - }, - { - .reg_frm_size = RKVDEC_REG_VP9_FRAME_SIZE(2), - .reg_hor_stride = RKVDEC_VP9_HOR_VIRSTRIDE(2), - .reg_y_stride = RKVDEC_VP9_ALTREF_FRAME_YSTRIDE, - .reg_yuv_stride = 0, - .reg_ref_base = RKVDEC_REG_VP9_ALTREF_FRAME_BASE, - } -}; - static struct rkvdec_decoded_buffer * get_ref_buf(struct rkvdec_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp) { @@ -412,18 +381,17 @@ static dma_addr_t get_mv_base_addr(struct rkvdec_decoded_buffer *buf) static void config_ref_registers(struct rkvdec_ctx *ctx, const struct rkvdec_vp9_run *run, struct rkvdec_decoded_buffer *ref_buf, - struct rkvdec_vp9_ref_reg *ref_reg) + int i) { unsigned int aligned_pitch, aligned_height, y_len, yuv_len; - struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_vp9_ctx *vp9_ctx = ctx->priv; + struct rkvdec_regs *regs = &vp9_ctx->regs; aligned_height = round_up(ref_buf->vp9.height, 64); - writel_relaxed(RKVDEC_VP9_FRAMEWIDTH(ref_buf->vp9.width) | - RKVDEC_VP9_FRAMEHEIGHT(ref_buf->vp9.height), - rkvdec->regs + ref_reg->reg_frm_size); + regs->vp9.reg17_19[i].frameheight = ref_buf->vp9.height; + regs->vp9.reg17_19[i].framewidth = ref_buf->vp9.width; - writel_relaxed(vb2_dma_contig_plane_dma_addr(&ref_buf->base.vb.vb2_buf, 0), - rkvdec->regs + ref_reg->reg_ref_base); + regs->vp9.refer_bases[i] = vb2_dma_contig_plane_dma_addr(&ref_buf->base.vb.vb2_buf, 0); if (&ref_buf->base.vb == run->base.bufs.dst) return; @@ -432,59 +400,50 @@ static void config_ref_registers(struct rkvdec_ctx *ctx, y_len = aligned_height * aligned_pitch; yuv_len = (y_len * 3) / 2; - writel_relaxed(RKVDEC_HOR_Y_VIRSTRIDE(aligned_pitch / 16) | - RKVDEC_HOR_UV_VIRSTRIDE(aligned_pitch / 16), - rkvdec->regs + ref_reg->reg_hor_stride); - writel_relaxed(RKVDEC_VP9_REF_YSTRIDE(y_len / 16), - rkvdec->regs + ref_reg->reg_y_stride); + regs->vp9.reg37_39[i].y_hor_virstride = aligned_pitch / 16; + regs->vp9.reg37_39[i].uv_hor_virstride = aligned_pitch / 16; + regs->vp9.reg48_50[i].virstride = y_len / 16; - if (!ref_reg->reg_yuv_stride) - return; - - writel_relaxed(RKVDEC_VP9_REF_YUVSTRIDE(yuv_len / 16), - rkvdec->regs + ref_reg->reg_yuv_stride); + if (!i) + regs->vp9.reg51.lastref_yuv_virstride = yuv_len / 16; } static void config_seg_registers(struct rkvdec_ctx *ctx, unsigned int segid) { struct rkvdec_vp9_ctx *vp9_ctx = ctx->priv; + struct rkvdec_regs *regs = &vp9_ctx->regs; const struct v4l2_vp9_segmentation *seg; - struct rkvdec_dev *rkvdec = ctx->dev; s16 feature_val; int feature_id; - u32 val = 0; seg = vp9_ctx->last.valid ? &vp9_ctx->last.seg : &vp9_ctx->cur.seg; feature_id = V4L2_VP9_SEG_LVL_ALT_Q; if (v4l2_vp9_seg_feat_enabled(seg->feature_enabled, feature_id, segid)) { feature_val = seg->feature_data[segid][feature_id]; - val |= RKVDEC_SEGID_FRAME_QP_DELTA_EN(1) | - RKVDEC_SEGID_FRAME_QP_DELTA(feature_val); + regs->vp9.reg20_27[segid].segid_frame_qp_delta_en = 1; + regs->vp9.reg20_27[segid].segid_frame_qp_delta = feature_val; } feature_id = V4L2_VP9_SEG_LVL_ALT_L; if (v4l2_vp9_seg_feat_enabled(seg->feature_enabled, feature_id, segid)) { feature_val = seg->feature_data[segid][feature_id]; - val |= RKVDEC_SEGID_FRAME_LOOPFILTER_VALUE_EN(1) | - RKVDEC_SEGID_FRAME_LOOPFILTER_VALUE(feature_val); + regs->vp9.reg20_27[segid].segid_frame_loopfilter_value_en = 1; + regs->vp9.reg20_27[segid].segid_frame_loopfilter_value = feature_val; } feature_id = V4L2_VP9_SEG_LVL_REF_FRAME; if (v4l2_vp9_seg_feat_enabled(seg->feature_enabled, feature_id, segid)) { feature_val = seg->feature_data[segid][feature_id]; - val |= RKVDEC_SEGID_REFERINFO_EN(1) | - RKVDEC_SEGID_REFERINFO(feature_val); + regs->vp9.reg20_27[segid].segid_referinfo_en = 1; + regs->vp9.reg20_27[segid].segid_referinfo = feature_val; } feature_id = V4L2_VP9_SEG_LVL_SKIP; - if (v4l2_vp9_seg_feat_enabled(seg->feature_enabled, feature_id, segid)) - val |= RKVDEC_SEGID_FRAME_SKIP_EN(1); + regs->vp9.reg20_27[segid].segid_frame_skip_en = + v4l2_vp9_seg_feat_enabled(seg->feature_enabled, feature_id, segid); - if (!segid && - (seg->flags & V4L2_VP9_SEGMENTATION_FLAG_ABS_OR_DELTA_UPDATE)) - val |= RKVDEC_SEGID_ABS_DELTA(1); - - writel_relaxed(val, rkvdec->regs + RKVDEC_VP9_SEGID_GRP(segid)); + regs->vp9.reg20_27[segid].segid_abs_delta = !segid && + (seg->flags & V4L2_VP9_SEGMENTATION_FLAG_ABS_OR_DELTA_UPDATE); } static void update_dec_buf_info(struct rkvdec_decoded_buffer *buf, @@ -521,7 +480,7 @@ static void config_registers(struct rkvdec_ctx *ctx, struct rkvdec_decoded_buffer *ref_bufs[3]; struct rkvdec_decoded_buffer *dst, *last, *mv_ref; struct rkvdec_vp9_ctx *vp9_ctx = ctx->priv; - u32 val, last_frame_info = 0; + struct rkvdec_regs *regs = &vp9_ctx->regs; const struct v4l2_vp9_segmentation *seg; struct rkvdec_dev *rkvdec = ctx->dev; dma_addr_t addr; @@ -547,8 +506,7 @@ static void config_registers(struct rkvdec_ctx *ctx, (V4L2_VP9_FRAME_FLAG_KEY_FRAME | V4L2_VP9_FRAME_FLAG_INTRA_ONLY)); - writel_relaxed(RKVDEC_MODE(RKVDEC_MODE_VP9), - rkvdec->regs + RKVDEC_REG_SYSCTRL); + regs->common.reg02.dec_mode = RKVDEC_MODE_VP9; bit_depth = dec_params->bit_depth; aligned_height = round_up(ctx->decoded_fmt.fmt.pix_mp.height, 64); @@ -560,17 +518,14 @@ static void config_registers(struct rkvdec_ctx *ctx, uv_len = y_len / 2; yuv_len = y_len + uv_len; - writel_relaxed(RKVDEC_Y_HOR_VIRSTRIDE(aligned_pitch / 16) | - RKVDEC_UV_HOR_VIRSTRIDE(aligned_pitch / 16), - rkvdec->regs + RKVDEC_REG_PICPAR); - writel_relaxed(RKVDEC_Y_VIRSTRIDE(y_len / 16), - rkvdec->regs + RKVDEC_REG_Y_VIRSTRIDE); - writel_relaxed(RKVDEC_YUV_VIRSTRIDE(yuv_len / 16), - rkvdec->regs + RKVDEC_REG_YUV_VIRSTRIDE); + regs->common.reg03.y_hor_virstride = aligned_pitch / 16; + regs->common.reg03.uv_hor_virstride = aligned_pitch / 16; + regs->common.reg08.y_virstride = y_len / 16; + regs->common.reg09.yuv_virstride = yuv_len / 16; stream_len = vb2_get_plane_payload(&run->base.bufs.src->vb2_buf, 0); - writel_relaxed(RKVDEC_STRM_LEN(stream_len), - rkvdec->regs + RKVDEC_REG_STRM_LEN); + + regs->common.stream_len = stream_len; /* * Reset count buffer, because decoder only output intra related syntax @@ -588,14 +543,13 @@ static void config_registers(struct rkvdec_ctx *ctx, vp9_ctx->cur.segmapid++; for (i = 0; i < ARRAY_SIZE(ref_bufs); i++) - config_ref_registers(ctx, run, ref_bufs[i], &ref_regs[i]); + config_ref_registers(ctx, run, ref_bufs[i], i); for (i = 0; i < 8; i++) config_seg_registers(ctx, i); - writel_relaxed(RKVDEC_VP9_TX_MODE(vp9_ctx->cur.tx_mode) | - RKVDEC_VP9_FRAME_REF_MODE(dec_params->reference_mode), - rkvdec->regs + RKVDEC_VP9_CPRHEADER_CONFIG); + regs->vp9.reg28.tx_mode = vp9_ctx->cur.tx_mode; + regs->vp9.reg28.frame_reference_mode = dec_params->reference_mode; if (!intra_only) { const struct v4l2_vp9_loop_filter *lf; @@ -606,46 +560,58 @@ static void config_registers(struct rkvdec_ctx *ctx, else lf = &vp9_ctx->cur.lf; - val = 0; for (i = 0; i < ARRAY_SIZE(lf->ref_deltas); i++) { delta = lf->ref_deltas[i]; - val |= RKVDEC_REF_DELTAS_LASTFRAME(i, delta); + switch (i) { + case 0: + regs->vp9.reg32.ref_deltas_lastframe0 = delta; + break; + case 1: + regs->vp9.reg32.ref_deltas_lastframe1 = delta; + break; + case 2: + regs->vp9.reg32.ref_deltas_lastframe2 = delta; + break; + case 3: + regs->vp9.reg32.ref_deltas_lastframe3 = delta; + break; + } } - writel_relaxed(val, - rkvdec->regs + RKVDEC_VP9_REF_DELTAS_LASTFRAME); - for (i = 0; i < ARRAY_SIZE(lf->mode_deltas); i++) { delta = lf->mode_deltas[i]; - last_frame_info |= RKVDEC_MODE_DELTAS_LASTFRAME(i, - delta); + switch (i) { + case 0: + regs->vp9.reg33.mode_deltas_lastframe0 = delta; + break; + case 1: + regs->vp9.reg33.mode_deltas_lastframe1 = delta; + break; + } } } - if (vp9_ctx->last.valid && !intra_only && - vp9_ctx->last.seg.flags & V4L2_VP9_SEGMENTATION_FLAG_ENABLED) - last_frame_info |= RKVDEC_SEG_EN_LASTFRAME; + regs->vp9.reg33.segmentation_enable_lstframe = + vp9_ctx->last.valid && !intra_only && + vp9_ctx->last.seg.flags & V4L2_VP9_SEGMENTATION_FLAG_ENABLED; - if (vp9_ctx->last.valid && - vp9_ctx->last.flags & V4L2_VP9_FRAME_FLAG_SHOW_FRAME) - last_frame_info |= RKVDEC_LAST_SHOW_FRAME; + regs->vp9.reg33.last_show_frame = + vp9_ctx->last.valid && + vp9_ctx->last.flags & V4L2_VP9_FRAME_FLAG_SHOW_FRAME; - if (vp9_ctx->last.valid && - vp9_ctx->last.flags & - (V4L2_VP9_FRAME_FLAG_KEY_FRAME | V4L2_VP9_FRAME_FLAG_INTRA_ONLY)) - last_frame_info |= RKVDEC_LAST_INTRA_ONLY; + regs->vp9.reg33.last_intra_only = + vp9_ctx->last.valid && + vp9_ctx->last.flags & + (V4L2_VP9_FRAME_FLAG_KEY_FRAME | V4L2_VP9_FRAME_FLAG_INTRA_ONLY); - if (vp9_ctx->last.valid && - last->vp9.width == dst->vp9.width && - last->vp9.height == dst->vp9.height) - last_frame_info |= RKVDEC_LAST_WIDHHEIGHT_EQCUR; + regs->vp9.reg33.last_widthheight_eqcur = + vp9_ctx->last.valid && + last->vp9.width == dst->vp9.width && + last->vp9.height == dst->vp9.height; - writel_relaxed(last_frame_info, - rkvdec->regs + RKVDEC_VP9_INFO_LASTFRAME); - - writel_relaxed(stream_len - dec_params->compressed_header_size - - dec_params->uncompressed_header_size, - rkvdec->regs + RKVDEC_VP9_LASTTILE_SIZE); + regs->vp9.reg36.lasttile_size = + stream_len - dec_params->compressed_header_size - + dec_params->uncompressed_header_size; for (i = 0; !intra_only && i < ARRAY_SIZE(ref_bufs); i++) { unsigned int refw = ref_bufs[i]->vp9.width; @@ -654,29 +620,28 @@ static void config_registers(struct rkvdec_ctx *ctx, hscale = (refw << 14) / dst->vp9.width; vscale = (refh << 14) / dst->vp9.height; - writel_relaxed(RKVDEC_VP9_REF_HOR_SCALE(hscale) | - RKVDEC_VP9_REF_VER_SCALE(vscale), - rkvdec->regs + RKVDEC_VP9_REF_SCALE(i)); + + regs->vp9.reg29_31[i].ref_hor_scale = hscale; + regs->vp9.reg29_31[i].ref_ver_scale = vscale; } addr = vb2_dma_contig_plane_dma_addr(&dst->base.vb.vb2_buf, 0); - writel_relaxed(addr, rkvdec->regs + RKVDEC_REG_DECOUT_BASE); + regs->common.decout_base = addr; addr = vb2_dma_contig_plane_dma_addr(&run->base.bufs.src->vb2_buf, 0); - writel_relaxed(addr, rkvdec->regs + RKVDEC_REG_STRM_RLC_BASE); - writel_relaxed(vp9_ctx->priv_tbl.dma + - offsetof(struct rkvdec_vp9_priv_tbl, probs), - rkvdec->regs + RKVDEC_REG_CABACTBL_PROB_BASE); - writel_relaxed(vp9_ctx->count_tbl.dma, - rkvdec->regs + RKVDEC_REG_VP9COUNT_BASE); + regs->common.strm_rlc_base = addr; - writel_relaxed(vp9_ctx->priv_tbl.dma + - offsetof(struct rkvdec_vp9_priv_tbl, segmap) + - (RKVDEC_VP9_MAX_SEGMAP_SIZE * vp9_ctx->cur.segmapid), - rkvdec->regs + RKVDEC_REG_VP9_SEGIDCUR_BASE); - writel_relaxed(vp9_ctx->priv_tbl.dma + - offsetof(struct rkvdec_vp9_priv_tbl, segmap) + - (RKVDEC_VP9_MAX_SEGMAP_SIZE * (!vp9_ctx->cur.segmapid)), - rkvdec->regs + RKVDEC_REG_VP9_SEGIDLAST_BASE); + regs->common.cabactbl_base = vp9_ctx->priv_tbl.dma + + offsetof(struct rkvdec_vp9_priv_tbl, probs); + + regs->vp9.count_base = vp9_ctx->count_tbl.dma; + + regs->vp9.segidlast_base = vp9_ctx->priv_tbl.dma + + offsetof(struct rkvdec_vp9_priv_tbl, segmap) + + (RKVDEC_VP9_MAX_SEGMAP_SIZE * (!vp9_ctx->cur.segmapid)); + + regs->vp9.segidcur_base = vp9_ctx->priv_tbl.dma + + offsetof(struct rkvdec_vp9_priv_tbl, segmap) + + (RKVDEC_VP9_MAX_SEGMAP_SIZE * vp9_ctx->cur.segmapid); if (!intra_only && !(dec_params->flags & V4L2_VP9_FRAME_FLAG_ERROR_RESILIENT) && @@ -685,12 +650,15 @@ static void config_registers(struct rkvdec_ctx *ctx, else mv_ref = dst; - writel_relaxed(get_mv_base_addr(mv_ref), - rkvdec->regs + RKVDEC_VP9_REF_COLMV_BASE); + regs->vp9.refcolmv_base = get_mv_base_addr(mv_ref); - writel_relaxed(ctx->decoded_fmt.fmt.pix_mp.width | - (ctx->decoded_fmt.fmt.pix_mp.height << 16), - rkvdec->regs + RKVDEC_REG_PERFORMANCE_CYCLE); + regs->vp9.performance_cycle = ctx->decoded_fmt.fmt.pix_mp.width | + (ctx->decoded_fmt.fmt.pix_mp.height << 16); + + regs->vp9.reg44.strmd_error_e = 0xe; + + rkvdec_memcpy_toio(rkvdec->regs, regs, + MIN(sizeof(*regs), sizeof(u32) * rkvdec->variant->num_regs)); } static int validate_dec_params(struct rkvdec_ctx *ctx, @@ -823,8 +791,6 @@ static int rkvdec_vp9_run(struct rkvdec_ctx *ctx) writel(1, rkvdec->regs + RKVDEC_REG_PREF_LUMA_CACHE_COMMAND); writel(1, rkvdec->regs + RKVDEC_REG_PREF_CHR_CACHE_COMMAND); - writel(0xe, rkvdec->regs + RKVDEC_REG_STRMD_ERR_EN); - if (rkvdec->variant->quirks & RKVDEC_QUIRK_DISABLE_QOS) rkvdec_quirks_disable_qos(ctx); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c index 5af9aa5ab353..776149f871b0 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c @@ -914,6 +914,15 @@ void rkvdec_quirks_disable_qos(struct rkvdec_ctx *ctx) writel(reg, rkvdec->regs + RKVDEC_REG_QOS_CTRL); } +void rkvdec_memcpy_toio(void __iomem *dst, void *src, size_t len) +{ +#ifdef CONFIG_ARM64 + __iowrite32_copy(dst, src, len / 4); +#else + memcpy_toio(dst, src, len); +#endif +} + static void rkvdec_device_run(void *priv) { struct rkvdec_ctx *ctx = priv; @@ -1227,7 +1236,6 @@ static void rkvdec_watchdog_func(struct work_struct *work) if (ctx) { dev_err(rkvdec->dev, "Frame processing timed out!\n"); writel(RKVDEC_IRQ_DIS, rkvdec->regs + RKVDEC_REG_INTERRUPT); - writel(0, rkvdec->regs + RKVDEC_REG_SYSCTRL); rkvdec_job_finish(ctx, VB2_BUF_STATE_ERROR); } } diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h index 566e06fa2b1e..f35f6e80ea2e 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.h @@ -151,6 +151,7 @@ struct rkvdec_aux_buf { void rkvdec_run_preamble(struct rkvdec_ctx *ctx, struct rkvdec_run *run); void rkvdec_run_postamble(struct rkvdec_ctx *ctx, struct rkvdec_run *run); +void rkvdec_memcpy_toio(void __iomem *dst, void *src, size_t len); void rkvdec_quirks_disable_qos(struct rkvdec_ctx *ctx); From 2b0ec90061672274978db0fbe898740a1e52c973 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:21 -0500 Subject: [PATCH 291/307] media: rkvdec: Move cabac tables to their own source file This is in preparation to add support for new variants that will use the same tables. Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/Makefile | 7 +- .../{rkvdec-hevc-data.c => rkvdec-cabac.c} | 506 +++++++++++++++++- .../platform/rockchip/rkvdec/rkvdec-cabac.h | 21 + .../platform/rockchip/rkvdec/rkvdec-h264.c | 500 +---------------- .../platform/rockchip/rkvdec/rkvdec-hevc.c | 4 +- 5 files changed, 531 insertions(+), 507 deletions(-) rename drivers/media/platform/rockchip/rkvdec/{rkvdec-hevc-data.c => rkvdec-cabac.c} (86%) create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-cabac.h diff --git a/drivers/media/platform/rockchip/rkvdec/Makefile b/drivers/media/platform/rockchip/rkvdec/Makefile index a77122641d14..a8ff5e3d7bec 100644 --- a/drivers/media/platform/rockchip/rkvdec/Makefile +++ b/drivers/media/platform/rockchip/rkvdec/Makefile @@ -1,3 +1,8 @@ obj-$(CONFIG_VIDEO_ROCKCHIP_VDEC) += rockchip-vdec.o -rockchip-vdec-y += rkvdec.o rkvdec-h264.o rkvdec-hevc.o rkvdec-vp9.o +rockchip-vdec-y += \ + rkvdec.o \ + rkvdec-cabac.o \ + rkvdec-h264.o \ + rkvdec-hevc.o \ + rkvdec-vp9.o diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-data.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-cabac.c similarity index 86% rename from drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-data.c rename to drivers/media/platform/rockchip/rkvdec/rkvdec-cabac.c index eac4ea604949..068bc0cd5f43 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-data.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-cabac.c @@ -1,19 +1,515 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Rockchip Video Decoder driver + * Rockchip Video Decoder CABAC tables * * Copyright (C) 2023 Collabora, Ltd. * Sebastian Fricke + * Copyright (C) 2019 Collabora, Ltd. + * Boris Brezillon */ -#include +#include "rkvdec-cabac.h" -#define RKV_CABAC_TABLE_SIZE 27456 +#define CABAC_ENTRY(ctxidx, idc0_m, idc0_n, idc1_m, idc1_n, \ + idc2_m, idc2_n, intra_m, intra_n) \ + [0][(ctxidx)] = {idc0_m, idc0_n}, \ + [1][(ctxidx)] = {idc1_m, idc1_n}, \ + [2][(ctxidx)] = {idc2_m, idc2_n}, \ + [3][(ctxidx)] = {intra_m, intra_n} /* - * This file is #include from rkvdec-hevc.c and not compiled. + * Constant CABAC table. + * Built from the tables described in section '9.3.1.1 Initialisation process + * for context variables' of the H264 spec. */ -static const u8 rkvdec_hevc_cabac_table[RKV_CABAC_TABLE_SIZE] = { +const s8 rkvdec_h264_cabac_table[4][464][2] = { + /* Table 9-12 – Values of variables m and n for ctxIdx from 0 to 10 */ + CABAC_ENTRY(0, 20, -15, 20, -15, 20, -15, 20, -15), + CABAC_ENTRY(1, 2, 54, 2, 54, 2, 54, 2, 54), + CABAC_ENTRY(2, 3, 74, 3, 74, 3, 74, 3, 74), + CABAC_ENTRY(3, 20, -15, 20, -15, 20, -15, 20, -15), + CABAC_ENTRY(4, 2, 54, 2, 54, 2, 54, 2, 54), + CABAC_ENTRY(5, 3, 74, 3, 74, 3, 74, 3, 74), + CABAC_ENTRY(6, -28, 127, -28, 127, -28, 127, -28, 127), + CABAC_ENTRY(7, -23, 104, -23, 104, -23, 104, -23, 104), + CABAC_ENTRY(8, -6, 53, -6, 53, -6, 53, -6, 53), + CABAC_ENTRY(9, -1, 54, -1, 54, -1, 54, -1, 54), + CABAC_ENTRY(10, 7, 51, 7, 51, 7, 51, 7, 51), + + /* Table 9-13 – Values of variables m and n for ctxIdx from 11 to 23 */ + CABAC_ENTRY(11, 23, 33, 22, 25, 29, 16, 0, 0), + CABAC_ENTRY(12, 23, 2, 34, 0, 25, 0, 0, 0), + CABAC_ENTRY(13, 21, 0, 16, 0, 14, 0, 0, 0), + CABAC_ENTRY(14, 1, 9, -2, 9, -10, 51, 0, 0), + CABAC_ENTRY(15, 0, 49, 4, 41, -3, 62, 0, 0), + CABAC_ENTRY(16, -37, 118, -29, 118, -27, 99, 0, 0), + CABAC_ENTRY(17, 5, 57, 2, 65, 26, 16, 0, 0), + CABAC_ENTRY(18, -13, 78, -6, 71, -4, 85, 0, 0), + CABAC_ENTRY(19, -11, 65, -13, 79, -24, 102, 0, 0), + CABAC_ENTRY(20, 1, 62, 5, 52, 5, 57, 0, 0), + CABAC_ENTRY(21, 12, 49, 9, 50, 6, 57, 0, 0), + CABAC_ENTRY(22, -4, 73, -3, 70, -17, 73, 0, 0), + CABAC_ENTRY(23, 17, 50, 10, 54, 14, 57, 0, 0), + + /* Table 9-14 – Values of variables m and n for ctxIdx from 24 to 39 */ + CABAC_ENTRY(24, 18, 64, 26, 34, 20, 40, 0, 0), + CABAC_ENTRY(25, 9, 43, 19, 22, 20, 10, 0, 0), + CABAC_ENTRY(26, 29, 0, 40, 0, 29, 0, 0, 0), + CABAC_ENTRY(27, 26, 67, 57, 2, 54, 0, 0, 0), + CABAC_ENTRY(28, 16, 90, 41, 36, 37, 42, 0, 0), + CABAC_ENTRY(29, 9, 104, 26, 69, 12, 97, 0, 0), + CABAC_ENTRY(30, -46, 127, -45, 127, -32, 127, 0, 0), + CABAC_ENTRY(31, -20, 104, -15, 101, -22, 117, 0, 0), + CABAC_ENTRY(32, 1, 67, -4, 76, -2, 74, 0, 0), + CABAC_ENTRY(33, -13, 78, -6, 71, -4, 85, 0, 0), + CABAC_ENTRY(34, -11, 65, -13, 79, -24, 102, 0, 0), + CABAC_ENTRY(35, 1, 62, 5, 52, 5, 57, 0, 0), + CABAC_ENTRY(36, -6, 86, 6, 69, -6, 93, 0, 0), + CABAC_ENTRY(37, -17, 95, -13, 90, -14, 88, 0, 0), + CABAC_ENTRY(38, -6, 61, 0, 52, -6, 44, 0, 0), + CABAC_ENTRY(39, 9, 45, 8, 43, 4, 55, 0, 0), + + /* Table 9-15 – Values of variables m and n for ctxIdx from 40 to 53 */ + CABAC_ENTRY(40, -3, 69, -2, 69, -11, 89, 0, 0), + CABAC_ENTRY(41, -6, 81, -5, 82, -15, 103, 0, 0), + CABAC_ENTRY(42, -11, 96, -10, 96, -21, 116, 0, 0), + CABAC_ENTRY(43, 6, 55, 2, 59, 19, 57, 0, 0), + CABAC_ENTRY(44, 7, 67, 2, 75, 20, 58, 0, 0), + CABAC_ENTRY(45, -5, 86, -3, 87, 4, 84, 0, 0), + CABAC_ENTRY(46, 2, 88, -3, 100, 6, 96, 0, 0), + CABAC_ENTRY(47, 0, 58, 1, 56, 1, 63, 0, 0), + CABAC_ENTRY(48, -3, 76, -3, 74, -5, 85, 0, 0), + CABAC_ENTRY(49, -10, 94, -6, 85, -13, 106, 0, 0), + CABAC_ENTRY(50, 5, 54, 0, 59, 5, 63, 0, 0), + CABAC_ENTRY(51, 4, 69, -3, 81, 6, 75, 0, 0), + CABAC_ENTRY(52, -3, 81, -7, 86, -3, 90, 0, 0), + CABAC_ENTRY(53, 0, 88, -5, 95, -1, 101, 0, 0), + + /* Table 9-16 – Values of variables m and n for ctxIdx from 54 to 59 */ + CABAC_ENTRY(54, -7, 67, -1, 66, 3, 55, 0, 0), + CABAC_ENTRY(55, -5, 74, -1, 77, -4, 79, 0, 0), + CABAC_ENTRY(56, -4, 74, 1, 70, -2, 75, 0, 0), + CABAC_ENTRY(57, -5, 80, -2, 86, -12, 97, 0, 0), + CABAC_ENTRY(58, -7, 72, -5, 72, -7, 50, 0, 0), + CABAC_ENTRY(59, 1, 58, 0, 61, 1, 60, 0, 0), + + /* Table 9-17 – Values of variables m and n for ctxIdx from 60 to 69 */ + CABAC_ENTRY(60, 0, 41, 0, 41, 0, 41, 0, 41), + CABAC_ENTRY(61, 0, 63, 0, 63, 0, 63, 0, 63), + CABAC_ENTRY(62, 0, 63, 0, 63, 0, 63, 0, 63), + CABAC_ENTRY(63, 0, 63, 0, 63, 0, 63, 0, 63), + CABAC_ENTRY(64, -9, 83, -9, 83, -9, 83, -9, 83), + CABAC_ENTRY(65, 4, 86, 4, 86, 4, 86, 4, 86), + CABAC_ENTRY(66, 0, 97, 0, 97, 0, 97, 0, 97), + CABAC_ENTRY(67, -7, 72, -7, 72, -7, 72, -7, 72), + CABAC_ENTRY(68, 13, 41, 13, 41, 13, 41, 13, 41), + CABAC_ENTRY(69, 3, 62, 3, 62, 3, 62, 3, 62), + + /* Table 9-18 – Values of variables m and n for ctxIdx from 70 to 104 */ + CABAC_ENTRY(70, 0, 45, 13, 15, 7, 34, 0, 11), + CABAC_ENTRY(71, -4, 78, 7, 51, -9, 88, 1, 55), + CABAC_ENTRY(72, -3, 96, 2, 80, -20, 127, 0, 69), + CABAC_ENTRY(73, -27, 126, -39, 127, -36, 127, -17, 127), + CABAC_ENTRY(74, -28, 98, -18, 91, -17, 91, -13, 102), + CABAC_ENTRY(75, -25, 101, -17, 96, -14, 95, 0, 82), + CABAC_ENTRY(76, -23, 67, -26, 81, -25, 84, -7, 74), + CABAC_ENTRY(77, -28, 82, -35, 98, -25, 86, -21, 107), + CABAC_ENTRY(78, -20, 94, -24, 102, -12, 89, -27, 127), + CABAC_ENTRY(79, -16, 83, -23, 97, -17, 91, -31, 127), + CABAC_ENTRY(80, -22, 110, -27, 119, -31, 127, -24, 127), + CABAC_ENTRY(81, -21, 91, -24, 99, -14, 76, -18, 95), + CABAC_ENTRY(82, -18, 102, -21, 110, -18, 103, -27, 127), + CABAC_ENTRY(83, -13, 93, -18, 102, -13, 90, -21, 114), + CABAC_ENTRY(84, -29, 127, -36, 127, -37, 127, -30, 127), + CABAC_ENTRY(85, -7, 92, 0, 80, 11, 80, -17, 123), + CABAC_ENTRY(86, -5, 89, -5, 89, 5, 76, -12, 115), + CABAC_ENTRY(87, -7, 96, -7, 94, 2, 84, -16, 122), + CABAC_ENTRY(88, -13, 108, -4, 92, 5, 78, -11, 115), + CABAC_ENTRY(89, -3, 46, 0, 39, -6, 55, -12, 63), + CABAC_ENTRY(90, -1, 65, 0, 65, 4, 61, -2, 68), + CABAC_ENTRY(91, -1, 57, -15, 84, -14, 83, -15, 84), + CABAC_ENTRY(92, -9, 93, -35, 127, -37, 127, -13, 104), + CABAC_ENTRY(93, -3, 74, -2, 73, -5, 79, -3, 70), + CABAC_ENTRY(94, -9, 92, -12, 104, -11, 104, -8, 93), + CABAC_ENTRY(95, -8, 87, -9, 91, -11, 91, -10, 90), + CABAC_ENTRY(96, -23, 126, -31, 127, -30, 127, -30, 127), + CABAC_ENTRY(97, 5, 54, 3, 55, 0, 65, -1, 74), + CABAC_ENTRY(98, 6, 60, 7, 56, -2, 79, -6, 97), + CABAC_ENTRY(99, 6, 59, 7, 55, 0, 72, -7, 91), + CABAC_ENTRY(100, 6, 69, 8, 61, -4, 92, -20, 127), + CABAC_ENTRY(101, -1, 48, -3, 53, -6, 56, -4, 56), + CABAC_ENTRY(102, 0, 68, 0, 68, 3, 68, -5, 82), + CABAC_ENTRY(103, -4, 69, -7, 74, -8, 71, -7, 76), + CABAC_ENTRY(104, -8, 88, -9, 88, -13, 98, -22, 125), + + /* Table 9-19 – Values of variables m and n for ctxIdx from 105 to 165 */ + CABAC_ENTRY(105, -2, 85, -13, 103, -4, 86, -7, 93), + CABAC_ENTRY(106, -6, 78, -13, 91, -12, 88, -11, 87), + CABAC_ENTRY(107, -1, 75, -9, 89, -5, 82, -3, 77), + CABAC_ENTRY(108, -7, 77, -14, 92, -3, 72, -5, 71), + CABAC_ENTRY(109, 2, 54, -8, 76, -4, 67, -4, 63), + CABAC_ENTRY(110, 5, 50, -12, 87, -8, 72, -4, 68), + CABAC_ENTRY(111, -3, 68, -23, 110, -16, 89, -12, 84), + CABAC_ENTRY(112, 1, 50, -24, 105, -9, 69, -7, 62), + CABAC_ENTRY(113, 6, 42, -10, 78, -1, 59, -7, 65), + CABAC_ENTRY(114, -4, 81, -20, 112, 5, 66, 8, 61), + CABAC_ENTRY(115, 1, 63, -17, 99, 4, 57, 5, 56), + CABAC_ENTRY(116, -4, 70, -78, 127, -4, 71, -2, 66), + CABAC_ENTRY(117, 0, 67, -70, 127, -2, 71, 1, 64), + CABAC_ENTRY(118, 2, 57, -50, 127, 2, 58, 0, 61), + CABAC_ENTRY(119, -2, 76, -46, 127, -1, 74, -2, 78), + CABAC_ENTRY(120, 11, 35, -4, 66, -4, 44, 1, 50), + CABAC_ENTRY(121, 4, 64, -5, 78, -1, 69, 7, 52), + CABAC_ENTRY(122, 1, 61, -4, 71, 0, 62, 10, 35), + CABAC_ENTRY(123, 11, 35, -8, 72, -7, 51, 0, 44), + CABAC_ENTRY(124, 18, 25, 2, 59, -4, 47, 11, 38), + CABAC_ENTRY(125, 12, 24, -1, 55, -6, 42, 1, 45), + CABAC_ENTRY(126, 13, 29, -7, 70, -3, 41, 0, 46), + CABAC_ENTRY(127, 13, 36, -6, 75, -6, 53, 5, 44), + CABAC_ENTRY(128, -10, 93, -8, 89, 8, 76, 31, 17), + CABAC_ENTRY(129, -7, 73, -34, 119, -9, 78, 1, 51), + CABAC_ENTRY(130, -2, 73, -3, 75, -11, 83, 7, 50), + CABAC_ENTRY(131, 13, 46, 32, 20, 9, 52, 28, 19), + CABAC_ENTRY(132, 9, 49, 30, 22, 0, 67, 16, 33), + CABAC_ENTRY(133, -7, 100, -44, 127, -5, 90, 14, 62), + CABAC_ENTRY(134, 9, 53, 0, 54, 1, 67, -13, 108), + CABAC_ENTRY(135, 2, 53, -5, 61, -15, 72, -15, 100), + CABAC_ENTRY(136, 5, 53, 0, 58, -5, 75, -13, 101), + CABAC_ENTRY(137, -2, 61, -1, 60, -8, 80, -13, 91), + CABAC_ENTRY(138, 0, 56, -3, 61, -21, 83, -12, 94), + CABAC_ENTRY(139, 0, 56, -8, 67, -21, 64, -10, 88), + CABAC_ENTRY(140, -13, 63, -25, 84, -13, 31, -16, 84), + CABAC_ENTRY(141, -5, 60, -14, 74, -25, 64, -10, 86), + CABAC_ENTRY(142, -1, 62, -5, 65, -29, 94, -7, 83), + CABAC_ENTRY(143, 4, 57, 5, 52, 9, 75, -13, 87), + CABAC_ENTRY(144, -6, 69, 2, 57, 17, 63, -19, 94), + CABAC_ENTRY(145, 4, 57, 0, 61, -8, 74, 1, 70), + CABAC_ENTRY(146, 14, 39, -9, 69, -5, 35, 0, 72), + CABAC_ENTRY(147, 4, 51, -11, 70, -2, 27, -5, 74), + CABAC_ENTRY(148, 13, 68, 18, 55, 13, 91, 18, 59), + CABAC_ENTRY(149, 3, 64, -4, 71, 3, 65, -8, 102), + CABAC_ENTRY(150, 1, 61, 0, 58, -7, 69, -15, 100), + CABAC_ENTRY(151, 9, 63, 7, 61, 8, 77, 0, 95), + CABAC_ENTRY(152, 7, 50, 9, 41, -10, 66, -4, 75), + CABAC_ENTRY(153, 16, 39, 18, 25, 3, 62, 2, 72), + CABAC_ENTRY(154, 5, 44, 9, 32, -3, 68, -11, 75), + CABAC_ENTRY(155, 4, 52, 5, 43, -20, 81, -3, 71), + CABAC_ENTRY(156, 11, 48, 9, 47, 0, 30, 15, 46), + CABAC_ENTRY(157, -5, 60, 0, 44, 1, 7, -13, 69), + CABAC_ENTRY(158, -1, 59, 0, 51, -3, 23, 0, 62), + CABAC_ENTRY(159, 0, 59, 2, 46, -21, 74, 0, 65), + CABAC_ENTRY(160, 22, 33, 19, 38, 16, 66, 21, 37), + CABAC_ENTRY(161, 5, 44, -4, 66, -23, 124, -15, 72), + CABAC_ENTRY(162, 14, 43, 15, 38, 17, 37, 9, 57), + CABAC_ENTRY(163, -1, 78, 12, 42, 44, -18, 16, 54), + CABAC_ENTRY(164, 0, 60, 9, 34, 50, -34, 0, 62), + CABAC_ENTRY(165, 9, 69, 0, 89, -22, 127, 12, 72), + + /* Table 9-20 – Values of variables m and n for ctxIdx from 166 to 226 */ + CABAC_ENTRY(166, 11, 28, 4, 45, 4, 39, 24, 0), + CABAC_ENTRY(167, 2, 40, 10, 28, 0, 42, 15, 9), + CABAC_ENTRY(168, 3, 44, 10, 31, 7, 34, 8, 25), + CABAC_ENTRY(169, 0, 49, 33, -11, 11, 29, 13, 18), + CABAC_ENTRY(170, 0, 46, 52, -43, 8, 31, 15, 9), + CABAC_ENTRY(171, 2, 44, 18, 15, 6, 37, 13, 19), + CABAC_ENTRY(172, 2, 51, 28, 0, 7, 42, 10, 37), + CABAC_ENTRY(173, 0, 47, 35, -22, 3, 40, 12, 18), + CABAC_ENTRY(174, 4, 39, 38, -25, 8, 33, 6, 29), + CABAC_ENTRY(175, 2, 62, 34, 0, 13, 43, 20, 33), + CABAC_ENTRY(176, 6, 46, 39, -18, 13, 36, 15, 30), + CABAC_ENTRY(177, 0, 54, 32, -12, 4, 47, 4, 45), + CABAC_ENTRY(178, 3, 54, 102, -94, 3, 55, 1, 58), + CABAC_ENTRY(179, 2, 58, 0, 0, 2, 58, 0, 62), + CABAC_ENTRY(180, 4, 63, 56, -15, 6, 60, 7, 61), + CABAC_ENTRY(181, 6, 51, 33, -4, 8, 44, 12, 38), + CABAC_ENTRY(182, 6, 57, 29, 10, 11, 44, 11, 45), + CABAC_ENTRY(183, 7, 53, 37, -5, 14, 42, 15, 39), + CABAC_ENTRY(184, 6, 52, 51, -29, 7, 48, 11, 42), + CABAC_ENTRY(185, 6, 55, 39, -9, 4, 56, 13, 44), + CABAC_ENTRY(186, 11, 45, 52, -34, 4, 52, 16, 45), + CABAC_ENTRY(187, 14, 36, 69, -58, 13, 37, 12, 41), + CABAC_ENTRY(188, 8, 53, 67, -63, 9, 49, 10, 49), + CABAC_ENTRY(189, -1, 82, 44, -5, 19, 58, 30, 34), + CABAC_ENTRY(190, 7, 55, 32, 7, 10, 48, 18, 42), + CABAC_ENTRY(191, -3, 78, 55, -29, 12, 45, 10, 55), + CABAC_ENTRY(192, 15, 46, 32, 1, 0, 69, 17, 51), + CABAC_ENTRY(193, 22, 31, 0, 0, 20, 33, 17, 46), + CABAC_ENTRY(194, -1, 84, 27, 36, 8, 63, 0, 89), + CABAC_ENTRY(195, 25, 7, 33, -25, 35, -18, 26, -19), + CABAC_ENTRY(196, 30, -7, 34, -30, 33, -25, 22, -17), + CABAC_ENTRY(197, 28, 3, 36, -28, 28, -3, 26, -17), + CABAC_ENTRY(198, 28, 4, 38, -28, 24, 10, 30, -25), + CABAC_ENTRY(199, 32, 0, 38, -27, 27, 0, 28, -20), + CABAC_ENTRY(200, 34, -1, 34, -18, 34, -14, 33, -23), + CABAC_ENTRY(201, 30, 6, 35, -16, 52, -44, 37, -27), + CABAC_ENTRY(202, 30, 6, 34, -14, 39, -24, 33, -23), + CABAC_ENTRY(203, 32, 9, 32, -8, 19, 17, 40, -28), + CABAC_ENTRY(204, 31, 19, 37, -6, 31, 25, 38, -17), + CABAC_ENTRY(205, 26, 27, 35, 0, 36, 29, 33, -11), + CABAC_ENTRY(206, 26, 30, 30, 10, 24, 33, 40, -15), + CABAC_ENTRY(207, 37, 20, 28, 18, 34, 15, 41, -6), + CABAC_ENTRY(208, 28, 34, 26, 25, 30, 20, 38, 1), + CABAC_ENTRY(209, 17, 70, 29, 41, 22, 73, 41, 17), + CABAC_ENTRY(210, 1, 67, 0, 75, 20, 34, 30, -6), + CABAC_ENTRY(211, 5, 59, 2, 72, 19, 31, 27, 3), + CABAC_ENTRY(212, 9, 67, 8, 77, 27, 44, 26, 22), + CABAC_ENTRY(213, 16, 30, 14, 35, 19, 16, 37, -16), + CABAC_ENTRY(214, 18, 32, 18, 31, 15, 36, 35, -4), + CABAC_ENTRY(215, 18, 35, 17, 35, 15, 36, 38, -8), + CABAC_ENTRY(216, 22, 29, 21, 30, 21, 28, 38, -3), + CABAC_ENTRY(217, 24, 31, 17, 45, 25, 21, 37, 3), + CABAC_ENTRY(218, 23, 38, 20, 42, 30, 20, 38, 5), + CABAC_ENTRY(219, 18, 43, 18, 45, 31, 12, 42, 0), + CABAC_ENTRY(220, 20, 41, 27, 26, 27, 16, 35, 16), + CABAC_ENTRY(221, 11, 63, 16, 54, 24, 42, 39, 22), + CABAC_ENTRY(222, 9, 59, 7, 66, 0, 93, 14, 48), + CABAC_ENTRY(223, 9, 64, 16, 56, 14, 56, 27, 37), + CABAC_ENTRY(224, -1, 94, 11, 73, 15, 57, 21, 60), + CABAC_ENTRY(225, -2, 89, 10, 67, 26, 38, 12, 68), + CABAC_ENTRY(226, -9, 108, -10, 116, -24, 127, 2, 97), + + /* Table 9-21 – Values of variables m and n for ctxIdx from 227 to 275 */ + CABAC_ENTRY(227, -6, 76, -23, 112, -24, 115, -3, 71), + CABAC_ENTRY(228, -2, 44, -15, 71, -22, 82, -6, 42), + CABAC_ENTRY(229, 0, 45, -7, 61, -9, 62, -5, 50), + CABAC_ENTRY(230, 0, 52, 0, 53, 0, 53, -3, 54), + CABAC_ENTRY(231, -3, 64, -5, 66, 0, 59, -2, 62), + CABAC_ENTRY(232, -2, 59, -11, 77, -14, 85, 0, 58), + CABAC_ENTRY(233, -4, 70, -9, 80, -13, 89, 1, 63), + CABAC_ENTRY(234, -4, 75, -9, 84, -13, 94, -2, 72), + CABAC_ENTRY(235, -8, 82, -10, 87, -11, 92, -1, 74), + CABAC_ENTRY(236, -17, 102, -34, 127, -29, 127, -9, 91), + CABAC_ENTRY(237, -9, 77, -21, 101, -21, 100, -5, 67), + CABAC_ENTRY(238, 3, 24, -3, 39, -14, 57, -5, 27), + CABAC_ENTRY(239, 0, 42, -5, 53, -12, 67, -3, 39), + CABAC_ENTRY(240, 0, 48, -7, 61, -11, 71, -2, 44), + CABAC_ENTRY(241, 0, 55, -11, 75, -10, 77, 0, 46), + CABAC_ENTRY(242, -6, 59, -15, 77, -21, 85, -16, 64), + CABAC_ENTRY(243, -7, 71, -17, 91, -16, 88, -8, 68), + CABAC_ENTRY(244, -12, 83, -25, 107, -23, 104, -10, 78), + CABAC_ENTRY(245, -11, 87, -25, 111, -15, 98, -6, 77), + CABAC_ENTRY(246, -30, 119, -28, 122, -37, 127, -10, 86), + CABAC_ENTRY(247, 1, 58, -11, 76, -10, 82, -12, 92), + CABAC_ENTRY(248, -3, 29, -10, 44, -8, 48, -15, 55), + CABAC_ENTRY(249, -1, 36, -10, 52, -8, 61, -10, 60), + CABAC_ENTRY(250, 1, 38, -10, 57, -8, 66, -6, 62), + CABAC_ENTRY(251, 2, 43, -9, 58, -7, 70, -4, 65), + CABAC_ENTRY(252, -6, 55, -16, 72, -14, 75, -12, 73), + CABAC_ENTRY(253, 0, 58, -7, 69, -10, 79, -8, 76), + CABAC_ENTRY(254, 0, 64, -4, 69, -9, 83, -7, 80), + CABAC_ENTRY(255, -3, 74, -5, 74, -12, 92, -9, 88), + CABAC_ENTRY(256, -10, 90, -9, 86, -18, 108, -17, 110), + CABAC_ENTRY(257, 0, 70, 2, 66, -4, 79, -11, 97), + CABAC_ENTRY(258, -4, 29, -9, 34, -22, 69, -20, 84), + CABAC_ENTRY(259, 5, 31, 1, 32, -16, 75, -11, 79), + CABAC_ENTRY(260, 7, 42, 11, 31, -2, 58, -6, 73), + CABAC_ENTRY(261, 1, 59, 5, 52, 1, 58, -4, 74), + CABAC_ENTRY(262, -2, 58, -2, 55, -13, 78, -13, 86), + CABAC_ENTRY(263, -3, 72, -2, 67, -9, 83, -13, 96), + CABAC_ENTRY(264, -3, 81, 0, 73, -4, 81, -11, 97), + CABAC_ENTRY(265, -11, 97, -8, 89, -13, 99, -19, 117), + CABAC_ENTRY(266, 0, 58, 3, 52, -13, 81, -8, 78), + CABAC_ENTRY(267, 8, 5, 7, 4, -6, 38, -5, 33), + CABAC_ENTRY(268, 10, 14, 10, 8, -13, 62, -4, 48), + CABAC_ENTRY(269, 14, 18, 17, 8, -6, 58, -2, 53), + CABAC_ENTRY(270, 13, 27, 16, 19, -2, 59, -3, 62), + CABAC_ENTRY(271, 2, 40, 3, 37, -16, 73, -13, 71), + CABAC_ENTRY(272, 0, 58, -1, 61, -10, 76, -10, 79), + CABAC_ENTRY(273, -3, 70, -5, 73, -13, 86, -12, 86), + CABAC_ENTRY(274, -6, 79, -1, 70, -9, 83, -13, 90), + CABAC_ENTRY(275, -8, 85, -4, 78, -10, 87, -14, 97), + + /* Table 9-22 – Values of variables m and n for ctxIdx from 277 to 337 */ + CABAC_ENTRY(277, -13, 106, -21, 126, -22, 127, -6, 93), + CABAC_ENTRY(278, -16, 106, -23, 124, -25, 127, -6, 84), + CABAC_ENTRY(279, -10, 87, -20, 110, -25, 120, -8, 79), + CABAC_ENTRY(280, -21, 114, -26, 126, -27, 127, 0, 66), + CABAC_ENTRY(281, -18, 110, -25, 124, -19, 114, -1, 71), + CABAC_ENTRY(282, -14, 98, -17, 105, -23, 117, 0, 62), + CABAC_ENTRY(283, -22, 110, -27, 121, -25, 118, -2, 60), + CABAC_ENTRY(284, -21, 106, -27, 117, -26, 117, -2, 59), + CABAC_ENTRY(285, -18, 103, -17, 102, -24, 113, -5, 75), + CABAC_ENTRY(286, -21, 107, -26, 117, -28, 118, -3, 62), + CABAC_ENTRY(287, -23, 108, -27, 116, -31, 120, -4, 58), + CABAC_ENTRY(288, -26, 112, -33, 122, -37, 124, -9, 66), + CABAC_ENTRY(289, -10, 96, -10, 95, -10, 94, -1, 79), + CABAC_ENTRY(290, -12, 95, -14, 100, -15, 102, 0, 71), + CABAC_ENTRY(291, -5, 91, -8, 95, -10, 99, 3, 68), + CABAC_ENTRY(292, -9, 93, -17, 111, -13, 106, 10, 44), + CABAC_ENTRY(293, -22, 94, -28, 114, -50, 127, -7, 62), + CABAC_ENTRY(294, -5, 86, -6, 89, -5, 92, 15, 36), + CABAC_ENTRY(295, 9, 67, -2, 80, 17, 57, 14, 40), + CABAC_ENTRY(296, -4, 80, -4, 82, -5, 86, 16, 27), + CABAC_ENTRY(297, -10, 85, -9, 85, -13, 94, 12, 29), + CABAC_ENTRY(298, -1, 70, -8, 81, -12, 91, 1, 44), + CABAC_ENTRY(299, 7, 60, -1, 72, -2, 77, 20, 36), + CABAC_ENTRY(300, 9, 58, 5, 64, 0, 71, 18, 32), + CABAC_ENTRY(301, 5, 61, 1, 67, -1, 73, 5, 42), + CABAC_ENTRY(302, 12, 50, 9, 56, 4, 64, 1, 48), + CABAC_ENTRY(303, 15, 50, 0, 69, -7, 81, 10, 62), + CABAC_ENTRY(304, 18, 49, 1, 69, 5, 64, 17, 46), + CABAC_ENTRY(305, 17, 54, 7, 69, 15, 57, 9, 64), + CABAC_ENTRY(306, 10, 41, -7, 69, 1, 67, -12, 104), + CABAC_ENTRY(307, 7, 46, -6, 67, 0, 68, -11, 97), + CABAC_ENTRY(308, -1, 51, -16, 77, -10, 67, -16, 96), + CABAC_ENTRY(309, 7, 49, -2, 64, 1, 68, -7, 88), + CABAC_ENTRY(310, 8, 52, 2, 61, 0, 77, -8, 85), + CABAC_ENTRY(311, 9, 41, -6, 67, 2, 64, -7, 85), + CABAC_ENTRY(312, 6, 47, -3, 64, 0, 68, -9, 85), + CABAC_ENTRY(313, 2, 55, 2, 57, -5, 78, -13, 88), + CABAC_ENTRY(314, 13, 41, -3, 65, 7, 55, 4, 66), + CABAC_ENTRY(315, 10, 44, -3, 66, 5, 59, -3, 77), + CABAC_ENTRY(316, 6, 50, 0, 62, 2, 65, -3, 76), + CABAC_ENTRY(317, 5, 53, 9, 51, 14, 54, -6, 76), + CABAC_ENTRY(318, 13, 49, -1, 66, 15, 44, 10, 58), + CABAC_ENTRY(319, 4, 63, -2, 71, 5, 60, -1, 76), + CABAC_ENTRY(320, 6, 64, -2, 75, 2, 70, -1, 83), + CABAC_ENTRY(321, -2, 69, -1, 70, -2, 76, -7, 99), + CABAC_ENTRY(322, -2, 59, -9, 72, -18, 86, -14, 95), + CABAC_ENTRY(323, 6, 70, 14, 60, 12, 70, 2, 95), + CABAC_ENTRY(324, 10, 44, 16, 37, 5, 64, 0, 76), + CABAC_ENTRY(325, 9, 31, 0, 47, -12, 70, -5, 74), + CABAC_ENTRY(326, 12, 43, 18, 35, 11, 55, 0, 70), + CABAC_ENTRY(327, 3, 53, 11, 37, 5, 56, -11, 75), + CABAC_ENTRY(328, 14, 34, 12, 41, 0, 69, 1, 68), + CABAC_ENTRY(329, 10, 38, 10, 41, 2, 65, 0, 65), + CABAC_ENTRY(330, -3, 52, 2, 48, -6, 74, -14, 73), + CABAC_ENTRY(331, 13, 40, 12, 41, 5, 54, 3, 62), + CABAC_ENTRY(332, 17, 32, 13, 41, 7, 54, 4, 62), + CABAC_ENTRY(333, 7, 44, 0, 59, -6, 76, -1, 68), + CABAC_ENTRY(334, 7, 38, 3, 50, -11, 82, -13, 75), + CABAC_ENTRY(335, 13, 50, 19, 40, -2, 77, 11, 55), + CABAC_ENTRY(336, 10, 57, 3, 66, -2, 77, 5, 64), + CABAC_ENTRY(337, 26, 43, 18, 50, 25, 42, 12, 70), + + /* Table 9-23 – Values of variables m and n for ctxIdx from 338 to 398 */ + CABAC_ENTRY(338, 14, 11, 19, -6, 17, -13, 15, 6), + CABAC_ENTRY(339, 11, 14, 18, -6, 16, -9, 6, 19), + CABAC_ENTRY(340, 9, 11, 14, 0, 17, -12, 7, 16), + CABAC_ENTRY(341, 18, 11, 26, -12, 27, -21, 12, 14), + CABAC_ENTRY(342, 21, 9, 31, -16, 37, -30, 18, 13), + CABAC_ENTRY(343, 23, -2, 33, -25, 41, -40, 13, 11), + CABAC_ENTRY(344, 32, -15, 33, -22, 42, -41, 13, 15), + CABAC_ENTRY(345, 32, -15, 37, -28, 48, -47, 15, 16), + CABAC_ENTRY(346, 34, -21, 39, -30, 39, -32, 12, 23), + CABAC_ENTRY(347, 39, -23, 42, -30, 46, -40, 13, 23), + CABAC_ENTRY(348, 42, -33, 47, -42, 52, -51, 15, 20), + CABAC_ENTRY(349, 41, -31, 45, -36, 46, -41, 14, 26), + CABAC_ENTRY(350, 46, -28, 49, -34, 52, -39, 14, 44), + CABAC_ENTRY(351, 38, -12, 41, -17, 43, -19, 17, 40), + CABAC_ENTRY(352, 21, 29, 32, 9, 32, 11, 17, 47), + CABAC_ENTRY(353, 45, -24, 69, -71, 61, -55, 24, 17), + CABAC_ENTRY(354, 53, -45, 63, -63, 56, -46, 21, 21), + CABAC_ENTRY(355, 48, -26, 66, -64, 62, -50, 25, 22), + CABAC_ENTRY(356, 65, -43, 77, -74, 81, -67, 31, 27), + CABAC_ENTRY(357, 43, -19, 54, -39, 45, -20, 22, 29), + CABAC_ENTRY(358, 39, -10, 52, -35, 35, -2, 19, 35), + CABAC_ENTRY(359, 30, 9, 41, -10, 28, 15, 14, 50), + CABAC_ENTRY(360, 18, 26, 36, 0, 34, 1, 10, 57), + CABAC_ENTRY(361, 20, 27, 40, -1, 39, 1, 7, 63), + CABAC_ENTRY(362, 0, 57, 30, 14, 30, 17, -2, 77), + CABAC_ENTRY(363, -14, 82, 28, 26, 20, 38, -4, 82), + CABAC_ENTRY(364, -5, 75, 23, 37, 18, 45, -3, 94), + CABAC_ENTRY(365, -19, 97, 12, 55, 15, 54, 9, 69), + CABAC_ENTRY(366, -35, 125, 11, 65, 0, 79, -12, 109), + CABAC_ENTRY(367, 27, 0, 37, -33, 36, -16, 36, -35), + CABAC_ENTRY(368, 28, 0, 39, -36, 37, -14, 36, -34), + CABAC_ENTRY(369, 31, -4, 40, -37, 37, -17, 32, -26), + CABAC_ENTRY(370, 27, 6, 38, -30, 32, 1, 37, -30), + CABAC_ENTRY(371, 34, 8, 46, -33, 34, 15, 44, -32), + CABAC_ENTRY(372, 30, 10, 42, -30, 29, 15, 34, -18), + CABAC_ENTRY(373, 24, 22, 40, -24, 24, 25, 34, -15), + CABAC_ENTRY(374, 33, 19, 49, -29, 34, 22, 40, -15), + CABAC_ENTRY(375, 22, 32, 38, -12, 31, 16, 33, -7), + CABAC_ENTRY(376, 26, 31, 40, -10, 35, 18, 35, -5), + CABAC_ENTRY(377, 21, 41, 38, -3, 31, 28, 33, 0), + CABAC_ENTRY(378, 26, 44, 46, -5, 33, 41, 38, 2), + CABAC_ENTRY(379, 23, 47, 31, 20, 36, 28, 33, 13), + CABAC_ENTRY(380, 16, 65, 29, 30, 27, 47, 23, 35), + CABAC_ENTRY(381, 14, 71, 25, 44, 21, 62, 13, 58), + CABAC_ENTRY(382, 8, 60, 12, 48, 18, 31, 29, -3), + CABAC_ENTRY(383, 6, 63, 11, 49, 19, 26, 26, 0), + CABAC_ENTRY(384, 17, 65, 26, 45, 36, 24, 22, 30), + CABAC_ENTRY(385, 21, 24, 22, 22, 24, 23, 31, -7), + CABAC_ENTRY(386, 23, 20, 23, 22, 27, 16, 35, -15), + CABAC_ENTRY(387, 26, 23, 27, 21, 24, 30, 34, -3), + CABAC_ENTRY(388, 27, 32, 33, 20, 31, 29, 34, 3), + CABAC_ENTRY(389, 28, 23, 26, 28, 22, 41, 36, -1), + CABAC_ENTRY(390, 28, 24, 30, 24, 22, 42, 34, 5), + CABAC_ENTRY(391, 23, 40, 27, 34, 16, 60, 32, 11), + CABAC_ENTRY(392, 24, 32, 18, 42, 15, 52, 35, 5), + CABAC_ENTRY(393, 28, 29, 25, 39, 14, 60, 34, 12), + CABAC_ENTRY(394, 23, 42, 18, 50, 3, 78, 39, 11), + CABAC_ENTRY(395, 19, 57, 12, 70, -16, 123, 30, 29), + CABAC_ENTRY(396, 22, 53, 21, 54, 21, 53, 34, 26), + CABAC_ENTRY(397, 22, 61, 14, 71, 22, 56, 29, 39), + CABAC_ENTRY(398, 11, 86, 11, 83, 25, 61, 19, 66), + + /* Values of variables m and n for ctxIdx from 399 to 463 (not documented) */ + CABAC_ENTRY(399, 12, 40, 25, 32, 21, 33, 31, 21), + CABAC_ENTRY(400, 11, 51, 21, 49, 19, 50, 31, 31), + CABAC_ENTRY(401, 14, 59, 21, 54, 17, 61, 25, 50), + CABAC_ENTRY(402, -4, 79, -5, 85, -3, 78, -17, 120), + CABAC_ENTRY(403, -7, 71, -6, 81, -8, 74, -20, 112), + CABAC_ENTRY(404, -5, 69, -10, 77, -9, 72, -18, 114), + CABAC_ENTRY(405, -9, 70, -7, 81, -10, 72, -11, 85), + CABAC_ENTRY(406, -8, 66, -17, 80, -18, 75, -15, 92), + CABAC_ENTRY(407, -10, 68, -18, 73, -12, 71, -14, 89), + CABAC_ENTRY(408, -19, 73, -4, 74, -11, 63, -26, 71), + CABAC_ENTRY(409, -12, 69, -10, 83, -5, 70, -15, 81), + CABAC_ENTRY(410, -16, 70, -9, 71, -17, 75, -14, 80), + CABAC_ENTRY(411, -15, 67, -9, 67, -14, 72, 0, 68), + CABAC_ENTRY(412, -20, 62, -1, 61, -16, 67, -14, 70), + CABAC_ENTRY(413, -19, 70, -8, 66, -8, 53, -24, 56), + CABAC_ENTRY(414, -16, 66, -14, 66, -14, 59, -23, 68), + CABAC_ENTRY(415, -22, 65, 0, 59, -9, 52, -24, 50), + CABAC_ENTRY(416, -20, 63, 2, 59, -11, 68, -11, 74), + CABAC_ENTRY(417, 9, -2, 17, -10, 9, -2, 23, -13), + CABAC_ENTRY(418, 26, -9, 32, -13, 30, -10, 26, -13), + CABAC_ENTRY(419, 33, -9, 42, -9, 31, -4, 40, -15), + CABAC_ENTRY(420, 39, -7, 49, -5, 33, -1, 49, -14), + CABAC_ENTRY(421, 41, -2, 53, 0, 33, 7, 44, 3), + CABAC_ENTRY(422, 45, 3, 64, 3, 31, 12, 45, 6), + CABAC_ENTRY(423, 49, 9, 68, 10, 37, 23, 44, 34), + CABAC_ENTRY(424, 45, 27, 66, 27, 31, 38, 33, 54), + CABAC_ENTRY(425, 36, 59, 47, 57, 20, 64, 19, 82), + CABAC_ENTRY(426, -6, 66, -5, 71, -9, 71, -3, 75), + CABAC_ENTRY(427, -7, 35, 0, 24, -7, 37, -1, 23), + CABAC_ENTRY(428, -7, 42, -1, 36, -8, 44, 1, 34), + CABAC_ENTRY(429, -8, 45, -2, 42, -11, 49, 1, 43), + CABAC_ENTRY(430, -5, 48, -2, 52, -10, 56, 0, 54), + CABAC_ENTRY(431, -12, 56, -9, 57, -12, 59, -2, 55), + CABAC_ENTRY(432, -6, 60, -6, 63, -8, 63, 0, 61), + CABAC_ENTRY(433, -5, 62, -4, 65, -9, 67, 1, 64), + CABAC_ENTRY(434, -8, 66, -4, 67, -6, 68, 0, 68), + CABAC_ENTRY(435, -8, 76, -7, 82, -10, 79, -9, 92), + CABAC_ENTRY(436, -5, 85, -3, 81, -3, 78, -14, 106), + CABAC_ENTRY(437, -6, 81, -3, 76, -8, 74, -13, 97), + CABAC_ENTRY(438, -10, 77, -7, 72, -9, 72, -15, 90), + CABAC_ENTRY(439, -7, 81, -6, 78, -10, 72, -12, 90), + CABAC_ENTRY(440, -17, 80, -12, 72, -18, 75, -18, 88), + CABAC_ENTRY(441, -18, 73, -14, 68, -12, 71, -10, 73), + CABAC_ENTRY(442, -4, 74, -3, 70, -11, 63, -9, 79), + CABAC_ENTRY(443, -10, 83, -6, 76, -5, 70, -14, 86), + CABAC_ENTRY(444, -9, 71, -5, 66, -17, 75, -10, 73), + CABAC_ENTRY(445, -9, 67, -5, 62, -14, 72, -10, 70), + CABAC_ENTRY(446, -1, 61, 0, 57, -16, 67, -10, 69), + CABAC_ENTRY(447, -8, 66, -4, 61, -8, 53, -5, 66), + CABAC_ENTRY(448, -14, 66, -9, 60, -14, 59, -9, 64), + CABAC_ENTRY(449, 0, 59, 1, 54, -9, 52, -5, 58), + CABAC_ENTRY(450, 2, 59, 2, 58, -11, 68, 2, 59), + CABAC_ENTRY(451, 21, -13, 17, -10, 9, -2, 21, -10), + CABAC_ENTRY(452, 33, -14, 32, -13, 30, -10, 24, -11), + CABAC_ENTRY(453, 39, -7, 42, -9, 31, -4, 28, -8), + CABAC_ENTRY(454, 46, -2, 49, -5, 33, -1, 28, -1), + CABAC_ENTRY(455, 51, 2, 53, 0, 33, 7, 29, 3), + CABAC_ENTRY(456, 60, 6, 64, 3, 31, 12, 29, 9), + CABAC_ENTRY(457, 61, 17, 68, 10, 37, 23, 35, 20), + CABAC_ENTRY(458, 55, 34, 66, 27, 31, 38, 29, 36), + CABAC_ENTRY(459, 42, 62, 47, 57, 20, 64, 14, 67), +}; + +const u8 rkvdec_hevc_cabac_table[RKV_HEVC_CABAC_TABLE_SIZE] = { 0x07, 0x0f, 0x48, 0x58, 0x58, 0x40, 0x40, 0x40, 0x40, 0x40, 0x0f, 0x40, 0x40, 0x40, 0x0f, 0x68, 0x48, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x07, 0x40, 0x40, 0x68, 0x58, 0x60, 0x40, 0x1f, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x48, 0x48, 0x60, diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-cabac.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-cabac.h new file mode 100644 index 000000000000..3b721c854b9b --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-cabac.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip Video Decoder CABAC tables + * + * Copyright (C) 2023 Collabora, Ltd. + * Sebastian Fricke + * Copyright (C) 2019 Collabora, Ltd. + * Boris Brezillon + */ + +#ifndef RKVDEC_CABAC_H_ +#define RKVDEC_CABAC_H_ + +#include + +#define RKV_HEVC_CABAC_TABLE_SIZE 27456 + +extern const s8 rkvdec_h264_cabac_table[4][464][2]; +extern const u8 rkvdec_hevc_cabac_table[RKV_HEVC_CABAC_TABLE_SIZE]; + +#endif /* RKVDEC_CABAC_H_ */ diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c index 68e20cb81a88..44e8f0098d92 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c @@ -14,6 +14,7 @@ #include "rkvdec.h" #include "rkvdec-regs.h" +#include "rkvdec-cabac.h" /* Size with u32 units. */ #define RKV_CABAC_INIT_BUFFER_SIZE (3680 + 128) @@ -118,505 +119,6 @@ struct rkvdec_h264_ctx { struct rkvdec_regs regs; }; -#define CABAC_ENTRY(ctxidx, idc0_m, idc0_n, idc1_m, idc1_n, \ - idc2_m, idc2_n, intra_m, intra_n) \ - [0][(ctxidx)] = {idc0_m, idc0_n}, \ - [1][(ctxidx)] = {idc1_m, idc1_n}, \ - [2][(ctxidx)] = {idc2_m, idc2_n}, \ - [3][(ctxidx)] = {intra_m, intra_n} - -/* - * Constant CABAC table. - * Built from the tables described in section '9.3.1.1 Initialisation process - * for context variables' of the H264 spec. - */ -static const s8 rkvdec_h264_cabac_table[4][464][2] = { - /* Table 9-12 – Values of variables m and n for ctxIdx from 0 to 10 */ - CABAC_ENTRY(0, 20, -15, 20, -15, 20, -15, 20, -15), - CABAC_ENTRY(1, 2, 54, 2, 54, 2, 54, 2, 54), - CABAC_ENTRY(2, 3, 74, 3, 74, 3, 74, 3, 74), - CABAC_ENTRY(3, 20, -15, 20, -15, 20, -15, 20, -15), - CABAC_ENTRY(4, 2, 54, 2, 54, 2, 54, 2, 54), - CABAC_ENTRY(5, 3, 74, 3, 74, 3, 74, 3, 74), - CABAC_ENTRY(6, -28, 127, -28, 127, -28, 127, -28, 127), - CABAC_ENTRY(7, -23, 104, -23, 104, -23, 104, -23, 104), - CABAC_ENTRY(8, -6, 53, -6, 53, -6, 53, -6, 53), - CABAC_ENTRY(9, -1, 54, -1, 54, -1, 54, -1, 54), - CABAC_ENTRY(10, 7, 51, 7, 51, 7, 51, 7, 51), - - /* Table 9-13 – Values of variables m and n for ctxIdx from 11 to 23 */ - CABAC_ENTRY(11, 23, 33, 22, 25, 29, 16, 0, 0), - CABAC_ENTRY(12, 23, 2, 34, 0, 25, 0, 0, 0), - CABAC_ENTRY(13, 21, 0, 16, 0, 14, 0, 0, 0), - CABAC_ENTRY(14, 1, 9, -2, 9, -10, 51, 0, 0), - CABAC_ENTRY(15, 0, 49, 4, 41, -3, 62, 0, 0), - CABAC_ENTRY(16, -37, 118, -29, 118, -27, 99, 0, 0), - CABAC_ENTRY(17, 5, 57, 2, 65, 26, 16, 0, 0), - CABAC_ENTRY(18, -13, 78, -6, 71, -4, 85, 0, 0), - CABAC_ENTRY(19, -11, 65, -13, 79, -24, 102, 0, 0), - CABAC_ENTRY(20, 1, 62, 5, 52, 5, 57, 0, 0), - CABAC_ENTRY(21, 12, 49, 9, 50, 6, 57, 0, 0), - CABAC_ENTRY(22, -4, 73, -3, 70, -17, 73, 0, 0), - CABAC_ENTRY(23, 17, 50, 10, 54, 14, 57, 0, 0), - - /* Table 9-14 – Values of variables m and n for ctxIdx from 24 to 39 */ - CABAC_ENTRY(24, 18, 64, 26, 34, 20, 40, 0, 0), - CABAC_ENTRY(25, 9, 43, 19, 22, 20, 10, 0, 0), - CABAC_ENTRY(26, 29, 0, 40, 0, 29, 0, 0, 0), - CABAC_ENTRY(27, 26, 67, 57, 2, 54, 0, 0, 0), - CABAC_ENTRY(28, 16, 90, 41, 36, 37, 42, 0, 0), - CABAC_ENTRY(29, 9, 104, 26, 69, 12, 97, 0, 0), - CABAC_ENTRY(30, -46, 127, -45, 127, -32, 127, 0, 0), - CABAC_ENTRY(31, -20, 104, -15, 101, -22, 117, 0, 0), - CABAC_ENTRY(32, 1, 67, -4, 76, -2, 74, 0, 0), - CABAC_ENTRY(33, -13, 78, -6, 71, -4, 85, 0, 0), - CABAC_ENTRY(34, -11, 65, -13, 79, -24, 102, 0, 0), - CABAC_ENTRY(35, 1, 62, 5, 52, 5, 57, 0, 0), - CABAC_ENTRY(36, -6, 86, 6, 69, -6, 93, 0, 0), - CABAC_ENTRY(37, -17, 95, -13, 90, -14, 88, 0, 0), - CABAC_ENTRY(38, -6, 61, 0, 52, -6, 44, 0, 0), - CABAC_ENTRY(39, 9, 45, 8, 43, 4, 55, 0, 0), - - /* Table 9-15 – Values of variables m and n for ctxIdx from 40 to 53 */ - CABAC_ENTRY(40, -3, 69, -2, 69, -11, 89, 0, 0), - CABAC_ENTRY(41, -6, 81, -5, 82, -15, 103, 0, 0), - CABAC_ENTRY(42, -11, 96, -10, 96, -21, 116, 0, 0), - CABAC_ENTRY(43, 6, 55, 2, 59, 19, 57, 0, 0), - CABAC_ENTRY(44, 7, 67, 2, 75, 20, 58, 0, 0), - CABAC_ENTRY(45, -5, 86, -3, 87, 4, 84, 0, 0), - CABAC_ENTRY(46, 2, 88, -3, 100, 6, 96, 0, 0), - CABAC_ENTRY(47, 0, 58, 1, 56, 1, 63, 0, 0), - CABAC_ENTRY(48, -3, 76, -3, 74, -5, 85, 0, 0), - CABAC_ENTRY(49, -10, 94, -6, 85, -13, 106, 0, 0), - CABAC_ENTRY(50, 5, 54, 0, 59, 5, 63, 0, 0), - CABAC_ENTRY(51, 4, 69, -3, 81, 6, 75, 0, 0), - CABAC_ENTRY(52, -3, 81, -7, 86, -3, 90, 0, 0), - CABAC_ENTRY(53, 0, 88, -5, 95, -1, 101, 0, 0), - - /* Table 9-16 – Values of variables m and n for ctxIdx from 54 to 59 */ - CABAC_ENTRY(54, -7, 67, -1, 66, 3, 55, 0, 0), - CABAC_ENTRY(55, -5, 74, -1, 77, -4, 79, 0, 0), - CABAC_ENTRY(56, -4, 74, 1, 70, -2, 75, 0, 0), - CABAC_ENTRY(57, -5, 80, -2, 86, -12, 97, 0, 0), - CABAC_ENTRY(58, -7, 72, -5, 72, -7, 50, 0, 0), - CABAC_ENTRY(59, 1, 58, 0, 61, 1, 60, 0, 0), - - /* Table 9-17 – Values of variables m and n for ctxIdx from 60 to 69 */ - CABAC_ENTRY(60, 0, 41, 0, 41, 0, 41, 0, 41), - CABAC_ENTRY(61, 0, 63, 0, 63, 0, 63, 0, 63), - CABAC_ENTRY(62, 0, 63, 0, 63, 0, 63, 0, 63), - CABAC_ENTRY(63, 0, 63, 0, 63, 0, 63, 0, 63), - CABAC_ENTRY(64, -9, 83, -9, 83, -9, 83, -9, 83), - CABAC_ENTRY(65, 4, 86, 4, 86, 4, 86, 4, 86), - CABAC_ENTRY(66, 0, 97, 0, 97, 0, 97, 0, 97), - CABAC_ENTRY(67, -7, 72, -7, 72, -7, 72, -7, 72), - CABAC_ENTRY(68, 13, 41, 13, 41, 13, 41, 13, 41), - CABAC_ENTRY(69, 3, 62, 3, 62, 3, 62, 3, 62), - - /* Table 9-18 – Values of variables m and n for ctxIdx from 70 to 104 */ - CABAC_ENTRY(70, 0, 45, 13, 15, 7, 34, 0, 11), - CABAC_ENTRY(71, -4, 78, 7, 51, -9, 88, 1, 55), - CABAC_ENTRY(72, -3, 96, 2, 80, -20, 127, 0, 69), - CABAC_ENTRY(73, -27, 126, -39, 127, -36, 127, -17, 127), - CABAC_ENTRY(74, -28, 98, -18, 91, -17, 91, -13, 102), - CABAC_ENTRY(75, -25, 101, -17, 96, -14, 95, 0, 82), - CABAC_ENTRY(76, -23, 67, -26, 81, -25, 84, -7, 74), - CABAC_ENTRY(77, -28, 82, -35, 98, -25, 86, -21, 107), - CABAC_ENTRY(78, -20, 94, -24, 102, -12, 89, -27, 127), - CABAC_ENTRY(79, -16, 83, -23, 97, -17, 91, -31, 127), - CABAC_ENTRY(80, -22, 110, -27, 119, -31, 127, -24, 127), - CABAC_ENTRY(81, -21, 91, -24, 99, -14, 76, -18, 95), - CABAC_ENTRY(82, -18, 102, -21, 110, -18, 103, -27, 127), - CABAC_ENTRY(83, -13, 93, -18, 102, -13, 90, -21, 114), - CABAC_ENTRY(84, -29, 127, -36, 127, -37, 127, -30, 127), - CABAC_ENTRY(85, -7, 92, 0, 80, 11, 80, -17, 123), - CABAC_ENTRY(86, -5, 89, -5, 89, 5, 76, -12, 115), - CABAC_ENTRY(87, -7, 96, -7, 94, 2, 84, -16, 122), - CABAC_ENTRY(88, -13, 108, -4, 92, 5, 78, -11, 115), - CABAC_ENTRY(89, -3, 46, 0, 39, -6, 55, -12, 63), - CABAC_ENTRY(90, -1, 65, 0, 65, 4, 61, -2, 68), - CABAC_ENTRY(91, -1, 57, -15, 84, -14, 83, -15, 84), - CABAC_ENTRY(92, -9, 93, -35, 127, -37, 127, -13, 104), - CABAC_ENTRY(93, -3, 74, -2, 73, -5, 79, -3, 70), - CABAC_ENTRY(94, -9, 92, -12, 104, -11, 104, -8, 93), - CABAC_ENTRY(95, -8, 87, -9, 91, -11, 91, -10, 90), - CABAC_ENTRY(96, -23, 126, -31, 127, -30, 127, -30, 127), - CABAC_ENTRY(97, 5, 54, 3, 55, 0, 65, -1, 74), - CABAC_ENTRY(98, 6, 60, 7, 56, -2, 79, -6, 97), - CABAC_ENTRY(99, 6, 59, 7, 55, 0, 72, -7, 91), - CABAC_ENTRY(100, 6, 69, 8, 61, -4, 92, -20, 127), - CABAC_ENTRY(101, -1, 48, -3, 53, -6, 56, -4, 56), - CABAC_ENTRY(102, 0, 68, 0, 68, 3, 68, -5, 82), - CABAC_ENTRY(103, -4, 69, -7, 74, -8, 71, -7, 76), - CABAC_ENTRY(104, -8, 88, -9, 88, -13, 98, -22, 125), - - /* Table 9-19 – Values of variables m and n for ctxIdx from 105 to 165 */ - CABAC_ENTRY(105, -2, 85, -13, 103, -4, 86, -7, 93), - CABAC_ENTRY(106, -6, 78, -13, 91, -12, 88, -11, 87), - CABAC_ENTRY(107, -1, 75, -9, 89, -5, 82, -3, 77), - CABAC_ENTRY(108, -7, 77, -14, 92, -3, 72, -5, 71), - CABAC_ENTRY(109, 2, 54, -8, 76, -4, 67, -4, 63), - CABAC_ENTRY(110, 5, 50, -12, 87, -8, 72, -4, 68), - CABAC_ENTRY(111, -3, 68, -23, 110, -16, 89, -12, 84), - CABAC_ENTRY(112, 1, 50, -24, 105, -9, 69, -7, 62), - CABAC_ENTRY(113, 6, 42, -10, 78, -1, 59, -7, 65), - CABAC_ENTRY(114, -4, 81, -20, 112, 5, 66, 8, 61), - CABAC_ENTRY(115, 1, 63, -17, 99, 4, 57, 5, 56), - CABAC_ENTRY(116, -4, 70, -78, 127, -4, 71, -2, 66), - CABAC_ENTRY(117, 0, 67, -70, 127, -2, 71, 1, 64), - CABAC_ENTRY(118, 2, 57, -50, 127, 2, 58, 0, 61), - CABAC_ENTRY(119, -2, 76, -46, 127, -1, 74, -2, 78), - CABAC_ENTRY(120, 11, 35, -4, 66, -4, 44, 1, 50), - CABAC_ENTRY(121, 4, 64, -5, 78, -1, 69, 7, 52), - CABAC_ENTRY(122, 1, 61, -4, 71, 0, 62, 10, 35), - CABAC_ENTRY(123, 11, 35, -8, 72, -7, 51, 0, 44), - CABAC_ENTRY(124, 18, 25, 2, 59, -4, 47, 11, 38), - CABAC_ENTRY(125, 12, 24, -1, 55, -6, 42, 1, 45), - CABAC_ENTRY(126, 13, 29, -7, 70, -3, 41, 0, 46), - CABAC_ENTRY(127, 13, 36, -6, 75, -6, 53, 5, 44), - CABAC_ENTRY(128, -10, 93, -8, 89, 8, 76, 31, 17), - CABAC_ENTRY(129, -7, 73, -34, 119, -9, 78, 1, 51), - CABAC_ENTRY(130, -2, 73, -3, 75, -11, 83, 7, 50), - CABAC_ENTRY(131, 13, 46, 32, 20, 9, 52, 28, 19), - CABAC_ENTRY(132, 9, 49, 30, 22, 0, 67, 16, 33), - CABAC_ENTRY(133, -7, 100, -44, 127, -5, 90, 14, 62), - CABAC_ENTRY(134, 9, 53, 0, 54, 1, 67, -13, 108), - CABAC_ENTRY(135, 2, 53, -5, 61, -15, 72, -15, 100), - CABAC_ENTRY(136, 5, 53, 0, 58, -5, 75, -13, 101), - CABAC_ENTRY(137, -2, 61, -1, 60, -8, 80, -13, 91), - CABAC_ENTRY(138, 0, 56, -3, 61, -21, 83, -12, 94), - CABAC_ENTRY(139, 0, 56, -8, 67, -21, 64, -10, 88), - CABAC_ENTRY(140, -13, 63, -25, 84, -13, 31, -16, 84), - CABAC_ENTRY(141, -5, 60, -14, 74, -25, 64, -10, 86), - CABAC_ENTRY(142, -1, 62, -5, 65, -29, 94, -7, 83), - CABAC_ENTRY(143, 4, 57, 5, 52, 9, 75, -13, 87), - CABAC_ENTRY(144, -6, 69, 2, 57, 17, 63, -19, 94), - CABAC_ENTRY(145, 4, 57, 0, 61, -8, 74, 1, 70), - CABAC_ENTRY(146, 14, 39, -9, 69, -5, 35, 0, 72), - CABAC_ENTRY(147, 4, 51, -11, 70, -2, 27, -5, 74), - CABAC_ENTRY(148, 13, 68, 18, 55, 13, 91, 18, 59), - CABAC_ENTRY(149, 3, 64, -4, 71, 3, 65, -8, 102), - CABAC_ENTRY(150, 1, 61, 0, 58, -7, 69, -15, 100), - CABAC_ENTRY(151, 9, 63, 7, 61, 8, 77, 0, 95), - CABAC_ENTRY(152, 7, 50, 9, 41, -10, 66, -4, 75), - CABAC_ENTRY(153, 16, 39, 18, 25, 3, 62, 2, 72), - CABAC_ENTRY(154, 5, 44, 9, 32, -3, 68, -11, 75), - CABAC_ENTRY(155, 4, 52, 5, 43, -20, 81, -3, 71), - CABAC_ENTRY(156, 11, 48, 9, 47, 0, 30, 15, 46), - CABAC_ENTRY(157, -5, 60, 0, 44, 1, 7, -13, 69), - CABAC_ENTRY(158, -1, 59, 0, 51, -3, 23, 0, 62), - CABAC_ENTRY(159, 0, 59, 2, 46, -21, 74, 0, 65), - CABAC_ENTRY(160, 22, 33, 19, 38, 16, 66, 21, 37), - CABAC_ENTRY(161, 5, 44, -4, 66, -23, 124, -15, 72), - CABAC_ENTRY(162, 14, 43, 15, 38, 17, 37, 9, 57), - CABAC_ENTRY(163, -1, 78, 12, 42, 44, -18, 16, 54), - CABAC_ENTRY(164, 0, 60, 9, 34, 50, -34, 0, 62), - CABAC_ENTRY(165, 9, 69, 0, 89, -22, 127, 12, 72), - - /* Table 9-20 – Values of variables m and n for ctxIdx from 166 to 226 */ - CABAC_ENTRY(166, 11, 28, 4, 45, 4, 39, 24, 0), - CABAC_ENTRY(167, 2, 40, 10, 28, 0, 42, 15, 9), - CABAC_ENTRY(168, 3, 44, 10, 31, 7, 34, 8, 25), - CABAC_ENTRY(169, 0, 49, 33, -11, 11, 29, 13, 18), - CABAC_ENTRY(170, 0, 46, 52, -43, 8, 31, 15, 9), - CABAC_ENTRY(171, 2, 44, 18, 15, 6, 37, 13, 19), - CABAC_ENTRY(172, 2, 51, 28, 0, 7, 42, 10, 37), - CABAC_ENTRY(173, 0, 47, 35, -22, 3, 40, 12, 18), - CABAC_ENTRY(174, 4, 39, 38, -25, 8, 33, 6, 29), - CABAC_ENTRY(175, 2, 62, 34, 0, 13, 43, 20, 33), - CABAC_ENTRY(176, 6, 46, 39, -18, 13, 36, 15, 30), - CABAC_ENTRY(177, 0, 54, 32, -12, 4, 47, 4, 45), - CABAC_ENTRY(178, 3, 54, 102, -94, 3, 55, 1, 58), - CABAC_ENTRY(179, 2, 58, 0, 0, 2, 58, 0, 62), - CABAC_ENTRY(180, 4, 63, 56, -15, 6, 60, 7, 61), - CABAC_ENTRY(181, 6, 51, 33, -4, 8, 44, 12, 38), - CABAC_ENTRY(182, 6, 57, 29, 10, 11, 44, 11, 45), - CABAC_ENTRY(183, 7, 53, 37, -5, 14, 42, 15, 39), - CABAC_ENTRY(184, 6, 52, 51, -29, 7, 48, 11, 42), - CABAC_ENTRY(185, 6, 55, 39, -9, 4, 56, 13, 44), - CABAC_ENTRY(186, 11, 45, 52, -34, 4, 52, 16, 45), - CABAC_ENTRY(187, 14, 36, 69, -58, 13, 37, 12, 41), - CABAC_ENTRY(188, 8, 53, 67, -63, 9, 49, 10, 49), - CABAC_ENTRY(189, -1, 82, 44, -5, 19, 58, 30, 34), - CABAC_ENTRY(190, 7, 55, 32, 7, 10, 48, 18, 42), - CABAC_ENTRY(191, -3, 78, 55, -29, 12, 45, 10, 55), - CABAC_ENTRY(192, 15, 46, 32, 1, 0, 69, 17, 51), - CABAC_ENTRY(193, 22, 31, 0, 0, 20, 33, 17, 46), - CABAC_ENTRY(194, -1, 84, 27, 36, 8, 63, 0, 89), - CABAC_ENTRY(195, 25, 7, 33, -25, 35, -18, 26, -19), - CABAC_ENTRY(196, 30, -7, 34, -30, 33, -25, 22, -17), - CABAC_ENTRY(197, 28, 3, 36, -28, 28, -3, 26, -17), - CABAC_ENTRY(198, 28, 4, 38, -28, 24, 10, 30, -25), - CABAC_ENTRY(199, 32, 0, 38, -27, 27, 0, 28, -20), - CABAC_ENTRY(200, 34, -1, 34, -18, 34, -14, 33, -23), - CABAC_ENTRY(201, 30, 6, 35, -16, 52, -44, 37, -27), - CABAC_ENTRY(202, 30, 6, 34, -14, 39, -24, 33, -23), - CABAC_ENTRY(203, 32, 9, 32, -8, 19, 17, 40, -28), - CABAC_ENTRY(204, 31, 19, 37, -6, 31, 25, 38, -17), - CABAC_ENTRY(205, 26, 27, 35, 0, 36, 29, 33, -11), - CABAC_ENTRY(206, 26, 30, 30, 10, 24, 33, 40, -15), - CABAC_ENTRY(207, 37, 20, 28, 18, 34, 15, 41, -6), - CABAC_ENTRY(208, 28, 34, 26, 25, 30, 20, 38, 1), - CABAC_ENTRY(209, 17, 70, 29, 41, 22, 73, 41, 17), - CABAC_ENTRY(210, 1, 67, 0, 75, 20, 34, 30, -6), - CABAC_ENTRY(211, 5, 59, 2, 72, 19, 31, 27, 3), - CABAC_ENTRY(212, 9, 67, 8, 77, 27, 44, 26, 22), - CABAC_ENTRY(213, 16, 30, 14, 35, 19, 16, 37, -16), - CABAC_ENTRY(214, 18, 32, 18, 31, 15, 36, 35, -4), - CABAC_ENTRY(215, 18, 35, 17, 35, 15, 36, 38, -8), - CABAC_ENTRY(216, 22, 29, 21, 30, 21, 28, 38, -3), - CABAC_ENTRY(217, 24, 31, 17, 45, 25, 21, 37, 3), - CABAC_ENTRY(218, 23, 38, 20, 42, 30, 20, 38, 5), - CABAC_ENTRY(219, 18, 43, 18, 45, 31, 12, 42, 0), - CABAC_ENTRY(220, 20, 41, 27, 26, 27, 16, 35, 16), - CABAC_ENTRY(221, 11, 63, 16, 54, 24, 42, 39, 22), - CABAC_ENTRY(222, 9, 59, 7, 66, 0, 93, 14, 48), - CABAC_ENTRY(223, 9, 64, 16, 56, 14, 56, 27, 37), - CABAC_ENTRY(224, -1, 94, 11, 73, 15, 57, 21, 60), - CABAC_ENTRY(225, -2, 89, 10, 67, 26, 38, 12, 68), - CABAC_ENTRY(226, -9, 108, -10, 116, -24, 127, 2, 97), - - /* Table 9-21 – Values of variables m and n for ctxIdx from 227 to 275 */ - CABAC_ENTRY(227, -6, 76, -23, 112, -24, 115, -3, 71), - CABAC_ENTRY(228, -2, 44, -15, 71, -22, 82, -6, 42), - CABAC_ENTRY(229, 0, 45, -7, 61, -9, 62, -5, 50), - CABAC_ENTRY(230, 0, 52, 0, 53, 0, 53, -3, 54), - CABAC_ENTRY(231, -3, 64, -5, 66, 0, 59, -2, 62), - CABAC_ENTRY(232, -2, 59, -11, 77, -14, 85, 0, 58), - CABAC_ENTRY(233, -4, 70, -9, 80, -13, 89, 1, 63), - CABAC_ENTRY(234, -4, 75, -9, 84, -13, 94, -2, 72), - CABAC_ENTRY(235, -8, 82, -10, 87, -11, 92, -1, 74), - CABAC_ENTRY(236, -17, 102, -34, 127, -29, 127, -9, 91), - CABAC_ENTRY(237, -9, 77, -21, 101, -21, 100, -5, 67), - CABAC_ENTRY(238, 3, 24, -3, 39, -14, 57, -5, 27), - CABAC_ENTRY(239, 0, 42, -5, 53, -12, 67, -3, 39), - CABAC_ENTRY(240, 0, 48, -7, 61, -11, 71, -2, 44), - CABAC_ENTRY(241, 0, 55, -11, 75, -10, 77, 0, 46), - CABAC_ENTRY(242, -6, 59, -15, 77, -21, 85, -16, 64), - CABAC_ENTRY(243, -7, 71, -17, 91, -16, 88, -8, 68), - CABAC_ENTRY(244, -12, 83, -25, 107, -23, 104, -10, 78), - CABAC_ENTRY(245, -11, 87, -25, 111, -15, 98, -6, 77), - CABAC_ENTRY(246, -30, 119, -28, 122, -37, 127, -10, 86), - CABAC_ENTRY(247, 1, 58, -11, 76, -10, 82, -12, 92), - CABAC_ENTRY(248, -3, 29, -10, 44, -8, 48, -15, 55), - CABAC_ENTRY(249, -1, 36, -10, 52, -8, 61, -10, 60), - CABAC_ENTRY(250, 1, 38, -10, 57, -8, 66, -6, 62), - CABAC_ENTRY(251, 2, 43, -9, 58, -7, 70, -4, 65), - CABAC_ENTRY(252, -6, 55, -16, 72, -14, 75, -12, 73), - CABAC_ENTRY(253, 0, 58, -7, 69, -10, 79, -8, 76), - CABAC_ENTRY(254, 0, 64, -4, 69, -9, 83, -7, 80), - CABAC_ENTRY(255, -3, 74, -5, 74, -12, 92, -9, 88), - CABAC_ENTRY(256, -10, 90, -9, 86, -18, 108, -17, 110), - CABAC_ENTRY(257, 0, 70, 2, 66, -4, 79, -11, 97), - CABAC_ENTRY(258, -4, 29, -9, 34, -22, 69, -20, 84), - CABAC_ENTRY(259, 5, 31, 1, 32, -16, 75, -11, 79), - CABAC_ENTRY(260, 7, 42, 11, 31, -2, 58, -6, 73), - CABAC_ENTRY(261, 1, 59, 5, 52, 1, 58, -4, 74), - CABAC_ENTRY(262, -2, 58, -2, 55, -13, 78, -13, 86), - CABAC_ENTRY(263, -3, 72, -2, 67, -9, 83, -13, 96), - CABAC_ENTRY(264, -3, 81, 0, 73, -4, 81, -11, 97), - CABAC_ENTRY(265, -11, 97, -8, 89, -13, 99, -19, 117), - CABAC_ENTRY(266, 0, 58, 3, 52, -13, 81, -8, 78), - CABAC_ENTRY(267, 8, 5, 7, 4, -6, 38, -5, 33), - CABAC_ENTRY(268, 10, 14, 10, 8, -13, 62, -4, 48), - CABAC_ENTRY(269, 14, 18, 17, 8, -6, 58, -2, 53), - CABAC_ENTRY(270, 13, 27, 16, 19, -2, 59, -3, 62), - CABAC_ENTRY(271, 2, 40, 3, 37, -16, 73, -13, 71), - CABAC_ENTRY(272, 0, 58, -1, 61, -10, 76, -10, 79), - CABAC_ENTRY(273, -3, 70, -5, 73, -13, 86, -12, 86), - CABAC_ENTRY(274, -6, 79, -1, 70, -9, 83, -13, 90), - CABAC_ENTRY(275, -8, 85, -4, 78, -10, 87, -14, 97), - - /* Table 9-22 – Values of variables m and n for ctxIdx from 277 to 337 */ - CABAC_ENTRY(277, -13, 106, -21, 126, -22, 127, -6, 93), - CABAC_ENTRY(278, -16, 106, -23, 124, -25, 127, -6, 84), - CABAC_ENTRY(279, -10, 87, -20, 110, -25, 120, -8, 79), - CABAC_ENTRY(280, -21, 114, -26, 126, -27, 127, 0, 66), - CABAC_ENTRY(281, -18, 110, -25, 124, -19, 114, -1, 71), - CABAC_ENTRY(282, -14, 98, -17, 105, -23, 117, 0, 62), - CABAC_ENTRY(283, -22, 110, -27, 121, -25, 118, -2, 60), - CABAC_ENTRY(284, -21, 106, -27, 117, -26, 117, -2, 59), - CABAC_ENTRY(285, -18, 103, -17, 102, -24, 113, -5, 75), - CABAC_ENTRY(286, -21, 107, -26, 117, -28, 118, -3, 62), - CABAC_ENTRY(287, -23, 108, -27, 116, -31, 120, -4, 58), - CABAC_ENTRY(288, -26, 112, -33, 122, -37, 124, -9, 66), - CABAC_ENTRY(289, -10, 96, -10, 95, -10, 94, -1, 79), - CABAC_ENTRY(290, -12, 95, -14, 100, -15, 102, 0, 71), - CABAC_ENTRY(291, -5, 91, -8, 95, -10, 99, 3, 68), - CABAC_ENTRY(292, -9, 93, -17, 111, -13, 106, 10, 44), - CABAC_ENTRY(293, -22, 94, -28, 114, -50, 127, -7, 62), - CABAC_ENTRY(294, -5, 86, -6, 89, -5, 92, 15, 36), - CABAC_ENTRY(295, 9, 67, -2, 80, 17, 57, 14, 40), - CABAC_ENTRY(296, -4, 80, -4, 82, -5, 86, 16, 27), - CABAC_ENTRY(297, -10, 85, -9, 85, -13, 94, 12, 29), - CABAC_ENTRY(298, -1, 70, -8, 81, -12, 91, 1, 44), - CABAC_ENTRY(299, 7, 60, -1, 72, -2, 77, 20, 36), - CABAC_ENTRY(300, 9, 58, 5, 64, 0, 71, 18, 32), - CABAC_ENTRY(301, 5, 61, 1, 67, -1, 73, 5, 42), - CABAC_ENTRY(302, 12, 50, 9, 56, 4, 64, 1, 48), - CABAC_ENTRY(303, 15, 50, 0, 69, -7, 81, 10, 62), - CABAC_ENTRY(304, 18, 49, 1, 69, 5, 64, 17, 46), - CABAC_ENTRY(305, 17, 54, 7, 69, 15, 57, 9, 64), - CABAC_ENTRY(306, 10, 41, -7, 69, 1, 67, -12, 104), - CABAC_ENTRY(307, 7, 46, -6, 67, 0, 68, -11, 97), - CABAC_ENTRY(308, -1, 51, -16, 77, -10, 67, -16, 96), - CABAC_ENTRY(309, 7, 49, -2, 64, 1, 68, -7, 88), - CABAC_ENTRY(310, 8, 52, 2, 61, 0, 77, -8, 85), - CABAC_ENTRY(311, 9, 41, -6, 67, 2, 64, -7, 85), - CABAC_ENTRY(312, 6, 47, -3, 64, 0, 68, -9, 85), - CABAC_ENTRY(313, 2, 55, 2, 57, -5, 78, -13, 88), - CABAC_ENTRY(314, 13, 41, -3, 65, 7, 55, 4, 66), - CABAC_ENTRY(315, 10, 44, -3, 66, 5, 59, -3, 77), - CABAC_ENTRY(316, 6, 50, 0, 62, 2, 65, -3, 76), - CABAC_ENTRY(317, 5, 53, 9, 51, 14, 54, -6, 76), - CABAC_ENTRY(318, 13, 49, -1, 66, 15, 44, 10, 58), - CABAC_ENTRY(319, 4, 63, -2, 71, 5, 60, -1, 76), - CABAC_ENTRY(320, 6, 64, -2, 75, 2, 70, -1, 83), - CABAC_ENTRY(321, -2, 69, -1, 70, -2, 76, -7, 99), - CABAC_ENTRY(322, -2, 59, -9, 72, -18, 86, -14, 95), - CABAC_ENTRY(323, 6, 70, 14, 60, 12, 70, 2, 95), - CABAC_ENTRY(324, 10, 44, 16, 37, 5, 64, 0, 76), - CABAC_ENTRY(325, 9, 31, 0, 47, -12, 70, -5, 74), - CABAC_ENTRY(326, 12, 43, 18, 35, 11, 55, 0, 70), - CABAC_ENTRY(327, 3, 53, 11, 37, 5, 56, -11, 75), - CABAC_ENTRY(328, 14, 34, 12, 41, 0, 69, 1, 68), - CABAC_ENTRY(329, 10, 38, 10, 41, 2, 65, 0, 65), - CABAC_ENTRY(330, -3, 52, 2, 48, -6, 74, -14, 73), - CABAC_ENTRY(331, 13, 40, 12, 41, 5, 54, 3, 62), - CABAC_ENTRY(332, 17, 32, 13, 41, 7, 54, 4, 62), - CABAC_ENTRY(333, 7, 44, 0, 59, -6, 76, -1, 68), - CABAC_ENTRY(334, 7, 38, 3, 50, -11, 82, -13, 75), - CABAC_ENTRY(335, 13, 50, 19, 40, -2, 77, 11, 55), - CABAC_ENTRY(336, 10, 57, 3, 66, -2, 77, 5, 64), - CABAC_ENTRY(337, 26, 43, 18, 50, 25, 42, 12, 70), - - /* Table 9-23 – Values of variables m and n for ctxIdx from 338 to 398 */ - CABAC_ENTRY(338, 14, 11, 19, -6, 17, -13, 15, 6), - CABAC_ENTRY(339, 11, 14, 18, -6, 16, -9, 6, 19), - CABAC_ENTRY(340, 9, 11, 14, 0, 17, -12, 7, 16), - CABAC_ENTRY(341, 18, 11, 26, -12, 27, -21, 12, 14), - CABAC_ENTRY(342, 21, 9, 31, -16, 37, -30, 18, 13), - CABAC_ENTRY(343, 23, -2, 33, -25, 41, -40, 13, 11), - CABAC_ENTRY(344, 32, -15, 33, -22, 42, -41, 13, 15), - CABAC_ENTRY(345, 32, -15, 37, -28, 48, -47, 15, 16), - CABAC_ENTRY(346, 34, -21, 39, -30, 39, -32, 12, 23), - CABAC_ENTRY(347, 39, -23, 42, -30, 46, -40, 13, 23), - CABAC_ENTRY(348, 42, -33, 47, -42, 52, -51, 15, 20), - CABAC_ENTRY(349, 41, -31, 45, -36, 46, -41, 14, 26), - CABAC_ENTRY(350, 46, -28, 49, -34, 52, -39, 14, 44), - CABAC_ENTRY(351, 38, -12, 41, -17, 43, -19, 17, 40), - CABAC_ENTRY(352, 21, 29, 32, 9, 32, 11, 17, 47), - CABAC_ENTRY(353, 45, -24, 69, -71, 61, -55, 24, 17), - CABAC_ENTRY(354, 53, -45, 63, -63, 56, -46, 21, 21), - CABAC_ENTRY(355, 48, -26, 66, -64, 62, -50, 25, 22), - CABAC_ENTRY(356, 65, -43, 77, -74, 81, -67, 31, 27), - CABAC_ENTRY(357, 43, -19, 54, -39, 45, -20, 22, 29), - CABAC_ENTRY(358, 39, -10, 52, -35, 35, -2, 19, 35), - CABAC_ENTRY(359, 30, 9, 41, -10, 28, 15, 14, 50), - CABAC_ENTRY(360, 18, 26, 36, 0, 34, 1, 10, 57), - CABAC_ENTRY(361, 20, 27, 40, -1, 39, 1, 7, 63), - CABAC_ENTRY(362, 0, 57, 30, 14, 30, 17, -2, 77), - CABAC_ENTRY(363, -14, 82, 28, 26, 20, 38, -4, 82), - CABAC_ENTRY(364, -5, 75, 23, 37, 18, 45, -3, 94), - CABAC_ENTRY(365, -19, 97, 12, 55, 15, 54, 9, 69), - CABAC_ENTRY(366, -35, 125, 11, 65, 0, 79, -12, 109), - CABAC_ENTRY(367, 27, 0, 37, -33, 36, -16, 36, -35), - CABAC_ENTRY(368, 28, 0, 39, -36, 37, -14, 36, -34), - CABAC_ENTRY(369, 31, -4, 40, -37, 37, -17, 32, -26), - CABAC_ENTRY(370, 27, 6, 38, -30, 32, 1, 37, -30), - CABAC_ENTRY(371, 34, 8, 46, -33, 34, 15, 44, -32), - CABAC_ENTRY(372, 30, 10, 42, -30, 29, 15, 34, -18), - CABAC_ENTRY(373, 24, 22, 40, -24, 24, 25, 34, -15), - CABAC_ENTRY(374, 33, 19, 49, -29, 34, 22, 40, -15), - CABAC_ENTRY(375, 22, 32, 38, -12, 31, 16, 33, -7), - CABAC_ENTRY(376, 26, 31, 40, -10, 35, 18, 35, -5), - CABAC_ENTRY(377, 21, 41, 38, -3, 31, 28, 33, 0), - CABAC_ENTRY(378, 26, 44, 46, -5, 33, 41, 38, 2), - CABAC_ENTRY(379, 23, 47, 31, 20, 36, 28, 33, 13), - CABAC_ENTRY(380, 16, 65, 29, 30, 27, 47, 23, 35), - CABAC_ENTRY(381, 14, 71, 25, 44, 21, 62, 13, 58), - CABAC_ENTRY(382, 8, 60, 12, 48, 18, 31, 29, -3), - CABAC_ENTRY(383, 6, 63, 11, 49, 19, 26, 26, 0), - CABAC_ENTRY(384, 17, 65, 26, 45, 36, 24, 22, 30), - CABAC_ENTRY(385, 21, 24, 22, 22, 24, 23, 31, -7), - CABAC_ENTRY(386, 23, 20, 23, 22, 27, 16, 35, -15), - CABAC_ENTRY(387, 26, 23, 27, 21, 24, 30, 34, -3), - CABAC_ENTRY(388, 27, 32, 33, 20, 31, 29, 34, 3), - CABAC_ENTRY(389, 28, 23, 26, 28, 22, 41, 36, -1), - CABAC_ENTRY(390, 28, 24, 30, 24, 22, 42, 34, 5), - CABAC_ENTRY(391, 23, 40, 27, 34, 16, 60, 32, 11), - CABAC_ENTRY(392, 24, 32, 18, 42, 15, 52, 35, 5), - CABAC_ENTRY(393, 28, 29, 25, 39, 14, 60, 34, 12), - CABAC_ENTRY(394, 23, 42, 18, 50, 3, 78, 39, 11), - CABAC_ENTRY(395, 19, 57, 12, 70, -16, 123, 30, 29), - CABAC_ENTRY(396, 22, 53, 21, 54, 21, 53, 34, 26), - CABAC_ENTRY(397, 22, 61, 14, 71, 22, 56, 29, 39), - CABAC_ENTRY(398, 11, 86, 11, 83, 25, 61, 19, 66), - - /* Values of variables m and n for ctxIdx from 399 to 463 (not documented) */ - CABAC_ENTRY(399, 12, 40, 25, 32, 21, 33, 31, 21), - CABAC_ENTRY(400, 11, 51, 21, 49, 19, 50, 31, 31), - CABAC_ENTRY(401, 14, 59, 21, 54, 17, 61, 25, 50), - CABAC_ENTRY(402, -4, 79, -5, 85, -3, 78, -17, 120), - CABAC_ENTRY(403, -7, 71, -6, 81, -8, 74, -20, 112), - CABAC_ENTRY(404, -5, 69, -10, 77, -9, 72, -18, 114), - CABAC_ENTRY(405, -9, 70, -7, 81, -10, 72, -11, 85), - CABAC_ENTRY(406, -8, 66, -17, 80, -18, 75, -15, 92), - CABAC_ENTRY(407, -10, 68, -18, 73, -12, 71, -14, 89), - CABAC_ENTRY(408, -19, 73, -4, 74, -11, 63, -26, 71), - CABAC_ENTRY(409, -12, 69, -10, 83, -5, 70, -15, 81), - CABAC_ENTRY(410, -16, 70, -9, 71, -17, 75, -14, 80), - CABAC_ENTRY(411, -15, 67, -9, 67, -14, 72, 0, 68), - CABAC_ENTRY(412, -20, 62, -1, 61, -16, 67, -14, 70), - CABAC_ENTRY(413, -19, 70, -8, 66, -8, 53, -24, 56), - CABAC_ENTRY(414, -16, 66, -14, 66, -14, 59, -23, 68), - CABAC_ENTRY(415, -22, 65, 0, 59, -9, 52, -24, 50), - CABAC_ENTRY(416, -20, 63, 2, 59, -11, 68, -11, 74), - CABAC_ENTRY(417, 9, -2, 17, -10, 9, -2, 23, -13), - CABAC_ENTRY(418, 26, -9, 32, -13, 30, -10, 26, -13), - CABAC_ENTRY(419, 33, -9, 42, -9, 31, -4, 40, -15), - CABAC_ENTRY(420, 39, -7, 49, -5, 33, -1, 49, -14), - CABAC_ENTRY(421, 41, -2, 53, 0, 33, 7, 44, 3), - CABAC_ENTRY(422, 45, 3, 64, 3, 31, 12, 45, 6), - CABAC_ENTRY(423, 49, 9, 68, 10, 37, 23, 44, 34), - CABAC_ENTRY(424, 45, 27, 66, 27, 31, 38, 33, 54), - CABAC_ENTRY(425, 36, 59, 47, 57, 20, 64, 19, 82), - CABAC_ENTRY(426, -6, 66, -5, 71, -9, 71, -3, 75), - CABAC_ENTRY(427, -7, 35, 0, 24, -7, 37, -1, 23), - CABAC_ENTRY(428, -7, 42, -1, 36, -8, 44, 1, 34), - CABAC_ENTRY(429, -8, 45, -2, 42, -11, 49, 1, 43), - CABAC_ENTRY(430, -5, 48, -2, 52, -10, 56, 0, 54), - CABAC_ENTRY(431, -12, 56, -9, 57, -12, 59, -2, 55), - CABAC_ENTRY(432, -6, 60, -6, 63, -8, 63, 0, 61), - CABAC_ENTRY(433, -5, 62, -4, 65, -9, 67, 1, 64), - CABAC_ENTRY(434, -8, 66, -4, 67, -6, 68, 0, 68), - CABAC_ENTRY(435, -8, 76, -7, 82, -10, 79, -9, 92), - CABAC_ENTRY(436, -5, 85, -3, 81, -3, 78, -14, 106), - CABAC_ENTRY(437, -6, 81, -3, 76, -8, 74, -13, 97), - CABAC_ENTRY(438, -10, 77, -7, 72, -9, 72, -15, 90), - CABAC_ENTRY(439, -7, 81, -6, 78, -10, 72, -12, 90), - CABAC_ENTRY(440, -17, 80, -12, 72, -18, 75, -18, 88), - CABAC_ENTRY(441, -18, 73, -14, 68, -12, 71, -10, 73), - CABAC_ENTRY(442, -4, 74, -3, 70, -11, 63, -9, 79), - CABAC_ENTRY(443, -10, 83, -6, 76, -5, 70, -14, 86), - CABAC_ENTRY(444, -9, 71, -5, 66, -17, 75, -10, 73), - CABAC_ENTRY(445, -9, 67, -5, 62, -14, 72, -10, 70), - CABAC_ENTRY(446, -1, 61, 0, 57, -16, 67, -10, 69), - CABAC_ENTRY(447, -8, 66, -4, 61, -8, 53, -5, 66), - CABAC_ENTRY(448, -14, 66, -9, 60, -14, 59, -9, 64), - CABAC_ENTRY(449, 0, 59, 1, 54, -9, 52, -5, 58), - CABAC_ENTRY(450, 2, 59, 2, 58, -11, 68, 2, 59), - CABAC_ENTRY(451, 21, -13, 17, -10, 9, -2, 21, -10), - CABAC_ENTRY(452, 33, -14, 32, -13, 30, -10, 24, -11), - CABAC_ENTRY(453, 39, -7, 42, -9, 31, -4, 28, -8), - CABAC_ENTRY(454, 46, -2, 49, -5, 33, -1, 28, -1), - CABAC_ENTRY(455, 51, 2, 53, 0, 33, 7, 29, 3), - CABAC_ENTRY(456, 60, 6, 64, 3, 31, 12, 29, 9), - CABAC_ENTRY(457, 61, 17, 68, 10, 37, 23, 35, 20), - CABAC_ENTRY(458, 55, 34, 66, 27, 31, 38, 29, 36), - CABAC_ENTRY(459, 42, 62, 47, 57, 20, 64, 14, 67), -}; - static void set_ps_field(u32 *buf, struct rkvdec_ps_field field, u32 value) { u8 bit = field.offset % 32, word = field.offset / 32; diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c index 2d9e0e947a6d..f8221c4089c7 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c @@ -16,7 +16,7 @@ #include "rkvdec.h" #include "rkvdec-regs.h" -#include "rkvdec-hevc-data.c" +#include "rkvdec-cabac.h" /* Size in u8/u32 units. */ #define RKV_SCALING_LIST_SIZE 1360 @@ -110,7 +110,7 @@ struct rkvdec_ps_field { /* Data structure describing auxiliary buffer format. */ struct rkvdec_hevc_priv_tbl { - u8 cabac_table[RKV_CABAC_TABLE_SIZE]; + u8 cabac_table[RKV_HEVC_CABAC_TABLE_SIZE]; u8 scaling_list[RKV_SCALING_LIST_SIZE]; struct rkvdec_sps_pps_packet param_set[RKV_PPS_LEN]; struct rkvdec_rps_packet rps[RKV_RPS_LEN]; From cf29115a68e334f2216cd7d4dfc6d51e986ca726 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:22 -0500 Subject: [PATCH 292/307] media: rkvdec: Use structs to represent the HW RPS This is in preparation to add support for other variants of the decoder. Moving to struct representation is mainly to prepare for multicore support that is present in e.g. rk3588. Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../platform/rockchip/rkvdec/rkvdec-h264.c | 93 +++++++++++++++++-- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c index 44e8f0098d92..d1d19dd6167b 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c @@ -18,7 +18,6 @@ /* Size with u32 units. */ #define RKV_CABAC_INIT_BUFFER_SIZE (3680 + 128) -#define RKV_RPS_SIZE ((128 + 128) / 4) #define RKV_ERROR_INFO_SIZE (256 * 144 * 4) #define RKVDEC_NUM_REFLIST 3 @@ -33,6 +32,40 @@ struct rkvdec_sps_pps_packet { u32 info[8]; }; +struct rkvdec_rps_entry { + u32 dpb_info0: 5; + u32 bottom_flag0: 1; + u32 view_index_off0: 1; + u32 dpb_info1: 5; + u32 bottom_flag1: 1; + u32 view_index_off1: 1; + u32 dpb_info2: 5; + u32 bottom_flag2: 1; + u32 view_index_off2: 1; + u32 dpb_info3: 5; + u32 bottom_flag3: 1; + u32 view_index_off3: 1; + u32 dpb_info4: 5; + u32 bottom_flag4: 1; + u32 view_index_off4: 1; + u32 dpb_info5: 5; + u32 bottom_flag5: 1; + u32 view_index_off5: 1; + u32 dpb_info6: 5; + u32 bottom_flag6: 1; + u32 view_index_off6: 1; + u32 dpb_info7: 5; + u32 bottom_flag7: 1; + u32 view_index_off7: 1; +} __packed; + +struct rkvdec_rps { + u16 frame_num[16]; + u32 reserved0; + struct rkvdec_rps_entry entries[12]; + u32 reserved1[66]; +} __packed; + struct rkvdec_ps_field { u16 offset; u8 len; @@ -93,7 +126,7 @@ struct rkvdec_ps_field { struct rkvdec_h264_priv_tbl { s8 cabac_table[4][464][2]; struct rkvdec_h264_scaling_list scaling_list; - u32 rps[RKV_RPS_SIZE]; + struct rkvdec_rps rps; struct rkvdec_sps_pps_packet param_set[256]; u8 err_info[RKV_ERROR_INFO_SIZE]; }; @@ -259,6 +292,51 @@ static void lookup_ref_buf_idx(struct rkvdec_ctx *ctx, } } +static void set_dpb_info(struct rkvdec_rps_entry *entries, + u8 reflist, + u8 refnum, + u8 info, + bool bottom) +{ + struct rkvdec_rps_entry *entry = &entries[(reflist * 4) + refnum / 8]; + u8 idx = refnum % 8; + + switch (idx) { + case 0: + entry->dpb_info0 = info; + entry->bottom_flag0 = bottom; + break; + case 1: + entry->dpb_info1 = info; + entry->bottom_flag1 = bottom; + break; + case 2: + entry->dpb_info2 = info; + entry->bottom_flag2 = bottom; + break; + case 3: + entry->dpb_info3 = info; + entry->bottom_flag3 = bottom; + break; + case 4: + entry->dpb_info4 = info; + entry->bottom_flag4 = bottom; + break; + case 5: + entry->dpb_info5 = info; + entry->bottom_flag5 = bottom; + break; + case 6: + entry->dpb_info6 = info; + entry->bottom_flag6 = bottom; + break; + case 7: + entry->dpb_info7 = info; + entry->bottom_flag7 = bottom; + break; + } +} + static void assemble_hw_rps(struct rkvdec_ctx *ctx, struct v4l2_h264_reflist_builder *builder, struct rkvdec_h264_run *run) @@ -268,11 +346,10 @@ static void assemble_hw_rps(struct rkvdec_ctx *ctx, struct rkvdec_h264_ctx *h264_ctx = ctx->priv; struct rkvdec_h264_priv_tbl *priv_tbl = h264_ctx->priv_tbl.cpu; - u32 *hw_rps = priv_tbl->rps; + struct rkvdec_rps *hw_rps = &priv_tbl->rps; u32 i, j; - u16 *p = (u16 *)hw_rps; - memset(hw_rps, 0, sizeof(priv_tbl->rps)); + memset(hw_rps, 0, sizeof(*hw_rps)); /* * Assign an invalid pic_num if DPB entry at that position is inactive. @@ -284,7 +361,7 @@ static void assemble_hw_rps(struct rkvdec_ctx *ctx, if (!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) continue; - p[i] = builder->refs[i].frame_num; + hw_rps->frame_num[i] = builder->refs[i].frame_num; } for (j = 0; j < RKVDEC_NUM_REFLIST; j++) { @@ -311,9 +388,7 @@ static void assemble_hw_rps(struct rkvdec_ctx *ctx, dpb_valid = run->ref_buf[ref->index] != NULL; bottom = ref->fields == V4L2_H264_BOTTOM_FIELD_REF; - set_ps_field(hw_rps, DPB_INFO(i, j), - ref->index | dpb_valid << 4); - set_ps_field(hw_rps, BOTTOM_FLAG(i, j), bottom); + set_dpb_info(hw_rps->entries, j, i, ref->index | (dpb_valid << 4), bottom); } } } From 560438ed7c2c09efacd5937771769d828d3aa525 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:23 -0500 Subject: [PATCH 293/307] media: rkvdec: Move h264 functions to common file This is a preparation commit to add support for new variants of the decoder. The functions will later be shared with vdpu381 (rk3588) and vdpu383 (rk3576). Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/Makefile | 1 + .../rockchip/rkvdec/rkvdec-h264-common.c | 258 +++++++++++++++ .../rockchip/rkvdec/rkvdec-h264-common.h | 85 +++++ .../platform/rockchip/rkvdec/rkvdec-h264.c | 308 +----------------- 4 files changed, 348 insertions(+), 304 deletions(-) create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-h264-common.c create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-h264-common.h diff --git a/drivers/media/platform/rockchip/rkvdec/Makefile b/drivers/media/platform/rockchip/rkvdec/Makefile index a8ff5e3d7bec..d2ba7a7c15e5 100644 --- a/drivers/media/platform/rockchip/rkvdec/Makefile +++ b/drivers/media/platform/rockchip/rkvdec/Makefile @@ -4,5 +4,6 @@ rockchip-vdec-y += \ rkvdec.o \ rkvdec-cabac.o \ rkvdec-h264.o \ + rkvdec-h264-common.o \ rkvdec-hevc.o \ rkvdec-vp9.o diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-h264-common.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264-common.c new file mode 100644 index 000000000000..e28f06394470 --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264-common.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip video decoder h264 common functions + * + * Copyright (C) 2025 Collabora, Ltd. + * Detlev Casanova + * + * Copyright (C) 2019 Collabora, Ltd. + * Boris Brezillon + * + * Copyright (C) 2016 Rockchip Electronics Co., Ltd. + * Jeffy Chen + */ + +#include +#include +#include + +#include "rkvdec.h" +#include "rkvdec-h264-common.h" + +#define RKVDEC_NUM_REFLIST 3 + +static void set_dpb_info(struct rkvdec_rps_entry *entries, + u8 reflist, + u8 refnum, + u8 info, + bool bottom) +{ + struct rkvdec_rps_entry *entry = &entries[(reflist * 4) + refnum / 8]; + u8 idx = refnum % 8; + + switch (idx) { + case 0: + entry->dpb_info0 = info; + entry->bottom_flag0 = bottom; + break; + case 1: + entry->dpb_info1 = info; + entry->bottom_flag1 = bottom; + break; + case 2: + entry->dpb_info2 = info; + entry->bottom_flag2 = bottom; + break; + case 3: + entry->dpb_info3 = info; + entry->bottom_flag3 = bottom; + break; + case 4: + entry->dpb_info4 = info; + entry->bottom_flag4 = bottom; + break; + case 5: + entry->dpb_info5 = info; + entry->bottom_flag5 = bottom; + break; + case 6: + entry->dpb_info6 = info; + entry->bottom_flag6 = bottom; + break; + case 7: + entry->dpb_info7 = info; + entry->bottom_flag7 = bottom; + break; + } +} + +void lookup_ref_buf_idx(struct rkvdec_ctx *ctx, + struct rkvdec_h264_run *run) +{ + const struct v4l2_ctrl_h264_decode_params *dec_params = run->decode_params; + u32 i; + + for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { + struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; + const struct v4l2_h264_dpb_entry *dpb = run->decode_params->dpb; + struct vb2_queue *cap_q = &m2m_ctx->cap_q_ctx.q; + struct vb2_buffer *buf = NULL; + + if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE) { + buf = vb2_find_buffer(cap_q, dpb[i].reference_ts); + if (!buf) + pr_debug("No buffer for reference_ts %llu", + dpb[i].reference_ts); + } + + run->ref_buf[i] = buf; + } +} + +void assemble_hw_rps(struct v4l2_h264_reflist_builder *builder, + struct rkvdec_h264_run *run, + struct rkvdec_h264_reflists *reflists, + struct rkvdec_rps *hw_rps) +{ + const struct v4l2_ctrl_h264_decode_params *dec_params = run->decode_params; + const struct v4l2_h264_dpb_entry *dpb = dec_params->dpb; + + u32 i, j; + + memset(hw_rps, 0, sizeof(*hw_rps)); + + /* + * Assign an invalid pic_num if DPB entry at that position is inactive. + * If we assign 0 in that position hardware will treat that as a real + * reference picture with pic_num 0, triggering output picture + * corruption. + */ + for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { + if (!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) + continue; + + hw_rps->frame_num[i] = builder->refs[i].frame_num; + } + + for (j = 0; j < RKVDEC_NUM_REFLIST; j++) { + for (i = 0; i < builder->num_valid; i++) { + struct v4l2_h264_reference *ref; + bool dpb_valid; + bool bottom; + + switch (j) { + case 0: + ref = &reflists->p[i]; + break; + case 1: + ref = &reflists->b0[i]; + break; + case 2: + ref = &reflists->b1[i]; + break; + } + + if (WARN_ON(ref->index >= ARRAY_SIZE(dec_params->dpb))) + continue; + + dpb_valid = !!(run->ref_buf[ref->index]); + bottom = ref->fields == V4L2_H264_BOTTOM_FIELD_REF; + + set_dpb_info(hw_rps->entries, j, i, ref->index | (dpb_valid << 4), bottom); + } + } +} + +void assemble_hw_scaling_list(struct rkvdec_h264_run *run, + struct rkvdec_h264_scaling_list *scaling_list) +{ + const struct v4l2_ctrl_h264_scaling_matrix *scaling = run->scaling_matrix; + const struct v4l2_ctrl_h264_pps *pps = run->pps; + + if (!(pps->flags & V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT)) + return; + + BUILD_BUG_ON(sizeof(scaling_list->scaling_list_4x4) != + sizeof(scaling->scaling_list_4x4)); + BUILD_BUG_ON(sizeof(scaling_list->scaling_list_8x8) != + sizeof(scaling->scaling_list_8x8)); + + memcpy(scaling_list->scaling_list_4x4, + scaling->scaling_list_4x4, + sizeof(scaling->scaling_list_4x4)); + + memcpy(scaling_list->scaling_list_8x8, + scaling->scaling_list_8x8, + sizeof(scaling->scaling_list_8x8)); +} + +#define RKVDEC_H264_MAX_DEPTH_IN_BYTES 2 + +int rkvdec_h264_adjust_fmt(struct rkvdec_ctx *ctx, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *fmt = &f->fmt.pix_mp; + + fmt->num_planes = 1; + if (!fmt->plane_fmt[0].sizeimage) + fmt->plane_fmt[0].sizeimage = fmt->width * fmt->height * + RKVDEC_H264_MAX_DEPTH_IN_BYTES; + return 0; +} + +enum rkvdec_image_fmt rkvdec_h264_get_image_fmt(struct rkvdec_ctx *ctx, + struct v4l2_ctrl *ctrl) +{ + const struct v4l2_ctrl_h264_sps *sps = ctrl->p_new.p_h264_sps; + + if (ctrl->id != V4L2_CID_STATELESS_H264_SPS) + return RKVDEC_IMG_FMT_ANY; + + if (sps->bit_depth_luma_minus8 == 0) { + if (sps->chroma_format_idc == 2) + return RKVDEC_IMG_FMT_422_8BIT; + else + return RKVDEC_IMG_FMT_420_8BIT; + } else if (sps->bit_depth_luma_minus8 == 2) { + if (sps->chroma_format_idc == 2) + return RKVDEC_IMG_FMT_422_10BIT; + else + return RKVDEC_IMG_FMT_420_10BIT; + } + + return RKVDEC_IMG_FMT_ANY; +} + +int rkvdec_h264_validate_sps(struct rkvdec_ctx *ctx, + const struct v4l2_ctrl_h264_sps *sps) +{ + unsigned int width, height; + + if (sps->chroma_format_idc > 2) + /* Only 4:0:0, 4:2:0 and 4:2:2 are supported */ + return -EINVAL; + if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8) + /* Luma and chroma bit depth mismatch */ + return -EINVAL; + if (sps->bit_depth_luma_minus8 != 0 && sps->bit_depth_luma_minus8 != 2) + /* Only 8-bit and 10-bit is supported */ + return -EINVAL; + + width = (sps->pic_width_in_mbs_minus1 + 1) * 16; + height = (sps->pic_height_in_map_units_minus1 + 1) * 16; + + /* + * When frame_mbs_only_flag is not set, this is field height, + * which is half the final height (see (7-18) in the + * specification) + */ + if (!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY)) + height *= 2; + + if (width > ctx->coded_fmt.fmt.pix_mp.width || + height > ctx->coded_fmt.fmt.pix_mp.height) + return -EINVAL; + + return 0; +} + +void rkvdec_h264_run_preamble(struct rkvdec_ctx *ctx, + struct rkvdec_h264_run *run) +{ + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_H264_DECODE_PARAMS); + run->decode_params = ctrl ? ctrl->p_cur.p : NULL; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_H264_SPS); + run->sps = ctrl ? ctrl->p_cur.p : NULL; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_H264_PPS); + run->pps = ctrl ? ctrl->p_cur.p : NULL; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_H264_SCALING_MATRIX); + run->scaling_matrix = ctrl ? ctrl->p_cur.p : NULL; + + rkvdec_run_preamble(ctx, &run->base); +} diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-h264-common.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264-common.h new file mode 100644 index 000000000000..5336370507d6 --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264-common.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip video decoder h264 common functions + * + * Copyright (C) 2025 Collabora, Ltd. + * Detlev Casanova + * + * Copyright (C) 2019 Collabora, Ltd. + * Boris Brezillon + * + * Copyright (C) 2016 Rockchip Electronics Co., Ltd. + * Jeffy Chen + */ + +#include +#include + +#include "rkvdec.h" + +struct rkvdec_h264_scaling_list { + u8 scaling_list_4x4[6][16]; + u8 scaling_list_8x8[6][64]; + u8 padding[128]; +}; + +struct rkvdec_h264_reflists { + struct v4l2_h264_reference p[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference b0[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference b1[V4L2_H264_REF_LIST_LEN]; +}; + +struct rkvdec_h264_run { + struct rkvdec_run base; + const struct v4l2_ctrl_h264_decode_params *decode_params; + const struct v4l2_ctrl_h264_sps *sps; + const struct v4l2_ctrl_h264_pps *pps; + const struct v4l2_ctrl_h264_scaling_matrix *scaling_matrix; + struct vb2_buffer *ref_buf[V4L2_H264_NUM_DPB_ENTRIES]; +}; + +struct rkvdec_rps_entry { + u32 dpb_info0: 5; + u32 bottom_flag0: 1; + u32 view_index_off0: 1; + u32 dpb_info1: 5; + u32 bottom_flag1: 1; + u32 view_index_off1: 1; + u32 dpb_info2: 5; + u32 bottom_flag2: 1; + u32 view_index_off2: 1; + u32 dpb_info3: 5; + u32 bottom_flag3: 1; + u32 view_index_off3: 1; + u32 dpb_info4: 5; + u32 bottom_flag4: 1; + u32 view_index_off4: 1; + u32 dpb_info5: 5; + u32 bottom_flag5: 1; + u32 view_index_off5: 1; + u32 dpb_info6: 5; + u32 bottom_flag6: 1; + u32 view_index_off6: 1; + u32 dpb_info7: 5; + u32 bottom_flag7: 1; + u32 view_index_off7: 1; +} __packed; + +struct rkvdec_rps { + u16 frame_num[16]; + u32 reserved0; + struct rkvdec_rps_entry entries[12]; + u32 reserved1[66]; +} __packed; + +void lookup_ref_buf_idx(struct rkvdec_ctx *ctx, struct rkvdec_h264_run *run); +void assemble_hw_rps(struct v4l2_h264_reflist_builder *builder, + struct rkvdec_h264_run *run, + struct rkvdec_h264_reflists *reflists, + struct rkvdec_rps *hw_rps); +void assemble_hw_scaling_list(struct rkvdec_h264_run *run, + struct rkvdec_h264_scaling_list *scaling_list); +int rkvdec_h264_adjust_fmt(struct rkvdec_ctx *ctx, struct v4l2_format *f); +enum rkvdec_image_fmt rkvdec_h264_get_image_fmt(struct rkvdec_ctx *ctx, struct v4l2_ctrl *ctrl); +int rkvdec_h264_validate_sps(struct rkvdec_ctx *ctx, const struct v4l2_ctrl_h264_sps *sps); +void rkvdec_h264_run_preamble(struct rkvdec_ctx *ctx, struct rkvdec_h264_run *run); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c index d1d19dd6167b..a1af12c97914 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c @@ -15,57 +15,16 @@ #include "rkvdec.h" #include "rkvdec-regs.h" #include "rkvdec-cabac.h" +#include "rkvdec-h264-common.h" /* Size with u32 units. */ #define RKV_CABAC_INIT_BUFFER_SIZE (3680 + 128) #define RKV_ERROR_INFO_SIZE (256 * 144 * 4) -#define RKVDEC_NUM_REFLIST 3 - -struct rkvdec_h264_scaling_list { - u8 scaling_list_4x4[6][16]; - u8 scaling_list_8x8[6][64]; - u8 padding[128]; -}; - struct rkvdec_sps_pps_packet { u32 info[8]; }; -struct rkvdec_rps_entry { - u32 dpb_info0: 5; - u32 bottom_flag0: 1; - u32 view_index_off0: 1; - u32 dpb_info1: 5; - u32 bottom_flag1: 1; - u32 view_index_off1: 1; - u32 dpb_info2: 5; - u32 bottom_flag2: 1; - u32 view_index_off2: 1; - u32 dpb_info3: 5; - u32 bottom_flag3: 1; - u32 view_index_off3: 1; - u32 dpb_info4: 5; - u32 bottom_flag4: 1; - u32 view_index_off4: 1; - u32 dpb_info5: 5; - u32 bottom_flag5: 1; - u32 view_index_off5: 1; - u32 dpb_info6: 5; - u32 bottom_flag6: 1; - u32 view_index_off6: 1; - u32 dpb_info7: 5; - u32 bottom_flag7: 1; - u32 view_index_off7: 1; -} __packed; - -struct rkvdec_rps { - u16 frame_num[16]; - u32 reserved0; - struct rkvdec_rps_entry entries[12]; - u32 reserved1[66]; -} __packed; - struct rkvdec_ps_field { u16 offset; u8 len; @@ -117,11 +76,6 @@ struct rkvdec_ps_field { #define SCALING_LIST_ADDRESS PS_FIELD(184, 32) #define IS_LONG_TERM(i) PS_FIELD(216 + (i), 1) -#define DPB_OFFS(i, j) (288 + ((j) * 32 * 7) + ((i) * 7)) -#define DPB_INFO(i, j) PS_FIELD(DPB_OFFS(i, j), 5) -#define BOTTOM_FLAG(i, j) PS_FIELD(DPB_OFFS(i, j) + 5, 1) -#define VIEW_INDEX_OFF(i, j) PS_FIELD(DPB_OFFS(i, j) + 6, 1) - /* Data structure describing auxiliary buffer format. */ struct rkvdec_h264_priv_tbl { s8 cabac_table[4][464][2]; @@ -131,21 +85,6 @@ struct rkvdec_h264_priv_tbl { u8 err_info[RKV_ERROR_INFO_SIZE]; }; -struct rkvdec_h264_reflists { - struct v4l2_h264_reference p[V4L2_H264_REF_LIST_LEN]; - struct v4l2_h264_reference b0[V4L2_H264_REF_LIST_LEN]; - struct v4l2_h264_reference b1[V4L2_H264_REF_LIST_LEN]; -}; - -struct rkvdec_h264_run { - struct rkvdec_run base; - const struct v4l2_ctrl_h264_decode_params *decode_params; - const struct v4l2_ctrl_h264_sps *sps; - const struct v4l2_ctrl_h264_pps *pps; - const struct v4l2_ctrl_h264_scaling_matrix *scaling_matrix; - struct vb2_buffer *ref_buf[V4L2_H264_NUM_DPB_ENTRIES]; -}; - struct rkvdec_h264_ctx { struct rkvdec_aux_buf priv_tbl; struct rkvdec_h264_reflists reflists; @@ -269,155 +208,6 @@ static void assemble_hw_pps(struct rkvdec_ctx *ctx, } } -static void lookup_ref_buf_idx(struct rkvdec_ctx *ctx, - struct rkvdec_h264_run *run) -{ - const struct v4l2_ctrl_h264_decode_params *dec_params = run->decode_params; - u32 i; - - for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { - struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; - const struct v4l2_h264_dpb_entry *dpb = run->decode_params->dpb; - struct vb2_queue *cap_q = &m2m_ctx->cap_q_ctx.q; - struct vb2_buffer *buf = NULL; - - if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE) { - buf = vb2_find_buffer(cap_q, dpb[i].reference_ts); - if (!buf) - pr_debug("No buffer for reference_ts %llu", - dpb[i].reference_ts); - } - - run->ref_buf[i] = buf; - } -} - -static void set_dpb_info(struct rkvdec_rps_entry *entries, - u8 reflist, - u8 refnum, - u8 info, - bool bottom) -{ - struct rkvdec_rps_entry *entry = &entries[(reflist * 4) + refnum / 8]; - u8 idx = refnum % 8; - - switch (idx) { - case 0: - entry->dpb_info0 = info; - entry->bottom_flag0 = bottom; - break; - case 1: - entry->dpb_info1 = info; - entry->bottom_flag1 = bottom; - break; - case 2: - entry->dpb_info2 = info; - entry->bottom_flag2 = bottom; - break; - case 3: - entry->dpb_info3 = info; - entry->bottom_flag3 = bottom; - break; - case 4: - entry->dpb_info4 = info; - entry->bottom_flag4 = bottom; - break; - case 5: - entry->dpb_info5 = info; - entry->bottom_flag5 = bottom; - break; - case 6: - entry->dpb_info6 = info; - entry->bottom_flag6 = bottom; - break; - case 7: - entry->dpb_info7 = info; - entry->bottom_flag7 = bottom; - break; - } -} - -static void assemble_hw_rps(struct rkvdec_ctx *ctx, - struct v4l2_h264_reflist_builder *builder, - struct rkvdec_h264_run *run) -{ - const struct v4l2_ctrl_h264_decode_params *dec_params = run->decode_params; - const struct v4l2_h264_dpb_entry *dpb = dec_params->dpb; - struct rkvdec_h264_ctx *h264_ctx = ctx->priv; - struct rkvdec_h264_priv_tbl *priv_tbl = h264_ctx->priv_tbl.cpu; - - struct rkvdec_rps *hw_rps = &priv_tbl->rps; - u32 i, j; - - memset(hw_rps, 0, sizeof(*hw_rps)); - - /* - * Assign an invalid pic_num if DPB entry at that position is inactive. - * If we assign 0 in that position hardware will treat that as a real - * reference picture with pic_num 0, triggering output picture - * corruption. - */ - for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { - if (!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) - continue; - - hw_rps->frame_num[i] = builder->refs[i].frame_num; - } - - for (j = 0; j < RKVDEC_NUM_REFLIST; j++) { - for (i = 0; i < builder->num_valid; i++) { - struct v4l2_h264_reference *ref; - bool dpb_valid; - bool bottom; - - switch (j) { - case 0: - ref = &h264_ctx->reflists.p[i]; - break; - case 1: - ref = &h264_ctx->reflists.b0[i]; - break; - case 2: - ref = &h264_ctx->reflists.b1[i]; - break; - } - - if (WARN_ON(ref->index >= ARRAY_SIZE(dec_params->dpb))) - continue; - - dpb_valid = run->ref_buf[ref->index] != NULL; - bottom = ref->fields == V4L2_H264_BOTTOM_FIELD_REF; - - set_dpb_info(hw_rps->entries, j, i, ref->index | (dpb_valid << 4), bottom); - } - } -} - -static void assemble_hw_scaling_list(struct rkvdec_ctx *ctx, - struct rkvdec_h264_run *run) -{ - const struct v4l2_ctrl_h264_scaling_matrix *scaling = run->scaling_matrix; - const struct v4l2_ctrl_h264_pps *pps = run->pps; - struct rkvdec_h264_ctx *h264_ctx = ctx->priv; - struct rkvdec_h264_priv_tbl *tbl = h264_ctx->priv_tbl.cpu; - - if (!(pps->flags & V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT)) - return; - - BUILD_BUG_ON(sizeof(tbl->scaling_list.scaling_list_4x4) != - sizeof(scaling->scaling_list_4x4)); - BUILD_BUG_ON(sizeof(tbl->scaling_list.scaling_list_8x8) != - sizeof(scaling->scaling_list_8x8)); - - memcpy(tbl->scaling_list.scaling_list_4x4, - scaling->scaling_list_4x4, - sizeof(scaling->scaling_list_4x4)); - - memcpy(tbl->scaling_list.scaling_list_8x8, - scaling->scaling_list_8x8, - sizeof(scaling->scaling_list_8x8)); -} - /* * Set the ref POC in the correct register. * @@ -568,76 +358,6 @@ static void config_registers(struct rkvdec_ctx *ctx, MIN(sizeof(*regs), sizeof(u32) * rkvdec->variant->num_regs)); } -#define RKVDEC_H264_MAX_DEPTH_IN_BYTES 2 - -static int rkvdec_h264_adjust_fmt(struct rkvdec_ctx *ctx, - struct v4l2_format *f) -{ - struct v4l2_pix_format_mplane *fmt = &f->fmt.pix_mp; - - fmt->num_planes = 1; - if (!fmt->plane_fmt[0].sizeimage) - fmt->plane_fmt[0].sizeimage = fmt->width * fmt->height * - RKVDEC_H264_MAX_DEPTH_IN_BYTES; - return 0; -} - -static enum rkvdec_image_fmt rkvdec_h264_get_image_fmt(struct rkvdec_ctx *ctx, - struct v4l2_ctrl *ctrl) -{ - const struct v4l2_ctrl_h264_sps *sps = ctrl->p_new.p_h264_sps; - - if (ctrl->id != V4L2_CID_STATELESS_H264_SPS) - return RKVDEC_IMG_FMT_ANY; - - if (sps->bit_depth_luma_minus8 == 0) { - if (sps->chroma_format_idc == 2) - return RKVDEC_IMG_FMT_422_8BIT; - else - return RKVDEC_IMG_FMT_420_8BIT; - } else if (sps->bit_depth_luma_minus8 == 2) { - if (sps->chroma_format_idc == 2) - return RKVDEC_IMG_FMT_422_10BIT; - else - return RKVDEC_IMG_FMT_420_10BIT; - } - - return RKVDEC_IMG_FMT_ANY; -} - -static int rkvdec_h264_validate_sps(struct rkvdec_ctx *ctx, - const struct v4l2_ctrl_h264_sps *sps) -{ - unsigned int width, height; - - if (sps->chroma_format_idc > 2) - /* Only 4:0:0, 4:2:0 and 4:2:2 are supported */ - return -EINVAL; - if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8) - /* Luma and chroma bit depth mismatch */ - return -EINVAL; - if (sps->bit_depth_luma_minus8 != 0 && sps->bit_depth_luma_minus8 != 2) - /* Only 8-bit and 10-bit is supported */ - return -EINVAL; - - width = (sps->pic_width_in_mbs_minus1 + 1) * 16; - height = (sps->pic_height_in_map_units_minus1 + 1) * 16; - - /* - * When frame_mbs_only_flag is not set, this is field height, - * which is half the final height (see (7-18) in the - * specification) - */ - if (!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY)) - height *= 2; - - if (width > ctx->coded_fmt.fmt.pix_mp.width || - height > ctx->coded_fmt.fmt.pix_mp.height) - return -EINVAL; - - return 0; -} - static int rkvdec_h264_start(struct rkvdec_ctx *ctx) { struct rkvdec_dev *rkvdec = ctx->dev; @@ -689,33 +409,13 @@ static void rkvdec_h264_stop(struct rkvdec_ctx *ctx) kfree(h264_ctx); } -static void rkvdec_h264_run_preamble(struct rkvdec_ctx *ctx, - struct rkvdec_h264_run *run) -{ - struct v4l2_ctrl *ctrl; - - ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, - V4L2_CID_STATELESS_H264_DECODE_PARAMS); - run->decode_params = ctrl ? ctrl->p_cur.p : NULL; - ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, - V4L2_CID_STATELESS_H264_SPS); - run->sps = ctrl ? ctrl->p_cur.p : NULL; - ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, - V4L2_CID_STATELESS_H264_PPS); - run->pps = ctrl ? ctrl->p_cur.p : NULL; - ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, - V4L2_CID_STATELESS_H264_SCALING_MATRIX); - run->scaling_matrix = ctrl ? ctrl->p_cur.p : NULL; - - rkvdec_run_preamble(ctx, &run->base); -} - static int rkvdec_h264_run(struct rkvdec_ctx *ctx) { struct v4l2_h264_reflist_builder reflist_builder; struct rkvdec_dev *rkvdec = ctx->dev; struct rkvdec_h264_ctx *h264_ctx = ctx->priv; struct rkvdec_h264_run run; + struct rkvdec_h264_priv_tbl *tbl = h264_ctx->priv_tbl.cpu; rkvdec_h264_run_preamble(ctx, &run); @@ -726,10 +426,10 @@ static int rkvdec_h264_run(struct rkvdec_ctx *ctx) v4l2_h264_build_b_ref_lists(&reflist_builder, h264_ctx->reflists.b0, h264_ctx->reflists.b1); - assemble_hw_scaling_list(ctx, &run); + assemble_hw_scaling_list(&run, &tbl->scaling_list); assemble_hw_pps(ctx, &run); lookup_ref_buf_idx(ctx, &run); - assemble_hw_rps(ctx, &reflist_builder, &run); + assemble_hw_rps(&reflist_builder, &run, &h264_ctx->reflists, &tbl->rps); config_registers(ctx, &run); rkvdec_run_postamble(ctx, &run.base); From 34e2b14ae90efdc19c4d348710c6b6803a4acfe2 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:24 -0500 Subject: [PATCH 294/307] media: rkvdec: Move hevc functions to common file This is a preparation commit to add support for new variants of the decoder. The functions will later be shared with vdpu381 (rk3588) and vdpu383 (rk3576). Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/Makefile | 1 + .../rockchip/rkvdec/rkvdec-hevc-common.c | 205 +++++++++++++++++ .../rockchip/rkvdec/rkvdec-hevc-common.h | 47 ++++ .../platform/rockchip/rkvdec/rkvdec-hevc.c | 214 +----------------- 4 files changed, 260 insertions(+), 207 deletions(-) create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h diff --git a/drivers/media/platform/rockchip/rkvdec/Makefile b/drivers/media/platform/rockchip/rkvdec/Makefile index d2ba7a7c15e5..1b4bc44be23e 100644 --- a/drivers/media/platform/rockchip/rkvdec/Makefile +++ b/drivers/media/platform/rockchip/rkvdec/Makefile @@ -6,4 +6,5 @@ rockchip-vdec-y += \ rkvdec-h264.o \ rkvdec-h264-common.o \ rkvdec-hevc.o \ + rkvdec-hevc-common.o \ rkvdec-vp9.o diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c new file mode 100644 index 000000000000..f13903ffbda8 --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip video decoder hevc common functions + * + * Copyright (C) 2025 Collabora, Ltd. + * Detlev Casanova + * + * Copyright (C) 2023 Collabora, Ltd. + * Sebastian Fricke + * + * Copyright (C) 2019 Collabora, Ltd. + * Boris Brezillon + * + * Copyright (C) 2016 Rockchip Electronics Co., Ltd. + * Jeffy Chen + */ + +#include +#include + +#include "rkvdec.h" +#include "rkvdec-hevc-common.h" + +/* + * Flip one or more matrices along their main diagonal and flatten them + * before writing it to the memory. + * Convert: + * ABCD AEIM + * EFGH => BFJN => AEIMBFJNCGKODHLP + * IJKL CGKO + * MNOP DHLP + */ +static void transpose_and_flatten_matrices(u8 *output, const u8 *input, + int matrices, int row_length) +{ + int i, j, row, x_offset, matrix_offset, rot_index, y_offset, matrix_size, new_value; + + matrix_size = row_length * row_length; + for (i = 0; i < matrices; i++) { + row = 0; + x_offset = 0; + matrix_offset = i * matrix_size; + for (j = 0; j < matrix_size; j++) { + y_offset = j - (row * row_length); + rot_index = y_offset * row_length + x_offset; + new_value = *(input + i * matrix_size + j); + output[matrix_offset + rot_index] = new_value; + if ((j + 1) % row_length == 0) { + row += 1; + x_offset += 1; + } + } + } +} + +static void assemble_scalingfactor0(u8 *output, const struct v4l2_ctrl_hevc_scaling_matrix *input) +{ + int offset = 0; + + transpose_and_flatten_matrices(output, (const u8 *)input->scaling_list_4x4, 6, 4); + offset = 6 * 16 * sizeof(u8); + transpose_and_flatten_matrices(output + offset, (const u8 *)input->scaling_list_8x8, 6, 8); + offset += 6 * 64 * sizeof(u8); + transpose_and_flatten_matrices(output + offset, + (const u8 *)input->scaling_list_16x16, 6, 8); + offset += 6 * 64 * sizeof(u8); + /* Add a 128 byte padding with 0s between the two 32x32 matrices */ + transpose_and_flatten_matrices(output + offset, + (const u8 *)input->scaling_list_32x32, 1, 8); + offset += 64 * sizeof(u8); + memset(output + offset, 0, 128); + offset += 128 * sizeof(u8); + transpose_and_flatten_matrices(output + offset, + (const u8 *)input->scaling_list_32x32 + (64 * sizeof(u8)), + 1, 8); + offset += 64 * sizeof(u8); + memset(output + offset, 0, 128); +} + +/* + * Required layout: + * A = scaling_list_dc_coef_16x16 + * B = scaling_list_dc_coef_32x32 + * 0 = Padding + * + * A, A, A, A, A, A, B, 0, 0, B, 0, 0 + */ +static void assemble_scalingdc(u8 *output, const struct v4l2_ctrl_hevc_scaling_matrix *input) +{ + u8 list_32x32[6] = {0}; + + memcpy(output, input->scaling_list_dc_coef_16x16, 6 * sizeof(u8)); + list_32x32[0] = input->scaling_list_dc_coef_32x32[0]; + list_32x32[3] = input->scaling_list_dc_coef_32x32[1]; + memcpy(output + 6 * sizeof(u8), list_32x32, 6 * sizeof(u8)); +} + +static void translate_scaling_list(struct scaling_factor *output, + const struct v4l2_ctrl_hevc_scaling_matrix *input) +{ + assemble_scalingfactor0(output->scalingfactor0, input); + memcpy(output->scalingfactor1, (const u8 *)input->scaling_list_4x4, 96); + assemble_scalingdc(output->scalingdc, input); + memset(output->reserved, 0, 4 * sizeof(u8)); +} + +void rkvdec_hevc_assemble_hw_scaling_list(struct rkvdec_hevc_run *run, + struct scaling_factor *scaling_factor, + struct v4l2_ctrl_hevc_scaling_matrix *cache) +{ + const struct v4l2_ctrl_hevc_scaling_matrix *scaling = run->scaling_matrix; + + if (!memcmp(cache, scaling, + sizeof(struct v4l2_ctrl_hevc_scaling_matrix))) + return; + + translate_scaling_list(scaling_factor, scaling); + + memcpy(cache, scaling, + sizeof(struct v4l2_ctrl_hevc_scaling_matrix)); +} + +struct vb2_buffer * +get_ref_buf(struct rkvdec_ctx *ctx, struct rkvdec_hevc_run *run, + unsigned int dpb_idx) +{ + struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; + const struct v4l2_ctrl_hevc_decode_params *decode_params = run->decode_params; + const struct v4l2_hevc_dpb_entry *dpb = decode_params->dpb; + struct vb2_queue *cap_q = &m2m_ctx->cap_q_ctx.q; + struct vb2_buffer *buf = NULL; + + if (dpb_idx < decode_params->num_active_dpb_entries) + buf = vb2_find_buffer(cap_q, dpb[dpb_idx].timestamp); + + /* + * If a DPB entry is unused or invalid, the address of current destination + * buffer is returned. + */ + if (!buf) + return &run->base.bufs.dst->vb2_buf; + + return buf; +} + +#define RKVDEC_HEVC_MAX_DEPTH_IN_BYTES 2 + +int rkvdec_hevc_adjust_fmt(struct rkvdec_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *fmt = &f->fmt.pix_mp; + + fmt->num_planes = 1; + if (!fmt->plane_fmt[0].sizeimage) + fmt->plane_fmt[0].sizeimage = fmt->width * fmt->height * + RKVDEC_HEVC_MAX_DEPTH_IN_BYTES; + return 0; +} + +enum rkvdec_image_fmt rkvdec_hevc_get_image_fmt(struct rkvdec_ctx *ctx, + struct v4l2_ctrl *ctrl) +{ + const struct v4l2_ctrl_hevc_sps *sps = ctrl->p_new.p_hevc_sps; + + if (ctrl->id != V4L2_CID_STATELESS_HEVC_SPS) + return RKVDEC_IMG_FMT_ANY; + + if (sps->bit_depth_luma_minus8 == 0) { + if (sps->chroma_format_idc == 2) + return RKVDEC_IMG_FMT_422_8BIT; + else + return RKVDEC_IMG_FMT_420_8BIT; + } else if (sps->bit_depth_luma_minus8 == 2) { + if (sps->chroma_format_idc == 2) + return RKVDEC_IMG_FMT_422_10BIT; + else + return RKVDEC_IMG_FMT_420_10BIT; + } + + return RKVDEC_IMG_FMT_ANY; +} + +void rkvdec_hevc_run_preamble(struct rkvdec_ctx *ctx, + struct rkvdec_hevc_run *run) +{ + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_DECODE_PARAMS); + run->decode_params = ctrl ? ctrl->p_cur.p : NULL; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_SLICE_PARAMS); + run->slices_params = ctrl ? ctrl->p_cur.p : NULL; + run->num_slices = ctrl ? ctrl->new_elems : 0; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_SPS); + run->sps = ctrl ? ctrl->p_cur.p : NULL; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_PPS); + run->pps = ctrl ? ctrl->p_cur.p : NULL; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_SCALING_MATRIX); + run->scaling_matrix = ctrl ? ctrl->p_cur.p : NULL; + + rkvdec_run_preamble(ctx, &run->base); +} diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h new file mode 100644 index 000000000000..564b2ad72233 --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip video decoder hevc common functions + * + * Copyright (C) 2025 Collabora, Ltd. + * Detlev Casanova + * + * Copyright (C) 2023 Collabora, Ltd. + * Sebastian Fricke + * + * Copyright (C) 2019 Collabora, Ltd. + * Boris Brezillon + * + * Copyright (C) 2016 Rockchip Electronics Co., Ltd. + * Jeffy Chen + */ + +#include + +#include "rkvdec.h" + +struct rkvdec_hevc_run { + struct rkvdec_run base; + const struct v4l2_ctrl_hevc_slice_params *slices_params; + const struct v4l2_ctrl_hevc_decode_params *decode_params; + const struct v4l2_ctrl_hevc_sps *sps; + const struct v4l2_ctrl_hevc_pps *pps; + const struct v4l2_ctrl_hevc_scaling_matrix *scaling_matrix; + int num_slices; +}; + +struct scaling_factor { + u8 scalingfactor0[1248]; + u8 scalingfactor1[96]; /*4X4 TU Rotate, total 16X4*/ + u8 scalingdc[12]; /*N1005 Vienna Meeting*/ + u8 reserved[4]; /*16Bytes align*/ +}; + +void rkvdec_hevc_assemble_hw_scaling_list(struct rkvdec_hevc_run *run, + struct scaling_factor *scaling_factor, + struct v4l2_ctrl_hevc_scaling_matrix *cache); +struct vb2_buffer *get_ref_buf(struct rkvdec_ctx *ctx, + struct rkvdec_hevc_run *run, + unsigned int dpb_idx); +int rkvdec_hevc_adjust_fmt(struct rkvdec_ctx *ctx, struct v4l2_format *f); +enum rkvdec_image_fmt rkvdec_hevc_get_image_fmt(struct rkvdec_ctx *ctx, struct v4l2_ctrl *ctrl); +void rkvdec_hevc_run_preamble(struct rkvdec_ctx *ctx, struct rkvdec_hevc_run *run); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c index f8221c4089c7..a981f910dd68 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c @@ -17,6 +17,7 @@ #include "rkvdec.h" #include "rkvdec-regs.h" #include "rkvdec-cabac.h" +#include "rkvdec-hevc-common.h" /* Size in u8/u32 units. */ #define RKV_SCALING_LIST_SIZE 1360 @@ -111,34 +112,17 @@ struct rkvdec_ps_field { /* Data structure describing auxiliary buffer format. */ struct rkvdec_hevc_priv_tbl { u8 cabac_table[RKV_HEVC_CABAC_TABLE_SIZE]; - u8 scaling_list[RKV_SCALING_LIST_SIZE]; + struct scaling_factor scaling_list; struct rkvdec_sps_pps_packet param_set[RKV_PPS_LEN]; struct rkvdec_rps_packet rps[RKV_RPS_LEN]; }; -struct rkvdec_hevc_run { - struct rkvdec_run base; - const struct v4l2_ctrl_hevc_slice_params *slices_params; - const struct v4l2_ctrl_hevc_decode_params *decode_params; - const struct v4l2_ctrl_hevc_sps *sps; - const struct v4l2_ctrl_hevc_pps *pps; - const struct v4l2_ctrl_hevc_scaling_matrix *scaling_matrix; - int num_slices; -}; - struct rkvdec_hevc_ctx { struct rkvdec_aux_buf priv_tbl; struct v4l2_ctrl_hevc_scaling_matrix scaling_matrix_cache; struct rkvdec_regs regs; }; -struct scaling_factor { - u8 scalingfactor0[1248]; - u8 scalingfactor1[96]; /*4X4 TU Rotate, total 16X4*/ - u8 scalingdc[12]; /*N1005 Vienna Meeting*/ - u8 reserved[4]; /*16Bytes align*/ -}; - static void set_ps_field(u32 *buf, struct rkvdec_ps_field field, u32 value) { u8 bit = field.offset % 32, word = field.offset / 32; @@ -415,131 +399,6 @@ static void assemble_sw_rps(struct rkvdec_ctx *ctx, } } -/* - * Flip one or more matrices along their main diagonal and flatten them - * before writing it to the memory. - * Convert: - * ABCD AEIM - * EFGH => BFJN => AEIMBFJNCGKODHLP - * IJKL CGKO - * MNOP DHLP - */ -static void transpose_and_flatten_matrices(u8 *output, const u8 *input, - int matrices, int row_length) -{ - int i, j, row, x_offset, matrix_offset, rot_index, y_offset, matrix_size, new_value; - - matrix_size = row_length * row_length; - for (i = 0; i < matrices; i++) { - row = 0; - x_offset = 0; - matrix_offset = i * matrix_size; - for (j = 0; j < matrix_size; j++) { - y_offset = j - (row * row_length); - rot_index = y_offset * row_length + x_offset; - new_value = *(input + i * matrix_size + j); - output[matrix_offset + rot_index] = new_value; - if ((j + 1) % row_length == 0) { - row += 1; - x_offset += 1; - } - } - } -} - -static void assemble_scalingfactor0(u8 *output, const struct v4l2_ctrl_hevc_scaling_matrix *input) -{ - int offset = 0; - - transpose_and_flatten_matrices(output, (const u8 *)input->scaling_list_4x4, 6, 4); - offset = 6 * 16 * sizeof(u8); - transpose_and_flatten_matrices(output + offset, (const u8 *)input->scaling_list_8x8, 6, 8); - offset += 6 * 64 * sizeof(u8); - transpose_and_flatten_matrices(output + offset, - (const u8 *)input->scaling_list_16x16, 6, 8); - offset += 6 * 64 * sizeof(u8); - /* Add a 128 byte padding with 0s between the two 32x32 matrices */ - transpose_and_flatten_matrices(output + offset, - (const u8 *)input->scaling_list_32x32, 1, 8); - offset += 64 * sizeof(u8); - memset(output + offset, 0, 128); - offset += 128 * sizeof(u8); - transpose_and_flatten_matrices(output + offset, - (const u8 *)input->scaling_list_32x32 + (64 * sizeof(u8)), - 1, 8); - offset += 64 * sizeof(u8); - memset(output + offset, 0, 128); -} - -/* - * Required layout: - * A = scaling_list_dc_coef_16x16 - * B = scaling_list_dc_coef_32x32 - * 0 = Padding - * - * A, A, A, A, A, A, B, 0, 0, B, 0, 0 - */ -static void assemble_scalingdc(u8 *output, const struct v4l2_ctrl_hevc_scaling_matrix *input) -{ - u8 list_32x32[6] = {0}; - - memcpy(output, input->scaling_list_dc_coef_16x16, 6 * sizeof(u8)); - list_32x32[0] = input->scaling_list_dc_coef_32x32[0]; - list_32x32[3] = input->scaling_list_dc_coef_32x32[1]; - memcpy(output + 6 * sizeof(u8), list_32x32, 6 * sizeof(u8)); -} - -static void translate_scaling_list(struct scaling_factor *output, - const struct v4l2_ctrl_hevc_scaling_matrix *input) -{ - assemble_scalingfactor0(output->scalingfactor0, input); - memcpy(output->scalingfactor1, (const u8 *)input->scaling_list_4x4, 96); - assemble_scalingdc(output->scalingdc, input); - memset(output->reserved, 0, 4 * sizeof(u8)); -} - -static void assemble_hw_scaling_list(struct rkvdec_ctx *ctx, - struct rkvdec_hevc_run *run) -{ - const struct v4l2_ctrl_hevc_scaling_matrix *scaling = run->scaling_matrix; - struct rkvdec_hevc_ctx *hevc_ctx = ctx->priv; - struct rkvdec_hevc_priv_tbl *tbl = hevc_ctx->priv_tbl.cpu; - u8 *dst; - - if (!memcmp((void *)&hevc_ctx->scaling_matrix_cache, scaling, - sizeof(struct v4l2_ctrl_hevc_scaling_matrix))) - return; - - dst = tbl->scaling_list; - translate_scaling_list((struct scaling_factor *)dst, scaling); - - memcpy((void *)&hevc_ctx->scaling_matrix_cache, scaling, - sizeof(struct v4l2_ctrl_hevc_scaling_matrix)); -} - -static struct vb2_buffer * -get_ref_buf(struct rkvdec_ctx *ctx, struct rkvdec_hevc_run *run, - unsigned int dpb_idx) -{ - struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; - const struct v4l2_ctrl_hevc_decode_params *decode_params = run->decode_params; - const struct v4l2_hevc_dpb_entry *dpb = decode_params->dpb; - struct vb2_queue *cap_q = &m2m_ctx->cap_q_ctx.q; - struct vb2_buffer *buf = NULL; - - if (dpb_idx < decode_params->num_active_dpb_entries) - buf = vb2_find_buffer(cap_q, dpb[dpb_idx].timestamp); - - /* - * If a DPB entry is unused or invalid, the address of current destination - * buffer is returned. - */ - if (!buf) - return &run->base.bufs.dst->vb2_buf; - - return buf; -} - static void config_registers(struct rkvdec_ctx *ctx, struct rkvdec_hevc_run *run) { @@ -643,43 +502,6 @@ static void config_registers(struct rkvdec_ctx *ctx, MIN(sizeof(*regs), sizeof(u32) * rkvdec->variant->num_regs)); } -#define RKVDEC_HEVC_MAX_DEPTH_IN_BYTES 2 - -static int rkvdec_hevc_adjust_fmt(struct rkvdec_ctx *ctx, - struct v4l2_format *f) -{ - struct v4l2_pix_format_mplane *fmt = &f->fmt.pix_mp; - - fmt->num_planes = 1; - if (!fmt->plane_fmt[0].sizeimage) - fmt->plane_fmt[0].sizeimage = fmt->width * fmt->height * - RKVDEC_HEVC_MAX_DEPTH_IN_BYTES; - return 0; -} - -static enum rkvdec_image_fmt rkvdec_hevc_get_image_fmt(struct rkvdec_ctx *ctx, - struct v4l2_ctrl *ctrl) -{ - const struct v4l2_ctrl_hevc_sps *sps = ctrl->p_new.p_hevc_sps; - - if (ctrl->id != V4L2_CID_STATELESS_HEVC_SPS) - return RKVDEC_IMG_FMT_ANY; - - if (sps->bit_depth_luma_minus8 == 0) { - if (sps->chroma_format_idc == 2) - return RKVDEC_IMG_FMT_422_8BIT; - else - return RKVDEC_IMG_FMT_420_8BIT; - } else if (sps->bit_depth_luma_minus8 == 2) { - if (sps->chroma_format_idc == 2) - return RKVDEC_IMG_FMT_422_10BIT; - else - return RKVDEC_IMG_FMT_420_10BIT; - } - - return RKVDEC_IMG_FMT_ANY; -} - static int rkvdec_hevc_validate_sps(struct rkvdec_ctx *ctx, const struct v4l2_ctrl_hevc_sps *sps) { @@ -690,7 +512,7 @@ static int rkvdec_hevc_validate_sps(struct rkvdec_ctx *ctx, /* Luma and chroma bit depth mismatch */ return -EINVAL; if (sps->bit_depth_luma_minus8 != 0 && sps->bit_depth_luma_minus8 != 2) - /* Only 8-bit and 10-bit is supported */ + /* Only 8-bit and 10-bit are supported */ return -EINVAL; if (sps->pic_width_in_luma_samples > ctx->coded_fmt.fmt.pix_mp.width || @@ -736,40 +558,18 @@ static void rkvdec_hevc_stop(struct rkvdec_ctx *ctx) kfree(hevc_ctx); } -static void rkvdec_hevc_run_preamble(struct rkvdec_ctx *ctx, - struct rkvdec_hevc_run *run) -{ - struct v4l2_ctrl *ctrl; - - ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, - V4L2_CID_STATELESS_HEVC_DECODE_PARAMS); - run->decode_params = ctrl ? ctrl->p_cur.p : NULL; - ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, - V4L2_CID_STATELESS_HEVC_SLICE_PARAMS); - run->slices_params = ctrl ? ctrl->p_cur.p : NULL; - run->num_slices = ctrl ? ctrl->new_elems : 0; - ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, - V4L2_CID_STATELESS_HEVC_SPS); - run->sps = ctrl ? ctrl->p_cur.p : NULL; - ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, - V4L2_CID_STATELESS_HEVC_PPS); - run->pps = ctrl ? ctrl->p_cur.p : NULL; - ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, - V4L2_CID_STATELESS_HEVC_SCALING_MATRIX); - run->scaling_matrix = ctrl ? ctrl->p_cur.p : NULL; - - rkvdec_run_preamble(ctx, &run->base); -} - static int rkvdec_hevc_run(struct rkvdec_ctx *ctx) { struct rkvdec_dev *rkvdec = ctx->dev; struct rkvdec_hevc_run run; + struct rkvdec_hevc_ctx *hevc_ctx = ctx->priv; + struct rkvdec_hevc_priv_tbl *tbl = hevc_ctx->priv_tbl.cpu; u32 reg; rkvdec_hevc_run_preamble(ctx, &run); - assemble_hw_scaling_list(ctx, &run); + rkvdec_hevc_assemble_hw_scaling_list(&run, &tbl->scaling_list, + &hevc_ctx->scaling_matrix_cache); assemble_hw_pps(ctx, &run); assemble_sw_rps(ctx, &run); config_registers(ctx, &run); From ae2070ca8ab2cc9cad0303fb6f9ed74cde9ec96c Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:25 -0500 Subject: [PATCH 295/307] media: rkvdec: Add variant specific coded formats list Prepare for adding new variants of the decoder and support specific formats and format ops per variant. This removes the need of capability flags for variants, so remove them. Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/rkvdec.c | 68 ++++++++++--------- .../media/platform/rockchip/rkvdec/rkvdec.h | 8 +-- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c index 776149f871b0..92b1c7b62bd2 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c @@ -328,7 +328,6 @@ static const struct rkvdec_coded_fmt_desc rkvdec_coded_fmts[] = { .ops = &rkvdec_hevc_fmt_ops, .num_decoded_fmts = ARRAY_SIZE(rkvdec_hevc_decoded_fmts), .decoded_fmts = rkvdec_hevc_decoded_fmts, - .capability = RKVDEC_CAPABILITY_HEVC, }, { .fourcc = V4L2_PIX_FMT_H264_SLICE, @@ -345,7 +344,6 @@ static const struct rkvdec_coded_fmt_desc rkvdec_coded_fmts[] = { .num_decoded_fmts = ARRAY_SIZE(rkvdec_h264_decoded_fmts), .decoded_fmts = rkvdec_h264_decoded_fmts, .subsystem_flags = VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF, - .capability = RKVDEC_CAPABILITY_H264, }, { .fourcc = V4L2_PIX_FMT_VP9_FRAME, @@ -361,27 +359,38 @@ static const struct rkvdec_coded_fmt_desc rkvdec_coded_fmts[] = { .ops = &rkvdec_vp9_fmt_ops, .num_decoded_fmts = ARRAY_SIZE(rkvdec_vp9_decoded_fmts), .decoded_fmts = rkvdec_vp9_decoded_fmts, - .capability = RKVDEC_CAPABILITY_VP9, } }; -static bool rkvdec_is_capable(struct rkvdec_ctx *ctx, unsigned int capability) -{ - return (ctx->dev->variant->capabilities & capability) == capability; -} +static const struct rkvdec_coded_fmt_desc rk3288_coded_fmts[] = { + { + .fourcc = V4L2_PIX_FMT_HEVC_SLICE, + .frmsize = { + .min_width = 64, + .max_width = 4096, + .step_width = 64, + .min_height = 64, + .max_height = 2304, + .step_height = 16, + }, + .ctrls = &rkvdec_hevc_ctrls, + .ops = &rkvdec_hevc_fmt_ops, + .num_decoded_fmts = ARRAY_SIZE(rkvdec_hevc_decoded_fmts), + .decoded_fmts = rkvdec_hevc_decoded_fmts, + } +}; static const struct rkvdec_coded_fmt_desc * rkvdec_enum_coded_fmt_desc(struct rkvdec_ctx *ctx, int index) { + const struct rkvdec_variant *variant = ctx->dev->variant; int fmt_idx = -1; unsigned int i; - for (i = 0; i < ARRAY_SIZE(rkvdec_coded_fmts); i++) { - if (!rkvdec_is_capable(ctx, rkvdec_coded_fmts[i].capability)) - continue; + for (i = 0; i < variant->num_coded_fmts; i++) { fmt_idx++; if (index == fmt_idx) - return &rkvdec_coded_fmts[i]; + return &variant->coded_fmts[i]; } return NULL; @@ -390,12 +399,12 @@ rkvdec_enum_coded_fmt_desc(struct rkvdec_ctx *ctx, int index) static const struct rkvdec_coded_fmt_desc * rkvdec_find_coded_fmt_desc(struct rkvdec_ctx *ctx, u32 fourcc) { + const struct rkvdec_variant *variant = ctx->dev->variant; unsigned int i; - for (i = 0; i < ARRAY_SIZE(rkvdec_coded_fmts); i++) { - if (rkvdec_is_capable(ctx, rkvdec_coded_fmts[i].capability) && - rkvdec_coded_fmts[i].fourcc == fourcc) - return &rkvdec_coded_fmts[i]; + for (i = 0; i < variant->num_coded_fmts; i++) { + if (variant->coded_fmts[i].fourcc == fourcc) + return &variant->coded_fmts[i]; } return NULL; @@ -1014,21 +1023,19 @@ static int rkvdec_add_ctrls(struct rkvdec_ctx *ctx, static int rkvdec_init_ctrls(struct rkvdec_ctx *ctx) { + const struct rkvdec_variant *variant = ctx->dev->variant; unsigned int i, nctrls = 0; int ret; - for (i = 0; i < ARRAY_SIZE(rkvdec_coded_fmts); i++) - if (rkvdec_is_capable(ctx, rkvdec_coded_fmts[i].capability)) - nctrls += rkvdec_coded_fmts[i].ctrls->num_ctrls; + for (i = 0; i < variant->num_coded_fmts; i++) + nctrls += variant->coded_fmts[i].ctrls->num_ctrls; v4l2_ctrl_handler_init(&ctx->ctrl_hdl, nctrls); - for (i = 0; i < ARRAY_SIZE(rkvdec_coded_fmts); i++) { - if (rkvdec_is_capable(ctx, rkvdec_coded_fmts[i].capability)) { - ret = rkvdec_add_ctrls(ctx, rkvdec_coded_fmts[i].ctrls); - if (ret) - goto err_free_handler; - } + for (i = 0; i < variant->num_coded_fmts; i++) { + ret = rkvdec_add_ctrls(ctx, variant->coded_fmts[i].ctrls); + if (ret) + goto err_free_handler; } ret = v4l2_ctrl_handler_setup(&ctx->ctrl_hdl); @@ -1242,22 +1249,21 @@ static void rkvdec_watchdog_func(struct work_struct *work) static const struct rkvdec_variant rk3288_rkvdec_variant = { .num_regs = 68, - .capabilities = RKVDEC_CAPABILITY_HEVC, + .coded_fmts = rk3288_coded_fmts, + .num_coded_fmts = ARRAY_SIZE(rk3288_coded_fmts), }; static const struct rkvdec_variant rk3328_rkvdec_variant = { .num_regs = 109, - .capabilities = RKVDEC_CAPABILITY_HEVC | - RKVDEC_CAPABILITY_H264 | - RKVDEC_CAPABILITY_VP9, + .coded_fmts = rkvdec_coded_fmts, + .num_coded_fmts = ARRAY_SIZE(rkvdec_coded_fmts), .quirks = RKVDEC_QUIRK_DISABLE_QOS, }; static const struct rkvdec_variant rk3399_rkvdec_variant = { .num_regs = 78, - .capabilities = RKVDEC_CAPABILITY_HEVC | - RKVDEC_CAPABILITY_H264 | - RKVDEC_CAPABILITY_VP9, + .coded_fmts = rkvdec_coded_fmts, + .num_coded_fmts = ARRAY_SIZE(rkvdec_coded_fmts), }; static const struct of_device_id of_rkvdec_match[] = { diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h index f35f6e80ea2e..8c4f96ba5cde 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.h @@ -22,10 +22,6 @@ #include #include -#define RKVDEC_CAPABILITY_HEVC BIT(0) -#define RKVDEC_CAPABILITY_H264 BIT(1) -#define RKVDEC_CAPABILITY_VP9 BIT(2) - #define RKVDEC_QUIRK_DISABLE_QOS BIT(0) struct rkvdec_ctx; @@ -71,7 +67,8 @@ vb2_to_rkvdec_decoded_buf(struct vb2_buffer *buf) struct rkvdec_variant { unsigned int num_regs; - unsigned int capabilities; + const struct rkvdec_coded_fmt_desc *coded_fmts; + size_t num_coded_fmts; unsigned int quirks; }; @@ -110,7 +107,6 @@ struct rkvdec_coded_fmt_desc { unsigned int num_decoded_fmts; const struct rkvdec_decoded_fmt_desc *decoded_fmts; u32 subsystem_flags; - unsigned int capability; }; struct rkvdec_dev { From e5640dbb991c498b0f6fcf2e968fa795a8062eb6 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:26 -0500 Subject: [PATCH 296/307] media: rkvdec: Add RCB and SRAM support The RCB (Rows and Cols Buffers) are a set of buffers used by other variations of the decoder to store temporary data. Those variation come with a dedicated SRAM area used to store those buffers for better performances. The buffer sizes are either the width or height of the frame being decoded multiplied by a documented factor and can be stored either in SRAM or RAM. A fallback to RAM is provided if the SRAM is full (e.g.: multiple streams are being decoded at the same time). To manage the different kind of allocation, an enum is added to the rkvdec_aux_buf struct to specify how the buffer was allocated, and so, how to free it. This commit is in preparation of other variants support. Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/Makefile | 1 + .../platform/rockchip/rkvdec/rkvdec-rcb.c | 179 ++++++++++++++++++ .../platform/rockchip/rkvdec/rkvdec-rcb.h | 29 +++ .../media/platform/rockchip/rkvdec/rkvdec.c | 27 ++- .../media/platform/rockchip/rkvdec/rkvdec.h | 13 ++ 5 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.c create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.h diff --git a/drivers/media/platform/rockchip/rkvdec/Makefile b/drivers/media/platform/rockchip/rkvdec/Makefile index 1b4bc44be23e..3d75103e536d 100644 --- a/drivers/media/platform/rockchip/rkvdec/Makefile +++ b/drivers/media/platform/rockchip/rkvdec/Makefile @@ -7,4 +7,5 @@ rockchip-vdec-y += \ rkvdec-h264-common.o \ rkvdec-hevc.o \ rkvdec-hevc-common.o \ + rkvdec-rcb.o \ rkvdec-vp9.o diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.c new file mode 100644 index 000000000000..fdcf1f177379 --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip video decoder Rows and Cols Buffers manager + * + * Copyright (C) 2025 Collabora, Ltd. + * Detlev Casanova + */ + +#include "rkvdec.h" +#include "rkvdec-rcb.h" + +#include +#include +#include +#include + +struct rkvdec_rcb_config { + struct rkvdec_aux_buf *rcb_bufs; + size_t rcb_count; +}; + +static size_t rkvdec_rcb_size(const struct rcb_size_info *size_info, + unsigned int width, unsigned int height) +{ + return size_info->multiplier * (size_info->axis == PIC_HEIGHT ? height : width); +} + +dma_addr_t rkvdec_rcb_buf_dma_addr(struct rkvdec_ctx *ctx, int id) +{ + return ctx->rcb_config->rcb_bufs[id].dma; +} + +size_t rkvdec_rcb_buf_size(struct rkvdec_ctx *ctx, int id) +{ + return ctx->rcb_config->rcb_bufs[id].size; +} + +int rkvdec_rcb_buf_count(struct rkvdec_ctx *ctx) +{ + return ctx->rcb_config->rcb_count; +} + +void rkvdec_free_rcb(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *dev = ctx->dev; + struct rkvdec_rcb_config *cfg = ctx->rcb_config; + unsigned long virt_addr; + int i; + + if (!cfg) + return; + + for (i = 0; i < cfg->rcb_count; i++) { + size_t rcb_size = cfg->rcb_bufs[i].size; + + if (!cfg->rcb_bufs[i].cpu) + continue; + + switch (cfg->rcb_bufs[i].type) { + case RKVDEC_ALLOC_SRAM: + virt_addr = (unsigned long)cfg->rcb_bufs[i].cpu; + + if (dev->iommu_domain) + iommu_unmap(dev->iommu_domain, virt_addr, rcb_size); + gen_pool_free(dev->sram_pool, virt_addr, rcb_size); + break; + case RKVDEC_ALLOC_DMA: + dma_free_coherent(dev->dev, + rcb_size, + cfg->rcb_bufs[i].cpu, + cfg->rcb_bufs[i].dma); + break; + } + } + + if (cfg->rcb_bufs) + devm_kfree(dev->dev, cfg->rcb_bufs); + + devm_kfree(dev->dev, cfg); +} + +int rkvdec_allocate_rcb(struct rkvdec_ctx *ctx, + const struct rcb_size_info *size_info, + size_t rcb_count) +{ + int ret, i; + u32 width, height; + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_rcb_config *cfg; + + if (!size_info || !rcb_count) { + ctx->rcb_config = NULL; + return 0; + } + + ctx->rcb_config = devm_kzalloc(rkvdec->dev, sizeof(*ctx->rcb_config), GFP_KERNEL); + if (!ctx->rcb_config) + return -ENOMEM; + + cfg = ctx->rcb_config; + + cfg->rcb_bufs = devm_kzalloc(rkvdec->dev, sizeof(*cfg->rcb_bufs) * rcb_count, GFP_KERNEL); + if (!cfg->rcb_bufs) { + ret = -ENOMEM; + goto err_alloc; + } + + width = ctx->decoded_fmt.fmt.pix_mp.width; + height = ctx->decoded_fmt.fmt.pix_mp.height; + + for (i = 0; i < rcb_count; i++) { + void *cpu = NULL; + dma_addr_t dma; + size_t rcb_size = rkvdec_rcb_size(&size_info[i], width, height); + enum rkvdec_alloc_type alloc_type = RKVDEC_ALLOC_SRAM; + + /* Try allocating an SRAM buffer */ + if (ctx->dev->sram_pool) { + if (rkvdec->iommu_domain) + rcb_size = ALIGN(rcb_size, SZ_4K); + + cpu = gen_pool_dma_zalloc_align(ctx->dev->sram_pool, + rcb_size, + &dma, + SZ_4K); + } + + /* If an IOMMU is used, map the SRAM address through it */ + if (cpu && rkvdec->iommu_domain) { + unsigned long virt_addr = (unsigned long)cpu; + phys_addr_t phys_addr = dma; + + ret = iommu_map(rkvdec->iommu_domain, virt_addr, phys_addr, + rcb_size, IOMMU_READ | IOMMU_WRITE, 0); + if (ret) { + gen_pool_free(ctx->dev->sram_pool, + (unsigned long)cpu, + rcb_size); + cpu = NULL; + goto ram_fallback; + } + + /* + * The registers will be configured with the virtual + * address so that it goes through the IOMMU + */ + dma = virt_addr; + } + +ram_fallback: + /* Fallback to RAM */ + if (!cpu) { + cpu = dma_alloc_coherent(ctx->dev->dev, + rcb_size, + &dma, + GFP_KERNEL); + alloc_type = RKVDEC_ALLOC_DMA; + } + + if (!cpu) { + ret = -ENOMEM; + goto err_alloc; + } + + cfg->rcb_bufs[i].cpu = cpu; + cfg->rcb_bufs[i].dma = dma; + cfg->rcb_bufs[i].size = rcb_size; + cfg->rcb_bufs[i].type = alloc_type; + + cfg->rcb_count += 1; + } + + return 0; + +err_alloc: + rkvdec_free_rcb(ctx); + + return ret; +} diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.h new file mode 100644 index 000000000000..30e8002555c8 --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip video decoder Rows and Cols Buffers manager + * + * Copyright (C) 2025 Collabora, Ltd. + * Detlev Casanova + */ + +#include + +struct rkvdec_ctx; + +enum rcb_axis { + PIC_WIDTH = 0, + PIC_HEIGHT = 1 +}; + +struct rcb_size_info { + u8 multiplier; + enum rcb_axis axis; +}; + +int rkvdec_allocate_rcb(struct rkvdec_ctx *ctx, + const struct rcb_size_info *size_info, + size_t rcb_count); +dma_addr_t rkvdec_rcb_buf_dma_addr(struct rkvdec_ctx *ctx, int id); +size_t rkvdec_rcb_buf_size(struct rkvdec_ctx *ctx, int id); +int rkvdec_rcb_buf_count(struct rkvdec_ctx *ctx); +void rkvdec_free_rcb(struct rkvdec_ctx *ctx); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c index 92b1c7b62bd2..ff6a09e45462 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c @@ -10,6 +10,7 @@ */ #include +#include #include #include #include @@ -28,6 +29,7 @@ #include "rkvdec.h" #include "rkvdec-regs.h" +#include "rkvdec-rcb.h" static bool rkvdec_image_fmt_match(enum rkvdec_image_fmt fmt1, enum rkvdec_image_fmt fmt2) @@ -778,6 +780,7 @@ static int rkvdec_start_streaming(struct vb2_queue *q, unsigned int count) { struct rkvdec_ctx *ctx = vb2_get_drv_priv(q); const struct rkvdec_coded_fmt_desc *desc; + const struct rkvdec_variant *variant = ctx->dev->variant; int ret; if (V4L2_TYPE_IS_CAPTURE(q->type)) @@ -787,13 +790,22 @@ static int rkvdec_start_streaming(struct vb2_queue *q, unsigned int count) if (WARN_ON(!desc)) return -EINVAL; + ret = rkvdec_allocate_rcb(ctx, variant->rcb_sizes, variant->num_rcb_sizes); + if (ret) + return ret; + if (desc->ops->start) { ret = desc->ops->start(ctx); if (ret) - return ret; + goto err_ops_start; } return 0; + +err_ops_start: + rkvdec_free_rcb(ctx); + + return ret; } static void rkvdec_queue_cleanup(struct vb2_queue *vq, u32 state) @@ -829,6 +841,8 @@ static void rkvdec_stop_streaming(struct vb2_queue *q) if (desc->ops->stop) desc->ops->stop(ctx); + + rkvdec_free_rcb(ctx); } rkvdec_queue_cleanup(q, VB2_BUF_STATE_ERROR); @@ -1345,6 +1359,10 @@ static int rkvdec_probe(struct platform_device *pdev) return ret; } + rkvdec->sram_pool = of_gen_pool_get(pdev->dev.of_node, "sram", 0); + if (!rkvdec->sram_pool && rkvdec->variant->num_rcb_sizes > 0) + dev_info(&pdev->dev, "No sram node, RCB will be stored in RAM\n"); + pm_runtime_set_autosuspend_delay(&pdev->dev, 100); pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_enable(&pdev->dev); @@ -1353,7 +1371,8 @@ static int rkvdec_probe(struct platform_device *pdev) if (ret) goto err_disable_runtime_pm; - if (iommu_get_domain_for_dev(&pdev->dev)) { + rkvdec->iommu_domain = iommu_get_domain_for_dev(&pdev->dev); + if (rkvdec->iommu_domain) { rkvdec->empty_domain = iommu_paging_domain_alloc(rkvdec->dev); if (IS_ERR(rkvdec->empty_domain)) { @@ -1367,6 +1386,10 @@ static int rkvdec_probe(struct platform_device *pdev) err_disable_runtime_pm: pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_disable(&pdev->dev); + + if (rkvdec->sram_pool) + gen_pool_destroy(rkvdec->sram_pool); + return ret; } diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h index 8c4f96ba5cde..751f39afe7e2 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.h @@ -19,12 +19,14 @@ #include #include #include +#include #include #include #define RKVDEC_QUIRK_DISABLE_QOS BIT(0) struct rkvdec_ctx; +struct rkvdec_rcb_config; struct rkvdec_ctrl_desc { struct v4l2_ctrl_config cfg; @@ -69,6 +71,8 @@ struct rkvdec_variant { unsigned int num_regs; const struct rkvdec_coded_fmt_desc *coded_fmts; size_t num_coded_fmts; + const struct rcb_size_info *rcb_sizes; + size_t num_rcb_sizes; unsigned int quirks; }; @@ -119,6 +123,8 @@ struct rkvdec_dev { void __iomem *regs; struct mutex vdev_lock; /* serializes ioctls */ struct delayed_work watchdog_work; + struct gen_pool *sram_pool; + struct iommu_domain *iommu_domain; struct iommu_domain *empty_domain; const struct rkvdec_variant *variant; }; @@ -131,6 +137,7 @@ struct rkvdec_ctx { struct v4l2_ctrl_handler ctrl_hdl; struct rkvdec_dev *dev; enum rkvdec_image_fmt image_fmt; + struct rkvdec_rcb_config *rcb_config; void *priv; }; @@ -139,10 +146,16 @@ static inline struct rkvdec_ctx *file_to_rkvdec_ctx(struct file *filp) return container_of(file_to_v4l2_fh(filp), struct rkvdec_ctx, fh); } +enum rkvdec_alloc_type { + RKVDEC_ALLOC_DMA = 0, + RKVDEC_ALLOC_SRAM = 1, +}; + struct rkvdec_aux_buf { void *cpu; dma_addr_t dma; size_t size; + enum rkvdec_alloc_type type; }; void rkvdec_run_preamble(struct rkvdec_ctx *ctx, struct rkvdec_run *run); From f9c7b7deeffd9e1fa2d9ed0c7a84c21226b24b0a Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:27 -0500 Subject: [PATCH 297/307] media: rkvdec: Support per-variant interrupt handler Prepare for supporting different variants with different interrupt managers. To support other variants specific function type later, introduce the rkvdec_variant_ops struct. Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/rkvdec.c | 21 ++++++++++++++++--- .../media/platform/rockchip/rkvdec/rkvdec.h | 5 +++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c index ff6a09e45462..174536ebdcc7 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c @@ -1222,10 +1222,9 @@ static void rkvdec_iommu_restore(struct rkvdec_dev *rkvdec) } } -static irqreturn_t rkvdec_irq_handler(int irq, void *priv) +static irqreturn_t rk3399_irq_handler(struct rkvdec_ctx *ctx) { - struct rkvdec_dev *rkvdec = priv; - struct rkvdec_ctx *ctx = v4l2_m2m_get_curr_priv(rkvdec->m2m_dev); + struct rkvdec_dev *rkvdec = ctx->dev; enum vb2_buffer_state state; u32 status; @@ -1246,6 +1245,15 @@ static irqreturn_t rkvdec_irq_handler(int irq, void *priv) return IRQ_HANDLED; } +static irqreturn_t rkvdec_irq_handler(int irq, void *priv) +{ + struct rkvdec_dev *rkvdec = priv; + struct rkvdec_ctx *ctx = v4l2_m2m_get_curr_priv(rkvdec->m2m_dev); + const struct rkvdec_variant *variant = rkvdec->variant; + + return variant->ops->irq_handler(ctx); +} + static void rkvdec_watchdog_func(struct work_struct *work) { struct rkvdec_dev *rkvdec; @@ -1261,16 +1269,22 @@ static void rkvdec_watchdog_func(struct work_struct *work) } } +static const struct rkvdec_variant_ops rk3399_variant_ops = { + .irq_handler = rk3399_irq_handler, +}; + static const struct rkvdec_variant rk3288_rkvdec_variant = { .num_regs = 68, .coded_fmts = rk3288_coded_fmts, .num_coded_fmts = ARRAY_SIZE(rk3288_coded_fmts), + .ops = &rk3399_variant_ops, }; static const struct rkvdec_variant rk3328_rkvdec_variant = { .num_regs = 109, .coded_fmts = rkvdec_coded_fmts, .num_coded_fmts = ARRAY_SIZE(rkvdec_coded_fmts), + .ops = &rk3399_variant_ops, .quirks = RKVDEC_QUIRK_DISABLE_QOS, }; @@ -1278,6 +1292,7 @@ static const struct rkvdec_variant rk3399_rkvdec_variant = { .num_regs = 78, .coded_fmts = rkvdec_coded_fmts, .num_coded_fmts = ARRAY_SIZE(rkvdec_coded_fmts), + .ops = &rk3399_variant_ops, }; static const struct of_device_id of_rkvdec_match[] = { diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h index 751f39afe7e2..faabedd2b9d8 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.h @@ -67,12 +67,17 @@ vb2_to_rkvdec_decoded_buf(struct vb2_buffer *buf) base.vb.vb2_buf); } +struct rkvdec_variant_ops { + irqreturn_t (*irq_handler)(struct rkvdec_ctx *ctx); +}; + struct rkvdec_variant { unsigned int num_regs; const struct rkvdec_coded_fmt_desc *coded_fmts; size_t num_coded_fmts; const struct rcb_size_info *rcb_sizes; size_t num_rcb_sizes; + const struct rkvdec_variant_ops *ops; unsigned int quirks; }; From 6a846f7d72c7b64627398c8fe82de74da97a7b2a Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:28 -0500 Subject: [PATCH 298/307] media: rkvdec: Enable all clocks without naming them For other variants, the clock names and number will differ. There is no need to keep track of the clock names in the driver so drop them to avoid having a list for each variant. Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/rkvdec.c | 26 +++++-------------- .../media/platform/rockchip/rkvdec/rkvdec.h | 1 + 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c index 174536ebdcc7..ce96a0470d4d 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c @@ -1312,15 +1312,10 @@ static const struct of_device_id of_rkvdec_match[] = { }; MODULE_DEVICE_TABLE(of, of_rkvdec_match); -static const char * const rkvdec_clk_names[] = { - "axi", "ahb", "cabac", "core" -}; - static int rkvdec_probe(struct platform_device *pdev) { const struct rkvdec_variant *variant; struct rkvdec_dev *rkvdec; - unsigned int i; int ret, irq; variant = of_device_get_match_data(&pdev->dev); @@ -1337,19 +1332,12 @@ static int rkvdec_probe(struct platform_device *pdev) mutex_init(&rkvdec->vdev_lock); INIT_DELAYED_WORK(&rkvdec->watchdog_work, rkvdec_watchdog_func); - rkvdec->clocks = devm_kcalloc(&pdev->dev, ARRAY_SIZE(rkvdec_clk_names), - sizeof(*rkvdec->clocks), GFP_KERNEL); - if (!rkvdec->clocks) - return -ENOMEM; - - for (i = 0; i < ARRAY_SIZE(rkvdec_clk_names); i++) - rkvdec->clocks[i].id = rkvdec_clk_names[i]; - - ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(rkvdec_clk_names), - rkvdec->clocks); - if (ret) + ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &rkvdec->clocks); + if (ret < 0) return ret; + rkvdec->num_clocks = ret; + rkvdec->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(rkvdec->regs)) return PTR_ERR(rkvdec->regs); @@ -1427,16 +1415,14 @@ static int rkvdec_runtime_resume(struct device *dev) { struct rkvdec_dev *rkvdec = dev_get_drvdata(dev); - return clk_bulk_prepare_enable(ARRAY_SIZE(rkvdec_clk_names), - rkvdec->clocks); + return clk_bulk_prepare_enable(rkvdec->num_clocks, rkvdec->clocks); } static int rkvdec_runtime_suspend(struct device *dev) { struct rkvdec_dev *rkvdec = dev_get_drvdata(dev); - clk_bulk_disable_unprepare(ARRAY_SIZE(rkvdec_clk_names), - rkvdec->clocks); + clk_bulk_disable_unprepare(rkvdec->num_clocks, rkvdec->clocks); return 0; } #endif diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h index faabedd2b9d8..7766a79caf68 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.h @@ -125,6 +125,7 @@ struct rkvdec_dev { struct v4l2_m2m_dev *m2m_dev; struct device *dev; struct clk_bulk_data *clocks; + unsigned int num_clocks; void __iomem *regs; struct mutex vdev_lock; /* serializes ioctls */ struct delayed_work watchdog_work; From e570307ac987482dfdd8950e7cce96048b047b10 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:29 -0500 Subject: [PATCH 299/307] media: rkvdec: Disable multicore support Similarly to what is done in Hantro, avoid exposing equal video codecs to userspace. Equal video codecs allow scheduling work between the cores. For that kernel support is required, which does not yet exist. Until that is implemented, avoid exposing each core separately to userspace so that multicore can be added in the future without breaking userspace ABI. This currently applies only to RK3588 which has 2 equal VDPU381 decoders, but will be applied for all SoC supported by rkvdec that has multiple DTS nodes with the same compatible. Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/rkvdec.c | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c index ce96a0470d4d..af2eced90026 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c @@ -1269,6 +1269,49 @@ static void rkvdec_watchdog_func(struct work_struct *work) } } +/* + * Some SoCs, like RK3588 have multiple identical VDPU cores, but the + * kernel is currently missing support for multi-core handling. Exposing + * separate devices for each core to userspace is bad, since that does + * not allow scheduling tasks properly (and creates ABI). With this workaround + * the driver will only probe for the first core and early exit for the other + * cores. Once the driver gains multi-core support, the same technique + * for detecting the first core can be used to cluster all cores together. + */ +static int rkvdec_disable_multicore(struct rkvdec_dev *rkvdec) +{ + struct device_node *node = NULL; + const char *compatible; + bool is_first_core; + int ret; + + /* Intentionally ignores the fallback strings */ + ret = of_property_read_string(rkvdec->dev->of_node, "compatible", &compatible); + if (ret) + return ret; + + /* The first compatible and available node found is considered the main core */ + do { + node = of_find_compatible_node(node, NULL, compatible); + if (of_device_is_available(node)) + break; + } while (node); + + if (!node) + return -EINVAL; + + is_first_core = (rkvdec->dev->of_node == node); + + of_node_put(node); + + if (!is_first_core) { + dev_info(rkvdec->dev, "missing multi-core support, ignoring this instance\n"); + return -ENODEV; + } + + return 0; +} + static const struct rkvdec_variant_ops rk3399_variant_ops = { .irq_handler = rk3399_irq_handler, }; @@ -1332,6 +1375,10 @@ static int rkvdec_probe(struct platform_device *pdev) mutex_init(&rkvdec->vdev_lock); INIT_DELAYED_WORK(&rkvdec->watchdog_work, rkvdec_watchdog_func); + ret = rkvdec_disable_multicore(rkvdec); + if (ret) + return ret; + ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &rkvdec->clocks); if (ret < 0) return ret; From e5aa698ea6591482286e99f4f6f2894cfeed3c22 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:30 -0500 Subject: [PATCH 300/307] media: rkvdec: Add H264 support for the VDPU381 variant This decoder variant is found in Rockchip RK3588 SoC family. Like for rkvdec on rk3399, it supports the NV12, NV15, NV16 and NV20 output formats and level up to 6.0. The maximum width and height have been significantly increased supporting up to 65520 pixels for both. Support for named register sections is added for this variant and future ones. Fluster score for JVT-AVC_V1 is 129/135. Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/Makefile | 1 + .../rockchip/rkvdec/rkvdec-vdpu381-h264.c | 469 ++++++++++++++++++ .../rockchip/rkvdec/rkvdec-vdpu381-regs.h | 430 ++++++++++++++++ .../media/platform/rockchip/rkvdec/rkvdec.c | 158 +++++- .../media/platform/rockchip/rkvdec/rkvdec.h | 12 + 5 files changed, 1067 insertions(+), 3 deletions(-) create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-h264.c create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-regs.h diff --git a/drivers/media/platform/rockchip/rkvdec/Makefile b/drivers/media/platform/rockchip/rkvdec/Makefile index 3d75103e536d..7bfd95151e40 100644 --- a/drivers/media/platform/rockchip/rkvdec/Makefile +++ b/drivers/media/platform/rockchip/rkvdec/Makefile @@ -8,4 +8,5 @@ rockchip-vdec-y += \ rkvdec-hevc.o \ rkvdec-hevc-common.o \ rkvdec-rcb.o \ + rkvdec-vdpu381-h264.o \ rkvdec-vp9.o diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-h264.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-h264.c new file mode 100644 index 000000000000..cd0aa3f3a13d --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-h264.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip VDPU381 Video Decoder H264 backend + * + * Copyright (C) 2024 Collabora, Ltd. + * Detlev Casanova + */ + +#include +#include + +#include "rkvdec.h" +#include "rkvdec-cabac.h" +#include "rkvdec-rcb.h" +#include "rkvdec-h264-common.h" +#include "rkvdec-vdpu381-regs.h" + +struct rkvdec_sps { + u16 seq_parameter_set_id: 4; + u16 profile_idc: 8; + u16 constraint_set3_flag: 1; + u16 chroma_format_idc: 2; + u16 bit_depth_luma: 3; + u16 bit_depth_chroma: 3; + u16 qpprime_y_zero_transform_bypass_flag: 1; + u16 log2_max_frame_num_minus4: 4; + u16 max_num_ref_frames: 5; + u16 pic_order_cnt_type: 2; + u16 log2_max_pic_order_cnt_lsb_minus4: 4; + u16 delta_pic_order_always_zero_flag: 1; + u16 pic_width_in_mbs: 12; + u16 pic_height_in_mbs: 12; + u16 frame_mbs_only_flag: 1; + u16 mb_adaptive_frame_field_flag: 1; + u16 direct_8x8_inference_flag: 1; + u16 mvc_extension_enable: 1; + u16 num_views: 2; + + u16 reserved_bits: 12; + u16 reserved[11]; +} __packed; + +struct rkvdec_pps { + u16 pic_parameter_set_id: 8; + u16 pps_seq_parameter_set_id: 5; + u16 entropy_coding_mode_flag: 1; + u16 bottom_field_pic_order_in_frame_present_flag: 1; + u16 num_ref_idx_l0_default_active_minus1: 5; + u16 num_ref_idx_l1_default_active_minus1: 5; + u16 weighted_pred_flag: 1; + u16 weighted_bipred_idc: 2; + u16 pic_init_qp_minus26: 7; + u16 pic_init_qs_minus26: 6; + u16 chroma_qp_index_offset: 5; + u16 deblocking_filter_control_present_flag: 1; + u16 constrained_intra_pred_flag: 1; + u16 redundant_pic_cnt_present: 1; + u16 transform_8x8_mode_flag: 1; + u16 second_chroma_qp_index_offset: 5; + u16 scaling_list_enable_flag: 1; + u32 scaling_list_address; + u16 is_longterm; + + u8 reserved[3]; +} __packed; + +struct rkvdec_sps_pps { + struct rkvdec_sps sps; + struct rkvdec_pps pps; +} __packed; + +/* Data structure describing auxiliary buffer format. */ +struct rkvdec_h264_priv_tbl { + s8 cabac_table[4][464][2]; + struct rkvdec_h264_scaling_list scaling_list; + struct rkvdec_sps_pps param_set[256]; + struct rkvdec_rps rps; +}; + +struct rkvdec_h264_ctx { + struct rkvdec_aux_buf priv_tbl; + struct rkvdec_h264_reflists reflists; + struct rkvdec_vdpu381_regs_h264 regs; +}; + +static void assemble_hw_pps(struct rkvdec_ctx *ctx, + struct rkvdec_h264_run *run) +{ + struct rkvdec_h264_ctx *h264_ctx = ctx->priv; + const struct v4l2_ctrl_h264_sps *sps = run->sps; + const struct v4l2_ctrl_h264_pps *pps = run->pps; + const struct v4l2_ctrl_h264_decode_params *dec_params = run->decode_params; + const struct v4l2_h264_dpb_entry *dpb = dec_params->dpb; + struct rkvdec_h264_priv_tbl *priv_tbl = h264_ctx->priv_tbl.cpu; + struct rkvdec_sps_pps *hw_ps; + dma_addr_t scaling_list_address; + u32 scaling_distance; + u32 i; + + /* + * HW read the SPS/PPS information from PPS packet index by PPS id. + * offset from the base can be calculated by PPS_id * 32 (size per PPS + * packet unit). so the driver copy SPS/PPS information to the exact PPS + * packet unit for HW accessing. + */ + hw_ps = &priv_tbl->param_set[pps->pic_parameter_set_id]; + memset(hw_ps, 0, sizeof(*hw_ps)); + + /* write sps */ + hw_ps->sps.seq_parameter_set_id = sps->seq_parameter_set_id; + hw_ps->sps.profile_idc = sps->profile_idc; + hw_ps->sps.constraint_set3_flag = !!(sps->constraint_set_flags & (1 << 3)); + hw_ps->sps.chroma_format_idc = sps->chroma_format_idc; + hw_ps->sps.bit_depth_luma = sps->bit_depth_luma_minus8; + hw_ps->sps.bit_depth_chroma = sps->bit_depth_chroma_minus8; + hw_ps->sps.qpprime_y_zero_transform_bypass_flag = + !!(sps->flags & V4L2_H264_SPS_FLAG_QPPRIME_Y_ZERO_TRANSFORM_BYPASS); + hw_ps->sps.log2_max_frame_num_minus4 = sps->log2_max_frame_num_minus4; + hw_ps->sps.max_num_ref_frames = sps->max_num_ref_frames; + hw_ps->sps.pic_order_cnt_type = sps->pic_order_cnt_type; + hw_ps->sps.log2_max_pic_order_cnt_lsb_minus4 = + sps->log2_max_pic_order_cnt_lsb_minus4; + hw_ps->sps.delta_pic_order_always_zero_flag = + !!(sps->flags & V4L2_H264_SPS_FLAG_DELTA_PIC_ORDER_ALWAYS_ZERO); + hw_ps->sps.mvc_extension_enable = 1; + hw_ps->sps.num_views = 1; + + /* + * Use the SPS values since they are already in macroblocks + * dimensions, height can be field height (halved) if + * V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY is not set and also it allows + * decoding smaller images into larger allocation which can be used + * to implementing SVC spatial layer support. + */ + hw_ps->sps.pic_width_in_mbs = sps->pic_width_in_mbs_minus1 + 1; + hw_ps->sps.pic_height_in_mbs = sps->pic_height_in_map_units_minus1 + 1; + hw_ps->sps.frame_mbs_only_flag = + !!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY); + hw_ps->sps.mb_adaptive_frame_field_flag = + !!(sps->flags & V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD); + hw_ps->sps.direct_8x8_inference_flag = + !!(sps->flags & V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE); + + /* write pps */ + hw_ps->pps.pic_parameter_set_id = pps->pic_parameter_set_id; + hw_ps->pps.pps_seq_parameter_set_id = pps->seq_parameter_set_id; + hw_ps->pps.entropy_coding_mode_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE); + hw_ps->pps.bottom_field_pic_order_in_frame_present_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT); + hw_ps->pps.num_ref_idx_l0_default_active_minus1 = + pps->num_ref_idx_l0_default_active_minus1; + hw_ps->pps.num_ref_idx_l1_default_active_minus1 = + pps->num_ref_idx_l1_default_active_minus1; + hw_ps->pps.weighted_pred_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_WEIGHTED_PRED); + hw_ps->pps.weighted_bipred_idc = pps->weighted_bipred_idc; + hw_ps->pps.pic_init_qp_minus26 = pps->pic_init_qp_minus26; + hw_ps->pps.pic_init_qs_minus26 = pps->pic_init_qs_minus26; + hw_ps->pps.chroma_qp_index_offset = pps->chroma_qp_index_offset; + hw_ps->pps.deblocking_filter_control_present_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT); + hw_ps->pps.constrained_intra_pred_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED); + hw_ps->pps.redundant_pic_cnt_present = + !!(pps->flags & V4L2_H264_PPS_FLAG_REDUNDANT_PIC_CNT_PRESENT); + hw_ps->pps.transform_8x8_mode_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE); + hw_ps->pps.second_chroma_qp_index_offset = pps->second_chroma_qp_index_offset; + hw_ps->pps.scaling_list_enable_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT); + + /* + * To be on the safe side, program the scaling matrix address + */ + scaling_distance = offsetof(struct rkvdec_h264_priv_tbl, scaling_list); + scaling_list_address = h264_ctx->priv_tbl.dma + scaling_distance; + hw_ps->pps.scaling_list_address = scaling_list_address; + + for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { + if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM) + hw_ps->pps.is_longterm |= (1 << i); + } +} + +static void rkvdec_write_regs(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_h264_ctx *h264_ctx = ctx->priv; + + rkvdec_memcpy_toio(rkvdec->regs + OFFSET_COMMON_REGS, + &h264_ctx->regs.common, + sizeof(h264_ctx->regs.common)); + rkvdec_memcpy_toio(rkvdec->regs + OFFSET_CODEC_PARAMS_REGS, + &h264_ctx->regs.h264_param, + sizeof(h264_ctx->regs.h264_param)); + rkvdec_memcpy_toio(rkvdec->regs + OFFSET_COMMON_ADDR_REGS, + &h264_ctx->regs.common_addr, + sizeof(h264_ctx->regs.common_addr)); + rkvdec_memcpy_toio(rkvdec->regs + OFFSET_CODEC_ADDR_REGS, + &h264_ctx->regs.h264_addr, + sizeof(h264_ctx->regs.h264_addr)); + rkvdec_memcpy_toio(rkvdec->regs + OFFSET_POC_HIGHBIT_REGS, + &h264_ctx->regs.h264_highpoc, + sizeof(h264_ctx->regs.h264_highpoc)); +} + +static void config_registers(struct rkvdec_ctx *ctx, + struct rkvdec_h264_run *run) +{ + const struct v4l2_ctrl_h264_decode_params *dec_params = run->decode_params; + const struct v4l2_h264_dpb_entry *dpb = dec_params->dpb; + struct rkvdec_h264_ctx *h264_ctx = ctx->priv; + dma_addr_t priv_start_addr = h264_ctx->priv_tbl.dma; + const struct v4l2_pix_format_mplane *dst_fmt; + struct vb2_v4l2_buffer *src_buf = run->base.bufs.src; + struct vb2_v4l2_buffer *dst_buf = run->base.bufs.dst; + struct rkvdec_vdpu381_regs_h264 *regs = &h264_ctx->regs; + const struct v4l2_format *f; + dma_addr_t rlc_addr; + dma_addr_t dst_addr; + u32 hor_virstride; + u32 ver_virstride; + u32 y_virstride; + u32 offset; + u32 pixels; + u32 i; + + memset(regs, 0, sizeof(*regs)); + + /* Set H264 mode */ + regs->common.reg009_dec_mode.dec_mode = VDPU381_MODE_H264; + + /* Set config */ + regs->common.reg011_important_en.buf_empty_en = 1; + regs->common.reg011_important_en.dec_clkgate_e = 1; + regs->common.reg011_important_en.dec_timeout_e = 1; + regs->common.reg011_important_en.pix_range_det_e = 1; + + /* + * Even though the scan list address can be set in RPS, + * with some frames, it will try to use the address set in the register. + */ + regs->common.reg012_secondary_en.scanlist_addr_valid_en = 1; + + /* Set IDR flag */ + regs->common.reg013_en_mode_set.cur_pic_is_idr = + !!(dec_params->flags & V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC); + + /* Set input stream length */ + regs->common.reg016_stream_len = vb2_get_plane_payload(&src_buf->vb2_buf, 0); + + /* Set max slice number */ + regs->common.reg017_slice_number.slice_num = MAX_SLICE_NUMBER; + + /* Set strides */ + f = &ctx->decoded_fmt; + dst_fmt = &f->fmt.pix_mp; + hor_virstride = dst_fmt->plane_fmt[0].bytesperline; + ver_virstride = dst_fmt->height; + y_virstride = hor_virstride * ver_virstride; + + regs->common.reg018_y_hor_stride.y_hor_virstride = hor_virstride / 16; + regs->common.reg019_uv_hor_stride.uv_hor_virstride = hor_virstride / 16; + regs->common.reg020_y_stride.y_virstride = y_virstride / 16; + + /* Activate block gating */ + regs->common.reg026_block_gating_en.inter_auto_gating_e = 1; + regs->common.reg026_block_gating_en.filterd_auto_gating_e = 1; + regs->common.reg026_block_gating_en.strmd_auto_gating_e = 1; + regs->common.reg026_block_gating_en.mcp_auto_gating_e = 1; + regs->common.reg026_block_gating_en.busifd_auto_gating_e = 0; + regs->common.reg026_block_gating_en.dec_ctrl_auto_gating_e = 1; + regs->common.reg026_block_gating_en.intra_auto_gating_e = 1; + regs->common.reg026_block_gating_en.mc_auto_gating_e = 1; + regs->common.reg026_block_gating_en.transd_auto_gating_e = 1; + regs->common.reg026_block_gating_en.sram_auto_gating_e = 1; + regs->common.reg026_block_gating_en.cru_auto_gating_e = 1; + regs->common.reg026_block_gating_en.reg_cfg_gating_en = 1; + + /* Set timeout threshold */ + pixels = dst_fmt->height * dst_fmt->width; + if (pixels < RKVDEC_1080P_PIXELS) + regs->common.reg032_timeout_threshold = RKVDEC_TIMEOUT_1080p; + else if (pixels < RKVDEC_4K_PIXELS) + regs->common.reg032_timeout_threshold = RKVDEC_TIMEOUT_4K; + else if (pixels < RKVDEC_8K_PIXELS) + regs->common.reg032_timeout_threshold = RKVDEC_TIMEOUT_8K; + else + regs->common.reg032_timeout_threshold = RKVDEC_TIMEOUT_MAX; + + /* Set TOP and BOTTOM POCs */ + regs->h264_param.reg065_cur_top_poc = dec_params->top_field_order_cnt; + regs->h264_param.reg066_cur_bot_poc = dec_params->bottom_field_order_cnt; + + /* Set ref pic address & poc */ + for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { + struct vb2_buffer *vb_buf = run->ref_buf[i]; + dma_addr_t buf_dma; + + /* + * If a DPB entry is unused or invalid, address of current destination + * buffer is returned. + */ + if (!vb_buf) + vb_buf = &dst_buf->vb2_buf; + + buf_dma = vb2_dma_contig_plane_dma_addr(vb_buf, 0); + + /* Set reference addresses */ + regs->h264_addr.reg164_180_ref_base[i] = buf_dma; + + /* Set COLMV addresses */ + regs->h264_addr.reg182_198_colmv_base[i] = buf_dma + ctx->colmv_offset; + + struct rkvdec_vdpu381_h264_ref_info *ref_info = + ®s->h264_param.reg099_102_ref_info_regs[i / 4].ref_info[i % 4]; + + ref_info->ref_field = + !!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_FIELD); + ref_info->ref_colmv_use_flag = + !!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE); + ref_info->ref_topfield_used = + !!(dpb[i].fields & V4L2_H264_TOP_FIELD_REF); + ref_info->ref_botfield_used = + !!(dpb[i].fields & V4L2_H264_BOTTOM_FIELD_REF); + + regs->h264_param.reg067_098_ref_poc[i * 2] = + dpb[i].top_field_order_cnt; + regs->h264_param.reg067_098_ref_poc[i * 2 + 1] = + dpb[i].bottom_field_order_cnt; + } + + /* Set rlc base address (input stream) */ + rlc_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + regs->common_addr.rlc_base = rlc_addr; + regs->common_addr.rlcwrite_base = rlc_addr; + + /* Set output base address */ + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + regs->common_addr.decout_base = dst_addr; + regs->common_addr.error_ref_base = dst_addr; + + /* Set colmv address */ + regs->common_addr.colmv_cur_base = dst_addr + ctx->colmv_offset; + + /* Set RCB addresses */ + for (i = 0; i < rkvdec_rcb_buf_count(ctx); i++) + regs->common_addr.rcb_base[i] = rkvdec_rcb_buf_dma_addr(ctx, i); + + /* Set hw pps address */ + offset = offsetof(struct rkvdec_h264_priv_tbl, param_set); + regs->h264_addr.reg161_pps_base = priv_start_addr + offset; + + /* Set hw rps address */ + offset = offsetof(struct rkvdec_h264_priv_tbl, rps); + regs->h264_addr.reg163_rps_base = priv_start_addr + offset; + + /* Set cabac table */ + offset = offsetof(struct rkvdec_h264_priv_tbl, cabac_table); + regs->h264_addr.reg199_cabactbl_base = priv_start_addr + offset; + + offset = offsetof(struct rkvdec_h264_priv_tbl, scaling_list); + regs->h264_addr.reg181_scanlist_addr = priv_start_addr + offset; + + rkvdec_write_regs(ctx); +} + +static int rkvdec_h264_start(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_h264_priv_tbl *priv_tbl; + struct rkvdec_h264_ctx *h264_ctx; + struct v4l2_ctrl *ctrl; + int ret; + + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_H264_SPS); + if (!ctrl) + return -EINVAL; + + ret = rkvdec_h264_validate_sps(ctx, ctrl->p_new.p_h264_sps); + if (ret) + return ret; + + h264_ctx = kzalloc(sizeof(*h264_ctx), GFP_KERNEL); + if (!h264_ctx) + return -ENOMEM; + + priv_tbl = dma_alloc_coherent(rkvdec->dev, sizeof(*priv_tbl), + &h264_ctx->priv_tbl.dma, GFP_KERNEL); + if (!priv_tbl) { + ret = -ENOMEM; + goto err_free_ctx; + } + + h264_ctx->priv_tbl.size = sizeof(*priv_tbl); + h264_ctx->priv_tbl.cpu = priv_tbl; + memcpy(priv_tbl->cabac_table, rkvdec_h264_cabac_table, + sizeof(rkvdec_h264_cabac_table)); + + ctx->priv = h264_ctx; + return 0; + +err_free_ctx: + kfree(h264_ctx); + return ret; +} + +static void rkvdec_h264_stop(struct rkvdec_ctx *ctx) +{ + struct rkvdec_h264_ctx *h264_ctx = ctx->priv; + struct rkvdec_dev *rkvdec = ctx->dev; + + dma_free_coherent(rkvdec->dev, h264_ctx->priv_tbl.size, + h264_ctx->priv_tbl.cpu, h264_ctx->priv_tbl.dma); + kfree(h264_ctx); +} + +static int rkvdec_h264_run(struct rkvdec_ctx *ctx) +{ + struct v4l2_h264_reflist_builder reflist_builder; + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_h264_ctx *h264_ctx = ctx->priv; + struct rkvdec_h264_priv_tbl *tbl = h264_ctx->priv_tbl.cpu; + struct rkvdec_h264_run run; + + rkvdec_h264_run_preamble(ctx, &run); + + /* Build the P/B{0,1} ref lists. */ + v4l2_h264_init_reflist_builder(&reflist_builder, run.decode_params, + run.sps, run.decode_params->dpb); + v4l2_h264_build_p_ref_list(&reflist_builder, h264_ctx->reflists.p); + v4l2_h264_build_b_ref_lists(&reflist_builder, h264_ctx->reflists.b0, + h264_ctx->reflists.b1); + + assemble_hw_scaling_list(&run, &tbl->scaling_list); + assemble_hw_pps(ctx, &run); + lookup_ref_buf_idx(ctx, &run); + assemble_hw_rps(&reflist_builder, &run, &h264_ctx->reflists, &tbl->rps); + + config_registers(ctx, &run); + + rkvdec_run_postamble(ctx, &run.base); + + rkvdec_schedule_watchdog(rkvdec, h264_ctx->regs.common.reg032_timeout_threshold); + + /* Start decoding! */ + writel(VDPU381_DEC_E_BIT, rkvdec->regs + VDPU381_REG_DEC_E); + + return 0; +} + +static int rkvdec_h264_try_ctrl(struct rkvdec_ctx *ctx, struct v4l2_ctrl *ctrl) +{ + if (ctrl->id == V4L2_CID_STATELESS_H264_SPS) + return rkvdec_h264_validate_sps(ctx, ctrl->p_new.p_h264_sps); + + return 0; +} + +const struct rkvdec_coded_fmt_ops rkvdec_vdpu381_h264_fmt_ops = { + .adjust_fmt = rkvdec_h264_adjust_fmt, + .get_image_fmt = rkvdec_h264_get_image_fmt, + .start = rkvdec_h264_start, + .stop = rkvdec_h264_stop, + .run = rkvdec_h264_run, + .try_ctrl = rkvdec_h264_try_ctrl, +}; diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-regs.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-regs.h new file mode 100644 index 000000000000..6da36031df2d --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-regs.h @@ -0,0 +1,430 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip VDPU381 Video Decoder driver registers description + * + * Copyright (C) 2024 Collabora, Ltd. + * Detlev Casanova + */ + +#include + +#ifndef _RKVDEC_REGS_H_ +#define _RKVDEC_REGS_H_ + +#define OFFSET_COMMON_REGS (8 * sizeof(u32)) +#define OFFSET_CODEC_PARAMS_REGS (64 * sizeof(u32)) +#define OFFSET_COMMON_ADDR_REGS (128 * sizeof(u32)) +#define OFFSET_CODEC_ADDR_REGS (160 * sizeof(u32)) +#define OFFSET_POC_HIGHBIT_REGS (200 * sizeof(u32)) + +#define VDPU381_MODE_HEVC 0 +#define VDPU381_MODE_H264 1 +#define VDPU381_MODE_VP9 2 +#define VDPU381_MODE_AVS2 3 + +#define MAX_SLICE_NUMBER 0x3fff + +#define RKVDEC_TIMEOUT_1080p (0xefffff) +#define RKVDEC_TIMEOUT_4K (0x2cfffff) +#define RKVDEC_TIMEOUT_8K (0x4ffffff) +#define RKVDEC_TIMEOUT_MAX (0xffffffff) + +#define VDPU381_REG_DEC_E 0x028 +#define VDPU381_DEC_E_BIT 1 + +#define VDPU381_REG_IMPORTANT_EN 0x02c +#define VDPU381_DEC_IRQ_DISABLE BIT(4) + +#define VDPU381_REG_STA_INT 0x380 +#define VDPU381_STA_INT_DEC_RDY_STA BIT(2) +#define VDPU381_STA_INT_ERROR BIT(4) +#define VDPU381_STA_INT_TIMEOUT BIT(5) +#define VDPU381_STA_INT_SOFTRESET_RDY BIT(9) + +/* base: OFFSET_COMMON_REGS */ +struct rkvdec_vdpu381_regs_common { + struct { + u32 in_endian : 1; + u32 in_swap32_e : 1; + u32 in_swap64_e : 1; + u32 str_endian : 1; + u32 str_swap32_e : 1; + u32 str_swap64_e : 1; + u32 out_endian : 1; + u32 out_swap32_e : 1; + u32 out_cbcr_swap : 1; + u32 out_swap64_e : 1; + u32 reserved : 22; + } reg008_in_out; + + struct { + u32 dec_mode : 10; + u32 reserved : 22; + } reg009_dec_mode; + + struct { + u32 dec_e : 1; + u32 reserved : 31; + } reg010_dec_e; + + struct { + u32 reserved0 : 1; + u32 dec_clkgate_e : 1; + u32 dec_e_strmd_clkgate_dis : 1; + u32 reserved1 : 1; + + u32 dec_irq_dis : 1; + u32 dec_timeout_e : 1; + u32 buf_empty_en : 1; + u32 reserved2 : 3; + + u32 dec_e_rewrite_valid : 1; + u32 reserved3 : 9; + u32 softrst_en_p : 1; + u32 force_softreset_valid : 1; + u32 reserved4 : 2; + u32 pix_range_det_e : 1; + u32 reserved5 : 7; + } reg011_important_en; + + struct { + u32 reserved0 : 1; + u32 colmv_compress_en : 1; + u32 fbc_e : 1; + u32 reserved1 : 1; + + u32 buspr_slot_disable : 1; + u32 error_info_en : 1; + u32 collect_info_en : 1; + u32 error_auto_rst_disable : 1; + + u32 scanlist_addr_valid_en : 1; + u32 scale_down_en : 1; + u32 error_cfg_wr_disable : 1; + u32 reserved2 : 21; + } reg012_secondary_en; + + struct { + u32 reserved0 : 1; + u32 req_timeout_rst_sel : 1; + u32 reserved1 : 1; + u32 dec_commonirq_mode : 1; + u32 reserved2 : 2; + u32 stmerror_waitdecfifo_empty : 1; + u32 reserved3 : 5; + u32 allow_not_wr_unref_bframe : 1; + u32 fbc_output_wr_disable : 1; + u32 reserved4 : 4; + u32 error_mode : 1; + u32 reserved5 : 2; + u32 ycacherd_prior : 1; + u32 reserved6 : 2; + u32 cur_pic_is_idr : 1; + u32 reserved7 : 1; + u32 right_auto_rst_disable : 1; + u32 frame_end_err_rst_flag : 1; + u32 rd_prior_mode : 1; + u32 rd_ctrl_prior_mode : 1; + u32 reserved8 : 1; + u32 filter_outbuf_mode : 1; + } reg013_en_mode_set; + + struct { + u32 fbc_force_uncompress : 1; + + u32 reserved0 : 2; + u32 allow_16x8_cp_flag : 1; + u32 reserved1 : 2; + + u32 fbc_h264_exten_4or8_flag : 1; + u32 reserved2 : 25; + } reg014_fbc_param_set; + + struct { + u32 rlc_mode_direct_write : 1; + u32 rlc_mode : 1; + u32 reserved0 : 3; + + u32 strm_start_bit : 7; + u32 reserved1 : 20; + } reg015_stream_param_set; + + u32 reg016_stream_len; + + struct { + u32 slice_num : 25; + u32 reserved : 7; + } reg017_slice_number; + + struct { + u32 y_hor_virstride : 16; + u32 reserved : 16; + } reg018_y_hor_stride; + + struct { + u32 uv_hor_virstride : 16; + u32 reserved : 16; + } reg019_uv_hor_stride; + + struct { + u32 y_virstride : 28; + u32 reserved : 4; + } reg020_y_stride; + + struct { + u32 inter_error_prc_mode : 1; + u32 error_intra_mode : 1; + u32 error_deb_en : 1; + u32 picidx_replace : 5; + u32 error_spread_e : 1; + u32 reserved0 : 3; + u32 error_inter_pred_cross_slice : 1; + u32 reserved1 : 11; + u32 roi_error_ctu_cal_en : 1; + u32 reserved2 : 7; + } reg021_error_ctrl_set; + + struct { + u32 roi_x_ctu_offset_st : 12; + u32 reserved0 : 4; + u32 roi_y_ctu_offset_st : 12; + u32 reserved1 : 4; + } reg022_err_roi_ctu_offset_start; + + struct { + u32 roi_x_ctu_offset_end : 12; + u32 reserved0 : 4; + u32 roi_y_ctu_offset_end : 12; + u32 reserved1 : 4; + } reg023_err_roi_ctu_offset_end; + + struct { + u32 cabac_err_en_lowbits : 32; + } reg024_cabac_error_en_lowbits; + + struct { + u32 cabac_err_en_highbits : 30; + u32 reserved : 2; + } reg025_cabac_error_en_highbits; + + struct { + u32 inter_auto_gating_e : 1; + u32 filterd_auto_gating_e : 1; + u32 strmd_auto_gating_e : 1; + u32 mcp_auto_gating_e : 1; + u32 busifd_auto_gating_e : 1; + u32 reserved0 : 3; + u32 dec_ctrl_auto_gating_e : 1; + u32 intra_auto_gating_e : 1; + u32 mc_auto_gating_e : 1; + u32 transd_auto_gating_e : 1; + u32 reserved1 : 4; + u32 sram_auto_gating_e : 1; + u32 cru_auto_gating_e : 1; + u32 reserved2 : 13; + u32 reg_cfg_gating_en : 1; + } reg026_block_gating_en; + + struct { + u32 core_safe_x_pixels : 16; + u32 core_safe_y_pixels : 16; + } reg027_core_safe_pixels; + + struct { + u32 vp9_wr_prob_idx : 3; + u32 reserved0 : 1; + u32 vp9_rd_prob_idx : 3; + u32 reserved1 : 1; + + u32 ref_req_advance_flag : 1; + u32 colmv_req_advance_flag : 1; + u32 poc_only_highbit_flag : 1; + u32 poc_arb_flag : 1; + + u32 reserved2 : 4; + u32 film_idx : 10; + u32 reserved3 : 2; + u32 pu_req_mismatch_dis : 1; + u32 colmv_req_mismatch_dis : 1; + u32 reserved4 : 2; + } reg028_multiply_core_ctrl; + + struct { + u32 scale_down_hor_ratio : 2; + u32 reserved0 : 6; + u32 scale_down_vrz_ratio : 2; + u32 reserved1 : 22; + } reg029_scale_down_ctrl; + + struct { + u32 y_scale_down_tile8x8_hor_stride : 20; + u32 reserved0 : 12; + } reg030_y_scale_down_tile8x8_hor_stride; + + struct { + u32 uv_scale_down8x8_tile_hor_stride : 20; + u32 reserved0 : 12; + } reg031_uv_scale_down_tile8x8_hor_stride; + + u32 reg032_timeout_threshold; +} __packed; + +/* base: OFFSET_COMMON_ADDR_REGS */ +struct rkvdec_vdpu381_regs_common_addr { + u32 rlc_base; + u32 rlcwrite_base; + u32 decout_base; + u32 colmv_cur_base; + u32 error_ref_base; + u32 rcb_base[10]; +} __packed; + +struct rkvdec_vdpu381_h26x_set { + u32 h26x_frame_orslice : 1; + u32 h26x_rps_mode : 1; + u32 h26x_stream_mode : 1; + u32 h26x_stream_lastpacket : 1; + u32 h264_firstslice_flag : 1; + u32 reserved : 27; +} __packed; + +/* base: OFFSET_CODEC_PARAMS_REGS */ +struct rkvdec_vdpu381_regs_h264_params { + struct rkvdec_vdpu381_h26x_set reg064_h26x_set; + + u32 reg065_cur_top_poc; + u32 reg066_cur_bot_poc; + u32 reg067_098_ref_poc[32]; + + struct rkvdec_vdpu381_h264_info { + struct rkvdec_vdpu381_h264_ref_info { + u32 ref_field : 1; + u32 ref_topfield_used : 1; + u32 ref_botfield_used : 1; + u32 ref_colmv_use_flag : 1; + u32 reserved : 4; + } __packed ref_info[4]; + } __packed reg099_102_ref_info_regs[4]; + + u32 reserved_103_111[9]; + + struct { + u32 avs2_ref_error_field : 1; + u32 avs2_ref_error_topfield : 1; + u32 ref_error_topfield_used : 1; + u32 ref_error_botfield_used : 1; + u32 reserved : 28; + } reg112_error_ref_info; +} __packed; + +struct rkvdec_vdpu381_regs_hevc_params { + struct rkvdec_vdpu381_h26x_set reg064_h26x_set; + + u32 reg065_cur_top_poc; + u32 reg066_cur_bot_poc; + u32 reg067_082_ref_poc[16]; + + u32 reserved_083_098[16]; + + struct { + u32 hevc_ref_valid_0 : 1; + u32 hevc_ref_valid_1 : 1; + u32 hevc_ref_valid_2 : 1; + u32 hevc_ref_valid_3 : 1; + u32 reserve0 : 4; + u32 hevc_ref_valid_4 : 1; + u32 hevc_ref_valid_5 : 1; + u32 hevc_ref_valid_6 : 1; + u32 hevc_ref_valid_7 : 1; + u32 reserve1 : 4; + u32 hevc_ref_valid_8 : 1; + u32 hevc_ref_valid_9 : 1; + u32 hevc_ref_valid_10 : 1; + u32 hevc_ref_valid_11 : 1; + u32 reserve2 : 4; + u32 hevc_ref_valid_12 : 1; + u32 hevc_ref_valid_13 : 1; + u32 hevc_ref_valid_14 : 1; + u32 reserve3 : 5; + } reg099_hevc_ref_valid; + + u32 reserved_100_102[3]; + + struct { + u32 ref_pic_layer_same_with_cur : 16; + u32 reserve : 16; + } reg103_hevc_mvc0; + + struct { + u32 poc_lsb_not_present_flag : 1; + u32 num_direct_ref_layers : 6; + u32 reserve0 : 1; + + u32 num_reflayer_pics : 6; + u32 default_ref_layers_active_flag : 1; + u32 max_one_active_ref_layer_flag : 1; + + u32 poc_reset_info_present_flag : 1; + u32 vps_poc_lsb_aligned_flag : 1; + u32 mvc_poc15_valid_flag : 1; + u32 reserve1 : 13; + } reg104_hevc_mvc1; + + u32 reserved_105_111[7]; + + struct { + u32 avs2_ref_error_field : 1; + u32 avs2_ref_error_topfield : 1; + u32 ref_error_topfield_used : 1; + u32 ref_error_botfield_used : 1; + u32 reserve : 28; + } reg112_hevc_ref_info; + +} __packed; + +/* base: OFFSET_CODEC_ADDR_REGS */ +struct rkvdec_vdpu381_regs_h26x_addr { + u32 reserved_160; + u32 reg161_pps_base; + u32 reserved_162; + u32 reg163_rps_base; + u32 reg164_180_ref_base[16]; + u32 reg181_scanlist_addr; + u32 reg182_198_colmv_base[16]; + u32 reg199_cabactbl_base; +} __packed; + +struct rkvdec_vdpu381_regs_h26x_highpoc { + struct { + u32 ref0_poc_highbit : 4; + u32 ref1_poc_highbit : 4; + u32 ref2_poc_highbit : 4; + u32 ref3_poc_highbit : 4; + u32 ref4_poc_highbit : 4; + u32 ref5_poc_highbit : 4; + u32 ref6_poc_highbit : 4; + u32 ref7_poc_highbit : 4; + } reg200_203_ref_poc_highbit[4]; + struct { + u32 cur_poc_highbit : 4; + u32 reserved : 28; + } reg204_cur_poc_highbit; +} __packed; + +struct rkvdec_vdpu381_regs_h264 { + struct rkvdec_vdpu381_regs_common common; + struct rkvdec_vdpu381_regs_h264_params h264_param; + struct rkvdec_vdpu381_regs_common_addr common_addr; + struct rkvdec_vdpu381_regs_h26x_addr h264_addr; + struct rkvdec_vdpu381_regs_h26x_highpoc h264_highpoc; +} __packed; + +struct rkvdec_vdpu381_regs_hevc { + struct rkvdec_vdpu381_regs_common common; + struct rkvdec_vdpu381_regs_hevc_params hevc_param; + struct rkvdec_vdpu381_regs_common_addr common_addr; + struct rkvdec_vdpu381_regs_h26x_addr hevc_addr; + struct rkvdec_vdpu381_regs_h26x_highpoc hevc_highpoc; +} __packed; + +#endif /* __RKVDEC_REGS_H__ */ diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c index af2eced90026..da192c866e2e 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c @@ -29,6 +29,7 @@ #include "rkvdec.h" #include "rkvdec-regs.h" +#include "rkvdec-vdpu381-regs.h" #include "rkvdec-rcb.h" static bool rkvdec_image_fmt_match(enum rkvdec_image_fmt fmt1, @@ -90,6 +91,9 @@ static void rkvdec_fill_decoded_pixfmt(struct rkvdec_ctx *ctx, { v4l2_fill_pixfmt_mp(pix_mp, pix_mp->pixelformat, pix_mp->width, pix_mp->height); + + ctx->colmv_offset = pix_mp->plane_fmt[0].sizeimage; + pix_mp->plane_fmt[0].sizeimage += 128 * DIV_ROUND_UP(pix_mp->width, 16) * DIV_ROUND_UP(pix_mp->height, 16); @@ -269,6 +273,53 @@ static const struct rkvdec_ctrls rkvdec_h264_ctrls = { .num_ctrls = ARRAY_SIZE(rkvdec_h264_ctrl_descs), }; +static const struct rkvdec_ctrl_desc vdpu38x_h264_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_SPS, + .cfg.ops = &rkvdec_ctrl_ops, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_PPS, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_SCALING_MATRIX, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_DECODE_MODE, + .cfg.min = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + .cfg.max = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + .cfg.def = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + }, + { + .cfg.id = V4L2_CID_STATELESS_H264_START_CODE, + .cfg.min = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + .cfg.def = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + .cfg.max = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + }, + { + .cfg.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, + .cfg.min = V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE, + .cfg.max = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422_INTRA, + .cfg.menu_skip_mask = + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE), + .cfg.def = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN, + }, + { + .cfg.id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, + .cfg.min = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, + .cfg.max = V4L2_MPEG_VIDEO_H264_LEVEL_6_0, + }, +}; + +static const struct rkvdec_ctrls vdpu38x_h264_ctrls = { + .ctrls = vdpu38x_h264_ctrl_descs, + .num_ctrls = ARRAY_SIZE(vdpu38x_h264_ctrl_descs), +}; + static const struct rkvdec_decoded_fmt_desc rkvdec_h264_decoded_fmts[] = { { .fourcc = V4L2_PIX_FMT_NV12, @@ -382,6 +433,25 @@ static const struct rkvdec_coded_fmt_desc rk3288_coded_fmts[] = { } }; +static const struct rkvdec_coded_fmt_desc vdpu381_coded_fmts[] = { + { + .fourcc = V4L2_PIX_FMT_H264_SLICE, + .frmsize = { + .min_width = 64, + .max_width = 65520, + .step_width = 64, + .min_height = 64, + .max_height = 65520, + .step_height = 16, + }, + .ctrls = &vdpu38x_h264_ctrls, + .ops = &rkvdec_vdpu381_h264_fmt_ops, + .num_decoded_fmts = ARRAY_SIZE(rkvdec_h264_decoded_fmts), + .decoded_fmts = rkvdec_h264_decoded_fmts, + .subsystem_flags = VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF, + }, +}; + static const struct rkvdec_coded_fmt_desc * rkvdec_enum_coded_fmt_desc(struct rkvdec_ctx *ctx, int index) { @@ -946,6 +1016,20 @@ void rkvdec_memcpy_toio(void __iomem *dst, void *src, size_t len) #endif } +void rkvdec_schedule_watchdog(struct rkvdec_dev *rkvdec, u32 timeout_threshold) +{ + /* Set watchdog at 2 times the hardware timeout threshold */ + u32 watchdog_time; + unsigned long axi_rate = clk_get_rate(rkvdec->axi_clk); + + if (axi_rate) + watchdog_time = 2 * div_u64(1000 * (u64)timeout_threshold, axi_rate); + else + watchdog_time = 2000; + + schedule_delayed_work(&rkvdec->watchdog_work, msecs_to_jiffies(watchdog_time)); +} + static void rkvdec_device_run(void *priv) { struct rkvdec_ctx *ctx = priv; @@ -1245,6 +1329,35 @@ static irqreturn_t rk3399_irq_handler(struct rkvdec_ctx *ctx) return IRQ_HANDLED; } +static irqreturn_t vdpu381_irq_handler(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + enum vb2_buffer_state state; + bool need_reset = 0; + u32 status; + + status = readl(rkvdec->regs + VDPU381_REG_STA_INT); + writel(0, rkvdec->regs + VDPU381_REG_STA_INT); + + if (status & VDPU381_STA_INT_DEC_RDY_STA) { + state = VB2_BUF_STATE_DONE; + } else { + state = VB2_BUF_STATE_ERROR; + if (status & (VDPU381_STA_INT_SOFTRESET_RDY | + VDPU381_STA_INT_TIMEOUT | + VDPU381_STA_INT_ERROR)) + rkvdec_iommu_restore(rkvdec); + } + + if (need_reset) + rkvdec_iommu_restore(rkvdec); + + if (cancel_delayed_work(&rkvdec->watchdog_work)) + rkvdec_job_finish(ctx, state); + + return IRQ_HANDLED; +} + static irqreturn_t rkvdec_irq_handler(int irq, void *priv) { struct rkvdec_dev *rkvdec = priv; @@ -1321,6 +1434,7 @@ static const struct rkvdec_variant rk3288_rkvdec_variant = { .coded_fmts = rk3288_coded_fmts, .num_coded_fmts = ARRAY_SIZE(rk3288_coded_fmts), .ops = &rk3399_variant_ops, + .has_single_reg_region = true, }; static const struct rkvdec_variant rk3328_rkvdec_variant = { @@ -1328,6 +1442,7 @@ static const struct rkvdec_variant rk3328_rkvdec_variant = { .coded_fmts = rkvdec_coded_fmts, .num_coded_fmts = ARRAY_SIZE(rkvdec_coded_fmts), .ops = &rk3399_variant_ops, + .has_single_reg_region = true, .quirks = RKVDEC_QUIRK_DISABLE_QOS, }; @@ -1336,6 +1451,32 @@ static const struct rkvdec_variant rk3399_rkvdec_variant = { .coded_fmts = rkvdec_coded_fmts, .num_coded_fmts = ARRAY_SIZE(rkvdec_coded_fmts), .ops = &rk3399_variant_ops, + .has_single_reg_region = true, +}; + +static const struct rcb_size_info vdpu381_rcb_sizes[] = { + {6, PIC_WIDTH}, // intrar + {1, PIC_WIDTH}, // transdr (Is actually 0.4*pic_width) + {1, PIC_HEIGHT}, // transdc (Is actually 0.1*pic_height) + {3, PIC_WIDTH}, // streamdr + {6, PIC_WIDTH}, // interr + {3, PIC_HEIGHT}, // interc + {22, PIC_WIDTH}, // dblkr + {6, PIC_WIDTH}, // saor + {11, PIC_WIDTH}, // fbcr + {67, PIC_HEIGHT}, // filtc col +}; + +static const struct rkvdec_variant_ops vdpu381_variant_ops = { + .irq_handler = vdpu381_irq_handler, +}; + +static const struct rkvdec_variant vdpu381_variant = { + .coded_fmts = vdpu381_coded_fmts, + .num_coded_fmts = ARRAY_SIZE(vdpu381_coded_fmts), + .rcb_sizes = vdpu381_rcb_sizes, + .num_rcb_sizes = ARRAY_SIZE(vdpu381_rcb_sizes), + .ops = &vdpu381_variant_ops, }; static const struct of_device_id of_rkvdec_match[] = { @@ -1351,6 +1492,10 @@ static const struct of_device_id of_rkvdec_match[] = { .compatible = "rockchip,rk3399-vdec", .data = &rk3399_rkvdec_variant, }, + { + .compatible = "rockchip,rk3588-vdec", + .data = &vdpu381_variant, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, of_rkvdec_match); @@ -1384,10 +1529,17 @@ static int rkvdec_probe(struct platform_device *pdev) return ret; rkvdec->num_clocks = ret; + rkvdec->axi_clk = devm_clk_get(&pdev->dev, "axi"); - rkvdec->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(rkvdec->regs)) - return PTR_ERR(rkvdec->regs); + if (rkvdec->variant->has_single_reg_region) { + rkvdec->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rkvdec->regs)) + return PTR_ERR(rkvdec->regs); + } else { + rkvdec->regs = devm_platform_ioremap_resource_byname(pdev, "function"); + if (IS_ERR(rkvdec->regs)) + return PTR_ERR(rkvdec->regs); + } ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); if (ret) { diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h index 7766a79caf68..4afa9cd690d5 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.h @@ -25,6 +25,10 @@ #define RKVDEC_QUIRK_DISABLE_QOS BIT(0) +#define RKVDEC_1080P_PIXELS (1920 * 1088) +#define RKVDEC_4K_PIXELS (4096 * 2304) +#define RKVDEC_8K_PIXELS (7680 * 4320) + struct rkvdec_ctx; struct rkvdec_rcb_config; @@ -78,6 +82,7 @@ struct rkvdec_variant { const struct rcb_size_info *rcb_sizes; size_t num_rcb_sizes; const struct rkvdec_variant_ops *ops; + bool has_single_reg_region; unsigned int quirks; }; @@ -126,6 +131,7 @@ struct rkvdec_dev { struct device *dev; struct clk_bulk_data *clocks; unsigned int num_clocks; + struct clk *axi_clk; void __iomem *regs; struct mutex vdev_lock; /* serializes ioctls */ struct delayed_work watchdog_work; @@ -144,6 +150,7 @@ struct rkvdec_ctx { struct rkvdec_dev *dev; enum rkvdec_image_fmt image_fmt; struct rkvdec_rcb_config *rcb_config; + u32 colmv_offset; void *priv; }; @@ -167,11 +174,16 @@ struct rkvdec_aux_buf { void rkvdec_run_preamble(struct rkvdec_ctx *ctx, struct rkvdec_run *run); void rkvdec_run_postamble(struct rkvdec_ctx *ctx, struct rkvdec_run *run); void rkvdec_memcpy_toio(void __iomem *dst, void *src, size_t len); +void rkvdec_schedule_watchdog(struct rkvdec_dev *rkvdec, u32 timeout_threshold); void rkvdec_quirks_disable_qos(struct rkvdec_ctx *ctx); +/* RKVDEC ops */ extern const struct rkvdec_coded_fmt_ops rkvdec_h264_fmt_ops; extern const struct rkvdec_coded_fmt_ops rkvdec_hevc_fmt_ops; extern const struct rkvdec_coded_fmt_ops rkvdec_vp9_fmt_ops; +/* VDPU381 ops */ +extern const struct rkvdec_coded_fmt_ops rkvdec_vdpu381_h264_fmt_ops; + #endif /* RKVDEC_H_ */ From fde24907570dcc56406f5e48d5c9d9e7e2da36fc Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:31 -0500 Subject: [PATCH 301/307] media: rkvdec: Add H264 support for the VDPU383 variant This variant is used on the RK3576 SoC. The moving vectors size requirements are slightly different so support for a colmv_size function per variant is added. Also, the link registers are used to start the decoder and read IRQ status. The fluster score is 128/135 for JVT-AVC_V1, with MPS_MW_A failing in addition to the usual ones. The other test suites are not supported yet. Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/Makefile | 1 + .../rockchip/rkvdec/rkvdec-vdpu383-h264.c | 538 ++++++++++++++++++ .../rockchip/rkvdec/rkvdec-vdpu383-regs.h | 281 +++++++++ .../media/platform/rockchip/rkvdec/rkvdec.c | 105 +++- .../media/platform/rockchip/rkvdec/rkvdec.h | 5 + 5 files changed, 925 insertions(+), 5 deletions(-) create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-h264.c create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-regs.h diff --git a/drivers/media/platform/rockchip/rkvdec/Makefile b/drivers/media/platform/rockchip/rkvdec/Makefile index 7bfd95151e40..a58d4aede2fe 100644 --- a/drivers/media/platform/rockchip/rkvdec/Makefile +++ b/drivers/media/platform/rockchip/rkvdec/Makefile @@ -9,4 +9,5 @@ rockchip-vdec-y += \ rkvdec-hevc-common.o \ rkvdec-rcb.o \ rkvdec-vdpu381-h264.o \ + rkvdec-vdpu383-h264.o \ rkvdec-vp9.o diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-h264.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-h264.c new file mode 100644 index 000000000000..6ab3167addc8 --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-h264.c @@ -0,0 +1,538 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip Video Decoder VDPU383 H264 backend + * + * Copyright (C) 2024 Collabora, Ltd. + * Detlev Casanova + */ + +#include +#include + +#include + +#include "rkvdec-rcb.h" +#include "rkvdec-cabac.h" +#include "rkvdec-vdpu383-regs.h" +#include "rkvdec-h264-common.h" + +struct rkvdec_sps { + u16 seq_parameter_set_id: 4; + u16 profile_idc: 8; + u16 constraint_set3_flag: 1; + u16 chroma_format_idc: 2; + u16 bit_depth_luma: 3; + u16 bit_depth_chroma: 3; + u16 qpprime_y_zero_transform_bypass_flag: 1; + u16 log2_max_frame_num_minus4: 4; + u16 max_num_ref_frames: 5; + u16 pic_order_cnt_type: 2; + u16 log2_max_pic_order_cnt_lsb_minus4: 4; + u16 delta_pic_order_always_zero_flag: 1; + + u16 pic_width_in_mbs: 16; + u16 pic_height_in_mbs: 16; + + u16 frame_mbs_only_flag: 1; + u16 mb_adaptive_frame_field_flag: 1; + u16 direct_8x8_inference_flag: 1; + u16 mvc_extension_enable: 1; + u16 num_views: 2; + u16 view_id0: 10; + u16 view_id1: 10; +} __packed; + +struct rkvdec_pps { + u32 pic_parameter_set_id: 8; + u32 pps_seq_parameter_set_id: 5; + u32 entropy_coding_mode_flag: 1; + u32 bottom_field_pic_order_in_frame_present_flag: 1; + u32 num_ref_idx_l0_default_active_minus1: 5; + u32 num_ref_idx_l1_default_active_minus1: 5; + u32 weighted_pred_flag: 1; + u32 weighted_bipred_idc: 2; + u32 pic_init_qp_minus26: 7; + u32 pic_init_qs_minus26: 6; + u32 chroma_qp_index_offset: 5; + u32 deblocking_filter_control_present_flag: 1; + u32 constrained_intra_pred_flag: 1; + u32 redundant_pic_cnt_present: 1; + u32 transform_8x8_mode_flag: 1; + u32 second_chroma_qp_index_offset: 5; + u32 scaling_list_enable_flag: 1; + u32 is_longterm: 16; + u32 voidx: 16; + + // dpb + u32 pic_field_flag: 1; + u32 pic_associated_flag: 1; + u32 cur_top_field: 32; + u32 cur_bot_field: 32; + + u32 top_field_order_cnt0: 32; + u32 bot_field_order_cnt0: 32; + u32 top_field_order_cnt1: 32; + u32 bot_field_order_cnt1: 32; + u32 top_field_order_cnt2: 32; + u32 bot_field_order_cnt2: 32; + u32 top_field_order_cnt3: 32; + u32 bot_field_order_cnt3: 32; + u32 top_field_order_cnt4: 32; + u32 bot_field_order_cnt4: 32; + u32 top_field_order_cnt5: 32; + u32 bot_field_order_cnt5: 32; + u32 top_field_order_cnt6: 32; + u32 bot_field_order_cnt6: 32; + u32 top_field_order_cnt7: 32; + u32 bot_field_order_cnt7: 32; + u32 top_field_order_cnt8: 32; + u32 bot_field_order_cnt8: 32; + u32 top_field_order_cnt9: 32; + u32 bot_field_order_cnt9: 32; + u32 top_field_order_cnt10: 32; + u32 bot_field_order_cnt10: 32; + u32 top_field_order_cnt11: 32; + u32 bot_field_order_cnt11: 32; + u32 top_field_order_cnt12: 32; + u32 bot_field_order_cnt12: 32; + u32 top_field_order_cnt13: 32; + u32 bot_field_order_cnt13: 32; + u32 top_field_order_cnt14: 32; + u32 bot_field_order_cnt14: 32; + u32 top_field_order_cnt15: 32; + u32 bot_field_order_cnt15: 32; + + u32 ref_field_flags: 16; + u32 ref_topfield_used: 16; + u32 ref_botfield_used: 16; + u32 ref_colmv_use_flag: 16; + + u32 reserved0: 30; + u32 reserved[3]; +} __packed; + +struct rkvdec_sps_pps { + struct rkvdec_sps sps; + struct rkvdec_pps pps; +} __packed; + +/* Data structure describing auxiliary buffer format. */ +struct rkvdec_h264_priv_tbl { + s8 cabac_table[4][464][2]; + struct rkvdec_h264_scaling_list scaling_list; + struct rkvdec_sps_pps param_set[256]; + struct rkvdec_rps rps; +} __packed; + +struct rkvdec_h264_ctx { + struct rkvdec_aux_buf priv_tbl; + struct rkvdec_h264_reflists reflists; + struct vdpu383_regs_h26x regs; +}; + +static void set_field_order_cnt(struct rkvdec_pps *pps, const struct v4l2_h264_dpb_entry *dpb) +{ + pps->top_field_order_cnt0 = dpb[0].top_field_order_cnt; + pps->bot_field_order_cnt0 = dpb[0].bottom_field_order_cnt; + pps->top_field_order_cnt1 = dpb[1].top_field_order_cnt; + pps->bot_field_order_cnt1 = dpb[1].bottom_field_order_cnt; + pps->top_field_order_cnt2 = dpb[2].top_field_order_cnt; + pps->bot_field_order_cnt2 = dpb[2].bottom_field_order_cnt; + pps->top_field_order_cnt3 = dpb[3].top_field_order_cnt; + pps->bot_field_order_cnt3 = dpb[3].bottom_field_order_cnt; + pps->top_field_order_cnt4 = dpb[4].top_field_order_cnt; + pps->bot_field_order_cnt4 = dpb[4].bottom_field_order_cnt; + pps->top_field_order_cnt5 = dpb[5].top_field_order_cnt; + pps->bot_field_order_cnt5 = dpb[5].bottom_field_order_cnt; + pps->top_field_order_cnt6 = dpb[6].top_field_order_cnt; + pps->bot_field_order_cnt6 = dpb[6].bottom_field_order_cnt; + pps->top_field_order_cnt7 = dpb[7].top_field_order_cnt; + pps->bot_field_order_cnt7 = dpb[7].bottom_field_order_cnt; + pps->top_field_order_cnt8 = dpb[8].top_field_order_cnt; + pps->bot_field_order_cnt8 = dpb[8].bottom_field_order_cnt; + pps->top_field_order_cnt9 = dpb[9].top_field_order_cnt; + pps->bot_field_order_cnt9 = dpb[9].bottom_field_order_cnt; + pps->top_field_order_cnt10 = dpb[10].top_field_order_cnt; + pps->bot_field_order_cnt10 = dpb[10].bottom_field_order_cnt; + pps->top_field_order_cnt11 = dpb[11].top_field_order_cnt; + pps->bot_field_order_cnt11 = dpb[11].bottom_field_order_cnt; + pps->top_field_order_cnt12 = dpb[12].top_field_order_cnt; + pps->bot_field_order_cnt12 = dpb[12].bottom_field_order_cnt; + pps->top_field_order_cnt13 = dpb[13].top_field_order_cnt; + pps->bot_field_order_cnt13 = dpb[13].bottom_field_order_cnt; + pps->top_field_order_cnt14 = dpb[14].top_field_order_cnt; + pps->bot_field_order_cnt14 = dpb[14].bottom_field_order_cnt; + pps->top_field_order_cnt15 = dpb[15].top_field_order_cnt; + pps->bot_field_order_cnt15 = dpb[15].bottom_field_order_cnt; +} + +static void assemble_hw_pps(struct rkvdec_ctx *ctx, + struct rkvdec_h264_run *run) +{ + struct rkvdec_h264_ctx *h264_ctx = ctx->priv; + const struct v4l2_ctrl_h264_sps *sps = run->sps; + const struct v4l2_ctrl_h264_pps *pps = run->pps; + const struct v4l2_ctrl_h264_decode_params *dec_params = run->decode_params; + const struct v4l2_h264_dpb_entry *dpb = dec_params->dpb; + struct rkvdec_h264_priv_tbl *priv_tbl = h264_ctx->priv_tbl.cpu; + struct rkvdec_sps_pps *hw_ps; + u32 pic_width, pic_height; + u32 i; + + /* + * HW read the SPS/PPS information from PPS packet index by PPS id. + * offset from the base can be calculated by PPS_id * 32 (size per PPS + * packet unit). so the driver copy SPS/PPS information to the exact PPS + * packet unit for HW accessing. + */ + hw_ps = &priv_tbl->param_set[pps->pic_parameter_set_id]; + memset(hw_ps, 0, sizeof(*hw_ps)); + + /* write sps */ + hw_ps->sps.seq_parameter_set_id = sps->seq_parameter_set_id; + hw_ps->sps.profile_idc = sps->profile_idc; + hw_ps->sps.constraint_set3_flag = !!(sps->constraint_set_flags & (1 << 3)); + hw_ps->sps.chroma_format_idc = sps->chroma_format_idc; + hw_ps->sps.bit_depth_luma = sps->bit_depth_luma_minus8; + hw_ps->sps.bit_depth_chroma = sps->bit_depth_chroma_minus8; + hw_ps->sps.qpprime_y_zero_transform_bypass_flag = + !!(sps->flags & V4L2_H264_SPS_FLAG_QPPRIME_Y_ZERO_TRANSFORM_BYPASS); + hw_ps->sps.log2_max_frame_num_minus4 = sps->log2_max_frame_num_minus4; + hw_ps->sps.max_num_ref_frames = sps->max_num_ref_frames; + hw_ps->sps.pic_order_cnt_type = sps->pic_order_cnt_type; + hw_ps->sps.log2_max_pic_order_cnt_lsb_minus4 = + sps->log2_max_pic_order_cnt_lsb_minus4; + hw_ps->sps.delta_pic_order_always_zero_flag = + !!(sps->flags & V4L2_H264_SPS_FLAG_DELTA_PIC_ORDER_ALWAYS_ZERO); + hw_ps->sps.mvc_extension_enable = 0; + hw_ps->sps.num_views = 0; + + /* + * Use the SPS values since they are already in macroblocks + * dimensions, height can be field height (halved) if + * V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY is not set and also it allows + * decoding smaller images into larger allocation which can be used + * to implementing SVC spatial layer support. + */ + pic_width = 16 * (sps->pic_width_in_mbs_minus1 + 1); + pic_height = 16 * (sps->pic_height_in_map_units_minus1 + 1); + if (!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY)) + pic_height *= 2; + if (!!(dec_params->flags & V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC)) + pic_height /= 2; + + hw_ps->sps.pic_width_in_mbs = pic_width; + hw_ps->sps.pic_height_in_mbs = pic_height; + + hw_ps->sps.frame_mbs_only_flag = + !!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY); + hw_ps->sps.mb_adaptive_frame_field_flag = + !!(sps->flags & V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD); + hw_ps->sps.direct_8x8_inference_flag = + !!(sps->flags & V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE); + + /* write pps */ + hw_ps->pps.pic_parameter_set_id = pps->pic_parameter_set_id; + hw_ps->pps.pps_seq_parameter_set_id = pps->seq_parameter_set_id; + hw_ps->pps.entropy_coding_mode_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE); + hw_ps->pps.bottom_field_pic_order_in_frame_present_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT); + hw_ps->pps.num_ref_idx_l0_default_active_minus1 = + pps->num_ref_idx_l0_default_active_minus1; + hw_ps->pps.num_ref_idx_l1_default_active_minus1 = + pps->num_ref_idx_l1_default_active_minus1; + hw_ps->pps.weighted_pred_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_WEIGHTED_PRED); + hw_ps->pps.weighted_bipred_idc = pps->weighted_bipred_idc; + hw_ps->pps.pic_init_qp_minus26 = pps->pic_init_qp_minus26; + hw_ps->pps.pic_init_qs_minus26 = pps->pic_init_qs_minus26; + hw_ps->pps.chroma_qp_index_offset = pps->chroma_qp_index_offset; + hw_ps->pps.deblocking_filter_control_present_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT); + hw_ps->pps.constrained_intra_pred_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED); + hw_ps->pps.redundant_pic_cnt_present = + !!(pps->flags & V4L2_H264_PPS_FLAG_REDUNDANT_PIC_CNT_PRESENT); + hw_ps->pps.transform_8x8_mode_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE); + hw_ps->pps.second_chroma_qp_index_offset = pps->second_chroma_qp_index_offset; + hw_ps->pps.scaling_list_enable_flag = + !!(pps->flags & V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT); + + set_field_order_cnt(&hw_ps->pps, dpb); + + for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { + if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM) + hw_ps->pps.is_longterm |= (1 << i); + + hw_ps->pps.ref_field_flags |= + (!!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_FIELD)) << i; + hw_ps->pps.ref_colmv_use_flag |= + (!!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) << i; + hw_ps->pps.ref_topfield_used |= + (!!(dpb[i].fields & V4L2_H264_TOP_FIELD_REF)) << i; + hw_ps->pps.ref_botfield_used |= + (!!(dpb[i].fields & V4L2_H264_BOTTOM_FIELD_REF)) << i; + } + + hw_ps->pps.pic_field_flag = + !!(dec_params->flags & V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC); + hw_ps->pps.pic_associated_flag = + !!(dec_params->flags & V4L2_H264_DECODE_PARAM_FLAG_BOTTOM_FIELD); + + hw_ps->pps.cur_top_field = dec_params->top_field_order_cnt; + hw_ps->pps.cur_bot_field = dec_params->bottom_field_order_cnt; +} + +static void rkvdec_write_regs(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_h264_ctx *h264_ctx = ctx->priv; + + rkvdec_memcpy_toio(rkvdec->regs + VDPU383_OFFSET_COMMON_REGS, + &h264_ctx->regs.common, + sizeof(h264_ctx->regs.common)); + rkvdec_memcpy_toio(rkvdec->regs + VDPU383_OFFSET_COMMON_ADDR_REGS, + &h264_ctx->regs.common_addr, + sizeof(h264_ctx->regs.common_addr)); + rkvdec_memcpy_toio(rkvdec->regs + VDPU383_OFFSET_CODEC_PARAMS_REGS, + &h264_ctx->regs.h26x_params, + sizeof(h264_ctx->regs.h26x_params)); + rkvdec_memcpy_toio(rkvdec->regs + VDPU383_OFFSET_CODEC_ADDR_REGS, + &h264_ctx->regs.h26x_addr, + sizeof(h264_ctx->regs.h26x_addr)); +} + +static void config_registers(struct rkvdec_ctx *ctx, + struct rkvdec_h264_run *run) +{ + const struct v4l2_ctrl_h264_decode_params *dec_params = run->decode_params; + struct rkvdec_h264_ctx *h264_ctx = ctx->priv; + dma_addr_t priv_start_addr = h264_ctx->priv_tbl.dma; + const struct v4l2_pix_format_mplane *dst_fmt; + struct vb2_v4l2_buffer *src_buf = run->base.bufs.src; + struct vb2_v4l2_buffer *dst_buf = run->base.bufs.dst; + struct vdpu383_regs_h26x *regs = &h264_ctx->regs; + const struct v4l2_format *f; + dma_addr_t rlc_addr; + dma_addr_t dst_addr; + u32 hor_virstride; + u32 ver_virstride; + u32 y_virstride; + u32 offset; + u32 pixels; + u32 i; + + memset(regs, 0, sizeof(*regs)); + + /* Set H264 mode */ + regs->common.reg008_dec_mode = VDPU383_MODE_H264; + + /* Set input stream length */ + regs->h26x_params.reg066_stream_len = vb2_get_plane_payload(&src_buf->vb2_buf, 0); + + /* Set strides */ + f = &ctx->decoded_fmt; + dst_fmt = &f->fmt.pix_mp; + hor_virstride = dst_fmt->plane_fmt[0].bytesperline; + ver_virstride = dst_fmt->height; + y_virstride = hor_virstride * ver_virstride; + + pixels = dst_fmt->height * dst_fmt->width; + + regs->h26x_params.reg068_hor_virstride = hor_virstride / 16; + regs->h26x_params.reg069_raster_uv_hor_virstride = hor_virstride / 16; + regs->h26x_params.reg070_y_virstride = y_virstride / 16; + + /* Activate block gating */ + regs->common.reg010_block_gating_en.strmd_auto_gating_e = 1; + regs->common.reg010_block_gating_en.inter_auto_gating_e = 1; + regs->common.reg010_block_gating_en.intra_auto_gating_e = 1; + regs->common.reg010_block_gating_en.transd_auto_gating_e = 1; + regs->common.reg010_block_gating_en.recon_auto_gating_e = 1; + regs->common.reg010_block_gating_en.filterd_auto_gating_e = 1; + regs->common.reg010_block_gating_en.bus_auto_gating_e = 1; + regs->common.reg010_block_gating_en.ctrl_auto_gating_e = 1; + regs->common.reg010_block_gating_en.rcb_auto_gating_e = 1; + regs->common.reg010_block_gating_en.err_prc_auto_gating_e = 1; + + /* Set timeout threshold */ + if (pixels < RKVDEC_1080P_PIXELS) + regs->common.reg013_core_timeout_threshold = VDPU383_TIMEOUT_1080p; + else if (pixels < RKVDEC_4K_PIXELS) + regs->common.reg013_core_timeout_threshold = VDPU383_TIMEOUT_4K; + else if (pixels < RKVDEC_8K_PIXELS) + regs->common.reg013_core_timeout_threshold = VDPU383_TIMEOUT_8K; + else + regs->common.reg013_core_timeout_threshold = VDPU383_TIMEOUT_MAX; + + regs->common.reg016_error_ctrl_set.error_proc_disable = 1; + + /* Set ref pic address & poc */ + for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { + struct vb2_buffer *vb_buf = run->ref_buf[i]; + dma_addr_t buf_dma; + + /* + * If a DPB entry is unused or invalid, address of current destination + * buffer is returned. + */ + if (!vb_buf) + vb_buf = &dst_buf->vb2_buf; + + buf_dma = vb2_dma_contig_plane_dma_addr(vb_buf, 0); + + /* Set reference addresses */ + regs->h26x_addr.reg170_185_ref_base[i] = buf_dma; + regs->h26x_addr.reg195_210_payload_st_ref_base[i] = buf_dma; + + /* Set COLMV addresses */ + regs->h26x_addr.reg217_232_colmv_ref_base[i] = buf_dma + ctx->colmv_offset; + } + + /* Set rlc base address (input stream) */ + rlc_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + regs->common_addr.reg128_strm_base = rlc_addr; + + /* Set output base address */ + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + regs->h26x_addr.reg168_decout_base = dst_addr; + regs->h26x_addr.reg169_error_ref_base = dst_addr; + regs->h26x_addr.reg192_payload_st_cur_base = dst_addr; + + /* Set colmv address */ + regs->h26x_addr.reg216_colmv_cur_base = dst_addr + ctx->colmv_offset; + + /* Set RCB addresses */ + for (i = 0; i < rkvdec_rcb_buf_count(ctx); i++) { + regs->common_addr.reg140_162_rcb_info[i].offset = rkvdec_rcb_buf_dma_addr(ctx, i); + regs->common_addr.reg140_162_rcb_info[i].size = rkvdec_rcb_buf_size(ctx, i); + } + + /* Set hw pps address */ + offset = offsetof(struct rkvdec_h264_priv_tbl, param_set); + regs->common_addr.reg131_gbl_base = priv_start_addr + offset; + regs->h26x_params.reg067_global_len = sizeof(struct rkvdec_sps_pps) / 16; + + /* Set hw rps address */ + offset = offsetof(struct rkvdec_h264_priv_tbl, rps); + regs->common_addr.reg129_rps_base = priv_start_addr + offset; + + /* Set cabac table */ + offset = offsetof(struct rkvdec_h264_priv_tbl, cabac_table); + regs->common_addr.reg130_cabactbl_base = priv_start_addr + offset; + + /* Set scaling list address */ + offset = offsetof(struct rkvdec_h264_priv_tbl, scaling_list); + regs->common_addr.reg132_scanlist_addr = priv_start_addr + offset; + + rkvdec_write_regs(ctx); +} + +static int rkvdec_h264_start(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_h264_priv_tbl *priv_tbl; + struct rkvdec_h264_ctx *h264_ctx; + struct v4l2_ctrl *ctrl; + int ret; + + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_H264_SPS); + if (!ctrl) + return -EINVAL; + + ret = rkvdec_h264_validate_sps(ctx, ctrl->p_new.p_h264_sps); + if (ret) + return ret; + + h264_ctx = kzalloc(sizeof(*h264_ctx), GFP_KERNEL); + if (!h264_ctx) + return -ENOMEM; + + priv_tbl = dma_alloc_coherent(rkvdec->dev, sizeof(*priv_tbl), + &h264_ctx->priv_tbl.dma, GFP_KERNEL); + if (!priv_tbl) { + ret = -ENOMEM; + goto err_free_ctx; + } + + h264_ctx->priv_tbl.size = sizeof(*priv_tbl); + h264_ctx->priv_tbl.cpu = priv_tbl; + memcpy(priv_tbl->cabac_table, rkvdec_h264_cabac_table, + sizeof(rkvdec_h264_cabac_table)); + + ctx->priv = h264_ctx; + + return 0; + +err_free_ctx: + kfree(h264_ctx); + return ret; +} + +static void rkvdec_h264_stop(struct rkvdec_ctx *ctx) +{ + struct rkvdec_h264_ctx *h264_ctx = ctx->priv; + struct rkvdec_dev *rkvdec = ctx->dev; + + dma_free_coherent(rkvdec->dev, h264_ctx->priv_tbl.size, + h264_ctx->priv_tbl.cpu, h264_ctx->priv_tbl.dma); + kfree(h264_ctx); +} + +static int rkvdec_h264_run(struct rkvdec_ctx *ctx) +{ + struct v4l2_h264_reflist_builder reflist_builder; + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_h264_ctx *h264_ctx = ctx->priv; + struct rkvdec_h264_run run; + struct rkvdec_h264_priv_tbl *tbl = h264_ctx->priv_tbl.cpu; + u32 timeout_threshold; + + rkvdec_h264_run_preamble(ctx, &run); + + /* Build the P/B{0,1} ref lists. */ + v4l2_h264_init_reflist_builder(&reflist_builder, run.decode_params, + run.sps, run.decode_params->dpb); + v4l2_h264_build_p_ref_list(&reflist_builder, h264_ctx->reflists.p); + v4l2_h264_build_b_ref_lists(&reflist_builder, h264_ctx->reflists.b0, + h264_ctx->reflists.b1); + + assemble_hw_scaling_list(&run, &tbl->scaling_list); + assemble_hw_pps(ctx, &run); + lookup_ref_buf_idx(ctx, &run); + assemble_hw_rps(&reflist_builder, &run, &h264_ctx->reflists, &tbl->rps); + + config_registers(ctx, &run); + + rkvdec_run_postamble(ctx, &run.base); + + timeout_threshold = h264_ctx->regs.common.reg013_core_timeout_threshold; + rkvdec_schedule_watchdog(rkvdec, timeout_threshold); + + /* Start decoding! */ + writel(timeout_threshold, rkvdec->link + VDPU383_LINK_TIMEOUT_THRESHOLD); + writel(0, rkvdec->link + VDPU383_LINK_IP_ENABLE); + writel(VDPU383_DEC_E_BIT, rkvdec->link + VDPU383_LINK_DEC_ENABLE); + + return 0; +} + +static int rkvdec_h264_try_ctrl(struct rkvdec_ctx *ctx, struct v4l2_ctrl *ctrl) +{ + if (ctrl->id == V4L2_CID_STATELESS_H264_SPS) + return rkvdec_h264_validate_sps(ctx, ctrl->p_new.p_h264_sps); + + return 0; +} + +const struct rkvdec_coded_fmt_ops rkvdec_vdpu383_h264_fmt_ops = { + .adjust_fmt = rkvdec_h264_adjust_fmt, + .get_image_fmt = rkvdec_h264_get_image_fmt, + .start = rkvdec_h264_start, + .stop = rkvdec_h264_stop, + .run = rkvdec_h264_run, + .try_ctrl = rkvdec_h264_try_ctrl, +}; diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-regs.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-regs.h new file mode 100644 index 000000000000..04eb23297e37 --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-regs.h @@ -0,0 +1,281 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip Video Decoder VDPU383 driver registers description + * + * Copyright (C) 2025 Collabora, Ltd. + * Detlev Casanova + */ + +#ifndef _RKVDEC_VDPU838_REGS_H_ +#define _RKVDEC_VDPU838_REGS_H_ + +#include + +#define VDPU383_OFFSET_COMMON_REGS (8 * sizeof(u32)) +#define VDPU383_OFFSET_CODEC_PARAMS_REGS (64 * sizeof(u32)) +#define VDPU383_OFFSET_COMMON_ADDR_REGS (128 * sizeof(u32)) +#define VDPU383_OFFSET_CODEC_ADDR_REGS (168 * sizeof(u32)) +#define VDPU383_OFFSET_POC_HIGHBIT_REGS (200 * sizeof(u32)) + +#define VDPU383_MODE_HEVC 0 +#define VDPU383_MODE_H264 1 + +#define VDPU383_TIMEOUT_1080p (0xffffff) +#define VDPU383_TIMEOUT_4K (0x2cfffff) +#define VDPU383_TIMEOUT_8K (0x4ffffff) +#define VDPU383_TIMEOUT_MAX (0xffffffff) + +#define VDPU383_LINK_TIMEOUT_THRESHOLD 0x54 + +#define VDPU383_LINK_IP_ENABLE 0x58 +#define VDPU383_IP_CRU_MODE BIT(24) + +#define VDPU383_LINK_DEC_ENABLE 0x40 +#define VDPU383_DEC_E_BIT BIT(0) + +#define VDPU383_LINK_INT_EN 0x048 +#define VDPU383_INT_EN_IRQ BIT(0) +#define VDPU383_INT_EN_LINE_IRQ BIT(1) + +#define VDPU383_LINK_STA_INT 0x04c +#define VDPU383_STA_INT_DEC_RDY_STA BIT(0) +#define VDPU383_STA_INT_SOFTRESET_RDY (BIT(10) | BIT(11)) +#define VDPU383_STA_INT_ALL 0x3ff + +struct vdpu383_regs_common { + u32 reg008_dec_mode; + + struct { + u32 fbc_e : 1; + u32 tile_e : 1; + u32 reserve0 : 2; + u32 buf_empty_en : 1; + u32 scale_down_en : 1; + u32 reserve1 : 1; + u32 pix_range_det_e : 1; + u32 av1_fgs_en : 1; + u32 reserve2 : 7; + u32 line_irq_en : 1; + u32 out_cbcr_swap : 1; + u32 fbc_force_uncompress : 1; + u32 fbc_sparse_mode : 1; + u32 reserve3 : 12; + } reg009_important_en; + + struct { + u32 strmd_auto_gating_e : 1; + u32 inter_auto_gating_e : 1; + u32 intra_auto_gating_e : 1; + u32 transd_auto_gating_e : 1; + u32 recon_auto_gating_e : 1; + u32 filterd_auto_gating_e : 1; + u32 bus_auto_gating_e : 1; + u32 ctrl_auto_gating_e : 1; + u32 rcb_auto_gating_e : 1; + u32 err_prc_auto_gating_e : 1; + u32 reserve0 : 22; + } reg010_block_gating_en; + + struct { + u32 reserve0 : 9; + u32 dec_timeout_dis : 1; + u32 reserve1 : 22; + } reg011_cfg_para; + + struct { + u32 reserve0 : 7; + u32 cache_hash_mask : 25; + } reg012_cache_hash_mask; + + u32 reg013_core_timeout_threshold; + + struct { + u32 dec_line_irq_step : 16; + u32 dec_line_offset_y_st : 16; + } reg014_line_irq_ctrl; + + struct { + u32 rkvdec_frame_rdy_sta : 1; + u32 rkvdec_strm_error_sta : 1; + u32 rkvdec_core_timeout_sta : 1; + u32 rkvdec_ip_timeout_sta : 1; + u32 rkvdec_bus_error_sta : 1; + u32 rkvdec_buffer_empty_sta : 1; + u32 rkvdec_colmv_ref_error_sta : 1; + u32 rkvdec_error_spread_sta : 1; + u32 create_core_timeout_sta : 1; + u32 wlast_miss_match_sta : 1; + u32 rkvdec_core_rst_rdy_sta : 1; + u32 rkvdec_ip_rst_rdy_sta : 1; + u32 force_busidle_rdy_sta : 1; + u32 ltb_pause_rdy_sta : 1; + u32 ltb_end_flag : 1; + u32 unsupport_decmode_error_sta : 1; + u32 wmask_bits : 15; + u32 reserve0 : 1; + } reg015_irq_sta; + + struct { + u32 error_proc_disable : 1; + u32 reserve0 : 7; + u32 error_spread_disable : 1; + u32 reserve1 : 15; + u32 roi_error_ctu_cal_en : 1; + u32 reserve2 : 7; + } reg016_error_ctrl_set; + + struct { + u32 roi_x_ctu_offset_st : 12; + u32 reserve0 : 4; + u32 roi_y_ctu_offset_st : 12; + u32 reserve1 : 4; + } reg017_err_roi_ctu_offset_start; + + struct { + u32 roi_x_ctu_offset_end : 12; + u32 reserve0 : 4; + u32 roi_y_ctu_offset_end : 12; + u32 reserve1 : 4; + } reg018_err_roi_ctu_offset_end; + + struct { + u32 avs2_ref_error_field : 1; + u32 avs2_ref_error_topfield : 1; + u32 ref_error_topfield_used : 1; + u32 ref_error_botfield_used : 1; + u32 reserve0 : 28; + } reg019_error_ref_info; + + u32 reg020_cabac_error_en_lowbits; + u32 reg021_cabac_error_en_highbits; + + u32 reg022_reserved; + + struct { + u32 fill_y : 10; + u32 fill_u : 10; + u32 fill_v : 10; + u32 reserve0 : 2; + } reg023_invalid_pixel_fill; + + u32 reg024_026_reserved[3]; + + struct { + u32 reserve0 : 4; + u32 ctu_align_wr_en : 1; + u32 reserve1 : 27; + } reg027_align_en; + + struct { + u32 axi_perf_work_e : 1; + u32 reserve0 : 2; + u32 axi_cnt_type : 1; + u32 rd_latency_id : 8; + u32 reserve1 : 4; + u32 rd_latency_thr : 12; + u32 reserve2 : 4; + } reg028_debug_perf_latency_ctrl0; + + struct { + u32 addr_align_type : 2; + u32 ar_cnt_id_type : 1; + u32 aw_cnt_id_type : 1; + u32 ar_count_id : 8; + u32 reserve0 : 4; + u32 aw_count_id : 8; + u32 rd_band_width_mode : 1; + u32 reserve1 : 7; + } reg029_debug_perf_latency_ctrl1; + + struct { + u32 axi_wr_qos_level : 4; + u32 reserve0 : 4; + u32 axi_wr_qos : 4; + u32 reserve1 : 4; + u32 axi_rd_qos_level : 4; + u32 reserve2 : 4; + u32 axi_rd_qos : 4; + u32 reserve3 : 4; + } reg030_qos_ctrl; +}; + +struct vdpu383_regs_common_addr { + u32 reg128_strm_base; + u32 reg129_rps_base; + u32 reg130_cabactbl_base; + u32 reg131_gbl_base; + u32 reg132_scanlist_addr; + u32 reg133_scale_down_base; + u32 reg134_fgs_base; + u32 reg135_139_reserved[5]; + + struct rcb_info { + u32 offset; + u32 size; + } reg140_162_rcb_info[11]; +}; + +struct vdpu383_regs_h26x_addr { + u32 reg168_decout_base; + u32 reg169_error_ref_base; + u32 reg170_185_ref_base[16]; + u32 reg186_191_reserved[6]; + u32 reg192_payload_st_cur_base; + u32 reg193_fbc_payload_offset; + u32 reg194_payload_st_error_ref_base; + u32 reg195_210_payload_st_ref_base[16]; + u32 reg211_215_reserved[5]; + u32 reg216_colmv_cur_base; + u32 reg217_232_colmv_ref_base[16]; +}; + +struct vdpu383_regs_h26x_params { + u32 reg064_start_decoder; + u32 reg065_strm_start_bit; + u32 reg066_stream_len; + u32 reg067_global_len; + u32 reg068_hor_virstride; + u32 reg069_raster_uv_hor_virstride; + u32 reg070_y_virstride; + u32 reg071_scl_ref_hor_virstride; + u32 reg072_scl_ref_raster_uv_hor_virstride; + u32 reg073_scl_ref_virstride; + u32 reg074_fgs_ref_hor_virstride; + u32 reg075_079_reserved[5]; + u32 reg080_error_ref_hor_virstride; + u32 reg081_error_ref_raster_uv_hor_virstride; + u32 reg082_error_ref_virstride; + u32 reg083_ref0_hor_virstride; + u32 reg084_ref0_raster_uv_hor_virstride; + u32 reg085_ref0_virstride; + u32 reg086_ref1_hor_virstride; + u32 reg087_ref1_raster_uv_hor_virstride; + u32 reg088_ref1_virstride; + u32 reg089_ref2_hor_virstride; + u32 reg090_ref2_raster_uv_hor_virstride; + u32 reg091_ref2_virstride; + u32 reg092_ref3_hor_virstride; + u32 reg093_ref3_raster_uv_hor_virstride; + u32 reg094_ref3_virstride; + u32 reg095_ref4_hor_virstride; + u32 reg096_ref4_raster_uv_hor_virstride; + u32 reg097_ref4_virstride; + u32 reg098_ref5_hor_virstride; + u32 reg099_ref5_raster_uv_hor_virstride; + u32 reg100_ref5_virstride; + u32 reg101_ref6_hor_virstride; + u32 reg102_ref6_raster_uv_hor_virstride; + u32 reg103_ref6_virstride; + u32 reg104_ref7_hor_virstride; + u32 reg105_ref7_raster_uv_hor_virstride; + u32 reg106_ref7_virstride; +}; + +struct vdpu383_regs_h26x { + struct vdpu383_regs_common common; /* 8-30 */ + struct vdpu383_regs_h26x_params h26x_params; /* 64-74, 80-106 */ + struct vdpu383_regs_common_addr common_addr; /* 128-134, 140-161 */ + struct vdpu383_regs_h26x_addr h26x_addr; /* 168-185, 192-210, 216-232 */ +} __packed; + +#endif /* __RKVDEC_VDPU838_REGS_H__ */ diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c index da192c866e2e..c988fae85843 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c @@ -9,6 +9,7 @@ * Copyright (C) 2011 Samsung Electronics Co., Ltd. */ +#include #include #include #include @@ -30,6 +31,7 @@ #include "rkvdec.h" #include "rkvdec-regs.h" #include "rkvdec-vdpu381-regs.h" +#include "rkvdec-vdpu383-regs.h" #include "rkvdec-rcb.h" static bool rkvdec_image_fmt_match(enum rkvdec_image_fmt fmt1, @@ -86,17 +88,26 @@ static bool rkvdec_is_valid_fmt(struct rkvdec_ctx *ctx, u32 fourcc, return false; } +static u32 rkvdec_colmv_size(u16 width, u16 height) +{ + return 128 * DIV_ROUND_UP(width, 16) * DIV_ROUND_UP(height, 16); +} + +static u32 vdpu383_colmv_size(u16 width, u16 height) +{ + return ALIGN(width, 64) * ALIGN(height, 16); +} + static void rkvdec_fill_decoded_pixfmt(struct rkvdec_ctx *ctx, struct v4l2_pix_format_mplane *pix_mp) { - v4l2_fill_pixfmt_mp(pix_mp, pix_mp->pixelformat, - pix_mp->width, pix_mp->height); + const struct rkvdec_variant *variant = ctx->dev->variant; + + v4l2_fill_pixfmt_mp(pix_mp, pix_mp->pixelformat, pix_mp->width, pix_mp->height); ctx->colmv_offset = pix_mp->plane_fmt[0].sizeimage; - pix_mp->plane_fmt[0].sizeimage += 128 * - DIV_ROUND_UP(pix_mp->width, 16) * - DIV_ROUND_UP(pix_mp->height, 16); + pix_mp->plane_fmt[0].sizeimage += variant->ops->colmv_size(pix_mp->width, pix_mp->height); } static void rkvdec_reset_fmt(struct rkvdec_ctx *ctx, struct v4l2_format *f, @@ -452,6 +463,25 @@ static const struct rkvdec_coded_fmt_desc vdpu381_coded_fmts[] = { }, }; +static const struct rkvdec_coded_fmt_desc vdpu383_coded_fmts[] = { + { + .fourcc = V4L2_PIX_FMT_H264_SLICE, + .frmsize = { + .min_width = 64, + .max_width = 65520, + .step_width = 64, + .min_height = 64, + .max_height = 65520, + .step_height = 16, + }, + .ctrls = &vdpu38x_h264_ctrls, + .ops = &rkvdec_vdpu383_h264_fmt_ops, + .num_decoded_fmts = ARRAY_SIZE(rkvdec_h264_decoded_fmts), + .decoded_fmts = rkvdec_h264_decoded_fmts, + .subsystem_flags = VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF, + }, +}; + static const struct rkvdec_coded_fmt_desc * rkvdec_enum_coded_fmt_desc(struct rkvdec_ctx *ctx, int index) { @@ -1358,6 +1388,35 @@ static irqreturn_t vdpu381_irq_handler(struct rkvdec_ctx *ctx) return IRQ_HANDLED; } +static irqreturn_t vdpu383_irq_handler(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + enum vb2_buffer_state state; + bool need_reset = 0; + u32 status; + + status = readl(rkvdec->link + VDPU383_LINK_STA_INT); + writel(FIELD_PREP_WM16(VDPU383_STA_INT_ALL, 0), rkvdec->link + VDPU383_LINK_STA_INT); + /* On vdpu383, the interrupts must be disabled */ + writel(FIELD_PREP_WM16(VDPU383_INT_EN_IRQ | VDPU383_INT_EN_LINE_IRQ, 0), + rkvdec->link + VDPU383_LINK_INT_EN); + + if (status & VDPU383_STA_INT_DEC_RDY_STA) { + state = VB2_BUF_STATE_DONE; + } else { + state = VB2_BUF_STATE_ERROR; + rkvdec_iommu_restore(rkvdec); + } + + if (need_reset) + rkvdec_iommu_restore(rkvdec); + + if (cancel_delayed_work(&rkvdec->watchdog_work)) + rkvdec_job_finish(ctx, state); + + return IRQ_HANDLED; +} + static irqreturn_t rkvdec_irq_handler(int irq, void *priv) { struct rkvdec_dev *rkvdec = priv; @@ -1427,6 +1486,7 @@ static int rkvdec_disable_multicore(struct rkvdec_dev *rkvdec) static const struct rkvdec_variant_ops rk3399_variant_ops = { .irq_handler = rk3399_irq_handler, + .colmv_size = rkvdec_colmv_size, }; static const struct rkvdec_variant rk3288_rkvdec_variant = { @@ -1469,6 +1529,7 @@ static const struct rcb_size_info vdpu381_rcb_sizes[] = { static const struct rkvdec_variant_ops vdpu381_variant_ops = { .irq_handler = vdpu381_irq_handler, + .colmv_size = rkvdec_colmv_size, }; static const struct rkvdec_variant vdpu381_variant = { @@ -1479,6 +1540,32 @@ static const struct rkvdec_variant vdpu381_variant = { .ops = &vdpu381_variant_ops, }; +static const struct rcb_size_info vdpu383_rcb_sizes[] = { + {6, PIC_WIDTH}, // streamd + {6, PIC_WIDTH}, // streamd_tile + {12, PIC_WIDTH}, // inter + {12, PIC_WIDTH}, // inter_tile + {16, PIC_WIDTH}, // intra + {10, PIC_WIDTH}, // intra_tile + {120, PIC_WIDTH}, // filterd + {120, PIC_WIDTH}, // filterd_protect + {120, PIC_WIDTH}, // filterd_tile_row + {180, PIC_HEIGHT}, // filterd_tile_col +}; + +static const struct rkvdec_variant_ops vdpu383_variant_ops = { + .irq_handler = vdpu383_irq_handler, + .colmv_size = vdpu383_colmv_size, +}; + +static const struct rkvdec_variant vdpu383_variant = { + .coded_fmts = vdpu383_coded_fmts, + .num_coded_fmts = ARRAY_SIZE(vdpu383_coded_fmts), + .rcb_sizes = vdpu383_rcb_sizes, + .num_rcb_sizes = ARRAY_SIZE(vdpu383_rcb_sizes), + .ops = &vdpu383_variant_ops, +}; + static const struct of_device_id of_rkvdec_match[] = { { .compatible = "rockchip,rk3288-vdec", @@ -1496,6 +1583,10 @@ static const struct of_device_id of_rkvdec_match[] = { .compatible = "rockchip,rk3588-vdec", .data = &vdpu381_variant, }, + { + .compatible = "rockchip,rk3576-vdec", + .data = &vdpu383_variant, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, of_rkvdec_match); @@ -1539,6 +1630,10 @@ static int rkvdec_probe(struct platform_device *pdev) rkvdec->regs = devm_platform_ioremap_resource_byname(pdev, "function"); if (IS_ERR(rkvdec->regs)) return PTR_ERR(rkvdec->regs); + + rkvdec->link = devm_platform_ioremap_resource_byname(pdev, "link"); + if (IS_ERR(rkvdec->link)) + return PTR_ERR(rkvdec->link); } ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h index 4afa9cd690d5..4e9cd12fba3b 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.h @@ -73,6 +73,7 @@ vb2_to_rkvdec_decoded_buf(struct vb2_buffer *buf) struct rkvdec_variant_ops { irqreturn_t (*irq_handler)(struct rkvdec_ctx *ctx); + u32 (*colmv_size)(u16 width, u16 height); }; struct rkvdec_variant { @@ -133,6 +134,7 @@ struct rkvdec_dev { unsigned int num_clocks; struct clk *axi_clk; void __iomem *regs; + void __iomem *link; struct mutex vdev_lock; /* serializes ioctls */ struct delayed_work watchdog_work; struct gen_pool *sram_pool; @@ -186,4 +188,7 @@ extern const struct rkvdec_coded_fmt_ops rkvdec_vp9_fmt_ops; /* VDPU381 ops */ extern const struct rkvdec_coded_fmt_ops rkvdec_vdpu381_h264_fmt_ops; +/* VDPU383 ops */ +extern const struct rkvdec_coded_fmt_ops rkvdec_vdpu383_h264_fmt_ops; + #endif /* RKVDEC_H_ */ From c9a59dc2acc72789d5c778af080d1e65af84862c Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:32 -0500 Subject: [PATCH 302/307] media: rkvdec: Add HEVC support for the VDPU381 variant The VDPU381 supports HEVC decoding up to 7680x4320@30fps. It could double that when using both decoder cores. It support YUV420 (8 and 10 bits) as well as AFBC (not implemented here) The fluster score is 146/147 for JCT-VC-HEVC_V1, tested on ROCK 5B. None of the other test suites works. Tested-by: Diederik de Haas # Rock 5B Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/Makefile | 1 + .../rockchip/rkvdec/rkvdec-hevc-common.c | 335 +++++++++ .../rockchip/rkvdec/rkvdec-hevc-common.h | 59 ++ .../rockchip/rkvdec/rkvdec-vdpu381-hevc.c | 640 ++++++++++++++++++ .../media/platform/rockchip/rkvdec/rkvdec.c | 82 +++ .../media/platform/rockchip/rkvdec/rkvdec.h | 3 + 6 files changed, 1120 insertions(+) create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c diff --git a/drivers/media/platform/rockchip/rkvdec/Makefile b/drivers/media/platform/rockchip/rkvdec/Makefile index a58d4aede2fe..e30fdd7d51c3 100644 --- a/drivers/media/platform/rockchip/rkvdec/Makefile +++ b/drivers/media/platform/rockchip/rkvdec/Makefile @@ -9,5 +9,6 @@ rockchip-vdec-y += \ rkvdec-hevc-common.o \ rkvdec-rcb.o \ rkvdec-vdpu381-h264.o \ + rkvdec-vdpu381-hevc.o \ rkvdec-vdpu383-h264.o \ rkvdec-vp9.o diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c index f13903ffbda8..f6d7610772ee 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c @@ -21,6 +21,125 @@ #include "rkvdec.h" #include "rkvdec-hevc-common.h" +/* Store the Short term ref pic set calculated values */ +struct calculated_rps_st_set { + u8 num_delta_pocs; + u8 num_negative_pics; + u8 num_positive_pics; + u8 used_by_curr_pic_s0[16]; + u8 used_by_curr_pic_s1[16]; + s32 delta_poc_s0[16]; + s32 delta_poc_s1[16]; +}; + +void compute_tiles_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size, + u16 width, u16 height, s32 pic_in_cts_width, + s32 pic_in_cts_height, u16 *column_width, u16 *row_height) +{ + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + int i; + + for (i = 0; i < pps->num_tile_columns_minus1 + 1; i++) + column_width[i] = ((i + 1) * pic_in_cts_width) / + (pps->num_tile_columns_minus1 + 1) - + (i * pic_in_cts_width) / + (pps->num_tile_columns_minus1 + 1); + + for (i = 0; i < pps->num_tile_rows_minus1 + 1; i++) + row_height[i] = ((i + 1) * pic_in_cts_height) / + (pps->num_tile_rows_minus1 + 1) - + (i * pic_in_cts_height) / + (pps->num_tile_rows_minus1 + 1); +} + +void compute_tiles_non_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size, + u16 width, u16 height, s32 pic_in_cts_width, + s32 pic_in_cts_height, u16 *column_width, u16 *row_height) +{ + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + s32 sum = 0; + int i; + + for (i = 0; i < pps->num_tile_columns_minus1; i++) { + column_width[i] = pps->column_width_minus1[i] + 1; + sum += column_width[i]; + } + column_width[i] = pic_in_cts_width - sum; + + sum = 0; + for (i = 0; i < pps->num_tile_rows_minus1; i++) { + row_height[i] = pps->row_height_minus1[i] + 1; + sum += row_height[i]; + } + row_height[i] = pic_in_cts_height - sum; +} + +static void set_ref_poc(struct rkvdec_rps_short_term_ref_set *set, int poc, int value, int flag) +{ + switch (poc) { + case 0: + set->delta_poc0 = value; + set->used_flag0 = flag; + break; + case 1: + set->delta_poc1 = value; + set->used_flag1 = flag; + break; + case 2: + set->delta_poc2 = value; + set->used_flag2 = flag; + break; + case 3: + set->delta_poc3 = value; + set->used_flag3 = flag; + break; + case 4: + set->delta_poc4 = value; + set->used_flag4 = flag; + break; + case 5: + set->delta_poc5 = value; + set->used_flag5 = flag; + break; + case 6: + set->delta_poc6 = value; + set->used_flag6 = flag; + break; + case 7: + set->delta_poc7 = value; + set->used_flag7 = flag; + break; + case 8: + set->delta_poc8 = value; + set->used_flag8 = flag; + break; + case 9: + set->delta_poc9 = value; + set->used_flag9 = flag; + break; + case 10: + set->delta_poc10 = value; + set->used_flag10 = flag; + break; + case 11: + set->delta_poc11 = value; + set->used_flag11 = flag; + break; + case 12: + set->delta_poc12 = value; + set->used_flag12 = flag; + break; + case 13: + set->delta_poc13 = value; + set->used_flag13 = flag; + break; + case 14: + set->delta_poc14 = value; + set->used_flag14 = flag; + break; + } +} + /* * Flip one or more matrices along their main diagonal and flatten them * before writing it to the memory. @@ -120,6 +239,211 @@ void rkvdec_hevc_assemble_hw_scaling_list(struct rkvdec_hevc_run *run, sizeof(struct v4l2_ctrl_hevc_scaling_matrix)); } +static void rkvdec_hevc_assemble_hw_lt_rps(struct rkvdec_hevc_run *run, struct rkvdec_rps *rps) +{ + const struct v4l2_ctrl_hevc_sps *sps = run->sps; + + if (!run->ext_sps_lt_rps) + return; + + for (int i = 0; i < sps->num_long_term_ref_pics_sps; i++) { + rps->refs[i].lt_ref_pic_poc_lsb = + run->ext_sps_lt_rps[i].lt_ref_pic_poc_lsb_sps; + rps->refs[i].used_by_curr_pic_lt_flag = + !!(run->ext_sps_lt_rps[i].flags & V4L2_HEVC_EXT_SPS_LT_RPS_FLAG_USED_LT); + } +} + +static void rkvdec_hevc_assemble_hw_st_rps(struct rkvdec_hevc_run *run, struct rkvdec_rps *rps, + struct calculated_rps_st_set *calculated_rps_st_sets) +{ + const struct v4l2_ctrl_hevc_sps *sps = run->sps; + + for (int i = 0; i < sps->num_short_term_ref_pic_sets; i++) { + int poc = 0; + int j = 0; + const struct calculated_rps_st_set *set = &calculated_rps_st_sets[i]; + + rps->short_term_ref_sets[i].num_negative = set->num_negative_pics; + rps->short_term_ref_sets[i].num_positive = set->num_positive_pics; + + for (; j < set->num_negative_pics; j++) { + set_ref_poc(&rps->short_term_ref_sets[i], j, + set->delta_poc_s0[j], set->used_by_curr_pic_s0[j]); + } + poc = j; + + for (j = 0; j < set->num_positive_pics; j++) { + set_ref_poc(&rps->short_term_ref_sets[i], poc + j, + set->delta_poc_s1[j], set->used_by_curr_pic_s1[j]); + } + } +} + +/* + * Compute the short term ref pic set parameters based on its reference short term ref pic + */ +static void st_ref_pic_set_prediction(struct rkvdec_hevc_run *run, int idx, + struct calculated_rps_st_set *calculated_rps_st_sets) +{ + const struct v4l2_ctrl_hevc_ext_sps_st_rps *rps_data = &run->ext_sps_st_rps[idx]; + struct calculated_rps_st_set *st_rps = &calculated_rps_st_sets[idx]; + struct calculated_rps_st_set *ref_rps; + u8 st_rps_idx = idx; + u8 ref_rps_idx = 0; + s16 delta_rps = 0; + u8 use_delta_flag[16] = { 0 }; + u8 used_by_curr_pic_flag[16] = { 0 }; + int i, j; + int dPoc; + + ref_rps_idx = st_rps_idx - (rps_data->delta_idx_minus1 + 1); /* 7-59 */ + delta_rps = (1 - 2 * rps_data->delta_rps_sign) * + (rps_data->abs_delta_rps_minus1 + 1); /* 7-60 */ + + ref_rps = &calculated_rps_st_sets[ref_rps_idx]; + + for (j = 0; j <= ref_rps->num_delta_pocs; j++) { + used_by_curr_pic_flag[j] = !!(rps_data->used_by_curr_pic & (1 << j)); + use_delta_flag[j] = !!(rps_data->use_delta_flag & (1 << j)); + } + + /* 7-61: calculate num_negative_pics, delta_poc_s0 and used_by_curr_pic_s0 */ + i = 0; + for (j = (ref_rps->num_positive_pics - 1); j >= 0; j--) { + dPoc = ref_rps->delta_poc_s1[j] + delta_rps; + if (dPoc < 0 && use_delta_flag[ref_rps->num_negative_pics + j]) { + st_rps->delta_poc_s0[i] = dPoc; + st_rps->used_by_curr_pic_s0[i++] = + used_by_curr_pic_flag[ref_rps->num_negative_pics + j]; + } + } + if (delta_rps < 0 && use_delta_flag[ref_rps->num_delta_pocs]) { + st_rps->delta_poc_s0[i] = delta_rps; + st_rps->used_by_curr_pic_s0[i++] = used_by_curr_pic_flag[ref_rps->num_delta_pocs]; + } + for (j = 0; j < ref_rps->num_negative_pics; j++) { + dPoc = ref_rps->delta_poc_s0[j] + delta_rps; + if (dPoc < 0 && use_delta_flag[j]) { + st_rps->delta_poc_s0[i] = dPoc; + st_rps->used_by_curr_pic_s0[i++] = used_by_curr_pic_flag[j]; + } + } + st_rps->num_negative_pics = i; + + /* 7-62: calculate num_positive_pics, delta_poc_s1 and used_by_curr_pic_s1 */ + i = 0; + for (j = (ref_rps->num_negative_pics - 1); j >= 0; j--) { + dPoc = ref_rps->delta_poc_s0[j] + delta_rps; + if (dPoc > 0 && use_delta_flag[j]) { + st_rps->delta_poc_s1[i] = dPoc; + st_rps->used_by_curr_pic_s1[i++] = used_by_curr_pic_flag[j]; + } + } + if (delta_rps > 0 && use_delta_flag[ref_rps->num_delta_pocs]) { + st_rps->delta_poc_s1[i] = delta_rps; + st_rps->used_by_curr_pic_s1[i++] = used_by_curr_pic_flag[ref_rps->num_delta_pocs]; + } + for (j = 0; j < ref_rps->num_positive_pics; j++) { + dPoc = ref_rps->delta_poc_s1[j] + delta_rps; + if (dPoc > 0 && use_delta_flag[ref_rps->num_negative_pics + j]) { + st_rps->delta_poc_s1[i] = dPoc; + st_rps->used_by_curr_pic_s1[i++] = + used_by_curr_pic_flag[ref_rps->num_negative_pics + j]; + } + } + st_rps->num_positive_pics = i; + + st_rps->num_delta_pocs = st_rps->num_positive_pics + st_rps->num_negative_pics; +} + +/* + * Compute the short term ref pic set parameters based on the control's data. + */ +static void st_ref_pic_set_calculate(struct rkvdec_hevc_run *run, int idx, + struct calculated_rps_st_set *calculated_rps_st_sets) +{ + const struct v4l2_ctrl_hevc_ext_sps_st_rps *rps_data = &run->ext_sps_st_rps[idx]; + struct calculated_rps_st_set *st_rps = &calculated_rps_st_sets[idx]; + int j, i = 0; + + /* 7-63 */ + st_rps->num_negative_pics = rps_data->num_negative_pics; + /* 7-64 */ + st_rps->num_positive_pics = rps_data->num_positive_pics; + + for (i = 0; i < st_rps->num_negative_pics; i++) { + /* 7-65 */ + st_rps->used_by_curr_pic_s0[i] = !!(rps_data->used_by_curr_pic & (1 << i)); + + if (i == 0) { + /* 7-67 */ + st_rps->delta_poc_s0[i] = -(rps_data->delta_poc_s0_minus1[i] + 1); + } else { + /* 7-69 */ + st_rps->delta_poc_s0[i] = + st_rps->delta_poc_s0[i - 1] - + (rps_data->delta_poc_s0_minus1[i] + 1); + } + } + + for (j = 0; j < st_rps->num_positive_pics; j++) { + /* 7-66 */ + st_rps->used_by_curr_pic_s1[j] = !!(rps_data->used_by_curr_pic & (1 << (i + j))); + + if (j == 0) { + /* 7-68 */ + st_rps->delta_poc_s1[j] = rps_data->delta_poc_s1_minus1[j] + 1; + } else { + /* 7-70 */ + st_rps->delta_poc_s1[j] = + st_rps->delta_poc_s1[j - 1] + + (rps_data->delta_poc_s1_minus1[j] + 1); + } + } + + /* 7-71 */ + st_rps->num_delta_pocs = st_rps->num_positive_pics + st_rps->num_negative_pics; +} + +static void rkvdec_hevc_prepare_hw_st_rps(struct rkvdec_hevc_run *run, struct rkvdec_rps *rps, + struct v4l2_ctrl_hevc_ext_sps_st_rps *cache) +{ + int idx; + + if (!run->ext_sps_st_rps) + return; + + if (!memcmp(cache, run->ext_sps_st_rps, sizeof(struct v4l2_ctrl_hevc_ext_sps_st_rps))) + return; + + struct calculated_rps_st_set *calculated_rps_st_sets = + kzalloc(sizeof(struct calculated_rps_st_set) * + run->sps->num_short_term_ref_pic_sets, GFP_KERNEL); + + for (idx = 0; idx < run->sps->num_short_term_ref_pic_sets; idx++) { + const struct v4l2_ctrl_hevc_ext_sps_st_rps *rps_data = &run->ext_sps_st_rps[idx]; + + if (rps_data->flags & V4L2_HEVC_EXT_SPS_ST_RPS_FLAG_INTER_REF_PIC_SET_PRED) + st_ref_pic_set_prediction(run, idx, calculated_rps_st_sets); + else + st_ref_pic_set_calculate(run, idx, calculated_rps_st_sets); + } + + rkvdec_hevc_assemble_hw_st_rps(run, rps, calculated_rps_st_sets); + + kfree(calculated_rps_st_sets); + + memcpy(cache, run->ext_sps_st_rps, sizeof(struct v4l2_ctrl_hevc_ext_sps_st_rps)); +} + +void rkvdec_hevc_assemble_hw_rps(struct rkvdec_hevc_run *run, struct rkvdec_rps *rps, + struct v4l2_ctrl_hevc_ext_sps_st_rps *st_cache) +{ + rkvdec_hevc_prepare_hw_st_rps(run, rps, st_cache); + rkvdec_hevc_assemble_hw_lt_rps(run, rps); +} + struct vb2_buffer * get_ref_buf(struct rkvdec_ctx *ctx, struct rkvdec_hevc_run *run, unsigned int dpb_idx) @@ -201,5 +525,16 @@ void rkvdec_hevc_run_preamble(struct rkvdec_ctx *ctx, V4L2_CID_STATELESS_HEVC_SCALING_MATRIX); run->scaling_matrix = ctrl ? ctrl->p_cur.p : NULL; + if (ctx->has_sps_st_rps) { + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS); + run->ext_sps_st_rps = ctrl ? ctrl->p_cur.p : NULL; + } + if (ctx->has_sps_lt_rps) { + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS); + run->ext_sps_lt_rps = ctrl ? ctrl->p_cur.p : NULL; + } + rkvdec_run_preamble(ctx, &run->base); } diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h index 564b2ad72233..ce413454317a 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h @@ -16,9 +16,58 @@ */ #include +#include #include "rkvdec.h" +struct rkvdec_rps_refs { + u16 lt_ref_pic_poc_lsb; + u16 used_by_curr_pic_lt_flag : 1; + u16 reserved : 15; +} __packed; + +struct rkvdec_rps_short_term_ref_set { + u32 num_negative : 4; + u32 num_positive : 4; + u32 delta_poc0 : 16; + u32 used_flag0 : 1; + u32 delta_poc1 : 16; + u32 used_flag1 : 1; + u32 delta_poc2 : 16; + u32 used_flag2 : 1; + u32 delta_poc3 : 16; + u32 used_flag3 : 1; + u32 delta_poc4 : 16; + u32 used_flag4 : 1; + u32 delta_poc5 : 16; + u32 used_flag5 : 1; + u32 delta_poc6 : 16; + u32 used_flag6 : 1; + u32 delta_poc7 : 16; + u32 used_flag7 : 1; + u32 delta_poc8 : 16; + u32 used_flag8 : 1; + u32 delta_poc9 : 16; + u32 used_flag9 : 1; + u32 delta_poc10 : 16; + u32 used_flag10 : 1; + u32 delta_poc11 : 16; + u32 used_flag11 : 1; + u32 delta_poc12 : 16; + u32 used_flag12 : 1; + u32 delta_poc13 : 16; + u32 used_flag13 : 1; + u32 delta_poc14 : 16; + u32 used_flag14 : 1; + u32 reserved_bits : 25; + u32 reserved[3]; +} __packed; + +struct rkvdec_rps { + struct rkvdec_rps_refs refs[32]; + struct rkvdec_rps_short_term_ref_set short_term_ref_sets[64]; +} __packed; + struct rkvdec_hevc_run { struct rkvdec_run base; const struct v4l2_ctrl_hevc_slice_params *slices_params; @@ -26,6 +75,8 @@ struct rkvdec_hevc_run { const struct v4l2_ctrl_hevc_sps *sps; const struct v4l2_ctrl_hevc_pps *pps; const struct v4l2_ctrl_hevc_scaling_matrix *scaling_matrix; + const struct v4l2_ctrl_hevc_ext_sps_st_rps *ext_sps_st_rps; + const struct v4l2_ctrl_hevc_ext_sps_lt_rps *ext_sps_lt_rps; int num_slices; }; @@ -36,6 +87,14 @@ struct scaling_factor { u8 reserved[4]; /*16Bytes align*/ }; +void compute_tiles_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size, + u16 width, u16 height, s32 pic_in_cts_width, + s32 pic_in_cts_height, u16 *column_width, u16 *row_height); +void compute_tiles_non_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size, + u16 width, u16 height, s32 pic_in_cts_width, + s32 pic_in_cts_height, u16 *column_width, u16 *row_height); +void rkvdec_hevc_assemble_hw_rps(struct rkvdec_hevc_run *run, struct rkvdec_rps *rps, + struct v4l2_ctrl_hevc_ext_sps_st_rps *st_cache); void rkvdec_hevc_assemble_hw_scaling_list(struct rkvdec_hevc_run *run, struct scaling_factor *scaling_factor, struct v4l2_ctrl_hevc_scaling_matrix *cache); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c new file mode 100644 index 000000000000..32c79e948f8f --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c @@ -0,0 +1,640 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip VDPU381 HEVC backend + * + * Copyright (C) 2025 Collabora, Ltd. + * Detlev Casanova + */ + +#include + +#include "rkvdec.h" +#include "rkvdec-cabac.h" +#include "rkvdec-rcb.h" +#include "rkvdec-hevc-common.h" +#include "rkvdec-vdpu381-regs.h" + +// SPS +struct rkvdec_hevc_sps { + u16 video_parameters_set_id : 4; + u16 seq_parameters_set_id_sps : 4; + u16 chroma_format_idc : 2; + u16 width : 16; + u16 height : 16; + u16 bit_depth_luma : 4; + u16 bit_depth_chroma : 4; + u16 max_pic_order_count_lsb : 5; + u16 diff_max_min_luma_coding_block_size : 2; + u16 min_luma_coding_block_size : 3; + u16 min_transform_block_size : 3; + u16 diff_max_min_transform_block_size : 2; + u16 max_transform_hierarchy_depth_inter : 3; + u16 max_transform_hierarchy_depth_intra : 3; + u16 scaling_list_enabled_flag : 1; + u16 amp_enabled_flag : 1; + u16 sample_adaptive_offset_enabled_flag : 1; + u16 pcm_enabled_flag : 1; + u16 pcm_sample_bit_depth_luma : 4; + u16 pcm_sample_bit_depth_chroma : 4; + u16 pcm_loop_filter_disabled_flag : 1; + u16 diff_max_min_pcm_luma_coding_block_size : 3; + u16 min_pcm_luma_coding_block_size : 3; + u16 num_short_term_ref_pic_sets : 7; + u16 long_term_ref_pics_present_flag : 1; + u16 num_long_term_ref_pics_sps : 6; + u16 sps_temporal_mvp_enabled_flag : 1; + u16 strong_intra_smoothing_enabled_flag : 1; + u16 reserved_0 : 7; + u16 sps_max_dec_pic_buffering_minus1 : 4; + u16 reserved_0_2 : 3; + u16 reserved_f : 8; +} __packed; + +//PPS +struct rkvdec_hevc_pps { + u16 picture_parameters_set_id : 6; + u16 seq_parameters_set_id_pps : 4; + u16 dependent_slice_segments_enabled_flag : 1; + u16 output_flag_present_flag : 1; + u16 num_extra_slice_header_bits : 13; + u16 sign_data_hiding_enabled_flag : 1; + u16 cabac_init_present_flag : 1; + u16 num_ref_idx_l0_default_active : 4; + u16 num_ref_idx_l1_default_active : 4; + u16 init_qp_minus26 : 7; + u16 constrained_intra_pred_flag : 1; + u16 transform_skip_enabled_flag : 1; + u16 cu_qp_delta_enabled_flag : 1; + u16 log2_min_cb_size : 3; + u16 pps_cb_qp_offset : 5; + u16 pps_cr_qp_offset : 5; + u16 pps_slice_chroma_qp_offsets_present_flag : 1; + u16 weighted_pred_flag : 1; + u16 weighted_bipred_flag : 1; + u16 transquant_bypass_enabled_flag : 1; + u16 tiles_enabled_flag : 1; + u16 entropy_coding_sync_enabled_flag : 1; + u16 pps_loop_filter_across_slices_enabled_flag : 1; + u16 loop_filter_across_tiles_enabled_flag : 1; + u16 deblocking_filter_override_enabled_flag : 1; + u16 pps_deblocking_filter_disabled_flag : 1; + u16 pps_beta_offset_div2 : 4; + u16 pps_tc_offset_div2 : 4; + u16 lists_modification_present_flag : 1; + u16 log2_parallel_merge_level : 3; + u16 slice_segment_header_extension_present_flag : 1; + u16 zeroes : 3; + u16 num_tile_columns : 5; + u16 num_tile_rows : 5; + u16 sps_pps_mode : 4; + u16 reserved_bits : 14; + u16 reserved; +} __packed; + +struct rkvdec_hevc_tile { + u16 value0 : 12; + u16 value1 : 12; +} __packed; + +struct rkvdec_sps_pps_packet { + struct rkvdec_hevc_sps sps; + struct rkvdec_hevc_pps pps; + struct rkvdec_hevc_tile column_width[10]; + struct rkvdec_hevc_tile row_height[11]; + u32 zeroes[3]; + u32 zeroes_bits : 6; + u32 padding_bits : 2; + u32 padding; +} __packed; + +struct rkvdec_hevc_priv_tbl { + struct rkvdec_sps_pps_packet param_set[64]; + struct rkvdec_rps rps; + struct scaling_factor scaling_list; + u8 cabac_table[27456]; +}; + +struct rkvdec_hevc_ctx { + struct rkvdec_aux_buf priv_tbl; + struct v4l2_ctrl_hevc_scaling_matrix scaling_matrix_cache; + struct v4l2_ctrl_hevc_ext_sps_st_rps st_cache; + struct rkvdec_vdpu381_regs_hevc regs; +}; + +static void assemble_hw_pps(struct rkvdec_ctx *ctx, + struct rkvdec_hevc_run *run) +{ + struct rkvdec_hevc_ctx *hevc_ctx = ctx->priv; + const struct v4l2_ctrl_hevc_sps *sps = run->sps; + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + struct rkvdec_hevc_priv_tbl *priv_tbl = hevc_ctx->priv_tbl.cpu; + struct rkvdec_sps_pps_packet *hw_ps; + bool tiles_enabled; + s32 max_cu_width; + s32 pic_in_cts_width; + s32 pic_in_cts_height; + u16 log2_min_cb_size, width, height; + u16 column_width[20]; + u16 row_height[22]; + u8 pcm_enabled; + u32 i; + + /* + * HW read the SPS/PPS information from PPS packet index by PPS id. + * offset from the base can be calculated by PPS_id * 32 (size per PPS + * packet unit). so the driver copy SPS/PPS information to the exact PPS + * packet unit for HW accessing. + */ + hw_ps = &priv_tbl->param_set[pps->pic_parameter_set_id]; + memset(hw_ps, 0, sizeof(*hw_ps)); + + /* write sps */ + hw_ps->sps.video_parameters_set_id = sps->video_parameter_set_id; + hw_ps->sps.seq_parameters_set_id_sps = sps->seq_parameter_set_id; + hw_ps->sps.chroma_format_idc = sps->chroma_format_idc; + + log2_min_cb_size = sps->log2_min_luma_coding_block_size_minus3 + 3; + width = sps->pic_width_in_luma_samples; + height = sps->pic_height_in_luma_samples; + hw_ps->sps.width = width; + hw_ps->sps.height = height; + hw_ps->sps.bit_depth_luma = sps->bit_depth_luma_minus8 + 8; + hw_ps->sps.bit_depth_chroma = sps->bit_depth_chroma_minus8 + 8; + hw_ps->sps.max_pic_order_count_lsb = sps->log2_max_pic_order_cnt_lsb_minus4 + 4; + hw_ps->sps.diff_max_min_luma_coding_block_size = + sps->log2_diff_max_min_luma_coding_block_size; + hw_ps->sps.min_luma_coding_block_size = sps->log2_min_luma_coding_block_size_minus3 + 3; + hw_ps->sps.min_transform_block_size = sps->log2_min_luma_transform_block_size_minus2 + 2; + hw_ps->sps.diff_max_min_transform_block_size = + sps->log2_diff_max_min_luma_transform_block_size; + hw_ps->sps.max_transform_hierarchy_depth_inter = sps->max_transform_hierarchy_depth_inter; + hw_ps->sps.max_transform_hierarchy_depth_intra = sps->max_transform_hierarchy_depth_intra; + hw_ps->sps.scaling_list_enabled_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED); + hw_ps->sps.amp_enabled_flag = !!(sps->flags & V4L2_HEVC_SPS_FLAG_AMP_ENABLED); + hw_ps->sps.sample_adaptive_offset_enabled_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_SAMPLE_ADAPTIVE_OFFSET); + + pcm_enabled = !!(sps->flags & V4L2_HEVC_SPS_FLAG_PCM_ENABLED); + hw_ps->sps.pcm_enabled_flag = pcm_enabled; + hw_ps->sps.pcm_sample_bit_depth_luma = + pcm_enabled ? sps->pcm_sample_bit_depth_luma_minus1 + 1 : 0; + hw_ps->sps.pcm_sample_bit_depth_chroma = + pcm_enabled ? sps->pcm_sample_bit_depth_chroma_minus1 + 1 : 0; + hw_ps->sps.pcm_loop_filter_disabled_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED); + hw_ps->sps.diff_max_min_pcm_luma_coding_block_size = + sps->log2_diff_max_min_pcm_luma_coding_block_size; + hw_ps->sps.min_pcm_luma_coding_block_size = + pcm_enabled ? sps->log2_min_pcm_luma_coding_block_size_minus3 + 3 : 0; + hw_ps->sps.num_short_term_ref_pic_sets = sps->num_short_term_ref_pic_sets; + hw_ps->sps.long_term_ref_pics_present_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT); + hw_ps->sps.num_long_term_ref_pics_sps = sps->num_long_term_ref_pics_sps; + hw_ps->sps.sps_temporal_mvp_enabled_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED); + hw_ps->sps.strong_intra_smoothing_enabled_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED); + hw_ps->sps.sps_max_dec_pic_buffering_minus1 = sps->sps_max_dec_pic_buffering_minus1; + hw_ps->sps.reserved_f = 0xff; + + /* write pps */ + hw_ps->pps.picture_parameters_set_id = pps->pic_parameter_set_id; + hw_ps->pps.seq_parameters_set_id_pps = sps->seq_parameter_set_id; + hw_ps->pps.dependent_slice_segments_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_DEPENDENT_SLICE_SEGMENT_ENABLED); + hw_ps->pps.output_flag_present_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_OUTPUT_FLAG_PRESENT); + hw_ps->pps.num_extra_slice_header_bits = pps->num_extra_slice_header_bits; + hw_ps->pps.sign_data_hiding_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED); + hw_ps->pps.cabac_init_present_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_CABAC_INIT_PRESENT); + hw_ps->pps.num_ref_idx_l0_default_active = pps->num_ref_idx_l0_default_active_minus1 + 1; + hw_ps->pps.num_ref_idx_l1_default_active = pps->num_ref_idx_l1_default_active_minus1 + 1; + hw_ps->pps.init_qp_minus26 = pps->init_qp_minus26; + hw_ps->pps.constrained_intra_pred_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED); + hw_ps->pps.transform_skip_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_TRANSFORM_SKIP_ENABLED); + hw_ps->pps.cu_qp_delta_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED); + hw_ps->pps.log2_min_cb_size = log2_min_cb_size + + sps->log2_diff_max_min_luma_coding_block_size - + pps->diff_cu_qp_delta_depth; + hw_ps->pps.pps_cb_qp_offset = pps->pps_cb_qp_offset; + hw_ps->pps.pps_cr_qp_offset = pps->pps_cr_qp_offset; + hw_ps->pps.pps_slice_chroma_qp_offsets_present_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT); + hw_ps->pps.weighted_pred_flag = !!(pps->flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED); + hw_ps->pps.weighted_bipred_flag = !!(pps->flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED); + hw_ps->pps.transquant_bypass_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED); + + tiles_enabled = !!(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED); + hw_ps->pps.tiles_enabled_flag = tiles_enabled; + hw_ps->pps.entropy_coding_sync_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED); + hw_ps->pps.pps_loop_filter_across_slices_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED); + hw_ps->pps.loop_filter_across_tiles_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED); + hw_ps->pps.deblocking_filter_override_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED); + hw_ps->pps.pps_deblocking_filter_disabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_PPS_DISABLE_DEBLOCKING_FILTER); + hw_ps->pps.pps_beta_offset_div2 = pps->pps_beta_offset_div2; + hw_ps->pps.pps_tc_offset_div2 = pps->pps_tc_offset_div2; + hw_ps->pps.lists_modification_present_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_LISTS_MODIFICATION_PRESENT); + hw_ps->pps.log2_parallel_merge_level = pps->log2_parallel_merge_level_minus2 + 2; + hw_ps->pps.slice_segment_header_extension_present_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_SLICE_SEGMENT_HEADER_EXTENSION_PRESENT); + hw_ps->pps.num_tile_columns = tiles_enabled ? pps->num_tile_columns_minus1 + 1 : 0; + hw_ps->pps.num_tile_rows = tiles_enabled ? pps->num_tile_rows_minus1 + 1 : 0; + hw_ps->pps.sps_pps_mode = 0; + hw_ps->pps.reserved_bits = 0x3fff; + hw_ps->pps.reserved = 0xffff; + + // Setup tiles information + memset(column_width, 0, sizeof(column_width)); + memset(row_height, 0, sizeof(row_height)); + + max_cu_width = 1 << (sps->log2_diff_max_min_luma_coding_block_size + log2_min_cb_size); + pic_in_cts_width = (width + max_cu_width - 1) / max_cu_width; + pic_in_cts_height = (height + max_cu_width - 1) / max_cu_width; + + if (pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED) { + if (pps->flags & V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING) { + compute_tiles_uniform(run, log2_min_cb_size, width, height, + pic_in_cts_width, pic_in_cts_height, + column_width, row_height); + } else { + compute_tiles_non_uniform(run, log2_min_cb_size, width, height, + pic_in_cts_width, pic_in_cts_height, + column_width, row_height); + } + } else { + column_width[0] = (width + max_cu_width - 1) / max_cu_width; + row_height[0] = (height + max_cu_width - 1) / max_cu_width; + } + + for (i = 0; i < 20; i++) { + if (column_width[i] > 0) + column_width[i]--; + + if (i & 1) + hw_ps->column_width[i / 2].value1 = column_width[i]; + else + hw_ps->column_width[i / 2].value0 = column_width[i]; + } + + for (i = 0; i < 22; i++) { + if (row_height[i] > 0) + row_height[i]--; + + if (i & 1) + hw_ps->row_height[i / 2].value1 = row_height[i]; + else + hw_ps->row_height[i / 2].value0 = row_height[i]; + } + + hw_ps->padding = 0xffffffff; + hw_ps->padding_bits = 0x3; +} + +static void set_ref_valid(struct rkvdec_vdpu381_regs_hevc *regs, int id, u32 valid) +{ + switch (id) { + case 0: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_0 = valid; + break; + case 1: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_1 = valid; + break; + case 2: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_2 = valid; + break; + case 3: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_3 = valid; + break; + case 4: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_4 = valid; + break; + case 5: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_5 = valid; + break; + case 6: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_6 = valid; + break; + case 7: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_7 = valid; + break; + case 8: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_8 = valid; + break; + case 9: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_9 = valid; + break; + case 10: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_10 = valid; + break; + case 11: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_11 = valid; + break; + case 12: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_12 = valid; + break; + case 13: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_13 = valid; + break; + case 14: + regs->hevc_param.reg099_hevc_ref_valid.hevc_ref_valid_14 = valid; + break; + } +} + +static void rkvdec_write_regs(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_hevc_ctx *hevc_ctx = ctx->priv; + + rkvdec_memcpy_toio(rkvdec->regs + OFFSET_COMMON_REGS, + &hevc_ctx->regs.common, + sizeof(hevc_ctx->regs.common)); + rkvdec_memcpy_toio(rkvdec->regs + OFFSET_CODEC_PARAMS_REGS, + &hevc_ctx->regs.hevc_param, + sizeof(hevc_ctx->regs.hevc_param)); + rkvdec_memcpy_toio(rkvdec->regs + OFFSET_COMMON_ADDR_REGS, + &hevc_ctx->regs.common_addr, + sizeof(hevc_ctx->regs.common_addr)); + rkvdec_memcpy_toio(rkvdec->regs + OFFSET_CODEC_ADDR_REGS, + &hevc_ctx->regs.hevc_addr, + sizeof(hevc_ctx->regs.hevc_addr)); + rkvdec_memcpy_toio(rkvdec->regs + OFFSET_POC_HIGHBIT_REGS, + &hevc_ctx->regs.hevc_highpoc, + sizeof(hevc_ctx->regs.hevc_highpoc)); +} + +static void config_registers(struct rkvdec_ctx *ctx, + struct rkvdec_hevc_run *run) +{ + const struct v4l2_ctrl_hevc_decode_params *dec_params = run->decode_params; + const struct v4l2_hevc_dpb_entry *dpb = dec_params->dpb; + struct rkvdec_hevc_ctx *hevc_ctx = ctx->priv; + struct rkvdec_vdpu381_regs_hevc *regs = &hevc_ctx->regs; + dma_addr_t priv_start_addr = hevc_ctx->priv_tbl.dma; + const struct v4l2_pix_format_mplane *dst_fmt; + struct vb2_v4l2_buffer *src_buf = run->base.bufs.src; + struct vb2_v4l2_buffer *dst_buf = run->base.bufs.dst; + const struct v4l2_format *f; + dma_addr_t rlc_addr; + u32 hor_virstride = 0; + u32 ver_virstride = 0; + u32 y_virstride = 0; + u32 offset; + u32 pixels; + dma_addr_t dst_addr; + u32 i; + + memset(regs, 0, sizeof(*regs)); + + /* Set HEVC mode */ + regs->common.reg009_dec_mode.dec_mode = VDPU381_MODE_HEVC; + + /* Set config */ + regs->common.reg011_important_en.buf_empty_en = 1; + regs->common.reg011_important_en.dec_clkgate_e = 1; + regs->common.reg011_important_en.dec_timeout_e = 1; + regs->common.reg011_important_en.pix_range_det_e = 1; + + /* Set IDR flag */ + regs->common.reg013_en_mode_set.cur_pic_is_idr = + !!(dec_params->flags & V4L2_HEVC_DECODE_PARAM_FLAG_IDR_PIC); + + /* Set input stream length */ + regs->common.reg016_stream_len = vb2_get_plane_payload(&src_buf->vb2_buf, 0); + + /* Set max slice number */ + regs->common.reg017_slice_number.slice_num = 1; + + /* Set strides */ + f = &ctx->decoded_fmt; + dst_fmt = &f->fmt.pix_mp; + hor_virstride = dst_fmt->plane_fmt[0].bytesperline; + ver_virstride = dst_fmt->height; + y_virstride = hor_virstride * ver_virstride; + + regs->common.reg018_y_hor_stride.y_hor_virstride = hor_virstride / 16; + regs->common.reg019_uv_hor_stride.uv_hor_virstride = hor_virstride / 16; + regs->common.reg020_y_stride.y_virstride = y_virstride / 16; + + /* Activate block gating */ + regs->common.reg026_block_gating_en.inter_auto_gating_e = 1; + regs->common.reg026_block_gating_en.filterd_auto_gating_e = 1; + regs->common.reg026_block_gating_en.strmd_auto_gating_e = 1; + regs->common.reg026_block_gating_en.mcp_auto_gating_e = 1; + regs->common.reg026_block_gating_en.busifd_auto_gating_e = 0; + regs->common.reg026_block_gating_en.dec_ctrl_auto_gating_e = 1; + regs->common.reg026_block_gating_en.intra_auto_gating_e = 1; + regs->common.reg026_block_gating_en.mc_auto_gating_e = 1; + regs->common.reg026_block_gating_en.transd_auto_gating_e = 1; + regs->common.reg026_block_gating_en.sram_auto_gating_e = 1; + regs->common.reg026_block_gating_en.cru_auto_gating_e = 1; + regs->common.reg026_block_gating_en.reg_cfg_gating_en = 1; + + /* Set timeout threshold */ + pixels = dst_fmt->height * dst_fmt->width; + if (pixels < RKVDEC_1080P_PIXELS) + regs->common.reg032_timeout_threshold = RKVDEC_TIMEOUT_1080p; + else if (pixels < RKVDEC_4K_PIXELS) + regs->common.reg032_timeout_threshold = RKVDEC_TIMEOUT_4K; + else if (pixels < RKVDEC_8K_PIXELS) + regs->common.reg032_timeout_threshold = RKVDEC_TIMEOUT_8K; + else + regs->common.reg032_timeout_threshold = RKVDEC_TIMEOUT_MAX; + + /* Set POC val */ + regs->hevc_param.reg065_cur_top_poc = dec_params->pic_order_cnt_val; + + /* Set ref pic address & poc */ + for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { + struct vb2_buffer *vb_buf = get_ref_buf(ctx, run, i); + dma_addr_t buf_dma = vb2_dma_contig_plane_dma_addr(vb_buf, 0); + u32 valid = !!(dec_params->num_active_dpb_entries > i); + + /* Set reference addresses */ + regs->hevc_addr.reg164_180_ref_base[i] = buf_dma; + + /* Set COLMV addresses */ + regs->hevc_addr.reg182_198_colmv_base[i] = buf_dma + ctx->colmv_offset; + + regs->hevc_param.reg067_082_ref_poc[i] = + dpb[i].pic_order_cnt_val; + + set_ref_valid(regs, i, valid); + regs->hevc_param.reg103_hevc_mvc0.ref_pic_layer_same_with_cur |= 1 << i; + } + + /* Set rlc base address (input stream) */ + rlc_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + regs->common_addr.rlc_base = rlc_addr; + regs->common_addr.rlcwrite_base = rlc_addr; + + /* Set output base address */ + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + regs->common_addr.decout_base = dst_addr; + regs->common_addr.error_ref_base = dst_addr; + + /* Set colmv address */ + regs->common_addr.colmv_cur_base = dst_addr + ctx->colmv_offset; + + /* Set RCB addresses */ + for (i = 0; i < rkvdec_rcb_buf_count(ctx); i++) + regs->common_addr.rcb_base[i] = rkvdec_rcb_buf_dma_addr(ctx, i); + + /* Set hw pps address */ + offset = offsetof(struct rkvdec_hevc_priv_tbl, param_set); + regs->hevc_addr.reg161_pps_base = priv_start_addr + offset; + + /* Set hw rps address */ + offset = offsetof(struct rkvdec_hevc_priv_tbl, rps); + regs->hevc_addr.reg163_rps_base = priv_start_addr + offset; + + /* Set cabac table */ + offset = offsetof(struct rkvdec_hevc_priv_tbl, cabac_table); + regs->hevc_addr.reg199_cabactbl_base = priv_start_addr + offset; + + /* Set scaling matrix */ + offset = offsetof(struct rkvdec_hevc_priv_tbl, scaling_list); + regs->hevc_addr.reg181_scanlist_addr = priv_start_addr + offset; + + rkvdec_write_regs(ctx); +} + +static int rkvdec_hevc_validate_sps(struct rkvdec_ctx *ctx, + const struct v4l2_ctrl_hevc_sps *sps) +{ + if (sps->chroma_format_idc != 1) + /* Only 4:2:0 is supported */ + return -EINVAL; + + if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8) + /* Luma and chroma bit depth mismatch */ + return -EINVAL; + + if (sps->bit_depth_luma_minus8 != 0 && sps->bit_depth_luma_minus8 != 2) + /* Only 8-bit and 10-bit are supported */ + return -EINVAL; + + if (sps->pic_width_in_luma_samples > ctx->coded_fmt.fmt.pix_mp.width || + sps->pic_height_in_luma_samples > ctx->coded_fmt.fmt.pix_mp.height) + return -EINVAL; + + return 0; +} + +static int rkvdec_hevc_start(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_hevc_priv_tbl *priv_tbl; + struct rkvdec_hevc_ctx *hevc_ctx; + struct v4l2_ctrl *ctrl; + int ret; + + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_SPS); + if (!ctrl) + return -EINVAL; + + ret = rkvdec_hevc_validate_sps(ctx, ctrl->p_new.p_hevc_sps); + if (ret) + return ret; + + hevc_ctx = kzalloc(sizeof(*hevc_ctx), GFP_KERNEL); + if (!hevc_ctx) + return -ENOMEM; + + priv_tbl = dma_alloc_coherent(rkvdec->dev, sizeof(*priv_tbl), + &hevc_ctx->priv_tbl.dma, GFP_KERNEL); + if (!priv_tbl) { + ret = -ENOMEM; + goto err_free_ctx; + } + + hevc_ctx->priv_tbl.size = sizeof(*priv_tbl); + hevc_ctx->priv_tbl.cpu = priv_tbl; + memcpy(priv_tbl->cabac_table, rkvdec_hevc_cabac_table, + sizeof(rkvdec_hevc_cabac_table)); + + ctx->priv = hevc_ctx; + return 0; + +err_free_ctx: + kfree(hevc_ctx); + return ret; +} + +static void rkvdec_hevc_stop(struct rkvdec_ctx *ctx) +{ + struct rkvdec_hevc_ctx *hevc_ctx = ctx->priv; + struct rkvdec_dev *rkvdec = ctx->dev; + + dma_free_coherent(rkvdec->dev, hevc_ctx->priv_tbl.size, + hevc_ctx->priv_tbl.cpu, hevc_ctx->priv_tbl.dma); + kfree(hevc_ctx); +} + +static int rkvdec_hevc_run(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_hevc_run run; + struct rkvdec_hevc_ctx *hevc_ctx = ctx->priv; + struct rkvdec_hevc_priv_tbl *tbl = hevc_ctx->priv_tbl.cpu; + + rkvdec_hevc_run_preamble(ctx, &run); + + rkvdec_hevc_assemble_hw_scaling_list(&run, + &tbl->scaling_list, + &hevc_ctx->scaling_matrix_cache); + assemble_hw_pps(ctx, &run); + + /* + * On vdpu381, not setting the long and short term ref sets will just output wrong frames. + * Let's just warn about it and let the decoder run anyway. + */ + if ((!ctx->has_sps_lt_rps && run.sps->num_long_term_ref_pics_sps) || + (!ctx->has_sps_st_rps && run.sps->num_short_term_ref_pic_sets)) { + dev_warn_ratelimited(rkvdec->dev, "Long and short term RPS not set\n"); + } else { + rkvdec_hevc_assemble_hw_rps(&run, &tbl->rps, &hevc_ctx->st_cache); + } + + config_registers(ctx, &run); + + rkvdec_run_postamble(ctx, &run.base); + + rkvdec_schedule_watchdog(rkvdec, hevc_ctx->regs.common.reg032_timeout_threshold); + + /* Start decoding! */ + writel(VDPU381_DEC_E_BIT, rkvdec->regs + VDPU381_REG_DEC_E); + + return 0; +} + +static int rkvdec_hevc_try_ctrl(struct rkvdec_ctx *ctx, struct v4l2_ctrl *ctrl) +{ + if (ctrl->id == V4L2_CID_STATELESS_HEVC_SPS) + return rkvdec_hevc_validate_sps(ctx, ctrl->p_new.p_hevc_sps); + + return 0; +} + +const struct rkvdec_coded_fmt_ops rkvdec_vdpu381_hevc_fmt_ops = { + .adjust_fmt = rkvdec_hevc_adjust_fmt, + .start = rkvdec_hevc_start, + .stop = rkvdec_hevc_stop, + .run = rkvdec_hevc_run, + .try_ctrl = rkvdec_hevc_try_ctrl, + .get_image_fmt = rkvdec_hevc_get_image_fmt, +}; diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c index c988fae85843..ba34a165374a 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c @@ -153,6 +153,16 @@ static int rkvdec_s_ctrl(struct v4l2_ctrl *ctrl) enum rkvdec_image_fmt image_fmt; struct vb2_queue *vq; + if (ctrl->id == V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS) { + ctx->has_sps_st_rps |= !!(ctrl->has_changed); + return 0; + } + + if (ctrl->id == V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS) { + ctx->has_sps_lt_rps |= !!(ctrl->has_changed); + return 0; + } + /* Check if this change requires a capture format reset */ if (!desc->ops->get_image_fmt) return 0; @@ -226,6 +236,62 @@ static const struct rkvdec_ctrls rkvdec_hevc_ctrls = { .num_ctrls = ARRAY_SIZE(rkvdec_hevc_ctrl_descs), }; +static const struct rkvdec_ctrl_desc vdpu38x_hevc_ctrl_descs[] = { + { + .cfg.id = V4L2_CID_STATELESS_HEVC_DECODE_PARAMS, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_SPS, + .cfg.ops = &rkvdec_ctrl_ops, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_PPS, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_DECODE_MODE, + .cfg.min = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED, + .cfg.max = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED, + .cfg.def = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_START_CODE, + .cfg.min = V4L2_STATELESS_HEVC_START_CODE_ANNEX_B, + .cfg.def = V4L2_STATELESS_HEVC_START_CODE_ANNEX_B, + .cfg.max = V4L2_STATELESS_HEVC_START_CODE_ANNEX_B, + }, + { + .cfg.id = V4L2_CID_MPEG_VIDEO_HEVC_PROFILE, + .cfg.min = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, + .cfg.max = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10, + .cfg.menu_skip_mask = + BIT(V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE), + .cfg.def = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, + }, + { + .cfg.id = V4L2_CID_MPEG_VIDEO_HEVC_LEVEL, + .cfg.min = V4L2_MPEG_VIDEO_HEVC_LEVEL_1, + .cfg.max = V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS, + .cfg.ops = &rkvdec_ctrl_ops, + .cfg.dims = { 65 }, + }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS, + .cfg.ops = &rkvdec_ctrl_ops, + .cfg.dims = { 65 }, + }, +}; + +static const struct rkvdec_ctrls vdpu38x_hevc_ctrls = { + .ctrls = vdpu38x_hevc_ctrl_descs, + .num_ctrls = ARRAY_SIZE(vdpu38x_hevc_ctrl_descs), +}; + static const struct rkvdec_decoded_fmt_desc rkvdec_hevc_decoded_fmts[] = { { .fourcc = V4L2_PIX_FMT_NV12, @@ -445,6 +511,22 @@ static const struct rkvdec_coded_fmt_desc rk3288_coded_fmts[] = { }; static const struct rkvdec_coded_fmt_desc vdpu381_coded_fmts[] = { + { + .fourcc = V4L2_PIX_FMT_HEVC_SLICE, + .frmsize = { + .min_width = 64, + .max_width = 65472, + .step_width = 64, + .min_height = 64, + .max_height = 65472, + .step_height = 16, + }, + .ctrls = &vdpu38x_hevc_ctrls, + .ops = &rkvdec_vdpu381_hevc_fmt_ops, + .num_decoded_fmts = ARRAY_SIZE(rkvdec_hevc_decoded_fmts), + .decoded_fmts = rkvdec_hevc_decoded_fmts, + .subsystem_flags = VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF, + }, { .fourcc = V4L2_PIX_FMT_H264_SLICE, .frmsize = { diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h index 4e9cd12fba3b..c723eaaef982 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.h @@ -154,6 +154,8 @@ struct rkvdec_ctx { struct rkvdec_rcb_config *rcb_config; u32 colmv_offset; void *priv; + u8 has_sps_st_rps: 1; + u8 has_sps_lt_rps: 1; }; static inline struct rkvdec_ctx *file_to_rkvdec_ctx(struct file *filp) @@ -187,6 +189,7 @@ extern const struct rkvdec_coded_fmt_ops rkvdec_vp9_fmt_ops; /* VDPU381 ops */ extern const struct rkvdec_coded_fmt_ops rkvdec_vdpu381_h264_fmt_ops; +extern const struct rkvdec_coded_fmt_ops rkvdec_vdpu381_hevc_fmt_ops; /* VDPU383 ops */ extern const struct rkvdec_coded_fmt_ops rkvdec_vdpu383_h264_fmt_ops; From e3b5b77e36894b4d61374ecb916355809c6e4aa4 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:33 -0500 Subject: [PATCH 303/307] media: rkvdec: Add HEVC support for the VDPU383 variant The VDPU383 decoder is used on the RK3576 SoC and has support for HEVC. This patch also moves some functions to a common rkvdec-hevc-common.c file and adds a specific scaling matrix flatten function. The fluster score for JCT-VC-HEVC_V1 is 146/147. Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/rockchip/rkvdec/Makefile | 1 + .../rockchip/rkvdec/rkvdec-hevc-common.c | 59 +- .../rockchip/rkvdec/rkvdec-hevc-common.h | 3 +- .../platform/rockchip/rkvdec/rkvdec-hevc.c | 2 +- .../rockchip/rkvdec/rkvdec-vdpu381-hevc.c | 3 +- .../rockchip/rkvdec/rkvdec-vdpu383-hevc.c | 652 ++++++++++++++++++ .../media/platform/rockchip/rkvdec/rkvdec.c | 91 +++ .../media/platform/rockchip/rkvdec/rkvdec.h | 2 + 8 files changed, 765 insertions(+), 48 deletions(-) create mode 100644 drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-hevc.c diff --git a/drivers/media/platform/rockchip/rkvdec/Makefile b/drivers/media/platform/rockchip/rkvdec/Makefile index e30fdd7d51c3..e629d571e4d8 100644 --- a/drivers/media/platform/rockchip/rkvdec/Makefile +++ b/drivers/media/platform/rockchip/rkvdec/Makefile @@ -11,4 +11,5 @@ rockchip-vdec-y += \ rkvdec-vdpu381-h264.o \ rkvdec-vdpu381-hevc.o \ rkvdec-vdpu383-h264.o \ + rkvdec-vdpu383-hevc.o \ rkvdec-vp9.o diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c index f6d7610772ee..28267ee30190 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c @@ -140,56 +140,26 @@ static void set_ref_poc(struct rkvdec_rps_short_term_ref_set *set, int poc, int } } -/* - * Flip one or more matrices along their main diagonal and flatten them - * before writing it to the memory. - * Convert: - * ABCD AEIM - * EFGH => BFJN => AEIMBFJNCGKODHLP - * IJKL CGKO - * MNOP DHLP - */ -static void transpose_and_flatten_matrices(u8 *output, const u8 *input, - int matrices, int row_length) -{ - int i, j, row, x_offset, matrix_offset, rot_index, y_offset, matrix_size, new_value; - - matrix_size = row_length * row_length; - for (i = 0; i < matrices; i++) { - row = 0; - x_offset = 0; - matrix_offset = i * matrix_size; - for (j = 0; j < matrix_size; j++) { - y_offset = j - (row * row_length); - rot_index = y_offset * row_length + x_offset; - new_value = *(input + i * matrix_size + j); - output[matrix_offset + rot_index] = new_value; - if ((j + 1) % row_length == 0) { - row += 1; - x_offset += 1; - } - } - } -} - -static void assemble_scalingfactor0(u8 *output, const struct v4l2_ctrl_hevc_scaling_matrix *input) +static void assemble_scalingfactor0(struct rkvdec_ctx *ctx, u8 *output, + const struct v4l2_ctrl_hevc_scaling_matrix *input) { + const struct rkvdec_variant *variant = ctx->dev->variant; int offset = 0; - transpose_and_flatten_matrices(output, (const u8 *)input->scaling_list_4x4, 6, 4); + variant->ops->flatten_matrices(output, (const u8 *)input->scaling_list_4x4, 6, 4); offset = 6 * 16 * sizeof(u8); - transpose_and_flatten_matrices(output + offset, (const u8 *)input->scaling_list_8x8, 6, 8); + variant->ops->flatten_matrices(output + offset, (const u8 *)input->scaling_list_8x8, 6, 8); offset += 6 * 64 * sizeof(u8); - transpose_and_flatten_matrices(output + offset, - (const u8 *)input->scaling_list_16x16, 6, 8); + variant->ops->flatten_matrices(output + offset, (const u8 *)input->scaling_list_16x16, + 6, 8); offset += 6 * 64 * sizeof(u8); /* Add a 128 byte padding with 0s between the two 32x32 matrices */ - transpose_and_flatten_matrices(output + offset, - (const u8 *)input->scaling_list_32x32, 1, 8); + variant->ops->flatten_matrices(output + offset, (const u8 *)input->scaling_list_32x32, + 1, 8); offset += 64 * sizeof(u8); memset(output + offset, 0, 128); offset += 128 * sizeof(u8); - transpose_and_flatten_matrices(output + offset, + variant->ops->flatten_matrices(output + offset, (const u8 *)input->scaling_list_32x32 + (64 * sizeof(u8)), 1, 8); offset += 64 * sizeof(u8); @@ -214,16 +184,17 @@ static void assemble_scalingdc(u8 *output, const struct v4l2_ctrl_hevc_scaling_m memcpy(output + 6 * sizeof(u8), list_32x32, 6 * sizeof(u8)); } -static void translate_scaling_list(struct scaling_factor *output, +static void translate_scaling_list(struct rkvdec_ctx *ctx, struct scaling_factor *output, const struct v4l2_ctrl_hevc_scaling_matrix *input) { - assemble_scalingfactor0(output->scalingfactor0, input); + assemble_scalingfactor0(ctx, output->scalingfactor0, input); memcpy(output->scalingfactor1, (const u8 *)input->scaling_list_4x4, 96); assemble_scalingdc(output->scalingdc, input); memset(output->reserved, 0, 4 * sizeof(u8)); } -void rkvdec_hevc_assemble_hw_scaling_list(struct rkvdec_hevc_run *run, +void rkvdec_hevc_assemble_hw_scaling_list(struct rkvdec_ctx *ctx, + struct rkvdec_hevc_run *run, struct scaling_factor *scaling_factor, struct v4l2_ctrl_hevc_scaling_matrix *cache) { @@ -233,7 +204,7 @@ void rkvdec_hevc_assemble_hw_scaling_list(struct rkvdec_hevc_run *run, sizeof(struct v4l2_ctrl_hevc_scaling_matrix))) return; - translate_scaling_list(scaling_factor, scaling); + translate_scaling_list(ctx, scaling_factor, scaling); memcpy(cache, scaling, sizeof(struct v4l2_ctrl_hevc_scaling_matrix)); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h index ce413454317a..6f4faca4c091 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.h @@ -95,7 +95,8 @@ void compute_tiles_non_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size s32 pic_in_cts_height, u16 *column_width, u16 *row_height); void rkvdec_hevc_assemble_hw_rps(struct rkvdec_hevc_run *run, struct rkvdec_rps *rps, struct v4l2_ctrl_hevc_ext_sps_st_rps *st_cache); -void rkvdec_hevc_assemble_hw_scaling_list(struct rkvdec_hevc_run *run, +void rkvdec_hevc_assemble_hw_scaling_list(struct rkvdec_ctx *ctx, + struct rkvdec_hevc_run *run, struct scaling_factor *scaling_factor, struct v4l2_ctrl_hevc_scaling_matrix *cache); struct vb2_buffer *get_ref_buf(struct rkvdec_ctx *ctx, diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c index a981f910dd68..3276f584f5c7 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c @@ -568,7 +568,7 @@ static int rkvdec_hevc_run(struct rkvdec_ctx *ctx) rkvdec_hevc_run_preamble(ctx, &run); - rkvdec_hevc_assemble_hw_scaling_list(&run, &tbl->scaling_list, + rkvdec_hevc_assemble_hw_scaling_list(ctx, &run, &tbl->scaling_list, &hevc_ctx->scaling_matrix_cache); assemble_hw_pps(ctx, &run); assemble_sw_rps(ctx, &run); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c index 32c79e948f8f..5f1c11ffb9f0 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c @@ -594,8 +594,7 @@ static int rkvdec_hevc_run(struct rkvdec_ctx *ctx) rkvdec_hevc_run_preamble(ctx, &run); - rkvdec_hevc_assemble_hw_scaling_list(&run, - &tbl->scaling_list, + rkvdec_hevc_assemble_hw_scaling_list(ctx, &run, &tbl->scaling_list, &hevc_ctx->scaling_matrix_cache); assemble_hw_pps(ctx, &run); diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-hevc.c new file mode 100644 index 000000000000..085c0a63a80c --- /dev/null +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu383-hevc.c @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip VDPU383 HEVC backend + * + * Copyright (C) 2025 Collabora, Ltd. + * Detlev Casanova + */ + +#include + +#include "rkvdec.h" +#include "rkvdec-cabac.h" +#include "rkvdec-rcb.h" +#include "rkvdec-hevc-common.h" +#include "rkvdec-vdpu383-regs.h" + +struct rkvdec_hevc_sps_pps { + // SPS + u16 video_parameters_set_id : 4; + u16 seq_parameters_set_id_sps : 4; + u16 chroma_format_idc : 2; + u16 width : 16; + u16 height : 16; + u16 bit_depth_luma : 3; + u16 bit_depth_chroma : 3; + u16 max_pic_order_count_lsb : 5; + u16 diff_max_min_luma_coding_block_size : 2; + u16 min_luma_coding_block_size : 3; + u16 min_transform_block_size : 3; + u16 diff_max_min_transform_block_size : 2; + u16 max_transform_hierarchy_depth_inter : 3; + u16 max_transform_hierarchy_depth_intra : 3; + u16 scaling_list_enabled_flag : 1; + u16 amp_enabled_flag : 1; + u16 sample_adaptive_offset_enabled_flag : 1; + u16 pcm_enabled_flag : 1; + u16 pcm_sample_bit_depth_luma : 4; + u16 pcm_sample_bit_depth_chroma : 4; + u16 pcm_loop_filter_disabled_flag : 1; + u16 diff_max_min_pcm_luma_coding_block_size : 3; + u16 min_pcm_luma_coding_block_size : 3; + u16 num_short_term_ref_pic_sets : 7; + u16 long_term_ref_pics_present_flag : 1; + u16 num_long_term_ref_pics_sps : 6; + u16 sps_temporal_mvp_enabled_flag : 1; + u16 strong_intra_smoothing_enabled_flag : 1; + u16 reserved0 : 7; + u16 sps_max_dec_pic_buffering_minus1 : 4; + u16 separate_colour_plane_flag : 1; + u16 high_precision_offsets_enabled_flag : 1; + u16 persistent_rice_adaptation_enabled_flag : 1; + + // PPS + u16 picture_parameters_set_id : 6; + u16 seq_parameters_set_id_pps : 4; + u16 dependent_slice_segments_enabled_flag : 1; + u16 output_flag_present_flag : 1; + u16 num_extra_slice_header_bits : 13; + u16 sign_data_hiding_enabled_flag : 1; + u16 cabac_init_present_flag : 1; + u16 num_ref_idx_l0_default_active : 4; + u16 num_ref_idx_l1_default_active : 4; + u16 init_qp_minus26 : 7; + u16 constrained_intra_pred_flag : 1; + u16 transform_skip_enabled_flag : 1; + u16 cu_qp_delta_enabled_flag : 1; + u16 log2_min_cb_size : 3; + u16 pps_cb_qp_offset : 5; + u16 pps_cr_qp_offset : 5; + u16 pps_slice_chroma_qp_offsets_present_flag : 1; + u16 weighted_pred_flag : 1; + u16 weighted_bipred_flag : 1; + u16 transquant_bypass_enabled_flag : 1; + u16 tiles_enabled_flag : 1; + u16 entropy_coding_sync_enabled_flag : 1; + u16 pps_loop_filter_across_slices_enabled_flag : 1; + u16 loop_filter_across_tiles_enabled_flag : 1; + u16 deblocking_filter_override_enabled_flag : 1; + u16 pps_deblocking_filter_disabled_flag : 1; + u16 pps_beta_offset_div2 : 4; + u16 pps_tc_offset_div2 : 4; + u16 lists_modification_present_flag : 1; + u16 log2_parallel_merge_level : 3; + u16 slice_segment_header_extension_present_flag : 1; + u16 reserved1 : 3; + + // pps extensions + u16 log2_max_transform_skip_block_size : 2; + u16 cross_component_prediction_enabled_flag : 1; + u16 chroma_qp_offset_list_enabled_flag : 1; + u16 log2_min_cu_chroma_qp_delta_size : 3; + u16 cb_qp_offset_list0 : 5; + u16 cb_qp_offset_list1 : 5; + u16 cb_qp_offset_list2 : 5; + u16 cb_qp_offset_list3 : 5; + u16 cb_qp_offset_list4 : 5; + u16 cb_qp_offset_list5 : 5; + u16 cb_cr_offset_list0 : 5; + u16 cb_cr_offset_list1 : 5; + u16 cb_cr_offset_list2 : 5; + u16 cb_cr_offset_list3 : 5; + u16 cb_cr_offset_list4 : 5; + u16 cb_cr_offset_list5 : 5; + u16 chroma_qp_offset_list_len_minus1 : 3; + + /* mvc0 && mvc1 */ + u16 mvc_ff : 16; + u16 mvc_00 : 9; + + /* poc info */ + u16 reserved2 : 3; + u32 current_poc : 32; + u32 ref_pic_poc0 : 32; + u32 ref_pic_poc1 : 32; + u32 ref_pic_poc2 : 32; + u32 ref_pic_poc3 : 32; + u32 ref_pic_poc4 : 32; + u32 ref_pic_poc5 : 32; + u32 ref_pic_poc6 : 32; + u32 ref_pic_poc7 : 32; + u32 ref_pic_poc8 : 32; + u32 ref_pic_poc9 : 32; + u32 ref_pic_poc10 : 32; + u32 ref_pic_poc11 : 32; + u32 ref_pic_poc12 : 32; + u32 ref_pic_poc13 : 32; + u32 ref_pic_poc14 : 32; + u32 reserved3 : 32; + u32 ref_is_valid : 15; + u32 reserved4 : 1; + + /* tile info*/ + u16 num_tile_columns : 5; + u16 num_tile_rows : 5; + u32 column_width0 : 24; + u32 column_width1 : 24; + u32 column_width2 : 24; + u32 column_width3 : 24; + u32 column_width4 : 24; + u32 column_width5 : 24; + u32 column_width6 : 24; + u32 column_width7 : 24; + u32 column_width8 : 24; + u32 column_width9 : 24; + u32 row_height0 : 24; + u32 row_height1 : 24; + u32 row_height2 : 24; + u32 row_height3 : 24; + u32 row_height4 : 24; + u32 row_height5 : 24; + u32 row_height6 : 24; + u32 row_height7 : 24; + u32 row_height8 : 24; + u32 row_height9 : 24; + u32 row_height10 : 24; + u32 reserved5 : 2; + u32 padding; +} __packed; + +struct rkvdec_hevc_priv_tbl { + struct rkvdec_hevc_sps_pps param_set; + struct rkvdec_rps rps; + struct scaling_factor scaling_list; + u8 cabac_table[27456]; +} __packed; + +struct rkvdec_hevc_ctx { + struct rkvdec_aux_buf priv_tbl; + struct v4l2_ctrl_hevc_scaling_matrix scaling_matrix_cache; + struct v4l2_ctrl_hevc_ext_sps_st_rps st_cache; + struct vdpu383_regs_h26x regs; +}; + +static void set_column_row(struct rkvdec_hevc_sps_pps *hw_ps, u16 *column, u16 *row) +{ + hw_ps->column_width0 = column[0] | (column[1] << 12); + hw_ps->row_height0 = row[0] | (row[1] << 12); + hw_ps->column_width1 = column[2] | (column[3] << 12); + hw_ps->row_height1 = row[2] | (row[3] << 12); + hw_ps->column_width2 = column[4] | (column[5] << 12); + hw_ps->row_height2 = row[4] | (row[5] << 12); + hw_ps->column_width3 = column[6] | (column[7] << 12); + hw_ps->row_height3 = row[6] | (row[7] << 12); + hw_ps->column_width4 = column[8] | (column[9] << 12); + hw_ps->row_height4 = row[8] | (row[9] << 12); + hw_ps->column_width5 = column[10] | (column[11] << 12); + hw_ps->row_height5 = row[10] | (row[11] << 12); + hw_ps->column_width6 = column[12] | (column[13] << 12); + hw_ps->row_height6 = row[12] | (row[13] << 12); + hw_ps->column_width7 = column[14] | (column[15] << 12); + hw_ps->row_height7 = row[14] | (row[15] << 12); + hw_ps->column_width8 = column[16] | (column[17] << 12); + hw_ps->row_height8 = row[16] | (row[17] << 12); + hw_ps->column_width9 = column[18] | (column[19] << 12); + hw_ps->row_height9 = row[18] | (row[19] << 12); + + hw_ps->row_height10 = row[20] | (row[21] << 12); +} + +static void set_pps_ref_pic_poc(struct rkvdec_hevc_sps_pps *hw_ps, const struct v4l2_hevc_dpb_entry *dpb) +{ + hw_ps->ref_pic_poc0 = dpb[0].pic_order_cnt_val; + hw_ps->ref_pic_poc1 = dpb[1].pic_order_cnt_val; + hw_ps->ref_pic_poc2 = dpb[2].pic_order_cnt_val; + hw_ps->ref_pic_poc3 = dpb[3].pic_order_cnt_val; + hw_ps->ref_pic_poc4 = dpb[4].pic_order_cnt_val; + hw_ps->ref_pic_poc5 = dpb[5].pic_order_cnt_val; + hw_ps->ref_pic_poc6 = dpb[6].pic_order_cnt_val; + hw_ps->ref_pic_poc7 = dpb[7].pic_order_cnt_val; + hw_ps->ref_pic_poc8 = dpb[8].pic_order_cnt_val; + hw_ps->ref_pic_poc9 = dpb[9].pic_order_cnt_val; + hw_ps->ref_pic_poc10 = dpb[10].pic_order_cnt_val; + hw_ps->ref_pic_poc11 = dpb[11].pic_order_cnt_val; + hw_ps->ref_pic_poc12 = dpb[12].pic_order_cnt_val; + hw_ps->ref_pic_poc13 = dpb[13].pic_order_cnt_val; + hw_ps->ref_pic_poc14 = dpb[14].pic_order_cnt_val; +} + +static void assemble_hw_pps(struct rkvdec_ctx *ctx, + struct rkvdec_hevc_run *run) +{ + struct rkvdec_hevc_ctx *h264_ctx = ctx->priv; + const struct v4l2_ctrl_hevc_sps *sps = run->sps; + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + const struct v4l2_ctrl_hevc_decode_params *dec_params = run->decode_params; + struct rkvdec_hevc_priv_tbl *priv_tbl = h264_ctx->priv_tbl.cpu; + struct rkvdec_hevc_sps_pps *hw_ps; + bool tiles_enabled; + s32 max_cu_width; + s32 pic_in_cts_width; + s32 pic_in_cts_height; + u16 log2_min_cb_size, width, height; + u16 column_width[22]; + u16 row_height[22]; + u8 pcm_enabled; + u32 i; + + /* + * HW read the SPS/PPS information from PPS packet index by PPS id. + * offset from the base can be calculated by PPS_id * 32 (size per PPS + * packet unit). so the driver copy SPS/PPS information to the exact PPS + * packet unit for HW accessing. + */ + hw_ps = &priv_tbl->param_set; + memset(hw_ps, 0, sizeof(*hw_ps)); + + /* write sps */ + hw_ps->video_parameters_set_id = sps->video_parameter_set_id; + hw_ps->seq_parameters_set_id_sps = sps->seq_parameter_set_id; + hw_ps->chroma_format_idc = sps->chroma_format_idc; + + log2_min_cb_size = sps->log2_min_luma_coding_block_size_minus3 + 3; + width = sps->pic_width_in_luma_samples; + height = sps->pic_height_in_luma_samples; + hw_ps->width = width; + hw_ps->height = height; + hw_ps->bit_depth_luma = sps->bit_depth_luma_minus8 + 8; + hw_ps->bit_depth_chroma = sps->bit_depth_chroma_minus8 + 8; + hw_ps->max_pic_order_count_lsb = sps->log2_max_pic_order_cnt_lsb_minus4 + 4; + hw_ps->diff_max_min_luma_coding_block_size = sps->log2_diff_max_min_luma_coding_block_size; + hw_ps->min_luma_coding_block_size = sps->log2_min_luma_coding_block_size_minus3 + 3; + hw_ps->min_transform_block_size = sps->log2_min_luma_transform_block_size_minus2 + 2; + hw_ps->diff_max_min_transform_block_size = + sps->log2_diff_max_min_luma_transform_block_size; + hw_ps->max_transform_hierarchy_depth_inter = sps->max_transform_hierarchy_depth_inter; + hw_ps->max_transform_hierarchy_depth_intra = sps->max_transform_hierarchy_depth_intra; + hw_ps->scaling_list_enabled_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED); + hw_ps->amp_enabled_flag = !!(sps->flags & V4L2_HEVC_SPS_FLAG_AMP_ENABLED); + hw_ps->sample_adaptive_offset_enabled_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_SAMPLE_ADAPTIVE_OFFSET); + + pcm_enabled = !!(sps->flags & V4L2_HEVC_SPS_FLAG_PCM_ENABLED); + hw_ps->pcm_enabled_flag = pcm_enabled; + hw_ps->pcm_sample_bit_depth_luma = + pcm_enabled ? sps->pcm_sample_bit_depth_luma_minus1 + 1 : 0; + hw_ps->pcm_sample_bit_depth_chroma = + pcm_enabled ? sps->pcm_sample_bit_depth_chroma_minus1 + 1 : 0; + hw_ps->pcm_loop_filter_disabled_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED); + hw_ps->diff_max_min_pcm_luma_coding_block_size = + sps->log2_diff_max_min_pcm_luma_coding_block_size; + hw_ps->min_pcm_luma_coding_block_size = + pcm_enabled ? sps->log2_min_pcm_luma_coding_block_size_minus3 + 3 : 0; + hw_ps->num_short_term_ref_pic_sets = sps->num_short_term_ref_pic_sets; + hw_ps->long_term_ref_pics_present_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT); + hw_ps->num_long_term_ref_pics_sps = sps->num_long_term_ref_pics_sps; + hw_ps->sps_temporal_mvp_enabled_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED); + hw_ps->strong_intra_smoothing_enabled_flag = + !!(sps->flags & V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED); + hw_ps->sps_max_dec_pic_buffering_minus1 = sps->sps_max_dec_pic_buffering_minus1; + + /* write pps */ + hw_ps->picture_parameters_set_id = pps->pic_parameter_set_id; + hw_ps->seq_parameters_set_id_pps = sps->seq_parameter_set_id; + hw_ps->dependent_slice_segments_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_DEPENDENT_SLICE_SEGMENT_ENABLED); + hw_ps->output_flag_present_flag = !!(pps->flags & V4L2_HEVC_PPS_FLAG_OUTPUT_FLAG_PRESENT); + hw_ps->num_extra_slice_header_bits = pps->num_extra_slice_header_bits; + hw_ps->sign_data_hiding_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED); + hw_ps->cabac_init_present_flag = !!(pps->flags & V4L2_HEVC_PPS_FLAG_CABAC_INIT_PRESENT); + hw_ps->num_ref_idx_l0_default_active = pps->num_ref_idx_l0_default_active_minus1 + 1; + hw_ps->num_ref_idx_l1_default_active = pps->num_ref_idx_l1_default_active_minus1 + 1; + hw_ps->init_qp_minus26 = pps->init_qp_minus26; + hw_ps->constrained_intra_pred_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED); + hw_ps->transform_skip_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_TRANSFORM_SKIP_ENABLED); + hw_ps->cu_qp_delta_enabled_flag = !!(pps->flags & V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED); + hw_ps->log2_min_cb_size = log2_min_cb_size + + sps->log2_diff_max_min_luma_coding_block_size - + pps->diff_cu_qp_delta_depth; + hw_ps->pps_cb_qp_offset = pps->pps_cb_qp_offset; + hw_ps->pps_cr_qp_offset = pps->pps_cr_qp_offset; + hw_ps->pps_slice_chroma_qp_offsets_present_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT); + hw_ps->weighted_pred_flag = !!(pps->flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED); + hw_ps->weighted_bipred_flag = !!(pps->flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED); + hw_ps->transquant_bypass_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED); + tiles_enabled = !!(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED); + hw_ps->tiles_enabled_flag = tiles_enabled; + hw_ps->entropy_coding_sync_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED); + hw_ps->pps_loop_filter_across_slices_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED); + hw_ps->loop_filter_across_tiles_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED); + hw_ps->deblocking_filter_override_enabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED); + hw_ps->pps_deblocking_filter_disabled_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_PPS_DISABLE_DEBLOCKING_FILTER); + hw_ps->pps_beta_offset_div2 = pps->pps_beta_offset_div2; + hw_ps->pps_tc_offset_div2 = pps->pps_tc_offset_div2; + hw_ps->lists_modification_present_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_LISTS_MODIFICATION_PRESENT); + hw_ps->log2_parallel_merge_level = pps->log2_parallel_merge_level_minus2 + 2; + hw_ps->slice_segment_header_extension_present_flag = + !!(pps->flags & V4L2_HEVC_PPS_FLAG_SLICE_SEGMENT_HEADER_EXTENSION_PRESENT); + hw_ps->num_tile_columns = tiles_enabled ? pps->num_tile_columns_minus1 + 1 : 1; + hw_ps->num_tile_rows = tiles_enabled ? pps->num_tile_rows_minus1 + 1 : 1; + hw_ps->mvc_ff = 0xffff; + + // Setup tiles information + memset(column_width, 0, sizeof(column_width)); + memset(row_height, 0, sizeof(row_height)); + + max_cu_width = 1 << (sps->log2_diff_max_min_luma_coding_block_size + log2_min_cb_size); + pic_in_cts_width = (width + max_cu_width - 1) / max_cu_width; + pic_in_cts_height = (height + max_cu_width - 1) / max_cu_width; + + if (tiles_enabled) { + if (pps->flags & V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING) { + compute_tiles_uniform(run, log2_min_cb_size, width, height, + pic_in_cts_width, pic_in_cts_height, + column_width, row_height); + } else { + compute_tiles_non_uniform(run, log2_min_cb_size, width, height, + pic_in_cts_width, pic_in_cts_height, + column_width, row_height); + } + } else { + column_width[0] = (width + max_cu_width - 1) / max_cu_width; + row_height[0] = (height + max_cu_width - 1) / max_cu_width; + } + + set_column_row(hw_ps, column_width, row_height); + + // Setup POC information + hw_ps->current_poc = dec_params->pic_order_cnt_val; + + set_pps_ref_pic_poc(hw_ps, dec_params->dpb); + for (i = 0; i < ARRAY_SIZE(dec_params->dpb); i++) { + u32 valid = !!(dec_params->num_active_dpb_entries > i); + hw_ps->ref_is_valid |= valid << i; + } +} + +static void rkvdec_write_regs(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_hevc_ctx *h265_ctx = ctx->priv; + + rkvdec_memcpy_toio(rkvdec->regs + VDPU383_OFFSET_COMMON_REGS, + &h265_ctx->regs.common, + sizeof(h265_ctx->regs.common)); + rkvdec_memcpy_toio(rkvdec->regs + VDPU383_OFFSET_COMMON_ADDR_REGS, + &h265_ctx->regs.common_addr, + sizeof(h265_ctx->regs.common_addr)); + rkvdec_memcpy_toio(rkvdec->regs + VDPU383_OFFSET_CODEC_PARAMS_REGS, + &h265_ctx->regs.h26x_params, + sizeof(h265_ctx->regs.h26x_params)); + rkvdec_memcpy_toio(rkvdec->regs + VDPU383_OFFSET_CODEC_ADDR_REGS, + &h265_ctx->regs.h26x_addr, + sizeof(h265_ctx->regs.h26x_addr)); +} + +static void config_registers(struct rkvdec_ctx *ctx, + struct rkvdec_hevc_run *run) +{ + const struct v4l2_ctrl_hevc_decode_params *dec_params = run->decode_params; + struct rkvdec_hevc_ctx *h265_ctx = ctx->priv; + const struct v4l2_ctrl_hevc_sps *sps = run->sps; + dma_addr_t priv_start_addr = h265_ctx->priv_tbl.dma; + const struct v4l2_pix_format_mplane *dst_fmt; + struct vb2_v4l2_buffer *src_buf = run->base.bufs.src; + struct vb2_v4l2_buffer *dst_buf = run->base.bufs.dst; + struct vdpu383_regs_h26x *regs = &h265_ctx->regs; + const struct v4l2_format *f; + dma_addr_t rlc_addr; + dma_addr_t dst_addr; + u32 hor_virstride; + u32 ver_virstride; + u32 y_virstride; + u32 offset; + u32 pixels; + u32 i; + + memset(regs, 0, sizeof(*regs)); + + /* Set HEVC mode */ + regs->common.reg008_dec_mode = VDPU383_MODE_HEVC; + + /* Set input stream length */ + regs->h26x_params.reg066_stream_len = vb2_get_plane_payload(&src_buf->vb2_buf, 0); + + /* Set strides */ + f = &ctx->decoded_fmt; + dst_fmt = &f->fmt.pix_mp; + hor_virstride = dst_fmt->plane_fmt[0].bytesperline; + ver_virstride = dst_fmt->height; + y_virstride = hor_virstride * ver_virstride; + + pixels = dst_fmt->height * dst_fmt->width; + + regs->h26x_params.reg068_hor_virstride = hor_virstride / 16; + regs->h26x_params.reg069_raster_uv_hor_virstride = hor_virstride / 16; + regs->h26x_params.reg070_y_virstride = y_virstride / 16; + + /* Activate block gating */ + regs->common.reg010_block_gating_en.strmd_auto_gating_e = 1; + regs->common.reg010_block_gating_en.inter_auto_gating_e = 1; + regs->common.reg010_block_gating_en.intra_auto_gating_e = 1; + regs->common.reg010_block_gating_en.transd_auto_gating_e = 1; + regs->common.reg010_block_gating_en.recon_auto_gating_e = 1; + regs->common.reg010_block_gating_en.filterd_auto_gating_e = 1; + regs->common.reg010_block_gating_en.bus_auto_gating_e = 1; + regs->common.reg010_block_gating_en.ctrl_auto_gating_e = 1; + regs->common.reg010_block_gating_en.rcb_auto_gating_e = 1; + regs->common.reg010_block_gating_en.err_prc_auto_gating_e = 1; + + /* Set timeout threshold */ + if (pixels < RKVDEC_1080P_PIXELS) + regs->common.reg013_core_timeout_threshold = VDPU383_TIMEOUT_1080p; + else if (pixels < RKVDEC_4K_PIXELS) + regs->common.reg013_core_timeout_threshold = VDPU383_TIMEOUT_4K; + else if (pixels < RKVDEC_8K_PIXELS) + regs->common.reg013_core_timeout_threshold = VDPU383_TIMEOUT_8K; + else + regs->common.reg013_core_timeout_threshold = VDPU383_TIMEOUT_MAX; + + regs->common.reg016_error_ctrl_set.error_proc_disable = 1; + + /* Set ref pic address & poc */ + for (i = 0; i < ARRAY_SIZE(dec_params->dpb) - 1; i++) { + struct vb2_buffer *vb_buf = get_ref_buf(ctx, run, i); + dma_addr_t buf_dma; + + buf_dma = vb2_dma_contig_plane_dma_addr(vb_buf, 0); + + /* Set reference addresses */ + regs->h26x_addr.reg170_185_ref_base[i] = buf_dma; + regs->h26x_addr.reg195_210_payload_st_ref_base[i] = buf_dma; + + /* Set COLMV addresses */ + regs->h26x_addr.reg217_232_colmv_ref_base[i] = buf_dma + ctx->colmv_offset; + } + + /* Set rlc base address (input stream) */ + rlc_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + regs->common_addr.reg128_strm_base = rlc_addr; + + /* Set output base address */ + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + regs->h26x_addr.reg168_decout_base = dst_addr; + regs->h26x_addr.reg169_error_ref_base = dst_addr; + regs->h26x_addr.reg192_payload_st_cur_base = dst_addr; + + /* Set colmv address */ + regs->h26x_addr.reg216_colmv_cur_base = dst_addr + ctx->colmv_offset; + + /* Set RCB addresses */ + for (i = 0; i < rkvdec_rcb_buf_count(ctx); i++) { + regs->common_addr.reg140_162_rcb_info[i].offset = rkvdec_rcb_buf_dma_addr(ctx, i); + regs->common_addr.reg140_162_rcb_info[i].size = rkvdec_rcb_buf_size(ctx, i); + } + + if (sps->flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED) { + /* Set scaling matrix */ + offset = offsetof(struct rkvdec_hevc_priv_tbl, scaling_list); + regs->common_addr.reg132_scanlist_addr = priv_start_addr + offset; + } + + /* Set hw pps address */ + offset = offsetof(struct rkvdec_hevc_priv_tbl, param_set); + regs->common_addr.reg131_gbl_base = priv_start_addr + offset; + regs->h26x_params.reg067_global_len = sizeof(struct rkvdec_hevc_sps_pps) / 16; + + /* Set hw rps address */ + offset = offsetof(struct rkvdec_hevc_priv_tbl, rps); + regs->common_addr.reg129_rps_base = priv_start_addr + offset; + + /* Set cabac table */ + offset = offsetof(struct rkvdec_hevc_priv_tbl, cabac_table); + regs->common_addr.reg130_cabactbl_base = priv_start_addr + offset; + + rkvdec_write_regs(ctx); +} + +static int rkvdec_hevc_validate_sps(struct rkvdec_ctx *ctx, + const struct v4l2_ctrl_hevc_sps *sps) +{ + if (sps->chroma_format_idc != 1) + /* Only 4:2:0 is supported */ + return -EINVAL; + + if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8) + /* Luma and chroma bit depth mismatch */ + return -EINVAL; + + if (sps->bit_depth_luma_minus8 != 0 && sps->bit_depth_luma_minus8 != 2) + /* Only 8-bit and 10-bit are supported */ + return -EINVAL; + + if (sps->pic_width_in_luma_samples > ctx->coded_fmt.fmt.pix_mp.width || + sps->pic_height_in_luma_samples > ctx->coded_fmt.fmt.pix_mp.height) + return -EINVAL; + + return 0; +} + +static int rkvdec_hevc_start(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_hevc_priv_tbl *priv_tbl; + struct rkvdec_hevc_ctx *hevc_ctx; + struct v4l2_ctrl *ctrl; + int ret; + + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_SPS); + if (!ctrl) + return -EINVAL; + + ret = rkvdec_hevc_validate_sps(ctx, ctrl->p_new.p_hevc_sps); + if (ret) + return ret; + + hevc_ctx = kzalloc(sizeof(*hevc_ctx), GFP_KERNEL); + if (!hevc_ctx) + return -ENOMEM; + + priv_tbl = dma_alloc_coherent(rkvdec->dev, sizeof(*priv_tbl), + &hevc_ctx->priv_tbl.dma, GFP_KERNEL); + if (!priv_tbl) { + ret = -ENOMEM; + goto err_free_ctx; + } + + hevc_ctx->priv_tbl.size = sizeof(*priv_tbl); + hevc_ctx->priv_tbl.cpu = priv_tbl; + memcpy(priv_tbl->cabac_table, rkvdec_hevc_cabac_table, + sizeof(rkvdec_hevc_cabac_table)); + + ctx->priv = hevc_ctx; + return 0; + +err_free_ctx: + kfree(hevc_ctx); + return ret; +} + +static void rkvdec_hevc_stop(struct rkvdec_ctx *ctx) +{ + struct rkvdec_hevc_ctx *hevc_ctx = ctx->priv; + struct rkvdec_dev *rkvdec = ctx->dev; + + dma_free_coherent(rkvdec->dev, hevc_ctx->priv_tbl.size, + hevc_ctx->priv_tbl.cpu, hevc_ctx->priv_tbl.dma); + kfree(hevc_ctx); +} + +static int rkvdec_hevc_run(struct rkvdec_ctx *ctx) +{ + struct rkvdec_dev *rkvdec = ctx->dev; + struct rkvdec_hevc_run run; + struct rkvdec_hevc_ctx *hevc_ctx = ctx->priv; + struct rkvdec_hevc_priv_tbl *tbl = hevc_ctx->priv_tbl.cpu; + u32 timeout_threshold; + + rkvdec_hevc_run_preamble(ctx, &run); + + /* + * On vdpu383, not setting the long and short term ref sets leads to IOMMU page faults. + * To be on the safe side for this new v4l2 control, write an error in the log and mark + * the buffer as failed by returning an error here. + */ + if ((!ctx->has_sps_lt_rps && run.sps->num_long_term_ref_pics_sps) || + (!ctx->has_sps_st_rps && run.sps->num_short_term_ref_pic_sets)) { + dev_err_ratelimited(rkvdec->dev, "Long and short term RPS not set\n"); + return -EINVAL; + } + + rkvdec_hevc_assemble_hw_scaling_list(ctx, &run, &tbl->scaling_list, + &hevc_ctx->scaling_matrix_cache); + assemble_hw_pps(ctx, &run); + rkvdec_hevc_assemble_hw_rps(&run, &tbl->rps, &hevc_ctx->st_cache); + + config_registers(ctx, &run); + + rkvdec_run_postamble(ctx, &run.base); + + timeout_threshold = hevc_ctx->regs.common.reg013_core_timeout_threshold; + rkvdec_schedule_watchdog(rkvdec, timeout_threshold); + + /* Start decoding! */ + writel(timeout_threshold, rkvdec->link + VDPU383_LINK_TIMEOUT_THRESHOLD); + writel(VDPU383_IP_CRU_MODE, rkvdec->link + VDPU383_LINK_IP_ENABLE); + writel(VDPU383_DEC_E_BIT, rkvdec->link + VDPU383_LINK_DEC_ENABLE); + + return 0; +} + +static int rkvdec_hevc_try_ctrl(struct rkvdec_ctx *ctx, struct v4l2_ctrl *ctrl) +{ + if (ctrl->id == V4L2_CID_STATELESS_HEVC_SPS) + return rkvdec_hevc_validate_sps(ctx, ctrl->p_new.p_hevc_sps); + + return 0; +} + +const struct rkvdec_coded_fmt_ops rkvdec_vdpu383_hevc_fmt_ops = { + .adjust_fmt = rkvdec_hevc_adjust_fmt, + .start = rkvdec_hevc_start, + .stop = rkvdec_hevc_stop, + .run = rkvdec_hevc_run, + .try_ctrl = rkvdec_hevc_try_ctrl, + .get_image_fmt = rkvdec_hevc_get_image_fmt, +}; diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c index ba34a165374a..967c452ab61f 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c @@ -546,6 +546,22 @@ static const struct rkvdec_coded_fmt_desc vdpu381_coded_fmts[] = { }; static const struct rkvdec_coded_fmt_desc vdpu383_coded_fmts[] = { + { + .fourcc = V4L2_PIX_FMT_HEVC_SLICE, + .frmsize = { + .min_width = 64, + .max_width = 65472, + .step_width = 64, + .min_height = 64, + .max_height = 65472, + .step_height = 16, + }, + .ctrls = &vdpu38x_hevc_ctrls, + .ops = &rkvdec_vdpu383_hevc_fmt_ops, + .num_decoded_fmts = ARRAY_SIZE(rkvdec_hevc_decoded_fmts), + .decoded_fmts = rkvdec_hevc_decoded_fmts, + .subsystem_flags = VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF, + }, { .fourcc = V4L2_PIX_FMT_H264_SLICE, .frmsize = { @@ -1508,6 +1524,78 @@ static irqreturn_t rkvdec_irq_handler(int irq, void *priv) return variant->ops->irq_handler(ctx); } +/* + * Flip one or more matrices along their main diagonal and flatten them + * before writing it to the memory. + * Convert: + * ABCD AEIM + * EFGH => BFJN => AEIMBFJNCGKODHLP + * IJKL CGKO + * MNOP DHLP + */ +static void transpose_and_flatten_matrices(u8 *output, const u8 *input, + int matrices, int row_length) +{ + int i, j, row, x_offset, matrix_offset, rot_index, y_offset, matrix_size, new_value; + + matrix_size = row_length * row_length; + for (i = 0; i < matrices; i++) { + row = 0; + x_offset = 0; + matrix_offset = i * matrix_size; + for (j = 0; j < matrix_size; j++) { + y_offset = j - (row * row_length); + rot_index = y_offset * row_length + x_offset; + new_value = *(input + i * matrix_size + j); + output[matrix_offset + rot_index] = new_value; + if ((j + 1) % row_length == 0) { + row += 1; + x_offset += 1; + } + } + } +} + +/* + * VDPU383 needs a specific order: + * The 8x8 flatten matrix is based on 4x4 blocks. + * Each 4x4 block is written separately in order. + * + * Base data => Transposed VDPU383 transposed + * + * ABCDEFGH AIQYaiqy AIQYBJRZ + * IJKLMNOP BJRZbjrz CKS0DLT1 + * QRSTUVWX CKS0cks6 aiqybjrz + * YZ012345 => DLT1dlt7 cks6dlt7 + * abcdefgh EMU2emu8 EMU2FNV3 + * ijklmnop FNV3fnv9 GOW4HPX5 + * qrstuvwx GOW4gow# emu8fnv9 + * yz6789#$ HPX5hpx$ gow#hpx$ + * + * As the function reads block of 4x4 it can be used for both 4x4 and 8x8 matrices. + * + */ +static void vdpu383_flatten_matrices(u8 *output, const u8 *input, int matrices, int row_length) +{ + u8 block; + int i, j, matrix_offset, matrix_size, new_value, input_idx, line_offset, block_offset; + + matrix_size = row_length * row_length; + for (i = 0; i < matrices; i++) { + matrix_offset = i * matrix_size; + for (j = 0; j < matrix_size; j++) { + block = j / 16; + line_offset = (j % 16) / 4; + block_offset = (block & 1) * 32 + (block & 2) * 2; + input_idx = ((j % 4) * row_length) + line_offset + block_offset; + + new_value = *(input + i * matrix_size + input_idx); + + output[matrix_offset + j] = new_value; + } + } +} + static void rkvdec_watchdog_func(struct work_struct *work) { struct rkvdec_dev *rkvdec; @@ -1569,6 +1657,7 @@ static int rkvdec_disable_multicore(struct rkvdec_dev *rkvdec) static const struct rkvdec_variant_ops rk3399_variant_ops = { .irq_handler = rk3399_irq_handler, .colmv_size = rkvdec_colmv_size, + .flatten_matrices = transpose_and_flatten_matrices, }; static const struct rkvdec_variant rk3288_rkvdec_variant = { @@ -1612,6 +1701,7 @@ static const struct rcb_size_info vdpu381_rcb_sizes[] = { static const struct rkvdec_variant_ops vdpu381_variant_ops = { .irq_handler = vdpu381_irq_handler, .colmv_size = rkvdec_colmv_size, + .flatten_matrices = transpose_and_flatten_matrices, }; static const struct rkvdec_variant vdpu381_variant = { @@ -1638,6 +1728,7 @@ static const struct rcb_size_info vdpu383_rcb_sizes[] = { static const struct rkvdec_variant_ops vdpu383_variant_ops = { .irq_handler = vdpu383_irq_handler, .colmv_size = vdpu383_colmv_size, + .flatten_matrices = vdpu383_flatten_matrices, }; static const struct rkvdec_variant vdpu383_variant = { diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h index c723eaaef982..a24be6638b6b 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec.h +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.h @@ -74,6 +74,7 @@ vb2_to_rkvdec_decoded_buf(struct vb2_buffer *buf) struct rkvdec_variant_ops { irqreturn_t (*irq_handler)(struct rkvdec_ctx *ctx); u32 (*colmv_size)(u16 width, u16 height); + void (*flatten_matrices)(u8 *output, const u8 *input, int matrices, int row_length); }; struct rkvdec_variant { @@ -193,5 +194,6 @@ extern const struct rkvdec_coded_fmt_ops rkvdec_vdpu381_hevc_fmt_ops; /* VDPU383 ops */ extern const struct rkvdec_coded_fmt_ops rkvdec_vdpu383_h264_fmt_ops; +extern const struct rkvdec_coded_fmt_ops rkvdec_vdpu383_hevc_fmt_ops; #endif /* RKVDEC_H_ */ From 3d9f32e02c2ed85338be627de672e2b81b88a836 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Thu, 13 Nov 2025 23:04:00 +0200 Subject: [PATCH 304/307] media: uvcvideo: Create an ID namespace for streaming output terminals Some devices, such as the Grandstream GUV3100 and the LSK Meeting Eye for Business & Home, exhibit entity ID collisions between units and streaming output terminals. The UVC specification requires unit and terminal IDs to be unique, and uses the ID to reference entities: - In control requests, to identify the target entity - In the UVC units and terminals descriptors' bSourceID field, to identify source entities - In the UVC input header descriptor's bTerminalLink, to identify the terminal associated with a streaming interface Entity ID collisions break accessing controls and make the graph description in the UVC descriptors ambiguous. However, collisions where one of the entities is a streaming output terminal and the other entity is not a streaming terminal are less severe. Streaming output terminals have no controls, and, as they are the final entity in pipelines, they are never referenced in descriptors as source entities. They are referenced by ID only from innput header descriptors, which by definition only reference streaming terminals. For these reasons, we can work around the collision by giving streaming output terminals their own ID namespace. Do so by setting bit UVC_TERM_OUTPUT (15) in the uvc_entity.id field, which is normally never set as the ID is a 8-bit value. This ID change doesn't affect the entity name in the media controller graph as the name isn't constructed from the ID, so there should not be any impact on the uAPI. Although this change handles some ID collisions automagically, keep printing an error in uvc_alloc_new_entity() when a camera has invalid descriptors. Hopefully this message will help vendors fix their invalid descriptors. This new method of handling ID collisions includes a revert of commit 758dbc756aad ("media: uvcvideo: Use heuristic to find stream entity") that attempted to fix the problem urgently due to regression reports. Suggested-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Tested-by: Lili Orosz Co-developed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart Link: https://patch.msgid.link/20251113210400.28618-1-laurent.pinchart@ideasonboard.com Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_driver.c | 54 ++++++++++++++++++------------ drivers/media/usb/uvc/uvcvideo.h | 3 +- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index ee4f54d68349..aa3e8d295e0f 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -165,28 +165,17 @@ static struct uvc_entity *uvc_entity_by_reference(struct uvc_device *dev, return NULL; } -static struct uvc_streaming *uvc_stream_by_id(struct uvc_device *dev, int id) +static struct uvc_streaming *uvc_stream_for_terminal(struct uvc_device *dev, + struct uvc_entity *term) { - struct uvc_streaming *stream, *last_stream; - unsigned int count = 0; + u16 id = UVC_HARDWARE_ENTITY_ID(term->id); + struct uvc_streaming *stream; list_for_each_entry(stream, &dev->streams, list) { - count += 1; - last_stream = stream; if (stream->header.bTerminalLink == id) return stream; } - /* - * If the streaming entity is referenced by an invalid ID, notify the - * user and use heuristics to guess the correct entity. - */ - if (count == 1 && id == UVC_INVALID_ENTITY_ID) { - dev_warn(&dev->intf->dev, - "UVC non compliance: Invalid USB header. The streaming entity has an invalid ID, guessing the correct one."); - return last_stream; - } - return NULL; } @@ -823,10 +812,12 @@ static struct uvc_entity *uvc_alloc_new_entity(struct uvc_device *dev, u16 type, } /* Per UVC 1.1+ spec 3.7.2, the ID is unique. */ - if (uvc_entity_by_id(dev, id)) { - dev_err(&dev->intf->dev, "Found multiple Units with ID %u\n", id); + if (uvc_entity_by_id(dev, UVC_HARDWARE_ENTITY_ID(id))) + dev_err(&dev->intf->dev, "Found multiple Units with ID %u\n", + UVC_HARDWARE_ENTITY_ID(id)); + + if (uvc_entity_by_id(dev, id)) id = UVC_INVALID_ENTITY_ID; - } extra_size = roundup(extra_size, sizeof(*entity->pads)); if (num_pads) @@ -982,6 +973,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev, struct usb_host_interface *alts = dev->intf->cur_altsetting; unsigned int i, n, p, len; const char *type_name; + unsigned int id; u16 type; switch (buffer[2]) { @@ -1120,8 +1112,28 @@ static int uvc_parse_standard_control(struct uvc_device *dev, return 0; } + id = buffer[3]; + + /* + * Some devices, such as the Grandstream GUV3100, exhibit entity + * ID collisions between units and streaming output terminals. + * Move streaming output terminals to their own ID namespace by + * setting bit UVC_TERM_OUTPUT (15), above the ID's 8-bit value. + * The bit is ignored in uvc_stream_for_terminal() when looking + * up the streaming interface for the terminal. + * + * This hack is safe to enable unconditionally, as the ID is not + * used for any other purpose (streaming output terminals have + * no controls and are never referenced as sources in UVC + * descriptors). Other types output terminals can have controls, + * so limit usage of this separate namespace to streaming output + * terminals. + */ + if (type & UVC_TT_STREAMING) + id |= UVC_TERM_OUTPUT; + term = uvc_alloc_new_entity(dev, type | UVC_TERM_OUTPUT, - buffer[3], 1, 0); + id, 1, 0); if (IS_ERR(term)) return PTR_ERR(term); @@ -2118,8 +2130,8 @@ static int uvc_register_terms(struct uvc_device *dev, if (UVC_ENTITY_TYPE(term) != UVC_TT_STREAMING) continue; - stream = uvc_stream_by_id(dev, term->id); - if (stream == NULL) { + stream = uvc_stream_for_terminal(dev, term); + if (!stream) { dev_info(&dev->intf->dev, "No streaming interface found for terminal %u.", term->id); diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index d583425893a5..8480d65ecb85 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -41,7 +41,8 @@ #define UVC_EXT_GPIO_UNIT 0x7ffe #define UVC_EXT_GPIO_UNIT_ID 0x100 -#define UVC_INVALID_ENTITY_ID 0xffff +#define UVC_HARDWARE_ENTITY_ID(id) ((id) & 0xff) +#define UVC_INVALID_ENTITY_ID 0xffff /* ------------------------------------------------------------------------ * Driver specific constants. From 4cf3b6fd54ebb1ebc977bdc47fb6cfcf9a471a22 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Wed, 15 Oct 2025 13:36:42 +0200 Subject: [PATCH 305/307] media: uvcvideo: Return queued buffers on start_streaming() failure Return buffers if streaming fails to start due to uvc_pm_get() error. This bug may be responsible for a warning I got running while :; do yavta -c3 /dev/video0; done on an xHCI controller which failed under this workload. I had no luck reproducing this warning again to confirm. xhci_hcd 0000:09:00.0: HC died; cleaning up usb 13-2: USB disconnect, device number 2 WARNING: CPU: 2 PID: 29386 at drivers/media/common/videobuf2/videobuf2-core.c:1803 vb2_start_streaming+0xac/0x120 Fixes: 7dd56c47784a ("media: uvcvideo: Remove stream->is_streaming field") Cc: stable@vger.kernel.org Signed-off-by: Michal Pecio Reviewed-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20251015133642.3dede646.michal.pecio@gmail.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_queue.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c index 681a74ed09fb..8b8f44b4a045 100644 --- a/drivers/media/usb/uvc/uvc_queue.c +++ b/drivers/media/usb/uvc/uvc_queue.c @@ -177,18 +177,20 @@ static int uvc_start_streaming_video(struct vb2_queue *vq, unsigned int count) ret = uvc_pm_get(stream->dev); if (ret) - return ret; + goto err_buffers; queue->buf_used = 0; ret = uvc_video_start_streaming(stream); - if (ret == 0) - return 0; + if (ret) + goto err_pm; + return 0; + +err_pm: uvc_pm_put(stream->dev); - +err_buffers: uvc_queue_return_buffers(queue, UVC_BUF_STATE_QUEUED); - return ret; } From 40d3ac25c11310bfaa50ed7614846ef75cb69a1e Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 14 Jan 2026 10:32:13 +0000 Subject: [PATCH 306/307] media: uvcvideo: Fix allocation for small frame sizes If a frame has size of less or equal than one packet size uvc_alloc_urb_buffers() is unable to allocate memory for it due to a off-by-one error. Fix the off-by-one-error and now that we are at it, make sure that stream->urb_size has always a valid value when we return from the function, even when an error happens. Fixes: efdc8a9585ce ("V4L/DVB (10295): uvcvideo: Retry URB buffers allocation when the system is low on memory.") Reported-by: Itay Chamiel Closes: https://lore.kernel.org/linux-media/CANiDSCsSoZf2LsCCoWAUbCg6tJT-ypXR1B85aa6rAdMVYr2iBQ@mail.gmail.com/T/#t Co-developed-by: Itay Chamiel Signed-off-by: Itay Chamiel Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Tested-by: Itay Chamiel Link: https://patch.msgid.link/20260114-uvc-alloc-urb-v1-1-cedf3fb66711@chromium.org Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_video.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index 2094e059d7d3..ec76595f3c4b 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1812,7 +1812,7 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream, npackets = UVC_MAX_PACKETS; /* Retry allocations until one succeed. */ - for (; npackets > 1; npackets /= 2) { + for (; npackets > 0; npackets /= 2) { stream->urb_size = psize * npackets; for (i = 0; i < UVC_URBS; ++i) { @@ -1837,6 +1837,7 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream, uvc_dbg(stream->dev, VIDEO, "Failed to allocate URB buffers (%u bytes per packet)\n", psize); + stream->urb_size = 0; return 0; } From c824345288d11e269ce41b36c105715bc2286050 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 14 Jan 2026 10:32:14 +0000 Subject: [PATCH 307/307] media: uvcvideo: Pass allocation size directly to uvc_alloc_urb_buffer The uvc_alloc_urb_buffer() function implicitly depended on the stream->urb_size field, which was set by its caller, uvc_alloc_urb_buffers(). This implicit data flow makes the code harder to follow. More importantly, stream->urb_size was updated within the allocation loop before the allocation was confirmed to be successful. If the allocation failed, the stream object would be left with a urb_size that doesn't correspond to valid, allocated URB buffers. Refactor uvc_alloc_urb_buffer() to accept the buffer size as an explicit argument. This makes the function's dependencies clear and improves the robustness of the error handling path. The stream->urb_size is now set only after a complete and successful allocation. This is a pure refactoring and introduces no functional changes. Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Tested-by: Itay Chamiel Link: https://patch.msgid.link/20260114-uvc-alloc-urb-v1-2-cedf3fb66711@chromium.org Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_video.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index ec76595f3c4b..59eb95a4b70c 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1771,12 +1771,13 @@ static void uvc_free_urb_buffers(struct uvc_streaming *stream) } static bool uvc_alloc_urb_buffer(struct uvc_streaming *stream, - struct uvc_urb *uvc_urb, gfp_t gfp_flags) + struct uvc_urb *uvc_urb, unsigned int size, + gfp_t gfp_flags) { struct usb_device *udev = stream->dev->udev; - uvc_urb->buffer = usb_alloc_noncoherent(udev, stream->urb_size, - gfp_flags, &uvc_urb->dma, + uvc_urb->buffer = usb_alloc_noncoherent(udev, size, gfp_flags, + &uvc_urb->dma, uvc_stream_dir(stream), &uvc_urb->sgt); return !!uvc_urb->buffer; @@ -1813,12 +1814,13 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream, /* Retry allocations until one succeed. */ for (; npackets > 0; npackets /= 2) { - stream->urb_size = psize * npackets; + unsigned int urb_size = psize * npackets; for (i = 0; i < UVC_URBS; ++i) { struct uvc_urb *uvc_urb = &stream->uvc_urb[i]; - if (!uvc_alloc_urb_buffer(stream, uvc_urb, gfp_flags)) { + if (!uvc_alloc_urb_buffer(stream, uvc_urb, urb_size, + gfp_flags)) { uvc_free_urb_buffers(stream); break; } @@ -1830,6 +1832,7 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream, uvc_dbg(stream->dev, VIDEO, "Allocated %u URB buffers of %ux%u bytes each\n", UVC_URBS, npackets, psize); + stream->urb_size = urb_size; return npackets; } } @@ -1837,7 +1840,6 @@ static int uvc_alloc_urb_buffers(struct uvc_streaming *stream, uvc_dbg(stream->dev, VIDEO, "Failed to allocate URB buffers (%u bytes per packet)\n", psize); - stream->urb_size = 0; return 0; }