From: Dan Scally <dan.scally@ideasonboard.com>
To: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Cc: linux-media@vger.kernel.org, devicetree@vger.kernel.org,
linux-renesas-soc@vger.kernel.org, Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Geert Uytterhoeven <geert+renesas@glider.be>,
Magnus Damm <magnus.damm@gmail.com>,
Philipp Zabel <p.zabel@pengutronix.de>,
biju.das.jz@bp.renesas.com
Subject: Re: [PATCH v2 2/3] media: platform: Add Renesas Input Video Control block driver
Date: Tue, 1 Jul 2025 14:33:10 +0100 [thread overview]
Message-ID: <cefac528-9f82-47eb-bb3d-2db1ae5571a8@ideasonboard.com> (raw)
In-Reply-To: <btrgnypglvff3k5lsh6g4ietat55ec5wctap67ylredsgdblrr@e722ix2cy74q>
Hi Jacopo
On 01/07/2025 14:31, Jacopo Mondi wrote:
> Hi Dan
>
> On Tue, Jul 01, 2025 at 02:21:09PM +0100, Dan Scally wrote:
>> Hi Jacopo
>>
>> On 01/07/2025 14:17, Jacopo Mondi wrote:
>>> Hi Dan
>>>
>>> On Tue, Jul 01, 2025 at 02:01:25PM +0100, Dan Scally wrote:
>>>> Hi Jacopo
>>>>
>>>> On 01/07/2025 13:58, Jacopo Mondi wrote:
>>>>> Hi Dan
>>>>>
>>>>> On Tue, Jul 01, 2025 at 12:27:41PM +0100, Dan Scally wrote:
>>>>>> Hi Jacopo
>>>>>>
>>>>> [snip]
>>>>>
>>>>>>>>>> +static bool rzv2h_ivc_pipeline_ready(struct media_pipeline *pipe)
>>>>>>>>>> +{
>>>>>>>>>> + struct media_pipeline_entity_iter iter;
>>>>>>>>>> + unsigned int n_video_devices = 0;
>>>>>>>>>> + struct media_entity *entity;
>>>>>>>>>> + int ret;
>>>>>>>>>> +
>>>>>>>>>> + ret = media_pipeline_entity_iter_init(pipe, &iter);
>>>>>>>>>> + if (ret)
>>>>>>>>>> + return ret;
>>>>>>>>>> +
>>>>>>>>>> + media_pipeline_for_each_entity(pipe, &iter, entity) {
>>>>>>>>>> + if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
>>>>>>>>>> + n_video_devices++;
>>>>>>>>>> + }
>>>>>>>>> This counts the ISP video devices as well, right ?
>>>>>>>> That's right
>>>>>>>>
>>>>>>>>>> +
>>>>>>>>>> + media_pipeline_entity_iter_cleanup(&iter);
>>>>>>>>>> +
>>>>>>>>>> + return n_video_devices == pipe->start_count;
>>>>>>>>> So this checks that all other video devices have started when this one
>>>>>>>>> is started as well. What if we start the IVC first and the ISP later?
>>>>>>>> Doesn't matter which order; nothing happens until they're all started and
>>>>>>>> then the .pipeline_started() callbacks for the entities run.
>>>>>>> Ah sure, thanks, I got it wrong.
>>>>>>>
>>>>>>> I see that all drivers in the series (IVC and mali) that use the
>>>>>>> media_pipeline_started() helper have to implement a function similar
>>>>>>> in spirit to rzv2h_ivc_pipeline_ready(). Can't the framework do that ?
>>>>>>> So that drivers can call media_pipeline_started() [*] unconditionally
>>>>>>> and use its return value to find out if the pipeline has actually
>>>>>>> started or not ?
>>>>>> The steer I got from Laurent and Sakari was that code from mc shouldn't be
>>>>>> checking for MEDIA_ENTITY_TYPE_VIDEO_DEVICE, but perhaps we could have a
>>>>>> V4L2 helper that does that instead?
>>>>> Do you mean this check ?
>>>>>
>>>>> media_pipeline_for_each_entity(pipe, &iter, entity) {
>>>>> if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
>>>>> n_video_devices++;
>>>>> }
>>>> Yes
>>>>
>>>>> We have wrappers like
>>>>>
>>>>> __must_check int video_device_pipeline_start(struct video_device *vdev,
>>>>> struct media_pipeline *pipe)
>>>>>
>>>>> already, so this might become something like
>>>>>
>>>>> int video_device_try_run_pipeline(vdev)
>>>>> {
>>>>> pipe = video_device_pipeline(&ivc->vdev.dev);
>>>>>
>>>>> ret = media_pipeline_entity_iter_init(pipe, &iter);
>>>>> if (ret)
>>>>> return ret;
>>>>>
>>>>> media_pipeline_for_each_entity(pipe, &iter, entity) {
>>>>> if (entity->obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
>>>>> n_video_devices++;
>>>>> }
>>>>>
>>>>> media_pipeline_entity_iter_cleanup(&iter);
>>>>>
>>>>> return (n_video_devices == pipe->start_count) ?
>>>>> media_pipeline_started(pipe) : -ENODEV;
>>>>> }
>>>>>
>>>>> The drivers then should become something like:
>>>>>
>>>>> static int rzv2h_ivc_start_streaming(struct vb2_queue *q, unsigned int count)
>>>>> {
>>>>> struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
>>>>> struct media_pipeline *pipe;
>>>>> int ret;
>>>>>
>>>>> ret = pm_runtime_resume_and_get(ivc->dev);
>>>>> if (ret)
>>>>> goto err_return_buffers;
>>>>>
>>>>> ret = video_device_pipeline_alloc_start(&ivc->vdev.dev);
>>>>> if (ret) {
>>>>> dev_err(ivc->dev, "failed to start media pipeline\n");
>>>>> goto err_pm_runtime_put;
>>>>> }
>>>>>
>>>>> rzv2h_ivc_format_configure(ivc);
>>>>>
>>>>> ivc->buffers.sequence = 0;
>>>>> ivc->vvalid_ifp = 0;
>>>>>
>>>>> if (!video_device_try_run_pipeline(ivc->dev))
>>>>> media_jobs_run_jobs(ivc->sched);
>>>>>
>>>>> return 0;
>>>>>
>>>>> err_pm_runtime_put:
>>>>> pm_runtime_put(ivc->dev);
>>>>> err_return_buffers:
>>>>> rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_QUEUED);
>>>>>
>>>>> return ret;
>>>>> }
>>>>>
>>>>> Removing a bit of boilerplate in all drivers using
>>>>> media_pipeline_started()/stopped() ?
>>>> Yes something like video_device_pipeline_start() is what I was thinking,
>>>> what you've done there looks spot on to me!
>>> You know, looking at what [video|media]_pipeline_[alloc]_start() does,
>>> it could even be renamed to pipeline_validate() and your new functions
>>> named 'start' :)
>>>
>>> Anyway, just for the sake of discussion, one could even create a
>>>
>>> [video|media]_pipeline_[alloc]_run() function that bundles together
>>> the existing _start() functions and the newly proposed _try_run().
>>>
>>> The driver would be an even more compact
>>>
>>> static int rzv2h_ivc_start_streaming(struct vb2_queue *q, unsigned int count)
>>> {
>>> struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
>>> struct media_pipeline *pipe;
>>> int ret;
>>>
>>> ret = pm_runtime_resume_and_get(ivc->dev);
>>> if (ret)
>>> goto err_return_buffers;
>>>
>>> rzv2h_ivc_format_configure(ivc);
>>>
>>> ivc->buffers.sequence = 0;
>>> ivc->vvalid_ifp = 0;
>>>
>>> ret = video_device_pipeline_alloc_run(&ivc->vdev.dev);
>>> if (ret) {
>>> dev_err(ivc->dev, "failed to start media pipeline\n");
>>> goto err_pm_runtime_put;
>>> }
>>>
>>> media_jobs_run_jobs(ivc->sched);
>> Wouldn't this still need to be conditional? We'd need
> Yeah my thinking was that if (ret) we don't get here
>
>> video_device_pipeline_alloc_run() to be able to return something that
>> indicated:
>>
>>
>> 1. Failure
>>
>> 2. Success, but the pipeline isn't ready to start
>>
>> 3. Success and the pipeline is ready to start
>>
>>
>> right?
> But yes, you have 3 possible return states, something that might make
> the API cumbersome to design
>
>>> return 0;
>>> }
>>>
>>> Do you see a use case for first starting the pipeline then trying to
>>> run it in a separate step ?
>>
>> Not off the top of my head...they're both operations that ought to happen in
>> .start_streaming()...but do you propose replacing the existing
>> video_device_pipeline_start() with a bundled function, or just adding a new
>> function and retaining the old one?
>>
> Not replacing them no, otherwise all drivers should be ported to use
> pipeline_started(), right ?
Yes
> However given the "three return values" problem, we might want to keep
> _try_run() separate ?
I'll have a think and post something...I'll probably bundle this and the pipeline_started() /
pipeline_stopped() entity ops patch
>> Dan
>>
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static int rzv2h_ivc_start_streaming(struct vb2_queue *q, unsigned int count)
>>>>>>>>>> +{
>>>>>>>>>> + struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
>>>>>>>>>> + struct media_pipeline *pipe;
>>>>>>>>>> + int ret;
>>>>>>>>>> +
>>>>>>>>>> + ret = pm_runtime_resume_and_get(ivc->dev);
>>>>>>>>>> + if (ret)
>>>>>>>>>> + goto err_return_buffers;
>>>>>>>>>> +
>>>>>>>>>> + ret = video_device_pipeline_alloc_start(&ivc->vdev.dev);
>>>>>>>>>> + if (ret) {
>>>>>>>>>> + dev_err(ivc->dev, "failed to start media pipeline\n");
>>>>>>>>>> + goto err_pm_runtime_put;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + rzv2h_ivc_format_configure(ivc);
>>>>>>>>>> +
>>>>>>>>>> + ivc->buffers.sequence = 0;
>>>>>>>>>> +
>>>>>>>>>> + spin_lock(&ivc->spinlock);
>>>>>>>>>> + ivc->vvalid_ifp = 0;
>>>>>>>>>> + spin_unlock(&ivc->spinlock);
>>>>>>>>> scoped_guard() maybe, and I wonder if you need this if you initialize
>>>>>>>>> the variable before resume_and_get
>>>>>>>> It was just to guarantee that it's in a known state, but I can probably drop it
>>>>>>> I don't contest resetting it to 0, I'm just pointing out you can do
>>>>>>> that earlier and avoid locking ?
>>>>>> Ah! Yes certainly true.
>>>>>>>>>> +
>>>>>>>>>> + pipe = video_device_pipeline(&ivc->vdev.dev);
>>>>>>>>>> + if (rzv2h_ivc_pipeline_ready(pipe)) {
>>>>>>>>>> + ret = media_pipeline_started(pipe);
>>>>>>>>>> + if (ret)
>>>>>>>>>> + goto err_stop_pipeline;
>>>>>>>>>> +
>>>>>>>>>> + media_jobs_run_jobs(ivc->sched);
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + return 0;
>>>>>>>>>> +
>>>>>>>>>> +err_stop_pipeline:
>>>>>>>>>> + video_device_pipeline_stop(&ivc->vdev.dev);
>>>>>>>>>> +err_pm_runtime_put:
>>>>>>>>>> + pm_runtime_put(ivc->dev);
>>>>>>>>>> +err_return_buffers:
>>>>>>>>>> + rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_QUEUED);
>>>>>>>>>> +
>>>>>>>>>> + return ret;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static void rzv2h_ivc_stop_streaming(struct vb2_queue *q)
>>>>>>>>>> +{
>>>>>>>>>> + struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
>>>>>>>>>> + struct media_pipeline *pipe;
>>>>>>>>>> +
>>>>>>>>>> + pipe = video_device_pipeline(&ivc->vdev.dev);
>>>>>>>>>> + if (rzv2h_ivc_pipeline_ready(pipe)) {
>>>>>>>>>> + media_pipeline_stopped(pipe);
>>>>>>>>>> + media_jobs_cancel_jobs(ivc->sched);
>>>>>>>>>> + }
>>>>>>>>> I suspect I already asked about this, but this returns true only if
>>>>>>>>> all video devices have started right ?
>>>>>>>> Right
>>>>>>>>> So what if ISP is stopped first
>>>>>>>>> then IVC ?
>>>>>>>> It doesn't matter which gets stopped first, it's just to make sure we run
>>>>>>>> media_pipeline_stopped() and media_jobs_cancel_jobs() whenever the _first_
>>>>>>>> video device is stopped
>>>>>>>>>> +
>>>>>>>>>> + rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_ERROR);
>>>>>>>>>> + video_device_pipeline_stop(&ivc->vdev.dev);
>>>>>>>>>> + pm_runtime_mark_last_busy(ivc->dev);
>>>>>>>>>> + pm_runtime_put_autosuspend(ivc->dev);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static const struct vb2_ops rzv2h_ivc_vb2_ops = {
>>>>>>>>>> + .queue_setup = &rzv2h_ivc_queue_setup,
>>>>>>>>>> + .buf_queue = &rzv2h_ivc_buf_queue,
>>>>>>>>>> + .wait_prepare = vb2_ops_wait_prepare,
>>>>>>>>>> + .wait_finish = vb2_ops_wait_finish,
>>>>>>>>>> + .start_streaming = &rzv2h_ivc_start_streaming,
>>>>>>>>>> + .stop_streaming = &rzv2h_ivc_stop_streaming,
>>>>>>>>>> +};
>>>>>>>>>> +
>>>>>>>>>> +static const struct rzv2h_ivc_format *
>>>>>>>>>> +rzv2h_ivc_format_from_pixelformat(u32 fourcc)
>>>>>>>>>> +{
>>>>>>>>>> + unsigned int i;
>>>>>>>>> nit: Could live inside the for loop
>>>>>>>> Ack
>>>>>>>>>> +
>>>>>>>>>> + for (i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++)
>>>>>>>>>> + if (fourcc == rzv2h_ivc_formats[i].fourcc)
>>>>>>>>>> + return &rzv2h_ivc_formats[i];
>>>>>>>>>> +
>>>>>>>>>> + return &rzv2h_ivc_formats[0];
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static int rzv2h_ivc_enum_fmt_vid_out(struct file *file, void *fh,
>>>>>>>>>> + struct v4l2_fmtdesc *f)
>>>>>>>>>> +{
>>>>>>>>>> + if (f->index >= ARRAY_SIZE(rzv2h_ivc_formats))
>>>>>>>>>> + return -EINVAL;
>>>>>>>>>> +
>>>>>>>>>> + f->pixelformat = rzv2h_ivc_formats[f->index].fourcc;
>>>>>>>>>> + return 0;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static int rzv2h_ivc_g_fmt_vid_out(struct file *file, void *fh,
>>>>>>>>>> + struct v4l2_format *f)
>>>>>>>>>> +{
>>>>>>>>>> + struct rzv2h_ivc *ivc = video_drvdata(file);
>>>>>>>>>> +
>>>>>>>>>> + f->fmt.pix = ivc->format.pix;
>>>>>>>>>> +
>>>>>>>>>> + return 0;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static void rzv2h_ivc_try_fmt(struct v4l2_pix_format *pix,
>>>>>>>>>> + const struct rzv2h_ivc_format *fmt)
>>>>>>>>>> +{
>>>>>>>>>> + pix->pixelformat = fmt->fourcc;
>>>>>>>>>> +
>>>>>>>>>> + pix->width = clamp(pix->width, RZV2H_IVC_MIN_WIDTH,
>>>>>>>>>> + RZV2H_IVC_MAX_WIDTH);
>>>>>>>>>> + pix->height = clamp(pix->height, RZV2H_IVC_MIN_HEIGHT,
>>>>>>>>>> + RZV2H_IVC_MAX_HEIGHT);
>>>>>>>>>> +
>>>>>>>>>> + pix->field = V4L2_FIELD_NONE;
>>>>>>>>>> + pix->colorspace = V4L2_COLORSPACE_RAW;
>>>>>>>>>> + pix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>>>>>>>> + pix->quantization = V4L2_QUANTIZATION_DEFAULT;
>>>>>>>>> Same as per the subdevice use explicit values, or at least the
>>>>>>>>> DEFAULT() macros
>>>>>>>>>
>>>>>>>>>> +
>>>>>>>>>> + v4l2_fill_pixfmt(pix, pix->pixelformat, pix->width, pix->height);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static void rzv2h_ivc_set_format(struct rzv2h_ivc *ivc,
>>>>>>>>>> + struct v4l2_pix_format *pix)
>>>>>>>>>> +{
>>>>>>>>>> + const struct rzv2h_ivc_format *fmt;
>>>>>>>>>> +
>>>>>>>>>> + fmt = rzv2h_ivc_format_from_pixelformat(pix->pixelformat);
>>>>>>>>>> +
>>>>>>>>>> + rzv2h_ivc_try_fmt(pix, fmt);
>>>>>>>>>> + ivc->format.pix = *pix;
>>>>>>>>>> + ivc->format.fmt = fmt;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static int rzv2h_ivc_s_fmt_vid_out(struct file *file, void *fh,
>>>>>>>>>> + struct v4l2_format *f)
>>>>>>>>>> +{
>>>>>>>>>> + struct rzv2h_ivc *ivc = video_drvdata(file);
>>>>>>>>>> + struct v4l2_pix_format *pix = &f->fmt.pix;
>>>>>>>>>> +
>>>>>>>>>> + if (vb2_is_busy(&ivc->vdev.vb2q))
>>>>>>>>>> + return -EBUSY;
>>>>>>>>>> +
>>>>>>>>>> + rzv2h_ivc_set_format(ivc, pix);
>>>>>>>>>> +
>>>>>>>>>> + return 0;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static int rzv2h_ivc_try_fmt_vid_out(struct file *file, void *fh,
>>>>>>>>>> + struct v4l2_format *f)
>>>>>>>>>> +{
>>>>>>>>>> + const struct rzv2h_ivc_format *fmt;
>>>>>>>>>> +
>>>>>>>>>> + fmt = rzv2h_ivc_format_from_pixelformat(f->fmt.pix.pixelformat);
>>>>>>>>>> +
>>>>>>>>>> + rzv2h_ivc_try_fmt(&f->fmt.pix, fmt);
>>>>>>>>> nit: maybe remove the previous empty line and add one before return ?
>>>>>>>>>
>>>>>>>>>> + return 0;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static int rzv2h_ivc_querycap(struct file *file, void *fh,
>>>>>>>>>> + struct v4l2_capability *cap)
>>>>>>>>>> +{
>>>>>>>>>> + strscpy(cap->driver, "rzv2h-ivc", sizeof(cap->driver));
>>>>>>>>>> + strscpy(cap->card, "Renesas Input Video Control", sizeof(cap->card));
>>>>>>>>>> +
>>>>>>>>>> + return 0;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static const struct v4l2_ioctl_ops rzv2h_ivc_v4l2_ioctl_ops = {
>>>>>>>>>> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
>>>>>>>>>> + .vidioc_querybuf = vb2_ioctl_querybuf,
>>>>>>>>>> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
>>>>>>>>>> + .vidioc_qbuf = vb2_ioctl_qbuf,
>>>>>>>>>> + .vidioc_expbuf = vb2_ioctl_expbuf,
>>>>>>>>>> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
>>>>>>>>>> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
>>>>>>>>>> + .vidioc_streamon = vb2_ioctl_streamon,
>>>>>>>>>> + .vidioc_streamoff = vb2_ioctl_streamoff,
>>>>>>>>>> + .vidioc_enum_fmt_vid_out = rzv2h_ivc_enum_fmt_vid_out,
>>>>>>>>>> + .vidioc_g_fmt_vid_out = rzv2h_ivc_g_fmt_vid_out,
>>>>>>>>>> + .vidioc_s_fmt_vid_out = rzv2h_ivc_s_fmt_vid_out,
>>>>>>>>>> + .vidioc_try_fmt_vid_out = rzv2h_ivc_try_fmt_vid_out,
>>>>>>>>>> + .vidioc_querycap = rzv2h_ivc_querycap,
>>>>>>>>>> + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
>>>>>>>>>> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
>>>>>>>>>> +};
>>>>>>>>>> +
>>>>>>>>>> +static const struct v4l2_file_operations rzv2h_ivc_v4l2_fops = {
>>>>>>>>>> + .owner = THIS_MODULE,
>>>>>>>>>> + .unlocked_ioctl = video_ioctl2,
>>>>>>>>>> + .open = v4l2_fh_open,
>>>>>>>>>> + .release = vb2_fop_release,
>>>>>>>>>> + .poll = vb2_fop_poll,
>>>>>>>>>> + .mmap = vb2_fop_mmap,
>>>>>>>>>> +};
>>>>>>>>>> +
>>>>>>>>>> +static bool rzv2h_ivc_job_ready(void *data)
>>>>>>>>>> +{
>>>>>>>>>> + struct rzv2h_ivc *ivc = data;
>>>>>>>>>> +
>>>>>>>>>> + guard(spinlock)(&ivc->buffers.lock);
>>>>>>>>>> +
>>>>>>>>>> + if (list_empty(&ivc->buffers.pending))
>>>>>>>>>> + return false;
>>>>>>>>>> +
>>>>>>>>>> + return true;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static void rzv2h_ivc_job_queue(void *data)
>>>>>>>>>> +{
>>>>>>>>>> + struct rzv2h_ivc *ivc = data;
>>>>>>>>>> + struct rzv2h_ivc_buf *buf;
>>>>>>>>>> +
>>>>>>>>>> + /*
>>>>>>>>>> + * We need to move an entry from the pending queue to the input queue
>>>>>>>>>> + * here. We know that there is one, or .check_dep() would not have
>>>>>>>>>> + * allowed us to get this far. The entry needs to be removed or the same
>>>>>>>>>> + * check would allow a new job to be queued for the exact same buffer.
>>>>>>>>>> + */
>>>>>>>>>> + guard(spinlock)(&ivc->buffers.lock);
>>>>>>>>>> + buf = list_first_entry(&ivc->buffers.pending,
>>>>>>>>>> + struct rzv2h_ivc_buf, queue);
>>>>>>>>>> + list_move_tail(&buf->queue, &ivc->buffers.queue);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static void rzv2h_ivc_job_abort(void *data)
>>>>>>>>>> +{
>>>>>>>>>> + struct rzv2h_ivc *ivc = data;
>>>>>>>>>> + struct rzv2h_ivc_buf *buf;
>>>>>>>>>> +
>>>>>>>>>> + guard(spinlock)(&ivc->buffers.lock);
>>>>>>>>>> + buf = list_first_entry(&ivc->buffers.queue,
>>>>>>>>>> + struct rzv2h_ivc_buf, queue);
>>>>>>>>>> +
>>>>>>>>>> + if (buf)
>>>>>>>>>> + list_move(&buf->queue, &ivc->buffers.pending);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static int rzv2h_ivc_job_add_steps(struct media_job *job, void *data)
>>>>>>>>>> +{
>>>>>>>>>> + struct rzv2h_ivc *ivc = data;
>>>>>>>>>> + int ret;
>>>>>>>>>> +
>>>>>>>>>> + ret = media_jobs_add_job_step(job, rzv2h_ivc_set_next_buffer, ivc,
>>>>>>>>>> + MEDIA_JOBS_FL_STEP_ANYWHERE, 0);
>>>>>>>>>> + if (ret)
>>>>>>>>>> + return ret;
>>>>>>>>>> +
>>>>>>>>>> + /*
>>>>>>>>>> + * This stage will be the second to last one to run - the ISP driver may
>>>>>>>>>> + * have some post-frame processing to do.
>>>>>>>>>> + */
>>>>>>>>>> + return media_jobs_add_job_step(job, rzv2h_ivc_transfer_buffer, ivc,
>>>>>>>>>> + MEDIA_JOBS_FL_STEP_FROM_BACK, 1);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +static struct media_job_contributor_ops rzv2h_ivc_media_job_ops = {
>>>>>>>>>> + .add_steps = rzv2h_ivc_job_add_steps,
>>>>>>>>>> + .ready = rzv2h_ivc_job_ready,
>>>>>>>>>> + .queue = rzv2h_ivc_job_queue,
>>>>>>>>>> + .abort = rzv2h_ivc_job_abort
>>>>>>>>>> +};
>>>>>>>>>> +
>>>>>>>>>> +int rzv2h_initialise_video_dev_and_queue(struct rzv2h_ivc *ivc,
>>>>>>>>>> + struct v4l2_device *v4l2_dev)
>>>>>>>>>> +{
>>>>>>>>>> + struct v4l2_pix_format pix = { };
>>>>>>>>>> + struct video_device *vdev;
>>>>>>>>>> + struct vb2_queue *vb2q;
>>>>>>>>>> + int ret;
>>>>>>>>>> +
>>>>>>>>>> + spin_lock_init(&ivc->buffers.lock);
>>>>>>>>>> + INIT_LIST_HEAD(&ivc->buffers.queue);
>>>>>>>>>> + INIT_LIST_HEAD(&ivc->buffers.pending);
>>>>>>>>>> + init_waitqueue_head(&ivc->buffers.wq);
>>>>>>>>>> +
>>>>>>>>>> + /* Initialise vb2 queue */
>>>>>>>>>> + vb2q = &ivc->vdev.vb2q;
>>>>>>>>>> + vb2q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
>>>>>>>>> it's my understandin that MPLANE API is usually preferred also for devices
>>>>>>>>> that only support single planar formats
>>>>>>>> Oh ok - thanks, I'll make the switch
>>>>>>>>>> + vb2q->io_modes = VB2_MMAP | VB2_DMABUF;
>>>>>>>>>> + vb2q->drv_priv = ivc;
>>>>>>>>>> + vb2q->mem_ops = &vb2_dma_contig_memops;
>>>>>>>>>> + vb2q->ops = &rzv2h_ivc_vb2_ops;
>>>>>>>>>> + vb2q->buf_struct_size = sizeof(struct rzv2h_ivc_buf);
>>>>>>>>>> + vb2q->min_queued_buffers = 0;
>>>>>>>>> You can spare this, or keep it if you want it explicit
>>>>>>>> I'll probably keep it
>>>>>>>>>> + vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>>>>>>>>>> + vb2q->lock = &ivc->lock;
>>>>>>>>>> + vb2q->dev = ivc->dev;
>>>>>>>>>> +
>>>>>>>>>> + ret = vb2_queue_init(vb2q);
>>>>>>>>>> + if (ret)
>>>>>>>>>> + return dev_err_probe(ivc->dev, ret, "vb2 queue init failed\n");
>>>>>>>>>> +
>>>>>>>>>> + /* Initialise Video Device */
>>>>>>>>>> + vdev = &ivc->vdev.dev;
>>>>>>>>>> + strscpy(vdev->name, "rzv2h-ivc", sizeof(vdev->name));
>>>>>>>>>> + vdev->release = video_device_release_empty;
>>>>>>>>>> + vdev->fops = &rzv2h_ivc_v4l2_fops;
>>>>>>>>>> + vdev->ioctl_ops = &rzv2h_ivc_v4l2_ioctl_ops;
>>>>>>>>>> + vdev->lock = &ivc->lock;
>>>>>>>>>> + vdev->v4l2_dev = v4l2_dev;
>>>>>>>>>> + vdev->queue = vb2q;
>>>>>>>>>> + vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
>>>>>>>>>> + vdev->vfl_dir = VFL_DIR_TX;
>>>>>>>>>> + video_set_drvdata(vdev, ivc);
>>>>>>>>>> +
>>>>>>>>>> + pix.pixelformat = V4L2_PIX_FMT_SRGGB16;
>>>>>>>>>> + pix.width = RZV2H_IVC_DEFAULT_WIDTH;
>>>>>>>>>> + pix.height = RZV2H_IVC_DEFAULT_HEIGHT;
>>>>>>>>>> + rzv2h_ivc_set_format(ivc, &pix);
>>>>>>>>>> +
>>>>>>>>>> + ivc->vdev.pad.flags = MEDIA_PAD_FL_SOURCE;
>>>>>>>>>> + ivc->vdev.dev.entity.ops = &rzv2h_ivc_media_ops;
>>>>>>>>>> + ret = media_entity_pads_init(&ivc->vdev.dev.entity, 1, &ivc->vdev.pad);
>>>>>>>>>> + if (ret)
>>>>>>>>>> + goto err_release_vb2q;
>>>>>>>>>> +
>>>>>>>>>> + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>>>>>>>>> + if (ret) {
>>>>>>>>>> + dev_err(ivc->dev, "failed to register IVC video device\n");
>>>>>>>>>> + goto err_cleanup_vdev_entity;
>>>>>>>>>> + }
>>>>>>>>> What is the path that registers the subdevice devnode to userspace ?
>>>>>>>>> IOW I was expecting to see v4l2_device_register_subdev_nodes()
>>>>>>>>> somewhere
>>>>>>>> That's in the ISP driver - the IVC's subdevice connects through the V4L2
>>>>>>>> asynchronous API to the ISP's notifier, and the notifier's .complete()
>>>>>>>> callback runs v4l2_device_register_subdev_nodes()
>>>>>>>>
>>>>>>> Ack, sure, thanks for clarifying it!
>>>>>>>
>>>>>>>>>> +
>>>>>>>>>> + ret = media_create_pad_link(&vdev->entity, 0, &ivc->subdev.sd.entity,
>>>>>>>>>> + RZV2H_IVC_SUBDEV_SINK_PAD,
>>>>>>>>>> + MEDIA_LNK_FL_ENABLED |
>>>>>>>>>> + MEDIA_LNK_FL_IMMUTABLE);
>>>>>>>>>> + if (ret) {
>>>>>>>>>> + dev_err(ivc->dev, "failed to create media link\n");
>>>>>>>>>> + goto err_unregister_vdev;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + ivc->sched = media_jobs_get_scheduler(vdev->entity.graph_obj.mdev);
>>>>>>>>>> + if (IS_ERR(ivc->sched)) {
>>>>>>>>>> + ret = PTR_ERR(ivc->sched);
>>>>>>>>>> + goto err_remove_link;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + ret = media_jobs_register_job_contributor(ivc->sched,
>>>>>>>>>> + &rzv2h_ivc_media_job_ops, ivc,
>>>>>>>>>> + MEDIA_JOB_TYPE_PIPELINE_PULSE);
>>>>>>>>>> + if (ret)
>>>>>>>>>> + goto err_put_media_job_scheduler;
>>>>>>>>>> +
>>>>>>>>>> + return 0;
>>>>>>>>>> +
>>>>>>>>>> +err_put_media_job_scheduler:
>>>>>>>>>> + media_jobs_put_scheduler(ivc->sched);
>>>>>>>>>> +err_remove_link:
>>>>>>>>>> + media_entity_remove_links(&vdev->entity);
>>>>>>>>>> +err_unregister_vdev:
>>>>>>>>>> + video_unregister_device(vdev);
>>>>>>>>>> +err_cleanup_vdev_entity:
>>>>>>>>>> + media_entity_cleanup(&vdev->entity);
>>>>>>>>>> +err_release_vb2q:
>>>>>>>>>> + vb2_queue_release(vb2q);
>>>>>>>>>> +
>>>>>>>>>> + return ret;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc)
>>>>>>>>>> +{
>>>>>>>>>> + struct video_device *vdev = &ivc->vdev.dev;
>>>>>>>>>> + struct vb2_queue *vb2q = &ivc->vdev.vb2q;
>>>>>>>>>> +
>>>>>>>>>> + if (!ivc->sched)
>>>>>>>>>> + return;
>>>>>>>>>> +
>>>>>>>>>> + media_jobs_put_scheduler(ivc->sched);
>>>>>>>>>> + vb2_video_unregister_device(vdev);
>>>>>>>>>> + media_entity_cleanup(&vdev->entity);
>>>>>>>>>> + vb2_queue_release(vb2q);
>>>>>>>>> Shouldn't you get here also in case of !ivc->sched ?
>>>>>>>> This driver (at least in this version) should always have a sched pointer,
>>>>>>>> so this was just a convenient way to check if initialisation finished before
>>>>>>>> trying to deinit anything...it'll probably change though.
>>>>>>> I see, a comment to explain that might be enough!
>>>>>> Sure - I'll add one.
>>>>>>
>>>>>>
>>>>>> Thanks
>>>>>>
>>>>>> Dan
>>>>>>
>>>>>>> Thanks
>>>>>>> j
>>>>>>>
>>>>>>>
>>>>>>>>>> +}
>>>>>>>>>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 0000000000000000000000000000000000000000..d2e310ce868125772d97259619b9369ccbcefe3d
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
>>>>>>>>>> @@ -0,0 +1,133 @@
>>>>>>>>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>>>>>>>>> +/*
>>>>>>>>>> + * Renesas RZ/V2H Input Video Control Block driver
>>>>>>>>>> + *
>>>>>>>>>> + * Copyright (C) 2025 Ideas on Board Oy
>>>>>>>>>> + */
>>>>>>>>>> +
>>>>>>>>>> +#include <linux/clk.h>
>>>>>>>>>> +#include <linux/list.h>
>>>>>>>>>> +#include <linux/mutex.h>
>>>>>>>>>> +#include <linux/reset.h>
>>>>>>>>>> +#include <linux/spinlock.h>
>>>>>>>>>> +#include <linux/types.h>
>>>>>>>>>> +#include <linux/videodev2.h>
>>>>>>>>>> +#include <linux/wait.h>
>>>>>>>>>> +
>>>>>>>>>> +#include <media/media-entity.h>
>>>>>>>>>> +#include <media/v4l2-device.h>
>>>>>>>>>> +#include <media/v4l2-subdev.h>
>>>>>>>>>> +#include <media/videobuf2-core.h>
>>>>>>>>>> +#include <media/videobuf2-v4l2.h>
>>>>>>>>>> +
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_PLNUM 0x0000
>>>>>>>>>> +#define RZV2H_IVC_ONE_EXPOSURE 0x00
>>>>>>>>>> +#define RZV2H_IVC_TWO_EXPOSURE 0x01
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_PXFMT 0x0004
>>>>>>>>>> +#define RZV2H_IVC_INPUT_FMT_MIPI (0 << 16)
>>>>>>>>>> +#define RZV2H_IVC_INPUT_FMT_CRU_PACKED (1 << 16)
>>>>>>>>>> +#define RZV2H_IVC_PXFMT_DTYPE GENMASK(7, 0)
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_SADDL_P0 0x0010
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_SADDH_P0 0x0014
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_SADDL_P1 0x0018
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_SADDH_P1 0x001c
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_HSIZE 0x0020
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_VSIZE 0x0024
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_BLANK 0x0028
>>>>>>>>>> +#define RZV2H_IVC_VBLANK(x) ((x) << 16)
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_STRD 0x0030
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_ISSU 0x0040
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_ERACT 0x0048
>>>>>>>>>> +#define RZV2H_IVC_REG_FM_CONTEXT 0x0100
>>>>>>>>>> +#define RZV2H_IVC_SOFTWARE_CFG 0x00
>>>>>>>>>> +#define RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG BIT(0)
>>>>>>>>>> +#define RZV2H_IVC_MULTI_CONTEXT_SW_HW_CFG BIT(1)
>>>>>>>>>> +#define RZV2H_IVC_REG_FM_MCON 0x0104
>>>>>>>>>> +#define RZV2H_IVC_REG_FM_FRCON 0x0108
>>>>>>>>>> +#define RZV2H_IVC_REG_FM_STOP 0x010c
>>>>>>>>>> +#define RZV2H_IVC_REG_FM_INT_EN 0x0120
>>>>>>>>>> +#define RZV2H_IVC_VVAL_IFPE BIT(0)
>>>>>>>>>> +#define RZV2H_IVC_REG_FM_INT_STA 0x0124
>>>>>>>>>> +#define RZV2H_IVC_REG_AXIRX_FIFOCAP0 0x0208
>>>>>>>>>> +#define RZV2H_IVC_REG_CORE_CAPCON 0x020c
>>>>>>>>>> +#define RZV2H_IVC_REG_CORE_FIFOCAP0 0x0228
>>>>>>>>>> +#define RZV2H_IVC_REG_CORE_FIFOCAP1 0x022c
>>>>>>>>>> +
>>>>>>>>>> +#define RZV2H_IVC_MIN_WIDTH 640
>>>>>>>>>> +#define RZV2H_IVC_MAX_WIDTH 4096
>>>>>>>>>> +#define RZV2H_IVC_MIN_HEIGHT 480
>>>>>>>>>> +#define RZV2H_IVC_MAX_HEIGHT 4096
>>>>>>>>>> +#define RZV2H_IVC_DEFAULT_WIDTH 1920
>>>>>>>>>> +#define RZV2H_IVC_DEFAULT_HEIGHT 1080
>>>>>>>>>> +
>>>>>>>>>> +#define RZV2H_IVC_NUM_CLOCKS 3
>>>>>>>>>> +#define RZV2H_IVC_NUM_RESETS 3
>>>>>>>>>> +
>>>>>>>>>> +struct device;
>>>>>>>>>> +
>>>>>>>>>> +enum rzv2h_ivc_subdev_pads {
>>>>>>>>>> + RZV2H_IVC_SUBDEV_SINK_PAD,
>>>>>>>>>> + RZV2H_IVC_SUBDEV_SOURCE_PAD,
>>>>>>>>>> + RZV2H_IVC_NUM_SUBDEV_PADS
>>>>>>>>>> +};
>>>>>>>>>> +
>>>>>>>>>> +struct rzv2h_ivc_format {
>>>>>>>>>> + u32 fourcc;
>>>>>>>>>> + /*
>>>>>>>>>> + * The CRU packed pixel formats are bayer-order agnostic, so each could
>>>>>>>>>> + * support any one of the 4 possible media bus formats.
>>>>>>>>>> + */
>>>>>>>>>> + u32 mbus_codes[4];
>>>>>>>>>> + u8 dtype;
>>>>>>>>>> +};
>>>>>>>>>> +
>>>>>>>>>> +struct rzv2h_ivc {
>>>>>>>>>> + struct device *dev;
>>>>>>>>>> + void __iomem *base;
>>>>>>>>>> + struct clk_bulk_data clks[RZV2H_IVC_NUM_CLOCKS];
>>>>>>>>>> + struct reset_control_bulk_data resets[RZV2H_IVC_NUM_RESETS];
>>>>>>>>>> + int irqnum;
>>>>>>>>>> + u8 vvalid_ifp;
>>>>>>>>>> +
>>>>>>>>>> + struct {
>>>>>>>>>> + struct video_device dev;
>>>>>>>>>> + struct vb2_queue vb2q;
>>>>>>>>>> + struct media_pad pad;
>>>>>>>>>> + } vdev;
>>>>>>>>>> +
>>>>>>>>>> + struct {
>>>>>>>>>> + struct v4l2_subdev sd;
>>>>>>>>>> + struct media_pad pads[RZV2H_IVC_NUM_SUBDEV_PADS];
>>>>>>>>>> + } subdev;
>>>>>>>>>> +
>>>>>>>>>> + struct {
>>>>>>>>>> + /* Spinlock to guard buffer queue */
>>>>>>>>>> + spinlock_t lock;
>>>>>>>>>> + wait_queue_head_t wq;
>>>>>>>>>> + struct list_head queue;
>>>>>>>>>> + struct list_head pending;
>>>>>>>>>> + struct rzv2h_ivc_buf *curr;
>>>>>>>>>> + unsigned int sequence;
>>>>>>>>>> + } buffers;
>>>>>>>>>> +
>>>>>>>>>> + struct media_job_scheduler *sched;
>>>>>>>>>> +
>>>>>>>>>> + struct {
>>>>>>>>>> + struct v4l2_pix_format pix;
>>>>>>>>>> + const struct rzv2h_ivc_format *fmt;
>>>>>>>>>> + } format;
>>>>>>>>>> +
>>>>>>>>>> + /* Mutex to provide to vb2 */
>>>>>>>>>> + struct mutex lock;
>>>>>>>>>> + /* Lock to protect the interrupt counter */
>>>>>>>>>> + spinlock_t spinlock;
>>>>>>>>>> +};
>>>>>>>>>> +
>>>>>>>>>> +int rzv2h_initialise_video_dev_and_queue(struct rzv2h_ivc *ivc,
>>>>>>>>>> + struct v4l2_device *v4l2_dev);
>>>>>>>>>> +void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc);
>>>>>>>>>> +int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc);
>>>>>>>>>> +void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc);
>>>>>>>>>> +void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val);
>>>>>>>>>> +void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr,
>>>>>>>>>> + u32 mask, u32 val);
>>>>>>>>>>
>>>>>>>>> As agreed, I didn't review the job scheduler part but only the IVC
>>>>>>>>> specific bits. A few nits here and there and next version should be
>>>>>>>>> good to go!
>>>>>>>>>
>>>>>>>>> Thanks
>>>>>>>>> j
>>>>>>>>>
>>>>>>>>>> --
>>>>>>>>>> 2.34.1
>>>>>>>>>>
>>>>>>>>>>
next prev parent reply other threads:[~2025-07-01 13:33 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-06-24 12:35 [PATCH v2 0/3] Add Input Video Control Block driver for RZ/V2H Daniel Scally
2025-06-24 12:35 ` [PATCH v2 1/3] dt-bindings: media: Add bindings for the RZ/V2H IVC block Daniel Scally
2025-06-24 13:16 ` Krzysztof Kozlowski
2025-06-24 14:17 ` Dan Scally
2025-06-24 15:41 ` Krzysztof Kozlowski
2025-06-24 16:45 ` Geert Uytterhoeven
2025-06-24 17:50 ` Dan Scally
2025-06-24 12:35 ` [PATCH v2 2/3] media: platform: Add Renesas Input Video Control block driver Daniel Scally
2025-06-30 16:05 ` Jacopo Mondi
2025-06-30 21:32 ` Dan Scally
2025-07-01 11:10 ` Jacopo Mondi
2025-07-01 11:27 ` Dan Scally
2025-07-01 12:58 ` Jacopo Mondi
2025-07-01 13:01 ` Dan Scally
2025-07-01 13:17 ` Jacopo Mondi
2025-07-01 13:21 ` Dan Scally
2025-07-01 13:31 ` Jacopo Mondi
2025-07-01 13:33 ` Dan Scally [this message]
2025-07-01 8:34 ` Philipp Zabel
2025-07-01 13:24 ` Dan Scally
2025-06-24 12:36 ` [PATCH v2 3/3] MAINTAINERS: Add entry for rzv2h-ivc driver Daniel Scally
2025-06-24 12:52 ` [PATCH v2 0/3] Add Input Video Control Block driver for RZ/V2H Krzysztof Kozlowski
2025-06-24 13:10 ` Dan Scally
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cefac528-9f82-47eb-bb3d-2db1ae5571a8@ideasonboard.com \
--to=dan.scally@ideasonboard.com \
--cc=biju.das.jz@bp.renesas.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=geert+renesas@glider.be \
--cc=jacopo.mondi@ideasonboard.com \
--cc=krzk+dt@kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=linux-renesas-soc@vger.kernel.org \
--cc=magnus.damm@gmail.com \
--cc=p.zabel@pengutronix.de \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox