From 3e8042f8d85d5572d15dc7acfc34ee1378d8c00b Mon Sep 17 00:00:00 2001 From: Arturo Buzarra Date: Tue, 28 Oct 2025 13:19:29 +0100 Subject: [PATCH] stm-st-stm32mp: add libcamera-stm32mp recipe Integrate ST libcamera recipe from meta-st-openstlinux layer at openstlinux-6.6-yocto-scarthgap-mpu-v25.06.11 tag. This recipe is required by the NPU demos in meta-st-x-linux-ai. Signed-off-by: Arturo Buzarra --- .../0001-0.3.0-stm32mp-add-dcmipp-ipa.patch | 8385 +++++++++++++++++ ...evice-Add-bool-return-type-to-unlock.patch | 59 + ...ca-instead-of-variable-length-arrays.patch | 43 + ...002-options-Replace-use-of-VLAs-in-C.patch | 128 + .../libcamera/libcamera-stm32mp_0.3.0.bb | 91 + .../packagegroup-dey-x-linux-ai.bb | 2 + 6 files changed, 8708 insertions(+) create mode 100644 meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-0.3.0-stm32mp-add-dcmipp-ipa.patch create mode 100644 meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-media_device-Add-bool-return-type-to-unlock.patch create mode 100644 meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-rpi-Use-alloca-instead-of-variable-length-arrays.patch create mode 100644 meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0002-options-Replace-use-of-VLAs-in-C.patch create mode 100644 meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/libcamera-stm32mp_0.3.0.bb diff --git a/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-0.3.0-stm32mp-add-dcmipp-ipa.patch b/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-0.3.0-stm32mp-add-dcmipp-ipa.patch new file mode 100644 index 000000000..d40f145a3 --- /dev/null +++ b/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-0.3.0-stm32mp-add-dcmipp-ipa.patch @@ -0,0 +1,8385 @@ +From 3a1b1068ecae127ab97cf4b3f6fd638893a8380a Mon Sep 17 00:00:00 2001 +From: Christophe Priouzeau +Date: Wed, 4 Jun 2025 13:47:46 +0200 +Subject: [PATCH] 0.3.0-stm32mp-add-dcmipp-ipa + +--- + README.rst | 16 + + include/libcamera/internal/media_device.h | 2 +- + include/libcamera/ipa/dcmipp.mojom | 36 + + include/libcamera/ipa/meson.build | 1 + + include/linux/stm32-dcmipp-config.h | 196 +++ + include/linux/v4l2-controls.h | 41 + + include/linux/videodev2.h | 5 + + meson.build | 1 + + meson_options.txt | 10 +- + src/apps/cam/camera_session.cpp | 11 +- + src/apps/common/options.cpp | 12 +- + src/gstreamer/gstlibcamera-utils.cpp | 16 + + src/gstreamer/gstlibcamerapad.cpp | 4 + + src/gstreamer/gstlibcamerasrc.cpp | 1124 +++++++++++++- + src/ipa/dcmipp/algorithms/aec.cpp | 704 +++++++++ + src/ipa/dcmipp/algorithms/aec.h | 68 + + src/ipa/dcmipp/algorithms/algorithm.h | 35 + + src/ipa/dcmipp/algorithms/awb.cpp | 948 ++++++++++++ + src/ipa/dcmipp/algorithms/awb.h | 65 + + src/ipa/dcmipp/algorithms/badpixel.cpp | 185 +++ + src/ipa/dcmipp/algorithms/badpixel.h | 50 + + src/ipa/dcmipp/algorithms/blc.cpp | 145 ++ + src/ipa/dcmipp/algorithms/blc.h | 44 + + src/ipa/dcmipp/algorithms/contrast.cpp | 141 ++ + src/ipa/dcmipp/algorithms/contrast.h | 44 + + src/ipa/dcmipp/algorithms/demosaicing.cpp | 146 ++ + src/ipa/dcmipp/algorithms/demosaicing.h | 44 + + src/ipa/dcmipp/algorithms/gamma.cpp | 91 ++ + src/ipa/dcmipp/algorithms/gamma.h | 44 + + src/ipa/dcmipp/algorithms/meson.build | 12 + + src/ipa/dcmipp/algorithms/statistic.cpp | 215 +++ + src/ipa/dcmipp/algorithms/statistic.h | 44 + + src/ipa/dcmipp/data/imx335.yaml | 57 + + .../dcmipp/data/imx335_judge2_light_box.yaml | 54 + + .../dcmipp/data/imx335_mini_light_box.yaml | 54 + + src/ipa/dcmipp/data/meson.build | 9 + + src/ipa/dcmipp/dcmipp.cpp | 567 +++++++ + src/ipa/dcmipp/ipa_context.h | 132 ++ + src/ipa/dcmipp/meson.build | 39 + + src/ipa/dcmipp/module.h | 27 + + src/ipa/rpi/controller/rpi/alsc.cpp | 7 +- + src/libcamera/control_ids_draft.yaml | 244 +++ + src/libcamera/ipc_unixsocket.cpp | 13 +- + src/libcamera/media_device.cpp | 6 +- + src/libcamera/pipeline/dcmipp/dcmipp.cpp | 1363 +++++++++++++++++ + src/libcamera/pipeline/dcmipp/dcmipp.h | 120 ++ + src/libcamera/pipeline/dcmipp/dcmipp_path.cpp | 496 ++++++ + src/libcamera/pipeline/dcmipp/meson.build | 5 + + src/libcamera/v4l2_device.cpp | 20 +- + 49 files changed, 7674 insertions(+), 39 deletions(-) + create mode 100644 include/libcamera/ipa/dcmipp.mojom + create mode 100644 include/linux/stm32-dcmipp-config.h + create mode 100644 src/ipa/dcmipp/algorithms/aec.cpp + create mode 100644 src/ipa/dcmipp/algorithms/aec.h + create mode 100644 src/ipa/dcmipp/algorithms/algorithm.h + create mode 100644 src/ipa/dcmipp/algorithms/awb.cpp + create mode 100644 src/ipa/dcmipp/algorithms/awb.h + create mode 100644 src/ipa/dcmipp/algorithms/badpixel.cpp + create mode 100644 src/ipa/dcmipp/algorithms/badpixel.h + create mode 100644 src/ipa/dcmipp/algorithms/blc.cpp + create mode 100644 src/ipa/dcmipp/algorithms/blc.h + create mode 100644 src/ipa/dcmipp/algorithms/contrast.cpp + create mode 100644 src/ipa/dcmipp/algorithms/contrast.h + create mode 100644 src/ipa/dcmipp/algorithms/demosaicing.cpp + create mode 100644 src/ipa/dcmipp/algorithms/demosaicing.h + create mode 100644 src/ipa/dcmipp/algorithms/gamma.cpp + create mode 100644 src/ipa/dcmipp/algorithms/gamma.h + create mode 100644 src/ipa/dcmipp/algorithms/meson.build + create mode 100644 src/ipa/dcmipp/algorithms/statistic.cpp + create mode 100644 src/ipa/dcmipp/algorithms/statistic.h + create mode 100644 src/ipa/dcmipp/data/imx335.yaml + create mode 100644 src/ipa/dcmipp/data/imx335_judge2_light_box.yaml + create mode 100644 src/ipa/dcmipp/data/imx335_mini_light_box.yaml + create mode 100644 src/ipa/dcmipp/data/meson.build + create mode 100644 src/ipa/dcmipp/dcmipp.cpp + create mode 100644 src/ipa/dcmipp/ipa_context.h + create mode 100644 src/ipa/dcmipp/meson.build + create mode 100644 src/ipa/dcmipp/module.h + create mode 100644 src/libcamera/pipeline/dcmipp/dcmipp.cpp + create mode 100644 src/libcamera/pipeline/dcmipp/dcmipp.h + create mode 100644 src/libcamera/pipeline/dcmipp/dcmipp_path.cpp + create mode 100644 src/libcamera/pipeline/dcmipp/meson.build + +diff --git a/README.rst b/README.rst +index 1da7a3d6..d605182b 100644 +--- a/README.rst ++++ b/README.rst +@@ -178,6 +178,22 @@ Which can be received on another device over the network with: + gst-launch-1.0 tcpclientsrc host=$DEVICE_IP port=5000 ! \ + multipartdemux ! jpegdec ! autovideosink + ++The GStreamer element also supports multiple streams. This is achieved by ++requesting additionnal source pads. Downstream caps filteris can be used ++to choose specific parameters like resolution and pixel format. The pad ++property ``stream-role`` can be used to select a role. ++ ++The following example displayis a 640x480 view finder while streamiing JPEG ++encoded 800x600 video. You can use the receiver pipleine above to view the ++remote stream from another device. ++ ++.. code:: ++ ++ gst-launch-1.0 libcamerasrc name=cs src::stream-role=view-finder src_0::stream-role=video-recording \ ++ cs.src ! queue ! video/x-raw,width=640,height=480 ! videoconvert ! autovideosink \ ++ cs.src_0 ! queue ! video/x-raw,width=800,height=600 ! videoconvert ! \ ++ jpegenc ! multipartmux ! tcpserversink host=0.0.0.0 port=5000 ++ + .. section-end-getting-started + + Troubleshooting +diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h +index 3b624e00..bf2e475d 100644 +--- a/include/libcamera/internal/media_device.h ++++ b/include/libcamera/internal/media_device.h +@@ -33,7 +33,7 @@ public: + bool busy() const { return acquired_; } + + bool lock(); +- bool unlock(); ++ void unlock(); + + int populate(); + bool isValid() const { return valid_; } +diff --git a/include/libcamera/ipa/dcmipp.mojom b/include/libcamera/ipa/dcmipp.mojom +new file mode 100644 +index 00000000..810c0f6c +--- /dev/null ++++ b/include/libcamera/ipa/dcmipp.mojom +@@ -0,0 +1,36 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++module ipa.dcmipp; ++ ++import "include/libcamera/ipa/core.mojom"; ++ ++interface IPADcmippInterface { ++ init(libcamera.IPASettings settings, ++ uint32 hwRevision, ++ libcamera.IPACameraSensorInfo sensorInfo, ++ libcamera.ControlInfoMap sensorControls) ++ => (int32 ret, libcamera.ControlInfoMap ipaControls); ++ configure(libcamera.IPACameraSensorInfo sensorInfo, ++ libcamera.ControlInfoMap sensorControls, ++ uint32 ispDecimationRatio) ++ => (int32 ret, libcamera.ControlInfoMap ipaControls); ++ ++ start() => (int32 ret); ++ stop(); ++ ++ mapBuffers(array buffers); ++ unmapBuffers(array ids); ++ ++ [async] processStatsBuffer(uint32 frame, uint32 bufferId); ++ [async] fillParamsBuffer(uint32 frame, uint32 bufferId); ++ [async] queueRequest(uint32 frame, libcamera.ControlList controls); ++}; ++ ++interface IPADcmippEventInterface { ++ statsBufferProcessed(uint32 bufferId); ++ paramsBufferReady(uint32 bufferId); ++ metadataReady(uint32 frame, libcamera.ControlList sensorControls); ++ setSensorControls(uint32 frame, libcamera.ControlList sensorControls); ++ setIspControls(uint32 frame, libcamera.ControlList ispControls); ++ setPostprocControls(uint32 frame, libcamera.ControlList postprocControls); ++}; +diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build +index 3352d08f..925aad2b 100644 +--- a/include/libcamera/ipa/meson.build ++++ b/include/libcamera/ipa/meson.build +@@ -62,6 +62,7 @@ libcamera_generated_ipa_headers += custom_target('core_ipa_serializer_h', + + # Mapping from pipeline handler name to mojom file + pipeline_ipa_mojom_mapping = { ++ 'dcmipp': 'dcmipp.mojom', + 'ipu3': 'ipu3.mojom', + 'rkisp1': 'rkisp1.mojom', + 'rpi/vc4': 'raspberrypi.mojom', +diff --git a/include/linux/stm32-dcmipp-config.h b/include/linux/stm32-dcmipp-config.h +new file mode 100644 +index 00000000..85b4cf73 +--- /dev/null ++++ b/include/linux/stm32-dcmipp-config.h +@@ -0,0 +1,196 @@ ++/* SPDX-License-Identifier: ((GPL-2.0+ WITH Linux-syscall-note) OR MIT) */ ++/* ++ * STM32 DCMIPP ISP userspace API ++ * Copyright (C) STMicroelectronics SA 2023 ++ */ ++ ++#ifndef _UAPI_STM32_DCMIPP_CONFIG_H ++#define _UAPI_STM32_DCMIPP_CONFIG_H ++ ++#include ++ ++/* Bad Pixel Removal */ ++#define STM32_DCMIPP_ISP_BPR (1U << 0) ++/* Black Level Correction */ ++#define STM32_DCMIPP_ISP_BLC (1U << 1) ++/* Exposure Control */ ++#define STM32_DCMIPP_ISP_EX (1U << 2) ++/* Demosaicing filters */ ++#define STM32_DCMIPP_ISP_DM (1U << 3) ++/* Color conversion Control */ ++#define STM32_DCMIPP_ISP_CC (1U << 4) ++/* Contrast Enhancement */ ++#define STM32_DCMIPP_ISP_CE (1U << 5) ++ ++/** ++ * struct stm32_dcmipp_isp_bpr_cfg - STM32 DCMIPP ISP bad pixel removal ++ * ++ * @en: enable / disable the bad pixel removal block ++ * @strength: strength (aggressiveness) of the bad pixel detection ++ */ ++struct stm32_dcmipp_isp_bpr_cfg { ++ __u32 en; ++ __u32 strength; ++}; ++ ++/** ++ * struct stm32_dcmipp_isp_blc_cfg - STM32 DCMIPP ISP black level correction ++ * ++ * @en: enable / disable the black level correction block ++ * @blc_r: Correction on the red component ++ * @blc_g: Correction on the green component ++ * @blc_b: Correction on the blue component ++ */ ++struct stm32_dcmipp_isp_blc_cfg { ++ __u32 en; ++ __u8 blc_r; ++ __u8 blc_g; ++ __u8 blc_b; ++}; ++ ++/** ++ * struct stm32_dcmipp_isp_ex_cfg - STM32 DCMIPP ISP exposure control ++ * ++ * @en: enable / disable the exposure control block ++ * @shift_r: red component exposure shift ++ * @mult_r: red component exposure multiplier ++ * @shift_g: green component exposure shift ++ * @mult_g: green component exposure multiplier ++ * @shift_b: blue component exposure shift ++ * @mult_b: blue component exposure multiplier ++ */ ++struct stm32_dcmipp_isp_ex_cfg { ++ __u32 en; ++ __u8 shift_r; ++ __u8 mult_r; ++ __u8 shift_g; ++ __u8 mult_g; ++ __u8 shift_b; ++ __u8 mult_b; ++}; ++ ++/** ++ * struct stm32_dcmipp_isp_dm_cfg - STM32 DCMIPP ISP demosaicing filters ++ * ++ * @en: enable / disable the demosaicing block ++ * @edge: strength of the edge detection ++ * @lineh: strength of the horizontal line detection ++ * @linev: strength of the vertical line detection ++ * @peak: strength of the peak detection ++ */ ++struct stm32_dcmipp_isp_dm_cfg { ++ __u32 en; ++ __u8 edge; ++ __u8 lineh; ++ __u8 linev; ++ __u8 peak; ++}; ++ ++enum stm32_dcmipp_isp_cc_clamp { ++ STM32_DCMIPP_ISP_CC_CLAMP_DISABLED, ++ STM32_DCMIPP_ISP_CC_CLAMP_Y235_U240_V240, ++ STM32_DCMIPP_ISP_CC_CLAMP_YUV235, ++}; ++ ++/** ++ * struct stm32_dcmipp_isp_cc_cfg - STM32 DCMIPP ISP color conversion ++ * ++ * @en: enable / disable the color conversion block ++ * @clamp: clamp configuration (from enum stm32_dcmipp_isp_cc_clamp) ++ * @rr: row 1 col 1 value of the matrix ++ * @rg: row 1 col 2 value of the matrix ++ * @rb: row 1 col 3 value of the matrix ++ * @ra: row 1 added value of the matrix ++ * @gr: row 2 col 1 value of the matrix ++ * @gg: row 2 col 2 value of the matrix ++ * @gb: row 2 col 3 value of the matrix ++ * @ga: row 2 added value of the matrix ++ * @br: row 3 col 1 value of the matrix ++ * @bg: row 3 col 2 value of the matrix ++ * @bb: row 3 col 3 value of the matrix ++ * @ba: row 3 added value of the matrix ++ */ ++struct stm32_dcmipp_isp_cc_cfg { ++ __u32 en; ++ __u32 clamp; ++ __u16 rr; ++ __u16 rg; ++ __u16 rb; ++ __u16 ra; ++ __u16 gr; ++ __u16 gg; ++ __u16 gb; ++ __u16 ga; ++ __u16 br; ++ __u16 bg; ++ __u16 bb; ++ __u16 ba; ++}; ++ ++/** ++ * struct stm32_dcmipp_isp_ce_cfg - STM32 DCMIPP ISP contrast enhancement ++ * ++ * @en: enable / disable the contrast enhancement block ++ * @lum: 9 elements table of luminance enhancement (value 16 is neutral) ++ */ ++struct stm32_dcmipp_isp_ce_cfg { ++ __u32 en; ++ __u8 lum[9]; ++}; ++ ++/** ++ * struct stm32_dcmipp_isp_ctrls_cfg - STM32 DCMIPP ISP Controls ++ * ++ * @bpr_cfg: configuration of the bad pixel removal block ++ * @blc_cfg: configuration of the black level correction block ++ * @ex_cfg: configuration of the exposure block ++ * @dm_cfg: configuration of the demosaicing filters block ++ * @cc_cfg: configuration of the color conversion block ++ * @ce_cfg: configuration of the contrast enhancement block ++ */ ++struct stm32_dcmipp_isp_ctrls_cfg { ++ struct stm32_dcmipp_isp_bpr_cfg bpr_cfg; ++ struct stm32_dcmipp_isp_blc_cfg blc_cfg; ++ struct stm32_dcmipp_isp_ex_cfg ex_cfg; ++ struct stm32_dcmipp_isp_dm_cfg dm_cfg; ++ struct stm32_dcmipp_isp_cc_cfg cc_cfg; ++ struct stm32_dcmipp_isp_ce_cfg ce_cfg; ++}; ++ ++/** ++ * struct stm32_dcmipp_params_cfg - STM32 DCMIPP ISP Input Parameters Meta Data ++ * ++ * @module_cfg_update: mask the config bits of which module should be updated ++ * @ctrls: configuration of other ISP blocks ++ */ ++struct stm32_dcmipp_params_cfg { ++ __u32 module_cfg_update; ++ ++ struct stm32_dcmipp_isp_ctrls_cfg ctrls; ++}; ++ ++/** ++ * struct stm32_dcmipp_stat_avr_bins - average & bins statistics ++ * ++ * @average_rgb[3]: average value of R/G/B components ++ * @bins[12]: 12 values histogram ++ */ ++struct stm32_dcmipp_stat_avr_bins { ++ __u32 average_RGB[3]; ++ __u32 bins[12]; ++}; ++ ++/** ++ * struct stm32_dcmipp_stat_buf - statistics buffer ++ * ++ * @pre: average & bins statistics at pre-demosaicing location ++ * @post: average & bins statistics at post-demosaicing location ++ * @bad_pixel_count: number of bad pixels detected in the frame ++ */ ++struct stm32_dcmipp_stat_buf { ++ struct stm32_dcmipp_stat_avr_bins pre; ++ struct stm32_dcmipp_stat_avr_bins post; ++ __u32 bad_pixel_count; ++}; ++ ++#endif +diff --git a/include/linux/v4l2-controls.h b/include/linux/v4l2-controls.h +index b9f64810..23772e04 100644 +--- a/include/linux/v4l2-controls.h ++++ b/include/linux/v4l2-controls.h +@@ -1218,6 +1218,47 @@ enum v4l2_jpeg_chroma_subsampling { + #define V4L2_CID_DEINTERLACING_MODE (V4L2_CID_IMAGE_PROC_CLASS_BASE + 4) + #define V4L2_CID_DIGITAL_GAIN (V4L2_CID_IMAGE_PROC_CLASS_BASE + 5) + ++#define V4L2_CID_ISP_STAT_REGION (V4L2_CID_IMAGE_PROC_CLASS_BASE + 10) ++/** ++ * struct v4l2_ctrl_isp_stat_region - Region where ISP statistics are collected ++ * ++ * @nb_regions: number of regions ++ * @top: top coordinate of a region ++ * @left: left coordinate of a region ++ * @width: width of a region ++ * @height: height of a region ++ */ ++struct v4l2_ctrl_isp_stat_region { ++ __u8 nb_regions; ++ __u32 top[25]; ++ __u32 left[25]; ++ __u32 width[25]; ++ __u32 height[25]; ++}; ++ ++#define V4L2_CID_ISP_STAT_AVG_FILTER (V4L2_CID_IMAGE_PROC_CLASS_BASE + 12) ++enum v4l2_isp_stat_avg_filter { ++ V4L2_STAT_AVG_FILTER_NONE = 0, ++ V4L2_STAT_AVG_FILTER_EXCL16 = 1, ++ V4L2_STAT_AVG_FILTER_EXCL32 = 2, ++ V4L2_STAT_AVG_FILTER_EXCL64 = 3, ++}; ++ ++#define V4L2_CID_ISP_STAT_BIN_COMP (V4L2_CID_IMAGE_PROC_CLASS_BASE + 13) ++enum v4l2_isp_stat_bin_comp { ++ V4L2_STAT_BIN_COMP_R = 0, ++ V4L2_STAT_BIN_COMP_G = 1, ++ V4L2_STAT_BIN_COMP_B = 2, ++ V4L2_STAT_BIN_COMP_L = 3, ++}; ++ ++#define V4L2_CID_ISP_STAT_PROFILE (V4L2_CID_IMAGE_PROC_CLASS_BASE + 14) ++enum v4l2_isp_stat_profile { ++ V4L2_STAT_PROFILE_FULL = 0, ++ V4L2_STAT_PROFILE_AVERAGE_PRE = 1, ++ V4L2_STAT_PROFILE_AVERAGE_POST = 2, ++}; ++ + /* DV-class control IDs defined by V4L2 */ + #define V4L2_CID_DV_CLASS_BASE (V4L2_CTRL_CLASS_DV | 0x900) + #define V4L2_CID_DV_CLASS (V4L2_CTRL_CLASS_DV | 1) +diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h +index 0b5482a0..acc17e57 100644 +--- a/include/linux/videodev2.h ++++ b/include/linux/videodev2.h +@@ -837,6 +837,10 @@ struct v4l2_pix_format { + /* The metadata format identifier for FE stats buffers. */ + #define V4L2_META_FMT_RPI_FE_STATS v4l2_fourcc('R', 'P', 'F', 'S') + ++/* Vendor specific - used for STM32_DCMIPP camera sub-system */ ++#define V4L2_META_FMT_ST_DCMIPP_ISP_PARAMS v4l2_fourcc('S', 'T', 'I', 'P') /* STM32 DCMIPP ISP Parameters */ ++#define V4L2_META_FMT_ST_DCMIPP_ISP_STAT v4l2_fourcc('S', 'T', 'I', 'S') /* STM32 DCMIPP ISP Statistics */ ++ + /* priv field value to indicates that subsequent fields are valid. */ + #define V4L2_PIX_FMT_PRIV_MAGIC 0xfeedcafe + +@@ -1880,6 +1884,7 @@ enum v4l2_ctrl_type { + V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX = 0x0273, + V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS = 0x0274, + ++ V4L2_CTRL_TYPE_ISP_STAT_REGION = 0x0310, + V4L2_CTRL_TYPE_AV1_SEQUENCE = 0x280, + V4L2_CTRL_TYPE_AV1_TILE_GROUP_ENTRY = 0x281, + V4L2_CTRL_TYPE_AV1_FRAME = 0x282, +diff --git a/meson.build b/meson.build +index 1902ea2f..c0f49810 100644 +--- a/meson.build ++++ b/meson.build +@@ -206,6 +206,7 @@ pipelines_support = { + 'rkisp1': arch_arm, + 'rpi/vc4': arch_arm, + 'simple': arch_arm, ++ 'dcmipp': arch_arm, + 'uvcvideo': ['any'], + 'vimc': ['test'], + } +diff --git a/meson_options.txt b/meson_options.txt +index 7aa41249..0674d2b2 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -32,7 +32,7 @@ option('gstreamer', + + option('ipas', + type : 'array', +- choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'simple', 'vimc'], ++ choices : ['dcmipp', 'ipu3', 'rkisp1', 'rpi/vc4', 'simple', 'vimc'], + description : 'Select which IPA modules to build') + + option('lc-compliance', +@@ -53,7 +53,8 @@ option('pipelines', + 'rpi/vc4', + 'simple', + 'uvcvideo', +- 'vimc' ++ 'vimc', ++ 'dcmipp' + ], + description : 'Select which pipeline handlers to build. If this is set to "auto", all the pipelines applicable to the target architecture will be built. If this is set to "all", all the pipelines will be built. If both are selected then "all" will take precedence.') + +@@ -86,3 +87,8 @@ option('v4l2', + type : 'boolean', + value : false, + description : 'Compile the V4L2 compatibility layer') ++ ++option('evision_algo', ++ type : 'boolean', ++ value : false, ++ description : 'Compile dcmipp with algorithms from evision libraries') +diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp +index f13355ba..35e2451a 100644 +--- a/src/apps/cam/camera_session.cpp ++++ b/src/apps/cam/camera_session.cpp +@@ -360,8 +360,15 @@ int CameraSession::queueRequest(Request *request) + if (captureLimit_ && queueCount_ >= captureLimit_) + return 0; + +- if (script_) +- request->controls() = script_->frameControls(queueCount_); ++ if (script_) { ++ const ControlList &controls = script_->frameControls(queueCount_); ++ for (auto const &ctrl : controls) ++ std::cout << "\tRequest ctrl: " ++ << controls::controls.at(ctrl.first)->name() ++ << " = " << ctrl.second.toString() ++ << std::endl; ++ request->controls() = controls; ++ } + + queueCount_++; + +diff --git a/src/apps/common/options.cpp b/src/apps/common/options.cpp +index f6d4f006..ab19aa3d 100644 +--- a/src/apps/common/options.cpp ++++ b/src/apps/common/options.cpp +@@ -879,8 +879,8 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv) + * Allocate short and long options arrays large enough to contain all + * options. + */ +- char *shortOptions = (char*)malloc(optionsMap_.size() * 3 + 2); +- struct option *longOptions = (struct option*)malloc(sizeof(struct option) * (optionsMap_.size() + 1)); ++ char shortOptions[optionsMap_.size() * 3 + 2]; ++ struct option longOptions[optionsMap_.size() + 1]; + unsigned int ids = 0; + unsigned int idl = 0; + +@@ -935,16 +935,12 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv) + std::cerr << argv[optind - 1] << std::endl; + + usage(); +- free(shortOptions); +- free(longOptions); + return options; + } + + const Option &option = *optionsMap_[c]; + if (!parseValue(option, optarg, &options)) { + usage(); +- free(shortOptions); +- free(longOptions); + return options; + } + } +@@ -953,14 +949,10 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv) + std::cerr << "Invalid non-option argument '" << argv[optind] + << "'" << std::endl; + usage(); +- free(shortOptions); +- free(longOptions); + return options; + } + + options.valid_ = true; +- free(shortOptions); +- free(longOptions); + return options; + } + +diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp +index ec4da435..d7ac64cb 100644 +--- a/src/gstreamer/gstlibcamera-utils.cpp ++++ b/src/gstreamer/gstlibcamera-utils.cpp +@@ -322,6 +322,22 @@ bare_structure_from_format(const PixelFormat &format) + case formats::SGBRG8: + case formats::SGRBG8: + case formats::SRGGB8: ++ case formats::SBGGR10: ++ case formats::SGBRG10: ++ case formats::SGRBG10: ++ case formats::SRGGB10: ++ case formats::SBGGR12: ++ case formats::SGBRG12: ++ case formats::SGRBG12: ++ case formats::SRGGB12: ++ case formats::SBGGR14: ++ case formats::SGBRG14: ++ case formats::SGRBG14: ++ case formats::SRGGB14: ++ case formats::SBGGR16: ++ case formats::SGBRG16: ++ case formats::SGRBG16: ++ case formats::SRGGB16: + return gst_structure_new("video/x-bayer", "format", G_TYPE_STRING, + bayer_format_to_string(format), nullptr); + +diff --git a/src/gstreamer/gstlibcamerapad.cpp b/src/gstreamer/gstlibcamerapad.cpp +index 7b22aebe..af0fe477 100644 +--- a/src/gstreamer/gstlibcamerapad.cpp ++++ b/src/gstreamer/gstlibcamerapad.cpp +@@ -99,6 +99,10 @@ gst_libcamera_stream_role_get_type() + static_cast(StreamRole::Viewfinder), + "libcamera::Viewfinder", + "view-finder", ++ }, { ++ static_cast(StreamRole::Raw), ++ "libcamera::Raw", ++ "raw", + }, + { 0, NULL, NULL } + }; +diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp +index 6a95b6af..164092bc 100644 +--- a/src/gstreamer/gstlibcamerasrc.cpp ++++ b/src/gstreamer/gstlibcamerasrc.cpp +@@ -30,6 +30,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -129,6 +130,9 @@ struct GstLibcameraSrcState { + ControlList initControls_; + guint group_id_; + ++ GMutex controlsLock_; /* Protects pendingControls_ */ ++ ControlList pendingControls_; ++ + int queueRequest(); + void requestCompleted(Request *request); + int processRequest(); +@@ -144,6 +148,48 @@ struct _GstLibcameraSrc { + gchar *camera_name; + controls::AfModeEnum auto_focus_mode = controls::AfModeManual; + ++ struct LibCameraControls { ++ /* Add all ISP controls */ ++ int hwrevision[2]; ++ int decimation; ++ bool black_level_enable; ++ int black_level_values[3]; ++ libcamera::Rectangle statistic_area; ++ bool contrast_enable; ++ int contrast_values[9]; ++ bool aec_algo_enable; ++ float aec_algo_exposure_compensation; ++ int aec_algo_exposure_target; ++ int aec_algo_antiflicker_frequency; ++ float sensor_gain_dB; ++ int sensor_exposure; ++ int sensor_delay; ++ int sensor_delay_measure; ++ bool badpix_enable; ++ int badpix_strength; ++ int badpix_count; ++ int badpix_algo_threshold; ++ bool ispgain_enable; ++ int ispgain_values[3]; ++ bool ccm_enable; ++ int ccm_values[9]; ++ bool awb_algo_enable; ++ std::array awb_algo_profile_names; ++ std::array awb_algo_profile_color_temps; ++ std::array awb_algo_profile_isp_gains; ++ std::array awb_algo_profile_ccms; ++ std::string awb_current_profile_name; ++ int awb_current_profile_color_temp; ++ bool demosaicing_enable; ++ std::array demosaicing_filters; ++ int statistic_profile; ++ std::array statistic_avg_up; ++ std::array statistic_avg_down; ++ std::array statistic_hist_up; ++ std::array statistic_hist_down; ++ bool gamma_enable; ++ } ctrl; ++ + std::atomic pending_eos; + + GstLibcameraSrcState *state; +@@ -155,9 +201,53 @@ enum { + PROP_0, + PROP_CAMERA_NAME, + PROP_AUTO_FOCUS_MODE, ++ PROP_HW_REVISION, ++ PROP_DECIMATION, ++ PROP_BLACK_LEVEL_ENABLE, ++ PROP_BLACK_LEVEL_VALUES, ++ PROP_STATISTIC_AREA, ++ PROP_CONTRAST_ENABLE, ++ PROP_CONTRAST_VALUES, ++ PROP_AEC_ALGO_ENABLE, ++ PROP_AEC_ALGO_EXPOSURE_COMPENSATION, ++ PROP_AEC_ALGO_EXPOSURE_TARGET, ++ PROP_AEC_ALGO_ANTIFLICKER_FREQUENCY, ++ PROP_SENSOR_GAIN, ++ PROP_SENSOR_EXPOSURE, ++ PROP_SENSOR_DELAY, ++ PROP_SENSOR_DELAY_MEASURE, ++ PROP_DO_SENSOR_DELAY_MEASURE, ++ PROP_BADPIX_ENABLE, ++ PROP_BADPIX_STRENGTH, ++ PROP_BADPIX_COUNT, ++ PROP_BADPIX_ALGO_THRESHOLD, ++ PROP_ISP_GAIN_ENABLE, ++ PROP_ISP_GAIN_VALUES, ++ PROP_CCM_ENABLE, ++ PROP_CCM_VALUES, ++ PROP_AWB_ALGO_ENABLE, ++ PROP_AWB_ALGO_PROFILE_NAMES, ++ PROP_AWB_ALGO_PROFILE_COLOR_TEMPS, ++ PROP_AWB_ALGO_PROFILE_ISP_GAINS, ++ PROP_AWB_ALGO_PROFILE_CCMS, ++ PROP_AWB_CURRENT_PROFILE_NAME, ++ PROP_AWB_CURRENT_PROFILE_COLOR_TEMP, ++ PROP_DEMOSAICING_ENABLE, ++ PROP_DEMOSAICING_FILTERS, ++ PROP_STATISTIC_PROFILE, ++ PROP_STATISTIC_GET_AVERAGE_UP, ++ PROP_STATISTIC_GET_AVERAGE_DOWN, ++ PROP_STATISTIC_GET_HISTOGRAM_UP, ++ PROP_STATISTIC_GET_HISTOGRAM_DOWN, ++ PROP_GAMMA_ENABLE, + }; + ++static void gst_libcamera_src_child_proxy_init(gpointer g_iface, ++ gpointer iface_data); ++ + G_DEFINE_TYPE_WITH_CODE(GstLibcameraSrc, gst_libcamera_src, GST_TYPE_ELEMENT, ++ G_IMPLEMENT_INTERFACE(GST_TYPE_CHILD_PROXY, ++ gst_libcamera_src_child_proxy_init) + GST_DEBUG_CATEGORY_INIT(source_debug, "libcamerasrc", 0, + "libcamera Source")) + +@@ -203,7 +293,19 @@ int GstLibcameraSrcState::queueRequest() + } + + GST_TRACE_OBJECT(src_, "Requesting buffers"); +- cam_->queueRequest(wrap->request_.get()); ++ ++ Request *req = wrap->request_.get(); ++ ++ /* Add then clear pending controls to the request */ ++ ControlList controls; ++ { ++ GLibLocker locker(&src_->state->controlsLock_); ++ controls = src_->state->pendingControls_; ++ src_->state->pendingControls_.clear(); ++ } ++ req->controls() = controls; ++ ++ cam_->queueRequest(req); + + { + GLibLocker locker(&lock_); +@@ -234,6 +336,176 @@ GstLibcameraSrcState::requestCompleted(Request *request) + return; + } + ++ /* Update the properties from the libcamera metadata */ ++ const auto HwRevision = request->metadata().get(controls::draft::PipelineHwRevision); ++ if (HwRevision) { ++ src_->ctrl.hwrevision[0] = *HwRevision >> 4; /* Major */ ++ src_->ctrl.hwrevision[1] = *HwRevision & 0x0F; /* Minor */ ++ } ++ const auto IspDecimation = request->metadata().get(controls::draft::IspDecimationRatio); ++ if (IspDecimation) { ++ src_->ctrl.decimation = *IspDecimation; ++ } ++ const auto BlackLevelEnable = request->metadata().get(controls::draft::BlackLevelCorrectionEnable); ++ if (BlackLevelEnable) { ++ src_->ctrl.black_level_enable = *BlackLevelEnable; ++ } ++ const auto BlackLevel = request->metadata().get(controls::draft::BlackLevelCorrectionLevels); ++ if (BlackLevel) { ++ src_->ctrl.black_level_values[0] = (*BlackLevel)[0]; ++ src_->ctrl.black_level_values[1] = (*BlackLevel)[1]; ++ src_->ctrl.black_level_values[2] = (*BlackLevel)[2]; ++ } ++ const auto StatisticArea = request->metadata().get(controls::draft::StatisticArea); ++ if (StatisticArea) { ++ src_->ctrl.statistic_area = *StatisticArea; ++ } ++ const auto ContrastEnable = request->metadata().get(controls::draft::ContrastLuminanceEnable); ++ if (ContrastEnable) { ++ src_->ctrl.contrast_enable = *ContrastEnable; ++ } ++ const auto ContrastLevel = request->metadata().get(controls::draft::ContrastLuminance); ++ if (ContrastLevel) { ++ for (unsigned int i = 0; i < 9; i++) { ++ src_->ctrl.contrast_values[i] = (*ContrastLevel)[i]; ++ } ++ } ++ const auto AecAlgoEnable = request->metadata().get(controls::AeEnable); ++ if (AecAlgoEnable) { ++ src_->ctrl.aec_algo_enable = *AecAlgoEnable; ++ } ++ const auto AecAlgoExposureCompensation = request->metadata().get(controls::ExposureValue); ++ if (AecAlgoExposureCompensation) { ++ src_->ctrl.aec_algo_exposure_compensation = *AecAlgoExposureCompensation; ++ } ++ const auto AecAlgoExposureTarget = request->metadata().get(controls::draft::AeExposureTarget); ++ if (AecAlgoExposureTarget) { ++ src_->ctrl.aec_algo_exposure_target = *AecAlgoExposureTarget; ++ } ++ const auto AecAntiFlickerFrequency = request->metadata().get(controls::draft::AntiFlickerFreq); ++ if (AecAntiFlickerFrequency) { ++ src_->ctrl.aec_algo_antiflicker_frequency = *AecAntiFlickerFrequency; ++ } ++ const auto SensorGaindB = request->metadata().get(controls::draft::AnalogueGain_dB); ++ if (SensorGaindB) { ++ src_->ctrl.sensor_gain_dB = *SensorGaindB; ++ } ++ const auto SensorExposure = request->metadata().get(controls::ExposureTime); ++ if (SensorExposure) { ++ src_->ctrl.sensor_exposure = *SensorExposure; ++ } ++ const auto SensorDelay = request->metadata().get(controls::draft::SensorDelay); ++ if (SensorDelay) { ++ src_->ctrl.sensor_delay = *SensorDelay; ++ } ++ const auto SensorDelayMeasure = request->metadata().get(controls::draft::SensorDelayMeasure); ++ if (SensorDelayMeasure) { ++ src_->ctrl.sensor_delay_measure = *SensorDelayMeasure; ++ } ++ const auto BadPixEnable = request->metadata().get(controls::draft::BadPixelRemovalEnable); ++ if (BadPixEnable) { ++ src_->ctrl.badpix_enable = *BadPixEnable; ++ } ++ const auto BadPixStrength = request->metadata().get(controls::draft::BadPixelRemovalStrength); ++ if (BadPixStrength) { ++ src_->ctrl.badpix_strength = *BadPixStrength; ++ } ++ const auto BadPixCount = request->metadata().get(controls::draft::BadPixelRemovalCount); ++ if (BadPixCount) { ++ src_->ctrl.badpix_count = *BadPixCount; ++ } ++ const auto BadPixAlgoThreshold = request->metadata().get(controls::draft::BadPixelRemovalThreshold); ++ if (BadPixAlgoThreshold) { ++ src_->ctrl.badpix_algo_threshold = *BadPixAlgoThreshold; ++ } ++ const auto IspGainEnable = request->metadata().get(controls::draft::ColourGains3Enable); ++ if (IspGainEnable) { ++ src_->ctrl.ispgain_enable = *IspGainEnable; ++ } ++ const auto IspGainValues = request->metadata().get(controls::draft::ColourGains3); ++ if (IspGainValues) { ++ for (unsigned int i = 0; i < 3; i++) { ++ src_->ctrl.ispgain_values[i] = (*IspGainValues)[i]; ++ } ++ } ++ const auto CcmEnable = request->metadata().get(controls::draft::ColourCorrectionEnable); ++ if (CcmEnable) { ++ src_->ctrl.ccm_enable = *CcmEnable; ++ } ++ const auto CcmValues = request->metadata().get(controls::draft::ColourCorrection); ++ if (CcmValues) { ++ for (unsigned int i = 0; i < 9; i++) { ++ src_->ctrl.ccm_values[i] = (*CcmValues)[i]; ++ } ++ } ++ const auto AwbAlgoEnable = request->metadata().get(controls::AwbEnable); ++ if (AwbAlgoEnable) { ++ src_->ctrl.awb_algo_enable = *AwbAlgoEnable; ++ } ++ const auto AwbAlgoProfileNames = request->metadata().get(controls::draft::AwbProfileName); ++ if (AwbAlgoProfileNames) { ++ /* The Controls class does not support array of string. So, split the concatenated string */ ++ std::string token; ++ std::stringstream ss(*AwbAlgoProfileNames); ++ for (int i = 0; i < (int)src_->ctrl.awb_algo_profile_names.size(); i++) { ++ if (!getline(ss, token, '$')) ++ token = ""; ++ src_->ctrl.awb_algo_profile_names[i] = token; ++ } ++ } ++ const auto AwbAlgoProfileColorTemps = request->metadata().get(controls::draft::AwbReferenceColorTemperature); ++ if (AwbAlgoProfileColorTemps) { ++ std::copy(std::begin(*AwbAlgoProfileColorTemps), std::end(*AwbAlgoProfileColorTemps), src_->ctrl.awb_algo_profile_color_temps.begin()); ++ } ++ const auto AwbAlgoProfileIspGains = request->metadata().get(controls::draft::AwbColourGains3); ++ if (AwbAlgoProfileIspGains) { ++ std::copy(std::begin(*AwbAlgoProfileIspGains), std::end(*AwbAlgoProfileIspGains), src_->ctrl.awb_algo_profile_isp_gains.begin()); ++ } ++ const auto AwbAlgoProfilesCcms = request->metadata().get(controls::draft::AwbColourCorrection); ++ if (AwbAlgoProfilesCcms) { ++ std::copy(std::begin(*AwbAlgoProfilesCcms), std::end(*AwbAlgoProfilesCcms), src_->ctrl.awb_algo_profile_ccms.begin()); ++ } ++ const auto AwbCurProfileName = request->metadata().get(controls::draft::AwbCurrentProfileName); ++ if (AwbCurProfileName) { ++ src_->ctrl.awb_current_profile_name = *AwbCurProfileName; ++ } ++ const auto AwbCurProfileColorTemp = request->metadata().get(controls::ColourTemperature); ++ if (AwbCurProfileColorTemp) { ++ src_->ctrl.awb_current_profile_color_temp = *AwbCurProfileColorTemp; ++ } ++ const auto DemosaicingEnable = request->metadata().get(controls::draft::DemosaicingEnable); ++ if (DemosaicingEnable) { ++ src_->ctrl.demosaicing_enable = *DemosaicingEnable; ++ } ++ const auto DemosaicingFilters = request->metadata().get(controls::draft::DemosaicingFilter); ++ if (DemosaicingFilters) { ++ std::copy(std::begin(*DemosaicingFilters), std::end(*DemosaicingFilters), src_->ctrl.demosaicing_filters.begin()); ++ } ++ const auto StatProfile = request->metadata().get(controls::draft::StatisticProfile); ++ if (StatProfile) { ++ src_->ctrl.statistic_profile = *StatProfile; ++ } ++ const auto StatAvgUp = request->metadata().get(controls::draft::StatisticAverageUp); ++ if (StatAvgUp) { ++ std::copy(std::begin(*StatAvgUp), std::end(*StatAvgUp), src_->ctrl.statistic_avg_up.begin()); ++ } ++ const auto StatAvgDown = request->metadata().get(controls::draft::StatisticAverageDown); ++ if (StatAvgDown) { ++ std::copy(std::begin(*StatAvgDown), std::end(*StatAvgDown), src_->ctrl.statistic_avg_down.begin()); ++ } ++ const auto StatHistUp = request->metadata().get(controls::draft::StatisticBinsUp); ++ if (StatHistUp) { ++ std::copy(std::begin(*StatHistUp), std::end(*StatHistUp), src_->ctrl.statistic_hist_up.begin()); ++ } ++ const auto StatHistDown = request->metadata().get(controls::draft::StatisticBinsDown); ++ if (StatHistDown) { ++ std::copy(std::begin(*StatHistDown), std::end(*StatHistDown), src_->ctrl.statistic_hist_down.begin()); ++ } ++ const auto GammaEnable = request->metadata().get(controls::draft::GammaCorrectionEnable); ++ if (GammaEnable) { ++ src_->ctrl.gamma_enable = *GammaEnable; ++ } ++ + if (GST_ELEMENT_CLOCK(src_)) { + int64_t timestamp = request->metadata().get(controls::SensorTimestamp).value_or(0); + +@@ -669,6 +941,7 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, + } + } + ++ /* Note: controls provided at start() are dropped by IPA */ + ret = state->cam_->start(&state->initControls_); + if (ret) { + GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, +@@ -730,6 +1003,7 @@ gst_libcamera_src_set_property(GObject *object, guint prop_id, + { + GLibLocker lock(GST_OBJECT(object)); + GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object); ++ GstLibcameraSrcState *state = self->state; + + switch (prop_id) { + case PROP_CAMERA_NAME: +@@ -739,6 +1013,252 @@ gst_libcamera_src_set_property(GObject *object, guint prop_id, + case PROP_AUTO_FOCUS_MODE: + self->auto_focus_mode = static_cast(g_value_get_enum(value)); + break; ++ case PROP_BLACK_LEVEL_ENABLE: ++ { ++ bool enable = g_value_get_boolean(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::BlackLevelCorrectionEnable, ++ enable); ++ break; ++ } ++ case PROP_BLACK_LEVEL_VALUES: ++ { ++ int blR = g_value_get_int(gst_value_array_get_value(value, 0)); ++ int blG = g_value_get_int(gst_value_array_get_value(value, 1)); ++ int blB = g_value_get_int(gst_value_array_get_value(value, 2)); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::BlackLevelCorrectionLevels, ++ { blR, blG, blB }); ++ break; ++ } ++ case PROP_STATISTIC_AREA: ++ { ++ libcamera::Rectangle statarea; ++ statarea.x = g_value_get_int(gst_value_array_get_value(value, 0)); ++ statarea.y = g_value_get_int(gst_value_array_get_value(value, 1)); ++ statarea.width = g_value_get_int(gst_value_array_get_value(value, 2)); ++ statarea.height = g_value_get_int(gst_value_array_get_value(value, 3)); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::StatisticArea, statarea); ++ break; ++ } ++ case PROP_CONTRAST_ENABLE: ++ { ++ bool enable = g_value_get_boolean(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::ContrastLuminanceEnable, ++ enable); ++ break; ++ } ++ case PROP_CONTRAST_VALUES: ++ { ++ int LUM[9]; ++ for (unsigned int i = 0; i < 9; i++) { ++ LUM[i] = g_value_get_int(gst_value_array_get_value(value, i)); ++ } ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::ContrastLuminance, ++ { LUM[0], LUM[1], LUM[2], LUM[3], LUM[4], LUM[5], LUM[6], LUM[7], LUM[8] }); ++ break; ++ } ++ case PROP_AEC_ALGO_ENABLE: ++ { ++ bool enable = g_value_get_boolean(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::AeEnable, ++ enable); ++ break; ++ } ++ case PROP_AEC_ALGO_EXPOSURE_COMPENSATION: ++ { ++ float aec_algo_exposure_compensation; ++ aec_algo_exposure_compensation = g_value_get_float(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::ExposureValue, aec_algo_exposure_compensation); ++ break; ++ } ++ case PROP_AEC_ALGO_ANTIFLICKER_FREQUENCY: ++ { ++ int aec_algo_antiflicker_frequency = g_value_get_int(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::AntiFlickerFreq, aec_algo_antiflicker_frequency); ++ break; ++ } ++ case PROP_SENSOR_GAIN: ++ { ++ float gain_dB; ++ gain_dB = g_value_get_float(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::AnalogueGain_dB, gain_dB); ++ break; ++ } ++ case PROP_SENSOR_EXPOSURE: ++ { ++ int exposure_time; ++ exposure_time = g_value_get_int(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::ExposureTime, exposure_time); ++ break; ++ } ++ case PROP_SENSOR_DELAY: ++ { ++ int sensor_delay; ++ sensor_delay = g_value_get_int(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::SensorDelay, sensor_delay); ++ break; ++ } ++ case PROP_DO_SENSOR_DELAY_MEASURE: ++ { ++ bool do_sensor_delay_measure; ++ do_sensor_delay_measure = g_value_get_boolean(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::DoSensorDelayMeasure, do_sensor_delay_measure); ++ break; ++ } ++ case PROP_BADPIX_ENABLE: ++ { ++ bool enable = g_value_get_boolean(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::BadPixelRemovalEnable, enable); ++ break; ++ } ++ case PROP_BADPIX_STRENGTH: ++ { ++ int strength; ++ strength = g_value_get_int(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::BadPixelRemovalStrength, strength); ++ break; ++ } ++ case PROP_BADPIX_ALGO_THRESHOLD: ++ { ++ int threshold; ++ threshold = g_value_get_int(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::BadPixelRemovalThreshold, threshold); ++ break; ++ } ++ case PROP_ISP_GAIN_ENABLE: ++ { ++ bool enable = g_value_get_boolean(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::ColourGains3Enable, enable); ++ break; ++ } ++ case PROP_ISP_GAIN_VALUES: ++ { ++ int ISPGain[3]; ++ for (unsigned int i = 0; i < 3; i++) { ++ ISPGain[i] = g_value_get_int(gst_value_array_get_value(value, i)); ++ } ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::ColourGains3, ++ { ISPGain[0], ISPGain[1], ISPGain[2] }); ++ break; ++ } ++ case PROP_CCM_ENABLE: ++ { ++ bool enable = g_value_get_boolean(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::ColourCorrectionEnable, enable); ++ break; ++ } ++ case PROP_CCM_VALUES: ++ { ++ int CCMCoeff[9]; ++ for (unsigned int i = 0; i < 9; i++) { ++ CCMCoeff[i] = g_value_get_int(gst_value_array_get_value(value, i)); ++ } ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::ColourCorrection, ++ { CCMCoeff[0], CCMCoeff[1], CCMCoeff[2], ++ CCMCoeff[3], CCMCoeff[4], CCMCoeff[5], ++ CCMCoeff[6], CCMCoeff[7], CCMCoeff[8] }); ++ break; ++ } ++ case PROP_AWB_ALGO_ENABLE: ++ { ++ bool enable = g_value_get_boolean(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::AwbEnable, enable); ++ break; ++ } ++ case PROP_AWB_ALGO_PROFILE_NAMES: ++ { ++ /* The Controls class does not support array of string. So, concatenate the strings in a single one */ ++ std::string concatProfileNames = ""; ++ for (unsigned int i = 0; i < 5; i++) { ++ if (!concatProfileNames.empty()) ++ concatProfileNames += '$'; ++ concatProfileNames += g_value_get_string(gst_value_array_get_value(value, i)); ++ } ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::AwbProfileName, concatProfileNames); ++ break; ++ } ++ case PROP_AWB_ALGO_PROFILE_COLOR_TEMPS: ++ { ++ std::array ColorTemps; ++ for (unsigned int i = 0; i < ColorTemps.size(); i++) { ++ ColorTemps[i] = g_value_get_int(gst_value_array_get_value(value, i)); ++ } ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::AwbReferenceColorTemperature, ColorTemps); ++ break; ++ } ++ case PROP_AWB_ALGO_PROFILE_ISP_GAINS: ++ { ++ std::array IspGains; ++ for (unsigned int i = 0; i < IspGains.size(); i++) { ++ IspGains[i] = g_value_get_int(gst_value_array_get_value(value, i)); ++ } ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::AwbColourGains3, IspGains); ++ break; ++ } ++ case PROP_AWB_ALGO_PROFILE_CCMS: ++ { ++ std::array Ccms; ++ for (unsigned int i = 0; i < Ccms.size(); i++) { ++ Ccms[i] = g_value_get_int(gst_value_array_get_value(value, i)); ++ } ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::AwbColourCorrection, Ccms); ++ break; ++ } ++ case PROP_DEMOSAICING_ENABLE: ++ { ++ bool enable = g_value_get_boolean(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::DemosaicingEnable, enable); ++ break; ++ } ++ case PROP_DEMOSAICING_FILTERS: ++ { ++ std::array Filters; ++ for (unsigned int i = 0; i < Filters.size(); i++) { ++ Filters[i] = g_value_get_int(gst_value_array_get_value(value, i)); ++ } ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::DemosaicingFilter, Filters); ++ break; ++ } ++ case PROP_STATISTIC_PROFILE: ++ { ++ int StatProfile; ++ StatProfile = g_value_get_int(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::StatisticProfile, StatProfile); ++ break; ++ } ++ case PROP_GAMMA_ENABLE: ++ { ++ bool enable = g_value_get_boolean(value); ++ GLibLocker locker(&state->controlsLock_); ++ state->pendingControls_.set(controls::draft::GammaCorrectionEnable, enable); ++ break; ++ } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; +@@ -759,6 +1279,262 @@ gst_libcamera_src_get_property(GObject *object, guint prop_id, GValue *value, + case PROP_AUTO_FOCUS_MODE: + g_value_set_enum(value, static_cast(self->auto_focus_mode)); + break; ++ case PROP_HW_REVISION: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < 2; ++i) { ++ g_value_set_int(&val, self->ctrl.hwrevision[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_DECIMATION: ++ g_value_set_int(value, self->ctrl.decimation); ++ break; ++ case PROP_BLACK_LEVEL_ENABLE: ++ g_value_set_boolean(value, self->ctrl.black_level_enable); ++ break; ++ case PROP_BLACK_LEVEL_VALUES: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < 3; ++i) { ++ g_value_set_int(&val, self->ctrl.black_level_values[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_STATISTIC_AREA: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ g_value_set_int(&val, self->ctrl.statistic_area.x); ++ gst_value_array_append_value(value, &val); ++ g_value_set_int(&val, self->ctrl.statistic_area.y); ++ gst_value_array_append_value(value, &val); ++ g_value_set_int(&val, self->ctrl.statistic_area.width); ++ gst_value_array_append_value(value, &val); ++ g_value_set_int(&val, self->ctrl.statistic_area.height); ++ gst_value_array_append_value(value, &val); ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_CONTRAST_ENABLE: ++ g_value_set_boolean(value, self->ctrl.contrast_enable); ++ break; ++ case PROP_CONTRAST_VALUES: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < 9; ++i) { ++ g_value_set_int(&val, self->ctrl.contrast_values[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_AEC_ALGO_ENABLE: ++ g_value_set_boolean(value, self->ctrl.aec_algo_enable); ++ break; ++ case PROP_AEC_ALGO_EXPOSURE_COMPENSATION: ++ g_value_set_float(value, self->ctrl.aec_algo_exposure_compensation); ++ break; ++ case PROP_AEC_ALGO_EXPOSURE_TARGET: ++ g_value_set_int(value, self->ctrl.aec_algo_exposure_target); ++ break; ++ case PROP_AEC_ALGO_ANTIFLICKER_FREQUENCY: ++ g_value_set_int(value, self->ctrl.aec_algo_antiflicker_frequency); ++ break; ++ case PROP_SENSOR_GAIN: ++ g_value_set_float(value, self->ctrl.sensor_gain_dB); ++ break; ++ case PROP_SENSOR_EXPOSURE: ++ g_value_set_int(value, self->ctrl.sensor_exposure); ++ break; ++ case PROP_SENSOR_DELAY: ++ g_value_set_int(value, self->ctrl.sensor_delay); ++ break; ++ case PROP_SENSOR_DELAY_MEASURE: ++ g_value_set_int(value, self->ctrl.sensor_delay_measure); ++ break; ++ case PROP_BADPIX_ENABLE: ++ g_value_set_boolean(value, self->ctrl.badpix_enable); ++ break; ++ case PROP_BADPIX_STRENGTH: ++ g_value_set_int(value, self->ctrl.badpix_strength); ++ break; ++ case PROP_BADPIX_COUNT: ++ g_value_set_int(value, self->ctrl.badpix_count); ++ break; ++ case PROP_BADPIX_ALGO_THRESHOLD: ++ g_value_set_int(value, self->ctrl.badpix_algo_threshold); ++ break; ++ case PROP_ISP_GAIN_ENABLE: ++ g_value_set_boolean(value, self->ctrl.ispgain_enable); ++ break; ++ case PROP_ISP_GAIN_VALUES: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < 3; ++i) { ++ g_value_set_int(&val, self->ctrl.ispgain_values[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_CCM_ENABLE: ++ g_value_set_boolean(value, self->ctrl.ccm_enable); ++ break; ++ case PROP_CCM_VALUES: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < 9; ++i) { ++ g_value_set_int(&val, self->ctrl.ccm_values[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_AWB_ALGO_ENABLE: ++ g_value_set_boolean(value, self->ctrl.awb_algo_enable); ++ break; ++ case PROP_AWB_ALGO_PROFILE_NAMES: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_STRING); ++ for (guint i = 0; i < self->ctrl.awb_algo_profile_names.size(); ++i) { ++ if (self->ctrl.awb_algo_profile_names[i].empty()) ++ g_value_set_string(&val, ""); ++ else ++ g_value_set_string(&val, self->ctrl.awb_algo_profile_names[i].c_str()); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_AWB_ALGO_PROFILE_COLOR_TEMPS: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < self->ctrl.awb_algo_profile_color_temps.size(); ++i) { ++ g_value_set_int(&val, self->ctrl.awb_algo_profile_color_temps[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_AWB_ALGO_PROFILE_ISP_GAINS: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < self->ctrl.awb_algo_profile_isp_gains.size(); ++i) { ++ g_value_set_int(&val, self->ctrl.awb_algo_profile_isp_gains[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_AWB_ALGO_PROFILE_CCMS: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < self->ctrl.awb_algo_profile_ccms.size(); ++i) { ++ g_value_set_int(&val, self->ctrl.awb_algo_profile_ccms[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_AWB_CURRENT_PROFILE_NAME: ++ g_value_set_string(value, self->ctrl.awb_current_profile_name.c_str()); ++ break; ++ case PROP_AWB_CURRENT_PROFILE_COLOR_TEMP: ++ g_value_set_int(value, self->ctrl.awb_current_profile_color_temp); ++ break; ++ case PROP_DEMOSAICING_ENABLE: ++ g_value_set_boolean(value, self->ctrl.demosaicing_enable); ++ break; ++ case PROP_DEMOSAICING_FILTERS: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < self->ctrl.demosaicing_filters.size(); ++i) { ++ g_value_set_int(&val, self->ctrl.demosaicing_filters[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_STATISTIC_PROFILE: ++ g_value_set_int(value, self->ctrl.statistic_profile); ++ break; ++ case PROP_STATISTIC_GET_AVERAGE_UP: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < self->ctrl.statistic_avg_up.size(); ++i) { ++ g_value_set_int(&val, self->ctrl.statistic_avg_up[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_STATISTIC_GET_AVERAGE_DOWN: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < self->ctrl.statistic_avg_down.size(); ++i) { ++ g_value_set_int(&val, self->ctrl.statistic_avg_down[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_STATISTIC_GET_HISTOGRAM_UP: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < self->ctrl.statistic_hist_up.size(); ++i) { ++ g_value_set_int(&val, self->ctrl.statistic_hist_up[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_STATISTIC_GET_HISTOGRAM_DOWN: ++ { ++ GValue val = G_VALUE_INIT; ++ g_value_reset (value); ++ g_value_init (&val, G_TYPE_INT); ++ for (guint i = 0; i < self->ctrl.statistic_hist_down.size(); ++i) { ++ g_value_set_int(&val, self->ctrl.statistic_hist_down[i]); ++ gst_value_array_append_value(value, &val); ++ } ++ g_value_unset (&val); ++ break; ++ } ++ case PROP_GAMMA_ENABLE: ++ g_value_set_boolean(value, self->ctrl.gamma_enable); ++ break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; +@@ -844,6 +1620,7 @@ gst_libcamera_src_finalize(GObject *object) + g_rec_mutex_clear(&self->stream_lock); + g_clear_object(&self->task); + g_mutex_clear(&self->state->lock_); ++ g_mutex_clear(&self->state->controlsLock_); + g_free(self->camera_name); + delete self->state; + +@@ -863,9 +1640,12 @@ gst_libcamera_src_init(GstLibcameraSrc *self) + gst_task_set_lock(self->task, &self->stream_lock); + + g_mutex_init(&state->lock_); ++ g_mutex_init(&state->controlsLock_); + +- state->srcpads_.push_back(gst_pad_new_from_template(templ, "src")); +- gst_element_add_pad(GST_ELEMENT(self), state->srcpads_.back()); ++ GstPad *pad = gst_pad_new_from_template(templ, "src"); ++ state->srcpads_.push_back(pad); ++ gst_element_add_pad(GST_ELEMENT(self), pad); ++ gst_child_proxy_child_added(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad)); + + GST_OBJECT_FLAG_SET(self, GST_ELEMENT_FLAG_SOURCE); + +@@ -896,6 +1676,8 @@ gst_libcamera_src_request_new_pad(GstElement *element, GstPadTemplate *templ, + return NULL; + } + ++ gst_child_proxy_child_added(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad)); ++ + return reinterpret_cast(g_steal_pointer(&pad)); + } + +@@ -904,6 +1686,8 @@ gst_libcamera_src_release_pad(GstElement *element, GstPad *pad) + { + GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element); + ++ gst_child_proxy_child_removed(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad)); ++ + GST_DEBUG_OBJECT(self, "Pad %" GST_PTR_FORMAT " being released", pad); + + { +@@ -963,4 +1747,338 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass) + static_cast(controls::AfModeManual), + G_PARAM_WRITABLE); + g_object_class_install_property(object_class, PROP_AUTO_FOCUS_MODE, spec); ++ ++ /* Add ISP properties */ ++ /* HW Revision values read property */ ++ spec = gst_param_spec_array("hw-revision", ++ "Hardware revsion ID", ++ "ISP Hardware revsion ID ('Major, Minor')", ++ g_param_spec_int ("hw-revision-value", "ISP Hardware revsion ID", ++ "One of Major, Minor value.", 0, 255, 0, ++ G_PARAM_READABLE), ++ G_PARAM_READABLE); ++ g_object_class_install_property(object_class, PROP_HW_REVISION, spec); ++ /* Decimation values read property */ ++ spec = g_param_spec_int("decimation-factor", ++ "Decimation factor", ++ "ISP Decimation factor ('1, 2, 4 or 8')", ++ 1, 8, 1, ++ G_PARAM_READABLE); ++ g_object_class_install_property(object_class, PROP_DECIMATION, spec); ++ /* Black level enable property */ ++ spec = g_param_spec_boolean("black-level-enable", ++ "Black level enable", ++ "ISP Black Level correction bloc activation", ++ false, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_BLACK_LEVEL_ENABLE, spec); ++ /* Black level values property */ ++ spec = gst_param_spec_array("black-level-values", ++ "Black level RGB values", ++ "ISP Black Level correction values ('')", ++ g_param_spec_int ("black-level-value", "Black level value", ++ "One of R, G, B value.", 0, 255, 0, ++ G_PARAM_READWRITE), ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_BLACK_LEVEL_VALUES, spec); ++ /* Satistic area property */ ++ spec = gst_param_spec_array("statistic-area", ++ "Statistic Area", ++ "ISP Statistic Area ('')", ++ g_param_spec_int ("statarea-value", "Satistic Area value", ++ "One of X0, Y0, XSize, YSize", 0, 4096, 0, ++ G_PARAM_READWRITE), ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_STATISTIC_AREA, spec); ++ /* Contrast enable property */ ++ spec = g_param_spec_boolean("contrast-enable", ++ "Contrast enable", ++ "ISP Contrast bloc activation", ++ false, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_CONTRAST_ENABLE, spec); ++ /* Contrast values property */ ++ spec = gst_param_spec_array("contrast-values", ++ "Contrast multiplication factor values", ++ "ISP Contrast multiplication factor values ('')", ++ g_param_spec_int ("contrast-value", "Contrast value", ++ "One of LUM_0, LUM_32, LUM_64, LUM_96, LUM_128, LUM_160, LUM_192, LUM_224, LUM_256 value.", 0, 394, 0, ++ G_PARAM_READWRITE), ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_CONTRAST_VALUES, spec); ++ /* AEC algo enable property */ ++ spec = g_param_spec_boolean("aec-algo-enable", ++ "AEC algo enable", ++ "Activation of the AEC algorithm", ++ false, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_AEC_ALGO_ENABLE, spec); ++ /* AEC algo exposure compensation */ ++ spec = g_param_spec_float("aec-algo-exposure-compensation", ++ "AEC exposure compensation", ++ "AEC exposure compensation when algo is enabled ('-2EV, -1.5EV, -1EV, -0.5EV, 0EV, +0.5EV, +1EV, +1.5EV, +2EV')", ++ -2, 2, 0, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_AEC_ALGO_EXPOSURE_COMPENSATION, spec); ++ /* AEC algo exposure target */ ++ spec = g_param_spec_int("aec-algo-exposure-target", ++ "AEC exposure target", ++ "AEC exposure target that depends on exposure compensation set", ++ 0, 255, 56, ++ G_PARAM_READABLE); ++ g_object_class_install_property(object_class, PROP_AEC_ALGO_EXPOSURE_TARGET, spec); ++ /* AEC anti-flicker frequency */ ++ spec = g_param_spec_int("aec-algo-antiflicker-frequency", ++ "AEC anti-flicker frequency", ++ "AEC anti-flicker frequency (0 for disabling the feature, 50Hz or 60Hz)", ++ 0, 60, 0, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_AEC_ALGO_ANTIFLICKER_FREQUENCY, spec); ++ /* Sensor gain property */ ++ spec = g_param_spec_float("sensor-gain", ++ "Sensor analog gain", ++ "Gain of the sensor in dB", ++ 0, 100, 0, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_SENSOR_GAIN, spec); ++ /* Sensor exposure time property */ ++ spec = g_param_spec_int("sensor-exposure", ++ "Sensor exposure time", ++ "Sensor exposure time in us", ++ 0, 100000, 0, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_SENSOR_EXPOSURE, spec); ++ /* Sensor delay property */ ++ spec = g_param_spec_int("sensor-delay", ++ "Sensor delay", ++ "Sensor delay in number of VSync", ++ 1, 255, 1, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_SENSOR_DELAY, spec); ++ /* Sensor delay measure property */ ++ spec = g_param_spec_int("sensor-delay-measure", ++ "Sensor delay measure", ++ "Sensor delay in number of VSync", ++ -1, 255, -1, ++ G_PARAM_READABLE); ++ g_object_class_install_property(object_class, PROP_SENSOR_DELAY_MEASURE, spec); ++ /* Do sensor delay measure property */ ++ spec = g_param_spec_boolean("do-sensor-delay-measure", ++ "Do sensor delay measure", ++ "Do sensor delay measure", ++ false, ++ G_PARAM_WRITABLE); ++ g_object_class_install_property(object_class, PROP_DO_SENSOR_DELAY_MEASURE, spec); ++ /* Bad pixel removal enable property */ ++ spec = g_param_spec_boolean("badpixel-enable", ++ "Bad pixel removal enable", ++ "Activation of the bad pixel removel block", ++ false, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_BADPIX_ENABLE, spec); ++ /* Bad pixel removal strength property */ ++ spec = g_param_spec_int("badpixel-strength", ++ "Bad pixel removal strength", ++ "Bad pixel removal filter strength", ++ 0, 7, 0, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_BADPIX_STRENGTH, spec); ++ /* Bad pixel removal count property */ ++ spec = g_param_spec_int("badpixel-count", ++ "Bad pixel removal count", ++ "Count of the number of bad pixel detected", ++ 0, 4095, 0, ++ G_PARAM_READABLE); ++ g_object_class_install_property(object_class, PROP_BADPIX_COUNT, spec); ++ /* Bad pixel removal algo threshold */ ++ spec = g_param_spec_int("badpixel-algo-threshold", ++ "Bad pixel removal algorithm threshold", ++ "Threshold defining the maximum number of bad pixel expected to be detected", ++ 0, 4095, 0, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_BADPIX_ALGO_THRESHOLD, spec); ++ /* ISP gain enable property */ ++ spec = g_param_spec_boolean("isp-gain-enable", ++ "ISP gain enable", ++ "ISP gain bloc activation", ++ false, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_ISP_GAIN_ENABLE, spec); ++ /* ISP gain values property */ ++ spec = gst_param_spec_array("isp-gain-values", ++ "ISP gain values", ++ "ISP gain multiplication factor values ('')", ++ g_param_spec_int ("isp-gain-value", "ISP gain value", ++ "One of ISPGainR, ISPGainG, ISPGainB value.", 0, 1600000000, 100000000, ++ G_PARAM_READWRITE), ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_ISP_GAIN_VALUES, spec); ++ /* CCM enable property */ ++ spec = g_param_spec_boolean("ccm-enable", ++ "Color correction matrix enable", ++ "ISP color correction matrix bloc activation", ++ false, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_CCM_ENABLE, spec); ++ /* CCM values property */ ++ spec = gst_param_spec_array("ccm-values", ++ "Color correction matrix values", ++ "ISP color correction matrix values ('')", ++ g_param_spec_int ("ccm-value", "CCM value", ++ "One of CCMCoeff11, CCMCoeff12, CCMCoeff13, CCMCoeff21, CCMCoeff22, CCMCoeff23, CCMCoeff31, CCMCoeff32, CCMCoeff33 value.", -399000000, 399000000, 0, ++ G_PARAM_READWRITE), ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_CCM_VALUES, spec); ++ /* AWB algo enable property */ ++ spec = g_param_spec_boolean("awb-algo-enable", ++ "AWB algo enable", ++ "Activation of the AWB algorithm", ++ false, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_AWB_ALGO_ENABLE, spec); ++ /* AWB algo profileID values property */ ++ spec = gst_param_spec_array("awb-algo-profile-names", ++ "AWB algo profile names", ++ "Array of AWB profile name (one entry per profile) used by the AWB algorithm ('')", ++ g_param_spec_string ("awb-algo-profile-name", "AWB profile name strings of 32bytes maximum", ++ "One of Name0, Name1, Name2, Name3, Name4", NULL, ++ G_PARAM_READWRITE), ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_AWB_ALGO_PROFILE_NAMES, spec); ++ /* AWB algo color temperature values property */ ++ spec = gst_param_spec_array("awb-algo-profile-color-temps", ++ "AWB algo color temperatures", ++ "Array of reference color tempeartures (one entry per profile) used by the AWB algorithm ('')", ++ g_param_spec_int ("awb-algo-profile-color-temp", "AWB profile color temperature value", ++ "One of ColorTemp0, ColorTemp1, ColorTemp2, ColorTemp3, ColorTemp4 value.", 0, 12000, 0, ++ G_PARAM_READWRITE), ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_AWB_ALGO_PROFILE_COLOR_TEMPS, spec); ++ /* AWB algo ISP gain values property */ ++ spec = gst_param_spec_array("awb-algo-profile-isp-gains", ++ "AWB algo ISP gains", ++ "Array of ISP gains (one entry per profile) used by the AWB algorithm ('')", ++ g_param_spec_int ("awb-algo-profile-isp-gain", "AWB profile ISP Gain value", ++ "One of ISPGain0R, ISPGain0G, ISPGain0B, ..., ISPGain4R, ISPGain4G, ISPGain4B value.", 0, 1600000000, 100000000, ++ G_PARAM_READWRITE), ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_AWB_ALGO_PROFILE_ISP_GAINS, spec); ++ /* AWB algo color correction matrix values property */ ++ spec = gst_param_spec_array("awb-algo-profile-ccms", ++ "AWB algo color correction matrix values", ++ "Array of color convertion matrices (one entry per profile) used by the AWB algorithm ('')", ++ g_param_spec_int ("awb-algo-profile-ccm", "AWB profile CCM value", ++ "One of CCM0Coeff11, CCM0Coeff12, ... , CCM0Coeff32, CCM0Coeff33, ... , CCM4Coeff11, CCM4Coeff12, ... , CCM4Coeff32, CCM4Coeff33 value.", -399000000, 399000000, 0, ++ G_PARAM_READWRITE), ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_AWB_ALGO_PROFILE_CCMS, spec); ++ /* AWB current profile name */ ++ spec = g_param_spec_string("awb-current-profile-name", ++ "AWB current profile name", ++ "Name of the current profile applied by the AWB (string of 32bytes maximum)", NULL, ++ G_PARAM_READABLE); ++ g_object_class_install_property(object_class, PROP_AWB_CURRENT_PROFILE_NAME, spec); ++ /* AWB current profile name */ ++ spec = g_param_spec_int ("awb-current-profile-color-temp", ++ "AWB current color temperature", ++ "Color temperature of the current profile applied by the AWB", ++ 0, 12000, 0, ++ G_PARAM_READABLE), ++ g_object_class_install_property(object_class, PROP_AWB_CURRENT_PROFILE_COLOR_TEMP, spec); ++ /* Demosaicing enable property */ ++ spec = g_param_spec_boolean("demosaicing-enable", ++ "Demosaicing enable", ++ "ISP demosaicing bloc activation", ++ false, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_DEMOSAICING_ENABLE, spec); ++ /* Demosaicing filter values property */ ++ spec = gst_param_spec_array("demosaicing-filters", ++ "Demosaicing filter values", ++ "ISP demosaicing filter values ('')", ++ g_param_spec_int ("demosaicing-filter", "Demosaicing filter value", ++ "One of Peak, LineV, LineH, Edge value.", 0, 7, 0, ++ G_PARAM_READWRITE), ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_DEMOSAICING_FILTERS, spec); ++ /* Statistic profile property */ ++ spec = g_param_spec_int("statistic-profile", ++ "Statistic profile", ++ "ISP statistic extraction profile ('<0 = Full stats (histogram and average, up and down), 1 = average up stats, 2 = average down stats>')", ++ 0, 2, 0, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_STATISTIC_PROFILE, spec); ++ /* Statistic up average property */ ++ spec = gst_param_spec_array("statistic-get-average-up", ++ "Get statistic average at up level", ++ "Up ISP average statistic results values ('')", ++ g_param_spec_int ("statistic-average-up", "Average statistic results value", ++ "One of R, G, B, L average statistic value", 0, 255, 0, ++ G_PARAM_READABLE), ++ G_PARAM_READABLE); ++ g_object_class_install_property(object_class, PROP_STATISTIC_GET_AVERAGE_UP, spec); ++ /* Statistic down average property */ ++ spec = gst_param_spec_array("statistic-get-average-down", ++ "Get statistic average at down level", ++ "Down ISP average statistic results values ('')", ++ g_param_spec_int ("statistic-average-down", "Average statistic results value", ++ "One of R, G, B, L average statistic value", 0, 255, 0, ++ G_PARAM_READABLE), ++ G_PARAM_READABLE); ++ g_object_class_install_property(object_class, PROP_STATISTIC_GET_AVERAGE_DOWN, spec); ++ /* Statistic up histogram property */ ++ spec = gst_param_spec_array("statistic-get-histogram-up", ++ "Get statistic histogram at up level", ++ "Up ISP histogram statistic results values ('')", ++ g_param_spec_int ("statistic-histogram-up", "Histogram statistic results value", ++ "One of bin0, ... , bin11 statistic value", 0, 16777215, 0, ++ G_PARAM_READABLE), ++ G_PARAM_READABLE); ++ g_object_class_install_property(object_class, PROP_STATISTIC_GET_HISTOGRAM_UP, spec); ++ /* Statistic down histogram property */ ++ spec = gst_param_spec_array("statistic-get-histogram-down", ++ "Get statistic histogram at down level", ++ "Down ISP histogram statistic results values ('')", ++ g_param_spec_int ("statistic-histogram-down", "Histogram statistic results value", ++ "One of bin0, ... , bin11 statistic value", 0, 16777215, 0, ++ G_PARAM_READABLE), ++ G_PARAM_READABLE); ++ g_object_class_install_property(object_class, PROP_STATISTIC_GET_HISTOGRAM_DOWN, spec); ++ /* Gamma enable property */ ++ spec = g_param_spec_boolean("gamma-enable", ++ "Gamma correction enable", ++ "Gamma correction bloc activation", ++ false, ++ G_PARAM_READWRITE); ++ g_object_class_install_property(object_class, PROP_GAMMA_ENABLE, spec); ++} ++ ++/* GstChildProxy implementation */ ++static GObject * ++gst_libcamera_src_child_proxy_get_child_by_index(GstChildProxy * child_proxy, ++ guint index) ++{ ++ GLibLocker lock(GST_OBJECT(child_proxy)); ++ GObject *obj = nullptr; ++ ++ obj = reinterpret_cast(g_list_nth_data(GST_ELEMENT(child_proxy)->srcpads, index)); ++ if (obj) ++ gst_object_ref(obj); ++ ++ return obj; ++} ++ ++static guint ++gst_libcamera_src_child_proxy_get_children_count(GstChildProxy * child_proxy) ++{ ++ GLibLocker lock(GST_OBJECT(child_proxy)); ++ return GST_ELEMENT_CAST(child_proxy)->numsrcpads; ++} ++ ++static void ++gst_libcamera_src_child_proxy_init(gpointer g_iface, [[maybe_unused]] gpointer iface_data) ++{ ++ GstChildProxyInterface *iface = reinterpret_cast(g_iface); ++ iface->get_child_by_index = gst_libcamera_src_child_proxy_get_child_by_index; ++ iface->get_children_count = gst_libcamera_src_child_proxy_get_children_count; + } +diff --git a/src/ipa/dcmipp/algorithms/aec.cpp b/src/ipa/dcmipp/algorithms/aec.cpp +new file mode 100644 +index 00000000..546cd672 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/aec.cpp +@@ -0,0 +1,704 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * Copyright (C) 2024 LACROIX - Impulse ++ * ++ * aec.cpp - STM32 DCMIPP AE control ++ */ ++ ++#include "aec.h" ++ ++#include ++ ++#include ++ ++#include ++ ++#ifdef EVISION_ALGO_ENABLED ++#include ++#include ++ ++#include "evision-api-st-ae.h" ++#endif /* EVISION_ALGO_ENABLED */ ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++LOG_DEFINE_CATEGORY(DcmippAec) ++ ++static constexpr uint8_t kAlgoIdealExposureTarget = 56; ++static constexpr float kEVMin = -2.0f; ++static constexpr float kEVMax = 2.0f; ++static constexpr float kEVDef = 0.0f; ++static constexpr uint32_t kAntiflickerFreqMin = 0; ++static constexpr uint32_t kAntiflickerFreqMax = 60; ++static constexpr uint32_t kAntiflickerFreqDef = 0; ++static constexpr int32_t kSensorDelayMin = 1; ++static constexpr int32_t kSensorDelayMax = 255; ++static constexpr int32_t kSensorDelayDef = 1; ++ ++#ifdef EVISION_ALGO_ENABLED ++static constexpr uint32_t kmdB = 1000; ++ ++static void *plib_handle = nullptr; ++static evision_st_ae_process_t *pIspAEprocess = nullptr; ++ ++typedef evision_st_ae_process_t *(*evision_api_st_ae_new_t)(evision_api_log_callback log_cb); ++typedef evision_return_t (*evision_api_st_ae_delete_t)(evision_st_ae_process_t *self); ++typedef evision_return_t (*evision_api_st_ae_init_t)(evision_st_ae_process_t *const self); ++typedef evision_return_t (*evision_api_st_ae_process_t)(evision_st_ae_process_t *const self, uint32_t current_gain, uint32_t current_exposure, uint8_t average_lum); ++ ++static evision_api_st_ae_new_t evision_api_st_ae_new = nullptr; ++static evision_api_st_ae_init_t evision_api_st_ae_init = nullptr; ++static evision_api_st_ae_delete_t evision_api_st_ae_delete = nullptr; ++static evision_api_st_ae_process_t evision_api_st_ae_process = nullptr; ++#endif /* EVISION_ALGO_ENABLED */ ++ ++static bool process_simple_algo = true; ++ ++static bool isExposureValueValid(float exposureValue) ++{ ++ return exposureValue >= kEVMin && exposureValue <= kEVMax; ++} ++ ++static bool isAntiflickerFreqValid(uint32_t antiflickerFreq) ++{ ++ return antiflickerFreq >= kAntiflickerFreqMin && antiflickerFreq <= kAntiflickerFreqMax; ++} ++ ++static bool isExposureTimeValid(int32_t exposureTime, IPAContext &context) ++{ ++ return exposureTime >= context.info.sensorExposureMin && exposureTime <= context.info.sensorExposureMax; ++} ++ ++static bool isGainValid(double gain, IPAContext &context) ++{ ++ return gain >= context.info.sensorGainMin && gain <= context.info.sensorGainMax; ++} ++ ++static bool isSensorDelayValid(int32_t delay) ++{ ++ return delay >= kSensorDelayMin && delay <= kSensorDelayMax; ++} ++ ++#ifdef EVISION_ALGO_ENABLED ++static bool initializeLibrary() ++{ ++ const char *lib_dir = "/usr/lib/"; ++ const char *lib_name = "libevision-st-ae.so.1"; ++ std::string lib_path = std::string(lib_dir) + lib_name; ++ ++ plib_handle = dlopen(lib_path.c_str(), RTLD_LAZY); ++ if (!plib_handle) { ++ LOG(DcmippAec, Warning) << "Cannot open library: " << dlerror(); ++ LOG(DcmippAec, Warning) << "Fall back to simple AEC algorithm processing"; ++ return true; ++ } ++ ++ dlerror(); // Clear any existing error ++ ++ process_simple_algo = false; ++ evision_api_st_ae_new = (evision_api_st_ae_new_t)dlsym(plib_handle, "evision_api_st_ae_new"); ++ const char *dlsym_error = dlerror(); ++ if (dlsym_error) { ++ LOG(DcmippAec, Error) << "Cannot load symbol 'evision_api_st_ae_new': " << dlsym_error; ++ dlclose(plib_handle); ++ return false; ++ } ++ ++ evision_api_st_ae_init = (evision_api_st_ae_init_t)dlsym(plib_handle, "evision_api_st_ae_init"); ++ dlsym_error = dlerror(); ++ if (dlsym_error) { ++ LOG(DcmippAec, Error) << "Cannot load symbol 'evision_api_st_ae_init': " << dlsym_error; ++ dlclose(plib_handle); ++ return false; ++ } ++ ++ evision_api_st_ae_delete = (evision_api_st_ae_delete_t)dlsym(plib_handle, "evision_api_st_ae_delete"); ++ dlsym_error = dlerror(); ++ if (dlsym_error) { ++ LOG(DcmippAec, Error) << "Cannot load symbol 'evision_api_st_ae_delete': " << dlsym_error; ++ dlclose(plib_handle); ++ return false; ++ } ++ ++ evision_api_st_ae_process = (evision_api_st_ae_process_t)dlsym(plib_handle, "evision_api_st_ae_process"); ++ dlsym_error = dlerror(); ++ if (dlsym_error) { ++ LOG(DcmippAec, Error) << "Cannot load symbol 'evision_api_st_ae_process': " << dlsym_error; ++ dlclose(plib_handle); ++ return false; ++ } ++ ++ return true; ++} ++ ++static void log_cb(const char *const msg) ++{ ++ LOG(DcmippAec, Debug) << msg; ++} ++ ++static bool initializeAeInstance() ++{ ++ if (!plib_handle) { ++ LOG(DcmippAec, Error) << "Library not loaded"; ++ return false; ++ } ++ ++ pIspAEprocess = evision_api_st_ae_new(log_cb); ++ if (pIspAEprocess == nullptr) { ++ LOG(DcmippAec, Error) << "Failed to create pIspAEprocess"; ++ return false; ++ } ++ ++ evision_return_t init_result = evision_api_st_ae_init(pIspAEprocess); ++ if (init_result != EVISION_RET_SUCCESS) { ++ LOG(DcmippAec, Error) << "Failed to initialize pIspAEprocess"; ++ evision_api_st_ae_delete(pIspAEprocess); ++ pIspAEprocess = nullptr; ++ return false; ++ } ++ ++ return true; ++} ++ ++static void deinitializeAeInstance() ++{ ++ if (pIspAEprocess) { ++ evision_api_st_ae_delete(pIspAEprocess); ++ pIspAEprocess = nullptr; ++ } ++} ++ ++static void deinitializeLibrary() ++{ ++ if (plib_handle) { ++ dlclose(plib_handle); ++ plib_handle = nullptr; ++ } ++} ++ ++evision_return_t processAeInstance(double *gain, int32_t *exposure, uint32_t average_lum) ++{ ++ if (!pIspAEprocess) { ++ LOG(DcmippAec, Error) << "AE instance not initialized"; ++ return EVISION_RET_PARAM_ERR; ++ } ++ ++ if (!gain || !exposure) { ++ LOG(DcmippAec, Error) << "Null pointer passed for gain or exposure"; ++ return EVISION_RET_PARAM_ERR; ++ } ++ ++ evision_return_t e_ret = evision_api_st_ae_process(pIspAEprocess, static_cast(*gain * kmdB), static_cast(*exposure), static_cast(average_lum)); ++ if (e_ret != EVISION_RET_SUCCESS) { ++ LOG(DcmippAec, Error) << "AE process failed"; ++ return EVISION_RET_FAILURE; ++ } ++ ++ *gain = static_cast(pIspAEprocess->new_gain) / kmdB; ++ *exposure = static_cast(pIspAEprocess->new_exposure); ++ ++ return EVISION_RET_SUCCESS; ++} ++#endif /* EVISION_ALGO_ENABLED */ ++ ++const int32_t kAecTolerance = 15; ++const float kAecCoeffLumGain = 0.1f; ++const float kAecGainUpdateMax = 5.0f; ++const float kAecExposureUpdateRatio = 1.2f; ++const int32_t kAecExposureMin = 400; ++const int32_t kAecSensorDelayMax = 10; ++const int32_t kAecSensorDelayLMargin = 2; ++const int32_t kAecSensorStartupFrames = 30; ++ ++int Aec::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) ++{ ++ /* Check for AEC algo config */ ++ params_.algoEnable = tuningData["AeEnable"].get(0); ++ params_.algoExposureValue = tuningData["ExposureValue"].get(kEVDef); ++ if (!isExposureValueValid(params_.algoExposureValue)) { ++ LOG(DcmippAec, Error) << "Invalid Exposure value: " << params_.algoExposureValue; ++ return -EINVAL; ++ } ++ ++ params_.algoTarget = kAlgoIdealExposureTarget * pow(2, params_.algoExposureValue); ++ ++ /* Check for sensor static config (exposure time in microseconds, gain in dB) */ ++ params_.sensorStatic.exposure = tuningData["ExposureTime"].get(10000); ++ if (!isExposureTimeValid(params_.sensorStatic.exposure, context)) { ++ LOG(DcmippAec, Error) << "Invalid Exposure time: " << params_.sensorStatic.exposure; ++ return -EINVAL; ++ } ++ params_.sensorStatic.gain = tuningData["AnalogueGain_dB"].get(1); ++ if (!isGainValid(params_.sensorStatic.gain, context)) { ++ LOG(DcmippAec, Error) << "Invalid Gain: " << params_.sensorStatic.gain; ++ return -EINVAL; ++ } ++ ++ /* Check for sensor delay */ ++ params_.sensorDelay = tuningData["SensorDelay"].get(kSensorDelayDef); ++ if (!isSensorDelayValid(params_.sensorDelay)) { ++ LOG(DcmippAec, Error) << "Invalid Sensor delay: " << params_.sensorDelay; ++ return -EINVAL; ++ } ++ ++ /* Prepare sensor delay test config */ ++ /* Config 0: Exposure = 0% - Gain = 0% ++ * .................. +20% ........... ++ * Config 5: Exposure = 100% - Gain = 0% ++ * ................................. +10% ++ * Config 11: Exposure = 100% - Gain = 60% ++ */ ++ for (int i = 0; i < kAecNbTestConfig / 2; i++) { ++ internal_.measure.testConfig[i].exposure = i ? (context.info.sensorExposureMax * 20 * i) / 100 : context.info.sensorExposureMin; ++ internal_.measure.testConfig[i].gain = context.info.sensorGainMin; ++ internal_.measure.testConfig[i + kAecNbTestConfig / 2].exposure = context.info.sensorExposureMax; ++ internal_.measure.testConfig[i + kAecNbTestConfig / 2].gain = (context.info.sensorGainMax * 10 * (i + 1)) / 100; ++ } ++ internal_.measure.sensorDelayMeasure = -1; ++ ++ /* Check for anti-flicker frequency */ ++ params_.antiFlickerFreq = tuningData["AntiFlickerFreq"].get(0); ++ if (!isAntiflickerFreqValid(params_.antiFlickerFreq)) { ++ LOG(DcmippAec, Error) << "Invalid Anti-flicker frequency: " << params_.antiFlickerFreq; ++ return -EINVAL; ++ } ++ ++#ifdef EVISION_ALGO_ENABLED ++ /* Iniitialize the ST AE algorithm library */ ++ if (params_.algoEnable) { ++ if (!initializeLibrary()) { ++ return 1; ++ } ++ ++ if (!process_simple_algo) { ++ if (!initializeAeInstance()) { ++ deinitializeLibrary(); ++ return 1; ++ } ++ ++ /* Configure algo (AEC target) */ ++ pIspAEprocess->hyper_params.target = (int32_t)params_.algoTarget; ++ ++ /* Configure algo (sensor config) */ ++ pIspAEprocess->hyper_params.exposure_min = context.info.sensorExposureMin; ++ pIspAEprocess->hyper_params.exposure_max = context.info.sensorExposureMax; ++ pIspAEprocess->hyper_params.gain_min = (uint32_t)(context.info.sensorGainMin * kmdB); ++ pIspAEprocess->hyper_params.gain_max = (uint32_t)(context.info.sensorGainMax * kmdB); ++ pIspAEprocess->hyper_params.compat_freq = (uint32_t)params_.antiFlickerFreq; ++ } ++ } else { ++ if (!process_simple_algo) { ++ deinitializeAeInstance(); ++ deinitializeLibrary(); ++ } ++ } ++#endif /* EVISION_ALGO_ENABLED */ ++ ++ /* Configure exposed controls */ ++ context.dcmippControls[&controls::AeEnable] = ControlInfo(false, true); ++ context.dcmippControls[&controls::ExposureValue] = ControlInfo(kEVMin, kEVMax, kEVDef); ++ context.dcmippControls[&controls::ExposureTime] = ControlInfo(context.info.sensorExposureMin, ++ context.info.sensorExposureMax, ++ context.info.sensorExposureDef); ++ context.dcmippControls[&controls::draft::AnalogueGain_dB] = ControlInfo((float)context.info.sensorGainMin, ++ (float)context.info.sensorGainMax, ++ (float)context.info.sensorGainDef); ++ context.dcmippControls[&controls::draft::SensorDelay] = ControlInfo(kSensorDelayMin, ++ kSensorDelayMax, ++ kSensorDelayDef); ++ context.dcmippControls[&controls::draft::DoSensorDelayMeasure] = ControlInfo(false, true); ++ context.dcmippControls[&controls::draft::AntiFlickerFreq] = ControlInfo((int32_t)kAntiflickerFreqMin, ++ (int32_t)kAntiflickerFreqMax, ++ (int32_t)kAntiflickerFreqDef); ++ ++ return 0; ++} ++ ++int Aec::configure(IPAContext &context, ++ [[maybe_unused]] const IPACameraSensorInfo &configInfo) ++{ ++ /* Set the initial ISP/sensor values in the pending config */ ++ if (params_.algoEnable) { ++ /* Start with gain and exposure = min */ ++ config_.sensor.gain = context.info.sensorGainMin; ++ if (process_simple_algo) { ++ config_.sensor.exposure = context.info.sensorExposureMax; ++ } else { ++ config_.sensor.exposure = context.info.sensorExposureMin; ++ } ++ } else { ++ /* Static config */ ++ config_.sensor = params_.sensorStatic; ++ } ++ ++ /* Update context immediately, so IPA can apply the sensor settings at start up */ ++ context.sensor = config_.sensor; ++ config_.pending = true; ++ ++ /* Update sensor exposed controls */ ++ context.dcmippControls.emplace(std::piecewise_construct, ++ std::forward_as_tuple(&controls::ExposureTime), ++ std::forward_as_tuple(context.info.sensorExposureMin, ++ context.info.sensorExposureMax, ++ context.info.sensorExposureDef)); ++ context.dcmippControls.emplace(std::piecewise_construct, ++ std::forward_as_tuple(&controls::draft::AnalogueGain_dB), ++ std::forward_as_tuple((float)context.info.sensorGainMin, ++ (float)context.info.sensorGainMax, ++ (float)context.info.sensorGainDef)); ++ return 0; ++} ++ ++void Aec::queueRequest([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ const ControlList &controls) ++{ ++ /* Algo ctrl: only update params_ which will be considered upon the next process() call */ ++ const auto &algoEnable = controls.get(controls::AeEnable); ++ const auto &algoExposureValue = controls.get(controls::ExposureValue); ++ const auto &algoAntiFlickerFreq = controls.get(controls::draft::AntiFlickerFreq); ++ const auto &sensorDelay = controls.get(controls::draft::SensorDelay); ++ const auto &doSensorDelayMeasure = controls.get(controls::draft::DoSensorDelayMeasure); ++ ++ if (algoEnable) { ++ params_.algoEnable = *algoEnable; ++ LOG(DcmippAec, Debug) << "Updating AeEnable to " << *algoEnable; ++ } ++ ++ if (algoExposureValue) { ++ if (!isExposureValueValid(*algoExposureValue)) { ++ LOG(DcmippAec, Error) << "Invalid Exposure value: " << *algoExposureValue; ++ return; ++ } ++ params_.algoExposureValue = *algoExposureValue; ++ LOG(DcmippAec, Debug) << "Updating ExposureValue to " << params_.algoExposureValue; ++ ++ params_.algoTarget = kAlgoIdealExposureTarget * pow(2, *algoExposureValue); ++ LOG(DcmippAec, Debug) << "Updating AeTarget to " << params_.algoTarget; ++ } ++ ++ if (algoAntiFlickerFreq) { ++ if (!isAntiflickerFreqValid(*algoAntiFlickerFreq)) { ++ LOG(DcmippAec, Error) << "Invalid Anti-flicker frequency: " << *algoAntiFlickerFreq; ++ return; ++ } ++ params_.antiFlickerFreq = *algoAntiFlickerFreq; ++ LOG(DcmippAec, Debug) << "Updating Anti-flicker frequency to " << params_.antiFlickerFreq; ++ } ++ ++ if (sensorDelay) { ++ if (!isSensorDelayValid(*sensorDelay)) { ++ LOG(DcmippAec, Error) << "Invalid Sensor delay: " << *sensorDelay; ++ return; ++ } ++ params_.sensorDelay = *sensorDelay; ++ LOG(DcmippAec, Debug) << "Updating Sensor Delay to " << params_.sensorDelay; ++ } ++ ++ if (doSensorDelayMeasure && *doSensorDelayMeasure) { ++ /* Start Measure */ ++ internal_.measure.doSensorDelayMeasure = true; ++ internal_.measure.sensorDelayMeasure = -1; ++ internal_.measure.testConfigId = 0; ++ internal_.measure.delay = 0; ++ internal_.measure.refL = 0; ++ internal_.measure.delays.fill(0); ++ ++ /* Apply the first test config */ ++ internal_.measure.initialConfig = config_.sensor; ++ config_.sensor.gain = internal_.measure.testConfig[internal_.measure.testConfigId].gain; ++ config_.sensor.exposure = internal_.measure.testConfig[internal_.measure.testConfigId].exposure; ++ config_.pending = true; ++ ++ LOG(DcmippAec, Debug) << "Starting sensor delay measure"; ++ } ++ ++ /* Static config: update params_ and if applicable force config_ update now */ ++ const auto &gain = controls.get(controls::draft::AnalogueGain_dB); ++ const auto &exposure = controls.get(controls::ExposureTime); ++ ++ if (gain) { ++ if (!isGainValid(*gain, context)) { ++ LOG(DcmippAec, Error) << "Invalid Gain: " << *gain; ++ return; ++ } ++ params_.sensorStatic.gain = *gain; ++ if (!params_.algoEnable) { ++ config_.sensor.gain = params_.sensorStatic.gain; ++ config_.pending = true; ++ } ++ LOG(DcmippAec, Debug) << "Updating static sensor gain to " << *gain; ++ } ++ ++ if (exposure) { ++ if (!isExposureTimeValid(*exposure, context)) { ++ LOG(DcmippAec, Error) << "Invalid Exposure time: " << *exposure; ++ return; ++ } ++ params_.sensorStatic.exposure = *exposure; ++ if (!params_.algoEnable) { ++ config_.sensor.exposure = *exposure; ++ config_.pending = true; ++ } ++ LOG(DcmippAec, Debug) << "Updating static sensor exposure to " << *exposure; ++ } ++} ++ ++void Aec::prepare(IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ [[maybe_unused]] stm32_dcmipp_params_cfg *params, ++ ControlList &sensorControls, ++ [[maybe_unused]] ControlList &ispControls) ++{ ++ /* Copy the pending config to the isp_params and sensor_ctrl. Update the IPAContext */ ++ if (config_.pending) { ++ /* Configure sensor controls */ ++ sensorControls.set(controls::draft::AnalogueGain_dB, config_.sensor.gain); ++ sensorControls.set(controls::ExposureTime, config_.sensor.exposure); ++ ++ /* Update context */ ++ context.sensor = config_.sensor; ++ ++ /* Start the invalid stats counter from this update. Consider both ISP and sensor */ ++ if (!internal_.measure.doSensorDelayMeasure) { ++ /* Nominal processing (not measuring delay) */ ++ internal_.invalidStats = context.info.ispStatLatency + params_.sensorDelay; ++ /* Our definition of 'sensorDelay' is the delay between a sensor control is updated ++ * and the time the stats are available. So this delay includes 1 VSYNC for the stat ++ * update, which is also considered in the definition of ispStatLatency. ++ * So at the end we shall decrease by one the summed value */ ++ internal_.invalidStats--; ++ ++ /* Increase this delay during the startup phase because the sensor response ++ * time may be greater here */ ++ if (frame <= kAecSensorStartupFrames) ++ internal_.invalidStats++; ++ } else { ++ /* During measure */ ++ if (internal_.measure.testConfigId != 0) ++ /* We'll check measure at next frame */ ++ internal_.invalidStats = 1; ++ else ++ /* First measure: wait for several frames to set up the initial point */ ++ internal_.invalidStats = kAecSensorDelayMax; ++ } ++ ++ /* Clear the pending request */ ++ config_.pending = false; ++ } ++} ++ ++void Aec::processSensorDelayMeasure(const stm32_dcmipp_stat_buf *stats) ++{ ++ int32_t avgL; ++ ++ /* Check if stats can be considered as valid */ ++ if (internal_.invalidStats) ++ internal_.invalidStats--; ++ ++ if (internal_.invalidStats) ++ return; ++ ++ avgL = (3 * stats->post.average_RGB[0] + 6 * stats->post.average_RGB[1] + 1 * stats->post.average_RGB[2]) / 10; ++ ++ if (internal_.measure.testConfigId > 0) { ++ /* New stat available, check if Luminance has changed */ ++ internal_.measure.delay++; ++ if (abs(avgL - internal_.measure.refL) <= kAecSensorDelayLMargin && internal_.measure.delay != kAecSensorDelayMax) { ++ /* No change, wait for next frame */ ++ internal_.invalidStats = 1; ++ return; ++ } ++ ++ /* Luminance was updated since we applied a new sensor configuration : store the result for this test. ++ * Reaching kAecSensorDelayMax happens when we have a totally black or white frame, the measure shall be ++ * considered as invalid. ++ */ ++ internal_.measure.delays[internal_.measure.testConfigId - 1] = internal_.measure.delay; ++ } ++ ++ /* New delay measure available */ ++ if (++internal_.measure.testConfigId != kAecNbTestConfig) { ++ /* Apply new sensor test configuration */ ++ config_.sensor.gain = internal_.measure.testConfig[internal_.measure.testConfigId].gain; ++ config_.sensor.exposure = internal_.measure.testConfig[internal_.measure.testConfigId].exposure; ++ config_.pending = true; ++ ++ /* Wait for stats at next frame */ ++ internal_.measure.delay = 0; ++ internal_.measure.refL = avgL; ++ internal_.invalidStats = 1; ++ } else { ++ /* All the configurations have been tested, finalize the test procedure */ ++ int32_t sum = 0; ++ int n_valid = 0; ++ /* Find the average delay among the valid (!= kAecSensorDelayMax) values */ ++ for (int i = 0; i < kAecNbTestConfig - 1; i++) ++ if (internal_.measure.delays[i] != kAecSensorDelayMax) { ++ sum += internal_.measure.delays[i]; ++ n_valid++; ++ } ++ if (n_valid) { ++ internal_.measure.sensorDelayMeasure = round((float)sum / n_valid); ++ /* Apply the measure internally */ ++ params_.sensorDelay = internal_.measure.sensorDelayMeasure; ++ } else { ++ internal_.measure.sensorDelayMeasure = 0; ++ } ++ ++ /* Restore initial sensor config and stop measure */ ++ config_.sensor = internal_.measure.initialConfig; ++ config_.pending = true; ++ internal_.measure.doSensorDelayMeasure = false; ++ } ++} ++ ++void Aec::process(IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ const stm32_dcmipp_stat_buf *stats, ++ ControlList &metadata) ++{ ++ /* Compute a new pending config from stats */ ++ if (internal_.measure.doSensorDelayMeasure) { ++ /* Special case: prosess sensor delay measure */ ++ processSensorDelayMeasure(stats); ++ } else if (params_.algoEnable) { ++ /* Check if stats can be considered as valid */ ++ if (internal_.invalidStats) ++ internal_.invalidStats--; ++ ++ if (internal_.invalidStats) { ++ LOG(DcmippAec, Debug) << "Can't update now"; ++ } else { ++ if (process_simple_algo) { ++ bool do_exposure_update; ++ float gain_update; ++ double gain = context.sensor.gain; ++ int32_t exposure = context.sensor.exposure; ++ int32_t target = (int32_t)params_.algoTarget; ++ int32_t avgL = (3 * stats->post.average_RGB[0] + ++ 6 * stats->post.average_RGB[1] + ++ 1 * stats->post.average_RGB[2]) / ++ 10; ++ ++ LOG(DcmippAec, Debug) << "Status: L = " << avgL << " - " ++ << "Gain = " << gain << " db - " ++ << "Exposure = " << exposure << " us"; ++ ++ /* Compare the average luminance with the target */ ++ gain_update = 0.0f; ++ if (avgL > target + kAecTolerance) { ++ /* Too bright, decrease gain */ ++ gain_update = (float)(target - avgL) * kAecCoeffLumGain; ++ if (gain_update < -kAecGainUpdateMax) ++ gain_update = -kAecGainUpdateMax; ++ } else if (avgL < target - kAecTolerance) { ++ /* Too dark vador, call a Jedi and increase gain */ ++ gain_update = (float)(target - avgL) * kAecCoeffLumGain; ++ if (gain_update > kAecGainUpdateMax) ++ gain_update = kAecGainUpdateMax; ++ } ++ ++ if (gain_update == 0.0f) { ++ LOG(DcmippAec, Debug) << "No change required"; ++ } else { ++ /* Need to change something (gain or exposure) */ ++ /* Check current exposure to decide whether we shall update gain or exposure */ ++ if ((gain == context.info.sensorGainMin) && ++ ((exposure != context.info.sensorExposureMax) || (gain_update < 0))) ++ do_exposure_update = true; ++ else ++ do_exposure_update = false; ++ ++ if (!do_exposure_update) { ++ /* Update gain as it has not reached its min value */ ++ gain += gain_update; ++ if (gain < context.info.sensorGainMin) ++ gain = context.info.sensorGainMin; ++ else if (gain > context.info.sensorGainMax) ++ gain = context.info.sensorGainMax; ++ ++ LOG(DcmippAec, Debug) << "New gain: " << gain << " db"; ++ config_.sensor.gain = gain; ++ config_.pending = true; ++ } else { ++ /* Update exposure since gain has reached its min value */ ++ if (gain_update < 0) { ++ /* Decrease exposure */ ++ exposure /= kAecExposureUpdateRatio; ++ /* Note: sensorExposureMin for IMX335 is wrong: use alternate value */ ++ if (exposure < kAecExposureMin) ++ exposure = kAecExposureMin; ++ } else { ++ /* Increase exposure */ ++ exposure *= kAecExposureUpdateRatio; ++ if (exposure > context.info.sensorExposureMax) ++ exposure = context.info.sensorExposureMax; ++ } ++ ++ LOG(DcmippAec, Debug) << "New exposure: " << exposure << " us"; ++ config_.sensor.exposure = exposure; ++ config_.pending = true; ++ } ++ } ++ } ++#ifdef EVISION_ALGO_ENABLED ++ else { ++ double gain = context.sensor.gain; ++ int32_t exposure = context.sensor.exposure; ++ int32_t avgL = (3 * stats->post.average_RGB[0] + ++ 6 * stats->post.average_RGB[1] + ++ 1 * stats->post.average_RGB[2]) / ++ 10; ++ ++ LOG(DcmippAec, Debug) << "Status: L = " << avgL << " - " ++ << "Gain = " << gain << " db - " ++ << "Exposure = " << exposure << " us"; ++ ++ /* Align on the target update (may have been updated with ISP_SetExposureTarget()) */ ++ pIspAEprocess->hyper_params.target = (uint8_t)params_.algoTarget; ++ pIspAEprocess->hyper_params.compat_freq = (uint32_t)params_.antiFlickerFreq; ++ ++ evision_return_t process_result = processAeInstance(&gain, &exposure, avgL); ++ if (process_result != EVISION_RET_SUCCESS) { ++ LOG(DcmippAec, Error) << "Failed to process pIspAEprocess"; ++ } ++ ++ if (config_.sensor.exposure != exposure) { ++ LOG(DcmippAec, Debug) << "New exposure: " << exposure << " us"; ++ config_.sensor.exposure = exposure; ++ config_.pending = true; ++ } ++ ++ if (config_.sensor.gain != gain) { ++ LOG(DcmippAec, Debug) << "New gain: " << gain << " db"; ++ config_.sensor.gain = gain; ++ config_.pending = true; ++ } ++ } ++#endif /* EVISION_ALGO_ENABLED */ ++ } ++ } ++ ++ /* Set metadata */ ++ metadata.set(controls::AeEnable, params_.algoEnable); ++ metadata.set(controls::draft::AeExposureTarget, params_.algoTarget); ++ metadata.set(controls::ExposureValue, params_.algoExposureValue); ++ metadata.set(controls::draft::AntiFlickerFreq, params_.antiFlickerFreq); ++ metadata.set(controls::draft::AnalogueGain_dB, (float)config_.sensor.gain); ++ metadata.set(controls::ExposureTime, config_.sensor.exposure); ++ metadata.set(controls::draft::SensorDelay, params_.sensorDelay); ++ metadata.set(controls::draft::SensorDelayMeasure, internal_.measure.sensorDelayMeasure); ++} ++ ++REGISTER_IPA_ALGORITHM(Aec, "Aec") ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/aec.h b/src/ipa/dcmipp/algorithms/aec.h +new file mode 100644 +index 00000000..27cc5735 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/aec.h +@@ -0,0 +1,68 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * Copyright (C) 2024 LACROIX - Impulse ++ * ++ * aec.h - STM32 DCMIPP AE control ++ */ ++ ++#pragma once ++ ++#include "algorithm.h" ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++class Aec : public Algorithm ++{ ++public: ++ Aec() = default; ++ ++ int init(IPAContext &context, const YamlObject &tuningData) override; ++ int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; ++ void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const ControlList &controls) override; ++ void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ControlList &sensorControls, ++ ControlList &ispControls) override; ++ void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const stm32_dcmipp_stat_buf *stats, ControlList &metadata) override; ++ ++private: ++ static constexpr int32_t kAecNbTestConfig = 12; ++ ++ struct algoParams { ++ bool algoEnable; ++ uint32_t algoTarget; ++ float algoExposureValue; ++ uint32_t antiFlickerFreq; ++ struct IPASensor sensorStatic; ++ int32_t sensorDelay; ++ } params_; ++ ++ struct algoInternal { ++ int32_t invalidStats; ++ struct measure { ++ bool doSensorDelayMeasure; ++ int32_t sensorDelayMeasure; ++ struct IPASensor initialConfig; ++ std::array testConfig; ++ int32_t testConfigId; ++ std::array delays; ++ int32_t delay; ++ int32_t refL; ++ } measure; ++ } internal_; ++ ++ struct algoConfig { ++ bool pending; ++ struct IPASensor sensor; ++ } config_; ++ ++ void processSensorDelayMeasure(const stm32_dcmipp_stat_buf *stats); ++}; ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/algorithm.h b/src/ipa/dcmipp/algorithms/algorithm.h +new file mode 100644 +index 00000000..8c59ceb0 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/algorithm.h +@@ -0,0 +1,35 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * algorithm.h - STM32 DCMIPP control algorithm interface ++ */ ++ ++#pragma once ++ ++#include ++ ++#include "../module.h" ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp { ++ ++class Algorithm : public libcamera::ipa::Algorithm ++{ ++ using libcamera::ipa::Algorithm::prepare; ++public: ++ /* prepare ISP params / controls and Sensor controls */ ++ virtual void prepare([[maybe_unused]] typename Module::Context &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] typename Module::FrameContext &frameContext, ++ [[maybe_unused]] typename Module::Params *params, ++ [[maybe_unused]] ControlList &sensorControls, ++ [[maybe_unused]] ControlList &ispControls) ++ { ++ } ++}; ++ ++} /* namespace ipa::dcmipp */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/awb.cpp b/src/ipa/dcmipp/algorithms/awb.cpp +new file mode 100644 +index 00000000..006ad4b8 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/awb.cpp +@@ -0,0 +1,948 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * Copyright (C) 2024 LACROIX - Impulse ++ * ++ * awb.cpp - STM32 DCMIPP AWB control ++ */ ++ ++#include "awb.h" ++ ++#include ++#include ++ ++#include ++ ++#include ++ ++#ifdef EVISION_ALGO_ENABLED ++#include ++#include ++ ++#include "evision-api-awb.h" ++#endif /* EVISION_ALGO_ENABLED */ ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++LOG_DEFINE_CATEGORY(DcmippAwb) ++ ++static constexpr uint32_t kPrecisionFactor = 100000000; ++static constexpr uint32_t kIdle = 128; ++static constexpr char kDelimiter = '$'; ++static constexpr int kColGainMin = 0; ++static constexpr int kColGainMax = 1600000000; ++static constexpr int kColGainDef = 100000000; ++static constexpr int kColCorrectionMin = -400000000; ++static constexpr int kColCorrectionMax = 400000000; ++static constexpr int kColCorrectionDef = 0; ++static constexpr int kProfileNameLen = 32; ++static constexpr int kColTemperatureMin = 0; ++static constexpr int kColTemperatureMax = 10000; ++static constexpr int kColTemperatureDef = 0; ++static constexpr int kIspApplyConfigLatency = 4; ++static constexpr int kColorTempAlgoDefaultMin = 4500; ++ ++#ifdef EVISION_ALGO_ENABLED ++static void *plib_handle = nullptr; ++static evision_awb_estimator_t *pIspAWBestimator = nullptr; ++static evision_awb_profile_t awbProfiles[EVISION_AWB_MAX_PROFILE_COUNT]; ++static float colorTempThresholds[EVISION_AWB_MAX_PROFILE_COUNT - 1]; ++ ++typedef evision_awb_estimator_t *(*evision_api_awb_new_t)(evision_api_log_callback log_cb); ++typedef evision_return_t (*evision_api_awb_delete_t)(evision_awb_estimator_t *self); ++typedef void (*evision_api_awb_set_profile_t)(evision_awb_profile_t *awb_profile, ++ float color_temperature, const float cfa_gains[EVISION_AWB_NB_DG_CFA_GAINS], ++ const float ccm_coefficients[EVISION_AWB_CCM_SIZE][EVISION_AWB_CCM_SIZE], ++ const float ccm_offsets[EVISION_AWB_CCM_SIZE]); ++typedef evision_return_t (*evision_api_awb_init_profiles_t)(evision_awb_estimator_t *const self, ++ double min_temp, double max_temp, ++ uint16_t nb_profiles, float decision_thresholds[EVISION_AWB_MAX_PROFILE_COUNT - 1], ++ evision_awb_profile_t awb_profiles[EVISION_AWB_MAX_PROFILE_COUNT]); ++typedef evision_return_t (*evision_api_awb_run_average_t)(evision_awb_estimator_t *const self, const evision_image_t *const image, ++ uint8_t use_ext_meas, double ext_meas[EVISION_AWB_EXT_MEAS_SIZE]); ++ ++static evision_api_awb_new_t evision_api_awb_new = nullptr; ++static evision_api_awb_delete_t evision_api_awb_delete = nullptr; ++static evision_api_awb_set_profile_t evision_api_awb_set_profile = nullptr; ++static evision_api_awb_init_profiles_t evision_api_awb_init_profiles = nullptr; ++static evision_api_awb_run_average_t evision_api_awb_run_average = nullptr; ++ ++static constexpr uint8_t kIspStatCheckSkipAfterInit = 10; ++static constexpr uint8_t kIspStatCheckSkipAfterCTEstimation = 4; ++static constexpr int32_t kIspStatDeltaMax = 2; ++ ++static int32_t stats_history[3][3] = { 0 }; ++static int32_t color_temperature_history[2] = { 0 }; ++static uint8_t skip_stat_check_count = kIspStatCheckSkipAfterInit; ++#endif ++ ++static bool process_simple_algo = true; ++ ++static bool isColourGainValid(int32_t gain) ++{ ++ return gain >= kColGainMin && gain <= kColGainMax; ++} ++ ++static bool isColourGainValid(Span gain) ++{ ++ for (auto const &g : gain) ++ if (!isColourGainValid(g)) ++ return false; ++ return true; ++} ++ ++static bool isColourCorrectionValid(int32_t correction) ++{ ++ return correction >= kColCorrectionMin && correction <= kColCorrectionMax; ++} ++ ++static bool isColourCorrectionValid(std::vector matrix) ++{ ++ for (auto const &c : matrix) ++ if (!isColourCorrectionValid(c)) ++ return false; ++ return true; ++} ++ ++static bool isColourCorrectionValid(Span matrix) ++{ ++ for (auto const &c : matrix) ++ if (!isColourCorrectionValid(c)) ++ return false; ++ return true; ++} ++ ++static bool isColourTemperatureValid(int32_t temperature) ++{ ++ return temperature >= kColTemperatureMin && temperature <= kColTemperatureMax; ++} ++ ++static bool isColourTemperatureValid(std::vector temperature) ++{ ++ for (auto const &t : temperature) ++ if (!isColourTemperatureValid(t)) ++ return false; ++ return true; ++} ++ ++static bool isColourTemperatureValid(Span temperature) ++{ ++ for (auto const &t : temperature) ++ if (!isColourTemperatureValid(t)) ++ return false; ++ return true; ++} ++ ++static void toShiftMultiplier(uint32_t gain, uint8_t *shift, uint8_t *multiplier) ++{ ++ /* Convert gain (Unit = 100000000 for "x1.0") to Multiplier (where 128 means "x1.0") */ ++ uint64_t mult64 = gain; ++ mult64 = (mult64 * kIdle) / kPrecisionFactor; ++ ++ /* Get Shift + Multiplier where Multiplier < 256 */ ++ *shift = 0; ++ while (mult64 >= 256) { ++ mult64 /= 2; ++ (*shift)++; ++ } ++ *multiplier = (uint8_t)mult64; ++} ++ ++static int16_t toCConvReg(int32_t coeff) ++{ ++ /* Convert coefficient (Unit = 100000000 for "x1.0") to register format */ ++ int64_t val = coeff; ++ int16_t reg; ++ ++ val = (val * 256) / kPrecisionFactor; ++ if (val >= 0) ++ reg = val; ++ else ++ reg = ((-val ^ 0x7FF) + 1) & 0x7FF; ++ ++ return reg; ++} ++ ++#ifdef EVISION_ALGO_ENABLED ++static void getStatUp(IPAContext &context, const __u32 average_RGB[3], ++ int32_t avgUpRGB[3]) ++{ ++ uint64_t R, G, B; ++ ++ /* Read the current ISP gain */ ++ if (context.isp.gain.enable == 1) { ++ R = ((uint64_t)average_RGB[0] * kPrecisionFactor) / context.isp.gain.gainR; ++ avgUpRGB[0] = (int32_t)R; ++ ++ G = ((uint64_t)average_RGB[1] * kPrecisionFactor) / context.isp.gain.gainG; ++ avgUpRGB[1] = (int32_t)G; ++ ++ B = ((uint64_t)average_RGB[2] * kPrecisionFactor) / context.isp.gain.gainB; ++ avgUpRGB[2] = (int32_t)B; ++ ++ if (context.isp.blackLevel.enable == 1) { ++ avgUpRGB[0] += context.isp.blackLevel.blcR; ++ avgUpRGB[1] += context.isp.blackLevel.blcG; ++ avgUpRGB[2] += context.isp.blackLevel.blcB; ++ } ++ } else { ++ avgUpRGB[0] = (int32_t)average_RGB[0]; ++ avgUpRGB[1] = (int32_t)average_RGB[1]; ++ avgUpRGB[2] = (int32_t)average_RGB[2]; ++ } ++} ++ ++double applyGammaInverse(uint32_t comp) ++{ ++ return 255 * std::pow(static_cast(comp) / 255, 1.0 / 2.2); ++} ++ ++void applyCConv(IPAContext &context, const uint32_t inRGB[3], uint32_t outRGB[3]) ++{ ++ if (context.isp.cconv.enable) { ++ const int32_t(*coeff)[3] = context.isp.cconv.coeff; ++ int64_t ccR, ccG, ccB; ++ ++ // Apply ColorConversion matrix to the input components ++ ccR = static_cast(inRGB[0]) * coeff[0][0] + static_cast(inRGB[1]) * coeff[0][1] + static_cast(inRGB[2]) * coeff[0][2]; ++ ccG = static_cast(inRGB[0]) * coeff[1][0] + static_cast(inRGB[1]) * coeff[1][1] + static_cast(inRGB[2]) * coeff[1][2]; ++ ccB = static_cast(inRGB[0]) * coeff[2][0] + static_cast(inRGB[1]) * coeff[2][1] + static_cast(inRGB[2]) * coeff[2][2]; ++ ++ ccR /= kPrecisionFactor; ++ ccG /= kPrecisionFactor; ++ ccB /= kPrecisionFactor; ++ ++ // Clamp values to 0-255 ++ ccR = std::clamp(ccR, static_cast(0), static_cast(255)); ++ ccG = std::clamp(ccG, static_cast(0), static_cast(255)); ++ ccB = std::clamp(ccB, static_cast(0), static_cast(255)); ++ ++ outRGB[0] = static_cast(ccR); ++ outRGB[1] = static_cast(ccG); ++ outRGB[2] = static_cast(ccB); ++ } else { ++ outRGB[0] = inRGB[0]; ++ outRGB[1] = inRGB[1]; ++ outRGB[2] = inRGB[2]; ++ } ++} ++ ++static bool initializeLibrary() ++{ ++ const char *lib_dir = "/usr/lib/"; ++ const char *lib_name = "libevision-awb.so.1"; ++ std::string lib_path = std::string(lib_dir) + lib_name; ++ ++ plib_handle = dlopen(lib_path.c_str(), RTLD_LAZY); ++ if (!plib_handle) { ++ LOG(DcmippAwb, Warning) << "Cannot open library: " << dlerror(); ++ LOG(DcmippAwb, Warning) << "Fall back to simple AWB algorithm processing"; ++ return true; ++ } ++ ++ dlerror(); // Clear any existing error ++ ++ process_simple_algo = false; ++ evision_api_awb_new = (evision_api_awb_new_t)dlsym(plib_handle, "evision_api_awb_new"); ++ const char *dlsym_error = dlerror(); ++ if (dlsym_error) { ++ LOG(DcmippAwb, Error) << "Cannot load symbol 'evision_api_awb_new': " << dlsym_error; ++ dlclose(plib_handle); ++ return false; ++ } ++ ++ evision_api_awb_delete = (evision_api_awb_delete_t)dlsym(plib_handle, "evision_api_awb_delete"); ++ dlsym_error = dlerror(); ++ if (dlsym_error) { ++ LOG(DcmippAwb, Error) << "Cannot load symbol 'evision_api_awb_delete': " << dlsym_error; ++ dlclose(plib_handle); ++ return false; ++ } ++ ++ evision_api_awb_set_profile = (evision_api_awb_set_profile_t)dlsym(plib_handle, "evision_api_awb_set_profile"); ++ dlsym_error = dlerror(); ++ if (dlsym_error) { ++ LOG(DcmippAwb, Error) << "Cannot load symbol 'evision_api_awb_set_profile': " << dlsym_error; ++ dlclose(plib_handle); ++ return false; ++ } ++ ++ evision_api_awb_init_profiles = (evision_api_awb_init_profiles_t)dlsym(plib_handle, "evision_api_awb_init_profiles"); ++ dlsym_error = dlerror(); ++ if (dlsym_error) { ++ LOG(DcmippAwb, Error) << "Cannot load symbol 'evision_api_awb_init_profiles': " << dlsym_error; ++ dlclose(plib_handle); ++ return false; ++ } ++ ++ evision_api_awb_run_average = (evision_api_awb_run_average_t)dlsym(plib_handle, "evision_api_awb_run_average"); ++ dlsym_error = dlerror(); ++ if (dlsym_error) { ++ LOG(DcmippAwb, Error) << "Cannot load symbol 'evision_api_awb_run_average': " << dlsym_error; ++ dlclose(plib_handle); ++ return false; ++ } ++ ++ return true; ++} ++ ++static void log_cb(const char *const msg) ++{ ++ LOG(DcmippAwb, Debug) << msg; ++} ++ ++bool initializeAwbInstance() ++{ ++ if (!plib_handle) { ++ LOG(DcmippAwb, Error) << "Library not loaded"; ++ return false; ++ } ++ ++ pIspAWBestimator = evision_api_awb_new(log_cb); ++ if (pIspAWBestimator == nullptr) { ++ LOG(DcmippAwb, Error) << "Failed to create pIspAWBestimator"; ++ return false; ++ } ++ ++ return true; ++} ++ ++bool Awb::configureAwbAlgo() ++{ ++ if (process_simple_algo) { ++ LOG(DcmippAwb, Debug) << "No algorithm reconfiguration needed"; ++ return true; ++ } ++ ++ evision_return_t e_ret; ++ uint32_t colorTemp, profId, profNb = 0; ++ float cfaGains[4], ccmCoeffs[3][3], ccmOffsets[3] = { 0 }; ++ ++ if (pIspAWBestimator == nullptr) { ++ LOG(DcmippAwb, Error) << "pIspAWBestimator not found"; ++ return false; ++ } ++ ++ /* Set profiles (color temperature, gains, color conv matrix) */ ++ for (profId = 0; profId < EVISION_AWB_MAX_PROFILE_COUNT; profId++) { ++ colorTemp = static_cast(params_.referenceColorTemp[profId]); ++ if (colorTemp == 0) ++ break; ++ ++ if (profNb > 0) { ++ /* Profile decision threshold = lowest ref. temperature + 1/4 of the distance between two reference temperatures */ ++ colorTempThresholds[profNb - 1] = static_cast((colorTemp + 3 * params_.referenceColorTemp[profId - 1]) / 4); ++ } ++ ++ /* Set cfa gains (RGGB) */ ++ cfaGains[0] = static_cast(params_.gainRGB[profId + 0 * kAwbNbRef]) / kPrecisionFactor; ++ cfaGains[1] = static_cast(params_.gainRGB[profId + 1 * kAwbNbRef]) / kPrecisionFactor; ++ cfaGains[2] = cfaGains[1]; ++ cfaGains[3] = static_cast(params_.gainRGB[profId + 2 * kAwbNbRef]) / kPrecisionFactor; ++ ++ /* Set CCM Coeff */ ++ for (uint32_t i = 0; i < 3; i++) { ++ for (uint32_t j = 0; j < 3; j++) { ++ ccmCoeffs[i][j] = static_cast(params_.cconv[profId * 9 + i * 3 + j]) / kPrecisionFactor; ++ } ++ } ++ ++ /* Set profile */ ++ evision_api_awb_set_profile(&awbProfiles[profId], static_cast(colorTemp), cfaGains, ccmCoeffs, ccmOffsets); ++ profNb++; ++ } ++ ++ if (profNb == 0) { ++ LOG(DcmippAwb, Error) << "No valid AWB profiles found"; ++ return false; ++ } ++ ++ /* Register profiles */ ++ e_ret = evision_api_awb_init_profiles(pIspAWBestimator, static_cast(params_.referenceColorTemp[0]), ++ static_cast(params_.referenceColorTemp[profNb - 1]), profNb, ++ colorTempThresholds, awbProfiles); ++ if (e_ret != EVISION_RET_SUCCESS) { ++ LOG(DcmippAwb, Error) << "Failed to initialize AWB profiles"; ++ return false; ++ } ++ ++ /* Configure algo */ ++ /* TODO: check if this shall be an IQ tuning parameter (like the LUT tables of AE algo) */ ++ pIspAWBestimator->hyper_params.speed_p_min = 1.35; ++ pIspAWBestimator->hyper_params.speed_p_max = (profNb < 4) ? 1.8 : 2.0; ++ pIspAWBestimator->hyper_params.gm_tolerance = 1; ++ pIspAWBestimator->hyper_params.conv_criterion = 3; ++ ++ return true; ++} ++ ++static void deinitializeAwbInstance() ++{ ++ if (pIspAWBestimator) { ++ evision_api_awb_delete(pIspAWBestimator); ++ pIspAWBestimator = nullptr; ++ } ++} ++ ++static void deinitializeLibrary() ++{ ++ if (plib_handle) { ++ dlclose(plib_handle); ++ plib_handle = nullptr; ++ } ++} ++#endif /* EVISION_ALGO_ENABLED */ ++ ++void Awb::applyProfile(int profId) ++{ ++ LOG(DcmippAwb, Debug) << "Changing AWB profile to " << params_.profileName[profId] ++ << " (" << params_.referenceColorTemp[profId] << ")"; ++ internal_.colorTemp = params_.referenceColorTemp[profId]; ++ internal_.profileName = params_.profileName[profId]; ++ ++ config_.gain.enable = 1; ++ config_.gain.gainR = params_.gainRGB[profId + 0 * kAwbNbRef]; ++ config_.gain.gainG = params_.gainRGB[profId + 1 * kAwbNbRef]; ++ config_.gain.gainB = params_.gainRGB[profId + 2 * kAwbNbRef]; ++ ++ config_.cconv.enable = 1; ++ int offset = 3 * 3 * profId; ++ std::copy(params_.cconv.begin() + offset, params_.cconv.begin() + offset + 3 * 3, ++ &config_.cconv.coeff[0][0]); ++ ++ internal_.profileDirty = false; ++ config_.pending = true; ++} ++ ++int Awb::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) ++{ ++ /* Check for AWB algo config */ ++ params_.algoEnable = tuningData["AwbEnable"].get(0); ++ ++ std::vector name = tuningData["ProfileName"].getList().value_or(std::vector{}); ++ if (name.size() == kAwbNbRef) { ++ std::copy(name.begin(), name.end(), params_.profileName.begin()); ++ } else if (name.size() != 0) { ++ LOG(DcmippAwb, Error) << "Invalid ProfileName"; ++ return -EINVAL; ++ } ++ ++ std::vector data = tuningData["RefColorTemp"].getList().value_or(std::vector{}); ++ if (data.size() == kAwbNbRef) { ++ if (!isColourTemperatureValid(data)) { ++ LOG(DcmippAwb, Error) << "Invalid RefColorTemp values"; ++ return -EINVAL; ++ } ++ std::copy(data.begin(), data.end(), params_.referenceColorTemp.begin()); ++ } else if (data.size() != 0) { ++ LOG(DcmippAwb, Error) << "Invalid RefColorTemp"; ++ return -EINVAL; ++ } ++ ++ std::vector dataR = tuningData["GainR"].getList().value_or(std::vector{}); ++ std::vector dataG = tuningData["GainG"].getList().value_or(std::vector{}); ++ std::vector dataB = tuningData["GainB"].getList().value_or(std::vector{}); ++ if ((dataR.size() == kAwbNbRef) && (dataG.size() == kAwbNbRef) && (dataB.size() == kAwbNbRef)) { ++ std::copy(dataR.begin(), dataR.end(), params_.gainRGB.begin() + 0 * dataR.size()); ++ std::copy(dataG.begin(), dataG.end(), params_.gainRGB.begin() + 1 * dataR.size()); ++ std::copy(dataB.begin(), dataB.end(), params_.gainRGB.begin() + 2 * dataR.size()); ++ if (!isColourGainValid(Span{ params_.gainRGB })) { ++ LOG(DcmippAwb, Error) << "Invalid GainR/G/B"; ++ return -EINVAL; ++ } ++ } else if ((dataR.size() != 0) || (dataG.size() != 0) || (dataB.size() != 0)) { ++ LOG(DcmippAwb, Error) << "Invalid GainR/G/B"; ++ return -EINVAL; ++ } ++ ++ std::vector matrix = tuningData["Cconv"].getList().value_or(std::vector{}); ++ if (matrix.size() == kAwbNbRef * 3 * 3) { ++ if (!isColourCorrectionValid(matrix)) { ++ LOG(DcmippAwb, Error) << "Invalid Cconv values"; ++ return -EINVAL; ++ } ++ std::copy(matrix.begin(), matrix.end(), params_.cconv.begin()); ++ } else if (matrix.size() != 0) { ++ LOG(DcmippAwb, Error) << "Invalid Cconv"; ++ return -EINVAL; ++ } ++ ++ /* Check for ISP Gain / ColorConv static config */ ++ params_.gainStatic.gainR = tuningData["FixedGainR"].get(0); ++ params_.gainStatic.gainG = tuningData["FixedGainG"].get(0); ++ params_.gainStatic.gainB = tuningData["FixedGainB"].get(0); ++ if (!isColourGainValid(params_.gainStatic.gainR) || !isColourGainValid(params_.gainStatic.gainG) || !isColourGainValid(params_.gainStatic.gainB)) { ++ LOG(DcmippAwb, Error) << "Invalid FixedGainR/G/B"; ++ return -EINVAL; ++ } ++ if (!params_.gainStatic.gainR && !params_.gainStatic.gainG && !params_.gainStatic.gainB) { ++ params_.gainStatic.enable = false; ++ } else { ++ params_.gainStatic.enable = tuningData["FixedGainEnable"].get(true); ++ } ++ ++ matrix = tuningData["FixedCconv"].getList().value_or(std::vector{}); ++ if (!matrix.size()) { ++ memset(params_.cconvStatic.coeff, 0, sizeof(params_.cconvStatic.coeff)); ++ params_.cconvStatic.enable = false; ++ } else if (matrix.size() == 3 * 3) { ++ if (!isColourCorrectionValid(matrix)) { ++ LOG(DcmippAwb, Error) << "Invalid FixedCconv values"; ++ return -EINVAL; ++ } ++ for (unsigned int i = 0; i < 3; i++) ++ for (unsigned int j = 0; j < 3; j++) ++ params_.cconvStatic.coeff[i][j] = matrix[i * 3 + j]; ++ params_.cconvStatic.enable = tuningData["FixedCconvEnable"].get(true); ++ } else { ++ LOG(DcmippAwb, Error) << "Invalid FixedCconv"; ++ return -EINVAL; ++ } ++ ++#ifdef EVISION_ALGO_ENABLED ++ if (params_.algoEnable) { ++ if (!initializeLibrary()) { ++ LOG(DcmippAwb, Error) << "Initialize library failed"; ++ return -EINVAL; ++ } ++ ++ if (!process_simple_algo) { ++ if (!initializeAwbInstance()) { ++ LOG(DcmippAwb, Error) << "Cannot initialize awb instance"; ++ deinitializeLibrary(); ++ return -EINVAL; ++ } ++ ++ if (!configureAwbAlgo()) { ++ LOG(DcmippAwb, Error) << "Cannot configure awb algorithm"; ++ deinitializeAwbInstance(); ++ deinitializeLibrary(); ++ return -EINVAL; ++ } ++ } ++ } ++#endif /* EVISION_ALGO_ENABLED */ ++ ++ /* Configure exposed controls */ ++ context.dcmippControls[&controls::draft::ColourGains3Enable] = ControlInfo(false, true); ++ context.dcmippControls[&controls::draft::ColourGains3] = ControlInfo(kColGainMin, kColGainMax, kColGainDef); ++ context.dcmippControls[&controls::draft::ColourCorrectionEnable] = ControlInfo(false, true); ++ context.dcmippControls[&controls::draft::ColourCorrection] = ControlInfo(kColCorrectionMin, kColCorrectionMax, kColCorrectionDef); ++ context.dcmippControls[&controls::AwbEnable] = ControlInfo(false, true); ++ context.dcmippControls[&controls::AwbMode] = ControlInfo(controls::AwbModeValues); ++ context.dcmippControls[&controls::draft::AwbProfileName] = ControlInfo(0, kProfileNameLen, kProfileNameLen); ++ context.dcmippControls[&controls::draft::AwbReferenceColorTemperature] = ControlInfo(kColTemperatureMin, kColTemperatureMax, kColTemperatureDef); ++ context.dcmippControls[&controls::draft::AwbColourGains3] = ControlInfo(kColGainMin, kColGainMax, kColGainDef); ++ context.dcmippControls[&controls::draft::AwbColourCorrection] = ControlInfo(kColCorrectionMin, kColCorrectionMax, kColCorrectionDef); ++ context.dcmippControls[&controls::draft::AwbCustomColorTemperature] = ControlInfo(kColTemperatureMin, kColTemperatureMax, kColTemperatureDef); ++ ++ return 0; ++} ++ ++int Awb::configure([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const IPACameraSensorInfo &configInfo) ++{ ++ /* Set the initial ISP values in the pending config */ ++ if (!params_.algoEnable) { ++ /* Apply static ISP Gain / ColorConv config */ ++ config_.gain = params_.gainStatic; ++ config_.cconv = params_.cconvStatic; ++ } else { ++ int i, profId; ++ /* Apply the first profile with color temp above minimum value before first processing of the algorithm */ ++ profId = 0; ++ for (i = 0; i < kAwbNbRef; i++) { ++ if (params_.referenceColorTemp[i] >= kColorTempAlgoDefaultMin) { ++ profId = i; ++ break; ++ } ++ } ++ applyProfile(profId); ++ } ++ ++ config_.pending = true; ++ ++ return 0; ++} ++ ++void Awb::queueRequest([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ const ControlList &controls) ++{ ++ bool awbModeAutoDisabling = false; ++ ++ /* Algo ctrl: only update params_ which will be considered upon the next process() call */ ++ const auto &algoEnable = controls.get(controls::AwbEnable); ++ const auto &awbMode = controls.get(controls::AwbMode); ++ const auto &profileName = controls.get(controls::draft::AwbProfileName); ++ const auto &referenceColorTemp = controls.get(controls::draft::AwbReferenceColorTemperature); ++ const auto &gainRGB = controls.get(controls::draft::AwbColourGains3); ++ const auto &cconv = controls.get(controls::draft::AwbColourCorrection); ++ ++ if (algoEnable) { ++ params_.algoEnable = *algoEnable; ++ LOG(DcmippAwb, Debug) << "Updating AwbEnable to " << *algoEnable; ++ } ++ ++ if (awbMode) { ++ if (*awbMode == controls::AwbAuto) { ++ params_.algoEnable = true; ++ /* mark the current profile as dirty so it will be applied at next algo run */ ++ internal_.profileDirty = true; ++ LOG(DcmippAwb, Debug) << "Updating AwbMode / AwbEnable to " ++ << params_.algoEnable; ++ } else if (*awbMode == controls::AwbCustom) { ++ if (params_.algoEnable) ++ awbModeAutoDisabling = true; ++ params_.algoEnable = false; ++ LOG(DcmippAwb, Debug) << "Updating AwbMode / AwbEnable to " ++ << params_.algoEnable; ++ } else { ++ LOG(DcmippAwb, Error) << "Unsupported AwbMode: " << *awbMode; ++ } ++ } ++ ++ if (profileName) { ++ /* The Controls class does not support array of string. So, split the concatenated string */ ++ std::string token; ++ std::stringstream ss(*profileName); ++ for (int i = 0; i < kAwbNbRef; i++) { ++ if (!getline(ss, token, kDelimiter)) ++ token = ""; ++ params_.profileName[i] = token; ++ } ++ /* mark the current profile as dirty so it will be applied at next algo run */ ++ internal_.profileDirty = true; ++ LOG(DcmippAwb, Debug) << "Updating AwbProfileName to " << *profileName; ++ } ++ ++ if (referenceColorTemp) { ++ if (!isColourTemperatureValid(*referenceColorTemp)) { ++ LOG(DcmippAwb, Error) << "Invalid RefColorTemp values"; ++ return; ++ } ++ std::copy(std::begin(*referenceColorTemp), std::end(*referenceColorTemp), params_.referenceColorTemp.begin()); ++ /* mark the current profile as dirty so it will be applied at next algo run */ ++ internal_.profileDirty = true; ++ LOG(DcmippAwb, Debug) << "Updating AwbRefColorTemp to " << (*referenceColorTemp)[0] << "..."; ++ } ++ ++ if (gainRGB) { ++ if (!isColourGainValid(*gainRGB)) { ++ LOG(DcmippAwb, Error) << "Invalid AwbColourGain values"; ++ return; ++ } ++ std::copy(std::begin(*gainRGB), std::end(*gainRGB), params_.gainRGB.begin()); ++ /* mark the current profile as dirty so it will be applied at next algo run */ ++ internal_.profileDirty = true; ++ LOG(DcmippAwb, Debug) << "Updating AwbColourGains3 to " << (*gainRGB)[0] << "..."; ++ } ++ ++ if (cconv) { ++ if (!isColourCorrectionValid(*cconv)) { ++ LOG(DcmippAwb, Error) << "Invalid CConv value"; ++ return; ++ } ++ std::copy(std::begin(*cconv), std::end(*cconv), params_.cconv.begin()); ++ /* mark the current profile as dirty so it will be applied at next algo run */ ++ internal_.profileDirty = true; ++ LOG(DcmippAwb, Debug) << "Updating AwbColourConv to " << (*cconv)[0] << "..."; ++ } ++ ++ if (internal_.profileDirty && !configureAwbAlgo()) { ++ LOG(DcmippAwb, Error) << "Cannot reconfigure awb algorithm"; ++ return; ++ } ++ ++ /* Static config: update params_ and if applicable force config_ update now */ ++ const auto &customColorTemp = controls.get(controls::draft::AwbCustomColorTemperature); ++ const auto &staticGainEnable = controls.get(controls::draft::ColourGains3Enable); ++ const auto &staticGain = controls.get(controls::draft::ColourGains3); ++ const auto &staticCconvEnable = controls.get(controls::draft::ColourCorrectionEnable); ++ const auto &staticCconv = controls.get(controls::draft::ColourCorrection); ++ ++ if (customColorTemp && !params_.algoEnable) { ++ /* Search for the corresponding profile and apply it */ ++ int profId; ++ for (profId = 0; profId < kAwbNbRef; profId++) ++ if (params_.referenceColorTemp[profId] == *customColorTemp) ++ break; ++ ++ if (profId >= kAwbNbRef) { ++ LOG(DcmippAwb, Error) << "Invalid Custom Colour Temp: " << *customColorTemp; ++ /* Revert the 'awbMode = AwbCustom' request if needed */ ++ if (awbModeAutoDisabling) ++ params_.algoEnable = true; ++ } else { ++ applyProfile(profId); ++ } ++ ++ internal_.customColorTemp = *customColorTemp; ++ } ++ ++ if (staticGainEnable) { ++ params_.gainStatic.enable = *staticGainEnable; ++ if (!params_.algoEnable) { ++ config_.gain.enable = *staticGainEnable; ++ config_.pending = true; ++ } ++ LOG(DcmippAwb, Debug) << "Updating ColourGains3Enable to " << *staticGainEnable; ++ } ++ ++ if (staticGain) { ++ if (!isColourGainValid(*staticGain)) { ++ LOG(DcmippAwb, Error) << "Invalid FixedGainR/G/B"; ++ return; ++ } ++ params_.gainStatic.gainR = (*staticGain)[0]; ++ params_.gainStatic.gainG = (*staticGain)[1]; ++ params_.gainStatic.gainB = (*staticGain)[2]; ++ if (!params_.algoEnable) { ++ config_.gain.gainR = (*staticGain)[0]; ++ config_.gain.gainG = (*staticGain)[1]; ++ config_.gain.gainB = (*staticGain)[2]; ++ config_.pending = true; ++ } ++ LOG(DcmippAwb, Debug) << "Updating ColourGains3 to " ++ << (*staticGain)[0] << " / " ++ << (*staticGain)[1] << " / " ++ << (*staticGain)[2]; ++ } ++ ++ if (staticCconvEnable) { ++ params_.cconvStatic.enable = *staticCconvEnable; ++ if (!params_.algoEnable) { ++ config_.cconv.enable = *staticCconvEnable; ++ config_.pending = true; ++ } ++ LOG(DcmippAwb, Debug) << "Updating ColourConvEnable to " << *staticCconvEnable; ++ } ++ ++ if (staticCconv) { ++ if (!isColourCorrectionValid(*staticCconv)) { ++ LOG(DcmippAwb, Error) << "Invalid staticCconv value"; ++ return; ++ } ++ for (unsigned int i = 0; i < 3; i++) ++ for (unsigned int j = 0; j < 3; j++) ++ params_.cconvStatic.coeff[i][j] = (*staticCconv)[i * 3 + j]; ++ if (!params_.algoEnable) { ++ memcpy(config_.cconv.coeff, params_.cconvStatic.coeff, sizeof(config_.cconv.coeff)); ++ config_.pending = true; ++ } ++ LOG(DcmippAwb, Debug) << "Updating ColourConv to " << (*staticCconv)[0] << "..."; ++ } ++} ++ ++void Awb::prepare(IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ++ [[maybe_unused]] ControlList &sensorControls, ++ [[maybe_unused]] ControlList &ispControls) ++{ ++ /* Copy the pending config to the isp_params. Update the IPAContext */ ++ if (config_.pending) { ++ /* Configure Gain params */ ++ params->module_cfg_update |= STM32_DCMIPP_ISP_EX; ++ toShiftMultiplier(config_.gain.gainR, ¶ms->ctrls.ex_cfg.shift_r, ¶ms->ctrls.ex_cfg.mult_r); ++ toShiftMultiplier(config_.gain.gainG, ¶ms->ctrls.ex_cfg.shift_g, ¶ms->ctrls.ex_cfg.mult_g); ++ toShiftMultiplier(config_.gain.gainB, ¶ms->ctrls.ex_cfg.shift_b, ¶ms->ctrls.ex_cfg.mult_b); ++ params->ctrls.ex_cfg.en = config_.gain.enable; ++ ++ /* Configure Color Conv params */ ++ params->module_cfg_update |= STM32_DCMIPP_ISP_CC; ++ params->ctrls.cc_cfg.ra = 0; ++ params->ctrls.cc_cfg.ga = 0; ++ params->ctrls.cc_cfg.ba = 0; ++ params->ctrls.cc_cfg.clamp = 0; ++ params->ctrls.cc_cfg.rr = toCConvReg(config_.cconv.coeff[0][0]); ++ params->ctrls.cc_cfg.rg = toCConvReg(config_.cconv.coeff[0][1]); ++ params->ctrls.cc_cfg.rb = toCConvReg(config_.cconv.coeff[0][2]); ++ params->ctrls.cc_cfg.gr = toCConvReg(config_.cconv.coeff[1][0]); ++ params->ctrls.cc_cfg.gg = toCConvReg(config_.cconv.coeff[1][1]); ++ params->ctrls.cc_cfg.gb = toCConvReg(config_.cconv.coeff[1][2]); ++ params->ctrls.cc_cfg.br = toCConvReg(config_.cconv.coeff[2][0]); ++ params->ctrls.cc_cfg.bg = toCConvReg(config_.cconv.coeff[2][1]); ++ params->ctrls.cc_cfg.bb = toCConvReg(config_.cconv.coeff[2][2]); ++ params->ctrls.cc_cfg.en = config_.cconv.enable; ++ ++ /* Update context */ ++ context.isp.gain = config_.gain; ++ context.isp.cconv = config_.cconv; ++ ++ /* Start the invalid stats counter from this update. Consider only ISP, not sensor */ ++ internal_.invalidStats = context.info.ispStatLatency + kIspApplyConfigLatency; ++ ++ /* Clear the pending request */ ++ config_.pending = false; ++ } ++} ++ ++void Awb::process([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ [[maybe_unused]] const stm32_dcmipp_stat_buf *stats, ++ ControlList &metadata) ++{ ++ /* Compute a new pending config from stats */ ++ if (params_.algoEnable) { ++ if (process_simple_algo) { ++ /* Fall back to simple AWB algorithm if evision library is not found or used. ++ In this case, we apply the first profile with color temp above the minimum value */ ++ int i, profId; ++ ++ profId = 0; ++ for (i = 0; i < kAwbNbRef; i++) { ++ if (params_.referenceColorTemp[i] >= kColorTempAlgoDefaultMin) { ++ profId = i; ++ break; ++ } ++ } ++ ++ /* Apply the first profile with color temperature higher than 4500. ++ If no such profile has been found, apply the first profile by default (id = 0) */ ++ applyProfile(profId); ++ } ++#ifdef EVISION_ALGO_ENABLED ++ else { ++ int32_t colorTemp = 0; ++ uint32_t ccAvgRGB[3]; ++ double meas[3]; ++ uint32_t profId = 0; ++ int32_t avgUpGain[3]; ++ ++ /* Check if stats can be considered as valid */ ++ if (internal_.invalidStats) ++ internal_.invalidStats--; ++ ++ if (!internal_.invalidStats) { ++ evision_return_t e_ret; ++ ++ /* Get RGB before ISP gain and Black level */ ++ getStatUp(context, stats->post.average_RGB, avgUpGain); ++ ++ bool stat_has_changed = false; ++ if (internal_.profileDirty) { ++ skip_stat_check_count = kIspStatCheckSkipAfterCTEstimation; ++ color_temperature_history[0] = 0; ++ color_temperature_history[1] = 0; ++ } else { ++ for (int i = 0; i < 3; ++i) ++ for (int j = 0; j < 3; ++j) ++ if (abs(avgUpGain[j] - stats_history[i][j]) > kIspStatDeltaMax) ++ stat_has_changed = true; ++ } ++ ++ if (skip_stat_check_count || stat_has_changed) { ++ stats_history[2][0] = avgUpGain[0]; ++ stats_history[2][1] = avgUpGain[1]; ++ stats_history[2][2] = avgUpGain[2]; ++ ++ /* Apply the current color conversion matrix to get stats after color conversion ISP block*/ ++ applyCConv(context, stats->post.average_RGB, ccAvgRGB); ++ ++ /* Invert gamma */ ++ for (int i = 0; i < 3; ++i) { ++ meas[i] = applyGammaInverse(ccAvgRGB[i]); ++ } ++ ++ /* Run algo to estimate gain and color conversion to apply */ ++ e_ret = evision_api_awb_run_average(pIspAWBestimator, NULL, 1, meas); ++ if (e_ret == EVISION_RET_SUCCESS) { ++ if (pIspAWBestimator->out_temp != internal_.colorTemp) { ++ if (pIspAWBestimator->out_temp == color_temperature_history[1]) { ++ skip_stat_check_count = 0; //oscillation detected ++ LOG(DcmippAwb, Debug) << "Oscillation detected"; ++ } else { ++ if (skip_stat_check_count <= kIspStatCheckSkipAfterCTEstimation) ++ skip_stat_check_count = kIspStatCheckSkipAfterCTEstimation; ++ /* Find the index profile for this referenceColorTemp */ ++ for (profId = 0; profId < EVISION_AWB_MAX_PROFILE_COUNT; profId++) { ++ if (pIspAWBestimator->out_temp == params_.referenceColorTemp[profId]) ++ break; ++ } ++ ++ if (profId == EVISION_AWB_MAX_PROFILE_COUNT) { ++ LOG(DcmippAwb, Error) << "Unknown AWB profile"; ++ return; ++ } else { ++ /* Apply new ISP Color Conversion */ ++ colorTemp = params_.referenceColorTemp[profId]; ++ } ++ } ++ } ++ ++ if (colorTemp || internal_.profileDirty) { ++ applyProfile((int)profId); ++ } ++ ++ } else { ++ LOG(DcmippAwb, Error) << "Unable to run evivion library"; ++ return; ++ } ++ } ++ ++ /* Decrease counter to limit the number of estmations before reaching convergence */ ++ if (skip_stat_check_count > 0) { ++ skip_stat_check_count--; ++ } ++ ++ /* Store history to be able to detect variation */ ++ stats_history[1][0] = stats_history[0][0]; ++ stats_history[1][1] = stats_history[0][1]; ++ stats_history[1][2] = stats_history[0][2]; ++ stats_history[0][0] = avgUpGain[0]; ++ stats_history[0][1] = avgUpGain[1]; ++ stats_history[0][2] = avgUpGain[2]; ++ color_temperature_history[1] = color_temperature_history[0]; ++ color_temperature_history[0] = colorTemp ? colorTemp : internal_.colorTemp; ++ } ++ } ++#endif /* EVISION_ALGO_ENABLED */ ++ } ++ ++ /* Set metadata */ ++ metadata.set(controls::draft::ColourGains3Enable, config_.gain.enable); ++ metadata.set(controls::draft::ColourGains3, ++ { static_cast(config_.gain.gainR), ++ static_cast(config_.gain.gainG), ++ static_cast(config_.gain.gainB) }); ++ ++ metadata.set(controls::draft::ColourCorrectionEnable, config_.cconv.enable); ++ metadata.set(controls::draft::ColourCorrection, ++ { config_.cconv.coeff[0][0], config_.cconv.coeff[0][1], config_.cconv.coeff[0][2], ++ config_.cconv.coeff[1][0], config_.cconv.coeff[1][1], config_.cconv.coeff[1][2], ++ config_.cconv.coeff[2][0], config_.cconv.coeff[2][1], config_.cconv.coeff[2][2] }); ++ ++ /* The Controls class does not support array of string. So, concatenate the strings in a single one */ ++ std::string concatProfiles = ""; ++ for (auto const &profile : params_.profileName) { ++ if (!concatProfiles.empty()) ++ concatProfiles += kDelimiter; ++ concatProfiles += profile; ++ } ++ metadata.set(controls::draft::AwbProfileName, concatProfiles); ++ ++ metadata.set(controls::AwbEnable, params_.algoEnable); ++ metadata.set(controls::AwbMode, params_.algoEnable ? controls::AwbAuto : controls::AwbCustom); ++ metadata.set(controls::draft::AwbReferenceColorTemperature, params_.referenceColorTemp); ++ metadata.set(controls::draft::AwbColourGains3, params_.gainRGB); ++ metadata.set(controls::draft::AwbColourCorrection, params_.cconv); ++ ++ if (internal_.colorTemp) ++ metadata.set(controls::ColourTemperature, internal_.colorTemp); ++ if (!internal_.profileName.empty()) ++ metadata.set(controls::draft::AwbCurrentProfileName, internal_.profileName); ++ if (internal_.customColorTemp && !params_.algoEnable) ++ metadata.set(controls::draft::AwbCustomColorTemperature, internal_.customColorTemp); ++} ++ ++REGISTER_IPA_ALGORITHM(Awb, "Awb") ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/awb.h b/src/ipa/dcmipp/algorithms/awb.h +new file mode 100644 +index 00000000..239868c9 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/awb.h +@@ -0,0 +1,65 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * Copyright (C) 2024 LACROIX - Impulse ++ * ++ * awb.h - STM32 DCMIPP AWB control ++ */ ++ ++#pragma once ++ ++#include "algorithm.h" ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++class Awb : public Algorithm ++{ ++public: ++ Awb() = default; ++ ++ int init(IPAContext &context, const YamlObject &tuningData) override; ++ int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; ++ void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const ControlList &controls) override; ++ void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ControlList &sensorControls, ++ ControlList &ispControls) override; ++ void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const stm32_dcmipp_stat_buf *stats, ControlList &metadata) override; ++ ++private: ++ static constexpr int32_t kAwbNbRef = 5; ++ ++ struct algoParams { ++ bool algoEnable; ++ std::array profileName; ++ std::array referenceColorTemp; ++ std::array gainRGB; ++ std::array cconv; ++ struct IPAIspGain gainStatic; ++ struct IPAIspColorConv cconvStatic; ++ } params_; ++ ++ struct algoInternal { ++ int32_t invalidStats; ++ int32_t colorTemp; ++ std::string profileName; ++ int32_t customColorTemp; ++ bool profileDirty; ++ } internal_; ++ ++ struct algoConfig { ++ bool pending; ++ struct IPAIspGain gain; ++ struct IPAIspColorConv cconv; ++ } config_; ++ ++ bool configureAwbAlgo(); ++ void applyProfile(int profId); ++}; ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/badpixel.cpp b/src/ipa/dcmipp/algorithms/badpixel.cpp +new file mode 100644 +index 00000000..eb681c13 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/badpixel.cpp +@@ -0,0 +1,185 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * badpixel.cpp - STM32 DCMIPP Bad Pixel Removal ++ */ ++ ++#include "badpixel.h" ++ ++#include ++#include ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++LOG_DEFINE_CATEGORY(DcmippBadPixel) ++ ++static constexpr int kStrengthMin = 0; ++static constexpr int kStrengthMax = 7; ++static constexpr int kStrengthDef = 0; ++static constexpr int kThresholdMin = 0; ++static constexpr int kThresholdMax = 4094 * 4094; ++static constexpr int kThresholdDef = 0; ++ ++static bool isStrengthValid(int8_t strength) ++{ ++ return (strength >= kStrengthMin && strength <= kStrengthMax) || strength == -1; ++} ++ ++static bool isThresholdValid(int32_t threshold) ++{ ++ return threshold >= kThresholdMin && threshold <= kThresholdMax; ++} ++ ++int BadPixel::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) ++{ ++ /* Parse Tuning Data to get algo parameters */ ++ int8_t strength = tuningData["Strength"].get(-1); ++ int32_t threshold = tuningData["Threshold"].get(kThresholdDef); ++ if (!isStrengthValid(strength)) { ++ LOG(DcmippBadPixel, Error) << "Invalid Strength: " << strength; ++ return -EINVAL; ++ } ++ if (!isThresholdValid(threshold)) { ++ LOG(DcmippBadPixel, Error) << "Invalid Threshold: " << threshold; ++ return -EINVAL; ++ } ++ if (strength == -1 && !threshold) { ++ internal_.threshold = 0; ++ params_.badpixel.strength = 0; ++ params_.badpixel.enable = false; ++ } else { ++ internal_.threshold = threshold; ++ internal_.countAccu = 0; ++ internal_.measureNb = 0; ++ params_.badpixel.strength = strength < 0 ? 0 : strength; ++ params_.badpixel.enable = tuningData["Enable"].get(true); ++ } ++ ++ /* Configure exposed controls */ ++ context.dcmippControls[&controls::draft::BadPixelRemovalEnable] = ControlInfo(false, true); ++ context.dcmippControls[&controls::draft::BadPixelRemovalStrength] = ControlInfo(kStrengthMin, kStrengthMax, kStrengthDef); ++ context.dcmippControls[&controls::draft::BadPixelRemovalThreshold] = ControlInfo(kThresholdMin, kThresholdMax, kThresholdDef); ++ ++ return 0; ++} ++ ++int BadPixel::configure([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const IPACameraSensorInfo &configInfo) ++{ ++ /* Set the initial ISP values in the pending config */ ++ config_.badpixel = params_.badpixel; ++ config_.pending = true; ++ ++ return 0; ++} ++ ++void BadPixel::queueRequest([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ const ControlList &controls) ++{ ++ /* Algo ctrl: only update internal_ which will be considered upon the next process() call */ ++ const auto &threshold = controls.get(controls::draft::BadPixelRemovalThreshold); ++ if (threshold) { ++ if (!isThresholdValid(*threshold)) { ++ LOG(DcmippBadPixel, Error) << "Invalid Threshold: " << *threshold; ++ return; ++ } ++ internal_.threshold = *threshold; ++ internal_.measureNb = 0; ++ internal_.countAccu = 0; ++ LOG(DcmippBadPixel, Debug) << "Updating threshold to " << *threshold; ++ } ++ ++ /* Update params_ and force config_ update now */ ++ const auto &enable = controls.get(controls::draft::BadPixelRemovalEnable); ++ const auto &strength = controls.get(controls::draft::BadPixelRemovalStrength); ++ ++ if (enable) { ++ params_.badpixel.enable = *enable; ++ config_.badpixel.enable = params_.badpixel.enable; ++ config_.pending = true; ++ LOG(DcmippBadPixel, Debug) << "Updating Bad Pixel status to " << *enable; ++ } ++ ++ if (strength) { ++ if (!isStrengthValid(*strength)) { ++ LOG(DcmippBadPixel, Error) << "Invalid Strength: " << *strength; ++ return; ++ } ++ params_.badpixel.strength = *strength; ++ if (!internal_.threshold) { ++ config_.badpixel.strength = params_.badpixel.strength; ++ config_.pending = true; ++ } ++ LOG(DcmippBadPixel, Debug) << "Updating strength to " << *strength; ++ } ++} ++ ++void BadPixel::prepare(IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ++ [[maybe_unused]] ControlList &sensorControls, ++ [[maybe_unused]] ControlList &ispControls) ++{ ++ /* Copy the pending config to the isp_params. Update the IPAContext */ ++ if (config_.pending) { ++ /* Configure Bad Pixel params */ ++ params->module_cfg_update |= STM32_DCMIPP_ISP_BPR; ++ params->ctrls.bpr_cfg.en = config_.badpixel.enable; ++ params->ctrls.bpr_cfg.strength = config_.badpixel.strength; ++ ++ /* Update context */ ++ context.isp.badpixel = config_.badpixel; ++ ++ /* Clear the pending request */ ++ config_.pending = false; ++ } ++} ++ ++const int32_t kBadPixelMeasures = 30; ++ ++void BadPixel::process(IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ [[maybe_unused]] const stm32_dcmipp_stat_buf *stats, ++ ControlList &metadata) ++{ ++ /* Adjust strength according to threshold */ ++ if (params_.badpixel.enable && internal_.threshold) { ++ /* Make kBadPixelMeasures measures before computing the average */ ++ internal_.countAccu += stats->bad_pixel_count; ++ if (++internal_.measureNb == kBadPixelMeasures) { ++ uint8_t strength = context.isp.badpixel.strength; ++ internal_.countAccu /= kBadPixelMeasures; ++ ++ /* Increase or decrease strength */ ++ if (internal_.countAccu > internal_.threshold && strength > 0) ++ strength--; ++ else if (internal_.countAccu < internal_.threshold && strength < kStrengthMax - 1) ++ strength++; ++ ++ /* Update config */ ++ config_.badpixel.strength = strength; ++ config_.pending = true; ++ internal_.measureNb = 0; ++ internal_.countAccu = 0; ++ } ++ } ++ ++ /* Set bad pixel metadata */ ++ metadata.set(controls::draft::BadPixelRemovalEnable, config_.badpixel.enable); ++ metadata.set(controls::draft::BadPixelRemovalStrength, config_.badpixel.strength); ++ metadata.set(controls::draft::BadPixelRemovalThreshold, internal_.threshold); ++ metadata.set(controls::draft::BadPixelRemovalCount, stats->bad_pixel_count); ++} ++ ++REGISTER_IPA_ALGORITHM(BadPixel, "BadPixel") ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/badpixel.h b/src/ipa/dcmipp/algorithms/badpixel.h +new file mode 100644 +index 00000000..0db7645a +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/badpixel.h +@@ -0,0 +1,50 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * badpixel.h - STM32 DCMIPP Bad Pixel Removal ++ */ ++ ++#pragma once ++ ++#include "algorithm.h" ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++class BadPixel : public Algorithm ++{ ++public: ++ BadPixel() = default; ++ ++ int init(IPAContext &context, const YamlObject &tuningData) override; ++ int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; ++ void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const ControlList &controls) override; ++ void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ControlList &sensorControls, ++ ControlList &ispControls) override; ++ void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const stm32_dcmipp_stat_buf *stats, ControlList &metadata) override; ++ ++private: ++ struct algoParams { ++ struct IPAIspBadpixel badpixel; ++ } params_; ++ ++ struct algoInternal { ++ int32_t threshold; ++ int32_t countAccu; ++ int32_t measureNb; ++ } internal_; ++ ++ struct algoConfig { ++ bool pending; ++ struct IPAIspBadpixel badpixel; ++ } config_; ++}; ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/blc.cpp b/src/ipa/dcmipp/algorithms/blc.cpp +new file mode 100644 +index 00000000..9b40dd5f +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/blc.cpp +@@ -0,0 +1,145 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * blc.cpp - STM32 DCMIPP Black Level Correction ++ */ ++ ++#include "blc.h" ++ ++#include ++#include ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++LOG_DEFINE_CATEGORY(DcmippBlc) ++ ++static constexpr int kBLMin = 0; ++static constexpr int kBLMax = 255; ++static constexpr int kBLDef = 0; ++ ++static bool isValid(int32_t level) ++{ ++ return level >= kBLMin && level <= kBLMax; ++} ++ ++int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) ++{ ++ /* Parse Tuning Data to get algo parameters */ ++ std::vector level = tuningData["Level"].getList().value_or(std::vector{}); ++ if (!level.size()) { ++ params_.blackLevel.blcR = kBLDef; ++ params_.blackLevel.blcG = kBLDef; ++ params_.blackLevel.blcB = kBLDef; ++ params_.blackLevel.enable = false; ++ } else if (level.size() == 3) { ++ for (auto const &l : level) ++ if (!isValid(l)) { ++ LOG(DcmippBlc, Error) << "Invalid black level value: " << l; ++ return -EINVAL; ++ } ++ params_.blackLevel.blcR = level[0]; ++ params_.blackLevel.blcG = level[1]; ++ params_.blackLevel.blcB = level[2]; ++ params_.blackLevel.enable = false; ++ params_.blackLevel.enable = tuningData["Enable"].get(true); ++ } else { ++ LOG(DcmippBlc, Error) << "Invalid nubmer of black levels"; ++ return -EINVAL; ++ } ++ ++ /* Configure exposed controls */ ++ context.dcmippControls[&controls::draft::BlackLevelCorrectionEnable] = ControlInfo(false, true); ++ context.dcmippControls[&controls::draft::BlackLevelCorrectionLevels] = ControlInfo(kBLMin, kBLMax, kBLDef); ++ ++ return 0; ++} ++ ++int BlackLevelCorrection::configure([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const IPACameraSensorInfo &configInfo) ++{ ++ /* Set the initial ISP/sensor values in the pending config */ ++ config_.blackLevel = params_.blackLevel; ++ config_.pending = true; ++ ++ return 0; ++} ++ ++void BlackLevelCorrection::queueRequest([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ const ControlList &controls) ++{ ++ /* Update params_ and force config_ update now */ ++ const auto &enable = controls.get(controls::draft::BlackLevelCorrectionEnable); ++ const auto &level = controls.get(controls::draft::BlackLevelCorrectionLevels); ++ ++ if (enable) { ++ params_.blackLevel.enable = *enable; ++ config_.blackLevel.enable = params_.blackLevel.enable; ++ config_.pending = true; ++ LOG(DcmippBlc, Debug) << "Updating black level status to " << *enable; ++ } ++ ++ if (level) { ++ for (auto const &l : *level) ++ if (!isValid(l)) { ++ LOG(DcmippBlc, Error) << "Invalid black level value: " << l; ++ return; ++ } ++ params_.blackLevel.blcR = (*level)[0]; ++ params_.blackLevel.blcG = (*level)[1]; ++ params_.blackLevel.blcB = (*level)[2]; ++ config_.blackLevel.blcR = params_.blackLevel.blcR; ++ config_.blackLevel.blcG = params_.blackLevel.blcG; ++ config_.blackLevel.blcB = params_.blackLevel.blcB; ++ config_.pending = true; ++ LOG(DcmippBlc, Debug) << "Updating black level to " << (*level)[0] << "..."; ++ } ++} ++ ++void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ++ [[maybe_unused]] ControlList &sensorControls, ++ [[maybe_unused]] ControlList &ispControls) ++{ ++ /* Copy the pending config to the isp_params. Update the IPAContext */ ++ if (config_.pending) { ++ /* Configure Black Level params */ ++ params->module_cfg_update |= STM32_DCMIPP_ISP_BLC; ++ params->ctrls.blc_cfg.blc_r = config_.blackLevel.blcR; ++ params->ctrls.blc_cfg.blc_g = config_.blackLevel.blcG; ++ params->ctrls.blc_cfg.blc_b = config_.blackLevel.blcB; ++ params->ctrls.blc_cfg.en = config_.blackLevel.enable; ++ ++ /* Update context */ ++ context.isp.blackLevel = config_.blackLevel; ++ ++ /* Clear the pending request */ ++ config_.pending = false; ++ } ++} ++ ++void BlackLevelCorrection::process([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ [[maybe_unused]] const stm32_dcmipp_stat_buf *stats, ++ ControlList &metadata) ++{ ++ /* Set BlackLevel metadata */ ++ metadata.set(controls::draft::BlackLevelCorrectionEnable, config_.blackLevel.enable); ++ metadata.set(controls::draft::BlackLevelCorrectionLevels, ++ { static_cast(config_.blackLevel.blcR), ++ static_cast(config_.blackLevel.blcG), ++ static_cast(config_.blackLevel.blcB) }); ++} ++ ++REGISTER_IPA_ALGORITHM(BlackLevelCorrection, "BlackLevelCorrection") ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/blc.h b/src/ipa/dcmipp/algorithms/blc.h +new file mode 100644 +index 00000000..968d5ce3 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/blc.h +@@ -0,0 +1,44 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * blc.h - STM32 DCMIPP Black Level Correction ++ */ ++ ++#pragma once ++ ++#include "algorithm.h" ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++class BlackLevelCorrection : public Algorithm ++{ ++public: ++ BlackLevelCorrection() = default; ++ ++ int init(IPAContext &context, const YamlObject &tuningData) override; ++ int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; ++ void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const ControlList &controls) override; ++ void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ControlList &sensorControls, ++ ControlList &ispControls) override; ++ void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const stm32_dcmipp_stat_buf *stats, ControlList &metadata) override; ++ ++private: ++ struct algoParams { ++ struct IPAIspBlackLevel blackLevel; ++ } params_; ++ ++ struct algoConfig { ++ bool pending; ++ struct IPAIspBlackLevel blackLevel; ++ } config_; ++}; ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/contrast.cpp b/src/ipa/dcmipp/algorithms/contrast.cpp +new file mode 100644 +index 00000000..412d9888 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/contrast.cpp +@@ -0,0 +1,141 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * contrast.cpp - STM32 DCMIPP Contrast Enhancement ++ */ ++ ++#include "contrast.h" ++ ++#include ++#include ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++LOG_DEFINE_CATEGORY(DcmippContrast) ++ ++static constexpr uint32_t kPrecisionFactor = 100; ++static constexpr uint32_t kIdle = 16; ++static constexpr int kContrastSize = 9; ++static constexpr int kFactorMin = 0; ++static constexpr int kFactorMax = 394; ++static constexpr int kFactorDef = 0; ++ ++static bool isFactorValid(int32_t factor) ++{ ++ return factor >= kFactorMin && factor <= kFactorMax; ++} ++ ++int Contrast::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) ++{ ++ /* Parse Tuning Data to get algo parameters */ ++ /* ISP Contrast */ ++ std::vector lum = tuningData["LuminanceFactor"].getList().value_or(std::vector{}); ++ if (!lum.size()) { ++ memset(params_.contrast.coeff, kFactorDef, kNbContrastFactor * sizeof(params_.contrast.coeff[0])); ++ params_.contrast.enable = false; ++ } else if (lum.size() == kNbContrastFactor) { ++ for (auto const &l : lum) ++ if (!isFactorValid(l)) { ++ LOG(DcmippContrast, Error) << "Invalid luminance factor: " << l; ++ return -EINVAL; ++ } ++ for (unsigned int i = 0; i < kNbContrastFactor; i++) ++ params_.contrast.coeff[i] = lum[i]; ++ params_.contrast.enable = tuningData["Enable"].get(true); ++ } else { ++ LOG(DcmippContrast, Error) << "Invalid LuminanceFactor"; ++ return -EINVAL; ++ } ++ ++ /* Configure exposed controls */ ++ context.dcmippControls[&controls::draft::ContrastLuminanceEnable] = ControlInfo(false, true); ++ context.dcmippControls[&controls::draft::ContrastLuminance] = ControlInfo(0, 394, 0); ++ ++ return 0; ++} ++ ++int Contrast::configure([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const IPACameraSensorInfo &configInfo) ++{ ++ /* Set the initial ISP values in the pending config */ ++ config_.contrast = params_.contrast; ++ config_.pending = true; ++ ++ return 0; ++} ++ ++void Contrast::queueRequest([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ const ControlList &controls) ++{ ++ /* Update params_ and force config_ update now */ ++ const auto &enable = controls.get(controls::draft::ContrastLuminanceEnable); ++ const auto &luminance = controls.get(controls::draft::ContrastLuminance); ++ ++ if (enable) { ++ params_.contrast.enable = *enable; ++ config_.contrast.enable = params_.contrast.enable; ++ config_.pending = true; ++ LOG(DcmippContrast, Debug) << "Updating contrast status to " << *enable; ++ } ++ ++ if (luminance) { ++ for (auto const &l : *luminance) ++ if (!isFactorValid(l)) { ++ LOG(DcmippContrast, Error) << "Invalid luminance factor: " << l; ++ return; ++ } ++ std::copy(std::begin(*luminance), std::end(*luminance), params_.contrast.coeff); ++ memcpy(config_.contrast.coeff, params_.contrast.coeff, sizeof(config_.contrast.coeff)); ++ config_.pending = true; ++ LOG(DcmippContrast, Debug) << "Updating contrast status to " << (*luminance)[0] << "..."; ++ } ++} ++ ++void Contrast::prepare(IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ++ [[maybe_unused]] ControlList &sensorControls, ++ [[maybe_unused]] ControlList &ispControls) ++{ ++ /* Copy the pending config to the isp_params. Update the IPAContext */ ++ if (config_.pending) { ++ /* Configure Contrast params */ ++ params->module_cfg_update |= STM32_DCMIPP_ISP_CE; ++ params->ctrls.ce_cfg.en = config_.contrast.enable; ++ for (unsigned int i = 0; i < kNbContrastFactor; i++) ++ /* Convert factor (Unit = 100 for "x1.0") to register format (where 16 means "x1.0") */ ++ params->ctrls.ce_cfg.lum[i] = (config_.contrast.coeff[i] * kIdle) / kPrecisionFactor; ++ ++ /* Update context */ ++ context.isp.contrast = config_.contrast; ++ ++ /* Clear the pending request */ ++ config_.pending = false; ++ } ++} ++ ++void Contrast::process([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ [[maybe_unused]] const stm32_dcmipp_stat_buf *stats, ++ ControlList &metadata) ++{ ++ /* Set contrast metadata */ ++ std::array contrast; ++ ++ metadata.set(controls::draft::ContrastLuminanceEnable, config_.contrast.enable); ++ std::copy(params_.contrast.coeff, params_.contrast.coeff + kContrastSize, contrast.begin()); ++ metadata.set(controls::draft::ContrastLuminance, contrast); ++} ++ ++REGISTER_IPA_ALGORITHM(Contrast, "Contrast") ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/contrast.h b/src/ipa/dcmipp/algorithms/contrast.h +new file mode 100644 +index 00000000..51dc85e7 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/contrast.h +@@ -0,0 +1,44 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * contrast.h - STM32 DCMIPP Contrast Enhancement ++ */ ++ ++#pragma once ++ ++#include "algorithm.h" ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++class Contrast : public Algorithm ++{ ++public: ++ Contrast() = default; ++ ++ int init(IPAContext &context, const YamlObject &tuningData) override; ++ int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; ++ void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const ControlList &controls) override; ++ void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ControlList &sensorControls, ++ ControlList &ispControls) override; ++ void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const stm32_dcmipp_stat_buf *stats, ControlList &metadata) override; ++ ++private: ++ struct algoParams { ++ struct IPAIspContrast contrast; ++ } params_; ++ ++ struct algoConfig { ++ bool pending; ++ struct IPAIspContrast contrast; ++ } config_; ++}; ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/demosaicing.cpp b/src/ipa/dcmipp/algorithms/demosaicing.cpp +new file mode 100644 +index 00000000..b0c7915a +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/demosaicing.cpp +@@ -0,0 +1,146 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * demosaicing.cpp - STM32 DCMIPP Demosaicing Filter ++ */ ++ ++#include "demosaicing.h" ++ ++#include ++#include ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++LOG_DEFINE_CATEGORY(DcmippDemosaicing) ++ ++static constexpr unsigned int kNbFilters = 4; ++static constexpr int kFilterMin = 0; ++static constexpr int kFilterMax = 7; ++static constexpr int kFilterDef = 0; ++ ++static bool isValid(int32_t filter) ++{ ++ return filter >= kFilterMin && filter <= kFilterMax; ++} ++ ++int Demosaicing::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) ++{ ++ /* Parse Tuning Data to get algo parameters */ ++ params_.demosaicing.enable = tuningData["Enable"].get(true); ++ /* ISP Demosaicing Filter */ ++ std::vector filter = tuningData["Filter"].getList().value_or(std::vector(kNbFilters, kFilterDef)); ++ if (filter.size() != kNbFilters) { ++ LOG(DcmippDemosaicing, Error) << "Invalid number of filters"; ++ return -EINVAL; ++ } ++ for (auto const &f : filter) ++ if (!isValid(f)) { ++ LOG(DcmippDemosaicing, Error) << "Invalid filter value: " << f; ++ return -EINVAL; ++ } ++ ++ params_.demosaicing.peak = filter[0]; ++ params_.demosaicing.linev = filter[1]; ++ params_.demosaicing.lineh = filter[2]; ++ params_.demosaicing.edge = filter[3]; ++ ++ /* Configure exposed controls */ ++ context.dcmippControls[&controls::draft::DemosaicingEnable] = ControlInfo(false, true); ++ context.dcmippControls[&controls::draft::DemosaicingFilter] = ControlInfo(kFilterMin, kFilterMax, kFilterDef); ++ ++ return 0; ++} ++ ++int Demosaicing::configure([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const IPACameraSensorInfo &configInfo) ++{ ++ /* Set the initial ISP values in the pending config */ ++ config_.demosaicing = params_.demosaicing; ++ config_.pending = true; ++ ++ return 0; ++} ++ ++void Demosaicing::queueRequest([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ const ControlList &controls) ++{ ++ /* Update params_ and force config_ update now */ ++ const auto &enable = controls.get(controls::draft::DemosaicingEnable); ++ const auto &filter = controls.get(controls::draft::DemosaicingFilter); ++ ++ if (enable) { ++ params_.demosaicing.enable = *enable; ++ config_.demosaicing.enable = params_.demosaicing.enable; ++ config_.pending = true; ++ LOG(DcmippDemosaicing, Debug) << "Updating demosaicing status to " << *enable; ++ } ++ ++ if (filter) { ++ for (auto const &f : *filter) ++ if (!isValid(f)) { ++ LOG(DcmippDemosaicing, Error) << "Invalid filter value: " << f; ++ return; ++ } ++ params_.demosaicing.peak = (*filter)[0]; ++ params_.demosaicing.linev = (*filter)[1]; ++ params_.demosaicing.lineh = (*filter)[2]; ++ params_.demosaicing.edge = (*filter)[3]; ++ config_.demosaicing.peak = params_.demosaicing.peak; ++ config_.demosaicing.linev = params_.demosaicing.linev; ++ config_.demosaicing.lineh = params_.demosaicing.lineh; ++ config_.demosaicing.edge = params_.demosaicing.edge; ++ config_.pending = true; ++ LOG(DcmippDemosaicing, Debug) << "Updating demosaicing filter to " << (*filter)[0] << "..."; ++ } ++} ++ ++void Demosaicing::prepare(IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ++ [[maybe_unused]] ControlList &sensorControls, ++ [[maybe_unused]] ControlList &ispControls) ++{ ++ /* Copy the pending config to the isp_params. Update the IPAContext */ ++ if (config_.pending) { ++ /* Configure Demosaicing filter params */ ++ params->module_cfg_update |= STM32_DCMIPP_ISP_DM; ++ params->ctrls.dm_cfg.en = config_.demosaicing.enable; ++ params->ctrls.dm_cfg.edge = config_.demosaicing.edge; ++ params->ctrls.dm_cfg.lineh = config_.demosaicing.lineh; ++ params->ctrls.dm_cfg.linev = config_.demosaicing.linev; ++ params->ctrls.dm_cfg.peak = config_.demosaicing.peak; ++ ++ /* Update context */ ++ context.isp.demosaicing = config_.demosaicing; ++ ++ /* Clear the pending request */ ++ config_.pending = false; ++ } ++} ++ ++void Demosaicing::process([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ [[maybe_unused]] const stm32_dcmipp_stat_buf *stats, ++ ControlList &metadata) ++{ ++ /* Set demosaicing metadata */ ++ metadata.set(controls::draft::DemosaicingEnable, config_.demosaicing.enable); ++ metadata.set(controls::draft::DemosaicingFilter, ++ { static_cast(config_.demosaicing.peak), ++ static_cast(config_.demosaicing.linev), ++ static_cast(config_.demosaicing.lineh), ++ static_cast(config_.demosaicing.edge) }); ++} ++ ++REGISTER_IPA_ALGORITHM(Demosaicing, "Demosaicing") ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/demosaicing.h b/src/ipa/dcmipp/algorithms/demosaicing.h +new file mode 100644 +index 00000000..9b407485 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/demosaicing.h +@@ -0,0 +1,44 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * demosaicing.h - STM32 DCMIPP Deomsaicing Filter ++ */ ++ ++#pragma once ++ ++#include "algorithm.h" ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++class Demosaicing : public Algorithm ++{ ++public: ++ Demosaicing() = default; ++ ++ int init(IPAContext &context, const YamlObject &tuningData) override; ++ int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; ++ void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const ControlList &controls) override; ++ void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ControlList &sensorControls, ++ ControlList &ispControls) override; ++ void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const stm32_dcmipp_stat_buf *stats, ControlList &metadata) override; ++ ++private: ++ struct algoParams { ++ struct IPAIspDemosaicing demosaicing; ++ } params_; ++ ++ struct algoConfig { ++ bool pending; ++ struct IPAIspDemosaicing demosaicing; ++ } config_; ++}; ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/gamma.cpp b/src/ipa/dcmipp/algorithms/gamma.cpp +new file mode 100644 +index 00000000..fd595479 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/gamma.cpp +@@ -0,0 +1,91 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2025 ST Microelectronics ++ * ++ * gamma.cpp - STM32 DCMIPP Gamma Correction configuration ++ */ ++ ++#include "gamma.h" ++ ++#include ++#include ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++LOG_DEFINE_CATEGORY(DcmippGamma) ++ ++int GammaCorrection::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) ++{ ++ /* Parse Tuning Data to get algo parameters */ ++ /* ISP Gamma Correction config */ ++ params_.gamma.enable = tuningData["Enable"].get(true); ++ ++ /* Configure exposed controls */ ++ context.dcmippControls[&controls::draft::GammaCorrectionEnable] = ControlInfo(false, true); ++ ++ return 0; ++} ++ ++int GammaCorrection::configure([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const IPACameraSensorInfo &configInfo) ++{ ++ /* Set the initial ISP values in the pending config */ ++ config_.gamma = params_.gamma; ++ config_.pending = true; ++ ++ return 0; ++} ++ ++void GammaCorrection::queueRequest([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ const ControlList &controls) ++{ ++ /* Update params_ and force config_ update now */ ++ const auto &enable = controls.get(controls::draft::GammaCorrectionEnable); ++ ++ if (enable) { ++ params_.gamma.enable = *enable; ++ config_.gamma = params_.gamma; ++ config_.pending = true; ++ LOG(DcmippGamma, Debug) << "Updating gamma correction to " << *enable; ++ } ++} ++ ++void GammaCorrection::prepare(IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ [[maybe_unused]] stm32_dcmipp_params_cfg *params, ++ [[maybe_unused]] ControlList &sensorControls, ++ ControlList &ispControls) ++{ ++ /* Copy the pending config to the isp controls (not isp_params). Update the IPAContext */ ++ if (config_.pending) { ++ /* Configure Gamma */ ++ ispControls.set(controls::draft::GammaCorrectionEnable, config_.gamma.enable); ++ ++ /* Update context */ ++ context.isp.gamma = config_.gamma; ++ ++ /* Clear the pending request */ ++ config_.pending = false; ++ } ++} ++ ++void GammaCorrection::process([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ [[maybe_unused]] const stm32_dcmipp_stat_buf *stats, ++ ControlList &metadata) ++{ ++ /* Set gamma metadata */ ++ metadata.set(controls::draft::GammaCorrectionEnable, config_.gamma.enable); ++} ++ ++REGISTER_IPA_ALGORITHM(GammaCorrection, "GammaCorrection") ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/gamma.h b/src/ipa/dcmipp/algorithms/gamma.h +new file mode 100644 +index 00000000..04449bd0 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/gamma.h +@@ -0,0 +1,44 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2025 ST Microelectronics ++ * ++ * gamma.h - STM32 DCMIPP Gamma Correction configuration ++ */ ++ ++#pragma once ++ ++#include "algorithm.h" ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++class GammaCorrection : public Algorithm ++{ ++public: ++ GammaCorrection() = default; ++ ++ int init(IPAContext &context, const YamlObject &tuningData) override; ++ int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; ++ void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const ControlList &controls) override; ++ void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ControlList &sensorControls, ++ ControlList &ispControls) override; ++ void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const stm32_dcmipp_stat_buf *stats, ControlList &metadata) override; ++ ++private: ++ struct algoParams { ++ struct IPAIspGammaCorrection gamma; ++ } params_; ++ ++ struct algoConfig { ++ bool pending; ++ struct IPAIspGammaCorrection gamma; ++ } config_; ++}; ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/meson.build b/src/ipa/dcmipp/algorithms/meson.build +new file mode 100644 +index 00000000..55c62894 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/meson.build +@@ -0,0 +1,12 @@ ++# SPDX-License-Identifier: CC0-1.0 ++ ++dcmipp_ipa_algorithms = files([ ++ 'statistic.cpp', ++ 'badpixel.cpp', ++ 'blc.cpp', ++ 'demosaicing.cpp', ++ 'contrast.cpp', ++ 'aec.cpp', ++ 'awb.cpp', ++ 'gamma.cpp', ++]) +diff --git a/src/ipa/dcmipp/algorithms/statistic.cpp b/src/ipa/dcmipp/algorithms/statistic.cpp +new file mode 100644 +index 00000000..9cba1960 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/statistic.cpp +@@ -0,0 +1,215 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * statistic.cpp - STM32 DCMIPP Statistic configuration ++ */ ++ ++#include "statistic.h" ++ ++#include ++#include ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++LOG_DEFINE_CATEGORY(DcmippStatistic) ++ ++enum StatProfile { ++ ProfileFull = 0, ++ ProfileAvgUp, ++ ProfileAvgDown ++}; ++ ++static constexpr int kBinsSize = 12; ++static constexpr int kSizeMin = 0; ++static constexpr int kSizeMax = 4094; ++static constexpr int kSizeDef = 0; ++static constexpr int kProfileMin = ProfileFull; ++static constexpr int kProfileMax = ProfileAvgDown; ++static constexpr int kProfileDef = ProfileFull; ++ ++static bool isSizeValid(int32_t size) ++{ ++ return size >= kSizeMin && size <= kSizeMax; ++} ++ ++static bool isProfileValid(int32_t profile) ++{ ++ return profile >= kProfileMin && profile <= kProfileMax; ++} ++ ++static int32_t getCycleDuration(uint32_t profile) ++{ ++ int32_t duration; ++ ++ switch (profile) { ++ default: ++ case ProfileFull: ++ /* 10 requests to get all stats: (up[1] + down[1]) * (avg[1] + bins[4]) */ ++ duration = (1 + 1) * (1 + 4); ++ break; ++ case ProfileAvgUp: ++ case ProfileAvgDown: ++ /* 1 single request to get AVG for up or down */ ++ duration = 1; ++ } ++ return duration; ++} ++ ++static int32_t luminanceFromRgb(const __u32 RGB[3]) ++{ ++ return (int32_t)((3 * RGB[0] + 6 * RGB[1] + RGB[2]) / 10); ++} ++ ++int Statistic::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) ++{ ++ /* Parse Tuning Data to get algo parameters */ ++ /* ISP Statistic config */ ++ params_.statistic.profile = tuningData["Profile"].get(kProfileDef); ++ if (!isProfileValid(params_.statistic.profile)) { ++ LOG(DcmippStatistic, Error) << "Invalid Profile: " << params_.statistic.profile; ++ return -EINVAL; ++ } ++ ++ /* Update ispStatLatency now, so it can be used by other algos from startup */ ++ context.info.ispStatLatency = getCycleDuration(params_.statistic.profile); ++ ++ std::vector rect = tuningData["Area"].getList().value_or(std::vector(4, kSizeDef)); ++ if (rect.size() != 4) { ++ LOG(DcmippStatistic, Error) << "Invalid Area"; ++ return -EINVAL; ++ } ++ for (auto const &s : rect) ++ if (!isSizeValid(s)) { ++ LOG(DcmippStatistic, Error) << "Invalid stat area"; ++ return -EINVAL; ++ } ++ params_.statistic.area.x0 = rect[0]; ++ params_.statistic.area.y0 = rect[1]; ++ params_.statistic.area.xSize = rect[2]; ++ params_.statistic.area.ySize = rect[3]; ++ ++ /* Configure exposed controls */ ++ context.dcmippControls[&controls::draft::StatisticArea] = ControlInfo(Rectangle{}, Rectangle(kSizeMax, kSizeMax, kSizeMax, kSizeMax), Rectangle{}); ++ context.dcmippControls[&controls::draft::StatisticProfile] = ControlInfo(0, 2, 0); ++ ++ return 0; ++} ++ ++int Statistic::configure([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const IPACameraSensorInfo &configInfo) ++{ ++ /* Set the initial ISP values in the pending config */ ++ config_.statistic = params_.statistic; ++ config_.pending = true; ++ ++ return 0; ++} ++ ++void Statistic::queueRequest([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ const ControlList &controls) ++{ ++ /* Update params_ and force config_ update now */ ++ const auto &profile = controls.get(controls::draft::StatisticProfile); ++ const auto &area = controls.get(controls::draft::StatisticArea); ++ ++ if (profile) { ++ if (!isProfileValid(*profile)) { ++ LOG(DcmippStatistic, Error) << "Invalid Profile: " << *profile; ++ return; ++ } ++ params_.statistic.profile = *profile; ++ config_.statistic.profile = params_.statistic.profile; ++ config_.pending = true; ++ LOG(DcmippStatistic, Debug) << "Updating static profile to " << *profile; ++ } ++ ++ if (area) { ++ if (!isSizeValid((*area).x) || !isSizeValid((*area).y) || !isSizeValid((*area).width) || !isSizeValid((*area).height)) { ++ LOG(DcmippStatistic, Error) << "Invalid stat area"; ++ return; ++ } ++ params_.statistic.area.x0 = (*area).x; ++ params_.statistic.area.y0 = (*area).y; ++ params_.statistic.area.xSize = (*area).width; ++ params_.statistic.area.ySize = (*area).height; ++ config_.statistic.area = params_.statistic.area; ++ config_.pending = true; ++ LOG(DcmippStatistic, Debug) << "Updating static area to " << *area; ++ } ++} ++ ++void Statistic::prepare(IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ [[maybe_unused]] stm32_dcmipp_params_cfg *params, ++ [[maybe_unused]] ControlList &sensorControls, ++ ControlList &ispControls) ++{ ++ /* Copy the pending config to the isp controls (not isp_params). Update the IPAContext */ ++ if (config_.pending) { ++ /* Configure Statistic area */ ++ Rectangle area(config_.statistic.area.x0, config_.statistic.area.y0, ++ config_.statistic.area.xSize, config_.statistic.area.ySize); ++ ispControls.set(controls::draft::StatisticArea, area); ++ ispControls.set(controls::draft::StatisticProfile, config_.statistic.profile); ++ ++ /* Update context */ ++ context.isp.statistic = config_.statistic; ++ ++ /* Stat Latency = stat gathering cycle length */ ++ context.info.ispStatLatency = getCycleDuration(config_.statistic.profile); ++ ++ /* Clear the pending request */ ++ config_.pending = false; ++ } ++} ++ ++void Statistic::process([[maybe_unused]] IPAContext &context, ++ [[maybe_unused]] const uint32_t frame, ++ [[maybe_unused]] IPAFrameContext &frameContext, ++ const stm32_dcmipp_stat_buf *stats, ++ ControlList &metadata) ++{ ++ /* Set statistic metadata */ ++ Rectangle area(config_.statistic.area.x0, config_.statistic.area.y0, ++ config_.statistic.area.xSize, config_.statistic.area.ySize); ++ metadata.set(controls::draft::StatisticArea, area); ++ metadata.set(controls::draft::StatisticProfile, config_.statistic.profile); ++ ++ if ((config_.statistic.profile == ProfileAvgUp) || ++ (config_.statistic.profile == ProfileFull)) { ++ metadata.set(controls::draft::StatisticAverageUp, ++ { static_cast(stats->pre.average_RGB[0]), ++ static_cast(stats->pre.average_RGB[1]), ++ static_cast(stats->pre.average_RGB[2]), ++ luminanceFromRgb(stats->pre.average_RGB) }); ++ } ++ if ((config_.statistic.profile == ProfileAvgDown) || ++ (config_.statistic.profile == ProfileFull)) { ++ metadata.set(controls::draft::StatisticAverageDown, ++ { static_cast(stats->post.average_RGB[0]), ++ static_cast(stats->post.average_RGB[1]), ++ static_cast(stats->post.average_RGB[2]), ++ luminanceFromRgb(stats->post.average_RGB) }); ++ } ++ if (config_.statistic.profile == ProfileFull) { ++ std::array bins; ++ ++ std::copy(stats->pre.bins, stats->pre.bins + kBinsSize, bins.begin()); ++ metadata.set(controls::draft::StatisticBinsUp, bins); ++ ++ std::copy(stats->post.bins, stats->post.bins + kBinsSize, bins.begin()); ++ metadata.set(controls::draft::StatisticBinsDown, bins); ++ } ++} ++ ++REGISTER_IPA_ALGORITHM(Statistic, "Statistic") ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/algorithms/statistic.h b/src/ipa/dcmipp/algorithms/statistic.h +new file mode 100644 +index 00000000..1185b1a6 +--- /dev/null ++++ b/src/ipa/dcmipp/algorithms/statistic.h +@@ -0,0 +1,44 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * statistic.h - STM32 DCMIPP Statistic configuration ++ */ ++ ++#pragma once ++ ++#include "algorithm.h" ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp::algorithms { ++ ++class Statistic : public Algorithm ++{ ++public: ++ Statistic() = default; ++ ++ int init(IPAContext &context, const YamlObject &tuningData) override; ++ int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; ++ void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const ControlList &controls) override; ++ void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ stm32_dcmipp_params_cfg *params, ControlList &sensorControls, ++ ControlList &ispControls) override; ++ void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, ++ const stm32_dcmipp_stat_buf *stats, ControlList &metadata) override; ++ ++private: ++ struct algoParams { ++ struct IPAIspStatistic statistic; ++ } params_; ++ ++ struct algoConfig { ++ bool pending; ++ struct IPAIspStatistic statistic; ++ } config_; ++}; ++ ++} /* namespace ipa::dcmipp::algorithms */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/data/imx335.yaml b/src/ipa/dcmipp/data/imx335.yaml +new file mode 100644 +index 00000000..3de5a11b +--- /dev/null ++++ b/src/ipa/dcmipp/data/imx335.yaml +@@ -0,0 +1,57 @@ ++# Copyright (c) 2024 STMicroelectronics. ++# All rights reserved. ++# ++# This software is licensed under SLA0044 terms that can be found here: ++# https://www.st.com/resource/en/license_agreement/SLA0044.txt ++# ++# imx335 configuration file for the STM32 dcmipp IPA. ++# ++# THIS FILE WAS GENERATED BY THE STM32 ISP IQTune ON 2024-10-09 12:13:15 ++# ++ ++%YAML 1.1 ++--- ++version: 1 ++algorithms: ++ - Aec: ++ AnalogueGain_dB: 0.0 ++ ExposureTime: 6318 ++ AeEnable: true ++ AntiFlickerFreq: 0 ++ ExposureValue: 0.0 ++ SensorDelay: 3 ++ - Awb: ++ FixedGainEnable: true ++ FixedGainR: 220000000 ++ FixedGainG: 100000000 ++ FixedGainB: 180000000 ++ FixedCconvEnable: true ++ FixedCconv: [180080000, -64840000, -15230000, -35550000, 169920000, -34380000, 9770000, -95700000, 185940000] ++ AwbEnable: true ++ ProfileName: ["IMX335-A", "IMX335-TL84", "IMX335-D50", "IMX335-D65", "Free Slot"] ++ RefColorTemp: [2856, 4000, 5000, 6500, 0] ++ GainR: [140000000, 177000000, 220000000, 245000000, 0] ++ GainG: [100000000, 100000000, 100000000, 100000000, 0] ++ GainB: [275000000, 235000000, 180000000, 155000000, 0] ++ Cconv: [151460000, -102340000, 50892000, -85991000, 210980000, -24984000, 25000000, -261000000, 341000000, 155134500, -69370000, 13106000, -38671000, 167689800, -33936000, 5546200, -66769999, 159944200, 180080000, -64840000, -15230000, -35550000, 169920000, -34380000, 9770000, -95700000, 185940000, 180080000, -64840000, -15230000, -35550000, 169920000, -34380000, 9770000, -95700000, 185940000, 0, 0, 0, 0, 0, 0, 0, 0, 0] ++ - BadPixel: ++ Enable: false ++ Strength: 0 ++ Threhsold: 0 ++ - BlackLevelCorrection: ++ Enable: true ++ Level: [12, 12, 12] ++ - Contrast: ++ Enable: false ++ LuminanceFactor: [100, 100, 100, 100, 100, 100, 100, 100, 100] ++ - Demosaicing: ++ Enable: true ++ Filter: [2, 4, 4, 6] ++ - Statistic: ++ Area: [648, 486, 1296, 972] ++ Profile: 2 ++ - GammaCorrection: ++ Enable: true ++# NOTE : parameters of 'statRemoval' are not handled in libcamera ++# NOTE : parameters of 'decimation' are not handled in libcamera ++# NOTE : parameter 'demosaicing.type' is not handled in libcamera +diff --git a/src/ipa/dcmipp/data/imx335_judge2_light_box.yaml b/src/ipa/dcmipp/data/imx335_judge2_light_box.yaml +new file mode 100644 +index 00000000..6e72e38c +--- /dev/null ++++ b/src/ipa/dcmipp/data/imx335_judge2_light_box.yaml +@@ -0,0 +1,54 @@ ++# Copyright (c) 2025 STMicroelectronics. ++# All rights reserved. ++# ++# This software is licensed under SLA0044 terms that can be found here: ++# https://www.st.com/resource/en/license_agreement/SLA0044.txt ++# ++# imx335 configuration file for the STM32 dcmipp IPA. ++# Tuned with the Mini Light Box ++# ++ ++%YAML 1.1 ++--- ++version: 1 ++algorithms: ++ - Aec: ++ AeEnable: true ++ AntiFlickerFreq: 0 ++ ExposureValue: 0.0 ++ SensorDelay: 3 ++ - Awb: ++ FixedGainEnable: true ++ FixedGainR: 1.77 ++ FixedGainG: 1.0 ++ FixedGainB: 2.35 ++ FixedCconvEnable: true ++ FixedCconv: [155134500, -69370000, 13106000, -38671000, 167689800, -33936000, 5546200, -66769999, 159944200] ++ AwbEnable: true ++ ProfileName: ["JudegeII-A", "JudegeII-TL84", "JudgeII-D65", "Free slot", "Free slot"] ++ RefColorTemp: [2810, 4015, 6650, 0, 0] ++ GainR: [137000000, 182000000, 244000000, 0, 0] ++ GainG: [100000000, 100000000, 100000000, 0, 0] ++ GainB: [287000000, 212000000, 143000000, 0, 0] ++ Cconv: [159760000, -9780000, -49990000, -45530000, 171540000, -26000000, -3300000, -110120000, 213430000, 164670000, -20970000, -43700000, -51330000, 178670000, -27339999, -12490000, -48170000, 160670000, 150570000, 2440000, -53010000, -37350000, 193760000, -56420000, -11100000, -35490000, 146590000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ++ - BadPixel: ++ Enable: false ++ Strength: 0 ++ Threhsold: 0 ++ - BlackLevelCorrection: ++ Enable: true ++ Level: [12, 12, 12] ++ - Contrast: ++ Enable: false ++ LuminanceFactor: [100, 100, 100, 100, 100, 100, 100, 100, 100] ++ - Demosaicing: ++ Enable: true ++ Filter: [2, 4, 4, 6] ++ - Statistic: ++ Area: [648, 486, 1296, 972] ++ Profile: 2 ++ - GammaCorrection: ++ Enable: true ++# NOTE : parameters of 'statRemoval' are not handled in libcamera ++# NOTE : parameters of 'decimation' are not handled in libcamera ++# NOTE : parameter 'demosaicing.type' is not handled in libcamera +diff --git a/src/ipa/dcmipp/data/imx335_mini_light_box.yaml b/src/ipa/dcmipp/data/imx335_mini_light_box.yaml +new file mode 100644 +index 00000000..11f1949d +--- /dev/null ++++ b/src/ipa/dcmipp/data/imx335_mini_light_box.yaml +@@ -0,0 +1,54 @@ ++# Copyright (c) 2025 STMicroelectronics. ++# All rights reserved. ++# ++# This software is licensed under SLA0044 terms that can be found here: ++# https://www.st.com/resource/en/license_agreement/SLA0044.txt ++# ++# imx335 configuration file for the STM32 dcmipp IPA. ++# Tuned with the Mini Light Box ++# ++ ++%YAML 1.1 ++--- ++version: 1 ++algorithms: ++ - Aec: ++ AeEnable: true ++ AntiFlickerFreq: 0 ++ ExposureValue: 0.0 ++ SensorDelay: 3 ++ - Awb: ++ FixedGainEnable: true ++ FixedGainR: 1.77 ++ FixedGainG: 1.0 ++ FixedGainB: 2.35 ++ FixedCconvEnable: true ++ FixedCconv: [155134500, -69370000, 13106000, -38671000, 167689800, -33936000, 5546200, -66769999, 159944200] ++ AwbEnable: true ++ ProfileName: ["MiniLBox A", "MiniLBox TL84", "MiniLBox D65", "Free slot", "Free slot"] ++ RefColorTemp: [2665, 3750, 6140, 0, 0] ++ GainR: [126000000, 157000000, 210000000, 0, 0] ++ GainG: [100000000, 100000000, 100000000, 0, 0] ++ GainB: [279000000, 199000000, 155000000, 0, 0] ++ Cconv: [277500000, -75420000, -46310000, -91640000, 272360000, -29450000, -1060000, -126200000, 334070000, 178510000, -54460000, -19030000, -46390000, 162010000, -21660000, 1520000, -56399999, 168130000, 176680000, -60550000, -15590000, -33130000, 138950000, -21970000, -1080000, -40140000, 135020000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ++ - BadPixel: ++ Enable: false ++ Strength: 0 ++ Threhsold: 0 ++ - BlackLevelCorrection: ++ Enable: true ++ Level: [12, 12, 12] ++ - Contrast: ++ Enable: false ++ LuminanceFactor: [100, 100, 100, 100, 100, 100, 100, 100, 100] ++ - Demosaicing: ++ Enable: true ++ Filter: [2, 4, 4, 6] ++ - Statistic: ++ Area: [648, 486, 1296, 972] ++ Profile: 2 ++ - GammaCorrection: ++ Enable: true ++# NOTE : parameters of 'statRemoval' are not handled in libcamera ++# NOTE : parameters of 'decimation' are not handled in libcamera ++# NOTE : parameter 'demosaicing.type' is not handled in libcamera +diff --git a/src/ipa/dcmipp/data/meson.build b/src/ipa/dcmipp/data/meson.build +new file mode 100644 +index 00000000..2a4f455c +--- /dev/null ++++ b/src/ipa/dcmipp/data/meson.build +@@ -0,0 +1,9 @@ ++# SPDX-License-Identifier: CC0-1.0 ++ ++conf_files = files([ ++ 'imx335.yaml', ++]) ++ ++install_data(conf_files, ++ install_dir : ipa_data_dir / 'dcmipp', ++ install_tag : 'runtime') +diff --git a/src/ipa/dcmipp/dcmipp.cpp b/src/ipa/dcmipp/dcmipp.cpp +new file mode 100644 +index 00000000..9aa30cd1 +--- /dev/null ++++ b/src/ipa/dcmipp/dcmipp.cpp +@@ -0,0 +1,567 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * dcmipp.cpp - STM32 DCMIPP Image Processing Algorithm module ++ */ ++#include ++ ++#include ++ ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++ ++#include "libcamera/internal/mapped_framebuffer.h" ++#include "libcamera/internal/yaml_parser.h" ++ ++#include "algorithms/algorithm.h" ++#include "libipa/camera_sensor_helper.h" ++#include "linux/stm32-dcmipp-config.h" ++ ++#include "ipa_context.h" ++#include "module.h" ++ ++/* ++* DCMIPP driver doesn't export (yet) the postproc Gamma control CID macro ++* hence define it here instead ++* TODO: to be removed once the V4L2_CID is exported by the kernel ++*/ ++#ifndef V4L2_CID_PIXELPROC_GAMMA_CORRECTION ++#define V4L2_CID_PIXELPROC_GAMMA_CORRECTION (V4L2_CID_USER_BASE | 0x1001) ++#endif ++ ++namespace libcamera { ++ ++LOG_DEFINE_CATEGORY(IPADcmipp) ++ ++namespace ipa::dcmipp { ++ ++class IPADcmipp : public IPADcmippInterface, public Module ++{ ++public: ++ IPADcmipp(); ++ ++ int init(const IPASettings &settings, unsigned int hwRevision, ++ const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ++ ControlInfoMap *ipaControls) override; ++ ++ int start() override; ++ void stop() override; ++ ++ int configure(const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ++ uint32_t decimation, ControlInfoMap *ipaControls) override; ++ ++ void mapBuffers(const std::vector &buffers) override; ++ void unmapBuffers(const std::vector &ids) override; ++ ++ void processStatsBuffer(uint32_t frame, uint32_t bufferId) override; ++ void fillParamsBuffer(uint32_t frame, uint32_t bufferId) override; ++ void queueRequest(uint32_t frame, const ControlList &controls) override; ++ ++protected: ++ std::string logPrefix() const override; ++ ++private: ++ float linearTodB(float gain); ++ float dBToLinear(float gain); ++ void convertControls(ControlList &controls, ControlList &v4l2Controls); ++ void setSensorProperties(const IPACameraSensorInfo &sensorInfo, ++ const ControlInfoMap &sensorControls); ++ void updateControls(ControlInfoMap *ipaControls); ++ void logConfig(struct stm32_dcmipp_params_cfg *isp_params, ControlList &sensorControls, ++ ControlList &ispControls); ++ float exposureFactor(struct stm32_dcmipp_isp_ex_cfg *exp, int comp); ++ float cconvCoeff(__u16 reg); ++ ++ std::map buffers_; ++ std::map mappedBuffers_; ++ ++ ControlInfoMap sensorControls_; ++ ++ std::unique_ptr camHelper_; ++ ++ struct IPAContext context_; ++ struct IPAFrameContext frameContext_; /* not used, kept for compatibility with module */ ++}; ++ ++IPADcmipp::IPADcmipp() ++ : context_({}) ++{ ++} ++ ++std::string IPADcmipp::logPrefix() const ++{ ++ return "DCMIPP"; ++} ++ ++int IPADcmipp::init(const IPASettings &settings, unsigned int hwRevision, ++ const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ++ ControlInfoMap *ipaControls) ++{ ++ LOG(IPADcmipp, Debug) << __func__; ++ ++ context_ = {}; ++ ++ context_.info.hwRevision = hwRevision; ++ sensorControls_ = sensorControls; ++ camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); ++ if (!camHelper_) ++ LOG(IPADcmipp, Warning) << "No camera sensor helper for " << settings.sensorModel; ++ ++ /* Get sensor exposure/time properties */ ++ setSensorProperties(sensorInfo, sensorControls); ++ ++ /* Load and parse the tuning data file */ ++ if (!settings.configurationFile.empty()) { ++ LOG(IPADcmipp, Debug) << "Parsing IPA settings from " << settings.configurationFile; ++ File file(settings.configurationFile); ++ if (!file.open(File::OpenModeFlag::ReadOnly)) { ++ int ret = file.error(); ++ LOG(IPADcmipp, Error) << "Failed to open " << settings.configurationFile << ": " ++ << strerror(-ret); ++ return ret; ++ } ++ ++ std::unique_ptr data = YamlParser::parse(file); ++ if (!data) ++ return -EINVAL; ++ int ret = createAlgorithms(context_, (*data)["algorithms"]); ++ if (ret) ++ return ret; ++ } ++ ++ /* Set controls supported by the algorithms */ ++ updateControls(ipaControls); ++ ++ return 0; ++} ++ ++int IPADcmipp::start() ++{ ++ LOG(IPADcmipp, Debug) << __func__; ++ ++ /* Apply the sensors settings before capturing the first frame */ ++ ControlList v4l2SensorControls(sensorControls_), sensorControls; ++ sensorControls.set(controls::draft::AnalogueGain_dB, context_.sensor.gain); ++ sensorControls.set(controls::ExposureTime, context_.sensor.exposure); ++ convertControls(sensorControls, v4l2SensorControls); ++ setSensorControls.emit(0, v4l2SensorControls); ++ ++ return 0; ++} ++ ++void IPADcmipp::stop() ++{ ++ LOG(IPADcmipp, Debug) << __func__; ++} ++ ++int IPADcmipp::configure(const IPACameraSensorInfo &sensorInfo, ++ const ControlInfoMap &sensorControls, ++ uint32_t decimation, ++ ControlInfoMap *ipaControls) ++{ ++ LOG(IPADcmipp, Debug) << __func__; ++ ++ /* Get sensor exposure/time properties */ ++ setSensorProperties(sensorInfo, sensorControls); ++ ++ /* Configure algorithms */ ++ for (auto const &algo : algorithms()) { ++ int ret = algo->configure(context_, sensorInfo); ++ if (ret) { ++ LOG(IPADcmipp, Error) << "Failed to configure algorithm: " << strerror(ret); ++ return ret; ++ } ++ } ++ ++ context_.isp.decimationRatio = decimation; ++ context_.info.sensorBitsPerPixel = sensorInfo.bitsPerPixel; ++ ++ /* Update the camera controls using the new sensor settings. */ ++ updateControls(ipaControls); ++ ++ return 0; ++} ++ ++void IPADcmipp::mapBuffers(const std::vector &buffers) ++{ ++ LOG(IPADcmipp, Debug) << __func__; ++ ++ for (const IPABuffer &buffer : buffers) { ++ auto elem = buffers_.emplace(std::piecewise_construct, ++ std::forward_as_tuple(buffer.id), ++ std::forward_as_tuple(buffer.planes)); ++ const FrameBuffer &fb = elem.first->second; ++ ++ MappedFrameBuffer mappedBuffer(&fb, MappedFrameBuffer::MapFlag::ReadWrite); ++ if (!mappedBuffer.isValid()) ++ LOG(IPADcmipp, Error) << "Failed to mmap buffer: " << strerror(mappedBuffer.error()); ++ ++ mappedBuffers_.emplace(buffer.id, std::move(mappedBuffer)); ++ } ++} ++ ++void IPADcmipp::unmapBuffers(const std::vector &ids) ++{ ++ LOG(IPADcmipp, Debug) << __func__; ++ ++ for (unsigned int id : ids) { ++ const auto fb = buffers_.find(id); ++ if (fb == buffers_.end()) ++ continue; ++ ++ mappedBuffers_.erase(id); ++ buffers_.erase(id); ++ } ++} ++ ++void IPADcmipp::queueRequest(uint32_t frame, const ControlList &controls) ++{ ++ LOG(IPADcmipp, Debug) << __func__ << " frame: " << frame; ++ ++ /* Send request to algorithms */ ++ for (auto const &algo : algorithms()) ++ algo->queueRequest(context_, frame, frameContext_, controls); ++} ++ ++void IPADcmipp::processStatsBuffer(uint32_t frame, uint32_t bufferId) ++{ ++ LOG(IPADcmipp, Debug) << __func__ << " frame: " << frame << " bufferId: " << bufferId; ++ ++ /* Check bufferId validity */ ++ auto it = buffers_.find(bufferId); ++ if (it == buffers_.end()) { ++ LOG(IPADcmipp, Error) << "Could not find stats buffer"; ++ return; ++ } ++ ++ /* Get buffer */ ++ struct stm32_dcmipp_stat_buf *stats = reinterpret_cast( ++ mappedBuffers_.at(bufferId).planes()[0].data()); ++ ++ /* Ask algo to process stats */ ++ LOG(IPADcmipp, Debug) << "Calling algo process"; ++ ControlList metadata; ++ for (auto const &algo : algorithms()) ++ algo->process(context_, frame, frameContext_, stats, metadata); ++ ++ /* Add additional information */ ++ metadata.set(controls::draft::IspDecimationRatio, context_.isp.decimationRatio); ++ metadata.set(controls::draft::SensorBitsPerPixel, context_.info.sensorBitsPerPixel); ++ metadata.set(controls::draft::PipelineHwRevision, context_.info.hwRevision); ++ ++ /* Send metadata (controls) */ ++ metadataReady.emit(frame, metadata); ++ ++ /* Inform that the buffer can be freed now */ ++ statsBufferProcessed.emit(bufferId); ++} ++ ++void IPADcmipp::fillParamsBuffer(uint32_t frame, uint32_t bufferId) ++{ ++ LOG(IPADcmipp, Debug) << __func__ << " frame: " << frame << " bufferId: " << bufferId; ++ ++ /* Check bufferId validity */ ++ auto it = buffers_.find(bufferId); ++ if (it == buffers_.end()) { ++ LOG(IPADcmipp, Error) << "Could not find parameter buffer"; ++ return; ++ } ++ ++ /* Get buffer */ ++ struct stm32_dcmipp_params_cfg *isp_params = reinterpret_cast( ++ mappedBuffers_.at(bufferId).planes()[0].data()); ++ ++ /* Ask algo to provide new configuration */ ++ ControlList ispControls, sensorControls; ++ memset(isp_params, 0, sizeof(struct stm32_dcmipp_params_cfg)); ++ ++ LOG(IPADcmipp, Debug) << "Calling algo prepare"; ++ for (auto const &a : algorithms()) { ++ Algorithm *algo = static_cast(a.get()); ++ algo->prepare(context_, frame, frameContext_, isp_params, sensorControls, ispControls); ++ } ++ ++ /* Log new isp and sensor config */ ++ logConfig(isp_params, sensorControls, ispControls); ++ ++ /* Inform that the sensor config can be applied */ ++ ControlList v4l2SensorControls(sensorControls_); ++ convertControls(sensorControls, v4l2SensorControls); ++ setSensorControls.emit(0, v4l2SensorControls); ++ ++ /* Inform that the buffer can be applied for ISP HW update (optional v4l2 ctrl) */ ++ if (!ispControls.empty()) { ++ /* Convert and split into two lists: an ISP one and a Postproc (Gamma) one */ ++ ControlList v4l2Controls, v4l2IspControls, v4l2PostprocControls; ++ convertControls(ispControls, v4l2Controls); ++ for (const auto &control : v4l2Controls) { ++ if (control.first != V4L2_CID_PIXELPROC_GAMMA_CORRECTION) ++ v4l2IspControls.set(control.first, control.second); ++ else ++ v4l2PostprocControls.set(control.first, control.second); ++ } ++ if (!v4l2IspControls.empty()) ++ setIspControls.emit(0, v4l2IspControls); ++ if (!v4l2PostprocControls.empty()) ++ setPostprocControls.emit(0, v4l2PostprocControls); ++ } ++ ++ /* Inform that the buffer can be applied for ISP HW update (buff params) */ ++ paramsBufferReady.emit(bufferId); ++} ++ ++float IPADcmipp::linearTodB(float gain) ++{ ++ return 20 * std::log10(gain); ++} ++ ++float IPADcmipp::dBToLinear(float gain) ++{ ++ return std::pow(10, gain / 20); ++} ++ ++void IPADcmipp::convertControls(ControlList &controls, ControlList &v4l2Controls) ++{ ++ /* Converts exposure to V4L2 control unit (number of lines) */ ++ const auto &exposure = controls.get(controls::ExposureTime); ++ if (exposure) ++ v4l2Controls.set(V4L2_CID_EXPOSURE, (int32_t)(*exposure / context_.info.sensorLineDuration_us)); ++ ++ /* Converts gain to V4L2 control unit (eg 0.3 dB step for IMX335 sensor) */ ++ const auto &gain = controls.get(controls::draft::AnalogueGain_dB); ++ if (gain && camHelper_) { ++ float gainLinear = dBToLinear(*gain); ++ v4l2Controls.set(V4L2_CID_ANALOGUE_GAIN, (int32_t)(camHelper_->gainCode(gainLinear))); ++ } ++ ++ /* Converts statistic area */ ++ const auto &area = controls.get(controls::draft::StatisticArea); ++ if (area) { ++ struct v4l2_ctrl_isp_stat_region region; ++ ++ region.nb_regions = 1; ++ region.top[0] = area->y / context_.isp.decimationRatio; ++ region.left[0] = area->x / context_.isp.decimationRatio; ++ region.width[0] = area->width / context_.isp.decimationRatio; ++ region.height[0] = area->height / context_.isp.decimationRatio; ++ ++ ControlValue c(Span{ reinterpret_cast(®ion), sizeof(region) }); ++ v4l2Controls.set(V4L2_CID_ISP_STAT_REGION, c); ++ } ++ ++ /* Converts statistic profile */ ++ const auto &profile = controls.get(controls::draft::StatisticProfile); ++ if (profile) ++ v4l2Controls.set(V4L2_CID_ISP_STAT_PROFILE, *profile); ++ ++ /* Converts gamma correction */ ++ const auto &gamma = controls.get(controls::draft::GammaCorrectionEnable); ++ if (gamma) ++ v4l2Controls.set(V4L2_CID_PIXELPROC_GAMMA_CORRECTION, (int32_t)*gamma); ++} ++ ++void IPADcmipp::setSensorProperties(const IPACameraSensorInfo &sensorInfo, ++ const ControlInfoMap &sensorControls) ++{ ++ /* Compute sensor exposure time properties */ ++ const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; ++ double lineDuration_us = sensorInfo.minLineLength / (sensorInfo.pixelRate / 1000000.0f); ++ context_.info.sensorLineDuration_us = lineDuration_us; ++ context_.info.sensorExposureMin = v4l2Exposure.min().get() * lineDuration_us; ++ context_.info.sensorExposureMax = v4l2Exposure.max().get() * lineDuration_us; ++ context_.info.sensorExposureDef = v4l2Exposure.def().get() * lineDuration_us; ++ ++ /* Compute sensor analogue gain properties */ ++ if (camHelper_) { ++ const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; ++ context_.info.sensorGainMin = linearTodB(camHelper_->gain(v4l2Gain.min().get())); ++ context_.info.sensorGainMax = linearTodB(camHelper_->gain(v4l2Gain.max().get())); ++ context_.info.sensorGainDef = linearTodB(camHelper_->gain(v4l2Gain.def().get())); ++ } ++ ++ /* Compute frame duration properties */ ++ const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second; ++ const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second; ++ ++ uint32_t hblank = v4l2HBlank.def().get(); ++ uint32_t lineLength = sensorInfo.outputSize.width + hblank; ++ uint32_t sizeHeight = sensorInfo.outputSize.height; ++ ++ uint64_t minFrameSize = (uint64_t)(v4l2VBlank.min().get() + sizeHeight) * lineLength; ++ uint64_t maxFrameSize = (uint64_t)(v4l2VBlank.max().get() + sizeHeight) * lineLength; ++ uint64_t defFrameSize = (uint64_t)(v4l2VBlank.def().get() + sizeHeight) * lineLength; ++ ++ context_.info.frameDurationsMin = minFrameSize / (sensorInfo.pixelRate / 1000000U); ++ context_.info.frameDurationsMax = maxFrameSize / (sensorInfo.pixelRate / 1000000U); ++ context_.info.frameDurationsDef = defFrameSize / (sensorInfo.pixelRate / 1000000U); ++} ++ ++void IPADcmipp::updateControls(ControlInfoMap *ipaControls) ++{ ++ ControlInfoMap::Map ctrlMap = context_.dcmippControls; ++ ++ /* Add the frame duration limits */ ++ ctrlMap.emplace(std::piecewise_construct, ++ std::forward_as_tuple(&controls::FrameDurationLimits), ++ std::forward_as_tuple(context_.info.frameDurationsMin, ++ context_.info.frameDurationsMax, ++ context_.info.frameDurationsDef)); ++ ++ *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); ++} ++ ++void IPADcmipp::logConfig(struct stm32_dcmipp_params_cfg *p, ControlList &sensorControls, ++ ControlList &ispControls) ++{ ++ if (p->module_cfg_update & STM32_DCMIPP_ISP_BPR) ++ LOG(IPADcmipp, Debug) << "ISP update - BAD PIXEL: " ++ << (p->ctrls.bpr_cfg.en ? "enabled" : "disabled") << " (" ++ << p->ctrls.bpr_cfg.strength << ")"; ++ ++ if (p->module_cfg_update & STM32_DCMIPP_ISP_BLC) ++ LOG(IPADcmipp, Debug) << "ISP update - BLACK LEVEL: " ++ << (p->ctrls.blc_cfg.en ? "enabled" : "disabled") << " (" ++ << (int)p->ctrls.blc_cfg.blc_r << " / " ++ << (int)p->ctrls.blc_cfg.blc_g << " / " ++ << (int)p->ctrls.blc_cfg.blc_b << ")"; ++ ++ if (p->module_cfg_update & STM32_DCMIPP_ISP_EX) ++ LOG(IPADcmipp, Debug) << "ISP update - EXPOSURE: " ++ << (p->ctrls.ex_cfg.en ? "enabled" : "disabled") << " (" ++ << "R x" << exposureFactor(&p->ctrls.ex_cfg, 0) << " /" ++ << "G x" << exposureFactor(&p->ctrls.ex_cfg, 1) << " /" ++ << "B x" << exposureFactor(&p->ctrls.ex_cfg, 2) << ")"; ++ ++ if (p->module_cfg_update & STM32_DCMIPP_ISP_CC) ++ LOG(IPADcmipp, Debug) << "ISP update - COLOR CONVERSION: " ++ << (p->ctrls.cc_cfg.en ? "enabled" : "disabled") << " (" ++ << " (" << cconvCoeff(p->ctrls.cc_cfg.rr) ++ << " , " << cconvCoeff(p->ctrls.cc_cfg.rg) ++ << " , " << cconvCoeff(p->ctrls.cc_cfg.rb) ++ << ") , (" << cconvCoeff(p->ctrls.cc_cfg.gr) ++ << " , " << cconvCoeff(p->ctrls.cc_cfg.gg) ++ << " , " << cconvCoeff(p->ctrls.cc_cfg.gb) ++ << ") , (" << cconvCoeff(p->ctrls.cc_cfg.br) ++ << " , " << cconvCoeff(p->ctrls.cc_cfg.bg) ++ << " , " << cconvCoeff(p->ctrls.cc_cfg.bb) << ")"; ++ ++ if (p->module_cfg_update & STM32_DCMIPP_ISP_DM) ++ LOG(IPADcmipp, Debug) << "ISP update - DEMOSAICING: " ++ << (p->ctrls.dm_cfg.en ? "enabled" : "disabled") << " (" ++ << (int)p->ctrls.dm_cfg.edge << " / " ++ << (int)p->ctrls.dm_cfg.lineh << " / " ++ << (int)p->ctrls.dm_cfg.linev << " / " ++ << (int)p->ctrls.dm_cfg.peak; ++ ++ if (p->module_cfg_update & STM32_DCMIPP_ISP_CE) ++ LOG(IPADcmipp, Debug) << "ISP update - CONTRAST: " ++ << (p->ctrls.ce_cfg.en ? "enabled" : "disabled") << " (" ++ << (float)p->ctrls.ce_cfg.lum[0] / 16 << ", " ++ << (float)p->ctrls.ce_cfg.lum[1] / 16 << ", " ++ << (float)p->ctrls.ce_cfg.lum[2] / 16 << ", " ++ << (float)p->ctrls.ce_cfg.lum[3] / 16 << ", " ++ << (float)p->ctrls.ce_cfg.lum[4] / 16 << ", " ++ << (float)p->ctrls.ce_cfg.lum[5] / 16 << ", " ++ << (float)p->ctrls.ce_cfg.lum[6] / 16 << ", " ++ << (float)p->ctrls.ce_cfg.lum[7] / 16 << ", " ++ << (float)p->ctrls.ce_cfg.lum[8] / 16 << ")"; ++ ++ /* ISP control updates */ ++ const auto &area = ispControls.get(controls::draft::StatisticArea); ++ if (area) ++ LOG(IPADcmipp, Debug) << "ISP update - STAT AREA: (" ++ << area->width << " x " ++ << area->height << ") @ (" ++ << area->x << ", " ++ << area->y << ")"; ++ ++ const auto &profile = ispControls.get(controls::draft::StatisticProfile); ++ if (profile) ++ LOG(IPADcmipp, Debug) << "ISP update - STAT PROFILE: " << *profile; ++ ++ const auto &gamma = ispControls.get(controls::draft::GammaCorrectionEnable); ++ if (gamma) ++ LOG(IPADcmipp, Debug) << "ISP update - GAMMA: " ++ << (*gamma ? "enabled" : "disabled"); ++ ++ /* Sensor Updates (exposure and gain) */ ++ const auto &exposure = sensorControls.get(controls::ExposureTime); ++ if (exposure) ++ LOG(IPADcmipp, Debug) << "SENSOR update - EXPOSURE: " << *exposure << " us"; ++ ++ const auto &gain = sensorControls.get(controls::draft::AnalogueGain_dB); ++ if (gain) ++ LOG(IPADcmipp, Debug) << "SENSOR update - GAIN: " << *gain << " dB"; ++} ++ ++float IPADcmipp::exposureFactor(struct stm32_dcmipp_isp_ex_cfg *exp, int comp) ++{ ++ /* Convert Shift + Multiplier to Factor */ ++ uint32_t shift, mult; ++ ++ switch (comp) { ++ default: ++ case 0: ++ shift = exp->shift_r; ++ mult = exp->mult_r; ++ break; ++ case 1: ++ shift = exp->shift_g; ++ mult = exp->mult_g; ++ break; ++ case 2: ++ shift = exp->shift_b; ++ mult = exp->mult_b; ++ break; ++ } ++ float factor = 1 << shift; ++ factor = (factor * mult) / 128; ++ return factor; ++} ++ ++float IPADcmipp::cconvCoeff(__u16 reg) ++{ ++ /* Convert from register format to float format */ ++ float coeff; ++ ++ if (reg & 0x400) { ++ int32_t val = reg; ++ val = (val - 1) ^ 0x7FF; ++ coeff = -(float)val / 256; ++ } else { ++ coeff = (float)reg / 256; ++ } ++ ++ return coeff; ++} ++ ++} /* namespace ipa::dcmipp */ ++ ++/* ++ * External IPA module interface ++ */ ++ ++extern "C" { ++const struct IPAModuleInfo ipaModuleInfo = { ++ IPA_MODULE_API_VERSION, ++ 0, ++ "dcmipp", ++ "dcmipp", ++}; ++ ++IPAInterface *ipaCreate() ++{ ++ LOG(IPADcmipp, Debug) << __func__; ++ ++ return new ipa::dcmipp::IPADcmipp(); ++} ++} ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/ipa_context.h b/src/ipa/dcmipp/ipa_context.h +new file mode 100644 +index 00000000..3a826f1c +--- /dev/null ++++ b/src/ipa/dcmipp/ipa_context.h +@@ -0,0 +1,132 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * ipa_context.h - STM32 DCMIPP IPA Context ++ */ ++ ++#pragma once ++ ++#include ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp { ++ ++static constexpr unsigned int kNbContrastFactor = 9; ++ ++/* Sensor config */ ++struct IPASensor { ++ double gain; /* Analogue gain in dB */ ++ int32_t exposure; /* Exposure time in microseconds */ ++}; ++ ++/* ISP Statistic */ ++struct IPAIspStatistic { ++ struct { /* Coordinates */ ++ uint32_t x0; ++ uint32_t y0; ++ uint32_t xSize; ++ uint32_t ySize; ++ } area; ++ uint32_t profile; /* Reported Stats (0:Full 1:AvgUp 2:AvgDown) */ ++}; ++ ++/* ISP Bad pixel */ ++struct IPAIspBadpixel { ++ uint8_t enable; /* Enable or disable */ ++ uint8_t strength; /* Strength of the bad pixel removal algorithm */ ++}; ++ ++/* ISP Black Level */ ++struct IPAIspBlackLevel { ++ uint8_t enable; /* Enable or disable */ ++ uint8_t blcR; /* Level offset for the red component */ ++ uint8_t blcG; /* Level offset for the green component */ ++ uint8_t blcB; /* Level offset for the blue component */ ++}; ++ ++/* ISP Demosaicing */ ++struct IPAIspDemosaicing { ++ uint8_t enable; /* Enable or disable */ ++ uint8_t edge; /* Edge detection strength */ ++ uint8_t lineh; /* Horizontal line detection strength */ ++ uint8_t linev; /* Vertical line detection strength */ ++ uint8_t peak; /* Peak detection strength */ ++}; ++ ++/* ISP Contrast */ ++struct IPAIspContrast { ++ uint8_t enable; /* Enable or disable */ ++ uint32_t coeff[kNbContrastFactor]; /* Luminance amplification factor. Unit = 100 for "x1.0" */ ++}; ++ ++/* ISP Gain ('exposure') */ ++struct IPAIspGain { ++ uint8_t enable; /* Enable or disable */ ++ uint32_t gainR; /* Gain of the red component. Unit = 100000000 for "x1.0", 150000000 for "x1.5". Max gain is "x16" */ ++ uint32_t gainG; /* Gain of the green component */ ++ uint32_t gainB; /* Gain of the blue component */ ++}; ++ ++/* ISP Color Conversion */ ++struct IPAIspColorConv { ++ uint8_t enable; /* Enable or disable */ ++ int32_t coeff[3][3]; /* 3x3 RGB to RGB matrix coefficients. Unit = 100000000 for "x1.0", -150000000 for "x-1.5". Range is "x-4.0" to "x4.0" */ ++}; ++ ++/* ISP / PostProc Gamma Correction */ ++struct IPAIspGammaCorrection { ++ uint8_t enable; /* Enable or disable */ ++}; ++ ++/* IPA context */ ++struct IPAContext { ++ /* ISP config */ ++ struct IPAIsp { ++ struct IPAIspStatistic statistic; ++ struct IPAIspBadpixel badpixel; ++ struct IPAIspBlackLevel blackLevel; ++ struct IPAIspDemosaicing demosaicing; ++ struct IPAIspContrast contrast; ++ struct IPAIspGain gain; ++ struct IPAIspColorConv cconv; ++ struct IPAIspGammaCorrection gamma; ++ uint32_t decimationRatio; ++ } isp; ++ ++ /* Sensor config */ ++ struct IPASensor sensor; ++ ++ /* General information */ ++ struct IPAInfo { ++ uint8_t AECEnable; /* AEC algo enable or disable */ ++ uint32_t AECExposureTarget; /* AEC Exposure Target */ ++ uint8_t AWBEnable; /* AWB algo enable or disable */ ++ uint32_t CCT; /* Estimated Correlated Color Temperature */ ++ int32_t ispStatLatency; /* Nb of Vsync to get stats after ISP update */ ++ uint32_t sensorBitsPerPixel; /* Nb of bpp from the sensor */ ++ uint32_t hwRevision; /* DCMIPP HW version */ ++ double sensorGainMin; /* Sensor min analogue gain in dB */ ++ double sensorGainMax; /* Sensor max analogue gain in dB */ ++ double sensorGainDef; /* Sensor default analogue gain in dB */ ++ double sensorLineDuration_us; /* Sensor line duration in microseconds */ ++ int32_t sensorExposureMin; /* Sensor min exposure time in microseconds */ ++ int32_t sensorExposureMax; /* Sensor max exposure time in microseconds */ ++ int32_t sensorExposureDef; /* Sensor default exposure time in microseconds */ ++ int64_t frameDurationsMin; /* Min frame duration in microseconds */ ++ int64_t frameDurationsMax; /* Max frame duration in microseconds */ ++ int64_t frameDurationsDef; /* Default frame duration in microseconds */ ++ } info; ++ ++ /* Supported controls */ ++ ControlInfoMap::Map dcmippControls; ++}; ++ ++/* Frame context, not used for the time being. Declaration required to use module */ ++struct IPAFrameContext { ++}; ++ ++} /* namespace ipa::dcmipp */ ++ ++} /* namespace libcamera */ +diff --git a/src/ipa/dcmipp/meson.build b/src/ipa/dcmipp/meson.build +new file mode 100644 +index 00000000..911372d0 +--- /dev/null ++++ b/src/ipa/dcmipp/meson.build +@@ -0,0 +1,39 @@ ++# SPDX-License-Identifier: CC0-1.0 ++ ++subdir('algorithms') ++subdir('data') ++ ++ipa_name = 'ipa_dcmipp' ++ ++dcmipp_ipa_sources = files([ ++ 'dcmipp.cpp', ++]) ++ ++dcmipp_ipa_sources += dcmipp_ipa_algorithms ++ ++dcmipp_cpp_arguments = [] ++ ++if get_option('evision_algo') ++ dcmipp_cpp_arguments += ['-DEVISION_ALGO_ENABLED'] ++endif ++ ++mod = shared_module(ipa_name, ++ [dcmipp_ipa_sources, libcamera_generated_ipa_headers], ++ name_prefix : '', ++ include_directories : [ipa_includes, libipa_includes], ++ dependencies : libcamera_private, ++ cpp_args : dcmipp_cpp_arguments, ++ link_with : libipa, ++ install : true, ++ install_dir : ipa_install_dir) ++ ++if ipa_sign_module ++ custom_target(ipa_name + '.so.sign', ++ input : mod, ++ output : ipa_name + '.so.sign', ++ command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'], ++ install : false, ++ build_by_default : true) ++endif ++ ++ipa_names += ipa_name +diff --git a/src/ipa/dcmipp/module.h b/src/ipa/dcmipp/module.h +new file mode 100644 +index 00000000..2faecf85 +--- /dev/null ++++ b/src/ipa/dcmipp/module.h +@@ -0,0 +1,27 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024 ST Microelectronics ++ * ++ * module.h - STM32 DCCMIPP IPA Module ++ */ ++ ++#pragma once ++ ++#include ++ ++#include ++ ++#include "linux/stm32-dcmipp-config.h" ++ ++#include "ipa_context.h" ++ ++namespace libcamera { ++ ++namespace ipa::dcmipp { ++ ++using Module = ipa::Module; ++ ++} /* namespace ipa::dcmipp */ ++ ++} /* namespace libcamera*/ +diff --git a/src/ipa/rpi/controller/rpi/alsc.cpp b/src/ipa/rpi/controller/rpi/alsc.cpp +index 961209ea..67029fc3 100644 +--- a/src/ipa/rpi/controller/rpi/alsc.cpp ++++ b/src/ipa/rpi/controller/rpi/alsc.cpp +@@ -496,8 +496,8 @@ void resampleCalTable(const Array2D &calTableIn, + * Precalculate and cache the x sampling locations and phases to save + * recomputing them on every row. + */ +- int *xLo = (int*)malloc(X), *xHi = (int*)malloc(X); +- double *xf = (double*)malloc(X); ++ int xLo[X], xHi[X]; ++ double xf[X]; + double scaleX = cameraMode.sensorWidth / + (cameraMode.width * cameraMode.scaleX); + double xOff = cameraMode.cropX / (double)cameraMode.sensorWidth; +@@ -539,9 +539,6 @@ void resampleCalTable(const Array2D &calTableIn, + *(out++) = above * (1 - yf) + below * yf; + } + } +- free(xf); +- free(xHi); +- free(xLo); + } + + /* Calculate chrominance statistics (R/G and B/G) for each region. */ +diff --git a/src/libcamera/control_ids_draft.yaml b/src/libcamera/control_ids_draft.yaml +index 9bef5bf1..f2993fcd 100644 +--- a/src/libcamera/control_ids_draft.yaml ++++ b/src/libcamera/control_ids_draft.yaml +@@ -227,4 +227,248 @@ controls: + value. All of the custom test patterns will be static (that is the + raw image must not vary from frame to frame). + ++ - AnalogueGain_dB: ++ type: float ++ description: | ++ Analogue gain value applied in the sensor device expressed in decibel. ++ The value of the control specifies the gain multiplier applied to all ++ colour channels. This value cannot be lower than 0.0. ++ ++ Setting this value means that it is now fixed and the AE algorithm may ++ not change it. Setting it back to a negative value returns it to the ++ control of the AE algorithm. ++ ++ - AeExposureTarget: ++ type: int32_t ++ description: | ++ Exposure target for the AE algorithm to use. ++ ++ - AntiFlickerFreq: ++ type: int32_t ++ description: | ++ Anti-flickering frequency for the AE algorithm to use. ++ ++ - ColourGains3Enable: ++ type: bool ++ description: | ++ ColourGains3 feature enable status. ++ ++ - ColourGains3: ++ type: int32_t ++ description: | ++ Gain values for the 3 colour channels, Red, Green and Blue in that ++ order. ++ Unit = 100000000 for "x1.0", 150000000 for "x1.5". ++ size: [3] ++ ++ - ColourCorrectionEnable: ++ type: bool ++ description: | ++ ColourCorrection feature enable status. ++ ++ - ColourCorrection: ++ type: int32_t ++ description: | ++ The 3x3 matrix that converts camera RGB to sRGB within the ++ imaging pipeline. This should describe the matrix that is used ++ after pixels have been white-balanced, but before any gamma ++ transformation. The 3x3 matrix is stored in conventional reading ++ order in an array of 9 values. ++ Unit = 100000000 for "x1.0", 150000000 for "x1.5". ++ size: [3,3] ++ ++ - AwbProfileName: ++ type: string ++ description: | ++ Array of AWB profile name (one entry per profile) used by the AWB ++ algorithm. ++ size: [5] ++ ++ - AwbCurrentProfileName: ++ type: string ++ description: Report the current profile name, for this frame. The ++ AwbCurrentProfileName control can only be returned in metadata. ++ ++ - AwbReferenceColorTemperature: ++ type: int32_t ++ description: | ++ Array of reference color tempeartures (one entry per profile) used by ++ the AWB algorithm. ++ size: [5] ++ ++ - AwbColourGains3: ++ type: int32_t ++ description: | ++ Array of ColourGains3 (one entry per profile) used by the AWB algorithm. ++ size: [5,3] ++ ++ - AwbColourCorrection: ++ type: int32_t ++ description: | ++ Array of ColourCorrection (one entry per profile) used by the AWB ++ algorithm. ++ size: [5,3,3] ++ ++ - AwbCustomColorTemperature: ++ type: int32_t ++ description: | ++ Color Temperature specifying a custom Awb mode. Must be a value of ++ AwbReferenceColorTemperature[]. ++ Applicable when AwbMode is set to AwbCustom. ++ ++ - BadPixelRemovalEnable: ++ type: bool ++ description: | ++ BadPixelRemoval feature enable status. ++ ++ - BadPixelRemovalStrength: ++ type: int32_t ++ description: | ++ Strength of the detection and correction artifacts generated by a bad ++ pixel on the sensor array. ++ If BadPixelRemovalThreshold is set to a positive value, then this ++ setting is under control of the BadPixelRemoval algorithm. ++ ++ - BadPixelRemovalThreshold: ++ type: int32_t ++ description: | ++ If set to a positive value, the BadPixelRemoval algorithm adjusts the ++ strength of detection so the amount of expected bad pixel corrected is ++ as close as possible to that threshold. ++ ++ - BadPixelRemovalCount: ++ type: int32_t ++ description: | ++ Report of the amount of bad components corrected. ++ ++ - BlackLevelCorrectionEnable: ++ type: bool ++ description: | ++ BlackLevelCorrection feature enable status. ++ ++ - BlackLevelCorrectionLevels: ++ type: int32_t ++ description: | ++ Black levels correction of the Red, Green and Blue colour channels, in ++ that order. ++ size: [3] ++ ++ - DemosaicingEnable: ++ type: bool ++ description: | ++ Demosaicing feature enable status. ++ ++ - DemosaicingFilter: ++ type: int32_t ++ description: | ++ Demosaicing filter parameters (strength of the detection) to adapt to ++ specific shapes in this order: Peak, Vertical line, Horizontal line, ++ Edge ++ size: [4] ++ ++ - ContrastLuminanceEnable: ++ type: bool ++ description: | ++ ContrastLuminance feature enable status. ++ ++ - ContrastLuminance: ++ type: int32_t ++ description: | ++ Luminance amplification factor table for preset luminance segments. ++ Entry 0 is for pixel with luminance of 0, entry 1 is for pixel with ++ luminance of 32, ..., entry 8 is for pixel with luminance of 256. ++ Unit = 100 for "x1.0", 150 for "x1.5". ++ size: [9] ++ ++ - IspDecimationRatio: ++ type: int32_t ++ description: | ++ ISP decimation ratio. This control can only be returned in metadata. ++ ++ - StatisticArea: ++ type: Rectangle ++ description: | ++ Area defining where the statistics are extracted within the frame. ++ If not defined, the statistics are extracted within the whole frame. ++ ++ - StatisticProfile: ++ type: int32_t ++ description: | ++ Defines which statistics are reported. ++ enum: ++ - name: Full ++ value: 0 ++ description: | ++ Bins and Average on Up and Down location are reported. The ISP needs ++ several VSYNC cycles to report such statistics. ++ - name: AverageUp ++ value: 1 ++ description: Only Average on Up location are reported (fast report). ++ - name: AverageDown ++ value: 2 ++ description: Only Average on Down location are reported (fast report). ++ ++ - StatisticAverageUp: ++ type: int32_t ++ description: | ++ Reported average value (from 0 to 255) measured at the up part of the ++ ISP pipeline. Red, Green, Blue colour channels and Luminance in that ++ order. ++ size: [4] ++ ++ - StatisticAverageDown: ++ type: int32_t ++ description: | ++ Reported average value (from 0 to 255) measured at the down part of the ++ ISP pipeline. Red, Green, Blue colour channels and Luminance in that ++ order. ++ size: [4] ++ ++ - StatisticBinsUp: ++ type: int32_t ++ description: | ++ Reported bins (number of pixels in one of the 12 ranges of luminance) ++ measured at the up part of the ISP pipeline. ++ size: [12] ++ ++ - StatisticBinsDown: ++ type: int32_t ++ description: | ++ Reported bins (number of pixels in one of the 12 ranges of luminance) ++ measured at the up part of the ISP pipeline. ++ size: [12] ++ ++ - SensorBitsPerPixel: ++ type: int32_t ++ description: | ++ Number of bits per pixel of the sensor. ++ This control can only be returned in metadata. ++ ++ - SensorDelay: ++ type: int32_t ++ description: | ++ Delay (in number of VSYNC) between the time a sensor control (gain or ++ exposure) is updated, and the time the frame is actually updated. ++ ++ - SensorDelayMeasure: ++ type: int32_t ++ description: | ++ Reported sensor delay measured by the IPA. -1 if the measure is not ++ available ++ ++ - DoSensorDelayMeasure: ++ type: bool ++ description: | ++ When set to True, order the IPA to perform the sensor delay measure ++ ++ - PipelineHwRevision: ++ type: int32_t ++ description: | ++ IPA hardware revision identifier. ++ ++ - GammaCorrectionEnable: ++ type: bool ++ description: | ++ GammaCorrection feature enable status. ++ + ... +diff --git a/src/libcamera/ipc_unixsocket.cpp b/src/libcamera/ipc_unixsocket.cpp +index 0163deed..75285b67 100644 +--- a/src/libcamera/ipc_unixsocket.cpp ++++ b/src/libcamera/ipc_unixsocket.cpp +@@ -8,7 +8,6 @@ + #include "libcamera/internal/ipc_unixsocket.h" + + #include +-#include + #include + #include + #include +@@ -248,8 +247,8 @@ int IPCUnixSocket::sendData(const void *buffer, size_t length, + iov[0].iov_base = const_cast(buffer); + iov[0].iov_len = length; + +- char *buf = (char*)malloc(CMSG_SPACE(num * sizeof(uint32_t))); +- memset((void*)buf, 0, sizeof(buf)); ++ char buf[CMSG_SPACE(num * sizeof(uint32_t))]; ++ memset(buf, 0, sizeof(buf)); + + struct cmsghdr *cmsg = (struct cmsghdr *)buf; + cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t)); +@@ -271,11 +270,9 @@ int IPCUnixSocket::sendData(const void *buffer, size_t length, + int ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to sendmsg: " << strerror(-ret); +- free(buf); + return ret; + } + +- free(buf); + return 0; + } + +@@ -286,8 +283,8 @@ int IPCUnixSocket::recvData(void *buffer, size_t length, + iov[0].iov_base = buffer; + iov[0].iov_len = length; + +- char *buf = (char*)malloc(CMSG_SPACE(num * sizeof(uint32_t))); +- memset((void*)buf, 0, sizeof(buf)); ++ char buf[CMSG_SPACE(num * sizeof(uint32_t))]; ++ memset(buf, 0, sizeof(buf)); + + struct cmsghdr *cmsg = (struct cmsghdr *)buf; + cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t)); +@@ -308,14 +305,12 @@ int IPCUnixSocket::recvData(void *buffer, size_t length, + if (ret != -EAGAIN) + LOG(IPCUnixSocket, Error) + << "Failed to recvmsg: " << strerror(-ret); +- free(buf); + return ret; + } + + if (fds) + memcpy(fds, CMSG_DATA(cmsg), num * sizeof(uint32_t)); + +- free(buf); + return 0; + } + +diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp +index 04f40132..bd054552 100644 +--- a/src/libcamera/media_device.cpp ++++ b/src/libcamera/media_device.cpp +@@ -159,12 +159,12 @@ bool MediaDevice::lock() + * + * \sa lock() + */ +-bool MediaDevice::unlock() ++void MediaDevice::unlock() + { + if (!fd_.isValid()) +- return false; ++ return; + +- return lockf(fd_.get(), F_ULOCK, 0) == 0; ++ lockf(fd_.get(), F_ULOCK, 0); + } + + /** +diff --git a/src/libcamera/pipeline/dcmipp/dcmipp.cpp b/src/libcamera/pipeline/dcmipp/dcmipp.cpp +new file mode 100644 +index 00000000..a7353fe2 +--- /dev/null ++++ b/src/libcamera/pipeline/dcmipp/dcmipp.cpp +@@ -0,0 +1,1363 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024, ST Microelectronics ++ * ++ * dcmipp.cpp - Pipeline handler for the stm32 DCMIPP device ++ * Originally based on the vimc pipeline handler ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "libcamera/internal/camera.h" ++#include "libcamera/internal/camera_sensor.h" ++#include "libcamera/internal/device_enumerator.h" ++#include "libcamera/internal/framebuffer.h" ++#include "libcamera/internal/ipa_manager.h" ++#include "libcamera/internal/media_device.h" ++#include "libcamera/internal/pipeline_handler.h" ++#include "libcamera/internal/v4l2_subdevice.h" ++#include "libcamera/internal/v4l2_videodevice.h" ++ ++#include "linux/stm32-dcmipp-config.h" ++#include "dcmipp.h" ++ ++namespace libcamera { ++ ++LOG_DEFINE_CATEGORY(DCMIPP) ++ ++class PipelineHandlerDcmipp; ++class DcmippCameraData; ++ ++struct DcmippFrameInfo { ++ unsigned int frame; ++ Request *request; ++ ++ FrameBuffer *dumpPathBuffer; ++ FrameBuffer *mainPathBuffer; ++ FrameBuffer *auxPathBuffer; ++ ++ FrameBuffer *statsBuffer; ++ ++ bool metadataProcessed; ++}; ++ ++class DcmippFrames ++{ ++public: ++ DcmippFrames(PipelineHandler *pipe); ++ ++ DcmippFrameInfo *create(const DcmippCameraData *data, Request *request); ++ int destroy(unsigned int frame); ++ void clear(); ++ ++ DcmippFrameInfo *find(unsigned int frame); ++ DcmippFrameInfo *find(FrameBuffer *buffer); ++ ++private: ++ PipelineHandlerDcmipp *pipe_; ++ std::map frameInfo_; ++}; ++ ++class DcmippCameraData : public Camera::Private ++{ ++public: ++ DcmippCameraData(PipelineHandler *pipe, DcmippDumpPath *dumpPath, ++ DcmippMainPath *mainPath, DcmippAuxPath *auxPath) ++ : Camera::Private(pipe), dumpPath_(dumpPath), ++ mainPath_(mainPath), auxPath_(auxPath), frameInfo_(pipe) ++ { ++ } ++ ++ PipelineHandlerDcmipp *pipe(); ++ ++ void paramsFilled(unsigned int id); ++ void statsFreed(unsigned int id); ++ void metadataReady(unsigned int frame, const ControlList &metadata); ++ void setSensorControls(unsigned int id, const ControlList &sensorControls); ++ void setIspControls(unsigned int id, const ControlList &ispControls); ++ void setPostprocControls(unsigned int id, const ControlList &ispControls); ++ ++ std::unique_ptr sensor_; ++ std::unique_ptr input_; ++ Stream Dumpstream_; ++ Stream Mainstream_; ++ Stream Auxstream_; ++ ++ DcmippDumpPath *dumpPath_; ++ DcmippMainPath *mainPath_; ++ DcmippAuxPath *auxPath_; ++ ++ std::vector ipaBuffers_; ++ std::unique_ptr ipa_; ++ DcmippFrames frameInfo_; ++ ++ std::queue requests_; ++}; ++ ++class DcmippCameraConfiguration : public CameraConfiguration ++{ ++public: ++ DcmippCameraConfiguration(DcmippCameraData *data); ++ ++ Status validate() override; ++ ++ const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; } ++ ++ unsigned int isp_decimation_ratio; ++ ++private: ++ bool fitsAllPaths(const StreamConfiguration &cfg); ++ ++ DcmippCameraData *data_; ++ V4L2SubdeviceFormat sensorFormat_; ++}; ++ ++class PipelineHandlerDcmipp : public PipelineHandler ++{ ++public: ++ PipelineHandlerDcmipp(CameraManager *manager); ++ ++ std::unique_ptr generateConfiguration(Camera *camera, ++ Span roles) override; ++ int configure(Camera *camera, CameraConfiguration *config) override; ++ ++ int exportFrameBuffers(Camera *camera, Stream *stream, ++ std::vector> *buffers) override; ++ ++ int start(Camera *camera, const ControlList *controls) override; ++ void stopDevice(Camera *camera) override; ++ ++ int queueRequestDevice(Camera *camera, Request *request) override; ++ ++ bool match(DeviceEnumerator *enumerator) override; ++ ++private: ++ int init(std::unique_ptr &data); ++ ++ DcmippCameraData *cameraData(Camera *camera) ++ { ++ return static_cast(camera->_d()); ++ } ++ ++ friend DcmippCameraData; ++ friend DcmippCameraConfiguration; ++ ++ int allocateBuffers(Camera *camera); ++ int freeBuffers(Camera *camera); ++ ++ void bufferReady(FrameBuffer *buffer); ++ void statsReady(FrameBuffer *buffer); ++ void paramsReady(FrameBuffer *buffer); ++ ++ DcmippDumpPath dumpPath_; ++ DcmippMainPath mainPath_; ++ DcmippAuxPath auxPath_; ++ ++ MediaDevice *media_; ++ std::unique_ptr bridge_; ++ std::unique_ptr input_; ++ ++ std::unique_ptr params_; ++ std::unique_ptr stats_; ++ ++ std::vector> isp_params_; ++ std::queue available_isp_params_; ++ std::vector> isp_stats_; ++ std::queue available_isp_stats_; ++ ++ void tryCompleteRequest(DcmippFrameInfo *info); ++ ++ Camera *activeCamera_; ++ ++ unsigned int ipaBufferCount_; ++}; ++ ++namespace { ++ ++static const std::map rawFormatToMediaBus{ ++ { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 }, ++ { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 }, ++ { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 }, ++ { formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 }, ++ { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 }, ++ { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 }, ++ { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 }, ++ { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 }, ++ { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 }, ++ { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 }, ++ { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 }, ++ { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 }, ++ { formats::SBGGR14, MEDIA_BUS_FMT_SBGGR14_1X14 }, ++ { formats::SGBRG14, MEDIA_BUS_FMT_SGBRG14_1X14 }, ++ { formats::SGRBG14, MEDIA_BUS_FMT_SGRBG14_1X14 }, ++ { formats::SRGGB14, MEDIA_BUS_FMT_SRGGB14_1X14 }, ++}; ++ ++} /* namespace */ ++ ++DcmippFrames::DcmippFrames(PipelineHandler *pipe) ++ : pipe_(static_cast(pipe)) ++{ ++} ++ ++DcmippFrameInfo *DcmippFrames::create(const DcmippCameraData *data, Request *request) ++{ ++ FrameBuffer *dumpPathBuffer = nullptr; ++ FrameBuffer *mainPathBuffer = nullptr; ++ FrameBuffer *auxPathBuffer = nullptr; ++ ++ if (data->dumpPath_->isEnabled()) { ++ dumpPathBuffer = request->findBuffer(&data->Dumpstream_); ++ if (!dumpPathBuffer) { ++ LOG(DCMIPP, Error) << "Attempt to queue request with invalid stream"; ++ return nullptr; ++ } ++ } ++ ++ if (data->mainPath_->isEnabled()) { ++ mainPathBuffer = request->findBuffer(&data->Mainstream_); ++ if (!mainPathBuffer) { ++ LOG(DCMIPP, Error) << "Attempt to queue request with invalid stream"; ++ return nullptr; ++ } ++ } ++ ++ if (data->auxPath_->isEnabled()) { ++ auxPathBuffer = request->findBuffer(&data->Auxstream_); ++ if (!auxPathBuffer) { ++ LOG(DCMIPP, Error) << "Attempt to queue request with invalid stream"; ++ return nullptr; ++ } ++ } ++ ++ DcmippFrameInfo *info = new DcmippFrameInfo; ++ ++ info->frame = request->sequence(); ++ info->request = request; ++ info->dumpPathBuffer = dumpPathBuffer; ++ info->mainPathBuffer = mainPathBuffer; ++ info->auxPathBuffer = auxPathBuffer; ++ ++ info->statsBuffer = nullptr; ++ info->metadataProcessed = false; ++ ++ frameInfo_[info->frame] = info; ++ ++ return info; ++} ++ ++int DcmippFrames::destroy(unsigned int frame) ++{ ++ DcmippFrameInfo *info = find(frame); ++ if (!info) ++ return -ENOENT; ++ ++ frameInfo_.erase(info->frame); ++ ++ delete info; ++ ++ return 0; ++} ++ ++void DcmippFrames::clear() ++{ ++ for (const auto &entry : frameInfo_) { ++ DcmippFrameInfo *info = entry.second; ++ ++ delete info; ++ } ++ ++ frameInfo_.clear(); ++} ++ ++DcmippFrameInfo *DcmippFrames::find(unsigned int frame) ++{ ++ auto itInfo = frameInfo_.find(frame); ++ ++ if (itInfo != frameInfo_.end()) ++ return itInfo->second; ++ ++ LOG(DCMIPP, Error) << "Frame #" << frame << " unknown in frameInfo list"; ++ ++ return nullptr; ++} ++ ++DcmippFrameInfo *DcmippFrames::find(FrameBuffer *buffer) ++{ ++ for (auto &itInfo : frameInfo_) { ++ DcmippFrameInfo *info = itInfo.second; ++ ++ if (info->dumpPathBuffer == buffer || ++ info->mainPathBuffer == buffer || ++ info->auxPathBuffer == buffer || ++ info->statsBuffer == buffer) ++ return info; ++ } ++ ++ LOG(DCMIPP, Error) << "FrameBuffer " << buffer << " unknown in frameInfo list"; ++ ++ return nullptr; ++} ++ ++DcmippCameraConfiguration::DcmippCameraConfiguration(DcmippCameraData *data) ++ : CameraConfiguration(), data_(data) ++{ ++} ++ ++bool DcmippCameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg) ++{ ++ const CameraSensor *sensor = data_->sensor_.get(); ++ StreamConfiguration config; ++ ++ /* ++ * We only check for non-RAW configuration using Main / Aux pipes since anyway ++ * we dedicate the Dump pipe for RAW ++ */ ++ if (PixelFormatInfo::info(cfg.pixelFormat).colourEncoding == PixelFormatInfo::ColourEncodingRAW) ++ return false; ++ ++ config = cfg; ++ if (data_->mainPath_->validate(sensor, &config, 1) != Valid) ++ return false; ++ ++ config = cfg; ++ if (data_->auxPath_->validate(sensor, &config, 1) != Valid) ++ return false; ++ ++ return true; ++} ++ ++CameraConfiguration::Status DcmippCameraConfiguration::validate() ++{ ++ const CameraSensor *sensor = data_->sensor_.get(); ++ PipelineHandlerDcmipp *pipe = ++ static_cast(data_->pipe()); ++ V4L2SubdeviceFormat sensorFormat; ++ Status status = Valid; ++ ++ /* ++ * List of RAW formats supported by the input subdevice ++ * sorted from the best quality to the lowest quality so ++ * that the pipeline handler will pick up the best quality ++ * possible by both sensor & dcmipp ++ */ ++ const std::vector mbusRAWInputCodes = { ++ MEDIA_BUS_FMT_SRGGB14_1X14, ++ MEDIA_BUS_FMT_SGRBG14_1X14, ++ MEDIA_BUS_FMT_SGBRG14_1X14, ++ MEDIA_BUS_FMT_SBGGR14_1X14, ++ MEDIA_BUS_FMT_SRGGB12_1X12, ++ MEDIA_BUS_FMT_SGRBG12_1X12, ++ MEDIA_BUS_FMT_SGBRG12_1X12, ++ MEDIA_BUS_FMT_SBGGR12_1X12, ++ MEDIA_BUS_FMT_SRGGB10_1X10, ++ MEDIA_BUS_FMT_SGRBG10_1X10, ++ MEDIA_BUS_FMT_SGBRG10_1X10, ++ MEDIA_BUS_FMT_SBGGR10_1X10, ++ MEDIA_BUS_FMT_SRGGB8_1X8, ++ MEDIA_BUS_FMT_SGRBG8_1X8, ++ MEDIA_BUS_FMT_SGBRG8_1X8, ++ MEDIA_BUS_FMT_SBGGR8_1X8, ++ }; ++ ++ LOG(DCMIPP, Debug) << "validate"; ++ ++ if (config_.empty()) ++ return Invalid; ++ ++ /* Cap the number of entries to the available streams. */ ++ if (config_.size() > 3) { ++ config_.resize(3); ++ status = Adjusted; ++ } ++ ++ /* ++ * Reorder the list of streams based on compatibility with pipes. Evaluate first ++ * streams that cannot work on all pipes. Create an order list, and push at the ++ * back of the list pipes that can run on both main & aux pipes ++ */ ++ std::vector order; ++ order.reserve(config_.size()); ++ for (unsigned int index = 0; index < config_.size(); index++) { ++ if (fitsAllPaths(config_[index])) ++ order.emplace(order.end(), index); ++ else ++ order.emplace(order.begin(), index); ++ } ++ ++ /* ++ * First walk through all Non-RAW (going through ISP) config to figure out the ++ * necessary ISP decimation factor to be applied ++ */ ++ Size post_isp_resolution = sensor->resolution(); ++ isp_decimation_ratio = 1; ++ ++ for (unsigned int index : order) { ++ StreamConfiguration &cfg = config_[index]; ++ ++ if (PixelFormatInfo::info(cfg.pixelFormat).colourEncoding == PixelFormatInfo::ColourEncodingRAW) ++ continue; ++ ++ /* ++ * For Main & Aux pipe postproc can perform decimate-downsize hence 8 * 8 reduction ++ * ++ * ISP decimation can also perform a maximum of 8 reduction. It is also mandatory ++ * to ensure that RAW frame size does not exceed 2688 width prior demosaicing. ++ */ ++ /* Ensure to not exceed maximum width for demosaicing */ ++ while (post_isp_resolution.width > DCMIPP_RAW_MAX_WIDTH || ++ post_isp_resolution.width / cfg.size.width > (8 * 8) || ++ post_isp_resolution.height / cfg.size.height > (8 * 8)) { ++ isp_decimation_ratio *= 2; ++ if (isp_decimation_ratio > 8) { ++ LOG(DCMIPP, Error) << "Necessary decimation factor too big."; ++ return Invalid; ++ } ++ post_isp_resolution /= 2; ++ } ++ } ++ ++ /* Pick a pipe and validate each configuration */ ++ bool dumpAllocated = false, mainAllocated = false, auxAllocated = false; ++ for (unsigned int index : order) { ++ StreamConfiguration &cfg = config_[index]; ++ DcmippPath *path; ++ Stream *stream; ++ ++ if (PixelFormatInfo::info(cfg.pixelFormat).colourEncoding == PixelFormatInfo::ColourEncodingRAW) { ++ if (dumpAllocated) { ++ LOG(DCMIPP, Error) << "Cannot perform RAW capture since Dump pipe already allocated"; ++ return Invalid; ++ } ++ LOG(DCMIPP, Debug) << "Select Dump pipe for config: pixelformat: " << cfg.pixelFormat << " size: " << cfg.size; ++ path = &pipe->dumpPath_; ++ stream = &data_->Dumpstream_; ++ dumpAllocated = true; ++ } else { ++ if (!mainAllocated) { ++ LOG(DCMIPP, Debug) << "Select Main pipe for config: pixelformat: " << cfg.pixelFormat << " size: " << cfg.size; ++ path = &pipe->mainPath_; ++ stream = &data_->Mainstream_; ++ mainAllocated = true; ++ } else if (!auxAllocated) { ++ LOG(DCMIPP, Debug) << "Select Aux pipe for config: pixelformat: " << cfg.pixelFormat << " size: " << cfg.size; ++ path = &pipe->auxPath_; ++ stream = &data_->Auxstream_; ++ auxAllocated = true; ++ } else { ++ LOG(DCMIPP, Error) << "No more pipe available"; ++ return Invalid; ++ } ++ } ++ ++ StreamConfiguration tryCfg = cfg; ++ if (path->validate(sensor, &tryCfg, isp_decimation_ratio) != Valid) ++ return Invalid; ++ ++ cfg = std::move(tryCfg); ++ cfg.setStream(stream); ++ ++ /* If we have validate a RAW format, this should be the sensor format */ ++ if (PixelFormatInfo::info(cfg.pixelFormat).colourEncoding == PixelFormatInfo::ColourEncodingRAW) { ++ /* Check that the format is supported */ ++ if (rawFormatToMediaBus.find(cfg.pixelFormat) == rawFormatToMediaBus.end()) { ++ LOG(DCMIPP, Debug) << "Format " << cfg.pixelFormat << " not supported"; ++ return Invalid; ++ } ++ ++ sensorFormat.code = rawFormatToMediaBus.find(cfg.pixelFormat)->second; ++ sensorFormat.size = sensor->resolution(); ++ } ++ } ++ ++ /* If we haven't got yet a sensorFormat, then pick the best one for us */ ++ if (sensorFormat.size.isNull()) { ++ sensorFormat = sensor->getFormat(mbusRAWInputCodes, sensor->resolution()); ++ if (sensorFormat.size.isNull()) { ++ LOG(DCMIPP, Debug) << __func__ << "Failed to get compatible RAW format"; ++ return Invalid; ++ } ++ } ++ ++ LOG(DCMIPP, Debug) << __func__ << " sensor MBUS format is " << sensorFormat.code << "/" << sensorFormat.size; ++ sensorFormat_ = sensorFormat; ++ ++ return status; ++} ++ ++PipelineHandlerDcmipp::PipelineHandlerDcmipp(CameraManager *manager) ++ : PipelineHandler(manager), media_(nullptr), activeCamera_(nullptr) ++{ ++} ++ ++std::unique_ptr ++PipelineHandlerDcmipp::generateConfiguration(Camera *camera, ++ Span roles) ++{ ++ DcmippCameraData *data = cameraData(camera); ++ std::unique_ptr config = ++ std::make_unique(data); ++ bool dumpAllocated = false, mainAllocated = false, auxAllocated = false; ++ int i = 0; ++ ++ LOG(DCMIPP, Debug) << "generateConfiguration"; ++ ++ /* It is allowed to not indicate a role, in which case we return a empty config */ ++ if (roles.empty()) ++ return config; ++ ++ /* DCMIPP can handle a maximum of 3 streams */ ++ if (roles.size() > 3) { ++ LOG(DCMIPP, Error) << "More streams requested than supported"; ++ return nullptr; ++ } ++ ++ /* ++ * We can allocate pipes depending on the role since preferred configuration for ++ * each role define the pipe to be used due to their HW capabilities ++ * ++ * StillCapture: targetting best image, with JPEG output via SW encoder behind hence ++ * YUV420 Planar format. That is MEDIA_BUS_FMT_UYVY8_1_5X8 in DCMIPP ++ * driver and V4L2_PIX_FMT_YUV420. ++ * This is exclusively done via the Main pipe. ++ * Viewfinder: targetting a display, hence limited size of the image to fit into the ++ * display and xRGB32 output (for the time being we use RGB888 instead. ++ * Hence MEDIA_BUS_FMT_RGB888_1X24 and V4L2_PIX_FMT_RGB24. ++ * This can be done by both Main & Aux pipe however we use Aux pipe here. ++ * VideoRecording: targetting a video encoder with a maximum resolution of HD. ++ * Hence MEDIA_BUS_FMT_YUYV8_1_5X8 and V4L2_PIX_FMT_NV12. ++ * This is exclusively done via the Main pipe. ++ * Raw: targetting RawBayer extraction of the image from a sensor. Format depends on ++ * the format of the sensor. This is only done by the Dump pipe. ++ */ ++ for (const StreamRole role : roles) { ++ DcmippPath *path; ++ Size maxSize; ++ PixelFormat pixelFormat; ++ ++ switch (role) { ++ case StreamRole::StillCapture: ++ maxSize = data->sensor_->resolution(); ++ pixelFormat = formats::YUV420; ++ if (mainAllocated) { ++ LOG(DCMIPP, Error) << "Main pipe already allocated, ABORT"; ++ return nullptr; ++ } ++ path = data->mainPath_; ++ mainAllocated = true; ++ break; ++ case StreamRole::Viewfinder: ++ maxSize = { 1024, 600 }; ++ pixelFormat = formats::RGB888; ++ if (auxAllocated) { ++ LOG(DCMIPP, Error) << "Aux pipe already allocated, ABORT"; ++ return nullptr; ++ } ++ path = data->auxPath_; ++ auxAllocated = true; ++ break; ++ case StreamRole::VideoRecording: ++ maxSize = { 1920, 1080 }; ++ pixelFormat = formats::NV12; ++ if (mainAllocated) { ++ LOG(DCMIPP, Error) << "Main pipe already allocated, ABORT"; ++ return nullptr; ++ } ++ path = data->mainPath_; ++ mainAllocated = true; ++ break; ++ case StreamRole::Raw: ++ maxSize = data->sensor_->resolution(); ++ /* ++ * In case of Raw role, the pixelFormat is actually given by the ++ * dcmipp_path generateConfiguration since it depends on sensor format ++ */ ++ pixelFormat = formats::SBGGR8; ++ if (dumpAllocated) { ++ LOG(DCMIPP, Error) << "Dump pipe already allocated, ABORT"; ++ return nullptr; ++ } ++ path = data->dumpPath_; ++ dumpAllocated = true; ++ break; ++ default: ++ LOG(DCMIPP, Error) << "Requested stream role not supported: " << roles[0]; ++ return nullptr; ++ } ++ ++ /* ++ * role is given here mainly for the ViewFinder role case since we need to figure ++ * out a way to indicate that we need to enable the GammaCorrection ++ */ ++ StreamConfiguration cfg = ++ path->generateConfiguration(data->sensor_.get(), maxSize, pixelFormat); ++ if (!cfg.pixelFormat.isValid()) ++ return nullptr; ++ ++ config->addConfiguration(cfg); ++ i++; ++ } ++ ++ config->validate(); ++ ++ return config; ++} ++ ++int PipelineHandlerDcmipp::configure(Camera *camera, CameraConfiguration *c) ++{ ++ DcmippCameraConfiguration *config = ++ static_cast(c); ++ DcmippCameraData *data = cameraData(camera); ++ int ret; ++ ++ LOG(DCMIPP, Debug) << "configure"; ++ ++ /* Disable all paths */ ++ dumpPath_.setEnabled(false); ++ mainPath_.setEnabled(false); ++ auxPath_.setEnabled(false); ++ ++ /* ++ * Only enable the relevant link between dcmipp_input and its ++ * source entity (bridge or sensor) ++ */ ++ const MediaEntity *source; ++ if (bridge_) ++ source = bridge_->entity(); ++ else ++ source = data->sensor_->entity(); ++ ++ const MediaPad *input_sinkPad = data->input_->entity()->getPadByIndex(0); ++ for (MediaLink *link : input_sinkPad->links()) { ++ if (link->source()->entity()->name().compare(source->name())) ++ link->setEnabled(false); ++ else ++ link->setEnabled(true); ++ } ++ ++ /* Get the sensor configuration and apply it */ ++ V4L2SubdeviceFormat subformat = config->sensorFormat(); ++ ret = data->sensor_->setFormat(&subformat); ++ if (ret) ++ return ret; ++ ++ /* Same configuration on both side of the bridge */ ++ if (bridge_) { ++ ret = bridge_->setFormat(0, &subformat); ++ if (ret) ++ return ret; ++ ret = bridge_->setFormat(1, &subformat); ++ if (ret) ++ return ret; ++ } ++ ++ /* Initialize links after input depending on the config */ ++ for (const StreamConfiguration &cfg : *config) { ++ if (cfg.stream() == &data->Dumpstream_) ++ ret = dumpPath_.setEnabled(true); ++ else if (cfg.stream() == &data->Mainstream_) ++ ret = mainPath_.setEnabled(true); ++ else if (cfg.stream() == &data->Auxstream_) ++ ret = auxPath_.setEnabled(true); ++ } ++ ++ /* Same configuration on both side of the input subdev */ ++ ret = input_->setFormat(0, &subformat); ++ if (ret) ++ return ret; ++ ++ /* ++ * Configuration of each path ++ * If main and/or aux are used, store the number of necessary ipa buffers ++ * this is set to the number of path buffer + 1 ++ */ ++ ipaBufferCount_ = 0; ++ for (const StreamConfiguration &cfg : *config) { ++ if (cfg.stream() == &data->Dumpstream_) ++ ret = dumpPath_.configure(cfg, subformat, 0); ++ else if (cfg.stream() == &data->Mainstream_) { ++ ret = mainPath_.configure(cfg, subformat, config->isp_decimation_ratio); ++ if (!ret && cfg.bufferCount > ipaBufferCount_ + 1) ++ ipaBufferCount_ = cfg.bufferCount + 1; ++ } else if (cfg.stream() == &data->Auxstream_) { ++ ret = auxPath_.configure(cfg, subformat, config->isp_decimation_ratio); ++ if (!ret && cfg.bufferCount > ipaBufferCount_ + 1) ++ ipaBufferCount_ = cfg.bufferCount + 1; ++ } ++ if (ret) { ++ LOG(DCMIPP, Error) << "Failed to configure path"; ++ return ret; ++ } ++ } ++ ++ /* Configure the IPA if we have one and use Main or Aux paths */ ++ if (data->ipa_ && (data->mainPath_->isEnabled() || data->auxPath_->isEnabled())) { ++ /* Configure the param & stat video devices */ ++ V4L2DeviceFormat paramFormat; ++ paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_ST_DCMIPP_ISP_PARAMS); ++ ret = params_->setFormat(¶mFormat); ++ if (ret) ++ return ret; ++ ++ V4L2DeviceFormat statFormat; ++ statFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_ST_DCMIPP_ISP_STAT); ++ ret = stats_->setFormat(&statFormat); ++ if (ret) ++ return ret; ++ ++ /* Disable the stat region by default */ ++ struct v4l2_ctrl_isp_stat_region region; ++ ++ region.nb_regions = 0; ++ ++ ControlValue ctrl(Span{ reinterpret_cast(®ion), sizeof(region) }); ++ ControlList statAreaControl; ++ statAreaControl.set(V4L2_CID_ISP_STAT_REGION, ctrl); ++ ret = stats_->setControls(&statAreaControl); ++ if (ret) { ++ LOG(DCMIPP, Error) << "Failed to set control of ISP Stat Area"; ++ return ret; ++ } ++ ++ /* ++ * Inform IPA of sensor controls. IPA will update ++ * data->controlInfo_ which specifies the list of controls supported by the camera. ++ */ ++ IPACameraSensorInfo sensorInfo; ++ data->sensor_->sensorInfo(&sensorInfo); ++ ++ ret = data->ipa_->configure(sensorInfo, data->sensor_->controls(), ++ config->isp_decimation_ratio, ++ &data->controlInfo_); ++ if (ret) { ++ LOG(DCMIPP, Error) << "Failed to configure IPA"; ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++int PipelineHandlerDcmipp::exportFrameBuffers([[maybe_unused]] Camera *camera, Stream *stream, ++ std::vector> *buffers) ++{ ++ DcmippCameraData *data = cameraData(camera); ++ unsigned int count = stream->configuration().bufferCount; ++ ++ if (stream == &data->Dumpstream_) ++ return dumpPath_.exportBuffers(count, buffers); ++ else if (stream == &data->Mainstream_) ++ return mainPath_.exportBuffers(count, buffers); ++ else if (stream == &data->Auxstream_) ++ return auxPath_.exportBuffers(count, buffers); ++ ++ return -EINVAL; ++} ++ ++int PipelineHandlerDcmipp::allocateBuffers(Camera *camera) ++{ ++ DcmippCameraData *data = cameraData(camera); ++ unsigned int ipaBufferId = 0; ++ int ret; ++ ++ ret = params_->allocateBuffers(ipaBufferCount_, &isp_params_); ++ if (ret < 0) ++ goto error; ++ ++ ret = stats_->allocateBuffers(ipaBufferCount_, &isp_stats_); ++ if (ret < 0) ++ goto error; ++ ++ for (std::unique_ptr &buffer : isp_stats_) { ++ buffer->setCookie(ipaBufferId++); ++ data->ipaBuffers_.emplace_back(buffer->cookie(), buffer->planes()); ++ available_isp_stats_.push(buffer.get()); ++ } ++ ++ for (std::unique_ptr &buffer : isp_params_) { ++ buffer->setCookie(ipaBufferId++); ++ buffer->_d()->metadata().planes()[0].bytesused = sizeof(struct stm32_dcmipp_params_cfg); ++ data->ipaBuffers_.emplace_back(buffer->cookie(), buffer->planes()); ++ available_isp_params_.push(buffer.get()); ++ } ++ ++ data->ipa_->mapBuffers(data->ipaBuffers_); ++ ++ return 0; ++ ++error: ++ isp_params_.clear(); ++ isp_stats_.clear(); ++ ++ return ret; ++} ++ ++int PipelineHandlerDcmipp::freeBuffers(Camera *camera) ++{ ++ DcmippCameraData *data = cameraData(camera); ++ ++ while (!available_isp_stats_.empty()) ++ available_isp_stats_.pop(); ++ ++ while (!available_isp_params_.empty()) ++ available_isp_params_.pop(); ++ ++ isp_params_.clear(); ++ isp_stats_.clear(); ++ ++ std::vector ids; ++ for (IPABuffer &ipabuf : data->ipaBuffers_) ++ ids.push_back(ipabuf.id); ++ ++ data->ipa_->unmapBuffers(ids); ++ data->ipaBuffers_.clear(); ++ ++ params_->releaseBuffers(); ++ stats_->releaseBuffers(); ++ ++ return 0; ++} ++ ++int PipelineHandlerDcmipp::start(Camera *camera, [[maybe_unused]] const ControlList *controls) ++{ ++ DcmippCameraData *data = cameraData(camera); ++ int ret; ++ ++ LOG(DCMIPP, Debug) << "start"; ++ ++ activeCamera_ = camera; ++ ++ if (data->dumpPath_->isEnabled()) { ++ ret = data->dumpPath_->start(); ++ if (ret < 0) { ++ LOG(DCMIPP, Error) << "Cannot start bytecapture"; ++ return ret; ++ } ++ } ++ ++ /* Nothing else to do if there is neither main or aux paths */ ++ if (!data->mainPath_->isEnabled() && !data->auxPath_->isEnabled()) ++ return 0; ++ ++ ret = allocateBuffers(camera); ++ if (ret < 0) { ++ freeBuffers(camera); ++ if (data->dumpPath_->isEnabled()) ++ data->dumpPath_->stop(); ++ LOG(DCMIPP, Error) << "Cannot allocate stats & params buffers for IPA"; ++ return ret; ++ } ++ ++ ret = data->ipa_->start(); ++ if (ret) { ++ LOG(DCMIPP, Error) << "Cannot start IPA"; ++ freeBuffers(camera); ++ if (data->dumpPath_->isEnabled()) ++ data->dumpPath_->stop(); ++ return ret; ++ } ++ ++ if (data->mainPath_->isEnabled()) { ++ ret = data->mainPath_->start(); ++ if (ret < 0) { ++ LOG(DCMIPP, Error) << "Cannot start pixelcapture"; ++ data->ipa_->stop(); ++ freeBuffers(camera); ++ if (data->dumpPath_->isEnabled()) ++ data->dumpPath_->stop(); ++ return ret; ++ } ++ } ++ ++ if (data->auxPath_->isEnabled()) { ++ ret = data->auxPath_->start(); ++ if (ret < 0) { ++ LOG(DCMIPP, Error) << "Cannot start pixelcapture"; ++ if (data->mainPath_->isEnabled()) ++ data->mainPath_->stop(); ++ data->ipa_->stop(); ++ freeBuffers(camera); ++ if (data->dumpPath_->isEnabled()) ++ data->dumpPath_->stop(); ++ return ret; ++ } ++ } ++ ++ ret = stats_->streamOn(); ++ if (ret < 0) { ++ LOG(DCMIPP, Error) << "Cannot start stat buffers"; ++ mainPath_.stop(); ++ data->ipa_->stop(); ++ freeBuffers(camera); ++ if (data->dumpPath_->isEnabled()) ++ data->dumpPath_->stop(); ++ return ret; ++ } ++ ++ ret = params_->streamOn(); ++ if (ret < 0) { ++ LOG(DCMIPP, Error) << "Cannot start params buffers"; ++ stats_->streamOff(); ++ mainPath_.stop(); ++ data->ipa_->stop(); ++ freeBuffers(camera); ++ if (data->dumpPath_->isEnabled()) ++ data->dumpPath_->stop(); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++void PipelineHandlerDcmipp::stopDevice(Camera *camera) ++{ ++ DcmippCameraData *data = cameraData(camera); ++ ++ LOG(DCMIPP, Debug) << "stop"; ++ ++ if (data->mainPath_->isEnabled() || data->mainPath_->isEnabled()) { ++ params_->streamOff(); ++ stats_->streamOff(); ++ if (data->auxPath_->isEnabled()) ++ data->auxPath_->stop(); ++ if (data->mainPath_->isEnabled()) ++ data->mainPath_->stop(); ++ data->ipa_->stop(); ++ freeBuffers(camera); ++ } ++ if (data->dumpPath_->isEnabled()) ++ data->dumpPath_->stop(); ++ ++ data->frameInfo_.clear(); ++ ++ activeCamera_ = nullptr; ++} ++ ++int PipelineHandlerDcmipp::queueRequestDevice(Camera *camera, Request *request) ++{ ++ DcmippCameraData *data = cameraData(camera); ++ FrameBuffer *paramBuffer; ++ int ret; ++ ++ LOG(DCMIPP, Debug) << "Function: " << __func__ << " sequence: " << request->sequence(); ++ ++ DcmippFrameInfo *info = data->frameInfo_.create(data, request); ++ if (!info) ++ return -ENOENT; ++ ++ /* ++ * DCMIPP driver does not output statistics frames from the very beginning. ++ * This depends on the profile being selected via the STAT_PROFILE control, ++ * however in order to keep the pipeline handler code simpler, set the longest ++ * delay here, which leads to libcamera not trying to get statistics from the ++ * DCMIPP driver during the first requests ++ */ ++ if (!available_isp_stats_.empty() && request->sequence() > DCMIPP_STATS_FRAME_DELAY) { ++ info->statsBuffer = available_isp_stats_.front(); ++ available_isp_stats_.pop(); ++ } ++ ++ if (info->dumpPathBuffer) { ++ ret = data->dumpPath_->queueBuffer(info->dumpPathBuffer); ++ if (ret < 0) { ++ LOG(DCMIPP, Error) << "Can not queue dumpPath buffer"; ++ return ret; ++ } ++ } ++ ++ /* Nothing else to do if there is neither main or aux paths */ ++ if (!info->mainPathBuffer && !info->auxPathBuffer) ++ return 0; ++ ++ /* Inform IPA of control update */ ++ data->ipa_->queueRequest(request->sequence(), request->controls()); ++ ++ /* Sends the params buffer to the IPA for filling */ ++ if (available_isp_params_.empty()) { ++ LOG(DCMIPP, Error) << "ISP parameters buffer underrun"; ++ } else { ++ paramBuffer = available_isp_params_.front(); ++ available_isp_params_.pop(); ++ data->ipa_->fillParamsBuffer(request->sequence(), paramBuffer->cookie()); ++ } ++ ++ /* Queue pipeline buffers */ ++ if (info->mainPathBuffer) { ++ ret = data->mainPath_->queueBuffer(info->mainPathBuffer); ++ if (ret < 0) { ++ LOG(DCMIPP, Error) << "Can not queue mainPath buffer"; ++ return ret; ++ } ++ } ++ ++ if (info->auxPathBuffer) { ++ ret = data->auxPath_->queueBuffer(info->auxPathBuffer); ++ if (ret < 0) { ++ LOG(DCMIPP, Error) << "Can not queue auxPath buffer"; ++ return ret; ++ } ++ } ++ ++ if (info->statsBuffer) { ++ ret = stats_->queueBuffer(info->statsBuffer); ++ if (ret < 0) { ++ LOG(DCMIPP, Error) << "Can not queue ISP stats buffer"; ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++bool PipelineHandlerDcmipp::match(DeviceEnumerator *enumerator) ++{ ++ LOG(DCMIPP, Debug) << "match"; ++ ++ DeviceMatch dm("dcmipp"); ++ ++ dm.add("dcmipp_input"); ++ dm.add("dcmipp_main_isp"); ++ dm.add("dcmipp_main_postproc"); ++ dm.add("dcmipp_main_capture"); ++ ++ media_ = acquireMediaDevice(enumerator, dm); ++ if (!media_) ++ return false; ++ ++ std::unique_ptr data = std::make_unique(this, ++ &dumpPath_, ++ &mainPath_, ++ &auxPath_); ++ ++ /* Locate and open all subdevices and video nodes */ ++ if (init(data)) ++ return false; ++ ++ data->ipa_ = IPAManager::createIPA(this, 0, 0); ++ if (!data->ipa_) { ++ LOG(DCMIPP, Error) << "no matching IPA found"; ++ return false; ++ } ++ ++ /* Connect the two event callback from IPA */ ++ data->ipa_->paramsBufferReady.connect(data.get(), &DcmippCameraData::paramsFilled); ++ data->ipa_->statsBufferProcessed.connect(data.get(), &DcmippCameraData::statsFreed); ++ data->ipa_->metadataReady.connect(data.get(), &DcmippCameraData::metadataReady); ++ data->ipa_->setSensorControls.connect(data.get(), &DcmippCameraData::setSensorControls); ++ data->ipa_->setIspControls.connect(data.get(), &DcmippCameraData::setIspControls); ++ data->ipa_->setPostprocControls.connect(data.get(), &DcmippCameraData::setPostprocControls); ++ ++ /* ++ * Initialize IPA. IPA is reponsible for setting data->controlInfo_ which ++ * specifies the list of controls supported by the camera. ++ */ ++ IPACameraSensorInfo sensorInfo; ++ if (data->sensor_->sensorInfo(&sensorInfo)) { ++ LOG(DCMIPP, Error) << "Camera sensor information not available"; ++ return false; ++ } ++ ++ /* Tuning file made from sensor name or from environment variable */ ++ std::string ipaTuningFile; ++ char const *configFromEnv = utils::secure_getenv("LIBCAMERA_DCMIPP_TUNING_FILE"); ++ if (configFromEnv && *configFromEnv != '\0') { ++ ipaTuningFile = std::string(configFromEnv); ++ } else { ++ ipaTuningFile = data->ipa_->configurationFile(data->sensor_->model() + ".yaml"); ++ } ++ ++ if (data->ipa_->init(IPASettings{ ipaTuningFile, data->sensor_->model() }, ++ media_->hwRevision(), sensorInfo, data->sensor_->controls(), ++ &data->controlInfo_) < 0) { ++ LOG(DCMIPP, Error) << "IPA initialization failure"; ++ return false; ++ } ++ ++ /* Create and register the camera. */ ++ std::set streams{ &data->Dumpstream_, &data->Mainstream_, &data->Auxstream_ }; ++ const std::string &id = data->sensor_->id(); ++ std::shared_ptr camera = ++ Camera::create(std::move(data), id, streams); ++ registerCamera(std::move(camera)); ++ ++ return true; ++} ++ ++int PipelineHandlerDcmipp::init(std::unique_ptr &data) ++{ ++ const MediaPad *sinkPad; ++ const MediaPad *remotePad; ++ ++ LOG(DCMIPP, Debug) << "init"; ++ ++ input_ = V4L2Subdevice::fromEntityName(media_, "dcmipp_input"); ++ if (input_->open()) ++ return -ENODEV; ++ ++ /* Locate and open the optional CSI-2 receiver */ ++ sinkPad = input_->entity()->getPadByIndex(0); ++ if (!sinkPad || sinkPad->links().empty()) ++ return false; ++ ++ /* Look at links going to input entity sink pad */ ++ for (MediaLink *link : sinkPad->links()) { ++ remotePad = link->source(); ++ if (remotePad->entity()->function() == MEDIA_ENT_F_VID_IF_BRIDGE) { ++ bridge_ = std::make_unique(remotePad->entity()); ++ if (bridge_->open()) ++ return false; ++ ++ sinkPad = bridge_->entity()->getPadByIndex(0); ++ if (!sinkPad || sinkPad->links().empty()) ++ return false; ++ ++ remotePad = sinkPad->links().at(0)->source(); ++ } ++ ++ /* Select the sensor which is not the TPG */ ++ if (remotePad->entity()->name().compare("dcmipp_tpg")) ++ break; ++ } ++ ++ /* Open the sensor subdev */ ++ data->sensor_ = std::make_unique(remotePad->entity()); ++ if (data->sensor_->init()) ++ return -ENODEV; ++ ++ data->input_ = V4L2Subdevice::fromEntityName(media_, "dcmipp_input"); ++ if (data->input_->open() < 0) ++ return -ENODEV; ++ ++ /* Initialize all path */ ++ if (!dumpPath_.init(media_)) ++ return -ENODEV; ++ dumpPath_.bufferReady().connect(this, &PipelineHandlerDcmipp::bufferReady); ++ ++ if (!mainPath_.init(media_)) ++ return -ENODEV; ++ mainPath_.bufferReady().connect(this, &PipelineHandlerDcmipp::bufferReady); ++ ++ if (!auxPath_.init(media_)) ++ return -ENODEV; ++ auxPath_.bufferReady().connect(this, &PipelineHandlerDcmipp::bufferReady); ++ ++ /* Initialize ISP video devices */ ++ params_ = V4L2VideoDevice::fromEntityName(media_, "dcmipp_main_isp_params_output"); ++ if (params_->open()) ++ return -ENODEV; ++ params_->bufferReady.connect(this, &PipelineHandlerDcmipp::paramsReady); ++ ++ stats_ = V4L2VideoDevice::fromEntityName(media_, "dcmipp_main_isp_stat_capture"); ++ if (stats_->open()) ++ return -ENODEV; ++ stats_->bufferReady.connect(this, &PipelineHandlerDcmipp::statsReady); ++ ++ /* Initialize the camera properties. */ ++ data->properties_ = data->sensor_->properties(); ++ ++ return 0; ++} ++ ++PipelineHandlerDcmipp *DcmippCameraData::pipe() ++{ ++ return static_cast(Camera::Private::pipe()); ++} ++ ++void PipelineHandlerDcmipp::tryCompleteRequest(DcmippFrameInfo *info) ++{ ++ DcmippCameraData *data = cameraData(activeCamera_); ++ Request *request = info->request; ++ ++ LOG(DCMIPP, Debug) << "Function: " << __func__; ++ ++ if (request->hasPendingBuffers()) ++ return; ++ ++ if (info->statsBuffer && !info->metadataProcessed) ++ return; ++ ++ data->frameInfo_.destroy(info->frame); ++ ++ completeRequest(request); ++} ++ ++void PipelineHandlerDcmipp::bufferReady(FrameBuffer *buffer) ++{ ++ ASSERT(activeCamera_); ++ DcmippCameraData *data = cameraData(activeCamera_); ++ Request *request = buffer->request(); ++ ++ LOG(DCMIPP, Debug) << "Function: " << __func__; ++ ++ DcmippFrameInfo *info = data->frameInfo_.find(buffer); ++ if (!info) ++ return; ++ ++ /* If the buffer is cancelled force a complete of the whole request. */ ++ if (buffer->metadata().status == FrameMetadata::FrameCancelled) { ++ for (auto &it : request->buffers()) { ++ FrameBuffer *b = it.second; ++ b->_d()->cancel(); ++ completeBuffer(request, b); ++ } ++ ++ info->metadataProcessed = true; ++ tryCompleteRequest(info); ++ ++ return; ++ } ++ ++ /* Record the sensor's timestamp in the request metadata. */ ++ request->metadata().set(controls::SensorTimestamp, ++ buffer->metadata().timestamp); ++ ++ completeBuffer(request, buffer); ++ tryCompleteRequest(info); ++} ++ ++void PipelineHandlerDcmipp::statsReady(FrameBuffer *buffer) ++{ ++ ASSERT(activeCamera_); ++ DcmippCameraData *data = cameraData(activeCamera_); ++ ++ DcmippFrameInfo *info = data->frameInfo_.find(buffer); ++ if (!info) { ++ LOG(DCMIPP, Error) << "Stats buffer not found"; ++ return; ++ } ++ ++ LOG(DCMIPP, Debug) << "Function: " << __func__; ++ ++ /* Sends the stats buffer to the IPA for analysis */ ++ data->ipa_->processStatsBuffer(info->frame, buffer->cookie()); ++} ++ ++void PipelineHandlerDcmipp::paramsReady(FrameBuffer *buffer) ++{ ++ LOG(DCMIPP, Debug) << "Function: " << __func__; ++ ++ /* Push this buffer into the available buffer list */ ++ available_isp_params_.push(buffer); ++} ++ ++void DcmippCameraData::paramsFilled(unsigned int id) ++{ ++ PipelineHandlerDcmipp *pipe = ++ static_cast(Camera::Private::pipe()); ++ ++ LOG(DCMIPP, Debug) << "Function: " << __func__ << " buffer cookie: " << id; ++ ++ /* Search the buffer and queue it back */ ++ for (std::unique_ptr &buffer : pipe->isp_params_) { ++ if (buffer.get()->cookie() == id) { ++ if (pipe->params_->queueBuffer(buffer.get()) < 0) ++ LOG(DCMIPP, Error) << "Could not queue back stats buffer"; ++ return; ++ } ++ } ++ ++ LOG(DCMIPP, Error) << "Could not find params buffer"; ++} ++ ++void DcmippCameraData::statsFreed(unsigned int id) ++{ ++ PipelineHandlerDcmipp *pipe = ++ static_cast(Camera::Private::pipe()); ++ ++ LOG(DCMIPP, Debug) << "Function: " << __func__ << " buffer cookie: " << id; ++ ++ /* Search the buffer and queue it back */ ++ for (std::unique_ptr &buffer : pipe->isp_stats_) { ++ if (buffer.get()->cookie() == id) { ++ pipe->available_isp_stats_.push(buffer.get()); ++ return; ++ } ++ } ++ ++ LOG(DCMIPP, Error) << "Could not find stats buffer"; ++} ++ ++void DcmippCameraData::metadataReady(unsigned int frame, const ControlList &metadata) ++{ ++ LOG(DCMIPP, Debug) << "Function: " << __func__ << " frame: " << frame; ++ ++ DcmippFrameInfo *info = frameInfo_.find(frame); ++ if (!info) ++ return; ++ ++ info->request->metadata().merge(metadata); ++ info->metadataProcessed = true; ++ ++ pipe()->tryCompleteRequest(info); ++} ++ ++void DcmippCameraData::setSensorControls([[maybe_unused]] unsigned int id, ++ const ControlList &sensorControls) ++{ ++ LOG(DCMIPP, Debug) << "Function: " << __func__; ++ ++ ControlList ctrls = sensorControls; ++ ++ int ret = sensor_->setControls(&ctrls); ++ if (ret) ++ LOG(DCMIPP, Error) << "Failed to set sensor controls: " << ret; ++} ++ ++void DcmippCameraData::setIspControls([[maybe_unused]] unsigned int id, ++ const ControlList &ispControls) ++{ ++ PipelineHandlerDcmipp *pipe = ++ static_cast(Camera::Private::pipe()); ++ ++ LOG(DCMIPP, Debug) << "Function: " << __func__; ++ ++ ControlList ctrls = ispControls; ++ ++ int ret = pipe->stats_->setControls(&ctrls); ++ if (ret) ++ LOG(DCMIPP, Error) << "Failed to set ISP controls: " << ret; ++} ++ ++void DcmippCameraData::setPostprocControls([[maybe_unused]] unsigned int id, ++ const ControlList &postprocControls) ++{ ++ PipelineHandlerDcmipp *pipe = ++ static_cast(Camera::Private::pipe()); ++ ++ LOG(DCMIPP, Debug) << "Function: " << __func__; ++ ++ int ret = pipe->mainPath_.setControls(postprocControls); ++ if (ret) ++ LOG(DCMIPP, Error) << "Failed to set main postproc controls: " << ret; ++ ++ ret = pipe->auxPath_.setControls(postprocControls); ++ if (ret) ++ LOG(DCMIPP, Error) << "Failed to set aux postproc controls: " << ret; ++} ++ ++ ++REGISTER_PIPELINE_HANDLER(PipelineHandlerDcmipp, "dcmipp") ++ ++} /* namespace libcamera */ +diff --git a/src/libcamera/pipeline/dcmipp/dcmipp.h b/src/libcamera/pipeline/dcmipp/dcmipp.h +new file mode 100644 +index 00000000..eaa668f3 +--- /dev/null ++++ b/src/libcamera/pipeline/dcmipp/dcmipp.h +@@ -0,0 +1,120 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024, ST Microelectronics ++ * ++ * dcmipp.h - STM32 DCMIPP common definitions ++ */ ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include "libcamera/internal/media_object.h" ++#include "libcamera/internal/v4l2_videodevice.h" ++ ++namespace libcamera { ++ ++class CameraSensor; ++class MediaDevice; ++class V4L2Subdevice; ++struct StreamConfiguration; ++struct V4L2SubdeviceFormat; ++ ++class DcmippPath ++{ ++public: ++ DcmippPath(const char *name); ++ DcmippPath(const char *name, const Span &formats); ++ ++ bool init(MediaDevice *media); ++ ++ int setEnabled(bool enable); ++ bool isEnabled() const { return enabled_; } ++ int setControls(const ControlList &postprocControls); ++ ++ StreamConfiguration generateConfiguration(const CameraSensor *sensor, ++ const Size &maxSize, ++ PixelFormat rolePixelFormat); ++ CameraConfiguration::Status validate(const CameraSensor *sensor, ++ StreamConfiguration *cfg, ++ unsigned int isp_decimation_ratio); ++ ++ int configure(const StreamConfiguration &config, ++ const V4L2SubdeviceFormat &inputFormat, ++ unsigned int isp_decimation_ratio); ++ ++ int exportBuffers(unsigned int bufferCount, ++ std::vector> *buffers) ++ { ++ return video_->exportBuffers(bufferCount, buffers); ++ } ++ ++ int start(); ++ void stop(); ++ ++ int queueBuffer(FrameBuffer *buffer) { return video_->queueBuffer(buffer); } ++ Signal &bufferReady() { return video_->bufferReady; } ++ ++private: ++ void populateFormats(); ++ ++ static constexpr unsigned int DCMIPP_BUFFER_COUNT = 4; ++ ++ const char *name_; ++ bool enabled_; ++ bool running_; ++ ++ const Span pathFormats_; ++ ++ std::unique_ptr input_; ++ std::unique_ptr isp_; ++ std::unique_ptr postproc_; ++ std::unique_ptr video_; ++ ++ unsigned int input_source_pad_; ++ ++ MediaLink *link_; ++ unsigned int bufferCount_; ++}; ++ ++class DcmippDumpPath : public DcmippPath ++{ ++public: ++ DcmippDumpPath(); ++}; ++ ++class DcmippMainPath : public DcmippPath ++{ ++public: ++ DcmippMainPath(); ++}; ++ ++class DcmippAuxPath : public DcmippPath ++{ ++public: ++ DcmippAuxPath(); ++}; ++ ++} /* namespace libcamera */ ++ ++#define DCMIPP_V4L2_BUFFER_NB 4 ++ ++#define DCMIPP_RAW_MAX_WIDTH 2688 ++ ++/* ++ * DCMIPP driver does not output statistics frames from the very beginning. ++ * This depends on the profile being selected via the STAT_PROFILE control, ++ * however in order to keep the pipeline handler code simpler, set the longest ++ * delay here, which leads to libcamera not trying to get statistics from the ++ * DCMIPP driver during the first requests ++ */ ++#define DCMIPP_STATS_FRAME_DELAY 10 +diff --git a/src/libcamera/pipeline/dcmipp/dcmipp_path.cpp b/src/libcamera/pipeline/dcmipp/dcmipp_path.cpp +new file mode 100644 +index 00000000..7f053792 +--- /dev/null ++++ b/src/libcamera/pipeline/dcmipp/dcmipp_path.cpp +@@ -0,0 +1,496 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* ++ * Copyright (C) 2024, ST Microelectronics ++ * ++ * dcmipp_path.cpp - STM32 DCMIPP path helper ++ * Based on rkisp1_path.cpp ++ */ ++ ++#include "dcmipp.h" ++ ++#include ++ ++#include ++#include ++ ++#include "libcamera/internal/camera_sensor.h" ++#include "libcamera/internal/media_device.h" ++#include "libcamera/internal/v4l2_subdevice.h" ++#include "libcamera/internal/v4l2_videodevice.h" ++ ++namespace libcamera { ++ ++LOG_DECLARE_CATEGORY(DCMIPP) ++ ++namespace { ++ ++constexpr std::array pixelformats_dump{ ++ formats::RGB565, ++ formats::YUYV, ++ formats::YVYU, ++ formats::UYVY, ++ formats::VYUY, ++ formats::R8, ++ formats::SBGGR8, ++ formats::SGBRG8, ++ formats::SGRBG8, ++ formats::SRGGB8, ++ formats::SBGGR10, ++ formats::SGBRG10, ++ formats::SGRBG10, ++ formats::SRGGB10, ++ formats::SBGGR12, ++ formats::SGBRG12, ++ formats::SGRBG12, ++ formats::SRGGB12, ++ formats::SBGGR14, ++ formats::SGBRG14, ++ formats::SGRBG14, ++ formats::SRGGB14, ++ formats::SBGGR16, ++ formats::SGBRG16, ++ formats::SGRBG16, ++ formats::SRGGB16, ++ formats::MJPEG, ++}; ++ ++constexpr std::array pixelformats_main{ ++ formats::RGB565, ++ formats::YUYV, ++ formats::YVYU, ++ formats::UYVY, ++ formats::VYUY, ++ formats::R8, ++ formats::RGB888, ++ formats::BGR888, ++ formats::NV12, ++ formats::NV21, ++ formats::NV16, ++ formats::NV61, ++ formats::YUV420, ++ formats::YVU420, ++}; ++ ++/* ++ * Since we only concentrate on RAW input and AUX doesn't have ++ * RGB to YUV conversion, AUX will only output RGB formats ++ * until we also add support for non-raw sensors. ++ */ ++constexpr std::array pixelformats_aux{ ++ formats::RGB565, ++ formats::RGB888, ++ formats::BGR888, ++}; ++ ++const std::map formatToMediaBus = { ++ { formats::RGB565, MEDIA_BUS_FMT_RGB888_1X24 }, ++ { formats::YUYV, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::YVYU, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::UYVY, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::VYUY, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::R8, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::RGB888, MEDIA_BUS_FMT_RGB888_1X24 }, ++ { formats::BGR888, MEDIA_BUS_FMT_RGB888_1X24 }, ++ { formats::ARGB8888, MEDIA_BUS_FMT_RGB888_1X24 }, ++ { formats::AVUY8888, MEDIA_BUS_FMT_RGB888_1X24 }, ++ { formats::NV12, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::NV21, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::NV16, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::NV61, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::YUV420, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::YVU420, MEDIA_BUS_FMT_YUV8_1X24 }, ++ { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 }, ++ { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 }, ++ { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 }, ++ { formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 }, ++ { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 }, ++ { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 }, ++ { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 }, ++ { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 }, ++ { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 }, ++ { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 }, ++ { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 }, ++ { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 }, ++ { formats::SBGGR14, MEDIA_BUS_FMT_SBGGR14_1X14 }, ++ { formats::SGBRG14, MEDIA_BUS_FMT_SGBRG14_1X14 }, ++ { formats::SGRBG14, MEDIA_BUS_FMT_SGRBG14_1X14 }, ++ { formats::SRGGB14, MEDIA_BUS_FMT_SRGGB14_1X14 }, ++ { formats::SBGGR16, MEDIA_BUS_FMT_SBGGR16_1X16 }, ++ { formats::SGBRG16, MEDIA_BUS_FMT_SGBRG16_1X16 }, ++ { formats::SGRBG16, MEDIA_BUS_FMT_SGRBG16_1X16 }, ++ { formats::SRGGB16, MEDIA_BUS_FMT_SRGGB16_1X16 }, ++ { formats::MJPEG, MEDIA_BUS_FMT_JPEG_1X8 }, ++}; ++ ++} /* namespace */ ++ ++DcmippPath::DcmippPath(const char *name) ++ : name_(name), enabled_(false), running_(false), input_source_pad_(1), link_(nullptr) ++{ ++} ++ ++DcmippPath::DcmippPath(const char *name, const Span &formats) ++ : name_(name), enabled_(false), running_(false), pathFormats_(formats), input_source_pad_(1), link_(nullptr) ++{ ++} ++ ++int DcmippPath::setEnabled(bool enable) ++{ ++ enabled_ = enable; ++ ++ if (link_->flags() & MEDIA_LNK_FL_IMMUTABLE) ++ return 0; ++ ++ return link_->setEnabled(enable); ++} ++ ++bool DcmippPath::init(MediaDevice *media) ++{ ++ std::string postproc = std::string("dcmipp_") + name_ + "_postproc"; ++ std::string video = std::string("dcmipp_") + name_ + "_capture"; ++ ++ LOG(DCMIPP, Debug) << "DcmippPath::init " << name_; ++ ++ input_ = V4L2Subdevice::fromEntityName(media, "dcmipp_input"); ++ if (input_->open() < 0) ++ return false; ++ ++ postproc_ = V4L2Subdevice::fromEntityName(media, postproc); ++ if (postproc_->open() < 0) ++ return false; ++ ++ video_ = V4L2VideoDevice::fromEntityName(media, video); ++ if (video_->open() < 0) ++ return false; ++ ++ if (name_ == std::string("dump")) { ++ link_ = media->link("dcmipp_input", 1, "dcmipp_dump_postproc", 0); ++ if (!link_) ++ return false; ++ input_source_pad_ = 1; ++ } else if (name_ == std::string("main")) { ++ isp_ = V4L2Subdevice::fromEntityName(media, "dcmipp_main_isp"); ++ if (isp_->open() < 0) ++ return false; ++ link_ = media->link("dcmipp_input", 2, "dcmipp_main_isp", 0); ++ if (!link_) ++ return false; ++ input_source_pad_ = 2; ++ } else if (name_ == std::string("aux")) { ++ link_ = media->link("dcmipp_main_isp", 1, "dcmipp_aux_postproc", 0); ++ if (!link_) ++ return false; ++ input_source_pad_ = 3; ++ } ++ ++ return true; ++} ++ ++/* ++ * This function is in charge of ++ * - list up all configuration that could be achieved by a path, taking into ++ * consideration sensor format, decimate of the ISP block, decimate / downsize ++ * of the postproc block and color conversion of the main pipe then available ++ * formats of the pixel cap video device ++ * - set a default configuration depending on the role, that matches the above ++ * constraint as well ++ * - as far as the Dump pipe is concerned, this is a matter of checking the ++ * sensor pipe & and bytecap available formats ++ * - we should not care about role at the path level, the decision being done ++ * at upper level ++ */ ++StreamConfiguration ++DcmippPath::generateConfiguration(const CameraSensor *sensor, const Size &maxSize, ++ PixelFormat rolePixelFormat) ++{ ++ const std::vector &mbusCodes = sensor->mbusCodes(); ++ Size resolution = sensor->resolution(); ++ Size minSize; ++ ++ LOG(DCMIPP, Debug) << "DcmippPath::generateConfiguration " << name_; ++ ++ /* ++ * For Main & Aux pipe postproc can perform decimate-downsize hence 8 * 8 reduction ++ * ++ * ISP decimation can also perform a maximum of 8 reduction. It is also mandatory ++ * to ensure that RAW frame size does not exceed 2688 width prior demosaicing. ++ */ ++ if (name_ != std::string("dump")) { ++ /* Ensure to not exceed maximum width for demosaicing */ ++ while (resolution.width > DCMIPP_RAW_MAX_WIDTH) ++ resolution /= 2; ++ ++ minSize = sensor->resolution() / (8 * 8 * 8); ++ minSize.expandTo({ 16, minSize.height }); ++ } ++ ++ std::map> formats; ++ ++ for (const auto &format : pathFormats_) { ++ const PixelFormatInfo &info = PixelFormatInfo::info(format); ++ ++ if (name_ != std::string("dump")) { ++ /* Handling of Pixel pipes (Main / Aux) */ ++ std::vector sizesRange{ ++ SizeRange{ minSize, resolution } ++ }; ++ formats[format] = std::move(sizesRange); ++ continue; ++ } else if (name_ == std::string("dump") && ++ info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) { ++ /* Handling of Byte pipe (Dump) exclusively for RAW formats */ ++ uint32_t mbusCode = formatToMediaBus.at(format); ++ if (std::find(mbusCodes.begin(), mbusCodes.end(), mbusCode) == ++ mbusCodes.end()) ++ /* Skip formats not supported by sensor. */ ++ continue; ++ else ++ rolePixelFormat = format; ++ ++ /* ++ * \todo DUMP pipe is also able to perform byte based reduction ++ * by skipping lines or bytes ++ * */ ++ formats[format] = { resolution, resolution }; ++ } ++ } ++ ++ StreamConfiguration cfg(formats); ++ cfg.size = maxSize; ++ cfg.pixelFormat = rolePixelFormat; ++ cfg.bufferCount = DCMIPP_V4L2_BUFFER_NB; ++ ++ return cfg; ++} ++ ++CameraConfiguration::Status DcmippPath::validate(const CameraSensor *sensor, ++ StreamConfiguration *cfg, ++ unsigned int isp_decimation_ratio) ++{ ++ bool format_found = false; ++ ++ LOG(DCMIPP, Debug) << "DcmippPath::validate " << name_; ++ ++ /* Check that configured size can be achieved */ ++ Size post_isp_resolution = sensor->resolution(); ++ ++ /* ++ * For Main & Aux pipe postproc can perform decimate-downsize hence 8 * 8 reduction ++ * ++ * ISP decimation can also perform a maximum of 8 reduction. It is also mandatory ++ * to ensure that RAW frame size does not exceed 2688 width prior demosaicing. ++ */ ++ if (name_ != std::string("dump")) { ++ /* ++ * Ensure that the isp output resolution is still larger or equal to ++ * requested size ++ */ ++ post_isp_resolution /= isp_decimation_ratio; ++ ++ if (post_isp_resolution.width < cfg->size.width || ++ post_isp_resolution.height < cfg->size.height) { ++ LOG(DCMIPP, Error) << "Can't reach requested resolution of " << cfg->size << " since ISP output resolution is " << post_isp_resolution; ++ return CameraConfiguration::Invalid; ++ } ++ } ++ ++ for (const auto &format : pathFormats_) { ++ if (format == cfg->pixelFormat) { ++ format_found = true; ++ break; ++ } ++ } ++ ++ if (!format_found) ++ return CameraConfiguration::Invalid; ++ ++ V4L2DeviceFormat format; ++ format.fourcc = video_->toV4L2PixelFormat(cfg->pixelFormat); ++ format.size = cfg->size; ++ ++ int ret = video_->tryFormat(&format); ++ if (ret) ++ return CameraConfiguration::Invalid; ++ ++ cfg->stride = format.planes[0].bpl; ++ cfg->frameSize = format.planes[0].size; ++ ++ return CameraConfiguration::Valid; ++} ++ ++int DcmippPath::configure(const StreamConfiguration &config, ++ const V4L2SubdeviceFormat &inputFormat, ++ unsigned int isp_decimation_ratio) ++{ ++ int ret; ++ V4L2SubdeviceFormat subformat = inputFormat; ++ ++ LOG(DCMIPP, Debug) << "DcmippPath::configure " << name_; ++ LOG(DCMIPP, Debug) << "Configuration: Size: " << config.size << " pixelFormat: " << config.pixelFormat << " bufferCount: " << config.bufferCount; ++ ++ ret = input_->setFormat(input_source_pad_, &subformat); ++ if (ret < 0) ++ return ret; ++ ++ if (name_ == std::string("dump")) { ++ /* Configuration of the postproc block */ ++ ret = postproc_->setFormat(0, &subformat); ++ if (ret) ++ return ret; ++ ret = postproc_->setFormat(1, &subformat); ++ if (ret) ++ return ret; ++ LOG(DCMIPP, Debug) ++ << "Configured pipe " << name_ << " with format " << subformat; ++ } else if (name_ == std::string("main")) { ++ /* Configuration of the ISP block */ ++ ret = isp_->setFormat(0, &subformat); ++ if (ret) ++ return ret; ++ ++ subformat.code = MEDIA_BUS_FMT_RGB888_1X24; ++ subformat.size /= isp_decimation_ratio; ++ Rectangle compose_isp{ 0, 0, subformat.size }; ++ ret = isp_->setSelection(0, V4L2_SEL_TGT_COMPOSE, &compose_isp); ++ if (ret) ++ return ret; ++ ++ LOG(DCMIPP, Debug) ++ << "Configured pipe " << name_ << " with ISP output format " ++ << subformat; ++ ++ /* Configuration of the postproc block */ ++ ret = postproc_->setFormat(0, &subformat); ++ if (ret) ++ return ret; ++ ++ /* This should not happen since config.pixelFormat has already been validated */ ++ if (formatToMediaBus.find(config.pixelFormat) == formatToMediaBus.end()) ++ return -EINVAL; ++ ++ subformat.code = formatToMediaBus.find(config.pixelFormat)->second; ++ ret = postproc_->setFormat(1, &subformat); ++ if (ret) ++ return ret; ++ ++ Rectangle compose{ 0, 0, config.size }; ++ ret = postproc_->setSelection(0, V4L2_SEL_TGT_COMPOSE, &compose); ++ if (ret) ++ return ret; ++ ++ LOG(DCMIPP, Debug) ++ << "Configured pipe " << name_ << " with format input format " ++ << inputFormat << " and output format " << subformat; ++ } else if (name_ == std::string("aux")) { ++ subformat.code = MEDIA_BUS_FMT_RGB888_1X24; ++ subformat.size /= isp_decimation_ratio; ++ ++ /* Configuration of the postproc block */ ++ ret = postproc_->setFormat(0, &subformat); ++ if (ret) ++ return ret; ++ Rectangle compose{ 0, 0, config.size }; ++ ret = postproc_->setSelection(0, V4L2_SEL_TGT_COMPOSE, &compose); ++ if (ret) ++ return ret; ++ ++ /* This should not happen since config.pixelFormat has already been validated */ ++ if (formatToMediaBus.find(config.pixelFormat) == formatToMediaBus.end()) ++ return -EINVAL; ++ ++ subformat.code = formatToMediaBus.find(config.pixelFormat)->second; ++ ret = postproc_->setFormat(1, &subformat); ++ if (ret) ++ return ret; ++ ++ LOG(DCMIPP, Debug) ++ << "Configured pipe " << name_ << " with output format " ++ << subformat; ++ } ++ ++ const PixelFormatInfo &info = PixelFormatInfo::info(config.pixelFormat); ++ V4L2DeviceFormat outputFormat; ++ outputFormat.fourcc = video_->toV4L2PixelFormat(config.pixelFormat); ++ outputFormat.size = config.size; ++ outputFormat.planesCount = info.numPlanes(); ++ ++ ret = video_->setFormat(&outputFormat); ++ if (ret) ++ return ret; ++ ++ bufferCount_ = config.bufferCount; ++ ++ return 0; ++} ++ ++int DcmippPath::setControls(const ControlList &postprocControls) ++{ ++ LOG(DCMIPP, Debug) << "Function: " << __func__; ++ ++ ControlList ctrls = postprocControls; ++ ++ int ret = postproc_->setControls(&ctrls); ++ if (ret) ++ LOG(DCMIPP, Error) << "Failed to set postproc controls: " << ret; ++ ++ return ret; ++} ++ ++int DcmippPath::start() ++{ ++ int ret; ++ ++ LOG(DCMIPP, Debug) << "DcmippPath::start " << name_; ++ ++ if (running_) ++ return -EBUSY; ++ ++ ret = video_->importBuffers(bufferCount_); ++ if (ret) ++ return ret; ++ ++ ret = video_->streamOn(); ++ if (ret) { ++ LOG(DCMIPP, Error) ++ << "Failed to start " << name_ << " path"; ++ ++ video_->releaseBuffers(); ++ return ret; ++ } ++ ++ running_ = true; ++ ++ return 0; ++} ++ ++void DcmippPath::stop() ++{ ++ LOG(DCMIPP, Debug) << "DcmippPath::stop " << name_; ++ ++ if (!running_) ++ return; ++ ++ if (video_->streamOff()) ++ LOG(DCMIPP, Warning) << "Failed to stop " << name_ << " path"; ++ ++ video_->releaseBuffers(); ++ ++ running_ = false; ++} ++ ++DcmippDumpPath::DcmippDumpPath() ++ : DcmippPath("dump", pixelformats_dump) ++{ ++} ++ ++DcmippMainPath::DcmippMainPath() ++ : DcmippPath("main", pixelformats_main) ++{ ++} ++ ++DcmippAuxPath::DcmippAuxPath() ++ : DcmippPath("aux", pixelformats_aux) ++{ ++} ++ ++} /* namespace libcamera */ +diff --git a/src/libcamera/pipeline/dcmipp/meson.build b/src/libcamera/pipeline/dcmipp/meson.build +new file mode 100644 +index 00000000..4f1555d9 +--- /dev/null ++++ b/src/libcamera/pipeline/dcmipp/meson.build +@@ -0,0 +1,5 @@ ++# SPDX-License-Identifier: CC0-1.0 ++ ++libcamera_sources += files([ ++ 'dcmipp.cpp', 'dcmipp_path.cpp' ++]) +diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp +index 4a2048cf..3131243a 100644 +--- a/src/libcamera/v4l2_device.cpp ++++ b/src/libcamera/v4l2_device.cpp +@@ -334,7 +334,13 @@ int V4L2Device::setControls(ControlList *ctrls) + + default: + /* \todo To be changed to support strings. */ +- v4l2Ctrl.value = value.get(); ++ if (value.isArray()) { ++ Span data = value.data(); ++ v4l2Ctrl.p_u32 = reinterpret_cast(data.data()); ++ v4l2Ctrl.size = data.size(); ++ } else { ++ v4l2Ctrl.value = value.get(); ++ } + break; + } + } +@@ -553,9 +559,13 @@ std::optional V4L2Device::v4l2ControlInfo(const v4l2_query_ext_ctrl + return v4l2MenuControlInfo(ctrl); + + default: +- return ControlInfo(static_cast(ctrl.minimum), +- static_cast(ctrl.maximum), +- static_cast(ctrl.default_value)); ++ if (ctrl.type >= V4L2_CTRL_COMPOUND_TYPES) ++ /* ControlInfo "None" for such control */ ++ return ControlInfo(); ++ else ++ return ControlInfo(static_cast(ctrl.minimum), ++ static_cast(ctrl.maximum), ++ static_cast(ctrl.default_value)); + } + } + +@@ -627,6 +637,8 @@ void V4L2Device::listControls() + break; + /* \todo Support other control types. */ + default: ++ if (ctrl.type >= V4L2_CTRL_COMPOUND_TYPES) ++ break; + LOG(V4L2, Debug) + << "Control " << utils::hex(ctrl.id) + << " has unsupported type " << ctrl.type; +-- +2.43.0 + diff --git a/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-media_device-Add-bool-return-type-to-unlock.patch b/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-media_device-Add-bool-return-type-to-unlock.patch new file mode 100644 index 000000000..12f034eff --- /dev/null +++ b/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-media_device-Add-bool-return-type-to-unlock.patch @@ -0,0 +1,59 @@ +From 6914c4fd3d53c0c6ea304123bf57429bb64ec16f Mon Sep 17 00:00:00 2001 +From: Khem Raj +Date: Wed, 31 Jan 2024 21:01:27 -0800 +Subject: [PATCH 1/2] media_device: Add bool return type to unlock() + +unlock uses lockf which is marked with __attribute__ +((warn_unused_result)) and compilers warn about it and some treat +-Wunused-result as error with -Werror turned on, It would be good to +check if lockf failed or succeeded, however, that piece is not changed +with this, this fixes build with clang++ 18 + + ../git/src/libcamera/media_device.cpp:167:2: error: ignoring return value of function declared with 'warn_unused_result' attribute [-Werror,-Wunused-result] + 167 | lockf(fd_.get(), F_ULOCK, 0); + | ^~~~~ ~~~~~~~~~~~~~~~~~~~~~ + 1 error generated. + +Upstream-Status: Submitted [https://lists.libcamera.org/pipermail/libcamera-devel/2024-February/040380.html] +Signed-off-by: Khem Raj +--- + include/libcamera/internal/media_device.h | 2 +- + src/libcamera/media_device.cpp | 6 +++--- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h +index eb8cfde4..b09dfd16 100644 +--- a/include/libcamera/internal/media_device.h ++++ b/include/libcamera/internal/media_device.h +@@ -33,7 +33,7 @@ public: + bool busy() const { return acquired_; } + + bool lock(); +- void unlock(); ++ bool unlock(); + + int populate(); + bool isValid() const { return valid_; } +diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp +index 2949816b..eaa2fdb0 100644 +--- a/src/libcamera/media_device.cpp ++++ b/src/libcamera/media_device.cpp +@@ -159,12 +159,12 @@ bool MediaDevice::lock() + * + * \sa lock() + */ +-void MediaDevice::unlock() ++bool MediaDevice::unlock() + { + if (!fd_.isValid()) +- return; ++ return false; + +- lockf(fd_.get(), F_ULOCK, 0); ++ return lockf(fd_.get(), F_ULOCK, 0) == 0; + } + + /** +-- +2.43.0 + diff --git a/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-rpi-Use-alloca-instead-of-variable-length-arrays.patch b/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-rpi-Use-alloca-instead-of-variable-length-arrays.patch new file mode 100644 index 000000000..c336e9254 --- /dev/null +++ b/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0001-rpi-Use-alloca-instead-of-variable-length-arrays.patch @@ -0,0 +1,43 @@ +From 11cc6dbd45f0880beea64cdc514f57484b90bc39 Mon Sep 17 00:00:00 2001 +From: Khem Raj +Date: Tue, 20 Feb 2024 18:44:23 -0800 +Subject: [PATCH] rpi: Use malloc instead of variable length arrays + +Clang-18+ diagnoses this as error + +| ../git/src/ipa/rpi/controller/rpi/alsc.cpp:499:10: error: variable length arrays in C++ are a Clang extension [-Werror,-Wvla-cxx-extension] | 499 | int xLo[X], xHi[X]; +| | ^ + +Upstream-Status: Submitted [https://lists.libcamera.org/pipermail/libcamera-devel/2024-February/040529.html] +Signed-off-by: Khem Raj + +s +--- + src/ipa/rpi/controller/rpi/alsc.cpp | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/src/ipa/rpi/controller/rpi/alsc.cpp b/src/ipa/rpi/controller/rpi/alsc.cpp +index 8a205c60..a7d42614 100644 +--- a/src/ipa/rpi/controller/rpi/alsc.cpp ++++ b/src/ipa/rpi/controller/rpi/alsc.cpp +@@ -496,8 +496,8 @@ void resampleCalTable(const Array2D &calTableIn, + * Precalculate and cache the x sampling locations and phases to save + * recomputing them on every row. + */ +- int xLo[X], xHi[X]; +- double xf[X]; ++ int *xLo = (int*)malloc(X), *xHi = (int*)malloc(X); ++ double *xf = (double*)malloc(X); + double scaleX = cameraMode.sensorWidth / + (cameraMode.width * cameraMode.scaleX); + double xOff = cameraMode.cropX / (double)cameraMode.sensorWidth; +@@ -539,6 +539,9 @@ void resampleCalTable(const Array2D &calTableIn, + *(out++) = above * (1 - yf) + below * yf; + } + } ++ free(xf); ++ free(xHi); ++ free(xLo); + } + + /* Calculate chrominance statistics (R/G and B/G) for each region. */ diff --git a/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0002-options-Replace-use-of-VLAs-in-C.patch b/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0002-options-Replace-use-of-VLAs-in-C.patch new file mode 100644 index 000000000..473820653 --- /dev/null +++ b/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/files/0002-options-Replace-use-of-VLAs-in-C.patch @@ -0,0 +1,128 @@ +From 6e4736180fcaffdb06acf52fd3eb50ba5baa3d2a Mon Sep 17 00:00:00 2001 +From: Khem Raj +Date: Wed, 31 Jan 2024 21:04:28 -0800 +Subject: [PATCH] options: Replace use of VLAs in C++ + +Clang++ 18 is fussy about this with new warning checks. + + ../git/src/apps/common/options.cpp:882:20: error: variable length arrays in C++ are a Clang extension [-Werror,-Wvla-cxx-extension] + 882 | char shortOptions[optionsMap_.size() * 3 + 2]; + | ^~~~~~~~~~~~~~~~~~~~~~~~~~ + +Therefore replace using VLAs with alloca and malloc/free + +Upstream-Status: Submitted [https://lists.libcamera.org/pipermail/libcamera-devel/2024-February/040381.html] +Signed-off-by: Khem Raj +--- + src/apps/common/options.cpp | 12 ++++++++++-- + src/libcamera/ipc_unixsocket.cpp | 13 +++++++++---- + 2 files changed, 19 insertions(+), 6 deletions(-) + +diff --git a/src/apps/common/options.cpp b/src/apps/common/options.cpp +index 4f7e8691..3656f3c1 100644 +--- a/src/apps/common/options.cpp ++++ b/src/apps/common/options.cpp +@@ -879,8 +879,8 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv) + * Allocate short and long options arrays large enough to contain all + * options. + */ +- char shortOptions[optionsMap_.size() * 3 + 2]; +- struct option longOptions[optionsMap_.size() + 1]; ++ char *shortOptions = (char*)malloc(optionsMap_.size() * 3 + 2); ++ struct option *longOptions = (struct option*)malloc(sizeof(struct option) * (optionsMap_.size() + 1)); + unsigned int ids = 0; + unsigned int idl = 0; + +@@ -935,12 +935,16 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv) + std::cerr << argv[optind - 1] << std::endl; + + usage(); ++ free(shortOptions); ++ free(longOptions); + return options; + } + + const Option &option = *optionsMap_[c]; + if (!parseValue(option, optarg, &options)) { + usage(); ++ free(shortOptions); ++ free(longOptions); + return options; + } + } +@@ -949,10 +953,14 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv) + std::cerr << "Invalid non-option argument '" << argv[optind] + << "'" << std::endl; + usage(); ++ free(shortOptions); ++ free(longOptions); + return options; + } + + options.valid_ = true; ++ free(shortOptions); ++ free(longOptions); + return options; + } + +diff --git a/src/libcamera/ipc_unixsocket.cpp b/src/libcamera/ipc_unixsocket.cpp +index 1980d374..3bd861cb 100644 +--- a/src/libcamera/ipc_unixsocket.cpp ++++ b/src/libcamera/ipc_unixsocket.cpp +@@ -8,6 +8,7 @@ + #include "libcamera/internal/ipc_unixsocket.h" + + #include ++#include + #include + #include + #include +@@ -247,8 +248,8 @@ int IPCUnixSocket::sendData(const void *buffer, size_t length, + iov[0].iov_base = const_cast(buffer); + iov[0].iov_len = length; + +- char buf[CMSG_SPACE(num * sizeof(uint32_t))]; +- memset(buf, 0, sizeof(buf)); ++ char *buf = (char*)malloc(CMSG_SPACE(num * sizeof(uint32_t))); ++ memset((void*)buf, 0, sizeof(buf)); + + struct cmsghdr *cmsg = (struct cmsghdr *)buf; + cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t)); +@@ -270,9 +271,11 @@ int IPCUnixSocket::sendData(const void *buffer, size_t length, + int ret = -errno; + LOG(IPCUnixSocket, Error) + << "Failed to sendmsg: " << strerror(-ret); ++ free(buf); + return ret; + } + ++ free(buf); + return 0; + } + +@@ -283,8 +286,8 @@ int IPCUnixSocket::recvData(void *buffer, size_t length, + iov[0].iov_base = buffer; + iov[0].iov_len = length; + +- char buf[CMSG_SPACE(num * sizeof(uint32_t))]; +- memset(buf, 0, sizeof(buf)); ++ char *buf = (char*)malloc(CMSG_SPACE(num * sizeof(uint32_t))); ++ memset((void*)buf, 0, sizeof(buf)); + + struct cmsghdr *cmsg = (struct cmsghdr *)buf; + cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t)); +@@ -305,12 +308,14 @@ int IPCUnixSocket::recvData(void *buffer, size_t length, + if (ret != -EAGAIN) + LOG(IPCUnixSocket, Error) + << "Failed to recvmsg: " << strerror(-ret); ++ free(buf); + return ret; + } + + if (fds) + memcpy(fds, CMSG_DATA(cmsg), num * sizeof(uint32_t)); + ++ free(buf); + return 0; + } + diff --git a/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/libcamera-stm32mp_0.3.0.bb b/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/libcamera-stm32mp_0.3.0.bb new file mode 100644 index 000000000..889a635d5 --- /dev/null +++ b/meta-digi-arm/dynamic-layers/stm-st-stm32mp/recipes-multimedia/libcamera/libcamera-stm32mp_0.3.0.bb @@ -0,0 +1,91 @@ +SUMMARY = "Linux libcamera framework" +SECTION = "libs" + +LICENSE = "GPL-2.0-or-later & LGPL-2.1-or-later" + +LIC_FILES_CHKSUM = "\ + file://LICENSES/GPL-2.0-or-later.txt;md5=fed54355545ffd980b814dab4a3b312c \ + file://LICENSES/LGPL-2.1-or-later.txt;md5=2a4f4fd2128ea2f65047ee63fbca9f68 \ +" + +# 0.3.0 +SRC_URI = " \ + git://git.libcamera.org/libcamera/libcamera.git;protocol=https;branch=master \ + file://0001-media_device-Add-bool-return-type-to-unlock.patch \ + file://0002-options-Replace-use-of-VLAs-in-C.patch \ + file://0001-rpi-Use-alloca-instead-of-variable-length-arrays.patch \ +" +SRCREV = "aee16c06913422a0ac84ee3217f87a9795e3c2d9" + +SRC_URI += " \ + file://0001-0.3.0-stm32mp-add-dcmipp-ipa.patch \ +" +PV = "v0.3.0-stm32mp" + +PROVIDES += "libcamera" + +PE = "1" + +S = "${WORKDIR}/git" + +DEPENDS = "python3-pyyaml-native python3-jinja2-native python3-ply-native python3-jinja2-native udev gnutls chrpath-native libevent libyaml" +DEPENDS += "${@bb.utils.contains('DISTRO_FEATURES', 'qt', 'qtbase qtbase-native', '', d)}" + +PACKAGES =+ "${PN}-gst" + +PACKAGECONFIG ??= "gst python" +PACKAGECONFIG[gst] = "-Dgstreamer=enabled,-Dgstreamer=disabled,gstreamer1.0 gstreamer1.0-plugins-base" +PACKAGECONFIG[python] = "-Dpycamera=enabled,-Dpycamera=disabled,python3-pybind11" + +LIBCAMERA_PIPELINES ??= "dcmipp" +LIBCAMERA_IPAS = "dcmipp" + +EXTRA_OEMESON = " \ + -Dpipelines=${LIBCAMERA_PIPELINES} \ + -Dv4l2=true \ + -Dcam=enabled \ + -Dlc-compliance=disabled \ + -Dtest=false \ + -Ddocumentation=disabled \ +" +EXTRA_OEMESON += " \ + -Dipas=${LIBCAMERA_IPAS} \ +" + + +RDEPENDS:${PN} = "${@bb.utils.contains('DISTRO_FEATURES', 'wayland qt', 'qtwayland', '', d)}" + +inherit meson pkgconfig python3native python3-dir + +do_configure:prepend() { + sed -i -e 's|py_compile=True,||' ${S}/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py +} + +do_install:append() { + chrpath -d ${D}${libdir}/libcamera.so + chrpath -d ${D}${libexecdir}/libcamera/v4l2-compat.so +} + +do_package:append() { + bb.build.exec_func("do_package_recalculate_ipa_signatures", d) +} + +do_package_recalculate_ipa_signatures() { + local modules + for module in $(find ${PKGD}/usr/lib/libcamera -name "*.so.sign"); do + module="${module%.sign}" + if [ -f "${module}" ] ; then + modules="${modules} ${module}" + fi + done + + ${S}/src/ipa/ipa-sign-install.sh ${B}/src/ipa-priv-key.pem "${modules}" +} + +FILES:${PN} += " ${libexecdir}/libcamera/v4l2-compat.so" +FILES:${PN} += "${datadir} ${libdir} ${PYTHON_SITEPACKAGES_DIR}" +FILES:${PN}-gst = "${libdir}/gstreamer-1.0" + +# libcamera-v4l2 explicitly sets _FILE_OFFSET_BITS=32 to get access to +# both 32 and 64 bit file APIs. +GLIBC_64BIT_TIME_FLAGS = "" diff --git a/meta-digi-dey/dynamic-layers/x-linux-ai/recipes-st/packagegroups/packagegroup-dey-x-linux-ai.bb b/meta-digi-dey/dynamic-layers/x-linux-ai/recipes-st/packagegroups/packagegroup-dey-x-linux-ai.bb index a7ce9a6d4..bb5aa4bfd 100644 --- a/meta-digi-dey/dynamic-layers/x-linux-ai/recipes-st/packagegroups/packagegroup-dey-x-linux-ai.bb +++ b/meta-digi-dey/dynamic-layers/x-linux-ai/recipes-st/packagegroups/packagegroup-dey-x-linux-ai.bb @@ -8,6 +8,8 @@ inherit packagegroup python3-dir COMMON_PACKAGES = " \ stai-mpu-tools \ tim-vx-tools \ + libcamera-stm32mp \ + libcamera-stm32mp-gst \ " TFLITE_PACKAGES = " \