diff --git a/target/linux/rockchip/patches-6.18/045-59-v6.19-media-dt-bindings-add-rockchip-px30-vip.patch b/target/linux/rockchip/patches-6.18/045-59-v6.19-media-dt-bindings-add-rockchip-px30-vip.patch new file mode 100644 index 00000000000..1ccc7e6f9ec --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-59-v6.19-media-dt-bindings-add-rockchip-px30-vip.patch @@ -0,0 +1,147 @@ +From a36a0b28b94e86432239c4893e6fc2c39ce31a53 Mon Sep 17 00:00:00 2001 +From: Mehdi Djait +Date: Fri, 14 Nov 2025 16:20:14 +0100 +Subject: [PATCH] media: dt-bindings: add rockchip px30 vip + +Add documentation for the Rockchip PX30 Video Input Processor (VIP). + +Signed-off-by: Mehdi Djait +[revised description] +Reviewed-by: Rob Herring (Arm) +Signed-off-by: Michael Riesch +Reviewed-by: Bryan O'Donoghue +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../bindings/media/rockchip,px30-vip.yaml | 124 ++++++++++++++++++ + 1 file changed, 124 insertions(+) + create mode 100644 Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml +@@ -0,0 +1,124 @@ ++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/media/rockchip,px30-vip.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Rockchip PX30 Video Input Processor (VIP) ++ ++maintainers: ++ - Mehdi Djait ++ - Michael Riesch ++ ++description: ++ The Rockchip PX30 Video Input Processor (VIP) receives the data from a camera ++ sensor or CCIR656 encoder and transfers it into system main memory by AXI bus. ++ ++properties: ++ compatible: ++ const: rockchip,px30-vip ++ ++ reg: ++ maxItems: 1 ++ ++ interrupts: ++ maxItems: 1 ++ ++ clocks: ++ items: ++ - description: ACLK ++ - description: HCLK ++ - description: PCLK ++ ++ clock-names: ++ items: ++ - const: aclk ++ - const: hclk ++ - const: pclk ++ ++ resets: ++ items: ++ - description: AXI ++ - description: AHB ++ - description: PCLK IN ++ ++ reset-names: ++ items: ++ - const: axi ++ - const: ahb ++ - const: pclkin ++ ++ power-domains: ++ maxItems: 1 ++ ++ ports: ++ $ref: /schemas/graph.yaml#/properties/ports ++ ++ properties: ++ port@0: ++ $ref: /schemas/graph.yaml#/$defs/port-base ++ unevaluatedProperties: false ++ description: input port on the parallel interface ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++ properties: ++ bus-type: ++ enum: ++ - 5 # MEDIA_BUS_TYPE_PARALLEL ++ - 6 # MEDIA_BUS_TYPE_BT656 ++ ++ required: ++ - bus-type ++ ++ required: ++ - port@0 ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ - clocks ++ - ports ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ #include ++ #include ++ ++ soc { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ video-capture@ff490000 { ++ compatible = "rockchip,px30-vip"; ++ reg = <0x0 0xff490000 0x0 0x200>; ++ interrupts = ; ++ clocks = <&cru ACLK_CIF>, <&cru HCLK_CIF>, <&cru PCLK_CIF>; ++ clock-names = "aclk", "hclk", "pclk"; ++ power-domains = <&power PX30_PD_VI>; ++ resets = <&cru SRST_CIF_A>, <&cru SRST_CIF_H>, <&cru SRST_CIF_PCLKIN>; ++ reset-names = "axi", "ahb", "pclkin"; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { ++ reg = <0>; ++ ++ cif_in: endpoint { ++ remote-endpoint = <&tw9900_out>; ++ bus-type = ; ++ }; ++ }; ++ }; ++ }; ++ }; diff --git a/target/linux/rockchip/patches-6.18/045-60-v6.19-media-dt-bindings-add-rockchip-rk3568-vicap.patch b/target/linux/rockchip/patches-6.18/045-60-v6.19-media-dt-bindings-add-rockchip-rk3568-vicap.patch new file mode 100644 index 00000000000..ca16e9f3274 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-60-v6.19-media-dt-bindings-add-rockchip-rk3568-vicap.patch @@ -0,0 +1,193 @@ +From 60836eeca0fb27ad6a41bc2c303be80a0371447f Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 14 Nov 2025 16:20:15 +0100 +Subject: [PATCH] media: dt-bindings: add rockchip rk3568 vicap + +Add documentation for the Rockchip RK3568 Video Capture (VICAP) unit. + +Signed-off-by: Michael Riesch +Reviewed-by: Krzysztof Kozlowski +Reviewed-by: Bryan O'Donoghue +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../bindings/media/rockchip,rk3568-vicap.yaml | 172 ++++++++++++++++++ + 1 file changed, 172 insertions(+) + create mode 100644 Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml +@@ -0,0 +1,172 @@ ++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/media/rockchip,rk3568-vicap.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Rockchip RK3568 Video Capture (VICAP) ++ ++maintainers: ++ - Michael Riesch ++ ++description: ++ The Rockchip RK3568 Video Capture (VICAP) block features a digital video ++ port (DVP, a parallel video interface) and a MIPI CSI-2 port. It receives ++ the data from camera sensors, video decoders, or other companion ICs and ++ transfers it into system main memory by AXI bus. ++ ++properties: ++ compatible: ++ const: rockchip,rk3568-vicap ++ ++ reg: ++ maxItems: 1 ++ ++ interrupts: ++ maxItems: 1 ++ ++ clocks: ++ items: ++ - description: ACLK ++ - description: HCLK ++ - description: DCLK ++ - description: ICLK ++ ++ clock-names: ++ items: ++ - const: aclk ++ - const: hclk ++ - const: dclk ++ - const: iclk ++ ++ iommus: ++ maxItems: 1 ++ ++ resets: ++ items: ++ - description: ARST ++ - description: HRST ++ - description: DRST ++ - description: PRST ++ - description: IRST ++ ++ reset-names: ++ items: ++ - const: arst ++ - const: hrst ++ - const: drst ++ - const: prst ++ - const: irst ++ ++ rockchip,grf: ++ $ref: /schemas/types.yaml#/definitions/phandle ++ description: Phandle to general register file used for video input block control. ++ ++ power-domains: ++ maxItems: 1 ++ ++ ports: ++ $ref: /schemas/graph.yaml#/properties/ports ++ ++ properties: ++ port@0: ++ $ref: /schemas/graph.yaml#/$defs/port-base ++ unevaluatedProperties: false ++ description: The digital video port (DVP, a parallel video interface). ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++ properties: ++ bus-type: ++ enum: ++ - 5 # MEDIA_BUS_TYPE_PARALLEL ++ - 6 # MEDIA_BUS_TYPE_BT656 ++ ++ rockchip,dvp-clk-delay: ++ $ref: /schemas/types.yaml#/definitions/uint32 ++ default: 0 ++ minimum: 0 ++ maximum: 127 ++ description: ++ Delay the DVP path clock input to align the sampling phase, ++ only valid in dual edge sampling mode. Delay is zero by ++ default and can be adjusted optionally. ++ ++ required: ++ - bus-type ++ ++ port@1: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: Port connected to the MIPI CSI-2 receiver output. ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ - clocks ++ - ports ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ #include ++ #include ++ #include ++ ++ soc { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ vicap: video-capture@fdfe0000 { ++ compatible = "rockchip,rk3568-vicap"; ++ reg = <0x0 0xfdfe0000 0x0 0x200>; ++ interrupts = ; ++ assigned-clocks = <&cru DCLK_VICAP>; ++ assigned-clock-rates = <300000000>; ++ clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>, ++ <&cru DCLK_VICAP>, <&cru ICLK_VICAP_G>; ++ clock-names = "aclk", "hclk", "dclk", "iclk"; ++ iommus = <&vicap_mmu>; ++ power-domains = <&power RK3568_PD_VI>; ++ resets = <&cru SRST_A_VICAP>, <&cru SRST_H_VICAP>, ++ <&cru SRST_D_VICAP>, <&cru SRST_P_VICAP>, ++ <&cru SRST_I_VICAP>; ++ reset-names = "arst", "hrst", "drst", "prst", "irst"; ++ rockchip,grf = <&grf>; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ vicap_dvp: port@0 { ++ reg = <0>; ++ ++ vicap_dvp_input: endpoint { ++ bus-type = ; ++ bus-width = <16>; ++ pclk-sample = ; ++ remote-endpoint = <&it6801_output>; ++ }; ++ }; ++ ++ vicap_mipi: port@1 { ++ reg = <1>; ++ ++ vicap_mipi_input: endpoint { ++ remote-endpoint = <&csi_output>; ++ }; ++ }; ++ }; ++ }; ++ }; diff --git a/target/linux/rockchip/patches-6.18/045-61-v6.19-media-rockchip-add-driver-for-the-rockchip-camera-interfac.patch b/target/linux/rockchip/patches-6.18/045-61-v6.19-media-rockchip-add-driver-for-the-rockchip-camera-interfac.patch new file mode 100644 index 00000000000..dbf1169ae23 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-61-v6.19-media-rockchip-add-driver-for-the-rockchip-camera-interfac.patch @@ -0,0 +1,398 @@ +From f53fb31a388901cfe11b95a3a2cf1594355321e4 Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 14 Nov 2025 16:20:16 +0100 +Subject: [PATCH] media: rockchip: add driver for the rockchip camera interface + +Add the skeleton for a media controller centric V4L2 driver for the +Rockchip Camera Interface (CIF). +The skeleton features support for the PX30 Video Input Processor (VIP) +and the RK3568 Video Capture (VICAP) unit. + +Tested-by: Gerald Loacker +Reviewed-by: Gerald Loacker +Reviewed-by: Bryan O'Donoghue +Reviewed-by: Mehdi Djait +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + drivers/media/platform/rockchip/Kconfig | 1 + + drivers/media/platform/rockchip/Makefile | 1 + + drivers/media/platform/rockchip/rkcif/Kconfig | 18 ++ + .../media/platform/rockchip/rkcif/Makefile | 4 + + .../platform/rockchip/rkcif/rkcif-common.h | 55 ++++ + .../media/platform/rockchip/rkcif/rkcif-dev.c | 262 ++++++++++++++++++ + 6 files changed, 341 insertions(+) + create mode 100644 drivers/media/platform/rockchip/rkcif/Kconfig + create mode 100644 drivers/media/platform/rockchip/rkcif/Makefile + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-common.h + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-dev.c + +--- a/drivers/media/platform/rockchip/Kconfig ++++ b/drivers/media/platform/rockchip/Kconfig +@@ -3,5 +3,6 @@ + comment "Rockchip media platform drivers" + + source "drivers/media/platform/rockchip/rga/Kconfig" ++source "drivers/media/platform/rockchip/rkcif/Kconfig" + source "drivers/media/platform/rockchip/rkisp1/Kconfig" + source "drivers/media/platform/rockchip/rkvdec/Kconfig" +--- a/drivers/media/platform/rockchip/Makefile ++++ b/drivers/media/platform/rockchip/Makefile +@@ -1,4 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0-only + obj-y += rga/ ++obj-y += rkcif/ + obj-y += rkisp1/ + obj-y += rkvdec/ +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/Kconfig +@@ -0,0 +1,18 @@ ++config VIDEO_ROCKCHIP_CIF ++ tristate "Rockchip Camera Interface (CIF)" ++ depends on VIDEO_DEV ++ depends on ARCH_ROCKCHIP || COMPILE_TEST ++ depends on V4L_PLATFORM_DRIVERS ++ depends on PM && COMMON_CLK ++ select MEDIA_CONTROLLER ++ select VIDEOBUF2_DMA_CONTIG ++ select V4L2_FWNODE ++ select VIDEO_V4L2_SUBDEV_API ++ help ++ This is a driver for Rockchip Camera Interface (CIF). It is featured ++ in many Rockchips SoCs in different variations, such as the PX30 ++ Video Input Processor (VIP, one Digital Video Port (DVP)) or the ++ RK3568 Video Capture (VICAP, one DVP, one MIPI CSI-2 receiver) unit. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called rockchip-cif. +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/Makefile +@@ -0,0 +1,4 @@ ++# SPDX-License-Identifier: GPL-2.0 ++obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-cif.o ++ ++rockchip-cif-objs += rkcif-dev.o +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h +@@ -0,0 +1,55 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Copyright (C) 2018 Rockchip Electronics Co., Ltd. ++ * Copyright (C) 2023 Mehdi Djait ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2025 Collabora, Ltd. ++ */ ++ ++#ifndef _RKCIF_COMMON_H ++#define _RKCIF_COMMON_H ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define RKCIF_DRIVER_NAME "rockchip-cif" ++#define RKCIF_CLK_MAX 4 ++ ++struct rkcif_remote { ++ struct v4l2_async_connection async_conn; ++ struct v4l2_subdev *sd; ++}; ++ ++struct rkcif_match_data { ++ const char *const *clks; ++ unsigned int clks_num; ++}; ++ ++struct rkcif_device { ++ struct device *dev; ++ ++ const struct rkcif_match_data *match_data; ++ struct clk_bulk_data clks[RKCIF_CLK_MAX]; ++ unsigned int clks_num; ++ struct regmap *grf; ++ struct reset_control *reset; ++ void __iomem *base_addr; ++ ++ struct media_device media_dev; ++ struct v4l2_device v4l2_dev; ++ struct v4l2_async_notifier notifier; ++}; ++ ++#endif +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c +@@ -0,0 +1,262 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Copyright (C) 2018 Rockchip Electronics Co., Ltd. ++ * Copyright (C) 2020 Maxime Chevallier ++ * Copyright (C) 2023 Mehdi Djait ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2025 Collabora, Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "rkcif-common.h" ++ ++static const char *const px30_vip_clks[] = { ++ "aclk", ++ "hclk", ++ "pclk", ++}; ++ ++static const struct rkcif_match_data px30_vip_match_data = { ++ .clks = px30_vip_clks, ++ .clks_num = ARRAY_SIZE(px30_vip_clks), ++}; ++ ++static const char *const rk3568_vicap_clks[] = { ++ "aclk", ++ "hclk", ++ "dclk", ++ "iclk", ++}; ++ ++static const struct rkcif_match_data rk3568_vicap_match_data = { ++ .clks = rk3568_vicap_clks, ++ .clks_num = ARRAY_SIZE(rk3568_vicap_clks), ++}; ++ ++static const struct of_device_id rkcif_plat_of_match[] = { ++ { ++ .compatible = "rockchip,px30-vip", ++ .data = &px30_vip_match_data, ++ }, ++ { ++ .compatible = "rockchip,rk3568-vicap", ++ .data = &rk3568_vicap_match_data, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, rkcif_plat_of_match); ++ ++static int rkcif_register(struct rkcif_device *rkcif) ++{ ++ return 0; ++} ++ ++static void rkcif_unregister(struct rkcif_device *rkcif) ++{ ++} ++ ++static int rkcif_notifier_bound(struct v4l2_async_notifier *notifier, ++ struct v4l2_subdev *sd, ++ struct v4l2_async_connection *asd) ++{ ++ struct rkcif_remote *remote = ++ container_of(asd, struct rkcif_remote, async_conn); ++ ++ remote->sd = sd; ++ ++ return 0; ++} ++ ++static int rkcif_notifier_complete(struct v4l2_async_notifier *notifier) ++{ ++ struct rkcif_device *rkcif = ++ container_of(notifier, struct rkcif_device, notifier); ++ ++ return v4l2_device_register_subdev_nodes(&rkcif->v4l2_dev); ++} ++ ++static const struct v4l2_async_notifier_operations rkcif_notifier_ops = { ++ .bound = rkcif_notifier_bound, ++ .complete = rkcif_notifier_complete, ++}; ++ ++static irqreturn_t rkcif_isr(int irq, void *ctx) ++{ ++ irqreturn_t ret = IRQ_NONE; ++ ++ return ret; ++} ++ ++static int rkcif_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct rkcif_device *rkcif; ++ int ret, irq; ++ ++ rkcif = devm_kzalloc(dev, sizeof(*rkcif), GFP_KERNEL); ++ if (!rkcif) ++ return -ENOMEM; ++ ++ rkcif->match_data = of_device_get_match_data(dev); ++ if (!rkcif->match_data) ++ return -ENODEV; ++ ++ dev_set_drvdata(dev, rkcif); ++ rkcif->dev = dev; ++ ++ rkcif->base_addr = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(rkcif->base_addr)) ++ return PTR_ERR(rkcif->base_addr); ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ ++ ret = devm_request_irq(dev, irq, rkcif_isr, IRQF_SHARED, ++ dev_driver_string(dev), dev); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to request irq\n"); ++ ++ if (rkcif->match_data->clks_num > RKCIF_CLK_MAX) ++ return dev_err_probe(dev, -EINVAL, "invalid number of clocks\n"); ++ ++ rkcif->clks_num = rkcif->match_data->clks_num; ++ for (unsigned int i = 0; i < rkcif->clks_num; i++) ++ rkcif->clks[i].id = rkcif->match_data->clks[i]; ++ ret = devm_clk_bulk_get(dev, rkcif->clks_num, rkcif->clks); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to get clocks\n"); ++ ++ rkcif->reset = devm_reset_control_array_get_exclusive(dev); ++ if (IS_ERR(rkcif->reset)) ++ return PTR_ERR(rkcif->reset); ++ ++ rkcif->grf = syscon_regmap_lookup_by_phandle(dev->of_node, ++ "rockchip,grf"); ++ if (IS_ERR(rkcif->grf)) ++ rkcif->grf = NULL; ++ ++ pm_runtime_enable(&pdev->dev); ++ ++ rkcif->media_dev.dev = dev; ++ strscpy(rkcif->media_dev.model, RKCIF_DRIVER_NAME, ++ sizeof(rkcif->media_dev.model)); ++ media_device_init(&rkcif->media_dev); ++ ++ rkcif->v4l2_dev.mdev = &rkcif->media_dev; ++ ret = v4l2_device_register(dev, &rkcif->v4l2_dev); ++ if (ret) ++ goto err_media_dev_cleanup; ++ ++ ret = media_device_register(&rkcif->media_dev); ++ if (ret < 0) { ++ dev_err(dev, "failed to register media device: %d\n", ret); ++ goto err_v4l2_dev_unregister; ++ } ++ ++ v4l2_async_nf_init(&rkcif->notifier, &rkcif->v4l2_dev); ++ rkcif->notifier.ops = &rkcif_notifier_ops; ++ ++ ret = rkcif_register(rkcif); ++ if (ret) { ++ dev_err(dev, "failed to register media entities: %d\n", ret); ++ goto err_notifier_cleanup; ++ } ++ ++ ret = v4l2_async_nf_register(&rkcif->notifier); ++ if (ret) ++ goto err_rkcif_unregister; ++ ++ return 0; ++ ++err_rkcif_unregister: ++ rkcif_unregister(rkcif); ++err_notifier_cleanup: ++ v4l2_async_nf_cleanup(&rkcif->notifier); ++ media_device_unregister(&rkcif->media_dev); ++err_v4l2_dev_unregister: ++ v4l2_device_unregister(&rkcif->v4l2_dev); ++err_media_dev_cleanup: ++ media_device_cleanup(&rkcif->media_dev); ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++} ++ ++static void rkcif_remove(struct platform_device *pdev) ++{ ++ struct rkcif_device *rkcif = platform_get_drvdata(pdev); ++ ++ v4l2_async_nf_unregister(&rkcif->notifier); ++ rkcif_unregister(rkcif); ++ v4l2_async_nf_cleanup(&rkcif->notifier); ++ media_device_unregister(&rkcif->media_dev); ++ v4l2_device_unregister(&rkcif->v4l2_dev); ++ media_device_cleanup(&rkcif->media_dev); ++ pm_runtime_disable(&pdev->dev); ++} ++ ++static int rkcif_runtime_suspend(struct device *dev) ++{ ++ struct rkcif_device *rkcif = dev_get_drvdata(dev); ++ ++ /* ++ * Reset CIF (CRU, DMA, FIFOs) to allow a clean resume. ++ * Since this resets the IOMMU too, we cannot issue this reset when ++ * resuming. ++ */ ++ reset_control_assert(rkcif->reset); ++ udelay(5); ++ reset_control_deassert(rkcif->reset); ++ ++ clk_bulk_disable_unprepare(rkcif->clks_num, rkcif->clks); ++ ++ return 0; ++} ++ ++static int rkcif_runtime_resume(struct device *dev) ++{ ++ struct rkcif_device *rkcif = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = clk_bulk_prepare_enable(rkcif->clks_num, rkcif->clks); ++ if (ret) { ++ dev_err(dev, "failed to enable clocks\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops rkcif_plat_pm_ops = { ++ .runtime_suspend = rkcif_runtime_suspend, ++ .runtime_resume = rkcif_runtime_resume, ++}; ++ ++static struct platform_driver rkcif_plat_drv = { ++ .driver = { ++ .name = RKCIF_DRIVER_NAME, ++ .of_match_table = rkcif_plat_of_match, ++ .pm = &rkcif_plat_pm_ops, ++ }, ++ .probe = rkcif_probe, ++ .remove = rkcif_remove, ++}; ++module_platform_driver(rkcif_plat_drv); ++ ++MODULE_DESCRIPTION("Rockchip Camera Interface (CIF) platform driver"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/rockchip/patches-6.18/045-62-v6.19-media-rockchip-rkcif-add-abstraction-for-interface-and-cro.patch b/target/linux/rockchip/patches-6.18/045-62-v6.19-media-rockchip-rkcif-add-abstraction-for-interface-and-cro.patch new file mode 100644 index 00000000000..db8487eeac1 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-62-v6.19-media-rockchip-rkcif-add-abstraction-for-interface-and-cro.patch @@ -0,0 +1,595 @@ +From 85411d17bee99b0a99e983f37188f9cdacfded54 Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 14 Nov 2025 16:20:17 +0100 +Subject: [PATCH] media: rockchip: rkcif: add abstraction for interface and + crop blocks + +Add an abstraction for the INTERFACE and CROP parts of the different +Rockchip Camera Interface (CIF) variants. These parts are represented +as V4L2 subdevice with one sink pad and one source pad. The sink pad +is connected to a subdevice: either the subdevice provided by the +driver of the companion chip connected to the DVP, or the subdevice +provided by the MIPI CSI-2 receiver. The source pad is connected to +V4l2 device(s) provided by one or many instance(s) of the DMA +abstraction. + +Tested-by: Gerald Loacker +Reviewed-by: Gerald Loacker +Reviewed-by: Bryan O'Donoghue +Reviewed-by: Mehdi Djait +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../media/platform/rockchip/rkcif/Makefile | 1 + + .../platform/rockchip/rkcif/rkcif-common.h | 71 +++ + .../media/platform/rockchip/rkcif/rkcif-dev.c | 13 + + .../platform/rockchip/rkcif/rkcif-interface.c | 405 ++++++++++++++++++ + .../platform/rockchip/rkcif/rkcif-interface.h | 31 ++ + 5 files changed, 521 insertions(+) + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-interface.c + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-interface.h + +--- a/drivers/media/platform/rockchip/rkcif/Makefile ++++ b/drivers/media/platform/rockchip/rkcif/Makefile +@@ -2,3 +2,4 @@ + obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-cif.o + + rockchip-cif-objs += rkcif-dev.o ++rockchip-cif-objs += rkcif-interface.o +--- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h +@@ -27,9 +27,78 @@ + #define RKCIF_DRIVER_NAME "rockchip-cif" + #define RKCIF_CLK_MAX 4 + ++enum rkcif_format_type { ++ RKCIF_FMT_TYPE_INVALID, ++ RKCIF_FMT_TYPE_YUV, ++ RKCIF_FMT_TYPE_RAW, ++}; ++ ++enum rkcif_interface_index { ++ RKCIF_DVP, ++ RKCIF_MIPI_BASE, ++ RKCIF_MIPI1 = RKCIF_MIPI_BASE, ++ RKCIF_MIPI2, ++ RKCIF_MIPI3, ++ RKCIF_MIPI4, ++ RKCIF_MIPI5, ++ RKCIF_MIPI6, ++ RKCIF_MIPI_MAX, ++ RKCIF_IF_MAX = RKCIF_MIPI_MAX ++}; ++ ++enum rkcif_interface_pad_index { ++ RKCIF_IF_PAD_SINK, ++ RKCIF_IF_PAD_SRC, ++ RKCIF_IF_PAD_MAX ++}; ++ ++enum rkcif_interface_status { ++ RKCIF_IF_INACTIVE, ++ RKCIF_IF_ACTIVE, ++}; ++ ++enum rkcif_interface_type { ++ RKCIF_IF_INVALID, ++ RKCIF_IF_DVP, ++ RKCIF_IF_MIPI, ++}; ++ ++struct rkcif_input_fmt { ++ u32 mbus_code; ++ ++ enum rkcif_format_type fmt_type; ++ enum v4l2_field field; ++}; ++ ++struct rkcif_interface; ++ + struct rkcif_remote { + struct v4l2_async_connection async_conn; + struct v4l2_subdev *sd; ++ ++ struct rkcif_interface *interface; ++}; ++ ++struct rkcif_dvp { ++ u32 dvp_clk_delay; ++}; ++ ++struct rkcif_interface { ++ enum rkcif_interface_type type; ++ enum rkcif_interface_status status; ++ enum rkcif_interface_index index; ++ struct rkcif_device *rkcif; ++ struct rkcif_remote *remote; ++ const struct rkcif_input_fmt *in_fmts; ++ unsigned int in_fmts_num; ++ ++ struct media_pad pads[RKCIF_IF_PAD_MAX]; ++ struct v4l2_fwnode_endpoint vep; ++ struct v4l2_subdev sd; ++ ++ union { ++ struct rkcif_dvp dvp; ++ }; + }; + + struct rkcif_match_data { +@@ -47,6 +116,8 @@ struct rkcif_device { + struct reset_control *reset; + void __iomem *base_addr; + ++ struct rkcif_interface interfaces[RKCIF_IF_MAX]; ++ + struct media_device media_dev; + struct v4l2_device v4l2_dev; + struct v4l2_async_notifier notifier; +--- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c +@@ -74,8 +74,21 @@ static int rkcif_notifier_bound(struct v + struct v4l2_subdev *sd, + struct v4l2_async_connection *asd) + { ++ struct rkcif_device *rkcif = ++ container_of(notifier, struct rkcif_device, notifier); + struct rkcif_remote *remote = + container_of(asd, struct rkcif_remote, async_conn); ++ struct media_pad *sink_pad = ++ &remote->interface->pads[RKCIF_IF_PAD_SINK]; ++ int ret; ++ ++ ret = v4l2_create_fwnode_links_to_pad(sd, sink_pad, ++ MEDIA_LNK_FL_ENABLED); ++ if (ret) { ++ dev_err(rkcif->dev, "failed to link source pad of %s\n", ++ sd->name); ++ return ret; ++ } + + remote->sd = sd; + +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c +@@ -0,0 +1,405 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2025 Collabora, Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "rkcif-common.h" ++#include "rkcif-interface.h" ++ ++static inline struct rkcif_interface *to_rkcif_interface(struct v4l2_subdev *sd) ++{ ++ return container_of(sd, struct rkcif_interface, sd); ++} ++ ++static const struct media_entity_operations rkcif_interface_media_ops = { ++ .link_validate = v4l2_subdev_link_validate, ++ .has_pad_interdep = v4l2_subdev_has_pad_interdep, ++}; ++ ++static int rkcif_interface_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct rkcif_interface *interface = to_rkcif_interface(sd); ++ const struct rkcif_input_fmt *input; ++ struct v4l2_mbus_framefmt *sink, *src; ++ struct v4l2_rect *crop; ++ u32 other_pad, other_stream; ++ int ret; ++ ++ /* the format on the source pad always matches the sink pad */ ++ if (format->pad == RKCIF_IF_PAD_SRC) ++ return v4l2_subdev_get_fmt(sd, state, format); ++ ++ input = rkcif_interface_find_input_fmt(interface, true, ++ format->format.code); ++ format->format.code = input->mbus_code; ++ ++ sink = v4l2_subdev_state_get_format(state, format->pad, format->stream); ++ if (!sink) ++ return -EINVAL; ++ ++ *sink = format->format; ++ ++ /* propagate the format to the source pad */ ++ src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, ++ format->stream); ++ if (!src) ++ return -EINVAL; ++ ++ *src = *sink; ++ ++ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, ++ format->pad, format->stream, ++ &other_pad, &other_stream); ++ if (ret) ++ return -EINVAL; ++ ++ crop = v4l2_subdev_state_get_crop(state, other_pad, other_stream); ++ if (!crop) ++ return -EINVAL; ++ ++ /* reset crop */ ++ crop->left = 0; ++ crop->top = 0; ++ crop->width = sink->width; ++ crop->height = sink->height; ++ ++ return 0; ++} ++ ++static int rkcif_interface_get_sel(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct v4l2_mbus_framefmt *sink; ++ struct v4l2_rect *crop; ++ int ret = 0; ++ ++ if (sel->pad != RKCIF_IF_PAD_SRC) ++ return -EINVAL; ++ ++ sink = v4l2_subdev_state_get_opposite_stream_format(state, sel->pad, ++ sel->stream); ++ if (!sink) ++ return -EINVAL; ++ ++ crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream); ++ if (!crop) ++ return -EINVAL; ++ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ sel->r.left = 0; ++ sel->r.top = 0; ++ sel->r.width = sink->width; ++ sel->r.height = sink->height; ++ break; ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *crop; ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int rkcif_interface_set_sel(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct v4l2_mbus_framefmt *sink, *src; ++ struct v4l2_rect *crop; ++ ++ if (sel->pad != RKCIF_IF_PAD_SRC || sel->target != V4L2_SEL_TGT_CROP) ++ return -EINVAL; ++ ++ sink = v4l2_subdev_state_get_opposite_stream_format(state, sel->pad, ++ sel->stream); ++ if (!sink) ++ return -EINVAL; ++ ++ src = v4l2_subdev_state_get_format(state, sel->pad, sel->stream); ++ if (!src) ++ return -EINVAL; ++ ++ crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream); ++ if (!crop) ++ return -EINVAL; ++ ++ *crop = sel->r; ++ ++ src->height = sel->r.height; ++ src->width = sel->r.width; ++ ++ return 0; ++} ++ ++static int rkcif_interface_set_routing(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ enum v4l2_subdev_format_whence which, ++ struct v4l2_subdev_krouting *routing) ++{ ++ int ret; ++ ++ ret = v4l2_subdev_routing_validate(sd, routing, ++ V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); ++ if (ret) ++ return ret; ++ ++ ret = v4l2_subdev_set_routing(sd, state, routing); ++ ++ return ret; ++} ++ ++static int rkcif_interface_enable_streams(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct v4l2_subdev *remote_sd; ++ struct media_pad *remote_pad; ++ u64 mask; ++ ++ remote_pad = ++ media_pad_remote_pad_first(&sd->entity.pads[RKCIF_IF_PAD_SINK]); ++ remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); ++ ++ mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK, ++ RKCIF_IF_PAD_SRC, &streams_mask); ++ ++ return v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask); ++} ++ ++static int rkcif_interface_disable_streams(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct v4l2_subdev *remote_sd; ++ struct media_pad *remote_pad; ++ u64 mask; ++ ++ remote_pad = ++ media_pad_remote_pad_first(&sd->entity.pads[RKCIF_IF_PAD_SINK]); ++ remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); ++ ++ mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK, ++ RKCIF_IF_PAD_SRC, &streams_mask); ++ ++ return v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); ++} ++ ++static const struct v4l2_subdev_pad_ops rkcif_interface_pad_ops = { ++ .get_fmt = v4l2_subdev_get_fmt, ++ .set_fmt = rkcif_interface_set_fmt, ++ .get_selection = rkcif_interface_get_sel, ++ .set_selection = rkcif_interface_set_sel, ++ .set_routing = rkcif_interface_set_routing, ++ .enable_streams = rkcif_interface_enable_streams, ++ .disable_streams = rkcif_interface_disable_streams, ++}; ++ ++static const struct v4l2_subdev_ops rkcif_interface_ops = { ++ .pad = &rkcif_interface_pad_ops, ++}; ++ ++static int rkcif_interface_init_state(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state) ++{ ++ struct rkcif_interface *interface = to_rkcif_interface(sd); ++ struct v4l2_subdev_route routes[] = { ++ { ++ .sink_pad = RKCIF_IF_PAD_SINK, ++ .sink_stream = 0, ++ .source_pad = RKCIF_IF_PAD_SRC, ++ .source_stream = 0, ++ .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, ++ }, ++ }; ++ struct v4l2_subdev_krouting routing = { ++ .len_routes = ARRAY_SIZE(routes), ++ .num_routes = ARRAY_SIZE(routes), ++ .routes = routes, ++ }; ++ const struct v4l2_mbus_framefmt dvp_default_format = { ++ .width = 3840, ++ .height = 2160, ++ .code = MEDIA_BUS_FMT_YUYV8_1X16, ++ .field = V4L2_FIELD_NONE, ++ .colorspace = V4L2_COLORSPACE_REC709, ++ .ycbcr_enc = V4L2_YCBCR_ENC_709, ++ .quantization = V4L2_QUANTIZATION_LIM_RANGE, ++ .xfer_func = V4L2_XFER_FUNC_NONE, ++ }; ++ const struct v4l2_mbus_framefmt mipi_default_format = { ++ .width = 3840, ++ .height = 2160, ++ .code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .field = V4L2_FIELD_NONE, ++ .colorspace = V4L2_COLORSPACE_RAW, ++ .ycbcr_enc = V4L2_YCBCR_ENC_601, ++ .quantization = V4L2_QUANTIZATION_FULL_RANGE, ++ .xfer_func = V4L2_XFER_FUNC_NONE, ++ }; ++ const struct v4l2_mbus_framefmt *default_format; ++ int ret; ++ ++ default_format = (interface->type == RKCIF_IF_DVP) ? ++ &dvp_default_format : ++ &mipi_default_format; ++ ++ ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing, ++ default_format); ++ ++ return ret; ++} ++ ++static const struct v4l2_subdev_internal_ops rkcif_interface_internal_ops = { ++ .init_state = rkcif_interface_init_state, ++}; ++ ++static int rkcif_interface_add(struct rkcif_interface *interface) ++{ ++ struct rkcif_device *rkcif = interface->rkcif; ++ struct rkcif_remote *remote; ++ struct v4l2_async_notifier *ntf = &rkcif->notifier; ++ struct v4l2_fwnode_endpoint *vep = &interface->vep; ++ struct device *dev = rkcif->dev; ++ struct fwnode_handle *ep; ++ u32 dvp_clk_delay = 0; ++ int ret; ++ ++ ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), interface->index, ++ 0, 0); ++ if (!ep) ++ return -ENODEV; ++ ++ vep->bus_type = V4L2_MBUS_UNKNOWN; ++ ret = v4l2_fwnode_endpoint_parse(ep, vep); ++ if (ret) ++ goto complete; ++ ++ if (interface->type == RKCIF_IF_DVP) { ++ if (vep->bus_type != V4L2_MBUS_BT656 && ++ vep->bus_type != V4L2_MBUS_PARALLEL) { ++ ret = dev_err_probe(dev, -EINVAL, ++ "unsupported bus type\n"); ++ goto complete; ++ } ++ ++ fwnode_property_read_u32(ep, "rockchip,dvp-clk-delay", ++ &dvp_clk_delay); ++ interface->dvp.dvp_clk_delay = dvp_clk_delay; ++ } ++ ++ remote = v4l2_async_nf_add_fwnode_remote(ntf, ep, struct rkcif_remote); ++ if (IS_ERR(remote)) { ++ ret = PTR_ERR(remote); ++ goto complete; ++ } ++ ++ remote->interface = interface; ++ interface->remote = remote; ++ interface->status = RKCIF_IF_ACTIVE; ++ ret = 0; ++ ++complete: ++ fwnode_handle_put(ep); ++ ++ return ret; ++} ++ ++int rkcif_interface_register(struct rkcif_device *rkcif, ++ struct rkcif_interface *interface) ++{ ++ struct media_pad *pads = interface->pads; ++ struct v4l2_subdev *sd = &interface->sd; ++ int ret; ++ ++ interface->rkcif = rkcif; ++ ++ v4l2_subdev_init(sd, &rkcif_interface_ops); ++ sd->dev = rkcif->dev; ++ sd->entity.ops = &rkcif_interface_media_ops; ++ sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; ++ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; ++ sd->internal_ops = &rkcif_interface_internal_ops; ++ sd->owner = THIS_MODULE; ++ ++ if (interface->type == RKCIF_IF_DVP) ++ snprintf(sd->name, sizeof(sd->name), "rkcif-dvp0"); ++ else if (interface->type == RKCIF_IF_MIPI) ++ snprintf(sd->name, sizeof(sd->name), "rkcif-mipi%d", ++ interface->index - RKCIF_MIPI_BASE); ++ ++ pads[RKCIF_IF_PAD_SINK].flags = MEDIA_PAD_FL_SINK; ++ pads[RKCIF_IF_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; ++ ret = media_entity_pads_init(&sd->entity, RKCIF_IF_PAD_MAX, pads); ++ if (ret) ++ goto err; ++ ++ ret = v4l2_subdev_init_finalize(sd); ++ if (ret) ++ goto err_entity_cleanup; ++ ++ ret = v4l2_device_register_subdev(&rkcif->v4l2_dev, sd); ++ if (ret) { ++ dev_err(sd->dev, "failed to register subdev\n"); ++ goto err_subdev_cleanup; ++ } ++ ++ ret = rkcif_interface_add(interface); ++ if (ret) ++ goto err_subdev_unregister; ++ ++ return 0; ++ ++err_subdev_unregister: ++ v4l2_device_unregister_subdev(sd); ++err_subdev_cleanup: ++ v4l2_subdev_cleanup(sd); ++err_entity_cleanup: ++ media_entity_cleanup(&sd->entity); ++err: ++ return ret; ++} ++ ++void rkcif_interface_unregister(struct rkcif_interface *interface) ++{ ++ struct v4l2_subdev *sd = &interface->sd; ++ ++ if (interface->status != RKCIF_IF_ACTIVE) ++ return; ++ ++ v4l2_device_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(&sd->entity); ++} ++ ++const struct rkcif_input_fmt * ++rkcif_interface_find_input_fmt(struct rkcif_interface *interface, bool ret_def, ++ u32 mbus_code) ++{ ++ const struct rkcif_input_fmt *fmt; ++ ++ WARN_ON(interface->in_fmts_num == 0); ++ ++ for (unsigned int i = 0; i < interface->in_fmts_num; i++) { ++ fmt = &interface->in_fmts[i]; ++ if (fmt->mbus_code == mbus_code) ++ return fmt; ++ } ++ if (ret_def) ++ return &interface->in_fmts[0]; ++ else ++ return NULL; ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.h +@@ -0,0 +1,31 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Abstraction for the INTERFACE and CROP parts of the different CIF variants. ++ * They shall be represented as V4L2 subdevice with one sink pad and one ++ * source pad. The sink pad is connected to a subdevice: either the subdevice ++ * provided by the driver of the companion chip connected to the DVP, or the ++ * subdevice provided by the MIPI CSI-2 receiver driver. The source pad is ++ * to V4l2 device(s) provided by one or many instance(s) of the DMA ++ * abstraction. ++ * ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2025 Collabora, Ltd. ++ */ ++ ++#ifndef _RKCIF_INTERFACE_H ++#define _RKCIF_INTERFACE_H ++ ++#include "rkcif-common.h" ++ ++int rkcif_interface_register(struct rkcif_device *rkcif, ++ struct rkcif_interface *interface); ++ ++void rkcif_interface_unregister(struct rkcif_interface *interface); ++ ++const struct rkcif_input_fmt * ++rkcif_interface_find_input_fmt(struct rkcif_interface *interface, bool ret_def, ++ u32 mbus_code); ++ ++#endif diff --git a/target/linux/rockchip/patches-6.18/045-63-v6.19-media-rockchip-rkcif-add-abstraction-for-dma-blocks.patch b/target/linux/rockchip/patches-6.18/045-63-v6.19-media-rockchip-rkcif-add-abstraction-for-dma-blocks.patch new file mode 100644 index 00000000000..2751ea5cff5 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-63-v6.19-media-rockchip-rkcif-add-abstraction-for-dma-blocks.patch @@ -0,0 +1,883 @@ +From 501802e2ad517a1e2b9766e1b4e396b10f13f140 Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 14 Nov 2025 16:20:18 +0100 +Subject: [PATCH] media: rockchip: rkcif: add abstraction for dma blocks + +Add an abstraction for the DMA parts and the ping-pong scheme (a +double-buffering mechanism) of the different CIF variants. Each +stream is represented as V4L2 device whose corresponding media +entity has one sink pad. This sink pad is connected to an instance +of the INTERFACE/CROP abstraction. + +Tested-by: Gerald Loacker +Reviewed-by: Gerald Loacker +Reviewed-by: Bryan O'Donoghue +Reviewed-by: Mehdi Djait +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +[hverkuil: dropped deprecated vb2_ops_wait_prepare/finish callbacks] +--- + .../media/platform/rockchip/rkcif/Makefile | 1 + + .../platform/rockchip/rkcif/rkcif-common.h | 67 ++ + .../platform/rockchip/rkcif/rkcif-interface.c | 37 + + .../platform/rockchip/rkcif/rkcif-stream.c | 636 ++++++++++++++++++ + .../platform/rockchip/rkcif/rkcif-stream.h | 32 + + 5 files changed, 773 insertions(+) + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-stream.c + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-stream.h + +--- a/drivers/media/platform/rockchip/rkcif/Makefile ++++ b/drivers/media/platform/rockchip/rkcif/Makefile +@@ -3,3 +3,4 @@ obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rock + + rockchip-cif-objs += rkcif-dev.o + rockchip-cif-objs += rkcif-interface.o ++rockchip-cif-objs += rkcif-stream.o +--- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h +@@ -33,6 +33,14 @@ enum rkcif_format_type { + RKCIF_FMT_TYPE_RAW, + }; + ++enum rkcif_id_index { ++ RKCIF_ID0, ++ RKCIF_ID1, ++ RKCIF_ID2, ++ RKCIF_ID3, ++ RKCIF_ID_MAX ++}; ++ + enum rkcif_interface_index { + RKCIF_DVP, + RKCIF_MIPI_BASE, +@@ -63,6 +71,19 @@ enum rkcif_interface_type { + RKCIF_IF_MIPI, + }; + ++struct rkcif_buffer { ++ struct vb2_v4l2_buffer vb; ++ struct list_head queue; ++ dma_addr_t buff_addr[VIDEO_MAX_PLANES]; ++ bool is_dummy; ++}; ++ ++struct rkcif_dummy_buffer { ++ struct rkcif_buffer buffer; ++ void *vaddr; ++ u32 size; ++}; ++ + struct rkcif_input_fmt { + u32 mbus_code; + +@@ -70,6 +91,12 @@ struct rkcif_input_fmt { + enum v4l2_field field; + }; + ++struct rkcif_output_fmt { ++ u32 fourcc; ++ u32 mbus_code; ++ u8 cplanes; ++}; ++ + struct rkcif_interface; + + struct rkcif_remote { +@@ -79,6 +106,42 @@ struct rkcif_remote { + struct rkcif_interface *interface; + }; + ++struct rkcif_stream { ++ enum rkcif_id_index id; ++ struct rkcif_device *rkcif; ++ struct rkcif_interface *interface; ++ const struct rkcif_output_fmt *out_fmts; ++ unsigned int out_fmts_num; ++ ++ /* in ping-pong mode, two buffers can be provided to the HW */ ++ struct rkcif_buffer *buffers[2]; ++ int frame_idx; ++ int frame_phase; ++ ++ /* in case of no available buffer, HW can write to the dummy buffer */ ++ struct rkcif_dummy_buffer dummy; ++ ++ bool stopping; ++ wait_queue_head_t wq_stopped; ++ ++ /* queue of available buffers plus spinlock that protects it */ ++ spinlock_t driver_queue_lock; ++ struct list_head driver_queue; ++ ++ /* lock used by the V4L2 core */ ++ struct mutex vlock; ++ ++ struct media_pad pad; ++ struct media_pipeline pipeline; ++ struct v4l2_pix_format_mplane pix; ++ struct vb2_queue buf_queue; ++ struct video_device vdev; ++ ++ void (*queue_buffer)(struct rkcif_stream *stream, unsigned int index); ++ int (*start_streaming)(struct rkcif_stream *stream); ++ void (*stop_streaming)(struct rkcif_stream *stream); ++}; ++ + struct rkcif_dvp { + u32 dvp_clk_delay; + }; +@@ -89,6 +152,8 @@ struct rkcif_interface { + enum rkcif_interface_index index; + struct rkcif_device *rkcif; + struct rkcif_remote *remote; ++ struct rkcif_stream streams[RKCIF_ID_MAX]; ++ unsigned int streams_num; + const struct rkcif_input_fmt *in_fmts; + unsigned int in_fmts_num; + +@@ -99,6 +164,8 @@ struct rkcif_interface { + union { + struct rkcif_dvp dvp; + }; ++ ++ void (*set_crop)(struct rkcif_stream *stream, u16 left, u16 top); + }; + + struct rkcif_match_data { +--- a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c +@@ -157,15 +157,41 @@ static int rkcif_interface_set_routing(s + if (ret) + return ret; + ++ for (unsigned int i = 0; i < routing->num_routes; i++) { ++ const struct v4l2_subdev_route *route = &routing->routes[i]; ++ ++ if (route->source_stream >= RKCIF_ID_MAX) ++ return -EINVAL; ++ } ++ + ret = v4l2_subdev_set_routing(sd, state, routing); + + return ret; + } + ++static int rkcif_interface_apply_crop(struct rkcif_stream *stream, ++ struct v4l2_subdev_state *state) ++{ ++ struct rkcif_interface *interface = stream->interface; ++ struct v4l2_rect *crop; ++ ++ crop = v4l2_subdev_state_get_crop(state, RKCIF_IF_PAD_SRC, stream->id); ++ if (!crop) ++ return -EINVAL; ++ ++ if (interface->set_crop) ++ interface->set_crop(stream, crop->left, crop->top); ++ ++ return 0; ++} ++ + static int rkcif_interface_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) + { ++ struct rkcif_interface *interface = to_rkcif_interface(sd); ++ struct rkcif_stream *stream; ++ struct v4l2_subdev_route *route; + struct v4l2_subdev *remote_sd; + struct media_pad *remote_pad; + u64 mask; +@@ -174,6 +200,17 @@ static int rkcif_interface_enable_stream + media_pad_remote_pad_first(&sd->entity.pads[RKCIF_IF_PAD_SINK]); + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + ++ /* DVP has one crop setting for all IDs */ ++ if (interface->type == RKCIF_IF_DVP) { ++ stream = &interface->streams[RKCIF_ID0]; ++ rkcif_interface_apply_crop(stream, state); ++ } else { ++ for_each_active_route(&state->routing, route) { ++ stream = &interface->streams[route->sink_stream]; ++ rkcif_interface_apply_crop(stream, state); ++ } ++ } ++ + mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK, + RKCIF_IF_PAD_SRC, &streams_mask); + +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-stream.c +@@ -0,0 +1,636 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2025 Collabora, Ltd. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "rkcif-common.h" ++#include "rkcif-stream.h" ++ ++#define CIF_REQ_BUFS_MIN 1 ++#define CIF_MIN_WIDTH 64 ++#define CIF_MIN_HEIGHT 64 ++#define CIF_MAX_WIDTH 8192 ++#define CIF_MAX_HEIGHT 8192 ++ ++static inline struct rkcif_buffer *to_rkcif_buffer(struct vb2_v4l2_buffer *vb) ++{ ++ return container_of(vb, struct rkcif_buffer, vb); ++} ++ ++static inline struct rkcif_stream *to_rkcif_stream(struct video_device *vdev) ++{ ++ return container_of(vdev, struct rkcif_stream, vdev); ++} ++ ++static struct rkcif_buffer *rkcif_stream_pop_buffer(struct rkcif_stream *stream) ++{ ++ struct rkcif_buffer *buffer; ++ ++ guard(spinlock_irqsave)(&stream->driver_queue_lock); ++ ++ if (list_empty(&stream->driver_queue)) ++ return NULL; ++ ++ buffer = list_first_entry(&stream->driver_queue, struct rkcif_buffer, ++ queue); ++ list_del(&buffer->queue); ++ ++ return buffer; ++} ++ ++static void rkcif_stream_push_buffer(struct rkcif_stream *stream, ++ struct rkcif_buffer *buffer) ++{ ++ guard(spinlock_irqsave)(&stream->driver_queue_lock); ++ ++ list_add_tail(&buffer->queue, &stream->driver_queue); ++} ++ ++static inline void rkcif_stream_return_buffer(struct rkcif_buffer *buffer, ++ enum vb2_buffer_state state) ++{ ++ struct vb2_v4l2_buffer *vb = &buffer->vb; ++ ++ vb2_buffer_done(&vb->vb2_buf, state); ++} ++ ++static void rkcif_stream_complete_buffer(struct rkcif_stream *stream, ++ struct rkcif_buffer *buffer) ++{ ++ struct vb2_v4l2_buffer *vb = &buffer->vb; ++ ++ vb->vb2_buf.timestamp = ktime_get_ns(); ++ vb->sequence = stream->frame_idx; ++ vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE); ++ stream->frame_idx++; ++} ++ ++void rkcif_stream_pingpong(struct rkcif_stream *stream) ++{ ++ struct rkcif_buffer *buffer; ++ ++ buffer = stream->buffers[stream->frame_phase]; ++ if (!buffer->is_dummy) ++ rkcif_stream_complete_buffer(stream, buffer); ++ ++ buffer = rkcif_stream_pop_buffer(stream); ++ if (buffer) { ++ stream->buffers[stream->frame_phase] = buffer; ++ stream->buffers[stream->frame_phase]->is_dummy = false; ++ } else { ++ stream->buffers[stream->frame_phase] = &stream->dummy.buffer; ++ stream->buffers[stream->frame_phase]->is_dummy = true; ++ dev_dbg(stream->rkcif->dev, ++ "no buffer available, frame will be dropped\n"); ++ } ++ ++ if (stream->queue_buffer) ++ stream->queue_buffer(stream, stream->frame_phase); ++ ++ stream->frame_phase = 1 - stream->frame_phase; ++} ++ ++static int rkcif_stream_init_buffers(struct rkcif_stream *stream) ++{ ++ struct v4l2_pix_format_mplane *pix = &stream->pix; ++ ++ stream->buffers[0] = rkcif_stream_pop_buffer(stream); ++ if (!stream->buffers[0]) ++ goto err_buff_0; ++ ++ stream->buffers[1] = rkcif_stream_pop_buffer(stream); ++ if (!stream->buffers[1]) ++ goto err_buff_1; ++ ++ if (stream->queue_buffer) { ++ stream->queue_buffer(stream, 0); ++ stream->queue_buffer(stream, 1); ++ } ++ ++ stream->dummy.size = pix->num_planes * pix->plane_fmt[0].sizeimage; ++ stream->dummy.vaddr = ++ dma_alloc_attrs(stream->rkcif->dev, stream->dummy.size, ++ &stream->dummy.buffer.buff_addr[0], GFP_KERNEL, ++ DMA_ATTR_NO_KERNEL_MAPPING); ++ if (!stream->dummy.vaddr) ++ goto err_dummy; ++ ++ for (unsigned int i = 1; i < pix->num_planes; i++) ++ stream->dummy.buffer.buff_addr[i] = ++ stream->dummy.buffer.buff_addr[i - 1] + ++ pix->plane_fmt[i - 1].bytesperline * pix->height; ++ ++ return 0; ++ ++err_dummy: ++ rkcif_stream_return_buffer(stream->buffers[1], VB2_BUF_STATE_QUEUED); ++ stream->buffers[1] = NULL; ++ ++err_buff_1: ++ rkcif_stream_return_buffer(stream->buffers[0], VB2_BUF_STATE_QUEUED); ++ stream->buffers[0] = NULL; ++err_buff_0: ++ return -EINVAL; ++} ++ ++static void rkcif_stream_return_all_buffers(struct rkcif_stream *stream, ++ enum vb2_buffer_state state) ++{ ++ struct rkcif_buffer *buffer; ++ ++ if (stream->buffers[0] && !stream->buffers[0]->is_dummy) { ++ rkcif_stream_return_buffer(stream->buffers[0], state); ++ stream->buffers[0] = NULL; ++ } ++ ++ if (stream->buffers[1] && !stream->buffers[1]->is_dummy) { ++ rkcif_stream_return_buffer(stream->buffers[1], state); ++ stream->buffers[1] = NULL; ++ } ++ ++ while ((buffer = rkcif_stream_pop_buffer(stream))) ++ rkcif_stream_return_buffer(buffer, state); ++ ++ if (stream->dummy.vaddr) { ++ dma_free_attrs(stream->rkcif->dev, stream->dummy.size, ++ stream->dummy.vaddr, ++ stream->dummy.buffer.buff_addr[0], ++ DMA_ATTR_NO_KERNEL_MAPPING); ++ stream->dummy.vaddr = NULL; ++ } ++} ++ ++static int rkcif_stream_setup_queue(struct vb2_queue *queue, ++ unsigned int *num_buffers, ++ unsigned int *num_planes, ++ unsigned int sizes[], ++ struct device *alloc_devs[]) ++{ ++ struct rkcif_stream *stream = queue->drv_priv; ++ struct v4l2_pix_format_mplane *pix = &stream->pix; ++ ++ if (*num_planes) { ++ if (*num_planes != pix->num_planes) ++ return -EINVAL; ++ ++ for (unsigned int i = 0; i < pix->num_planes; i++) ++ if (sizes[i] < pix->plane_fmt[i].sizeimage) ++ return -EINVAL; ++ } else { ++ *num_planes = pix->num_planes; ++ for (unsigned int i = 0; i < pix->num_planes; i++) ++ sizes[i] = pix->plane_fmt[i].sizeimage; ++ } ++ ++ return 0; ++} ++ ++static int rkcif_stream_prepare_buffer(struct vb2_buffer *vb) ++{ ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ struct rkcif_buffer *buffer = to_rkcif_buffer(vbuf); ++ struct rkcif_stream *stream = vb->vb2_queue->drv_priv; ++ const struct rkcif_output_fmt *fmt; ++ struct v4l2_pix_format_mplane *pix = &stream->pix; ++ unsigned int i; ++ ++ memset(buffer->buff_addr, 0, sizeof(buffer->buff_addr)); ++ for (i = 0; i < pix->num_planes; i++) ++ buffer->buff_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i); ++ ++ /* apply fallback for non-mplane formats, if required */ ++ if (pix->num_planes == 1) { ++ fmt = rkcif_stream_find_output_fmt(stream, true, ++ pix->pixelformat); ++ for (i = 1; i < fmt->cplanes; i++) ++ buffer->buff_addr[i] = ++ buffer->buff_addr[i - 1] + ++ pix->plane_fmt[i - 1].bytesperline * ++ pix->height; ++ } ++ ++ for (i = 0; i < pix->num_planes; i++) { ++ unsigned long size = pix->plane_fmt[i].sizeimage; ++ ++ if (vb2_plane_size(vb, i) < size) { ++ dev_err(stream->rkcif->dev, ++ "user buffer too small (%ld < %ld)\n", ++ vb2_plane_size(vb, i), size); ++ return -EINVAL; ++ } ++ ++ vb2_set_plane_payload(vb, i, size); ++ } ++ ++ vbuf->field = V4L2_FIELD_NONE; ++ ++ return 0; ++} ++ ++static void rkcif_stream_queue_buffer(struct vb2_buffer *vb) ++{ ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ struct rkcif_buffer *buffer = to_rkcif_buffer(vbuf); ++ struct rkcif_stream *stream = vb->vb2_queue->drv_priv; ++ ++ rkcif_stream_push_buffer(stream, buffer); ++} ++ ++static int rkcif_stream_start_streaming(struct vb2_queue *queue, ++ unsigned int count) ++{ ++ struct rkcif_stream *stream = queue->drv_priv; ++ struct rkcif_device *rkcif = stream->rkcif; ++ u64 mask; ++ int ret; ++ ++ stream->frame_idx = 0; ++ stream->frame_phase = 0; ++ ++ ret = video_device_pipeline_start(&stream->vdev, &stream->pipeline); ++ if (ret) { ++ dev_err(rkcif->dev, "failed to start pipeline %d\n", ret); ++ goto err_out; ++ } ++ ++ ret = pm_runtime_resume_and_get(rkcif->dev); ++ if (ret < 0) { ++ dev_err(rkcif->dev, "failed to get runtime pm, %d\n", ret); ++ goto err_pipeline_stop; ++ } ++ ++ ret = rkcif_stream_init_buffers(stream); ++ if (ret) ++ goto err_runtime_put; ++ ++ if (stream->start_streaming) { ++ ret = stream->start_streaming(stream); ++ if (ret < 0) ++ goto err_runtime_put; ++ } ++ ++ mask = BIT_ULL(stream->id); ++ ret = v4l2_subdev_enable_streams(&stream->interface->sd, ++ RKCIF_IF_PAD_SRC, mask); ++ if (ret < 0) ++ goto err_stop_stream; ++ ++ return 0; ++ ++err_stop_stream: ++ if (stream->stop_streaming) ++ stream->stop_streaming(stream); ++err_runtime_put: ++ pm_runtime_put(rkcif->dev); ++err_pipeline_stop: ++ video_device_pipeline_stop(&stream->vdev); ++err_out: ++ rkcif_stream_return_all_buffers(stream, VB2_BUF_STATE_QUEUED); ++ return ret; ++} ++ ++static void rkcif_stream_stop_streaming(struct vb2_queue *queue) ++{ ++ struct rkcif_stream *stream = queue->drv_priv; ++ struct rkcif_device *rkcif = stream->rkcif; ++ u64 mask; ++ int ret; ++ ++ mask = BIT_ULL(stream->id); ++ v4l2_subdev_disable_streams(&stream->interface->sd, RKCIF_IF_PAD_SRC, ++ mask); ++ ++ stream->stopping = true; ++ ret = wait_event_timeout(stream->wq_stopped, !stream->stopping, ++ msecs_to_jiffies(1000)); ++ ++ if (!ret && stream->stop_streaming) ++ stream->stop_streaming(stream); ++ ++ pm_runtime_put(rkcif->dev); ++ ++ rkcif_stream_return_all_buffers(stream, VB2_BUF_STATE_ERROR); ++ ++ video_device_pipeline_stop(&stream->vdev); ++} ++ ++static const struct vb2_ops rkcif_stream_vb2_ops = { ++ .queue_setup = rkcif_stream_setup_queue, ++ .buf_prepare = rkcif_stream_prepare_buffer, ++ .buf_queue = rkcif_stream_queue_buffer, ++ .start_streaming = rkcif_stream_start_streaming, ++ .stop_streaming = rkcif_stream_stop_streaming, ++}; ++ ++static int rkcif_stream_fill_format(struct rkcif_stream *stream, ++ struct v4l2_pix_format_mplane *pix) ++{ ++ const struct rkcif_output_fmt *fmt; ++ u32 height, width; ++ int ret; ++ ++ fmt = rkcif_stream_find_output_fmt(stream, true, pix->pixelformat); ++ height = clamp_t(u32, pix->height, CIF_MIN_HEIGHT, CIF_MAX_HEIGHT); ++ width = clamp_t(u32, pix->width, CIF_MIN_WIDTH, CIF_MAX_WIDTH); ++ ret = v4l2_fill_pixfmt_mp(pix, fmt->fourcc, width, height); ++ if (ret) ++ return ret; ++ ++ pix->field = V4L2_FIELD_NONE; ++ ++ return 0; ++} ++ ++static int rkcif_stream_try_format(struct file *file, void *fh, ++ struct v4l2_format *f) ++{ ++ struct rkcif_stream *stream = video_drvdata(file); ++ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; ++ ++ return rkcif_stream_fill_format(stream, pix); ++} ++ ++static int rkcif_stream_set_format(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ struct rkcif_stream *stream = video_drvdata(file); ++ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; ++ int ret; ++ ++ if (vb2_is_busy(&stream->buf_queue)) ++ return -EBUSY; ++ ++ ret = rkcif_stream_try_format(file, priv, f); ++ if (ret) ++ return ret; ++ ++ stream->pix = *pix; ++ ++ return 0; ++} ++ ++static int rkcif_stream_get_format(struct file *file, void *fh, ++ struct v4l2_format *f) ++{ ++ struct rkcif_stream *stream = video_drvdata(file); ++ ++ f->fmt.pix_mp = stream->pix; ++ ++ return 0; ++} ++ ++static int rkcif_stream_enum_formats(struct file *file, void *priv, ++ struct v4l2_fmtdesc *f) ++{ ++ struct rkcif_stream *stream = video_drvdata(file); ++ ++ if (f->index >= stream->out_fmts_num) ++ return -EINVAL; ++ ++ f->pixelformat = stream->out_fmts[f->index].fourcc; ++ ++ return 0; ++} ++ ++static int rkcif_stream_enum_framesizes(struct file *file, void *fh, ++ struct v4l2_frmsizeenum *fsize) ++{ ++ struct rkcif_stream *stream = video_drvdata(file); ++ ++ if (fsize->index > 0) ++ return -EINVAL; ++ ++ if (!rkcif_stream_find_output_fmt(stream, false, fsize->pixel_format)) ++ return -EINVAL; ++ ++ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; ++ fsize->stepwise.min_width = CIF_MIN_WIDTH; ++ fsize->stepwise.max_width = CIF_MAX_WIDTH; ++ fsize->stepwise.step_width = 8; ++ fsize->stepwise.min_height = CIF_MIN_HEIGHT; ++ fsize->stepwise.max_height = CIF_MAX_HEIGHT; ++ fsize->stepwise.step_height = 8; ++ ++ return 0; ++} ++ ++static int rkcif_stream_querycap(struct file *file, void *priv, ++ struct v4l2_capability *cap) ++{ ++ struct rkcif_stream *stream = video_drvdata(file); ++ struct device *dev = stream->rkcif->dev; ++ ++ strscpy(cap->driver, dev->driver->name, sizeof(cap->driver)); ++ strscpy(cap->card, dev->driver->name, sizeof(cap->card)); ++ ++ return 0; ++} ++ ++static const struct v4l2_ioctl_ops rkcif_stream_ioctl_ops = { ++ .vidioc_reqbufs = vb2_ioctl_reqbufs, ++ .vidioc_querybuf = vb2_ioctl_querybuf, ++ .vidioc_create_bufs = vb2_ioctl_create_bufs, ++ .vidioc_qbuf = vb2_ioctl_qbuf, ++ .vidioc_expbuf = vb2_ioctl_expbuf, ++ .vidioc_dqbuf = vb2_ioctl_dqbuf, ++ .vidioc_prepare_buf = vb2_ioctl_prepare_buf, ++ .vidioc_streamon = vb2_ioctl_streamon, ++ .vidioc_streamoff = vb2_ioctl_streamoff, ++ .vidioc_try_fmt_vid_cap_mplane = rkcif_stream_try_format, ++ .vidioc_s_fmt_vid_cap_mplane = rkcif_stream_set_format, ++ .vidioc_g_fmt_vid_cap_mplane = rkcif_stream_get_format, ++ .vidioc_enum_fmt_vid_cap = rkcif_stream_enum_formats, ++ .vidioc_enum_framesizes = rkcif_stream_enum_framesizes, ++ .vidioc_querycap = rkcif_stream_querycap, ++}; ++ ++static int rkcif_stream_link_validate(struct media_link *link) ++{ ++ struct video_device *vdev = ++ media_entity_to_video_device(link->sink->entity); ++ struct v4l2_mbus_framefmt *source_fmt; ++ struct v4l2_subdev *sd; ++ struct v4l2_subdev_state *state; ++ struct rkcif_stream *stream = to_rkcif_stream(vdev); ++ int ret = -EINVAL; ++ ++ if (!media_entity_remote_source_pad_unique(link->sink->entity)) ++ return -ENOTCONN; ++ ++ sd = media_entity_to_v4l2_subdev(link->source->entity); ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ ++ source_fmt = v4l2_subdev_state_get_format(state, link->source->index, ++ stream->id); ++ if (!source_fmt) ++ goto out; ++ ++ if (source_fmt->height != stream->pix.height || ++ source_fmt->width != stream->pix.width) { ++ dev_dbg(stream->rkcif->dev, ++ "link '%s':%u -> '%s':%u not valid: %ux%u != %ux%u\n", ++ link->source->entity->name, link->source->index, ++ link->sink->entity->name, link->sink->index, ++ source_fmt->width, source_fmt->height, ++ stream->pix.width, stream->pix.height); ++ goto out; ++ } ++ ++ ret = 0; ++ ++out: ++ v4l2_subdev_unlock_state(state); ++ return ret; ++} ++ ++static const struct media_entity_operations rkcif_stream_media_ops = { ++ .link_validate = rkcif_stream_link_validate, ++}; ++ ++static const struct v4l2_file_operations rkcif_stream_file_ops = { ++ .open = v4l2_fh_open, ++ .release = vb2_fop_release, ++ .unlocked_ioctl = video_ioctl2, ++ .poll = vb2_fop_poll, ++ .mmap = vb2_fop_mmap, ++}; ++ ++static int rkcif_stream_init_vb2_queue(struct vb2_queue *q, ++ struct rkcif_stream *stream) ++{ ++ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ++ q->io_modes = VB2_MMAP | VB2_DMABUF; ++ q->drv_priv = stream; ++ q->ops = &rkcif_stream_vb2_ops; ++ q->mem_ops = &vb2_dma_contig_memops; ++ q->buf_struct_size = sizeof(struct rkcif_buffer); ++ q->min_queued_buffers = CIF_REQ_BUFS_MIN; ++ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; ++ q->lock = &stream->vlock; ++ q->dev = stream->rkcif->dev; ++ ++ return vb2_queue_init(q); ++} ++ ++int rkcif_stream_register(struct rkcif_device *rkcif, ++ struct rkcif_stream *stream) ++{ ++ struct rkcif_interface *interface = stream->interface; ++ struct v4l2_device *v4l2_dev = &rkcif->v4l2_dev; ++ struct video_device *vdev = &stream->vdev; ++ u32 link_flags = 0; ++ int ret; ++ ++ stream->rkcif = rkcif; ++ ++ INIT_LIST_HEAD(&stream->driver_queue); ++ spin_lock_init(&stream->driver_queue_lock); ++ ++ init_waitqueue_head(&stream->wq_stopped); ++ ++ mutex_init(&stream->vlock); ++ ++ vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING | ++ V4L2_CAP_IO_MC; ++ vdev->entity.ops = &rkcif_stream_media_ops; ++ vdev->fops = &rkcif_stream_file_ops; ++ vdev->ioctl_ops = &rkcif_stream_ioctl_ops; ++ vdev->lock = &stream->vlock; ++ vdev->minor = -1; ++ vdev->release = video_device_release_empty; ++ vdev->v4l2_dev = v4l2_dev; ++ vdev->vfl_dir = VFL_DIR_RX; ++ video_set_drvdata(vdev, stream); ++ ++ stream->pad.flags = MEDIA_PAD_FL_SINK; ++ ++ stream->pix.height = CIF_MIN_HEIGHT; ++ stream->pix.width = CIF_MIN_WIDTH; ++ rkcif_stream_fill_format(stream, &stream->pix); ++ ++ rkcif_stream_init_vb2_queue(&stream->buf_queue, stream); ++ ++ vdev->queue = &stream->buf_queue; ++ if (interface->type == RKCIF_IF_DVP) ++ snprintf(vdev->name, sizeof(vdev->name), "rkcif-dvp0-id%d", ++ stream->id); ++ else if (interface->type == RKCIF_IF_MIPI) ++ snprintf(vdev->name, sizeof(vdev->name), "rkcif-mipi%d-id%d", ++ interface->index - RKCIF_MIPI_BASE, stream->id); ++ ++ ret = media_entity_pads_init(&vdev->entity, 1, &stream->pad); ++ if (ret < 0) { ++ dev_err(rkcif->dev, ++ "failed to initialize stream media pad: %d\n", ret); ++ return ret; ++ } ++ ++ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); ++ if (ret < 0) { ++ dev_err(rkcif->dev, "failed to register video device: %d\n", ++ ret); ++ goto err_media_entity_cleanup; ++ } ++ ++ /* enable only stream ID0 by default */ ++ if (stream->id == RKCIF_ID0) ++ link_flags |= MEDIA_LNK_FL_ENABLED; ++ ++ ret = media_create_pad_link(&interface->sd.entity, RKCIF_IF_PAD_SRC, ++ &stream->vdev.entity, 0, link_flags); ++ if (ret) { ++ dev_err(rkcif->dev, "failed to link stream media pad: %d\n", ++ ret); ++ goto err_video_unregister; ++ } ++ ++ v4l2_info(v4l2_dev, "registered %s as /dev/video%d\n", vdev->name, ++ vdev->num); ++ ++ return 0; ++ ++err_video_unregister: ++ video_unregister_device(&stream->vdev); ++err_media_entity_cleanup: ++ media_entity_cleanup(&stream->vdev.entity); ++ return ret; ++} ++ ++void rkcif_stream_unregister(struct rkcif_stream *stream) ++{ ++ video_unregister_device(&stream->vdev); ++ media_entity_cleanup(&stream->vdev.entity); ++} ++ ++const struct rkcif_output_fmt * ++rkcif_stream_find_output_fmt(struct rkcif_stream *stream, bool ret_def, ++ u32 pixelfmt) ++{ ++ const struct rkcif_output_fmt *fmt; ++ ++ WARN_ON(stream->out_fmts_num == 0); ++ ++ for (unsigned int i = 0; i < stream->out_fmts_num; i++) { ++ fmt = &stream->out_fmts[i]; ++ if (fmt->fourcc == pixelfmt) ++ return fmt; ++ } ++ ++ if (ret_def) ++ return &stream->out_fmts[0]; ++ else ++ return NULL; ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-stream.h +@@ -0,0 +1,32 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Abstraction for the DMA part and the ping-pong scheme (a double-buffering ++ * mechanism) of the different CIF variants. ++ * Each stream is represented as V4L2 device whose corresponding media entity ++ * has one sink pad. ++ * The sink pad is connected to an instance of the INTERFACE/CROP abstraction ++ * in rkcif-interface.c. ++ * ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2025 Collabora, Ltd. ++ */ ++ ++#ifndef _RKCIF_STREAM_H ++#define _RKCIF_STREAM_H ++ ++#include "rkcif-common.h" ++ ++void rkcif_stream_pingpong(struct rkcif_stream *stream); ++ ++int rkcif_stream_register(struct rkcif_device *rkcif, ++ struct rkcif_stream *stream); ++ ++void rkcif_stream_unregister(struct rkcif_stream *stream); ++ ++const struct rkcif_output_fmt * ++rkcif_stream_find_output_fmt(struct rkcif_stream *stream, bool ret_def, ++ u32 pixelfmt); ++ ++#endif diff --git a/target/linux/rockchip/patches-6.18/045-64-v6.19-media-rockchip-rkcif-add-support-for-px30-vip-dvp-capture.patch b/target/linux/rockchip/patches-6.18/045-64-v6.19-media-rockchip-rkcif-add-support-for-px30-vip-dvp-capture.patch new file mode 100644 index 00000000000..62b83fba439 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-64-v6.19-media-rockchip-rkcif-add-support-for-px30-vip-dvp-capture.patch @@ -0,0 +1,906 @@ +From 75e28594624f68322c6eb052e5f4e8c0589d89a6 Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 14 Nov 2025 16:20:19 +0100 +Subject: [PATCH] media: rockchip: rkcif: add support for px30 vip dvp capture + +The PX30 Video Input Processor (VIP) unit features a Digital Video +Port (DVP). Add support for the DVP in general and for the PX30 +VIP DVP in particular. + +Tested-by: Gerald Loacker +Reviewed-by: Gerald Loacker +Reviewed-by: Bryan O'Donoghue +Reviewed-by: Mehdi Djait +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../media/platform/rockchip/rkcif/Makefile | 1 + + .../rockchip/rkcif/rkcif-capture-dvp.c | 589 ++++++++++++++++++ + .../rockchip/rkcif/rkcif-capture-dvp.h | 24 + + .../platform/rockchip/rkcif/rkcif-common.h | 28 + + .../media/platform/rockchip/rkcif/rkcif-dev.c | 15 + + .../platform/rockchip/rkcif/rkcif-regs.h | 131 ++++ + 6 files changed, 788 insertions(+) + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.h + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-regs.h + +--- a/drivers/media/platform/rockchip/rkcif/Makefile ++++ b/drivers/media/platform/rockchip/rkcif/Makefile +@@ -1,6 +1,7 @@ + # SPDX-License-Identifier: GPL-2.0 + obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-cif.o + ++rockchip-cif-objs += rkcif-capture-dvp.o + rockchip-cif-objs += rkcif-dev.o + rockchip-cif-objs += rkcif-interface.o + rockchip-cif-objs += rkcif-stream.o +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c +@@ -0,0 +1,589 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Copyright (C) 2018 Rockchip Electronics Co., Ltd. ++ * Copyright (C) 2020 Maxime Chevallier ++ * Copyright (C) 2023 Mehdi Djait ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2025 Collabora, Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "rkcif-capture-dvp.h" ++#include "rkcif-common.h" ++#include "rkcif-interface.h" ++#include "rkcif-regs.h" ++#include "rkcif-stream.h" ++ ++static const struct rkcif_output_fmt dvp_out_fmts[] = { ++ { ++ .fourcc = V4L2_PIX_FMT_NV16, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 | ++ RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV, ++ .cplanes = 2, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_NV16M, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 | ++ RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV, ++ .cplanes = 2, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_NV61, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 | ++ RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU, ++ .cplanes = 2, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_NV61M, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 | ++ RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU, ++ .cplanes = 2, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_NV12, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 | ++ RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV, ++ .cplanes = 2, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_NV12M, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 | ++ RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV, ++ .cplanes = 2, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_NV21, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 | ++ RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU, ++ .cplanes = 2, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_NV21M, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 | ++ RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU, ++ .cplanes = 2, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_RGB24, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_RGB565, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_BGR666, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SRGGB8, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGRBG8, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGBRG8, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SBGGR8, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SRGGB10, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGRBG10, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGBRG10, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SBGGR10, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SRGGB12, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGRBG12, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGBRG12, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SBGGR12, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SBGGR16, ++ .cplanes = 1, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_Y16, ++ .cplanes = 1, ++ }, ++}; ++ ++static const struct rkcif_input_fmt px30_dvp_in_fmts[] = { ++ { ++ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_8, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_8, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_8, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_8, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_10, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_10, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_10, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_10, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_12, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_12, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_12, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_12, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_Y8_1X8, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_8, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_Y10_1X10, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_10, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_Y12_1X12, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_12, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ } ++}; ++ ++const struct rkcif_dvp_match_data rkcif_px30_vip_dvp_match_data = { ++ .in_fmts = px30_dvp_in_fmts, ++ .in_fmts_num = ARRAY_SIZE(px30_dvp_in_fmts), ++ .out_fmts = dvp_out_fmts, ++ .out_fmts_num = ARRAY_SIZE(dvp_out_fmts), ++ .has_scaler = true, ++ .regs = { ++ [RKCIF_DVP_CTRL] = 0x00, ++ [RKCIF_DVP_INTEN] = 0x04, ++ [RKCIF_DVP_INTSTAT] = 0x08, ++ [RKCIF_DVP_FOR] = 0x0c, ++ [RKCIF_DVP_LINE_NUM_ADDR] = 0x10, ++ [RKCIF_DVP_FRM0_ADDR_Y] = 0x14, ++ [RKCIF_DVP_FRM0_ADDR_UV] = 0x18, ++ [RKCIF_DVP_FRM1_ADDR_Y] = 0x1c, ++ [RKCIF_DVP_FRM1_ADDR_UV] = 0x20, ++ [RKCIF_DVP_VIR_LINE_WIDTH] = 0x24, ++ [RKCIF_DVP_SET_SIZE] = 0x28, ++ [RKCIF_DVP_SCL_CTRL] = 0x48, ++ [RKCIF_DVP_FRAME_STATUS] = 0x60, ++ [RKCIF_DVP_LAST_LINE] = 0x68, ++ [RKCIF_DVP_LAST_PIX] = 0x6c, ++ }, ++}; ++ ++static inline unsigned int rkcif_dvp_get_addr(struct rkcif_device *rkcif, ++ unsigned int index) ++{ ++ if (WARN_ON_ONCE(index >= RKCIF_DVP_REGISTER_MAX)) ++ return RKCIF_REGISTER_NOTSUPPORTED; ++ ++ return rkcif->match_data->dvp->regs[index]; ++} ++ ++static inline __maybe_unused void rkcif_dvp_write(struct rkcif_device *rkcif, ++ unsigned int index, u32 val) ++{ ++ unsigned int addr = rkcif_dvp_get_addr(rkcif, index); ++ ++ if (addr == RKCIF_REGISTER_NOTSUPPORTED) ++ return; ++ ++ writel(val, rkcif->base_addr + addr); ++} ++ ++static inline __maybe_unused u32 rkcif_dvp_read(struct rkcif_device *rkcif, ++ unsigned int index) ++{ ++ unsigned int addr = rkcif_dvp_get_addr(rkcif, index); ++ ++ if (addr == RKCIF_REGISTER_NOTSUPPORTED) ++ return 0; ++ ++ return readl(rkcif->base_addr + addr); ++} ++ ++static void rkcif_dvp_queue_buffer(struct rkcif_stream *stream, ++ unsigned int index) ++{ ++ struct rkcif_device *rkcif = stream->rkcif; ++ struct rkcif_buffer *buffer = stream->buffers[index]; ++ u32 frm_addr_y, frm_addr_uv; ++ ++ frm_addr_y = index ? RKCIF_DVP_FRM1_ADDR_Y : RKCIF_DVP_FRM0_ADDR_Y; ++ frm_addr_uv = index ? RKCIF_DVP_FRM1_ADDR_UV : RKCIF_DVP_FRM0_ADDR_UV; ++ ++ rkcif_dvp_write(rkcif, frm_addr_y, buffer->buff_addr[RKCIF_PLANE_Y]); ++ rkcif_dvp_write(rkcif, frm_addr_uv, buffer->buff_addr[RKCIF_PLANE_UV]); ++} ++ ++static int rkcif_dvp_start_streaming(struct rkcif_stream *stream) ++{ ++ struct rkcif_device *rkcif = stream->rkcif; ++ struct rkcif_interface *interface = stream->interface; ++ struct v4l2_mbus_config_parallel *parallel; ++ struct v4l2_mbus_framefmt *source_fmt; ++ struct v4l2_subdev_state *state; ++ const struct rkcif_input_fmt *active_in_fmt; ++ const struct rkcif_output_fmt *active_out_fmt; ++ u32 val = 0; ++ int ret = -EINVAL; ++ ++ state = v4l2_subdev_lock_and_get_active_state(&interface->sd); ++ source_fmt = v4l2_subdev_state_get_format(state, RKCIF_IF_PAD_SRC, ++ stream->id); ++ if (!source_fmt) ++ goto out; ++ ++ active_in_fmt = rkcif_interface_find_input_fmt(interface, false, ++ source_fmt->code); ++ active_out_fmt = rkcif_stream_find_output_fmt(stream, false, ++ stream->pix.pixelformat); ++ if (!active_in_fmt || !active_out_fmt) ++ goto out; ++ ++ parallel = &interface->vep.bus.parallel; ++ if (parallel->bus_width == 16 && ++ (parallel->flags & V4L2_MBUS_PCLK_SAMPLE_DUALEDGE)) ++ val |= RKCIF_FORMAT_BT1120_CLOCK_DOUBLE_EDGES; ++ val |= active_in_fmt->dvp_fmt_val; ++ val |= active_out_fmt->dvp_fmt_val; ++ rkcif_dvp_write(rkcif, RKCIF_DVP_FOR, val); ++ ++ val = stream->pix.width; ++ if (active_in_fmt->fmt_type == RKCIF_FMT_TYPE_RAW) ++ val = stream->pix.width * 2; ++ rkcif_dvp_write(rkcif, RKCIF_DVP_VIR_LINE_WIDTH, val); ++ ++ val = RKCIF_XY_COORD(stream->pix.width, stream->pix.height); ++ rkcif_dvp_write(rkcif, RKCIF_DVP_SET_SIZE, val); ++ ++ rkcif_dvp_write(rkcif, RKCIF_DVP_FRAME_STATUS, RKCIF_FRAME_STAT_CLS); ++ rkcif_dvp_write(rkcif, RKCIF_DVP_INTSTAT, RKCIF_INTSTAT_CLS); ++ if (rkcif->match_data->dvp->has_scaler) { ++ val = active_in_fmt->fmt_type == RKCIF_FMT_TYPE_YUV ? ++ RKCIF_SCL_CTRL_ENABLE_YUV_16BIT_BYPASS : ++ RKCIF_SCL_CTRL_ENABLE_RAW_16BIT_BYPASS; ++ rkcif_dvp_write(rkcif, RKCIF_DVP_SCL_CTRL, val); ++ } ++ ++ rkcif_dvp_write(rkcif, RKCIF_DVP_INTEN, ++ RKCIF_INTEN_FRAME_END_EN | ++ RKCIF_INTEN_PST_INF_FRAME_END_EN); ++ ++ rkcif_dvp_write(rkcif, RKCIF_DVP_CTRL, ++ RKCIF_CTRL_AXI_BURST_16 | RKCIF_CTRL_MODE_PINGPONG | ++ RKCIF_CTRL_ENABLE_CAPTURE); ++ ++ ret = 0; ++ ++out: ++ v4l2_subdev_unlock_state(state); ++ return ret; ++} ++ ++static void rkcif_dvp_stop_streaming(struct rkcif_stream *stream) ++{ ++ struct rkcif_device *rkcif = stream->rkcif; ++ u32 val; ++ ++ val = rkcif_dvp_read(rkcif, RKCIF_DVP_CTRL); ++ rkcif_dvp_write(rkcif, RKCIF_DVP_CTRL, ++ val & (~RKCIF_CTRL_ENABLE_CAPTURE)); ++ rkcif_dvp_write(rkcif, RKCIF_DVP_INTEN, 0x0); ++ rkcif_dvp_write(rkcif, RKCIF_DVP_INTSTAT, 0x3ff); ++ rkcif_dvp_write(rkcif, RKCIF_DVP_FRAME_STATUS, 0x0); ++ ++ stream->stopping = false; ++} ++ ++static void rkcif_dvp_reset_stream(struct rkcif_device *rkcif) ++{ ++ u32 ctl = rkcif_dvp_read(rkcif, RKCIF_DVP_CTRL); ++ ++ rkcif_dvp_write(rkcif, RKCIF_DVP_CTRL, ++ ctl & (~RKCIF_CTRL_ENABLE_CAPTURE)); ++ rkcif_dvp_write(rkcif, RKCIF_DVP_CTRL, ctl | RKCIF_CTRL_ENABLE_CAPTURE); ++} ++ ++static void rkcif_dvp_set_crop(struct rkcif_stream *stream, u16 left, u16 top) ++{ ++ struct rkcif_device *rkcif = stream->rkcif; ++ u32 val; ++ ++ val = RKCIF_XY_COORD(left, top); ++ rkcif_dvp_write(rkcif, RKCIF_DVP_CROP, val); ++} ++ ++irqreturn_t rkcif_dvp_isr(int irq, void *ctx) ++{ ++ struct device *dev = ctx; ++ struct rkcif_device *rkcif = dev_get_drvdata(dev); ++ struct rkcif_stream *stream; ++ u32 intstat, lastline, lastpix, cif_frmst; ++ irqreturn_t ret = IRQ_NONE; ++ ++ if (!rkcif->match_data->dvp) ++ return ret; ++ ++ intstat = rkcif_dvp_read(rkcif, RKCIF_DVP_INTSTAT); ++ cif_frmst = rkcif_dvp_read(rkcif, RKCIF_DVP_FRAME_STATUS); ++ lastline = RKCIF_FETCH_Y(rkcif_dvp_read(rkcif, RKCIF_DVP_LAST_LINE)); ++ lastpix = RKCIF_FETCH_Y(rkcif_dvp_read(rkcif, RKCIF_DVP_LAST_PIX)); ++ ++ if (intstat & RKCIF_INTSTAT_FRAME_END) { ++ rkcif_dvp_write(rkcif, RKCIF_DVP_INTSTAT, ++ RKCIF_INTSTAT_FRAME_END_CLR | ++ RKCIF_INTSTAT_LINE_END_CLR); ++ ++ stream = &rkcif->interfaces[RKCIF_DVP].streams[RKCIF_ID0]; ++ ++ if (stream->stopping) { ++ rkcif_dvp_stop_streaming(stream); ++ wake_up(&stream->wq_stopped); ++ ret = IRQ_HANDLED; ++ goto out; ++ } ++ ++ if (lastline != stream->pix.height) { ++ v4l2_err(&rkcif->v4l2_dev, ++ "bad frame, irq:%#x frmst:%#x size:%dx%d\n", ++ intstat, cif_frmst, lastpix, lastline); ++ ++ rkcif_dvp_reset_stream(rkcif); ++ } ++ ++ rkcif_stream_pingpong(stream); ++ ++ ret = IRQ_HANDLED; ++ } ++out: ++ return ret; ++} ++ ++int rkcif_dvp_register(struct rkcif_device *rkcif) ++{ ++ struct rkcif_interface *interface; ++ unsigned int streams_num; ++ int ret; ++ ++ if (!rkcif->match_data->dvp) ++ return 0; ++ ++ interface = &rkcif->interfaces[RKCIF_DVP]; ++ interface->index = RKCIF_DVP; ++ interface->type = RKCIF_IF_DVP; ++ interface->in_fmts = rkcif->match_data->dvp->in_fmts; ++ interface->in_fmts_num = rkcif->match_data->dvp->in_fmts_num; ++ interface->set_crop = rkcif_dvp_set_crop; ++ ret = rkcif_interface_register(rkcif, interface); ++ if (ret) ++ return ret; ++ ++ if (rkcif->match_data->dvp->setup) ++ rkcif->match_data->dvp->setup(rkcif); ++ ++ streams_num = rkcif->match_data->dvp->has_ids ? 4 : 1; ++ for (unsigned int i = 0; i < streams_num; i++) { ++ struct rkcif_stream *stream = &interface->streams[i]; ++ ++ stream->id = i; ++ stream->interface = interface; ++ stream->out_fmts = rkcif->match_data->dvp->out_fmts; ++ stream->out_fmts_num = rkcif->match_data->dvp->out_fmts_num; ++ stream->queue_buffer = rkcif_dvp_queue_buffer; ++ stream->start_streaming = rkcif_dvp_start_streaming; ++ stream->stop_streaming = rkcif_dvp_stop_streaming; ++ ++ ret = rkcif_stream_register(rkcif, stream); ++ if (ret) ++ goto err_streams_unregister; ++ ++ interface->streams_num++; ++ } ++ ++ return 0; ++ ++err_streams_unregister: ++ for (unsigned int i = 0; i < interface->streams_num; i++) ++ rkcif_stream_unregister(&interface->streams[i]); ++ ++ rkcif_interface_unregister(interface); ++ ++ return ret; ++} ++ ++void rkcif_dvp_unregister(struct rkcif_device *rkcif) ++{ ++ struct rkcif_interface *interface; ++ ++ if (!rkcif->match_data->dvp) ++ return; ++ ++ interface = &rkcif->interfaces[RKCIF_DVP]; ++ ++ for (unsigned int i = 0; i < interface->streams_num; i++) ++ rkcif_stream_unregister(&interface->streams[i]); ++ ++ rkcif_interface_unregister(interface); ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Copyright (C) 2018 Rockchip Electronics Co., Ltd. ++ * Copyright (C) 2023 Mehdi Djait ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2025 Collabora, Ltd. ++ */ ++ ++#ifndef _RKCIF_CAPTURE_DVP_H ++#define _RKCIF_CAPTURE_DVP_H ++ ++#include "rkcif-common.h" ++ ++extern const struct rkcif_dvp_match_data rkcif_px30_vip_dvp_match_data; ++ ++int rkcif_dvp_register(struct rkcif_device *rkcif); ++ ++void rkcif_dvp_unregister(struct rkcif_device *rkcif); ++ ++irqreturn_t rkcif_dvp_isr(int irq, void *ctx); ++ ++#endif +--- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h +@@ -24,6 +24,8 @@ + #include + #include + ++#include "rkcif-regs.h" ++ + #define RKCIF_DRIVER_NAME "rockchip-cif" + #define RKCIF_CLK_MAX 4 + +@@ -84,17 +86,31 @@ struct rkcif_dummy_buffer { + u32 size; + }; + ++enum rkcif_plane_index { ++ RKCIF_PLANE_Y, ++ RKCIF_PLANE_UV, ++ RKCIF_PLANE_MAX ++}; ++ + struct rkcif_input_fmt { + u32 mbus_code; + + enum rkcif_format_type fmt_type; + enum v4l2_field field; ++ ++ union { ++ u32 dvp_fmt_val; ++ }; + }; + + struct rkcif_output_fmt { + u32 fourcc; + u32 mbus_code; + u8 cplanes; ++ ++ union { ++ u32 dvp_fmt_val; ++ }; + }; + + struct rkcif_interface; +@@ -168,9 +184,21 @@ struct rkcif_interface { + void (*set_crop)(struct rkcif_stream *stream, u16 left, u16 top); + }; + ++struct rkcif_dvp_match_data { ++ const struct rkcif_input_fmt *in_fmts; ++ unsigned int in_fmts_num; ++ const struct rkcif_output_fmt *out_fmts; ++ unsigned int out_fmts_num; ++ void (*setup)(struct rkcif_device *rkcif); ++ bool has_scaler; ++ bool has_ids; ++ unsigned int regs[RKCIF_DVP_REGISTER_MAX]; ++}; ++ + struct rkcif_match_data { + const char *const *clks; + unsigned int clks_num; ++ const struct rkcif_dvp_match_data *dvp; + }; + + struct rkcif_device { +--- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c +@@ -23,6 +23,7 @@ + #include + #include + ++#include "rkcif-capture-dvp.h" + #include "rkcif-common.h" + + static const char *const px30_vip_clks[] = { +@@ -34,6 +35,7 @@ static const char *const px30_vip_clks[] + static const struct rkcif_match_data px30_vip_match_data = { + .clks = px30_vip_clks, + .clks_num = ARRAY_SIZE(px30_vip_clks), ++ .dvp = &rkcif_px30_vip_dvp_match_data, + }; + + static const char *const rk3568_vicap_clks[] = { +@@ -63,11 +65,21 @@ MODULE_DEVICE_TABLE(of, rkcif_plat_of_ma + + static int rkcif_register(struct rkcif_device *rkcif) + { ++ int ret; ++ ++ ret = rkcif_dvp_register(rkcif); ++ if (ret && ret != -ENODEV) ++ goto err; ++ + return 0; ++ ++err: ++ return ret; + } + + static void rkcif_unregister(struct rkcif_device *rkcif) + { ++ rkcif_dvp_unregister(rkcif); + } + + static int rkcif_notifier_bound(struct v4l2_async_notifier *notifier, +@@ -112,6 +124,9 @@ static irqreturn_t rkcif_isr(int irq, vo + { + irqreturn_t ret = IRQ_NONE; + ++ if (rkcif_dvp_isr(irq, ctx) == IRQ_HANDLED) ++ ret = IRQ_HANDLED; ++ + return ret; + } + +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h +@@ -0,0 +1,131 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Copyright (C) 2018 Rockchip Electronics Co., Ltd. ++ * Copyright (C) 2023 Mehdi Djait ++ * Copyright (C) 2025 Michael Riesch ++ */ ++ ++#ifndef _RKCIF_REGS_H ++#define _RKCIF_REGS_H ++ ++#define RKCIF_REGISTER_NOTSUPPORTED 0x420000 ++#define RKCIF_FETCH_Y(VAL) ((VAL) & 0x1fff) ++#define RKCIF_XY_COORD(x, y) (((y) << 16) | (x)) ++ ++/* DVP register contents */ ++#define RKCIF_CTRL_ENABLE_CAPTURE BIT(0) ++#define RKCIF_CTRL_MODE_PINGPONG BIT(1) ++#define RKCIF_CTRL_MODE_LINELOOP BIT(2) ++#define RKCIF_CTRL_AXI_BURST_16 (0xf << 12) ++ ++#define RKCIF_INTEN_FRAME_END_EN BIT(0) ++#define RKCIF_INTEN_LINE_ERR_EN BIT(2) ++#define RKCIF_INTEN_BUS_ERR_EN BIT(6) ++#define RKCIF_INTEN_SCL_ERR_EN BIT(7) ++#define RKCIF_INTEN_PST_INF_FRAME_END_EN BIT(9) ++ ++#define RKCIF_INTSTAT_CLS 0x3ff ++#define RKCIF_INTSTAT_FRAME_END BIT(0) ++#define RKCIF_INTSTAT_LINE_END BIT(1) ++#define RKCIF_INTSTAT_LINE_ERR BIT(2) ++#define RKCIF_INTSTAT_PIX_ERR BIT(3) ++#define RKCIF_INTSTAT_DFIFO_OF BIT(5) ++#define RKCIF_INTSTAT_BUS_ERR BIT(6) ++#define RKCIF_INTSTAT_PRE_INF_FRAME_END BIT(8) ++#define RKCIF_INTSTAT_PST_INF_FRAME_END BIT(9) ++#define RKCIF_INTSTAT_FRAME_END_CLR BIT(0) ++#define RKCIF_INTSTAT_LINE_END_CLR BIT(1) ++#define RKCIF_INTSTAT_LINE_ERR_CLR BIT(2) ++#define RKCIF_INTSTAT_PST_INF_FRAME_END_CLR BIT(9) ++#define RKCIF_INTSTAT_ERR 0xfc ++ ++#define RKCIF_FRAME_STAT_CLS 0x00 ++#define RKCIF_FRAME_FRM0_STAT_CLS 0x20 ++ ++#define RKCIF_FORMAT_VSY_HIGH_ACTIVE BIT(0) ++#define RKCIF_FORMAT_HSY_LOW_ACTIVE BIT(1) ++ ++#define RKCIF_FORMAT_INPUT_MODE_YUV (0x00 << 2) ++#define RKCIF_FORMAT_INPUT_MODE_PAL (0x02 << 2) ++#define RKCIF_FORMAT_INPUT_MODE_NTSC (0x03 << 2) ++#define RKCIF_FORMAT_INPUT_MODE_BT1120 (0x07 << 2) ++#define RKCIF_FORMAT_INPUT_MODE_RAW (0x04 << 2) ++#define RKCIF_FORMAT_INPUT_MODE_JPEG (0x05 << 2) ++#define RKCIF_FORMAT_INPUT_MODE_MIPI (0x06 << 2) ++ ++#define RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY (0x00 << 5) ++#define RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU (0x01 << 5) ++#define RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY (0x02 << 5) ++#define RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV (0x03 << 5) ++#define RKCIF_FORMAT_YUV_INPUT_422 (0x00 << 7) ++#define RKCIF_FORMAT_YUV_INPUT_420 BIT(7) ++ ++#define RKCIF_FORMAT_INPUT_420_ORDER_ODD BIT(8) ++ ++#define RKCIF_FORMAT_CCIR_INPUT_ORDER_EVEN BIT(9) ++ ++#define RKCIF_FORMAT_RAW_DATA_WIDTH_8 (0x00 << 11) ++#define RKCIF_FORMAT_RAW_DATA_WIDTH_10 (0x01 << 11) ++#define RKCIF_FORMAT_RAW_DATA_WIDTH_12 (0x02 << 11) ++ ++#define RKCIF_FORMAT_YUV_OUTPUT_422 (0x00 << 16) ++#define RKCIF_FORMAT_YUV_OUTPUT_420 BIT(16) ++ ++#define RKCIF_FORMAT_OUTPUT_420_ORDER_EVEN (0x00 << 17) ++#define RKCIF_FORMAT_OUTPUT_420_ORDER_ODD BIT(17) ++ ++#define RKCIF_FORMAT_RAWD_DATA_LITTLE_ENDIAN (0x00 << 18) ++#define RKCIF_FORMAT_RAWD_DATA_BIG_ENDIAN BIT(18) ++ ++#define RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV (0x00 << 19) ++#define RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU BIT(19) ++ ++#define RKCIF_FORMAT_BT1120_CLOCK_SINGLE_EDGES (0x00 << 24) ++#define RKCIF_FORMAT_BT1120_CLOCK_DOUBLE_EDGES BIT(24) ++#define RKCIF_FORMAT_BT1120_TRANSMIT_INTERFACE (0x00 << 25) ++#define RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS BIT(25) ++#define RKCIF_FORMAT_BT1120_YC_SWAP BIT(26) ++ ++#define RKCIF_SCL_CTRL_ENABLE_SCL_DOWN BIT(0) ++#define RKCIF_SCL_CTRL_ENABLE_SCL_UP BIT(1) ++#define RKCIF_SCL_CTRL_ENABLE_YUV_16BIT_BYPASS BIT(4) ++#define RKCIF_SCL_CTRL_ENABLE_RAW_16BIT_BYPASS BIT(5) ++#define RKCIF_SCL_CTRL_ENABLE_32BIT_BYPASS BIT(6) ++#define RKCIF_SCL_CTRL_DISABLE_32BIT_BYPASS (0x00 << 6) ++ ++#define RKCIF_INTSTAT_F0_READY BIT(0) ++#define RKCIF_INTSTAT_F1_READY BIT(1) ++ ++/* GRF register offsets and contents */ ++#define RK3568_GRF_VI_CON0 0x340 ++#define RK3568_GRF_VI_CON1 0x344 ++#define RK3568_GRF_VI_STATUS0 0x348 ++ ++#define RK3568_GRF_VI_CON1_CIF_DATAPATH BIT(9) ++#define RK3568_GRF_VI_CON1_CIF_CLK_DELAYNUM GENMASK(6, 0) ++ ++#define RK3568_GRF_WRITE_ENABLE(x) ((x) << 16) ++ ++enum rkcif_dvp_register_index { ++ RKCIF_DVP_CTRL, ++ RKCIF_DVP_INTEN, ++ RKCIF_DVP_INTSTAT, ++ RKCIF_DVP_FOR, ++ RKCIF_DVP_LINE_NUM_ADDR, ++ RKCIF_DVP_FRM0_ADDR_Y, ++ RKCIF_DVP_FRM0_ADDR_UV, ++ RKCIF_DVP_FRM1_ADDR_Y, ++ RKCIF_DVP_FRM1_ADDR_UV, ++ RKCIF_DVP_VIR_LINE_WIDTH, ++ RKCIF_DVP_SET_SIZE, ++ RKCIF_DVP_SCL_CTRL, ++ RKCIF_DVP_CROP, ++ RKCIF_DVP_FRAME_STATUS, ++ RKCIF_DVP_LAST_LINE, ++ RKCIF_DVP_LAST_PIX, ++ RKCIF_DVP_REGISTER_MAX ++}; ++ ++#endif diff --git a/target/linux/rockchip/patches-6.18/045-65-v6.19-media-rockchip-rkcif-add-support-for-rk3568-vicap-dvp-capt.patch b/target/linux/rockchip/patches-6.18/045-65-v6.19-media-rockchip-rkcif-add-support-for-rk3568-vicap-dvp-capt.patch new file mode 100644 index 00000000000..0a92c9422ef --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-65-v6.19-media-rockchip-rkcif-add-support-for-rk3568-vicap-dvp-capt.patch @@ -0,0 +1,327 @@ +From c348d671697b556d2198c7d2e49e95390dc33e66 Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 14 Nov 2025 16:20:20 +0100 +Subject: [PATCH] media: rockchip: rkcif: add support for rk3568 vicap dvp + capture + +The RK3568 Video Capture (VICAP) unit features a Digital Video Port +(DVP). Add support for it. + +Tested-by: Gerald Loacker +Reviewed-by: Gerald Loacker +Reviewed-by: Bryan O'Donoghue +Reviewed-by: Mehdi Djait +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../rockchip/rkcif/rkcif-capture-dvp.c | 276 ++++++++++++++++++ + .../rockchip/rkcif/rkcif-capture-dvp.h | 1 + + .../media/platform/rockchip/rkcif/rkcif-dev.c | 1 + + 3 files changed, 278 insertions(+) + +--- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c +@@ -332,6 +332,282 @@ const struct rkcif_dvp_match_data rkcif_ + }, + }; + ++static const struct rkcif_input_fmt rk3568_dvp_in_fmts[] = { ++ { ++ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY, ++ .fmt_type = RKCIF_FMT_TYPE_YUV, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV | ++ RKCIF_FORMAT_INPUT_MODE_BT1120 | ++ RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV | ++ RKCIF_FORMAT_INPUT_MODE_BT1120, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU | ++ RKCIF_FORMAT_INPUT_MODE_BT1120 | ++ RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU | ++ RKCIF_FORMAT_INPUT_MODE_BT1120, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV | ++ RKCIF_FORMAT_INPUT_MODE_BT1120 | ++ RKCIF_FORMAT_BT1120_YC_SWAP | ++ RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV | ++ RKCIF_FORMAT_BT1120_YC_SWAP | ++ RKCIF_FORMAT_INPUT_MODE_BT1120, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU | ++ RKCIF_FORMAT_INPUT_MODE_BT1120 | ++ RKCIF_FORMAT_BT1120_YC_SWAP | ++ RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, ++ .dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 | ++ RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU | ++ RKCIF_FORMAT_BT1120_YC_SWAP | ++ RKCIF_FORMAT_INPUT_MODE_BT1120, ++ .field = V4L2_FIELD_INTERLACED, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_8, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_8, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_8, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_8, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_10, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_10, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_10, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_10, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_12, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_12, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_12, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_12, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_Y8_1X8, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_8, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_Y10_1X10, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_10, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_Y12_1X12, ++ .dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW | ++ RKCIF_FORMAT_RAW_DATA_WIDTH_12, ++ .fmt_type = RKCIF_FMT_TYPE_RAW, ++ .field = V4L2_FIELD_NONE, ++ }, ++}; ++ ++static void rk3568_dvp_grf_setup(struct rkcif_device *rkcif) ++{ ++ u32 con1 = RK3568_GRF_WRITE_ENABLE(RK3568_GRF_VI_CON1_CIF_DATAPATH | ++ RK3568_GRF_VI_CON1_CIF_CLK_DELAYNUM); ++ ++ if (!rkcif->grf) ++ return; ++ ++ con1 |= rkcif->interfaces[RKCIF_DVP].dvp.dvp_clk_delay & ++ RK3568_GRF_VI_CON1_CIF_CLK_DELAYNUM; ++ ++ if (rkcif->interfaces[RKCIF_DVP].vep.bus.parallel.flags & ++ V4L2_MBUS_PCLK_SAMPLE_DUALEDGE) ++ con1 |= RK3568_GRF_VI_CON1_CIF_DATAPATH; ++ ++ regmap_write(rkcif->grf, RK3568_GRF_VI_CON1, con1); ++} ++ ++const struct rkcif_dvp_match_data rkcif_rk3568_vicap_dvp_match_data = { ++ .in_fmts = rk3568_dvp_in_fmts, ++ .in_fmts_num = ARRAY_SIZE(rk3568_dvp_in_fmts), ++ .out_fmts = dvp_out_fmts, ++ .out_fmts_num = ARRAY_SIZE(dvp_out_fmts), ++ .setup = rk3568_dvp_grf_setup, ++ .has_scaler = false, ++ .regs = { ++ [RKCIF_DVP_CTRL] = 0x00, ++ [RKCIF_DVP_INTEN] = 0x04, ++ [RKCIF_DVP_INTSTAT] = 0x08, ++ [RKCIF_DVP_FOR] = 0x0c, ++ [RKCIF_DVP_LINE_NUM_ADDR] = 0x2c, ++ [RKCIF_DVP_FRM0_ADDR_Y] = 0x14, ++ [RKCIF_DVP_FRM0_ADDR_UV] = 0x18, ++ [RKCIF_DVP_FRM1_ADDR_Y] = 0x1c, ++ [RKCIF_DVP_FRM1_ADDR_UV] = 0x20, ++ [RKCIF_DVP_VIR_LINE_WIDTH] = 0x24, ++ [RKCIF_DVP_SET_SIZE] = 0x28, ++ [RKCIF_DVP_CROP] = 0x34, ++ [RKCIF_DVP_FRAME_STATUS] = 0x3c, ++ [RKCIF_DVP_LAST_LINE] = 0x44, ++ [RKCIF_DVP_LAST_PIX] = 0x48, ++ }, ++}; ++ + static inline unsigned int rkcif_dvp_get_addr(struct rkcif_device *rkcif, + unsigned int index) + { +--- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.h ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.h +@@ -14,6 +14,7 @@ + #include "rkcif-common.h" + + extern const struct rkcif_dvp_match_data rkcif_px30_vip_dvp_match_data; ++extern const struct rkcif_dvp_match_data rkcif_rk3568_vicap_dvp_match_data; + + int rkcif_dvp_register(struct rkcif_device *rkcif); + +--- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c +@@ -48,6 +48,7 @@ static const char *const rk3568_vicap_cl + static const struct rkcif_match_data rk3568_vicap_match_data = { + .clks = rk3568_vicap_clks, + .clks_num = ARRAY_SIZE(rk3568_vicap_clks), ++ .dvp = &rkcif_rk3568_vicap_dvp_match_data, + }; + + static const struct of_device_id rkcif_plat_of_match[] = { diff --git a/target/linux/rockchip/patches-6.18/045-66-v6.19-media-rockchip-rkcif-add-support-for-rk3568-vicap-mipi-cap.patch b/target/linux/rockchip/patches-6.18/045-66-v6.19-media-rockchip-rkcif-add-support-for-rk3568-vicap-mipi-cap.patch new file mode 100644 index 00000000000..4728bf9f37e --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-66-v6.19-media-rockchip-rkcif-add-support-for-rk3568-vicap-mipi-cap.patch @@ -0,0 +1,984 @@ +From 1f2353f5a1af995efbf7bea44341aa0d03460b28 Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 14 Nov 2025 16:20:21 +0100 +Subject: [PATCH] media: rockchip: rkcif: add support for rk3568 vicap mipi + capture + +The RK3568 Video Capture (VICAP) unit features a MIPI CSI-2 capture +interface. Add support for the MIPI capture interface in general +and for the RK3568 VICAP MIPI capture in particular. + +Signed-off-by: Michael Riesch +Reviewed-by: Bryan O'Donoghue +Reviewed-by: Mehdi Djait +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../media/platform/rockchip/rkcif/Makefile | 1 + + .../rockchip/rkcif/rkcif-capture-mipi.c | 777 ++++++++++++++++++ + .../rockchip/rkcif/rkcif-capture-mipi.h | 23 + + .../platform/rockchip/rkcif/rkcif-common.h | 29 + + .../media/platform/rockchip/rkcif/rkcif-dev.c | 12 + + .../platform/rockchip/rkcif/rkcif-regs.h | 22 + + 6 files changed, 864 insertions(+) + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c + create mode 100644 drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h + +--- a/drivers/media/platform/rockchip/rkcif/Makefile ++++ b/drivers/media/platform/rockchip/rkcif/Makefile +@@ -2,6 +2,7 @@ + obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-cif.o + + rockchip-cif-objs += rkcif-capture-dvp.o ++rockchip-cif-objs += rkcif-capture-mipi.o + rockchip-cif-objs += rkcif-dev.o + rockchip-cif-objs += rkcif-interface.o + rockchip-cif-objs += rkcif-stream.o +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +@@ -0,0 +1,777 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Copyright (C) 2018 Rockchip Electronics Co., Ltd. ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2025 Collabora, Ltd. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "rkcif-capture-mipi.h" ++#include "rkcif-common.h" ++#include "rkcif-interface.h" ++#include "rkcif-regs.h" ++#include "rkcif-stream.h" ++ ++#define RK3568_MIPI_CTRL0_HIGH_ALIGN BIT(31) ++#define RK3568_MIPI_CTRL0_UV_SWAP_EN BIT(7) ++#define RK3568_MIPI_CTRL0_COMPACT_EN BIT(6) ++#define RK3568_MIPI_CTRL0_CROP_EN BIT(5) ++#define RK3568_MIPI_CTRL0_WRDDR(type) ((type) << 1) ++ ++#define RKCIF_MIPI_CTRL0_DT_ID(id) ((id) << 10) ++#define RKCIF_MIPI_CTRL0_VC_ID(id) ((id) << 8) ++#define RKCIF_MIPI_CTRL0_CAP_EN BIT(0) ++ ++#define RKCIF_MIPI_INT_FRAME0_END(id) BIT(8 + (id) * 2 + 0) ++#define RKCIF_MIPI_INT_FRAME1_END(id) BIT(8 + (id) * 2 + 1) ++ ++static const struct rkcif_output_fmt mipi_out_fmts[] = { ++ /* YUV formats */ ++ { ++ .fourcc = V4L2_PIX_FMT_YUYV, ++ .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, ++ .depth = 16, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_YUV422_8B, ++ .type = RKCIF_MIPI_TYPE_RAW8, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_UYVY, ++ .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, ++ .depth = 16, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_YUV422_8B, ++ .type = RKCIF_MIPI_TYPE_RAW8, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_YVYU, ++ .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, ++ .depth = 16, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_YUV422_8B, ++ .type = RKCIF_MIPI_TYPE_RAW8, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_VYUY, ++ .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, ++ .depth = 16, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_YUV422_8B, ++ .type = RKCIF_MIPI_TYPE_RAW8, ++ }, ++ }, ++ /* RGB formats */ ++ { ++ .fourcc = V4L2_PIX_FMT_RGB24, ++ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, ++ .depth = 24, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RGB888, ++ .type = RKCIF_MIPI_TYPE_RGB888, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_BGR24, ++ .mbus_code = MEDIA_BUS_FMT_BGR888_1X24, ++ .depth = 24, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RGB888, ++ .type = RKCIF_MIPI_TYPE_RGB888, ++ }, ++ }, ++ /* Bayer formats */ ++ { ++ .fourcc = V4L2_PIX_FMT_SBGGR8, ++ .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, ++ .depth = 8, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW8, ++ .type = RKCIF_MIPI_TYPE_RAW8, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGBRG8, ++ .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, ++ .depth = 8, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW8, ++ .type = RKCIF_MIPI_TYPE_RAW8, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGRBG8, ++ .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, ++ .depth = 8, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW8, ++ .type = RKCIF_MIPI_TYPE_RAW8, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SRGGB8, ++ .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, ++ .depth = 8, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW8, ++ .type = RKCIF_MIPI_TYPE_RAW8, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SBGGR10, ++ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, ++ .depth = 10, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW10, ++ .type = RKCIF_MIPI_TYPE_RAW10, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SBGGR10P, ++ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, ++ .depth = 10, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW10, ++ .compact = true, ++ .type = RKCIF_MIPI_TYPE_RAW10, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGBRG10, ++ .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, ++ .depth = 10, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW10, ++ .type = RKCIF_MIPI_TYPE_RAW10, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGBRG10P, ++ .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, ++ .depth = 10, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW10, ++ .compact = true, ++ .type = RKCIF_MIPI_TYPE_RAW10, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGRBG10, ++ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, ++ .depth = 10, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW10, ++ .type = RKCIF_MIPI_TYPE_RAW10, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGRBG10P, ++ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, ++ .depth = 10, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW10, ++ .compact = true, ++ .type = RKCIF_MIPI_TYPE_RAW10, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SRGGB10, ++ .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .depth = 10, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW10, ++ .type = RKCIF_MIPI_TYPE_RAW10, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SRGGB10P, ++ .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .depth = 10, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW10, ++ .compact = true, ++ .type = RKCIF_MIPI_TYPE_RAW10, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SBGGR12, ++ .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, ++ .depth = 12, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW12, ++ .type = RKCIF_MIPI_TYPE_RAW12, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SBGGR12P, ++ .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, ++ .depth = 12, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW12, ++ .compact = true, ++ .type = RKCIF_MIPI_TYPE_RAW12, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGBRG12, ++ .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, ++ .depth = 12, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW12, ++ .type = RKCIF_MIPI_TYPE_RAW12, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGBRG12P, ++ .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, ++ .depth = 12, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW12, ++ .compact = true, ++ .type = RKCIF_MIPI_TYPE_RAW12, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGRBG12, ++ .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, ++ .depth = 12, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW12, ++ .type = RKCIF_MIPI_TYPE_RAW12, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SGRBG12P, ++ .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, ++ .depth = 12, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW12, ++ .compact = true, ++ .type = RKCIF_MIPI_TYPE_RAW12, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SRGGB12, ++ .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, ++ .depth = 12, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW12, ++ .type = RKCIF_MIPI_TYPE_RAW12, ++ }, ++ }, ++ { ++ .fourcc = V4L2_PIX_FMT_SRGGB12P, ++ .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, ++ .depth = 12, ++ .cplanes = 1, ++ .mipi = { ++ .dt = MIPI_CSI2_DT_RAW12, ++ .compact = true, ++ .type = RKCIF_MIPI_TYPE_RAW12, ++ }, ++ }, ++}; ++ ++static const struct rkcif_input_fmt mipi_in_fmts[] = { ++ /* YUV formats */ ++ { ++ .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, ++ }, ++ /* RGB formats */ ++ { ++ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_BGR888_1X24, ++ }, ++ /* Bayer formats */ ++ { ++ .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, ++ }, ++ { ++ .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, ++ }, ++}; ++ ++static u32 ++rkcif_rk3568_mipi_ctrl0(struct rkcif_stream *stream, ++ const struct rkcif_output_fmt *active_out_fmt) ++{ ++ u32 ctrl0 = 0; ++ ++ ctrl0 |= RKCIF_MIPI_CTRL0_DT_ID(active_out_fmt->mipi.dt); ++ ctrl0 |= RKCIF_MIPI_CTRL0_CAP_EN; ++ ctrl0 |= RK3568_MIPI_CTRL0_CROP_EN; ++ ++ if (active_out_fmt->mipi.compact) ++ ctrl0 |= RK3568_MIPI_CTRL0_COMPACT_EN; ++ ++ switch (active_out_fmt->mipi.type) { ++ case RKCIF_MIPI_TYPE_RAW8: ++ break; ++ case RKCIF_MIPI_TYPE_RAW10: ++ ctrl0 |= RK3568_MIPI_CTRL0_WRDDR(0x1); ++ break; ++ case RKCIF_MIPI_TYPE_RAW12: ++ ctrl0 |= RK3568_MIPI_CTRL0_WRDDR(0x2); ++ break; ++ case RKCIF_MIPI_TYPE_RGB888: ++ ctrl0 |= RK3568_MIPI_CTRL0_WRDDR(0x3); ++ break; ++ case RKCIF_MIPI_TYPE_YUV422SP: ++ ctrl0 |= RK3568_MIPI_CTRL0_WRDDR(0x4); ++ break; ++ case RKCIF_MIPI_TYPE_YUV420SP: ++ ctrl0 |= RK3568_MIPI_CTRL0_WRDDR(0x5); ++ break; ++ case RKCIF_MIPI_TYPE_YUV400: ++ ctrl0 |= RK3568_MIPI_CTRL0_WRDDR(0x6); ++ break; ++ default: ++ break; ++ } ++ ++ return ctrl0; ++} ++ ++const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data = { ++ .mipi_num = 1, ++ .mipi_ctrl0 = rkcif_rk3568_mipi_ctrl0, ++ .regs = { ++ [RKCIF_MIPI_CTRL] = 0x20, ++ [RKCIF_MIPI_INTEN] = 0xa4, ++ [RKCIF_MIPI_INTSTAT] = 0xa8, ++ }, ++ .regs_id = { ++ [RKCIF_ID0] = { ++ [RKCIF_MIPI_CTRL0] = 0x00, ++ [RKCIF_MIPI_CTRL1] = 0x04, ++ [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x24, ++ [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x2c, ++ [RKCIF_MIPI_FRAME0_VLW_Y] = 0x34, ++ [RKCIF_MIPI_FRAME0_VLW_UV] = 0x3c, ++ [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x28, ++ [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x30, ++ [RKCIF_MIPI_FRAME1_VLW_Y] = 0x38, ++ [RKCIF_MIPI_FRAME1_VLW_UV] = 0x40, ++ [RKCIF_MIPI_CROP_START] = 0xbc, ++ }, ++ [RKCIF_ID1] = { ++ [RKCIF_MIPI_CTRL0] = 0x08, ++ [RKCIF_MIPI_CTRL1] = 0x0c, ++ [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x44, ++ [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x4c, ++ [RKCIF_MIPI_FRAME0_VLW_Y] = 0x54, ++ [RKCIF_MIPI_FRAME0_VLW_UV] = 0x5c, ++ [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x48, ++ [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x50, ++ [RKCIF_MIPI_FRAME1_VLW_Y] = 0x58, ++ [RKCIF_MIPI_FRAME1_VLW_UV] = 0x60, ++ [RKCIF_MIPI_CROP_START] = 0xc0, ++ }, ++ [RKCIF_ID2] = { ++ [RKCIF_MIPI_CTRL0] = 0x10, ++ [RKCIF_MIPI_CTRL1] = 0x14, ++ [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x64, ++ [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x6c, ++ [RKCIF_MIPI_FRAME0_VLW_Y] = 0x74, ++ [RKCIF_MIPI_FRAME0_VLW_UV] = 0x7c, ++ [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x68, ++ [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x70, ++ [RKCIF_MIPI_FRAME1_VLW_Y] = 0x78, ++ [RKCIF_MIPI_FRAME1_VLW_UV] = 0x80, ++ [RKCIF_MIPI_CROP_START] = 0xc4, ++ }, ++ [RKCIF_ID3] = { ++ [RKCIF_MIPI_CTRL0] = 0x18, ++ [RKCIF_MIPI_CTRL1] = 0x1c, ++ [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x84, ++ [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x8c, ++ [RKCIF_MIPI_FRAME0_VLW_Y] = 0x94, ++ [RKCIF_MIPI_FRAME0_VLW_UV] = 0x9c, ++ [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x88, ++ [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x90, ++ [RKCIF_MIPI_FRAME1_VLW_Y] = 0x98, ++ [RKCIF_MIPI_FRAME1_VLW_UV] = 0xa0, ++ [RKCIF_MIPI_CROP_START] = 0xc8, ++ }, ++ }, ++ .blocks = { ++ { ++ .offset = 0x80, ++ }, ++ }, ++}; ++ ++static inline unsigned int rkcif_mipi_get_reg(struct rkcif_interface *interface, ++ unsigned int index) ++{ ++ struct rkcif_device *rkcif = interface->rkcif; ++ unsigned int block, offset, reg; ++ ++ block = interface->index - RKCIF_MIPI_BASE; ++ ++ if (WARN_ON_ONCE(block > RKCIF_MIPI_MAX - RKCIF_MIPI_BASE) || ++ WARN_ON_ONCE(index > RKCIF_MIPI_REGISTER_MAX)) ++ return RKCIF_REGISTER_NOTSUPPORTED; ++ ++ offset = rkcif->match_data->mipi->blocks[block].offset; ++ reg = rkcif->match_data->mipi->regs[index]; ++ if (reg == RKCIF_REGISTER_NOTSUPPORTED) ++ return reg; ++ ++ return offset + reg; ++} ++ ++static inline unsigned int rkcif_mipi_id_get_reg(struct rkcif_stream *stream, ++ unsigned int index) ++{ ++ struct rkcif_device *rkcif = stream->rkcif; ++ unsigned int block, id, offset, reg; ++ ++ block = stream->interface->index - RKCIF_MIPI_BASE; ++ id = stream->id; ++ ++ if (WARN_ON_ONCE(block > RKCIF_MIPI_MAX - RKCIF_MIPI_BASE) || ++ WARN_ON_ONCE(id > RKCIF_ID_MAX) || ++ WARN_ON_ONCE(index > RKCIF_MIPI_ID_REGISTER_MAX)) ++ return RKCIF_REGISTER_NOTSUPPORTED; ++ ++ offset = rkcif->match_data->mipi->blocks[block].offset; ++ reg = rkcif->match_data->mipi->regs_id[id][index]; ++ if (reg == RKCIF_REGISTER_NOTSUPPORTED) ++ return reg; ++ ++ return offset + reg; ++} ++ ++static inline __maybe_unused void ++rkcif_mipi_write(struct rkcif_interface *interface, unsigned int index, u32 val) ++{ ++ unsigned int addr = rkcif_mipi_get_reg(interface, index); ++ ++ if (addr == RKCIF_REGISTER_NOTSUPPORTED) ++ return; ++ ++ writel(val, interface->rkcif->base_addr + addr); ++} ++ ++static inline __maybe_unused void ++rkcif_mipi_stream_write(struct rkcif_stream *stream, unsigned int index, ++ u32 val) ++{ ++ unsigned int addr = rkcif_mipi_id_get_reg(stream, index); ++ ++ if (addr == RKCIF_REGISTER_NOTSUPPORTED) ++ return; ++ ++ writel(val, stream->rkcif->base_addr + addr); ++} ++ ++static inline __maybe_unused u32 ++rkcif_mipi_read(struct rkcif_interface *interface, unsigned int index) ++{ ++ unsigned int addr = rkcif_mipi_get_reg(interface, index); ++ ++ if (addr == RKCIF_REGISTER_NOTSUPPORTED) ++ return 0; ++ ++ return readl(interface->rkcif->base_addr + addr); ++} ++ ++static inline __maybe_unused u32 ++rkcif_mipi_stream_read(struct rkcif_stream *stream, unsigned int index) ++{ ++ unsigned int addr = rkcif_mipi_id_get_reg(stream, index); ++ ++ if (addr == RKCIF_REGISTER_NOTSUPPORTED) ++ return 0; ++ ++ return readl(stream->rkcif->base_addr + addr); ++} ++ ++static void rkcif_mipi_queue_buffer(struct rkcif_stream *stream, ++ unsigned int index) ++{ ++ struct rkcif_buffer *buffer = stream->buffers[index]; ++ u32 frm_addr_y, frm_addr_uv; ++ ++ frm_addr_y = index ? RKCIF_MIPI_FRAME1_ADDR_Y : ++ RKCIF_MIPI_FRAME0_ADDR_Y; ++ frm_addr_uv = index ? RKCIF_MIPI_FRAME1_ADDR_UV : ++ RKCIF_MIPI_FRAME0_ADDR_UV; ++ ++ rkcif_mipi_stream_write(stream, frm_addr_y, ++ buffer->buff_addr[RKCIF_PLANE_Y]); ++ rkcif_mipi_stream_write(stream, frm_addr_uv, ++ buffer->buff_addr[RKCIF_PLANE_UV]); ++} ++ ++static int rkcif_mipi_start_streaming(struct rkcif_stream *stream) ++{ ++ struct rkcif_interface *interface = stream->interface; ++ const struct rkcif_output_fmt *active_out_fmt; ++ const struct rkcif_mipi_match_data *match_data; ++ struct v4l2_subdev_state *state; ++ u32 ctrl0 = 0, ctrl1 = 0, int_temp = 0, int_mask = 0, vlw = 0; ++ u16 height, width; ++ int ret = -EINVAL; ++ ++ state = v4l2_subdev_lock_and_get_active_state(&interface->sd); ++ ++ active_out_fmt = rkcif_stream_find_output_fmt(stream, false, ++ stream->pix.pixelformat); ++ if (!active_out_fmt) ++ goto out; ++ ++ height = stream->pix.height; ++ width = stream->pix.width; ++ vlw = stream->pix.plane_fmt[0].bytesperline; ++ ++ match_data = stream->rkcif->match_data->mipi; ++ if (match_data->mipi_ctrl0) ++ ctrl0 = match_data->mipi_ctrl0(stream, active_out_fmt); ++ ++ ctrl1 = RKCIF_XY_COORD(width, height); ++ ++ int_mask |= RKCIF_MIPI_INT_FRAME0_END(stream->id); ++ int_mask |= RKCIF_MIPI_INT_FRAME1_END(stream->id); ++ ++ int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTEN); ++ int_temp |= int_mask; ++ rkcif_mipi_write(interface, RKCIF_MIPI_INTEN, int_temp); ++ ++ int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT); ++ int_temp &= ~int_mask; ++ rkcif_mipi_write(interface, RKCIF_MIPI_INTSTAT, int_temp); ++ ++ rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME0_VLW_Y, vlw); ++ rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME1_VLW_Y, vlw); ++ rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME0_VLW_UV, vlw); ++ rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME1_VLW_UV, vlw); ++ rkcif_mipi_stream_write(stream, RKCIF_MIPI_CROP_START, 0x0); ++ rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL1, ctrl1); ++ rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL0, ctrl0); ++ ++ ret = 0; ++ ++out: ++ v4l2_subdev_unlock_state(state); ++ return ret; ++} ++ ++static void rkcif_mipi_stop_streaming(struct rkcif_stream *stream) ++{ ++ struct rkcif_interface *interface = stream->interface; ++ struct v4l2_subdev_state *state; ++ u32 int_temp = 0, int_mask = 0; ++ ++ state = v4l2_subdev_lock_and_get_active_state(&interface->sd); ++ ++ rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL0, 0); ++ ++ int_mask |= RKCIF_MIPI_INT_FRAME0_END(stream->id); ++ int_mask |= RKCIF_MIPI_INT_FRAME1_END(stream->id); ++ ++ int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTEN); ++ int_temp &= ~int_mask; ++ rkcif_mipi_write(interface, RKCIF_MIPI_INTEN, int_temp); ++ ++ int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT); ++ int_temp &= ~int_mask; ++ rkcif_mipi_write(interface, RKCIF_MIPI_INTSTAT, int_temp); ++ ++ stream->stopping = false; ++ ++ v4l2_subdev_unlock_state(state); ++} ++ ++static void rkcif_mipi_set_crop(struct rkcif_stream *stream, u16 left, u16 top) ++{ ++ u32 val; ++ ++ val = RKCIF_XY_COORD(left, top); ++ rkcif_mipi_stream_write(stream, RKCIF_MIPI_CROP_START, val); ++} ++ ++irqreturn_t rkcif_mipi_isr(int irq, void *ctx) ++{ ++ struct device *dev = ctx; ++ struct rkcif_device *rkcif = dev_get_drvdata(dev); ++ irqreturn_t ret = IRQ_NONE; ++ u32 intstat; ++ ++ for (unsigned int i = 0; i < rkcif->match_data->mipi->mipi_num; i++) { ++ enum rkcif_interface_index index = RKCIF_MIPI_BASE + i; ++ struct rkcif_interface *interface = &rkcif->interfaces[index]; ++ ++ intstat = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT); ++ rkcif_mipi_write(interface, RKCIF_MIPI_INTSTAT, intstat); ++ ++ for (unsigned int j = 0; j < interface->streams_num; j++) { ++ struct rkcif_stream *stream = &interface->streams[j]; ++ ++ if (intstat & RKCIF_MIPI_INT_FRAME0_END(stream->id) || ++ intstat & RKCIF_MIPI_INT_FRAME1_END(stream->id)) { ++ ret = IRQ_HANDLED; ++ ++ if (stream->stopping) { ++ rkcif_mipi_stop_streaming(stream); ++ wake_up(&stream->wq_stopped); ++ continue; ++ } ++ ++ rkcif_stream_pingpong(stream); ++ } ++ } ++ } ++ ++ return ret; ++} ++ ++int rkcif_mipi_register(struct rkcif_device *rkcif) ++{ ++ int ret; ++ ++ if (!rkcif->match_data->mipi) ++ return 0; ++ ++ for (unsigned int i = 0; i < rkcif->match_data->mipi->mipi_num; i++) { ++ enum rkcif_interface_index index = RKCIF_MIPI_BASE + i; ++ struct rkcif_interface *interface = &rkcif->interfaces[index]; ++ ++ interface->index = index; ++ interface->type = RKCIF_IF_MIPI; ++ interface->in_fmts = mipi_in_fmts; ++ interface->in_fmts_num = ARRAY_SIZE(mipi_in_fmts); ++ interface->set_crop = rkcif_mipi_set_crop; ++ interface->streams_num = 0; ++ ret = rkcif_interface_register(rkcif, interface); ++ if (ret) ++ continue; ++ ++ for (unsigned int j = 0; j < RKCIF_ID_MAX; j++) { ++ struct rkcif_stream *stream = &interface->streams[j]; ++ ++ stream->id = j; ++ stream->interface = interface; ++ stream->out_fmts = mipi_out_fmts; ++ stream->out_fmts_num = ARRAY_SIZE(mipi_out_fmts); ++ stream->queue_buffer = rkcif_mipi_queue_buffer; ++ stream->start_streaming = rkcif_mipi_start_streaming; ++ stream->stop_streaming = rkcif_mipi_stop_streaming; ++ ret = rkcif_stream_register(rkcif, stream); ++ if (ret) ++ goto err; ++ interface->streams_num++; ++ } ++ } ++ ++ return 0; ++ ++err: ++ for (unsigned int i = 0; i < rkcif->match_data->mipi->mipi_num; i++) { ++ enum rkcif_interface_index index = RKCIF_MIPI_BASE + i; ++ struct rkcif_interface *interface = &rkcif->interfaces[index]; ++ ++ for (unsigned int j = 0; j < interface->streams_num; j++) ++ rkcif_stream_unregister(&interface->streams[j]); ++ ++ rkcif_interface_unregister(interface); ++ } ++ return ret; ++} ++ ++void rkcif_mipi_unregister(struct rkcif_device *rkcif) ++{ ++ if (!rkcif->match_data->mipi) ++ return; ++ ++ for (unsigned int i = 0; i < rkcif->match_data->mipi->mipi_num; i++) { ++ enum rkcif_interface_index index = RKCIF_MIPI_BASE + i; ++ struct rkcif_interface *interface = &rkcif->interfaces[index]; ++ ++ for (unsigned int j = 0; j < interface->streams_num; j++) ++ rkcif_stream_unregister(&interface->streams[j]); ++ ++ rkcif_interface_unregister(interface); ++ } ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h +@@ -0,0 +1,23 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Rockchip Camera Interface (CIF) Driver ++ * ++ * Copyright (C) 2018 Rockchip Electronics Co., Ltd. ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2025 Collabora, Ltd. ++ */ ++ ++#ifndef _RKCIF_CAPTURE_MIPI_H ++#define _RKCIF_CAPTURE_MIPI_H ++ ++#include "rkcif-common.h" ++ ++extern const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data; ++ ++int rkcif_mipi_register(struct rkcif_device *rkcif); ++ ++void rkcif_mipi_unregister(struct rkcif_device *rkcif); ++ ++irqreturn_t rkcif_mipi_isr(int irq, void *ctx); ++ ++#endif +--- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h +@@ -73,6 +73,17 @@ enum rkcif_interface_type { + RKCIF_IF_MIPI, + }; + ++enum rkcif_mipi_format_type { ++ RKCIF_MIPI_TYPE_INVALID, ++ RKCIF_MIPI_TYPE_RAW8, ++ RKCIF_MIPI_TYPE_RAW10, ++ RKCIF_MIPI_TYPE_RAW12, ++ RKCIF_MIPI_TYPE_RGB888, ++ RKCIF_MIPI_TYPE_YUV422SP, ++ RKCIF_MIPI_TYPE_YUV420SP, ++ RKCIF_MIPI_TYPE_YUV400, ++}; ++ + struct rkcif_buffer { + struct vb2_v4l2_buffer vb; + struct list_head queue; +@@ -107,9 +118,15 @@ struct rkcif_output_fmt { + u32 fourcc; + u32 mbus_code; + u8 cplanes; ++ u8 depth; + + union { + u32 dvp_fmt_val; ++ struct { ++ u8 dt; ++ bool compact; ++ enum rkcif_mipi_format_type type; ++ } mipi; + }; + }; + +@@ -184,6 +201,17 @@ struct rkcif_interface { + void (*set_crop)(struct rkcif_stream *stream, u16 left, u16 top); + }; + ++struct rkcif_mipi_match_data { ++ unsigned int mipi_num; ++ unsigned int regs[RKCIF_MIPI_REGISTER_MAX]; ++ unsigned int regs_id[RKCIF_ID_MAX][RKCIF_MIPI_ID_REGISTER_MAX]; ++ u32 (*mipi_ctrl0)(struct rkcif_stream *stream, ++ const struct rkcif_output_fmt *active_out_fmt); ++ struct { ++ unsigned int offset; ++ } blocks[RKCIF_MIPI_MAX - RKCIF_MIPI_BASE]; ++}; ++ + struct rkcif_dvp_match_data { + const struct rkcif_input_fmt *in_fmts; + unsigned int in_fmts_num; +@@ -199,6 +227,7 @@ struct rkcif_match_data { + const char *const *clks; + unsigned int clks_num; + const struct rkcif_dvp_match_data *dvp; ++ const struct rkcif_mipi_match_data *mipi; + }; + + struct rkcif_device { +--- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c +@@ -24,6 +24,7 @@ + #include + + #include "rkcif-capture-dvp.h" ++#include "rkcif-capture-mipi.h" + #include "rkcif-common.h" + + static const char *const px30_vip_clks[] = { +@@ -49,6 +50,7 @@ static const struct rkcif_match_data rk3 + .clks = rk3568_vicap_clks, + .clks_num = ARRAY_SIZE(rk3568_vicap_clks), + .dvp = &rkcif_rk3568_vicap_dvp_match_data, ++ .mipi = &rkcif_rk3568_vicap_mipi_match_data, + }; + + static const struct of_device_id rkcif_plat_of_match[] = { +@@ -72,14 +74,21 @@ static int rkcif_register(struct rkcif_d + if (ret && ret != -ENODEV) + goto err; + ++ ret = rkcif_mipi_register(rkcif); ++ if (ret && ret != -ENODEV) ++ goto err_dvp_unregister; ++ + return 0; + ++err_dvp_unregister: ++ rkcif_dvp_unregister(rkcif); + err: + return ret; + } + + static void rkcif_unregister(struct rkcif_device *rkcif) + { ++ rkcif_mipi_unregister(rkcif); + rkcif_dvp_unregister(rkcif); + } + +@@ -128,6 +137,9 @@ static irqreturn_t rkcif_isr(int irq, vo + if (rkcif_dvp_isr(irq, ctx) == IRQ_HANDLED) + ret = IRQ_HANDLED; + ++ if (rkcif_mipi_isr(irq, ctx) == IRQ_HANDLED) ++ ret = IRQ_HANDLED; ++ + return ret; + } + +--- a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h +@@ -128,4 +128,26 @@ enum rkcif_dvp_register_index { + RKCIF_DVP_REGISTER_MAX + }; + ++enum rkcif_mipi_register_index { ++ RKCIF_MIPI_CTRL, ++ RKCIF_MIPI_INTEN, ++ RKCIF_MIPI_INTSTAT, ++ RKCIF_MIPI_REGISTER_MAX ++}; ++ ++enum rkcif_mipi_id_register_index { ++ RKCIF_MIPI_CTRL0, ++ RKCIF_MIPI_CTRL1, ++ RKCIF_MIPI_FRAME0_ADDR_Y, ++ RKCIF_MIPI_FRAME0_ADDR_UV, ++ RKCIF_MIPI_FRAME0_VLW_Y, ++ RKCIF_MIPI_FRAME0_VLW_UV, ++ RKCIF_MIPI_FRAME1_ADDR_Y, ++ RKCIF_MIPI_FRAME1_ADDR_UV, ++ RKCIF_MIPI_FRAME1_VLW_Y, ++ RKCIF_MIPI_FRAME1_VLW_UV, ++ RKCIF_MIPI_CROP_START, ++ RKCIF_MIPI_ID_REGISTER_MAX ++}; ++ + #endif diff --git a/target/linux/rockchip/patches-6.18/045-67-v7.0-media-dt-bindings-add-rockchip-mipi-csi-2-receiver.patch b/target/linux/rockchip/patches-6.18/045-67-v7.0-media-dt-bindings-add-rockchip-mipi-csi-2-receiver.patch new file mode 100644 index 00000000000..eadbf32e49d --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-67-v7.0-media-dt-bindings-add-rockchip-mipi-csi-2-receiver.patch @@ -0,0 +1,161 @@ +From a1da27d0c3e08d88980a3205fc61773ec6089666 Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Tue, 20 Jan 2026 13:22:27 +0100 +Subject: [PATCH] media: dt-bindings: add rockchip mipi csi-2 receiver + +Add documentation for the Rockchip MIPI CSI-2 Receiver. + +Signed-off-by: Michael Riesch +Reviewed-by: Krzysztof Kozlowski +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../media/rockchip,rk3568-mipi-csi2.yaml | 141 ++++++++++++++++++ + 1 file changed, 141 insertions(+) + create mode 100644 Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml +@@ -0,0 +1,141 @@ ++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/media/rockchip,rk3568-mipi-csi2.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Rockchip MIPI CSI-2 Receiver ++ ++maintainers: ++ - Michael Riesch ++ ++description: ++ The Rockchip MIPI CSI-2 Receiver is a CSI-2 bridge with one input port and ++ one output port. It receives the data with the help of an external MIPI PHY ++ (C-PHY or D-PHY) and passes it to the Rockchip Video Capture (VICAP) block. ++ ++properties: ++ compatible: ++ enum: ++ - rockchip,rk3568-mipi-csi2 ++ ++ reg: ++ maxItems: 1 ++ ++ interrupts: ++ items: ++ - description: Interrupt that signals changes in CSI2HOST_ERR1. ++ - description: Interrupt that signals changes in CSI2HOST_ERR2. ++ ++ interrupt-names: ++ items: ++ - const: err1 ++ - const: err2 ++ ++ clocks: ++ maxItems: 1 ++ ++ phys: ++ maxItems: 1 ++ description: MIPI C-PHY or D-PHY. ++ ++ ports: ++ $ref: /schemas/graph.yaml#/properties/ports ++ ++ properties: ++ port@0: ++ $ref: /schemas/graph.yaml#/$defs/port-base ++ unevaluatedProperties: false ++ description: Input port node. Connect to e.g., a MIPI CSI-2 image sensor. ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++ properties: ++ bus-type: ++ enum: ++ - 1 # MEDIA_BUS_TYPE_CSI2_CPHY ++ - 4 # MEDIA_BUS_TYPE_CSI2_DPHY ++ ++ data-lanes: ++ minItems: 1 ++ maxItems: 4 ++ ++ required: ++ - bus-type ++ - data-lanes ++ ++ port@1: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: Output port connected to a Rockchip VICAP port. ++ ++ required: ++ - port@0 ++ - port@1 ++ ++ power-domains: ++ maxItems: 1 ++ ++ resets: ++ maxItems: 1 ++ ++required: ++ - compatible ++ - reg ++ - clocks ++ - phys ++ - ports ++ - power-domains ++ - resets ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ #include ++ #include ++ ++ soc { ++ interrupt-parent = <&gic>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ++ csi: csi@fdfb0000 { ++ compatible = "rockchip,rk3568-mipi-csi2"; ++ reg = <0x0 0xfdfb0000 0x0 0x10000>; ++ interrupts = , ++ ; ++ interrupt-names = "err1", "err2"; ++ clocks = <&cru PCLK_CSI2HOST1>; ++ phys = <&csi_dphy>; ++ power-domains = <&power RK3568_PD_VI>; ++ resets = <&cru SRST_P_CSI2HOST1>; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ csi_in: port@0 { ++ reg = <0>; ++ ++ csi_input: endpoint { ++ bus-type = ; ++ data-lanes = <1 2 3 4>; ++ remote-endpoint = <&imx415_output>; ++ }; ++ }; ++ ++ csi_out: port@1 { ++ reg = <1>; ++ ++ csi_output: endpoint { ++ remote-endpoint = <&vicap_mipi_input>; ++ }; ++ }; ++ }; ++ }; ++ }; diff --git a/target/linux/rockchip/patches-6.18/045-68-v7.0-media-synopsys-add-driver-for-the-designware-mipi-csi-2-re.patch b/target/linux/rockchip/patches-6.18/045-68-v7.0-media-synopsys-add-driver-for-the-designware-mipi-csi-2-re.patch new file mode 100644 index 00000000000..8e777e3c086 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-68-v7.0-media-synopsys-add-driver-for-the-designware-mipi-csi-2-re.patch @@ -0,0 +1,783 @@ +From 355a110040665e439af126e8ec9c3968e73f163c Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Tue, 20 Jan 2026 13:22:28 +0100 +Subject: [PATCH] media: synopsys: add driver for the designware mipi csi-2 + receiver + +The Synopsys DesignWare MIPI CSI-2 Receiver is a CSI-2 bridge with +one input port and one output port. It receives the data with the +help of an external MIPI PHY (C-PHY or D-PHY) and passes it to e.g., +the Rockchip Video Capture (VICAP) block on recent Rockchip SoCs. + +Add a V4L2 subdevice driver for this unit. + +Signed-off-by: Michael Riesch +Reviewed-by: Bryan O'Donoghue +Reviewed-by: Mehdi Djait +Signed-off-by: Michael Riesch +[Sakari Ailus: Make sparse and smatch happy.] +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + drivers/media/platform/synopsys/Kconfig | 18 + + drivers/media/platform/synopsys/Makefile | 2 + + .../media/platform/synopsys/dw-mipi-csi2rx.c | 722 ++++++++++++++++++ + 3 files changed, 742 insertions(+) + create mode 100644 drivers/media/platform/synopsys/dw-mipi-csi2rx.c + +--- a/drivers/media/platform/synopsys/Kconfig ++++ b/drivers/media/platform/synopsys/Kconfig +@@ -1,3 +1,21 @@ + # SPDX-License-Identifier: GPL-2.0-only + + source "drivers/media/platform/synopsys/hdmirx/Kconfig" ++ ++config VIDEO_DW_MIPI_CSI2RX ++ tristate "Synopsys DesignWare MIPI CSI-2 Receiver" ++ depends on VIDEO_DEV ++ depends on V4L_PLATFORM_DRIVERS ++ depends on PM && COMMON_CLK ++ select MEDIA_CONTROLLER ++ select V4L2_FWNODE ++ select VIDEO_V4L2_SUBDEV_API ++ help ++ The Synopsys DesignWare MIPI CSI-2 Receiver is a CSI-2 bridge with ++ one input port and one output port. It receives the data with the ++ help of an external MIPI PHY (C-PHY or D-PHY) and passes it to e.g., ++ the Rockchip Video Capture (VICAP) block on recent Rockchip SoCs. ++ This is a driver for this unit. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called dw-mipi-csi2rx. +--- a/drivers/media/platform/synopsys/Makefile ++++ b/drivers/media/platform/synopsys/Makefile +@@ -1,2 +1,4 @@ + # SPDX-License-Identifier: GPL-2.0-only + obj-y += hdmirx/ ++ ++obj-$(CONFIG_VIDEO_DW_MIPI_CSI2RX) += dw-mipi-csi2rx.o +--- /dev/null ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -0,0 +1,722 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Synopsys DesignWare MIPI CSI-2 Receiver Driver ++ * ++ * Copyright (C) 2019 Rockchip Electronics Co., Ltd. ++ * Copyright (C) 2025 Michael Riesch ++ * Copyright (C) 2026 Collabora, Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define DW_MIPI_CSI2RX_N_LANES 0x04 ++#define DW_MIPI_CSI2RX_RESETN 0x10 ++#define DW_MIPI_CSI2RX_PHY_STATE 0x14 ++#define DW_MIPI_CSI2RX_ERR1 0x20 ++#define DW_MIPI_CSI2RX_ERR2 0x24 ++#define DW_MIPI_CSI2RX_MSK1 0x28 ++#define DW_MIPI_CSI2RX_MSK2 0x2c ++#define DW_MIPI_CSI2RX_CONTROL 0x40 ++ ++#define SW_CPHY_EN(x) ((x) << 0) ++#define SW_DSI_EN(x) ((x) << 4) ++#define SW_DATATYPE_FS(x) ((x) << 8) ++#define SW_DATATYPE_FE(x) ((x) << 14) ++#define SW_DATATYPE_LS(x) ((x) << 20) ++#define SW_DATATYPE_LE(x) ((x) << 26) ++ ++#define DW_MIPI_CSI2RX_CLKS_MAX 1 ++ ++enum { ++ DW_MIPI_CSI2RX_PAD_SINK, ++ DW_MIPI_CSI2RX_PAD_SRC, ++ DW_MIPI_CSI2RX_PAD_MAX, ++}; ++ ++struct dw_mipi_csi2rx_format { ++ u32 code; ++ u8 depth; ++ u8 csi_dt; ++}; ++ ++struct dw_mipi_csi2rx_device { ++ struct device *dev; ++ ++ void __iomem *base_addr; ++ struct clk_bulk_data *clks; ++ unsigned int clks_num; ++ struct phy *phy; ++ struct reset_control *reset; ++ ++ const struct dw_mipi_csi2rx_format *formats; ++ unsigned int formats_num; ++ ++ struct media_pad pads[DW_MIPI_CSI2RX_PAD_MAX]; ++ struct v4l2_async_notifier notifier; ++ struct v4l2_subdev sd; ++ ++ enum v4l2_mbus_type bus_type; ++ u32 lanes_num; ++}; ++ ++static const struct v4l2_mbus_framefmt default_format = { ++ .width = 3840, ++ .height = 2160, ++ .code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .field = V4L2_FIELD_NONE, ++ .colorspace = V4L2_COLORSPACE_RAW, ++ .ycbcr_enc = V4L2_YCBCR_ENC_601, ++ .quantization = V4L2_QUANTIZATION_FULL_RANGE, ++ .xfer_func = V4L2_XFER_FUNC_NONE, ++}; ++ ++static const struct dw_mipi_csi2rx_format formats[] = { ++ /* YUV formats */ ++ { ++ .code = MEDIA_BUS_FMT_YUYV8_1X16, ++ .depth = 16, ++ .csi_dt = MIPI_CSI2_DT_YUV422_8B, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_UYVY8_1X16, ++ .depth = 16, ++ .csi_dt = MIPI_CSI2_DT_YUV422_8B, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_YVYU8_1X16, ++ .depth = 16, ++ .csi_dt = MIPI_CSI2_DT_YUV422_8B, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_VYUY8_1X16, ++ .depth = 16, ++ .csi_dt = MIPI_CSI2_DT_YUV422_8B, ++ }, ++ /* RGB formats */ ++ { ++ .code = MEDIA_BUS_FMT_RGB888_1X24, ++ .depth = 24, ++ .csi_dt = MIPI_CSI2_DT_RGB888, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_BGR888_1X24, ++ .depth = 24, ++ .csi_dt = MIPI_CSI2_DT_RGB888, ++ }, ++ /* Bayer formats */ ++ { ++ .code = MEDIA_BUS_FMT_SBGGR8_1X8, ++ .depth = 8, ++ .csi_dt = MIPI_CSI2_DT_RAW8, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SGBRG8_1X8, ++ .depth = 8, ++ .csi_dt = MIPI_CSI2_DT_RAW8, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SGRBG8_1X8, ++ .depth = 8, ++ .csi_dt = MIPI_CSI2_DT_RAW8, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SRGGB8_1X8, ++ .depth = 8, ++ .csi_dt = MIPI_CSI2_DT_RAW8, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SBGGR10_1X10, ++ .depth = 10, ++ .csi_dt = MIPI_CSI2_DT_RAW10, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SGBRG10_1X10, ++ .depth = 10, ++ .csi_dt = MIPI_CSI2_DT_RAW10, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SGRBG10_1X10, ++ .depth = 10, ++ .csi_dt = MIPI_CSI2_DT_RAW10, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .depth = 10, ++ .csi_dt = MIPI_CSI2_DT_RAW10, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SBGGR12_1X12, ++ .depth = 12, ++ .csi_dt = MIPI_CSI2_DT_RAW12, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SGBRG12_1X12, ++ .depth = 12, ++ .csi_dt = MIPI_CSI2_DT_RAW12, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SGRBG12_1X12, ++ .depth = 12, ++ .csi_dt = MIPI_CSI2_DT_RAW12, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SRGGB12_1X12, ++ .depth = 12, ++ .csi_dt = MIPI_CSI2_DT_RAW12, ++ }, ++}; ++ ++static inline struct dw_mipi_csi2rx_device *to_csi2(struct v4l2_subdev *sd) ++{ ++ return container_of(sd, struct dw_mipi_csi2rx_device, sd); ++} ++ ++static inline void dw_mipi_csi2rx_write(struct dw_mipi_csi2rx_device *csi2, ++ unsigned int addr, u32 val) ++{ ++ writel(val, csi2->base_addr + addr); ++} ++ ++static inline u32 dw_mipi_csi2rx_read(struct dw_mipi_csi2rx_device *csi2, ++ unsigned int addr) ++{ ++ return readl(csi2->base_addr + addr); ++} ++ ++static const struct dw_mipi_csi2rx_format * ++dw_mipi_csi2rx_find_format(struct dw_mipi_csi2rx_device *csi2, u32 mbus_code) ++{ ++ WARN_ON(csi2->formats_num == 0); ++ ++ for (unsigned int i = 0; i < csi2->formats_num; i++) { ++ const struct dw_mipi_csi2rx_format *format = &csi2->formats[i]; ++ ++ if (format->code == mbus_code) ++ return format; ++ } ++ ++ return NULL; ++} ++ ++static int dw_mipi_csi2rx_start(struct dw_mipi_csi2rx_device *csi2) ++{ ++ struct media_pad *source_pad; ++ union phy_configure_opts opts; ++ u32 lanes = csi2->lanes_num; ++ u32 control = 0; ++ s64 link_freq; ++ int ret; ++ ++ if (lanes < 1 || lanes > 4) ++ return -EINVAL; ++ ++ source_pad = media_pad_remote_pad_unique( ++ &csi2->pads[DW_MIPI_CSI2RX_PAD_SINK]); ++ if (IS_ERR(source_pad)) ++ return PTR_ERR(source_pad); ++ ++ /* set mult and div to 0, thus completely rely on V4L2_CID_LINK_FREQ */ ++ link_freq = v4l2_get_link_freq(source_pad, 0, 0); ++ if (link_freq < 0) ++ return link_freq; ++ ++ switch (csi2->bus_type) { ++ case V4L2_MBUS_CSI2_DPHY: ++ ret = phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, ++ lanes, &opts.mipi_dphy); ++ if (ret) ++ return ret; ++ ++ ret = phy_set_mode(csi2->phy, PHY_MODE_MIPI_DPHY); ++ if (ret) ++ return ret; ++ ++ ret = phy_configure(csi2->phy, &opts); ++ if (ret) ++ return ret; ++ ++ control |= SW_CPHY_EN(0); ++ break; ++ ++ case V4L2_MBUS_CSI2_CPHY: ++ /* TODO: implement CPHY configuration */ ++ return -EOPNOTSUPP; ++ default: ++ return -EINVAL; ++ } ++ ++ control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) | ++ SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03); ++ ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_N_LANES, lanes - 1); ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_CONTROL, control); ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_RESETN, 1); ++ ++ return phy_power_on(csi2->phy); ++} ++ ++static void dw_mipi_csi2rx_stop(struct dw_mipi_csi2rx_device *csi2) ++{ ++ phy_power_off(csi2->phy); ++ ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_RESETN, 0); ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_MSK1, ~0); ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_MSK2, ~0); ++} ++ ++static const struct media_entity_operations dw_mipi_csi2rx_media_ops = { ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++static int ++dw_mipi_csi2rx_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct dw_mipi_csi2rx_device *csi2 = to_csi2(sd); ++ ++ switch (code->pad) { ++ case DW_MIPI_CSI2RX_PAD_SRC: ++ if (code->index) ++ return -EINVAL; ++ ++ code->code = ++ v4l2_subdev_state_get_format(sd_state, ++ DW_MIPI_CSI2RX_PAD_SINK)->code; ++ ++ return 0; ++ case DW_MIPI_CSI2RX_PAD_SINK: ++ if (code->index > csi2->formats_num) ++ return -EINVAL; ++ ++ code->code = csi2->formats[code->index].code; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int dw_mipi_csi2rx_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct dw_mipi_csi2rx_device *csi2 = to_csi2(sd); ++ const struct dw_mipi_csi2rx_format *fmt; ++ struct v4l2_mbus_framefmt *sink, *src; ++ ++ /* the format on the source pad always matches the sink pad */ ++ if (format->pad == DW_MIPI_CSI2RX_PAD_SRC) ++ return v4l2_subdev_get_fmt(sd, state, format); ++ ++ sink = v4l2_subdev_state_get_format(state, format->pad, format->stream); ++ if (!sink) ++ return -EINVAL; ++ ++ fmt = dw_mipi_csi2rx_find_format(csi2, format->format.code); ++ if (!fmt) ++ format->format = default_format; ++ ++ *sink = format->format; ++ ++ /* propagate the format to the source pad */ ++ src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, ++ format->stream); ++ if (!src) ++ return -EINVAL; ++ ++ *src = *sink; ++ ++ return 0; ++} ++ ++static int dw_mipi_csi2rx_set_routing(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ enum v4l2_subdev_format_whence which, ++ struct v4l2_subdev_krouting *routing) ++{ ++ int ret; ++ ++ ret = v4l2_subdev_routing_validate(sd, routing, ++ V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); ++ if (ret) ++ return ret; ++ ++ return v4l2_subdev_set_routing_with_fmt(sd, state, routing, ++ &default_format); ++} ++ ++static int dw_mipi_csi2rx_enable_streams(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct dw_mipi_csi2rx_device *csi2 = to_csi2(sd); ++ struct v4l2_subdev *remote_sd; ++ struct media_pad *sink_pad, *remote_pad; ++ struct device *dev = csi2->dev; ++ u64 mask; ++ int ret; ++ ++ sink_pad = &sd->entity.pads[DW_MIPI_CSI2RX_PAD_SINK]; ++ remote_pad = media_pad_remote_pad_first(sink_pad); ++ remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); ++ ++ mask = v4l2_subdev_state_xlate_streams(state, DW_MIPI_CSI2RX_PAD_SINK, ++ DW_MIPI_CSI2RX_PAD_SRC, ++ &streams_mask); ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) ++ goto err; ++ ++ ret = dw_mipi_csi2rx_start(csi2); ++ if (ret) { ++ dev_err(dev, "failed to enable CSI hardware\n"); ++ goto err_pm_runtime_put; ++ } ++ ++ ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask); ++ if (ret) ++ goto err_csi_stop; ++ ++ return 0; ++ ++err_csi_stop: ++ dw_mipi_csi2rx_stop(csi2); ++err_pm_runtime_put: ++ pm_runtime_put(dev); ++err: ++ return ret; ++} ++ ++static int dw_mipi_csi2rx_disable_streams(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct dw_mipi_csi2rx_device *csi2 = to_csi2(sd); ++ struct v4l2_subdev *remote_sd; ++ struct media_pad *sink_pad, *remote_pad; ++ struct device *dev = csi2->dev; ++ u64 mask; ++ int ret; ++ ++ sink_pad = &sd->entity.pads[DW_MIPI_CSI2RX_PAD_SINK]; ++ remote_pad = media_pad_remote_pad_first(sink_pad); ++ remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); ++ ++ mask = v4l2_subdev_state_xlate_streams(state, DW_MIPI_CSI2RX_PAD_SINK, ++ DW_MIPI_CSI2RX_PAD_SRC, ++ &streams_mask); ++ ++ ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); ++ ++ dw_mipi_csi2rx_stop(csi2); ++ ++ pm_runtime_put(dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_subdev_pad_ops dw_mipi_csi2rx_pad_ops = { ++ .enum_mbus_code = dw_mipi_csi2rx_enum_mbus_code, ++ .get_fmt = v4l2_subdev_get_fmt, ++ .set_fmt = dw_mipi_csi2rx_set_fmt, ++ .set_routing = dw_mipi_csi2rx_set_routing, ++ .enable_streams = dw_mipi_csi2rx_enable_streams, ++ .disable_streams = dw_mipi_csi2rx_disable_streams, ++}; ++ ++static const struct v4l2_subdev_ops dw_mipi_csi2rx_ops = { ++ .pad = &dw_mipi_csi2rx_pad_ops, ++}; ++ ++static int dw_mipi_csi2rx_init_state(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state) ++{ ++ struct v4l2_subdev_route routes[] = { ++ { ++ .sink_pad = DW_MIPI_CSI2RX_PAD_SINK, ++ .sink_stream = 0, ++ .source_pad = DW_MIPI_CSI2RX_PAD_SRC, ++ .source_stream = 0, ++ .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, ++ }, ++ }; ++ struct v4l2_subdev_krouting routing = { ++ .len_routes = ARRAY_SIZE(routes), ++ .num_routes = ARRAY_SIZE(routes), ++ .routes = routes, ++ }; ++ ++ return v4l2_subdev_set_routing_with_fmt(sd, state, &routing, ++ &default_format); ++} ++ ++static const struct v4l2_subdev_internal_ops dw_mipi_csi2rx_internal_ops = { ++ .init_state = dw_mipi_csi2rx_init_state, ++}; ++ ++static int dw_mipi_csi2rx_notifier_bound(struct v4l2_async_notifier *notifier, ++ struct v4l2_subdev *sd, ++ struct v4l2_async_connection *asd) ++{ ++ struct dw_mipi_csi2rx_device *csi2 = ++ container_of(notifier, struct dw_mipi_csi2rx_device, notifier); ++ struct media_pad *sink_pad = &csi2->pads[DW_MIPI_CSI2RX_PAD_SINK]; ++ int ret; ++ ++ ret = v4l2_create_fwnode_links_to_pad(sd, sink_pad, ++ MEDIA_LNK_FL_ENABLED); ++ if (ret) { ++ dev_err(csi2->dev, "failed to link source pad of %s\n", ++ sd->name); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct v4l2_async_notifier_operations dw_mipi_csi2rx_notifier_ops = { ++ .bound = dw_mipi_csi2rx_notifier_bound, ++}; ++ ++static int dw_mipi_csi2rx_register_notifier(struct dw_mipi_csi2rx_device *csi2) ++{ ++ struct v4l2_async_connection *asd; ++ struct v4l2_async_notifier *ntf = &csi2->notifier; ++ struct v4l2_fwnode_endpoint vep; ++ struct v4l2_subdev *sd = &csi2->sd; ++ struct device *dev = csi2->dev; ++ int ret; ++ ++ struct fwnode_handle *ep __free(fwnode_handle) = ++ fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); ++ if (!ep) ++ return dev_err_probe(dev, -ENODEV, "failed to get endpoint\n"); ++ ++ vep.bus_type = V4L2_MBUS_UNKNOWN; ++ ret = v4l2_fwnode_endpoint_parse(ep, &vep); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to parse endpoint\n"); ++ ++ if (vep.bus_type != V4L2_MBUS_CSI2_DPHY && ++ vep.bus_type != V4L2_MBUS_CSI2_CPHY) ++ return dev_err_probe(dev, -EINVAL, ++ "invalid bus type of endpoint\n"); ++ ++ csi2->bus_type = vep.bus_type; ++ csi2->lanes_num = vep.bus.mipi_csi2.num_data_lanes; ++ ++ v4l2_async_subdev_nf_init(ntf, sd); ++ ntf->ops = &dw_mipi_csi2rx_notifier_ops; ++ ++ asd = v4l2_async_nf_add_fwnode_remote(ntf, ep, ++ struct v4l2_async_connection); ++ if (IS_ERR(asd)) { ++ ret = PTR_ERR(asd); ++ goto err_nf_cleanup; ++ } ++ ++ ret = v4l2_async_nf_register(ntf); ++ if (ret) { ++ ret = dev_err_probe(dev, ret, "failed to register notifier\n"); ++ goto err_nf_cleanup; ++ } ++ ++ return 0; ++ ++err_nf_cleanup: ++ v4l2_async_nf_cleanup(ntf); ++ ++ return ret; ++} ++ ++static int dw_mipi_csi2rx_register(struct dw_mipi_csi2rx_device *csi2) ++{ ++ struct media_pad *pads = csi2->pads; ++ struct v4l2_subdev *sd = &csi2->sd; ++ int ret; ++ ++ ret = dw_mipi_csi2rx_register_notifier(csi2); ++ if (ret) ++ goto err; ++ ++ v4l2_subdev_init(sd, &dw_mipi_csi2rx_ops); ++ sd->dev = csi2->dev; ++ sd->entity.ops = &dw_mipi_csi2rx_media_ops; ++ sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; ++ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; ++ sd->internal_ops = &dw_mipi_csi2rx_internal_ops; ++ snprintf(sd->name, sizeof(sd->name), "dw-mipi-csi2rx %s", ++ dev_name(csi2->dev)); ++ ++ pads[DW_MIPI_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK | ++ MEDIA_PAD_FL_MUST_CONNECT; ++ pads[DW_MIPI_CSI2RX_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; ++ ret = media_entity_pads_init(&sd->entity, DW_MIPI_CSI2RX_PAD_MAX, pads); ++ if (ret) ++ goto err_notifier_unregister; ++ ++ ret = v4l2_subdev_init_finalize(sd); ++ if (ret) ++ goto err_entity_cleanup; ++ ++ ret = v4l2_async_register_subdev(sd); ++ if (ret) { ++ dev_err(sd->dev, "failed to register CSI-2 subdev\n"); ++ goto err_subdev_cleanup; ++ } ++ ++ return 0; ++ ++err_subdev_cleanup: ++ v4l2_subdev_cleanup(sd); ++err_entity_cleanup: ++ media_entity_cleanup(&sd->entity); ++err_notifier_unregister: ++ v4l2_async_nf_unregister(&csi2->notifier); ++ v4l2_async_nf_cleanup(&csi2->notifier); ++err: ++ return ret; ++} ++ ++static void dw_mipi_csi2rx_unregister(struct dw_mipi_csi2rx_device *csi2) ++{ ++ struct v4l2_subdev *sd = &csi2->sd; ++ ++ v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(&sd->entity); ++ v4l2_async_nf_unregister(&csi2->notifier); ++ v4l2_async_nf_cleanup(&csi2->notifier); ++} ++ ++static const struct of_device_id dw_mipi_csi2rx_of_match[] = { ++ { ++ .compatible = "rockchip,rk3568-mipi-csi2", ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, dw_mipi_csi2rx_of_match); ++ ++static int dw_mipi_csi2rx_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct dw_mipi_csi2rx_device *csi2; ++ int ret; ++ ++ csi2 = devm_kzalloc(dev, sizeof(*csi2), GFP_KERNEL); ++ if (!csi2) ++ return -ENOMEM; ++ csi2->dev = dev; ++ dev_set_drvdata(dev, csi2); ++ ++ csi2->base_addr = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(csi2->base_addr)) ++ return PTR_ERR(csi2->base_addr); ++ ++ ret = devm_clk_bulk_get_all(dev, &csi2->clks); ++ if (ret != DW_MIPI_CSI2RX_CLKS_MAX) ++ return dev_err_probe(dev, -ENODEV, "failed to get clocks\n"); ++ csi2->clks_num = ret; ++ ++ csi2->phy = devm_phy_get(dev, NULL); ++ if (IS_ERR(csi2->phy)) ++ return dev_err_probe(dev, PTR_ERR(csi2->phy), ++ "failed to get MIPI CSI-2 PHY\n"); ++ ++ csi2->reset = devm_reset_control_get_exclusive(dev, NULL); ++ if (IS_ERR(csi2->reset)) ++ return dev_err_probe(dev, PTR_ERR(csi2->reset), ++ "failed to get reset\n"); ++ ++ csi2->formats = formats; ++ csi2->formats_num = ARRAY_SIZE(formats); ++ ++ ret = devm_pm_runtime_enable(dev); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to enable pm runtime\n"); ++ ++ ret = phy_init(csi2->phy); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "failed to initialize MIPI CSI-2 PHY\n"); ++ ++ ret = dw_mipi_csi2rx_register(csi2); ++ if (ret) ++ goto err_phy_exit; ++ ++ return 0; ++ ++err_phy_exit: ++ phy_exit(csi2->phy); ++ ++ return ret; ++} ++ ++static void dw_mipi_csi2rx_remove(struct platform_device *pdev) ++{ ++ struct dw_mipi_csi2rx_device *csi2 = platform_get_drvdata(pdev); ++ ++ dw_mipi_csi2rx_unregister(csi2); ++ phy_exit(csi2->phy); ++} ++ ++static int dw_mipi_csi2rx_runtime_suspend(struct device *dev) ++{ ++ struct dw_mipi_csi2rx_device *csi2 = dev_get_drvdata(dev); ++ ++ clk_bulk_disable_unprepare(csi2->clks_num, csi2->clks); ++ ++ return 0; ++} ++ ++static int dw_mipi_csi2rx_runtime_resume(struct device *dev) ++{ ++ struct dw_mipi_csi2rx_device *csi2 = dev_get_drvdata(dev); ++ int ret; ++ ++ reset_control_assert(csi2->reset); ++ udelay(5); ++ reset_control_deassert(csi2->reset); ++ ++ ret = clk_bulk_prepare_enable(csi2->clks_num, csi2->clks); ++ if (ret) { ++ dev_err(dev, "failed to enable clocks\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static DEFINE_RUNTIME_DEV_PM_OPS(dw_mipi_csi2rx_pm_ops, ++ dw_mipi_csi2rx_runtime_suspend, ++ dw_mipi_csi2rx_runtime_resume, NULL); ++ ++static struct platform_driver dw_mipi_csi2rx_drv = { ++ .driver = { ++ .name = "dw-mipi-csi2rx", ++ .of_match_table = dw_mipi_csi2rx_of_match, ++ .pm = pm_ptr(&dw_mipi_csi2rx_pm_ops), ++ }, ++ .probe = dw_mipi_csi2rx_probe, ++ .remove = dw_mipi_csi2rx_remove, ++}; ++module_platform_driver(dw_mipi_csi2rx_drv); ++ ++MODULE_DESCRIPTION("Synopsys DesignWare MIPI CSI-2 Receiver platform driver"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/rockchip/patches-6.18/045-69-v7.0-media-synopsys-csi2rx-fix-out-of-bounds-check-for-formats-.patch b/target/linux/rockchip/patches-6.18/045-69-v7.0-media-synopsys-csi2rx-fix-out-of-bounds-check-for-formats-.patch new file mode 100644 index 00000000000..0073a11ba29 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-69-v7.0-media-synopsys-csi2rx-fix-out-of-bounds-check-for-formats-.patch @@ -0,0 +1,31 @@ +From 9232fa05921bc0ea0bc71947440ec9a50b3ad26e Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Mon, 16 Feb 2026 13:28:47 +0100 +Subject: [PATCH] media: synopsys: csi2rx: fix out-of-bounds check for formats + array + +The out-of-bounds check for the format array is off by one. Fix the +check. + +Fixes: 355a11004066 ("media: synopsys: add driver for the designware mipi csi-2 receiver") +Cc: stable@kernel.org +Suggested-by: Dan Carpenter +Signed-off-by: Michael Riesch +Reviewed-by: Frank Li +Signed-off-by: Sakari Ailus +Signed-off-by: Mauro Carvalho Chehab +--- + drivers/media/platform/synopsys/dw-mipi-csi2rx.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -301,7 +301,7 @@ dw_mipi_csi2rx_enum_mbus_code(struct v4l + + return 0; + case DW_MIPI_CSI2RX_PAD_SINK: +- if (code->index > csi2->formats_num) ++ if (code->index >= csi2->formats_num) + return -EINVAL; + + code->code = csi2->formats[code->index].code; diff --git a/target/linux/rockchip/patches-6.18/045-70-v7.1-media-synopsys-csi2rx-use-devm_reset_control_get_optional_.patch b/target/linux/rockchip/patches-6.18/045-70-v7.1-media-synopsys-csi2rx-use-devm_reset_control_get_optional_.patch new file mode 100644 index 00000000000..ec0f9c63295 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-70-v7.1-media-synopsys-csi2rx-use-devm_reset_control_get_optional_.patch @@ -0,0 +1,31 @@ +From 6c03c1c4e8a4d9873432134fb7d22f3c02df7226 Mon Sep 17 00:00:00 2001 +From: Frank Li +Date: Mon, 16 Feb 2026 14:18:39 -0500 +Subject: [PATCH] media: synopsys: csi2rx: use + devm_reset_control_get_optional_exclusive() + +The DW MIPI CSI-2 RX is used on different SoCs, not all of which provide a +reset controller. Switch to devm_reset_control_get_optional_exclusive() +to support such platforms. + +Reset presence and numbering are validated by the DT binding. + +Reviewed-by: Michael Riesch +Signed-off-by: Frank Li +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + drivers/media/platform/synopsys/dw-mipi-csi2rx.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -639,7 +639,7 @@ static int dw_mipi_csi2rx_probe(struct p + return dev_err_probe(dev, PTR_ERR(csi2->phy), + "failed to get MIPI CSI-2 PHY\n"); + +- csi2->reset = devm_reset_control_get_exclusive(dev, NULL); ++ csi2->reset = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(csi2->reset)) + return dev_err_probe(dev, PTR_ERR(csi2->reset), + "failed to get reset\n"); diff --git a/target/linux/rockchip/patches-6.18/045-71-v7.1-media-synopsys-csi2rx-only-check-errors-from-devm_clk_bulk.patch b/target/linux/rockchip/patches-6.18/045-71-v7.1-media-synopsys-csi2rx-only-check-errors-from-devm_clk_bulk.patch new file mode 100644 index 00000000000..fc3375a17e4 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-71-v7.1-media-synopsys-csi2rx-only-check-errors-from-devm_clk_bulk.patch @@ -0,0 +1,40 @@ +From 6fb44d14929036ca4d10ed5d1df47af8a6b642b6 Mon Sep 17 00:00:00 2001 +From: Frank Li +Date: Mon, 16 Feb 2026 14:18:40 -0500 +Subject: [PATCH] media: synopsys: csi2rx: only check errors from + devm_clk_bulk_get_all() + +devm_clk_bulk_get_all() returns all clocks described in the DT, which are +already validated by the binding. Do not need enforce an expected clock +count. + +Only check for error returns (< 0) to support more SoCs. + +Reviewed-by: Michael Riesch +Signed-off-by: Frank Li +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + drivers/media/platform/synopsys/dw-mipi-csi2rx.c | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -40,8 +40,6 @@ + #define SW_DATATYPE_LS(x) ((x) << 20) + #define SW_DATATYPE_LE(x) ((x) << 26) + +-#define DW_MIPI_CSI2RX_CLKS_MAX 1 +- + enum { + DW_MIPI_CSI2RX_PAD_SINK, + DW_MIPI_CSI2RX_PAD_SRC, +@@ -630,7 +628,7 @@ static int dw_mipi_csi2rx_probe(struct p + return PTR_ERR(csi2->base_addr); + + ret = devm_clk_bulk_get_all(dev, &csi2->clks); +- if (ret != DW_MIPI_CSI2RX_CLKS_MAX) ++ if (ret < 0) + return dev_err_probe(dev, -ENODEV, "failed to get clocks\n"); + csi2->clks_num = ret; + diff --git a/target/linux/rockchip/patches-6.18/045-72-v7.1-media-synopsys-csi2rx-implement-.get_frame_desc-callback.patch b/target/linux/rockchip/patches-6.18/045-72-v7.1-media-synopsys-csi2rx-implement-.get_frame_desc-callback.patch new file mode 100644 index 00000000000..3301b6c0af1 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-72-v7.1-media-synopsys-csi2rx-implement-.get_frame_desc-callback.patch @@ -0,0 +1,50 @@ +From 6f4699ae1e012f807af73617405fcc755d1343ad Mon Sep 17 00:00:00 2001 +From: Frank Li +Date: Mon, 16 Feb 2026 14:18:41 -0500 +Subject: [PATCH] media: synopsys: csi2rx: implement .get_frame_desc() callback + +Implement the .get_frame_desc() callback to fetch information from the +remote endpoint. + +Signed-off-by: Frank Li +Reviewed-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../media/platform/synopsys/dw-mipi-csi2rx.c | 21 +++++++++++++++++++ + 1 file changed, 21 insertions(+) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -429,10 +429,31 @@ static int dw_mipi_csi2rx_disable_stream + return ret; + } + ++static int ++dw_mipi_csi2rx_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, ++ struct v4l2_mbus_frame_desc *fd) ++{ ++ struct dw_mipi_csi2rx_device *csi2 = to_csi2(sd); ++ struct v4l2_subdev *remote_sd; ++ struct media_pad *remote_pad; ++ ++ remote_pad = media_pad_remote_pad_unique(&csi2->pads[DW_MIPI_CSI2RX_PAD_SINK]); ++ if (IS_ERR(remote_pad)) { ++ dev_err(csi2->dev, "can't get remote source pad\n"); ++ return PTR_ERR(remote_pad); ++ } ++ ++ remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); ++ ++ return v4l2_subdev_call(remote_sd, pad, get_frame_desc, ++ remote_pad->index, fd); ++} ++ + static const struct v4l2_subdev_pad_ops dw_mipi_csi2rx_pad_ops = { + .enum_mbus_code = dw_mipi_csi2rx_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = dw_mipi_csi2rx_set_fmt, ++ .get_frame_desc = dw_mipi_csi2rx_get_frame_desc, + .set_routing = dw_mipi_csi2rx_set_routing, + .enable_streams = dw_mipi_csi2rx_enable_streams, + .disable_streams = dw_mipi_csi2rx_disable_streams, diff --git a/target/linux/rockchip/patches-6.18/045-73-v7.1-media-synopsys-csi2rx-Use-enum-and-u32-array-for-register-.patch b/target/linux/rockchip/patches-6.18/045-73-v7.1-media-synopsys-csi2rx-Use-enum-and-u32-array-for-register-.patch new file mode 100644 index 00000000000..5c4aa9715f9 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-73-v7.1-media-synopsys-csi2rx-Use-enum-and-u32-array-for-register-.patch @@ -0,0 +1,173 @@ +From 2b301f534883ee1eed1a78a5d43e49ead72c2a0c Mon Sep 17 00:00:00 2001 +From: Frank Li +Date: Mon, 16 Feb 2026 14:18:42 -0500 +Subject: [PATCH] media: synopsys: csi2rx: Use enum and u32 array for register + offsets + +Use enum dw_mipi_csi2rx_regs_index together with a u32 array to describe +register offsets. This allows supporting new IP versions with different +register layouts in a structured way. + +Add rk3568_regs matching the previous macro definitions and pass it as +driver data during probe. + +No functional change intended. + +Reviewed-by: Michael Riesch +Signed-off-by: Frank Li +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../media/platform/synopsys/dw-mipi-csi2rx.c | 94 ++++++++++++++++--- + 1 file changed, 81 insertions(+), 13 deletions(-) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -24,15 +24,6 @@ + #include + #include + +-#define DW_MIPI_CSI2RX_N_LANES 0x04 +-#define DW_MIPI_CSI2RX_RESETN 0x10 +-#define DW_MIPI_CSI2RX_PHY_STATE 0x14 +-#define DW_MIPI_CSI2RX_ERR1 0x20 +-#define DW_MIPI_CSI2RX_ERR2 0x24 +-#define DW_MIPI_CSI2RX_MSK1 0x28 +-#define DW_MIPI_CSI2RX_MSK2 0x2c +-#define DW_MIPI_CSI2RX_CONTROL 0x40 +- + #define SW_CPHY_EN(x) ((x) << 0) + #define SW_DSI_EN(x) ((x) << 4) + #define SW_DATATYPE_FS(x) ((x) << 8) +@@ -40,12 +31,32 @@ + #define SW_DATATYPE_LS(x) ((x) << 20) + #define SW_DATATYPE_LE(x) ((x) << 26) + ++#define DW_REG_EXIST BIT(31) ++#define DW_REG(x) (DW_REG_EXIST | (x)) ++ ++enum dw_mipi_csi2rx_regs_index { ++ DW_MIPI_CSI2RX_N_LANES, ++ DW_MIPI_CSI2RX_RESETN, ++ DW_MIPI_CSI2RX_PHY_STATE, ++ DW_MIPI_CSI2RX_ERR1, ++ DW_MIPI_CSI2RX_ERR2, ++ DW_MIPI_CSI2RX_MSK1, ++ DW_MIPI_CSI2RX_MSK2, ++ DW_MIPI_CSI2RX_CONTROL, ++ ++ DW_MIPI_CSI2RX_MAX, ++}; ++ + enum { + DW_MIPI_CSI2RX_PAD_SINK, + DW_MIPI_CSI2RX_PAD_SRC, + DW_MIPI_CSI2RX_PAD_MAX, + }; + ++struct dw_mipi_csi2rx_drvdata { ++ const u32 *regs; ++}; ++ + struct dw_mipi_csi2rx_format { + u32 code; + u8 depth; +@@ -70,6 +81,23 @@ struct dw_mipi_csi2rx_device { + + enum v4l2_mbus_type bus_type; + u32 lanes_num; ++ ++ const struct dw_mipi_csi2rx_drvdata *drvdata; ++}; ++ ++static const u32 rk3568_regs[DW_MIPI_CSI2RX_MAX] = { ++ [DW_MIPI_CSI2RX_N_LANES] = DW_REG(0x4), ++ [DW_MIPI_CSI2RX_RESETN] = DW_REG(0x10), ++ [DW_MIPI_CSI2RX_PHY_STATE] = DW_REG(0x14), ++ [DW_MIPI_CSI2RX_ERR1] = DW_REG(0x20), ++ [DW_MIPI_CSI2RX_ERR2] = DW_REG(0x24), ++ [DW_MIPI_CSI2RX_MSK1] = DW_REG(0x28), ++ [DW_MIPI_CSI2RX_MSK2] = DW_REG(0x2c), ++ [DW_MIPI_CSI2RX_CONTROL] = DW_REG(0x40), ++}; ++ ++static const struct dw_mipi_csi2rx_drvdata rk3568_drvdata = { ++ .regs = rk3568_regs, + }; + + static const struct v4l2_mbus_framefmt default_format = { +@@ -184,16 +212,50 @@ static inline struct dw_mipi_csi2rx_devi + return container_of(sd, struct dw_mipi_csi2rx_device, sd); + } + ++static bool dw_mipi_csi2rx_has_reg(struct dw_mipi_csi2rx_device *csi2, ++ enum dw_mipi_csi2rx_regs_index index) ++{ ++ if (index < DW_MIPI_CSI2RX_MAX && ++ (csi2->drvdata->regs[index] & DW_REG_EXIST)) ++ return true; ++ ++ return false; ++} ++ ++static void __iomem * ++dw_mipi_csi2rx_get_regaddr(struct dw_mipi_csi2rx_device *csi2, ++ enum dw_mipi_csi2rx_regs_index index) ++{ ++ u32 off = (~DW_REG_EXIST) & csi2->drvdata->regs[index]; ++ ++ return csi2->base_addr + off; ++} ++ + static inline void dw_mipi_csi2rx_write(struct dw_mipi_csi2rx_device *csi2, +- unsigned int addr, u32 val) ++ enum dw_mipi_csi2rx_regs_index index, ++ u32 val) + { +- writel(val, csi2->base_addr + addr); ++ if (!dw_mipi_csi2rx_has_reg(csi2, index)) { ++ dev_err_once(csi2->dev, ++ "write to non-existent register index: %d\n", ++ index); ++ return; ++ } ++ ++ writel(val, dw_mipi_csi2rx_get_regaddr(csi2, index)); + } + + static inline u32 dw_mipi_csi2rx_read(struct dw_mipi_csi2rx_device *csi2, +- unsigned int addr) ++ enum dw_mipi_csi2rx_regs_index index) + { +- return readl(csi2->base_addr + addr); ++ if (!dw_mipi_csi2rx_has_reg(csi2, index)) { ++ dev_err_once(csi2->dev, ++ "read non-existent register index: %d\n", index); ++ /* return 0 for non-existent registers */ ++ return 0; ++ } ++ ++ return readl(dw_mipi_csi2rx_get_regaddr(csi2, index)); + } + + static const struct dw_mipi_csi2rx_format * +@@ -627,6 +689,7 @@ static void dw_mipi_csi2rx_unregister(st + static const struct of_device_id dw_mipi_csi2rx_of_match[] = { + { + .compatible = "rockchip,rk3568-mipi-csi2", ++ .data = &rk3568_drvdata, + }, + {} + }; +@@ -648,6 +711,11 @@ static int dw_mipi_csi2rx_probe(struct p + if (IS_ERR(csi2->base_addr)) + return PTR_ERR(csi2->base_addr); + ++ csi2->drvdata = device_get_match_data(dev); ++ if (!csi2->drvdata) ++ return dev_err_probe(dev, -EINVAL, ++ "failed to get driver data\n"); ++ + ret = devm_clk_bulk_get_all(dev, &csi2->clks); + if (ret < 0) + return dev_err_probe(dev, -ENODEV, "failed to get clocks\n"); diff --git a/target/linux/rockchip/patches-6.18/045-74-v7.1-media-dt-bindings-add-NXP-i.MX93-compatible-string.patch b/target/linux/rockchip/patches-6.18/045-74-v7.1-media-dt-bindings-add-NXP-i.MX93-compatible-string.patch new file mode 100644 index 00000000000..ee0119e6e34 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-74-v7.1-media-dt-bindings-add-NXP-i.MX93-compatible-string.patch @@ -0,0 +1,101 @@ +From 7425b2eaf50a90f57cde586a4a7a0e2e9c2fc0d1 Mon Sep 17 00:00:00 2001 +From: Frank Li +Date: Mon, 16 Feb 2026 14:18:43 -0500 +Subject: [PATCH] media: dt-bindings: add NXP i.MX93 compatible string + +The i.MX93 uses the DW CSI-2 RX controller, which is similar to the +Rockchip RK3568 implementation. + +The i.MX93 variant provides one IRQ, two clocks, and no resets. Add the +"fsl,imx93-mipi-csi2" compatible string and keep the same constraints for +RK3568. + +Reviewed-by: Krzysztof Kozlowski +Acked-by: Michael Riesch +Signed-off-by: Frank Li +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../media/rockchip,rk3568-mipi-csi2.yaml | 47 ++++++++++++++++++- + 1 file changed, 45 insertions(+), 2 deletions(-) + +--- a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml ++++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml +@@ -17,6 +17,7 @@ description: + properties: + compatible: + enum: ++ - fsl,imx93-mipi-csi2 + - rockchip,rk3568-mipi-csi2 + + reg: +@@ -26,14 +27,23 @@ properties: + items: + - description: Interrupt that signals changes in CSI2HOST_ERR1. + - description: Interrupt that signals changes in CSI2HOST_ERR2. ++ minItems: 1 + + interrupt-names: + items: + - const: err1 + - const: err2 ++ minItems: 1 + + clocks: +- maxItems: 1 ++ minItems: 1 ++ maxItems: 2 ++ ++ clock-names: ++ items: ++ - const: per ++ - const: pixel ++ minItems: 1 + + phys: + maxItems: 1 +@@ -88,10 +98,43 @@ required: + - phys + - ports + - power-domains +- - resets + + additionalProperties: false + ++allOf: ++ - if: ++ properties: ++ compatible: ++ contains: ++ const: rockchip,rk3568-mipi-csi2 ++ then: ++ properties: ++ interrupts: ++ minItems: 2 ++ interrupt-names: ++ minItems: 2 ++ clocks: ++ maxItems: 1 ++ clock-names: ++ maxItems: 1 ++ required: ++ - resets ++ ++ - if: ++ properties: ++ compatible: ++ contains: ++ const: fsl,imx93-mipi-csi2 ++ then: ++ properties: ++ interrupts: ++ maxItems: 1 ++ interrupt-names: false ++ clocks: ++ minItems: 2 ++ clock-names: ++ minItems: 2 ++ + examples: + - | + #include diff --git a/target/linux/rockchip/patches-6.18/045-75-v7.1-media-synopsys-csi2rx-add-i.MX93-support.patch b/target/linux/rockchip/patches-6.18/045-75-v7.1-media-synopsys-csi2rx-add-i.MX93-support.patch new file mode 100644 index 00000000000..5bf90262b8f --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-75-v7.1-media-synopsys-csi2rx-add-i.MX93-support.patch @@ -0,0 +1,251 @@ +From ec40b431f0abe682db2e443b857d638cd3653330 Mon Sep 17 00:00:00 2001 +From: Frank Li +Date: Mon, 16 Feb 2026 14:18:44 -0500 +Subject: [PATCH] media: synopsys: csi2rx: add i.MX93 support + +The i.MX93 uses a newer version of the DW CSI-2 controller with a changed +register layout and an integrated Image Pixel Interface (IPI), which +converts the received CSI-2 packets from byte to pixel format and produces +a pixel data bus containing vertical and horizontal synchronization +information. + +The reset flow also differs, so add the .assert_reset(), .deassert_reset(), +and .idi_enable() callbacks to support it. + +Reviewed-by: Michael Riesch +Signed-off-by: Frank Li +[Sakari Ailus: include missing linux/bitfield.h.] +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +--- + .../media/platform/synopsys/dw-mipi-csi2rx.c | 160 +++++++++++++++++- + 1 file changed, 156 insertions(+), 4 deletions(-) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -7,6 +7,7 @@ + * Copyright (C) 2026 Collabora, Ltd. + */ + ++#include + #include + #include + #include +@@ -34,6 +35,22 @@ + #define DW_REG_EXIST BIT(31) + #define DW_REG(x) (DW_REG_EXIST | (x)) + ++#define DPHY_TEST_CTRL0_TEST_CLR BIT(0) ++ ++#define IPI_VCID_VC(x) FIELD_PREP(GENMASK(1, 0), (x)) ++#define IPI_VCID_VC_0_1(x) FIELD_PREP(GENMASK(3, 2), (x)) ++#define IPI_VCID_VC_2 BIT(4) ++ ++#define IPI_DATA_TYPE_DT(x) FIELD_PREP(GENMASK(5, 0), (x)) ++#define IPI_DATA_TYPE_EMB_DATA_EN BIT(8) ++ ++#define IPI_MODE_CONTROLLER BIT(1) ++#define IPI_MODE_COLOR_MODE16 BIT(8) ++#define IPI_MODE_CUT_THROUGH BIT(16) ++#define IPI_MODE_ENABLE BIT(24) ++ ++#define IPI_MEM_FLUSH_AUTO BIT(8) ++ + enum dw_mipi_csi2rx_regs_index { + DW_MIPI_CSI2RX_N_LANES, + DW_MIPI_CSI2RX_RESETN, +@@ -43,6 +60,16 @@ enum dw_mipi_csi2rx_regs_index { + DW_MIPI_CSI2RX_MSK1, + DW_MIPI_CSI2RX_MSK2, + DW_MIPI_CSI2RX_CONTROL, ++ /* imx93 (v150) new register */ ++ DW_MIPI_CSI2RX_DPHY_RSTZ, ++ DW_MIPI_CSI2RX_PHY_TST_CTRL0, ++ DW_MIPI_CSI2RX_PHY_TST_CTRL1, ++ DW_MIPI_CSI2RX_PHY_SHUTDOWNZ, ++ DW_MIPI_CSI2RX_IPI_DATATYPE, ++ DW_MIPI_CSI2RX_IPI_MEM_FLUSH, ++ DW_MIPI_CSI2RX_IPI_MODE, ++ DW_MIPI_CSI2RX_IPI_SOFTRSTN, ++ DW_MIPI_CSI2RX_IPI_VCID, + + DW_MIPI_CSI2RX_MAX, + }; +@@ -53,8 +80,13 @@ enum { + DW_MIPI_CSI2RX_PAD_MAX, + }; + ++struct dw_mipi_csi2rx_device; ++ + struct dw_mipi_csi2rx_drvdata { + const u32 *regs; ++ void (*dphy_assert_reset)(struct dw_mipi_csi2rx_device *csi2); ++ void (*dphy_deassert_reset)(struct dw_mipi_csi2rx_device *csi2); ++ void (*ipi_enable)(struct dw_mipi_csi2rx_device *csi2); + }; + + struct dw_mipi_csi2rx_format { +@@ -100,6 +132,21 @@ static const struct dw_mipi_csi2rx_drvda + .regs = rk3568_regs, + }; + ++static const u32 imx93_regs[DW_MIPI_CSI2RX_MAX] = { ++ [DW_MIPI_CSI2RX_N_LANES] = DW_REG(0x4), ++ [DW_MIPI_CSI2RX_RESETN] = DW_REG(0x8), ++ [DW_MIPI_CSI2RX_PHY_SHUTDOWNZ] = DW_REG(0x40), ++ [DW_MIPI_CSI2RX_DPHY_RSTZ] = DW_REG(0x44), ++ [DW_MIPI_CSI2RX_PHY_STATE] = DW_REG(0x48), ++ [DW_MIPI_CSI2RX_PHY_TST_CTRL0] = DW_REG(0x50), ++ [DW_MIPI_CSI2RX_PHY_TST_CTRL1] = DW_REG(0x54), ++ [DW_MIPI_CSI2RX_IPI_MODE] = DW_REG(0x80), ++ [DW_MIPI_CSI2RX_IPI_VCID] = DW_REG(0x84), ++ [DW_MIPI_CSI2RX_IPI_DATATYPE] = DW_REG(0x88), ++ [DW_MIPI_CSI2RX_IPI_MEM_FLUSH] = DW_REG(0x8c), ++ [DW_MIPI_CSI2RX_IPI_SOFTRSTN] = DW_REG(0xa0), ++}; ++ + static const struct v4l2_mbus_framefmt default_format = { + .width = 3840, + .height = 2160, +@@ -320,14 +367,32 @@ static int dw_mipi_csi2rx_start(struct d + return -EINVAL; + } + ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_RESETN, 0); ++ ++ if (csi2->drvdata->dphy_assert_reset) ++ csi2->drvdata->dphy_assert_reset(csi2); ++ + control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) | + SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03); + + dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_N_LANES, lanes - 1); +- dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_CONTROL, control); ++ ++ if (dw_mipi_csi2rx_has_reg(csi2, DW_MIPI_CSI2RX_CONTROL)) ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_CONTROL, control); ++ ++ ret = phy_power_on(csi2->phy); ++ if (ret) ++ return ret; ++ ++ if (csi2->drvdata->dphy_deassert_reset) ++ csi2->drvdata->dphy_deassert_reset(csi2); ++ + dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_RESETN, 1); + +- return phy_power_on(csi2->phy); ++ if (csi2->drvdata->ipi_enable) ++ csi2->drvdata->ipi_enable(csi2); ++ ++ return 0; + } + + static void dw_mipi_csi2rx_stop(struct dw_mipi_csi2rx_device *csi2) +@@ -335,8 +400,12 @@ static void dw_mipi_csi2rx_stop(struct d + phy_power_off(csi2->phy); + + dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_RESETN, 0); +- dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_MSK1, ~0); +- dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_MSK2, ~0); ++ ++ if (dw_mipi_csi2rx_has_reg(csi2, DW_MIPI_CSI2RX_MSK1)) ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_MSK1, ~0); ++ ++ if (dw_mipi_csi2rx_has_reg(csi2, DW_MIPI_CSI2RX_MSK2)) ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_MSK2, ~0); + } + + static const struct media_entity_operations dw_mipi_csi2rx_media_ops = { +@@ -686,8 +755,91 @@ static void dw_mipi_csi2rx_unregister(st + v4l2_async_nf_cleanup(&csi2->notifier); + } + ++static void imx93_csi2rx_dphy_assert_reset(struct dw_mipi_csi2rx_device *csi2) ++{ ++ u32 val; ++ ++ /* Release Synopsys DPHY test codes from reset */ ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_DPHY_RSTZ, 0); ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_PHY_SHUTDOWNZ, 0); ++ ++ val = dw_mipi_csi2rx_read(csi2, DW_MIPI_CSI2RX_PHY_TST_CTRL0); ++ val &= ~DPHY_TEST_CTRL0_TEST_CLR; ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_PHY_TST_CTRL0, val); ++ ++ val = dw_mipi_csi2rx_read(csi2, DW_MIPI_CSI2RX_PHY_TST_CTRL0); ++ /* Wait for at least 15ns */ ++ ndelay(15); ++ val |= DPHY_TEST_CTRL0_TEST_CLR; ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_PHY_TST_CTRL0, val); ++} ++ ++static void imx93_csi2rx_dphy_deassert_reset(struct dw_mipi_csi2rx_device *csi2) ++{ ++ /* Release PHY from reset */ ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_PHY_SHUTDOWNZ, 0x1); ++ /* ++ * ndelay() is not necessary have MMIO operation, need dummy read to ++ * ensure that the write operation above reaches its target. ++ */ ++ dw_mipi_csi2rx_read(csi2, DW_MIPI_CSI2RX_PHY_SHUTDOWNZ); ++ ndelay(5); ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_DPHY_RSTZ, 0x1); ++ ++ dw_mipi_csi2rx_read(csi2, DW_MIPI_CSI2RX_DPHY_RSTZ); ++ ndelay(5); ++} ++ ++static void imx93_csi2rx_dphy_ipi_enable(struct dw_mipi_csi2rx_device *csi2) ++{ ++ int dt = csi2->formats->csi_dt; ++ u32 val; ++ ++ /* Do IPI soft reset */ ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_IPI_SOFTRSTN, 0x0); ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_IPI_SOFTRSTN, 0x1); ++ ++ /* Select virtual channel and data type to be processed by IPI */ ++ val = IPI_DATA_TYPE_DT(dt); ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_IPI_DATATYPE, val); ++ ++ /* Set virtual channel 0 as default */ ++ val = IPI_VCID_VC(0); ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_IPI_VCID, val); ++ ++ /* ++ * Select IPI camera timing mode and allow the pixel stream ++ * to be non-continuous when pixel interface FIFO is empty ++ */ ++ val = dw_mipi_csi2rx_read(csi2, DW_MIPI_CSI2RX_IPI_MODE); ++ val &= ~IPI_MODE_CONTROLLER; ++ val &= ~IPI_MODE_COLOR_MODE16; ++ val |= IPI_MODE_CUT_THROUGH; ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_IPI_MODE, val); ++ ++ /* Memory is automatically flushed at each Frame Start */ ++ val = IPI_MEM_FLUSH_AUTO; ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_IPI_MEM_FLUSH, val); ++ ++ /* Enable IPI */ ++ val = dw_mipi_csi2rx_read(csi2, DW_MIPI_CSI2RX_IPI_MODE); ++ val |= IPI_MODE_ENABLE; ++ dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_IPI_MODE, val); ++} ++ ++static const struct dw_mipi_csi2rx_drvdata imx93_drvdata = { ++ .regs = imx93_regs, ++ .dphy_assert_reset = imx93_csi2rx_dphy_assert_reset, ++ .dphy_deassert_reset = imx93_csi2rx_dphy_deassert_reset, ++ .ipi_enable = imx93_csi2rx_dphy_ipi_enable, ++}; ++ + static const struct of_device_id dw_mipi_csi2rx_of_match[] = { + { ++ .compatible = "fsl,imx93-mipi-csi2", ++ .data = &imx93_drvdata, ++ }, ++ { + .compatible = "rockchip,rk3568-mipi-csi2", + .data = &rk3568_drvdata, + }, diff --git a/target/linux/rockchip/patches-6.18/045-76-v7.1-media-rockchip-rkcif-fix-off-by-one-bugs.patch b/target/linux/rockchip/patches-6.18/045-76-v7.1-media-rockchip-rkcif-fix-off-by-one-bugs.patch new file mode 100644 index 00000000000..50f4886a0cc --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-76-v7.1-media-rockchip-rkcif-fix-off-by-one-bugs.patch @@ -0,0 +1,50 @@ +From e4056b84af0fc18c84b4e5741df04ecd8ca17973 Mon Sep 17 00:00:00 2001 +From: Dan Carpenter +Date: Fri, 20 Feb 2026 09:15:07 +0100 +Subject: [PATCH] media: rockchip: rkcif: fix off by one bugs + +Change these comparisons from > vs >= to avoid accessing one element +beyond the end of the arrays. +While at it, use ARRAY_SIZE instead of the _MAX enum values. + +Fixes: 1f2353f5a1af ("media: rockchip: rkcif: add support for rk3568 vicap mipi capture") +Cc: stable@kernel.org +Signed-off-by: Dan Carpenter +Reviewed-by: Michael Riesch +Reviewed-by: Paul Elder +Reviewed-by: Laurent Pinchart +Tested-by: Chen-Yu Tsai +[fix cosmetic issues] +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +Signed-off-by: Mauro Carvalho Chehab +--- + .../media/platform/rockchip/rkcif/rkcif-capture-mipi.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +--- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +@@ -489,8 +489,8 @@ static inline unsigned int rkcif_mipi_ge + + block = interface->index - RKCIF_MIPI_BASE; + +- if (WARN_ON_ONCE(block > RKCIF_MIPI_MAX - RKCIF_MIPI_BASE) || +- WARN_ON_ONCE(index > RKCIF_MIPI_REGISTER_MAX)) ++ if (WARN_ON_ONCE(block >= ARRAY_SIZE(rkcif->match_data->mipi->blocks)) || ++ WARN_ON_ONCE(index >= ARRAY_SIZE(rkcif->match_data->mipi->regs))) + return RKCIF_REGISTER_NOTSUPPORTED; + + offset = rkcif->match_data->mipi->blocks[block].offset; +@@ -510,9 +510,9 @@ static inline unsigned int rkcif_mipi_id + block = stream->interface->index - RKCIF_MIPI_BASE; + id = stream->id; + +- if (WARN_ON_ONCE(block > RKCIF_MIPI_MAX - RKCIF_MIPI_BASE) || +- WARN_ON_ONCE(id > RKCIF_ID_MAX) || +- WARN_ON_ONCE(index > RKCIF_MIPI_ID_REGISTER_MAX)) ++ if (WARN_ON_ONCE(block >= ARRAY_SIZE(rkcif->match_data->mipi->blocks)) || ++ WARN_ON_ONCE(id >= ARRAY_SIZE(rkcif->match_data->mipi->regs_id)) || ++ WARN_ON_ONCE(index >= ARRAY_SIZE(rkcif->match_data->mipi->regs_id[id]))) + return RKCIF_REGISTER_NOTSUPPORTED; + + offset = rkcif->match_data->mipi->blocks[block].offset; diff --git a/target/linux/rockchip/patches-6.18/045-77-v7.1-media-rockchip-rkcif-comply-with-minimum-number-of-buffers.patch b/target/linux/rockchip/patches-6.18/045-77-v7.1-media-rockchip-rkcif-comply-with-minimum-number-of-buffers.patch new file mode 100644 index 00000000000..b95cc262079 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-77-v7.1-media-rockchip-rkcif-comply-with-minimum-number-of-buffers.patch @@ -0,0 +1,92 @@ +From 48c8292d4445088d8b3c9d639c7982744a94d819 Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 20 Feb 2026 09:15:08 +0100 +Subject: [PATCH] media: rockchip: rkcif: comply with minimum number of buffers + requirement + +Each stream requires CIF_REQ_BUFS_MIN=1 buffers to enable streaming. +However, it failed with only one buffer provided. + +Comply with the minimum number of buffers requirement and accept +exactly one buffer. + +Fixes: 501802e2ad51 ("media: rockchip: rkcif: add abstraction for dma blocks") +Cc: stable@kernel.org +Tested-by: Paul Elder +Tested-by: Chen-Yu Tsai +Signed-off-by: Michael Riesch +Reviewed-by: Isaac Scott +Reviewed-by: Paul Elder +Signed-off-by: Sakari Ailus +Signed-off-by: Mauro Carvalho Chehab +--- + .../platform/rockchip/rkcif/rkcif-stream.c | 44 +++++++++---------- + 1 file changed, 22 insertions(+), 22 deletions(-) + +--- a/drivers/media/platform/rockchip/rkcif/rkcif-stream.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-stream.c +@@ -106,42 +106,42 @@ static int rkcif_stream_init_buffers(str + { + struct v4l2_pix_format_mplane *pix = &stream->pix; + +- stream->buffers[0] = rkcif_stream_pop_buffer(stream); +- if (!stream->buffers[0]) +- goto err_buff_0; +- +- stream->buffers[1] = rkcif_stream_pop_buffer(stream); +- if (!stream->buffers[1]) +- goto err_buff_1; +- +- if (stream->queue_buffer) { +- stream->queue_buffer(stream, 0); +- stream->queue_buffer(stream, 1); +- } +- + stream->dummy.size = pix->num_planes * pix->plane_fmt[0].sizeimage; + stream->dummy.vaddr = + dma_alloc_attrs(stream->rkcif->dev, stream->dummy.size, + &stream->dummy.buffer.buff_addr[0], GFP_KERNEL, + DMA_ATTR_NO_KERNEL_MAPPING); + if (!stream->dummy.vaddr) +- goto err_dummy; ++ return -ENOMEM; + + for (unsigned int i = 1; i < pix->num_planes; i++) + stream->dummy.buffer.buff_addr[i] = + stream->dummy.buffer.buff_addr[i - 1] + + pix->plane_fmt[i - 1].bytesperline * pix->height; + ++ stream->buffers[0] = rkcif_stream_pop_buffer(stream); ++ if (!stream->buffers[0]) ++ goto err_dummy_free; ++ ++ stream->buffers[1] = rkcif_stream_pop_buffer(stream); ++ if (!stream->buffers[1]) { ++ stream->buffers[1] = &stream->dummy.buffer; ++ stream->buffers[1]->is_dummy = true; ++ } ++ ++ if (stream->queue_buffer) { ++ stream->queue_buffer(stream, 0); ++ stream->queue_buffer(stream, 1); ++ } ++ + return 0; + +-err_dummy: +- rkcif_stream_return_buffer(stream->buffers[1], VB2_BUF_STATE_QUEUED); +- stream->buffers[1] = NULL; +- +-err_buff_1: +- rkcif_stream_return_buffer(stream->buffers[0], VB2_BUF_STATE_QUEUED); +- stream->buffers[0] = NULL; +-err_buff_0: ++err_dummy_free: ++ dma_free_attrs(stream->rkcif->dev, stream->dummy.size, ++ stream->dummy.vaddr, ++ stream->dummy.buffer.buff_addr[0], ++ DMA_ATTR_NO_KERNEL_MAPPING); ++ stream->dummy.vaddr = NULL; + return -EINVAL; + } + diff --git a/target/linux/rockchip/patches-6.18/045-78-v7.1-media-rockchip-rkcif-Add-missing-MUST_CONNECT-flag-to-pads.patch b/target/linux/rockchip/patches-6.18/045-78-v7.1-media-rockchip-rkcif-Add-missing-MUST_CONNECT-flag-to-pads.patch new file mode 100644 index 00000000000..be793af83e4 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-78-v7.1-media-rockchip-rkcif-Add-missing-MUST_CONNECT-flag-to-pads.patch @@ -0,0 +1,61 @@ +From 8e3c751259dc2d1325838eff26f41032523c7b57 Mon Sep 17 00:00:00 2001 +From: Dang Huynh +Date: Thu, 29 Jan 2026 14:24:02 +0700 +Subject: [PATCH] media: rockchip: rkcif: Add missing MUST_CONNECT flag to pads + +The pads missed checks for connected devices which may a null dereference +when the stream is enabled. + +Unable to handle kernel NULL pointer dereference at virtual address +0000000000000020 +pc : rkcif_interface_enable_streams+0x48/0xf0 +lr : rkcif_interface_enable_streams+0x44/0xf0 +Call trace: + rkcif_interface_enable_streams+0x48/0xf0 + v4l2_subdev_enable_streams+0x26c/0x3f0 + rkcif_stream_start_streaming+0x140/0x278 + vb2_start_streaming+0x74/0x188 + vb2_core_streamon+0xe0/0x1d8 + vb2_ioctl_streamon+0x60/0xa8 + v4l_streamon+0x2c/0x40 + __video_do_ioctl+0x34c/0x400 + video_usercopy+0x2d0/0x800 + video_ioctl2+0x20/0x60 + v4l2_ioctl+0x48/0x78 + +Fixes: 501802e2ad51 ("media: rockchip: rkcif: add abstraction for dma blocks") +Fixes: 85411d17bee9 ("media: rockchip: rkcif: add abstraction for interface and crop blocks") +Cc: stable@vger.kernel.org +Signed-off-by: Dang Huynh +Reviewed-by: Michael Riesch +Reviewed-by: Laurent Pinchart +Signed-off-by: Sakari Ailus +Signed-off-by: Mauro Carvalho Chehab +--- + drivers/media/platform/rockchip/rkcif/rkcif-interface.c | 3 ++- + drivers/media/platform/rockchip/rkcif/rkcif-stream.c | 2 +- + 2 files changed, 3 insertions(+), 2 deletions(-) + +--- a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c +@@ -378,7 +378,8 @@ int rkcif_interface_register(struct rkci + snprintf(sd->name, sizeof(sd->name), "rkcif-mipi%d", + interface->index - RKCIF_MIPI_BASE); + +- pads[RKCIF_IF_PAD_SINK].flags = MEDIA_PAD_FL_SINK; ++ pads[RKCIF_IF_PAD_SINK].flags = MEDIA_PAD_FL_SINK | ++ MEDIA_PAD_FL_MUST_CONNECT; + pads[RKCIF_IF_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, RKCIF_IF_PAD_MAX, pads); + if (ret) +--- a/drivers/media/platform/rockchip/rkcif/rkcif-stream.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-stream.c +@@ -555,7 +555,7 @@ int rkcif_stream_register(struct rkcif_d + vdev->vfl_dir = VFL_DIR_RX; + video_set_drvdata(vdev, stream); + +- stream->pad.flags = MEDIA_PAD_FL_SINK; ++ stream->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + + stream->pix.height = CIF_MIN_HEIGHT; + stream->pix.width = CIF_MIN_WIDTH; diff --git a/target/linux/rockchip/patches-6.18/045-79-v7.2-media-synopsys-Fix-IPI-using-hardcoded-datatype.patch b/target/linux/rockchip/patches-6.18/045-79-v7.2-media-synopsys-Fix-IPI-using-hardcoded-datatype.patch new file mode 100644 index 00000000000..9112a958a8e --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-79-v7.2-media-synopsys-Fix-IPI-using-hardcoded-datatype.patch @@ -0,0 +1,62 @@ +From 8ba166ff7c921da610376156d7c0a5fc985fa983 Mon Sep 17 00:00:00 2001 +From: Guoniu Zhou +Date: Tue, 19 May 2026 10:07:38 +0800 +Subject: [PATCH] media: synopsys: Fix IPI using hardcoded datatype + +The imx93_csi2rx_dphy_ipi_enable() function configures the IPI datatype +using csi2->formats->csi_dt, which is initialized during probe but never +updated in set_fmt(). This causes the IPI to always use the probe-time +default datatype, ignoring the actual media bus format negotiated at +runtime. When userspace requests a different format, the IPI hardware is +configured with the wrong datatype, resulting in incorrect image output. + +Fix by updating csi2->formats in the set_fmt callback to reflect the +currently negotiated format, ensuring the IPI configuration matches the +runtime datatype. + +Fixes: ec40b431f0ab ("media: synopsys: csi2rx: add i.MX93 support") +Reviewed-by: Frank Li +Signed-off-by: Guoniu Zhou +Signed-off-by: Sakari Ailus +--- + drivers/media/platform/synopsys/dw-mipi-csi2rx.c | 15 +++++++++++++-- + 1 file changed, 13 insertions(+), 2 deletions(-) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -311,7 +311,7 @@ dw_mipi_csi2rx_find_format(struct dw_mip + WARN_ON(csi2->formats_num == 0); + + for (unsigned int i = 0; i < csi2->formats_num; i++) { +- const struct dw_mipi_csi2rx_format *format = &csi2->formats[i]; ++ const struct dw_mipi_csi2rx_format *format = &formats[i]; + + if (format->code == mbus_code) + return format; +@@ -433,7 +433,7 @@ dw_mipi_csi2rx_enum_mbus_code(struct v4l + if (code->index >= csi2->formats_num) + return -EINVAL; + +- code->code = csi2->formats[code->index].code; ++ code->code = formats[code->index].code; + return 0; + default: + return -EINVAL; +@@ -470,6 +470,17 @@ static int dw_mipi_csi2rx_set_fmt(struct + + *src = *sink; + ++ /* Store the CSIS format descriptor for active formats. */ ++ if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { ++ csi2->formats = fmt ? : ++ dw_mipi_csi2rx_find_format(csi2, default_format.code); ++ ++ if (!csi2->formats) { ++ dev_err(csi2->dev, "Failed to find valid format\n"); ++ return -EINVAL; ++ } ++ } ++ + return 0; + } + diff --git a/target/linux/rockchip/patches-6.18/045-80-v7.2-media-synopsys-Add-support-for-RAW16-Bayer-formats.patch b/target/linux/rockchip/patches-6.18/045-80-v7.2-media-synopsys-Add-support-for-RAW16-Bayer-formats.patch new file mode 100644 index 00000000000..e1329446be5 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-80-v7.2-media-synopsys-Add-support-for-RAW16-Bayer-formats.patch @@ -0,0 +1,44 @@ +From 4a7004c03d2227643c2ef2e546ad173b2b1480b7 Mon Sep 17 00:00:00 2001 +From: Guoniu Zhou +Date: Tue, 19 May 2026 10:07:39 +0800 +Subject: [PATCH] media: synopsys: Add support for RAW16 Bayer formats + +Add higher bit-depth raw image data support for the sensors, which supports +16-bit output. + +Reviewed-by: Frank Li +Signed-off-by: Guoniu Zhou +Signed-off-by: Sakari Ailus +--- + .../media/platform/synopsys/dw-mipi-csi2rx.c | 20 +++++++++++++++++++ + 1 file changed, 20 insertions(+) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -252,6 +252,26 @@ static const struct dw_mipi_csi2rx_forma + .depth = 12, + .csi_dt = MIPI_CSI2_DT_RAW12, + }, ++ { ++ .code = MEDIA_BUS_FMT_SBGGR16_1X16, ++ .depth = 16, ++ .csi_dt = MIPI_CSI2_DT_RAW16, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SGBRG16_1X16, ++ .depth = 16, ++ .csi_dt = MIPI_CSI2_DT_RAW16, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SGRBG16_1X16, ++ .depth = 16, ++ .csi_dt = MIPI_CSI2_DT_RAW16, ++ }, ++ { ++ .code = MEDIA_BUS_FMT_SRGGB16_1X16, ++ .depth = 16, ++ .csi_dt = MIPI_CSI2_DT_RAW16, ++ }, + }; + + static inline struct dw_mipi_csi2rx_device *to_csi2(struct v4l2_subdev *sd) diff --git a/target/linux/rockchip/patches-6.18/045-81-v7.2-media-synopsys-Add-support-for-multiple-streams.patch b/target/linux/rockchip/patches-6.18/045-81-v7.2-media-synopsys-Add-support-for-multiple-streams.patch new file mode 100644 index 00000000000..c498d4bb53b --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-81-v7.2-media-synopsys-Add-support-for-multiple-streams.patch @@ -0,0 +1,91 @@ +From 9823c6bd80082906e2b1d0ca03f470ff395b5557 Mon Sep 17 00:00:00 2001 +From: Guoniu Zhou +Date: Tue, 19 May 2026 10:07:40 +0800 +Subject: [PATCH] media: synopsys: Add support for multiple streams + +The current driver only supports single stream operation. Add support +for multiple concurrent streams by tracking enabled streams with a +bitmask and only initializing the hardware once for the first stream. + +This enables use cases such as surround view systems where multiple +camera streams need to be processed simultaneously through the same +CSI-2 receiver interface. + +Reviewed-by: Frank Li +Signed-off-by: Guoniu Zhou +Signed-off-by: Sakari Ailus +--- + .../media/platform/synopsys/dw-mipi-csi2rx.c | 35 +++++++++++++------ + 1 file changed, 24 insertions(+), 11 deletions(-) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -113,6 +113,7 @@ struct dw_mipi_csi2rx_device { + + enum v4l2_mbus_type bus_type; + u32 lanes_num; ++ u64 enabled_streams; + + const struct dw_mipi_csi2rx_drvdata *drvdata; + }; +@@ -539,26 +540,33 @@ static int dw_mipi_csi2rx_enable_streams + DW_MIPI_CSI2RX_PAD_SRC, + &streams_mask); + +- ret = pm_runtime_resume_and_get(dev); +- if (ret) +- goto err; ++ if (!csi2->enabled_streams) { ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) ++ goto err; + +- ret = dw_mipi_csi2rx_start(csi2); +- if (ret) { +- dev_err(dev, "failed to enable CSI hardware\n"); +- goto err_pm_runtime_put; ++ ret = dw_mipi_csi2rx_start(csi2); ++ if (ret) { ++ dev_err(dev, "failed to enable CSI hardware\n"); ++ goto err_pm_runtime_put; ++ } + } + + ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask); + if (ret) + goto err_csi_stop; + ++ csi2->enabled_streams |= streams_mask; ++ + return 0; + + err_csi_stop: +- dw_mipi_csi2rx_stop(csi2); ++ /* Stop CSI hardware if no streams are enabled */ ++ if (!csi2->enabled_streams) ++ dw_mipi_csi2rx_stop(csi2); + err_pm_runtime_put: +- pm_runtime_put(dev); ++ if (!csi2->enabled_streams) ++ pm_runtime_put(dev); + err: + return ret; + } +@@ -583,10 +591,15 @@ static int dw_mipi_csi2rx_disable_stream + &streams_mask); + + ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); ++ if (ret) ++ dev_err(dev, "failed to disable streams on remote subdev: %d\n", ret); + +- dw_mipi_csi2rx_stop(csi2); ++ csi2->enabled_streams &= ~streams_mask; + +- pm_runtime_put(dev); ++ if (!csi2->enabled_streams) { ++ dw_mipi_csi2rx_stop(csi2); ++ pm_runtime_put(dev); ++ } + + return ret; + } diff --git a/target/linux/rockchip/patches-6.18/045-82-v7.2-media-synopsys-Add-PHY-stopstate-wait-for-i.MX93.patch b/target/linux/rockchip/patches-6.18/045-82-v7.2-media-synopsys-Add-PHY-stopstate-wait-for-i.MX93.patch new file mode 100644 index 00000000000..5fac0240159 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-82-v7.2-media-synopsys-Add-PHY-stopstate-wait-for-i.MX93.patch @@ -0,0 +1,113 @@ +From 82cdee9be284af7f3aa2b2ba1fa61c410642cac4 Mon Sep 17 00:00:00 2001 +From: Guoniu Zhou +Date: Tue, 19 May 2026 10:07:41 +0800 +Subject: [PATCH] media: synopsys: Add PHY stopstate wait for i.MX93 + +Implement waiting for D-PHY lanes to enter stop state on i.MX93. This +ensures proper PHY initialization by verifying that the clock lane and +all active data lanes have entered the stop state before proceeding with +further operations. + +Reviewed-by: Frank Li +Signed-off-by: Guoniu Zhou +Signed-off-by: Sakari Ailus +--- + .../media/platform/synopsys/dw-mipi-csi2rx.c | 36 +++++++++++++++++++ + 1 file changed, 36 insertions(+) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -35,6 +36,8 @@ + #define DW_REG_EXIST BIT(31) + #define DW_REG(x) (DW_REG_EXIST | (x)) + ++#define DPHY_STOPSTATE_CLK_LANE BIT(16) ++ + #define DPHY_TEST_CTRL0_TEST_CLR BIT(0) + + #define IPI_VCID_VC(x) FIELD_PREP(GENMASK(1, 0), (x)) +@@ -65,6 +68,7 @@ enum dw_mipi_csi2rx_regs_index { + DW_MIPI_CSI2RX_PHY_TST_CTRL0, + DW_MIPI_CSI2RX_PHY_TST_CTRL1, + DW_MIPI_CSI2RX_PHY_SHUTDOWNZ, ++ DW_MIPI_CSI2RX_PHY_STOPSTATE, + DW_MIPI_CSI2RX_IPI_DATATYPE, + DW_MIPI_CSI2RX_IPI_MEM_FLUSH, + DW_MIPI_CSI2RX_IPI_MODE, +@@ -87,6 +91,7 @@ struct dw_mipi_csi2rx_drvdata { + void (*dphy_assert_reset)(struct dw_mipi_csi2rx_device *csi2); + void (*dphy_deassert_reset)(struct dw_mipi_csi2rx_device *csi2); + void (*ipi_enable)(struct dw_mipi_csi2rx_device *csi2); ++ int (*wait_for_phy_stopstate)(struct dw_mipi_csi2rx_device *csi2); + }; + + struct dw_mipi_csi2rx_format { +@@ -139,6 +144,7 @@ static const u32 imx93_regs[DW_MIPI_CSI2 + [DW_MIPI_CSI2RX_PHY_SHUTDOWNZ] = DW_REG(0x40), + [DW_MIPI_CSI2RX_DPHY_RSTZ] = DW_REG(0x44), + [DW_MIPI_CSI2RX_PHY_STATE] = DW_REG(0x48), ++ [DW_MIPI_CSI2RX_PHY_STOPSTATE] = DW_REG(0x4c), + [DW_MIPI_CSI2RX_PHY_TST_CTRL0] = DW_REG(0x50), + [DW_MIPI_CSI2RX_PHY_TST_CTRL1] = DW_REG(0x54), + [DW_MIPI_CSI2RX_IPI_MODE] = DW_REG(0x80), +@@ -556,10 +562,19 @@ static int dw_mipi_csi2rx_enable_streams + if (ret) + goto err_csi_stop; + ++ if (!csi2->enabled_streams && ++ csi2->drvdata->wait_for_phy_stopstate) { ++ ret = csi2->drvdata->wait_for_phy_stopstate(csi2); ++ if (ret) ++ goto err_disable_streams; ++ } ++ + csi2->enabled_streams |= streams_mask; + + return 0; + ++err_disable_streams: ++ v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); + err_csi_stop: + /* Stop CSI hardware if no streams are enabled */ + if (!csi2->enabled_streams) +@@ -871,11 +886,32 @@ static void imx93_csi2rx_dphy_ipi_enable + dw_mipi_csi2rx_write(csi2, DW_MIPI_CSI2RX_IPI_MODE, val); + } + ++static int imx93_csi2rx_wait_for_phy_stopstate(struct dw_mipi_csi2rx_device *csi2) ++{ ++ struct device *dev = csi2->dev; ++ u32 stopstate_mask; ++ u32 val; ++ int ret; ++ ++ stopstate_mask = DPHY_STOPSTATE_CLK_LANE | GENMASK(csi2->lanes_num - 1, 0); ++ ++ ret = read_poll_timeout(dw_mipi_csi2rx_read, val, ++ (val & stopstate_mask) == stopstate_mask, ++ 10, 1000, true, ++ csi2, DW_MIPI_CSI2RX_PHY_STOPSTATE); ++ if (ret) ++ dev_err(dev, "lanes are not in stop state: %#x, expected %#x\n", ++ val, stopstate_mask); ++ ++ return ret; ++} ++ + static const struct dw_mipi_csi2rx_drvdata imx93_drvdata = { + .regs = imx93_regs, + .dphy_assert_reset = imx93_csi2rx_dphy_assert_reset, + .dphy_deassert_reset = imx93_csi2rx_dphy_deassert_reset, + .ipi_enable = imx93_csi2rx_dphy_ipi_enable, ++ .wait_for_phy_stopstate = imx93_csi2rx_wait_for_phy_stopstate, + }; + + static const struct of_device_id dw_mipi_csi2rx_of_match[] = { diff --git a/target/linux/rockchip/patches-6.18/045-83-v7.2-media-synopsys-Add-support-for-i.MX95.patch b/target/linux/rockchip/patches-6.18/045-83-v7.2-media-synopsys-Add-support-for-i.MX95.patch new file mode 100644 index 00000000000..cacd7ae2abf --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-83-v7.2-media-synopsys-Add-support-for-i.MX95.patch @@ -0,0 +1,63 @@ +From af92c6d86b2bc3849935dc6ecfe78eba016a47a4 Mon Sep 17 00:00:00 2001 +From: Guoniu Zhou +Date: Tue, 19 May 2026 10:07:43 +0800 +Subject: [PATCH] media: synopsys: Add support for i.MX95 + +Add support for the i.MX95 MIPI CSI-2 receiver. The i.MX95 variant is +nearly identical to i.MX93, with the main difference being the use of +IDI (Image Data Interface) instead of IPI (Image Pixel Interface). +However, the IDI interface is transparent to software, requiring only +a different register map definition while sharing the same PHY control +functions with i.MX93. + +Reviewed-by: Frank Li +Signed-off-by: Guoniu Zhou +Signed-off-by: Sakari Ailus +--- + .../media/platform/synopsys/dw-mipi-csi2rx.c | 22 +++++++++++++++++++ + 1 file changed, 22 insertions(+) + +--- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c ++++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c +@@ -154,6 +154,17 @@ static const u32 imx93_regs[DW_MIPI_CSI2 + [DW_MIPI_CSI2RX_IPI_SOFTRSTN] = DW_REG(0xa0), + }; + ++static const u32 imx95_regs[DW_MIPI_CSI2RX_MAX] = { ++ [DW_MIPI_CSI2RX_N_LANES] = DW_REG(0x4), ++ [DW_MIPI_CSI2RX_RESETN] = DW_REG(0x8), ++ [DW_MIPI_CSI2RX_PHY_SHUTDOWNZ] = DW_REG(0x40), ++ [DW_MIPI_CSI2RX_DPHY_RSTZ] = DW_REG(0x44), ++ [DW_MIPI_CSI2RX_PHY_STATE] = DW_REG(0x48), ++ [DW_MIPI_CSI2RX_PHY_STOPSTATE] = DW_REG(0x4c), ++ [DW_MIPI_CSI2RX_PHY_TST_CTRL0] = DW_REG(0x50), ++ [DW_MIPI_CSI2RX_PHY_TST_CTRL1] = DW_REG(0x54), ++}; ++ + static const struct v4l2_mbus_framefmt default_format = { + .width = 3840, + .height = 2160, +@@ -914,12 +925,23 @@ static const struct dw_mipi_csi2rx_drvda + .wait_for_phy_stopstate = imx93_csi2rx_wait_for_phy_stopstate, + }; + ++static const struct dw_mipi_csi2rx_drvdata imx95_drvdata = { ++ .regs = imx95_regs, ++ .dphy_assert_reset = imx93_csi2rx_dphy_assert_reset, ++ .dphy_deassert_reset = imx93_csi2rx_dphy_deassert_reset, ++ .wait_for_phy_stopstate = imx93_csi2rx_wait_for_phy_stopstate, ++}; ++ + static const struct of_device_id dw_mipi_csi2rx_of_match[] = { + { + .compatible = "fsl,imx93-mipi-csi2", + .data = &imx93_drvdata, + }, + { ++ .compatible = "fsl,imx95-mipi-csi2", ++ .data = &imx95_drvdata, ++ }, ++ { + .compatible = "rockchip,rk3568-mipi-csi2", + .data = &rk3568_drvdata, + }, diff --git a/target/linux/rockchip/patches-6.18/045-84-v7.2-media-dt-bindings-add-rockchip-rk3588-vicap.patch b/target/linux/rockchip/patches-6.18/045-84-v7.2-media-dt-bindings-add-rockchip-rk3588-vicap.patch new file mode 100644 index 00000000000..d0d142b4cd2 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-84-v7.2-media-dt-bindings-add-rockchip-rk3588-vicap.patch @@ -0,0 +1,250 @@ +From 4a6b7bf1de948251ca5f09ca1a632f036e52085c Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 22 May 2026 23:23:08 +0200 +Subject: [PATCH] media: dt-bindings: add rockchip rk3588 vicap + +Add documentation for the Rockchip RK3588 Video Capture (VICAP) unit. To +that end, make the existing rockchip,rk3568-vicap documentation more +general and introduce variant specific constraints. + +Acked-by: Conor Dooley +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +--- + .../bindings/media/rockchip,rk3568-vicap.yaml | 173 ++++++++++++++++-- + 1 file changed, 154 insertions(+), 19 deletions(-) + +--- a/Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml ++++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml +@@ -15,9 +15,15 @@ description: + the data from camera sensors, video decoders, or other companion ICs and + transfers it into system main memory by AXI bus. + ++ The Rockchip RK3588 Video Capture (VICAP) is similar to its RK3568 ++ counterpart, but features six MIPI CSI-2 ports and additional connections ++ to the image signal processor (ISP) blocks. ++ + properties: + compatible: +- const: rockchip,rk3568-vicap ++ enum: ++ - rockchip,rk3568-vicap ++ - rockchip,rk3588-vicap + + reg: + maxItems: 1 +@@ -26,11 +32,8 @@ properties: + maxItems: 1 + + clocks: +- items: +- - description: ACLK +- - description: HCLK +- - description: DCLK +- - description: ICLK ++ minItems: 4 ++ maxItems: 5 + + clock-names: + items: +@@ -38,25 +41,19 @@ properties: + - const: hclk + - const: dclk + - const: iclk ++ - const: iclk1 ++ minItems: 4 + + iommus: + maxItems: 1 + + resets: +- items: +- - description: ARST +- - description: HRST +- - description: DRST +- - description: PRST +- - description: IRST ++ minItems: 5 ++ maxItems: 9 + + reset-names: +- items: +- - const: arst +- - const: hrst +- - const: drst +- - const: prst +- - const: irst ++ minItems: 5 ++ maxItems: 9 + + rockchip,grf: + $ref: /schemas/types.yaml#/definitions/phandle +@@ -67,8 +64,15 @@ properties: + + ports: + $ref: /schemas/graph.yaml#/properties/ports ++ additionalProperties: false + + properties: ++ "#address-cells": ++ const: 1 ++ ++ "#size-cells": ++ const: 0 ++ + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false +@@ -100,13 +104,75 @@ properties: + + port@1: + $ref: /schemas/graph.yaml#/properties/port +- description: Port connected to the MIPI CSI-2 receiver output. ++ description: Port connected to the MIPI CSI-2 receiver 0 output. ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++ port@2: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: Port connected to the MIPI CSI-2 receiver 1 output. ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++ port@3: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: Port connected to the MIPI CSI-2 receiver 2 output. ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++ port@4: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: Port connected to the MIPI CSI-2 receiver 3 output. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + ++ port@5: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: Port connected to the MIPI CSI-2 receiver 4 output. ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++ port@6: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: Port connected to the MIPI CSI-2 receiver 5 output. ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++ port@10: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: Port connected to the ISP0 input. ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false ++ ++ port@11: ++ $ref: /schemas/graph.yaml#/properties/port ++ description: Port connected to the ISP1 input. ++ ++ properties: ++ endpoint: ++ $ref: video-interfaces.yaml# ++ unevaluatedProperties: false + required: + - compatible + - reg +@@ -114,6 +180,75 @@ required: + - clocks + - ports + ++allOf: ++ - if: ++ properties: ++ compatible: ++ contains: ++ const: rockchip,rk3568-vicap ++ then: ++ properties: ++ clocks: ++ maxItems: 4 ++ ++ clock-names: ++ maxItems: 4 ++ ++ resets: ++ maxItems: 5 ++ ++ reset-names: ++ items: ++ - const: arst ++ - const: hrst ++ - const: drst ++ - const: prst ++ - const: irst ++ ++ ports: ++ properties: ++ port@2: false ++ ++ port@3: false ++ ++ port@4: false ++ ++ port@5: false ++ ++ port@6: false ++ ++ port@10: false ++ ++ port@11: false ++ ++ - if: ++ properties: ++ compatible: ++ contains: ++ const: rockchip,rk3588-vicap ++ then: ++ properties: ++ clocks: ++ minItems: 5 ++ ++ clock-names: ++ minItems: 5 ++ ++ resets: ++ minItems: 9 ++ ++ reset-names: ++ items: ++ - const: arst ++ - const: hrst ++ - const: drst ++ - const: irst0 ++ - const: irst1 ++ - const: irst2 ++ - const: irst3 ++ - const: irst4 ++ - const: irst5 ++ + additionalProperties: false + + examples: diff --git a/target/linux/rockchip/patches-6.18/045-85-v7.2-media-rockchip-rkcif-add-support-for-rk3588-vicap-mipi-cap.patch b/target/linux/rockchip/patches-6.18/045-85-v7.2-media-rockchip-rkcif-add-support-for-rk3588-vicap-mipi-cap.patch new file mode 100644 index 00000000000..f9273d5ac9a --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-85-v7.2-media-rockchip-rkcif-add-support-for-rk3588-vicap-mipi-cap.patch @@ -0,0 +1,253 @@ +From 2175323fdf820ebf2d861283d1de7ef394b048c5 Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 22 May 2026 23:23:09 +0200 +Subject: [PATCH] media: rockchip: rkcif: add support for rk3588 vicap mipi + capture + +The RK3588 Video Capture (VICAP) unit features a Digital Video Port (DVP) +and six MIPI CSI-2 capture interfaces. Add initial support for this variant +to the rkcif driver and enable the MIPI CSI-2 capture interfaces. + +Reviewed-by: Mehdi Djait +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +--- + .../rockchip/rkcif/rkcif-capture-mipi.c | 148 +++++++++++++++++- + .../rockchip/rkcif/rkcif-capture-mipi.h | 1 + + .../platform/rockchip/rkcif/rkcif-common.h | 2 +- + .../media/platform/rockchip/rkcif/rkcif-dev.c | 18 +++ + 4 files changed, 163 insertions(+), 6 deletions(-) + +--- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c +@@ -30,6 +30,14 @@ + #define RK3568_MIPI_CTRL0_CROP_EN BIT(5) + #define RK3568_MIPI_CTRL0_WRDDR(type) ((type) << 1) + ++#define RK3588_MIPI_CTRL0_DMA_EN BIT(28) ++#define RK3588_MIPI_CTRL0_HIGH_ALIGN BIT(27) ++#define RK3588_MIPI_CTRL0_WRDDR(type) ((type) << 5) ++#define RK3588_MIPI_CTRL0_CROP_EN BIT(4) ++#define RK3588_MIPI_CTRL0_PARSE(type) ((type) << 1) ++ ++#define RK3588_MIPI_CTRL_CAP_EN BIT(0) ++ + #define RKCIF_MIPI_CTRL0_DT_ID(id) ((id) << 10) + #define RKCIF_MIPI_CTRL0_VC_ID(id) ((id) << 8) + #define RKCIF_MIPI_CTRL0_CAP_EN BIT(0) +@@ -375,11 +383,8 @@ static u32 + rkcif_rk3568_mipi_ctrl0(struct rkcif_stream *stream, + const struct rkcif_output_fmt *active_out_fmt) + { +- u32 ctrl0 = 0; +- +- ctrl0 |= RKCIF_MIPI_CTRL0_DT_ID(active_out_fmt->mipi.dt); +- ctrl0 |= RKCIF_MIPI_CTRL0_CAP_EN; +- ctrl0 |= RK3568_MIPI_CTRL0_CROP_EN; ++ u32 ctrl0 = RKCIF_MIPI_CTRL0_DT_ID(active_out_fmt->mipi.dt) | ++ RKCIF_MIPI_CTRL0_CAP_EN | RK3568_MIPI_CTRL0_CROP_EN; + + if (active_out_fmt->mipi.compact) + ctrl0 |= RK3568_MIPI_CTRL0_COMPACT_EN; +@@ -481,6 +486,132 @@ const struct rkcif_mipi_match_data rkcif + }, + }; + ++static u32 ++rkcif_rk3588_mipi_ctrl0(struct rkcif_stream *stream, ++ const struct rkcif_output_fmt *active_out_fmt) ++{ ++ u32 ctrl0 = 0; ++ ++ ctrl0 |= RK3588_MIPI_CTRL0_DMA_EN; ++ ctrl0 |= RKCIF_MIPI_CTRL0_DT_ID(active_out_fmt->mipi.dt); ++ ctrl0 |= RK3588_MIPI_CTRL0_CROP_EN; ++ ctrl0 |= RKCIF_MIPI_CTRL0_CAP_EN; ++ ++ switch (active_out_fmt->mipi.type) { ++ case RKCIF_MIPI_TYPE_RAW8: ++ break; ++ case RKCIF_MIPI_TYPE_RAW10: ++ ctrl0 |= RK3588_MIPI_CTRL0_PARSE(0x1); ++ if (!active_out_fmt->mipi.compact) ++ ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x1); ++ break; ++ case RKCIF_MIPI_TYPE_RAW12: ++ ctrl0 |= RK3588_MIPI_CTRL0_PARSE(0x2); ++ if (!active_out_fmt->mipi.compact) ++ ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x1); ++ break; ++ case RKCIF_MIPI_TYPE_RGB888: ++ break; ++ case RKCIF_MIPI_TYPE_YUV422SP: ++ ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x4); ++ break; ++ case RKCIF_MIPI_TYPE_YUV420SP: ++ ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x5); ++ break; ++ case RKCIF_MIPI_TYPE_YUV400: ++ ctrl0 |= RK3588_MIPI_CTRL0_WRDDR(0x3); ++ break; ++ default: ++ break; ++ } ++ ++ return ctrl0; ++} ++ ++const struct rkcif_mipi_match_data rkcif_rk3588_vicap_mipi_match_data = { ++ .mipi_num = 6, ++ .mipi_ctrl0 = rkcif_rk3588_mipi_ctrl0, ++ .regs = { ++ [RKCIF_MIPI_CTRL] = 0x20, ++ [RKCIF_MIPI_INTEN] = 0x74, ++ [RKCIF_MIPI_INTSTAT] = 0x78, ++ }, ++ .regs_id = { ++ [RKCIF_ID0] = { ++ [RKCIF_MIPI_CTRL0] = 0x00, ++ [RKCIF_MIPI_CTRL1] = 0x04, ++ [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x24, ++ [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x2c, ++ [RKCIF_MIPI_FRAME0_VLW_Y] = 0x34, ++ [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x28, ++ [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x30, ++ [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_CROP_START] = 0x8c, ++ }, ++ [RKCIF_ID1] = { ++ [RKCIF_MIPI_CTRL0] = 0x08, ++ [RKCIF_MIPI_CTRL1] = 0x0c, ++ [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x38, ++ [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x40, ++ [RKCIF_MIPI_FRAME0_VLW_Y] = 0x48, ++ [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x3c, ++ [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x44, ++ [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_CROP_START] = 0x90, ++ }, ++ [RKCIF_ID2] = { ++ [RKCIF_MIPI_CTRL0] = 0x10, ++ [RKCIF_MIPI_CTRL1] = 0x14, ++ [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x4c, ++ [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x54, ++ [RKCIF_MIPI_FRAME0_VLW_Y] = 0x5c, ++ [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x50, ++ [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x58, ++ [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_CROP_START] = 0x94, ++ }, ++ [RKCIF_ID3] = { ++ [RKCIF_MIPI_CTRL0] = 0x18, ++ [RKCIF_MIPI_CTRL1] = 0x1c, ++ [RKCIF_MIPI_FRAME0_ADDR_Y] = 0x60, ++ [RKCIF_MIPI_FRAME0_ADDR_UV] = 0x68, ++ [RKCIF_MIPI_FRAME0_VLW_Y] = 0x70, ++ [RKCIF_MIPI_FRAME0_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_FRAME1_ADDR_Y] = 0x64, ++ [RKCIF_MIPI_FRAME1_ADDR_UV] = 0x6c, ++ [RKCIF_MIPI_FRAME1_VLW_Y] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_FRAME1_VLW_UV] = RKCIF_REGISTER_NOTSUPPORTED, ++ [RKCIF_MIPI_CROP_START] = 0x98, ++ }, ++ }, ++ .blocks = { ++ { ++ .offset = 0x100, ++ }, ++ { ++ .offset = 0x200, ++ }, ++ { ++ .offset = 0x300, ++ }, ++ { ++ .offset = 0x400, ++ }, ++ { ++ .offset = 0x500, ++ }, ++ { ++ .offset = 0x600, ++ }, ++ }, ++}; ++ + static inline unsigned int rkcif_mipi_get_reg(struct rkcif_interface *interface, + unsigned int index) + { +@@ -631,6 +762,13 @@ static int rkcif_mipi_start_streaming(st + rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL1, ctrl1); + rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL0, ctrl0); + ++ /* ++ * TODO: This bit has a different meaning on the RK3568, but it is ++ * set there by default anyway. While correct, this is not exactly ++ * nice and shall be reworked during the next refactoring. ++ */ ++ rkcif_mipi_write(interface, RKCIF_MIPI_CTRL, RK3588_MIPI_CTRL_CAP_EN); ++ + ret = 0; + + out: +--- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h +@@ -13,6 +13,7 @@ + #include "rkcif-common.h" + + extern const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data; ++extern const struct rkcif_mipi_match_data rkcif_rk3588_vicap_mipi_match_data; + + int rkcif_mipi_register(struct rkcif_device *rkcif); + +--- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h +@@ -27,7 +27,7 @@ + #include "rkcif-regs.h" + + #define RKCIF_DRIVER_NAME "rockchip-cif" +-#define RKCIF_CLK_MAX 4 ++#define RKCIF_CLK_MAX 5 + + enum rkcif_format_type { + RKCIF_FMT_TYPE_INVALID, +--- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c ++++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c +@@ -53,6 +53,20 @@ static const struct rkcif_match_data rk3 + .mipi = &rkcif_rk3568_vicap_mipi_match_data, + }; + ++static const char *const rk3588_vicap_clks[] = { ++ "aclk", ++ "hclk", ++ "dclk", ++ "iclk", ++ "iclk1", ++}; ++ ++static const struct rkcif_match_data rk3588_vicap_match_data = { ++ .clks = rk3588_vicap_clks, ++ .clks_num = ARRAY_SIZE(rk3588_vicap_clks), ++ .mipi = &rkcif_rk3588_vicap_mipi_match_data, ++}; ++ + static const struct of_device_id rkcif_plat_of_match[] = { + { + .compatible = "rockchip,px30-vip", +@@ -62,6 +76,10 @@ static const struct of_device_id rkcif_p + .compatible = "rockchip,rk3568-vicap", + .data = &rk3568_vicap_match_data, + }, ++ { ++ .compatible = "rockchip,rk3588-vicap", ++ .data = &rk3588_vicap_match_data, ++ }, + {} + }; + MODULE_DEVICE_TABLE(of, rkcif_plat_of_match); diff --git a/target/linux/rockchip/patches-6.18/045-86-v7.2-media-dt-bindings-rockchip-rk3568-mipi-csi2-add-rk3588-com.patch b/target/linux/rockchip/patches-6.18/045-86-v7.2-media-dt-bindings-rockchip-rk3568-mipi-csi2-add-rk3588-com.patch new file mode 100644 index 00000000000..60e95340795 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/045-86-v7.2-media-dt-bindings-rockchip-rk3568-mipi-csi2-add-rk3588-com.patch @@ -0,0 +1,37 @@ +From 658810422076140f9a003a174f73b0eeaffa0611 Mon Sep 17 00:00:00 2001 +From: Michael Riesch +Date: Fri, 27 Mar 2026 00:10:00 +0100 +Subject: [PATCH] media: dt-bindings: rockchip,rk3568-mipi-csi2: add rk3588 + compatible + +The RK3588 MIPI CSI-2 receivers are compatible to the ones found in the +RK3568. Introduce a list of compatible variants and add the RK3588 variant +to it. + +Acked-by: Rob Herring (Arm) +Signed-off-by: Michael Riesch +Signed-off-by: Sakari Ailus +--- + .../bindings/media/rockchip,rk3568-mipi-csi2.yaml | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +--- a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml ++++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml +@@ -16,9 +16,14 @@ description: + + properties: + compatible: +- enum: +- - fsl,imx93-mipi-csi2 +- - rockchip,rk3568-mipi-csi2 ++ oneOf: ++ - enum: ++ - fsl,imx93-mipi-csi2 ++ - rockchip,rk3568-mipi-csi2 ++ - items: ++ - enum: ++ - rockchip,rk3588-mipi-csi2 ++ - const: rockchip,rk3568-mipi-csi2 + + reg: + maxItems: 1