From 9f86a7a402318fecea8ced0447943a3fbb607214 Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Mon, 28 Jul 2025 21:45:45 +0800 Subject: [PATCH 01/14] PCI: rockchip: dw-dmatest: Fix compile warning drivers/pci/controller/dwc/pcie-dw-dmatest.c:157:5: error: no previous prototype for 'rk_pcie_local_dma_tobus_block' [-Werror=missing-prototypes] Change-Id: I616a4759d856a72b469996a3c6f07e4af90d5616 Signed-off-by: Jon Lin --- drivers/pci/controller/dwc/pcie-dw-dmatest.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-dmatest.c b/drivers/pci/controller/dwc/pcie-dw-dmatest.c index 3d5b44f4a15e..3d5145d93f6c 100644 --- a/drivers/pci/controller/dwc/pcie-dw-dmatest.c +++ b/drivers/pci/controller/dwc/pcie-dw-dmatest.c @@ -154,8 +154,8 @@ static int rk_pcie_local_dma_frombus_block(struct dma_trx_obj *obj, u32 chn, return ret; } -int rk_pcie_local_dma_tobus_block(struct dma_trx_obj *obj, u32 chn, - u64 bus_paddr, u64 local_paddr, u32 size) +static int rk_pcie_local_dma_tobus_block(struct dma_trx_obj *obj, u32 chn, + u64 bus_paddr, u64 local_paddr, u32 size) { struct pcie_dw_dmatest_dev *dmatest_dev = (struct pcie_dw_dmatest_dev *)obj->priv; struct dma_table *table; From 303b0de1b8fd9f33a8cbfa935e0885c53fe9f78c Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Wed, 16 Jul 2025 14:22:21 +0800 Subject: [PATCH 02/14] drm/bridge: analogix_dp: Add &analogix_dp_plat_data.disable_psr to check whether to disable PSR First of all, since the PSR feature can help to reduce the power consumption, the Source device, which can support PSR function, should enable PSR if the PSR capability of Sink device is detected rather than depending on the user to add 'support-psr' DTS property manually. Different platforms that use the same Analogix DP bridge driver may have different methods for parsing the PSR capability. Therefore, add a new flag &analogix_dp_plat_data.disable_psr to disable PSR forcely, which set in the platform side, should be more reasonable. If the user truly does not want to enable PSR function or the Panel has something wrong with it, the property 'rockchip,disable-psr' will be helpful. Fixes: 9622f2d0f10a ("drm/bridge: analogix_dp: disable PSR feature by default") Change-Id: Id2fce34857df80de5a1ec97f342709a6e2840ed4 Signed-off-by: Damon Ding --- drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 2 +- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 1 + include/drm/bridge/analogix_dp.h | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 7432329d9d9f..64d38350fc07 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -191,7 +191,7 @@ static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp) unsigned char psr_version; int ret; - if (!device_property_read_bool(dp->dev, "support-psr")) + if (dp->plat_data->disable_psr) return 0; ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_SUPPORT, &psr_version); diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index d511922e7619..158a1c3b148e 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -733,6 +733,7 @@ static int rockchip_dp_probe(struct platform_device *pdev) dp->plat_data.convert_to_origin_mode = drm_mode_convert_to_origin_mode; dp->plat_data.skip_connector = rockchip_dp_skip_connector(bridge); dp->plat_data.bridge = bridge; + dp->plat_data.disable_psr = device_property_read_bool(dp->dev, "rockchip,disable-psr"); ret = rockchip_dp_of_probe(dp); if (ret < 0) diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h index 35484105d7f0..00661612d4c7 100644 --- a/include/drm/bridge/analogix_dp.h +++ b/include/drm/bridge/analogix_dp.h @@ -53,6 +53,8 @@ struct analogix_dp_plat_data { bool dual_connector_split; bool left_display; + bool disable_psr; + u8 max_bpc; struct analogix_dp_device *left; From 554eda652fe6629a85fe585f910b0a75a6152ca4 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Tue, 29 Jul 2025 11:43:24 +0800 Subject: [PATCH 03/14] drm/rockchip: vop: Add errno if &vop->lut memory allocation failed in vop_create_crtc() If no errno assignment for this case, there will be a warning: drivers/gpu/drm/rockchip/rockchip_drm_vop.c:5754 vop_create_crtc() warn: missing error code 'ret' Change-Id: I9e78fe99b3ca24d8734ee286b1dc1f0908721f25 Signed-off-by: Damon Ding --- drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index 58d40e5687b2..ab5f76a21758 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -5756,8 +5756,10 @@ static int vop_create_crtc(struct vop *vop) vop->lut = devm_kmalloc_array(dev, lut_len, sizeof(*vop->lut), GFP_KERNEL); - if (!vop->lut) + if (!vop->lut) { + ret = -ENOMEM; goto err_unregister_crtc_funcs; + } if (vop_of_init_display_lut(vop)) { for (i = 0; i < lut_len; i++) { From 3b97d716d55ed3ea6e6bc241df36a1123217463e Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Sat, 12 Jul 2025 09:38:16 +0800 Subject: [PATCH 04/14] drm/rockchip: Move the init/cleanup of self refresh helper from VOP/VOP2 to eDP/RGB drivers Before this commit, the drm_self_refresh_helper_init() was called in &component_ops.bind() of VOP/VOP2 drivers. The VOP or VPs, which do not want to enable PSR functionality, will also initialize the self refresh helper. Since it wastes resources(e.g., allocating &drm_self_refresh_data and initializing &drm_self_refresh_data.entry_work), we move the init and cleanup process from bind()/unbind() of VOP/VOP2 drivers to the ones of eDP/RGB drivers, which can support PSR functionality. Change-Id: Ie7643b54f42ea3d5ab7b0cdbc77ccfdb06c614b9 Signed-off-by: Damon Ding --- .../gpu/drm/rockchip/analogix_dp-rockchip.c | 36 ++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 8 ---- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 8 ---- drivers/gpu/drm/rockchip/rockchip_rgb.c | 37 +++++++++++++++++++ 4 files changed, 73 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index 158a1c3b148e..bc9652a02024 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "rockchip_drm_drv.h" @@ -634,6 +635,38 @@ static int rockchip_dp_drm_create_encoder(struct rockchip_dp_device *dp) return 0; } +static void rockchip_dp_drm_self_refresh_helper_init(struct rockchip_dp_device *dp) +{ + struct drm_encoder *encoder = &dp->encoder.encoder; + struct drm_crtc *crtc; + int ret; + + if (!dp->plat_data.disable_psr) { + drm_for_each_crtc(crtc, encoder->dev) { + if (drm_encoder_crtc_ok(encoder, crtc)) { + ret = drm_self_refresh_helper_init(crtc); + if (ret) + dev_warn(dp->dev, + "Failed to init self refresh helper for crtc-%d\n", + drm_crtc_index(crtc)); + } + } + } +} + +static void rockchip_dp_drm_self_refresh_helper_cleanup(struct rockchip_dp_device *dp) +{ + struct drm_encoder *encoder = &dp->encoder.encoder; + struct drm_crtc *crtc; + + if (!dp->plat_data.disable_psr) { + drm_for_each_crtc(crtc, encoder->dev) { + if (drm_encoder_crtc_ok(encoder, crtc)) + drm_self_refresh_helper_cleanup(crtc); + } + } +} + static int rockchip_dp_bind(struct device *dev, struct device *master, void *data) { @@ -657,6 +690,8 @@ static int rockchip_dp_bind(struct device *dev, struct device *master, if (ret) goto err_unregister_audio_pdev; + rockchip_dp_drm_self_refresh_helper_init(dp); + return 0; err_unregister_audio_pdev: @@ -672,6 +707,7 @@ static void rockchip_dp_unbind(struct device *dev, struct device *master, if (dp->audio_pdev) platform_device_unregister(dp->audio_pdev); + rockchip_dp_drm_self_refresh_helper_cleanup(dp); analogix_dp_unbind(dp->adp); dp->encoder.encoder.funcs->destroy(&dp->encoder.encoder); } diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index ab5f76a21758..c7df6a48f10a 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -37,7 +37,6 @@ #include #include #include -#include #include #include @@ -5744,11 +5743,6 @@ static int vop_create_crtc(struct vop *vop) VOP_ATTACH_MODE_CONFIG_PROP(tv_bottom_margin_property, 100); #undef VOP_ATTACH_MODE_CONFIG_PROP vop_crtc_create_feature_property(vop, crtc); - ret = drm_self_refresh_helper_init(crtc); - if (ret) - DRM_DEV_DEBUG_KMS(vop->dev, - "Failed to init %s with SR helpers %d, ignoring\n", - crtc->name, ret); if (vop->lut_regs) { u16 *r_base, *g_base, *b_base; @@ -5802,8 +5796,6 @@ static void vop_destroy_crtc(struct vop *vop) struct drm_device *drm_dev = vop->drm_dev; struct drm_plane *plane, *tmp; - drm_self_refresh_helper_cleanup(crtc); - of_node_put(crtc->port); /* diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index ff5a9316cdbc..735f86630ac4 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #ifdef CONFIG_DRM_ANALOGIX_DP @@ -15370,12 +15369,6 @@ static int vop2_create_crtc(struct vop2 *vop2, uint8_t enabled_vp_mask) vop2_crtc_create_feature_property(vop2, crtc); vop2_crtc_create_vrr_property(vop2, crtc); - ret = drm_self_refresh_helper_init(crtc); - if (ret) - DRM_DEV_DEBUG_KMS(vop2->dev, - "Failed to init %s with SR helpers %d, ignoring\n", - crtc->name, ret); - if (vp_data->feature & (VOP_FEATURE_VIVID_HDR | VOP_FEATURE_DOVI)) vop2_crtc_create_hdr_property(vop2, crtc); if (vp_data->feature & VOP_FEATURE_POST_ACM) @@ -15463,7 +15456,6 @@ static void vop2_destroy_crtc(struct drm_crtc *crtc) { struct vop2_video_port *vp = to_vop2_video_port(crtc); - drm_self_refresh_helper_cleanup(crtc); if (vp->hdr_lut_gem_obj) rockchip_gem_free_object(&vp->hdr_lut_gem_obj->base); diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c index 7e80542e562e..b312b265428a 100644 --- a/drivers/gpu/drm/rockchip/rockchip_rgb.c +++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -914,6 +915,38 @@ static struct backlight_device *rockchip_mcu_panel_find_backlight(struct rockchi return bd; } +static void rockchip_rgb_drm_self_refresh_helper_init(struct rockchip_rgb *rgb) +{ + struct drm_encoder *encoder = &rgb->encoder; + struct drm_crtc *crtc; + int ret; + + if (rgb->np_mcu_panel && rgb->support_psr) { + drm_for_each_crtc(crtc, encoder->dev) { + if (drm_encoder_crtc_ok(encoder, crtc)) { + ret = drm_self_refresh_helper_init(crtc); + if (ret) + dev_warn(rgb->dev, + "Failed to init self refresh helper for crtc-%d\n", + drm_crtc_index(crtc)); + } + } + } +} + +static void rockchip_rgb_drm_self_refresh_helper_cleanup(struct rockchip_rgb *rgb) +{ + struct drm_encoder *encoder = &rgb->encoder; + struct drm_crtc *crtc; + + if (rgb->np_mcu_panel && rgb->support_psr) { + drm_for_each_crtc(crtc, encoder->dev) { + if (drm_encoder_crtc_ok(encoder, crtc)) + drm_self_refresh_helper_cleanup(crtc); + } + } +} + static int rockchip_rgb_bind(struct device *dev, struct device *master, void *data) { @@ -1019,6 +1052,8 @@ static int rockchip_rgb_bind(struct device *dev, struct device *master, rockchip_drm_register_sub_dev(&rgb->sub_dev); } + rockchip_rgb_drm_self_refresh_helper_init(rgb); + return 0; err_free_connector: @@ -1033,6 +1068,8 @@ static void rockchip_rgb_unbind(struct device *dev, struct device *master, { struct rockchip_rgb *rgb = dev_get_drvdata(dev); + rockchip_rgb_drm_self_refresh_helper_cleanup(rgb); + if (rgb->sub_dev.connector) rockchip_drm_unregister_sub_dev(&rgb->sub_dev); if (rgb->panel) From 0b1e03cc77f7677a8f5d837bc150a09c1b46c605 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Fri, 18 Jul 2025 15:33:32 +0800 Subject: [PATCH 05/14] arm64: dts: rockchip: Remove property support-psr of eDP nodes Since the PSR feature can help to reduce the power consumption, the Source device, which can support PSR function, should enable PSR if the PSR capability of Sink device is detected rather than depending on the user to add 'support-psr' DTS property manually. Change-Id: I2f51312621f62519f388e06561fb61f01145256b Signed-off-by: Damon Ding --- .../arm64/boot/dts/rockchip/rk3576-evb1-v10-edp-NE160QAM-NX1.dts | 1 - .../boot/dts/rockchip/rk3588-evb1-lp4-v10-edp-NE160QAM-NX1.dts | 1 - arch/arm64/boot/dts/rockchip/rk3588s-tablet-single.dtsi | 1 - arch/arm64/boot/dts/rockchip/rk3588s-tablet.dtsi | 1 - 4 files changed, 4 deletions(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10-edp-NE160QAM-NX1.dts b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10-edp-NE160QAM-NX1.dts index 60ffa8e705e4..3549687c2dde 100644 --- a/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10-edp-NE160QAM-NX1.dts +++ b/arch/arm64/boot/dts/rockchip/rk3576-evb1-v10-edp-NE160QAM-NX1.dts @@ -88,7 +88,6 @@ &edp { force-hpd; - support-psr; status = "okay"; ports { diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-lp4-v10-edp-NE160QAM-NX1.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-lp4-v10-edp-NE160QAM-NX1.dts index b548a6a3e1d3..dabc0a632e6e 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-lp4-v10-edp-NE160QAM-NX1.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-lp4-v10-edp-NE160QAM-NX1.dts @@ -75,7 +75,6 @@ &edp0 { force-hpd; - support-psr; status = "okay"; ports { diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-tablet-single.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s-tablet-single.dtsi index 033c32a5664d..003518824cd3 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-tablet-single.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588s-tablet-single.dtsi @@ -416,7 +416,6 @@ }; &edp0 { - support-psr; force-hpd; status = "okay"; diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-tablet.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s-tablet.dtsi index 41bae84dd5d4..28666f252dd0 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-tablet.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588s-tablet.dtsi @@ -363,7 +363,6 @@ }; &edp0 { - support-psr; force-hpd; status = "okay"; }; From b27d0c8a1538fe1d4ec486bfc127bd0022793a8e Mon Sep 17 00:00:00 2001 From: Yu Qiaowei Date: Thu, 24 Jul 2025 21:01:01 +0800 Subject: [PATCH 06/14] video: rockchip: rga3: fix image size cal error in BPP format Change-Id: I2e3ad31172e9ba5b013500927e016b88b69c87f5 Signed-off-by: Yu Qiaowei --- drivers/video/rockchip/rga3/rga_common.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/video/rockchip/rga3/rga_common.c b/drivers/video/rockchip/rga3/rga_common.c index 1563583ddeb8..c6066de6b8f7 100644 --- a/drivers/video/rockchip/rga3/rga_common.c +++ b/drivers/video/rockchip/rga3/rga_common.c @@ -488,9 +488,6 @@ int rga_get_pixel_stride_from_format(uint32_t format) case RGA_FORMAT_YCrCb_422_SP_10B: pixel_stride = 10; break; - case RGA_FORMAT_BPP1: - case RGA_FORMAT_BPP2: - case RGA_FORMAT_BPP4: case RGA_FORMAT_BPP8: case RGA_FORMAT_YCbCr_400: case RGA_FORMAT_A8: @@ -505,9 +502,16 @@ int rga_get_pixel_stride_from_format(uint32_t format) case RGA_FORMAT_Y8: pixel_stride = 8; break; + case RGA_FORMAT_BPP4: case RGA_FORMAT_Y4: pixel_stride = 4; break; + case RGA_FORMAT_BPP2: + pixel_stride = 2; + break; + case RGA_FORMAT_BPP1: + pixel_stride = 1; + break; default: rga_err("unknown format [0x%x]\n", format); return -1; @@ -823,14 +827,22 @@ int rga_image_size_cal(int w, int h, int format, uv = (w * h) >> 2; v = uv; break; + case RGA_FORMAT_BPP8: case RGA_FORMAT_YCbCr_400: case RGA_FORMAT_A8: case RGA_FORMAT_Y8: yrgb = w * h; break; + case RGA_FORMAT_BPP4: case RGA_FORMAT_Y4: yrgb = (w * h) >> 1; break; + case RGA_FORMAT_BPP2: + yrgb = (w * h) >> 2; + break; + case RGA_FORMAT_BPP1: + yrgb = (w * h) >> 3; + break; default: rga_err("Unsuport format [0x%x]\n", format); return -EFAULT; From 95b8ad3b834c43efa3c55c2d3b9641c5fbc69fe9 Mon Sep 17 00:00:00 2001 From: Yu Qiaowei Date: Tue, 29 Jul 2025 19:49:52 +0800 Subject: [PATCH 07/14] video: rockchip: rga3: fix error of using DMA_MAPPING_ERROR directly Change-Id: Iefacf3223404d6806c1999c96126bc25700645e8 Signed-off-by: Yu Qiaowei --- drivers/video/rockchip/rga3/rga_dma_buf.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/video/rockchip/rga3/rga_dma_buf.c b/drivers/video/rockchip/rga3/rga_dma_buf.c index 96609d0784fe..4e8ae1b12a2d 100644 --- a/drivers/video/rockchip/rga3/rga_dma_buf.c +++ b/drivers/video/rockchip/rga3/rga_dma_buf.c @@ -254,12 +254,14 @@ int rga_dma_memory_check(struct rga_dma_buffer *rga_dma_buffer, struct rga_img_i int rga_dma_map_phys_addr(phys_addr_t phys_addr, size_t size, struct rga_dma_buffer *buffer, enum dma_data_direction dir, struct device *map_dev) { + int ret; dma_addr_t addr; addr = dma_map_resource(map_dev, phys_addr, size, dir, 0); - if (addr == DMA_MAPPING_ERROR) { - rga_err("dma_map_resouce failed!\n"); - return -EINVAL; + ret = dma_mapping_error(map_dev, addr); + if (ret < 0) { + rga_err("dma_map_resouce failed!, ret = %d\n", ret); + return ret; } buffer->dma_addr = addr; From eb1d1e7885f442938b54d55603809229f56e5e29 Mon Sep 17 00:00:00 2001 From: XiaoDong Huang Date: Tue, 22 Apr 2025 19:44:29 +0800 Subject: [PATCH 08/14] dt-bindings: suspend: add RKPM_SLP_LP_AOA macro for rv1126b Signed-off-by: XiaoDong Huang Change-Id: I83b3d537c78e188232c95890fb4d316457ee07ac --- include/dt-bindings/suspend/rockchip-rv1126b.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/suspend/rockchip-rv1126b.h b/include/dt-bindings/suspend/rockchip-rv1126b.h index 013e1317eda8..3b51c78d626d 100644 --- a/include/dt-bindings/suspend/rockchip-rv1126b.h +++ b/include/dt-bindings/suspend/rockchip-rv1126b.h @@ -29,6 +29,7 @@ #define RKPM_SLP_PMU_DBG BIT(26) #define RKPM_SLP_LP_PR BIT(27) #define RKPM_SLP_ARCH_TIMER_RESET BIT(28) +#define RKPM_SLP_LP_AOA BIT(29) /* the wake up source */ #define RKPM_CPU0_WKUP_EN BIT(0) From 6dd93bf724aee69b14dd7301893917830ad535c6 Mon Sep 17 00:00:00 2001 From: XiaoDong Huang Date: Tue, 29 Jul 2025 15:53:27 +0800 Subject: [PATCH 09/14] arm64: dts: rockchip: rv1126b-evb1-v10&rv1126b-evb2-v10: enable low power aoa Signed-off-by: XiaoDong Huang Signed-off-by: Xing Zheng Change-Id: I5c0019df2576450fb15751ba1b83529c307ae13e --- .../boot/dts/rockchip/rv1126b-evb1-v10.dts | 8 ++++++++ .../boot/dts/rockchip/rv1126b-evb2-v10.dts | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rv1126b-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rv1126b-evb1-v10.dts index 6ab9eb32912f..89b327e90aa7 100644 --- a/arch/arm64/boot/dts/rockchip/rv1126b-evb1-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rv1126b-evb1-v10.dts @@ -25,6 +25,7 @@ | RKPM_SLP_PMU_PMUALIVE_32K | RKPM_SLP_PMU_DIS_OSC | RKPM_SLP_32K_EXT + | RKPM_SLP_LP_AOA ) >; @@ -156,6 +157,13 @@ | RKPM_IO_CFG_ID(25) ) >; + + rockchip,wakeup-config = < + (0 + | RKPM_GPIO0_WKUP_EN + | RKPM_AAD_WKUP_EN + ) + >; }; &rtc { diff --git a/arch/arm64/boot/dts/rockchip/rv1126b-evb2-v10.dts b/arch/arm64/boot/dts/rockchip/rv1126b-evb2-v10.dts index 96dd2a1089d5..dae3ae83a7c5 100644 --- a/arch/arm64/boot/dts/rockchip/rv1126b-evb2-v10.dts +++ b/arch/arm64/boot/dts/rockchip/rv1126b-evb2-v10.dts @@ -21,6 +21,16 @@ &rockchip_suspend { status = "okay"; + rockchip,sleep-mode-config = < + (0 + | RKPM_SLP_ARMOFF_LOGOFF + | RKPM_SLP_PMU_PMUALIVE_32K + | RKPM_SLP_PMU_DIS_OSC + | RKPM_SLP_32K_EXT + | RKPM_SLP_LP_AOA + ) + >; + rockchip,sleep-pin-config = < (0 | RKPM_SLEEP_PIN0_EN @@ -30,6 +40,13 @@ ) >; + rockchip,wakeup-config = < + (0 + | RKPM_GPIO0_WKUP_EN + | RKPM_AAD_WKUP_EN + ) + >; + rockchip,sleep-io-config = < /* pmic_sleep */ (0 From 95c2e9defccafedc80cf528f98973ccc1d69b9d2 Mon Sep 17 00:00:00 2001 From: Zefa Chen Date: Fri, 11 Jul 2025 11:59:32 +0800 Subject: [PATCH 10/14] include: rk-camera-module: add cmd RKMODULE_GET_ERROR_INFO Change-Id: I06c31f514f771d6e51a46eae3ee287e0c01d0956 Signed-off-by: Zefa Chen --- include/uapi/linux/rk-camera-module.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/uapi/linux/rk-camera-module.h b/include/uapi/linux/rk-camera-module.h index 75100050e3e9..d1c5a63b0bad 100644 --- a/include/uapi/linux/rk-camera-module.h +++ b/include/uapi/linux/rk-camera-module.h @@ -229,6 +229,9 @@ #define RKMODULE_SET_CMPS_MODE \ _IOW('V', BASE_VIDIOC_PRIVATE + 55, __u32) +#define RKMODULE_GET_ERROR_INFO \ + _IOR('V', BASE_VIDIOC_PRIVATE + 56, struct rkmodule_error_info) + struct rkmodule_i2cdev_info { __u8 slave_addr; } __attribute__ ((packed)); @@ -987,4 +990,9 @@ enum rkmodule_cmps_mode { CMPS_HIGH_BIT_WIDTH_MODE, }; +struct rkmodule_error_info { + __u32 err_code; + __u8 detail[256]; +}; + #endif /* _UAPI_RKMODULE_CAMERA_H */ From 992ff35c2db2a8ffc942c1028dd83f735cd0844c Mon Sep 17 00:00:00 2001 From: Zefa Chen Date: Fri, 11 Jul 2025 11:59:55 +0800 Subject: [PATCH 11/14] media: rockchip: vicap support get error info Change-Id: I61fc0834e3574876f40d563290222044550594a4 Signed-off-by: Zefa Chen --- drivers/media/platform/rockchip/cif/capture.c | 74 +++++++++++++++---- drivers/media/platform/rockchip/cif/dev.h | 2 + .../media/platform/rockchip/cif/mipi-csi2.c | 71 ++++++++++++++++++ .../media/platform/rockchip/cif/mipi-csi2.h | 5 ++ 4 files changed, 139 insertions(+), 13 deletions(-) diff --git a/drivers/media/platform/rockchip/cif/capture.c b/drivers/media/platform/rockchip/cif/capture.c index 5659bed0cb48..8ee8030b5f9a 100644 --- a/drivers/media/platform/rockchip/cif/capture.c +++ b/drivers/media/platform/rockchip/cif/capture.c @@ -6912,20 +6912,8 @@ void rkcif_do_stop_stream(struct rkcif_stream *stream, ret = dev->pipe.close(&dev->pipe); if (ret < 0) v4l2_err(v4l2_dev, "pipeline close failed error:%d\n", ret); - if (dev->hdr.hdr_mode == HDR_X2) { - if (dev->stream[RKCIF_STREAM_MIPI_ID0].state == RKCIF_STATE_READY && - dev->stream[RKCIF_STREAM_MIPI_ID1].state == RKCIF_STATE_READY) { - dev->can_be_reset = true; - } - } else if (dev->hdr.hdr_mode == HDR_X3) { - if (dev->stream[RKCIF_STREAM_MIPI_ID0].state == RKCIF_STATE_READY && - dev->stream[RKCIF_STREAM_MIPI_ID1].state == RKCIF_STATE_READY && - dev->stream[RKCIF_STREAM_MIPI_ID2].state == RKCIF_STATE_READY) { - dev->can_be_reset = true; - } - } else { + if (atomic_read(&dev->pipe.stream_cnt) == 0) dev->can_be_reset = true; - } mutex_lock(&hw_dev->dev_lock); for (i = 0; i < hw_dev->dev_num; i++) { if (atomic_read(&hw_dev->cif_dev[i]->pipe.stream_cnt) != 0) { @@ -9903,6 +9891,55 @@ void rkcif_flip_end_wait_work(struct work_struct *work) mutex_unlock(&dev->stream_lock); } +static int rkcif_get_error_info(struct rkcif_device *cif_dev, + struct rkmodule_error_info *err_info) +{ + int count = 0; + int is_dvp = (cif_dev->inf_id == RKCIF_DVP); + + memset(err_info, 0, sizeof(*err_info)); + + if (is_dvp) + count = snprintf(err_info->detail, sizeof(err_info->detail), "rkcif_dvp:"); + else + count = snprintf(err_info->detail, sizeof(err_info->detail), "rkcif_mipi%d:", cif_dev->csi_host_idx); + +#define APPEND_STAT(field) \ + do {\ + ssize_t remaining = sizeof(err_info->detail) - count;\ + if (remaining > 0) { \ + int written = snprintf(err_info->detail + count, remaining, "%lld,", cif_dev->irq_stats.field); \ + if (written >= 0) { \ + count += written; \ + } \ + } \ + } while (0) + + APPEND_STAT(bus0_err); + APPEND_STAT(bus1_err); + + if (is_dvp) { + APPEND_STAT(dvp_overflow_cnt); + APPEND_STAT(dvp_bwidth_lack_cnt); + APPEND_STAT(dvp_size_err_cnt); + } else { + APPEND_STAT(csi_overflow_cnt); + APPEND_STAT(csi_bwidth_lack_cnt); + APPEND_STAT(csi_size_err_cnt); + } + + count += snprintf(err_info->detail + count, sizeof(err_info->detail) - count, + "%lld,%lld,%lld,%lld,", + cif_dev->irq_stats.not_active_buf_cnt[0], + cif_dev->irq_stats.not_active_buf_cnt[1], + cif_dev->irq_stats.not_active_buf_cnt[2], + cif_dev->irq_stats.not_active_buf_cnt[3]); + + err_info->err_code = cif_dev->irq_stats.all_err_cnt ? BIT(0) : 0; + + return 0; +} + static int rkcif_do_reset_work(struct rkcif_device *cif_dev, enum rkmodule_reset_src reset_src); @@ -9933,6 +9970,7 @@ static long rkcif_ioctl_default(struct file *file, void *fh, u32 vblank = 0; struct rkisp_vicap_mode vicap_mode; struct v4l2_subdev *sd = NULL; + struct rkmodule_error_info *err_info; switch (cmd) { case RKCIF_CMD_GET_CSI_MEMORY_MODE: @@ -10235,6 +10273,10 @@ static long rkcif_ioctl_default(struct file *file, void *fh, else dev->is_support_get_exp = false; break; + case RKMODULE_GET_ERROR_INFO: + err_info = (struct rkmodule_error_info *)arg; + ret = rkcif_get_error_info(dev, err_info); + break; default: return -EINVAL; } @@ -14102,12 +14144,16 @@ unsigned int rkcif_irq_global(struct rkcif_device *cif_dev) v4l2_err(&cif_dev->v4l2_dev, "ERROR: AXI0 bus err intstat_glb:0x%x !!\n", intstat_glb); + cif_dev->irq_stats.bus0_err++; + cif_dev->irq_stats.all_err_cnt++; return 0; } if (cif_dev->chip_id == CHIP_RK3588_CIF && intstat_glb & SCALE_TOISP_AXI1_ERR) { v4l2_err(&cif_dev->v4l2_dev, "ERROR: AXI1 bus err intstat_glb:0x%x !!\n", intstat_glb); + cif_dev->irq_stats.bus1_err++; + cif_dev->irq_stats.all_err_cnt++; return 0; } return intstat_glb; @@ -15029,12 +15075,14 @@ void rkcif_irq_pingpong_v1(struct rkcif_device *cif_dev) if (cif_dev->chip_id >= CHIP_RV1103B_CIF) rkcif_write_register_and(cif_dev, CIF_REG_MIPI_LVDS_CTRL, ~0x000f0000); } + cif_dev->irq_stats.all_err_cnt++; return; } if (intstat & CSI_FIFO_OVERFLOW_V1) { cif_dev->irq_stats.csi_overflow_cnt++; cif_dev->err_state |= RKCIF_ERR_OVERFLOW; + cif_dev->irq_stats.all_err_cnt++; return; } diff --git a/drivers/media/platform/rockchip/cif/dev.h b/drivers/media/platform/rockchip/cif/dev.h index ca6e0983b186..c3dbe068391e 100644 --- a/drivers/media/platform/rockchip/cif/dev.h +++ b/drivers/media/platform/rockchip/cif/dev.h @@ -398,6 +398,8 @@ struct rkcif_irq_stats { u64 frm_end_cnt[RKCIF_MAX_STREAM_MIPI]; u64 not_active_buf_cnt[RKCIF_MAX_STREAM_MIPI]; u64 trig_simult_cnt[RKCIF_MAX_STREAM_MIPI]; + u64 bus0_err; + u64 bus1_err; u64 all_err_cnt; }; diff --git a/drivers/media/platform/rockchip/cif/mipi-csi2.c b/drivers/media/platform/rockchip/cif/mipi-csi2.c index 8258dfd669a2..9cefbbcbde04 100644 --- a/drivers/media/platform/rockchip/cif/mipi-csi2.c +++ b/drivers/media/platform/rockchip/cif/mipi-csi2.c @@ -641,12 +641,51 @@ static void csi2_quick_stream_off(struct csi2_dev *csi2) } } +static int csi2_get_error_info(struct csi2_dev *csi2, + struct rkmodule_error_info *err_info) +{ + int count = 0; + + memset(err_info, 0, sizeof(*err_info)); + + count = snprintf(err_info->detail, sizeof(err_info->detail), "%s:", csi2->dev_name); + +#define APPEND_STAT(field) \ + do {\ + ssize_t remaining = sizeof(err_info->detail) - count;\ + if (remaining > 0) { \ + int written = snprintf(err_info->detail + count, remaining, "%u,", csi2->err_list[field].cnt); \ + if (written >= 0) { \ + count += written; \ + } \ + } \ + } while (0) + + APPEND_STAT(RK_CSI2_ERR_SOTSYN); + APPEND_STAT(RK_CSI2_ERR_FS_FE_MIS); + APPEND_STAT(RK_CSI2_ERR_FRM_SEQ_ERR); + APPEND_STAT(RK_CSI2_ERR_CRC_ONCE); + APPEND_STAT(RK_CSI2_ERR_CRC); + APPEND_STAT(RK_CSI2_ERR_ECC2); + APPEND_STAT(RK_CSI2_ERR_CTRL); + APPEND_STAT(RK_CSI2_ERR_ULPM); + APPEND_STAT(RK_CSI2_ERR_SOT); + APPEND_STAT(RK_CSI2_ERR_ECC); + APPEND_STAT(RK_CSI2_ERR_ID); + APPEND_STAT(RK_CSI2_ERR_CODE); + + err_info->err_code = csi2->err_list[RK_CSI2_ERR_ALL].cnt ? BIT(0) : 0; + + return 0; +} + static long rkcif_csi2_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { struct csi2_dev *csi2 = sd_to_dev(sd); struct v4l2_subdev *sensor = get_remote_sensor(sd); long ret = 0; int i = 0; + struct rkmodule_error_info *err_info; switch (cmd) { case RKCIF_CMD_SET_CSI_IDX: @@ -667,6 +706,10 @@ static long rkcif_csi2_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg else csi2_quick_stream_off(csi2); break; + case RKMODULE_GET_ERROR_INFO: + err_info = (struct rkmodule_error_info *)arg; + ret = csi2_get_error_info(csi2, err_info); + break; default: ret = -ENOIOCTLCMD; break; @@ -683,6 +726,7 @@ static long rkcif_csi2_compat_ioctl32(struct v4l2_subdev *sd, struct rkcif_csi_info csi_info; int sw_dbg = 0; long ret; + struct rkmodule_error_info *err_info; switch (cmd) { case RKCIF_CMD_SET_CSI_IDX: @@ -697,6 +741,22 @@ static long rkcif_csi2_compat_ioctl32(struct v4l2_subdev *sd, ret = rkcif_csi2_ioctl(sd, cmd, &sw_dbg); break; + case RKMODULE_GET_ERROR_INFO: + err_info = kzalloc(sizeof(*err_info), GFP_KERNEL); + if (!err_info) { + ret = -ENOMEM; + return ret; + } + + ret = rkcif_csi2_ioctl(sd, cmd, err_info); + if (!ret) { + if (copy_to_user(up, err_info, sizeof(*err_info))) { + kfree(err_info); + return -EFAULT; + } + } + kfree(err_info); + break; default: ret = -ENOIOCTLCMD; break; @@ -960,6 +1020,7 @@ static irqreturn_t rk_csirx_irq2_handler(int irq, void *ctx) char err_str[CSI_ERRSTR_LEN] = {0}; char vc_info[CSI_VCINFO_LEN] = {0}; u64 cur_timestamp = ktime_get_ns(); + struct csi2_err_stats *err_list = NULL; if (!csi2_hw) { disable_irq_nosync(irq); @@ -975,26 +1036,36 @@ static irqreturn_t rk_csirx_irq2_handler(int irq, void *ctx) val = read_csihost_reg(csi2_hw->base, CSIHOST_ERR2); if (val) { if (val & CSIHOST_ERR2_PHYERR_ESC) { + err_list = &csi2->err_list[RK_CSI2_ERR_ULPM]; + err_list->cnt++; csi2_find_err_vc(val & 0xf, vc_info); snprintf(cur_str, CSI_ERRSTR_LEN, "(ULPM,lane:%s) ", vc_info); csi2_err_strncat(err_str, cur_str); } if (val & CSIHOST_ERR2_PHYERR_SOTHS) { + err_list = &csi2->err_list[RK_CSI2_ERR_SOT]; + err_list->cnt++; csi2_find_err_vc((val >> 4) & 0xf, vc_info); snprintf(cur_str, CSI_ERRSTR_LEN, "(sot,lane:%s) ", vc_info); csi2_err_strncat(err_str, cur_str); } if (val & CSIHOST_ERR2_ECC_CORRECTED) { + err_list = &csi2->err_list[RK_CSI2_ERR_ECC]; + err_list->cnt++; csi2_find_err_vc((val >> 8) & 0xf, vc_info); snprintf(cur_str, CSI_ERRSTR_LEN, "(ecc,vc:%s) ", vc_info); csi2_err_strncat(err_str, cur_str); } if (val & CSIHOST_ERR2_ERR_ID) { + err_list = &csi2->err_list[RK_CSI2_ERR_ID]; + err_list->cnt++; csi2_find_err_vc((val >> 12) & 0xf, vc_info); snprintf(cur_str, CSI_ERRSTR_LEN, "(err id,vc:%s) ", vc_info); csi2_err_strncat(err_str, cur_str); } if (val & CSIHOST_ERR2_PHYERR_CODEHS) { + err_list = &csi2->err_list[RK_CSI2_ERR_CODE]; + err_list->cnt++; snprintf(cur_str, CSI_ERRSTR_LEN, "(err code) "); csi2_err_strncat(err_str, cur_str); } diff --git a/drivers/media/platform/rockchip/cif/mipi-csi2.h b/drivers/media/platform/rockchip/cif/mipi-csi2.h index 93c4bc3fbc9b..02489e4f33d6 100644 --- a/drivers/media/platform/rockchip/cif/mipi-csi2.h +++ b/drivers/media/platform/rockchip/cif/mipi-csi2.h @@ -115,6 +115,11 @@ enum csi2_err { RK_CSI2_ERR_CRC, RK_CSI2_ERR_ECC2, RK_CSI2_ERR_CTRL, + RK_CSI2_ERR_ULPM, + RK_CSI2_ERR_SOT, + RK_CSI2_ERR_ECC, + RK_CSI2_ERR_ID, + RK_CSI2_ERR_CODE, RK_CSI2_ERR_ALL, RK_CSI2_ERR_MAX }; From 08768510198e11de10f5f082535c0f8250703e40 Mon Sep 17 00:00:00 2001 From: LongChang Ma Date: Fri, 23 May 2025 08:41:24 +0800 Subject: [PATCH 12/14] media: i2c: add support os04e10 sensor driver for v6.1 Signed-off-by: LongChang Ma Change-Id: I94c770909e6def65c9a5257daceeb838cb9d9621 --- drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/os04e10.c | 2391 +++++++++++++++++++++++++++++++++++ 3 files changed, 2403 insertions(+) create mode 100644 drivers/media/i2c/os04e10.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 68853cccf05d..f8ccbabe73cd 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1131,6 +1131,17 @@ config VIDEO_OS04D10 This is a Video4Linux2 sensor driver for the OmniVision OS04D10 camera. +config VIDEO_OS04E10 + tristate "OmniVision OS04E10 sensor support" + depends on I2C && VIDEO_DEV + depends on MEDIA_CAMERA_SUPPORT + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OS04E10 camera. + config VIDEO_OS05A20 tristate "OmniVision OS05A20 sensor support" depends on I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index dac1a04a40b2..6a10ddfba1ab 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -159,6 +159,7 @@ obj-$(CONFIG_VIDEO_OS02K10) += os02k10.o obj-$(CONFIG_VIDEO_OS03B10) += os03b10.o obj-$(CONFIG_VIDEO_OS04A10) += os04a10.o obj-$(CONFIG_VIDEO_OS04D10) += os04d10.o +obj-$(CONFIG_VIDEO_OS04E10) += os04e10.o obj-$(CONFIG_VIDEO_OS05A20) += os05a20.o obj-$(CONFIG_VIDEO_OS05L10) += os05l10.o obj-$(CONFIG_VIDEO_OS08A20) += os08a20.o diff --git a/drivers/media/i2c/os04e10.c b/drivers/media/i2c/os04e10.c new file mode 100644 index 000000000000..8771c75c6094 --- /dev/null +++ b/drivers/media/i2c/os04e10.c @@ -0,0 +1,2391 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * os04e10 driver + * + * Copyright (C) 2024 Rockchip Electronics Co., Ltd. + * + * V0.0X01.0X01 first version + */ + +// #define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../platform/rockchip/isp/rkisp_tb_helper.h" +#include "cam-tb-setup.h" + +#define DRIVER_VERSION KERNEL_VERSION(0, 0x01, 0x01) + +#ifndef V4L2_CID_DIGITAL_GAIN +#define V4L2_CID_DIGITAL_GAIN V4L2_CID_GAIN +#endif + +#define OS04E10_LANES 2 +#define OS04E10_BITS_PER_SAMPLE 10 +#define OS04E10_LINK_FREQ_900 450000000 + +#define PIXEL_RATE_WITH_900M_10BIT (OS04E10_LINK_FREQ_900 * 2 * \ + OS04E10_LANES / OS04E10_BITS_PER_SAMPLE) +#define OS04E10_XVCLK_FREQ 24000000 + +#define CHIP_ID 0x530641 +#define OS04E10_REG_CHIP_ID 0x300A + +#define OS04E10_REG_CTRL_MODE 0x0100 +#define OS04E10_MODE_SW_STANDBY 0x0 +#define OS04E10_MODE_STREAMING BIT(0) + +#define OS04E10_REG_EXPOSURE_H 0x3500 +#define OS04E10_REG_EXPOSURE_M 0x3501 +#define OS04E10_REG_EXPOSURE_L 0x3502 +#define OS04E10_REG_EXPOSURE_SHORT_H 0x3510 +#define OS04E10_REG_EXPOSURE_SHORT_M 0x3511 +#define OS04E10_REG_EXPOSURE_SHORT_L 0x3512 +#define OS04E10_EXPOSURE_MIN 2 +#define OS04E10_EXPOSURE_STEP 1 +#define OS04E10_VTS_MAX 0x7fff + +#define OS04E10_REG_ANA_GAIN_H 0x3508 +#define OS04E10_REG_ANA_GAIN_L 0x3509 +#define OS04E10_REG_DIG_GAIN_H 0x350A +#define OS04E10_REG_DIG_GAIN_L 0x350B +#define OS04E10_REG_ANA_GAIN_SHORT_H 0x350C +#define OS04E10_REG_ANA_GAIN_SHORT_L 0x350D +#define OS04E10_REG_DIG_GAIN_SHORT_H 0x350E +#define OS04E10_REG_DIG_GAIN_SHORT_L 0x350F + +#define OS04E10_LGAIN 0 +#define OS04E10_SGAIN 1 +#define OS04E10_GAIN_MIN 0x0080 +#define OS04E10_GAIN_MAX (31743) //15.5 * 16 * 128 +#define OS04E10_GAIN_STEP 1 +#define OS04E10_GAIN_DEFAULT 0x80 + +#define OS04E10_REG_GROUP_HOLD 0x3812 +#define OS04E10_GROUP_HOLD_START 0x00 +#define OS04E10_GROUP_HOLD_END 0x30 + +#define OS04E10_REG_TEST_PATTERN 0x5080 +#define OS04E10_TEST_PATTERN_BIT_MASK BIT(3) + +#define OS04E10_REG_VTS_H 0x380E +#define OS04E10_REG_VTS_L 0x380F + +#define OS04E10_FLIP_MIRROR_REG 0x3820 + +#define OS04E10_FETCH_EXP_H(VAL) (((VAL) >> 16) & 0xF) +#define OS04E10_FETCH_EXP_M(VAL) (((VAL) >> 8) & 0xFF) +#define OS04E10_FETCH_EXP_L(VAL) ((VAL) & 0xFF) + +#define OS04E10_FETCH_AGAIN_H(VAL) (((VAL) >> 8) & 0x3F) +#define OS04E10_FETCH_AGAIN_L(VAL) ((VAL) & 0xFF) + +#define OS04E10_FETCH_DGAIN_H(VAL) (((VAL) >> 8) & 0x3F) +#define OS04E10_FETCH_DGAIN_L(VAL) ((VAL) & 0xFF) + +#define OS04E10_FETCH_MIRROR(VAL, ENABLE) (ENABLE ? VAL & 0xf7 : VAL | 0x08) +#define OS04E10_FETCH_FLIP(VAL, ENABLE) (ENABLE ? VAL | 0x30 : VAL & 0xcf) + +#define REG_DELAY 0xFFFE +#define REG_NULL 0xFFFF + +#define OS04E10_REG_VALUE_08BIT 1 +#define OS04E10_REG_VALUE_16BIT 2 +#define OS04E10_REG_VALUE_24BIT 3 + +#define OF_CAMERA_PINCTRL_STATE_DEFAULT "rockchip,camera_default" +#define OF_CAMERA_PINCTRL_STATE_SLEEP "rockchip,camera_sleep" +#define OF_CAMERA_HDR_MODE "rockchip,camera-hdr-mode" +#define OS04E10_NAME "os04e10" + +static const char * const os04e10_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +#define OS04E10_NUM_SUPPLIES ARRAY_SIZE(os04e10_supply_names) + +struct regval { + u16 addr; + u8 val; +}; + +struct os04e10_mode { + u32 bus_fmt; + u32 width; + u32 height; + struct v4l2_fract max_fps; + u32 hts_def; + u32 vts_def; + u32 exp_def; + const struct regval *reg_list; + u32 hdr_mode; + u32 vc[PAD_MAX]; +}; + +struct os04e10 { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + struct regulator_bulk_data supplies[OS04E10_NUM_SUPPLIES]; + + struct pinctrl *pinctrl; + struct pinctrl_state *pins_default; + struct pinctrl_state *pins_sleep; + + struct v4l2_subdev subdev; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *anal_gain; + struct v4l2_ctrl *digi_gain; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *test_pattern; + struct mutex mutex; + struct v4l2_fract cur_fps; + bool streaming; + bool power_on; + const struct os04e10_mode *cur_mode; + u32 module_index; + const char *module_facing; + const char *module_name; + const char *len_name; + u32 cur_vts; + bool has_init_exp; + bool is_thunderboot; + bool is_first_streamoff; + struct preisp_hdrae_exp_s init_hdrae_exp; +}; + +#define to_os04e10(sd) container_of(sd, struct os04e10, subdev) + +/* + * Xclk 24Mhz + */ +static const struct regval os04e10_global_regs[] = { + {REG_NULL, 0x00}, +}; + +/* + * Xclk 24Mhz + * max_framerate 30fps + * mipi_datarate per lane 900Mbps, 2lane + */ +static const struct regval os04e10_linear_10_2048x2048_regs[] = { + {0x0103, 0x01}, + {0x0301, 0x44}, + {0x0303, 0x02}, + {0x0304, 0x00}, + {0x0305, 0x4b}, + {0x0306, 0x00}, + {0x0325, 0x3b}, + {0x0327, 0x04}, + {0x0328, 0x05}, + {0x3002, 0x21}, + {0x3016, 0x32}, + {0x301e, 0xb4}, + {0x301f, 0xd0}, + {0x3021, 0x03}, + {0x3022, 0x01}, + {0x3107, 0xa1}, + {0x3108, 0x7d}, + {0x3109, 0xfc}, + {0x3500, 0x00}, + {0x3501, 0x08}, + {0x3502, 0x54}, + {0x3503, 0x88}, + {0x3508, 0x01}, + {0x3509, 0x00}, + {0x350a, 0x04}, + {0x350b, 0x00}, + {0x350c, 0x04}, + {0x350d, 0x00}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x00}, + {0x3512, 0x20}, + {0x3600, 0x4c}, + {0x3601, 0x08}, + {0x3610, 0x87}, + {0x3611, 0x24}, + {0x3614, 0x4c}, + {0x3620, 0x0c}, + {0x3621, 0x04}, + {0x3632, 0x80}, + {0x3633, 0x00}, + {0x3660, 0x00}, + {0x3662, 0x10}, + {0x3664, 0x70}, + {0x3665, 0x00}, + {0x3666, 0x00}, + {0x3667, 0x00}, + {0x366a, 0x14}, + {0x3670, 0x0b}, + {0x3671, 0x0b}, + {0x3672, 0x0b}, + {0x3673, 0x0b}, + {0x3674, 0x00}, + {0x3678, 0x2b}, + {0x3679, 0x43}, + {0x3681, 0xff}, + {0x3682, 0x86}, + {0x3683, 0x44}, + {0x3684, 0x24}, + {0x3685, 0x00}, + {0x368a, 0x00}, + {0x368d, 0x2b}, + {0x368e, 0x6b}, + {0x3690, 0x00}, + {0x3691, 0x0b}, + {0x3692, 0x0b}, + {0x3693, 0x0b}, + {0x3694, 0x0b}, + {0x3699, 0x03}, + {0x369d, 0x68}, + {0x369e, 0x34}, + {0x369f, 0x1b}, + {0x36a0, 0x0f}, + {0x36a1, 0x77}, + {0x36a2, 0x00}, + {0x36a3, 0x02}, + {0x36a4, 0x02}, + {0x36b0, 0x30}, + {0x36b1, 0xf0}, + {0x36b2, 0x00}, + {0x36b3, 0x00}, + {0x36b4, 0x00}, + {0x36b5, 0x00}, + {0x36b6, 0x00}, + {0x36b7, 0x00}, + {0x36b8, 0x00}, + {0x36b9, 0x00}, + {0x36ba, 0x00}, + {0x36bb, 0x00}, + {0x36bc, 0x00}, + {0x36bd, 0x00}, + {0x36be, 0x00}, + {0x36bf, 0x00}, + {0x36c0, 0x1f}, + {0x36c1, 0x00}, + {0x36c2, 0x00}, + {0x36c3, 0x00}, + {0x36c4, 0x00}, + {0x36c5, 0x00}, + {0x36c6, 0x00}, + {0x36c7, 0x00}, + {0x36c8, 0x00}, + {0x36c9, 0x00}, + {0x36ca, 0x0e}, + {0x36cb, 0x0e}, + {0x36cc, 0x0e}, + {0x36cd, 0x0e}, + {0x36ce, 0x0c}, + {0x36cf, 0x0c}, + {0x36d0, 0x0c}, + {0x36d1, 0x0c}, + {0x36d2, 0x00}, + {0x36d3, 0x08}, + {0x36d4, 0x10}, + {0x36d5, 0x10}, + {0x36d6, 0x00}, + {0x36d7, 0x08}, + {0x36d8, 0x10}, + {0x36d9, 0x10}, + {0x3704, 0x05}, + {0x3705, 0x00}, + {0x3706, 0x2b}, + {0x3709, 0x49}, + {0x370a, 0x00}, + {0x370b, 0x60}, + {0x370e, 0x0c}, + {0x370f, 0x1c}, + {0x3710, 0x00}, + {0x3713, 0x00}, + {0x3714, 0x24}, + {0x3716, 0x24}, + {0x371a, 0x1e}, + {0x3724, 0x0d}, + {0x3725, 0xb2}, + {0x372b, 0x54}, + {0x3739, 0x10}, + {0x373f, 0xb0}, + {0x3740, 0x2b}, + {0x3741, 0x2b}, + {0x3742, 0x2b}, + {0x3743, 0x2b}, + {0x3744, 0x60}, + {0x3745, 0x60}, + {0x3746, 0x60}, + {0x3747, 0x60}, + {0x3748, 0x00}, + {0x3749, 0x00}, + {0x374a, 0x00}, + {0x374b, 0x00}, + {0x374c, 0x00}, + {0x374d, 0x00}, + {0x374e, 0x00}, + {0x374f, 0x00}, + {0x3756, 0x00}, + {0x3757, 0x0e}, + {0x3760, 0x11}, + {0x3767, 0x08}, + {0x3773, 0x01}, + {0x3774, 0x02}, + {0x3775, 0x12}, + {0x3776, 0x02}, + {0x377b, 0x40}, + {0x377c, 0x00}, + {0x377d, 0x0c}, + {0x3782, 0x02}, + {0x3787, 0x24}, + {0x3795, 0x24}, + {0x3796, 0x01}, + {0x3798, 0x40}, + {0x379c, 0x00}, + {0x379d, 0x00}, + {0x379e, 0x00}, + {0x379f, 0x01}, + {0x37a1, 0x10}, + {0x37a6, 0x00}, + {0x37ac, 0xa0}, + {0x37bb, 0x02}, + {0x37be, 0x0a}, + {0x37bf, 0x0a}, + {0x37c2, 0x04}, + {0x37c4, 0x11}, + {0x37c5, 0x80}, + {0x37c6, 0x14}, + {0x37c7, 0x08}, + {0x37c8, 0x42}, + {0x37cd, 0x17}, + {0x37ce, 0x04}, + {0x37d9, 0x08}, + {0x37dc, 0x01}, + {0x37e0, 0x30}, + {0x37e1, 0x10}, + {0x37e2, 0x14}, + {0x37e4, 0x28}, + {0x37ef, 0x00}, + {0x37f4, 0x00}, + {0x37f5, 0x00}, + {0x37f6, 0x00}, + {0x37f7, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x08}, + {0x3805, 0x0f}, + {0x3806, 0x08}, + {0x3807, 0x0f}, + {0x3808, 0x08}, + {0x3809, 0x00}, + {0x380a, 0x08}, + {0x380b, 0x00}, + {0x380c, 0x06}, + {0x380d, 0x50}, + {0x380e, 0x08}, + {0x380f, 0x74}, + {0x3810, 0x00}, + {0x3811, 0x08}, + {0x3812, 0x00}, + {0x3813, 0x08}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x01}, + {0x3817, 0x01}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x381a, 0x00}, + {0x381b, 0x01}, + {0x3820, 0x80}, + {0x3821, 0x00}, + {0x3822, 0x04}, + {0x3823, 0x08}, + {0x3824, 0x00}, + {0x3825, 0x20}, + {0x3826, 0x00}, + {0x3827, 0x08}, + {0x3829, 0x03}, + {0x382a, 0x00}, + {0x382b, 0x00}, + {0x3832, 0x08}, + {0x3838, 0x00}, + {0x3839, 0x00}, + {0x383a, 0x00}, + {0x383b, 0x00}, + {0x383d, 0x01}, + {0x383e, 0x00}, + {0x383f, 0x00}, + {0x3843, 0x00}, + {0x3848, 0x08}, + {0x3849, 0x00}, + {0x384a, 0x08}, + {0x384b, 0x00}, + {0x384c, 0x00}, + {0x384d, 0x08}, + {0x384e, 0x00}, + {0x384f, 0x08}, + {0x3880, 0x16}, + {0x3881, 0x00}, + {0x3882, 0x08}, + {0x388a, 0x00}, + {0x389a, 0x00}, + {0x389b, 0x00}, + {0x389c, 0x00}, + {0x38a2, 0x02}, + {0x38a3, 0x02}, + {0x38a4, 0x02}, + {0x38a5, 0x02}, + {0x38a7, 0x04}, + {0x38ae, 0x1e}, + {0x38b8, 0x02}, + {0x38c3, 0x06}, + {0x3c80, 0x3f}, + {0x3c86, 0x01}, + {0x3c87, 0x02}, + {0x3ca0, 0x01}, + {0x3ca2, 0x0c}, + {0x3d8c, 0x71}, + {0x3d8d, 0xe2}, + {0x3f00, 0xcb}, + {0x3f04, 0x04}, + {0x3f07, 0x04}, + {0x3f09, 0x50}, + {0x3f9e, 0x07}, + {0x3f9f, 0x04}, + {0x4000, 0xf3}, + {0x4002, 0x00}, + {0x4003, 0x40}, + {0x4008, 0x00}, + {0x4009, 0x0f}, + {0x400a, 0x01}, + {0x400b, 0x78}, + {0x400f, 0x89}, + {0x4040, 0x00}, + {0x4041, 0x07}, + {0x4090, 0x14}, + {0x40b0, 0x00}, + {0x40b1, 0x00}, + {0x40b2, 0x00}, + {0x40b3, 0x00}, + {0x40b4, 0x00}, + {0x40b5, 0x00}, + {0x40b7, 0x00}, + {0x40b8, 0x00}, + {0x40b9, 0x00}, + {0x40ba, 0x00}, + {0x4300, 0xff}, + {0x4301, 0x00}, + {0x4302, 0x0f}, + {0x4303, 0x01}, + {0x4304, 0x01}, + {0x4305, 0x83}, + {0x4306, 0x21}, + {0x430d, 0x00}, + {0x4501, 0x00}, + {0x4505, 0xc4}, + {0x4506, 0x00}, + {0x4507, 0x60}, + {0x4508, 0x00}, + {0x4800, 0x64}, + {0x4803, 0x00}, + {0x4809, 0x8e}, + {0x480e, 0x00}, + {0x4813, 0x00}, + {0x4814, 0x2a}, + {0x481b, 0x3c}, + {0x481f, 0x26}, + {0x4825, 0x32}, + {0x4829, 0x64}, + {0x4837, 0x11}, + {0x484b, 0x07}, + {0x4883, 0x36}, + {0x4885, 0x03}, + {0x488b, 0x00}, + {0x4d00, 0x04}, + {0x4d01, 0x99}, + {0x4d02, 0xbd}, + {0x4d03, 0xac}, + {0x4d04, 0xf2}, + {0x4d05, 0x54}, + {0x4e00, 0x2a}, + {0x4e0d, 0x00}, + {0x5000, 0xbb}, + {0x5001, 0x09}, + {0x5004, 0x00}, + {0x5005, 0x0e}, + {0x5036, 0x00}, + {0x5080, 0x04}, + {0x5082, 0x00}, + {0x5180, 0x70}, + {0x5181, 0x10}, + {0x5182, 0x71}, + {0x5183, 0xdf}, + {0x5184, 0x02}, + {0x5185, 0x6c}, + {0x5189, 0x48}, + {0x5324, 0x09}, + {0x5325, 0x11}, + {0x5326, 0x1f}, + {0x5327, 0x3b}, + {0x5328, 0x49}, + {0x5329, 0x61}, + {0x532a, 0x9c}, + {0x532b, 0xc9}, + {0x5335, 0x04}, + {0x5336, 0x00}, + {0x5337, 0x04}, + {0x5338, 0x00}, + {0x5339, 0x0b}, + {0x533a, 0x00}, + {0x53a4, 0x09}, + {0x53a5, 0x11}, + {0x53a6, 0x1f}, + {0x53a7, 0x3b}, + {0x53a8, 0x49}, + {0x53a9, 0x61}, + {0x53aa, 0x9c}, + {0x53ab, 0xc9}, + {0x53b5, 0x04}, + {0x53b6, 0x00}, + {0x53b7, 0x04}, + {0x53b8, 0x00}, + {0x53b9, 0x0b}, + {0x53ba, 0x00}, + {0x580b, 0x03}, + {0x580d, 0x00}, + {0x580f, 0x00}, + {0x5820, 0x00}, + {0x5821, 0x00}, + {0x5888, 0x01}, + {REG_NULL, 0x00}, +}; + + +/* + * Xclk 24Mhz + * max_framerate 15fps + * mipi_datarate per lane 900Mbps, 2lane + */ +static const struct regval os04e10_hdrx2_10_2048x2048_15fps_regs[] = { + {0x0103, 0x01}, + {0x0301, 0x44}, + {0x0303, 0x02}, + {0x0304, 0x00}, + {0x0305, 0x4b}, + {0x0306, 0x00}, + {0x0325, 0x3b}, + {0x0327, 0x04}, + {0x0328, 0x05}, + {0x3002, 0x21}, + {0x3016, 0x32}, + {0x301b, 0xf0}, + {0x301e, 0xb4}, + {0x301f, 0xf0}, + {0x3021, 0x03}, + {0x3022, 0x01}, + {0x3107, 0xa1}, + {0x3108, 0x7d}, + {0x3109, 0xfc}, + {0x3500, 0x00}, + {0x3501, 0x04}, + {0x3502, 0x2a}, + {0x3503, 0x88}, + {0x3508, 0x01}, + {0x3509, 0x00}, + {0x350a, 0x04}, + {0x350b, 0x00}, + {0x350c, 0x04}, + {0x350d, 0x00}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x00}, + {0x3512, 0x20}, + {0x3600, 0x4c}, + {0x3601, 0x08}, + {0x3610, 0x87}, + {0x3611, 0x24}, + {0x3614, 0x4c}, + {0x3620, 0x0c}, + {0x3621, 0x04}, + {0x3632, 0x80}, + {0x3633, 0x00}, + {0x3660, 0x04}, + {0x3662, 0x10}, + {0x3664, 0x70}, + {0x3665, 0x00}, + {0x3666, 0x00}, + {0x3667, 0x00}, + {0x366a, 0x54}, + {0x3670, 0x0b}, + {0x3671, 0x0b}, + {0x3672, 0x0b}, + {0x3673, 0x0b}, + {0x3674, 0x00}, + {0x3678, 0x2b}, + {0x3679, 0x43}, + {0x3681, 0xff}, + {0x3682, 0x86}, + {0x3683, 0x44}, + {0x3684, 0x24}, + {0x3685, 0x00}, + {0x368a, 0x00}, + {0x368d, 0x2b}, + {0x368e, 0x6b}, + {0x3690, 0x00}, + {0x3691, 0x0b}, + {0x3692, 0x0b}, + {0x3693, 0x0b}, + {0x3694, 0x0b}, + {0x3699, 0x03}, + {0x369d, 0xff}, + {0x369e, 0x86}, + {0x369f, 0x44}, + {0x36a0, 0x24}, + {0x36a1, 0x77}, + {0x36a2, 0xf0}, + {0x36a3, 0x82}, + {0x36a4, 0x82}, + {0x36b0, 0x30}, + {0x36b1, 0xf0}, + {0x36b2, 0x00}, + {0x36b3, 0x00}, + {0x36b4, 0x00}, + {0x36b5, 0x00}, + {0x36b6, 0x00}, + {0x36b7, 0x00}, + {0x36b8, 0x00}, + {0x36b9, 0x00}, + {0x36ba, 0x00}, + {0x36bb, 0x00}, + {0x36bc, 0x1f}, + {0x36bd, 0x00}, + {0x36be, 0x00}, + {0x36bf, 0x00}, + {0x36c0, 0x1f}, + {0x36c1, 0x00}, + {0x36c2, 0x00}, + {0x36c3, 0x00}, + {0x36c4, 0x00}, + {0x36c5, 0x00}, + {0x36c6, 0x00}, + {0x36c7, 0x00}, + {0x36c8, 0x00}, + {0x36c9, 0x00}, + {0x36ca, 0x0c}, + {0x36cb, 0x0c}, + {0x36cc, 0x0c}, + {0x36cd, 0x0c}, + {0x36ce, 0x0c}, + {0x36cf, 0x0c}, + {0x36d0, 0x0c}, + {0x36d1, 0x0c}, + {0x36d2, 0x00}, + {0x36d3, 0x08}, + {0x36d4, 0x10}, + {0x36d5, 0x10}, + {0x36d6, 0x00}, + {0x36d7, 0x08}, + {0x36d8, 0x10}, + {0x36d9, 0x10}, + {0x3704, 0x01}, + {0x3705, 0x00}, + {0x3706, 0x2b}, + {0x3709, 0x46}, + {0x370a, 0x00}, + {0x370b, 0x60}, + {0x370e, 0x0c}, + {0x370f, 0x1c}, + {0x3710, 0x00}, + {0x3713, 0x00}, + {0x3714, 0x24}, + {0x3716, 0x24}, + {0x371a, 0x1e}, + {0x3724, 0x0d}, + {0x3725, 0xb2}, + {0x372b, 0x54}, + {0x3739, 0x10}, + {0x373f, 0xb0}, + {0x3740, 0x2b}, + {0x3741, 0x2b}, + {0x3742, 0x2b}, + {0x3743, 0x2b}, + {0x3744, 0x60}, + {0x3745, 0x60}, + {0x3746, 0x60}, + {0x3747, 0x60}, + {0x3748, 0x00}, + {0x3749, 0x00}, + {0x374a, 0x00}, + {0x374b, 0x00}, + {0x374c, 0x00}, + {0x374d, 0x00}, + {0x374e, 0x00}, + {0x374f, 0x00}, + {0x3756, 0x00}, + {0x3757, 0x00}, + {0x3760, 0x22}, + {0x3767, 0x08}, + {0x3773, 0x01}, + {0x3774, 0x02}, + {0x3775, 0x12}, + {0x3776, 0x02}, + {0x377b, 0x4a}, + {0x377c, 0x00}, + {0x377d, 0x0c}, + {0x3782, 0x01}, + {0x3787, 0x14}, + {0x3795, 0x14}, + {0x3796, 0x01}, + {0x3798, 0x40}, + {0x379c, 0x0c}, + {0x379d, 0x1c}, + {0x379e, 0x0c}, + {0x379f, 0x00}, + {0x37a1, 0x10}, + {0x37a6, 0x00}, + {0x37ac, 0x00}, + {0x37bb, 0x01}, + {0x37be, 0x0a}, + {0x37bf, 0x0a}, + {0x37c2, 0x04}, + {0x37c4, 0x11}, + {0x37c5, 0x80}, + {0x37c6, 0x14}, + {0x37c7, 0x08}, + {0x37c8, 0x42}, + {0x37cd, 0x17}, + {0x37ce, 0x04}, + {0x37d9, 0x08}, + {0x37dc, 0x01}, + {0x37e0, 0x30}, + {0x37e1, 0x10}, + {0x37e2, 0x14}, + {0x37e4, 0x28}, + {0x37ef, 0x00}, + {0x37f4, 0x00}, + {0x37f5, 0x00}, + {0x37f6, 0x00}, + {0x37f7, 0x00}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x08}, + {0x3805, 0x0f}, + {0x3806, 0x08}, + {0x3807, 0x0f}, + {0x3808, 0x08}, + {0x3809, 0x00}, + {0x380a, 0x08}, + {0x380b, 0x00}, + {0x380c, 0x06}, + {0x380d, 0x34}, + {0x380e, 0x08}, + {0x380f, 0x9b}, + {0x3810, 0x00}, + {0x3811, 0x08}, + {0x3812, 0x00}, + {0x3813, 0x08}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x01}, + {0x3817, 0x01}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x381a, 0x00}, + {0x381b, 0x01}, + {0x3820, 0x88}, + {0x3821, 0x04}, + {0x3822, 0x14}, + {0x3823, 0x08}, + {0x3824, 0x00}, + {0x3825, 0x20}, + {0x3826, 0x00}, + {0x3827, 0x08}, + {0x3829, 0x03}, + {0x382a, 0x00}, + {0x382b, 0x00}, + {0x3832, 0x08}, + {0x3838, 0x00}, + {0x3839, 0x00}, + {0x383a, 0x00}, + {0x383b, 0x00}, + {0x383d, 0x01}, + {0x383e, 0x00}, + {0x383f, 0x00}, + {0x3843, 0x00}, + {0x3848, 0x08}, + {0x3849, 0x00}, + {0x384a, 0x08}, + {0x384b, 0x00}, + {0x384c, 0x00}, + {0x384d, 0x08}, + {0x384e, 0x00}, + {0x384f, 0x08}, + {0x3880, 0x16}, + {0x3881, 0x16}, + {0x3882, 0x08}, + {0x388a, 0x00}, + {0x389a, 0x00}, + {0x389b, 0x00}, + {0x389c, 0x00}, + {0x38a2, 0x01}, + {0x38a3, 0x01}, + {0x38a4, 0x01}, + {0x38a5, 0x01}, + {0x38a7, 0x04}, + {0x38ae, 0x1e}, + {0x38b8, 0x02}, + {0x38c3, 0x06}, + {0x3c80, 0x3e}, + {0x3c86, 0x01}, + {0x3c87, 0x02}, + {0x3ca0, 0x01}, + {0x3ca2, 0x0c}, + {0x3d8c, 0x71}, + {0x3d8d, 0xe2}, + {0x3f00, 0xcb}, + {0x3f04, 0x04}, + {0x3f07, 0x04}, + {0x3f09, 0x50}, + {0x3f9e, 0x07}, + {0x3f9f, 0x04}, + {0x4000, 0xf3}, + {0x4002, 0x00}, + {0x4003, 0x40}, + {0x4008, 0x00}, + {0x4009, 0x0f}, + {0x400a, 0x01}, + {0x400b, 0x78}, + {0x400f, 0x89}, + {0x4040, 0x00}, + {0x4041, 0x07}, + {0x4090, 0x14}, + {0x40b0, 0x00}, + {0x40b1, 0x00}, + {0x40b2, 0x00}, + {0x40b3, 0x00}, + {0x40b4, 0x00}, + {0x40b5, 0x00}, + {0x40b7, 0x00}, + {0x40b8, 0x00}, + {0x40b9, 0x00}, + {0x40ba, 0x01}, + {0x4300, 0xff}, + {0x4301, 0x00}, + {0x4302, 0x0f}, + {0x4303, 0x01}, + {0x4304, 0x01}, + {0x4305, 0x83}, + {0x4306, 0x21}, + {0x430d, 0x00}, + {0x4501, 0x00}, + {0x4505, 0xc4}, + {0x4506, 0x00}, + {0x4507, 0x43}, + {0x4508, 0x00}, + {0x4600, 0x00}, + {0x4601, 0x40}, + {0x4603, 0x03}, + {0x4800, 0x64}, + {0x4803, 0x00}, + {0x4809, 0x8e}, + {0x480e, 0x04}, + {0x4813, 0xe4}, + {0x4814, 0x2a}, + {0x481b, 0x3c}, + {0x481f, 0x26}, + {0x4825, 0x32}, + {0x4829, 0x64}, + {0x4837, 0x11}, + {0x484b, 0x27}, + {0x4883, 0x36}, + {0x4885, 0x03}, + {0x488b, 0x00}, + {0x4d00, 0x04}, + {0x4d01, 0x99}, + {0x4d02, 0xbd}, + {0x4d03, 0xac}, + {0x4d04, 0xf2}, + {0x4d05, 0x54}, + {0x4e00, 0x2a}, + {0x4e0d, 0x00}, + {0x5000, 0xbb}, + {0x5001, 0x09}, + {0x5004, 0x00}, + {0x5005, 0x0e}, + {0x5036, 0x80}, + {0x5080, 0x04}, + {0x5082, 0x00}, + {0x5180, 0x70}, + {0x5181, 0x10}, + {0x5182, 0x71}, + {0x5183, 0xdf}, + {0x5184, 0x02}, + {0x5185, 0x6c}, + {0x5189, 0x48}, + {0x5324, 0x09}, + {0x5325, 0x11}, + {0x5326, 0x1f}, + {0x5327, 0x3b}, + {0x5328, 0x49}, + {0x5329, 0x61}, + {0x532a, 0x9c}, + {0x532b, 0xc9}, + {0x5335, 0x04}, + {0x5336, 0x00}, + {0x5337, 0x04}, + {0x5338, 0x00}, + {0x5339, 0x0b}, + {0x533a, 0x00}, + {0x53a4, 0x09}, + {0x53a5, 0x11}, + {0x53a6, 0x1f}, + {0x53a7, 0x3b}, + {0x53a8, 0x49}, + {0x53a9, 0x61}, + {0x53aa, 0x9c}, + {0x53ab, 0xc9}, + {0x53b5, 0x04}, + {0x53b6, 0x00}, + {0x53b7, 0x04}, + {0x53b8, 0x00}, + {0x53b9, 0x0b}, + {0x53ba, 0x00}, + {0x580b, 0x03}, + {0x580d, 0x00}, + {0x580f, 0x00}, + {0x5820, 0x00}, + {0x5821, 0x00}, + {0x5888, 0x01}, + {REG_NULL, 0x00}, +}; + +static const struct os04e10_mode supported_modes[] = { + { + .width = 2048, + .height = 2048, + .max_fps = { + .numerator = 10000, + .denominator = 300000, + }, + .exp_def = 0x0080, + .hts_def = 0x0650 * 2, + .vts_def = 0x0874, + .bus_fmt = MEDIA_BUS_FMT_SBGGR10_1X10, + .reg_list = os04e10_linear_10_2048x2048_regs, + .hdr_mode = NO_HDR, + .vc[PAD0] = 0, + }, + { + .width = 2048, + .height = 2048, + .max_fps = { + .numerator = 10000, + .denominator = 150000, + }, + .exp_def = 0x0080, + .hts_def = 0x0634 * 2, + .vts_def = 0x089B, + .bus_fmt = MEDIA_BUS_FMT_SBGGR10_1X10, + .reg_list = os04e10_hdrx2_10_2048x2048_15fps_regs, + .hdr_mode = HDR_X2, + .vc[PAD0] = 1, + .vc[PAD1] = 0,//L->csi wr0 + .vc[PAD2] = 1, + .vc[PAD3] = 1,//M->csi wr2 + }, +}; + +static const s64 link_freq_menu_items[] = { + OS04E10_LINK_FREQ_900 +}; + +static const char * const os04e10_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* Write registers up to 4 at a time */ +static int os04e10_write_reg(struct i2c_client *client, u16 reg, + u32 len, u32 val) +{ + u32 buf_i, val_i; + u8 buf[6]; + u8 *val_p; + __be32 val_be; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val_be = cpu_to_be32(val); + val_p = (u8 *)&val_be; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + return 0; +} + +static int os04e10_write_array(struct i2c_client *client, + const struct regval *regs) +{ + u32 i; + int ret = 0; + + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) + ret = os04e10_write_reg(client, regs[i].addr, + OS04E10_REG_VALUE_08BIT, regs[i].val); + + return ret; +} + +/* Read registers up to 4 at a time */ +static int os04e10_read_reg(struct i2c_client *client, u16 reg, unsigned int len, + u32 *val) +{ + struct i2c_msg msgs[2]; + u8 *data_be_p; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + int ret; + + if (len > 4 || !len) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +/* mode: 0 = lgain 1 = sgain */ +static int os04e10_set_gain_reg(struct os04e10 *os04e10, u32 gain, int mode) +{ + struct i2c_client *client = os04e10->client; + u32 again = 0, dgain = 0; + int ret = 0; + + if (gain < OS04E10_GAIN_MIN) + gain = OS04E10_GAIN_MIN; + else if (gain > OS04E10_GAIN_MAX) + gain = OS04E10_GAIN_MAX; + + if (gain < 0x7c0) { + again = gain; + dgain = 0x400; + } else { + again = 0x7c0; + dgain = gain * 1024 / 0x7c0; + } + + dev_dbg(&client->dev, "again: 0x%02x, dgain: 0x%02x mode %d\n", + again, dgain, mode); + + if (mode == OS04E10_LGAIN) { + ret = os04e10_write_reg(os04e10->client, + OS04E10_REG_ANA_GAIN_H, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_AGAIN_H(again)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_ANA_GAIN_L, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_AGAIN_L(again)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_DIG_GAIN_H, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_DGAIN_H(dgain)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_DIG_GAIN_L, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_DGAIN_L(dgain)); + } else if (mode == OS04E10_SGAIN) { + ret = os04e10_write_reg(os04e10->client, + OS04E10_REG_ANA_GAIN_SHORT_H, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_AGAIN_H(again)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_ANA_GAIN_SHORT_L, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_AGAIN_L(again)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_DIG_GAIN_SHORT_H, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_DGAIN_H(dgain)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_DIG_GAIN_SHORT_L, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_DGAIN_L(dgain)); + } + + return ret; +} + +static int os04e10_set_hdrae(struct os04e10 *os04e10, + struct preisp_hdrae_exp_s *ae) +{ + int ret = 0; + u32 l_exp_time, m_exp_time, s_exp_time; + u32 l_a_gain, m_a_gain, s_a_gain; + + if (!os04e10->has_init_exp && !os04e10->streaming) { + os04e10->init_hdrae_exp = *ae; + os04e10->has_init_exp = true; + dev_dbg(&os04e10->client->dev, "OS04E10 don't stream, record exp for hdr!\n"); + return ret; + } + l_exp_time = ae->long_exp_reg; + m_exp_time = ae->middle_exp_reg; + s_exp_time = ae->short_exp_reg; + l_a_gain = ae->long_gain_reg; + m_a_gain = ae->middle_gain_reg; + s_a_gain = ae->short_gain_reg; + + dev_dbg(&os04e10->client->dev, + "rev exp req: L_exp: 0x%x, 0x%x, M_exp: 0x%x, 0x%x S_exp: 0x%x, 0x%x\n", + l_exp_time, l_a_gain, m_exp_time, + m_a_gain, s_exp_time, s_a_gain); + + if (os04e10->cur_mode->hdr_mode == HDR_X2) { + //2 stagger + l_a_gain = m_a_gain; + l_exp_time = m_exp_time; + } + + // (max_l_time = vts - 8) + if (l_exp_time > os04e10->cur_vts - 8) + l_exp_time = os04e10->cur_vts - 8; + // (max_s_time = vts - l_exp_time - 12) + if (s_exp_time > os04e10->cur_vts - l_exp_time - 12) + s_exp_time = os04e10->cur_vts - l_exp_time - 12; + ret = os04e10_write_reg(os04e10->client, + OS04E10_REG_EXPOSURE_H, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_EXP_H(l_exp_time)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_EXPOSURE_M, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_EXP_M(l_exp_time)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_EXPOSURE_L, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_EXP_L(l_exp_time)); + + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_EXPOSURE_SHORT_H, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_EXP_H(s_exp_time)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_EXPOSURE_SHORT_M, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_EXP_M(s_exp_time)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_EXPOSURE_SHORT_L, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_EXP_L(s_exp_time)); + + ret |= os04e10_set_gain_reg(os04e10, l_a_gain, OS04E10_LGAIN); + ret |= os04e10_set_gain_reg(os04e10, s_a_gain, OS04E10_SGAIN); + return ret; +} + +static int os04e10_get_reso_dist(const struct os04e10_mode *mode, + struct v4l2_mbus_framefmt *framefmt) +{ + return abs(mode->width - framefmt->width) + + abs(mode->height - framefmt->height); +} + +static const struct os04e10_mode * +os04e10_find_best_fit(struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt = &fmt->format; + int dist; + int cur_best_fit = 0; + int cur_best_fit_dist = -1; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) { + dist = os04e10_get_reso_dist(&supported_modes[i], framefmt); + if (cur_best_fit_dist == -1 || dist < cur_best_fit_dist) { + cur_best_fit_dist = dist; + cur_best_fit = i; + } + } + + return &supported_modes[cur_best_fit]; +} + +static int os04e10_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct os04e10 *os04e10 = to_os04e10(sd); + const struct os04e10_mode *mode; + s64 h_blank, vblank_def; + + mutex_lock(&os04e10->mutex); + + mode = os04e10_find_best_fit(fmt); + fmt->format.code = mode->bus_fmt; + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; +#else + mutex_unlock(&os04e10->mutex); + return -ENOTTY; +#endif + } else { + os04e10->cur_mode = mode; + h_blank = mode->hts_def - mode->width; + __v4l2_ctrl_modify_range(os04e10->hblank, h_blank, + h_blank, 1, h_blank); + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(os04e10->vblank, vblank_def, + OS04E10_VTS_MAX - mode->height, + 1, vblank_def); + os04e10->cur_fps = mode->max_fps; + os04e10->cur_vts = mode->vts_def; + } + + mutex_unlock(&os04e10->mutex); + + return 0; +} + +static int os04e10_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct os04e10 *os04e10 = to_os04e10(sd); + const struct os04e10_mode *mode = os04e10->cur_mode; + + mutex_lock(&os04e10->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + fmt->format = *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); +#else + mutex_unlock(&os04e10->mutex); + return -ENOTTY; +#endif + } else { + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = mode->bus_fmt; + fmt->format.field = V4L2_FIELD_NONE; + /* format info: width/height/data type/virctual channel */ + if (fmt->pad < PAD_MAX && mode->hdr_mode != NO_HDR) + fmt->reserved[0] = mode->vc[fmt->pad]; + else + fmt->reserved[0] = mode->vc[PAD0]; + } + mutex_unlock(&os04e10->mutex); + + return 0; +} + +static int os04e10_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct os04e10 *os04e10 = to_os04e10(sd); + + if (code->index != 0) + return -EINVAL; + code->code = os04e10->cur_mode->bus_fmt; + + return 0; +} + +static int os04e10_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != supported_modes[0].bus_fmt) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = supported_modes[fse->index].width; + fse->max_height = supported_modes[fse->index].height; + fse->min_height = supported_modes[fse->index].height; + + return 0; +} + +static int os04e10_enable_test_pattern(struct os04e10 *os04e10, u32 pattern) +{ + u32 val = 0; + int ret = 0; + + ret = os04e10_read_reg(os04e10->client, OS04E10_REG_TEST_PATTERN, + OS04E10_REG_VALUE_08BIT, &val); + if (pattern) + val |= OS04E10_TEST_PATTERN_BIT_MASK; + else + val &= ~OS04E10_TEST_PATTERN_BIT_MASK; + + ret |= os04e10_write_reg(os04e10->client, OS04E10_REG_TEST_PATTERN, + OS04E10_REG_VALUE_08BIT, val); + return ret; +} + +static int os04e10_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct os04e10 *os04e10 = to_os04e10(sd); + const struct os04e10_mode *mode = os04e10->cur_mode; + + if (os04e10->streaming) + fi->interval = os04e10->cur_fps; + else + fi->interval = mode->max_fps; + + return 0; +} + +static int os04e10_g_mbus_config(struct v4l2_subdev *sd, + unsigned int pad_id, + struct v4l2_mbus_config *config) +{ + config->type = V4L2_MBUS_CSI2_DPHY; + config->bus.mipi_csi2.num_data_lanes = OS04E10_LANES; + + return 0; +} + +static void os04e10_get_module_inf(struct os04e10 *os04e10, + struct rkmodule_inf *inf) +{ + memset(inf, 0, sizeof(*inf)); + strscpy(inf->base.sensor, OS04E10_NAME, sizeof(inf->base.sensor)); + strscpy(inf->base.module, os04e10->module_name, + sizeof(inf->base.module)); + strscpy(inf->base.lens, os04e10->len_name, sizeof(inf->base.lens)); +} + +static long os04e10_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + struct os04e10 *os04e10 = to_os04e10(sd); + struct rkmodule_hdr_cfg *hdr; + u32 i, h, w; + long ret = 0; + u32 stream = 0; + + switch (cmd) { + case RKMODULE_GET_MODULE_INFO: + os04e10_get_module_inf(os04e10, (struct rkmodule_inf *)arg); + break; + case RKMODULE_GET_HDR_CFG: + hdr = (struct rkmodule_hdr_cfg *)arg; + hdr->esp.mode = HDR_NORMAL_VC; + hdr->hdr_mode = os04e10->cur_mode->hdr_mode; + break; + case RKMODULE_SET_HDR_CFG: + hdr = (struct rkmodule_hdr_cfg *)arg; + w = os04e10->cur_mode->width; + h = os04e10->cur_mode->height; + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) { + if (w == supported_modes[i].width && + h == supported_modes[i].height && + supported_modes[i].hdr_mode == hdr->hdr_mode) { + os04e10->cur_mode = &supported_modes[i]; + break; + } + } + if (i == ARRAY_SIZE(supported_modes)) { + dev_err(&os04e10->client->dev, + "not find hdr mode:%d %dx%d config\n", + hdr->hdr_mode, w, h); + ret = -EINVAL; + } else { + w = os04e10->cur_mode->hts_def - os04e10->cur_mode->width; + h = os04e10->cur_mode->vts_def - os04e10->cur_mode->height; + __v4l2_ctrl_modify_range(os04e10->hblank, w, w, 1, w); + __v4l2_ctrl_modify_range(os04e10->vblank, h, + OS04E10_VTS_MAX - os04e10->cur_mode->height, 1, h); + os04e10->cur_fps = os04e10->cur_mode->max_fps; + os04e10->cur_vts = os04e10->cur_mode->vts_def; + } + break; + case PREISP_CMD_SET_HDRAE_EXP: + os04e10_set_hdrae(os04e10, arg); + break; + case RKMODULE_SET_QUICK_STREAM: + + stream = *((u32 *)arg); + + if (stream) + ret = os04e10_write_reg(os04e10->client, OS04E10_REG_CTRL_MODE, + OS04E10_REG_VALUE_08BIT, OS04E10_MODE_STREAMING); + else + ret = os04e10_write_reg(os04e10->client, OS04E10_REG_CTRL_MODE, + OS04E10_REG_VALUE_08BIT, OS04E10_MODE_SW_STANDBY); + break; + default: + ret = -ENOIOCTLCMD; + break; + } + + return ret; +} + +#ifdef CONFIG_COMPAT +static long os04e10_compat_ioctl32(struct v4l2_subdev *sd, + unsigned int cmd, unsigned long arg) +{ + void __user *up = compat_ptr(arg); + struct rkmodule_inf *inf; + struct rkmodule_hdr_cfg *hdr; + struct preisp_hdrae_exp_s *hdrae; + long ret; + u32 stream = 0; + + switch (cmd) { + case RKMODULE_GET_MODULE_INFO: + inf = kzalloc(sizeof(*inf), GFP_KERNEL); + if (!inf) { + ret = -ENOMEM; + return ret; + } + + ret = os04e10_ioctl(sd, cmd, inf); + if (!ret) { + if (copy_to_user(up, inf, sizeof(*inf))) + ret = -EFAULT; + } + kfree(inf); + break; + case RKMODULE_GET_HDR_CFG: + hdr = kzalloc(sizeof(*hdr), GFP_KERNEL); + if (!hdr) { + ret = -ENOMEM; + return ret; + } + + ret = os04e10_ioctl(sd, cmd, hdr); + if (!ret) { + if (copy_to_user(up, hdr, sizeof(*hdr))) + ret = -EFAULT; + } + kfree(hdr); + break; + case RKMODULE_SET_HDR_CFG: + hdr = kzalloc(sizeof(*hdr), GFP_KERNEL); + if (!hdr) { + ret = -ENOMEM; + return ret; + } + + ret = copy_from_user(hdr, up, sizeof(*hdr)); + if (!ret) + ret = os04e10_ioctl(sd, cmd, hdr); + else + ret = -EFAULT; + kfree(hdr); + break; + case PREISP_CMD_SET_HDRAE_EXP: + hdrae = kzalloc(sizeof(*hdrae), GFP_KERNEL); + if (!hdrae) { + ret = -ENOMEM; + return ret; + } + + ret = copy_from_user(hdrae, up, sizeof(*hdrae)); + if (!ret) + ret = os04e10_ioctl(sd, cmd, hdrae); + else + ret = -EFAULT; + kfree(hdrae); + break; + case RKMODULE_SET_QUICK_STREAM: + ret = copy_from_user(&stream, up, sizeof(u32)); + if (!ret) + ret = os04e10_ioctl(sd, cmd, &stream); + else + ret = -EFAULT; + break; + default: + ret = -ENOIOCTLCMD; + break; + } + + return ret; +} +#endif + +static int __os04e10_start_stream(struct os04e10 *os04e10) +{ + int ret; + + dev_info(&os04e10->client->dev, + "%dx%d@%d, mode %d, vts 0x%x\n", + os04e10->cur_mode->width, + os04e10->cur_mode->height, + os04e10->cur_fps.denominator / os04e10->cur_fps.numerator, + os04e10->cur_mode->hdr_mode, + os04e10->cur_vts); + + if (!os04e10->is_thunderboot) { + ret = os04e10_write_array(os04e10->client, os04e10->cur_mode->reg_list); + if (ret) + return ret; + /* In case these controls are set before streaming */ + ret = __v4l2_ctrl_handler_setup(&os04e10->ctrl_handler); + if (ret) + return ret; + if (os04e10->has_init_exp && os04e10->cur_mode->hdr_mode != NO_HDR) { + ret = os04e10_ioctl(&os04e10->subdev, PREISP_CMD_SET_HDRAE_EXP, + &os04e10->init_hdrae_exp); + if (ret) { + dev_err(&os04e10->client->dev, + "init exp fail in hdr mode\n"); + return ret; + } + } + } + return os04e10_write_reg(os04e10->client, OS04E10_REG_CTRL_MODE, + OS04E10_REG_VALUE_08BIT, OS04E10_MODE_STREAMING); +} + +static int __os04e10_stop_stream(struct os04e10 *os04e10) +{ + os04e10->has_init_exp = false; + if (os04e10->is_thunderboot) { + os04e10->is_first_streamoff = true; + pm_runtime_put(&os04e10->client->dev); + } + return os04e10_write_reg(os04e10->client, OS04E10_REG_CTRL_MODE, + OS04E10_REG_VALUE_08BIT, OS04E10_MODE_SW_STANDBY); +} + +static int __os04e10_power_on(struct os04e10 *os04e10); +static int os04e10_s_stream(struct v4l2_subdev *sd, int on) +{ + struct os04e10 *os04e10 = to_os04e10(sd); + struct i2c_client *client = os04e10->client; + int ret = 0; + + mutex_lock(&os04e10->mutex); + on = !!on; + if (on == os04e10->streaming) + goto unlock_and_return; + if (on) { + if (os04e10->is_thunderboot && rkisp_tb_get_state() == RKISP_TB_NG) { + os04e10->is_thunderboot = false; + __os04e10_power_on(os04e10); + } + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + ret = __os04e10_start_stream(os04e10); + if (ret) { + v4l2_err(sd, "start stream failed while write regs\n"); + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + } else { + __os04e10_stop_stream(os04e10); + pm_runtime_put(&client->dev); + } + + os04e10->streaming = on; +unlock_and_return: + mutex_unlock(&os04e10->mutex); + return ret; +} + +static int os04e10_s_power(struct v4l2_subdev *sd, int on) +{ + struct os04e10 *os04e10 = to_os04e10(sd); + struct i2c_client *client = os04e10->client; + int ret = 0; + + mutex_lock(&os04e10->mutex); + + /* If the power state is not modified - no work to do. */ + if (os04e10->power_on == !!on) + goto unlock_and_return; + + if (on) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + + if (!os04e10->is_thunderboot) { + ret = os04e10_write_array(os04e10->client, os04e10_global_regs); + if (ret) { + v4l2_err(sd, "could not set init registers\n"); + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + } + + os04e10->power_on = true; + } else { + pm_runtime_put(&client->dev); + os04e10->power_on = false; + } + +unlock_and_return: + mutex_unlock(&os04e10->mutex); + + return ret; +} + +/* Calculate the delay in us by clock rate and clock cycles */ +static inline u32 os04e10_cal_delay(u32 cycles) +{ + return DIV_ROUND_UP(cycles, OS04E10_XVCLK_FREQ / 1000 / 1000); +} + +static int __os04e10_power_on(struct os04e10 *os04e10) +{ + int ret; + u32 delay_us; + struct device *dev = &os04e10->client->dev; + + if (!IS_ERR_OR_NULL(os04e10->pins_default)) { + ret = pinctrl_select_state(os04e10->pinctrl, + os04e10->pins_default); + if (ret < 0) + dev_err(dev, "could not set pins\n"); + } + ret = clk_set_rate(os04e10->xvclk, OS04E10_XVCLK_FREQ); + if (ret < 0) + dev_warn(dev, "Failed to set xvclk rate (24MHz)\n"); + if (clk_get_rate(os04e10->xvclk) != OS04E10_XVCLK_FREQ) + dev_warn(dev, "xvclk mismatched, modes are based on 24MHz\n"); + ret = clk_prepare_enable(os04e10->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + + if (os04e10->is_thunderboot) + return 0; + + if (!IS_ERR(os04e10->reset_gpio)) + gpiod_set_value_cansleep(os04e10->reset_gpio, 0); + + ret = regulator_bulk_enable(OS04E10_NUM_SUPPLIES, os04e10->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + goto disable_clk; + } + + if (!IS_ERR(os04e10->reset_gpio)) + gpiod_set_value_cansleep(os04e10->reset_gpio, 1); + + usleep_range(500, 1000); + + if (!IS_ERR(os04e10->pwdn_gpio)) + gpiod_set_value_cansleep(os04e10->pwdn_gpio, 1); + + if (!IS_ERR(os04e10->reset_gpio)) + usleep_range(6000, 8000); + else + usleep_range(12000, 16000); + + /* 8192 cycles prior to first SCCB transaction */ + delay_us = os04e10_cal_delay(8192); + usleep_range(delay_us, delay_us * 2); + + return 0; + +disable_clk: + clk_disable_unprepare(os04e10->xvclk); + + return ret; +} + +static void __os04e10_power_off(struct os04e10 *os04e10) +{ + int ret; + struct device *dev = &os04e10->client->dev; + + clk_disable_unprepare(os04e10->xvclk); + if (os04e10->is_thunderboot) { + if (os04e10->is_first_streamoff) { + os04e10->is_thunderboot = false; + os04e10->is_first_streamoff = false; + } else { + return; + } + } + + if (!IS_ERR(os04e10->pwdn_gpio)) + gpiod_set_value_cansleep(os04e10->pwdn_gpio, 0); + clk_disable_unprepare(os04e10->xvclk); + if (!IS_ERR(os04e10->reset_gpio)) + gpiod_set_value_cansleep(os04e10->reset_gpio, 0); + if (!IS_ERR_OR_NULL(os04e10->pins_sleep)) { + ret = pinctrl_select_state(os04e10->pinctrl, + os04e10->pins_sleep); + if (ret < 0) + dev_dbg(dev, "could not set pins\n"); + } + regulator_bulk_disable(OS04E10_NUM_SUPPLIES, os04e10->supplies); +} + +static int os04e10_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct os04e10 *os04e10 = to_os04e10(sd); + + return __os04e10_power_on(os04e10); +} + +static int os04e10_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct os04e10 *os04e10 = to_os04e10(sd); + + __os04e10_power_off(os04e10); + + return 0; +} + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static int os04e10_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct os04e10 *os04e10 = to_os04e10(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->state, 0); + const struct os04e10_mode *def_mode = &supported_modes[0]; + + mutex_lock(&os04e10->mutex); + /* Initialize try_fmt */ + try_fmt->width = def_mode->width; + try_fmt->height = def_mode->height; + try_fmt->code = def_mode->bus_fmt; + try_fmt->field = V4L2_FIELD_NONE; + + mutex_unlock(&os04e10->mutex); + /* No crop or compose */ + + return 0; +} +#endif + +static int os04e10_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + if (fie->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + fie->code = supported_modes[fie->index].bus_fmt; + fie->width = supported_modes[fie->index].width; + fie->height = supported_modes[fie->index].height; + fie->interval = supported_modes[fie->index].max_fps; + fie->reserved[0] = supported_modes[fie->index].hdr_mode; + return 0; +} + +static const struct dev_pm_ops os04e10_pm_ops = { + SET_RUNTIME_PM_OPS(os04e10_runtime_suspend, os04e10_runtime_resume, NULL) +}; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static const struct v4l2_subdev_internal_ops os04e10_internal_ops = { + .open = os04e10_open, +}; +#endif + +static const struct v4l2_subdev_core_ops os04e10_core_ops = { + .s_power = os04e10_s_power, + .ioctl = os04e10_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = os04e10_compat_ioctl32, +#endif +}; + +static const struct v4l2_subdev_video_ops os04e10_video_ops = { + .s_stream = os04e10_s_stream, + .g_frame_interval = os04e10_g_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops os04e10_pad_ops = { + .enum_mbus_code = os04e10_enum_mbus_code, + .enum_frame_size = os04e10_enum_frame_sizes, + .enum_frame_interval = os04e10_enum_frame_interval, + .get_fmt = os04e10_get_fmt, + .set_fmt = os04e10_set_fmt, + .get_mbus_config = os04e10_g_mbus_config, +}; + +static const struct v4l2_subdev_ops os04e10_subdev_ops = { + .core = &os04e10_core_ops, + .video = &os04e10_video_ops, + .pad = &os04e10_pad_ops, +}; + +static void os04e10_modify_fps_info(struct os04e10 *os04e10) +{ + const struct os04e10_mode *mode = os04e10->cur_mode; + + os04e10->cur_fps.denominator = mode->max_fps.denominator * mode->vts_def / + os04e10->cur_vts; +} + +static int os04e10_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct os04e10 *os04e10 = container_of(ctrl->handler, + struct os04e10, ctrl_handler); + struct i2c_client *client = os04e10->client; + s64 max; + int ret = 0; + u32 val = 0; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = os04e10->cur_mode->height + ctrl->val - 8; + __v4l2_ctrl_modify_range(os04e10->exposure, + os04e10->exposure->minimum, max, + os04e10->exposure->step, + os04e10->exposure->default_value); + break; + } + + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + dev_dbg(&client->dev, "set exposure 0x%x\n", ctrl->val); + if (os04e10->cur_mode->hdr_mode == NO_HDR) { + val = ctrl->val; + /* 4 least significant bits of expsoure are fractional part */ + ret = os04e10_write_reg(os04e10->client, + OS04E10_REG_EXPOSURE_H, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_EXP_H(val)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_EXPOSURE_M, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_EXP_M(val)); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_EXPOSURE_L, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_EXP_L(val)); + } + break; + case V4L2_CID_ANALOGUE_GAIN: + dev_dbg(&client->dev, + "set gain 0x%x mode %d\n", + ctrl->val, + os04e10->cur_mode->hdr_mode); + if (os04e10->cur_mode->hdr_mode == NO_HDR) + ret = os04e10_set_gain_reg(os04e10, ctrl->val, OS04E10_LGAIN); + break; + case V4L2_CID_VBLANK: + dev_dbg(&client->dev, "set vblank 0x%x\n", ctrl->val); + ret = os04e10_write_reg(os04e10->client, + OS04E10_REG_VTS_H, + OS04E10_REG_VALUE_08BIT, + ((ctrl->val + os04e10->cur_mode->height) + >> 8) & 0x7F); + ret |= os04e10_write_reg(os04e10->client, + OS04E10_REG_VTS_L, + OS04E10_REG_VALUE_08BIT, + (ctrl->val + os04e10->cur_mode->height) + & 0xff); + os04e10->cur_vts = ctrl->val + os04e10->cur_mode->height; + os04e10_modify_fps_info(os04e10); + break; + case V4L2_CID_TEST_PATTERN: + ret = os04e10_enable_test_pattern(os04e10, ctrl->val); + break; + case V4L2_CID_HFLIP: + dev_dbg(&client->dev, "set hflip 0x%x\n", ctrl->val); + os04e10_read_reg(os04e10->client, + OS04E10_FLIP_MIRROR_REG, + OS04E10_REG_VALUE_08BIT, + &val); + os04e10_write_reg(os04e10->client, 0x0100, OS04E10_REG_VALUE_08BIT, 0x00); + os04e10_write_reg(os04e10->client, 0x3716, OS04E10_REG_VALUE_08BIT, 0x24); + os04e10_write_reg(os04e10->client, + OS04E10_FLIP_MIRROR_REG, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_MIRROR(val, ctrl->val)); + os04e10_write_reg(os04e10->client, 0x0100, OS04E10_REG_VALUE_08BIT, 0x01); + break; + case V4L2_CID_VFLIP: + dev_dbg(&client->dev, "set vflip 0x%x\n", ctrl->val); + os04e10_read_reg(os04e10->client, + OS04E10_FLIP_MIRROR_REG, + OS04E10_REG_VALUE_08BIT, + &val); + os04e10_write_reg(os04e10->client, 0x0100, OS04E10_REG_VALUE_08BIT, 0x00); + os04e10_write_reg(os04e10->client, + 0x3716, + OS04E10_REG_VALUE_08BIT, + ctrl->val ? 0x04 : 0x24); + os04e10_write_reg(os04e10->client, + OS04E10_FLIP_MIRROR_REG, + OS04E10_REG_VALUE_08BIT, + OS04E10_FETCH_FLIP(val, ctrl->val)); + os04e10_write_reg(os04e10->client, 0x0100, OS04E10_REG_VALUE_08BIT, 0x01); + break; + default: + dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", + __func__, ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops os04e10_ctrl_ops = { + .s_ctrl = os04e10_set_ctrl, +}; + +static int os04e10_initialize_controls(struct os04e10 *os04e10) +{ + const struct os04e10_mode *mode; + struct v4l2_ctrl_handler *handler; + struct v4l2_ctrl *ctrl; + s64 exposure_max, vblank_def; + u32 h_blank; + int ret; + + handler = &os04e10->ctrl_handler; + mode = os04e10->cur_mode; + ret = v4l2_ctrl_handler_init(handler, 9); + if (ret) + return ret; + handler->lock = &os04e10->mutex; + + ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, + 0, 0, link_freq_menu_items); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, + 0, PIXEL_RATE_WITH_900M_10BIT, 1, PIXEL_RATE_WITH_900M_10BIT); + + h_blank = mode->hts_def - mode->width; + os04e10->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, + h_blank, h_blank, 1, h_blank); + if (os04e10->hblank) + os04e10->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + vblank_def = mode->vts_def - mode->height; + os04e10->vblank = v4l2_ctrl_new_std(handler, &os04e10_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + OS04E10_VTS_MAX - mode->height, + 1, vblank_def); + exposure_max = mode->vts_def - 8; + os04e10->exposure = v4l2_ctrl_new_std(handler, &os04e10_ctrl_ops, + V4L2_CID_EXPOSURE, OS04E10_EXPOSURE_MIN, + exposure_max, OS04E10_EXPOSURE_STEP, + mode->exp_def); + os04e10->anal_gain = v4l2_ctrl_new_std(handler, &os04e10_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, OS04E10_GAIN_MIN, + OS04E10_GAIN_MAX, OS04E10_GAIN_STEP, + OS04E10_GAIN_DEFAULT); + os04e10->test_pattern = v4l2_ctrl_new_std_menu_items(handler, + &os04e10_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(os04e10_test_pattern_menu) - 1, + 0, 0, os04e10_test_pattern_menu); + v4l2_ctrl_new_std(handler, &os04e10_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(handler, &os04e10_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (handler->error) { + ret = handler->error; + dev_err(&os04e10->client->dev, + "Failed to init controls(%d)\n", ret); + goto err_free_handler; + } + + os04e10->subdev.ctrl_handler = handler; + os04e10->has_init_exp = false; + os04e10->cur_fps = mode->max_fps; + + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(handler); + + return ret; +} + +static int os04e10_check_sensor_id(struct os04e10 *os04e10, + struct i2c_client *client) +{ + struct device *dev = &os04e10->client->dev; + u32 id = 0; + int ret; + + if (os04e10->is_thunderboot) { + dev_info(dev, "Enable thunderboot mode, skip sensor id check\n"); + return 0; + } + + ret = os04e10_read_reg(client, OS04E10_REG_CHIP_ID, + OS04E10_REG_VALUE_16BIT, &id); + if (id != CHIP_ID) { + dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret); + return -ENODEV; + } + + dev_info(dev, "Detected OV%06x sensor\n", CHIP_ID); + + return 0; +} + +static int os04e10_configure_regulators(struct os04e10 *os04e10) +{ + unsigned int i; + + for (i = 0; i < OS04E10_NUM_SUPPLIES; i++) + os04e10->supplies[i].supply = os04e10_supply_names[i]; + + return devm_regulator_bulk_get(&os04e10->client->dev, + OS04E10_NUM_SUPPLIES, + os04e10->supplies); +} + +#ifdef CONFIG_VIDEO_ROCKCHIP_THUNDER_BOOT_ISP +static void find_terminal_resolution(struct os04e10 *os04e10) +{ + int i = 0; + const struct os04e10_mode *mode = NULL; + const struct os04e10_mode *fit_mode = NULL; + u32 cur_fps = 0; + u32 dst_fps = 0; + u32 tmp_fps = 0; + u32 rk_cam_hdr, rk_cam_w, rk_cam_h, rk_cam_fps; + + rk_cam_hdr = get_rk_cam_hdr(); + rk_cam_w = get_rk_cam_w(); + rk_cam_h = get_rk_cam_h(); + rk_cam_fps = get_rk_cam_fps(); + + if (rk_cam_w == 0 || rk_cam_h == 0 || + rk_cam_fps == 0) + goto err_find_res; + + dev_info(&os04e10->client->dev, "find resolution width: %d, height: %d, hdr: %d, fps: %d\n", + rk_cam_w, rk_cam_h, rk_cam_hdr, rk_cam_fps); + dst_fps = rk_cam_fps; + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) { + mode = &supported_modes[i]; + cur_fps = mode->max_fps.denominator / mode->max_fps.numerator; + if (mode->width == rk_cam_w && mode->height == rk_cam_h && + mode->hdr_mode == rk_cam_hdr) { + if (cur_fps == dst_fps) { + os04e10->cur_mode = mode; + return; + } + if (cur_fps >= dst_fps) { + if (fit_mode) { + tmp_fps = fit_mode->max_fps.denominator / + fit_mode->max_fps.numerator; + if (tmp_fps - dst_fps > cur_fps - dst_fps) + fit_mode = mode; + } else { + fit_mode = mode; + } + } + } + } + if (fit_mode) { + os04e10->cur_mode = fit_mode; + return; + } +err_find_res: + dev_err(&os04e10->client->dev, "not match %dx%d@%dfps mode %d\n!", + rk_cam_w, rk_cam_h, dst_fps, rk_cam_hdr); + os04e10->cur_mode = &supported_modes[0]; +} +#else +static void find_terminal_resolution(struct os04e10 *os04e10) +{ + u32 hdr_mode = 0; + struct device_node *node = os04e10->client->dev.of_node; + int i = 0; + + of_property_read_u32(node, OF_CAMERA_HDR_MODE, &hdr_mode); + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) { + if (hdr_mode == supported_modes[i].hdr_mode) { + os04e10->cur_mode = &supported_modes[i]; + break; + } + } + if (i == ARRAY_SIZE(supported_modes)) + os04e10->cur_mode = &supported_modes[0]; + +} +#endif + +static int os04e10_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device_node *node = dev->of_node; + struct os04e10 *os04e10; + struct v4l2_subdev *sd; + char facing[2]; + int ret; + + dev_info(dev, "driver version: %02x.%02x.%02x", + DRIVER_VERSION >> 16, + (DRIVER_VERSION & 0xff00) >> 8, + DRIVER_VERSION & 0x00ff); + + os04e10 = devm_kzalloc(dev, sizeof(*os04e10), GFP_KERNEL); + if (!os04e10) + return -ENOMEM; + + ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX, + &os04e10->module_index); + ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING, + &os04e10->module_facing); + ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME, + &os04e10->module_name); + ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME, + &os04e10->len_name); + if (ret) { + dev_err(dev, "could not get module information!\n"); + return -EINVAL; + } + + os04e10->is_thunderboot = IS_ENABLED(CONFIG_VIDEO_ROCKCHIP_THUNDER_BOOT_ISP); + + os04e10->client = client; + + find_terminal_resolution(os04e10); + + os04e10->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(os04e10->xvclk)) { + dev_err(dev, "Failed to get xvclk\n"); + return -EINVAL; + } + + os04e10->reset_gpio = devm_gpiod_get(dev, "reset", + os04e10->is_thunderboot ? GPIOD_ASIS : GPIOD_OUT_LOW); + if (IS_ERR(os04e10->reset_gpio)) + dev_warn(dev, "Failed to get reset-gpios\n"); + + os04e10->pwdn_gpio = devm_gpiod_get(dev, "pwdn", + os04e10->is_thunderboot ? GPIOD_ASIS : GPIOD_OUT_LOW); + if (IS_ERR(os04e10->pwdn_gpio)) + dev_warn(dev, "Failed to get pwdn-gpios\n"); + + os04e10->pinctrl = devm_pinctrl_get(dev); + if (!IS_ERR(os04e10->pinctrl)) { + os04e10->pins_default = + pinctrl_lookup_state(os04e10->pinctrl, + OF_CAMERA_PINCTRL_STATE_DEFAULT); + if (IS_ERR(os04e10->pins_default)) + dev_err(dev, "could not get default pinstate\n"); + + os04e10->pins_sleep = + pinctrl_lookup_state(os04e10->pinctrl, + OF_CAMERA_PINCTRL_STATE_SLEEP); + if (IS_ERR(os04e10->pins_sleep)) + dev_err(dev, "could not get sleep pinstate\n"); + } else { + dev_err(dev, "no pinctrl\n"); + } + + ret = os04e10_configure_regulators(os04e10); + if (ret) { + dev_err(dev, "Failed to get power regulators\n"); + return ret; + } + + mutex_init(&os04e10->mutex); + + sd = &os04e10->subdev; + v4l2_i2c_subdev_init(sd, client, &os04e10_subdev_ops); + ret = os04e10_initialize_controls(os04e10); + if (ret) + goto err_destroy_mutex; + + ret = __os04e10_power_on(os04e10); + if (ret) + goto err_free_handler; + + ret = os04e10_check_sensor_id(os04e10, client); + if (ret) + goto err_power_off; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + sd->internal_ops = &os04e10_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; +#endif +#if defined(CONFIG_MEDIA_CONTROLLER) + os04e10->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &os04e10->pad); + if (ret < 0) + goto err_power_off; +#endif + + memset(facing, 0, sizeof(facing)); + if (strcmp(os04e10->module_facing, "back") == 0) + facing[0] = 'b'; + else + facing[0] = 'f'; + + snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s", + os04e10->module_index, facing, + OS04E10_NAME, dev_name(sd->dev)); + ret = v4l2_async_register_subdev_sensor(sd); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_clean_entity; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + if (os04e10->is_thunderboot) + pm_runtime_get_sync(dev); + else + pm_runtime_idle(dev); + + return 0; + +err_clean_entity: +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif +err_power_off: + __os04e10_power_off(os04e10); +err_free_handler: + v4l2_ctrl_handler_free(&os04e10->ctrl_handler); +err_destroy_mutex: + mutex_destroy(&os04e10->mutex); + + return ret; +} + +static void os04e10_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct os04e10 *os04e10 = to_os04e10(sd); + + v4l2_async_unregister_subdev(sd); +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif + v4l2_ctrl_handler_free(&os04e10->ctrl_handler); + mutex_destroy(&os04e10->mutex); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + __os04e10_power_off(os04e10); + pm_runtime_set_suspended(&client->dev); +} + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id os04e10_of_match[] = { + { .compatible = "OmniVision,os04e10" }, + {}, +}; +MODULE_DEVICE_TABLE(of, os04e10_of_match); +#endif + +static const struct i2c_device_id os04e10_match_id[] = { + { "OmniVision,os04e10", 0 }, + { }, +}; + +static struct i2c_driver os04e10_i2c_driver = { + .driver = { + .name = OS04E10_NAME, + .pm = &os04e10_pm_ops, + .of_match_table = of_match_ptr(os04e10_of_match), + }, + .probe = &os04e10_probe, + .remove = &os04e10_remove, + .id_table = os04e10_match_id, +}; + +static int __init sensor_mod_init(void) +{ + return i2c_add_driver(&os04e10_i2c_driver); +} + +static void __exit sensor_mod_exit(void) +{ + i2c_del_driver(&os04e10_i2c_driver); +} + +#if defined(CONFIG_VIDEO_ROCKCHIP_THUNDER_BOOT_ISP) +subsys_initcall(sensor_mod_init); +#else +device_initcall_sync(sensor_mod_init); +#endif +module_exit(sensor_mod_exit); + +MODULE_DESCRIPTION("OmniVision os04e10 sensor driver"); +MODULE_LICENSE("GPL"); From 0c442bded1090877bc54b1a2b081752673374838 Mon Sep 17 00:00:00 2001 From: LongChang Ma Date: Tue, 29 Jul 2025 20:17:47 +0800 Subject: [PATCH 13/14] media: i2c: add support sc485sl sensor driver Signed-off-by: LongChang Ma Change-Id: I6893ddf907860c4f4f48365e5e0422c7bfee67d5 --- drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/sc485sl.c | 3291 +++++++++++++++++++++++++++++++++++ 3 files changed, 3302 insertions(+) create mode 100644 drivers/media/i2c/sc485sl.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index f8ccbabe73cd..fd0602b82cd1 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -2109,6 +2109,16 @@ config VIDEO_SC450AI This is a Video4Linux2 sensor driver for the SmartSens SC450ai camera. +config VIDEO_SC485SL + tristate "SmartSens SC485SL sensor support" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the SmartSens + sc485sl camera. + config VIDEO_SC500AI tristate "SmartSens SC500AI sensor support" depends on I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 6a10ddfba1ab..ef52d805886d 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -258,6 +258,7 @@ obj-$(CONFIG_VIDEO_SC430CS) += sc430cs.o obj-$(CONFIG_VIDEO_SC4336) += sc4336.o obj-$(CONFIG_VIDEO_SC4336P) += sc4336p.o obj-$(CONFIG_VIDEO_SC450AI) += sc450ai.o +obj-$(CONFIG_VIDEO_SC485SL) += sc485sl.o obj-$(CONFIG_VIDEO_SC500AI) += sc500ai.o obj-$(CONFIG_VIDEO_SC501AI) += sc501ai.o obj-$(CONFIG_VIDEO_SC530AI) += sc530ai.o diff --git a/drivers/media/i2c/sc485sl.c b/drivers/media/i2c/sc485sl.c new file mode 100644 index 000000000000..79fc66a5c11a --- /dev/null +++ b/drivers/media/i2c/sc485sl.c @@ -0,0 +1,3291 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * sc485sl driver + * + * Copyright (C) 2025 Rockchip Electronics Co., Ltd. + * + * V0.0X01.0X01 first version + * support thunderboot + * support sleep wake-up mode + * V0.0X01.0X02 support 4lane 30fps setting + * V0.0X01.0X03 support 2lane 60fps setting + */ + +// #define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../platform/rockchip/isp/rkisp_tb_helper.h" +#include "cam-tb-setup.h" +#include "cam-sleep-wakeup.h" + +#define DRIVER_VERSION KERNEL_VERSION(0, 0x01, 0x03) + +#ifndef V4L2_CID_DIGITAL_GAIN +#define V4L2_CID_DIGITAL_GAIN V4L2_CID_GAIN +#endif + +#define SC485SL_BITS_PER_SAMPLE 10 +#define SC485SL_LINK_FREQ_396 396000000 /* 792Mbps pre lane*/ +#define SC485SL_LINK_FREQ_360 360000000 /* 720Mbps pre lane*/ +#define SC485SL_LINK_FREQ_540 540000000 /* 1080Mbps pre lane*/ +#define SC485SL_LINK_FREQ_720 720000000 /* 1440Mbps pre lane*/ + +#define OF_CAMERA_HDR_MODE "rockchip,camera-hdr-mode" + +/* 2 lane */ +/* 720Mbps pre lane*/ +#define PIXEL_RATE_WITH_360M_10BIT_2L (SC485SL_LINK_FREQ_360 * 2 * \ + 2 / SC485SL_BITS_PER_SAMPLE) + +/* 1440Mbps pre lane */ +#define PIXEL_RATE_WITH_720M_10BIT_2L (SC485SL_LINK_FREQ_720 * 2 / \ + SC485SL_BITS_PER_SAMPLE * 2) + +/* 4 lane */ +/* 720Mbps pre lane*/ +#define PIXEL_RATE_WITH_396M_10BIT_4L (SC485SL_LINK_FREQ_396 * 2 / \ + SC485SL_BITS_PER_SAMPLE * 4) +/* 1080Mbps pre lane */ +#define PIXEL_RATE_WITH_540M_10BIT_4L (SC485SL_LINK_FREQ_540 * 2 / \ + SC485SL_BITS_PER_SAMPLE * 4) + +#define SC485SL_XVCLK_FREQ 27000000 + +#define CHIP_ID 0xbd82 +#define SC485SL_REG_CHIP_ID 0x3107 + +#define SC485SL_REG_MIPI_CTRL 0x3019 +#define SC485SL_MIPI_CTRL_ON 0x00 +#define SC485SL_MIPI_CTRL_OFF 0xff + +#define SC485SL_REG_CTRL_MODE 0x0100 +#define SC485SL_MODE_SW_STANDBY 0x0 +#define SC485SL_MODE_STREAMING BIT(0) + +#define SC485SL_REG_EXPOSURE_H 0x3e00 +#define SC485SL_REG_EXPOSURE_M 0x3e01 +#define SC485SL_REG_EXPOSURE_L 0x3e02 +#define SC485SL_REG_SEXPOSURE_H 0x3e22 +#define SC485SL_REG_SEXPOSURE_M 0x3e04 +#define SC485SL_REG_SEXPOSURE_L 0x3e05 + +#define SC485SL_EXPOSURE_MIN 1 +#define SC485SL_EXPOSURE_STEP 1 +#define SC485SL_VTS_MAX 0x3ffff0 + +#define SC485SL_REG_DIG_GAIN 0x3e06 +#define SC485SL_REG_DIG_FINE_GAIN 0x3e07 +#define SC485SL_REG_ANA_GAIN 0x3e08 +#define SC485SL_REG_ANA_FINE_GAIN 0x3e09 +#define SC485SL_REG_SDIG_GAIN 0x3e10 +#define SC485SL_REG_SDIG_FINE_GAIN 0x3e11 +#define SC485SL_REG_SANA_GAIN 0x3e12 +#define SC485SL_REG_SANA_FINE_GAIN 0x3e13 +#define SC485SL_GAIN_MIN 0x0040 +#define SC485SL_GAIN_MAX 109133 // 107.415*15.875*64 = 109133 +#define SC485SL_GAIN_STEP 1 +#define SC485SL_GAIN_DEFAULT 0x0040 +#define SC485SL_LGAIN 0 +#define SC485SL_SGAIN 1 + +#define SC485SL_REG_GROUP_HOLD 0x3812 +#define SC485SL_GROUP_HOLD_START 0x00 // start hold +#define SC485SL_GROUP_HOLD_END 0x30 // release hold +#define SC485SL_REG_HOLD_DELAY 0x3802 //effective after group hold + +/* led strobe mode 1*/ +#define SC485SL_REG_LED_STROBE_EN_M1 0x3362 // 0x00: auto mode; 0x01: manuale mode; +#define SC485SL_REG_LED_STROBE_OUTPUT_PIN0_M1 0x300a // [2:1, 6], use fsync as output single pin +#define SC485SL_REG_LED_STROBE_OUTPUT_PIN1_M1 0x3033 // [1] +#define SC485SL_REG_LED_STROBE_OUTPUT_PIN2_M1 0x3035 // 0x00 +#define SC485SL_REG_LED_STROBE_PUSLE_START_H 0x3382 // start at {16’h320e,16’h320f} – 1 –{16’h3382,16’h3383} +#define SC485SL_REG_LED_STROBE_PUSLE_START_L 0x3383 +#define SC485SL_REG_LED_STROBE_PUSLE_END_H 0x3386 // end at {16’h320e,16’h320f} – 1 –{16’h3386,16’h3387} +#define SC485SL_REG_LED_STROBE_PUSLE_END_L 0x3387 +/* led strobe mode 2 */ +#define SC485SL_REG_LED_STROBE_EN_M2 0x4d0b // 0x00: disable; 0x01: enable +#define SC485SL_REG_LED_STROBE_OUTPUT_PIN0_M2 0x300a // [2:1], use fsync as output single pin +#define SC485SL_REG_LED_STROBE_OUTPUT_PIN1_M2 0x3033 // [1] +#define SC485SL_REG_LED_STROBE_OUTPUT_PIN2_M2 0x3035 // 0x00 +#define SC485SL_REG_LED_STROBE_PUSLE_WIDTH_H 0x4d0c // use {16’h320c,16’h320d} as unit +#define SC485SL_REG_LED_STROBE_PUSLE_WIDTH_L 0x4d0d + +#define SC485SL_REG_TEST_PATTERN 0x4501 +#define SC485SL_TEST_PATTERN_BIT_MASK BIT(3) // 0 -normal image; 1 - increasing gradient pattern + +/* max frame length 0x3ffff0 */ +#define SC485SL_REG_VTS_H 0x326d // [5:0] +#define SC485SL_REG_VTS_M 0x320e +#define SC485SL_REG_VTS_L 0x320f + +#define SC485SL_FLIP_MIRROR_REG 0x3221 + +#define SC485SL_FETCH_EXP_H(VAL) (((VAL) >> 12) & 0xF) +#define SC485SL_FETCH_EXP_M(VAL) (((VAL) >> 4) & 0xFF) +#define SC485SL_FETCH_EXP_L(VAL) (((VAL) & 0xF) << 4) + +#define SC485SL_FETCH_MIRROR(VAL, ENABLE) (ENABLE ? VAL | 0x06 : VAL & 0xf9) +#define SC485SL_FETCH_FLIP(VAL, ENABLE) (ENABLE ? VAL | 0x60 : VAL & 0x9f) + +#define REG_DELAY 0xFFFE +#define REG_NULL 0xFFFF + +#define SC485SL_REG_VALUE_08BIT 1 +#define SC485SL_REG_VALUE_16BIT 2 +#define SC485SL_REG_VALUE_24BIT 3 + +#define OF_CAMERA_PINCTRL_STATE_DEFAULT "rockchip,camera_default" +#define OF_CAMERA_PINCTRL_STATE_SLEEP "rockchip,camera_sleep" +#define SC485SL_NAME "sc485sl" + +static const char *const sc485sl_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +#define SC485SL_NUM_SUPPLIES ARRAY_SIZE(sc485sl_supply_names) + +struct regval { + u16 addr; + u8 val; +}; + +struct sc485sl_mode { + u32 bus_fmt; + u32 width; + u32 height; + struct v4l2_fract max_fps; + u32 hts_def; + u32 vts_def; + u32 exp_def; + const struct regval *global_reg_list; + const struct regval *reg_list; + u32 hdr_mode; + u32 mclk; + u32 link_freq_idx; + u32 vc[PAD_MAX]; + u8 bpp; + u32 lanes; +}; + +struct sc485sl { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + struct regulator_bulk_data supplies[SC485SL_NUM_SUPPLIES]; + + struct pinctrl *pinctrl; + struct pinctrl_state *pins_default; + struct pinctrl_state *pins_sleep; + + struct v4l2_subdev subdev; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *anal_gain; + struct v4l2_ctrl *digi_gain; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *test_pattern; + struct mutex mutex; + struct v4l2_fract cur_fps; + bool streaming; + bool power_on; + const struct sc485sl_mode *supported_modes; + const struct sc485sl_mode *cur_mode; + u32 cfg_num; + u32 module_index; + const char *module_facing; + const char *module_name; + const char *len_name; + u32 standby_hw; + u32 cur_vts; + bool has_init_exp; + bool is_thunderboot; + bool is_first_streamoff; + bool is_standby; + struct preisp_hdrae_exp_s init_hdrae_exp; + struct cam_sw_info *cam_sw_inf; + struct v4l2_fwnode_endpoint bus_cfg; +}; + +#define to_sc485sl(sd) container_of(sd, struct sc485sl, subdev) + +/* + * Xclk 24Mhz + */ +static const struct regval sc485sl_global_4lane_regs[] = { + {REG_NULL, 0x00}, +}; + +/* + * Xclk 27Mhz + * max_framerate 30fps + * mipi_datarate per lane 720Mbps, 4lane + * linear: 2688x1520 + * Cleaned_0x02_SC485SL_raw_MIPI_27Minput_4Lane_10bit_792Mbps_2688x1520_30fps_20250418.ini + */ +static const struct regval sc485sl_linear_10_2688x1520_30fps_4lane_regs[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x36e9, 0x80}, + {0x37f9, 0x80}, + {0x23b0, 0x00}, + {0x23b1, 0x08}, + {0x23b2, 0x00}, + {0x23b3, 0x18}, + {0x23b4, 0x00}, + {0x23b5, 0x38}, + {0x23b6, 0x04}, + {0x23b7, 0x08}, + {0x23b8, 0x04}, + {0x23b9, 0x18}, + {0x23ba, 0x04}, + {0x23bb, 0x38}, + {0x23bc, 0x04}, + {0x23bd, 0x08}, + {0x23be, 0x04}, + {0x23bf, 0x78}, + {0x23c0, 0x04}, + {0x23c1, 0x00}, + {0x23c2, 0x04}, + {0x23c3, 0x18}, + {0x23c4, 0x04}, + {0x23c5, 0x38}, + {0x23c6, 0x04}, + {0x23c7, 0x08}, + {0x23c8, 0x04}, + {0x23c9, 0x78}, + {0x3018, 0x7a}, + {0x301e, 0xf0}, + {0x301f, 0x02}, + {0x302c, 0x00}, + {0x3032, 0x2c}, + {0x3034, 0xee}, + {0x30b0, 0x01}, + {0x3204, 0x0a}, + {0x3205, 0x8f}, + {0x3206, 0x05}, + {0x3207, 0xff}, + {0x3208, 0x0a}, + {0x3209, 0x80}, + {0x320a, 0x05}, + {0x320b, 0xf0}, + {0x320c, 0x02}, + {0x320d, 0xee}, + {0x320e, 0x0c}, + {0x320f, 0x80}, + {0x3211, 0x08}, + {0x3213, 0x08}, + {0x3214, 0x11}, + {0x3215, 0x11}, + {0x3250, 0x00}, + {0x325f, 0x2d}, + {0x32d1, 0x1d}, + {0x32e0, 0x00}, + {0x3301, 0x0a}, + {0x3304, 0x50}, + {0x3305, 0x00}, + {0x3306, 0x58}, + {0x3308, 0x10}, + {0x3309, 0x70}, + {0x330a, 0x00}, + {0x330b, 0xb8}, + {0x330d, 0x08}, + {0x330e, 0x18}, + {0x330f, 0x04}, + {0x3310, 0x04}, + {0x3314, 0x94}, + {0x331e, 0x39}, + {0x331f, 0x59}, + {0x3333, 0x10}, + {0x3334, 0x40}, + {0x3347, 0x0f}, + {0x334c, 0x08}, + {0x3364, 0x5e}, + {0x3393, 0x0e}, + {0x3394, 0x2c}, + {0x3395, 0x3c}, + {0x3399, 0x0a}, + {0x339a, 0x0c}, + {0x339b, 0x10}, + {0x339c, 0x52}, + {0x33ac, 0x10}, + {0x33ad, 0x1c}, + {0x33ae, 0x40}, + {0x33af, 0x60}, + {0x33b0, 0x0f}, + {0x33b2, 0x32}, + {0x33b3, 0x08}, + {0x33f8, 0x00}, + {0x33f9, 0x58}, + {0x33fa, 0x00}, + {0x33fb, 0x58}, + {0x349f, 0x03}, + {0x34a8, 0x08}, + {0x34a9, 0x00}, + {0x34aa, 0x00}, + {0x34ab, 0xb8}, + {0x34ac, 0x00}, + {0x34ad, 0xb8}, + {0x34f9, 0x00}, + {0x3632, 0x6d}, + {0x3633, 0x41}, + {0x363a, 0x8c}, + {0x363b, 0x5d}, + {0x3670, 0x41}, + {0x3671, 0x42}, + {0x3672, 0x33}, + {0x3673, 0x04}, + {0x3674, 0x08}, + {0x3675, 0x04}, + {0x3676, 0x18}, + {0x367e, 0x69}, + {0x367f, 0x6d}, + {0x3680, 0x6d}, + {0x3681, 0x04}, + {0x3682, 0x00}, + {0x3683, 0x04}, + {0x3684, 0x38}, + {0x3685, 0x81}, + {0x3686, 0x84}, + {0x3687, 0x84}, + {0x3688, 0x84}, + {0x3689, 0x83}, + {0x368a, 0x83}, + {0x368b, 0x85}, + {0x368c, 0x88}, + {0x368d, 0x00}, + {0x368e, 0x08}, + {0x368f, 0x00}, + {0x3690, 0x18}, + {0x3691, 0x04}, + {0x3692, 0x00}, + {0x3693, 0x04}, + {0x3694, 0x08}, + {0x3695, 0x04}, + {0x3696, 0x18}, + {0x3697, 0x04}, + {0x3698, 0x38}, + {0x3699, 0x04}, + {0x369a, 0x78}, + {0x36b9, 0x1b}, + {0x36ba, 0x1b}, + {0x36bb, 0x13}, + {0x36bc, 0x04}, + {0x36bd, 0x08}, + {0x36be, 0x04}, + {0x36bf, 0x38}, + {0x36d0, 0x0d}, + {0x36d1, 0x20}, + {0x36ea, 0x16}, + {0x36eb, 0x45}, + {0x36ec, 0x69}, + {0x36ed, 0x98}, + {0x370f, 0x31}, + {0x3722, 0x03}, + {0x3724, 0xc1}, + {0x3727, 0x24}, + {0x3729, 0x84}, + {0x37b0, 0x03}, + {0x37b1, 0x03}, + {0x37b2, 0xf3}, + {0x37b3, 0x04}, + {0x37b4, 0x08}, + {0x37b5, 0x04}, + {0x37b6, 0x78}, + {0x37b7, 0xa4}, + {0x37b8, 0xf4}, + {0x37b9, 0xb4}, + {0x37ba, 0x08}, + {0x37bb, 0x1c}, + {0x37bc, 0x4e}, + {0x37bd, 0x04}, + {0x37be, 0x08}, + {0x37bf, 0x04}, + {0x37c0, 0x18}, + {0x37c1, 0x04}, + {0x37c2, 0x08}, + {0x37c3, 0x04}, + {0x37c4, 0x18}, + {0x37fa, 0x10}, + {0x37fb, 0x55}, + {0x37fc, 0x18}, + {0x37fd, 0x1a}, + {0x3900, 0x05}, + {0x3901, 0x02}, + {0x3903, 0x60}, + {0x3907, 0x01}, + {0x3908, 0x00}, + {0x391a, 0x70}, + {0x391b, 0x46}, + {0x391c, 0x26}, + {0x391d, 0x00}, + {0x391f, 0x61}, + {0x3926, 0xe2}, + {0x3933, 0x80}, + {0x3934, 0x07}, + {0x3935, 0x01}, + {0x3936, 0x00}, + {0x3937, 0x7b}, + {0x3938, 0x7d}, + {0x3939, 0x00}, + {0x393a, 0x00}, + {0x393b, 0x00}, + {0x393c, 0x00}, + {0x393f, 0x80}, + {0x3940, 0x20}, + {0x3941, 0x00}, + {0x3942, 0x20}, + {0x3943, 0x80}, + {0x3944, 0x80}, + {0x3945, 0x7f}, + {0x3946, 0x80}, + {0x39c9, 0x60}, + {0x39dd, 0x00}, + {0x39de, 0x08}, + {0x39e7, 0x04}, + {0x39e8, 0x04}, + {0x39e9, 0x80}, + {0x3e00, 0x00}, + {0x3e01, 0xc7}, + {0x3e02, 0x80}, + {0x3e03, 0x0b}, + {0x3e08, 0x00}, + {0x3e16, 0x01}, + {0x3e17, 0xe2}, + {0x3e18, 0x01}, + {0x3e19, 0xe2}, + {0x4402, 0x02}, + {0x4403, 0x08}, + {0x4404, 0x16}, + {0x4405, 0x1d}, + {0x440c, 0x24}, + {0x440d, 0x24}, + {0x440e, 0x1b}, + {0x440f, 0x2d}, + {0x4412, 0x01}, + {0x4424, 0x01}, + {0x450d, 0x08}, + {0x4800, 0x24}, + {0x480f, 0x03}, + {0x4837, 0x28}, + {0x5784, 0x10}, + {0x5785, 0x08}, + {0x5787, 0x0c}, + {0x5788, 0x0c}, + {0x5789, 0x0c}, + {0x578a, 0x0c}, + {0x578b, 0x0c}, + {0x578c, 0x0c}, + {0x578d, 0x40}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x04}, + {0x5793, 0x08}, + {0x5794, 0x04}, + {0x5795, 0x04}, + {0x5799, 0x46}, + {0x579a, 0x77}, + {0x57a1, 0x04}, + {0x57aa, 0x2a}, + {0x57ab, 0x7f}, + {0x57ac, 0x00}, + {0x57ad, 0x00}, + {0x36e9, 0x44}, + {0x37f9, 0x24}, + {REG_NULL, 0x00}, +}; + +/* + * Xclk 27Mhz + * max_framerate 30fps + * mipi_datarate per lane 1080Mbps, 4lane + * hdr2: 2688x1520 + * Cleaned_0x0b_SC485SL_raw_MIPI_27Minput_4Lane_10bit_1080Mbps_2688x1520_30fps_SHDR_VC_20250418.ini + */ +static const struct regval sc485sl_hdr2_10_2688x1520_30fps_4lane_regs[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x36e9, 0x80}, + {0x37f9, 0x80}, + {0x23b0, 0x00}, + {0x23b1, 0x08}, + {0x23b2, 0x00}, + {0x23b3, 0x18}, + {0x23b4, 0x00}, + {0x23b5, 0x38}, + {0x23b6, 0x04}, + {0x23b7, 0x08}, + {0x23b8, 0x04}, + {0x23b9, 0x18}, + {0x23ba, 0x04}, + {0x23bb, 0x38}, + {0x23bc, 0x04}, + {0x23bd, 0x08}, + {0x23be, 0x04}, + {0x23bf, 0x78}, + {0x23c0, 0x04}, + {0x23c1, 0x00}, + {0x23c2, 0x04}, + {0x23c3, 0x18}, + {0x23c4, 0x04}, + {0x23c5, 0x38}, + {0x23c6, 0x04}, + {0x23c7, 0x08}, + {0x23c8, 0x04}, + {0x23c9, 0x78}, + {0x3018, 0x7a}, + {0x301e, 0xf0}, + {0x301f, 0x0b}, + {0x302c, 0x00}, + {0x3032, 0x2c}, + {0x3034, 0xee}, + {0x30b0, 0x01}, + {0x3204, 0x0a}, + {0x3205, 0x8f}, + {0x3206, 0x05}, + {0x3207, 0xff}, + {0x3208, 0x0a}, + {0x3209, 0x80}, + {0x320a, 0x05}, + {0x320b, 0xf0}, + {0x320c, 0x02}, + {0x320d, 0xee}, + {0x320e, 0x0c}, + {0x320f, 0x80}, + {0x3211, 0x08}, + {0x3213, 0x08}, + {0x3214, 0x11}, + {0x3215, 0x11}, + {0x3250, 0xff}, + {0x325f, 0x2d}, + {0x3281, 0x01}, + {0x32d1, 0x1d}, + {0x32e0, 0x00}, + {0x3301, 0x0a}, + {0x3304, 0x50}, + {0x3305, 0x00}, + {0x3306, 0x58}, + {0x3308, 0x10}, + {0x3309, 0x70}, + {0x330a, 0x00}, + {0x330b, 0xb8}, + {0x330d, 0x08}, + {0x330e, 0x18}, + {0x330f, 0x04}, + {0x3310, 0x04}, + {0x3314, 0x94}, + {0x331e, 0x39}, + {0x331f, 0x59}, + {0x3333, 0x10}, + {0x3334, 0x40}, + {0x3347, 0x0f}, + {0x334c, 0x08}, + {0x3364, 0x5e}, + {0x3393, 0x0e}, + {0x3394, 0x2c}, + {0x3395, 0x3c}, + {0x3399, 0x0a}, + {0x339a, 0x0c}, + {0x339b, 0x10}, + {0x339c, 0x52}, + {0x33ac, 0x10}, + {0x33ad, 0x1c}, + {0x33ae, 0x40}, + {0x33af, 0x60}, + {0x33b0, 0x0f}, + {0x33b2, 0x32}, + {0x33b3, 0x08}, + {0x33f8, 0x00}, + {0x33f9, 0x58}, + {0x33fa, 0x00}, + {0x33fb, 0x58}, + {0x3432, 0x72}, + {0x349f, 0x03}, + {0x34a8, 0x08}, + {0x34a9, 0x00}, + {0x34aa, 0x00}, + {0x34ab, 0xb8}, + {0x34ac, 0x00}, + {0x34ad, 0xb8}, + {0x34f9, 0x00}, + {0x3632, 0x6d}, + {0x3633, 0x41}, + {0x363a, 0x8c}, + {0x363b, 0x5d}, + {0x3670, 0x41}, + {0x3671, 0x42}, + {0x3672, 0x33}, + {0x3673, 0x04}, + {0x3674, 0x08}, + {0x3675, 0x04}, + {0x3676, 0x18}, + {0x367e, 0x69}, + {0x367f, 0x6d}, + {0x3680, 0x6d}, + {0x3681, 0x04}, + {0x3682, 0x00}, + {0x3683, 0x04}, + {0x3684, 0x38}, + {0x3685, 0x81}, + {0x3686, 0x84}, + {0x3687, 0x84}, + {0x3688, 0x84}, + {0x3689, 0x83}, + {0x368a, 0x83}, + {0x368b, 0x85}, + {0x368c, 0x88}, + {0x368d, 0x00}, + {0x368e, 0x08}, + {0x368f, 0x00}, + {0x3690, 0x18}, + {0x3691, 0x04}, + {0x3692, 0x00}, + {0x3693, 0x04}, + {0x3694, 0x08}, + {0x3695, 0x04}, + {0x3696, 0x18}, + {0x3697, 0x04}, + {0x3698, 0x38}, + {0x3699, 0x04}, + {0x369a, 0x78}, + {0x36b9, 0x1b}, + {0x36ba, 0x1b}, + {0x36bb, 0x13}, + {0x36bc, 0x04}, + {0x36bd, 0x08}, + {0x36be, 0x04}, + {0x36bf, 0x38}, + {0x36d0, 0x0d}, + {0x36d1, 0x20}, + {0x36ea, 0x14}, + {0x36eb, 0x45}, + {0x36ec, 0x69}, + {0x36ed, 0x98}, + {0x370f, 0x31}, + {0x3722, 0x03}, + {0x3724, 0xc1}, + {0x3727, 0x24}, + {0x3729, 0x84}, + {0x37b0, 0x03}, + {0x37b1, 0x03}, + {0x37b2, 0xf3}, + {0x37b3, 0x04}, + {0x37b4, 0x08}, + {0x37b5, 0x04}, + {0x37b6, 0x78}, + {0x37b7, 0xa4}, + {0x37b8, 0xf4}, + {0x37b9, 0xb4}, + {0x37ba, 0x08}, + {0x37bb, 0x1c}, + {0x37bc, 0x4e}, + {0x37bd, 0x04}, + {0x37be, 0x08}, + {0x37bf, 0x04}, + {0x37c0, 0x18}, + {0x37c1, 0x04}, + {0x37c2, 0x08}, + {0x37c3, 0x04}, + {0x37c4, 0x18}, + {0x37fa, 0x10}, + {0x37fb, 0x55}, + {0x37fc, 0x18}, + {0x37fd, 0x1a}, + {0x3900, 0x05}, + {0x3901, 0x02}, + {0x3903, 0x60}, + {0x3907, 0x01}, + {0x3908, 0x00}, + {0x391a, 0x70}, + {0x391b, 0x46}, + {0x391c, 0x26}, + {0x391d, 0x00}, + {0x391f, 0x61}, + {0x3926, 0xe2}, + {0x3933, 0x80}, + {0x3934, 0x07}, + {0x3935, 0x01}, + {0x3936, 0x00}, + {0x3937, 0x7b}, + {0x3938, 0x7d}, + {0x3939, 0x00}, + {0x393a, 0x00}, + {0x393b, 0x00}, + {0x393c, 0x00}, + {0x393f, 0x00}, + {0x3940, 0x20}, + {0x3941, 0x00}, + {0x3942, 0x20}, + {0x3943, 0x80}, + {0x3944, 0x80}, + {0x3945, 0x7f}, + {0x3946, 0x80}, + {0x39c9, 0x60}, + {0x39dd, 0x00}, + {0x39de, 0x08}, + {0x39e7, 0x04}, + {0x39e8, 0x04}, + {0x39e9, 0x80}, + {0x3e00, 0x00}, + {0x3e01, 0xb9}, + {0x3e02, 0x00}, + {0x3e03, 0x0b}, + {0x3e04, 0x0b}, + {0x3e05, 0x90}, + {0x3e08, 0x00}, + {0x3e16, 0x01}, + {0x3e17, 0xe2}, + {0x3e18, 0x01}, + {0x3e19, 0xe2}, + {0x3e23, 0x00}, + {0x3e24, 0xc4}, + {0x4402, 0x02}, + {0x4403, 0x08}, + {0x4404, 0x16}, + {0x4405, 0x1d}, + {0x440c, 0x24}, + {0x440d, 0x24}, + {0x440e, 0x1b}, + {0x440f, 0x2d}, + {0x4412, 0x01}, + {0x4424, 0x01}, + {0x450d, 0x08}, + {0x4800, 0x24}, + {0x480f, 0x03}, + {0x481f, 0x3c}, + {0x4827, 0x38}, + {0x4837, 0x1e}, + {0x5784, 0x10}, + {0x5785, 0x08}, + {0x5787, 0x0c}, + {0x5788, 0x0c}, + {0x5789, 0x0c}, + {0x578a, 0x0c}, + {0x578b, 0x0c}, + {0x578c, 0x0c}, + {0x578d, 0x40}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x04}, + {0x5793, 0x08}, + {0x5794, 0x04}, + {0x5795, 0x04}, + {0x5799, 0x46}, + {0x579a, 0x77}, + {0x57a1, 0x04}, + {0x57aa, 0x2a}, + {0x57ab, 0x7f}, + {0x57ac, 0x00}, + {0x57ad, 0x00}, + {0x36e9, 0x24}, + {0x37f9, 0x24}, + {REG_NULL, 0x00}, +}; + +static const struct regval sc485sl_global_2lane_regs[] = { + {REG_NULL, 0x00}, +}; + +/* + * Xclk 27Mhz + * max_framerate 30fps + * mipi_datarate per lane 720Mbps, 2lane + * linear: 2688x1520 + * Cleaned_0x0c_SC485SL_raw_MIPI_27Minput_2Lane_10bit_720Mbps_2688x1520_30fps_20250418.ini + */ +static const struct regval sc485sl_linear_10_2688x1520_30fps_2lane_regs[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x36e9, 0x80}, + {0x37f9, 0x80}, + {0x23b0, 0x00}, + {0x23b1, 0x08}, + {0x23b2, 0x00}, + {0x23b3, 0x18}, + {0x23b4, 0x00}, + {0x23b5, 0x38}, + {0x23b6, 0x04}, + {0x23b7, 0x08}, + {0x23b8, 0x04}, + {0x23b9, 0x18}, + {0x23ba, 0x04}, + {0x23bb, 0x38}, + {0x23bc, 0x04}, + {0x23bd, 0x08}, + {0x23be, 0x04}, + {0x23bf, 0x78}, + {0x23c0, 0x04}, + {0x23c1, 0x00}, + {0x23c2, 0x04}, + {0x23c3, 0x18}, + {0x23c4, 0x04}, + {0x23c5, 0x38}, + {0x23c6, 0x04}, + {0x23c7, 0x08}, + {0x23c8, 0x04}, + {0x23c9, 0x78}, + {0x3018, 0x3a}, + {0x3019, 0x0c}, + {0x301e, 0xf0}, + {0x301f, 0x0c}, + {0x302c, 0x00}, + {0x3032, 0x2c}, + {0x3034, 0xee}, + {0x30b0, 0x01}, + {0x3204, 0x0a}, + {0x3205, 0x8f}, + {0x3206, 0x05}, + {0x3207, 0xff}, + {0x3208, 0x0a}, + {0x3209, 0x80}, + {0x320a, 0x05}, + {0x320b, 0xf0}, + {0x320c, 0x02}, + {0x320d, 0xee}, + {0x320e, 0x06}, + {0x320f, 0x40}, + {0x3211, 0x08}, + {0x3213, 0x08}, + {0x3214, 0x11}, + {0x3215, 0x11}, + {0x3250, 0x00}, + {0x325f, 0x2d}, + {0x32d1, 0x1d}, + {0x32e0, 0x00}, + {0x3301, 0x0a}, + {0x3304, 0x50}, + {0x3305, 0x00}, + {0x3306, 0x58}, + {0x3308, 0x10}, + {0x3309, 0x70}, + {0x330a, 0x00}, + {0x330b, 0xb8}, + {0x330d, 0x08}, + {0x330e, 0x18}, + {0x330f, 0x04}, + {0x3310, 0x04}, + {0x3314, 0x94}, + {0x331e, 0x39}, + {0x331f, 0x59}, + {0x3333, 0x10}, + {0x3334, 0x40}, + {0x3347, 0x0f}, + {0x334c, 0x08}, + {0x3364, 0x5e}, + {0x3393, 0x0e}, + {0x3394, 0x2c}, + {0x3395, 0x3c}, + {0x3399, 0x0a}, + {0x339a, 0x0c}, + {0x339b, 0x10}, + {0x339c, 0x52}, + {0x33ac, 0x10}, + {0x33ad, 0x1c}, + {0x33ae, 0x40}, + {0x33af, 0x60}, + {0x33b0, 0x0f}, + {0x33b2, 0x32}, + {0x33b3, 0x08}, + {0x33f8, 0x00}, + {0x33f9, 0x58}, + {0x33fa, 0x00}, + {0x33fb, 0x58}, + {0x349f, 0x03}, + {0x34a8, 0x08}, + {0x34a9, 0x00}, + {0x34aa, 0x00}, + {0x34ab, 0xb8}, + {0x34ac, 0x00}, + {0x34ad, 0xb8}, + {0x34f9, 0x00}, + {0x3632, 0x6d}, + {0x3633, 0x41}, + {0x363a, 0x8c}, + {0x363b, 0x5d}, + {0x3670, 0x41}, + {0x3671, 0x42}, + {0x3672, 0x33}, + {0x3673, 0x04}, + {0x3674, 0x08}, + {0x3675, 0x04}, + {0x3676, 0x18}, + {0x367e, 0x69}, + {0x367f, 0x6d}, + {0x3680, 0x6d}, + {0x3681, 0x04}, + {0x3682, 0x00}, + {0x3683, 0x04}, + {0x3684, 0x38}, + {0x3685, 0x81}, + {0x3686, 0x84}, + {0x3687, 0x84}, + {0x3688, 0x84}, + {0x3689, 0x83}, + {0x368a, 0x83}, + {0x368b, 0x85}, + {0x368c, 0x88}, + {0x368d, 0x00}, + {0x368e, 0x08}, + {0x368f, 0x00}, + {0x3690, 0x18}, + {0x3691, 0x04}, + {0x3692, 0x00}, + {0x3693, 0x04}, + {0x3694, 0x08}, + {0x3695, 0x04}, + {0x3696, 0x18}, + {0x3697, 0x04}, + {0x3698, 0x38}, + {0x3699, 0x04}, + {0x369a, 0x78}, + {0x36b9, 0x1b}, + {0x36ba, 0x1b}, + {0x36bb, 0x13}, + {0x36bc, 0x04}, + {0x36bd, 0x08}, + {0x36be, 0x04}, + {0x36bf, 0x38}, + {0x36d0, 0x0d}, + {0x36d1, 0x20}, + {0x36ea, 0x14}, + {0x36eb, 0x4f}, + {0x36ec, 0x69}, + {0x36ed, 0x98}, + {0x370f, 0x31}, + {0x3722, 0x03}, + {0x3724, 0xc1}, + {0x3727, 0x24}, + {0x3729, 0x84}, + {0x37b0, 0x03}, + {0x37b1, 0x03}, + {0x37b2, 0xf3}, + {0x37b3, 0x04}, + {0x37b4, 0x08}, + {0x37b5, 0x04}, + {0x37b6, 0x78}, + {0x37b7, 0xa4}, + {0x37b8, 0xf4}, + {0x37b9, 0xb4}, + {0x37ba, 0x08}, + {0x37bb, 0x1c}, + {0x37bc, 0x4e}, + {0x37bd, 0x04}, + {0x37be, 0x08}, + {0x37bf, 0x04}, + {0x37c0, 0x18}, + {0x37c1, 0x04}, + {0x37c2, 0x08}, + {0x37c3, 0x04}, + {0x37c4, 0x18}, + {0x37fa, 0x0c}, + {0x37fb, 0x55}, + {0x37fc, 0x18}, + {0x37fd, 0x1a}, + {0x3900, 0x05}, + {0x3901, 0x02}, + {0x3903, 0x60}, + {0x3907, 0x01}, + {0x3908, 0x00}, + {0x391a, 0x70}, + {0x391b, 0x46}, + {0x391c, 0x26}, + {0x391d, 0x00}, + {0x391f, 0x61}, + {0x3926, 0xe2}, + {0x3933, 0x80}, + {0x3934, 0x07}, + {0x3935, 0x01}, + {0x3936, 0x00}, + {0x3937, 0x7b}, + {0x3938, 0x7d}, + {0x3939, 0x00}, + {0x393a, 0x00}, + {0x393b, 0x00}, + {0x393c, 0x00}, + {0x393f, 0x80}, + {0x3940, 0x20}, + {0x3941, 0x00}, + {0x3942, 0x20}, + {0x3943, 0x80}, + {0x3944, 0x80}, + {0x3945, 0x7f}, + {0x3946, 0x80}, + {0x39c9, 0x60}, + {0x39dd, 0x00}, + {0x39de, 0x08}, + {0x39e7, 0x04}, + {0x39e8, 0x04}, + {0x39e9, 0x80}, + {0x3e00, 0x00}, + {0x3e01, 0x63}, + {0x3e02, 0x80}, + {0x3e03, 0x0b}, + {0x3e08, 0x00}, + {0x3e16, 0x01}, + {0x3e17, 0xe2}, + {0x3e18, 0x01}, + {0x3e19, 0xe2}, + {0x4402, 0x01}, + {0x4403, 0x04}, + {0x4404, 0x0b}, + {0x4405, 0x0f}, + {0x440c, 0x12}, + {0x440d, 0x12}, + {0x440e, 0x0e}, + {0x440f, 0x17}, + {0x4412, 0x01}, + {0x4424, 0x01}, + {0x450d, 0x08}, + {0x4800, 0x24}, + {0x480f, 0x03}, + {0x4837, 0x2c}, + {0x5784, 0x10}, + {0x5785, 0x08}, + {0x5787, 0x0c}, + {0x5788, 0x0c}, + {0x5789, 0x0c}, + {0x578a, 0x0c}, + {0x578b, 0x0c}, + {0x578c, 0x0c}, + {0x578d, 0x40}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x04}, + {0x5793, 0x08}, + {0x5794, 0x04}, + {0x5795, 0x04}, + {0x5799, 0x46}, + {0x579a, 0x77}, + {0x57a1, 0x04}, + {0x57aa, 0x2a}, + {0x57ab, 0x7f}, + {0x57ac, 0x00}, + {0x57ad, 0x00}, + {0x36e9, 0x44}, + {0x37f9, 0x44}, + {REG_NULL, 0x00}, + +}; + + +/* + * Xclk 27Mhz + * max_framerate 60fps + * mipi_datarate per lane 1440Mbps, 2lane + * linear: 2688x1520 + * Cleaned_0x29_SC485SL_raw_MIPI_27Minput_2Lane_10bit_1440Mbps_2688x1520_60fps_20250310.ini + */ +static const struct regval sc485sl_linear_10_2688x1520_60fps_2lane_regs[] = { + {0x0103, 0x01}, + {0x0100, 0x00}, + {0x36e9, 0x80}, + {0x37f9, 0x80}, + {0x23b0, 0x00}, + {0x23b1, 0x08}, + {0x23b2, 0x00}, + {0x23b3, 0x18}, + {0x23b4, 0x00}, + {0x23b5, 0x38}, + {0x23b6, 0x04}, + {0x23b7, 0x08}, + {0x23b8, 0x04}, + {0x23b9, 0x18}, + {0x23ba, 0x04}, + {0x23bb, 0x38}, + {0x23bc, 0x04}, + {0x23bd, 0x08}, + {0x23be, 0x04}, + {0x23bf, 0x78}, + {0x23c0, 0x04}, + {0x23c1, 0x00}, + {0x23c2, 0x04}, + {0x23c3, 0x18}, + {0x23c4, 0x04}, + {0x23c5, 0x38}, + {0x23c6, 0x04}, + {0x23c7, 0x08}, + {0x23c8, 0x04}, + {0x23c9, 0x78}, + {0x3018, 0x3a}, + {0x3019, 0x0c}, + {0x301e, 0xf0}, + {0x301f, 0x29}, + {0x302c, 0x00}, + {0x3032, 0x2c}, + {0x3034, 0xee}, + {0x30b0, 0x01}, + {0x3204, 0x0a}, + {0x3205, 0x8f}, + {0x3206, 0x05}, + {0x3207, 0xff}, + {0x3208, 0x0a}, + {0x3209, 0x80}, + {0x320a, 0x05}, + {0x320b, 0xf0}, + {0x320c, 0x02}, + {0x320d, 0xee}, + {0x320e, 0x06}, + {0x320f, 0x40}, + {0x3211, 0x08}, + {0x3213, 0x08}, + {0x3214, 0x11}, + {0x3215, 0x11}, + {0x3250, 0x00}, + {0x325f, 0x2d}, + {0x32d1, 0x1d}, + {0x32e0, 0x00}, + {0x3301, 0x0a}, + {0x3304, 0x50}, + {0x3305, 0x00}, + {0x3306, 0x58}, + {0x3308, 0x10}, + {0x3309, 0x70}, + {0x330a, 0x00}, + {0x330b, 0xb8}, + {0x330d, 0x08}, + {0x330e, 0x18}, + {0x330f, 0x04}, + {0x3310, 0x04}, + {0x3314, 0x94}, + {0x331e, 0x39}, + {0x331f, 0x59}, + {0x3333, 0x10}, + {0x3334, 0x40}, + {0x3347, 0x0f}, + {0x334c, 0x08}, + {0x3364, 0x5e}, + {0x3393, 0x0e}, + {0x3394, 0x2c}, + {0x3395, 0x3c}, + {0x3399, 0x0a}, + {0x339a, 0x0c}, + {0x339b, 0x10}, + {0x339c, 0x52}, + {0x33ac, 0x10}, + {0x33ad, 0x1c}, + {0x33ae, 0x40}, + {0x33af, 0x60}, + {0x33b0, 0x0f}, + {0x33b2, 0x32}, + {0x33b3, 0x08}, + {0x33f8, 0x00}, + {0x33f9, 0x58}, + {0x33fa, 0x00}, + {0x33fb, 0x58}, + {0x349f, 0x03}, + {0x34a8, 0x08}, + {0x34a9, 0x00}, + {0x34aa, 0x00}, + {0x34ab, 0xb8}, + {0x34ac, 0x00}, + {0x34ad, 0xb8}, + {0x34f9, 0x00}, + {0x3632, 0x6d}, + {0x3633, 0x41}, + {0x363a, 0x8c}, + {0x363b, 0x5d}, + {0x3670, 0x41}, + {0x3671, 0x42}, + {0x3672, 0x33}, + {0x3673, 0x04}, + {0x3674, 0x08}, + {0x3675, 0x04}, + {0x3676, 0x18}, + {0x367e, 0x69}, + {0x367f, 0x6d}, + {0x3680, 0x6d}, + {0x3681, 0x04}, + {0x3682, 0x00}, + {0x3683, 0x04}, + {0x3684, 0x38}, + {0x3685, 0x81}, + {0x3686, 0x84}, + {0x3687, 0x84}, + {0x3688, 0x84}, + {0x3689, 0x83}, + {0x368a, 0x83}, + {0x368b, 0x85}, + {0x368c, 0x88}, + {0x368d, 0x00}, + {0x368e, 0x08}, + {0x368f, 0x00}, + {0x3690, 0x18}, + {0x3691, 0x04}, + {0x3692, 0x00}, + {0x3693, 0x04}, + {0x3694, 0x08}, + {0x3695, 0x04}, + {0x3696, 0x18}, + {0x3697, 0x04}, + {0x3698, 0x38}, + {0x3699, 0x04}, + {0x369a, 0x78}, + {0x36b9, 0x1b}, + {0x36ba, 0x1b}, + {0x36bb, 0x13}, + {0x36bc, 0x04}, + {0x36bd, 0x08}, + {0x36be, 0x04}, + {0x36bf, 0x38}, + {0x36d0, 0x0d}, + {0x36d1, 0x20}, + {0x36ea, 0x10}, + {0x36eb, 0x45}, + {0x36ec, 0x69}, + {0x36ed, 0xa8}, + {0x370f, 0x31}, + {0x3722, 0x03}, + {0x3724, 0xc1}, + {0x3727, 0x24}, + {0x3729, 0x84}, + {0x37b0, 0x03}, + {0x37b1, 0x03}, + {0x37b2, 0xf3}, + {0x37b3, 0x04}, + {0x37b4, 0x08}, + {0x37b5, 0x04}, + {0x37b6, 0x78}, + {0x37b7, 0xa4}, + {0x37b8, 0xf4}, + {0x37b9, 0xb4}, + {0x37ba, 0x08}, + {0x37bb, 0x1c}, + {0x37bc, 0x4e}, + {0x37bd, 0x04}, + {0x37be, 0x08}, + {0x37bf, 0x04}, + {0x37c0, 0x18}, + {0x37c1, 0x04}, + {0x37c2, 0x08}, + {0x37c3, 0x04}, + {0x37c4, 0x18}, + {0x37fa, 0x10}, + {0x37fb, 0x55}, + {0x37fc, 0x18}, + {0x37fd, 0x1a}, + {0x3900, 0x05}, + {0x3901, 0x02}, + {0x3903, 0x60}, + {0x3907, 0x01}, + {0x3908, 0x00}, + {0x391a, 0x70}, + {0x391b, 0x46}, + {0x391c, 0x26}, + {0x391d, 0x00}, + {0x391f, 0x61}, + {0x3926, 0xe2}, + {0x3933, 0x80}, + {0x3934, 0x07}, + {0x3935, 0x01}, + {0x3936, 0x00}, + {0x3937, 0x7b}, + {0x3938, 0x7d}, + {0x3939, 0x00}, + {0x393a, 0x00}, + {0x393b, 0x00}, + {0x393c, 0x00}, + {0x393f, 0x80}, + {0x3940, 0x20}, + {0x3941, 0x00}, + {0x3942, 0x20}, + {0x3943, 0x80}, + {0x3944, 0x80}, + {0x3945, 0x7f}, + {0x3946, 0x80}, + {0x39c9, 0x60}, + {0x39dd, 0x00}, + {0x39de, 0x08}, + {0x39e7, 0x04}, + {0x39e8, 0x04}, + {0x39e9, 0x80}, + {0x3e00, 0x00}, + {0x3e01, 0x63}, + {0x3e02, 0x80}, + {0x3e03, 0x0b}, + {0x3e08, 0x00}, + {0x3e16, 0x01}, + {0x3e17, 0xe2}, + {0x3e18, 0x01}, + {0x3e19, 0xe2}, + {0x4402, 0x02}, + {0x4403, 0x08}, + {0x4404, 0x16}, + {0x4405, 0x1d}, + {0x440c, 0x24}, + {0x440d, 0x24}, + {0x440e, 0x1b}, + {0x440f, 0x2d}, + {0x4412, 0x01}, + {0x4424, 0x01}, + {0x450d, 0x08}, + {0x4800, 0x24}, + {0x480f, 0x03}, + {0x4837, 0x16}, + {0x5784, 0x10}, + {0x5785, 0x08}, + {0x5787, 0x0c}, + {0x5788, 0x0c}, + {0x5789, 0x0c}, + {0x578a, 0x0c}, + {0x578b, 0x0c}, + {0x578c, 0x0c}, + {0x578d, 0x40}, + {0x5790, 0x08}, + {0x5791, 0x04}, + {0x5792, 0x04}, + {0x5793, 0x08}, + {0x5794, 0x04}, + {0x5795, 0x04}, + {0x5799, 0x46}, + {0x579a, 0x77}, + {0x57a1, 0x04}, + {0x57aa, 0x2a}, + {0x57ab, 0x7f}, + {0x57ac, 0x00}, + {0x57ad, 0x00}, + {0x36e9, 0x44}, + {0x37f9, 0x24}, + // {0x0100, 0x01}, + {REG_NULL, 0x00}, +}; + +/* + * The width and height must be configured to be + * the same as the current output resolution of the sensor. + * The input width of the isp needs to be 16 aligned. + * The input height of the isp needs to be 8 aligned. + * If the width or height does not meet the alignment rules, + * you can configure the cropping parameters with the following function to + * crop out the appropriate resolution. + * struct v4l2_subdev_pad_ops { + * .get_selection + * } + */ + +static const struct sc485sl_mode supported_modes_4lane[] = { + { + .width = 2688, + .height = 1520, + .max_fps = { + .numerator = 10000, + .denominator = 300000, + }, + .exp_def = 0x0c78, + .hts_def = 0x2ee * 4, + .vts_def = 0x0c80, + .bus_fmt = MEDIA_BUS_FMT_SBGGR10_1X10, + .global_reg_list = sc485sl_global_4lane_regs, + .reg_list = sc485sl_linear_10_2688x1520_30fps_4lane_regs, + .hdr_mode = NO_HDR, + .mclk = 27000000, + .link_freq_idx = 0, + .bpp = 10, + .vc[PAD0] = 0, + .lanes = 4, + }, + { + .width = 2688, + .height = 1520, + .max_fps = { + .numerator = 10000, + .denominator = 300000, + }, + .exp_def = 0x0c78, + .hts_def = 0x2ee * 4, + .vts_def = 0x0c80, + + .bus_fmt = MEDIA_BUS_FMT_SBGGR10_1X10, + .global_reg_list = sc485sl_global_4lane_regs, + .reg_list = sc485sl_hdr2_10_2688x1520_30fps_4lane_regs, + .hdr_mode = HDR_X2, + .mclk = 27000000, + .link_freq_idx = 2, + .bpp = 10, + .vc[PAD0] = 1, + .vc[PAD1] = 0,//L->csi wr0 + .vc[PAD2] = 1, + .vc[PAD3] = 1,//M->csi wr2 + .lanes = 4, + } +}; + +static const struct sc485sl_mode supported_modes_2lane[] = { + /* 30fps 2lane */ + { + .width = 2688, + .height = 1520, + .max_fps = { + .numerator = 10000, + .denominator = 300000, + }, + .exp_def = 0x0638, + .hts_def = 0x2ee * 4, + .vts_def = 0x0640, + .bus_fmt = MEDIA_BUS_FMT_SBGGR10_1X10, + .global_reg_list = sc485sl_global_2lane_regs, + .reg_list = sc485sl_linear_10_2688x1520_30fps_2lane_regs, + .hdr_mode = NO_HDR, + .mclk = 27000000, + .link_freq_idx = 1, + .bpp = 10, + .vc[PAD0] = 0, + .lanes = 2, + }, + /* 60fps 2lane */ + { + .width = 2688, + .height = 1520, + .max_fps = { + .numerator = 10000, + .denominator = 600000, + }, + .exp_def = 0x0638, + .hts_def = 0x2ee * 4, + .vts_def = 0x0640, + .bus_fmt = MEDIA_BUS_FMT_SBGGR10_1X10, + .global_reg_list = sc485sl_global_2lane_regs, + .reg_list = sc485sl_linear_10_2688x1520_60fps_2lane_regs, + .hdr_mode = NO_HDR, + .mclk = 27000000, + .link_freq_idx = 3, + .bpp = 10, + .vc[PAD0] = 0, + .lanes = 2, + }, +}; + +static const u32 bus_code[] = { + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static const s64 link_freq_menu_items[] = { + SC485SL_LINK_FREQ_396, /* 4 lanes */ + SC485SL_LINK_FREQ_360, /* 2 lanes */ + SC485SL_LINK_FREQ_540, /* 4 lanes */ + SC485SL_LINK_FREQ_720, /* 2 lanes */ +}; + +static const char *const sc485sl_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4", +}; + + +static int sc485sl_write_reg(struct i2c_client *client, u16 reg, + u32 len, u32 val) +{ + u32 buf_i, val_i; + u8 buf[6]; + u8 *val_p; + __be32 val_be; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val_be = cpu_to_be32(val); + val_p = (u8 *)&val_be; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + return 0; +} + +static int sc485sl_write_array(struct i2c_client *client, + const struct regval *regs) +{ + u32 i; + int ret = 0; + + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) + ret = sc485sl_write_reg(client, regs[i].addr, + SC485SL_REG_VALUE_08BIT, regs[i].val); + + return ret; +} + +static int sc485sl_read_reg(struct i2c_client *client, u16 reg, unsigned int len, + u32 *val) +{ + struct i2c_msg msgs[2]; + u8 *data_be_p; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + int ret; + + if (len > 4 || !len) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +/* mode: 0 = lgain 1 = sgain */ +static int sc485sl_set_gain_reg(struct sc485sl *sc485sl, u32 gain, int mode) +{ + struct i2c_client *client = sc485sl->client; + u32 coarse_again = 0, coarse_dgain = 0, fine_again = 0, fine_dgain = 0; + int ret = 0, gain_factor; + + if (gain <= 64) + gain = 64; + else if (gain > SC485SL_GAIN_MAX) + gain = SC485SL_GAIN_MAX; + + gain_factor = gain * 1000 / 64; + if (gain_factor < 2000) { /* start again, 1.0x - 2.0x, 1000 * 2 = 2000*/ + coarse_again = 0x00; + coarse_dgain = 0x00; + fine_dgain = 0x80; + fine_again = DIV_ROUND_UP(gain_factor * 32, 1000); + } else if (gain_factor < 3410) { /* 2.0x - 3.41x, 1000 * 3.41 = 3410 */ + coarse_again = 0x01; + coarse_dgain = 0x00; + fine_dgain = 0x80; + fine_again = DIV_ROUND_UP(gain_factor * 32, 2000); + } else if (gain_factor < 6820) { /* 3.41x - 6.82x, 1000 * 6.82 = 6820 */ + coarse_again = 0x80; + coarse_dgain = 0x00; + fine_dgain = 0x80; + fine_again = DIV_ROUND_UP(gain_factor * 32, 3410); + } else if (gain_factor < 13640) { /* 6.82x - 13.64x, 1000 * 13.64 = 13640 */ + coarse_again = 0x81; + coarse_dgain = 0x00; + fine_dgain = 0x80; + fine_again = DIV_ROUND_UP(gain_factor * 32, 6820); + } else if (gain_factor < 27280) { /* 13.64x - 27.28x, 1000 * 27.28 = 27280 */ + coarse_again = 0x83; + coarse_dgain = 0x00; + fine_dgain = 0x80; + fine_again = DIV_ROUND_UP(gain_factor * 32, 13640); + } else if (gain_factor < 54560) { /* 27.28x - 54.56x, 1000 * 54.56 = 54560 */ + coarse_again = 0x87; + coarse_dgain = 0x00; + fine_dgain = 0x80; + fine_again = DIV_ROUND_UP(gain_factor * 32, 27280); + } else if (gain_factor <= 107415) { /* 54.56x - 107.415x, 1000 * 107.415 = 107415 */ + coarse_again = 0x8f; + coarse_dgain = 0x00; + fine_dgain = 0x80; + fine_again = DIV_ROUND_UP(gain_factor * 32, 54560); + } else if (gain_factor < 107415 * 2) { + // open dgain begin, max digital gain 15.875x, + // the accuracy of the digital fractional gain is 1/64. + coarse_again = 0x8f; + coarse_dgain = 0x00; + fine_again = 0x3f; + fine_dgain = DIV_ROUND_UP(gain_factor * 128, 107415); + } else if (gain_factor < 107415 * 4) { + coarse_again = 0x8f; + coarse_dgain = 0x01; + fine_again = 0x3f; + fine_dgain = DIV_ROUND_UP(gain_factor * 128, 107415 * 2); + } else if (gain_factor < 107415 * 8) { + coarse_again = 0x8f; + coarse_dgain = 0x03; + fine_again = 0x3f; + fine_dgain = DIV_ROUND_UP(gain_factor * 128, 107415 * 4); + } else if (gain_factor < 107415 * 16) { + coarse_again = 0x8f; + coarse_dgain = 0x07; + fine_again = 0x3f; + fine_dgain = DIV_ROUND_UP(gain_factor * 128, 107415 * 8); + } + dev_dbg(&client->dev, "c_again: 0x%x, c_dgain: 0x%x, f_again: 0x%x, f_dgain: 0x%0x\n", + coarse_again, coarse_dgain, fine_again, fine_dgain); + + if (mode == SC485SL_LGAIN) { + ret = sc485sl_write_reg(sc485sl->client, + SC485SL_REG_DIG_GAIN, + SC485SL_REG_VALUE_08BIT, + coarse_dgain); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_DIG_FINE_GAIN, + SC485SL_REG_VALUE_08BIT, + fine_dgain); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_ANA_GAIN, + SC485SL_REG_VALUE_08BIT, + coarse_again); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_ANA_FINE_GAIN, + SC485SL_REG_VALUE_08BIT, + fine_again); + } else { + ret = sc485sl_write_reg(sc485sl->client, + SC485SL_REG_SDIG_GAIN, + SC485SL_REG_VALUE_08BIT, + coarse_dgain); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_SDIG_FINE_GAIN, + SC485SL_REG_VALUE_08BIT, + fine_dgain); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_SANA_GAIN, + SC485SL_REG_VALUE_08BIT, + coarse_again); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_SANA_FINE_GAIN, + SC485SL_REG_VALUE_08BIT, + fine_again); + } + return ret; +} + +static int sc485sl_set_hdrae(struct sc485sl *sc485sl, + struct preisp_hdrae_exp_s *ae) +{ + int ret = 0; + u32 l_exp_time, m_exp_time, s_exp_time; + u32 l_a_gain, m_a_gain, s_a_gain; + u32 l_exp_max = 0; + + if (!sc485sl->has_init_exp && !sc485sl->streaming) { + sc485sl->init_hdrae_exp = *ae; + sc485sl->has_init_exp = true; + dev_dbg(&sc485sl->client->dev, "sc485sl don't stream, record exp for hdr!\n"); + return ret; + } + l_exp_time = ae->long_exp_reg; + m_exp_time = ae->middle_exp_reg; + s_exp_time = ae->short_exp_reg; + l_a_gain = ae->long_gain_reg; + m_a_gain = ae->middle_gain_reg; + s_a_gain = ae->short_gain_reg; + + dev_dbg(&sc485sl->client->dev, + "rev exp req: L_exp: 0x%x, 0x%x, M_exp: 0x%x, 0x%x S_exp: 0x%x, 0x%x\n", + l_exp_time, m_exp_time, s_exp_time, + l_a_gain, m_a_gain, s_a_gain); + + if (sc485sl->cur_mode->hdr_mode == HDR_X2) { + //2 stagger + l_a_gain = m_a_gain; + l_exp_time = m_exp_time; + } + + // manual long exposure time in double-line overlap HDR mode, + // register value is in units of one line + // (3e23~3e24) default value is 0x00c4 from reg list + l_exp_max = sc485sl->cur_vts - 196 - 14; // vts - (3e33,3e23~3e24) - 14 + //set exposure + l_exp_time = l_exp_time * 2; + s_exp_time = s_exp_time * 2; + if (l_exp_time > l_exp_max) + l_exp_time = l_exp_max; + + // read regs list to get (3e23~3e24) value, then subtract 11 + // (3e23~3e24) default value is 0x00c4 from reg list + if (s_exp_time > 184) // 184 = (3e33,3e23~3e24) - 12 + s_exp_time = 184; + + ret = sc485sl_write_reg(sc485sl->client, + SC485SL_REG_EXPOSURE_H, + SC485SL_REG_VALUE_08BIT, + SC485SL_FETCH_EXP_H(l_exp_time)); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_EXPOSURE_M, + SC485SL_REG_VALUE_08BIT, + SC485SL_FETCH_EXP_M(l_exp_time)); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_EXPOSURE_L, + SC485SL_REG_VALUE_08BIT, + SC485SL_FETCH_EXP_L(l_exp_time)); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_SEXPOSURE_M, + SC485SL_REG_VALUE_08BIT, + SC485SL_FETCH_EXP_M(s_exp_time)); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_SEXPOSURE_L, + SC485SL_REG_VALUE_08BIT, + SC485SL_FETCH_EXP_L(s_exp_time)); + + ret |= sc485sl_set_gain_reg(sc485sl, l_a_gain, SC485SL_LGAIN); + ret |= sc485sl_set_gain_reg(sc485sl, s_a_gain, SC485SL_SGAIN); + return ret; +} + +static int sc485sl_get_reso_dist(const struct sc485sl_mode *mode, + struct v4l2_mbus_framefmt *framefmt) +{ + return abs(mode->width - framefmt->width) + + abs(mode->height - framefmt->height); +} + +static const struct sc485sl_mode * +sc485sl_find_best_fit(struct sc485sl *sc485sl, struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt = &fmt->format; + int dist; + int cur_best_fit = 0; + int cur_best_fit_dist = -1; + unsigned int i; + + for (i = 0; i < sc485sl->cfg_num; i++) { + dist = sc485sl_get_reso_dist(&sc485sl->supported_modes[i], framefmt); + if (cur_best_fit_dist == -1 || dist < cur_best_fit_dist) { + cur_best_fit_dist = dist; + cur_best_fit = i; + } else if (dist == cur_best_fit_dist && + framefmt->code == sc485sl->supported_modes[i].bus_fmt) { + cur_best_fit = i; + break; + } + } + + return &sc485sl->supported_modes[cur_best_fit]; +} + +static int sc485sl_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + const struct sc485sl_mode *mode; + s64 h_blank, vblank_def; + u64 dst_link_freq = 0; + u64 dst_pixel_rate = 0; + u8 lanes = sc485sl->bus_cfg.bus.mipi_csi2.num_data_lanes; + + mutex_lock(&sc485sl->mutex); + + mode = sc485sl_find_best_fit(sc485sl, fmt); + fmt->format.code = mode->bus_fmt; + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; +#else + mutex_unlock(&sc485sl->mutex); + return -ENOTTY; +#endif + } else { + sc485sl->cur_mode = mode; + h_blank = mode->hts_def - mode->width; + __v4l2_ctrl_modify_range(sc485sl->hblank, h_blank, + h_blank, 1, h_blank); + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(sc485sl->vblank, vblank_def, + SC485SL_VTS_MAX - mode->height, + 1, vblank_def); + dst_link_freq = mode->link_freq_idx; + dst_pixel_rate = (u32)link_freq_menu_items[mode->link_freq_idx] / + mode->bpp * 2 * lanes; + __v4l2_ctrl_s_ctrl_int64(sc485sl->pixel_rate, + dst_pixel_rate); + __v4l2_ctrl_s_ctrl(sc485sl->link_freq, + dst_link_freq); + sc485sl->cur_fps = mode->max_fps; + } + + mutex_unlock(&sc485sl->mutex); + + return 0; +} + +static int sc485sl_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + const struct sc485sl_mode *mode = sc485sl->cur_mode; + + mutex_lock(&sc485sl->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + fmt->format = *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); +#else + mutex_unlock(&sc485sl->mutex); + return -ENOTTY; +#endif + } else { + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = mode->bus_fmt; + fmt->format.field = V4L2_FIELD_NONE; + /* format info: width/height/data type/virctual channel */ + if (fmt->pad < PAD_MAX && mode->hdr_mode != NO_HDR) + fmt->reserved[0] = mode->vc[fmt->pad]; + else + fmt->reserved[0] = mode->vc[PAD0]; + } + mutex_unlock(&sc485sl->mutex); + + return 0; +} + +static int sc485sl_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(bus_code)) + return -EINVAL; + code->code = bus_code[code->index]; + + return 0; +} + +static int sc485sl_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + + if (fse->index >= sc485sl->cfg_num) + return -EINVAL; + + if (fse->code != sc485sl->supported_modes[fse->index].bus_fmt) + return -EINVAL; + + fse->min_width = sc485sl->supported_modes[fse->index].width; + fse->max_width = sc485sl->supported_modes[fse->index].width; + fse->min_height = sc485sl->supported_modes[fse->index].height; + fse->max_height = sc485sl->supported_modes[fse->index].height; + + return 0; +} + +static int sc485sl_enable_test_pattern(struct sc485sl *sc485sl, u32 pattern) +{ + u32 val = 0; + int ret = 0; + + ret = sc485sl_read_reg(sc485sl->client, SC485SL_REG_TEST_PATTERN, + SC485SL_REG_VALUE_08BIT, &val); + if (pattern) + val |= SC485SL_TEST_PATTERN_BIT_MASK; + else + val &= ~SC485SL_TEST_PATTERN_BIT_MASK; + + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_REG_TEST_PATTERN, + SC485SL_REG_VALUE_08BIT, val); + return ret; +} + +static int sc485sl_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + const struct sc485sl_mode *mode = sc485sl->cur_mode; + + if (sc485sl->streaming) + fi->interval = sc485sl->cur_fps; + else + fi->interval = mode->max_fps; + return 0; +} + +static const struct sc485sl_mode *sc485sl_find_mode(struct sc485sl *sc485sl, int fps) +{ + const struct sc485sl_mode *mode = NULL; + const struct sc485sl_mode *match = NULL; + int cur_fps = 0; + int i = 0; + + for (i = 0; i < sc485sl->cfg_num; i++) { + mode = &sc485sl->supported_modes[i]; + if (mode->width == sc485sl->cur_mode->width && + mode->height == sc485sl->cur_mode->height && + mode->hdr_mode == sc485sl->cur_mode->hdr_mode && + mode->bus_fmt == sc485sl->cur_mode->bus_fmt) { + cur_fps = DIV_ROUND_CLOSEST(mode->max_fps.denominator, mode->max_fps.numerator); + if (cur_fps == fps) { + match = mode; + break; + } + } + } + return match; +} + +static int sc485sl_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + const struct sc485sl_mode *mode = NULL; + struct v4l2_fract *fract = &fi->interval; + s64 h_blank, vblank_def; + u64 pixel_rate = 0; + int fps; + + if (sc485sl->streaming) + return -EBUSY; + + if (fi->pad != 0) + return -EINVAL; + + if (fract->numerator == 0) { + v4l2_err(sd, "error param, check interval param\n"); + return -EINVAL; + } + fps = DIV_ROUND_CLOSEST(fract->denominator, fract->numerator); + mode = sc485sl_find_mode(sc485sl, fps); + if (mode == NULL) { + v4l2_err(sd, "couldn't match fi\n"); + return -EINVAL; + } + + sc485sl->cur_mode = mode; + + h_blank = mode->hts_def - mode->width; + __v4l2_ctrl_modify_range(sc485sl->hblank, h_blank, + h_blank, 1, h_blank); + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(sc485sl->vblank, vblank_def, + SC485SL_VTS_MAX - mode->height, + 1, vblank_def); + pixel_rate = (u32)link_freq_menu_items[mode->link_freq_idx] / + mode->bpp * 2 * mode->lanes; + + __v4l2_ctrl_s_ctrl_int64(sc485sl->pixel_rate, + pixel_rate); + __v4l2_ctrl_s_ctrl(sc485sl->link_freq, + mode->link_freq_idx); + sc485sl->cur_fps = mode->max_fps; + + return 0; +} + +static int sc485sl_g_mbus_config(struct v4l2_subdev *sd, + unsigned int pad_id, + struct v4l2_mbus_config *config) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + u8 lanes = sc485sl->bus_cfg.bus.mipi_csi2.num_data_lanes; + + config->type = V4L2_MBUS_CSI2_DPHY; + config->bus.mipi_csi2.num_data_lanes = lanes; + + return 0; +} + +static void sc485sl_get_module_inf(struct sc485sl *sc485sl, + struct rkmodule_inf *inf) +{ + memset(inf, 0, sizeof(*inf)); + strscpy(inf->base.sensor, SC485SL_NAME, sizeof(inf->base.sensor)); + strscpy(inf->base.module, sc485sl->module_name, + sizeof(inf->base.module)); + strscpy(inf->base.lens, sc485sl->len_name, sizeof(inf->base.lens)); +} + +static int sc485sl_set_setting(struct sc485sl *sc485sl, struct rk_sensor_setting *setting) +{ + int i = 0; + int cur_fps = 0; + s64 h_blank, vblank_def; + u64 pixel_rate = 0; + const struct sc485sl_mode *mode = NULL; + const struct sc485sl_mode *match = NULL; + u8 lane = sc485sl->bus_cfg.bus.mipi_csi2.num_data_lanes; + + dev_info(&sc485sl->client->dev, + "sensor setting: %d x %d, fps:%d fmt:%d, mode:%d\n", + setting->width, setting->height, + setting->fps, setting->fmt, setting->mode); + + for (i = 0; i < sc485sl->cfg_num; i++) { + mode = &sc485sl->supported_modes[i]; + if (mode->width == setting->width && + mode->height == setting->height && + mode->hdr_mode == setting->mode && + mode->bus_fmt == setting->fmt) { + cur_fps = DIV_ROUND_CLOSEST(mode->max_fps.denominator, mode->max_fps.numerator); + if (cur_fps == setting->fps) { + match = mode; + break; + } + } + } + + if (match) { + dev_info(&sc485sl->client->dev, "-----%s: match the support mode, mode idx:%d-----\n", + __func__, i); + sc485sl->cur_mode = mode; + + h_blank = mode->hts_def - mode->width; + __v4l2_ctrl_modify_range(sc485sl->hblank, h_blank, + h_blank, 1, h_blank); + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(sc485sl->vblank, vblank_def, + SC485SL_VTS_MAX - mode->height, + 1, vblank_def); + + + __v4l2_ctrl_s_ctrl(sc485sl->link_freq, mode->link_freq_idx); + pixel_rate = (u32)link_freq_menu_items[mode->link_freq_idx] / + mode->bpp * 2 * lane; + __v4l2_ctrl_s_ctrl_int64(sc485sl->pixel_rate, pixel_rate); + dev_info(&sc485sl->client->dev, "freq_idx:%d pixel_rate:%lld\n", + mode->link_freq_idx, pixel_rate); + + sc485sl->cur_vts = mode->vts_def; + sc485sl->cur_fps = mode->max_fps; + + dev_info(&sc485sl->client->dev, "hts_def:%d cur_vts:%d cur_fps:%d\n", + mode->hts_def, mode->vts_def, + sc485sl->cur_fps.denominator / sc485sl->cur_fps.numerator); + } else { + dev_err(&sc485sl->client->dev, "couldn't match the support modes\n"); + return -EINVAL; + } + + return 0; +} + +static long sc485sl_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + struct rkmodule_hdr_cfg *hdr; + struct rk_sensor_setting *setting; + u32 i, h, w; + long ret = 0; + u32 stream = 0; + u64 dst_link_freq = 0; + u64 dst_pixel_rate = 0; + u8 lanes = sc485sl->bus_cfg.bus.mipi_csi2.num_data_lanes; + const struct sc485sl_mode *mode; + int cur_best_fit = -1; + int cur_best_fit_dist = -1; + int cur_dist, cur_fps, dst_fps; + + switch (cmd) { + case RKMODULE_GET_MODULE_INFO: + sc485sl_get_module_inf(sc485sl, (struct rkmodule_inf *)arg); + break; + case RKMODULE_GET_HDR_CFG: + hdr = (struct rkmodule_hdr_cfg *)arg; + hdr->esp.mode = HDR_NORMAL_VC; + hdr->hdr_mode = sc485sl->cur_mode->hdr_mode; + break; + case RKMODULE_SET_HDR_CFG: + hdr = (struct rkmodule_hdr_cfg *)arg; + if (hdr->hdr_mode == sc485sl->cur_mode->hdr_mode) + return 0; + w = sc485sl->cur_mode->width; + h = sc485sl->cur_mode->height; + dst_fps = DIV_ROUND_CLOSEST(sc485sl->cur_mode->max_fps.denominator, + sc485sl->cur_mode->max_fps.numerator); + for (i = 0; i < sc485sl->cfg_num; i++) { + if (w == sc485sl->supported_modes[i].width && + h == sc485sl->supported_modes[i].height && + sc485sl->supported_modes[i].hdr_mode == hdr->hdr_mode && + sc485sl->supported_modes[i].bus_fmt == sc485sl->cur_mode->bus_fmt) { + cur_fps = DIV_ROUND_CLOSEST(sc485sl->supported_modes[i].max_fps.denominator, + sc485sl->supported_modes[i].max_fps.numerator); + cur_dist = abs(cur_fps - dst_fps); + if (cur_best_fit_dist == -1 || cur_dist < cur_best_fit_dist) { + cur_best_fit_dist = cur_dist; + cur_best_fit = i; + } else if (cur_dist == cur_best_fit_dist) { + cur_best_fit = i; + break; + } + } + } + if (cur_best_fit == -1) { + dev_err(&sc485sl->client->dev, + "not find hdr mode:%d %dx%d config\n", + hdr->hdr_mode, w, h); + ret = -EINVAL; + } else { + sc485sl->cur_mode = &sc485sl->supported_modes[cur_best_fit]; + mode = sc485sl->cur_mode; + w = mode->hts_def - mode->width; + h = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(sc485sl->hblank, w, w, 1, w); + __v4l2_ctrl_modify_range(sc485sl->vblank, h, + SC485SL_VTS_MAX - sc485sl->cur_mode->height, 1, h); + sc485sl->cur_fps = sc485sl->cur_mode->max_fps; + + dst_link_freq = mode->link_freq_idx; + dst_pixel_rate = (u32)link_freq_menu_items[mode->link_freq_idx] / + mode->bpp * 2 * lanes; + __v4l2_ctrl_s_ctrl_int64(sc485sl->pixel_rate, + dst_pixel_rate); + __v4l2_ctrl_s_ctrl(sc485sl->link_freq, + dst_link_freq); + } + break; + case PREISP_CMD_SET_HDRAE_EXP: + sc485sl_set_hdrae(sc485sl, arg); + if (sc485sl->cam_sw_inf) + memcpy(&sc485sl->cam_sw_inf->hdr_ae, (struct preisp_hdrae_exp_s *)(arg), + sizeof(struct preisp_hdrae_exp_s)); + break; + case RKMODULE_SET_QUICK_STREAM: + + stream = *((u32 *)arg); + + if (sc485sl->standby_hw) { /* hardware standby */ + if (stream) { + sc485sl->is_standby = false; + /* pwdn gpio pull up */ + if (!IS_ERR(sc485sl->pwdn_gpio)) + gpiod_set_value_cansleep(sc485sl->pwdn_gpio, 1); + // Make sure __v4l2_ctrl_handler_setup can be called correctly + usleep_range(4000, 5000); + /* mipi clk on */ + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_REG_MIPI_CTRL, + SC485SL_REG_VALUE_08BIT, + SC485SL_MIPI_CTRL_ON); + +#if IS_REACHABLE(CONFIG_VIDEO_CAM_SLEEP_WAKEUP) + if (__v4l2_ctrl_handler_setup(&sc485sl->ctrl_handler)) + dev_err(&sc485sl->client->dev, "__v4l2_ctrl_handler_setup fail!"); + /* Check if the current mode is HDR and cam sw info is available */ + if (sc485sl->cur_mode->hdr_mode != NO_HDR && sc485sl->cam_sw_inf) { + ret = sc485sl_ioctl(&sc485sl->subdev, + PREISP_CMD_SET_HDRAE_EXP, + &sc485sl->cam_sw_inf->hdr_ae); + if (ret) { + dev_err(&sc485sl->client->dev, + "Failed init exp fail in hdr mode\n"); + return ret; + } + + } +#endif + + /* stream on */ + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_REG_CTRL_MODE, + SC485SL_REG_VALUE_08BIT, + SC485SL_MODE_STREAMING); + dev_info(&sc485sl->client->dev, + "quickstream, streaming on: exit hw standby mode\n"); + } else { + /* stream off */ + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_REG_CTRL_MODE, + SC485SL_REG_VALUE_08BIT, + SC485SL_MODE_SW_STANDBY); + /* mipi clk off */ + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_REG_MIPI_CTRL, + SC485SL_REG_VALUE_08BIT, + SC485SL_MIPI_CTRL_OFF); + + sc485sl->is_standby = true; + /* pwnd gpio pull down */ + if (!IS_ERR(sc485sl->pwdn_gpio)) + gpiod_set_value_cansleep(sc485sl->pwdn_gpio, 0); + dev_info(&sc485sl->client->dev, + "quickstream, streaming off: enter hw standby mode\n"); + } + } else { /* software standby */ + if (stream) { + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_REG_MIPI_CTRL, + SC485SL_REG_VALUE_08BIT, + SC485SL_MIPI_CTRL_ON); + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_REG_CTRL_MODE, + SC485SL_REG_VALUE_08BIT, + SC485SL_MODE_STREAMING); + dev_info(&sc485sl->client->dev, + "quickstream, streaming on: exit soft standby mode\n"); + } else { + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_REG_CTRL_MODE, + SC485SL_REG_VALUE_08BIT, + SC485SL_MODE_SW_STANDBY); + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_REG_MIPI_CTRL, + SC485SL_REG_VALUE_08BIT, + SC485SL_MIPI_CTRL_OFF); + dev_info(&sc485sl->client->dev, + "quickstream, streaming off: enter soft standby mode\n"); + } + } + break; + case RKCIS_CMD_SELECT_SETTING: + setting = (struct rk_sensor_setting *)arg; + ret = sc485sl_set_setting(sc485sl, setting); + break; + default: + ret = -ENOIOCTLCMD; + break; + } + + return ret; +} + + +#ifdef CONFIG_COMPAT +static long sc485sl_compat_ioctl32(struct v4l2_subdev *sd, + unsigned int cmd, unsigned long arg) +{ + void __user *up = compat_ptr(arg); + struct rkmodule_inf *inf; + struct rkmodule_hdr_cfg *hdr; + struct preisp_hdrae_exp_s *hdrae; + struct rk_sensor_setting *setting; + long ret; + u32 stream = 0; + + switch (cmd) { + case RKMODULE_GET_MODULE_INFO: + inf = kzalloc(sizeof(*inf), GFP_KERNEL); + if (!inf) { + ret = -ENOMEM; + return ret; + } + + ret = sc485sl_ioctl(sd, cmd, inf); + if (!ret) { + if (copy_to_user(up, inf, sizeof(*inf))) + ret = -EFAULT; + } + kfree(inf); + break; + case RKMODULE_GET_HDR_CFG: + hdr = kzalloc(sizeof(*hdr), GFP_KERNEL); + if (!hdr) { + ret = -ENOMEM; + return ret; + } + + ret = sc485sl_ioctl(sd, cmd, hdr); + if (!ret) { + if (copy_to_user(up, hdr, sizeof(*hdr))) + ret = -EFAULT; + } + kfree(hdr); + break; + case RKMODULE_SET_HDR_CFG: + hdr = kzalloc(sizeof(*hdr), GFP_KERNEL); + if (!hdr) { + ret = -ENOMEM; + return ret; + } + + ret = copy_from_user(hdr, up, sizeof(*hdr)); + if (!ret) + ret = sc485sl_ioctl(sd, cmd, hdr); + else + ret = -EFAULT; + kfree(hdr); + break; + case PREISP_CMD_SET_HDRAE_EXP: + hdrae = kzalloc(sizeof(*hdrae), GFP_KERNEL); + if (!hdrae) { + ret = -ENOMEM; + return ret; + } + + ret = copy_from_user(hdrae, up, sizeof(*hdrae)); + if (!ret) + ret = sc485sl_ioctl(sd, cmd, hdrae); + else + ret = -EFAULT; + kfree(hdrae); + break; + case RKMODULE_SET_QUICK_STREAM: + ret = copy_from_user(&stream, up, sizeof(u32)); + if (!ret) + ret = sc485sl_ioctl(sd, cmd, &stream); + else + ret = -EFAULT; + break; + case RKCIS_CMD_SELECT_SETTING: + setting = kzalloc(sizeof(*setting), GFP_KERNEL); + if (!setting) { + ret = -ENOMEM; + return ret; + } + + ret = copy_from_user(setting, up, sizeof(*setting)); + if (!ret) + ret = sc485sl_ioctl(sd, cmd, setting); + else + ret = -EFAULT; + kfree(setting); + break; + default: + ret = -ENOIOCTLCMD; + break; + } + + return ret; +} +#endif + +static int __sc485sl_start_stream(struct sc485sl *sc485sl) +{ + int ret; + + if (!sc485sl->is_thunderboot) { + ret = sc485sl_write_array(sc485sl->client, sc485sl->cur_mode->reg_list); + if (ret) + return ret; + /* In case these controls are set before streaming */ + ret = __v4l2_ctrl_handler_setup(&sc485sl->ctrl_handler); + if (ret) + return ret; + if (sc485sl->has_init_exp && sc485sl->cur_mode->hdr_mode != NO_HDR) { + ret = sc485sl_ioctl(&sc485sl->subdev, PREISP_CMD_SET_HDRAE_EXP, + &sc485sl->init_hdrae_exp); + if (ret) { + dev_err(&sc485sl->client->dev, + "init exp fail in hdr mode\n"); + return ret; + } + } + } + ret = sc485sl_write_reg(sc485sl->client, SC485SL_REG_CTRL_MODE, + SC485SL_REG_VALUE_08BIT, SC485SL_MODE_STREAMING); + return ret; +} + +static int __sc485sl_stop_stream(struct sc485sl *sc485sl) +{ + sc485sl->has_init_exp = false; + if (sc485sl->is_thunderboot) + sc485sl->is_first_streamoff = true; + return sc485sl_write_reg(sc485sl->client, SC485SL_REG_CTRL_MODE, + SC485SL_REG_VALUE_08BIT, SC485SL_MODE_SW_STANDBY); +} + +/* Calculate the delay in us by clock rate and clock cycles */ +static inline u32 sc485sl_cal_delay(u32 cycles, struct sc485sl *sc485sl) +{ + return DIV_ROUND_UP(cycles, sc485sl->cur_mode->mclk / 1000 / 1000); +} + +static int __sc485sl_power_on(struct sc485sl *sc485sl) +{ + int ret; + u32 delay_us; + struct device *dev = &sc485sl->client->dev; + + if (!IS_ERR_OR_NULL(sc485sl->pins_default)) { + ret = pinctrl_select_state(sc485sl->pinctrl, + sc485sl->pins_default); + if (ret < 0) + dev_err(dev, "could not set pins\n"); + } + ret = clk_set_rate(sc485sl->xvclk, sc485sl->cur_mode->mclk); + if (ret < 0) + dev_warn(dev, "Failed to set xvclk rate (%dHz)\n", sc485sl->cur_mode->mclk); + if (clk_get_rate(sc485sl->xvclk) != sc485sl->cur_mode->mclk) + dev_warn(dev, "xvclk mismatched, modes are based on %dHz\n", + sc485sl->cur_mode->mclk); + ret = clk_prepare_enable(sc485sl->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + + cam_sw_regulator_bulk_init(sc485sl->cam_sw_inf, SC485SL_NUM_SUPPLIES, sc485sl->supplies); + + if (sc485sl->is_thunderboot) + return 0; + + if (!IS_ERR(sc485sl->reset_gpio)) + gpiod_set_value_cansleep(sc485sl->reset_gpio, 0); + + ret = regulator_bulk_enable(SC485SL_NUM_SUPPLIES, sc485sl->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + goto disable_clk; + } + + if (!IS_ERR(sc485sl->reset_gpio)) + gpiod_set_value_cansleep(sc485sl->reset_gpio, 1); + + usleep_range(500, 1000); + + if (!IS_ERR(sc485sl->pwdn_gpio)) + gpiod_set_value_cansleep(sc485sl->pwdn_gpio, 1); + + if (!IS_ERR(sc485sl->reset_gpio)) + usleep_range(6000, 8000); + else + usleep_range(12000, 16000); + + /* 8192 cycles prior to first SCCB transaction */ + delay_us = sc485sl_cal_delay(8192, sc485sl); + usleep_range(delay_us, delay_us * 2); + + return 0; + +disable_clk: + clk_disable_unprepare(sc485sl->xvclk); + + return ret; +} + +static void __sc485sl_power_off(struct sc485sl *sc485sl) +{ + int ret; + struct device *dev = &sc485sl->client->dev; + + clk_disable_unprepare(sc485sl->xvclk); + if (sc485sl->is_thunderboot) { + if (sc485sl->is_first_streamoff) { + sc485sl->is_thunderboot = false; + sc485sl->is_first_streamoff = false; + } else { + return; + } + } + + if (!IS_ERR(sc485sl->pwdn_gpio)) + gpiod_set_value_cansleep(sc485sl->pwdn_gpio, 0); + clk_disable_unprepare(sc485sl->xvclk); + if (!IS_ERR(sc485sl->reset_gpio)) + gpiod_set_value_cansleep(sc485sl->reset_gpio, 0); + if (!IS_ERR_OR_NULL(sc485sl->pins_sleep)) { + ret = pinctrl_select_state(sc485sl->pinctrl, + sc485sl->pins_sleep); + if (ret < 0) + dev_dbg(dev, "could not set pins\n"); + } + regulator_bulk_disable(SC485SL_NUM_SUPPLIES, sc485sl->supplies); +} + +static int sc485sl_s_stream(struct v4l2_subdev *sd, int on) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + struct i2c_client *client = sc485sl->client; + int ret = 0; + + mutex_lock(&sc485sl->mutex); + + on = !!on; + if (on == sc485sl->streaming) + goto unlock_and_return; + + if (on) { + if (sc485sl->is_thunderboot && rkisp_tb_get_state() == RKISP_TB_NG) { + sc485sl->is_thunderboot = false; + __sc485sl_power_on(sc485sl); + } + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + ret = __sc485sl_start_stream(sc485sl); + if (ret) { + v4l2_err(sd, "start stream failed while write regs\n"); + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + } else { + __sc485sl_stop_stream(sc485sl); + pm_runtime_put(&client->dev); + } + + sc485sl->streaming = on; +unlock_and_return: + mutex_unlock(&sc485sl->mutex); + return ret; +} + +static int sc485sl_s_power(struct v4l2_subdev *sd, int on) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + struct i2c_client *client = sc485sl->client; + int ret = 0; + + mutex_lock(&sc485sl->mutex); + + /* If the power state is not modified - no work to do. */ + if (sc485sl->power_on == !!on) + goto unlock_and_return; + + if (on) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + + if (!sc485sl->is_thunderboot) { + ret = sc485sl_write_array(sc485sl->client, + sc485sl->cur_mode->global_reg_list); + if (ret) { + v4l2_err(sd, "could not set init registers\n"); + pm_runtime_put_noidle(&client->dev); + goto unlock_and_return; + } + } + + sc485sl->power_on = true; + } else { + pm_runtime_put(&client->dev); + sc485sl->power_on = false; + } + +unlock_and_return: + mutex_unlock(&sc485sl->mutex); + + return ret; +} + +#if IS_REACHABLE(CONFIG_VIDEO_CAM_SLEEP_WAKEUP) +static int __maybe_unused sc485sl_resume(struct device *dev) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct sc485sl *sc485sl = to_sc485sl(sd); + + if (sc485sl->standby_hw) { + dev_info(dev, "resume standby!"); + return 0; + } + + cam_sw_prepare_wakeup(sc485sl->cam_sw_inf, dev); + usleep_range(4000, 5000); + cam_sw_write_array(sc485sl->cam_sw_inf); + + if (__v4l2_ctrl_handler_setup(&sc485sl->ctrl_handler)) + dev_err(dev, "__v4l2_ctrl_handler_setup fail!"); + + if (sc485sl->has_init_exp && sc485sl->cur_mode != NO_HDR) { // hdr mode + ret = sc485sl_ioctl(&sc485sl->subdev, PREISP_CMD_SET_HDRAE_EXP, + &sc485sl->cam_sw_inf->hdr_ae); + if (ret) { + dev_err(&sc485sl->client->dev, "set exp fail in hdr mode\n"); + return ret; + } + } + + return 0; +} + +static int __maybe_unused sc485sl_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct sc485sl *sc485sl = to_sc485sl(sd); + + if (sc485sl->standby_hw) { + dev_info(dev, "suspend standby!"); + return 0; + } + + cam_sw_write_array_cb_init(sc485sl->cam_sw_inf, client, + (void *)sc485sl->cur_mode->reg_list, + (sensor_write_array)sc485sl_write_array); + cam_sw_prepare_sleep(sc485sl->cam_sw_inf); + + return 0; +} +#else +#define sc485sl_resume NULL +#define sc485sl_suspend NULL +#endif + +static int __maybe_unused sc485sl_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct sc485sl *sc485sl = to_sc485sl(sd); + + return __sc485sl_power_on(sc485sl); +} + +static int __maybe_unused sc485sl_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct sc485sl *sc485sl = to_sc485sl(sd); + + __sc485sl_power_off(sc485sl); + + return 0; +} + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static int sc485sl_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->state, 0); + const struct sc485sl_mode *def_mode = &sc485sl->supported_modes[0]; + + mutex_lock(&sc485sl->mutex); + /* Initialize try_fmt */ + try_fmt->width = def_mode->width; + try_fmt->height = def_mode->height; + try_fmt->code = def_mode->bus_fmt; + try_fmt->field = V4L2_FIELD_NONE; + + mutex_unlock(&sc485sl->mutex); + /* No crop or compose */ + + return 0; +} +#endif + +static int sc485sl_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct sc485sl *sc485sl = to_sc485sl(sd); + + if (fie->index >= sc485sl->cfg_num) + return -EINVAL; + + fie->code = sc485sl->supported_modes[fie->index].bus_fmt; + fie->width = sc485sl->supported_modes[fie->index].width; + fie->height = sc485sl->supported_modes[fie->index].height; + fie->interval = sc485sl->supported_modes[fie->index].max_fps; + fie->reserved[0] = sc485sl->supported_modes[fie->index].hdr_mode; + return 0; +} + +static const struct dev_pm_ops sc485sl_pm_ops = { + SET_RUNTIME_PM_OPS(sc485sl_runtime_suspend, + sc485sl_runtime_resume, NULL) +#ifdef CONFIG_VIDEO_CAM_SLEEP_WAKEUP + SET_LATE_SYSTEM_SLEEP_PM_OPS(sc485sl_suspend, sc485sl_resume) +#endif +}; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API +static const struct v4l2_subdev_internal_ops sc485sl_internal_ops = { + .open = sc485sl_open, +}; +#endif + +static const struct v4l2_subdev_core_ops sc485sl_core_ops = { + .s_power = sc485sl_s_power, + .ioctl = sc485sl_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = sc485sl_compat_ioctl32, +#endif +}; + +static const struct v4l2_subdev_video_ops sc485sl_video_ops = { + .s_stream = sc485sl_s_stream, + .g_frame_interval = sc485sl_g_frame_interval, + .s_frame_interval = sc485sl_s_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops sc485sl_pad_ops = { + .enum_mbus_code = sc485sl_enum_mbus_code, + .enum_frame_size = sc485sl_enum_frame_sizes, + .enum_frame_interval = sc485sl_enum_frame_interval, + .get_fmt = sc485sl_get_fmt, + .set_fmt = sc485sl_set_fmt, + .get_mbus_config = sc485sl_g_mbus_config, +}; + +static const struct v4l2_subdev_ops sc485sl_subdev_ops = { + .core = &sc485sl_core_ops, + .video = &sc485sl_video_ops, + .pad = &sc485sl_pad_ops, +}; + +static void sc485sl_modify_fps_info(struct sc485sl *sc485sl) +{ + const struct sc485sl_mode *mode = sc485sl->cur_mode; + + sc485sl->cur_fps.denominator = mode->max_fps.denominator * mode->vts_def / + sc485sl->cur_vts; +} + +static int sc485sl_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct sc485sl *sc485sl = container_of(ctrl->handler, + struct sc485sl, ctrl_handler); + struct i2c_client *client = sc485sl->client; + s64 max; + int ret = 0; + u32 val = 0; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = sc485sl->cur_mode->height + ctrl->val - 5; + __v4l2_ctrl_modify_range(sc485sl->exposure, + sc485sl->exposure->minimum, max, + sc485sl->exposure->step, + sc485sl->exposure->default_value); + break; + } + + if (sc485sl->standby_hw && sc485sl->is_standby) { + dev_dbg(&client->dev, "%s: is_standby = true, will return\n", __func__); + return 0; + } + + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + dev_dbg(&client->dev, "set exposure 0x%x\n", ctrl->val); + if (sc485sl->cur_mode->hdr_mode == NO_HDR) { + /* exposure use one line as unit */ + /* 4 least significant bits of expsoure are fractional part */ + ret = sc485sl_write_reg(sc485sl->client, + SC485SL_REG_EXPOSURE_H, + SC485SL_REG_VALUE_08BIT, + SC485SL_FETCH_EXP_H(ctrl->val)); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_EXPOSURE_M, + SC485SL_REG_VALUE_08BIT, + SC485SL_FETCH_EXP_M(ctrl->val)); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_EXPOSURE_L, + SC485SL_REG_VALUE_08BIT, + SC485SL_FETCH_EXP_L(ctrl->val)); + } + break; + case V4L2_CID_ANALOGUE_GAIN: + dev_dbg(&client->dev, "set gain 0x%x\n", ctrl->val); + if (sc485sl->cur_mode->hdr_mode == NO_HDR) + ret = sc485sl_set_gain_reg(sc485sl, ctrl->val, SC485SL_LGAIN); + break; + case V4L2_CID_VBLANK: + dev_dbg(&client->dev, "set vblank 0x%x\n", ctrl->val); + ret = sc485sl_write_reg(sc485sl->client, + SC485SL_REG_VTS_H, + SC485SL_REG_VALUE_08BIT, + 0x00); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_VTS_M, + SC485SL_REG_VALUE_08BIT, + (ctrl->val + sc485sl->cur_mode->height) + >> 8); + ret |= sc485sl_write_reg(sc485sl->client, + SC485SL_REG_VTS_L, + SC485SL_REG_VALUE_08BIT, + (ctrl->val + sc485sl->cur_mode->height) + & 0xff); + sc485sl->cur_vts = ctrl->val + sc485sl->cur_mode->height; + if (sc485sl->cur_vts != sc485sl->cur_mode->vts_def) + sc485sl_modify_fps_info(sc485sl); + break; + case V4L2_CID_TEST_PATTERN: + ret = sc485sl_enable_test_pattern(sc485sl, ctrl->val); + break; + case V4L2_CID_HFLIP: + ret = sc485sl_read_reg(sc485sl->client, SC485SL_FLIP_MIRROR_REG, + SC485SL_REG_VALUE_08BIT, &val); + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_FLIP_MIRROR_REG, + SC485SL_REG_VALUE_08BIT, + SC485SL_FETCH_MIRROR(val, ctrl->val)); + break; + case V4L2_CID_VFLIP: + ret = sc485sl_read_reg(sc485sl->client, SC485SL_FLIP_MIRROR_REG, + SC485SL_REG_VALUE_08BIT, &val); + ret |= sc485sl_write_reg(sc485sl->client, SC485SL_FLIP_MIRROR_REG, + SC485SL_REG_VALUE_08BIT, + SC485SL_FETCH_FLIP(val, ctrl->val)); + break; + default: + dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", + __func__, ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + + +static const struct v4l2_ctrl_ops sc485sl_ctrl_ops = { + .s_ctrl = sc485sl_set_ctrl, +}; + +static int sc485sl_initialize_controls(struct sc485sl *sc485sl) +{ + const struct sc485sl_mode *mode; + struct v4l2_ctrl_handler *handler; + s64 exposure_max, vblank_def; + u32 h_blank; + int ret; + u64 dst_link_freq = 0; + u64 dst_pixel_rate = 0; + u8 lanes = sc485sl->bus_cfg.bus.mipi_csi2.num_data_lanes; + + handler = &sc485sl->ctrl_handler; + mode = sc485sl->cur_mode; + ret = v4l2_ctrl_handler_init(handler, 9); + if (ret) + return ret; + handler->lock = &sc485sl->mutex; + + sc485sl->link_freq = v4l2_ctrl_new_int_menu(handler, NULL, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + if (sc485sl->link_freq) + sc485sl->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + dst_link_freq = mode->link_freq_idx; + /* pixel rate = link frequency * 2 * lanes / BITS_PER_SAMPLE */ + dst_pixel_rate = (u32)link_freq_menu_items[mode->link_freq_idx] / + mode->bpp * 2 * lanes; + if (lanes == 2) { + if (dst_link_freq == SC485SL_LINK_FREQ_360) + sc485sl->pixel_rate = + v4l2_ctrl_new_std(handler, NULL, + V4L2_CID_PIXEL_RATE, 0, + PIXEL_RATE_WITH_360M_10BIT_2L, + 1, dst_pixel_rate); + else if (dst_link_freq == SC485SL_LINK_FREQ_720) + sc485sl->pixel_rate = + v4l2_ctrl_new_std(handler, NULL, + V4L2_CID_PIXEL_RATE, 0, + PIXEL_RATE_WITH_720M_10BIT_2L, + 1, dst_pixel_rate); + + + } else if (lanes == 4) { + if (mode->hdr_mode == NO_HDR) + sc485sl->pixel_rate = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, + 0, PIXEL_RATE_WITH_396M_10BIT_4L, + 1, dst_pixel_rate); + else if (mode->hdr_mode == HDR_X2) + sc485sl->pixel_rate = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, + 0, PIXEL_RATE_WITH_540M_10BIT_4L, + 1, dst_pixel_rate); + } + + __v4l2_ctrl_s_ctrl(sc485sl->link_freq, dst_link_freq); + + h_blank = mode->hts_def - mode->width; + sc485sl->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, + h_blank, h_blank, 1, h_blank); + if (sc485sl->hblank) + sc485sl->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + vblank_def = mode->vts_def - mode->height; + sc485sl->vblank = v4l2_ctrl_new_std(handler, &sc485sl_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + SC485SL_VTS_MAX - mode->height, + 1, vblank_def); + exposure_max = mode->vts_def - 8; + sc485sl->exposure = v4l2_ctrl_new_std(handler, &sc485sl_ctrl_ops, + V4L2_CID_EXPOSURE, SC485SL_EXPOSURE_MIN, + exposure_max, SC485SL_EXPOSURE_STEP, + mode->exp_def); //Set default exposure + sc485sl->anal_gain = v4l2_ctrl_new_std(handler, &sc485sl_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, SC485SL_GAIN_MIN, + SC485SL_GAIN_MAX, SC485SL_GAIN_STEP, + SC485SL_GAIN_DEFAULT); //Set default gain + sc485sl->test_pattern = v4l2_ctrl_new_std_menu_items(handler, + &sc485sl_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(sc485sl_test_pattern_menu) - 1, + 0, 0, sc485sl_test_pattern_menu); + v4l2_ctrl_new_std(handler, &sc485sl_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(handler, &sc485sl_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (handler->error) { + ret = handler->error; + dev_err(&sc485sl->client->dev, + "Failed to init controls(%d)\n", ret); + goto err_free_handler; + } + + sc485sl->subdev.ctrl_handler = handler; + sc485sl->has_init_exp = false; + sc485sl->cur_fps = mode->max_fps; + sc485sl->is_standby = false; + + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(handler); + + return ret; +} + +static int sc485sl_check_sensor_id(struct sc485sl *sc485sl, + struct i2c_client *client) +{ + struct device *dev = &sc485sl->client->dev; + u32 id = 0; + int ret; + + if (sc485sl->is_thunderboot) { + dev_info(dev, "Enable thunderboot mode, skip sensor id check\n"); + return 0; + } + + ret = sc485sl_read_reg(client, SC485SL_REG_CHIP_ID, + SC485SL_REG_VALUE_16BIT, &id); + if (id != CHIP_ID) { + dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret); + return -ENODEV; + } + + dev_info(dev, "Detected SC485SL (0x%04x) sensor\n", CHIP_ID); + + return 0; +} + +static int sc485sl_configure_regulators(struct sc485sl *sc485sl) +{ + unsigned int i; + + for (i = 0; i < SC485SL_NUM_SUPPLIES; i++) + sc485sl->supplies[i].supply = sc485sl_supply_names[i]; + + return devm_regulator_bulk_get(&sc485sl->client->dev, + SC485SL_NUM_SUPPLIES, + sc485sl->supplies); +} + +static int sc485sl_read_module_info(struct sc485sl *sc485sl) +{ + int ret; + struct device *dev = &sc485sl->client->dev; + struct device_node *node = dev->of_node; + + ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX, + &sc485sl->module_index); + ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING, + &sc485sl->module_facing); + ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME, + &sc485sl->module_name); + ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME, + &sc485sl->len_name); + if (ret) + dev_err(dev, "could not get module information!\n"); + + /* Compatible with non-standby mode if this attribute is not configured in dts*/ + of_property_read_u32(node, RKMODULE_CAMERA_STANDBY_HW, + &sc485sl->standby_hw); + dev_info(dev, "sc485sl->standby_hw = %d\n", sc485sl->standby_hw); + + return ret; +} + +static int sc485sl_find_modes(struct sc485sl *sc485sl) +{ + int i, ret; + u32 hdr_mode = 0; + struct device_node *endpoint; + struct device *dev = &sc485sl->client->dev; + struct device_node *node = sc485sl->client->dev.of_node; + + ret = of_property_read_u32(node, OF_CAMERA_HDR_MODE, &hdr_mode); + if (ret) { + hdr_mode = NO_HDR; + dev_warn(dev, "Get hdr mode failed! no hdr default\n"); + } else + dev_warn(dev, "Get hdr mode OK! hdr_mode = %d\n", hdr_mode); + + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!endpoint) { + dev_err(dev, "Failed to get endpoint\n"); + return -EINVAL; + } + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), + &sc485sl->bus_cfg); + of_node_put(endpoint); + if (ret) { + dev_err(dev, "Failed to get bus config\n"); + return -EINVAL; + } + + if (sc485sl->bus_cfg.bus.mipi_csi2.num_data_lanes == 4) { + sc485sl->supported_modes = supported_modes_4lane; + sc485sl->cfg_num = ARRAY_SIZE(supported_modes_4lane); + dev_info(dev, "Detect sc485sl lane: %d\n", + sc485sl->bus_cfg.bus.mipi_csi2.num_data_lanes); + } else { + sc485sl->supported_modes = supported_modes_2lane; + sc485sl->cfg_num = ARRAY_SIZE(supported_modes_2lane); + dev_info(dev, "Detect sc485sl lane: %d\n", + sc485sl->bus_cfg.bus.mipi_csi2.num_data_lanes); + } + + for (i = 0; i < sc485sl->cfg_num; i++) { + if (hdr_mode == sc485sl->supported_modes[i].hdr_mode) { + sc485sl->cur_mode = &sc485sl->supported_modes[i]; + break; + } + } + + if (i == sc485sl->cfg_num) + sc485sl->cur_mode = &sc485sl->supported_modes[0]; + + return 0; +} + +static int sc485sl_setup_clocks_and_gpios(struct sc485sl *sc485sl) +{ + struct device *dev = &sc485sl->client->dev; + + sc485sl->xvclk = devm_clk_get(dev, "xvclk"); + if (IS_ERR(sc485sl->xvclk)) { + dev_err(dev, "Failed to get xvclk\n"); + return -EINVAL; + } + + sc485sl->reset_gpio = devm_gpiod_get(dev, "reset", + sc485sl->is_thunderboot ? GPIOD_ASIS : GPIOD_OUT_LOW); + if (IS_ERR(sc485sl->reset_gpio)) + dev_warn(dev, "Failed to get reset-gpios\n"); + + sc485sl->pwdn_gpio = devm_gpiod_get(dev, "pwdn", + sc485sl->is_thunderboot ? GPIOD_ASIS : GPIOD_OUT_LOW); + if (IS_ERR(sc485sl->pwdn_gpio)) + dev_warn(dev, "Failed to get pwdn-gpios\n"); + + sc485sl->pinctrl = devm_pinctrl_get(dev); + if (!IS_ERR(sc485sl->pinctrl)) { + sc485sl->pins_default = + pinctrl_lookup_state(sc485sl->pinctrl, + OF_CAMERA_PINCTRL_STATE_DEFAULT); + if (IS_ERR(sc485sl->pins_default)) + dev_err(dev, "could not get default pinstate\n"); + + sc485sl->pins_sleep = + pinctrl_lookup_state(sc485sl->pinctrl, + OF_CAMERA_PINCTRL_STATE_SLEEP); + if (IS_ERR(sc485sl->pins_sleep)) + dev_err(dev, "could not get sleep pinstate\n"); + } else { + dev_err(dev, "no pinctrl\n"); + } + + return 0; +} + +static int sc485sl_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct sc485sl *sc485sl; + struct v4l2_subdev *sd; + + char facing[2]; + int ret; + + dev_info(dev, "driver version: %02x.%02x.%02x", + DRIVER_VERSION >> 16, + (DRIVER_VERSION & 0xff00) >> 8, + DRIVER_VERSION & 0x00ff); + + sc485sl = devm_kzalloc(dev, sizeof(*sc485sl), GFP_KERNEL); + if (!sc485sl) + return -ENOMEM; + + sc485sl->is_thunderboot = IS_ENABLED(CONFIG_VIDEO_ROCKCHIP_THUNDER_BOOT_ISP); + sc485sl->client = client; + + ret = sc485sl_read_module_info(sc485sl); + if (ret) { + dev_err(dev, "could not get module information!\n"); + return -EINVAL; + } + + /* Set current mode based on HDR mode */ + ret = sc485sl_find_modes(sc485sl); + if (ret) { + dev_err(dev, "Failed to get modes!\n"); + return -EINVAL; + } + + /* setup sc485sl clock and gpios*/ + ret = sc485sl_setup_clocks_and_gpios(sc485sl); + if (ret) { + dev_err(dev, "Failed to set up clocks and GPIOs\n"); + return ret; + } + + ret = sc485sl_configure_regulators(sc485sl); + if (ret) { + dev_err(dev, "Failed to get power regulators\n"); + return ret; + } + + mutex_init(&sc485sl->mutex); + + sd = &sc485sl->subdev; + v4l2_i2c_subdev_init(sd, client, &sc485sl_subdev_ops); + ret = sc485sl_initialize_controls(sc485sl); + if (ret) + goto err_destroy_mutex; + + ret = __sc485sl_power_on(sc485sl); + if (ret) + goto err_free_handler; + + ret = sc485sl_check_sensor_id(sc485sl, client); + if (ret) + goto err_power_off; + +#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API + sd->internal_ops = &sc485sl_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; +#endif +#if defined(CONFIG_MEDIA_CONTROLLER) + sc485sl->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &sc485sl->pad); + if (ret < 0) + goto err_power_off; +#endif + + if (!sc485sl->cam_sw_inf) { + sc485sl->cam_sw_inf = cam_sw_init(); + cam_sw_clk_init(sc485sl->cam_sw_inf, sc485sl->xvclk, + sc485sl->cur_mode->mclk); + cam_sw_reset_pin_init(sc485sl->cam_sw_inf, sc485sl->reset_gpio, 0); + cam_sw_pwdn_pin_init(sc485sl->cam_sw_inf, sc485sl->pwdn_gpio, 1); + } + + memset(facing, 0, sizeof(facing)); + if (strcmp(sc485sl->module_facing, "back") == 0) + facing[0] = 'b'; + else + facing[0] = 'f'; + + snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s", + sc485sl->module_index, facing, + SC485SL_NAME, dev_name(sd->dev)); + ret = v4l2_async_register_subdev_sensor(sd); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_clean_entity; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + if (sc485sl->is_thunderboot) + pm_runtime_get_sync(dev); + else + pm_runtime_idle(dev); + + return 0; + +err_clean_entity: +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif +err_power_off: + __sc485sl_power_off(sc485sl); +err_free_handler: + v4l2_ctrl_handler_free(&sc485sl->ctrl_handler); +err_destroy_mutex: + mutex_destroy(&sc485sl->mutex); + + return ret; +} + +static void sc485sl_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct sc485sl *sc485sl = to_sc485sl(sd); + + v4l2_async_unregister_subdev(sd); +#if defined(CONFIG_MEDIA_CONTROLLER) + media_entity_cleanup(&sd->entity); +#endif + v4l2_ctrl_handler_free(&sc485sl->ctrl_handler); + mutex_destroy(&sc485sl->mutex); + + cam_sw_deinit(sc485sl->cam_sw_inf); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + __sc485sl_power_off(sc485sl); + pm_runtime_set_suspended(&client->dev); +} + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id sc485sl_of_match[] = { + { .compatible = "smartsens,sc485sl" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sc485sl_of_match); +#endif + +static const struct i2c_device_id sc485sl_match_id[] = { + { "smartsens,sc485sl", 0 }, + { }, +}; + +static struct i2c_driver sc485sl_i2c_driver = { + .driver = { + .name = SC485SL_NAME, + .pm = &sc485sl_pm_ops, + .of_match_table = of_match_ptr(sc485sl_of_match), + }, + .probe = sc485sl_probe, + .remove = sc485sl_remove, + .id_table = sc485sl_match_id, +}; + +static int __init sensor_mod_init(void) +{ + return i2c_add_driver(&sc485sl_i2c_driver); +} + +static void __exit sensor_mod_exit(void) +{ + i2c_del_driver(&sc485sl_i2c_driver); +} + +#if defined(CONFIG_VIDEO_ROCKCHIP_THUNDER_BOOT_ISP) +subsys_initcall(sensor_mod_init); +#else +device_initcall_sync(sensor_mod_init); +#endif +module_exit(sensor_mod_exit); + +MODULE_DESCRIPTION("smartsens sc485sl CMOS Image Sensor driver"); +MODULE_LICENSE("GPL"); From 243363ccfdc2f2ed3be8e210d1687d7a8b14a97a Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Wed, 30 Jul 2025 11:29:33 +0800 Subject: [PATCH 14/14] misc: rockchip: pcie-rkep: Remove redundancy vm_flags 1.It will be added by remap_pfn_range_internal; 2.Fix compile warning: drivers/misc/rockchip/pcie-rkep.c:613:23: error: assignment of read-only member 'vm_flags' 613 | vma->vm_flags |= VM_IO; | ^~ drivers/misc/rockchip/pcie-rkep.c:614:23: error: assignment of read-only member 'vm_flags' 614 | vma->vm_flags |= (VM_DONTEXPAND | VM_DONTDUMP); | ^~ Change-Id: I3c705248b216c246a8efb25f28c44fc419110fee Signed-off-by: Jon Lin --- drivers/misc/rockchip/pcie-rkep.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/misc/rockchip/pcie-rkep.c b/drivers/misc/rockchip/pcie-rkep.c index 1d96a61d53a4..f448513101fc 100644 --- a/drivers/misc/rockchip/pcie-rkep.c +++ b/drivers/misc/rockchip/pcie-rkep.c @@ -610,9 +610,6 @@ static int pcie_rkep_mmap(struct file *file, struct vm_area_struct *vma) return -EINVAL; } - vma->vm_flags |= VM_IO; - vma->vm_flags |= (VM_DONTEXPAND | VM_DONTDUMP); - if (pcie_rkep->cur_mmap_res == PCIE_EP_MMAP_RESOURCE_BAR2 || pcie_rkep->cur_mmap_res == PCIE_EP_MMAP_RESOURCE_USER_MEM) vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);