From 3982d2eeb1bdf7a9c14e4ff69015cb456f11da51 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 6 Jul 2026 12:25:43 +0100 Subject: [PATCH 001/596] media: streamzap: Add missing rc_unregister_device() If usb_submit_urb() fails during probe, then the error path is missing a call to rc_unregister_device(), which will leak various things like the input device. Fixes: 42844992664f ("media: rc: streamzap: Error handling in probe") Signed-off-by: Sean Young Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/streamzap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/rc/streamzap.c b/drivers/media/rc/streamzap.c index 307985d74fe8..41195ad82734 100644 --- a/drivers/media/rc/streamzap.c +++ b/drivers/media/rc/streamzap.c @@ -365,6 +365,7 @@ static int streamzap_probe(struct usb_interface *intf, return 0; rc_submit_fail: + rc_unregister_device(sz->rdev); rc_free_device(sz->rdev); usb_set_intfdata(intf, NULL); rc_dev_fail: From cd49f62f4161c7340d9c9f53b67757575900c0bc Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 6 Jul 2026 13:57:10 +0100 Subject: [PATCH 002/596] media: redrat3: Ensure we don't read beyond the end of the packet The length and offset is provided by the usb device, so it should be validated. Fixes: 2154be651b90 ("[media] redrat3: new rc-core IR transceiver device driver") Signed-off-by: Sean Young Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/redrat3.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/media/rc/redrat3.c b/drivers/media/rc/redrat3.c index 3f828a564e19..658e223a4745 100644 --- a/drivers/media/rc/redrat3.c +++ b/drivers/media/rc/redrat3.c @@ -358,8 +358,24 @@ static void redrat3_process_ir_data(struct redrat3_dev *rr3) /* process each rr3 encoded byte into an int */ sig_size = be16_to_cpu(rr3->irdata.sig_size); + + /* + * Note we are not checking if we are reading beyond the end of the + * packet which was sent, and reading stale data. If the device + * sends a packet which is short then we get garbage IR, but no + * out of bounds read. + */ + if (sig_size > RR3_MAX_SIG_SIZE) { + dev_err(dev, "length %u is incorrect\n", sig_size); + return; + } + for (i = 0; i < sig_size; i++) { offset = rr3->irdata.sigdata[i]; + if (offset >= RR3_DRIVER_MAXLENS) { + dev_err(dev, "offset %u is incorrect\n", offset); + return; + } val = get_unaligned_be16(&rr3->irdata.lens[offset]); /* we should always get pulse/space/pulse/space samples */ From 1582f545273f2aa4eb4ee6f3a89b433f5c85c68f Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 13 Jul 2026 13:04:31 +0100 Subject: [PATCH 003/596] media: redrat3: Ensure all urbs are suspended Ensure that the learn urb is stopped before suspend. Fixes: c49fcdde38cb ("[media] redrat3: enable carrier reports using wideband receiver") Signed-off-by: Sean Young Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/redrat3.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/rc/redrat3.c b/drivers/media/rc/redrat3.c index 658e223a4745..30111deb6013 100644 --- a/drivers/media/rc/redrat3.c +++ b/drivers/media/rc/redrat3.c @@ -1164,6 +1164,7 @@ static int redrat3_dev_suspend(struct usb_interface *intf, pm_message_t message) usb_kill_urb(rr3->narrow_urb); usb_kill_urb(rr3->wide_urb); usb_kill_urb(rr3->flash_urb); + usb_kill_urb(rr3->learn_urb); return 0; } From 8024d5e84531d828535365718278f1cf0ce67db0 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 13 Jul 2026 13:08:40 +0100 Subject: [PATCH 004/596] media: redrat3: Error path leaves device in transmitting state In the allocation failure error path, the transmitting boolean is not cleared. If this error path is followed the transmitter cannot be used again until the device is replugged or the module reloaded. Fix the error path by moving the assignment statement. Remove the check for the transmitting state in transmit, since rc-core ensures there is never a reentrant call to the driver transmit function; the transmit function is always called with dev->lock held. The transmitting boolean is only used for interpretting firmware errors. Fixes: fac59136bc22 ("[media] RedRat3: Return directly after a failed kcalloc() in redrat3_transmit_ir()")" Signed-off-by: Sean Young Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/redrat3.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/media/rc/redrat3.c b/drivers/media/rc/redrat3.c index 30111deb6013..b07ff832a938 100644 --- a/drivers/media/rc/redrat3.c +++ b/drivers/media/rc/redrat3.c @@ -773,17 +773,9 @@ static int redrat3_transmit_ir(struct rc_dev *rcdev, unsigned *txbuf, u8 curlencheck = 0; unsigned i, sendbuf_len; - if (rr3->transmitting) { - dev_warn(dev, "%s: transmitter already in use\n", __func__); - return -EAGAIN; - } - if (count > RR3_MAX_SIG_SIZE - RR3_TX_TRAILER_LEN) return -EINVAL; - /* rr3 will disable rc detector on transmit */ - rr3->transmitting = true; - sample_lens = kzalloc_objs(*sample_lens, RR3_DRIVER_MAXLENS); if (!sample_lens) return -ENOMEM; @@ -794,6 +786,9 @@ static int redrat3_transmit_ir(struct rc_dev *rcdev, unsigned *txbuf, goto out; } + /* rr3 will disable rc detector on transmit */ + rr3->transmitting = true; + for (i = 0; i < count; i++) { cur_sample_len = redrat3_us_to_len(txbuf[i]); if (cur_sample_len > 0xffff) { From f9b33ec404200c03dd490ec133b8f9bf599c5444 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 6 Jul 2026 12:37:04 +0100 Subject: [PATCH 005/596] media: sunxi-cir: Ensure no more interrupts can occur before free Only call rc_free_device() once the hardware has been stopped. Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks") Signed-off-by: Sean Young Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/sunxi-cir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/rc/sunxi-cir.c b/drivers/media/rc/sunxi-cir.c index 28e840a7e5b8..af1ee08ffdbe 100644 --- a/drivers/media/rc/sunxi-cir.c +++ b/drivers/media/rc/sunxi-cir.c @@ -374,8 +374,8 @@ static void sunxi_ir_remove(struct platform_device *pdev) struct sunxi_ir *ir = platform_get_drvdata(pdev); rc_unregister_device(ir->rc); - rc_free_device(ir->rc); sunxi_ir_hw_exit(&pdev->dev); + rc_free_device(ir->rc); } static void sunxi_ir_shutdown(struct platform_device *pdev) From 1517326fcc9dfdb9acb24f42472366ac1b4b8237 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 6 Jul 2026 13:13:29 +0100 Subject: [PATCH 006/596] media: meson-ir-tx: Ensure clock is disabled on unbind clk_prepare_enable() needs a call to clk_disable_unprepare() on driver unbind. Make it devm managed. Fixes: 49be1c78d575 ("media: rc: introduce Meson IR TX driver") Signed-off-by: Sean Young Reviewed-by: Martin Blumenstingl Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/meson-ir-tx.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/media/rc/meson-ir-tx.c b/drivers/media/rc/meson-ir-tx.c index fded2c256f2a..6beb9e97453d 100644 --- a/drivers/media/rc/meson-ir-tx.c +++ b/drivers/media/rc/meson-ir-tx.c @@ -288,11 +288,12 @@ static int meson_irtx_mod_clock_probe(struct meson_irtx *ir, if (!np) return -ENODEV; - clock = devm_clk_get(ir->dev, "xtal"); - if (IS_ERR(clock) || clk_prepare_enable(clock)) + *clk_nr = IRB_MOD_XTAL3_CLK; + + clock = devm_clk_get_enabled(ir->dev, "xtal"); + if (IS_ERR(clock)) return -ENODEV; - *clk_nr = IRB_MOD_XTAL3_CLK; ir->clk_rate = clk_get_rate(clock) / 3; if (ir->clk_rate < IRB_MOD_1US_CLK_RATE) { From 32999b8a9d5c4f497c9739e70ac4ee390cd650a7 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 6 Jul 2026 19:24:59 +0100 Subject: [PATCH 007/596] media: meson-ir-tx: Ensure rc_free_device() is called on unbind Make rc_dev devm managed. Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks") Signed-off-by: Sean Young Reviewed-by: Martin Blumenstingl Reviewed-by: Neil Armstrong Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/meson-ir-tx.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/media/rc/meson-ir-tx.c b/drivers/media/rc/meson-ir-tx.c index 6beb9e97453d..e8e3c1a30327 100644 --- a/drivers/media/rc/meson-ir-tx.c +++ b/drivers/media/rc/meson-ir-tx.c @@ -346,7 +346,7 @@ static int meson_irtx_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "irq request failed\n"); - rc = rc_allocate_device(RC_DRIVER_IR_RAW_TX); + rc = devm_rc_allocate_device(dev, RC_DRIVER_IR_RAW_TX); if (!rc) return -ENOMEM; @@ -359,10 +359,8 @@ static int meson_irtx_probe(struct platform_device *pdev) rc->s_tx_duty_cycle = meson_irtx_set_duty_cycle; ret = devm_rc_register_device(dev, rc); - if (ret < 0) { - rc_free_device(rc); + if (ret < 0) return dev_err_probe(dev, ret, "rc_dev registration failed\n"); - } return 0; } From 2eb8a4cba70f521a8217021e227cfa28ba17724b Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 13 Jul 2026 13:52:43 +0100 Subject: [PATCH 008/596] media: meson-ir-tx: Ensure probe error is propagated devm_clk_get_enabled() may return -EPROBE_DEFER which needs to be propagated else the probe will not be deferred, it will fail instead. Fixes: 49be1c78d575 ("media: rc: introduce Meson IR TX driver") Signed-off-by: Sean Young Reviewed-by: Martin Blumenstingl Reviewed-by: Neil Armstrong Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/meson-ir-tx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/rc/meson-ir-tx.c b/drivers/media/rc/meson-ir-tx.c index e8e3c1a30327..1ab898069e67 100644 --- a/drivers/media/rc/meson-ir-tx.c +++ b/drivers/media/rc/meson-ir-tx.c @@ -292,7 +292,7 @@ static int meson_irtx_mod_clock_probe(struct meson_irtx *ir, clock = devm_clk_get_enabled(ir->dev, "xtal"); if (IS_ERR(clock)) - return -ENODEV; + return PTR_ERR(clock); ir->clk_rate = clk_get_rate(clock) / 3; @@ -325,7 +325,7 @@ static int meson_irtx_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) - return -ENODEV; + return irq; ir->dev = dev; ir->carrier = MIRTX_DEFAULT_CARRIER; From f7509d2567cf92eeccc31c363a2e2ae43188930c Mon Sep 17 00:00:00 2001 From: Sean Young Date: Tue, 7 Jul 2026 18:10:02 +0100 Subject: [PATCH 009/596] media: ir-hix5hd2: Ensure rdev is setup before interrupts are enabled Once the interrupt handler is enabled, priv->rdev can be used. Ensure it is setup correctly so there is no race condition. Fixes: a84fcdaa9058 ("[media] rc: Introduce hix5hd2 IR transmitter driver") Signed-off-by: Sean Young Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/ir-hix5hd2.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/media/rc/ir-hix5hd2.c b/drivers/media/rc/ir-hix5hd2.c index 1b061e4a3dcf..aa3de4d57a58 100644 --- a/drivers/media/rc/ir-hix5hd2.c +++ b/drivers/media/rc/ir-hix5hd2.c @@ -316,6 +316,9 @@ static int hix5hd2_ir_probe(struct platform_device *pdev) if (ret < 0) goto clkerr; + priv->rdev = rdev; + priv->dev = dev; + if (devm_request_irq(dev, priv->irq, hix5hd2_ir_rx_interrupt, 0, pdev->name, priv) < 0) { dev_err(dev, "IRQ %d register failed\n", priv->irq); @@ -323,8 +326,6 @@ static int hix5hd2_ir_probe(struct platform_device *pdev) goto regerr; } - priv->rdev = rdev; - priv->dev = dev; platform_set_drvdata(pdev, priv); return ret; From a127f5e9f9ea0470ee3bf83fab82441ae11af208 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Wed, 22 Jul 2026 17:08:09 +0100 Subject: [PATCH 010/596] media: cx88: Specify rc type at rc_allocate_type() Changing the type of the rc device after allocation is ugly and this will also break in a future commit. Ensure that the allocation happens with the correct type in the first place. Signed-off-by: Sean Young Reviewed-by: Hans Verkuil --- drivers/media/pci/cx88/cx88-input.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/media/pci/cx88/cx88-input.c b/drivers/media/pci/cx88/cx88-input.c index 5d9ce4f9af01..c96d289e5ab7 100644 --- a/drivers/media/pci/cx88/cx88-input.c +++ b/drivers/media/pci/cx88/cx88-input.c @@ -259,7 +259,7 @@ static void cx88_ir_close(struct rc_dev *rc) int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) { struct cx88_IR *ir; - struct rc_dev *dev; + struct rc_dev *dev = NULL; char *ir_codes = NULL; u64 rc_proto = RC_PROTO_BIT_OTHER; int err = -ENOMEM; @@ -268,11 +268,8 @@ int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) */ ir = kzalloc_obj(*ir); - dev = rc_allocate_device(RC_DRIVER_IR_RAW); - if (!ir || !dev) - goto err_out_free; - - ir->dev = dev; + if (!ir) + return -ENOMEM; /* detect & configure */ switch (core->boardnr) { @@ -439,6 +436,13 @@ int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) goto err_out_free; } + dev = rc_allocate_device(ir->sampling ? + RC_DRIVER_IR_RAW : RC_DRIVER_SCANCODE); + if (!dev) + goto err_out_free; + + ir->dev = dev; + /* * The usage of mask_keycode were very convenient, due to several * reasons. Among others, the scancode tables were using the scancode @@ -477,12 +481,10 @@ int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) dev->close = cx88_ir_close; dev->scancode_mask = hardware_mask; - if (ir->sampling) { + if (ir->sampling) dev->timeout = MS_TO_US(10); /* 10 ms */ - } else { - dev->driver_type = RC_DRIVER_SCANCODE; + else dev->allowed_protocols = rc_proto; - } ir->core = core; core->ir = ir; From 33154da9f78041edd93f0895a7d697c67994d75b Mon Sep 17 00:00:00 2001 From: Sean Young Date: Wed, 22 Jul 2026 17:13:12 +0100 Subject: [PATCH 011/596] media: saa7134: Specify rc type at rc_allocate_type() Changing the type of the rc device after allocation is ugly and this will also break in a future commit. Ensure that the allocation happens with the correct type in the first place. Signed-off-by: Sean Young Reviewed-by: Hans Verkuil --- drivers/media/pci/saa7134/saa7134-input.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/media/pci/saa7134/saa7134-input.c b/drivers/media/pci/saa7134/saa7134-input.c index 7f6680de3156..b2a2372d149a 100644 --- a/drivers/media/pci/saa7134/saa7134-input.c +++ b/drivers/media/pci/saa7134/saa7134-input.c @@ -769,7 +769,8 @@ int saa7134_input_init1(struct saa7134_dev *dev) } ir = kzalloc_obj(*ir); - rc = rc_allocate_device(RC_DRIVER_SCANCODE); + rc = rc_allocate_device(raw_decode ? + RC_DRIVER_IR_RAW : RC_DRIVER_SCANCODE); if (!ir || !rc) { err = -ENOMEM; goto err_out_free; @@ -792,10 +793,8 @@ int saa7134_input_init1(struct saa7134_dev *dev) rc->priv = dev; rc->open = saa7134_ir_open; rc->close = saa7134_ir_close; - if (raw_decode) { - rc->driver_type = RC_DRIVER_IR_RAW; + if (raw_decode) rc->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; - } rc->device_name = saa7134_boards[dev->board].name; rc->input_phys = ir->phys; From ec7cb49871e5c4f85d57290320c50ee0bae4db84 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Fri, 17 Jul 2026 13:36:31 +0100 Subject: [PATCH 012/596] media: rc: Fix race condition during rc_register_device() Device drivers can call ir_raw_event_store() or ir_raw_event_handle() right after rc_allocate_device(). This means that while rc_register_device() is being called, those functions can be called from e.g. interrupt handlers. Currently dev->raw is being allocated and populated by ir_raw_event_prepare() which is called from rc_register_device(). This is not done in a safe way, e.g. dev->raw is being set before the raw members are populated. Call ir_raw_event_prepare() from rc_allocate_device() instead. Fixes: a3572c34da8d ("V4L/DVB: ir-core: Add logic to decode IR protocols at the IR core") Signed-off-by: Sean Young Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/rc-ir-raw.c | 20 +------------------- drivers/media/rc/rc-main.c | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/drivers/media/rc/rc-ir-raw.c b/drivers/media/rc/rc-ir-raw.c index ba24c2f22d39..46c50f423798 100644 --- a/drivers/media/rc/rc-ir-raw.c +++ b/drivers/media/rc/rc-ir-raw.c @@ -71,9 +71,6 @@ static int ir_raw_event_thread(void *data) */ int ir_raw_event_store(struct rc_dev *dev, struct ir_raw_event *ev) { - if (!dev->raw) - return -EINVAL; - dev_dbg(&dev->dev, "sample: (%05dus %s)\n", ev->duration, TO_STR(ev->pulse)); @@ -102,9 +99,6 @@ int ir_raw_event_store_edge(struct rc_dev *dev, bool pulse) ktime_t now; struct ir_raw_event ev = {}; - if (!dev->raw) - return -EINVAL; - now = ktime_get(); ev.duration = ktime_to_us(ktime_sub(now, dev->raw->last_event)); ev.pulse = !pulse; @@ -129,9 +123,6 @@ int ir_raw_event_store_with_timeout(struct rc_dev *dev, struct ir_raw_event *ev) ktime_t now; int rc = 0; - if (!dev->raw) - return -EINVAL; - now = ktime_get(); spin_lock(&dev->raw->edge_spinlock); @@ -166,9 +157,6 @@ EXPORT_SYMBOL_GPL(ir_raw_event_store_with_timeout); */ int ir_raw_event_store_with_filter(struct rc_dev *dev, struct ir_raw_event *ev) { - if (!dev->raw) - return -EINVAL; - /* Ignore spaces in idle mode */ if (dev->idle && !ev->pulse) return 0; @@ -200,9 +188,6 @@ EXPORT_SYMBOL_GPL(ir_raw_event_store_with_filter); */ void ir_raw_event_set_idle(struct rc_dev *dev, bool idle) { - if (!dev->raw) - return; - dev_dbg(&dev->dev, "%s idle mode\n", idle ? "enter" : "leave"); if (idle) { @@ -226,7 +211,7 @@ EXPORT_SYMBOL_GPL(ir_raw_event_set_idle); */ void ir_raw_event_handle(struct rc_dev *dev) { - if (!dev->raw || !dev->raw->thread) + if (!dev->raw->thread) return; wake_up_process(dev->raw->thread); @@ -612,9 +597,6 @@ EXPORT_SYMBOL(ir_raw_encode_carrier); */ int ir_raw_event_prepare(struct rc_dev *dev) { - if (!dev) - return -EINVAL; - dev->raw = kzalloc_obj(*dev->raw); if (!dev->raw) return -ENOMEM; diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c index dda3479ea3ad..d93e98189c1a 100644 --- a/drivers/media/rc/rc-main.c +++ b/drivers/media/rc/rc-main.c @@ -1701,14 +1701,24 @@ static const struct device_type rc_dev_type = { struct rc_dev *rc_allocate_device(enum rc_driver_type type) { struct rc_dev *dev; + int ret; dev = kzalloc_obj(*dev); if (!dev) return NULL; + if (type == RC_DRIVER_IR_RAW) { + ret = ir_raw_event_prepare(dev); + if (ret < 0) { + kfree(dev); + return NULL; + } + } + if (type != RC_DRIVER_IR_RAW_TX) { dev->input_dev = input_allocate_device(); if (!dev->input_dev) { + ir_raw_event_free(dev); kfree(dev); return NULL; } @@ -1724,6 +1734,7 @@ struct rc_dev *rc_allocate_device(enum rc_driver_type type) spin_lock_init(&dev->rc_map.lock); spin_lock_init(&dev->keylock); } + mutex_init(&dev->lock); dev->dev.type = &rc_dev_type; @@ -1917,12 +1928,6 @@ int rc_register_device(struct rc_dev *dev) dev->sysfs_groups[attr++] = &rc_dev_wakeup_filter_attr_grp; dev->sysfs_groups[attr++] = NULL; - if (dev->driver_type == RC_DRIVER_IR_RAW) { - rc = ir_raw_event_prepare(dev); - if (rc < 0) - goto out_minor; - } - if (dev->driver_type != RC_DRIVER_IR_RAW_TX) { rc = rc_prepare_rx_device(dev); if (rc) @@ -1979,8 +1984,6 @@ int rc_register_device(struct rc_dev *dev) out_rx_free: ir_free_table(&dev->rc_map); out_raw: - ir_raw_event_free(dev); -out_minor: ida_free(&rc_ida, minor); return rc; } From 3ae19773168104fa57d2a2ec57731e89df54175f Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 13 Jul 2026 11:18:39 +0100 Subject: [PATCH 013/596] media: rc: Use after free in ir_raw_event_handle() If rc_unregister_device() is called while IR is being processed, then ir_raw_event_handle() could call wake_up_process(dev->raw->thread) after kthread_stop(dev->raw->thread). Avoid use after free by using get_task_struct(). Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks") Signed-off-by: Sean Young Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/rc-ir-raw.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/media/rc/rc-ir-raw.c b/drivers/media/rc/rc-ir-raw.c index 46c50f423798..86de1b26731d 100644 --- a/drivers/media/rc/rc-ir-raw.c +++ b/drivers/media/rc/rc-ir-raw.c @@ -630,8 +630,13 @@ int ir_raw_event_register(struct rc_dev *dev) void ir_raw_event_free(struct rc_dev *dev) { - kfree(dev->raw); - dev->raw = NULL; + if (dev->raw) { + timer_delete_sync(&dev->raw->edge_handle); + if (dev->raw->thread) + put_task_struct(dev->raw->thread); + kfree(dev->raw); + dev->raw = NULL; + } } void ir_raw_event_unregister(struct rc_dev *dev) @@ -641,6 +646,13 @@ void ir_raw_event_unregister(struct rc_dev *dev) if (!dev || !dev->raw) return; + /* + * After ir_raw_event_unregister() is called, an sync + * call to ir_raw_event_handle() can still arrive. This function + * may call wake_up_process(dev->raw->thread). Ensure this memory + * is not freed by kthread_stop(). + */ + get_task_struct(dev->raw->thread); kthread_stop(dev->raw->thread); timer_delete_sync(&dev->raw->edge_handle); From f95a9f8472f97c30106513fc7494b7abad243949 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 6 Jul 2026 18:47:51 +0100 Subject: [PATCH 014/596] media: rc: Fix use after free in bpf progs Since commit dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks"), rcdev->raw is no longer set to NULL after device unregister. raw->progs could point to stale data. Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks") Signed-off-by: Sean Young Cc: stable@vger.kernel.org Reviewed-by: Hans Verkuil --- drivers/media/rc/bpf-lirc.c | 18 +++++++++++++----- drivers/media/rc/rc-ir-raw.c | 11 +++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/drivers/media/rc/bpf-lirc.c b/drivers/media/rc/bpf-lirc.c index 2f7564f26445..14ab611e7445 100644 --- a/drivers/media/rc/bpf-lirc.c +++ b/drivers/media/rc/bpf-lirc.c @@ -148,12 +148,13 @@ static int lirc_bpf_attach(struct rc_dev *rcdev, struct bpf_prog *prog) if (ret) return ret; - raw = rcdev->raw; - if (!raw) { + if (!rcdev->registered) { ret = -ENODEV; goto unlock; } + raw = rcdev->raw; + old_array = lirc_rcu_dereference(raw->progs); if (old_array && bpf_prog_array_length(old_array) >= BPF_MAX_PROGS) { ret = -E2BIG; @@ -186,12 +187,13 @@ static int lirc_bpf_detach(struct rc_dev *rcdev, struct bpf_prog *prog) if (ret) return ret; - raw = rcdev->raw; - if (!raw) { + if (!rcdev->registered) { ret = -ENODEV; goto unlock; } + raw = rcdev->raw; + old_array = lirc_rcu_dereference(raw->progs); ret = bpf_prog_array_copy(old_array, prog, NULL, 0, &new_array); /* @@ -235,7 +237,8 @@ void lirc_bpf_free(struct rc_dev *rcdev) struct bpf_prog_array_item *item; struct bpf_prog_array *array; - array = lirc_rcu_dereference(rcdev->raw->progs); + array = rcu_replace_pointer(rcdev->raw->progs, NULL, + lockdep_is_held(&ir_raw_handler_lock)); if (!array) return; @@ -316,6 +319,11 @@ int lirc_prog_query(const union bpf_attr *attr, union bpf_attr __user *uattr) if (ret) goto put; + if (!rcdev->registered) { + ret = -ENODEV; + goto unlock; + } + progs = lirc_rcu_dereference(rcdev->raw->progs); cnt = progs ? bpf_prog_array_length(progs) : 0; diff --git a/drivers/media/rc/rc-ir-raw.c b/drivers/media/rc/rc-ir-raw.c index 86de1b26731d..54b323becb1f 100644 --- a/drivers/media/rc/rc-ir-raw.c +++ b/drivers/media/rc/rc-ir-raw.c @@ -632,8 +632,11 @@ void ir_raw_event_free(struct rc_dev *dev) { if (dev->raw) { timer_delete_sync(&dev->raw->edge_handle); + mutex_lock(&ir_raw_handler_lock); if (dev->raw->thread) put_task_struct(dev->raw->thread); + lirc_bpf_free(dev); + mutex_unlock(&ir_raw_handler_lock); kfree(dev->raw); dev->raw = NULL; } @@ -643,9 +646,6 @@ void ir_raw_event_unregister(struct rc_dev *dev) { struct ir_raw_handler *handler; - if (!dev || !dev->raw) - return; - /* * After ir_raw_event_unregister() is called, an sync * call to ir_raw_event_handle() can still arrive. This function @@ -665,11 +665,6 @@ void ir_raw_event_unregister(struct rc_dev *dev) lirc_bpf_free(dev); - /* - * A user can be calling bpf(BPF_PROG_{QUERY|ATTACH|DETACH}), so - * ensure that the raw member is null on unlock; this is how - * "device gone" is checked. - */ mutex_unlock(&ir_raw_handler_lock); } From af452b9e01331b5338a1e5141624f17607239207 Mon Sep 17 00:00:00 2001 From: Rik van Riel Date: Sat, 8 Aug 2026 23:20:31 -0400 Subject: [PATCH 015/596] media: redrat3: fix UAF in probe error path leaving rc device registered redrat3 stores its device name and phys path inside the main rr3 structure, and the rc device's device_name and input_phys point into rr3. When the RC device is registered, it keeps those pointers. KASAN: slab-use-after-free in string_nocheck lib/vsprintf.c:648 Read of size 1 at addr ffff888051fda758 by task udevd/7464 Call Trace: string_nocheck lib/vsprintf.c:648 [inline] string+0x216/0x2d0 lib/vsprintf.c:730 vsnprintf+0x74a/0xef0 lib/vsprintf.c:2945 vscnprintf+0x41/0x90 lib/vsprintf.c:3014 sysfs_emit+0x10e/0x180 fs/sysfs/file.c:761 input_dev_show_name+0x58/0x70 drivers/input/input.c:1282 Allocated by task 10: redrat3_dev_probe+0x477/0x2570 drivers/media/rc/redrat3.c:1023 Freed by task 10: redrat3_delete drivers/media/rc/redrat3.c:466 [inline] redrat3_dev_probe+0x1bf4/0x2570 drivers/media/rc/redrat3.c:1124 Syzkaller triggers this via usb probing. It probes the RedRat3 USB interface, which calls redrat3_dev_probe() in redrat3.c. That function allocates rr3, then builds an rc device whose name points into rr3 via redrat3_init_rc_dev() in redrat3.c, and registers it with rc_register_device(). When the detector enable fails after the RC device is registered, the probe jumps to the led_free path. That path unregisters the LED but does not unregister the RC device before freeing rr3 via redrat3_delete() in redrat3.c. The RC device still holds device_name = rr3->name which is now freed. Later udevd reads /sys/.../input device name via sysfs_emit() in file.c, which calls input_dev_show_name() in input.c, which emits dev->name which is the freed rr3->name. When the detector enable fails after the RC device is registered, the error path must not leave the RC device registered. Make the probe error path clean up the same way redrat3_dev_disconnect() does. This change should be safe because the RC device is fully registered at this point and its teardown via rc_unregister_device() is protected by the input device mutex, and rr3 is still alive during unregister so device_name remains valid until after unregister. The URBs are killed before rc_free, so no completion can run after rc is freed. No new lock ordering is introduced. Reported-by: syzbot+302b9b575a06733ff60c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=302b9b575a06733ff60c Link: https://lore.kernel.org/all/6a74a76d.ec7c9571.3ac9bb.0056.GAE@google.com/ Fixes: 2154be651b90 ("[media] redrat3: new rc-core IR transceiver device driver") Cc: stable@vger.kernel.org Assisted-by: Hermes:muse-spark-1.2 syzkaller Signed-off-by: Rik van Riel Signed-off-by: Sean Young --- drivers/media/rc/redrat3.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/media/rc/redrat3.c b/drivers/media/rc/redrat3.c index b07ff832a938..3e82b5e66477 100644 --- a/drivers/media/rc/redrat3.c +++ b/drivers/media/rc/redrat3.c @@ -986,6 +986,7 @@ static int redrat3_dev_probe(struct usb_interface *intf, struct device *dev = &intf->dev; struct usb_host_interface *uhi; struct redrat3_dev *rr3; + struct rc_dev *rc; struct usb_endpoint_descriptor *ep; struct usb_endpoint_descriptor *ep_narrow = NULL; struct usb_endpoint_descriptor *ep_wide = NULL; @@ -1130,9 +1131,12 @@ static int redrat3_dev_probe(struct usb_interface *intf, return 0; led_free: + rc_unregister_device(rr3->rc); led_classdev_unregister(&rr3->led); redrat_free: + rc = rr3->rc; redrat3_delete(rr3, rr3->udev); + rc_free_device(rc); no_endpoints: return retval; From 274af88c8acaa4bb538791047bd17fa5136ba78f Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Fri, 19 Jun 2026 14:08:16 +0300 Subject: [PATCH 016/596] media: imon: use sysfs_emit() in associate_remote_show() Prefer the convenient 'sysfs_emit()' over 'strscpy()' and 'strlen()' in 'associate_remote_show()' sysfs callback. Signed-off-by: Dmitry Antipov Signed-off-by: Sean Young --- drivers/media/rc/imon.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/media/rc/imon.c b/drivers/media/rc/imon.c index 049a73b5f882..621554005f2f 100644 --- a/drivers/media/rc/imon.c +++ b/drivers/media/rc/imon.c @@ -805,19 +805,16 @@ static ssize_t associate_remote_show(struct device *d, char *buf) { struct imon_context *ictx = dev_get_drvdata(d); + int len; if (!ictx) return -ENODEV; mutex_lock(&ictx->lock); - if (ictx->rf_isassociating) - strscpy(buf, "associating\n", PAGE_SIZE); - else - strscpy(buf, "closed\n", PAGE_SIZE); - + len = sysfs_emit(buf, (ictx->rf_isassociating ? "associating\n" : "closed\n")); dev_info(d, "Visit https://www.lirc.org/html/imon-24g.html for instructions on how to associate your iMON 2.4G DT/LT remote\n"); mutex_unlock(&ictx->lock); - return strlen(buf); + return len; } static ssize_t associate_remote_store(struct device *d, From 61b039dac1e36c85e27a0fda4e25aef3b60401dc Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 3 Aug 2026 15:27:08 +0300 Subject: [PATCH 017/596] MAINTAINERS: Update Intel cvs and ipu6 driver entries Add Antti and Sarang as reviewers for a set of Intel ipu6 drivers, Antti as a reviewer of the cvs driver and myself as a maintainter to the cvs driver. Signed-off-by: Sakari Ailus Reviewed-by: Miguel Vadillo --- MAINTAINERS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..bf87c3eca883 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13033,6 +13033,8 @@ F: drivers/scsi/isci/ INTEL COMPUTER VISION SENSING (CVS) DRIVER M: Miguel Vadillo +M: Sakari Ailus +R: Antti Laakso L: linux-media@vger.kernel.org S: Maintained F: drivers/media/i2c/cvs/ @@ -13241,6 +13243,8 @@ F: drivers/staging/media/ipu3/ INTEL IPU6 INPUT SYSTEM DRIVER M: Sakari Ailus +R: Antti Laakso +R: "Sapre, Sarang" L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media.git From 02f9d6b1920ff18a3793c1e326a2f6be067d5413 Mon Sep 17 00:00:00 2001 From: Kartik Panesar Date: Thu, 27 Aug 2026 18:30:40 +0530 Subject: [PATCH 018/596] staging: media: ipu3: remove unnecessary dev_dbg function trace Remove a dev_dbg() call that only logs function entry via __func__, flagged by checkpatch.pl as unnecessary ftrace-like logging . Function entry/exit is already available through ftrace's function and function_graph tracers making this redundant. Signed-off-by: Kartik Panesar Signed-off-by: Sakari Ailus --- drivers/staging/media/ipu3/ipu3-css.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/staging/media/ipu3/ipu3-css.c b/drivers/staging/media/ipu3/ipu3-css.c index 8063401246fb..b2c8fb7d6813 100644 --- a/drivers/staging/media/ipu3/ipu3-css.c +++ b/drivers/staging/media/ipu3/ipu3-css.c @@ -290,7 +290,6 @@ int imgu_css_set_powerup(struct device *dev, void __iomem *base, void imgu_css_set_powerdown(struct device *dev, void __iomem *base) { - dev_dbg(dev, "%s\n", __func__); /* wait for cio idle signal */ if (imgu_hw_wait(base, IMGU_REG_CIO_GATE_BURST_STATE, IMGU_CIO_GATE_BURST_MASK, 0)) From 73c84e21c0fb7e09d3d170b7c08e181d5d6ff723 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 11 Aug 2026 18:35:44 +0300 Subject: [PATCH 019/596] MAINTAINERS: media: ipu-bridge: Add entry Add an entry for the ipu-bridge, a firmware parsing helper for the Intel IPU drivers, with myself and Dan Scally as maintainers and Hans de Goede as reviewer. Signed-off-by: Sakari Ailus Acked-by: Hans de Goede Acked-by: Daniel Scally --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index bf87c3eca883..ac7c99394167 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13222,6 +13222,15 @@ S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git F: drivers/iommu/intel/ +INTEL IPU BRIDGE +M: Sakari Ailus +M: Dan Scally +R: Hans de Goede +L: linux-media@vger.kernel.org +S: Maintained +F: drivers/media/pci/intel/ipu-bridge.c +F: include/media/ipu-bridge.h + INTEL IPU3 CSI-2 CIO2 DRIVER M: Yong Zhi M: Sakari Ailus From 0458b71de5e26d74ece0e1113f5992f6940dee18 Mon Sep 17 00:00:00 2001 From: Robby Cai Date: Tue, 19 May 2026 13:38:23 +0200 Subject: [PATCH 020/596] media: dt-bindings: nxp,imx8mq-mipi-csi2: Fix example endpoint label typo The example in imx8mq-mipi-csi2.yaml uses imx8mm_mipi_csi_{in,out} endpoint labels, which is confusing for an i.MX8MQ binding. The labels could be removed as they are not functionally required in the example, but they have a documentation purpose that brings value to the reader. Rename them mipi_csi_{in,out} to avoid the confusion. Signed-off-by: Robby Cai Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart Acked-by: Rob Herring (Arm) Signed-off-by: Frank Li Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml index 4fcfc4fd3565..9eee67ed2685 100644 --- a/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml +++ b/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml @@ -220,7 +220,7 @@ examples: port@0 { reg = <0>; - imx8mm_mipi_csi_in: endpoint { + mipi_csi_in: endpoint { remote-endpoint = <&imx477_out>; data-lanes = <1 2 3 4>; }; @@ -229,7 +229,7 @@ examples: port@1 { reg = <1>; - imx8mm_mipi_csi_out: endpoint { + mipi_csi_out: endpoint { remote-endpoint = <&csi_in>; }; }; From a33f2efd6fa32042c3e06ebcb4b650f7ab180626 Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Wed, 3 Jun 2026 00:15:40 +0100 Subject: [PATCH 021/596] MAINTAINERS: media: imx: imx8mq-mipi-csi2: Add myself as reviewer to imx8mq-mipi-csi2 At the media summit in Nice this year we discussed that cross reviewing from different people on LKML was lacking and desirable. Laurent suggested NXP/Qcom do some cross pollination. Happy to read and review NXP stuff in that spirit. Signed-off-by: Bryan O'Donoghue Reviewed-by: Frank Li Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Signed-off-by: Hans Verkuil --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index ac7c99394167..da5106c8f8b8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16421,6 +16421,7 @@ M: Frank Li M: Martin Kepplinger-Novakovic R: Rui Miguel Silva R: Purism Kernel Team +R: Bryan O'Donoghue L: imx@lists.linux.dev L: linux-media@vger.kernel.org S: Maintained From 37d020b109a977aff6ab30b1ea19d70ffe1d76c6 Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Wed, 3 Jun 2026 00:15:41 +0100 Subject: [PATCH 022/596] MAINTAINERS: media: rockchip: dw-mipi-csi2rx: Add myself to the reviewers list As a cross pollination activity between Qcom and others add myself to the Rockchip list. We discussed @ Nice making reviews more formal because frankly we can get gummed up in not reviewing. Add myself +R here in that light. Signed-off-by: Bryan O'Donoghue Acked-by: Michael Riesch Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Signed-off-by: Hans Verkuil --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index da5106c8f8b8..a33b46da5fd3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26567,6 +26567,7 @@ F: include/linux/soc/amd/isp4_misc.h SYNOPSYS DESIGNWARE MIPI CSI-2 RECEIVER DRIVER M: Michael Riesch +R: Bryan O'Donoghue L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml From 78fdfbf117c409273eecbfa7a528ef42358a0e38 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 23 Jun 2026 10:30:31 +0000 Subject: [PATCH 023/596] media: imx-jpeg: cancel timeout worker when streaming stops Each per-fd context ctx owns a delayed_work (ctx->task_timer, callback mxc_jpeg_device_run_timeout) armed via schedule_delayed_work() at the end of mxc_jpeg_device_run() to recover a stalled encode/decode job. The only existing cancellation is cancel_delayed_work() in the frame-done IRQ handler, which de-queues a pending work item but does not wait for a callback that has already started, and it only runs when a frame completes. When the fd is closed while a job is in flight (the frame-done IRQ has not fired yet), nothing syncs the worker before mxc_jpeg_release() frees ctx with kfree() after v4l2_m2m_ctx_release(). A queued or executing mxc_jpeg_device_run_timeout() can then recover ctx through container_of(&ctx->task_timer) and dereference it (ctx->mxc_jpeg, slot_data, dev_warn) after ctx has been freed. Cancel the worker from mxc_jpeg_stop_streaming(). The cancel cannot live in mxc_jpeg_release(): mxc_jpeg_device_run() arms the timer while holding only hw_lock, not the mxc_jpeg->lock mutex that release holds, so a cancel in release could still race a concurrent mxc_jpeg_device_run() that re-arms the timer afterwards. mxc_jpeg_stop_streaming() instead runs inside v4l2_m2m_ctx_release() -> vb2_queue_release(), i.e. after v4l2_m2m_cancel_job() has set TRANS_ABORT and waited for any in-flight job to finish (so __v4l2_m2m_try_queue() will not queue and v4l2_m2m_try_run() will not run any further job for this context, which prevents mxc_jpeg_device_run() from re-arming the timer) and before the m2m context is freed. cancel_delayed_work_sync() removes a pending work item and waits for a running callback, so the worker can no longer race with the subsequent kfree(). The cancel is placed before the buffer-release loop so a concurrently running timeout callback cannot race with it over the same buffers. If the frame-done IRQ canceled a still-pending timer, this cancel is a no-op; if the timeout callback has already started, it waits for the callback to finish. The same mxc_jpeg_stop_streaming() call is also reached from VIDIOC_STREAMOFF, which drains the worker early, although STREAMOFF itself does not free ctx -- the use-after-free arises only when the fd is later closed. This bug was found by static analysis. Fixes: cfed9632ca8e ("media: imx-jpeg: Add a timeout mechanism for each frame") Cc: stable@vger.kernel.org Signed-off-by: Fan Wu Reviewed-by: Ming Qian Signed-off-by: Frank Li Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c index 725e94152884..ea2fad5dd646 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -1798,6 +1798,8 @@ static void mxc_jpeg_stop_streaming(struct vb2_queue *q) dev_dbg(ctx->mxc_jpeg->dev, "Stop streaming ctx=%p", ctx); + cancel_delayed_work_sync(&ctx->task_timer); + /* Release all active buffers */ for (;;) { if (V4L2_TYPE_IS_OUTPUT(q->type)) From 30b35fd2b999e1b8c26f8191d974d20c8848d2d6 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 24 Jun 2026 00:32:08 +0000 Subject: [PATCH 024/596] media: imx-jpeg: finish the job on device_run() error paths mxc_jpeg_device_run() returns early through a shared "end" label on several error paths (no free slot, mxc_jpeg_alloc_slot_data() failure, or missing buffers or queue data), and none of them calls v4l2_m2m_job_finish(). Since the delayed work is queued only after the hardware is started, those paths neither finish the job directly nor queue timeout work that could finish it later. The job is left with TRANS_RUNNING set, so the wait_event() in v4l2_m2m_cancel_job() (reached from v4l2_m2m_ctx_release() at close) waits indefinitely and the close hangs. mxc_jpeg_alloc_slot_data() uses dma_alloc_coherent(), so the failure path is reachable under memory pressure. Return the src/dst buffers with VB2_BUF_STATE_ERROR and call v4l2_m2m_job_finish() on those paths: paths that have buffers use a "buf_finish" label; the no-buffer path uses "job_finish" directly. This mirrors the existing jpeg_parse_error path. This bug was found by static analysis. Fixes: 2db16c6ed72c ("media: imx-jpeg: Add V4L2 driver for i.MX8 JPEG Encoder/Decoder") Cc: stable@vger.kernel.org Signed-off-by: Fan Wu Reviewed-by: Ming Qian Signed-off-by: Frank Li Signed-off-by: Hans Verkuil --- .../media/platform/nxp/imx-jpeg/mxc-jpeg.c | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c index ea2fad5dd646..e17085935435 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -1590,15 +1590,15 @@ static void mxc_jpeg_device_run(void *priv) dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); if (!src_buf || !dst_buf) { dev_err(dev, "Null src or dst buf\n"); - goto end; + goto job_finish; } q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); if (!q_data_cap) - goto end; + goto buf_finish; q_data_out = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); if (!q_data_out) - goto end; + goto buf_finish; src_buf->sequence = q_data_out->sequence++; dst_buf->sequence = q_data_cap->sequence++; @@ -1636,11 +1636,11 @@ static void mxc_jpeg_device_run(void *priv) ctx->slot = mxc_get_free_slot(&jpeg->slot_data); if (ctx->slot < 0) { dev_err(dev, "No more free slots\n"); - goto end; + goto buf_finish; } if (!mxc_jpeg_alloc_slot_data(jpeg)) { dev_err(dev, "Cannot allocate slot data\n"); - goto end; + goto buf_finish; } mxc_jpeg_enable_slot(reg, ctx->slot); @@ -1660,8 +1660,17 @@ static void mxc_jpeg_device_run(void *priv) mxc_jpeg_dec_mode_go(dev, reg); } schedule_delayed_work(&ctx->task_timer, msecs_to_jiffies(hw_timeout)); -end: + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + return; +buf_finish: + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); +job_finish: + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); } static int mxc_jpeg_decoder_cmd(struct file *file, void *priv, From 2df589d0232876c2f1048f8baf3d06d2a746a3f9 Mon Sep 17 00:00:00 2001 From: Guoniu Zhou Date: Tue, 28 Jul 2026 14:14:14 +0800 Subject: [PATCH 025/596] media: dt-bindings: Add CSI Pixel Formatter DT bindings The i.MX95 CSI pixel formatting module uses packet info, pixel and non-pixel data from the CSI-2 host controller and reformat them to match Pixel Link(PL) definition. Signed-off-by: Guoniu Zhou Reviewed-by: Frank Li Reviewed-by: Krzysztof Kozlowski Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Signed-off-by: Hans Verkuil --- .../media/fsl,imx95-csi-formatter.yaml | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml diff --git a/Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml b/Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml new file mode 100644 index 000000000000..58c4e1cc056b --- /dev/null +++ b/Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/fsl,imx95-csi-formatter.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: i.MX95 CSI Pixel Formatter + +maintainers: + - Guoniu Zhou + +description: + The CSI pixel formatting module found on i.MX95 uses packet info, pixel + and non-pixel data from the CSI-2 host controller and reformat them to + match Pixel Link(PL) definition. + +properties: + compatible: + const: fsl,imx95-csi-formatter + + reg: + maxItems: 1 + description: Register offset and size within the parent syscon + + clocks: + maxItems: 1 + + power-domains: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: + Input port, connects to MIPI CSI-2 receiver output (IDI interface) + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: + Output port, connects to ISI input via Pixel Link (PL) + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - clocks + - power-domains + - ports + +additionalProperties: false + +examples: + - | + #include + + formatter@20 { + compatible = "fsl,imx95-csi-formatter"; + reg = <0x20 0x100>; + clocks = <&cameramix_csr IMX95_CLK_CAMBLK_CSI2_FOR0>; + power-domains = <&scmi_devpd 3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + endpoint { + remote-endpoint = <&mipi_csi_0_out>; + }; + }; + + port@1 { + reg = <1>; + + endpoint { + remote-endpoint = <&isi_in_2>; + }; + }; + }; + }; From e0f6cf85a20fa481788b6176d5e6358cdf714097 Mon Sep 17 00:00:00 2001 From: Guoniu Zhou Date: Tue, 28 Jul 2026 14:14:15 +0800 Subject: [PATCH 026/596] dt-bindings: clock: imx95-blk-ctl: Define formatter child node schema The Camera CSR contains control registers for multiple CSI formatter IPs at different register offsets. Each formatter is an independent hardware block with its own clock input and media pipeline connection. Define schema to allow formatter child nodes under nxp,imx95-camera-csr, with 'reg' property specifying the formatter's register offset within the CSR address space. Signed-off-by: Guoniu Zhou Reviewed-by: Frank Li Reviewed-by: Krzysztof Kozlowski Reviewed-by: Laurent Pinchart Acked-by: Abel Vesa Signed-off-by: Frank Li Signed-off-by: Hans Verkuil --- .../bindings/clock/nxp,imx95-blk-ctl.yaml | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml b/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml index 27403b4c52d6..fbbf1b3f1790 100644 --- a/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml +++ b/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml @@ -39,6 +39,18 @@ properties: ID in its "clocks" phandle cell. See include/dt-bindings/clock/nxp,imx95-clock.h + '#address-cells': + const: 1 + + '#size-cells': + const: 1 + +patternProperties: + '^formatter@[0-9a-f]+$': + type: object + $ref: /schemas/media/fsl,imx95-csi-formatter.yaml# + unevaluatedProperties: false + required: - compatible - reg @@ -46,6 +58,23 @@ required: - power-domains - clocks +allOf: + - if: + properties: + compatible: + contains: + const: nxp,imx95-camera-csr + then: + required: + - '#address-cells' + - '#size-cells' + else: + properties: + '#address-cells': false + '#size-cells': false + patternProperties: + '^formatter@[0-9a-f]+$': false + additionalProperties: false examples: @@ -57,4 +86,46 @@ examples: clocks = <&scmi_clk 114>; power-domains = <&scmi_devpd 21>; }; + + - | + #include + + syscon@4ac10000 { + compatible = "nxp,imx95-camera-csr", "syscon"; + reg = <0x4ac10000 0x10000>; + #address-cells = <1>; + #size-cells = <1>; + #clock-cells = <1>; + clocks = <&scmi_clk 62>; + power-domains = <&scmi_devpd 3>; + + formatter@20 { + compatible = "fsl,imx95-csi-formatter"; + reg = <0x20 0x100>; + clocks = <&cameramix_csr IMX95_CLK_CAMBLK_CSI2_FOR0>; + power-domains = <&scmi_devpd 3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + endpoint { + remote-endpoint = <&mipi_csi_0_out>; + }; + + }; + + port@1 { + reg = <1>; + + endpoint { + remote-endpoint = <&isi_in_2>; + }; + }; + }; + }; + }; ... From 3652add7e9b01d6ea4d3d4ff3ade8f60084867f6 Mon Sep 17 00:00:00 2001 From: Guoniu Zhou Date: Tue, 28 Jul 2026 14:14:16 +0800 Subject: [PATCH 027/596] media: nxp: Add i.MX95 CSI pixel formatter v4l2 driver The CSI pixel formatter is a module found on i.MX95 used to reformat packet info, pixel and non-pixel data from CSI-2 host controller to match Pixel Link(PL) definition. Add data formatting support. Signed-off-by: Guoniu Zhou Reviewed-by: Frank Li Reviewed-by: Loic Poulain Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Signed-off-by: Hans Verkuil --- MAINTAINERS | 8 + drivers/media/platform/nxp/Kconfig | 16 + drivers/media/platform/nxp/Makefile | 1 + .../media/platform/nxp/imx95-csi-formatter.c | 758 ++++++++++++++++++ 4 files changed, 783 insertions(+) create mode 100644 drivers/media/platform/nxp/imx95-csi-formatter.c diff --git a/MAINTAINERS b/MAINTAINERS index a33b46da5fd3..baf4ca2c3928 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19739,6 +19739,14 @@ S: Maintained F: Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml F: drivers/media/platform/nxp/imx-jpeg +NXP i.MX 95 CSI PIXEL FORMATTER V4L2 DRIVER +M: Guoniu Zhou +L: imx@lists.linux.dev +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/fsl,imx95-csi-formatter.yaml +F: drivers/media/platform/nxp/imx95-csi-formatter.c + NXP i.MX CLOCK DRIVERS M: Abel Vesa R: Peng Fan diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig index 40e3436669e2..5ff263fe7c6f 100644 --- a/drivers/media/platform/nxp/Kconfig +++ b/drivers/media/platform/nxp/Kconfig @@ -28,6 +28,22 @@ config VIDEO_IMX8MQ_MIPI_CSI2 Video4Linux2 driver for the MIPI CSI-2 receiver found on the i.MX8MQ SoC. +config VIDEO_IMX95_CSI_FORMATTER + tristate "NXP i.MX95 CSI Pixel Formatter driver" + depends on ARCH_MXC || COMPILE_TEST + depends on PM_CLK + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select MFD_SYSCON + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + This driver provides support for the CSI Pixel Formatter found on + i.MX95 series SoCs. This module unpacks the pixels received from the + CSI-2 interface and reformats them to meet pixel link requirements. + + Say Y here to enable CSI Pixel Formatter module for i.MX95 SoC. + config VIDEO_IMX_MIPI_CSIS tristate "NXP MIPI CSI-2 CSIS receiver found on i.MX7 and i.MX8 models" depends on ARCH_MXC || COMPILE_TEST diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile index 4d90eb713652..6410115d870e 100644 --- a/drivers/media/platform/nxp/Makefile +++ b/drivers/media/platform/nxp/Makefile @@ -6,6 +6,7 @@ obj-y += imx8-isi/ obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o obj-$(CONFIG_VIDEO_IMX8MQ_MIPI_CSI2) += imx8mq-mipi-csi2.o +obj-$(CONFIG_VIDEO_IMX95_CSI_FORMATTER) += imx95-csi-formatter.o obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o diff --git a/drivers/media/platform/nxp/imx95-csi-formatter.c b/drivers/media/platform/nxp/imx95-csi-formatter.c new file mode 100644 index 000000000000..2043f5caaad1 --- /dev/null +++ b/drivers/media/platform/nxp/imx95-csi-formatter.c @@ -0,0 +1,758 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* CSI Pixel Formatter registers map */ + +#define CSI_VC_INTERLACED_LINE_CNT(vc) (0x00 + (vc) * 0x04) +#define INTERLACED_ODD_LINE_CNT_SET(x) FIELD_PREP(GENMASK(13, 0), (x)) +#define INTERLACED_EVEN_LINE_CNT_SET(x) FIELD_PREP(GENMASK(29, 16), (x)) + +#define CSI_VC_INTERLACED_CTRL 0x20 + +#define CSI_VC_INTERLACED_ERR 0x24 +#define CSI_VC_ERR_MASK GENMASK(7, 0) +#define CSI_VC_ERR(vc) BIT((vc)) + +#define CSI_VC_YUV420_FIRST_LINE_EVEN 0x28 +#define YUV420_FIRST_LINE_EVEN(vc) BIT((vc)) + +#define CSI_RAW32_CTRL 0x30 +#define CSI_VC_RAW32_MODE(vc) BIT((vc)) +#define CSI_VC_RAW32_SWAP_MODE(vc) BIT((vc) + 8) + +#define CSI_STREAM_FENCING_CTRL 0x34 +#define CSI_VC_STREAM_FENCING(vc) BIT((vc)) +#define CSI_VC_STREAM_FENCING_RST(vc) BIT((vc) + 8) + +#define CSI_STREAM_FENCING_STS 0x38 +#define CSI_STREAM_FENCING_STS_MASK GENMASK(7, 0) + +#define CSI_VC_NON_PIXEL_DATA_TYPE(vc) (0x40 + (vc) * 0x04) + +#define CSI_VC_PIXEL_DATA_CTRL(vc) (0x60 + (vc) * 0x04) +#define NEW_VC(vc) FIELD_PREP(GENMASK(3, 1), vc) +#define REROUTE_VC_ENABLE BIT(0) + +#define CSI_VC_ROUTE_PIXEL_DATA_TYPE(vc) (0x80 + (vc) * 0x04) + +#define CSI_VC_NON_PIXEL_DATA_CTRL(vc) (0xa0 + (vc) * 0x04) + +#define CSI_VC_PIXEL_DATA_TYPE(vc) (0xc0 + (vc) * 0x04) + +#define CSI_VC_PIXEL_DATA_TYPE_ERR(vc) (0xe0 + (vc) * 0x04) + +#define CSI_FORMATTER_PAD_SINK 0 +#define CSI_FORMATTER_PAD_SOURCE 1 +#define CSI_FORMATTER_PAD_NUM 2 + +#define CSI_FORMATTER_VC_NUM 8 /* Number of virtual channels */ + +struct csi_formatter_pix_format { + u32 code; + u32 data_type; +}; + +struct csi_formatter { + struct device *dev; + struct regmap *regs; + u32 reg_offset; + + struct v4l2_subdev sd; + struct v4l2_async_notifier notifier; + struct media_pad pads[CSI_FORMATTER_PAD_NUM]; + + struct v4l2_subdev *remote_sd; + u32 remote_pad; + + u64 enabled_streams; +}; + +struct csi_formatter_dt_index { + u8 dtype; + u8 index; +}; + +/* + * The index corresponds to the bit index in the register that enables + * the data type of pixel data transported by the Formatter. + */ +static const struct csi_formatter_dt_index csi_formatter_dt_to_index_map[] = { + { .dtype = MIPI_CSI2_DT_YUV420_8B, .index = 0 }, + { .dtype = MIPI_CSI2_DT_YUV420_8B_LEGACY, .index = 2 }, + { .dtype = MIPI_CSI2_DT_YUV422_8B, .index = 6 }, + { .dtype = MIPI_CSI2_DT_RGB444, .index = 8 }, + { .dtype = MIPI_CSI2_DT_RGB555, .index = 9 }, + { .dtype = MIPI_CSI2_DT_RGB565, .index = 10 }, + { .dtype = MIPI_CSI2_DT_RGB666, .index = 11 }, + { .dtype = MIPI_CSI2_DT_RGB888, .index = 12 }, + { .dtype = MIPI_CSI2_DT_RAW6, .index = 16 }, + { .dtype = MIPI_CSI2_DT_RAW7, .index = 17 }, + { .dtype = MIPI_CSI2_DT_RAW8, .index = 18 }, + { .dtype = MIPI_CSI2_DT_RAW10, .index = 19 }, + { .dtype = MIPI_CSI2_DT_RAW12, .index = 20 }, + { .dtype = MIPI_CSI2_DT_RAW14, .index = 21 }, + { .dtype = MIPI_CSI2_DT_RAW16, .index = 22 }, +}; + +static const struct csi_formatter_pix_format csi_formatter_formats[] = { + /* YUV formats */ + { MEDIA_BUS_FMT_UYVY8_1X16, MIPI_CSI2_DT_YUV422_8B }, + /* RGB formats */ + { MEDIA_BUS_FMT_RGB565_1X16, MIPI_CSI2_DT_RGB565 }, + { MEDIA_BUS_FMT_RGB888_1X24, MIPI_CSI2_DT_RGB888 }, + /* RAW (Bayer and greyscale) formats */ + { MEDIA_BUS_FMT_SBGGR8_1X8, MIPI_CSI2_DT_RAW8 }, + { MEDIA_BUS_FMT_SGBRG8_1X8, MIPI_CSI2_DT_RAW8 }, + { MEDIA_BUS_FMT_SGRBG8_1X8, MIPI_CSI2_DT_RAW8 }, + { MEDIA_BUS_FMT_SRGGB8_1X8, MIPI_CSI2_DT_RAW8 }, + { MEDIA_BUS_FMT_Y8_1X8, MIPI_CSI2_DT_RAW8 }, + { MEDIA_BUS_FMT_SBGGR10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SRGGB10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_Y10_1X10, MIPI_CSI2_DT_RAW10 }, + { MEDIA_BUS_FMT_SBGGR12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SGBRG12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_Y12_1X12, MIPI_CSI2_DT_RAW12 }, + { MEDIA_BUS_FMT_SBGGR14_1X14, MIPI_CSI2_DT_RAW14 }, + { MEDIA_BUS_FMT_SGBRG14_1X14, MIPI_CSI2_DT_RAW14 }, + { MEDIA_BUS_FMT_SGRBG14_1X14, MIPI_CSI2_DT_RAW14 }, + { MEDIA_BUS_FMT_SRGGB14_1X14, MIPI_CSI2_DT_RAW14 }, + { MEDIA_BUS_FMT_SBGGR16_1X16, MIPI_CSI2_DT_RAW16 }, + { MEDIA_BUS_FMT_SGBRG16_1X16, MIPI_CSI2_DT_RAW16 }, + { MEDIA_BUS_FMT_SGRBG16_1X16, MIPI_CSI2_DT_RAW16 }, + { MEDIA_BUS_FMT_SRGGB16_1X16, MIPI_CSI2_DT_RAW16 }, +}; + +static const struct v4l2_mbus_framefmt formatter_default_fmt = { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .width = 1920U, + .height = 1080U, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + .xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SMPTE170M), + .ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SMPTE170M), + .quantization = V4L2_QUANTIZATION_LIM_RANGE, +}; + +static const struct csi_formatter_pix_format *csi_formatter_find_format(u32 code) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(csi_formatter_formats); i++) { + if (code == csi_formatter_formats[i].code) + return &csi_formatter_formats[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static inline struct csi_formatter *sd_to_formatter(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct csi_formatter, sd); +} + +static int __csi_formatter_subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + 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, + &formatter_default_fmt); +} + +static int csi_formatter_subdev_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = CSI_FORMATTER_PAD_SINK, + .sink_stream = 0, + .source_pad = CSI_FORMATTER_PAD_SOURCE, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return __csi_formatter_subdev_set_routing(sd, sd_state, &routing); +} + +static int csi_formatter_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad == CSI_FORMATTER_PAD_SOURCE) { + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) + return -EINVAL; + + fmt = v4l2_subdev_state_get_format(sd_state, code->pad, + code->stream); + code->code = fmt->code; + return 0; + } + + if (code->index >= ARRAY_SIZE(csi_formatter_formats)) + return -EINVAL; + + code->code = csi_formatter_formats[code->index].code; + + return 0; +} + +static int csi_formatter_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && + media_entity_is_streaming(&sd->entity)) + return -EBUSY; + + if (format->pad == CSI_FORMATTER_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, sd_state, format); + + if (!csi_formatter_find_format(format->format.code)) + format->format.code = csi_formatter_formats[0].code; + + v4l_bound_align_image(&format->format.width, 1, 0xffff, 2, + &format->format.height, 1, 0xffff, 0, 0); + + /* TODO: Add interlaced format support */ + format->format.field = V4L2_FIELD_NONE; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad, + format->stream); + *fmt = format->format; + + /* Propagate the format from sink stream to source stream */ + fmt = v4l2_subdev_state_get_opposite_stream_format(sd_state, format->pad, + format->stream); + *fmt = format->format; + + return 0; +} + +static int csi_formatter_subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && + media_entity_is_streaming(&sd->entity)) + return -EBUSY; + + return __csi_formatter_subdev_set_routing(sd, state, routing); +} + +static u8 csi_formatter_get_index_by_dt(struct csi_formatter *formatter, + u8 data_type) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(csi_formatter_dt_to_index_map); ++i) { + const struct csi_formatter_dt_index *entry = + &csi_formatter_dt_to_index_map[i]; + + if (data_type == entry->dtype) + return entry->index; + } + + dev_WARN(formatter->dev, "Unsupported data type 0x%x, using default\n", + data_type); + + return csi_formatter_dt_to_index_map[0].index; +} + +static int csi_formatter_get_vc(struct csi_formatter *formatter, + struct v4l2_mbus_frame_desc *fd, + unsigned int stream) +{ + struct v4l2_mbus_frame_desc_entry *entry = NULL; + u8 vc; + + for (unsigned int i = 0; i < fd->num_entries; ++i) { + if (fd->entry[i].stream == stream) { + entry = &fd->entry[i]; + break; + } + } + + if (!entry) { + dev_err(formatter->dev, + "No frame desc entry for stream %u\n", stream); + return -EPIPE; + } + + vc = entry->bus.csi2.vc; + + if (vc >= CSI_FORMATTER_VC_NUM) { + dev_err(formatter->dev, "Invalid virtual channel %u\n", vc); + return -EPIPE; + } + + return vc; +} + +static int csi_formatter_get_frame_desc(struct csi_formatter *formatter, + struct v4l2_mbus_frame_desc *fd) +{ + int ret; + + ret = v4l2_subdev_call(formatter->remote_sd, pad, get_frame_desc, + formatter->remote_pad, fd); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_err(formatter->dev, "Failed to get frame desc: %d\n", ret); + return ret; + } + + /* + * If the source doesn't implement .get_frame_desc(), assume a single + * stream on VC 0. fd is zero-initialized, only set the fields that have + * a non-zero value. + */ + if (ret == -ENOIOCTLCMD) { + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + fd->num_entries = 1; + } + + return 0; +} + +static void csi_formatter_stop_stream(struct csi_formatter *formatter, + struct v4l2_subdev_state *state, + u64 stream_mask) +{ + const struct csi_formatter_pix_format *pix_fmt; + struct v4l2_mbus_frame_desc fd = {}; + struct v4l2_subdev_route *route; + struct v4l2_mbus_framefmt *fmt; + unsigned int reg; + unsigned int mask; + int vc; + int ret; + + ret = csi_formatter_get_frame_desc(formatter, &fd); + if (ret) + return; + + for_each_active_route(&state->routing, route) { + if (!(stream_mask & BIT_ULL(route->source_stream))) + continue; + + vc = csi_formatter_get_vc(formatter, &fd, route->sink_stream); + if (vc < 0) + continue; + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + + pix_fmt = csi_formatter_find_format(fmt->code); + if (WARN_ON(!pix_fmt)) + continue; + + reg = CSI_VC_PIXEL_DATA_TYPE(vc) + formatter->reg_offset; + mask = BIT(csi_formatter_get_index_by_dt(formatter, + pix_fmt->data_type)); + + /* Clear the data type bit to disable this VC */ + regmap_clear_bits(formatter->regs, reg, mask); + } +} + +static int csi_formatter_start_stream(struct csi_formatter *formatter, + struct v4l2_subdev_state *state, + u64 stream_mask) +{ + const struct csi_formatter_pix_format *pix_fmt; + struct v4l2_subdev_route *route; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_mbus_frame_desc fd = {}; + u64 configured_streams = 0; + unsigned int reg; + unsigned int mask; + int vc; + int ret; + + ret = csi_formatter_get_frame_desc(formatter, &fd); + if (ret) + return ret; + + for_each_active_route(&state->routing, route) { + if (!(stream_mask & BIT_ULL(route->source_stream))) + continue; + + vc = csi_formatter_get_vc(formatter, &fd, route->sink_stream); + if (vc < 0) { + ret = vc; + goto err_cleanup; + } + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + + pix_fmt = csi_formatter_find_format(fmt->code); + if (WARN_ON(!pix_fmt)) { + ret = -EINVAL; + goto err_cleanup; + } + + reg = CSI_VC_PIXEL_DATA_TYPE(vc) + formatter->reg_offset; + mask = BIT(csi_formatter_get_index_by_dt(formatter, + pix_fmt->data_type)); + + /* Set the data type bit to enable this VC */ + regmap_set_bits(formatter->regs, reg, mask); + + configured_streams |= BIT_ULL(route->source_stream); + } + + return 0; + +err_cleanup: + csi_formatter_stop_stream(formatter, state, configured_streams); + return ret; +} + +static int csi_formatter_subdev_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct csi_formatter *formatter = sd_to_formatter(sd); + struct device *dev = formatter->dev; + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, + CSI_FORMATTER_PAD_SOURCE, + CSI_FORMATTER_PAD_SINK, + &streams_mask); + if (!sink_streams || !streams_mask) + return -EINVAL; + + if (!formatter->enabled_streams) { + ret = pm_runtime_resume_and_get(formatter->dev); + if (ret < 0) { + dev_err(dev, "Failed to resume runtime PM: %d\n", ret); + return ret; + } + } + + ret = csi_formatter_start_stream(formatter, state, streams_mask); + if (ret) + goto err_runtime_put; + + ret = v4l2_subdev_enable_streams(formatter->remote_sd, + formatter->remote_pad, + sink_streams); + if (ret) + goto err_stop_stream; + + formatter->enabled_streams |= streams_mask; + + return 0; + +err_stop_stream: + csi_formatter_stop_stream(formatter, state, streams_mask); +err_runtime_put: + if (!formatter->enabled_streams) + pm_runtime_put_autosuspend(formatter->dev); + return ret; +} + +static int csi_formatter_subdev_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct csi_formatter *formatter = sd_to_formatter(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, + CSI_FORMATTER_PAD_SOURCE, + CSI_FORMATTER_PAD_SINK, + &streams_mask); + if (!sink_streams || !streams_mask) + return -EINVAL; + + ret = v4l2_subdev_disable_streams(formatter->remote_sd, formatter->remote_pad, + sink_streams); + if (ret) + dev_err(formatter->dev, "Failed to disable streams: %d\n", ret); + + csi_formatter_stop_stream(formatter, state, streams_mask); + + formatter->enabled_streams &= ~streams_mask; + + if (!formatter->enabled_streams) + pm_runtime_put_autosuspend(formatter->dev); + + return ret; +} + +static const struct v4l2_subdev_pad_ops formatter_subdev_pad_ops = { + .enum_mbus_code = csi_formatter_subdev_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = csi_formatter_subdev_set_fmt, + .get_frame_desc = v4l2_subdev_get_frame_desc_passthrough, + .set_routing = csi_formatter_subdev_set_routing, + .enable_streams = csi_formatter_subdev_enable_streams, + .disable_streams = csi_formatter_subdev_disable_streams, +}; + +static const struct v4l2_subdev_ops formatter_subdev_ops = { + .pad = &formatter_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops formatter_internal_ops = { + .init_state = csi_formatter_subdev_init_state, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +static const struct media_entity_operations formatter_entity_ops = { + .link_validate = v4l2_subdev_link_validate, + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, +}; + +static int csi_formatter_subdev_init(struct csi_formatter *formatter) +{ + struct v4l2_subdev *sd = &formatter->sd; + int ret; + + v4l2_subdev_init(sd, &formatter_subdev_ops); + + strscpy(sd->name, dev_name(formatter->dev), sizeof(sd->name)); + sd->internal_ops = &formatter_internal_ops; + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_STREAMS; + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops = &formatter_entity_ops; + sd->dev = formatter->dev; + + formatter->pads[CSI_FORMATTER_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; + formatter->pads[CSI_FORMATTER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&sd->entity, CSI_FORMATTER_PAD_NUM, + formatter->pads); + if (ret) { + dev_err(formatter->dev, "Failed to init pads\n"); + return ret; + } + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + media_entity_cleanup(&sd->entity); + + return ret; +} + +static inline struct csi_formatter * +notifier_to_csi_formatter(struct v4l2_async_notifier *n) +{ + return container_of(n, struct csi_formatter, notifier); +} + +static int csi_formatter_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + const unsigned int link_flags = MEDIA_LNK_FL_IMMUTABLE + | MEDIA_LNK_FL_ENABLED; + struct csi_formatter *formatter = notifier_to_csi_formatter(notifier); + struct v4l2_subdev *sdev = &formatter->sd; + struct media_pad *sink = &sdev->entity.pads[CSI_FORMATTER_PAD_SINK]; + int ret; + + formatter->remote_sd = sd; + + ret = v4l2_create_fwnode_links_to_pad(sd, sink, link_flags); + if (ret < 0) + return ret; + + formatter->remote_pad = media_pad_remote_pad_first(sink)->index; + + return 0; +} + +static const struct v4l2_async_notifier_operations formatter_notify_ops = { + .bound = csi_formatter_notify_bound, +}; + +static int csi_formatter_async_register(struct csi_formatter *formatter) +{ + struct device *dev = formatter->dev; + struct v4l2_async_connection *asc; + int ret; + + struct fwnode_handle *ep __free(fwnode_handle) = + fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + v4l2_async_subdev_nf_init(&formatter->notifier, &formatter->sd); + + asc = v4l2_async_nf_add_fwnode_remote(&formatter->notifier, ep, + struct v4l2_async_connection); + if (IS_ERR(asc)) { + ret = PTR_ERR(asc); + goto err_cleanup_notifier; + } + + formatter->notifier.ops = &formatter_notify_ops; + + ret = v4l2_async_nf_register(&formatter->notifier); + if (ret) + goto err_cleanup_notifier; + + ret = v4l2_async_register_subdev(&formatter->sd); + if (ret) + goto err_unregister_notifier; + + return 0; + +err_unregister_notifier: + v4l2_async_nf_unregister(&formatter->notifier); +err_cleanup_notifier: + v4l2_async_nf_cleanup(&formatter->notifier); + return ret; +} + +static void csi_formatter_async_unregister(struct csi_formatter *formatter) +{ + v4l2_async_unregister_subdev(&formatter->sd); + v4l2_async_nf_unregister(&formatter->notifier); + v4l2_async_nf_cleanup(&formatter->notifier); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(csi_formatter_pm_ops, + pm_clk_suspend, pm_clk_resume, NULL); + +static int csi_formatter_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct csi_formatter *formatter; + int ret; + + formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); + if (!formatter) + return -ENOMEM; + + formatter->dev = dev; + + formatter->regs = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(formatter->regs)) + return dev_err_probe(dev, PTR_ERR(formatter->regs), + "Failed to get csi formatter regmap\n"); + + ret = of_property_read_u32(dev->of_node, "reg", &formatter->reg_offset); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get csi formatter reg property\n"); + + ret = devm_pm_clk_create(dev); + if (ret) + return ret; + + ret = of_pm_clk_add_clks(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add clocks\n"); + + ret = csi_formatter_subdev_init(formatter); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to initialize formatter subdev\n"); + + platform_set_drvdata(pdev, formatter); + + /* Enable runtime PM with autosuspend. */ + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + goto err_pm_disable; + + ret = csi_formatter_async_register(formatter); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to register async subdevice\n"); + goto err_pm_disable; + } + + return 0; + +err_pm_disable: + pm_runtime_dont_use_autosuspend(dev); + v4l2_subdev_cleanup(&formatter->sd); + media_entity_cleanup(&formatter->sd.entity); + return ret; +} + +static void csi_formatter_remove(struct platform_device *pdev) +{ + struct csi_formatter *formatter = platform_get_drvdata(pdev); + + csi_formatter_async_unregister(formatter); + + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_suspend(&pdev->dev); + + v4l2_subdev_cleanup(&formatter->sd); + media_entity_cleanup(&formatter->sd.entity); +} + +static const struct of_device_id csi_formatter_of_match[] = { + { .compatible = "fsl,imx95-csi-formatter" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, csi_formatter_of_match); + +static struct platform_driver csi_formatter_device_driver = { + .driver = { + .name = "csi-pixel-formatter", + .of_match_table = csi_formatter_of_match, + .pm = pm_ptr(&csi_formatter_pm_ops), + }, + .probe = csi_formatter_probe, + .remove = csi_formatter_remove, +}; + +module_platform_driver(csi_formatter_device_driver); + +MODULE_AUTHOR("NXP Semiconductor, Inc."); +MODULE_DESCRIPTION("NXP i.MX95 CSI Pixel Formatter driver"); +MODULE_LICENSE("GPL"); From e8a24041a0974f4f1fcc16004c54df8116ffaef1 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 28 Jul 2026 21:06:43 +0300 Subject: [PATCH 028/596] MAINTAINERS: Add L: linux-media for int3472 The int3472 patches now go via LMML, add the list to L: section for the int3472 driver in MAINTAINERS as well. Signed-off-by: Sakari Ailus Acked-by: Daniel Scally --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index baf4ca2c3928..e12dc3ca57ab 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13491,6 +13491,7 @@ K: \bSGX_ INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally M: Sakari Ailus +L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media.git F: drivers/platform/x86/intel/int3472/ From 94ad8a0abaa76c09e11520f55e86ec610ccf65bf Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 20 Aug 2026 23:17:31 +0300 Subject: [PATCH 029/596] platform/x86: int3472: Address Coccinelle warning on an error print MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the following Coccinelle warning: ./tps68470.c:164:58-65: WARNING: Consider using %pe to print PTR_ERR() by using %pe specifier for printing an error code. Signed-off-by: Sakari Ailus Reviewed-by: Ilpo Järvinen Reviewed-by: Ricardo Ribalda Acked-by: Rafael J. Wysocki (Intel) Reviewed-by: Daniel Scally --- drivers/platform/x86/intel/int3472/tps68470.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index a77ed32abe55..dc777dbac61f 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -161,7 +161,7 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); if (IS_ERR(regmap)) { - dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); + dev_err(&client->dev, "Failed to create regmap: %pe\n", regmap); return PTR_ERR(regmap); } From 70e6384fe0f4cf9768944f81924f6c1478b0ce43 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 20 Aug 2026 23:19:01 +0300 Subject: [PATCH 030/596] platform/x86: int3472: Fix uninitialised variable warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a compiler warning about an uninitialised err_msg variable by printing the error where it is handled: drivers/platform/x86/intel/int3472/discrete.c:440:24: warning: ‘err_msg’ may be used uninitialized [-Wmaybe-uninitialized] 440 | return dev_err_probe(int3472->dev, ret, err_msg); Fixes: 5de691bffe57 ("platform/x86: Add intel_skl_int3472 driver") Cc: stable@vger.kernel.org Signed-off-by: Sakari Ailus Reviewed-by: Ricardo Ribalda Acked-by: Rafael J. Wysocki (Intel) Reviewed-by: Daniel Scally --- drivers/platform/x86/intel/int3472/discrete.c | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 6c729fcfce5d..9cd4f78875ab 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -329,7 +329,6 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, unsigned long gpio_flags; union acpi_object *obj; struct gpio_desc *gpio; - const char *err_msg; const char *con_id; int ret; @@ -375,7 +374,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, case INT3472_GPIO_TYPE_HOTPLUG_DETECT: ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, con_id, gpio_flags); if (ret) - err_msg = "Failed to map GPIO pin to sensor\n"; + dev_err_probe(int3472->dev, ret, + "Failed to map GPIO pin to sensor\n"); break; case INT3472_GPIO_TYPE_CLK_ENABLE: @@ -387,7 +387,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags); if (IS_ERR(gpio)) { ret = PTR_ERR(gpio); - err_msg = "Failed to get GPIO\n"; + dev_err_probe(int3472->dev, ret, "Failed to get GPIO\n"); break; } @@ -395,14 +395,16 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, case INT3472_GPIO_TYPE_CLK_ENABLE: ret = skl_int3472_register_gpio_clock(int3472, gpio); if (ret) - err_msg = "Failed to register clock\n"; + dev_err_probe(int3472->dev, ret, + "Failed to register clock\n"); break; case INT3472_GPIO_TYPE_PRIVACY_LED: case INT3472_GPIO_TYPE_STROBE: ret = skl_int3472_register_led(int3472, gpio, con_id); if (ret) - err_msg = "Failed to register LED\n"; + dev_err_probe(int3472->dev, ret, + "Failed to register LED\n"); break; case INT3472_GPIO_TYPE_POWER_ENABLE: @@ -413,7 +415,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, ret = skl_int3472_register_regulator(int3472, gpio, enable_time_us, con_id, second_sensor); if (ret) - err_msg = "Failed to register regulator\n"; + dev_err_probe(int3472->dev, ret, + "Failed to register regulator\n"); break; default: /* Never reached */ @@ -436,11 +439,11 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, int3472->ngpios++; ACPI_FREE(obj); - if (ret < 0) - return dev_err_probe(int3472->dev, ret, err_msg); - - /* Tell acpi_dev_get_resources() to not make a copy of the resource */ - return 1; + /* + * Either return an error or tell acpi_dev_get_resources() to not make a + * copy of the resource. + */ + return ret < 0 ? ret : 1; } int int3472_discrete_parse_crs(struct int3472_discrete_device *int3472) From 280bc0dc42c408f6690d3bbd754ef7108e935231 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 25 Aug 2026 22:39:00 +0300 Subject: [PATCH 031/596] platform/x86: int3472: Clean up GPIO parsing In skl_int3472_handle_gpio_resources(), only a single integer value from the ACPI object is used so release it early and avoid having to call ACPI_FREE() on it later on. This makes it possible to return an error where it happens, except when we're holding a reference to a GPIO. ngpios is also incremented earlier on but that does not introduce a functional change. Signed-off-by: Sakari Ailus Acked-by: Rafael J. Wysocki (Intel) Reviewed-by: Daniel Scally --- drivers/platform/x86/intel/int3472/discrete.c | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 9cd4f78875ab..1f8751a7db0c 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -328,6 +328,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, u8 active_value, pin, type; unsigned long gpio_flags; union acpi_object *obj; + unsigned int obj_value; struct gpio_desc *gpio; const char *con_id; int ret; @@ -343,24 +344,27 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, &int3472_gpio_guid, 0x00, int3472->ngpios + 2, NULL, ACPI_TYPE_INTEGER); - if (!obj) { dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", agpio->pin_table[0]); return 1; } - type = FIELD_GET(INT3472_GPIO_DSM_TYPE, obj->integer.value); + obj_value = obj->integer.value; + + ACPI_FREE(obj); + + type = FIELD_GET(INT3472_GPIO_DSM_TYPE, obj_value); int3472_get_con_id_and_polarity(int3472, &type, &con_id, &gpio_flags, &enable_time_us); - pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj->integer.value); + pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj_value); /* Pin field is not really used under Windows and wraps around at 8 bits */ if (pin != (agpio->pin_table[0] & 0xff)) dev_dbg(int3472->dev, FW_BUG "%s %s pin number mismatch _DSM %d resource %d\n", con_id, agpio->resource_source.string_ptr, pin, agpio->pin_table[0]); - active_value = FIELD_GET(INT3472_GPIO_DSM_SENSOR_ON_VAL, obj->integer.value); + active_value = FIELD_GET(INT3472_GPIO_DSM_SENSOR_ON_VAL, obj_value); if (!active_value) gpio_flags ^= GPIO_ACTIVE_LOW; @@ -368,16 +372,18 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, agpio->resource_source.string_ptr, agpio->pin_table[0], str_high_low(gpio_flags == GPIO_ACTIVE_HIGH)); + int3472->ngpios++; + switch (type) { case INT3472_GPIO_TYPE_RESET: case INT3472_GPIO_TYPE_POWERDOWN: case INT3472_GPIO_TYPE_HOTPLUG_DETECT: ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, con_id, gpio_flags); if (ret) - dev_err_probe(int3472->dev, ret, - "Failed to map GPIO pin to sensor\n"); + return dev_err_probe(int3472->dev, ret, + "Failed to map GPIO pin to sensor\n"); - break; + return 1; case INT3472_GPIO_TYPE_CLK_ENABLE: case INT3472_GPIO_TYPE_PRIVACY_LED: case INT3472_GPIO_TYPE_STROBE: @@ -385,11 +391,9 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, case INT3472_GPIO_TYPE_DOVDD: case INT3472_GPIO_TYPE_HANDSHAKE: gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags); - if (IS_ERR(gpio)) { - ret = PTR_ERR(gpio); - dev_err_probe(int3472->dev, ret, "Failed to get GPIO\n"); - break; - } + if (IS_ERR(gpio)) + return dev_err_probe(int3472->dev, PTR_ERR(gpio), + "Failed to get GPIO\n"); switch (type) { case INT3472_GPIO_TYPE_CLK_ENABLE: @@ -427,23 +431,13 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, if (ret) gpiod_put(gpio); - break; + return ret < 0 ? ret : 1; default: dev_warn(int3472->dev, "GPIO type 0x%02x unknown; the sensor may not work\n", type); - ret = 1; - break; + return 1; } - - int3472->ngpios++; - ACPI_FREE(obj); - - /* - * Either return an error or tell acpi_dev_get_resources() to not make a - * copy of the resource. - */ - return ret < 0 ? ret : 1; } int int3472_discrete_parse_crs(struct int3472_discrete_device *int3472) From 844982d3b99340c21dd2125f44e1ad04ace6a38e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 18 Aug 2026 14:28:21 +0200 Subject: [PATCH 032/596] platform/x86: int3472: Increase handshake GPIO delay to 200 ms There have been several reports of the sensor not properly powering on, on laptops where an INT3472 handshake GPIO is used. Both with ov08x40 and hi556 sensors. Testing has shown that a delay of 200 ms is necessary in some cases. The handshake GPIO is typically used to signal an extra bridge or other IC with that IC running the full sensor power sequence based on the handshake signal and also possible controlling I2C pass-through. It seems that in some cases that extra IC takes up to 200 ms to before the sensor is fully powered up and ready for I2C communication. With the default handshake signal delay increased to 200 ms the quirk to increase it to 45 ms for the ov08x40 is no longer necessary and is actually undesirable since some ov08x40 setups also need the longer delay, drop it. Fixes: 30359c239ba8 ("platform/x86: int3472: Increase ov08x40 handshake GPIO delay to 45 ms") Closes: https://lore.kernel.org/platform-driver-x86/20260816204247.2844986-1-opensource@inspiredexperts.com/ Reported-by: James Alexander Closes: https://bugzilla.redhat.com/show_bug.cgi?id=2333331 Closes: https://bugzilla.redhat.com/show_bug.cgi?id=2333647 Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus --- drivers/platform/x86/intel/int3472/discrete.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 1f8751a7db0c..749e2e323583 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -15,6 +15,7 @@ #include #include #include +#include #include /* @@ -178,13 +179,6 @@ static const struct int3472_gpio_map int3472_gpio_map[] = { .type_to = INT3472_GPIO_TYPE_RESET, .con_id = "enable", }, - { /* ov08x40's handshake pin needs a 45 ms delay on some HP laptops */ - .hids = (const char * const[]) { "OVTI08F4", NULL }, - .type_from = INT3472_GPIO_TYPE_HANDSHAKE, - .type_to = INT3472_GPIO_TYPE_HANDSHAKE, - .con_id = "dvdd", - .enable_time_us = 45 * USEC_PER_MSEC, - }, { /* Sensors which expect "vana" as con_id for power enable */ .hids = power_enable_hids_vana, .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, @@ -273,8 +267,8 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 case INT3472_GPIO_TYPE_HANDSHAKE: *con_id = "dvdd"; *gpio_flags = GPIO_ACTIVE_HIGH; - /* Setups using a handshake pin need 25 ms enable delay */ - *enable_time_us = 25 * USEC_PER_MSEC; + /* Powering up the sensor through the handshake pin takes up to 200 ms */ + *enable_time_us = 200 * USEC_PER_MSEC; break; default: *con_id = "unknown"; From 59cab094b8b50d7d4483bb04143294e1fb66aacc Mon Sep 17 00:00:00 2001 From: "D. Manresa" Date: Mon, 31 Aug 2026 12:23:06 +0200 Subject: [PATCH 033/596] platform/x86: int3472: map the ov7251 power enable GPIO to "vdda" On IPU6 designs carrying an OV7251 IR sensor (ACPI HID INT347E) the INT3472 describes the sensor's single gated rail as a power-enable GPIO, which the driver registers as a regulator with con_id "avdd". The ov7251 driver and its DT bindings name the sensor supplies vdda, vddd and vdddo, so the lookup finds nothing, the rail is never enabled, and probe fails on the first I2C access: int3472-discrete INT3472:02: avdd \_SB.GPI0 pin 131 active-high ov7251 i2c-INT347E:00: supply vdda not found, using dummy regulator ov7251 i2c-INT347E:00: probe with driver ov7251 failed with error -121 Map the power-enable GPIO to con_id "vdda" for INT347E, the same way the existing entries map it to "vdd" for the mt9m114 and to "vana" for the imx471. The sensor's other two supplies keep falling back to the dummy regulator, which is correct here: the firmware gates a single rail. The table already has an INT347E entry mapping the reset GPIO to "enable", added when the ov7251 was brought up on these machines; this is the same class of firmware-vs-binding name mismatch for the remaining pin. Tested on a Microsoft Surface Pro 7+ (Tiger Lake, IPU6): with this change the IR sensor probes, the illuminator strobes, and the camera streams 640x480 at 30 fps through libcamera. The same failure was reproduced independently on a second Pro 7+ running stock firmware and a distro kernel, with no out-of-tree code, in [1]. Developed with the assistance of an AI tool (Claude) and verified on hardware. Link: https://github.com/linux-surface/linux-surface/issues/1702 [1] Reviewed-by: Hans de Goede Signed-off-by: D. Manresa Tested-by: Jakob Berg Jespersen # Surface Pro 7+, probe/power-on Signed-off-by: Sakari Ailus --- drivers/platform/x86/intel/int3472/discrete.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 749e2e323583..04aaf4958fa7 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -144,6 +144,11 @@ static const char * const power_enable_hids_enable[] = { NULL }; +static const char * const power_enable_hids_vdda[] = { + "INT347E", /* ov7251 */ + NULL +}; + /** * struct int3472_gpio_map - Map GPIOs to whatever is expected by the * sensor driver (as in DT bindings) @@ -179,6 +184,13 @@ static const struct int3472_gpio_map int3472_gpio_map[] = { .type_to = INT3472_GPIO_TYPE_RESET, .con_id = "enable", }, + { /* Sensors which expect "vdda" as con_id for power enable */ + .hids = power_enable_hids_vdda, + .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, + .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, + .con_id = "vdda", + .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, + }, { /* Sensors which expect "vana" as con_id for power enable */ .hids = power_enable_hids_vana, .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, From 34e31390a3b939db2e0cbfef4c40d95052572df0 Mon Sep 17 00:00:00 2001 From: Jakob Berg Jespersen Date: Sat, 29 Aug 2026 10:29:09 +0200 Subject: [PATCH 034/596] platform/x86: int3472: support the POWER1 GPIO type INT3472 can describe a second sensor power rail as a GPIO of type POWER1 (0x08), which the driver does not recognise, so the rail is left unmapped and never enabled: int3472-discrete INT3472:01: GPIO type 0x08 unknown; the sensor may not work On the Microsoft Surface Pro 7+ the rear camera's INT3472 (INT347A, ov8865) has such a pin; without it the ov8865 "dvdd" supply resolves to a dummy regulator and the sensor never probes. Define the POWER0 (0x07) and POWER1 (0x08) GPIO types and map POWER1 to a regulator with con_id "dvdd" for all devices, the supply the in-tree ov8865 driver already requests. POWER0 is defined but left unmapped, as no device that uses it is known. With this change the Surface Pro 7+ rear camera probes and streams reliably. Link: https://github.com/linux-surface/linux-surface/pull/1867 Link: https://github.com/linux-surface/linux-surface/pull/2201 Suggested-by: Sakari Ailus Signed-off-by: Jakob Berg Jespersen Reviewed-by: Hans de Goede [Sakari Ailus: Sanitised the commit message a little.] Signed-off-by: Sakari Ailus --- drivers/platform/x86/intel/int3472/discrete.c | 8 ++++++++ include/linux/platform_data/x86/int3472.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 04aaf4958fa7..b48cf1b7fd5f 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -272,6 +272,10 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 *con_id = "avdd"; *gpio_flags = GPIO_ACTIVE_HIGH; break; + case INT3472_GPIO_TYPE_POWER1: + *con_id = "dvdd"; + *gpio_flags = GPIO_ACTIVE_HIGH; + break; case INT3472_GPIO_TYPE_DOVDD: *con_id = "dovdd"; *gpio_flags = GPIO_ACTIVE_HIGH; @@ -302,6 +306,8 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 * 0x00 Reset * 0x01 Power down * 0x02 Strobe + * 0x07 Power 0 + * 0x08 Power 1 * 0x0b Power enable * 0x0c Clock enable * 0x0d Privacy LED @@ -394,6 +400,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, case INT3472_GPIO_TYPE_PRIVACY_LED: case INT3472_GPIO_TYPE_STROBE: case INT3472_GPIO_TYPE_POWER_ENABLE: + case INT3472_GPIO_TYPE_POWER1: case INT3472_GPIO_TYPE_DOVDD: case INT3472_GPIO_TYPE_HANDSHAKE: gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags); @@ -420,6 +427,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, case INT3472_GPIO_TYPE_POWER_ENABLE: second_sensor = int3472->quirks.avdd_second_sensor; fallthrough; + case INT3472_GPIO_TYPE_POWER1: case INT3472_GPIO_TYPE_DOVDD: case INT3472_GPIO_TYPE_HANDSHAKE: ret = skl_int3472_register_regulator(int3472, gpio, enable_time_us, diff --git a/include/linux/platform_data/x86/int3472.h b/include/linux/platform_data/x86/int3472.h index a73841dfae27..b1040e36deb8 100644 --- a/include/linux/platform_data/x86/int3472.h +++ b/include/linux/platform_data/x86/int3472.h @@ -25,6 +25,8 @@ #define INT3472_GPIO_TYPE_RESET 0x00 #define INT3472_GPIO_TYPE_POWERDOWN 0x01 #define INT3472_GPIO_TYPE_STROBE 0x02 +#define INT3472_GPIO_TYPE_POWER0 0x07 +#define INT3472_GPIO_TYPE_POWER1 0x08 #define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b #define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c #define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d From f35aa1f59245b046f30ee6ccbc968bba0a76d97f Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 11 Aug 2026 08:29:05 +0300 Subject: [PATCH 035/596] media: ipu-bridge: Print more debug information on connected devices Print information mainly from SSDB: port number, number of lanes, rotation and orientation as well as VCM model on debug level. Also print the fwnode path of the connected sensor. Signed-off-by: Sakari Ailus Reviewed-by: Hans de Goede --- drivers/media/pci/intel/ipu-bridge.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 1bb3a3e98d6b..125fe5da7a60 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -412,6 +412,7 @@ static enum v4l2_fwnode_orientation ipu_bridge_parse_orientation(struct acpi_dev int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor) { + acpi_handle handle = acpi_device_handle(ACPI_PTR(adev)); struct ipu_sensor_ssdb ssdb = {}; int ret; @@ -435,8 +436,15 @@ int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor) sensor->rotation = ipu_bridge_parse_rotation(adev, &ssdb); sensor->orientation = ipu_bridge_parse_orientation(adev); - if (ssdb.vcmtype) + acpi_handle_debug(handle, + "CSI-2 port %u, lanes %u, mclkspeed %u, rotation %u (SSDB %u), orientation %u\n", + sensor->link, sensor->lanes, sensor->mclkspeed, + sensor->rotation, ssdb.degree, sensor->orientation); + + if (ssdb.vcmtype) { sensor->vcm_type = ipu_vcm_types[ssdb.vcmtype - 1]; + acpi_handle_debug(handle, "VCM %s\n", sensor->vcm_type); + } return 0; } @@ -832,8 +840,8 @@ static int ipu_bridge_connect_sensor(const struct ipu_sensor_config *cfg, if (ret) goto err_free_swnodes; - dev_info(bridge->dev, "Found supported sensor %s\n", - acpi_dev_name(adev)); + dev_info(bridge->dev, "Found supported sensor %s (%pfw)\n", + acpi_dev_name(adev), primary); bridge->n_sensors++; } From 89ee42c3fac185eabe991399092dbeb82783f0d3 Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Wed, 19 Aug 2026 12:28:05 +0200 Subject: [PATCH 036/596] media: v4l2-common: Convert v4l2_fill_pixfmt_mp() to static inline wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert v4l2_fill_pixfmt_mp() to static inline wrapper: drop the exported v4l2_fill_pixfmt_mp() function from v4l2-common.c and replace it with an equivalent static inline in the header that delegates to v4l2_fill_pixfmt_mp_aligned() with stride_alignment=1. Reviewed-by: Jacopo Mondi Signed-off-by: Tommaso Merciai Reviewed-by: Sven Püschel Reviewed-by: Lad Prabhakar Signed-off-by: Sakari Ailus --- drivers/media/v4l2-core/v4l2-common.c | 8 -------- include/media/v4l2-common.h | 9 +++++++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index 65db7340ad38..54995ba8c20d 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -537,14 +537,6 @@ int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt, } EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp_aligned); -int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, - u32 pixelformat, u32 width, u32 height) -{ - return v4l2_fill_pixfmt_mp_aligned(pixfmt, pixelformat, - width, height, 1); -} -EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp); - int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat, u32 width, u32 height) { diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h index edd416178c33..749fe38c134e 100644 --- a/include/media/v4l2-common.h +++ b/include/media/v4l2-common.h @@ -556,13 +556,18 @@ void v4l2_apply_frmsize_constraints(u32 *width, u32 *height, const struct v4l2_frmsize_stepwise *frmsize); int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat, u32 width, u32 height); -int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, u32 pixelformat, - u32 width, u32 height); + /* @stride_alignment is a power of 2 value in bytes */ int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt, u32 pixelformat, u32 width, u32 height, u8 stride_alignment); +static inline int v4l2_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, + u32 pixelformat, u32 width, u32 height) +{ + return v4l2_fill_pixfmt_mp_aligned(pixfmt, pixelformat, width, height, 1); +} + /** * v4l2_get_link_freq - Get link rate from transmitter * From 9dd982125dced7e166ee64643d5776abfd66f8c2 Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Wed, 19 Aug 2026 12:28:06 +0200 Subject: [PATCH 037/596] media: v4l2-common: Add v4l2_fill_pixfmt_aligned() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add v4l2_fill_pixfmt_aligned(), a variant of v4l2_fill_pixfmt() that accepts a stride_alignment parameter, mirroring the existing v4l2_fill_pixfmt_mp() / v4l2_fill_pixfmt_mp_aligned() pair. v4l2_fill_pixfmt() is refactored to call v4l2_fill_pixfmt_aligned() with stride_alignment=1, preserving its existing behaviour. The new helper is needed by drivers whose DMA engine requires the line stride to be a multiple of a specific value, such as the Renesas RZ/G3E CRU which requires 128-byte alignment. Reviewed-by: Jacopo Mondi Signed-off-by: Tommaso Merciai Reviewed-by: Sven Püschel Reviewed-by: Lad Prabhakar Signed-off-by: Sakari Ailus --- drivers/media/v4l2-core/v4l2-common.c | 12 +++++---- include/media/v4l2-common.h | 38 +++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index 54995ba8c20d..2ce4f1c20fbc 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -537,8 +537,8 @@ int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt, } EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_mp_aligned); -int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat, - u32 width, u32 height) +int v4l2_fill_pixfmt_aligned(struct v4l2_pix_format *pixfmt, u32 pixelformat, + u32 width, u32 height, u8 stride_alignment) { const struct v4l2_format_info *info; int i; @@ -554,15 +554,17 @@ int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat, pixfmt->width = width; pixfmt->height = height; pixfmt->pixelformat = pixelformat; - pixfmt->bytesperline = v4l2_format_plane_stride(info, 0, width, 1); + pixfmt->bytesperline = v4l2_format_plane_stride(info, 0, width, + stride_alignment); pixfmt->sizeimage = 0; for (i = 0; i < info->comp_planes; i++) pixfmt->sizeimage += - v4l2_format_plane_size(info, i, width, height, 1); + v4l2_format_plane_size(info, i, width, height, + stride_alignment); return 0; } -EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt); +EXPORT_SYMBOL_GPL(v4l2_fill_pixfmt_aligned); #ifdef CONFIG_MEDIA_CONTROLLER static s64 v4l2_get_link_freq_ctrl(struct v4l2_ctrl_handler *handler, diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h index 749fe38c134e..4e5c5ffaf651 100644 --- a/include/media/v4l2-common.h +++ b/include/media/v4l2-common.h @@ -554,8 +554,42 @@ static inline bool v4l2_is_format_bayer(const struct v4l2_format_info *f) const struct v4l2_format_info *v4l2_format_info(u32 format); void v4l2_apply_frmsize_constraints(u32 *width, u32 *height, const struct v4l2_frmsize_stepwise *frmsize); -int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, u32 pixelformat, - u32 width, u32 height); + +/** + * v4l2_fill_pixfmt_aligned - Fill in a &struct v4l2_pix_format with stride + * alignment requirements + * + * @pixfmt: pointer to the &struct v4l2_pix_format to be filled + * @pixelformat: the V4L2 pixel format (V4L2_PIX_FMT_*) + * @width: image width in pixels + * @height: image height in pixels + * @stride_alignment: stride alignment in bytes, must be a power of 2 + * + * Fills all fields of @pixfmt for the given pixel format, dimensions, and + * stride alignment. Only formats stored in a single memory plane are + * supported; returns -EINVAL for multi-memory-plane formats. + * + * @pixfmt->bytesperline is set to the stride of the primary (plane 0) plane, + * rounded up to a multiple of @stride_alignment. For formats that store + * multiple component planes in a single memory buffer (e.g. YUV420), the + * alignment applied to each component plane's stride is scaled relative to + * @stride_alignment so that the chroma stride remains consistently derivable + * from the luma stride. @pixfmt->bytesperline therefore reflects only the + * primary plane stride. + * + * @pixfmt->sizeimage is set to the total size in bytes of all planes. + * + * Return: 0 on success, -EINVAL if @pixelformat is unknown or uses multiple + * memory planes. + */ +int v4l2_fill_pixfmt_aligned(struct v4l2_pix_format *pixfmt, u32 pixelformat, + u32 width, u32 height, u8 stride_alignment); + +static inline int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, + u32 pixelformat, u32 width, u32 height) +{ + return v4l2_fill_pixfmt_aligned(pixfmt, pixelformat, width, height, 1); +} /* @stride_alignment is a power of 2 value in bytes */ int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt, From ef3081d495ff9a05ce146fe9288d01fc5c0baad3 Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Wed, 19 Aug 2026 12:28:07 +0200 Subject: [PATCH 038/596] media: v4l2-common: Add kernel-doc for v4l2_fill_pixfmt_mp_aligned() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the bare placeholder comment with a full kernel-doc block documenting all parameters, the function behaviour for both single memory plane (mem_planes == 1) and multiple memory plane (mem_planes > 1) formats, and the return value. Reviewed-by: Jacopo Mondi Signed-off-by: Tommaso Merciai Reviewed-by: Sven Püschel Reviewed-by: Lad Prabhakar Signed-off-by: Sakari Ailus --- include/media/v4l2-common.h | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h index 4e5c5ffaf651..33f5713734cb 100644 --- a/include/media/v4l2-common.h +++ b/include/media/v4l2-common.h @@ -591,7 +591,33 @@ static inline int v4l2_fill_pixfmt(struct v4l2_pix_format *pixfmt, return v4l2_fill_pixfmt_aligned(pixfmt, pixelformat, width, height, 1); } -/* @stride_alignment is a power of 2 value in bytes */ +/** + * v4l2_fill_pixfmt_mp_aligned - Fill in a &struct v4l2_pix_format_mplane with + * stride alignment requirements. + * + * @pixfmt: pointer to the &struct v4l2_pix_format_mplane to be filled + * @pixelformat: the V4L2 pixel format (V4L2_PIX_FMT_*) + * @width: image width in pixels + * @height: image height in pixels + * @stride_alignment: stride alignment in bytes; must be a power of 2 + * + * Fills all fields of @pixfmt for the given pixel format, dimensions, and + * stride alignment. + * + * For formats stored in a single memory plane (mem_planes == 1), the + * behaviour matches v4l2_fill_pixfmt_aligned(): plane_fmt[0].bytesperline + * is set to the primary plane stride. The strides of all components are + * aligned to the @stride_alignment. To keep the chroma strides consistently + * derivable from the luma stride, strides may be aligned to a multiple of + * the @stride_alignment instead. plane_fmt[0].sizeimage covers all + * component planes. + * + * For formats with multiple memory planes (mem_planes > 1), each plane's + * bytesperline is independently rounded up to @stride_alignment, and each + * plane's sizeimage is set to bytesperline multiplied by the plane height. + * + * Return: 0 on success, -EINVAL if @pixelformat is unknown. + */ int v4l2_fill_pixfmt_mp_aligned(struct v4l2_pix_format_mplane *pixfmt, u32 pixelformat, u32 width, u32 height, u8 stride_alignment); From 4bcf5025452002ca0ac45dc6cbe8ec29ec0b66cc Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Wed, 19 Aug 2026 12:28:08 +0200 Subject: [PATCH 039/596] media: rzg2l-cru: Use v4l2_fill_pixfmt_aligned() for stride alignment Replace the open-coded bytesperline/sizeimage rounding with the newly added v4l2_fill_pixfmt_aligned(). No functional change intended. Reviewed-by: Jacopo Mondi Signed-off-by: Tommaso Merciai Reviewed-by: Lad Prabhakar Signed-off-by: Sakari Ailus --- drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c index 91eda5034248..27a35ef2a6df 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -849,12 +849,8 @@ static void rzg2l_cru_format_align(struct rzg2l_cru_dev *cru, v4l_bound_align_image(&pix->width, 320, info->max_width, 1, &pix->height, 240, info->max_height, 2, 0); - v4l2_fill_pixfmt(pix, pix->pixelformat, pix->width, pix->height); - - if (info->has_stride) { - pix->bytesperline = ALIGN(pix->bytesperline, RZG2L_CRU_STRIDE_ALIGN); - pix->sizeimage = pix->bytesperline * pix->height; - } + v4l2_fill_pixfmt_aligned(pix, pix->pixelformat, pix->width, pix->height, + info->has_stride ? RZG2L_CRU_STRIDE_ALIGN : 1); dev_dbg(cru->dev, "Format %ux%u bpl: %u size: %u\n", pix->width, pix->height, pix->bytesperline, pix->sizeimage); From 3542921bd11eb4e8f3efc68991bcbeff337cb8c7 Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Wed, 19 Aug 2026 12:28:09 +0200 Subject: [PATCH 040/596] media: rzg2l-cru: Replace has_stride with stride_align field RZG2L_CRU_STRIDE_ALIGN hardcodes an alignment only RZ/G3E and RZ/V2H need, as only they have an AMnIS register. Store the alignment into rzg2l_cru_info instead: 128 on RZ/G3E, 1 on RZ/G2L, and update the code accordingly. No functional change intended. Signed-off-by: Tommaso Merciai Reviewed-by: Lad Prabhakar Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c | 3 ++- drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h | 2 +- drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c | 7 +++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c index 798ef2916262..8c7f46ccce5f 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c @@ -360,7 +360,7 @@ static const struct rzg2l_cru_info rzg3e_cru_info = { .max_width = 4095, .max_height = 4095, .image_conv = ICnIPMC_C0, - .has_stride = true, + .stride_align = 128, .regs = rzg3e_cru_regs, .irq_handler = rzg3e_cru_irq, .enable_interrupts = rzg3e_cru_enable_interrupts, @@ -405,6 +405,7 @@ static const struct rzg2l_cru_info rzg2l_cru_info = { .max_width = 2800, .max_height = 4095, .image_conv = ICnMC, + .stride_align = 1, .regs = rzg2l_cru_regs, .irq_handler = rzg2l_cru_irq, .enable_interrupts = rzg2l_cru_enable_interrupts, diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h index b426bc7898bf..2c192d370dcb 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h @@ -75,7 +75,7 @@ struct rzg2l_cru_info { unsigned int max_height; u16 image_conv; const u16 *regs; - bool has_stride; + u8 stride_align; irqreturn_t (*irq_handler)(int irq, void *data); void (*enable_interrupts)(struct rzg2l_cru_dev *cru); void (*disable_interrupts)(struct rzg2l_cru_dev *cru); diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c index 27a35ef2a6df..a7b6dce66570 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -32,7 +32,6 @@ #define RZG2L_CRU_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB #define RZG2L_CRU_STRIDE_MAX 32640 -#define RZG2L_CRU_STRIDE_ALIGN 128 struct rzg2l_cru_buffer { struct vb2_v4l2_buffer vb; @@ -277,11 +276,11 @@ static void rzg2l_cru_initialize_axi(struct rzg2l_cru_dev *cru) rzg2l_cru_fill_hw_slot(cru, cru->num_buf - 1); } - if (info->has_stride) { + if (info->stride_align > 1) { u32 stride = cru->format.bytesperline; u32 amnis; - stride /= RZG2L_CRU_STRIDE_ALIGN; + stride /= info->stride_align; amnis = rzg2l_cru_read(cru, AMnIS) & ~AMnIS_IS_MASK; rzg2l_cru_write(cru, AMnIS, amnis | AMnIS_IS(stride)); } @@ -850,7 +849,7 @@ static void rzg2l_cru_format_align(struct rzg2l_cru_dev *cru, &pix->height, 240, info->max_height, 2, 0); v4l2_fill_pixfmt_aligned(pix, pix->pixelformat, pix->width, pix->height, - info->has_stride ? RZG2L_CRU_STRIDE_ALIGN : 1); + info->stride_align); dev_dbg(cru->dev, "Format %ux%u bpl: %u size: %u\n", pix->width, pix->height, pix->bytesperline, pix->sizeimage); From 6ab5acfb8dd387297fc907c5411ebba1b6fc2efa Mon Sep 17 00:00:00 2001 From: Kieran Bingham Date: Tue, 4 Aug 2026 20:43:28 +0100 Subject: [PATCH 041/596] dt-bindings: media: Add macros for video interface devices Add a new dt-bindings/media/video-interface-devices.h header that defines macros corresponding to the orientation enumeration types from media/video-interface-devices.yaml. Expand the documentation of the video-interface-devices orientation to reference the new header which provides human readable defines for the orientation enum, to help avoid hardcoding values in dts. Reviewed-by: Laurent Pinchart Acked-by: Conor Dooley Acked-by: Sakari Ailus Reviewed-by: Vladimir Zapolskiy Reviewed-by: Frank Li Acked-by: Michael Riesch Reviewed-by: David Heidelberg Signed-off-by: Kieran Bingham Signed-off-by: Sakari Ailus --- .../bindings/media/video-interface-devices.yaml | 17 +++++++++++------ .../dt-bindings/media/video-interface-devices.h | 13 +++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 include/dt-bindings/media/video-interface-devices.h diff --git a/Documentation/devicetree/bindings/media/video-interface-devices.yaml b/Documentation/devicetree/bindings/media/video-interface-devices.yaml index a81d2a155fe6..c9c3f4f16719 100644 --- a/Documentation/devicetree/bindings/media/video-interface-devices.yaml +++ b/Documentation/devicetree/bindings/media/video-interface-devices.yaml @@ -392,17 +392,22 @@ properties: The orientation of a device (typically an image sensor or a flash LED) describing its mounting position relative to the usage orientation of the system where the device is installed on. + See include/dt-bindings/media/video-interface-devices.h. + $ref: /schemas/types.yaml#/definitions/uint32 enum: - # Front. The device is mounted on the front facing side of the system. For - # mobile devices such as smartphones, tablets and laptops the front side - # is the user facing side. + # MEDIA_ORIENTATION_FRONT + # The device is mounted on the front facing side of the system. For + # mobile devices such as smartphones, tablets and laptops the front + # side is the user facing side. - 0 - # Back. The device is mounted on the back side of the system, which is + # MEDIA_ORIENTATION_BACK + # The device is mounted on the back side of the system, which is # defined as the opposite side of the front facing one. - 1 - # External. The device is not attached directly to the system but is - # attached in a way that allows it to move freely. + # MEDIA_ORIENTATION_EXTERNAL + # The device is not attached directly to the system but is attached in + # a way that allows it to move freely. - 2 additionalProperties: true diff --git a/include/dt-bindings/media/video-interface-devices.h b/include/dt-bindings/media/video-interface-devices.h new file mode 100644 index 000000000000..d2340b457292 --- /dev/null +++ b/include/dt-bindings/media/video-interface-devices.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2026 Kieran Bingham + */ + +#ifndef __DT_BINDINGS_MEDIA_VIDEO_INTERFACE_DEVICES_H__ +#define __DT_BINDINGS_MEDIA_VIDEO_INTERFACE_DEVICES_H__ + +#define MEDIA_ORIENTATION_FRONT 0 +#define MEDIA_ORIENTATION_BACK 1 +#define MEDIA_ORIENTATION_EXTERNAL 2 + +#endif /* __DT_BINDINGS_MEDIA_VIDEO_INTERFACE_DEVICES_H__ */ From 0cf9df256e97bb6ea1e936a7399be1675c7f564a Mon Sep 17 00:00:00 2001 From: Kieran Bingham Date: Tue, 4 Aug 2026 20:43:29 +0100 Subject: [PATCH 042/596] dt-bindings: media: i2c: Utilise video-interface-devices enums The orientation property for video interface devices now has definitions to prevent hardcoded integer values for the enum options. Update the existing examples throughout the bindings documentation for camera sensors. Reviewed-by: Vladimir Zapolskiy Reviewed-by: Laurent Pinchart Reviewed-by: Benjamin Mugnier Reviewed-by: David Heidelberg Signed-off-by: Kieran Bingham Signed-off-by: Sakari Ailus --- Documentation/devicetree/bindings/media/i2c/hynix,hi846.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/ovti,ov08d10.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/ovti,ov5675.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/ovti,ov64a40.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/sony,imx111.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/st,vd56g3.yaml | 3 ++- Documentation/devicetree/bindings/media/i2c/thine,thp7312.yaml | 3 ++- 12 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Documentation/devicetree/bindings/media/i2c/hynix,hi846.yaml b/Documentation/devicetree/bindings/media/i2c/hynix,hi846.yaml index 1a57f2aa1982..b7bc6ba26e6e 100644 --- a/Documentation/devicetree/bindings/media/i2c/hynix,hi846.yaml +++ b/Documentation/devicetree/bindings/media/i2c/hynix,hi846.yaml @@ -86,6 +86,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -102,7 +103,7 @@ examples: vddio-supply = <®_camera_vddio>; reset-gpios = <&gpio1 25 GPIO_ACTIVE_LOW>; shutdown-gpios = <&gpio5 4 GPIO_ACTIVE_LOW>; - orientation = <0>; + orientation = ; rotation = <0>; port { diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov08d10.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov08d10.yaml index 6f2017c75125..b9c61395b24f 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov08d10.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov08d10.yaml @@ -69,6 +69,7 @@ examples: - | #include #include + #include i2c { #address-cells = <1>; @@ -84,7 +85,7 @@ examples: avdd-supply = <&ov08d10_vdda_2v8>; dvdd-supply = <&ov08d10_vddd_1v2>; - orientation = <2>; + orientation = ; rotation = <0>; reset-gpios = <&gpio 1 GPIO_ACTIVE_LOW>; diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml index d96199031b66..fcd617848ce3 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml @@ -96,6 +96,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -114,7 +115,7 @@ examples: powerdown-gpios = <&pio 107 GPIO_ACTIVE_LOW>; reset-gpios = <&pio 109 GPIO_ACTIVE_LOW>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov5675.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov5675.yaml index ad07204057f9..6df62fd0c0c0 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov5675.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov5675.yaml @@ -85,6 +85,7 @@ examples: - | #include #include + #include #include i2c { @@ -108,7 +109,7 @@ examples: dovdd-supply = <&vcc_2v8>; rotation = <90>; - orientation = <0>; + orientation = ; port { ucam_out: endpoint { diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml index 3368b3bd8ef2..5732657e1484 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml @@ -103,6 +103,7 @@ examples: - | #include #include + #include #include i2c { @@ -126,7 +127,7 @@ examples: dovdd-supply = <&vcc_2v8>; rotation = <90>; - orientation = <0>; + orientation = ; port { ucam_out: endpoint { diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov64a40.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov64a40.yaml index 2b6143aff391..24787c9aa155 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov64a40.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov64a40.yaml @@ -72,6 +72,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -87,7 +88,7 @@ examples: powerdown-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>; reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; rotation = <180>; - orientation = <2>; + orientation = ; port { endpoint { diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx111.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx111.yaml index 20f48d5e9b2d..56fb5f18f07b 100644 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx111.yaml +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx111.yaml @@ -69,6 +69,7 @@ examples: - | #include #include + #include i2c { #address-cells = <1>; @@ -84,7 +85,7 @@ examples: dvdd-supply = <&camera_vddd_1v2>; avdd-supply = <&camera_vdda_2v7>; - orientation = <1>; + orientation = ; rotation = <90>; nvmem = <&eeprom>; diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml index d9cdfda699bf..6699c238ee0e 100644 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx355.yaml @@ -81,6 +81,7 @@ examples: - | #include #include + #include i2c { #address-cells = <1>; @@ -105,7 +106,7 @@ examples: pinctrl-0 = <&cam_front_default>; rotation = <270>; - orientation = <0>; + orientation = ; port { cam_front_endpoint: endpoint { diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml index 7c11e871dca6..69a37ff68db3 100644 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml @@ -86,6 +86,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -98,7 +99,7 @@ examples: clocks = <&clock_cam>; dvdd-supply = <&vcc1v1_cam>; lens-focus = <&vcm>; - orientation = <2>; + orientation = ; ovdd-supply = <&vcc1v8_cam>; reset-gpios = <&gpio_expander 14 GPIO_ACTIVE_LOW>; rotation = <180>; diff --git a/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml b/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml index 58b1f9e85a9d..dfd87beba065 100644 --- a/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml +++ b/Documentation/devicetree/bindings/media/i2c/st,vd55g1.yaml @@ -106,6 +106,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -124,7 +125,7 @@ examples: reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>; st,leds = <2>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/Documentation/devicetree/bindings/media/i2c/st,vd56g3.yaml b/Documentation/devicetree/bindings/media/i2c/st,vd56g3.yaml index c6673b8539db..48db22ca4a7e 100644 --- a/Documentation/devicetree/bindings/media/i2c/st,vd56g3.yaml +++ b/Documentation/devicetree/bindings/media/i2c/st,vd56g3.yaml @@ -107,6 +107,7 @@ unevaluatedProperties: false examples: - | #include + #include i2c { #address-cells = <1>; @@ -125,7 +126,7 @@ examples: reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>; st,leds = <6>; - orientation = <2>; + orientation = ; rotation = <0>; port { diff --git a/Documentation/devicetree/bindings/media/i2c/thine,thp7312.yaml b/Documentation/devicetree/bindings/media/i2c/thine,thp7312.yaml index bc339a7374b2..4a66cb711372 100644 --- a/Documentation/devicetree/bindings/media/i2c/thine,thp7312.yaml +++ b/Documentation/devicetree/bindings/media/i2c/thine,thp7312.yaml @@ -173,6 +173,7 @@ examples: - | #include #include + #include i2c { #address-cells = <1>; @@ -196,7 +197,7 @@ examples: vddgpio-0-supply = <&vsys_v4p2>; vddgpio-1-supply = <&vsys_v4p2>; - orientation = <0>; + orientation = ; rotation = <0>; sensors { From 7456b40bc992a3fc34b587545716729923d507b9 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 3 Sep 2026 13:07:53 +0300 Subject: [PATCH 043/596] media: i2c: cvs: Add sensor node in bound callback Sensor device nodes are created in the complete callback, which is only called when all devices are successfully registered. If any device fails to register, e.g. due to a missing driver, the complete callback is never invoked and sensor nodes are not created. Create sensor device nodes in the bound callback instead, ensuring they are available as soon as sensor is bound regardless of other devices registration status. Fixes: 8e2b43d2c10b ("media: i2c: cvs: Add driver of Intel Computer Vision Sensing Controller(CVS)") Cc: stable@vger.kernel.org Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/i2c/cvs/v4l2.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c index 9fadca7a3bee..863112cde266 100644 --- a/drivers/media/i2c/cvs/v4l2.c +++ b/drivers/media/i2c/cvs/v4l2.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -400,7 +401,7 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, struct v4l2_async_connection *asc) { struct icvs *ctx = notifier_to_csi(notifier); - int pad; + int pad, ret; pad = media_entity_get_fwnode_pad(&sd->entity, asc->match.fwnode, MEDIA_PAD_FL_SOURCE); @@ -409,9 +410,13 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, ctx->remote = &sd->entity.pads[pad]; - return media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, - ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | - MEDIA_LNK_FL_IMMUTABLE); + ret = media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, + ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) + return ret; + + return v4l2_device_register_subdev_nodes(sd->v4l2_dev); } /** From ce92674d8d8b29562de397b437e16bee499dd92b Mon Sep 17 00:00:00 2001 From: Sergey Zagursky Date: Wed, 2 Sep 2026 22:15:24 +0100 Subject: [PATCH 044/596] media: ipu-bridge: do not use the CVS device lookup for IVSC Since commit c6b1b34b5090 ("media: pci: intel: Add CVS support for IPU bridge driver") the internal camera no longer works on laptops where the sensor sits behind an IVSC, for example a Dell XPS 16 9640 (IPU6, INTC10CF, ov02c10): intel-ipu6 0000:00:05.0: Found supported sensor OVTI02C1:00 intel-ipu6 0000:00:05.0: Connected 1 cameras ivsc_csi intel_vsc-92335fcf-3203-4472-af93-7b4453ac29da: mei-csi probed without device fwnode! No sensor subdevice is registered, the media graph has no sensor entity and userspace finds no camera at all. ipu_bridge_get_ivsc_csi_dev() first looks for the platform device named "intel_vsc" and returns its mei-csi child. That device is created by mei_vsc, which on this machine only appears once the LJCA USB bridge and its SPI controller have probed, about a second after the IPU6 probe that runs the bridge: 07:59:29.297 platform INTC10CF:00 created (ACPI scan) 07:59:41 intel-ipu6 probe -> ipu_bridge_init() 07:59:42.391 platform intel_vsc created (mei_vsc) The commit above added two fallbacks for CVS which match on the ACPI companion alone. They are reached for every entry of ivsc_acpi_ids[], IVSC IDs included. The IVSC ACPI device has two physical nodes: INTC10CF:00/physical_node -> platform/INTC10CF:00 (no driver bound) INTC10CF:00/physical_node1 -> platform/intel_vsc (mei_vsc) so bus_find_device_by_acpi_dev(&platform_bus_type, adev) returns the bare platform device. ipu_bridge_instantiate_ivsc() then attaches the IVSC software node to that device instead of to the mei-csi client, the bridge reports success, and the probe is never retried. mei_csi later probes without a fwnode, the CSI-2 link is never described, and the sensor ACPI device, which has an honoured _DEP on the IVSC device, is never enumerated. Before those fallbacks existed the lookup returned NULL here, the bridge failed with -ENODEV and the probe was retried once the IVSC device had shown up. Skip those fallbacks for IVSC devices, keying on the IVSC IDs rather than the CVS ones: new CVS IDs keep being added, whereas the IVSC list is complete. CVS binds a driver to the ACPI device itself, so matching on the companion stays unambiguous there. Fixes: c6b1b34b5090 ("media: pci: intel: Add CVS support for IPU bridge driver") Link: https://lore.kernel.org/linux-media/20260901194526.6369-1-gvozdoder@gmail.com/ Cc: stable@vger.kernel.org Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Sergey Zagursky Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 125fe5da7a60..883a90249602 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -232,6 +232,19 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { { "INTC10FA" }, /* NVL */ }; +/* + * The subset of ivsc_acpi_ids[] which are IVSC, rather than CVS, devices. The + * CVS IDs are deliberately not listed here: new ones keep being added, whereas + * this list is complete. + */ +static const struct acpi_device_id ivsc_only_acpi_ids[] = { + { "INTC1059" }, + { "INTC1095" }, + { "INTC100A" }, + { "INTC10CF" }, + { } +}; + static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev) { unsigned int i; @@ -283,6 +296,17 @@ static struct device *ipu_bridge_get_ivsc_csi_dev(struct acpi_device *adev) return csi_dev; } + /* + * The lookups below match on the ACPI companion alone. That is fine for + * CVS, which binds a driver to that very device, but not for IVSC: there + * the ACPI device also has a driverless platform device, which would be + * returned instead of the mei-csi client. Return NULL for IVSC so that + * the caller fails and the probe is retried once the IVSC device shows + * up. + */ + if (!acpi_match_device_ids(adev, ivsc_only_acpi_ids)) + return NULL; + /* Try to locate CVS device on the I2C bus */ csi_dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev); if (csi_dev) From 609a71568664acb7ee57b42de002ca782e3769e8 Mon Sep 17 00:00:00 2001 From: Serin Yeh Date: Thu, 6 Aug 2026 14:18:04 +0800 Subject: [PATCH 045/596] media: i2c: ov8856 : remove ACPI node bypass mechanism On Intel platforms the sensor is enumerated via ACPI, and an INT3472 "discrete" companion device (the power-logic-control driver under drivers/platform/x86/intel/int3472) registers the sensor's reset/powerdown GPIOs and the regulator supply. These resources are therefore discoverable and usable through the standard gpiod/regulator lookups at probe time. However, the driver guarded both the GPIO/regulator acquisition in ov8856_get_hwcfg() and the whole power-on/off sequence with is_acpi_node() and skipped them on ACPI. As a result the sensor was never powered or reset by the driver, and probe failed when reading the chip ID in ov8856_identify_module(). Remove the is_acpi_node() checks so the GPIO/regulator resources are acquired and the full power sequence runs regardless of the firmware interface. The driver now behaves consistently across DT and ACPI platforms and initialises the hardware correctly. Tested on Intel platforms with kernel 6.17 and 7.0. Fixes: d2fa1134a48b ("media: i2c: ov8856: Use V4L2 legacy sensor clock helper") Cc: stable@vger.kernel.org Signed-off-by: Serin Yeh Reviewed-by: Bryan O'Donoghue Reviewed-by: Kieran Bingham Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov8856.c | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/drivers/media/i2c/ov8856.c b/drivers/media/i2c/ov8856.c index 8bedb47cd7cf..9d2b0469a576 100644 --- a/drivers/media/i2c/ov8856.c +++ b/drivers/media/i2c/ov8856.c @@ -2082,9 +2082,6 @@ static int ov8856_power_on(struct device *dev) struct ov8856 *ov8856 = to_ov8856(sd); int ret; - if (is_acpi_node(dev_fwnode(dev))) - return 0; - ret = clk_prepare_enable(ov8856->xvclk); if (ret < 0) { dev_err(dev, "failed to enable xvclk\n"); @@ -2120,9 +2117,6 @@ static int ov8856_power_off(struct device *dev) struct v4l2_subdev *sd = dev_get_drvdata(dev); struct ov8856 *ov8856 = to_ov8856(sd); - if (is_acpi_node(dev_fwnode(dev))) - return 0; - gpiod_set_value_cansleep(ov8856->reset_gpio, 1); regulator_bulk_disable(ARRAY_SIZE(ov8856_supply_names), ov8856->supplies); @@ -2293,21 +2287,18 @@ static int ov8856_get_hwcfg(struct ov8856 *ov8856) dev_warn(dev, "external clock rate %u is unsupported", xvclk_rate); - if (!is_acpi_node(fwnode)) { - ov8856->reset_gpio = devm_gpiod_get_optional(dev, "reset", - GPIOD_OUT_LOW); - if (IS_ERR(ov8856->reset_gpio)) - return PTR_ERR(ov8856->reset_gpio); + ov8856->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ov8856->reset_gpio)) + return PTR_ERR(ov8856->reset_gpio); - for (i = 0; i < ARRAY_SIZE(ov8856_supply_names); i++) - ov8856->supplies[i].supply = ov8856_supply_names[i]; + for (i = 0; i < ARRAY_SIZE(ov8856_supply_names); i++) + ov8856->supplies[i].supply = ov8856_supply_names[i]; - ret = devm_regulator_bulk_get(dev, - ARRAY_SIZE(ov8856_supply_names), - ov8856->supplies); - if (ret) - return ret; - } + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ov8856_supply_names), + ov8856->supplies); + if (ret) + return ret; ep = fwnode_graph_get_next_endpoint(fwnode, NULL); if (!ep) From a15976e69b1c70ea16ef1f7172a846f6ef5dfa14 Mon Sep 17 00:00:00 2001 From: Serin Yeh Date: Thu, 6 Aug 2026 14:18:19 +0800 Subject: [PATCH 046/596] media: i2c: ov8856: fix debug message to report actual CSI-2 lane count The driver currently prints the number of data lanes using ov8856->cur_mode->data_lanes in ov8856_get_hwcfg(). However, the ov8856->cur_mode structure is not assigned to a clearly supported mode at that point. Due to the missing pointer, enabling dynamic debug may lead to a kernel panic. Instead, the correct number of CSI-2 data lanes should be taken from bus_cfg.bus.mipi_csi2.num_data_lanes, which represents the real hardware configuration determined at runtime. Fixes: c492ec9ae9ed ("media: ov8856: Add support for 2 data lanes") Cc: stable@vger.kernel.org Signed-off-by: Serin Yeh Reviewed-by: Kieran Bingham Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov8856.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov8856.c b/drivers/media/i2c/ov8856.c index 9d2b0469a576..6011233a218d 100644 --- a/drivers/media/i2c/ov8856.c +++ b/drivers/media/i2c/ov8856.c @@ -2318,7 +2318,8 @@ static int ov8856_get_hwcfg(struct ov8856 *ov8856) goto check_hwcfg_error; } - dev_dbg(dev, "Using %u data lanes\n", ov8856->cur_mode->data_lanes); + dev_dbg(dev, "Using %u data lanes\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); if (bus_cfg.bus.mipi_csi2.num_data_lanes == 2) ov8856->priv_lane = &lane_cfg_2; From f9536a8065d9eed0d9114878c56ffc4e5a18bdfc Mon Sep 17 00:00:00 2001 From: Serin Yeh Date: Fri, 24 Apr 2026 10:19:13 +0800 Subject: [PATCH 047/596] media: ipu-bridge: Add support additional link frequency Support additional frequency 900 MHz for the OV05C10 sensor Signed-off-by: Serin Yeh Reviewed-by: Hans de Goede Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 883a90249602..7e65491faf6d 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -83,7 +83,7 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { /* Omnivision OV02E10 */ IPU_SENSOR_CONFIG("OVTI02E1", 1, 360000000), /* Omnivision ov05c10 */ - IPU_SENSOR_CONFIG("OVTI05C1", 1, 480000000), + IPU_SENSOR_CONFIG("OVTI05C1", 2, 480000000, 900000000), /* Omnivision OV08A10 */ IPU_SENSOR_CONFIG("OVTI08A1", 1, 500000000), /* Omnivision OV08x40 */ From 13b108d7559cb625fff42f4db298e8b58cea60d9 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:32:53 +0300 Subject: [PATCH 048/596] media: ipu6: Add helpers for IPU runtime variation And replace inline functions with defines. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-isys.c | 4 +-- drivers/media/pci/intel/ipu6/ipu6.c | 29 +++++++++-------- drivers/media/pci/intel/ipu6/ipu6.h | 40 +++++------------------- 3 files changed, 24 insertions(+), 49 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 24db2763de54..799e7b30e6a2 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -1104,9 +1104,9 @@ static int isys_probe(struct auxiliary_device *auxdev, isys_iwake_watermark_init(isys); - if (is_ipu6se(adev->isp->hw_ver)) + if (IS_IPU6SE(adev->isp)) isys->phy_set_power = ipu6_isys_jsl_phy_set_power; - else if (is_ipu6ep_mtl(adev->isp->hw_ver)) + else if (IS_IPU6EP_MTL(adev->isp)) isys->phy_set_power = ipu6_isys_dwc_phy_set_power; else isys->phy_set_power = ipu6_isys_mcd_phy_set_power; diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 5449a2006bcc..6d3235352a1c 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -290,8 +290,6 @@ EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, "INTEL_IPU6"); static void ipu6_internal_pdata_init(struct ipu6_device *isp) { - u8 hw_ver = isp->hw_ver; - isys_ipdata.num_parallel_streams = IPU6_ISYS_NUM_STREAMS; isys_ipdata.sram_gran_shift = IPU6_SRAM_GRANULARITY_SHIFT; isys_ipdata.sram_gran_size = IPU6_SRAM_GRANULARITY_SIZE; @@ -314,19 +312,19 @@ static void ipu6_internal_pdata_init(struct ipu6_device *isp) IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS; isys_ipdata.csi2.ctrl0_irq_lnp = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE; - isys_ipdata.enhanced_iwake = is_ipu6ep_mtl(hw_ver) || is_ipu6ep(hw_ver); + isys_ipdata.enhanced_iwake = IS_IPU6EP_MTL(isp) || IS_IPU6EP(isp); psys_ipdata.hw_variant.spc_offset = IPU6_PSYS_SPC_OFFSET; isys_ipdata.csi2.fw_access_port_ofs = CSI_REG_HUB_FW_ACCESS_PORT_OFS; - if (is_ipu6ep(hw_ver)) { + if (IS_IPU6EP(isp)) { isys_ipdata.ltr = IPU6EP_LTR_VALUE; isys_ipdata.memopen_threshold = IPU6EP_MIN_MEMOPEN_TH; } - if (is_ipu6_tgl(hw_ver)) + if (IS_IPU6_TGL(isp)) isys_ipdata.csi2.nports = IPU6_TGL_ISYS_CSI2_NPORTS; - if (is_ipu6ep_mtl(hw_ver)) { + if (IS_IPU6EP_MTL(isp)) { isys_ipdata.csi2.nports = IPU6EP_MTL_ISYS_CSI2_NPORTS; isys_ipdata.csi2.ctrl0_irq_edge = @@ -347,7 +345,7 @@ static void ipu6_internal_pdata_init(struct ipu6_device *isp) isys_ipdata.memopen_threshold = IPU6EP_MTL_MIN_MEMOPEN_TH; } - if (is_ipu6se(hw_ver)) { + if (IS_IPU6SE(isp)) { isys_ipdata.csi2.nports = IPU6SE_ISYS_CSI2_NPORTS; isys_ipdata.csi2.irq_mask = IPU6SE_CSI_RX_ERROR_IRQ_MASK; isys_ipdata.num_parallel_streams = IPU6SE_ISYS_NUM_STREAMS; @@ -454,12 +452,13 @@ ipu6_psys_init(struct pci_dev *pdev, struct device *parent, return psys_adev; } -static int ipu6_pci_config_setup(struct pci_dev *dev, u8 hw_ver) +static int ipu6_pci_config_setup(struct pci_dev *dev) { + struct ipu6_device *isp = pci_get_drvdata(dev); int ret; /* No PCI msi capability for IPU6EP */ - if (is_ipu6ep(hw_ver) || is_ipu6ep_mtl(hw_ver)) { + if (IS_IPU6EP(isp) || IS_IPU6EP_MTL(isp)) { /* likely do nothing as msi not enabled by default */ pci_disable_msi(dev); return 0; @@ -525,26 +524,26 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt); switch (id->device) { case PCI_DEVICE_ID_INTEL_IPU6: - isp->hw_ver = IPU6_VER_6; + isp->hw_ver = IPU_VERSION_6; isp->cpd_fw_name = IPU6_FIRMWARE_NAME; break; case PCI_DEVICE_ID_INTEL_IPU6SE: - isp->hw_ver = IPU6_VER_6SE; + isp->hw_ver = IPU_VERSION_6SE; isp->cpd_fw_name = IPU6SE_FIRMWARE_NAME; isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6se_cpd_metadata_cmpnt); break; case PCI_DEVICE_ID_INTEL_IPU6EP_ADLP: case PCI_DEVICE_ID_INTEL_IPU6EP_RPLP: - isp->hw_ver = IPU6_VER_6EP; + isp->hw_ver = IPU_VERSION_6EP; isp->cpd_fw_name = IPU6EP_FIRMWARE_NAME; break; case PCI_DEVICE_ID_INTEL_IPU6EP_ADLN: - isp->hw_ver = IPU6_VER_6EP; + isp->hw_ver = IPU_VERSION_6EP; isp->cpd_fw_name = IPU6EPADLN_FIRMWARE_NAME; break; case PCI_DEVICE_ID_INTEL_IPU6EP_MTL: - isp->hw_ver = IPU6_VER_6EP_MTL; + isp->hw_ver = IPU_VERSION_6EP_MTL; isp->cpd_fw_name = IPU6EPMTL_FIRMWARE_NAME; break; default: @@ -564,7 +563,7 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) dma_set_max_seg_size(dev, UINT_MAX); - ret = ipu6_pci_config_setup(pdev, isp->hw_ver); + ret = ipu6_pci_config_setup(pdev); if (ret) return ret; diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index 92e3c3414c91..07ca59048f44 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -23,39 +23,15 @@ struct ipu6_bus_device; #define IPU6EPMTL_FIRMWARE_NAME "intel/ipu/ipu6epmtl_fw.bin" #define IPU6EPADLN_FIRMWARE_NAME "intel/ipu/ipu6epadln_fw.bin" -enum ipu6_version { - IPU6_VER_INVALID = 0, - IPU6_VER_6 = 1, - IPU6_VER_6SE = 3, - IPU6_VER_6EP = 5, - IPU6_VER_6EP_MTL = 6, -}; +#define IPU_VERSION_6 BIT(0) /* TGL */ +#define IPU_VERSION_6SE BIT(1) /* JSL */ +#define IPU_VERSION_6EP BIT(2) /* ADL/RPL */ +#define IPU_VERSION_6EP_MTL BIT(3) /* MTL */ -/* - * IPU6 - TGL - * IPU6SE - JSL - * IPU6EP - ADL/RPL - * IPU6EP_MTL - MTL - */ -static inline bool is_ipu6se(u8 hw_ver) -{ - return hw_ver == IPU6_VER_6SE; -} - -static inline bool is_ipu6ep(u8 hw_ver) -{ - return hw_ver == IPU6_VER_6EP; -} - -static inline bool is_ipu6ep_mtl(u8 hw_ver) -{ - return hw_ver == IPU6_VER_6EP_MTL; -} - -static inline bool is_ipu6_tgl(u8 hw_ver) -{ - return hw_ver == IPU6_VER_6; -} +#define IS_IPU6_TGL(isp) ((isp)->hw_ver & IPU_VERSION_6) +#define IS_IPU6SE(isp) ((isp)->hw_ver & IPU_VERSION_6SE) +#define IS_IPU6EP(isp) ((isp)->hw_ver & IPU_VERSION_6EP) +#define IS_IPU6EP_MTL(isp) ((isp)->hw_ver & IPU_VERSION_6EP_MTL) /* * ISYS DMA can overshoot. For higher resolutions over allocation is one line From ac1a76fdf7e1815445c76d90ef688eff85cbb012 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:32:54 +0300 Subject: [PATCH 049/596] media: ipu6: Rename pointer to firmware context The name fwcom is used to refer firmware communication context and firmware communication configuration. Try to avoid confusion and rename context variable. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 56 ++++++++++--------- .../media/pci/intel/ipu6/ipu6-isys-video.c | 4 +- drivers/media/pci/intel/ipu6/ipu6-isys.c | 6 +- drivers/media/pci/intel/ipu6/ipu6-isys.h | 5 +- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c index 62ed92ff1d30..a65e9f1aa104 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c @@ -32,7 +32,7 @@ static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id) struct ipu6_fw_isys_proxy_resp_info_abi *resp; int ret; - resp = ipu6_recv_get_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); + resp = ipu6_recv_get_token(isys->fwctx, IPU6_BASE_PROXY_RECV_QUEUES); if (!resp) return 1; @@ -42,7 +42,7 @@ static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id) ret = req_id == resp->request_id ? 0 : -EIO; - ipu6_recv_put_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); + ipu6_recv_put_token(isys->fwctx, IPU6_BASE_PROXY_RECV_QUEUES); return ret; } @@ -52,7 +52,7 @@ int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, unsigned int index, unsigned int offset, u32 value) { - struct ipu6_fw_com_context *ctx = isys->fwcom; + struct ipu6_fw_com_context *ctx = isys->fwctx; struct device *dev = &isys->adev->auxdev.dev; struct ipu6_fw_proxy_send_queue_token *token; unsigned int timeout = 1000; @@ -96,7 +96,7 @@ int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, dma_addr_t dma_mapped_buf, size_t size, u16 send_type) { - struct ipu6_fw_com_context *ctx = isys->fwcom; + struct ipu6_fw_com_context *ctx = isys->fwctx; struct device *dev = &isys->adev->auxdev.dev; struct ipu6_fw_send_queue_token *token; @@ -138,7 +138,7 @@ int ipu6_fw_isys_close(struct ipu6_isys *isys) struct device *dev = &isys->adev->auxdev.dev; int retry = IPU6_ISYS_CLOSE_RETRY; unsigned long flags; - void *fwcom; + void *fwctx; int ret; /* @@ -148,9 +148,9 @@ int ipu6_fw_isys_close(struct ipu6_isys *isys) * spinlock to wait the interrupt handler to be finished */ spin_lock_irqsave(&isys->power_lock, flags); - ret = ipu6_fw_com_close(isys->fwcom); - fwcom = isys->fwcom; - isys->fwcom = NULL; + ret = ipu6_fw_com_close(isys->fwctx); + fwctx = isys->fwctx; + isys->fwctx = NULL; spin_unlock_irqrestore(&isys->power_lock, flags); if (ret) dev_err(dev, "Device close failure: %d\n", ret); @@ -158,14 +158,14 @@ int ipu6_fw_isys_close(struct ipu6_isys *isys) /* release probably fails if the close failed. Let's try still */ do { usleep_range(400, 500); - ret = ipu6_fw_com_release(fwcom, 0); + ret = ipu6_fw_com_release(fwctx, 0); retry--; } while (ret && retry); if (ret) { dev_err(dev, "Device release time out %d\n", ret); spin_lock_irqsave(&isys->power_lock, flags); - isys->fwcom = fwcom; + isys->fwctx = fwctx; spin_unlock_irqrestore(&isys->power_lock, flags); } @@ -176,11 +176,11 @@ void ipu6_fw_isys_cleanup(struct ipu6_isys *isys) { int ret; - ret = ipu6_fw_com_release(isys->fwcom, 1); + ret = ipu6_fw_com_release(isys->fwctx, 1); if (ret < 0) dev_warn(&isys->adev->auxdev.dev, "Device busy, fw_com release failed."); - isys->fwcom = NULL; + isys->fwctx = NULL; } static void start_sp(struct ipu6_bus_device *adev) @@ -212,7 +212,7 @@ static int query_sp(struct ipu6_bus_device *adev) } static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, - struct ipu6_fw_com_cfg *fwcom, + struct ipu6_fw_com_cfg *fwcom_cfg, unsigned int num_streams) { unsigned int max_send_queues, max_sram_blocks, max_devq_size; @@ -258,14 +258,16 @@ static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, if (!output_queue_cfg) return -ENOMEM; - fwcom->input = input_queue_cfg; - fwcom->output = output_queue_cfg; + fwcom_cfg->input = input_queue_cfg; + fwcom_cfg->output = output_queue_cfg; - fwcom->num_input_queues = isys_fw_cfg->num_send_queues[type_proxy] + + fwcom_cfg->num_input_queues = + isys_fw_cfg->num_send_queues[type_proxy] + isys_fw_cfg->num_send_queues[type_dev] + isys_fw_cfg->num_send_queues[type_msg]; - fwcom->num_output_queues = isys_fw_cfg->num_recv_queues[type_proxy] + + fwcom_cfg->num_output_queues = + isys_fw_cfg->num_recv_queues[type_proxy] + isys_fw_cfg->num_recv_queues[type_dev] + isys_fw_cfg->num_recv_queues[type_msg]; @@ -280,7 +282,7 @@ static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, isys_fw_cfg->buffer_partition.num_gda_pages[i] = 0; } - /* FW assumes proxy interface at fwcom queue 0 */ + /* FW assumes proxy interface at fwcom_cfg queue 0 */ for (i = 0; i < isys_fw_cfg->num_send_queues[type_proxy]; i++) { input_queue_cfg[i].token_size = sizeof(struct ipu6_fw_proxy_send_queue_token); @@ -314,9 +316,9 @@ static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, IPU6_ISYS_SIZE_RECV_QUEUE; } - fwcom->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset; - fwcom->specific_addr = isys_fw_cfg; - fwcom->specific_size = sizeof(*isys_fw_cfg); + fwcom_cfg->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset; + fwcom_cfg->specific_addr = isys_fw_cfg; + fwcom_cfg->specific_size = sizeof(*isys_fw_cfg); return 0; } @@ -325,23 +327,23 @@ int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) { struct device *dev = &isys->adev->auxdev.dev; int retry = IPU6_ISYS_OPEN_RETRY; - struct ipu6_fw_com_cfg fwcom = { + struct ipu6_fw_com_cfg fwcom_cfg = { .cell_start = start_sp, .cell_ready = query_sp, .buttress_boot_offset = SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET, }; int ret; - ipu6_isys_fwcom_cfg_init(isys, &fwcom, num_streams); + ipu6_isys_fwcom_cfg_init(isys, &fwcom_cfg, num_streams); - isys->fwcom = ipu6_fw_com_prepare(&fwcom, isys->adev, + isys->fwctx = ipu6_fw_com_prepare(&fwcom_cfg, isys->adev, isys->pdata->base); - if (!isys->fwcom) { + if (!isys->fwctx) { dev_err(dev, "isys fw com prepare failed\n"); return -EIO; } - ret = ipu6_fw_com_open(isys->fwcom); + ret = ipu6_fw_com_open(isys->fwctx); if (ret) { dev_err(dev, "isys fw com open failed %d\n", ret); return ret; @@ -349,7 +351,7 @@ int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) do { usleep_range(400, 500); - if (ipu6_fw_com_ready(isys->fwcom)) + if (ipu6_fw_com_ready(isys->fwctx)) break; retry--; } while (retry > 0); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index 3ac48d2076da..89eb265737d7 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -1114,7 +1114,7 @@ int ipu6_isys_fw_open(struct ipu6_isys *isys) */ ipu6_cleanup_fw_msg_bufs(isys); - if (isys->fwcom) { + if (isys->fwctx) { /* * Something went wrong in previous shutdown. As we are now * restarting isys we can safely delete old context. @@ -1147,7 +1147,7 @@ void ipu6_isys_fw_close(struct ipu6_isys *isys) isys->ref_count--; if (!isys->ref_count) { ipu6_fw_isys_close(isys); - if (isys->fwcom) { + if (isys->fwctx) { isys->need_reset = true; dev_warn(&isys->adev->auxdev.dev, "failed to close fw isys\n"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 799e7b30e6a2..180a53f3d7ab 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -1212,10 +1212,10 @@ static int isys_isr_one(struct ipu6_bus_device *adev) u32 index; u64 ts; - if (!isys->fwcom) + if (!isys->fwctx) return 1; - resp = ipu6_fw_isys_get_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES); + resp = ipu6_fw_isys_get_resp(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); if (!resp) return 1; @@ -1324,7 +1324,7 @@ static int isys_isr_one(struct ipu6_bus_device *adev) ipu6_isys_put_stream(stream); leave: - ipu6_fw_isys_put_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES); + ipu6_fw_isys_put_resp(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); return 0; } diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h index 7fb8cb820912..0b139ab24431 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -114,8 +114,7 @@ struct sensor_async_sd { * @csi2_rx_ctrl_cached: cached shared value between all CSI2 receivers * @streams_lock: serialise access to streams * @streams: streams per firmware stream ID - * @fwcom: fw communication layer private pointer - * or optional external library private pointer + * @fwctx: fw communication layer context pointer * @phy_termcal_val: the termination calibration value, only used for DWC PHY * @need_reset: Isys requires d0i0->i3 transition * @ref_count: total number of callers fw open @@ -136,7 +135,7 @@ struct ipu6_isys { spinlock_t streams_lock; struct ipu6_isys_stream streams[IPU6_ISYS_MAX_STREAMS]; int streams_ref_count[IPU6_ISYS_MAX_STREAMS]; - void *fwcom; + void *fwctx; u32 phy_termcal_val; bool need_reset; bool icache_prefetch; From 1176d29e7be41b5e3345b03e1d8ba03b485b24eb Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:32:55 +0300 Subject: [PATCH 050/596] media: ipu6: Rename buttress_ipc pointer The ipu6_buttress_ipc structure is called as cse sometimes, rename it to ipc for consistency. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-buttress.c | 31 ++++++++++---------- drivers/media/pci/intel/ipu6/ipu6-buttress.h | 2 +- drivers/media/pci/intel/ipu6/ipu6.c | 4 +-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c index e0ecb4c8b081..cbe356589a9d 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.c +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -221,7 +221,7 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, unsigned long tx_timeout_jiffies, rx_timeout_jiffies; unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY; struct ipu6_buttress *b = &isp->buttress; - struct ipu6_buttress_ipc *ipc = &b->cse; + struct ipu6_buttress_ipc *ipc = &b->ipc; u32 val; int ret; int tout; @@ -378,14 +378,15 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) if (irq_status & BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING) { dev_dbg(&isp->pdev->dev, "BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n"); - ipu6_buttress_ipc_recv(isp, &b->cse, &b->cse.recv_data); - complete(&b->cse.recv_complete); + + ipu6_buttress_ipc_recv(isp, &b->ipc, &b->ipc.recv_data); + complete(&b->ipc.recv_complete); } if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) { dev_dbg(&isp->pdev->dev, "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); - complete(&b->cse.send_complete); + complete(&b->ipc.send_complete); } if (irq_status & BUTTRESS_ISR_SAI_VIOLATION && @@ -830,17 +831,17 @@ int ipu6_buttress_init(struct ipu6_device *isp) mutex_init(&b->auth_mutex); mutex_init(&b->cons_mutex); mutex_init(&b->ipc_mutex); - init_completion(&b->cse.send_complete); - init_completion(&b->cse.recv_complete); + init_completion(&b->ipc.send_complete); + init_completion(&b->ipc.recv_complete); - b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; - b->cse.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK; - b->cse.csr_in = BUTTRESS_REG_CSE2IUCSR; - b->cse.csr_out = BUTTRESS_REG_IU2CSECSR; - b->cse.db0_in = BUTTRESS_REG_CSE2IUDB0; - b->cse.db0_out = BUTTRESS_REG_IU2CSEDB0; - b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0; - b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0; + b->ipc.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; + b->ipc.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK; + b->ipc.csr_in = BUTTRESS_REG_CSE2IUCSR; + b->ipc.csr_out = BUTTRESS_REG_IU2CSECSR; + b->ipc.db0_in = BUTTRESS_REG_CSE2IUDB0; + b->ipc.db0_out = BUTTRESS_REG_IU2CSEDB0; + b->ipc.data0_in = BUTTRESS_REG_CSE2IUDATA0; + b->ipc.data0_out = BUTTRESS_REG_IU2CSEDATA0; INIT_LIST_HEAD(&b->constraints); @@ -877,7 +878,7 @@ int ipu6_buttress_init(struct ipu6_device *isp) /* Retry couple of times in case of CSE initialization is delayed */ do { - ret = ipu6_buttress_ipc_reset(isp, &b->cse); + ret = ipu6_buttress_ipc_reset(isp, &b->ipc); if (ret) { dev_warn(&isp->pdev->dev, "IPC reset protocol failed, retrying\n"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h index 51e5ad48db82..b1c1a10e3e6d 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.h +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h @@ -44,7 +44,7 @@ struct ipu6_buttress_ipc { struct ipu6_buttress { struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex; - struct ipu6_buttress_ipc cse; + struct ipu6_buttress_ipc ipc; struct list_head constraints; u32 wdt_cached_value; bool force_suspend; diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 6d3235352a1c..efb58bc4d127 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -765,7 +765,7 @@ static int ipu6_resume(struct device *dev) ipu6_buttress_restore(isp); - ret = ipu6_buttress_ipc_reset(isp, &b->cse); + ret = ipu6_buttress_ipc_reset(isp, &b->ipc); if (ret) dev_err(&isp->pdev->dev, "IPC reset protocol failed!\n"); @@ -797,7 +797,7 @@ static int ipu6_runtime_resume(struct device *dev) struct ipu6_buttress *b = &isp->buttress; isp->need_ipc_reset = false; - ret = ipu6_buttress_ipc_reset(isp, &b->cse); + ret = ipu6_buttress_ipc_reset(isp, &b->ipc); if (ret) dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); } From 2b00dc416102d80bae83196e24d62e8125719676 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:32:56 +0300 Subject: [PATCH 051/596] media: ipu6: Remove duplicate warnings in cpd validation Some of the error paths printed same or similar errors twice, once is enough. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-cpd.c | 29 ++++++------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.c b/drivers/media/pci/intel/ipu6/ipu6-cpd.c index b7013f6524ec..966a16a300f8 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-cpd.c +++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.c @@ -266,7 +266,6 @@ static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp, u32 moduledata_size) { const struct ipu6_cpd_module_data_hdr *mod_hdr = moduledata; - int ret; /* Ensure moduledata hdr is within moduledata */ if (moduledata_size < sizeof(*mod_hdr) || @@ -276,15 +275,9 @@ static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp, } dev_dbg(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date); - ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len, - moduledata_size - mod_hdr->hdr_len, - moduledata_size); - if (ret) { - dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n"); - return ret; - } - - return 0; + return ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len, + moduledata_size - mod_hdr->hdr_len, + moduledata_size); } static int ipu6_cpd_validate_metadata(struct ipu6_device *isp, @@ -325,10 +318,8 @@ int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size, cpd_file_size); - if (ret) { - dev_err(&isp->pdev->dev, "Invalid CPD in file\n"); + if (ret) return ret; - } /* Check for CPD file marker */ if (hdr->hdr_mark != CPD_HDR_MARK) { @@ -346,17 +337,11 @@ int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, /* Validate metadata */ ent = ipu6_cpd_get_metadata(cpd_file); ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len); - if (ret) { - dev_err(&isp->pdev->dev, "Invalid CPD metadata\n"); + if (ret) return ret; - } /* Validate moduledata */ ent = ipu6_cpd_get_moduledata(cpd_file); - ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset, - ent->len); - if (ret) - dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n"); - - return ret; + return ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset, + ent->len); } From ef09c6c2c96beaf3f204c6da9f3c4e686c13cae4 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:32:57 +0300 Subject: [PATCH 052/596] media: ipu6: Remove unused ipu6 firmware struct The struct ipu6_fw_proxy_write_queue_token is not used anywhere, remove it. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h index b60f02076d8a..393245a633f4 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h @@ -511,21 +511,6 @@ struct ipu6_fw_isys_proxy_resp_info_abi { struct ipu6_fw_isys_proxy_error_info_abi error_info; }; -/** - * struct ipu6_fw_proxy_write_queue_token - ISYS proxy write queue token - * @request_id: update id for the specific proxy write request - * @region_index: Region id for the proxy write request - * @offset: Offset of the write request according to the base address - * of the region - * @value: Value that is requested to be written with the proxy write request - */ -struct ipu6_fw_proxy_write_queue_token { - u32 request_id; - u32 region_index; - u32 offset; - u32 value; -}; - /** * struct ipu6_fw_resp_queue_token - ISYS response queue token * @resp_info: response info From 9d03c3569fdbe0c450de6c732e7ffd4d4ec0ff29 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:32:58 +0300 Subject: [PATCH 053/596] media: ipu6: Cleanup ipu6_mmu_init() The struct ipu6_mmu_pdata is used only in ipu6_mmu_init(), and not really needed, remove it. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-mmu.c | 13 ++++++------- drivers/media/pci/intel/ipu6/ipu6.h | 6 ------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c index 0e6dca36b6ba..6b1a49ebbcc2 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.c +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c @@ -750,27 +750,26 @@ struct ipu6_mmu *ipu6_mmu_init(struct device *dev, const struct ipu6_hw_variants *hw) { struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev)); - struct ipu6_mmu_pdata *pdata; + struct ipu6_mmu_hw *mmu_hw; struct ipu6_mmu *mmu; unsigned int i; if (hw->nr_mmus > IPU6_MMU_MAX_DEVICES) return ERR_PTR(-EINVAL); - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) + mmu_hw = devm_kcalloc(dev, sizeof(*mmu_hw), hw->nr_mmus, GFP_KERNEL); + if (!mmu_hw) return ERR_PTR(-ENOMEM); for (i = 0; i < hw->nr_mmus; i++) { - struct ipu6_mmu_hw *pdata_mmu = &pdata->mmu_hw[i]; const struct ipu6_mmu_hw *src_mmu = &hw->mmu_hw[i]; if (src_mmu->nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS || src_mmu->nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS) return ERR_PTR(-EINVAL); - *pdata_mmu = *src_mmu; - pdata_mmu->base = base + src_mmu->offset; + mmu_hw[i] = *src_mmu; + mmu_hw[i].base = base + src_mmu->offset; } mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL); @@ -778,7 +777,7 @@ struct ipu6_mmu *ipu6_mmu_init(struct device *dev, return ERR_PTR(-ENOMEM); mmu->mmid = mmid; - mmu->mmu_hw = pdata->mmu_hw; + mmu->mmu_hw = mmu_hw; mmu->nr_mmus = hw->nr_mmus; mmu->tlb_invalidate = tlb_invalidate; mmu->ready = false; diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index 07ca59048f44..874b377a2e9a 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -239,12 +239,6 @@ struct ipu6_mmu_hw { bool insert_read_before_invalidate; }; -struct ipu6_mmu_pdata { - u32 nr_mmus; - struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES]; - int mmid; -}; - struct ipu6_isys_csi2_pdata { void __iomem *base; }; From b77afd924a70391c689eda4ce162712cf965408e Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:32:59 +0300 Subject: [PATCH 054/596] media: ipu6: Simplify firmware com arguments We need only pointer to ipu6_isys struct, and queue number can be removed as it is always the same. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 8 ++++---- drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 4 ++-- drivers/media/pci/intel/ipu6/ipu6-isys.c | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c index a65e9f1aa104..fc2f24c05de7 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c @@ -366,14 +366,14 @@ int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) } struct ipu6_fw_isys_resp_info_abi * -ipu6_fw_isys_get_resp(void *context, unsigned int queue) +ipu6_fw_isys_get_resp(struct ipu6_isys *isys) { - return ipu6_recv_get_token(context, queue); + return ipu6_recv_get_token(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); } -void ipu6_fw_isys_put_resp(void *context, unsigned int queue) +void ipu6_fw_isys_put_resp(struct ipu6_isys *isys) { - ipu6_recv_put_token(context, queue); + ipu6_recv_put_token(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); } void ipu6_fw_isys_dump_stream_cfg(struct device *dev, diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h index 393245a633f4..15d4ae355f28 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h @@ -576,6 +576,6 @@ int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, unsigned int offset, u32 value); void ipu6_fw_isys_cleanup(struct ipu6_isys *isys); struct ipu6_fw_isys_resp_info_abi * -ipu6_fw_isys_get_resp(void *context, unsigned int queue); -void ipu6_fw_isys_put_resp(void *context, unsigned int queue); +ipu6_fw_isys_get_resp(struct ipu6_isys *isys); +void ipu6_fw_isys_put_resp(struct ipu6_isys *isys); #endif diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 180a53f3d7ab..8af891081bd3 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -1215,7 +1215,7 @@ static int isys_isr_one(struct ipu6_bus_device *adev) if (!isys->fwctx) return 1; - resp = ipu6_fw_isys_get_resp(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); + resp = ipu6_fw_isys_get_resp(isys); if (!resp) return 1; @@ -1324,7 +1324,7 @@ static int isys_isr_one(struct ipu6_bus_device *adev) ipu6_isys_put_stream(stream); leave: - ipu6_fw_isys_put_resp(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); + ipu6_fw_isys_put_resp(isys); return 0; } From ec0d7fc2a01f3b8b83029ac0881c5a77e1a266fc Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:00 +0300 Subject: [PATCH 055/596] media: ipu6: Add helper to identify ipu7 The ipu7 comes in two flavors as of now, ipu7 and ipu7p5. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index 874b377a2e9a..ebae876058ca 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -27,11 +27,17 @@ struct ipu6_bus_device; #define IPU_VERSION_6SE BIT(1) /* JSL */ #define IPU_VERSION_6EP BIT(2) /* ADL/RPL */ #define IPU_VERSION_6EP_MTL BIT(3) /* MTL */ +#define IPU_VERSION_7 BIT(4) /* LNL */ +#define IPU_VERSION_7P5 BIT(5) /* PTL */ #define IS_IPU6_TGL(isp) ((isp)->hw_ver & IPU_VERSION_6) #define IS_IPU6SE(isp) ((isp)->hw_ver & IPU_VERSION_6SE) #define IS_IPU6EP(isp) ((isp)->hw_ver & IPU_VERSION_6EP) #define IS_IPU6EP_MTL(isp) ((isp)->hw_ver & IPU_VERSION_6EP_MTL) +#define IS_IPU7(isp) ((isp)->hw_ver & \ + (IPU_VERSION_7 | IPU_VERSION_7P5)) +#define IS_IPU7_MTL(isp) ((isp)->hw_ver & IPU_VERSION_7) +#define IS_IPU7P5(isp) ((isp)->hw_ver & IPU_VERSION_7P5) /* * ISYS DMA can overshoot. For higher resolutions over allocation is one line From 59b96c5ef2b9b20328d740bfe6bad20f34933b18 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:01 +0300 Subject: [PATCH 056/596] media: ipu6: Prepare buttress for ipu7 support Introduce a struct to hold buttress registers and bitmasks that vary across hardware versions beyond ipu6 to simplify support for future hardware versions. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-buttress.c | 96 ++++++++++--------- drivers/media/pci/intel/ipu6/ipu6-buttress.h | 25 +++++ .../intel/ipu6/ipu6-platform-buttress-regs.h | 2 +- drivers/media/pci/intel/ipu6/ipu6.c | 26 +++++ 4 files changed, 101 insertions(+), 48 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c index cbe356589a9d..3c5f4e407b7c 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.c +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -55,11 +55,6 @@ #define BUTTRESS_MAX_CONSECUTIVE_IRQS 100 -static const u32 ipu6_adev_irq_mask[2] = { - BUTTRESS_ISR_IS_IRQ, - BUTTRESS_ISR_PS_IRQ -}; - int ipu6_buttress_ipc_reset(struct ipu6_device *isp, struct ipu6_buttress_ipc *ipc) { @@ -337,7 +332,8 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) struct ipu6_device *isp = isp_ptr; struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; struct ipu6_buttress *b = &isp->buttress; - u32 reg_irq_sts = BUTTRESS_REG_ISR_STATUS; + const struct ipu6_buttress_registers *regs = b->regs; + const u32 adev_irq_mask[] = { regs->irq_is, regs->irq_ps }; irqreturn_t ret = IRQ_NONE; u32 disable_irqs = 0; u32 irq_status; @@ -348,7 +344,7 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) if (!active) return IRQ_NONE; - irq_status = readl(isp->base + reg_irq_sts); + irq_status = readl(isp->base + regs->irq_status); if (irq_status == 0 || WARN_ON_ONCE(irq_status == 0xffffffffu)) { if (active > 0) pm_runtime_put_noidle(&isp->pdev->dev); @@ -356,26 +352,26 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) } do { - writel(irq_status, isp->base + BUTTRESS_REG_ISR_CLEAR); + writel(irq_status, isp->base + regs->irq_clear); - for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask); i++) { + for (i = 0; i < ARRAY_SIZE(adev_irq_mask); i++) { irqreturn_t r = ipu6_buttress_call_isr(adev[i]); - if (!(irq_status & ipu6_adev_irq_mask[i])) + if (!(irq_status & adev_irq_mask[i])) continue; if (r == IRQ_WAKE_THREAD) { ret = IRQ_WAKE_THREAD; - disable_irqs |= ipu6_adev_irq_mask[i]; + disable_irqs |= adev_irq_mask[i]; } else if (ret == IRQ_NONE && r == IRQ_HANDLED) { ret = IRQ_HANDLED; } } - if ((irq_status & BUTTRESS_EVENT) && ret == IRQ_NONE) + if ((irq_status & regs->irq_events) && ret == IRQ_NONE) ret = IRQ_HANDLED; - if (irq_status & BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING) { + if (irq_status & regs->irq_cse_ipc) { dev_dbg(&isp->pdev->dev, "BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n"); @@ -383,13 +379,13 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) complete(&b->ipc.recv_complete); } - if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) { + if (irq_status & regs->irq_exec_done) { dev_dbg(&isp->pdev->dev, "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); complete(&b->ipc.send_complete); } - if (irq_status & BUTTRESS_ISR_SAI_VIOLATION && + if (irq_status & regs->irq_sai && ipu6_buttress_get_secure_mode(isp)) dev_err(&isp->pdev->dev, "BUTTRESS_ISR_SAI_VIOLATION\n"); @@ -408,12 +404,12 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) break; } - irq_status = readl(isp->base + reg_irq_sts); + irq_status = readl(isp->base + regs->irq_status); } while (irq_status); if (disable_irqs) - writel(BUTTRESS_IRQS & ~disable_irqs, - isp->base + BUTTRESS_REG_ISR_ENABLE); + writel(regs->irq_all & ~disable_irqs, + isp->base + regs->irq_enable); if (active > 0) pm_runtime_put(&isp->pdev->dev); @@ -424,12 +420,13 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr) { struct ipu6_device *isp = isp_ptr; + const struct ipu6_buttress_registers *regs = isp->buttress.regs; struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; const struct ipu6_auxdrv_data *drv_data = NULL; irqreturn_t ret = IRQ_NONE; unsigned int i; - for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask) && adev[i]; i++) { + for (i = 0; i < ARRAY_SIZE(adev) && adev[i]; i++) { drv_data = adev[i]->auxdrv_data; if (!drv_data) continue; @@ -439,7 +436,7 @@ irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr) ret = IRQ_HANDLED; } - writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + writel(regs->irq_all, isp->base + regs->irq_enable); return ret; } @@ -472,7 +469,7 @@ int ipu6_buttress_power(struct device *dev, writel(val, isp->base + ctrl->freq_ctl); - ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, + ret = readl_poll_timeout(isp->base + isp->buttress.regs->pwr_status, val, (val & ctrl->pwr_sts_mask) == pwr_sts, 100, BUTTRESS_POWER_TIMEOUT_US); if (ret) @@ -488,7 +485,7 @@ bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp) { u32 val; - val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + val = readl(isp->base + isp->buttress.regs->security_ctl); return val & BUTTRESS_SECURITY_CTL_FW_SECURE_MODE; } @@ -500,7 +497,7 @@ bool ipu6_buttress_auth_done(struct ipu6_device *isp) if (!isp->secure_mode) return true; - val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + val = readl(isp->base + isp->buttress.regs->security_ctl); val = FIELD_GET(BUTTRESS_SECURITY_CTL_FW_SETUP_MASK, val); return val == BUTTRESS_SECURITY_CTL_AUTH_DONE; @@ -518,10 +515,10 @@ int ipu6_buttress_reset_authentication(struct ipu6_device *isp) } writel(BUTTRESS_FW_RESET_CTL_START, isp->base + - BUTTRESS_REG_FW_RESET_CTL); + isp->buttress.regs->fw_reset_ctl); - ret = readl_poll_timeout(isp->base + BUTTRESS_REG_FW_RESET_CTL, val, - val & BUTTRESS_FW_RESET_CTL_DONE, 500, + ret = readl_poll_timeout(isp->base + isp->buttress.regs->fw_reset_ctl, + val, val & BUTTRESS_FW_RESET_CTL_DONE, 500, BUTTRESS_CSE_FWRESET_TIMEOUT_US); if (ret) { dev_err(&isp->pdev->dev, @@ -530,7 +527,8 @@ int ipu6_buttress_reset_authentication(struct ipu6_device *isp) } dev_dbg(&isp->pdev->dev, "FW reset for authentication done\n"); - writel(0, isp->base + BUTTRESS_REG_FW_RESET_CTL); + writel(0, isp->base + isp->buttress.regs->fw_reset_ctl); + /* leave some time for HW restore */ usleep_range(800, 1000); @@ -660,7 +658,7 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) mask = BUTTRESS_SECURITY_CTL_FW_SETUP_MASK; done = BUTTRESS_SECURITY_CTL_FW_SETUP_DONE; fail = BUTTRESS_SECURITY_CTL_AUTH_FAILED; - ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ret = readl_poll_timeout(isp->base + b->regs->security_ctl, data, ((data & mask) == done || (data & mask) == fail), 500, BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); @@ -700,7 +698,7 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) } done = BUTTRESS_SECURITY_CTL_AUTH_DONE; - ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ret = readl_poll_timeout(isp->base + b->regs->security_ctl, data, ((data & mask) == done || (data & mask) == fail), 500, BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US); @@ -725,15 +723,16 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) { + const struct ipu6_buttress_registers *regs = isp->buttress.regs; u32 val, mask, done; int ret; mask = BUTTRESS_PWR_STATE_HH_STATUS_MASK; writel(BUTTRESS_FABRIC_CMD_START_TSC_SYNC, - isp->base + BUTTRESS_REG_FABRIC_CMD); + isp->base + regs->fabric_cmd); - val = readl(isp->base + BUTTRESS_REG_PWR_STATE); + val = readl(isp->base + regs->pwr_status); val = FIELD_GET(mask, val); if (val == BUTTRESS_PWR_STATE_HH_STATE_ERR) { dev_err(&isp->pdev->dev, "Start tsc sync failed\n"); @@ -741,8 +740,8 @@ static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) } done = BUTTRESS_PWR_STATE_HH_STATE_DONE; - ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, val, - FIELD_GET(mask, val) == done, 500, + ret = readl_poll_timeout(isp->base + regs->pwr_status, + val, FIELD_GET(mask, val) == done, 500, BUTTRESS_TSC_SYNC_TIMEOUT_US); if (ret) dev_err(&isp->pdev->dev, "Start tsc sync timeout\n"); @@ -762,11 +761,13 @@ int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) if (ret != -ETIMEDOUT) return ret; - val = readl(isp->base + BUTTRESS_REG_TSW_CTL); + u32 tsw_ctl = isp->buttress.regs->tsw_ctl; + + val = readl(isp->base + tsw_ctl); val = val | BUTTRESS_TSW_CTL_SOFT_RESET; - writel(val, isp->base + BUTTRESS_REG_TSW_CTL); + writel(val, isp->base + tsw_ctl); val = val & ~BUTTRESS_TSW_CTL_SOFT_RESET; - writel(val, isp->base + BUTTRESS_REG_TSW_CTL); + writel(val, isp->base + tsw_ctl); } dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n"); @@ -777,13 +778,14 @@ EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, "INTEL_IPU6"); void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val) { + void __iomem *tsc = isp->base + isp->buttress.regs->tsc_lo; u32 tsc_hi_1, tsc_hi_2, tsc_lo; unsigned long flags; local_irq_save(flags); - tsc_hi_1 = readl(isp->base + BUTTRESS_REG_TSC_HI); - tsc_lo = readl(isp->base + BUTTRESS_REG_TSC_LO); - tsc_hi_2 = readl(isp->base + BUTTRESS_REG_TSC_HI); + tsc_hi_1 = readl(tsc + BUTTRESS_TSC_HI_OFFSET); + tsc_lo = readl(tsc); + tsc_hi_2 = readl(tsc + BUTTRESS_TSC_HI_OFFSET); if (tsc_hi_1 == tsc_hi_2) { *val = (u64)tsc_hi_1 << 32 | tsc_lo; } else { @@ -816,9 +818,9 @@ void ipu6_buttress_restore(struct ipu6_device *isp) { struct ipu6_buttress *b = &isp->buttress; - writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); - writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); - writel(b->wdt_cached_value, isp->base + BUTTRESS_REG_WDT); + writel(b->regs->irq_all, isp->base + b->regs->irq_clear); + writel(b->regs->irq_all, isp->base + b->regs->irq_enable); + writel(b->wdt_cached_value, isp->base + b->regs->wdt); } int ipu6_buttress_init(struct ipu6_device *isp) @@ -851,12 +853,12 @@ int ipu6_buttress_init(struct ipu6_device *isp) readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH), readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); - b->wdt_cached_value = readl(isp->base + BUTTRESS_REG_WDT); - writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); - writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + b->wdt_cached_value = readl(isp->base + b->regs->wdt); + writel(b->regs->irq_all, isp->base + b->regs->irq_clear); + writel(b->regs->irq_all, isp->base + b->regs->irq_enable); /* get ref_clk frequency by reading the indication in btrs control */ - val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); + val = readl(isp->base + b->regs->btrs_ctrl); val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val); switch (val) { @@ -902,7 +904,7 @@ void ipu6_buttress_exit(struct ipu6_device *isp) { struct ipu6_buttress *b = &isp->buttress; - writel(0, isp->base + BUTTRESS_REG_ISR_ENABLE); + writel(0, isp->base + b->regs->irq_enable); mutex_destroy(&b->power_mutex); mutex_destroy(&b->auth_mutex); diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h index b1c1a10e3e6d..68586a32fda3 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.h +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h @@ -42,9 +42,34 @@ struct ipu6_buttress_ipc { u32 data0_in; }; +struct ipu6_buttress_registers { + /* Registers */ + u32 irq_status; + u32 irq_clear; + u32 irq_enable; + u32 pwr_status; + u32 security_ctl; + u32 fw_reset_ctl; + u32 fabric_cmd; + u32 tsw_ctl; + u32 tsc_lo; + u32 wdt; + u32 btrs_ctrl; + + /* Bitmasks */ + u32 irq_is; + u32 irq_ps; + u32 irq_all; + u32 irq_events; + u32 irq_cse_ipc; + u32 irq_exec_done; + u32 irq_sai; +}; + struct ipu6_buttress { struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex; struct ipu6_buttress_ipc ipc; + const struct ipu6_buttress_registers *regs; struct list_head constraints; u32 wdt_cached_value; bool force_suspend; diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h index efd65e494c16..5006cce74b23 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h +++ b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h @@ -212,7 +212,7 @@ enum { #define BUTTRESS_TSW_CTL_SOFT_RESET BIT(8) #define BUTTRESS_REG_TSC_LO 0x164 -#define BUTTRESS_REG_TSC_HI 0x168 +#define BUTTRESS_TSC_HI_OFFSET 4 #define BUTTRESS_IRQS (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index efb58bc4d127..55e660fda421 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -228,6 +228,30 @@ static const struct ipu6_buttress_ctrl psys_buttress_ctrl = { .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, }; +static const struct ipu6_buttress_registers ipu6_buttress_regs = { + /* Registers */ + .irq_status = BUTTRESS_REG_ISR_STATUS, + .irq_clear = BUTTRESS_REG_ISR_CLEAR, + .irq_enable = BUTTRESS_REG_ISR_ENABLE, + .pwr_status = BUTTRESS_REG_PWR_STATE, + .security_ctl = BUTTRESS_REG_SECURITY_CTL, + .fw_reset_ctl = BUTTRESS_REG_FW_RESET_CTL, + .fabric_cmd = BUTTRESS_REG_FABRIC_CMD, + .tsw_ctl = BUTTRESS_REG_TSW_CTL, + .tsc_lo = BUTTRESS_REG_TSC_LO, + .wdt = BUTTRESS_REG_WDT, + .btrs_ctrl = BUTTRESS_REG_BTRS_CTRL, + + /* Bitmasks */ + .irq_is = BUTTRESS_ISR_IS_IRQ, + .irq_ps = BUTTRESS_ISR_PS_IRQ, + .irq_all = BUTTRESS_IRQS, + .irq_events = BUTTRESS_EVENT, + .irq_cse_ipc = BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING, + .irq_exec_done = BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE, + .irq_sai = BUTTRESS_ISR_SAI_VIOLATION, +}; + static void ipu6_pkg_dir_configure_spc(struct ipu6_device *isp, const struct ipu6_hw_variants *hw_variant, @@ -522,6 +546,8 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) pci_set_master(pdev); isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt); + isp->buttress.regs = &ipu6_buttress_regs; + switch (id->device) { case PCI_DEVICE_ID_INTEL_IPU6: isp->hw_ver = IPU_VERSION_6; From 7aa505f2bf653a088ae4e112b5bc6fbc3f842eba Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:02 +0300 Subject: [PATCH 057/596] media: ipu6: Use single struct for registers Move register values from ipu6_buttress_ipc struct to ipu6_buttress_registers as these are different in future hardware versions as well. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-buttress.c | 86 +++++++++----------- drivers/media/pci/intel/ipu6/ipu6-buttress.h | 17 ++-- drivers/media/pci/intel/ipu6/ipu6.c | 13 +-- 3 files changed, 54 insertions(+), 62 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c index 3c5f4e407b7c..e77a569bb97e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.c +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -55,11 +55,11 @@ #define BUTTRESS_MAX_CONSECUTIVE_IRQS 100 -int ipu6_buttress_ipc_reset(struct ipu6_device *isp, - struct ipu6_buttress_ipc *ipc) +int ipu6_buttress_ipc_reset(struct ipu6_device *isp) { unsigned int retries = BUTTRESS_IPC_RESET_RETRY; struct ipu6_buttress *b = &isp->buttress; + const struct ipu6_buttress_registers *regs = b->regs; u32 val = 0, csr_in_clr; if (!isp->secure_mode) { @@ -70,11 +70,11 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, mutex_lock(&b->ipc_mutex); /* Clear-by-1 CSR (all bits), corresponding internal states. */ - val = readl(isp->base + ipc->csr_in); - writel(val, isp->base + ipc->csr_in); + val = readl(isp->base + regs->csr_in); + writel(val, isp->base + regs->csr_in); /* Set peer CSR bit IPC_PEER_COMP_ACTIONS_RST_PHASE1 */ - writel(ENTRY, isp->base + ipc->csr_out); + writel(ENTRY, isp->base + regs->csr_out); /* * Clear-by-1 all CSR bits EXCEPT following * bits: @@ -89,7 +89,7 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, do { usleep_range(400, 500); - val = readl(isp->base + ipc->csr_in); + val = readl(isp->base + regs->csr_in); switch (val) { case ENTRY | EXIT: case ENTRY | EXIT | QUERY: @@ -100,8 +100,8 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, * 2) Set peer CSR bit * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. */ - writel(ENTRY | EXIT, isp->base + ipc->csr_in); - writel(QUERY, isp->base + ipc->csr_out); + writel(ENTRY | EXIT, isp->base + regs->csr_in); + writel(QUERY, isp->base + regs->csr_out); break; case ENTRY: case ENTRY | QUERY: @@ -112,8 +112,8 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, * 2) Set peer CSR bit * IPC_PEER_COMP_ACTIONS_RST_PHASE1. */ - writel(ENTRY | QUERY, isp->base + ipc->csr_in); - writel(ENTRY, isp->base + ipc->csr_out); + writel(ENTRY | QUERY, isp->base + regs->csr_in); + writel(ENTRY, isp->base + regs->csr_out); break; case EXIT: case EXIT | QUERY: @@ -130,17 +130,17 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, * 3) Set peer CSR bit * IPC_PEER_COMP_ACTIONS_RST_PHASE2. */ - writel(EXIT, isp->base + ipc->csr_in); - writel(0, isp->base + ipc->db0_in); - writel(csr_in_clr, isp->base + ipc->csr_in); - writel(EXIT, isp->base + ipc->csr_out); + writel(EXIT, isp->base + regs->csr_in); + writel(0, isp->base + regs->db0_in); + writel(csr_in_clr, isp->base + regs->csr_in); + writel(EXIT, isp->base + regs->csr_out); /* * Read csr_in again to make sure if RST_PHASE2 is done. * If csr_in is QUERY, it should be handled again. */ usleep_range(200, 300); - val = readl(isp->base + ipc->csr_in); + val = readl(isp->base + regs->csr_in); if (val & QUERY) { dev_dbg(&isp->pdev->dev, "RST_PHASE2 retry csr_in = %x\n", val); @@ -155,8 +155,8 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, * 2) Set peer CSR bit * IPC_PEER_COMP_ACTIONS_RST_PHASE1 */ - writel(QUERY, isp->base + ipc->csr_in); - writel(ENTRY, isp->base + ipc->csr_out); + writel(QUERY, isp->base + regs->csr_in); + writel(ENTRY, isp->base + regs->csr_out); break; default: dev_dbg_ratelimited(&isp->pdev->dev, @@ -171,42 +171,42 @@ int ipu6_buttress_ipc_reset(struct ipu6_device *isp, return -ETIMEDOUT; } -static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp, - struct ipu6_buttress_ipc *ipc) +static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp) { writel(BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ, - isp->base + ipc->csr_out); + isp->base + isp->buttress.regs->csr_out); } static int -ipu6_buttress_ipc_validity_open(struct ipu6_device *isp, - struct ipu6_buttress_ipc *ipc) +ipu6_buttress_ipc_validity_open(struct ipu6_device *isp) { unsigned int mask = BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID; + const struct ipu6_buttress_registers *regs = isp->buttress.regs; void __iomem *addr; int ret; u32 val; writel(BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ, - isp->base + ipc->csr_out); + isp->base + regs->csr_out); - addr = isp->base + ipc->csr_in; + addr = isp->base + regs->csr_in; ret = readl_poll_timeout(addr, val, val & mask, 200, BUTTRESS_IPC_VALIDITY_TIMEOUT_US); if (ret) { dev_err(&isp->pdev->dev, "CSE validity timeout 0x%x\n", val); - ipu6_buttress_ipc_validity_close(isp, ipc); + ipu6_buttress_ipc_validity_close(isp); } return ret; } -static void ipu6_buttress_ipc_recv(struct ipu6_device *isp, - struct ipu6_buttress_ipc *ipc, u32 *ipc_msg) +static void ipu6_buttress_ipc_recv(struct ipu6_device *isp, u32 *ipc_msg) { + const struct ipu6_buttress_registers *regs = isp->buttress.regs; + if (ipc_msg) - *ipc_msg = readl(isp->base + ipc->data0_in); - writel(0, isp->base + ipc->db0_in); + *ipc_msg = readl(isp->base + regs->data0_in); + writel(0, isp->base + regs->db0_in); } static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, @@ -217,13 +217,14 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY; struct ipu6_buttress *b = &isp->buttress; struct ipu6_buttress_ipc *ipc = &b->ipc; + const struct ipu6_buttress_registers *regs = b->regs; u32 val; int ret; int tout; mutex_lock(&b->ipc_mutex); - ret = ipu6_buttress_ipc_validity_open(isp, ipc); + ret = ipu6_buttress_ipc_validity_open(isp); if (ret) { dev_err(&isp->pdev->dev, "IPC validity open failed\n"); goto out; @@ -239,9 +240,9 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, dev_dbg(&isp->pdev->dev, "bulk IPC command: 0x%x\n", msgs[i].cmd); - writel(msgs[i].cmd, isp->base + ipc->data0_out); + writel(msgs[i].cmd, isp->base + regs->data0_out); val = BUTTRESS_IU2CSEDB0_BUSY | msgs[i].cmd_size; - writel(val, isp->base + ipc->db0_out); + writel(val, isp->base + regs->db0_out); tout = wait_for_completion_timeout(&ipc->send_complete, tx_timeout_jiffies); @@ -253,7 +254,7 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, } /* Try again if CSE is not responding on first try */ - writel(0, isp->base + ipc->db0_out); + writel(0, isp->base + regs->db0_out); i--; continue; } @@ -271,8 +272,8 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, goto out; } - if (ipc->nack_mask && - (ipc->recv_data & ipc->nack_mask) == ipc->nack) { + if ((ipc->recv_data & BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK) == + BUTTRESS_CSE2IUDATA0_IPC_NACK) { dev_err(&isp->pdev->dev, "IPC NACK for cmd 0x%x\n", msgs[i].cmd); ret = -EIO; @@ -291,7 +292,7 @@ static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, dev_dbg(&isp->pdev->dev, "bulk IPC commands done\n"); out: - ipu6_buttress_ipc_validity_close(isp, ipc); + ipu6_buttress_ipc_validity_close(isp); mutex_unlock(&b->ipc_mutex); return ret; } @@ -375,7 +376,7 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) dev_dbg(&isp->pdev->dev, "BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n"); - ipu6_buttress_ipc_recv(isp, &b->ipc, &b->ipc.recv_data); + ipu6_buttress_ipc_recv(isp, &b->ipc.recv_data); complete(&b->ipc.recv_complete); } @@ -836,15 +837,6 @@ int ipu6_buttress_init(struct ipu6_device *isp) init_completion(&b->ipc.send_complete); init_completion(&b->ipc.recv_complete); - b->ipc.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; - b->ipc.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK; - b->ipc.csr_in = BUTTRESS_REG_CSE2IUCSR; - b->ipc.csr_out = BUTTRESS_REG_IU2CSECSR; - b->ipc.db0_in = BUTTRESS_REG_CSE2IUDB0; - b->ipc.db0_out = BUTTRESS_REG_IU2CSEDB0; - b->ipc.data0_in = BUTTRESS_REG_CSE2IUDATA0; - b->ipc.data0_out = BUTTRESS_REG_IU2CSEDATA0; - INIT_LIST_HEAD(&b->constraints); isp->secure_mode = ipu6_buttress_get_secure_mode(isp); @@ -880,7 +872,7 @@ int ipu6_buttress_init(struct ipu6_device *isp) /* Retry couple of times in case of CSE initialization is delayed */ do { - ret = ipu6_buttress_ipc_reset(isp, &b->ipc); + ret = ipu6_buttress_ipc_reset(isp); if (ret) { dev_warn(&isp->pdev->dev, "IPC reset protocol failed, retrying\n"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h index 68586a32fda3..a3c23adede7b 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.h +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h @@ -31,15 +31,7 @@ struct ipu6_buttress_ctrl { struct ipu6_buttress_ipc { struct completion send_complete; struct completion recv_complete; - u32 nack; - u32 nack_mask; u32 recv_data; - u32 csr_out; - u32 csr_in; - u32 db0_in; - u32 db0_out; - u32 data0_out; - u32 data0_in; }; struct ipu6_buttress_registers { @@ -55,6 +47,12 @@ struct ipu6_buttress_registers { u32 tsc_lo; u32 wdt; u32 btrs_ctrl; + u32 csr_in; + u32 csr_out; + u32 db0_in; + u32 db0_out; + u32 data0_in; + u32 data0_out; /* Bitmasks */ u32 irq_is; @@ -83,8 +81,7 @@ struct ipu6_ipc_buttress_bulk_msg { u8 cmd_size; }; -int ipu6_buttress_ipc_reset(struct ipu6_device *isp, - struct ipu6_buttress_ipc *ipc); +int ipu6_buttress_ipc_reset(struct ipu6_device *isp); int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, const struct firmware *fw, struct sg_table *sgt); diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 55e660fda421..7ac9c92744c0 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -241,6 +241,12 @@ static const struct ipu6_buttress_registers ipu6_buttress_regs = { .tsc_lo = BUTTRESS_REG_TSC_LO, .wdt = BUTTRESS_REG_WDT, .btrs_ctrl = BUTTRESS_REG_BTRS_CTRL, + .csr_in = BUTTRESS_REG_CSE2IUCSR, + .csr_out = BUTTRESS_REG_IU2CSECSR, + .db0_in = BUTTRESS_REG_CSE2IUDB0, + .db0_out = BUTTRESS_REG_IU2CSEDB0, + .data0_in = BUTTRESS_REG_CSE2IUDATA0, + .data0_out = BUTTRESS_REG_IU2CSEDATA0, /* Bitmasks */ .irq_is = BUTTRESS_ISR_IS_IRQ, @@ -779,7 +785,6 @@ static int ipu6_resume(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct ipu6_device *isp = pci_get_drvdata(pdev); - struct ipu6_buttress *b = &isp->buttress; int ret; /* Configure the arbitration mechanisms for VC requests */ @@ -791,7 +796,7 @@ static int ipu6_resume(struct device *dev) ipu6_buttress_restore(isp); - ret = ipu6_buttress_ipc_reset(isp, &b->ipc); + ret = ipu6_buttress_ipc_reset(isp); if (ret) dev_err(&isp->pdev->dev, "IPC reset protocol failed!\n"); @@ -820,10 +825,8 @@ static int ipu6_runtime_resume(struct device *dev) ipu6_buttress_restore(isp); if (isp->need_ipc_reset) { - struct ipu6_buttress *b = &isp->buttress; - isp->need_ipc_reset = false; - ret = ipu6_buttress_ipc_reset(isp, &b->ipc); + ret = ipu6_buttress_ipc_reset(isp); if (ret) dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); } From b741032df1ff19b795a138bfceb4c43479976c82 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:03 +0300 Subject: [PATCH 058/596] media: ipu6: Rename IPU subsys ID We will need subsys ID later elsewhere, so move it to common header too. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-mmu.h | 3 --- drivers/media/pci/intel/ipu6/ipu6.c | 5 ++--- drivers/media/pci/intel/ipu6/ipu6.h | 3 +++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.h b/drivers/media/pci/intel/ipu6/ipu6-mmu.h index 8e40b4a66d7d..35cac80d2d4e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.h +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.h @@ -4,9 +4,6 @@ #ifndef IPU6_MMU_H #define IPU6_MMU_H -#define ISYS_MMID 1 -#define PSYS_MMID 0 - #include #include #include diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 7ac9c92744c0..c24e18520de6 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -424,8 +424,7 @@ ipu6_isys_init(struct pci_dev *pdev, struct device *parent, "ipu6_bus_initialize_device isys failed\n"); } - isys_adev->mmu = ipu6_mmu_init(dev, base, ISYS_MMID, - &ipdata->hw_variant); + isys_adev->mmu = ipu6_mmu_init(dev, base, IPU_ISYS, &ipdata->hw_variant); if (IS_ERR(isys_adev->mmu)) { put_device(&isys_adev->auxdev.dev); return dev_err_cast_probe(dev, isys_adev->mmu, @@ -465,7 +464,7 @@ ipu6_psys_init(struct pci_dev *pdev, struct device *parent, "ipu6_bus_initialize_device psys failed\n"); } - psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, PSYS_MMID, + psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, IPU_PSYS, &ipdata->hw_variant); if (IS_ERR(psys_adev->mmu)) { put_device(&psys_adev->auxdev.dev); diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index ebae876058ca..dc98a787612f 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -70,6 +70,9 @@ struct ipu6_device { bool bus_ready_to_probe; }; +#define IPU_PSYS 0 +#define IPU_ISYS 1 + #define IPU6_ISYS_NAME "isys" #define IPU6_PSYS_NAME "psys" From 381f97a28c04ffebaabf616260a99f054d9b32fd Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:04 +0300 Subject: [PATCH 059/596] media: ipu6: Add ipu7 buttress support The ipu7 differs from ipu6 e.g. in power management, authentication, interrupt handling and clock setup. Add support for ipu7 buttress. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-buttress.c | 296 +++++++++++++++--- drivers/media/pci/intel/ipu6/ipu6-buttress.h | 3 + .../intel/ipu6/ipu6-platform-buttress-regs.h | 102 ++++++ drivers/media/pci/intel/ipu6/ipu6.c | 104 ++++-- drivers/media/pci/intel/ipu6/ipu6.h | 2 + include/media/ipu6-pci-table.h | 2 + 6 files changed, 446 insertions(+), 63 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c index e77a569bb97e..13fd7a083b25 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.c +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -345,6 +345,13 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) if (!active) return IRQ_NONE; + if (IS_IPU7(isp)) { + u32 pb_irq; + + pb_irq = readl(isp->pb_base + IPU7_PB_INTERRUPT_STATUS); + writel(pb_irq, isp->pb_base + IPU7_PB_INTERRUPT_STATUS); + } + irq_status = readl(isp->base + regs->irq_status); if (irq_status == 0 || WARN_ON_ONCE(irq_status == 0xffffffffu)) { if (active > 0) @@ -442,28 +449,141 @@ irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr) return ret; } -int ipu6_buttress_power(struct device *dev, +static int ipu7_isys_d2d_power(struct ipu6_device *isp, bool on) +{ + u32 target = on ? IPU7_BUTTRESS_D2D_PWR_ACK : 0U; + u32 val; + int ret; + + val = readl(isp->base + IPU7_BUTTRESS_REG_D2D_CTL); + if ((val & IPU7_BUTTRESS_D2D_PWR_ACK) == target) + return 0; + + if (on) + val |= IPU7_BUTTRESS_D2D_PWR_EN; + else + val &= ~IPU7_BUTTRESS_D2D_PWR_EN; + writel(val, isp->base + IPU7_BUTTRESS_REG_D2D_CTL); + + ret = readl_poll_timeout(isp->base + IPU7_BUTTRESS_REG_D2D_CTL, val, + (val & IPU7_BUTTRESS_D2D_PWR_ACK) == target, + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) + dev_err(&isp->pdev->dev, "D2D power %s timeout: 0x%x\n", + on ? "up" : "down", val); + + return ret; +} + +static void ipu7_nde_control(struct ipu6_device *isp, bool on) +{ + u32 val; + + val = FIELD_PREP(IPU7_NDE_VAL_MASK, + on ? IPU7_NDE_VAL_ACTIVE : IPU7_NDE_VAL_DEFAULT) | + FIELD_PREP(IPU7_NDE_SCALE_MASK, + on ? IPU7_NDE_SCALE_ACTIVE : IPU7_NDE_SCALE_DEFAULT) | + FIELD_PREP(IPU7_NDE_VALID_MASK, + on ? IPU7_NDE_VALID_ACTIVE : IPU7_NDE_VALID_DEFAULT) | + FIELD_PREP(IPU7_NDE_RESVEC_MASK, IPU7_NDE_RESVEC); + writel(val, isp->base + IPU7_BUTTRESS_REG_NDE_CONTROL); +} + +static int __ipu7_power_on(struct device *dev, + const struct ipu6_buttress_ctrl *ctrl) +{ + struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; + bool is_isys = ctrl->subsys_id == IPU_ISYS; + u32 pwr_sts, val, ovrd_clk, slp, own_clk_ack; + int ret; + + val = ctrl->ratio | (IPU7_FREQ_CTL_CDYN << IPU7_FREQ_CTL_CDYN_SHIFT); + pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; + + if (is_isys) { + ret = ipu7_isys_d2d_power(isp, true); + if (ret) + return ret; + + ipu7_nde_control(isp, true); + } + + /* Request clock ownership. */ + ovrd_clk = is_isys ? IPU7_BUTTRESS_OVERRIDE_IS_CLK : + IPU7_BUTTRESS_OVERRIDE_PS_CLK; + + slp = readl(isp->base + IPU7_BUTTRESS_REG_SLEEP_LEVEL_CFG); + writel(slp | ovrd_clk, isp->base + IPU7_BUTTRESS_REG_SLEEP_LEVEL_CFG); + + own_clk_ack = is_isys ? IPU7_BUTTRESS_OWN_ACK_IS_CLK : + IPU7_BUTTRESS_OWN_ACK_PS_CLK; + ret = readl_poll_timeout(isp->base + IPU7_BUTTRESS_REG_SLEEP_LEVEL_STS, + slp, (slp & own_clk_ack), + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) + dev_warn(&isp->pdev->dev, "clock ownership timeout: 0x%x\n", + slp); + + writel(val, isp->base + ctrl->freq_ctl); + + ret = readl_poll_timeout(isp->base + isp->buttress.regs->pwr_status, + val, (val & ctrl->pwr_sts_mask) == pwr_sts, + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, + "Change power status timeout with 0x%x\n", val); + return ret; + } + + slp = readl(isp->base + IPU7_BUTTRESS_REG_SLEEP_LEVEL_CFG); + writel(slp & ~ovrd_clk, isp->base + IPU7_BUTTRESS_REG_SLEEP_LEVEL_CFG); + + return 0; +} + +static int __ipu7_power_off(struct device *dev, + const struct ipu6_buttress_ctrl *ctrl) +{ + struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; + u32 pwr_sts, val; + int ret; + + writel(0x8U, isp->base + ctrl->freq_ctl); + + pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; + ret = readl_poll_timeout(isp->base + isp->buttress.regs->pwr_status, + val, (val & ctrl->pwr_sts_mask) == pwr_sts, + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, + "Change power status timeout with 0x%x\n", val); + return ret; + } + + if (ctrl->subsys_id == IPU_ISYS) { + ipu7_isys_d2d_power(isp, false); + ipu7_nde_control(isp, false); + } + + return 0; +} + +static int __ipu6_power(struct device *dev, const struct ipu6_buttress_ctrl *ctrl, bool on) { struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; u32 pwr_sts, val; int ret; - if (!ctrl) - return 0; - - mutex_lock(&isp->buttress.power_mutex); - if (!on) { val = 0; pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; } else { val = BUTTRESS_FREQ_CTL_START | - FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK, - ctrl->ratio) | - FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK, - ctrl->qos_floor) | - BUTTRESS_FREQ_CTL_ICCMAX_LEVEL; + FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK, ctrl->ratio) | + FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK, + ctrl->qos_floor) | + BUTTRESS_FREQ_CTL_ICCMAX_LEVEL; pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; } @@ -477,6 +597,26 @@ int ipu6_buttress_power(struct device *dev, dev_err(&isp->pdev->dev, "Change power status timeout with 0x%x\n", val); + return ret; +} + +int ipu6_buttress_power(struct device *dev, + const struct ipu6_buttress_ctrl *ctrl, bool on) +{ + struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; + int ret; + + if (!ctrl) + return 0; + + mutex_lock(&isp->buttress.power_mutex); + + if (IS_IPU7(isp)) + ret = on ? __ipu7_power_on(dev, ctrl) : + __ipu7_power_off(dev, ctrl); + else + ret = __ipu6_power(dev, ctrl, on); + mutex_unlock(&isp->buttress.power_mutex); return ret; @@ -634,11 +774,18 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) * Write address of FIT table to FW_SOURCE register * Let's use fw address. I.e. not using FIT table yet */ - data = lower_32_bits(isp->psys->pkg_dir_dma_addr); - writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO); + if (IS_IPU7(isp)) { + writel(isp->cpd_fw->size, + isp->base + IPU7_BUTTRESS_REG_FW_SOURCE_SIZE); + writel(sg_dma_address(isp->psys->fw_sgt.sgl), + isp->base + IPU7_BUTTRESS_REG_FW_SOURCE_BASE); + } else { + data = lower_32_bits(isp->psys->pkg_dir_dma_addr); + writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO); - data = upper_32_bits(isp->psys->pkg_dir_dma_addr); - writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI); + data = upper_32_bits(isp->psys->pkg_dir_dma_addr); + writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI); + } /* * Write boot_load into IU2CSEDATA0 @@ -674,8 +821,11 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) goto out_unlock; } - ret = readl_poll_timeout(psys_pdata->base + BOOTLOADER_STATUS_OFFSET, - data, data == BOOTLOADER_MAGIC_KEY, 500, + void __iomem *base = IS_IPU7(isp) ? + isp->base + IPU7_BUTTRESS_REG_FW_BOOT_PARAMS7 : + psys_pdata->base + BOOTLOADER_STATUS_OFFSET; + + ret = readl_poll_timeout(base, data, data == BOOTLOADER_MAGIC_KEY, 500, BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); if (ret) { dev_err(&isp->pdev->dev, "Unexpected magic number 0x%x\n", @@ -815,12 +965,61 @@ u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp) } EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, "INTEL_IPU6"); +/* trigger uc control to wakeup fw */ +void ipu7_buttress_wakeup_isys(const struct ipu6_device *isp) +{ + u32 val; + + val = readl(isp->base + IPU7_BUTTRESS_REG_ISYS_UCX_CTRL_STATUS); + val |= IPU7_UCX_CTL_WAKEUP; + writel(val, isp->base + IPU7_BUTTRESS_REG_ISYS_UCX_CTRL_STATUS); +} +EXPORT_SYMBOL_NS_GPL(ipu7_buttress_wakeup_isys, "INTEL_IPU6"); + +u32 ipu7_buttress_get_isys_freq(struct ipu6_device *isp) +{ + u32 val; + + val = readl(isp->base + IPU7_BUTTRESS_REG_IS_WORKPOINT_REQ); + val &= IPU7_BUTTRESS_IS_FREQ_CTL_RATIO_MASK; + + return val * 50 / 3; +} +EXPORT_SYMBOL_NS_GPL(ipu7_buttress_get_isys_freq, "INTEL_IPU6"); + +static void ipu7_buttress_setup(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + u32 val; + + /* program PB BAR */ + writel(0, isp->pb_base + IPU7_GLOBAL_INTERRUPT_MASK); + val = readl(isp->pb_base + IPU7_BAR2_MISC_CONFIG); + val |= 0x100U; + + writel(val, isp->pb_base + IPU7_BAR2_MISC_CONFIG); + + writel(BIT(22), isp->pb_base + IPU7_TLBID_HASH_ENABLE_63_32); + writel(BIT(1), isp->pb_base + IPU7_TLBID_HASH_ENABLE_127_96); + + writel(b->regs->irq_all, isp->base + b->regs->irq_clear); + writel(b->regs->irq_all, isp->base + IPU7_BUTTRESS_REG_IRQ_MASK); + writel(b->regs->irq_all, isp->base + b->regs->irq_enable); + + /* LNL SW workaround for PS PD hang when PS sub-domain during PD */ + writel(IPU7_BUTTRESS_CG_CTRL_PS_FSM_CG, isp->base + IPU7_BUTTRESS_REG_CG_CTRL_BITS); +} + void ipu6_buttress_restore(struct ipu6_device *isp) { struct ipu6_buttress *b = &isp->buttress; - writel(b->regs->irq_all, isp->base + b->regs->irq_clear); - writel(b->regs->irq_all, isp->base + b->regs->irq_enable); + if (IS_IPU7(isp)) { + ipu7_buttress_setup(isp); + } else { + writel(b->regs->irq_all, isp->base + b->regs->irq_clear); + writel(b->regs->irq_all, isp->base + b->regs->irq_enable); + } writel(b->wdt_cached_value, isp->base + b->regs->wdt); } @@ -838,37 +1037,44 @@ int ipu6_buttress_init(struct ipu6_device *isp) init_completion(&b->ipc.recv_complete); INIT_LIST_HEAD(&b->constraints); - isp->secure_mode = ipu6_buttress_get_secure_mode(isp); - dev_dbg(&isp->pdev->dev, "IPU6 in %s mode touch 0x%x mask 0x%x\n", - isp->secure_mode ? "secure" : "non-secure", - readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH), - readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); - b->wdt_cached_value = readl(isp->base + b->regs->wdt); - writel(b->regs->irq_all, isp->base + b->regs->irq_clear); - writel(b->regs->irq_all, isp->base + b->regs->irq_enable); + dev_dbg(&isp->pdev->dev, "IPU in %s mode\n", + isp->secure_mode ? "secure" : "non-secure"); - /* get ref_clk frequency by reading the indication in btrs control */ - val = readl(isp->base + b->regs->btrs_ctrl); - val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val); - - switch (val) { - case 0x0: - b->ref_clk = 240; - break; - case 0x1: - b->ref_clk = 192; - break; - case 0x2: + if (IS_IPU7(isp)) { + ipu7_buttress_setup(isp); b->ref_clk = 384; - break; - default: - dev_warn(&isp->pdev->dev, - "Unsupported ref clock, use 19.2Mhz by default.\n"); - b->ref_clk = 192; - break; + } else { + dev_dbg(&isp->pdev->dev, "IPU6 touch 0x%x mask 0x%x\n", + readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH), + readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); + + writel(b->regs->irq_all, isp->base + b->regs->irq_clear); + writel(b->regs->irq_all, isp->base + b->regs->irq_enable); + + /* get ref_clk frequency by reading the indication in btrs control */ + val = readl(isp->base + b->regs->btrs_ctrl); + val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val); + + switch (val) { + case 0x0: + b->ref_clk = 240; + break; + case 0x1: + b->ref_clk = 192; + break; + case 0x2: + b->ref_clk = 384; + break; + default: + dev_warn(&isp->pdev->dev, + "Unsupported ref clock, use 19.2Mhz by default.\n"); + b->ref_clk = 192; + break; + } } + b->wdt_cached_value = readl(isp->base + b->regs->wdt); /* Retry couple of times in case of CSE initialization is delayed */ do { diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h index a3c23adede7b..af72de2b8729 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.h +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h @@ -26,6 +26,7 @@ struct ipu6_buttress_ctrl { u32 freq_ctl, pwr_sts_shift, pwr_sts_mask, pwr_sts_on, pwr_sts_off; unsigned int ratio; unsigned int qos_floor; + int subsys_id; }; struct ipu6_buttress_ipc { @@ -104,4 +105,6 @@ void ipu6_buttress_exit(struct ipu6_device *isp); void ipu6_buttress_csi_port_config(struct ipu6_device *isp, u32 legacy, u32 combo); void ipu6_buttress_restore(struct ipu6_device *isp); +void ipu7_buttress_wakeup_isys(const struct ipu6_device *isp); +u32 ipu7_buttress_get_isys_freq(struct ipu6_device *isp); #endif /* IPU6_BUTTRESS_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h index 5006cce74b23..c4da72c6bd8f 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h +++ b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h @@ -221,4 +221,106 @@ enum { #define BUTTRESS_EVENT (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ BUTTRESS_ISR_SAI_VIOLATION) + +/* IPU7 */ + +/* IPU7 buttress register offsets */ +#define IPU7_BUTTRESS_REG_SKU 0x3030 +#define IPU7_BUTTRESS_REG_IRQ_STATUS 0x2000 +#define IPU7_BUTTRESS_REG_IRQ_ENABLE 0x2008 +#define IPU7_BUTTRESS_REG_IRQ_CLEAR 0x200c +#define IPU7_BUTTRESS_REG_IRQ_MASK 0x2010 +#define IPU7_BUTTRESS_REG_TSC_CMD 0x2014 +#define IPU7_BUTTRESS_REG_TSC_CTL 0x2018 +#define IPU7_BUTTRESS_REG_TSC_LO 0x201c +#define IPU7_BUTTRESS_REG_IS_WORKPOINT_REQ 0x2104 +#define IPU7_BUTTRESS_REG_PS_WORKPOINT_REQ 0x2100 +#define IPU7_BUTTRESS_REG_IDLE_WDT 0x218c +#define IPU7_BUTTRESS_REG_ISYS_UCX_CTRL_STATUS 0x2200 +#define IPU7_BUTTRESS_REG_ISYS_UCX_START_ADDR 0x2204 +#define IPU7_BUTTRESS_REG_PSYS_UCX_CTRL_STATUS 0x2208 +#define IPU7_BUTTRESS_REG_PSYS_UCX_START_ADDR 0x220c +#define IPU7_BUTTRESS_REG_SECURITY_CTL 0x2318 +#define IPU7_BUTTRESS_REG_FW_RESET_CTL 0x2334 +#define IPU7_BUTTRESS_REG_FW_SOURCE_SIZE 0x2338 +#define IPU7_BUTTRESS_REG_FW_SOURCE_BASE 0x233c +#define IPU7_BUTTRESS_REG_CSE2IUDB0 0x2500 +#define IPU7_BUTTRESS_REG_CSE2IUDATA0 0x2504 +#define IPU7_BUTTRESS_REG_CSE2IUCSR 0x2508 +#define IPU7_BUTTRESS_REG_IU2CSEDB0 0x250c +#define IPU7_BUTTRESS_REG_IU2CSEDATA0 0x2510 +#define IPU7_BUTTRESS_REG_IU2CSECSR 0x2514 +#define IPU7_BUTTRESS_REG_CG_CTRL_BITS 0x3014 +#define IPU7_BUTTRESS_REG_FW_BOOT_PARAMS0 0x4000 +#define IPU7_BUTTRESS_REG_FW_BOOT_PARAMS7 0x401c +#define IPU7_BUTTRESS_REG_PWR_STATUS 0x2114 + +#define IPU7_BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE BIT(0) +#define IPU7_BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING BIT(1) +#define IPU7_BUTTRESS_IRQ_CSE_CSR_SET BIT(2) +#define IPU7_BUTTRESS_IRQ_SAI_VIOLATION BIT(4) +#define IPU7_BUTTRESS_IRQ_IS_IRQ BIT(30) +#define IPU7_BUTTRESS_IRQ_PS_IRQ BIT(31) + +#define IPU7_BUTTRESS_IRQS (IPU7_BUTTRESS_IRQ_IS_IRQ | \ + IPU7_BUTTRESS_IRQ_PS_IRQ | \ + IPU7_BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING | \ + IPU7_BUTTRESS_IRQ_CSE_CSR_SET | \ + IPU7_BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE) + +/* LNL SW workaround for PS PD hang */ +#define IPU7_BUTTRESS_CG_CTRL_PS_FSM_CG BIT(3) + +/* P-unit Bridge (PB) BAR registers */ +#define IPU7_PB_INTERRUPT_STATUS 0x0 + +/* Frequency control encoding */ +#define IPU7_FREQ_CTL_CDYN 0x80 +#define IPU7_FREQ_CTL_CDYN_SHIFT 8 +#define IPU7_BUTTRESS_IS_FREQ_CTL_RATIO_MASK GENMASK(7, 0) +#define IPU7_IS_FREQ_CTL_DEFAULT_RATIO 0x1b +#define IPU7_PS_FREQ_CTL_DEFAULT_RATIO 0x14 + +/* D2D power control */ +#define IPU7_BUTTRESS_REG_D2D_CTL 0x21d4 +#define IPU7_BUTTRESS_D2D_PWR_EN BIT(0) +#define IPU7_BUTTRESS_D2D_PWR_ACK BIT(4) + +#define IPU7_BUTTRESS_PWR_STATE_IS_PWR_SHIFT 0 +#define IPU7_BUTTRESS_PWR_STATE_IS_PWR_MASK (0x3U << 0) +#define IPU7_BUTTRESS_PWR_STATE_PS_PWR_SHIFT 4 +#define IPU7_BUTTRESS_PWR_STATE_PS_PWR_MASK (0x3U << 4) + +/* NDE */ +#define IPU7_BUTTRESS_REG_NDE_CONTROL 0x21a4 +#define IPU7_NDE_VAL_MASK GENMASK(9, 0) +#define IPU7_NDE_SCALE_MASK GENMASK(12, 10) +#define IPU7_NDE_VALID_MASK BIT(13) +#define IPU7_NDE_RESVEC_MASK GENMASK(19, 16) +#define IPU7_NDE_VAL_ACTIVE 48 +#define IPU7_NDE_SCALE_ACTIVE 2 +#define IPU7_NDE_VALID_ACTIVE 1 +#define IPU7_NDE_VAL_DEFAULT 1023 +#define IPU7_NDE_SCALE_DEFAULT 2 +#define IPU7_NDE_VALID_DEFAULT 0 +#define IPU7_NDE_RESVEC 0xe + +/* IS UCX control */ +#define IPU7_UCX_CTL_RESET BIT(0) +#define IPU7_UCX_CTL_RUN BIT(1) +#define IPU7_UCX_CTL_WAKEUP BIT(2) + +#define IPU7_BUTTRESS_REG_SLEEP_LEVEL_CFG 0x21b0 +#define IPU7_BUTTRESS_REG_SLEEP_LEVEL_STS 0x21b4 +#define IPU7_BUTTRESS_OVERRIDE_IS_CLK BIT(1) +#define IPU7_BUTTRESS_OWN_ACK_IS_CLK BIT(9) +#define IPU7_BUTTRESS_OVERRIDE_PS_CLK BIT(2) +#define IPU7_BUTTRESS_OWN_ACK_PS_CLK BIT(10) + +/* PB registers */ +#define IPU7_GLOBAL_INTERRUPT_MASK 0x8 +#define IPU7_TLBID_HASH_ENABLE_63_32 0x34 +#define IPU7_TLBID_HASH_ENABLE_127_96 0x3c +#define IPU7_BAR2_MISC_CONFIG 0x64 + #endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index c24e18520de6..4af0b068e759 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -34,6 +34,7 @@ #include "ipu6-platform-regs.h" #define IPU6_PCI_BAR 0 +#define IPU7_PCI_PBBAR 4 struct ipu6_cell_program { u32 magic_number; @@ -209,6 +210,7 @@ static struct ipu6_psys_internal_pdata psys_ipdata = { }; static const struct ipu6_buttress_ctrl isys_buttress_ctrl = { + .subsys_id = IPU_ISYS, .ratio = IPU6_IS_FREQ_CTL_DEFAULT_RATIO, .qos_floor = IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, .freq_ctl = IPU6_BUTTRESS_REG_IS_FREQ_CTL, @@ -219,6 +221,7 @@ static const struct ipu6_buttress_ctrl isys_buttress_ctrl = { }; static const struct ipu6_buttress_ctrl psys_buttress_ctrl = { + .subsys_id = IPU_PSYS, .ratio = IPU6_PS_FREQ_CTL_DEFAULT_RATIO, .qos_floor = IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, .freq_ctl = IPU6_BUTTRESS_REG_PS_FREQ_CTL, @@ -228,6 +231,28 @@ static const struct ipu6_buttress_ctrl psys_buttress_ctrl = { .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, }; +static const struct ipu6_buttress_ctrl ipu7_isys_buttress_ctrl = { + .subsys_id = IPU_ISYS, + .ratio = IPU7_IS_FREQ_CTL_DEFAULT_RATIO, + .qos_floor = 0, + .freq_ctl = IPU7_BUTTRESS_REG_IS_WORKPOINT_REQ, + .pwr_sts_shift = IPU7_BUTTRESS_PWR_STATE_IS_PWR_SHIFT, + .pwr_sts_mask = IPU7_BUTTRESS_PWR_STATE_IS_PWR_MASK, + .pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE, + .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, +}; + +static const struct ipu6_buttress_ctrl ipu7_psys_buttress_ctrl = { + .subsys_id = IPU_PSYS, + .ratio = IPU7_PS_FREQ_CTL_DEFAULT_RATIO, + .qos_floor = 0, + .freq_ctl = IPU7_BUTTRESS_REG_PS_WORKPOINT_REQ, + .pwr_sts_shift = IPU7_BUTTRESS_PWR_STATE_PS_PWR_SHIFT, + .pwr_sts_mask = IPU7_BUTTRESS_PWR_STATE_PS_PWR_MASK, + .pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE, + .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, +}; + static const struct ipu6_buttress_registers ipu6_buttress_regs = { /* Registers */ .irq_status = BUTTRESS_REG_ISR_STATUS, @@ -258,6 +283,35 @@ static const struct ipu6_buttress_registers ipu6_buttress_regs = { .irq_sai = BUTTRESS_ISR_SAI_VIOLATION, }; +static const struct ipu6_buttress_registers ipu7_buttress_regs = { + /* Registers */ + .irq_status = IPU7_BUTTRESS_REG_IRQ_STATUS, + .irq_clear = IPU7_BUTTRESS_REG_IRQ_CLEAR, + .irq_enable = IPU7_BUTTRESS_REG_IRQ_ENABLE, + .pwr_status = IPU7_BUTTRESS_REG_PWR_STATUS, + .security_ctl = IPU7_BUTTRESS_REG_SECURITY_CTL, + .fw_reset_ctl = IPU7_BUTTRESS_REG_FW_RESET_CTL, + .fabric_cmd = IPU7_BUTTRESS_REG_TSC_CMD, + .tsw_ctl = IPU7_BUTTRESS_REG_TSC_CTL, + .tsc_lo = IPU7_BUTTRESS_REG_TSC_LO, + .wdt = IPU7_BUTTRESS_REG_IDLE_WDT, + .csr_in = IPU7_BUTTRESS_REG_CSE2IUCSR, + .csr_out = IPU7_BUTTRESS_REG_IU2CSECSR, + .db0_in = IPU7_BUTTRESS_REG_CSE2IUDB0, + .db0_out = IPU7_BUTTRESS_REG_IU2CSEDB0, + .data0_in = IPU7_BUTTRESS_REG_CSE2IUDATA0, + .data0_out = IPU7_BUTTRESS_REG_IU2CSEDATA0, + + /* Bitmasks */ + .irq_is = IPU7_BUTTRESS_IRQ_IS_IRQ, + .irq_ps = IPU7_BUTTRESS_IRQ_PS_IRQ, + .irq_all = IPU7_BUTTRESS_IRQS, + .irq_events = IPU7_BUTTRESS_IRQS, + .irq_cse_ipc = IPU7_BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING, + .irq_exec_done = IPU7_BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE, + .irq_sai = IPU7_BUTTRESS_IRQ_SAI_VIOLATION, +}; + static void ipu6_pkg_dir_configure_spc(struct ipu6_device *isp, const struct ipu6_hw_variants *hw_variant, @@ -532,24 +586,6 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (!isp) return -ENOMEM; - isp->pdev = pdev; - INIT_LIST_HEAD(&isp->devices); - - ret = pcim_enable_device(pdev); - if (ret) - return dev_err_probe(dev, ret, "Enable PCI device failed\n"); - - phys = pci_resource_start(pdev, IPU6_PCI_BAR); - dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys); - - isp->base = pcim_iomap_region(pdev, IPU6_PCI_BAR, IPU6_NAME); - if (IS_ERR(isp->base)) - return dev_err_probe(dev, PTR_ERR(isp->base), - "Failed to I/O mem remapping\n"); - - pci_set_drvdata(pdev, isp); - pci_set_master(pdev); - isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt); isp->buttress.regs = &ipu6_buttress_regs; @@ -577,12 +613,44 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) isp->hw_ver = IPU_VERSION_6EP_MTL; isp->cpd_fw_name = IPU6EPMTL_FIRMWARE_NAME; break; + case PCI_DEVICE_ID_INTEL_IPU7: + isp->hw_ver = IPU_VERSION_7; + isp->cpd_fw_name = IPU7_FIRMWARE_NAME; + isp->buttress.regs = &ipu7_buttress_regs; + break; default: return dev_err_probe(dev, -ENODEV, "Unsupported IPU6 device %x\n", id->device); } + isp->pdev = pdev; + INIT_LIST_HEAD(&isp->devices); + + ret = pcim_enable_device(pdev); + if (ret) + return dev_err_probe(dev, ret, "Enable PCI device failed\n"); + + phys = pci_resource_start(pdev, IPU6_PCI_BAR); + dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys); + + isp->base = pcim_iomap_region(pdev, IPU6_PCI_BAR, IPU6_NAME); + if (IS_ERR(isp->base)) + return dev_err_probe(dev, PTR_ERR(isp->base), + "Failed to I/O mem remapping\n"); + + if (IS_IPU7(isp)) { + isp->pb_base = pcim_iomap_region(pdev, IPU7_PCI_PBBAR, + IPU6_NAME); + if (IS_ERR(isp->pb_base)) + return dev_err_probe(dev, PTR_ERR(isp->pb_base), + "I/O remapping PB BAR %u failed\n", + IPU7_PCI_PBBAR); + } + + pci_set_drvdata(pdev, isp); + pci_set_master(pdev); + ipu6_internal_pdata_init(isp); isys_base = isp->base + isys_ipdata.hw_variant.offset; diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index dc98a787612f..a14b067f169f 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -22,6 +22,7 @@ struct ipu6_bus_device; #define IPU6_FIRMWARE_NAME "intel/ipu/ipu6_fw.bin" #define IPU6EPMTL_FIRMWARE_NAME "intel/ipu/ipu6epmtl_fw.bin" #define IPU6EPADLN_FIRMWARE_NAME "intel/ipu/ipu6epadln_fw.bin" +#define IPU7_FIRMWARE_NAME "intel/ipu/ipu7_fw.bin" #define IPU_VERSION_6 BIT(0) /* TGL */ #define IPU_VERSION_6SE BIT(1) /* JSL */ @@ -64,6 +65,7 @@ struct ipu6_device { u32 cpd_metadata_cmpnt_size; void __iomem *base; + void __iomem *pb_base; bool need_ipc_reset; bool secure_mode; u8 hw_ver; diff --git a/include/media/ipu6-pci-table.h b/include/media/ipu6-pci-table.h index 0899d9d2f978..cd5b941fb96c 100644 --- a/include/media/ipu6-pci-table.h +++ b/include/media/ipu6-pci-table.h @@ -14,6 +14,8 @@ #define PCI_DEVICE_ID_INTEL_IPU6EP_ADLN 0x462e #define PCI_DEVICE_ID_INTEL_IPU6EP_RPLP 0xa75d #define PCI_DEVICE_ID_INTEL_IPU6EP_MTL 0x7d19 +#define PCI_DEVICE_ID_INTEL_IPU7 0x645d +#define PCI_DEVICE_ID_INTEL_IPU7P5 0xb05d static const struct pci_device_id ipu6_pci_tbl[] = { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6) }, From bb5d82a96d0b135720a5542cc235646578931f61 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:05 +0300 Subject: [PATCH 060/596] media: ipu6: Prepare mmu driver for hw variation We are about to add support for ipu7 mmu. Split ipu6 mmu hardware specific and common code to separate files. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/Makefile | 1 + drivers/media/pci/intel/ipu6/ipu6-dma.c | 4 +- drivers/media/pci/intel/ipu6/ipu6-mmu-hw.c | 296 +++++++++++++++++++++ drivers/media/pci/intel/ipu6/ipu6-mmu.c | 120 +-------- drivers/media/pci/intel/ipu6/ipu6-mmu.h | 149 ++++++++++- drivers/media/pci/intel/ipu6/ipu6.c | 125 +-------- drivers/media/pci/intel/ipu6/ipu6.h | 132 +-------- 7 files changed, 456 insertions(+), 371 deletions(-) create mode 100644 drivers/media/pci/intel/ipu6/ipu6-mmu-hw.c diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile index a821b0a1567f..2106ab84acde 100644 --- a/drivers/media/pci/intel/ipu6/Makefile +++ b/drivers/media/pci/intel/ipu6/Makefile @@ -4,6 +4,7 @@ intel-ipu6-y := ipu6.o \ ipu6-bus.o \ ipu6-dma.o \ ipu6-mmu.o \ + ipu6-mmu-hw.o \ ipu6-buttress.o \ ipu6-cpd.o \ ipu6-fw-com.o diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.c b/drivers/media/pci/intel/ipu6/ipu6-dma.c index fdcdb15b073c..6e8295d9c3a3 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-dma.c +++ b/drivers/media/pci/intel/ipu6/ipu6-dma.c @@ -286,7 +286,7 @@ void ipu6_dma_free(struct ipu6_bus_device *sys, size_t size, void *vaddr, __free_buffer(pages, size, attrs); - mmu->tlb_invalidate(mmu); + mmu->ops->tlb_invalidate(mmu); __free_iova(&mmu->dmap->iovad, iova); @@ -366,7 +366,7 @@ void ipu6_dma_unmap_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), PFN_PHYS(iova_size(iova))); - mmu->tlb_invalidate(mmu); + mmu->ops->tlb_invalidate(mmu); __free_iova(&mmu->dmap->iovad, iova); } EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sg, "INTEL_IPU6"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu-hw.c b/drivers/media/pci/intel/ipu6/ipu6-mmu-hw.c new file mode 100644 index 000000000000..2b395fbd5969 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu-hw.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Intel Corporation + */ +#include + +#include +#include +#include +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-dma.h" +#include "ipu6-mmu.h" +#include "ipu6-platform-regs.h" + +#define ISP_PAGE_SHIFT 12 +#define ISP_PAGE_SIZE BIT(ISP_PAGE_SHIFT) +#define ISP_PAGE_MASK (~(ISP_PAGE_SIZE - 1)) + +#define ISP_L1PT_SHIFT 22 +#define ISP_L1PT_MASK (~((1U << ISP_L1PT_SHIFT) - 1)) + +#define ISP_L2PT_SHIFT 12 +#define ISP_L2PT_MASK (~(ISP_L1PT_MASK | (~(ISP_PAGE_MASK)))) + +#define ISP_L1PT_PTES 1024 +#define ISP_L2PT_PTES 1024 + +#define ISP_PADDR_SHIFT 12 + +#define REG_TLB_INVALIDATE 0x0000 + +#define REG_L1_PHYS 0x0004 /* 27-bit pfn */ +#define REG_INFO 0x0008 + +#define TBL_PHYS_ADDR(a) ((phys_addr_t)(a) << ISP_PADDR_SHIFT) + +static struct ipu6_mmu_hw ipu6_isys_mmu_hwdata[] = { + { + .offset = IPU6_ISYS_IOMMU0_OFFSET, + .info_bits = IPU6_INFO_REQUEST_DESTINATION_IOSF, + .nr_l1streams = 16, + .l1_block_sz = { + 3, 8, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 1, 1 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_ISYS_IOMMU1_OFFSET, + .info_bits = 0, + .nr_l1streams = 16, + .l1_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 1, 1, 4 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_ISYS_IOMMUI_OFFSET, + .info_bits = 0, + .nr_l1streams = 0, + .nr_l2streams = 0, + .insert_read_before_invalidate = false, + }, +}; + +static struct ipu6_mmu_hw ipu6_psys_mmu_hwdata[] = { + { + .offset = IPU6_PSYS_IOMMU0_OFFSET, + .info_bits = + IPU6_INFO_REQUEST_DESTINATION_IOSF, + .nr_l1streams = 16, + .l1_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_PSYS_IOMMU1_OFFSET, + .info_bits = 0, + .nr_l1streams = 32, + .l1_block_sz = { + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 10, + 5, 4, 14, 6, 4, 14, 6, 4, 8, + 4, 2, 1, 1, 1, 1, 14 + }, + .nr_l2streams = 32, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_PSYS_IOMMU1R_OFFSET, + .info_bits = 0, + .nr_l1streams = 16, + .l1_block_sz = { + 1, 4, 4, 4, 4, 16, 8, 4, 32, + 16, 16, 2, 2, 2, 1, 12 + }, + .nr_l2streams = 16, + .l2_block_sz = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2 + }, + .insert_read_before_invalidate = false, + .l1_stream_id_reg_offset = + IPU6_MMU_L1_STREAM_ID_REG_OFFSET, + .l2_stream_id_reg_offset = + IPU6_MMU_L2_STREAM_ID_REG_OFFSET, + }, + { + .offset = IPU6_PSYS_IOMMUI_OFFSET, + .info_bits = 0, + .nr_l1streams = 0, + .nr_l2streams = 0, + .insert_read_before_invalidate = false, + }, +}; + +struct ipu6_mmu_hwdata { + struct ipu6_mmu_hw *hwdata; + unsigned int nr_mmus; +}; + +static const struct ipu6_mmu_hwdata ipu6_mmu_hwdata_lookup[IPU_SUBSYS_NUM] = { + [IPU_PSYS] = { + .hwdata = ipu6_psys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu6_psys_mmu_hwdata), + }, + [IPU_ISYS] = { + .hwdata = ipu6_isys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu6_isys_mmu_hwdata), + }, +}; + +static void __ipu6_tlb_invalidate(struct ipu6_mmu *mmu) +{ + struct ipu6_mmu_hw *mmu_hw = mmu->ipu6_mmu_hw; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&mmu->ready_lock, flags); + if (!mmu->ready) { + spin_unlock_irqrestore(&mmu->ready_lock, flags); + return; + } + + for (i = 0; i < mmu->nr_mmus; i++) { + /* + * To avoid the HW bug induced dead lock in some of the IPU6 + * MMUs on successive invalidate calls, we need to first do a + * read to the page table base before writing the invalidate + * register. MMUs which need to implement this WA, will have + * the insert_read_before_invalidate flags set as true. + * Disregard the return value of the read. + */ + if (mmu_hw[i].insert_read_before_invalidate) + readl(mmu_hw[i].base + REG_L1_PHYS); + + writel(0xffffffff, mmu_hw[i].base + REG_TLB_INVALIDATE); + /* + * The TLB invalidation is a "single cycle" (IOMMU clock cycles) + * When the actual MMIO write reaches the IPU6 TLB Invalidate + * register, wmb() will force the TLB invalidate out if the CPU + * attempts to update the IOMMU page table (or sooner). + */ + wmb(); + } + spin_unlock_irqrestore(&mmu->ready_lock, flags); +} + +static int __ipu6_mmu_hw_init(struct ipu6_mmu *mmu) +{ + struct ipu6_mmu_info *mmu_info = mmu->dmap->mmu_info; + struct ipu6_mmu_hw *mmu_hw = mmu->ipu6_mmu_hw; + + /* Initialise the each MMU HW block */ + for (unsigned int i = 0; i < mmu->nr_mmus; i++) { + unsigned int j; + u16 block_addr; + + /* Write page table address per MMU */ + writel((phys_addr_t)mmu_info->l1_pt_dma, + mmu_hw[i].base + REG_L1_PHYS); + + /* Set info bits per MMU */ + writel(mmu_hw[i].info_bits, mmu_hw[i].base + REG_INFO); + + /* Configure MMU TLB stream configuration for L1 */ + for (j = 0, block_addr = 0; j < mmu_hw[i].nr_l1streams; + block_addr += mmu_hw[i].l1_block_sz[j], j++) { + if (block_addr > IPU6_MAX_LI_BLOCK_ADDR) { + dev_err(mmu->dev, "invalid L1 configuration\n"); + return -EINVAL; + } + + /* Write block start address for each streams */ + writel(block_addr, mmu_hw[i].base + + mmu_hw[i].l1_stream_id_reg_offset + 4 * j); + } + + /* Configure MMU TLB stream configuration for L2 */ + for (j = 0, block_addr = 0; j < mmu_hw[i].nr_l2streams; + block_addr += mmu_hw[i].l2_block_sz[j], j++) { + if (block_addr > IPU6_MAX_L2_BLOCK_ADDR) { + dev_err(mmu->dev, "invalid L2 configuration\n"); + return -EINVAL; + } + + writel(block_addr, mmu_hw[i].base + + mmu_hw[i].l2_stream_id_reg_offset + 4 * j); + } + } + + return 0; +} + +static int __ipu6_mmu_init_hw_data(struct ipu6_mmu *mmu, struct device *dev, + void __iomem *base) +{ + const struct ipu6_mmu_hwdata *lookup; + struct ipu6_mmu_hw *mmu_hw, *src; + unsigned int i, nr_mmus; + + if (mmu->mmid >= IPU_SUBSYS_NUM) + return -EINVAL; + + lookup = &ipu6_mmu_hwdata_lookup[mmu->mmid]; + src = lookup->hwdata; + nr_mmus = lookup->nr_mmus; + + mmu_hw = devm_kcalloc(dev, nr_mmus, sizeof(*mmu_hw), GFP_KERNEL); + if (!mmu_hw) + return -ENOMEM; + + for (i = 0; i < nr_mmus; i++) { + if (src[i].nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS || + src[i].nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS) + return -EINVAL; + + mmu_hw[i] = src[i]; + mmu_hw[i].base = base + src[i].offset; + } + + mmu->nr_mmus = nr_mmus; + mmu->ipu6_mmu_hw = mmu_hw; + + return 0; +} + +const struct ipu6_mmu_hw_ops ipu6_mmu_ops = { + .init_hw_data = __ipu6_mmu_init_hw_data, + .hw_init = __ipu6_mmu_hw_init, + .tlb_invalidate = __ipu6_tlb_invalidate, +}; diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c index 6b1a49ebbcc2..98ac59d00b78 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.c +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -13,10 +12,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -51,42 +48,6 @@ #define TBL_PHYS_ADDR(a) ((phys_addr_t)(a) << ISP_PADDR_SHIFT) -static void tlb_invalidate(struct ipu6_mmu *mmu) -{ - unsigned long flags; - unsigned int i; - - spin_lock_irqsave(&mmu->ready_lock, flags); - if (!mmu->ready) { - spin_unlock_irqrestore(&mmu->ready_lock, flags); - return; - } - - for (i = 0; i < mmu->nr_mmus; i++) { - /* - * To avoid the HW bug induced dead lock in some of the IPU6 - * MMUs on successive invalidate calls, we need to first do a - * read to the page table base before writing the invalidate - * register. MMUs which need to implement this WA, will have - * the insert_read_before_invalidate flags set as true. - * Disregard the return value of the read. - */ - if (mmu->mmu_hw[i].insert_read_before_invalidate) - readl(mmu->mmu_hw[i].base + REG_L1_PHYS); - - writel(0xffffffff, mmu->mmu_hw[i].base + - REG_TLB_INVALIDATE); - /* - * The TLB invalidation is a "single cycle" (IOMMU clock cycles) - * When the actual MMIO write reaches the IPU6 TLB Invalidate - * register, wmb() will force the TLB invalidate out if the CPU - * attempts to update the IOMMU page table (or sooner). - */ - wmb(); - } - spin_unlock_irqrestore(&mmu->ready_lock, flags); -} - #ifdef DEBUG static void page_table_dump(struct ipu6_mmu_info *mmu_info) { @@ -472,55 +433,14 @@ static int allocate_trash_buffer(struct ipu6_mmu *mmu) int ipu6_mmu_hw_init(struct ipu6_mmu *mmu) { - struct ipu6_mmu_info *mmu_info; unsigned long flags; - unsigned int i; + int ret; - mmu_info = mmu->dmap->mmu_info; - - /* Initialise the each MMU HW block */ - for (i = 0; i < mmu->nr_mmus; i++) { - struct ipu6_mmu_hw *mmu_hw = &mmu->mmu_hw[i]; - unsigned int j; - u16 block_addr; - - /* Write page table address per MMU */ - writel((phys_addr_t)mmu_info->l1_pt_dma, - mmu->mmu_hw[i].base + REG_L1_PHYS); - - /* Set info bits per MMU */ - writel(mmu->mmu_hw[i].info_bits, - mmu->mmu_hw[i].base + REG_INFO); - - /* Configure MMU TLB stream configuration for L1 */ - for (j = 0, block_addr = 0; j < mmu_hw->nr_l1streams; - block_addr += mmu->mmu_hw[i].l1_block_sz[j], j++) { - if (block_addr > IPU6_MAX_LI_BLOCK_ADDR) { - dev_err(mmu->dev, "invalid L1 configuration\n"); - return -EINVAL; - } - - /* Write block start address for each streams */ - writel(block_addr, mmu_hw->base + - mmu_hw->l1_stream_id_reg_offset + 4 * j); - } - - /* Configure MMU TLB stream configuration for L2 */ - for (j = 0, block_addr = 0; j < mmu_hw->nr_l2streams; - block_addr += mmu->mmu_hw[i].l2_block_sz[j], j++) { - if (block_addr > IPU6_MAX_L2_BLOCK_ADDR) { - dev_err(mmu->dev, "invalid L2 configuration\n"); - return -EINVAL; - } - - writel(block_addr, mmu_hw->base + - mmu_hw->l2_stream_id_reg_offset + 4 * j); - } - } + ret = mmu->ops->hw_init(mmu); + if (ret) + return ret; if (!mmu->trash_page) { - int ret; - mmu->trash_page = alloc_page(GFP_KERNEL); if (!mmu->trash_page) { dev_err(mmu->dev, "insufficient memory for trash buffer\n"); @@ -746,44 +666,26 @@ static void ipu6_mmu_destroy(struct ipu6_mmu *mmu) } struct ipu6_mmu *ipu6_mmu_init(struct device *dev, - void __iomem *base, int mmid, - const struct ipu6_hw_variants *hw) + void __iomem *base, int mmid) { struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev)); - struct ipu6_mmu_hw *mmu_hw; struct ipu6_mmu *mmu; - unsigned int i; - - if (hw->nr_mmus > IPU6_MMU_MAX_DEVICES) - return ERR_PTR(-EINVAL); - - mmu_hw = devm_kcalloc(dev, sizeof(*mmu_hw), hw->nr_mmus, GFP_KERNEL); - if (!mmu_hw) - return ERR_PTR(-ENOMEM); - - for (i = 0; i < hw->nr_mmus; i++) { - const struct ipu6_mmu_hw *src_mmu = &hw->mmu_hw[i]; - - if (src_mmu->nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS || - src_mmu->nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS) - return ERR_PTR(-EINVAL); - - mmu_hw[i] = *src_mmu; - mmu_hw[i].base = base + src_mmu->offset; - } + int ret; mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL); if (!mmu) return ERR_PTR(-ENOMEM); + mmu->ops = &ipu6_mmu_ops; mmu->mmid = mmid; - mmu->mmu_hw = mmu_hw; - mmu->nr_mmus = hw->nr_mmus; - mmu->tlb_invalidate = tlb_invalidate; mmu->ready = false; INIT_LIST_HEAD(&mmu->vma_list); spin_lock_init(&mmu->ready_lock); + ret = mmu->ops->init_hw_data(mmu, dev, base); + if (ret) + return ERR_PTR(ret); + mmu->dmap = alloc_dma_mapping(isp); if (!mmu->dmap) { dev_err(dev, "can't alloc dma mapping\n"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.h b/drivers/media/pci/intel/ipu6/ipu6-mmu.h index 35cac80d2d4e..a741f1c0e284 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.h +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (C) 2013--2024 Intel Corporation */ +/* Copyright (C) 2013--2026 Intel Corporation */ #ifndef IPU6_MMU_H #define IPU6_MMU_H @@ -10,7 +10,6 @@ struct device; struct page; -struct ipu6_hw_variants; struct ipu6_mmu_info { struct device *dev; @@ -32,10 +31,147 @@ struct ipu6_mmu_info { struct ipu6_dma_mapping *dmap; }; +/* + * MMU Invalidation HW bug workaround by ZLW mechanism + * + * Old IPU6 MMUV2 has a bug in the invalidation mechanism which might result in + * wrong translation or replication of the translation. This will cause data + * corruption. So we cannot directly use the MMU V2 invalidation registers + * to invalidate the MMU. Instead, whenever an invalidate is called, we need to + * clear the TLB by evicting all the valid translations by filling it with trash + * buffer (which is guaranteed not to be used by any other processes). ZLW is + * used to fill the L1 and L2 caches with the trash buffer translations. ZLW + * or Zero length write, is pre-fetch mechanism to pre-fetch the pages in + * advance to the L1 and L2 caches without triggering any memory operations. + * + * In MMU V2, L1 -> 16 streams and 64 blocks, maximum 16 blocks per stream + * One L1 block has 16 entries, hence points to 16 * 4K pages + * L2 -> 16 streams and 32 blocks. 2 blocks per streams + * One L2 block maps to 1024 L1 entries, hence points to 4MB address range + * 2 blocks per L2 stream means, 1 stream points to 8MB range + * + * As we need to clear the caches and 8MB being the biggest cache size, we need + * to have trash buffer which points to 8MB address range. As these trash + * buffers are not used for any memory transactions, we need only the least + * amount of physical memory. So we reserve 8MB IOVA address range but only + * one page is reserved from physical memory. Each of this 8MB IOVA address + * range is then mapped to the same physical memory page. + */ +/* One L2 entry maps 1024 L1 entries and one L1 entry per page */ +#define IPU6_MMUV2_L2_RANGE (1024 * PAGE_SIZE) +/* Max L2 blocks per stream */ +#define IPU6_MMUV2_MAX_L2_BLOCKS 2 +/* Max L1 blocks per stream */ +#define IPU6_MMUV2_MAX_L1_BLOCKS 16 +#define IPU6_MMUV2_TRASH_RANGE (IPU6_MMUV2_L2_RANGE * IPU6_MMUV2_MAX_L2_BLOCKS) +/* Entries per L1 block */ +#define MMUV2_ENTRIES_PER_L1_BLOCK 16 +#define MMUV2_TRASH_L1_BLOCK_OFFSET (MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE) +#define MMUV2_TRASH_L2_BLOCK_OFFSET IPU6_MMUV2_L2_RANGE + +/* + * In some of the IPU6 MMUs, there is provision to configure L1 and L2 page + * table caches. Both these L1 and L2 caches are divided into multiple sections + * called streams. There is maximum 16 streams for both caches. Each of these + * sections are subdivided into multiple blocks. When nr_l1streams = 0 and + * nr_l2streams = 0, means the MMU is of type MMU_V1 and do not support + * L1/L2 page table caches. + * + * L1 stream per block sizes are configurable and varies per usecase. + * L2 has constant block sizes - 2 blocks per stream. + * + * MMU1 support pre-fetching of the pages to have less cache lookup misses. To + * enable the pre-fetching, MMU1 AT (Address Translator) device registers + * need to be configured. + * + * There are four types of memory accesses which requires ZLW configuration. + * ZLW(Zero Length Write) is a mechanism to enable VT-d pre-fetching on IOMMU. + * + * 1. Sequential Access or 1D mode + * Set ZLW_EN -> 1 + * set ZLW_PAGE_CROSS_1D -> 1 + * Set ZLW_N to "N" pages so that ZLW will be inserte N pages ahead where + * N is pre-defined and hardcoded in the platform data + * Set ZLW_2D -> 0 + * + * 2. ZLW 2D mode + * Set ZLW_EN -> 1 + * set ZLW_PAGE_CROSS_1D -> 1, + * Set ZLW_N -> 0 + * Set ZLW_2D -> 1 + * + * 3. ZLW Enable (no 1D or 2D mode) + * Set ZLW_EN -> 1 + * set ZLW_PAGE_CROSS_1D -> 0, + * Set ZLW_N -> 0 + * Set ZLW_2D -> 0 + * + * 4. ZLW disable + * Set ZLW_EN -> 0 + * set ZLW_PAGE_CROSS_1D -> 0, + * Set ZLW_N -> 0 + * Set ZLW_2D -> 0 + * + * To configure the ZLW for the above memory access, four registers are + * available. Hence to track these four settings, we have the following entries + * in the struct ipu6_mmu_hw. Each of these entries are per stream and + * available only for the L1 streams. + * + * a. l1_zlw_en -> To track zlw enabled per stream (ZLW_EN) + * b. l1_zlw_1d_mode -> Track 1D mode per stream. ZLW inserted at page boundary + * c. l1_ins_zlw_ahead_pages -> to track how advance the ZLW need to be inserted + * Insert ZLW request N pages ahead address. + * d. l1_zlw_2d_mode -> To track 2D mode per stream (ZLW_2D) + * + * + * Currently L1/L2 streams, blocks, AT ZLW configurations etc. are pre-defined + * as per the usecase specific calculations. Any change to this pre-defined + * table has to happen in sync with IPU6 FW. + */ + +struct ipu6_mmu_hw { + union { + unsigned long offset; + void __iomem *base; + }; + u32 info_bits; + u8 nr_l1streams; + /* + * L1 has variable blocks per stream - total of 64 blocks and maximum of + * 16 blocks per stream. Configurable by using the block start address + * per stream. Block start address is calculated from the block size + */ + u8 l1_block_sz[IPU6_MMU_MAX_TLB_L1_STREAMS]; + /* Is ZLW is enabled in each stream */ + bool l1_zlw_en[IPU6_MMU_MAX_TLB_L1_STREAMS]; + bool l1_zlw_1d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; + u8 l1_ins_zlw_ahead_pages[IPU6_MMU_MAX_TLB_L1_STREAMS]; + bool l1_zlw_2d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; + + u32 l1_stream_id_reg_offset; + u32 l2_stream_id_reg_offset; + + u8 nr_l2streams; + /* + * L2 has fixed 2 blocks per stream. Block address is calculated + * from the block size + */ + u8 l2_block_sz[IPU6_MMU_MAX_TLB_L2_STREAMS]; + /* flag to track if WA is needed for successive invalidate HW bug */ + bool insert_read_before_invalidate; +}; + +struct ipu6_mmu_hw_ops { + int (*init_hw_data)(struct ipu6_mmu *mmu, struct device *dev, + void __iomem *base); + int (*hw_init)(struct ipu6_mmu *mmu); + void (*tlb_invalidate)(struct ipu6_mmu *mmu); +}; + struct ipu6_mmu { struct list_head node; - struct ipu6_mmu_hw *mmu_hw; + struct ipu6_mmu_hw *ipu6_mmu_hw; unsigned int nr_mmus; unsigned int mmid; @@ -52,12 +188,13 @@ struct ipu6_mmu { bool ready; spinlock_t ready_lock; /* Serialize access to bool ready */ - void (*tlb_invalidate)(struct ipu6_mmu *mmu); + const struct ipu6_mmu_hw_ops *ops; }; +extern const struct ipu6_mmu_hw_ops ipu6_mmu_ops; + struct ipu6_mmu *ipu6_mmu_init(struct device *dev, - void __iomem *base, int mmid, - const struct ipu6_hw_variants *hw); + void __iomem *base, int mmid); void ipu6_mmu_cleanup(struct ipu6_mmu *mmu); int ipu6_mmu_hw_init(struct ipu6_mmu *mmu); void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu); diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 4af0b068e759..51174cafb1b4 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -74,54 +74,6 @@ struct ipu6_cell_program { static struct ipu6_isys_internal_pdata isys_ipdata = { .hw_variant = { .offset = IPU6_UNIFIED_OFFSET, - .nr_mmus = 3, - .mmu_hw = { - { - .offset = IPU6_ISYS_IOMMU0_OFFSET, - .info_bits = IPU6_INFO_REQUEST_DESTINATION_IOSF, - .nr_l1streams = 16, - .l1_block_sz = { - 3, 8, 2, 2, 2, 2, 2, 2, 1, 1, - 1, 1, 1, 1, 1, 1 - }, - .nr_l2streams = 16, - .l2_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .insert_read_before_invalidate = false, - .l1_stream_id_reg_offset = - IPU6_MMU_L1_STREAM_ID_REG_OFFSET, - .l2_stream_id_reg_offset = - IPU6_MMU_L2_STREAM_ID_REG_OFFSET, - }, - { - .offset = IPU6_ISYS_IOMMU1_OFFSET, - .info_bits = 0, - .nr_l1streams = 16, - .l1_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 1, 1, 4 - }, - .nr_l2streams = 16, - .l2_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .insert_read_before_invalidate = false, - .l1_stream_id_reg_offset = - IPU6_MMU_L1_STREAM_ID_REG_OFFSET, - .l2_stream_id_reg_offset = - IPU6_MMU_L2_STREAM_ID_REG_OFFSET, - }, - { - .offset = IPU6_ISYS_IOMMUI_OFFSET, - .info_bits = 0, - .nr_l1streams = 0, - .nr_l2streams = 0, - .insert_read_before_invalidate = false, - }, - }, .cdc_fifos = 3, .cdc_fifo_threshold = {6, 8, 2}, .dmem_offset = IPU6_ISYS_DMEM_OFFSET, @@ -133,78 +85,6 @@ static struct ipu6_isys_internal_pdata isys_ipdata = { static struct ipu6_psys_internal_pdata psys_ipdata = { .hw_variant = { .offset = IPU6_UNIFIED_OFFSET, - .nr_mmus = 4, - .mmu_hw = { - { - .offset = IPU6_PSYS_IOMMU0_OFFSET, - .info_bits = - IPU6_INFO_REQUEST_DESTINATION_IOSF, - .nr_l1streams = 16, - .l1_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .nr_l2streams = 16, - .l2_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .insert_read_before_invalidate = false, - .l1_stream_id_reg_offset = - IPU6_MMU_L1_STREAM_ID_REG_OFFSET, - .l2_stream_id_reg_offset = - IPU6_MMU_L2_STREAM_ID_REG_OFFSET, - }, - { - .offset = IPU6_PSYS_IOMMU1_OFFSET, - .info_bits = 0, - .nr_l1streams = 32, - .l1_block_sz = { - 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 10, - 5, 4, 14, 6, 4, 14, 6, 4, 8, - 4, 2, 1, 1, 1, 1, 14 - }, - .nr_l2streams = 32, - .l2_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .insert_read_before_invalidate = false, - .l1_stream_id_reg_offset = - IPU6_MMU_L1_STREAM_ID_REG_OFFSET, - .l2_stream_id_reg_offset = - IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET, - }, - { - .offset = IPU6_PSYS_IOMMU1R_OFFSET, - .info_bits = 0, - .nr_l1streams = 16, - .l1_block_sz = { - 1, 4, 4, 4, 4, 16, 8, 4, 32, - 16, 16, 2, 2, 2, 1, 12 - }, - .nr_l2streams = 16, - .l2_block_sz = { - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2 - }, - .insert_read_before_invalidate = false, - .l1_stream_id_reg_offset = - IPU6_MMU_L1_STREAM_ID_REG_OFFSET, - .l2_stream_id_reg_offset = - IPU6_MMU_L2_STREAM_ID_REG_OFFSET, - }, - { - .offset = IPU6_PSYS_IOMMUI_OFFSET, - .info_bits = 0, - .nr_l1streams = 0, - .nr_l2streams = 0, - .insert_read_before_invalidate = false, - }, - }, .dmem_offset = IPU6_PSYS_DMEM_OFFSET, }, }; @@ -478,7 +358,7 @@ ipu6_isys_init(struct pci_dev *pdev, struct device *parent, "ipu6_bus_initialize_device isys failed\n"); } - isys_adev->mmu = ipu6_mmu_init(dev, base, IPU_ISYS, &ipdata->hw_variant); + isys_adev->mmu = ipu6_mmu_init(dev, base, IPU_ISYS); if (IS_ERR(isys_adev->mmu)) { put_device(&isys_adev->auxdev.dev); return dev_err_cast_probe(dev, isys_adev->mmu, @@ -518,8 +398,7 @@ ipu6_psys_init(struct pci_dev *pdev, struct device *parent, "ipu6_bus_initialize_device psys failed\n"); } - psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, IPU_PSYS, - &ipdata->hw_variant); + psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, IPU_PSYS); if (IS_ERR(psys_adev->mmu)) { put_device(&psys_adev->auxdev.dev); return dev_err_cast_probe(&pdev->dev, psys_adev->mmu, diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index a14b067f169f..debd571b6f6d 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -74,6 +74,7 @@ struct ipu6_device { #define IPU_PSYS 0 #define IPU_ISYS 1 +#define IPU_SUBSYS_NUM 2 #define IPU6_ISYS_NAME "isys" #define IPU6_PSYS_NAME "psys" @@ -121,135 +122,6 @@ struct ipu6_device { #define IPU6_BTRS_ARB_STALL_MODE_VC1 \ IPU6_BTRS_ARB_MODE_TYPE_REARB -/* - * MMU Invalidation HW bug workaround by ZLW mechanism - * - * Old IPU6 MMUV2 has a bug in the invalidation mechanism which might result in - * wrong translation or replication of the translation. This will cause data - * corruption. So we cannot directly use the MMU V2 invalidation registers - * to invalidate the MMU. Instead, whenever an invalidate is called, we need to - * clear the TLB by evicting all the valid translations by filling it with trash - * buffer (which is guaranteed not to be used by any other processes). ZLW is - * used to fill the L1 and L2 caches with the trash buffer translations. ZLW - * or Zero length write, is pre-fetch mechanism to pre-fetch the pages in - * advance to the L1 and L2 caches without triggering any memory operations. - * - * In MMU V2, L1 -> 16 streams and 64 blocks, maximum 16 blocks per stream - * One L1 block has 16 entries, hence points to 16 * 4K pages - * L2 -> 16 streams and 32 blocks. 2 blocks per streams - * One L2 block maps to 1024 L1 entries, hence points to 4MB address range - * 2 blocks per L2 stream means, 1 stream points to 8MB range - * - * As we need to clear the caches and 8MB being the biggest cache size, we need - * to have trash buffer which points to 8MB address range. As these trash - * buffers are not used for any memory transactions, we need only the least - * amount of physical memory. So we reserve 8MB IOVA address range but only - * one page is reserved from physical memory. Each of this 8MB IOVA address - * range is then mapped to the same physical memory page. - */ -/* One L2 entry maps 1024 L1 entries and one L1 entry per page */ -#define IPU6_MMUV2_L2_RANGE (1024 * PAGE_SIZE) -/* Max L2 blocks per stream */ -#define IPU6_MMUV2_MAX_L2_BLOCKS 2 -/* Max L1 blocks per stream */ -#define IPU6_MMUV2_MAX_L1_BLOCKS 16 -#define IPU6_MMUV2_TRASH_RANGE (IPU6_MMUV2_L2_RANGE * IPU6_MMUV2_MAX_L2_BLOCKS) -/* Entries per L1 block */ -#define MMUV2_ENTRIES_PER_L1_BLOCK 16 -#define MMUV2_TRASH_L1_BLOCK_OFFSET (MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE) -#define MMUV2_TRASH_L2_BLOCK_OFFSET IPU6_MMUV2_L2_RANGE - -/* - * In some of the IPU6 MMUs, there is provision to configure L1 and L2 page - * table caches. Both these L1 and L2 caches are divided into multiple sections - * called streams. There is maximum 16 streams for both caches. Each of these - * sections are subdivided into multiple blocks. When nr_l1streams = 0 and - * nr_l2streams = 0, means the MMU is of type MMU_V1 and do not support - * L1/L2 page table caches. - * - * L1 stream per block sizes are configurable and varies per usecase. - * L2 has constant block sizes - 2 blocks per stream. - * - * MMU1 support pre-fetching of the pages to have less cache lookup misses. To - * enable the pre-fetching, MMU1 AT (Address Translator) device registers - * need to be configured. - * - * There are four types of memory accesses which requires ZLW configuration. - * ZLW(Zero Length Write) is a mechanism to enable VT-d pre-fetching on IOMMU. - * - * 1. Sequential Access or 1D mode - * Set ZLW_EN -> 1 - * set ZLW_PAGE_CROSS_1D -> 1 - * Set ZLW_N to "N" pages so that ZLW will be inserte N pages ahead where - * N is pre-defined and hardcoded in the platform data - * Set ZLW_2D -> 0 - * - * 2. ZLW 2D mode - * Set ZLW_EN -> 1 - * set ZLW_PAGE_CROSS_1D -> 1, - * Set ZLW_N -> 0 - * Set ZLW_2D -> 1 - * - * 3. ZLW Enable (no 1D or 2D mode) - * Set ZLW_EN -> 1 - * set ZLW_PAGE_CROSS_1D -> 0, - * Set ZLW_N -> 0 - * Set ZLW_2D -> 0 - * - * 4. ZLW disable - * Set ZLW_EN -> 0 - * set ZLW_PAGE_CROSS_1D -> 0, - * Set ZLW_N -> 0 - * Set ZLW_2D -> 0 - * - * To configure the ZLW for the above memory access, four registers are - * available. Hence to track these four settings, we have the following entries - * in the struct ipu6_mmu_hw. Each of these entries are per stream and - * available only for the L1 streams. - * - * a. l1_zlw_en -> To track zlw enabled per stream (ZLW_EN) - * b. l1_zlw_1d_mode -> Track 1D mode per stream. ZLW inserted at page boundary - * c. l1_ins_zlw_ahead_pages -> to track how advance the ZLW need to be inserted - * Insert ZLW request N pages ahead address. - * d. l1_zlw_2d_mode -> To track 2D mode per stream (ZLW_2D) - * - * - * Currently L1/L2 streams, blocks, AT ZLW configurations etc. are pre-defined - * as per the usecase specific calculations. Any change to this pre-defined - * table has to happen in sync with IPU6 FW. - */ -struct ipu6_mmu_hw { - union { - unsigned long offset; - void __iomem *base; - }; - u32 info_bits; - u8 nr_l1streams; - /* - * L1 has variable blocks per stream - total of 64 blocks and maximum of - * 16 blocks per stream. Configurable by using the block start address - * per stream. Block start address is calculated from the block size - */ - u8 l1_block_sz[IPU6_MMU_MAX_TLB_L1_STREAMS]; - /* Is ZLW is enabled in each stream */ - bool l1_zlw_en[IPU6_MMU_MAX_TLB_L1_STREAMS]; - bool l1_zlw_1d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; - u8 l1_ins_zlw_ahead_pages[IPU6_MMU_MAX_TLB_L1_STREAMS]; - bool l1_zlw_2d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; - - u32 l1_stream_id_reg_offset; - u32 l2_stream_id_reg_offset; - - u8 nr_l2streams; - /* - * L2 has fixed 2 blocks per stream. Block address is calculated - * from the block size - */ - u8 l2_block_sz[IPU6_MMU_MAX_TLB_L2_STREAMS]; - /* flag to track if WA is needed for successive invalidate HW bug */ - bool insert_read_before_invalidate; -}; - struct ipu6_isys_csi2_pdata { void __iomem *base; }; @@ -274,8 +146,6 @@ struct ipu6_isys_internal_tpg_pdata { struct ipu6_hw_variants { unsigned long offset; - u32 nr_mmus; - struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES]; u8 cdc_fifos; u8 cdc_fifo_threshold[IPU6_MAX_VC_IOSF_PORTS]; u32 dmem_offset; From 4304721445e7fa5584f59953e206bc19656520db Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:06 +0300 Subject: [PATCH 061/596] media: ipu6: Add ipu7 mmu support The ipu7 mmu have similar page tables as ipu6, but e.g. register interface is different. Add own driver handling ipu7 mmu specifics. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/Makefile | 1 + drivers/media/pci/intel/ipu6/ipu6-mmu.c | 14 +- drivers/media/pci/intel/ipu6/ipu6-mmu.h | 8 +- drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c | 599 +++++++++++++++++++++ drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h | 155 ++++++ 5 files changed, 773 insertions(+), 4 deletions(-) create mode 100644 drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c create mode 100644 drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile index 2106ab84acde..c0daf0995b22 100644 --- a/drivers/media/pci/intel/ipu6/Makefile +++ b/drivers/media/pci/intel/ipu6/Makefile @@ -5,6 +5,7 @@ intel-ipu6-y := ipu6.o \ ipu6-dma.o \ ipu6-mmu.o \ ipu6-mmu-hw.o \ + ipu7-mmu-hw.o \ ipu6-buttress.o \ ipu6-cpd.o \ ipu6-fw-com.o diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c index 98ac59d00b78..243d438786ba 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.c +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c @@ -473,7 +473,12 @@ static struct ipu6_mmu_info *ipu6_mmu_alloc(struct ipu6_device *isp) if (!mmu_info) return NULL; - mmu_info->aperture_start = 0; + if (IS_IPU7(isp)) + mmu_info->aperture_start = isp->secure_mode ? + IPU7_FW_CODE_REGION_END : IPU7_FW_CODE_REGION_START; + else + mmu_info->aperture_start = 0; + mmu_info->aperture_end = (dma_addr_t)DMA_BIT_MASK(isp->secure_mode ? IPU6_MMU_ADDR_BITS : @@ -532,6 +537,7 @@ EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, "INTEL_IPU6"); static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp) { struct ipu6_dma_mapping *dmap; + unsigned long base_pfn; dmap = kzalloc_obj(*dmap); if (!dmap) @@ -543,7 +549,9 @@ static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp) return NULL; } - init_iova_domain(&dmap->iovad, SZ_4K, 1); + base_pfn = max_t(unsigned long, 1, + PFN_DOWN(dmap->mmu_info->aperture_start)); + init_iova_domain(&dmap->iovad, SZ_4K, base_pfn); dmap->mmu_info->dmap = dmap; dev_dbg(&isp->pdev->dev, "alloc mapping\n"); @@ -676,7 +684,7 @@ struct ipu6_mmu *ipu6_mmu_init(struct device *dev, if (!mmu) return ERR_PTR(-ENOMEM); - mmu->ops = &ipu6_mmu_ops; + mmu->ops = IS_IPU7(isp) ? &ipu7_mmu_ops : &ipu6_mmu_ops; mmu->mmid = mmid; mmu->ready = false; INIT_LIST_HEAD(&mmu->vma_list); diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.h b/drivers/media/pci/intel/ipu6/ipu6-mmu.h index a741f1c0e284..44880478d242 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-mmu.h +++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.h @@ -8,6 +8,8 @@ #include #include +#include "ipu7-mmu-hw.h" + struct device; struct page; @@ -171,7 +173,10 @@ struct ipu6_mmu_hw_ops { struct ipu6_mmu { struct list_head node; - struct ipu6_mmu_hw *ipu6_mmu_hw; + union { + struct ipu6_mmu_hw *ipu6_mmu_hw; + struct ipu7_mmu_hw *ipu7_mmu_hw; + }; unsigned int nr_mmus; unsigned int mmid; @@ -192,6 +197,7 @@ struct ipu6_mmu { }; extern const struct ipu6_mmu_hw_ops ipu6_mmu_ops; +extern const struct ipu6_mmu_hw_ops ipu7_mmu_ops; struct ipu6_mmu *ipu6_mmu_init(struct device *dev, void __iomem *base, int mmid); diff --git a/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c new file mode 100644 index 000000000000..3cd9a818733c --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Intel Corporation + */ + +#include +#include + +#include "ipu6.h" +#include "ipu6-dma.h" +#include "ipu6-mmu.h" + +static struct ipu7_mmu_hw ipu7_isys_mmu_hwdata[] = { + { + .offset = IPU7_IS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7_IS_ZLX_UC_RD_OFFSET, + .uao_offset = IPU7_IS_UAO_UC_RD_OFFSET, + .info_bits = 0x20006701, + .refill = 0x00002726, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { 0x0, 0x8, 0xa }, + .l2_block_sz = { 0x0, 0x2, 0x4 }, + .zlx_nr = IPU7_IS_ZLX_UC_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { 0, 0, 0, 0 }, + .zlx_conf = { 0x0, 0x0, 0x0, 0x0 }, + .uao_p_num = IPU7_IS_UAO_UC_RD_PLANENUM, + .uao_p2tlb = { 0x61, 0x64, 0x65 }, + }, + { + .offset = IPU7_IS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7_IS_ZLX_UC_WR_OFFSET, + .uao_offset = IPU7_IS_UAO_UC_WR_OFFSET, + .info_bits = 0x20006801, + .refill = 0x00002524, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { 0x0, 0x8, 0xa }, + .l2_block_sz = { 0x0, 0x2, 0x4 }, + .zlx_nr = IPU7_IS_ZLX_UC_WR_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 0, 1, 1, 0 }, + .zlx_conf = { 0x0, 0x00010101, 0x00010101 }, + .uao_p_num = IPU7_IS_UAO_UC_WR_PLANENUM, + .uao_p2tlb = { 0x61, 0x62, 0x63 }, + }, + { + .offset = IPU7_IS_MMU_M0_OFFSET, + .zlx_offset = IPU7_IS_ZLX_M0_OFFSET, + .uao_offset = IPU7_IS_UAO_M0_WR_OFFSET, + .info_bits = 0x20006601, + .refill = 0x00002120, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_M0_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_M0_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_M0_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_M0_STREAM_NUM, + .l1_block_sz = { 0x0, 0x3, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10 }, + .l2_block_sz = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe }, + .zlx_nr = IPU7_IS_ZLX_M0_NUM, + .zlx_axi_pool = { 0x00000f10 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1 }, + .zlx_conf = { + 0x00010103, + 0x00010103, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + }, + .uao_p_num = IPU7_IS_UAO_M0_WR_PLANENUM, + .uao_p2tlb = { + 0x00000049, + 0x0000004a, + 0x0000004b, + 0x0000004c, + 0x0000004d, + 0x0000004e, + 0x0000004f, + 0x00000050, + }, + }, + { + .offset = IPU7_IS_MMU_M1_OFFSET, + .zlx_offset = IPU7_IS_ZLX_M1_OFFSET, + .uao_offset = IPU7_IS_UAO_M1_WR_OFFSET, + .info_bits = 0x20006901, + .refill = 0x00002322, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_M1_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_M1_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_M1_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_M1_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x3, 0x6, 0x9, 0xc, + 0xe, 0x10, 0x12, 0x14, 0x16, + 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, + }, + .zlx_nr = IPU7_IS_ZLX_M1_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + .zlx_conf = { + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + }, + .uao_p_num = IPU7_IS_UAO_M1_WR_PLANENUM, + .uao_p2tlb = { + 0x00000051, + 0x00000052, + 0x00000053, + 0x00000054, + 0x00000055, + 0x00000056, + 0x00000057, + 0x00000058, + 0x00000059, + 0x0000005a, + 0x0000005b, + 0x0000005c, + 0x0000005d, + 0x0000005e, + 0x0000005f, + 0x00000060, + }, + }, +}; + +static struct ipu7_mmu_hw ipu7_psys_mmu_hwdata[] = { + { + .name = "PS_FW_RD", + .offset = IPU7_PS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7_PS_ZLX_FW_RD_OFFSET, + .uao_offset = IPU7_PS_UAO_FW_RD_OFFSET, + .info_bits = 0x20004801, + .refill = 0x00002726, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { + 0, 0x8, 0xa, 0xc, 0xd, + 0xf, 0x11, 0x12, 0x13, 0x14, + 0x16, 0x18, 0x19, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, + 0x1e, 0x20, 0x22, 0x24, 0x26, + }, + .zlx_nr = IPU7_PS_ZLX_FW_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + .zlx_conf = { 0x0 }, + .uao_p_num = IPU7_PS_UAO_FW_RD_PLANENUM, + .uao_p2tlb = { + 0x00000036, + 0x0000003d, + 0x0000003e, + 0x00000039, + 0x0000003f, + 0x00000040, + 0x00000041, + 0x0000003a, + 0x0000003b, + 0x00000042, + 0x00000043, + 0x00000044, + 0x0000003c, + }, + }, + { + .offset = IPU7_PS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7_PS_ZLX_FW_WR_OFFSET, + .uao_offset = IPU7_PS_UAO_FW_WR_OFFSET, + .info_bits = 0x20004601, + .refill = 0x00002322, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { + 0, 0x8, 0xa, 0xc, 0xd, + 0xe, 0xf, 0x10, 0x10, 0x10, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + }, + .zlx_nr = IPU7_PS_ZLX_FW_WR_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 0, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, + .zlx_conf = { 0x0, 0x00010101, 0x00010101 }, + .uao_p_num = IPU7_PS_UAO_FW_WR_PLANENUM, + .uao_p2tlb = { 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c }, + }, + { + .offset = IPU7_PS_MMU_SRT_RD_OFFSET, + .zlx_offset = IPU7_PS_ZLX_DATA_RD_OFFSET, + .uao_offset = IPU7_PS_UAO_SRT_RD_OFFSET, + .info_bits = 0x20004701, + .refill = 0x00002120, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_SRT_RD_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_SRT_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_SRT_RD_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_SRT_RD_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x4, 0x6, 0x8, 0xb, + 0xd, 0xf, 0x11, 0x13, 0x15, + 0x17, 0x23, 0x2b, 0x37, 0x3f, + 0x41, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x48, 0x49, 0x4a, 0x4b, + 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x55, 0x57, + 0x59, 0x5b, 0x5d, 0x5f, 0x61, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, + 0x1e, 0x20, 0x22, 0x24, 0x26, + 0x28, 0x2a, 0x2c, 0x2e, 0x30, + 0x32, 0x34, 0x36, 0x38, 0x3a, + 0x3c, 0x3e, 0x40, 0x42, 0x44, + 0x46, 0x48, 0x4a, 0x4c, 0x4e, + }, + .zlx_nr = IPU7_PS_ZLX_DATA_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + .zlx_conf = { + 0x00030303, + 0x00010101, + 0x00010101, + 0x00030202, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00030800, + 0x00030500, + 0x00020101, + 0x00042000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00020400, + 0x00010101, + }, + .uao_p_num = IPU7_PS_UAO_SRT_RD_PLANENUM, + .uao_p2tlb = { + 0x00000022, + 0x00000023, + 0x00000024, + 0x00000025, + 0x00000026, + 0x00000027, + 0x00000028, + 0x00000029, + 0x0000002a, + 0x0000002b, + 0x0000002c, + 0x0000002d, + 0x0000002e, + 0x0000002f, + 0x00000030, + 0x00000031, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0000001e, + 0x0000001f, + 0x00000020, + 0x00000021, + 0x00000032, + 0x00000033, + 0x00000034, + 0x00000035, + }, + }, + { + .offset = IPU7_PS_MMU_SRT_WR_OFFSET, + .zlx_offset = IPU7_PS_ZLX_DATA_WR_OFFSET, + .uao_offset = IPU7_PS_UAO_SRT_WR_OFFSET, + .info_bits = 0x20004501, + .refill = 0x00002120, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_SRT_WR_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_SRT_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_SRT_WR_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_SRT_WR_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x2, 0x6, 0xa, 0xc, + 0xe, 0x10, 0x12, 0x14, 0x16, + 0x18, 0x1a, 0x1c, 0x1e, 0x20, + 0x22, 0x24, 0x26, 0x32, 0x3a, + 0x3c, 0x3e, 0x4a, 0x52, 0x58, + 0x64, 0x6c, 0x72, 0x7e, 0x86, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + 0x91, 0x92, 0x94, 0x96, 0x98, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, + 0x1e, 0x20, 0x22, 0x24, 0x26, + 0x28, 0x2a, 0x2c, 0x2e, 0x30, + 0x32, 0x34, 0x36, 0x38, 0x3a, + 0x3c, 0x3e, 0x40, 0x42, 0x44, + 0x46, 0x48, 0x4a, 0x4c, 0x4e, + }, + .zlx_nr = IPU7_PS_ZLX_DATA_WR_NUM, + .zlx_axi_pool = { 0x00000f50 }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 0, + }, + .zlx_conf = { + 0x00010102, + 0x00030103, + 0x00030103, + 0x00010101, + 0x00010101, + 0x00030101, + 0x00010101, + 0x38010101, + 0x0, + 0x0, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x00010101, + 0x00042000, + 0x00031000, + 0x00010101, + 0x00010101, + 0x00042000, + 0x00031000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00031000, + 0x0, + 0x0, + }, + .uao_p_num = IPU7_PS_UAO_SRT_WR_PLANENUM, + .uao_p2tlb = { + 0x00000000, + 0x00000001, + 0x00000002, + 0x00000003, + 0x00000004, + 0x00000005, + 0x00000006, + 0x00000007, + 0x00000008, + 0x00000009, + 0x0000000a, + 0x0000000b, + 0x0000000c, + 0x0000000d, + 0x0000000e, + 0x0000000f, + 0x00000010, + 0x00000011, + 0x00000012, + 0x00000013, + 0x00000014, + 0x00000015, + 0x00000016, + 0x00000017, + 0x00000018, + 0x00000019, + 0x0000001a, + 0x0000001b, + 0x0000001c, + 0x0000001d, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0000001e, + 0x0000001f, + 0x00000020, + 0x00000021, + }, + }, +}; + +static const struct ipu7_mmu_hwdata ipu7_mmu_hwdata_lookup[IPU_SUBSYS_NUM] = { + [IPU_PSYS] = { + .hwdata = ipu7_psys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu7_psys_mmu_hwdata), + }, + [IPU_ISYS] = { + .hwdata = ipu7_isys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu7_isys_mmu_hwdata), + }, +}; + +static void __ipu7_tlb_invalidate(struct ipu6_mmu *mmu) +{ + struct ipu7_mmu_hw *mmu_hw = mmu->ipu7_mmu_hw; + unsigned long flags; + unsigned int i; + int ret; + u32 val; + + spin_lock_irqsave(&mmu->ready_lock, flags); + if (!mmu->ready) { + spin_unlock_irqrestore(&mmu->ready_lock, flags); + return; + } + + for (i = 0; i < mmu->nr_mmus; i++) { + writel(0xffffffffU, mmu_hw[i].base + + IPU7_MMU_REG_INVALIDATE_0); + + /* Need check with HW, use l1streams or l2streams */ + if (mmu_hw[i].nr_l2streams > 32) + writel(0xffffffffU, mmu_hw[i].base + + IPU7_MMU_REG_INVALIDATE_1); + + /* + * The TLB invalidation is a "single cycle" (IOMMU clock cycles) + * When the actual MMIO write reaches the IPU TLB Invalidate + * register, wmb() will force the TLB invalidate out if the CPU + * attempts to update the IOMMU page table (or sooner). + */ + wmb(); + + /* wait invalidation done */ + ret = readl_poll_timeout_atomic(mmu_hw[i].base + + IPU7_MMU_REG_INVALIDATION_STATUS, + val, !(val & 0x1U), 500, + IPU7_MMU_TLB_INVALIDATE_TIMEOUT); + if (ret) + dev_err(mmu->dev, "MMU[%u] TLB invalidate failed\n", i); + } + + spin_unlock_irqrestore(&mmu->ready_lock, flags); +} + +static int __ipu7_mmu_hw_init(struct ipu6_mmu *mmu) +{ + struct ipu6_mmu_info *mmu_info; + struct ipu7_mmu_hw *mmu_hw = mmu->ipu7_mmu_hw; + unsigned int i, j; + + mmu_info = mmu->dmap->mmu_info; + for (i = 0; i < mmu->nr_mmus; i++) { + /* Write page table address per MMU */ + writel((phys_addr_t)mmu_info->l1_pt_dma, + mmu_hw[i].base + IPU7_MMU_REG_PAGE_TABLE_BASE_ADDR); + + /* Set info bits and axi_refill per MMU */ + writel(mmu_hw[i].info_bits, + mmu_hw[i].base + IPU7_MMU_REG_USER_INFO_BITS); + writel(mmu_hw[i].refill, mmu_hw[i].base + IPU7_MMU_REG_AXI_REFILL_IF_ID); + writel(mmu_hw[i].collapse_en_bitmap, + mmu_hw[i].base + IPU7_MMU_REG_COLLAPSE_ENABLE_BITMAP); + + if (mmu_hw[i].at_sp_arb_cfg) + writel(mmu_hw[i].at_sp_arb_cfg, + mmu_hw[i].base + IPU7_MMU_REG_AT_SP_ARB_CFG); + + /* Default irq configuration */ + writel(0x3ff, mmu_hw[i].base + IPU7_MMU_REG_IRQ_MASK); + writel(0x3ff, mmu_hw[i].base + IPU7_MMU_REG_IRQ_ENABLE); + + /* Configure MMU TLB stream configuration for L1/L2 */ + for (j = 0; j < mmu_hw[i].nr_l1streams; j++) { + writel(mmu_hw[i].l1_block_sz[j], mmu_hw[i].base + + mmu_hw[i].l1_block + 4U * j); + } + + for (j = 0; j < mmu_hw[i].nr_l2streams; j++) { + writel(mmu_hw[i].l2_block_sz[j], mmu_hw[i].base + + mmu_hw[i].l2_block + 4U * j); + } + + for (j = 0; j < mmu_hw[i].uao_p_num; j++) { + if (!mmu_hw[i].uao_p2tlb[j]) + continue; + writel(mmu_hw[i].uao_p2tlb[j], mmu_hw[i].uao_base + 4U * j); + } + } + + for (i = 0; i < mmu->nr_mmus; i++) { + for (j = 0; j < IPU7_ZLX_POOL_NUM; j++) { + if (!mmu_hw[i].zlx_axi_pool[j]) + continue; + writel(mmu_hw[i].zlx_axi_pool[j], + mmu_hw[i].zlx_base + IPU7_ZLX_REG_AXI_POOL + j * 0x4U); + } + + for (j = 0; j < mmu_hw[i].zlx_nr; j++) { + if (!mmu_hw[i].zlx_conf[j]) + continue; + + writel(mmu_hw[i].zlx_conf[j], + mmu_hw[i].zlx_base + IPU7_ZLX_REG_CONF + j * 0x8U); + } + + for (j = 0; j < mmu_hw[i].zlx_nr; j++) { + if (!mmu_hw[i].zlx_en[j]) + continue; + + writel(mmu_hw[i].zlx_en[j], + mmu_hw[i].zlx_base + IPU7_ZLX_REG_EN + j * 0x8U); + } + } + + return 0; +} + +static int __ipu7_mmu_init_hw_data(struct ipu6_mmu *mmu, struct device *dev, + void __iomem *base) +{ + const struct ipu7_mmu_hwdata *lookup = ipu7_mmu_hwdata_lookup; + struct ipu7_mmu_hw *mmu_hw, *src; + unsigned int i, nr_mmus; + + if (mmu->mmid >= IPU_SUBSYS_NUM) + return -EINVAL; + + src = lookup[mmu->mmid].hwdata; + nr_mmus = lookup[mmu->mmid].nr_mmus; + + mmu_hw = devm_kcalloc(dev, nr_mmus, sizeof(*mmu_hw), GFP_KERNEL); + if (!mmu_hw) + return -ENOMEM; + + for (i = 0; i < nr_mmus; i++) { + if (src[i].nr_l1streams > IPU7_MMU_MAX_TLB_L1_STREAMS || + src[i].nr_l2streams > IPU7_MMU_MAX_TLB_L2_STREAMS) + return -EINVAL; + + mmu_hw[i] = src[i]; + mmu_hw[i].base = base + src[i].offset; + mmu_hw[i].zlx_base = base + src[i].zlx_offset; + mmu_hw[i].uao_base = base + src[i].uao_offset; + } + + mmu->nr_mmus = nr_mmus; + mmu->ipu7_mmu_hw = mmu_hw; + + return 0; +} + +const struct ipu6_mmu_hw_ops ipu7_mmu_ops = { + .init_hw_data = __ipu7_mmu_init_hw_data, + .hw_init = __ipu7_mmu_hw_init, + .tlb_invalidate = __ipu7_tlb_invalidate, +}; diff --git a/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h new file mode 100644 index 000000000000..68663057138a --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_MMU_HW_H +#define IPU7_MMU_HW_H + +#define IPU7_MMU_REG_IRQ_MASK 0x2c +#define IPU7_MMU_REG_IRQ_ENABLE 0x30 +#define IPU7_MMU_REG_PAGE_TABLE_BASE_ADDR 0x08 +#define IPU7_MMU_REG_USER_INFO_BITS 0x0c +#define IPU7_MMU_REG_AXI_REFILL_IF_ID 0x10 +#define IPU7_MMU_REG_COLLAPSE_ENABLE_BITMAP 0x18 +#define IPU7_MMU_REG_AT_SP_ARB_CFG 0x20 + +#define IPU7_ZLX_REG_AXI_POOL 0x0 +#define IPU7_ZLX_REG_EN 0x20 +#define IPU7_ZLX_REG_CONF 0x24 +#define IPU7_ZLX_POOL_NUM 8U + +#define IPU7_MMU_MAX_TLB_L1_STREAMS 40U +#define IPU7_MMU_MAX_TLB_L2_STREAMS 40U +#define IPU7_UAO_PLANE_MAX_NUM 64U +#define IPU7_ZLX_MAX_NUM 32U + +#define IPU7_MMU_REG_INVALIDATE_0 0x00 +#define IPU7_MMU_REG_INVALIDATE_1 0x04 +#define IPU7_MMU_REG_INVALIDATION_STATUS 0x24 +#define IPU7_MMU_TLB_INVALIDATE_TIMEOUT 2000 + +#define IPU7_FW_CODE_REGION_SIZE 0x1000000 /* 16MB */ +#define IPU7_FW_CODE_REGION_START 0x4000000 /* 64MB */ +#define IPU7_FW_CODE_REGION_END (IPU7_FW_CODE_REGION_START + \ + IPU7_FW_CODE_REGION_SIZE) /* 80MB */ + +#define IPU7_IS_MMU_FW_RD_OFFSET 0x274000 +#define IPU7_IS_MMU_FW_RD_STREAM_NUM 3 +#define IPU7_IS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_FW_RD_L2_BLOCKNR_REG 0x60 + +#define IPU7_IS_MMU_FW_WR_OFFSET 0x275000 +#define IPU7_IS_MMU_FW_WR_STREAM_NUM 3 +#define IPU7_IS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_FW_WR_L2_BLOCKNR_REG 0x60 + +#define IPU7_IS_MMU_M0_OFFSET 0x276000 +#define IPU7_IS_MMU_M0_STREAM_NUM 8 +#define IPU7_IS_MMU_M0_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_M0_L2_BLOCKNR_REG 0x74 + +#define IPU7_IS_MMU_M1_OFFSET 0x277000 +#define IPU7_IS_MMU_M1_STREAM_NUM 16 +#define IPU7_IS_MMU_M1_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_M1_L2_BLOCKNR_REG 0x94 + +#define IPU7_PS_MMU_FW_RD_OFFSET 0x148000 +#define IPU7_PS_MMU_FW_RD_STREAM_NUM 20 +#define IPU7_PS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_FW_RD_L2_BLOCKNR_REG 0xa4 + +#define IPU7_PS_MMU_FW_WR_OFFSET 0x149000 +#define IPU7_PS_MMU_FW_WR_STREAM_NUM 10 +#define IPU7_PS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_FW_WR_L2_BLOCKNR_REG 0x7c + +#define IPU7_PS_MMU_SRT_RD_OFFSET 0x14a000 +#define IPU7_PS_MMU_SRT_RD_STREAM_NUM 40 +#define IPU7_PS_MMU_SRT_RD_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_SRT_RD_L2_BLOCKNR_REG 0xf4 + +#define IPU7_PS_MMU_SRT_WR_OFFSET 0x14b000 +#define IPU7_PS_MMU_SRT_WR_STREAM_NUM 40 +#define IPU7_PS_MMU_SRT_WR_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_SRT_WR_L2_BLOCKNR_REG 0xf4 + +#define IPU7_IS_UAO_UC_RD_OFFSET 0x27c000 +#define IPU7_IS_UAO_UC_RD_PLANENUM 4 + +#define IPU7_IS_UAO_UC_WR_OFFSET 0x27d000 +#define IPU7_IS_UAO_UC_WR_PLANENUM 4 + +#define IPU7_IS_UAO_M0_WR_OFFSET 0x27e000 +#define IPU7_IS_UAO_M0_WR_PLANENUM 8 + +#define IPU7_IS_UAO_M1_WR_OFFSET 0x27f000 +#define IPU7_IS_UAO_M1_WR_PLANENUM 16 + +#define IPU7_PS_UAO_FW_RD_OFFSET 0x156000 +#define IPU7_PS_UAO_FW_RD_PLANENUM 20 + +#define IPU7_PS_UAO_FW_WR_OFFSET 0x157000 +#define IPU7_PS_UAO_FW_WR_PLANENUM 16 + +#define IPU7_PS_UAO_SRT_RD_OFFSET 0x154000 +#define IPU7_PS_UAO_SRT_RD_PLANENUM 40 + +#define IPU7_PS_UAO_SRT_WR_OFFSET 0x155000 +#define IPU7_PS_UAO_SRT_WR_PLANENUM 40 + +#define IPU7_IS_ZLX_UC_RD_OFFSET 0x278000 +#define IPU7_IS_ZLX_UC_WR_OFFSET 0x279000 +#define IPU7_IS_ZLX_M0_OFFSET 0x27a000 +#define IPU7_IS_ZLX_M1_OFFSET 0x27b000 +#define IPU7_IS_ZLX_UC_RD_NUM 4 +#define IPU7_IS_ZLX_UC_WR_NUM 4 +#define IPU7_IS_ZLX_M0_NUM 8 +#define IPU7_IS_ZLX_M1_NUM 16 + +#define IPU7_PS_ZLX_DATA_RD_OFFSET 0x14e000 +#define IPU7_PS_ZLX_DATA_WR_OFFSET 0x14f000 +#define IPU7_PS_ZLX_FW_RD_OFFSET 0x150000 +#define IPU7_PS_ZLX_FW_WR_OFFSET 0x151000 +#define IPU7_PS_ZLX_DATA_RD_NUM 32 +#define IPU7_PS_ZLX_DATA_WR_NUM 32 +#define IPU7_PS_ZLX_FW_RD_NUM 16 +#define IPU7_PS_ZLX_FW_WR_NUM 10 + +struct ipu7_mmu_hw { + char name[32]; + + void __iomem *base; + void __iomem *zlx_base; + void __iomem *uao_base; + + u32 offset; + u32 zlx_offset; + u32 uao_offset; + + u32 info_bits; + u32 refill; + u32 collapse_en_bitmap; + u32 at_sp_arb_cfg; + + u32 l1_block; + u32 l2_block; + + u8 nr_l1streams; + u8 nr_l2streams; + u32 l1_block_sz[IPU7_MMU_MAX_TLB_L1_STREAMS]; + u32 l2_block_sz[IPU7_MMU_MAX_TLB_L2_STREAMS]; + + u8 zlx_nr; + u32 zlx_axi_pool[IPU7_ZLX_POOL_NUM]; + u32 zlx_en[IPU7_ZLX_MAX_NUM]; + u32 zlx_conf[IPU7_ZLX_MAX_NUM]; + + u32 uao_p_num; + u32 uao_p2tlb[IPU7_UAO_PLANE_MAX_NUM]; +}; + +struct ipu7_mmu_hwdata { + struct ipu7_mmu_hw *hwdata; + unsigned int nr_mmus; +}; + +#endif From f57cfd50e9349cd59a5e9fe31805a4675105958b Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:07 +0300 Subject: [PATCH 062/596] media: ipu6: Add ipu7 cpd handling The firmware format differs slightly between ipu6 and ipu7. Add support for ipu7 firmware validation and handling. Signed-off-by: Antti Laakso [Sakari Ailus: Use ipu6_ prefix for ipu7 related global symbols.] Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-cpd.c | 171 +++++++++++++++++++++--- drivers/media/pci/intel/ipu6/ipu6-cpd.h | 43 ++++++ 2 files changed, 198 insertions(+), 16 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.c b/drivers/media/pci/intel/ipu6/ipu6-cpd.c index 966a16a300f8..c4b2d954a9d4 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-cpd.c +++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.c @@ -16,6 +16,7 @@ #include "ipu6-bus.h" #include "ipu6-cpd.h" #include "ipu6-dma.h" +#include "ipu7-mmu-hw.h" /* 15 entries + header*/ #define MAX_PKG_DIR_ENT_CNT 16 @@ -62,6 +63,20 @@ static inline const struct ipu6_cpd_ent *ipu6_cpd_get_entry(const void *cpd, #define ipu6_cpd_get_metadata(cpd) ipu6_cpd_get_entry(cpd, METADATA_IDX) #define ipu6_cpd_get_moduledata(cpd) ipu6_cpd_get_entry(cpd, MODULEDATA_IDX) +#define IPU7_CPD_BINARY_START_IDX 1U +#define IPU7_CPD_METADATA_START_IDX 2U +#define IPU7_CPD_BINARY_NUM 2U /* ISYS + PSYS */ +#define IPU7_CPD_METADATA_ATTR 0xa +#define IPU7_CPD_METADATA_IPL 0x1c +/* + * Entries include: + * 1 manifest entry. + * 1 metadata entry for each sub system(ISYS and PSYS). + * 1 binary entry for each sub system(ISYS and PSYS). + */ +#define IPU7_CPD_ENTRY_NUM (IPU7_CPD_BINARY_NUM * 2U + 1U) +#define IPU7_MAX_MANIFEST_SIZE (SZ_4K * sizeof(u32)) + static const struct ipu6_cpd_metadata_cmpnt_hdr * ipu6_cpd_metadata_get_cmpnt(struct ipu6_device *isp, const void *metadata, unsigned int metadata_size, u8 idx) @@ -309,11 +324,122 @@ static int ipu6_cpd_validate_metadata(struct ipu6_device *isp, return 0; } +static struct ipu7_cpd_metadata *ipu7_cpd_get_metadata(const void *cpd, int idx) +{ + const struct ipu6_cpd_ent *cpd_ent = + ipu6_cpd_get_entry(cpd, IPU7_CPD_METADATA_START_IDX + idx * 2); + + return (struct ipu7_cpd_metadata *)((u8 *)cpd + cpd_ent->offset); +} + +static int ipu7_cpd_validate_metadata(struct ipu6_device *isp, + const void *cpd, int idx) +{ + const struct ipu6_cpd_ent *cpd_ent = + ipu6_cpd_get_entry(cpd, IPU7_CPD_METADATA_START_IDX + idx * 2); + const struct ipu6_cpd_ent *bin_ent = + ipu6_cpd_get_entry(cpd, IPU7_CPD_BINARY_START_IDX + idx * 2); + const struct ipu7_cpd_metadata *metadata = + ipu7_cpd_get_metadata(cpd, idx); + struct device *dev = &isp->pdev->dev; + u32 offset; + + /* Sanity check for metadata size */ + if (cpd_ent->len != sizeof(struct ipu7_cpd_metadata)) { + dev_err(dev, "Invalid metadata size\n"); + return -EINVAL; + } + + /* Validate type and length of metadata sections */ + if (metadata->attr.hdr.type != IPU7_CPD_METADATA_ATTR) { + dev_err(dev, "Invalid metadata attr type (%d)\n", + metadata->attr.hdr.type); + return -EINVAL; + } + if (metadata->attr.hdr.len != sizeof(struct ipu7_cpd_metadata_attr)) { + dev_err(dev, "Invalid metadata attr size (%d)\n", + metadata->attr.hdr.len); + return -EINVAL; + } + if (metadata->ipl.hdr.type != IPU7_CPD_METADATA_IPL) { + dev_err(dev, "Invalid metadata ipl type (%d)\n", + metadata->ipl.hdr.type); + return -EINVAL; + } + if (metadata->ipl.hdr.len != sizeof(struct ipu7_cpd_metadata_ipl)) { + dev_err(dev, "Invalid metadata ipl size (%d)\n", + metadata->ipl.hdr.len); + return -EINVAL; + } + + offset = metadata->ipl.param[0]; + if (offset > IPU7_FW_CODE_REGION_SIZE || + bin_ent->len > IPU7_FW_CODE_REGION_SIZE - offset) { + dev_err(dev, "Incorrect binary size %u and offset %u\n", + bin_ent->len, offset); + return -EINVAL; + } + + return 0; +} + +static int __ipu7_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, + unsigned long cpd_file_size) +{ + const struct ipu6_cpd_ent *ent; + const struct ipu7_cpd_hdr *hdr = cpd_file; + unsigned int i; + + ent = ipu6_cpd_get_manifest(cpd_file); + if (ent->len > IPU7_MAX_MANIFEST_SIZE) { + dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n"); + return -EINVAL; + } + /* Sanity check for CPD entry header */ + if (hdr->ent_cnt != IPU7_CPD_ENTRY_NUM) { + dev_err(&isp->pdev->dev, "Invalid CPD entry number %d\n", + hdr->ent_cnt); + return -EINVAL; + } + + /* Validate metadata */ + for (i = 0; i < IPU7_CPD_BINARY_NUM; i++) { + int ret = ipu7_cpd_validate_metadata(isp, cpd_file, i); + + if (ret) { + dev_err(&isp->pdev->dev, "Invalid metadata(%d)\n", i); + return ret; + } + } + return 0; +} + +static int __ipu6_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, + unsigned long cpd_file_size) +{ + const struct ipu6_cpd_ent *ent; + int ret; + + ent = ipu6_cpd_get_manifest(cpd_file); + if (ent->len > MAX_MANIFEST_SIZE) { + dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n"); + return -EINVAL; + } + + ent = ipu6_cpd_get_metadata(cpd_file); + ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len); + if (ret) + return ret; + + ent = ipu6_cpd_get_moduledata(cpd_file); + return ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset, + ent->len); +} + int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, unsigned long cpd_file_size) { const struct ipu6_cpd_hdr *hdr = cpd_file; - const struct ipu6_cpd_ent *ent; int ret; ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size, @@ -327,21 +453,34 @@ int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, return -EINVAL; } - /* Sanity check for manifest size */ - ent = ipu6_cpd_get_manifest(cpd_file); - if (ent->len > MAX_MANIFEST_SIZE) { - dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n"); - return -EINVAL; + if (IS_IPU7(isp)) + return __ipu7_validate_cpd_file(isp, cpd_file, cpd_file_size); + + return __ipu6_validate_cpd_file(isp, cpd_file, cpd_file_size); +} + +int ipu6_ipu7_cpd_copy_binary(const void *cpd, const char *name, void *dst, + u32 *entry) +{ + unsigned int i; + + for (i = 0; i < IPU7_CPD_BINARY_NUM; i++) { + const struct ipu7_cpd_metadata *metadata; + u8 idx = IPU7_CPD_BINARY_START_IDX + i * 2U; + const struct ipu6_cpd_ent *ent = + ipu6_cpd_get_entry(cpd, idx); + + if (strncmp(ent->name, name, sizeof(ent->name))) + continue; + + metadata = ipu7_cpd_get_metadata(cpd, i); + memcpy(dst + metadata->ipl.param[0], cpd + ent->offset, + ent->len); + *entry = metadata->ipl.param[2]; + + return 0; } - /* Validate metadata */ - ent = ipu6_cpd_get_metadata(cpd_file); - ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len); - if (ret) - return ret; - - /* Validate moduledata */ - ent = ipu6_cpd_get_moduledata(cpd_file); - return ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset, - ent->len); + return -ENOENT; } +EXPORT_SYMBOL_NS_GPL(ipu6_ipu7_cpd_copy_binary, "INTEL_IPU6"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.h b/drivers/media/pci/intel/ipu6/ipu6-cpd.h index e0e4fdeca902..c3a4859d8e79 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-cpd.h +++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.h @@ -98,8 +98,51 @@ struct ipu6_cpd_client_pkg_hdr { u32 prog_bin_size; } __packed; +/* IPU7 */ + +struct ipu7_cpd_hdr { + u32 hdr_mark; + u32 ent_cnt; + u8 hdr_ver; + u8 ent_ver; + u8 hdr_len; + u8 rsvd; + u8 partition_name[4]; + u32 crc32; +} __packed; + +struct ipu7_cpd_metadata_hdr { + u32 type; + u32 len; +} __packed; + +struct ipu7_cpd_metadata_attr { + struct ipu7_cpd_metadata_hdr hdr; + u8 compression_type; + u8 encryption_type; + u8 rsvd[2]; + u32 uncompressed_size; + u32 compressed_size; + u32 module_id; + u8 hash[48]; +} __packed; + +struct ipu7_cpd_metadata_ipl { + struct ipu7_cpd_metadata_hdr hdr; + u32 param[4]; + u8 rsvd[8]; +} __packed; + +struct ipu7_cpd_metadata { + struct ipu7_cpd_metadata_attr attr; + struct ipu7_cpd_metadata_ipl ipl; +} __packed; + int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src); void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev); int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, unsigned long cpd_file_size); +int ipu6_ipu7_cpd_copy_binary(const void *cpd, const char *name, void *dst, + u32 *entry); + #endif /* IPU6_CPD_H */ From 66b2b2136a0276ef2cbf4b141133086acb92ef68 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:08 +0300 Subject: [PATCH 063/596] media: ipu6: Add check for pkg_dir before freeing We are adding support for mapping ipu7 firmware which doesn't have pkg_dir. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-cpd.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.c b/drivers/media/pci/intel/ipu6/ipu6-cpd.c index c4b2d954a9d4..1fadb0f2f6c1 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-cpd.c +++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.c @@ -235,8 +235,9 @@ EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, "INTEL_IPU6"); void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev) { - ipu6_dma_free(adev, adev->pkg_dir_size, adev->pkg_dir, - adev->pkg_dir_dma_addr, 0); + if (adev->pkg_dir) + ipu6_dma_free(adev, adev->pkg_dir_size, adev->pkg_dir, + adev->pkg_dir_dma_addr, 0); } EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, "INTEL_IPU6"); From 7d07d25d45efc54b333e4e27ddc9f0741ae61b4c Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:09 +0300 Subject: [PATCH 064/596] media: ipu6: Rename isys fw msg union Later we need to abstract ipu7 firmware abi structs same way we have it now for ipu6. Renaming union per ipu helps to make the split more clear. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-isys-queue.c | 4 ++-- drivers/media/pci/intel/ipu6/ipu6-isys-video.c | 4 ++-- drivers/media/pci/intel/ipu6/ipu6-isys.c | 4 ++-- drivers/media/pci/intel/ipu6/ipu6-isys.h | 2 +- 4 files changed, 7 insertions(+), 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 fabaed63df0c..529973860e9e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -316,7 +316,7 @@ static int ipu6_isys_stream_start(struct ipu6_isys_video *av, if (!msg) return -ENOMEM; - buf = &msg->fw_msg.frame; + buf = &msg->ipu6.frame; ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); @@ -396,7 +396,7 @@ static void buf_queue(struct vb2_buffer *vb) goto out; } - buf = &msg->fw_msg.frame; + buf = &msg->ipu6.frame; ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl); ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index 89eb265737d7..c925386a1e98 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -533,7 +533,7 @@ static int start_stream_firmware(struct ipu6_isys_video *av, if (!msg) return -ENOMEM; - stream_cfg = &msg->fw_msg.stream; + stream_cfg = &msg->ipu6.stream; stream_cfg->src = stream->stream_source; stream_cfg->vc = stream->vc; stream_cfg->isl_use = 0; @@ -590,7 +590,7 @@ static int start_stream_firmware(struct ipu6_isys_video *av, ret = -ENOMEM; goto out_put_stream_opened; } - buf = &msg->fw_msg.frame; + buf = &msg->ipu6.frame; ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 8af891081bd3..8f2d361b89f6 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -1003,7 +1003,7 @@ struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream) msg = list_last_entry(&isys->framebuflist, struct isys_fw_msgs, head); list_move(&msg->head, &isys->framebuflist_fw); spin_unlock_irqrestore(&isys->listlock, flags); - memset(&msg->fw_msg, 0, sizeof(msg->fw_msg)); + memset(&msg->ipu6, 0, sizeof(msg->ipu6)); return msg; } @@ -1029,7 +1029,7 @@ void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, uintptr_t data) return; spin_lock_irqsave(&isys->listlock, flags); - msg = container_of(ptr, struct isys_fw_msgs, fw_msg.dummy); + msg = container_of(ptr, struct isys_fw_msgs, ipu6.dummy); list_move(&msg->head, &isys->framebuflist); spin_unlock_irqrestore(&isys->listlock, flags); } diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h index 0b139ab24431..abcd5e1cbc2c 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -169,7 +169,7 @@ struct isys_fw_msgs { u64 dummy; struct ipu6_fw_isys_frame_buff_set_abi frame; struct ipu6_fw_isys_stream_cfg_data_abi stream; - } fw_msg; + } ipu6; struct list_head head; dma_addr_t dma_addr; }; From 33f56ed128438dd791b9114ad079ab188daa1c09 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:10 +0300 Subject: [PATCH 065/596] media: ipu6: Move isys isr handlers to fw file The ipu7 will have its own isr handlers. Move ipu6 isr code to hardware specific file. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 259 +++++++++++++++++++ drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 3 + drivers/media/pci/intel/ipu6/ipu6-isys.c | 263 +------------------- 3 files changed, 263 insertions(+), 262 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c index fc2f24c05de7..7f7125202ffd 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c @@ -487,3 +487,262 @@ ipu6_fw_isys_dump_frame_buff_set(struct device *dev, dev_dbg(dev, "-----------------------------------------------------\n"); } + +struct fwmsg { + int type; + char *msg; + bool valid_ts; +}; + +static const struct fwmsg fw_msg[] = { + {IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, "STREAM_START_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, + "STREAM_START_AND_CAPTURE_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, "STREAM_STOP_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY", 1}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK", 0}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, + "STREAM_START_AND_CAPTURE_DONE", 1}, + {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE", 1}, + {IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF", 1}, + {IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF", 1}, + {IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, "STATS_READY", 1}, + {-1, "UNKNOWN MESSAGE", 0} +}; + +static u32 resp_type_to_index(int type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fw_msg); i++) + if (fw_msg[i].type == type) + return i; + + return ARRAY_SIZE(fw_msg) - 1; +} + +int ipu6_isys_isr_one(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu6_fw_isys_resp_info_abi *resp; + struct ipu6_isys_stream *stream; + struct ipu6_isys_csi2 *csi2 = NULL; + u32 index; + u64 ts; + + if (!isys->fwctx) + return 1; + + resp = ipu6_fw_isys_get_resp(isys); + if (!resp) + return 1; + + ts = (u64)resp->timestamp[1] << 32 | resp->timestamp[0]; + + index = resp_type_to_index(resp->type); + dev_dbg(&adev->auxdev.dev, + "FW resp %02d %s, stream %u, ts 0x%16.16llx, pin %d\n", + resp->type, fw_msg[index].msg, resp->stream_handle, + fw_msg[index].valid_ts ? ts : 0, resp->pin_id); + + if (resp->error_info.error == IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION) + /* Suspension is kind of special case: not enough buffers */ + dev_dbg(&adev->auxdev.dev, + "FW error resp SUSPENSION, details %d\n", + resp->error_info.error_details); + else if (resp->error_info.error) + dev_dbg(&adev->auxdev.dev, + "FW error resp error %d, details %d\n", + resp->error_info.error, resp->error_info.error_details); + + if (resp->stream_handle >= IPU6_ISYS_MAX_STREAMS) { + dev_err(&adev->auxdev.dev, "bad stream handle %u\n", + resp->stream_handle); + goto leave; + } + + stream = ipu6_isys_query_stream_by_handle(isys, resp->stream_handle); + if (!stream) { + dev_err(&adev->auxdev.dev, "stream of stream_handle %u is unused\n", + resp->stream_handle); + goto leave; + } + stream->error = resp->error_info.error; + + csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + + switch (resp->type) { + case IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE: + complete(&stream->stream_open_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK: + complete(&stream->stream_close_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK: + complete(&stream->stream_start_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK: + complete(&stream->stream_start_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY: + /* + * firmware only release the capture msg until software + * get pin_data_ready event + */ + ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), resp->buf_id); + if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS && + stream->output_pins_queue[resp->pin_id]) + ipu6_isys_queue_buf_ready(stream, resp); + else + dev_warn(&adev->auxdev.dev, + "%d:No queue for pin id %d\n", + resp->stream_handle, resp->pin_id); + if (csi2) + ipu6_isys_csi2_error(csi2); + + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK: + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE: + break; + case IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF: + + ipu6_isys_csi2_sof_event_by_stream(stream); + stream->seq[stream->seq_index].sequence = + atomic_read(&stream->sequence) - 1; + stream->seq[stream->seq_index].timestamp = ts; + dev_dbg(&adev->auxdev.dev, + "sof: handle %d: (index %u), timestamp 0x%16.16llx\n", + resp->stream_handle, + stream->seq[stream->seq_index].sequence, ts); + stream->seq_index = (stream->seq_index + 1) + % IPU6_ISYS_MAX_PARALLEL_SOF; + break; + case IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF: + ipu6_isys_csi2_eof_event_by_stream(stream); + dev_dbg(&adev->auxdev.dev, + "eof: handle %d: (index %u), timestamp 0x%16.16llx\n", + resp->stream_handle, + stream->seq[stream->seq_index].sequence, ts); + break; + case IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY: + break; + default: + dev_err(&adev->auxdev.dev, "%d:unknown response type %u\n", + resp->stream_handle, resp->type); + break; + } + + ipu6_isys_put_stream(stream); +leave: + ipu6_fw_isys_put_resp(isys); + return 0; +} + +static void ipu6_isys_csi2_isr(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys_stream *stream; + unsigned int i; + u32 status; + int source; + + ipu6_isys_register_errors(csi2); + + status = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); + + writel(status, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + + source = csi2->asd.source; + for (i = 0; i < NR_OF_CSI2_VC; i++) { + if (status & IPU_CSI_RX_IRQ_FS_VC(i)) { + stream = ipu6_isys_query_stream_by_source(csi2->isys, + source, i); + if (stream) { + ipu6_isys_csi2_sof_event_by_stream(stream); + ipu6_isys_put_stream(stream); + } + } + + if (status & IPU_CSI_RX_IRQ_FE_VC(i)) { + stream = ipu6_isys_query_stream_by_source(csi2->isys, + source, i); + if (stream) { + ipu6_isys_csi2_eof_event_by_stream(stream); + ipu6_isys_put_stream(stream); + } + } + } +} + +irqreturn_t ipu6_isys_isr(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + void __iomem *base = isys->pdata->base; + u32 status_sw, status_csi; + u32 ctrl0_status, ctrl0_clear; + + spin_lock(&isys->power_lock); + if (!isys->power) { + spin_unlock(&isys->power_lock); + return IRQ_NONE; + } + + ctrl0_status = isys->pdata->ipdata->csi2.ctrl0_irq_status; + ctrl0_clear = isys->pdata->ipdata->csi2.ctrl0_irq_clear; + + status_csi = readl(isys->pdata->base + ctrl0_status); + status_sw = readl(isys->pdata->base + + IPU6_REG_ISYS_UNISPART_IRQ_STATUS); + + writel(ISYS_UNISPART_IRQS & ~IPU6_ISYS_UNISPART_IRQ_SW, + base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); + + do { + writel(status_csi, isys->pdata->base + ctrl0_clear); + + writel(status_sw, isys->pdata->base + + IPU6_REG_ISYS_UNISPART_IRQ_CLEAR); + + if (isys->isr_csi2_bits & status_csi) { + unsigned int i; + + for (i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { + /* irq from not enabled port */ + if (!isys->csi2[i].base) + continue; + if (status_csi & IPU6_ISYS_UNISPART_IRQ_CSI2(i)) + ipu6_isys_csi2_isr(&isys->csi2[i]); + } + } + + writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG); + + if (!ipu6_isys_isr_one(adev)) + status_sw = IPU6_ISYS_UNISPART_IRQ_SW; + else + status_sw = 0; + + status_csi = readl(isys->pdata->base + ctrl0_status); + status_sw |= readl(isys->pdata->base + + IPU6_REG_ISYS_UNISPART_IRQ_STATUS); + } while ((status_csi & isys->isr_csi2_bits) || + (status_sw & IPU6_ISYS_UNISPART_IRQ_SW)); + + writel(ISYS_UNISPART_IRQS, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); + + spin_unlock(&isys->power_lock); + + return IRQ_HANDLED; +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h index 15d4ae355f28..f65461a4c907 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h @@ -578,4 +578,7 @@ void ipu6_fw_isys_cleanup(struct ipu6_isys *isys); struct ipu6_fw_isys_resp_info_abi * ipu6_fw_isys_get_resp(struct ipu6_isys *isys); void ipu6_fw_isys_put_resp(struct ipu6_isys *isys); +int ipu6_isys_isr_one(struct ipu6_bus_device *adev); +irqreturn_t ipu6_isys_isr(struct ipu6_bus_device *adev); + #endif diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 8f2d361b89f6..a58a35112ebd 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -100,8 +100,6 @@ enum ltr_did_type { #define ISYS_PM_QOS_VALUE 300 -static int isys_isr_one(struct ipu6_bus_device *adev); - static int isys_complete_ext_device_registration(struct ipu6_isys *isys, struct v4l2_subdev *sd, @@ -304,104 +302,6 @@ static void isys_setup_hw(struct ipu6_isys *isys) writel(thd[i], base + IPU6_REG_ISYS_CDC_THRESHOLD(i)); } -static void ipu6_isys_csi2_isr(struct ipu6_isys_csi2 *csi2) -{ - struct ipu6_isys_stream *stream; - unsigned int i; - u32 status; - int source; - - ipu6_isys_register_errors(csi2); - - status = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + - CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); - - writel(status, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + - CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); - - source = csi2->asd.source; - for (i = 0; i < NR_OF_CSI2_VC; i++) { - if (status & IPU_CSI_RX_IRQ_FS_VC(i)) { - stream = ipu6_isys_query_stream_by_source(csi2->isys, - source, i); - if (stream) { - ipu6_isys_csi2_sof_event_by_stream(stream); - ipu6_isys_put_stream(stream); - } - } - - if (status & IPU_CSI_RX_IRQ_FE_VC(i)) { - stream = ipu6_isys_query_stream_by_source(csi2->isys, - source, i); - if (stream) { - ipu6_isys_csi2_eof_event_by_stream(stream); - ipu6_isys_put_stream(stream); - } - } - } -} - -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; - u32 status_sw, status_csi; - u32 ctrl0_status, ctrl0_clear; - - spin_lock(&isys->power_lock); - if (!isys->power) { - spin_unlock(&isys->power_lock); - return IRQ_NONE; - } - - ctrl0_status = isys->pdata->ipdata->csi2.ctrl0_irq_status; - ctrl0_clear = isys->pdata->ipdata->csi2.ctrl0_irq_clear; - - status_csi = readl(isys->pdata->base + ctrl0_status); - status_sw = readl(isys->pdata->base + - IPU6_REG_ISYS_UNISPART_IRQ_STATUS); - - writel(ISYS_UNISPART_IRQS & ~IPU6_ISYS_UNISPART_IRQ_SW, - base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); - - do { - writel(status_csi, isys->pdata->base + ctrl0_clear); - - writel(status_sw, isys->pdata->base + - IPU6_REG_ISYS_UNISPART_IRQ_CLEAR); - - if (isys->isr_csi2_bits & status_csi) { - unsigned int i; - - for (i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { - /* irq from not enabled port */ - if (!isys->csi2[i].base) - continue; - if (status_csi & IPU6_ISYS_UNISPART_IRQ_CSI2(i)) - ipu6_isys_csi2_isr(&isys->csi2[i]); - } - } - - writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG); - - if (!isys_isr_one(adev)) - status_sw = IPU6_ISYS_UNISPART_IRQ_SW; - else - status_sw = 0; - - status_csi = readl(isys->pdata->base + ctrl0_status); - status_sw |= readl(isys->pdata->base + - IPU6_REG_ISYS_UNISPART_IRQ_STATUS); - } while ((status_csi & isys->isr_csi2_bits) || - (status_sw & IPU6_ISYS_UNISPART_IRQ_SW)); - - writel(ISYS_UNISPART_IRQS, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); - - spin_unlock(&isys->power_lock); - - return IRQ_HANDLED; -} - static void get_lut_ltrdid(struct ipu6_isys *isys, struct ltr_did *pltr_did) { struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; @@ -1167,169 +1067,8 @@ static void isys_remove(struct auxiliary_device *auxdev) mutex_destroy(&isys->mutex); } -struct fwmsg { - int type; - char *msg; - bool valid_ts; -}; - -static const struct fwmsg fw_msg[] = { - {IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, "STREAM_START_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, - "STREAM_START_AND_CAPTURE_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, "STREAM_STOP_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY", 1}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK", 0}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, - "STREAM_START_AND_CAPTURE_DONE", 1}, - {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE", 1}, - {IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF", 1}, - {IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF", 1}, - {IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, "STATS_READY", 1}, - {-1, "UNKNOWN MESSAGE", 0} -}; - -static u32 resp_type_to_index(int type) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(fw_msg); i++) - if (fw_msg[i].type == type) - return i; - - return ARRAY_SIZE(fw_msg) - 1; -} - -static int isys_isr_one(struct ipu6_bus_device *adev) -{ - struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); - struct ipu6_fw_isys_resp_info_abi *resp; - struct ipu6_isys_stream *stream; - struct ipu6_isys_csi2 *csi2 = NULL; - u32 index; - u64 ts; - - if (!isys->fwctx) - return 1; - - resp = ipu6_fw_isys_get_resp(isys); - if (!resp) - return 1; - - ts = (u64)resp->timestamp[1] << 32 | resp->timestamp[0]; - - index = resp_type_to_index(resp->type); - dev_dbg(&adev->auxdev.dev, - "FW resp %02d %s, stream %u, ts 0x%16.16llx, pin %d\n", - resp->type, fw_msg[index].msg, resp->stream_handle, - fw_msg[index].valid_ts ? ts : 0, resp->pin_id); - - if (resp->error_info.error == IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION) - /* Suspension is kind of special case: not enough buffers */ - dev_dbg(&adev->auxdev.dev, - "FW error resp SUSPENSION, details %d\n", - resp->error_info.error_details); - else if (resp->error_info.error) - dev_dbg(&adev->auxdev.dev, - "FW error resp error %d, details %d\n", - resp->error_info.error, resp->error_info.error_details); - - if (resp->stream_handle >= IPU6_ISYS_MAX_STREAMS) { - dev_err(&adev->auxdev.dev, "bad stream handle %u\n", - resp->stream_handle); - goto leave; - } - - stream = ipu6_isys_query_stream_by_handle(isys, resp->stream_handle); - if (!stream) { - dev_err(&adev->auxdev.dev, "stream of stream_handle %u is unused\n", - resp->stream_handle); - goto leave; - } - stream->error = resp->error_info.error; - - csi2 = ipu6_isys_subdev_to_csi2(stream->asd); - - switch (resp->type) { - case IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE: - complete(&stream->stream_open_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK: - complete(&stream->stream_close_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK: - complete(&stream->stream_start_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK: - complete(&stream->stream_start_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK: - complete(&stream->stream_stop_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK: - complete(&stream->stream_stop_completion); - break; - case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY: - /* - * firmware only release the capture msg until software - * get pin_data_ready event - */ - ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), resp->buf_id); - if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS && - stream->output_pins_queue[resp->pin_id]) - ipu6_isys_queue_buf_ready(stream, resp); - else - dev_warn(&adev->auxdev.dev, - "%d:No queue for pin id %d\n", - resp->stream_handle, resp->pin_id); - if (csi2) - ipu6_isys_csi2_error(csi2); - - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK: - break; - case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: - case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE: - break; - case IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF: - - ipu6_isys_csi2_sof_event_by_stream(stream); - stream->seq[stream->seq_index].sequence = - atomic_read(&stream->sequence) - 1; - stream->seq[stream->seq_index].timestamp = ts; - dev_dbg(&adev->auxdev.dev, - "sof: handle %d: (index %u), timestamp 0x%16.16llx\n", - resp->stream_handle, - stream->seq[stream->seq_index].sequence, ts); - stream->seq_index = (stream->seq_index + 1) - % IPU6_ISYS_MAX_PARALLEL_SOF; - break; - case IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF: - ipu6_isys_csi2_eof_event_by_stream(stream); - dev_dbg(&adev->auxdev.dev, - "eof: handle %d: (index %u), timestamp 0x%16.16llx\n", - resp->stream_handle, - stream->seq[stream->seq_index].sequence, ts); - break; - case IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY: - break; - default: - dev_err(&adev->auxdev.dev, "%d:unknown response type %u\n", - resp->stream_handle, resp->type); - break; - } - - ipu6_isys_put_stream(stream); -leave: - ipu6_fw_isys_put_resp(isys); - return 0; -} - static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = { - .isr = isys_isr, + .isr = ipu6_isys_isr, .isr_threaded = NULL, .wake_isr_thread = false, }; From 34c9ef5c22917d478ed530eaf34434a76a487adf Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:11 +0300 Subject: [PATCH 066/596] media: ipu6: Move hw specific buffer handling down Move handling of hardware specific buffers out of common code, like ipu6-isys-video.c. Now the ipu6_put_fw_msg_buf() works with fw_msg_bufs, like its counterpart ipu6_get_fw_msg_buf(). Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 11 ++++++++++- drivers/media/pci/intel/ipu6/ipu6-isys-video.c | 6 +++--- drivers/media/pci/intel/ipu6/ipu6-isys.c | 7 ++----- drivers/media/pci/intel/ipu6/ipu6-isys.h | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c index 7f7125202ffd..8906727ca84b 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c @@ -530,6 +530,7 @@ int ipu6_isys_isr_one(struct ipu6_bus_device *adev) struct ipu6_fw_isys_resp_info_abi *resp; struct ipu6_isys_stream *stream; struct ipu6_isys_csi2 *csi2 = NULL; + struct isys_fw_msgs *isys_fw_msg = NULL; u32 index; u64 ts; @@ -598,7 +599,15 @@ int ipu6_isys_isr_one(struct ipu6_bus_device *adev) * firmware only release the capture msg until software * get pin_data_ready event */ - ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), resp->buf_id); + if (!resp->buf_id) + dev_warn(&adev->auxdev.dev, "%d: Invalid buf ID\n", + resp->stream_handle); + else + isys_fw_msg = + container_of((void *)(uintptr_t)resp->buf_id, + struct isys_fw_msgs, ipu6.dummy); + + ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), isys_fw_msg); if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS && stream->output_pins_queue[resp->pin_id]) ipu6_isys_queue_buf_ready(stream, resp); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index c925386a1e98..fabea6a75d7d 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -544,7 +544,7 @@ static int start_stream_firmware(struct ipu6_isys_video *av, ret = ipu6_isys_fw_pin_cfg(__av, stream_cfg); if (ret < 0) { - ipu6_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + ipu6_put_fw_msg_buf(av->isys, msg); return ret; } } @@ -561,7 +561,7 @@ static int start_stream_firmware(struct ipu6_isys_video *av, IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN); if (ret < 0) { dev_err(dev, "can't open stream (%d)\n", ret); - ipu6_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + ipu6_put_fw_msg_buf(av->isys, msg); return ret; } @@ -570,7 +570,7 @@ static int start_stream_firmware(struct ipu6_isys_video *av, tout = wait_for_completion_timeout(&stream->stream_open_completion, IPU6_FW_CALL_TIMEOUT_JIFFIES); - ipu6_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + ipu6_put_fw_msg_buf(av->isys, msg); if (!tout) { dev_err(dev, "stream open time out\n"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index a58a35112ebd..bb3c0d6bc6c9 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -919,17 +919,14 @@ void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys) spin_unlock_irqrestore(&isys->listlock, flags); } -void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, uintptr_t data) +void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, struct isys_fw_msgs *msg) { - struct isys_fw_msgs *msg; unsigned long flags; - void *ptr = (void *)data; - if (!ptr) + if (!msg) return; spin_lock_irqsave(&isys->listlock, flags); - msg = container_of(ptr, struct isys_fw_msgs, ipu6.dummy); list_move(&msg->head, &isys->framebuflist); spin_unlock_irqrestore(&isys->listlock, flags); } diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h index abcd5e1cbc2c..f39566513a87 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -175,7 +175,7 @@ struct isys_fw_msgs { }; struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream); -void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, uintptr_t data); +void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, struct isys_fw_msgs *msg); void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys); extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops; From 1651fdd4184591f6acdf6f308fc30d5b22ae502c Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:12 +0300 Subject: [PATCH 067/596] media: ipu6: Isolate hw specific buffer handling Move ipu6 specific buffer and stream handling down to hardware specific file, to make it possible to add ipu7 specific functionality. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 241 ++++++++++++++++-- drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 42 ++- .../media/pci/intel/ipu6/ipu6-isys-queue.c | 77 +----- .../media/pci/intel/ipu6/ipu6-isys-queue.h | 4 - .../media/pci/intel/ipu6/ipu6-isys-video.c | 128 +--------- 5 files changed, 270 insertions(+), 222 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c index 8906727ca84b..9256ea3b68c1 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c @@ -90,11 +90,11 @@ int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, return ret; } -int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, - const unsigned int stream_handle, - void *cpu_mapped_buf, - dma_addr_t dma_mapped_buf, - size_t size, u16 send_type) +static int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, + const unsigned int stream_handle, + void *cpu_mapped_buf, + dma_addr_t dma_mapped_buf, + size_t size, u16 send_type) { struct ipu6_fw_com_context *ctx = isys->fwctx; struct device *dev = &isys->adev->auxdev.dev; @@ -126,13 +126,6 @@ int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, return 0; } -int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, - const unsigned int stream_handle, u16 send_type) -{ - return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, - send_type); -} - int ipu6_fw_isys_close(struct ipu6_isys *isys) { struct device *dev = &isys->adev->auxdev.dev; @@ -376,9 +369,9 @@ void ipu6_fw_isys_put_resp(struct ipu6_isys *isys) ipu6_recv_put_token(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); } -void ipu6_fw_isys_dump_stream_cfg(struct device *dev, - struct ipu6_fw_isys_stream_cfg_data_abi *cfg) +void ipu6_fw_isys_dump_stream_cfg(struct device *dev, struct isys_fw_msgs *msg) { + struct ipu6_fw_isys_stream_cfg_data_abi *cfg = &msg->ipu6.stream; unsigned int i; dev_dbg(dev, "-----------------------------------------------------\n"); @@ -454,12 +447,14 @@ void ipu6_fw_isys_dump_stream_cfg(struct device *dev, } void -ipu6_fw_isys_dump_frame_buff_set(struct device *dev, - struct ipu6_fw_isys_frame_buff_set_abi *buf, +ipu6_fw_isys_dump_frame_buff_set(struct device *dev, struct isys_fw_msgs *msg, unsigned int outputs) { + struct ipu6_fw_isys_frame_buff_set_abi *buf; unsigned int i; + buf = &msg->ipu6.frame; + dev_dbg(dev, "-----------------------------------------------------\n"); dev_dbg(dev, "IPU6_FW_ISYS_FRAME_BUFF_SET\n"); @@ -596,16 +591,17 @@ int ipu6_isys_isr_one(struct ipu6_bus_device *adev) break; case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY: /* - * firmware only release the capture msg until software + * firmware only release the capture message until software * get pin_data_ready event */ - if (!resp->buf_id) + if (!resp->buf_id) { dev_warn(&adev->auxdev.dev, "%d: Invalid buf ID\n", resp->stream_handle); - else - isys_fw_msg = - container_of((void *)(uintptr_t)resp->buf_id, - struct isys_fw_msgs, ipu6.dummy); + goto leave_put_stream; + } + + isys_fw_msg = container_of((void *)(uintptr_t)resp->buf_id, + struct isys_fw_msgs, ipu6.dummy); ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), isys_fw_msg); if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS && @@ -652,6 +648,7 @@ int ipu6_isys_isr_one(struct ipu6_bus_device *adev) break; } +leave_put_stream: ipu6_isys_put_stream(stream); leave: ipu6_fw_isys_put_resp(isys); @@ -755,3 +752,203 @@ irqreturn_t ipu6_isys_isr(struct ipu6_bus_device *adev) return IRQ_HANDLED; } + +static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, + struct ipu6_fw_isys_stream_cfg_data_abi *cfg) +{ + 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; + struct ipu6_isys_queue *aq = &av->aq; + struct v4l2_mbus_framefmt fmt; + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); + struct v4l2_rect v4l2_crop; + struct ipu6_isys *isys = av->isys; + int input_pins = cfg->nof_input_pins++; + int output_pins; + u32 src_stream; + + src_stream = ipu6_isys_get_src_stream_by_src_pad(sd, src_pad->index); + 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; + input_pin->input_res.height = fmt.height; + input_pin->dt = av->dt; + input_pin->bits_per_pix = pfmt->bpp_packed; + input_pin->mapped_dt = 0x40; /* invalid mipi data type */ + input_pin->mipi_decompression = 0; + input_pin->capture_mode = IPU6_FW_ISYS_CAPTURE_MODE_REGULAR; + input_pin->mipi_store_mode = pfmt->bpp == pfmt->bpp_packed ? + IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER : + IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL; + input_pin->crop_first_and_last_lines = v4l2_crop.top & 1; + + output_pins = cfg->nof_output_pins++; + aq->fw_output = output_pins; + stream->output_pins_queue[output_pins] = aq; + + output_pin = &cfg->output_pins[output_pins]; + output_pin->input_pin_id = input_pins; + output_pin->output_res.width = ipu6_isys_get_frame_width(av); + output_pin->output_res.height = ipu6_isys_get_frame_height(av); + + output_pin->stride = ipu6_isys_get_bytes_per_line(av); + if (pfmt->bpp != pfmt->bpp_packed) + output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC; + else + output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_MIPI; + output_pin->ft = pfmt->css_pixelformat; + output_pin->send_irq = 1; + memset(output_pin->ts_offsets, 0, sizeof(output_pin->ts_offsets)); + output_pin->s2m_pixel_soc_pixel_remapping = + S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; + output_pin->csi_be_soc_pixel_remapping = + CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; + + output_pin->snoopable = true; + output_pin->error_handling_enable = false; + output_pin->sensor_type = isys->sensor_type++; + if (isys->sensor_type > isys->pdata->ipdata->sensor_type_end) + isys->sensor_type = isys->pdata->ipdata->sensor_type_start; + + return 0; +} + +int ipu6_fw_isys_prepare_stream_cfg(struct ipu6_isys_video *av, + struct isys_fw_msgs *msg) +{ + struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg; + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_queue *aq; + + stream_cfg = &msg->ipu6.stream; + stream_cfg->src = stream->stream_source; + stream_cfg->vc = stream->vc; + stream_cfg->isl_use = 0; + stream_cfg->sensor_type = IPU6_FW_ISYS_SENSOR_MODE_NORMAL; + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_video *__av = ipu6_isys_queue_to_video(aq); + int ret; + + ret = ipu6_isys_fw_pin_cfg(__av, stream_cfg); + if (ret < 0) + return ret; + } + + ipu6_fw_isys_dump_stream_cfg(dev, msg); + + stream->nr_output_pins = stream_cfg->nof_output_pins; + + return 0; +} + +int ipu6_fw_isys_stream_open(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg) +{ + return ipu6_fw_isys_complex_cmd(isys, stream_handle, + &msg->ipu6.stream, msg->dma_addr, + sizeof(msg->ipu6.stream), + IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN); +} + +int ipu6_fw_isys_stream_close(struct ipu6_isys *isys, + const unsigned int stream_handle) +{ + return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, + IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); +} + +int ipu6_fw_isys_stream_flush(struct ipu6_isys *isys, + const unsigned int stream_handle) +{ + return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, + IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH); +} + +int ipu6_fw_isys_stream_start(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg, bool capture) +{ + u16 cmd_type; + + if (capture) + cmd_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; + else + cmd_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; + + return ipu6_fw_isys_complex_cmd(isys, stream_handle, + &msg->ipu6.stream, msg->dma_addr, + sizeof(msg->ipu6.stream), cmd_type); +} + +int ipu6_fw_isys_stream_capture(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg) +{ + return ipu6_fw_isys_complex_cmd(isys, stream_handle, + &msg->ipu6.stream, msg->dma_addr, + sizeof(msg->ipu6.stream), + IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE); +} + +static void +ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, + struct ipu6_fw_isys_frame_buff_set_abi *set) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_video_buffer *ivb = + vb2_buffer_to_ipu6_isys_video_buffer(vvb); + + set->output_pins[aq->fw_output].addr = ivb->dma_addr; + set->output_pins[aq->fw_output].out_buf_id = vb->index + 1; +} + +/* + * Convert a buffer list to a isys fw ABI framebuffer set. The + * buffer list is not modified. + */ +#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30) +void +ipu6_fw_isys_prepare_buf_set(struct isys_fw_msgs *msg, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) +{ + struct ipu6_fw_isys_frame_buff_set_abi *set = &msg->ipu6.frame; + struct ipu6_isys_buffer *ib; + + WARN_ON(!bl->nbufs); + + set->send_irq_sof = 1; + set->send_resp_sof = 1; + set->send_irq_eof = 0; + set->send_resp_eof = 0; + + if (stream->streaming) + set->send_irq_capture_ack = 0; + else + set->send_irq_capture_ack = 1; + set->send_irq_capture_done = 0; + + set->send_resp_capture_ack = 1; + set->send_resp_capture_done = 1; + if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) { + set->send_resp_capture_ack = 0; + set->send_resp_capture_done = 0; + } + + list_for_each_entry(ib, &bl->head, head) { + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + ipu6_isys_buf_to_fw_frame_buf_pin(vb, set); + } +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h index f65461a4c907..b589ae7fbd4e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h @@ -8,6 +8,10 @@ struct device; struct ipu6_isys; +struct ipu6_isys_video; +struct isys_fw_msgs; +struct ipu6_isys_stream; +struct ipu6_isys_buffer_list; /* Max number of Input/Output Pins */ #define IPU6_MAX_IPINS 4 @@ -555,21 +559,8 @@ struct ipu6_fw_proxy_send_queue_token { u32 value; }; -void -ipu6_fw_isys_dump_stream_cfg(struct device *dev, - struct ipu6_fw_isys_stream_cfg_data_abi *cfg); -void -ipu6_fw_isys_dump_frame_buff_set(struct device *dev, - struct ipu6_fw_isys_frame_buff_set_abi *buf, - unsigned int outputs); int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams); int ipu6_fw_isys_close(struct ipu6_isys *isys); -int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, - const unsigned int stream_handle, u16 send_type); -int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, - const unsigned int stream_handle, - void *cpu_mapped_buf, dma_addr_t dma_mapped_buf, - size_t size, u16 send_type); int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, unsigned int req_id, unsigned int index, @@ -581,4 +572,29 @@ void ipu6_fw_isys_put_resp(struct ipu6_isys *isys); int ipu6_isys_isr_one(struct ipu6_bus_device *adev); irqreturn_t ipu6_isys_isr(struct ipu6_bus_device *adev); +int ipu6_fw_isys_stream_open(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg); +int ipu6_fw_isys_stream_close(struct ipu6_isys *isys, + const unsigned int stream_handle); +int ipu6_fw_isys_stream_flush(struct ipu6_isys *isys, + const unsigned int stream_handle); +int ipu6_fw_isys_stream_start(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg, bool capture); +int ipu6_fw_isys_stream_capture(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg); +int ipu6_fw_isys_prepare_stream_cfg(struct ipu6_isys_video *av, + struct isys_fw_msgs *msg); +void +ipu6_fw_isys_prepare_buf_set(struct isys_fw_msgs *msg, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl); +void +ipu6_fw_isys_dump_stream_cfg(struct device *dev, struct isys_fw_msgs *set); +void +ipu6_fw_isys_dump_frame_buff_set(struct device *dev, struct isys_fw_msgs *set, + unsigned int outputs); + #endif diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index 529973860e9e..9f6f283600c8 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -233,58 +233,6 @@ static int buffer_list_get(struct ipu6_isys_stream *stream, return 0; } -static void -ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, - struct ipu6_fw_isys_frame_buff_set_abi *set) -{ - struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); - struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); - struct ipu6_isys_video_buffer *ivb = - vb2_buffer_to_ipu6_isys_video_buffer(vvb); - - set->output_pins[aq->fw_output].addr = ivb->dma_addr; - set->output_pins[aq->fw_output].out_buf_id = vb->index + 1; -} - -/* - * Convert a buffer list to a isys fw ABI framebuffer set. The - * buffer list is not modified. - */ -#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30) -void -ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, - struct ipu6_isys_stream *stream, - struct ipu6_isys_buffer_list *bl) -{ - struct ipu6_isys_buffer *ib; - - WARN_ON(!bl->nbufs); - - set->send_irq_sof = 1; - set->send_resp_sof = 1; - set->send_irq_eof = 0; - set->send_resp_eof = 0; - - if (stream->streaming) - set->send_irq_capture_ack = 0; - else - set->send_irq_capture_ack = 1; - set->send_irq_capture_done = 0; - - set->send_resp_capture_ack = 1; - set->send_resp_capture_done = 1; - if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) { - set->send_resp_capture_ack = 0; - set->send_resp_capture_done = 0; - } - - list_for_each_entry(ib, &bl->head, head) { - struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); - - ipu6_isys_buf_to_fw_frame_buf_pin(vb, 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) @@ -304,9 +252,7 @@ static int ipu6_isys_stream_start(struct ipu6_isys_video *av, bl = &__bl; do { - struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; struct isys_fw_msgs *msg; - u16 send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE; ret = buffer_list_get(stream, bl); if (ret < 0) @@ -316,16 +262,14 @@ static int ipu6_isys_stream_start(struct ipu6_isys_video *av, if (!msg) return -ENOMEM; - buf = &msg->ipu6.frame; - ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); - ipu6_fw_isys_dump_frame_buff_set(dev, buf, + ipu6_fw_isys_prepare_buf_set(msg, stream, bl); + ipu6_fw_isys_dump_frame_buff_set(dev, msg, stream->nr_output_pins); ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); - ret = ipu6_fw_isys_complex_cmd(stream->isys, - stream->stream_handle, buf, - msg->dma_addr, sizeof(*buf), - send_type); + + ret = ipu6_fw_isys_stream_capture(stream->isys, + stream->stream_handle, msg); } while (!WARN_ON(ret)); return 0; @@ -349,7 +293,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 ipu6_fw_isys_frame_buff_set_abi *buf = NULL; struct ipu6_isys_stream *stream = av->stream; struct ipu6_isys_buffer_list bl; struct isys_fw_msgs *msg; @@ -396,9 +339,8 @@ static void buf_queue(struct vb2_buffer *vb) goto out; } - buf = &msg->ipu6.frame; - ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl); - ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); + ipu6_fw_isys_prepare_buf_set(msg, stream, &bl); + ipu6_fw_isys_dump_frame_buff_set(dev, msg, stream->nr_output_pins); /* * We must queue the buffers in the buffer list to the @@ -408,9 +350,8 @@ static void buf_queue(struct vb2_buffer *vb) */ ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); - ret = ipu6_fw_isys_complex_cmd(stream->isys, stream->stream_handle, - buf, msg->dma_addr, sizeof(*buf), - IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE); + ret = ipu6_fw_isys_stream_capture(stream->isys, stream->stream_handle, + msg); if (ret < 0) dev_err(dev, "send stream capture failed\n"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h index dec1fed44dd2..51de14183ccf 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h @@ -60,10 +60,6 @@ struct ipu6_isys_buffer_list { void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, unsigned long op_flags, enum vb2_buffer_state state); -void -ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, - struct ipu6_isys_stream *stream, - struct ipu6_isys_buffer_list *bl); void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, struct ipu6_fw_isys_resp_info_abi *info); int ipu6_isys_queue_init(struct ipu6_isys_queue *aq); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index fabea6a75d7d..a6e50263f8da 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -450,115 +450,28 @@ static void put_stream_opened(struct ipu6_isys_video *av) spin_unlock_irqrestore(&av->isys->streams_lock, flags); } -static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, - struct ipu6_fw_isys_stream_cfg_data_abi *cfg) -{ - 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; - struct ipu6_isys_queue *aq = &av->aq; - struct v4l2_mbus_framefmt fmt; - const struct ipu6_isys_pixelformat *pfmt = - ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); - struct v4l2_rect v4l2_crop; - struct ipu6_isys *isys = av->isys; - int input_pins = cfg->nof_input_pins++; - int output_pins; - u32 src_stream; - - src_stream = ipu6_isys_get_src_stream_by_src_pad(sd, src_pad->index); - 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; - input_pin->input_res.height = fmt.height; - input_pin->dt = av->dt; - input_pin->bits_per_pix = pfmt->bpp_packed; - input_pin->mapped_dt = 0x40; /* invalid mipi data type */ - input_pin->mipi_decompression = 0; - input_pin->capture_mode = IPU6_FW_ISYS_CAPTURE_MODE_REGULAR; - input_pin->mipi_store_mode = pfmt->bpp == pfmt->bpp_packed ? - IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER : - IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL; - input_pin->crop_first_and_last_lines = v4l2_crop.top & 1; - - output_pins = cfg->nof_output_pins++; - aq->fw_output = output_pins; - stream->output_pins_queue[output_pins] = aq; - - output_pin = &cfg->output_pins[output_pins]; - output_pin->input_pin_id = input_pins; - output_pin->output_res.width = ipu6_isys_get_frame_width(av); - output_pin->output_res.height = ipu6_isys_get_frame_height(av); - - output_pin->stride = ipu6_isys_get_bytes_per_line(av); - if (pfmt->bpp != pfmt->bpp_packed) - output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC; - else - output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_MIPI; - output_pin->ft = pfmt->css_pixelformat; - output_pin->send_irq = 1; - memset(output_pin->ts_offsets, 0, sizeof(output_pin->ts_offsets)); - output_pin->s2m_pixel_soc_pixel_remapping = - S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; - output_pin->csi_be_soc_pixel_remapping = - CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; - - output_pin->snoopable = true; - output_pin->error_handling_enable = false; - output_pin->sensor_type = isys->sensor_type++; - if (isys->sensor_type > isys->pdata->ipdata->sensor_type_end) - isys->sensor_type = isys->pdata->ipdata->sensor_type_start; - - return 0; -} - static int start_stream_firmware(struct ipu6_isys_video *av, struct ipu6_isys_buffer_list *bl) { - struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg; - struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; struct ipu6_isys_stream *stream = av->stream; struct device *dev = &av->isys->adev->auxdev.dev; struct isys_fw_msgs *msg = NULL; - struct ipu6_isys_queue *aq; int ret, retout, tout; - u16 send_type; + bool capture = bl ? true : false; msg = ipu6_get_fw_msg_buf(stream); if (!msg) return -ENOMEM; - stream_cfg = &msg->ipu6.stream; - stream_cfg->src = stream->stream_source; - stream_cfg->vc = stream->vc; - stream_cfg->isl_use = 0; - stream_cfg->sensor_type = IPU6_FW_ISYS_SENSOR_MODE_NORMAL; - - list_for_each_entry(aq, &stream->queues, node) { - struct ipu6_isys_video *__av = ipu6_isys_queue_to_video(aq); - - ret = ipu6_isys_fw_pin_cfg(__av, stream_cfg); - if (ret < 0) { - ipu6_put_fw_msg_buf(av->isys, msg); - return ret; - } + ret = ipu6_fw_isys_prepare_stream_cfg(av, msg); + if (ret < 0) { + ipu6_put_fw_msg_buf(av->isys, msg); + return ret; } - ipu6_fw_isys_dump_stream_cfg(dev, stream_cfg); - - stream->nr_output_pins = stream_cfg->nof_output_pins; - reinit_completion(&stream->stream_open_completion); - ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, - stream_cfg, msg->dma_addr, - sizeof(*stream_cfg), - IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN); + ret = ipu6_fw_isys_stream_open(av->isys, stream->stream_handle, msg); if (ret < 0) { dev_err(dev, "can't open stream (%d)\n", ret); ipu6_put_fw_msg_buf(av->isys, msg); @@ -590,26 +503,16 @@ static int start_stream_firmware(struct ipu6_isys_video *av, ret = -ENOMEM; goto out_put_stream_opened; } - buf = &msg->ipu6.frame; - ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); + + ipu6_fw_isys_prepare_buf_set(msg, stream, bl); ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); } reinit_completion(&stream->stream_start_completion); - if (bl) { - send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; - ipu6_fw_isys_dump_frame_buff_set(dev, buf, - stream_cfg->nof_output_pins); - ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, - buf, msg->dma_addr, - sizeof(*buf), send_type); - } else { - send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; - ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, - send_type); - } + ret = ipu6_fw_isys_stream_start(av->isys, stream->stream_handle, msg, + capture); if (ret < 0) { dev_err(dev, "can't start streaming (%d)\n", ret); @@ -635,9 +538,7 @@ static int start_stream_firmware(struct ipu6_isys_video *av, out_stream_close: reinit_completion(&stream->stream_close_completion); - retout = ipu6_fw_isys_simple_cmd(av->isys, - stream->stream_handle, - IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); + retout = ipu6_fw_isys_stream_close(av->isys, stream->stream_handle); if (retout < 0) { dev_dbg(dev, "can't close stream (%d)\n", retout); goto out_put_stream_opened; @@ -666,9 +567,7 @@ static void stop_streaming_firmware(struct ipu6_isys_video *av) reinit_completion(&stream->stream_stop_completion); - ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, - IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH); - + ret = ipu6_fw_isys_stream_flush(av->isys, stream->stream_handle); if (ret < 0) { dev_err(dev, "can't stop stream (%d)\n", ret); return; @@ -692,8 +591,7 @@ static void close_streaming_firmware(struct ipu6_isys_video *av) reinit_completion(&stream->stream_close_completion); - ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, - IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); + ret = ipu6_fw_isys_stream_close(av->isys, stream->stream_handle); if (ret < 0) { dev_err(dev, "can't close stream (%d)\n", ret); return; From 85eb55c4c33e08afd0980934ef2d989bca196c09 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:13 +0300 Subject: [PATCH 068/596] media: ipu6: Add isys firmware ops Add struct for ipu6 firmware ops. This is a preparation to add support for ipu7 firmware communication. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-bus.h | 3 + drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 71 +++++++++++-------- drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 38 +--------- .../media/pci/intel/ipu6/ipu6-isys-queue.c | 24 ++++--- .../media/pci/intel/ipu6/ipu6-isys-video.c | 35 +++++---- drivers/media/pci/intel/ipu6/ipu6-isys.c | 1 + drivers/media/pci/intel/ipu6/ipu6-isys.h | 32 +++++++++ 7 files changed, 116 insertions(+), 88 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.h b/drivers/media/pci/intel/ipu6/ipu6-bus.h index a08c5468d536..62aa2b090699 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-bus.h +++ b/drivers/media/pci/intel/ipu6/ipu6-bus.h @@ -37,6 +37,7 @@ struct ipu6_auxdrv_data { irqreturn_t (*isr)(struct ipu6_bus_device *adev); irqreturn_t (*isr_threaded)(struct ipu6_bus_device *adev); bool wake_isr_thread; + const struct ipu6_fw_isys_ops *fw_ops; }; #define to_ipu6_bus_device(_dev) \ @@ -45,6 +46,8 @@ struct ipu6_auxdrv_data { container_of(_auxdev, struct ipu6_bus_device, auxdev) #define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev) +extern const struct ipu6_fw_isys_ops ipu6_fw_isys_ops; + struct ipu6_bus_device * ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, void *pdata, const struct ipu6_buttress_ctrl *ctrl, diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c index 9256ea3b68c1..4ee5137f310e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c @@ -126,7 +126,7 @@ static int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, return 0; } -int ipu6_fw_isys_close(struct ipu6_isys *isys) +static int ipu6_fw_isys_close(struct ipu6_isys *isys) { struct device *dev = &isys->adev->auxdev.dev; int retry = IPU6_ISYS_CLOSE_RETRY; @@ -165,7 +165,7 @@ int ipu6_fw_isys_close(struct ipu6_isys *isys) return ret; } -void ipu6_fw_isys_cleanup(struct ipu6_isys *isys) +static void ipu6_fw_isys_cleanup(struct ipu6_isys *isys) { int ret; @@ -316,7 +316,7 @@ static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, return 0; } -int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) +static int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) { struct device *dev = &isys->adev->auxdev.dev; int retry = IPU6_ISYS_OPEN_RETRY; @@ -358,18 +358,19 @@ int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) return ret; } -struct ipu6_fw_isys_resp_info_abi * +static struct ipu6_fw_isys_resp_info_abi * ipu6_fw_isys_get_resp(struct ipu6_isys *isys) { return ipu6_recv_get_token(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); } -void ipu6_fw_isys_put_resp(struct ipu6_isys *isys) +static void ipu6_fw_isys_put_resp(struct ipu6_isys *isys) { ipu6_recv_put_token(isys->fwctx, IPU6_BASE_MSG_RECV_QUEUES); } -void ipu6_fw_isys_dump_stream_cfg(struct device *dev, struct isys_fw_msgs *msg) +static void ipu6_fw_isys_dump_stream_cfg(struct device *dev, + struct isys_fw_msgs *msg) { struct ipu6_fw_isys_stream_cfg_data_abi *cfg = &msg->ipu6.stream; unsigned int i; @@ -446,9 +447,9 @@ void ipu6_fw_isys_dump_stream_cfg(struct device *dev, struct isys_fw_msgs *msg) dev_dbg(dev, "-----------------------------------------------------\n"); } -void -ipu6_fw_isys_dump_frame_buff_set(struct device *dev, struct isys_fw_msgs *msg, - unsigned int outputs) +static void +ipu6_fw_isys_dump_frame_buf_set(struct device *dev, struct isys_fw_msgs *msg, + unsigned int outputs) { struct ipu6_fw_isys_frame_buff_set_abi *buf; unsigned int i; @@ -820,8 +821,8 @@ static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, return 0; } -int ipu6_fw_isys_prepare_stream_cfg(struct ipu6_isys_video *av, - struct isys_fw_msgs *msg) +static int ipu6_fw_isys_prepare_stream_cfg(struct ipu6_isys_video *av, + struct isys_fw_msgs *msg) { struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg; struct device *dev = &av->isys->adev->auxdev.dev; @@ -850,9 +851,9 @@ int ipu6_fw_isys_prepare_stream_cfg(struct ipu6_isys_video *av, return 0; } -int ipu6_fw_isys_stream_open(struct ipu6_isys *isys, - const unsigned int stream_handle, - struct isys_fw_msgs *msg) +static int ipu6_fw_isys_stream_open(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg) { return ipu6_fw_isys_complex_cmd(isys, stream_handle, &msg->ipu6.stream, msg->dma_addr, @@ -860,23 +861,23 @@ int ipu6_fw_isys_stream_open(struct ipu6_isys *isys, IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN); } -int ipu6_fw_isys_stream_close(struct ipu6_isys *isys, - const unsigned int stream_handle) +static int ipu6_fw_isys_stream_close(struct ipu6_isys *isys, + const unsigned int stream_handle) { return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); } -int ipu6_fw_isys_stream_flush(struct ipu6_isys *isys, - const unsigned int stream_handle) +static int ipu6_fw_isys_stream_flush(struct ipu6_isys *isys, + const unsigned int stream_handle) { return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH); } -int ipu6_fw_isys_stream_start(struct ipu6_isys *isys, - const unsigned int stream_handle, - struct isys_fw_msgs *msg, bool capture) +static int ipu6_fw_isys_stream_start(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg, bool capture) { u16 cmd_type; @@ -890,9 +891,9 @@ int ipu6_fw_isys_stream_start(struct ipu6_isys *isys, sizeof(msg->ipu6.stream), cmd_type); } -int ipu6_fw_isys_stream_capture(struct ipu6_isys *isys, - const unsigned int stream_handle, - struct isys_fw_msgs *msg) +static int ipu6_fw_isys_stream_capture(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg) { return ipu6_fw_isys_complex_cmd(isys, stream_handle, &msg->ipu6.stream, msg->dma_addr, @@ -918,10 +919,9 @@ ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, * buffer list is not modified. */ #define IPU6_ISYS_FRAME_NUM_THRESHOLD (30) -void -ipu6_fw_isys_prepare_buf_set(struct isys_fw_msgs *msg, - struct ipu6_isys_stream *stream, - struct ipu6_isys_buffer_list *bl) +static void ipu6_fw_isys_prepare_buf_set(struct isys_fw_msgs *msg, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) { struct ipu6_fw_isys_frame_buff_set_abi *set = &msg->ipu6.frame; struct ipu6_isys_buffer *ib; @@ -952,3 +952,18 @@ ipu6_fw_isys_prepare_buf_set(struct isys_fw_msgs *msg, ipu6_isys_buf_to_fw_frame_buf_pin(vb, set); } } + +const struct ipu6_fw_isys_ops ipu6_fw_isys_ops = { + .init = ipu6_fw_isys_init, + .close = ipu6_fw_isys_close, + .cleanup = ipu6_fw_isys_cleanup, + .prepare_stream_cfg = ipu6_fw_isys_prepare_stream_cfg, + .prepare_buf_set = ipu6_fw_isys_prepare_buf_set, + .stream_open = ipu6_fw_isys_stream_open, + .stream_start = ipu6_fw_isys_stream_start, + .stream_capture = ipu6_fw_isys_stream_capture, + .stream_flush = ipu6_fw_isys_stream_flush, + .stream_close = ipu6_fw_isys_stream_close, + .dump_stream_cfg = ipu6_fw_isys_dump_stream_cfg, + .dump_frame_buf_set = ipu6_fw_isys_dump_frame_buf_set, +}; diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h index b589ae7fbd4e..2a679d995961 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h @@ -559,42 +559,10 @@ struct ipu6_fw_proxy_send_queue_token { u32 value; }; -int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams); -int ipu6_fw_isys_close(struct ipu6_isys *isys); -int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, - unsigned int req_id, - unsigned int index, - unsigned int offset, u32 value); -void ipu6_fw_isys_cleanup(struct ipu6_isys *isys); -struct ipu6_fw_isys_resp_info_abi * -ipu6_fw_isys_get_resp(struct ipu6_isys *isys); -void ipu6_fw_isys_put_resp(struct ipu6_isys *isys); +int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, unsigned int req_id, + unsigned int index, unsigned int offset, + u32 value); int ipu6_isys_isr_one(struct ipu6_bus_device *adev); irqreturn_t ipu6_isys_isr(struct ipu6_bus_device *adev); -int ipu6_fw_isys_stream_open(struct ipu6_isys *isys, - const unsigned int stream_handle, - struct isys_fw_msgs *msg); -int ipu6_fw_isys_stream_close(struct ipu6_isys *isys, - const unsigned int stream_handle); -int ipu6_fw_isys_stream_flush(struct ipu6_isys *isys, - const unsigned int stream_handle); -int ipu6_fw_isys_stream_start(struct ipu6_isys *isys, - const unsigned int stream_handle, - struct isys_fw_msgs *msg, bool capture); -int ipu6_fw_isys_stream_capture(struct ipu6_isys *isys, - const unsigned int stream_handle, - struct isys_fw_msgs *msg); -int ipu6_fw_isys_prepare_stream_cfg(struct ipu6_isys_video *av, - struct isys_fw_msgs *msg); -void -ipu6_fw_isys_prepare_buf_set(struct isys_fw_msgs *msg, - struct ipu6_isys_stream *stream, - struct ipu6_isys_buffer_list *bl); -void -ipu6_fw_isys_dump_stream_cfg(struct device *dev, struct isys_fw_msgs *set); -void -ipu6_fw_isys_dump_frame_buff_set(struct device *dev, struct isys_fw_msgs *set, - unsigned int outputs); - #endif diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index 9f6f283600c8..4cb1741445c6 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -237,8 +237,10 @@ static int buffer_list_get(struct ipu6_isys_stream *stream, static int ipu6_isys_stream_start(struct ipu6_isys_video *av, struct ipu6_isys_buffer_list *bl) { + struct ipu6_bus_device *adev = av->isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; struct ipu6_isys_stream *stream = av->stream; - struct device *dev = &stream->isys->adev->auxdev.dev; + struct device *dev = &adev->auxdev.dev; struct ipu6_isys_buffer_list __bl; int ret; @@ -262,14 +264,13 @@ static int ipu6_isys_stream_start(struct ipu6_isys_video *av, if (!msg) return -ENOMEM; - ipu6_fw_isys_prepare_buf_set(msg, stream, bl); - ipu6_fw_isys_dump_frame_buff_set(dev, msg, - stream->nr_output_pins); + fw_ops->prepare_buf_set(msg, stream, bl); + fw_ops->dump_frame_buf_set(dev, msg, stream->nr_output_pins); ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); - ret = ipu6_fw_isys_stream_capture(stream->isys, - stream->stream_handle, msg); + ret = fw_ops->stream_capture(stream->isys, + stream->stream_handle, msg); } while (!WARN_ON(ret)); return 0; @@ -288,11 +289,13 @@ static void buf_queue(struct vb2_buffer *vb) { struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_bus_device *adev = av->isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); struct ipu6_isys_video_buffer *ivb = vb2_buffer_to_ipu6_isys_video_buffer(vvb); struct ipu6_isys_buffer *ib = &ivb->ib; - struct device *dev = &av->isys->adev->auxdev.dev; + struct device *dev = &adev->auxdev.dev; struct ipu6_isys_stream *stream = av->stream; struct ipu6_isys_buffer_list bl; struct isys_fw_msgs *msg; @@ -339,8 +342,8 @@ static void buf_queue(struct vb2_buffer *vb) goto out; } - ipu6_fw_isys_prepare_buf_set(msg, stream, &bl); - ipu6_fw_isys_dump_frame_buff_set(dev, msg, stream->nr_output_pins); + fw_ops->prepare_buf_set(msg, stream, &bl); + fw_ops->dump_frame_buf_set(dev, msg, stream->nr_output_pins); /* * We must queue the buffers in the buffer list to the @@ -350,8 +353,7 @@ static void buf_queue(struct vb2_buffer *vb) */ ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); - ret = ipu6_fw_isys_stream_capture(stream->isys, stream->stream_handle, - msg); + ret = fw_ops->stream_capture(stream->isys, stream->stream_handle, msg); if (ret < 0) dev_err(dev, "send stream capture failed\n"); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index a6e50263f8da..803450b0156e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -453,8 +453,10 @@ static void put_stream_opened(struct ipu6_isys_video *av) static int start_stream_firmware(struct ipu6_isys_video *av, struct ipu6_isys_buffer_list *bl) { + struct ipu6_bus_device *adev = av->isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; struct ipu6_isys_stream *stream = av->stream; - struct device *dev = &av->isys->adev->auxdev.dev; + struct device *dev = &adev->auxdev.dev; struct isys_fw_msgs *msg = NULL; int ret, retout, tout; bool capture = bl ? true : false; @@ -463,7 +465,7 @@ static int start_stream_firmware(struct ipu6_isys_video *av, if (!msg) return -ENOMEM; - ret = ipu6_fw_isys_prepare_stream_cfg(av, msg); + ret = fw_ops->prepare_stream_cfg(av, msg); if (ret < 0) { ipu6_put_fw_msg_buf(av->isys, msg); return ret; @@ -471,7 +473,7 @@ static int start_stream_firmware(struct ipu6_isys_video *av, reinit_completion(&stream->stream_open_completion); - ret = ipu6_fw_isys_stream_open(av->isys, stream->stream_handle, msg); + ret = fw_ops->stream_open(av->isys, stream->stream_handle, msg); if (ret < 0) { dev_err(dev, "can't open stream (%d)\n", ret); ipu6_put_fw_msg_buf(av->isys, msg); @@ -504,15 +506,15 @@ static int start_stream_firmware(struct ipu6_isys_video *av, goto out_put_stream_opened; } - ipu6_fw_isys_prepare_buf_set(msg, stream, bl); + fw_ops->prepare_buf_set(msg, stream, bl); ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); } reinit_completion(&stream->stream_start_completion); - ret = ipu6_fw_isys_stream_start(av->isys, stream->stream_handle, msg, - capture); + ret = fw_ops->stream_start(av->isys, stream->stream_handle, msg, + capture); if (ret < 0) { dev_err(dev, "can't start streaming (%d)\n", ret); @@ -538,7 +540,7 @@ static int start_stream_firmware(struct ipu6_isys_video *av, out_stream_close: reinit_completion(&stream->stream_close_completion); - retout = ipu6_fw_isys_stream_close(av->isys, stream->stream_handle); + retout = fw_ops->stream_close(av->isys, stream->stream_handle); if (retout < 0) { dev_dbg(dev, "can't close stream (%d)\n", retout); goto out_put_stream_opened; @@ -561,13 +563,15 @@ static int start_stream_firmware(struct ipu6_isys_video *av, static void stop_streaming_firmware(struct ipu6_isys_video *av) { - struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_bus_device *adev = av->isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; + struct device *dev = &adev->auxdev.dev; struct ipu6_isys_stream *stream = av->stream; int ret, tout; reinit_completion(&stream->stream_stop_completion); - ret = ipu6_fw_isys_stream_flush(av->isys, stream->stream_handle); + ret = fw_ops->stream_flush(av->isys, stream->stream_handle); if (ret < 0) { dev_err(dev, "can't stop stream (%d)\n", ret); return; @@ -585,13 +589,15 @@ static void stop_streaming_firmware(struct ipu6_isys_video *av) static void close_streaming_firmware(struct ipu6_isys_video *av) { + struct ipu6_bus_device *adev = av->isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; struct ipu6_isys_stream *stream = av->stream; - struct device *dev = &av->isys->adev->auxdev.dev; + struct device *dev = &adev->auxdev.dev; int ret, tout; reinit_completion(&stream->stream_close_completion); - ret = ipu6_fw_isys_stream_close(av->isys, stream->stream_handle); + ret = fw_ops->stream_close(av->isys, stream->stream_handle); if (ret < 0) { dev_err(dev, "can't close stream (%d)\n", ret); return; @@ -990,6 +996,7 @@ static const struct v4l2_file_operations isys_fops = { int ipu6_isys_fw_open(struct ipu6_isys *isys) { struct ipu6_bus_device *adev = isys->adev; + const struct ipu6_fw_isys_ops *fw_ops = adev->auxdrv_data->fw_ops; const struct ipu6_isys_internal_pdata *ipdata = isys->pdata->ipdata; int ret; @@ -1018,10 +1025,10 @@ int ipu6_isys_fw_open(struct ipu6_isys *isys) * restarting isys we can safely delete old context. */ dev_warn(&adev->auxdev.dev, "clearing old context\n"); - ipu6_fw_isys_cleanup(isys); + fw_ops->cleanup(isys); } - ret = ipu6_fw_isys_init(isys, ipdata->num_parallel_streams); + ret = fw_ops->init(isys, ipdata->num_parallel_streams); if (ret < 0) goto out; @@ -1044,7 +1051,7 @@ void ipu6_isys_fw_close(struct ipu6_isys *isys) isys->ref_count--; if (!isys->ref_count) { - ipu6_fw_isys_close(isys); + isys->adev->auxdrv_data->fw_ops->close(isys); if (isys->fwctx) { isys->need_reset = true; dev_warn(&isys->adev->auxdev.dev, diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index bb3c0d6bc6c9..159e769931b7 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -1068,6 +1068,7 @@ static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = { .isr = ipu6_isys_isr, .isr_threaded = NULL, .wake_isr_thread = false, + .fw_ops = &ipu6_fw_isys_ops, }; static const struct auxiliary_device_id ipu6_isys_id_table[] = { diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h index f39566513a87..fa9bc2730d64 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -174,6 +174,38 @@ struct isys_fw_msgs { dma_addr_t dma_addr; }; +struct ipu6_fw_isys_ops { + int (*init)(struct ipu6_isys *isys, unsigned int num_streams); + int (*close)(struct ipu6_isys *isys); + int (*send_cmd)(struct ipu6_isys *isys, + const unsigned int stream_handle, + void *cpu_mapped_buf, + dma_addr_t dma_mapped_buf, + size_t size, u16 send_type); + void (*cleanup)(struct ipu6_isys *isys); + int (*prepare_stream_cfg)(struct ipu6_isys_video *av, + struct isys_fw_msgs *msg); + void (*prepare_buf_set)(struct isys_fw_msgs *msg, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl); + int (*stream_open)(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg); + int (*stream_start)(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg, bool capture); + int (*stream_capture)(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg); + int (*stream_flush)(struct ipu6_isys *isys, + const unsigned int stream_handle); + int (*stream_close)(struct ipu6_isys *isys, + const unsigned int stream_handle); + void (*dump_stream_cfg)(struct device *dev, struct isys_fw_msgs *msg); + void (*dump_frame_buf_set)(struct device *dev, struct isys_fw_msgs *msg, + unsigned int outputs); +}; + struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream); void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, struct isys_fw_msgs *msg); void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys); From 87be1d46a260772d42a0ebda2def8df1750c2bc8 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:14 +0300 Subject: [PATCH 069/596] media: ipu6: Add ipu7 fw start/stop functionality The ipu7 firmware start/stop sequence differs from ipu6. Add ipu7-specific functions to handle this. Signed-off-by: Antti Laakso [Sakari Ailus: Use ipu6_ prefix for ipu7 related global symbols.] Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu7-boot.c | 405 ++++++++++++++++++ drivers/media/pci/intel/ipu6/ipu7-boot.h | 46 ++ .../media/pci/intel/ipu6/ipu7-platform-regs.h | 12 + 3 files changed, 463 insertions(+) create mode 100644 drivers/media/pci/intel/ipu6/ipu7-boot.c create mode 100644 drivers/media/pci/intel/ipu6/ipu7-boot.h create mode 100644 drivers/media/pci/intel/ipu6/ipu7-platform-regs.h diff --git a/drivers/media/pci/intel/ipu6/ipu7-boot.c b/drivers/media/pci/intel/ipu6/ipu7-boot.c new file mode 100644 index 000000000000..952c73efd237 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-boot.c @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 - 2026 Intel Corporation + */ + +#include +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-buttress.h" +#include "ipu6-dma.h" +#include "ipu6-isys.h" +#include "ipu6-platform-buttress-regs.h" +#include "ipu7-boot.h" +#include "ipu7-platform-regs.h" + +#define IPU7_FW_START_STOP_TIMEOUT 2000 +#define IPU7_BOOT_CELL_RESET_TIMEOUT (2 * USEC_PER_SEC) +#define IPU7_BOOT_STATE_CRITICAL(s) (((s) & 0xffff0000U) == 0xdead0000U) +#define IPU7_BOOT_STATE_READY(s) ((s) == 0x57a7e100U) +#define IPU7_BOOT_STATE_INACTIVE(s) ((s) == 0x57a7e300U) +#define IPU7_BUTTRESS_REG_FW_BOOT_PARAMS0 0x4000 +#define IPU7_BUTTRESS_FW_BOOT_PARAMS_ENTRY(i) \ + (IPU7_BUTTRESS_REG_FW_BOOT_PARAMS0 + ((i) * 4U)) + +struct boot_regs { + u32 base; + u32 dmem_address; + u32 status_ctrl_reg; + u32 fw_start_address_reg; + u32 fw_code_base_reg; +}; + +enum ipu7_boot_reg_id { + IPU7_FW_BOOT_CONFIG_ID = 0, + IPU7_FW_BOOT_STATE_ID = 1, + IPU7_FW_BOOT_SYSCOM_QUEUE_INDICES_BASE_ID = 2, + IPU7_FW_BOOT_UNTRUSTED_ADDR_MIN_ID = 3, + IPU7_FW_BOOT_MESSAGING_VERSION_ID = 4, + IPU7_FW_BOOT_ID_MAX, +}; + +enum ipu7_boot_state { + IPU7_FW_BOOT_STATE_UNINIT = 0x57a7e000U, + IPU7_FW_BOOT_STATE_READY = 0x57a7e100U, + IPU7_FW_BOOT_STATE_SHUTDOWN_CMD = 0x57a7f001U, + IPU7_FW_BOOT_STATE_INACTIVE = 0x57a7e300U, +}; + +static const struct boot_regs boot_regs[IPU_SUBSYS_NUM] = { + [IPU_ISYS] = { + .dmem_address = IPU7_ISYS_DMEM_OFFSET, + .status_ctrl_reg = IPU7_BUTTRESS_REG_ISYS_UCX_CTRL_STATUS, + .fw_start_address_reg = IPU7_BUTTRESS_REG_ISYS_UCX_START_ADDR, + .fw_code_base_reg = IPU7_IS_UC_CTRL_BASE + }, + [IPU_PSYS] = { + .dmem_address = IPU7_PSYS_DMEM_OFFSET, + .status_ctrl_reg = IPU7_BUTTRESS_REG_PSYS_UCX_CTRL_STATUS, + .fw_start_address_reg = IPU7_BUTTRESS_REG_PSYS_UCX_START_ADDR, + .fw_code_base_reg = IPU7_PS_UC_CTRL_BASE + } +}; + +static u32 get_fw_boot_reg_addr(const struct ipu6_bus_device *adev, + enum ipu7_boot_reg_id reg) +{ + u32 base = (adev->ctrl->subsys_id == IPU_ISYS) ? + 0U : (u32)IPU7_FW_BOOT_ID_MAX; + + return IPU7_BUTTRESS_FW_BOOT_PARAMS_ENTRY(base + (u32)reg); +} + +static void write_fw_boot_param(const struct ipu6_bus_device *adev, + enum ipu7_boot_reg_id reg, + u32 val) +{ + void __iomem *base = adev->isp->base; + + dev_dbg(&adev->auxdev.dev, + "write boot param reg: %d addr: %x val: 0x%x\n", + reg, get_fw_boot_reg_addr(adev, reg), val); + writel(val, base + get_fw_boot_reg_addr(adev, reg)); +} + +static u32 read_fw_boot_param(const struct ipu6_bus_device *adev, + enum ipu7_boot_reg_id reg) +{ + void __iomem *base = adev->isp->base; + + return readl(base + get_fw_boot_reg_addr(adev, reg)); +} + +static int ipu7_boot_cell_reset(const struct ipu6_bus_device *adev) +{ + const struct device *dev = &adev->auxdev.dev; + const struct boot_regs *regs = &boot_regs[adev->ctrl->subsys_id]; + u32 ucx_ctrl_status = regs->status_ctrl_reg; + u32 timeout = IPU7_BOOT_CELL_RESET_TIMEOUT; + void __iomem *base = adev->isp->base; + u32 val, val2; + int ret; + + val = readl(base + ucx_ctrl_status); + val |= IPU7_UCX_CTL_RESET; + val &= ~IPU7_UCX_CTL_RUN; + + writel(val, base + ucx_ctrl_status); + + ret = readl_poll_timeout(base + ucx_ctrl_status, val2, + (val2 & 0x3U) == (val & 0x3U), 100, timeout); + if (ret) { + dev_err(dev, "cell enter reset timeout. status: 0x%x\n", val2); + return -ETIMEDOUT; + } + + val = readl(base + ucx_ctrl_status); + val &= ~(IPU7_UCX_CTL_RESET | IPU7_UCX_CTL_RUN); + writel(val, base + ucx_ctrl_status); + + ret = readl_poll_timeout(base + ucx_ctrl_status, val2, + (val2 & 0x3U) == (val & 0x3U), 100, timeout); + if (ret) { + dev_err(dev, "cell exit reset timeout. status: 0x%x\n", val2); + return -ETIMEDOUT; + } + + return 0; +} + +static void ipu7_boot_cell_start(const struct ipu6_bus_device *adev) +{ + const struct boot_regs *regs = &boot_regs[adev->ctrl->subsys_id]; + void __iomem *base = adev->isp->base; + u32 val; + + val = readl(base + regs->status_ctrl_reg); + WARN_ON(val & (IPU7_UCX_CTL_RESET | IPU7_UCX_CTL_RUN)); + + val &= ~IPU7_UCX_CTL_RESET; + val |= IPU7_UCX_CTL_RUN; + writel(val, base + regs->status_ctrl_reg); +} + +static void ipu7_boot_cell_stop(const struct ipu6_bus_device *adev) +{ + const struct boot_regs *regs = &boot_regs[adev->ctrl->subsys_id]; + void __iomem *base = adev->isp->base; + u32 val; + + val = readl(base + regs->status_ctrl_reg); + val &= ~IPU7_UCX_CTL_RUN; + writel(val, base + regs->status_ctrl_reg); + + /* Wait for uC transactions complete */ + usleep_range(10, 20); + + val = readl(base + regs->status_ctrl_reg); + val |= IPU7_UCX_CTL_RESET; + writel(val, base + regs->status_ctrl_reg); +} + +static int ipu7_boot_cell_init(const struct ipu6_bus_device *adev) +{ + const struct boot_regs *regs = &boot_regs[adev->ctrl->subsys_id]; + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu7_fw_com_context *fwctx = isys->fwctx; + void __iomem *base = adev->isp->base; + + writel(fwctx->fw_entry, base + regs->fw_start_address_reg); + + return ipu7_boot_cell_reset(adev); +} + +static void init_cfg_versions(struct ipu7_boot_abi_cfg *boot_cfg, u32 length, u8 major) +{ + boot_cfg->length = length; + boot_cfg->config_version.major = 1U; + boot_cfg->config_version.minor = 0U; + boot_cfg->config_version.subminor = 0U; + boot_cfg->config_version.patch = 0U; + + boot_cfg->client_version_support.num_versions = 1U; + boot_cfg->client_version_support.versions[0].major = major; + boot_cfg->client_version_support.versions[0].minor = 0U; + boot_cfg->client_version_support.versions[0].subminor = 0U; + boot_cfg->client_version_support.versions[0].patch = 0U; +} + +int ipu6_ipu7_init_boot_config(struct ipu6_bus_device *adev, + struct ipu7_fw_com_queue_config *qconfigs, + int num_queues, u32 uc_freq, + dma_addr_t subsys_config, u8 major) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu7_fw_com_context *fwctx = isys->fwctx; + struct ipu7_boot_abi_cfg *boot_config; + struct ipu7_fw_com_queue_params_config *cfgs; + struct device *dev = &adev->auxdev.dev; + u32 total_queue_size_aligned = 0; + dma_addr_t queue_mem_dma_ptr; + void *queue_mem_ptr; + unsigned int i; + + /* Allocate boot config. */ + fwctx->boot_config_size = + sizeof(*cfgs) * num_queues + sizeof(*boot_config); + fwctx->boot_config = ipu6_dma_alloc(adev, fwctx->boot_config_size, + &fwctx->boot_config_dma_addr, + GFP_KERNEL, 0); + if (!fwctx->boot_config) { + dev_err(dev, "Failed to allocate boot config.\n"); + return -ENOMEM; + } + + boot_config = fwctx->boot_config; + memset(boot_config, 0, sizeof(*boot_config)); + init_cfg_versions(boot_config, fwctx->boot_config_size, major); + boot_config->subsys_config = subsys_config; + + boot_config->uc_tile_frequency = uc_freq; + boot_config->uc_tile_frequency_units = 0; + boot_config->fw_com_config.max_output_queues = + fwctx->num_output_queues; + boot_config->fw_com_config.max_input_queues = + fwctx->num_input_queues; + + ipu6_dma_sync_single(adev, fwctx->boot_config_dma_addr, + fwctx->boot_config_size); + + for (i = 0; i < num_queues; i++) { + u32 queue_size = qconfigs[i].max_capacity * + qconfigs[i].token_size_in_bytes; + + queue_size = ALIGN(queue_size, 64U); + total_queue_size_aligned += queue_size; + qconfigs[i].queue_size = queue_size; + } + + /* Allocate queue memory */ + fwctx->queue_mem = ipu6_dma_alloc(adev, total_queue_size_aligned, + &fwctx->queue_mem_dma_addr, + GFP_KERNEL, 0); + if (!fwctx->queue_mem) { + dev_err(dev, "Failed to allocate queue memory.\n"); + return -ENOMEM; + } + fwctx->queue_mem_size = total_queue_size_aligned; + + cfgs = ipu7_fw_com_get_queue_config(&boot_config->fw_com_config); + queue_mem_ptr = fwctx->queue_mem; + queue_mem_dma_ptr = fwctx->queue_mem_dma_addr; + for (i = 0; i < num_queues; i++) { + cfgs[i].token_array_mem = queue_mem_dma_ptr; + cfgs[i].max_capacity = qconfigs[i].max_capacity; + cfgs[i].token_size_in_bytes = qconfigs[i].token_size_in_bytes; + qconfigs[i].token_array_mem = queue_mem_ptr; + queue_mem_dma_ptr += qconfigs[i].queue_size; + queue_mem_ptr += qconfigs[i].queue_size; + } + + ipu6_dma_sync_single(adev, fwctx->queue_mem_dma_addr, + total_queue_size_aligned); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_ipu7_init_boot_config, "INTEL_IPU6"); + +void ipu6_ipu7_release_boot_config(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu7_fw_com_context *fwctx; + + if (!isys || !isys->fwctx) + return; + + fwctx = isys->fwctx; + + if (fwctx->queue_mem) { + ipu6_dma_free(adev, fwctx->queue_mem_size, + fwctx->queue_mem, + fwctx->queue_mem_dma_addr, 0); + fwctx->queue_mem = NULL; + fwctx->queue_mem_dma_addr = 0; + } + + if (fwctx->boot_config) { + ipu6_dma_free(adev, fwctx->boot_config_size, + fwctx->boot_config, + fwctx->boot_config_dma_addr, 0); + fwctx->boot_config = NULL; + fwctx->boot_config_dma_addr = 0; + } +} +EXPORT_SYMBOL_NS_GPL(ipu6_ipu7_release_boot_config, "INTEL_IPU6"); + +int ipu6_ipu7_boot_start_fw(const struct ipu6_bus_device *adev) +{ + const struct device *dev = &adev->auxdev.dev; + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu7_fw_com_context *fwctx = isys->fwctx; + u32 timeout = IPU7_FW_START_STOP_TIMEOUT; + void __iomem *base = adev->isp->base; + u32 boot_state, last_boot_state; + u32 indices_addr, msg_ver, id; + int ret; + + ret = ipu7_boot_cell_init(adev); + if (ret) + return ret; + + /* store "uninit" state to boot state reg */ + write_fw_boot_param(adev, IPU7_FW_BOOT_STATE_ID, + IPU7_FW_BOOT_STATE_UNINIT); + /* Set registers to zero, recommended for diagnostics. */ + write_fw_boot_param(adev, + IPU7_FW_BOOT_SYSCOM_QUEUE_INDICES_BASE_ID, 0); + write_fw_boot_param(adev, IPU7_FW_BOOT_MESSAGING_VERSION_ID, 0); + /* store firmware configuration address */ + write_fw_boot_param(adev, IPU7_FW_BOOT_CONFIG_ID, + fwctx->boot_config_dma_addr); + + ipu7_boot_cell_start(adev); + + last_boot_state = IPU7_FW_BOOT_STATE_UNINIT; + while (timeout--) { + boot_state = read_fw_boot_param(adev, + IPU7_FW_BOOT_STATE_ID); + if (boot_state != last_boot_state) { + dev_dbg(dev, "boot state changed from 0x%x to 0x%x\n", + last_boot_state, boot_state); + last_boot_state = boot_state; + } + if (IPU7_BOOT_STATE_CRITICAL(boot_state) || + IPU7_BOOT_STATE_READY(boot_state)) + break; + usleep_range(1000, 1200); + } + + if (IPU7_BOOT_STATE_CRITICAL(boot_state)) { + dev_err(dev, "critical boot state error 0x%x\n", boot_state); + return -EINVAL; + } else if (!IPU7_BOOT_STATE_READY(boot_state)) { + dev_err(dev, "fw boot timeout. state: 0x%x\n", boot_state); + return -ETIMEDOUT; + } + dev_dbg(dev, "fw boot done.\n"); + + id = IPU7_FW_BOOT_SYSCOM_QUEUE_INDICES_BASE_ID; + indices_addr = read_fw_boot_param(adev, id); + fwctx->queue_indices = base + indices_addr; + dev_dbg(dev, "fw queue indices offset is 0x%x\n", indices_addr); + + msg_ver = read_fw_boot_param(adev, + IPU7_FW_BOOT_MESSAGING_VERSION_ID); + dev_dbg(dev, "ipu message version is 0x%08x\n", msg_ver); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_ipu7_boot_start_fw, "INTEL_IPU6"); + +int ipu6_ipu7_boot_stop_fw(const struct ipu6_bus_device *adev) +{ + const struct device *dev = &adev->auxdev.dev; + u32 timeout = IPU7_FW_START_STOP_TIMEOUT; + u32 boot_state; + + boot_state = read_fw_boot_param(adev, IPU7_FW_BOOT_STATE_ID); + if (IPU7_BOOT_STATE_CRITICAL(boot_state) || + !IPU7_BOOT_STATE_READY(boot_state)) { + dev_err(dev, "fw not ready for shutdown, state 0x%x\n", + boot_state); + return -EBUSY; + } + + /* Issue shutdown to start shutdown process */ + dev_dbg(dev, "stopping fw...\n"); + write_fw_boot_param(adev, IPU7_FW_BOOT_STATE_ID, + IPU7_FW_BOOT_STATE_SHUTDOWN_CMD); + while (timeout--) { + boot_state = read_fw_boot_param(adev, + IPU7_FW_BOOT_STATE_ID); + if (IPU7_BOOT_STATE_CRITICAL(boot_state) || + IPU7_BOOT_STATE_INACTIVE(boot_state)) + break; + usleep_range(1000, 1200); + } + + if (IPU7_BOOT_STATE_CRITICAL(boot_state)) { + dev_err(dev, "critical boot state error 0x%x\n", boot_state); + return -EINVAL; + } else if (!IPU7_BOOT_STATE_INACTIVE(boot_state)) { + dev_err(dev, "stop fw timeout. state: 0x%x\n", boot_state); + return -ETIMEDOUT; + } + + ipu7_boot_cell_stop(adev); + dev_dbg(dev, "stop fw done.\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_ipu7_boot_stop_fw, "INTEL_IPU6"); diff --git a/drivers/media/pci/intel/ipu6/ipu7-boot.h b/drivers/media/pci/intel/ipu6/ipu7-boot.h new file mode 100644 index 000000000000..f2172b53bed5 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-boot.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_BOOT_H +#define IPU7_BOOT_H + +#include "ipu7-fw-com.h" + +#define IPU7_BOOT_MSG_VER_MAX_ENTRIES 3U + +struct ipu7_boot_abi_version { + u8 patch; + u8 subminor; + u8 minor; + u8 major; +}; + +struct ipu7_boot_abi_msg_versions { + u8 num_versions; + u8 reserved[3]; + struct ipu7_boot_abi_version versions[IPU7_BOOT_MSG_VER_MAX_ENTRIES]; +}; + +struct ipu7_boot_abi_cfg { + u32 length; + struct ipu7_boot_abi_version config_version; + struct ipu7_boot_abi_msg_versions client_version_support; + u32 pkg_dir; + u32 subsys_config; + u32 uc_tile_frequency; + u16 checksum; + u8 uc_tile_frequency_units; + u8 padding[1]; + u32 reserved[58]; + struct ipu7_fw_com_config fw_com_config; +} __packed; + +int ipu6_ipu7_init_boot_config(struct ipu6_bus_device *adev, + struct ipu7_fw_com_queue_config *qconfigs, + int num_queues, u32 uc_freq, + dma_addr_t subsys_config, u8 major); +void ipu6_ipu7_release_boot_config(struct ipu6_bus_device *adev); +int ipu6_ipu7_boot_start_fw(const struct ipu6_bus_device *adev); +int ipu6_ipu7_boot_stop_fw(const struct ipu6_bus_device *adev); + +#endif diff --git a/drivers/media/pci/intel/ipu6/ipu7-platform-regs.h b/drivers/media/pci/intel/ipu6/ipu7-platform-regs.h new file mode 100644 index 000000000000..3ca9d65e58b9 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-platform-regs.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_PLATFORM_REGS_H +#define IPU7_PLATFORM_REGS_H + +#define IPU7_IS_UC_CTRL_BASE 0x230000 +#define IPU7_ISYS_DMEM_OFFSET 0x200000 +#define IPU7_PS_UC_CTRL_BASE 0x130000 +#define IPU7_PSYS_DMEM_OFFSET 0x100000 + +#endif From a90a8f700e6aab2bd5f38674efe858dd87d3a9c7 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:15 +0300 Subject: [PATCH 070/596] media: ipu6: Add ipu7 fw com methods The get/put token methods read/write to ipu7 firmware message queues. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/Makefile | 4 +- drivers/media/pci/intel/ipu6/ipu7-fw-com.c | 74 ++++++++++++++++++++++ drivers/media/pci/intel/ipu6/ipu7-fw-com.h | 51 +++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 drivers/media/pci/intel/ipu6/ipu7-fw-com.c create mode 100644 drivers/media/pci/intel/ipu6/ipu7-fw-com.h diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile index c0daf0995b22..406e2e19df17 100644 --- a/drivers/media/pci/intel/ipu6/Makefile +++ b/drivers/media/pci/intel/ipu6/Makefile @@ -8,7 +8,9 @@ intel-ipu6-y := ipu6.o \ ipu7-mmu-hw.o \ ipu6-buttress.o \ ipu6-cpd.o \ - ipu6-fw-com.o + ipu6-fw-com.o \ + ipu7-fw-com.o \ + ipu7-boot.o obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6.o diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-com.c b/drivers/media/pci/intel/ipu6/ipu7-fw-com.c new file mode 100644 index 000000000000..7dd1e683aa92 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-com.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Intel Corporation + */ + +#include + +#include "ipu7-fw-com.h" + +static void __iomem *ipu7_fw_com_get_indices(struct ipu7_fw_com_context *ctx, + u32 q) +{ + return ctx->queue_indices + (q * sizeof(struct ipu7_fw_com_queue_indices)); +} + +void *ipu7_fw_com_get_token(struct ipu7_fw_com_context *ctx, int q) +{ + struct ipu7_fw_com_queue_config *queue_params = &ctx->queue_configs[q]; + void __iomem *queue_indices = ipu7_fw_com_get_indices(ctx, q); + u32 write_index = readl(queue_indices + + offsetof(struct ipu7_fw_com_queue_indices, + write_index)); + u32 read_index = readl(queue_indices + + offsetof(struct ipu7_fw_com_queue_indices, + read_index)); + void *token = NULL; + + if (q < ctx->num_output_queues) { + /* Output queue */ + bool empty = (write_index == read_index); + + if (!empty) + token = queue_params->token_array_mem + + read_index * + queue_params->token_size_in_bytes; + } else { + /* Input queue */ + bool full = (read_index == ((write_index + 1U) % + (u32)queue_params->max_capacity)); + + if (!full) + token = queue_params->token_array_mem + + write_index * queue_params->token_size_in_bytes; + } + return token; +} +EXPORT_SYMBOL_NS_GPL(ipu7_fw_com_get_token, "INTEL_IPU6"); + +void ipu7_fw_com_put_token(struct ipu7_fw_com_context *ctx, int q) +{ + struct ipu7_fw_com_queue_config *queue_params = &ctx->queue_configs[q]; + void __iomem *queue_indices = ipu7_fw_com_get_indices(ctx, q); + u32 offset, index; + + if (q < ctx->num_output_queues) + /* Output queue */ + offset = offsetof(struct ipu7_fw_com_queue_indices, read_index); + + else + /* Input queue */ + offset = offsetof(struct ipu7_fw_com_queue_indices, write_index); + + index = readl(queue_indices + offset); + writel((index + 1U) % queue_params->max_capacity, + queue_indices + offset); +} +EXPORT_SYMBOL_NS_GPL(ipu7_fw_com_put_token, "INTEL_IPU6"); + +struct ipu7_fw_com_queue_params_config * +ipu7_fw_com_get_queue_config(struct ipu7_fw_com_config *config) +{ + return (struct ipu7_fw_com_queue_params_config *)(&config[1]); +} +EXPORT_SYMBOL_NS_GPL(ipu7_fw_com_get_queue_config, "INTEL_IPU6"); diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-com.h b/drivers/media/pci/intel/ipu6/ipu7-fw-com.h new file mode 100644 index 000000000000..10c09e759bed --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-com.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_FW_COM_H +#define IPU7_FW_COM_H + +#include + +struct ipu7_fw_com_queue_config { + void *token_array_mem; + u32 queue_size; + u16 token_size_in_bytes; + u16 max_capacity; +}; + +struct ipu7_fw_com_context { + u16 num_input_queues; + u16 num_output_queues; + struct ipu7_fw_com_queue_config *queue_configs; + void __iomem *queue_indices; + dma_addr_t queue_mem_dma_addr; + void *queue_mem; + u32 queue_mem_size; + struct ipu7_boot_abi_cfg *boot_config; + dma_addr_t boot_config_dma_addr; + u32 boot_config_size; + u32 fw_entry; +}; + +struct ipu7_fw_com_queue_params_config { + u32 token_array_mem; + u16 token_size_in_bytes; + u16 max_capacity; +}; + +struct ipu7_fw_com_config { + u16 max_output_queues; + u16 max_input_queues; +}; + +struct ipu7_fw_com_queue_indices { + u32 read_index; + u32 write_index; +}; + +void ipu7_fw_com_put_token(struct ipu7_fw_com_context *ctx, int q); +void *ipu7_fw_com_get_token(struct ipu7_fw_com_context *ctx, int q); +struct ipu7_fw_com_queue_params_config * +ipu7_fw_com_get_queue_config(struct ipu7_fw_com_config *config); + +#endif /* IPU7_FW_COM_H */ From 9ab793dbc176a2428e6eda02077e6e05515655ab Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:16 +0300 Subject: [PATCH 071/596] media: ipu6: Add ipu7 fw isys ops The ipu7 isys firmware functions handle messaging with ipu7 hardware. Signed-off-by: Antti Laakso [Sakari Ailus: Use ipu6_ prefix for ipu7_ related global symbols.] Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/Makefile | 1 + drivers/media/pci/intel/ipu6/ipu6-bus.h | 2 + drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 3 +- .../media/pci/intel/ipu6/ipu6-isys-queue.c | 2 +- .../media/pci/intel/ipu6/ipu6-isys-queue.h | 2 + .../media/pci/intel/ipu6/ipu6-isys-video.c | 1 + .../media/pci/intel/ipu6/ipu6-isys-video.h | 1 + drivers/media/pci/intel/ipu6/ipu6-isys.c | 15 +- drivers/media/pci/intel/ipu6/ipu6-isys.h | 13 +- drivers/media/pci/intel/ipu6/ipu7-fw-com.h | 2 + drivers/media/pci/intel/ipu6/ipu7-fw-isys.c | 465 ++++++++++++++++++ drivers/media/pci/intel/ipu6/ipu7-fw-isys.h | 258 ++++++++++ 12 files changed, 757 insertions(+), 8 deletions(-) create mode 100644 drivers/media/pci/intel/ipu6/ipu7-fw-isys.c create mode 100644 drivers/media/pci/intel/ipu6/ipu7-fw-isys.h diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile index 406e2e19df17..66986d5709ae 100644 --- a/drivers/media/pci/intel/ipu6/Makefile +++ b/drivers/media/pci/intel/ipu6/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6.o intel-ipu6-isys-y := ipu6-isys.o \ ipu6-isys-csi2.o \ ipu6-fw-isys.o \ + ipu7-fw-isys.o \ ipu6-isys-video.o \ ipu6-isys-queue.o \ ipu6-isys-subdev.o \ diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.h b/drivers/media/pci/intel/ipu6/ipu6-bus.h index 62aa2b090699..aef8e4a66c4a 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-bus.h +++ b/drivers/media/pci/intel/ipu6/ipu6-bus.h @@ -31,6 +31,7 @@ struct ipu6_bus_device { u64 *pkg_dir; dma_addr_t pkg_dir_dma_addr; unsigned int pkg_dir_size; + u32 fw_entry; }; struct ipu6_auxdrv_data { @@ -47,6 +48,7 @@ struct ipu6_auxdrv_data { #define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev) extern const struct ipu6_fw_isys_ops ipu6_fw_isys_ops; +extern const struct ipu6_fw_isys_ops ipu7_fw_isys_ops; struct ipu6_bus_device * ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c index 4ee5137f310e..3cad7d8f9ca9 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c @@ -15,6 +15,7 @@ #include "ipu6-isys.h" #include "ipu6-platform-isys-csi2-reg.h" #include "ipu6-platform-regs.h" +#include "ipu7-fw-isys.h" static const char send_msg_types[N_IPU6_FW_ISYS_SEND_TYPE][32] = { "STREAM_OPEN", @@ -602,7 +603,7 @@ int ipu6_isys_isr_one(struct ipu6_bus_device *adev) } isys_fw_msg = container_of((void *)(uintptr_t)resp->buf_id, - struct isys_fw_msgs, ipu6.dummy); + struct isys_fw_msgs, dummy); ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), isys_fw_msg); if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS && diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c index 4cb1741445c6..38db162d64ca 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -684,7 +684,7 @@ static void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib) } } -static void +void ipu6_stream_buf_ready(struct ipu6_isys_stream *stream, u8 pin_id, u32 pin_addr, u64 time, bool error_check) { diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h index 51de14183ccf..5df47be0c181 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h @@ -63,4 +63,6 @@ void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, struct ipu6_fw_isys_resp_info_abi *info); int ipu6_isys_queue_init(struct ipu6_isys_queue *aq); +void ipu6_stream_buf_ready(struct ipu6_isys_stream *stream, u8 pin_id, + u32 pin_addr, u64 time, bool error_check); #endif /* IPU6_ISYS_QUEUE_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index 803450b0156e..560524e6e624 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -627,6 +627,7 @@ int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, stream->nr_queues = nr_queues; atomic_set(&stream->sequence, 0); + atomic_set(&stream->buf_id, 0); stream->seq_index = 0; memset(stream->seq, 0, sizeof(stream->seq)); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h index 2ff53315d7b9..a7157646d563 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h @@ -44,6 +44,7 @@ struct sequence_info { struct ipu6_isys_stream { struct mutex mutex; atomic_t sequence; + atomic_t buf_id; unsigned int seq_index; struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF]; int stream_source; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 159e769931b7..086c0f01ccda 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -931,6 +931,9 @@ void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, struct isys_fw_msgs *msg) spin_unlock_irqrestore(&isys->listlock, flags); } +static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data; +static const struct ipu6_auxdrv_data ipu7_isys_auxdrv_data; + static int isys_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *auxdev_id) { @@ -949,8 +952,8 @@ static int isys_probe(struct auxiliary_device *auxdev, if (!isys) return -ENOMEM; - adev->auxdrv_data = - (const struct ipu6_auxdrv_data *)auxdev_id->driver_data; + adev->auxdrv_data = IS_IPU7(isp) ? &ipu7_isys_auxdrv_data : + &ipu6_isys_auxdrv_data; adev->auxdrv = to_auxiliary_drv(auxdev->dev.driver); isys->adev = adev; isys->pdata = adev->pdata; @@ -1071,10 +1074,16 @@ static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = { .fw_ops = &ipu6_fw_isys_ops, }; +static const struct ipu6_auxdrv_data ipu7_isys_auxdrv_data = { + .isr = ipu6_isys_isr, + .isr_threaded = NULL, + .wake_isr_thread = false, + .fw_ops = &ipu7_fw_isys_ops, +}; + static const struct auxiliary_device_id ipu6_isys_id_table[] = { { .name = "intel_ipu6.isys", - .driver_data = (kernel_ulong_t)&ipu6_isys_auxdrv_data, }, { } }; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h index fa9bc2730d64..697c090cc3b1 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -17,6 +17,7 @@ #include "ipu6.h" #include "ipu6-fw-isys.h" +#include "ipu7-fw-isys.h" #include "ipu6-isys-csi2.h" #include "ipu6-isys-video.h" @@ -167,9 +168,15 @@ struct ipu6_isys { struct isys_fw_msgs { union { u64 dummy; - struct ipu6_fw_isys_frame_buff_set_abi frame; - struct ipu6_fw_isys_stream_cfg_data_abi stream; - } ipu6; + union { + struct ipu6_fw_isys_frame_buff_set_abi frame; + struct ipu6_fw_isys_stream_cfg_data_abi stream; + } ipu6; + union { + struct ipu7_fw_isys_frame_buff_set frame; + struct ipu7_fw_isys_stream_cfg stream; + } ipu7; + }; struct list_head head; dma_addr_t dma_addr; }; diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-com.h b/drivers/media/pci/intel/ipu6/ipu7-fw-com.h index 10c09e759bed..097eaab99547 100644 --- a/drivers/media/pci/intel/ipu6/ipu7-fw-com.h +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-com.h @@ -25,6 +25,8 @@ struct ipu7_fw_com_context { dma_addr_t boot_config_dma_addr; u32 boot_config_size; u32 fw_entry; + struct ipu7_insys_config *fw_config; + dma_addr_t fw_config_dma_addr; }; struct ipu7_fw_com_queue_params_config { diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c new file mode 100644 index 000000000000..5a362b567feb --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Intel Corporation + */ + +#include "ipu6-bus.h" +#include "ipu6-dma.h" +#include "ipu6-isys.h" +#include "ipu7-boot.h" +#include "ipu7-fw-com.h" +#include "ipu7-fw-isys.h" + +static void ipu7_fw_isys_cleanup(struct ipu6_isys *isys) +{ + struct ipu6_bus_device *adev = isys->adev; + struct ipu7_fw_com_context *fwctx = isys->fwctx; + + if (!fwctx) + return; + + ipu6_ipu7_release_boot_config(adev); + + if (fwctx->fw_config) { + ipu6_dma_free(adev, sizeof(*fwctx->fw_config), fwctx->fw_config, + fwctx->fw_config_dma_addr, 0); + fwctx->fw_config = NULL; + fwctx->fw_config_dma_addr = 0; + } + + isys->fwctx = NULL; +} + +static int ipu7_fw_isys_open(struct ipu6_isys *isys) +{ + return ipu6_ipu7_boot_start_fw(isys->adev); +} + +static int ipu7_fw_isys_close(struct ipu6_isys *isys) +{ + int ret; + + ret = ipu6_ipu7_boot_stop_fw(isys->adev); + if (ret) + return ret; + + ipu7_fw_isys_cleanup(isys); + + return ret; +} + +static int ipu7_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) +{ + struct ipu7_fw_com_queue_config *queue_configs; + struct ipu6_bus_device *adev = isys->adev; + struct device *dev = &adev->auxdev.dev; + struct ipu7_insys_config *fw_config; + struct ipu7_fw_com_context *fwctx; + dma_addr_t fw_config_dma_addr; + unsigned int i, num_queues; + u32 freq; + int ret; + + /* Allocate and init firmware context. */ + fwctx = devm_kzalloc(dev, sizeof(struct ipu7_fw_com_context), + GFP_KERNEL); + if (!fwctx) + return -ENOMEM; + + fwctx->num_input_queues = IPU7_INSYS_MAX_INPUT_QUEUES; + fwctx->num_output_queues = IPU7_INSYS_MAX_OUTPUT_QUEUES; + num_queues = fwctx->num_input_queues + fwctx->num_output_queues; + + queue_configs = devm_kcalloc(dev, num_queues, sizeof(*queue_configs), + GFP_KERNEL); + if (!queue_configs) { + ipu7_fw_isys_cleanup(isys); + return -ENOMEM; + } + fwctx->fw_entry = adev->fw_entry; + fwctx->queue_configs = queue_configs; + queue_configs[IPU7_INSYS_OUTPUT_MSG_QUEUE].max_capacity = + IPU7_ISYS_SIZE_RECV_QUEUE; + queue_configs[IPU7_INSYS_OUTPUT_MSG_QUEUE].token_size_in_bytes = + sizeof(struct ipu7_insys_resp); + queue_configs[IPU7_INSYS_OUTPUT_LOG_QUEUE].max_capacity = + IPU7_ISYS_SIZE_LOG_QUEUE; + queue_configs[IPU7_INSYS_OUTPUT_LOG_QUEUE].token_size_in_bytes = + sizeof(struct ipu7_insys_resp); + queue_configs[IPU7_INSYS_OUTPUT_RESERVED_QUEUE].max_capacity = 0; + queue_configs[IPU7_INSYS_OUTPUT_RESERVED_QUEUE].token_size_in_bytes = 0; + + queue_configs[IPU7_INSYS_INPUT_DEV_QUEUE].max_capacity = + IPU7_ISYS_MAX_STREAMS; + queue_configs[IPU7_INSYS_INPUT_DEV_QUEUE].token_size_in_bytes = + sizeof(struct ipu7_insys_send_queue_token); + + for (i = IPU7_INSYS_INPUT_MSG_QUEUE; i < num_queues; i++) { + queue_configs[i].max_capacity = IPU7_ISYS_SIZE_SEND_QUEUE; + queue_configs[i].token_size_in_bytes = + sizeof(struct ipu7_insys_send_queue_token); + } + + /* Allocate ISYS subsys config. */ + fw_config = ipu6_dma_alloc(adev, sizeof(*fw_config), + &fw_config_dma_addr, GFP_KERNEL, 0); + if (!fw_config) { + dev_err(dev, "Failed to allocate isys subsys config.\n"); + ipu7_fw_isys_cleanup(isys); + return -ENOMEM; + } + fwctx->fw_config = fw_config; + fwctx->fw_config_dma_addr = fw_config_dma_addr; + memset(fw_config, 0, sizeof(*fw_config)); + fw_config->logger_config.use_source_severity = 0; + fw_config->logger_config.use_channels_enable_bitmask = 1; + fw_config->logger_config.channels_enable_bitmask = + IPU7_LOGGER_CFG_CHANNEL_ENABLE_SYSCOM; + fw_config->logger_config.hw_printf_buffer_base_addr = 0U; + fw_config->logger_config.hw_printf_buffer_size_bytes = 0U; + fw_config->wdt_config.wdt_timer1_us = 0; + fw_config->wdt_config.wdt_timer2_us = 0; + freq = ipu7_buttress_get_isys_freq(adev->isp); + + ipu6_dma_sync_single(adev, fw_config_dma_addr, + sizeof(struct ipu7_insys_config)); + + isys->fwctx = fwctx; + + ret = ipu6_ipu7_init_boot_config(adev, queue_configs, num_queues, + freq, fw_config_dma_addr, 1U); + if (ret) { + ipu7_fw_isys_cleanup(isys); + return ret; + } + + ret = ipu7_fw_isys_open(isys); + if (ret) + ipu7_fw_isys_cleanup(isys); + + return ret; +} + +static struct ipu7_insys_resp *ipu7_fw_isys_get_resp(struct ipu6_isys *isys) +{ + return ipu7_fw_com_get_token(isys->fwctx, IPU7_INSYS_OUTPUT_MSG_QUEUE); +} + +static void ipu7_fw_isys_put_resp(struct ipu6_isys *isys) +{ + ipu7_fw_com_put_token(isys->fwctx, IPU7_INSYS_OUTPUT_MSG_QUEUE); +} + +static int ipu7_isys_fw_pin_cfg(struct ipu6_isys_video *av, + struct ipu7_fw_isys_stream_cfg *cfg) +{ + 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 ipu7_fw_isys_input_pin *input_pin; + struct ipu7_fw_isys_output_pin *output_pin; + struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_queue *aq = &av->aq; + struct v4l2_mbus_framefmt fmt; + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); + int input_pins = cfg->nof_input_pins++; + int output_pins; + u32 src_stream; + + src_stream = ipu6_isys_get_src_stream_by_src_pad(sd, src_pad->index); + fmt = *v4l2_subdev_state_get_format(state, src_pad->index, src_stream); + + input_pin = &cfg->input_pins[input_pins]; + input_pin->input_res.width = fmt.width; + input_pin->input_res.height = fmt.height; + input_pin->dt = av->dt; + input_pin->disable_mipi_unpacking = 0; + if (pfmt->bpp == pfmt->bpp_packed && pfmt->bpp % BITS_PER_BYTE) + input_pin->disable_mipi_unpacking = 1; + input_pin->mapped_dt = IPU7_N_INSYS_MIPI_DATA_TYPE; + input_pin->dt_rename_mode = IPU7_INSYS_MIPI_DT_NO_RENAME; + input_pin->sync_msg_map = + IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF | + IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF_DISCARDED | + IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF | + IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF_DISCARDED; + + output_pins = cfg->nof_output_pins++; + aq->fw_output = output_pins; + stream->output_pins_queue[output_pins] = aq; + + output_pin = &cfg->output_pins[output_pins]; + memset(output_pin, 0, sizeof(*output_pin)); + output_pin->link.buffer_lines = 0; + output_pin->link.foreign_key = IPU7_MSG_LINK_FOREIGN_KEY_NONE; + output_pin->link.pbk_id = IPU7_MSG_LINK_PBK_ID_DONT_CARE; + output_pin->link.pbk_slot_id = IPU7_MSG_LINK_PBK_SLOT_ID_DONT_CARE; + output_pin->link.dest = 0; /* IPU_INSYS_OUTPUT_LINK_DEST_MEM */ + output_pin->link.use_sw_managed = 1; + output_pin->crop.line_top = 0; + output_pin->crop.line_bottom = 0; + output_pin->dpcm.enable = 0; + output_pin->ft = pfmt->css_pixelformat; + output_pin->stride = ipu6_isys_get_bytes_per_line(av); + output_pin->send_irq = 1; + output_pin->input_pin_id = input_pins; + + return 0; +} + +static int +ipu7_fw_isys_send_cmd(struct ipu6_isys *isys, const unsigned int stream_handle, + void *cpu_mapped_buf, dma_addr_t dma_mapped_buf, + size_t size, u16 send_type) +{ + struct ipu7_fw_com_context *ctx = isys->fwctx; + /*struct device *dev = &isys->adev->auxdev.dev;*/ + struct ipu7_insys_send_queue_token *token; + + if (send_type >= N_IPU7_INSYS_SEND_TYPE) + return -EINVAL; + + if (cpu_mapped_buf) + clflush_cache_range(cpu_mapped_buf, size); + + token = ipu7_fw_com_get_token(ctx, stream_handle + + IPU7_INSYS_INPUT_MSG_QUEUE); + if (!token) + return -EBUSY; + + token->addr = dma_mapped_buf; + token->buf_handle = (unsigned long)cpu_mapped_buf; + token->send_type = send_type; + token->stream_id = stream_handle; + token->flag = IPU7_INSYS_SEND_QUEUE_TOKEN_FLAG_NONE; + + ipu7_fw_com_put_token(ctx, stream_handle + IPU7_INSYS_INPUT_MSG_QUEUE); + /* now wakeup FW */ + ipu7_buttress_wakeup_isys(isys->adev->isp); + + return 0; +} + +static void ipu7_fw_isys_dump_stream_cfg(struct device *dev, + struct isys_fw_msgs *msg) +{ + struct ipu7_fw_isys_stream_cfg *cfg = &msg->ipu7.stream; + unsigned int i; + + dev_dbg(dev, "---------------------------\n"); + dev_dbg(dev, "IPU_FW_ISYS_STREAM_CFG_DATA\n"); + + dev_dbg(dev, ".port id %d\n", cfg->port_id); + dev_dbg(dev, ".vc %d\n", cfg->vc); + dev_dbg(dev, ".nof_input_pins = %d\n", cfg->nof_input_pins); + dev_dbg(dev, ".nof_output_pins = %d\n", cfg->nof_output_pins); + dev_dbg(dev, ".stream_msg_map = 0x%x\n", cfg->stream_msg_map); + + for (i = 0; i < cfg->nof_input_pins; i++) { + dev_dbg(dev, ".input_pin[%d]:\n", i); + dev_dbg(dev, "\t.dt = 0x%0x\n", + cfg->input_pins[i].dt); + dev_dbg(dev, "\t.disable_mipi_unpacking = %d\n", + cfg->input_pins[i].disable_mipi_unpacking); + dev_dbg(dev, "\t.dt_rename_mode = %d\n", + cfg->input_pins[i].dt_rename_mode); + dev_dbg(dev, "\t.mapped_dt = 0x%0x\n", + cfg->input_pins[i].mapped_dt); + dev_dbg(dev, "\t.input_res = %d x %d\n", + cfg->input_pins[i].input_res.width, + cfg->input_pins[i].input_res.height); + dev_dbg(dev, "\t.sync_msg_map = 0x%x\n", + cfg->input_pins[i].sync_msg_map); + } + + for (i = 0; i < cfg->nof_output_pins; i++) { + dev_dbg(dev, ".output_pin[%d]:\n", i); + dev_dbg(dev, "\t.input_pin_id = %d\n", + cfg->output_pins[i].input_pin_id); + dev_dbg(dev, "\t.stride = %d\n", cfg->output_pins[i].stride); + dev_dbg(dev, "\t.send_irq = %d\n", + cfg->output_pins[i].send_irq); + dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft); + + dev_dbg(dev, "\t.link.buffer_lines = %d\n", + cfg->output_pins[i].link.buffer_lines); + dev_dbg(dev, "\t.link.foreign_key = %d\n", + cfg->output_pins[i].link.foreign_key); + dev_dbg(dev, "\t.link.granularity_pointer_update = %d\n", + cfg->output_pins[i].link.granularity_pointer_update); + dev_dbg(dev, "\t.link.msg_link_streaming_mode = %d\n", + cfg->output_pins[i].link.msg_link_streaming_mode); + dev_dbg(dev, "\t.link.pbk_id = %d\n", + cfg->output_pins[i].link.pbk_id); + dev_dbg(dev, "\t.link.pbk_slot_id = %d\n", + cfg->output_pins[i].link.pbk_slot_id); + dev_dbg(dev, "\t.link.dest = %d\n", + cfg->output_pins[i].link.dest); + dev_dbg(dev, "\t.link.use_sw_managed = %d\n", + cfg->output_pins[i].link.use_sw_managed); + dev_dbg(dev, "\t.link.is_snoop = %d\n", + cfg->output_pins[i].link.is_snoop); + + dev_dbg(dev, "\t.crop.line_top = %d\n", + cfg->output_pins[i].crop.line_top); + dev_dbg(dev, "\t.crop.line_bottom = %d\n", + cfg->output_pins[i].crop.line_bottom); + + dev_dbg(dev, "\t.dpcm_enable = %d\n", + cfg->output_pins[i].dpcm.enable); + dev_dbg(dev, "\t.dpcm.type = %d\n", + cfg->output_pins[i].dpcm.type); + dev_dbg(dev, "\t.dpcm.predictor = %d\n", + cfg->output_pins[i].dpcm.predictor); + } + dev_dbg(dev, "---------------------------\n"); +} + +static void ipu7_fw_isys_dump_frame_buf_set(struct device *dev, + struct isys_fw_msgs *msg, + unsigned int outputs) +{ + struct ipu7_fw_isys_frame_buff_set *buf = &msg->ipu7.frame; + unsigned int i; + + dev_dbg(dev, "--------------------------\n"); + dev_dbg(dev, "IPU_ISYS_BUFF_SET\n"); + dev_dbg(dev, ".capture_msg_map = %d\n", buf->capture_msg_map); + dev_dbg(dev, ".frame_id = %d\n", buf->frame_id); + dev_dbg(dev, ".skip_frame = %d\n", buf->skip_frame); + + for (i = 0; i < outputs; i++) { + dev_dbg(dev, ".output_pin[%d]:\n", i); + dev_dbg(dev, "\t.user_token = %llx\n", + buf->output_pins[i].user_token); + dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr); + } + dev_dbg(dev, "---------------------------\n"); +} + +static int ipu7_fw_isys_prepare_stream_cfg(struct ipu6_isys_video *av, + struct isys_fw_msgs *msg) +{ + struct ipu7_fw_isys_stream_cfg *cfg = &msg->ipu7.stream; + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_queue *aq; + + memset(cfg, 0, sizeof(*cfg)); + cfg->port_id = stream->stream_source; + cfg->vc = stream->vc; + cfg->stream_msg_map = IPU7_INSYS_STREAM_ENABLE_MSG_SEND_RESP | + IPU7_INSYS_STREAM_ENABLE_MSG_SEND_IRQ; + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_video *__av = ipu6_isys_queue_to_video(aq); + int ret = ipu7_isys_fw_pin_cfg(__av, cfg); + + if (ret < 0) + return ret; + } + + stream->nr_output_pins = cfg->nof_output_pins; + + ipu7_fw_isys_dump_stream_cfg(dev, msg); + + return 0; +} + +static void +ipu7_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, + struct ipu7_fw_isys_frame_buff_set *set) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_video_buffer *ivb = + vb2_buffer_to_ipu6_isys_video_buffer(vvb); + + set->output_pins[aq->fw_output].addr = ivb->dma_addr; + set->output_pins[aq->fw_output].user_token = (u64)(uintptr_t)set; +} + +static void +ipu7_fw_isys_prepare_buf_set(struct isys_fw_msgs *msg, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) +{ + struct ipu7_fw_isys_frame_buff_set *set = &msg->ipu7.frame; + struct ipu6_isys_buffer *ib; + + WARN_ON(!bl->nbufs); + + memset(set, 0, sizeof(*set)); + set->capture_msg_map = IPU7_INSYS_FRAME_ENABLE_MSG_SEND_RESP | + IPU7_INSYS_FRAME_ENABLE_MSG_SEND_IRQ; + set->frame_id = atomic_fetch_inc(&stream->buf_id) % 256; + + list_for_each_entry(ib, &bl->head, head) { + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + ipu7_isys_buf_to_fw_frame_buf_pin(vb, set); + } + + dev_dbg(&stream->isys->adev->auxdev.dev, + "ipu7 frame_buf: cap_map=0x%x fid=%u opin[0].addr=0x%x token=0x%llx\n", + set->capture_msg_map, set->frame_id, + set->output_pins[0].addr, set->output_pins[0].user_token); +} + +static int ipu7_fw_isys_stream_open(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg) +{ + return ipu7_fw_isys_send_cmd(isys, stream_handle, &msg->ipu7.stream, + msg->dma_addr, sizeof(msg->ipu7.stream), + IPU7_INSYS_SEND_TYPE_STREAM_OPEN); +} + +static int ipu7_fw_isys_stream_close(struct ipu6_isys *isys, + const unsigned int stream_handle) +{ + return ipu7_fw_isys_send_cmd(isys, stream_handle, NULL, 0, 0, + IPU7_INSYS_SEND_TYPE_STREAM_CLOSE); +} + +static int ipu7_fw_isys_stream_flush(struct ipu6_isys *isys, + const unsigned int stream_handle) +{ + return ipu7_fw_isys_send_cmd(isys, stream_handle, NULL, 0, 0, + IPU7_INSYS_SEND_TYPE_STREAM_FLUSH); +} + +static int ipu7_fw_isys_stream_start(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg, bool capture) +{ + return ipu7_fw_isys_send_cmd(isys, stream_handle, &msg->ipu7.frame, + msg->dma_addr, sizeof(msg->ipu7.frame), + IPU7_INSYS_SEND_TYPE_STREAM_START_AND_CAPTURE); +} + +static int ipu7_fw_isys_stream_capture(struct ipu6_isys *isys, + const unsigned int stream_handle, + struct isys_fw_msgs *msg) +{ + return ipu7_fw_isys_send_cmd(isys, stream_handle, &msg->ipu7.frame, + msg->dma_addr, sizeof(msg->ipu7.frame), + IPU7_INSYS_SEND_TYPE_STREAM_CAPTURE); +} + +const struct ipu6_fw_isys_ops ipu7_fw_isys_ops = { + .init = ipu7_fw_isys_init, + .close = ipu7_fw_isys_close, + .send_cmd = ipu7_fw_isys_send_cmd, + .cleanup = ipu7_fw_isys_cleanup, + .prepare_stream_cfg = ipu7_fw_isys_prepare_stream_cfg, + .prepare_buf_set = ipu7_fw_isys_prepare_buf_set, + .stream_open = ipu7_fw_isys_stream_open, + .stream_start = ipu7_fw_isys_stream_start, + .stream_capture = ipu7_fw_isys_stream_capture, + .stream_flush = ipu7_fw_isys_stream_flush, + .stream_close = ipu7_fw_isys_stream_close, + .dump_stream_cfg = ipu7_fw_isys_dump_stream_cfg, + .dump_frame_buf_set = ipu7_fw_isys_dump_frame_buf_set, +}; diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.h new file mode 100644 index 000000000000..122531065362 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.h @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_FW_ISYS_H +#define IPU7_FW_ISYS_H + +#define IPU7_FWLOG_MAX_LOGGER_SOURCES (64U) +#define IPU7_INSYS_MAX_OUTPUT_QUEUES 3U +#define IPU7_INSYS_STREAM_ID_MAX 16U +#define IPU7_INSYS_MAX_INPUT_QUEUES (IPU7_INSYS_STREAM_ID_MAX + 1U) +#define IPU7_INSYS_OUTPUT_MSG_QUEUE 0U +#define IPU7_INSYS_OUTPUT_LOG_QUEUE 1U +#define IPU7_INSYS_OUTPUT_RESERVED_QUEUE 2U +#define IPU7_INSYS_INPUT_DEV_QUEUE 3U + +#define IPU7_INSYS_INPUT_FIRST_QUEUE 3U +#define IPU7_INSYS_INPUT_MSG_QUEUE 4U +#define IPU7_INSYS_INPUT_MSG_MAX_QUEUE 16U + +#define IPU7_MSG_ERR_MAX_DETAILS 4U +#define IPU7_ISYS_SIZE_RECV_QUEUE 40U +#define IPU7_ISYS_SIZE_LOG_QUEUE 256U +#define IPU7_ISYS_SIZE_SEND_QUEUE 40U +#define IPU7_ISYS_NUM_RECV_QUEUE 1U +#define IPU7_INSYS_SEND_QUEUE_TOKEN_FLAG_NONE 0U + +#define IPU7_LOGGER_CFG_CHANNEL_ENABLE_SYSCOM BIT(1) + +#define IPU7_ISYS_MAX_STREAMS 16U +#define IPU7_MAX_OPINS 4 +#define IPU7_MAX_IPINS 4 + +#define IPU7_N_INSYS_MIPI_DATA_TYPE 0x40 + +#define IPU7_MSG_LINK_FOREIGN_KEY_NONE (65535U) +#define IPU7_MSG_LINK_PBK_ID_DONT_CARE (255U) +#define IPU7_MSG_LINK_PBK_SLOT_ID_DONT_CARE (255U) + +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF BIT(0) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_EOF BIT(1) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF BIT(2) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_EOF BIT(3) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF_DISCARDED BIT(4) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_RESP_EOF_DISCARDED BIT(5) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF_DISCARDED BIT(6) +#define IPU7_INSYS_STREAM_SYNC_MSG_SEND_IRQ_EOF_DISCARDED BIT(7) + +#define IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_OPEN_DONE BIT(0) +#define IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_OPEN_DONE BIT(1) +#define IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_START_ACK BIT(2) +#define IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_START_ACK BIT(3) +#define IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_CLOSE_ACK BIT(4) +#define IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_CLOSE_ACK BIT(5) +#define IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_FLUSH_ACK BIT(6) +#define IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_FLUSH_ACK BIT(7) +#define IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_ABORT_ACK BIT(8) +#define IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_ABORT_ACK BIT(9) + +#define IPU7_INSYS_STREAM_ENABLE_MSG_SEND_RESP ( \ + IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_OPEN_DONE | \ + IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_START_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_CLOSE_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_FLUSH_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_RESP_STREAM_ABORT_ACK) +#define IPU7_INSYS_STREAM_ENABLE_MSG_SEND_IRQ ( \ + IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_OPEN_DONE | \ + IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_START_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_CLOSE_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_FLUSH_ACK | \ + IPU7_INSYS_STREAM_MSG_SEND_IRQ_STREAM_ABORT_ACK) + +#define IPU7_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_ACK BIT(0) +#define IPU7_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_ACK BIT(1) +#define IPU7_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_DONE BIT(2) +#define IPU7_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_DONE BIT(3) +#define IPU7_INSYS_FRAME_MSG_SEND_RESP_PIN_DATA_READY BIT(4) +#define IPU7_INSYS_FRAME_MSG_SEND_IRQ_PIN_DATA_READY BIT(5) + +#define IPU7_INSYS_FRAME_ENABLE_MSG_SEND_RESP ( \ + IPU7_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_ACK | \ + IPU7_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_DONE | \ + IPU7_INSYS_FRAME_MSG_SEND_RESP_PIN_DATA_READY) +#define IPU7_INSYS_FRAME_ENABLE_MSG_SEND_IRQ ( \ + IPU7_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_ACK | \ + IPU7_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_DONE | \ + IPU7_INSYS_FRAME_MSG_SEND_IRQ_PIN_DATA_READY) + +enum ipu7_insys_send_type { + IPU7_INSYS_SEND_TYPE_STREAM_OPEN = 0, + IPU7_INSYS_SEND_TYPE_STREAM_START_AND_CAPTURE = 1, + IPU7_INSYS_SEND_TYPE_STREAM_CAPTURE = 2, + IPU7_INSYS_SEND_TYPE_STREAM_ABORT = 3, + IPU7_INSYS_SEND_TYPE_STREAM_FLUSH = 4, + IPU7_INSYS_SEND_TYPE_STREAM_CLOSE = 5, + N_IPU7_INSYS_SEND_TYPE +}; + +enum ipu7_insys_resp_type { + IPU7_INSYS_RESP_TYPE_STREAM_OPEN_DONE = 0, + IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK = 1, + IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_ACK = 2, + IPU7_INSYS_RESP_TYPE_STREAM_ABORT_ACK = 3, + IPU7_INSYS_RESP_TYPE_STREAM_FLUSH_ACK = 4, + IPU7_INSYS_RESP_TYPE_STREAM_CLOSE_ACK = 5, + IPU7_INSYS_RESP_TYPE_PIN_DATA_READY = 6, + IPU7_INSYS_RESP_TYPE_FRAME_SOF = 7, + IPU7_INSYS_RESP_TYPE_FRAME_EOF = 8, + IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE = 9, + IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_DONE = 10, + IPU7_INSYS_RESP_TYPE_PWM_IRQ = 11, + N_IPU7_INSYS_RESP_TYPE +}; + +enum ipu7_insys_mipi_dt_rename_mode { + IPU7_INSYS_MIPI_DT_NO_RENAME = 0, + IPU7_INSYS_MIPI_DT_RENAMED_MODE = 1, + N_IPU7_INSYS_MIPI_DT_MODE +}; + +struct ipu7_fw_isys_logger_config { + u8 use_source_severity; + u8 source_severity[IPU7_FWLOG_MAX_LOGGER_SOURCES]; + u8 use_channels_enable_bitmask; + u8 channels_enable_bitmask; + u8 padding[1]; + u32 hw_printf_buffer_base_addr; + u32 hw_printf_buffer_size_bytes; +}; + +struct ipu7_wdt_abi { + u32 wdt_timer1_us; + u32 wdt_timer2_us; +}; + +struct ipu7_insys_config { + u32 timeout_val_ms; + struct ipu7_fw_isys_logger_config logger_config; + struct ipu7_wdt_abi wdt_config; +}; + +struct ipu7_insys_capture_output_pin_payload { + u64 user_token; + u32 addr; + u8 pad[4]; +}; + +struct ipu7_fw_isys_msg_err { + u32 err_group; + u32 err_code; + u32 err_detail[IPU7_MSG_ERR_MAX_DETAILS]; +}; + +struct ipu7_insys_resp { + u64 buf_id; + struct ipu7_insys_capture_output_pin_payload pin; + struct ipu7_fw_isys_msg_err error_info; + u32 timestamp[2]; + u8 type; + u8 msg_link_streaming_mode; + u8 stream_id; + u8 pin_id; + u8 frame_id; + u8 skip_frame; + u8 pad[2]; +}; + +struct ipu7_insys_resp_queue_token { + struct ipu7_insys_resp resp_info; +}; + +struct ipu7_insys_send_queue_token { + u64 buf_handle; + u32 addr; + u16 stream_id; + u8 send_type; + u8 flag; +}; + +struct ipu7_fw_isys_output_link { + u32 buffer_lines; + u16 foreign_key; + u16 granularity_pointer_update; + u8 msg_link_streaming_mode; + u8 pbk_id; + u8 pbk_slot_id; + u8 dest; + u8 use_sw_managed; + u8 is_snoop; + u8 pad[2]; +} __packed; + +struct ipu7_fw_isys_output_cropping { + u16 line_top; + u16 line_bottom; +} __packed; + +struct ipu7_fw_isys_output_dpcm { + u8 enable; + u8 type; + u8 predictor; + u8 pad; +} __packed; + +struct ipu7_fw_isys_output_pin { + struct ipu7_fw_isys_output_link link; + struct ipu7_fw_isys_output_cropping crop; + struct ipu7_fw_isys_output_dpcm dpcm; + u32 stride; + u16 ft; + u8 send_irq; + u8 input_pin_id; + u8 early_ack_en; + u8 pad[3]; +} __packed; + +struct ipu7_fw_isys_resolution { + u32 width; + u32 height; +} __packed; + +struct ipu7_fw_isys_input_pin { + struct ipu7_fw_isys_resolution input_res; + u16 sync_msg_map; + u8 dt; + u8 disable_mipi_unpacking; + u8 dt_rename_mode; + u8 mapped_dt; + u8 pad[2]; +} __packed; + +struct ipu7_fw_isys_stream_cfg { + struct ipu7_fw_isys_input_pin input_pins[IPU7_MAX_IPINS]; + struct ipu7_fw_isys_output_pin output_pins[IPU7_MAX_OPINS]; + u16 stream_msg_map; + u8 port_id; + u8 vc; + u8 nof_input_pins; + u8 nof_output_pins; + u8 pad[2]; +} __packed; + +struct ipu7_fw_isys_capture_output_pin { + u64 user_token; + u32 addr; + u8 pad[4]; +} __packed; + +struct ipu7_fw_isys_frame_buff_set { + struct ipu7_fw_isys_capture_output_pin output_pins[IPU7_MAX_OPINS]; + u8 capture_msg_map; + u8 frame_id; + u8 skip_frame; + u8 pad[5]; +} __packed; + +struct ipu6_fw_isys_ops *ipu7_fw_isys_get_ops(void); + +#endif From e934e8443c734a0c731cc76b219f4d43592c838d Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:17 +0300 Subject: [PATCH 072/596] media: ipu6: Add ipu7 csi2 register definitions This is needed later when adding support for the ipu7 csi2 receiver. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- .../pci/intel/ipu6/ipu7-isys-csi2-regs.h | 1189 +++++++++++++++++ 1 file changed, 1189 insertions(+) create mode 100644 drivers/media/pci/intel/ipu6/ipu7-isys-csi2-regs.h diff --git a/drivers/media/pci/intel/ipu6/ipu7-isys-csi2-regs.h b/drivers/media/pci/intel/ipu6/ipu7-isys-csi2-regs.h new file mode 100644 index 000000000000..3859401d4802 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-isys-csi2-regs.h @@ -0,0 +1,1189 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 - 2026 Intel Corporation + */ + +#ifndef IPU7_ISYS_CSI2_REG_H +#define IPU7_ISYS_CSI2_REG_H + +/* IS main regs base */ +#define IPU7_IS_MAIN_BASE 0x240000 +#define IPU7_IS_MAIN_S2B_BASE (IPU7_IS_MAIN_BASE + 0x22000) +#define IPU7_IS_MAIN_B2O_BASE (IPU7_IS_MAIN_BASE + 0x26000) +#define IPU7_IS_MAIN_ISD_M0_BASE (IPU7_IS_MAIN_BASE + 0x2b000) +#define IPU7_IS_MAIN_ISD_M1_BASE (IPU7_IS_MAIN_BASE + 0x2b100) +#define IPU7_IS_MAIN_ISD_INT_BASE (IPU7_IS_MAIN_BASE + 0x2b200) +#define IPU7_IS_MAIN_GDA_BASE (IPU7_IS_MAIN_BASE + 0x32000) +#define IPU7_IS_MAIN_GPREGS_MAIN_BASE (IPU7_IS_MAIN_BASE + 0x32500) +#define IPU7_IS_MAIN_IRQ_CTRL_BASE (IPU7_IS_MAIN_BASE + 0x32700) +#define IPU7_IS_MAIN_PWM_CTRL_BASE (IPU7_IS_MAIN_BASE + 0x32b00) + +#define IPU7_S2B_IRQ_COMMON_0_CTL_STATUS (IPU7_IS_MAIN_S2B_BASE + 0x1c) +#define IPU7_S2B_IRQ_COMMON_0_CTL_CLEAR (IPU7_IS_MAIN_S2B_BASE + 0x20) +#define IPU7_S2B_IRQ_COMMON_0_CTL_ENABLE (IPU7_IS_MAIN_S2B_BASE + 0x24) +#define IPU7_S2B_IID_IRQ_CTL_STATUS(iid) (IPU7_IS_MAIN_S2B_BASE + 0x94 + \ + 0x100 * (iid)) + +#define IPU7_B2O_IRQ_COMMON_0_CTL_STATUS (IPU7_IS_MAIN_B2O_BASE + 0x30) +#define IPU7_B2O_IRQ_COMMON_0_CTL_CLEAR (IPU7_IS_MAIN_B2O_BASE + 0x34) +#define IPU7_B2O_IRQ_COMMON_0_CTL_ENABLE (IPU7_IS_MAIN_B2O_BASE + 0x38) +#define IPU7_B2O_IID_IRQ_CTL_STATUS(oid) (IPU7_IS_MAIN_B2O_BASE + 0x3dc + \ + 0x200 * (oid)) + +#define IPU7_ISD_M0_IRQ_CTL_STATUS (IPU7_IS_MAIN_ISD_M0_BASE + 0x1c) +#define IPU7_ISD_M0_IRQ_CTL_CLEAR (IPU7_IS_MAIN_ISD_M0_BASE + 0x20) +#define IPU7_ISD_M0_IRQ_CTL_ENABLE (IPU7_IS_MAIN_ISD_M0_BASE + 0x24) + +#define IPU7_ISD_M1_IRQ_CTL_STATUS (IPU7_IS_MAIN_ISD_M1_BASE + 0x1c) +#define IPU7_ISD_M1_IRQ_CTL_CLEAR (IPU7_IS_MAIN_ISD_M1_BASE + 0x20) +#define IPU7_ISD_M1_IRQ_CTL_ENABLE (IPU7_IS_MAIN_ISD_M1_BASE + 0x24) + +#define IPU7_ISD_INT_IRQ_CTL_STATUS (IPU7_IS_MAIN_ISD_INT_BASE + 0x1c) +#define IPU7_ISD_INT_IRQ_CTL_CLEAR (IPU7_IS_MAIN_ISD_INT_BASE + 0x20) +#define IPU7_ISD_INT_IRQ_CTL_ENABLE (IPU7_IS_MAIN_ISD_INT_BASE + 0x24) + +#define IPU7_GDA_IRQ_CTL_STATUS (IPU7_IS_MAIN_GDA_BASE + 0x1c) +#define IPU7_GDA_IRQ_CTL_CLEAR (IPU7_IS_MAIN_GDA_BASE + 0x20) +#define IPU7_GDA_IRQ_CTL_ENABLE (IPU7_IS_MAIN_GDA_BASE + 0x24) + +#define IPU7_IS_MAIN_IRQ_CTL_EDGE IPU7_IS_MAIN_IRQ_CTRL_BASE +#define IPU7_IS_MAIN_IRQ_CTL_MASK (IPU7_IS_MAIN_IRQ_CTRL_BASE + 0x4) +#define IPU7_IS_MAIN_IRQ_CTL_STATUS (IPU7_IS_MAIN_IRQ_CTRL_BASE + 0x8) +#define IPU7_IS_MAIN_IRQ_CTL_CLEAR (IPU7_IS_MAIN_IRQ_CTRL_BASE + 0xc) +#define IPU7_IS_MAIN_IRQ_CTL_ENABLE (IPU7_IS_MAIN_IRQ_CTRL_BASE + 0x10) +#define IPU7_IS_MAIN_IRQ_CTL_LEVEL_NOT_PULSE (IPU7_IS_MAIN_IRQ_CTRL_BASE + 0x14) + +/* IS IO regs base */ +#define IPU7_IS_PHY_NUM 4U +#define IPU7_IS_IO_BASE 0x280000 + +/* dwc csi cdphy registers */ +#define IPU7_IS_IO_CDPHY_BASE(i) (IPU7_IS_IO_BASE + 0x10000 * (i)) +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_0 0x1800 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_1 0x1802 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_2 0x1804 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_3 0x1806 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_4 0x1808 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_5 0x180a +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_6 0x180c +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_7 0x180e +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_8 0x1810 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_9 0x1812 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_A 0x1814 +#define IPU7_PPI_STARTUP_RW_COMMON_DPHY_10 0x1820 +#define IPU7_PPI_STARTUP_RW_COMMON_STARTUP_1_1 0x1822 +#define IPU7_PPI_STARTUP_RW_COMMON_STARTUP_1_2 0x1824 +#define IPU7_PPI_CALIBCTRL_RW_COMMON_CALIBCTRL_2_0 0x1840 +#define IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_1 0x1842 +#define IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_2 0x1844 +#define IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_3 0x1846 +#define IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_4 0x1848 +#define IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_5 0x184a +#define IPU7_PPI_CALIBCTRL_RW_COMMON_BG_0 0x184c +#define IPU7_PPI_CALIBCTRL_RW_COMMON_CALIBCTRL_2_7 0x184e +#define IPU7_PPI_CALIBCTRL_RW_ADC_CFG_0 0x1850 +#define IPU7_PPI_CALIBCTRL_RW_ADC_CFG_1 0x1852 +#define IPU7_PPI_CALIBCTRL_R_ADC_DEBUG 0x1854 +#define IPU7_PPI_RW_LPDCOCAL_TOP_OVERRIDE 0x1c00 +#define IPU7_PPI_RW_LPDCOCAL_TIMEBASE 0x1c02 +#define IPU7_PPI_RW_LPDCOCAL_NREF 0x1c04 +#define IPU7_PPI_RW_LPDCOCAL_NREF_RANGE 0x1c06 +#define IPU7_PPI_RW_LPDCOCAL_NREF_TRIGGER_MAN 0x1c08 +#define IPU7_PPI_RW_LPDCOCAL_TWAIT_CONFIG 0x1c0a +#define IPU7_PPI_RW_LPDCOCAL_VT_CONFIG 0x1c0c +#define IPU7_PPI_R_LPDCOCAL_DEBUG_RB 0x1c0e +#define IPU7_PPI_RW_LPDCOCAL_COARSE_CFG 0x1c10 +#define IPU7_PPI_R_LPDCOCAL_DEBUG_COARSE_RB 0x1c12 +#define IPU7_PPI_R_LPDCOCAL_DEBUG_COARSE_MEAS_0_RB 0x1c14 +#define IPU7_PPI_R_LPDCOCAL_DEBUG_COARSE_MEAS_1_RB 0x1c16 +#define IPU7_PPI_R_LPDCOCAL_DEBUG_COARSE_FWORD_RB 0x1c18 +#define IPU7_PPI_R_LPDCOCAL_DEBUG_MEASURE_CURR_ERROR 0x1c1a +#define IPU7_PPI_R_LPDCOCAL_DEBUG_MEASURE_LAST_ERROR 0x1c1c +#define IPU7_PPI_R_LPDCOCAL_DEBUG_VT 0x1c1e +#define IPU7_PPI_RW_LB_TIMEBASE_CONFIG 0x1c20 +#define IPU7_PPI_RW_LB_STARTCMU_CONFIG 0x1c22 +#define IPU7_PPI_R_LBPULSE_COUNTER_RB 0x1c24 +#define IPU7_PPI_R_LB_START_CMU_RB 0x1c26 +#define IPU7_PPI_RW_LB_DPHY_BURST_START 0x1c28 +#define IPU7_PPI_RW_LB_CPHY_BURST_START 0x1c2a +#define IPU7_PPI_RW_DDLCAL_CFG_0 0x1c40 +#define IPU7_PPI_RW_DDLCAL_CFG_1 0x1c42 +#define IPU7_PPI_RW_DDLCAL_CFG_2 0x1c44 +#define IPU7_PPI_RW_DDLCAL_CFG_3 0x1c46 +#define IPU7_PPI_RW_DDLCAL_CFG_4 0x1c48 +#define IPU7_PPI_RW_DDLCAL_CFG_5 0x1c4a +#define IPU7_PPI_RW_DDLCAL_CFG_6 0x1c4c +#define IPU7_PPI_RW_DDLCAL_CFG_7 0x1c4e +#define IPU7_PPI_R_DDLCAL_DEBUG_0 0x1c50 +#define IPU7_PPI_R_DDLCAL_DEBUG_1 0x1c52 +#define IPU7_PPI_RW_PARITY_TEST 0x1c60 +#define IPU7_PPI_RW_STARTUP_OVR_0 0x1c62 +#define IPU7_PPI_RW_STARTUP_STATE_OVR_1 0x1c64 +#define IPU7_PPI_RW_DTB_SELECTOR 0x1c66 +#define IPU7_PPI_RW_DPHY_CLK_SPARE 0x1c6a +#define IPU7_PPI_RW_COMMON_CFG 0x1c6c +#define IPU7_PPI_RW_TERMCAL_CFG_0 0x1c80 +#define IPU7_PPI_R_TERMCAL_DEBUG_0 0x1c82 +#define IPU7_PPI_RW_TERMCAL_CTRL_0 0x1c84 +#define IPU7_PPI_RW_OFFSETCAL_CFG_0 0x1ca0 +#define IPU7_PPI_R_OFFSETCAL_DEBUG_LANE0 0x1ca2 +#define IPU7_PPI_R_OFFSETCAL_DEBUG_LANE1 0x1ca4 +#define IPU7_PPI_R_OFFSETCAL_DEBUG_LANE2 0x1ca6 +#define IPU7_PPI_R_OFFSETCAL_DEBUG_LANE3 0x1ca8 +#define IPU7_PPI_R_OFFSETCAL_DEBUG_LANE4 0x1caa +#define IPU7_PPI_RW_HSDCOCAL_CFG_O 0x1d00 +#define IPU7_PPI_RW_HSDCOCAL_CFG_1 0x1d02 +#define IPU7_PPI_RW_HSDCOCAL_CFG_2 0x1d04 +#define IPU7_PPI_RW_HSDCOCAL_CFG_3 0x1d06 +#define IPU7_PPI_RW_HSDCOCAL_CFG_4 0x1d08 +#define IPU7_PPI_RW_HSDCOCAL_CFG_5 0x1d0a +#define IPU7_PPI_RW_HSDCOCAL_CFG_6 0x1d0c +#define IPU7_PPI_RW_HSDCOCAL_CFG_7 0x1d0e +#define IPU7_PPI_RW_HSDCOCAL_CFG_8 0x1d10 +#define IPU7_PPI_R_HSDCOCAL_DEBUG_RB 0x1d12 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_0 0x2000 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_1 0x2002 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_2 0x2004 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_3 0x2006 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_4 0x2008 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_5 0x200a +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_6 0x200c +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_7 0x200e +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_8 0x2010 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_9 0x2012 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_10 0x2014 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_11 0x2016 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_12 0x2018 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_13 0x201a +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_14 0x201c +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_15 0x201e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_0 0x2020 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_1 0x2022 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_2 0x2024 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_3 0x2026 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_4 0x2028 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_5 0x202a +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_6 0x202c +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_7 0x202e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_8 0x2030 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_9 0x2032 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_10 0x2034 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_11 0x2036 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_12 0x2038 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_13 0x203a +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_14 0x203c +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_15 0x203e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_0 0x2040 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_1 0x2042 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_2 0x2044 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_3 0x2046 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_4 0x2048 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_5 0x204a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_6 0x204c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_7 0x204e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_8 0x2050 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9 0x2052 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_10 0x2054 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_11 0x2056 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_12 0x2058 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_13 0x205a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_14 0x205c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_15 0x205e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_0 0x2060 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_1 0x2062 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_2 0x2064 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_3 0x2066 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_4 0x2068 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_5 0x206a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_6 0x206c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_7 0x206e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_8 0x2070 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_9 0x2072 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_10 0x2074 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_11 0x2076 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_12 0x2078 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_13 0x207a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_14 0x207c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_15 0x207e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_0 0x2080 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_1 0x2082 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_2 0x2084 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_3 0x2086 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_4 0x2088 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_5_0 0x20a0 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_5_1 0x20a2 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_5_2 0x20a4 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_5_3 0x20a6 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_5_4 0x20a8 +#define IPU7_CORE_DIG_RW_TRIO0_0 0x2100 +#define IPU7_CORE_DIG_RW_TRIO0_1 0x2102 +#define IPU7_CORE_DIG_RW_TRIO0_2 0x2104 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_0 0x2400 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_1 0x2402 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_2 0x2404 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_3 0x2406 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_4 0x2408 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_5 0x240a +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_6 0x240c +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_7 0x240e +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_8 0x2410 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_9 0x2412 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_10 0x2414 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_11 0x2416 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_12 0x2418 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_13 0x241a +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_14 0x241c +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_15 0x241e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_0 0x2420 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_1 0x2422 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_2 0x2424 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_3 0x2426 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_4 0x2428 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_5 0x242a +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_6 0x242c +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_7 0x242e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_8 0x2430 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_9 0x2432 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_10 0x2434 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_11 0x2436 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_12 0x2438 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_13 0x243a +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_14 0x243c +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_15 0x243e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_0 0x2440 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_1 0x2442 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2 0x2444 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_3 0x2446 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_4 0x2448 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_5 0x244a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_6 0x244c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_7 0x244e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_8 0x2450 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9 0x2452 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_10 0x2454 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_11 0x2456 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_12 0x2458 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_13 0x245a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_14 0x245c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_15 0x245e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_0 0x2460 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_1 0x2462 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_2 0x2464 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_3 0x2466 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_4 0x2468 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_5 0x246a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_6 0x246c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_7 0x246e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_8 0x2470 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_9 0x2472 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_10 0x2474 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_11 0x2476 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_12 0x2478 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_13 0x247a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_14 0x247c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_15 0x247e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_0 0x2480 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_1 0x2482 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_2 0x2484 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_3 0x2486 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_4 0x2488 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_5_0 0x24a0 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_5_1 0x24a2 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_5_2 0x24a4 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_5_3 0x24a6 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_5_4 0x24a8 +#define IPU7_CORE_DIG_RW_TRIO1_0 0x2500 +#define IPU7_CORE_DIG_RW_TRIO1_1 0x2502 +#define IPU7_CORE_DIG_RW_TRIO1_2 0x2504 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_0 0x2800 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_1 0x2802 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_2 0x2804 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_3 0x2806 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_4 0x2808 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_5 0x280a +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_6 0x280c +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_7 0x280e +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_8 0x2810 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_9 0x2812 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_10 0x2814 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_11 0x2816 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_12 0x2818 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_13 0x281a +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_14 0x281c +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_15 0x281e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_0 0x2820 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_1 0x2822 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_2 0x2824 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_3 0x2826 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_4 0x2828 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_5 0x282a +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_6 0x282c +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_7 0x282e +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_8 0x2830 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_9 0x2832 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_10 0x2834 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_11 0x2836 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_12 0x2838 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_13 0x283a +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_14 0x283c +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_15 0x283e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_0 0x2840 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_1 0x2842 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2 0x2844 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_3 0x2846 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_4 0x2848 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_5 0x284a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_6 0x284c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_7 0x284e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_8 0x2850 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9 0x2852 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_10 0x2854 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_11 0x2856 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_12 0x2858 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_13 0x285a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_14 0x285c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_15 0x285e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_0 0x2860 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_1 0x2862 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_2 0x2864 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_3 0x2866 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_4 0x2868 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_5 0x286a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_6 0x286c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_7 0x286e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_8 0x2870 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_9 0x2872 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_10 0x2874 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_11 0x2876 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_12 0x2878 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_13 0x287a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_14 0x287c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_15 0x287e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_0 0x2880 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_1 0x2882 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_2 0x2884 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_3 0x2886 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_4 0x2888 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_5_0 0x28a0 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_5_1 0x28a2 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_5_2 0x28a4 +#define IPU7_CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_5_3 0x28a6 +#define IPU7_CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_5_4 0x28a8 +#define IPU7_CORE_DIG_RW_TRIO2_0 0x2900 +#define IPU7_CORE_DIG_RW_TRIO2_1 0x2902 +#define IPU7_CORE_DIG_RW_TRIO2_2 0x2904 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_0 0x2c00 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_1 0x2c02 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_2 0x2c04 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_3 0x2c06 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_4 0x2c08 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_5 0x2c0a +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_6 0x2c0c +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_7 0x2c0e +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_8 0x2c10 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_9 0x2c12 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_10 0x2c14 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_11 0x2c16 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_12 0x2c18 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_13 0x2c1a +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_14 0x2c1c +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_15 0x2c1e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_0 0x2c40 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_1 0x2c42 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_2 0x2c44 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_3 0x2c46 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_4 0x2c48 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_5 0x2c4a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_6 0x2c4c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_7 0x2c4e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_8 0x2c50 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_9 0x2c52 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_10 0x2c54 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_11 0x2c56 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_12 0x2c58 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_13 0x2c5a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_14 0x2c5c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_15 0x2c5e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_0 0x2c60 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_1 0x2c62 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_2 0x2c64 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_3 0x2c66 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_4 0x2c68 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_5 0x2c6a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_6 0x2c6c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_7 0x2c6e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_8 0x2c70 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_9 0x2c72 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_10 0x2c74 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_11 0x2c76 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_12 0x2c78 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_13 0x2c7a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_14 0x2c7c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_15 0x2c7e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_0 0x2c80 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_1 0x2c82 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_2 0x2c84 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_3 0x2c86 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_4 0x2c88 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_0 0x3040 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_1 0x3042 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_2 0x3044 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_3 0x3046 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_4 0x3048 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_5 0x304a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_6 0x304c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_7 0x304e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_8 0x3050 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_9 0x3052 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_10 0x3054 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_11 0x3056 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_12 0x3058 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_13 0x305a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_14 0x305c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_15 0x305e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_0 0x3060 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_1 0x3062 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_2 0x3064 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_3 0x3066 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_4 0x3068 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_5 0x306a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_6 0x306c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_7 0x306e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_8 0x3070 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_9 0x3072 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_10 0x3074 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_11 0x3076 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_12 0x3078 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_13 0x307a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_14 0x307c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_15 0x307e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_0 0x3080 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_1 0x3082 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_2 0x3084 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_3 0x3086 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_4 0x3088 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_CLK_OVR_0_0 0x3400 +#define IPU7_CORE_DIG_IOCTRL_RW_DPHY_PPI_CLK_OVR_0_1 0x3402 +#define IPU7_CORE_DIG_IOCTRL_R_DPHY_PPI_CLK_OVR_0_2 0x3404 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_0 0x3800 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_1 0x3802 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_2 0x3804 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_3 0x3806 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_4 0x3808 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_5 0x380a +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_6 0x380c +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_7 0x380e +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_8 0x3810 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_9 0x3812 +#define IPU7_CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_10 0x3814 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_11 0x3816 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_12 0x3818 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_13 0x381a +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_14 0x381c +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_15 0x381e +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_0 0x3820 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_1 0x3822 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_2 0x3824 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_3 0x3826 +#define IPU7_CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_4 0x3828 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_0 0x3840 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_1 0x3842 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_2 0x3844 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_3 0x3846 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_4 0x3848 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_5 0x384a +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_6 0x384c +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_7 0x384e +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_8 0x3850 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_9 0x3852 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_10 0x3854 +#define IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_11 0x3856 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_12 0x3858 +#define IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_13 0x385a +#define IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_14 0x385c +#define IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_15 0x385e +#define IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_3_0 0x3860 +#define IPU7_CORE_DIG_RW_COMMON_0 0x3880 +#define IPU7_CORE_DIG_RW_COMMON_1 0x3882 +#define IPU7_CORE_DIG_RW_COMMON_2 0x3884 +#define IPU7_CORE_DIG_RW_COMMON_3 0x3886 +#define IPU7_CORE_DIG_RW_COMMON_4 0x3888 +#define IPU7_CORE_DIG_RW_COMMON_5 0x388a +#define IPU7_CORE_DIG_RW_COMMON_6 0x388c +#define IPU7_CORE_DIG_RW_COMMON_7 0x388e +#define IPU7_CORE_DIG_RW_COMMON_8 0x3890 +#define IPU7_CORE_DIG_RW_COMMON_9 0x3892 +#define IPU7_CORE_DIG_RW_COMMON_10 0x3894 +#define IPU7_CORE_DIG_RW_COMMON_11 0x3896 +#define IPU7_CORE_DIG_RW_COMMON_12 0x3898 +#define IPU7_CORE_DIG_RW_COMMON_13 0x389a +#define IPU7_CORE_DIG_RW_COMMON_14 0x389c +#define IPU7_CORE_DIG_RW_COMMON_15 0x389e +#define IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_0 0x39e0 +#define IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_1 0x39e2 +#define IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_2 0x39e4 +#define IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_3 0x39e6 +#define IPU7_CORE_DIG_COMMON_RW_DESKEW_FINE_MEM 0x3fe0 +#define IPU7_CORE_DIG_COMMON_R_DESKEW_FINE_MEM 0x3fe2 +#define IPU7_PPI_RW_DPHY_LANE0_LBERT_0 0x4000 +#define IPU7_PPI_RW_DPHY_LANE0_LBERT_1 0x4002 +#define IPU7_PPI_R_DPHY_LANE0_LBERT_0 0x4004 +#define IPU7_PPI_R_DPHY_LANE0_LBERT_1 0x4006 +#define IPU7_PPI_RW_DPHY_LANE0_SPARE 0x4008 +#define IPU7_PPI_RW_DPHY_LANE1_LBERT_0 0x4400 +#define IPU7_PPI_RW_DPHY_LANE1_LBERT_1 0x4402 +#define IPU7_PPI_R_DPHY_LANE1_LBERT_0 0x4404 +#define IPU7_PPI_R_DPHY_LANE1_LBERT_1 0x4406 +#define IPU7_PPI_RW_DPHY_LANE1_SPARE 0x4408 +#define IPU7_PPI_RW_DPHY_LANE2_LBERT_0 0x4800 +#define IPU7_PPI_RW_DPHY_LANE2_LBERT_1 0x4802 +#define IPU7_PPI_R_DPHY_LANE2_LBERT_0 0x4804 +#define IPU7_PPI_R_DPHY_LANE2_LBERT_1 0x4806 +#define IPU7_PPI_RW_DPHY_LANE2_SPARE 0x4808 +#define IPU7_PPI_RW_DPHY_LANE3_LBERT_0 0x4c00 +#define IPU7_PPI_RW_DPHY_LANE3_LBERT_1 0x4c02 +#define IPU7_PPI_R_DPHY_LANE3_LBERT_0 0x4c04 +#define IPU7_PPI_R_DPHY_LANE3_LBERT_1 0x4c06 +#define IPU7_PPI_RW_DPHY_LANE3_SPARE 0x4c08 +#define IPU7_CORE_DIG_DLANE_0_RW_CFG_0 0x6000 +#define IPU7_CORE_DIG_DLANE_0_RW_CFG_1 0x6002 +#define IPU7_CORE_DIG_DLANE_0_RW_CFG_2 0x6004 +#define IPU7_CORE_DIG_DLANE_0_RW_LP_0 0x6080 +#define IPU7_CORE_DIG_DLANE_0_RW_LP_1 0x6082 +#define IPU7_CORE_DIG_DLANE_0_RW_LP_2 0x6084 +#define IPU7_CORE_DIG_DLANE_0_R_LP_0 0x60a0 +#define IPU7_CORE_DIG_DLANE_0_R_LP_1 0x60a2 +#define IPU7_CORE_DIG_DLANE_0_R_HS_TX_0 0x60e0 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_0 0x6100 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_1 0x6102 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_2 0x6104 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_3 0x6106 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_4 0x6108 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_5 0x610a +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_6 0x610c +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_7 0x610e +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_8 0x6110 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_RX_9 0x6112 +#define IPU7_CORE_DIG_DLANE_0_R_HS_RX_0 0x6120 +#define IPU7_CORE_DIG_DLANE_0_R_HS_RX_1 0x6122 +#define IPU7_CORE_DIG_DLANE_0_R_HS_RX_2 0x6124 +#define IPU7_CORE_DIG_DLANE_0_R_HS_RX_3 0x6126 +#define IPU7_CORE_DIG_DLANE_0_R_HS_RX_4 0x6128 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_0 0x6200 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_1 0x6202 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_2 0x6204 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_3 0x6206 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_4 0x6208 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_5 0x620a +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_6 0x620c +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_7 0x620e +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_8 0x6210 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_9 0x6212 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_10 0x6214 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_11 0x6216 +#define IPU7_CORE_DIG_DLANE_0_RW_HS_TX_12 0x6218 +#define IPU7_CORE_DIG_DLANE_1_RW_CFG_0 0x6400 +#define IPU7_CORE_DIG_DLANE_1_RW_CFG_1 0x6402 +#define IPU7_CORE_DIG_DLANE_1_RW_CFG_2 0x6404 +#define IPU7_CORE_DIG_DLANE_1_RW_LP_0 0x6480 +#define IPU7_CORE_DIG_DLANE_1_RW_LP_1 0x6482 +#define IPU7_CORE_DIG_DLANE_1_RW_LP_2 0x6484 +#define IPU7_CORE_DIG_DLANE_1_R_LP_0 0x64a0 +#define IPU7_CORE_DIG_DLANE_1_R_LP_1 0x64a2 +#define IPU7_CORE_DIG_DLANE_1_R_HS_TX_0 0x64e0 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_0 0x6500 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_1 0x6502 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_2 0x6504 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_3 0x6506 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_4 0x6508 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_5 0x650a +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_6 0x650c +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_7 0x650e +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_8 0x6510 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_RX_9 0x6512 +#define IPU7_CORE_DIG_DLANE_1_R_HS_RX_0 0x6520 +#define IPU7_CORE_DIG_DLANE_1_R_HS_RX_1 0x6522 +#define IPU7_CORE_DIG_DLANE_1_R_HS_RX_2 0x6524 +#define IPU7_CORE_DIG_DLANE_1_R_HS_RX_3 0x6526 +#define IPU7_CORE_DIG_DLANE_1_R_HS_RX_4 0x6528 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_0 0x6600 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_1 0x6602 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_2 0x6604 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_3 0x6606 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_4 0x6608 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_5 0x660a +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_6 0x660c +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_7 0x660e +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_8 0x6610 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_9 0x6612 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_10 0x6614 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_11 0x6616 +#define IPU7_CORE_DIG_DLANE_1_RW_HS_TX_12 0x6618 +#define IPU7_CORE_DIG_DLANE_2_RW_CFG_0 0x6800 +#define IPU7_CORE_DIG_DLANE_2_RW_CFG_1 0x6802 +#define IPU7_CORE_DIG_DLANE_2_RW_CFG_2 0x6804 +#define IPU7_CORE_DIG_DLANE_2_RW_LP_0 0x6880 +#define IPU7_CORE_DIG_DLANE_2_RW_LP_1 0x6882 +#define IPU7_CORE_DIG_DLANE_2_RW_LP_2 0x6884 +#define IPU7_CORE_DIG_DLANE_2_R_LP_0 0x68a0 +#define IPU7_CORE_DIG_DLANE_2_R_LP_1 0x68a2 +#define IPU7_CORE_DIG_DLANE_2_R_HS_TX_0 0x68e0 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_0 0x6900 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_1 0x6902 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_2 0x6904 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_3 0x6906 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_4 0x6908 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_5 0x690a +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_6 0x690c +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_7 0x690e +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_8 0x6910 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_RX_9 0x6912 +#define IPU7_CORE_DIG_DLANE_2_R_HS_RX_0 0x6920 +#define IPU7_CORE_DIG_DLANE_2_R_HS_RX_1 0x6922 +#define IPU7_CORE_DIG_DLANE_2_R_HS_RX_2 0x6924 +#define IPU7_CORE_DIG_DLANE_2_R_HS_RX_3 0x6926 +#define IPU7_CORE_DIG_DLANE_2_R_HS_RX_4 0x6928 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_0 0x6a00 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_1 0x6a02 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_2 0x6a04 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_3 0x6a06 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_4 0x6a08 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_5 0x6a0a +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_6 0x6a0c +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_7 0x6a0e +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_8 0x6a10 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_9 0x6a12 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_10 0x6a14 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_11 0x6a16 +#define IPU7_CORE_DIG_DLANE_2_RW_HS_TX_12 0x6a18 +#define IPU7_CORE_DIG_DLANE_3_RW_CFG_0 0x6c00 +#define IPU7_CORE_DIG_DLANE_3_RW_CFG_1 0x6c02 +#define IPU7_CORE_DIG_DLANE_3_RW_CFG_2 0x6c04 +#define IPU7_CORE_DIG_DLANE_3_RW_LP_0 0x6c80 +#define IPU7_CORE_DIG_DLANE_3_RW_LP_1 0x6c82 +#define IPU7_CORE_DIG_DLANE_3_RW_LP_2 0x6c84 +#define IPU7_CORE_DIG_DLANE_3_R_LP_0 0x6ca0 +#define IPU7_CORE_DIG_DLANE_3_R_LP_1 0x6ca2 +#define IPU7_CORE_DIG_DLANE_3_R_HS_TX_0 0x6ce0 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_0 0x6d00 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_1 0x6d02 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_2 0x6d04 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_3 0x6d06 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_4 0x6d08 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_5 0x6d0a +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_6 0x6d0c +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_7 0x6d0e +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_8 0x6d10 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_RX_9 0x6d12 +#define IPU7_CORE_DIG_DLANE_3_R_HS_RX_0 0x6d20 +#define IPU7_CORE_DIG_DLANE_3_R_HS_RX_1 0x6d22 +#define IPU7_CORE_DIG_DLANE_3_R_HS_RX_2 0x6d24 +#define IPU7_CORE_DIG_DLANE_3_R_HS_RX_3 0x6d26 +#define IPU7_CORE_DIG_DLANE_3_R_HS_RX_4 0x6d28 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_0 0x6e00 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_1 0x6e02 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_2 0x6e04 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_3 0x6e06 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_4 0x6e08 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_5 0x6e0a +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_6 0x6e0c +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_7 0x6e0e +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_8 0x6e10 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_9 0x6e12 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_10 0x6e14 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_11 0x6e16 +#define IPU7_CORE_DIG_DLANE_3_RW_HS_TX_12 0x6e18 +#define IPU7_CORE_DIG_DLANE_CLK_RW_CFG_0 0x7000 +#define IPU7_CORE_DIG_DLANE_CLK_RW_CFG_1 0x7002 +#define IPU7_CORE_DIG_DLANE_CLK_RW_CFG_2 0x7004 +#define IPU7_CORE_DIG_DLANE_CLK_RW_LP_0 0x7080 +#define IPU7_CORE_DIG_DLANE_CLK_RW_LP_1 0x7082 +#define IPU7_CORE_DIG_DLANE_CLK_RW_LP_2 0x7084 +#define IPU7_CORE_DIG_DLANE_CLK_R_LP_0 0x70a0 +#define IPU7_CORE_DIG_DLANE_CLK_R_LP_1 0x70a2 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_TX_0 0x70e0 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_0 0x7100 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_1 0x7102 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_2 0x7104 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_3 0x7106 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_4 0x7108 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_5 0x710a +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_6 0x710c +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_7 0x710e +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_8 0x7110 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_9 0x7112 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_RX_0 0x7120 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_RX_1 0x7122 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_RX_2 0x7124 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_RX_3 0x7126 +#define IPU7_CORE_DIG_DLANE_CLK_R_HS_RX_4 0x7128 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_0 0x7200 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_1 0x7202 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_2 0x7204 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_3 0x7206 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_4 0x7208 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_5 0x720a +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_6 0x720c +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_7 0x720e +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_8 0x7210 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_9 0x7212 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_10 0x7214 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_11 0x7216 +#define IPU7_CORE_DIG_DLANE_CLK_RW_HS_TX_12 0x7218 +#define IPU7_PPI_RW_CPHY_TRIO0_LBERT_0 0x8000 +#define IPU7_PPI_RW_CPHY_TRIO0_LBERT_1 0x8002 +#define IPU7_PPI_R_CPHY_TRIO0_LBERT_0 0x8004 +#define IPU7_PPI_R_CPHY_TRIO0_LBERT_1 0x8006 +#define IPU7_PPI_RW_CPHY_TRIO0_SPARE 0x8008 +#define IPU7_PPI_RW_CPHY_TRIO1_LBERT_0 0x8400 +#define IPU7_PPI_RW_CPHY_TRIO1_LBERT_1 0x8402 +#define IPU7_PPI_R_CPHY_TRIO1_LBERT_0 0x8404 +#define IPU7_PPI_R_CPHY_TRIO1_LBERT_1 0x8406 +#define IPU7_PPI_RW_CPHY_TRIO1_SPARE 0x8408 +#define IPU7_PPI_RW_CPHY_TRIO2_LBERT_0 0x8800 +#define IPU7_PPI_RW_CPHY_TRIO2_LBERT_1 0x8802 +#define IPU7_PPI_R_CPHY_TRIO2_LBERT_0 0x8804 +#define IPU7_PPI_R_CPHY_TRIO2_LBERT_1 0x8806 +#define IPU7_PPI_RW_CPHY_TRIO2_SPARE 0x8808 +#define IPU7_CORE_DIG_CLANE_0_RW_CFG_0 0xa000 +#define IPU7_CORE_DIG_CLANE_0_RW_CFG_2 0xa004 +#define IPU7_CORE_DIG_CLANE_0_RW_LP_0 0xa080 +#define IPU7_CORE_DIG_CLANE_0_RW_LP_1 0xa082 +#define IPU7_CORE_DIG_CLANE_0_RW_LP_2 0xa084 +#define IPU7_CORE_DIG_CLANE_0_R_LP_0 0xa0a0 +#define IPU7_CORE_DIG_CLANE_0_R_LP_1 0xa0a2 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_0 0xa100 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_1 0xa102 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_2 0xa104 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_3 0xa106 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_4 0xa108 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_5 0xa10a +#define IPU7_CORE_DIG_CLANE_0_RW_HS_RX_6 0xa10c +#define IPU7_CORE_DIG_CLANE_0_R_RX_0 0xa120 +#define IPU7_CORE_DIG_CLANE_0_R_RX_1 0xa122 +#define IPU7_CORE_DIG_CLANE_0_R_TX_0 0xa124 +#define IPU7_CORE_DIG_CLANE_0_R_RX_2 0xa126 +#define IPU7_CORE_DIG_CLANE_0_R_RX_3 0xa128 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_0 0xa200 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_1 0xa202 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_2 0xa204 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_3 0xa206 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_4 0xa208 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_5 0xa20a +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_6 0xa20c +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_7 0xa20e +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_8 0xa210 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_9 0xa212 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_10 0xa214 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_11 0xa216 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_12 0xa218 +#define IPU7_CORE_DIG_CLANE_0_RW_HS_TX_13 0xa21a +#define IPU7_CORE_DIG_CLANE_1_RW_CFG_0 0xa400 +#define IPU7_CORE_DIG_CLANE_1_RW_CFG_2 0xa404 +#define IPU7_CORE_DIG_CLANE_1_RW_LP_0 0xa480 +#define IPU7_CORE_DIG_CLANE_1_RW_LP_1 0xa482 +#define IPU7_CORE_DIG_CLANE_1_RW_LP_2 0xa484 +#define IPU7_CORE_DIG_CLANE_1_R_LP_0 0xa4a0 +#define IPU7_CORE_DIG_CLANE_1_R_LP_1 0xa4a2 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_0 0xa500 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_1 0xa502 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_2 0xa504 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_3 0xa506 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_4 0xa508 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_5 0xa50a +#define IPU7_CORE_DIG_CLANE_1_RW_HS_RX_6 0xa50c +#define IPU7_CORE_DIG_CLANE_1_R_RX_0 0xa520 +#define IPU7_CORE_DIG_CLANE_1_R_RX_1 0xa522 +#define IPU7_CORE_DIG_CLANE_1_R_TX_0 0xa524 +#define IPU7_CORE_DIG_CLANE_1_R_RX_2 0xa526 +#define IPU7_CORE_DIG_CLANE_1_R_RX_3 0xa528 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_0 0xa600 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_1 0xa602 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_2 0xa604 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_3 0xa606 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_4 0xa608 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_5 0xa60a +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_6 0xa60c +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_7 0xa60e +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_8 0xa610 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_9 0xa612 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_10 0xa614 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_11 0xa616 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_12 0xa618 +#define IPU7_CORE_DIG_CLANE_1_RW_HS_TX_13 0xa61a +#define IPU7_CORE_DIG_CLANE_2_RW_CFG_0 0xa800 +#define IPU7_CORE_DIG_CLANE_2_RW_CFG_2 0xa804 +#define IPU7_CORE_DIG_CLANE_2_RW_LP_0 0xa880 +#define IPU7_CORE_DIG_CLANE_2_RW_LP_1 0xa882 +#define IPU7_CORE_DIG_CLANE_2_RW_LP_2 0xa884 +#define IPU7_CORE_DIG_CLANE_2_R_LP_0 0xa8a0 +#define IPU7_CORE_DIG_CLANE_2_R_LP_1 0xa8a2 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_0 0xa900 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_1 0xa902 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_2 0xa904 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_3 0xa906 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_4 0xa908 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_5 0xa90a +#define IPU7_CORE_DIG_CLANE_2_RW_HS_RX_6 0xa90c +#define IPU7_CORE_DIG_CLANE_2_R_RX_0 0xa920 +#define IPU7_CORE_DIG_CLANE_2_R_RX_1 0xa922 +#define IPU7_CORE_DIG_CLANE_2_R_TX_0 0xa924 +#define IPU7_CORE_DIG_CLANE_2_R_RX_2 0xa926 +#define IPU7_CORE_DIG_CLANE_2_R_RX_3 0xa928 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_0 0xaa00 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_1 0xaa02 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_2 0xaa04 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_3 0xaa06 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_4 0xaa08 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_5 0xaa0a +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_6 0xaa0c +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_7 0xaa0e +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_8 0xaa10 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_9 0xaa12 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_10 0xaa14 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_11 0xaa16 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_12 0xaa18 +#define IPU7_CORE_DIG_CLANE_2_RW_HS_TX_13 0xaa1a + +/* dwc csi host controller registers */ +#define IPU7_IS_IO_CSI2_HOST_BASE(i) (IPU7_IS_IO_BASE + 0x40000 + \ + 0x2000 * (i)) +#define IPU7_VERSION 0x0 +#define IPU7_N_LANES 0x4 +#define IPU7_CSI2_RESETN 0x8 +#define IPU7_INT_ST_MAIN 0xc +#define IPU7_DATA_IDS_1 0x10 +#define IPU7_DATA_IDS_2 0x14 +#define IPU7_CDPHY_MODE 0x1c +#define IPU7_DATA_IDS_VC_1 0x30 +#define IPU7_DATA_IDS_VC_2 0x34 +#define IPU7_PHY_SHUTDOWNZ 0x40 +#define IPU7_DPHY_RSTZ 0x44 +#define IPU7_PHY_RX 0x48 +#define IPU7_PHY_STOPSTATE 0x4c +#define IPU7_PHY_TEST_CTRL0 0x50 +#define IPU7_PHY_TEST_CTRL1 0x54 +#define IPU7_PPI_PG_PATTERN_VRES 0x60 +#define IPU7_PPI_PG_PATTERN_HRES 0x64 +#define IPU7_PPI_PG_CONFIG 0x68 +#define IPU7_PPI_PG_ENABLE 0x6c +#define IPU7_PPI_PG_STATUS 0x70 +#define IPU7_VC_EXTENSION 0xc8 +#define IPU7_PHY_CAL 0xcc +#define IPU7_INT_ST_PHY_FATAL 0xe0 +#define IPU7_INT_MSK_PHY_FATAL 0xe4 +#define IPU7_INT_FORCE_PHY_FATAL 0xe8 +#define IPU7_INT_ST_PKT_FATAL 0xf0 +#define IPU7_INT_MSK_PKT_FATAL 0xf4 +#define IPU7_INT_FORCE_PKT_FATAL 0xf8 +#define IPU7_INT_ST_PHY 0x110 +#define IPU7_INT_MSK_PHY 0x114 +#define IPU7_INT_FORCE_PHY 0x118 +#define IPU7_INT_ST_LINE 0x130 +#define IPU7_INT_MSK_LINE 0x134 +#define IPU7_INT_FORCE_LINE 0x138 +#define IPU7_INT_ST_BNDRY_FRAME_FATAL 0x280 +#define IPU7_INT_MSK_BNDRY_FRAME_FATAL 0x284 +#define IPU7_INT_FORCE_BNDRY_FRAME_FATAL 0x288 +#define IPU7_INT_ST_SEQ_FRAME_FATAL 0x290 +#define IPU7_INT_MSK_SEQ_FRAME_FATAL 0x294 +#define IPU7_INT_FORCE_SEQ_FRAME_FATAL 0x298 +#define IPU7_INT_ST_CRC_FRAME_FATAL 0x2a0 +#define IPU7_INT_MSK_CRC_FRAME_FATAL 0x2a4 +#define IPU7_INT_FORCE_CRC_FRAME_FATAL 0x2a8 +#define IPU7_INT_ST_PLD_CRC_FATAL 0x2b0 +#define IPU7_INT_MSK_PLD_CRC_FATAL 0x2b4 +#define IPU7_INT_FORCE_PLD_CRC_FATAL 0x2b8 +#define IPU7_INT_ST_DATA_ID 0x2c0 +#define IPU7_INT_MSK_DATA_ID 0x2c4 +#define IPU7_INT_FORCE_DATA_ID 0x2c8 +#define IPU7_INT_ST_ECC_CORRECTED 0x2d0 +#define IPU7_INT_MSK_ECC_CORRECTED 0x2d4 +#define IPU7_INT_FORCE_ECC_CORRECTED 0x2d8 +#define IPU7_SCRAMBLING 0x300 +#define IPU7_SCRAMBLING_SEED1 0x304 +#define IPU7_SCRAMBLING_SEED2 0x308 +#define IPU7_SCRAMBLING_SEED3 0x30c +#define IPU7_SCRAMBLING_SEED4 0x310 +#define IPU7_SCRAMBLING 0x300 + +#define IPU7_IS_IO_CSI2_ADPL_PORT_BASE(i) (IPU7_IS_IO_BASE + 0x40800 + \ + 0x2000 * (i)) +#define IPU7_CSI2_ADPL_INPUT_MODE 0x0 +#define IPU7_CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_EN 0x4 +#define IPU7_CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_ADDR 0x8 +#define IPU7_CSI2_ADPL_CSI_RX_ERR_IRQ_STATUS 0xc +#define IPU7_CSI2_ADPL_IRQ_CTL_COMMON_STATUS 0xa4 +#define IPU7_CSI2_ADPL_IRQ_CTL_COMMON_CLEAR 0xa8 +#define IPU7_CSI2_ADPL_IRQ_CTL_COMMON_ENABLE 0xac +#define IPU7_CSI2_ADPL_IRQ_CTL_FS_STATUS 0xbc +#define IPU7_CSI2_ADPL_IRQ_CTL_FS_CLEAR 0xc0 +#define IPU7_CSI2_ADPL_IRQ_CTL_FS_ENABLE 0xc4 +#define IPU7_CSI2_ADPL_IRQ_CTL_FE_STATUS 0xc8 +#define IPU7_CSI2_ADPL_IRQ_CTL_FE_CLEAR 0xcc +#define IPU7_CSI2_ADPL_IRQ_CTL_FE_ENABLE 0xd0 + +/* software control the legacy csi irq */ +#define IPU7_IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(i) (IPU7_IS_IO_BASE + \ + 0x40c00 + 0x2000 * (i)) +#define IPU7_IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(i) (IPU7_IS_IO_BASE + \ + 0x40d00 + 0x2000 * (i)) +#define IPU7_IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE (IPU7_IS_IO_BASE + \ + 0x49000) +#define IPU7_IS_IO_CSI2_IRQ_CTRL_BASE (IPU7_IS_IO_BASE + 0x4e100) + +#define IPU7_IRQ_CTL_EDGE 0x0 +#define IPU7_IRQ_CTL_MASK 0x4 +#define IPU7_IRQ_CTL_STATUS 0x8 +#define IPU7_IRQ_CTL_CLEAR 0xc +#define IPU7_IRQ_CTL_ENABLE 0x10 +/* FE irq for PTL */ +#define IPU7_IRQ1_CTL_MASK 0x14 +#define IPU7_IRQ1_CTL_STATUS 0x18 +#define IPU7_IRQ1_CTL_CLEAR 0x1c +#define IPU7_IRQ1_CTL_ENABLE 0x20 + +/* software to set the clock gate to use the port or mgc */ +#define IPU7_IS_IO_GPREGS_BASE (IPU7_IS_IO_BASE + 0x49200) +#define IPU7_SRST_PORT_ARB 0x0 +#define IPU7_SRST_MGC 0x4 +#define IPU7_SRST_WIDTH_CONV 0x8 +#define IPU7_SRST_CSI_IRQ 0xc +#define IPU7_SRST_CSI_LEGACY_IRQ 0x10 +#define IPU7_CLK_EN_TXCLKESC 0x14 +#define IPU7_CLK_DIV_FACTOR_IS_CLK 0x18 +#define IPU7_CLK_DIV_FACTOR_APB_CLK 0x1c +#define IPU7_CSI_PORT_CLK_GATE 0x20 +#define IPU7_CSI_PORTAB_AGGREGATION 0x24 +#define IPU7_MGC_CLK_GATE 0x2c +#define IPU7_CG_CTRL_BITS 0x3c +#define IPU7_SPARE_RW 0xf8 +#define IPU7_SPARE_RO 0xfc + +#define IPU7_IS_IO_CSI2_MPF_PORT_BASE(i) (IPU7_IS_IO_BASE + 0x53000 + \ + 0x1000 * (i)) +#define IPU7_MPF_16_IRQ_CNTRL_STATUS 0x238 +#define IPU7_MPF_16_IRQ_CNTRL_CLEAR 0x23c +#define IPU7_MPF_16_IRQ_CNTRL_ENABLE 0x240 + +/* software config the phy */ +#define IPU7_IS_IO_CSI2_GPREGS_BASE (IPU7_IS_IO_BASE + 0x53400) +#define IPU8_IS_IO_CSI2_GPREGS_BASE (IPU7_IS_IO_BASE + 0x40e00) +#define IPU7_CSI_ADAPT_LAYER_SRST 0x0 +#define IPU7_MPF_SRST_RST 0x4 +#define IPU7_CSI_ERR_IRQ_CTRL_SRST 0x8 +#define IPU7_CSI_SYNC_RC_SRST 0xc +#define IPU7_CSI_CG_CTRL_BITS 0x10 +#define IPU7_SOC_CSI2HOST_SELECT 0x14 +#define IPU7_PHY_RESET 0x18 +#define IPU7_PHY_SHUTDOWN 0x1c +#define IPU7_PHY_MODE 0x20 +#define IPU7_PHY_READY 0x24 +#define IPU7_PHY_CLK_LANE_FORCE_CONTROL 0x28 +#define IPU7_PHY_CLK_LANE_CONTROL 0x2c +#define IPU7_PHY_CLK_LANE_STATUS 0x30 +#define IPU7_PHY_LANE_RX_ESC_REQ 0x34 +#define IPU7_PHY_LANE_RX_ESC_DATA 0x38 +#define IPU7_PHY_LANE_TURNDISABLE 0x3c +#define IPU7_PHY_LANE_DIRECTION 0x40 +#define IPU7_PHY_LANE_FORCE_CONTROL 0x44 +#define IPU7_PHY_LANE_CONTROL_EN 0x48 +#define IPU7_PHY_LANE_CONTROL_DATAWIDTH 0x4c +#define IPU7_PHY_LANE_STATUS 0x50 +#define IPU7_PHY_LANE_ERR 0x54 +#define IPU7_PHY_LANE_RXALP 0x58 +#define IPU7_PHY_LANE_RXALP_NIBBLE 0x5c +#define IPU7_PHY_PARITY_ERROR 0x60 +#define IPU7_PHY_DEBUG_REGS_CLK_GATE_EN 0x64 +#define IPU7_SPARE_RW 0xf8 +#define IPU7_SPARE_RO 0xfc + +/* software not touch */ +#define IPU7_PORT_ARB_BASE (IPU7_IS_IO_BASE + 0x4e000) +#define IPU7_PORT_ARB_IRQ_CTL_STATUS 0x4 +#define IPU7_PORT_ARB_IRQ_CTL_CLEAR 0x8 +#define IPU7_PORT_ARB_IRQ_CTL_ENABLE 0xc + +#define IPU7_MGC_PPC 4U +#define IPU7_MGC_DTYPE_RAW(i) (((i) - 8) / 2) +#define IPU7_IS_IO_MGC_BASE (IPU7_IS_IO_BASE + 0x48000) +#define IPU7_MGC_KICK 0x0 +#define IPU7_MGC_ASYNC_STOP 0x4 +#define IPU7_MGC_PORT_OFFSET 0x100 +#define IPU7_MGC_CSI_PORT_MAP(i) (0x8 + (i) * 0x4) +#define IPU7_MGC_MG_PORT(i) (IPU7_IS_IO_MGC_BASE + \ + (i) * IPU7_MGC_PORT_OFFSET) +/* per mgc instance */ +#define IPU7_MGC_MG_CSI_ADAPT_LAYER_TYPE 0x28 +#define IPU7_MGC_MG_MODE 0x2c +#define IPU7_MGC_MG_INIT_COUNTER 0x30 +#define IPU7_MGC_MG_MIPI_VC 0x34 +#define IPU7_MGC_MG_MIPI_DTYPES 0x38 +#define IPU7_MGC_MG_MULTI_DTYPES_MODE 0x3c +#define IPU7_MGC_MG_NOF_FRAMES 0x40 +#define IPU7_MGC_MG_FRAME_DIM 0x44 +#define IPU7_MGC_MG_HBLANK 0x48 +#define IPU7_MGC_MG_VBLANK 0x4c +#define IPU7_MGC_MG_TPG_MODE 0x50 +#define IPU7_MGC_MG_TPG_R0 0x54 +#define IPU7_MGC_MG_TPG_G0 0x58 +#define IPU7_MGC_MG_TPG_B0 0x5c +#define IPU7_MGC_MG_TPG_R1 0x60 +#define IPU7_MGC_MG_TPG_G1 0x64 +#define IPU7_MGC_MG_TPG_B1 0x68 +#define IPU7_MGC_MG_TPG_FACTORS 0x6c +#define IPU7_MGC_MG_TPG_MASKS 0x70 +#define IPU7_MGC_MG_TPG_XY_MASK 0x74 +#define IPU7_MGC_MG_TPG_TILE_DIM 0x78 +#define IPU7_MGC_MG_PRBS_LFSR_INIT_0 0x7c +#define IPU7_MGC_MG_PRBS_LFSR_INIT_1 0x80 +#define IPU7_MGC_MG_SYNC_STOP_POINT 0x84 +#define IPU7_MGC_MG_SYNC_STOP_POINT_LOC 0x88 +#define IPU7_MGC_MG_ERR_INJECT 0x8c +#define IPU7_MGC_MG_ERR_LOCATION 0x90 +#define IPU7_MGC_MG_DTO_SPEED_CTRL_EN 0x94 +#define IPU7_MGC_MG_DTO_SPEED_CTRL_INCR_VAL 0x98 +#define IPU7_MGC_MG_HOR_LOC_STTS 0x9c +#define IPU7_MGC_MG_VER_LOC_STTS 0xa0 +#define IPU7_MGC_MG_FRAME_NUM_STTS 0xa4 +#define IPU7_MGC_MG_BUSY_STTS 0xa8 +#define IPU7_MGC_MG_STOPPED_STTS 0xac +/* tile width and height in pixels for Chess board and Color palette */ +#define IPU7_MGC_TPG_TILE_WIDTH 64U +#define IPU7_MGC_TPG_TILE_HEIGHT 64U + +#define IPU7_CSI_PORT_A_ADDR_OFFSET 0x0 +#define IPU7_CSI_PORT_B_ADDR_OFFSET 0x0 +#define IPU7_CSI_PORT_C_ADDR_OFFSET 0x0 +#define IPU7_CSI_PORT_D_ADDR_OFFSET 0x0 + +/* + * 0 - CSI RX Port 0 interrupt; + * 1 - MPF Port 0 interrupt; + * 2 - CSI RX Port 1 interrupt; + * 3 - MPF Port 1 interrupt; + * 4 - CSI RX Port 2 interrupt; + * 5 - MPF Port 2 interrupt; + * 6 - CSI RX Port 3 interrupt; + * 7 - MPF Port 3 interrupt; + * 8 - Port ARB FIFO 0 overflow; + * 9 - Port ARB FIFO 1 overflow; + * 10 - Port ARB FIFO 2 overflow; + * 11 - Port ARB FIFO 3 overflow; + * 12 - isys_cfgnoc_err_probe_intl; + * 13-15 - reserved + */ +#define IPU7_CSI_IS_IO_IRQ_MASK 0xffff + +/* Adapter layer irq */ +#define IPU7_CSI_ADPL_IRQ_MASK 0xffff + +/* sw irq from legacy irq control + * legacy irq status + * IPU7 + * 0 - CSI Port 0 error interrupt + * 1 - CSI Port 0 sync interrupt + * 2 - CSI Port 1 error interrupt + * 3 - CSI Port 1 sync interrupt + * 4 - CSI Port 2 error interrupt + * 5 - CSI Port 2 sync interrupt + * 6 - CSI Port 3 error interrupt + * 7 - CSI Port 3 sync interrupt + * IPU7P5 + * 0 - CSI Port 0 error interrupt + * 1 - CSI Port 0 fs interrupt + * 2 - CSI Port 0 fe interrupt + * 3 - CSI Port 1 error interrupt + * 4 - CSI Port 1 fs interrupt + * 5 - CSI Port 1 fe interrupt + * 6 - CSI Port 2 error interrupt + * 7 - CSI Port 2 fs interrupt + * 8 - CSI Port 2 fe interrupt + */ +#define IPU7_CSI_RX_LEGACY_IRQ_MASK 0x1ff + +/* legacy error status per port + * 0 - Error handler FIFO full; + * 1 - Reserved Short Packet encoding detected; + * 2 - Reserved Long Packet encoding detected; + * 3 - Received packet is too short (fewer data words than specified in header); + * 4 - Received packet is too long (more data words than specified in header); + * 5 - Short packet discarded due to errors; + * 6 - Long packet discarded due to errors; + * 7 - CSI Combo Rx interrupt; + * 8 - IDI CDC FIFO overflow; remaining bits are reserved and tied to 0; + */ +#define IPU7_CSI_RX_ERROR_IRQ_MASK 0xfff + +/* + * 0 - VC0 frame start received + * 1 - VC0 frame end received + * 2 - VC1 frame start received + * 3 - VC1 frame end received + * 4 - VC2 frame start received + * 5 - VC2 frame end received + * 6 - VC3 frame start received + * 7 - VC3 frame end received + * 8 - VC4 frame start received + * 9 - VC4 frame end received + * 10 - VC5 frame start received + * 11 - VC5 frame end received + * 12 - VC6 frame start received + * 13 - VC6 frame end received + * 14 - VC7 frame start received + * 15 - VC7 frame end received + * 16 - VC8 frame start received + * 17 - VC8 frame end received + * 18 - VC9 frame start received + * 19 - VC9 frame end received + * 20 - VC10 frame start received + * 21 - VC10 frame end received + * 22 - VC11 frame start received + * 23 - VC11 frame end received + * 24 - VC12 frame start received + * 25 - VC12 frame end received + * 26 - VC13 frame start received + * 27 - VC13 frame end received + * 28 - VC14 frame start received + * 29 - VC14 frame end received + * 30 - VC15 frame start received + * 31 - VC15 frame end received + */ + +#define IPU7_CSI_RX_SYNC_IRQ_MASK 0x0 +#define IPU7P5_CSI_RX_SYNC_FE_IRQ_MASK 0x0 + +#define IPU7_CSI_RX_NUM_ERRORS_IN_IRQ 12U +#define IPU7_CSI_RX_NUM_SYNC_IN_IRQ 32U + +enum IPU7_MGC_CSI_ADPL_TYPE { + IPU7_MGC_MAPPED_2_LANES = 0, + IPU7_MGC_MAPPED_4_LANES = 1, +}; + +enum IPU7_CSI2HOST_SELECTION { + IPU7_CSI2HOST_SEL_SOC = 0, + IPU7_CSI2HOST_SEL_CSI2HOST = 1, +}; + +#define IPU7_CSI_LEGACY_IRQ_MASK(port) (0x3 << ((port) * 2)) +#define IPU7P5_CSI_LEGACY_IRQ_MASK(port) (0x7 << ((port) * 3)) + +#define IPU7_ISYS_LEGACY_IRQ_CSI2(port) (0x3 << (port)) +#define IPU7P5_ISYS_LEGACY_IRQ_CSI2(port) (0x7 << (port)) + +/* ---------------------------------------------------------------- */ +#define IPU7_CSI_REG_BASE 0x220000 +#define IPU7_CSI_REG_BASE_PORT(id) ((id) * 0x1000) + +/* CSI Port General Purpose Registers */ +#define IPU7_CSI_REG_PORT_GPREG_SRST 0x0 +#define IPU7_CSI_REG_PORT_GPREG_CSI2_SLV_REG_SRST 0x4 +#define IPU7_CSI_REG_PORT_GPREG_CSI2_PORT_CONTROL 0x8 + +#define IPU7_CSI_RX_SYNC_FS_VC 0x55555555 +#define IPU7_CSI_RX_SYNC_FE_VC 0xaaaaaaaa +#define IPU7P5_CSI_RX_SYNC_FS_VC 0xffff +#define IPU7P5_CSI_RX_SYNC_FE_VC 0xffff + +#endif /* IPU7_ISYS_CSI2_REG_H */ From f268203d74ff25f5443a16813196fb8ecd775be3 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:18 +0300 Subject: [PATCH 073/596] media: ipu6: Add ipu7 isr handler Add ipu7 interrupt handler for software and CSI events. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h | 1 + drivers/media/pci/intel/ipu6/ipu6-isys.c | 2 +- drivers/media/pci/intel/ipu6/ipu7-fw-isys.c | 330 +++++++++++++++++- drivers/media/pci/intel/ipu6/ipu7-fw-isys.h | 38 ++ .../media/pci/intel/ipu6/ipu7-platform-regs.h | 20 ++ 5 files changed, 386 insertions(+), 5 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h index ce8eed91065c..ef367c7dc0fa 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h @@ -43,6 +43,7 @@ struct ipu6_isys_csi2 { u32 receiver_errors; unsigned int nlanes; unsigned int port; + u32 legacy_irq_mask; }; struct ipu6_isys_csi2_timing { diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 086c0f01ccda..bcdaf96e9350 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -1075,7 +1075,7 @@ static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = { }; static const struct ipu6_auxdrv_data ipu7_isys_auxdrv_data = { - .isr = ipu6_isys_isr, + .isr = ipu7_isys_isr, .isr_threaded = NULL, .wake_isr_thread = false, .fw_ops = &ipu7_fw_isys_ops, diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c index 5a362b567feb..f821ec5f55b2 100644 --- a/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c @@ -3,12 +3,17 @@ * Copyright (C) 2026 Intel Corporation */ +#include + #include "ipu6-bus.h" #include "ipu6-dma.h" #include "ipu6-isys.h" +#include "ipu6-platform-regs.h" #include "ipu7-boot.h" #include "ipu7-fw-com.h" #include "ipu7-fw-isys.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-platform-regs.h" static void ipu7_fw_isys_cleanup(struct ipu6_isys *isys) { @@ -56,7 +61,7 @@ static int ipu7_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) struct ipu7_insys_config *fw_config; struct ipu7_fw_com_context *fwctx; dma_addr_t fw_config_dma_addr; - unsigned int i, num_queues; + unsigned int num_queues; u32 freq; int ret; @@ -94,7 +99,7 @@ static int ipu7_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) queue_configs[IPU7_INSYS_INPUT_DEV_QUEUE].token_size_in_bytes = sizeof(struct ipu7_insys_send_queue_token); - for (i = IPU7_INSYS_INPUT_MSG_QUEUE; i < num_queues; i++) { + for (unsigned int i = IPU7_INSYS_INPUT_MSG_QUEUE; i < num_queues; i++) { queue_configs[i].max_capacity = IPU7_ISYS_SIZE_SEND_QUEUE; queue_configs[i].token_size_in_bytes = sizeof(struct ipu7_insys_send_queue_token); @@ -321,7 +326,6 @@ static void ipu7_fw_isys_dump_frame_buf_set(struct device *dev, unsigned int outputs) { struct ipu7_fw_isys_frame_buff_set *buf = &msg->ipu7.frame; - unsigned int i; dev_dbg(dev, "--------------------------\n"); dev_dbg(dev, "IPU_ISYS_BUFF_SET\n"); @@ -329,7 +333,7 @@ static void ipu7_fw_isys_dump_frame_buf_set(struct device *dev, dev_dbg(dev, ".frame_id = %d\n", buf->frame_id); dev_dbg(dev, ".skip_frame = %d\n", buf->skip_frame); - for (i = 0; i < outputs; i++) { + for (unsigned int i = 0; i < outputs; i++) { dev_dbg(dev, ".output_pin[%d]:\n", i); dev_dbg(dev, "\t.user_token = %llx\n", buf->output_pins[i].user_token); @@ -463,3 +467,321 @@ const struct ipu6_fw_isys_ops ipu7_fw_isys_ops = { .dump_stream_cfg = ipu7_fw_isys_dump_stream_cfg, .dump_frame_buf_set = ipu7_fw_isys_dump_frame_buf_set, }; + +static const struct ipu7_csi2_error { + const char *error_string; + bool is_info_only; +} dphy_rx_errors[] = { + { "Error handler FIFO full", false }, + { "Reserved Short Packet encoding detected", true }, + { "Reserved Long Packet encoding detected", true }, + { "Received packet is too short", false}, + { "Received packet is too long", false}, + { "Short packet discarded due to errors", false }, + { "Long packet discarded due to errors", false }, + { "CSI Combo Rx interrupt", false }, + { "IDI CDC FIFO overflow(remaining bits are reserved as 0)", false }, + { "Received NULL packet", true }, + { "Received blanking packet", true }, + { "Tie to 0", true }, +}; + +static void ipu7_isys_register_errors(struct ipu6_isys_csi2 *csi2) +{ + u32 offset = IPU7_IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port); + u32 status = readl(csi2->base + offset + IPU7_IRQ_CTL_STATUS); + u32 mask = IPU7_CSI_RX_ERROR_IRQ_MASK; + + if (!status) + return; + + dev_dbg(&csi2->isys->adev->auxdev.dev, "csi2-%u error status 0x%08x\n", + csi2->port, status); + + writel(status & mask, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + csi2->receiver_errors |= status & mask; +} + +static void ipu7_isys_csi2_error(struct ipu6_isys_csi2 *csi2) +{ + u32 status; + + /* Register errors once more in case of error interrupts are disabled */ + ipu7_isys_register_errors(csi2); + status = csi2->receiver_errors; + csi2->receiver_errors = 0; + + for (unsigned int i = 0; i < ARRAY_SIZE(dphy_rx_errors); i++) { + if (status & BIT(i)) + dev_err_ratelimited(&csi2->isys->adev->auxdev.dev, + "csi2-%i error: %s\n", + csi2->port, + dphy_rx_errors[i].error_string); + } +} + +static const struct resp_to_msg { + enum ipu7_insys_resp_type type; + const char *msg; +} is_fw_msg[] = { + { IPU7_INSYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE" }, + { IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, + "STREAM_START_AND_CAPTURE_ACK" }, + { IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK" }, + { IPU7_INSYS_RESP_TYPE_STREAM_ABORT_ACK, "STREAM_ABORT_ACK" }, + { IPU7_INSYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK" }, + { IPU7_INSYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK" }, + { IPU7_INSYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY" }, + { IPU7_INSYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF" }, + { IPU7_INSYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF" }, + { IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, + "STREAM_START_AND_CAPTURE_DONE" }, + { IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE" }, + { N_IPU7_INSYS_RESP_TYPE, "N_IPU7_INSYS_RESP_TYPE" }, +}; + +static int ipu7_isys_isr_one(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + struct ipu6_isys_stream *stream = NULL; + struct device *dev = &adev->auxdev.dev; + struct ipu6_isys_csi2 *csi2 = NULL; + struct ipu7_fw_isys_msg_err err_info; + struct isys_fw_msgs *isys_fw_msg; + struct ipu7_insys_resp *resp; + u64 ts; + + if (!isys->fwctx) + return 1; + + resp = ipu7_fw_isys_get_resp(isys); + if (!resp) + return 1; + + if (resp->type >= N_IPU7_INSYS_RESP_TYPE) { + dev_err(dev, "Unknown response type %u stream %u\n", + resp->type, resp->stream_id); + ipu7_fw_isys_put_resp(isys); + return 1; + } + + err_info = resp->error_info; + ts = ((u64)resp->timestamp[1] << 32) | resp->timestamp[0]; + + if (err_info.err_group == INSYS_MSG_ERR_GROUP_CAPTURE && + err_info.err_code == INSYS_MSG_ERR_CAPTURE_SYNC_FRAME_DROP) { + /* receive a sp w/o command, firmware drop it */ + dev_dbg(dev, "FRAME DROP: %02u %s stream %u\n", + resp->type, is_fw_msg[resp->type].msg, + resp->stream_id); + dev_dbg(dev, "\tpin %u buf_id %llx frame %u\n", + resp->pin_id, resp->buf_id, resp->frame_id); + dev_dbg(dev, "\terror group %u code %u details [%u %u]\n", + err_info.err_group, err_info.err_code, + err_info.err_detail[0], err_info.err_detail[1]); + } else if (err_info.err_code) { + dev_err(dev, "%02u %s stream %u pin %u buf_id %llx frame %u\n", + resp->type, is_fw_msg[resp->type].msg, resp->stream_id, + resp->pin_id, resp->buf_id, resp->frame_id); + dev_err(dev, "\terror group %u code %u details [%u %u]\n", + err_info.err_group, err_info.err_code, + err_info.err_detail[0], err_info.err_detail[1]); + } else { + dev_dbg(dev, "%02u %s stream %u pin %u buf_id %llx frame %u\n", + resp->type, is_fw_msg[resp->type].msg, resp->stream_id, + resp->pin_id, resp->buf_id, resp->frame_id); + dev_dbg(dev, "\tts %llu\n", ts); + } + + if (resp->stream_id >= IPU7_ISYS_MAX_STREAMS) { + dev_err(dev, "bad stream handle %u\n", + resp->stream_id); + goto leave; + } + + stream = ipu6_isys_query_stream_by_handle(isys, resp->stream_id); + if (!stream) { + dev_err(dev, "stream of stream_handle %u is unused\n", + resp->stream_id); + goto leave; + } + + stream->error = err_info.err_code; + + if (stream->asd) + csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + + switch (resp->type) { + case IPU7_INSYS_RESP_TYPE_STREAM_OPEN_DONE: + complete(&stream->stream_open_completion); + break; + case IPU7_INSYS_RESP_TYPE_STREAM_CLOSE_ACK: + complete(&stream->stream_close_completion); + break; + case IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK: + complete(&stream->stream_start_completion); + break; + case IPU7_INSYS_RESP_TYPE_STREAM_ABORT_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU7_INSYS_RESP_TYPE_STREAM_FLUSH_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU7_INSYS_RESP_TYPE_PIN_DATA_READY: + /* + * firmware only release the capture msg until software + * get pin_data_ready event + */ + isys_fw_msg = container_of((void *)(uintptr_t)resp->buf_id, + struct isys_fw_msgs, dummy); + + ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), isys_fw_msg); + if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS) + ipu6_stream_buf_ready(stream, resp->pin_id, + resp->pin.addr, ts, 0); + else + dev_err(dev, "No handler for pin %u ready\n", + resp->pin_id); + if (csi2) + ipu7_isys_csi2_error(csi2); + + break; + case IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_ACK: + break; + case IPU7_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: + case IPU7_INSYS_RESP_TYPE_STREAM_CAPTURE_DONE: + break; + case IPU7_INSYS_RESP_TYPE_FRAME_SOF: + if (csi2) + ipu6_isys_csi2_sof_event_by_stream(stream); + + stream->seq[stream->seq_index].sequence = + atomic_read(&stream->sequence) - 1U; + stream->seq[stream->seq_index].timestamp = ts; + dev_dbg(dev, + "SOF: stream %u frame %u (index %u), ts 0x%16.16llx\n", + resp->stream_id, resp->frame_id, + stream->seq[stream->seq_index].sequence, ts); + stream->seq_index = (stream->seq_index + 1U) + % IPU6_ISYS_MAX_PARALLEL_SOF; + break; + case IPU7_INSYS_RESP_TYPE_FRAME_EOF: + if (csi2) + ipu6_isys_csi2_eof_event_by_stream(stream); + + dev_dbg(dev, "eof: stream %d(index %u) ts 0x%16.16llx\n", + resp->stream_id, + stream->seq[stream->seq_index].sequence, ts); + break; + default: + dev_err(dev, "Unknown response type %u stream %u\n", + resp->type, resp->stream_id); + break; + } + + ipu6_isys_put_stream(stream); +leave: + ipu7_fw_isys_put_resp(isys); + + return 0; +} + +#define IPU7_NR_OF_CSI2_VC 16U +static void ipu7_isys_csi2_isr(struct ipu6_isys_csi2 *csi2) +{ + struct device *dev = &csi2->isys->adev->auxdev.dev; + struct ipu6_device *isp = csi2->isys->adev->isp; + struct ipu6_isys_stream *s; + u32 sync, offset; + u32 fe = 0; + u8 vc; + + ipu7_isys_register_errors(csi2); + + offset = IPU7_IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port); + sync = readl(csi2->base + offset + IPU7_IRQ_CTL_STATUS); + writel(sync, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + dev_dbg(dev, "csi2-%u sync status 0x%08x\n", csi2->port, sync); + + if (!IS_IPU7_MTL(isp)) { + fe = readl(csi2->base + offset + IPU7_IRQ1_CTL_STATUS); + writel(fe, csi2->base + offset + IPU7_IRQ1_CTL_CLEAR); + dev_dbg(dev, "csi2-%u FE status 0x%08x\n", csi2->port, fe); + } + + for (vc = 0; vc < IPU7_NR_OF_CSI2_VC && (sync || fe); vc++) { + s = ipu6_isys_query_stream_by_source(csi2->isys, + csi2->asd.source, vc); + if (!s) + continue; + + if (!IS_IPU7_MTL(isp)) { + if (sync & IPU7P5_CSI_RX_SYNC_FS_VC & (1U << vc)) + ipu6_isys_csi2_sof_event_by_stream(s); + + if (fe & IPU7P5_CSI_RX_SYNC_FE_VC & (1U << vc)) + ipu6_isys_csi2_eof_event_by_stream(s); + } else { + if (sync & IPU7_CSI_RX_SYNC_FS_VC & (1U << (vc * 2))) + ipu6_isys_csi2_sof_event_by_stream(s); + + if (sync & IPU7_CSI_RX_SYNC_FE_VC & (2U << (vc * 2))) + ipu6_isys_csi2_eof_event_by_stream(s); + } + } +} + +static void ipu7_dispatch_csi2_isr(struct ipu6_isys *isys, u32 status) +{ + for (unsigned int i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { + if (!isys->csi2[i].base) + continue; + if (status & isys->csi2[i].legacy_irq_mask) + ipu7_isys_csi2_isr(&isys->csi2[i]); + } +} + +irqreturn_t ipu7_isys_isr(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + void __iomem *base = isys->pdata->base; + u32 status_sw, status_csi; + u32 csi_offset, sw_offset; + + guard(spinlock)(&isys->power_lock); + + if (!isys->power) + return IRQ_NONE; + + csi_offset = IPU7_IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE; + sw_offset = IPU7_IS_UC_CTRL_BASE; + + status_csi = readl(base + csi_offset + IPU7_IRQ_CTL_STATUS); + status_sw = readl(base + sw_offset + IPU7_TO_SW_IRQ_CNTL_STATUS); + + if (!status_csi && !status_sw) + return IRQ_NONE; + + do { + writel(status_sw, base + sw_offset + IPU7_TO_SW_IRQ_CNTL_CLEAR); + writel(status_csi, base + csi_offset + IPU7_IRQ_CTL_CLEAR); + + if (isys->isr_csi2_bits & status_csi) + ipu7_dispatch_csi2_isr(isys, status_csi); + + if (!ipu7_isys_isr_one(adev)) + status_sw = IPU7_TO_SW_IRQ_FW; + else + status_sw = 0; + + status_csi = readl(base + csi_offset + IPU7_IRQ_CTL_STATUS); + status_sw |= readl(base + sw_offset + + IPU7_TO_SW_IRQ_CNTL_STATUS); + } while ((status_csi & isys->isr_csi2_bits) || + (status_sw & IPU7_TO_SW_IRQ_FW)); + + writel(IPU7_IS_UC_TO_SW_IRQ_MASK, + base + sw_offset + IPU7_TO_SW_IRQ_CNTL_MASK_N); + + return IRQ_HANDLED; +} diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.h index 122531065362..d5289f6add8c 100644 --- a/drivers/media/pci/intel/ipu6/ipu7-fw-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.h @@ -117,6 +117,43 @@ enum ipu7_insys_mipi_dt_rename_mode { N_IPU7_INSYS_MIPI_DT_MODE }; +enum insys_msg_err_capture { + INSYS_MSG_ERR_CAPTURE_OK = 0, + INSYS_MSG_ERR_CAPTURE_STREAM_ID = 1, + INSYS_MSG_ERR_CAPTURE_PAYLOAD_PTR = 2, + INSYS_MSG_ERR_CAPTURE_MEM_SLOT = 3, + INSYS_MSG_ERR_CAPTURE_STREAMING_MODE = 4, + INSYS_MSG_ERR_CAPTURE_AVAILABLE_CMD_SLOT = 5, + INSYS_MSG_ERR_CAPTURE_CONSUMED_CMD_SLOT = 6, + INSYS_MSG_ERR_CAPTURE_CMD_SLOT_PAYLOAD_PTR = 7, + INSYS_MSG_ERR_CAPTURE_CMD_PREPARE = 8, + INSYS_MSG_ERR_CAPTURE_OUTPUT_PIN = 9, + INSYS_MSG_ERR_CAPTURE_SYNC_FRAME_DROP = 10, + INSYS_MSG_ERR_CAPTURE_FRAME_MESSAGES_MAP = 11, + INSYS_MSG_ERR_CAPTURE_TIMEOUT = 12, + INSYS_MSG_ERR_CAPTURE_INVALID_STREAM_STATE = 13, + INSYS_MSG_ERR_CAPTURE_HW_ERR_MULTIBIT_PH_ERROR_DETECTED = 14, + INSYS_MSG_ERR_CAPTURE_HW_ERR_PAYLOAD_CRC_ERROR = 15, + INSYS_MSG_ERR_CAPTURE_HW_ERR_INPUT_DATA_LOSS_ELASTIC_FIFO_OVFL = 16, + INSYS_MSG_ERR_CAPTURE_HW_ERR_PIXEL_BUFFER_OVERFLOW = 17, + INSYS_MSG_ERR_CAPTURE_HW_ERR_BAD_FRAME_DIM = 18, + INSYS_MSG_ERR_CAPTURE_HW_ERR_PHY_SYNC_ERR = 19, + INSYS_MSG_ERR_CAPTURE_HW_ERR_SECURE_TOUCH = 20, + INSYS_MSG_ERR_CAPTURE_HW_ERR_MASTER_SLAVE_SYNC_ERR = 21, + INSYS_MSG_ERR_CAPTURE_FRAME_SKIP_ERR = 22, + INSYS_MSG_ERR_CAPTURE_FE_INPUT_FIFO_OVERFLOW_ERR = 23, + INSYS_MSG_ERR_CAPTURE_CMD_SUBMIT_TO_HW = 24, + INSYS_MSG_ERR_CAPTURE_N +}; + +enum insys_msg_err_groups { + INSYS_MSG_ERR_GROUP_RESERVED = 0, + INSYS_MSG_ERR_GROUP_GENERAL = 1, + INSYS_MSG_ERR_GROUP_STREAM = 2, + INSYS_MSG_ERR_GROUP_CAPTURE = 3, + INSYS_MSG_ERR_GROUP_N, +}; + struct ipu7_fw_isys_logger_config { u8 use_source_severity; u8 source_severity[IPU7_FWLOG_MAX_LOGGER_SOURCES]; @@ -254,5 +291,6 @@ struct ipu7_fw_isys_frame_buff_set { } __packed; struct ipu6_fw_isys_ops *ipu7_fw_isys_get_ops(void); +irqreturn_t ipu7_isys_isr(struct ipu6_bus_device *adev); #endif diff --git a/drivers/media/pci/intel/ipu6/ipu7-platform-regs.h b/drivers/media/pci/intel/ipu6/ipu7-platform-regs.h index 3ca9d65e58b9..39cd5c0890e5 100644 --- a/drivers/media/pci/intel/ipu6/ipu7-platform-regs.h +++ b/drivers/media/pci/intel/ipu6/ipu7-platform-regs.h @@ -9,4 +9,24 @@ #define IPU7_PS_UC_CTRL_BASE 0x130000 #define IPU7_PSYS_DMEM_OFFSET 0x100000 +#define IPU7_IS_IO_BASE 0x280000 +#define IPU7_IS_IO_CSI2_GPREGS_BASE (IPU7_IS_IO_BASE + 0x53400) + +#define IPU7_IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE (IPU7_IS_IO_BASE + 0x49000) +#define IPU7_IRQ_CTL_EDGE 0x0 +#define IPU7_IRQ_CTL_MASK 0x4 +#define IPU7_IRQ_CTL_STATUS 0x8 +#define IPU7_IRQ_CTL_CLEAR 0xc +#define IPU7_IRQ_CTL_ENABLE 0x10 +#define IPU7_CSI_RX_LEGACY_IRQ_MASK 0x1ff + +#define IPU7_TO_SW_IRQ_CNTL_EDGE 0x4000 +#define IPU7_TO_SW_IRQ_CNTL_MASK_N 0x4004 +#define IPU7_TO_SW_IRQ_CNTL_STATUS 0x4008 +#define IPU7_TO_SW_IRQ_CNTL_CLEAR 0x400c +#define IPU7_TO_SW_IRQ_CNTL_ENABLE 0x4010 +#define IPU7_IS_UC_TO_SW_IRQ_MASK 0xf +#define IPU7_TO_SW_IRQ_FW BIT(0) +#define IPU7_REG_PRINTF_AXI_CNTL 0x301c + #endif From 749dd921c748fc63f84b93889d629aecef66504f Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:19 +0300 Subject: [PATCH 074/596] media: ipu6: Add ipu7 csi phy driver The csi receiver is completely different in ipu7 compared to ipu6, add a driver for it. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/Makefile | 3 +- drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h | 4 + drivers/media/pci/intel/ipu6/ipu6-isys.c | 7 +- drivers/media/pci/intel/ipu6/ipu6-isys.h | 1 + drivers/media/pci/intel/ipu6/ipu6.c | 6 + drivers/media/pci/intel/ipu6/ipu6.h | 2 + .../media/pci/intel/ipu6/ipu7-isys-csi-phy.c | 1072 +++++++++++++++++ .../media/pci/intel/ipu6/ipu7-isys-csi-phy.h | 16 + 8 files changed, 1109 insertions(+), 2 deletions(-) create mode 100644 drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.c create mode 100644 drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.h diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile index 66986d5709ae..676bc978b972 100644 --- a/drivers/media/pci/intel/ipu6/Makefile +++ b/drivers/media/pci/intel/ipu6/Makefile @@ -23,6 +23,7 @@ intel-ipu6-isys-y := ipu6-isys.o \ ipu6-isys-subdev.o \ ipu6-isys-mcd-phy.o \ ipu6-isys-jsl-phy.o \ - ipu6-isys-dwc-phy.o + ipu6-isys-dwc-phy.o \ + ipu7-isys-csi-phy.o obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-isys.o diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h index ef367c7dc0fa..dbaaf00af623 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h @@ -16,6 +16,9 @@ struct ipu6_isys_video; struct ipu6_isys; struct ipu6_isys_stream; +#define PHY_MODE_DPHY 0 +#define PHY_MODE_CPHY 1 + #define NR_OF_CSI2_VC 16 #define INVALID_VC_ID -1 #define NR_OF_CSI2_SINK_PADS 1 @@ -44,6 +47,7 @@ struct ipu6_isys_csi2 { unsigned int nlanes; unsigned int port; u32 legacy_irq_mask; + unsigned int phy_mode; }; struct ipu6_isys_csi2_timing { diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index bcdaf96e9350..5de5fd16ca30 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -41,6 +41,9 @@ #include "ipu6-platform-buttress-regs.h" #include "ipu6-platform-isys-csi2-reg.h" #include "ipu6-platform-regs.h" +#include "ipu7-isys-csi-phy.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-platform-regs.h" #define IPU6_BUTTRESS_FABIC_CONTROL 0x68 #define GDA_ENABLE_IWAKE_INDEX 2 @@ -1004,7 +1007,9 @@ static int isys_probe(struct auxiliary_device *auxdev, isys_iwake_watermark_init(isys); - if (IS_IPU6SE(adev->isp)) + if (IS_IPU7(adev->isp)) + isys->phy_set_power = ipu7_isys_csi_phy_set_power; + else if (IS_IPU6SE(adev->isp)) isys->phy_set_power = ipu6_isys_jsl_phy_set_power; else if (IS_IPU6EP_MTL(adev->isp)) isys->phy_set_power = ipu6_isys_dwc_phy_set_power; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h index 697c090cc3b1..30539ea5f826 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -138,6 +138,7 @@ struct ipu6_isys { int streams_ref_count[IPU6_ISYS_MAX_STREAMS]; void *fwctx; u32 phy_termcal_val; + u32 phy_rext_cal; bool need_reset; bool icache_prefetch; bool csi2_cse_ipc_not_supported; diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 51174cafb1b4..fd7888a011a5 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -32,6 +32,7 @@ #include "ipu6-platform-buttress-regs.h" #include "ipu6-platform-isys-csi2-reg.h" #include "ipu6-platform-regs.h" +#include "ipu7-isys-csi2-regs.h" #define IPU6_PCI_BAR 0 #define IPU7_PCI_PBBAR 4 @@ -325,6 +326,11 @@ static void ipu6_internal_pdata_init(struct ipu6_device *isp) isys_ipdata.max_devq_size = IPU6SE_DEV_SEND_QUEUE_SIZE; psys_ipdata.hw_variant.spc_offset = IPU6SE_PSYS_SPC_OFFSET; } + + if (IS_IPU7(isp)) { + isys_ipdata.csi2.gpreg = IPU7_IS_IO_CSI2_GPREGS_BASE; + isys_ipdata.csi2.nports = 4; + } } static struct ipu6_bus_device * diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index debd571b6f6d..6bef45a02c2b 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -136,6 +136,8 @@ struct ipu6_isys_internal_csi2_pdata { u32 ctrl0_irq_lnp; u32 ctrl0_irq_status; u32 fw_access_port_ofs; + /* IPU7-specific field */ + u32 gpreg; }; struct ipu6_isys_internal_tpg_pdata { diff --git a/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.c b/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.c new file mode 100644 index 000000000000..10273c687faa --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.c @@ -0,0 +1,1072 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2026 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-isys.h" +#include "ipu6-isys-csi2.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-isys-csi-phy.h" + +#define PORT_A 0U +#define PORT_B 1U +#define PORT_C 2U +#define PORT_D 3U + +#define N_DATA_IDS 8U +static DECLARE_BITMAP(data_ids, N_DATA_IDS); + +struct ddlcal_counter_ref_s { + u16 min_mbps; + u16 max_mbps; + + u16 ddlcal_counter_ref; +}; + +struct ddlcal_params { + u16 min_mbps; + u16 max_mbps; + u16 oa_lanex_hsrx_cdphy_sel_fast; + u16 ddlcal_max_phase; + u16 phase_bound; + u16 ddlcal_dll_fbk; + u16 ddlcal_ddl_coarse_bank; + u16 fjump_deskew; + u16 min_eye_opening_deskew; +}; + +struct i_thssettle_params { + u16 min_mbps; + u16 max_mbps; + u16 i_thssettle; +}; + +/* lane2 for 4l3t, lane1 for 2l2t */ +struct oa_lane_clk_div_params { + u16 min_mbps; + u16 max_mbps; + u16 oa_lane_hsrx_hs_clk_div; +}; + +struct cdr_fbk_cap_prog_params { + u16 min_mbps; + u16 max_mbps; + u16 val; +}; + +static const struct ddlcal_counter_ref_s table0[] = { + { 1500, 1999, 118 }, + { 2000, 2499, 157 }, + { 2500, 3499, 196 }, + { 3500, 4499, 274 }, + { 4500, 4500, 352 }, + { } +}; + +static const struct ddlcal_params table1[] = { + { 1500, 1587, 0, 143, 167, 17, 3, 4, 29 }, + { 1588, 1687, 0, 135, 167, 15, 3, 4, 27 }, + { 1688, 1799, 0, 127, 135, 15, 2, 4, 26 }, + { 1800, 1928, 0, 119, 135, 13, 2, 3, 24 }, + { 1929, 2076, 0, 111, 135, 13, 2, 3, 23 }, + { 2077, 2249, 0, 103, 135, 11, 2, 3, 21 }, + { 2250, 2454, 0, 95, 103, 11, 1, 3, 19 }, + { 2455, 2699, 0, 87, 103, 9, 1, 3, 18 }, + { 2700, 2999, 0, 79, 103, 9, 1, 2, 16 }, + { 3000, 3229, 0, 71, 71, 7, 1, 2, 15 }, + { 3230, 3599, 1, 87, 103, 9, 1, 3, 18 }, + { 3600, 3999, 1, 79, 103, 9, 1, 2, 16 }, + { 4000, 4499, 1, 71, 103, 7, 1, 2, 15 }, + { 4500, 4500, 1, 63, 71, 7, 0, 2, 13 }, + { } +}; + +static const struct i_thssettle_params table2[] = { + { 80, 124, 24 }, + { 125, 249, 20 }, + { 250, 499, 16 }, + { 500, 749, 14 }, + { 750, 1499, 13 }, + { 1500, 4500, 12 }, + { } +}; + +static const struct oa_lane_clk_div_params table6[] = { + { 80, 159, 0x1 }, + { 160, 319, 0x2 }, + { 320, 639, 0x3 }, + { 640, 1279, 0x4 }, + { 1280, 2560, 0x5 }, + { 2561, 4500, 0x6 }, + { } +}; + +static const struct cdr_fbk_cap_prog_params table7[] = { + { 80, 919, 0 }, + { 920, 1029, 1 }, + { 1030, 1169, 2 }, + { 1170, 1349, 3 }, + { 1350, 1589, 4 }, + { 1590, 1949, 5 }, + { 1950, 2499, 6 }, + { 2500, 3500, 7 }, + { } +}; + +static void dwc_phy_write(struct ipu6_isys *isys, u32 id, u32 addr, u16 data) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU7_IS_IO_CDPHY_BASE(id); + + dev_dbg(&isys->adev->auxdev.dev, "phy write: reg 0x%lx = data 0x%04x", + (unsigned long)(base + addr - isys_base), data); + writew(data, base + addr); +} + +static u16 dwc_phy_read(struct ipu6_isys *isys, u32 id, u32 addr) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU7_IS_IO_CDPHY_BASE(id); + u16 data; + + data = readw(base + addr); + dev_dbg(&isys->adev->auxdev.dev, "phy read: reg 0x%lx = data 0x%04x", + (unsigned long)(base + addr - isys_base), data); + + return data; +} + +static void dwc_csi_write(struct ipu6_isys *isys, u32 id, u32 addr, u32 data) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU7_IS_IO_CSI2_HOST_BASE(id); + struct device *dev = &isys->adev->auxdev.dev; + + dev_dbg(dev, "csi write: reg 0x%lx = data 0x%08x", + (unsigned long)(base + addr - isys_base), data); + writel(data, base + addr); + dev_dbg(dev, "csi read: reg 0x%lx = data 0x%08x", + (unsigned long)(base + addr - isys_base), + readl(base + addr)); +} + +static void gpreg_write(struct ipu6_isys *isys, u32 id, u32 addr, u32 data) +{ + void __iomem *isys_base = isys->pdata->base; + u32 gpreg = isys->pdata->ipdata->csi2.gpreg; + void __iomem *base = isys_base + gpreg + 0x1000 * id; + struct device *dev = &isys->adev->auxdev.dev; + + dev_dbg(dev, "gpreg write: reg 0x%lx = data 0x%08x", + (unsigned long)(base + addr - isys_base), data); + writel(data, base + addr); + dev_dbg(dev, "gpreg read: reg 0x%lx = data 0x%08x", + (unsigned long)(base + addr - isys_base), + readl(base + addr)); +} + +static u32 dwc_csi_read(struct ipu6_isys *isys, u32 id, u32 addr) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU7_IS_IO_CSI2_HOST_BASE(id); + u32 data; + + data = readl(base + addr); + dev_dbg(&isys->adev->auxdev.dev, "csi read: reg 0x%lx = data 0x%x", + (unsigned long)(base + addr - isys_base), data); + + return data; +} + +static void dwc_phy_write_mask(struct ipu6_isys *isys, u32 id, u32 addr, + u16 val, u8 lo, u8 hi) +{ + u32 temp, mask; + + WARN_ON(lo > hi); + WARN_ON(hi > 15); + + mask = ((~0U - (1U << lo) + 1U)) & (~0U >> (31 - hi)); + temp = dwc_phy_read(isys, id, addr); + temp &= ~mask; + temp |= (val << lo) & mask; + dwc_phy_write(isys, id, addr, temp); +} + +static void dwc_csi_write_mask(struct ipu6_isys *isys, u32 id, u32 addr, + u32 val, u8 hi, u8 lo) +{ + u32 temp, mask; + + WARN_ON(lo > hi); + + mask = ((~0U - (1U << lo) + 1U)) & (~0U >> (31 - hi)); + temp = dwc_csi_read(isys, id, addr); + temp &= ~mask; + temp |= (val << lo) & mask; + dwc_csi_write(isys, id, addr, temp); +} + +static void ipu7_isys_csi_ctrl_cfg(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + struct device *dev = &isys->adev->auxdev.dev; + u32 id, lanes, phy_mode; + u32 val; + + id = csi2->port; + lanes = csi2->nlanes; + phy_mode = csi2->phy_mode; + dev_dbg(dev, "csi-%d controller init with %u lanes, phy mode %u", + id, lanes, phy_mode); + + val = dwc_csi_read(isys, id, IPU7_VERSION); + dev_dbg(dev, "csi-%d controller version = 0x%x", id, val); + + /* num of active data lanes */ + dwc_csi_write(isys, id, IPU7_N_LANES, lanes - 1); + dwc_csi_write(isys, id, IPU7_CDPHY_MODE, phy_mode); + dwc_csi_write(isys, id, IPU7_VC_EXTENSION, 0); + + /* only mask PHY_FATAL and PKT_FATAL interrupts */ + dwc_csi_write(isys, id, IPU7_INT_MSK_PHY_FATAL, 0xff); + dwc_csi_write(isys, id, IPU7_INT_MSK_PKT_FATAL, 0x3); + dwc_csi_write(isys, id, IPU7_INT_MSK_PHY, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_LINE, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_BNDRY_FRAME_FATAL, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_SEQ_FRAME_FATAL, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_CRC_FRAME_FATAL, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_PLD_CRC_FATAL, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_DATA_ID, 0x0); + dwc_csi_write(isys, id, IPU7_INT_MSK_ECC_CORRECTED, 0x0); +} + +static void ipu7_isys_csi_phy_reset(struct ipu6_isys *isys, u32 id) +{ + dwc_csi_write(isys, id, IPU7_PHY_SHUTDOWNZ, 0); + dwc_csi_write(isys, id, IPU7_DPHY_RSTZ, 0); + dwc_csi_write(isys, id, IPU7_CSI2_RESETN, 0); + gpreg_write(isys, id, IPU7_PHY_RESET, 0); + gpreg_write(isys, id, IPU7_PHY_SHUTDOWN, 0); +} + +/* 8 Data ID monitors, each Data ID is composed by pair of VC and data type */ +static int __dids_config(struct ipu6_isys_csi2 *csi2, u32 id, u8 vc, u8 dt) +{ + struct ipu6_isys *isys = csi2->isys; + u32 reg, n; + u8 lo, hi; + int ret; + + dev_dbg(&isys->adev->auxdev.dev, + "config CSI-%u with vc:%u dt:0x%02x\n", id, vc, dt); + + dwc_csi_write(isys, id, IPU7_VC_EXTENSION, 0x0); + n = find_first_zero_bit(data_ids, N_DATA_IDS); + if (n == N_DATA_IDS) + return -ENOSPC; + + ret = test_and_set_bit(n, data_ids); + if (ret) + return -EBUSY; + + reg = n < 4 ? IPU7_DATA_IDS_VC_1 : IPU7_DATA_IDS_VC_2; + lo = (n % 4) * 8; + hi = lo + 4; + dwc_csi_write_mask(isys, id, reg, vc & GENMASK(4, 0), hi, lo); + + reg = n < 4 ? IPU7_DATA_IDS_1 : IPU7_DATA_IDS_2; + lo = (n % 4) * 8; + hi = lo + 5; + dwc_csi_write_mask(isys, id, reg, dt & GENMASK(5, 0), hi, lo); + + return 0; +} + +static int ipu7_isys_csi_ctrl_dids_config(struct ipu6_isys_csi2 *csi2, u32 id) +{ + struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; + struct device *dev = &csi2->isys->adev->auxdev.dev; + struct v4l2_mbus_frame_desc desc; + struct v4l2_subdev *ext_sd; + struct media_pad *pad; + int ret; + + pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); + if (IS_ERR(pad)) { + dev_warn(dev, "can't get remote source pad of %s (%pe)\n", + csi2->asd.sd.name, pad); + return PTR_ERR(pad); + } + + ext_sd = media_entity_to_v4l2_subdev(pad->entity); + if (WARN(!ext_sd, "Failed to get subdev for entity %s\n", + pad->entity->name)) + return -ENODEV; + + ret = v4l2_subdev_call(ext_sd, pad, get_frame_desc, pad->index, &desc); + if (ret) + return ret; + + if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + dev_warn(dev, "Unsupported frame descriptor type\n"); + return -EINVAL; + } + + for (unsigned int i = 0; i < desc.num_entries; i++) { + desc_entry = &desc.entry[i]; + if (desc_entry->bus.csi2.vc < NR_OF_CSI2_VC) { + ret = __dids_config(csi2, id, desc_entry->bus.csi2.vc, + desc_entry->bus.csi2.dt); + if (ret) + return ret; + } + } + + return 0; +} + +#define CDPHY_TIMEOUT 5000000U +static int ipu7_isys_phy_ready(struct ipu6_isys *isys, u32 id) +{ + void __iomem *isys_base = isys->pdata->base; + u32 gpreg_offset = isys->pdata->ipdata->csi2.gpreg; + void __iomem *gpreg = isys_base + gpreg_offset + 0x1000 * id; + struct device *dev = &isys->adev->auxdev.dev; + u32 phy_ready; + u32 reg, rext; + int ret; + + dev_dbg(dev, "waiting phy ready...\n"); + ret = readl_poll_timeout(gpreg + IPU7_PHY_READY, phy_ready, + phy_ready & BIT(0) && phy_ready != ~0U, + 100, CDPHY_TIMEOUT); + dev_dbg(dev, "phy %u ready = 0x%08x\n", + id, readl(gpreg + IPU7_PHY_READY)); + dev_dbg(dev, "csi %u IPU7_PHY_RX = 0x%08x\n", id, + dwc_csi_read(isys, id, IPU7_PHY_RX)); + dev_dbg(dev, "csi %u IPU7_PHY_STOPSTATE = 0x%08x\n", id, + dwc_csi_read(isys, id, IPU7_PHY_STOPSTATE)); + dev_dbg(dev, "csi %u IPU7_PHY_CAL = 0x%08x\n", id, + dwc_csi_read(isys, id, IPU7_PHY_CAL)); + for (unsigned int i = 0; i < 4U; i++) { + reg = IPU7_CORE_DIG_DLANE_0_R_HS_RX_0 + (i * 0x400U); + dev_dbg(dev, "phy %u DLANE%u skewcal = 0x%04x\n", + id, i, dwc_phy_read(isys, id, reg)); + } + dev_dbg(dev, "phy %u DDLCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, + IPU7_PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_5)); + dev_dbg(dev, "phy %u TERMCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, IPU7_PPI_R_TERMCAL_DEBUG_0)); + dev_dbg(dev, "phy %u LPDCOCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, IPU7_PPI_R_LPDCOCAL_DEBUG_RB)); + dev_dbg(dev, "phy %u HSDCOCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, IPU7_PPI_R_HSDCOCAL_DEBUG_RB)); + dev_dbg(dev, "phy %u LPDCOCAL_VT = 0x%04x\n", id, + dwc_phy_read(isys, id, IPU7_PPI_R_LPDCOCAL_DEBUG_VT)); + + if (!ret) { + if (id) { + dev_dbg(dev, "ignore phy %u rext\n", id); + return 0; + } + + rext = dwc_phy_read(isys, id, + IPU7_CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_15) & + 0xfU; + dev_dbg(dev, "phy %u rext value = %u\n", id, rext); + isys->phy_rext_cal = (rext ? rext : 5); + + return 0; + } + + dev_err(dev, "wait phy ready timeout!\n"); + + return ret; +} + +static int lookup_table1(u64 mbps) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(table1); i++) { + if (mbps >= table1[i].min_mbps && mbps <= table1[i].max_mbps) + return i; + } + + return -ENXIO; +} + +static const u16 deskew_fine_mem[] = { + 0x0404, 0x040c, 0x0414, 0x041c, + 0x0423, 0x0429, 0x0430, 0x043a, + 0x0445, 0x044a, 0x0450, 0x045a, + 0x0465, 0x0469, 0x0472, 0x047a, + 0x0485, 0x0489, 0x0490, 0x049a, + 0x04a4, 0x04ac, 0x04b4, 0x04bc, + 0x04c4, 0x04cc, 0x04d4, 0x04dc, + 0x04e4, 0x04ec, 0x04f4, 0x04fc, + 0x0504, 0x050c, 0x0514, 0x051c, + 0x0523, 0x0529, 0x0530, 0x053a, + 0x0545, 0x054a, 0x0550, 0x055a, + 0x0565, 0x0569, 0x0572, 0x057a, + 0x0585, 0x0589, 0x0590, 0x059a, + 0x05a4, 0x05ac, 0x05b4, 0x05bc, + 0x05c4, 0x05cc, 0x05d4, 0x05dc, + 0x05e4, 0x05ec, 0x05f4, 0x05fc, + 0x0604, 0x060c, 0x0614, 0x061c, + 0x0623, 0x0629, 0x0632, 0x063a, + 0x0645, 0x064a, 0x0650, 0x065a, + 0x0665, 0x0669, 0x0672, 0x067a, + 0x0685, 0x0689, 0x0690, 0x069a, + 0x06a4, 0x06ac, 0x06b4, 0x06bc, + 0x06c4, 0x06cc, 0x06d4, 0x06dc, + 0x06e4, 0x06ec, 0x06f4, 0x06fc, + 0x0704, 0x070c, 0x0714, 0x071c, + 0x0723, 0x072a, 0x0730, 0x073a, + 0x0745, 0x074a, 0x0750, 0x075a, + 0x0765, 0x0769, 0x0772, 0x077a, + 0x0785, 0x0789, 0x0790, 0x079a, + 0x07a4, 0x07ac, 0x07b4, 0x07bc, + 0x07c4, 0x07cc, 0x07d4, 0x07dc, + 0x07e4, 0x07ec, 0x07f4, 0x07fc, +}; + +static void ipu7_isys_dphy_config(struct ipu6_isys *isys, u8 id, u8 lanes, + bool aggregation, u64 mbps) +{ + struct ipu6_device *isp = isys->adev->isp; + u16 hsrxval0 = 0; + u16 hsrxval1 = 0; + u16 hsrxval2 = 0; + int index; + u16 reg; + u16 val; + u32 i; + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_RW_COMMON_7, 0, 0, 9); + if (mbps > 1500) + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_7, + 40, 0, 7); + else + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_7, + 104, 0, 7); + + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_8, + 80, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_0, 191, 0, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_7, 34, 7, 12); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_1, 38, 8, 15); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_2, 4, 12, 15); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_2, 2, 10, 11); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_2, 1, 8, 8); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_2, 38, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_2, 1, 9, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_4, 10, 0, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_6, 20, 0, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_7, 19, 0, 6); + + for (i = 0; i < ARRAY_SIZE(table0); i++) { + if (mbps >= table0[i].min_mbps && mbps <= table0[i].max_mbps) { + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_3, + table0[i].ddlcal_counter_ref, + 0, 9); + break; + } + } + + index = lookup_table1(mbps); + if (index >= 0) { + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_1, + table1[index].phase_bound, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_5, + table1[index].ddlcal_dll_fbk, 4, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_DDLCAL_CFG_5, + table1[index].ddlcal_ddl_coarse_bank, 0, 3); + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_8; + val = table1[index].oa_lanex_hsrx_cdphy_sel_fast; + for (i = 0; i < lanes + 1; i++) + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, + 12, 12); + } + + reg = IPU7_CORE_DIG_DLANE_0_RW_LP_0; + for (i = 0; i < lanes; i++) + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 6, 8, 11); + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_2, + 0, 0, 0); + if (!IS_IPU7_MTL(isp) || id == PORT_B || id == PORT_C) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2, + 1, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2, + 0, 0, 0); + } else { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2, + 0, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2, + 1, 0, 0); + } + + if (lanes == 4 && IS_IPU7_MTL(isp)) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_2, + 0, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_2, + 0, 0, 0); + } + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_RW_COMMON_6, 1, 0, 2); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_RW_COMMON_6, 1, 3, 5); + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_12; + val = (mbps > 1500) ? 0 : 1; + for (i = 0; i < lanes + 1; i++) { + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 1, 1); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), !val, 3, 3); + } + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_13; + val = (mbps > 1500) ? 0 : 1; + for (i = 0; i < lanes + 1; i++) { + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 1, 1); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 3, 3); + } + + if (!IS_IPU7_MTL(isp) || id == PORT_B || id == PORT_C) + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9; + else + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9; + + for (i = 0; i < ARRAY_SIZE(table6); i++) { + if (mbps >= table6[i].min_mbps && mbps <= table6[i].max_mbps) { + dwc_phy_write_mask(isys, id, reg, + table6[i].oa_lane_hsrx_hs_clk_div, + 5, 7); + break; + } + } + + if (aggregation) { + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_RW_COMMON_0, 1, + 1, 1); + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_15; + dwc_phy_write_mask(isys, id, reg, 3, 3, 4); + + val = (id == PORT_A) ? 3 : 0; + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_15; + dwc_phy_write_mask(isys, id, reg, val, 3, 4); + + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_15; + dwc_phy_write_mask(isys, id, reg, 3, 3, 4); + } + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_0, + 28, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_DLANE_CLK_RW_HS_RX_7, + 6, 0, 7); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_0; + for (i = 0; i < ARRAY_SIZE(table2); i++) { + if (mbps >= table2[i].min_mbps && mbps <= table2[i].max_mbps) { + u8 j; + + for (j = 0; j < lanes; j++) + dwc_phy_write_mask(isys, id, reg + (j * 0x400), + table2[i].i_thssettle, + 8, 15); + break; + } + } + + /* deskew */ + for (i = 0; i < lanes; i++) { + reg = IPU7_CORE_DIG_DLANE_0_RW_CFG_1; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), + ((mbps > 1500) ? 0x1 : 0x2), 2, 3); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_2; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), + ((mbps > 2500) ? 0 : 1), 15, 15); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 13, 13); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 7, 9, 12); + + reg = IPU7_CORE_DIG_DLANE_0_RW_LP_0; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 12, 15); + + reg = IPU7_CORE_DIG_DLANE_0_RW_LP_2; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 0, 0); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_1; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 16, 0, 7); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_3; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 2, 0, 2); + index = lookup_table1(mbps); + if (index >= 0) { + val = table1[index].fjump_deskew; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, + 3, 8); + } + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_4; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 150, 0, 15); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_5; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 0, 7); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 8, 15); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_6; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 2, 0, 7); + index = lookup_table1(mbps); + if (index >= 0) { + val = table1[index].min_eye_opening_deskew; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, + 8, 15); + } + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_7; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 13, 13); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 15, 15); + + reg = IPU7_CORE_DIG_DLANE_0_RW_HS_RX_9; + index = lookup_table1(mbps); + if (index >= 0) { + val = table1[index].ddlcal_max_phase; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), + val, 0, 7); + } + } + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_DLANE_CLK_RW_LP_0, + 1, 12, 15); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_DLANE_CLK_RW_LP_2, 0, 0, 0); + + for (i = 0; i < ARRAY_SIZE(deskew_fine_mem); i++) + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_COMMON_RW_DESKEW_FINE_MEM, + deskew_fine_mem[i], 0, 15); + + if (mbps > 1500) { + hsrxval0 = 4; + hsrxval2 = 3; + } + + if (mbps > 2500) + hsrxval1 = 2; + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9, + hsrxval0, 0, 2); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9, + hsrxval0, 0, 2); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9, + hsrxval0, 0, 2); + if (lanes == 4 && IS_IPU7_MTL(isp)) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_9, + hsrxval0, 0, 2); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_9, + hsrxval0, 0, 2); + } + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9, + hsrxval1, 3, 4); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9, + hsrxval1, 3, 4); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9, + hsrxval1, 3, 4); + if (lanes == 4 && IS_IPU7_MTL(isp)) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_9, + hsrxval1, 3, 4); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_9, + hsrxval1, 3, 4); + } + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_15, + hsrxval2, 0, 2); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_15, + hsrxval2, 0, 2); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_15, + hsrxval2, 0, 2); + if (lanes == 4 && IS_IPU7_MTL(isp)) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_15, + hsrxval2, 0, 2); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_15, + hsrxval2, 0, 2); + } + + /* force and override rext */ + if (isys->phy_rext_cal && id) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_8, + isys->phy_rext_cal, 0, 3); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_7, + 1, 11, 11); + } +} + +static void ipu7_isys_cphy_config(struct ipu6_isys *isys, u8 id, u8 lanes, + bool aggregation, u64 mbps) +{ + struct ipu6_device *isp = isys->adev->isp; + u8 trios = 2; + u16 coarse_target; + u16 deass_thresh; + u16 delay_thresh; + u16 reset_thresh; + u16 cap_prog = 6U; + u16 reg; + u16 val; + u32 i; + u64 r64; + u32 r; + + if (IS_IPU7P5(isp)) + val = 0x15; + else + val = 0x155; + + if (IS_IPU7_MTL(isp)) + trios = 3; + + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_RW_COMMON_7, val, 0, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_7, + 104, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_8, + 16, 0, 7); + + reg = IPU7_CORE_DIG_CLANE_0_RW_LP_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 6, 8, 11); + + val = (mbps > 900U) ? 1U : 0U; + for (i = 0; i < trios; i++) { + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_0; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 0, 0); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 1, 1); + + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_1; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 38, 0, 15); + + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_5; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 38, 0, 15); + + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_6; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 10, 0, 15); + } + + /* + * Below 900Msps, always use the same value. + * The formula is suitable for data rate 80-3500Msps. + * Timebase (us) = 1, DIV = 32, TDDL (UI) = 0.5 + */ + if (mbps >= 80U) + coarse_target = DIV_ROUND_UP_ULL(mbps, 16) - 1; + else + coarse_target = 56; + + for (i = 0; i < trios; i++) { + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_2 + i * 0x400; + dwc_phy_write_mask(isys, id, reg, coarse_target, 0, 15); + } + + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_2, 1, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2, 0, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2, 1, 0, 0); + + if (!IS_IPU7P5(isp) && lanes == 4) { + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_2, + 1, 0, 0); + dwc_phy_write_mask(isys, id, + IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_2, + 0, 0, 0); + } + + for (i = 0; i < trios; i++) { + reg = IPU7_CORE_DIG_RW_TRIO0_0 + i * 0x400; + dwc_phy_write_mask(isys, id, reg, 1, 6, 8); + dwc_phy_write_mask(isys, id, reg, 1, 3, 5); + dwc_phy_write_mask(isys, id, reg, 2, 0, 2); + } + + deass_thresh = (u16)div64_u64_rem(7 * 1000 * 6, mbps * 5U, &r64) + 1; + if (r64 != 0) + deass_thresh++; + + reg = IPU7_CORE_DIG_RW_TRIO0_2; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, + deass_thresh, 0, 7); + + delay_thresh = div64_u64((224U - (9U * 7U)) * 1000U, 5U * mbps) - 7u; + + if (delay_thresh < 1) + delay_thresh = 1; + + reg = IPU7_CORE_DIG_RW_TRIO0_1; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, + delay_thresh, 0, 15); + + reset_thresh = (u16)div_u64_rem(2U * 5U * mbps, 7U * 1000U, &r); + if (!r) + reset_thresh--; + + if (reset_thresh < 1) + reset_thresh = 1; + + reg = IPU7_CORE_DIG_RW_TRIO0_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, + reset_thresh, 9, 11); + + /* Tuning ITMINRX to 2 for CPHY */ + reg = IPU7_CORE_DIG_CLANE_0_RW_LP_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, 2, 12, 15); + + reg = IPU7_CORE_DIG_CLANE_0_RW_LP_2; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, 0, 0, 0); + + reg = IPU7_CORE_DIG_CLANE_0_RW_HS_RX_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, 12, 2, 6); + + for (i = 0; i < ARRAY_SIZE(table7); i++) { + if (mbps >= table7[i].min_mbps && mbps <= table7[i].max_mbps) { + cap_prog = table7[i].val; + break; + } + } + + for (i = 0; i < (lanes + 1); i++) { + reg = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9 + 0x400 * i; + dwc_phy_write_mask(isys, id, reg, 4U, 0, 2); + /* 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 = IPU7_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_7 + 0x400 * i; + dwc_phy_write_mask(isys, id, reg, cap_prog, 10, 12); + } +} + +static int ipu7_isys_phy_config(struct ipu6_isys *isys, u8 id, u8 lanes, + bool aggregation) +{ + struct device *dev = &isys->adev->auxdev.dev; + u32 phy_mode; + s64 link_freq; + u64 mbps; + + if (aggregation) + link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[0]); + else + link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[id]); + + if (link_freq < 0) { + dev_err(dev, "get link freq failed (%lld)\n", link_freq); + return link_freq; + } + + mbps = div_u64(link_freq, 500000); + dev_dbg(dev, "config phy %u with lanes %u aggregation %d mbps %lld\n", + id, lanes, aggregation, mbps); + + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_10, + 48, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_2, + 1, 12, 13); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_0, + 63, 2, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_STARTUP_1_1, + 563, 0, 11); + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_2, + 5, 0, 7); + /* bypass the RCAL state (bit6) */ + if (aggregation && id != PORT_A) + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_2, + 0x45, 0, 7); + + dwc_phy_write_mask(isys, id, IPU7_PPI_STARTUP_RW_COMMON_DPHY_6, + 39, 0, 7); + dwc_phy_write_mask(isys, id, IPU7_PPI_CALIBCTRL_RW_COMMON_BG_0, + 500, 0, 8); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_TERMCAL_CFG_0, 38, 0, 6); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_OFFSETCAL_CFG_0, 7, 0, 4); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_TIMEBASE, 153, 0, 9); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_NREF, 800, 0, 10); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_NREF_RANGE, + 27, 0, 4); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_TWAIT_CONFIG, + 47, 0, 8); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_TWAIT_CONFIG, + 127, 9, 15); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_VT_CONFIG, 47, 7, 15); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_VT_CONFIG, 27, 2, 6); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_VT_CONFIG, 3, 0, 1); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_LPDCOCAL_COARSE_CFG, 1, 0, 1); + dwc_phy_write_mask(isys, id, IPU7_PPI_RW_COMMON_CFG, 3, 0, 1); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_0, + 0, 10, 10); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_1, + 1, 10, 10); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_1, + 0, 15, 15); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_3, + 3, 8, 9); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_0, + 0, 15, 15); + dwc_phy_write_mask(isys, id, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_6, + 7, 12, 14); + dwc_phy_write_mask(isys, id, IPU7_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, IPU7_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_5, + 1, 8, 8); + + if (aggregation) + phy_mode = isys->csi2[0].phy_mode; + else + phy_mode = isys->csi2[id].phy_mode; + + if (phy_mode == PHY_MODE_DPHY) { + ipu7_isys_dphy_config(isys, id, lanes, aggregation, mbps); + } else if (phy_mode == PHY_MODE_CPHY) { + ipu7_isys_cphy_config(isys, id, lanes, aggregation, mbps); + } else { + dev_err(dev, "unsupported phy mode %d!\n", + isys->csi2[id].phy_mode); + } + + return 0; +} + +static int ipu7_isys_csi_phy_powerup(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + u32 lanes = csi2->nlanes; + bool aggregation = false; + u32 id = csi2->port; + int ret; + + /* lanes remapping for aggregation (port AB) mode */ + if (!IS_IPU7_MTL(isys->adev->isp) && lanes > 2 && id == PORT_A) { + aggregation = true; + lanes = 2; + } + + ipu7_isys_csi_phy_reset(isys, id); + gpreg_write(isys, id, IPU7_PHY_CLK_LANE_CONTROL, 0x1); + gpreg_write(isys, id, IPU7_PHY_CLK_LANE_FORCE_CONTROL, 0x2); + gpreg_write(isys, id, IPU7_PHY_LANE_CONTROL_EN, (1U << lanes) - 1U); + gpreg_write(isys, id, IPU7_PHY_LANE_FORCE_CONTROL, 0xf); + gpreg_write(isys, id, IPU7_PHY_MODE, csi2->phy_mode); + + /* config PORT_B if aggregation mode */ + if (aggregation) { + ipu7_isys_csi_phy_reset(isys, PORT_B); + gpreg_write(isys, PORT_B, IPU7_PHY_CLK_LANE_CONTROL, 0x0); + gpreg_write(isys, PORT_B, IPU7_PHY_LANE_CONTROL_EN, 0x3); + gpreg_write(isys, PORT_B, IPU7_PHY_CLK_LANE_FORCE_CONTROL, 0x2); + gpreg_write(isys, PORT_B, IPU7_PHY_LANE_FORCE_CONTROL, 0xf); + gpreg_write(isys, PORT_B, IPU7_PHY_MODE, csi2->phy_mode); + } + + ipu7_isys_csi_ctrl_cfg(csi2); + ipu7_isys_csi_ctrl_dids_config(csi2, id); + + ret = ipu7_isys_phy_config(isys, id, lanes, aggregation); + if (ret < 0) + return ret; + + gpreg_write(isys, id, IPU7_PHY_RESET, 1); + gpreg_write(isys, id, IPU7_PHY_SHUTDOWN, 1); + dwc_csi_write(isys, id, IPU7_DPHY_RSTZ, 1); + dwc_csi_write(isys, id, IPU7_PHY_SHUTDOWNZ, 1); + dwc_csi_write(isys, id, IPU7_CSI2_RESETN, 1); + + ret = ipu7_isys_phy_ready(isys, id); + if (ret < 0) + return ret; + + gpreg_write(isys, id, IPU7_PHY_LANE_FORCE_CONTROL, 0); + gpreg_write(isys, id, IPU7_PHY_CLK_LANE_FORCE_CONTROL, 0); + + /* config PORT_B if aggregation mode */ + if (aggregation) { + ret = ipu7_isys_phy_config(isys, PORT_B, 2, aggregation); + if (ret < 0) + return ret; + + gpreg_write(isys, PORT_B, IPU7_PHY_RESET, 1); + gpreg_write(isys, PORT_B, IPU7_PHY_SHUTDOWN, 1); + dwc_csi_write(isys, PORT_B, IPU7_DPHY_RSTZ, 1); + dwc_csi_write(isys, PORT_B, IPU7_PHY_SHUTDOWNZ, 1); + dwc_csi_write(isys, PORT_B, IPU7_CSI2_RESETN, 1); + ret = ipu7_isys_phy_ready(isys, PORT_B); + if (ret < 0) + return ret; + + gpreg_write(isys, PORT_B, IPU7_PHY_LANE_FORCE_CONTROL, 0); + gpreg_write(isys, PORT_B, IPU7_PHY_CLK_LANE_FORCE_CONTROL, 0); + } + + return 0; +} + +static void ipu7_isys_csi_phy_powerdown(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + + ipu7_isys_csi_phy_reset(isys, csi2->port); + if (!IS_IPU7_MTL(isys->adev->isp) && + csi2->nlanes > 2U && csi2->port == PORT_A) + ipu7_isys_csi_phy_reset(isys, PORT_B); +} + +int ipu7_isys_csi_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on) +{ + struct ipu6_isys_csi2 *csi2 = &isys->csi2[cfg->port]; + + if (on) + return ipu7_isys_csi_phy_powerup(csi2); + + ipu7_isys_csi_phy_powerdown(csi2); + + return 0; +} diff --git a/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.h b/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.h new file mode 100644 index 000000000000..849fe888db07 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu7-isys-csi-phy.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2026 Intel Corporation */ + +#ifndef IPU7_ISYS_CSI_PHY_H +#define IPU7_ISYS_CSI_PHY_H + +struct ipu6_isys; +struct ipu6_isys_csi2_config; +struct ipu6_isys_csi2_timing; + +int ipu7_isys_csi_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on); + +#endif /* IPU7_ISYS_CSI_PHY_H */ From 0fc93da7f11f5bf9cc25a14f032992087ad9a66e Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:20 +0300 Subject: [PATCH 075/596] media: ipu6: Split ipu6 csi2 stream enable/disable Instead of single function, split csi2 receiver stream enable and disable into own functions. This make it easier to add ipu7 support. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 100 ++++++++---------- 1 file changed, 44 insertions(+), 56 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c index 7e539a0c6c92..7b63b08f5c54 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c @@ -228,64 +228,55 @@ void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2) } } -static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd, - const struct ipu6_isys_csi2_timing *timing, - unsigned int nlanes, int enable) +static void ipu6_isys_csi2_stream_disable(struct ipu6_isys_csi2 *csi2) { - struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); - struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); struct ipu6_isys *isys = csi2->isys; - struct device *dev = &isys->adev->auxdev.dev; struct ipu6_isys_csi2_config cfg; - unsigned int nports; - int ret = 0; - u32 mask = 0; - u32 i; - - dev_dbg(dev, "stream %s CSI2-%u with %u lanes\n", enable ? "on" : "off", - csi2->port, nlanes); + u32 mask = isys->pdata->ipdata->csi2.irq_mask; cfg.port = csi2->port; - cfg.nlanes = nlanes; + cfg.nlanes = csi2->nlanes; + + writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE); + writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE); + writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); + writel(mask, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); + writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); + + isys->phy_set_power(isys, &cfg, NULL, false); + + writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT + (isys->pdata->ipdata->csi2.fw_access_port_ofs, csi2->port)); + writel(0, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(csi2->port)); +} + +static int ipu6_isys_csi2_stream_enable(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + struct ipu6_isys_csi2_timing timing = { }; + struct ipu6_isys_csi2_config cfg; + unsigned int nports; + u32 mask; + int ret; + + cfg.port = csi2->port; + cfg.nlanes = csi2->nlanes; mask = isys->pdata->ipdata->csi2.irq_mask; nports = isys->pdata->ipdata->csi2.nports; - if (!enable) { - writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE); - writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE); - - writel(0, - csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + - CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); - writel(mask, - csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + - CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); - writel(0, - csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + - CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); - writel(0xffffffff, - csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + - CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); - - isys->phy_set_power(isys, &cfg, timing, false); - - writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT - (isys->pdata->ipdata->csi2.fw_access_port_ofs, - csi2->port)); - writel(0, isys->pdata->base + - CSI_REG_HUB_DRV_ACCESS_PORT(csi2->port)); - - return ret; - } - /* reset port reset */ writel(0x1, csi2->base + CSI_REG_PORT_GPREG_SRST); usleep_range(100, 200); writel(0x0, csi2->base + CSI_REG_PORT_GPREG_SRST); /* enable port clock */ - for (i = 0; i < nports; i++) { + for (unsigned int i = 0; i < nports; i++) { writel(1, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(i)); writel(1, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT (isys->pdata->ipdata->csi2.fw_access_port_ofs, i)); @@ -329,18 +320,18 @@ static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd, writel(CSI_SENSOR_INPUT, csi2->base + CSI_REG_CSI_FE_MUX_CTRL); writel(CSI_CNTR_SENSOR_LINE_ID | CSI_CNTR_SENSOR_FRAME_ID, csi2->base + CSI_REG_CSI_FE_SYNC_CNTR_SEL); - writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, nlanes - 1), + writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, + csi2->nlanes - 1), csi2->base + CSI_REG_PPI2CSI_CONFIG_PPI_INTF); writel(1, csi2->base + CSI_REG_PPI2CSI_ENABLE); writel(1, csi2->base + CSI_REG_CSI_FE_ENABLE); - ret = isys->phy_set_power(isys, &cfg, timing, true); + ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV); if (ret) - dev_err(dev, "csi-%d phy power up failed %d\n", csi2->port, - ret); + return ret; - return ret; + return isys->phy_set_power(isys, &cfg, &timing, true); } static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, @@ -349,7 +340,6 @@ static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, { struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); - struct ipu6_isys_csi2_timing timing = { }; struct v4l2_subdev *remote_sd; struct media_pad *remote_pad; u64 sink_streams; @@ -362,18 +352,14 @@ static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK, &streams_mask); - ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV); - if (ret) - return ret; - - ret = ipu6_isys_csi2_set_stream(sd, &timing, csi2->nlanes, true); + ret = ipu6_isys_csi2_stream_enable(csi2); if (ret) return ret; ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, sink_streams); if (ret) { - ipu6_isys_csi2_set_stream(sd, NULL, 0, false); + ipu6_isys_csi2_stream_disable(csi2); return ret; } @@ -384,6 +370,8 @@ static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, u32 pad, u64 streams_mask) { + struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); + struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); struct v4l2_subdev *remote_sd; struct media_pad *remote_pad; u64 sink_streams; @@ -395,7 +383,7 @@ static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd, remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]); remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); - ipu6_isys_csi2_set_stream(sd, NULL, 0, false); + ipu6_isys_csi2_stream_disable(csi2); v4l2_subdev_disable_streams(remote_sd, remote_pad->index, sink_streams); From e748401ca7dab74ff0fc17b842cd6756dfc3c2b9 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:21 +0300 Subject: [PATCH 076/596] media: ipu6: Add support for ipu7 csi2 receiver The csi2 receiver is different in ipu7, add support for it. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 119 +++++++++++++++++- 1 file changed, 114 insertions(+), 5 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c index 7b63b08f5c54..160eace947f4 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c @@ -24,6 +24,7 @@ #include "ipu6-isys-csi2.h" #include "ipu6-isys-subdev.h" #include "ipu6-platform-isys-csi2-reg.h" +#include "ipu7-isys-csi2-regs.h" static const u32 csi2_supported_codes[] = { MEDIA_BUS_FMT_RGB565_1X16, @@ -267,6 +268,10 @@ static int ipu6_isys_csi2_stream_enable(struct ipu6_isys_csi2 *csi2) cfg.port = csi2->port; cfg.nlanes = csi2->nlanes; + ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV); + if (ret) + return ret; + mask = isys->pdata->ipdata->csi2.irq_mask; nports = isys->pdata->ipdata->csi2.nports; @@ -327,11 +332,106 @@ static int ipu6_isys_csi2_stream_enable(struct ipu6_isys_csi2 *csi2) writel(1, csi2->base + CSI_REG_PPI2CSI_ENABLE); writel(1, csi2->base + CSI_REG_CSI_FE_ENABLE); - ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV); + return isys->phy_set_power(isys, &cfg, &timing, true); +} + +static void ipu7_csi2_irq_enable(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + unsigned int offset, mask; + + /* enable CSI2 legacy error irq */ + offset = IPU7_IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_ERROR_IRQ_MASK; + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_MASK); + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_ENABLE); + + /* enable CSI2 legacy sync irq */ + offset = IPU7_IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_SYNC_IRQ_MASK; + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_MASK); + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_ENABLE); + + if (!IS_IPU7_MTL(isys->adev->isp)) { + mask = IPU7P5_CSI_RX_SYNC_FE_IRQ_MASK; + writel(mask, csi2->base + offset + IPU7_IRQ1_CTL_CLEAR); + writel(mask, csi2->base + offset + IPU7_IRQ1_CTL_MASK); + writel(mask, csi2->base + offset + IPU7_IRQ1_CTL_ENABLE); + } +} + +static void ipu7_csi2_irq_disable(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + unsigned int offset, mask; + + /* disable CSI2 legacy error irq */ + offset = IPU7_IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_ERROR_IRQ_MASK; + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + writel(0, csi2->base + offset + IPU7_IRQ_CTL_MASK); + writel(0, csi2->base + offset + IPU7_IRQ_CTL_ENABLE); + + /* disable CSI2 legacy sync irq */ + offset = IPU7_IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_SYNC_IRQ_MASK; + writel(mask, csi2->base + offset + IPU7_IRQ_CTL_CLEAR); + writel(0, csi2->base + offset + IPU7_IRQ_CTL_MASK); + writel(0, csi2->base + offset + IPU7_IRQ_CTL_ENABLE); + + if (!IS_IPU7_MTL(isys->adev->isp)) { + writel(mask, csi2->base + offset + IPU7_IRQ1_CTL_CLEAR); + writel(0, csi2->base + offset + IPU7_IRQ1_CTL_MASK); + writel(0, csi2->base + offset + IPU7_IRQ1_CTL_ENABLE); + } +} + +static void ipu7_isys_csi2_stream_disable(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + void __iomem *base = isys->pdata->base; + struct ipu6_isys_csi2_config cfg; + + cfg.port = csi2->port; + cfg.nlanes = csi2->nlanes; + + isys->phy_set_power(isys, &cfg, NULL, false); + + writel(0x4, + base + IPU7_IS_IO_GPREGS_BASE + IPU7_CLK_DIV_FACTOR_APB_CLK); + ipu7_csi2_irq_disable(csi2); +} + +static int ipu7_isys_csi2_stream_enable(struct ipu6_isys_csi2 *csi2) +{ + struct ipu6_isys *isys = csi2->isys; + void __iomem *base = isys->pdata->base; + struct ipu6_isys_csi2_config cfg; + unsigned int offset = IPU7_IS_IO_GPREGS_BASE; + int ret; + + writel(0x2, base + offset + IPU7_CLK_DIV_FACTOR_APB_CLK); + if (csi2->port == 0U && csi2->nlanes == 4U && + !IS_IPU7_MTL(isys->adev->isp)) + writel(0x1, base + offset + IPU7_CSI_PORTAB_AGGREGATION); + + /* input is coming from CSI receiver (sensor) */ + offset = IPU7_IS_IO_CSI2_ADPL_PORT_BASE(csi2->port); + writel(CSI_SENSOR_INPUT, base + offset + IPU7_CSI2_ADPL_INPUT_MODE); + writel(1, base + offset + IPU7_CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_EN); + + cfg.port = csi2->port; + cfg.nlanes = csi2->nlanes; + + ret = isys->phy_set_power(isys, &cfg, NULL, true); if (ret) return ret; - return isys->phy_set_power(isys, &cfg, &timing, true); + ipu7_csi2_irq_enable(csi2); + + return 0; } static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, @@ -340,6 +440,7 @@ static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, { struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); + struct ipu6_device *isp = asd->isys->adev->isp; struct v4l2_subdev *remote_sd; struct media_pad *remote_pad; u64 sink_streams; @@ -352,14 +453,18 @@ static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK, &streams_mask); - ret = ipu6_isys_csi2_stream_enable(csi2); + ret = IS_IPU7(isp) ? ipu7_isys_csi2_stream_enable(csi2) : + ipu6_isys_csi2_stream_enable(csi2); if (ret) return ret; ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, sink_streams); if (ret) { - ipu6_isys_csi2_stream_disable(csi2); + if IS_IPU7(isp) + ipu7_isys_csi2_stream_disable(csi2); + else + ipu6_isys_csi2_stream_disable(csi2); return ret; } @@ -372,6 +477,7 @@ static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd, { struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); + struct ipu6_device *isp = asd->isys->adev->isp; struct v4l2_subdev *remote_sd; struct media_pad *remote_pad; u64 sink_streams; @@ -383,7 +489,10 @@ static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd, remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]); remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); - ipu6_isys_csi2_stream_disable(csi2); + if IS_IPU7(isp) + ipu7_isys_csi2_stream_disable(csi2); + else + ipu6_isys_csi2_stream_disable(csi2); v4l2_subdev_disable_streams(remote_sd, remote_pad->index, sink_streams); From 414194b1f03c873beda1eb37028cb808eaceb430 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:22 +0300 Subject: [PATCH 077/596] media: ipu6: Parse bus type for ipu7 For ipu7 bus type can be CPHY or DPHY. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-isys.c | 8 ++++++++ drivers/media/pci/intel/ipu6/ipu6-isys.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 5de5fd16ca30..976ed064f262 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -133,6 +133,9 @@ isys_complete_ext_device_registration(struct ipu6_isys *isys, } isys->csi2[csi2->port].nlanes = csi2->nlanes; + isys->csi2[csi2->port].phy_mode = + csi2->bus_type == V4L2_MBUS_CSI2_DPHY ? + PHY_MODE_DPHY : PHY_MODE_CPHY; return 0; @@ -639,6 +642,10 @@ static int isys_notifier_init(struct ipu6_isys *isys) continue; ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret && IS_IPU7(isp)) { + vep.bus_type = V4L2_MBUS_CSI2_CPHY; + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + } if (ret) { dev_err(dev, "fwnode endpoint parse failed: %d\n", ret); goto err_parse; @@ -654,6 +661,7 @@ static int isys_notifier_init(struct ipu6_isys *isys) s_asd->csi2.port = vep.base.port; s_asd->csi2.nlanes = vep.bus.mipi_csi2.num_data_lanes; + s_asd->csi2.bus_type = vep.bus_type; dev_dbg(dev, "remote endpoint port %d with %d lanes added\n", s_asd->csi2.port, s_asd->csi2.nlanes); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h index 30539ea5f826..8674c041b994 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -14,6 +14,7 @@ #include #include #include +#include #include "ipu6.h" #include "ipu6-fw-isys.h" @@ -96,6 +97,7 @@ struct isys_iwake_watermark { struct ipu6_isys_csi2_config { u32 nlanes; u32 port; + enum v4l2_mbus_type bus_type; }; struct sensor_async_sd { From 3f106123a649e1644a91292addc5449089f0e0fc Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:23 +0300 Subject: [PATCH 078/596] media: ipu6: Enable ipu7 isys interrupts Add ipu7 specific isys initialization of clocks and enabling interrupts. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-isys.c | 56 +++++++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 976ed064f262..622f74021f6d 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -180,13 +180,23 @@ static int isys_csi2_register_subdevices(struct ipu6_isys *isys) int ret; for (i = 0; i < csi2_pdata->nports; i++) { - ret = ipu6_isys_csi2_init(&isys->csi2[i], isys, - isys->pdata->base + - CSI_REG_PORT_BASE(i), i); + void __iomem *base = isys->pdata->base; + + if (IS_IPU7(isys->adev->isp)) { + u32 mask = IS_IPU7_MTL(isys->adev->isp) ? + IPU7_CSI_LEGACY_IRQ_MASK(i) : + IPU7P5_CSI_LEGACY_IRQ_MASK(i); + + isys->isr_csi2_bits |= mask; + isys->csi2[i].legacy_irq_mask = mask; + } else { + base += CSI_REG_PORT_BASE(i); + isys->isr_csi2_bits |= IPU6_ISYS_UNISPART_IRQ_CSI2(i); + } + + ret = ipu6_isys_csi2_init(&isys->csi2[i], isys, base, i); if (ret) goto fail; - - isys->isr_csi2_bits |= IPU6_ISYS_UNISPART_IRQ_CSI2(i); } return 0; @@ -273,7 +283,36 @@ static int isys_register_video_devices(struct ipu6_isys *isys) return ret; } -static void isys_setup_hw(struct ipu6_isys *isys) +static void ipu7_isys_setup_hw(struct ipu6_isys *isys) +{ + u32 offset, mask; + void __iomem *base = isys->pdata->base; + + offset = IPU7_IS_IO_GPREGS_BASE; + + writel(0x0, base + offset + IPU7_CLK_EN_TXCLKESC); + /* Update if ISYS freq updated (0: 400/1, 1:400/2, 63:400/64) */ + writel(0x0, base + offset + IPU7_CLK_DIV_FACTOR_IS_CLK); + /* correct the initial printf configuration */ + writel(0x200, base + IPU7_IS_UC_CTRL_BASE + IPU7_REG_PRINTF_AXI_CNTL); + + offset = IPU7_IS_UC_CTRL_BASE; + mask = IPU7_IS_UC_TO_SW_IRQ_MASK; + + writel(mask, base + offset + IPU7_TO_SW_IRQ_CNTL_CLEAR); + writel(mask, base + offset + IPU7_TO_SW_IRQ_CNTL_MASK_N); + writel(mask, base + offset + IPU7_TO_SW_IRQ_CNTL_ENABLE); + + offset = IPU7_IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE; + mask = IPU7_CSI_RX_LEGACY_IRQ_MASK; + + writel(mask, base + offset + IPU7_IRQ_CTL_EDGE); + writel(mask, base + offset + IPU7_IRQ_CTL_CLEAR); + writel(mask, base + offset + IPU7_IRQ_CTL_MASK); + writel(mask, base + offset + IPU7_IRQ_CTL_ENABLE); +} + +static void ipu6_isys_setup_hw(struct ipu6_isys *isys) { void __iomem *base = isys->pdata->base; const u8 *thd = isys->pdata->ipdata->hw_variant.cdc_fifo_threshold; @@ -783,7 +822,10 @@ static int isys_runtime_pm_resume(struct device *dev) isys->power = 1; spin_unlock_irqrestore(&isys->power_lock, flags); - isys_setup_hw(isys); + if (IS_IPU7(isp)) + ipu7_isys_setup_hw(isys); + else + ipu6_isys_setup_hw(isys); set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON); From 39828f3e84ae48875df6805ae3c662e8a60d9126 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:24 +0300 Subject: [PATCH 079/596] media: ipu6: Skip watermark configuration for ipu7 The ipu7 don't have watermark nor LTR support, skip it. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-isys-video.c | 4 +++- drivers/media/pci/intel/ipu6/ipu6-isys.c | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c index 560524e6e624..9634b4f232bd 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -727,8 +727,10 @@ void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state) { struct isys_iwake_watermark *iwake_watermark = &av->isys->iwake_watermark; + struct ipu6_device *isp = av->isys->adev->isp; - if (!av->watermark.pixel_rate) + if (IS_IPU7(isp) || + !av->watermark.pixel_rate) return; if (state) { diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 622f74021f6d..5a3ba5007249 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -822,12 +822,12 @@ static int isys_runtime_pm_resume(struct device *dev) isys->power = 1; spin_unlock_irqrestore(&isys->power_lock, flags); - if (IS_IPU7(isp)) + if (IS_IPU7(isp)) { ipu7_isys_setup_hw(isys); - else + } else { ipu6_isys_setup_hw(isys); - - set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON); + set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON); + } return 0; } @@ -836,6 +836,7 @@ static int isys_runtime_pm_suspend(struct device *dev) { struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); struct ipu6_isys *isys = dev_get_drvdata(dev); + struct ipu6_device *isp = adev->isp; unsigned long flags; spin_lock_irqsave(&isys->power_lock, flags); @@ -849,7 +850,8 @@ static int isys_runtime_pm_suspend(struct device *dev) isys->phy_termcal_val = 0; cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); - set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF); + if (!IS_IPU7(isp)) + set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF); ipu6_mmu_hw_cleanup(adev->mmu); From 2aa00051884b44e2ad6eeb07cd6eab5ee4c532dd Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:25 +0300 Subject: [PATCH 080/596] media: ipu6: The SPC init is valid only for ipu6 The ipu7 don't have SPC. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index fd7888a011a5..cb1145080460 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -232,10 +232,16 @@ void ipu6_configure_spc(struct ipu6_device *isp, int pkg_dir_idx, void __iomem *base, u64 *pkg_dir, dma_addr_t pkg_dir_dma_addr) { - void __iomem *dmem_base = base + hw_variant->dmem_offset; - void __iomem *spc_regs_base = base + hw_variant->spc_offset; + void __iomem *dmem_base; + void __iomem *spc_regs_base; u32 val; + if (IS_IPU7(isp)) + return; + + dmem_base = base + hw_variant->dmem_offset; + spc_regs_base = base + hw_variant->spc_offset; + val = readl(spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); val |= IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; writel(val, spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); From b3c78e2d3e79ae1f6d5ae641b05aedba27904f00 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:26 +0300 Subject: [PATCH 081/596] media: ipu6: The VC arbitration mechanism is ipu6 only The ipu7 doesn't support the VC arbitration mechanism that exist in ipu6. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index cb1145080460..7d1c3713a69b 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -447,7 +447,12 @@ static int ipu6_pci_config_setup(struct pci_dev *dev) static void ipu6_configure_vc_mechanism(struct ipu6_device *isp) { - u32 val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); + u32 val; + + if (IS_IPU7(isp)) + return; + + val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); if (IPU6_BTRS_ARB_STALL_MODE_VC0 == IPU6_BTRS_ARB_MODE_TYPE_STALL) val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0; From 8705c3020519aedb44c63cbf6b90613c85e30a0a Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:27 +0300 Subject: [PATCH 082/596] media: ipu6: Move buttress mem alloc out from probe Buttress registers are stored in separate buffer, which is passed down to bus driver. Move allocation of this buffer to subsystem init to cleanup pci probe function a bit. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6.c | 43 +++++++++++++++-------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 7d1c3713a69b..4e4f32723904 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -90,7 +90,7 @@ static struct ipu6_psys_internal_pdata psys_ipdata = { }, }; -static const struct ipu6_buttress_ctrl isys_buttress_ctrl = { +static const struct ipu6_buttress_ctrl ipu6_isys_buttress_ctrl = { .subsys_id = IPU_ISYS, .ratio = IPU6_IS_FREQ_CTL_DEFAULT_RATIO, .qos_floor = IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, @@ -101,7 +101,7 @@ static const struct ipu6_buttress_ctrl isys_buttress_ctrl = { .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, }; -static const struct ipu6_buttress_ctrl psys_buttress_ctrl = { +static const struct ipu6_buttress_ctrl ipu6_psys_buttress_ctrl = { .subsys_id = IPU_PSYS, .ratio = IPU6_PS_FREQ_CTL_DEFAULT_RATIO, .qos_floor = IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, @@ -341,11 +341,12 @@ static void ipu6_internal_pdata_init(struct ipu6_device *isp) static struct ipu6_bus_device * ipu6_isys_init(struct pci_dev *pdev, struct device *parent, - struct ipu6_buttress_ctrl *ctrl, void __iomem *base, + const struct ipu6_buttress_ctrl *ctrl, void __iomem *base, const struct ipu6_isys_internal_pdata *ipdata) { struct device *dev = &pdev->dev; struct ipu6_bus_device *isys_adev; + struct ipu6_buttress_ctrl *devm_ctrl; struct ipu6_isys_pdata *pdata; int ret; @@ -355,6 +356,10 @@ ipu6_isys_init(struct pci_dev *pdev, struct device *parent, return ERR_PTR(ret); } + devm_ctrl = devm_kmemdup(dev, ctrl, sizeof(*ctrl), GFP_KERNEL); + if (!devm_ctrl) + return ERR_PTR(-ENOMEM); + pdata = kzalloc_obj(*pdata); if (!pdata) return ERR_PTR(-ENOMEM); @@ -362,7 +367,7 @@ ipu6_isys_init(struct pci_dev *pdev, struct device *parent, pdata->base = base; pdata->ipdata = ipdata; - isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, + isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, devm_ctrl, IPU6_ISYS_NAME); if (IS_ERR(isys_adev)) { kfree(pdata); @@ -388,13 +393,19 @@ ipu6_isys_init(struct pci_dev *pdev, struct device *parent, static struct ipu6_bus_device * ipu6_psys_init(struct pci_dev *pdev, struct device *parent, - struct ipu6_buttress_ctrl *ctrl, void __iomem *base, + const struct ipu6_buttress_ctrl *ctrl, void __iomem *base, const struct ipu6_psys_internal_pdata *ipdata) { + struct device *dev = &pdev->dev; struct ipu6_bus_device *psys_adev; + struct ipu6_buttress_ctrl *devm_ctrl; struct ipu6_psys_pdata *pdata; int ret; + devm_ctrl = devm_kmemdup(dev, ctrl, sizeof(*ctrl), GFP_KERNEL); + if (!devm_ctrl) + return ERR_PTR(-ENOMEM); + pdata = kzalloc_obj(*pdata); if (!pdata) return ERR_PTR(-ENOMEM); @@ -402,7 +413,7 @@ ipu6_psys_init(struct pci_dev *pdev, struct device *parent, pdata->base = base; pdata->ipdata = ipdata; - psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, + psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, devm_ctrl, IPU6_PSYS_NAME); if (IS_ERR(psys_adev)) { kfree(pdata); @@ -469,7 +480,7 @@ static void ipu6_configure_vc_mechanism(struct ipu6_device *isp) static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - struct ipu6_buttress_ctrl *isys_ctrl = NULL, *psys_ctrl = NULL; + const struct ipu6_buttress_ctrl *isys_ctrl, *psys_ctrl; struct device *dev = &pdev->dev; void __iomem *isys_base = NULL; void __iomem *psys_base = NULL; @@ -484,6 +495,8 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt); isp->buttress.regs = &ipu6_buttress_regs; + isys_ctrl = &ipu6_isys_buttress_ctrl; + psys_ctrl = &ipu6_psys_buttress_ctrl; switch (id->device) { case PCI_DEVICE_ID_INTEL_IPU6: @@ -513,6 +526,8 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) isp->hw_ver = IPU_VERSION_7; isp->cpd_fw_name = IPU7_FIRMWARE_NAME; isp->buttress.regs = &ipu7_buttress_regs; + isys_ctrl = &ipu7_isys_buttress_ctrl; + psys_ctrl = &ipu7_psys_buttress_ctrl; break; default: return dev_err_probe(dev, -ENODEV, @@ -582,13 +597,6 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_ipu6_bus_del_devices; } - isys_ctrl = devm_kmemdup(dev, &isys_buttress_ctrl, - sizeof(isys_buttress_ctrl), GFP_KERNEL); - if (!isys_ctrl) { - ret = -ENOMEM; - goto out_ipu6_bus_del_devices; - } - isp->isys = ipu6_isys_init(pdev, dev, isys_ctrl, isys_base, &isys_ipdata); if (IS_ERR(isp->isys)) { @@ -596,13 +604,6 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_ipu6_bus_del_devices; } - psys_ctrl = devm_kmemdup(dev, &psys_buttress_ctrl, - sizeof(psys_buttress_ctrl), GFP_KERNEL); - if (!psys_ctrl) { - ret = -ENOMEM; - goto out_ipu6_bus_del_devices; - } - isp->psys = ipu6_psys_init(pdev, &isp->isys->auxdev.dev, psys_ctrl, psys_base, &psys_ipdata); if (IS_ERR(isp->psys)) { From bf34ce5c43513c866664d9901df868072380affa Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:28 +0300 Subject: [PATCH 083/596] media: ipu6: Read correct SKU ID for ipu7 Add SKU ID to buttress register map to get correct ID for both IPUs. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-buttress.h | 1 + drivers/media/pci/intel/ipu6/ipu6.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h index af72de2b8729..47a49e961945 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.h +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h @@ -54,6 +54,7 @@ struct ipu6_buttress_registers { u32 db0_out; u32 data0_in; u32 data0_out; + u32 sku_id; /* Bitmasks */ u32 irq_is; diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 4e4f32723904..428ad046e239 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -153,6 +153,7 @@ static const struct ipu6_buttress_registers ipu6_buttress_regs = { .db0_out = BUTTRESS_REG_IU2CSEDB0, .data0_in = BUTTRESS_REG_CSE2IUDATA0, .data0_out = BUTTRESS_REG_IU2CSEDATA0, + .sku_id = BUTTRESS_REG_SKU, /* Bitmasks */ .irq_is = BUTTRESS_ISR_IS_IRQ, @@ -182,6 +183,7 @@ static const struct ipu6_buttress_registers ipu7_buttress_regs = { .db0_out = IPU7_BUTTRESS_REG_IU2CSEDB0, .data0_in = IPU7_BUTTRESS_REG_CSE2IUDATA0, .data0_out = IPU7_BUTTRESS_REG_IU2CSEDATA0, + .sku_id = IPU7_BUTTRESS_REG_SKU, /* Bitmasks */ .irq_is = IPU7_BUTTRESS_IRQ_IS_IRQ, @@ -657,7 +659,7 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) /* Configure the arbitration mechanisms for VC requests */ ipu6_configure_vc_mechanism(isp); - val = readl(isp->base + BUTTRESS_REG_SKU); + val = readl(isp->base + isp->buttress.regs->sku_id); sku_id = FIELD_GET(GENMASK(6, 4), val); version = FIELD_GET(GENMASK(3, 0), val); dev_info(dev, "IPU%u-v%u[%x] hardware version %d\n", version, sku_id, From 0f728bbf36687a19b5cf8ad48ebb1d6f40dded0b Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:29 +0300 Subject: [PATCH 084/596] media: ipu6: Add support for fixed iova region The ipu7 need specific region for firmware in non-secure mode, add support for it. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-dma.c | 20 +++++++++++++++++--- drivers/media/pci/intel/ipu6/ipu6-dma.h | 2 ++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.c b/drivers/media/pci/intel/ipu6/ipu6-dma.c index 6e8295d9c3a3..90f530a93779 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-dma.c +++ b/drivers/media/pci/intel/ipu6/ipu6-dma.c @@ -371,6 +371,17 @@ void ipu6_dma_unmap_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, } EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sg, "INTEL_IPU6"); +static struct iova *ipu7_get_fw_code_region(struct ipu6_bus_device *sys) +{ + struct ipu6_mmu *mmu = sys->mmu; + unsigned long lo, hi; + + lo = iova_pfn(&mmu->dmap->iovad, IPU7_FW_CODE_REGION_START); + hi = iova_pfn(&mmu->dmap->iovad, IPU7_FW_CODE_REGION_END) - 1U; + + return reserve_iova(&mmu->dmap->iovad, lo, hi); +} + int ipu6_dma_map_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, int nents, enum dma_data_direction dir, unsigned long attrs) @@ -397,10 +408,13 @@ int ipu6_dma_map_sg(struct ipu6_bus_device *sys, struct scatterlist *sglist, dev_dbg(dev, "dmamap trying to map %d ents %zu pages\n", nents, npages); - iova = alloc_iova(&mmu->dmap->iovad, npages, - PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); + if (attrs & DMA_ATTR_RESERVE_REGION) + iova = ipu7_get_fw_code_region(sys); + else + iova = alloc_iova(&mmu->dmap->iovad, npages, + PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); if (!iova) - return 0; + return -ENOMEM; dev_dbg(dev, "dmamap: iova low pfn %lu, high pfn %lu\n", iova->pfn_lo, iova->pfn_hi); diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.h b/drivers/media/pci/intel/ipu6/ipu6-dma.h index ae9b9a5df57f..e5e0e7860423 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-dma.h +++ b/drivers/media/pci/intel/ipu6/ipu6-dma.h @@ -10,6 +10,8 @@ #include "ipu6-bus.h" +#define DMA_ATTR_RESERVE_REGION BIT(31) + struct ipu6_mmu_info; struct ipu6_dma_mapping { From d155161199a6bc4bc394fb8e0fd2ec608700bbb5 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:30 +0300 Subject: [PATCH 085/596] media: ipu6: Make fw mapping function reusable Make ipu6_map_fw_region reusable as for ipu7 we want to map other buffers too, not only firmware. Also add DMA data direction and mapping attributes as arguments as these are different for ipu7. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-buttress.c | 40 ++++++++++---------- drivers/media/pci/intel/ipu6/ipu6-buttress.h | 10 ++--- drivers/media/pci/intel/ipu6/ipu6-isys.c | 7 ++-- drivers/media/pci/intel/ipu6/ipu6.c | 8 ++-- 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c index 13fd7a083b25..74db0a37561e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.c +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -676,55 +676,55 @@ int ipu6_buttress_reset_authentication(struct ipu6_device *isp) return 0; } -int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, - const struct firmware *fw, struct sg_table *sgt) +int ipu6_map_fw_region(struct ipu6_bus_device *sys, const void *data, + size_t size, enum dma_data_direction dir, + unsigned long attrs) { - bool is_vmalloc = is_vmalloc_addr(fw->data); + bool is_vmalloc = is_vmalloc_addr(data); struct pci_dev *pdev = sys->isp->pdev; + struct sg_table *sgt = &sys->fw_sgt; struct page **pages; - const void *addr; unsigned long n_pages; unsigned int i; int ret; - if (!is_vmalloc && !virt_addr_valid(fw->data)) + if (!is_vmalloc && !virt_addr_valid(data)) return -EDOM; - n_pages = PFN_UP(fw->size); + n_pages = PFN_UP(size); pages = kmalloc_objs(*pages, n_pages); if (!pages) return -ENOMEM; - addr = fw->data; for (i = 0; i < n_pages; i++) { struct page *p = is_vmalloc ? - vmalloc_to_page(addr) : virt_to_page(addr); + vmalloc_to_page(data) : virt_to_page(data); if (!p) { ret = -ENOMEM; goto out; } pages[i] = p; - addr += PAGE_SIZE; + data += PAGE_SIZE; } - ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, fw->size, + ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, size, GFP_KERNEL); if (ret) { ret = -ENOMEM; goto out; } - ret = dma_map_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0); + ret = dma_map_sgtable(&pdev->dev, sgt, dir, 0); if (ret) { sg_free_table(sgt); goto out; } - ret = ipu6_dma_map_sgtable(sys, sgt, DMA_TO_DEVICE, 0); + ret = ipu6_dma_map_sgtable(sys, sgt, dir, attrs); if (ret) { - dma_unmap_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0); + dma_unmap_sgtable(&pdev->dev, sgt, dir, 0); sg_free_table(sgt); goto out; } @@ -736,18 +736,18 @@ int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, return ret; } -EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, "INTEL_IPU6"); +EXPORT_SYMBOL_NS_GPL(ipu6_map_fw_region, "INTEL_IPU6"); -void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, - struct sg_table *sgt) +void ipu6_unmap_fw_region(struct ipu6_bus_device *sys, + enum dma_data_direction dir) { struct pci_dev *pdev = sys->isp->pdev; - ipu6_dma_unmap_sgtable(sys, sgt, DMA_TO_DEVICE, 0); - dma_unmap_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0); - sg_free_table(sgt); + ipu6_dma_unmap_sgtable(sys, &sys->fw_sgt, dir, 0); + dma_unmap_sgtable(&pdev->dev, &sys->fw_sgt, dir, 0); + sg_free_table(&sys->fw_sgt); } -EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, "INTEL_IPU6"); +EXPORT_SYMBOL_NS_GPL(ipu6_unmap_fw_region, "INTEL_IPU6"); int ipu6_buttress_authenticate(struct ipu6_device *isp) { diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h index 47a49e961945..79d8f5893686 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.h +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h @@ -84,11 +84,11 @@ struct ipu6_ipc_buttress_bulk_msg { }; int ipu6_buttress_ipc_reset(struct ipu6_device *isp); -int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, - const struct firmware *fw, - struct sg_table *sgt); -void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, - struct sg_table *sgt); +int ipu6_map_fw_region(struct ipu6_bus_device *sys, const void *data, + size_t size, enum dma_data_direction dir, + unsigned long attrs); +void ipu6_unmap_fw_region(struct ipu6_bus_device *sys, + enum dma_data_direction dir); int ipu6_buttress_power(struct device *dev, const struct ipu6_buttress_ctrl *ctrl, bool on); bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 5a3ba5007249..459121e6c1cb 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -1042,7 +1042,8 @@ static int isys_probe(struct auxiliary_device *auxdev, if (!isp->secure_mode) { fw = isp->cpd_fw; - ret = ipu6_buttress_map_fw_image(adev, fw, &adev->fw_sgt); + ret = ipu6_map_fw_region(adev, fw->data, fw->size, + DMA_TO_DEVICE, 0); if (ret) goto release_firmware; @@ -1082,7 +1083,7 @@ static int isys_probe(struct auxiliary_device *auxdev, ipu6_cpd_free_pkg_dir(adev); remove_shared_buffer: if (!isp->secure_mode) - ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt); + ipu6_unmap_fw_region(adev, DMA_TO_DEVICE); release_firmware: if (!isp->secure_mode) release_firmware(adev->fw); @@ -1112,7 +1113,7 @@ static void isys_remove(struct auxiliary_device *auxdev) if (!isp->secure_mode) { ipu6_cpd_free_pkg_dir(adev); - ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt); + ipu6_unmap_fw_region(adev, DMA_TO_DEVICE); release_firmware(adev->fw); } diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 428ad046e239..af5581da7149 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -624,8 +624,8 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_ipu6_rpm_put; } - ret = ipu6_buttress_map_fw_image(isp->psys, isp->cpd_fw, - &isp->psys->fw_sgt); + ret = ipu6_map_fw_region(isp->psys, isp->cpd_fw->data, + isp->cpd_fw->size, DMA_TO_DEVICE, 0); if (ret) { dev_err_probe(&isp->pdev->dev, ret, "failed to map fw image\n"); goto out_ipu6_rpm_put; @@ -679,7 +679,7 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) out_ipu6_bus_del_devices: if (!IS_ERR_OR_NULL(isp->psys)) { ipu6_cpd_free_pkg_dir(isp->psys); - ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt); + ipu6_unmap_fw_region(isp->psys, DMA_TO_DEVICE); } if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu)) ipu6_mmu_cleanup(isp->psys->mmu); @@ -702,7 +702,7 @@ static void ipu6_pci_remove(struct pci_dev *pdev) devm_free_irq(&pdev->dev, pdev->irq, isp); ipu6_cpd_free_pkg_dir(isp->psys); - ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt); + ipu6_unmap_fw_region(isp->psys, DMA_TO_DEVICE); ipu6_buttress_exit(isp); ipu6_bus_del_devices(pdev); From 69ebb1bfc86580841b22940b7de0b36db57d6ded Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:31 +0300 Subject: [PATCH 086/596] media: ipu6: Move isys fw mapping to pci_probe Consolidate isys and psys firmware mapping into a single location to simplify ipu7 support and error handling. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-bus.h | 1 - drivers/media/pci/intel/ipu6/ipu6-isys.c | 29 ---------- drivers/media/pci/intel/ipu6/ipu6.c | 71 ++++++++++++++++++------ 3 files changed, 54 insertions(+), 47 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.h b/drivers/media/pci/intel/ipu6/ipu6-bus.h index aef8e4a66c4a..d2f93eb03dea 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-bus.h +++ b/drivers/media/pci/intel/ipu6/ipu6-bus.h @@ -26,7 +26,6 @@ struct ipu6_bus_device { struct ipu6_mmu *mmu; struct ipu6_device *isp; const struct ipu6_buttress_ctrl *ctrl; - const struct firmware *fw; struct sg_table fw_sgt; u64 *pkg_dir; dma_addr_t pkg_dir_dma_addr; diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 459121e6c1cb..3fb34d2d189c 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -995,7 +995,6 @@ static int isys_probe(struct auxiliary_device *auxdev, const struct ipu6_isys_internal_csi2_pdata *csi2_pdata; struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); struct ipu6_device *isp = adev->isp; - const struct firmware *fw; struct ipu6_isys *isys; unsigned int i; int ret; @@ -1040,18 +1039,6 @@ static int isys_probe(struct auxiliary_device *auxdev, isys_stream_init(isys); - if (!isp->secure_mode) { - fw = isp->cpd_fw; - ret = ipu6_map_fw_region(adev, fw->data, fw->size, - DMA_TO_DEVICE, 0); - if (ret) - goto release_firmware; - - ret = ipu6_cpd_create_pkg_dir(adev, isp->cpd_fw->data); - if (ret) - goto remove_shared_buffer; - } - cpu_latency_qos_add_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); ret = alloc_fw_msg_bufs(isys, 20); @@ -1079,14 +1066,6 @@ static int isys_probe(struct auxiliary_device *auxdev, free_fw_msg_bufs(isys); out_remove_pkg_dir_shared_buffer: cpu_latency_qos_remove_request(&isys->pm_qos); - if (!isp->secure_mode) - ipu6_cpd_free_pkg_dir(adev); -remove_shared_buffer: - if (!isp->secure_mode) - ipu6_unmap_fw_region(adev, DMA_TO_DEVICE); -release_firmware: - if (!isp->secure_mode) - release_firmware(adev->fw); for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) mutex_destroy(&isys->streams[i].mutex); @@ -1099,9 +1078,7 @@ static int isys_probe(struct auxiliary_device *auxdev, static void isys_remove(struct auxiliary_device *auxdev) { - struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); struct ipu6_isys *isys = dev_get_drvdata(&auxdev->dev); - struct ipu6_device *isp = adev->isp; unsigned int i; free_fw_msg_bufs(isys); @@ -1111,12 +1088,6 @@ static void isys_remove(struct auxiliary_device *auxdev) cpu_latency_qos_remove_request(&isys->pm_qos); - if (!isp->secure_mode) { - ipu6_cpd_free_pkg_dir(adev); - ipu6_unmap_fw_region(adev, DMA_TO_DEVICE); - release_firmware(adev->fw); - } - for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) mutex_destroy(&isys->streams[i].mutex); diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index af5581da7149..6e1d3b7efa20 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -480,6 +480,42 @@ static void ipu6_configure_vc_mechanism(struct ipu6_device *isp) writel(val, isp->base + BUTTRESS_REG_BTRS_CTRL); } +static int __ipu6_map_fw_by_sys(struct ipu6_device *isp, struct ipu6_bus_device *adev) +{ + int ret; + + ret = ipu6_map_fw_region(adev, isp->cpd_fw->data, isp->cpd_fw->size, + DMA_TO_DEVICE, 0); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "Firmware mapping failed\n"); + return ret; + } + + ret = ipu6_cpd_create_pkg_dir(adev, isp->cpd_fw->data); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "failed to create pkg dir\n"); + return ret; + } + + return 0; +} + +static int ipu6_map_fw(struct ipu6_device *isp) +{ + int ret; + + ret = __ipu6_map_fw_by_sys(isp, isp->psys); + if (ret) + return ret; + + if (!isp->secure_mode) + return __ipu6_map_fw_by_sys(isp, isp->isys); + + return 0; +} + static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { const struct ipu6_buttress_ctrl *isys_ctrl, *psys_ctrl; @@ -624,19 +660,9 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_ipu6_rpm_put; } - ret = ipu6_map_fw_region(isp->psys, isp->cpd_fw->data, - isp->cpd_fw->size, DMA_TO_DEVICE, 0); - if (ret) { - dev_err_probe(&isp->pdev->dev, ret, "failed to map fw image\n"); + ret = ipu6_map_fw(isp); + if (ret) 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_rpm_put; - } ret = devm_request_threaded_irq(dev, pdev->irq, ipu6_buttress_isr, ipu6_buttress_isr_threaded, @@ -679,7 +705,13 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) out_ipu6_bus_del_devices: if (!IS_ERR_OR_NULL(isp->psys)) { ipu6_cpd_free_pkg_dir(isp->psys); - ipu6_unmap_fw_region(isp->psys, DMA_TO_DEVICE); + if (isp->psys->fw_sgt.nents) + ipu6_unmap_fw_region(isp->psys, DMA_TO_DEVICE); + } + if (!IS_ERR_OR_NULL(isp->isys)) { + ipu6_cpd_free_pkg_dir(isp->isys); + if (isp->isys->fw_sgt.nents) + ipu6_unmap_fw_region(isp->isys, DMA_TO_DEVICE); } if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu)) ipu6_mmu_cleanup(isp->psys->mmu); @@ -696,13 +728,18 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) static void ipu6_pci_remove(struct pci_dev *pdev) { struct ipu6_device *isp = pci_get_drvdata(pdev); - struct ipu6_mmu *isys_mmu = isp->isys->mmu; - struct ipu6_mmu *psys_mmu = isp->psys->mmu; devm_free_irq(&pdev->dev, pdev->irq, isp); ipu6_cpd_free_pkg_dir(isp->psys); ipu6_unmap_fw_region(isp->psys, DMA_TO_DEVICE); + + if (isp->isys) { + ipu6_cpd_free_pkg_dir(isp->isys); + if (isp->isys->fw_sgt.nents) + ipu6_unmap_fw_region(isp->isys, DMA_TO_DEVICE); + } + ipu6_buttress_exit(isp); ipu6_bus_del_devices(pdev); @@ -712,8 +749,8 @@ static void ipu6_pci_remove(struct pci_dev *pdev) release_firmware(isp->cpd_fw); - ipu6_mmu_cleanup(psys_mmu); - ipu6_mmu_cleanup(isys_mmu); + ipu6_mmu_cleanup(isp->psys->mmu); + ipu6_mmu_cleanup(isp->isys->mmu); } static void ipu6_pci_reset_prepare(struct pci_dev *pdev) From e2e08193a6fa98e983c27397a8fa719473f341be Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:32 +0300 Subject: [PATCH 087/596] media: ipu6: Map ipu7 firmware Add ipu7 firmware mapping helpers to handle differences from ipu6. Signed-off-by: Antti Laakso [Sakari Ailus: Use ipu6_ prefix for ipu7 related global symbols.] Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6.c | 83 ++++++++++++++++++++++++++--- drivers/media/pci/intel/ipu6/ipu6.h | 1 + 2 files changed, 76 insertions(+), 8 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 6e1d3b7efa20..817bf54e332a 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,7 @@ #include "ipu6-bus.h" #include "ipu6-buttress.h" #include "ipu6-cpd.h" +#include "ipu6-dma.h" #include "ipu6-isys.h" #include "ipu6-mmu.h" #include "ipu6-platform-buttress-regs.h" @@ -516,6 +518,58 @@ static int ipu6_map_fw(struct ipu6_device *isp) return 0; } +static int __ipu7_map_fw_non_secure(struct ipu6_device *isp) +{ + int ret; + + /* + * Allocate and map memory for running the firmware. Not + * required in secure mode, in which firmware runs in IMR. + */ + isp->fw_code_region = vmalloc(IPU7_FW_CODE_REGION_SIZE); + if (!isp->fw_code_region) + return -ENOMEM; + + ret = ipu6_ipu7_cpd_copy_binary(isp->cpd_fw->data, "isys", + isp->fw_code_region, + &isp->isys->fw_entry); + if (ret) + return ret; + + ret = ipu6_map_fw_region(isp->isys, isp->fw_code_region, + IPU7_FW_CODE_REGION_SIZE, DMA_BIDIRECTIONAL, + DMA_ATTR_RESERVE_REGION); + if (ret) + return ret; + + ret = ipu6_ipu7_cpd_copy_binary(isp->cpd_fw->data, "psys", + isp->fw_code_region, + &isp->psys->fw_entry); + if (ret) + return ret; + + return ipu6_map_fw_region(isp->psys, isp->fw_code_region, + IPU7_FW_CODE_REGION_SIZE, DMA_BIDIRECTIONAL, + DMA_ATTR_RESERVE_REGION); +} + +static int ipu7_map_fw(struct ipu6_device *isp) +{ + int ret; + + ret = isp->secure_mode ? + ipu6_map_fw_region(isp->psys, isp->cpd_fw->data, + isp->cpd_fw->size, DMA_BIDIRECTIONAL, 0) : + __ipu7_map_fw_non_secure(isp); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "Failed to init ipu7 firmware region\n"); + return ret; + } + + return 0; +} + static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { const struct ipu6_buttress_ctrl *isys_ctrl, *psys_ctrl; @@ -525,6 +579,7 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) struct ipu6_device *isp; phys_addr_t phys; u32 val, version, sku_id; + unsigned long dir; int ret; isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); @@ -660,7 +715,7 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_ipu6_rpm_put; } - ret = ipu6_map_fw(isp); + ret = IS_IPU7(isp) ? ipu7_map_fw(isp) : ipu6_map_fw(isp); if (ret) goto out_ipu6_rpm_put; @@ -703,16 +758,21 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) out_ipu6_rpm_put: pm_runtime_put_sync(&isp->psys->auxdev.dev); out_ipu6_bus_del_devices: + dir = IS_IPU7(isp) ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE; if (!IS_ERR_OR_NULL(isp->psys)) { ipu6_cpd_free_pkg_dir(isp->psys); if (isp->psys->fw_sgt.nents) - ipu6_unmap_fw_region(isp->psys, DMA_TO_DEVICE); + ipu6_unmap_fw_region(isp->psys, dir); } if (!IS_ERR_OR_NULL(isp->isys)) { ipu6_cpd_free_pkg_dir(isp->isys); if (isp->isys->fw_sgt.nents) - ipu6_unmap_fw_region(isp->isys, DMA_TO_DEVICE); + ipu6_unmap_fw_region(isp->isys, dir); } + + vfree(isp->fw_code_region); + isp->fw_code_region = NULL; + if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu)) ipu6_mmu_cleanup(isp->psys->mmu); if (!IS_ERR_OR_NULL(isp->isys) && !IS_ERR_OR_NULL(isp->isys->mmu)) @@ -728,18 +788,23 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) static void ipu6_pci_remove(struct pci_dev *pdev) { struct ipu6_device *isp = pci_get_drvdata(pdev); + unsigned long dir; devm_free_irq(&pdev->dev, pdev->irq, isp); - ipu6_cpd_free_pkg_dir(isp->psys); - ipu6_unmap_fw_region(isp->psys, DMA_TO_DEVICE); + dir = IS_IPU7(isp) ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE; + ipu6_cpd_free_pkg_dir(isp->psys); + ipu6_unmap_fw_region(isp->psys, dir); if (isp->isys) { ipu6_cpd_free_pkg_dir(isp->isys); if (isp->isys->fw_sgt.nents) - ipu6_unmap_fw_region(isp->isys, DMA_TO_DEVICE); + ipu6_unmap_fw_region(isp->isys, dir); } + vfree(isp->fw_code_region); + isp->fw_code_region = NULL; + ipu6_buttress_exit(isp); ipu6_bus_del_devices(pdev); @@ -749,8 +814,10 @@ static void ipu6_pci_remove(struct pci_dev *pdev) release_firmware(isp->cpd_fw); - ipu6_mmu_cleanup(isp->psys->mmu); - ipu6_mmu_cleanup(isp->isys->mmu); + if (isp->psys) + ipu6_mmu_cleanup(isp->psys->mmu); + if (isp->isys) + ipu6_mmu_cleanup(isp->isys->mmu); } static void ipu6_pci_reset_prepare(struct pci_dev *pdev) diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index 6bef45a02c2b..a2992ffe8e3c 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -70,6 +70,7 @@ struct ipu6_device { bool secure_mode; u8 hw_ver; bool bus_ready_to_probe; + u32 *fw_code_region; }; #define IPU_PSYS 0 From 967f47ed1e8bdafb4c48782b6416f1e73b828dfc Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:33 +0300 Subject: [PATCH 088/596] media: ipu6: Set model name for ipu7 We are about to enable support for ipu7 hardware, set model name accordingly. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-isys.c | 2 +- drivers/media/pci/intel/ipu6/ipu6.c | 2 ++ drivers/media/pci/intel/ipu6/ipu6.h | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c index 3fb34d2d189c..08f29b678d1b 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -739,7 +739,7 @@ static int isys_register_devices(struct ipu6_isys *isys) isys->media_dev.dev = dev; media_device_pci_init(&isys->media_dev, - pdev, IPU6_MEDIA_DEV_MODEL_NAME); + pdev, isys->adev->isp->model_name); strscpy(isys->v4l2_dev.name, isys->media_dev.model, sizeof(isys->v4l2_dev.name)); diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 817bf54e332a..14a60f10e649 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -588,6 +588,7 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt); isp->buttress.regs = &ipu6_buttress_regs; + isp->model_name = IPU6_MEDIA_DEV_MODEL_NAME; isys_ctrl = &ipu6_isys_buttress_ctrl; psys_ctrl = &ipu6_psys_buttress_ctrl; @@ -618,6 +619,7 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) case PCI_DEVICE_ID_INTEL_IPU7: isp->hw_ver = IPU_VERSION_7; isp->cpd_fw_name = IPU7_FIRMWARE_NAME; + isp->model_name = IPU7_MEDIA_DEV_MODEL_NAME; isp->buttress.regs = &ipu7_buttress_regs; isys_ctrl = &ipu7_isys_buttress_ctrl; psys_ctrl = &ipu7_psys_buttress_ctrl; diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index a2992ffe8e3c..8a696caf4b88 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -16,6 +16,8 @@ struct ipu6_bus_device; #define IPU6_NAME "intel-ipu6" #define IPU6_MEDIA_DEV_MODEL_NAME "ipu6" +#define IPU7_MEDIA_DEV_MODEL_NAME "ipu7" +#define IPU7P5_MEDIA_DEV_MODEL_NAME "ipu7.5" #define IPU6SE_FIRMWARE_NAME "intel/ipu/ipu6se_fw.bin" #define IPU6EP_FIRMWARE_NAME "intel/ipu/ipu6ep_fw.bin" @@ -63,6 +65,7 @@ struct ipu6_device { const struct firmware *cpd_fw; const char *cpd_fw_name; u32 cpd_metadata_cmpnt_size; + const char *model_name; void __iomem *base; void __iomem *pb_base; From 1352f7e448242c601979ae225c47d483699bb0f8 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:34 +0300 Subject: [PATCH 089/596] media: ipu6: Add ipu7.5 buttress support The ipu7.5 buttress differs from ipu7 slightly, add support for ipu7.5. Also set hardware version, firmware and model names for ipu7.5. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu6-buttress.c | 53 +++++++++++++++++-- .../intel/ipu6/ipu6-platform-buttress-regs.h | 7 +++ drivers/media/pci/intel/ipu6/ipu6.c | 38 +++++++++++++ drivers/media/pci/intel/ipu6/ipu6.h | 1 + 4 files changed, 94 insertions(+), 5 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c index 74db0a37561e..105de1744dff 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-buttress.c +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -3,6 +3,7 @@ * Copyright (C) 2013--2024 Intel Corporation */ +#include #include #include #include @@ -900,11 +901,30 @@ static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) return ret; } -int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) +static int __ipu7p5_start_tsc_sync(struct ipu6_device *isp) { - unsigned int i; + u32 val; - for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { + val = readl(isp->base + IPU7_BUTTRESS_REG_TSC_CTL); + val |= IPU7_BUTTRESS_SEL_PB_TIMESTAMP; + writel(val, isp->base + IPU7_BUTTRESS_REG_TSC_CTL); + + for (unsigned int i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { + val = readl(isp->base + IPU7_BUTTRESS_REG_PB_TIMESTAMP_VALID); + if (val == 1) + return 0; + + usleep_range(40, 50); + } + + dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n"); + + return -ETIMEDOUT; +} + +static int __ipu6_start_tsc_sync(struct ipu6_device *isp) +{ + for (unsigned int i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { u32 val; int ret; @@ -925,6 +945,14 @@ int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) return -ETIMEDOUT; } + +int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) +{ + if (IS_IPU7P5(isp)) + return __ipu7p5_start_tsc_sync(isp); + + return __ipu6_start_tsc_sync(isp); +} EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, "INTEL_IPU6"); void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val) @@ -987,6 +1015,13 @@ u32 ipu7_buttress_get_isys_freq(struct ipu6_device *isp) } EXPORT_SYMBOL_NS_GPL(ipu7_buttress_get_isys_freq, "INTEL_IPU6"); +static const struct x86_cpu_id ipu7_misc_cfg_exclusion[] = { + X86_MATCH_VFM_STEPS(INTEL_PANTHERLAKE_L, 0x1, 0x1, 0), + {}, +}; + +#define IPU7_WRXREQOP_OVRD_VAL_MASK GENMASK(22, 19) + static void ipu7_buttress_setup(struct ipu6_device *isp) { struct ipu6_buttress *b = &isp->buttress; @@ -995,12 +1030,20 @@ static void ipu7_buttress_setup(struct ipu6_device *isp) /* program PB BAR */ writel(0, isp->pb_base + IPU7_GLOBAL_INTERRUPT_MASK); val = readl(isp->pb_base + IPU7_BAR2_MISC_CONFIG); + val |= 0x100U; + if (!IS_IPU7_MTL(isp) && !x86_match_cpu(ipu7_misc_cfg_exclusion)) + val |= FIELD_PREP(IPU7_WRXREQOP_OVRD_VAL_MASK, 0xf) | BIT(18); writel(val, isp->pb_base + IPU7_BAR2_MISC_CONFIG); - writel(BIT(22), isp->pb_base + IPU7_TLBID_HASH_ENABLE_63_32); - writel(BIT(1), isp->pb_base + IPU7_TLBID_HASH_ENABLE_127_96); + if (IS_IPU7P5(isp)) { + writel(BIT(14), isp->pb_base + IPU7_TLBID_HASH_ENABLE_63_32); + writel(BIT(9), isp->pb_base + IPU7_TLBID_HASH_ENABLE_95_64); + } else { + writel(BIT(22), isp->pb_base + IPU7_TLBID_HASH_ENABLE_63_32); + writel(BIT(1), isp->pb_base + IPU7_TLBID_HASH_ENABLE_127_96); + } writel(b->regs->irq_all, isp->base + b->regs->irq_clear); writel(b->regs->irq_all, isp->base + IPU7_BUTTRESS_REG_IRQ_MASK); diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h index c4da72c6bd8f..57e661cc2177 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h +++ b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h @@ -233,6 +233,8 @@ enum { #define IPU7_BUTTRESS_REG_TSC_CMD 0x2014 #define IPU7_BUTTRESS_REG_TSC_CTL 0x2018 #define IPU7_BUTTRESS_REG_TSC_LO 0x201c +#define IPU7_BUTTRESS_REG_PB_TIMESTAMP_LO 0x2030 +#define IPU7_BUTTRESS_REG_PB_TIMESTAMP_VALID 0x2038 #define IPU7_BUTTRESS_REG_IS_WORKPOINT_REQ 0x2104 #define IPU7_BUTTRESS_REG_PS_WORKPOINT_REQ 0x2100 #define IPU7_BUTTRESS_REG_IDLE_WDT 0x218c @@ -319,8 +321,13 @@ enum { /* PB registers */ #define IPU7_GLOBAL_INTERRUPT_MASK 0x8 +#define IPU7_TLBID_HASH_ENABLE_31_0 0x30 #define IPU7_TLBID_HASH_ENABLE_63_32 0x34 +#define IPU7_TLBID_HASH_ENABLE_95_64 0x38 #define IPU7_TLBID_HASH_ENABLE_127_96 0x3c #define IPU7_BAR2_MISC_CONFIG 0x64 +/* IPU7.5 */ +#define IPU7_BUTTRESS_SEL_PB_TIMESTAMP BIT(9) + #endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 14a60f10e649..74f9cf96410f 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -197,6 +197,36 @@ static const struct ipu6_buttress_registers ipu7_buttress_regs = { .irq_sai = IPU7_BUTTRESS_IRQ_SAI_VIOLATION, }; +static const struct ipu6_buttress_registers ipu7p5_buttress_regs = { + /* Registers */ + .irq_status = IPU7_BUTTRESS_REG_IRQ_STATUS, + .irq_clear = IPU7_BUTTRESS_REG_IRQ_CLEAR, + .irq_enable = IPU7_BUTTRESS_REG_IRQ_ENABLE, + .pwr_status = IPU7_BUTTRESS_REG_PWR_STATUS, + .security_ctl = IPU7_BUTTRESS_REG_SECURITY_CTL, + .fw_reset_ctl = IPU7_BUTTRESS_REG_FW_RESET_CTL, + .fabric_cmd = IPU7_BUTTRESS_REG_TSC_CMD, + .tsw_ctl = IPU7_BUTTRESS_REG_TSC_CTL, + .tsc_lo = IPU7_BUTTRESS_REG_PB_TIMESTAMP_LO, + .wdt = IPU7_BUTTRESS_REG_IDLE_WDT, + .csr_in = IPU7_BUTTRESS_REG_CSE2IUCSR, + .csr_out = IPU7_BUTTRESS_REG_IU2CSECSR, + .db0_in = IPU7_BUTTRESS_REG_CSE2IUDB0, + .db0_out = IPU7_BUTTRESS_REG_IU2CSEDB0, + .data0_in = IPU7_BUTTRESS_REG_CSE2IUDATA0, + .data0_out = IPU7_BUTTRESS_REG_IU2CSEDATA0, + .sku_id = IPU7_BUTTRESS_REG_SKU, + + /* Bitmasks */ + .irq_is = IPU7_BUTTRESS_IRQ_IS_IRQ, + .irq_ps = IPU7_BUTTRESS_IRQ_PS_IRQ, + .irq_all = IPU7_BUTTRESS_IRQS, + .irq_events = IPU7_BUTTRESS_IRQS, + .irq_cse_ipc = IPU7_BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING, + .irq_exec_done = IPU7_BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE, + .irq_sai = IPU7_BUTTRESS_IRQ_SAI_VIOLATION, +}; + static void ipu6_pkg_dir_configure_spc(struct ipu6_device *isp, const struct ipu6_hw_variants *hw_variant, @@ -624,6 +654,14 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) isys_ctrl = &ipu7_isys_buttress_ctrl; psys_ctrl = &ipu7_psys_buttress_ctrl; break; + case PCI_DEVICE_ID_INTEL_IPU7P5: + isp->hw_ver = IPU_VERSION_7P5; + isp->cpd_fw_name = IPU7P5_FIRMWARE_NAME; + isp->model_name = IPU7P5_MEDIA_DEV_MODEL_NAME; + isp->buttress.regs = &ipu7p5_buttress_regs; + isys_ctrl = &ipu7_isys_buttress_ctrl; + psys_ctrl = &ipu7_psys_buttress_ctrl; + break; default: return dev_err_probe(dev, -ENODEV, "Unsupported IPU6 device %x\n", diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h index 8a696caf4b88..72c5063d5198 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.h +++ b/drivers/media/pci/intel/ipu6/ipu6.h @@ -25,6 +25,7 @@ struct ipu6_bus_device; #define IPU6EPMTL_FIRMWARE_NAME "intel/ipu/ipu6epmtl_fw.bin" #define IPU6EPADLN_FIRMWARE_NAME "intel/ipu/ipu6epadln_fw.bin" #define IPU7_FIRMWARE_NAME "intel/ipu/ipu7_fw.bin" +#define IPU7P5_FIRMWARE_NAME "intel/ipu/ipu7ptl_fw.bin" #define IPU_VERSION_6 BIT(0) /* TGL */ #define IPU_VERSION_6SE BIT(1) /* JSL */ From 9836cd184ed8dadf09f717b8bb7ca4bf45e235d2 Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:35 +0300 Subject: [PATCH 090/596] media: ipu6: Add ipu7.5 mmu initialization data The ipu7.5 mmu is different compared to ipu7. Signed-off-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c | 261 ++++++++++++++++++++- drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h | 99 ++++++++ 2 files changed, 359 insertions(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c index 3cd9a818733c..c9b015e89ee6 100644 --- a/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c +++ b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.c @@ -431,6 +431,250 @@ static struct ipu7_mmu_hw ipu7_psys_mmu_hwdata[] = { }, }; +static struct ipu7_mmu_hw ipu7p5_isys_mmu_hwdata[] = { + { + .name = "IS_FW_RD", + .offset = IPU7P5_IS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_UC_RD_OFFSET, + .uao_offset = IPU7P5_IS_UAO_UC_RD_OFFSET, + .info_bits = 0x20005101, + .refill = 0x00002726, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { 0x0, 0x8, 0xa }, + .l2_block_sz = { 0x0, 0x2, 0x4 }, + .zlx_nr = IPU7P5_IS_ZLX_UC_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { 0, 1, 0, 0 }, + .zlx_conf = { 0x0 }, + .uao_p_num = IPU7P5_IS_UAO_UC_RD_PLANENUM, + .uao_p2tlb = { 0x49, 0x4c, 0x4d, 0x0 }, + }, + { + .name = "IS_FW_WR", + .offset = IPU7P5_IS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_UC_WR_OFFSET, + .uao_offset = IPU7P5_IS_UAO_UC_WR_OFFSET, + .info_bits = 0x20005001, + .refill = 0x00002524, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { 0x0, 0x8, 0xa }, + .l2_block_sz = { 0x0, 0x2, 0x4 }, + .zlx_nr = IPU7P5_IS_ZLX_UC_WR_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 0, 1, 1, 0 }, + .zlx_conf = { 0x0, 0x00010101, 0x00010101, 0x0 }, + .uao_p_num = IPU7P5_IS_UAO_UC_WR_PLANENUM, + .uao_p2tlb = { 0x49, 0x4a, 0x4b, 0x0 }, + }, + { + .name = "IS_DATA_WR_ISOC", + .offset = IPU7P5_IS_MMU_M0_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_M0_OFFSET, + .uao_offset = IPU7P5_IS_UAO_M0_WR_OFFSET, + .info_bits = 0x20004e01, + .refill = 0x00002120, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_M0_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_M0_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_M0_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_M0_STREAM_NUM, + .l1_block_sz = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, + 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x01e }, + .l2_block_sz = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, + 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e }, + .zlx_nr = IPU7P5_IS_ZLX_M0_NUM, + .zlx_axi_pool = { 0x00000f10 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + .zlx_conf = { 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103 }, + .uao_p_num = IPU7P5_IS_UAO_M0_WR_PLANENUM, + .uao_p2tlb = { 0x00000041, 0x00000042, 0x00000043, 0x00000044, + 0x00000041, 0x00000042, 0x00000043, 0x00000044, + 0x00000041, 0x00000042, 0x00000043, 0x00000044, + 0x00000041, 0x00000042, 0x00000043, 0x00000044 }, + }, + { + .name = "IS_DATA_WR_SNOOP", + .offset = IPU7P5_IS_MMU_M1_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_M1_OFFSET, + .uao_offset = IPU7P5_IS_UAO_M1_WR_OFFSET, + .info_bits = 0x20004f01, + .refill = 0x00002322, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_M1_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_M1_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_M1_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_M1_STREAM_NUM, + .l1_block_sz = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, + 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e }, + .l2_block_sz = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, + 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e }, + .zlx_nr = IPU7P5_IS_ZLX_M1_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + .zlx_conf = { 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103, + 0x00010103, 0x00010103, 0x00010103, 0x00010103 }, + .uao_p_num = IPU7P5_IS_UAO_M1_WR_PLANENUM, + .uao_p2tlb = { 0x00000045, 0x00000046, 0x00000047, 0x00000048, + 0x00000045, 0x00000046, 0x00000047, 0x00000048, + 0x00000045, 0x00000046, 0x00000047, 0x00000048, + 0x00000045, 0x00000046, 0x00000047, 0x00000048 }, + }, +}; + +static struct ipu7_mmu_hw ipu7p5_psys_mmu_hwdata[] = { + { + .name = "PS_FW_RD", + .offset = IPU7P5_PS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_FW_RD_OFFSET, + .uao_offset = IPU7P5_PS_UAO_FW_RD_OFFSET, + .info_bits = 0x20004001, + .refill = 0x00002726, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { 0x00, 0x08, 0x0a, 0x0c, 0x0d, 0x0f, 0x11, + 0x12, 0x13, 0x14, 0x16, 0x18, 0x19, 0x1a, + 0x1a, 0x1a }, + .l2_block_sz = { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, + 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, + 0x1c, 0x1e }, + .zlx_nr = IPU7P5_PS_ZLX_FW_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, + .zlx_conf = { 0x00000000, 0x00010101, 0x00000000, 0x00000000, + 0x00010101, 0x00010101, 0x00000000, 0x00000000, + 0x00000000, 0x00010101, 0x00010101, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + .uao_p_num = IPU7P5_PS_UAO_FW_RD_PLANENUM, + .uao_p2tlb = { 0x2e, 0x35, 0x36, 0x31, 0x37, 0x38, 0x39, 0x32, + 0x33, 0x3a, 0x3b, 0x3c, 0x34, 0x00, 0x00, 0x00 }, + }, + { + .name = "PS_FW_WR", + .offset = IPU7P5_PS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_FW_WR_OFFSET, + .uao_offset = IPU7P5_PS_UAO_FW_WR_OFFSET, + .info_bits = 0x20003e01, + .refill = 0x00002322, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { 0x00, 0x08, 0x0a, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x10, 0x10 }, + .l2_block_sz = { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, + 0x10, 0x12 }, + .zlx_nr = IPU7P5_PS_ZLX_FW_WR_NUM, + .zlx_axi_pool = { 0x00000f20 }, + .zlx_en = { 0, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, + .zlx_conf = { 0x00000000, 0x00010101, 0x00010101, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 }, + .uao_p_num = IPU7P5_PS_UAO_FW_WR_PLANENUM, + .uao_p2tlb = { 0x0000002e, 0x0000002f, 0x00000030, 0x00000031, + 0x00000032, 0x00000033, 0x00000034, 0x00000000, + 0x00000000, 0x00000000 }, + }, + { + .name = "PS_DATA_RD", + .offset = IPU7P5_PS_MMU_SRT_RD_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_DATA_RD_OFFSET, + .uao_offset = IPU7P5_PS_UAO_SRT_RD_OFFSET, + .info_bits = 0x20003f01, + .refill = 0x00002524, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_SRT_RD_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_SRT_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_SRT_RD_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_SRT_RD_STREAM_NUM, + .l1_block_sz = { 0x00, 0x04, 0x06, 0x08, 0x0b, 0x0d, 0x0f, 0x13, + 0x17, 0x19, 0x1b, 0x1d, 0x1f, 0x2b, 0x33, 0x3f, + 0x47, 0x49, 0x4b, 0x4c, 0x4d, 0x4e }, + .l2_block_sz = { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, + 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, + 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a }, + .zlx_nr = IPU7P5_PS_ZLX_DATA_RD_NUM, + .zlx_axi_pool = { 0x00000f30 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0 }, + .zlx_conf = { 0x00030303, 0x00010101, 0x00010101, 0x00030202, + 0x00010101, 0x00010101, 0x00030303, 0x00030303, + 0x00010101, 0x00030800, 0x00030500, 0x00020101, + 0x00042000, 0x00031000, 0x00042000, 0x00031000, + 0x00020400, 0x00010101, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 }, + .uao_p_num = IPU7P5_PS_UAO_SRT_RD_PLANENUM, + .uao_p2tlb = { 0x0000001c, 0x0000001d, 0x0000001e, 0x0000001f, + 0x00000020, 0x00000021, 0x00000022, 0x00000023, + 0x00000024, 0x00000025, 0x00000026, 0x00000027, + 0x00000028, 0x00000029, 0x0000002a, 0x0000002b, + 0x0000002c, 0x0000002d, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 }, + }, + { + .name = "PS_DATA_WR", + .offset = IPU7P5_PS_MMU_SRT_WR_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_DATA_WR_OFFSET, + .uao_offset = IPU7P5_PS_UAO_SRT_WR_OFFSET, + .info_bits = 0x20003d01, + .refill = 0x00002120, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_SRT_WR_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_SRT_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_SRT_WR_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_SRT_WR_STREAM_NUM, + .l1_block_sz = { 0x00, 0x02, 0x06, 0x0a, 0x0c, 0x0e, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, + 0x24, 0x28, 0x2a, 0x36, 0x3e, 0x40, 0x42, 0x4e, + 0x56, 0x5c, 0x68, 0x70, 0x76, 0x77, 0x78, 0x79 }, + .l2_block_sz = { 0x00, 0x02, 0x06, 0x0a, 0x0c, 0x0e, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, + 0x24, 0x28, 0x2a, 0x36, 0x3e, 0x40, 0x42, 0x4e, + 0x56, 0x5c, 0x68, 0x70, 0x76, 0x77, 0x78, 0x79 }, + .zlx_nr = IPU7P5_PS_ZLX_DATA_WR_NUM, + .zlx_axi_pool = { 0x00000f50 }, + .zlx_en = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + .zlx_conf = { 0x00010102, 0x00030103, 0x00030103, 0x00010101, + 0x00010101, 0x00030101, 0x00010101, 0x38010101, + 0x00000000, 0x00000000, 0x38010101, 0x38010101, + 0x38010101, 0x38010101, 0x38010101, 0x38010101, + 0x00030303, 0x00010101, 0x00042000, 0x00031000, + 0x00010101, 0x00010101, 0x00042000, 0x00031000, + 0x00031000, 0x00042000, 0x00031000, 0x00031000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + .uao_p_num = IPU7P5_PS_UAO_SRT_WR_PLANENUM, + .uao_p2tlb = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x00, 0x00, 0x00, 0x00 }, + }, +}; + static const struct ipu7_mmu_hwdata ipu7_mmu_hwdata_lookup[IPU_SUBSYS_NUM] = { [IPU_PSYS] = { .hwdata = ipu7_psys_mmu_hwdata, @@ -442,6 +686,17 @@ static const struct ipu7_mmu_hwdata ipu7_mmu_hwdata_lookup[IPU_SUBSYS_NUM] = { }, }; +static const struct ipu7_mmu_hwdata ipu7p5_mmu_hwdata_lookup[IPU_SUBSYS_NUM] = { + [IPU_PSYS] = { + .hwdata = ipu7p5_psys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu7p5_psys_mmu_hwdata), + }, + [IPU_ISYS] = { + .hwdata = ipu7p5_isys_mmu_hwdata, + .nr_mmus = ARRAY_SIZE(ipu7p5_isys_mmu_hwdata), + }, +}; + static void __ipu7_tlb_invalidate(struct ipu6_mmu *mmu) { struct ipu7_mmu_hw *mmu_hw = mmu->ipu7_mmu_hw; @@ -561,13 +816,17 @@ static int __ipu7_mmu_hw_init(struct ipu6_mmu *mmu) static int __ipu7_mmu_init_hw_data(struct ipu6_mmu *mmu, struct device *dev, void __iomem *base) { - const struct ipu7_mmu_hwdata *lookup = ipu7_mmu_hwdata_lookup; + struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev)); + const struct ipu7_mmu_hwdata *lookup; struct ipu7_mmu_hw *mmu_hw, *src; unsigned int i, nr_mmus; if (mmu->mmid >= IPU_SUBSYS_NUM) return -EINVAL; + lookup = IS_IPU7P5(isp) ? ipu7p5_mmu_hwdata_lookup : + ipu7_mmu_hwdata_lookup; + src = lookup[mmu->mmid].hwdata; nr_mmus = lookup[mmu->mmid].nr_mmus; diff --git a/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h index 68663057138a..ba31ed31b245 100644 --- a/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h +++ b/drivers/media/pci/intel/ipu6/ipu7-mmu-hw.h @@ -114,6 +114,105 @@ #define IPU7_PS_ZLX_FW_RD_NUM 16 #define IPU7_PS_ZLX_FW_WR_NUM 10 +/* IPU7P5 */ +/* IS MMU Cmd RD */ +#define IPU7P5_IS_MMU_FW_RD_OFFSET 0x274000 +#define IPU7P5_IS_MMU_FW_RD_STREAM_NUM 3 +#define IPU7P5_IS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_FW_RD_L2_BLOCKNR_REG 0x60 + +/* IS MMU Cmd WR */ +#define IPU7P5_IS_MMU_FW_WR_OFFSET 0x275000 +#define IPU7P5_IS_MMU_FW_WR_STREAM_NUM 3 +#define IPU7P5_IS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_FW_WR_L2_BLOCKNR_REG 0x60 + +/* IS MMU Data WR Snoop */ +#define IPU7P5_IS_MMU_M0_OFFSET 0x276000 +#define IPU7P5_IS_MMU_M0_STREAM_NUM 16 +#define IPU7P5_IS_MMU_M0_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_M0_L2_BLOCKNR_REG 0x94 + +/* IS MMU Data WR ISOC */ +#define IPU7P5_IS_MMU_M1_OFFSET 0x277000 +#define IPU7P5_IS_MMU_M1_STREAM_NUM 16 +#define IPU7P5_IS_MMU_M1_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_M1_L2_BLOCKNR_REG 0x94 + +/* PS MMU FW RD */ +#define IPU7P5_PS_MMU_FW_RD_OFFSET 0x148000 +#define IPU7P5_PS_MMU_FW_RD_STREAM_NUM 16 +#define IPU7P5_PS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_FW_RD_L2_BLOCKNR_REG 0x94 + +/* PS MMU FW WR */ +#define IPU7P5_PS_MMU_FW_WR_OFFSET 0x149000 +#define IPU7P5_PS_MMU_FW_WR_STREAM_NUM 10 +#define IPU7P5_PS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_FW_WR_L2_BLOCKNR_REG 0x7c + +/* PS MMU FW Data RD VC0 */ +#define IPU7P5_PS_MMU_SRT_RD_OFFSET 0x14a000 +#define IPU7P5_PS_MMU_SRT_RD_STREAM_NUM 22 +#define IPU7P5_PS_MMU_SRT_RD_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_SRT_RD_L2_BLOCKNR_REG 0xac + +/* PS MMU FW Data WR VC0 */ +#define IPU7P5_PS_MMU_SRT_WR_OFFSET 0x14b000 +#define IPU7P5_PS_MMU_SRT_WR_STREAM_NUM 32 +#define IPU7P5_PS_MMU_SRT_WR_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_SRT_WR_L2_BLOCKNR_REG 0xd4 + +/* IS UAO UC RD */ +#define IPU7P5_IS_UAO_UC_RD_OFFSET 0x27c000 +#define IPU7P5_IS_UAO_UC_RD_PLANENUM 4 + +/* IS UAO UC WR */ +#define IPU7P5_IS_UAO_UC_WR_OFFSET 0x27d000 +#define IPU7P5_IS_UAO_UC_WR_PLANENUM 4 + +/* IS UAO M0 WR */ +#define IPU7P5_IS_UAO_M0_WR_OFFSET 0x27e000 +#define IPU7P5_IS_UAO_M0_WR_PLANENUM 16 + +/* IS UAO M1 WR */ +#define IPU7P5_IS_UAO_M1_WR_OFFSET 0x27f000 +#define IPU7P5_IS_UAO_M1_WR_PLANENUM 16 + +/* PS UAO FW RD */ +#define IPU7P5_PS_UAO_FW_RD_OFFSET 0x156000 +#define IPU7P5_PS_UAO_FW_RD_PLANENUM 16 + +/* PS UAO FW WR */ +#define IPU7P5_PS_UAO_FW_WR_OFFSET 0x157000 +#define IPU7P5_PS_UAO_FW_WR_PLANENUM 10 + +/* PS UAO SRT RD */ +#define IPU7P5_PS_UAO_SRT_RD_OFFSET 0x154000 +#define IPU7P5_PS_UAO_SRT_RD_PLANENUM 22 + +/* PS UAO SRT WR */ +#define IPU7P5_PS_UAO_SRT_WR_OFFSET 0x155000 +#define IPU7P5_PS_UAO_SRT_WR_PLANENUM 32 + +#define IPU7P5_IS_ZLX_UC_RD_OFFSET 0x278000 +#define IPU7P5_IS_ZLX_UC_WR_OFFSET 0x279000 +#define IPU7P5_IS_ZLX_M0_OFFSET 0x27a000 +#define IPU7P5_IS_ZLX_M1_OFFSET 0x27b000 +#define IPU7P5_IS_ZLX_UC_RD_NUM 4 +#define IPU7P5_IS_ZLX_UC_WR_NUM 4 +#define IPU7P5_IS_ZLX_M0_NUM 16 +#define IPU7P5_IS_ZLX_M1_NUM 16 + +#define IPU7P5_PS_ZLX_DATA_RD_OFFSET 0x14e000 +#define IPU7P5_PS_ZLX_DATA_WR_OFFSET 0x14f000 +#define IPU7P5_PS_ZLX_FW_RD_OFFSET 0x150000 +#define IPU7P5_PS_ZLX_FW_WR_OFFSET 0x151000 +#define IPU7P5_PS_ZLX_DATA_RD_NUM 22 +#define IPU7P5_PS_ZLX_DATA_WR_NUM 32 +#define IPU7P5_PS_ZLX_FW_RD_NUM 16 +#define IPU7P5_PS_ZLX_FW_WR_NUM 10 + struct ipu7_mmu_hw { char name[32]; From 2f4ffd14b6c0556ae0b5e5ba5691ba8fc6d1fcad Mon Sep 17 00:00:00 2001 From: Antti Laakso Date: Thu, 27 Aug 2026 15:33:36 +0300 Subject: [PATCH 091/596] media: ipu6: Enable support for IPU 7 and IPU 7.5 Enable support for Intel IPU 7 and IPU 7.5, found in Lunar lake and Panther lake, respectively, in the ipu6 driver. Disabling the CONFIG_VIDEO_INTEL_IPU6_IPU7 Kconfig option can be used to still default the support of IPU7 and 7.5 to the ipu7 driver, while default is enabled. The driver binding can still be configured at runtime by using force_probe and force_no_ipu7_probe options in ipu6 and ipu7 drivers, respectively. Signed-off-by: Antti Laakso Co-developed-by: Sakari Ailus Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/Kconfig | 9 +++++++++ drivers/media/pci/intel/ipu6/ipu6.c | 8 ++++++++ drivers/staging/media/ipu7/ipu7.c | 7 +++++++ include/media/ipu6-pci-table.h | 2 ++ 4 files changed, 26 insertions(+) diff --git a/drivers/media/pci/intel/ipu6/Kconfig b/drivers/media/pci/intel/ipu6/Kconfig index 1129e2beb4be..a1d036a78b57 100644 --- a/drivers/media/pci/intel/ipu6/Kconfig +++ b/drivers/media/pci/intel/ipu6/Kconfig @@ -16,3 +16,12 @@ config VIDEO_INTEL_IPU6 To compile this driver, say Y here! It contains 2 modules - intel_ipu6 and intel_ipu6_isys. + +config VIDEO_INTEL_IPU6_IPU7 + bool "Support IPU 7 and IPU 7.5 in ipu6 driver by default" + depends on VIDEO_INTEL_IPU6 + default y + help + Support Intel IPU 7 and IPU 7.5 in ipu6 driver by default, instead of + the ipu7 staging driver. This behaviour can be changed at runtime with + module parameters in the respective drivers. diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 74f9cf96410f..43d951735f72 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -39,6 +39,10 @@ #define IPU6_PCI_BAR 0 #define IPU7_PCI_PBBAR 4 +static int force_no_probe_ipu7 = !IS_BUILTIN(CONFIG_VIDEO_INTEL_IPU6_IPU7); +module_param(force_no_probe_ipu7, int, 0644); +MODULE_PARM_DESC(force_no_probe_ipu7, "Don't probe ipu7 and ipu7.5 devices"); + struct ipu6_cell_program { u32 magic_number; @@ -612,6 +616,10 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) unsigned long dir; int ret; + if ((id->device == PCI_DEVICE_ID_INTEL_IPU7 || + id->device == PCI_DEVICE_ID_INTEL_IPU7P5) && force_no_probe_ipu7) + return -ENODEV; + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); if (!isp) return -ENOMEM; diff --git a/drivers/staging/media/ipu7/ipu7.c b/drivers/staging/media/ipu7/ipu7.c index 48a35bda4237..88e362c7ae35 100644 --- a/drivers/staging/media/ipu7/ipu7.c +++ b/drivers/staging/media/ipu7/ipu7.c @@ -39,6 +39,10 @@ #define IPU_PCI_BAR 0 #define IPU_PCI_PBBAR 4 +static int force_probe = !IS_BUILTIN(CONFIG_VIDEO_INTEL_IPU6_IPU7); +module_param(force_probe, int, 0644); +MODULE_PARM_DESC(force_probe, "Probe ipu7 and ipu7.5 with this driver instead of ipu6"); + static const unsigned int ipu7_csi_offsets[] = { IPU_CSI_PORT_A_ADDR_OFFSET, IPU_CSI_PORT_B_ADDR_OFFSET, @@ -2414,6 +2418,9 @@ static int ipu7_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) u32 is_es; int ret; + if (!force_probe) + return -ENODEV; + if (!fwnode || fwnode_property_read_u32(fwnode, "is_es", &is_es)) is_es = 0; diff --git a/include/media/ipu6-pci-table.h b/include/media/ipu6-pci-table.h index cd5b941fb96c..cacb8a3170d0 100644 --- a/include/media/ipu6-pci-table.h +++ b/include/media/ipu6-pci-table.h @@ -24,6 +24,8 @@ static const struct pci_device_id ipu6_pci_tbl[] = { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_ADLN) }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_RPLP) }, { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_MTL) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7P5) }, { } }; From 9d6b3fa3f5bdaba65bce21e2e94afce1059b8cf8 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 3 Sep 2026 22:56:05 +0300 Subject: [PATCH 092/596] staging: media: ipu7: Update TODO file Update the TODO file to reflect the plan to drop the ipu7 driver in favour of IPU7 and later support in ipu6 driver. The ipu7 driver may still exist for some time as users switch to the ipu6 driver. Signed-off-by: Sakari Ailus --- drivers/staging/media/ipu7/TODO | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/drivers/staging/media/ipu7/TODO b/drivers/staging/media/ipu7/TODO index dc27bb6463da..57cca4e4033f 100644 --- a/drivers/staging/media/ipu7/TODO +++ b/drivers/staging/media/ipu7/TODO @@ -1,26 +1,2 @@ -This is a list of things that need to be done to get this driver out of the -staging directory. - -- ABI headers cleanup - Cleanup the firmware ABI headers - -- Add metadata capture support - The IPU7 hardware should support metadata capture, but it is not - fully verified with IPU7 firmware ABI so far, need to add the metadata - capture support. - -- Refine CSI2 PHY code - Refine the ipu7-isys-csi2-phy.c, move the hardware specific variant - into structure, clarify and explain the PHY registers to make it more - readable. - -- Work with the common IPU module - Sakari commented much of the driver code is the same than the IPU6 driver. - IPU7 driver is expected to work with the common IPU module in future. - -- Register definition cleanup - Add IPU7 prefix for IPU7 specific registers and related macros. Some - ISYS IO sub-blocks register definitions are offset values from specific - sub-block base, but it is not clear and well suited for driver to use, - need to update the register definitions to make it more clear and - readable. +This driver will be removed soon in favour of supporting IPU7 and later +in the ipu6 driver. From b157413c569b86de5da16479c3917c97c3d14b5d Mon Sep 17 00:00:00 2001 From: Ben Hoff Date: Sat, 1 Aug 2026 01:37:41 -0400 Subject: [PATCH 093/596] media: hws: fix device info register field masks The baseline driver decodes the device version from bits 15:8 and the sub-version from bits 23:16 of HWS_REG_DEVICE_INFO. The current masks decode both fields one byte too low, reading the device version from a byte the baseline driver ignores and the sub-version from the device version field. hws_configure_hardware_capabilities() uses the device version to classify the hardware generation. An incorrect version can classify newer hardware as legacy, select the legacy capture path, and skip HWS_REG_DMA_MAX_SIZE programming. Correct both field masks and update the register layout documentation. Signed-off-by: Ben Hoff Signed-off-by: Hans Verkuil --- drivers/media/pci/hws/hws_pci.c | 4 ++-- drivers/media/pci/hws/hws_reg.h | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/media/pci/hws/hws_pci.c b/drivers/media/pci/hws/hws_pci.c index 30bb7d34465b..75be77d6c197 100644 --- a/drivers/media/pci/hws/hws_pci.c +++ b/drivers/media/pci/hws/hws_pci.c @@ -33,8 +33,8 @@ static unsigned long long hws_elapsed_us(u64 start_ns) } /* register layout inside HWS_REG_DEVICE_INFO */ -#define DEVINFO_VER GENMASK(7, 0) -#define DEVINFO_SUBVER GENMASK(15, 8) +#define DEVINFO_VER GENMASK(15, 8) +#define DEVINFO_SUBVER GENMASK(23, 16) #define DEVINFO_YV12 GENMASK(31, 28) #define DEVINFO_HWKEY GENMASK(27, 24) #define DEVINFO_PORTID GENMASK(25, 24) /* low 2 bits of HW-key */ diff --git a/drivers/media/pci/hws/hws_reg.h b/drivers/media/pci/hws/hws_reg.h index e4fb4af44434..ac607360d628 100644 --- a/drivers/media/pci/hws/hws_reg.h +++ b/drivers/media/pci/hws/hws_reg.h @@ -121,9 +121,10 @@ #define HWS_REG_DEVICE_INFO (CVBS_IN_BASE + 88 * PCIE_BARADDROFSIZE) /* * Reading this 32-bit word returns: - * bits 7:0 = "device version" - * bits 15:8 = "device sub-version" - * bits 23:24 = "HW key / port ID" etc. + * bits 7:0 = unused by the baseline driver + * bits 15:8 = device version + * bits 23:16 = device sub-version + * bits 27:24 = HW key (port ID in bits 25:24) * bits 31:28 = "support YV12" flags */ From 4d28f97400376d4064c69e2c194ce18eeb69f3e5 Mon Sep 17 00:00:00 2001 From: Ben Hoff Date: Fri, 7 Aug 2026 03:46:47 -0400 Subject: [PATCH 094/596] media: hws: report measured colorimetry At 1920x1080, the driver advertises V4L2_COLORSPACE_REC709 with V4L2_YCBCR_ENC_DEFAULT. The default maps to a Rec.709 matrix, but controlled A/B captures using the in-tree and baseline drivers show that both produce YUYV samples using a BT.601 matrix and full-range quantization. The captures differ by at most one code value, whereas a 601-to-709 conversion would move primary luma values by roughly 11 to 33 codes. Set the Y'CbCr encoding explicitly to V4L2_YCBCR_ENC_601 at every resolution. Keep the resolution-based colorspace so HD retains presumed Rec.709 primaries and transfer characteristics while SD continues to use SMPTE 170M. Refresh the colorspace on detected geometry changes so an SD/HD transition does not leave stale metadata. The decoder mode register is undocumented. Stop claiming that value 0x13 selects BT.709; both drivers use that value while producing the measured output. Fixes: ba07fd2f5742 ("media: pci: add AVMatrix HWS capture driver") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Ben Hoff Signed-off-by: Hans Verkuil --- drivers/media/pci/hws/hws.h | 4 ++-- drivers/media/pci/hws/hws_reg.h | 3 ++- drivers/media/pci/hws/hws_v4l2_ioctl.c | 5 +++-- drivers/media/pci/hws/hws_video.c | 4 +++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/media/pci/hws/hws.h b/drivers/media/pci/hws/hws.h index 8fbe1fe27844..d87d52674b69 100644 --- a/drivers/media/pci/hws/hws.h +++ b/drivers/media/pci/hws/hws.h @@ -33,8 +33,8 @@ struct hws_pix_state { u32 sizeimage; /* full frame */ enum v4l2_field field; /* V4L2_FIELD_NONE or INTERLACED */ enum v4l2_colorspace colorspace; /* e.g., REC709 */ - enum v4l2_ycbcr_encoding ycbcr_enc; /* V4L2_YCBCR_ENC_DEFAULT */ - enum v4l2_quantization quantization; /* V4L2_QUANTIZATION_LIM_RANGE */ + enum v4l2_ycbcr_encoding ycbcr_enc; /* V4L2_YCBCR_ENC_601 */ + enum v4l2_quantization quantization; /* V4L2_QUANTIZATION_FULL_RANGE */ enum v4l2_xfer_func xfer_func; /* V4L2_XFER_FUNC_DEFAULT */ bool interlaced; /* cached hardware state */ u32 half_size; /* hardware half-frame size */ diff --git a/drivers/media/pci/hws/hws_reg.h b/drivers/media/pci/hws/hws_reg.h index ac607360d628..ba1d22f4376c 100644 --- a/drivers/media/pci/hws/hws_reg.h +++ b/drivers/media/pci/hws/hws_reg.h @@ -70,7 +70,8 @@ #define HWS_CTL_IRQ_ENABLE_BIT BIT(0) /* Global interrupt enable bit */ /* Write 0x00 to fully reset decoder, * set bit 31=1 to "start run", - * low byte=0x13 selects YUYV/BT.709/etc, + * low byte=0x13 is the vendor baseline capture-mode value; its individual + * bit meanings are not documented, * in ReadChipId() we also write 0x00 and 0x10 here for chip-ID sequencing. */ diff --git a/drivers/media/pci/hws/hws_v4l2_ioctl.c b/drivers/media/pci/hws/hws_v4l2_ioctl.c index 0303e311ee4a..ce396b7225d2 100644 --- a/drivers/media/pci/hws/hws_v4l2_ioctl.c +++ b/drivers/media/pci/hws/hws_v4l2_ioctl.c @@ -511,8 +511,9 @@ static inline void hws_set_colorimetry_state(struct hws_pix_state *p) { bool sd = p->height <= 576; + /* Captured samples use full-range BT.601 Y'CbCr at every resolution. */ p->colorspace = sd ? V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; - p->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + p->ycbcr_enc = V4L2_YCBCR_ENC_601; p->quantization = V4L2_QUANTIZATION_FULL_RANGE; p->xfer_func = V4L2_XFER_FUNC_DEFAULT; } @@ -736,7 +737,7 @@ static inline void hws_set_colorimetry_fmt(struct v4l2_pix_format *p) bool sd = p->height <= 576; p->colorspace = sd ? V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709; - p->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + p->ycbcr_enc = V4L2_YCBCR_ENC_601; p->quantization = V4L2_QUANTIZATION_FULL_RANGE; p->xfer_func = V4L2_XFER_FUNC_DEFAULT; } diff --git a/drivers/media/pci/hws/hws_video.c b/drivers/media/pci/hws/hws_video.c index 18e4bc6901d3..09cfb5a909f9 100644 --- a/drivers/media/pci/hws/hws_video.c +++ b/drivers/media/pci/hws/hws_video.c @@ -327,7 +327,7 @@ int hws_video_init_channel(struct hws_pcie_dev *pdev, int ch) vid->pix.sizeimage = vid->pix.bytesperline * vid->pix.height; vid->pix.field = V4L2_FIELD_NONE; vid->pix.colorspace = V4L2_COLORSPACE_REC709; - vid->pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + vid->pix.ycbcr_enc = V4L2_YCBCR_ENC_601; vid->pix.quantization = V4L2_QUANTIZATION_FULL_RANGE; vid->pix.xfer_func = V4L2_XFER_FUNC_DEFAULT; vid->pix.interlaced = false; @@ -895,6 +895,8 @@ static void hws_video_apply_mode_change(struct hws_pcie_dev *pdx, v->pix.width = w; v->pix.height = h; v->pix.interlaced = interlaced; + v->pix.colorspace = h <= 576 ? V4L2_COLORSPACE_SMPTE170M : + V4L2_COLORSPACE_REC709; hws_set_current_dv_timings(v, w, h, interlaced); v->current_fps = fps; From 1ba17b5a54187e8bb251ff9636ed65c84719b013 Mon Sep 17 00:00:00 2001 From: Hao-Qun Huang Date: Sat, 8 Aug 2026 23:08:46 +0800 Subject: [PATCH 095/596] media: hws: Wait for IRQ handler before returning buffers hws_stop_streaming() disables capture and then collects the active and queued buffers straight away. Clearing cap_active and setting stop_requested only stops a VDONE handler that has not checked them yet; one already running on another CPU has passed those checks and cannot be recalled. That handler snapshots v->active into a local pointer and drops irq_lock before it touches the buffer, so stop_streaming can run in between. Without a next_prepared buffer both paths complete the same buffer, and the second vb2_buffer_done() hits the WARN_ON for a buffer that is no longer active. With a next_prepared buffer the snapshot is the only remaining reference to the old active buffer, so stop_streaming returns without it and vb2 reports "stop_streaming operation is leaving buffer %u in active state" before completing it with an error. Either way the driver breaks the vb2 rule that stop_streaming has to give back every buffer it owns before it returns. Wait for the handler once the hardware is disabled and before the buffers are collected. The live mode change and the channel cleanup paths already do this around the same collect helper. Fixes: ba07fd2f5742 ("media: pci: add AVMatrix HWS capture driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Hao-Qun Huang Reviewed-by: Ben Hoff Tested-by: Ben Hoff Signed-off-by: Hans Verkuil --- drivers/media/pci/hws/hws_video.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/pci/hws/hws_video.c b/drivers/media/pci/hws/hws_video.c index 09cfb5a909f9..dbe0fc2a66b5 100644 --- a/drivers/media/pci/hws/hws_video.c +++ b/drivers/media/pci/hws/hws_video.c @@ -1294,6 +1294,8 @@ static void hws_stop_streaming(struct vb2_queue *q) WRITE_ONCE(v->stop_requested, true); hws_enable_video_capture(v->parent, v->channel_index, false); + if (hws->irq >= 0) + synchronize_irq(hws->irq); /* 2) Collect in-flight + queued under the IRQ lock */ spin_lock_irqsave(&v->irq_lock, flags); From ad5390c35ccf98cec66ef1009351305f25b5a757 Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Wed, 19 Aug 2026 14:33:51 +0800 Subject: [PATCH 096/596] media: pci: hws: only init channels the device actually has hws_probe() initializes max_channels (always 4) control handlers, but register/unregister only walk cur_max_video_ch (1 or 2 on smaller chips). The extra handlers are never freed. Initialize the same number of channels that cleanup uses. Fixes: ba07fd2f5742 ("media: pci: add AVMatrix HWS capture driver") Cc: stable@vger.kernel.org Signed-off-by: Linkai Gong Reviewed-by: Ben Hoff Signed-off-by: Hans Verkuil --- drivers/media/pci/hws/hws_pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/pci/hws/hws_pci.c b/drivers/media/pci/hws/hws_pci.c index 75be77d6c197..f06e60dc2ee6 100644 --- a/drivers/media/pci/hws/hws_pci.c +++ b/drivers/media/pci/hws/hws_pci.c @@ -441,7 +441,7 @@ static int hws_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) hws_init_video_sys(hws, false); /* 5) Init channels (video state, locks, vb2, ctrls) */ - for (i = 0; i < hws->max_channels; i++) { + for (i = 0; i < hws->cur_max_video_ch; i++) { ret = hws_video_init_channel(hws, i); if (ret) { dev_err(&pdev->dev, "video channel init failed (ch=%d)\n", i); From a691c54986412e1fa87f036d2577d5a78a75e658 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 29 Jul 2026 07:26:13 +0000 Subject: [PATCH 097/596] media: cx231xx-audio: drain wq_trigger before audio fini audio_trigger() is deferred work (dev->wq_trigger) armed from snd_cx231xx_capture_trigger() on every PCM START/STOP; it dereferences dev->adev state and may free the URBs via cx231xx_isoc_audio_deinit(). cx231xx_audio_fini() tore down that state without draining wq_trigger, so work armed before or racing fini ran against freed state. Use disable_work_sync() in fini to drain the work and prevent further queueing. Initialize the work, lock and stream_started counter at the top of cx231xx_audio_init(), before any fallible allocation, and clear the partially-built audio state on its error path, so fini is safe even if a later step fails. This issue was found by an in-house static analysis tool. Fixes: 61b04cb24a12 ("[media] cx231xx-audio: fix some locking issues") Cc: stable@vger.kernel.org # v6.10+ Link: https://lore.kernel.org/r/8c7e1294-b906-4636-890c-b64d03b0e1d0@kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Hans Verkuil --- drivers/media/usb/cx231xx/cx231xx-audio.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/media/usb/cx231xx/cx231xx-audio.c b/drivers/media/usb/cx231xx/cx231xx-audio.c index b24ceef497e4..af04fb104cc4 100644 --- a/drivers/media/usb/cx231xx/cx231xx-audio.c +++ b/drivers/media/usb/cx231xx/cx231xx-audio.c @@ -581,12 +581,19 @@ static int cx231xx_audio_init(struct cx231xx *dev) dev_dbg(dev->dev, "probing for cx231xx non standard usbaudio\n"); + /* + * Extension init errors are ignored by the cx231xx core, so fini() + * must be safe even if initialization fails part way through. + */ + spin_lock_init(&adev->slock); + INIT_WORK(&dev->wq_trigger, audio_trigger); + atomic_set(&dev->stream_started, 0); + err = snd_card_new(dev->dev, index[devnr], "Cx231xx Audio", THIS_MODULE, 0, &card); if (err < 0) return err; - spin_lock_init(&adev->slock); err = snd_pcm_new(card, "Cx231xx Audio", 0, 0, 1, &pcm); if (err < 0) goto err_free_card; @@ -601,8 +608,6 @@ static int cx231xx_audio_init(struct cx231xx *dev) strscpy(card->shortname, "Cx231xx Audio", sizeof(card->shortname)); strscpy(card->longname, "Conexant cx231xx Audio", sizeof(card->longname)); - INIT_WORK(&dev->wq_trigger, audio_trigger); - err = snd_card_register(card); if (err < 0) goto err_free_card; @@ -656,8 +661,10 @@ static int cx231xx_audio_init(struct cx231xx *dev) err_free_pkt_size: kfree(adev->alt_max_pkt_size); + adev->alt_max_pkt_size = NULL; err_free_card: snd_card_free(card); + adev->sndcard = NULL; return err; } @@ -674,6 +681,8 @@ static int cx231xx_audio_fini(struct cx231xx *dev) return 0; } + disable_work_sync(&dev->wq_trigger); + if (dev->adev.sndcard) { snd_card_free_when_closed(dev->adev.sndcard); kfree(dev->adev.alt_max_pkt_size); From 6e30287eaf3e41b86dfb86df3b811526693d74b4 Mon Sep 17 00:00:00 2001 From: Andi Shyti Date: Fri, 28 Aug 2026 15:59:50 +0200 Subject: [PATCH 098/596] media: cx231xx: Fix cleanup path on failure If cx231xx_init_v4l2() fails the probe currently jumps to err_init, which only unregisters the V4L2 device. The analog devices and I2C resources are left registered while the driver state can be released. A subsequent driver bind attempts to create the same I2C mux channel links and triggers a warning when the existing "channel-0" link is encountered. The remaining devices may also access released driver state. This produces the following warning can't create symlink to channel 0 WARNING: drivers/i2c/i2c-mux.c:403 at i2c_mux_add_adapter+0xcd8/0xeb0 Call Trace: i2c_mux_add_adapter+0xcdc/0xeb0 cx231xx_dev_init+0x2fb/0x1260 cx231xx_usb_probe+0xc48/0x2140 usb_probe_interface+0x657/0xc70 ... Jump to err_video_alt instead, which performs the full device cleanup before unregistering the V4L2 device. Reported-by: Farhad Alemi Closes: https://github.com/farhad-alemi/public_bug_reports/tree/main/185-warning-in-i2c-mux-add-adapter/ Fixes: 4d2a7d3509f5 ("[media] cx231xx: move analog init code to a separate function") Signed-off-by: Andi Shyti Cc: # v3.17+ Signed-off-by: Hans Verkuil --- drivers/media/usb/cx231xx/cx231xx-cards.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/usb/cx231xx/cx231xx-cards.c b/drivers/media/usb/cx231xx/cx231xx-cards.c index 69b24205bc56..b0941cd2a2d8 100644 --- a/drivers/media/usb/cx231xx/cx231xx-cards.c +++ b/drivers/media/usb/cx231xx/cx231xx-cards.c @@ -1826,7 +1826,7 @@ static int cx231xx_usb_probe(struct usb_interface *interface, retval = cx231xx_init_v4l2(dev, udev, interface, isoc_pipe); if (retval) - goto err_init; + goto err_video_alt; if (dev->current_pcb_config.ts1_source != 0xff) { /* compute alternate max packet sizes for TS1 */ From 8a3435f98175aa611e6cb347738f85d7cd6df792 Mon Sep 17 00:00:00 2001 From: Nguyen Quang Le Kien Date: Mon, 3 Aug 2026 18:40:08 +0800 Subject: [PATCH 099/596] media: pvrusb2: fix URB pending flag leak on invalid endpoint In pvr2_send_request_ex(), when usb_urb_ep_type_check() fails for either the write or read control endpoint, the code returned -EINVAL directly without clearing the corresponding pending flags (ctl_write_pend_flag or ctl_read_pend_flag) or going through the done: cleanup path. This left the pending flags set while the URBs were never actually submitted. On the next call to pvr2_send_request_ex(), the URBs would be filled and submitted while the kernel still considered them active, triggering the WARNING "URB submitted while active" in usb_submit_urb(). Fix this by: - Clearing the pending flag before returning on invalid endpoint - Using goto done instead of direct return to go through proper cleanup - For the read endpoint case, unlinking the write URB if it was already submitted and waiting for its completion before returning Reported-by: syzbot+20fef510634faf733060@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=20fef510634faf733060 Signed-off-by: Nguyen Quang Le Kien Signed-off-by: Hans Verkuil --- drivers/media/usb/pvrusb2/pvrusb2-hdw.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c index 3c270ef00752..3a857e95bc0c 100644 --- a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c +++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c @@ -3669,7 +3669,9 @@ static int pvr2_send_request_ex(struct pvr2_hdw *hdw, pvr2_trace( PVR2_TRACE_ERROR_LEGS, "Invalid write control endpoint"); - return -EINVAL; + hdw->ctl_write_pend_flag = 0; + status = -EINVAL; + goto done; } status = usb_submit_urb(hdw->ctl_write_urb,GFP_KERNEL); if (status < 0) { @@ -3699,7 +3701,13 @@ status); pvr2_trace( PVR2_TRACE_ERROR_LEGS, "Invalid read control endpoint"); - return -EINVAL; + hdw->ctl_read_pend_flag = 0; + status = -EINVAL; + if (hdw->ctl_write_pend_flag) { + usb_unlink_urb(hdw->ctl_write_urb); + wait_for_completion(&hdw->ctl_done); + } + goto done; } status = usb_submit_urb(hdw->ctl_read_urb,GFP_KERNEL); if (status < 0) { From 6802ad4b6b267dda02209b5811edac7825c8a5f6 Mon Sep 17 00:00:00 2001 From: Anuj Bolewar Date: Tue, 4 Aug 2026 10:18:21 +0530 Subject: [PATCH 100/596] media: hackrf: fix use-after-free in hackrf_alloc_urbs() error path hackrf_alloc_urbs() frees the URBs it allocated so far when one allocation fails, but leaves the entries in dev->urb_list[] and dev->urbs_initialized untouched. The caller, hackrf_start_streaming(), then calls hackrf_free_urbs() on the error path, which walks dev->urbs_initialized entries and calls usb_free_urb() a second time on the already-freed URBs, causing a use-after-free (slab-use-after-free Write in usb_free_urb()). Drop the redundant cleanup loop inside hackrf_alloc_urbs() and let hackrf_free_urbs(), which the caller already invokes on error, own the cleanup of the successfully allocated URBs. Reported-by: syzbot+832ce9fa3face1b7d44d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=832ce9fa3face1b7d44d Fixes: 969ec1f6bd92 ("[media] hackrf: HackRF SDR driver") Cc: stable@vger.kernel.org Signed-off-by: Anuj Bolewar Signed-off-by: Hans Verkuil --- drivers/media/usb/hackrf/hackrf.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/media/usb/hackrf/hackrf.c b/drivers/media/usb/hackrf/hackrf.c index a15829a60e88..70fd95f3e97d 100644 --- a/drivers/media/usb/hackrf/hackrf.c +++ b/drivers/media/usb/hackrf/hackrf.c @@ -665,7 +665,7 @@ static int hackrf_free_urbs(struct hackrf_dev *dev) static int hackrf_alloc_urbs(struct hackrf_dev *dev, bool rcv) { - int i, j; + int i; unsigned int pipe; usb_complete_t complete; @@ -681,11 +681,8 @@ static int hackrf_alloc_urbs(struct hackrf_dev *dev, bool rcv) for (i = 0; i < MAX_BULK_BUFS; i++) { dev_dbg(dev->dev, "alloc urb=%d\n", i); dev->urb_list[i] = usb_alloc_urb(0, GFP_KERNEL); - if (!dev->urb_list[i]) { - for (j = 0; j < i; j++) - usb_free_urb(dev->urb_list[j]); + if (!dev->urb_list[i]) return -ENOMEM; - } usb_fill_bulk_urb(dev->urb_list[i], dev->udev, pipe, From 75016a992d9b03f5cc6df1414d1da35840d4feeb Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Tue, 4 Aug 2026 14:47:01 +0800 Subject: [PATCH 101/596] media: cx23885: Fix use-after-free in cx23885_finidev due to race condition In cx23885_v4l2_dev_notify_init, &dev->cx25840_work is bound with cx23885_av_work_handler, and &dev->ir_rx_work and &dev->ir_tx_work are bound with cx23885_ir_rx_work_handler and cx23885_ir_tx_work_handler. cx23885_irq can schedule these works on system_wq when an AV_CORE interrupt is received, and the IR subdevice can also schedule the IR works from its interrupt service routine via the v4l2_device notify callback. If we remove the device, cx23885_finidev makes cleanup and the memory allocated for dev is released by kfree(dev), while the works mentioned above may still be pending or running. The sequence of operations that may lead to a UAF bug is as follows: CPU0 CPU1 | cx23885_irq | schedule_work(&dev->cx25840_work) cx23885_finidev | cx23885_input_fini(dev) | cx23885_ir_fini(dev) | cx23885_shutdown(dev) | free_irq(pci_dev->irq, dev) | pci_disable_device(pci_dev) | cx23885_dev_unregister(dev) | v4l2_device_unregister(v4l2_dev) | kfree(dev) | // dev is freed | | cx23885_av_work_handler | // use dev (use-after-free) Fix it by canceling the works after the IRQ handler that can schedule them has been stopped, and before proceeding with the remaining cleanup in cx23885_finidev. Note that the flush_work() calls in cx23885_input_ir_stop() do not close this race: they only wait for works that are already queued or running at that moment, they do not prevent the IRQ handler, which is still registered at that point, from scheduling the works again afterwards, and they are skipped entirely when dev->sd_ir is NULL. The cancel_work_sync() calls are therefore placed after free_irq(), the only point at which no new work can be scheduled. Fixes: e5514f104d87 ("V4L/DVB: cx23885: Move AV Core irq handling to a work handler") Cc: stable@vger.kernel.org Assisted-by: Codex:deepseek-v4-flash Signed-off-by: Pei Xiao Signed-off-by: Hans Verkuil --- drivers/media/pci/cx23885/cx23885-core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/media/pci/cx23885/cx23885-core.c b/drivers/media/pci/cx23885/cx23885-core.c index 5fb26285e4af..7498091176b7 100644 --- a/drivers/media/pci/cx23885/cx23885-core.c +++ b/drivers/media/pci/cx23885/cx23885-core.c @@ -2246,6 +2246,10 @@ static void cx23885_finidev(struct pci_dev *pci_dev) /* unregister stuff */ free_irq(pci_dev->irq, dev); + cancel_work_sync(&dev->cx25840_work); + cancel_work_sync(&dev->ir_rx_work); + cancel_work_sync(&dev->ir_tx_work); + pci_disable_device(pci_dev); cx23885_dev_unregister(dev); From 521030837bee352515d5ea57cc6a29581db3506e Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Mon, 10 Aug 2026 05:51:28 +0000 Subject: [PATCH 102/596] media: au0828: fix use-after-free in bulk_timeout timer on disconnect start_urb_transfer() arms dev->bulk_timeout, whose callback au0828_bulk_timeout() queues dev->restart_streaming; that work in turn calls start_urb_transfer() and re-arms the timer. au0828_dvb_unregister() cancels restart_streaming on disconnect but does not sync bulk_timeout. au0828_usb_release() then frees dev with kfree(), so a timer still armed or pending at that point can fire after the free and dereference dev through timer_container_of(). Sync the timer with timer_shutdown_sync() before cancel_work_sync(). The timer callback queues the work, so the timer must be stopped first; the shutdown variant is needed because the work re-arms the timer through start_urb_transfer(). The dvb->frontend == NULL early return guards the register-failure path where the timer was never set up. This bug was found by static analysis. Fixes: 53460c53b761 ("[media] au0828: Add timer to restart TS stream if no data arrives on bulk endpoint") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Hans Verkuil --- drivers/media/usb/au0828/au0828-dvb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/usb/au0828/au0828-dvb.c b/drivers/media/usb/au0828/au0828-dvb.c index 9c95b7ceaecd..d3fff47f0edf 100644 --- a/drivers/media/usb/au0828/au0828-dvb.c +++ b/drivers/media/usb/au0828/au0828-dvb.c @@ -537,6 +537,7 @@ void au0828_dvb_unregister(struct au0828_dev *dev) if (dvb->frontend == NULL) return; + timer_shutdown_sync(&dev->bulk_timeout); cancel_work_sync(&dev->restart_streaming); dvb_net_release(&dvb->net); From 3dbbbc3cafa2de377d9b97af4e35b8b6b5282047 Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Wed, 12 Aug 2026 23:12:35 -0400 Subject: [PATCH 103/596] media: gspca: use vb2_video_unregister_device() on disconnect gspca uses vb2_fop_release(), but gspca_disconnect() only unregisters the video device. If the queue owner closes the device after USB disconnect, vb2_fop_release() releases the queue and invokes gspca_stop_streaming() at that point. gspca_stream_off() calls subdriver stop callbacks and usb_set_interface() through gspca_dev->dev. By the time the late close runs, the USB core can have freed the usb_device, resulting in a use-after-free. This was observed as a KASAN use-after-free in sd_stopN(). Use vb2_video_unregister_device() so the queue is released and streaming is stopped synchronously during disconnect, while the usb_device is still valid. Since the helper takes the queue lock, which is usb_lock, call it after dropping that lock. Fixes: f729ef5796d8 ("media: videobuf2-v4l2.c: add vb2_video_unregister_device helper function") Cc: stable@vger.kernel.org # 5.10.x Suggested-by: Hans Verkuil Signed-off-by: Shuangpeng Bai Signed-off-by: Hans Verkuil --- drivers/media/usb/gspca/gspca.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/media/usb/gspca/gspca.c b/drivers/media/usb/gspca/gspca.c index 594d73e50b9f..4185c9047241 100644 --- a/drivers/media/usb/gspca/gspca.c +++ b/drivers/media/usb/gspca/gspca.c @@ -1636,9 +1636,8 @@ void gspca_disconnect(struct usb_interface *intf) #endif v4l2_device_disconnect(&gspca_dev->v4l2_dev); - video_unregister_device(&gspca_dev->vdev); - mutex_unlock(&gspca_dev->usb_lock); + vb2_video_unregister_device(&gspca_dev->vdev); /* (this will call gspca_release() immediately or on last close) */ v4l2_device_put(&gspca_dev->v4l2_dev); From b8dd133d96a40012f4b67fd48d8ddd7306a3e024 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Fri, 14 Aug 2026 21:41:00 +0800 Subject: [PATCH 104/596] media: au0828: Free URBs when starting DVB streaming fails start_urb_transfer() stores each allocated URB in dev->urbs[], but sets urb_streaming only after all URBs have been submitted. If a later URB or transfer buffer allocation fails, earlier entries are left allocated. A submission failure calls stop_urb_transfer(), but that function returns immediately while urb_streaming is false, leaving both submitted and unsubmitted URBs behind. Make stop_urb_transfer() release every populated slot regardless of the streaming flag and clear each slot after release. Route all start errors through this cleanup. usb_kill_urb() safely handles both submitted and unsubmitted URBs, while the existing preallocation check preserves the lifetime of shared transfer buffers. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 265a6510629a ("V4L/DVB (7621): Add support for Hauppauge HVR950Q/HVR850/FusioHDTV7-USB") Cc: stable@vger.kernel.org Signed-off-by: Ruoyu Wang Signed-off-by: Hans Verkuil --- drivers/media/usb/au0828/au0828-dvb.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/media/usb/au0828/au0828-dvb.c b/drivers/media/usb/au0828/au0828-dvb.c index d3fff47f0edf..601a12e122f1 100644 --- a/drivers/media/usb/au0828/au0828-dvb.c +++ b/drivers/media/usb/au0828/au0828-dvb.c @@ -163,9 +163,6 @@ static int stop_urb_transfer(struct au0828_dev *dev) dprintk(2, "%s()\n", __func__); - if (!dev->urb_streaming) - return 0; - if (dev->bulk_timeout_running == 1) { dev->bulk_timeout_running = 0; timer_delete(&dev->bulk_timeout); @@ -179,6 +176,7 @@ static int stop_urb_transfer(struct au0828_dev *dev) kfree(dev->urbs[i]->transfer_buffer); usb_free_urb(dev->urbs[i]); + dev->urbs[i] = NULL; } } @@ -200,8 +198,10 @@ static int start_urb_transfer(struct au0828_dev *dev) for (i = 0; i < URB_COUNT; i++) { dev->urbs[i] = usb_alloc_urb(0, GFP_KERNEL); - if (!dev->urbs[i]) - return -ENOMEM; + if (!dev->urbs[i]) { + ret = -ENOMEM; + goto err; + } purb = dev->urbs[i]; @@ -217,7 +217,7 @@ static int start_urb_transfer(struct au0828_dev *dev) ret = -ENOMEM; pr_err("%s: failed big buffer allocation, err = %d\n", __func__, ret); - return ret; + goto err; } purb->status = -EINPROGRESS; @@ -235,10 +235,9 @@ static int start_urb_transfer(struct au0828_dev *dev) for (i = 0; i < URB_COUNT; i++) { ret = usb_submit_urb(dev->urbs[i], GFP_ATOMIC); if (ret != 0) { - stop_urb_transfer(dev); pr_err("%s: failed urb submission, err = %d\n", __func__, ret); - return ret; + goto err; } } @@ -249,6 +248,10 @@ static int start_urb_transfer(struct au0828_dev *dev) dev->bulk_timeout_running = 1; return 0; + +err: + stop_urb_transfer(dev); + return ret; } static void au0828_start_transport(struct au0828_dev *dev) From cb705f75d03e946a4514488beb9e3bac9bcdb2e6 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Mon, 17 Aug 2026 14:09:49 +0000 Subject: [PATCH 105/596] media: usbtv: fix null-pointer dereference on disconnect A null-pointer dereference can occur in usb_make_path() when called from usbtv_querycap() (and other ioctl handlers) during device disconnection. Oops: general protection fault, probably for non-canonical address 0xdffffc000000000a: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000050-0x0000000000000057] RIP: 0010:usb_make_path include/linux/usb.h:985 [inline] RIP: 0010:usbtv_querycap+0x9c/0x100 drivers/media/usb/usbtv/usbtv-video.c:612 ... Call Trace: v4l_querycap+0x236/0x470 drivers/media/v4l2-core/v4l2-ioctl.c:1106 __video_do_ioctl+0x8af/0xc70 drivers/media/v4l2-core/v4l2-ioctl.c:3133 video_usercopy+0x860/0x1430 drivers/media/v4l2-core/v4l2-ioctl.c:3475 v4l2_ioctl+0x18d/0x1e0 drivers/media/v4l2-core/v4l2-dev.c:366 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94 This happens due to a race condition between the USB device disconnect routine (usbtv_disconnect()) and V4L2 ioctls. When the device is disconnected, usbtv_disconnect() sets usbtv->udev = NULL without holding the usbtv->v4l2_lock mutex. Concurrently, an ioctl handler like usbtv_querycap() can be executing under the v4l2_lock and attempt to use usbtv->udev, leading to a crash. To fix this, move the usbtv->udev = NULL assignment into usbtv_video_free() and protect it with mutex_lock(&usbtv->v4l2_lock). This ensures that no non-queue ioctl handler can run concurrently with the pointer being nullified. If an ioctl is already running, the mutex will block the disconnect path until the ioctl finishes. If an ioctl is blocked waiting for the lock, it will acquire it after the disconnect routine releases it, but will then check video_is_registered() (which was cleared by vb2_video_unregister_device()) and safely return -ENODEV. Acquiring v4l2_lock after vb2_video_unregister_device() avoids reintroducing a historical deadlock issue, and usbtv_audio_free() remains safe as it is called before usbtv_video_free(). Fixes: 65e6a2773d65 ("media: usbtv: Remove useless locks in usbtv_video_free()") Cc: stable@vger.kernel.org Assisted-by: Gemini:gemini-3.6-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+37a57a84893052ab6071@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=37a57a84893052ab6071 Link: https://syzkaller.appspot.com/ai_job?id=5abbe65a-5853-4bc2-9135-5c75f88d51ed Signed-off-by: Aleksandr Nogikh Signed-off-by: Hans Verkuil --- drivers/media/usb/usbtv/usbtv-core.c | 2 -- drivers/media/usb/usbtv/usbtv-video.c | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/media/usb/usbtv/usbtv-core.c b/drivers/media/usb/usbtv/usbtv-core.c index 4f10f6613bc4..89c3424e7685 100644 --- a/drivers/media/usb/usbtv/usbtv-core.c +++ b/drivers/media/usb/usbtv/usbtv-core.c @@ -139,8 +139,6 @@ static void usbtv_disconnect(struct usb_interface *intf) usbtv_audio_free(usbtv); usbtv_video_free(usbtv); - usbtv->udev = NULL; - /* the usbtv structure will be deallocated when v4l2 will be done using it */ v4l2_device_put(&usbtv->v4l2_dev); diff --git a/drivers/media/usb/usbtv/usbtv-video.c b/drivers/media/usb/usbtv/usbtv-video.c index 92bc7a2509c3..2a91af8d1e30 100644 --- a/drivers/media/usb/usbtv/usbtv-video.c +++ b/drivers/media/usb/usbtv/usbtv-video.c @@ -966,5 +966,9 @@ void usbtv_video_free(struct usbtv *usbtv) vb2_video_unregister_device(&usbtv->vdev); v4l2_device_disconnect(&usbtv->v4l2_dev); + mutex_lock(&usbtv->v4l2_lock); + usbtv->udev = NULL; + mutex_unlock(&usbtv->v4l2_lock); + v4l2_device_put(&usbtv->v4l2_dev); } From 449936b6e627a6adfadfd7b3cfc5fe980bae49a3 Mon Sep 17 00:00:00 2001 From: Triet Hoang Date: Fri, 21 Aug 2026 13:27:37 +0700 Subject: [PATCH 106/596] media: bttv: Convert to DEFINE_SIMPLE_DEV_PM_OPS() Convert the deprecated SIMPLE_DEV_PM_OPS to DEFINE_SIMPLE_DEV_PM_OPS and pm_sleep_ptr(). This lets us drop the __maybe_unused annotations from its suspend and resume callbacks, also reduces kernel size in case CONFIG_PM or CONFIG_PM_SLEEP is disabled. Signed-off-by: Triet Hoang Signed-off-by: Hans Verkuil --- drivers/media/pci/bt8xx/bttv-driver.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/pci/bt8xx/bttv-driver.c b/drivers/media/pci/bt8xx/bttv-driver.c index c631b8bbd386..8b9ca59f9434 100644 --- a/drivers/media/pci/bt8xx/bttv-driver.c +++ b/drivers/media/pci/bt8xx/bttv-driver.c @@ -3504,7 +3504,7 @@ static void bttv_remove(struct pci_dev *pci_dev) return; } -static int __maybe_unused bttv_suspend(struct device *dev) +static int bttv_suspend(struct device *dev) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); struct bttv *btv = to_bttv(v4l2_dev); @@ -3535,7 +3535,7 @@ static int __maybe_unused bttv_suspend(struct device *dev) return 0; } -static int __maybe_unused bttv_resume(struct device *dev) +static int bttv_resume(struct device *dev) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); struct bttv *btv = to_bttv(v4l2_dev); @@ -3573,7 +3573,7 @@ static const struct pci_device_id bttv_pci_tbl[] = { MODULE_DEVICE_TABLE(pci, bttv_pci_tbl); -static SIMPLE_DEV_PM_OPS(bttv_pm_ops, +static DEFINE_SIMPLE_DEV_PM_OPS(bttv_pm_ops, bttv_suspend, bttv_resume); @@ -3582,7 +3582,7 @@ static struct pci_driver bttv_pci_driver = { .id_table = bttv_pci_tbl, .probe = bttv_probe, .remove = bttv_remove, - .driver.pm = &bttv_pm_ops, + .driver.pm = pm_sleep_ptr(&bttv_pm_ops), }; static int __init bttv_init_module(void) From f7cacd7775d634c9ced6d199c5e23a011373b103 Mon Sep 17 00:00:00 2001 From: Triet Hoang Date: Fri, 21 Aug 2026 13:38:29 +0700 Subject: [PATCH 107/596] media: cx88: Convert to DEFINE_SIMPLE_DEV_PM_OPS() Convert the deprecated SIMPLE_DEV_PM_OPS to DEFINE_SIMPLE_DEV_PM_OPS and pm_sleep_ptr(). This lets us drop the __maybe_unused annotations from its suspend and resume callbacks, also reduces kernel size in case CONFIG_PM or CONFIG_PM_SLEEP is disabled. Signed-off-by: Triet Hoang Signed-off-by: Hans Verkuil --- drivers/media/pci/cx88/cx88-video.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/pci/cx88/cx88-video.c b/drivers/media/pci/cx88/cx88-video.c index eaa46a2f92e7..f8f2843495f3 100644 --- a/drivers/media/pci/cx88/cx88-video.c +++ b/drivers/media/pci/cx88/cx88-video.c @@ -1549,7 +1549,7 @@ static void cx8800_finidev(struct pci_dev *pci_dev) kfree(dev); } -static int __maybe_unused cx8800_suspend(struct device *dev_d) +static int cx8800_suspend(struct device *dev_d) { struct cx8800_dev *dev = dev_get_drvdata(dev_d); struct cx88_core *core = dev->core; @@ -1576,7 +1576,7 @@ static int __maybe_unused cx8800_suspend(struct device *dev_d) return 0; } -static int __maybe_unused cx8800_resume(struct device *dev_d) +static int cx8800_resume(struct device *dev_d) { struct cx8800_dev *dev = dev_get_drvdata(dev_d); struct cx88_core *core = dev->core; @@ -1617,14 +1617,14 @@ static const struct pci_device_id cx8800_pci_tbl[] = { }; MODULE_DEVICE_TABLE(pci, cx8800_pci_tbl); -static SIMPLE_DEV_PM_OPS(cx8800_pm_ops, cx8800_suspend, cx8800_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(cx8800_pm_ops, cx8800_suspend, cx8800_resume); static struct pci_driver cx8800_pci_driver = { .name = "cx8800", .id_table = cx8800_pci_tbl, .probe = cx8800_initdev, .remove = cx8800_finidev, - .driver.pm = &cx8800_pm_ops, + .driver.pm = pm_sleep_ptr(&cx8800_pm_ops), }; module_pci_driver(cx8800_pci_driver); From 631f636411e420d4e0a8c6f9d3242a3419fecdfe Mon Sep 17 00:00:00 2001 From: Triet Hoang Date: Fri, 21 Aug 2026 13:40:43 +0700 Subject: [PATCH 108/596] media: saa7134: Convert to DEFINE_SIMPLE_DEV_PM_OPS() Convert the deprecated SIMPLE_DEV_PM_OPS to DEFINE_SIMPLE_DEV_PM_OPS and pm_sleep_ptr(). Signed-off-by: Triet Hoang Signed-off-by: Hans Verkuil --- drivers/media/pci/saa7134/saa7134-core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/pci/saa7134/saa7134-core.c b/drivers/media/pci/saa7134/saa7134-core.c index 2f5b258d682b..507ad23776e0 100644 --- a/drivers/media/pci/saa7134/saa7134-core.c +++ b/drivers/media/pci/saa7134/saa7134-core.c @@ -1368,7 +1368,7 @@ static int __maybe_unused saa7134_buffer_requeue(struct saa7134_dev *dev, return 0; } -static int __maybe_unused saa7134_suspend(struct device *dev_d) +static int saa7134_suspend(struct device *dev_d) { struct pci_dev *pci_dev = to_pci_dev(dev_d); struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); @@ -1400,7 +1400,7 @@ static int __maybe_unused saa7134_suspend(struct device *dev_d) return 0; } -static int __maybe_unused saa7134_resume(struct device *dev_d) +static int saa7134_resume(struct device *dev_d) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev_d); struct saa7134_dev *dev = container_of(v4l2_dev, struct saa7134_dev, v4l2_dev); @@ -1484,14 +1484,14 @@ EXPORT_SYMBOL(saa7134_ts_unregister); /* ----------------------------------------------------------- */ -static SIMPLE_DEV_PM_OPS(saa7134_pm_ops, saa7134_suspend, saa7134_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(saa7134_pm_ops, saa7134_suspend, saa7134_resume); static struct pci_driver saa7134_pci_driver = { .name = "saa7134", .id_table = saa7134_pci_tbl, .probe = saa7134_initdev, .remove = saa7134_finidev, - .driver.pm = &saa7134_pm_ops, + .driver.pm = pm_sleep_ptr(&saa7134_pm_ops), }; static int __init saa7134_init(void) From 576457a7e9910f9402765606df8ca6dffea01b59 Mon Sep 17 00:00:00 2001 From: Triet Hoang Date: Fri, 21 Aug 2026 13:43:36 +0700 Subject: [PATCH 109/596] media: tw68: Convert to DEFINE_SIMPLE_DEV_PM_OPS() Convert the deprecated SIMPLE_DEV_PM_OPS to DEFINE_SIMPLE_DEV_PM_OPS and pm_sleep_ptr(). This lets us drop the __maybe_unused annotations from its suspend and resume callbacks, also reduces kernel size in case CONFIG_PM or CONFIG_PM_SLEEP is disabled. Signed-off-by: Triet Hoang Signed-off-by: Hans Verkuil --- drivers/media/pci/tw68/tw68-core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/pci/tw68/tw68-core.c b/drivers/media/pci/tw68/tw68-core.c index 509d7ddec150..a18107ddb0ac 100644 --- a/drivers/media/pci/tw68/tw68-core.c +++ b/drivers/media/pci/tw68/tw68-core.c @@ -359,7 +359,7 @@ static void tw68_finidev(struct pci_dev *pci_dev) v4l2_device_unregister(&dev->v4l2_dev); } -static int __maybe_unused tw68_suspend(struct device *dev_d) +static int tw68_suspend(struct device *dev_d) { struct pci_dev *pci_dev = to_pci_dev(dev_d); struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); @@ -377,7 +377,7 @@ static int __maybe_unused tw68_suspend(struct device *dev_d) return 0; } -static int __maybe_unused tw68_resume(struct device *dev_d) +static int tw68_resume(struct device *dev_d) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev_d); struct tw68_dev *dev = container_of(v4l2_dev, @@ -405,14 +405,14 @@ static int __maybe_unused tw68_resume(struct device *dev_d) /* ----------------------------------------------------------- */ -static SIMPLE_DEV_PM_OPS(tw68_pm_ops, tw68_suspend, tw68_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(tw68_pm_ops, tw68_suspend, tw68_resume); static struct pci_driver tw68_pci_driver = { .name = "tw68", .id_table = tw68_pci_tbl, .probe = tw68_initdev, .remove = tw68_finidev, - .driver.pm = &tw68_pm_ops, + .driver.pm = pm_sleep_ptr(&tw68_pm_ops), }; module_pci_driver(tw68_pci_driver); From cdee6cae0c64b45e14b80cf9be11aa237d2ce2d2 Mon Sep 17 00:00:00 2001 From: Zain Aboobacker Date: Mon, 24 Aug 2026 20:22:40 -0400 Subject: [PATCH 110/596] media: saa7164: remove register dump debugging helper saa7164_dumpregs() dumps device register contents during initialization for debugging purposes. The helper has a single caller and is explicitly marked for removal. Remove the call, helper, and declaration. Signed-off-by: Zain Aboobacker Signed-off-by: Hans Verkuil --- drivers/media/pci/saa7164/saa7164-core.c | 30 ------------------------ drivers/media/pci/saa7164/saa7164.h | 1 - 2 files changed, 31 deletions(-) diff --git a/drivers/media/pci/saa7164/saa7164-core.c b/drivers/media/pci/saa7164/saa7164-core.c index ac5eb6b923e2..8c587d31ffec 100644 --- a/drivers/media/pci/saa7164/saa7164-core.c +++ b/drivers/media/pci/saa7164/saa7164-core.c @@ -742,35 +742,6 @@ u32 saa7164_getcurrentfirmwareversion(struct saa7164_dev *dev) return reg; } -/* TODO: Debugging func, remove */ -void saa7164_dumpregs(struct saa7164_dev *dev, u32 addr) -{ - int i; - - dprintk(1, "--------------------> 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); - - for (i = 0; i < 0x100; i += 16) - dprintk(1, "region0[0x%08x] = %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", - i, - (u8)saa7164_readb(addr + i + 0), - (u8)saa7164_readb(addr + i + 1), - (u8)saa7164_readb(addr + i + 2), - (u8)saa7164_readb(addr + i + 3), - (u8)saa7164_readb(addr + i + 4), - (u8)saa7164_readb(addr + i + 5), - (u8)saa7164_readb(addr + i + 6), - (u8)saa7164_readb(addr + i + 7), - (u8)saa7164_readb(addr + i + 8), - (u8)saa7164_readb(addr + i + 9), - (u8)saa7164_readb(addr + i + 10), - (u8)saa7164_readb(addr + i + 11), - (u8)saa7164_readb(addr + i + 12), - (u8)saa7164_readb(addr + i + 13), - (u8)saa7164_readb(addr + i + 14), - (u8)saa7164_readb(addr + i + 15) - ); -} - static void saa7164_dump_hwdesc(struct saa7164_dev *dev) { dprintk(1, "@0x%p hwdesc sizeof(struct tmComResHWDescr) = %d bytes\n", @@ -1344,7 +1315,6 @@ static int saa7164_initdev(struct pci_dev *pci_dev, } saa7164_get_descriptors(dev); - saa7164_dumpregs(dev, 0); saa7164_getcurrentfirmwareversion(dev); saa7164_getfirmwarestatus(dev); err = saa7164_bus_setup(dev); diff --git a/drivers/media/pci/saa7164/saa7164.h b/drivers/media/pci/saa7164/saa7164.h index 94e987e7b5e5..53ef9c3343ba 100644 --- a/drivers/media/pci/saa7164/saa7164.h +++ b/drivers/media/pci/saa7164/saa7164.h @@ -490,7 +490,6 @@ extern unsigned int vbi_buffers; /* ----------------------------------------------------------- */ /* saa7164-core.c */ -void saa7164_dumpregs(struct saa7164_dev *dev, u32 addr); void saa7164_getfirmwarestatus(struct saa7164_dev *dev); u32 saa7164_getcurrentfirmwareversion(struct saa7164_dev *dev); void saa7164_histogram_update(struct saa7164_histogram *hg, u32 val); From cb37e83f1bbdf0964ed0a9524ce45a0694f98205 Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Tue, 25 Aug 2026 10:26:16 +0800 Subject: [PATCH 111/596] media: i2c: tc358743: add missing color-space strings VI_STATUS3 S_V_COLOR is four bits, so the index after the shift is 0-15. input_color_space[] only has 14 entries. Add the two reserved values so log_status() cannot walk off the table. Fixes: d32d98642de6 ("[media] Driver for Toshiba TC358743 HDMI to CSI-2 bridge") Cc: stable@vger.kernel.org Signed-off-by: Linkai Gong Signed-off-by: Hans Verkuil --- drivers/media/i2c/tc358743.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index fbd38bbfee03..ce75812cbf72 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -1373,7 +1373,8 @@ static int tc358743_log_status(struct v4l2_subdev *sd) static const char * const input_color_space[] = { "RGB", "YCbCr 601", "opRGB", "YCbCr 709", "NA (4)", "xvYCC 601", "NA(6)", "xvYCC 709", "NA(8)", "sYCC601", - "NA(10)", "NA(11)", "NA(12)", "opYCC 601"}; + "NA(10)", "NA(11)", "NA(12)", "opYCC 601", "NA(14)", + "NA(15)"}; v4l2_info(sd, "-----Chip status-----\n"); v4l2_info(sd, "Chip ID: 0x%02x\n", From 53637d9250df031946e17894dea149cefc4f9b92 Mon Sep 17 00:00:00 2001 From: Bradford Love Date: Tue, 25 Aug 2026 14:16:39 -0500 Subject: [PATCH 112/596] media: em28xx: Fix PAL analog TV colours Red/Blue chroma are swapped on PAL analog TV due to register 0x7A07 missing in the configuration block for PAL. Copying in the setting for composite fixes the issue and everything decodes properly. Signed-off-by: Bradford Love Signed-off-by: Hans Verkuil --- drivers/media/usb/em28xx/em28xx-video.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c index c418add65bb5..864bab86ef69 100644 --- a/drivers/media/usb/em28xx/em28xx-video.c +++ b/drivers/media/usb/em28xx/em28xx-video.c @@ -431,6 +431,7 @@ static void em2828X_decoder_set_std(struct em28xx *dev, v4l2_std_id norm) } else if (INPUT(dev->ctl_input)->vmux == EM2828X_TELEVISION) { em28xx_write_reg(dev, 0x7A00, 0x32); em28xx_write_reg(dev, 0x7A03, 0x09); + em28xx_write_reg(dev, 0x7A07, 0x2f); em28xx_write_reg(dev, 0x7A30, 0x2a); em28xx_write_reg(dev, 0x7A80, 0x03); em28xx_write_reg(dev, 0x7A20, 0x35); From 2bea8516a561685a9aef2e3a3584c871e28df289 Mon Sep 17 00:00:00 2001 From: Bhoomika Hardwani Date: Wed, 26 Aug 2026 16:42:45 +0530 Subject: [PATCH 113/596] media: radio: si4713: replace msleep() with usleep_range() for delays less than 20ms msleep() isn't precise for delays under 20ms and relies on jiffies, which can lead to sleeps longer than requested, depending on the system's HZ configurations. Replace msleep(3) with usleep_range(3000, 4000) to use high-resolution timers, without forcing the system to wait unnecessarily. Signed-off-by: Bhoomika Hardwani Signed-off-by: Hans Verkuil --- drivers/media/radio/si4713/radio-usb-si4713.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/radio/si4713/radio-usb-si4713.c b/drivers/media/radio/si4713/radio-usb-si4713.c index 6e6764143e21..62877980fc04 100644 --- a/drivers/media/radio/si4713/radio-usb-si4713.c +++ b/drivers/media/radio/si4713/radio-usb-si4713.c @@ -192,7 +192,7 @@ static int si4713_send_startup_command(struct si4713_usb_device *radio) } if (time_is_before_jiffies(until_jiffies)) return -EIO; - msleep(3); + usleep_range(3000, 4000); } return retval; @@ -339,7 +339,7 @@ static int si4713_i2c_read(struct si4713_usb_device *radio, char *data, int len) data[0] = 0; return 0; } - msleep(3); + usleep_range(3000, 4000); } } From 816c4628b23e8c3d925978433f4dd1a13620c01b Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 22:59:10 +0800 Subject: [PATCH 114/596] media: go7007: bound EZ-USB I2C transfers to the usb_buf size go7007_usb_i2c_master_xfer() copies msgs[i].len bytes into go->usb_buf, a 16-byte buffer embedded in struct go7007, without any length check. The adapter declares no transfer limits, so an SMBus block transfer issued through /dev/i2c-N can write up to 34 bytes into it and corrupt the struct fields that follow the buffer. Bound the transfer size with i2c_adapter_quirks so the I2C core rejects oversized messages before they reach the driver. Fixes: 7955f03d18d1 ("[media] go7007: move out of staging into drivers/media/usb.") Cc: stable@vger.kernel.org Signed-off-by: Shengzhuo Wei Assisted-by: GLM:5.3 Signed-off-by: Hans Verkuil --- drivers/media/usb/go7007/go7007-usb.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/media/usb/go7007/go7007-usb.c b/drivers/media/usb/go7007/go7007-usb.c index c0cb92fa6ab9..867548d655b3 100644 --- a/drivers/media/usb/go7007/go7007-usb.c +++ b/drivers/media/usb/go7007/go7007-usb.c @@ -1029,10 +1029,16 @@ static const struct i2c_algorithm go7007_usb_algo = { .functionality = go7007_usb_functionality, }; +static const struct i2c_adapter_quirks go7007_usb_quirks = { + .max_write_len = 12, + .max_read_len = 15, +}; + static struct i2c_adapter go7007_usb_adap_templ = { .owner = THIS_MODULE, .name = "WIS GO7007SB EZ-USB", .algo = &go7007_usb_algo, + .quirks = &go7007_usb_quirks, }; /********************* USB add/remove functions *********************/ From 702eb8bc6d5fc2d921de8765da13380e6b880f49 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 27 Aug 2026 12:03:07 -0400 Subject: [PATCH 115/596] media: dt-bindings: tc358743: Convert to YAML Convert the Toshiba TC358743 HDMI-RX to MIPI CSI2-TX bridge device tree binding from txt to YAML format. Signed-off-by: Frank Li Reviewed-by: Rob Herring (Arm) Signed-off-by: Hans Verkuil --- .../bindings/media/i2c/toshiba,tc358743.txt | 48 ---------- .../bindings/media/i2c/toshiba,tc358743.yaml | 93 +++++++++++++++++++ MAINTAINERS | 2 +- 3 files changed, 94 insertions(+), 49 deletions(-) delete mode 100644 Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.txt create mode 100644 Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.txt b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.txt deleted file mode 100644 index 59102edcf01e..000000000000 --- a/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.txt +++ /dev/null @@ -1,48 +0,0 @@ -* Toshiba TC358743 HDMI-RX to MIPI CSI2-TX Bridge - -The Toshiba TC358743 HDMI-RX to MIPI CSI2-TX (H2C) is a bridge that converts -a HDMI stream to MIPI CSI-2 TX. It is programmable through I2C. - -Required Properties: - -- compatible: value should be "toshiba,tc358743" -- clocks, clock-names: should contain a phandle link to the reference clock - source, the clock input is named "refclk". - -Optional Properties: - -- reset-gpios: gpio phandle GPIO connected to the reset pin -- interrupts: GPIO connected to the interrupt pin -- data-lanes: should be <1 2 3 4> for four-lane operation, - or <1 2> for two-lane operation -- clock-lanes: should be <0> -- clock-noncontinuous: Presence of this boolean property decides whether the - MIPI CSI-2 clock is continuous or non-continuous. -- link-frequencies: List of allowed link frequencies in Hz. Each frequency is - expressed as a 64-bit big-endian integer. The frequency - is half of the bps per lane due to DDR transmission. - -For further information on the MIPI CSI-2 endpoint node properties, see -Documentation/devicetree/bindings/media/video-interfaces.txt. - -Example: - - tc358743@f { - compatible = "toshiba,tc358743"; - reg = <0x0f>; - clocks = <&hdmi_osc>; - clock-names = "refclk"; - reset-gpios = <&gpio6 9 GPIO_ACTIVE_LOW>; - interrupt-parent = <&gpio2>; - interrupts = <5 IRQ_TYPE_LEVEL_HIGH>; - - port { - tc358743_out: endpoint { - remote-endpoint = <&mipi_csi2_in>; - data-lanes = <1 2 3 4>; - clock-lanes = <0>; - clock-noncontinuous; - link-frequencies = /bits/ 64 <297000000>; - }; - }; - }; diff --git a/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.yaml b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.yaml new file mode 100644 index 000000000000..29dd1d58f766 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/toshiba,tc358743.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Toshiba TC358743 HDMI-RX to MIPI CSI2-TX Bridge + +maintainers: + - Hans Verkuil + +description: + The Toshiba TC358743 HDMI-RX to MIPI CSI2-TX (H2C) is a bridge that converts + a HDMI stream to MIPI CSI-2 TX. It is programmable through I2C. + +properties: + compatible: + const: toshiba,tc358743 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + const: refclk + + reset-gpios: + maxItems: 1 + + interrupts: + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + oneOf: + - items: + - const: 1 + - const: 2 + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + +required: + - compatible + - reg + - clocks + - clock-names + - port + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + bridge@f { + compatible = "toshiba,tc358743"; + reg = <0x0f>; + clocks = <&hdmi_osc>; + clock-names = "refclk"; + reset-gpios = <&gpio6 9 GPIO_ACTIVE_LOW>; + interrupt-parent = <&gpio2>; + interrupts = <5 IRQ_TYPE_LEVEL_HIGH>; + + port { + endpoint { + remote-endpoint = <&mipi_csi2_in>; + data-lanes = <1 2 3 4>; + clock-lanes = <0>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <297000000>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index e12dc3ca57ab..308c67b8b0cd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27688,7 +27688,7 @@ TOSHIBA TC358743 DRIVER M: Hans Verkuil L: linux-media@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.txt +F: Documentation/devicetree/bindings/media/i2c/toshiba,tc358743.yaml F: drivers/media/i2c/tc358743* F: include/media/i2c/tc358743.h From e07c75a6bc4adf1ed09ede31756261e2c6bdede5 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 30 Aug 2026 22:30:02 +0800 Subject: [PATCH 116/596] media: saa7146: Initialize interrupt lock before requesting IRQ The saa7146 interrupt handler can call SAA7146_IER_DISABLE(), which serializes register access with dev->int_slock. request_irq() allows the shared handler to run before saa7146_init_one() reaches the current lock initialization block. Initialize dev->int_slock before requesting the interrupt so every handler path sees an initialized lock. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Signed-off-by: Hans Verkuil --- drivers/media/common/saa7146/saa7146_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/common/saa7146/saa7146_core.c b/drivers/media/common/saa7146/saa7146_core.c index c297d019f2b3..70d6ca00513f 100644 --- a/drivers/media/common/saa7146/saa7146_core.c +++ b/drivers/media/common/saa7146/saa7146_core.c @@ -340,6 +340,8 @@ static int saa7146_init_one(struct pci_dev *pci, const struct pci_device_id *ent goto out; } + spin_lock_init(&dev->int_slock); + /* create a nice device name */ sprintf(dev->name, "saa7146 (%d)", saa7146_num); @@ -425,7 +427,6 @@ static int saa7146_init_one(struct pci_dev *pci, const struct pci_device_id *ent dev->ext = ext; mutex_init(&dev->v4l2_lock); - spin_lock_init(&dev->int_slock); spin_lock_init(&dev->slock); mutex_init(&dev->i2c_lock); From 50f5fed44d8e24081e8feed5154746fe193f11b8 Mon Sep 17 00:00:00 2001 From: Younho Choi Date: Tue, 26 May 2026 21:22:05 +0900 Subject: [PATCH 117/596] media: vim2m: keep transaction buffer count stable while streaming V4L2_CID_TRANS_NUM_BUFS controls how many buffer pairs a vim2m mem2mem job processes before the job is completed. The driver stores the value in ctx->translen and device_work() uses it later to decide whether the current transaction should continue. Letting userspace change this control while streaming is active can make a queued job observe a different transaction length than the one it started with. That leaves the transaction state inconsistent with the buffers currently queued for the job. Grab the transaction buffer count control while either queue is streaming, and release it only after both queues have stopped streaming. The V4L2 control framework then rejects changes with -EBUSY while the value is in use, while still allowing userspace to configure the value before streaming starts. Keep the control handler alive until after v4l2_m2m_ctx_release(), since releasing the mem2mem context can call stop_streaming(), which now ungrabs the control. Fixes: 96d8eab5d0a1 ("V4L/DVB: [v5,2/2] v4l: Add a mem-to-mem videobuf framework test device") Cc: stable@vger.kernel.org Signed-off-by: Younho Choi Reviewed-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/vim2m.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/media/test-drivers/vim2m.c b/drivers/media/test-drivers/vim2m.c index bb2dd11eef0e..f4a2c4083829 100644 --- a/drivers/media/test-drivers/vim2m.c +++ b/drivers/media/test-drivers/vim2m.c @@ -205,6 +205,7 @@ struct vim2m_ctx { struct vim2m_dev *dev; struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *trans_num_bufs_ctrl; /* Processed buffers in this transaction */ u8 num_processed; @@ -1258,9 +1259,27 @@ static int vim2m_start_streaming(struct vb2_queue *q, unsigned int count) ctx->aborting = 0; q_data->sequence = 0; + v4l2_ctrl_grab(ctx->trans_num_bufs_ctrl, true); + return 0; } +static bool vim2m_other_queue_is_streaming(struct vim2m_ctx *ctx, + struct vb2_queue *q) +{ + struct vb2_queue *other_vq; + + if (!ctx->fh.m2m_ctx) + return false; + + if (V4L2_TYPE_IS_OUTPUT(q->type)) + other_vq = v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx); + else + other_vq = v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx); + + return vb2_is_streaming(other_vq); +} + static void vim2m_stop_streaming(struct vb2_queue *q) { struct vim2m_ctx *ctx = vb2_get_drv_priv(q); @@ -1274,11 +1293,14 @@ static void vim2m_stop_streaming(struct vb2_queue *q) else vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); if (!vbuf) - return; + break; v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req, &ctx->hdl); v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); } + + if (!vim2m_other_queue_is_streaming(ctx, q)) + v4l2_ctrl_grab(ctx->trans_num_bufs_ctrl, false); } static void vim2m_buf_request_complete(struct vb2_buffer *vb) @@ -1380,7 +1402,8 @@ static int vim2m_open(struct file *file) vim2m_ctrl_trans_time_msec.def = default_transtime; v4l2_ctrl_new_custom(hdl, &vim2m_ctrl_trans_time_msec, NULL); - v4l2_ctrl_new_custom(hdl, &vim2m_ctrl_trans_num_bufs, NULL); + ctx->trans_num_bufs_ctrl = + v4l2_ctrl_new_custom(hdl, &vim2m_ctrl_trans_num_bufs, NULL); if (hdl->error) { rc = hdl->error; v4l2_ctrl_handler_free(hdl); @@ -1435,10 +1458,10 @@ static int vim2m_release(struct file *file) v4l2_fh_del(&ctx->fh, file); v4l2_fh_exit(&ctx->fh); - v4l2_ctrl_handler_free(&ctx->hdl); mutex_lock(&dev->dev_mutex); v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); mutex_unlock(&dev->dev_mutex); + v4l2_ctrl_handler_free(&ctx->hdl); kfree(ctx); atomic_dec(&dev->num_inst); From 7d77ef247f2abc6f71c584ed41a97a4957ea2b4c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 3 Jun 2026 13:17:24 +0200 Subject: [PATCH 118/596] media: tda18250: Use %*ph to print small buffer Use %*ph format to print small buffer as hex string. Signed-off-by: Andy Shevchenko Signed-off-by: Hans Verkuil --- drivers/media/tuners/tda18250.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/media/tuners/tda18250.c b/drivers/media/tuners/tda18250.c index bd7ed50fb6e8..e4b4106e91b6 100644 --- a/drivers/media/tuners/tda18250.c +++ b/drivers/media/tuners/tda18250.c @@ -804,8 +804,7 @@ static int tda18250_probe(struct i2c_client *client) /* read the three chip ID registers */ regmap_bulk_read(dev->regmap, R00_ID1, &chip_id, 3); - dev_dbg(&client->dev, "chip_id=%02x:%02x:%02x", - chip_id[0], chip_id[1], chip_id[2]); + dev_dbg(&client->dev, "chip_id=%3phC", chip_id); switch (chip_id[0]) { case 0xc7: From bbc952491f5f05f2274c811d34174d5b88632103 Mon Sep 17 00:00:00 2001 From: Faizel K B Date: Mon, 27 Jul 2026 17:15:45 -0700 Subject: [PATCH 119/596] media: vimc: sensor: fix pixel_rate type to resolve do_div() warning vsensor->pixel_rate->val is a plain s32 field. Declaring the local "pixel_rate" variable as u64 misrepresented its actual range and made do_div()'s divisor look wider than the 32-bit division it actually performs, which is exactly what the do_div() coccinelle check warns. Revert pixel_rate back to its original s32 type to match vsensor->pixel_rate->val, resolving the warning correctly. Fixes: ec1e620b2454 ("media: vimc: sensor: Add pixel_rate,vblank and hblank configuration") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604191731.10nxGZ92-lkp@intel.com/ Link: https://lore.kernel.org/lkml/CANiDSCs9s03DUjKwcPgDtZt2QJ4i0FGwab4zpEP+wCawagSC2Q@mail.gmail.com/ Cc: stable@vger.kernel.org Signed-off-by: Faizel K B Reviewed-by: Ricardo Ribalda Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/vimc/vimc-sensor.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/media/test-drivers/vimc/vimc-sensor.c b/drivers/media/test-drivers/vimc/vimc-sensor.c index 83dcc9d61ee0..7cb544fdfddb 100644 --- a/drivers/media/test-drivers/vimc/vimc-sensor.c +++ b/drivers/media/test-drivers/vimc/vimc-sensor.c @@ -97,18 +97,14 @@ static void vimc_sensor_update_frame_timing(struct v4l2_subdev *sd, { struct vimc_sensor_device *vsensor = container_of(sd, struct vimc_sensor_device, sd); - u64 pixel_rate = vsensor->pixel_rate->val; + u32 pixel_rate = vsensor->pixel_rate->val; u32 hts = width + vsensor->hblank->val; u32 vts = height + vsensor->vblank->val; u64 total_pixels = (u64)hts * vts; u64 frame_interval_ns; - /* Sanity check, pixel rate is fixed and fits in 32 bits. */ - if (WARN_ON(pixel_rate >= 0x100000000)) - return; - frame_interval_ns = total_pixels * NSEC_PER_SEC; - do_div(frame_interval_ns, (u32)pixel_rate); + do_div(frame_interval_ns, pixel_rate); vsensor->hw.fps_jiffies = nsecs_to_jiffies(frame_interval_ns); if (vsensor->hw.fps_jiffies == 0) vsensor->hw.fps_jiffies = 1; From 0183746ebca4c478027e2261b904f844f1443b19 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Sat, 15 Aug 2026 22:56:36 +0800 Subject: [PATCH 120/596] media: vicodec: fix out-of-bounds write on under-rounded coded dimensions A capture format whose width or height is one more than a multiple of 16 makes the FWHT codec write one 8x8 block row, or column, past the end of the capture plane, and read the same block out of the reference frame for P-coded frames. vic_round_dim() is documented to round a frame dimension up so that both the luma and the chroma plane end up a multiple of 8, but it only rounds the chroma plane: round_up((dim) / (div), 8) * (div) For div == 2 the result is a multiple of 16 in every case but one: when dim % 16 == 1, dim / div is already a multiple of 8 and the macro returns dim - 1. encode_plane() and decode_plane() round the same dimension with round_up(dim, 8), which yields dim + 7 -- one block more than the coded dimension that sized the buffer. On a KASAN-enabled kernel, a 641x360 YUYV P-frame triggers: BUG: KASAN: slab-out-of-bounds in add_deltas+0x450/0xcc0 Read of size 1 at addr ffff888009070800 by task trigger_bin/70 Call Trace: add_deltas+0x450/0xcc0 decode_plane+0x1916/0x3390 fwht_decode_frame+0x173/0x620 v4l2_fwht_decode+0x751/0x1120 device_run+0x6bb/0x1850 Round the dimension itself up to a multiple of 8 * div. The rounding changes only for div == 2 and dim % 16 == 1, and MAX_WIDTH and MAX_HEIGHT are both multiples of 16, so the rounded value still fits the advertised limits. Fixes: 3b15f68e19c2 ("media: vicodec: Add support for resolution change event.") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/vicodec/codec-fwht.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/test-drivers/vicodec/codec-fwht.h b/drivers/media/test-drivers/vicodec/codec-fwht.h index 0eab24020e9e..4b4d39031089 100644 --- a/drivers/media/test-drivers/vicodec/codec-fwht.h +++ b/drivers/media/test-drivers/vicodec/codec-fwht.h @@ -61,7 +61,7 @@ * both luma and chroma components resolutions are rounded up to * a multiple of 8 */ -#define vic_round_dim(dim, div) (round_up((dim) / (div), 8) * (div)) +#define vic_round_dim(dim, div) round_up(dim, 8 * (div)) struct fwht_cframe_hdr { u32 magic1; From 2532e9d2f8ccb330a9c924b1ee34c5629240d352 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Sat, 15 Aug 2026 22:56:38 +0800 Subject: [PATCH 121/596] media: vicodec: make encoder CAPTURE dimensions read-only Setting the encoder's compressed CAPTURE format to a smaller resolution than the raw OUTPUT format makes the encoder write past the end of the CAPTURE buffer. For a stateful encoder the CAPTURE width and height are not client-settable; Documentation/userspace-api/media/v4l/dev-encoder.rst specifies them as "ignored (read-only)" on VIDIOC_S_FMT. vicodec only implements half of that: vidioc_s_fmt_vid_out() derives the CAPTURE coded size and sizeimage from the OUTPUT format, but S_FMT on the CAPTURE queue overwrites them. The encoder then takes its geometry from the OUTPUT queue alone, and v4l2_fwht_encode() gets no destination length. Overwrite the requested width and height with the OUTPUT queue's coded dimensions in vidioc_try_fmt_vid_cap(), making them read-only as the interface requires. Fixes: efec9c815e5d ("media: vicodec: pass on enc output format to capture side") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/vicodec/vicodec-core.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/media/test-drivers/vicodec/vicodec-core.c b/drivers/media/test-drivers/vicodec/vicodec-core.c index ff9d50fb05fd..25ec3a43fc71 100644 --- a/drivers/media/test-drivers/vicodec/vicodec-core.c +++ b/drivers/media/test-drivers/vicodec/vicodec-core.c @@ -890,6 +890,8 @@ static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct vicodec_ctx *ctx = file2ctx(file); + struct vicodec_q_data *q_data_out = + get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); struct v4l2_pix_format_mplane *pix_mp; struct v4l2_pix_format *pix; @@ -900,6 +902,10 @@ static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, pix = &f->fmt.pix; pix->pixelformat = ctx->is_enc ? V4L2_PIX_FMT_FWHT : find_fmt(f->fmt.pix.pixelformat)->id; + if (ctx->is_enc) { + pix->width = q_data_out->coded_width; + pix->height = q_data_out->coded_height; + } pix->colorspace = ctx->state.colorspace; pix->xfer_func = ctx->state.xfer_func; pix->ycbcr_enc = ctx->state.ycbcr_enc; @@ -911,6 +917,10 @@ static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, pix_mp = &f->fmt.pix_mp; pix_mp->pixelformat = ctx->is_enc ? V4L2_PIX_FMT_FWHT : find_fmt(pix_mp->pixelformat)->id; + if (ctx->is_enc) { + pix_mp->width = q_data_out->coded_width; + pix_mp->height = q_data_out->coded_height; + } pix_mp->colorspace = ctx->state.colorspace; pix_mp->xfer_func = ctx->state.xfer_func; pix_mp->ycbcr_enc = ctx->state.ycbcr_enc; From 6f412f9fe6a1f3ce9694757f50353bbec221d832 Mon Sep 17 00:00:00 2001 From: Piyush Patle Date: Mon, 24 Aug 2026 19:21:15 +0530 Subject: [PATCH 122/596] media: vidtv: fix frontend reference leak on unbind dvb_register_frontend() keeps two references to the frontend. One is released by dvb_unregister_frontend(), and the other by dvb_frontend_detach(). vidtv only called dvb_unregister_frontend(), so the second reference was never released. As a result, __dvb_frontend_free() was never called, leaking the frontend private data and its struct dvb_device. The detach call was removed by commit 63101b756893 ("media: vidtv: fix driver unbind/remove") because it caused an OOPS. The demod .release callback freed vidtv_demod_state, and the I2C remove callbacks then accessed the freed state. That commit also removed those accesses from the I2C remove callbacks. Restore the detach call, but remove the demod .release callback. vidtv_demod_state is owned by the I2C client and is already freed by vidtv_demod_i2c_remove(), so the frontend detach path should not free it. Tested with kmemleak and KASAN over 10 bind/unbind cycles. The reported frontend and dvb_device leaks were present before the fix and were gone after it, with no KASAN reports. Reported-by: syzbot+32f018fd65e799f79ae0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=32f018fd65e799f79ae0 Fixes: 63101b756893 ("media: vidtv: fix driver unbind/remove") Cc: stable@vger.kernel.org Tested-by: syzbot+32f018fd65e799f79ae0@syzkaller.appspotmail.com Signed-off-by: Piyush Patle Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/vidtv/vidtv_bridge.c | 2 ++ drivers/media/test-drivers/vidtv/vidtv_demod.c | 9 --------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c index fd69b4ee16f4..9887860b0198 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_bridge.c +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c @@ -474,6 +474,7 @@ static int vidtv_bridge_dvb_init(struct vidtv_dvb *dvb) fail_demod_probe: for (i = i - 1; i >= 0; --i) { dvb_unregister_frontend(dvb->fe[i]); + dvb_frontend_detach(dvb->fe[i]); fail_fe: dvb_module_release(dvb->i2c_client_tuner[i]); fail_tuner_probe: @@ -552,6 +553,7 @@ static void vidtv_bridge_remove(struct platform_device *pdev) for (i = 0; i < NUM_FE; ++i) { dvb_unregister_frontend(dvb->fe[i]); + dvb_frontend_detach(dvb->fe[i]); dvb_module_release(dvb->i2c_client_tuner[i]); dvb_module_release(dvb->i2c_client_demod[i]); } diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.c b/drivers/media/test-drivers/vidtv/vidtv_demod.c index 6e5fe402976b..3aa586004638 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_demod.c +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.c @@ -343,13 +343,6 @@ static int vidtv_diseqc_send_burst(struct dvb_frontend *fe, return 0; } -static void vidtv_demod_release(struct dvb_frontend *fe) -{ - struct vidtv_demod_state *state = fe->demodulator_priv; - - kfree(state); -} - static const struct dvb_frontend_ops vidtv_demod_ops = { .delsys = { SYS_DVBT, @@ -390,8 +383,6 @@ static const struct dvb_frontend_ops vidtv_demod_ops = { FE_CAN_HIERARCHY_AUTO, }, - .release = vidtv_demod_release, - .set_frontend = vidtv_demod_set_frontend, .get_frontend = vidtv_demod_get_frontend, From 2af02ccd048b77713695d7817cf645ca50bf349e Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Thu, 27 Aug 2026 09:52:29 +0530 Subject: [PATCH 123/596] media: vidtv: fix uaf in vidtv_bridge_on_new_pkts_avail Attempting to unbind a dvbdevice that is in the process of feeding data causes a UAF as we free the underlying device without stopping the feed first. Fix this by stopping the stream first using vidtv_stop_streaming(). However, our codepath in the reproducer (mentioned in the below reply) does not decrement our users (dmxdev->dvr_dvbdev->users) to 1 after it has been incremented to 2 by our read() in the reproducer, that is only possible on .release. This can cause a task hang as dvb_dmxdev_release() uses wait_event() in the wait_queue unless we use a close(fd)(in the reproducer). Is this a problem? Please advise. Reported-by: syzbot+c7fc4794e59786f5b4dc@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c7fc4794e59786f5b4dc Fixes: f90cf6079bf6 ("media: vidtv: add a bridge driver") Cc: stable@vger.kernel.org Signed-off-by: Jeffin Philip Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/vidtv/vidtv_bridge.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c index 9887860b0198..f951b877ada6 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_bridge.c +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c @@ -551,6 +551,8 @@ static void vidtv_bridge_remove(struct platform_device *pdev) mutex_destroy(&dvb->feed_lock); + vidtv_stop_streaming(dvb); + for (i = 0; i < NUM_FE; ++i) { dvb_unregister_frontend(dvb->fe[i]); dvb_frontend_detach(dvb->fe[i]); From e1a7367a93673647f6e78a9690a030af0c9fc47d Mon Sep 17 00:00:00 2001 From: Alberto Pimpo Date: Sun, 30 Aug 2026 17:08:47 +0000 Subject: [PATCH 124/596] media: vidtv: validate the frequency module parameters The lists of frequencies the simulated tuner locks onto are module parameters, and were handed over to the tuner module without being checked first. A terrestrial or cable frequency outside the range the demodulator reports to the DVB core is unreachable, as dvb_frontend_check_parameters() rejects such a tuning request before the tuner is ever asked about the frequency. A satellite frequency outside the Ku-band covered by the simulated LNBf is equally meaningless, since it is downconverted using the LNBf local oscillators before reaching the tuner. In both cases vidtv used to initialize successfully and then never lock on anything, leaving no clue about the cause. Check the terrestrial and cable frequencies against the range advertised by the demodulator, and the satellite ones against the Ku-band covered by the simulated LNBf, failing the probe with an explicit error message otherwise. Signed-off-by: Alberto Pimpo Signed-off-by: Hans Verkuil --- .../media/test-drivers/vidtv/vidtv_bridge.c | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c index f951b877ada6..4c98dfbd406f 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_bridge.c +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c @@ -44,6 +44,10 @@ #define LNB_LOW_FREQ 9750000 /* low IF frequency */ #define LNB_HIGH_FREQ 10600000 /* transition frequency */ +/* The Ku-band range covered by such an LNBf, in kHz */ +#define LNB_KU_BAND_MIN_FREQ 10700000 +#define LNB_KU_BAND_MAX_FREQ 12750000 + static unsigned int drop_tslock_prob_on_low_snr; module_param(drop_tslock_prob_on_low_snr, uint, 0444); MODULE_PARM_DESC(drop_tslock_prob_on_low_snr, @@ -367,6 +371,36 @@ static int vidtv_bridge_probe_demod(struct vidtv_dvb *dvb, u32 n) return 0; } +/* + * Reject frequencies the simulation could never tune into, as the module + * would otherwise load just fine and then never lock on anything, leaving + * no clue about what went wrong. + */ +static int vidtv_bridge_check_freqs(struct vidtv_dvb *dvb, + const unsigned int *freqs, + u32 array_sz, + u32 min_freq, + u32 max_freq, + const char *name) +{ + u32 i; + + for (i = 0; i < array_sz; i++) { + /* a zeroed entry means an unused slot */ + if (!freqs[i]) + continue; + + if (freqs[i] < min_freq || freqs[i] > max_freq) { + dev_err(&dvb->pdev->dev, + "%s[%u]: %u is out of range (%u..%u)\n", + name, i, freqs[i], min_freq, max_freq); + return -EINVAL; + } + } + + return 0; +} + static int vidtv_bridge_probe_tuner(struct vidtv_dvb *dvb, u32 n) { struct vidtv_tuner_config cfg = { @@ -374,10 +408,45 @@ static int vidtv_bridge_probe_tuner(struct vidtv_dvb *dvb, u32 n) .mock_power_up_delay_msec = mock_power_up_delay_msec, .mock_tune_delay_msec = mock_tune_delay_msec, }; + u32 min_freq = dvb->fe[n]->ops.info.frequency_min_hz; + u32 max_freq = dvb->fe[n]->ops.info.frequency_max_hz; u32 freq; + int ret; int i; - /* TODO: check if the frequencies are at a valid range */ + /* + * Terrestrial and cable frequencies are given in Hz and are used as + * is, so they have to fit within the range the demod reports to the + * DVB core: the core rejects a tuning request outside of it before + * the tuner is ever asked about the frequency. + */ + ret = vidtv_bridge_check_freqs(dvb, vidtv_valid_dvb_t_freqs, + ARRAY_SIZE(vidtv_valid_dvb_t_freqs), + min_freq, max_freq, + "vidtv_valid_dvb_t_freqs"); + if (ret) + return ret; + + ret = vidtv_bridge_check_freqs(dvb, vidtv_valid_dvb_c_freqs, + ARRAY_SIZE(vidtv_valid_dvb_c_freqs), + min_freq, max_freq, + "vidtv_valid_dvb_c_freqs"); + if (ret) + return ret; + + /* + * Satellite frequencies are given in kHz at Ku-band and are + * downconverted below, so check them against the band the simulated + * LNBf covers instead. Doing so also ensures that the frequencies + * are above the LNBf local oscillators. + */ + ret = vidtv_bridge_check_freqs(dvb, vidtv_valid_dvb_s_freqs, + ARRAY_SIZE(vidtv_valid_dvb_s_freqs), + LNB_KU_BAND_MIN_FREQ, + LNB_KU_BAND_MAX_FREQ, + "vidtv_valid_dvb_s_freqs"); + if (ret) + return ret; memcpy(cfg.vidtv_valid_dvb_t_freqs, vidtv_valid_dvb_t_freqs, From 5d8722588b92056adb9e08fc80ec6ae67cada758 Mon Sep 17 00:00:00 2001 From: Joyeta Modak Date: Fri, 19 Jun 2026 01:19:00 +0530 Subject: [PATCH 125/596] staging: media: av7110: remove commented out macro av7110_ir.c contains MODULE_AUTHOR() and MODULE_LICENSE() commented out. This file is compiled into dvb-ttpci.ko (dvb-ttpci-objs += av7110_ir.o in Makefile), and the MODULE_LICENSE(), MODULE_AUTHOR() and MODULE_DESCRIPTION() are declared in av7110.c. These commented out lines are unchanged since before kernel moved to git which was checked using git log. Removing them will clean up the file. Signed-off-by: Joyeta Modak Signed-off-by: Hans Verkuil --- drivers/staging/media/av7110/av7110_ir.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/staging/media/av7110/av7110_ir.c b/drivers/staging/media/av7110/av7110_ir.c index 9b7bf1857868..d0948a80d387 100644 --- a/drivers/staging/media/av7110/av7110_ir.c +++ b/drivers/staging/media/av7110/av7110_ir.c @@ -154,5 +154,3 @@ void av7110_ir_exit(struct av7110 *av7110) rc_free_device(av7110->ir.rcdev); } -//MODULE_AUTHOR("Holger Waechtler , Oliver Endriss "); -//MODULE_LICENSE("GPL"); From 3c51c1cae26168d74c5a1f5fde5025524e4e02c7 Mon Sep 17 00:00:00 2001 From: Hao-Qun Huang Date: Mon, 27 Jul 2026 22:20:56 +0800 Subject: [PATCH 126/596] staging: media: tegra-video: vi: move port parsing into a subfunction tegra_vi_channels_alloc() needs two error variables because its loop body mixes two things: ports that are skipped because they cannot be used, and errors that have to stop the whole allocation. Move the loop body into tegra_vi_port_channel_alloc(), which returns 0 both when the port is skipped and when a channel was allocated, and a negative error only when allocation must stop. The loop is then a call and an error check, and the second error variable is no longer needed. No functional change intended. Suggested-by: Luca Ceresoli Assisted-by: Claude:claude-fable-5 Signed-off-by: Hao-Qun Huang Signed-off-by: Hans Verkuil --- drivers/staging/media/tegra-video/vi.c | 92 ++++++++++++++------------ 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c index 01622013c109..be9179a75fee 100644 --- a/drivers/staging/media/tegra-video/vi.c +++ b/drivers/staging/media/tegra-video/vi.c @@ -1247,17 +1247,54 @@ static int tegra_vi_tpg_channels_alloc(struct tegra_vi *vi) return 0; } +static int tegra_vi_port_channel_alloc(struct tegra_vi *vi, + struct device_node *port) +{ + struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 }; + struct device_node *parent; + struct device_node *ep; + unsigned int port_num; + unsigned int lanes; + int ret; + + if (!of_node_name_eq(port, "port")) + return 0; + + if (of_property_read_u32(port, "reg", &port_num) < 0) + return 0; + + if (port_num > vi->soc->vi_max_channels) { + dev_err(vi->dev, "invalid port num %d for %pOF\n", + port_num, port); + return -EINVAL; + } + + ep = of_get_child_by_name(port, "endpoint"); + if (!ep) + return 0; + + parent = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + if (!parent) + return 0; + + ep = of_graph_get_endpoint_by_regs(parent, 0, 0); + of_node_put(parent); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); + of_node_put(ep); + if (ret) + return 0; + + lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; + + return tegra_vi_channel_alloc(vi, port_num, port, lanes); +} + static int tegra_vi_channels_alloc(struct tegra_vi *vi) { struct device_node *node = vi->dev->of_node; - struct device_node *ep = NULL; struct device_node *ports; - struct device_node *port = NULL; - unsigned int port_num; - struct device_node *parent; - struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 }; - unsigned int lanes; - int err; + struct device_node *port; int ret = 0; ports = of_get_child_by_name(node, "ports"); @@ -1265,46 +1302,15 @@ static int tegra_vi_channels_alloc(struct tegra_vi *vi) return dev_err_probe(vi->dev, -ENODEV, "%pOF: missing 'ports' node\n", node); for_each_child_of_node(ports, port) { - if (!of_node_name_eq(port, "port")) - continue; - - err = of_property_read_u32(port, "reg", &port_num); - if (err < 0) - continue; - - if (port_num > vi->soc->vi_max_channels) { - dev_err(vi->dev, "invalid port num %d for %pOF\n", - port_num, port); - ret = -EINVAL; - goto cleanup; + ret = tegra_vi_port_channel_alloc(vi, port); + if (ret) { + of_node_put(port); + break; } - - ep = of_get_child_by_name(port, "endpoint"); - if (!ep) - continue; - - parent = of_graph_get_remote_port_parent(ep); - of_node_put(ep); - if (!parent) - continue; - - ep = of_graph_get_endpoint_by_regs(parent, 0, 0); - of_node_put(parent); - err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), - &v4l2_ep); - of_node_put(ep); - if (err) - continue; - - lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; - ret = tegra_vi_channel_alloc(vi, port_num, port, lanes); - if (ret < 0) - goto cleanup; } -cleanup: - of_node_put(port); of_node_put(ports); + return ret; } From 1179929b3abd4adb6ca7fae91d649c64a7293aca Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sat, 1 Aug 2026 18:53:54 +0100 Subject: [PATCH 127/596] media: marvell-cam: fix unmet dependency for VIDEO_MMP_CAMERA VIDEO_MMP_CAMERA selects I2C_GPIO without ensuring 'GPIOLIB || COMPILE_TEST' is enabled, despite I2C_GPIO depending on it. Let's add the same dependency to VIDEO_MMP_CAMERA. This unmet dependency bug was found by kconfirm, a static analysis tool for Kconfig. Fixes: 67a8dbbc4e04 ("[media] marvell-cam: Basic working MMP camera driver") Cc: stable@vger.kernel.org Signed-off-by: Julian Braha Signed-off-by: Hans Verkuil --- drivers/media/platform/marvell/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/marvell/Kconfig b/drivers/media/platform/marvell/Kconfig index d31f4730f2a3..ab58175e433c 100644 --- a/drivers/media/platform/marvell/Kconfig +++ b/drivers/media/platform/marvell/Kconfig @@ -22,6 +22,7 @@ config VIDEO_MMP_CAMERA depends on V4L_PLATFORM_DRIVERS depends on I2C && VIDEO_DEV depends on ARCH_MMP || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST depends on COMMON_CLK select VIDEO_OV7670 if MEDIA_SUBDRV_AUTOSELECT && VIDEO_CAMERA_SENSOR select I2C_GPIO From affd2694728284a633a5dbf939d9ed06d2d3fc22 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 1 Aug 2026 21:09:53 +0200 Subject: [PATCH 128/596] media: marvell: cafe: drop unneeded semicolon When a function-like macro expands to an expression, that expression doesn't need a semicolon after it. All uses have been verified to have their own semicolons. This was found using the following Coccinelle semantic patch: @r@ identifier i : script:ocaml() { String.lowercase_ascii i = i }; expression e; @@ *#define i(...) e; Signed-off-by: Julia Lawall Signed-off-by: Hans Verkuil --- drivers/media/platform/marvell/cafe-driver.c | 2 +- drivers/media/platform/marvell/mcam-core.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/marvell/cafe-driver.c b/drivers/media/platform/marvell/cafe-driver.c index 22034df6cba9..13d8faaabb50 100644 --- a/drivers/media/platform/marvell/cafe-driver.c +++ b/drivers/media/platform/marvell/cafe-driver.c @@ -127,7 +127,7 @@ struct cafe_camera { * Debugging and related. */ #define cam_err(cam, fmt, arg...) \ - dev_err(&(cam)->pdev->dev, fmt, ##arg); + dev_err(&(cam)->pdev->dev, fmt, ##arg) #define cam_warn(cam, fmt, arg...) \ dev_warn(&(cam)->pdev->dev, fmt, ##arg); diff --git a/drivers/media/platform/marvell/mcam-core.c b/drivers/media/platform/marvell/mcam-core.c index b8360d37000a..c1e8a2a3012d 100644 --- a/drivers/media/platform/marvell/mcam-core.c +++ b/drivers/media/platform/marvell/mcam-core.c @@ -224,11 +224,11 @@ static void mcam_buffer_done(struct mcam_camera *cam, int frame, * Debugging and related. */ #define cam_err(cam, fmt, arg...) \ - dev_err((cam)->dev, fmt, ##arg); + dev_err((cam)->dev, fmt, ##arg) #define cam_warn(cam, fmt, arg...) \ - dev_warn((cam)->dev, fmt, ##arg); + dev_warn((cam)->dev, fmt, ##arg) #define cam_dbg(cam, fmt, arg...) \ - dev_dbg((cam)->dev, fmt, ##arg); + dev_dbg((cam)->dev, fmt, ##arg) /* From 5d1c1a8ed79f6df9d2094dbee507e17e6b5358f1 Mon Sep 17 00:00:00 2001 From: Ashmit Kumar Date: Tue, 4 Aug 2026 11:15:04 +0000 Subject: [PATCH 129/596] staging: media: av7110: remove unnecessary parentheses from return The Linux kernel coding style specifies that return is not a function and therefore parentheses are not required. Signed-off-by: Ashmit Kumar Signed-off-by: Hans Verkuil --- drivers/staging/media/av7110/sp8870.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/staging/media/av7110/sp8870.c b/drivers/staging/media/av7110/sp8870.c index 29fb4934c039..83c6ee305d62 100644 --- a/drivers/staging/media/av7110/sp8870.c +++ b/drivers/staging/media/av7110/sp8870.c @@ -92,7 +92,7 @@ static int sp8870_readreg(struct sp8870_state *state, u16 reg) return -1; } - return (b1[0] << 8 | b1[1]); + return b1[0] << 8 | b1[1]; } static int sp8870_firmware_upload(struct sp8870_state *state, const struct firmware *fw) @@ -167,7 +167,7 @@ static void sp8870_microcontroller_start(struct sp8870_state *state) static int sp8870_read_data_valid_signal(struct sp8870_state *state) { - return (sp8870_readreg(state, 0x0D02) > 0); + return sp8870_readreg(state, 0x0D02) > 0; } static int configure_reg0xc05(struct dtv_frontend_properties *p, u16 *reg0xc05) From 0f29f84317c1e35f6a9265277be33e60d3fc8c1c Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 7 Aug 2026 05:35:53 +0000 Subject: [PATCH 130/596] media: marvell: mcam: stop DMA and cancel s_bh_work before freeing DMA buffers In vmalloc mode the frame-completion IRQ queues mcam_frame_work() on cam->s_bh_work, which memcpy()s from the dma_bufs[] that mcam_free_dma_bufs() frees with dma_free_coherent(). mccic_shutdown() frees those buffers without stopping the controller, so while streaming a late frame IRQ can re-arm the work after the buffers are gone, causing a use-after-free. Stop the controller in mccic_shutdown() when streaming is still active (gated on an open fd, which holds a runtime-PM reference, so the device is powered), cancel s_bh_work in mcam_free_dma_bufs(), and move INIT_WORK() into mccic_register() before the device can be published. Fixes: 67a8dbbc4e04 ("[media] marvell-cam: Basic working MMP camera driver") Cc: stable@vger.kernel.org Signed-off-by: Fan Wu Signed-off-by: Hans Verkuil --- drivers/media/platform/marvell/mcam-core.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/marvell/mcam-core.c b/drivers/media/platform/marvell/mcam-core.c index c1e8a2a3012d..2d0b8b60c509 100644 --- a/drivers/media/platform/marvell/mcam-core.c +++ b/drivers/media/platform/marvell/mcam-core.c @@ -406,6 +406,8 @@ static void mcam_free_dma_bufs(struct mcam_camera *cam) { int i; + cancel_work_sync(&cam->s_bh_work); + for (i = 0; i < cam->nbufs; i++) { dma_free_coherent(cam->dev, cam->dma_buf_size, cam->dma_bufs[i], cam->dma_handles[i]); @@ -1306,7 +1308,6 @@ static int mcam_setup_vb2(struct mcam_camera *cam) break; case B_vmalloc: #ifdef MCAM_MODE_VMALLOC - INIT_WORK(&cam->s_bh_work, mcam_frame_work); vq->ops = &mcam_vb2_ops; vq->mem_ops = &vb2_vmalloc_memops; cam->dma_setup = mcam_ctlr_dma_vmalloc; @@ -1864,6 +1865,12 @@ int mccic_register(struct mcam_camera *cam) goto out; } +#ifdef MCAM_MODE_VMALLOC + /* Init before sensor bind: armed by IRQ, cancelled on probe-error paths. */ + if (cam->buffer_mode == B_vmalloc) + INIT_WORK(&cam->s_bh_work, mcam_frame_work); +#endif + mutex_init(&cam->s_mutex); cam->state = S_NOTREADY; mcam_set_config_needed(cam, 1); @@ -1922,10 +1929,15 @@ void mccic_shutdown(struct mcam_camera *cam) * take it down again will wedge the machine, which is frowned * upon. */ + mutex_lock(&cam->s_mutex); if (!list_empty(&cam->vdev.fh_list)) { cam_warn(cam, "Removing a device with users!\n"); + /* Stop so the IRQ can't re-arm s_bh_work after the buffers are freed. */ + if (cam->state == S_STREAMING) + mcam_ctlr_stop_dma(cam); sensor_call(cam, core, s_power, 0); } + mutex_unlock(&cam->s_mutex); if (cam->buffer_mode == B_vmalloc) mcam_free_dma_bufs(cam); v4l2_ctrl_handler_free(&cam->ctrl_handler); From 5764f9bd7b7a72c6df49db6c8c00d4f9994a55ba Mon Sep 17 00:00:00 2001 From: Punnay Sharma Date: Sat, 8 Aug 2026 00:04:42 +0530 Subject: [PATCH 131/596] staging: media: av7110: fix corrupted BER reporting in sp8870 In sp8870_read_ber(), the 14-bit Bit Error Rate (BER) is assembled by reading two I2C registers: 0xC08 (lower 6 bits) and 0xC07 (upper 8 bits). The current implementation masks the lower bits via `tmp = ret & 0x3F;` but subsequently overwrites `tmp` entirely when processing the upper bits using a direct assignment (`tmp = ret << 6;`). This logical error causes the lower 6 bits of the BER hardware metric to be silently discarded. Fix this by using a bitwise OR (`tmp |= ret << 6;`) to correctly merge the MSB and LSB payloads before returning the metric to the DVB core. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Punnay Sharma Signed-off-by: Hans Verkuil --- 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 83c6ee305d62..340e4b22859d 100644 --- a/drivers/staging/media/av7110/sp8870.c +++ b/drivers/staging/media/av7110/sp8870.c @@ -401,7 +401,7 @@ static int sp8870_read_ber(struct dvb_frontend *fe, u32 *ber) if (ret < 0) return -EIO; - tmp = ret << 6; + tmp |= ret << 6; if (tmp >= 0x3FFF0) tmp = ~0; From 30684fa98394bc0eb4362282908612aab7d4dad9 Mon Sep 17 00:00:00 2001 From: Punnay Sharma Date: Sat, 8 Aug 2026 00:04:43 +0530 Subject: [PATCH 132/596] staging: media: av7110: fix sp8870 initialization failure state The SP8870 DVB frontend driver prematurely flags the device as initialized at the very beginning of the sp8870_init() routine, prior to requesting the firmware (dvb-fe-sp8870.fw) and executing the I2C upload sequence. If request_firmware() times out or sp8870_firmware_upload() encounters an I2C bus error, the function aborts and returns -EIO. However, because `state->initialised` is already set to 1, all subsequent invocations of `fe->ops.init()` by the DVB core will immediately return 0 (success) without attempting to load the firmware again. This leaves the demodulator microcontroller halted and the frontend permanently dead until the module is forcibly reloaded. Relocate the `state->initialised = 1` assignment to the end of sp8870_init(), ensuring the flag is only set after the firmware is successfully uploaded and the system controller is actually restarted. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Punnay Sharma Signed-off-by: Hans Verkuil --- drivers/staging/media/av7110/sp8870.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/av7110/sp8870.c b/drivers/staging/media/av7110/sp8870.c index 340e4b22859d..aecdbd87976f 100644 --- a/drivers/staging/media/av7110/sp8870.c +++ b/drivers/staging/media/av7110/sp8870.c @@ -315,7 +315,6 @@ static int sp8870_init(struct dvb_frontend *fe) sp8870_wake_up(state); if (state->initialised) return 0; - state->initialised = 1; dprintk("initialising frontend...\n"); @@ -353,6 +352,8 @@ static int sp8870_init(struct dvb_frontend *fe) sp8870_writereg(state, 0x0D00, 0x010); sp8870_writereg(state, 0x0D01, 0x000); + state->initialised = 1; + return 0; } From 4ee3ebede5d01efdcdd88c94f9df271d2eb237de Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 11 Aug 2026 19:28:40 +0800 Subject: [PATCH 133/596] media: m2m-deinterlace: fix default capture field queue_init() initializes default formats for both the source and capture queues. It first sets the source field to V4L2_FIELD_SEQ_TB, but then stores the capture default, V4L2_FIELD_INTERLACED_TB, in the source queue again while initializing the capture queue. This overwrites the valid source default and leaves the capture field at its zero-initialized value, V4L2_FIELD_ANY. vidioc_streamon() accepts only V4L2_FIELD_SEQ_TB or V4L2_FIELD_SEQ_BT on the source queue, and requires the capture queue to use a compatible interlaced or NONE field. Userspace that relies on the default formats can therefore get -EINVAL when starting streaming. Initialize the capture field instead. The bug is usually hidden because mem2mem applications commonly call S_FMT on both queues before streaming; the TRY_FMT paths normalize the fields and S_FMT overwrites q_data[].field. Fixes: 8f0755c06b90 ("[media] media: Add mem2mem deinterlacing driver") Cc: stable@vger.kernel.org Reviewed-by: Jacopo Mondi Signed-off-by: Xu Rao Signed-off-by: Hans Verkuil --- drivers/media/platform/m2m-deinterlace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/m2m-deinterlace.c b/drivers/media/platform/m2m-deinterlace.c index 1d0e70eaea3d..9dcc4bd6cbdd 100644 --- a/drivers/media/platform/m2m-deinterlace.c +++ b/drivers/media/platform/m2m-deinterlace.c @@ -822,7 +822,7 @@ static int queue_init(void *priv, struct vb2_queue *src_vq, q_data[V4L2_M2M_DST].width = 640; q_data[V4L2_M2M_DST].height = 480; q_data[V4L2_M2M_DST].sizeimage = (640 * 480 * 3) / 2; - q_data[V4L2_M2M_SRC].field = V4L2_FIELD_INTERLACED_TB; + q_data[V4L2_M2M_DST].field = V4L2_FIELD_INTERLACED_TB; return vb2_queue_init(dst_vq); } From 65ed6e9e3fe20c25a5c293d905a5bdd08137f702 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Fri, 31 Jul 2026 12:36:52 +0200 Subject: [PATCH 134/596] media: usb: em28xx: check if vbi is supported before calling vb2_queue_init If the device does not support vbi, then the vbi queue is still initialized with vb2_queue_init(), which fails with a WARNing because the q->lock pointer is never set. Only call vb2_queue_init for the vbi queue if vbi is actually supported. Signed-off-by: Hans Verkuil Tested-by: Hans Verkuil Reported-by: Lars Ljung Closes: https://lore.kernel.org/linux-media/8ede91cc-b505-458a-94ef-fb02270f9701@matholka.se/T/#u Reviewed-by: Sakari Ailus --- drivers/media/usb/em28xx/em28xx-video.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c index 864bab86ef69..c8eeb20afc38 100644 --- a/drivers/media/usb/em28xx/em28xx-video.c +++ b/drivers/media/usb/em28xx/em28xx-video.c @@ -1411,6 +1411,9 @@ static int em28xx_vb2_setup(struct em28xx *dev) if (rc < 0) return rc; + if (!em28xx_vbi_supported(dev)) + return 0; + /* Setup Videobuf2 for VBI capture */ q = &v4l2->vb_vbiq; q->type = V4L2_BUF_TYPE_VBI_CAPTURE; From a4d0f0c88ae8185315afbc1eb68c978ed710f759 Mon Sep 17 00:00:00 2001 From: Elgin Perumbilly Date: Tue, 8 Sep 2026 17:12:27 +0530 Subject: [PATCH 135/596] dt-bindings: media: i2c: Add os02g10 sensor Add bindings for Omnivision OS02G10 sensor. Signed-off-by: Elgin Perumbilly Reviewed-by: Krzysztof Kozlowski Reviewed-by: Vladimir Zapolskiy Signed-off-by: Sakari Ailus --- .../bindings/media/i2c/ovti,os02g10.yaml | 94 +++++++++++++++++++ MAINTAINERS | 7 ++ 2 files changed, 101 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml new file mode 100644 index 000000000000..e240974dcc9a --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ovti,os02g10.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: OmniVision OS02G10 Image Sensor + +maintainers: + - Tarang Raval + +description: + The OmniVision OS02G10 is a 2MP (1920x1080) color CMOS image sensor controlled + through an I2C-compatible SCCB bus. It outputs RAW10 format data and supports + a 2-lane MIPI interface. + +properties: + compatible: + const: ovti,os02g10 + + 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 core Power Supply (1.5v) + + 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 + + required: + - link-frequencies + + required: + - endpoint + +required: + - compatible + - reg + - clocks + - avdd-supply + - dovdd-supply + - dvdd-supply + - port + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera-sensor@3c { + compatible = "ovti,os02g10"; + reg = <0x3c>; + clocks = <&os02g10_clk>; + reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; + + avdd-supply = <&os02g10_avdd_2v8>; + dvdd-supply = <&os02g10_dvdd_1v5>; + dovdd-supply = <&os02g10_dovdd_1v8>; + + port { + cam_out: endpoint { + remote-endpoint = <&mipi_in_cam>; + data-lanes = <1 2>; + link-frequencies = /bits/ 64 <720000000>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 308c67b8b0cd..20077eab310f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20138,6 +20138,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 OS02G10 SENSOR DRIVER +M: Tarang Raval +M: Elgin Perumbilly +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml + OMNIVISION OS05B10 SENSOR DRIVER M: Himanshu Bhavani M: Elgin Perumbilly From 263d0fa1d46ac1ae2eebc5a5490fec58233c69ad Mon Sep 17 00:00:00 2001 From: Elgin Perumbilly Date: Tue, 8 Sep 2026 17:12:28 +0530 Subject: [PATCH 136/596] media: i2c: add os02g10 image sensor driver Add a v4l2 subdevice driver for the Omnivision os02g10 sensor. The Omnivision os02g10 is a CMOS image sensor with an active array size of 1920 x 1080. The following features are supported: - Manual exposure an gain control support - vblank/hblank control support - vflip/hflip control support - Test pattern control support - Supported resolution: 1920 x 1080 @ 30fps (SBGGR10) Signed-off-by: Elgin Perumbilly Reviewed-by: Tarang Raval Reviewed-by: Vladimir Zapolskiy Signed-off-by: Sakari Ailus --- MAINTAINERS | 1 + drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/os02g10.c | 932 ++++++++++++++++++++++++++++++++++++ 4 files changed, 944 insertions(+) create mode 100644 drivers/media/i2c/os02g10.c diff --git a/MAINTAINERS b/MAINTAINERS index 20077eab310f..283f85e2f97c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20144,6 +20144,7 @@ M: Elgin Perumbilly L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml +F: drivers/media/i2c/os02g10.c OMNIVISION OS05B10 SENSOR DRIVER M: Himanshu Bhavani diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 5c52007f9cbe..b5ff3f52ec3e 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -396,6 +396,16 @@ config VIDEO_OG0VE1B To compile this driver as a module, choose M here: the module will be called og0ve1b. +config VIDEO_OS02G10 + tristate "OmniVision OS02G10 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for Omnivision + OS02G10 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called os02g10. + config VIDEO_OS05B10 tristate "OmniVision OS05B10 sensor support" select V4L2_CCI_I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index d04bd5724552..0847e8097bc0 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -86,6 +86,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_OS02G10) += os02g10.o obj-$(CONFIG_VIDEO_OS05B10) += os05b10.o obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c new file mode 100644 index 000000000000..61de45c54d18 --- /dev/null +++ b/drivers/media/i2c/os02g10.c @@ -0,0 +1,932 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 Support for the OS02G10 + * + * Copyright (C) 2026 Silicon Signals Pvt. Ltd. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define OS02G10_XCLK_FREQ_24MHZ (24 * HZ_PER_MHZ) + +/* Page 0 */ +#define OS02G10_REG_CHIPID CCI_REG24(0x002) +#define OS02G10_CHIPID 0x560247 + +#define OS02G10_REG_PLL_DIV_CTRL CCI_REG8(0x030) +#define OS02G10_REG_PLL_DCTL_BIAS_CTRL CCI_REG8(0x035) +#define OS02G10_REG_GATE_EN_CTRL CCI_REG8(0x038) +#define OS02G10_REG_DPLL_NC CCI_REG8(0x041) +#define OS02G10_REG_MP_PHASE_CTRL CCI_REG8(0x044) + +/* Page 1 */ +#define OS02G10_REG_FRAME_SYNC CCI_REG8(0x101) + +#define OS02G10_REG_LONG_EXPOSURE CCI_REG16(0x103) +#define OS02G10_EXPOSURE_MIN 4 +#define OS02G10_EXPOSURE_STEP 1 +#define OS02G10_EXPOSURE_MARGIN 9 + +#define OS02G10_REG_HBLANK CCI_REG16(0x109) + +#define OS02G10_REG_FRAME_TEST_CTRL CCI_REG8(0x10d) +#define OS02G10_FRAME_EXP_SEPERATE_EN BIT(4) +#define OS02G10_TEST_PATTERN_ENABLE BIT(0) + +#define OS02G10_REG_FRAME_LENGTH CCI_REG16(0x10e) +#define OS02G10_FRAME_LENGTH_MAX (BIT(16) - 1) + +#define OS02G10_REG_ANALOG_GAIN CCI_REG8(0x124) +#define OS02G10_ANALOG_GAIN_MIN 16 +#define OS02G10_ANALOG_GAIN_MAX 248 +#define OS02G10_ANALOG_GAIN_STEP 1 +#define OS02G10_ANALOG_GAIN_DEFAULT 16 + +#define OS02G10_REG_DIGITAL_GAIN_H CCI_REG8(0x137) +#define OS02G10_REG_DIGITAL_GAIN_L CCI_REG8(0x139) +#define OS02G10_DIGITAL_GAIN_MIN 64 +#define OS02G10_DIGITAL_GAIN_MAX 2048 +#define OS02G10_DIGITAL_GAIN_STEP 64 +#define OS02G10_DIGITAL_GAIN_DEFAULT 64 + +#define OS02G10_REG_ULP_PWD_DUMMY_CTRL CCI_REG8(0x13c) + +#define OS02G10_REG_FLIP_MIRROR CCI_REG8(0x13f) +#define OS02G10_FLIP BIT(1) +#define OS02G10_MIRROR BIT(0) + +#define OS02G10_REG_DC_LEVEL_LIMIT_EN CCI_REG8(0x146) +#define OS02G10_REG_DC_LEVEL_LIMIT_L CCI_REG8(0x147) +#define OS02G10_REG_BLC_DATA_LIMIT_L CCI_REG8(0x148) +#define OS02G10_REG_DC_BLC_LIMIT_H CCI_REG8(0x149) + +#define OS02G10_REG_H_SIZE_MIPI CCI_REG16(0x18e) +#define OS02G10_REG_V_SIZE_MIPI CCI_REG16(0x190) + +#define OS02G10_REG_HS_LP_CTRL CCI_REG8(0x192) +#define OS02G10_REG_HS_LEVEL CCI_REG8(0x19d) +#define OS02G10_REG_HS_DRV CCI_REG8(0x19e) + +#define OS02G10_REG_MIPI_TX_SPEED_CTRL CCI_REG8(0x1a1) + +#define OS02G10_REG_STREAM_CTRL CCI_REG8(0x1b1) +#define OS02G10_STREAM_CTRL_ON 0x03 +#define OS02G10_STREAM_CTRL_OFF 0x00 + +#define OS02G10_REG_GB_SUBOFFSET CCI_REG8(0x1f0) +#define OS02G10_REG_BLUE_SUBOFFSET CCI_REG8(0x1f1) +#define OS02G10_REG_RED_SUBOFFSET CCI_REG8(0x1f2) +#define OS02G10_REG_GR_SUBOFFSET CCI_REG8(0x1f3) + +#define OS02G10_REG_ABL_TRIGGER CCI_REG8(0x1fa) +#define OS02G10_REG_ABL CCI_REG8(0x1fb) + +/* Page 2 */ +#define OS02G10_REG_SIF_CTRL CCI_REG8(0x25e) +#define OS02G10_ORIENTATION_BAYER_FIX 0x32 + +#define OS02G10_REG_V_START CCI_REG16(0x2a0) +#define OS02G10_REG_V_SIZE CCI_REG16(0x2a2) +#define OS02G10_REG_H_START CCI_REG16(0x2a4) +#define OS02G10_REG_H_SIZE CCI_REG16(0x2a6) + +#define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) +#define OS02G10_DATA_LANES 2 + +/* OS02G10 native and active pixel array size */ +static const struct v4l2_rect os02g10_native_area = { + .top = 0, + .left = 0, + .width = 1928, + .height = 1088, +}; + +static const struct v4l2_rect os02g10_active_area = { + .top = 4, + .left = 4, + .width = 1920, + .height = 1080, +}; + +static const char * const os02g10_supply_name[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +struct os02g10 { + struct device *dev; + struct regmap *cci; + struct v4l2_subdev sd; + struct media_pad pad; + struct clk *xclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(os02g10_supply_name)]; + + /* V4L2 Controls */ + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; +}; + +struct os02g10_mode { + u32 width; + u32 height; + u32 vts_def; + u32 exp_def; + u32 x_start; + u32 y_start; +}; + +static const struct cci_reg_sequence os02g10_common_regs[] = { + { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, + { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, + { OS02G10_REG_GATE_EN_CTRL, 0x11}, + { OS02G10_REG_DPLL_NC, 0x06}, + { OS02G10_REG_MP_PHASE_CTRL, 0x20}, + { CCI_REG8(0x119), 0x50}, + { CCI_REG8(0x11a), 0x0c}, + { CCI_REG8(0x11b), 0x0d}, + { CCI_REG8(0x11c), 0x00}, + { CCI_REG8(0x11d), 0x75}, + { CCI_REG8(0x11e), 0x52}, + { CCI_REG8(0x122), 0x14}, + { CCI_REG8(0x125), 0x44}, + { CCI_REG8(0x126), 0x0f}, + { OS02G10_REG_ULP_PWD_DUMMY_CTRL, 0xca}, + { CCI_REG8(0x13d), 0x4a}, + { CCI_REG8(0x140), 0x0f}, + { CCI_REG8(0x143), 0x38}, + { OS02G10_REG_DC_LEVEL_LIMIT_EN, 0x01}, + { OS02G10_REG_DC_LEVEL_LIMIT_L, 0x00}, + { OS02G10_REG_DC_BLC_LIMIT_H, 0x32}, + { CCI_REG8(0x150), 0x01}, + { CCI_REG8(0x151), 0x28}, + { CCI_REG8(0x152), 0x20}, + { CCI_REG8(0x153), 0x03}, + { CCI_REG8(0x157), 0x16}, + { CCI_REG8(0x159), 0x01}, + { CCI_REG8(0x15a), 0x01}, + { CCI_REG8(0x15d), 0x04}, + { CCI_REG8(0x16a), 0x04}, + { CCI_REG8(0x16b), 0x03}, + { CCI_REG8(0x16e), 0x28}, + { CCI_REG8(0x171), 0xc2}, + { CCI_REG8(0x172), 0x04}, + { CCI_REG8(0x173), 0x38}, + { CCI_REG8(0x174), 0x04}, + { CCI_REG8(0x179), 0x00}, + { CCI_REG8(0x17a), 0xb2}, + { CCI_REG8(0x17b), 0x10}, + { OS02G10_REG_HS_LP_CTRL, 0x02}, + { OS02G10_REG_HS_LEVEL, 0x03}, + { OS02G10_REG_HS_DRV, 0x55}, + { CCI_REG8(0x1b8), 0x70}, + { CCI_REG8(0x1b9), 0x70}, + { CCI_REG8(0x1ba), 0x70}, + { CCI_REG8(0x1bb), 0x70}, + { CCI_REG8(0x1bc), 0x00}, + { CCI_REG8(0x1c4), 0x6d}, + { CCI_REG8(0x1c5), 0x6d}, + { CCI_REG8(0x1c6), 0x6d}, + { CCI_REG8(0x1c7), 0x6d}, + { CCI_REG8(0x1cc), 0x11}, + { CCI_REG8(0x1cd), 0xe0}, + { CCI_REG8(0x1d0), 0x1b}, + { CCI_REG8(0x1d2), 0x76}, + { CCI_REG8(0x1d3), 0x68}, + { CCI_REG8(0x1d4), 0x68}, + { CCI_REG8(0x1d5), 0x73}, + { CCI_REG8(0x1d6), 0x73}, + { CCI_REG8(0x1e8), 0x55}, + { OS02G10_REG_GB_SUBOFFSET, 0x40}, + { OS02G10_REG_BLUE_SUBOFFSET, 0x40}, + { OS02G10_REG_RED_SUBOFFSET, 0x40}, + { OS02G10_REG_GR_SUBOFFSET, 0x40}, + { OS02G10_REG_ABL_TRIGGER, 0x1c}, + { OS02G10_REG_ABL, 0x33}, + { CCI_REG8(0x1fc), 0x80}, + { CCI_REG8(0x1fe), 0x80}, + { CCI_REG8(0x303), 0x67}, + { CCI_REG8(0x300), 0x59}, + { CCI_REG8(0x304), 0x11}, + { CCI_REG8(0x305), 0x04}, + { CCI_REG8(0x306), 0x0c}, + { CCI_REG8(0x307), 0x08}, + { CCI_REG8(0x308), 0x08}, + { CCI_REG8(0x309), 0x4f}, + { CCI_REG8(0x30b), 0x08}, + { CCI_REG8(0x30d), 0x26}, + { CCI_REG8(0x30f), 0x00}, + { CCI_REG8(0x234), 0xfe}, + { OS02G10_REG_MIPI_TX_SPEED_CTRL, 0x05}, +}; + +static const struct os02g10_mode supported_modes[] = { + { + .width = 1920, + .height = 1080, + .vts_def = 1246, + .exp_def = 1100, + .x_start = 2, + .y_start = 6, + }, +}; + +static const s64 link_freq_menu_items[] = { + OS02G10_LINK_FREQ_720MHZ, +}; + +static const char * const os02g10_test_pattern_menu[] = { + "Disabled", + "Colorbar", +}; + +static inline struct os02g10 *to_os02g10(struct v4l2_subdev *sd) +{ + return container_of_const(sd, struct os02g10, sd); +} + +static u32 os02g10_get_format_code(struct os02g10 *os02g10) +{ + static const u32 codes[2][2] = { + { MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, }, + { MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10, }, + }; + + return codes[os02g10->vflip->val][os02g10->hflip->val]; +} + +static int os02g10_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct os02g10 *os02g10 = container_of_const(ctrl->handler, + struct os02g10, handler); + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + state = v4l2_subdev_get_locked_active_state(&os02g10->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 - OS02G10_EXPOSURE_MARGIN; + + ret = __v4l2_ctrl_modify_range(os02g10->exposure, + os02g10->exposure->minimum, max, + os02g10->exposure->step, + os02g10->exposure->default_value); + if (ret) + return ret; + } + + if (pm_runtime_get_if_active(os02g10->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + cci_write(os02g10->cci, OS02G10_REG_LONG_EXPOSURE, + ctrl->val, &ret); + break; + case V4L2_CID_ANALOGUE_GAIN: + cci_write(os02g10->cci, OS02G10_REG_ANALOG_GAIN, + ctrl->val, &ret); + break; + case V4L2_CID_DIGITAL_GAIN: + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_L, + (ctrl->val & 0xff), &ret); + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_H, + ((ctrl->val >> 8) & 0x7), &ret); + break; + case V4L2_CID_VBLANK: { + u64 vts = ctrl->val + fmt->height; + + cci_update_bits(os02g10->cci, OS02G10_REG_FRAME_TEST_CTRL, + OS02G10_FRAME_EXP_SEPERATE_EN, + OS02G10_FRAME_EXP_SEPERATE_EN, &ret); + cci_write(os02g10->cci, OS02G10_REG_FRAME_LENGTH, vts, &ret); + break; + } + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + cci_write(os02g10->cci, OS02G10_REG_FLIP_MIRROR, + os02g10->hflip->val | os02g10->vflip->val << 1, &ret); + cci_write(os02g10->cci, OS02G10_REG_SIF_CTRL, + OS02G10_ORIENTATION_BAYER_FIX, &ret); + break; + case V4L2_CID_TEST_PATTERN: + cci_update_bits(os02g10->cci, + OS02G10_REG_FRAME_TEST_CTRL, + OS02G10_TEST_PATTERN_ENABLE, + ctrl->val ? OS02G10_TEST_PATTERN_ENABLE : 0, + &ret); + break; + default: + ret = -EINVAL; + break; + } + cci_write(os02g10->cci, OS02G10_REG_FRAME_SYNC, 0x01, &ret); + + pm_runtime_put(os02g10->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops os02g10_ctrl_ops = { + .s_ctrl = os02g10_set_ctrl, +}; + +static int os02g10_init_controls(struct os02g10 *os02g10) +{ + const struct os02g10_mode *mode = &supported_modes[0]; + struct v4l2_fwnode_device_properties props; + u64 vblank_def, exp_max, pixel_rate; + struct v4l2_ctrl_handler *ctrl_hdlr; + struct v4l2_ctrl *link_freq; + int ret; + + ret = v4l2_fwnode_device_parse(os02g10->dev, &props); + if (ret) + return ret; + + ctrl_hdlr = &os02g10->handler; + v4l2_ctrl_handler_init(ctrl_hdlr, 11); + + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ + pixel_rate = div_u64(OS02G10_LINK_FREQ_720MHZ * 2 * OS02G10_DATA_LANES, 10); + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, V4L2_CID_PIXEL_RATE, 0, + pixel_rate, 1, pixel_rate); + + link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + + vblank_def = mode->vts_def - mode->height; + os02g10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + OS02G10_FRAME_LENGTH_MAX - mode->height, + 1, vblank_def); + + exp_max = mode->vts_def - OS02G10_EXPOSURE_MARGIN; + os02g10->exposure = + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_EXPOSURE, + OS02G10_EXPOSURE_MIN, exp_max, + OS02G10_EXPOSURE_STEP, mode->exp_def); + + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, OS02G10_ANALOG_GAIN_MIN, + OS02G10_ANALOG_GAIN_MAX, OS02G10_ANALOG_GAIN_STEP, + OS02G10_ANALOG_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_DIGITAL_GAIN, OS02G10_DIGITAL_GAIN_MIN, + OS02G10_DIGITAL_GAIN_MAX, OS02G10_DIGITAL_GAIN_STEP, + OS02G10_DIGITAL_GAIN_DEFAULT); + + os02g10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + os02g10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(os02g10_test_pattern_menu) - 1, + 0, 0, os02g10_test_pattern_menu); + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, + &os02g10_ctrl_ops, &props); + if (ret) + goto err_handler_free; + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + goto err_handler_free; + } + + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + os02g10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + os02g10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + os02g10->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +err_handler_free: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int os02g10_set_framefmt(struct os02g10 *os02g10, + struct v4l2_subdev_state *state) +{ + const struct v4l2_mbus_framefmt *format; + const struct os02g10_mode *mode; + int ret = 0; + + format = v4l2_subdev_state_get_format(state, 0); + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, + height, format->width, format->height); + + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); + + return ret; +} + +static int os02g10_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + int ret; + + ret = pm_runtime_resume_and_get(os02g10->dev); + if (ret < 0) + return ret; + + ret = cci_multi_reg_write(os02g10->cci, os02g10_common_regs, + ARRAY_SIZE(os02g10_common_regs), NULL); + if (ret) { + dev_err(os02g10->dev, "failed to write common registers\n"); + goto err_rpm_put; + } + + ret = os02g10_set_framefmt(os02g10, state); + if (ret) { + dev_err(os02g10->dev, "failed to set frame foramt\n"); + goto err_rpm_put; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(os02g10->sd.ctrl_handler); + if (ret) + goto err_rpm_put; + + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, + OS02G10_STREAM_CTRL_ON, NULL); + if (ret) + goto err_rpm_put; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(os02g10->vflip, true); + __v4l2_ctrl_grab(os02g10->hflip, true); + + return 0; + +err_rpm_put: + pm_runtime_put(os02g10->dev); + return ret; +} + +static int os02g10_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + int ret; + + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, + OS02G10_STREAM_CTRL_OFF, NULL); + if (ret) + dev_err(os02g10->dev, "Failed to stop stream\n"); + + __v4l2_ctrl_grab(os02g10->vflip, false); + __v4l2_ctrl_grab(os02g10->hflip, false); + + pm_runtime_put(os02g10->dev); + + return ret; +} + +static int os02g10_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r = os02g10_native_area; + return 0; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r = os02g10_active_area; + return 0; + default: + return -EINVAL; + } +} + +static int os02g10_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + + if (code->index) + return -EINVAL; + + code->code = os02g10_get_format_code(os02g10); + + return 0; +} + +static int os02g10_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != os02g10_get_format_code(os02g10)) + 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; + + return 0; +} + +static int os02g10_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + struct v4l2_mbus_framefmt *format; + const struct os02g10_mode *mode; + + format = v4l2_subdev_state_get_format(sd_state, 0); + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + + fmt->format.code = os02g10_get_format_code(os02g10); + 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 = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + u32 vblank_def = mode->vts_def - mode->height; + + return __v4l2_ctrl_modify_range(os02g10->vblank, vblank_def, + OS02G10_FRAME_LENGTH_MAX - + mode->height, 1, vblank_def); + } + + return 0; +} + +static int os02g10_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .format = { + .width = supported_modes[0].width, + .height = supported_modes[0].height, + }, + }; + + return os02g10_set_pad_format(sd, state, &fmt); +} + +static const struct v4l2_subdev_video_ops os02g10_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { + .enum_mbus_code = os02g10_enum_mbus_code, + .enum_frame_size = os02g10_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = os02g10_set_pad_format, + .get_selection = os02g10_get_selection, + .enable_streams = os02g10_enable_streams, + .disable_streams = os02g10_disable_streams, +}; + +static const struct v4l2_subdev_ops os02g10_subdev_ops = { + .video = &os02g10_video_ops, + .pad = &os02g10_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops os02g10_internal_ops = { + .init_state = os02g10_init_state, +}; + +static int os02g10_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct os02g10 *os02g10 = to_os02g10(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(os02g10_supply_name), + os02g10->supplies); + if (ret) { + dev_err(os02g10->dev, "failed to enable regulators\n"); + return ret; + } + + /* Wait for T3/T4 timing requirements after supplies become stable */ + fsleep(5 * USEC_PER_MSEC); + + ret = clk_prepare_enable(os02g10->xclk); + if (ret) + goto err_regulator_off; + + gpiod_set_value_cansleep(os02g10->reset_gpio, 0); + + /* T5: delay from sensor power up stable to SCCB initialization */ + fsleep(5 * USEC_PER_MSEC); + + return 0; + +err_regulator_off: + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); + + return ret; +} + +static int os02g10_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct os02g10 *os02g10 = to_os02g10(sd); + + clk_disable_unprepare(os02g10->xclk); + gpiod_set_value_cansleep(os02g10->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); + + return 0; +} + +static int os02g10_identify_module(struct os02g10 *os02g10) +{ + u64 chip_id; + int ret; + + ret = cci_read(os02g10->cci, OS02G10_REG_CHIPID, &chip_id, NULL); + if (ret) + return dev_err_probe(os02g10->dev, ret, + "failed to read chip id %x\n", + OS02G10_CHIPID); + + if (chip_id != OS02G10_CHIPID) + return dev_err_probe(os02g10->dev, -EIO, + "chip id mismatch: %x!=%llx\n", + OS02G10_CHIPID, chip_id); + + return 0; +} + +static int os02g10_parse_endpoint(struct os02g10 *os02g10) +{ + 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_next_endpoint(dev_fwnode(os02g10->dev), NULL); + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + ret = v4l2_link_freq_to_bitmap(os02g10->dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + link_freq_menu_items, + ARRAY_SIZE(link_freq_menu_items), + &link_freq_bitmap); + + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +}; + +static const struct regmap_range_cfg os02g10_ranges[] = { + { + .range_min = 0x0000, + .range_max = 0x03ff, + .selector_reg = 0xfd, + .selector_mask = 0x03, + .selector_shift = 0, + .window_start = 0x00, + .window_len = 0x100, + }, +}; + +static const struct regmap_config os02g10_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .max_register = 0x3ff, + .ranges = os02g10_ranges, + .num_ranges = ARRAY_SIZE(os02g10_ranges), + .disable_locking = true, +}; + +static int os02g10_probe(struct i2c_client *client) +{ + struct os02g10 *os02g10; + unsigned int xclk_freq; + int ret; + + os02g10 = devm_kzalloc(&client->dev, sizeof(*os02g10), GFP_KERNEL); + if (!os02g10) + return -ENOMEM; + + os02g10->dev = &client->dev; + + v4l2_i2c_subdev_init(&os02g10->sd, client, &os02g10_subdev_ops); + os02g10->sd.internal_ops = &os02g10_internal_ops; + + /* + * This is not using devm_cci_regmap_init_i2c(), because the driver + * makes use of regmap's pagination feature. The chosen settings are + * compatible with the CCI helpers. + */ + os02g10->cci = devm_regmap_init_i2c(client, &os02g10_regmap_config); + if (IS_ERR(os02g10->cci)) + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->cci), + "failed to initialize CCI\n"); + + ret = os02g10_parse_endpoint(os02g10); + if (ret) + return dev_err_probe(os02g10->dev, ret, + "failed to parse endpoint configuration\n"); + + /* Get system clock (xvclk) */ + os02g10->xclk = devm_v4l2_sensor_clk_get(os02g10->dev, NULL); + if (IS_ERR(os02g10->xclk)) + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->xclk), + "failed to get xclk\n"); + + xclk_freq = clk_get_rate(os02g10->xclk); + if (xclk_freq != OS02G10_XCLK_FREQ_24MHZ) + return dev_err_probe(os02g10->dev, -EINVAL, + "xclk frequency not supported: %u Hz\n", + xclk_freq); + + for (unsigned int i = 0; i < ARRAY_SIZE(os02g10_supply_name); i++) + os02g10->supplies[i].supply = os02g10_supply_name[i]; + + ret = devm_regulator_bulk_get(os02g10->dev, + ARRAY_SIZE(os02g10_supply_name), + os02g10->supplies); + if (ret) + return dev_err_probe(os02g10->dev, ret, + "failed to get regulators\n"); + + os02g10->reset_gpio = devm_gpiod_get_optional(os02g10->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(os02g10->reset_gpio)) + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->reset_gpio), + "failed to get reset GPIO\n"); + + ret = os02g10_power_on(os02g10->dev); + if (ret) + return ret; + + ret = os02g10_identify_module(os02g10); + if (ret) + goto error_power_off; + + ret = os02g10_init_controls(os02g10); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + os02g10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + os02g10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + os02g10->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&os02g10->sd.entity, 1, &os02g10->pad); + if (ret) { + dev_err_probe(os02g10->dev, ret, "failed to init entity pads\n"); + goto error_handler_free; + } + + os02g10->sd.state_lock = os02g10->handler.lock; + ret = v4l2_subdev_init_finalize(&os02g10->sd); + if (ret) { + dev_err_probe(os02g10->dev, ret, "subdev init error\n"); + goto error_media_entity; + } + + pm_runtime_set_active(os02g10->dev); + pm_runtime_enable(os02g10->dev); + + ret = v4l2_async_register_subdev_sensor(&os02g10->sd); + if (ret) { + dev_err_probe(os02g10->dev, ret, + "failed to register os02g10 sub-device\n"); + goto error_subdev_cleanup; + } + + pm_runtime_idle(os02g10->dev); + + return 0; + +error_subdev_cleanup: + v4l2_subdev_cleanup(&os02g10->sd); + pm_runtime_disable(os02g10->dev); + pm_runtime_set_suspended(os02g10->dev); + +error_media_entity: + media_entity_cleanup(&os02g10->sd.entity); + +error_handler_free: + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); + +error_power_off: + os02g10_power_off(os02g10->dev); + + return ret; +} + +static void os02g10_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct os02g10 *os02g10 = to_os02g10(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(&os02g10->sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + os02g10_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } +} + +static DEFINE_RUNTIME_DEV_PM_OPS(os02g10_pm_ops, + os02g10_power_off, os02g10_power_on, NULL); + +static const struct of_device_id os02g10_id[] = { + { .compatible = "ovti,os02g10" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, os02g10_id); + +static struct i2c_driver os02g10_driver = { + .driver = { + .name = "os02g10", + .pm = pm_ptr(&os02g10_pm_ops), + .of_match_table = os02g10_id, + }, + .probe = os02g10_probe, + .remove = os02g10_remove, +}; +module_i2c_driver(os02g10_driver); + +MODULE_DESCRIPTION("OS02G10 Camera Sensor Driver"); +MODULE_AUTHOR("Tarang Raval "); +MODULE_AUTHOR("Elgin Perumbilly "); +MODULE_LICENSE("GPL"); From a07eb1a65790078a3dfd03f9298b55b9fdf7ee85 Mon Sep 17 00:00:00 2001 From: Matthias Fend Date: Mon, 22 Jun 2026 10:13:22 +0200 Subject: [PATCH 137/596] media: dt-bindings: i2c: add Himax HM1246 image sensor Add YAML device tree binding for Himax HM1246 image sensor. Reviewed-by: Rob Herring (Arm) Signed-off-by: Matthias Fend Signed-off-by: Sakari Ailus --- .../bindings/media/i2c/himax,hm1246.yaml | 120 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 126 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml b/Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml new file mode 100644 index 000000000000..a65488fcc643 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2025 Matthias Fend +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/himax,hm1246.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Himax HM1246-AWD 1/3.7-Inch megapixel SoC image sensor + +maintainers: + - Matthias Fend + +description: + The Himax HM1246-AWD is a 1/3.7-Inch CMOS image sensor SoC with an active + array size of 1296 x 976. It is programmable through an I2C interface and + connected via parallel bus. + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: himax,hm1246 + + reg: + maxItems: 1 + + clocks: + description: Input reference clock (6 - 27 MHz) + maxItems: 1 + + reset-gpios: + description: Active low XSHUTDOWN pin + maxItems: 1 + + avdd-supply: + description: Power for analog circuit (3.0 - 3.6 V) + + iovdd-supply: + description: Power for I/O circuit (1.7 - 3.6 V) + + dvdd-supply: + description: Power for digital circuit (1.5 / 1.8 V) + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + description: Parallel video output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-width: + default: 10 + + hsync-active: + default: 1 + + vsync-active: + default: 1 + + pclk-sample: + default: 0 + + required: + - link-frequencies + + required: + - endpoint + +required: + - compatible + - reg + - clocks + - avdd-supply + - iovdd-supply + - dvdd-supply + - port + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@24 { + compatible = "himax,hm1246"; + reg = <0x24>; + + clocks = <&hm1246_clk>; + + reset-gpios = <&gpio0 0 GPIO_ACTIVE_LOW>; + + avdd-supply = <&hm1246_avdd>; + iovdd-supply = <&hm1246_iovdd>; + dvdd-supply = <&hm1246_dvdd>; + + orientation = <2>; + rotation = <0>; + + port { + endpoint { + remote-endpoint = <&isp_par_in>; + bus-width = <10>; + hsync-active = <1>; /* active high */ + vsync-active = <1>; /* active high */ + pclk-sample = <1>; /* sample on rising edge */ + link-frequencies = /bits/ 64 <42200000>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 283f85e2f97c..16ebf1ac3e71 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11731,6 +11731,12 @@ W: http://www.highpoint-tech.com F: Documentation/scsi/hptiop.rst F: drivers/scsi/hptiop.c +HIMAX HM1246 SENSOR DRIVER +M: Matthias Fend +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml + HIMAX HX83112B TOUCHSCREEN SUPPORT M: Job Noorman L: linux-input@vger.kernel.org From 89248e39751f815dc5fa9683effef8460f1f6626 Mon Sep 17 00:00:00 2001 From: Matthias Fend Date: Mon, 22 Jun 2026 10:13:23 +0200 Subject: [PATCH 138/596] media: i2c: add Himax HM1246 image sensor driver Add a V4L2 sub-device driver for Himax HM1246 image sensor. The Himax HM1246-AWD is a 1/3.7-Inch CMOS image sensor SoC with an active array size of 1296 x 976. It is programmable through an I2C interface and connected via parallel bus. The sensor has an internal ISP with a complete image processing pipeline including control loops. However, this driver uses the sensor in raw mode and the entire ISP is bypassed. Reviewed-by: Andy Shevchenko Signed-off-by: Matthias Fend Signed-off-by: Sakari Ailus --- MAINTAINERS | 1 + drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/hm1246.c | 1285 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1298 insertions(+) create mode 100644 drivers/media/i2c/hm1246.c diff --git a/MAINTAINERS b/MAINTAINERS index 16ebf1ac3e71..f0bb3f1b5ebb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11736,6 +11736,7 @@ M: Matthias Fend L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/himax,hm1246.yaml +F: drivers/media/i2c/hm1246.c HIMAX HX83112B TOUCHSCREEN SUPPORT M: Job Noorman diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index b5ff3f52ec3e..df6a73731803 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -137,6 +137,17 @@ config VIDEO_HI847 To compile this driver as a module, choose M here: the module will be called hi847. +config VIDEO_HM1246 + tristate "Himax HM1246 sensor support" + depends on OF + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Himax + HM1246 camera. + + To compile this driver as a module, choose M here: the + module will be called hm1246. + config VIDEO_IMX111 tristate "Sony IMX111 sensor support" select V4L2_CCI_I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 0847e8097bc0..9c7f990a3e66 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_VIDEO_GC2145) += gc2145.o obj-$(CONFIG_VIDEO_HI556) += hi556.o obj-$(CONFIG_VIDEO_HI846) += hi846.o obj-$(CONFIG_VIDEO_HI847) += hi847.o +obj-$(CONFIG_VIDEO_HM1246) += hm1246.o obj-$(CONFIG_VIDEO_I2C) += video-i2c.o obj-$(CONFIG_VIDEO_IMX111) += imx111.o obj-$(CONFIG_VIDEO_IMX208) += imx208.o diff --git a/drivers/media/i2c/hm1246.c b/drivers/media/i2c/hm1246.c new file mode 100644 index 000000000000..f87ccb9b5a56 --- /dev/null +++ b/drivers/media/i2c/hm1246.c @@ -0,0 +1,1285 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Himax HM1246 image sensor + * + * Copyright 2026 Matthias Fend + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Status registers */ +#define HM1246_MODEL_ID_REG CCI_REG16(0x0000) + +/* General setup registers */ +#define HM1246_MODE_SELECT_REG CCI_REG8(0x0100) +#define HM1246_MODE_SELECT_STANDBY 0x00 +#define HM1246_MODE_SELECT_STREAM 0x01 +#define HM1246_MODE_SELECT_STOP 0x02 +#define HM1246_IMAGE_ORIENTATION_REG CCI_REG8(0x0101) +#define HM1246_IMAGE_ORIENTATION_VFLIP BIT(1) +#define HM1246_IMAGE_ORIENTATION_HFLIP BIT(0) +#define HM1246_CMU_UPDATE_REG CCI_REG8(0x0104) + +/* Output setup registers */ +#define HM1246_COARSE_INTG_REG CCI_REG16(0x0202) +#define HM1246_ANALOG_GLOBAL_GAIN_REG CCI_REG8(0x0205) + +/* Clock setup registers */ +#define HM1246_PLL1CFG_REG CCI_REG8(0x0303) +#define HM1246_PLL1CFG_MULTIPLIER(x) (((x) & 0xff) << 0) +#define HM1246_PLL2CFG_REG CCI_REG8(0x0305) +#define HM1246_PLL2CFG_PRE_DIV(x) (((x) & 0x1f) << 1) +#define HM1246_PLL2CFG_MULTIPLIER(x) (((x) & 0x01) << 0) +#define HM1246_PLL3CFG_REG CCI_REG8(0x0307) +#define HM1246_PLL3CFG_POST_DIV(x) (((x) & 0x3) << 6) +#define HM1246_PLL3CFG_SYSCLK_DIV(x) (((x) & 0x3) << 4) +#define HM1246_PLL3CFG_PCLK_DIV(x) (((x) & 0x7) << 0) + +/* Frame timing registers */ +#define HM1246_FRAME_LENGTH_LINES_REG CCI_REG16(0x0340) +#define HM1246_LINE_LENGTH_PCK_REG CCI_REG16(0x0342) + +/* Image size registers */ +#define HM1246_X_ADDR_START_REG CCI_REG16(0x0344) +#define HM1246_Y_ADDR_START_REG CCI_REG16(0x0346) +#define HM1246_X_ADDR_END_REG CCI_REG16(0x0348) +#define HM1246_Y_ADDR_END_REG CCI_REG16(0x034a) +#define HM1246_X_LA_START_REG CCI_REG16(0x0351) +#define HM1246_X_LA_END_REG CCI_REG16(0x0353) +#define HM1246_Y_LA_START_REG CCI_REG16(0x0355) +#define HM1246_Y_LA_END_REG CCI_REG16(0x0357) + +/* Test pattern registers */ +#define HM1246_TEST_PATTERN_MODE_REG CCI_REG8(0x0601) +#define HM1246_TEST_PATTERN_MODE_MODE(x) (((x) & 0xf) << 4) +#define HM1246_TEST_PATTERN_MODE_ENABLE BIT(0) +#define HM1246_TEST_DATA_BLUE_REG CCI_REG16(0x0602) +#define HM1246_TEST_DATA_GB_REG CCI_REG16(0x0604) +#define HM1246_TEST_DATA_RED_REG CCI_REG16(0x0606) +#define HM1246_TEST_DATA_GR_REG CCI_REG16(0x0608) + +/* SBC registers */ +#define HM1246_SBC_BOOT_REF2_REG CCI_REG8(0x2001) +#define HM1246_SBC_BOOT_REF2_PLL_LOCK BIT(4) +#define HM1246_SBC_CTRL_REG CCI_REG8(0x2003) +#define HM1246_SBC_CTRL_PLL_EN BIT(0) + +/* System registers */ +#define HM1246_OUTPUT_PRT_CTRL_REG CCI_REG8(0x2f02) +#define HM1246_POLARITY_CTRL_REG CCI_REG8(0x2f20) +#define HM1246_POLARITY_CTRL_HSYNC BIT(7) +#define HM1246_POLARITY_CTRL_VSYNC BIT(6) +#define HM1246_PCLK_CTRL_REG CCI_REG8(0x2f24) +#define HM1246_PCLK_CTRL_POL BIT(3) + +/* Digital window control & parameter registers */ +#define HM1246_DWIN_XOFFSET_REG CCI_REG16(0xd5e4) +#define HM1246_DWIN_XSIZE_REG CCI_REG16(0xd5e6) +#define HM1246_DWIN_YOFFSET_REG CCI_REG16(0xd5e8) +#define HM1246_DWIN_YSIZE_REG CCI_REG16(0xd5ea) + +#define HM1246_MODEL_ID 0x1245 + +#define HM1246_NATIVE_WIDTH 1296 +#define HM1246_NATIVE_HEIGHT 976 + +#define HM1246_VTS_MAX 65535 + +#define HM1246_COARSE_INTG_MARGIN 2 +#define HM1246_COARSE_INTG_MIN 4 +#define HM1246_COARSE_INTG_STEP 1 + +#define HM1246_ANALOG_GLOBAL_GAIN_MIN 0x00 +#define HM1246_ANALOG_GLOBAL_GAIN_MAX 0xe8 +#define HM1246_ANALOG_GLOBAL_GAIN_STEP 0x01 + +#define HM1246_XCLK_MIN (6 * HZ_PER_MHZ) +#define HM1246_XCLK_MAX (27 * HZ_PER_MHZ) + +#define HM1246_PCLK_MIN (8 * HZ_PER_MHZ) +#define HM1246_PCLK_MAX (96 * HZ_PER_MHZ) + +#define HM1246_PLL_VCO_MIN (360 * HZ_PER_MHZ) +#define HM1246_PLL_VCO_MAX (680 * HZ_PER_MHZ) + +#define HM1246_PLL_INCLK_MIN (1000 * HZ_PER_KHZ) +#define HM1246_PLL_INCLK_MAX (2500 * HZ_PER_KHZ) + +#define HM1246_PLL_MULTI_L_MIN 1 +#define HM1246_PLL_MULTI_L_MAX 256 + +#define HM1246_PLL_MULTI_H_MIN 2 +#define HM1246_PLL_MULTI_H_MAX 3 + +#define HM1246_PLL_MULTI_MIN \ + (HM1246_PLL_MULTI_H_MIN * HM1246_PLL_MULTI_L_MIN) +#define HM1246_PLL_MULTI_MAX \ + (HM1246_PLL_MULTI_H_MAX * HM1246_PLL_MULTI_L_MAX) + +static const char *const hm1246_test_pattern_menu[] = { + "Disabled", + "Checkerboard", + "Ramp", + "Moving ones", + "Blending color bars", + "Color bars", + "Solid white", + "Solid black", + "Solid red", + "Solid green", + "Solid blue", +}; + +static const char *const hm1246_supply_names[] = { + "avdd", + "iovdd", + "dvdd", +}; + +struct hm1246 { + struct v4l2_subdev sd; + struct media_pad pad; + struct device *dev; + + struct regulator_bulk_data supplies[ARRAY_SIZE(hm1246_supply_names)]; + struct clk *xclk; + unsigned long xclk_freq; + struct reset_control *reset; + unsigned int mbus_flags; + s64 link_frequency; + + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *exposure_ctrl; + struct v4l2_ctrl *hflip_ctrl; + struct v4l2_ctrl *vflip_ctrl; + + struct regmap *regmap; + + bool identified; +}; + +static const struct cci_reg_sequence mode_1296x976_raw[] = { + { HM1246_X_LA_START_REG, 60 }, + { HM1246_X_LA_END_REG, 1355 }, + { HM1246_Y_LA_START_REG, 0 }, + { HM1246_Y_LA_END_REG, 975 }, + { HM1246_OUTPUT_PRT_CTRL_REG, 0x20 }, + { CCI_REG8(0x300a), 0x01 }, + { CCI_REG8(0x300b), 0x00 }, + { CCI_REG8(0x50f5), 0x01 }, + { CCI_REG8(0x50dd), 0x00 }, + { CCI_REG8(0x50a1), 0x02 }, + { CCI_REG8(0x50aa), 0x1c }, + { CCI_REG8(0x50ac), 0xdd }, + { CCI_REG8(0x50ad), 0x08 }, + { CCI_REG8(0x50ab), 0x04 }, + { CCI_REG8(0x50a0), 0x40 }, + { CCI_REG8(0x50a2), 0x12 }, + { CCI_REG8(0x50ae), 0x30 }, + { CCI_REG8(0x50b3), 0x04 }, + { CCI_REG8(0x5204), 0x40 }, + { CCI_REG8(0x5208), 0x55 }, + { CCI_REG8(0x520b), 0x05 }, + { CCI_REG8(0x520d), 0x40 }, + { CCI_REG8(0x5214), 0x18 }, + { CCI_REG8(0x5215), 0x0f }, + { CCI_REG8(0x5217), 0x01 }, + { CCI_REG8(0x5218), 0x07 }, + { CCI_REG8(0x5219), 0x01 }, + { CCI_REG8(0x521a), 0x50 }, + { CCI_REG8(0x521b), 0x24 }, + { CCI_REG8(0x5232), 0x01 }, + { CCI_REG8(0x5220), 0x11 }, + { CCI_REG8(0x5227), 0x01 }, + { CCI_REG8(0x5106), 0xc1 }, + { CCI_REG8(0x5115), 0xc0 }, + { CCI_REG8(0x5116), 0xc1 }, + { CCI_REG8(0x5138), 0x40 }, + { CCI_REG8(0x5139), 0x60 }, + { CCI_REG8(0x513a), 0x80 }, + { CCI_REG8(0x513b), 0xa0 }, + { CCI_REG8(0x513c), 0xa1 }, + { CCI_REG8(0x513d), 0xa2 }, + { CCI_REG8(0x513e), 0xa3 }, + { CCI_REG8(0x5140), 0x40 }, + { CCI_REG8(0x5141), 0x60 }, + { CCI_REG8(0x5142), 0x80 }, + { CCI_REG8(0x5143), 0x81 }, + { CCI_REG8(0x5144), 0x82 }, + { CCI_REG8(0x5145), 0x83 }, + { CCI_REG8(0x5146), 0x93 }, + { CCI_REG8(0x51c1), 0xc3 }, + { CCI_REG8(0x51c5), 0xc3 }, + { CCI_REG8(0x51c9), 0xc3 }, + { CCI_REG8(0x51cd), 0xc2 }, + { CCI_REG8(0x51d1), 0xc1 }, + { CCI_REG8(0x51d5), 0xc1 }, + { CCI_REG8(0x51d9), 0x81 }, + { CCI_REG8(0x51dd), 0x81 }, + { CCI_REG8(0x51c2), 0x49 }, + { CCI_REG8(0x51c6), 0x49 }, + { CCI_REG8(0x51ca), 0x49 }, + { CCI_REG8(0x51ce), 0x49 }, + { CCI_REG8(0x51d2), 0x49 }, + { CCI_REG8(0x51d6), 0x59 }, + { CCI_REG8(0x51da), 0x59 }, + { CCI_REG8(0x51de), 0x59 }, + { CCI_REG8(0x51c3), 0x20 }, + { CCI_REG8(0x51c7), 0x38 }, + { CCI_REG8(0x51cb), 0x21 }, + { CCI_REG8(0x51cf), 0x11 }, + { CCI_REG8(0x51d3), 0x11 }, + { CCI_REG8(0x51d7), 0x13 }, + { CCI_REG8(0x51db), 0x13 }, + { CCI_REG8(0x51df), 0x13 }, + { CCI_REG8(0x51e0), 0x03 }, + { CCI_REG8(0x51e2), 0x03 }, + { CCI_REG8(0x51f0), 0x42 }, + { CCI_REG8(0x51f1), 0x40 }, + { CCI_REG8(0x51f2), 0x4a }, + { CCI_REG8(0x51f3), 0x48 }, + { CCI_REG8(0x5015), 0x73 }, + { CCI_REG8(0x504a), 0x04 }, + { CCI_REG8(0x5044), 0x07 }, + { CCI_REG8(0x5040), 0x03 }, + { CCI_REG8(0x5135), 0xc4 }, + { CCI_REG8(0x5136), 0xc5 }, + { CCI_REG8(0x5166), 0xc4 }, + { CCI_REG8(0x5196), 0xc4 }, + { CCI_REG8(0x51c0), 0x10 }, + { CCI_REG8(0x51c4), 0x10 }, + { CCI_REG8(0x51c8), 0xa0 }, + { CCI_REG8(0x51cc), 0xa0 }, + { CCI_REG8(0x51d0), 0xa1 }, + { CCI_REG8(0x51d4), 0xa5 }, + { CCI_REG8(0x51d8), 0xa5 }, + { CCI_REG8(0x51dc), 0xa5 }, + { CCI_REG8(0x5200), 0xe4 }, + { CCI_REG8(0x5209), 0x04 }, + { CCI_REG8(0x301b), 0x01 }, + { CCI_REG8(0x3130), 0x01 }, + { CCI_REG8(0x5013), 0x07 }, + { CCI_REG8(0x5016), 0x01 }, + { CCI_REG8(0x501d), 0x50 }, + { CCI_REG8(0x0350), 0xfe }, + { CCI_REG8(0x2f03), 0x15 }, + { CCI_REG8(0xd380), 0x00 }, + { CCI_REG8(0x3047), 0x7f }, + { CCI_REG8(0x304d), 0x34 }, + { CCI_REG8(0x3041), 0x4b }, + { CCI_REG8(0x3042), 0x2d }, + { CCI_REG8(0x3056), 0x64 }, + { CCI_REG8(0x3059), 0x1e }, + { CCI_REG8(0x305e), 0x10 }, + { CCI_REG8(0x305f), 0x10 }, + { CCI_REG8(0x306d), 0x10 }, + { CCI_REG8(0x306e), 0x0c }, + { CCI_REG8(0x3064), 0x50 }, + { CCI_REG8(0x3067), 0x78 }, + { CCI_REG8(0x3068), 0x4b }, + { CCI_REG8(0x306a), 0x78 }, + { CCI_REG8(0x306b), 0x4b }, + { CCI_REG8(0xd442), 0x3d }, + { CCI_REG8(0xd443), 0x06 }, + { CCI_REG8(0xd440), 0x63 }, + { CCI_REG8(0xd446), 0xb0 }, + { CCI_REG8(0xd447), 0x60 }, + { CCI_REG8(0xd448), 0x48 }, + { CCI_REG8(0xd449), 0x30 }, + { CCI_REG8(0xd44a), 0x18 }, + { CCI_REG8(0xd360), 0x03 }, + { CCI_REG8(0x30ac), 0x10 }, + { CCI_REG8(0x30ad), 0x10 }, + { CCI_REG8(0x30ae), 0x10 }, + { CCI_REG8(0x3040), 0x0b }, + { CCI_REG8(0x2002), 0x00 }, + { CCI_REG8(0x2000), 0x08 }, +}; + +struct hm1246_reg_list { + u32 num_of_regs; + const struct cci_reg_sequence *regs; +}; + +struct hm1246_mode { + u32 codes[4]; + u32 clocks_per_pixel; + struct v4l2_rect rect; + u32 hts; + u32 vts_min; + const struct hm1246_reg_list reg_list; +}; + +#define FLIP_FORMAT_INDEX(v, h) ((v ? 2 : 0) | (h ? 1 : 0)) + +/* Get the format code of the mode considering current flip setting. */ +static u32 hm1246_get_format_code(struct hm1246 *hm1246, + const struct hm1246_mode *hm1246_mode) +{ + return hm1246_mode->codes[FLIP_FORMAT_INDEX(hm1246->vflip_ctrl->val, + hm1246->hflip_ctrl->val)]; +} + +static const struct hm1246_mode hm1246_modes[] = { + { + .codes = { + [FLIP_FORMAT_INDEX(0, 0)] = MEDIA_BUS_FMT_SBGGR10_1X10, + [FLIP_FORMAT_INDEX(0, 1)] = MEDIA_BUS_FMT_SGBRG10_1X10, + [FLIP_FORMAT_INDEX(1, 0)] = MEDIA_BUS_FMT_SGRBG10_1X10, + [FLIP_FORMAT_INDEX(1, 1)] = MEDIA_BUS_FMT_SRGGB10_1X10, + }, + .clocks_per_pixel = 1, + .rect.top = 0, + .rect.left = 0, + .rect.width = 1296, + .rect.height = 976, + .hts = 1420, + .vts_min = 990, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x976_raw), + .regs = mode_1296x976_raw, + }, + }, +}; + +static inline struct hm1246 *to_hm1246(struct v4l2_subdev *sd) +{ + return container_of_const(sd, struct hm1246, sd); +} + +static const struct hm1246_mode * +hm1246_find_mode_by_mbus_code(struct hm1246 *hm1246, u32 code) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(hm1246_modes); i++) { + if (code == hm1246_get_format_code(hm1246, &hm1246_modes[i])) + return &hm1246_modes[i]; + } + + return NULL; +} + +static int hm1246_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct hm1246 *hm1246 = to_hm1246(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(hm1246_supply_names), + hm1246->supplies); + if (ret) { + dev_err(hm1246->dev, "failed to enable regulators\n"); + return ret; + } + + ret = clk_prepare_enable(hm1246->xclk); + if (ret) { + regulator_bulk_disable(ARRAY_SIZE(hm1246_supply_names), + hm1246->supplies); + dev_err(hm1246->dev, "failed to enable clock\n"); + return ret; + } + + reset_control_deassert(hm1246->reset); + + /* + * XSHUTDOWN to crystal clock oscillation (tcrystal): 650us (typical) + * Sample bootstrap pin (tsample): 2000us (maximum) + * Built in self test (tbist): 3000us (maximum) + */ + fsleep(6 * USEC_PER_MSEC); + + return 0; +} + +static int hm1246_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct hm1246 *hm1246 = to_hm1246(sd); + + reset_control_assert(hm1246->reset); + + clk_disable_unprepare(hm1246->xclk); + + regulator_bulk_disable(ARRAY_SIZE(hm1246_supply_names), + hm1246->supplies); + + return 0; +} + +static int hm1246_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct hm1246 *hm1246 = to_hm1246(sd); + + if (code->index >= ARRAY_SIZE(hm1246_modes)) + return -EINVAL; + + code->code = hm1246_get_format_code(hm1246, &hm1246_modes[code->index]); + + return 0; +} + +static int hm1246_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct hm1246 *hm1246 = to_hm1246(subdev); + const struct hm1246_mode *mode; + + if (fse->index > 0) + return -EINVAL; + + mode = hm1246_find_mode_by_mbus_code(hm1246, fse->code); + if (!mode) + return -EINVAL; + + fse->min_width = mode->rect.width; + fse->max_width = mode->rect.width; + fse->min_height = mode->rect.height; + fse->max_height = mode->rect.height; + + return 0; +} + +static void hm1246_update_pad_format(struct hm1246 *hm1246, + const struct hm1246_mode *hm1246_mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->width = hm1246_mode->rect.width; + fmt->height = hm1246_mode->rect.height; + fmt->code = hm1246_get_format_code(hm1246, hm1246_mode); + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; +} + +static int hm1246_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct hm1246 *hm1246 = to_hm1246(sd); + struct v4l2_mbus_framefmt *mbus_fmt; + struct v4l2_rect *crop; + const struct hm1246_mode *mode; + + mode = hm1246_find_mode_by_mbus_code(hm1246, fmt->format.code); + if (!mode) + mode = &hm1246_modes[0]; + + crop = v4l2_subdev_state_get_crop(state, 0); + *crop = mode->rect; + + hm1246_update_pad_format(hm1246, mode, &fmt->format); + mbus_fmt = v4l2_subdev_state_get_format(state, 0); + *mbus_fmt = fmt->format; + + return 0; +} + +static int hm1246_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + const struct v4l2_mbus_framefmt *format; + const struct hm1246_mode *mode; + + format = v4l2_subdev_state_get_format(state, 0); + mode = v4l2_find_nearest_size(hm1246_modes, ARRAY_SIZE(hm1246_modes), + rect.width, rect.height, format->width, + format->height); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(state, 0); + return 0; + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = HM1246_NATIVE_WIDTH; + sel->r.height = HM1246_NATIVE_HEIGHT; + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r = mode->rect; + return 0; + + default: + return -EINVAL; + } +} + +static int hm1246_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct hm1246 *hm1246 = to_hm1246(sd); + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = 0, + .format = { + .code = hm1246_get_format_code(hm1246, + &hm1246_modes[0]), + .width = hm1246_modes[0].rect.width, + .height = hm1246_modes[0].rect.height, + }, + }; + + hm1246_set_format(sd, state, &fmt); + + return 0; +} + +static int hm1246_calc_pll(u32 xclk, u32 link_freq, u32 clocks_per_pixel, + u8 *pll1, u8 *pll2, u8 *pll3) +{ + static const u8 pclk_div_table[] = { 4, 5, 6, 7, 8, 12, 14, 16 }; + static const u8 sysclk_div_table[] = { 1, 2, 3, 4 }; + static const u8 post_div_table[] = { 1, 2, 4, 8 }; + static const int sysclk_pclk_ratio = 3; /* Recommended value */ + u32 pclk, vco_out; + int pclk_div_index, sysclk_div_index, post_div_index; + bool sysclk_pclk_ratio_found = false; + + if (link_freq < HM1246_PCLK_MIN || link_freq > HM1246_PCLK_MAX) + return -EINVAL; + + /* + * In raw mode (1 pixel per clock) the pixel clock is internally + * divided by two. + */ + pclk = 2 * link_freq / clocks_per_pixel; + + /* Find suitable PCLK and SYSCLK dividers. */ + for (pclk_div_index = 0; pclk_div_index < ARRAY_SIZE(pclk_div_table); + pclk_div_index++) { + for (sysclk_div_index = 0; + sysclk_div_index < ARRAY_SIZE(sysclk_div_table); + sysclk_div_index++) { + if (sysclk_div_table[sysclk_div_index] * + sysclk_pclk_ratio == + pclk_div_table[pclk_div_index]) { + sysclk_pclk_ratio_found = true; + break; + } + } + if (sysclk_pclk_ratio_found) + break; + } + + if (!sysclk_pclk_ratio_found) + return -EINVAL; + + /* Determine an appropriate post divider. */ + for (post_div_index = 0; post_div_index < ARRAY_SIZE(post_div_table); + post_div_index++) { + vco_out = pclk * pclk_div_table[pclk_div_index] * + post_div_table[post_div_index]; + + if (vco_out >= HM1246_PLL_VCO_MIN && + vco_out <= HM1246_PLL_VCO_MAX) + break; + } + if (post_div_index >= ARRAY_SIZE(post_div_table)) + return -EINVAL; + + /* Find pre-divider and multiplier values. */ + for (u32 div = DIV_ROUND_UP(xclk, HM1246_PLL_INCLK_MAX); + div <= xclk / HM1246_PLL_INCLK_MIN; div++) { + u32 multi, multi_h, multi_l, vco; + + multi = DIV_ROUND_CLOSEST_ULL((u64)vco_out * div, xclk); + if (multi < HM1246_PLL_MULTI_MIN || + multi > HM1246_PLL_MULTI_MAX) + continue; + + multi_h = multi / (HM1246_PLL_MULTI_H_MIN * + HM1246_PLL_MULTI_L_MAX) + + 2; + multi_l = multi / multi_h; + vco = div_u64((u64)xclk * multi_h * multi_l, div); + + if (vco != vco_out) + continue; + + if (pll1 && pll2 && pll3) { + *pll1 = HM1246_PLL1CFG_MULTIPLIER(multi_l - 1); + *pll2 = HM1246_PLL2CFG_PRE_DIV(div - 1) | + HM1246_PLL2CFG_MULTIPLIER(multi_h - 2); + *pll3 = HM1246_PLL3CFG_POST_DIV(post_div_index) | + HM1246_PLL3CFG_SYSCLK_DIV(sysclk_div_index) | + HM1246_PLL3CFG_PCLK_DIV(pclk_div_index); + } + + return 0; + } + + return -EINVAL; +} + +static int hm1246_cci_write_pll(struct hm1246 *hm1246, u8 pll1, u8 pll2, + u8 pll3) +{ + const struct cci_reg_sequence pll_regs[] = { + { HM1246_PLL1CFG_REG, pll1 }, + { HM1246_PLL2CFG_REG, pll2 }, + { HM1246_PLL3CFG_REG, pll3 }, + { HM1246_SBC_CTRL_REG, HM1246_SBC_CTRL_PLL_EN }, + }; + + return cci_multi_reg_write(hm1246->regmap, pll_regs, + ARRAY_SIZE(pll_regs), NULL); +} + +static int hm1246_pll_check_locked(struct hm1246 *hm1246) +{ + u64 boot_ref2; + int ret; + + ret = cci_read(hm1246->regmap, HM1246_SBC_BOOT_REF2_REG, &boot_ref2, + NULL); + if (ret) + return ret; + + return (boot_ref2 & HM1246_SBC_BOOT_REF2_PLL_LOCK) ? 0 : -EIO; +} + +static int hm1246_setup_pll(struct hm1246 *hm1246, + const struct hm1246_mode *mode) +{ + u8 pll1, pll2, pll3; + int ret; + + ret = hm1246_calc_pll(hm1246->xclk_freq, hm1246->link_frequency, + mode->clocks_per_pixel, &pll1, &pll2, &pll3); + if (ret) + return ret; + + ret = hm1246_cci_write_pll(hm1246, pll1, pll2, pll3); + if (ret) + return ret; + + /* PLL lock time (tpll): 100us (typical) */ + fsleep(200); + + return hm1246_pll_check_locked(hm1246); +} + +static int hm1246_cci_write_test_pattern(struct hm1246 *hm1246, u8 mode, + u16 r, u16 g, u16 b) +{ + const struct cci_reg_sequence tpg_enable_regs[] = { + { HM1246_TEST_DATA_RED_REG, r }, + { HM1246_TEST_DATA_GR_REG, g }, + { HM1246_TEST_DATA_GB_REG, g }, + { HM1246_TEST_DATA_BLUE_REG, b }, + { HM1246_TEST_PATTERN_MODE_REG, mode }, + }; + + return cci_multi_reg_write(hm1246->regmap, tpg_enable_regs, + ARRAY_SIZE(tpg_enable_regs), NULL); +} + +static int hm1246_test_pattern(struct hm1246 *hm1246, u32 index) +{ + static const u16 RGBMIN = 0, RGBMAX = 0x3ff; + static const struct tp { + int pattern; + u16 r, g, b; + } tps[] = { + /* Disabled */ + [0] = { .pattern = 0, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Checkboard pattern */ + [1] = { .pattern = 0, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Ramp */ + [2] = { .pattern = 1, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Moving ones */ + [3] = { .pattern = 2, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Blending color bars */ + [4] = { .pattern = 3, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Color bars */ + [5] = { .pattern = 4, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Solid white */ + [6] = { .pattern = 15, .r = RGBMAX, .g = RGBMAX, .b = RGBMAX }, + /* Solid black */ + [7] = { .pattern = 15, .r = RGBMIN, .g = RGBMIN, .b = RGBMIN }, + /* Solid red */ + [8] = { .pattern = 15, .r = RGBMAX, .g = RGBMIN, .b = RGBMIN }, + /* Solid green */ + [9] = { .pattern = 15, .r = RGBMIN, .g = RGBMAX, .b = RGBMIN }, + /* Solid blue */ + [10] = { .pattern = 15, .r = RGBMIN, .g = RGBMIN, .b = RGBMAX }, + }; + u8 mode; + + if (index >= ARRAY_SIZE(tps)) + return -EINVAL; + + mode = HM1246_TEST_PATTERN_MODE_MODE(tps[index].pattern); + if (index) + mode |= HM1246_TEST_PATTERN_MODE_ENABLE; + + return hm1246_cci_write_test_pattern(hm1246, mode, tps[index].r, + tps[index].g, tps[index].b); +} + +static int hm1246_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct hm1246 *hm1246 = + container_of_const(ctrl->handler, struct hm1246, ctrls); + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *format; + u32 val; + bool needs_cmu_update = true; + int ret; + + state = v4l2_subdev_get_locked_active_state(&hm1246->sd); + format = v4l2_subdev_state_get_format(state, 0); + + if (ctrl->id == V4L2_CID_VBLANK) { + s64 exposure_max; + + exposure_max = + format->height + ctrl->val - HM1246_COARSE_INTG_MARGIN; + ret = __v4l2_ctrl_modify_range(hm1246->exposure_ctrl, + hm1246->exposure_ctrl->minimum, + exposure_max, + hm1246->exposure_ctrl->step, + exposure_max); + + if (ret) { + dev_err(hm1246->dev, "exposure ctrl range update failed\n"); + return ret; + } + } + + if (!pm_runtime_get_if_active(hm1246->dev)) + return 0; + + ret = 0; + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + cci_write(hm1246->regmap, HM1246_COARSE_INTG_REG, ctrl->val, + &ret); + break; + + case V4L2_CID_ANALOGUE_GAIN: + cci_write(hm1246->regmap, HM1246_ANALOG_GLOBAL_GAIN_REG, + ctrl->val, &ret); + break; + + case V4L2_CID_VBLANK: + val = format->height + ctrl->val; + cci_write(hm1246->regmap, HM1246_FRAME_LENGTH_LINES_REG, val, + &ret); + break; + + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + val = 0; + if (hm1246->hflip_ctrl->val) + val |= HM1246_IMAGE_ORIENTATION_HFLIP; + if (hm1246->vflip_ctrl->val) + val |= HM1246_IMAGE_ORIENTATION_VFLIP; + + cci_write(hm1246->regmap, HM1246_IMAGE_ORIENTATION_REG, val, + &ret); + break; + + case V4L2_CID_TEST_PATTERN: + ret = hm1246_test_pattern(hm1246, ctrl->val); + needs_cmu_update = false; + break; + + default: + ret = -EINVAL; + needs_cmu_update = false; + break; + } + + if (needs_cmu_update) + cci_write(hm1246->regmap, HM1246_CMU_UPDATE_REG, 0, &ret); + + pm_runtime_put(hm1246->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops hm1246_ctrl_ops = { + .s_ctrl = hm1246_set_ctrl, +}; + +static int hm1246_identify_module(struct hm1246 *hm1246) +{ + u64 model_id; + int ret; + + if (hm1246->identified) + return 0; + + ret = cci_read(hm1246->regmap, HM1246_MODEL_ID_REG, &model_id, NULL); + if (ret) + return ret; + + if (model_id != HM1246_MODEL_ID) { + dev_err(hm1246->dev, "model id mismatch: 0x%llx!=0x%x\n", + model_id, HM1246_MODEL_ID); + return -ENXIO; + } + + hm1246->identified = true; + + return 0; +} + +static int hm1246_setup_moderegs(struct hm1246 *hm1246, + const struct hm1246_mode *mode) +{ + const struct hm1246_reg_list *reg_list = &mode->reg_list; + const struct cci_reg_sequence modeaw[] = { + { HM1246_X_ADDR_START_REG, mode->rect.left }, + { HM1246_Y_ADDR_START_REG, mode->rect.top }, + { HM1246_X_ADDR_END_REG, mode->rect.width - 1 }, + { HM1246_Y_ADDR_END_REG, mode->rect.height - 1 }, + { HM1246_DWIN_XOFFSET_REG, mode->rect.left }, + { HM1246_DWIN_YOFFSET_REG, mode->rect.top }, + { HM1246_DWIN_XSIZE_REG, mode->rect.width }, + { HM1246_DWIN_YSIZE_REG, mode->rect.height }, + { HM1246_LINE_LENGTH_PCK_REG, mode->hts }, + }; + int ret = 0; + + cci_multi_reg_write(hm1246->regmap, modeaw, ARRAY_SIZE(modeaw), &ret); + cci_multi_reg_write(hm1246->regmap, reg_list->regs, + reg_list->num_of_regs, &ret); + + return ret; +} + +static int hm1246_setup_bus(struct hm1246 *hm1246) +{ + u64 polarity_ctrl = 0, pclk_ctrl = 0; + int ret = 0; + + if (hm1246->mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + polarity_ctrl |= HM1246_POLARITY_CTRL_HSYNC; + + if (hm1246->mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + polarity_ctrl |= HM1246_POLARITY_CTRL_VSYNC; + + cci_write(hm1246->regmap, HM1246_POLARITY_CTRL_REG, polarity_ctrl, + &ret); + + /* + * If the clock output polarity flag PCLK_CTRL[3] is set (high), the + * data lines change state on the falling edge of PCLK and should + * therefore be sampled on the rising edge. + * This is different than described in the data sheet. + */ + if (hm1246->mbus_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + pclk_ctrl |= HM1246_PCLK_CTRL_POL; + + cci_write(hm1246->regmap, HM1246_PCLK_CTRL_REG, pclk_ctrl, &ret); + + return ret; +} + +static int hm1246_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct hm1246 *hm1246 = to_hm1246(sd); + const struct v4l2_mbus_framefmt *format; + const struct hm1246_mode *mode; + int ret; + + format = v4l2_subdev_state_get_format(state, 0); + mode = v4l2_find_nearest_size(hm1246_modes, ARRAY_SIZE(hm1246_modes), + rect.width, rect.height, format->width, + format->height); + + ret = pm_runtime_resume_and_get(hm1246->dev); + if (ret) + return ret; + + ret = hm1246_identify_module(hm1246); + if (ret) + goto err_rpm_put; + + ret = hm1246_setup_pll(hm1246, mode); + if (ret) { + dev_err(hm1246->dev, "failed to setup PLL\n"); + goto err_rpm_put; + } + + ret = hm1246_setup_moderegs(hm1246, mode); + if (ret) + goto err_rpm_put; + + ret = hm1246_setup_bus(hm1246); + if (ret) + goto err_rpm_put; + + ret = __v4l2_ctrl_handler_setup(&hm1246->ctrls); + if (ret) { + dev_err(hm1246->dev, "failed to setup v4l2 controls\n"); + goto err_rpm_put; + } + + ret = cci_write(hm1246->regmap, HM1246_MODE_SELECT_REG, + HM1246_MODE_SELECT_STREAM, NULL); + if (ret) + goto err_rpm_put; + + /* + * Since mirroring may change the actual pixel format, it must not be + * changed during streaming. + */ + __v4l2_ctrl_grab(hm1246->vflip_ctrl, true); + __v4l2_ctrl_grab(hm1246->hflip_ctrl, true); + + return 0; + +err_rpm_put: + pm_runtime_put_autosuspend(hm1246->dev); + + return ret; +} + +static int hm1246_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct hm1246 *hm1246 = to_hm1246(sd); + int ret; + + ret = cci_write(hm1246->regmap, HM1246_MODE_SELECT_REG, + HM1246_MODE_SELECT_STANDBY, NULL); + + __v4l2_ctrl_grab(hm1246->vflip_ctrl, false); + __v4l2_ctrl_grab(hm1246->hflip_ctrl, false); + + pm_runtime_put_autosuspend(hm1246->dev); + + return ret; +} + +static const struct v4l2_subdev_video_ops hm1246_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops hm1246_subdev_pad_ops = { + .enum_mbus_code = hm1246_enum_mbus_code, + .enum_frame_size = hm1246_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = hm1246_set_format, + .get_selection = hm1246_get_selection, + .enable_streams = hm1246_enable_streams, + .disable_streams = hm1246_disable_streams, +}; + +static const struct v4l2_subdev_ops hm1246_subdev_ops = { + .video = &hm1246_video_ops, + .pad = &hm1246_subdev_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops hm1246_internal_ops = { + .init_state = hm1246_init_state, +}; + +static int hm1246_get_regulators(struct device *dev, struct hm1246 *hm1246) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(hm1246_supply_names); i++) + hm1246->supplies[i].supply = hm1246_supply_names[i]; + + return devm_regulator_bulk_get(dev, ARRAY_SIZE(hm1246_supply_names), + hm1246->supplies); +} + +static int hm1246_parse_fwnode(struct hm1246 *hm1246) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_PARALLEL, + }; + int ret; + + endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(hm1246->dev), + 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); + fwnode_handle_put(endpoint); + if (ret) + return dev_err_probe(hm1246->dev, ret, + "parsing endpoint node failed\n"); + + hm1246->mbus_flags = bus_cfg.bus.parallel.flags; + + if (bus_cfg.nr_of_link_frequencies > 0) + hm1246->link_frequency = bus_cfg.link_frequencies[0]; + + v4l2_fwnode_endpoint_free(&bus_cfg); + + if (!hm1246->link_frequency) + return dev_err_probe(hm1246->dev, -EINVAL, + "one link frequency expected\n"); + + return 0; +} + +static int hm1246_init_controls(struct hm1246 *hm1246) +{ + const struct hm1246_mode *mode = &hm1246_modes[0]; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr = &hm1246->ctrls; + struct v4l2_ctrl *ctrl; + s64 pixel_rate, exposure_max, vblank_min, hblank; + int ret; + + ret = v4l2_fwnode_device_parse(hm1246->dev, &props); + if (ret) + return ret; + + v4l2_ctrl_handler_init(ctrl_hdlr, 11); + + hm1246->hflip_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (hm1246->hflip_ctrl) + hm1246->hflip_ctrl->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + hm1246->vflip_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (hm1246->vflip_ctrl) + hm1246->vflip_ctrl->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_cluster(2, &hm1246->hflip_ctrl); + + ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_LINK_FREQ, + 0, 0, + &hm1246->link_frequency); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate = div_u64(hm1246->link_frequency, mode->clocks_per_pixel); + ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_PIXEL_RATE, + pixel_rate, pixel_rate, 1, + pixel_rate); + + vblank_min = mode->vts_min - mode->rect.height; + ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_VBLANK, vblank_min, + HM1246_VTS_MAX - mode->rect.height, + 1, vblank_min); + + hblank = mode->hts - mode->rect.width; + ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, + 1, hblank); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + HM1246_ANALOG_GLOBAL_GAIN_MIN, + HM1246_ANALOG_GLOBAL_GAIN_MAX, + HM1246_ANALOG_GLOBAL_GAIN_STEP, + HM1246_ANALOG_GLOBAL_GAIN_MIN); + + exposure_max = mode->vts_min - HM1246_COARSE_INTG_MARGIN; + hm1246->exposure_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_EXPOSURE, + HM1246_COARSE_INTG_MIN, + exposure_max, + HM1246_COARSE_INTG_STEP, + exposure_max); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &hm1246_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(hm1246_test_pattern_menu) - 1, + 0, 0, hm1246_test_pattern_menu); + + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &hm1246_ctrl_ops, &props); + + if (ctrl_hdlr->error) { + v4l2_ctrl_handler_free(ctrl_hdlr); + return ctrl_hdlr->error; + } + + hm1246->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static int hm1246_probe(struct i2c_client *client) +{ + struct hm1246 *hm1246; + int ret; + + hm1246 = devm_kzalloc(&client->dev, sizeof(*hm1246), GFP_KERNEL); + if (!hm1246) + return -ENOMEM; + + hm1246->dev = &client->dev; + + ret = hm1246_parse_fwnode(hm1246); + if (ret) + return ret; + + hm1246->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(hm1246->regmap)) + return dev_err_probe(hm1246->dev, PTR_ERR(hm1246->regmap), + "failed to init CCI\n"); + + hm1246->xclk = devm_v4l2_sensor_clk_get(hm1246->dev, NULL); + if (IS_ERR(hm1246->xclk)) + return dev_err_probe(hm1246->dev, PTR_ERR(hm1246->xclk), + "failed to get xclk\n"); + + hm1246->xclk_freq = clk_get_rate(hm1246->xclk); + if (hm1246->xclk_freq < HM1246_XCLK_MIN || + hm1246->xclk_freq > HM1246_XCLK_MAX) + return dev_err_probe(hm1246->dev, -EINVAL, + "xclk frequency out of range: %luHz\n", + hm1246->xclk_freq); + + for (unsigned int i = 0; i < ARRAY_SIZE(hm1246_modes); i++) { + ret = hm1246_calc_pll(hm1246->xclk_freq, hm1246->link_frequency, + hm1246_modes[i].clocks_per_pixel, + NULL, NULL, NULL); + if (ret) + return dev_err_probe(hm1246->dev, ret, + "no PLL setup for %lld Hz\n", + hm1246->link_frequency); + } + + ret = hm1246_get_regulators(hm1246->dev, hm1246); + if (ret) + return dev_err_probe(hm1246->dev, ret, + "failed to get regulators\n"); + + hm1246->reset = devm_reset_control_get_optional(hm1246->dev, NULL); + if (IS_ERR(hm1246->reset)) + return dev_err_probe(hm1246->dev, PTR_ERR(hm1246->reset), + "failed to get reset\n"); + reset_control_assert(hm1246->reset); + + v4l2_i2c_subdev_init(&hm1246->sd, client, &hm1246_subdev_ops); + hm1246->sd.internal_ops = &hm1246_internal_ops; + + ret = hm1246_init_controls(hm1246); + if (ret) + return dev_err_probe(hm1246->dev, ret, + "failed to init controls\n"); + + hm1246->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + hm1246->pad.flags = MEDIA_PAD_FL_SOURCE; + hm1246->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + ret = media_entity_pads_init(&hm1246->sd.entity, 1, &hm1246->pad); + if (ret) { + dev_err_probe(hm1246->dev, ret, "failed to init media pads\n"); + goto err_v4l2_ctrl_handler_free; + } + + hm1246->sd.state_lock = hm1246->ctrls.lock; + ret = v4l2_subdev_init_finalize(&hm1246->sd); + if (ret) { + dev_err_probe(hm1246->dev, ret, "failed to init v4l2 subdev\n"); + goto err_media_entity_cleanup; + } + + pm_runtime_enable(hm1246->dev); + pm_runtime_set_autosuspend_delay(hm1246->dev, 1000); + pm_runtime_use_autosuspend(hm1246->dev); + + ret = v4l2_async_register_subdev_sensor(&hm1246->sd); + if (ret) { + dev_err_probe(hm1246->dev, ret, + "failed to register v4l2 subdev\n"); + goto err_subdev_cleanup; + } + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(&hm1246->sd); + pm_runtime_disable(hm1246->dev); + pm_runtime_set_suspended(hm1246->dev); + +err_media_entity_cleanup: + media_entity_cleanup(&hm1246->sd.entity); + +err_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(&hm1246->ctrls); + + return ret; +} + +static void hm1246_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct hm1246 *hm1246 = to_hm1246(sd); + + v4l2_async_unregister_subdev(&hm1246->sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&hm1246->sd.entity); + v4l2_ctrl_handler_free(&hm1246->ctrls); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + hm1246_power_off(hm1246->dev); + pm_runtime_set_suspended(&client->dev); + } +} + +static const struct of_device_id hm1246_of_match[] = { + { .compatible = "himax,hm1246" }, + {} +}; +MODULE_DEVICE_TABLE(of, hm1246_of_match); + +static DEFINE_RUNTIME_DEV_PM_OPS(hm1246_pm_ops, + hm1246_power_off, hm1246_power_on, NULL); + +static struct i2c_driver hm1246_i2c_driver = { + .driver = { + .of_match_table = hm1246_of_match, + .pm = pm_ptr(&hm1246_pm_ops), + .name = "hm1246", + }, + .probe = hm1246_probe, + .remove = hm1246_remove, +}; +module_i2c_driver(hm1246_i2c_driver); + +MODULE_DESCRIPTION("Himax HM1246 camera driver"); +MODULE_AUTHOR("Matthias Fend "); +MODULE_LICENSE("GPL"); From 6fc8b1e568474e825572b57f363dafce8582178f Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:03 +0200 Subject: [PATCH 139/596] media: stm32: dcmipp: share struct dcmipp_device among subdevs In preparation of need for sharing of data between subdevices, make the struct dcmipp_device structure part of dcmipp_common.h and share it with subdevs at init time. This allows for simplifying parameters of each subdev init function as well. Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../st/stm32/stm32-dcmipp/dcmipp-bytecap.c | 13 +++--- .../st/stm32/stm32-dcmipp/dcmipp-byteproc.c | 11 ++--- .../st/stm32/stm32-dcmipp/dcmipp-common.h | 43 ++++++++++++++----- .../st/stm32/stm32-dcmipp/dcmipp-core.c | 33 ++------------ .../st/stm32/stm32-dcmipp/dcmipp-input.c | 12 +++--- 5 files changed, 55 insertions(+), 57 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 f0e809458489..483af0f39f0d 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c @@ -857,12 +857,11 @@ static const struct media_entity_operations dcmipp_bytecap_entity_ops = { .link_validate = dcmipp_bytecap_link_validate, }; -struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev, - const char *entity_name, - struct v4l2_device *v4l2_dev, - void __iomem *regs) +struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp) { struct dcmipp_bytecap_device *vcap; + struct device *dev = dcmipp->dev; struct video_device *vdev; struct vb2_queue *q; const unsigned long pad_flag = MEDIA_PAD_FL_SINK; @@ -880,6 +879,8 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev, goto err_free_vcap; } + vcap->ved.dcmipp = dcmipp; + /* Initialize the media entity */ vcap->vdev.entity.name = entity_name; vcap->vdev.entity.function = MEDIA_ENT_F_IO_V4L; @@ -930,7 +931,7 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev, vcap->ved.handler = dcmipp_bytecap_irq_callback; vcap->ved.thread_fn = dcmipp_bytecap_irq_thread; vcap->dev = dev; - vcap->regs = regs; + vcap->regs = dcmipp->regs; /* Initialize the video_device struct */ vdev = &vcap->vdev; @@ -941,7 +942,7 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev, vdev->ioctl_ops = &dcmipp_bytecap_ioctl_ops; vdev->lock = &vcap->lock; vdev->queue = q; - vdev->v4l2_dev = v4l2_dev; + vdev->v4l2_dev = &dcmipp->v4l2_dev; strscpy(vdev->name, entity_name, sizeof(vdev->name)); video_set_drvdata(vdev, &vcap->ved); 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 0c7aeb0888f6..5fcd5123136f 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c @@ -571,8 +571,8 @@ void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved) } struct dcmipp_ent_device * -dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name, - struct v4l2_device *v4l2_dev, void __iomem *regs) +dcmipp_byteproc_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp) { struct dcmipp_byteproc_device *byteproc; const unsigned long pads_flag[] = { @@ -585,11 +585,11 @@ dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name, if (!byteproc) return ERR_PTR(-ENOMEM); - byteproc->regs = regs; + byteproc->regs = dcmipp->regs; /* Initialize ved and sd */ ret = dcmipp_ent_sd_register(&byteproc->ved, &byteproc->sd, - v4l2_dev, entity_name, + &dcmipp->v4l2_dev, entity_name, MEDIA_ENT_F_PROC_VIDEO_SCALER, ARRAY_SIZE(pads_flag), pads_flag, &dcmipp_byteproc_int_ops, @@ -600,7 +600,8 @@ dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name, return ERR_PTR(ret); } - byteproc->dev = dev; + byteproc->ved.dcmipp = dcmipp; + byteproc->dev = dcmipp->dev; return &byteproc->ved; } diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h index fe5f97233f5e..e5c0eda8b18a 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h @@ -58,10 +58,36 @@ do { \ (fmt)->xfer_func = DCMIPP_XFER_FUNC_DEFAULT; \ } while (0) +struct dcmipp_device { + /* The platform device */ + struct platform_device pdev; + struct device *dev; + + /* Hardware resources */ + void __iomem *regs; + struct clk *mclk; + struct clk *kclk; + + /* The pipeline configuration */ + const struct dcmipp_pipeline_config *pipe_cfg; + + /* The Associated media_device parent */ + struct media_device mdev; + + /* Internal v4l2 parent device*/ + struct v4l2_device v4l2_dev; + + /* Entities */ + struct dcmipp_ent_device **entity; + + struct v4l2_async_notifier notifier; +}; + /** * struct dcmipp_ent_device - core struct that represents a node in the topology * * @ent: the pointer to struct media_entity for the node + * @dcmipp: the pointer to the parent dcmipp_device * @pads: the list of pads of the node * @bus: struct v4l2_mbus_config_parallel describing input bus * @bus_type: type of input bus (parallel or BT656) @@ -84,6 +110,7 @@ do { \ */ struct dcmipp_ent_device { struct media_entity *ent; + struct dcmipp_device *dcmipp; struct media_pad *pads; /* Parallel input device */ @@ -199,19 +226,15 @@ static inline void __reg_clear(struct device *dev, void __iomem *base, u32 reg, } /* DCMIPP subdev init / release entry points */ -struct dcmipp_ent_device *dcmipp_inp_ent_init(struct device *dev, - const char *entity_name, - struct v4l2_device *v4l2_dev, - void __iomem *regs); +struct dcmipp_ent_device *dcmipp_inp_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp); void dcmipp_inp_ent_release(struct dcmipp_ent_device *ved); struct dcmipp_ent_device * -dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name, - struct v4l2_device *v4l2_dev, void __iomem *regs); +dcmipp_byteproc_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp); void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved); -struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev, - const char *entity_name, - struct v4l2_device *v4l2_dev, - void __iomem *regs); +struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp); void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved); #endif 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 516334541b2c..f526680391b4 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c @@ -33,31 +33,6 @@ .flags = link_flags, \ } -struct dcmipp_device { - /* The platform device */ - struct platform_device pdev; - struct device *dev; - - /* Hardware resources */ - void __iomem *regs; - struct clk *mclk; - struct clk *kclk; - - /* The pipeline configuration */ - const struct dcmipp_pipeline_config *pipe_cfg; - - /* The Associated media_device parent */ - struct media_device mdev; - - /* Internal v4l2 parent device*/ - struct v4l2_device v4l2_dev; - - /* Entities */ - struct dcmipp_ent_device **entity; - - struct v4l2_async_notifier notifier; -}; - static inline struct dcmipp_device * notifier_to_dcmipp(struct v4l2_async_notifier *n) { @@ -68,8 +43,8 @@ notifier_to_dcmipp(struct v4l2_async_notifier *n) struct dcmipp_ent_config { const char *name; struct dcmipp_ent_device *(*init) - (struct device *dev, const char *entity_name, - struct v4l2_device *v4l2_dev, void __iomem *regs); + (const char *entity_name, + struct dcmipp_device *dcmipp); void (*release)(struct dcmipp_ent_device *ved); }; @@ -221,9 +196,7 @@ static int dcmipp_create_subdevs(struct dcmipp_device *dcmipp) dev_dbg(dcmipp->dev, "add subdev %s\n", name); dcmipp->entity[i] = - dcmipp->pipe_cfg->ents[i].init(dcmipp->dev, name, - &dcmipp->v4l2_dev, - dcmipp->regs); + dcmipp->pipe_cfg->ents[i].init(name, dcmipp); if (IS_ERR(dcmipp->entity[i])) { dev_err(dcmipp->dev, "failed to init subdev %s\n", name); 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 7d3f5857cbfe..564f37e2e1dd 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c @@ -515,15 +515,14 @@ void dcmipp_inp_ent_release(struct dcmipp_ent_device *ved) dcmipp_ent_sd_unregister(ved, &inp->sd); } -struct dcmipp_ent_device *dcmipp_inp_ent_init(struct device *dev, - const char *entity_name, - struct v4l2_device *v4l2_dev, - void __iomem *regs) +struct dcmipp_ent_device *dcmipp_inp_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp) { struct dcmipp_inp_device *inp; const unsigned long pads_flag[] = { MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE, }; + struct device *dev = dcmipp->dev; int ret; /* Allocate the inp struct */ @@ -531,10 +530,10 @@ struct dcmipp_ent_device *dcmipp_inp_ent_init(struct device *dev, if (!inp) return ERR_PTR(-ENOMEM); - inp->regs = regs; + inp->regs = dcmipp->regs; /* Initialize ved and sd */ - ret = dcmipp_ent_sd_register(&inp->ved, &inp->sd, v4l2_dev, + ret = dcmipp_ent_sd_register(&inp->ved, &inp->sd, &dcmipp->v4l2_dev, entity_name, MEDIA_ENT_F_VID_IF_BRIDGE, ARRAY_SIZE(pads_flag), pads_flag, &dcmipp_inp_int_ops, &dcmipp_inp_ops, @@ -543,6 +542,7 @@ struct dcmipp_ent_device *dcmipp_inp_ent_init(struct device *dev, kfree(inp); return ERR_PTR(ret); } + inp->ved.dcmipp = dcmipp; inp->dev = dev; From 8cec4b08ceae4e7963b0bc266a12e237b9e4510f Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:04 +0200 Subject: [PATCH 140/596] media: stm32: dcmipp: make dcmipp_state & cmsr2 read common In preparation of the introduction of the pixel pipes capture devices, move struct dcmipp_state into common header and perform interrupt status register CMSR2 into the core interrupt handler and share the value with each subdevs. Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c | 11 ++++------- .../platform/st/stm32/stm32-dcmipp/dcmipp-common.h | 8 ++++++++ .../platform/st/stm32/stm32-dcmipp/dcmipp-core.c | 7 +++++++ 3 files changed, 19 insertions(+), 7 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 483af0f39f0d..0e789bbd1198 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c @@ -110,12 +110,6 @@ struct dcmipp_buf { struct list_head list; }; -enum dcmipp_state { - DCMIPP_STOPPED = 0, - DCMIPP_WAIT_FOR_BUFFER, - DCMIPP_RUNNING, -}; - struct dcmipp_bytecap_device { struct dcmipp_ent_device ved; struct video_device vdev; @@ -799,9 +793,12 @@ static irqreturn_t dcmipp_bytecap_irq_callback(int irq, void *arg) { struct dcmipp_bytecap_device *vcap = container_of(arg, struct dcmipp_bytecap_device, ved); + struct dcmipp_ent_device *ved = arg; /* Store interrupt status register */ - vcap->cmsr2 = reg_read(vcap, DCMIPP_CMSR2) & DCMIPP_CMIER_P0ALL; + vcap->cmsr2 = ved->cmsr2 & DCMIPP_CMIER_P0ALL; + if (!vcap->cmsr2) + return IRQ_HANDLED; vcap->count.it++; /* Clear interrupt */ diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h index e5c0eda8b18a..adcd16664cf5 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h @@ -94,6 +94,7 @@ struct dcmipp_device { * @handler: irq handler dedicated to the subdev * @handler_ret: value returned by the irq handler * @thread_fn: threaded irq handler + * @cmsr2: dcmipp status reg value captured upon an interrupt * * The DCMIPP provides a single IRQ line and a IRQ status registers for all * subdevs, hence once the main irq handler (registered at probe time) is @@ -119,6 +120,13 @@ struct dcmipp_ent_device { irq_handler_t handler; irqreturn_t handler_ret; irq_handler_t thread_fn; + u32 cmsr2; +}; + +enum dcmipp_state { + DCMIPP_STOPPED = 0, + DCMIPP_WAIT_FOR_BUFFER, + DCMIPP_RUNNING, }; /** 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 f526680391b4..e9fd1980926a 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c @@ -33,6 +33,8 @@ .flags = link_flags, \ } +#define DCMIPP_CMSR2 0x3f8 + static inline struct dcmipp_device * notifier_to_dcmipp(struct v4l2_async_notifier *n) { @@ -251,10 +253,15 @@ static irqreturn_t dcmipp_irq_callback(int irq, void *arg) struct dcmipp_ent_device *ved; irqreturn_t ret = IRQ_HANDLED; unsigned int i; + u32 cmsr2; + + /* Centralized read of CMSR2 */ + cmsr2 = reg_read(dcmipp, DCMIPP_CMSR2); /* Call irq handler of each entities of pipeline */ for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) { ved = dcmipp->entity[i]; + ved->cmsr2 = cmsr2; if (ved->handler) ved->handler_ret = ved->handler(irq, ved); else if (ved->thread_fn) From 5e33378cd77e163094a27dbc2a57c07a5fcb8301 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:05 +0200 Subject: [PATCH 141/596] media: stm32: dcmipp: bytecap: protect CMIER register access CMIER register is common between all pipes and thus needs to be protected from concurrent access. The struct v4l2_device structure, unique to the whole driver embeds a spin_lock which can also be used by the driver itself as explained in its description. Rely on this spin_lock to protect from concurrent access to the CMIER register. Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c | 4 ++++ 1 file changed, 4 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 0e789bbd1198..f6011b42203c 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c @@ -438,7 +438,9 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, dcmipp_start_capture(vcap, vcap->next); /* Enable interruptions */ + spin_lock(&vcap->vdev.v4l2_dev->lock); reg_set(vcap, DCMIPP_CMIER, DCMIPP_CMIER_P0ALL); + spin_unlock(&vcap->vdev.v4l2_dev->lock); vcap->state = DCMIPP_RUNNING; @@ -494,7 +496,9 @@ static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) media_pipeline_stop(vcap->vdev.entity.pads); /* Disable interruptions */ + spin_lock(&vcap->vdev.v4l2_dev->lock); reg_clear(vcap, DCMIPP_CMIER, DCMIPP_CMIER_P0ALL); + spin_unlock(&vcap->vdev.v4l2_dev->lock); /* Stop capture */ reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ); From 48005eb35deba28c20781107cf998ce52086f577 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:06 +0200 Subject: [PATCH 142/596] media: stm32: dcmipp: move common structures in dcmipp-common.h Move the structure dcmipp_pipeline_config into dcmipp-common.h so that all subdeves can have access to the information of capabilities. Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../st/stm32/stm32-dcmipp/dcmipp-common.h | 37 +++++++++++++++++++ .../st/stm32/stm32-dcmipp/dcmipp-core.c | 37 ------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h index adcd16664cf5..b94a9c87f56e 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h @@ -83,6 +83,43 @@ struct dcmipp_device { struct v4l2_async_notifier notifier; }; +#define DCMIPP_ENT_LINK(src, srcpad, sink, sinkpad, link_flags) { \ + .src_ent = src, \ + .src_pad = srcpad, \ + .sink_ent = sink, \ + .sink_pad = sinkpad, \ + .flags = link_flags, \ +} + +/* Structure which describes individual configuration for each entity */ +struct dcmipp_ent_config { + const char *name; + struct dcmipp_ent_device *(*init) + (const char *entity_name, + struct dcmipp_device *dcmipp); + void (*release)(struct dcmipp_ent_device *ved); +}; + +/* Structure which describes links between entities */ +struct dcmipp_ent_link { + unsigned int src_ent; + u16 src_pad; + unsigned int sink_ent; + u16 sink_pad; + u32 flags; +}; + +/* Structure which describes the whole topology */ +struct dcmipp_pipeline_config { + const struct dcmipp_ent_config *ents; + size_t num_ents; + const struct dcmipp_ent_link *links; + size_t num_links; + u32 hw_revision; + bool has_csi2; + bool needs_mclk; +}; + /** * struct dcmipp_ent_device - core struct that represents a node in the topology * 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 e9fd1980926a..8f7e131238fc 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c @@ -25,14 +25,6 @@ #define DCMIPP_MDEV_MODEL_NAME "DCMIPP MDEV" -#define DCMIPP_ENT_LINK(src, srcpad, sink, sinkpad, link_flags) { \ - .src_ent = src, \ - .src_pad = srcpad, \ - .sink_ent = sink, \ - .sink_pad = sinkpad, \ - .flags = link_flags, \ -} - #define DCMIPP_CMSR2 0x3f8 static inline struct dcmipp_device * @@ -41,35 +33,6 @@ notifier_to_dcmipp(struct v4l2_async_notifier *n) return container_of(n, struct dcmipp_device, notifier); } -/* Structure which describes individual configuration for each entity */ -struct dcmipp_ent_config { - const char *name; - struct dcmipp_ent_device *(*init) - (const char *entity_name, - struct dcmipp_device *dcmipp); - void (*release)(struct dcmipp_ent_device *ved); -}; - -/* Structure which describes links between entities */ -struct dcmipp_ent_link { - unsigned int src_ent; - u16 src_pad; - unsigned int sink_ent; - u16 sink_pad; - u32 flags; -}; - -/* Structure which describes the whole topology */ -struct dcmipp_pipeline_config { - const struct dcmipp_ent_config *ents; - size_t num_ents; - const struct dcmipp_ent_link *links; - size_t num_links; - u32 hw_revision; - bool has_csi2; - bool needs_mclk; -}; - /* -------------------------------------------------------------------------- * Topology Configuration */ From ff2b9d2ccb7efc33d3e6315380fe916558d363b0 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:07 +0200 Subject: [PATCH 143/596] media: stm32: dcmipp: correct swap in YUYV data with parallel input When used with parallel input, the DCMIPP is expecting data to come in YUYV order (for all DUMP/MAIN and AUX pipes). Not doing so will lead to bad color generated by the pipes when processing is done. The DUMP pipe is also doing by default a swap since, while it accepts YUYV data, it will by default generate UYVY data. Current implementation is not correct for parallel input since it is performing a cycle swap on the input side and since the dump pipe is also internally doing a swap, the data captured from the dump pipe are correct, while the data captured from the main / aux pipes are not. To correct this, only perform cycle swap when it is necessary, hence changing from YUYV to UYVY for example, and for all parallel YUV MBUS, add the P0PPCR based SWAPYUV to put back the data into the ordering of the input. Keep previous behavior when the SWAPYUV is not available (such as stm32mp13). Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../st/stm32/stm32-dcmipp/dcmipp-byteproc.c | 16 ++++++++++- .../st/stm32/stm32-dcmipp/dcmipp-common.h | 1 + .../st/stm32/stm32-dcmipp/dcmipp-core.c | 3 +- .../st/stm32/stm32-dcmipp/dcmipp-input.c | 28 +++++++++++++------ 4 files changed, 38 insertions(+), 10 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 5fcd5123136f..f61618e12322 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c @@ -25,6 +25,7 @@ #define DCMIPP_P0SCSZR_HSIZE_SHIFT 0 #define DCMIPP_P0SCSZR_VSIZE_SHIFT 16 #define DCMIPP_P0PPCR 0x5c0 +#define DCMIPP_P0PPCR_SWAPYUV BIT(0) #define DCMIPP_P0PPCR_BSM_1_2 0x1 #define DCMIPP_P0PPCR_BSM_1_4 0x2 #define DCMIPP_P0PPCR_BSM_2_4 0x3 @@ -428,9 +429,11 @@ static int dcmipp_byteproc_configure_scale_crop if (!vpix) return -EINVAL; - /* clear decimation/crop */ + /* clear decimation/crop/swap yuv */ reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_BSM_MASK); reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_LSM); + if (byteproc->ved.dcmipp->pipe_cfg->has_swapyuv) + reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_SWAPYUV); reg_write(byteproc, DCMIPP_P0SCSTR, 0); reg_write(byteproc, DCMIPP_P0SCSZR, 0); @@ -451,6 +454,17 @@ static int dcmipp_byteproc_configure_scale_crop if (vprediv == 2) val |= DCMIPP_P0PPCR_LSM | DCMIPP_P0PPCR_OELS; + /* + * Perform a SWAP YUV if input is parallel since in this mode + * the DCMIPP will swap YUV by default + */ + if (byteproc->ved.dcmipp->pipe_cfg->has_swapyuv && + (sink_fmt->code == MEDIA_BUS_FMT_YUYV8_2X8 || + sink_fmt->code == MEDIA_BUS_FMT_YVYU8_2X8 || + sink_fmt->code == MEDIA_BUS_FMT_UYVY8_2X8 || + sink_fmt->code == MEDIA_BUS_FMT_VYUY8_2X8)) + val |= DCMIPP_P0PPCR_SWAPYUV; + /* decimate using bytes and lines skipping */ if (val) { reg_set(byteproc, DCMIPP_P0PPCR, val); diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h index b94a9c87f56e..b491ce00c656 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h @@ -118,6 +118,7 @@ struct dcmipp_pipeline_config { u32 hw_revision; bool has_csi2; bool needs_mclk; + bool has_swapyuv; }; /** 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 8f7e131238fc..a4dc9da70cd6 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c @@ -108,7 +108,8 @@ static const struct dcmipp_pipeline_config stm32mp25_pipe_cfg = { .num_links = ARRAY_SIZE(stm32mp25_ent_links), .hw_revision = DCMIPP_STM32MP25_VERR, .has_csi2 = true, - .needs_mclk = true + .needs_mclk = true, + .has_swapyuv = true }; #define LINK_FLAG_TO_STR(f) ((f) == 0 ? "" :\ 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 564f37e2e1dd..75421035cfad 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c @@ -80,15 +80,15 @@ static const struct dcmipp_inp_pix_map dcmipp_inp_pix_map_list[] = { 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_2X8, YUYV8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_1X16, YUYV8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), - PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, UYVY8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), - PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, UYVY8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, UYVY8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, UYVY8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_1X16, UYVY8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), - PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, YUYV8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), - PIXMAP_SINK_SRC_PRCR_SWAP(YVYU8_2X8, YVYU8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, YUYV8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(YVYU8_2X8, YVYU8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), PIXMAP_SINK_SRC_PRCR_SWAP(YVYU8_1X16, YVYU8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), - PIXMAP_SINK_SRC_PRCR_SWAP(VYUY8_2X8, VYUY8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B), + PIXMAP_SINK_SRC_PRCR_SWAP(VYUY8_2X8, VYUY8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B), 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), @@ -356,8 +356,20 @@ static int dcmipp_inp_configure_parallel(struct dcmipp_inp_device *inp, val |= vpix->prcr_format << DCMIPP_PRCR_FORMAT_SHIFT; /* swap cycles */ - if (vpix->prcr_swapcycles) - val |= DCMIPP_PRCR_SWAPCYCLES; + /* + * Table dcmipp_inp_pix_map_list take into consideration that SWAPYUV + * bit is available when dealing with 16bit YUV formats. If it is not + * available (such as on stm32mp13), swapcycle setting should be + * reversed + */ + if (!inp->ved.dcmipp->pipe_cfg->has_swapyuv && + (src_fmt->code == MEDIA_BUS_FMT_YUYV8_2X8 || + src_fmt->code == MEDIA_BUS_FMT_YVYU8_2X8 || + src_fmt->code == MEDIA_BUS_FMT_UYVY8_2X8 || + src_fmt->code == MEDIA_BUS_FMT_VYUY8_2X8)) + val |= (!vpix->prcr_swapcycles ? DCMIPP_PRCR_SWAPCYCLES : 0); + else + val |= (vpix->prcr_swapcycles ? DCMIPP_PRCR_SWAPCYCLES : 0); reg_write(inp, DCMIPP_PRCR, val); From b1c889e71596c00641337146a32fa02721e4e588 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:08 +0200 Subject: [PATCH 144/596] media: stm32: dcmipp: configure csi input of all pipes on stm32mp25 The STM32MP25 has CSI input and 2 additional pixel pipes in addition to the byte pipe. Each pipe can select which data to receive based on CSI VC/DT selection. The multi-stream support of DCMIPP will be added in a future commit, however, to start putting proper control method, the input subset has now 3 SRC pads, one per pipe available. Currently, and until multi-stream support is added, same data is sent to all pipes. Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../st/stm32/stm32-dcmipp/dcmipp-input.c | 88 +++++++++++++------ 1 file changed, 59 insertions(+), 29 deletions(-) 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 75421035cfad..220e03ea38aa 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c @@ -43,12 +43,18 @@ #define DCMIPP_CMCR_INSEL BIT(0) #define DCMIPP_P0FSCR 0x404 -#define DCMIPP_P0FSCR_DTMODE_MASK GENMASK(17, 16) -#define DCMIPP_P0FSCR_DTMODE_SHIFT 16 -#define DCMIPP_P0FSCR_DTMODE_DTIDA 0x00 +#define DCMIPP_P1FSCR 0x804 +#define DCMIPP_P2FSCR 0xC04 +#define DCMIPP_PXFSCR_DTMODE_MASK GENMASK(17, 16) +#define DCMIPP_PXFSCR_DTMODE_SHIFT 16 +#define DCMIPP_PXFSCR_DTMODE_DTIDA 0x00 #define DCMIPP_P0FSCR_DTMODE_ALLDT 0x03 -#define DCMIPP_P0FSCR_DTIDA_MASK GENMASK(5, 0) -#define DCMIPP_P0FSCR_DTIDA_SHIFT 0 +#define DCMIPP_PXFSCR_DTIDA_MASK GENMASK(5, 0) +#define DCMIPP_PXFSCR_DTIDA_SHIFT 0 + +#define DCMIPP_PXFSCR(a) (((a) == 0) ? DCMIPP_P0FSCR :\ + ((a) == 1) ? DCMIPP_P1FSCR :\ + DCMIPP_P2FSCR) #define IS_SINK(pad) (!(pad)) #define IS_SRC(pad) ((pad)) @@ -274,6 +280,7 @@ static int dcmipp_inp_set_fmt(struct v4l2_subdev *sd, { struct dcmipp_inp_device *inp = v4l2_get_subdevdata(sd); struct v4l2_mbus_framefmt *mf; + int pad; if (v4l2_subdev_is_streaming(sd)) return -EBUSY; @@ -298,9 +305,11 @@ static int dcmipp_inp_set_fmt(struct v4l2_subdev *sd, /* When setting the sink format, report that format on the src pad */ if (IS_SINK(fmt->pad)) { - mf = v4l2_subdev_state_get_format(sd_state, 1); - *mf = fmt->format; - dcmipp_inp_adjust_fmt(inp, mf, 1); + for (pad = 1; pad < sd->entity.num_pads; pad++) { + mf = v4l2_subdev_state_get_format(sd_state, pad); + *mf = fmt->format; + dcmipp_inp_adjust_fmt(inp, mf, pad); + } } return 0; @@ -383,7 +392,8 @@ static int dcmipp_inp_configure_parallel(struct dcmipp_inp_device *inp, } static int dcmipp_inp_configure_csi(struct dcmipp_inp_device *inp, - struct v4l2_subdev_state *state) + struct v4l2_subdev_state *state, + u32 pad) { const struct dcmipp_inp_pix_map *vpix; struct v4l2_mbus_framefmt *sink_fmt; @@ -391,7 +401,7 @@ static int dcmipp_inp_configure_csi(struct dcmipp_inp_device *inp, /* Get format information */ sink_fmt = v4l2_subdev_state_get_format(state, 0); - src_fmt = v4l2_subdev_state_get_format(state, 1); + src_fmt = v4l2_subdev_state_get_format(state, pad); vpix = dcmipp_inp_pix_map_by_code(sink_fmt->code, src_fmt->code); if (!vpix) { @@ -399,22 +409,28 @@ static int dcmipp_inp_configure_csi(struct dcmipp_inp_device *inp, return -EINVAL; } - /* Apply configuration on each input pipe */ - reg_clear(inp, DCMIPP_P0FSCR, - DCMIPP_P0FSCR_DTMODE_MASK | DCMIPP_P0FSCR_DTIDA_MASK); + /* Perform the configuration on the related pad/pipe */ + reg_clear(inp, DCMIPP_PXFSCR(pad - 1), + DCMIPP_PXFSCR_DTMODE_MASK | DCMIPP_PXFSCR_DTIDA_MASK); /* In case of JPEG we don't know the DT so we allow all data */ /* * TODO - check instead dt == 0 for the time being to allow other * unknown data-type */ - if (!vpix->dt) + if (!vpix->dt) { + if (pad != 1) { + dev_err(inp->dev, "JPEG only available on pipe 0\n"); + return -EINVAL; + } + /* Only available on Pipe #0 */ reg_set(inp, DCMIPP_P0FSCR, - DCMIPP_P0FSCR_DTMODE_ALLDT << DCMIPP_P0FSCR_DTMODE_SHIFT); - else - reg_set(inp, DCMIPP_P0FSCR, - vpix->dt << DCMIPP_P0FSCR_DTIDA_SHIFT | - DCMIPP_P0FSCR_DTMODE_DTIDA); + DCMIPP_P0FSCR_DTMODE_ALLDT << DCMIPP_PXFSCR_DTMODE_SHIFT); + } else { + reg_set(inp, DCMIPP_PXFSCR(pad - 1), + vpix->dt << DCMIPP_PXFSCR_DTIDA_SHIFT | + DCMIPP_PXFSCR_DTMODE_DTIDA); + } /* Select the DCMIPP CSI interface */ reg_write(inp, DCMIPP_CMCR, DCMIPP_CMCR_INSEL); @@ -432,20 +448,24 @@ static int dcmipp_inp_enable_streams(struct v4l2_subdev *sd, struct media_pad *s_pad; int ret = 0; + if (inp->ved.bus_type == V4L2_MBUS_PARALLEL || + inp->ved.bus_type == V4L2_MBUS_BT656) + ret = dcmipp_inp_configure_parallel(inp, state); + else if (inp->ved.bus_type == V4L2_MBUS_CSI2_DPHY) + ret = dcmipp_inp_configure_csi(inp, state, pad); + if (ret) + return ret; + + /* If there where no other pad enabled, then enable the source subdev */ + if (sd->enabled_pads) + return 0; + /* Get source subdev */ s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) return -EINVAL; s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); - if (inp->ved.bus_type == V4L2_MBUS_PARALLEL || - inp->ved.bus_type == V4L2_MBUS_BT656) - ret = dcmipp_inp_configure_parallel(inp, state); - else if (inp->ved.bus_type == V4L2_MBUS_CSI2_DPHY) - ret = dcmipp_inp_configure_csi(inp, state); - if (ret) - return ret; - ret = v4l2_subdev_enable_streams(s_subdev, s_pad->index, BIT_ULL(0)); if (ret < 0) { dev_err(inp->dev, @@ -466,6 +486,10 @@ static int dcmipp_inp_disable_streams(struct v4l2_subdev *sd, struct media_pad *s_pad; int ret; + /* Don't do anything if there are still other pads enabled */ + if ((sd->enabled_pads & ~BIT(pad))) + return 0; + /* Get source subdev */ s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) @@ -533,8 +557,10 @@ struct dcmipp_ent_device *dcmipp_inp_ent_init(const char *entity_name, struct dcmipp_inp_device *inp; const unsigned long pads_flag[] = { MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE, + MEDIA_PAD_FL_SOURCE, MEDIA_PAD_FL_SOURCE, }; struct device *dev = dcmipp->dev; + u16 num_pads = ARRAY_SIZE(pads_flag); int ret; /* Allocate the inp struct */ @@ -543,18 +569,22 @@ struct dcmipp_ent_device *dcmipp_inp_ent_init(const char *entity_name, return ERR_PTR(-ENOMEM); inp->regs = dcmipp->regs; + inp->ved.dcmipp = dcmipp; + + /* For DCMIPP without CSI2, there is only a single pipe hence 2 pads */ + if (!inp->ved.dcmipp->pipe_cfg->has_csi2) + num_pads = 2; /* Initialize ved and sd */ ret = dcmipp_ent_sd_register(&inp->ved, &inp->sd, &dcmipp->v4l2_dev, entity_name, MEDIA_ENT_F_VID_IF_BRIDGE, - ARRAY_SIZE(pads_flag), pads_flag, + num_pads, pads_flag, &dcmipp_inp_int_ops, &dcmipp_inp_ops, NULL, NULL); if (ret) { kfree(inp); return ERR_PTR(ret); } - inp->ved.dcmipp = dcmipp; inp->dev = dev; From 78bc155d2fd404977a2d3006fa2ea39907815e67 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:09 +0200 Subject: [PATCH 145/596] media: stm32: dcmipp: introduce a dcmipp global media_pipeline With the introduction of stm32mp25 containing several capture devices, it becomes necessary to share the media_pipeline structure among all capture devices since subdev pads can be shared between several capture devices. Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c | 3 +-- drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h | 1 + 2 files changed, 2 insertions(+), 2 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 f6011b42203c..53341b798457 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c @@ -125,7 +125,6 @@ struct dcmipp_bytecap_device { /* mutex used as vdev and queue lock */ struct mutex lock; u32 sequence; - struct media_pipeline pipe; struct v4l2_subdev *s_subdev; u32 s_subdev_pad_nb; @@ -410,7 +409,7 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, goto err_buffer_done; } - ret = media_pipeline_start(entity->pads, &vcap->pipe); + ret = media_pipeline_start(entity->pads, &vcap->ved.dcmipp->pipe); if (ret) { dev_dbg(vcap->dev, "%s: Failed to start streaming, media pipeline start error (%d)\n", __func__, ret); diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h index b491ce00c656..9ec6d589b503 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h @@ -73,6 +73,7 @@ struct dcmipp_device { /* The Associated media_device parent */ struct media_device mdev; + struct media_pipeline pipe; /* Internal v4l2 parent device*/ struct v4l2_device v4l2_dev; From 3d119cac10d906c8a66066c36ab509d884edde43 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:10 +0200 Subject: [PATCH 146/596] media: stm32: dcmipp: add pixel pipes helper functions Pixel pipes are made of an isp subdev (only main pipe) and a postproc subdev. This commit add a helper functions common to those 2 subdevs such as for handling format enumeration, set_selection handling, so that they do not have to be duplicated in the two subdeves. Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../platform/st/stm32/stm32-dcmipp/Makefile | 1 + .../stm32/stm32-dcmipp/dcmipp-pixelcommon.c | 180 ++++++++++++++++++ .../stm32/stm32-dcmipp/dcmipp-pixelcommon.h | 41 ++++ 3 files changed, 222 insertions(+) create mode 100644 drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c create mode 100644 drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile index 159105fb40b8..54231569ed6f 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 stm32-dcmipp-y := dcmipp-core.o dcmipp-common.o dcmipp-input.o dcmipp-byteproc.o dcmipp-bytecap.o +stm32-dcmipp-y += dcmipp-pixelcommon.o obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp.o diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c new file mode 100644 index 000000000000..8b0699e870ef --- /dev/null +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for STM32 Digital Camera Memory Interface Pixel Processor + * + * Copyright (C) STMicroelectronics SA 2026 + * Authors: Hugues Fruchet + * Alain Volmat + * for STMicroelectronics. + */ + +#include +#include +#include + +#include "dcmipp-common.h" +#include "dcmipp-pixelcommon.h" + +#define DCMIPP_ENT(id, pad) (1 << (2 * (id) + (pad))) +#define DCMIPP_ISP_SINK (DCMIPP_ENT(DCMIPP_ISP, 0)) +#define DCMIPP_ISP_SRC (DCMIPP_ENT(DCMIPP_ISP, 1)) +#define DCMIPP_ISP_INOUT (DCMIPP_ISP_SINK | DCMIPP_ISP_SRC) +#define DCMIPP_MAIN_POSTPROC_SINK (DCMIPP_ENT(DCMIPP_MAIN, 0)) +#define DCMIPP_MAIN_POSTPROC_SRC (DCMIPP_ENT(DCMIPP_MAIN, 1)) +#define DCMIPP_MAIN_POSTPROC_INOUT \ + (DCMIPP_MAIN_POSTPROC_SINK | DCMIPP_MAIN_POSTPROC_SRC) +#define DCMIPP_AUX_POSTPROC_SINK (DCMIPP_ENT(DCMIPP_AUX, 0)) +#define DCMIPP_AUX_POSTPROC_SRC (DCMIPP_ENT(DCMIPP_AUX, 1)) +#define DCMIPP_AUX_POSTPROC_INOUT \ + (DCMIPP_AUX_POSTPROC_SINK | DCMIPP_AUX_POSTPROC_SRC) +#define DCMIPP_ALL_POSTPROC_SINK \ + (DCMIPP_MAIN_POSTPROC_SINK | DCMIPP_AUX_POSTPROC_SINK) +#define DCMIPP_ALL_POSTPROC_INOUT \ + (DCMIPP_MAIN_POSTPROC_INOUT | DCMIPP_AUX_POSTPROC_INOUT) + +#define PIXMAP_MBUS(mbus, applicable_pipes) \ + { \ + .code = MEDIA_BUS_FMT_##mbus, \ + .pipes = applicable_pipes, \ + } +static const struct dcmipp_pixelpipe_pix_map +dcmipp_pixel_formats_list[] = { + /* RGB formats */ + /* RGB565 / RGB888 */ + PIXMAP_MBUS(RGB565_2X8_LE, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(RGB565_1X16, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(RGB888_3X8, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(RGB888_1X24, DCMIPP_ALL_POSTPROC_INOUT | DCMIPP_ISP_INOUT), + /* YUV formats */ + PIXMAP_MBUS(YUYV8_2X8, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(UYVY8_1X16, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(YUV8_1X24, DCMIPP_ALL_POSTPROC_INOUT | DCMIPP_ISP_SRC), + /* GREY */ + PIXMAP_MBUS(Y8_1X8, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(Y10_1X10, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(Y12_1X12, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + PIXMAP_MBUS(Y14_1X14, DCMIPP_AUX_POSTPROC_SINK | DCMIPP_ISP_SINK), + /* Raw Bayer */ + /* Raw 8 */ + PIXMAP_MBUS(SBGGR8_1X8, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGBRG8_1X8, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGRBG8_1X8, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SRGGB8_1X8, DCMIPP_ISP_SINK), + /* Raw 10 */ + PIXMAP_MBUS(SBGGR10_1X10, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGBRG10_1X10, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGRBG10_1X10, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SRGGB10_1X10, DCMIPP_ISP_SINK), + /* Raw 12 */ + PIXMAP_MBUS(SBGGR12_1X12, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGBRG12_1X12, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGRBG12_1X12, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SRGGB12_1X12, DCMIPP_ISP_SINK), + /* Raw 14 */ + PIXMAP_MBUS(SBGGR14_1X14, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGBRG14_1X14, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SGRBG14_1X14, DCMIPP_ISP_SINK), + PIXMAP_MBUS(SRGGB14_1X14, DCMIPP_ISP_SINK), +}; + +const struct dcmipp_pixelpipe_pix_map * +dcmipp_pixelpipe_pix_map_by_code(__u32 code, unsigned int id, unsigned int pad) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dcmipp_pixel_formats_list); i++) { + if (dcmipp_pixel_formats_list[i].code == code && + dcmipp_pixel_formats_list[i].pipes & DCMIPP_ENT(id, pad)) + return &dcmipp_pixel_formats_list[i]; + } + + return NULL; +} + +int dcmipp_pixelpipe_enum_mbus_code(unsigned int id, + struct v4l2_subdev_mbus_code_enum *code) +{ + unsigned int index = code->index; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dcmipp_pixel_formats_list); i++) { + if (!(dcmipp_pixel_formats_list[i].pipes & + DCMIPP_ENT(id, code->pad))) + continue; + + if (index == 0) + break; + + index--; + } + + if (i == ARRAY_SIZE(dcmipp_pixel_formats_list)) + return -EINVAL; + + code->code = dcmipp_pixel_formats_list[i].code; + + return 0; +} + +int dcmipp_pixelpipe_enum_frame_size(unsigned int id, + struct v4l2_subdev_frame_size_enum *fse) +{ + const struct dcmipp_pixelpipe_pix_map *vpix; + + if (fse->index) + return -EINVAL; + + /* Only accept code in the pix map table */ + vpix = dcmipp_pixelpipe_pix_map_by_code(fse->code, id, fse->pad); + if (!vpix) + return -EINVAL; + + fse->min_width = DCMIPP_FRAME_MIN_WIDTH; + fse->max_width = DCMIPP_FRAME_MAX_WIDTH; + fse->min_height = DCMIPP_FRAME_MIN_HEIGHT; + fse->max_height = DCMIPP_FRAME_MAX_HEIGHT; + + return 0; +} + +int dcmipp_pixelpipe_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *s) +{ + struct v4l2_mbus_framefmt *sink_fmt; + + if (IS_SRC(s->pad)) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r = *v4l2_subdev_state_get_crop(state, s->pad); + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + sink_fmt = v4l2_subdev_state_get_format(state, s->pad); + s->r.top = 0; + s->r.left = 0; + s->r.width = sink_fmt->width; + s->r.height = sink_fmt->height; + break; + case V4L2_SEL_TGT_COMPOSE: + s->r = *v4l2_subdev_state_get_compose(state, s->pad); + break; + default: + return -EINVAL; + } + + return 0; +} + +__u32 dcmipp_pixelpipe_src_format(__u32 input_format) +{ + if (input_format >= MEDIA_BUS_FMT_Y8_1X8 && + input_format < MEDIA_BUS_FMT_SBGGR8_1X8) + return MEDIA_BUS_FMT_YUV8_1X24; + + return MEDIA_BUS_FMT_RGB888_1X24; +} diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h new file mode 100644 index 000000000000..db29e1fe939e --- /dev/null +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for STM32 Digital Camera Memory Interface Pixel Processor + * + * Copyright (C) STMicroelectronics SA 2026 + * Authors: Hugues Fruchet + * Alain Volmat + * for STMicroelectronics. + */ + +#ifndef _DCMIPP_PIXELCOMMON_H +#define _DCMIPP_PIXELCOMMON_H + +#define IS_SINK(pad) (!(pad)) +#define IS_SRC(pad) ((pad)) + +#define DCMIPP_ISP 0 +#define DCMIPP_MAIN 1 +#define DCMIPP_AUX 2 + +struct dcmipp_pixelpipe_pix_map { + __u32 code; + __u32 pipes; +}; + +const struct dcmipp_pixelpipe_pix_map * +dcmipp_pixelpipe_pix_map_by_code(__u32 code, unsigned int id, unsigned int pad); + +int dcmipp_pixelpipe_enum_mbus_code(unsigned int id, + struct v4l2_subdev_mbus_code_enum *code); + +int dcmipp_pixelpipe_enum_frame_size(unsigned int id, + struct v4l2_subdev_frame_size_enum *fse); + +int dcmipp_pixelpipe_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *s); + +__u32 dcmipp_pixelpipe_src_format(__u32 input_format); + +#endif From a231ed57976e32d2f9c8d93904803023a65824e9 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:11 +0200 Subject: [PATCH 147/596] media: stm32: dcmipp: addition of a dcmipp-isp subdev The ISP subdev is the first element after input of the main pipeline. Part (static configuration) of this block is done via this subdev while other configuration done on a per-frame basis will be done via a output metadata device attached to this subdev. This subdev handled the following features of the ISP block: - statistic removal (top / bottom of the frame) - decimation - demosaicing - control of frame export to the aux pipeline Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../platform/st/stm32/stm32-dcmipp/Makefile | 2 +- .../st/stm32/stm32-dcmipp/dcmipp-common.h | 3 + .../st/stm32/stm32-dcmipp/dcmipp-isp.c | 491 ++++++++++++++++++ 3 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile index 54231569ed6f..a708534a51af 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 stm32-dcmipp-y := dcmipp-core.o dcmipp-common.o dcmipp-input.o dcmipp-byteproc.o dcmipp-bytecap.o -stm32-dcmipp-y += dcmipp-pixelcommon.o +stm32-dcmipp-y += dcmipp-pixelcommon.o dcmipp-isp.o obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp.o diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h index 9ec6d589b503..c699905ee6e8 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h @@ -283,5 +283,8 @@ void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved); struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, struct dcmipp_device *dcmipp); void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved); +struct dcmipp_ent_device *dcmipp_isp_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp); +void dcmipp_isp_ent_release(struct dcmipp_ent_device *ved); #endif diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c new file mode 100644 index 000000000000..1586cc9c59a6 --- /dev/null +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for STM32 Digital Camera Memory Interface Pixel Processor + * + * Copyright (C) STMicroelectronics SA 2026 + * Authors: Hugues Fruchet + * Alain Volmat + * for STMicroelectronics. + */ + +#include +#include +#include + +#include "dcmipp-common.h" +#include "dcmipp-pixelcommon.h" + +#define DCMIPP_P1FSCR 0x804 +#define DCMIPP_P1FSCR_PIPEDIFF BIT(18) + +#define DCMIPP_P1SRCR 0x820 +#define DCMIPP_P1SRCR_LASTLINE_SHIFT 0 +#define DCMIPP_P1SRCR_FIRSTLINEDEL_SHIFT 12 +#define DCMIPP_P1SRCR_CROPEN BIT(15) + +#define DCMIPP_P1DECR 0x830 +#define DCMIPP_P1DECR_ENABLE BIT(0) +#define DCMIPP_P1DECR_HDEC_SHIFT 1 +#define DCMIPP_P1DECR_VDEC_SHIFT 3 + +#define DCMIPP_P1DMCR 0x870 +#define DCMIPP_P1DMCR_ENABLE BIT(0) +#define DCMIPP_P1DMCR_TYPE_SHIFT 1 +#define DCMIPP_P1DMCR_TYPE_MASK GENMASK(2, 1) +#define DCMIPP_P1DMCR_TYPE_RGGB 0x0 +#define DCMIPP_P1DMCR_TYPE_GRBG 0x1 +#define DCMIPP_P1DMCR_TYPE_GBRG 0x2 +#define DCMIPP_P1DMCR_TYPE_BGGR 0x3 + +#define ISP_MEDIA_BUS_SINK_FMT_DEFAULT MEDIA_BUS_FMT_RGB565_1X16 +#define ISP_MEDIA_BUS_SRC_FMT_DEFAULT MEDIA_BUS_FMT_RGB888_1X24 + +struct dcmipp_isp_device { + struct dcmipp_ent_device ved; + struct v4l2_subdev sd; + struct device *dev; + + void __iomem *regs; +}; + +static const struct v4l2_mbus_framefmt fmt_default = { + .width = DCMIPP_FMT_WIDTH_DEFAULT, + .height = DCMIPP_FMT_HEIGHT_DEFAULT, + .code = ISP_MEDIA_BUS_SINK_FMT_DEFAULT, + .field = V4L2_FIELD_NONE, + .colorspace = DCMIPP_COLORSPACE_DEFAULT, + .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT, + .quantization = DCMIPP_QUANTIZATION_DEFAULT, + .xfer_func = DCMIPP_XFER_FUNC_DEFAULT, +}; + +static inline unsigned int dcmipp_isp_set_compose(__u32 size, __u32 req) +{ + unsigned int i = 0; + + if (req > size) + return size; + + /* Maximum decimation factor is 8 */ + while (size > req && i++ < 3) + size /= 2; + + return size; +} + +static void dcmipp_isp_adjust_fmt(struct v4l2_mbus_framefmt *fmt, u32 pad) +{ + /* Only accept code in the pix map table */ + if (!dcmipp_pixelpipe_pix_map_by_code(fmt->code, DCMIPP_ISP, pad)) + fmt->code = IS_SRC(pad) ? ISP_MEDIA_BUS_SRC_FMT_DEFAULT : + ISP_MEDIA_BUS_SINK_FMT_DEFAULT; + + fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH, + DCMIPP_FRAME_MAX_WIDTH) & ~1; + fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT, + DCMIPP_FRAME_MAX_HEIGHT); + + if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE) + fmt->field = V4L2_FIELD_NONE; + + dcmipp_colorimetry_clamp(fmt); +} + +static int dcmipp_isp_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + for (unsigned int i = 0; i < sd->entity.num_pads; i++) { + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_state_get_format(state, i); + *mf = fmt_default; + mf->code = IS_SRC(i) ? ISP_MEDIA_BUS_SRC_FMT_DEFAULT : + ISP_MEDIA_BUS_SINK_FMT_DEFAULT; + + if (IS_SINK(i)) { + struct v4l2_rect r = { + .top = 0, + .left = 0, + .width = DCMIPP_FMT_WIDTH_DEFAULT, + .height = DCMIPP_FMT_HEIGHT_DEFAULT, + }; + + *v4l2_subdev_state_get_crop(state, i) = r; + *v4l2_subdev_state_get_compose(state, i) = r; + } + } + + return 0; +} + +static int dcmipp_isp_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + return dcmipp_pixelpipe_enum_mbus_code(DCMIPP_ISP, code); +} + +static int dcmipp_isp_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + return dcmipp_pixelpipe_enum_frame_size(DCMIPP_ISP, fse); +} + +static int dcmipp_isp_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE && + v4l2_subdev_is_streaming(sd)) + return -EBUSY; + + dcmipp_isp_adjust_fmt(&fmt->format, fmt->pad); + + if (IS_SINK(fmt->pad)) { + struct v4l2_mbus_framefmt *src_fmt = + v4l2_subdev_state_get_format(state, 1); + struct v4l2_rect r = { + .top = 0, + .left = 0, + .width = fmt->format.width, + .height = fmt->format.height, + }; + + /* Adjust SINK pad crop/compose */ + *v4l2_subdev_state_get_crop(state, 0) = r; + *v4l2_subdev_state_get_compose(state, 0) = r; + + /* Forward format to SRC pads */ + *src_fmt = fmt->format; + src_fmt->code = dcmipp_pixelpipe_src_format(fmt->format.code); + *v4l2_subdev_state_get_format(state, 2) = *src_fmt; + } else { + struct v4l2_mbus_framefmt *sink_fmt = + v4l2_subdev_state_get_format(state, 0); + struct v4l2_rect *compose = + v4l2_subdev_state_get_compose(state, 0); + + fmt->format = *sink_fmt; + fmt->format.code = dcmipp_pixelpipe_src_format(sink_fmt->code); + if (compose->width && compose->height) { + fmt->format.width = compose->width; + fmt->format.height = compose->height; + } + /* Set to the 2nd SRC pad */ + *v4l2_subdev_state_get_format(state, fmt->pad == 1 ? 2 : 1) = + fmt->format; + } + + /* Update the selected pad format */ + *v4l2_subdev_state_get_format(state, fmt->pad) = fmt->format; + + return 0; +} + +static void dcmipp_isp_adjust_crop(struct v4l2_rect *r, + const struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_rect crop_min = { + .width = fmt->width, + .height = DCMIPP_FRAME_MIN_HEIGHT, + }; + + /* + * Crop is related here to the statistics removal, that is + * firsts and/or lasts lines of the frame. Up to the first + * 7 lines can be skipped + */ +#define DCMIPP_ISP_MAX_TOP_CROP 7 + v4l2_rect_set_min_size(r, &crop_min); + if (r->top > DCMIPP_ISP_MAX_TOP_CROP) + r->top = DCMIPP_ISP_MAX_TOP_CROP; + if ((r->height + r->top) > fmt->height) + r->height = fmt->height - r->top; +} + +static int dcmipp_isp_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *s) +{ + struct dcmipp_isp_device *isp = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *crop, *compose; + __u32 pad; + + if (IS_SRC(s->pad)) + return -EINVAL; + + if (s->which == V4L2_SUBDEV_FORMAT_ACTIVE && + v4l2_subdev_is_streaming(sd)) + return -EBUSY; + + crop = v4l2_subdev_state_get_crop(state, s->pad); + compose = v4l2_subdev_state_get_compose(state, s->pad); + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + sink_fmt = v4l2_subdev_state_get_format(state, s->pad); + dcmipp_isp_adjust_crop(&s->r, sink_fmt); + + *crop = s->r; + *compose = s->r; + + dev_dbg(isp->dev, "s_selection: crop (%d,%d)/%ux%u\n", + crop->left, crop->top, crop->width, crop->height); + break; + case V4L2_SEL_TGT_COMPOSE: + s->r.top = 0; + s->r.left = 0; + if (s->r.width < DCMIPP_FRAME_MIN_WIDTH) + s->r.width = DCMIPP_FRAME_MIN_WIDTH; + s->r.width = dcmipp_isp_set_compose(crop->width, s->r.width); + if (s->r.height < DCMIPP_FRAME_MIN_HEIGHT) + s->r.height = DCMIPP_FRAME_MIN_HEIGHT; + s->r.height = dcmipp_isp_set_compose(crop->height, s->r.height); + *compose = s->r; + + dev_dbg(isp->dev, "s_selection: compose (%d,%d)/%ux%u\n", + compose->left, compose->top, + compose->width, compose->height); + break; + default: + return -EINVAL; + } + + /* Update the source pad size */ + for (pad = 1; pad < sd->entity.num_pads; pad++) { + src_fmt = v4l2_subdev_state_get_format(state, pad); + src_fmt->width = s->r.width; + src_fmt->height = s->r.height; + } + + return 0; +} + +#define STM32_DCMIPP_IS_BAYER_VARIANT(code, variant) \ + ((code) == MEDIA_BUS_FMT_S##variant##8_1X8 || \ + (code) == MEDIA_BUS_FMT_S##variant##10_1X10 || \ + (code) == MEDIA_BUS_FMT_S##variant##12_1X12 || \ + (code) == MEDIA_BUS_FMT_S##variant##14_1X14 || \ + (code) == MEDIA_BUS_FMT_S##variant##16_1X16) +static void dcmipp_isp_config_demosaicing(struct dcmipp_isp_device *isp, + struct v4l2_subdev_state *state) +{ + __u32 code = v4l2_subdev_state_get_format(state, 0)->code; + unsigned int val = 0; + + /* Disable demosaicing */ + reg_clear(isp, DCMIPP_P1DMCR, + DCMIPP_P1DMCR_ENABLE | DCMIPP_P1DMCR_TYPE_MASK); + + /* Only perform demosaicing if format is bayer */ + if (code < MEDIA_BUS_FMT_SBGGR8_1X8 || code >= MEDIA_BUS_FMT_JPEG_1X8) + return; + + dev_dbg(isp->dev, "Input is RawBayer, enable Demosaicing\n"); + + if (STM32_DCMIPP_IS_BAYER_VARIANT(code, BGGR)) + val = DCMIPP_P1DMCR_TYPE_BGGR << DCMIPP_P1DMCR_TYPE_SHIFT; + else if (STM32_DCMIPP_IS_BAYER_VARIANT(code, GBRG)) + val = DCMIPP_P1DMCR_TYPE_GBRG << DCMIPP_P1DMCR_TYPE_SHIFT; + else if (STM32_DCMIPP_IS_BAYER_VARIANT(code, GRBG)) + val = DCMIPP_P1DMCR_TYPE_GRBG << DCMIPP_P1DMCR_TYPE_SHIFT; + else if (STM32_DCMIPP_IS_BAYER_VARIANT(code, RGGB)) + val = DCMIPP_P1DMCR_TYPE_RGGB << DCMIPP_P1DMCR_TYPE_SHIFT; + + val |= DCMIPP_P1DMCR_ENABLE; + + reg_set(isp, DCMIPP_P1DMCR, val); +} + +static bool dcmipp_isp_is_aux_output_enabled(struct dcmipp_isp_device *isp) +{ + struct media_link *link; + + for_each_media_entity_data_link(isp->ved.ent, link) { + if (link->source != &isp->ved.pads[2]) + continue; + + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + if (!strcmp(link->sink->entity->name, "dcmipp_aux_postproc")) + return true; + } + + return false; +} + +static void dcmipp_isp_config_decimation(struct dcmipp_isp_device *isp, + struct v4l2_subdev_state *state) +{ + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0); + struct v4l2_rect *compose = v4l2_subdev_state_get_compose(state, 0); + u32 decr; + + decr = (fls(crop->width / compose->width) - 1) << DCMIPP_P1DECR_HDEC_SHIFT | + (fls(crop->height / compose->height) - 1) << DCMIPP_P1DECR_VDEC_SHIFT; + if (decr) + decr |= DCMIPP_P1DECR_ENABLE; + + reg_write(isp, DCMIPP_P1DECR, decr); +} + +static int dcmipp_isp_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct dcmipp_isp_device *isp = v4l2_get_subdevdata(sd); + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0); + struct v4l2_subdev *s_subdev; + struct media_pad *s_pad; + int ret; + + /* Perform configuration only if no other pad is enabled */ + if (sd->enabled_pads) + return 0; + + /* Get source subdev */ + s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); + if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) + return -EINVAL; + s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); + + /* Check if link between ISP & Pipe2 postproc is enabled */ + if (dcmipp_isp_is_aux_output_enabled(isp)) + reg_clear(isp, DCMIPP_P1FSCR, DCMIPP_P1FSCR_PIPEDIFF); + else + reg_set(isp, DCMIPP_P1FSCR, DCMIPP_P1FSCR_PIPEDIFF); + + /* Configure Statistic Removal */ + crop = v4l2_subdev_state_get_crop(state, 0); + reg_write(isp, DCMIPP_P1SRCR, + ((crop->top << DCMIPP_P1SRCR_FIRSTLINEDEL_SHIFT) | + (crop->height << DCMIPP_P1SRCR_LASTLINE_SHIFT) | + DCMIPP_P1SRCR_CROPEN)); + + /* Configure Decimation */ + dcmipp_isp_config_decimation(isp, state); + + /* Configure Demosaicing */ + dcmipp_isp_config_demosaicing(isp, state); + + ret = v4l2_subdev_enable_streams(s_subdev, s_pad->index, BIT_ULL(0)); + if (ret < 0) { + dev_err(isp->dev, + "failed to start source subdev streaming (%d)\n", ret); + return ret; + } + + return 0; +} + +static int dcmipp_isp_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct dcmipp_isp_device *isp = v4l2_get_subdevdata(sd); + struct v4l2_subdev *s_subdev; + struct media_pad *s_pad; + int ret; + + /* Don't do anything if there are still other pads enabled */ + if ((sd->enabled_pads & ~BIT(pad))) + return 0; + + /* Get source subdev */ + s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); + if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) + return -EINVAL; + s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); + + /* Disable all blocks */ + reg_write(isp, DCMIPP_P1SRCR, 0); + reg_write(isp, DCMIPP_P1DECR, 0); + reg_write(isp, DCMIPP_P1DMCR, 0); + + ret = v4l2_subdev_disable_streams(s_subdev, s_pad->index, BIT_ULL(0)); + if (ret < 0) { + dev_err(isp->dev, + "failed to disable source subdev streaming (%d)\n", ret); + return ret; + } + + return 0; +} + +static const struct v4l2_subdev_pad_ops dcmipp_isp_pad_ops = { + .enum_mbus_code = dcmipp_isp_enum_mbus_code, + .enum_frame_size = dcmipp_isp_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = dcmipp_isp_set_fmt, + .get_selection = dcmipp_pixelpipe_get_selection, + .set_selection = dcmipp_isp_set_selection, + .enable_streams = dcmipp_isp_enable_streams, + .disable_streams = dcmipp_isp_disable_streams, +}; + +static const struct v4l2_subdev_video_ops dcmipp_isp_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_ops dcmipp_isp_ops = { + .pad = &dcmipp_isp_pad_ops, + .video = &dcmipp_isp_video_ops, +}; + +static void dcmipp_isp_release(struct v4l2_subdev *sd) +{ + struct dcmipp_isp_device *isp = v4l2_get_subdevdata(sd); + + kfree(isp); +} + +static const struct v4l2_subdev_internal_ops dcmipp_isp_int_ops = { + .init_state = dcmipp_isp_init_state, + .release = dcmipp_isp_release, +}; + +void dcmipp_isp_ent_release(struct dcmipp_ent_device *ved) +{ + struct dcmipp_isp_device *isp = + container_of(ved, struct dcmipp_isp_device, ved); + + dcmipp_ent_sd_unregister(ved, &isp->sd); +} + +struct dcmipp_ent_device *dcmipp_isp_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp) +{ + struct dcmipp_isp_device *isp; + const unsigned long pads_flag[] = { + MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE, + MEDIA_PAD_FL_SOURCE, + }; + int ret; + + /* Allocate the isp struct */ + isp = kzalloc_obj(*isp); + if (!isp) + return ERR_PTR(-ENOMEM); + + isp->regs = dcmipp->regs; + + /* Initialize ved and sd */ + ret = dcmipp_ent_sd_register(&isp->ved, &isp->sd, + &dcmipp->v4l2_dev, entity_name, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER, + ARRAY_SIZE(pads_flag), pads_flag, + &dcmipp_isp_int_ops, &dcmipp_isp_ops, + NULL, NULL); + if (ret) { + kfree(isp); + return ERR_PTR(ret); + } + + isp->ved.dcmipp = dcmipp; + isp->dev = dcmipp->dev; + + return &isp->ved; +} From e31bd1eb17f927d067e40916abb17c4ce5f95ece Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:12 +0200 Subject: [PATCH 148/596] media: uapi: Add controls for ST DCMIPP Driver Add definitions and documentation for the custom control provided by the DCMIPP driver in order to control the pixelproc gamma correction. Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../userspace-api/media/drivers/dcmipp.rst | 14 ++++++++++++++ .../userspace-api/media/drivers/index.rst | 1 + include/uapi/linux/media/st/dcmipp_config.h | 16 ++++++++++++++++ include/uapi/linux/v4l2-controls.h | 6 ++++++ 4 files changed, 37 insertions(+) create mode 100644 Documentation/userspace-api/media/drivers/dcmipp.rst create mode 100644 include/uapi/linux/media/st/dcmipp_config.h diff --git a/Documentation/userspace-api/media/drivers/dcmipp.rst b/Documentation/userspace-api/media/drivers/dcmipp.rst new file mode 100644 index 000000000000..ed4272da4b68 --- /dev/null +++ b/Documentation/userspace-api/media/drivers/dcmipp.rst @@ -0,0 +1,14 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +ST DCMIPP driver +================ + +The ST DCMIPP driver implements a driver specific control as part of its +pixelproc subdev. + +``V4L2_CID_DCMIPP_PIXELPROC_GAMMA_CORRECTION_ENABLE (boolean)`` + Enable / disable the gamma correction block. + + The DCMIPP PixelProc stage implements a gamma compression on each R, G, B + component, using a static gamma exponent 2.2. + The gamma is implemented as a 7-segment linear curve. diff --git a/Documentation/userspace-api/media/drivers/index.rst b/Documentation/userspace-api/media/drivers/index.rst index 02967c9b18d6..8d2c489b8453 100644 --- a/Documentation/userspace-api/media/drivers/index.rst +++ b/Documentation/userspace-api/media/drivers/index.rst @@ -30,6 +30,7 @@ For more details see the file COPYING in the source distribution of Linux. camera-sensor ccs cx2341x-uapi + dcmipp dw100 imx-uapi mali-c55 diff --git a/include/uapi/linux/media/st/dcmipp_config.h b/include/uapi/linux/media/st/dcmipp_config.h new file mode 100644 index 000000000000..ad8d146820ea --- /dev/null +++ b/include/uapi/linux/media/st/dcmipp_config.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * ST DCMIPP Driver - Userspace API + * + * Copyright (C) STMicroelectronics SA 2026 + */ + +#ifndef __UAPI_DCMIPP_CONFIG_H +#define __UAPI_DCMIPP_CONFIG_H + +#include +#include + +#define V4L2_CID_DCMIPP_PIXELPROC_GAMMA_CORRECTION_ENABLE (V4L2_CID_USER_DCMIPP_BASE + 0x0) + +#endif /* __UAPI_DCMIPP_CONFIG_H */ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index affec0ab4781..42b4f82afeb8 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -234,6 +234,12 @@ enum v4l2_colorfx { */ #define V4L2_CID_USER_MALI_C55_BASE (V4L2_CID_USER_BASE + 0x1230) +/* + * The base for the ST DCMIPP driver controls. + * We reserve 16 controls for this driver + */ +#define V4L2_CID_USER_DCMIPP_BASE (V4L2_CID_USER_BASE + 0x1240) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ From 239cf9e5fda1251fb2beb57a2d454c671e56fc83 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:13 +0200 Subject: [PATCH 149/596] media: stm32: dcmipp: pixelproc: addition of dcmipp-pixelproc subdev Addition of the driver for dcmipp-pixelproc subdev. This subdev is the last one before the capture device at the tail of both main and aux pipelines. It is in charge of: - framerate adjustment - downscale - gamma correction - color conversion - pixel packing Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../platform/st/stm32/stm32-dcmipp/Makefile | 2 +- .../st/stm32/stm32-dcmipp/dcmipp-common.h | 4 + .../st/stm32/stm32-dcmipp/dcmipp-pixelproc.c | 940 ++++++++++++++++++ 3 files changed, 945 insertions(+), 1 deletion(-) create mode 100644 drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile index a708534a51af..7178934bb116 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 stm32-dcmipp-y := dcmipp-core.o dcmipp-common.o dcmipp-input.o dcmipp-byteproc.o dcmipp-bytecap.o -stm32-dcmipp-y += dcmipp-pixelcommon.o dcmipp-isp.o +stm32-dcmipp-y += dcmipp-pixelcommon.o dcmipp-isp.o dcmipp-pixelproc.o obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp.o diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h index c699905ee6e8..68766145d543 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h @@ -286,5 +286,9 @@ void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved); struct dcmipp_ent_device *dcmipp_isp_ent_init(const char *entity_name, struct dcmipp_device *dcmipp); void dcmipp_isp_ent_release(struct dcmipp_ent_device *ved); +struct dcmipp_ent_device * +dcmipp_pixelproc_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp); +void dcmipp_pixelproc_ent_release(struct dcmipp_ent_device *ved); #endif diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c new file mode 100644 index 000000000000..d771b1b21bd2 --- /dev/null +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c @@ -0,0 +1,940 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for STM32 Digital Camera Memory Interface Pixel Processor + * + * Copyright (C) STMicroelectronics SA 2026 + * Authors: Hugues Fruchet + * Alain Volmat + * for STMicroelectronics. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dcmipp-common.h" +#include "dcmipp-pixelcommon.h" + +#define DCMIPP_P1CRSTR 0x904 +#define DCMIPP_P2CRSTR 0xD04 +#define DCMIPP_PxCRSTR(id) (((id) == 1) ? DCMIPP_P1CRSTR :\ + DCMIPP_P2CRSTR) +#define DCMIPP_PxCRSTR_HSTART_SHIFT 0 +#define DCMIPP_PxCRSTR_VSTART_SHIFT 16 +#define DCMIPP_P1CRSZR 0x908 +#define DCMIPP_P2CRSZR 0xD08 +#define DCMIPP_PxCRSZR(id) (((id) == 1) ? DCMIPP_P1CRSZR :\ + DCMIPP_P2CRSZR) +#define DCMIPP_PxCRSZR_ENABLE BIT(31) +#define DCMIPP_PxCRSZR_HSIZE_SHIFT 0 +#define DCMIPP_PxCRSZR_VSIZE_SHIFT 16 + +#define DCMIPP_P1DCCR 0x90C +#define DCMIPP_P2DCCR 0xD0C +#define DCMIPP_PxDCCR(id) (((id) == 1) ? DCMIPP_P1DCCR :\ + DCMIPP_P2DCCR) +#define DCMIPP_PxDCCR_ENABLE BIT(0) +#define DCMIPP_PxDCCR_HDEC_SHIFT 1 +#define DCMIPP_PxDCCR_VDEC_SHIFT 3 + +#define DCMIPP_P1DSCR 0x910 +#define DCMIPP_P2DSCR 0xD10 +#define DCMIPP_PxDSCR(id) (((id) == 1) ? DCMIPP_P1DSCR :\ + DCMIPP_P2DSCR) +#define DCMIPP_PxDSCR_HDIV_SHIFT 0 +#define DCMIPP_PxDSCR_VDIV_SHIFT 16 +#define DCMIPP_PxDSCR_ENABLE BIT(31) + +#define DCMIPP_P1DSRTIOR 0x914 +#define DCMIPP_P2DSRTIOR 0xD14 +#define DCMIPP_PxDSRTIOR(id) (((id) == 1) ? DCMIPP_P1DSRTIOR :\ + DCMIPP_P2DSRTIOR) +#define DCMIPP_PxDSRTIOR_HRATIO_SHIFT 0 +#define DCMIPP_PxDSRTIOR_HRATIO_MASK GENMASK(15, 0) +#define DCMIPP_PxDSRTIOR_VRATIO_SHIFT 16 +#define DCMIPP_PxDSRTIOR_VRATIO_MASK GENMASK(31, 16) + +#define DCMIPP_P1DSSZR 0x918 +#define DCMIPP_P2DSSZR 0xD18 +#define DCMIPP_PxDSSZR(id) (((id) == 1) ? DCMIPP_P1DSSZR :\ + DCMIPP_P2DSSZR) +#define DCMIPP_PxDSSZR_HSIZE_SHIFT 0 +#define DCMIPP_PxDSSZR_HSIZE_MASK GENMASK(11, 0) +#define DCMIPP_PxDSSZR_VSIZE_SHIFT 16 +#define DCMIPP_PxDSSZR_VSIZE_MASK GENMASK(27, 16) + +#define DCMIPP_P1GMCR 0x970 +#define DCMIPP_P2GMCR 0xD70 +#define DCMIPP_PxGMCR(id) (((id) == 1) ? DCMIPP_P1GMCR :\ + DCMIPP_P2GMCR) +#define DCMIPP_PxGMCR_ENABLE BIT(0) + +#define DCMIPP_P1YUVCR 0x980 +#define DCMIPP_P1YUVCR_ENABLE BIT(0) +#define DCMIPP_P1YUVCR_TYPE_RGB BIT(1) +#define DCMIPP_P1YUVCR_CLAMP BIT(2) +#define DCMIPP_P1YUVRR1 0x984 +#define DCMIPP_P1YUVRR2 0x988 +#define DCMIPP_P1YUVGR1 0x98C +#define DCMIPP_P1YUVGR2 0x990 +#define DCMIPP_P1YUVBR1 0x994 +#define DCMIPP_P1YUVBR2 0x998 + +#define PIXELPROC_MEDIA_BUS_FMT_DEFAULT MEDIA_BUS_FMT_RGB888_1X24 + +/* Macro for negative coefficient, 11 bits coded */ +#define N11(val) (((val) ^ 0x7ff) + 1) +/* Macro for added value, 10 bits coded */ +#define N10(val) (((val) ^ 0x3ff) + 1) + +/* Macro to convert row matrix to DCMIPP PxCCCyy register value */ +#define CCTBL(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba) \ + .conv_matrix = { \ + ((rg) << 16 | (rr)), ((ra) << 16 | (rb)), \ + ((gg) << 16 | (gr)), ((ga) << 16 | (gb)), \ + ((bg) << 16 | (br)), ((ba) << 16 | (bb)) } + +struct dcmipp_colorconv_config { + unsigned int conv_matrix[6]; + bool clamping; + bool clamping_as_rgb; +}; + +static const struct dcmipp_colorconv_config dcmipp_rgbfull_to_yuv601full = { + /* R G B Add */ + CCTBL(131, N11(110), N11(21), 128, /* Cr */ + 77, 150, 29, 0, /* Y */ + N11(44), N11(87), 131, 128), /* Cb */ +}; + +static const struct dcmipp_colorconv_config dcmipp_rgbfull_to_yuv601lim = { + /* R G B Add */ + CCTBL(112, N11(94), N11(18), 128, /* Cr */ + 66, 129, 25, 16, /* Y */ + N11(38), N11(74), 112, 128), /* Cb */ + .clamping = true, +}; + +static const struct dcmipp_colorconv_config dcmipp_rgbfull_to_yuv709full = { + /* R G B Add */ + CCTBL(131, N11(119), N11(12), 128, /* Cr */ + 55, 183, 18, 0, /* Y */ + N11(30), N11(101), 131, 128), /* Cb */ +}; + +static const struct dcmipp_colorconv_config dcmipp_rgbfull_to_yuv709lim = { + /* R G B Add */ + CCTBL(112, N11(102), N11(10), 128, /* Cr */ + 47, 157, 16, 16, /* Y */ + N11(26), N11(87), 112, 128), /* Cb */ + .clamping = true, +}; + +static const struct dcmipp_colorconv_config dcmipp_rgblim_to_yuv601lim = { + /* R G B Add */ + CCTBL(131, N11(110), N11(21), 128, /* Cr */ + 77, 150, 29, 0, /* Y */ + N11(44), N11(87), 131, 128), /* Cb */ + .clamping = true, +}; + +static const struct dcmipp_colorconv_config dcmipp_rgblim_to_yuv709lim = { + /* R G B Add */ + CCTBL(131, N11(119), N11(12), 128, /* Cr */ + 55, 183, 18, 0, /* Y */ + N11(30), N11(101), 131, 128), /* Cb */ + .clamping = true, +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv601full_to_rgbfull = { + /* Cr Y Cb Add */ + CCTBL(351, 256, 0, N10(175), /* R */ + N11(179), 256, N11(86), 132, /* G */ + 0, 256, 443, N10(222)), /* B */ +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv601lim_to_rgbfull = { + /* Cr Y Cb Add */ + CCTBL(409, 298, 0, N10(223), /* R */ + N11(208), 298, N11(100), 135, /* G */ + 0, 298, 517, N10(277)), /* B */ +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv601lim_to_rgblim = { + /* Cr Y Cb Add */ + CCTBL(351, 256, 0, N10(175), /* R */ + N11(179), 256, N11(86), 132, /* G */ + 0, 256, 443, N10(222)), /* B */ + .clamping = true, + .clamping_as_rgb = true, +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv709full_to_rgbfull = { + /* Cr Y Cb Add */ + CCTBL(394, 256, 0, N10(197), /* R */ + N11(118), 256, N11(47), 82, /* G */ + 0, 256, 456, N10(232)), /* B */ +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv709lim_to_rgbfull = { + /* Cr Y Cb Add */ + CCTBL(459, 298, 0, N10(248), /* R */ + N11(137), 298, N11(55), 77, /* G */ + 0, 298, 541, N10(289)), /* B */ +}; + +static const struct dcmipp_colorconv_config dcmipp_yuv709lim_to_rgblim = { + /* Cr Y Cb Add */ + CCTBL(394, 256, 0, N10(197), /* R */ + N11(118), 256, N11(47), 82, /* G */ + 0, 256, 465, N10(232)), /* B */ + .clamping = true, + .clamping_as_rgb = true, +}; + +/* cconv_matrices[src_fmt][src_range][sink_fmt][sink_range] */ +static const struct dcmipp_colorconv_config *dcmipp_cconv_cfgs[3][2][3][2] = { + /* RGB */ + { + /* RGB full range */ + { + /* RGB full range => RGB */ + { + NULL, NULL, + }, + /* RGB full range => YUV601 */ + { + &dcmipp_rgbfull_to_yuv601full, + &dcmipp_rgbfull_to_yuv601lim, + }, + /* RGB full range => YUV709 */ + { + &dcmipp_rgbfull_to_yuv709full, + &dcmipp_rgbfull_to_yuv709lim, + }, + }, + /* RGB limited range */ + { + /* RGB limited range => RGB */ + { + NULL, NULL, + }, + /* RGB limited range => YUV601 */ + { + NULL, &dcmipp_rgblim_to_yuv601lim, + }, + /* RGB limited range => YUV709 */ + { + NULL, &dcmipp_rgblim_to_yuv709lim, + }, + }, + }, + /* YUV601 */ + { + /* YUV601 full range */ + { + /* YUV601 full range => RGB */ + { + &dcmipp_yuv601full_to_rgbfull, NULL, + }, + /* YUV601 full range => YUV601 */ + { + NULL, NULL, + }, + /* YUV601 full range => YUV709 */ + { + NULL, NULL, + }, + }, + /* YUV601 limited range */ + { + /* YUV601 limited range => RGB */ + { + &dcmipp_yuv601lim_to_rgbfull, + &dcmipp_yuv601lim_to_rgblim, + }, + /* YUV601 limited range => YUV601 */ + { + NULL, NULL, + }, + /* YUV601 limited range => YUV709 */ + { + NULL, NULL, + }, + }, + }, + /* YUV709 */ + { + /* YUV709 full range */ + { + /* YUV709 full range => RGB */ + { + &dcmipp_yuv709full_to_rgbfull, NULL, + }, + /* YUV709 full range => YUV601 */ + { + NULL, NULL, + }, + /* YUV709 full range => YUV709 */ + { + NULL, NULL, + }, + }, + /* YUV709 limited range */ + { + /* YUV709 limited range => RGB */ + { + &dcmipp_yuv709lim_to_rgbfull, + &dcmipp_yuv709lim_to_rgblim, + }, + /* YUV709 limited range => YUV601 */ + { + NULL, NULL, + }, + /* YUV709 limited range => YUV709 */ + { + NULL, NULL, + }, + }, + }, +}; + +enum dcmipp_cconv_fmt { + FMT_RGB = 0, + FMT_YUV601, + FMT_YUV709 +}; + +static inline enum dcmipp_cconv_fmt to_cconv_fmt(struct v4l2_mbus_framefmt *fmt) +{ + /* YUV format codes are within the 0x2xxx */ + if (fmt->code >= MEDIA_BUS_FMT_Y8_1X8 && + fmt->code < MEDIA_BUS_FMT_SBGGR8_1X8) { + if (fmt->ycbcr_enc == V4L2_YCBCR_ENC_709) + return FMT_YUV709; + else + return FMT_YUV601; + } + + /* All other formats are referred as RGB, indeed, demosaicing bloc + * generate RGB format + */ + return FMT_RGB; +}; + +#define FMT_STR(f) ({ \ + typeof(f) __f = (f); \ + (__f) == FMT_RGB ? "RGB" : \ + (__f) == FMT_YUV601 ? "YUV601" : \ + (__f) == FMT_YUV709 ? "YUV709" : "?"; }) + +enum dcmipp_cconv_range { + RANGE_FULL = 0, + RANGE_LIMITED, +}; + +static inline enum dcmipp_cconv_range +to_cconv_range(struct v4l2_mbus_framefmt *fmt) +{ + if (fmt->quantization == V4L2_QUANTIZATION_FULL_RANGE) + return RANGE_FULL; + + return RANGE_LIMITED; +}; + +#define RANGE_STR(range) ((range) == RANGE_FULL ? "full" : "limited") + +struct dcmipp_pixelproc_device { + struct dcmipp_ent_device ved; + struct v4l2_subdev sd; + struct device *dev; + bool streaming; + + void __iomem *regs; + struct v4l2_ctrl_handler ctrls; + + u32 pipe_id; +}; + +static const struct v4l2_mbus_framefmt fmt_default = { + .width = DCMIPP_FMT_WIDTH_DEFAULT, + .height = DCMIPP_FMT_HEIGHT_DEFAULT, + .code = PIXELPROC_MEDIA_BUS_FMT_DEFAULT, + .field = V4L2_FIELD_NONE, + .colorspace = DCMIPP_COLORSPACE_DEFAULT, + .ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT, + .quantization = DCMIPP_QUANTIZATION_DEFAULT, + .xfer_func = DCMIPP_XFER_FUNC_DEFAULT, +}; + +static const struct v4l2_rect min_rect = { + .width = DCMIPP_FRAME_MIN_WIDTH, + .height = DCMIPP_FRAME_MIN_HEIGHT, + .top = 0, + .left = 0, +}; + +/* + * Downscale is a combination of both decimation block (1/2/4/8) + * and downsize block (up to 8x) for a total of maximum downscale of 64 + */ +#define DCMIPP_MAX_DECIMATION_RATIO 8 +#define DCMIPP_MAX_DOWNSIZE_RATIO 8 +#define DCMIPP_MAX_DOWNSCALE_RATIO 64 + +/* + * Functions handling controls + */ +static int dcmipp_pixelproc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dcmipp_pixelproc_device *pixelproc = + container_of(ctrl->handler, + struct dcmipp_pixelproc_device, ctrls); + + if (!pm_runtime_get_if_in_use(pixelproc->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_DCMIPP_PIXELPROC_GAMMA_CORRECTION_ENABLE: + reg_write(pixelproc, DCMIPP_PxGMCR(pixelproc->pipe_id), + (ctrl->val ? DCMIPP_PxGMCR_ENABLE : 0)); + break; + } + + pm_runtime_put(pixelproc->dev); + + return 0; +}; + +static const struct v4l2_ctrl_ops dcmipp_pixelproc_ctrl_ops = { + .s_ctrl = dcmipp_pixelproc_s_ctrl, +}; + +static const struct v4l2_ctrl_config dcmipp_pixelproc_ctrls[] = { + { + .ops = &dcmipp_pixelproc_ctrl_ops, + .id = V4L2_CID_DCMIPP_PIXELPROC_GAMMA_CORRECTION_ENABLE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Gamma Correction Enable", + .min = 0, + .max = 1, + .step = 1, + .def = 0, + } +}; + +static void dcmipp_pixelproc_adjust_crop(struct v4l2_rect *r, + const struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_rect src_rect = { + .top = 0, + .left = 0, + .width = fmt->width, + .height = fmt->height, + }; + + /* Disallow rectangles smaller than the minimal one. */ + v4l2_rect_set_min_size(r, &min_rect); + v4l2_rect_map_inside(r, &src_rect); +} + +static void +dcmipp_pixelproc_adjust_fmt(struct dcmipp_pixelproc_device *pixelproc, + struct v4l2_mbus_framefmt *fmt, u32 pad) +{ + const struct dcmipp_pixelpipe_pix_map *vpix; + + /* Only accept code in the pix map table */ + vpix = dcmipp_pixelpipe_pix_map_by_code(fmt->code, + pixelproc->pipe_id == 1 ? DCMIPP_MAIN : DCMIPP_AUX, + pad); + if (!vpix) + fmt->code = PIXELPROC_MEDIA_BUS_FMT_DEFAULT; + + fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH, + DCMIPP_FRAME_MAX_WIDTH); + fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT, + DCMIPP_FRAME_MAX_HEIGHT); + + if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE) + fmt->field = V4L2_FIELD_NONE; + + dcmipp_colorimetry_clamp(fmt); +} + +static int dcmipp_pixelproc_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + unsigned int i; + + for (i = 0; i < sd->entity.num_pads; i++) { + *v4l2_subdev_state_get_format(state, i) = fmt_default; + + if (IS_SINK(i)) { + struct v4l2_rect r = { + .top = 0, + .left = 0, + .width = DCMIPP_FMT_WIDTH_DEFAULT, + .height = DCMIPP_FMT_HEIGHT_DEFAULT, + }; + *v4l2_subdev_state_get_crop(state, i) = r; + *v4l2_subdev_state_get_compose(state, i) = r; + } + } + + return 0; +} + +static int +dcmipp_pixelproc_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + + return dcmipp_pixelpipe_enum_mbus_code(pixelproc->pipe_id == 1 ? DCMIPP_MAIN : DCMIPP_AUX, + code); +} + +static int +dcmipp_pixelproc_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + + return dcmipp_pixelpipe_enum_frame_size(pixelproc->pipe_id == 1 ? DCMIPP_MAIN : DCMIPP_AUX, + fse); +} + +static int dcmipp_pixelproc_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + + if (v4l2_subdev_is_streaming(sd)) + return -EBUSY; + + dcmipp_pixelproc_adjust_fmt(pixelproc, &fmt->format, fmt->pad); + + if (IS_SINK(fmt->pad)) { + struct v4l2_mbus_framefmt *src_fmt = + v4l2_subdev_state_get_format(state, 1); + struct v4l2_rect r = { + .top = 0, + .left = 0, + .width = fmt->format.width, + .height = fmt->format.height, + }; + + /* Adjust SINK pad crop/compose */ + *v4l2_subdev_state_get_crop(state, 0) = r; + *v4l2_subdev_state_get_compose(state, 0) = r; + + /* Forward format to SRC pad */ + *src_fmt = fmt->format; + src_fmt->code = dcmipp_pixelpipe_src_format(fmt->format.code); + } else { + struct v4l2_rect *compose = + v4l2_subdev_state_get_compose(state, 0); + + /* AUX (pipe_nb 2) cannot perform color conv */ + if (pixelproc->pipe_id == 2) { + struct v4l2_mbus_framefmt *sink_fmt = + v4l2_subdev_state_get_format(state, 0); + + fmt->format = *sink_fmt; + fmt->format.code = + dcmipp_pixelpipe_src_format(fmt->format.code); + } + + fmt->format.width = compose->width; + fmt->format.height = compose->height; + } + + /* Update the selected pad format */ + *v4l2_subdev_state_get_format(state, fmt->pad) = fmt->format; + + return 0; +} + +static int dcmipp_pixelproc_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *s) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *crop, *compose; + + if (IS_SRC(s->pad)) + return -EINVAL; + + if (v4l2_subdev_is_streaming(sd)) + return -EBUSY; + + crop = v4l2_subdev_state_get_crop(state, s->pad); + compose = v4l2_subdev_state_get_compose(state, s->pad); + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + sink_fmt = v4l2_subdev_state_get_format(state, s->pad); + dcmipp_pixelproc_adjust_crop(&s->r, sink_fmt); + + *crop = s->r; + *compose = s->r; + + dev_dbg(pixelproc->dev, "s_selection: crop (%d,%d)/%ux%u\n", + crop->left, crop->top, crop->width, crop->height); + break; + case V4L2_SEL_TGT_COMPOSE: + s->r.top = 0; + s->r.left = 0; + s->r.width = clamp_t(u32, s->r.width, + crop->width / DCMIPP_MAX_DOWNSCALE_RATIO, + crop->width); + s->r.height = clamp_t(u32, s->r.height, + crop->height / DCMIPP_MAX_DOWNSCALE_RATIO, + crop->height); + v4l2_rect_set_min_size(&s->r, &min_rect); + *compose = s->r; + + dev_dbg(pixelproc->dev, "s_selection: compose (%d,%d)/%ux%u\n", + compose->left, compose->top, + compose->width, compose->height); + break; + default: + return -EINVAL; + } + + /* Update the source pad size */ + src_fmt = v4l2_subdev_state_get_format(state, 1); + src_fmt->width = s->r.width; + src_fmt->height = s->r.height; + + return 0; +} + +static int +dcmipp_pixelproc_colorconv_config(struct dcmipp_pixelproc_device *pixelproc, + struct v4l2_mbus_framefmt *sink, + struct v4l2_mbus_framefmt *src) +{ + const struct dcmipp_colorconv_config *cconv_cfg; + enum dcmipp_cconv_fmt sink_fmt = to_cconv_fmt(sink); + enum dcmipp_cconv_range sink_range = to_cconv_range(sink); + enum dcmipp_cconv_fmt src_fmt = to_cconv_fmt(src); + enum dcmipp_cconv_range src_range = to_cconv_range(src); + unsigned int val = 0; + int i; + + /* Disable color conversion by default */ + reg_write(pixelproc, DCMIPP_P1YUVCR, 0); + + if (sink_fmt == src_fmt && sink_range == src_range) + return 0; + + /* color conversion */ + cconv_cfg = dcmipp_cconv_cfgs[sink_fmt][sink_range][src_fmt][src_range]; + if (!cconv_cfg) { + dev_err(pixelproc->dev, + "Unsupported color conversion %s-%s => %s-%s\n", + FMT_STR(sink_fmt), RANGE_STR(sink_range), + FMT_STR(src_fmt), RANGE_STR(src_range)); + return -EINVAL; + } + + dev_dbg(pixelproc->dev, "color conversion %s-%s => %s-%s\n", + FMT_STR(sink_fmt), RANGE_STR(sink_range), + FMT_STR(src_fmt), RANGE_STR(src_range)); + + for (i = 0; i < 6; i++) + reg_write(pixelproc, DCMIPP_P1YUVRR1 + (4 * i), + cconv_cfg->conv_matrix[i]); + + if (cconv_cfg->clamping) + val |= DCMIPP_P1YUVCR_CLAMP; + if (cconv_cfg->clamping_as_rgb) + val |= DCMIPP_P1YUVCR_TYPE_RGB; + val |= DCMIPP_P1YUVCR_ENABLE; + + reg_write(pixelproc, DCMIPP_P1YUVCR, val); + + return 0; +} + +#define DCMIPP_PIXELPROC_HVRATIO_CONS 8192 +#define DCMIPP_PIXELPROC_HVRATIO_MAX 65535 +#define DCMIPP_PIXELPROC_HVDIV_CONS 1024 +#define DCMIPP_PIXELPROC_HVDIV_MAX 1023 +static void +dcmipp_pixelproc_set_crop_downscale(struct dcmipp_pixelproc_device *pixelproc, + struct v4l2_rect *compose, + struct v4l2_rect *crop) +{ + unsigned int hratio, vratio, hdiv, vdiv; + unsigned int hdec = 0, vdec = 0; + unsigned int h_post_dec = crop->width; + unsigned int v_post_dec = crop->height; + + /* Configure cropping */ + reg_write(pixelproc, DCMIPP_PxCRSTR(pixelproc->pipe_id), + (crop->top << DCMIPP_PxCRSTR_VSTART_SHIFT) | + (crop->left << DCMIPP_PxCRSTR_HSTART_SHIFT)); + reg_write(pixelproc, DCMIPP_PxCRSZR(pixelproc->pipe_id), + (crop->width << DCMIPP_PxCRSZR_HSIZE_SHIFT) | + (crop->height << DCMIPP_PxCRSZR_VSIZE_SHIFT) | + DCMIPP_PxCRSZR_ENABLE); + + /* Compute decimation factors (HDEC/VDEC) */ + while (compose->width * DCMIPP_MAX_DOWNSIZE_RATIO < h_post_dec) { + hdec++; + h_post_dec /= 2; + } + while (compose->height * DCMIPP_MAX_DOWNSIZE_RATIO < v_post_dec) { + vdec++; + v_post_dec /= 2; + } + + /* Compute downsize factor */ + hratio = h_post_dec * DCMIPP_PIXELPROC_HVRATIO_CONS / + compose->width; + if (hratio > DCMIPP_PIXELPROC_HVRATIO_MAX) + hratio = DCMIPP_PIXELPROC_HVRATIO_MAX; + vratio = v_post_dec * DCMIPP_PIXELPROC_HVRATIO_CONS / + compose->height; + if (vratio > DCMIPP_PIXELPROC_HVRATIO_MAX) + vratio = DCMIPP_PIXELPROC_HVRATIO_MAX; + hdiv = (DCMIPP_PIXELPROC_HVDIV_CONS * compose->width) / + h_post_dec; + if (hdiv > DCMIPP_PIXELPROC_HVDIV_MAX) + hdiv = DCMIPP_PIXELPROC_HVDIV_MAX; + vdiv = (DCMIPP_PIXELPROC_HVDIV_CONS * compose->height) / + v_post_dec; + if (vdiv > DCMIPP_PIXELPROC_HVDIV_MAX) + vdiv = DCMIPP_PIXELPROC_HVDIV_MAX; + + dev_dbg(pixelproc->dev, "%s: decimation config: hdec: 0x%x, vdec: 0x%x\n", + pixelproc->sd.name, + hdec, vdec); + dev_dbg(pixelproc->dev, "%s: downsize config: hratio: 0x%x, vratio: 0x%x, hdiv: 0x%x, vdiv: 0x%x\n", + pixelproc->sd.name, + hratio, vratio, + hdiv, vdiv); + + reg_clear(pixelproc, DCMIPP_PxDCCR(pixelproc->pipe_id), + DCMIPP_PxDCCR_ENABLE); + if (hdec || vdec) + reg_write(pixelproc, DCMIPP_PxDCCR(pixelproc->pipe_id), + (hdec << DCMIPP_PxDCCR_HDEC_SHIFT) | + (vdec << DCMIPP_PxDCCR_VDEC_SHIFT) | + DCMIPP_PxDCCR_ENABLE); + + reg_clear(pixelproc, DCMIPP_PxDSCR(pixelproc->pipe_id), + DCMIPP_PxDSCR_ENABLE); + reg_write(pixelproc, DCMIPP_PxDSRTIOR(pixelproc->pipe_id), + (hratio << DCMIPP_PxDSRTIOR_HRATIO_SHIFT) | + (vratio << DCMIPP_PxDSRTIOR_VRATIO_SHIFT)); + reg_write(pixelproc, DCMIPP_PxDSSZR(pixelproc->pipe_id), + (compose->width << DCMIPP_PxDSSZR_HSIZE_SHIFT) | + (compose->height << DCMIPP_PxDSSZR_VSIZE_SHIFT)); + reg_write(pixelproc, DCMIPP_PxDSCR(pixelproc->pipe_id), + (hdiv << DCMIPP_PxDSCR_HDIV_SHIFT) | + (vdiv << DCMIPP_PxDSCR_VDIV_SHIFT) | + DCMIPP_PxDSCR_ENABLE); +} + +static int dcmipp_pixelproc_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + struct v4l2_subdev *s_subdev; + struct media_pad *s_pad; + int ret; + + /* Get source subdev */ + s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); + if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) + return -EINVAL; + s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); + + /* Configure crop/downscale */ + dcmipp_pixelproc_set_crop_downscale(pixelproc, + v4l2_subdev_state_get_compose(state, 0), + v4l2_subdev_state_get_crop(state, 0)); + + /* Configure YUV Conversion (if applicable) */ + if (pixelproc->pipe_id == 1) { + ret = dcmipp_pixelproc_colorconv_config(pixelproc, + v4l2_subdev_state_get_format(state, 0), + v4l2_subdev_state_get_format(state, 1)); + if (ret) + return ret; + } + + /* Apply customized values from user when stream starts. */ + ret = v4l2_ctrl_handler_setup(pixelproc->sd.ctrl_handler); + if (ret < 0) { + dev_err(pixelproc->dev, + "failed to start source subdev streaming (%d)\n", ret); + return ret; + } + + ret = v4l2_subdev_enable_streams(s_subdev, s_pad->index, BIT_ULL(0)); + if (ret < 0) { + dev_err(pixelproc->dev, + "failed to start source subdev streaming (%d)\n", ret); + return ret; + } + + return 0; +} + +static int dcmipp_pixelproc_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + struct v4l2_subdev *s_subdev; + struct media_pad *s_pad; + int ret; + + /* Get source subdev */ + s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]); + if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity)) + return -EINVAL; + s_subdev = media_entity_to_v4l2_subdev(s_pad->entity); + + ret = v4l2_subdev_disable_streams(s_subdev, s_pad->index, BIT_ULL(0)); + if (ret < 0) + dev_err(pixelproc->dev, + "failed to stop source subdev streaming (%d)\n", + ret); + return ret; +} + +static const struct v4l2_subdev_pad_ops dcmipp_pixelproc_pad_ops = { + .enum_mbus_code = dcmipp_pixelproc_enum_mbus_code, + .enum_frame_size = dcmipp_pixelproc_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = dcmipp_pixelproc_set_fmt, + .get_selection = dcmipp_pixelpipe_get_selection, + .set_selection = dcmipp_pixelproc_set_selection, + .enable_streams = dcmipp_pixelproc_enable_streams, + .disable_streams = dcmipp_pixelproc_disable_streams, +}; + +static const struct v4l2_subdev_core_ops dcmipp_pixelproc_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops dcmipp_pixelproc_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_ops dcmipp_pixelproc_ops = { + .core = &dcmipp_pixelproc_core_ops, + .pad = &dcmipp_pixelproc_pad_ops, + .video = &dcmipp_pixelproc_video_ops, +}; + +static void dcmipp_pixelproc_release(struct v4l2_subdev *sd) +{ + struct dcmipp_pixelproc_device *pixelproc = v4l2_get_subdevdata(sd); + + v4l2_ctrl_handler_free(&pixelproc->ctrls); + kfree(pixelproc); +} + +static const struct v4l2_subdev_internal_ops dcmipp_pixelproc_int_ops = { + .init_state = dcmipp_pixelproc_init_state, + .release = dcmipp_pixelproc_release, +}; + +void dcmipp_pixelproc_ent_release(struct dcmipp_ent_device *ved) +{ + struct dcmipp_pixelproc_device *pixelproc = + container_of(ved, struct dcmipp_pixelproc_device, ved); + + dcmipp_ent_sd_unregister(ved, &pixelproc->sd); +} + +static int dcmipp_name_to_pipe_id(const char *name) +{ + if (strstr(name, "main")) + return 1; + else if (strstr(name, "aux")) + return 2; + else + return -EINVAL; +} + +struct dcmipp_ent_device * +dcmipp_pixelproc_ent_init(const char *entity_name, + struct dcmipp_device *dcmipp) +{ + struct dcmipp_pixelproc_device *pixelproc; + const unsigned long pads_flag[] = { + MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE, + }; + int ret, i; + + /* Allocate the pixelproc struct */ + pixelproc = kzalloc_obj(*pixelproc); + if (!pixelproc) + return ERR_PTR(-ENOMEM); + + pixelproc->regs = dcmipp->regs; + pixelproc->dev = dcmipp->dev; + + /* Pipe identifier */ + pixelproc->pipe_id = dcmipp_name_to_pipe_id(entity_name); + if (pixelproc->pipe_id != 1 && pixelproc->pipe_id != 2) { + dev_err(pixelproc->dev, "failed to retrieve pipe_id\n"); + ret = -EIO; + goto err_kfree; + } + + /* Initialize controls */ + v4l2_ctrl_handler_init(&pixelproc->ctrls, + ARRAY_SIZE(dcmipp_pixelproc_ctrls)); + + for (i = 0; i < ARRAY_SIZE(dcmipp_pixelproc_ctrls); i++) + v4l2_ctrl_new_custom(&pixelproc->ctrls, + &dcmipp_pixelproc_ctrls[i], NULL); + + pixelproc->sd.ctrl_handler = &pixelproc->ctrls; + if (pixelproc->ctrls.error) { + ret = pixelproc->ctrls.error; + dev_err(pixelproc->dev, "control initialization error %d\n", ret); + goto err_ctrl_handler_free; + } + + /* Initialize ved and sd */ + ret = dcmipp_ent_sd_register(&pixelproc->ved, &pixelproc->sd, + &dcmipp->v4l2_dev, entity_name, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER, + ARRAY_SIZE(pads_flag), pads_flag, + &dcmipp_pixelproc_int_ops, + &dcmipp_pixelproc_ops, + NULL, NULL); + if (ret) + goto err_ctrl_handler_free; + + pixelproc->ved.dcmipp = dcmipp; + + return &pixelproc->ved; + +err_ctrl_handler_free: + v4l2_ctrl_handler_free(&pixelproc->ctrls); +err_kfree: + kfree(pixelproc); + + return ERR_PTR(ret); +} From 30cbd574ea1eabe9a2c85e86bad502454dc290a4 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:14 +0200 Subject: [PATCH 150/596] media: stm32: dcmipp: add pixel-pipe support in bytecap The dump pipe and pixel pipes capture part (tail of each pipe) is different in that pixel pipes have a pixel packer capable of generating various output format while the on dump pipe no such manipulation is possible. Still, all the buffer handling, format related manipulations are all same hence both dump and pixel pipe capture part are put together to avoid having large duplication of code. This patch adds the pixel pipe capture within bytecap hence name isn't modified and a further commit should rename the file and probably function name to not only highlight byte capture (aka dump pipe). Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../st/stm32/stm32-dcmipp/dcmipp-bytecap.c | 421 +++++++++++++++--- 1 file changed, 355 insertions(+), 66 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 53341b798457..4a8976da576a 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c @@ -25,27 +25,81 @@ #define DCMIPP_CMIER_P0ALL (DCMIPP_CMIER_P0VSYNCIE |\ DCMIPP_CMIER_P0FRAMEIE |\ DCMIPP_CMIER_P0OVRIE) +#define DCMIPP_CMIER_P1FRAMEIE BIT(17) +#define DCMIPP_CMIER_P1VSYNCIE BIT(18) +#define DCMIPP_CMIER_P1OVRIE BIT(23) +#define DCMIPP_CMIER_P1ALL (DCMIPP_CMIER_P1VSYNCIE |\ + DCMIPP_CMIER_P1FRAMEIE |\ + DCMIPP_CMIER_P1OVRIE) +#define DCMIPP_CMIER_P2FRAMEIE BIT(25) +#define DCMIPP_CMIER_P2VSYNCIE BIT(26) +#define DCMIPP_CMIER_P2OVRIE BIT(31) +#define DCMIPP_CMIER_P2ALL (DCMIPP_CMIER_P2VSYNCIE |\ + DCMIPP_CMIER_P2FRAMEIE |\ + DCMIPP_CMIER_P2OVRIE) +#define DCMIPP_CMIER_PxALL(id) (((id) == 0) ? DCMIPP_CMIER_P0ALL : \ + (((id) == 1) ? DCMIPP_CMIER_P1ALL : \ + DCMIPP_CMIER_P2ALL)) #define DCMIPP_CMSR1 0x3f4 #define DCMIPP_CMSR2 0x3f8 #define DCMIPP_CMSR2_P0FRAMEF BIT(9) #define DCMIPP_CMSR2_P0VSYNCF BIT(10) #define DCMIPP_CMSR2_P0OVRF BIT(15) +#define DCMIPP_CMSR2_P1FRAMEF BIT(17) +#define DCMIPP_CMSR2_P1VSYNCF BIT(18) +#define DCMIPP_CMSR2_P1OVRF BIT(23) +#define DCMIPP_CMSR2_P2FRAMEF BIT(25) +#define DCMIPP_CMSR2_P2VSYNCF BIT(26) +#define DCMIPP_CMSR2_P2OVRF BIT(31) +#define DCMIPP_CMSR2_PxFRAMEF(id) (((id) == 0) ? DCMIPP_CMSR2_P0FRAMEF :\ + (((id) == 1) ? DCMIPP_CMSR2_P1FRAMEF :\ + DCMIPP_CMSR2_P2FRAMEF)) +#define DCMIPP_CMSR2_PxVSYNCF(id) (((id) == 0) ? DCMIPP_CMSR2_P0VSYNCF :\ + (((id) == 1) ? DCMIPP_CMSR2_P1VSYNCF :\ + DCMIPP_CMSR2_P2VSYNCF)) +#define DCMIPP_CMSR2_PxOVRF(id) (((id) == 0) ? DCMIPP_CMSR2_P0OVRF :\ + (((id) == 1) ? DCMIPP_CMSR2_P1OVRF :\ + DCMIPP_CMSR2_P2OVRF)) #define DCMIPP_CMFCR 0x3fc -#define DCMIPP_P0FSCR 0x404 -#define DCMIPP_P0FSCR_PIPEN BIT(31) -#define DCMIPP_P0FCTCR 0x500 -#define DCMIPP_P0FCTCR_CPTREQ BIT(3) +#define DCMIPP_PxFSCR(id) (0x404 + ((id) * 0x400)) +#define DCMIPP_PxFSCR_PIPEN BIT(31) +#define DCMIPP_PxFCTCR(id) (0x500 + ((id) * 0x400)) +#define DCMIPP_PxFCTCR_CPTREQ BIT(3) #define DCMIPP_P0DCCNTR 0x5b0 #define DCMIPP_P0DCLMTR 0x5b4 #define DCMIPP_P0DCLMTR_ENABLE BIT(31) #define DCMIPP_P0DCLMTR_LIMIT_MASK GENMASK(23, 0) -#define DCMIPP_P0PPM0AR1 0x5c4 -#define DCMIPP_P0SR 0x5f8 -#define DCMIPP_P0SR_CPTACT BIT(23) + +#define DCMIPP_PxPPM0AR1(id) (0x5c4 + ((id) * 0x400)) +#define DCMIPP_PxPPM0PR(id) (0x9cc + (((id) - 1) * 0x400)) +#define DCMIPP_P1PPM1AR1 0x9d4 +#define DCMIPP_P1PPM1PR 0x9dc +#define DCMIPP_P1PPM2AR1 0x9e4 + +#define DCMIPP_PxSR(id) (0x5f8 + ((id) * 0x400)) +#define DCMIPP_PxSR_CPTACT BIT(23) + +#define DCMIPP_PxPPCR(id) (0x9c0 + (((id) - 1) * 0x400)) +#define DCMIPP_PxPPCR_FORMAT_RGB888 0x0 +#define DCMIPP_PxPPCR_FORMAT_RGB565 0x1 +#define DCMIPP_PxPPCR_FORMAT_ARGB8888 0x2 +#define DCMIPP_PxPPCR_FORMAT_RGBA8888 0x3 +#define DCMIPP_PxPPCR_FORMAT_Y8 0x4 +#define DCMIPP_PxPPCR_FORMAT_YUV444 0x5 +#define DCMIPP_PxPPCR_FORMAT_YUYV 0x6 +#define DCMIPP_P1PPCR_FORMAT_NV61 0x7 +#define DCMIPP_P1PPCR_FORMAT_NV21 0x8 +#define DCMIPP_P1PPCR_FORMAT_YV12 0x9 +#define DCMIPP_PxPPCR_FORMAT_UYVY 0xa + +#define DCMIPP_PxPPCR_SWAPRB BIT(4) struct dcmipp_bytecap_pix_map { unsigned int code; u32 pixelformat; + u32 plane_nb; + unsigned int ppcr_fmt; + unsigned int swap_uv; }; #define PIXMAP_MBUS_PFMT(mbus, fmt) \ @@ -54,7 +108,7 @@ struct dcmipp_bytecap_pix_map { .pixelformat = V4L2_PIX_FMT_##fmt \ } -static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pix_map_list[] = { +static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_dump_pix_map_list[] = { PIXMAP_MBUS_PFMT(RGB565_2X8_LE, RGB565), PIXMAP_MBUS_PFMT(RGB565_1X16, RGB565), PIXMAP_MBUS_PFMT(RGB888_1X24, RGB24), @@ -89,24 +143,47 @@ static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pix_map_list[] = { PIXMAP_MBUS_PFMT(JPEG_1X8, JPEG), }; -static const struct dcmipp_bytecap_pix_map * -dcmipp_bytecap_pix_map_by_pixelformat(u32 pixelformat) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) { - if (dcmipp_bytecap_pix_map_list[i].pixelformat == pixelformat) - return &dcmipp_bytecap_pix_map_list[i]; +#define PIXMAP_MBUS_PIXEL_PFMT(mbus, fmt, nb_plane, pp_code, swap) \ + { \ + .code = MEDIA_BUS_FMT_##mbus, \ + .pixelformat = V4L2_PIX_FMT_##fmt, \ + .plane_nb = nb_plane, \ + .ppcr_fmt = pp_code, \ + .swap_uv = swap, \ } - return NULL; -} +static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pixel_pix_map_list[] = { + /* Coplanar formats are supported on main & aux pipe */ + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, RGB565, 1, DCMIPP_PxPPCR_FORMAT_RGB565, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, YUYV, 1, DCMIPP_PxPPCR_FORMAT_YUYV, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, YVYU, 1, DCMIPP_PxPPCR_FORMAT_YUYV, 1), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, UYVY, 1, DCMIPP_PxPPCR_FORMAT_UYVY, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, VYUY, 1, DCMIPP_PxPPCR_FORMAT_UYVY, 1), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, GREY, 1, DCMIPP_PxPPCR_FORMAT_Y8, 0), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, RGB24, 1, DCMIPP_PxPPCR_FORMAT_RGB888, 1), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, BGR24, 1, DCMIPP_PxPPCR_FORMAT_RGB888, 0), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, ARGB32, 1, DCMIPP_PxPPCR_FORMAT_RGBA8888, 1), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, ABGR32, 1, DCMIPP_PxPPCR_FORMAT_ARGB8888, 0), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, RGBA32, 1, DCMIPP_PxPPCR_FORMAT_ARGB8888, 1), + PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, BGRA32, 1, DCMIPP_PxPPCR_FORMAT_RGBA8888, 0), + + /* Semiplanar & planar formats (plane_nb > 1) are only supported on main pipe */ + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, NV12, 2, DCMIPP_P1PPCR_FORMAT_NV21, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, NV21, 2, DCMIPP_P1PPCR_FORMAT_NV21, 1), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, NV16, 2, DCMIPP_P1PPCR_FORMAT_NV61, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, NV61, 2, DCMIPP_P1PPCR_FORMAT_NV61, 1), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, YUV420, 3, DCMIPP_P1PPCR_FORMAT_YV12, 0), + PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, YVU420, 3, DCMIPP_P1PPCR_FORMAT_YV12, 1), +}; struct dcmipp_buf { struct vb2_v4l2_buffer vb; bool prepared; dma_addr_t addr; size_t size; + dma_addr_t addrs[3]; + u32 strides[3]; + u64 sizes[3]; struct list_head list; }; @@ -140,6 +217,11 @@ struct dcmipp_bytecap_device { void __iomem *regs; + int pipe_id; + + const struct dcmipp_bytecap_pix_map *pix_map; + unsigned int pix_map_array_size; + u32 cmsr2; struct { @@ -155,6 +237,30 @@ struct dcmipp_bytecap_device { } count; }; +static const struct dcmipp_bytecap_pix_map * +dcmipp_bytecap_pix_map_by_pixelformat(struct dcmipp_bytecap_device *vcap, + u32 pixelformat) +{ + for (unsigned int i = 0; i < vcap->pix_map_array_size; i++) { + if (vcap->pix_map[i].pixelformat == pixelformat) + return &vcap->pix_map[i]; + } + + return NULL; +} + +static bool dcmipp_bytecap_is_format_valid(struct dcmipp_bytecap_device *vcap, + unsigned int pixelformat) +{ + const struct dcmipp_bytecap_pix_map *vpix = + dcmipp_bytecap_pix_map_by_pixelformat(vcap, pixelformat); + + if (!vpix || (vpix->plane_nb > 1 && vcap->pipe_id != 1)) + return false; + + return true; +} + static const struct v4l2_pix_format fmt_default = { .width = DCMIPP_FMT_WIDTH_DEFAULT, .height = DCMIPP_FMT_HEIGHT_DEFAULT, @@ -168,6 +274,73 @@ static const struct v4l2_pix_format fmt_default = { .xfer_func = DCMIPP_XFER_FUNC_DEFAULT, }; +static inline int hdw_pixel_alignment(u32 format) +{ + /* 16 bytes alignment required by hardware */ + switch (format) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_GREY: + return 4;/* 2^4 = 16 pixels = 16 bytes */ + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + return 3;/* 2^3 = 8 pixels = 16 bytes */ + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + return 4;/* 2^4 = 16 pixels = 48 bytes */ + case V4L2_PIX_FMT_ARGB32: + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_RGBA32: + case V4L2_PIX_FMT_BGRA32: + return 2;/* 2^2 = 4 pixels = 16 bytes */ + default: + return 0; + } +} + +static inline int frame_planes(dma_addr_t base_addr, dma_addr_t addrs[], + u32 strides[], u64 sizes[], + u32 width, u32 height, u32 format) +{ + const struct v4l2_format_info *info; + + /* Only used by dump pipe hence addrs[0] is enough */ + if (format == V4L2_PIX_FMT_JPEG) { + addrs[0] = base_addr; + return 0; + } + + info = v4l2_format_info(format); + if (!info) + return -EINVAL; + + /* Fill-in each plane information */ + addrs[0] = base_addr; + strides[0] = width * info->bpp[0]; + sizes[0] = strides[0] * height; + + if (info->comp_planes > 1) { + addrs[1] = addrs[0] + sizes[0]; + strides[1] = width * info->bpp[1] / info->hdiv; + sizes[1] = strides[1] * height / info->vdiv; + } + + if (info->comp_planes > 2) { + addrs[2] = addrs[1] + sizes[1]; + strides[2] = width * info->bpp[2] / info->hdiv; + sizes[2] = strides[2] * height / info->vdiv; + } + + return 0; +} + static int dcmipp_bytecap_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { @@ -192,21 +365,24 @@ static int dcmipp_bytecap_try_fmt_vid_cap(struct file *file, void *priv, { struct dcmipp_bytecap_device *vcap = video_drvdata(file); struct v4l2_pix_format *format = &f->fmt.pix; - const struct dcmipp_bytecap_pix_map *vpix; u32 in_w, in_h; /* Don't accept a pixelformat that is not on the table */ - vpix = dcmipp_bytecap_pix_map_by_pixelformat(format->pixelformat); - if (!vpix) + if (!dcmipp_bytecap_is_format_valid(vcap, format->pixelformat)) format->pixelformat = fmt_default.pixelformat; /* Adjust width & height */ in_w = format->width; in_h = format->height; - v4l_bound_align_image(&format->width, DCMIPP_FRAME_MIN_WIDTH, - DCMIPP_FRAME_MAX_WIDTH, 0, &format->height, - DCMIPP_FRAME_MIN_HEIGHT, DCMIPP_FRAME_MAX_HEIGHT, - 0, 0); + format->width = clamp_t(u32, format->width, DCMIPP_FRAME_MIN_WIDTH, + vcap->pipe_id != 0 ? + DCMIPP_FRAME_MAX_WIDTH : DCMIPP_FRAME_MAX_WIDTH); + if (vcap->pipe_id != 0) + format->width = round_up(format->width, + 1 << hdw_pixel_alignment(format->pixelformat)); + format->height = clamp_t(u32, format->height, DCMIPP_FRAME_MIN_HEIGHT, + vcap->pipe_id != 0 ? + DCMIPP_FRAME_MAX_HEIGHT : DCMIPP_FRAME_MAX_HEIGHT); if (format->width != in_w || format->height != in_h) dev_dbg(vcap->dev, "resolution updated: %dx%d -> %dx%d\n", in_w, in_h, format->width, format->height); @@ -262,7 +438,7 @@ static int dcmipp_bytecap_s_fmt_vid_cap(struct file *file, void *priv, static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - const struct dcmipp_bytecap_pix_map *vpix; + struct dcmipp_bytecap_device *vcap = video_drvdata(file); unsigned int index = f->index; unsigned int i, prev_pixelformat = 0; @@ -271,17 +447,20 @@ static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv, * care of removing duplicated entries (due to support of both * parallel & csi 16 bits formats */ - for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) { - vpix = &dcmipp_bytecap_pix_map_list[i]; + for (i = 0; i < vcap->pix_map_array_size; i++) { + /* Only main pipe supports (Semi)-planar formats */ + if (vcap->pipe_id != 1 && vcap->pix_map[i].plane_nb > 1) + continue; + /* Skip formats not matching requested mbus code */ - if (f->mbus_code && vpix->code != f->mbus_code) + if (f->mbus_code && vcap->pix_map[i].code != f->mbus_code) continue; /* Skip duplicated pixelformat */ - if (vpix->pixelformat == prev_pixelformat) + if (vcap->pix_map[i].pixelformat == prev_pixelformat) continue; - prev_pixelformat = vpix->pixelformat; + prev_pixelformat = vcap->pix_map[i].pixelformat; if (index == 0) break; @@ -289,10 +468,10 @@ static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv, index--; } - if (i == ARRAY_SIZE(dcmipp_bytecap_pix_map_list)) + if (i == vcap->pix_map_array_size) return -EINVAL; - f->pixelformat = vpix->pixelformat; + f->pixelformat = vcap->pix_map[i].pixelformat; return 0; } @@ -300,14 +479,14 @@ static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv, static int dcmipp_bytecap_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *fsize) { - const struct dcmipp_bytecap_pix_map *vpix; + struct dcmipp_bytecap_device *vcap = video_drvdata(file); + if (fsize->index) return -EINVAL; /* Only accept code in the pix map table */ - vpix = dcmipp_bytecap_pix_map_by_pixelformat(fsize->pixel_format); - if (!vpix) + if (!dcmipp_bytecap_is_format_valid(vcap, fsize->pixel_format)) return -EINVAL; fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; @@ -355,14 +534,27 @@ static void dcmipp_start_capture(struct dcmipp_bytecap_device *vcap, struct dcmipp_buf *buf) { /* Set buffer address */ - reg_write(vcap, DCMIPP_P0PPM0AR1, buf->addr); + reg_write(vcap, DCMIPP_PxPPM0AR1(vcap->pipe_id), buf->addrs[0]); - /* Set buffer size */ - reg_write(vcap, DCMIPP_P0DCLMTR, DCMIPP_P0DCLMTR_ENABLE | - ((buf->size / 4) & DCMIPP_P0DCLMTR_LIMIT_MASK)); + if (vcap->pipe_id == 0) { + /* Set buffer size */ + reg_write(vcap, DCMIPP_P0DCLMTR, DCMIPP_P0DCLMTR_ENABLE | + ((buf->size / 4) & DCMIPP_P0DCLMTR_LIMIT_MASK)); + } else { + reg_write(vcap, DCMIPP_PxPPM0PR(vcap->pipe_id), + buf->strides[0]); + + if (buf->addrs[1]) { + reg_write(vcap, DCMIPP_P1PPM1AR1, buf->addrs[1]); + reg_write(vcap, DCMIPP_P1PPM1PR, buf->strides[1]); + } + + if (buf->addrs[2]) + reg_write(vcap, DCMIPP_P1PPM2AR1, buf->addrs[2]); + } /* Capture request */ - reg_set(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ); + reg_set(vcap, DCMIPP_PxFCTCR(vcap->pipe_id), DCMIPP_PxFCTCR_CPTREQ); } static void dcmipp_bytecap_all_buffers_done(struct dcmipp_bytecap_device *vcap, @@ -423,8 +615,25 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, spin_lock_irq(&vcap->irqlock); + if (vcap->pipe_id != 0) { + const struct dcmipp_bytecap_pix_map *vpix = + dcmipp_bytecap_pix_map_by_pixelformat(vcap, vcap->format.pixelformat); + unsigned int ppcr = 0; + + /* + * Configure the Pixel Packer + * vpix is guaranteed to be valid since pixelformat is validated + * in dcmipp_pixelcap_s_fmt_vid_cap function before + */ + ppcr = vpix->ppcr_fmt; + if (vpix->swap_uv) + ppcr |= DCMIPP_PxPPCR_SWAPRB; + + reg_write(vcap, DCMIPP_PxPPCR(vcap->pipe_id), ppcr); + } + /* Enable pipe at the end of programming */ - reg_set(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN); + reg_set(vcap, DCMIPP_PxFSCR(vcap->pipe_id), DCMIPP_PxFSCR_PIPEN); /* * vb2 framework guarantee that we have at least 'min_queued_buffers' @@ -438,7 +647,7 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, /* Enable interruptions */ spin_lock(&vcap->vdev.v4l2_dev->lock); - reg_set(vcap, DCMIPP_CMIER, DCMIPP_CMIER_P0ALL); + reg_set(vcap, DCMIPP_CMIER, DCMIPP_CMIER_PxALL(vcap->pipe_id)); spin_unlock(&vcap->vdev.v4l2_dev->lock); vcap->state = DCMIPP_RUNNING; @@ -469,7 +678,7 @@ static void dcmipp_dump_status(struct dcmipp_bytecap_device *vcap) struct device *dev = vcap->dev; dev_dbg(dev, "[DCMIPP_PRSR] =%#10.8x\n", reg_read(vcap, DCMIPP_PRSR)); - dev_dbg(dev, "[DCMIPP_P0SR] =%#10.8x\n", reg_read(vcap, DCMIPP_P0SR)); + dev_dbg(dev, "[DCMIPP_P0SR] =%#10.8x\n", reg_read(vcap, DCMIPP_PxSR(0))); dev_dbg(dev, "[DCMIPP_P0DCCNTR]=%#10.8x\n", reg_read(vcap, DCMIPP_P0DCCNTR)); dev_dbg(dev, "[DCMIPP_CMSR1] =%#10.8x\n", reg_read(vcap, DCMIPP_CMSR1)); @@ -496,25 +705,26 @@ static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) /* Disable interruptions */ spin_lock(&vcap->vdev.v4l2_dev->lock); - reg_clear(vcap, DCMIPP_CMIER, DCMIPP_CMIER_P0ALL); + reg_clear(vcap, DCMIPP_CMIER, DCMIPP_CMIER_PxALL(vcap->pipe_id)); spin_unlock(&vcap->vdev.v4l2_dev->lock); /* Stop capture */ - reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ); + reg_clear(vcap, DCMIPP_PxFCTCR(vcap->pipe_id), DCMIPP_PxFCTCR_CPTREQ); /* Wait until CPTACT become 0 */ - ret = readl_relaxed_poll_timeout(vcap->regs + DCMIPP_P0SR, status, - !(status & DCMIPP_P0SR_CPTACT), + ret = readl_relaxed_poll_timeout(vcap->regs + DCMIPP_PxSR(vcap->pipe_id), + status, + !(status & DCMIPP_PxSR_CPTACT), 20 * USEC_PER_MSEC, 1000 * USEC_PER_MSEC); if (ret) dev_warn(vcap->dev, "Timeout when stopping\n"); /* Disable pipe */ - reg_clear(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN); + reg_clear(vcap, DCMIPP_PxFSCR(vcap->pipe_id), DCMIPP_PxFSCR_PIPEN); /* Clear any pending interrupts */ - reg_write(vcap, DCMIPP_CMFCR, DCMIPP_CMIER_P0ALL); + reg_write(vcap, DCMIPP_CMFCR, DCMIPP_CMIER_PxALL(vcap->pipe_id)); spin_lock_irq(&vcap->irqlock); @@ -527,7 +737,8 @@ static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) spin_unlock_irq(&vcap->irqlock); - dcmipp_dump_status(vcap); + if (vcap->pipe_id == 0) + dcmipp_dump_status(vcap); pm_runtime_put(vcap->dev); @@ -543,7 +754,9 @@ static int dcmipp_bytecap_buf_prepare(struct vb2_buffer *vb) struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vb->vb2_queue); struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb); + struct v4l2_pix_format *format = &vcap->format; unsigned long size; + int ret; size = vcap->format.sizeimage; @@ -559,6 +772,26 @@ static int dcmipp_bytecap_buf_prepare(struct vb2_buffer *vb) /* Get memory addresses */ buf->addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0); + + ret = frame_planes(buf->addr, + buf->addrs, buf->strides, buf->sizes, + format->width, format->height, + format->pixelformat); + if (ret) { + dev_err(vcap->dev, "%s: Unsupported pixel format (%x)\n", + __func__, format->pixelformat); + return ret; + } + + /* Check for 16 bytes alignment required by hardware */ + WARN_ON(buf->addrs[0] & 15); + if (vcap->pipe_id != 0) { + WARN_ON(buf->strides[0] & 15); + WARN_ON(buf->addrs[1] & 15); + WARN_ON(buf->strides[1] & 15); + WARN_ON(buf->addrs[2] & 15); + } + buf->prepared = true; vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size); @@ -692,7 +925,7 @@ dcmipp_bytecap_set_next_frame_or_stop(struct dcmipp_bytecap_device *vcap) * for next frame). On-going frame capture will continue until * FRAME END but no further capture will be done. */ - reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ); + reg_clear(vcap, DCMIPP_PxFCTCR(vcap->pipe_id), DCMIPP_PxFCTCR_CPTREQ); dev_dbg(vcap->dev, "Capture restart is deferred to next buffer queueing\n"); vcap->next = NULL; @@ -709,7 +942,13 @@ dcmipp_bytecap_set_next_frame_or_stop(struct dcmipp_bytecap_device *vcap) * This register is shadowed and will be taken into * account on next VSYNC (start of next frame) */ - reg_write(vcap, DCMIPP_P0PPM0AR1, vcap->next->addr); + reg_write(vcap, DCMIPP_PxPPM0AR1(vcap->pipe_id), vcap->next->addrs[0]); + if (vcap->pipe_id == 1) { + if (vcap->next->addrs[1]) + reg_write(vcap, DCMIPP_P1PPM1AR1, vcap->next->addrs[1]); + if (vcap->next->addrs[2]) + reg_write(vcap, DCMIPP_P1PPM2AR1, vcap->next->addrs[2]); + } dev_dbg(vcap->dev, "Write [%d] %p phy=%pad\n", vcap->next->vb.vb2_buf.index, vcap->next, &vcap->next->addr); } @@ -745,29 +984,39 @@ static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg) { struct dcmipp_bytecap_device *vcap = container_of(arg, struct dcmipp_bytecap_device, ved); + u32 cmsr2_pxframef; + u32 cmsr2_pxvsyncf; + u32 cmsr2_pxovrf; size_t bytesused = 0; spin_lock_irq(&vcap->irqlock); + cmsr2_pxovrf = DCMIPP_CMSR2_PxOVRF(vcap->pipe_id); + cmsr2_pxvsyncf = DCMIPP_CMSR2_PxVSYNCF(vcap->pipe_id); + cmsr2_pxframef = DCMIPP_CMSR2_PxFRAMEF(vcap->pipe_id); + /* * 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 (vcap->cmsr2 & DCMIPP_CMSR2_P0OVRF) { + if (vcap->cmsr2 & cmsr2_pxovrf) { vcap->count.errors++; vcap->count.overrun++; } - if (vcap->cmsr2 & DCMIPP_CMSR2_P0FRAMEF) { + if (vcap->cmsr2 & cmsr2_pxframef) { vcap->count.frame++; /* Read captured buffer size */ - bytesused = reg_read(vcap, DCMIPP_P0DCCNTR); + if (vcap->pipe_id == 0) + bytesused = reg_read(vcap, DCMIPP_P0DCCNTR); + else + bytesused = vcap->format.sizeimage; dcmipp_bytecap_process_frame(vcap, bytesused); } - if (vcap->cmsr2 & DCMIPP_CMSR2_P0VSYNCF) { + if (vcap->cmsr2 & cmsr2_pxvsyncf) { vcap->count.vsync++; if (vcap->state == DCMIPP_WAIT_FOR_BUFFER) { vcap->count.underrun++; @@ -799,7 +1048,7 @@ static irqreturn_t dcmipp_bytecap_irq_callback(int irq, void *arg) struct dcmipp_ent_device *ved = arg; /* Store interrupt status register */ - vcap->cmsr2 = ved->cmsr2 & DCMIPP_CMIER_P0ALL; + vcap->cmsr2 = ved->cmsr2 & DCMIPP_CMIER_PxALL(vcap->pipe_id); if (!vcap->cmsr2) return IRQ_HANDLED; vcap->count.it++; @@ -822,29 +1071,40 @@ static int dcmipp_bytecap_link_validate(struct media_link *link) .which = V4L2_SUBDEV_FORMAT_ACTIVE, .pad = link->source->index, }; + u32 width_aligned; int ret, i; ret = v4l2_subdev_call(source_sd, pad, get_fmt, NULL, &source_fmt); if (ret < 0) return 0; - if (source_fmt.format.width != vcap->format.width || + width_aligned = source_fmt.format.width; + + /* + * On pixel pipes there can be alignment constraints. + * Depending on the format & pixelpacker constraints, vcap width is + * different from mbus width. Compute expected vcap width based on + * mbus width + */ + if (vcap->pipe_id != 0) + width_aligned = round_up(source_fmt.format.width, + 1 << hdw_pixel_alignment(vcap->format.pixelformat)); + + if (width_aligned != vcap->format.width || source_fmt.format.height != vcap->format.height) { dev_err(vcap->dev, "Wrong width or height %ux%u (%ux%u expected)\n", vcap->format.width, vcap->format.height, - source_fmt.format.width, source_fmt.format.height); + width_aligned, source_fmt.format.height); return -EINVAL; } - for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) { - if (dcmipp_bytecap_pix_map_list[i].pixelformat == - vcap->format.pixelformat && - dcmipp_bytecap_pix_map_list[i].code == - source_fmt.format.code) + for (i = 0; i < vcap->pix_map_array_size; i++) { + if (vcap->pix_map[i].pixelformat == vcap->format.pixelformat && + vcap->pix_map[i].code == source_fmt.format.code) break; } - if (i == ARRAY_SIZE(dcmipp_bytecap_pix_map_list)) { + if (i == vcap->pix_map_array_size) { dev_err(vcap->dev, "mbus code 0x%x do not match capture device format (0x%x)\n", vcap->format.pixelformat, source_fmt.format.code); return -EINVAL; @@ -857,6 +1117,18 @@ static const struct media_entity_operations dcmipp_bytecap_entity_ops = { .link_validate = dcmipp_bytecap_link_validate, }; +static int dcmipp_name_to_pipe_id(const char *name) +{ + if (strstr(name, "dump")) + return 0; + else if (strstr(name, "main")) + return 1; + else if (strstr(name, "aux")) + return 2; + else + return -EINVAL; +} + struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, struct dcmipp_device *dcmipp) { @@ -872,6 +1144,23 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, if (!vcap) return ERR_PTR(-ENOMEM); + /* Retrieve the pipe_id */ + vcap->pipe_id = dcmipp_name_to_pipe_id(entity_name); + if (vcap->pipe_id < 0) { + ret = -EIO; + dev_err(dev, "failed to retrieve pipe_id\n"); + goto err_free_vcap; + } + + /* Initialize supported format table format */ + if (vcap->pipe_id == 0) { + vcap->pix_map = dcmipp_bytecap_dump_pix_map_list; + vcap->pix_map_array_size = ARRAY_SIZE(dcmipp_bytecap_dump_pix_map_list); + } else { + vcap->pix_map = dcmipp_bytecap_pixel_pix_map_list; + vcap->pix_map_array_size = ARRAY_SIZE(dcmipp_bytecap_pixel_pix_map_list); + } + /* Allocate the pads */ vcap->ved.pads = dcmipp_pads_init(1, &pad_flag); if (IS_ERR(vcap->ved.pads)) { From 7898737c9d0f0c38682d8b684a3dcf1222bb81be Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:15 +0200 Subject: [PATCH 151/596] media: stm32: dcmipp: rename bytecap into capture Since the bytecap video driver is now handling all capture (byte & pixel), rename the file and structs *into capture. This is done by: - renaming of dcmipp-bytecap.c into dcmipp-capture.c - replace of dcmipp_bytecap strings into dcmipp_capture Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../platform/st/stm32/stm32-dcmipp/Makefile | 2 +- .../{dcmipp-bytecap.c => dcmipp-capture.c} | 188 +++++++++--------- .../st/stm32/stm32-dcmipp/dcmipp-common.h | 4 +- .../st/stm32/stm32-dcmipp/dcmipp-core.c | 8 +- 4 files changed, 101 insertions(+), 101 deletions(-) rename drivers/media/platform/st/stm32/stm32-dcmipp/{dcmipp-bytecap.c => dcmipp-capture.c} (85%) diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile index 7178934bb116..e35d45a0aca2 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -stm32-dcmipp-y := dcmipp-core.o dcmipp-common.o dcmipp-input.o dcmipp-byteproc.o dcmipp-bytecap.o +stm32-dcmipp-y := dcmipp-core.o dcmipp-common.o dcmipp-input.o dcmipp-byteproc.o dcmipp-capture.o stm32-dcmipp-y += dcmipp-pixelcommon.o dcmipp-isp.o dcmipp-pixelproc.o obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp.o diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-capture.c similarity index 85% rename from drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c rename to drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-capture.c index 4a8976da576a..fe7767f908b7 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-bytecap.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-capture.c @@ -94,7 +94,7 @@ #define DCMIPP_PxPPCR_SWAPRB BIT(4) -struct dcmipp_bytecap_pix_map { +struct dcmipp_capture_pix_map { unsigned int code; u32 pixelformat; u32 plane_nb; @@ -108,7 +108,7 @@ struct dcmipp_bytecap_pix_map { .pixelformat = V4L2_PIX_FMT_##fmt \ } -static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_dump_pix_map_list[] = { +static const struct dcmipp_capture_pix_map dcmipp_capture_dump_pix_map_list[] = { PIXMAP_MBUS_PFMT(RGB565_2X8_LE, RGB565), PIXMAP_MBUS_PFMT(RGB565_1X16, RGB565), PIXMAP_MBUS_PFMT(RGB888_1X24, RGB24), @@ -152,7 +152,7 @@ static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_dump_pix_map_list[] = .swap_uv = swap, \ } -static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pixel_pix_map_list[] = { +static const struct dcmipp_capture_pix_map dcmipp_capture_pixel_pix_map_list[] = { /* Coplanar formats are supported on main & aux pipe */ PIXMAP_MBUS_PIXEL_PFMT(RGB888_1X24, RGB565, 1, DCMIPP_PxPPCR_FORMAT_RGB565, 0), PIXMAP_MBUS_PIXEL_PFMT(YUV8_1X24, YUYV, 1, DCMIPP_PxPPCR_FORMAT_YUYV, 0), @@ -187,7 +187,7 @@ struct dcmipp_buf { struct list_head list; }; -struct dcmipp_bytecap_device { +struct dcmipp_capture_device { struct dcmipp_ent_device ved; struct video_device vdev; struct device *dev; @@ -219,7 +219,7 @@ struct dcmipp_bytecap_device { int pipe_id; - const struct dcmipp_bytecap_pix_map *pix_map; + const struct dcmipp_capture_pix_map *pix_map; unsigned int pix_map_array_size; u32 cmsr2; @@ -237,8 +237,8 @@ struct dcmipp_bytecap_device { } count; }; -static const struct dcmipp_bytecap_pix_map * -dcmipp_bytecap_pix_map_by_pixelformat(struct dcmipp_bytecap_device *vcap, +static const struct dcmipp_capture_pix_map * +dcmipp_capture_pix_map_by_pixelformat(struct dcmipp_capture_device *vcap, u32 pixelformat) { for (unsigned int i = 0; i < vcap->pix_map_array_size; i++) { @@ -249,11 +249,11 @@ dcmipp_bytecap_pix_map_by_pixelformat(struct dcmipp_bytecap_device *vcap, return NULL; } -static bool dcmipp_bytecap_is_format_valid(struct dcmipp_bytecap_device *vcap, +static bool dcmipp_capture_is_format_valid(struct dcmipp_capture_device *vcap, unsigned int pixelformat) { - const struct dcmipp_bytecap_pix_map *vpix = - dcmipp_bytecap_pix_map_by_pixelformat(vcap, pixelformat); + const struct dcmipp_capture_pix_map *vpix = + dcmipp_capture_pix_map_by_pixelformat(vcap, pixelformat); if (!vpix || (vpix->plane_nb > 1 && vcap->pipe_id != 1)) return false; @@ -341,7 +341,7 @@ static inline int frame_planes(dma_addr_t base_addr, dma_addr_t addrs[], return 0; } -static int dcmipp_bytecap_querycap(struct file *file, void *priv, +static int dcmipp_capture_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { strscpy(cap->driver, DCMIPP_PDEV_NAME, sizeof(cap->driver)); @@ -350,25 +350,25 @@ static int dcmipp_bytecap_querycap(struct file *file, void *priv, return 0; } -static int dcmipp_bytecap_g_fmt_vid_cap(struct file *file, void *priv, +static int dcmipp_capture_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct dcmipp_bytecap_device *vcap = video_drvdata(file); + struct dcmipp_capture_device *vcap = video_drvdata(file); f->fmt.pix = vcap->format; return 0; } -static int dcmipp_bytecap_try_fmt_vid_cap(struct file *file, void *priv, +static int dcmipp_capture_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct dcmipp_bytecap_device *vcap = video_drvdata(file); + struct dcmipp_capture_device *vcap = video_drvdata(file); struct v4l2_pix_format *format = &f->fmt.pix; u32 in_w, in_h; /* Don't accept a pixelformat that is not on the table */ - if (!dcmipp_bytecap_is_format_valid(vcap, format->pixelformat)) + if (!dcmipp_capture_is_format_valid(vcap, format->pixelformat)) format->pixelformat = fmt_default.pixelformat; /* Adjust width & height */ @@ -403,17 +403,17 @@ static int dcmipp_bytecap_try_fmt_vid_cap(struct file *file, void *priv, return 0; } -static int dcmipp_bytecap_s_fmt_vid_cap(struct file *file, void *priv, +static int dcmipp_capture_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { - struct dcmipp_bytecap_device *vcap = video_drvdata(file); + struct dcmipp_capture_device *vcap = video_drvdata(file); int ret; /* Do not change the format while stream is on */ if (vb2_is_busy(&vcap->queue)) return -EBUSY; - ret = dcmipp_bytecap_try_fmt_vid_cap(file, priv, f); + ret = dcmipp_capture_try_fmt_vid_cap(file, priv, f); if (ret) return ret; @@ -435,10 +435,10 @@ static int dcmipp_bytecap_s_fmt_vid_cap(struct file *file, void *priv, return 0; } -static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv, +static int dcmipp_capture_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - struct dcmipp_bytecap_device *vcap = video_drvdata(file); + struct dcmipp_capture_device *vcap = video_drvdata(file); unsigned int index = f->index; unsigned int i, prev_pixelformat = 0; @@ -476,17 +476,17 @@ static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv, return 0; } -static int dcmipp_bytecap_enum_framesizes(struct file *file, void *fh, +static int dcmipp_capture_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *fsize) { - struct dcmipp_bytecap_device *vcap = video_drvdata(file); + struct dcmipp_capture_device *vcap = video_drvdata(file); if (fsize->index) return -EINVAL; /* Only accept code in the pix map table */ - if (!dcmipp_bytecap_is_format_valid(vcap, fsize->pixel_format)) + if (!dcmipp_capture_is_format_valid(vcap, fsize->pixel_format)) return -EINVAL; fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; @@ -500,7 +500,7 @@ static int dcmipp_bytecap_enum_framesizes(struct file *file, void *fh, return 0; } -static const struct v4l2_file_operations dcmipp_bytecap_fops = { +static const struct v4l2_file_operations dcmipp_capture_fops = { .owner = THIS_MODULE, .open = v4l2_fh_open, .release = vb2_fop_release, @@ -510,14 +510,14 @@ static const struct v4l2_file_operations dcmipp_bytecap_fops = { .mmap = vb2_fop_mmap, }; -static const struct v4l2_ioctl_ops dcmipp_bytecap_ioctl_ops = { - .vidioc_querycap = dcmipp_bytecap_querycap, +static const struct v4l2_ioctl_ops dcmipp_capture_ioctl_ops = { + .vidioc_querycap = dcmipp_capture_querycap, - .vidioc_g_fmt_vid_cap = dcmipp_bytecap_g_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = dcmipp_bytecap_s_fmt_vid_cap, - .vidioc_try_fmt_vid_cap = dcmipp_bytecap_try_fmt_vid_cap, - .vidioc_enum_fmt_vid_cap = dcmipp_bytecap_enum_fmt_vid_cap, - .vidioc_enum_framesizes = dcmipp_bytecap_enum_framesizes, + .vidioc_g_fmt_vid_cap = dcmipp_capture_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = dcmipp_capture_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = dcmipp_capture_try_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = dcmipp_capture_enum_fmt_vid_cap, + .vidioc_enum_framesizes = dcmipp_capture_enum_framesizes, .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_create_bufs = vb2_ioctl_create_bufs, @@ -530,7 +530,7 @@ static const struct v4l2_ioctl_ops dcmipp_bytecap_ioctl_ops = { .vidioc_streamoff = vb2_ioctl_streamoff, }; -static void dcmipp_start_capture(struct dcmipp_bytecap_device *vcap, +static void dcmipp_start_capture(struct dcmipp_capture_device *vcap, struct dcmipp_buf *buf) { /* Set buffer address */ @@ -557,7 +557,7 @@ static void dcmipp_start_capture(struct dcmipp_bytecap_device *vcap, reg_set(vcap, DCMIPP_PxFCTCR(vcap->pipe_id), DCMIPP_PxFCTCR_CPTREQ); } -static void dcmipp_bytecap_all_buffers_done(struct dcmipp_bytecap_device *vcap, +static void dcmipp_capture_all_buffers_done(struct dcmipp_capture_device *vcap, enum vb2_buffer_state state) { struct dcmipp_buf *buf, *node; @@ -568,10 +568,10 @@ static void dcmipp_bytecap_all_buffers_done(struct dcmipp_bytecap_device *vcap, } } -static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, +static int dcmipp_capture_start_streaming(struct vb2_queue *vq, unsigned int count) { - struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq); + struct dcmipp_capture_device *vcap = vb2_get_drv_priv(vq); struct media_entity *entity = &vcap->vdev.entity; struct dcmipp_buf *buf; struct media_pad *pad; @@ -582,7 +582,7 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, /* * Get source subdev - since link is IMMUTABLE, pointer is cached - * within the dcmipp_bytecap_device structure + * within the dcmipp_capture_device structure */ if (!vcap->s_subdev) { pad = media_pad_remote_pad_first(&vcap->vdev.entity.pads[0]); @@ -616,8 +616,8 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, spin_lock_irq(&vcap->irqlock); if (vcap->pipe_id != 0) { - const struct dcmipp_bytecap_pix_map *vpix = - dcmipp_bytecap_pix_map_by_pixelformat(vcap, vcap->format.pixelformat); + const struct dcmipp_capture_pix_map *vpix = + dcmipp_capture_pix_map_by_pixelformat(vcap, vcap->format.pixelformat); unsigned int ppcr = 0; /* @@ -666,14 +666,14 @@ static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq, * Return all buffers to vb2 in QUEUED state. * This will give ownership back to userspace */ - dcmipp_bytecap_all_buffers_done(vcap, VB2_BUF_STATE_QUEUED); + dcmipp_capture_all_buffers_done(vcap, VB2_BUF_STATE_QUEUED); vcap->active = NULL; spin_unlock_irq(&vcap->irqlock); return ret; } -static void dcmipp_dump_status(struct dcmipp_bytecap_device *vcap) +static void dcmipp_dump_status(struct dcmipp_capture_device *vcap) { struct device *dev = vcap->dev; @@ -689,9 +689,9 @@ static void dcmipp_dump_status(struct dcmipp_bytecap_device *vcap) * Stop the stream engine. Any remaining buffers in the stream queue are * dequeued and passed on to the vb2 framework marked as STATE_ERROR. */ -static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) +static void dcmipp_capture_stop_streaming(struct vb2_queue *vq) { - struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq); + struct dcmipp_capture_device *vcap = vb2_get_drv_priv(vq); int ret; u32 status; @@ -729,7 +729,7 @@ static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) spin_lock_irq(&vcap->irqlock); /* Return all queued buffers to vb2 in ERROR state */ - dcmipp_bytecap_all_buffers_done(vcap, VB2_BUF_STATE_ERROR); + dcmipp_capture_all_buffers_done(vcap, VB2_BUF_STATE_ERROR); INIT_LIST_HEAD(&vcap->buffers); vcap->active = NULL; @@ -749,9 +749,9 @@ static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq) vcap->count.underrun, vcap->count.buffers); } -static int dcmipp_bytecap_buf_prepare(struct vb2_buffer *vb) +static int dcmipp_capture_buf_prepare(struct vb2_buffer *vb) { - struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vb->vb2_queue); + struct dcmipp_capture_device *vcap = vb2_get_drv_priv(vb->vb2_queue); struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb); struct v4l2_pix_format *format = &vcap->format; @@ -803,9 +803,9 @@ static int dcmipp_bytecap_buf_prepare(struct vb2_buffer *vb) return 0; } -static void dcmipp_bytecap_buf_queue(struct vb2_buffer *vb2_buf) +static void dcmipp_capture_buf_queue(struct vb2_buffer *vb2_buf) { - struct dcmipp_bytecap_device *vcap = + struct dcmipp_capture_device *vcap = vb2_get_drv_priv(vb2_buf->vb2_queue); struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2_buf); struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb); @@ -829,13 +829,13 @@ static void dcmipp_bytecap_buf_queue(struct vb2_buffer *vb2_buf) spin_unlock_irq(&vcap->irqlock); } -static int dcmipp_bytecap_queue_setup(struct vb2_queue *vq, +static int dcmipp_capture_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[]) { - struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq); + struct dcmipp_capture_device *vcap = vb2_get_drv_priv(vq); unsigned int size; size = vcap->format.sizeimage; @@ -853,7 +853,7 @@ static int dcmipp_bytecap_queue_setup(struct vb2_queue *vq, return 0; } -static int dcmipp_bytecap_buf_init(struct vb2_buffer *vb) +static int dcmipp_capture_buf_init(struct vb2_buffer *vb) { struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb); @@ -863,19 +863,19 @@ static int dcmipp_bytecap_buf_init(struct vb2_buffer *vb) return 0; } -static const struct vb2_ops dcmipp_bytecap_qops = { - .start_streaming = dcmipp_bytecap_start_streaming, - .stop_streaming = dcmipp_bytecap_stop_streaming, - .buf_init = dcmipp_bytecap_buf_init, - .buf_prepare = dcmipp_bytecap_buf_prepare, - .buf_queue = dcmipp_bytecap_buf_queue, - .queue_setup = dcmipp_bytecap_queue_setup, +static const struct vb2_ops dcmipp_capture_qops = { + .start_streaming = dcmipp_capture_start_streaming, + .stop_streaming = dcmipp_capture_stop_streaming, + .buf_init = dcmipp_capture_buf_init, + .buf_prepare = dcmipp_capture_buf_prepare, + .buf_queue = dcmipp_capture_buf_queue, + .queue_setup = dcmipp_capture_queue_setup, }; -static void dcmipp_bytecap_release(struct video_device *vdev) +static void dcmipp_capture_release(struct video_device *vdev) { - struct dcmipp_bytecap_device *vcap = - container_of(vdev, struct dcmipp_bytecap_device, vdev); + struct dcmipp_capture_device *vcap = + container_of(vdev, struct dcmipp_capture_device, vdev); dcmipp_pads_cleanup(vcap->ved.pads); mutex_destroy(&vcap->lock); @@ -883,16 +883,16 @@ static void dcmipp_bytecap_release(struct video_device *vdev) kfree(vcap); } -void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved) +void dcmipp_capture_ent_release(struct dcmipp_ent_device *ved) { - struct dcmipp_bytecap_device *vcap = - container_of(ved, struct dcmipp_bytecap_device, ved); + struct dcmipp_capture_device *vcap = + container_of(ved, struct dcmipp_capture_device, ved); media_entity_cleanup(ved->ent); vb2_video_unregister_device(&vcap->vdev); } -static void dcmipp_buffer_done(struct dcmipp_bytecap_device *vcap, +static void dcmipp_buffer_done(struct dcmipp_capture_device *vcap, struct dcmipp_buf *buf, size_t bytesused, int err) @@ -916,7 +916,7 @@ static void dcmipp_buffer_done(struct dcmipp_bytecap_device *vcap, /* irqlock must be held */ static void -dcmipp_bytecap_set_next_frame_or_stop(struct dcmipp_bytecap_device *vcap) +dcmipp_capture_set_next_frame_or_stop(struct dcmipp_capture_device *vcap) { if (!vcap->next && list_is_singular(&vcap->buffers)) { /* @@ -954,7 +954,7 @@ dcmipp_bytecap_set_next_frame_or_stop(struct dcmipp_bytecap_device *vcap) } /* irqlock must be held */ -static void dcmipp_bytecap_process_frame(struct dcmipp_bytecap_device *vcap, +static void dcmipp_capture_process_frame(struct dcmipp_capture_device *vcap, size_t bytesused) { int err = 0; @@ -980,10 +980,10 @@ static void dcmipp_bytecap_process_frame(struct dcmipp_bytecap_device *vcap, vcap->active = NULL; } -static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg) +static irqreturn_t dcmipp_capture_irq_thread(int irq, void *arg) { - struct dcmipp_bytecap_device *vcap = - container_of(arg, struct dcmipp_bytecap_device, ved); + struct dcmipp_capture_device *vcap = + container_of(arg, struct dcmipp_capture_device, ved); u32 cmsr2_pxframef; u32 cmsr2_pxvsyncf; u32 cmsr2_pxovrf; @@ -1013,7 +1013,7 @@ static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg) bytesused = reg_read(vcap, DCMIPP_P0DCCNTR); else bytesused = vcap->format.sizeimage; - dcmipp_bytecap_process_frame(vcap, bytesused); + dcmipp_capture_process_frame(vcap, bytesused); } if (vcap->cmsr2 & cmsr2_pxvsyncf) { @@ -1033,7 +1033,7 @@ static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg) * active (but not used) buffer and put it back into next. */ swap(vcap->active, vcap->next); - dcmipp_bytecap_set_next_frame_or_stop(vcap); + dcmipp_capture_set_next_frame_or_stop(vcap); } out: @@ -1041,10 +1041,10 @@ static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg) return IRQ_HANDLED; } -static irqreturn_t dcmipp_bytecap_irq_callback(int irq, void *arg) +static irqreturn_t dcmipp_capture_irq_callback(int irq, void *arg) { - struct dcmipp_bytecap_device *vcap = - container_of(arg, struct dcmipp_bytecap_device, ved); + struct dcmipp_capture_device *vcap = + container_of(arg, struct dcmipp_capture_device, ved); struct dcmipp_ent_device *ved = arg; /* Store interrupt status register */ @@ -1059,12 +1059,12 @@ static irqreturn_t dcmipp_bytecap_irq_callback(int irq, void *arg) return IRQ_WAKE_THREAD; } -static int dcmipp_bytecap_link_validate(struct media_link *link) +static int dcmipp_capture_link_validate(struct media_link *link) { struct media_entity *entity = link->sink->entity; struct video_device *vd = media_entity_to_video_device(entity); - struct dcmipp_bytecap_device *vcap = container_of(vd, - struct dcmipp_bytecap_device, vdev); + struct dcmipp_capture_device *vcap = container_of(vd, + struct dcmipp_capture_device, vdev); struct v4l2_subdev *source_sd = media_entity_to_v4l2_subdev(link->source->entity); struct v4l2_subdev_format source_fmt = { @@ -1113,8 +1113,8 @@ static int dcmipp_bytecap_link_validate(struct media_link *link) return 0; } -static const struct media_entity_operations dcmipp_bytecap_entity_ops = { - .link_validate = dcmipp_bytecap_link_validate, +static const struct media_entity_operations dcmipp_capture_entity_ops = { + .link_validate = dcmipp_capture_link_validate, }; static int dcmipp_name_to_pipe_id(const char *name) @@ -1129,17 +1129,17 @@ static int dcmipp_name_to_pipe_id(const char *name) return -EINVAL; } -struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, +struct dcmipp_ent_device *dcmipp_capture_ent_init(const char *entity_name, struct dcmipp_device *dcmipp) { - struct dcmipp_bytecap_device *vcap; + struct dcmipp_capture_device *vcap; struct device *dev = dcmipp->dev; struct video_device *vdev; struct vb2_queue *q; const unsigned long pad_flag = MEDIA_PAD_FL_SINK; int ret = 0; - /* Allocate the dcmipp_bytecap_device struct */ + /* Allocate the dcmipp_capture_device struct */ vcap = kzalloc_obj(*vcap); if (!vcap) return ERR_PTR(-ENOMEM); @@ -1154,11 +1154,11 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, /* Initialize supported format table format */ if (vcap->pipe_id == 0) { - vcap->pix_map = dcmipp_bytecap_dump_pix_map_list; - vcap->pix_map_array_size = ARRAY_SIZE(dcmipp_bytecap_dump_pix_map_list); + vcap->pix_map = dcmipp_capture_dump_pix_map_list; + vcap->pix_map_array_size = ARRAY_SIZE(dcmipp_capture_dump_pix_map_list); } else { - vcap->pix_map = dcmipp_bytecap_pixel_pix_map_list; - vcap->pix_map_array_size = ARRAY_SIZE(dcmipp_bytecap_pixel_pix_map_list); + vcap->pix_map = dcmipp_capture_pixel_pix_map_list; + vcap->pix_map_array_size = ARRAY_SIZE(dcmipp_capture_pixel_pix_map_list); } /* Allocate the pads */ @@ -1173,7 +1173,7 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, /* Initialize the media entity */ vcap->vdev.entity.name = entity_name; vcap->vdev.entity.function = MEDIA_ENT_F_IO_V4L; - vcap->vdev.entity.ops = &dcmipp_bytecap_entity_ops; + vcap->vdev.entity.ops = &dcmipp_capture_entity_ops; ret = media_entity_pads_init(&vcap->vdev.entity, 1, vcap->ved.pads); if (ret) goto err_clean_pads; @@ -1188,7 +1188,7 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, q->lock = &vcap->lock; q->drv_priv = vcap; q->buf_struct_size = sizeof(struct dcmipp_buf); - q->ops = &dcmipp_bytecap_qops; + q->ops = &dcmipp_capture_qops; q->mem_ops = &vb2_dma_contig_memops; q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->min_queued_buffers = 1; @@ -1217,8 +1217,8 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, /* Fill the dcmipp_ent_device struct */ vcap->ved.ent = &vcap->vdev.entity; - vcap->ved.handler = dcmipp_bytecap_irq_callback; - vcap->ved.thread_fn = dcmipp_bytecap_irq_thread; + vcap->ved.handler = dcmipp_capture_irq_callback; + vcap->ved.thread_fn = dcmipp_capture_irq_thread; vcap->dev = dev; vcap->regs = dcmipp->regs; @@ -1226,9 +1226,9 @@ struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, vdev = &vcap->vdev; vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; - vdev->release = dcmipp_bytecap_release; - vdev->fops = &dcmipp_bytecap_fops; - vdev->ioctl_ops = &dcmipp_bytecap_ioctl_ops; + vdev->release = dcmipp_capture_release; + vdev->fops = &dcmipp_capture_fops; + vdev->ioctl_ops = &dcmipp_capture_ioctl_ops; vdev->lock = &vcap->lock; vdev->queue = q; vdev->v4l2_dev = &dcmipp->v4l2_dev; diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h index 68766145d543..007912c3a7f8 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-common.h @@ -280,9 +280,9 @@ struct dcmipp_ent_device * dcmipp_byteproc_ent_init(const char *entity_name, struct dcmipp_device *dcmipp); void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved); -struct dcmipp_ent_device *dcmipp_bytecap_ent_init(const char *entity_name, +struct dcmipp_ent_device *dcmipp_capture_ent_init(const char *entity_name, struct dcmipp_device *dcmipp); -void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved); +void dcmipp_capture_ent_release(struct dcmipp_ent_device *ved); struct dcmipp_ent_device *dcmipp_isp_ent_init(const char *entity_name, struct dcmipp_device *dcmipp); void dcmipp_isp_ent_release(struct dcmipp_ent_device *ved); 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 a4dc9da70cd6..6abbf0f023ee 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c @@ -50,8 +50,8 @@ static const struct dcmipp_ent_config stm32mp13_ent_config[] = { }, { .name = "dcmipp_dump_capture", - .init = dcmipp_bytecap_ent_init, - .release = dcmipp_bytecap_ent_release, + .init = dcmipp_capture_ent_init, + .release = dcmipp_capture_ent_release, }, }; @@ -88,8 +88,8 @@ static const struct dcmipp_ent_config stm32mp25_ent_config[] = { }, { .name = "dcmipp_dump_capture", - .init = dcmipp_bytecap_ent_init, - .release = dcmipp_bytecap_ent_release, + .init = dcmipp_capture_ent_init, + .release = dcmipp_capture_ent_release, }, }; From b8eaee70a455fe980e1161178605404090caa75d Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:16 +0200 Subject: [PATCH 152/596] media: stm32: dcmipp: instantiate & link stm32mp25 subdevs Add topology of the two pixel pipes (main & aux) of the stm32mp25. Do not make the link from dcmipp_input immutable and enabled by default since not all pipes are always used together so when a pipeline is not being used its link should be disconnected to allow proper pipeline check. Not doing this would most probably lead to pipeline start failure due to incompatible pads configurations on the unused pipe. Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- .../st/stm32/stm32-dcmipp/dcmipp-core.c | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) 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 6abbf0f023ee..54e28b4a5b2c 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-core.c @@ -75,6 +75,11 @@ static const struct dcmipp_pipeline_config stm32mp13_pipe_cfg = { .hw_revision = DCMIPP_STM32MP13_VERR }; +#define ID_MAIN_ISP 3 +#define ID_MAIN_POSTPROC 4 +#define ID_MAIN_CAPTURE 5 +#define ID_AUX_POSTPROC 6 +#define ID_AUX_CAPTURE 7 static const struct dcmipp_ent_config stm32mp25_ent_config[] = { { .name = "dcmipp_input", @@ -91,13 +96,46 @@ static const struct dcmipp_ent_config stm32mp25_ent_config[] = { .init = dcmipp_capture_ent_init, .release = dcmipp_capture_ent_release, }, + { + .name = "dcmipp_main_isp", + .init = dcmipp_isp_ent_init, + .release = dcmipp_isp_ent_release, + }, + { + .name = "dcmipp_main_postproc", + .init = dcmipp_pixelproc_ent_init, + .release = dcmipp_pixelproc_ent_release, + }, + { + .name = "dcmipp_main_capture", + .init = dcmipp_capture_ent_init, + .release = dcmipp_capture_ent_release, + }, + { + .name = "dcmipp_aux_postproc", + .init = dcmipp_pixelproc_ent_init, + .release = dcmipp_pixelproc_ent_release, + }, + { + .name = "dcmipp_aux_capture", + .init = dcmipp_capture_ent_init, + .release = dcmipp_capture_ent_release, + }, }; static const struct dcmipp_ent_link stm32mp25_ent_links[] = { - DCMIPP_ENT_LINK(ID_INPUT, 1, ID_DUMP_BYTEPROC, 0, - MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), + DCMIPP_ENT_LINK(ID_INPUT, 1, ID_DUMP_BYTEPROC, 0, MEDIA_LNK_FL_ENABLED), DCMIPP_ENT_LINK(ID_DUMP_BYTEPROC, 1, ID_DUMP_CAPTURE, 0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), + DCMIPP_ENT_LINK(ID_INPUT, 2, ID_MAIN_ISP, 0, 0), + DCMIPP_ENT_LINK(ID_MAIN_ISP, 1, ID_MAIN_POSTPROC, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), + DCMIPP_ENT_LINK(ID_MAIN_ISP, 2, ID_AUX_POSTPROC, 0, 0), + DCMIPP_ENT_LINK(ID_MAIN_POSTPROC, 1, ID_MAIN_CAPTURE, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), + DCMIPP_ENT_LINK(ID_INPUT, 3, ID_AUX_POSTPROC, 0, 0), + DCMIPP_ENT_LINK(ID_AUX_POSTPROC, 1, ID_AUX_CAPTURE, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE), }; #define DCMIPP_STM32MP25_VERR 0x30 From 5540b48c0702f6a1389db3d473d7fd0db930422a Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 17 Aug 2026 12:21:58 +0530 Subject: [PATCH 153/596] media: microchip-isc: fix awb_mutex and lock lifecycle isc->lock and awb_mutex were initialised in isc_async_complete() and destroyed in isc_async_unbind(), tying their lifetime to the subdev bind/unbind cycle instead of the device: - isc_async_unbind() destroyed awb_mutex before cancel_work_sync(), which takes it; - a failed .complete() left them initialised, and isc->lock was destroyed only on the .complete() error path, so the normal unbind path leaked it; - a rebind runs .complete() again and reinitialises a live mutex. Initialise both with devm_mutex_init() at probe so they live for the whole device lifetime and are freed at remove, and drop the init and destroy from the .complete()/.unbind() callbacks. isc_async_complete_err then only returned ret, so drop the label and return directly at each error site. Fixes: 314c96e5203d ("media: atmel: atmel-isc-base: use mutex to lock awb workq from streaming") Cc: stable@vger.kernel.org Reviewed-by: Eugen Hristev Signed-off-by: Balakrishnan Sambath Signed-off-by: Hans Verkuil --- .../media/platform/microchip/microchip-isc-base.c | 15 ++++----------- .../platform/microchip/microchip-sama5d2-isc.c | 9 +++++++++ .../platform/microchip/microchip-sama7g5-isc.c | 9 +++++++++ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index a7cdc743fda7..eebbcb28a7ee 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1703,7 +1703,6 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier, { struct isc_device *isc = container_of(notifier->v4l2_dev, struct isc_device, v4l2_dev); - mutex_destroy(&isc->awb_mutex); cancel_work_sync(&isc->awb_work); video_unregister_device(&isc->video_dev); v4l2_ctrl_handler_free(&isc->ctrls.handler); @@ -1767,8 +1766,6 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) isc->current_subdev = container_of(notifier, struct isc_subdev_entity, notifier); - mutex_init(&isc->lock); - mutex_init(&isc->awb_mutex); init_completion(&isc->comp); @@ -1787,7 +1784,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) ret = vb2_queue_init(q); if (ret < 0) { dev_err(isc->dev, "vb2_queue_init() failed: %d\n", ret); - goto isc_async_complete_err; + return ret; } /* Init video dma queues */ @@ -1798,13 +1795,13 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) ret = isc_set_default_fmt(isc); if (ret) { dev_err(isc->dev, "Could not set default format\n"); - goto isc_async_complete_err; + return ret; } ret = isc_ctrl_init(isc); if (ret) { dev_err(isc->dev, "Init isc ctrols failed: %d\n", ret); - goto isc_async_complete_err; + return ret; } /* Register video device */ @@ -1824,7 +1821,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret < 0) { dev_err(isc->dev, "video_register_device failed: %d\n", ret); - goto isc_async_complete_err; + return ret; } ret = isc_scaler_link(isc); @@ -1839,10 +1836,6 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier) isc_async_complete_unregister_device: video_unregister_device(vdev); - -isc_async_complete_err: - mutex_destroy(&isc->awb_mutex); - mutex_destroy(&isc->lock); return ret; } diff --git a/drivers/media/platform/microchip/microchip-sama5d2-isc.c b/drivers/media/platform/microchip/microchip-sama5d2-isc.c index 66d3d7891991..25d241b4c66a 100644 --- a/drivers/media/platform/microchip/microchip-sama5d2-isc.c +++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -417,6 +418,14 @@ static int microchip_isc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, isc); isc->dev = dev; + ret = devm_mutex_init(dev, &isc->lock); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &isc->awb_mutex); + if (ret) + return ret; + io_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(io_base)) return PTR_ERR(io_base); diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index 7383341ec51d..28d975002476 100644 --- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -406,6 +407,14 @@ static int microchip_xisc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, isc); isc->dev = dev; + ret = devm_mutex_init(dev, &isc->lock); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &isc->awb_mutex); + if (ret) + return ret; + io_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(io_base)) return PTR_ERR(io_base); From f2eb2fd388bf3f1959556a326e797cacca341c86 Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 17 Aug 2026 12:21:59 +0530 Subject: [PATCH 154/596] media: microchip-isc: take a reference on the parsed endpoints for_each_endpoint_of_node() drops the reference on the current node as it advances. xisc_parse_dt() and isc_parse_dt() store the node in subdev_entity->epn and release it later with of_node_put(), but never took their own reference, so the stored pointer refers to an already-released node. This underflows the refcount and can use-after-free, reachable through the camera device tree overlay. Take a reference with of_node_get() when storing the node, and drop it in microchip_isc_subdev_cleanup() so the entities the bind loop never reaches on an early exit do not leak it. Fixes: c9aa973884a1 ("media: atmel: atmel-isc: add microchip-xisc driver") Fixes: d6701f13bd07 ("media: atmel: Use v4l2_async_notifier_add_fwnode_remote_subdev") Cc: stable@vger.kernel.org Signed-off-by: Balakrishnan Sambath Reviewed-by: Eugen Hristev Signed-off-by: Hans Verkuil --- .../platform/microchip/microchip-isc-base.c | 6 ++++++ .../platform/microchip/microchip-sama5d2-isc.c | 17 ++++++++++++----- .../platform/microchip/microchip-sama7g5-isc.c | 17 ++++++++++++----- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index eebbcb28a7ee..ca4f3b5f58aa 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1853,6 +1853,12 @@ void microchip_isc_subdev_cleanup(struct isc_device *isc) list_for_each_entry(subdev_entity, &isc->subdev_entities, list) { v4l2_async_nf_unregister(&subdev_entity->notifier); v4l2_async_nf_cleanup(&subdev_entity->notifier); + /* + * Release the endpoint reference taken while parsing. It is + * NULL for entities the bind loop already consumed, so this + * only drops the ones left over on an early exit. + */ + of_node_put(subdev_entity->epn); } INIT_LIST_HEAD(&isc->subdev_entities); diff --git a/drivers/media/platform/microchip/microchip-sama5d2-isc.c b/drivers/media/platform/microchip/microchip-sama5d2-isc.c index 25d241b4c66a..18e1ec0b94fa 100644 --- a/drivers/media/platform/microchip/microchip-sama5d2-isc.c +++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c @@ -357,28 +357,30 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) struct device_node *epn; struct isc_subdev_entity *subdev_entity; unsigned int flags; + int ret; INIT_LIST_HEAD(&isc->subdev_entities); for_each_endpoint_of_node(np, epn) { struct v4l2_fwnode_endpoint v4l2_epn = { .bus_type = 0 }; - int ret; ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(epn), &v4l2_epn); if (ret) { - of_node_put(epn); dev_err(dev, "Could not parse the endpoint\n"); - return -EINVAL; + of_node_put(epn); + ret = -EINVAL; + goto err_cleanup; } subdev_entity = devm_kzalloc(dev, sizeof(*subdev_entity), GFP_KERNEL); if (!subdev_entity) { of_node_put(epn); - return -ENOMEM; + ret = -ENOMEM; + goto err_cleanup; } - subdev_entity->epn = epn; + subdev_entity->epn = of_node_get(epn); flags = v4l2_epn.bus.parallel.flags; @@ -399,6 +401,11 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) } return 0; + +err_cleanup: + list_for_each_entry(subdev_entity, &isc->subdev_entities, list) + of_node_put(subdev_entity->epn); + return ret; } static int microchip_isc_probe(struct platform_device *pdev) diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index 28d975002476..07cfe1109ff0 100644 --- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -341,6 +341,7 @@ static int xisc_parse_dt(struct device *dev, struct isc_device *isc) struct isc_subdev_entity *subdev_entity; unsigned int flags; bool mipi_mode; + int ret; INIT_LIST_HEAD(&isc->subdev_entities); @@ -348,23 +349,24 @@ static int xisc_parse_dt(struct device *dev, struct isc_device *isc) for_each_endpoint_of_node(np, epn) { struct v4l2_fwnode_endpoint v4l2_epn = { .bus_type = 0 }; - int ret; ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(epn), &v4l2_epn); if (ret) { - of_node_put(epn); dev_err(dev, "Could not parse the endpoint\n"); - return -EINVAL; + of_node_put(epn); + ret = -EINVAL; + goto err_cleanup; } subdev_entity = devm_kzalloc(dev, sizeof(*subdev_entity), GFP_KERNEL); if (!subdev_entity) { of_node_put(epn); - return -ENOMEM; + ret = -ENOMEM; + goto err_cleanup; } - subdev_entity->epn = epn; + subdev_entity->epn = of_node_get(epn); flags = v4l2_epn.bus.parallel.flags; @@ -388,6 +390,11 @@ static int xisc_parse_dt(struct device *dev, struct isc_device *isc) } return 0; + +err_cleanup: + list_for_each_entry(subdev_entity, &isc->subdev_entities, list) + of_node_put(subdev_entity->epn); + return ret; } static int microchip_xisc_probe(struct platform_device *pdev) From 19fa5407da5ef3e605cacfcd90f70d8b2c121cd7 Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 17 Aug 2026 12:22:00 +0530 Subject: [PATCH 155/596] media: microchip-isc: synchronize the IRQ before disabling clocks on stop isc_stop_streaming() masks the DMA interrupt and then drops the runtime PM reference, which disables the ISC clocks. microchip_isc_interrupt() may still be executing on another CPU at that point; it reads ISC_INTSR over regmap, and touching the unclocked registers triggers an external abort. Store the IRQ number at probe and call synchronize_irq() after masking the interrupt, before dropping the PM reference. Fixes: 106267444f12 ("[media] atmel-isc: add the Image Sensor Controller code") Cc: stable@vger.kernel.org Signed-off-by: Balakrishnan Sambath Reviewed-by: Eugen Hristev Signed-off-by: Hans Verkuil --- drivers/media/platform/microchip/microchip-isc-base.c | 3 +++ drivers/media/platform/microchip/microchip-isc.h | 1 + drivers/media/platform/microchip/microchip-sama5d2-isc.c | 2 ++ drivers/media/platform/microchip/microchip-sama7g5-isc.c | 2 ++ 4 files changed, 8 insertions(+) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index ca4f3b5f58aa..debbc38717de 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -425,6 +425,9 @@ static void isc_stop_streaming(struct vb2_queue *vq) /* Disable DMA interrupt */ regmap_write(isc->regmap, ISC_INTDIS, ISC_INT_DDONE); + /* let a running IRQ handler finish before the clock is disabled */ + synchronize_irq(isc->irq); + pm_runtime_put_sync(isc->dev); /* Disable stream on the sub device */ diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h index ad4e98a1dd8f..f5e322c2e36b 100644 --- a/drivers/media/platform/microchip/microchip-isc.h +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -287,6 +287,7 @@ struct isc_device { u32 dcfg; struct device *dev; + int irq; struct v4l2_device v4l2_dev; struct video_device video_dev; diff --git a/drivers/media/platform/microchip/microchip-sama5d2-isc.c b/drivers/media/platform/microchip/microchip-sama5d2-isc.c index 18e1ec0b94fa..5bd109e93a8f 100644 --- a/drivers/media/platform/microchip/microchip-sama5d2-isc.c +++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c @@ -448,6 +448,8 @@ static int microchip_isc_probe(struct platform_device *pdev) if (irq < 0) return irq; + isc->irq = irq; + ret = devm_request_irq(dev, irq, microchip_isc_interrupt, 0, "microchip-sama5d2-isc", isc); if (ret < 0) { diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index 07cfe1109ff0..d978ce448a4d 100644 --- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -437,6 +437,8 @@ static int microchip_xisc_probe(struct platform_device *pdev) if (irq < 0) return irq; + isc->irq = irq; + ret = devm_request_irq(dev, irq, microchip_isc_interrupt, 0, "microchip-sama7g5-xisc", isc); if (ret < 0) { From 00d87c379915a3689b2488bd37f490875d79e20f Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 17 Aug 2026 12:22:01 +0530 Subject: [PATCH 156/596] media: microchip-isc: disable histogram and flush AWB work on teardown isc_stop_streaming() and the isc_start_streaming() error path dropped the runtime PM reference with the histogram still enabled. A HISDONE firing just before the stop, or a failed isc_update_profile() on the start path, can queue isc_awb_work(), which reads the histogram registers before taking its own PM reference and faults on the unclocked device. Disable the histogram, synchronize the IRQ and flush the work before the device is left unclocked. isc_configure() is the one enabling the histogram and then calling isc_update_profile(), so do the cleanup in its own failure path; isc_stop_streaming() does the same on teardown. synchronize_irq() must come before cancel_work_sync(), so an in-flight handler cannot re-queue awb_work after it is cancelled. Fixes: 93d4a26c3dab ("[media] atmel-isc: add the isc pipeline function") Cc: stable@vger.kernel.org Signed-off-by: Balakrishnan Sambath Reviewed-by: Eugen Hristev Signed-off-by: Hans Verkuil --- .../media/platform/microchip/microchip-isc-base.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index debbc38717de..0f486dd5bd82 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -289,6 +289,7 @@ static int isc_configure(struct isc_device *isc) struct regmap *regmap = isc->regmap; u32 pfe_cfg0, dcfg, mask, pipeline; struct isc_subdev_entity *subdev = isc->current_subdev; + int ret; pfe_cfg0 = isc->config.sd_format->pfe_cfg0_bps; pipeline = isc->config.bits_pipeline; @@ -321,7 +322,15 @@ static int isc_configure(struct isc_device *isc) isc_set_histogram(isc, false); /* Update profile */ - return isc_update_profile(isc); + ret = isc_update_profile(isc); + if (ret) { + /* flush the histogram work before the clocks are gated */ + isc_set_histogram(isc, false); + synchronize_irq(isc->irq); + cancel_work_sync(&isc->awb_work); + } + + return ret; } static int isc_prepare_streaming(struct vb2_queue *vq) @@ -425,9 +434,13 @@ static void isc_stop_streaming(struct vb2_queue *vq) /* Disable DMA interrupt */ regmap_write(isc->regmap, ISC_INTDIS, ISC_INT_DDONE); + isc_set_histogram(isc, false); + /* let a running IRQ handler finish before the clock is disabled */ synchronize_irq(isc->irq); + cancel_work_sync(&isc->awb_work); + pm_runtime_put_sync(isc->dev); /* Disable stream on the sub device */ From d8983c47d4d95eb1ed889dbb7516d3738fa854d8 Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 17 Aug 2026 12:22:02 +0530 Subject: [PATCH 157/596] media: microchip-isc: do not touch WB registers when not streaming isc_s_awb_ctrl() called isc_update_awb_ctrls() unconditionally, writing the white balance registers even when the device is runtime suspended; on many ARM platforms accessing the unclocked registers is an external abort. The write was also done without awb_lock, racing isc_awb_work(), which holds it so the DMA done IRQ cannot latch a half-updated pipeline. Write the registers only while streaming and not stopping, under awb_lock, and update the profile there. The isc->stop check covers the window where isc_stop_streaming() has gated the clocks but vb2 still reports streaming. Otherwise the new values stay cached and isc_configure() programs them at the next stream start. Fixes: 4e52889f48fe ("media: atmel: atmel-isc-base: expose white balance as v4l2 controls") Cc: stable@vger.kernel.org Reviewed-by: Eugen Hristev Signed-off-by: Balakrishnan Sambath Signed-off-by: Hans Verkuil --- .../platform/microchip/microchip-isc-base.c | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index 0f486dd5bd82..cf4611443fd1 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1511,20 +1511,24 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl) if (ctrl->cluster[ISC_CTRL_GB_OFF]->is_new) ctrls->offset[ISC_HIS_CFG_MODE_GB] = isc->gb_off_ctrl->val; - isc_update_awb_ctrls(isc); - mutex_lock(&isc->awb_mutex); - if (vb2_is_streaming(&isc->vb2_vidq)) { + if (vb2_is_streaming(&isc->vb2_vidq) && !isc->stop) { + unsigned long flags; + /* - * If we are streaming, we can update profile to - * have the new settings in place. + * awb_lock keeps the DMA done IRQ from latching a + * partially written WB pipeline. */ + spin_lock_irqsave(&isc->awb_lock, flags); + isc_update_awb_ctrls(isc); + spin_unlock_irqrestore(&isc->awb_lock, flags); + isc_update_profile(isc); } else { /* - * The auto cluster will activate automatically this - * control. This has to be deactivated when not - * streaming. + * Not streaming: keep the cached values for the next + * stream start and deactivate the cluster-activated + * do_white_balance button. */ v4l2_ctrl_activate(isc->do_wb_ctrl, false); } From 14cbe597504a04c5c49342d0566438e2b438a948 Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 17 Aug 2026 12:22:03 +0530 Subject: [PATCH 158/596] media: microchip-isc: store the unshifted PFE_CFG0 BPS value pfe_cfg0_bps held a pre-shifted BPS value (ISC_PFE_CFG0_BPS_EIGHT was 0x4 << 28) ORed straight into the register. The other format selectors like cfa_baycfg store the bare field value and position it at the write, so this one was inconsistent. Store the bare BPS value and apply it with FIELD_PREP() at the PFE_CFG0 write, and fix the pfe_cfg0_bps comment to match. No functional change. Suggested-by: Eugen Hristev Signed-off-by: Balakrishnan Sambath Reviewed-by: Eugen Hristev Signed-off-by: Hans Verkuil --- drivers/media/platform/microchip/microchip-isc-base.c | 4 +++- drivers/media/platform/microchip/microchip-isc-regs.h | 10 +++++----- drivers/media/platform/microchip/microchip-isc.h | 4 +++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index cf4611443fd1..fe5a1fb88b21 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -8,6 +8,7 @@ * Author: Eugen Hristev * */ +#include #include #include #include @@ -291,7 +292,8 @@ static int isc_configure(struct isc_device *isc) struct isc_subdev_entity *subdev = isc->current_subdev; int ret; - pfe_cfg0 = isc->config.sd_format->pfe_cfg0_bps; + pfe_cfg0 = FIELD_PREP(ISC_PFE_CFG0_BPS_MASK, + isc->config.sd_format->pfe_cfg0_bps); pipeline = isc->config.bits_pipeline; dcfg = isc->config.dcfg_imode | isc->dcfg; diff --git a/drivers/media/platform/microchip/microchip-isc-regs.h b/drivers/media/platform/microchip/microchip-isc-regs.h index e77e1d9a1db8..185ef0eab7ad 100644 --- a/drivers/media/platform/microchip/microchip-isc-regs.h +++ b/drivers/media/platform/microchip/microchip-isc-regs.h @@ -31,11 +31,11 @@ #define ISC_PFE_CFG0_MODE_PROGRESSIVE (0x0 << 4) #define ISC_PFE_CFG0_MODE_MASK GENMASK(6, 4) -#define ISC_PFE_CFG0_BPS_EIGHT (0x4 << 28) -#define ISC_PFG_CFG0_BPS_NINE (0x3 << 28) -#define ISC_PFG_CFG0_BPS_TEN (0x2 << 28) -#define ISC_PFG_CFG0_BPS_ELEVEN (0x1 << 28) -#define ISC_PFG_CFG0_BPS_TWELVE (0x0 << 28) +#define ISC_PFE_CFG0_BPS_EIGHT 0x4 +#define ISC_PFG_CFG0_BPS_NINE 0x3 +#define ISC_PFG_CFG0_BPS_TEN 0x2 +#define ISC_PFG_CFG0_BPS_ELEVEN 0x1 +#define ISC_PFG_CFG0_BPS_TWELVE 0x0 #define ISC_PFE_CFG0_BPS_MASK GENMASK(30, 28) #define ISC_PFE_CFG0_COLEN BIT(12) diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h index f5e322c2e36b..d7bcd74efff9 100644 --- a/drivers/media/platform/microchip/microchip-isc.h +++ b/drivers/media/platform/microchip/microchip-isc.h @@ -62,7 +62,9 @@ struct isc_subdev_entity { * @mbus_code: V4L2 media bus format code. * @cfa_baycfg: If this format is RAW BAYER, indicate the type of bayer. this is either BGBG, RGRG, etc. - * @pfe_cfg0_bps: Number of hardware data lines connected to the ISC + * @pfe_cfg0_bps: ISC_PFE_CFG0 BPS field value (e.g. ISC_PFE_CFG0_BPS_EIGHT), + written into PFE_CFG0 with FIELD_PREP(ISC_PFE_CFG0_BPS_MASK) + at configure time. * @raw: If the format is raw bayer. */ From 96defd0723fc893db6ccf5f4968323db4af0b0aa Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 17 Aug 2026 12:22:04 +0530 Subject: [PATCH 159/596] media: microchip-isc: fix ISC_PFG_CFG0_BPS macro name typo The BPS field macros for ISC_PFE_CFG0 were spelled ISC_PFG_CFG0_BPS_* (NINE, TEN, ELEVEN, TWELVE). The register is PFE, not PFG. Rename them to ISC_PFE_CFG0_BPS_* to match the register and the EIGHT and MASK macros. No functional change. Signed-off-by: Balakrishnan Sambath Reviewed-by: Eugen Hristev Signed-off-by: Hans Verkuil --- .../platform/microchip/microchip-isc-regs.h | 8 ++++---- .../platform/microchip/microchip-sama5d2-isc.c | 18 +++++++++--------- .../platform/microchip/microchip-sama7g5-isc.c | 18 +++++++++--------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-regs.h b/drivers/media/platform/microchip/microchip-isc-regs.h index 185ef0eab7ad..9ddbbb6dd68b 100644 --- a/drivers/media/platform/microchip/microchip-isc-regs.h +++ b/drivers/media/platform/microchip/microchip-isc-regs.h @@ -32,10 +32,10 @@ #define ISC_PFE_CFG0_MODE_MASK GENMASK(6, 4) #define ISC_PFE_CFG0_BPS_EIGHT 0x4 -#define ISC_PFG_CFG0_BPS_NINE 0x3 -#define ISC_PFG_CFG0_BPS_TEN 0x2 -#define ISC_PFG_CFG0_BPS_ELEVEN 0x1 -#define ISC_PFG_CFG0_BPS_TWELVE 0x0 +#define ISC_PFE_CFG0_BPS_NINE 0x3 +#define ISC_PFE_CFG0_BPS_TEN 0x2 +#define ISC_PFE_CFG0_BPS_ELEVEN 0x1 +#define ISC_PFE_CFG0_BPS_TWELVE 0x0 #define ISC_PFE_CFG0_BPS_MASK GENMASK(30, 28) #define ISC_PFE_CFG0_COLEN BIT(12) diff --git a/drivers/media/platform/microchip/microchip-sama5d2-isc.c b/drivers/media/platform/microchip/microchip-sama5d2-isc.c index 5bd109e93a8f..57a5aae3cabf 100644 --- a/drivers/media/platform/microchip/microchip-sama5d2-isc.c +++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c @@ -147,49 +147,49 @@ static struct isc_format sama5d2_formats_list[] = { { .fourcc = V4L2_PIX_FMT_SBGGR10, .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_RGRG, }, { .fourcc = V4L2_PIX_FMT_SGBRG10, .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_GBGB, }, { .fourcc = V4L2_PIX_FMT_SGRBG10, .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_GRGR, }, { .fourcc = V4L2_PIX_FMT_SRGGB10, .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_RGRG, }, { .fourcc = V4L2_PIX_FMT_SBGGR12, .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_BGBG, }, { .fourcc = V4L2_PIX_FMT_SGBRG12, .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_GBGB, }, { .fourcc = V4L2_PIX_FMT_SGRBG12, .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_GRGR, }, { .fourcc = V4L2_PIX_FMT_SRGGB12, .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_RGRG, }, { @@ -210,7 +210,7 @@ static struct isc_format sama5d2_formats_list[] = { { .fourcc = V4L2_PIX_FMT_Y10, .mbus_code = MEDIA_BUS_FMT_Y10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, }, }; diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index d978ce448a4d..a841266ffc30 100644 --- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -156,49 +156,49 @@ static struct isc_format sama7g5_formats_list[] = { { .fourcc = V4L2_PIX_FMT_SBGGR10, .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_RGRG, }, { .fourcc = V4L2_PIX_FMT_SGBRG10, .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_GBGB, }, { .fourcc = V4L2_PIX_FMT_SGRBG10, .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_GRGR, }, { .fourcc = V4L2_PIX_FMT_SRGGB10, .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, .cfa_baycfg = ISC_BAY_CFG_RGRG, }, { .fourcc = V4L2_PIX_FMT_SBGGR12, .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_BGBG, }, { .fourcc = V4L2_PIX_FMT_SGBRG12, .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_GBGB, }, { .fourcc = V4L2_PIX_FMT_SGRBG12, .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_GRGR, }, { .fourcc = V4L2_PIX_FMT_SRGGB12, .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TWELVE, .cfa_baycfg = ISC_BAY_CFG_RGRG, }, { @@ -224,7 +224,7 @@ static struct isc_format sama7g5_formats_list[] = { { .fourcc = V4L2_PIX_FMT_Y10, .mbus_code = MEDIA_BUS_FMT_Y10_1X10, - .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, }, }; From 21ad8225822a892c4c273bf1368d477a83a19dbb Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 17 Aug 2026 12:22:05 +0530 Subject: [PATCH 160/596] media: microchip-isc: fix PM runtime leak in AWB work handler Early return when streaming stops skips pm_runtime_put_sync(), leaking the reference and preventing runtime suspend. Fixes: 314c96e5203d ("media: atmel: atmel-isc-base: use mutex to lock awb workq from streaming") Cc: stable@vger.kernel.org Signed-off-by: Balakrishnan Sambath Reviewed-by: Eugen Hristev Signed-off-by: Hans Verkuil --- drivers/media/platform/microchip/microchip-isc-base.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index fe5a1fb88b21..e5ecf6ffd24a 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -1434,7 +1434,7 @@ static void isc_awb_work(struct work_struct *w) /* streaming is not active anymore */ if (isc->stop) { mutex_unlock(&isc->awb_mutex); - return; + goto out_pm_put; } isc_update_profile(isc); @@ -1445,6 +1445,7 @@ static void isc_awb_work(struct work_struct *w) if (ctrls->awb) regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ); +out_pm_put: pm_runtime_put_sync(isc->dev); } From bb96c78c5f87072913f8825faebe64bf711a503c Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 17 Aug 2026 12:22:06 +0530 Subject: [PATCH 161/596] media: microchip-isc: fix SBGGR10 Bayer pattern SBGGR10 was mapped to ISC_BAY_CFG_RGRG instead of ISC_BAY_CFG_BGBG, causing red/blue channel swap. Fixes: 1e8f401474d6 ("media: atmel: atmel-isc: reworked driver and formats") Cc: stable@vger.kernel.org Signed-off-by: Balakrishnan Sambath Reviewed-by: Eugen Hristev Signed-off-by: Hans Verkuil --- drivers/media/platform/microchip/microchip-sama5d2-isc.c | 2 +- drivers/media/platform/microchip/microchip-sama7g5-isc.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-sama5d2-isc.c b/drivers/media/platform/microchip/microchip-sama5d2-isc.c index 57a5aae3cabf..5e41eee45dbd 100644 --- a/drivers/media/platform/microchip/microchip-sama5d2-isc.c +++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c @@ -148,7 +148,7 @@ static struct isc_format sama5d2_formats_list[] = { .fourcc = V4L2_PIX_FMT_SBGGR10, .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, - .cfa_baycfg = ISC_BAY_CFG_RGRG, + .cfa_baycfg = ISC_BAY_CFG_BGBG, }, { .fourcc = V4L2_PIX_FMT_SGBRG10, diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c index a841266ffc30..9e78ea07b013 100644 --- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c +++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c @@ -157,7 +157,7 @@ static struct isc_format sama7g5_formats_list[] = { .fourcc = V4L2_PIX_FMT_SBGGR10, .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_TEN, - .cfa_baycfg = ISC_BAY_CFG_RGRG, + .cfa_baycfg = ISC_BAY_CFG_BGBG, }, { .fourcc = V4L2_PIX_FMT_SGBRG10, From 6e9eaa3b93870e4edac1bd2539fe113c24663eaf Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 17 Aug 2026 12:22:07 +0530 Subject: [PATCH 162/596] media: microchip-isc: fix WB offset and gain register field masking ISC_WB_O_* and ISC_WB_G_* each pack two 13-bit fields. A negative offset sign-extends and corrupts the adjacent field. Add masks for the two fields and write them with FIELD_PREP(), which masks each value into its field, so sign extension can no longer bleed across. Fixes: 73c1a577b839 ("media: atmel: atmel-isc: reworked white balance feature") Cc: stable@vger.kernel.org Reviewed-by: Eugen Hristev Signed-off-by: Balakrishnan Sambath Signed-off-by: Hans Verkuil --- .../platform/microchip/microchip-isc-base.c | 16 ++++++++-------- .../platform/microchip/microchip-isc-regs.h | 6 ++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c index e5ecf6ffd24a..963d5fd53b58 100644 --- a/drivers/media/platform/microchip/microchip-isc-base.c +++ b/drivers/media/platform/microchip/microchip-isc-base.c @@ -63,17 +63,17 @@ static inline void isc_update_awb_ctrls(struct isc_device *isc) /* In here we set our actual hw pipeline config */ regmap_write(isc->regmap, ISC_WB_O_RGR, - ((ctrls->offset[ISC_HIS_CFG_MODE_R])) | - ((ctrls->offset[ISC_HIS_CFG_MODE_GR]) << 16)); + FIELD_PREP(ISC_WB_O_LO, ctrls->offset[ISC_HIS_CFG_MODE_R]) | + FIELD_PREP(ISC_WB_O_HI, ctrls->offset[ISC_HIS_CFG_MODE_GR])); regmap_write(isc->regmap, ISC_WB_O_BGB, - ((ctrls->offset[ISC_HIS_CFG_MODE_B])) | - ((ctrls->offset[ISC_HIS_CFG_MODE_GB]) << 16)); + FIELD_PREP(ISC_WB_O_LO, ctrls->offset[ISC_HIS_CFG_MODE_B]) | + FIELD_PREP(ISC_WB_O_HI, ctrls->offset[ISC_HIS_CFG_MODE_GB])); regmap_write(isc->regmap, ISC_WB_G_RGR, - ctrls->gain[ISC_HIS_CFG_MODE_R] | - (ctrls->gain[ISC_HIS_CFG_MODE_GR] << 16)); + FIELD_PREP(ISC_WB_G_LO, ctrls->gain[ISC_HIS_CFG_MODE_R]) | + FIELD_PREP(ISC_WB_G_HI, ctrls->gain[ISC_HIS_CFG_MODE_GR])); regmap_write(isc->regmap, ISC_WB_G_BGB, - ctrls->gain[ISC_HIS_CFG_MODE_B] | - (ctrls->gain[ISC_HIS_CFG_MODE_GB] << 16)); + FIELD_PREP(ISC_WB_G_LO, ctrls->gain[ISC_HIS_CFG_MODE_B]) | + FIELD_PREP(ISC_WB_G_HI, ctrls->gain[ISC_HIS_CFG_MODE_GB])); } static inline void isc_reset_awb_ctrls(struct isc_device *isc) diff --git a/drivers/media/platform/microchip/microchip-isc-regs.h b/drivers/media/platform/microchip/microchip-isc-regs.h index 9ddbbb6dd68b..fe145b142b82 100644 --- a/drivers/media/platform/microchip/microchip-isc-regs.h +++ b/drivers/media/platform/microchip/microchip-isc-regs.h @@ -149,6 +149,12 @@ /* ISC White Balance Gain for B, GB Register */ #define ISC_WB_G_BGB 0x0000006c +/* Each WB offset/gain register packs two 13-bit fields, low and high */ +#define ISC_WB_O_LO GENMASK(12, 0) /* R or B offset [12:0] */ +#define ISC_WB_O_HI GENMASK(28, 16) /* GR or GB offset [28:16] */ +#define ISC_WB_G_LO GENMASK(12, 0) /* R or B gain [12:0] */ +#define ISC_WB_G_HI GENMASK(28, 16) /* GR or GB gain [28:16] */ + /* ISC Color Filter Array Control Register */ #define ISC_CFA_CTRL 0x00000070 From 61490ff518c7b3ea3ff1cd16bbc012e9e4f1ab74 Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Wed, 19 Aug 2026 11:49:20 +0530 Subject: [PATCH 163/596] media: microchip-isc: don't sleep in the clk .is_enabled callback isc_clk_is_enabled() calls pm_runtime_resume_and_get() and pm_runtime_put_sync(), which can sleep and are not safe here, as .is_enabled must run in atomic context. clk_disable_unused() calls it so at boot, and CONFIG_DEBUG_ATOMIC_SLEEP reports a "sleeping function called from invalid context" BUG. Use the atomic-safe pm_runtime_get_if_active() and pm_runtime_put() instead. A suspended ISC has its clocks gated, so report the clock disabled when the device is not already active. Fixes: 01192aa1c5c2 ("media: atmel-isc: Enable the clocks during probe") Cc: stable@vger.kernel.org Signed-off-by: Balakrishnan Sambath Reviewed-by: Eugen Hristev Signed-off-by: Hans Verkuil --- drivers/media/platform/microchip/microchip-isc-clk.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/microchip/microchip-isc-clk.c b/drivers/media/platform/microchip/microchip-isc-clk.c index 24358d804e75..66dc522a6190 100644 --- a/drivers/media/platform/microchip/microchip-isc-clk.c +++ b/drivers/media/platform/microchip/microchip-isc-clk.c @@ -98,15 +98,14 @@ static int isc_clk_is_enabled(struct clk_hw *hw) { struct isc_clk *isc_clk = to_isc_clk(hw); u32 status; - int ret; - ret = pm_runtime_resume_and_get(isc_clk->dev); - if (ret < 0) + /* Runs in atomic context, so must not sleep to resume the ISC. */ + if (pm_runtime_get_if_active(isc_clk->dev) <= 0) return 0; regmap_read(isc_clk->regmap, ISC_CLKSR, &status); - pm_runtime_put_sync(isc_clk->dev); + pm_runtime_put(isc_clk->dev); return status & ISC_CLK(isc_clk->id) ? 1 : 0; } From b83651d321d4fd07979a59f359dcb0266391f17a Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Tue, 21 Jul 2026 21:01:19 +0200 Subject: [PATCH 164/596] media: stm32: csi: correct remote pad retrieval The function media_entity_get_fwnode_pad is called in order to retrieve the remote pad, which is the source of the CSI. However this function is expecting the sensor endpoint fwnode and not sensor device fwnode as it is currently given. Correct this by properly relying on the notified matched fwnode data instead of the subdev entity fwnode. Cc: stable@kernel.org Fixes: dcb0f4c16be5 ("media: stm32: csi: addition of the STM32 CSI driver") Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- drivers/media/platform/st/stm32/stm32-csi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/st/stm32/stm32-csi.c b/drivers/media/platform/st/stm32/stm32-csi.c index fd2b6dfbd44c..ecd0ed8e76b5 100644 --- a/drivers/media/platform/st/stm32/stm32-csi.c +++ b/drivers/media/platform/st/stm32/stm32-csi.c @@ -825,7 +825,7 @@ static int stm32_csi_async_bound(struct v4l2_async_notifier *notifier, int remote_pad; remote_pad = media_entity_get_fwnode_pad(&s_subdev->entity, - s_subdev->fwnode, + asd->match.fwnode, MEDIA_PAD_FL_SOURCE); if (remote_pad < 0) { dev_err(csidev->dev, "Couldn't find output pad for subdev %s\n", From 4957d480a0c87cf3d024e6ff1f57d1222f9212cb Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 29 Jul 2026 11:18:34 +0900 Subject: [PATCH 165/596] media: stm32: csi: unregister async notifier on cleanup stm32_csi_parse_dt() registers the async notifier before probe finishes. If a later operation fails, the error path only cleans up the notifier's connections. It does not unregister the notifier, leaving its entry on the global notifier list after the devm-allocated csidev is freed. The remove path has the same problem. A later async operation can then walk the stale notifier entry and access freed memory. Unregister the notifier before cleaning up its resources on both paths. Fixes: dcb0f4c16be5 ("media: stm32: csi: addition of the STM32 CSI driver") Cc: stable@vger.kernel.org Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Signed-off-by: Hans Verkuil --- drivers/media/platform/st/stm32/stm32-csi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/platform/st/stm32/stm32-csi.c b/drivers/media/platform/st/stm32/stm32-csi.c index ecd0ed8e76b5..78c68cf90ffc 100644 --- a/drivers/media/platform/st/stm32/stm32-csi.c +++ b/drivers/media/platform/st/stm32/stm32-csi.c @@ -1059,6 +1059,7 @@ static int stm32_csi_probe(struct platform_device *pdev) return 0; err_cleanup: + v4l2_async_nf_unregister(&csidev->notifier); v4l2_async_nf_cleanup(&csidev->notifier); return ret; } @@ -1067,6 +1068,8 @@ static void stm32_csi_remove(struct platform_device *pdev) { struct stm32_csi_dev *csidev = platform_get_drvdata(pdev); + v4l2_async_nf_unregister(&csidev->notifier); + v4l2_async_nf_cleanup(&csidev->notifier); v4l2_async_unregister_subdev(&csidev->sd); pm_runtime_disable(&pdev->dev); From c7528789244ce7329ca883170fb9315d739ae985 Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Sat, 8 Aug 2026 16:07:29 +0700 Subject: [PATCH 166/596] media: microchip-csi2dc: fix async notifier leak on probe error path csi2dc_of_parse() ends up calling csi2dc_prepare_notifier(), which registers the V4L2 async notifier with v4l2_async_nf_register(). The probe error label csi2dc_probe_cleanup_notifier is reached after csi2dc_of_parse() has already succeeded (e.g. when media entity init, csi2dc_power() or v4l2_async_register_subdev() fail), but it calls only v4l2_async_nf_cleanup() and never v4l2_async_nf_unregister(). The notifier therefore stays chained in the global notifier_list while the enclosing struct csi2dc_device is freed, leading to list corruption and a use-after-free when the list is next walked. Unregister the notifier before cleaning it up on the error path, matching the teardown already done in csi2dc_remove(). Fixes: 2de0b3c0f678 ("media: atmel: introduce microchip csi2dc driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Reviewed-by: Eugen Hristev Signed-off-by: Hans Verkuil --- drivers/media/platform/microchip/microchip-csi2dc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/microchip/microchip-csi2dc.c b/drivers/media/platform/microchip/microchip-csi2dc.c index e69292f3b2a9..b2c17ff2322f 100644 --- a/drivers/media/platform/microchip/microchip-csi2dc.c +++ b/drivers/media/platform/microchip/microchip-csi2dc.c @@ -735,6 +735,7 @@ static int csi2dc_probe(struct platform_device *pdev) return 0; csi2dc_probe_cleanup_notifier: + v4l2_async_nf_unregister(&csi2dc->notifier); v4l2_async_nf_cleanup(&csi2dc->notifier); csi2dc_probe_cleanup_entity: media_entity_cleanup(&csi2dc->csi2dc_sd.entity); From 10d8dfda1c08f6788049aa100685e223d857f901 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 9 Apr 2026 15:49:44 +0200 Subject: [PATCH 167/596] media: adv7604: avoid negative array index in log_status when cp_read fails cp_read() returns the negative errno from regmap_read() on I2C failure. adv76xx_log_status() shifts the result right by 4 and uses it directly to index csc_coeff_sel_rb[16] causing the right shift of a negative number to result in -1, reading a negative place in the array. Commit 8163419e3e05 ("media: adv7842: Avoid possible out-of-bounds array accesses in adv7842_cp_log_status()") fixed the identical pattern in the adv7842, so do the same thing here. Fixes: 54450f591c99 ("[media] adv7604: driver for the Analog Devices ADV7604 video decoder") Cc: stable@kernel.org Assisted-by: gregkh_clanker_t1000 Signed-off-by: Greg Kroah-Hartman Signed-off-by: Hans Verkuil --- drivers/media/i2c/adv7604.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index ac9c69ce438f..231edd3797ab 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -2641,8 +2641,9 @@ static int adv76xx_log_status(struct v4l2_subdev *sd) "(16-235)" : "(0-255)", (reg_io_0x02 & 0x08) ? "enabled" : "disabled"); } + ret = cp_read(sd, info->cp_csc) >> 4; v4l2_info(sd, "Color space conversion: %s\n", - csc_coeff_sel_rb[cp_read(sd, info->cp_csc) >> 4]); + ret < 0 ? "" : csc_coeff_sel_rb[ret]); if (!is_digital_input(sd)) return 0; From bad6ce6e0856f344910ba95b006e6c0ebee2d9c8 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 9 Apr 2026 15:49:45 +0200 Subject: [PATCH 168/596] media: stv090x: bound DiSEqC reply length to msg[] size The FIFO_BYTENBR_FIELD register field is 4 bits wide, giving a length of 0..15, but reply->msg is __u8[4] in struct dvb_diseqc_slave_reply. A faulty or malicious DiSEqC slave (or i2c bus glitch) reporting more than 4 bytes will the array and clobber the stack. The stb0899, tda10071, and s5h1420 drivers all properly bound the FIFO count against sizeof(reply->msg) before the read loop, so do the same thing in this driver. Fixes: e415c689a884 ("V4L/DVB (11579): Initial go at TT S2-1600") Cc: stable@kernel.org Assisted-by: gregkh_clanker_t1000 Signed-off-by: Greg Kroah-Hartman Signed-off-by: Hans Verkuil --- drivers/media/dvb-frontends/stv090x.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/dvb-frontends/stv090x.c b/drivers/media/dvb-frontends/stv090x.c index 932bbed5497a..63be9ee104ab 100644 --- a/drivers/media/dvb-frontends/stv090x.c +++ b/drivers/media/dvb-frontends/stv090x.c @@ -3902,6 +3902,8 @@ static int stv090x_recv_slave_reply(struct dvb_frontend *fe, struct dvb_diseqc_s if (rx_end) { reply->msg_len = STV090x_GETFIELD_Px(reg, FIFO_BYTENBR_FIELD); + if (reply->msg_len > sizeof(reply->msg)) + reply->msg_len = sizeof(reply->msg); for (i = 0; i < reply->msg_len; i++) reply->msg[i] = STV090x_READ_DEMOD(state, DISRXDATA); } From 6d7e70111b9c80a9e4cb17fe6a6c4f5470847722 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 9 Apr 2026 15:49:46 +0200 Subject: [PATCH 169/596] media: stv0900: bound DiSEqC reply length to msg[] size The FIFO_BYTENBR field is 4 bits (mask 0x0f), giving a length of 0..15 but reply->msg is __u8[4] in struct dvb_diseqc_slave_reply. A faulty or malicious device reporting more than 4 bytes will the array and clobber the stack. The stb0899, tda10071, and s5h1420 drivers all properly bound the FIFO count against sizeof(reply->msg) before the read loop, so do the same thing in this driver. Fixes: 99277b3824e4 ("V4L/DVB (10803): Add core code for ST STV0900 dual demodulator.") Cc: stable@kernel.org Assisted-by: gregkh_clanker_t1000 Signed-off-by: Greg Kroah-Hartman Signed-off-by: Hans Verkuil --- drivers/media/dvb-frontends/stv0900_core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/dvb-frontends/stv0900_core.c b/drivers/media/dvb-frontends/stv0900_core.c index d15c55de2723..0ca6b6d81273 100644 --- a/drivers/media/dvb-frontends/stv0900_core.c +++ b/drivers/media/dvb-frontends/stv0900_core.c @@ -1773,6 +1773,8 @@ static int stv0900_recv_slave_reply(struct dvb_frontend *fe, if (stv0900_get_bits(intp, RX_END)) { reply->msg_len = stv0900_get_bits(intp, FIFO_BYTENBR); + if (reply->msg_len > sizeof(reply->msg)) + reply->msg_len = sizeof(reply->msg); for (i = 0; i < reply->msg_len; i++) reply->msg[i] = stv0900_read_reg(intp, DISRXDATA); From fce1e4fa3867c0ff19a31f9977741696710855a6 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Fri, 3 Jul 2026 10:00:29 +0300 Subject: [PATCH 170/596] media: synopsys: hdmirx: use request_any_context_irq() for 5V detection IRQ Replace devm_request_irq() with devm_request_any_context_irq() for the 5V detection interrupt handler. This allows the kernel to choose the appropriate execution context (hardirq or threaded) depending on the underlying interrupt controller. On some platforms, the interrupt may be nested or require a threaded handler, and using the fixed hardirq version can cause probe failures. Switching to the more flexible function improves driver portability without affecting functionality. Fixes: 7b59b132ad43 ("media: platform: synopsys: Add support for HDMI input driver") Cc: stable@kernel.org Signed-off-by: Alexander Shiyan Reviewed-by: Dmitry Osipenko Signed-off-by: Hans Verkuil --- drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c index 25f8ca0d6d94..e84d4418c530 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c @@ -2609,10 +2609,11 @@ static int hdmirx_setup_irq(struct snps_hdmirx_dev *hdmirx_dev, irq_set_status_flags(irq, IRQ_NOAUTOEN); hdmirx_dev->det_irq = irq; - ret = devm_request_irq(dev, irq, hdmirx_5v_det_irq_handler, - IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, - "rk_hdmirx-5v", hdmirx_dev); - if (ret) { + ret = devm_request_any_context_irq(dev, irq, hdmirx_5v_det_irq_handler, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING, + "rk_hdmirx-5v", hdmirx_dev); + if (ret < 0) { dev_err_probe(dev, ret, "failed to request hdmirx-5v irq\n"); return ret; } From ae8a4b51df30e39c07704efd5500361d8138c4d0 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 16 Jul 2026 10:15:39 +0000 Subject: [PATCH 171/596] media: nuvoton: npcm-video: quiesce VCD IRQ before teardown The VCD IRQ is devm-requested, but npcm_video_remove() frees the video object before devres releases that IRQ. The threaded handler dereferences video->vcd_regmap before checking VIDEO_STREAMING, so an interrupt in that interval can access freed memory. Request the IRQ with IRQF_NO_AUTOEN. Enable it after starting capture and setting VIDEO_STREAMING, and disable it first in stop_streaming(). disable_irq() waits for an in-flight threaded handler to finish, after which stop_streaming() can mask and reset the VCD without a handler re-enabling it. Use vb2_video_unregister_device() during remove. It releases the vb2 queue and calls stop_streaming() for an active stream, ensuring that the IRQ is disabled before the video object is freed. Do not release the queue separately. If streaming is never started, IRQF_NO_AUTOEN keeps the IRQ disabled until devres releases it. This issue was found by an in-house static analysis tool. Fixes: 46c15a4ff1f4 ("media: nuvoton: Add driver for NPCM video capture and encoding engine") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Tested-by: Marvin Lin Signed-off-by: Hans Verkuil --- drivers/media/platform/nuvoton/npcm-video.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/nuvoton/npcm-video.c b/drivers/media/platform/nuvoton/npcm-video.c index 52505af35c08..c28d9d7edd83 100644 --- a/drivers/media/platform/nuvoton/npcm-video.c +++ b/drivers/media/platform/nuvoton/npcm-video.c @@ -120,6 +120,7 @@ struct npcm_video { struct list_head buffers; struct mutex buffer_lock; /* buffer list lock */ + int irq; unsigned long flags; unsigned int sequence; @@ -1486,6 +1487,7 @@ static int npcm_video_start_streaming(struct vb2_queue *q, unsigned int count) } set_bit(VIDEO_STREAMING, &video->flags); + enable_irq(video->irq); return 0; } @@ -1494,6 +1496,7 @@ static void npcm_video_stop_streaming(struct vb2_queue *q) struct npcm_video *video = vb2_get_drv_priv(q); struct regmap *vcd = video->vcd_regmap; + disable_irq(video->irq); clear_bit(VIDEO_STREAMING, &video->flags); regmap_write(vcd, VCD_INTE, 0); regmap_write(vcd, VCD_STAT, VCD_STAT_CLEAR); @@ -1707,9 +1710,10 @@ static int npcm_video_init(struct npcm_video *video) dev_err(dev, "Failed to find VCD IRQ\n"); return -ENODEV; } + video->irq = irq; rc = devm_request_threaded_irq(dev, irq, NULL, npcm_video_irq, - IRQF_ONESHOT, DEVICE_NAME, video); + IRQF_ONESHOT | IRQF_NO_AUTOEN, DEVICE_NAME, video); if (rc < 0) { dev_err(dev, "Failed to request IRQ %d\n", irq); return rc; @@ -1807,8 +1811,7 @@ static void npcm_video_remove(struct platform_device *pdev) struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); struct npcm_video *video = to_npcm_video(v4l2_dev); - video_unregister_device(&video->vdev); - vb2_queue_release(&video->queue); + vb2_video_unregister_device(&video->vdev); v4l2_ctrl_handler_free(&video->ctrl_handler); v4l2_device_unregister(v4l2_dev); if (video->ece.enable) From ee6a964dca657dc23fc5c34e80a0133b80cce3ed Mon Sep 17 00:00:00 2001 From: Igor Paunovic Date: Tue, 21 Jul 2026 08:41:12 +0200 Subject: [PATCH 172/596] dt-bindings: media: snps,dw-hdmi-rx: add #sound-dai-cells The HDMI RX controller can expose the audio embedded in the incoming HDMI stream as an ALSA capture device. Document the #sound-dai-cells property so that a sound card can reference the HDMI RX audio DAI. Signed-off-by: Igor Paunovic Reviewed-by: Sebastian Reichel Reviewed-by: Krzysztof Kozlowski Reviewed-by: Dmitry Osipenko Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/snps,dw-hdmi-rx.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml index b7f6c87d0e06..b80660da1050 100644 --- a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml +++ b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml @@ -78,6 +78,13 @@ properties: The phandle of the syscon node for the Video Output GRF register to enable EDID transfer through SDAIN and SCLIN. + "#sound-dai-cells": + const: 1 + description: + The HDMI RX controller has two digital audio interfaces, one for + I2S and one for S/PDIF. The DAI cell selects the interface, 0 for + I2S and 1 for S/PDIF. + required: - compatible - reg @@ -90,7 +97,10 @@ required: - pinctrl-0 - hpd-gpios -additionalProperties: false +allOf: + - $ref: /schemas/sound/dai-common.yaml# + +unevaluatedProperties: false examples: - | @@ -129,4 +139,5 @@ examples: pinctrl-0 = <&hdmim1_rx_cec &hdmim1_rx_hpdin &hdmim1_rx_scl &hdmim1_rx_sda &hdmirx_5v_detection>; pinctrl-names = "default"; hpd-gpios = <&gpio1 22 GPIO_ACTIVE_LOW>; + #sound-dai-cells = <1>; }; From d667a3c4d003b53d107ce957c5f4a38f1082f5d5 Mon Sep 17 00:00:00 2001 From: Igor Paunovic Date: Tue, 21 Jul 2026 08:41:13 +0200 Subject: [PATCH 173/596] media: synopsys: hdmirx: add HDMI audio capture support The Synopsys DesignWare HDMI RX controller extracts the audio stream embedded in the incoming HDMI signal and feeds it to an on-SoC I2S controller. Expose it as an ALSA capture device by registering the generic hdmi-codec as a child of the controller, so that a simple-audio-card in the device tree can bind the HDMI RX audio DAI. The sample rate is recovered from the ACR N/CTS values together with the measured TMDS character rate. A periodic worker keeps the local audio reference clock locked to the source by nudging it in small ppm steps to hold the audio FIFO fill level near its target, which avoids FIFO under/overflow and the resulting dropped samples. Across system suspend the worker is stopped before the controller clocks are gated. Resume fully resets the controller, wiping the audio configuration, so when a capture stream is active it re-programs the audio path from the last known sample rate and re-arms the worker, whose rate change and FIFO error paths resynchronize once the source delivers audio again. Signed-off-by: Igor Paunovic Reviewed-by: Dmitry Osipenko Tested-by: Dmitry Osipenko Signed-off-by: Hans Verkuil --- .../platform/synopsys/hdmirx/snps_hdmirx.c | 306 ++++++++++++++++++ .../platform/synopsys/hdmirx/snps_hdmirx.h | 8 + 2 files changed, 314 insertions(+) diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c index e84d4418c530..978bc91d3257 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c @@ -41,6 +41,8 @@ #include #include +#include + #include "snps_hdmirx.h" #include "snps_hdmirx_cec.h" @@ -132,6 +134,13 @@ struct snps_hdmirx_dev { struct delayed_work delayed_work_hotplug; struct delayed_work delayed_work_res_change; struct hdmirx_cec *cec; + struct platform_device *audio_pdev; + struct clk *audio_clk; + struct delayed_work audio_work; + u32 audio_clkrate; + u32 audio_fs; + int audio_pre_state; + bool audio_streaming; 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 */ @@ -2279,6 +2288,13 @@ static int hdmirx_parse_dt(struct snps_hdmirx_dev *hdmirx_dev) if (hdmirx_dev->num_clks < 1) return -ENODEV; + for (int i = 0; i < hdmirx_dev->num_clks; i++) { + if (!strcmp(hdmirx_dev->clks[i].id, "audio")) { + hdmirx_dev->audio_clk = hdmirx_dev->clks[i].clk; + break; + } + } + hdmirx_dev->resets[HDMIRX_RST_A].id = "axi"; hdmirx_dev->resets[HDMIRX_RST_P].id = "apb"; hdmirx_dev->resets[HDMIRX_RST_REF].id = "ref"; @@ -2523,10 +2539,19 @@ static void hdmirx_enable_irq(struct device *dev) msecs_to_jiffies(110)); } +static void hdmirx_audio_setup(struct snps_hdmirx_dev *hdmirx_dev, u32 fs); + static __maybe_unused int hdmirx_suspend(struct device *dev) { struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev); + /* + * Stop the audio worker before the controller clocks are gated; + * the audio path is restored and the worker re-armed from + * resume() while a capture stream is active. + */ + cancel_delayed_work_sync(&hdmirx_dev->audio_work); + hdmirx_disable_irq(dev); /* TODO store CEC HW state */ @@ -2549,6 +2574,19 @@ static __maybe_unused int hdmirx_resume(struct device *dev) hdmirx_hpd_ctrl(hdmirx_dev, true); } + /* + * hdmirx_enable() fully reset the controller, wiping the audio + * configuration. If a capture stream is active across suspend, + * re-program the audio path with the last known sample rate and + * restart the worker; its rate change and FIFO error paths + * resynchronize once the source delivers audio again. + */ + if (READ_ONCE(hdmirx_dev->audio_streaming)) { + hdmirx_audio_setup(hdmirx_dev, hdmirx_dev->audio_fs); + mod_delayed_work(system_unbound_wq, &hdmirx_dev->audio_work, + msecs_to_jiffies(200)); + } + /* TODO restore CEC HW state */ enable_irq(hdmirx_dev->cec->irq); @@ -2647,6 +2685,266 @@ static int hdmirx_register_cec(struct snps_hdmirx_dev *hdmirx_dev, return 0; } +#define HDMIRX_AUDIO_INIT_FIFO_STATE 128 +#define HDMIRX_AUDIO_INIT_STATE (HDMIRX_AUDIO_INIT_FIFO_STATE * 4) + +static const int hdmirx_supported_fs[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000, 768000, -1 +}; + +static int hdmirx_audio_closest_fs(int fs) +{ + int i = 0, fs_t = hdmirx_supported_fs[0]; + + while (fs_t > 0) { + if (abs(fs - fs_t) <= 2000) + return fs_t; + fs_t = hdmirx_supported_fs[++i]; + } + return 0; +} + +/* Recover the incoming audio sample rate from the ACR N/CTS + TMDS clock. */ +static u32 hdmirx_audio_fs(struct snps_hdmirx_dev *hdmirx_dev) +{ + u64 tmds_clk, fs_audio = 0; + u32 acr_cts, acr_n, tmdsqpclk_freq; + u32 acr_pb3_0, acr_pb7_4; + + tmdsqpclk_freq = hdmirx_readl(hdmirx_dev, CMU_TMDSQPCLK_FREQ); + hdmirx_readl(hdmirx_dev, PKTDEC_ACR_PH2_1); + acr_pb3_0 = hdmirx_readl(hdmirx_dev, PKTDEC_ACR_PB3_0); + acr_pb7_4 = hdmirx_readl(hdmirx_dev, PKTDEC_ACR_PB7_4); + /* + * The packet decoder stores the ACR subpacket bytes with packet byte + * 0 in register bits [7:0], so byte-reverse each word to line the + * bytes up: CTS is packet bytes 1-3 (PKTDEC_ACR_PB3_0) and N is + * packet bytes 4-6 (PKTDEC_ACR_PB7_4), 20 bits each. readl() + * already abstracts the bus endianness, so the reversal is + * unconditional. + */ + acr_cts = swab32(acr_pb3_0) & 0xfffff; + acr_n = (swab32(acr_pb7_4) & 0x0fffff00) >> 8; + tmds_clk = tmdsqpclk_freq * 4 * 1000U; + if (acr_cts != 0) { + fs_audio = div_u64((tmds_clk * acr_n), acr_cts); + fs_audio /= 128; + fs_audio = hdmirx_audio_closest_fs(fs_audio); + } + return (u32)fs_audio; +} + +/* Nudge the audio reference clock by +/- ppm to keep the FIFO balanced. */ +static void hdmirx_audio_clk_ppm_inc(struct snps_hdmirx_dev *hdmirx_dev, int ppm) +{ + int delta, inc; + long rate = hdmirx_dev->audio_clkrate; + + if (ppm < 0) { + ppm = -ppm; + inc = -1; + } else { + inc = 1; + } + delta = (int)div64_u64((u64)rate * ppm + 500000, 1000000); + delta *= inc; + rate = hdmirx_dev->audio_clkrate + delta; + clk_set_rate(hdmirx_dev->audio_clk, rate); + hdmirx_dev->audio_clkrate = rate; +} + +static int hdmirx_audio_clk_adjust(struct snps_hdmirx_dev *hdmirx_dev, + int total_offset, int single_offset) +{ + int schedule_time = 500; + int ppm = 10; + u32 offset_abs = abs(total_offset); + + if (offset_abs > 200) { + ppm += 200; + schedule_time -= 100; + } + if (offset_abs > 100) { + ppm += 200; + schedule_time -= 100; + } + if (offset_abs > 32) { + ppm += 20; + schedule_time -= 100; + } + if (offset_abs > 16) + ppm += 20; + if (total_offset > 16 && single_offset > 0) + hdmirx_audio_clk_ppm_inc(hdmirx_dev, ppm); + else if (total_offset < -16 && single_offset < 0) + hdmirx_audio_clk_ppm_inc(hdmirx_dev, -ppm); + return schedule_time; +} + +static void hdmirx_audio_fifo_reinit(struct snps_hdmirx_dev *hdmirx_dev) +{ + hdmirx_writel(hdmirx_dev, AUDIO_FIFO_CONTROL, 1); + usleep_range(200, 210); + hdmirx_writel(hdmirx_dev, AUDIO_FIFO_CONTROL, 0); +} + +/* + * Program the audio clock, FIFO thresholds and enables for the given + * sample rate. Shared by hw_params and system resume: the controller is + * fully reset on resume, so the whole configuration must be re-applied. + */ +static void hdmirx_audio_setup(struct snps_hdmirx_dev *hdmirx_dev, u32 fs) +{ + hdmirx_dev->audio_fs = fs; + hdmirx_dev->audio_clkrate = fs * 128; + clk_set_rate(hdmirx_dev->audio_clk, hdmirx_dev->audio_clkrate); + + hdmirx_audio_fifo_reinit(hdmirx_dev); + hdmirx_writel(hdmirx_dev, AUDIO_FIFO_THR_PASS, HDMIRX_AUDIO_INIT_FIFO_STATE); + hdmirx_writel(hdmirx_dev, AUDIO_FIFO_THR, + AFIFO_THR_LOW_QST(0x20) | AFIFO_THR_HIGH_QST(0x160)); + hdmirx_writel(hdmirx_dev, AUDIO_FIFO_MUTE_THR, + AFIFO_THR_MUTE_LOW_QST(0x8) | AFIFO_THR_MUTE_HIGH_QST(0x178)); + + hdmirx_update_bits(hdmirx_dev, AUDIO_PROC_CONFIG0, I2S_EN, I2S_EN); + hdmirx_update_bits(hdmirx_dev, GLOBAL_SWENABLE, AUDIO_ENABLE, AUDIO_ENABLE); + + hdmirx_dev->audio_pre_state = 0; +} + +/* + * Periodic worker that locks the local audio clock to the source by keeping + * the audio FIFO fill level close to its target, avoiding under/overflow. + */ +static void hdmirx_audio_work(struct work_struct *work) +{ + struct snps_hdmirx_dev *hdmirx_dev = + container_of(to_delayed_work(work), struct snps_hdmirx_dev, audio_work); + unsigned long delay = 200; + int cur, total, single; + u32 fifo, fs; + + fs = hdmirx_audio_fs(hdmirx_dev); + fifo = hdmirx_readl(hdmirx_dev, AUDIO_FIFO_STATUS2); + + if (fifo & (AFIFO_UNDERFLOW_ST | AFIFO_OVERFLOW_ST)) { + if (fs) { + clk_set_rate(hdmirx_dev->audio_clk, fs * 128); + hdmirx_dev->audio_clkrate = fs * 128; + hdmirx_dev->audio_fs = fs; + } + hdmirx_audio_fifo_reinit(hdmirx_dev); + hdmirx_dev->audio_pre_state = 0; + goto out; + } + + cur = fifo & 0xffff; + total = cur - HDMIRX_AUDIO_INIT_STATE; + single = cur - hdmirx_dev->audio_pre_state; + + if (fs && abs((int)fs - (int)hdmirx_dev->audio_fs) > 1000) { + clk_set_rate(hdmirx_dev->audio_clk, fs * 128); + hdmirx_dev->audio_clkrate = fs * 128; + hdmirx_dev->audio_fs = fs; + hdmirx_audio_fifo_reinit(hdmirx_dev); + hdmirx_dev->audio_pre_state = 0; + goto out; + } + + if (cur != 0) + delay = hdmirx_audio_clk_adjust(hdmirx_dev, total, single); + hdmirx_dev->audio_pre_state = cur; +out: + /* Only re-arm while streaming; avoids a self-reschedule race with + * the cancel_delayed_work_sync() callers (hw_params and + * audio_shutdown). + */ + if (READ_ONCE(hdmirx_dev->audio_streaming)) + queue_delayed_work(system_unbound_wq, &hdmirx_dev->audio_work, + msecs_to_jiffies(delay)); +} + +static int hdmirx_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev); + u32 fs; + + /* Only the I2S interface (DAI 0) is wired up so far. */ + if (fmt->fmt == HDMI_SPDIF) + return -EOPNOTSUPP; + + /* + * Stop the worker before touching the shared audio state; it is + * re-armed below once the new parameters are in place. + */ + WRITE_ONCE(hdmirx_dev->audio_streaming, false); + cancel_delayed_work_sync(&hdmirx_dev->audio_work); + + fs = hdmirx_audio_fs(hdmirx_dev); + if (!fs) + fs = hparms ? hparms->sample_rate : 48000; + if (!fs) + fs = 48000; + + hdmirx_audio_setup(hdmirx_dev, fs); + + WRITE_ONCE(hdmirx_dev->audio_streaming, true); + mod_delayed_work(system_unbound_wq, &hdmirx_dev->audio_work, + msecs_to_jiffies(200)); + + dev_dbg(dev, "audio hw_params: fs=%u\n", fs); + return 0; +} + +static void hdmirx_audio_shutdown(struct device *dev, void *data) +{ + struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev); + + WRITE_ONCE(hdmirx_dev->audio_streaming, false); + cancel_delayed_work_sync(&hdmirx_dev->audio_work); + hdmirx_update_bits(hdmirx_dev, GLOBAL_SWENABLE, AUDIO_ENABLE, 0); +} + +static const struct hdmi_codec_ops hdmirx_audio_codec_ops = { + .hw_params = hdmirx_audio_hw_params, + .audio_shutdown = hdmirx_audio_shutdown, +}; + +static int hdmirx_register_audio_device(struct snps_hdmirx_dev *hdmirx_dev) +{ + struct hdmi_codec_pdata codec_data = { + .ops = &hdmirx_audio_codec_ops, + .i2s = 1, + .no_i2s_playback = 1, + .max_i2s_channels = 8, + /* + * The controller also has an S/PDIF audio interface (DAI 1 in + * the binding). Register it so DAI indexes match the binding, + * but reject its use in hw_params() until it is wired up. + */ + .spdif = 1, + .no_spdif_playback = 1, + .data = hdmirx_dev, + }; + struct platform_device *audio_pdev; + + if (!hdmirx_dev->audio_clk) + return -ENODEV; + + audio_pdev = platform_device_register_data(hdmirx_dev->dev, + HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + if (IS_ERR(audio_pdev)) + return PTR_ERR(audio_pdev); + + hdmirx_dev->audio_pdev = audio_pdev; + + return 0; +} + static int hdmirx_probe(struct platform_device *pdev) { struct snps_hdmirx_dev *hdmirx_dev; @@ -2698,6 +2996,7 @@ static int hdmirx_probe(struct platform_device *pdev) hdmirx_delayed_work_hotplug); INIT_DELAYED_WORK(&hdmirx_dev->delayed_work_res_change, hdmirx_delayed_work_res_change); + INIT_DELAYED_WORK(&hdmirx_dev->audio_work, hdmirx_audio_work); hdmirx_dev->cur_fmt_fourcc = V4L2_PIX_FMT_BGR24; hdmirx_dev->timings = cea640x480; @@ -2766,6 +3065,10 @@ static int hdmirx_probe(struct platform_device *pdev) V4L2_DEBUGFS_IF_AVI, hdmirx_dev, hdmirx_debugfs_if_read); + ret = hdmirx_register_audio_device(hdmirx_dev); + if (ret) + dev_warn(dev, "failed to register HDMI audio codec: %d\n", ret); + return 0; err_unreg_video_dev: @@ -2785,6 +3088,9 @@ static void hdmirx_remove(struct platform_device *pdev) struct device *dev = &pdev->dev; struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev); + if (hdmirx_dev->audio_pdev) + platform_device_unregister(hdmirx_dev->audio_pdev); + v4l2_debugfs_if_free(hdmirx_dev->infoframes); debugfs_remove_recursive(hdmirx_dev->debugfs_dir); diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h index 31b887e94e87..a99f54fd1744 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h @@ -81,6 +81,7 @@ #define DATAPATH_ENABLE BIT(12) #define PKTFIFO_ENABLE BIT(11) #define AVPUNIT_ENABLE BIT(8) +#define AUDIO_ENABLE BIT(9) #define MAIN_ENABLE BIT(0) #define GLOBAL_TIMER_REF_BASE 0x0028 #define CORE_CONFIG 0x0050 @@ -177,20 +178,27 @@ #define VPROC_FMT_OVR_VALUE(x) UPDATE(x, 6, 4) #define VPROC_FMT_OVR_EN BIT(0) +#define AUDIO_FIFO_CONFIG 0x0460 #define AFIFO_FILL_RESTART BIT(0) +#define AUDIO_FIFO_CONTROL 0x0464 #define AFIFO_INIT_P BIT(0) +#define AUDIO_FIFO_THR_PASS 0x0468 +#define AUDIO_FIFO_THR 0x046c #define AFIFO_THR_LOW_QST_MASK GENMASK(25, 16) #define AFIFO_THR_LOW_QST(x) UPDATE(x, 25, 16) #define AFIFO_THR_HIGH_QST_MASK GENMASK(9, 0) #define AFIFO_THR_HIGH_QST(x) UPDATE(x, 9, 0) +#define AUDIO_FIFO_MUTE_THR 0x0470 #define AFIFO_THR_MUTE_LOW_QST_MASK GENMASK(25, 16) #define AFIFO_THR_MUTE_LOW_QST(x) UPDATE(x, 25, 16) #define AFIFO_THR_MUTE_HIGH_QST_MASK GENMASK(9, 0) #define AFIFO_THR_MUTE_HIGH_QST(x) UPDATE(x, 9, 0) +#define AUDIO_FIFO_STATUS2 0x0478 #define AFIFO_UNDERFLOW_ST BIT(25) #define AFIFO_OVERFLOW_ST BIT(24) +#define AUDIO_PROC_CONFIG0 0x0480 #define SPEAKER_ALLOC_OVR_EN BIT(16) #define I2S_BPCUV_EN BIT(4) #define SPDIF_EN BIT(2) From dbe74db4491307b1fe8eb31651a17d167ab26b39 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 23 Jul 2026 14:19:03 +0300 Subject: [PATCH 174/596] media: ti: cal: Deprecate non-MC API usage The MC API was introduced in the CAL driver more than five years ago in commit 38e89e790fe2 ("media: ti-vpe: cal: Implement media controller centric API") as a disabled by default option. Five years later, it's time to give users another nudge to move to the MC API. Deprecate non-MC API usage by printing a warning at probe time, and defaulting to the MC API. The legacy API can still be selected through the mc_api module parameter. Signed-off-by: Laurent Pinchart Reviewed-by: Tomi Valkeinen Reviewed-by: Yemike Abhilash Chandra Acked-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/ti/Kconfig | 11 ----------- drivers/media/platform/ti/cal/cal.c | 10 +++------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/drivers/media/platform/ti/Kconfig b/drivers/media/platform/ti/Kconfig index 1a020b2bbb4f..03df5617e99d 100644 --- a/drivers/media/platform/ti/Kconfig +++ b/drivers/media/platform/ti/Kconfig @@ -30,17 +30,6 @@ config VIDEO_TI_CAL In TI Technical Reference Manual this module is referred as Camera Interface Subsystem (CAMSS). -config VIDEO_TI_CAL_MC - bool "Media Controller centric mode by default" - depends on VIDEO_TI_CAL - default n - help - Enables Media Controller centric mode by default. - - If set, CAL driver will start in Media Controller mode by - 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 diff --git a/drivers/media/platform/ti/cal/cal.c b/drivers/media/platform/ti/cal/cal.c index b7e77b6b8950..5c82378d1eee 100644 --- a/drivers/media/platform/ti/cal/cal.c +++ b/drivers/media/platform/ti/cal/cal.c @@ -43,13 +43,7 @@ unsigned int cal_debug; module_param_named(debug, cal_debug, uint, 0644); MODULE_PARM_DESC(debug, "activates debug info"); -#ifdef CONFIG_VIDEO_TI_CAL_MC -#define CAL_MC_API_DEFAULT 1 -#else -#define CAL_MC_API_DEFAULT 0 -#endif - -bool cal_mc_api = CAL_MC_API_DEFAULT; +bool cal_mc_api = 1; module_param_named(mc_api, cal_mc_api, bool, 0444); MODULE_PARM_DESC(mc_api, "activates the MC API"); @@ -1228,6 +1222,8 @@ static int cal_probe(struct platform_device *pdev) /* Create contexts. */ if (!cal_mc_api) { + dev_warn(cal->dev, "The legacy non-MC API is deprecated\n"); + for (i = 0; i < cal->data->num_csi2_phy; ++i) { struct cal_ctx *ctx; From cee2619b011a784e6eadd6f818321901365c7e3a Mon Sep 17 00:00:00 2001 From: Hermes Wu Date: Tue, 11 Aug 2026 18:31:25 +0800 Subject: [PATCH 175/596] dt-bindings: media: add ITE IT6625/IT6626 HDMI bridge Document the devicetree binding for the ITE IT6625/IT6626 HDMI to MIPI CSI-2 bridge. The device exposes two graph ports: port@0 (MIPI0) and port@1 (MIPI1), the two selectable CSI-2 D-PHY/C-PHY outputs. Only port@0 is required, since a board only needs to wire up as many of the bridge's outputs as it actually uses. Signed-off-by: Hermes Wu Reviewed-by: Krzysztof Kozlowski Signed-off-by: Hans Verkuil --- .../bindings/media/i2c/ite,it6625.yaml | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml b/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml new file mode 100644 index 000000000000..756fe655f534 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml @@ -0,0 +1,175 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ite,it6625.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ITE IT6625/IT6626 HDMI to dual MIPI CSI-2 bridge + +maintainers: + - Hermes Wu + +description: + The ITE IT6625 and IT6626 are HDMI to MIPI CSI-2 bridge devices. + IT6625 supports an HDMI 2.0 input and converts it to one or two D-PHY CSI + outputs, while IT6626 supports an HDMI 2.1 input and converts it to one or + two C/D-PHY CSI outputs. The bridges are programmable through I2C and + expose two selectable CSI-2 output ports and an HDMI input port. The + bridge can operate in split mode or clone mode. + +properties: + compatible: + enum: + - ite,it6625 + - ite,it6626 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + reset-gpios: + description: + GPIO connected to the active-low reset line. + maxItems: 1 + + vcc10-supply: + description: 1.0V core supply + + vdd33-supply: + description: 3.3V supply + + ovdd-supply: + description: I/O supply voltage + + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 output port MIPI0 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + required: + - data-lanes + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 output port MIPI1 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + + required: + - data-lanes + + port@2: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: HDMI input port + + required: + - port@0 + +required: + - compatible + - reg + - ports + - vcc10-supply + - vdd33-supply + - ovdd-supply + +allOf: + - if: + properties: + compatible: + contains: + const: ite,it6626 + then: + properties: + ports: + properties: + port@0: + properties: + endpoint: + required: + - bus-type + port@1: + properties: + endpoint: + required: + - bus-type + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + hdmi-bridge@4c { + compatible = "ite,it6625"; + reg = <0x4c>; + interrupts = <3 IRQ_TYPE_LEVEL_LOW>; + + reset-gpios = <&gpio 2 GPIO_ACTIVE_LOW>; + + vcc10-supply = <&vcc10_reg>; + vdd33-supply = <&vdd33_reg>; + ovdd-supply = <&ovdd_reg>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + csi_out0: endpoint { + remote-endpoint = <&csi2_rx0>; + bus-type = <4>; /* MEDIA_BUS_TYPE_CSI2_DPHY */ + data-lanes = <1 2 3 4>; + }; + }; + + port@2 { + reg = <2>; + endpoint { + remote-endpoint = <&hdmi_con>; + }; + }; + }; + }; + }; From 142e5f00bd57ffdc03fcec2699f95f0cc6b7e9c6 Mon Sep 17 00:00:00 2001 From: Hermes Wu Date: Tue, 11 Aug 2026 18:31:26 +0800 Subject: [PATCH 176/596] media: i2c: add driver for ITE IT6625/IT6626 Add a V4L2 subdevice driver for the ITE IT6625/IT6626 HDMI-to-MIPI CSI-2 bridge chips. IT6625 accepts an HDMI 2.0 input and IT6626 an HDMI 2.1 input, converting it to a D-PHY (or C/D-PHY on IT6626) MIPI CSI-2 output. The bridge is configured over I2C, exposes an HDMI CEC adapter, and supports EDID read/write, DV timings detection/configuration, and HPD control via the standard V4L2 subdevice pad and video ops. Signed-off-by: Hermes Wu Signed-off-by: Hans Verkuil [hverkuil: change it6625_v4l2_sd_ctrl_update to a void function] --- MAINTAINERS | 8 + drivers/media/i2c/Kconfig | 18 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/it6625.c | 2390 ++++++++++++++++++++++++++++ include/uapi/linux/it6625.h | 25 + include/uapi/linux/v4l2-controls.h | 6 + 6 files changed, 2448 insertions(+) create mode 100644 drivers/media/i2c/it6625.c create mode 100644 include/uapi/linux/it6625.h diff --git a/MAINTAINERS b/MAINTAINERS index f0bb3f1b5ebb..f515e1473af9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13965,6 +13965,14 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git F: Documentation/devicetree/bindings/display/bridge/ite,it66121.yaml F: drivers/gpu/drm/bridge/ite-it66121.c +ITE IT6625 HDMI to MIPI MEDIA DRIVER +M: Hermes Wu +S: Maintained +T: git git://linuxtv.org/media.git +F: Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml +F: drivers/media/i2c/it6625.c +F: include/uapi/linux/it6625.h + IVTV VIDEO4LINUX DRIVER M: Andy Walls L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index df6a73731803..3370b66ab4ee 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1322,6 +1322,24 @@ config VIDEO_ISL7998X Support for Intersil ISL7998x analog to MIPI-CSI2 or BT.656 decoder. +config VIDEO_IT6625 + tristate "IT6625 HDMI to MIPI CSI bridge" + depends on VIDEO_DEV && I2C + depends on OF + select CEC_CORE + select MEDIA_CONTROLLER + select REGMAP_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + V4L2 subdevice driver for the ITE IT6625/IT6626 HDMI to MIPI + CSI-2 bridge chips. IT6625 accepts an HDMI 2.0 input and + IT6626 an HDMI 2.1 input, converting it to a MIPI CSI-2 + output. The driver also exposes an HDMI CEC adapter. + + To compile this driver as a module, choose M here: the + module will be called it6625. + config VIDEO_LT6911UXE tristate "Lontium LT6911UXE decoder" depends on ACPI && VIDEO_DEV && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 9c7f990a3e66..fd1cb25718c0 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_VIDEO_IMX678) += imx678.o obj-$(CONFIG_VIDEO_IMX471) += imx471.o obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o +obj-$(CONFIG_VIDEO_IT6625) += it6625.o obj-$(CONFIG_VIDEO_KS0127) += ks0127.o obj-$(CONFIG_VIDEO_LM3560) += lm3560.o obj-$(CONFIG_VIDEO_LM3646) += lm3646.o diff --git a/drivers/media/i2c/it6625.c b/drivers/media/i2c/it6625.c new file mode 100644 index 000000000000..da17e5d5ce9e --- /dev/null +++ b/drivers/media/i2c/it6625.c @@ -0,0 +1,2390 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * it6625 - ite HDMI to MIPI bridge + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int debug = 3; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-3)"); + +#define REG_CHIP_ID_0 0x00 +#define REG_CHIP_ID_1 0x01 +#define REG_FW_VER_MAJOR 0x03 +#define REG_FW_VER_MINOR 0x04 +#define REG_PROTOCOL_VERSION 0x05 +#define B_PVER_MINOR BIT(0) +#define B_PVER_MAJOR BIT(4) + +#define REG_CMD_SET 0x10 +#define CMD_SET_CEC_LA 0xC0 +#define CMD_SET_CEC_ENABLE 0xC1 + +#define REG_EDID_START 0x20 +#define REG_CEC_RX_DATA 0x20 +#define REG_CEC_TX_DATA 0x30 + +#define REG_H_ACTIVE_1 0x50 +#define REG_H_ACTIVE_0 0x51 +#define REG_V_ACTIVE_1 0x52 +#define REG_V_ACTIVE_0 0x53 +#define REG_H_TOTAL_1 0x54 +#define REG_H_TOTAL_0 0x55 +#define REG_V_TOTAL_1 0x56 +#define REG_V_TOTAL_0 0x57 +#define REG_VID_PCLK 0x58 +#define REG_H_FP_1 0x5C +#define REG_H_FP_0 0x5D +#define REG_H_SW_1 0x5E +#define REG_H_SW_0 0x5F +#define REG_H_BP_1 0x60 +#define REG_H_BP_0 0x61 +#define REG_V_FP_1 0x62 +#define REG_V_FP_0 0x63 +#define REG_V_SW_1 0x64 +#define REG_V_SW_0 0x65 +#define REG_V_BP_1 0x66 +#define REG_V_BP_0 0x67 +#define REG_VID_INFO 0x68 +#define B_INTERLACE BIT(0) +#define B_HSWPOL BIT(1) +#define B_VSWPOL BIT(2) +#define B_PIXEL_REP BIT(4) + +#define REG_VIC 0x69 +#define REG_HDMI_VIDEO_INFO 0x6A +#define B_COLOR_MODE BIT(0) +#define B_COLOR_DEPTH BIT(4) +#define REG_HDMI_AUDIO_INFO1 0x6B +#define B_AUD_FS BIT(0) + +#define REG_HDMI_AUDIO_INFO2 0x6C +#define B_AUD_CH BIT(0) +#define B_AUD_WL BIT(4) + +#define REG_HDMI_AUDIO_INFO3 0x6D +#define B_AUD_TYPE BIT(0) +#define B_AUD_MS BIT(2) +#define B_AUD_3D BIT(3) + +#define REG_AUDIO_FMT 0x6E +#define B_I2S_WL BIT(0) +#define B_I2S_ALN BIT(2) +#define B_I2S_DLY BIT(3) +#define B_I2S_LR BIT(4) +#define B_I2S_SFT BIT(5) +#define B_AUD_OUT_IF BIT(6) + +#define REG_IF_LATCH_HB 0x6F +#define REG_IF_DATA 0x70 +#define REG_EMP_DATA 0xA0 +#define REG_AVI_DATA 0xAE + +#define REG_TX_STATUS 0xE0 + +#define REG_RX_STATUS 0xE2 +#define B_RX_5V BIT(0) +#define B_RX_HPD BIT(1) +#define B_RX_STABLE BIT(2) +#define B_RX_HDMI BIT(3) +#define B_RX_AVMUTE BIT(4) +#define B_RX_AUD_ON BIT(5) + +#define REG_RX_HDCP_STS 0xE3 +#define B_HDCP1_AUTH_START BIT(0) +#define B_HDCP2_AUTH_START BIT(1) +#define B_HDCP_AUTH_DONE BIT(2) +#define B_HDCP_ENC BIT(3) + +#define REG_CEC_STATUS 0xE4 +#define B_CEC_TX_DONE BIT(0) +#define B_CEC_TX_NACK BIT(1) + +#define REG_CEC_RX_DATA_LEN 0xE5 +#define REG_CEC_TX_DATA_LEN 0xE6 + +#define REG_SYS_MIPI_INT 0xEB +#define B_MIPI_OUTPUT_ENABLE BIT(0) +#define B_MIPI_VIDEO_UNSTABLE BIT(1) + +#define REG_RX_INT_STATUS1 0xEC +#define B_HDMI_5V_CHG BIT(0) +#define B_HDMI_VID_CHG BIT(1) +#define B_HDMI_AUD_CHG BIT(2) +#define B_HDMI_CP_CHG BIT(3) +#define B_HDMI_IF_LATCH BIT(4) +#define B_HDMI_NO_IF_LATCH BIT(5) +#define B_HDMI_EMP BIT(6) +#define B_HDMI_NO_EMP BIT(7) + +#define REG_RX_INT_STATUS2 0xED +#define B_HDMI_AVI BIT(0) +#define B_HDMI_NO_AVI BIT(1) +#define B_HDMI_VSIF BIT(2) +#define B_HDMI_NO_VSIF BIT(3) +#define B_HDMI_AVMUTE_CHG BIT(4) + +#define REG_CHIP_CONTROL 0xF0 +#define B_HDMI_RESET BIT(5) +#define B_FW_START BIT(6) + +#define REG_MIPI_CFG 0xF1 +#define M_MIPI_LANE 0x03 +#define B_MIPI_SPLIT BIT(2) +#define B_MIPI_SPLIT_CFG BIT(3) +#define B_MIPI_DPHY BIT(4) +#define B_MIPI_USE_DSI BIT(5) +#define B_MIPI_CONTINU_CLK BIT(6) + +#define REG_MIPI_DATA_TYPE 0xF2 +#define CSI_RGB444 0x20 +#define CSI_RGB555 0x21 +#define CSI_RGB565 0x22 +#define CSI_RGB666 0x23 +#define CSI_RGB888 0x24 +#define CSI_YUV420_8b_L 0x1A +#define CSI_YUV420_8b 0x1C +#define CSI_YUV420_10b 0x1D +#define CSI_YUV422_8b 0x1E +#define CSI_YUV422_10b 0x1F +#define CSI_RGB_10b 0x30 +#define CSI_RGB_12b 0x31 +#define CSI_YUV422_12b 0x32 +#define CSI_YUV420_10b_L 0x33 +#define CSI_YUV420_12b 0x34 +#define CSI_YUV444_8b 0x35 +#define CSI_YUV444_10b 0x36 +#define CSI_YUV444_12b 0x37 + +#define REG_MIPI_CONTROL 0xF3 +#define B_MIPI_OUTPUT BIT(0) + +#define REG_RX_CFG 0xF4 +#define B_MANUAL_HPD BIT(0) +#define B_HPD_HIGH BIT(1) +#define B_HPD_TOGGLE BIT(3) + +#define REG_CSC_CFG 0xF5 +#define B_DYNAMIC_RANGE BIT(0) + +#define REG_MISC_CFG 0xF6 +#define B_BAUD_RATE BIT(0) +#define B_DEBUG_MSG BIT(1) + +#define REG_HPD_DELAY 0xF7 +#define B_DELAY_COUNT BIT(0) +#define B_DELAY_UNIT BIT(7) + +#define REG_INFO_BANK_SEL 0xFD +#define CTL_BANK_EDID_READ 1 +#define CTL_BANK_EDID_WRITE 5 + +#define REG_HOST_CTRL_INT 0xFE +#define B_CMD_SET BIT(4) +#define B_CEC_SEND_DATA BIT(5) +#define B_CONFIG_UPDATE BIT(6) +#define B_IF_BANK BIT(7) + +#define REG_MCU_INTERRUPT 0xFF +#define B_SYS_INT_ACTIVE BIT(0) +#define B_CEC_RX_RECEIVED BIT(1) +#define B_CEC_TX_UPDATE BIT(2) + +#define EDID_NUM_BLOCKS_MAX 4 +#define EDID_BLOCK_SIZE 128 + +#define I2C_MAX_XFER_SIZE 8 +#define POLL_INTERVAL_CEC_MS 10 +#define POLL_INTERVAL_MS 40 + +#define AUD32K 0x03 +#define AUD44K 0x00 +#define AUD48K 0x02 +#define AUD64K 0x0B +#define AUD88K 0x08 +#define AUD96K 0x0A +#define AUD128K 0x2B +#define AUD176K 0x0C +#define AUD192K 0x0E +#define AUD256K 0x1B +#define AUD352K 0x0D +#define AUD384K 0x05 +#define AUD512K 0x3B +#define AUD705K 0x2D +#define AUD768K 0x09 +#define AUD1024K 0x35 +#define AUD1411K 0x1D +#define AUD1536K 0x15 + +enum it6625_chip_type { + IT6625_CHIP = 0, + IT6626_CHIP = 1, +}; + +struct it6625 { + struct device *dev; + struct i2c_client *i2c_client; + struct regmap *it6625_regmap; + enum it6625_chip_type chip_type; + + /* protects concurrent access to the chip's registers and state */ + struct mutex it6625_lock; + /* serializes the complete VIDIOC_S_EDID sequence against itself */ + struct mutex edid_lock; + /* serializes a full InfoFrame debugfs read transaction against itself */ + struct mutex if_read_lock; + /* + * protects if_active/if_type/if_snapshot/if_snapshot_err/ + * if_snapshot_done and the REG_MCU_INTERRUPT/REG_RX_INT_STATUS1/2/ + * REG_IF_LATCH_HB/REG_IF_DATA register group between the interrupt + * path and the InfoFrame debugfs callback. Never held across + * wait_for_completion_timeout(). Nests outside it6625_lock. + */ + struct mutex if_state_lock; + + struct v4l2_subdev sd; + struct v4l2_mbus_config_mipi_csi2 bus; + struct video_device *vdev; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + + /* controls */ + struct v4l2_ctrl *ctrl_5v_detect; + struct v4l2_ctrl *ctrl_audio_sampling_rate; + struct v4l2_ctrl *ctrl_audio_present; + struct v4l2_ctrl *ctrl_link_freq; + + struct delayed_work hpd_delayed_work; + + struct timer_list timer; + struct work_struct polling_work; + + struct v4l2_dv_timings timings; + + u8 csi_lanes; + u8 port_num; + enum v4l2_mbus_type bus_type; + u8 csi_format; + u32 mbus_fmt_code; + /* number of EDID blocks currently loaded, protected by edid_lock */ + u8 edid_blocks; + + struct gpio_desc *reset_gpio; + + struct cec_adapter *cec_adap; + + struct dentry *debugfs_dir; + + struct v4l2_debugfs_if *infoframes; + struct completion if_latched; + /* true while an InfoFrame debugfs request is outstanding */ + bool if_active; + /* HDMI packet-type byte currently armed in REG_IF_LATCH_HB */ + u32 if_type; + /* driver-private copy of REG_IF_DATA, captured at the genuine latch */ + u8 if_snapshot[31]; + /* it6625_read_bytes() result for the if_snapshot capture */ + int if_snapshot_err; + /* set just before complete(), to disambiguate timeout vs. capture */ + bool if_snapshot_done; +}; + +/* + * Index 0: D-PHY (4-lane). Index 1: C-PHY (3-trio) -- the confirmed + * hardware max C-PHY capability, tested single-port/three-trio. + */ +static const s64 it6625_link_freq[] = { + 445500000, + 2500000000LL, +}; + +/* + * Shared cap for every topology except the reference exception below: + * IT6625 (no C-PHY support at all), IT6626 running D-PHY, and IT6626 + * running C-PHY with fewer than three trios. This is conservative + * scoping, not a claim that one-/two-trio C-PHY can't also support a + * higher rate -- they're simply unvalidated. + */ +static const struct v4l2_dv_timings_cap it6625_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + + V4L2_INIT_BT_TIMINGS(640, 3840, 480, 2160, 27000000, 300000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_INTERLACED | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM) +}; + +/* + * IT6626 C-PHY, three trios: raised pixel-clock ceiling (594 MHz vs + * the shared 300 MHz cap) for this topology's higher C-PHY capability. + */ +static const struct v4l2_dv_timings_cap it6626_cphy_3trio_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .reserved = { 0 }, + + V4L2_INIT_BT_TIMINGS(640, 3840, 480, 2160, 27000000, 594000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_INTERLACED | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM) +}; + +static const struct v4l2_dv_timings_cap * +it6625_get_timings_cap(struct it6625 *it6625) +{ + if (it6625->chip_type == IT6626_CHIP && + it6625->bus_type == V4L2_MBUS_CSI2_CPHY && + it6625->csi_lanes == 3) + return &it6626_cphy_3trio_timings_cap; + + return &it6625_timings_cap; +} + +static const struct it6625_format_info { + u8 csi_format; + u32 mbus_fmt_code; +} it6625_formats[] = { + { CSI_YUV422_8b, MEDIA_BUS_FMT_UYVY8_1X16 }, + { CSI_RGB888, MEDIA_BUS_FMT_RGB888_1X24 }, + { CSI_YUV444_8b, MEDIA_BUS_FMT_YUV8_1X24 }, +}; + +static inline int it6625_csi_format_idx(u8 csi_format) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(it6625_formats); i++) { + if (it6625_formats[i].csi_format == csi_format) + return i; + } + + return -EINVAL; +} + +static inline int it6625_csi_mbus_code_idx(u32 mbus_fmt_code) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(it6625_formats); i++) { + if (it6625_formats[i].mbus_fmt_code == mbus_fmt_code) + return i; + } + + return -EINVAL; +} + +static inline struct it6625 *sd_to_6625(struct v4l2_subdev *sd) +{ + return container_of(sd, struct it6625, sd); +} + +static const struct regmap_config it6625_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + .max_raw_read = I2C_MAX_XFER_SIZE, + .max_raw_write = I2C_MAX_XFER_SIZE, +}; + +static int it6625_regmap_i2c_init(struct i2c_client *client, + struct it6625 *it6625) +{ + it6625->i2c_client = client; + it6625->dev = &client->dev; + + it6625->it6625_regmap = devm_regmap_init_i2c(it6625->i2c_client, + &it6625_regmap_config); + if (IS_ERR(it6625->it6625_regmap)) + return PTR_ERR(it6625->it6625_regmap); + + return 0; +} + +static int it6625_read_byte(struct it6625 *it6625, u8 reg) +{ + unsigned int val; + int err; + struct device *dev = it6625->dev; + + err = regmap_read(it6625->it6625_regmap, reg, &val); + if (err < 0) { + dev_err(dev, "reg[0x%x] read failed err: %d", reg, err); + return err; + } + + return val; +} + +static int it6625_write_byte(struct it6625 *it6625, u8 reg, u8 val) +{ + int err; + struct device *dev = it6625->dev; + + err = regmap_write(it6625->it6625_regmap, reg, val); + if (err < 0) { + dev_err(dev, "reg[0x%x] write failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_set_bits(struct it6625 *it6625, u8 reg, u8 mask, u8 val) +{ + int err; + struct device *dev = it6625->dev; + + err = regmap_update_bits(it6625->it6625_regmap, reg, mask, val); + if (err < 0) { + dev_err(dev, "reg[0x%x] set bits failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_read_bytes(struct it6625 *it6625, u8 reg, u8 *buf, int len) +{ + int err; + struct device *dev = it6625->dev; + + err = regmap_bulk_read(it6625->it6625_regmap, reg, buf, len); + if (err < 0) { + dev_err(dev, "reg[0x%x] read failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_write_bytes(struct it6625 *it6625, u8 reg, u8 *buf, int len) +{ + int err; + struct device *dev = it6625->dev; + + err = regmap_bulk_write(it6625->it6625_regmap, reg, buf, len); + if (err < 0) { + dev_err(dev, "reg[0x%x] write failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_wait_for_status(struct it6625 *it6625, u8 reg, u8 val, + int timeout_ms) +{ + struct device *dev = it6625->dev; + int status; + int rval; + int sleep_ms = 10; + int timeout_round_ms = DIV_ROUND_UP(timeout_ms, sleep_ms) * sleep_ms; + + status = read_poll_timeout(it6625_read_byte, rval, rval == val, + sleep_ms * 1000, + timeout_round_ms * 1000, + false, it6625, reg); + + dev_info(dev, "%s status = %d %d", __func__, status, (int)rval); + if (status < 0) { + dev_err(dev, "%s err status = %d", __func__, status); + return -ETIMEDOUT; + } + + return 0; +} + +static void it6625_write_command(struct it6625 *it6625, u8 *cmds, int cmd_len) +{ + it6625_write_bytes(it6625, REG_CMD_SET, cmds, cmd_len); + it6625_write_byte(it6625, REG_HOST_CTRL_INT, B_CMD_SET); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_update_config(struct it6625 *it6625) +{ + int err; + + err = it6625_set_bits(it6625, REG_HOST_CTRL_INT, + B_CONFIG_UPDATE, B_CONFIG_UPDATE); + if (err < 0) + return err; + + return it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_set_bank(struct it6625 *it6625, u8 bank) +{ + int err; + + err = it6625_write_byte(it6625, REG_INFO_BANK_SEL, bank); + if (err < 0) + return err; + + err = it6625_write_byte(it6625, REG_HOST_CTRL_INT, B_IF_BANK); + if (err < 0) + return err; + + return it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static inline bool is_hdmi(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? false : (val & B_RX_HDMI); +} + +static inline bool hdmi_5v_power_present(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? false : (val & B_RX_5V); +} + +static inline bool no_signal(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? true : !(val & B_RX_STABLE); +} + +static inline bool audio_present(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? false : (val & B_RX_AUD_ON); +} + +static int get_audio_sampling_rate(struct it6625 *it6625) +{ + int fs_id; + int i, freq = 0; + const struct fs_id_map { + u8 fs_id; + u32 freq; + } s_fsid_map[] = { + { AUD32K, 32000 }, + { AUD44K, 44100 }, + { AUD48K, 48000 }, + { AUD64K, 64000 }, + { AUD88K, 88200 }, + { AUD96K, 96000 }, + + { AUD128K, 128000 }, + { AUD176K, 176400 }, + { AUD192K, 192000 }, + { AUD256K, 256000 }, + { AUD352K, 352800 }, + { AUD384K, 384000 }, + + { AUD512K, 512000 }, + { AUD705K, 705600 }, + { AUD768K, 768000 }, + { AUD1024K, 1024000 }, + { AUD1411K, 1411200 }, + { AUD1536K, 1536000 }, + }; + + if (no_signal(it6625) || !audio_present(it6625)) + return 0; + + guard(mutex)(&it6625->it6625_lock); + + fs_id = it6625_read_byte(it6625, REG_HDMI_AUDIO_INFO1); + if (fs_id < 0) + return 0; + + for (i = 0; i < ARRAY_SIZE(s_fsid_map); i++) { + if (s_fsid_map[i].fs_id == fs_id) { + freq = s_fsid_map[i].freq; + break; + } + } + + return freq; +} + +static u64 it6625_get_pclk(struct it6625 *it6625) +{ + u32 pclk; + u8 ck[4]; + int ret; + + ret = it6625_read_bytes(it6625, REG_VID_PCLK, ck, 4); + if (ret < 0) { + dev_err(it6625->dev, "failed to read pixel clock"); + return 0; + } + + pclk = ck[0]; + pclk <<= 8; + pclk |= ck[1]; + pclk <<= 8; + pclk |= ck[2]; + pclk <<= 8; + pclk |= ck[3]; + + v4l2_dbg(1, debug, &it6625->sd, "%s: pclk=%u (%08x)", + __func__, pclk, pclk); + + return (u64)pclk * 1000; +} + +static int it6625_read_edid(struct it6625 *it6625, u8 *edid, int start_block, + int num_blocks) +{ + int i, bank_ctrl, err = 0; + struct device *dev = it6625->dev; + + if (!edid) { + dev_err(dev, "edid buffer is NULL"); + return -EINVAL; + } + + if (start_block < 0 || num_blocks <= 0 || + start_block > EDID_NUM_BLOCKS_MAX || + num_blocks > EDID_NUM_BLOCKS_MAX || + start_block + num_blocks > EDID_NUM_BLOCKS_MAX) { + dev_err(dev, + "invalid block range: start_block=%d, num_blocks=%d", + start_block, num_blocks); + return -EINVAL; + } + + guard(mutex)(&it6625->it6625_lock); + for (i = 0; i < num_blocks; i++) { + bank_ctrl = CTL_BANK_EDID_READ + start_block + i; + err = it6625_set_bank(it6625, bank_ctrl); + if (err < 0) + break; + + err = it6625_read_bytes(it6625, REG_EDID_START, + edid + (i * 128), 128); + if (err < 0) + break; + } + + it6625_set_bank(it6625, 0); + + return err < 0 ? err : num_blocks; +} + +static int it6625_write_edid(struct it6625 *it6625, u8 *edid, int start_block, + int num_blocks) +{ + int i, bank_ctrl, err = 0; + struct device *dev = it6625->dev; + + if (start_block < 0 || num_blocks <= 0 || + start_block > EDID_NUM_BLOCKS_MAX || + num_blocks > EDID_NUM_BLOCKS_MAX || + start_block + num_blocks > EDID_NUM_BLOCKS_MAX) { + dev_err(dev, + "invalid block range: start_block=%d, num_blocks=%d", + start_block, num_blocks); + return -EINVAL; + } + + guard(mutex)(&it6625->it6625_lock); + for (i = 0; i < num_blocks; i++) { + bank_ctrl = CTL_BANK_EDID_WRITE + start_block + i; + err = it6625_set_bank(it6625, bank_ctrl); + if (err < 0) + break; + + err = it6625_write_bytes(it6625, REG_EDID_START, + edid + (i * 128), 128); + if (err < 0) + break; + + err = it6625_update_config(it6625); + if (err < 0) + break; + } + + it6625_set_bank(it6625, 0); + + return err < 0 ? err : num_blocks; +} + +static void it6625_enable_auto_hpd(struct it6625 *it6625) +{ + dev_dbg(it6625->dev, "%s: auto HPD", __func__); + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, 0x00); + it6625_update_config(it6625); +} + +static void it6625_disable_hpd(struct it6625 *it6625) +{ + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, 0x01); + it6625_update_config(it6625); +} + +static void it6625_enable_hpd(struct it6625 *it6625) +{ + schedule_delayed_work(&it6625->hpd_delayed_work, HZ / 7); +} + +static void it6625_hpd_delayed_work(struct work_struct *work) +{ + struct it6625 *it6625 = container_of(work, + struct it6625, hpd_delayed_work.work); + int val = 0; + + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, val); + it6625_update_config(it6625); +} + +static int it6625_get_detected_timings(struct it6625 *it6625, + struct v4l2_dv_timings *timings) +{ + struct v4l2_bt_timings *bt = &timings->bt; + int val; + unsigned int width, height; + u8 buffer[4]; + u8 buffer2[12]; + + if (no_signal(it6625)) { + dev_err(it6625->dev, "no signal detected"); + return -ENOLINK; + } + + guard(mutex)(&it6625->it6625_lock); + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + timings->type = V4L2_DV_BT_656_1120; + val = it6625_read_byte(it6625, REG_VID_INFO); + if (val < 0) { + dev_err(it6625->dev, "failed to read video info"); + return -EIO; + } + + bt->interlaced = val & B_INTERLACE ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + + if (it6625_read_bytes(it6625, REG_H_ACTIVE_1, buffer, 4) < 0) + return -EIO; + + width = ((buffer[0] & 0xff) << 8) + buffer[1]; + height = ((buffer[2] & 0xff) << 8) + buffer[3]; + + bt->width = width; + bt->height = height; + + if (it6625_read_bytes(it6625, REG_H_FP_1, buffer2, 12) < 0) + return -EIO; + + bt->hfrontporch = ((buffer2[0] & 0xff) << 8) + buffer2[1]; + bt->hsync = ((buffer2[2] & 0xff) << 8) + buffer2[3]; + bt->hbackporch = ((buffer2[4] & 0xff) << 8) + buffer2[5]; + bt->vfrontporch = ((buffer2[6] & 0xff) << 8) + buffer2[7]; + bt->vsync = ((buffer2[8] & 0xff) << 8) + buffer2[9]; + bt->vbackporch = ((buffer2[10] & 0xff) << 8) + buffer2[11]; + + bt->pixelclock = it6625_get_pclk(it6625); + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height *= 2; + bt->il_vsync = bt->vsync + 1; + } + + return 0; +} + +static void it6625_show_avi_infoframe(struct it6625 *it6625) +{ + struct device *dev = it6625->dev; + union hdmi_infoframe frame; + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; + u8 ver, len; + int ret; + + if (!is_hdmi(it6625)) { + dev_err(dev, "not HDMI signal, skip AVI infoframe log"); + return; + } + + ret = it6625_read_bytes(it6625, REG_AVI_DATA, buffer + 1, + HDMI_INFOFRAME_SIZE(AVI) - 1); + if (ret < 0) { + dev_err(dev, "failed to read AVI infoframe data"); + return; + } + + len = buffer[1]; + ver = buffer[2]; + + buffer[0] = HDMI_INFOFRAME_TYPE_AVI; + buffer[1] = ver; + buffer[2] = len; + + ret = hdmi_infoframe_unpack(&frame, buffer, sizeof(buffer)); + if (ret < 0) { + dev_err(dev, "unpack of AVI infoframe failed"); + return; + } + + hdmi_infoframe_log(KERN_INFO, dev, &frame); +} + +static int it6625_s_ctrl_detect_hdmi_5v(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_5v_detect, + hdmi_5v_power_present(it6625)); +} + +static int it6625_s_ctrl_audio_sampling_rate(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_sampling_rate, + get_audio_sampling_rate(it6625)); +} + +static int it6625_s_ctrl_audio_present(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_present, + audio_present(it6625)); +} + +static void it6625_v4l2_sd_ctrl_update(struct v4l2_subdev *sd) +{ + it6625_s_ctrl_detect_hdmi_5v(sd); + it6625_s_ctrl_audio_sampling_rate(sd); + it6625_s_ctrl_audio_present(sd); +} + +static void it6625_enable_stream_locked(struct it6625 *it6625, bool enable) +{ + struct v4l2_subdev *sd = &it6625->sd; + int val; + + lockdep_assert_held(&it6625->it6625_lock); + + v4l2_dbg(3, debug, sd, "%s: %sable", + __func__, enable ? "en" : "dis"); + + val = enable ? B_MIPI_OUTPUT : 0; + it6625_set_bits(it6625, REG_MIPI_CONTROL, B_MIPI_OUTPUT, val); + it6625_update_config(it6625); +} + +static void it6625_enable_stream(struct it6625 *it6625, bool enable) +{ + guard(mutex)(&it6625->it6625_lock); + it6625_enable_stream_locked(it6625, enable); +} + +static void it6625_set_mipi_config_locked(struct it6625 *it6625, u32 cfg_val) +{ + u8 mipi_data_type; + + lockdep_assert_held(&it6625->it6625_lock); + + dev_dbg(it6625->dev, "mipi_data_type = 0x%x", cfg_val); + + mipi_data_type = cfg_val & 0xFF; + it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, mipi_data_type); + it6625_update_config(it6625); +} + +static inline unsigned int fps_from_bt_timings(const struct v4l2_bt_timings *t) +{ + if (!V4L2_DV_BT_FRAME_HEIGHT(t) || !V4L2_DV_BT_FRAME_WIDTH(t)) + return 0; + + return DIV_ROUND_CLOSEST((unsigned int)t->pixelclock, + V4L2_DV_BT_FRAME_HEIGHT(t) * + V4L2_DV_BT_FRAME_WIDTH(t)); +} + +static void it6625_initial_setup(struct it6625 *it6625) +{ + int val = 0; + + guard(mutex)(&it6625->it6625_lock); + + /* + * REG_MIPI_CFG[0:2] lane count field: 1 lane -> 0, 2 lanes -> 1, + * 3 lanes (C-PHY only) -> 3, 4 lanes (D-PHY only) -> 3. + */ + switch (it6625->csi_lanes) { + case 1: + val = FIELD_PREP(M_MIPI_LANE, 0); + break; + case 2: + val = FIELD_PREP(M_MIPI_LANE, 1); + break; + default: + val = FIELD_PREP(M_MIPI_LANE, 3); + break; + } + + if (it6625->bus_type == V4L2_MBUS_CSI2_DPHY) + val |= FIELD_PREP(B_MIPI_DPHY, 1); + + if (it6625->port_num == 2) + val |= FIELD_PREP(B_MIPI_SPLIT, 1); + + it6625_write_byte(it6625, REG_MIPI_CFG, val); + it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, it6625->csi_format); + it6625_write_byte(it6625, REG_MIPI_CONTROL, 0x00); + it6625_write_byte(it6625, REG_RX_CFG, 0x00); + + it6625_set_bits(it6625, REG_HOST_CTRL_INT, B_CONFIG_UPDATE, B_CONFIG_UPDATE); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct it6625 *it6625 = adap->priv; + u8 cmds[2]; + + cmds[0] = CMD_SET_CEC_ENABLE; + cmds[1] = enable ? 1 : 0; + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); + + return 0; +} + +static void it6625_cec_reset_la(struct it6625 *it6625, bool keep_enabled) +{ + u8 cmds[2]; + + if (keep_enabled) { + cmds[0] = CMD_SET_CEC_LA; + cmds[1] = CEC_LOG_ADDR_UNREGISTERED; + } else { + cmds[0] = CMD_SET_CEC_ENABLE; + cmds[1] = 0; + } + + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); +} + +static int it6625_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) +{ + struct it6625 *it6625 = adap->priv; + u8 cmds[2] = {CMD_SET_CEC_LA, log_addr}; + + dev_dbg(it6625->dev, "%s: la=%d", __func__, log_addr); + + if (log_addr == CEC_LOG_ADDR_INVALID) { + it6625_cec_reset_la(it6625, adap->is_enabled); + return 0; + } + + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); + + return 0; +} + +static int it6625_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct it6625 *it6625 = adap->priv; + + guard(mutex)(&it6625->it6625_lock); + it6625_write_bytes(it6625, REG_CEC_TX_DATA, msg->msg, msg->len); + it6625_write_byte(it6625, REG_CEC_TX_DATA_LEN, msg->len); + it6625_set_bits(it6625, REG_HOST_CTRL_INT, B_CEC_SEND_DATA, B_CEC_SEND_DATA); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); + + return 0; +} + +static const struct cec_adap_ops it6625_cec_adap_ops = { + .adap_enable = it6625_cec_adap_enable, + .adap_log_addr = it6625_cec_adap_log_addr, + .adap_transmit = it6625_cec_adap_transmit, +}; + +static void it6625_cec_handler(struct it6625 *it6625, u8 intstatus) +{ + struct cec_msg rxmsg = {}; + int val = 0; + + if (intstatus & B_CEC_RX_RECEIVED) { + scoped_guard(mutex, &it6625->it6625_lock) { + val = it6625_read_byte(it6625, REG_CEC_RX_DATA_LEN); + if (val > 0 && val <= CEC_MAX_MSG_SIZE) + it6625_read_bytes(it6625, REG_CEC_RX_DATA, &rxmsg.msg[0], val); + it6625_write_byte(it6625, REG_CEC_RX_DATA_LEN, 0); + } + + if (val > 0 && val <= CEC_MAX_MSG_SIZE) { + rxmsg.len = val; + cec_received_msg(it6625->cec_adap, &rxmsg); + } else { + dev_err(it6625->dev, "invalid CEC RX length %d", val); + } + } + + if (intstatus & B_CEC_TX_UPDATE) { + val = it6625_read_byte(it6625, REG_CEC_STATUS); + if (val < 0) { + dev_err(it6625->dev, "read CEC status failed"); + return; + } + + if (val & BIT(0)) { + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_OK); + } else if (val & BIT(1)) { + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_NACK); + } else { + dev_info(it6625->dev, "unknown CEC status %02X", val); + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_NACK); + } + } +} + +static void it6625_irq_format_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + struct v4l2_dv_timings timings; + const struct v4l2_event it6625_ev_fmt = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + int ret; + + if (no_signal(it6625)) { + if (sd->devnode) + v4l2_subdev_notify_event(sd, &it6625_ev_fmt); + return; + } + + ret = it6625_get_detected_timings(it6625, &timings); + if (ret < 0) { + v4l2_dbg(1, debug, sd, "Failed to get detected timings"); + return; + } + + if (sd->devnode) + v4l2_subdev_notify_event(sd, &it6625_ev_fmt); +} + +static void it6625_irq_hdmi_audio_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + + it6625_s_ctrl_audio_sampling_rate(sd); + it6625_s_ctrl_audio_present(sd); +} + +static void it6625_get_timings(struct it6625 *it6625, + struct v4l2_dv_timings *timings) +{ + guard(mutex)(&it6625->it6625_lock); + *timings = it6625->timings; +} + +static void it6625_clear_timings(struct it6625 *it6625) +{ + guard(mutex)(&it6625->it6625_lock); + memset(&it6625->timings, 0, sizeof(it6625->timings)); +} + +static void it6625_irq_hdmi_5v_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + + it6625_clear_timings(it6625); + it6625_v4l2_sd_ctrl_update(sd); +} + +static void it6625_irq_hdcp_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + u8 cp_sts; + + cp_sts = it6625_read_byte(it6625, REG_RX_HDCP_STS); + v4l2_info(sd, "HDCP change to %02X", cp_sts); +} + +static void it6625_irq_infoframe_latch(struct it6625 *it6625) +{ + if (!it6625->if_active) + return; + + it6625->if_active = false; + + scoped_guard(mutex, &it6625->it6625_lock) { + it6625->if_snapshot_err = + it6625_read_bytes(it6625, REG_IF_DATA, + it6625->if_snapshot, 31); + } + it6625->if_snapshot_done = true; + complete(&it6625->if_latched); +} + +static void it6625_irq_emata_packet_latch(struct it6625 *it6625) +{ + u8 extend_packet[14]; + + it6625_read_bytes(it6625, REG_EMP_DATA, extend_packet, 13); +} + +static void it6625_irq_avmute_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + int val; + u8 avmute; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + if (val < 0) + return; + + avmute = (val & B_RX_AVMUTE) ? 1 : 0; + v4l2_info(sd, "AVMute change to %d", avmute); +} + +static void it6625_dispatch_int_status1(struct it6625 *it6625, int int_sts1) +{ + if (int_sts1 & B_HDMI_5V_CHG) + it6625_irq_hdmi_5v_change(it6625); + + if (int_sts1 & B_HDMI_VID_CHG) + it6625_irq_format_change(it6625); + + if (int_sts1 & B_HDMI_AUD_CHG) + it6625_irq_hdmi_audio_change(it6625); + + if (int_sts1 & B_HDMI_CP_CHG) + it6625_irq_hdcp_change(it6625); + + if (int_sts1 & B_HDMI_IF_LATCH) + it6625_irq_infoframe_latch(it6625); + + if (int_sts1 & B_HDMI_EMP) + it6625_irq_emata_packet_latch(it6625); +} + +static void it6625_dispatch_int_status2(struct it6625 *it6625, int int_sts2) +{ + struct v4l2_subdev *sd = &it6625->sd; + + if (int_sts2 & B_HDMI_AVI) + it6625_show_avi_infoframe(it6625); + + if (int_sts2 & B_HDMI_NO_AVI) + v4l2_info(sd, "AVI-info stopped"); + + if (int_sts2 & B_HDMI_AVMUTE_CHG) + it6625_irq_avmute_change(it6625); +} + +/* caller must already hold if_state_lock */ +static int it6625_drain_rx_int_status(struct it6625 *it6625) +{ + int val1, val2, err; + + val1 = it6625_read_byte(it6625, REG_RX_INT_STATUS1); + val2 = it6625_read_byte(it6625, REG_RX_INT_STATUS2); + + err = 0; + if (val1 < 0) + err = val1; + else if (val2 < 0) + err = val2; + + if (val1 >= 0) { + int werr = it6625_write_byte(it6625, REG_RX_INT_STATUS1, 0x00); + + if (werr && !err) + err = werr; + it6625_dispatch_int_status1(it6625, val1); + } + + if (val2 >= 0) { + int werr = it6625_write_byte(it6625, REG_RX_INT_STATUS2, 0x00); + + if (werr && !err) + err = werr; + it6625_dispatch_int_status2(it6625, val2); + } + + return err; +} + +/* caller must already hold if_state_lock */ +static int it6625_drain_interrupts(struct it6625 *it6625, bool *had_event) +{ + struct v4l2_subdev *sd = &it6625->sd; + int val, err; + + val = it6625_read_byte(it6625, REG_MCU_INTERRUPT); + if (val < 0) { + if (had_event) + *had_event = false; + return val; + } + if (had_event) + *had_event = val > 0; + if (val == 0) + return 0; + + err = it6625_write_byte(it6625, REG_MCU_INTERRUPT, val); + + v4l2_dbg(1, debug, sd, "%s: INT = 0x%02X", __func__, val); + + if (val & (B_CEC_RX_RECEIVED | B_CEC_TX_UPDATE) && it6625->cec_adap) + it6625_cec_handler(it6625, val & (B_CEC_RX_RECEIVED | B_CEC_TX_UPDATE)); + + if (val & B_SYS_INT_ACTIVE) { + int child_err = it6625_drain_rx_int_status(it6625); + + if (child_err && !err) + err = child_err; + } + + return err; +} + +static bool it6625_interrupt_handler(struct it6625 *it6625) +{ + bool had_event = false; + + guard(mutex)(&it6625->if_state_lock); + it6625_drain_interrupts(it6625, &had_event); + return had_event; +} + +static irqreturn_t it6625_irq_handler(int unused, void *data) +{ + struct it6625 *it6625 = data; + + return it6625_interrupt_handler(it6625) ? IRQ_HANDLED : IRQ_NONE; +} + +static void it6625_irq_poll_timer(struct timer_list *t) +{ + struct it6625 *it6625 = timer_container_of(it6625, t, timer); + unsigned int msecs; + + schedule_work(&it6625->polling_work); + /* + * If CEC is present, then we need to poll more frequently, + * otherwise we will miss CEC messages. + */ + msecs = it6625->cec_adap ? POLL_INTERVAL_CEC_MS : POLL_INTERVAL_MS; + mod_timer(&it6625->timer, jiffies + msecs_to_jiffies(msecs)); +} + +static void it6625_polling_work(struct work_struct *work) +{ + struct it6625 *it6625 = container_of(work, struct it6625, + polling_work); + + it6625_interrupt_handler(it6625); +} + +static const char *it6625_csi_format_name(u8 csi_format) +{ + switch (csi_format) { + case CSI_YUV422_8b: + return "YUV422 8bit"; + case CSI_RGB888: + return "RGB888 8bit"; + case CSI_YUV444_8b: + return "YUV444 8bit"; + default: + return "unknown"; + } +} + +static int it6625_log_status(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_dv_timings timings, configured_timings; + struct v4l2_bt_timings bt; + u8 csi_format; + + if (it6625_get_detected_timings(it6625, &timings)) + v4l2_info(sd, "No video detected"); + else + v4l2_print_dv_timings(sd->name, "Detected format: ", &timings, + true); + + it6625_get_timings(it6625, &configured_timings); + v4l2_print_dv_timings(sd->name, "Configured format: ", + &configured_timings, true); + + /* snapshot together so the reported pair was actually configured together */ + scoped_guard(mutex, &it6625->it6625_lock) { + csi_format = it6625->csi_format; + bt = it6625->timings.bt; + } + + v4l2_info(sd, "CSI format: %s @ %uHz", + it6625_csi_format_name(csi_format), + fps_from_bt_timings(&bt)); + + it6625_show_avi_infoframe(it6625); + + return 0; +} + +static int it6625_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + schedule_work(&it6625->polling_work); + *handled = true; + + return 0; +} + +static int it6625_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static int it6625_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct it6625 *it6625 = sd_to_6625(sd); + bool val; + + val = no_signal(it6625); + *status = 0; + *status |= val ? V4L2_IN_ST_NO_SIGNAL : 0; + *status |= val ? V4L2_IN_ST_NO_SYNC : 0; + + v4l2_dbg(1, debug, sd, "%s: status = 0x%x", __func__, *status); + + return 0; +} + +static int +it6625_update_timings_if_changed(struct it6625 *it6625, + const struct v4l2_dv_timings *timings) +{ + int ret; + + guard(mutex)(&it6625->it6625_lock); + if (v4l2_match_dv_timings(&it6625->timings, timings, 0, false)) { + ret = 0; + } else if (!v4l2_valid_dv_timings(timings, it6625_get_timings_cap(it6625), + NULL, NULL)) { + ret = -ERANGE; + } else { + it6625->timings = *timings; + ret = 1; + } + + return ret; +} + +static int it6625_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (timings->pad != 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, + it6625_get_timings_cap(it6625), NULL, NULL); +} + +static int it6625_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (cap->pad != 0) + return -EINVAL; + + *cap = *it6625_get_timings_cap(it6625); + + return 0; +} + +static int it6625_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + it6625_enable_stream(it6625, enable); + return 0; +} + +static int it6625_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + dev_dbg(sd->dev, "%s: index=%d", __func__, code->index); + + if (code->index >= ARRAY_SIZE(it6625_formats)) + return -EINVAL; + + dev_dbg(sd->dev, "%s: code=0x%08x", __func__, + it6625_formats[code->index].mbus_fmt_code); + code->code = it6625_formats[code->index].mbus_fmt_code; + + return 0; +} + +static int it6625_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (pad != 0) + return -EINVAL; + + cfg->type = it6625->bus_type; + cfg->bus.mipi_csi2.flags = 0; + cfg->bus.mipi_csi2.num_data_lanes = it6625->csi_lanes; + + return 0; +} + +static int it6625_pad_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int ret; + + if (pad != 0) + return -EINVAL; + + if (!timings) + return -EINVAL; + + if (debug) + v4l2_print_dv_timings(sd->name, __func__, timings, false); + + ret = it6625_update_timings_if_changed(it6625, timings); + if (ret == -ERANGE) { + v4l2_dbg(1, debug, sd, "%s: timings out of range", __func__); + return ret; + } + + if (ret == 0) + v4l2_dbg(1, debug, sd, "%s: no change", __func__); + + return 0; +} + +static int it6625_pad_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (pad != 0) + return -EINVAL; + + it6625_get_timings(it6625, timings); + + return 0; +} + +static int it6625_pad_query_dv_timings(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int ret; + + if (pad != 0) + return -EINVAL; + + ret = it6625_get_detected_timings(it6625, timings); + if (ret) + return ret; + + if (debug) + v4l2_print_dv_timings(sd->name, __func__, timings, false); + + if (!v4l2_valid_dv_timings(timings, it6625_get_timings_cap(it6625), NULL, NULL)) { + v4l2_dbg(1, debug, sd, "%s: timings out of range", __func__); + return -ERANGE; + } + + return 0; +} + +static inline u32 format_to_colorspace(u8 csi_format) +{ + switch (csi_format) { + case CSI_RGB444: + case CSI_RGB555: + case CSI_RGB565: + case CSI_RGB666: + case CSI_RGB888: + case CSI_RGB_10b: + case CSI_RGB_12b: + return V4L2_COLORSPACE_SRGB; + case CSI_YUV420_8b_L: + case CSI_YUV420_8b: + case CSI_YUV420_10b: + case CSI_YUV422_8b: + case CSI_YUV422_10b: + case CSI_YUV422_12b: + case CSI_YUV420_10b_L: + case CSI_YUV420_12b: + case CSI_YUV444_8b: + case CSI_YUV444_10b: + case CSI_YUV444_12b: + return V4L2_COLORSPACE_REC709; + default: + return 0; + } +} + +static int it6625_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_dv_timings timings; + + if (format->pad != 0) + return -EINVAL; + + it6625_get_timings(it6625, &timings); + format->format.width = timings.bt.width; + format->format.height = timings.bt.height; + format->format.field = timings.bt.interlaced == V4L2_DV_INTERLACED ? + V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + format->format.code = fmt->code; + format->format.colorspace = fmt->colorspace; + } else { + scoped_guard(mutex, &it6625->it6625_lock) { + format->format.colorspace = + format_to_colorspace(it6625->csi_format); + format->format.code = it6625->mbus_fmt_code; + } + } + + return 0; +} + +static int it6625_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int ret; + u32 mbus_fmt_code = format->format.code; + + ret = it6625_get_fmt(sd, sd_state, format); + format->format.code = mbus_fmt_code; + + if (ret) + return ret; + + ret = it6625_csi_mbus_code_idx(mbus_fmt_code); + + if (ret < 0) { + v4l2_dbg(1, debug, sd, + "%s: unsupported format code 0x%x, falling back to default", + __func__, mbus_fmt_code); + ret = 0; + mbus_fmt_code = it6625_formats[ret].mbus_fmt_code; + format->format.code = mbus_fmt_code; + } + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + fmt->code = format->format.code; + fmt->colorspace = format_to_colorspace(it6625_formats[ret].csi_format); + format->format.colorspace = fmt->colorspace; + v4l2_dbg(1, debug, sd, "%s: try format code = 0x%x", + __func__, format->format.code); + return 0; + } + + scoped_guard(mutex, &it6625->it6625_lock) { + it6625->csi_format = it6625_formats[ret].csi_format; + it6625->mbus_fmt_code = format->format.code; + it6625_enable_stream_locked(it6625, false); + it6625_set_mipi_config_locked(it6625, it6625->csi_format); + } + + format->format.colorspace = format_to_colorspace(it6625_formats[ret].csi_format); + + return 0; +} + +static int it6625_g_edid(struct v4l2_subdev *sd, + struct v4l2_subdev_edid *edid) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int err; + + if (edid->pad != 0) + return -EINVAL; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + guard(mutex)(&it6625->edid_lock); + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = it6625->edid_blocks; + return 0; + } + + if (it6625->edid_blocks == 0) + return -ENODATA; + + if (edid->start_block >= it6625->edid_blocks || edid->blocks == 0) + return -EINVAL; + + if (edid->blocks > it6625->edid_blocks - edid->start_block) + edid->blocks = it6625->edid_blocks - edid->start_block; + + err = it6625_read_edid(it6625, edid->edid, edid->start_block, + edid->blocks); + + return (err < 0) ? err : 0; +} + +static int it6625_s_edid(struct v4l2_subdev *sd, + struct v4l2_subdev_edid *edid) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int err; + u16 parent_pa = CEC_PHYS_ADDR_INVALID; + + if (edid->pad != 0) { + v4l2_err(sd, "invalid pad %d", edid->pad); + return -EINVAL; + } + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->start_block != 0) { + v4l2_err(sd, "start_block must be 0 for set edid"); + return -EINVAL; + } + + if (edid->blocks > EDID_NUM_BLOCKS_MAX) { + v4l2_err(sd, "too many edid blocks: %d", edid->blocks); + edid->blocks = EDID_NUM_BLOCKS_MAX; + return -E2BIG; + } + + if (edid->blocks != 0) { + u16 pa = v4l2_get_edid_phys_addr(edid->edid, + edid->blocks * 128, NULL); + err = v4l2_phys_addr_validate(pa, &parent_pa, NULL); + if (err) { + v4l2_err(sd, "invalid CEC physical address in EDID"); + return err; + } + } + + guard(mutex)(&it6625->edid_lock); + + it6625_disable_hpd(it6625); + cec_phys_addr_invalidate(it6625->cec_adap); + it6625->edid_blocks = 0; + + if (edid->blocks == 0) + return 0; + + err = it6625_write_edid(it6625, edid->edid, + edid->start_block, edid->blocks); + if (err < 0) { + v4l2_err(sd, "write edid failed"); + return err; + } + + it6625->edid_blocks = edid->blocks; + cec_s_phys_addr(it6625->cec_adap, parent_pa, false); + + if (hdmi_5v_power_present(it6625)) { + it6625_enable_hpd(it6625); + it6625_s_ctrl_detect_hdmi_5v(sd); + } else { + it6625_enable_auto_hpd(it6625); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops it6625_core_ops = { + .log_status = it6625_log_status, + .interrupt_service_routine = it6625_isr, + .subscribe_event = it6625_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops it6625_video_ops = { + .g_input_status = it6625_g_input_status, + .s_stream = it6625_s_stream, +}; + +static const struct v4l2_subdev_pad_ops it6625_pad_ops = { + .enum_mbus_code = it6625_enum_mbus_code, + .set_fmt = it6625_set_fmt, + .get_fmt = it6625_get_fmt, + .get_edid = it6625_g_edid, + .set_edid = it6625_s_edid, + .enum_dv_timings = it6625_enum_dv_timings, + .dv_timings_cap = it6625_dv_timings_cap, + .get_mbus_config = it6625_get_mbus_config, + .s_dv_timings = it6625_pad_s_dv_timings, + .g_dv_timings = it6625_pad_g_dv_timings, + .query_dv_timings = it6625_pad_query_dv_timings, +}; + +static const struct v4l2_subdev_ops it6625_ops = { + .core = &it6625_core_ops, + .video = &it6625_video_ops, + .pad = &it6625_pad_ops, +}; + +static int it6625_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(sd_state, 0); + + fmt->code = it6625_formats[0].mbus_fmt_code; + fmt->colorspace = format_to_colorspace(it6625_formats[0].csi_format); + + return 0; +} + +static const struct v4l2_subdev_internal_ops it6625_internal_ops = { + .init_state = it6625_init_state, +}; + +static const struct v4l2_ctrl_config it6625_ctrl_audio_sampling_rate = { + .id = V4L2_CID_IT6625_AUDIO_SAMPLING_RATE, + .name = "Audio Sampling Rate", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 1536000, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config it6625_ctrl_audio_present = { + .id = V4L2_CID_IT6625_AUDIO_PRESENT, + .name = "Audio Present", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static int it6625_v4l2_init_controls(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_ctrl_handler *hdl = &it6625->hdl; + + v4l2_ctrl_handler_init(hdl, 4); + it6625->ctrl_5v_detect = + v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_RX_POWER_PRESENT, + 0, 1, 0, 0); + + it6625->ctrl_audio_sampling_rate = + v4l2_ctrl_new_custom(hdl, + &it6625_ctrl_audio_sampling_rate, + NULL); + it6625->ctrl_audio_present = + v4l2_ctrl_new_custom(hdl, &it6625_ctrl_audio_present, NULL); + it6625->ctrl_link_freq = + v4l2_ctrl_new_int_menu(hdl, NULL, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(it6625_link_freq) - 1, + it6625->bus_type == V4L2_MBUS_CSI2_CPHY ? 1 : 0, + it6625_link_freq); + if (hdl->error) { + v4l2_err(sd, "Failed to initialize controls"); + v4l2_ctrl_handler_free(hdl); + return hdl->error; + } + + sd->ctrl_handler = hdl; + + return 0; +} + +static void it6625_regdump_print(struct seq_file *s, const u8 *reg_buf) +{ + int i; + + seq_puts(s, " 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F\n"); + + for (i = 0; i < 256; i++) { + if (i % 16 == 0) + seq_printf(s, "[%02X] ", i & 0xF0); + seq_printf(s, "0x%02X ", reg_buf[i]); + if (i % 16 == 15) + seq_putc(s, '\n'); + } +} + +static int it6625_mipi_reg_show(struct seq_file *s, void *data) +{ + struct it6625 *it6625 = s->private; + u8 reg_buf[256]; + int ret; + + scoped_guard(mutex, &it6625->it6625_lock) + ret = it6625_read_bytes(it6625, 0x00, reg_buf, sizeof(reg_buf)); + if (ret < 0) + return ret; + it6625_regdump_print(s, reg_buf); + + return 0; +} + +static int it6625_mipi_reg_open(struct inode *inode, struct file *file) +{ + return single_open(file, it6625_mipi_reg_show, inode->i_private); +} + +static ssize_t it6625_mipi_reg_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct it6625 *it6625 = file_inode(file)->i_private; + char buf[32] = {}; + unsigned int addr, val; + ssize_t len; + loff_t pos = 0; + + len = simple_write_to_buffer(buf, sizeof(buf) - 1, &pos, user_buf, count); + if (len < 0) + return len; + buf[len] = '\0'; + + if (sscanf(buf, "%X %X", &addr, &val) != 2) + return -EINVAL; + + scoped_guard(mutex, &it6625->it6625_lock) + it6625_write_byte(it6625, addr, val); + + return count; +} + +static const struct file_operations it6625_mipi_reg_fops = { + .owner = THIS_MODULE, + .open = it6625_mipi_reg_open, + .read = seq_read, + .write = it6625_mipi_reg_write, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * Maps a V4L2_DEBUGFS_IF_* framework flag to the actual HDMI packet-type + * header byte REG_IF_LATCH_HB expects to arm that InfoFrame type. + */ +static int it6625_if_packet_type(u32 type) +{ + switch (type) { + case V4L2_DEBUGFS_IF_AVI: + return 0x82; + case V4L2_DEBUGFS_IF_AUDIO: + return 0x84; + case V4L2_DEBUGFS_IF_SPD: + return 0x83; + case V4L2_DEBUGFS_IF_HDMI: + return 0x81; + default: + return -EINVAL; + } +} + +static ssize_t it6625_debugfs_if_read(u32 type, void *priv, struct file *filp, + char __user *ubuf, size_t count, + loff_t *ppos) +{ + struct v4l2_subdev *sd = priv; + struct it6625 *it6625 = sd_to_6625(sd); + u8 buf[32] = {}; + int packet_type; + int err, err_reset; + bool captured; + int len; + + packet_type = it6625_if_packet_type(type); + if (packet_type < 0) + return 0; + + guard(mutex)(&it6625->if_read_lock); + + scoped_guard(mutex, &it6625->if_state_lock) { + scoped_guard(mutex, &it6625->it6625_lock) + err = it6625_write_byte(it6625, REG_IF_LATCH_HB, 0); + if (err) + return err; + + err = it6625_drain_interrupts(it6625, NULL); + if (err) + return err; + + reinit_completion(&it6625->if_latched); + + it6625->if_active = true; + it6625->if_type = packet_type; + it6625->if_snapshot_done = false; + it6625->if_snapshot_err = 0; + + scoped_guard(mutex, &it6625->it6625_lock) + err = it6625_write_byte(it6625, REG_IF_LATCH_HB, + packet_type); + if (err) { + scoped_guard(mutex, &it6625->it6625_lock) + it6625_write_byte(it6625, REG_IF_LATCH_HB, 0); + it6625->if_active = false; + return err; + } + } + + wait_for_completion_timeout(&it6625->if_latched, msecs_to_jiffies(100)); + + scoped_guard(mutex, &it6625->if_state_lock) { + captured = it6625->if_snapshot_done; + it6625->if_active = false; + + scoped_guard(mutex, &it6625->it6625_lock) + err_reset = it6625_write_byte(it6625, REG_IF_LATCH_HB, 0); + + if (!captured) { + if (err_reset) + return err_reset; + return 0; + } + + if (!it6625->if_snapshot_err) { + buf[0] = packet_type; + memcpy(&buf[1], it6625->if_snapshot, 31); + } + + err = it6625->if_snapshot_err ? it6625->if_snapshot_err : err_reset; + if (err) + return err; + } + + len = buf[2] ? buf[2] + 4 : -ENOENT; + if (len > (int)sizeof(buf)) + len = -ENOENT; + if (len < 0) + return 0; + return simple_read_from_buffer(ubuf, count, ppos, buf, len); +} + +static void it6625_debugfs_init(struct it6625 *it6625, struct i2c_client *client) +{ + it6625->debugfs_dir = debugfs_create_dir(dev_name(&client->dev), NULL); + + debugfs_create_file("mipi_reg", 0600, it6625->debugfs_dir, it6625, + &it6625_mipi_reg_fops); + + it6625->infoframes = v4l2_debugfs_if_alloc(it6625->debugfs_dir, + V4L2_DEBUGFS_IF_AVI | V4L2_DEBUGFS_IF_AUDIO | + V4L2_DEBUGFS_IF_SPD | V4L2_DEBUGFS_IF_HDMI, + &it6625->sd, it6625_debugfs_if_read); +} + +static void it6625_init_data(struct it6625 *it6625) +{ + static struct v4l2_dv_timings default_timing = + V4L2_DV_BT_CEA_1920X1080P60; + + it6625->csi_lanes = 4; + it6625->port_num = 1; + it6625->bus_type = V4L2_MBUS_CSI2_DPHY; + it6625->csi_format = it6625_formats[0].csi_format; + it6625->mbus_fmt_code = it6625_formats[0].mbus_fmt_code; + it6625->timings = default_timing; + /* firmware ships with a verified 2-block default EDID in EDID RAM */ + it6625->edid_blocks = 2; +} + +static int it6625_parse_endpoint(struct it6625 *it6625) +{ + struct device *dev = it6625->dev; + /* + * Pre-setting bus_type here makes v4l2_fwnode_endpoint_alloc_parse() + * treat it as a hard requirement and reject any endpoint whose DT + * bus-type disagrees, so this must stay V4L2_MBUS_UNKNOWN to let it + * autodetect C-PHY vs D-PHY from the endpoint itself. + */ + struct v4l2_fwnode_endpoint endpoint = { .bus_type = V4L2_MBUS_UNKNOWN }; + struct device_node *ep = NULL; + unsigned int max_lanes; + unsigned int port; + int ret; + + /* + * port@0 and port@1 are the two CSI-2 output ports MIPI0/MIPI1 + * (port@2 is the HDMI input). This chip series can drive both + * simultaneously in split or mirror mode, so port_num counts how + * many of MIPI0/MIPI1 have an endpoint wired up. This driver only + * wires up a single source pad, so lane/bus-type configuration is + * parsed from whichever of the two is found first. + */ + it6625->port_num = 0; + for (port = 0; port < 2; port++) { + struct device_node *port_ep = + of_graph_get_endpoint_by_regs(dev->of_node, port, -1); + + if (!port_ep) + continue; + + it6625->port_num++; + if (!ep) + ep = port_ep; + else + of_node_put(port_ep); + } + + if (!ep) { + it6625->port_num = 1; + dev_dbg(dev, "no CSI-2 endpoint node found, using default %u CSI lanes", + it6625->csi_lanes); + return 0; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(ep), &endpoint); + of_node_put(ep); + if (ret) { + dev_err(dev, "failed to parse endpoint: %d", ret); + return ret; + } + + if (endpoint.bus_type != V4L2_MBUS_CSI2_DPHY && + endpoint.bus_type != V4L2_MBUS_CSI2_CPHY) { + dev_err(dev, "unsupported bus type %d, expected CSI-2 D-PHY or C-PHY", + endpoint.bus_type); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + if (endpoint.bus_type == V4L2_MBUS_CSI2_CPHY && + it6625->chip_type != IT6626_CHIP) { + dev_err(dev, "IT6625 does not support C-PHY, only IT6626 does"); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + max_lanes = (endpoint.bus_type == V4L2_MBUS_CSI2_CPHY) ? 3 : 4; + + if (endpoint.bus.mipi_csi2.num_data_lanes == 0 || + endpoint.bus.mipi_csi2.num_data_lanes > max_lanes) { + dev_err(dev, + "invalid number of CSI data lanes: %u (max %u for this bus type)", + endpoint.bus.mipi_csi2.num_data_lanes, max_lanes); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + it6625->csi_lanes = endpoint.bus.mipi_csi2.num_data_lanes; + it6625->bus_type = endpoint.bus_type; + v4l2_fwnode_endpoint_free(&endpoint); + + return 0; +} + +static int it6625_parse_dt(struct it6625 *it6625) +{ + return it6625_parse_endpoint(it6625); +} + +static int it6625_init_v4l2_subdev(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + int err; + + sd->dev = it6625->dev; + + v4l2_i2c_subdev_init(sd, it6625->i2c_client, &it6625_ops); + sd->internal_ops = &it6625_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + if (it6625_v4l2_init_controls(sd)) { + dev_err(it6625->dev, "Failed to initialize v4l2 controls"); + return -ENOMEM; + } + + it6625->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + err = media_entity_pads_init(&sd->entity, 1, &it6625->pad); + if (err < 0) { + dev_err(it6625->dev, "%s %d err=%d", __func__, __LINE__, err); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return err; + } + + return 0; +} + +static int it6625_check_device(struct it6625 *it6625) +{ + static const u8 chip_ids[][2] = { + { 0x66, 0x25 }, + { 0x66, 0x26 }, + }; + int chip_id0, chip_id1; + + chip_id0 = it6625_read_byte(it6625, REG_CHIP_ID_0); + chip_id1 = it6625_read_byte(it6625, REG_CHIP_ID_1); + if (chip_id0 != chip_ids[it6625->chip_type][0] || + chip_id1 != chip_ids[it6625->chip_type][1]) { + dev_err(it6625->dev, + "chip ID mismatch: got 0x%02x%02x, expected 0x%02x%02x", + chip_id0, chip_id1, + chip_ids[it6625->chip_type][0], + chip_ids[it6625->chip_type][1]); + return -ENODEV; + } + + return 0; +} + +static int it6625_probe(struct i2c_client *client) +{ + struct it6625 *it6625; + struct v4l2_subdev *sd; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + it6625 = devm_kzalloc(&client->dev, sizeof(struct it6625), GFP_KERNEL); + if (!it6625) + return -ENOMEM; + + it6625->chip_type = (uintptr_t)i2c_get_match_data(client); + + it6625->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(it6625->reset_gpio)) + return PTR_ERR(it6625->reset_gpio); + + if (it6625->reset_gpio) { + usleep_range(1000, 2000); + gpiod_set_value_cansleep(it6625->reset_gpio, 0); + usleep_range(10000, 11000); + } + + err = it6625_regmap_i2c_init(client, it6625); + if (err) + return err; + + err = it6625_check_device(it6625); + if (err) + return err; + + it6625_init_data(it6625); + + err = it6625_parse_dt(it6625); + if (err) + return err; + + mutex_init(&it6625->it6625_lock); + mutex_init(&it6625->edid_lock); + mutex_init(&it6625->if_read_lock); + mutex_init(&it6625->if_state_lock); + init_completion(&it6625->if_latched); + INIT_DELAYED_WORK(&it6625->hpd_delayed_work, it6625_hpd_delayed_work); + INIT_WORK(&it6625->polling_work, it6625_polling_work); + + if (client->irq) { + err = devm_request_threaded_irq(&client->dev, client->irq, + NULL, it6625_irq_handler, + IRQF_ONESHOT | + IRQF_NO_AUTOEN, + "it6625", it6625); + if (err) + goto err_clean_work_queues; + } else { + dev_info(it6625->dev, "no IRQ, falling back to polling"); + timer_setup(&it6625->timer, it6625_irq_poll_timer, 0); + } + + sd = &it6625->sd; + err = it6625_init_v4l2_subdev(it6625); + if (err) + goto err_clean_work_queues; + + err = v4l2_ctrl_handler_setup(sd->ctrl_handler); + if (err) + goto err_clean_hdl; + + it6625->cec_adap = cec_allocate_adapter(&it6625_cec_adap_ops, + it6625, dev_name(it6625->dev), + CEC_CAP_DEFAULTS | + CEC_CAP_MONITOR_ALL | + CEC_CAP_PHYS_ADDR, + 1); + if (IS_ERR(it6625->cec_adap)) { + err = PTR_ERR(it6625->cec_adap); + dev_err(it6625->dev, "%s %d", __func__, __LINE__); + goto err_clean_hdl; + } + + err = cec_register_adapter(it6625->cec_adap, &client->dev); + if (err < 0) { + dev_err(it6625->dev, "%s: failed to register the cec device", __func__); + cec_delete_adapter(it6625->cec_adap); + it6625->cec_adap = NULL; + goto err_clean_hdl; + } + + it6625_debugfs_init(it6625, client); + + it6625_initial_setup(it6625); + it6625_v4l2_sd_ctrl_update(sd); + + err = v4l2_async_register_subdev(sd); + if (err < 0) { + dev_err(it6625->dev, "%s %d err=%d", __func__, __LINE__, err); + goto err_clean_debugfs; + } + + if (client->irq) + enable_irq(client->irq); + else + mod_timer(&it6625->timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS)); + + return 0; + +err_clean_debugfs: + v4l2_debugfs_if_free(it6625->infoframes); + debugfs_remove_recursive(it6625->debugfs_dir); + cec_unregister_adapter(it6625->cec_adap); +err_clean_hdl: + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&it6625->hdl); + +err_clean_work_queues: + if (!client->irq) + timer_shutdown_sync(&it6625->timer); + cancel_work_sync(&it6625->polling_work); + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + mutex_destroy(&it6625->it6625_lock); + mutex_destroy(&it6625->edid_lock); + mutex_destroy(&it6625->if_read_lock); + mutex_destroy(&it6625->if_state_lock); + return err; +} + +static void it6625_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct it6625 *it6625 = sd_to_6625(sd); + + v4l2_debugfs_if_free(it6625->infoframes); + + if (client->irq) + disable_irq(client->irq); + else + timer_shutdown_sync(&it6625->timer); + + cancel_work_sync(&it6625->polling_work); + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + + v4l2_async_unregister_subdev(sd); + v4l2_device_unregister_subdev(sd); + + debugfs_remove_recursive(it6625->debugfs_dir); + cec_unregister_adapter(it6625->cec_adap); + mutex_destroy(&it6625->it6625_lock); + mutex_destroy(&it6625->edid_lock); + mutex_destroy(&it6625->if_read_lock); + mutex_destroy(&it6625->if_state_lock); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&it6625->hdl); +} + +static const struct i2c_device_id it6625_id[] = { + { .name = "it6625", .driver_data = IT6625_CHIP }, + { .name = "it6626", .driver_data = IT6626_CHIP }, + {} +}; +MODULE_DEVICE_TABLE(i2c, it6625_id); + +static const struct of_device_id it6625_of_match[] = { + { .compatible = "ite,it6625", .data = (void *)IT6625_CHIP }, + { .compatible = "ite,it6626", .data = (void *)IT6626_CHIP }, + {}, +}; +MODULE_DEVICE_TABLE(of, it6625_of_match); + +static struct i2c_driver it6625_driver = { + .driver = { + .name = "it6625", + .of_match_table = it6625_of_match, + }, + .probe = it6625_probe, + .remove = it6625_remove, + .id_table = it6625_id, +}; +module_i2c_driver(it6625_driver); + +MODULE_DESCRIPTION("iTE it6625/it6626 HDMI to MIPI CSI bridge driver"); +MODULE_AUTHOR("Hermes Wu "); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/it6625.h b/include/uapi/linux/it6625.h new file mode 100644 index 000000000000..a895658aaf6a --- /dev/null +++ b/include/uapi/linux/it6625.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Controls header for IT6625/IT6626 driver + */ + +#ifndef _UAPI_LINUX_IT6625_H +#define _UAPI_LINUX_IT6625_H + +#include + +/* + * Currently detected HDMI audio sampling rate, in Hz. Read-only. + * 0 means the rate is unavailable/unknown: no audio is currently + * present on the input, the hardware reported a sample-rate id this + * driver doesn't recognize, or the status read itself failed. Never a + * literal 0 Hz sample rate. + */ +#define V4L2_CID_IT6625_AUDIO_SAMPLING_RATE (V4L2_CID_USER_IT6625_BASE + 0) + +/* + * Whether HDMI audio is currently present on the input. Read-only. + */ +#define V4L2_CID_IT6625_AUDIO_PRESENT (V4L2_CID_USER_IT6625_BASE + 1) + +#endif /* _UAPI_LINUX_IT6625_H */ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 42b4f82afeb8..3a2c0a7ba5e2 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -240,6 +240,12 @@ enum v4l2_colorfx { */ #define V4L2_CID_USER_DCMIPP_BASE (V4L2_CID_USER_BASE + 0x1240) +/* + * The base for IT6625/IT6626 driver controls. + * We reserve 16 controls for this driver. + */ +#define V4L2_CID_USER_IT6625_BASE (V4L2_CID_USER_BASE + 0x1250) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ From 4f4b6a9220f0401ae84bd2644aabd7221930c1c5 Mon Sep 17 00:00:00 2001 From: Tharit Tangkijwanichakul Date: Tue, 11 Aug 2026 17:04:14 +0000 Subject: [PATCH 177/596] media: v4l2-ctrls: default HEVC SPS chroma format to 4:2:0 The default value of a compound control is zero initialized when no explicit default is provided. For V4L2_CTRL_TYPE_HEVC_SPS this results in chroma_format_idc being set to 0, which represents monochrome video. Most stateless HEVC decoders do not support monochrome video. In particular, the VDPU38x variant of rkvdec fails v4l2-compliance in two tests related to V4L2_CID_STATELESS_HEVC_SPS. The Control ioctls test reads the control with GET_EXT_CTRLS and writes the same value back; TRY_EXT_CTRLS then returns -EINVAL because the VDPU38x rkvdec_hevc_validate_sps() rejects sps->chroma_format_idc == 0. v4l2-compliance 1.33.0-5491, 64 bits, 64-bit time_t v4l2-compliance SHA: b32589c51481 2026-07-16 08:51:38 Control ioctls: fail: v4l2-test-controls.cpp(942): try_ext_ctrls returned an error (22) test VIDIOC_G/S/TRY_EXT_CTRLS: FAIL Buffer ioctls: fail: v4l2-test-buffers.cpp(3102): node->streamon(q.g_type()) fail: v4l2-test-buffers.cpp(3157): testBlockingDQBuf(node, m2m_q) test blocking wait: FAIL Set the common HEVC SPS compound control default to chroma_format_idc = 1, corresponding to 4:2:0. Fixes: c9a59dc2acc7 ("media: rkvdec: Add HEVC support for the VDPU381 variant") Cc: stable@kernel.org Signed-off-by: Tharit Tangkijwanichakul Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 5b8a594fb9e2..9caca56478d1 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -115,6 +115,7 @@ static void std_init_compound(const struct v4l2_ctrl *ctrl, u32 idx, struct v4l2_ctrl_fwht_params *p_fwht_params; struct v4l2_ctrl_h264_scaling_matrix *p_h264_scaling_matrix; struct v4l2_ctrl_av1_sequence *p_av1_sequence; + struct v4l2_ctrl_hevc_sps *p_hevc_sps; void *p = ptr.p + idx * ctrl->elem_size; if (ctrl->p_def.p_const) @@ -188,6 +189,12 @@ static void std_init_compound(const struct v4l2_ctrl *ctrl, u32 idx, */ memset(p_h264_scaling_matrix, 16, sizeof(*p_h264_scaling_matrix)); break; + case V4L2_CTRL_TYPE_HEVC_SPS: + p_hevc_sps = p; + + /* 4:2:0 */ + p_hevc_sps->chroma_format_idc = 1; + break; } } From e22872703386c3885d8c6bd595124587bc4e5081 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 14 Aug 2026 16:08:02 +0800 Subject: [PATCH 178/596] media: require two-byte Cypress type 04 records Both Cypress Intel HEX parsers read two address bytes from type 04 records. Generic record framing permits any record length, so a short record can make the parser consume the checksum or bytes beyond the firmware record. A longer record also violates the Intel HEX type 04 layout. Require the type 04 payload length to be exactly two bytes in both implementations. Fixes: 79a63c60a6a2 ("[media] media: move dvb-usb-v2/cypress_firmware.c to media/common") Fixes: 776338e121b9 ("[PATCH] dvb: Add generalized dvb-usb driver") Cc: stable@kernel.org Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou Signed-off-by: Hans Verkuil --- drivers/media/common/cypress_firmware.c | 2 ++ drivers/media/usb/dvb-usb/dvb-usb-firmware.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/media/common/cypress_firmware.c b/drivers/media/common/cypress_firmware.c index 66274fdf5243..d0f66ed01c4b 100644 --- a/drivers/media/common/cypress_firmware.c +++ b/drivers/media/common/cypress_firmware.c @@ -59,6 +59,8 @@ static int cypress_get_hexline(const struct firmware *fw, if (hx->type == 0x04) { /* b[4] and b[5] are the Extended linear address record data * field */ + if (hx->len != 2) + return -EINVAL; hx->addr |= (b[4] << 24) | (b[5] << 16); } diff --git a/drivers/media/usb/dvb-usb/dvb-usb-firmware.c b/drivers/media/usb/dvb-usb/dvb-usb-firmware.c index 0fb3fa6100e4..675d9d1d4f47 100644 --- a/drivers/media/usb/dvb-usb/dvb-usb-firmware.c +++ b/drivers/media/usb/dvb-usb/dvb-usb-firmware.c @@ -141,6 +141,8 @@ int dvb_usb_get_hexline(const struct firmware *fw, struct hexline *hx, if (hx->type == 0x04) { /* b[4] and b[5] are the Extended linear address record data field */ + if (hx->len != 2) + return -EINVAL; hx->addr |= (b[4] << 24) | (b[5] << 16); /* hx->len -= 2; data_offs += 2; */ From df9e11101656fa6accbd85a9d6bb056aa78c3c18 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 21 Aug 2026 12:04:28 +0200 Subject: [PATCH 179/596] media: cec: meson: ao-cec-g12a: Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. Make sure all members are fully initialized, to avoid such bugs, and to prevent future breakage when converting drivers to a different method for specifying the parents. Signed-off-by: Geert Uytterhoeven Signed-off-by: Hans Verkuil --- drivers/media/cec/platform/meson/ao-cec-g12a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/cec/platform/meson/ao-cec-g12a.c b/drivers/media/cec/platform/meson/ao-cec-g12a.c index b175be3f2bf4..55bd0d416460 100644 --- a/drivers/media/cec/platform/meson/ao-cec-g12a.c +++ b/drivers/media/cec/platform/meson/ao-cec-g12a.c @@ -334,7 +334,7 @@ static int meson_ao_cec_g12a_setup_clk(struct meson_ao_cec_g12a_device *ao_cec) { struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk; struct device *dev = &ao_cec->pdev->dev; - struct clk_init_data init; + struct clk_init_data init = {}; const char *parent_name; struct clk *clk; char *name; From f99360acdb773eed86df6e3a4751a50507908511 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:36:58 +0530 Subject: [PATCH 180/596] media: synopsys: hdmirx: Use devm_of_reserved_mem_device_init() Replace the hand-rolled devm_hdmirx_of_reserved_mem_device_release() devres action with devm_of_reserved_mem_device_init(), which handles the cleanup automatically. This removes both the wrapper function and the devm_add_action_or_reset() call. Reviewed-by: Dmitry Osipenko Signed-off-by: Mukesh Ojha Signed-off-by: Hans Verkuil --- .../media/platform/synopsys/hdmirx/snps_hdmirx.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c index 978bc91d3257..3aff624c211a 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c @@ -2274,11 +2274,6 @@ static const struct hdmirx_cec_ops hdmirx_cec_ops = { .read = hdmirx_readl, }; -static void devm_hdmirx_of_reserved_mem_device_release(void *dev) -{ - of_reserved_mem_device_release(dev); -} - static int hdmirx_parse_dt(struct snps_hdmirx_dev *hdmirx_dev) { struct device *dev = hdmirx_dev->dev; @@ -2332,16 +2327,9 @@ static int hdmirx_parse_dt(struct snps_hdmirx_dev *hdmirx_dev) if (!device_property_read_bool(dev, "hpd-is-active-low")) hdmirx_dev->hpd_trigger_level_high = true; - ret = of_reserved_mem_device_init(dev); - if (ret) { + ret = devm_of_reserved_mem_device_init(dev); + if (ret) dev_warn(dev, "no reserved memory for HDMIRX, use default CMA\n"); - } else { - ret = devm_add_action_or_reset(dev, - devm_hdmirx_of_reserved_mem_device_release, - dev); - if (ret) - return ret; - } return 0; } From 2a9511d64c3fdf95b52ffafc285c3d9275724c56 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:36:59 +0530 Subject: [PATCH 181/596] media: aspeed: Use devm_of_reserved_mem_device_init() Switch to devm_of_reserved_mem_device_init() so the reserved memory region is released automatically on probe failure or device removal. Remove the three explicit of_reserved_mem_device_release() calls and rename the error label from err_release_reserved_mem to err_unprepare_vclk to reflect that it now only unwinds clock state. Signed-off-by: Mukesh Ojha Signed-off-by: Hans Verkuil --- drivers/media/platform/aspeed/aspeed-video.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/media/platform/aspeed/aspeed-video.c b/drivers/media/platform/aspeed/aspeed-video.c index a292275f6b7b..7ba9314fdbd2 100644 --- a/drivers/media/platform/aspeed/aspeed-video.c +++ b/drivers/media/platform/aspeed/aspeed-video.c @@ -2267,19 +2267,19 @@ static int aspeed_video_init(struct aspeed_video *video) if (rc) goto err_unprepare_eclk; - of_reserved_mem_device_init(dev); + devm_of_reserved_mem_device_init(dev); rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); if (rc) { dev_err(dev, "Failed to set DMA mask\n"); - goto err_release_reserved_mem; + goto err_unprepare_vclk; } if (!aspeed_video_alloc_buf(video, &video->jpeg, VE_JPEG_HEADER_SIZE)) { dev_err(dev, "Failed to allocate DMA for JPEG header\n"); rc = -ENOMEM; - goto err_release_reserved_mem; + goto err_unprepare_vclk; } dev_info(video->dev, "alloc mem size(%d) at %pad for jpeg header\n", VE_JPEG_HEADER_SIZE, &video->jpeg.dma); @@ -2288,8 +2288,7 @@ static int aspeed_video_init(struct aspeed_video *video) return 0; -err_release_reserved_mem: - of_reserved_mem_device_release(dev); +err_unprepare_vclk: clk_unprepare(video->vclk); err_unprepare_eclk: clk_unprepare(video->eclk); @@ -2343,7 +2342,6 @@ static int aspeed_video_probe(struct platform_device *pdev) rc = aspeed_video_setup_video(video); if (rc) { aspeed_video_free_buf(video, &video->jpeg); - of_reserved_mem_device_release(&pdev->dev); clk_unprepare(video->vclk); clk_unprepare(video->eclk); return rc; @@ -2374,8 +2372,6 @@ static void aspeed_video_remove(struct platform_device *pdev) v4l2_device_unregister(v4l2_dev); aspeed_video_free_buf(video, &video->jpeg); - - of_reserved_mem_device_release(dev); } static struct platform_driver aspeed_video_driver = { From f9c94646c1baeeeaba57373ba5765d8ed5fff77a Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 3 Sep 2026 01:37:00 +0530 Subject: [PATCH 182/596] media: nuvoton: npcm-video: Use devm_of_reserved_mem_device_init() Switch to devm_of_reserved_mem_device_init() so the reserved memory region is released automatically on probe failure or device removal. Remove the four explicit of_reserved_mem_device_release() calls and collapse the now-empty err_release_mem label into err_free. Signed-off-by: Mukesh Ojha Signed-off-by: Hans Verkuil --- drivers/media/platform/nuvoton/npcm-video.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/media/platform/nuvoton/npcm-video.c b/drivers/media/platform/nuvoton/npcm-video.c index c28d9d7edd83..a6737c65d9b2 100644 --- a/drivers/media/platform/nuvoton/npcm-video.c +++ b/drivers/media/platform/nuvoton/npcm-video.c @@ -1719,17 +1719,15 @@ static int npcm_video_init(struct npcm_video *video) return rc; } - of_reserved_mem_device_init(dev); + devm_of_reserved_mem_device_init(dev); rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); if (rc) { dev_err(dev, "Failed to set DMA mask\n"); - of_reserved_mem_device_release(dev); return rc; } rc = npcm_video_ece_init(video); if (rc) { - of_reserved_mem_device_release(dev); dev_err(dev, "Failed to initialize ECE\n"); return rc; } @@ -1793,13 +1791,11 @@ static int npcm_video_probe(struct platform_device *pdev) rc = npcm_video_setup_video(video); if (rc) - goto err_release_mem; + goto err_free; dev_info(video->dev, "NPCM video driver probed\n"); return 0; -err_release_mem: - of_reserved_mem_device_release(&pdev->dev); err_free: kfree(video); return rc; @@ -1817,7 +1813,6 @@ static void npcm_video_remove(struct platform_device *pdev) if (video->ece.enable) npcm_video_ece_stop(video); kfree(video); - of_reserved_mem_device_release(dev); } static const struct of_device_id npcm_video_match[] = { From 4abbba28924962b406d5b83968727d0c88ba7806 Mon Sep 17 00:00:00 2001 From: Aleksandr Chubuchnyi Date: Thu, 3 Sep 2026 16:50:58 +0300 Subject: [PATCH 183/596] MAINTAINERS: add Tegra CSI bindings to TEGRA VIDEO DRIVER The tegra20 and tegra210 CSI bindings are not listed in any MAINTAINERS entry, so get_maintainer.pl does not match TEGRA VIDEO DRIVER for changes to them and reports neither linux-media nor two of the four maintainers of that entry. The entry already covers the VI and VIP bindings, and csi.c binds to the compatibles documented by both files. Add them as well. Signed-off-by: Aleksandr Chubuchnyi Reviewed-by: Luca Ceresoli Signed-off-by: Hans Verkuil --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index f515e1473af9..4cc4a2dc6d30 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26978,9 +26978,11 @@ M: Luca Ceresoli L: linux-media@vger.kernel.org L: linux-tegra@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-csi.yaml F: Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml F: Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-vi.yaml F: Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-vip.yaml +F: Documentation/devicetree/bindings/display/tegra/nvidia,tegra210-csi.yaml F: drivers/staging/media/tegra-video/ TEGRA XUSB PADCTL DRIVER From 75352cab0e264560da82420aaefdb818140f9cfe Mon Sep 17 00:00:00 2001 From: Muhammad Haris Awan Date: Fri, 4 Sep 2026 15:56:25 +0500 Subject: [PATCH 184/596] media: cx25840: return -EOPNOTSUPP instead of WARN_ON in cx25840_init cx25840_init() implements generic mode video output configuration only for cx2584x chips. For other chips, it triggers a WARN_ON(1) and returns 0, falsely indicating successful initialization while leaving the chip unconfigured and marking generic_mode as true. Using WARN_ON() for unsupported hardware variants is discouraged as it triggers kernel warnings and syzbot alerts on faulty or unsupported devices. Return -EOPNOTSUPP early if the chip is not a cx2584x variant so callers (such as cxusb_medion_register_analog_subdevs()) can handle the error cleanly. Reported-by: syzbot+9123948aef13fe92d706@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9123948aef13fe92d706 Tested-by: syzbot+9123948aef13fe92d706@syzkaller.appspotmail.com Signed-off-by: Muhammad Haris Awan Signed-off-by: Hans Verkuil [hverkuil: fixed the mangled patch] --- drivers/media/i2c/cx25840/cx25840-core.c | 38 +++++++++++------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c index 8b7dd43ed208..159231b80daf 100644 --- a/drivers/media/i2c/cx25840/cx25840-core.c +++ b/drivers/media/i2c/cx25840/cx25840-core.c @@ -2289,29 +2289,27 @@ static int cx25840_init(struct v4l2_subdev *sd, u32 val) { struct cx25840_state *state = to_state(sd); + if (!is_cx2584x(state)) + return -EOPNOTSUPP; + state->generic_mode = true; - if (is_cx2584x(state)) { - /* set datasheet video output defaults */ - state->vid_config = CX25840_VCONFIG_FMT_BT656 | - CX25840_VCONFIG_RES_8BIT | - CX25840_VCONFIG_VBIRAW_DISABLED | - CX25840_VCONFIG_ANCDATA_ENABLED | - CX25840_VCONFIG_TASKBIT_ONE | - CX25840_VCONFIG_ACTIVE_HORIZONTAL | - CX25840_VCONFIG_VALID_NORMAL | - CX25840_VCONFIG_HRESETW_NORMAL | - CX25840_VCONFIG_CLKGATE_NONE | - CX25840_VCONFIG_DCMODE_DWORDS | - CX25840_VCONFIG_IDID0S_NORMAL | - CX25840_VCONFIG_VIPCLAMP_DISABLED; + /* set datasheet video output defaults */ + state->vid_config = CX25840_VCONFIG_FMT_BT656 | + CX25840_VCONFIG_RES_8BIT | + CX25840_VCONFIG_VBIRAW_DISABLED | + CX25840_VCONFIG_ANCDATA_ENABLED | + CX25840_VCONFIG_TASKBIT_ONE | + CX25840_VCONFIG_ACTIVE_HORIZONTAL | + CX25840_VCONFIG_VALID_NORMAL | + CX25840_VCONFIG_HRESETW_NORMAL | + CX25840_VCONFIG_CLKGATE_NONE | + CX25840_VCONFIG_DCMODE_DWORDS | + CX25840_VCONFIG_IDID0S_NORMAL | + CX25840_VCONFIG_VIPCLAMP_DISABLED; - /* add additional settings */ - cx25840_vconfig_add(state, val); - } else { - /* TODO: generic mode needs to be developed for other chips */ - WARN_ON(1); - } + /* add additional settings */ + cx25840_vconfig_add(state, val); return 0; } From e3f835eefda86ddd46fc3b9b224324518a27c5ce Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:04:42 +0530 Subject: [PATCH 185/596] media: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Hans Verkuil --- drivers/media/dvb-frontends/drxd_map_firm.h | 4 ++-- drivers/media/i2c/ccs/ccs-data.h | 4 ++-- drivers/media/i2c/hi847.c | 2 +- drivers/media/i2c/ov772x.c | 2 +- drivers/media/i2c/wm8739.c | 2 +- drivers/media/pci/cx18/cx23418.h | 2 +- drivers/media/pci/saa7146/mxb.c | 4 ++-- drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h | 2 +- drivers/media/platform/mediatek/vcodec/decoder/vdec_ipi_msg.h | 4 ++-- drivers/media/platform/renesas/rcar_drif.c | 2 +- drivers/media/radio/si4713/si4713.c | 2 +- drivers/media/usb/dvb-usb/dib0700_core.c | 2 +- drivers/media/usb/gspca/w996Xcf.c | 2 +- .../atomisp/pci/isp/kernels/s3a/s3a_1.0/ia_css_s3a_types.h | 4 ++-- drivers/staging/media/av7110/av7110.c | 2 +- 15 files changed, 20 insertions(+), 20 deletions(-) diff --git a/drivers/media/dvb-frontends/drxd_map_firm.h b/drivers/media/dvb-frontends/drxd_map_firm.h index bdcc63576df1..a6585e53d879 100644 --- a/drivers/media/dvb-frontends/drxd_map_firm.h +++ b/drivers/media/dvb-frontends/drxd_map_firm.h @@ -10,8 +10,8 @@ /* * Note: originally, this file contained 12000+ lines of data - * Probably a few lines for every firwmare assembler instruction. However, - * only a few defines were actually used. So, removed all uneeded lines. + * Probably a few lines for every firmware assembler instruction. However, + * only a few defines were actually used. So, removed all unneeded lines. * If ever needed, the other lines can be easily obtained via git history. */ diff --git a/drivers/media/i2c/ccs/ccs-data.h b/drivers/media/i2c/ccs/ccs-data.h index 638df69804ec..522466b81929 100644 --- a/drivers/media/i2c/ccs/ccs-data.h +++ b/drivers/media/i2c/ccs/ccs-data.h @@ -160,7 +160,7 @@ struct ccs_pdaf_pix_loc_pixel_desc_group { * @main_offset_y: Start Y coordinate of PDAF pixel blocks * @global_pdaf_type: PDAF pattern type * @block_width: Width of a block in pixels - * @block_height: Heigth of a block in pixels + * @block_height: Height of a block in pixels * @num_block_desc_groups: Number of block descriptor groups * @block_desc_groups: Block descriptor groups * @num_pixel_desc_grups: Number of pixel descriptor groups @@ -197,7 +197,7 @@ struct ccs_pdaf_pix_loc { * @module_rules: Rules for the module * @sensor_pdaf: PDAF data for the sensor * @module_pdaf: PDAF data for the module - * @license_length: Lenght of the license data + * @license_length: Length of the license data * @license: License data * @end: Whether or not there's an end block * @backing: Raw data, pointed to from elsewhere so keep it around diff --git a/drivers/media/i2c/hi847.c b/drivers/media/i2c/hi847.c index def01aa07b2f..710328d7984f 100644 --- a/drivers/media/i2c/hi847.c +++ b/drivers/media/i2c/hi847.c @@ -2187,7 +2187,7 @@ struct hi847 { /* Current mode */ const struct hi847_mode *cur_mode; - /* To serialize asynchronus callbacks */ + /* To serialize asynchronous callbacks */ struct mutex mutex; }; diff --git a/drivers/media/i2c/ov772x.c b/drivers/media/i2c/ov772x.c index be3ba284ee0b..041f3b02e222 100644 --- a/drivers/media/i2c/ov772x.c +++ b/drivers/media/i2c/ov772x.c @@ -122,7 +122,7 @@ #define LC_COEFB 0x4B /* Lens B channel compensation coefficient */ #define LC_COEFR 0x4C /* Lens R channel compensation coefficient */ -#define FIXGAIN 0x4D /* Analog fix gain amplifer */ +#define FIXGAIN 0x4D /* Analog fix gain amplifier */ #define AREF0 0x4E /* Sensor reference control */ #define AREF1 0x4F /* Sensor reference current control */ #define AREF2 0x50 /* Analog reference control */ diff --git a/drivers/media/i2c/wm8739.c b/drivers/media/i2c/wm8739.c index db62bafb447c..9eaf48f1c3a2 100644 --- a/drivers/media/i2c/wm8739.c +++ b/drivers/media/i2c/wm8739.c @@ -217,7 +217,7 @@ static int wm8739_probe(struct i2c_client *client) /* reset */ wm8739_write(sd, R15, 0x00); - /* filter setting, high path, offet clear */ + /* filter setting, high path, offset clear */ wm8739_write(sd, R5, 0x000); /* ADC, OSC, Power Off mode Disable */ wm8739_write(sd, R6, 0x000); diff --git a/drivers/media/pci/cx18/cx23418.h b/drivers/media/pci/cx18/cx23418.h index 22486f39bcda..4ee45be757cb 100644 --- a/drivers/media/pci/cx18/cx23418.h +++ b/drivers/media/pci/cx18/cx23418.h @@ -24,7 +24,7 @@ #define CX18_CREATE_TASK (MGR_CMD_MASK | 0x0001) /* Description: This command destroys an instance of a task - IN[0] - Task handle. Hanlde of the task to destroy + IN[0] - Task handle. Handle of the task to destroy ReturnCode - One of the ERR_SYS_... */ #define CX18_DESTROY_TASK (MGR_CMD_MASK | 0x0002) diff --git a/drivers/media/pci/saa7146/mxb.c b/drivers/media/pci/saa7146/mxb.c index d759e8a87e24..129d41c1862b 100644 --- a/drivers/media/pci/saa7146/mxb.c +++ b/drivers/media/pci/saa7146/mxb.c @@ -37,7 +37,7 @@ /* global variable */ static int mxb_num; -/* initial frequence the tuner will be tuned to. +/* initial frequency the tuner will be tuned to. in verden (lower saxony, germany) 4148 is a channel called "phoenix" */ static int freq = 4148; @@ -429,7 +429,7 @@ static int mxb_init_done(struct saa7146_dev* dev) /* some stuff is done via direct write to the registers */ /* this is ugly, but because of the fact that this is completely - hardware dependend, it should be done directly... */ + hardware dependent, it should be done directly... */ saa7146_write(dev, DD1_STREAM_B, 0x00000000); saa7146_write(dev, DD1_INIT, 0x02000200); saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h b/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h index b810c96695c8..9744e00b0066 100644 --- a/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h @@ -56,7 +56,7 @@ struct mdp_ipi_comm { * @ipi_id : IPI_MDP * @ap_inst : AP mtk_mdp_vpu address * @vpu_inst_addr : VPU MDP instance address - * @status : VPU exeuction result + * @status : VPU execution result */ struct mdp_ipi_comm_ack { uint32_t msg_id; diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec_ipi_msg.h b/drivers/media/platform/mediatek/vcodec/decoder/vdec_ipi_msg.h index 47070be2a991..c2c2a3a63dfd 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/vdec_ipi_msg.h +++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec_ipi_msg.h @@ -53,7 +53,7 @@ struct vdec_ap_ipi_cmd { /** * struct vdec_vpu_ipi_ack - generic VPU to AP ipi command format * @msg_id : vdec_ipi_msgid - * @status : VPU exeuction result + * @status : VPU execution result * @ap_inst_addr : AP video decoder instance address */ struct vdec_vpu_ipi_ack { @@ -98,7 +98,7 @@ struct vdec_ap_ipi_dec_start { /** * struct vdec_vpu_ipi_init_ack - for VPU_IPIMSG_DEC_INIT_ACK * @msg_id : VPU_IPIMSG_DEC_INIT_ACK - * @status : VPU exeuction result + * @status : VPU execution result * @ap_inst_addr : AP vcodec_vpu_inst instance address * @vpu_inst_addr : VPU decoder instance address * @vdec_abi_version: ABI version of the firmware. Kernel can use it to diff --git a/drivers/media/platform/renesas/rcar_drif.c b/drivers/media/platform/renesas/rcar_drif.c index 0844934f7aa6..bd67e8e16f53 100644 --- a/drivers/media/platform/renesas/rcar_drif.c +++ b/drivers/media/platform/renesas/rcar_drif.c @@ -464,7 +464,7 @@ rcar_drif_get_fbuf(struct rcar_drif_sdr *sdr) rcar_drif_frame_buf, list); if (!fbuf) { /* - * App is late in enqueing buffers. Samples lost & there will + * App is late in enqueuing buffers. Samples lost & there will * be a gap in sequence number when app recovers */ rdrif_dbg(sdr, "\napp late: prod %u\n", sdr->produced); diff --git a/drivers/media/radio/si4713/si4713.c b/drivers/media/radio/si4713/si4713.c index 0c0354566b0a..150d07f6b319 100644 --- a/drivers/media/radio/si4713/si4713.c +++ b/drivers/media/radio/si4713/si4713.c @@ -667,7 +667,7 @@ static int si4713_tx_tune_measure(struct si4713_device *sdev, u16 frequency, /* * si4713_tx_tune_status- Returns the status of the tx_tune_freq, tx_tune_mea or * tx_tune_power commands. This command return the current - * frequency, output voltage in dBuV, the antenna tunning + * frequency, output voltage in dBuV, the antenna tuning * capacitance value and the received noise level. The * command also clears the stcint interrupt bit when the * first bit of its arguments is high. diff --git a/drivers/media/usb/dvb-usb/dib0700_core.c b/drivers/media/usb/dvb-usb/dib0700_core.c index 1caabb51ea47..5d2fa7037c00 100644 --- a/drivers/media/usb/dvb-usb/dib0700_core.c +++ b/drivers/media/usb/dvb-usb/dib0700_core.c @@ -560,7 +560,7 @@ int dib0700_download_firmware(struct usb_device *udev, const struct firmware *fw fw_version = (buf[8] << 24) | (buf[9] << 16) | (buf[10] << 8) | buf[11]; /* set the buffer size - DVB-USB is allocating URB buffers - * only after the firwmare download was successful */ + * only after the firmware download was successful */ for (i = 0; i < dib0700_device_count; i++) { for (adap_num = 0; adap_num < dib0700_devices[i].num_adapters; adap_num++) { diff --git a/drivers/media/usb/gspca/w996Xcf.c b/drivers/media/usb/gspca/w996Xcf.c index 79baa0c1a031..700c39fe307c 100644 --- a/drivers/media/usb/gspca/w996Xcf.c +++ b/drivers/media/usb/gspca/w996Xcf.c @@ -407,7 +407,7 @@ static void w9968cf_set_crop_window(struct sd *sd) if (sd->sensor == SEN_OV7620) { /* - * Sigh, this is dependend on the clock / framerate changes + * Sigh, this is dependent on the clock / framerate changes * made by the frequency control, sick. * * Note we cannot use v4l2_ctrl_g_ctrl here, as we get called diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/s3a/s3a_1.0/ia_css_s3a_types.h b/drivers/staging/media/atomisp/pci/isp/kernels/s3a/s3a_1.0/ia_css_s3a_types.h index b8206d2f3d31..97e63082d254 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/s3a/s3a_1.0/ia_css_s3a_types.h +++ b/drivers/staging/media/atomisp/pci/isp/kernels/s3a/s3a_1.0/ia_css_s3a_types.h @@ -102,8 +102,8 @@ struct ia_css_3a_grid_info { * awb_lg_*: Thresholds to check the saturated bayer pixels for AWB. * Condition of effective pixel for AWB level gate check: * bayer(sensor) <= awb_lg_high_raw && - * bayer(when AWB statisitcs is calculated) >= awb_lg_low && - * bayer(when AWB statisitcs is calculated) <= awb_lg_high + * bayer(when AWB statistics is calculated) >= awb_lg_low && + * bayer(when AWB statistics is calculated) <= awb_lg_high * af_fir*: Coefficients of high pass filter to calculate AF statistics. * * ISP block: S3A1(ae_y_* for AE/AF, awb_lg_* for AWB) diff --git a/drivers/staging/media/av7110/av7110.c b/drivers/staging/media/av7110/av7110.c index 06c43b5f8a87..432404eceaea 100644 --- a/drivers/staging/media/av7110/av7110.c +++ b/drivers/staging/media/av7110/av7110.c @@ -2347,7 +2347,7 @@ static int av7110_attach(struct saa7146_dev *dev, /* RESET SAA7146 */ saa7146_write(dev, MC1, MASK_31); - /* autodetection success seems to be time-dependend after reset */ + /* autodetection success seems to be time-dependent after reset */ /* Fix VSYNC level */ saa7146_setgpio(dev, 3, SAA7146_GPIO_OUTLO); From 40b16e35b104bb86a3e751a63503b879a8570fef Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:04:43 +0530 Subject: [PATCH 186/596] media: fix repeated words in comments Drop words accidentally written twice, reported by checkpatch.pl as a possible repeated word. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Hans Verkuil --- drivers/media/i2c/adv7175.c | 2 +- drivers/media/platform/renesas/vsp1/vsp1_dl.c | 2 +- drivers/media/usb/gspca/ov519.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/adv7175.c b/drivers/media/i2c/adv7175.c index f1caab8e2abd..b6f0e1b24383 100644 --- a/drivers/media/i2c/adv7175.c +++ b/drivers/media/i2c/adv7175.c @@ -209,7 +209,7 @@ static int adv7175_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) /* This is an attempt to convert * SECAM->PAL (typically it does not work * due to genlock: when decoder is in SECAM - * and encoder in in PAL the subcarrier can + * and encoder in PAL the subcarrier can * not be synchronized with horizontal * quency) */ adv7175_write_block(sd, init_pal, sizeof(init_pal)); diff --git a/drivers/media/platform/renesas/vsp1/vsp1_dl.c b/drivers/media/platform/renesas/vsp1/vsp1_dl.c index 6430f2ec8b32..8257759421f3 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_dl.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_dl.c @@ -649,7 +649,7 @@ static void __vsp1_dl_list_put(struct vsp1_dl_list *dl) dl->post_cmd = NULL; /* - * body0 is reused as as an optimisation as presently every display list + * body0 is reused as an optimisation as presently every display list * has at least one body, thus we reinitialise the entries list. */ dl->body0->num_entries = 0; diff --git a/drivers/media/usb/gspca/ov519.c b/drivers/media/usb/gspca/ov519.c index bffa94e76da5..57219a738c73 100644 --- a/drivers/media/usb/gspca/ov519.c +++ b/drivers/media/usb/gspca/ov519.c @@ -13,7 +13,7 @@ * Copyright (c) 1999-2006 Mark W. McClelland * Support for OV519, OV8610 Copyright (c) 2003 Joerg Heckenbach * Many improvements by Bret Wallach - * Color fixes by by Orion Sky Lawlor (2/26/2000) + * Color fixes by Orion Sky Lawlor (2/26/2000) * OV7620 fixes by Charl P. Botha * Changes by Claudio Matsuoka * From d82595b5c787bf9dd8cdbcd50433fe0b72a07fae Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:04:44 +0530 Subject: [PATCH 187/596] media: saa7164: fix repeated word in log message Drop the word written twice, reported by checkpatch.pl as a possible repeated word. Only the message text changes, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Hans Verkuil --- drivers/media/pci/saa7164/saa7164-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/pci/saa7164/saa7164-core.c b/drivers/media/pci/saa7164/saa7164-core.c index 8c587d31ffec..debcfaaf36dc 100644 --- a/drivers/media/pci/saa7164/saa7164-core.c +++ b/drivers/media/pci/saa7164/saa7164-core.c @@ -827,7 +827,7 @@ static void saa7164_get_descriptors(struct saa7164_dev *dev) saa7164_dump_hwdesc(dev); if (dev->intfdesc.bLength != sizeof(struct tmComResInterfaceDescr)) { - printk(KERN_ERR "struct struct tmComResInterfaceDescr is mangled\n"); + printk(KERN_ERR "struct tmComResInterfaceDescr is mangled\n"); printk(KERN_ERR "Need %x got %d\n", dev->intfdesc.bLength, (u32)sizeof(struct tmComResInterfaceDescr)); } else From f28cd7a8477da34e1eafd0eda8c08924ca2a6378 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:05:57 +0530 Subject: [PATCH 188/596] media: dvb-usb-v2/mxl111sf-i2c: fix typo "initilize" in comments Correct "initilize" to "initialize", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Hans Verkuil --- drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c index 100a1052dcbc..1a7135b03c33 100644 --- a/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c +++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c @@ -755,7 +755,7 @@ static int mxl111sf_i2c_hw_xfer_msg(struct mxl111sf_state *state, buf[0] = USB_WRITE_I2C_CMD; buf[1] = 0x00; - /* de-initilize I2C BUS */ + /* de-initialize I2C BUS */ buf[5] = USB_END_I2C_CMD; mxl111sf_i2c_send_data(state, 0, buf); @@ -769,7 +769,7 @@ static int mxl111sf_i2c_hw_xfer_msg(struct mxl111sf_state *state, buf[6] = 0x00; buf[7] = 0x00; - /* de-initilize I2C BUS */ + /* de-initialize I2C BUS */ buf[8] = USB_END_I2C_CMD; mxl111sf_i2c_send_data(state, 0, buf); From a2ec28dd2a88d0721fc4a30242861e2be01d4023 Mon Sep 17 00:00:00 2001 From: Maxim Skokov Date: Sat, 5 Sep 2026 18:58:18 +0300 Subject: [PATCH 189/596] media: vivid: round the height down to the vertical subsampling factor syzbot reports a vmalloc out-of-bounds write in the test pattern generator: BUG: KASAN: vmalloc-out-of-bounds in tpg_fill_plane_pattern drivers/media/common/v4l2-tpg/v4l2-tpg-core.c:2617 [inline] BUG: KASAN: vmalloc-out-of-bounds in tpg_fill_plane_buffer+0x2063/0x4160 drivers/media/common/v4l2-tpg/v4l2-tpg-core.c:2705 Write of size 720 at addr ffffc900038f9d50 by task vivid-000-vid-c/6017 The reproducer requests a 720x49 NV12 capture format, i.e. an odd height for a format whose chroma plane is vertically subsampled. The buffer size is derived from the height by a truncating division: sizes[p] = (tpg_g_line_width(&dev->tpg, p) * h) / dev->fmt_cap->vdownsampling[p] + dev->fmt_cap->data_offset[p]; For a single buffer holding both planes tpg_g_line_width() returns 720 + 720 / 2 = 1080, so 1080 * 49 = 52920 bytes get allocated. tpg_fill_plane_buffer() however emits one chroma line for every two luma lines, i.e. DIV_ROUND_UP(49, 2) = 25 lines, and thus needs 49 * 720 + 25 * 720 = 53280 bytes. The memcpy() of the last chroma line runs 360 bytes past the end of the buffer. An odd height is not meaningful for a 4:2:0 format in the first place, since the chroma plane would have to hold half a line. Rather than fixing up each of the ~10 sites that divide the height by vdownsampling[], round the height down to a multiple of the vertical subsampling factor where it enters the driver. Adjusting the format is what TRY_FMT/S_FMT are for, and it keeps every later division exact. Formats without vertical subsampling are unaffected and keep accepting odd heights. Tested with the syzbot reproducer, which no longer triggers the splat, and by streaming NV12, NV21, YUV420, YVU420 and YUYV at heights 47, 48, 49, 51, 480, 481 and 1081. v4l2-compliance gives identical results before and after (48 of 50 succeeded on the vivid device in both cases; the two failures are pre-existing and unrelated). Fixes: ddcaee9dd4c0 ("[media] vivid: add support for single buffer planar formats") Cc: stable@kernel.org Reported-by: syzbot+cb43e758a4dc84dd467f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=cb43e758a4dc84dd467f Signed-off-by: Maxim Skokov Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/vivid/vivid-vid-cap.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/media/test-drivers/vivid/vivid-vid-cap.c b/drivers/media/test-drivers/vivid/vivid-vid-cap.c index e20449084709..147af0f9b077 100644 --- a/drivers/media/test-drivers/vivid/vivid-vid-cap.c +++ b/drivers/media/test-drivers/vivid/vivid-vid-cap.c @@ -570,6 +570,7 @@ int vivid_try_fmt_vid_cap(struct file *file, void *priv, const struct vivid_fmt *fmt; unsigned bytesperline, max_bpl; unsigned factor = 1; + unsigned int vdiv = 1; unsigned w, h; unsigned p; bool user_set_csc = !!(mp->flags & V4L2_PIX_FMT_FLAG_SET_CSC); @@ -622,6 +623,18 @@ int vivid_try_fmt_vid_cap(struct file *file, void *priv, mp->height = r.height / factor; } + /* + * The chroma planes of vertically subsampled formats hold + * height / vdownsampling lines. If the height is not a multiple of + * the subsampling factor, then the buffer size calculations round + * that number down while the test pattern generator rounds it up, + * so the generator writes one line past the end of the buffer. + * Round the height down to keep both in sync. + */ + for (p = 0; p < fmt->planes; p++) + vdiv = max(vdiv, fmt->vdownsampling[p]); + mp->height = rounddown(mp->height, vdiv); + /* This driver supports custom bytesperline values */ mp->num_planes = fmt->buffers; From 489ac9a332f162246290930f9c497661720627dd Mon Sep 17 00:00:00 2001 From: Amin Gattout Date: Mon, 7 Sep 2026 09:52:45 +0200 Subject: [PATCH 190/596] media: synopsys: hdmirx: Remove redundant dev_err_probe() The devm_request_threaded_irq() function now automatically logs detailed error messages on failure. This eliminates the need for driver-specific dev_err_probe() calls that print generic messages. Signed-off-by: Amin Gattout Reviewed-by: Igor Paunovic Acked-by: Dmitry Osipenko Signed-off-by: Hans Verkuil --- drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c index 3aff624c211a..14c29ed2254b 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c @@ -2621,10 +2621,8 @@ static int hdmirx_setup_irq(struct snps_hdmirx_dev *hdmirx_dev, ret = devm_request_threaded_irq(dev, irq, NULL, hdmirx_dma_irq_handler, IRQF_ONESHOT, "rk_hdmirx-dma", hdmirx_dev); - if (ret) { - dev_err_probe(dev, ret, "failed to request dma irq\n"); + if (ret) return ret; - } irq = gpiod_to_irq(hdmirx_dev->detect_5v_gpio); if (irq < 0) { From a19c9502a69bbd42c18816b09cbefdeb4cb68066 Mon Sep 17 00:00:00 2001 From: Juan Perdomo Date: Tue, 8 Sep 2026 00:07:34 -0400 Subject: [PATCH 191/596] media: cx231xx: preserve I2C transfer length The I2C core stores message lengths in u16, but cx231xx truncates them to u8 when populating cx231xx_i2c_xfer_data. A 0x700-byte read therefore becomes a zero-length request. USB treats zero-length control transfers as OUT regardless of bRequestType, so the IN control pipe trips the usb_submit_urb() direction warning. Use u16 for buf_size, matching both i2c_msg.len and VENDOR_REQUEST_IN.wLength. Oversized transfers then reach the existing URB_MAX_CTRL_SIZE check and return -EINVAL. Fixes: e0d3bafd0258 ("V4L/DVB (10954): Add cx231xx USB driver") Cc: stable@kernel.org Reported-by: syzbot+ddccc0c0dbc0a38ffccb@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ddccc0c0dbc0a38ffccb Tested-by: syzbot+ddccc0c0dbc0a38ffccb@syzkaller.appspotmail.com Signed-off-by: Juan Perdomo Signed-off-by: Hans Verkuil --- drivers/media/usb/cx231xx/cx231xx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/usb/cx231xx/cx231xx.h b/drivers/media/usb/cx231xx/cx231xx.h index 19f5036a78d7..f85d054fbb5c 100644 --- a/drivers/media/usb/cx231xx/cx231xx.h +++ b/drivers/media/usb/cx231xx/cx231xx.h @@ -463,7 +463,7 @@ struct cx231xx_i2c_xfer_data { u8 direction; /* 1 - IN, 0 - OUT */ u8 saddr_len; /* sub address len */ u16 saddr_dat; /* sub addr data */ - u8 buf_size; /* buffer size */ + u16 buf_size; /* buffer size */ u8 *p_buffer; /* pointer to the buffer */ }; From 48f7e8e50ca495bd8a2ad233462a1e7db95ea0fe Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 8 Sep 2026 12:10:39 +0200 Subject: [PATCH 192/596] media: vim2m: give vim2m a proper control ID range The vim2m driver has two driver-specific controls, but the control IDs were never reserved in v4l2-control.h. While these control IDs clash with the range reserved for the old meye driver, since that meye driver has been removed from the kernel quite a long time ago we just officially claim that range for the vim2m driver. Signed-off-by: Hans Verkuil Reported-by: Nicolas Dufresne Closes: https://lore.kernel.org/linux-media/71a3fa9710cf1afbb80ebe850878557c3e95a1b2.camel@ndufresne.ca/ Reviewed-by: Nicolas Dufresne --- drivers/media/test-drivers/vim2m.c | 4 ++-- include/uapi/linux/v4l2-controls.h | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/media/test-drivers/vim2m.c b/drivers/media/test-drivers/vim2m.c index f4a2c4083829..459fd4aedf30 100644 --- a/drivers/media/test-drivers/vim2m.c +++ b/drivers/media/test-drivers/vim2m.c @@ -150,8 +150,8 @@ enum { V4L2_M2M_DST = 1, }; -#define V4L2_CID_TRANS_TIME_MSEC (V4L2_CID_USER_BASE + 0x1000) -#define V4L2_CID_TRANS_NUM_BUFS (V4L2_CID_USER_BASE + 0x1001) +#define V4L2_CID_TRANS_TIME_MSEC (V4L2_CID_USER_VIM2M_BASE + 0) +#define V4L2_CID_TRANS_NUM_BUFS (V4L2_CID_USER_VIM2M_BASE + 1) static struct vim2m_fmt *find_format(u32 fourcc) { diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 3a2c0a7ba5e2..d17e41d51d2e 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -123,6 +123,15 @@ enum v4l2_colorfx { #define V4L2_CID_USER_MEYE_BASE (V4L2_CID_USER_BASE + 0x1000) #endif +/* + * The base for the vim2m driver controls. + * We reserve 16 controls for this driver. + * The vim2m control range clashed with the meye control range, which was not + * intended, but since the meye driver has been removed, we can just keep the + * vim2m control range. + */ +#define V4L2_CID_USER_VIM2M_BASE (V4L2_CID_USER_BASE + 0x1000) + /* The base for the bttv driver controls. * We reserve 32 controls for this driver. */ #define V4L2_CID_USER_BTTV_BASE (V4L2_CID_USER_BASE + 0x1010) From 1adaefca7111235ff0a2219dd4899ac24601d6e8 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 9 Sep 2026 08:13:41 +0000 Subject: [PATCH 193/596] media: vivid: cancel svid ctrl work on release vivid_dev_release() cancels update_hdmi_ctrl_work but not its sibling update_svid_ctrl_work, which vivid_vid_cap_s_ctrl() queues on update_svid_ctrls_workqueue when a VIVID_CID_SVID_IS_CONNECTED_TO_OUTPUT control is changed. The two work items were added together, but only one of them is cancelled on release. If the svid work is still pending or running when the last v4l2_device reference is dropped, update_svid_ctrls_work_handler() runs after kfree(dev): the handler iterates the global vivid_devs[] array and calls v4l2_ctrl_modify_range() on every vivid_dev instance, including this one after it has been freed. Add the missing cancel_work_sync() next to the hdmi sibling, before vivid_free_controls() releases the control handlers the work uses. This issue was found by an in-house static analysis tool. Fixes: d7c969f37515 ("media: vivid: Add 'Is Connected To' menu controls") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/vivid/vivid-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/test-drivers/vivid/vivid-core.c b/drivers/media/test-drivers/vivid/vivid-core.c index 62cfb5feb2cf..c042d92db175 100644 --- a/drivers/media/test-drivers/vivid/vivid-core.c +++ b/drivers/media/test-drivers/vivid/vivid-core.c @@ -854,6 +854,7 @@ static void vivid_dev_release(struct v4l2_device *v4l2_dev) struct vivid_dev *dev = container_of(v4l2_dev, struct vivid_dev, v4l2_dev); cancel_work_sync(&dev->update_hdmi_ctrl_work); + cancel_work_sync(&dev->update_svid_ctrl_work); vivid_free_controls(dev); v4l2_device_unregister(&dev->v4l2_dev); #ifdef CONFIG_MEDIA_CONTROLLER From 27953c044974baf7e24dee3e9342fe0103dea80c Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Tue, 8 Sep 2026 15:27:21 +0800 Subject: [PATCH 194/596] media: vicodec: zero-initialize stateful decoder heap buffers The stateful decoder leaks uninitialized kernel heap memory to userspace. A process that can open the decoder video node gets it back in the CAPTURE buffers it dequeues. The reference frame and the compressed frame buffer are allocated with kvmalloc() in vicodec_start_streaming(), and the decoder can read them before they have been written. The first frame is allowed to be a P-frame, in which case it is decoded against a reference frame that was never produced, and the padding rows below the visible area are never written for any frame. Use kvzalloc() for both allocations. Fixes: 256bf813ba39 ("media: vicodec: add the virtual codec driver") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/vicodec/vicodec-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/test-drivers/vicodec/vicodec-core.c b/drivers/media/test-drivers/vicodec/vicodec-core.c index 25ec3a43fc71..7ea024b14f0e 100644 --- a/drivers/media/test-drivers/vicodec/vicodec-core.c +++ b/drivers/media/test-drivers/vicodec/vicodec-core.c @@ -1605,9 +1605,9 @@ static int vicodec_start_streaming(struct vb2_queue *q, } state->ref_stride = q_data->coded_width * info->luma_alpha_step; - state->ref_frame.buf = kvmalloc(total_planes_size, GFP_KERNEL); + state->ref_frame.buf = kvzalloc(total_planes_size, GFP_KERNEL); state->ref_frame.luma = state->ref_frame.buf; - new_comp_frame = kvmalloc(ctx->comp_max_size, GFP_KERNEL); + new_comp_frame = kvzalloc(ctx->comp_max_size, GFP_KERNEL); if (!state->ref_frame.luma || !new_comp_frame) { kvfree(state->ref_frame.luma); From eda381fd8c0c6f77cc0b3b8d777129eb884a08e0 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 7 Sep 2026 17:30:21 +0300 Subject: [PATCH 195/596] media: ipu-bridge: Add ipu_bridge_get_ipu6() Add ipu_bridge_get_ipu6() for obtaining the IPU 6 (or soon later) PCI device. This isn't technically a job of the IPU bridge, but the functionality is required in conjunction with the firmware definitions that necessitate the use of the IPU bridge on many systems. Signed-off-by: Sakari Ailus Reviewed-by: Antti Laakso Reviewed-by: Daniel Scally --- drivers/media/pci/intel/ipu-bridge.c | 13 +++++++++++++ include/media/ipu-bridge.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 7e65491faf6d..c7c568c1e722 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -15,6 +15,7 @@ #include #include +#include #include #define ADEV_DEV(adev) ACPI_PTR(&((adev)->dev)) @@ -950,6 +951,18 @@ static int ipu_bridge_check_fwnode_graph(struct fwnode_handle *fwnode) return ipu_bridge_check_fwnode_graph(fwnode->secondary); } +struct pci_dev *ipu_bridge_get_ipu6(void) +{ + struct pci_dev *ipu = NULL; + + for (unsigned int i = 0; !ipu && ipu6_pci_tbl[i].vendor; i++) + ipu = pci_get_device(ipu6_pci_tbl[i].vendor, + ipu6_pci_tbl[i].device, NULL); + + return ipu; +} +EXPORT_SYMBOL_NS_GPL(ipu_bridge_get_ipu6, "INTEL_IPU_BRIDGE"); + static DEFINE_MUTEX(ipu_bridge_mutex); int ipu_bridge_init(struct device *dev, diff --git a/include/media/ipu-bridge.h b/include/media/ipu-bridge.h index 16fac765456e..b46b6693f856 100644 --- a/include/media/ipu-bridge.h +++ b/include/media/ipu-bridge.h @@ -169,11 +169,13 @@ struct ipu_bridge { }; #if IS_ENABLED(CONFIG_IPU_BRIDGE) +struct pci_dev *ipu_bridge_get_ipu6(void); int ipu_bridge_init(struct device *dev, ipu_parse_sensor_fwnode_t parse_sensor_fwnode); int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor); int ipu_bridge_instantiate_vcm(struct device *sensor); #else +static inline struct pci_dev *ipu_bridge_get_ipu6(void) { return NULL; } /* Use a define to avoid the @parse_sensor_fwnode argument getting evaluated */ #define ipu_bridge_init(dev, parse_sensor_fwnode) (0) static inline int ipu_bridge_instantiate_vcm(struct device *s) { return 0; } From fd6d6d2ace9e76995d24fbbb40530f586d6123e4 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 7 Sep 2026 17:32:18 +0300 Subject: [PATCH 196/596] media: cvs: Use ipu_bridge_get_ipu6() Instead of going through the PCI device ID table, use ipu_bridge_get_ipu6() to obtain a pointer to the IPU PCI device. Signed-off-by: Sakari Ailus Reviewed-by: Antti Laakso Reviewed-by: Daniel Scally --- drivers/media/i2c/cvs/core.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c index d4a3b9c3bab1..98cadd58a075 100644 --- a/drivers/media/i2c/cvs/core.c +++ b/drivers/media/i2c/cvs/core.c @@ -21,7 +21,6 @@ #include #include -#include #include "icvs.h" @@ -656,14 +655,12 @@ static int cvs_configure_dev_caps(struct icvs *ctx) */ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) { - struct pci_dev *ipu = NULL; + struct pci_dev *ipu; struct icvs *ctx; int ret; /* Locate IPU device */ - for (unsigned int i = 0; !ipu && ipu6_pci_tbl[i].vendor; i++) - ipu = pci_get_device(ipu6_pci_tbl[i].vendor, - ipu6_pci_tbl[i].device, NULL); + ipu = ipu_bridge_get_ipu6(); for (unsigned int i = 0; !ipu && icvs_ipu7_tbl[i].vendor; i++) ipu = pci_get_device(icvs_ipu7_tbl[i].vendor, icvs_ipu7_tbl[i].device, NULL); From 781b70506410308cf7fe58a4c075d055e0c80e8f Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 7 Sep 2026 17:33:04 +0300 Subject: [PATCH 197/596] media: ivsc: Use ipu_bridge_get_ipu6() Instead of going through the PCI device ID table, use ipu_bridge_get_ipu6() to obtain a pointer to the IPU PCI device. Signed-off-by: Sakari Ailus Reviewed-by: Antti Laakso Reviewed-by: Daniel Scally --- drivers/media/pci/intel/ivsc/mei_csi.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/media/pci/intel/ivsc/mei_csi.c b/drivers/media/pci/intel/ivsc/mei_csi.c index c2917e156345..d4256415a829 100644 --- a/drivers/media/pci/intel/ivsc/mei_csi.c +++ b/drivers/media/pci/intel/ivsc/mei_csi.c @@ -27,7 +27,6 @@ #include #include -#include #include #include #include @@ -644,13 +643,9 @@ static int mei_csi_probe(struct mei_cl_device *cldev, struct device *dev = &cldev->dev; struct pci_dev *ipu; struct mei_csi *csi; - unsigned int i; int ret; - for (i = 0, ipu = NULL; !ipu && ipu6_pci_tbl[i].vendor; i++) - ipu = pci_get_device(ipu6_pci_tbl[i].vendor, - ipu6_pci_tbl[i].device, NULL); - + ipu = ipu_bridge_get_ipu6(); if (!ipu) return -ENODEV; From 010009ac5ad33d871fb219bd42a3097122fcb82a Mon Sep 17 00:00:00 2001 From: Pierre Pinon Date: Fri, 4 Sep 2026 08:56:51 +0200 Subject: [PATCH 198/596] media: ipu-bridge: Add upside-down quirk for Dell Pro 14 Premium PA14260 The Dell Pro 14 Premium PA14260 (Panther Lake, IPU7 + Intel CVS) has its OV08X40 front sensor mounted upside down, but reports a rotation of 0 in its SSDB, so without a DMI quirk the image is presented upside down. Add a DMI quirk entry for this model so that a rotation of 180 degrees is reported for its OVTI08F4 sensor. Tested on the affected machine: with the quirk applied, camera_sensor_rotation reads 180 and the captured image is the right way up. Signed-off-by: Pierre Pinon Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index c7c568c1e722..806a3e8fc0d0 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -146,6 +146,13 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { }, .driver_data = "OVTI02C1", }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell Pro 14 Premium PA14260"), + }, + .driver_data = "OVTI08F4", + }, /* * The first four characters of DMI_BOARD_NAME identify the Lenovo * machine type/model. For example, a DMI_BOARD_NAME starting with From df7980112ceeabcdcd6fee245cc7c2ac99a58aaf Mon Sep 17 00:00:00 2001 From: Fernando Rimoli Date: Wed, 2 Sep 2026 16:23:15 +0200 Subject: [PATCH 199/596] media: i2c: ov5693: Add OVTI5693 ACPI HID The ov5693 driver only matches the "INT33BE" ACPI HID. Some Intel IPU6 Surface devices (e.g. Microsoft Surface Pro 9) enumerate the OV5693 front camera with the ACPI HID "OVTI5693" instead, so the i2c core never binds the driver. Add "OVTI5693" to the ACPI match table. Devices that use "INT33BE" (e.g. Surface Go 4) are unaffected. Tested on Surface Pro 9 (IPU6): the sensor enumerates as OVTI5693:00 (ACPI path \_SB_.PC00.I2C3.CAMF) and binds with this change. Link: https://github.com/linux-surface/linux-surface/pull/2171 Signed-off-by: Fernando Rimoli Reviewed-by: Daniel Scally Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov5693.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c index 4cc796bbee92..02236f3db19d 100644 --- a/drivers/media/i2c/ov5693.c +++ b/drivers/media/i2c/ov5693.c @@ -1396,6 +1396,7 @@ static const struct dev_pm_ops ov5693_pm_ops = { static const struct acpi_device_id ov5693_acpi_match[] = { {"INT33BE"}, + {"OVTI5693"}, {}, }; MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); From b304130a403caf0abf956969d6e35be716190cbf Mon Sep 17 00:00:00 2001 From: Fernando Rimoli Date: Wed, 2 Sep 2026 16:23:16 +0200 Subject: [PATCH 200/596] media: ipu-bridge: Add OVTI5693 to the list of supported sensors The IPU bridge builds the firmware node graph only for sensors listed in ipu_supported_sensors[]. The OV5693 is currently listed only under its legacy "INT33BE" HID, so on Intel IPU6 Surface devices that enumerate it as "OVTI5693" (e.g. Microsoft Surface Pro 9) the bridge never wires up the sensor and the front camera is unusable. Add an "OVTI5693" entry. The link frequency (419200000) matches the existing INT33BE entry, as it is the same sensor. Tested on Surface Pro 9 (IPU6). Link: https://github.com/linux-surface/linux-surface/pull/2171 Signed-off-by: Fernando Rimoli Reviewed-by: Daniel Scally Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 806a3e8fc0d0..7f6bc869a3ff 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -96,6 +96,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("OVTI2680", 1, 331200000), /* Omnivision OV5675 */ IPU_SENSOR_CONFIG("OVTI5675", 1, 450000000), + /* Omnivision OV5693 */ + IPU_SENSOR_CONFIG("OVTI5693", 1, 419200000), /* Omnivision OV8856 */ IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), /* Sony IMX471 */ From 8fe9836a1ef81bd46fee95b91dd8206ae356e819 Mon Sep 17 00:00:00 2001 From: Fernando Rimoli Date: Wed, 2 Sep 2026 16:23:17 +0200 Subject: [PATCH 201/596] dt-bindings: media: ov5693: Add clock-noncontinuous The sensor can gate its MIPI clock lane while idle, which the CSI-2 receivers on some platforms require. Allow the endpoint property that asks for it, as ov5647 already does. Signed-off-by: Fernando Rimoli Acked-by: Conor Dooley Signed-off-by: Sakari Ailus --- Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml index 5732657e1484..b2eb63d7d40a 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov5693.yaml @@ -82,6 +82,8 @@ properties: unevaluatedProperties: false properties: + clock-noncontinuous: true + link-frequencies: true data-lanes: From 0e12587945f66d5b8c72be145b95fc5f109b3a32 Mon Sep 17 00:00:00 2001 From: Fernando Rimoli Date: Wed, 2 Sep 2026 16:23:18 +0200 Subject: [PATCH 202/596] media: i2c: ov5693: Gate the MIPI clock lane for non-continuous clock The ov5693 never programs MIPI_CTRL00 (0x4800), leaving it at its 0x00 power-on default, which lets the MIPI clock run freely. The IPU3 CSI-2 receiver tolerates this, but the IPU6 receiver (e.g. on Microsoft Surface Pro 7+, Pro 8, Pro 9 and Surface Go 4) fails to lock onto the link, so the sensor streams but capture times out with "stream stop time out". On most affected machines no frames arrive at all; on some the failure is intermittent. Gate the clock lane while idle at stream on when the endpoint requests a non-continuous clock. Only the gate bit is touched, so platforms that do not request it are unaffected. No counterpart is needed at stream off, as the link is down by then and the register returns to its default when the sensor is powered off. The property is supplied by the ipu-bridge in a subsequent patch. Link: https://github.com/linux-surface/linux-surface/pull/2171 Co-developed-by: Arsalan Naeem Signed-off-by: Arsalan Naeem Signed-off-by: Fernando Rimoli Tested-by: Jakob Berg Jespersen # Surface Pro 7+, IPU6 Tiger Lake Tested-by: Fil Dunsky # Surface Pro 8, IPU6 Tiger Lake (8086:9a19) Tested-by: Lucas Lis # Surface Pro 7+, IPU6 Tiger Lake (0x9a19) Tested-by: Kengo Oki # Surface Go 4, IPU6 Alder Lake-N 8086:462e Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov5693.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c index 02236f3db19d..cedc6ea032c5 100644 --- a/drivers/media/i2c/ov5693.c +++ b/drivers/media/i2c/ov5693.c @@ -35,6 +35,11 @@ #define OV5693_STOP_STREAMING 0x00 #define OV5693_SW_RESET 0x01 +/* MIPI transmitter control */ +#define OV5693_MIPI_CTRL00_REG CCI_REG8(0x4800) +/* Gate the clock lane when there is no packet to transmit */ +#define OV5693_MIPI_CTRL00_CLOCK_LANE_GATE BIT(5) + #define OV5693_REG_CHIP_ID CCI_REG16(0x300a) /* Yes, this is right. The datasheet for the OV5693 gives its ID as 0x5690 */ #define OV5693_CHIP_ID 0x5690 @@ -144,6 +149,9 @@ struct ov5693_device { struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; struct clk *xvclk; + /* Gate the MIPI clock lane when idle (CSI-2 non-continuous clock) */ + bool clock_ncont; + struct ov5693_mode { struct v4l2_rect crop; struct v4l2_mbus_framefmt format; @@ -611,6 +619,19 @@ static int ov5693_enable_streaming(struct ov5693_device *ov5693, bool enable) { int ret = 0; + /* + * Gate the MIPI clock lane while idle if the CSI-2 link is configured + * for a non-continuous clock. Only that bit is touched, and only in + * that case, so the register keeps whatever the platform left in it + * and the clock stays free-running as before everywhere else. It + * needs no counterpart at stream off: the link is down by then, and + * the register returns to its default when the sensor is powered off. + */ + if (enable && ov5693->clock_ncont) + cci_update_bits(ov5693->regmap, OV5693_MIPI_CTRL00_REG, + OV5693_MIPI_CTRL00_CLOCK_LANE_GATE, + OV5693_MIPI_CTRL00_CLOCK_LANE_GATE, &ret); + cci_write(ov5693->regmap, OV5693_SW_STREAM_REG, enable ? OV5693_START_STREAMING : OV5693_STOP_STREAMING, &ret); @@ -1259,6 +1280,9 @@ static int ov5693_check_hwcfg(struct ov5693_device *ov5693) goto out_free_bus_cfg; } + ov5693->clock_ncont = bus_cfg.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + out_free_bus_cfg: v4l2_fwnode_endpoint_free(&bus_cfg); From 3436405beac895b589e2ad4a7be3b57e17605057 Mon Sep 17 00:00:00 2001 From: Fernando Rimoli Date: Wed, 2 Sep 2026 16:23:19 +0200 Subject: [PATCH 203/596] media: ipu-bridge: Assign endpoint property indices dynamically Index the ep_properties array dynamically instead of plain numerical values as is done in mipi-disco-img.c. Signed-off-by: Fernando Rimoli Tested-by: Jakob Berg Jespersen # Surface Pro 7+, IPU6 Tiger Lake Tested-by: Fil Dunsky # Surface Pro 8, IPU6 Tiger Lake (8086:9a19) Tested-by: Lucas Lis # Surface Pro 7+, IPU6 Tiger Lake (0x9a19) Tested-by: Kengo Oki # Surface Go 4, IPU6 Alder Lake-N 8086:462e Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 27 ++++++++++++++------------- include/media/ipu-bridge.h | 19 ++++++++++++++++++- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 7f6bc869a3ff..9cd864bcce34 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -491,6 +491,7 @@ static void ipu_bridge_create_fwnode_properties( { struct ipu_property_names *names = &sensor->prop_names; struct software_node *nodes = sensor->swnodes; + unsigned int i = 0; sensor->prop_names = prop_names; @@ -548,21 +549,21 @@ static void ipu_bridge_create_fwnode_properties( PROPERTY_ENTRY_REF_ARRAY("lens-focus", sensor->vcm_ref); } - sensor->ep_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.bus_type, - V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); - sensor->ep_properties[1] = PROPERTY_ENTRY_U32_ARRAY_LEN( - sensor->prop_names.data_lanes, - bridge->data_lanes, sensor->lanes); - sensor->ep_properties[2] = PROPERTY_ENTRY_REF_ARRAY( - sensor->prop_names.remote_endpoint, - sensor->local_ref); + sensor->ep_properties[IPU_BRIDGE_NEXT_PROPERTY(i, EP_BUS_TYPE)] = + PROPERTY_ENTRY_U32(names->bus_type, + V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); + sensor->ep_properties[IPU_BRIDGE_NEXT_PROPERTY(i, EP_DATA_LANES)] = + PROPERTY_ENTRY_U32_ARRAY_LEN(names->data_lanes, + bridge->data_lanes, sensor->lanes); + sensor->ep_properties[IPU_BRIDGE_NEXT_PROPERTY(i, EP_REMOTE_EP)] = + PROPERTY_ENTRY_REF_ARRAY(names->remote_endpoint, + sensor->local_ref); if (cfg->nr_link_freqs > 0) - sensor->ep_properties[3] = PROPERTY_ENTRY_U64_ARRAY_LEN( - sensor->prop_names.link_frequencies, - cfg->link_freqs, - cfg->nr_link_freqs); + sensor->ep_properties[IPU_BRIDGE_NEXT_PROPERTY(i, EP_LINK_FREQUENCIES)] = + PROPERTY_ENTRY_U64_ARRAY_LEN(names->link_frequencies, + cfg->link_freqs, + cfg->nr_link_freqs); sensor->ipu_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN( sensor->prop_names.data_lanes, diff --git a/include/media/ipu-bridge.h b/include/media/ipu-bridge.h index b46b6693f856..e8aeb23dc5a2 100644 --- a/include/media/ipu-bridge.h +++ b/include/media/ipu-bridge.h @@ -64,6 +64,23 @@ enum ipu_sensor_swnodes { SWNODE_COUNT }; +enum ipu_bridge_ep_props { + IPU_BRIDGE_EP_BUS_TYPE, + IPU_BRIDGE_EP_DATA_LANES, + IPU_BRIDGE_EP_REMOTE_EP, + IPU_BRIDGE_EP_LINK_FREQUENCIES, + IPU_BRIDGE_EP_NUM_OF, + IPU_BRIDGE_EP_NUM_ENTRIES +}; + +/* + * Get the index of the next property in a property array, with a given maximum + * value. + */ +#define IPU_BRIDGE_NEXT_PROPERTY(index, max) \ + (WARN_ON((index) > IPU_BRIDGE_##max) ? \ + IPU_BRIDGE_##max : (index)++) + /* Data representation as it is in ACPI SSDB buffer */ struct ipu_sensor_ssdb { u8 version; @@ -141,7 +158,7 @@ struct ipu_sensor { const char *vcm_type; struct ipu_property_names prop_names; - struct property_entry ep_properties[5]; + struct property_entry ep_properties[IPU_BRIDGE_EP_NUM_ENTRIES]; struct property_entry dev_properties[5]; struct property_entry ipu_properties[3]; struct property_entry ivsc_properties[1]; From b883954d842f57f6f349bbbdf3d4347db3a416dc Mon Sep 17 00:00:00 2001 From: Fernando Rimoli Date: Wed, 2 Sep 2026 16:23:20 +0200 Subject: [PATCH 204/596] media: ipu-bridge: Match sensor configs per IPU and add config flags Some sensors need different treatment depending on which IPU they are connected to, so the ACPI HID alone is not enough. Match on an optional list of IPU PCI product IDs. Entries for one HID must be adjacent, IPU-specific ones first, so the generic entry is skipped once a specific one has matched. Signed-off-by: Fernando Rimoli Tested-by: Jakob Berg Jespersen # Surface Pro 7+, IPU6 Tiger Lake Tested-by: Fil Dunsky # Surface Pro 8, IPU6 Tiger Lake (8086:9a19) Tested-by: Lucas Lis # Surface Pro 7+, IPU6 Tiger Lake (0x9a19) Tested-by: Kengo Oki # Surface Go 4, IPU6 Alder Lake-N 8086:462e Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 35 ++++++++++++++++++++++++++++ include/media/ipu-bridge.h | 26 +++++++++++++++++---- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 9cd864bcce34..3d4de959c890 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -893,8 +894,28 @@ static int ipu_bridge_connect_sensor(const struct ipu_sensor_config *cfg, return ret; } +/* + * Whether a sensor config applies to the IPU this bridge sits on. A config + * listing PCI product IDs only applies to those IPUs. + */ +static bool ipu_bridge_config_matches(const struct ipu_sensor_config *cfg, + struct ipu_bridge *bridge) +{ + const u16 *id; + + if (!cfg->pci_ids) + return true; + + for (id = cfg->pci_ids; *id; id++) + if (*id == bridge->pci_id) + return true; + + return false; +} + static int ipu_bridge_connect_sensors(struct ipu_bridge *bridge) { + const char *done_hid = NULL; unsigned int i; int ret; @@ -902,9 +923,22 @@ static int ipu_bridge_connect_sensors(struct ipu_bridge *bridge) const struct ipu_sensor_config *cfg = &ipu_supported_sensors[i]; + /* + * Entries for one HID are adjacent, IPU-specific ones first, + * so the generic entry is skipped once a specific one has + * matched and the sensor is not connected twice. + */ + if (done_hid && !strcmp(cfg->hid, done_hid)) + continue; + + if (!ipu_bridge_config_matches(cfg, bridge)) + continue; + ret = ipu_bridge_connect_sensor(cfg, bridge); if (ret) goto err_unregister_sensors; + + done_hid = cfg->hid; } return 0; @@ -1000,6 +1034,7 @@ int ipu_bridge_init(struct device *dev, sizeof(bridge->ipu_node_name)); bridge->ipu_hid_node.name = bridge->ipu_node_name; bridge->dev = dev; + bridge->pci_id = dev_is_pci(dev) ? to_pci_dev(dev)->device : 0; bridge->parse_sensor_fwnode = parse_sensor_fwnode; ret = software_node_register(&bridge->ipu_hid_node); diff --git a/include/media/ipu-bridge.h b/include/media/ipu-bridge.h index e8aeb23dc5a2..c562db794ca8 100644 --- a/include/media/ipu-bridge.h +++ b/include/media/ipu-bridge.h @@ -17,13 +17,24 @@ #define IPU_SENSOR_ROTATION_NORMAL 0 #define IPU_SENSOR_ROTATION_INVERTED 1 -#define IPU_SENSOR_CONFIG(_HID, _NR, ...) \ - (const struct ipu_sensor_config) { \ - .hid = _HID, \ - .nr_link_freqs = _NR, \ - .link_freqs = { __VA_ARGS__ } \ +/* + * Sensor config specific to one or more IPUs, identified by their PCI product + * IDs, with flags describing what the sensor needs there. Entries for one HID + * must be adjacent in ipu_supported_sensors[], with the IPU-specific ones + * before the generic one. + */ +#define IPU_SENSOR_CONFIG_MATCH_FL(_HID, _IDS, _FLAGS, _NR, ...) \ + (const struct ipu_sensor_config) { \ + .hid = _HID, \ + .pci_ids = _IDS, \ + .flags = _FLAGS, \ + .nr_link_freqs = _NR, \ + .link_freqs = { __VA_ARGS__ } \ } +#define IPU_SENSOR_CONFIG(_HID, _NR, ...) \ + IPU_SENSOR_CONFIG_MATCH_FL(_HID, NULL, 0, _NR, __VA_ARGS__) + #define NODE_SENSOR(_HID, _PROPS) \ (const struct software_node) { \ .name = _HID, \ @@ -132,6 +143,9 @@ struct ipu_node_names { struct ipu_sensor_config { const char *hid; + /* Zero-terminated list of IPU PCI product IDs, NULL for any IPU */ + const u16 *pci_ids; + const u32 flags; const u8 nr_link_freqs; const u64 link_freqs[MAX_NUM_LINK_FREQS]; }; @@ -177,6 +191,8 @@ typedef int (*ipu_parse_sensor_fwnode_t)(struct acpi_device *adev, struct ipu_bridge { struct device *dev; + /* PCI product ID of the IPU, 0 if it is not a PCI device */ + u16 pci_id; ipu_parse_sensor_fwnode_t parse_sensor_fwnode; char ipu_node_name[ACPI_ID_LEN]; struct software_node ipu_hid_node; From 8e3def7bf4102c5b32b597b2f6ea1419174fc480 Mon Sep 17 00:00:00 2001 From: Fernando Rimoli Date: Wed, 2 Sep 2026 16:23:21 +0200 Subject: [PATCH 205/596] media: ipu-bridge: Request non-continuous clock for ov5693 on IPU6 The ov5693 (INT33BE / OVTI5693) needs its MIPI clock lane gated while idle for the IPU6 CSI-2 receiver to lock onto the link, which the sensor driver does when the endpoint requests a non-continuous clock. The same sensor works with a free-running clock on the IPU3, so request it only for the IPU6 variants where it has been confirmed on hardware: Tiger Lake, Alder Lake-P and Alder Lake-N. Signed-off-by: Fernando Rimoli Tested-by: Jakob Berg Jespersen # Surface Pro 7+, IPU6 Tiger Lake Tested-by: Fil Dunsky # Surface Pro 8, IPU6 Tiger Lake (8086:9a19) Tested-by: Lucas Lis # Surface Pro 7+, IPU6 Tiger Lake (0x9a19) Tested-by: Kengo Oki # Surface Go 4, IPU6 Alder Lake-N 8086:462e Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 18 ++++++++++++++++++ include/media/ipu-bridge.h | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 3d4de959c890..0817216f72ea 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -50,6 +50,14 @@ * * Please keep the list sorted by ACPI HID. */ +/* IPU6 variants whose CSI-2 receiver needs the ov5693 clock lane gated */ +static const u16 ipu6_ov5693_ncont_clk[] = { + PCI_DEVICE_ID_INTEL_IPU6, /* Tiger Lake */ + PCI_DEVICE_ID_INTEL_IPU6EP_ADLP, /* Alder Lake-P */ + PCI_DEVICE_ID_INTEL_IPU6EP_ADLN, /* Alder Lake-N */ + 0 +}; + static const struct ipu_sensor_config ipu_supported_sensors[] = { /* Himax HM1092 */ IPU_SENSOR_CONFIG("HIMX1092", 2, 180000000, 180480000), @@ -62,6 +70,9 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { /* GalaxyCore GC0310 */ IPU_SENSOR_CONFIG("INT0310", 1, 55692000), /* Omnivision OV5693 */ + IPU_SENSOR_CONFIG_MATCH_FL("INT33BE", ipu6_ov5693_ncont_clk, + IPU_BR_FL_CSI2_CLK_NONCONTINUOUS, + 1, 419200000), IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), /* Onsemi MT9M114 */ IPU_SENSOR_CONFIG("INT33F0", 1, 384000000), @@ -98,6 +109,9 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { /* Omnivision OV5675 */ IPU_SENSOR_CONFIG("OVTI5675", 1, 450000000), /* Omnivision OV5693 */ + IPU_SENSOR_CONFIG_MATCH_FL("OVTI5693", ipu6_ov5693_ncont_clk, + IPU_BR_FL_CSI2_CLK_NONCONTINUOUS, + 1, 419200000), IPU_SENSOR_CONFIG("OVTI5693", 1, 419200000), /* Omnivision OV8856 */ IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), @@ -566,6 +580,10 @@ static void ipu_bridge_create_fwnode_properties( cfg->link_freqs, cfg->nr_link_freqs); + if (cfg->flags & IPU_BR_FL_CSI2_CLK_NONCONTINUOUS) + sensor->ep_properties[IPU_BRIDGE_NEXT_PROPERTY(i, EP_CLOCK_NONCONTINUOUS)] = + PROPERTY_ENTRY_BOOL("clock-noncontinuous"); + sensor->ipu_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN( sensor->prop_names.data_lanes, bridge->data_lanes, sensor->lanes); diff --git a/include/media/ipu-bridge.h b/include/media/ipu-bridge.h index c562db794ca8..760f0767ce58 100644 --- a/include/media/ipu-bridge.h +++ b/include/media/ipu-bridge.h @@ -17,6 +17,10 @@ #define IPU_SENSOR_ROTATION_NORMAL 0 #define IPU_SENSOR_ROTATION_INVERTED 1 +/* Flags for struct ipu_sensor_config */ +/* The sensor's CSI-2 transmitter needs a non-continuous clock */ +#define IPU_BR_FL_CSI2_CLK_NONCONTINUOUS BIT(0) + /* * Sensor config specific to one or more IPUs, identified by their PCI product * IDs, with flags describing what the sensor needs there. Entries for one HID @@ -80,6 +84,7 @@ enum ipu_bridge_ep_props { IPU_BRIDGE_EP_DATA_LANES, IPU_BRIDGE_EP_REMOTE_EP, IPU_BRIDGE_EP_LINK_FREQUENCIES, + IPU_BRIDGE_EP_CLOCK_NONCONTINUOUS, IPU_BRIDGE_EP_NUM_OF, IPU_BRIDGE_EP_NUM_ENTRIES }; From 3d42d1c82717f0853379b516a7fab54b636e09cc Mon Sep 17 00:00:00 2001 From: Yibo Tan Date: Mon, 7 Sep 2026 06:43:50 +0800 Subject: [PATCH 206/596] media: ipu-bridge: Fix use-after-free in IVSC error path ipu_bridge_get_ivsc_acpi_dev() returns an ACPI device with a reference held. If no matching CSI device is found, ipu_bridge_check_ivsc_dev() drops that reference before passing the device to dev_err(). Concurrent CSI device and ACPI namespace removal can drop the other references, making this the final put. dev_err() then reads the freed struct acpi_device and its kobject name. This was reproduced without kprobes by racing normal CSI platform-device unregistration and ACPI configfs table removal against the production ipu_bridge_init() entry. KASAN reported the final free through device_release(), followed by invalid reads through _dev_err(). Emit the error message while the lookup reference is still held, then drop the reference as before. Source reproducers and vulnerable/fixed serial logs, with only the build hostname redacted, are available at: https://github.com/kimaiden1984-boop/linux-ipu-bridge-acpi-uaf-reproducer The fixed target builds with W=1 on x86-64. This was not tested on physical IPU hardware. The tests establish runtime reachability but do not measure occurrence frequency on shipping systems or establish exploitability. Fixes: c66821f381ae ("media: pci: intel: Add IVSC support for IPU bridge driver") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Yibo Tan Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 0817216f72ea..63470be888ba 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -351,8 +351,8 @@ static int ipu_bridge_check_ivsc_dev(struct ipu_sensor *sensor, if (adev) { csi_dev = ipu_bridge_get_ivsc_csi_dev(adev); if (!csi_dev) { - acpi_dev_put(adev); dev_err(ADEV_DEV(adev), "Failed to find MEI or CVS CSI dev\n"); + acpi_dev_put(adev); return -ENODEV; } From ad9514aa372ef45bd7e0d9364a07b7d8cca2ae58 Mon Sep 17 00:00:00 2001 From: Felipe Calliari Date: Sat, 5 Sep 2026 00:04:35 -0300 Subject: [PATCH 207/596] media: ipu-bridge: Add upside-down sensor DMI quirk for Samsung Galaxy Book3 Ultra On the Samsung Galaxy Book3 Ultra (960XFH) the OV02C10 sensor is mounted upside-down. The 180 degree rotation is not described in the sensor's ACPI data: both the SSDB and the _PLD report a rotation of 0 degrees, so ipu_bridge_parse_rotation() assigns a "rotation" property of 0 to the sensor fwnode and the image is rendered upside-down in userspace. Because the SSDB degree field holds the valid value 0, the flipped mounting goes unnoticed: the driver logs no warning and userspace (e.g. libcamera) has no way to correct the image. Add a DMI quirk entry for this laptop so that the sensor fwnode gets a "rotation" property of 180 and userspace (e.g. libcamera) can compensate. Tested on the Galaxy Book3 Ultra: with the sensor reporting V4L2_CID_CAMERA_SENSOR_ROTATION = 180 (verified with an equivalent local sensor-driver quirk), libcamera renders the image upright. Signed-off-by: Felipe Calliari Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 63470be888ba..7520bb950de6 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -217,6 +217,14 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { }, .driver_data = "OVTI02E1", }, + { + /* Samsung Galaxy Book3 Ultra */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "960XFH"), + }, + .driver_data = "OVTI02C1", + }, {} /* Terminating entry */ }; From df36316fdd9753799304395b69fc70ddbb45e1a9 Mon Sep 17 00:00:00 2001 From: Steve Kaznak Date: Sat, 15 Aug 2026 02:23:50 +0000 Subject: [PATCH 208/596] media: ipu-bridge: Add OV01A1B sensor config Add the Omnivision OV01A1B (ACPI HID OVTI01AB) to the ipu_supported_sensors[] table, with the same single 400 MHz link frequency as the OV01A10 and OV01A1S entries it shares a comment with. The sensor is the IR camera in the Dell XPS 13 Plus 9320, behind an Intel Vision Sensing Controller (IVSC). Commit 36eab90d3f4f ("media: i2c: ov01a10: Add ov01a1b support") added driver support for it, but without a bridge entry no fwnode graph endpoint is created, so the driver never binds and the camera is not enumerated. Tested on an XPS 13 Plus 9320: the sensor binds and streams 1296x816 Y10 at 30 fps. Signed-off-by: Steve Kaznak Reviewed-by: Mehdi Djait Reviewed-by: Hans de Goede Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 7520bb950de6..952868ad8d5f 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -88,8 +88,9 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("INT3537", 1, 437000000), /* Lontium lt6911uxe */ IPU_SENSOR_CONFIG("INTC10C5", 0), - /* Omnivision OV01A10 / OV01A1S */ + /* Omnivision OV01A10 / OV01A1B / OV01A1S */ IPU_SENSOR_CONFIG("OVTI01A0", 1, 400000000), + IPU_SENSOR_CONFIG("OVTI01AB", 1, 400000000), IPU_SENSOR_CONFIG("OVTI01AS", 1, 400000000), /* Omnivision OV02C10 */ IPU_SENSOR_CONFIG("OVTI02C1", 1, 400000000), From 3942272da7f6896a76ba071d5b7d490067b53c06 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Wed, 2 Sep 2026 15:06:53 +0800 Subject: [PATCH 209/596] media: i2c: imx471: Set max analog gain to 960 The maximum analog gain was previously restricted to resolve image flickering and overexposure issues. With recent developments in libcamera, this limit can now be restored to its original value of 960 to ensure image brightness is properly evaluated and adjusted. Link: https://raw.githubusercontent.com/intel/ipu6-drivers/refs/heads/master/drivers/media/i2c/imx471.c Signed-off-by: Kate Hsuan Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx471.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c index 4053aed84340..8d9c3d0723c0 100644 --- a/drivers/media/i2c/imx471.c +++ b/drivers/media/i2c/imx471.c @@ -41,7 +41,7 @@ /* Analog gain control */ #define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204) #define IMX471_ANA_GAIN_MIN 0 -#define IMX471_ANA_GAIN_MAX 800 +#define IMX471_ANA_GAIN_MAX 960 #define IMX471_ANA_GAIN_STEP 1 #define IMX471_ANA_GAIN_DEFAULT 0 From 5714821327c4a5938fffeb3df0e20b04e18d071a Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Thu, 25 Jun 2026 20:47:55 +0530 Subject: [PATCH 210/596] media: ti: j721e-csi2rx: Support RAW12 bayer formats Add support for RAW12 bayer formats in the j721e-csi2rx driver. Signed-off-by: Jai Luthra Signed-off-by: Yemike Abhilash Chandra Reviewed-by: Rishikesh Donadkar Reviewed-by: Jai Luthra Signed-off-by: Sakari Ailus --- .../platform/ti/j721e-csi2rx/j721e-csi2rx.c | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c index 4769931b1930..4ba0ba6ad935 100644 --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c @@ -225,6 +225,30 @@ static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = { .csi_dt = MIPI_CSI2_DT_RAW10, .bpp = 16, .size = SHIM_DMACNTX_SIZE_16, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .csi_dt = MIPI_CSI2_DT_RAW12, + .bpp = 16, + .size = SHIM_DMACNTX_SIZE_16, }, { .fourcc = V4L2_PIX_FMT_RGB565X, .code = MEDIA_BUS_FMT_RGB565_1X16, From 45855607c38b87cf10bd869027dfb3e7da7cce6a Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Thu, 25 Jun 2026 20:47:56 +0530 Subject: [PATCH 211/596] media: cadence: csi2rx: Support RAW12 bayer formats Add support for RAW12 bayer formats in the cadence csi2rx driver. Signed-off-by: Jai Luthra Signed-off-by: Yemike Abhilash Chandra Reviewed-by: Rishikesh Donadkar Reviewed-by: Jai Luthra Signed-off-by: Sakari Ailus --- drivers/media/platform/cadence/cdns-csi2rx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c index 1ff2d8f78d5b..9e05b3b9ee34 100644 --- a/drivers/media/platform/cadence/cdns-csi2rx.c +++ b/drivers/media/platform/cadence/cdns-csi2rx.c @@ -166,6 +166,10 @@ static const struct csi2rx_fmt formats[] = { { .code = MEDIA_BUS_FMT_SGBRG10_1X10, .bpp = 10, .max_pixels = 2, }, { .code = MEDIA_BUS_FMT_SGRBG10_1X10, .bpp = 10, .max_pixels = 2, }, { .code = MEDIA_BUS_FMT_SRGGB10_1X10, .bpp = 10, .max_pixels = 2, }, + { .code = MEDIA_BUS_FMT_SBGGR12_1X12, .bpp = 12, .max_pixels = 2, }, + { .code = MEDIA_BUS_FMT_SGBRG12_1X12, .bpp = 12, .max_pixels = 2, }, + { .code = MEDIA_BUS_FMT_SGRBG12_1X12, .bpp = 12, .max_pixels = 2, }, + { .code = MEDIA_BUS_FMT_SRGGB12_1X12, .bpp = 12, .max_pixels = 2, }, { .code = MEDIA_BUS_FMT_RGB565_1X16, .bpp = 16, .max_pixels = 1, }, { .code = MEDIA_BUS_FMT_RGB888_1X24, .bpp = 24, .max_pixels = 1, }, { .code = MEDIA_BUS_FMT_BGR888_1X24, .bpp = 24, .max_pixels = 1, }, From b38d06ad1e13c32970d58039c154f009b81a3368 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 8 Sep 2026 21:49:56 +0100 Subject: [PATCH 212/596] media: ipu6: Fix up missing IWYU issue In -next: /tmp/next/build/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c:229:3: error: call to undeclared function 'clflush_cache_range'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] 229 | clflush_cache_range(cpu_mapped_buf, size); | ^ Signed-off-by: Mark Brown Reviewed-by: Antti Laakso Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu6/ipu7-fw-isys.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c index f821ec5f55b2..aee9227fd66c 100644 --- a/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c +++ b/drivers/media/pci/intel/ipu6/ipu7-fw-isys.c @@ -4,6 +4,7 @@ */ #include +#include #include "ipu6-bus.h" #include "ipu6-dma.h" From 71f3c75dad20f05865a071836a0675b42f2bebab Mon Sep 17 00:00:00 2001 From: Arash Golgol Date: Tue, 4 Aug 2026 10:56:28 +0330 Subject: [PATCH 213/596] media: sun6i-mipi-csi2: Use V4L2 subdev active state Use the V4L2 subdev active state API to store the active format. This simplifies the driver not only by dropping the bridge mbus_format field, but it also allows dropping the bridge lock, replaced with the state lock. The sun6i-mipi-csi2 hardware does not perform any format conversion. Enforce identical formats on the sink and source pads in the set_fmt() and init_state() callbacks. Signed-off-by: Arash Golgol Reviewed-by: Laurent Pinchart Reviewed-by: Paul Kocialkowski Tested-by: Paul Kocialkowski Signed-off-by: Sakari Ailus --- .../sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c | 107 +++++++++--------- .../sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h | 2 - 2 files changed, 53 insertions(+), 56 deletions(-) diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c index b06cb73015cd..17a9a215a98a 100644 --- a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c @@ -95,12 +95,12 @@ static void sun6i_mipi_csi2_disable(struct sun6i_mipi_csi2_device *csi2_dev) SUN6I_MIPI_CSI2_CTL_EN, 0); } -static void sun6i_mipi_csi2_configure(struct sun6i_mipi_csi2_device *csi2_dev) +static void sun6i_mipi_csi2_configure(struct sun6i_mipi_csi2_device *csi2_dev, + const struct v4l2_mbus_framefmt *mbus_format) { struct regmap *regmap = csi2_dev->regmap; unsigned int lanes_count = csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes; - struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; const struct sun6i_mipi_csi2_format *format; struct device *dev = csi2_dev->dev; u32 version = 0; @@ -173,7 +173,8 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) struct v4l2_subdev *source_subdev = csi2_dev->bridge.source_subdev; union phy_configure_opts dphy_opts = { 0 }; struct phy_configure_opts_mipi_dphy *dphy_cfg = &dphy_opts.mipi_dphy; - struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *mbus_format; const struct sun6i_mipi_csi2_format *format; struct phy *dphy = csi2_dev->dphy; struct device *dev = csi2_dev->dev; @@ -183,8 +184,12 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) unsigned long pixel_rate; int ret; - if (!source_subdev) - return -ENODEV; + state = v4l2_subdev_lock_and_get_active_state(subdev); + + if (!source_subdev) { + ret = -ENODEV; + goto unlock; + } if (!on) { v4l2_subdev_call(source_subdev, video, s_stream, 0); @@ -196,7 +201,7 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) ret = pm_runtime_resume_and_get(dev); if (ret < 0) - return ret; + goto unlock; /* Sensor Pixel Rate */ @@ -222,6 +227,8 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) goto error_pm; } + mbus_format = v4l2_subdev_state_get_format(state, + SUN6I_MIPI_CSI2_PAD_SINK); format = sun6i_mipi_csi2_format_find(mbus_format->code); if (WARN_ON(!format)) { ret = -ENODEV; @@ -260,7 +267,7 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) /* Controller */ - sun6i_mipi_csi2_configure(csi2_dev); + sun6i_mipi_csi2_configure(csi2_dev, mbus_format); sun6i_mipi_csi2_enable(csi2_dev); /* D-PHY */ @@ -277,7 +284,8 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) if (ret && ret != -ENOIOCTLCMD) goto disable; - return 0; + ret = 0; + goto unlock; disable: phy_power_off(dphy); @@ -286,6 +294,8 @@ static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) error_pm: pm_runtime_put(dev); +unlock: + v4l2_subdev_unlock_state(state); return ret; } @@ -308,21 +318,23 @@ sun6i_mipi_csi2_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) static int sun6i_mipi_csi2_init_state(struct v4l2_subdev *subdev, struct v4l2_subdev_state *state) { - struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); - unsigned int pad = SUN6I_MIPI_CSI2_PAD_SINK; - struct v4l2_mbus_framefmt *mbus_format = - v4l2_subdev_state_get_format(state, pad); - struct mutex *lock = &csi2_dev->bridge.lock; + unsigned int pad; - mutex_lock(lock); + /* + * This subdev does not perform format conversion, + * initialize both pads identically. + */ + for (pad = 0; pad < subdev->entity.num_pads; pad++) { + struct v4l2_mbus_framefmt *mbus_format; - mbus_format->code = sun6i_mipi_csi2_formats[0].mbus_code; - mbus_format->width = 640; - mbus_format->height = 480; + mbus_format = v4l2_subdev_state_get_format(state, pad); - sun6i_mipi_csi2_mbus_format_prepare(mbus_format); + mbus_format->code = sun6i_mipi_csi2_formats[0].mbus_code; + mbus_format->width = 640; + mbus_format->height = 480; - mutex_unlock(lock); + sun6i_mipi_csi2_mbus_format_prepare(mbus_format); + } return 0; } @@ -340,53 +352,32 @@ sun6i_mipi_csi2_enum_mbus_code(struct v4l2_subdev *subdev, return 0; } -static int sun6i_mipi_csi2_get_fmt(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *state, - struct v4l2_subdev_format *format) -{ - struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi2_dev->bridge.lock; - - mutex_lock(lock); - - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *mbus_format = *v4l2_subdev_state_get_format(state, - format->pad); - else - *mbus_format = csi2_dev->bridge.mbus_format; - - mutex_unlock(lock); - - return 0; -} - static int sun6i_mipi_csi2_set_fmt(struct v4l2_subdev *subdev, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi2_dev->bridge.lock; + struct v4l2_mbus_framefmt *fmt; - mutex_lock(lock); + /* The format on the source pad always matches the sink pad. */ + if (format->pad != SUN6I_MIPI_CSI2_PAD_SINK) + return v4l2_subdev_get_fmt(subdev, state, format); - sun6i_mipi_csi2_mbus_format_prepare(mbus_format); + sun6i_mipi_csi2_mbus_format_prepare(&format->format); - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *v4l2_subdev_state_get_format(state, format->pad) = - *mbus_format; - else - csi2_dev->bridge.mbus_format = *mbus_format; + /* Set the format on the sink pad. */ + fmt = v4l2_subdev_state_get_format(state, format->pad); + *fmt = format->format; - mutex_unlock(lock); + /* Propagate the format to the source pad. */ + fmt = v4l2_subdev_state_get_format(state, SUN6I_MIPI_CSI2_PAD_SOURCE); + *fmt = format->format; return 0; } static const struct v4l2_subdev_pad_ops sun6i_mipi_csi2_pad_ops = { .enum_mbus_code = sun6i_mipi_csi2_enum_mbus_code, - .get_fmt = sun6i_mipi_csi2_get_fmt, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = sun6i_mipi_csi2_set_fmt, }; @@ -502,8 +493,6 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) bool notifier_registered = false; int ret; - mutex_init(&bridge->lock); - /* V4L2 Subdev */ v4l2_subdev_init(subdev, &sun6i_mipi_csi2_subdev_ops); @@ -532,6 +521,12 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) if (ret) return ret; + /* V4L2 Subdev finalize */ + + ret = v4l2_subdev_init_finalize(subdev); + if (ret < 0) + goto error_media_entity_cleanup; + /* V4L2 Async */ v4l2_async_subdev_nf_init(notifier, subdev); @@ -565,6 +560,9 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) error_v4l2_notifier_cleanup: v4l2_async_nf_cleanup(notifier); + v4l2_subdev_cleanup(subdev); + +error_media_entity_cleanup: media_entity_cleanup(&subdev->entity); return ret; @@ -579,6 +577,7 @@ sun6i_mipi_csi2_bridge_cleanup(struct sun6i_mipi_csi2_device *csi2_dev) v4l2_async_unregister_subdev(subdev); v4l2_async_nf_unregister(notifier); v4l2_async_nf_cleanup(notifier); + v4l2_subdev_cleanup(subdev); media_entity_cleanup(&subdev->entity); } diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h index 24b15e34b5e8..d72dfbd6a993 100644 --- a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h @@ -32,8 +32,6 @@ struct sun6i_mipi_csi2_bridge { struct media_pad pads[SUN6I_MIPI_CSI2_PAD_COUNT]; struct v4l2_fwnode_endpoint endpoint; struct v4l2_async_notifier notifier; - struct v4l2_mbus_framefmt mbus_format; - struct mutex lock; /* Mbus format lock. */ struct v4l2_subdev *source_subdev; }; From 2b4137597ed1104f843df6199e6175eac93a362a Mon Sep 17 00:00:00 2001 From: Arash Golgol Date: Tue, 4 Aug 2026 10:56:29 +0330 Subject: [PATCH 214/596] media: sun8i-a83t-mipi-csi2: Use V4L2 subdev active state Use the V4L2 subdev active state API to store the active format. This simplifies the driver not only by dropping the bridge mbus_format field, but it also allows dropping the bridge lock, replaced with the state lock. The sun8i-a83t-mipi-csi2 hardware does not perform any format conversion. Enforce identical formats on the sink and source pads in the set_fmt() and init_state() callbacks. Signed-off-by: Arash Golgol Reviewed-by: Paul Kocialkowski Tested-by: Paul Kocialkowski Signed-off-by: Sakari Ailus --- .../sun8i_a83t_mipi_csi2.c | 112 +++++++++--------- .../sun8i_a83t_mipi_csi2.h | 2 - 2 files changed, 55 insertions(+), 59 deletions(-) diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c index dbc51daa4fe3..38a6ef19f4ff 100644 --- a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c @@ -144,12 +144,12 @@ sun8i_a83t_mipi_csi2_disable(struct sun8i_a83t_mipi_csi2_device *csi2_dev) } static void -sun8i_a83t_mipi_csi2_configure(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +sun8i_a83t_mipi_csi2_configure(struct sun8i_a83t_mipi_csi2_device *csi2_dev, + const struct v4l2_mbus_framefmt *mbus_format) { struct regmap *regmap = csi2_dev->regmap; unsigned int lanes_count = csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes; - struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; const struct sun8i_a83t_mipi_csi2_format *format; struct device *dev = csi2_dev->dev; u32 version = 0; @@ -205,7 +205,8 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) struct v4l2_subdev *source_subdev = csi2_dev->bridge.source_subdev; union phy_configure_opts dphy_opts = { 0 }; struct phy_configure_opts_mipi_dphy *dphy_cfg = &dphy_opts.mipi_dphy; - struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *mbus_format; const struct sun8i_a83t_mipi_csi2_format *format; struct phy *dphy = csi2_dev->dphy; struct device *dev = csi2_dev->dev; @@ -215,8 +216,12 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) unsigned long pixel_rate; int ret; - if (!source_subdev) - return -ENODEV; + state = v4l2_subdev_lock_and_get_active_state(subdev); + + if (!source_subdev) { + ret = -ENODEV; + goto unlock; + } if (!on) { v4l2_subdev_call(source_subdev, video, s_stream, 0); @@ -228,7 +233,7 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) ret = pm_runtime_resume_and_get(dev); if (ret < 0) - return ret; + goto unlock; /* Sensor pixel rate */ @@ -254,6 +259,9 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) goto error_pm; } + mbus_format = + v4l2_subdev_state_get_format(state, + SUN8I_A83T_MIPI_CSI2_PAD_SINK); format = sun8i_a83t_mipi_csi2_format_find(mbus_format->code); if (WARN_ON(!format)) { ret = -ENODEV; @@ -292,7 +300,7 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) /* Controller */ - sun8i_a83t_mipi_csi2_configure(csi2_dev); + sun8i_a83t_mipi_csi2_configure(csi2_dev, mbus_format); sun8i_a83t_mipi_csi2_enable(csi2_dev); /* D-PHY */ @@ -309,7 +317,8 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) if (ret && ret != -ENOIOCTLCMD) goto disable; - return 0; + ret = 0; + goto unlock; disable: phy_power_off(dphy); @@ -318,6 +327,8 @@ static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) error_pm: pm_runtime_put(dev); +unlock: + v4l2_subdev_unlock_state(state); return ret; } @@ -341,22 +352,23 @@ sun8i_a83t_mipi_csi2_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) static int sun8i_a83t_mipi_csi2_init_state(struct v4l2_subdev *subdev, struct v4l2_subdev_state *state) { - struct sun8i_a83t_mipi_csi2_device *csi2_dev = - v4l2_get_subdevdata(subdev); - unsigned int pad = SUN8I_A83T_MIPI_CSI2_PAD_SINK; - struct v4l2_mbus_framefmt *mbus_format = - v4l2_subdev_state_get_format(state, pad); - struct mutex *lock = &csi2_dev->bridge.lock; + unsigned int pad; - mutex_lock(lock); + /* + * This subdev does not perform format conversion, + * initialize both pads identically. + */ + for (pad = 0; pad < subdev->entity.num_pads; pad++) { + struct v4l2_mbus_framefmt *mbus_format; - mbus_format->code = sun8i_a83t_mipi_csi2_formats[0].mbus_code; - mbus_format->width = 640; - mbus_format->height = 480; + mbus_format = v4l2_subdev_state_get_format(state, pad); - sun8i_a83t_mipi_csi2_mbus_format_prepare(mbus_format); + mbus_format->code = sun8i_a83t_mipi_csi2_formats[0].mbus_code; + mbus_format->width = 640; + mbus_format->height = 480; - mutex_unlock(lock); + sun8i_a83t_mipi_csi2_mbus_format_prepare(mbus_format); + } return 0; } @@ -375,55 +387,33 @@ sun8i_a83t_mipi_csi2_enum_mbus_code(struct v4l2_subdev *subdev, return 0; } -static int sun8i_a83t_mipi_csi2_get_fmt(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *state, - struct v4l2_subdev_format *format) -{ - struct sun8i_a83t_mipi_csi2_device *csi2_dev = - v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi2_dev->bridge.lock; - - mutex_lock(lock); - - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *mbus_format = *v4l2_subdev_state_get_format(state, - format->pad); - else - *mbus_format = csi2_dev->bridge.mbus_format; - - mutex_unlock(lock); - - return 0; -} - static int sun8i_a83t_mipi_csi2_set_fmt(struct v4l2_subdev *subdev, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - struct sun8i_a83t_mipi_csi2_device *csi2_dev = - v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi2_dev->bridge.lock; + struct v4l2_mbus_framefmt *fmt; - mutex_lock(lock); + /* The format on the source pad always matches the sink pad. */ + if (format->pad != SUN8I_A83T_MIPI_CSI2_PAD_SINK) + return v4l2_subdev_get_fmt(subdev, state, format); - sun8i_a83t_mipi_csi2_mbus_format_prepare(mbus_format); + sun8i_a83t_mipi_csi2_mbus_format_prepare(&format->format); - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *v4l2_subdev_state_get_format(state, format->pad) = - *mbus_format; - else - csi2_dev->bridge.mbus_format = *mbus_format; + /* Set the format on the sink pad. */ + fmt = v4l2_subdev_state_get_format(state, format->pad); + *fmt = format->format; - mutex_unlock(lock); + /* Propagate the format to the source pad. */ + fmt = v4l2_subdev_state_get_format(state, + SUN8I_A83T_MIPI_CSI2_PAD_SOURCE); + *fmt = format->format; return 0; } static const struct v4l2_subdev_pad_ops sun8i_a83t_mipi_csi2_pad_ops = { .enum_mbus_code = sun8i_a83t_mipi_csi2_enum_mbus_code, - .get_fmt = sun8i_a83t_mipi_csi2_get_fmt, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = sun8i_a83t_mipi_csi2_set_fmt, }; @@ -540,8 +530,6 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) bool notifier_registered = false; int ret; - mutex_init(&bridge->lock); - /* V4L2 Subdev */ v4l2_subdev_init(subdev, &sun8i_a83t_mipi_csi2_subdev_ops); @@ -570,6 +558,12 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) if (ret) return ret; + /* V4L2 Subdev finalize */ + + ret = v4l2_subdev_init_finalize(subdev); + if (ret < 0) + goto error_media_entity_cleanup; + /* V4L2 Async */ v4l2_async_subdev_nf_init(notifier, subdev); @@ -603,6 +597,9 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) error_v4l2_notifier_cleanup: v4l2_async_nf_cleanup(notifier); + v4l2_subdev_cleanup(subdev); + +error_media_entity_cleanup: media_entity_cleanup(&subdev->entity); return ret; @@ -617,6 +614,7 @@ sun8i_a83t_mipi_csi2_bridge_cleanup(struct sun8i_a83t_mipi_csi2_device *csi2_dev v4l2_async_unregister_subdev(subdev); v4l2_async_nf_unregister(notifier); v4l2_async_nf_cleanup(notifier); + v4l2_subdev_cleanup(subdev); media_entity_cleanup(&subdev->entity); } diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h index f1e64c53434c..819527bcd64d 100644 --- a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h @@ -33,8 +33,6 @@ struct sun8i_a83t_mipi_csi2_bridge { struct media_pad pads[SUN8I_A83T_MIPI_CSI2_PAD_COUNT]; struct v4l2_fwnode_endpoint endpoint; struct v4l2_async_notifier notifier; - struct v4l2_mbus_framefmt mbus_format; - struct mutex lock; /* Mbus format lock. */ struct v4l2_subdev *source_subdev; }; From 63c578491cb406089b8718f2054eb8328c9dd43c Mon Sep 17 00:00:00 2001 From: Arash Golgol Date: Tue, 4 Aug 2026 10:56:30 +0330 Subject: [PATCH 215/596] media: sun6i-csi: bridge: Use V4L2 subdev active state Use the V4L2 subdev active state API to store the active format. This simplifies the driver not only by dropping the bridge mbus_format field, but it also allows dropping the bridge lock, replaced with the state lock. Previously, capture accessed bridge private state directly. After moving to framework-managed state, resolve the format through the subdev pad API. The sun6i-csi-bridge hardware does not perform any format conversion. Enforce identical formats on the sink and source pads in the set_fmt() and init_state() callbacks. Signed-off-by: Arash Golgol Reviewed-by: Paul Kocialkowski Tested-by: Paul Kocialkowski Reviewed-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- .../sunxi/sun6i-csi/sun6i_csi_bridge.c | 155 ++++++++---------- .../sunxi/sun6i-csi/sun6i_csi_bridge.h | 9 - .../sunxi/sun6i-csi/sun6i_csi_capture.c | 27 ++- 3 files changed, 86 insertions(+), 105 deletions(-) diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c index d006d9dd0170..43a85bcc2ba2 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c @@ -13,26 +13,6 @@ #include "sun6i_csi_bridge.h" #include "sun6i_csi_reg.h" -/* Helpers */ - -void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev, - unsigned int *width, unsigned int *height) -{ - if (width) - *width = csi_dev->bridge.mbus_format.width; - if (height) - *height = csi_dev->bridge.mbus_format.height; -} - -void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev, - u32 *mbus_code, u32 *field) -{ - if (mbus_code) - *mbus_code = csi_dev->bridge.mbus_format.code; - if (field) - *field = csi_dev->bridge.mbus_format.field; -} - /* Format */ static const struct sun6i_csi_bridge_format sun6i_csi_bridge_formats[] = { @@ -226,7 +206,8 @@ static void sun6i_csi_bridge_disable(struct sun6i_csi_device *csi_dev) } static void -sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev) +sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev, + const struct v4l2_mbus_framefmt *mbus_format) { struct device *dev = csi_dev->dev; struct regmap *regmap = csi_dev->regmap; @@ -234,11 +215,9 @@ sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev) &csi_dev->bridge.source_parallel.endpoint; unsigned char bus_width = endpoint->bus.parallel.bus_width; unsigned int flags = endpoint->bus.parallel.flags; - u32 field; + u32 field = mbus_format->field; u32 value = SUN6I_CSI_IF_CFG_IF_CSI; - sun6i_csi_bridge_format(csi_dev, NULL, &field); - if (field == V4L2_FIELD_INTERLACED || field == V4L2_FIELD_INTERLACED_TB || field == V4L2_FIELD_INTERLACED_BT) @@ -317,13 +296,12 @@ sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev) } static void -sun6i_csi_bridge_configure_mipi_csi2(struct sun6i_csi_device *csi_dev) +sun6i_csi_bridge_configure_mipi_csi2(struct sun6i_csi_device *csi_dev, + const struct v4l2_mbus_framefmt *mbus_format) { struct regmap *regmap = csi_dev->regmap; u32 value = SUN6I_CSI_IF_CFG_IF_MIPI; - u32 field; - - sun6i_csi_bridge_format(csi_dev, NULL, &field); + u32 field = mbus_format->field; if (field == V4L2_FIELD_INTERLACED || field == V4L2_FIELD_INTERLACED_TB || @@ -335,19 +313,20 @@ sun6i_csi_bridge_configure_mipi_csi2(struct sun6i_csi_device *csi_dev) regmap_write(regmap, SUN6I_CSI_IF_CFG_REG, value); } -static void sun6i_csi_bridge_configure_format(struct sun6i_csi_device *csi_dev) +static void +sun6i_csi_bridge_configure_format(struct sun6i_csi_device *csi_dev, + const struct v4l2_mbus_framefmt *mbus_format) { struct regmap *regmap = csi_dev->regmap; bool capture_streaming = csi_dev->capture.state.streaming; const struct sun6i_csi_bridge_format *bridge_format; const struct sun6i_csi_capture_format *capture_format; - u32 mbus_code, field, pixelformat; + u32 pixelformat; + u32 field = mbus_format->field; u8 input_format, input_yuv_seq, output_format; u32 value = 0; - sun6i_csi_bridge_format(csi_dev, &mbus_code, &field); - - bridge_format = sun6i_csi_bridge_format_find(mbus_code); + bridge_format = sun6i_csi_bridge_format_find(mbus_format->code); if (WARN_ON(!bridge_format)) return; @@ -391,16 +370,17 @@ static void sun6i_csi_bridge_configure_format(struct sun6i_csi_device *csi_dev) } static void sun6i_csi_bridge_configure(struct sun6i_csi_device *csi_dev, - struct sun6i_csi_bridge_source *source) + struct sun6i_csi_bridge_source *source, + const struct v4l2_mbus_framefmt *mbus_format) { struct sun6i_csi_bridge *bridge = &csi_dev->bridge; if (source == &bridge->source_parallel) - sun6i_csi_bridge_configure_parallel(csi_dev); + sun6i_csi_bridge_configure_parallel(csi_dev, mbus_format); else - sun6i_csi_bridge_configure_mipi_csi2(csi_dev); + sun6i_csi_bridge_configure_mipi_csi2(csi_dev, mbus_format); - sun6i_csi_bridge_configure_format(csi_dev); + sun6i_csi_bridge_configure_format(csi_dev, mbus_format); } /* V4L2 Subdev */ @@ -415,6 +395,8 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) struct sun6i_csi_bridge_source *source; struct v4l2_subdev *source_subdev; struct media_pad *remote_pad; + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *mbus_format; int ret; /* Source */ @@ -433,6 +415,10 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) else source = &bridge->source_mipi_csi2; + /* Active State */ + + state = v4l2_subdev_lock_and_get_active_state(subdev); + if (!on) { v4l2_subdev_call(source_subdev, video, s_stream, 0); ret = 0; @@ -443,7 +429,7 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) ret = pm_runtime_resume_and_get(dev); if (ret < 0) - return ret; + goto unlock; /* Clear */ @@ -451,7 +437,9 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) /* Configure */ - sun6i_csi_bridge_configure(csi_dev, source); + mbus_format = v4l2_subdev_state_get_format(state, + SUN6I_CSI_BRIDGE_PAD_SINK); + sun6i_csi_bridge_configure(csi_dev, source, mbus_format); if (capture_streaming) sun6i_csi_capture_configure(csi_dev); @@ -472,7 +460,8 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) if (ret && ret != -ENOIOCTLCMD) goto disable; - return 0; + ret = 0; + goto unlock; disable: if (capture_streaming) @@ -482,6 +471,8 @@ static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) pm_runtime_put(dev); +unlock: + v4l2_subdev_unlock_state(state); return ret; } @@ -504,21 +495,23 @@ sun6i_csi_bridge_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) static int sun6i_csi_bridge_init_state(struct v4l2_subdev *subdev, struct v4l2_subdev_state *state) { - struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); - unsigned int pad = SUN6I_CSI_BRIDGE_PAD_SINK; - struct v4l2_mbus_framefmt *mbus_format = - v4l2_subdev_state_get_format(state, pad); - struct mutex *lock = &csi_dev->bridge.lock; + unsigned int pad; - mutex_lock(lock); + /* + * This subdev does not perform format conversion, + * initialize both pads identically. + */ + for (pad = 0; pad < subdev->entity.num_pads; pad++) { + struct v4l2_mbus_framefmt *mbus_format; - mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code; - mbus_format->width = 1280; - mbus_format->height = 720; + mbus_format = v4l2_subdev_state_get_format(state, pad); - sun6i_csi_bridge_mbus_format_prepare(mbus_format); + mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code; + mbus_format->width = 1280; + mbus_format->height = 720; - mutex_unlock(lock); + sun6i_csi_bridge_mbus_format_prepare(mbus_format); + } return 0; } @@ -536,53 +529,32 @@ sun6i_csi_bridge_enum_mbus_code(struct v4l2_subdev *subdev, return 0; } -static int sun6i_csi_bridge_get_fmt(struct v4l2_subdev *subdev, - struct v4l2_subdev_state *state, - struct v4l2_subdev_format *format) -{ - struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi_dev->bridge.lock; - - mutex_lock(lock); - - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *mbus_format = *v4l2_subdev_state_get_format(state, - format->pad); - else - *mbus_format = csi_dev->bridge.mbus_format; - - mutex_unlock(lock); - - return 0; -} - static int sun6i_csi_bridge_set_fmt(struct v4l2_subdev *subdev, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); - struct v4l2_mbus_framefmt *mbus_format = &format->format; - struct mutex *lock = &csi_dev->bridge.lock; + struct v4l2_mbus_framefmt *fmt; - mutex_lock(lock); + /* The format on the source pad always matches the sink pad. */ + if (format->pad != SUN6I_CSI_BRIDGE_PAD_SINK) + return v4l2_subdev_get_fmt(subdev, state, format); - sun6i_csi_bridge_mbus_format_prepare(mbus_format); + sun6i_csi_bridge_mbus_format_prepare(&format->format); - if (format->which == V4L2_SUBDEV_FORMAT_TRY) - *v4l2_subdev_state_get_format(state, format->pad) = - *mbus_format; - else - csi_dev->bridge.mbus_format = *mbus_format; + /* Set the format on the sink pad. */ + fmt = v4l2_subdev_state_get_format(state, format->pad); + *fmt = format->format; - mutex_unlock(lock); + /* Propagate the format to the source pad. */ + fmt = v4l2_subdev_state_get_format(state, SUN6I_CSI_BRIDGE_PAD_SOURCE); + *fmt = format->format; return 0; } static const struct v4l2_subdev_pad_ops sun6i_csi_bridge_pad_ops = { .enum_mbus_code = sun6i_csi_bridge_enum_mbus_code, - .get_fmt = sun6i_csi_bridge_get_fmt, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = sun6i_csi_bridge_set_fmt, }; @@ -780,8 +752,6 @@ int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev) }; int ret; - mutex_init(&bridge->lock); - /* V4L2 Subdev */ v4l2_subdev_init(subdev, &sun6i_csi_bridge_subdev_ops); @@ -809,6 +779,12 @@ int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev) if (ret < 0) return ret; + /* V4L2 Subdev finalize */ + + ret = v4l2_subdev_init_finalize(subdev); + if (ret < 0) + goto error_media_entity; + /* V4L2 Subdev */ if (csi_dev->isp_available) @@ -818,7 +794,7 @@ int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev) if (ret) { dev_err(dev, "failed to register v4l2 subdev: %d\n", ret); - goto error_media_entity; + goto error_subdev_finalize; } /* V4L2 Async */ @@ -852,6 +828,9 @@ int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev) else v4l2_device_unregister_subdev(subdev); +error_subdev_finalize: + v4l2_subdev_cleanup(subdev); + error_media_entity: media_entity_cleanup(&subdev->entity); @@ -868,5 +847,7 @@ void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev) v4l2_device_unregister_subdev(subdev); + v4l2_subdev_cleanup(subdev); + media_entity_cleanup(&subdev->entity); } diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h index 44653b38f722..a5b0a6f064dd 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h @@ -42,20 +42,11 @@ struct sun6i_csi_bridge { struct v4l2_subdev subdev; struct v4l2_async_notifier notifier; struct media_pad pads[2]; - struct v4l2_mbus_framefmt mbus_format; - struct mutex lock; /* Mbus format lock. */ struct sun6i_csi_bridge_source source_parallel; struct sun6i_csi_bridge_source source_mipi_csi2; }; -/* Helpers */ - -void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev, - unsigned int *width, unsigned int *height); -void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev, - u32 *mbus_code, u32 *field); - /* Format */ const struct sun6i_csi_bridge_format * diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c index 65879f4802c0..d90abba21309 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c @@ -888,14 +888,19 @@ static int sun6i_csi_capture_link_validate(struct media_link *link) media_entity_to_video_device(link->sink->entity); struct sun6i_csi_device *csi_dev = video_get_drvdata(video_dev); struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct v4l2_subdev *src_subdev = + media_entity_to_v4l2_subdev(link->source->entity); const struct sun6i_csi_capture_format *capture_format; const struct sun6i_csi_bridge_format *bridge_format; unsigned int capture_width, capture_height; - unsigned int bridge_width, bridge_height; const struct v4l2_format_info *format_info; + struct v4l2_subdev_format src_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = link->source->index + }; u32 pixelformat, capture_field; - u32 mbus_code, bridge_field; bool match; + int ret; sun6i_csi_capture_dimensions(csi_dev, &capture_width, &capture_height); @@ -904,19 +909,22 @@ static int sun6i_csi_capture_link_validate(struct media_link *link) if (WARN_ON(!capture_format)) return -EINVAL; - sun6i_csi_bridge_dimensions(csi_dev, &bridge_width, &bridge_height); + /* Resolve csi bridge format. */ + ret = v4l2_subdev_call(src_subdev, pad, get_fmt, NULL, &src_fmt); + if (ret) + return ret; - sun6i_csi_bridge_format(csi_dev, &mbus_code, &bridge_field); - bridge_format = sun6i_csi_bridge_format_find(mbus_code); + bridge_format = sun6i_csi_bridge_format_find(src_fmt.format.code); if (WARN_ON(!bridge_format)) return -EINVAL; /* No cropping/scaling is supported. */ - if (capture_width != bridge_width || capture_height != bridge_height) { + if (capture_width != src_fmt.format.width || + capture_height != src_fmt.format.height) { v4l2_err(v4l2_dev, "invalid input/output dimensions: %ux%u/%ux%u\n", - bridge_width, bridge_height, capture_width, - capture_height); + src_fmt.format.width, src_fmt.format.height, + capture_width, capture_height); return -EINVAL; } @@ -947,7 +955,8 @@ static int sun6i_csi_capture_link_validate(struct media_link *link) /* With raw input mode, we need a 1:1 match between input and output. */ if (bridge_format->input_format == SUN6I_CSI_INPUT_FMT_RAW || capture_format->input_format_raw) { - match = sun6i_csi_capture_format_match(pixelformat, mbus_code); + match = sun6i_csi_capture_format_match(pixelformat, + src_fmt.format.code); if (!match) goto invalid; } From 0d2ca640318612402ae1c640d2da56b2914ff994 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Tue, 15 Sep 2026 15:45:30 +0800 Subject: [PATCH 216/596] media: i2c: alvium-csi2: balance runtime PM usage count on remove alvium_probe() increments the runtime PM usage count with pm_runtime_get_noresume() before enabling runtime PM. The probe error path correctly balances this reference with pm_runtime_put_noidle(), but the normal remove path only disables runtime PM. pm_runtime_disable() does not decrement the usage count, so a successful probe followed by driver removal leaves the runtime PM usage count unbalanced. Add the missing pm_runtime_put_noidle() to the remove path, matching the existing probe error cleanup. This issue was found by manual code inspection. Fixes: 0a7af872915e ("media: i2c: Add support for alvium camera") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Sakari Ailus --- drivers/media/i2c/alvium-csi2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/i2c/alvium-csi2.c b/drivers/media/i2c/alvium-csi2.c index f51f9b987759..f218999e0ed9 100644 --- a/drivers/media/i2c/alvium-csi2.c +++ b/drivers/media/i2c/alvium-csi2.c @@ -2523,6 +2523,7 @@ static void alvium_remove(struct i2c_client *client) * make sure to turn power off manually. */ pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); if (!pm_runtime_status_suspended(dev)) alvium_set_power(alvium, false); pm_runtime_set_suspended(dev); From 069a0343581bfcaee44bc598e2b95be2e2789807 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Tue, 15 Sep 2026 15:55:20 +0800 Subject: [PATCH 217/596] media: i2c: ar0521: remove duplicate media entity cleanup The error path taken when ar0521_power_on() fails calls media_entity_cleanup() at the disable label and then falls through to the entity_cleanup label, where media_entity_cleanup() is called again. There is no need to clean up the media entity twice. Remove the first call and let all error paths converge on the common entity_cleanup label. This issue was found by manual code inspection. Fixes: 852b50aeed15 ("media: On Semi AR0521 sensor driver") Signed-off-by: Guangshuo Li Signed-off-by: Sakari Ailus --- drivers/media/i2c/ar0521.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/media/i2c/ar0521.c b/drivers/media/i2c/ar0521.c index ed324c2d87aa..be68789eb8f5 100644 --- a/drivers/media/i2c/ar0521.c +++ b/drivers/media/i2c/ar0521.c @@ -1142,7 +1142,6 @@ static int ar0521_probe(struct i2c_client *client) disable: v4l2_async_unregister_subdev(&sensor->sd); - media_entity_cleanup(&sensor->sd.entity); free_ctrls: v4l2_ctrl_handler_free(&sensor->ctrls.handler); entity_cleanup: From a26c5ac46191ae34b3413f7a45d6241f63e15d42 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Tue, 15 Sep 2026 16:35:34 +0800 Subject: [PATCH 218/596] media: i2c: og01a1b: power off sensor on remove og01a1b_probe() powers the sensor on explicitly before identifying and initializing it. After a successful probe, runtime PM is enabled and pm_runtime_idle() is used to allow the runtime suspend callback to power the sensor off. The probe error path explicitly calls og01a1b_power_off(), but the normal remove path only disables runtime PM. pm_runtime_disable() does not guarantee that an active device is runtime suspended, so the sensor can remain powered when the driver is removed. This also leaves the xvclk enable performed by og01a1b_power_on() unbalanced. After disabling runtime PM, check whether the device is already suspended. If it is still active, power the sensor off explicitly and update the runtime PM state accordingly. Avoid powering it off again when runtime suspend has already done so. This issue was found by manual code inspection. Fixes: a95ffde28783 ("media: i2c: og01a1b: Add support of xvclk supply clock in power management") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Sakari Ailus --- drivers/media/i2c/og01a1b.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/media/i2c/og01a1b.c b/drivers/media/i2c/og01a1b.c index 1675f0460969..1cece90c59ee 100644 --- a/drivers/media/i2c/og01a1b.c +++ b/drivers/media/i2c/og01a1b.c @@ -956,6 +956,11 @@ static void og01a1b_remove(struct i2c_client *client) media_entity_cleanup(&sd->entity); v4l2_ctrl_handler_free(sd->ctrl_handler); pm_runtime_disable(og01a1b->dev); + + if (!pm_runtime_status_suspended(og01a1b->dev)) { + og01a1b_power_off(og01a1b->dev); + pm_runtime_set_suspended(og01a1b->dev); + } } static int og01a1b_probe(struct i2c_client *client) From 06d4f202f007b160e650e8dd861a5fc223024f34 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Tue, 15 Sep 2026 16:50:31 +0800 Subject: [PATCH 219/596] media: i2c: ov2735: fix power cleanup on remove ov2735_probe() explicitly powers on the sensor before enabling runtime PM. The probe failure paths call ov2735_power_off(), but the remove path does not perform the corresponding power-off operation. The managed runtime PM helpers only clean up the runtime PM state. devm_pm_runtime_set_active_enabled() disables runtime PM and restores the suspended state during device resource release, while devm_pm_runtime_get_noresume() drops the runtime PM usage reference. Neither helper invokes ov2735_power_off(). As a result, removing the driver after a successful probe can leave the sensor clock and regulators enabled and the GPIOs in the powered state. Call ov2735_power_off() from ov2735_remove() to match the successful ov2735_power_on() performed during probe. This issue was found by manual code inspection. Fixes: fa9e6df636fb ("media: i2c: add ov2735 image sensor driver") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov2735.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/i2c/ov2735.c b/drivers/media/i2c/ov2735.c index dcb1add1fd9f..2defcaa8b795 100644 --- a/drivers/media/i2c/ov2735.c +++ b/drivers/media/i2c/ov2735.c @@ -1081,6 +1081,7 @@ static void ov2735_remove(struct i2c_client *client) v4l2_subdev_cleanup(&ov2735->sd); media_entity_cleanup(&sd->entity); v4l2_ctrl_handler_free(ov2735->sd.ctrl_handler); + ov2735_power_off(ov2735->dev); } static DEFINE_RUNTIME_DEV_PM_OPS(ov2735_pm_ops, From 2695038ac43cf63b95580544a4e9d1031660aa99 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Tue, 15 Sep 2026 17:19:20 +0800 Subject: [PATCH 220/596] media: i2c: t4ka3: drop unmatched runtime PM put t4ka3_probe() marks the device runtime active and enables runtime PM, but does not increment the runtime PM usage counter. Nevertheless, the probe failure path calls pm_runtime_put_noidle(), which has no matching runtime PM get operation. pm_runtime_put_noidle() currently does not decrement usage_count when it is already zero, so the call is ineffective. Remove the unmatched put to keep the runtime PM reference handling balanced and avoid misleading cleanup code. This issue was found by manual code inspection. Fixes: fd55319692151 ("media: Add t4ka3 camera sensor driver") Signed-off-by: Guangshuo Li Signed-off-by: Sakari Ailus --- drivers/media/i2c/t4ka3.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/media/i2c/t4ka3.c b/drivers/media/i2c/t4ka3.c index a5a68e3fbec2..bf48a56f5a91 100644 --- a/drivers/media/i2c/t4ka3.c +++ b/drivers/media/i2c/t4ka3.c @@ -1034,7 +1034,6 @@ static int t4ka3_probe(struct i2c_client *client) err_pm_disable: pm_runtime_disable(&client->dev); - pm_runtime_put_noidle(&client->dev); t4ka3_pm_suspend(&client->dev); return ret; From 22412a2031738f239cdc9b7b3c9f5c25aef83626 Mon Sep 17 00:00:00 2001 From: Shravan Chippa Date: Mon, 3 Aug 2026 11:20:34 +0530 Subject: [PATCH 221/596] media: i2c: imx334: add new link frequency configuration Add 222 MHz link frequency support and refactor clock registers into per-frequency register lists. Introduce imx334_clk_params to map each link frequency to its register set and supported resolution range. Make the link frequency control writable so userspace can select the desired operating point. Signed-off-by: Shravan Chippa Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx334.c | 132 +++++++++++++++++++++++++++++++------ 1 file changed, 112 insertions(+), 20 deletions(-) diff --git a/drivers/media/i2c/imx334.c b/drivers/media/i2c/imx334.c index 553a16b84f4d..fb036873859c 100644 --- a/drivers/media/i2c/imx334.c +++ b/drivers/media/i2c/imx334.c @@ -109,6 +109,7 @@ /* CSI2 HW configuration */ #define IMX334_LINK_FREQ_891M 891000000 #define IMX334_LINK_FREQ_445M 445500000 +#define IMX334_LINK_FREQ_222M 222750000 #define IMX334_NUM_DATA_LANES 4 #define IMX334_REG_MIN 0x00 @@ -154,7 +155,6 @@ struct imx334_reg_list { * @vblank_min: Minimal vertical blanking in lines * @vblank_max: Maximum vertical blanking in lines * @pclk: Sensor pixel clock - * @link_freq_idx: Link frequency index * @reg_list: Register list for sensor mode */ struct imx334_mode { @@ -165,7 +165,28 @@ struct imx334_mode { u32 vblank_min; u32 vblank_max; u64 pclk; - u32 link_freq_idx; + struct imx334_reg_list reg_list; +}; + +/** + * struct imx334_clk_params - imx334 sensor clock parameters + * @data_rate_per_lane: Data rate per lane in bits per second + * @link_freq: Link frequency in Hz + * @width_max: Maximum image width in pixels + * @height_max: Maximum image height in pixels + * @width_min: Minimum image width in pixels + * @height_min: Minimum image height in pixels + * @default_mode: Pointer to the default sensor mode + * @reg_list: Register list for clock configuration + */ +struct imx334_clk_params { + u32 data_rate_per_lane; + u32 link_freq; + u32 width_max; + u32 height_max; + u32 width_min; + u32 height_min; + const struct imx334_mode *default_mode; struct imx334_reg_list reg_list; }; @@ -216,6 +237,7 @@ struct imx334 { static const s64 link_freq[] = { IMX334_LINK_FREQ_891M, IMX334_LINK_FREQ_445M, + IMX334_LINK_FREQ_222M, }; /* Sensor common mode registers values */ @@ -233,13 +255,6 @@ static const struct cci_reg_sequence common_mode_regs[] = { { IMX334_REG_UNREAD_PARAM6, 0x0008 }, { IMX334_REG_XVS_XHS_OUTSEL, 0x20 }, { IMX334_REG_XVS_XHS_DRV, 0x0f }, - { IMX334_REG_BCWAIT_TIME, 0x3b }, - { IMX334_REG_CPWAIT_TIME, 0x2a }, - { IMX334_REG_INCKSEL1, 0x0129 }, - { IMX334_REG_INCKSEL2, 0x06 }, - { IMX334_REG_INCKSEL3, 0xa0 }, - { IMX334_REG_INCKSEL4, 0x7e }, - { IMX334_REG_SYS_MODE, 0x02 }, { IMX334_REG_HADD_VADD, 0x00 }, { IMX334_REG_VALID_EXPAND, 0x03 }, { IMX334_REG_TCYCLE, 0x00 }, @@ -397,6 +412,39 @@ static const struct cci_reg_sequence mode_3840x2160_regs[] = { { IMX334_REG_TPLX, 0x005f }, }; +/* Data rate 1782Mbps per lane and 891Mhz link frequency */ +static const struct cci_reg_sequence link_freq_891m_regs[] = { + { IMX334_REG_BCWAIT_TIME, 0x3b }, + { IMX334_REG_CPWAIT_TIME, 0x2a }, + { IMX334_REG_INCKSEL1, 0x0129 }, + { IMX334_REG_INCKSEL2, 0x02 }, + { IMX334_REG_INCKSEL3, 0xa0 }, + { IMX334_REG_INCKSEL4, 0x7e }, + { IMX334_REG_SYS_MODE, 0x00 }, +}; + +/* Data rate 891Mbps per lane and 445Mhz link frequency */ +static const struct cci_reg_sequence link_freq_445m_regs[] = { + { IMX334_REG_BCWAIT_TIME, 0x3b }, + { IMX334_REG_CPWAIT_TIME, 0x2a }, + { IMX334_REG_INCKSEL1, 0x0129 }, + { IMX334_REG_INCKSEL2, 0x06 }, + { IMX334_REG_INCKSEL3, 0xa0 }, + { IMX334_REG_INCKSEL4, 0x7e }, + { IMX334_REG_SYS_MODE, 0x02 }, +}; + +/* Data rate 445Mbps per lane and 222Mhz link frequency */ +static const struct cci_reg_sequence link_freq_222m_regs[] = { + { IMX334_REG_BCWAIT_TIME, 0x3b }, + { IMX334_REG_CPWAIT_TIME, 0x2a }, + { IMX334_REG_INCKSEL1, 0x0129 }, + { IMX334_REG_INCKSEL2, 0x0a }, + { IMX334_REG_INCKSEL3, 0xa0 }, + { IMX334_REG_INCKSEL4, 0x7e }, + { IMX334_REG_SYS_MODE, 0x02 }, +}; + static const char * const imx334_test_pattern_menu[] = { "Disabled", "Vertical Color Bars", @@ -442,7 +490,6 @@ static const struct imx334_mode supported_modes[] = { .vblank_min = 90, .vblank_max = 132840, .pclk = 594000000, - .link_freq_idx = 0, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_3840x2160_regs), .regs = mode_3840x2160_regs, @@ -455,7 +502,6 @@ static const struct imx334_mode supported_modes[] = { .vblank_min = 45, .vblank_max = 132840, .pclk = 297000000, - .link_freq_idx = 1, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs), .regs = mode_1920x1080_regs, @@ -468,7 +514,6 @@ static const struct imx334_mode supported_modes[] = { .vblank_min = 45, .vblank_max = 132840, .pclk = 297000000, - .link_freq_idx = 1, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), .regs = mode_1280x720_regs, @@ -481,7 +526,6 @@ static const struct imx334_mode supported_modes[] = { .vblank_min = 45, .vblank_max = 132840, .pclk = 297000000, - .link_freq_idx = 1, .reg_list = { .num_of_regs = ARRAY_SIZE(mode_640x480_regs), .regs = mode_640x480_regs, @@ -489,6 +533,46 @@ static const struct imx334_mode supported_modes[] = { }, }; +static const struct imx334_clk_params imx334_clk_params[] = { + { + .data_rate_per_lane = 1782000000, + .link_freq = IMX334_LINK_FREQ_891M, + .width_max = 3840, + .height_max = 2160, + .width_min = 3840, + .height_min = 2160, + .default_mode = &supported_modes[0], /* 3840x2160 */ + .reg_list = { + .num_of_regs = ARRAY_SIZE(link_freq_891m_regs), + .regs = link_freq_891m_regs, + }, + }, { + .data_rate_per_lane = 891000000, + .link_freq = IMX334_LINK_FREQ_445M, + .width_max = 1920, + .height_max = 1080, + .width_min = 640, + .height_min = 480, + .default_mode = &supported_modes[1], /* 1920x1080 */ + .reg_list = { + .num_of_regs = ARRAY_SIZE(link_freq_445m_regs), + .regs = link_freq_445m_regs, + }, + }, { + .data_rate_per_lane = 445500000, + .link_freq = IMX334_LINK_FREQ_222M, + .width_max = 1920, + .height_max = 1080, + .width_min = 640, + .height_min = 480, + .default_mode = &supported_modes[1], /* 1920x1080 */ + .reg_list = { + .num_of_regs = ARRAY_SIZE(link_freq_222m_regs), + .regs = link_freq_222m_regs, + }, + } +}; + /** * to_imx334() - imv334 V4L2 sub-device to imx334 device. * @subdev: pointer to imx334 V4L2 sub-device @@ -512,10 +596,6 @@ static int imx334_update_controls(struct imx334 *imx334, { int ret; - ret = __v4l2_ctrl_s_ctrl(imx334->link_freq_ctrl, mode->link_freq_idx); - if (ret) - return ret; - ret = __v4l2_ctrl_modify_range(imx334->pclk_ctrl, mode->pclk, mode->pclk, 1, mode->pclk); if (ret) @@ -746,6 +826,7 @@ static int imx334_set_pad_format(struct v4l2_subdev *sd, { struct imx334 *imx334 = to_imx334(sd); const struct imx334_mode *mode; + const struct imx334_clk_params *clk_params; int ret = 0; mode = v4l2_find_nearest_size(supported_modes, @@ -753,6 +834,11 @@ static int imx334_set_pad_format(struct v4l2_subdev *sd, width, height, fmt->format.width, fmt->format.height); + clk_params = &imx334_clk_params[imx334->link_freq_ctrl->val]; + if (mode->width > clk_params->width_max || mode->height > clk_params->height_max || + mode->width < clk_params->width_min || mode->height < clk_params->height_min) + mode = clk_params->default_mode; + imx334_fill_pad_format(imx334, mode, fmt); fmt->format.code = imx334_get_format_code(imx334, fmt->format.code); @@ -824,6 +910,15 @@ static int imx334_enable_streams(struct v4l2_subdev *sd, goto err_rpm_put; } + /* Write sensor link freq registers */ + reg_list = &imx334_clk_params[imx334->link_freq_ctrl->val].reg_list; + ret = cci_multi_reg_write(imx334->cci, reg_list->regs, + reg_list->num_of_regs, NULL); + if (ret) { + dev_err(imx334->dev, "fail to write initial registers\n"); + goto err_rpm_put; + } + /* Write sensor mode registers */ reg_list = &imx334->cur_mode->reg_list; ret = cci_multi_reg_write(imx334->cci, reg_list->regs, @@ -1096,9 +1191,6 @@ static int imx334_init_controls(struct imx334 *imx334) __ffs(imx334->link_freq_bitmap), link_freq); - if (imx334->link_freq_ctrl) - imx334->link_freq_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; - imx334->hblank_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, &imx334_ctrl_ops, V4L2_CID_HBLANK, From 7c68f23334a6a0b8b5a1f90dee753b5cfad83304 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Mon, 7 Sep 2026 18:25:03 +0800 Subject: [PATCH 222/596] media: i2c: cvs: Add Lattice device quirk for VID:0x2ac1 PID:0x20d1 On Dell laptops such as the XPS 14 (DA14250), the IPU6 camera fails to initialize due to a missing device quirk for the Lattice device. The kernel logs the following error during probe: [ 7.299665] intel_cvs i2c-INTC10E0:00: No quirks for device (VID:0x2ac1 PID:0x20d1) [ 7.299976] intel_cvs i2c-INTC10E0:00: error -EREMOTEIO: get caps failed [ 7.300026] intel_cvs i2c-INTC10E0:00: probe with driver intel_cvs failed with error -121 Add a matching quirk entry for VID 0x2ac1 and PID 0x20d1 to allow intel_cvs to configure the device properly and enable the camera. Link: https://bugzilla.redhat.com/show_bug.cgi?id=2526246 Signed-off-by: Kate Hsuan Signed-off-by: Sakari Ailus --- drivers/media/i2c/cvs/core.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c index 98cadd58a075..49bee9e3a43b 100644 --- a/drivers/media/i2c/cvs/core.c +++ b/drivers/media/i2c/cvs/core.c @@ -69,6 +69,13 @@ static const struct icvs_device_quirk cvs_quirk_table[] = { ICVS_NO_CAPS | ICVS_NO_FW_UPDATE }, /* Lattice NX33 */ + { 0x2ac1, 0x20d1, ICVS_NO_MIPI_CONFIG | + ICVS_NO_CAPS | + ICVS_NO_FW_UPDATE + }, /* + * The Lattice device was found on a Dell laptop + * XPS 14 (Dell 14 Premium) DA14250 + */ { 0x06CB, 0x0701, ICVS_SKIP_FW_RESET | ICVS_HOST_SENSOR_PWR_CTRL | ICVS_HOST_PRIV_CTRL | From eae3df07eb2f08f5c696aa8322fe2432417b38f4 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Mon, 7 Sep 2026 18:25:04 +0800 Subject: [PATCH 223/596] media: ipu-bridge: Add sensor rotation quirk for Dell XPS 14 (Dell 14 Premium) DA14250 Add "Dell XPS 14 (Dell 14 Premium) DA14250" to the list of systems requiring a 180-degree sensor rotation quirk. This resolves an issue where the IPU camera stream is displayed upside-down. Link: https://bugzilla.redhat.com/show_bug.cgi?id=2510889 Signed-off-by: Kate Hsuan Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 952868ad8d5f..e39697335c92 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -143,6 +143,13 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { }, .driver_data = "OVTI02C1", }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 14 (Dell 14 Premium) DA14250"), + }, + .driver_data = "OVTI02C1", + }, { .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), From 49b410966014ac92523327777385e109109715a9 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:05 +0300 Subject: [PATCH 224/596] media: rcar-vin: Fix comment related to stride handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment says the stride has to be aligned to 16 bytes, but it should be 16 pixels, as VNIS_REG register is in pixels. Fixes: 40c6da8a9c0f ("media: renesas: vin: Fix RAW8 (again)") Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- drivers/media/platform/renesas/rcar-vin/rcar-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index 73cda0e2d45a..5e515d16c164 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -678,7 +678,7 @@ void rvin_crop_scale_comp(struct rvin_dev *vin) /* * VNIS_REG has four lowest bits always 0, i.e. the stride has to be - * aligned to 16 bytes. This is done in rvin_format_bytesperline(). + * aligned to 16 pixels. This is done in rvin_format_bytesperline(). */ fmt = rvin_format_from_pixel(vin, vin->format.pixelformat); From 6e9b75f5d2e616d5250743b189e313c855284504 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:06 +0300 Subject: [PATCH 225/596] media: rcar-vin: Link VINs on Gen3 to a single channel on each CSI-2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stream routing will be fully configurable when we add full streams support to the rcar pipeline. Thus there is no need for the user to be able to link VINs to different CSI-2 channels. In fact, allowing e.g. VIN0 to be connected to CSI-2 channel 3 would be wrong, as the hardware doesn't allow that. Change rvin_csi2_create_link() so that it creates media links only between matching VINs and CSI-2 channels (VIN0 - channel 0, VIN1 - channel 1, etc.). Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- .../platform/renesas/rcar-vin/rcar-core.c | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index c8d564aa1eba..2fcea715101c 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -673,23 +673,26 @@ static int rvin_csi2_create_link(struct rvin_group *group, unsigned int id, struct media_entity *source = &group->remotes[route->csi].subdev->entity; struct media_entity *sink = &group->vin[id]->vdev.entity; struct media_pad *sink_pad = &sink->pads[0]; + struct media_pad *source_pad; + unsigned int source_idx; unsigned int channel; - int ret; - for (channel = 0; channel < 4; channel++) { - unsigned int source_idx = rvin_group_csi_channel_to_pad(channel); - struct media_pad *source_pad = &source->pads[source_idx]; + /* + * The channels from CSI-2 blocks and the VIN groups have a set of + * hardcoded routing options to choose from. We only support the routing + * where all VINs in a group are connected to the same CSI-2 block, + * and the Nth VIN in the group is connected to the Nth CSI-2 channel. + */ - /* Skip if link already exists. */ - if (media_entity_find_link(source_pad, sink_pad)) - continue; + channel = id % 4; + source_idx = rvin_group_csi_channel_to_pad(channel); + source_pad = &source->pads[source_idx]; - ret = media_create_pad_link(source, source_idx, sink, 0, 0); - if (ret) - return ret; - } + /* Skip if link already exists. */ + if (media_entity_find_link(source_pad, sink_pad)) + return 0; - return 0; + return media_create_pad_link(source, source_idx, sink, 0, 0); } static int rvin_parallel_setup_links(struct rvin_group *group) From 720531bddc8683e73ce461d64d3c778794284b56 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:07 +0300 Subject: [PATCH 226/596] media: rcar-isp: Move {enable|disable}_streams() calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With multiple streams the operation to enable the ISP hardware and to call {enable|disable}_streams() on upstream subdev will need to be handled separately. Prepare for that by moving {enable|disable}_streams() calls out from risp_start() and risp_stop(). On Gen4, a side effect of this change is that if the sink side devices call .enable_streams() on rcar-isp multiple times, the second call will fail. This is because we always use stream ID 0, so the second call would attempt to enable the same stream again, leading to an error. In other words, a normal single-stream setup continues to work, but trying to use the current driver's custom VC based routing will fail. Reviewed-by: Laurent Pinchart Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- .../media/platform/renesas/rcar-isp/csisp.c | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-isp/csisp.c b/drivers/media/platform/renesas/rcar-isp/csisp.c index 53ce47020d17..97ddae942fe3 100644 --- a/drivers/media/platform/renesas/rcar-isp/csisp.c +++ b/drivers/media/platform/renesas/rcar-isp/csisp.c @@ -268,18 +268,11 @@ static int risp_start(struct rcar_isp *isp, struct v4l2_subdev_state *state) /* Start ISP. */ risp_write_cs(isp, ISPSTART_REG, ISPSTART_START); - ret = v4l2_subdev_enable_streams(isp->remote, isp->remote_pad, - BIT_ULL(0)); - if (ret) - risp_power_off(isp); - - return ret; + return 0; } static void risp_stop(struct rcar_isp *isp) { - v4l2_subdev_disable_streams(isp->remote, isp->remote_pad, BIT_ULL(0)); - /* Stop ISP. */ risp_write_cs(isp, ISPSTART_REG, ISPSTART_STOP); @@ -291,7 +284,7 @@ static int risp_enable_streams(struct v4l2_subdev *sd, u64 source_streams_mask) { struct rcar_isp *isp = sd_to_isp(sd); - int ret = 0; + int ret; if (source_streams_mask != 1) return -EINVAL; @@ -305,9 +298,17 @@ static int risp_enable_streams(struct v4l2_subdev *sd, return ret; } + ret = v4l2_subdev_enable_streams(isp->remote, isp->remote_pad, + BIT_ULL(0)); + if (ret) { + if (isp->stream_count == 0) + risp_stop(isp); + return ret; + } + isp->stream_count += 1; - return ret; + return 0; } static int risp_disable_streams(struct v4l2_subdev *sd, @@ -315,6 +316,7 @@ static int risp_disable_streams(struct v4l2_subdev *sd, u64 source_streams_mask) { struct rcar_isp *isp = sd_to_isp(sd); + int ret; if (source_streams_mask != 1) return -EINVAL; @@ -322,6 +324,11 @@ static int risp_disable_streams(struct v4l2_subdev *sd, if (!isp->remote) return -ENODEV; + ret = v4l2_subdev_disable_streams(isp->remote, isp->remote_pad, + BIT_ULL(0)); + if (ret) + return ret; + if (isp->stream_count == 1) risp_stop(isp); From f8140e71fc39069372eab67185279aae10cbc67c Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:08 +0300 Subject: [PATCH 227/596] media: rcar-csi2: Move {enable|disable}_streams() calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With multiple streams the operation to enable the CSI-2 hardware and to call {enable|disable}_streams() on upstream subdev will need to be handled separately. Prepare for that by moving {enable|disable}_streams() calls out from rcsi2_start() and rcsi2_stop(). As the error recovery in rcsi2_irq_thread() relies on rcsi2_start() and rcsi2_stop(), we need to modify rcsi2_irq_thread() accordingly. On Gen3, a side effect of this change is that if the sink side devices call .enable_streams() on rcar-csi2 multiple times, the second call will fail. This is because we always use stream ID 0, so the second call would attempt to enable the same stream again, leading to an error. In other words, a normal single-stream setup continues to work, but trying to use the current driver's custom VC based routing will fail. On Gen4, this doesn't matter as the rcar-isp behaves in a similar way as described above, and thus rcar-csi2 will only get a single .enable_streams() call. Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- drivers/media/platform/renesas/rcar-csi2.c | 63 +++++++++++++++++----- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 6635f5782175..1c8150af734f 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -1822,20 +1822,12 @@ static int rcsi2_start(struct rcar_csi2 *priv, struct v4l2_subdev_state *state) return ret; } - ret = v4l2_subdev_enable_streams(priv->remote, priv->remote_pad, - BIT_ULL(0)); - if (ret) { - rcsi2_enter_standby(priv); - return ret; - } - return 0; } static void rcsi2_stop(struct rcar_csi2 *priv) { rcsi2_enter_standby(priv); - v4l2_subdev_disable_streams(priv->remote, priv->remote_pad, BIT_ULL(0)); } static int rcsi2_enable_streams(struct v4l2_subdev *sd, @@ -1857,6 +1849,14 @@ static int rcsi2_enable_streams(struct v4l2_subdev *sd, return ret; } + ret = v4l2_subdev_enable_streams(priv->remote, priv->remote_pad, + BIT_ULL(0)); + if (ret) { + if (priv->stream_count == 0) + rcsi2_stop(priv); + return ret; + } + priv->stream_count += 1; return ret; @@ -1867,7 +1867,7 @@ static int rcsi2_disable_streams(struct v4l2_subdev *sd, u32 source_pad, u64 source_streams_mask) { struct rcar_csi2 *priv = sd_to_csi2(sd); - int ret = 0; + int ret; if (source_streams_mask != 1) return -EINVAL; @@ -1878,9 +1878,14 @@ static int rcsi2_disable_streams(struct v4l2_subdev *sd, if (priv->stream_count == 1) rcsi2_stop(priv); + ret = v4l2_subdev_disable_streams(priv->remote, priv->remote_pad, + BIT_ULL(0)); + if (ret) + return ret; + priv->stream_count -= 1; - return ret; + return 0; } static int rcsi2_set_pad_format(struct v4l2_subdev *sd, @@ -1971,14 +1976,44 @@ static irqreturn_t rcsi2_irq_thread(int irq, void *data) { struct v4l2_subdev_state *state; struct rcar_csi2 *priv = data; + int ret; state = v4l2_subdev_lock_and_get_active_state(&priv->subdev); - rcsi2_stop(priv); - usleep_range(1000, 2000); - if (rcsi2_start(priv, state)) - dev_warn(priv->dev, "Failed to restart CSI-2 receiver\n"); + if (priv->stream_count == 0) + goto out; + rcsi2_stop(priv); + + ret = v4l2_subdev_disable_streams(priv->remote, priv->remote_pad, + BIT_ULL(0)); + if (ret) { + dev_warn(priv->dev, + "Error recovery: failed to disable streams: %d\n", + ret); + goto out; + } + + usleep_range(1000, 2000); + + ret = rcsi2_start(priv, state); + if (ret) { + dev_warn(priv->dev, + "Error recovery: failed to start CSI-2 receiver: %d\n", + ret); + goto out; + } + + ret = v4l2_subdev_enable_streams(priv->remote, priv->remote_pad, + BIT_ULL(0)); + if (ret) { + dev_warn(priv->dev, + "Error recovery: failed to start streams: %d\n", + ret); + goto out; + } + +out: v4l2_subdev_unlock_state(state); return IRQ_HANDLED; From 4b08253ab04c259a6f322f456aeffd2a181732e9 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:09 +0300 Subject: [PATCH 228/596] media: rcar-csi2: Switch to Streams API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch to Streams API with a single hardcoded route. For single-stream use case there should be no change in behavior. Reviewed-by: Laurent Pinchart Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- drivers/media/platform/renesas/rcar-csi2.c | 64 +++++++++++++++++----- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 1c8150af734f..c064979003ed 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -1023,17 +1023,24 @@ static int rcsi2_calc_mbps(struct rcar_csi2 *priv, */ freq = v4l2_get_link_freq(remote_pad, 0, 0); if (freq < 0) { + const struct v4l2_subdev_route *route; const struct rcar_csi2_format *format; const struct v4l2_mbus_framefmt *fmt; unsigned int lanes; unsigned int bpp; int ret; + if (state->routing.num_routes != 1) + return -EINVAL; + ret = rcsi2_get_active_lanes(priv, &lanes); if (ret) return ret; - fmt = v4l2_subdev_state_get_format(state, RCAR_CSI2_SINK); + route = &state->routing.routes[0]; + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); if (!fmt) return -EINVAL; @@ -1062,6 +1069,7 @@ static int rcsi2_calc_mbps(struct rcar_csi2 *priv, static int rcsi2_start_receiver_gen3(struct rcar_csi2 *priv, struct v4l2_subdev_state *state) { + const struct v4l2_subdev_route *route; const struct rcar_csi2_format *format; u32 phycnt, vcdt = 0, vcdt2 = 0, fld = 0; const struct v4l2_mbus_framefmt *fmt; @@ -1070,7 +1078,16 @@ static int rcsi2_start_receiver_gen3(struct rcar_csi2 *priv, int mbps, ret; /* Use the format on the sink pad to compute the receiver config. */ - fmt = v4l2_subdev_state_get_format(state, RCAR_CSI2_SINK); + + if (state->routing.num_routes != 1) + return -EINVAL; + + route = &state->routing.routes[0]; + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + if (!fmt) + return -EINVAL; dev_dbg(priv->dev, "Input size (%ux%u%c)\n", fmt->width, fmt->height, @@ -1892,8 +1909,7 @@ static int rcsi2_set_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - struct rcar_csi2 *priv = sd_to_csi2(sd); - unsigned int num_pads = rcsi2_num_pads(priv); + struct v4l2_mbus_framefmt *fmt; if (format->pad > RCAR_CSI2_SINK) return v4l2_subdev_get_fmt(sd, state, format); @@ -1901,11 +1917,20 @@ static int rcsi2_set_pad_format(struct v4l2_subdev *sd, if (!rcsi2_code_to_fmt(format->format.code)) format->format.code = rcar_csi2_formats[0].code; - *v4l2_subdev_state_get_format(state, format->pad) = format->format; + /* Set sink format. */ + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; - /* Propagate the format to the source pads. */ - for (unsigned int i = RCAR_CSI2_SOURCE_VC0; i < num_pads; i++) - *v4l2_subdev_state_get_format(state, i) = format->format; + *fmt = format->format; + + /* Propagate the format to the source pad. */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; return 0; } @@ -1925,8 +1950,15 @@ static const struct v4l2_subdev_ops rcar_csi2_subdev_ops = { static int rcsi2_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { - struct rcar_csi2 *priv = sd_to_csi2(sd); - unsigned int num_pads = rcsi2_num_pads(priv); + static struct v4l2_subdev_route routes[] = { + { + .sink_pad = RCAR_CSI2_SINK, + .sink_stream = 0, + .source_pad = RCAR_CSI2_SOURCE_VC0, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; static const struct v4l2_mbus_framefmt rcar_csi2_default_fmt = { .width = 1920, @@ -1939,10 +1971,13 @@ static int rcsi2_init_state(struct v4l2_subdev *sd, .xfer_func = V4L2_XFER_FUNC_DEFAULT, }; - for (unsigned int i = RCAR_CSI2_SINK; i < num_pads; i++) - *v4l2_subdev_state_get_format(state, i) = rcar_csi2_default_fmt; + static const struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; - return 0; + return v4l2_subdev_set_routing_with_fmt(sd, state, &routing, + &rcar_csi2_default_fmt); } static const struct v4l2_subdev_internal_ops rcar_csi2_internal_ops = { @@ -2625,7 +2660,8 @@ static int rcsi2_probe(struct platform_device *pdev) v4l2_set_subdevdata(&priv->subdev, &pdev->dev); snprintf(priv->subdev.name, sizeof(priv->subdev.name), "%s %s", KBUILD_MODNAME, dev_name(&pdev->dev)); - priv->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + priv->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_STREAMS; priv->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; priv->subdev.entity.ops = &rcar_csi2_entity_ops; From bfcdb1ec23d45732da4650c12620c873c332cb5f Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:10 +0300 Subject: [PATCH 229/596] media: rcar-isp: Switch to Streams API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch to Streams API with a single hardcoded route. For single-stream use case there should be no change in behavior. Reviewed-by: Laurent Pinchart Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- .../media/platform/renesas/rcar-isp/csisp.c | 69 +++++++++++++++++-- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-isp/csisp.c b/drivers/media/platform/renesas/rcar-isp/csisp.c index 97ddae942fe3..97465467d047 100644 --- a/drivers/media/platform/renesas/rcar-isp/csisp.c +++ b/drivers/media/platform/renesas/rcar-isp/csisp.c @@ -123,6 +123,17 @@ static const struct rcar_isp_format rcar_isp_formats[] = { }, }; +static const struct v4l2_mbus_framefmt risp_default_fmt = { + .width = 1920, + .height = 1080, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .colorspace = V4L2_COLORSPACE_SRGB, + .field = V4L2_FIELD_NONE, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + static const struct rcar_isp_format *risp_code_to_fmt(unsigned int code) { unsigned int i; @@ -216,13 +227,20 @@ static void risp_power_off(struct rcar_isp *isp) static int risp_start(struct rcar_isp *isp, struct v4l2_subdev_state *state) { + const struct v4l2_subdev_route *route; const struct v4l2_mbus_framefmt *fmt; const struct rcar_isp_format *format; unsigned int vc; u32 sel_csi = 0; int ret; - fmt = v4l2_subdev_state_get_format(state, RCAR_ISP_SINK); + if (state->routing.num_routes != 1) + return -EINVAL; + + route = &state->routing.routes[0]; + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); if (!fmt) return -EINVAL; @@ -341,7 +359,7 @@ static int risp_set_pad_format(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - struct v4l2_mbus_framefmt *framefmt; + struct v4l2_mbus_framefmt *fmt; if (format->pad > RCAR_ISP_SINK) return v4l2_subdev_get_fmt(sd, state, format); @@ -349,10 +367,20 @@ static int risp_set_pad_format(struct v4l2_subdev *sd, if (!risp_code_to_fmt(format->format.code)) format->format.code = rcar_isp_formats[0].code; - for (unsigned int i = 0; i < RCAR_ISP_NUM_PADS; i++) { - framefmt = v4l2_subdev_state_get_format(state, i); - *framefmt = format->format; - } + /* Set sink format. */ + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + /* Propagate the format to the source pad. */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; return 0; } @@ -369,6 +397,32 @@ static const struct v4l2_subdev_ops rcar_isp_subdev_ops = { .pad = &risp_pad_ops, }; +static int risp_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + static struct v4l2_subdev_route routes[] = { + { + .sink_pad = RCAR_ISP_SINK, + .sink_stream = 0, + .source_pad = RCAR_ISP_PORT0, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + static const struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return v4l2_subdev_set_routing_with_fmt(sd, state, &routing, + &risp_default_fmt); +} + +static const struct v4l2_subdev_internal_ops risp_internal_ops = { + .init_state = risp_init_state, +}; + /* ----------------------------------------------------------------------------- * Async handling and registration of subdevices and links */ @@ -541,11 +595,12 @@ static int risp_probe(struct platform_device *pdev) isp->subdev.owner = THIS_MODULE; isp->subdev.dev = &pdev->dev; + isp->subdev.internal_ops = &risp_internal_ops; v4l2_subdev_init(&isp->subdev, &rcar_isp_subdev_ops); v4l2_set_subdevdata(&isp->subdev, &pdev->dev); snprintf(isp->subdev.name, sizeof(isp->subdev.name), "%s %s", KBUILD_MODNAME, dev_name(&pdev->dev)); - isp->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + isp->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; isp->subdev.entity.function = MEDIA_ENT_F_VID_MUX; isp->subdev.entity.ops = &risp_entity_ops; From 28d399cc67af8d2a0b47f97a6bf147a8b5810f57 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:11 +0300 Subject: [PATCH 230/596] media: rcar-csi2: Add .get_frame_desc op MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add v4l2_subdev_pad_ops.get_frame_desc() implementation. We also implement a fallback for the case where the upstream subdevice does not implement .get_frame_desc. It assumes a single stream with VC = 0 and DT based on the configured stream mbus format. Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- drivers/media/platform/renesas/rcar-csi2.c | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index c064979003ed..7bff2fe8daad 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -1935,12 +1935,89 @@ static int rcsi2_set_pad_format(struct v4l2_subdev *sd, return 0; } +static int rcsi2_get_frame_desc_fallback(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct v4l2_subdev_route *route; + const struct rcar_csi2_format *format; + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + if (state->routing.num_routes != 1) { + ret = -EINVAL; + goto out; + } + + route = &state->routing.routes[0]; + + if (route->source_pad != pad) { + ret = -EINVAL; + goto out; + } + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + if (!fmt) { + ret = -EINVAL; + goto out; + } + + format = rcsi2_code_to_fmt(fmt->code); + if (!format) { + ret = -EINVAL; + goto out; + } + + fd->num_entries = 1; + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + fd->entry[0].stream = route->source_stream; + fd->entry[0].pixelcode = fmt->code; + fd->entry[0].bus.csi2.vc = 0; + fd->entry[0].bus.csi2.dt = format->datatype; + +out: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int rcsi2_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct rcar_csi2 *priv = sd_to_csi2(sd); + int ret; + + /* + * Providing a frame descriptor on the source pad only makes sense on + * Gen4. On Gen3 the CSI-2 IP is used to demux the CSI streams to each + * VIN instance, while on Gen4 this job is done by the Channel Selector + * of the ISP, and that needs the frame descriptors. So WARN if someone + * calls this on Gen3, as it indicates a driver bug. + */ + if (WARN_ON(!priv->info->use_isp)) + return -ENOTTY; + + if (WARN_ON(pad != RCAR_CSI2_SOURCE_VC0)) + return -EINVAL; + + ret = v4l2_subdev_get_frame_desc_passthrough(sd, pad, fd); + if (ret == -ENOIOCTLCMD) + ret = rcsi2_get_frame_desc_fallback(sd, pad, fd); + return ret; +} + static const struct v4l2_subdev_pad_ops rcar_csi2_pad_ops = { .enable_streams = rcsi2_enable_streams, .disable_streams = rcsi2_disable_streams, .set_fmt = rcsi2_set_pad_format, .get_fmt = v4l2_subdev_get_fmt, + + .get_frame_desc = rcsi2_get_frame_desc, }; static const struct v4l2_subdev_ops rcar_csi2_subdev_ops = { From f3065534869a46d9730c885b2fd53d55ef6ce778 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:12 +0300 Subject: [PATCH 231/596] media: rcar-isp: Call get_frame_desc to find out VC & DT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call get_frame_desc to find out VC & DT, instead of hardcoding the VC routing and deducing the DT based on the mbus format. Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- .../media/platform/renesas/rcar-isp/csisp.c | 114 ++++++++++++------ 1 file changed, 76 insertions(+), 38 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-isp/csisp.c b/drivers/media/platform/renesas/rcar-isp/csisp.c index 97465467d047..6480f6a2a435 100644 --- a/drivers/media/platform/renesas/rcar-isp/csisp.c +++ b/drivers/media/platform/renesas/rcar-isp/csisp.c @@ -41,6 +41,9 @@ #define ISPCS_DT_CODE03_EN0 BIT(7) #define ISPCS_DT_CODE03_DT0(dt) ((dt) & 0x3f) +/* ISP has 12 channels, of which channels 4 to 11 are connected to VINs */ +#define ISPCS_NUM_CHANNELS 12 + struct rcar_isp_format { u32 code; unsigned int datatype; @@ -225,31 +228,82 @@ static void risp_power_off(struct rcar_isp *isp) pm_runtime_put(isp->dev); } -static int risp_start(struct rcar_isp *isp, struct v4l2_subdev_state *state) +static int risp_configure_routing(struct rcar_isp *isp, + struct v4l2_subdev_state *state) { - const struct v4l2_subdev_route *route; - const struct v4l2_mbus_framefmt *fmt; - const struct rcar_isp_format *format; - unsigned int vc; - u32 sel_csi = 0; + struct v4l2_mbus_frame_desc source_fd; + struct v4l2_subdev_route *route; int ret; - if (state->routing.num_routes != 1) - return -EINVAL; + ret = v4l2_subdev_call(isp->remote, pad, get_frame_desc, + isp->remote_pad, &source_fd); + if (ret) + return ret; - route = &state->routing.routes[0]; - - fmt = v4l2_subdev_state_get_format(state, route->sink_pad, - route->sink_stream); - if (!fmt) - return -EINVAL; - - format = risp_code_to_fmt(fmt->code); - if (!format) { - dev_err(isp->dev, "Unsupported bus format\n"); - return -EINVAL; + /* Clear the channel registers */ + for (unsigned int ch = 0; ch < ISPCS_NUM_CHANNELS; ++ch) { + risp_write_cs(isp, ISPCS_FILTER_ID_CH_REG(ch), 0); + risp_write_cs(isp, ISPCS_DT_CODE03_CH_REG(ch), 0); } + for_each_active_route(&state->routing, route) { + struct v4l2_mbus_frame_desc_entry *source_entry = NULL; + const struct rcar_isp_format *format; + const struct v4l2_mbus_framefmt *fmt; + unsigned int i; + u8 vc, dt, ch; + u32 v; + + for (i = 0; i < source_fd.num_entries; i++) { + if (source_fd.entry[i].stream == route->sink_stream) { + source_entry = &source_fd.entry[i]; + break; + } + } + + if (!source_entry) { + dev_err(isp->dev, + "Failed to find source frame desc entry for stream\n"); + return -EPIPE; + } + + vc = source_entry->bus.csi2.vc; + dt = source_entry->bus.csi2.dt; + /* Channels 4 - 11 go to VIN */ + ch = route->source_pad - 1 + 4; + + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + if (!fmt) + return -EINVAL; + + format = risp_code_to_fmt(fmt->code); + if (!format) { + dev_err(isp->dev, "Unsupported bus format\n"); + return -EINVAL; + } + + /* VC Filtering */ + risp_write_cs(isp, ISPCS_FILTER_ID_CH_REG(ch), BIT(vc)); + + /* DT Filtering */ + risp_write_cs(isp, ISPCS_DT_CODE03_CH_REG(ch), + ISPCS_DT_CODE03_EN0 | ISPCS_DT_CODE03_DT0(dt)); + + /* Proc mode */ + v = risp_read_cs(isp, ISPPROCMODE_DT_REG(dt)); + v |= ISPPROCMODE_DT_PROC_MODE_VCn(vc, format->procmode); + risp_write_cs(isp, ISPPROCMODE_DT_REG(dt), v); + } + + return 0; +} + +static int risp_start(struct rcar_isp *isp, struct v4l2_subdev_state *state) +{ + u32 sel_csi = 0; + int ret; + ret = risp_power_on(isp); if (ret) { dev_err(isp->dev, "Failed to power on ISP\n"); @@ -263,25 +317,9 @@ static int risp_start(struct rcar_isp *isp, struct v4l2_subdev_state *state) risp_write_cs(isp, ISPINPUTSEL0_REG, risp_read_cs(isp, ISPINPUTSEL0_REG) | sel_csi); - /* Configure Channel Selector. */ - for (vc = 0; vc < 4; vc++) { - u8 ch = vc + 4; - u8 dt = format->datatype; - - risp_write_cs(isp, ISPCS_FILTER_ID_CH_REG(ch), BIT(vc)); - risp_write_cs(isp, ISPCS_DT_CODE03_CH_REG(ch), - ISPCS_DT_CODE03_EN3 | ISPCS_DT_CODE03_DT3(dt) | - ISPCS_DT_CODE03_EN2 | ISPCS_DT_CODE03_DT2(dt) | - ISPCS_DT_CODE03_EN1 | ISPCS_DT_CODE03_DT1(dt) | - ISPCS_DT_CODE03_EN0 | ISPCS_DT_CODE03_DT0(dt)); - } - - /* Setup processing method. */ - risp_write_cs(isp, ISPPROCMODE_DT_REG(format->datatype), - ISPPROCMODE_DT_PROC_MODE_VCn(3, format->procmode) | - ISPPROCMODE_DT_PROC_MODE_VCn(2, format->procmode) | - ISPPROCMODE_DT_PROC_MODE_VCn(1, format->procmode) | - ISPPROCMODE_DT_PROC_MODE_VCn(0, format->procmode)); + ret = risp_configure_routing(isp, state); + if (ret) + return ret; /* Start ISP. */ risp_write_cs(isp, ISPSTART_REG, ISPSTART_START); From e159ca006bea6f9663334392d643d78dc4fe5e55 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:13 +0300 Subject: [PATCH 232/596] media: rcar-csi2: Call get_frame_desc to find out VC & DT (Gen3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call get_frame_desc to find out VC & DT, for Gen3 platforms, instead of hardcoding the VC routing and deducing the DT based on the mbus format. If the source subdevice doesn't implement .get_frame_desc, we use a fallback case where we assume there's a single stream with VC = 0 and DT based on the mbus format. Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- drivers/media/platform/renesas/rcar-csi2.c | 119 +++++++++++++-------- 1 file changed, 75 insertions(+), 44 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 7bff2fe8daad..65e3c9decfa4 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -1069,62 +1069,93 @@ static int rcsi2_calc_mbps(struct rcar_csi2 *priv, static int rcsi2_start_receiver_gen3(struct rcar_csi2 *priv, struct v4l2_subdev_state *state) { - const struct v4l2_subdev_route *route; - const struct rcar_csi2_format *format; - u32 phycnt, vcdt = 0, vcdt2 = 0, fld = 0; - const struct v4l2_mbus_framefmt *fmt; + u32 phycnt, vcdt = 0, vcdt2 = 0; + u32 fld = FLD_DET_SEL(1); + struct v4l2_mbus_frame_desc source_fd; + struct v4l2_subdev_route *route; unsigned int lanes; - unsigned int i; int mbps, ret; + u8 ch = 0; - /* Use the format on the sink pad to compute the receiver config. */ + ret = v4l2_subdev_call(priv->remote, pad, get_frame_desc, + priv->remote_pad, &source_fd); + if (ret && ret != -ENOIOCTLCMD) + return ret; - if (state->routing.num_routes != 1) - return -EINVAL; + if (ret == -ENOIOCTLCMD) { + /* Create a fallback source_fd */ + struct v4l2_mbus_frame_desc *fd = &source_fd; + const struct v4l2_subdev_route *route; + const struct rcar_csi2_format *format; + struct v4l2_mbus_framefmt *fmt; - route = &state->routing.routes[0]; + if (state->routing.num_routes != 1) + return -EINVAL; - fmt = v4l2_subdev_state_get_format(state, route->sink_pad, - route->sink_stream); - if (!fmt) - return -EINVAL; + route = &state->routing.routes[0]; - dev_dbg(priv->dev, "Input size (%ux%u%c)\n", - fmt->width, fmt->height, - fmt->field == V4L2_FIELD_NONE ? 'p' : 'i'); + fmt = v4l2_subdev_state_get_format(state, route->sink_pad, + route->sink_stream); + if (!fmt) + return -EINVAL; - /* Code is validated in set_fmt. */ - format = rcsi2_code_to_fmt(fmt->code); - if (!format) - return -EINVAL; + format = rcsi2_code_to_fmt(fmt->code); + if (!format) + return -EINVAL; - /* - * Enable all supported CSI-2 channels with virtual channel and - * data type matching. - * - * NOTE: It's not possible to get individual datatype for each - * source virtual channel. Once this is possible in V4L2 - * it should be used here. - */ - for (i = 0; i < priv->info->num_channels; i++) { - u32 vcdt_part; + memset(fd, 0, sizeof(*fd)); - if (priv->channel_vc[i] < 0) - continue; - - vcdt_part = VCDT_SEL_VC(priv->channel_vc[i]) | VCDT_VCDTN_EN | - VCDT_SEL_DTN_ON | VCDT_SEL_DT(format->datatype); - - /* Store in correct reg and offset. */ - if (i < 2) - vcdt |= vcdt_part << ((i % 2) * 16); - else - vcdt2 |= vcdt_part << ((i % 2) * 16); + fd->num_entries = 1; + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + fd->entry[0].stream = 0; + fd->entry[0].pixelcode = fmt->code; + fd->entry[0].bus.csi2.vc = 0; + fd->entry[0].bus.csi2.dt = format->datatype; } - if (fmt->field == V4L2_FIELD_ALTERNATE) - fld = FLD_DET_SEL(1) | FLD_FLD_EN(3) | FLD_FLD_EN(2) | - FLD_FLD_EN(1) | FLD_FLD_EN(0); + for_each_active_route(&state->routing, route) { + const struct v4l2_mbus_frame_desc_entry *source_entry = NULL; + const struct v4l2_mbus_framefmt *fmt; + unsigned int i; + u32 vcdt_part; + + for (i = 0; i < source_fd.num_entries; i++) { + if (source_fd.entry[i].stream == route->sink_stream) { + source_entry = &source_fd.entry[i]; + break; + } + } + + if (!source_entry) { + dev_err(priv->dev, + "Failed to find stream from source frame desc\n"); + return -EPIPE; + } + + vcdt_part = VCDT_SEL_VC(source_entry->bus.csi2.vc) | + VCDT_VCDTN_EN | VCDT_SEL_DTN_ON | + VCDT_SEL_DT(source_entry->bus.csi2.dt); + + /* Store in correct reg and offset. */ + if (ch < 2) + vcdt |= vcdt_part << ((ch % 2) * 16); + else + vcdt2 |= vcdt_part << ((ch % 2) * 16); + + fmt = v4l2_subdev_state_get_format(state, RCAR_CSI2_SINK, + route->sink_stream); + if (!fmt) + return -EINVAL; + + dev_dbg(priv->dev, "Input size (%ux%u%c)\n", + fmt->width, fmt->height, + fmt->field == V4L2_FIELD_NONE ? 'p' : 'i'); + + if (fmt->field == V4L2_FIELD_ALTERNATE) + fld |= FLD_FLD_EN(ch); + + ch++; + } /* * Get the number of active data lanes inspecting the remote mbus From 1587be2f4242660730c85a05f3cc42485e5d42a0 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:14 +0300 Subject: [PATCH 233/596] media: rcar-csi2: Add full streams support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing pieces to enable full streams support: - Add set_routing - Drop the explicit uses of a single stream, and instead use the streams mask - Track enabled sink streams so that in rcsi2_irq_thread() we can disable and enable all currently enabled streams Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- drivers/media/platform/renesas/rcar-csi2.c | 95 +++++++++++++++------- 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 65e3c9decfa4..41e3c6b2ebb7 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -702,6 +702,17 @@ static const struct rcar_csi2_format rcar_csi2_formats[] = { }, }; +static const struct v4l2_mbus_framefmt rcar_csi2_default_fmt = { + .width = 1920, + .height = 1080, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .colorspace = V4L2_COLORSPACE_SRGB, + .field = V4L2_FIELD_NONE, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + static const struct rcar_csi2_format *rcsi2_code_to_fmt(unsigned int code) { unsigned int i; @@ -773,7 +784,7 @@ struct rcar_csi2 { int channel_vc[4]; - int stream_count; + u64 enabled_sink_streams_mask; bool cphy; unsigned short lanes; @@ -1883,29 +1894,32 @@ static int rcsi2_enable_streams(struct v4l2_subdev *sd, u64 source_streams_mask) { struct rcar_csi2 *priv = sd_to_csi2(sd); - int ret = 0; - - if (source_streams_mask != 1) - return -EINVAL; + u64 sink_streams; + int ret; if (!priv->remote) return -ENODEV; - if (priv->stream_count == 0) { + if (!priv->enabled_sink_streams_mask) { ret = rcsi2_start(priv, state); if (ret) return ret; } + sink_streams = v4l2_subdev_state_xlate_streams(state, + source_pad, + RCAR_CSI2_SINK, + &source_streams_mask); + ret = v4l2_subdev_enable_streams(priv->remote, priv->remote_pad, - BIT_ULL(0)); + sink_streams); if (ret) { - if (priv->stream_count == 0) + if (!priv->enabled_sink_streams_mask) rcsi2_stop(priv); return ret; } - priv->stream_count += 1; + priv->enabled_sink_streams_mask |= sink_streams; return ret; } @@ -1915,23 +1929,26 @@ static int rcsi2_disable_streams(struct v4l2_subdev *sd, u32 source_pad, u64 source_streams_mask) { struct rcar_csi2 *priv = sd_to_csi2(sd); + u64 sink_streams; int ret; - if (source_streams_mask != 1) - return -EINVAL; - if (!priv->remote) return -ENODEV; - if (priv->stream_count == 1) + sink_streams = v4l2_subdev_state_xlate_streams(state, + source_pad, + RCAR_CSI2_SINK, + &source_streams_mask); + + if (priv->enabled_sink_streams_mask == sink_streams) rcsi2_stop(priv); ret = v4l2_subdev_disable_streams(priv->remote, priv->remote_pad, - BIT_ULL(0)); + sink_streams); if (ret) return ret; - priv->stream_count -= 1; + priv->enabled_sink_streams_mask &= ~sink_streams; return 0; } @@ -1966,6 +1983,34 @@ static int rcsi2_set_pad_format(struct v4l2_subdev *sd, return 0; } +static int rcsi2_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct rcar_csi2 *priv = sd_to_csi2(sd); + int ret; + + if (priv->info->use_isp) { + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + } else { + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 | + V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING); + } + + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, + &rcar_csi2_default_fmt); + if (ret) + return ret; + + return 0; +} + static int rcsi2_get_frame_desc_fallback(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_frame_desc *fd) @@ -2048,6 +2093,7 @@ static const struct v4l2_subdev_pad_ops rcar_csi2_pad_ops = { .set_fmt = rcsi2_set_pad_format, .get_fmt = v4l2_subdev_get_fmt, + .set_routing = rcsi2_set_routing, .get_frame_desc = rcsi2_get_frame_desc, }; @@ -2068,17 +2114,6 @@ static int rcsi2_init_state(struct v4l2_subdev *sd, }, }; - static const struct v4l2_mbus_framefmt rcar_csi2_default_fmt = { - .width = 1920, - .height = 1080, - .code = MEDIA_BUS_FMT_RGB888_1X24, - .colorspace = V4L2_COLORSPACE_SRGB, - .field = V4L2_FIELD_NONE, - .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, - .quantization = V4L2_QUANTIZATION_DEFAULT, - .xfer_func = V4L2_XFER_FUNC_DEFAULT, - }; - static const struct v4l2_subdev_krouting routing = { .num_routes = ARRAY_SIZE(routes), .routes = routes, @@ -2123,13 +2158,13 @@ static irqreturn_t rcsi2_irq_thread(int irq, void *data) state = v4l2_subdev_lock_and_get_active_state(&priv->subdev); - if (priv->stream_count == 0) + if (!priv->enabled_sink_streams_mask) goto out; rcsi2_stop(priv); ret = v4l2_subdev_disable_streams(priv->remote, priv->remote_pad, - BIT_ULL(0)); + priv->enabled_sink_streams_mask); if (ret) { dev_warn(priv->dev, "Error recovery: failed to disable streams: %d\n", @@ -2148,7 +2183,7 @@ static irqreturn_t rcsi2_irq_thread(int irq, void *data) } ret = v4l2_subdev_enable_streams(priv->remote, priv->remote_pad, - BIT_ULL(0)); + priv->enabled_sink_streams_mask); if (ret) { dev_warn(priv->dev, "Error recovery: failed to start streams: %d\n", @@ -2747,8 +2782,6 @@ static int rcsi2_probe(struct platform_device *pdev) priv->dev = &pdev->dev; - priv->stream_count = 0; - ret = rcsi2_probe_resources(priv, pdev); if (ret) { dev_err(priv->dev, "Failed to get resources\n"); From e4594825b70511c19d6424d2a59dd81115aafcb5 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 8 Sep 2026 09:33:15 +0300 Subject: [PATCH 234/596] media: rcar-isp: Add full streams support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing pieces to enable full streams support: - Add set_routing - Drop the explicit uses of a single stream, and instead use the streams mask. Reviewed-by: Niklas Söderlund Tested-by: Niklas Söderlund Signed-off-by: Tomi Valkeinen Signed-off-by: Sakari Ailus --- .../media/platform/renesas/rcar-isp/csisp.c | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-isp/csisp.c b/drivers/media/platform/renesas/rcar-isp/csisp.c index 6480f6a2a435..3adee4e87a9c 100644 --- a/drivers/media/platform/renesas/rcar-isp/csisp.c +++ b/drivers/media/platform/renesas/rcar-isp/csisp.c @@ -340,6 +340,7 @@ static int risp_enable_streams(struct v4l2_subdev *sd, u64 source_streams_mask) { struct rcar_isp *isp = sd_to_isp(sd); + u64 sink_streams; int ret; if (source_streams_mask != 1) @@ -354,8 +355,13 @@ static int risp_enable_streams(struct v4l2_subdev *sd, return ret; } + sink_streams = v4l2_subdev_state_xlate_streams(state, + source_pad, + RCAR_ISP_SINK, + &source_streams_mask); + ret = v4l2_subdev_enable_streams(isp->remote, isp->remote_pad, - BIT_ULL(0)); + sink_streams); if (ret) { if (isp->stream_count == 0) risp_stop(isp); @@ -372,6 +378,7 @@ static int risp_disable_streams(struct v4l2_subdev *sd, u64 source_streams_mask) { struct rcar_isp *isp = sd_to_isp(sd); + u64 sink_streams; int ret; if (source_streams_mask != 1) @@ -380,8 +387,12 @@ static int risp_disable_streams(struct v4l2_subdev *sd, if (!isp->remote) return -ENODEV; - ret = v4l2_subdev_disable_streams(isp->remote, isp->remote_pad, - BIT_ULL(0)); + sink_streams = v4l2_subdev_state_xlate_streams(state, + source_pad, + RCAR_ISP_SINK, + &source_streams_mask); + + ret = v4l2_subdev_disable_streams(isp->remote, isp->remote_pad, sink_streams); if (ret) return ret; @@ -423,12 +434,34 @@ static int risp_set_pad_format(struct v4l2_subdev *sd, return 0; } +static int risp_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 | + V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, + &risp_default_fmt); + if (ret) + return ret; + + return 0; +} + static const struct v4l2_subdev_pad_ops risp_pad_ops = { .enable_streams = risp_enable_streams, .disable_streams = risp_disable_streams, .set_fmt = risp_set_pad_format, .get_fmt = v4l2_subdev_get_fmt, .link_validate = v4l2_subdev_link_validate_default, + .set_routing = risp_set_routing, }; static const struct v4l2_subdev_ops rcar_isp_subdev_ops = { From ed447e2b1da9bfe7d076e12cde496f3f689f7fdd Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Sun, 13 Sep 2026 21:30:17 +0800 Subject: [PATCH 235/596] media: i2c: cvs: Get the wake IRQ without claiming the GPIO The wake line is only used as an IRQ source, yet the driver requests it with devm_gpiod_get(). On the Dell XPS 14 DA14260 (Panther Lake) that request lands on the pin the four CS35L57 amplifiers read their speaker ID from. Each amplifier's _CRS carries GpioIo (Shared, PullNone, 0, 0, IoRestrictionInputOnly, "\_SB.GPI1", 0, ResourceConsumer,,) {20} and INTC10E1 builds its _CRS at runtime from pad numbers kept in NVS: a GpioInt (Edge, ActiveLow, Shared) first, then an Exclusive output-only GpioIo for reset and two lines on the USB GPIO expander. The driver maps index 0 to "wake". While it holds the line, cs35l56 fails and does not retry: cs35l56 sdw:0:2:01fa:3557:01:2: error -EBUSY: Failed to get spk-id-gpios All four fail and no card is registered. Fedora 7.1.13 is the first Fedora kernel with the driver enabled; 7.1.12 on the same board creates the card, and 7.2.4 creates it again with intel_cvs blacklisted. Where ipu-bridge does not expose the CSI endpoints, CSI init returns -EPROBE_DEFER and every retry claims the line again for the length of the handshake, which is how 7.1 fails as well. Take the IRQ from the GpioInt entry with acpi_dev_gpio_irq_get_by(), as the I2C core does for client->irq; this also applies the trigger type from _CRS. The vendor driver in intel/vision-drivers does the same. The driver binds as a platform device too, hence the explicit lookup. A DKMS build of the patched driver on a DA14260 running 7.2.4 binds with all four amplifiers probing and the camera working (third link). Fixes: 8e2b43d2c10b ("media: i2c: cvs: Add driver of Intel Computer Vision Sensing Controller(CVS)") Cc: stable@vger.kernel.org Reported-by: jan p. springer Closes: https://bugzilla.redhat.com/show_bug.cgi?id=2529031 Link: https://github.com/thesofproject/sof/issues/11152 Link: https://github.com/omacom/omarchy-pkgs/pull/419 Signed-off-by: Junjie Cao Tested-by: Manuel Knitza Signed-off-by: Sakari Ailus --- drivers/media/i2c/cvs/core.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c index 49bee9e3a43b..d9c51e1c0fb5 100644 --- a/drivers/media/i2c/cvs/core.c +++ b/drivers/media/i2c/cvs/core.c @@ -729,8 +729,6 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) } if (ctx->res == ICVS_FULLCAP) { - struct gpio_desc *wake; - ctx->rst = devm_gpiod_get(dev, "rst", GPIOD_OUT_HIGH); if (IS_ERR(ctx->rst)) { ret = dev_err_probe(dev, PTR_ERR(ctx->rst), @@ -738,14 +736,12 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) goto err_put_ipu; } - wake = devm_gpiod_get(dev, "wake", GPIOD_IN); - if (IS_ERR(wake)) { - ret = dev_err_probe(dev, PTR_ERR(wake), - "failed to get wake GPIO\n"); - goto err_put_ipu; - } - - ctx->irq = gpiod_to_irq(wake); + /* + * Do not request the line: another device's _CRS may list + * the same pin, and its driver would then fail with -EBUSY. + */ + ctx->irq = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), + "wake", 0); if (ctx->irq < 0) { ret = dev_err_probe(dev, ctx->irq, "failed to get wake IRQ\n"); From 4423beecd5db7c176761b2cd7b7b725ac50ad0bf Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Mon, 7 Sep 2026 14:36:09 +0300 Subject: [PATCH 236/596] media: v4l2-common: add Y12 to v4l2_format_info() Add the V4L2_PIX_FMT_Y12 (12-bit greyscale stored in 16 bit container) to the v4l2_format_info table. Signed-off-by: Tomi Valkeinen Reviewed-by: Nicolas Dufresne Signed-off-by: Sakari Ailus --- drivers/media/v4l2-core/v4l2-common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index 2ce4f1c20fbc..c825820fd481 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -333,6 +333,7 @@ const struct v4l2_format_info *v4l2_format_info(u32 format) { .format = V4L2_PIX_FMT_YVU420, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 }, { .format = V4L2_PIX_FMT_YUV422P, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 }, { .format = V4L2_PIX_FMT_GREY, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_Y12, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, /* Tiled YUV formats */ { .format = V4L2_PIX_FMT_NV12_4L4, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 }, From 5bf928d12d870919698d99ae56e9ea8c35923368 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 6 Feb 2026 11:39:21 +0200 Subject: [PATCH 237/596] media: Documentation: Improve pixel rate calculation documentation Improve documentation on calculating the pixel rate, by adding references to relevant functions and mentioning V4L2 fwnode endpoint instead of OF endpoint. Signed-off-by: Sakari Ailus Reviewed-by: Mirela Rabulea Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart --- Documentation/driver-api/media/tx-rx.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/driver-api/media/tx-rx.rst b/Documentation/driver-api/media/tx-rx.rst index 7df2407817b3..9b231fa0216a 100644 --- a/Documentation/driver-api/media/tx-rx.rst +++ b/Documentation/driver-api/media/tx-rx.rst @@ -104,7 +104,11 @@ where * - k - 16 for D-PHY and 7 for C-PHY. -Information on whether D-PHY or C-PHY is used, and the value of ``nr_of_lanes``, can be obtained from the OF endpoint configuration. +Information on whether D-PHY or C-PHY is used as well as the value of +``nr_of_lanes`` can be obtained from the V4L2 endpoint configuration; see +:c:func:`v4l2_fwnode_endpoint_alloc_parse()`, +:c:func:`v4l2_fwnode_endpoint_parse()` and +:c:func:`v4l2_get_active_data_lanes()`. .. note:: From db111b66f2db7f3f79d9359f4a9762e832d6fde9 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 20 Apr 2026 20:31:49 +0300 Subject: [PATCH 238/596] media: imx219: Account rate_factor in setting upper exposure limit The rate_factor multiplier is used to multiply a few values in the sensor's timing configuration and the exposure time is one of them. This also needs to be taken into account in exposure time margin: multiply it by rate_factor so that sensor's exposure time margin is respected. Fixes: f513997119f4 ("media: i2c: imx219: Scale the pixel rate for analog binning") Cc: stable@vger.kernel.org Signed-off-by: Sakari Ailus Reviewed-by: Dave Stevenson Reviewed-by: Laurent Pinchart --- drivers/media/i2c/imx219.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index 9571f3622d2d..5184523de8be 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -466,7 +466,8 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl) int exposure_max, exposure_def; /* Update max exposure while meeting expected vblanking */ - exposure_max = format->height + ctrl->val - IMX219_EXPOSURE_OFFSET; + exposure_max = format->height + ctrl->val - + IMX219_EXPOSURE_OFFSET * rate_factor; exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? exposure_max : IMX219_EXPOSURE_DEFAULT; ret = __v4l2_ctrl_modify_range(imx219->exposure, @@ -893,6 +894,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, crop->top = (IMX219_NATIVE_HEIGHT - crop->height) / 2; if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + int rate_factor = imx219_get_rate_factor(state); int exposure_max; int exposure_def; int llp_min; @@ -911,7 +913,8 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, return ret; /* Update max exposure while meeting expected vblanking */ - exposure_max = mode->fll_def - IMX219_EXPOSURE_OFFSET; + exposure_max = mode->fll_def - + IMX219_EXPOSURE_OFFSET * rate_factor; exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? exposure_max : IMX219_EXPOSURE_DEFAULT; ret = __v4l2_ctrl_modify_range(imx219->exposure, From 57043aef0a04bd96d97881422590e71c1e4c45b1 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 22 Jun 2026 14:16:45 +0300 Subject: [PATCH 239/596] media: imx219: Account for rate_factor in control steps The controls that are divided by the rate_factor before writing them to the registers have the step of the value of the rate_factor. Take this into account when the control's range is modified. The controls are created in a configuration where rate_factor is always 1, hence there's no need to change the code adding new controls. Fixes: f513997119f4 ("media: i2c: imx219: Scale the pixel rate for analog binning") Cc: stable@vger.kernel.org Signed-off-by: Sakari Ailus Reviewed-by: Dave Stevenson Reviewed-by: Laurent Pinchart --- drivers/media/i2c/imx219.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index 5184523de8be..b1f29037af74 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -340,13 +340,13 @@ static const struct imx219_mode supported_modes[] = { /* 2x2 binned 60fps mode */ .width = 1640, .height = 1232, - .fll_def = 1707, + .fll_def = 1706, }, { /* 640x480 60fps mode */ .width = 640, .height = 480, - .fll_def = 1707, + .fll_def = 1706, }, }; @@ -473,8 +473,7 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl) ret = __v4l2_ctrl_modify_range(imx219->exposure, imx219->exposure->minimum, exposure_max, - imx219->exposure->step, - exposure_def); + rate_factor, exposure_def); if (ret) return ret; @@ -902,7 +901,8 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, /* Update limits and set FPS to default */ ret = __v4l2_ctrl_modify_range(imx219->vblank, IMX219_VBLANK_MIN, - IMX219_FLL_MAX - mode->height, 1, + IMX219_FLL_MAX - mode->height, + rate_factor, mode->fll_def - mode->height); if (ret) return ret; @@ -920,8 +920,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, ret = __v4l2_ctrl_modify_range(imx219->exposure, imx219->exposure->minimum, exposure_max, - imx219->exposure->step, - exposure_def); + rate_factor, exposure_def); if (ret) return ret; @@ -946,8 +945,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, return ret; /* Scale the pixel rate based on the mode specific factor */ - pixel_rate = imx219_get_pixel_rate(imx219) * - imx219_get_rate_factor(state); + pixel_rate = imx219_get_pixel_rate(imx219) * rate_factor; ret = __v4l2_ctrl_modify_range(imx219->pixel_rate, pixel_rate, pixel_rate, 1, pixel_rate); if (ret) From 47576c541e9b473841742bd0686d4940f5c93564 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 22 Jun 2026 14:23:40 +0300 Subject: [PATCH 240/596] media: imx219: The horizontal blanking step is 8 The step for the LINE_LENGTH_A register on imx219 is 8. Take this into account in creating and modifying the horizontal blanking control. The minimum line length in pixels as well as the horizontal blanking values are already divisible by 8. Fixes: cd5e2fd89923 ("media: i2c: imx219: make HBLANK r/w to allow longer exposures") Cc: stable@vger.kernel.org Signed-off-by: Sakari Ailus Reviewed-by: Dave Stevenson Reviewed-by: Laurent Pinchart --- drivers/media/i2c/imx219.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index b1f29037af74..05717b1e3224 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -78,6 +78,7 @@ #define IMX219_LLP_MIN 0x0d78 #define IMX219_BINNED_LLP_MIN 0x0de8 #define IMX219_LLP_MAX 0x7ff0 +#define IMX219_LLP_STEP 8 #define IMX219_REG_X_ADD_STA_A CCI_REG16(0x0164) #define IMX219_REG_X_ADD_END_A CCI_REG16(0x0166) @@ -593,7 +594,8 @@ static int imx219_init_controls(struct imx219 *imx219) imx219->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx219_ctrl_ops, V4L2_CID_HBLANK, IMX219_LLP_MIN - mode->width, - IMX219_LLP_MAX - mode->width, 1, + IMX219_LLP_MAX - mode->width, + IMX219_LLP_STEP, IMX219_LLP_MIN - mode->width); exposure_max = mode->fll_def - IMX219_EXPOSURE_OFFSET; exposure_def = (exposure_max < IMX219_EXPOSURE_DEFAULT) ? @@ -935,7 +937,8 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, IMX219_BINNED_LLP_MIN : IMX219_LLP_MIN; ret = __v4l2_ctrl_modify_range(imx219->hblank, llp_min - mode->width, - IMX219_LLP_MAX - mode->width, 1, + IMX219_LLP_MAX - mode->width, + IMX219_LLP_STEP, llp_min - mode->width); if (ret) return ret; From c4b1944b56af4e48eca940fcc3b9e410aad85cf6 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 16 Mar 2026 11:44:14 +0200 Subject: [PATCH 241/596] media: imx219: Rename "binning" as "bin_hv" in imx219_set_pad_format Rename "binning" as "bin_hv" in anticipation of having a variable called "binning" for another purpose. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Reviewed-by: Dave Stevenson --- drivers/media/i2c/imx219.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index 05717b1e3224..f1e10b13abda 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -853,7 +853,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, const struct imx219_mode *mode; struct v4l2_mbus_framefmt *format; struct v4l2_rect *crop; - u8 bin_h, bin_v, binning; + u8 bin_h, bin_v, bin_hv; int ret; format = v4l2_subdev_state_get_format(state, 0); @@ -886,11 +886,11 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, bin_v = min(IMX219_ACTIVE_AREA_HEIGHT / format->height, 2U); /* Ensure bin_h and bin_v are same to avoid 1:2 or 2:1 stretching */ - binning = min(bin_h, bin_v); + bin_hv = min(bin_h, bin_v); crop = v4l2_subdev_state_get_crop(state, 0); - crop->width = format->width * binning; - crop->height = format->height * binning; + crop->width = format->width * bin_hv; + crop->height = format->height * bin_hv; crop->left = (IMX219_NATIVE_WIDTH - crop->width) / 2; crop->top = (IMX219_NATIVE_HEIGHT - crop->height) / 2; From 96298497e15ba96f93d02db5a7eb7a8d8210b072 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 7 Aug 2025 17:11:32 +0300 Subject: [PATCH 242/596] media: Improve enable_streams and disable_streams documentation Document that enable_streams may start additional streams and disable_streams may not disable requested streams if other related streams are still enabled. Signed-off-by: Sakari Ailus Reviewed-by: Jacopo Mondi Reviewed-by: Mirela Rabulea Reviewed-by: Laurent Pinchart --- include/media/v4l2-subdev.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index d256b7ec8f84..d9dca6905b33 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -814,6 +814,11 @@ struct v4l2_subdev_state { * V4L2_SUBDEV_CAP_STREAMS sub-device capability flag can ignore the mask * argument. * + * Due to device constraints, starting the requested streams may result in + * additional streams also being started by the driver. Streams that are + * started and stopped together due to the nature of the hardware are + * called a stream group. + * * @disable_streams: Disable the streams defined in streams_mask on the given * source pad. Subdevs that implement this operation must use the active * state management provided by the subdev core (enabled through a call to @@ -823,6 +828,9 @@ struct v4l2_subdev_state { * Drivers that support only a single stream without setting the * V4L2_SUBDEV_CAP_STREAMS sub-device capability flag can ignore the mask * argument. + * + * When the requested stream are part of a stream group, they will be + * stopped once all streams in the group are stopped. */ struct v4l2_subdev_pad_ops { int (*enum_mbus_code)(struct v4l2_subdev *sd, From c90c3b692a2d92465a877ee87c554f3377e7114f Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 29 Jan 2026 23:00:18 +0200 Subject: [PATCH 243/596] media: v4l2-subdev: Move subdev client capabilities into a new struct Add struct v4l2_subdev_client_info to hold sub-device client capability bits that used to be stored in the client_caps field of struct v4l2_subdev_fh. The intent is to enable passing this struct to sub-device pad operation callbacks for capability information. The main reason why this is a new struct instead of a u64 field is that modifying the callback arguments requires touching almost every sub-device driver and that is desirable to avoid in the future, should more than the client capability bits need to be known to the callbacks. Signed-off-by: Sakari Ailus Reviewed-by: Mirela Rabulea Reviewed-by: Frank Li Reviewed-by: Laurent Pinchart --- drivers/media/v4l2-core/v4l2-subdev.c | 8 ++++---- include/media/v4l2-subdev.h | 12 ++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index e9f81b9be9e2..f392586d5dba 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -611,7 +611,7 @@ subdev_ioctl_get_state(struct v4l2_subdev *sd, struct v4l2_subdev_fh *subdev_fh, case VIDIOC_SUBDEV_S_FRAME_INTERVAL: { struct v4l2_subdev_frame_interval *fi = arg; - if (!(subdev_fh->client_caps & + if (!(subdev_fh->ci.caps & V4L2_SUBDEV_CLIENT_CAP_INTERVAL_USES_WHICH)) fi->which = V4L2_SUBDEV_FORMAT_ACTIVE; @@ -650,7 +650,7 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, struct v4l2_subdev_fh *subdev_fh = to_v4l2_subdev_fh(vfh); bool ro_subdev = test_bit(V4L2_FL_SUBDEV_RO_DEVNODE, &vdev->flags); bool streams_subdev = sd->flags & V4L2_SUBDEV_FL_STREAMS; - bool client_supports_streams = subdev_fh->client_caps & + bool client_supports_streams = subdev_fh->ci.caps & V4L2_SUBDEV_CLIENT_CAP_STREAMS; int rval; @@ -1117,7 +1117,7 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, case VIDIOC_SUBDEV_G_CLIENT_CAP: { struct v4l2_subdev_client_capability *client_cap = arg; - client_cap->capabilities = subdev_fh->client_caps; + client_cap->capabilities = subdev_fh->ci.caps; return 0; } @@ -1137,7 +1137,7 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, client_cap->capabilities &= (V4L2_SUBDEV_CLIENT_CAP_STREAMS | V4L2_SUBDEV_CLIENT_CAP_INTERVAL_USES_WHICH); - subdev_fh->client_caps = client_cap->capabilities; + subdev_fh->ci.caps = client_cap->capabilities; return 0; } diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index d9dca6905b33..04fa23b9a651 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -734,6 +734,14 @@ struct v4l2_subdev_state { struct v4l2_subdev_stream_configs stream_configs; }; +/** + * struct v4l2_subdev_client_info - Sub-device client information + * @caps: bitmask of ``V4L2_SUBDEV_CLIENT_CAP_*`` + */ +struct v4l2_subdev_client_info { + u64 caps; +}; + /** * struct v4l2_subdev_pad_ops - v4l2-subdev pad level operations * @@ -1130,14 +1138,14 @@ struct v4l2_subdev { * @vfh: pointer to &struct v4l2_fh * @state: pointer to &struct v4l2_subdev_state * @owner: module pointer to the owner of this file handle - * @client_caps: bitmask of ``V4L2_SUBDEV_CLIENT_CAP_*`` + * @ci: sub-device client info related to this file handle */ struct v4l2_subdev_fh { struct v4l2_fh vfh; struct module *owner; #if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) struct v4l2_subdev_state *state; - u64 client_caps; + struct v4l2_subdev_client_info ci; #endif }; From cdc456ee23ae5105c3f776d4d7604c299426d83a Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 9 Jun 2026 11:47:56 +0300 Subject: [PATCH 244/596] media: v4l2-subdev: Move op check to sub-device op wrappers In anticipation of performing work for sub-device operation when the driver doesn't implement one, move the check of operation existence to the wrapper itself. No functional change intended. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart --- drivers/media/v4l2-core/v4l2-subdev.c | 92 ++++++++++++++++----------- include/media/v4l2-subdev.h | 6 +- 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index f392586d5dba..f53bac995822 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -244,20 +244,32 @@ static inline int check_format(struct v4l2_subdev *sd, check_state(sd, state, format->which, format->pad, format->stream); } +#define do_subdev_call(sd, check, o, f, args...) \ + (!(sd)->ops->o->f ? -ENOIOCTLCMD : (check) ? : \ + (sd)->ops->o->f(sd, ##args)) + static int call_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - return check_format(sd, state, format) ? : - sd->ops->pad->get_fmt(sd, state, format); + return do_subdev_call(sd, check_format(sd, state, format), pad, get_fmt, + state, format); } static int call_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - return check_format(sd, state, format) ? : - sd->ops->pad->set_fmt(sd, state, format); + return do_subdev_call(sd, check_format(sd, state, format), pad, set_fmt, + state, format); +} + +static int check_which_pad_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 which, + u32 pad, u32 stream) +{ + return check_which(which) ? : check_pad(sd, pad) ? : + check_state(sd, state, which, pad, stream); } static int call_enum_mbus_code(struct v4l2_subdev *sd, @@ -267,9 +279,9 @@ static int call_enum_mbus_code(struct v4l2_subdev *sd, if (!code) return -EINVAL; - return check_which(code->which) ? : check_pad(sd, code->pad) ? : - check_state(sd, state, code->which, code->pad, code->stream) ? : - sd->ops->pad->enum_mbus_code(sd, state, code); + return do_subdev_call(sd, check_which_pad_state(sd, state, code->which, + code->pad, code->stream), + pad, enum_mbus_code, state, code); } static int call_enum_frame_size(struct v4l2_subdev *sd, @@ -279,9 +291,9 @@ static int call_enum_frame_size(struct v4l2_subdev *sd, if (!fse) return -EINVAL; - return check_which(fse->which) ? : check_pad(sd, fse->pad) ? : - check_state(sd, state, fse->which, fse->pad, fse->stream) ? : - sd->ops->pad->enum_frame_size(sd, state, fse); + return do_subdev_call(sd, check_which_pad_state(sd, state, fse->which, + fse->pad, fse->stream), + pad, enum_frame_size, state, fse); } static int call_enum_frame_interval(struct v4l2_subdev *sd, @@ -291,9 +303,9 @@ static int call_enum_frame_interval(struct v4l2_subdev *sd, if (!fie) return -EINVAL; - return check_which(fie->which) ? : check_pad(sd, fie->pad) ? : - check_state(sd, state, fie->which, fie->pad, fie->stream) ? : - sd->ops->pad->enum_frame_interval(sd, state, fie); + return do_subdev_call(sd, check_which_pad_state(sd, state, fie->which, + fie->pad, fie->stream), + pad, enum_frame_interval, state, fie); } static inline int check_selection(struct v4l2_subdev *sd, @@ -311,16 +323,16 @@ static int call_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { - return check_selection(sd, state, sel) ? : - sd->ops->pad->get_selection(sd, state, sel); + return do_subdev_call(sd, check_selection(sd, state, sel), + pad, get_selection, state, sel); } static int call_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { - return check_selection(sd, state, sel) ? : - sd->ops->pad->set_selection(sd, state, sel); + return do_subdev_call(sd, check_selection(sd, state, sel), + pad, set_selection, state, sel); } static inline int check_frame_interval(struct v4l2_subdev *sd, @@ -338,16 +350,16 @@ static int call_get_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_frame_interval *fi) { - return check_frame_interval(sd, state, fi) ? : - sd->ops->pad->get_frame_interval(sd, state, fi); + return do_subdev_call(sd, check_frame_interval(sd, state, fi), + pad, get_frame_interval, state, fi); } static int call_set_frame_interval(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_frame_interval *fi) { - return check_frame_interval(sd, state, fi) ? : - sd->ops->pad->set_frame_interval(sd, state, fi); + return do_subdev_call(sd, check_frame_interval(sd, state, fi), + pad, set_frame_interval, state, fi); } static int call_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, @@ -361,6 +373,9 @@ static int call_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, return -EOPNOTSUPP; #endif + if (!sd->ops->pad->get_frame_desc) + return -ENOIOCTLCMD; + memset(fd, 0, sizeof(*fd)); ret = sd->ops->pad->get_frame_desc(sd, pad, fd); @@ -405,12 +420,12 @@ static inline int check_edid(struct v4l2_subdev *sd, static int call_get_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) { - return check_edid(sd, edid) ? : sd->ops->pad->get_edid(sd, edid); + return do_subdev_call(sd, check_edid(sd, edid), pad, get_edid, edid); } static int call_set_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid) { - return check_edid(sd, edid) ? : sd->ops->pad->set_edid(sd, edid); + return do_subdev_call(sd, check_edid(sd, edid), pad, set_edid, edid); } static int call_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, @@ -419,8 +434,8 @@ static int call_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, if (!timings) return -EINVAL; - return check_pad(sd, pad) ? : - sd->ops->pad->s_dv_timings(sd, pad, timings); + return do_subdev_call(sd, check_pad(sd, pad), + pad, s_dv_timings, pad, timings); } static int call_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, @@ -429,8 +444,8 @@ static int call_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, if (!timings) return -EINVAL; - return check_pad(sd, pad) ? : - sd->ops->pad->g_dv_timings(sd, pad, timings); + return do_subdev_call(sd, check_pad(sd, pad), + pad, g_dv_timings, pad, timings); } static int call_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad, @@ -439,8 +454,8 @@ static int call_query_dv_timings(struct v4l2_subdev *sd, unsigned int pad, if (!timings) return -EINVAL; - return check_pad(sd, pad) ? : - sd->ops->pad->query_dv_timings(sd, pad, timings); + return do_subdev_call(sd, check_pad(sd, pad), + pad, query_dv_timings, pad, timings); } static int call_dv_timings_cap(struct v4l2_subdev *sd, @@ -449,8 +464,8 @@ static int call_dv_timings_cap(struct v4l2_subdev *sd, if (!cap) return -EINVAL; - return check_pad(sd, cap->pad) ? : - sd->ops->pad->dv_timings_cap(sd, cap); + return do_subdev_call(sd, check_pad(sd, cap->pad), + pad, dv_timings_cap, cap); } static int call_enum_dv_timings(struct v4l2_subdev *sd, @@ -459,8 +474,8 @@ static int call_enum_dv_timings(struct v4l2_subdev *sd, if (!dvt) return -EINVAL; - return check_pad(sd, dvt->pad) ? : - sd->ops->pad->enum_dv_timings(sd, dvt); + return do_subdev_call(sd, check_pad(sd, dvt->pad), + pad, enum_dv_timings, dvt); } static int call_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, @@ -468,14 +483,17 @@ static int call_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, { memset(config, 0, sizeof(*config)); - return check_pad(sd, pad) ? : - sd->ops->pad->get_mbus_config(sd, pad, config); + return do_subdev_call(sd, check_pad(sd, pad), pad, get_mbus_config, + pad, config); } static int call_s_stream(struct v4l2_subdev *sd, int enable) { int ret; + if (!sd->ops->video->s_stream) + return -ENOIOCTLCMD; + /* * The .s_stream() operation must never be called to start or stop an * already started or stopped subdev. Catch offenders but don't return @@ -509,7 +527,7 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable) * wrapper handles the case where the caller does not provide the called * subdev's state. This should be removed when all the callers are fixed. */ -#define DEFINE_STATE_WRAPPER(f, arg_type) \ +#define DEFINE_STATE_WRAPPER(f, arg_type) \ static int call_##f##_state(struct v4l2_subdev *sd, \ struct v4l2_subdev_state *_state, \ arg_type *arg) \ @@ -526,7 +544,7 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable) #else /* CONFIG_MEDIA_CONTROLLER */ -#define DEFINE_STATE_WRAPPER(f, arg_type) \ +#define DEFINE_STATE_WRAPPER(f, arg_type) \ static int call_##f##_state(struct v4l2_subdev *sd, \ struct v4l2_subdev_state *state, \ arg_type *arg) \ diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index 04fa23b9a651..b8874eeec2e5 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -1951,14 +1951,16 @@ extern const struct v4l2_subdev_ops v4l2_subdev_call_wrappers; int __result; \ if (!__sd) \ __result = -ENODEV; \ - else if (!(__sd->ops->o && __sd->ops->o->f)) \ + else if (!__sd->ops->o) \ __result = -ENOIOCTLCMD; \ else if (v4l2_subdev_call_wrappers.o && \ v4l2_subdev_call_wrappers.o->f) \ __result = v4l2_subdev_call_wrappers.o->f( \ __sd, ##args); \ - else \ + else if (__sd->ops->o->f) \ __result = __sd->ops->o->f(__sd, ##args); \ + else \ + __result = -ENOIOCTLCMD; \ __result; \ }) From 48d8b2418f55efb7e4c437ab19e8c39ba271656d Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 9 Jun 2026 21:59:34 +0300 Subject: [PATCH 245/596] media: v4l2-subdev: Always call get_fmt() if set_fmt() is unavailable Many drivers implement set_fmt() pad op that simply returns the format just as get_fmt() would do, usually because the driver only supports a single one. As a driver should always implement set_fmt() either on its own or via get_fmt(), call get_fmt() in case set_fmt() isn't implemented by the driver. This allows removing a fair amount of boilerplate code as well as ensures that set_fmt() works whenever get_fmt() does. Signed-off-by: Sakari Ailus Reviewed-by: Hans Verkuil Reviewed-by: Laurent Pinchart --- drivers/media/v4l2-core/v4l2-subdev.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index f53bac995822..58e50d9442e7 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -260,8 +260,19 @@ static int call_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { - return do_subdev_call(sd, check_format(sd, state, format), pad, set_fmt, - state, format); + int ret; + + if (!sd->ops->pad->set_fmt && !sd->ops->pad->get_fmt) + return -ENOIOCTLCMD; + + ret = check_format(sd, state, format); + if (ret) + return ret; + + if (sd->ops->pad->set_fmt) + return sd->ops->pad->set_fmt(sd, state, format); + + return sd->ops->pad->get_fmt(sd, state, format); } static int check_which_pad_state(struct v4l2_subdev *sd, From 92e0c721f754948d54d7ac51a417a7bb7772fa72 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 3 Aug 2026 14:26:38 +0300 Subject: [PATCH 246/596] media: v4l2-subdev: Don't assign set_fmt where it's equivalent to get_fmt Do not assign the set_fmt() pad op where it is the same as get_fmt(). The get_fmt() pad op is called by the framework now in absence of set_fmt() op. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Reviewed-by: Hans Verkuil --- drivers/media/i2c/ak881x.c | 1 - drivers/media/i2c/gc0310.c | 1 - drivers/media/i2c/imx678.c | 1 - drivers/media/i2c/ml86v7667.c | 1 - drivers/media/i2c/rdacm20.c | 1 - drivers/media/i2c/rdacm21.c | 1 - drivers/media/i2c/tvp5150.c | 1 - drivers/staging/media/max96712/max96712.c | 1 - 8 files changed, 8 deletions(-) diff --git a/drivers/media/i2c/ak881x.c b/drivers/media/i2c/ak881x.c index cea46f01997d..4cedfc2d4136 100644 --- a/drivers/media/i2c/ak881x.c +++ b/drivers/media/i2c/ak881x.c @@ -216,7 +216,6 @@ static const struct v4l2_subdev_video_ops ak881x_subdev_video_ops = { static const struct v4l2_subdev_pad_ops ak881x_subdev_pad_ops = { .enum_mbus_code = ak881x_enum_mbus_code, .get_selection = ak881x_get_selection, - .set_fmt = ak881x_fill_fmt, .get_fmt = ak881x_fill_fmt, }; diff --git a/drivers/media/i2c/gc0310.c b/drivers/media/i2c/gc0310.c index 754e82ad50ae..f2c381602d18 100644 --- a/drivers/media/i2c/gc0310.c +++ b/drivers/media/i2c/gc0310.c @@ -537,7 +537,6 @@ static const struct v4l2_subdev_pad_ops gc0310_pad_ops = { .enum_mbus_code = gc0310_enum_mbus_code, .enum_frame_size = gc0310_enum_frame_size, .get_fmt = v4l2_subdev_get_fmt, - .set_fmt = v4l2_subdev_get_fmt, /* Only 1 fixed mode supported */ .get_selection = gc0310_get_selection, .set_selection = gc0310_get_selection, .enable_streams = gc0310_enable_streams, diff --git a/drivers/media/i2c/imx678.c b/drivers/media/i2c/imx678.c index 0efbf43d2fe6..9aa04c2fcb4e 100644 --- a/drivers/media/i2c/imx678.c +++ b/drivers/media/i2c/imx678.c @@ -1130,7 +1130,6 @@ static const struct v4l2_subdev_video_ops imx678_video_ops = { static const struct v4l2_subdev_pad_ops imx678_pad_ops = { .enum_mbus_code = imx678_enum_mbus_code, .get_fmt = v4l2_subdev_get_fmt, - .set_fmt = v4l2_subdev_get_fmt, .get_selection = imx678_get_selection, .enum_frame_size = imx678_enum_frame_size, .enable_streams = imx678_enable_streams, diff --git a/drivers/media/i2c/ml86v7667.c b/drivers/media/i2c/ml86v7667.c index 48b7d589df31..eda41a80d20c 100644 --- a/drivers/media/i2c/ml86v7667.c +++ b/drivers/media/i2c/ml86v7667.c @@ -298,7 +298,6 @@ static const struct v4l2_subdev_video_ops ml86v7667_subdev_video_ops = { static const struct v4l2_subdev_pad_ops ml86v7667_subdev_pad_ops = { .enum_mbus_code = ml86v7667_enum_mbus_code, .get_fmt = ml86v7667_fill_fmt, - .set_fmt = ml86v7667_fill_fmt, .get_mbus_config = ml86v7667_get_mbus_config, }; diff --git a/drivers/media/i2c/rdacm20.c b/drivers/media/i2c/rdacm20.c index 52e8e2620b4d..0179508626b5 100644 --- a/drivers/media/i2c/rdacm20.c +++ b/drivers/media/i2c/rdacm20.c @@ -442,7 +442,6 @@ static const struct v4l2_subdev_video_ops rdacm20_video_ops = { static const struct v4l2_subdev_pad_ops rdacm20_subdev_pad_ops = { .enum_mbus_code = rdacm20_enum_mbus_code, .get_fmt = rdacm20_get_fmt, - .set_fmt = rdacm20_get_fmt, }; static const struct v4l2_subdev_ops rdacm20_subdev_ops = { diff --git a/drivers/media/i2c/rdacm21.c b/drivers/media/i2c/rdacm21.c index ece8a410e7ce..68d2b9d83c3c 100644 --- a/drivers/media/i2c/rdacm21.c +++ b/drivers/media/i2c/rdacm21.c @@ -322,7 +322,6 @@ static const struct v4l2_subdev_video_ops rdacm21_video_ops = { static const struct v4l2_subdev_pad_ops rdacm21_subdev_pad_ops = { .enum_mbus_code = rdacm21_enum_mbus_code, .get_fmt = rdacm21_get_fmt, - .set_fmt = rdacm21_get_fmt, }; static const struct v4l2_subdev_ops rdacm21_subdev_ops = { diff --git a/drivers/media/i2c/tvp5150.c b/drivers/media/i2c/tvp5150.c index 9c204f38935d..5b92424a0148 100644 --- a/drivers/media/i2c/tvp5150.c +++ b/drivers/media/i2c/tvp5150.c @@ -1724,7 +1724,6 @@ static const struct v4l2_subdev_vbi_ops tvp5150_vbi_ops = { static const struct v4l2_subdev_pad_ops tvp5150_pad_ops = { .enum_mbus_code = tvp5150_enum_mbus_code, .enum_frame_size = tvp5150_enum_frame_size, - .set_fmt = tvp5150_fill_fmt, .get_fmt = tvp5150_fill_fmt, .get_selection = tvp5150_get_selection, .set_selection = tvp5150_set_selection, diff --git a/drivers/staging/media/max96712/max96712.c b/drivers/staging/media/max96712/max96712.c index 0751b2e04895..94ae304ac85f 100644 --- a/drivers/staging/media/max96712/max96712.c +++ b/drivers/staging/media/max96712/max96712.c @@ -264,7 +264,6 @@ static const struct v4l2_subdev_internal_ops max96712_internal_ops = { static const struct v4l2_subdev_pad_ops max96712_pad_ops = { .get_fmt = v4l2_subdev_get_fmt, - .set_fmt = v4l2_subdev_get_fmt, }; static const struct v4l2_subdev_ops max96712_subdev_ops = { From e33ce9db24af0f1b646a5fade47a388ba9a06a2e Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Sun, 28 Jun 2026 16:40:36 +0300 Subject: [PATCH 247/596] media: mt9m001: Pass sub-device state to set_selection() callback When the set_selection() pad operation is called from mt9m001_s_fmt(), it receives a NULL pointer for the state argument. As the function does not use the state this does not cause any issue in practice, but it could cause NULL pointer dereferences if the mt9m001_set_selection() implementation is modified. Avoid future issues by passing the subdev state. Suggested-by: Laurent Pinchart Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart --- drivers/media/i2c/mt9m001.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/mt9m001.c b/drivers/media/i2c/mt9m001.c index 0ade967b357b..d8fffed0818a 100644 --- a/drivers/media/i2c/mt9m001.c +++ b/drivers/media/i2c/mt9m001.c @@ -343,6 +343,7 @@ static int mt9m001_get_fmt(struct v4l2_subdev *sd, } static int mt9m001_s_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, const struct mt9m001_datafmt *fmt, struct v4l2_mbus_framefmt *mf) { @@ -359,7 +360,7 @@ static int mt9m001_s_fmt(struct v4l2_subdev *sd, int ret; /* No support for scaling so far, just crop. TODO: use skipping */ - ret = mt9m001_set_selection(sd, NULL, &sel); + ret = mt9m001_set_selection(sd, state, &sel); if (!ret) { mf->width = mt9m001->rect.width; mf->height = mt9m001->rect.height; @@ -404,7 +405,7 @@ static int mt9m001_set_fmt(struct v4l2_subdev *sd, mf->xfer_func = V4L2_XFER_FUNC_DEFAULT; if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) - return mt9m001_s_fmt(sd, fmt, mf); + return mt9m001_s_fmt(sd, sd_state, fmt, mf); *v4l2_subdev_state_get_format(sd_state, 0) = *mf; return 0; } From c590d6d4882e2df694acd6931f58c4a67762a97c Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 1 Jul 2026 09:50:47 +0300 Subject: [PATCH 248/596] media: cvs: Drop comments on sub-device operations The sub-device operations are well documented in the V4L2 sub-device framework already. Drop them from the CVS driver. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart --- drivers/media/i2c/cvs/v4l2.c | 82 ------------------------------------ 1 file changed, 82 deletions(-) diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c index 863112cde266..e2104ed2b932 100644 --- a/drivers/media/i2c/cvs/v4l2.c +++ b/drivers/media/i2c/cvs/v4l2.c @@ -72,19 +72,6 @@ static int csi_set_link_cfg(struct icvs *ctx, u64 link_freq) * Streaming */ -/** - * cvs_csi_enable_streams - Start streaming through the bridge - * @sd: Sub-device pointer - * @state: Active state - * @pad: Pad identifier (must be ICVS_CSI_PAD_SOURCE) - * @streams_mask: Streams to enable (bit 0 supported) - * - * Runtime-resumes the bridge (triggering cvs_runtime_resume() to claim CSI-2 - * link ownership), fetches the link frequency, programs the MIPI configuration, - * and forwards the enable request downstream. - * - * Return: 0 on success or negative errno. - */ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, u32 pad, u64 streams_mask) @@ -130,19 +117,6 @@ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, return ret; } -/** - * cvs_csi_disable_streams - Stop streaming through the bridge - * @sd: Sub-device pointer - * @state: Active state - * @pad: Pad identifier (must be ICVS_CSI_PAD_SOURCE) - * @streams_mask: Streams to disable (bit 0 supported) - * - * Disables the remote sensor stream then drops the PM reference acquired - * during enable. After the autosuspend delay, cvs_runtime_suspend() will - * return CSI-2 link ownership to CVS firmware. - * - * Return: 0 on success or negative errno. - */ static int cvs_csi_disable_streams(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, u32 pad, u64 streams_mask) @@ -168,15 +142,6 @@ static int cvs_csi_disable_streams(struct v4l2_subdev *sd, /* * Pad operations / formats */ -/** - * cvs_csi_init_state - Initialize pad formats in subdev state - * @sd: Sub-device - * @state: State container - * - * Sets all pad formats to a minimal 1x1 default. - * - * Return: 0. - */ static int cvs_csi_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { @@ -187,17 +152,6 @@ static int cvs_csi_init_state(struct v4l2_subdev *sd, return 0; } -/** - * cvs_csi_set_fmt - Negotiate pad format - * @sd: Sub-device - * @state: State - * @format: Desired / returned format - * - * Mirrors sink format onto source pad. Accepts many media bus codes, falling - * back to Y8 if unsupported. Normalizes field setting. - * - * Return: 0. - */ static int cvs_csi_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) @@ -320,17 +274,6 @@ static int cvs_csi_set_fmt(struct v4l2_subdev *sd, return 0; } -/** - * cvs_csi_get_mbus_config - Provide current CSI-2 bus configuration - * @sd: Sub-device - * @pad: Pad index - * @cfg: Returned bus config - * - * Fills lane ordering and number of lanes; retrieves link frequency from - * remote entity. - * - * Return: 0 on success or negative errno. - */ static int cvs_csi_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_config *cfg) { @@ -385,17 +328,6 @@ static const struct media_entity_operations cvs_csi_entity_ops = { /* * Async notifier */ -/** - * cvs_csi_notify_bound - Remote sensor bound callback - * @notifier: Async notifier - * @sd: Remote subdev - * @asc: Async match connection - * - * Locates the source pad of the remote sensor and creates a media link to - * the CVS bridge sink pad enabling it by default. - * - * Return: 0 on success or negative errno. - */ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, struct v4l2_subdev *sd, struct v4l2_async_connection *asc) @@ -419,12 +351,6 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, return v4l2_device_register_subdev_nodes(sd->v4l2_dev); } -/** - * cvs_csi_notify_unbind - Remote sensor unbind callback - * @notifier: Notifier - * @sd: Remote subdev - * @asc: Connection - */ static void cvs_csi_notify_unbind(struct v4l2_async_notifier *notifier, struct v4l2_subdev *sd, struct v4l2_async_connection *asc) @@ -442,14 +368,6 @@ static const struct v4l2_async_notifier_operations cvs_csi_notify_ops = { /* * Controls */ -/** - * cvs_csi_init_controls - Initialize V4L2 controls - * @ctx: CVS context - * - * Currently sets up a read-only privacy control placeholder. - * - * Return: 0 on success or negative errno. - */ static int cvs_csi_init_controls(struct icvs *ctx) { struct v4l2_ctrl *privacy_ctrl; From 7eef49c164615a6460dc49448b67444a65997017 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 30 Jan 2026 10:55:44 +0200 Subject: [PATCH 249/596] media: v4l2-subdev: Add struct v4l2_subdev_client_info pointer to pad ops Add a pointer to const struct v4l2_subdev_client_info to the get_fmt, set_fmt, get_selection and set_selection sub-device pad ops. The client info struct will soon be used to differentiate UAPI based on client capabilities. Signed-off-by: Sakari Ailus Reviewed-by: Frank Li Reviewed-by: Laurent Pinchart --- drivers/media/i2c/adv7170.c | 1 + drivers/media/i2c/adv7175.c | 1 + drivers/media/i2c/adv7180.c | 3 +- drivers/media/i2c/adv7183.c | 3 +- drivers/media/i2c/adv748x/adv748x-afe.c | 1 + drivers/media/i2c/adv748x/adv748x-csi2.c | 1 + drivers/media/i2c/adv748x/adv748x-hdmi.c | 1 + drivers/media/i2c/adv7511-v4l2.c | 1 + drivers/media/i2c/adv7604.c | 2 + drivers/media/i2c/adv7842.c | 1 + drivers/media/i2c/ak881x.c | 1 + drivers/media/i2c/alvium-csi2.c | 3 + drivers/media/i2c/ar0521.c | 1 + drivers/media/i2c/ccs/ccs-core.c | 12 +++- drivers/media/i2c/cvs/v4l2.c | 1 + drivers/media/i2c/cx25840/cx25840-core.c | 1 + drivers/media/i2c/ds90ub913.c | 1 + drivers/media/i2c/ds90ub953.c | 1 + drivers/media/i2c/ds90ub960.c | 1 + drivers/media/i2c/et8ek8/et8ek8_driver.c | 1 + drivers/media/i2c/gc0308.c | 1 + drivers/media/i2c/gc0310.c | 1 + drivers/media/i2c/gc05a2.c | 4 +- drivers/media/i2c/gc08a3.c | 4 +- drivers/media/i2c/gc2145.c | 2 + drivers/media/i2c/hi556.c | 2 + drivers/media/i2c/hi846.c | 2 + drivers/media/i2c/hi847.c | 1 + drivers/media/i2c/hm1246.c | 4 +- drivers/media/i2c/imx111.c | 1 + drivers/media/i2c/imx208.c | 1 + drivers/media/i2c/imx214.c | 4 +- drivers/media/i2c/imx219.c | 4 +- drivers/media/i2c/imx258.c | 2 + drivers/media/i2c/imx274.c | 3 + drivers/media/i2c/imx283.c | 2 + drivers/media/i2c/imx290.c | 4 +- drivers/media/i2c/imx296.c | 7 ++- drivers/media/i2c/imx319.c | 1 + drivers/media/i2c/imx334.c | 3 +- drivers/media/i2c/imx335.c | 4 +- drivers/media/i2c/imx355.c | 4 +- drivers/media/i2c/imx412.c | 3 +- drivers/media/i2c/imx415.c | 4 +- drivers/media/i2c/imx471.c | 4 +- drivers/media/i2c/imx678.c | 1 + drivers/media/i2c/isl7998x.c | 1 + drivers/media/i2c/it6625.c | 1 + drivers/media/i2c/lt6911uxe.c | 5 +- drivers/media/i2c/max9286.c | 1 + drivers/media/i2c/max96714.c | 1 + drivers/media/i2c/max96717.c | 1 + drivers/media/i2c/mt9m001.c | 8 ++- drivers/media/i2c/mt9m111.c | 3 + drivers/media/i2c/mt9m114.c | 6 ++ drivers/media/i2c/mt9p031.c | 3 + drivers/media/i2c/mt9t112.c | 3 + drivers/media/i2c/mt9v011.c | 1 + drivers/media/i2c/mt9v032.c | 3 + drivers/media/i2c/mt9v111.c | 1 + drivers/media/i2c/og01a1b.c | 3 +- drivers/media/i2c/og0ve1b.c | 3 +- drivers/media/i2c/os02g10.c | 4 +- drivers/media/i2c/os05b10.c | 2 + drivers/media/i2c/ov01a10.c | 3 + drivers/media/i2c/ov02a10.c | 3 +- drivers/media/i2c/ov02c10.c | 1 + drivers/media/i2c/ov02e10.c | 1 + drivers/media/i2c/ov08d10.c | 1 + drivers/media/i2c/ov08x40.c | 1 + drivers/media/i2c/ov13858.c | 1 + drivers/media/i2c/ov13b10.c | 1 + drivers/media/i2c/ov2640.c | 2 + drivers/media/i2c/ov2659.c | 1 + drivers/media/i2c/ov2680.c | 3 + drivers/media/i2c/ov2685.c | 2 + drivers/media/i2c/ov2732.c | 4 +- drivers/media/i2c/ov2735.c | 4 +- drivers/media/i2c/ov2740.c | 1 + drivers/media/i2c/ov4689.c | 2 + drivers/media/i2c/ov5640.c | 2 + drivers/media/i2c/ov5645.c | 4 +- drivers/media/i2c/ov5647.c | 2 + drivers/media/i2c/ov5648.c | 1 + drivers/media/i2c/ov5670.c | 2 + drivers/media/i2c/ov5675.c | 2 + drivers/media/i2c/ov5693.c | 3 + drivers/media/i2c/ov5695.c | 1 + drivers/media/i2c/ov6211.c | 3 +- drivers/media/i2c/ov64a40.c | 2 + drivers/media/i2c/ov7251.c | 4 +- drivers/media/i2c/ov7670.c | 1 + drivers/media/i2c/ov772x.c | 2 + drivers/media/i2c/ov7740.c | 1 + drivers/media/i2c/ov8856.c | 1 + drivers/media/i2c/ov8858.c | 3 +- drivers/media/i2c/ov8865.c | 2 + drivers/media/i2c/ov9282.c | 4 +- drivers/media/i2c/ov9640.c | 2 + drivers/media/i2c/ov9650.c | 1 + drivers/media/i2c/ov9734.c | 1 + drivers/media/i2c/rj54n1cb0c.c | 3 + drivers/media/i2c/s5c73m3/s5c73m3-core.c | 2 + drivers/media/i2c/s5k3m5.c | 4 +- drivers/media/i2c/s5k5baf.c | 3 + drivers/media/i2c/s5k6a3.c | 1 + drivers/media/i2c/s5kjn1.c | 4 +- drivers/media/i2c/saa6752hs.c | 1 + drivers/media/i2c/saa7115.c | 1 + drivers/media/i2c/saa717x.c | 1 + drivers/media/i2c/st-mipid02.c | 1 + drivers/media/i2c/t4ka3.c | 3 + drivers/media/i2c/tc358743.c | 1 + drivers/media/i2c/tc358746.c | 1 + drivers/media/i2c/tda1997x.c | 1 + drivers/media/i2c/thp7312.c | 1 + drivers/media/i2c/tvp514x.c | 1 + drivers/media/i2c/tvp5150.c | 2 + drivers/media/i2c/tvp7002.c | 1 + drivers/media/i2c/tw9900.c | 1 + drivers/media/i2c/tw9910.c | 2 + drivers/media/i2c/vd55g1.c | 4 +- drivers/media/i2c/vd56g3.c | 4 +- drivers/media/i2c/vgxy61.c | 4 +- drivers/media/pci/cobalt/cobalt-driver.c | 8 +-- drivers/media/pci/cobalt/cobalt-v4l2.c | 10 ++-- drivers/media/pci/cx18/cx18-av-core.c | 1 + drivers/media/pci/cx18/cx18-controls.c | 2 +- drivers/media/pci/cx18/cx18-ioctl.c | 2 +- drivers/media/pci/cx23885/cx23885-video.c | 4 +- drivers/media/pci/intel/ipu3/ipu3-cio2.c | 1 + drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 2 + .../media/pci/intel/ipu6/ipu6-isys-subdev.c | 1 + .../media/pci/intel/ipu6/ipu6-isys-subdev.h | 1 + drivers/media/pci/intel/ivsc/mei_csi.c | 1 + drivers/media/pci/ivtv/ivtv-controls.c | 2 +- drivers/media/pci/ivtv/ivtv-ioctl.c | 2 +- drivers/media/pci/saa7134/saa7134-empress.c | 4 +- drivers/media/platform/amd/isp4/isp4_subdev.c | 1 + drivers/media/platform/amd/isp4/isp4_video.c | 2 +- .../platform/amlogic/c3/isp/c3-isp-core.c | 1 + .../platform/amlogic/c3/isp/c3-isp-resizer.c | 3 + .../amlogic/c3/mipi-adapter/c3-mipi-adap.c | 1 + .../amlogic/c3/mipi-csi2/c3-mipi-csi2.c | 1 + .../platform/arm/mali-c55/mali-c55-isp.c | 3 + .../platform/arm/mali-c55/mali-c55-resizer.c | 15 +++-- .../platform/arm/mali-c55/mali-c55-tpg.c | 1 + drivers/media/platform/atmel/atmel-isi.c | 4 +- .../media/platform/broadcom/bcm2835-unicam.c | 1 + drivers/media/platform/cadence/cdns-csi2rx.c | 1 + drivers/media/platform/cadence/cdns-csi2tx.c | 1 + drivers/media/platform/intel/pxa_camera.c | 6 +- drivers/media/platform/marvell/mcam-core.c | 4 +- .../platform/microchip/microchip-csi2dc.c | 1 + .../platform/microchip/microchip-isc-scaler.c | 2 + drivers/media/platform/nxp/imx-mipi-csis.c | 3 +- drivers/media/platform/nxp/imx7-media-csi.c | 1 + .../platform/nxp/imx8-isi/imx8-isi-crossbar.c | 1 + .../platform/nxp/imx8-isi/imx8-isi-pipe.c | 3 + drivers/media/platform/nxp/imx8mq-mipi-csi2.c | 1 + .../media/platform/nxp/imx95-csi-formatter.c | 1 + .../media/platform/qcom/camss/camss-csid.c | 3 +- .../media/platform/qcom/camss/camss-csiphy.c | 3 +- .../media/platform/qcom/camss/camss-ispif.c | 3 +- drivers/media/platform/qcom/camss/camss-tpg.c | 3 +- drivers/media/platform/qcom/camss/camss-vfe.c | 12 ++-- .../media/platform/raspberrypi/rp1-cfe/csi2.c | 1 + .../platform/raspberrypi/rp1-cfe/pisp-fe.c | 1 + drivers/media/platform/renesas/rcar-csi2.c | 1 + .../media/platform/renesas/rcar-isp/csisp.c | 1 + drivers/media/platform/renesas/renesas-ceu.c | 7 ++- .../platform/renesas/rzg2l-cru/rzg2l-csi2.c | 3 +- .../platform/renesas/rzg2l-cru/rzg2l-ip.c | 3 +- .../renesas/rzv2h-ivc/rzv2h-ivc-subdev.c | 1 + drivers/media/platform/renesas/sh_vou.c | 6 +- .../media/platform/renesas/vsp1/vsp1_brx.c | 3 + .../media/platform/renesas/vsp1/vsp1_drm.c | 18 +++--- .../media/platform/renesas/vsp1/vsp1_entity.c | 4 +- .../media/platform/renesas/vsp1/vsp1_entity.h | 1 + .../media/platform/renesas/vsp1/vsp1_histo.c | 3 + .../media/platform/renesas/vsp1/vsp1_hsit.c | 1 + .../media/platform/renesas/vsp1/vsp1_rwpf.c | 3 + .../media/platform/renesas/vsp1/vsp1_sru.c | 1 + .../media/platform/renesas/vsp1/vsp1_uds.c | 1 + .../media/platform/renesas/vsp1/vsp1_uif.c | 2 + .../media/platform/renesas/vsp1/vsp1_vspx.c | 3 +- .../platform/rockchip/rkcif/rkcif-interface.c | 3 + .../platform/rockchip/rkisp1/rkisp1-csi.c | 1 + .../platform/rockchip/rkisp1/rkisp1-isp.c | 3 + .../platform/rockchip/rkisp1/rkisp1-resizer.c | 3 + .../samsung/exynos4-is/fimc-capture.c | 8 ++- .../platform/samsung/exynos4-is/fimc-isp.c | 1 + .../platform/samsung/exynos4-is/fimc-lite.c | 3 + .../platform/samsung/exynos4-is/mipi-csis.c | 1 + .../samsung/s3c-camif/camif-capture.c | 3 + .../platform/samsung/s3c-camif/camif-core.c | 2 +- drivers/media/platform/st/stm32/stm32-csi.c | 1 + drivers/media/platform/st/stm32/stm32-dcmi.c | 31 ++++++++-- .../st/stm32/stm32-dcmipp/dcmipp-byteproc.c | 3 + .../st/stm32/stm32-dcmipp/dcmipp-input.c | 1 + .../st/stm32/stm32-dcmipp/dcmipp-isp.c | 2 + .../stm32/stm32-dcmipp/dcmipp-pixelcommon.c | 1 + .../stm32/stm32-dcmipp/dcmipp-pixelcommon.h | 1 + .../st/stm32/stm32-dcmipp/dcmipp-pixelproc.c | 2 + .../platform/sunxi/sun4i-csi/sun4i_v4l2.c | 1 + .../sunxi/sun6i-csi/sun6i_csi_bridge.c | 1 + .../sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c | 1 + .../sun8i_a83t_mipi_csi2.c | 1 + .../media/platform/synopsys/dw-mipi-csi2rx.c | 1 + .../media/platform/ti/am437x/am437x-vpfe.c | 2 +- drivers/media/platform/ti/cal/cal-camerarx.c | 1 + drivers/media/platform/ti/cal/cal-video.c | 21 ++++++- .../platform/ti/j721e-csi2rx/j721e-csi2rx.c | 1 + drivers/media/platform/ti/omap3isp/ispccdc.c | 5 +- drivers/media/platform/ti/omap3isp/ispccp2.c | 3 +- drivers/media/platform/ti/omap3isp/ispcsi2.c | 3 +- .../media/platform/ti/omap3isp/isppreview.c | 5 +- .../media/platform/ti/omap3isp/ispresizer.c | 5 +- drivers/media/platform/ti/omap3isp/ispvideo.c | 4 +- drivers/media/platform/ti/vpe/vip.c | 4 +- drivers/media/platform/via/via-camera.c | 4 +- drivers/media/platform/video-mux.c | 1 + .../media/platform/xilinx/xilinx-csi2rxss.c | 1 + drivers/media/platform/xilinx/xilinx-tpg.c | 1 + .../media/test-drivers/vimc/vimc-debayer.c | 1 + drivers/media/test-drivers/vimc/vimc-scaler.c | 3 + drivers/media/test-drivers/vimc/vimc-sensor.c | 1 + drivers/media/usb/cx231xx/cx231xx-417.c | 2 +- drivers/media/usb/cx231xx/cx231xx-video.c | 4 +- drivers/media/usb/dvb-usb/cxusb-analog.c | 6 +- drivers/media/usb/em28xx/em28xx-camera.c | 2 +- drivers/media/usb/go7007/go7007-v4l2.c | 2 +- drivers/media/usb/go7007/s2250-board.c | 1 + drivers/media/usb/pvrusb2/pvrusb2-hdw.c | 2 +- drivers/media/v4l2-core/v4l2-subdev.c | 58 ++++++++++++++----- .../media/atomisp/i2c/atomisp-gc2235.c | 1 + .../media/atomisp/i2c/atomisp-ov2722.c | 1 + .../staging/media/atomisp/pci/atomisp_cmd.c | 16 +++-- .../staging/media/atomisp/pci/atomisp_csi2.c | 1 + .../media/atomisp/pci/atomisp_subdev.c | 3 + .../staging/media/atomisp/pci/atomisp_v4l2.c | 8 +-- drivers/staging/media/imx/imx-ic-prp.c | 1 + drivers/staging/media/imx/imx-ic-prpencvf.c | 1 + drivers/staging/media/imx/imx-media-csi.c | 3 + drivers/staging/media/imx/imx-media-vdic.c | 1 + drivers/staging/media/imx/imx6-mipi-csi2.c | 1 + drivers/staging/media/ipu3/ipu3-v4l2.c | 3 + drivers/staging/media/ipu7/ipu7-isys-csi2.c | 2 + drivers/staging/media/ipu7/ipu7-isys-subdev.c | 1 + drivers/staging/media/ipu7/ipu7-isys-subdev.h | 1 + .../media/sunxi/sun6i-isp/sun6i_isp_proc.c | 1 + drivers/staging/media/tegra-video/csi.c | 1 + drivers/staging/media/tegra-video/vi.c | 10 ++-- include/media/v4l2-subdev.h | 8 ++- 254 files changed, 598 insertions(+), 154 deletions(-) diff --git a/drivers/media/i2c/adv7170.c b/drivers/media/i2c/adv7170.c index 812998729207..9a7edd6583c1 100644 --- a/drivers/media/i2c/adv7170.c +++ b/drivers/media/i2c/adv7170.c @@ -284,6 +284,7 @@ static int adv7170_get_fmt(struct v4l2_subdev *sd, } static int adv7170_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/adv7175.c b/drivers/media/i2c/adv7175.c index b6f0e1b24383..b77737e23a47 100644 --- a/drivers/media/i2c/adv7175.c +++ b/drivers/media/i2c/adv7175.c @@ -322,6 +322,7 @@ static int adv7175_get_fmt(struct v4l2_subdev *sd, } static int adv7175_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c index a1c7f68225b4..5951da285b6f 100644 --- a/drivers/media/i2c/adv7180.c +++ b/drivers/media/i2c/adv7180.c @@ -770,6 +770,7 @@ static int adv7180_get_pad_format(struct v4l2_subdev *sd, } static int adv7180_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -807,7 +808,7 @@ static int adv7180_init_state(struct v4l2_subdev *sd, : V4L2_SUBDEV_FORMAT_ACTIVE, }; - return adv7180_set_pad_format(sd, sd_state, &fmt); + return adv7180_set_pad_format(sd, NULL, sd_state, &fmt); } static int adv7180_get_mbus_config(struct v4l2_subdev *sd, diff --git a/drivers/media/i2c/adv7183.c b/drivers/media/i2c/adv7183.c index a04a1a205fe0..9e4cbebe6e5a 100644 --- a/drivers/media/i2c/adv7183.c +++ b/drivers/media/i2c/adv7183.c @@ -420,6 +420,7 @@ static int adv7183_enum_mbus_code(struct v4l2_subdev *sd, } static int adv7183_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -598,7 +599,7 @@ static int adv7183_probe(struct i2c_client *client) adv7183_s_std(sd, decoder->std); fmt.format.width = 720; fmt.format.height = 576; - adv7183_set_fmt(sd, NULL, &fmt); + adv7183_set_fmt(sd, NULL, NULL, &fmt); /* initialize the hardware to the default control values */ ret = v4l2_ctrl_handler_setup(hdl); diff --git a/drivers/media/i2c/adv748x/adv748x-afe.c b/drivers/media/i2c/adv748x/adv748x-afe.c index 678199196b84..28b755df30cf 100644 --- a/drivers/media/i2c/adv748x/adv748x-afe.c +++ b/drivers/media/i2c/adv748x/adv748x-afe.c @@ -349,6 +349,7 @@ static int adv748x_afe_get_format(struct v4l2_subdev *sd, } static int adv748x_afe_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/media/i2c/adv748x/adv748x-csi2.c b/drivers/media/i2c/adv748x/adv748x-csi2.c index ebe7da8ebed7..1d7ab685d229 100644 --- a/drivers/media/i2c/adv748x/adv748x-csi2.c +++ b/drivers/media/i2c/adv748x/adv748x-csi2.c @@ -226,6 +226,7 @@ static bool adv748x_csi2_is_fmt_supported(struct adv748x_csi2 *tx, u32 code) } static int adv748x_csi2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/media/i2c/adv748x/adv748x-hdmi.c b/drivers/media/i2c/adv748x/adv748x-hdmi.c index b154dea29ba2..8d5ebb369961 100644 --- a/drivers/media/i2c/adv748x/adv748x-hdmi.c +++ b/drivers/media/i2c/adv748x/adv748x-hdmi.c @@ -440,6 +440,7 @@ static int adv748x_hdmi_get_format(struct v4l2_subdev *sd, } static int adv748x_hdmi_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/media/i2c/adv7511-v4l2.c b/drivers/media/i2c/adv7511-v4l2.c index 860cff50c522..27e0e2c31dbd 100644 --- a/drivers/media/i2c/adv7511-v4l2.c +++ b/drivers/media/i2c/adv7511-v4l2.c @@ -1273,6 +1273,7 @@ static int adv7511_get_fmt(struct v4l2_subdev *sd, } static int adv7511_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index 231edd3797ab..9d21c55aba19 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -1943,6 +1943,7 @@ static int adv76xx_get_format(struct v4l2_subdev *sd, } static int adv76xx_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1963,6 +1964,7 @@ static int adv76xx_get_selection(struct v4l2_subdev *sd, } static int adv76xx_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/adv7842.c b/drivers/media/i2c/adv7842.c index 3cfae89ce944..7fe394da0e6a 100644 --- a/drivers/media/i2c/adv7842.c +++ b/drivers/media/i2c/adv7842.c @@ -2110,6 +2110,7 @@ static int adv7842_get_format(struct v4l2_subdev *sd, } static int adv7842_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ak881x.c b/drivers/media/i2c/ak881x.c index 4cedfc2d4136..3449d95ee8a0 100644 --- a/drivers/media/i2c/ak881x.c +++ b/drivers/media/i2c/ak881x.c @@ -122,6 +122,7 @@ static int ak881x_enum_mbus_code(struct v4l2_subdev *sd, } static int ak881x_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/alvium-csi2.c b/drivers/media/i2c/alvium-csi2.c index f218999e0ed9..d9a73565457f 100644 --- a/drivers/media/i2c/alvium-csi2.c +++ b/drivers/media/i2c/alvium-csi2.c @@ -1887,6 +1887,7 @@ static int alvium_init_state(struct v4l2_subdev *sd, } static int alvium_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1922,6 +1923,7 @@ static int alvium_set_fmt(struct v4l2_subdev *sd, } static int alvium_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1957,6 +1959,7 @@ static int alvium_set_selection(struct v4l2_subdev *sd, } static int alvium_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ar0521.c b/drivers/media/i2c/ar0521.c index be68789eb8f5..3ddbf10ef8a4 100644 --- a/drivers/media/i2c/ar0521.c +++ b/drivers/media/i2c/ar0521.c @@ -457,6 +457,7 @@ static int ar0521_get_fmt(struct v4l2_subdev *sd, } static int ar0521_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ccs/ccs-core.c b/drivers/media/i2c/ccs/ccs-core.c index 8e25f970fd12..1cc4350856fd 100644 --- a/drivers/media/i2c/ccs/ccs-core.c +++ b/drivers/media/i2c/ccs/ccs-core.c @@ -2189,6 +2189,7 @@ static void ccs_propagate(struct v4l2_subdev *subdev, } static int ccs_set_format_source(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -2242,6 +2243,7 @@ static int ccs_set_format_source(struct v4l2_subdev *subdev, } static int ccs_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -2252,7 +2254,7 @@ static int ccs_set_format(struct v4l2_subdev *subdev, if (fmt->pad == ssd->source_pad) { int rval; - rval = ccs_set_format_source(subdev, sd_state, fmt); + rval = ccs_set_format_source(subdev, ci, sd_state, fmt); return rval; } @@ -2467,6 +2469,7 @@ static void ccs_set_compose_scaler(struct v4l2_subdev *subdev, } /* We're only called on source pads. This function sets scaling. */ static int ccs_set_compose(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2536,6 +2539,7 @@ static int ccs_sel_supported(struct v4l2_subdev *subdev, } static int ccs_set_crop(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2587,6 +2591,7 @@ static void ccs_get_native_size(struct ccs_subdev *ssd, struct v4l2_rect *r) } static int ccs_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2633,6 +2638,7 @@ static int ccs_get_selection(struct v4l2_subdev *subdev, } static int ccs_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2655,10 +2661,10 @@ static int ccs_set_selection(struct v4l2_subdev *subdev, switch (sel->target) { case V4L2_SEL_TGT_CROP: - ret = ccs_set_crop(subdev, sd_state, sel); + ret = ccs_set_crop(subdev, ci, sd_state, sel); break; case V4L2_SEL_TGT_COMPOSE: - ret = ccs_set_compose(subdev, sd_state, sel); + ret = ccs_set_compose(subdev, ci, sd_state, sel); break; default: ret = -EINVAL; diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c index e2104ed2b932..776fe9460168 100644 --- a/drivers/media/i2c/cvs/v4l2.c +++ b/drivers/media/i2c/cvs/v4l2.c @@ -153,6 +153,7 @@ static int cvs_csi_init_state(struct v4l2_subdev *sd, } static int cvs_csi_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/cx25840/cx25840-core.c b/drivers/media/i2c/cx25840/cx25840-core.c index 159231b80daf..e90d71fe9959 100644 --- a/drivers/media/i2c/cx25840/cx25840-core.c +++ b/drivers/media/i2c/cx25840/cx25840-core.c @@ -1771,6 +1771,7 @@ static int cx25840_s_ctrl(struct v4l2_ctrl *ctrl) /* ----------------------------------------------------------------------- */ static int cx25840_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c index 6abb5e324ae1..ca0b07d7109b 100644 --- a/drivers/media/i2c/ds90ub913.c +++ b/drivers/media/i2c/ds90ub913.c @@ -373,6 +373,7 @@ static int ub913_set_routing(struct v4l2_subdev *sd, } static int ub913_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c index d4228e1134ff..e6ae9cadefa2 100644 --- a/drivers/media/i2c/ds90ub953.c +++ b/drivers/media/i2c/ds90ub953.c @@ -426,6 +426,7 @@ static int ub953_set_routing(struct v4l2_subdev *sd, static int ub953_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c index 15a9797b47ac..7d7ba6d349fc 100644 --- a/drivers/media/i2c/ds90ub960.c +++ b/drivers/media/i2c/ds90ub960.c @@ -4045,6 +4045,7 @@ static int ub960_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, } static int ub960_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c index 738e2801016a..e8a7b7d87bb1 100644 --- a/drivers/media/i2c/et8ek8/et8ek8_driver.c +++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c @@ -1013,6 +1013,7 @@ static int et8ek8_get_pad_format(struct v4l2_subdev *subdev, } static int et8ek8_set_pad_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/gc0308.c b/drivers/media/i2c/gc0308.c index 15900d5414cf..893dfe2e1c3e 100644 --- a/drivers/media/i2c/gc0308.c +++ b/drivers/media/i2c/gc0308.c @@ -1043,6 +1043,7 @@ static void gc0308_update_pad_format(const struct gc0308_frame_size *mode, } static int gc0308_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/gc0310.c b/drivers/media/i2c/gc0310.c index f2c381602d18..5e941291dbf7 100644 --- a/drivers/media/i2c/gc0310.c +++ b/drivers/media/i2c/gc0310.c @@ -365,6 +365,7 @@ static void gc0310_fill_format(struct v4l2_mbus_framefmt *fmt) } static int gc0310_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/gc05a2.c b/drivers/media/i2c/gc05a2.c index 7cf7cde1f936..5b95d8881649 100644 --- a/drivers/media/i2c/gc05a2.c +++ b/drivers/media/i2c/gc05a2.c @@ -730,6 +730,7 @@ static void gc05a2_update_pad_format(struct gc05a2 *gc08a3, } static int gc05a2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -761,6 +762,7 @@ static int gc05a2_set_format(struct v4l2_subdev *sd, } static int gc05a2_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -795,7 +797,7 @@ static int gc05a2_init_state(struct v4l2_subdev *sd, }, }; - gc05a2_set_format(sd, state, &fmt); + gc05a2_set_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/gc08a3.c b/drivers/media/i2c/gc08a3.c index 4144aad8f2da..c1ae05ed1f2a 100644 --- a/drivers/media/i2c/gc08a3.c +++ b/drivers/media/i2c/gc08a3.c @@ -705,6 +705,7 @@ static void gc08a3_update_pad_format(struct gc08a3 *gc08a3, } static int gc08a3_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -737,6 +738,7 @@ static int gc08a3_set_format(struct v4l2_subdev *sd, } static int gc08a3_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -771,7 +773,7 @@ static int gc08a3_init_state(struct v4l2_subdev *sd, }, }; - gc08a3_set_format(sd, state, &fmt); + gc08a3_set_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/gc2145.c b/drivers/media/i2c/gc2145.c index b215963a2648..661a59641f19 100644 --- a/drivers/media/i2c/gc2145.c +++ b/drivers/media/i2c/gc2145.c @@ -709,6 +709,7 @@ static int gc2145_init_state(struct v4l2_subdev *sd, } static int gc2145_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -774,6 +775,7 @@ static int gc2145_enum_frame_size(struct v4l2_subdev *sd, } static int gc2145_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c index de573cee4451..4c13adf4a900 100644 --- a/drivers/media/i2c/hi556.c +++ b/drivers/media/i2c/hi556.c @@ -960,6 +960,7 @@ __hi556_get_pad_crop(struct hi556 *hi556, } static int hi556_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1071,6 +1072,7 @@ static int hi556_set_stream(struct v4l2_subdev *sd, int enable) } static int hi556_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/hi846.c b/drivers/media/i2c/hi846.c index a3f77b8434ca..025f8bcd17bc 100644 --- a/drivers/media/i2c/hi846.c +++ b/drivers/media/i2c/hi846.c @@ -1688,6 +1688,7 @@ static int __maybe_unused hi846_resume(struct device *dev) } static int hi846_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1840,6 +1841,7 @@ static int hi846_enum_frame_size(struct v4l2_subdev *sd, } static int hi846_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/hi847.c b/drivers/media/i2c/hi847.c index 710328d7984f..41b8cac6a5e3 100644 --- a/drivers/media/i2c/hi847.c +++ b/drivers/media/i2c/hi847.c @@ -2639,6 +2639,7 @@ static int hi847_set_stream(struct v4l2_subdev *sd, int enable) } static int hi847_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/hm1246.c b/drivers/media/i2c/hm1246.c index f87ccb9b5a56..be1c3d75657e 100644 --- a/drivers/media/i2c/hm1246.c +++ b/drivers/media/i2c/hm1246.c @@ -480,6 +480,7 @@ static void hm1246_update_pad_format(struct hm1246 *hm1246, } static int hm1246_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -503,6 +504,7 @@ static int hm1246_set_format(struct v4l2_subdev *sd, } static int hm1246_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -551,7 +553,7 @@ static int hm1246_init_state(struct v4l2_subdev *sd, }, }; - hm1246_set_format(sd, state, &fmt); + hm1246_set_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/imx111.c b/drivers/media/i2c/imx111.c index 8eb919788ef7..5b860fa052b1 100644 --- a/drivers/media/i2c/imx111.c +++ b/drivers/media/i2c/imx111.c @@ -1122,6 +1122,7 @@ static int imx111_enum_frame_size(struct v4l2_subdev *sd, } static int imx111_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/imx208.c b/drivers/media/i2c/imx208.c index d5350bb46f14..3a899b41a9d2 100644 --- a/drivers/media/i2c/imx208.c +++ b/drivers/media/i2c/imx208.c @@ -574,6 +574,7 @@ static int imx208_get_pad_format(struct v4l2_subdev *sd, } static int imx208_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/imx214.c b/drivers/media/i2c/imx214.c index d4945b192776..96833f2bf287 100644 --- a/drivers/media/i2c/imx214.c +++ b/drivers/media/i2c/imx214.c @@ -662,6 +662,7 @@ static const struct v4l2_subdev_core_ops imx214_core_ops = { }; static int imx214_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -717,6 +718,7 @@ static int imx214_set_format(struct v4l2_subdev *sd, } static int imx214_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -754,7 +756,7 @@ static int imx214_entity_init_state(struct v4l2_subdev *subdev, fmt.format.width = imx214_modes[0].width; fmt.format.height = imx214_modes[0].height; - imx214_set_format(subdev, sd_state, &fmt); + imx214_set_format(subdev, NULL, sd_state, &fmt); return 0; } diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c index f1e10b13abda..1580562093e8 100644 --- a/drivers/media/i2c/imx219.c +++ b/drivers/media/i2c/imx219.c @@ -846,6 +846,7 @@ static int imx219_enum_frame_size(struct v4l2_subdev *sd, } static int imx219_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -959,6 +960,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd, } static int imx219_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -1001,7 +1003,7 @@ static int imx219_init_state(struct v4l2_subdev *sd, }, }; - return imx219_set_pad_format(sd, state, &fmt); + return imx219_set_pad_format(sd, NULL, state, &fmt); } static const struct v4l2_subdev_video_ops imx219_video_ops = { diff --git a/drivers/media/i2c/imx258.c b/drivers/media/i2c/imx258.c index bc9ee449a87c..065c33380f60 100644 --- a/drivers/media/i2c/imx258.c +++ b/drivers/media/i2c/imx258.c @@ -913,6 +913,7 @@ static int imx258_get_pad_format(struct v4l2_subdev *sd, } static int imx258_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -988,6 +989,7 @@ __imx258_get_pad_crop(struct imx258 *imx258, } static int imx258_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/imx274.c b/drivers/media/i2c/imx274.c index 76da647f9cf9..3245afc8ae5b 100644 --- a/drivers/media/i2c/imx274.c +++ b/drivers/media/i2c/imx274.c @@ -1059,6 +1059,7 @@ static int imx274_get_fmt(struct v4l2_subdev *sd, } static int imx274_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1092,6 +1093,7 @@ static int imx274_set_fmt(struct v4l2_subdev *sd, } static int imx274_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1208,6 +1210,7 @@ static int imx274_set_selection_crop(struct stimx274 *imx274, } static int imx274_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/imx283.c b/drivers/media/i2c/imx283.c index 38ea1902f2c2..e3cd27ca238e 100644 --- a/drivers/media/i2c/imx283.c +++ b/drivers/media/i2c/imx283.c @@ -958,6 +958,7 @@ static void imx283_set_framing_limits(struct imx283 *imx283, } static int imx283_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1260,6 +1261,7 @@ static int imx283_identify_module(struct imx283 *imx283) } static int imx283_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 21cbc81cb2ed..d562a9aa455c 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -1149,6 +1149,7 @@ static int imx290_enum_frame_size(struct v4l2_subdev *sd, } static int imx290_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1187,6 +1188,7 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, } static int imx290_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1246,7 +1248,7 @@ static int imx290_entity_init_state(struct v4l2_subdev *subdev, }, }; - imx290_set_fmt(subdev, sd_state, &fmt); + imx290_set_fmt(subdev, NULL, sd_state, &fmt); return 0; } diff --git a/drivers/media/i2c/imx296.c b/drivers/media/i2c/imx296.c index 69636db11a2b..74bb295799fd 100644 --- a/drivers/media/i2c/imx296.c +++ b/drivers/media/i2c/imx296.c @@ -675,6 +675,7 @@ static int imx296_enum_frame_size(struct v4l2_subdev *sd, } static int imx296_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -726,6 +727,7 @@ static int imx296_set_format(struct v4l2_subdev *sd, } static int imx296_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -751,6 +753,7 @@ static int imx296_get_selection(struct v4l2_subdev *sd, } static int imx296_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -812,8 +815,8 @@ static int imx296_init_state(struct v4l2_subdev *sd, }, }; - imx296_set_selection(sd, state, &sel); - imx296_set_format(sd, state, &format); + imx296_set_selection(sd, NULL, state, &sel); + imx296_set_format(sd, NULL, state, &format); return 0; } diff --git a/drivers/media/i2c/imx319.c b/drivers/media/i2c/imx319.c index 953310ef3046..281d39ed4194 100644 --- a/drivers/media/i2c/imx319.c +++ b/drivers/media/i2c/imx319.c @@ -2030,6 +2030,7 @@ static int imx319_get_pad_format(struct v4l2_subdev *sd, static int imx319_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/imx334.c b/drivers/media/i2c/imx334.c index fb036873859c..da19d5fc19f5 100644 --- a/drivers/media/i2c/imx334.c +++ b/drivers/media/i2c/imx334.c @@ -821,6 +821,7 @@ static int imx334_get_pad_format(struct v4l2_subdev *sd, } static int imx334_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -872,7 +873,7 @@ static int imx334_init_state(struct v4l2_subdev *sd, ~(imx334->link_freq_bitmap), __ffs(imx334->link_freq_bitmap)); - return imx334_set_pad_format(sd, sd_state, &fmt); + return imx334_set_pad_format(sd, NULL, sd_state, &fmt); } static int imx334_set_framefmt(struct imx334 *imx334) diff --git a/drivers/media/i2c/imx335.c b/drivers/media/i2c/imx335.c index 1f777a1a8192..8f29d9f2da83 100644 --- a/drivers/media/i2c/imx335.c +++ b/drivers/media/i2c/imx335.c @@ -844,6 +844,7 @@ static void imx335_fill_pad_format(struct imx335 *imx335, } static int imx335_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -901,10 +902,11 @@ static int imx335_init_state(struct v4l2_subdev *sd, ~(imx335->link_freq_bitmap), __ffs(imx335->link_freq_bitmap)); - return imx335_set_pad_format(sd, sd_state, &fmt); + return imx335_set_pad_format(sd, NULL, sd_state, &fmt); } static int imx335_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/imx355.c b/drivers/media/i2c/imx355.c index 8eb8588cb71b..45e3002bd93c 100644 --- a/drivers/media/i2c/imx355.c +++ b/drivers/media/i2c/imx355.c @@ -714,6 +714,7 @@ static void imx355_update_pad_format(struct imx355 *imx355, static int imx355_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -765,6 +766,7 @@ imx355_set_pad_format(struct v4l2_subdev *sd, } static int imx355_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -796,7 +798,7 @@ static int imx355_entity_init_state(struct v4l2_subdev *subdev, fmt.format.width = supported_modes[0].width; fmt.format.height = supported_modes[0].height; - imx355_set_pad_format(subdev, sd_state, &fmt); + imx355_set_pad_format(subdev, NULL, sd_state, &fmt); return 0; } diff --git a/drivers/media/i2c/imx412.c b/drivers/media/i2c/imx412.c index d2760e9e6194..4628e2eda231 100644 --- a/drivers/media/i2c/imx412.c +++ b/drivers/media/i2c/imx412.c @@ -588,6 +588,7 @@ static int imx412_get_pad_format(struct v4l2_subdev *sd, } static int imx412_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -621,7 +622,7 @@ static int imx412_init_state(struct v4l2_subdev *sd, fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; imx412_fill_pad_format(imx412, &supported_mode, &fmt); - return imx412_set_pad_format(sd, sd_state, &fmt); + return imx412_set_pad_format(sd, NULL, sd_state, &fmt); } /** diff --git a/drivers/media/i2c/imx415.c b/drivers/media/i2c/imx415.c index c3b22b238ea0..2571718cffff 100644 --- a/drivers/media/i2c/imx415.c +++ b/drivers/media/i2c/imx415.c @@ -1021,6 +1021,7 @@ static int imx415_enum_frame_size(struct v4l2_subdev *sd, } static int imx415_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1042,6 +1043,7 @@ static int imx415_set_format(struct v4l2_subdev *sd, } static int imx415_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1070,7 +1072,7 @@ static int imx415_init_state(struct v4l2_subdev *sd, }, }; - imx415_set_format(sd, state, &format); + imx415_set_format(sd, NULL, state, &format); return 0; } diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c index 8d9c3d0723c0..03173197f332 100644 --- a/drivers/media/i2c/imx471.c +++ b/drivers/media/i2c/imx471.c @@ -416,6 +416,7 @@ static void imx471_update_pad_format(struct imx471 *sensor, } static int imx471_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -455,6 +456,7 @@ static int imx471_set_pad_format(struct v4l2_subdev *sd, } static int imx471_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -494,7 +496,7 @@ static int imx471_init_state(struct v4l2_subdev *sd, }, }; - return imx471_set_pad_format(sd, sd_state, &fmt); + return imx471_set_pad_format(sd, NULL, sd_state, &fmt); } static int imx471_identify_module(struct imx471 *sensor) diff --git a/drivers/media/i2c/imx678.c b/drivers/media/i2c/imx678.c index 9aa04c2fcb4e..8413fd44d657 100644 --- a/drivers/media/i2c/imx678.c +++ b/drivers/media/i2c/imx678.c @@ -849,6 +849,7 @@ static int imx678_enum_frame_size(struct v4l2_subdev *sd, } static int imx678_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/isl7998x.c b/drivers/media/i2c/isl7998x.c index a77538d2343c..cb2b752aaffe 100644 --- a/drivers/media/i2c/isl7998x.c +++ b/drivers/media/i2c/isl7998x.c @@ -1027,6 +1027,7 @@ static int isl7998x_get_fmt(struct v4l2_subdev *sd, } static int isl7998x_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/it6625.c b/drivers/media/i2c/it6625.c index da17e5d5ce9e..a5a3afae219d 100644 --- a/drivers/media/i2c/it6625.c +++ b/drivers/media/i2c/it6625.c @@ -1623,6 +1623,7 @@ static int it6625_get_fmt(struct v4l2_subdev *sd, } static int it6625_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/lt6911uxe.c b/drivers/media/i2c/lt6911uxe.c index bdefdd157e69..c9da174dbfa6 100644 --- a/drivers/media/i2c/lt6911uxe.c +++ b/drivers/media/i2c/lt6911uxe.c @@ -384,6 +384,7 @@ static int lt6911uxe_disable_streams(struct v4l2_subdev *sd, } static int lt6911uxe_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -439,7 +440,7 @@ static int lt6911uxe_init_state(struct v4l2_subdev *sd, : V4L2_SUBDEV_FORMAT_ACTIVE, }; - return lt6911uxe_set_format(sd, sd_state, &fmt); + return lt6911uxe_set_format(sd, NULL, sd_state, &fmt); } static const struct v4l2_subdev_video_ops lt6911uxe_video_ops = { @@ -562,7 +563,7 @@ static irqreturn_t lt6911uxe_threaded_irq_fn(int irq, void *dev_id) * As a HDMI to CSI2 bridge, it needs to update the format in time * when the HDMI source changes. */ - lt6911uxe_set_format(sd, state, &fmt); + lt6911uxe_set_format(sd, NULL, state, &fmt); v4l2_subdev_unlock_state(state); return IRQ_HANDLED; diff --git a/drivers/media/i2c/max9286.c b/drivers/media/i2c/max9286.c index 79eab9045e24..050ad9405c5f 100644 --- a/drivers/media/i2c/max9286.c +++ b/drivers/media/i2c/max9286.c @@ -910,6 +910,7 @@ static int max9286_enum_mbus_code(struct v4l2_subdev *sd, } static int max9286_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/max96714.c b/drivers/media/i2c/max96714.c index e3e625e6f11a..0c2ff62a77ea 100644 --- a/drivers/media/i2c/max96714.c +++ b/drivers/media/i2c/max96714.c @@ -327,6 +327,7 @@ static int max96714_disable_streams(struct v4l2_subdev *sd, } static int max96714_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c index 72f021b1a7b9..ff1df1d84049 100644 --- a/drivers/media/i2c/max96717.c +++ b/drivers/media/i2c/max96717.c @@ -414,6 +414,7 @@ static int max96717_set_routing(struct v4l2_subdev *sd, } static int max96717_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/mt9m001.c b/drivers/media/i2c/mt9m001.c index d8fffed0818a..fe46ea65550a 100644 --- a/drivers/media/i2c/mt9m001.c +++ b/drivers/media/i2c/mt9m001.c @@ -248,6 +248,7 @@ static int mt9m001_s_stream(struct v4l2_subdev *sd, int enable) } static int mt9m001_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -289,6 +290,7 @@ static int mt9m001_set_selection(struct v4l2_subdev *sd, } static int mt9m001_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -343,6 +345,7 @@ static int mt9m001_get_fmt(struct v4l2_subdev *sd, } static int mt9m001_s_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, const struct mt9m001_datafmt *fmt, struct v4l2_mbus_framefmt *mf) @@ -360,7 +363,7 @@ static int mt9m001_s_fmt(struct v4l2_subdev *sd, int ret; /* No support for scaling so far, just crop. TODO: use skipping */ - ret = mt9m001_set_selection(sd, state, &sel); + ret = mt9m001_set_selection(sd, ci, state, &sel); if (!ret) { mf->width = mt9m001->rect.width; mf->height = mt9m001->rect.height; @@ -372,6 +375,7 @@ static int mt9m001_s_fmt(struct v4l2_subdev *sd, } static int mt9m001_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -405,7 +409,7 @@ static int mt9m001_set_fmt(struct v4l2_subdev *sd, mf->xfer_func = V4L2_XFER_FUNC_DEFAULT; if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) - return mt9m001_s_fmt(sd, sd_state, fmt, mf); + return mt9m001_s_fmt(sd, ci, sd_state, fmt, mf); *v4l2_subdev_state_get_format(sd_state, 0) = *mf; return 0; } diff --git a/drivers/media/i2c/mt9m111.c b/drivers/media/i2c/mt9m111.c index 4e748080b798..f911c1cb43e1 100644 --- a/drivers/media/i2c/mt9m111.c +++ b/drivers/media/i2c/mt9m111.c @@ -446,6 +446,7 @@ static int mt9m111_reset(struct mt9m111 *mt9m111) } static int mt9m111_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -490,6 +491,7 @@ static int mt9m111_set_selection(struct v4l2_subdev *sd, } static int mt9m111_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -617,6 +619,7 @@ static int mt9m111_set_pixfmt(struct mt9m111 *mt9m111, } static int mt9m111_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 848ea06e70ab..c6c950d5c8f3 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -1254,6 +1254,7 @@ static int mt9m114_pa_enum_framesizes(struct v4l2_subdev *sd, } static int mt9m114_pa_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1281,6 +1282,7 @@ static int mt9m114_pa_set_fmt(struct v4l2_subdev *sd, } static int mt9m114_pa_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -1304,6 +1306,7 @@ static int mt9m114_pa_get_selection(struct v4l2_subdev *sd, } static int mt9m114_pa_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -1877,6 +1880,7 @@ static void mt9m114_ifp_update_sel_and_src_fmt(struct v4l2_subdev_state *state) } static int mt9m114_ifp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1923,6 +1927,7 @@ static int mt9m114_ifp_set_fmt(struct v4l2_subdev *sd, } static int mt9m114_ifp_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -1982,6 +1987,7 @@ static int mt9m114_ifp_get_selection(struct v4l2_subdev *sd, } static int mt9m114_ifp_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/mt9p031.c b/drivers/media/i2c/mt9p031.c index 2b09e8315c8e..5b0a84c66043 100644 --- a/drivers/media/i2c/mt9p031.c +++ b/drivers/media/i2c/mt9p031.c @@ -580,6 +580,7 @@ static int mt9p031_get_format(struct v4l2_subdev *subdev, } static int mt9p031_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -618,6 +619,7 @@ static int mt9p031_set_format(struct v4l2_subdev *subdev, } static int mt9p031_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -642,6 +644,7 @@ static int mt9p031_get_selection(struct v4l2_subdev *subdev, } static int mt9p031_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/mt9t112.c b/drivers/media/i2c/mt9t112.c index b3a6c6d76632..7a47756b9356 100644 --- a/drivers/media/i2c/mt9t112.c +++ b/drivers/media/i2c/mt9t112.c @@ -872,6 +872,7 @@ static int mt9t112_set_params(struct mt9t112_priv *priv, } static int mt9t112_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -897,6 +898,7 @@ static int mt9t112_get_selection(struct v4l2_subdev *sd, } static int mt9t112_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -953,6 +955,7 @@ static int mt9t112_s_fmt(struct v4l2_subdev *sd, } static int mt9t112_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/mt9v011.c b/drivers/media/i2c/mt9v011.c index ff55a8ff32b4..6ea31a4a6463 100644 --- a/drivers/media/i2c/mt9v011.c +++ b/drivers/media/i2c/mt9v011.c @@ -336,6 +336,7 @@ static int mt9v011_enum_mbus_code(struct v4l2_subdev *sd, } static int mt9v011_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/mt9v032.c b/drivers/media/i2c/mt9v032.c index 5113826534d7..29823378f0c7 100644 --- a/drivers/media/i2c/mt9v032.c +++ b/drivers/media/i2c/mt9v032.c @@ -502,6 +502,7 @@ static unsigned int mt9v032_calc_ratio(unsigned int input, unsigned int output) } static int mt9v032_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -546,6 +547,7 @@ static int mt9v032_set_format(struct v4l2_subdev *subdev, } static int mt9v032_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -560,6 +562,7 @@ static int mt9v032_get_selection(struct v4l2_subdev *subdev, } static int mt9v032_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/mt9v111.c b/drivers/media/i2c/mt9v111.c index 64a758c95ab7..6e9b3e190bca 100644 --- a/drivers/media/i2c/mt9v111.c +++ b/drivers/media/i2c/mt9v111.c @@ -886,6 +886,7 @@ static int mt9v111_get_format(struct v4l2_subdev *subdev, } static int mt9v111_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/og01a1b.c b/drivers/media/i2c/og01a1b.c index 1cece90c59ee..b207b1d853a4 100644 --- a/drivers/media/i2c/og01a1b.c +++ b/drivers/media/i2c/og01a1b.c @@ -677,6 +677,7 @@ static int og01a1b_disable_streams(struct v4l2_subdev *sd, } static int og01a1b_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -763,7 +764,7 @@ static int og01a1b_init_state(struct v4l2_subdev *sd, }, }; - og01a1b_set_format(sd, state, &fmt); + og01a1b_set_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/og0ve1b.c b/drivers/media/i2c/og0ve1b.c index 84a28cdcade1..84682389d989 100644 --- a/drivers/media/i2c/og0ve1b.c +++ b/drivers/media/i2c/og0ve1b.c @@ -481,6 +481,7 @@ static int og0ve1b_disable_streams(struct v4l2_subdev *sd, } static int og0ve1b_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -544,7 +545,7 @@ static int og0ve1b_init_state(struct v4l2_subdev *sd, }, }; - og0ve1b_set_pad_format(sd, state, &fmt); + og0ve1b_set_pad_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c index 61de45c54d18..141b7cb11fc5 100644 --- a/drivers/media/i2c/os02g10.c +++ b/drivers/media/i2c/os02g10.c @@ -534,6 +534,7 @@ static int os02g10_disable_streams(struct v4l2_subdev *sd, } static int os02g10_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -586,6 +587,7 @@ static int os02g10_enum_frame_size(struct v4l2_subdev *sd, } static int os02g10_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -632,7 +634,7 @@ static int os02g10_init_state(struct v4l2_subdev *sd, }, }; - return os02g10_set_pad_format(sd, state, &fmt); + return os02g10_set_pad_format(sd, NULL, state, &fmt); } static const struct v4l2_subdev_video_ops os02g10_video_ops = { diff --git a/drivers/media/i2c/os05b10.c b/drivers/media/i2c/os05b10.c index e0453c988e4a..a46cfa513e98 100644 --- a/drivers/media/i2c/os05b10.c +++ b/drivers/media/i2c/os05b10.c @@ -595,6 +595,7 @@ static int os05b10_set_framing_limits(struct os05b10 *os05b10, } static int os05b10_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -624,6 +625,7 @@ static int os05b10_set_pad_format(struct v4l2_subdev *sd, } static int os05b10_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov01a10.c b/drivers/media/i2c/ov01a10.c index 8a29e5b4b6ba..b9a7dacd603e 100644 --- a/drivers/media/i2c/ov01a10.c +++ b/drivers/media/i2c/ov01a10.c @@ -634,6 +634,7 @@ static void ov01a10_update_blank_ctrls(struct ov01a10 *ov01a10, } static int ov01a10_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -715,6 +716,7 @@ static int ov01a10_enum_frame_size(struct v4l2_subdev *sd, } static int ov01a10_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -747,6 +749,7 @@ static int ov01a10_get_selection(struct v4l2_subdev *sd, } static int ov01a10_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov02a10.c b/drivers/media/i2c/ov02a10.c index 0150e4d296af..375cd52d61b9 100644 --- a/drivers/media/i2c/ov02a10.c +++ b/drivers/media/i2c/ov02a10.c @@ -296,6 +296,7 @@ static void ov02a10_fill_fmt(const struct ov02a10_mode *mode, } static int ov02a10_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -523,7 +524,7 @@ static int ov02a10_init_state(struct v4l2_subdev *sd, } }; - ov02a10_set_fmt(sd, sd_state, &fmt); + ov02a10_set_fmt(sd, NULL, sd_state, &fmt); return 0; } diff --git a/drivers/media/i2c/ov02c10.c b/drivers/media/i2c/ov02c10.c index cf93d36032e1..d622f5dcac60 100644 --- a/drivers/media/i2c/ov02c10.c +++ b/drivers/media/i2c/ov02c10.c @@ -701,6 +701,7 @@ static int ov02c10_power_on(struct device *dev) } static int ov02c10_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov02e10.c b/drivers/media/i2c/ov02e10.c index 4a64cba99991..5c1e7013f559 100644 --- a/drivers/media/i2c/ov02e10.c +++ b/drivers/media/i2c/ov02e10.c @@ -590,6 +590,7 @@ static int ov02e10_power_on(struct device *dev) } static int ov02e10_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov08d10.c b/drivers/media/i2c/ov08d10.c index 9adef5446a61..0710d2243cc0 100644 --- a/drivers/media/i2c/ov08d10.c +++ b/drivers/media/i2c/ov08d10.c @@ -1178,6 +1178,7 @@ static int ov08d10_set_stream(struct v4l2_subdev *sd, int enable) } static int ov08d10_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov08x40.c b/drivers/media/i2c/ov08x40.c index 5eaf454f4763..6de0c17e633d 100644 --- a/drivers/media/i2c/ov08x40.c +++ b/drivers/media/i2c/ov08x40.c @@ -1844,6 +1844,7 @@ static int ov08x40_get_pad_format(struct v4l2_subdev *sd, static int ov08x40_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov13858.c b/drivers/media/i2c/ov13858.c index 09d58e8b1c7f..de2b79a9a0e3 100644 --- a/drivers/media/i2c/ov13858.c +++ b/drivers/media/i2c/ov13858.c @@ -1345,6 +1345,7 @@ static int ov13858_get_pad_format(struct v4l2_subdev *sd, static int ov13858_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov13b10.c b/drivers/media/i2c/ov13b10.c index b0d34141a13a..242a95834126 100644 --- a/drivers/media/i2c/ov13b10.c +++ b/drivers/media/i2c/ov13b10.c @@ -1118,6 +1118,7 @@ static int ov13b10_get_pad_format(struct v4l2_subdev *sd, static int ov13b10_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov2640.c b/drivers/media/i2c/ov2640.c index 50feb608b92b..3dbefac6d305 100644 --- a/drivers/media/i2c/ov2640.c +++ b/drivers/media/i2c/ov2640.c @@ -938,6 +938,7 @@ static int ov2640_get_fmt(struct v4l2_subdev *sd, } static int ov2640_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1028,6 +1029,7 @@ static int ov2640_enum_mbus_code(struct v4l2_subdev *sd, } static int ov2640_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov2659.c b/drivers/media/i2c/ov2659.c index 7d8c7c3465a4..5bc30e7b8325 100644 --- a/drivers/media/i2c/ov2659.c +++ b/drivers/media/i2c/ov2659.c @@ -1080,6 +1080,7 @@ static void __ov2659_try_frame_size(struct v4l2_mbus_framefmt *mf, } static int ov2659_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov2680.c b/drivers/media/i2c/ov2680.c index 5f1938c7a944..147862a813c5 100644 --- a/drivers/media/i2c/ov2680.c +++ b/drivers/media/i2c/ov2680.c @@ -639,6 +639,7 @@ static int ov2680_get_fmt(struct v4l2_subdev *sd, } static int ov2680_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -703,6 +704,7 @@ static int ov2680_set_fmt(struct v4l2_subdev *sd, } static int ov2680_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -733,6 +735,7 @@ static int ov2680_get_selection(struct v4l2_subdev *sd, } static int ov2680_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov2685.c b/drivers/media/i2c/ov2685.c index 4911a4eea126..162859a140eb 100644 --- a/drivers/media/i2c/ov2685.c +++ b/drivers/media/i2c/ov2685.c @@ -340,6 +340,7 @@ static void ov2685_fill_fmt(const struct ov2685_mode *mode, } static int ov2685_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -413,6 +414,7 @@ __ov2685_get_pad_crop(struct ov2685 *ov2685, } static int ov2685_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov2732.c b/drivers/media/i2c/ov2732.c index 40035320fec6..57dd0dd3fd85 100644 --- a/drivers/media/i2c/ov2732.c +++ b/drivers/media/i2c/ov2732.c @@ -281,6 +281,7 @@ static int ov2732_enum_frame_size(struct v4l2_subdev *sd, } static int ov2732_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -317,6 +318,7 @@ static int ov2732_set_fmt(struct v4l2_subdev *sd, } static int ov2732_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -442,7 +444,7 @@ static int ov2732_init_state(struct v4l2_subdev *sd, } }; - return ov2732_set_fmt(sd, sd_state, &fmt); + return ov2732_set_fmt(sd, NULL, sd_state, &fmt); } static const struct v4l2_subdev_internal_ops ov2732_internal_ops = { diff --git a/drivers/media/i2c/ov2735.c b/drivers/media/i2c/ov2735.c index 2defcaa8b795..0361904c4841 100644 --- a/drivers/media/i2c/ov2735.c +++ b/drivers/media/i2c/ov2735.c @@ -674,6 +674,7 @@ static int ov2735_disable_streams(struct v4l2_subdev *sd, } static int ov2735_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -742,6 +743,7 @@ static int ov2735_set_framing_limits(struct ov2735 *ov2735, } static int ov2735_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -792,7 +794,7 @@ static int ov2735_init_state(struct v4l2_subdev *sd, }, }; - ov2735_set_pad_format(sd, state, &fmt); + ov2735_set_pad_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c index 39003c1632ad..536325bb7b92 100644 --- a/drivers/media/i2c/ov2740.c +++ b/drivers/media/i2c/ov2740.c @@ -1023,6 +1023,7 @@ static int ov2740_set_stream(struct v4l2_subdev *sd, int enable) } static int ov2740_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov4689.c b/drivers/media/i2c/ov4689.c index a59d25b09b5b..466b6f74d982 100644 --- a/drivers/media/i2c/ov4689.c +++ b/drivers/media/i2c/ov4689.c @@ -330,6 +330,7 @@ static void ov4689_fill_fmt(const struct ov4689_mode *mode, } static int ov4689_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -385,6 +386,7 @@ static int ov4689_enable_test_pattern(struct ov4689 *ov4689, u32 pattern) } static int ov4689_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c index 8deb5f5501fa..22d08d0a90cf 100644 --- a/drivers/media/i2c/ov5640.c +++ b/drivers/media/i2c/ov5640.c @@ -2947,6 +2947,7 @@ static int ov5640_update_pixel_rate(struct ov5640_dev *sensor) } static int ov5640_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -2993,6 +2994,7 @@ static int ov5640_set_fmt(struct v4l2_subdev *sd, } static int ov5640_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov5645.c b/drivers/media/i2c/ov5645.c index c772ef6e51d2..a8d740ed4dc9 100644 --- a/drivers/media/i2c/ov5645.c +++ b/drivers/media/i2c/ov5645.c @@ -848,6 +848,7 @@ static int ov5645_enum_frame_size(struct v4l2_subdev *subdev, } static int ov5645_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -905,12 +906,13 @@ static int ov5645_init_state(struct v4l2_subdev *subdev, }, }; - ov5645_set_format(subdev, sd_state, &fmt); + ov5645_set_format(subdev, NULL, sd_state, &fmt); return 0; } static int ov5645_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c index 3facf92b3841..4824013454f5 100644 --- a/drivers/media/i2c/ov5647.c +++ b/drivers/media/i2c/ov5647.c @@ -787,6 +787,7 @@ static int ov5647_get_pad_fmt(struct v4l2_subdev *sd, } static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -837,6 +838,7 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd, } static int ov5647_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov5648.c b/drivers/media/i2c/ov5648.c index f0b839cd65f1..d06f5ebd9464 100644 --- a/drivers/media/i2c/ov5648.c +++ b/drivers/media/i2c/ov5648.c @@ -2215,6 +2215,7 @@ static int ov5648_get_fmt(struct v4l2_subdev *subdev, } static int ov5648_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ov5670.c b/drivers/media/i2c/ov5670.c index 01fa892de079..940f21fe0dd3 100644 --- a/drivers/media/i2c/ov5670.c +++ b/drivers/media/i2c/ov5670.c @@ -2285,6 +2285,7 @@ static int ov5670_get_pad_format(struct v4l2_subdev *sd, } static int ov5670_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -2553,6 +2554,7 @@ __ov5670_get_pad_crop(struct ov5670 *sensor, struct v4l2_subdev_state *state, } static int ov5670_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov5675.c b/drivers/media/i2c/ov5675.c index 1c31b2a57eea..c5eb07576129 100644 --- a/drivers/media/i2c/ov5675.c +++ b/drivers/media/i2c/ov5675.c @@ -1015,6 +1015,7 @@ static int ov5675_power_on(struct device *dev) } static int ov5675_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1074,6 +1075,7 @@ static int ov5675_get_format(struct v4l2_subdev *sd, } static int ov5675_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c index cedc6ea032c5..56599f9ca74b 100644 --- a/drivers/media/i2c/ov5693.c +++ b/drivers/media/i2c/ov5693.c @@ -828,6 +828,7 @@ static int ov5693_get_fmt(struct v4l2_subdev *sd, } static int ov5693_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -903,6 +904,7 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, } static int ov5693_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -936,6 +938,7 @@ static int ov5693_get_selection(struct v4l2_subdev *sd, } static int ov5693_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov5695.c b/drivers/media/i2c/ov5695.c index 5bb6ce7b3237..5b23f04ab9f8 100644 --- a/drivers/media/i2c/ov5695.c +++ b/drivers/media/i2c/ov5695.c @@ -805,6 +805,7 @@ ov5695_find_best_fit(struct v4l2_subdev_format *fmt) } static int ov5695_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov6211.c b/drivers/media/i2c/ov6211.c index 034d5d57d67e..2ad31b2c5249 100644 --- a/drivers/media/i2c/ov6211.c +++ b/drivers/media/i2c/ov6211.c @@ -459,6 +459,7 @@ static int ov6211_disable_streams(struct v4l2_subdev *sd, } static int ov6211_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -522,7 +523,7 @@ static int ov6211_init_state(struct v4l2_subdev *sd, }, }; - ov6211_set_pad_format(sd, state, &fmt); + ov6211_set_pad_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/ov64a40.c b/drivers/media/i2c/ov64a40.c index ed59b4818c55..b369c002a25a 100644 --- a/drivers/media/i2c/ov64a40.c +++ b/drivers/media/i2c/ov64a40.c @@ -3106,6 +3106,7 @@ static int ov64a40_enum_frame_size(struct v4l2_subdev *sd, } static int ov64a40_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -3137,6 +3138,7 @@ static int ov64a40_get_selection(struct v4l2_subdev *sd, } static int ov64a40_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c index 311c61d9e25d..fce6344b5120 100644 --- a/drivers/media/i2c/ov7251.c +++ b/drivers/media/i2c/ov7251.c @@ -1213,6 +1213,7 @@ ov7251_find_mode_by_ival(struct ov7251 *ov7251, struct v4l2_fract *timeperframe) } static int ov7251_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1295,12 +1296,13 @@ static int ov7251_init_state(struct v4l2_subdev *subdev, } }; - ov7251_set_format(subdev, sd_state, &fmt); + ov7251_set_format(subdev, NULL, sd_state, &fmt); return 0; } static int ov7251_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov7670.c b/drivers/media/i2c/ov7670.c index 4d040e9feeac..5a553d50f3a3 100644 --- a/drivers/media/i2c/ov7670.c +++ b/drivers/media/i2c/ov7670.c @@ -1097,6 +1097,7 @@ static int ov7670_apply_fmt(struct v4l2_subdev *sd) * Set a format. */ static int ov7670_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ov772x.c b/drivers/media/i2c/ov772x.c index 041f3b02e222..cb3f3f813837 100644 --- a/drivers/media/i2c/ov772x.c +++ b/drivers/media/i2c/ov772x.c @@ -1171,6 +1171,7 @@ static int ov772x_set_params(struct ov772x_priv *priv, } static int ov772x_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1212,6 +1213,7 @@ static int ov772x_get_fmt(struct v4l2_subdev *sd, } static int ov772x_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ov7740.c b/drivers/media/i2c/ov7740.c index b4e14171556f..c8a74ce39841 100644 --- a/drivers/media/i2c/ov7740.c +++ b/drivers/media/i2c/ov7740.c @@ -766,6 +766,7 @@ static int ov7740_try_fmt_internal(struct v4l2_subdev *sd, } static int ov7740_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/ov8856.c b/drivers/media/i2c/ov8856.c index 6011233a218d..42c4b9c4e380 100644 --- a/drivers/media/i2c/ov8856.c +++ b/drivers/media/i2c/ov8856.c @@ -2126,6 +2126,7 @@ static int ov8856_power_off(struct device *dev) } static int ov8856_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov8858.c b/drivers/media/i2c/ov8858.c index 3f45f7fab833..0bfc4350a8c9 100644 --- a/drivers/media/i2c/ov8858.c +++ b/drivers/media/i2c/ov8858.c @@ -1409,6 +1409,7 @@ static const struct v4l2_subdev_video_ops ov8858_video_ops = { */ static int ov8858_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1486,7 +1487,7 @@ static int ov8858_init_state(struct v4l2_subdev *sd, }, }; - ov8858_set_fmt(sd, sd_state, &fmt); + ov8858_set_fmt(sd, NULL, sd_state, &fmt); return 0; } diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c index 289a34a3eeb1..5a59cca8eeae 100644 --- a/drivers/media/i2c/ov8865.c +++ b/drivers/media/i2c/ov8865.c @@ -2702,6 +2702,7 @@ static int ov8865_get_fmt(struct v4l2_subdev *subdev, } static int ov8865_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -2810,6 +2811,7 @@ __ov8865_get_pad_crop(struct ov8865_sensor *sensor, } static int ov8865_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c index 5d301660a87d..3204e8d60809 100644 --- a/drivers/media/i2c/ov9282.c +++ b/drivers/media/i2c/ov9282.c @@ -780,6 +780,7 @@ static int ov9282_get_pad_format(struct v4l2_subdev *sd, } static int ov9282_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -826,7 +827,7 @@ static int ov9282_init_state(struct v4l2_subdev *sd, ov9282_fill_pad_format(ov9282, &supported_modes[DEFAULT_MODE], ov9282->code, &fmt); - return ov9282_set_pad_format(sd, sd_state, &fmt); + return ov9282_set_pad_format(sd, NULL, sd_state, &fmt); } static const struct v4l2_rect * @@ -845,6 +846,7 @@ __ov9282_get_pad_crop(struct ov9282 *ov9282, } static int ov9282_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov9640.c b/drivers/media/i2c/ov9640.c index 122f411044ce..d24b3be1e0b6 100644 --- a/drivers/media/i2c/ov9640.c +++ b/drivers/media/i2c/ov9640.c @@ -519,6 +519,7 @@ static int ov9640_s_fmt(struct v4l2_subdev *sd, } static int ov9640_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -563,6 +564,7 @@ static int ov9640_enum_mbus_code(struct v4l2_subdev *sd, } static int ov9640_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/ov9650.c b/drivers/media/i2c/ov9650.c index 5c85db8a4a38..b596cfb2d7f3 100644 --- a/drivers/media/i2c/ov9650.c +++ b/drivers/media/i2c/ov9650.c @@ -1226,6 +1226,7 @@ static void __ov965x_try_frame_size(struct v4l2_mbus_framefmt *mf, } static int ov965x_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/ov9734.c b/drivers/media/i2c/ov9734.c index 0eaf33807fc9..8f39609ad1b6 100644 --- a/drivers/media/i2c/ov9734.c +++ b/drivers/media/i2c/ov9734.c @@ -681,6 +681,7 @@ static int ov9734_set_stream(struct v4l2_subdev *sd, int enable) } static int ov9734_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/rj54n1cb0c.c b/drivers/media/i2c/rj54n1cb0c.c index 23352d71a108..211f890a9cbd 100644 --- a/drivers/media/i2c/rj54n1cb0c.c +++ b/drivers/media/i2c/rj54n1cb0c.c @@ -541,6 +541,7 @@ static int rj54n1_sensor_scale(struct v4l2_subdev *sd, s32 *in_w, s32 *in_h, s32 *out_w, s32 *out_h); static int rj54n1_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -578,6 +579,7 @@ static int rj54n1_set_selection(struct v4l2_subdev *sd, } static int rj54n1_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -973,6 +975,7 @@ static int rj54n1_reg_init(struct i2c_client *client) } static int rj54n1_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/s5c73m3/s5c73m3-core.c b/drivers/media/i2c/s5c73m3/s5c73m3-core.c index 551387cea521..680add1da244 100644 --- a/drivers/media/i2c/s5c73m3/s5c73m3-core.c +++ b/drivers/media/i2c/s5c73m3/s5c73m3-core.c @@ -1069,6 +1069,7 @@ static int s5c73m3_oif_get_fmt(struct v4l2_subdev *sd, } static int s5c73m3_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1108,6 +1109,7 @@ static int s5c73m3_set_fmt(struct v4l2_subdev *sd, } static int s5c73m3_oif_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/s5k3m5.c b/drivers/media/i2c/s5k3m5.c index c591b580d2e7..bdf5bea78685 100644 --- a/drivers/media/i2c/s5k3m5.c +++ b/drivers/media/i2c/s5k3m5.c @@ -958,6 +958,7 @@ static void s5k3m5_update_pad_format(struct s5k3m5 *s5k3m5, } static int s5k3m5_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1038,6 +1039,7 @@ static int s5k3m5_enum_frame_size(struct v4l2_subdev *sd, } static int s5k3m5_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1075,7 +1077,7 @@ static int s5k3m5_init_state(struct v4l2_subdev *sd, }, }; - s5k3m5_set_pad_format(sd, state, &fmt); + s5k3m5_set_pad_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/s5k5baf.c b/drivers/media/i2c/s5k5baf.c index 378d273055ee..b13c4754ec3c 100644 --- a/drivers/media/i2c/s5k5baf.c +++ b/drivers/media/i2c/s5k5baf.c @@ -1307,6 +1307,7 @@ static int s5k5baf_get_fmt(struct v4l2_subdev *sd, } static int s5k5baf_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1370,6 +1371,7 @@ static int s5k5baf_is_bound_target(u32 target) } static int s5k5baf_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1463,6 +1465,7 @@ static bool s5k5baf_cmp_rect(const struct v4l2_rect *r1, } static int s5k5baf_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/s5k6a3.c b/drivers/media/i2c/s5k6a3.c index ba6477e88da3..071b31728a00 100644 --- a/drivers/media/i2c/s5k6a3.c +++ b/drivers/media/i2c/s5k6a3.c @@ -131,6 +131,7 @@ static struct v4l2_mbus_framefmt *__s5k6a3_get_format( } static int s5k6a3_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/s5kjn1.c b/drivers/media/i2c/s5kjn1.c index a707cb740556..80d713ba3a1e 100644 --- a/drivers/media/i2c/s5kjn1.c +++ b/drivers/media/i2c/s5kjn1.c @@ -985,6 +985,7 @@ static void s5kjn1_update_pad_format(struct s5kjn1 *s5kjn1, } static int s5kjn1_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -1065,6 +1066,7 @@ static int s5kjn1_enum_frame_size(struct v4l2_subdev *sd, } static int s5kjn1_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1102,7 +1104,7 @@ static int s5kjn1_init_state(struct v4l2_subdev *sd, }, }; - s5kjn1_set_pad_format(sd, state, &fmt); + s5kjn1_set_pad_format(sd, NULL, state, &fmt); return 0; } diff --git a/drivers/media/i2c/saa6752hs.c b/drivers/media/i2c/saa6752hs.c index c6bf0b0902e8..6e2a3e4a63f7 100644 --- a/drivers/media/i2c/saa6752hs.c +++ b/drivers/media/i2c/saa6752hs.c @@ -563,6 +563,7 @@ static int saa6752hs_get_fmt(struct v4l2_subdev *sd, } static int saa6752hs_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c index d20695238b48..88b159418c7c 100644 --- a/drivers/media/i2c/saa7115.c +++ b/drivers/media/i2c/saa7115.c @@ -1159,6 +1159,7 @@ static int saa711x_s_sliced_fmt(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_f } static int saa711x_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/saa717x.c b/drivers/media/i2c/saa717x.c index 0536ceb54650..367539667948 100644 --- a/drivers/media/i2c/saa717x.c +++ b/drivers/media/i2c/saa717x.c @@ -980,6 +980,7 @@ static int saa717x_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_regi #endif static int saa717x_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/st-mipid02.c b/drivers/media/i2c/st-mipid02.c index 4675181af5fb..f9eabd9632d3 100644 --- a/drivers/media/i2c/st-mipid02.c +++ b/drivers/media/i2c/st-mipid02.c @@ -597,6 +597,7 @@ static int mipid02_enum_mbus_code(struct v4l2_subdev *sd, } static int mipid02_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/t4ka3.c b/drivers/media/i2c/t4ka3.c index bf48a56f5a91..6506225683bb 100644 --- a/drivers/media/i2c/t4ka3.c +++ b/drivers/media/i2c/t4ka3.c @@ -363,6 +363,7 @@ static void t4ka3_get_vblank_limits(struct t4ka3_data *sensor, } static int t4ka3_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -626,6 +627,7 @@ static int t4ka3_disable_stream(struct v4l2_subdev *sd, } static int t4ka3_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -651,6 +653,7 @@ static int t4ka3_get_selection(struct v4l2_subdev *sd, } static int t4ka3_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index ce75812cbf72..87a908426940 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -1817,6 +1817,7 @@ static int tc358743_get_fmt(struct v4l2_subdev *sd, } static int tc358743_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/tc358746.c b/drivers/media/i2c/tc358746.c index 86d9ba3ea4e5..d55539c35a26 100644 --- a/drivers/media/i2c/tc358746.c +++ b/drivers/media/i2c/tc358746.c @@ -873,6 +873,7 @@ static int tc358746_enum_mbus_code(struct v4l2_subdev *sd, } static int tc358746_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/tda1997x.c b/drivers/media/i2c/tda1997x.c index afa1d6f34c9c..bbc8e3ff5175 100644 --- a/drivers/media/i2c/tda1997x.c +++ b/drivers/media/i2c/tda1997x.c @@ -1798,6 +1798,7 @@ static int tda1997x_get_format(struct v4l2_subdev *sd, } static int tda1997x_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/thp7312.c b/drivers/media/i2c/thp7312.c index 775cfba188d8..f1c7f149c06c 100644 --- a/drivers/media/i2c/thp7312.c +++ b/drivers/media/i2c/thp7312.c @@ -729,6 +729,7 @@ static int thp7312_enum_frame_interval(struct v4l2_subdev *sd, } static int thp7312_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/tvp514x.c b/drivers/media/i2c/tvp514x.c index 99ace2acdb35..1e927896a4b3 100644 --- a/drivers/media/i2c/tvp514x.c +++ b/drivers/media/i2c/tvp514x.c @@ -879,6 +879,7 @@ static int tvp514x_get_pad_format(struct v4l2_subdev *sd, } static int tvp514x_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/tvp5150.c b/drivers/media/i2c/tvp5150.c index 5b92424a0148..91064f06f8c1 100644 --- a/drivers/media/i2c/tvp5150.c +++ b/drivers/media/i2c/tvp5150.c @@ -1104,6 +1104,7 @@ static void tvp5150_set_hw_selection(struct v4l2_subdev *sd, } static int tvp5150_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1156,6 +1157,7 @@ static int tvp5150_set_selection(struct v4l2_subdev *sd, } static int tvp5150_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/i2c/tvp7002.c b/drivers/media/i2c/tvp7002.c index 3979ccde5a95..ada28cb8af7c 100644 --- a/drivers/media/i2c/tvp7002.c +++ b/drivers/media/i2c/tvp7002.c @@ -854,6 +854,7 @@ tvp7002_get_pad_format(struct v4l2_subdev *sd, */ static int tvp7002_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/tw9900.c b/drivers/media/i2c/tw9900.c index 617fcf7f0b45..60c102aba750 100644 --- a/drivers/media/i2c/tw9900.c +++ b/drivers/media/i2c/tw9900.c @@ -198,6 +198,7 @@ static int tw9900_get_fmt(struct v4l2_subdev *sd, } static int tw9900_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/i2c/tw9910.c b/drivers/media/i2c/tw9910.c index 872207e688bf..731c97a40c82 100644 --- a/drivers/media/i2c/tw9910.c +++ b/drivers/media/i2c/tw9910.c @@ -715,6 +715,7 @@ static int tw9910_set_frame(struct v4l2_subdev *sd, u32 *width, u32 *height) } static int tw9910_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -792,6 +793,7 @@ static int tw9910_s_fmt(struct v4l2_subdev *sd, } static int tw9910_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/i2c/vd55g1.c b/drivers/media/i2c/vd55g1.c index 6f458f611f63..22b1497e8851 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -1255,6 +1255,7 @@ static int vd55g1_patch(struct vd55g1 *sensor) } static int vd55g1_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1332,6 +1333,7 @@ static int vd55g1_new_format_change_controls(struct vd55g1 *sensor, } static int vd55g1_set_pad_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sd_fmt) { @@ -1402,7 +1404,7 @@ static int vd55g1_init_state(struct v4l2_subdev *sd, fmt.format.width = vd55g1_supported_modes[VD55G1_MODE_IDX_DEF].width; fmt.format.height = vd55g1_supported_modes[VD55G1_MODE_IDX_DEF].height; - return vd55g1_set_pad_fmt(sd, sd_state, &fmt); + return vd55g1_set_pad_fmt(sd, NULL, sd_state, &fmt); } static int vd55g1_enum_frame_size(struct v4l2_subdev *sd, diff --git a/drivers/media/i2c/vd56g3.c b/drivers/media/i2c/vd56g3.c index 157acea9e286..ca0dd1b24072 100644 --- a/drivers/media/i2c/vd56g3.c +++ b/drivers/media/i2c/vd56g3.c @@ -823,6 +823,7 @@ static void vd56g3_update_img_pad_format(struct vd56g3 *sensor, } static int vd56g3_set_pad_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sd_fmt) { @@ -858,6 +859,7 @@ static int vd56g3_set_pad_fmt(struct v4l2_subdev *sd, } static int vd56g3_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1038,7 +1040,7 @@ static int vd56g3_init_state(struct v4l2_subdev *sd, }, }; - return vd56g3_set_pad_fmt(sd, sd_state, &fmt); + return vd56g3_set_pad_fmt(sd, NULL, sd_state, &fmt); } static const struct v4l2_subdev_video_ops vd56g3_video_ops = { diff --git a/drivers/media/i2c/vgxy61.c b/drivers/media/i2c/vgxy61.c index 3fb2166c81ef..e7819691723d 100644 --- a/drivers/media/i2c/vgxy61.c +++ b/drivers/media/i2c/vgxy61.c @@ -652,6 +652,7 @@ static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, } static int vgxy61_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1197,6 +1198,7 @@ static int vgxy61_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, } static int vgxy61_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -1260,7 +1262,7 @@ static int vgxy61_init_state(struct v4l2_subdev *sd, vgxy61_fill_framefmt(sensor, sensor->current_mode, &fmt.format, VGXY61_MEDIA_BUS_FMT_DEF); - return vgxy61_set_fmt(sd, sd_state, &fmt); + return vgxy61_set_fmt(sd, NULL, sd_state, &fmt); } static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl) diff --git a/drivers/media/pci/cobalt/cobalt-driver.c b/drivers/media/pci/cobalt/cobalt-driver.c index 7b1ca1238c8d..ca96cbe4f194 100644 --- a/drivers/media/pci/cobalt/cobalt-driver.c +++ b/drivers/media/pci/cobalt/cobalt-driver.c @@ -524,8 +524,8 @@ static int cobalt_subdevs_init(struct cobalt *cobalt) &cobalt_edid); if (err) return err; - err = v4l2_subdev_call(s[i].sd, pad, set_fmt, NULL, - &sd_fmt); + err = v4l2_subdev_call(s[i].sd, pad, set_fmt, NULL, NULL, + &sd_fmt); if (err) return err; /* Reset channel video module */ @@ -608,8 +608,8 @@ static int cobalt_subdevs_hsma_init(struct cobalt *cobalt) if (err) return err; - err = v4l2_subdev_call(s->sd, pad, set_fmt, NULL, - &sd_fmt); + err = v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, + &sd_fmt); if (err) return err; cobalt->have_hsma_rx = true; diff --git a/drivers/media/pci/cobalt/cobalt-v4l2.c b/drivers/media/pci/cobalt/cobalt-v4l2.c index 51fd9576c6c2..ad89e62a2747 100644 --- a/drivers/media/pci/cobalt/cobalt-v4l2.c +++ b/drivers/media/pci/cobalt/cobalt-v4l2.c @@ -172,7 +172,7 @@ static void cobalt_enable_output(struct cobalt_stream *s) sd_fmt.format.code = MEDIA_BUS_FMT_RGB888_1X24; break; } - v4l2_subdev_call(s->sd, pad, set_fmt, NULL, &sd_fmt); + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, &sd_fmt); iowrite32(0, &vo->control); /* 1080p60 */ @@ -223,14 +223,14 @@ static void cobalt_enable_input(struct cobalt_stream *s) iowrite32(M00235_CONTROL_BITMAP_ENABLE_MSK | (1 << M00235_CONTROL_BITMAP_PACK_FORMAT_OFST), &packer->control); - v4l2_subdev_call(s->sd, pad, set_fmt, NULL, + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, &sd_fmt_yuyv); break; case V4L2_PIX_FMT_RGB24: iowrite32(M00235_CONTROL_BITMAP_ENABLE_MSK | (2 << M00235_CONTROL_BITMAP_PACK_FORMAT_OFST), &packer->control); - v4l2_subdev_call(s->sd, pad, set_fmt, NULL, + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, &sd_fmt_rgb); break; case V4L2_PIX_FMT_BGR32: @@ -238,7 +238,7 @@ static void cobalt_enable_input(struct cobalt_stream *s) M00235_CONTROL_BITMAP_ENDIAN_FORMAT_MSK | (3 << M00235_CONTROL_BITMAP_PACK_FORMAT_OFST), &packer->control); - v4l2_subdev_call(s->sd, pad, set_fmt, NULL, + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, &sd_fmt_rgb); break; } @@ -938,7 +938,7 @@ static int cobalt_s_fmt_vid_out(struct file *file, void *priv, s->ycbcr_enc = pix->ycbcr_enc; s->quantization = pix->quantization; v4l2_fill_mbus_format(&sd_fmt.format, pix, code); - v4l2_subdev_call(s->sd, pad, set_fmt, NULL, &sd_fmt); + v4l2_subdev_call(s->sd, pad, set_fmt, NULL, NULL, &sd_fmt); return 0; } diff --git a/drivers/media/pci/cx18/cx18-av-core.c b/drivers/media/pci/cx18/cx18-av-core.c index 4fb19d26ee29..f5961853eeae 100644 --- a/drivers/media/pci/cx18/cx18-av-core.c +++ b/drivers/media/pci/cx18/cx18-av-core.c @@ -930,6 +930,7 @@ static int cx18_av_s_ctrl(struct v4l2_ctrl *ctrl) } static int cx18_av_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/pci/cx18/cx18-controls.c b/drivers/media/pci/cx18/cx18-controls.c index 78eadad8b6e8..41f388ba22ab 100644 --- a/drivers/media/pci/cx18/cx18-controls.c +++ b/drivers/media/pci/cx18/cx18-controls.c @@ -85,7 +85,7 @@ static int cx18_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val) fmt->width = cxhdl->width / (is_mpeg1 ? 2 : 1); fmt->height = cxhdl->height; fmt->code = MEDIA_BUS_FMT_FIXED; - v4l2_subdev_call(cx->sd_av, pad, set_fmt, NULL, &format); + v4l2_subdev_call(cx->sd_av, pad, set_fmt, NULL, NULL, &format); return 0; } diff --git a/drivers/media/pci/cx18/cx18-ioctl.c b/drivers/media/pci/cx18/cx18-ioctl.c index 0d676a57e24e..6f8d7f700016 100644 --- a/drivers/media/pci/cx18/cx18-ioctl.c +++ b/drivers/media/pci/cx18/cx18-ioctl.c @@ -150,7 +150,7 @@ static int cx18_s_fmt_vid_cap(struct file *file, void *fh, format.format.width = cx->cxhdl.width = w; format.format.height = cx->cxhdl.height = h; format.format.code = MEDIA_BUS_FMT_FIXED; - v4l2_subdev_call(cx->sd_av, pad, set_fmt, NULL, &format); + v4l2_subdev_call(cx->sd_av, pad, set_fmt, NULL, NULL, &format); return cx18_g_fmt_vid_cap(file, fh, fmt); } diff --git a/drivers/media/pci/cx23885/cx23885-video.c b/drivers/media/pci/cx23885/cx23885-video.c index 14d219fd1d8a..94ce00154e1f 100644 --- a/drivers/media/pci/cx23885/cx23885-video.c +++ b/drivers/media/pci/cx23885/cx23885-video.c @@ -134,7 +134,7 @@ int cx23885_set_tvnorm(struct cx23885_dev *dev, v4l2_std_id norm) format.format.width = dev->width; format.format.height = dev->height; format.format.field = dev->field; - call_all(dev, pad, set_fmt, NULL, &format); + call_all(dev, pad, set_fmt, NULL, NULL, &format); return 0; } @@ -619,7 +619,7 @@ static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, dprintk(2, "%s() width=%d height=%d field=%d\n", __func__, dev->width, dev->height, dev->field); v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); - call_all(dev, pad, set_fmt, NULL, &format); + call_all(dev, pad, set_fmt, NULL, NULL, &format); v4l2_fill_pix_format(&f->fmt.pix, &format.format); /* set_fmt overwrites f->fmt.pix.field, restore it */ f->fmt.pix.field = dev->field; diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c index eb1824ee86fd..4d2b231291f5 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c @@ -1229,6 +1229,7 @@ static int cio2_subdev_init_state(struct v4l2_subdev *sd, } static int cio2_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c index 160eace947f4..c0e5afd76f6a 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c @@ -500,6 +500,7 @@ static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd, } static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -551,6 +552,7 @@ static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd, } static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c index dbd6f76a066d..ab04b5b0ad08 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c @@ -160,6 +160,7 @@ u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y) } int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h index 35069099c364..d4f76d513dc6 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h @@ -31,6 +31,7 @@ bool ipu6_isys_is_bayer_format(u32 code); u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y); int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt); int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, diff --git a/drivers/media/pci/intel/ivsc/mei_csi.c b/drivers/media/pci/intel/ivsc/mei_csi.c index d4256415a829..005b1414a252 100644 --- a/drivers/media/pci/intel/ivsc/mei_csi.c +++ b/drivers/media/pci/intel/ivsc/mei_csi.c @@ -337,6 +337,7 @@ static int mei_csi_init_state(struct v4l2_subdev *sd, } static int mei_csi_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/pci/ivtv/ivtv-controls.c b/drivers/media/pci/ivtv/ivtv-controls.c index f087a12c4ebd..816e634de1eb 100644 --- a/drivers/media/pci/ivtv/ivtv-controls.c +++ b/drivers/media/pci/ivtv/ivtv-controls.c @@ -60,7 +60,7 @@ static int ivtv_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val) format.format.width = cxhdl->width / (is_mpeg1 ? 2 : 1); format.format.height = cxhdl->height; format.format.code = MEDIA_BUS_FMT_FIXED; - v4l2_subdev_call(itv->sd_video, pad, set_fmt, NULL, &format); + v4l2_subdev_call(itv->sd_video, pad, set_fmt, NULL, NULL, &format); return 0; } diff --git a/drivers/media/pci/ivtv/ivtv-ioctl.c b/drivers/media/pci/ivtv/ivtv-ioctl.c index fc95f0bf48d5..aba55fcc1e22 100644 --- a/drivers/media/pci/ivtv/ivtv-ioctl.c +++ b/drivers/media/pci/ivtv/ivtv-ioctl.c @@ -587,7 +587,7 @@ static int ivtv_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f format.format.width = fmt->fmt.pix.width; format.format.height = h; format.format.code = MEDIA_BUS_FMT_FIXED; - v4l2_subdev_call(itv->sd_video, pad, set_fmt, NULL, &format); + v4l2_subdev_call(itv->sd_video, pad, set_fmt, NULL, NULL, &format); return ivtv_g_fmt_vid_cap(file, fh, fmt); } diff --git a/drivers/media/pci/saa7134/saa7134-empress.c b/drivers/media/pci/saa7134/saa7134-empress.c index 8c4f70e4177d..d04a68bb05d8 100644 --- a/drivers/media/pci/saa7134/saa7134-empress.c +++ b/drivers/media/pci/saa7134/saa7134-empress.c @@ -122,7 +122,7 @@ static int empress_s_fmt_vid_cap(struct file *file, void *priv, }; v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); - saa_call_all(dev, pad, set_fmt, NULL, &format); + saa_call_all(dev, pad, set_fmt, NULL, NULL, &format); v4l2_fill_pix_format(&f->fmt.pix, &format.format); f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; @@ -145,7 +145,7 @@ static int empress_try_fmt_vid_cap(struct file *file, void *priv, }; v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); - saa_call_all(dev, pad, set_fmt, &pad_state, &format); + saa_call_all(dev, pad, set_fmt, NULL, &pad_state, &format); v4l2_fill_pix_format(&f->fmt.pix, &format.format); f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.c b/drivers/media/platform/amd/isp4/isp4_subdev.c index 6716ab9c128a..a108d90ace2d 100644 --- a/drivers/media/platform/amd/isp4/isp4_subdev.c +++ b/drivers/media/platform/amd/isp4/isp4_subdev.c @@ -906,6 +906,7 @@ static const struct v4l2_subdev_video_ops isp4sd_video_ops = { }; static int isp4sd_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/amd/isp4/isp4_video.c b/drivers/media/platform/amd/isp4/isp4_video.c index 0cebb39f98e1..856a2a0b4a12 100644 --- a/drivers/media/platform/amd/isp4/isp4_video.c +++ b/drivers/media/platform/amd/isp4/isp4_video.c @@ -240,7 +240,7 @@ static int isp4vid_set_fmt_2_isp(struct v4l2_subdev *sdev, fmt.pad = ISP4VID_PAD_VIDEO_OUTPUT; fmt.format.width = pix_fmt->width; fmt.format.height = pix_fmt->height; - return v4l2_subdev_call(sdev, pad, set_fmt, NULL, &fmt); + return v4l2_subdev_call(sdev, pad, set_fmt, NULL, NULL, &fmt); } static int isp4vid_s_fmt_vid_cap(struct file *file, void *priv, diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c index ff6413fff889..553c808f8f3b 100644 --- a/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-core.c @@ -461,6 +461,7 @@ static void c3_isp_core_set_source_fmt(struct v4l2_subdev_state *state, } static int c3_isp_core_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c index 453a889e0b27..1f9c16eb0842 100644 --- a/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c +++ b/drivers/media/platform/amlogic/c3/isp/c3-isp-resizer.c @@ -621,6 +621,7 @@ static void c3_isp_rsz_set_source_fmt(struct v4l2_subdev_state *state, } static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -633,6 +634,7 @@ static int c3_isp_rsz_set_fmt(struct v4l2_subdev *sd, } static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -674,6 +676,7 @@ static int c3_isp_rsz_get_selection(struct v4l2_subdev *sd, } static int c3_isp_rsz_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c b/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c index 4bd98fb9c7e9..6f75112723b5 100644 --- a/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c +++ b/drivers/media/platform/amlogic/c3/mipi-adapter/c3-mipi-adap.c @@ -521,6 +521,7 @@ static int c3_mipi_adap_enum_mbus_code(struct v4l2_subdev *sd, } static int c3_mipi_adap_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c b/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c index b9e4ef3fc308..0c399665f7fa 100644 --- a/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c +++ b/drivers/media/platform/amlogic/c3/mipi-csi2/c3-mipi-csi2.c @@ -491,6 +491,7 @@ static int c3_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd, } static int c3_mipi_csi_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-isp.c b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c index e128adf6ee37..c8464dec9a21 100644 --- a/drivers/media/platform/arm/mali-c55/mali-c55-isp.c +++ b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c @@ -203,6 +203,7 @@ static int mali_c55_isp_enum_frame_size(struct v4l2_subdev *sd, } static int mali_c55_isp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -265,6 +266,7 @@ static int mali_c55_isp_set_fmt(struct v4l2_subdev *sd, } static int mali_c55_isp_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -278,6 +280,7 @@ static int mali_c55_isp_get_selection(struct v4l2_subdev *sd, } static int mali_c55_isp_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c index 6706939b4a90..774352c8a939 100644 --- a/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c +++ b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c @@ -715,6 +715,7 @@ static int mali_c55_rsz_enum_frame_size(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_sink_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -773,6 +774,7 @@ static int mali_c55_rsz_set_sink_fmt(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_source_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -812,6 +814,7 @@ static int mali_c55_rsz_set_source_fmt(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -826,12 +829,13 @@ static int mali_c55_rsz_set_fmt(struct v4l2_subdev *sd, if (format->pad == MALI_C55_RSZ_SINK_PAD || format->pad == MALI_C55_RSZ_SINK_BYPASS_PAD) - return mali_c55_rsz_set_sink_fmt(sd, state, format); + return mali_c55_rsz_set_sink_fmt(sd, ci, state, format); - return mali_c55_rsz_set_source_fmt(sd, state, format); + return mali_c55_rsz_set_source_fmt(sd, ci, state, format); } static int mali_c55_rsz_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -850,6 +854,7 @@ static int mali_c55_rsz_get_selection(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_crop(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -908,6 +913,7 @@ static int mali_c55_rsz_set_crop(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_compose(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -956,6 +962,7 @@ static int mali_c55_rsz_set_compose(struct v4l2_subdev *sd, } static int mali_c55_rsz_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -963,10 +970,10 @@ static int mali_c55_rsz_set_selection(struct v4l2_subdev *sd, return -EINVAL; if (sel->target == V4L2_SEL_TGT_CROP) - return mali_c55_rsz_set_crop(sd, state, sel); + return mali_c55_rsz_set_crop(sd, ci, state, sel); if (sel->target == V4L2_SEL_TGT_COMPOSE) - return mali_c55_rsz_set_compose(sd, state, sel); + return mali_c55_rsz_set_compose(sd, ci, state, sel); return -EINVAL; } diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c index 894f4cf377af..cc390a3fc253 100644 --- a/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c +++ b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c @@ -180,6 +180,7 @@ static int mali_c55_tpg_enum_frame_size(struct v4l2_subdev *sd, } static int mali_c55_tpg_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/atmel/atmel-isi.c b/drivers/media/platform/atmel/atmel-isi.c index a05a744cbb75..f5e85271a275 100644 --- a/drivers/media/platform/atmel/atmel-isi.c +++ b/drivers/media/platform/atmel/atmel-isi.c @@ -609,7 +609,7 @@ static int isi_try_fmt(struct atmel_isi *isi, struct v4l2_format *f, isi_try_fse(isi, isi_fmt, &pad_state); - ret = v4l2_subdev_call(isi->entity.subdev, pad, set_fmt, + ret = v4l2_subdev_call(isi->entity.subdev, pad, set_fmt, NULL, &pad_state, &format); if (ret < 0) return ret; @@ -641,7 +641,7 @@ static int isi_set_fmt(struct atmel_isi *isi, struct v4l2_format *f) v4l2_fill_mbus_format(&format.format, &f->fmt.pix, current_fmt->mbus_code); ret = v4l2_subdev_call(isi->entity.subdev, pad, - set_fmt, NULL, &format); + set_fmt, NULL, NULL, &format); if (ret < 0) return ret; diff --git a/drivers/media/platform/broadcom/bcm2835-unicam.c b/drivers/media/platform/broadcom/bcm2835-unicam.c index 14bb916dd7b1..e7c708cdd7f8 100644 --- a/drivers/media/platform/broadcom/bcm2835-unicam.c +++ b/drivers/media/platform/broadcom/bcm2835-unicam.c @@ -1327,6 +1327,7 @@ static int unicam_subdev_enum_frame_size(struct v4l2_subdev *sd, } static int unicam_subdev_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c index 9e05b3b9ee34..de7cce76397a 100644 --- a/drivers/media/platform/cadence/cdns-csi2rx.c +++ b/drivers/media/platform/cadence/cdns-csi2rx.c @@ -626,6 +626,7 @@ static int csi2rx_set_routing(struct v4l2_subdev *subdev, } static int csi2rx_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/cadence/cdns-csi2tx.c b/drivers/media/platform/cadence/cdns-csi2tx.c index 629b0fa838a2..07277866dca7 100644 --- a/drivers/media/platform/cadence/cdns-csi2tx.c +++ b/drivers/media/platform/cadence/cdns-csi2tx.c @@ -201,6 +201,7 @@ static int csi2tx_get_pad_format(struct v4l2_subdev *subdev, } static int csi2tx_set_pad_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/intel/pxa_camera.c b/drivers/media/platform/intel/pxa_camera.c index 2269d852ab0e..9e1fdd963b55 100644 --- a/drivers/media/platform/intel/pxa_camera.c +++ b/drivers/media/platform/intel/pxa_camera.c @@ -1819,7 +1819,7 @@ static int pxac_vidioc_try_fmt_vid_cap(struct file *filp, void *priv, pixfmt == V4L2_PIX_FMT_YUV422P ? 4 : 0); v4l2_fill_mbus_format(mf, pix, xlate->code); - ret = sensor_call(pcdev, pad, set_fmt, &pad_state, &format); + ret = sensor_call(pcdev, pad, set_fmt, NULL, &pad_state, &format); if (ret < 0) return ret; @@ -1882,7 +1882,7 @@ static int pxac_vidioc_s_fmt_vid_cap(struct file *filp, void *priv, xlate = pxa_mbus_xlate_by_fourcc(pcdev->user_formats, pix->pixelformat); v4l2_fill_mbus_format(&format.format, pix, xlate->code); - ret = sensor_call(pcdev, pad, set_fmt, NULL, &format); + ret = sensor_call(pcdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) { dev_warn(pcdev_to_dev(pcdev), "Failed to configure for format %x\n", @@ -2091,7 +2091,7 @@ static int pxa_camera_sensor_bound(struct v4l2_async_notifier *notifier, if (err) goto out; - err = sensor_call(pcdev, pad, set_fmt, NULL, &format); + err = sensor_call(pcdev, pad, set_fmt, NULL, NULL, &format); if (err) goto out_sensor_poweroff; diff --git a/drivers/media/platform/marvell/mcam-core.c b/drivers/media/platform/marvell/mcam-core.c index 2d0b8b60c509..34809d075a42 100644 --- a/drivers/media/platform/marvell/mcam-core.c +++ b/drivers/media/platform/marvell/mcam-core.c @@ -1024,7 +1024,7 @@ static int mcam_cam_configure(struct mcam_camera *cam) v4l2_fill_mbus_format(&format.format, &cam->pix_format, cam->mbus_code); ret = sensor_call(cam, core, init, 0); if (ret == 0) - ret = sensor_call(cam, pad, set_fmt, NULL, &format); + ret = sensor_call(cam, pad, set_fmt, NULL, NULL, &format); /* * OV7670 does weird things if flip is set *before* format... */ @@ -1363,7 +1363,7 @@ static int mcam_vidioc_try_fmt_vid_cap(struct file *filp, void *priv, f = mcam_find_format(pix->pixelformat); pix->pixelformat = f->pixelformat; v4l2_fill_mbus_format(&format.format, pix, f->mbus_code); - ret = sensor_call(cam, pad, set_fmt, &pad_state, &format); + ret = sensor_call(cam, pad, set_fmt, NULL, &pad_state, &format); v4l2_fill_pix_format(pix, &format.format); pix->bytesperline = pix->width * f->bpp; switch (f->pixelformat) { diff --git a/drivers/media/platform/microchip/microchip-csi2dc.c b/drivers/media/platform/microchip/microchip-csi2dc.c index b2c17ff2322f..0368f3e80c0c 100644 --- a/drivers/media/platform/microchip/microchip-csi2dc.c +++ b/drivers/media/platform/microchip/microchip-csi2dc.c @@ -244,6 +244,7 @@ static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd, } static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *req_fmt) { diff --git a/drivers/media/platform/microchip/microchip-isc-scaler.c b/drivers/media/platform/microchip/microchip-isc-scaler.c index e83463543e21..192532f87271 100644 --- a/drivers/media/platform/microchip/microchip-isc-scaler.c +++ b/drivers/media/platform/microchip/microchip-isc-scaler.c @@ -46,6 +46,7 @@ static int isc_scaler_get_fmt(struct v4l2_subdev *sd, } static int isc_scaler_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *req_fmt) { @@ -124,6 +125,7 @@ static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd, } static int isc_scaler_g_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/nxp/imx-mipi-csis.c b/drivers/media/platform/nxp/imx-mipi-csis.c index 5f9691d76434..1b9012dbae39 100644 --- a/drivers/media/platform/nxp/imx-mipi-csis.c +++ b/drivers/media/platform/nxp/imx-mipi-csis.c @@ -1104,6 +1104,7 @@ static int mipi_csis_enum_mbus_code(struct v4l2_subdev *sd, } static int mipi_csis_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *sdformat) { @@ -1225,7 +1226,7 @@ static int mipi_csis_init_state(struct v4l2_subdev *sd, V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt.format.colorspace, fmt.format.ycbcr_enc); - return mipi_csis_set_fmt(sd, state, &fmt); + return mipi_csis_set_fmt(sd, NULL, state, &fmt); } static int mipi_csis_log_status(struct v4l2_subdev *sd) diff --git a/drivers/media/platform/nxp/imx7-media-csi.c b/drivers/media/platform/nxp/imx7-media-csi.c index 7ddc7ba06e3d..0b2ee4dfdcbb 100644 --- a/drivers/media/platform/nxp/imx7-media-csi.c +++ b/drivers/media/platform/nxp/imx7-media-csi.c @@ -1890,6 +1890,7 @@ static void imx7_csi_try_fmt(struct v4l2_subdev *sd, } static int imx7_csi_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { 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 7bb1335f2110..8d50e5fb3b13 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c @@ -260,6 +260,7 @@ static int mxc_isi_crossbar_enum_mbus_code(struct v4l2_subdev *sd, } static int mxc_isi_crossbar_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c index 934f7b356258..a12e7445073c 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c @@ -524,6 +524,7 @@ static int mxc_isi_pipe_enum_mbus_code(struct v4l2_subdev *sd, } static int mxc_isi_pipe_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -619,6 +620,7 @@ static int mxc_isi_pipe_set_fmt(struct v4l2_subdev *sd, } static int mxc_isi_pipe_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -678,6 +680,7 @@ static int mxc_isi_pipe_get_selection(struct v4l2_subdev *sd, } static int mxc_isi_pipe_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c index 04ebed8a0493..17756b7fe1dd 100644 --- a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c +++ b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c @@ -597,6 +597,7 @@ static int imx8mq_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd, } static int imx8mq_mipi_csi_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/media/platform/nxp/imx95-csi-formatter.c b/drivers/media/platform/nxp/imx95-csi-formatter.c index 2043f5caaad1..37dfcefbf0d1 100644 --- a/drivers/media/platform/nxp/imx95-csi-formatter.c +++ b/drivers/media/platform/nxp/imx95-csi-formatter.c @@ -233,6 +233,7 @@ static int csi_formatter_subdev_enum_mbus_code(struct v4l2_subdev *sd, } static int csi_formatter_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/qcom/camss/camss-csid.c b/drivers/media/platform/qcom/camss/camss-csid.c index 48459b46a981..a00791b00feb 100644 --- a/drivers/media/platform/qcom/camss/camss-csid.c +++ b/drivers/media/platform/qcom/camss/camss-csid.c @@ -987,6 +987,7 @@ static int csid_get_format(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int csid_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1036,7 +1037,7 @@ static int csid_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) } }; - return csid_set_format(sd, fh ? fh->state : NULL, &format); + return csid_set_format(sd, NULL, fh ? fh->state : NULL, &format); } /* diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c index 539ac4888b60..0dd50f3879d9 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy.c @@ -503,6 +503,7 @@ static int csiphy_get_format(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int csiphy_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -555,7 +556,7 @@ static int csiphy_init_formats(struct v4l2_subdev *sd, } }; - return csiphy_set_format(sd, fh ? fh->state : NULL, &format); + return csiphy_set_format(sd, NULL, fh ? fh->state : NULL, &format); } static bool __printf(2, 3) diff --git a/drivers/media/platform/qcom/camss/camss-ispif.c b/drivers/media/platform/qcom/camss/camss-ispif.c index 20ccd7b1f11f..f266f8532ecc 100644 --- a/drivers/media/platform/qcom/camss/camss-ispif.c +++ b/drivers/media/platform/qcom/camss/camss-ispif.c @@ -1037,6 +1037,7 @@ static int ispif_get_format(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int ispif_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1085,7 +1086,7 @@ static int ispif_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) } }; - return ispif_set_format(sd, fh ? fh->state : NULL, &format); + return ispif_set_format(sd, NULL, fh ? fh->state : NULL, &format); } /* diff --git a/drivers/media/platform/qcom/camss/camss-tpg.c b/drivers/media/platform/qcom/camss/camss-tpg.c index c5b75132add4..5764a60fa34c 100644 --- a/drivers/media/platform/qcom/camss/camss-tpg.c +++ b/drivers/media/platform/qcom/camss/camss-tpg.c @@ -292,6 +292,7 @@ static int tpg_get_format(struct v4l2_subdev *sd, } static int tpg_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -322,7 +323,7 @@ static int tpg_init_formats(struct v4l2_subdev *sd, } }; - return tpg_set_format(sd, fh ? fh->state : NULL, &format); + return tpg_set_format(sd, NULL, fh ? fh->state : NULL, &format); } static int tpg_s_ctrl(struct v4l2_ctrl *ctrl) diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c index 319d19158988..c14d97a131f6 100644 --- a/drivers/media/platform/qcom/camss/camss-vfe.c +++ b/drivers/media/platform/qcom/camss/camss-vfe.c @@ -1562,6 +1562,7 @@ static int vfe_get_format(struct v4l2_subdev *sd, } static int vfe_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel); @@ -1574,6 +1575,7 @@ static int vfe_set_selection(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int vfe_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1608,7 +1610,7 @@ static int vfe_set_format(struct v4l2_subdev *sd, sel.target = V4L2_SEL_TGT_COMPOSE; sel.r.width = fmt->format.width; sel.r.height = fmt->format.height; - ret = vfe_set_selection(sd, sd_state, &sel); + ret = vfe_set_selection(sd, ci, sd_state, &sel); if (ret < 0) return ret; } @@ -1625,6 +1627,7 @@ static int vfe_set_format(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int vfe_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1695,6 +1698,7 @@ static int vfe_get_selection(struct v4l2_subdev *sd, * Return -EINVAL or zero on success */ static int vfe_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1721,7 +1725,7 @@ static int vfe_set_selection(struct v4l2_subdev *sd, crop.pad = MSM_VFE_PAD_SRC; crop.target = V4L2_SEL_TGT_CROP; crop.r = *rect; - ret = vfe_set_selection(sd, sd_state, &crop); + ret = vfe_set_selection(sd, ci, sd_state, &crop); } else if (sel->target == V4L2_SEL_TGT_CROP && sel->pad == MSM_VFE_PAD_SRC) { struct v4l2_subdev_format fmt = { 0 }; @@ -1742,7 +1746,7 @@ static int vfe_set_selection(struct v4l2_subdev *sd, fmt.format.width = rect->width; fmt.format.height = rect->height; - ret = vfe_set_format(sd, sd_state, &fmt); + ret = vfe_set_format(sd, ci, sd_state, &fmt); } else { ret = -EINVAL; } @@ -1772,7 +1776,7 @@ static int vfe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) } }; - return vfe_set_format(sd, fh ? fh->state : NULL, &format); + return vfe_set_format(sd, NULL, fh ? fh->state : NULL, &format); } /* diff --git a/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c b/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c index 104908afbf41..66d0dde6d840 100644 --- a/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c +++ b/drivers/media/platform/raspberrypi/rp1-cfe/csi2.c @@ -404,6 +404,7 @@ static int csi2_init_state(struct v4l2_subdev *sd, } static int csi2_pad_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/raspberrypi/rp1-cfe/pisp-fe.c b/drivers/media/platform/raspberrypi/rp1-cfe/pisp-fe.c index 05762b1be2bc..ea21fb1f60cf 100644 --- a/drivers/media/platform/raspberrypi/rp1-cfe/pisp-fe.c +++ b/drivers/media/platform/raspberrypi/rp1-cfe/pisp-fe.c @@ -429,6 +429,7 @@ static int pisp_fe_init_state(struct v4l2_subdev *sd, } static int pisp_fe_pad_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 41e3c6b2ebb7..4851fe649d8a 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -1954,6 +1954,7 @@ static int rcsi2_disable_streams(struct v4l2_subdev *sd, } static int rcsi2_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/renesas/rcar-isp/csisp.c b/drivers/media/platform/renesas/rcar-isp/csisp.c index 3adee4e87a9c..635eeacb3b19 100644 --- a/drivers/media/platform/renesas/rcar-isp/csisp.c +++ b/drivers/media/platform/renesas/rcar-isp/csisp.c @@ -405,6 +405,7 @@ static int risp_disable_streams(struct v4l2_subdev *sd, } static int risp_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/renesas/renesas-ceu.c b/drivers/media/platform/renesas/renesas-ceu.c index 65f7659a9e02..47b9e4753301 100644 --- a/drivers/media/platform/renesas/renesas-ceu.c +++ b/drivers/media/platform/renesas/renesas-ceu.c @@ -841,12 +841,13 @@ static int __ceu_try_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt * time. */ sd_format.format.code = mbus_code; - ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, &pad_state, &sd_format); + ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &pad_state, + &sd_format); if (ret) { if (ret == -EINVAL) { /* fallback */ sd_format.format.code = mbus_code_old; - ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, + ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &pad_state, &sd_format); } @@ -900,7 +901,7 @@ static int ceu_set_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt) format.format.code = mbus_code; v4l2_fill_mbus_format_mplane(&format.format, &v4l2_fmt->fmt.pix_mp); - ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &format); + ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, NULL, &format); if (ret) return ret; diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c index 6dc4b53607b4..c17283cba3a6 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c @@ -633,6 +633,7 @@ static int rzg2l_csi2_post_streamoff(struct v4l2_subdev *sd) } static int rzg2l_csi2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -687,7 +688,7 @@ static int rzg2l_csi2_init_state(struct v4l2_subdev *sd, fmt.format.quantization = V4L2_QUANTIZATION_DEFAULT; fmt.format.xfer_func = V4L2_XFER_FUNC_DEFAULT; - return rzg2l_csi2_set_format(sd, sd_state, &fmt); + return rzg2l_csi2_set_format(sd, NULL, sd_state, &fmt); } static int rzg2l_csi2_enum_mbus_code(struct v4l2_subdev *sd, diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c index 5f2c87858bfe..21dd0162fd6e 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c @@ -200,6 +200,7 @@ static int rzg2l_cru_ip_s_stream(struct v4l2_subdev *sd, int enable) } static int rzg2l_cru_ip_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -300,7 +301,7 @@ static int rzg2l_cru_ip_init_state(struct v4l2_subdev *sd, fmt.format.quantization = V4L2_QUANTIZATION_DEFAULT; fmt.format.xfer_func = V4L2_XFER_FUNC_DEFAULT; - return rzg2l_cru_ip_set_format(sd, sd_state, &fmt); + return rzg2l_cru_ip_set_format(sd, NULL, sd_state, &fmt); } static const struct v4l2_subdev_video_ops rzg2l_cru_ip_video_ops = { diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c index b1659544eaa0..04e168b9526f 100644 --- a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c @@ -157,6 +157,7 @@ static int rzv2h_ivc_enum_frame_size(struct v4l2_subdev *sd, } static int rzv2h_ivc_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/renesas/sh_vou.c b/drivers/media/platform/renesas/sh_vou.c index 4ad7ae188d5b..6d4ec80edd8f 100644 --- a/drivers/media/platform/renesas/sh_vou.c +++ b/drivers/media/platform/renesas/sh_vou.c @@ -714,7 +714,7 @@ static int sh_vou_set_fmt_vid_out(struct sh_vou_device *vou_dev, mbfmt->width = geo.output.width; mbfmt->height = geo.output.height; ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, - set_fmt, NULL, &format); + set_fmt, NULL, NULL, &format); /* Must be implemented, so, don't check for -ENOIOCTLCMD */ if (ret < 0) return ret; @@ -974,11 +974,11 @@ static int sh_vou_s_selection(struct file *file, void *fh, * final encoder configuration. */ v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, - set_selection, NULL, &sd_sel); + set_selection, NULL, NULL, &sd_sel); format.format.width = geo.output.width; format.format.height = geo.output.height; ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, - set_fmt, NULL, &format); + set_fmt, NULL, NULL, &format); /* Must be implemented, so, don't check for -ENOIOCTLCMD */ if (ret < 0) return ret; diff --git a/drivers/media/platform/renesas/vsp1/vsp1_brx.c b/drivers/media/platform/renesas/vsp1/vsp1_brx.c index 360a42502947..a70b82e22e69 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_brx.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_brx.c @@ -124,6 +124,7 @@ static void brx_try_format(struct vsp1_brx *brx, } static int brx_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -165,6 +166,7 @@ static int brx_set_format(struct v4l2_subdev *subdev, } static int brx_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -200,6 +202,7 @@ static int brx_get_selection(struct v4l2_subdev *subdev, } static int brx_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/renesas/vsp1/vsp1_drm.c b/drivers/media/platform/renesas/vsp1/vsp1_drm.c index 9cd5c025d2be..adb89e699389 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_drm.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_drm.c @@ -137,7 +137,8 @@ static int vsp1_du_insert_uif(struct vsp1_device *vsp1, format.pad = UIF_PAD_SINK; - ret = v4l2_subdev_call(&uif->subdev, pad, set_fmt, NULL, &format); + ret = v4l2_subdev_call(&uif->subdev, pad, set_fmt, NULL, NULL, + &format); if (ret < 0) return ret; @@ -184,7 +185,7 @@ static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, format.format.ycbcr_enc = input->ycbcr_enc; format.format.quantization = input->quantization; - ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) return ret; @@ -199,6 +200,7 @@ static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, sel.r = input->crop; ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_selection, NULL, + NULL, &sel); if (ret < 0) return ret; @@ -226,7 +228,7 @@ static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; - ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) return ret; @@ -240,7 +242,7 @@ static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, /* BRx sink, propagate the format from the RPF source. */ format.pad = brx_input; - ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) return ret; @@ -254,6 +256,7 @@ static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, sel.r = vsp1->drm->inputs[rpf->entity.index].compose; ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_selection, NULL, + NULL, &sel); if (ret < 0) return ret; @@ -387,7 +390,7 @@ static int vsp1_du_pipeline_setup_brx(struct vsp1_device *vsp1, format.format.height = drm_pipe->height; format.format.field = V4L2_FIELD_NONE; - ret = v4l2_subdev_call(&brx->subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&brx->subdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) return ret; @@ -538,7 +541,8 @@ static int vsp1_du_pipeline_setup_output(struct vsp1_device *vsp1, format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; format.format.field = V4L2_FIELD_NONE; - ret = v4l2_subdev_call(&pipe->output->entity.subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&pipe->output->entity.subdev, pad, set_fmt, + NULL, NULL, &format); if (ret < 0) return ret; @@ -558,7 +562,7 @@ static int vsp1_du_pipeline_setup_output(struct vsp1_device *vsp1, format.format.code, pipe->output->entity.index); format.pad = LIF_PAD_SINK; - ret = v4l2_subdev_call(&pipe->lif->subdev, pad, set_fmt, NULL, + ret = v4l2_subdev_call(&pipe->lif->subdev, pad, set_fmt, NULL, NULL, &format); if (ret < 0) return ret; diff --git a/drivers/media/platform/renesas/vsp1/vsp1_entity.c b/drivers/media/platform/renesas/vsp1/vsp1_entity.c index 26b21559878d..f7359d347947 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_entity.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_entity.c @@ -300,6 +300,7 @@ int vsp1_subdev_enum_frame_size(struct v4l2_subdev *subdev, * entity's limits, and propagates the sink pad format to the source pad. */ int vsp1_subdev_set_pad_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -380,7 +381,8 @@ static int vsp1_entity_init_state(struct v4l2_subdev *subdev, : V4L2_SUBDEV_FORMAT_ACTIVE, }; - v4l2_subdev_call(subdev, pad, set_fmt, sd_state, &format); + v4l2_subdev_call(subdev, pad, set_fmt, NULL, sd_state, + &format); } return 0; diff --git a/drivers/media/platform/renesas/vsp1/vsp1_entity.h b/drivers/media/platform/renesas/vsp1/vsp1_entity.h index c0c1fe7d3e40..677a1efc104a 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_entity.h +++ b/drivers/media/platform/renesas/vsp1/vsp1_entity.h @@ -188,6 +188,7 @@ int vsp1_subdev_get_pad_format(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt); int vsp1_subdev_set_pad_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt); int vsp1_subdev_enum_mbus_code(struct v4l2_subdev *subdev, diff --git a/drivers/media/platform/renesas/vsp1/vsp1_histo.c b/drivers/media/platform/renesas/vsp1/vsp1_histo.c index 97dbfb93abe9..bd9efb42fd1f 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_histo.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_histo.c @@ -185,6 +185,7 @@ static int histo_enum_frame_size(struct v4l2_subdev *subdev, } static int histo_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -306,6 +307,7 @@ static int histo_set_compose(struct v4l2_subdev *subdev, } static int histo_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -330,6 +332,7 @@ static int histo_set_selection(struct v4l2_subdev *subdev, } static int histo_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/renesas/vsp1/vsp1_hsit.c b/drivers/media/platform/renesas/vsp1/vsp1_hsit.c index df069c228243..d734b5775af6 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_hsit.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_hsit.c @@ -109,6 +109,7 @@ static int hsit_enum_frame_size(struct v4l2_subdev *subdev, } static int hsit_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/renesas/vsp1/vsp1_rwpf.c b/drivers/media/platform/renesas/vsp1/vsp1_rwpf.c index ced01870acd6..ff5894265b54 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_rwpf.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_rwpf.c @@ -110,6 +110,7 @@ static int vsp1_rwpf_enum_frame_size(struct v4l2_subdev *subdev, } static int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -214,6 +215,7 @@ static int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, } static int vsp1_rwpf_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -255,6 +257,7 @@ static int vsp1_rwpf_get_selection(struct v4l2_subdev *subdev, } static int vsp1_rwpf_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/renesas/vsp1/vsp1_sru.c b/drivers/media/platform/renesas/vsp1/vsp1_sru.c index 3fd9fde5c724..5e1cf60be311 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_sru.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_sru.c @@ -210,6 +210,7 @@ static void sru_try_format(struct vsp1_sru *sru, } static int sru_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/renesas/vsp1/vsp1_uds.c b/drivers/media/platform/renesas/vsp1/vsp1_uds.c index 9f7bb112929e..d129964ec14d 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_uds.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_uds.c @@ -193,6 +193,7 @@ static void uds_try_format(struct vsp1_uds *uds, } static int uds_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/renesas/vsp1/vsp1_uif.c b/drivers/media/platform/renesas/vsp1/vsp1_uif.c index 52dbfe58a70d..16ddcd594134 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_uif.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_uif.c @@ -54,6 +54,7 @@ static const unsigned int uif_codes[] = { }; static int uif_get_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -92,6 +93,7 @@ static int uif_get_selection(struct v4l2_subdev *subdev, } static int uif_set_selection(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/renesas/vsp1/vsp1_vspx.c b/drivers/media/platform/renesas/vsp1/vsp1_vspx.c index 1673479be0ff..5c39bacfb13f 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_vspx.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_vspx.c @@ -120,7 +120,8 @@ static int vsp1_vspx_rwpf_set_subdev_fmt(struct vsp1_device *vsp1, format.format.field = V4L2_FIELD_NONE; format.format.code = rwpf->fmtinfo->mbus; - return v4l2_subdev_call(&ent->subdev, pad, set_fmt, NULL, &format); + return v4l2_subdev_call(&ent->subdev, pad, set_fmt, NULL, NULL, + &format); } /* Configure the RPF->IIF->WPF pipeline for ConfigDMA or RAW image transfer. */ diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c index 414a9980cf2e..2cba42d7c5d9 100644 --- a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c +++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c @@ -25,6 +25,7 @@ static const struct media_entity_operations rkcif_interface_media_ops = { }; static int rkcif_interface_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -77,6 +78,7 @@ static int rkcif_interface_set_fmt(struct v4l2_subdev *sd, } static int rkcif_interface_get_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -115,6 +117,7 @@ static int rkcif_interface_get_sel(struct v4l2_subdev *sd, } static int rkcif_interface_set_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c index ddc6182f3e4b..9fa8a0407c90 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c @@ -304,6 +304,7 @@ static int rkisp1_csi_init_state(struct v4l2_subdev *sd, } static int rkisp1_csi_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c index 2311672cedb1..4043a75ca3f0 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c @@ -817,6 +817,7 @@ static void rkisp1_isp_set_sink_fmt(struct rkisp1_isp *isp, } static int rkisp1_isp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -834,6 +835,7 @@ static int rkisp1_isp_set_fmt(struct v4l2_subdev *sd, } static int rkisp1_isp_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -872,6 +874,7 @@ static int rkisp1_isp_get_selection(struct v4l2_subdev *sd, } static int rkisp1_isp_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c index 8e6b753d3081..5926917434af 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c @@ -543,6 +543,7 @@ static void rkisp1_rsz_set_sink_fmt(struct rkisp1_resizer *rsz, } static int rkisp1_rsz_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -558,6 +559,7 @@ static int rkisp1_rsz_set_fmt(struct v4l2_subdev *sd, } static int rkisp1_rsz_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -591,6 +593,7 @@ static int rkisp1_rsz_get_selection(struct v4l2_subdev *sd, } static int rkisp1_rsz_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-capture.c b/drivers/media/platform/samsung/exynos4-is/fimc-capture.c index d85811f4b8c5..8d7e7b190532 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-capture.c +++ b/drivers/media/platform/samsung/exynos4-is/fimc-capture.c @@ -796,7 +796,8 @@ static int fimc_pipeline_try_format(struct fimc_ctx *ctx, sd = media_entity_to_v4l2_subdev(me); sfmt.pad = 0; - ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &sfmt); + ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, NULL, + &sfmt); if (ret) return ret; @@ -804,7 +805,7 @@ static int fimc_pipeline_try_format(struct fimc_ctx *ctx, sfmt.pad = me->num_pads - 1; mf->code = tfmt->code; ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, - &sfmt); + NULL, &sfmt); if (ret) return ret; } @@ -1509,6 +1510,7 @@ static int fimc_subdev_get_fmt(struct v4l2_subdev *sd, } static int fimc_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1575,6 +1577,7 @@ static int fimc_subdev_set_fmt(struct v4l2_subdev *sd, } static int fimc_subdev_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1631,6 +1634,7 @@ static int fimc_subdev_get_selection(struct v4l2_subdev *sd, } static int fimc_subdev_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-isp.c b/drivers/media/platform/samsung/exynos4-is/fimc-isp.c index 3c5d7bee2655..a0a9093081dd 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-isp.c +++ b/drivers/media/platform/samsung/exynos4-is/fimc-isp.c @@ -191,6 +191,7 @@ static void __isp_subdev_try_format(struct fimc_isp *isp, } static int fimc_isp_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-lite.c b/drivers/media/platform/samsung/exynos4-is/fimc-lite.c index 8be20fd32d1c..1b7bae15189b 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-lite.c +++ b/drivers/media/platform/samsung/exynos4-is/fimc-lite.c @@ -1052,6 +1052,7 @@ static int fimc_lite_subdev_get_fmt(struct v4l2_subdev *sd, } static int fimc_lite_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1113,6 +1114,7 @@ static int fimc_lite_subdev_set_fmt(struct v4l2_subdev *sd, } static int fimc_lite_subdev_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1148,6 +1150,7 @@ static int fimc_lite_subdev_get_selection(struct v4l2_subdev *sd, } static int fimc_lite_subdev_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/samsung/exynos4-is/mipi-csis.c b/drivers/media/platform/samsung/exynos4-is/mipi-csis.c index 452880b5350c..da6e67a79c09 100644 --- a/drivers/media/platform/samsung/exynos4-is/mipi-csis.c +++ b/drivers/media/platform/samsung/exynos4-is/mipi-csis.c @@ -575,6 +575,7 @@ static struct v4l2_mbus_framefmt *__s5pcsis_get_format( } static int s5pcsis_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/samsung/s3c-camif/camif-capture.c b/drivers/media/platform/samsung/s3c-camif/camif-capture.c index ed1a1d693293..98dfd394a1aa 100644 --- a/drivers/media/platform/samsung/s3c-camif/camif-capture.c +++ b/drivers/media/platform/samsung/s3c-camif/camif-capture.c @@ -1275,6 +1275,7 @@ static void __camif_subdev_try_format(struct camif_dev *camif, } static int s3c_camif_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1342,6 +1343,7 @@ static int s3c_camif_subdev_set_fmt(struct v4l2_subdev *sd, } static int s3c_camif_subdev_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1429,6 +1431,7 @@ static void __camif_try_crop(struct camif_dev *camif, struct v4l2_rect *r) } static int s3c_camif_subdev_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/platform/samsung/s3c-camif/camif-core.c b/drivers/media/platform/samsung/s3c-camif/camif-core.c index bb06847f3a63..2ff71e375efe 100644 --- a/drivers/media/platform/samsung/s3c-camif/camif-core.c +++ b/drivers/media/platform/samsung/s3c-camif/camif-core.c @@ -227,7 +227,7 @@ static int camif_register_sensor(struct camif_dev *camif) return 0; format.pad = CAMIF_SD_PAD_SINK; - v4l2_subdev_call(&camif->subdev, pad, set_fmt, NULL, &format); + v4l2_subdev_call(&camif->subdev, pad, set_fmt, NULL, NULL, &format); v4l2_info(sd, "Initial format from sensor: %dx%d, %#x\n", format.format.width, format.format.height, diff --git a/drivers/media/platform/st/stm32/stm32-csi.c b/drivers/media/platform/st/stm32/stm32-csi.c index 78c68cf90ffc..e71142634a17 100644 --- a/drivers/media/platform/st/stm32/stm32-csi.c +++ b/drivers/media/platform/st/stm32/stm32-csi.c @@ -736,6 +736,7 @@ static int stm32_csi_enum_mbus_code(struct v4l2_subdev *sd, } static int stm32_csi_set_pad_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c index aa79fe60ff0c..3d774cb4907b 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmi.c +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -738,7 +738,7 @@ static int dcmi_pipeline_s_fmt(struct stm32_dcmi *dcmi, format->format.width, format->format.height); fmt.pad = pad->index; - ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &fmt); + ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, NULL, &fmt); if (ret < 0) { dev_err(dcmi->dev, "%s: Failed to set format 0x%x %ux%u on \"%s\":%d pad (%d)\n", __func__, format->format.code, @@ -1022,6 +1022,27 @@ static void __find_outer_frame_size(struct stm32_dcmi *dcmi, *framesize = *match; } +static int dcmi_source_call_try_state_set_fmt(struct v4l2_subdev *source, + struct v4l2_subdev_format *fmt) +{ + static struct lock_class_key lock_key; + const char *lock_name = KBUILD_BASENAME ":" __stringify(__LINE__) + ":state->lock"; + struct v4l2_subdev_state *source_state; + int ret; + + source_state = __v4l2_subdev_state_alloc(source, lock_name, &lock_key); + if (IS_ERR(source_state)) + return PTR_ERR(source_state); + + v4l2_subdev_lock_state(source_state); + ret = v4l2_subdev_call(source, pad, set_fmt, NULL, source_state, fmt); + v4l2_subdev_unlock_state(source_state); + __v4l2_subdev_state_free(source_state); + + return ret; +} + static int dcmi_try_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f, const struct dcmi_format **sd_format, struct dcmi_framesize *sd_framesize) @@ -1063,8 +1084,8 @@ static int dcmi_try_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f, } v4l2_fill_mbus_format(&format.format, pix, sd_fmt->mbus_code); - ret = v4l2_subdev_call_state_try(dcmi->source, pad, set_fmt, &format); - if (ret < 0) + ret = dcmi_source_call_try_state_set_fmt(dcmi->source, &format); + if (ret) return ret; /* Update pix regarding to what sensor can do */ @@ -1224,7 +1245,7 @@ static int dcmi_set_sensor_format(struct stm32_dcmi *dcmi, } v4l2_fill_mbus_format(&format.format, pix, sd_fmt->mbus_code); - ret = v4l2_subdev_call_state_try(dcmi->source, pad, set_fmt, &format); + ret = dcmi_source_call_try_state_set_fmt(dcmi->source, &format); if (ret < 0) return ret; @@ -1246,7 +1267,7 @@ static int dcmi_get_sensor_bounds(struct stm32_dcmi *dcmi, /* * Get sensor bounds first */ - ret = v4l2_subdev_call(dcmi->source, pad, get_selection, + ret = v4l2_subdev_call(dcmi->source, pad, get_selection, NULL, NULL, &bounds); if (!ret) *r = bounds.r; 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 f61618e12322..346cfddcf229 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-byteproc.c @@ -265,6 +265,7 @@ dcmipp_byteproc_enum_frame_size(struct v4l2_subdev *sd, } static int dcmipp_byteproc_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -299,6 +300,7 @@ static int dcmipp_byteproc_set_fmt(struct v4l2_subdev *sd, } static int dcmipp_byteproc_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *s) { @@ -352,6 +354,7 @@ static int dcmipp_byteproc_get_selection(struct v4l2_subdev *sd, } static int dcmipp_byteproc_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *s) { 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 220e03ea38aa..e66a9456deb6 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-input.c @@ -275,6 +275,7 @@ static void dcmipp_inp_adjust_fmt(struct dcmipp_inp_device *inp, } static int dcmipp_inp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c index 1586cc9c59a6..088301c20547 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-isp.c @@ -133,6 +133,7 @@ static int dcmipp_isp_enum_frame_size(struct v4l2_subdev *sd, } static int dcmipp_isp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -205,6 +206,7 @@ static void dcmipp_isp_adjust_crop(struct v4l2_rect *r, } static int dcmipp_isp_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *s) { diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c index 8b0699e870ef..d6f19db848d5 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.c @@ -138,6 +138,7 @@ int dcmipp_pixelpipe_enum_frame_size(unsigned int id, } int dcmipp_pixelpipe_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *s) { diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h index db29e1fe939e..2d7c16c36c7d 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelcommon.h @@ -33,6 +33,7 @@ int dcmipp_pixelpipe_enum_frame_size(unsigned int id, struct v4l2_subdev_frame_size_enum *fse); int dcmipp_pixelpipe_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *s); diff --git a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c index d771b1b21bd2..7d8e5f0863bf 100644 --- a/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c +++ b/drivers/media/platform/st/stm32/stm32-dcmipp/dcmipp-pixelproc.c @@ -512,6 +512,7 @@ dcmipp_pixelproc_enum_frame_size(struct v4l2_subdev *sd, } static int dcmipp_pixelproc_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { @@ -564,6 +565,7 @@ static int dcmipp_pixelproc_set_fmt(struct v4l2_subdev *sd, } static int dcmipp_pixelproc_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *s) { diff --git a/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c b/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c index 744197b0fccb..af35ece1d5cf 100644 --- a/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c +++ b/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c @@ -295,6 +295,7 @@ static int sun4i_csi_subdev_get_fmt(struct v4l2_subdev *subdev, } static int sun4i_csi_subdev_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c index 43a85bcc2ba2..f9ee94fc1293 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c @@ -530,6 +530,7 @@ sun6i_csi_bridge_enum_mbus_code(struct v4l2_subdev *subdev, } static int sun6i_csi_bridge_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c index 17a9a215a98a..dcc8c7eadf9a 100644 --- a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c @@ -353,6 +353,7 @@ sun6i_mipi_csi2_enum_mbus_code(struct v4l2_subdev *subdev, } static int sun6i_mipi_csi2_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c index 38a6ef19f4ff..46604dcc8dcc 100644 --- a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c @@ -388,6 +388,7 @@ sun8i_a83t_mipi_csi2_enum_mbus_code(struct v4l2_subdev *subdev, } static int sun8i_a83t_mipi_csi2_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c index 41e48365167e..b7247d597c9c 100644 --- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c @@ -479,6 +479,7 @@ dw_mipi_csi2rx_enum_mbus_code(struct v4l2_subdev *sd, } static int dw_mipi_csi2rx_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/ti/am437x/am437x-vpfe.c b/drivers/media/platform/ti/am437x/am437x-vpfe.c index 1ca559df7e59..3cd5ee69d3c4 100644 --- a/drivers/media/platform/ti/am437x/am437x-vpfe.c +++ b/drivers/media/platform/ti/am437x/am437x-vpfe.c @@ -1314,7 +1314,7 @@ static int __subdev_set_format(struct vpfe_device *vpfe, *mbus_fmt = *fmt; - ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &sd_fmt); + ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, NULL, &sd_fmt); if (ret) return ret; diff --git a/drivers/media/platform/ti/cal/cal-camerarx.c b/drivers/media/platform/ti/cal/cal-camerarx.c index 00a71dac0ff4..a978d27fe029 100644 --- a/drivers/media/platform/ti/cal/cal-camerarx.c +++ b/drivers/media/platform/ti/cal/cal-camerarx.c @@ -762,6 +762,7 @@ static int cal_camerarx_sd_enum_frame_size(struct v4l2_subdev *sd, } static int cal_camerarx_sd_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/ti/cal/cal-video.c b/drivers/media/platform/ti/cal/cal-video.c index d40e24ab1127..59f0cc4f0d1a 100644 --- a/drivers/media/platform/ti/cal/cal-video.c +++ b/drivers/media/platform/ti/cal/cal-video.c @@ -123,6 +123,23 @@ static int __subdev_get_format(struct cal_ctx *ctx, return 0; } +static int cal_call_active_state_set_fmt(struct v4l2_subdev *source, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_subdev_state *source_state; + int ret; + + source_state = v4l2_subdev_lock_and_get_active_state(source); + if (IS_ERR(source_state)) + return PTR_ERR(source_state); + + ret = v4l2_subdev_call(source, pad, set_fmt, NULL, source_state, fmt); + if (source_state) + v4l2_subdev_unlock_state(source_state); + + return ret; +} + static int __subdev_set_format(struct cal_ctx *ctx, struct v4l2_mbus_framefmt *fmt) { @@ -136,7 +153,7 @@ static int __subdev_set_format(struct cal_ctx *ctx, *mbus_fmt = *fmt; - ret = v4l2_subdev_call_state_active(sd, pad, set_fmt, &sd_fmt); + ret = cal_call_active_state_set_fmt(sd, &sd_fmt); if (ret) return ret; @@ -281,7 +298,7 @@ static int cal_legacy_s_fmt_vid_cap(struct file *file, void *priv, ctx->v_fmt.fmt.pix.field = sd_fmt.format.field; cal_calc_format_size(ctx, fmtinfo, &ctx->v_fmt); - v4l2_subdev_call_state_active(sd, pad, set_fmt, &sd_fmt); + cal_call_active_state_set_fmt(sd, &sd_fmt); ctx->fmtinfo = fmtinfo; *f = ctx->v_fmt; diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c index 4ba0ba6ad935..022ea3d616c9 100644 --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c @@ -1067,6 +1067,7 @@ static int ti_csi2rx_enum_mbus_code(struct v4l2_subdev *subdev, } static int ti_csi2rx_sd_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/platform/ti/omap3isp/ispccdc.c b/drivers/media/platform/ti/omap3isp/ispccdc.c index 4708b6303493..f422d70ab6e4 100644 --- a/drivers/media/platform/ti/omap3isp/ispccdc.c +++ b/drivers/media/platform/ti/omap3isp/ispccdc.c @@ -2232,6 +2232,7 @@ static int ccdc_enum_frame_size(struct v4l2_subdev *sd, * Return 0 on success or a negative error code otherwise. */ static int ccdc_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2276,6 +2277,7 @@ static int ccdc_get_selection(struct v4l2_subdev *sd, * Return 0 on success or a negative error code otherwise. */ static int ccdc_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2346,6 +2348,7 @@ static int ccdc_get_format(struct v4l2_subdev *sd, * to the format type. */ static int ccdc_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -2472,7 +2475,7 @@ static int ccdc_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) format.format.code = MEDIA_BUS_FMT_SGRBG10_1X10; format.format.width = 4096; format.format.height = 4096; - ccdc_set_format(sd, fh ? fh->state : NULL, &format); + ccdc_set_format(sd, NULL, fh ? fh->state : NULL, &format); return 0; } diff --git a/drivers/media/platform/ti/omap3isp/ispccp2.c b/drivers/media/platform/ti/omap3isp/ispccp2.c index d668111b44f4..c0da5cca9ddc 100644 --- a/drivers/media/platform/ti/omap3isp/ispccp2.c +++ b/drivers/media/platform/ti/omap3isp/ispccp2.c @@ -776,6 +776,7 @@ static int ccp2_get_format(struct v4l2_subdev *sd, * returns zero */ static int ccp2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -820,7 +821,7 @@ static int ccp2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) format.format.code = MEDIA_BUS_FMT_SGRBG10_1X10; format.format.width = 4096; format.format.height = 4096; - ccp2_set_format(sd, fh ? fh->state : NULL, &format); + ccp2_set_format(sd, NULL, fh ? fh->state : NULL, &format); return 0; } diff --git a/drivers/media/platform/ti/omap3isp/ispcsi2.c b/drivers/media/platform/ti/omap3isp/ispcsi2.c index f227042b61b6..8fc2948d654c 100644 --- a/drivers/media/platform/ti/omap3isp/ispcsi2.c +++ b/drivers/media/platform/ti/omap3isp/ispcsi2.c @@ -994,6 +994,7 @@ static int csi2_get_format(struct v4l2_subdev *sd, * return -EINVAL or zero on success */ static int csi2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1038,7 +1039,7 @@ static int csi2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) format.format.code = MEDIA_BUS_FMT_SGRBG10_1X10; format.format.width = 4096; format.format.height = 4096; - csi2_set_format(sd, fh ? fh->state : NULL, &format); + csi2_set_format(sd, NULL, fh ? fh->state : NULL, &format); return 0; } diff --git a/drivers/media/platform/ti/omap3isp/isppreview.c b/drivers/media/platform/ti/omap3isp/isppreview.c index 3f3b5bd9cdc7..ac5025ce4315 100644 --- a/drivers/media/platform/ti/omap3isp/isppreview.c +++ b/drivers/media/platform/ti/omap3isp/isppreview.c @@ -1925,6 +1925,7 @@ static int preview_enum_frame_size(struct v4l2_subdev *sd, * Return 0 on success or a negative error code otherwise. */ static int preview_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1968,6 +1969,7 @@ static int preview_get_selection(struct v4l2_subdev *sd, * Return 0 on success or a negative error code otherwise. */ static int preview_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -2035,6 +2037,7 @@ static int preview_get_format(struct v4l2_subdev *sd, * return -EINVAL or zero on success */ static int preview_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -2090,7 +2093,7 @@ static int preview_init_formats(struct v4l2_subdev *sd, format.format.code = MEDIA_BUS_FMT_SGRBG10_1X10; format.format.width = 4096; format.format.height = 4096; - preview_set_format(sd, fh ? fh->state : NULL, &format); + preview_set_format(sd, NULL, fh ? fh->state : NULL, &format); return 0; } diff --git a/drivers/media/platform/ti/omap3isp/ispresizer.c b/drivers/media/platform/ti/omap3isp/ispresizer.c index ad0127f5b5cb..874aa451a2d0 100644 --- a/drivers/media/platform/ti/omap3isp/ispresizer.c +++ b/drivers/media/platform/ti/omap3isp/ispresizer.c @@ -1222,6 +1222,7 @@ static void resizer_try_crop(const struct v4l2_mbus_framefmt *sink, * Return 0 on success or a negative error code otherwise. */ static int resizer_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1275,6 +1276,7 @@ static int resizer_get_selection(struct v4l2_subdev *sd, * Return 0 on success or a negative error code otherwise. */ static int resizer_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1500,6 +1502,7 @@ static int resizer_get_format(struct v4l2_subdev *sd, * return -EINVAL or zero on success */ static int resizer_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -1577,7 +1580,7 @@ static int resizer_init_formats(struct v4l2_subdev *sd, format.format.code = MEDIA_BUS_FMT_YUYV8_1X16; format.format.width = 4096; format.format.height = 4096; - resizer_set_format(sd, fh ? fh->state : NULL, &format); + resizer_set_format(sd, NULL, fh ? fh->state : NULL, &format); return 0; } diff --git a/drivers/media/platform/ti/omap3isp/ispvideo.c b/drivers/media/platform/ti/omap3isp/ispvideo.c index b946c8087c77..cd324c730318 100644 --- a/drivers/media/platform/ti/omap3isp/ispvideo.c +++ b/drivers/media/platform/ti/omap3isp/ispvideo.c @@ -839,7 +839,7 @@ isp_video_get_selection(struct file *file, void *fh, struct v4l2_selection *sel) * implemented. */ sdsel.pad = pad; - ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, &sdsel); + ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, NULL, &sdsel); if (!ret) sel->r = sdsel.r; if (ret != -ENOIOCTLCMD) @@ -890,7 +890,7 @@ isp_video_set_selection(struct file *file, void *fh, struct v4l2_selection *sel) sdsel.pad = pad; mutex_lock(&video->mutex); - ret = v4l2_subdev_call(subdev, pad, set_selection, NULL, &sdsel); + ret = v4l2_subdev_call(subdev, pad, set_selection, NULL, NULL, &sdsel); mutex_unlock(&video->mutex); if (!ret) sel->r = sdsel.r; diff --git a/drivers/media/platform/ti/vpe/vip.c b/drivers/media/platform/ti/vpe/vip.c index ccb688f4d7af..2bbe8eb31555 100644 --- a/drivers/media/platform/ti/vpe/vip.c +++ b/drivers/media/platform/ti/vpe/vip.c @@ -1748,7 +1748,7 @@ static int vip_s_fmt_vid_cap(struct file *file, void *priv, sfmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; sfmt.pad = 0; - ret = v4l2_subdev_call(port->subdev, pad, set_fmt, NULL, &sfmt); + ret = v4l2_subdev_call(port->subdev, pad, set_fmt, NULL, NULL, &sfmt); if (ret) { v4l2_dbg(1, debug, &dev->v4l2_dev, "set_fmt failed in subdev\n"); return ret; @@ -2620,7 +2620,7 @@ static int vip_init_port(struct vip_port *port) 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, + ret = v4l2_subdev_call(port->subdev, pad, set_fmt, NULL, NULL, &sd_fmt); if (ret) v4l2_dbg(1, debug, &dev->v4l2_dev, "init_port set_fmt failed in subdev: (%d)\n", diff --git a/drivers/media/platform/via/via-camera.c b/drivers/media/platform/via/via-camera.c index 1b81acba7da0..6a6e10c6f649 100644 --- a/drivers/media/platform/via/via-camera.c +++ b/drivers/media/platform/via/via-camera.c @@ -249,7 +249,7 @@ static int viacam_configure_sensor(struct via_camera *cam) v4l2_fill_mbus_format(&format.format, &cam->sensor_format, cam->mbus_code); ret = sensor_call(cam, core, init, 0); if (ret == 0) - ret = sensor_call(cam, pad, set_fmt, NULL, &format); + ret = sensor_call(cam, pad, set_fmt, NULL, NULL, &format); /* * OV7670 does weird things if flip is set *before* format... */ @@ -842,7 +842,7 @@ static int viacam_do_try_fmt(struct via_camera *cam, upix->pixelformat = f->pixelformat; viacam_fmt_pre(upix, spix); v4l2_fill_mbus_format(&format.format, spix, f->mbus_code); - ret = sensor_call(cam, pad, set_fmt, &pad_state, &format); + ret = sensor_call(cam, pad, set_fmt, NULL, &pad_state, &format); v4l2_fill_pix_format(spix, &format.format); viacam_fmt_post(upix, spix); return ret; diff --git a/drivers/media/platform/video-mux.c b/drivers/media/platform/video-mux.c index cba34893258a..b253b260b605 100644 --- a/drivers/media/platform/video-mux.c +++ b/drivers/media/platform/video-mux.c @@ -146,6 +146,7 @@ static const struct v4l2_subdev_video_ops video_mux_subdev_video_ops = { }; static int video_mux_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/media/platform/xilinx/xilinx-csi2rxss.c b/drivers/media/platform/xilinx/xilinx-csi2rxss.c index 146131b8f37e..2f6f3af4e492 100644 --- a/drivers/media/platform/xilinx/xilinx-csi2rxss.c +++ b/drivers/media/platform/xilinx/xilinx-csi2rxss.c @@ -694,6 +694,7 @@ static int xcsi2rxss_get_format(struct v4l2_subdev *sd, } static int xcsi2rxss_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/platform/xilinx/xilinx-tpg.c b/drivers/media/platform/xilinx/xilinx-tpg.c index 7deec6e37edc..bf39ddb9c296 100644 --- a/drivers/media/platform/xilinx/xilinx-tpg.c +++ b/drivers/media/platform/xilinx/xilinx-tpg.c @@ -278,6 +278,7 @@ static int xtpg_get_format(struct v4l2_subdev *subdev, } static int xtpg_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/test-drivers/vimc/vimc-debayer.c b/drivers/media/test-drivers/vimc/vimc-debayer.c index 0c2e715a8a16..cfda447a5b76 100644 --- a/drivers/media/test-drivers/vimc/vimc-debayer.c +++ b/drivers/media/test-drivers/vimc/vimc-debayer.c @@ -240,6 +240,7 @@ static void vimc_debayer_adjust_sink_fmt(struct v4l2_mbus_framefmt *fmt) } static int vimc_debayer_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/test-drivers/vimc/vimc-scaler.c b/drivers/media/test-drivers/vimc/vimc-scaler.c index e2037c67e423..d8dc65d33f62 100644 --- a/drivers/media/test-drivers/vimc/vimc-scaler.c +++ b/drivers/media/test-drivers/vimc/vimc-scaler.c @@ -140,6 +140,7 @@ static int vimc_scaler_enum_frame_size(struct v4l2_subdev *sd, } static int vimc_scaler_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { @@ -204,6 +205,7 @@ static int vimc_scaler_set_fmt(struct v4l2_subdev *sd, } static int vimc_scaler_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -239,6 +241,7 @@ static void vimc_scaler_adjust_sink_crop(struct v4l2_rect *r, } static int vimc_scaler_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/media/test-drivers/vimc/vimc-sensor.c b/drivers/media/test-drivers/vimc/vimc-sensor.c index 7cb544fdfddb..f07f4237f6ad 100644 --- a/drivers/media/test-drivers/vimc/vimc-sensor.c +++ b/drivers/media/test-drivers/vimc/vimc-sensor.c @@ -150,6 +150,7 @@ static u32 vimc_calc_vblank(u32 width, u32 height, } static int vimc_sensor_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/media/usb/cx231xx/cx231xx-417.c b/drivers/media/usb/cx231xx/cx231xx-417.c index c695a97e202b..d0955ea18195 100644 --- a/drivers/media/usb/cx231xx/cx231xx-417.c +++ b/drivers/media/usb/cx231xx/cx231xx-417.c @@ -1666,7 +1666,7 @@ static int cx231xx_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val) format.format.width = cxhdl->width / (is_mpeg1 ? 2 : 1); format.format.height = cxhdl->height; format.format.code = MEDIA_BUS_FMT_FIXED; - v4l2_subdev_call(dev->sd_cx25840, pad, set_fmt, NULL, &format); + v4l2_subdev_call(dev->sd_cx25840, pad, set_fmt, NULL, NULL, &format); return 0; } diff --git a/drivers/media/usb/cx231xx/cx231xx-video.c b/drivers/media/usb/cx231xx/cx231xx-video.c index 70aa99fead27..e8c9c74b4b8a 100644 --- a/drivers/media/usb/cx231xx/cx231xx-video.c +++ b/drivers/media/usb/cx231xx/cx231xx-video.c @@ -909,7 +909,7 @@ static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, dev->format = format_by_fourcc(f->fmt.pix.pixelformat); v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED); - call_all(dev, pad, set_fmt, NULL, &format); + call_all(dev, pad, set_fmt, NULL, NULL, &format); v4l2_fill_pix_format(&f->fmt.pix, &format.format); return rc; @@ -950,7 +950,7 @@ static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm) format.format.code = MEDIA_BUS_FMT_FIXED; format.format.width = dev->width; format.format.height = dev->height; - call_all(dev, pad, set_fmt, NULL, &format); + call_all(dev, pad, set_fmt, NULL, NULL, &format); /* do mode control overrides */ cx231xx_do_mode_ctrl_overrides(dev); diff --git a/drivers/media/usb/dvb-usb/cxusb-analog.c b/drivers/media/usb/dvb-usb/cxusb-analog.c index 3bbee1fcbc8d..8c7d0888c721 100644 --- a/drivers/media/usb/dvb-usb/cxusb-analog.c +++ b/drivers/media/usb/dvb-usb/cxusb-analog.c @@ -1031,7 +1031,8 @@ static int cxusb_medion_try_s_fmt_vid_cap(struct file *file, subfmt.format.field = field; subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M; - ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt); + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, NULL, + &subfmt); if (ret != 0) return ret; @@ -1513,7 +1514,8 @@ int cxusb_medion_analog_init(struct dvb_usb_device *dvbdev) subfmt.format.field = V4L2_FIELD_SEQ_TB; subfmt.format.colorspace = V4L2_COLORSPACE_SMPTE170M; - ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, &subfmt); + ret = v4l2_subdev_call(cxdev->cx25840, pad, set_fmt, NULL, NULL, + &subfmt); if (ret != 0) dev_warn(&dvbdev->udev->dev, "cx25840 format set failed (%d)\n", ret); diff --git a/drivers/media/usb/em28xx/em28xx-camera.c b/drivers/media/usb/em28xx/em28xx-camera.c index b5f58dc6dd0f..bc55ad66658c 100644 --- a/drivers/media/usb/em28xx/em28xx-camera.c +++ b/drivers/media/usb/em28xx/em28xx-camera.c @@ -392,7 +392,7 @@ int em28xx_init_camera(struct em28xx *dev) format.format.code = MEDIA_BUS_FMT_YUYV8_2X8; format.format.width = 640; format.format.height = 480; - v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format); + v4l2_subdev_call(subdev, pad, set_fmt, NULL, NULL, &format); /* NOTE: for UXGA=1600x1200 switch to 12MHz */ dev->board.xclk = EM28XX_XCLK_FREQUENCY_24MHZ; diff --git a/drivers/media/usb/go7007/go7007-v4l2.c b/drivers/media/usb/go7007/go7007-v4l2.c index 2087ffcb85a5..86b853e09ecb 100644 --- a/drivers/media/usb/go7007/go7007-v4l2.c +++ b/drivers/media/usb/go7007/go7007-v4l2.c @@ -252,7 +252,7 @@ static int set_capture_size(struct go7007 *go, struct v4l2_format *fmt, int try) go->encoder_h_halve = 0; go->encoder_v_halve = 0; go->encoder_subsample = 0; - call_all(&go->v4l2_dev, pad, set_fmt, NULL, &format); + call_all(&go->v4l2_dev, pad, set_fmt, NULL, NULL, &format); } else { if (width <= sensor_width / 4) { go->encoder_h_halve = 1; diff --git a/drivers/media/usb/go7007/s2250-board.c b/drivers/media/usb/go7007/s2250-board.c index d11f8e723624..a57ae661c02b 100644 --- a/drivers/media/usb/go7007/s2250-board.c +++ b/drivers/media/usb/go7007/s2250-board.c @@ -410,6 +410,7 @@ static int s2250_s_ctrl(struct v4l2_ctrl *ctrl) } static int s2250_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c index 3a857e95bc0c..c749ef7e490a 100644 --- a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c +++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c @@ -2924,7 +2924,7 @@ static void pvr2_subdev_update(struct pvr2_hdw *hdw) format.format.code = MEDIA_BUS_FMT_FIXED; pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_size(%dx%d)", format.format.width, format.format.height); - v4l2_device_call_all(&hdw->v4l2_dev, 0, pad, set_fmt, + v4l2_device_call_all(&hdw->v4l2_dev, 0, pad, set_fmt, NULL, NULL, &format); } diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 58e50d9442e7..a07d77e584c3 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -257,6 +257,7 @@ static int call_get_fmt(struct v4l2_subdev *sd, } static int call_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { @@ -270,7 +271,7 @@ static int call_set_fmt(struct v4l2_subdev *sd, return ret; if (sd->ops->pad->set_fmt) - return sd->ops->pad->set_fmt(sd, state, format); + return sd->ops->pad->set_fmt(sd, ci, state, format); return sd->ops->pad->get_fmt(sd, state, format); } @@ -331,19 +332,21 @@ static inline int check_selection(struct v4l2_subdev *sd, } static int call_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { return do_subdev_call(sd, check_selection(sd, state, sel), - pad, get_selection, state, sel); + pad, get_selection, ci, state, sel); } static int call_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { return do_subdev_call(sd, check_selection(sd, state, sel), - pad, set_selection, state, sel); + pad, set_selection, ci, state, sel); } static inline int check_frame_interval(struct v4l2_subdev *sd, @@ -552,6 +555,21 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable) v4l2_subdev_unlock_state(state); \ return ret; \ } +#define DEFINE_CI_STATE_WRAPPER(f, arg_type) \ + static int call_##f##_state(struct v4l2_subdev *sd, \ + const struct v4l2_subdev_client_info *ci, \ + struct v4l2_subdev_state *_state, \ + arg_type *arg) \ + { \ + struct v4l2_subdev_state *state = _state; \ + int ret; \ + if (!_state) \ + state = v4l2_subdev_lock_and_get_active_state(sd); \ + ret = call_##f(sd, ci, state, arg); \ + if (!_state && state) \ + v4l2_subdev_unlock_state(state); \ + return ret; \ + } #else /* CONFIG_MEDIA_CONTROLLER */ @@ -563,15 +581,24 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable) return call_##f(sd, state, arg); \ } +#define DEFINE_CI_STATE_WRAPPER(f, arg_type) \ + static int call_##f##_state(struct v4l2_subdev *sd, \ + const struct v4l2_subdev_client_info *ci, \ + struct v4l2_subdev_state *state, \ + arg_type *arg) \ + { \ + return call_##f(sd, ci, state, arg); \ + } + #endif /* CONFIG_MEDIA_CONTROLLER */ DEFINE_STATE_WRAPPER(get_fmt, struct v4l2_subdev_format); -DEFINE_STATE_WRAPPER(set_fmt, struct v4l2_subdev_format); +DEFINE_CI_STATE_WRAPPER(set_fmt, struct v4l2_subdev_format); DEFINE_STATE_WRAPPER(enum_mbus_code, struct v4l2_subdev_mbus_code_enum); DEFINE_STATE_WRAPPER(enum_frame_size, struct v4l2_subdev_frame_size_enum); DEFINE_STATE_WRAPPER(enum_frame_interval, struct v4l2_subdev_frame_interval_enum); -DEFINE_STATE_WRAPPER(get_selection, struct v4l2_subdev_selection); -DEFINE_STATE_WRAPPER(set_selection, struct v4l2_subdev_selection); +DEFINE_CI_STATE_WRAPPER(get_selection, struct v4l2_subdev_selection); +DEFINE_CI_STATE_WRAPPER(set_selection, struct v4l2_subdev_selection); static const struct v4l2_subdev_pad_ops v4l2_subdev_call_pad_wrappers = { .get_fmt = call_get_fmt_state, @@ -846,7 +873,8 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, memset(format->reserved, 0, sizeof(format->reserved)); memset(format->format.reserved, 0, sizeof(format->format.reserved)); - return v4l2_subdev_call(sd, pad, set_fmt, state, format); + return v4l2_subdev_call(sd, pad, set_fmt, &subdev_fh->ci, state, + format); } case VIDIOC_SUBDEV_G_CROP: { @@ -863,8 +891,8 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, sel.stream = crop->stream; sel.target = V4L2_SEL_TGT_CROP; - rval = v4l2_subdev_call( - sd, pad, get_selection, state, &sel); + rval = v4l2_subdev_call(sd, pad, get_selection, &subdev_fh->ci, + state, &sel); crop->rect = sel.r; @@ -889,8 +917,8 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, sel.target = V4L2_SEL_TGT_CROP; sel.r = crop->rect; - rval = v4l2_subdev_call( - sd, pad, set_selection, state, &sel); + rval = v4l2_subdev_call(sd, pad, set_selection, &subdev_fh->ci, + state, &sel); crop->rect = sel.r; @@ -960,8 +988,8 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, sel->stream = 0; memset(sel->reserved, 0, sizeof(sel->reserved)); - return v4l2_subdev_call( - sd, pad, get_selection, state, sel); + return v4l2_subdev_call(sd, pad, get_selection, &subdev_fh->ci, + state, sel); } case VIDIOC_SUBDEV_S_SELECTION: { @@ -974,8 +1002,8 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, sel->stream = 0; memset(sel->reserved, 0, sizeof(sel->reserved)); - return v4l2_subdev_call( - sd, pad, set_selection, state, sel); + return v4l2_subdev_call(sd, pad, set_selection, &subdev_fh->ci, + state, sel); } case VIDIOC_G_EDID: { diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c b/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c index f9cc5f45fe00..38f69582c174 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c @@ -520,6 +520,7 @@ static int gc2235_startup(struct v4l2_subdev *sd) } static int gc2235_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/staging/media/atomisp/i2c/atomisp-ov2722.c b/drivers/staging/media/atomisp/i2c/atomisp-ov2722.c index 2c41c496daa6..e3137fbc5802 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-ov2722.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-ov2722.c @@ -623,6 +623,7 @@ static int ov2722_startup(struct v4l2_subdev *sd) } static int ov2722_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { diff --git a/drivers/staging/media/atomisp/pci/atomisp_cmd.c b/drivers/staging/media/atomisp/pci/atomisp_cmd.c index 6cd500d9fd26..52c71cd86dec 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_cmd.c +++ b/drivers/staging/media/atomisp/pci/atomisp_cmd.c @@ -3745,14 +3745,16 @@ static int atomisp_set_sensor_crop_and_fmt(struct atomisp_device *isp, sel.r.left = ((input->native_rect.width - sel.r.width) / 2) & ~1; sel.r.top = ((input->native_rect.height - sel.r.height) / 2) & ~1; - ret = v4l2_subdev_call(input->sensor, pad, set_selection, sd_state, &sel); + ret = v4l2_subdev_call(input->sensor, pad, set_selection, NULL, + sd_state, &sel); if (ret) dev_err(isp->dev, "Error setting crop to (%d,%d)/%ux%u: %d\n", sel.r.left, sel.r.top, sel.r.width, sel.r.height, ret); set_fmt: if (ret == 0) { - ret = v4l2_subdev_call(input->sensor, pad, set_fmt, sd_state, &format); + ret = v4l2_subdev_call(input->sensor, pad, set_fmt, NULL, + sd_state, &format); dev_dbg(isp->dev, "Set sensor format ret: %d size %dx%d\n", ret, format.format.width, format.format.height); } @@ -3765,13 +3767,16 @@ static int atomisp_set_sensor_crop_and_fmt(struct atomisp_device *isp, sd_state = v4l2_subdev_lock_and_get_active_state(input->sensor_isp); format.pad = SENSOR_ISP_PAD_SINK; - ret = v4l2_subdev_call(input->sensor_isp, pad, set_fmt, sd_state, &format); + ret = v4l2_subdev_call(input->sensor_isp, pad, set_fmt, NULL, + sd_state, &format); dev_dbg(isp->dev, "Set sensor ISP sink format ret: %d size %dx%d\n", ret, format.format.width, format.format.height); if (ret == 0) { format.pad = SENSOR_ISP_PAD_SOURCE; - ret = v4l2_subdev_call(input->sensor_isp, pad, set_fmt, sd_state, &format); + ret = v4l2_subdev_call(input->sensor_isp, pad, + set_fmt, NULL, sd_state, + &format); dev_dbg(isp->dev, "Set sensor ISP source format ret: %d size %dx%d\n", ret, format.format.width, format.format.height); } @@ -3783,7 +3788,8 @@ static int atomisp_set_sensor_crop_and_fmt(struct atomisp_device *isp, /* Propagate new fmt to CSI port */ if (ret == 0 && which == V4L2_SUBDEV_FORMAT_ACTIVE) { format.pad = CSI2_PAD_SINK; - ret = v4l2_subdev_call(input->csi_port, pad, set_fmt, NULL, &format); + ret = v4l2_subdev_call(input->csi_port, pad, set_fmt, NULL, + NULL, &format); if (ret) return ret; } diff --git a/drivers/staging/media/atomisp/pci/atomisp_csi2.c b/drivers/staging/media/atomisp/pci/atomisp_csi2.c index 95b9113d75e9..71df4ef629c2 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_csi2.c +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2.c @@ -123,6 +123,7 @@ int atomisp_csi2_set_ffmt(struct v4l2_subdev *sd, * return -EINVAL or zero on success */ static int csi2_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/staging/media/atomisp/pci/atomisp_subdev.c b/drivers/staging/media/atomisp/pci/atomisp_subdev.c index 9de9cd884d99..bdd2d9b18da2 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_subdev.c +++ b/drivers/staging/media/atomisp/pci/atomisp_subdev.c @@ -259,6 +259,7 @@ static void isp_get_fmt_rect(struct v4l2_subdev *sd, } static int isp_subdev_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -452,6 +453,7 @@ int atomisp_subdev_set_selection(struct v4l2_subdev *sd, } static int isp_subdev_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -555,6 +557,7 @@ static int isp_subdev_get_format(struct v4l2_subdev *sd, * to the format type. */ static int isp_subdev_set_format(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c index 812230397409..476fe143a591 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c +++ b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c @@ -913,7 +913,7 @@ static void atomisp_init_sensor(struct atomisp_input_subdev *input) sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; sel.target = V4L2_SEL_TGT_NATIVE_SIZE; - err = v4l2_subdev_call(input->sensor, pad, get_selection, + err = v4l2_subdev_call(input->sensor, pad, get_selection, NULL, act_sd_state, &sel); if (err) goto unlock_act_sd_state; @@ -922,7 +922,7 @@ static void atomisp_init_sensor(struct atomisp_input_subdev *input) sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; sel.target = V4L2_SEL_TGT_CROP_DEFAULT; - err = v4l2_subdev_call(input->sensor, pad, get_selection, + err = v4l2_subdev_call(input->sensor, pad, get_selection, NULL, act_sd_state, &sel); if (err) goto unlock_act_sd_state; @@ -968,7 +968,7 @@ static void atomisp_init_sensor(struct atomisp_input_subdev *input) if (!input->sensor->state_lock) v4l2_subdev_lock_state(input->try_sd_state); - err = v4l2_subdev_call(input->sensor, pad, set_selection, + err = v4l2_subdev_call(input->sensor, pad, set_selection, NULL, input->try_sd_state, &sel); if (!input->sensor->state_lock) @@ -980,7 +980,7 @@ static void atomisp_init_sensor(struct atomisp_input_subdev *input) sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; sel.target = V4L2_SEL_TGT_CROP; sel.r = input->native_rect; - err = v4l2_subdev_call(input->sensor, pad, set_selection, + err = v4l2_subdev_call(input->sensor, pad, set_selection, NULL, act_sd_state, &sel); if (err) goto unlock_act_sd_state; diff --git a/drivers/staging/media/imx/imx-ic-prp.c b/drivers/staging/media/imx/imx-ic-prp.c index 2b80d54006b3..46ac33821f89 100644 --- a/drivers/staging/media/imx/imx-ic-prp.c +++ b/drivers/staging/media/imx/imx-ic-prp.c @@ -151,6 +151,7 @@ static int prp_get_fmt(struct v4l2_subdev *sd, } static int prp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/staging/media/imx/imx-ic-prpencvf.c b/drivers/staging/media/imx/imx-ic-prpencvf.c index 77360bfe081a..5ee59b418a5e 100644 --- a/drivers/staging/media/imx/imx-ic-prpencvf.c +++ b/drivers/staging/media/imx/imx-ic-prpencvf.c @@ -919,6 +919,7 @@ static void prp_try_fmt(struct prp_priv *priv, } static int prp_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/staging/media/imx/imx-media-csi.c b/drivers/staging/media/imx/imx-media-csi.c index ef22a083f8eb..43c637b333f2 100644 --- a/drivers/staging/media/imx/imx-media-csi.c +++ b/drivers/staging/media/imx/imx-media-csi.c @@ -1525,6 +1525,7 @@ static void csi_try_fmt(struct csi_priv *priv, } static int csi_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { @@ -1593,6 +1594,7 @@ static int csi_set_fmt(struct v4l2_subdev *sd, } static int csi_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -1657,6 +1659,7 @@ static int csi_set_scale(u32 *compose, u32 crop, u32 flags) } static int csi_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/staging/media/imx/imx-media-vdic.c b/drivers/staging/media/imx/imx-media-vdic.c index 58f1112e28e5..8dcbb6fe7649 100644 --- a/drivers/staging/media/imx/imx-media-vdic.c +++ b/drivers/staging/media/imx/imx-media-vdic.c @@ -566,6 +566,7 @@ static void vdic_try_fmt(struct vdic_priv *priv, } static int vdic_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/staging/media/imx/imx6-mipi-csi2.c b/drivers/staging/media/imx/imx6-mipi-csi2.c index 211f67fb92b5..f6cbf0310e89 100644 --- a/drivers/staging/media/imx/imx6-mipi-csi2.c +++ b/drivers/staging/media/imx/imx6-mipi-csi2.c @@ -525,6 +525,7 @@ static int csi2_get_fmt(struct v4l2_subdev *sd, } static int csi2_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c index 2f6041d342f4..61fdefe78c3d 100644 --- a/drivers/staging/media/ipu3/ipu3-v4l2.c +++ b/drivers/staging/media/ipu3/ipu3-v4l2.c @@ -145,6 +145,7 @@ static int imgu_subdev_get_fmt(struct v4l2_subdev *sd, } static int imgu_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { @@ -212,6 +213,7 @@ imgu_subdev_get_compose(struct imgu_v4l2_subdev *sd, } static int imgu_subdev_get_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { @@ -236,6 +238,7 @@ static int imgu_subdev_get_selection(struct v4l2_subdev *sd, } static int imgu_subdev_set_selection(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/staging/media/ipu7/ipu7-isys-csi2.c b/drivers/staging/media/ipu7/ipu7-isys-csi2.c index f34eabfe8a98..fcc7efcf69c3 100644 --- a/drivers/staging/media/ipu7/ipu7-isys-csi2.c +++ b/drivers/staging/media/ipu7/ipu7-isys-csi2.c @@ -190,6 +190,7 @@ static int ipu7_isys_csi2_enable_stream(struct ipu7_isys_csi2 *csi2) } static int ipu7_isys_csi2_set_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { @@ -241,6 +242,7 @@ static int ipu7_isys_csi2_set_sel(struct v4l2_subdev *sd, } static int ipu7_isys_csi2_get_sel(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) { diff --git a/drivers/staging/media/ipu7/ipu7-isys-subdev.c b/drivers/staging/media/ipu7/ipu7-isys-subdev.c index 67a776033d5b..2cb0521b4240 100644 --- a/drivers/staging/media/ipu7/ipu7-isys-subdev.c +++ b/drivers/staging/media/ipu7/ipu7-isys-subdev.c @@ -99,6 +99,7 @@ u32 ipu7_isys_convert_bayer_order(u32 code, int x, int y) } int ipu7_isys_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/staging/media/ipu7/ipu7-isys-subdev.h b/drivers/staging/media/ipu7/ipu7-isys-subdev.h index faa50031cf24..c3585d1c4128 100644 --- a/drivers/staging/media/ipu7/ipu7-isys-subdev.h +++ b/drivers/staging/media/ipu7/ipu7-isys-subdev.h @@ -31,6 +31,7 @@ bool ipu7_isys_is_bayer_format(u32 code); u32 ipu7_isys_convert_bayer_order(u32 code, int x, int y); int ipu7_isys_subdev_set_fmt(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format); int ipu7_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c index 46a334b602f1..3f376af6c228 100644 --- a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c @@ -313,6 +313,7 @@ static int sun6i_isp_proc_get_fmt(struct v4l2_subdev *subdev, } static int sun6i_isp_proc_set_fmt(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format) { diff --git a/drivers/staging/media/tegra-video/csi.c b/drivers/staging/media/tegra-video/csi.c index 41d57dac61a7..5a5fca378ae6 100644 --- a/drivers/staging/media/tegra-video/csi.c +++ b/drivers/staging/media/tegra-video/csi.c @@ -170,6 +170,7 @@ static int csi_enum_frameintervals(struct v4l2_subdev *subdev, } static int csi_set_format(struct v4l2_subdev *subdev, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c index be9179a75fee..6a5f66da348b 100644 --- a/drivers/staging/media/tegra-video/vi.c +++ b/drivers/staging/media/tegra-video/vi.c @@ -477,7 +477,7 @@ static int __tegra_channel_try_format(struct tegra_vi_channel *chan, ret = v4l2_subdev_call(subdev, pad, enum_frame_size, sd_state, &fse); if (ret) { if (!v4l2_subdev_has_op(subdev, pad, get_selection) || - v4l2_subdev_call(subdev, pad, get_selection, NULL, &sdsel)) { + v4l2_subdev_call(subdev, pad, get_selection, NULL, NULL, &sdsel)) { try_crop->width = 0; try_crop->height = 0; } else { @@ -489,7 +489,7 @@ static int __tegra_channel_try_format(struct tegra_vi_channel *chan, try_crop->height = fse.max_height; } - ret = v4l2_subdev_call(subdev, pad, set_fmt, sd_state, &fmt); + ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, sd_state, &fmt); if (ret < 0) goto out_free; @@ -543,7 +543,7 @@ static int tegra_channel_set_format(struct file *file, void *fh, fmt.pad = 0; v4l2_fill_mbus_format(&fmt.format, pix, fmtinfo->code); subdev = tegra_channel_get_remote_source_subdev(chan); - ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &fmt); + ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, NULL, &fmt); if (ret < 0) return ret; @@ -626,7 +626,7 @@ static int tegra_channel_g_selection(struct file *file, void *priv, * Try the get selection operation and fallback to get format if not * implemented. */ - ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, &sdsel); + ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, NULL, &sdsel); if (!ret) sel->r = sdsel.r; if (ret != -ENOIOCTLCMD) @@ -667,7 +667,7 @@ static int tegra_channel_s_selection(struct file *file, void *fh, if (vb2_is_busy(&chan->queue)) return -EBUSY; - ret = v4l2_subdev_call(subdev, pad, set_selection, NULL, &sdsel); + ret = v4l2_subdev_call(subdev, pad, set_selection, NULL, NULL, &sdsel); if (!ret) { sel->r = sdsel.r; /* diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index b8874eeec2e5..5796e52678b9 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -755,11 +755,14 @@ struct v4l2_subdev_client_info { * * @get_fmt: callback for VIDIOC_SUBDEV_G_FMT() ioctl handler code. * - * @set_fmt: callback for VIDIOC_SUBDEV_S_FMT() ioctl handler code. + * @set_fmt: callback for VIDIOC_SUBDEV_S_FMT() ioctl handler code. The ci + * pointer may be NULL for in-kernel calls. * * @get_selection: callback for VIDIOC_SUBDEV_G_SELECTION() ioctl handler code. + * The ci pointer may be NULL for in-kernel calls. * * @set_selection: callback for VIDIOC_SUBDEV_S_SELECTION() ioctl handler code. + * The ci pointer may be NULL for in-kernel calls. * * @get_frame_interval: callback for VIDIOC_SUBDEV_G_FRAME_INTERVAL() * ioctl handler code. @@ -854,12 +857,15 @@ struct v4l2_subdev_pad_ops { struct v4l2_subdev_state *state, struct v4l2_subdev_format *format); int (*set_fmt)(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_format *format); int (*get_selection)(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel); int (*set_selection)(struct v4l2_subdev *sd, + const struct v4l2_subdev_client_info *ci, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel); int (*get_frame_interval)(struct v4l2_subdev *sd, From f9be93338c2fbaffdbda4f89045e879932e827ca Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 10:00:10 +0000 Subject: [PATCH 250/596] media: ivsc: Fix csi_dev use-after-free in mei_ace_setup_dev_link() device_link_add() only takes a reference to the supplier when the link is created successfully. When it fails it returns NULL without taking one, so the put_device(csi_dev) before the error check can drop the last reference to csi_dev and dev_name(csi_dev) then dereferences a freed device. Move the put_device() below the error check and release the reference through the existing err_put label on the failure path so that csi_dev is no longer touched after it has been put. Fixes: 765abb76f51f ("media: ivsc: Release csi_dev reference early in mei_ace_setup_dev_link()") Signed-off-by: Wentao Liang Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ivsc/mei_ace.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/pci/intel/ivsc/mei_ace.c b/drivers/media/pci/intel/ivsc/mei_ace.c index b306a320b70f..bb57656fc85a 100644 --- a/drivers/media/pci/intel/ivsc/mei_ace.c +++ b/drivers/media/pci/intel/ivsc/mei_ace.c @@ -414,13 +414,13 @@ static int mei_ace_setup_dev_link(struct mei_ace *ace) /* setup link between mei_ace and mei_csi */ ace->csi_link = device_link_add(csi_dev, dev, DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE | DL_FLAG_STATELESS); - put_device(csi_dev); if (!ace->csi_link) { ret = -EINVAL; dev_err(dev, "failed to link to %s\n", dev_name(csi_dev)); - goto err; + goto err_put; } + put_device(csi_dev); ace->csi_dev = csi_dev; return 0; From e2f0c9082d25a000f9e9bd987fd85ec15f2ab608 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 16 Sep 2026 10:35:20 +0200 Subject: [PATCH 251/596] media: i2c: it6625: select CONFIG_HDMI Without HDMI support, the driver fails to link: arm-linux-gnueabi-ld: drivers/media/i2c/it6625.o: in function `it6625_show_avi_infoframe': it6625.c:(.text.it6625_show_avi_infoframe+0x94): undefined reference to `hdmi_infoframe_unpack' Select the option like the other similar drivers do. Fixes: 142e5f00bd57 ("media: i2c: add driver for ITE IT6625/IT6626") Signed-off-by: Arnd Bergmann Signed-off-by: Sakari Ailus --- drivers/media/i2c/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 3370b66ab4ee..4d9946479160 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1327,6 +1327,7 @@ config VIDEO_IT6625 depends on VIDEO_DEV && I2C depends on OF select CEC_CORE + select HDMI select MEDIA_CONTROLLER select REGMAP_I2C select V4L2_FWNODE From b238116ccd4bb3dea52e306b7fca3d3f17995a98 Mon Sep 17 00:00:00 2001 From: Sergey Lebedev Date: Thu, 17 Sep 2026 14:45:35 +0000 Subject: [PATCH 252/596] media: ipu-bridge: Add upside-down quirk for Surface Pro 11 The Microsoft Surface Pro for Business 11th Edition with Intel mounts its OV13858 rear sensor rotated 180 degrees, but reports a rotation of 0 in its SSDB and has no matching _PLD, so without a DMI quirk the image is presented upside down. Add a DMI quirk entry for this model so that a rotation of 180 degrees is reported for its OVTID858 sensor. Tested on the affected machine: camera_sensor_rotation reads 180 and libcamera reports Rotation = 180. Confirmed independently on a second machine of the same model, where GNOME Snapshot and Firefox present the image the right way up once the quirk is applied; qcam, which talks to libcamera directly, does not. Signed-off-by: Sergey Lebedev Tested-by: German Pablo Lindo Signed-off-by: Sakari Ailus --- drivers/media/pci/intel/ipu-bridge.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index e39697335c92..77257311d1d8 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -217,6 +217,14 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { }, .driver_data = "SONY471A", }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, + "Surface Pro for Business 11th Edition with Intel"), + }, + .driver_data = "OVTID858", + }, { /* Samsung Galaxy Book5 Pro 360 */ .matches = { From 3b696f0d04fa42e58d2eacffd1365a56ac862db4 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 25 Jun 2026 10:17:21 -0400 Subject: [PATCH 253/596] device property: Introduce fwnode_graph_for_each_endpoint_scoped() Similar to recently propose for_each_child_of_node_scoped() this new version of the loop macro instantiates a new local struct fwnode_handle * that uses the __free(fwnode_handle) auto cleanup handling so that if a reference to a node is held on early exit from the loop the reference will be released. If the loop runs to completion, the child pointer will be NULL and no action will be taken. The reason this is useful is that it removes the need for fwnode_handle_put() on early loop exits. If there is a need to retain the reference, then return_ptr(child) or no_free_ptr(child) may be used to safely disable the auto cleanup. Reviewed-by: Andy Shevchenko Reviewed-by: Guoniu Zhou Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Acked-by: Danilo Krummrich Signed-off-by: Sakari Ailus --- include/linux/property.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/property.h b/include/linux/property.h index 907c790a3f01..9143fe4f5c05 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -547,6 +547,11 @@ unsigned int fwnode_graph_get_endpoint_count(const struct fwnode_handle *fwnode, for (child = fwnode_graph_get_next_endpoint(fwnode, NULL); child; \ child = fwnode_graph_get_next_endpoint(fwnode, child)) +#define fwnode_graph_for_each_endpoint_scoped(fwnode, child) \ + for (struct fwnode_handle *child __free(fwnode_handle) = \ + fwnode_graph_get_next_endpoint(fwnode, NULL); \ + child; child = fwnode_graph_get_next_endpoint(fwnode, child)) + int fwnode_graph_parse_endpoint(const struct fwnode_handle *fwnode, struct fwnode_endpoint *endpoint); From c0244e809202e9adc19eb61a271401dff8297702 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 25 Jun 2026 10:17:22 -0400 Subject: [PATCH 254/596] media: mc: use fwnode_graph_for_each_endpoint_scoped() to simpilfy code Use cleanup helper fwnode_graph_for_each_endpoint_scoped() to simpilfy code. Reviewed-by: Guoniu Zhou Reviewed-by: Andy Shevchenko Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Signed-off-by: Sakari Ailus --- drivers/media/v4l2-core/v4l2-mc.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-mc.c b/drivers/media/v4l2-core/v4l2-mc.c index 937d358697e1..5d7fcd67dc42 100644 --- a/drivers/media/v4l2-core/v4l2-mc.c +++ b/drivers/media/v4l2-core/v4l2-mc.c @@ -324,12 +324,10 @@ EXPORT_SYMBOL_GPL(v4l_vb2q_enable_media_source); int v4l2_create_fwnode_links_to_pad(struct v4l2_subdev *src_sd, struct media_pad *sink, u32 flags) { - struct fwnode_handle *endpoint; - if (!(sink->flags & MEDIA_PAD_FL_SINK)) return -EINVAL; - fwnode_graph_for_each_endpoint(src_sd->fwnode, endpoint) { + fwnode_graph_for_each_endpoint_scoped(src_sd->fwnode, endpoint) { struct fwnode_handle *remote_ep; int src_idx, sink_idx, ret; struct media_pad *src; @@ -397,7 +395,6 @@ int v4l2_create_fwnode_links_to_pad(struct v4l2_subdev *src_sd, src_sd->entity.name, src_idx, sink->entity->name, sink_idx, ret); - fwnode_handle_put(endpoint); return ret; } } From a17399f49f0b62f5d6ea614ab489e75c9cf5d10e Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 25 Jun 2026 10:17:23 -0400 Subject: [PATCH 255/596] media: rkisp1: use fwnode_graph_for_each_endpoint_scoped() to simplify code Use fwnode_graph_for_each_endpoint_scoped() to simplify code. No functional changes. Reviewed-by: Andy Shevchenko Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Signed-off-by: Sakari Ailus --- drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c index 1791c02a40ae..f90e01301943 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c @@ -187,7 +187,6 @@ static int rkisp1_subdev_notifier_register(struct rkisp1_device *rkisp1) { struct v4l2_async_notifier *ntf = &rkisp1->notifier; struct fwnode_handle *fwnode = dev_fwnode(rkisp1->dev); - struct fwnode_handle *ep; unsigned int index = 0; int ret = 0; @@ -195,7 +194,7 @@ static int rkisp1_subdev_notifier_register(struct rkisp1_device *rkisp1) ntf->ops = &rkisp1_subdev_notifier_ops; - fwnode_graph_for_each_endpoint(fwnode, ep) { + fwnode_graph_for_each_endpoint_scoped(fwnode, ep) { struct fwnode_handle *port; struct v4l2_fwnode_endpoint vep = { }; struct rkisp1_sensor_async *rk_asd; @@ -286,7 +285,6 @@ static int rkisp1_subdev_notifier_register(struct rkisp1_device *rkisp1) } if (ret) { - fwnode_handle_put(ep); v4l2_async_nf_cleanup(ntf); return ret; } From aedd77ea8168f00687c1248cdf115071e869cf93 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 25 Jun 2026 10:17:24 -0400 Subject: [PATCH 256/596] media: qcom: camss: use fwnode_graph_for_each_endpoint_scoped() to simplify code Use fwnode_graph_for_each_endpoint_scoped() to simplify code. No functional changes. Reviewed-by: Guoniu Zhou Reviewed-by: Loic Poulain Reviewed-by: Andy Shevchenko Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Acked-by: Bryan O'Donoghue Signed-off-by: Sakari Ailus --- drivers/media/platform/qcom/camss/camss.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 2123f6388e3d..23f3cc30a15a 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4793,30 +4793,23 @@ static int camss_parse_endpoint_node(struct device *dev, static int camss_parse_ports(struct camss *camss) { struct device *dev = camss->dev; - struct fwnode_handle *fwnode = dev_fwnode(dev), *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); int ret; - fwnode_graph_for_each_endpoint(fwnode, ep) { + fwnode_graph_for_each_endpoint_scoped(fwnode, ep) { struct camss_async_subdev *csd; csd = v4l2_async_nf_add_fwnode_remote(&camss->notifier, ep, typeof(*csd)); - if (IS_ERR(csd)) { - ret = PTR_ERR(csd); - goto err_cleanup; - } + if (IS_ERR(csd)) + return PTR_ERR(csd); ret = camss_parse_endpoint_node(dev, ep, csd); if (ret < 0) - goto err_cleanup; + return ret; } return 0; - -err_cleanup: - fwnode_handle_put(ep); - - return ret; } /* From 07643be7b1eff94f7a49e142c1e92a1029d3db21 Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Mon, 27 Jul 2026 17:49:27 +0700 Subject: [PATCH 257/596] staging: media: imx: csc-scaler: fix ipu_image_convert_run leak on queue error device_run() allocates an ipu_image_convert_run object and, on the success path, hands it to the IPU image conversion core via ipu_image_convert_queue(); the run object is then freed asynchronously by the completion callback ipu_ic_pp_complete(). However, if ipu_image_convert_queue() fails, the core never takes ownership of the run object and the completion callback is never invoked. The "err" label finishes the mem2mem job and reports the buffers as errored, but never frees the run object that was just allocated, leaking it on every queue failure. Free the run object in the error path. There is no risk of a double free: the completion callback only runs after the object was successfully queued, which is mutually exclusive with reaching the error path. It is also safe for the earlier "goto err" on allocation failure, since run is NULL there and kfree(NULL) is a no-op. Fixes: a8ef0488cc59 ("media: imx: add csc/scaler mem2mem device") Cc: stable@kernel.org Signed-off-by: Cong Nguyen Signed-off-by: Frank Li Signed-off-by: Hans Verkuil --- drivers/staging/media/imx/imx-media-csc-scaler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/staging/media/imx/imx-media-csc-scaler.c b/drivers/staging/media/imx/imx-media-csc-scaler.c index 00fcdd4d0487..359d973d297f 100644 --- a/drivers/staging/media/imx/imx-media-csc-scaler.c +++ b/drivers/staging/media/imx/imx-media-csc-scaler.c @@ -147,6 +147,7 @@ static void device_run(void *_ctx) v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); v4l2_m2m_job_finish(priv->m2m_dev, ctx->fh.m2m_ctx); + kfree(run); } /* From 2f35a044d0905bab74060db1a1ac3cda81733c5d Mon Sep 17 00:00:00 2001 From: Rajveer Chaudhari Date: Wed, 11 Feb 2026 19:52:08 +0530 Subject: [PATCH 258/596] staging: media: imx: remove unused header includes Remove unused header includes from imx-ic-prp.c imx-ic-prpencvf.c imx-media-capture.c imx-media-csc-scaler.c imx-media-dev.c to reduce unnecessary dependencies and improve compilation time. Signed-off-by: Rajveer Chaudhari Reviewed-by: Frank Li Signed-off-by: Frank Li Signed-off-by: Hans Verkuil --- drivers/staging/media/imx/imx-ic-prp.c | 1 - drivers/staging/media/imx/imx-ic-prpencvf.c | 3 +-- drivers/staging/media/imx/imx-media-capture.c | 1 - drivers/staging/media/imx/imx-media-csc-scaler.c | 2 -- drivers/staging/media/imx/imx-media-dev.c | 1 - 5 files changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/staging/media/imx/imx-ic-prp.c b/drivers/staging/media/imx/imx-ic-prp.c index 46ac33821f89..b2c648149964 100644 --- a/drivers/staging/media/imx/imx-ic-prp.c +++ b/drivers/staging/media/imx/imx-ic-prp.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/staging/media/imx/imx-ic-prpencvf.c b/drivers/staging/media/imx/imx-ic-prpencvf.c index 5ee59b418a5e..3430ddeac356 100644 --- a/drivers/staging/media/imx/imx-ic-prpencvf.c +++ b/drivers/staging/media/imx/imx-ic-prpencvf.c @@ -9,9 +9,8 @@ * Copyright (c) 2012-2017 Mentor Graphics Inc. */ #include -#include #include -#include +#include #include #include #include diff --git a/drivers/staging/media/imx/imx-media-capture.c b/drivers/staging/media/imx/imx-media-capture.c index bfd71d25facc..9216a221b72f 100644 --- a/drivers/staging/media/imx/imx-media-capture.c +++ b/drivers/staging/media/imx/imx-media-capture.c @@ -5,7 +5,6 @@ * Copyright (c) 2012-2016 Mentor Graphics Inc. */ #include -#include #include #include #include diff --git a/drivers/staging/media/imx/imx-media-csc-scaler.c b/drivers/staging/media/imx/imx-media-csc-scaler.c index 359d973d297f..6f4c0b09bb36 100644 --- a/drivers/staging/media/imx/imx-media-csc-scaler.c +++ b/drivers/staging/media/imx/imx-media-csc-scaler.c @@ -7,8 +7,6 @@ */ #include #include -#include -#include #include #include