From 16e17fc16c4e84e97364395dd163d7a515e530a3 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 21:34:17 -0500 Subject: [PATCH 1/4] Prepare the original context for the carried ISP metadata bounds work Preserve the complete original omacom commit separately from the 7.3 adaptation that retains Aurora frame gain and exposure support. Signed-off-by: Satya Benson --- drivers/media/platform/apple/isp/isp-drv.c | 169 ++----- drivers/media/platform/apple/isp/isp-drv.h | 101 +--- .../media/platform/apple/isp/isp-v4l2-test.c | 293 ----------- drivers/media/platform/apple/isp/isp-v4l2.c | 463 ++++++------------ 4 files changed, 207 insertions(+), 819 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 93a21e901ff6a..2cbdcd2fb00bd 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -144,44 +144,17 @@ static int apple_isp_map_fw_mmio(struct apple_isp *isp) return 0; } -/* The IOVA range of a memory-region, from its last iommu-addresses entry. */ -static int isp_of_region_iova(struct device *dev, const char *name, - phys_addr_t *base, size_t *size) -{ - const __be32 *maps, *end; - int idx, len; - - idx = of_property_match_string(dev->of_node, "memory-region-names", - name); - if (idx < 0) - return -ENOENT; - - struct device_node *np __free(device_node) = - of_parse_phandle(dev->of_node, "memory-region", idx); - if (!np) - return -ENODEV; - - maps = of_get_property(np, "iommu-addresses", &len); - if (!maps || !len) - return -ENODEV; - - end = maps + len / sizeof(__be32); - - while (maps < end) { - maps++; - maps = of_translate_dma_region(dev->of_node, maps, base, size); - } - - return 0; -} - static int apple_isp_init_iommu(struct apple_isp *isp) { struct device *dev = isp->dev; - phys_addr_t heap_base, data_base; - size_t heap_size, data_size; + phys_addr_t heap_base; + size_t heap_size; u64 vm_size; int err; + int idx; + int size; + struct device_node *mem_node; + const __be32 *maps, *end; isp->domain = iommu_get_domain_for_dev(isp->dev); if (!isp->domain) @@ -189,38 +162,30 @@ static int apple_isp_init_iommu(struct apple_isp *isp) isp->shift = __ffs(isp->domain->pgsize_bitmap); isp->fw_iova_mask = isp->hw->fw_iova_mask ?: U64_MAX; - err = isp_of_region_iova(dev, "heap", &heap_base, &heap_size); - if (err) { + idx = of_property_match_string(dev->of_node, "memory-region-names", + "heap"); + mem_node = of_parse_phandle(dev->of_node, "memory-region", idx); + if (!mem_node) { + dev_err(dev, "No memory-region found for heap\n"); + return -ENODEV; + } + + maps = of_get_property(mem_node, "iommu-addresses", &size); + if (!maps || !size) { dev_err(dev, "No valid iommu-addresses found for heap\n"); return -ENODEV; } - isp->fw.heap_base = heap_base; - isp->fw.heap_top = heap_base + heap_size; + end = maps + size / sizeof(__be32); - /* - * Where the bootloader loads the firmware's data, it backs the - * firmware's main memory only up to the end of it. The rest, up to - * the heap top, is the host's to back. - */ - err = -ENOENT; - if (isp->hw->gen == ISP_GEN_T6040) - err = isp_of_region_iova(dev, "data", &data_base, &data_size); - if (!err) { - isp->fw.data_top = data_base + data_size; - if (isp->fw.data_top <= isp->fw.heap_base || - isp->fw.data_top > isp->fw.heap_top || - !IS_ALIGNED(isp->fw.data_top, 1UL << isp->shift)) { - dev_err(dev, "data 0x%llx outside the heap 0x%llx-0x%llx\n", - isp->fw.data_top, isp->fw.heap_base, - isp->fw.heap_top); - return -EINVAL; - } - } else if (err != -ENOENT) { - dev_err(dev, "No valid iommu-addresses found for data\n"); - return -ENODEV; + while (maps < end) { + maps++; + maps = of_translate_dma_region(dev->of_node, maps, &heap_base, + &heap_size); } + isp->fw.heap_top = heap_base + heap_size; + err = of_property_read_u64(dev->of_node, "apple,dart-vm-size", &vm_size); if (err) { @@ -230,11 +195,11 @@ static int apple_isp_init_iommu(struct apple_isp *isp) // FIXME: refactor this, maybe use regular iova stuff? isp->iova_size = vm_size - (heap_base & 0xffffffff); - mt_init_flags(&isp->iovas, MT_FLAGS_ALLOC_RANGE); + drm_mm_init(&isp->iovad, isp->fw.heap_top, isp->iova_size); err = apple_isp_map_fw_mmio(isp); if (err) { - mtree_destroy(&isp->iovas); + drm_mm_takedown(&isp->iovad); return err; } @@ -244,8 +209,7 @@ static int apple_isp_init_iommu(struct apple_isp *isp) static void apple_isp_free_iommu(struct apple_isp *isp) { apple_isp_unmap_fw_mmio(isp, isp->hw->num_fw_mmio); - WARN_ON(!mtree_empty(&isp->iovas)); - mtree_destroy(&isp->iovas); + drm_mm_takedown(&isp->iovad); } static int isp_of_read_coord(struct device *dev, struct device_node *np, @@ -457,9 +421,18 @@ static int apple_isp_probe(struct platform_device *pdev) if (err) isp->temporal_filter = 0; - /* the sensor connection layout, for firmware that takes it at boot */ - of_property_read_u32(dev->of_node, "apple,cam-connections-scheme", - &isp->cam_connections_scheme); + /* + * M3 MacBook Air firmware 14.7 stalls capture with temporal filter 1. + * The J615 has the J613's sensor and ISP configuration; only the J613 + * has been tested. + */ + if ((of_machine_is_compatible("apple,j613") || + of_machine_is_compatible("apple,j615")) && + isp->fw_compat == ISP_FIRMWARE_V_14_7 && + isp->temporal_filter == 1) { + dev_info(dev, "using temporal filter 0 on M3 Air firmware 14.7\n"); + isp->temporal_filter = 0; + } err = apple_isp_init_presets(isp); if (err) { @@ -530,6 +503,7 @@ static int apple_isp_probe(struct platform_device *pdev) goto detach_genpd; } + mutex_init(&isp->iovad_lock); mutex_init(&isp->video_lock); spin_lock_init(&isp->buf_lock); init_waitqueue_head(&isp->wait); @@ -538,7 +512,6 @@ static int apple_isp_probe(struct platform_device *pdev) mutex_init(&isp->gc_lock); INIT_LIST_HEAD(&isp->bufs_pending); INIT_LIST_HEAD(&isp->bufs_submitted); - INIT_LIST_HEAD(&isp->bufs_returned); isp->wq = alloc_workqueue("apple-isp-wq", WQ_UNBOUND, 0); if (!isp->wq) { dev_err(dev, "failed to create workqueue\n"); @@ -586,7 +559,6 @@ static int apple_isp_probe(struct platform_device *pdev) detach_genpd: apple_isp_detach_genpd(isp); free_isp: - kfree(isp->sensor_configs); /* Nothing else can reach it before the video device is registered. */ kfree(isp); return err; @@ -715,7 +687,7 @@ static const struct apple_isp_hw apple_isp_hw_t6020 = { static const struct apple_isp_hw apple_isp_hw_t8122 = { .gen = ISP_GEN_T8112, - .pmu_base = 0x2d07040000, + .pmu_base = 0x2d0704000, .dsid_count = 1, .dsid_clr_base0 = 0x2221c4000, @@ -827,66 +799,6 @@ static const struct apple_isp_hw apple_isp_hw_t8140 = { .boot_mode = 1, .capture_meta_size = ISP_CAPTURE_META_SIZE_T8140, .resident_fw = true, - .meta_exposure = true, -}; - -/* The same for T6040, from the J616s ADT's DART address filter. */ -static const struct isp_mmio_window apple_isp_fw_mmio_t6040[] = { - { 0x220004000, 0x1d4000 }, /* DSID broadcast-clear windows */ - { 0x222004000, 0x1d4000 }, - { 0x224004000, 0x1d4000 }, - { 0x226004000, 0x1d4000 }, - { 0x485660000, 0x4000 }, - { 0x5003c0000, 0x28000 }, /* PMGR, with the PMP clock scratch */ - { 0x500e3c000, 0x4000 }, - { 0x502284000, 0x4000 }, /* PMGR, ISP power states */ - { 0x502880000, 0x8000 }, - { 0x504824000, 0x4000 }, /* PMP bandwidth scratch */ - { 0x51062c000, 0x20000 }, -}; - -/* - * T6040 runs the same coprocessor and firmware interface as T8140, booted - * as macOS boots it on the J616s (macOS 26.6.2 camera log, kext register - * map) and as aurora's m1n1 proxy captures with it: no coprocessor reset, - * four broadcast-clear windows in eight slots, the scaler 1 output path and - * no capture metadata pool or watchdog. - */ -static const struct apple_isp_hw apple_isp_hw_t6040 = { - .gen = ISP_GEN_T6040, - .fw_abi = ISP_FW_ABI_H17, - .pmu_base = 0x0, - - .dsid_count = 4, - .dsid_slots = 8, - .dsid_clr_base0 = 0x2201d4000, - .dsid_clr_base1 = 0x2221d4000, - .dsid_clr_base2 = 0x2241d4000, - .dsid_clr_base3 = 0x2261d4000, - .dsid_clr_range0 = 0x2fc, - - /* the PMGR and PMP scratch words of the ISP's DAPF windows */ - .clock_scratch = 0x5003d0fa8, - .clock_base = 0x0, - .clock_bit = 0x0, - .clock_size = 0x8, - .bandwidth_scratch = 0x504824000, - .bandwidth_base = 0x0, - .bandwidth_bit = 0x0, - .bandwidth_size = 0x8, - .mbox_irq_enable = ISP_MBOX_IRQ_ENABLE_T6031, - - .scl1 = true, - .lpdp = false, - .meta_size = ISP_META_SIZE_T8140, - .fw_mmio = apple_isp_fw_mmio_t6040, - .num_fw_mmio = ARRAY_SIZE(apple_isp_fw_mmio_t6040), - - .coproc_control = ISP_COPROC_CONTROL_T8140, - .boot_mode = 1, - .shared_size = 0xde600000, - .resident_fw = true, - .meta_exposure = true, }; static const struct of_device_id apple_isp_of_match[] = { @@ -898,7 +810,6 @@ static const struct of_device_id apple_isp_of_match[] = { { .compatible = "apple,t6030-isp", .data = &apple_isp_hw_t6030 }, { .compatible = "apple,t6031-isp", .data = &apple_isp_hw_t6031 }, { .compatible = "apple,t8140-isp", .data = &apple_isp_hw_t8140 }, - { .compatible = "apple,t6041-isp", .data = &apple_isp_hw_t6040 }, {}, }; MODULE_DEVICE_TABLE(of, apple_isp_of_match); diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 30425f6cf483d..8f945c26f00e6 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -6,11 +6,11 @@ #include #include -#include #include #include #include +#include #include #include #include @@ -30,8 +30,6 @@ /* used to limit the user space buffers to the buffer_pool_config */ #define ISP_MAX_BUFFERS 16 -#define ISP_MAX_SENSOR_CONFIGS 64 -#define ISP_SENSOR_CONFIG_SIZE 0x120 /* capture metadata buffers, for firmware that has that pool */ #define ISP_CAPMETA_BUFFERS 8 @@ -53,7 +51,6 @@ enum isp_generation { ISP_GEN_T8112, ISP_GEN_T6031, ISP_GEN_T8140, - ISP_GEN_T6040, }; /* @@ -82,6 +79,7 @@ enum isp_firmware_version { }; struct isp_surf { + struct drm_mm_node *mm; struct list_head head; u64 size; u64 type; @@ -93,7 +91,6 @@ struct isp_surf { refcount_t refcount; bool gc; bool submitted; - bool iova_reserved; /* @iova comes from &apple_isp.iovas */ }; struct isp_message { @@ -118,8 +115,6 @@ struct isp_channel { void *virt; u32 doorbell; u32 cursor; - unsigned long deadline; - bool deadline_active; struct mutex lock; struct isp_message req; struct isp_message rsp; @@ -143,8 +138,6 @@ struct isp_preset { struct coord output_dim; struct coord crop_offset; struct coord crop_size; - /* Processed P010 geometry established by preview or a config reply. */ - bool p010; }; struct apple_isp_hw { @@ -153,8 +146,6 @@ struct apple_isp_hw { u64 pmu_base; int dsid_count; - /* windows of the H17 multi broadcast-clear command, the unused all ones */ - u8 dsid_slots; u64 dsid_clr_base0; u64 dsid_clr_base1; u64 dsid_clr_base2; @@ -195,15 +186,9 @@ struct apple_isp_hw { /* ISP_GPIO_6 boot mode */ u32 boot_mode; - /* shared memory size in the H16 boot descriptor, 0 for the T8140 one */ - u64 shared_size; - /* size of a capture metadata buffer, 0 for no such pool */ u32 capture_meta_size; - /* metadata buffers carry the frame's exposure report (H17 firmware) */ - bool meta_exposure; - /* * The firmware can be started only once per system boot, so it is * started at probe and kept running while the driver is bound. @@ -259,23 +244,13 @@ enum isp_sensor_id { ISP_IMX814_2123_01, ISP_IMX853_7622_01, ISP_IMX913_7523_01, - ISP_IMX958_2224_01, ISP_VD56G0_6221_01, ISP_VD56G0_6222_01, }; -struct isp_sensor_config { - struct coord input_dim; - struct coord output_dim; - u32 sensor_clk; - bool usable; - u8 reply[ISP_SENSOR_CONFIG_SIZE]; -}; - struct isp_format { enum isp_sensor_id id; u32 version; - u32 pixelformat; struct isp_preset *preset; unsigned int num_planes; u32 strides[VB2_MAX_PLANES]; @@ -289,12 +264,8 @@ struct apple_isp { enum isp_firmware_version fw_compat; u32 platform_id; u32 temporal_filter; - u32 cam_connections_scheme; struct isp_preset *presets; int num_presets; - /* Read-only sensor configuration replies, cached before video registration. */ - unsigned int num_sensor_configs; - struct isp_sensor_config *sensor_configs; int num_channels; struct isp_format fmts[ISP_MAX_CHANNELS]; @@ -305,16 +276,10 @@ struct apple_isp { struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; struct v4l2_ctrl *exposure_priority; - struct v4l2_ctrl *power_line_frequency; - /* read-only exposure report of the last frame, NULL without one */ - struct v4l2_ctrl *gain; - struct v4l2_ctrl *exposure; - /* last reported total gain (1/256 steps) and exposure (us) */ - u32 frame_gain; - u32 frame_exposure_us; struct vb2_queue vbq; struct mutex video_lock; unsigned int sequence; + bool multiplanar; /* capture rate in frames per second, changes under video_lock */ unsigned int frame_rate; @@ -335,15 +300,13 @@ struct apple_isp { struct iommu_domain *domain; unsigned long shift; - /* allocated IOVA ranges, in IOMMU pages, lowest free range first */ - struct maple_tree iovas; - u64 iova_size; /* size of the range @iovas allocates from */ + struct drm_mm iovad; /* TODO iova.c can't allocate bottom-up */ + u64 iova_size; /* size of the iovad range */ u64 fw_iova_mask; /* see isp_fw_iova() */ + struct mutex iovad_lock; struct isp_firmware { - u64 heap_base; u64 heap_top; - u64 data_top; /* 0 unless the bootloader loads the data */ } fw; /* changes under video_lock once the video device is registered */ enum isp_fw_state fw_state; @@ -351,7 +314,6 @@ struct apple_isp { struct isp_surf *ipc_surf; struct isp_surf *extra_surf; struct isp_surf *data_surf; - struct isp_surf *main_tail_surf; struct isp_surf *log_surf; struct isp_surf *bt_surf; struct isp_surf *meta_surfs[ISP_MAX_BUFFERS]; @@ -385,35 +347,16 @@ struct apple_isp { spinlock_t buf_lock; struct list_head bufs_pending; struct list_head bufs_submitted; - struct list_head bufs_returned; - u64 buffer_tag; - unsigned long meta_returned; - /* - * The channel of a timed-out request that has not been answered yet, - * see isp_ipc_resync(). Sends are serialized by the callers, which - * share the command area. - */ - struct isp_channel *ipc_stale; - /* - * Not even a late answer came, so where the firmware is in the ring - * is unknown. Nothing is sent until the firmware is stopped, and - * resident firmware cannot be started again before the next boot. - */ - bool ipc_failed; }; struct isp_chan_ops { int (*handle)(struct apple_isp *isp, struct isp_channel *chan); - /* Called after the response slot and its doorbell are published. */ - void (*complete)(struct apple_isp *isp); }; struct isp_buffer { struct vb2_v4l2_buffer vb; struct list_head link; struct isp_surf surfs[VB2_MAX_PLANES]; - u64 tag; - enum vb2_buffer_state return_state; }; #define to_isp_buffer(x) container_of((x), struct isp_buffer, vb) @@ -441,36 +384,4 @@ enum { ((isp)->hw->capture_meta_size ? ISP_CAPMETA_BUFFERS : 0) #define isp_get_current_format(isp) (isp_get_format(isp, isp->current_ch)) -static inline bool isp_supports_p010(struct apple_isp *isp) -{ - return isp->hw->gen == ISP_GEN_T6040 && - isp_get_current_format(isp)->version == 0x958; -} - -static inline bool isp_has_p010(struct apple_isp *isp) -{ - if (!isp_supports_p010(isp)) - return false; - for (int i = 0; i < isp->num_presets; i++) { - if (isp->presets[i].p010) - return true; - } - return false; -} - -static inline bool isp_is_p010(struct apple_isp *isp) -{ - return isp_get_current_format(isp)->pixelformat == V4L2_PIX_FMT_P010; -} - -static inline unsigned int isp_num_rendered(struct apple_isp *isp) -{ - return isp_is_p010(isp) ? 2 : ISP_MAX_BUFFERS; -} - -static inline unsigned int isp_num_meta(struct apple_isp *isp) -{ - return isp_is_p010(isp) ? 8 : ISP_MAX_BUFFERS; -} - #endif /* __ISP_DRV_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index 88f428b3b0c27..895a6d1299c22 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -7,7 +7,6 @@ #include #include -#include #include #define ISP_TEST_FRAME_SIZE 0x1000 @@ -20,8 +19,6 @@ static struct apple_isp *isp_test_alloc(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, isp); mutex_init(&isp->video_lock); - spin_lock_init(&isp->buf_lock); - INIT_LIST_HEAD(&isp->bufs_returned); isp_get_current_format(isp)->total_size = ISP_TEST_FRAME_SIZE; return isp; @@ -37,8 +34,6 @@ static void isp_test_queue_init(struct kunit *test) KUNIT_ASSERT_EQ(test, isp_vb2_queue_init(isp), 0); KUNIT_EXPECT_GE(test, isp->vbq.max_num_buffers, VB2_MAX_FRAME); - KUNIT_EXPECT_EQ(test, isp->vbq.type, V4L2_BUF_TYPE_VIDEO_CAPTURE); - KUNIT_EXPECT_FALSE(test, isp->vbq.is_multiplanar); vb2_queue_release(&isp->vbq); } @@ -104,12 +99,6 @@ static void isp_test_create_bufs_limit(struct kunit *test) KUNIT_EXPECT_EQ(test, isp_test_setup(test, ISP_MAX_BUFFERS, &nbuffers, &num_planes, sizes), -ENOBUFS); - /* The contiguous API must refuse a second independent memory plane. */ - nbuffers = 1; - num_planes = 2; - KUNIT_EXPECT_EQ(test, isp_test_setup(test, 0, &nbuffers, &num_planes, - sizes), -EINVAL); - /* A plane smaller than a frame is still refused. */ nbuffers = 1; num_planes = 1; @@ -118,289 +107,7 @@ static void isp_test_create_bufs_limit(struct kunit *test) sizes), -EINVAL); } -static const struct apple_isp_hw isp_test_t6040 = { - .gen = ISP_GEN_T6040, - .fw_abi = ISP_FW_ABI_H17, -}; - -static void isp_test_p010_format(struct kunit *test) -{ - struct apple_isp *isp = isp_test_alloc(test); - struct isp_format *fmt = isp_get_current_format(isp); - struct isp_preset preset = { .output_dim = { 1920, 1080 }, .p010 = true }; - struct v4l2_format f = {}; - unsigned int sizes[VB2_MAX_PLANES] = {}; - unsigned int nbuffers = 16, num_planes = 0; - - isp->hw = &isp_test_t6040; - fmt->version = 0x958; - KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); - isp_get_sp_pix_format(isp, &f, fmt); - KUNIT_EXPECT_EQ(test, f.fmt.pix.pixelformat, V4L2_PIX_FMT_P010); - KUNIT_EXPECT_EQ(test, f.fmt.pix.bytesperline, 3840); - KUNIT_EXPECT_EQ(test, f.fmt.pix.sizeimage, 6220800); - KUNIT_EXPECT_EQ(test, fmt->plane_size[0], (size_t)4147200); - KUNIT_EXPECT_EQ(test, fmt->plane_size[1], (size_t)2073600); - KUNIT_EXPECT_EQ(test, f.fmt.pix.quantization, V4L2_QUANTIZATION_LIM_RANGE); - - isp->vbq.drv_priv = isp; - isp_test_set_num_buffers(test, &isp->vbq, 0); - KUNIT_ASSERT_EQ(test, - isp_vb2_queue_setup(&isp->vbq, &nbuffers, &num_planes, sizes, NULL), 0); - KUNIT_EXPECT_EQ(test, nbuffers, 2); - KUNIT_EXPECT_EQ(test, num_planes, 1); - KUNIT_EXPECT_EQ(test, sizes[0], 6220800); - KUNIT_EXPECT_EQ(test, isp_num_meta(isp), 8); - - preset.output_dim = (struct coord){1280, 720}; - KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); - KUNIT_EXPECT_EQ(test, fmt->strides[0], 2560); - KUNIT_EXPECT_EQ(test, fmt->total_size, (size_t)2764800); - /* NV12 remains the default and restores the existing pool capacity. */ - KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_NV12), 0); - KUNIT_EXPECT_EQ(test, fmt->pixelformat, V4L2_PIX_FMT_NV12); - KUNIT_EXPECT_EQ(test, fmt->total_size, (size_t)1382400); - KUNIT_EXPECT_EQ(test, isp_num_rendered(isp), ISP_MAX_BUFFERS); - KUNIT_EXPECT_EQ(test, isp_num_meta(isp), ISP_MAX_BUFFERS); - -} - -static void isp_test_p010_admission(struct kunit *test) -{ - static const struct apple_isp_hw neo = { .gen = ISP_GEN_T8140 }; - struct apple_isp *isp = isp_test_alloc(test); - struct isp_format *fmt = isp_get_current_format(isp); - struct isp_preset preset = { .output_dim = {1280, 720}, .p010 = true }; - struct v4l2_fmtdesc desc = { .index = 0 }; - - isp->presets = &preset; - isp->num_presets = 1; - isp->hw = &isp_test_t6040; - fmt->version = 0x958; - KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); - KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_NV12); - desc.index = 1; - KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); - KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_P010); - desc.index = 2; - KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); - desc.index = 1; - isp->hw = &neo; - KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); - KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); - KUNIT_EXPECT_EQ(test, fmt->pixelformat, V4L2_PIX_FMT_NV12); - KUNIT_EXPECT_EQ(test, fmt->strides[0], 1280); -} - -static void isp_test_p010_completion_identity(struct kunit *test) -{ - struct isp_format fmt = { .plane_size = {4147200, 2073600} }; - struct isp_buffer buf = { .surfs = {{ .iova = 0x10020000000ULL }}, .tag = 42 }; - struct isp_buflist_buffer desc = { - .iovas = {0x20000000, 0x203f4800}, - .num_planes = 2, .pool_type = CISP_POOL_TYPE_RENDERED_SCL1, .tag = 42, - }; - - KUNIT_EXPECT_TRUE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); - desc.tag--; - KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); - desc.tag++; - desc.iovas[1]++; - KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); - desc.iovas[1]--; - desc.iovas[0] |= BIT_ULL(40); - /* Reject mixed encodings, accept both exact full submitted addresses. */ - KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); - desc.iovas[1] |= BIT_ULL(40); - KUNIT_EXPECT_TRUE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); - desc.iovas[0] |= BIT_ULL(41); - desc.iovas[1] |= BIT_ULL(41); - KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); - desc.iovas[0] = (u32)desc.iovas[0]; - desc.iovas[1] = (u32)desc.iovas[1]; - desc.pool_type = CISP_POOL_TYPE_RENDERED; - KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); - desc.pool_type = CISP_POOL_TYPE_RENDERED_SCL1; - desc.num_planes = 1; - KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); -} - -static void isp_test_p010_report_defers_completion(struct kunit *test) -{ - struct apple_isp *isp = isp_test_alloc(test); - struct isp_format *fmt = isp_get_current_format(isp); - struct isp_buffer *buf = kunit_kzalloc(test, sizeof(*buf), GFP_KERNEL); - struct { - u64 type, count; - struct isp_buflist_buffer desc; - } report = { - .type = 2, .count = 1, - .desc = { .iovas = {0x20000000, 0x203f4800}, - .num_planes = 2, .pool_type = 9, .tag = 42 }, - }; - struct isp_surf report_surf = { - .iova = 0x30000000, .size = sizeof(report), .virt = &report, - }; - struct isp_channel chan = { - .req = { .arg0 = report_surf.iova, .arg1 = sizeof(report), - .arg2 = ISP_IPC_BUFEXC_FLAG_RENDER }, - }; - - KUNIT_ASSERT_NOT_NULL(test, buf); - fmt->pixelformat = V4L2_PIX_FMT_P010; - fmt->plane_size[0] = 4147200; - isp->fw_iova_mask = U64_MAX; - isp->bt_surf = &report_surf; - buf->surfs[0].iova = 0x10020000000ULL; - buf->tag = 42; - INIT_LIST_HEAD(&isp->bufs_submitted); - list_add(&buf->link, &isp->bufs_submitted); - /* There is no vb2 queue: completing it before the ACK would fault. */ - KUNIT_ASSERT_EQ(test, ipc_bt_handle(isp, &chan), 0); - KUNIT_EXPECT_TRUE(test, list_empty(&isp->bufs_submitted)); - KUNIT_EXPECT_PTR_EQ(test, - list_first_entry(&isp->bufs_returned, struct isp_buffer, link), buf); - KUNIT_EXPECT_EQ(test, buf->return_state, VB2_BUF_STATE_DONE); - KUNIT_EXPECT_EQ(test, chan.rsp.arg0, report_surf.iova | ISP_IPC_FLAG_ACK); -} - -static void isp_test_p010_mode_selection(struct kunit *test) -{ - struct apple_isp *isp = isp_test_alloc(test); - struct isp_preset presets[] = { - { .index = 5, .output_dim = {1920, 1080} }, - { .index = 5, .output_dim = {1280, 720}, .p010 = true }, - { .index = 1, .output_dim = {4096, 3072} }, - }; - - isp->hw = &isp_test_t6040; - isp_get_current_format(isp)->version = 0x958; - isp->presets = presets; - isp->num_presets = ARRAY_SIZE(presets); - KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 0, 0, V4L2_PIX_FMT_P010), &presets[1]); - KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 1920, 1080, V4L2_PIX_FMT_P010), - &presets[1]); - KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 4096, 3072, V4L2_PIX_FMT_P010), - &presets[1]); - KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 1920, 1080, V4L2_PIX_FMT_NV12), - &presets[0]); - /* Only a matching firmware output enables the larger P010 size. */ - presets[2].p010 = true; - KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 4096, 3072, V4L2_PIX_FMT_P010), - &presets[2]); -} - -static int isp_test_buffer_ack(struct apple_isp *isp, struct isp_channel *chan, - unsigned long timeout) -{ - unsigned int *calls = kunit_get_current_test()->priv; - - (*calls)++; - return 0; -} - -static void isp_test_imx958_last_lease_requeue(struct kunit *test) -{ - struct apple_isp *isp = isp_test_alloc(test); - struct isp_format *fmt = isp_get_current_format(isp); - struct isp_buffer *buf = kunit_kzalloc(test, sizeof(*buf), GFP_KERNEL); - struct isp_buffer *submitted; - unsigned int calls = 0; - - KUNIT_ASSERT_NOT_NULL(test, buf); - isp->hw = &isp_test_t6040; - fmt->version = 0x958; - fmt->pixelformat = V4L2_PIX_FMT_P010; - fmt->num_planes = 2; - fmt->plane_size[0] = 1843200; - fmt->plane_size[1] = 921600; - INIT_LIST_HEAD(&isp->bufs_pending); - INIT_LIST_HEAD(&isp->bufs_submitted); - isp->cmd_virt = kunit_kzalloc(test, ISP_CMD_AREA_SIZE(0), GFP_KERNEL); - isp->chan_bh = kunit_kzalloc(test, sizeof(*isp->chan_bh), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); - KUNIT_ASSERT_NOT_NULL(test, isp->chan_bh); - for (unsigned int i = 0; i < ISP_MAX_BUFFERS; i++) { - isp->meta_surfs[i] = kunit_kzalloc(test, sizeof(struct isp_surf), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, isp->meta_surfs[i]); - isp->meta_surfs[i]->submitted = true; - } - isp->vbq.drv_priv = isp; - buf->vb.vb2_buf.vb2_queue = &isp->vbq; - buf->surfs[0].iova = 0x20000000; - set_bit(ISP_STATE_STREAMING, &isp->state); - test->priv = &calls; - kunit_activate_static_stub(test, ipc_chan_send, isp_test_buffer_ack); - for (unsigned int format = 0; format < 2; format++) { - fmt->pixelformat = format ? V4L2_PIX_FMT_NV12 : V4L2_PIX_FMT_P010; - calls = 0; - for (unsigned int i = 1; i <= 10000; i++) { - /* Both lists empty, as after the last completion was dequeued. */ - isp_vb2_buf_queue(&buf->vb.vb2_buf); - KUNIT_ASSERT_EQ(test, calls, i); - if (!format) - KUNIT_ASSERT_EQ(test, buf->tag, (u64)i); - KUNIT_ASSERT_TRUE(test, list_empty(&isp->bufs_pending)); - submitted = list_first_entry(&isp->bufs_submitted, struct isp_buffer, link); - KUNIT_ASSERT_PTR_EQ(test, submitted, buf); - list_del(&buf->link); - } - } -} - -/* The exposure report is found through the section table, never past it. */ -static void isp_test_meta_exposure(struct kunit *test) -{ - static const struct apple_isp_hw hw = { .meta_exposure = true }; - static const struct apple_isp_hw hw_none = { }; - struct apple_isp *isp = isp_test_alloc(test); - struct isp_surf meta = { .size = 0x2000 }; - u8 *virt = kunit_kzalloc(test, meta.size, GFP_KERNEL); - - KUNIT_ASSERT_NOT_NULL(test, virt); - meta.virt = virt; - isp->hw = &hw; - isp->frame_gain = 256; - - /* fourth table entry: the section at 0x1870 */ - put_unaligned_le32(0x1870, virt + 0x1c); - put_unaligned_le32(32475, virt + 0x1870); - put_unaligned_le16(0x0800, virt + 0x1874); - put_unaligned_le16(3253, virt + 0x1876); - isp_meta_read_exposure(isp, &meta); - KUNIT_EXPECT_EQ(test, isp->frame_exposure_us, 32475); - KUNIT_EXPECT_EQ(test, isp->frame_gain, 3253); - - /* a section that would end past the buffer is ignored */ - put_unaligned_le32(meta.size - 4, virt + 0x1c); - put_unaligned_le32(1000, virt + meta.size - 4); - isp_meta_read_exposure(isp, &meta); - KUNIT_EXPECT_EQ(test, isp->frame_gain, 3253); - - /* so is an unwritten section */ - put_unaligned_le32(0x1000, virt + 0x1c); - isp_meta_read_exposure(isp, &meta); - KUNIT_EXPECT_EQ(test, isp->frame_exposure_us, 32475); - - /* and firmware without the report */ - put_unaligned_le32(0x1870, virt + 0x1c); - put_unaligned_le16(600, virt + 0x1876); - isp->hw = &hw_none; - isp_meta_read_exposure(isp, &meta); - KUNIT_EXPECT_EQ(test, isp->frame_gain, 3253); - isp->hw = &hw; - isp_meta_read_exposure(isp, &meta); - KUNIT_EXPECT_EQ(test, isp->frame_gain, 600); -} - static struct kunit_case isp_v4l2_test_cases[] = { - KUNIT_CASE(isp_test_meta_exposure), - KUNIT_CASE(isp_test_imx958_last_lease_requeue), - KUNIT_CASE(isp_test_p010_report_defers_completion), - KUNIT_CASE(isp_test_p010_mode_selection), - KUNIT_CASE(isp_test_p010_format), - KUNIT_CASE(isp_test_p010_admission), - KUNIT_CASE(isp_test_p010_completion_identity), KUNIT_CASE(isp_test_queue_init), KUNIT_CASE(isp_test_reqbufs_limit), KUNIT_CASE(isp_test_create_bufs_limit), diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 24506ed526a34..072b8523279c2 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -3,7 +3,6 @@ #include #include -#include #include #include @@ -22,20 +21,24 @@ #define ISP_MIN_FRAMES 2 #define ISP_MAX_PLANES 4 +#define ISP_MAX_PIX_FORMATS 2 #define ISP_BUFFER_TIMEOUT msecs_to_jiffies(1500) #define ISP_STRIDE_ALIGNMENT 64 -static void isp_vb2_set_min_buffers(struct apple_isp *isp); - /* Capture rates offered to userspace, in frames per second */ static const unsigned int isp_frame_rates[] = { 30, 25, 24, 20, 15 }; +static bool multiplanar = false; +module_param(multiplanar, bool, 0644); +MODULE_PARM_DESC(multiplanar, "Enable multiplanar API"); + struct isp_buflist_buffer { u64 iovas[ISP_MAX_PLANES]; u32 flags[ISP_MAX_PLANES]; u32 num_planes; u32 pool_type; - u64 tag; + u32 tag; + u32 pad; } __packed; static_assert(sizeof(struct isp_buflist_buffer) == ISP_BUFLIST_DESC_SIZE); @@ -54,77 +57,6 @@ static_assert(ISP_CMD_AREA_SIZE(0) >= ISP_IPC_BUFEXC_STAT_SIZE); */ #define ISP_BUFLIST_POOL_CAPTURE_META 2 -static bool isp_p010_buffer_matches(const struct isp_format *fmt, - const struct isp_buffer *buf, - const struct isp_buflist_buffer *desc) -{ - u64 y = buf->surfs[0].iova; - u64 uv = y + fmt->plane_size[0]; - bool full = desc->iovas[0] == y && desc->iovas[1] == uv; - bool low = desc->iovas[0] == (u32)y && desc->iovas[1] == (u32)uv; - - /* Accept the submitted pair or the zero-extended pair observed on H17. */ - return desc->num_planes == 2 && - desc->pool_type == CISP_POOL_TYPE_RENDERED_SCL1 && (full || low) && - !desc->iovas[2] && !desc->iovas[3] && desc->tag == buf->tag; -} - -/* - * A metadata buffer starts with a table of section offsets. On H17 firmware - * the fourth section opens with the frame's exposure: the integration time in - * microseconds (le32 at 0) and the total sensor gain in 1/256 steps, 256 for - * unity (le16 at 6). Observed on T6040 and T8140 with AE converging under a - * fixed scene and with the frame rate capping the integration time. - */ -#define ISP_META_SECTIONS 0x10 -#define ISP_META_SECTION_EXPOSURE 3 -#define ISP_META_EXPOSURE_TIME 0x0 -#define ISP_META_EXPOSURE_GAIN 0x6 -#define ISP_META_EXPOSURE_SIZE 0x8 - -static void isp_meta_read_exposure(struct apple_isp *isp, - const struct isp_surf *meta) -{ - const u8 *virt = meta->virt; - u32 offset, time; - u16 gain; - - if (!isp->hw->meta_exposure || !virt) - return; - - offset = get_unaligned_le32(virt + ISP_META_SECTIONS + - 4 * ISP_META_SECTION_EXPOSURE); - if (offset < ISP_META_SECTIONS || offset > meta->size || - meta->size - offset < ISP_META_EXPOSURE_SIZE) - return; - - time = get_unaligned_le32(virt + offset + ISP_META_EXPOSURE_TIME); - gain = get_unaligned_le16(virt + offset + ISP_META_EXPOSURE_GAIN); - /* An unwritten section or one this layout does not describe */ - if (!time || !gain) - return; - - WRITE_ONCE(isp->frame_exposure_us, time); - WRITE_ONCE(isp->frame_gain, gain); -} - -void ipc_bt_complete(struct apple_isp *isp) -{ - struct isp_buffer *buf, *tmp; - unsigned long flags, i; - - /* The firmware has received its ACK before userspace can reuse a lease. */ - spin_lock_irqsave(&isp->buf_lock, flags); - for_each_set_bit(i, &isp->meta_returned, ISP_MAX_BUFFERS) - isp->meta_surfs[i]->submitted = false; - isp->meta_returned = 0; - list_for_each_entry_safe(buf, tmp, &isp->bufs_returned, link) { - list_del(&buf->link); - vb2_buffer_done(&buf->vb.vb2_buf, buf->return_state); - } - spin_unlock_irqrestore(&isp->buf_lock, flags); -} - int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) { struct isp_message *req = &chan->req, *rsp = &chan->rsp; @@ -164,15 +96,11 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) bufd->pool_type); */ if (bufd->pool_type == 0) { - for (int j = 0; j < isp_num_meta(isp); j++) { + for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) { struct isp_surf *meta = isp->meta_surfs[j]; if ((u32)bufd->iovas[0] == (u32)meta->iova) { WARN_ON(!meta->submitted); - isp_meta_read_exposure(isp, meta); - if (isp_is_p010(isp)) - __set_bit(j, &isp->meta_returned); - else - meta->submitted = false; + meta->submitted = false; } } } else if (bufd->pool_type == ISP_BUFLIST_POOL_CAPTURE_META && @@ -186,10 +114,8 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) } else { list_for_each_entry_safe_reverse( buf, tmp, &isp->bufs_submitted, link) { - if (isp_is_p010(isp) ? - isp_p010_buffer_matches(isp_get_current_format(isp), - buf, bufd) : - (u32)buf->surfs[0].iova == (u32)bufd->iovas[0]) { + if ((u32)buf->surfs[0].iova == + (u32)bufd->iovas[0]) { enum vb2_buffer_state state = VB2_BUF_STATE_ERROR; @@ -200,13 +126,9 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) if (req->arg2 == ISP_IPC_BUFEXC_FLAG_RENDER) state = VB2_BUF_STATE_DONE; - if (isp_is_p010(isp)) { - buf->return_state = state; - list_move_tail(&buf->link, &isp->bufs_returned); - } else { - vb2_buffer_done(&buf->vb.vb2_buf, state); - list_del(&buf->link); - } + vb2_buffer_done(&buf->vb.vb2_buf, + state); + list_del(&buf->link); } } } @@ -239,17 +161,12 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) struct isp_buflist *bl = isp->cmd_virt; struct isp_buflist_buffer *bufd = &bl->buffers[0]; - /* Before the command area is overwritten */ - err = isp_ipc_resync(isp); - if (err) - return err; - /* Clear what earlier commands and batches left in the reserved fields. */ memset(bl, 0, ISP_CMD_AREA_SIZE(isp_num_capmeta(isp))); bl->type = 1; spin_lock_irqsave(&isp->buf_lock, flags); - for (int i = 0; i < isp_num_meta(isp); i++) { + for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { struct isp_surf *meta = isp->meta_surfs[i]; if (meta->submitted) @@ -288,13 +205,9 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) } /* One rendered pool per batch; the rest stays pending for the next. */ - while (num_rendered < isp_num_rendered(isp) && + while (num_rendered < ISP_MAX_BUFFERS && (buf = list_first_entry_or_null(&isp->bufs_pending, struct isp_buffer, link))) { - if (isp_is_p010(isp)) { - buf->tag = ++isp->buffer_tag; - bufd->tag = buf->tag; - } bufd->num_planes = fmt->num_planes; bufd->pool_type = isp->hw->scl1 ? CISP_POOL_TYPE_RENDERED_SCL1 : CISP_POOL_TYPE_RENDERED; @@ -336,12 +249,6 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); if (err) { - /* - * The firmware may have taken the batch. Keep its leases owned - * until the timeout is resolved, see apple_isp_stop_camera(). - */ - if (isp_is_p010(isp)) - return err; /* If we fail, consider the buffer not submitted. */ dev_err(isp->dev, "%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n", @@ -362,7 +269,7 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) &isp->bufs_pending); } } - for (int j = 0; j < isp_num_meta(isp); j++) { + for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) { struct isp_surf *meta = isp->meta_surfs[j]; if (bufd->iovas[0] == meta->iova) { meta->submitted = false; @@ -401,13 +308,13 @@ static int isp_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, * REQBUFS frees the old buffers first, CREATE_BUFS adds to them, so * count the buffers the queue already has. */ - if (q_num_bufs >= isp_num_rendered(isp)) + if (q_num_bufs >= ISP_MAX_BUFFERS) return -ENOBUFS; *nbuffers = min_t(unsigned int, *nbuffers, - isp_num_rendered(isp) - q_num_bufs); + ISP_MAX_BUFFERS - q_num_bufs); if (*num_planes) { - if (*num_planes != 1 || sizes[0] < fmt->total_size) + if (sizes[0] < fmt->total_size) return -EINVAL; return 0; @@ -500,23 +407,8 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb) list_add_tail(&buf->link, &isp->bufs_pending); spin_unlock_irqrestore(&isp->buf_lock, flags); - /* Restart an empty IMX958 queue when its last returned lease is queued. */ - if (test_bit(ISP_STATE_STREAMING, &isp->state) && - (isp_supports_p010(isp) || !empty)) { - if (isp_submit_buffers(isp, false) && isp_is_p010(isp)) { - /* - * After a timeout the leases stay owned until the - * stream stops. Only firmware that did not answer at - * all is stopped here, before its leases are released. - */ - if (isp->ipc_failed) { - apple_isp_firmware_halt(isp); - isp_vb2_release_buffers(isp, - VB2_BUF_STATE_ERROR); - } - vb2_queue_error(&isp->vbq); - } - } + if (test_bit(ISP_STATE_STREAMING, &isp->state) && !empty) + isp_submit_buffers(isp, false); } static int apple_isp_start_streaming(struct apple_isp *isp) @@ -592,14 +484,12 @@ static int isp_vb2_start_streaming(struct vb2_queue *q, unsigned int count) isp->sequence = 0; - /* The exposure controls are applied when the stream starts. */ + /* The exposure priority is applied when the stream starts. */ v4l2_ctrl_grab(isp->exposure_priority, true); - v4l2_ctrl_grab(isp->power_line_frequency, true); err = apple_isp_start_streaming(isp); if (err) { v4l2_ctrl_grab(isp->exposure_priority, false); - v4l2_ctrl_grab(isp->power_line_frequency, false); isp_vb2_release_buffers(isp, VB2_BUF_STATE_QUEUED); } @@ -613,7 +503,6 @@ static void isp_vb2_stop_streaming(struct vb2_queue *q) apple_isp_stop_streaming(isp); isp_vb2_release_buffers(isp, VB2_BUF_STATE_ERROR); v4l2_ctrl_grab(isp->exposure_priority, false); - v4l2_ctrl_grab(isp->power_line_frequency, false); } int apple_isp_video_suspend(struct apple_isp *isp) @@ -676,23 +565,18 @@ static const struct vb2_ops isp_vb2_ops = { }; static int isp_set_preset(struct apple_isp *isp, struct isp_format *fmt, - struct isp_preset *preset, u32 pixelformat) + struct isp_preset *preset) { - unsigned int bytes_per_sample; int i; size_t total_size; fmt->preset = preset; - /* P010 has MSB-aligned 10-bit samples in little-endian 16-bit words. */ - fmt->pixelformat = pixelformat == V4L2_PIX_FMT_P010 && - isp_supports_p010(isp) && preset->p010 ? - V4L2_PIX_FMT_P010 : V4L2_PIX_FMT_NV12; - bytes_per_sample = fmt->pixelformat == V4L2_PIX_FMT_P010 ? 2 : 1; + /* I really fucking hope they all use NV12. */ fmt->num_planes = 2; - fmt->strides[0] = ALIGN(preset->output_dim.x * bytes_per_sample, - ISP_STRIDE_ALIGNMENT); - fmt->strides[1] = fmt->strides[0]; + fmt->strides[0] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT); + /* UV subsampled interleaved */ + fmt->strides[1] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT); fmt->plane_size[0] = fmt->strides[0] * preset->output_dim.y; fmt->plane_size[1] = fmt->strides[1] * preset->output_dim.y / 2; @@ -705,25 +589,17 @@ static int isp_set_preset(struct apple_isp *isp, struct isp_format *fmt, } static struct isp_preset *isp_select_preset(struct apple_isp *isp, u32 width, - u32 height, u32 pixelformat) + u32 height) { struct isp_preset *preset, *best = &isp->presets[0]; int i, score, best_score = INT_MAX; - bool p010 = pixelformat == V4L2_PIX_FMT_P010 && isp_has_p010(isp); - - /* An unspecified P010 size selects the processed 720p preview first. */ - if (width == 0 || height == 0) { - if (!p010) - return &isp->presets[0]; - width = 1280; - height = 720; - } + /* Default if no dimensions */ + if (width == 0 || height == 0) + return &isp->presets[0]; for (i = 0; i < isp->num_presets; i++) { preset = &isp->presets[i]; - if (p010 && !preset->p010) - continue; score = abs((int)preset->output_dim.x - (int)width) + abs((int)preset->output_dim.y - (int)height); if (score < best_score) { @@ -747,47 +623,44 @@ static int isp_vidioc_querycap(struct file *file, void *priv, return 0; } -static int isp_enum_pixelformat(struct apple_isp *isp, struct v4l2_fmtdesc *f) +static int isp_vidioc_enum_format(struct file *file, void *fh, + struct v4l2_fmtdesc *f) { - /* Browser/WebRTC compatibility: the shipped first format remains NV12. */ - if (!f->index) + struct apple_isp *isp = video_drvdata(file); + + if (f->index >= ISP_MAX_PIX_FORMATS) + return -EINVAL; + + switch (f->index) { + case 0: f->pixelformat = V4L2_PIX_FMT_NV12; - else if (f->index == 1 && isp_has_p010(isp)) - f->pixelformat = V4L2_PIX_FMT_P010; - else + break; + case 1: + if (!isp->multiplanar) + return -EINVAL; + f->pixelformat = V4L2_PIX_FMT_NV12M; + break; + default: return -EINVAL; - return 0; -} + } -static int isp_vidioc_enum_format(struct file *file, void *fh, - struct v4l2_fmtdesc *f) -{ - return isp_enum_pixelformat(video_drvdata(file), f); + return 0; } static int isp_vidioc_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *f) { struct apple_isp *isp = video_drvdata(file); - struct isp_preset *preset = NULL; - u32 index = 0; - if (f->pixel_format != V4L2_PIX_FMT_NV12 && - !(f->pixel_format == V4L2_PIX_FMT_P010 && isp_has_p010(isp))) + if (f->index >= isp->num_presets) return -EINVAL; - for (int i = 0; i < isp->num_presets; i++) { - if (f->pixel_format == V4L2_PIX_FMT_P010 && !isp->presets[i].p010) - continue; - if (index++ == f->index) { - preset = &isp->presets[i]; - break; - } - } - if (!preset) + if ((f->pixel_format != V4L2_PIX_FMT_NV12) && + (f->pixel_format != V4L2_PIX_FMT_NV12M)) return -EINVAL; - f->discrete.width = preset->output_dim.x; - f->discrete.height = preset->output_dim.y; + + f->discrete.width = isp->presets[f->index].output_dim.x; + f->discrete.height = isp->presets[f->index].output_dim.y; f->type = V4L2_FRMSIZE_TYPE_DISCRETE; return 0; @@ -803,12 +676,10 @@ static int isp_vidioc_enum_frameintervals(struct file *file, void *fh, return -EINVAL; if (interval->pixel_format != V4L2_PIX_FMT_NV12 && - !(interval->pixel_format == V4L2_PIX_FMT_P010 && isp_has_p010(isp))) + interval->pixel_format != V4L2_PIX_FMT_NV12M) return -EINVAL; for (i = 0; i < isp->num_presets; i++) { - if (interval->pixel_format == V4L2_PIX_FMT_P010 && !isp->presets[i].p010) - continue; if (isp->presets[i].output_dim.x == interval->width && isp->presets[i].output_dim.y == interval->height) break; @@ -855,12 +726,29 @@ static inline void isp_get_sp_pix_format(struct apple_isp *isp, f->fmt.pix.sizeimage = fmt->total_size; f->fmt.pix.field = V4L2_FIELD_NONE; - f->fmt.pix.pixelformat = fmt->pixelformat; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_709; f->fmt.pix.xfer_func = V4L2_XFER_FUNC_709; - if (fmt->pixelformat == V4L2_PIX_FMT_P010) - f->fmt.pix.quantization = V4L2_QUANTIZATION_LIM_RANGE; +} + +static inline void isp_get_mp_pix_format(struct apple_isp *isp, + struct v4l2_format *f, + struct isp_format *fmt) +{ + f->fmt.pix_mp.width = fmt->preset->output_dim.x; + f->fmt.pix_mp.height = fmt->preset->output_dim.y; + f->fmt.pix_mp.num_planes = fmt->num_planes; + for (int i = 0; i < fmt->num_planes; i++) { + f->fmt.pix_mp.plane_fmt[i].sizeimage = fmt->plane_size[i]; + f->fmt.pix_mp.plane_fmt[i].bytesperline = fmt->strides[i]; + } + + f->fmt.pix_mp.field = V4L2_FIELD_NONE; + f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M; + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; + f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_709; + f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_709; } static int isp_vidioc_get_format(struct file *file, void *fh, @@ -886,16 +774,14 @@ static int isp_vidioc_set_format(struct file *file, void *fh, if (vb2_is_busy(&isp->vbq)) return -EBUSY; - preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height, - f->fmt.pix.pixelformat); - err = isp_set_preset(isp, fmt, preset, f->fmt.pix.pixelformat); + preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); + err = isp_set_preset(isp, fmt, preset); if (err) return err; isp_get_sp_pix_format(isp, f, fmt); isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - isp_vb2_set_min_buffers(isp); return 0; } @@ -908,9 +794,8 @@ static int isp_vidioc_try_format(struct file *file, void *fh, struct isp_preset *preset; int err; - preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height, - f->fmt.pix.pixelformat); - err = isp_set_preset(isp, &fmt, preset, f->fmt.pix.pixelformat); + preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); + err = isp_set_preset(isp, &fmt, preset); if (err) return err; @@ -919,6 +804,69 @@ static int isp_vidioc_try_format(struct file *file, void *fh, return 0; } +static int isp_vidioc_get_format_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct apple_isp *isp = video_drvdata(file); + struct isp_format *fmt = isp_get_current_format(isp); + + if (!isp->multiplanar) + return -ENOTTY; + + isp_get_mp_pix_format(isp, f, fmt); + + return 0; +} + +static int isp_vidioc_set_format_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct apple_isp *isp = video_drvdata(file); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_preset *preset; + int err; + + if (!isp->multiplanar) + return -ENOTTY; + + if (vb2_is_busy(&isp->vbq)) + return -EBUSY; + + preset = isp_select_preset(isp, f->fmt.pix_mp.width, + f->fmt.pix_mp.height); + err = isp_set_preset(isp, fmt, preset); + if (err) + return err; + + isp_get_mp_pix_format(isp, f, fmt); + + isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + + return 0; +} + +static int isp_vidioc_try_format_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct apple_isp *isp = video_drvdata(file); + struct isp_format fmt = *isp_get_current_format(isp); + struct isp_preset *preset; + int err; + + if (!isp->multiplanar) + return -ENOTTY; + + preset = isp_select_preset(isp, f->fmt.pix_mp.width, + f->fmt.pix_mp.height); + err = isp_set_preset(isp, &fmt, preset); + if (err) + return err; + + isp_get_mp_pix_format(isp, f, &fmt); + + return 0; +} + static int isp_vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *inp) { @@ -951,7 +899,9 @@ static int isp_vidioc_get_param(struct file *file, void *fh, { struct apple_isp *isp = video_drvdata(file); - if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + (!isp->multiplanar || + a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) return -EINVAL; a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; @@ -967,7 +917,9 @@ static int isp_vidioc_set_param(struct file *file, void *fh, struct apple_isp *isp = video_drvdata(file); struct v4l2_fract *tpf = &a->parm.capture.timeperframe; - if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + (!isp->multiplanar || + a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) return -EINVAL; /* The rate is configured when the stream starts. */ @@ -988,27 +940,6 @@ static int isp_vidioc_set_param(struct file *file, void *fh, return 0; } -static int isp_vidioc_log_status(struct file *file, void *fh) -{ - struct apple_isp *isp = video_drvdata(file); - - v4l2_ctrl_log_status(file, fh); - for (unsigned int i = 0; i < isp->num_sensor_configs; i++) { - const struct isp_sensor_config *config = &isp->sensor_configs[i]; - - v4l2_info(&isp->v4l2_dev, - "sensor config %u: input %ux%u output %ux%u clock %u%s\n", - i, config->input_dim.x, config->input_dim.y, - config->output_dim.x, config->output_dim.y, - config->sensor_clk, - config->usable ? "" : " (unverified geometry)"); - /* This explicit diagnostic ioctl replaces a probe-time parameter. */ - print_hex_dump(KERN_INFO, "apple-isp config: ", DUMP_PREFIX_OFFSET, - 16, 4, config->reply, sizeof(config->reply), false); - } - return 0; -} - static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_querycap = isp_vidioc_querycap, @@ -1016,6 +947,9 @@ static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_g_fmt_vid_cap = isp_vidioc_get_format, .vidioc_s_fmt_vid_cap = isp_vidioc_set_format, .vidioc_try_fmt_vid_cap = isp_vidioc_try_format, + .vidioc_g_fmt_vid_cap_mplane = isp_vidioc_get_format_mplane, + .vidioc_s_fmt_vid_cap_mplane = isp_vidioc_set_format_mplane, + .vidioc_try_fmt_vid_cap_mplane = isp_vidioc_try_format_mplane, .vidioc_enum_framesizes = isp_vidioc_enum_framesizes, .vidioc_enum_frameintervals = isp_vidioc_enum_frameintervals, @@ -1035,7 +969,7 @@ static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_streamon = vb2_ioctl_streamon, .vidioc_streamoff = vb2_ioctl_streamoff, - .vidioc_log_status = isp_vidioc_log_status, + .vidioc_log_status = v4l2_ctrl_log_status, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; @@ -1067,7 +1001,6 @@ static void apple_isp_release(struct v4l2_device *v4l2_dev) v4l2_ctrl_handler_free(&isp->ctrl_handler); media_device_cleanup(&isp->mdev); - kfree(isp->sensor_configs); kfree(isp); } @@ -1086,22 +1019,6 @@ static void isp_free_meta_surfaces(struct apple_isp *isp) } } -/* - * vb2 allocates at least min_queued_buffers + 1 buffers, and the queue may - * hold no more than the rendered pool (isp_vb2_queue_setup()). The P010 pool - * has two slots, so a P010 stream starts with one queued buffer; otherwise - * REQBUFS can never get the three buffers vb2 asks for and fails with ENOMEM. - * Only called while the queue has no buffers. - */ -static void isp_vb2_set_min_buffers(struct apple_isp *isp) -{ - struct vb2_queue *vbq = &isp->vbq; - - vbq->min_queued_buffers = min_t(unsigned int, ISP_MIN_FRAMES, - isp_num_rendered(isp) - 1); - vbq->min_reqbufs_allocation = vbq->min_queued_buffers + 1; -} - static int isp_vb2_queue_init(struct apple_isp *isp) { struct vb2_queue *vbq = &isp->vbq; @@ -1120,53 +1037,6 @@ static int isp_vb2_queue_init(struct apple_isp *isp) return vb2_queue_init(vbq); } -static int isp_g_volatile_ctrl(struct v4l2_ctrl *ctrl) -{ - struct apple_isp *isp = - container_of(ctrl->handler, struct apple_isp, ctrl_handler); - - switch (ctrl->id) { - case V4L2_CID_GAIN: - ctrl->val = READ_ONCE(isp->frame_gain); - return 0; - case V4L2_CID_EXPOSURE_ABSOLUTE: - /* in 100 us units */ - ctrl->val = DIV_ROUND_CLOSEST(READ_ONCE(isp->frame_exposure_us), - 100); - return 0; - } - - return -EINVAL; -} - -static const struct v4l2_ctrl_ops isp_exposure_ctrl_ops = { - .g_volatile_ctrl = isp_g_volatile_ctrl, -}; - -/* - * Auto exposure picks the gain and exposure of each frame. Report the last - * frame's values read-only, so that userspace can follow the scene light, for - * example to decide when to denoise. The gain is the firmware's total sensor - * gain in 1/256 steps. - */ -static void isp_init_exposure_ctrls(struct apple_isp *isp) -{ - isp->frame_gain = 256; - isp->gain = v4l2_ctrl_new_std(&isp->ctrl_handler, - &isp_exposure_ctrl_ops, V4L2_CID_GAIN, - 0, U16_MAX, 1, 256); - isp->exposure = v4l2_ctrl_new_std(&isp->ctrl_handler, - &isp_exposure_ctrl_ops, - V4L2_CID_EXPOSURE_ABSOLUTE, - 0, 10000, 1, 0); - if (isp->gain) - isp->gain->flags |= V4L2_CTRL_FLAG_READ_ONLY | - V4L2_CTRL_FLAG_VOLATILE; - if (isp->exposure) - isp->exposure->flags |= V4L2_CTRL_FLAG_READ_ONLY | - V4L2_CTRL_FLAG_VOLATILE; -} - int apple_isp_setup_video(struct apple_isp *isp) { struct video_device *vdev = &isp->vdev; @@ -1174,7 +1044,7 @@ int apple_isp_setup_video(struct apple_isp *isp) struct isp_format *fmt = isp_get_current_format(isp); int err; - err = isp_set_preset(isp, fmt, &isp->presets[0], V4L2_PIX_FMT_NV12); + err = isp_set_preset(isp, fmt, &isp->presets[0]); if (err) { dev_err(isp->dev, "failed to set default preset: %d\n", err); return err; @@ -1184,8 +1054,7 @@ int apple_isp_setup_video(struct apple_isp *isp) for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { isp->meta_surfs[i] = - isp_alloc_surface_vmap(isp, isp_has_p010(isp) ? 0x8000 : - isp->hw->meta_size); + isp_alloc_surface_vmap(isp, isp->hw->meta_size); if (!isp->meta_surfs[i]) { isp_err(isp, "failed to alloc meta surface\n"); err = -ENOMEM; @@ -1217,28 +1086,16 @@ int apple_isp_setup_video(struct apple_isp *isp) goto media_cleanup; } + isp->multiplanar = multiplanar; + /* * Auto exposure is always on. By default it holds the frame rate, as * V4L2 specifies; with exposure priority it may slow down in low light. */ - v4l2_ctrl_handler_init(&isp->ctrl_handler, 4); + v4l2_ctrl_handler_init(&isp->ctrl_handler, 1); isp->exposure_priority = v4l2_ctrl_new_std(&isp->ctrl_handler, NULL, V4L2_CID_EXPOSURE_AUTO_PRIORITY, 0, 1, 1, 0); - /* - * The H17 firmware's auto exposure avoids mains flicker at the given - * frequency. 50 Hz is the default, as on macOS when it does not know - * the country; userspace sets 60 Hz where the mains runs at 60 Hz. - */ - if (isp->hw->fw_abi == ISP_FW_ABI_H17) - isp->power_line_frequency = - v4l2_ctrl_new_std_menu(&isp->ctrl_handler, NULL, - V4L2_CID_POWER_LINE_FREQUENCY, - V4L2_CID_POWER_LINE_FREQUENCY_60HZ, - BIT(V4L2_CID_POWER_LINE_FREQUENCY_DISABLED), - V4L2_CID_POWER_LINE_FREQUENCY_50HZ); - if (isp->hw->meta_exposure) - isp_init_exposure_ctrls(isp); if (isp->ctrl_handler.error) { err = isp->ctrl_handler.error; dev_err(isp->dev, "failed to create controls: %d\n", err); @@ -1262,6 +1119,8 @@ int apple_isp_setup_video(struct apple_isp *isp) vdev->fops = &isp_v4l2_fops; vdev->ioctl_ops = &isp_v4l2_ioctl_ops; vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + if (isp->multiplanar) + vdev->device_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE; vdev->entity.flags = MEDIA_ENT_FL_DEFAULT; vdev->v4l2_dev = &isp->v4l2_dev; vdev->vfl_type = VFL_TYPE_VIDEO; From ba71c00448458c575a06fc55e12de12a7eeae732 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sat, 31 Oct 2026 17:17:49 +1100 Subject: [PATCH 2/4] media: apple: isp: report the frame gain and exposure Userspace has no way to tell how hard the sensor is working. Gain is the signal a camera pipeline needs to decide when the picture is noisy enough to denoise, and the firmware already writes it into every frame's metadata buffer, which the driver maps but never reads. The metadata buffer opens with a table of section offsets. On H17 firmware the fourth section starts with the frame's exposure: the integration time in microseconds, then the total sensor gain in 1/256 steps with 256 as unity. This was established on T6040 and T8140 by watching auto exposure converge on a fixed scene, where the gain climbed from 260 to its final value once the integration time hit the frame period, and by stepping the frame rate, which capped the integration time at the frame period. Read the section when a metadata buffer comes back, through the table and bounded by the buffer size, and report the last values as read-only, volatile V4L2_CID_GAIN and V4L2_CID_EXPOSURE_ABSOLUTE controls. Only hardware whose match data says its metadata carries the report gets the controls. (cherry picked from commit 3c5441114af9f9e6f93fadf94710269b27676b54) Port to integration/aurora: only T8140 exists on this base; omit the T6040/P010 dependencies. Keep T8122 metadata reporting disabled because the 26.6.2 layout and units are unqualified. Bound the table-entry read as well as its target and test short headers and control admission. Signed-off-by: Ryan Murray Signed-off-by: Satya Benson --- drivers/media/platform/apple/isp/isp-drv.c | 2 + drivers/media/platform/apple/isp/isp-drv.h | 9 ++ .../media/platform/apple/isp/isp-v4l2-test.c | 78 +++++++++++++++ drivers/media/platform/apple/isp/isp-v4l2.c | 95 ++++++++++++++++++- 4 files changed, 183 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 2cbdcd2fb00bd..9572f820e9641 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -685,6 +685,7 @@ static const struct apple_isp_hw apple_isp_hw_t6020 = { .meta_size = ISP_META_SIZE_T8112, }; +/* T8122/26.6.2 metadata layout and units are not qualified: no gain control. */ static const struct apple_isp_hw apple_isp_hw_t8122 = { .gen = ISP_GEN_T8112, .pmu_base = 0x2d0704000, @@ -799,6 +800,7 @@ static const struct apple_isp_hw apple_isp_hw_t8140 = { .boot_mode = 1, .capture_meta_size = ISP_CAPTURE_META_SIZE_T8140, .resident_fw = true, + .meta_exposure = true, }; static const struct of_device_id apple_isp_of_match[] = { diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 8f945c26f00e6..3e4b94f744c36 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -189,6 +189,9 @@ struct apple_isp_hw { /* size of a capture metadata buffer, 0 for no such pool */ u32 capture_meta_size; + /* metadata buffers carry the frame's exposure report (H17 firmware) */ + bool meta_exposure; + /* * The firmware can be started only once per system boot, so it is * started at probe and kept running while the driver is bound. @@ -276,6 +279,12 @@ struct apple_isp { struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; struct v4l2_ctrl *exposure_priority; + /* read-only exposure report of the last frame, NULL without one */ + struct v4l2_ctrl *gain; + struct v4l2_ctrl *exposure; + /* last reported total gain (1/256 steps) and exposure (us) */ + u32 frame_gain; + u32 frame_exposure_us; struct vb2_queue vbq; struct mutex video_lock; unsigned int sequence; diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index 895a6d1299c22..b954dac335736 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -107,7 +107,85 @@ static void isp_test_create_bufs_limit(struct kunit *test) sizes), -EINVAL); } +/* The exposure report is found through the section table, never past it. */ +static void isp_test_meta_exposure(struct kunit *test) +{ + static const struct apple_isp_hw hw = { .meta_exposure = true }; + static const struct apple_isp_hw hw_none = { }; + struct apple_isp *isp = isp_test_alloc(test); + struct isp_surf meta = { .size = 0x2000 }; + u8 *virt = kunit_kzalloc(test, meta.size, GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, virt); + meta.virt = virt; + isp->hw = &hw; + isp->frame_gain = 256; + + /* fourth table entry: the section at 0x1870 */ + put_unaligned_le32(0x1870, virt + 0x1c); + put_unaligned_le32(32475, virt + 0x1870); + put_unaligned_le16(0x0800, virt + 0x1874); + put_unaligned_le16(3253, virt + 0x1876); + isp_meta_read_exposure(isp, &meta); + KUNIT_EXPECT_EQ(test, isp->frame_exposure_us, 32475); + KUNIT_EXPECT_EQ(test, isp->frame_gain, 3253); + + /* A short header cannot even supply the section-table entry. */ + put_unaligned_le16(700, virt + 0x1876); + meta.size = 0x1f; + isp_meta_read_exposure(isp, &meta); + KUNIT_EXPECT_EQ(test, isp->frame_gain, 3253); + meta.size = 0x2000; + put_unaligned_le16(3253, virt + 0x1876); + + /* a section that would end past the buffer is ignored */ + put_unaligned_le32(meta.size - 4, virt + 0x1c); + put_unaligned_le32(1000, virt + meta.size - 4); + isp_meta_read_exposure(isp, &meta); + KUNIT_EXPECT_EQ(test, isp->frame_gain, 3253); + + /* so is an unwritten section */ + put_unaligned_le32(0x1000, virt + 0x1c); + isp_meta_read_exposure(isp, &meta); + KUNIT_EXPECT_EQ(test, isp->frame_exposure_us, 32475); + + /* and firmware without the report */ + put_unaligned_le32(0x1870, virt + 0x1c); + put_unaligned_le16(600, virt + 0x1876); + isp->hw = &hw_none; + isp_meta_read_exposure(isp, &meta); + KUNIT_EXPECT_EQ(test, isp->frame_gain, 3253); + isp->hw = &hw; + isp_meta_read_exposure(isp, &meta); + KUNIT_EXPECT_EQ(test, isp->frame_gain, 600); +} + +static void isp_test_exposure_control_gate(struct kunit *test) +{ + static const struct apple_isp_hw hw_none = { }; + static const struct apple_isp_hw hw = { .meta_exposure = true }; + struct apple_isp *isp = isp_test_alloc(test); + + v4l2_ctrl_handler_init(&isp->ctrl_handler, 3); + isp->hw = &hw_none; + isp_init_exposure_ctrls(isp); + KUNIT_EXPECT_PTR_EQ(test, isp->gain, NULL); + KUNIT_EXPECT_PTR_EQ(test, isp->exposure, NULL); + isp->hw = &hw; + isp_init_exposure_ctrls(isp); + KUNIT_EXPECT_EQ(test, isp->ctrl_handler.error, 0); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, isp->gain); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, isp->exposure); + if (isp->gain) + KUNIT_EXPECT_EQ(test, isp->gain->flags & + (V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE), + (u32)(V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE)); + v4l2_ctrl_handler_free(&isp->ctrl_handler); +} + static struct kunit_case isp_v4l2_test_cases[] = { + KUNIT_CASE(isp_test_meta_exposure), + KUNIT_CASE(isp_test_exposure_control_gate), KUNIT_CASE(isp_test_queue_init), KUNIT_CASE(isp_test_reqbufs_limit), KUNIT_CASE(isp_test_create_bufs_limit), diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 072b8523279c2..41ae504b82dc7 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -57,6 +58,46 @@ static_assert(ISP_CMD_AREA_SIZE(0) >= ISP_IPC_BUFEXC_STAT_SIZE); */ #define ISP_BUFLIST_POOL_CAPTURE_META 2 +/* + * A metadata buffer starts with a table of section offsets. On H17 firmware + * the fourth section opens with the frame's exposure: the integration time in + * microseconds (le32 at 0) and the total sensor gain in 1/256 steps, 256 for + * unity (le16 at 6). Observed on T6040 and T8140 with AE converging under a + * fixed scene and with the frame rate capping the integration time. + */ +#define ISP_META_SECTIONS 0x10 +#define ISP_META_SECTION_EXPOSURE 3 +#define ISP_META_EXPOSURE_TIME 0x0 +#define ISP_META_EXPOSURE_GAIN 0x6 +#define ISP_META_EXPOSURE_SIZE 0x8 + +static void isp_meta_read_exposure(struct apple_isp *isp, + const struct isp_surf *meta) +{ + const u8 *virt = meta->virt; + u32 offset, time; + u16 gain; + + if (!isp->hw->meta_exposure || !virt || + meta->size < ISP_META_SECTIONS + 4 * (ISP_META_SECTION_EXPOSURE + 1)) + return; + + offset = get_unaligned_le32(virt + ISP_META_SECTIONS + + 4 * ISP_META_SECTION_EXPOSURE); + if (offset < ISP_META_SECTIONS || offset > meta->size || + meta->size - offset < ISP_META_EXPOSURE_SIZE) + return; + + time = get_unaligned_le32(virt + offset + ISP_META_EXPOSURE_TIME); + gain = get_unaligned_le16(virt + offset + ISP_META_EXPOSURE_GAIN); + /* An unwritten section or one this layout does not describe */ + if (!time || !gain) + return; + + WRITE_ONCE(isp->frame_exposure_us, time); + WRITE_ONCE(isp->frame_gain, gain); +} + int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) { struct isp_message *req = &chan->req, *rsp = &chan->rsp; @@ -100,6 +141,7 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) struct isp_surf *meta = isp->meta_surfs[j]; if ((u32)bufd->iovas[0] == (u32)meta->iova) { WARN_ON(!meta->submitted); + isp_meta_read_exposure(isp, meta); meta->submitted = false; } } @@ -1037,6 +1079,56 @@ static int isp_vb2_queue_init(struct apple_isp *isp) return vb2_queue_init(vbq); } +static int isp_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct apple_isp *isp = + container_of(ctrl->handler, struct apple_isp, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_GAIN: + ctrl->val = READ_ONCE(isp->frame_gain); + return 0; + case V4L2_CID_EXPOSURE_ABSOLUTE: + /* in 100 us units */ + ctrl->val = DIV_ROUND_CLOSEST(READ_ONCE(isp->frame_exposure_us), + 100); + return 0; + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops isp_exposure_ctrl_ops = { + .g_volatile_ctrl = isp_g_volatile_ctrl, +}; + +/* + * Auto exposure picks the gain and exposure of each frame. Report the last + * frame's values read-only, so that userspace can follow the scene light, for + * example to decide when to denoise. The gain is the firmware's total sensor + * gain in 1/256 steps. + */ +static void isp_init_exposure_ctrls(struct apple_isp *isp) +{ + if (!isp->hw->meta_exposure) + return; + + isp->frame_gain = 256; + isp->gain = v4l2_ctrl_new_std(&isp->ctrl_handler, + &isp_exposure_ctrl_ops, V4L2_CID_GAIN, + 0, U16_MAX, 1, 256); + isp->exposure = v4l2_ctrl_new_std(&isp->ctrl_handler, + &isp_exposure_ctrl_ops, + V4L2_CID_EXPOSURE_ABSOLUTE, + 0, 10000, 1, 0); + if (isp->gain) + isp->gain->flags |= V4L2_CTRL_FLAG_READ_ONLY | + V4L2_CTRL_FLAG_VOLATILE; + if (isp->exposure) + isp->exposure->flags |= V4L2_CTRL_FLAG_READ_ONLY | + V4L2_CTRL_FLAG_VOLATILE; +} + int apple_isp_setup_video(struct apple_isp *isp) { struct video_device *vdev = &isp->vdev; @@ -1092,10 +1184,11 @@ int apple_isp_setup_video(struct apple_isp *isp) * Auto exposure is always on. By default it holds the frame rate, as * V4L2 specifies; with exposure priority it may slow down in low light. */ - v4l2_ctrl_handler_init(&isp->ctrl_handler, 1); + v4l2_ctrl_handler_init(&isp->ctrl_handler, 3); isp->exposure_priority = v4l2_ctrl_new_std(&isp->ctrl_handler, NULL, V4L2_CID_EXPOSURE_AUTO_PRIORITY, 0, 1, 1, 0); + isp_init_exposure_ctrls(isp); if (isp->ctrl_handler.error) { err = isp->ctrl_handler.error; dev_err(isp->dev, "failed to create controls: %d\n", err); From d83a42af99db6e4f05fbcc3dfacde6aba6894f2b Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 21:34:18 -0500 Subject: [PATCH 3/4] media: apple: isp: compose metadata bounds with Aurora capture support Retain the existing T6040/P010 path and frame gain/exposure reporting. Carry only the additional section-table bound and regression coverage from the preceding unchanged public commit into the final source tree. Signed-off-by: Satya Benson --- drivers/media/platform/apple/isp/isp-drv.c | 169 ++++++-- drivers/media/platform/apple/isp/isp-drv.h | 92 ++++- .../media/platform/apple/isp/isp-v4l2-test.c | 249 +++++++++++- drivers/media/platform/apple/isp/isp-v4l2.c | 379 ++++++++++-------- 4 files changed, 676 insertions(+), 213 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 9572f820e9641..93a21e901ff6a 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -144,17 +144,44 @@ static int apple_isp_map_fw_mmio(struct apple_isp *isp) return 0; } +/* The IOVA range of a memory-region, from its last iommu-addresses entry. */ +static int isp_of_region_iova(struct device *dev, const char *name, + phys_addr_t *base, size_t *size) +{ + const __be32 *maps, *end; + int idx, len; + + idx = of_property_match_string(dev->of_node, "memory-region-names", + name); + if (idx < 0) + return -ENOENT; + + struct device_node *np __free(device_node) = + of_parse_phandle(dev->of_node, "memory-region", idx); + if (!np) + return -ENODEV; + + maps = of_get_property(np, "iommu-addresses", &len); + if (!maps || !len) + return -ENODEV; + + end = maps + len / sizeof(__be32); + + while (maps < end) { + maps++; + maps = of_translate_dma_region(dev->of_node, maps, base, size); + } + + return 0; +} + static int apple_isp_init_iommu(struct apple_isp *isp) { struct device *dev = isp->dev; - phys_addr_t heap_base; - size_t heap_size; + phys_addr_t heap_base, data_base; + size_t heap_size, data_size; u64 vm_size; int err; - int idx; - int size; - struct device_node *mem_node; - const __be32 *maps, *end; isp->domain = iommu_get_domain_for_dev(isp->dev); if (!isp->domain) @@ -162,30 +189,38 @@ static int apple_isp_init_iommu(struct apple_isp *isp) isp->shift = __ffs(isp->domain->pgsize_bitmap); isp->fw_iova_mask = isp->hw->fw_iova_mask ?: U64_MAX; - idx = of_property_match_string(dev->of_node, "memory-region-names", - "heap"); - mem_node = of_parse_phandle(dev->of_node, "memory-region", idx); - if (!mem_node) { - dev_err(dev, "No memory-region found for heap\n"); - return -ENODEV; - } - - maps = of_get_property(mem_node, "iommu-addresses", &size); - if (!maps || !size) { + err = isp_of_region_iova(dev, "heap", &heap_base, &heap_size); + if (err) { dev_err(dev, "No valid iommu-addresses found for heap\n"); return -ENODEV; } - end = maps + size / sizeof(__be32); + isp->fw.heap_base = heap_base; + isp->fw.heap_top = heap_base + heap_size; - while (maps < end) { - maps++; - maps = of_translate_dma_region(dev->of_node, maps, &heap_base, - &heap_size); + /* + * Where the bootloader loads the firmware's data, it backs the + * firmware's main memory only up to the end of it. The rest, up to + * the heap top, is the host's to back. + */ + err = -ENOENT; + if (isp->hw->gen == ISP_GEN_T6040) + err = isp_of_region_iova(dev, "data", &data_base, &data_size); + if (!err) { + isp->fw.data_top = data_base + data_size; + if (isp->fw.data_top <= isp->fw.heap_base || + isp->fw.data_top > isp->fw.heap_top || + !IS_ALIGNED(isp->fw.data_top, 1UL << isp->shift)) { + dev_err(dev, "data 0x%llx outside the heap 0x%llx-0x%llx\n", + isp->fw.data_top, isp->fw.heap_base, + isp->fw.heap_top); + return -EINVAL; + } + } else if (err != -ENOENT) { + dev_err(dev, "No valid iommu-addresses found for data\n"); + return -ENODEV; } - isp->fw.heap_top = heap_base + heap_size; - err = of_property_read_u64(dev->of_node, "apple,dart-vm-size", &vm_size); if (err) { @@ -195,11 +230,11 @@ static int apple_isp_init_iommu(struct apple_isp *isp) // FIXME: refactor this, maybe use regular iova stuff? isp->iova_size = vm_size - (heap_base & 0xffffffff); - drm_mm_init(&isp->iovad, isp->fw.heap_top, isp->iova_size); + mt_init_flags(&isp->iovas, MT_FLAGS_ALLOC_RANGE); err = apple_isp_map_fw_mmio(isp); if (err) { - drm_mm_takedown(&isp->iovad); + mtree_destroy(&isp->iovas); return err; } @@ -209,7 +244,8 @@ static int apple_isp_init_iommu(struct apple_isp *isp) static void apple_isp_free_iommu(struct apple_isp *isp) { apple_isp_unmap_fw_mmio(isp, isp->hw->num_fw_mmio); - drm_mm_takedown(&isp->iovad); + WARN_ON(!mtree_empty(&isp->iovas)); + mtree_destroy(&isp->iovas); } static int isp_of_read_coord(struct device *dev, struct device_node *np, @@ -421,18 +457,9 @@ static int apple_isp_probe(struct platform_device *pdev) if (err) isp->temporal_filter = 0; - /* - * M3 MacBook Air firmware 14.7 stalls capture with temporal filter 1. - * The J615 has the J613's sensor and ISP configuration; only the J613 - * has been tested. - */ - if ((of_machine_is_compatible("apple,j613") || - of_machine_is_compatible("apple,j615")) && - isp->fw_compat == ISP_FIRMWARE_V_14_7 && - isp->temporal_filter == 1) { - dev_info(dev, "using temporal filter 0 on M3 Air firmware 14.7\n"); - isp->temporal_filter = 0; - } + /* the sensor connection layout, for firmware that takes it at boot */ + of_property_read_u32(dev->of_node, "apple,cam-connections-scheme", + &isp->cam_connections_scheme); err = apple_isp_init_presets(isp); if (err) { @@ -503,7 +530,6 @@ static int apple_isp_probe(struct platform_device *pdev) goto detach_genpd; } - mutex_init(&isp->iovad_lock); mutex_init(&isp->video_lock); spin_lock_init(&isp->buf_lock); init_waitqueue_head(&isp->wait); @@ -512,6 +538,7 @@ static int apple_isp_probe(struct platform_device *pdev) mutex_init(&isp->gc_lock); INIT_LIST_HEAD(&isp->bufs_pending); INIT_LIST_HEAD(&isp->bufs_submitted); + INIT_LIST_HEAD(&isp->bufs_returned); isp->wq = alloc_workqueue("apple-isp-wq", WQ_UNBOUND, 0); if (!isp->wq) { dev_err(dev, "failed to create workqueue\n"); @@ -559,6 +586,7 @@ static int apple_isp_probe(struct platform_device *pdev) detach_genpd: apple_isp_detach_genpd(isp); free_isp: + kfree(isp->sensor_configs); /* Nothing else can reach it before the video device is registered. */ kfree(isp); return err; @@ -685,10 +713,9 @@ static const struct apple_isp_hw apple_isp_hw_t6020 = { .meta_size = ISP_META_SIZE_T8112, }; -/* T8122/26.6.2 metadata layout and units are not qualified: no gain control. */ static const struct apple_isp_hw apple_isp_hw_t8122 = { .gen = ISP_GEN_T8112, - .pmu_base = 0x2d0704000, + .pmu_base = 0x2d07040000, .dsid_count = 1, .dsid_clr_base0 = 0x2221c4000, @@ -803,6 +830,65 @@ static const struct apple_isp_hw apple_isp_hw_t8140 = { .meta_exposure = true, }; +/* The same for T6040, from the J616s ADT's DART address filter. */ +static const struct isp_mmio_window apple_isp_fw_mmio_t6040[] = { + { 0x220004000, 0x1d4000 }, /* DSID broadcast-clear windows */ + { 0x222004000, 0x1d4000 }, + { 0x224004000, 0x1d4000 }, + { 0x226004000, 0x1d4000 }, + { 0x485660000, 0x4000 }, + { 0x5003c0000, 0x28000 }, /* PMGR, with the PMP clock scratch */ + { 0x500e3c000, 0x4000 }, + { 0x502284000, 0x4000 }, /* PMGR, ISP power states */ + { 0x502880000, 0x8000 }, + { 0x504824000, 0x4000 }, /* PMP bandwidth scratch */ + { 0x51062c000, 0x20000 }, +}; + +/* + * T6040 runs the same coprocessor and firmware interface as T8140, booted + * as macOS boots it on the J616s (macOS 26.6.2 camera log, kext register + * map) and as aurora's m1n1 proxy captures with it: no coprocessor reset, + * four broadcast-clear windows in eight slots, the scaler 1 output path and + * no capture metadata pool or watchdog. + */ +static const struct apple_isp_hw apple_isp_hw_t6040 = { + .gen = ISP_GEN_T6040, + .fw_abi = ISP_FW_ABI_H17, + .pmu_base = 0x0, + + .dsid_count = 4, + .dsid_slots = 8, + .dsid_clr_base0 = 0x2201d4000, + .dsid_clr_base1 = 0x2221d4000, + .dsid_clr_base2 = 0x2241d4000, + .dsid_clr_base3 = 0x2261d4000, + .dsid_clr_range0 = 0x2fc, + + /* the PMGR and PMP scratch words of the ISP's DAPF windows */ + .clock_scratch = 0x5003d0fa8, + .clock_base = 0x0, + .clock_bit = 0x0, + .clock_size = 0x8, + .bandwidth_scratch = 0x504824000, + .bandwidth_base = 0x0, + .bandwidth_bit = 0x0, + .bandwidth_size = 0x8, + .mbox_irq_enable = ISP_MBOX_IRQ_ENABLE_T6031, + + .scl1 = true, + .lpdp = false, + .meta_size = ISP_META_SIZE_T8140, + .fw_mmio = apple_isp_fw_mmio_t6040, + .num_fw_mmio = ARRAY_SIZE(apple_isp_fw_mmio_t6040), + + .coproc_control = ISP_COPROC_CONTROL_T8140, + .boot_mode = 1, + .shared_size = 0xde600000, + .resident_fw = true, + .meta_exposure = true, +}; + static const struct of_device_id apple_isp_of_match[] = { { .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 }, { .compatible = "apple,t8112-isp", .data = &apple_isp_hw_t8112 }, @@ -812,6 +898,7 @@ static const struct of_device_id apple_isp_of_match[] = { { .compatible = "apple,t6030-isp", .data = &apple_isp_hw_t6030 }, { .compatible = "apple,t6031-isp", .data = &apple_isp_hw_t6031 }, { .compatible = "apple,t8140-isp", .data = &apple_isp_hw_t8140 }, + { .compatible = "apple,t6041-isp", .data = &apple_isp_hw_t6040 }, {}, }; MODULE_DEVICE_TABLE(of, apple_isp_of_match); diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 3e4b94f744c36..30425f6cf483d 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -6,11 +6,11 @@ #include #include +#include #include #include #include -#include #include #include #include @@ -30,6 +30,8 @@ /* used to limit the user space buffers to the buffer_pool_config */ #define ISP_MAX_BUFFERS 16 +#define ISP_MAX_SENSOR_CONFIGS 64 +#define ISP_SENSOR_CONFIG_SIZE 0x120 /* capture metadata buffers, for firmware that has that pool */ #define ISP_CAPMETA_BUFFERS 8 @@ -51,6 +53,7 @@ enum isp_generation { ISP_GEN_T8112, ISP_GEN_T6031, ISP_GEN_T8140, + ISP_GEN_T6040, }; /* @@ -79,7 +82,6 @@ enum isp_firmware_version { }; struct isp_surf { - struct drm_mm_node *mm; struct list_head head; u64 size; u64 type; @@ -91,6 +93,7 @@ struct isp_surf { refcount_t refcount; bool gc; bool submitted; + bool iova_reserved; /* @iova comes from &apple_isp.iovas */ }; struct isp_message { @@ -115,6 +118,8 @@ struct isp_channel { void *virt; u32 doorbell; u32 cursor; + unsigned long deadline; + bool deadline_active; struct mutex lock; struct isp_message req; struct isp_message rsp; @@ -138,6 +143,8 @@ struct isp_preset { struct coord output_dim; struct coord crop_offset; struct coord crop_size; + /* Processed P010 geometry established by preview or a config reply. */ + bool p010; }; struct apple_isp_hw { @@ -146,6 +153,8 @@ struct apple_isp_hw { u64 pmu_base; int dsid_count; + /* windows of the H17 multi broadcast-clear command, the unused all ones */ + u8 dsid_slots; u64 dsid_clr_base0; u64 dsid_clr_base1; u64 dsid_clr_base2; @@ -186,6 +195,9 @@ struct apple_isp_hw { /* ISP_GPIO_6 boot mode */ u32 boot_mode; + /* shared memory size in the H16 boot descriptor, 0 for the T8140 one */ + u64 shared_size; + /* size of a capture metadata buffer, 0 for no such pool */ u32 capture_meta_size; @@ -247,13 +259,23 @@ enum isp_sensor_id { ISP_IMX814_2123_01, ISP_IMX853_7622_01, ISP_IMX913_7523_01, + ISP_IMX958_2224_01, ISP_VD56G0_6221_01, ISP_VD56G0_6222_01, }; +struct isp_sensor_config { + struct coord input_dim; + struct coord output_dim; + u32 sensor_clk; + bool usable; + u8 reply[ISP_SENSOR_CONFIG_SIZE]; +}; + struct isp_format { enum isp_sensor_id id; u32 version; + u32 pixelformat; struct isp_preset *preset; unsigned int num_planes; u32 strides[VB2_MAX_PLANES]; @@ -267,8 +289,12 @@ struct apple_isp { enum isp_firmware_version fw_compat; u32 platform_id; u32 temporal_filter; + u32 cam_connections_scheme; struct isp_preset *presets; int num_presets; + /* Read-only sensor configuration replies, cached before video registration. */ + unsigned int num_sensor_configs; + struct isp_sensor_config *sensor_configs; int num_channels; struct isp_format fmts[ISP_MAX_CHANNELS]; @@ -279,6 +305,7 @@ struct apple_isp { struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; struct v4l2_ctrl *exposure_priority; + struct v4l2_ctrl *power_line_frequency; /* read-only exposure report of the last frame, NULL without one */ struct v4l2_ctrl *gain; struct v4l2_ctrl *exposure; @@ -288,7 +315,6 @@ struct apple_isp { struct vb2_queue vbq; struct mutex video_lock; unsigned int sequence; - bool multiplanar; /* capture rate in frames per second, changes under video_lock */ unsigned int frame_rate; @@ -309,13 +335,15 @@ struct apple_isp { struct iommu_domain *domain; unsigned long shift; - struct drm_mm iovad; /* TODO iova.c can't allocate bottom-up */ - u64 iova_size; /* size of the iovad range */ + /* allocated IOVA ranges, in IOMMU pages, lowest free range first */ + struct maple_tree iovas; + u64 iova_size; /* size of the range @iovas allocates from */ u64 fw_iova_mask; /* see isp_fw_iova() */ - struct mutex iovad_lock; struct isp_firmware { + u64 heap_base; u64 heap_top; + u64 data_top; /* 0 unless the bootloader loads the data */ } fw; /* changes under video_lock once the video device is registered */ enum isp_fw_state fw_state; @@ -323,6 +351,7 @@ struct apple_isp { struct isp_surf *ipc_surf; struct isp_surf *extra_surf; struct isp_surf *data_surf; + struct isp_surf *main_tail_surf; struct isp_surf *log_surf; struct isp_surf *bt_surf; struct isp_surf *meta_surfs[ISP_MAX_BUFFERS]; @@ -356,16 +385,35 @@ struct apple_isp { spinlock_t buf_lock; struct list_head bufs_pending; struct list_head bufs_submitted; + struct list_head bufs_returned; + u64 buffer_tag; + unsigned long meta_returned; + /* + * The channel of a timed-out request that has not been answered yet, + * see isp_ipc_resync(). Sends are serialized by the callers, which + * share the command area. + */ + struct isp_channel *ipc_stale; + /* + * Not even a late answer came, so where the firmware is in the ring + * is unknown. Nothing is sent until the firmware is stopped, and + * resident firmware cannot be started again before the next boot. + */ + bool ipc_failed; }; struct isp_chan_ops { int (*handle)(struct apple_isp *isp, struct isp_channel *chan); + /* Called after the response slot and its doorbell are published. */ + void (*complete)(struct apple_isp *isp); }; struct isp_buffer { struct vb2_v4l2_buffer vb; struct list_head link; struct isp_surf surfs[VB2_MAX_PLANES]; + u64 tag; + enum vb2_buffer_state return_state; }; #define to_isp_buffer(x) container_of((x), struct isp_buffer, vb) @@ -393,4 +441,36 @@ enum { ((isp)->hw->capture_meta_size ? ISP_CAPMETA_BUFFERS : 0) #define isp_get_current_format(isp) (isp_get_format(isp, isp->current_ch)) +static inline bool isp_supports_p010(struct apple_isp *isp) +{ + return isp->hw->gen == ISP_GEN_T6040 && + isp_get_current_format(isp)->version == 0x958; +} + +static inline bool isp_has_p010(struct apple_isp *isp) +{ + if (!isp_supports_p010(isp)) + return false; + for (int i = 0; i < isp->num_presets; i++) { + if (isp->presets[i].p010) + return true; + } + return false; +} + +static inline bool isp_is_p010(struct apple_isp *isp) +{ + return isp_get_current_format(isp)->pixelformat == V4L2_PIX_FMT_P010; +} + +static inline unsigned int isp_num_rendered(struct apple_isp *isp) +{ + return isp_is_p010(isp) ? 2 : ISP_MAX_BUFFERS; +} + +static inline unsigned int isp_num_meta(struct apple_isp *isp) +{ + return isp_is_p010(isp) ? 8 : ISP_MAX_BUFFERS; +} + #endif /* __ISP_DRV_H__ */ diff --git a/drivers/media/platform/apple/isp/isp-v4l2-test.c b/drivers/media/platform/apple/isp/isp-v4l2-test.c index b954dac335736..ee38dc9ce7d21 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2-test.c +++ b/drivers/media/platform/apple/isp/isp-v4l2-test.c @@ -7,6 +7,7 @@ #include #include +#include #include #define ISP_TEST_FRAME_SIZE 0x1000 @@ -19,6 +20,8 @@ static struct apple_isp *isp_test_alloc(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, isp); mutex_init(&isp->video_lock); + spin_lock_init(&isp->buf_lock); + INIT_LIST_HEAD(&isp->bufs_returned); isp_get_current_format(isp)->total_size = ISP_TEST_FRAME_SIZE; return isp; @@ -34,6 +37,8 @@ static void isp_test_queue_init(struct kunit *test) KUNIT_ASSERT_EQ(test, isp_vb2_queue_init(isp), 0); KUNIT_EXPECT_GE(test, isp->vbq.max_num_buffers, VB2_MAX_FRAME); + KUNIT_EXPECT_EQ(test, isp->vbq.type, V4L2_BUF_TYPE_VIDEO_CAPTURE); + KUNIT_EXPECT_FALSE(test, isp->vbq.is_multiplanar); vb2_queue_release(&isp->vbq); } @@ -99,6 +104,12 @@ static void isp_test_create_bufs_limit(struct kunit *test) KUNIT_EXPECT_EQ(test, isp_test_setup(test, ISP_MAX_BUFFERS, &nbuffers, &num_planes, sizes), -ENOBUFS); + /* The contiguous API must refuse a second independent memory plane. */ + nbuffers = 1; + num_planes = 2; + KUNIT_EXPECT_EQ(test, isp_test_setup(test, 0, &nbuffers, &num_planes, + sizes), -EINVAL); + /* A plane smaller than a frame is still refused. */ nbuffers = 1; num_planes = 1; @@ -107,6 +118,236 @@ static void isp_test_create_bufs_limit(struct kunit *test) sizes), -EINVAL); } +static const struct apple_isp_hw isp_test_t6040 = { + .gen = ISP_GEN_T6040, + .fw_abi = ISP_FW_ABI_H17, +}; + +static void isp_test_p010_format(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_preset preset = { .output_dim = { 1920, 1080 }, .p010 = true }; + struct v4l2_format f = {}; + unsigned int sizes[VB2_MAX_PLANES] = {}; + unsigned int nbuffers = 16, num_planes = 0; + + isp->hw = &isp_test_t6040; + fmt->version = 0x958; + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); + isp_get_sp_pix_format(isp, &f, fmt); + KUNIT_EXPECT_EQ(test, f.fmt.pix.pixelformat, V4L2_PIX_FMT_P010); + KUNIT_EXPECT_EQ(test, f.fmt.pix.bytesperline, 3840); + KUNIT_EXPECT_EQ(test, f.fmt.pix.sizeimage, 6220800); + KUNIT_EXPECT_EQ(test, fmt->plane_size[0], (size_t)4147200); + KUNIT_EXPECT_EQ(test, fmt->plane_size[1], (size_t)2073600); + KUNIT_EXPECT_EQ(test, f.fmt.pix.quantization, V4L2_QUANTIZATION_LIM_RANGE); + + isp->vbq.drv_priv = isp; + isp_test_set_num_buffers(test, &isp->vbq, 0); + KUNIT_ASSERT_EQ(test, + isp_vb2_queue_setup(&isp->vbq, &nbuffers, &num_planes, sizes, NULL), 0); + KUNIT_EXPECT_EQ(test, nbuffers, 2); + KUNIT_EXPECT_EQ(test, num_planes, 1); + KUNIT_EXPECT_EQ(test, sizes[0], 6220800); + KUNIT_EXPECT_EQ(test, isp_num_meta(isp), 8); + + preset.output_dim = (struct coord){1280, 720}; + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); + KUNIT_EXPECT_EQ(test, fmt->strides[0], 2560); + KUNIT_EXPECT_EQ(test, fmt->total_size, (size_t)2764800); + /* NV12 remains the default and restores the existing pool capacity. */ + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_NV12), 0); + KUNIT_EXPECT_EQ(test, fmt->pixelformat, V4L2_PIX_FMT_NV12); + KUNIT_EXPECT_EQ(test, fmt->total_size, (size_t)1382400); + KUNIT_EXPECT_EQ(test, isp_num_rendered(isp), ISP_MAX_BUFFERS); + KUNIT_EXPECT_EQ(test, isp_num_meta(isp), ISP_MAX_BUFFERS); + +} + +static void isp_test_p010_admission(struct kunit *test) +{ + static const struct apple_isp_hw neo = { .gen = ISP_GEN_T8140 }; + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_preset preset = { .output_dim = {1280, 720}, .p010 = true }; + struct v4l2_fmtdesc desc = { .index = 0 }; + + isp->presets = &preset; + isp->num_presets = 1; + isp->hw = &isp_test_t6040; + fmt->version = 0x958; + KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); + KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_NV12); + desc.index = 1; + KUNIT_ASSERT_EQ(test, isp_enum_pixelformat(isp, &desc), 0); + KUNIT_EXPECT_EQ(test, desc.pixelformat, V4L2_PIX_FMT_P010); + desc.index = 2; + KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); + desc.index = 1; + isp->hw = &neo; + KUNIT_EXPECT_EQ(test, isp_enum_pixelformat(isp, &desc), -EINVAL); + KUNIT_ASSERT_EQ(test, isp_set_preset(isp, fmt, &preset, V4L2_PIX_FMT_P010), 0); + KUNIT_EXPECT_EQ(test, fmt->pixelformat, V4L2_PIX_FMT_NV12); + KUNIT_EXPECT_EQ(test, fmt->strides[0], 1280); +} + +static void isp_test_p010_completion_identity(struct kunit *test) +{ + struct isp_format fmt = { .plane_size = {4147200, 2073600} }; + struct isp_buffer buf = { .surfs = {{ .iova = 0x10020000000ULL }}, .tag = 42 }; + struct isp_buflist_buffer desc = { + .iovas = {0x20000000, 0x203f4800}, + .num_planes = 2, .pool_type = CISP_POOL_TYPE_RENDERED_SCL1, .tag = 42, + }; + + KUNIT_EXPECT_TRUE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.tag--; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.tag++; + desc.iovas[1]++; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[1]--; + desc.iovas[0] |= BIT_ULL(40); + /* Reject mixed encodings, accept both exact full submitted addresses. */ + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[1] |= BIT_ULL(40); + KUNIT_EXPECT_TRUE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[0] |= BIT_ULL(41); + desc.iovas[1] |= BIT_ULL(41); + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.iovas[0] = (u32)desc.iovas[0]; + desc.iovas[1] = (u32)desc.iovas[1]; + desc.pool_type = CISP_POOL_TYPE_RENDERED; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); + desc.pool_type = CISP_POOL_TYPE_RENDERED_SCL1; + desc.num_planes = 1; + KUNIT_EXPECT_FALSE(test, isp_p010_buffer_matches(&fmt, &buf, &desc)); +} + +static void isp_test_p010_report_defers_completion(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_buffer *buf = kunit_kzalloc(test, sizeof(*buf), GFP_KERNEL); + struct { + u64 type, count; + struct isp_buflist_buffer desc; + } report = { + .type = 2, .count = 1, + .desc = { .iovas = {0x20000000, 0x203f4800}, + .num_planes = 2, .pool_type = 9, .tag = 42 }, + }; + struct isp_surf report_surf = { + .iova = 0x30000000, .size = sizeof(report), .virt = &report, + }; + struct isp_channel chan = { + .req = { .arg0 = report_surf.iova, .arg1 = sizeof(report), + .arg2 = ISP_IPC_BUFEXC_FLAG_RENDER }, + }; + + KUNIT_ASSERT_NOT_NULL(test, buf); + fmt->pixelformat = V4L2_PIX_FMT_P010; + fmt->plane_size[0] = 4147200; + isp->fw_iova_mask = U64_MAX; + isp->bt_surf = &report_surf; + buf->surfs[0].iova = 0x10020000000ULL; + buf->tag = 42; + INIT_LIST_HEAD(&isp->bufs_submitted); + list_add(&buf->link, &isp->bufs_submitted); + /* There is no vb2 queue: completing it before the ACK would fault. */ + KUNIT_ASSERT_EQ(test, ipc_bt_handle(isp, &chan), 0); + KUNIT_EXPECT_TRUE(test, list_empty(&isp->bufs_submitted)); + KUNIT_EXPECT_PTR_EQ(test, + list_first_entry(&isp->bufs_returned, struct isp_buffer, link), buf); + KUNIT_EXPECT_EQ(test, buf->return_state, VB2_BUF_STATE_DONE); + KUNIT_EXPECT_EQ(test, chan.rsp.arg0, report_surf.iova | ISP_IPC_FLAG_ACK); +} + +static void isp_test_p010_mode_selection(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_preset presets[] = { + { .index = 5, .output_dim = {1920, 1080} }, + { .index = 5, .output_dim = {1280, 720}, .p010 = true }, + { .index = 1, .output_dim = {4096, 3072} }, + }; + + isp->hw = &isp_test_t6040; + isp_get_current_format(isp)->version = 0x958; + isp->presets = presets; + isp->num_presets = ARRAY_SIZE(presets); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 0, 0, V4L2_PIX_FMT_P010), &presets[1]); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 1920, 1080, V4L2_PIX_FMT_P010), + &presets[1]); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 4096, 3072, V4L2_PIX_FMT_P010), + &presets[1]); + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 1920, 1080, V4L2_PIX_FMT_NV12), + &presets[0]); + /* Only a matching firmware output enables the larger P010 size. */ + presets[2].p010 = true; + KUNIT_EXPECT_PTR_EQ(test, isp_select_preset(isp, 4096, 3072, V4L2_PIX_FMT_P010), + &presets[2]); +} + +static int isp_test_buffer_ack(struct apple_isp *isp, struct isp_channel *chan, + unsigned long timeout) +{ + unsigned int *calls = kunit_get_current_test()->priv; + + (*calls)++; + return 0; +} + +static void isp_test_imx958_last_lease_requeue(struct kunit *test) +{ + struct apple_isp *isp = isp_test_alloc(test); + struct isp_format *fmt = isp_get_current_format(isp); + struct isp_buffer *buf = kunit_kzalloc(test, sizeof(*buf), GFP_KERNEL); + struct isp_buffer *submitted; + unsigned int calls = 0; + + KUNIT_ASSERT_NOT_NULL(test, buf); + isp->hw = &isp_test_t6040; + fmt->version = 0x958; + fmt->pixelformat = V4L2_PIX_FMT_P010; + fmt->num_planes = 2; + fmt->plane_size[0] = 1843200; + fmt->plane_size[1] = 921600; + INIT_LIST_HEAD(&isp->bufs_pending); + INIT_LIST_HEAD(&isp->bufs_submitted); + isp->cmd_virt = kunit_kzalloc(test, ISP_CMD_AREA_SIZE(0), GFP_KERNEL); + isp->chan_bh = kunit_kzalloc(test, sizeof(*isp->chan_bh), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->cmd_virt); + KUNIT_ASSERT_NOT_NULL(test, isp->chan_bh); + for (unsigned int i = 0; i < ISP_MAX_BUFFERS; i++) { + isp->meta_surfs[i] = kunit_kzalloc(test, sizeof(struct isp_surf), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp->meta_surfs[i]); + isp->meta_surfs[i]->submitted = true; + } + isp->vbq.drv_priv = isp; + buf->vb.vb2_buf.vb2_queue = &isp->vbq; + buf->surfs[0].iova = 0x20000000; + set_bit(ISP_STATE_STREAMING, &isp->state); + test->priv = &calls; + kunit_activate_static_stub(test, ipc_chan_send, isp_test_buffer_ack); + for (unsigned int format = 0; format < 2; format++) { + fmt->pixelformat = format ? V4L2_PIX_FMT_NV12 : V4L2_PIX_FMT_P010; + calls = 0; + for (unsigned int i = 1; i <= 10000; i++) { + /* Both lists empty, as after the last completion was dequeued. */ + isp_vb2_buf_queue(&buf->vb.vb2_buf); + KUNIT_ASSERT_EQ(test, calls, i); + if (!format) + KUNIT_ASSERT_EQ(test, buf->tag, (u64)i); + KUNIT_ASSERT_TRUE(test, list_empty(&isp->bufs_pending)); + submitted = list_first_entry(&isp->bufs_submitted, struct isp_buffer, link); + KUNIT_ASSERT_PTR_EQ(test, submitted, buf); + list_del(&buf->link); + } + } +} + /* The exposure report is found through the section table, never past it. */ static void isp_test_meta_exposure(struct kunit *test) { @@ -130,7 +371,7 @@ static void isp_test_meta_exposure(struct kunit *test) KUNIT_EXPECT_EQ(test, isp->frame_exposure_us, 32475); KUNIT_EXPECT_EQ(test, isp->frame_gain, 3253); - /* A short header cannot even supply the section-table entry. */ + /* A short header cannot supply the section-table entry. */ put_unaligned_le16(700, virt + 0x1876); meta.size = 0x1f; isp_meta_read_exposure(isp, &meta); @@ -186,6 +427,12 @@ static void isp_test_exposure_control_gate(struct kunit *test) static struct kunit_case isp_v4l2_test_cases[] = { KUNIT_CASE(isp_test_meta_exposure), KUNIT_CASE(isp_test_exposure_control_gate), + KUNIT_CASE(isp_test_imx958_last_lease_requeue), + KUNIT_CASE(isp_test_p010_report_defers_completion), + KUNIT_CASE(isp_test_p010_mode_selection), + KUNIT_CASE(isp_test_p010_format), + KUNIT_CASE(isp_test_p010_admission), + KUNIT_CASE(isp_test_p010_completion_identity), KUNIT_CASE(isp_test_queue_init), KUNIT_CASE(isp_test_reqbufs_limit), KUNIT_CASE(isp_test_create_bufs_limit), diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 41ae504b82dc7..65c97376ec2a3 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -22,24 +22,20 @@ #define ISP_MIN_FRAMES 2 #define ISP_MAX_PLANES 4 -#define ISP_MAX_PIX_FORMATS 2 #define ISP_BUFFER_TIMEOUT msecs_to_jiffies(1500) #define ISP_STRIDE_ALIGNMENT 64 +static void isp_vb2_set_min_buffers(struct apple_isp *isp); + /* Capture rates offered to userspace, in frames per second */ static const unsigned int isp_frame_rates[] = { 30, 25, 24, 20, 15 }; -static bool multiplanar = false; -module_param(multiplanar, bool, 0644); -MODULE_PARM_DESC(multiplanar, "Enable multiplanar API"); - struct isp_buflist_buffer { u64 iovas[ISP_MAX_PLANES]; u32 flags[ISP_MAX_PLANES]; u32 num_planes; u32 pool_type; - u32 tag; - u32 pad; + u64 tag; } __packed; static_assert(sizeof(struct isp_buflist_buffer) == ISP_BUFLIST_DESC_SIZE); @@ -58,6 +54,21 @@ static_assert(ISP_CMD_AREA_SIZE(0) >= ISP_IPC_BUFEXC_STAT_SIZE); */ #define ISP_BUFLIST_POOL_CAPTURE_META 2 +static bool isp_p010_buffer_matches(const struct isp_format *fmt, + const struct isp_buffer *buf, + const struct isp_buflist_buffer *desc) +{ + u64 y = buf->surfs[0].iova; + u64 uv = y + fmt->plane_size[0]; + bool full = desc->iovas[0] == y && desc->iovas[1] == uv; + bool low = desc->iovas[0] == (u32)y && desc->iovas[1] == (u32)uv; + + /* Accept the submitted pair or the zero-extended pair observed on H17. */ + return desc->num_planes == 2 && + desc->pool_type == CISP_POOL_TYPE_RENDERED_SCL1 && (full || low) && + !desc->iovas[2] && !desc->iovas[3] && desc->tag == buf->tag; +} + /* * A metadata buffer starts with a table of section offsets. On H17 firmware * the fourth section opens with the frame's exposure: the integration time in @@ -98,6 +109,23 @@ static void isp_meta_read_exposure(struct apple_isp *isp, WRITE_ONCE(isp->frame_gain, gain); } +void ipc_bt_complete(struct apple_isp *isp) +{ + struct isp_buffer *buf, *tmp; + unsigned long flags, i; + + /* The firmware has received its ACK before userspace can reuse a lease. */ + spin_lock_irqsave(&isp->buf_lock, flags); + for_each_set_bit(i, &isp->meta_returned, ISP_MAX_BUFFERS) + isp->meta_surfs[i]->submitted = false; + isp->meta_returned = 0; + list_for_each_entry_safe(buf, tmp, &isp->bufs_returned, link) { + list_del(&buf->link); + vb2_buffer_done(&buf->vb.vb2_buf, buf->return_state); + } + spin_unlock_irqrestore(&isp->buf_lock, flags); +} + int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) { struct isp_message *req = &chan->req, *rsp = &chan->rsp; @@ -137,12 +165,15 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) bufd->pool_type); */ if (bufd->pool_type == 0) { - for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) { + for (int j = 0; j < isp_num_meta(isp); j++) { struct isp_surf *meta = isp->meta_surfs[j]; if ((u32)bufd->iovas[0] == (u32)meta->iova) { WARN_ON(!meta->submitted); isp_meta_read_exposure(isp, meta); - meta->submitted = false; + if (isp_is_p010(isp)) + __set_bit(j, &isp->meta_returned); + else + meta->submitted = false; } } } else if (bufd->pool_type == ISP_BUFLIST_POOL_CAPTURE_META && @@ -156,8 +187,10 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) } else { list_for_each_entry_safe_reverse( buf, tmp, &isp->bufs_submitted, link) { - if ((u32)buf->surfs[0].iova == - (u32)bufd->iovas[0]) { + if (isp_is_p010(isp) ? + isp_p010_buffer_matches(isp_get_current_format(isp), + buf, bufd) : + (u32)buf->surfs[0].iova == (u32)bufd->iovas[0]) { enum vb2_buffer_state state = VB2_BUF_STATE_ERROR; @@ -168,9 +201,13 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan) if (req->arg2 == ISP_IPC_BUFEXC_FLAG_RENDER) state = VB2_BUF_STATE_DONE; - vb2_buffer_done(&buf->vb.vb2_buf, - state); - list_del(&buf->link); + if (isp_is_p010(isp)) { + buf->return_state = state; + list_move_tail(&buf->link, &isp->bufs_returned); + } else { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->link); + } } } } @@ -203,12 +240,17 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) struct isp_buflist *bl = isp->cmd_virt; struct isp_buflist_buffer *bufd = &bl->buffers[0]; + /* Before the command area is overwritten */ + err = isp_ipc_resync(isp); + if (err) + return err; + /* Clear what earlier commands and batches left in the reserved fields. */ memset(bl, 0, ISP_CMD_AREA_SIZE(isp_num_capmeta(isp))); bl->type = 1; spin_lock_irqsave(&isp->buf_lock, flags); - for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { + for (int i = 0; i < isp_num_meta(isp); i++) { struct isp_surf *meta = isp->meta_surfs[i]; if (meta->submitted) @@ -247,9 +289,13 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) } /* One rendered pool per batch; the rest stays pending for the next. */ - while (num_rendered < ISP_MAX_BUFFERS && + while (num_rendered < isp_num_rendered(isp) && (buf = list_first_entry_or_null(&isp->bufs_pending, struct isp_buffer, link))) { + if (isp_is_p010(isp)) { + buf->tag = ++isp->buffer_tag; + bufd->tag = buf->tag; + } bufd->num_planes = fmt->num_planes; bufd->pool_type = isp->hw->scl1 ? CISP_POOL_TYPE_RENDERED_SCL1 : CISP_POOL_TYPE_RENDERED; @@ -291,6 +337,12 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT); if (err) { + /* + * The firmware may have taken the batch. Keep its leases owned + * until the timeout is resolved, see apple_isp_stop_camera(). + */ + if (isp_is_p010(isp)) + return err; /* If we fail, consider the buffer not submitted. */ dev_err(isp->dev, "%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n", @@ -311,7 +363,7 @@ static int isp_submit_buffers(struct apple_isp *isp, bool pre_start) &isp->bufs_pending); } } - for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) { + for (int j = 0; j < isp_num_meta(isp); j++) { struct isp_surf *meta = isp->meta_surfs[j]; if (bufd->iovas[0] == meta->iova) { meta->submitted = false; @@ -350,13 +402,13 @@ static int isp_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, * REQBUFS frees the old buffers first, CREATE_BUFS adds to them, so * count the buffers the queue already has. */ - if (q_num_bufs >= ISP_MAX_BUFFERS) + if (q_num_bufs >= isp_num_rendered(isp)) return -ENOBUFS; *nbuffers = min_t(unsigned int, *nbuffers, - ISP_MAX_BUFFERS - q_num_bufs); + isp_num_rendered(isp) - q_num_bufs); if (*num_planes) { - if (sizes[0] < fmt->total_size) + if (*num_planes != 1 || sizes[0] < fmt->total_size) return -EINVAL; return 0; @@ -449,8 +501,23 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb) list_add_tail(&buf->link, &isp->bufs_pending); spin_unlock_irqrestore(&isp->buf_lock, flags); - if (test_bit(ISP_STATE_STREAMING, &isp->state) && !empty) - isp_submit_buffers(isp, false); + /* Restart an empty IMX958 queue when its last returned lease is queued. */ + if (test_bit(ISP_STATE_STREAMING, &isp->state) && + (isp_supports_p010(isp) || !empty)) { + if (isp_submit_buffers(isp, false) && isp_is_p010(isp)) { + /* + * After a timeout the leases stay owned until the + * stream stops. Only firmware that did not answer at + * all is stopped here, before its leases are released. + */ + if (isp->ipc_failed) { + apple_isp_firmware_halt(isp); + isp_vb2_release_buffers(isp, + VB2_BUF_STATE_ERROR); + } + vb2_queue_error(&isp->vbq); + } + } } static int apple_isp_start_streaming(struct apple_isp *isp) @@ -526,12 +593,14 @@ static int isp_vb2_start_streaming(struct vb2_queue *q, unsigned int count) isp->sequence = 0; - /* The exposure priority is applied when the stream starts. */ + /* The exposure controls are applied when the stream starts. */ v4l2_ctrl_grab(isp->exposure_priority, true); + v4l2_ctrl_grab(isp->power_line_frequency, true); err = apple_isp_start_streaming(isp); if (err) { v4l2_ctrl_grab(isp->exposure_priority, false); + v4l2_ctrl_grab(isp->power_line_frequency, false); isp_vb2_release_buffers(isp, VB2_BUF_STATE_QUEUED); } @@ -545,6 +614,7 @@ static void isp_vb2_stop_streaming(struct vb2_queue *q) apple_isp_stop_streaming(isp); isp_vb2_release_buffers(isp, VB2_BUF_STATE_ERROR); v4l2_ctrl_grab(isp->exposure_priority, false); + v4l2_ctrl_grab(isp->power_line_frequency, false); } int apple_isp_video_suspend(struct apple_isp *isp) @@ -607,18 +677,23 @@ static const struct vb2_ops isp_vb2_ops = { }; static int isp_set_preset(struct apple_isp *isp, struct isp_format *fmt, - struct isp_preset *preset) + struct isp_preset *preset, u32 pixelformat) { + unsigned int bytes_per_sample; int i; size_t total_size; fmt->preset = preset; - /* I really fucking hope they all use NV12. */ + /* P010 has MSB-aligned 10-bit samples in little-endian 16-bit words. */ + fmt->pixelformat = pixelformat == V4L2_PIX_FMT_P010 && + isp_supports_p010(isp) && preset->p010 ? + V4L2_PIX_FMT_P010 : V4L2_PIX_FMT_NV12; + bytes_per_sample = fmt->pixelformat == V4L2_PIX_FMT_P010 ? 2 : 1; fmt->num_planes = 2; - fmt->strides[0] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT); - /* UV subsampled interleaved */ - fmt->strides[1] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT); + fmt->strides[0] = ALIGN(preset->output_dim.x * bytes_per_sample, + ISP_STRIDE_ALIGNMENT); + fmt->strides[1] = fmt->strides[0]; fmt->plane_size[0] = fmt->strides[0] * preset->output_dim.y; fmt->plane_size[1] = fmt->strides[1] * preset->output_dim.y / 2; @@ -631,17 +706,25 @@ static int isp_set_preset(struct apple_isp *isp, struct isp_format *fmt, } static struct isp_preset *isp_select_preset(struct apple_isp *isp, u32 width, - u32 height) + u32 height, u32 pixelformat) { struct isp_preset *preset, *best = &isp->presets[0]; int i, score, best_score = INT_MAX; - /* Default if no dimensions */ - if (width == 0 || height == 0) - return &isp->presets[0]; + bool p010 = pixelformat == V4L2_PIX_FMT_P010 && isp_has_p010(isp); + + /* An unspecified P010 size selects the processed 720p preview first. */ + if (width == 0 || height == 0) { + if (!p010) + return &isp->presets[0]; + width = 1280; + height = 720; + } for (i = 0; i < isp->num_presets; i++) { preset = &isp->presets[i]; + if (p010 && !preset->p010) + continue; score = abs((int)preset->output_dim.x - (int)width) + abs((int)preset->output_dim.y - (int)height); if (score < best_score) { @@ -665,44 +748,47 @@ static int isp_vidioc_querycap(struct file *file, void *priv, return 0; } -static int isp_vidioc_enum_format(struct file *file, void *fh, - struct v4l2_fmtdesc *f) +static int isp_enum_pixelformat(struct apple_isp *isp, struct v4l2_fmtdesc *f) { - struct apple_isp *isp = video_drvdata(file); - - if (f->index >= ISP_MAX_PIX_FORMATS) - return -EINVAL; - - switch (f->index) { - case 0: + /* Browser/WebRTC compatibility: the shipped first format remains NV12. */ + if (!f->index) f->pixelformat = V4L2_PIX_FMT_NV12; - break; - case 1: - if (!isp->multiplanar) - return -EINVAL; - f->pixelformat = V4L2_PIX_FMT_NV12M; - break; - default: + else if (f->index == 1 && isp_has_p010(isp)) + f->pixelformat = V4L2_PIX_FMT_P010; + else return -EINVAL; - } - return 0; } +static int isp_vidioc_enum_format(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + return isp_enum_pixelformat(video_drvdata(file), f); +} + static int isp_vidioc_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *f) { struct apple_isp *isp = video_drvdata(file); + struct isp_preset *preset = NULL; + u32 index = 0; - if (f->index >= isp->num_presets) + if (f->pixel_format != V4L2_PIX_FMT_NV12 && + !(f->pixel_format == V4L2_PIX_FMT_P010 && isp_has_p010(isp))) return -EINVAL; - if ((f->pixel_format != V4L2_PIX_FMT_NV12) && - (f->pixel_format != V4L2_PIX_FMT_NV12M)) + for (int i = 0; i < isp->num_presets; i++) { + if (f->pixel_format == V4L2_PIX_FMT_P010 && !isp->presets[i].p010) + continue; + if (index++ == f->index) { + preset = &isp->presets[i]; + break; + } + } + if (!preset) return -EINVAL; - - f->discrete.width = isp->presets[f->index].output_dim.x; - f->discrete.height = isp->presets[f->index].output_dim.y; + f->discrete.width = preset->output_dim.x; + f->discrete.height = preset->output_dim.y; f->type = V4L2_FRMSIZE_TYPE_DISCRETE; return 0; @@ -718,10 +804,12 @@ static int isp_vidioc_enum_frameintervals(struct file *file, void *fh, return -EINVAL; if (interval->pixel_format != V4L2_PIX_FMT_NV12 && - interval->pixel_format != V4L2_PIX_FMT_NV12M) + !(interval->pixel_format == V4L2_PIX_FMT_P010 && isp_has_p010(isp))) return -EINVAL; for (i = 0; i < isp->num_presets; i++) { + if (interval->pixel_format == V4L2_PIX_FMT_P010 && !isp->presets[i].p010) + continue; if (isp->presets[i].output_dim.x == interval->width && isp->presets[i].output_dim.y == interval->height) break; @@ -768,29 +856,12 @@ static inline void isp_get_sp_pix_format(struct apple_isp *isp, f->fmt.pix.sizeimage = fmt->total_size; f->fmt.pix.field = V4L2_FIELD_NONE; - f->fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; + f->fmt.pix.pixelformat = fmt->pixelformat; f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_709; f->fmt.pix.xfer_func = V4L2_XFER_FUNC_709; -} - -static inline void isp_get_mp_pix_format(struct apple_isp *isp, - struct v4l2_format *f, - struct isp_format *fmt) -{ - f->fmt.pix_mp.width = fmt->preset->output_dim.x; - f->fmt.pix_mp.height = fmt->preset->output_dim.y; - f->fmt.pix_mp.num_planes = fmt->num_planes; - for (int i = 0; i < fmt->num_planes; i++) { - f->fmt.pix_mp.plane_fmt[i].sizeimage = fmt->plane_size[i]; - f->fmt.pix_mp.plane_fmt[i].bytesperline = fmt->strides[i]; - } - - f->fmt.pix_mp.field = V4L2_FIELD_NONE; - f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M; - f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; - f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_709; - f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_709; + if (fmt->pixelformat == V4L2_PIX_FMT_P010) + f->fmt.pix.quantization = V4L2_QUANTIZATION_LIM_RANGE; } static int isp_vidioc_get_format(struct file *file, void *fh, @@ -816,14 +887,16 @@ static int isp_vidioc_set_format(struct file *file, void *fh, if (vb2_is_busy(&isp->vbq)) return -EBUSY; - preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); - err = isp_set_preset(isp, fmt, preset); + preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat); + err = isp_set_preset(isp, fmt, preset, f->fmt.pix.pixelformat); if (err) return err; isp_get_sp_pix_format(isp, f, fmt); isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + isp_vb2_set_min_buffers(isp); return 0; } @@ -836,8 +909,9 @@ static int isp_vidioc_try_format(struct file *file, void *fh, struct isp_preset *preset; int err; - preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height); - err = isp_set_preset(isp, &fmt, preset); + preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat); + err = isp_set_preset(isp, &fmt, preset, f->fmt.pix.pixelformat); if (err) return err; @@ -846,69 +920,6 @@ static int isp_vidioc_try_format(struct file *file, void *fh, return 0; } -static int isp_vidioc_get_format_mplane(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct apple_isp *isp = video_drvdata(file); - struct isp_format *fmt = isp_get_current_format(isp); - - if (!isp->multiplanar) - return -ENOTTY; - - isp_get_mp_pix_format(isp, f, fmt); - - return 0; -} - -static int isp_vidioc_set_format_mplane(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct apple_isp *isp = video_drvdata(file); - struct isp_format *fmt = isp_get_current_format(isp); - struct isp_preset *preset; - int err; - - if (!isp->multiplanar) - return -ENOTTY; - - if (vb2_is_busy(&isp->vbq)) - return -EBUSY; - - preset = isp_select_preset(isp, f->fmt.pix_mp.width, - f->fmt.pix_mp.height); - err = isp_set_preset(isp, fmt, preset); - if (err) - return err; - - isp_get_mp_pix_format(isp, f, fmt); - - isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - - return 0; -} - -static int isp_vidioc_try_format_mplane(struct file *file, void *fh, - struct v4l2_format *f) -{ - struct apple_isp *isp = video_drvdata(file); - struct isp_format fmt = *isp_get_current_format(isp); - struct isp_preset *preset; - int err; - - if (!isp->multiplanar) - return -ENOTTY; - - preset = isp_select_preset(isp, f->fmt.pix_mp.width, - f->fmt.pix_mp.height); - err = isp_set_preset(isp, &fmt, preset); - if (err) - return err; - - isp_get_mp_pix_format(isp, f, &fmt); - - return 0; -} - static int isp_vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *inp) { @@ -941,9 +952,7 @@ static int isp_vidioc_get_param(struct file *file, void *fh, { struct apple_isp *isp = video_drvdata(file); - if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && - (!isp->multiplanar || - a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; @@ -959,9 +968,7 @@ static int isp_vidioc_set_param(struct file *file, void *fh, struct apple_isp *isp = video_drvdata(file); struct v4l2_fract *tpf = &a->parm.capture.timeperframe; - if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && - (!isp->multiplanar || - a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; /* The rate is configured when the stream starts. */ @@ -982,6 +989,27 @@ static int isp_vidioc_set_param(struct file *file, void *fh, return 0; } +static int isp_vidioc_log_status(struct file *file, void *fh) +{ + struct apple_isp *isp = video_drvdata(file); + + v4l2_ctrl_log_status(file, fh); + for (unsigned int i = 0; i < isp->num_sensor_configs; i++) { + const struct isp_sensor_config *config = &isp->sensor_configs[i]; + + v4l2_info(&isp->v4l2_dev, + "sensor config %u: input %ux%u output %ux%u clock %u%s\n", + i, config->input_dim.x, config->input_dim.y, + config->output_dim.x, config->output_dim.y, + config->sensor_clk, + config->usable ? "" : " (unverified geometry)"); + /* This explicit diagnostic ioctl replaces a probe-time parameter. */ + print_hex_dump(KERN_INFO, "apple-isp config: ", DUMP_PREFIX_OFFSET, + 16, 4, config->reply, sizeof(config->reply), false); + } + return 0; +} + static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_querycap = isp_vidioc_querycap, @@ -989,9 +1017,6 @@ static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_g_fmt_vid_cap = isp_vidioc_get_format, .vidioc_s_fmt_vid_cap = isp_vidioc_set_format, .vidioc_try_fmt_vid_cap = isp_vidioc_try_format, - .vidioc_g_fmt_vid_cap_mplane = isp_vidioc_get_format_mplane, - .vidioc_s_fmt_vid_cap_mplane = isp_vidioc_set_format_mplane, - .vidioc_try_fmt_vid_cap_mplane = isp_vidioc_try_format_mplane, .vidioc_enum_framesizes = isp_vidioc_enum_framesizes, .vidioc_enum_frameintervals = isp_vidioc_enum_frameintervals, @@ -1011,7 +1036,7 @@ static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = { .vidioc_streamon = vb2_ioctl_streamon, .vidioc_streamoff = vb2_ioctl_streamoff, - .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_log_status = isp_vidioc_log_status, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; @@ -1043,6 +1068,7 @@ static void apple_isp_release(struct v4l2_device *v4l2_dev) v4l2_ctrl_handler_free(&isp->ctrl_handler); media_device_cleanup(&isp->mdev); + kfree(isp->sensor_configs); kfree(isp); } @@ -1061,6 +1087,22 @@ static void isp_free_meta_surfaces(struct apple_isp *isp) } } +/* + * vb2 allocates at least min_queued_buffers + 1 buffers, and the queue may + * hold no more than the rendered pool (isp_vb2_queue_setup()). The P010 pool + * has two slots, so a P010 stream starts with one queued buffer; otherwise + * REQBUFS can never get the three buffers vb2 asks for and fails with ENOMEM. + * Only called while the queue has no buffers. + */ +static void isp_vb2_set_min_buffers(struct apple_isp *isp) +{ + struct vb2_queue *vbq = &isp->vbq; + + vbq->min_queued_buffers = min_t(unsigned int, ISP_MIN_FRAMES, + isp_num_rendered(isp) - 1); + vbq->min_reqbufs_allocation = vbq->min_queued_buffers + 1; +} + static int isp_vb2_queue_init(struct apple_isp *isp) { struct vb2_queue *vbq = &isp->vbq; @@ -1110,9 +1152,6 @@ static const struct v4l2_ctrl_ops isp_exposure_ctrl_ops = { */ static void isp_init_exposure_ctrls(struct apple_isp *isp) { - if (!isp->hw->meta_exposure) - return; - isp->frame_gain = 256; isp->gain = v4l2_ctrl_new_std(&isp->ctrl_handler, &isp_exposure_ctrl_ops, V4L2_CID_GAIN, @@ -1136,7 +1175,7 @@ int apple_isp_setup_video(struct apple_isp *isp) struct isp_format *fmt = isp_get_current_format(isp); int err; - err = isp_set_preset(isp, fmt, &isp->presets[0]); + err = isp_set_preset(isp, fmt, &isp->presets[0], V4L2_PIX_FMT_NV12); if (err) { dev_err(isp->dev, "failed to set default preset: %d\n", err); return err; @@ -1146,7 +1185,8 @@ int apple_isp_setup_video(struct apple_isp *isp) for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) { isp->meta_surfs[i] = - isp_alloc_surface_vmap(isp, isp->hw->meta_size); + isp_alloc_surface_vmap(isp, isp_has_p010(isp) ? 0x8000 : + isp->hw->meta_size); if (!isp->meta_surfs[i]) { isp_err(isp, "failed to alloc meta surface\n"); err = -ENOMEM; @@ -1178,17 +1218,28 @@ int apple_isp_setup_video(struct apple_isp *isp) goto media_cleanup; } - isp->multiplanar = multiplanar; - /* * Auto exposure is always on. By default it holds the frame rate, as * V4L2 specifies; with exposure priority it may slow down in low light. */ - v4l2_ctrl_handler_init(&isp->ctrl_handler, 3); + v4l2_ctrl_handler_init(&isp->ctrl_handler, 4); isp->exposure_priority = v4l2_ctrl_new_std(&isp->ctrl_handler, NULL, V4L2_CID_EXPOSURE_AUTO_PRIORITY, 0, 1, 1, 0); - isp_init_exposure_ctrls(isp); + /* + * The H17 firmware's auto exposure avoids mains flicker at the given + * frequency. 50 Hz is the default, as on macOS when it does not know + * the country; userspace sets 60 Hz where the mains runs at 60 Hz. + */ + if (isp->hw->fw_abi == ISP_FW_ABI_H17) + isp->power_line_frequency = + v4l2_ctrl_new_std_menu(&isp->ctrl_handler, NULL, + V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_60HZ, + BIT(V4L2_CID_POWER_LINE_FREQUENCY_DISABLED), + V4L2_CID_POWER_LINE_FREQUENCY_50HZ); + if (isp->hw->meta_exposure) + isp_init_exposure_ctrls(isp); if (isp->ctrl_handler.error) { err = isp->ctrl_handler.error; dev_err(isp->dev, "failed to create controls: %d\n", err); @@ -1212,8 +1263,6 @@ int apple_isp_setup_video(struct apple_isp *isp) vdev->fops = &isp_v4l2_fops; vdev->ioctl_ops = &isp_v4l2_ioctl_ops; vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; - if (isp->multiplanar) - vdev->device_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE; vdev->entity.flags = MEDIA_ENT_FL_DEFAULT; vdev->v4l2_dev = &isp->v4l2_dev; vdev->vfl_type = VFL_TYPE_VIDEO; From 76db72372939a06916421d60da7cfd27af4119af Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Thu, 5 Nov 2026 01:22:40 -0500 Subject: [PATCH 4/4] media: apple: isp: keep exposure controls gated in the helper Keep the 7.3 registration-side profile check and make the helper enforce the same gate. This preserves the carried control-gate regression test and refuses controls when the selected hardware has no qualified exposure metadata. Signed-off-by: Satya Benson --- drivers/media/platform/apple/isp/isp-v4l2.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 65c97376ec2a3..26408e3c669d7 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -1152,6 +1152,9 @@ static const struct v4l2_ctrl_ops isp_exposure_ctrl_ops = { */ static void isp_init_exposure_ctrls(struct apple_isp *isp) { + if (!isp->hw->meta_exposure) + return; + isp->frame_gain = 256; isp->gain = v4l2_ctrl_new_std(&isp->ctrl_handler, &isp_exposure_ctrl_ops, V4L2_CID_GAIN,