* [PATCH v3 0/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver
@ 2026-01-13 21:09 Michael Riesch via B4 Relay
2026-01-13 21:09 ` [PATCH v3 1/3] media: dt-bindings: add " Michael Riesch via B4 Relay
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Michael Riesch via B4 Relay @ 2026-01-13 21:09 UTC (permalink / raw)
To: Michael Riesch, Mehdi Djait, Bryan O'Donoghue,
Laurent Pinchart, Hans Verkuil, Mauro Carvalho Chehab,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Philipp Zabel, Sebastian Reichel, Nicolas Dufresne,
Collabora Kernel Team, Sakari Ailus
Cc: linux-media, devicetree, linux-arm-kernel, linux-rockchip,
linux-kernel, Michael Riesch
Habidere,
This series adds support for the Rockchip MIPI CSI-2 Receiver that is
integrated into recent Rockchip SoCs, such as the RK3568 and the RK3588.
These changes have been discussed and reviewed thoroughly in roughly 10
iterations of the rkcif patch series (see [0] and predecessors). Thus,
they should be ready for inclusion in mainline.
However, as has been discussed in [0], it would be beneficial to add
support for the split mode (a feature of the Rockchip CSI-2 DPHY)
before integrating the MIPI CSI-2 Receiver into the respective SoC
device tree includes. We can readily add the DT binding and the driver
as I am positive they will not need to be changed when this feature is
introduced, but there are no patches in the series that introduce the
DT nodes.
This constitutes a small step towards mainline video capture and camera
support on the Rockchip RK3588.
Looking forward to your comments!
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
[0] https://lore.kernel.org/r/20240220-rk3568-vicap-v14-0-b38b6da0fc80@collabora.com
---
Changes in v3:
- rebased onto v6.19/recent media-committers/next
- Link to v2: https://lore.kernel.org/r/20251114-rockchip-mipi-receiver-v2-0-eb9b43377fc4@collabora.com
Changes in v2:
- dropped one R-b by Bryan, as there were two of them on the same patch
(Krzysztof)
- Link to v1: https://lore.kernel.org/r/20251114-rockchip-mipi-receiver-v1-0-d13086e810dd@collabora.com
---
Michael Riesch (3):
media: dt-bindings: add rockchip mipi csi-2 receiver
media: rockchip: add driver for the rockchip mipi csi-2 receiver
arm64: defconfig: enable rockchip mipi csi-2 receiver
.../bindings/media/rockchip,rk3568-mipi-csi.yaml | 146 ++++
MAINTAINERS | 7 +
arch/arm64/configs/defconfig | 1 +
drivers/media/platform/rockchip/Kconfig | 1 +
drivers/media/platform/rockchip/Makefile | 1 +
drivers/media/platform/rockchip/rkcsi/Kconfig | 16 +
drivers/media/platform/rockchip/rkcsi/Makefile | 3 +
drivers/media/platform/rockchip/rkcsi/rkcsi.c | 742 +++++++++++++++++++++
8 files changed, 917 insertions(+)
---
base-commit: 3aa9296a23ec41a8424e9a2346eea59fb6cb7d8c
change-id: 20251114-rockchip-mipi-receiver-0baf244c9c8e
Best regards,
--
Michael Riesch <michael.riesch@collabora.com>
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v3 1/3] media: dt-bindings: add rockchip mipi csi-2 receiver 2026-01-13 21:09 [PATCH v3 0/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver Michael Riesch via B4 Relay @ 2026-01-13 21:09 ` Michael Riesch via B4 Relay 2026-01-13 21:09 ` [PATCH v3 2/3] media: rockchip: add driver for the " Michael Riesch via B4 Relay 2026-01-13 21:09 ` [PATCH v3 3/3] arm64: defconfig: enable " Michael Riesch via B4 Relay 2 siblings, 0 replies; 10+ messages in thread From: Michael Riesch via B4 Relay @ 2026-01-13 21:09 UTC (permalink / raw) To: Michael Riesch, Mehdi Djait, Bryan O'Donoghue, Laurent Pinchart, Hans Verkuil, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Philipp Zabel, Sebastian Reichel, Nicolas Dufresne, Collabora Kernel Team, Sakari Ailus Cc: linux-media, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel, Michael Riesch From: Michael Riesch <michael.riesch@collabora.com> Add documentation for the Rockchip MIPI CSI-2 Receiver. Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net> Reviewed-by: Rob Herring (Arm) <robh@kernel.org> Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Signed-off-by: Michael Riesch <michael.riesch@collabora.com> --- .../bindings/media/rockchip,rk3568-mipi-csi.yaml | 146 +++++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 152 insertions(+) diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml new file mode 100644 index 000000000000..52fe959af8f9 --- /dev/null +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml @@ -0,0 +1,146 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/rockchip,rk3568-mipi-csi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip MIPI CSI-2 Receiver + +maintainers: + - Michael Riesch <michael.riesch@collabora.com> + +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 RK3568 Video Capture (VICAP) + block. + +properties: + compatible: + oneOf: + - items: + - enum: + - rockchip,rk3588-mipi-csi + - const: rockchip,rk3568-mipi-csi + - const: rockchip,rk3568-mipi-csi + + 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 RK3568 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 <dt-bindings/clock/rk3568-cru.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/media/video-interfaces.h> + #include <dt-bindings/power/rk3568-power.h> + + soc { + interrupt-parent = <&gic>; + #address-cells = <2>; + #size-cells = <2>; + + csi: csi@fdfb0000 { + compatible = "rockchip,rk3568-mipi-csi"; + reg = <0x0 0xfdfb0000 0x0 0x10000>; + interrupts = <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>; + 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 = <MEDIA_BUS_TYPE_CSI2_DPHY>; + 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/MAINTAINERS b/MAINTAINERS index 752ccaea38b0..dafa8ef7a000 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22636,6 +22636,12 @@ F: Documentation/userspace-api/media/v4l/metafmt-rkisp1.rst F: drivers/media/platform/rockchip/rkisp1 F: include/uapi/linux/rkisp1-config.h +ROCKCHIP MIPI CSI-2 RECEIVER DRIVER +M: Michael Riesch <michael.riesch@collabora.com> +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml + ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT M: Daniel Golle <daniel@makrotopia.org> M: Aurelien Jarno <aurelien@aurel32.net> -- 2.39.5 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 2/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver 2026-01-13 21:09 [PATCH v3 0/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver Michael Riesch via B4 Relay 2026-01-13 21:09 ` [PATCH v3 1/3] media: dt-bindings: add " Michael Riesch via B4 Relay @ 2026-01-13 21:09 ` Michael Riesch via B4 Relay 2026-01-13 21:59 ` Frank Li 2026-01-14 9:34 ` Sakari Ailus 2026-01-13 21:09 ` [PATCH v3 3/3] arm64: defconfig: enable " Michael Riesch via B4 Relay 2 siblings, 2 replies; 10+ messages in thread From: Michael Riesch via B4 Relay @ 2026-01-13 21:09 UTC (permalink / raw) To: Michael Riesch, Mehdi Djait, Bryan O'Donoghue, Laurent Pinchart, Hans Verkuil, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Philipp Zabel, Sebastian Reichel, Nicolas Dufresne, Collabora Kernel Team, Sakari Ailus Cc: linux-media, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel, Michael Riesch From: Michael Riesch <michael.riesch@collabora.com> 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. Add a V4L2 subdevice driver for this unit. Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net> Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> Reviewed-by: Mehdi Djait <mehdi.djait@linux.intel.com> Signed-off-by: Michael Riesch <michael.riesch@collabora.com> --- MAINTAINERS | 1 + drivers/media/platform/rockchip/Kconfig | 1 + drivers/media/platform/rockchip/Makefile | 1 + drivers/media/platform/rockchip/rkcsi/Kconfig | 16 + drivers/media/platform/rockchip/rkcsi/Makefile | 3 + drivers/media/platform/rockchip/rkcsi/rkcsi.c | 742 +++++++++++++++++++++++++ 6 files changed, 764 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index dafa8ef7a000..4cfbd82afc3b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22641,6 +22641,7 @@ M: Michael Riesch <michael.riesch@collabora.com> L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml +F: drivers/media/platform/rockchip/rkcsi/ ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT M: Daniel Golle <daniel@makrotopia.org> diff --git a/drivers/media/platform/rockchip/Kconfig b/drivers/media/platform/rockchip/Kconfig index ba401d32f01b..54b698c4cd2c 100644 --- a/drivers/media/platform/rockchip/Kconfig +++ b/drivers/media/platform/rockchip/Kconfig @@ -4,5 +4,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/rkcsi/Kconfig" source "drivers/media/platform/rockchip/rkisp1/Kconfig" source "drivers/media/platform/rockchip/rkvdec/Kconfig" diff --git a/drivers/media/platform/rockchip/Makefile b/drivers/media/platform/rockchip/Makefile index 0e0b2cbbd4bd..522a7d3e30b0 100644 --- a/drivers/media/platform/rockchip/Makefile +++ b/drivers/media/platform/rockchip/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y += rga/ obj-y += rkcif/ +obj-y += rkcsi/ obj-y += rkisp1/ obj-y += rkvdec/ diff --git a/drivers/media/platform/rockchip/rkcsi/Kconfig b/drivers/media/platform/rockchip/rkcsi/Kconfig new file mode 100644 index 000000000000..d8004198c386 --- /dev/null +++ b/drivers/media/platform/rockchip/rkcsi/Kconfig @@ -0,0 +1,16 @@ +config VIDEO_ROCKCHIP_CSI + tristate "Rockchip MIPI CSI-2 Receiver" + depends on VIDEO_DEV + depends on ARCH_ROCKCHIP || COMPILE_TEST + depends on V4L_PLATFORM_DRIVERS + depends on PM && COMMON_CLK + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + This is a driver for Rockchip MIPI CSI-2 Receiver. It is featured + in various Rockchips SoCs, usually in combination with a Video + Capture (VICAP) unit (see Rockchip Camera Interface (CIF) driver). + + To compile this driver as a module, choose M here: the module + will be called rockchip-mipi-csi. diff --git a/drivers/media/platform/rockchip/rkcsi/Makefile b/drivers/media/platform/rockchip/rkcsi/Makefile new file mode 100644 index 000000000000..147712cbb68a --- /dev/null +++ b/drivers/media/platform/rockchip/rkcsi/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_VIDEO_ROCKCHIP_CSI) += rockchip-mipi-csi.o +rockchip-mipi-csi-objs += rkcsi.o diff --git a/drivers/media/platform/rockchip/rkcsi/rkcsi.c b/drivers/media/platform/rockchip/rkcsi/rkcsi.c new file mode 100644 index 000000000000..03597f03f08f --- /dev/null +++ b/drivers/media/platform/rockchip/rkcsi/rkcsi.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip MIPI CSI-2 Receiver Driver + * + * Copyright (C) 2019 Rockchip Electronics Co., Ltd. + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net> + * Copyright (C) 2026 Collabora, Ltd. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> + +#include <media/mipi-csi2.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define CSI2HOST_N_LANES 0x04 +#define CSI2HOST_CSI2_RESETN 0x10 +#define CSI2HOST_PHY_STATE 0x14 +#define CSI2HOST_ERR1 0x20 +#define CSI2HOST_ERR2 0x24 +#define CSI2HOST_MSK1 0x28 +#define CSI2HOST_MSK2 0x2c +#define CSI2HOST_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 RKCSI_CLKS_MAX 1 + +enum { + RKCSI_PAD_SINK, + RKCSI_PAD_SRC, + RKCSI_PAD_MAX, +}; + +struct rkcsi_format { + u32 code; + u8 depth; + u8 csi_dt; +}; + +struct rkcsi_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 rkcsi_format *formats; + unsigned int formats_num; + + struct media_pad pads[RKCSI_PAD_MAX]; + struct v4l2_async_notifier notifier; + struct v4l2_fwnode_endpoint vep; + struct v4l2_subdev sd; + + struct v4l2_subdev *source_sd; + u32 source_pad; +}; + +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 rkcsi_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 rkcsi_device *to_rkcsi(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rkcsi_device, sd); +} + +static inline __maybe_unused void rkcsi_write(struct rkcsi_device *csi_dev, + unsigned int addr, u32 val) +{ + writel(val, csi_dev->base_addr + addr); +} + +static inline __maybe_unused u32 rkcsi_read(struct rkcsi_device *csi_dev, + unsigned int addr) +{ + return readl(csi_dev->base_addr + addr); +} + +static const struct rkcsi_format * +rkcsi_find_format(struct rkcsi_device *csi_dev, u32 mbus_code) +{ + const struct rkcsi_format *format; + + WARN_ON(csi_dev->formats_num == 0); + + for (int i = 0; i < csi_dev->formats_num; i++) { + format = &csi_dev->formats[i]; + if (format->code == mbus_code) + return format; + } + + return NULL; +} + +static int rkcsi_start(struct rkcsi_device *csi_dev) +{ + struct media_pad *source_pad = + &csi_dev->source_sd->entity.pads[csi_dev->source_pad]; + enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type; + union phy_configure_opts opts; + s64 link_freq; + u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes; + u32 control = 0; + int ret; + + if (lanes < 1 || lanes > 4) + return -EINVAL; + + /* 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 -EINVAL; + + if (bus_type == V4L2_MBUS_CSI2_DPHY) { + struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy; + + ret = phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, + lanes, cfg); + if (ret) + return ret; + + ret = phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY); + if (ret) + return ret; + + ret = phy_configure(csi_dev->phy, &opts); + if (ret) + return ret; + + control |= SW_CPHY_EN(0); + + } else if (bus_type == V4L2_MBUS_CSI2_CPHY) { + /* TODO: implement CPHY configuration */ + return -EOPNOTSUPP; + } else { + return -EINVAL; + } + + control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) | + SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03); + + rkcsi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1); + rkcsi_write(csi_dev, CSI2HOST_CONTROL, control); + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1); + + ret = phy_power_on(csi_dev->phy); + if (ret) + return ret; + + return 0; +} + +static void rkcsi_stop(struct rkcsi_device *csi_dev) +{ + phy_power_off(csi_dev->phy); + + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0); + rkcsi_write(csi_dev, CSI2HOST_MSK1, ~0); + rkcsi_write(csi_dev, CSI2HOST_MSK2, ~0); +} + +static const struct media_entity_operations rkcsi_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int rkcsi_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct rkcsi_device *csi_dev = to_rkcsi(sd); + + if (code->pad == RKCSI_PAD_SRC) { + const struct v4l2_mbus_framefmt *sink_fmt; + + if (code->index) + return -EINVAL; + + sink_fmt = v4l2_subdev_state_get_format(sd_state, + RKCSI_PAD_SINK); + code->code = sink_fmt->code; + + return 0; + } else if (code->pad == RKCSI_PAD_SINK) { + if (code->index > csi_dev->formats_num) + return -EINVAL; + + code->code = csi_dev->formats[code->index].code; + return 0; + } + + return -EINVAL; +} + +static int rkcsi_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct rkcsi_device *csi_dev = to_rkcsi(sd); + const struct rkcsi_format *fmt; + struct v4l2_mbus_framefmt *sink, *src; + + /* the format on the source pad always matches the sink pad */ + if (format->pad == RKCSI_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 = rkcsi_find_format(csi_dev, 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 rkcsi_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_with_fmt(sd, state, routing, + &default_format); + if (ret) + return ret; + + return 0; +} + +static int rkcsi_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct rkcsi_device *csi_dev = to_rkcsi(sd); + struct v4l2_subdev *remote_sd; + struct media_pad *sink_pad, *remote_pad; + struct device *dev = csi_dev->dev; + u64 mask; + int ret; + + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, + RKCSI_PAD_SRC, &streams_mask); + + ret = pm_runtime_resume_and_get(dev); + if (ret) + goto err; + + ret = rkcsi_start(csi_dev); + 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: + rkcsi_stop(csi_dev); +err_pm_runtime_put: + pm_runtime_put_sync(dev); +err: + return ret; +} + +static int rkcsi_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct rkcsi_device *csi_dev = to_rkcsi(sd); + struct v4l2_subdev *remote_sd; + struct media_pad *sink_pad, *remote_pad; + struct device *dev = csi_dev->dev; + u64 mask; + int ret; + + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, + RKCSI_PAD_SRC, &streams_mask); + + ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); + + rkcsi_stop(csi_dev); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static const struct v4l2_subdev_pad_ops rkcsi_pad_ops = { + .enum_mbus_code = rkcsi_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = rkcsi_set_fmt, + .set_routing = rkcsi_set_routing, + .enable_streams = rkcsi_enable_streams, + .disable_streams = rkcsi_disable_streams, +}; + +static const struct v4l2_subdev_ops rkcsi_ops = { + .pad = &rkcsi_pad_ops, +}; + +static int rkcsi_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = RKCSI_PAD_SINK, + .sink_stream = 0, + .source_pad = RKCSI_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, + }; + int ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing, + &default_format); + + return ret; +} + +static const struct v4l2_subdev_internal_ops rkcsi_internal_ops = { + .init_state = rkcsi_init_state, +}; + +static int rkcsi_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asd) +{ + struct rkcsi_device *csi_dev = + container_of(notifier, struct rkcsi_device, notifier); + int source_pad; + + source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode, + MEDIA_PAD_FL_SOURCE); + if (source_pad < 0) { + dev_err(csi_dev->dev, "failed to find source pad for %s\n", + sd->name); + return source_pad; + } + + csi_dev->source_sd = sd; + csi_dev->source_pad = source_pad; + + return media_create_pad_link(&sd->entity, source_pad, + &csi_dev->sd.entity, RKCSI_PAD_SINK, + MEDIA_LNK_FL_ENABLED); +} + +static const struct v4l2_async_notifier_operations rkcsi_notifier_ops = { + .bound = rkcsi_notifier_bound, +}; + +static int rkcsi_register_notifier(struct rkcsi_device *csi_dev) +{ + struct v4l2_async_connection *asd; + struct v4l2_async_notifier *ntf = &csi_dev->notifier; + struct v4l2_fwnode_endpoint *vep = &csi_dev->vep; + struct v4l2_subdev *sd = &csi_dev->sd; + struct device *dev = csi_dev->dev; + struct fwnode_handle *ep; + int ret = 0; + + ep = 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) { + ret = dev_err_probe(dev, ret, "failed to parse endpoint\n"); + goto out; + } + + if (vep->bus_type != V4L2_MBUS_CSI2_DPHY && + vep->bus_type != V4L2_MBUS_CSI2_CPHY) { + ret = dev_err_probe(dev, -EINVAL, + "invalid bus type of endpoint\n"); + goto out; + } + + v4l2_async_subdev_nf_init(ntf, sd); + ntf->ops = &rkcsi_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; + } + + goto out; + +err_nf_cleanup: + v4l2_async_nf_cleanup(ntf); +out: + fwnode_handle_put(ep); + return ret; +} + +static int rkcsi_register(struct rkcsi_device *csi_dev) +{ + struct media_pad *pads = csi_dev->pads; + struct v4l2_subdev *sd = &csi_dev->sd; + int ret; + + ret = rkcsi_register_notifier(csi_dev); + if (ret) + goto err; + + v4l2_subdev_init(sd, &rkcsi_ops); + sd->dev = csi_dev->dev; + sd->entity.ops = &rkcsi_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 = &rkcsi_internal_ops; + sd->owner = THIS_MODULE; + snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s", + dev_name(csi_dev->dev)); + + pads[RKCSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[RKCSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, RKCSI_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 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(&csi_dev->notifier); + v4l2_async_nf_cleanup(&csi_dev->notifier); +err: + return ret; +} + +static void rkcsi_unregister(struct rkcsi_device *csi_dev) +{ + struct v4l2_subdev *sd = &csi_dev->sd; + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_async_nf_unregister(&csi_dev->notifier); + v4l2_async_nf_cleanup(&csi_dev->notifier); +} + +static const struct of_device_id rkcsi_of_match[] = { + { + .compatible = "rockchip,rk3568-mipi-csi", + }, + {} +}; +MODULE_DEVICE_TABLE(of, rkcsi_of_match); + +static int rkcsi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rkcsi_device *csi_dev; + int ret; + + csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL); + if (!csi_dev) + return -ENOMEM; + csi_dev->dev = dev; + dev_set_drvdata(dev, csi_dev); + + csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi_dev->base_addr)) + return PTR_ERR(csi_dev->base_addr); + + ret = devm_clk_bulk_get_all(dev, &csi_dev->clks); + if (ret != RKCSI_CLKS_MAX) + return dev_err_probe(dev, -ENODEV, "failed to get clocks\n"); + csi_dev->clks_num = ret; + + csi_dev->phy = devm_phy_get(dev, NULL); + if (IS_ERR(csi_dev->phy)) + return dev_err_probe(dev, PTR_ERR(csi_dev->phy), + "failed to get MIPI CSI PHY\n"); + + csi_dev->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(csi_dev->reset)) + return dev_err_probe(dev, PTR_ERR(csi_dev->reset), + "failed to get reset\n"); + + csi_dev->formats = formats; + csi_dev->formats_num = ARRAY_SIZE(formats); + + pm_runtime_enable(dev); + + ret = phy_init(csi_dev->phy); + if (ret) { + ret = dev_err_probe(dev, ret, + "failed to initialize MIPI CSI PHY\n"); + goto err_pm_runtime_disable; + } + + ret = rkcsi_register(csi_dev); + if (ret) + goto err_phy_exit; + + return 0; + +err_phy_exit: + phy_exit(csi_dev->phy); +err_pm_runtime_disable: + pm_runtime_disable(dev); + return ret; +} + +static void rkcsi_remove(struct platform_device *pdev) +{ + struct rkcsi_device *csi_dev = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + rkcsi_unregister(csi_dev); + phy_exit(csi_dev->phy); + pm_runtime_disable(dev); +} + +static int rkcsi_runtime_suspend(struct device *dev) +{ + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks); + + return 0; +} + +static int rkcsi_runtime_resume(struct device *dev) +{ + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); + int ret; + + reset_control_assert(csi_dev->reset); + udelay(5); + reset_control_deassert(csi_dev->reset); + + ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks); + if (ret) { + dev_err(dev, "failed to enable clocks\n"); + return ret; + } + + return 0; +} + +static const struct dev_pm_ops rkcsi_pm_ops = { + .runtime_suspend = rkcsi_runtime_suspend, + .runtime_resume = rkcsi_runtime_resume, +}; + +static struct platform_driver rkcsi_drv = { + .driver = { + .name = "rockchip-mipi-csi", + .of_match_table = rkcsi_of_match, + .pm = &rkcsi_pm_ops, + }, + .probe = rkcsi_probe, + .remove = rkcsi_remove, +}; +module_platform_driver(rkcsi_drv); + +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver"); +MODULE_LICENSE("GPL"); -- 2.39.5 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver 2026-01-13 21:09 ` [PATCH v3 2/3] media: rockchip: add driver for the " Michael Riesch via B4 Relay @ 2026-01-13 21:59 ` Frank Li 2026-01-13 22:44 ` Michael Riesch 2026-01-14 9:34 ` Sakari Ailus 1 sibling, 1 reply; 10+ messages in thread From: Frank Li @ 2026-01-13 21:59 UTC (permalink / raw) To: Michael Riesch Cc: Mehdi Djait, Bryan O'Donoghue, Laurent Pinchart, Hans Verkuil, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Philipp Zabel, Sebastian Reichel, Nicolas Dufresne, Collabora Kernel Team, Sakari Ailus, linux-media, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel On Tue, Jan 13, 2026 at 10:09:28PM +0100, Michael Riesch wrote: > 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. > > Add a V4L2 subdevice driver for this unit. > > Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net> > Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > Reviewed-by: Mehdi Djait <mehdi.djait@linux.intel.com> > Signed-off-by: Michael Riesch <michael.riesch@collabora.com> > --- > MAINTAINERS | 1 + > drivers/media/platform/rockchip/Kconfig | 1 + > drivers/media/platform/rockchip/Makefile | 1 + > drivers/media/platform/rockchip/rkcsi/Kconfig | 16 + > drivers/media/platform/rockchip/rkcsi/Makefile | 3 + > drivers/media/platform/rockchip/rkcsi/rkcsi.c | 742 +++++++++++++++++++++++++ > 6 files changed, 764 insertions(+) > > diff --git a/MAINTAINERS b/MAINTAINERS > index dafa8ef7a000..4cfbd82afc3b 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -22641,6 +22641,7 @@ M: Michael Riesch <michael.riesch@collabora.com> > L: linux-media@vger.kernel.org > S: Maintained > F: Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml > +F: drivers/media/platform/rockchip/rkcsi/ > > ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT > M: Daniel Golle <daniel@makrotopia.org> > diff --git a/drivers/media/platform/rockchip/Kconfig b/drivers/media/platform/rockchip/Kconfig > index ba401d32f01b..54b698c4cd2c 100644 > --- a/drivers/media/platform/rockchip/Kconfig > +++ b/drivers/media/platform/rockchip/Kconfig > @@ -4,5 +4,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/rkcsi/Kconfig" > source "drivers/media/platform/rockchip/rkisp1/Kconfig" > source "drivers/media/platform/rockchip/rkvdec/Kconfig" > diff --git a/drivers/media/platform/rockchip/Makefile b/drivers/media/platform/rockchip/Makefile > index 0e0b2cbbd4bd..522a7d3e30b0 100644 > --- a/drivers/media/platform/rockchip/Makefile > +++ b/drivers/media/platform/rockchip/Makefile > @@ -1,5 +1,6 @@ > # SPDX-License-Identifier: GPL-2.0-only > obj-y += rga/ > obj-y += rkcif/ > +obj-y += rkcsi/ > obj-y += rkisp1/ > obj-y += rkvdec/ > diff --git a/drivers/media/platform/rockchip/rkcsi/Kconfig b/drivers/media/platform/rockchip/rkcsi/Kconfig > new file mode 100644 > index 000000000000..d8004198c386 > --- /dev/null > +++ b/drivers/media/platform/rockchip/rkcsi/Kconfig > @@ -0,0 +1,16 @@ > +config VIDEO_ROCKCHIP_CSI > + tristate "Rockchip MIPI CSI-2 Receiver" > + depends on VIDEO_DEV > + depends on ARCH_ROCKCHIP || COMPILE_TEST > + depends on V4L_PLATFORM_DRIVERS > + depends on PM && COMMON_CLK > + select MEDIA_CONTROLLER > + select V4L2_FWNODE > + select VIDEO_V4L2_SUBDEV_API > + help > + This is a driver for Rockchip MIPI CSI-2 Receiver. It is featured > + in various Rockchips SoCs, usually in combination with a Video > + Capture (VICAP) unit (see Rockchip Camera Interface (CIF) driver). > + > + To compile this driver as a module, choose M here: the module > + will be called rockchip-mipi-csi. > diff --git a/drivers/media/platform/rockchip/rkcsi/Makefile b/drivers/media/platform/rockchip/rkcsi/Makefile > new file mode 100644 > index 000000000000..147712cbb68a > --- /dev/null > +++ b/drivers/media/platform/rockchip/rkcsi/Makefile > @@ -0,0 +1,3 @@ > +# SPDX-License-Identifier: GPL-2.0 > +obj-$(CONFIG_VIDEO_ROCKCHIP_CSI) += rockchip-mipi-csi.o > +rockchip-mipi-csi-objs += rkcsi.o > diff --git a/drivers/media/platform/rockchip/rkcsi/rkcsi.c b/drivers/media/platform/rockchip/rkcsi/rkcsi.c > new file mode 100644 > index 000000000000..03597f03f08f > --- /dev/null > +++ b/drivers/media/platform/rockchip/rkcsi/rkcsi.c > @@ -0,0 +1,742 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Rockchip MIPI CSI-2 Receiver Driver > + * > + * Copyright (C) 2019 Rockchip Electronics Co., Ltd. > + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net> > + * Copyright (C) 2026 Collabora, Ltd. > + */ > + > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/io.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_graph.h> > +#include <linux/of_platform.h> > +#include <linux/phy/phy.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/reset.h> > + > +#include <media/mipi-csi2.h> > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-fwnode.h> > +#include <media/v4l2-subdev.h> > + > +#define CSI2HOST_N_LANES 0x04 > +#define CSI2HOST_CSI2_RESETN 0x10 > +#define CSI2HOST_PHY_STATE 0x14 > +#define CSI2HOST_ERR1 0x20 > +#define CSI2HOST_ERR2 0x24 > +#define CSI2HOST_MSK1 0x28 > +#define CSI2HOST_MSK2 0x2c > +#define CSI2HOST_CONTROL 0x40 I remember I said before. It is DWC compatible's CSI2 controller, even it is not name as DWC. The register define and offset is the same. Can you move to drivers/media/platform/synopsys/ So other vendor can be reuse it easily. Frank > + > +#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 RKCSI_CLKS_MAX 1 > + > +enum { > + RKCSI_PAD_SINK, > + RKCSI_PAD_SRC, > + RKCSI_PAD_MAX, > +}; > + > +struct rkcsi_format { > + u32 code; > + u8 depth; > + u8 csi_dt; > +}; > + > +struct rkcsi_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 rkcsi_format *formats; > + unsigned int formats_num; > + > + struct media_pad pads[RKCSI_PAD_MAX]; > + struct v4l2_async_notifier notifier; > + struct v4l2_fwnode_endpoint vep; > + struct v4l2_subdev sd; > + > + struct v4l2_subdev *source_sd; > + u32 source_pad; > +}; > + > +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 rkcsi_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 rkcsi_device *to_rkcsi(struct v4l2_subdev *sd) > +{ > + return container_of(sd, struct rkcsi_device, sd); > +} > + > +static inline __maybe_unused void rkcsi_write(struct rkcsi_device *csi_dev, > + unsigned int addr, u32 val) > +{ > + writel(val, csi_dev->base_addr + addr); > +} > + > +static inline __maybe_unused u32 rkcsi_read(struct rkcsi_device *csi_dev, > + unsigned int addr) > +{ > + return readl(csi_dev->base_addr + addr); > +} > + > +static const struct rkcsi_format * > +rkcsi_find_format(struct rkcsi_device *csi_dev, u32 mbus_code) > +{ > + const struct rkcsi_format *format; > + > + WARN_ON(csi_dev->formats_num == 0); > + > + for (int i = 0; i < csi_dev->formats_num; i++) { > + format = &csi_dev->formats[i]; > + if (format->code == mbus_code) > + return format; > + } > + > + return NULL; > +} > + > +static int rkcsi_start(struct rkcsi_device *csi_dev) > +{ > + struct media_pad *source_pad = > + &csi_dev->source_sd->entity.pads[csi_dev->source_pad]; > + enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type; > + union phy_configure_opts opts; > + s64 link_freq; > + u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes; > + u32 control = 0; > + int ret; > + > + if (lanes < 1 || lanes > 4) > + return -EINVAL; > + > + /* 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 -EINVAL; > + > + if (bus_type == V4L2_MBUS_CSI2_DPHY) { > + struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy; > + > + ret = phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, > + lanes, cfg); > + if (ret) > + return ret; > + > + ret = phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY); > + if (ret) > + return ret; > + > + ret = phy_configure(csi_dev->phy, &opts); > + if (ret) > + return ret; > + > + control |= SW_CPHY_EN(0); > + > + } else if (bus_type == V4L2_MBUS_CSI2_CPHY) { > + /* TODO: implement CPHY configuration */ > + return -EOPNOTSUPP; > + } else { > + return -EINVAL; > + } > + > + control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) | > + SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03); > + > + rkcsi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1); > + rkcsi_write(csi_dev, CSI2HOST_CONTROL, control); > + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1); > + > + ret = phy_power_on(csi_dev->phy); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static void rkcsi_stop(struct rkcsi_device *csi_dev) > +{ > + phy_power_off(csi_dev->phy); > + > + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0); > + rkcsi_write(csi_dev, CSI2HOST_MSK1, ~0); > + rkcsi_write(csi_dev, CSI2HOST_MSK2, ~0); > +} > + > +static const struct media_entity_operations rkcsi_media_ops = { > + .link_validate = v4l2_subdev_link_validate, > +}; > + > +static int rkcsi_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + struct rkcsi_device *csi_dev = to_rkcsi(sd); > + > + if (code->pad == RKCSI_PAD_SRC) { > + const struct v4l2_mbus_framefmt *sink_fmt; > + > + if (code->index) > + return -EINVAL; > + > + sink_fmt = v4l2_subdev_state_get_format(sd_state, > + RKCSI_PAD_SINK); > + code->code = sink_fmt->code; > + > + return 0; > + } else if (code->pad == RKCSI_PAD_SINK) { > + if (code->index > csi_dev->formats_num) > + return -EINVAL; > + > + code->code = csi_dev->formats[code->index].code; > + return 0; > + } > + > + return -EINVAL; > +} > + > +static int rkcsi_set_fmt(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, > + struct v4l2_subdev_format *format) > +{ > + struct rkcsi_device *csi_dev = to_rkcsi(sd); > + const struct rkcsi_format *fmt; > + struct v4l2_mbus_framefmt *sink, *src; > + > + /* the format on the source pad always matches the sink pad */ > + if (format->pad == RKCSI_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 = rkcsi_find_format(csi_dev, 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 rkcsi_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_with_fmt(sd, state, routing, > + &default_format); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static int rkcsi_enable_streams(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, u32 pad, > + u64 streams_mask) > +{ > + struct rkcsi_device *csi_dev = to_rkcsi(sd); > + struct v4l2_subdev *remote_sd; > + struct media_pad *sink_pad, *remote_pad; > + struct device *dev = csi_dev->dev; > + u64 mask; > + int ret; > + > + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, > + RKCSI_PAD_SRC, &streams_mask); > + > + ret = pm_runtime_resume_and_get(dev); > + if (ret) > + goto err; > + > + ret = rkcsi_start(csi_dev); > + 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: > + rkcsi_stop(csi_dev); > +err_pm_runtime_put: > + pm_runtime_put_sync(dev); > +err: > + return ret; > +} > + > +static int rkcsi_disable_streams(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, u32 pad, > + u64 streams_mask) > +{ > + struct rkcsi_device *csi_dev = to_rkcsi(sd); > + struct v4l2_subdev *remote_sd; > + struct media_pad *sink_pad, *remote_pad; > + struct device *dev = csi_dev->dev; > + u64 mask; > + int ret; > + > + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, > + RKCSI_PAD_SRC, &streams_mask); > + > + ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); > + > + rkcsi_stop(csi_dev); > + > + pm_runtime_mark_last_busy(dev); > + pm_runtime_put_autosuspend(dev); > + > + return ret; > +} > + > +static const struct v4l2_subdev_pad_ops rkcsi_pad_ops = { > + .enum_mbus_code = rkcsi_enum_mbus_code, > + .get_fmt = v4l2_subdev_get_fmt, > + .set_fmt = rkcsi_set_fmt, > + .set_routing = rkcsi_set_routing, > + .enable_streams = rkcsi_enable_streams, > + .disable_streams = rkcsi_disable_streams, > +}; > + > +static const struct v4l2_subdev_ops rkcsi_ops = { > + .pad = &rkcsi_pad_ops, > +}; > + > +static int rkcsi_init_state(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state) > +{ > + struct v4l2_subdev_route routes[] = { > + { > + .sink_pad = RKCSI_PAD_SINK, > + .sink_stream = 0, > + .source_pad = RKCSI_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, > + }; > + int ret; > + > + ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing, > + &default_format); > + > + return ret; > +} > + > +static const struct v4l2_subdev_internal_ops rkcsi_internal_ops = { > + .init_state = rkcsi_init_state, > +}; > + > +static int rkcsi_notifier_bound(struct v4l2_async_notifier *notifier, > + struct v4l2_subdev *sd, > + struct v4l2_async_connection *asd) > +{ > + struct rkcsi_device *csi_dev = > + container_of(notifier, struct rkcsi_device, notifier); > + int source_pad; > + > + source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode, > + MEDIA_PAD_FL_SOURCE); > + if (source_pad < 0) { > + dev_err(csi_dev->dev, "failed to find source pad for %s\n", > + sd->name); > + return source_pad; > + } > + > + csi_dev->source_sd = sd; > + csi_dev->source_pad = source_pad; > + > + return media_create_pad_link(&sd->entity, source_pad, > + &csi_dev->sd.entity, RKCSI_PAD_SINK, > + MEDIA_LNK_FL_ENABLED); > +} > + > +static const struct v4l2_async_notifier_operations rkcsi_notifier_ops = { > + .bound = rkcsi_notifier_bound, > +}; > + > +static int rkcsi_register_notifier(struct rkcsi_device *csi_dev) > +{ > + struct v4l2_async_connection *asd; > + struct v4l2_async_notifier *ntf = &csi_dev->notifier; > + struct v4l2_fwnode_endpoint *vep = &csi_dev->vep; > + struct v4l2_subdev *sd = &csi_dev->sd; > + struct device *dev = csi_dev->dev; > + struct fwnode_handle *ep; > + int ret = 0; > + > + ep = 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) { > + ret = dev_err_probe(dev, ret, "failed to parse endpoint\n"); > + goto out; > + } > + > + if (vep->bus_type != V4L2_MBUS_CSI2_DPHY && > + vep->bus_type != V4L2_MBUS_CSI2_CPHY) { > + ret = dev_err_probe(dev, -EINVAL, > + "invalid bus type of endpoint\n"); > + goto out; > + } > + > + v4l2_async_subdev_nf_init(ntf, sd); > + ntf->ops = &rkcsi_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; > + } > + > + goto out; > + > +err_nf_cleanup: > + v4l2_async_nf_cleanup(ntf); > +out: > + fwnode_handle_put(ep); > + return ret; > +} > + > +static int rkcsi_register(struct rkcsi_device *csi_dev) > +{ > + struct media_pad *pads = csi_dev->pads; > + struct v4l2_subdev *sd = &csi_dev->sd; > + int ret; > + > + ret = rkcsi_register_notifier(csi_dev); > + if (ret) > + goto err; > + > + v4l2_subdev_init(sd, &rkcsi_ops); > + sd->dev = csi_dev->dev; > + sd->entity.ops = &rkcsi_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 = &rkcsi_internal_ops; > + sd->owner = THIS_MODULE; > + snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s", > + dev_name(csi_dev->dev)); > + > + pads[RKCSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK | > + MEDIA_PAD_FL_MUST_CONNECT; > + pads[RKCSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; > + ret = media_entity_pads_init(&sd->entity, RKCSI_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 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(&csi_dev->notifier); > + v4l2_async_nf_cleanup(&csi_dev->notifier); > +err: > + return ret; > +} > + > +static void rkcsi_unregister(struct rkcsi_device *csi_dev) > +{ > + struct v4l2_subdev *sd = &csi_dev->sd; > + > + v4l2_async_unregister_subdev(sd); > + v4l2_subdev_cleanup(sd); > + media_entity_cleanup(&sd->entity); > + v4l2_async_nf_unregister(&csi_dev->notifier); > + v4l2_async_nf_cleanup(&csi_dev->notifier); > +} > + > +static const struct of_device_id rkcsi_of_match[] = { > + { > + .compatible = "rockchip,rk3568-mipi-csi", > + }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, rkcsi_of_match); > + > +static int rkcsi_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct rkcsi_device *csi_dev; > + int ret; > + > + csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL); > + if (!csi_dev) > + return -ENOMEM; > + csi_dev->dev = dev; > + dev_set_drvdata(dev, csi_dev); > + > + csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(csi_dev->base_addr)) > + return PTR_ERR(csi_dev->base_addr); > + > + ret = devm_clk_bulk_get_all(dev, &csi_dev->clks); > + if (ret != RKCSI_CLKS_MAX) > + return dev_err_probe(dev, -ENODEV, "failed to get clocks\n"); > + csi_dev->clks_num = ret; > + > + csi_dev->phy = devm_phy_get(dev, NULL); > + if (IS_ERR(csi_dev->phy)) > + return dev_err_probe(dev, PTR_ERR(csi_dev->phy), > + "failed to get MIPI CSI PHY\n"); > + > + csi_dev->reset = devm_reset_control_get_exclusive(dev, NULL); > + if (IS_ERR(csi_dev->reset)) > + return dev_err_probe(dev, PTR_ERR(csi_dev->reset), > + "failed to get reset\n"); > + > + csi_dev->formats = formats; > + csi_dev->formats_num = ARRAY_SIZE(formats); > + > + pm_runtime_enable(dev); > + > + ret = phy_init(csi_dev->phy); > + if (ret) { > + ret = dev_err_probe(dev, ret, > + "failed to initialize MIPI CSI PHY\n"); > + goto err_pm_runtime_disable; > + } > + > + ret = rkcsi_register(csi_dev); > + if (ret) > + goto err_phy_exit; > + > + return 0; > + > +err_phy_exit: > + phy_exit(csi_dev->phy); > +err_pm_runtime_disable: > + pm_runtime_disable(dev); > + return ret; > +} > + > +static void rkcsi_remove(struct platform_device *pdev) > +{ > + struct rkcsi_device *csi_dev = platform_get_drvdata(pdev); > + struct device *dev = &pdev->dev; > + > + rkcsi_unregister(csi_dev); > + phy_exit(csi_dev->phy); > + pm_runtime_disable(dev); > +} > + > +static int rkcsi_runtime_suspend(struct device *dev) > +{ > + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); > + > + clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks); > + > + return 0; > +} > + > +static int rkcsi_runtime_resume(struct device *dev) > +{ > + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); > + int ret; > + > + reset_control_assert(csi_dev->reset); > + udelay(5); > + reset_control_deassert(csi_dev->reset); > + > + ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks); > + if (ret) { > + dev_err(dev, "failed to enable clocks\n"); > + return ret; > + } > + > + return 0; > +} > + > +static const struct dev_pm_ops rkcsi_pm_ops = { > + .runtime_suspend = rkcsi_runtime_suspend, > + .runtime_resume = rkcsi_runtime_resume, > +}; > + > +static struct platform_driver rkcsi_drv = { > + .driver = { > + .name = "rockchip-mipi-csi", > + .of_match_table = rkcsi_of_match, > + .pm = &rkcsi_pm_ops, > + }, > + .probe = rkcsi_probe, > + .remove = rkcsi_remove, > +}; > +module_platform_driver(rkcsi_drv); > + > +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver"); > +MODULE_LICENSE("GPL"); > > -- > 2.39.5 > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver 2026-01-13 21:59 ` Frank Li @ 2026-01-13 22:44 ` Michael Riesch 2026-01-13 23:00 ` Frank Li 0 siblings, 1 reply; 10+ messages in thread From: Michael Riesch @ 2026-01-13 22:44 UTC (permalink / raw) To: Frank Li Cc: Mehdi Djait, Bryan O'Donoghue, Laurent Pinchart, Hans Verkuil, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Philipp Zabel, Sebastian Reichel, Nicolas Dufresne, Collabora Kernel Team, Sakari Ailus, linux-media, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel Hi Frank, On 1/13/26 22:59, Frank Li wrote: > [...] >> +#define CSI2HOST_N_LANES 0x04 >> +#define CSI2HOST_CSI2_RESETN 0x10 >> +#define CSI2HOST_PHY_STATE 0x14 >> +#define CSI2HOST_ERR1 0x20 >> +#define CSI2HOST_ERR2 0x24 >> +#define CSI2HOST_MSK1 0x28 >> +#define CSI2HOST_MSK2 0x2c >> +#define CSI2HOST_CONTROL 0x40 > > I remember I said before. It is DWC compatible's CSI2 controller, even > it is not name as DWC. The register define and offset is the same. > > Can you move to > drivers/media/platform/synopsys/ > > So other vendor can be reuse it easily. Quote from my mail from 2025-11-26: "In principle, I agree with refactoring out common code. However, I am not sure how similar these IP cores really are. Again, the answer I received from Rockchip states that this is *not* a Synopsys IP core. Can you maybe test whether the Rockchip MIPI CSI-2 Receiver driver in this series works on your hardware? Then we will know if (and what) parts can be shared." [0] You didn't even bother to reply to that. Of course, I *could* rename the driver and move it (hey, I already did that once [1], why not again), but why should I bother given that you don't even respond to my (I'd still say reasonable) request? All that said, this can be resolved quite easily: if you test the driver at hand on your HW and confirm that it works for you, and I don't hear any other objections, I'll rename the driver, change the prefixes to "csirx_", and move it to drivers/media/platform/synopsys/csirx. Suggestions for better naming welcome of course. However, if I don't hear from you, I'll ask the maintainers kindly to apply the patches at hand. They have seen 11 iterations in total (v6-v14 of the RK3568 VICAP series and v1-v2 of this series) and I doubt there is any harm in accepting it. The driver can be renamed and moved at a later stage as well, BTW. Best regards, Michael [0] https://lore.kernel.org/all/87049b4c-df7d-454b-a7f4-073975a7ee94@collabora.com/ [1] https://lore.kernel.org/all/3aab9697-30fe-4697-a5ac-7fb8ebb61213@collabora.com/ > > Frank > >> + >> +#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 RKCSI_CLKS_MAX 1 >> + >> +enum { >> + RKCSI_PAD_SINK, >> + RKCSI_PAD_SRC, >> + RKCSI_PAD_MAX, >> +}; >> + >> +struct rkcsi_format { >> + u32 code; >> + u8 depth; >> + u8 csi_dt; >> +}; >> + >> +struct rkcsi_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 rkcsi_format *formats; >> + unsigned int formats_num; >> + >> + struct media_pad pads[RKCSI_PAD_MAX]; >> + struct v4l2_async_notifier notifier; >> + struct v4l2_fwnode_endpoint vep; >> + struct v4l2_subdev sd; >> + >> + struct v4l2_subdev *source_sd; >> + u32 source_pad; >> +}; >> + >> +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 rkcsi_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 rkcsi_device *to_rkcsi(struct v4l2_subdev *sd) >> +{ >> + return container_of(sd, struct rkcsi_device, sd); >> +} >> + >> +static inline __maybe_unused void rkcsi_write(struct rkcsi_device *csi_dev, >> + unsigned int addr, u32 val) >> +{ >> + writel(val, csi_dev->base_addr + addr); >> +} >> + >> +static inline __maybe_unused u32 rkcsi_read(struct rkcsi_device *csi_dev, >> + unsigned int addr) >> +{ >> + return readl(csi_dev->base_addr + addr); >> +} >> + >> +static const struct rkcsi_format * >> +rkcsi_find_format(struct rkcsi_device *csi_dev, u32 mbus_code) >> +{ >> + const struct rkcsi_format *format; >> + >> + WARN_ON(csi_dev->formats_num == 0); >> + >> + for (int i = 0; i < csi_dev->formats_num; i++) { >> + format = &csi_dev->formats[i]; >> + if (format->code == mbus_code) >> + return format; >> + } >> + >> + return NULL; >> +} >> + >> +static int rkcsi_start(struct rkcsi_device *csi_dev) >> +{ >> + struct media_pad *source_pad = >> + &csi_dev->source_sd->entity.pads[csi_dev->source_pad]; >> + enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type; >> + union phy_configure_opts opts; >> + s64 link_freq; >> + u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes; >> + u32 control = 0; >> + int ret; >> + >> + if (lanes < 1 || lanes > 4) >> + return -EINVAL; >> + >> + /* 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 -EINVAL; >> + >> + if (bus_type == V4L2_MBUS_CSI2_DPHY) { >> + struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy; >> + >> + ret = phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, >> + lanes, cfg); >> + if (ret) >> + return ret; >> + >> + ret = phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY); >> + if (ret) >> + return ret; >> + >> + ret = phy_configure(csi_dev->phy, &opts); >> + if (ret) >> + return ret; >> + >> + control |= SW_CPHY_EN(0); >> + >> + } else if (bus_type == V4L2_MBUS_CSI2_CPHY) { >> + /* TODO: implement CPHY configuration */ >> + return -EOPNOTSUPP; >> + } else { >> + return -EINVAL; >> + } >> + >> + control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) | >> + SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03); >> + >> + rkcsi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1); >> + rkcsi_write(csi_dev, CSI2HOST_CONTROL, control); >> + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1); >> + >> + ret = phy_power_on(csi_dev->phy); >> + if (ret) >> + return ret; >> + >> + return 0; >> +} >> + >> +static void rkcsi_stop(struct rkcsi_device *csi_dev) >> +{ >> + phy_power_off(csi_dev->phy); >> + >> + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0); >> + rkcsi_write(csi_dev, CSI2HOST_MSK1, ~0); >> + rkcsi_write(csi_dev, CSI2HOST_MSK2, ~0); >> +} >> + >> +static const struct media_entity_operations rkcsi_media_ops = { >> + .link_validate = v4l2_subdev_link_validate, >> +}; >> + >> +static int rkcsi_enum_mbus_code(struct v4l2_subdev *sd, >> + struct v4l2_subdev_state *sd_state, >> + struct v4l2_subdev_mbus_code_enum *code) >> +{ >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); >> + >> + if (code->pad == RKCSI_PAD_SRC) { >> + const struct v4l2_mbus_framefmt *sink_fmt; >> + >> + if (code->index) >> + return -EINVAL; >> + >> + sink_fmt = v4l2_subdev_state_get_format(sd_state, >> + RKCSI_PAD_SINK); >> + code->code = sink_fmt->code; >> + >> + return 0; >> + } else if (code->pad == RKCSI_PAD_SINK) { >> + if (code->index > csi_dev->formats_num) >> + return -EINVAL; >> + >> + code->code = csi_dev->formats[code->index].code; >> + return 0; >> + } >> + >> + return -EINVAL; >> +} >> + >> +static int rkcsi_set_fmt(struct v4l2_subdev *sd, >> + struct v4l2_subdev_state *state, >> + struct v4l2_subdev_format *format) >> +{ >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); >> + const struct rkcsi_format *fmt; >> + struct v4l2_mbus_framefmt *sink, *src; >> + >> + /* the format on the source pad always matches the sink pad */ >> + if (format->pad == RKCSI_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 = rkcsi_find_format(csi_dev, 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 rkcsi_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_with_fmt(sd, state, routing, >> + &default_format); >> + if (ret) >> + return ret; >> + >> + return 0; >> +} >> + >> +static int rkcsi_enable_streams(struct v4l2_subdev *sd, >> + struct v4l2_subdev_state *state, u32 pad, >> + u64 streams_mask) >> +{ >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); >> + struct v4l2_subdev *remote_sd; >> + struct media_pad *sink_pad, *remote_pad; >> + struct device *dev = csi_dev->dev; >> + u64 mask; >> + int ret; >> + >> + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, >> + RKCSI_PAD_SRC, &streams_mask); >> + >> + ret = pm_runtime_resume_and_get(dev); >> + if (ret) >> + goto err; >> + >> + ret = rkcsi_start(csi_dev); >> + 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: >> + rkcsi_stop(csi_dev); >> +err_pm_runtime_put: >> + pm_runtime_put_sync(dev); >> +err: >> + return ret; >> +} >> + >> +static int rkcsi_disable_streams(struct v4l2_subdev *sd, >> + struct v4l2_subdev_state *state, u32 pad, >> + u64 streams_mask) >> +{ >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); >> + struct v4l2_subdev *remote_sd; >> + struct media_pad *sink_pad, *remote_pad; >> + struct device *dev = csi_dev->dev; >> + u64 mask; >> + int ret; >> + >> + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, >> + RKCSI_PAD_SRC, &streams_mask); >> + >> + ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); >> + >> + rkcsi_stop(csi_dev); >> + >> + pm_runtime_mark_last_busy(dev); >> + pm_runtime_put_autosuspend(dev); >> + >> + return ret; >> +} >> + >> +static const struct v4l2_subdev_pad_ops rkcsi_pad_ops = { >> + .enum_mbus_code = rkcsi_enum_mbus_code, >> + .get_fmt = v4l2_subdev_get_fmt, >> + .set_fmt = rkcsi_set_fmt, >> + .set_routing = rkcsi_set_routing, >> + .enable_streams = rkcsi_enable_streams, >> + .disable_streams = rkcsi_disable_streams, >> +}; >> + >> +static const struct v4l2_subdev_ops rkcsi_ops = { >> + .pad = &rkcsi_pad_ops, >> +}; >> + >> +static int rkcsi_init_state(struct v4l2_subdev *sd, >> + struct v4l2_subdev_state *state) >> +{ >> + struct v4l2_subdev_route routes[] = { >> + { >> + .sink_pad = RKCSI_PAD_SINK, >> + .sink_stream = 0, >> + .source_pad = RKCSI_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, >> + }; >> + int ret; >> + >> + ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing, >> + &default_format); >> + >> + return ret; >> +} >> + >> +static const struct v4l2_subdev_internal_ops rkcsi_internal_ops = { >> + .init_state = rkcsi_init_state, >> +}; >> + >> +static int rkcsi_notifier_bound(struct v4l2_async_notifier *notifier, >> + struct v4l2_subdev *sd, >> + struct v4l2_async_connection *asd) >> +{ >> + struct rkcsi_device *csi_dev = >> + container_of(notifier, struct rkcsi_device, notifier); >> + int source_pad; >> + >> + source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode, >> + MEDIA_PAD_FL_SOURCE); >> + if (source_pad < 0) { >> + dev_err(csi_dev->dev, "failed to find source pad for %s\n", >> + sd->name); >> + return source_pad; >> + } >> + >> + csi_dev->source_sd = sd; >> + csi_dev->source_pad = source_pad; >> + >> + return media_create_pad_link(&sd->entity, source_pad, >> + &csi_dev->sd.entity, RKCSI_PAD_SINK, >> + MEDIA_LNK_FL_ENABLED); >> +} >> + >> +static const struct v4l2_async_notifier_operations rkcsi_notifier_ops = { >> + .bound = rkcsi_notifier_bound, >> +}; >> + >> +static int rkcsi_register_notifier(struct rkcsi_device *csi_dev) >> +{ >> + struct v4l2_async_connection *asd; >> + struct v4l2_async_notifier *ntf = &csi_dev->notifier; >> + struct v4l2_fwnode_endpoint *vep = &csi_dev->vep; >> + struct v4l2_subdev *sd = &csi_dev->sd; >> + struct device *dev = csi_dev->dev; >> + struct fwnode_handle *ep; >> + int ret = 0; >> + >> + ep = 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) { >> + ret = dev_err_probe(dev, ret, "failed to parse endpoint\n"); >> + goto out; >> + } >> + >> + if (vep->bus_type != V4L2_MBUS_CSI2_DPHY && >> + vep->bus_type != V4L2_MBUS_CSI2_CPHY) { >> + ret = dev_err_probe(dev, -EINVAL, >> + "invalid bus type of endpoint\n"); >> + goto out; >> + } >> + >> + v4l2_async_subdev_nf_init(ntf, sd); >> + ntf->ops = &rkcsi_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; >> + } >> + >> + goto out; >> + >> +err_nf_cleanup: >> + v4l2_async_nf_cleanup(ntf); >> +out: >> + fwnode_handle_put(ep); >> + return ret; >> +} >> + >> +static int rkcsi_register(struct rkcsi_device *csi_dev) >> +{ >> + struct media_pad *pads = csi_dev->pads; >> + struct v4l2_subdev *sd = &csi_dev->sd; >> + int ret; >> + >> + ret = rkcsi_register_notifier(csi_dev); >> + if (ret) >> + goto err; >> + >> + v4l2_subdev_init(sd, &rkcsi_ops); >> + sd->dev = csi_dev->dev; >> + sd->entity.ops = &rkcsi_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 = &rkcsi_internal_ops; >> + sd->owner = THIS_MODULE; >> + snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s", >> + dev_name(csi_dev->dev)); >> + >> + pads[RKCSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK | >> + MEDIA_PAD_FL_MUST_CONNECT; >> + pads[RKCSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; >> + ret = media_entity_pads_init(&sd->entity, RKCSI_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 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(&csi_dev->notifier); >> + v4l2_async_nf_cleanup(&csi_dev->notifier); >> +err: >> + return ret; >> +} >> + >> +static void rkcsi_unregister(struct rkcsi_device *csi_dev) >> +{ >> + struct v4l2_subdev *sd = &csi_dev->sd; >> + >> + v4l2_async_unregister_subdev(sd); >> + v4l2_subdev_cleanup(sd); >> + media_entity_cleanup(&sd->entity); >> + v4l2_async_nf_unregister(&csi_dev->notifier); >> + v4l2_async_nf_cleanup(&csi_dev->notifier); >> +} >> + >> +static const struct of_device_id rkcsi_of_match[] = { >> + { >> + .compatible = "rockchip,rk3568-mipi-csi", >> + }, >> + {} >> +}; >> +MODULE_DEVICE_TABLE(of, rkcsi_of_match); >> + >> +static int rkcsi_probe(struct platform_device *pdev) >> +{ >> + struct device *dev = &pdev->dev; >> + struct rkcsi_device *csi_dev; >> + int ret; >> + >> + csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL); >> + if (!csi_dev) >> + return -ENOMEM; >> + csi_dev->dev = dev; >> + dev_set_drvdata(dev, csi_dev); >> + >> + csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0); >> + if (IS_ERR(csi_dev->base_addr)) >> + return PTR_ERR(csi_dev->base_addr); >> + >> + ret = devm_clk_bulk_get_all(dev, &csi_dev->clks); >> + if (ret != RKCSI_CLKS_MAX) >> + return dev_err_probe(dev, -ENODEV, "failed to get clocks\n"); >> + csi_dev->clks_num = ret; >> + >> + csi_dev->phy = devm_phy_get(dev, NULL); >> + if (IS_ERR(csi_dev->phy)) >> + return dev_err_probe(dev, PTR_ERR(csi_dev->phy), >> + "failed to get MIPI CSI PHY\n"); >> + >> + csi_dev->reset = devm_reset_control_get_exclusive(dev, NULL); >> + if (IS_ERR(csi_dev->reset)) >> + return dev_err_probe(dev, PTR_ERR(csi_dev->reset), >> + "failed to get reset\n"); >> + >> + csi_dev->formats = formats; >> + csi_dev->formats_num = ARRAY_SIZE(formats); >> + >> + pm_runtime_enable(dev); >> + >> + ret = phy_init(csi_dev->phy); >> + if (ret) { >> + ret = dev_err_probe(dev, ret, >> + "failed to initialize MIPI CSI PHY\n"); >> + goto err_pm_runtime_disable; >> + } >> + >> + ret = rkcsi_register(csi_dev); >> + if (ret) >> + goto err_phy_exit; >> + >> + return 0; >> + >> +err_phy_exit: >> + phy_exit(csi_dev->phy); >> +err_pm_runtime_disable: >> + pm_runtime_disable(dev); >> + return ret; >> +} >> + >> +static void rkcsi_remove(struct platform_device *pdev) >> +{ >> + struct rkcsi_device *csi_dev = platform_get_drvdata(pdev); >> + struct device *dev = &pdev->dev; >> + >> + rkcsi_unregister(csi_dev); >> + phy_exit(csi_dev->phy); >> + pm_runtime_disable(dev); >> +} >> + >> +static int rkcsi_runtime_suspend(struct device *dev) >> +{ >> + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); >> + >> + clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks); >> + >> + return 0; >> +} >> + >> +static int rkcsi_runtime_resume(struct device *dev) >> +{ >> + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); >> + int ret; >> + >> + reset_control_assert(csi_dev->reset); >> + udelay(5); >> + reset_control_deassert(csi_dev->reset); >> + >> + ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks); >> + if (ret) { >> + dev_err(dev, "failed to enable clocks\n"); >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static const struct dev_pm_ops rkcsi_pm_ops = { >> + .runtime_suspend = rkcsi_runtime_suspend, >> + .runtime_resume = rkcsi_runtime_resume, >> +}; >> + >> +static struct platform_driver rkcsi_drv = { >> + .driver = { >> + .name = "rockchip-mipi-csi", >> + .of_match_table = rkcsi_of_match, >> + .pm = &rkcsi_pm_ops, >> + }, >> + .probe = rkcsi_probe, >> + .remove = rkcsi_remove, >> +}; >> +module_platform_driver(rkcsi_drv); >> + >> +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver"); >> +MODULE_LICENSE("GPL"); >> >> -- >> 2.39.5 >> ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver 2026-01-13 22:44 ` Michael Riesch @ 2026-01-13 23:00 ` Frank Li 0 siblings, 0 replies; 10+ messages in thread From: Frank Li @ 2026-01-13 23:00 UTC (permalink / raw) To: Michael Riesch Cc: Mehdi Djait, Bryan O'Donoghue, Laurent Pinchart, Hans Verkuil, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Philipp Zabel, Sebastian Reichel, Nicolas Dufresne, Collabora Kernel Team, Sakari Ailus, linux-media, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel On Tue, Jan 13, 2026 at 11:44:56PM +0100, Michael Riesch wrote: > Hi Frank, > > On 1/13/26 22:59, Frank Li wrote: > > [...] > >> +#define CSI2HOST_N_LANES 0x04 > >> +#define CSI2HOST_CSI2_RESETN 0x10 > >> +#define CSI2HOST_PHY_STATE 0x14 > >> +#define CSI2HOST_ERR1 0x20 > >> +#define CSI2HOST_ERR2 0x24 > >> +#define CSI2HOST_MSK1 0x28 > >> +#define CSI2HOST_MSK2 0x2c > >> +#define CSI2HOST_CONTROL 0x40 > > > > I remember I said before. It is DWC compatible's CSI2 controller, even > > it is not name as DWC. The register define and offset is the same. > > > > Can you move to > > drivers/media/platform/synopsys/ > > > > So other vendor can be reuse it easily. > > Quote from my mail from 2025-11-26: "In principle, I agree with > refactoring out common code. However, I am > not sure how similar these IP cores really are. Again, the answer I > received from Rockchip states that this is *not* a Synopsys IP core. This is not important. Many difference vendor controller is compatible. Register define and offset is the same. > > Can you maybe test whether the Rockchip MIPI CSI-2 Receiver driver in > this series works on your hardware? Then we will know if (and what) > parts can be shared." [0] Laurent have not reply my last email https://lore.kernel.org/all/aS3XfZFF8rmt8ZT6@lizhi-Precision-Tower-5810/ If agree base on this patch for DWC CSI2 support, I can start test it at my hardware, which need some efforts to adapt it. If maintainer have difference idea, that waste efforts. Frank > > You didn't even bother to reply to that. Of course, I *could* rename the > driver and move it (hey, I already did that once [1], why not again), > but why should I bother given that you don't even respond to my (I'd > still say reasonable) request? > > All that said, this can be resolved quite easily: if you test the driver > at hand on your HW and confirm that it works for you, and I don't hear > any other objections, I'll rename the driver, change the prefixes to > "csirx_", and move it to drivers/media/platform/synopsys/csirx. > Suggestions for better naming welcome of course. > > However, if I don't hear from you, I'll ask the maintainers kindly to > apply the patches at hand. They have seen 11 iterations in total (v6-v14 > of the RK3568 VICAP series and v1-v2 of this series) and I doubt there > is any harm in accepting it. The driver can be renamed and moved at a > later stage as well, BTW. > > Best regards, > Michael > > [0] > https://lore.kernel.org/all/87049b4c-df7d-454b-a7f4-073975a7ee94@collabora.com/ > [1] > https://lore.kernel.org/all/3aab9697-30fe-4697-a5ac-7fb8ebb61213@collabora.com/ > > > > > > Frank > > > >> + > >> +#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 RKCSI_CLKS_MAX 1 > >> + > >> +enum { > >> + RKCSI_PAD_SINK, > >> + RKCSI_PAD_SRC, > >> + RKCSI_PAD_MAX, > >> +}; > >> + > >> +struct rkcsi_format { > >> + u32 code; > >> + u8 depth; > >> + u8 csi_dt; > >> +}; > >> + > >> +struct rkcsi_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 rkcsi_format *formats; > >> + unsigned int formats_num; > >> + > >> + struct media_pad pads[RKCSI_PAD_MAX]; > >> + struct v4l2_async_notifier notifier; > >> + struct v4l2_fwnode_endpoint vep; > >> + struct v4l2_subdev sd; > >> + > >> + struct v4l2_subdev *source_sd; > >> + u32 source_pad; > >> +}; > >> + > >> +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 rkcsi_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 rkcsi_device *to_rkcsi(struct v4l2_subdev *sd) > >> +{ > >> + return container_of(sd, struct rkcsi_device, sd); > >> +} > >> + > >> +static inline __maybe_unused void rkcsi_write(struct rkcsi_device *csi_dev, > >> + unsigned int addr, u32 val) > >> +{ > >> + writel(val, csi_dev->base_addr + addr); > >> +} > >> + > >> +static inline __maybe_unused u32 rkcsi_read(struct rkcsi_device *csi_dev, > >> + unsigned int addr) > >> +{ > >> + return readl(csi_dev->base_addr + addr); > >> +} > >> + > >> +static const struct rkcsi_format * > >> +rkcsi_find_format(struct rkcsi_device *csi_dev, u32 mbus_code) > >> +{ > >> + const struct rkcsi_format *format; > >> + > >> + WARN_ON(csi_dev->formats_num == 0); > >> + > >> + for (int i = 0; i < csi_dev->formats_num; i++) { > >> + format = &csi_dev->formats[i]; > >> + if (format->code == mbus_code) > >> + return format; > >> + } > >> + > >> + return NULL; > >> +} > >> + > >> +static int rkcsi_start(struct rkcsi_device *csi_dev) > >> +{ > >> + struct media_pad *source_pad = > >> + &csi_dev->source_sd->entity.pads[csi_dev->source_pad]; > >> + enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type; > >> + union phy_configure_opts opts; > >> + s64 link_freq; > >> + u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes; > >> + u32 control = 0; > >> + int ret; > >> + > >> + if (lanes < 1 || lanes > 4) > >> + return -EINVAL; > >> + > >> + /* 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 -EINVAL; > >> + > >> + if (bus_type == V4L2_MBUS_CSI2_DPHY) { > >> + struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy; > >> + > >> + ret = phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, > >> + lanes, cfg); > >> + if (ret) > >> + return ret; > >> + > >> + ret = phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY); > >> + if (ret) > >> + return ret; > >> + > >> + ret = phy_configure(csi_dev->phy, &opts); > >> + if (ret) > >> + return ret; > >> + > >> + control |= SW_CPHY_EN(0); > >> + > >> + } else if (bus_type == V4L2_MBUS_CSI2_CPHY) { > >> + /* TODO: implement CPHY configuration */ > >> + return -EOPNOTSUPP; > >> + } else { > >> + return -EINVAL; > >> + } > >> + > >> + control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) | > >> + SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03); > >> + > >> + rkcsi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1); > >> + rkcsi_write(csi_dev, CSI2HOST_CONTROL, control); > >> + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1); > >> + > >> + ret = phy_power_on(csi_dev->phy); > >> + if (ret) > >> + return ret; > >> + > >> + return 0; > >> +} > >> + > >> +static void rkcsi_stop(struct rkcsi_device *csi_dev) > >> +{ > >> + phy_power_off(csi_dev->phy); > >> + > >> + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0); > >> + rkcsi_write(csi_dev, CSI2HOST_MSK1, ~0); > >> + rkcsi_write(csi_dev, CSI2HOST_MSK2, ~0); > >> +} > >> + > >> +static const struct media_entity_operations rkcsi_media_ops = { > >> + .link_validate = v4l2_subdev_link_validate, > >> +}; > >> + > >> +static int rkcsi_enum_mbus_code(struct v4l2_subdev *sd, > >> + struct v4l2_subdev_state *sd_state, > >> + struct v4l2_subdev_mbus_code_enum *code) > >> +{ > >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); > >> + > >> + if (code->pad == RKCSI_PAD_SRC) { > >> + const struct v4l2_mbus_framefmt *sink_fmt; > >> + > >> + if (code->index) > >> + return -EINVAL; > >> + > >> + sink_fmt = v4l2_subdev_state_get_format(sd_state, > >> + RKCSI_PAD_SINK); > >> + code->code = sink_fmt->code; > >> + > >> + return 0; > >> + } else if (code->pad == RKCSI_PAD_SINK) { > >> + if (code->index > csi_dev->formats_num) > >> + return -EINVAL; > >> + > >> + code->code = csi_dev->formats[code->index].code; > >> + return 0; > >> + } > >> + > >> + return -EINVAL; > >> +} > >> + > >> +static int rkcsi_set_fmt(struct v4l2_subdev *sd, > >> + struct v4l2_subdev_state *state, > >> + struct v4l2_subdev_format *format) > >> +{ > >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); > >> + const struct rkcsi_format *fmt; > >> + struct v4l2_mbus_framefmt *sink, *src; > >> + > >> + /* the format on the source pad always matches the sink pad */ > >> + if (format->pad == RKCSI_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 = rkcsi_find_format(csi_dev, 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 rkcsi_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_with_fmt(sd, state, routing, > >> + &default_format); > >> + if (ret) > >> + return ret; > >> + > >> + return 0; > >> +} > >> + > >> +static int rkcsi_enable_streams(struct v4l2_subdev *sd, > >> + struct v4l2_subdev_state *state, u32 pad, > >> + u64 streams_mask) > >> +{ > >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); > >> + struct v4l2_subdev *remote_sd; > >> + struct media_pad *sink_pad, *remote_pad; > >> + struct device *dev = csi_dev->dev; > >> + u64 mask; > >> + int ret; > >> + > >> + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, > >> + RKCSI_PAD_SRC, &streams_mask); > >> + > >> + ret = pm_runtime_resume_and_get(dev); > >> + if (ret) > >> + goto err; > >> + > >> + ret = rkcsi_start(csi_dev); > >> + 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: > >> + rkcsi_stop(csi_dev); > >> +err_pm_runtime_put: > >> + pm_runtime_put_sync(dev); > >> +err: > >> + return ret; > >> +} > >> + > >> +static int rkcsi_disable_streams(struct v4l2_subdev *sd, > >> + struct v4l2_subdev_state *state, u32 pad, > >> + u64 streams_mask) > >> +{ > >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); > >> + struct v4l2_subdev *remote_sd; > >> + struct media_pad *sink_pad, *remote_pad; > >> + struct device *dev = csi_dev->dev; > >> + u64 mask; > >> + int ret; > >> + > >> + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, > >> + RKCSI_PAD_SRC, &streams_mask); > >> + > >> + ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); > >> + > >> + rkcsi_stop(csi_dev); > >> + > >> + pm_runtime_mark_last_busy(dev); > >> + pm_runtime_put_autosuspend(dev); > >> + > >> + return ret; > >> +} > >> + > >> +static const struct v4l2_subdev_pad_ops rkcsi_pad_ops = { > >> + .enum_mbus_code = rkcsi_enum_mbus_code, > >> + .get_fmt = v4l2_subdev_get_fmt, > >> + .set_fmt = rkcsi_set_fmt, > >> + .set_routing = rkcsi_set_routing, > >> + .enable_streams = rkcsi_enable_streams, > >> + .disable_streams = rkcsi_disable_streams, > >> +}; > >> + > >> +static const struct v4l2_subdev_ops rkcsi_ops = { > >> + .pad = &rkcsi_pad_ops, > >> +}; > >> + > >> +static int rkcsi_init_state(struct v4l2_subdev *sd, > >> + struct v4l2_subdev_state *state) > >> +{ > >> + struct v4l2_subdev_route routes[] = { > >> + { > >> + .sink_pad = RKCSI_PAD_SINK, > >> + .sink_stream = 0, > >> + .source_pad = RKCSI_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, > >> + }; > >> + int ret; > >> + > >> + ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing, > >> + &default_format); > >> + > >> + return ret; > >> +} > >> + > >> +static const struct v4l2_subdev_internal_ops rkcsi_internal_ops = { > >> + .init_state = rkcsi_init_state, > >> +}; > >> + > >> +static int rkcsi_notifier_bound(struct v4l2_async_notifier *notifier, > >> + struct v4l2_subdev *sd, > >> + struct v4l2_async_connection *asd) > >> +{ > >> + struct rkcsi_device *csi_dev = > >> + container_of(notifier, struct rkcsi_device, notifier); > >> + int source_pad; > >> + > >> + source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode, > >> + MEDIA_PAD_FL_SOURCE); > >> + if (source_pad < 0) { > >> + dev_err(csi_dev->dev, "failed to find source pad for %s\n", > >> + sd->name); > >> + return source_pad; > >> + } > >> + > >> + csi_dev->source_sd = sd; > >> + csi_dev->source_pad = source_pad; > >> + > >> + return media_create_pad_link(&sd->entity, source_pad, > >> + &csi_dev->sd.entity, RKCSI_PAD_SINK, > >> + MEDIA_LNK_FL_ENABLED); > >> +} > >> + > >> +static const struct v4l2_async_notifier_operations rkcsi_notifier_ops = { > >> + .bound = rkcsi_notifier_bound, > >> +}; > >> + > >> +static int rkcsi_register_notifier(struct rkcsi_device *csi_dev) > >> +{ > >> + struct v4l2_async_connection *asd; > >> + struct v4l2_async_notifier *ntf = &csi_dev->notifier; > >> + struct v4l2_fwnode_endpoint *vep = &csi_dev->vep; > >> + struct v4l2_subdev *sd = &csi_dev->sd; > >> + struct device *dev = csi_dev->dev; > >> + struct fwnode_handle *ep; > >> + int ret = 0; > >> + > >> + ep = 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) { > >> + ret = dev_err_probe(dev, ret, "failed to parse endpoint\n"); > >> + goto out; > >> + } > >> + > >> + if (vep->bus_type != V4L2_MBUS_CSI2_DPHY && > >> + vep->bus_type != V4L2_MBUS_CSI2_CPHY) { > >> + ret = dev_err_probe(dev, -EINVAL, > >> + "invalid bus type of endpoint\n"); > >> + goto out; > >> + } > >> + > >> + v4l2_async_subdev_nf_init(ntf, sd); > >> + ntf->ops = &rkcsi_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; > >> + } > >> + > >> + goto out; > >> + > >> +err_nf_cleanup: > >> + v4l2_async_nf_cleanup(ntf); > >> +out: > >> + fwnode_handle_put(ep); > >> + return ret; > >> +} > >> + > >> +static int rkcsi_register(struct rkcsi_device *csi_dev) > >> +{ > >> + struct media_pad *pads = csi_dev->pads; > >> + struct v4l2_subdev *sd = &csi_dev->sd; > >> + int ret; > >> + > >> + ret = rkcsi_register_notifier(csi_dev); > >> + if (ret) > >> + goto err; > >> + > >> + v4l2_subdev_init(sd, &rkcsi_ops); > >> + sd->dev = csi_dev->dev; > >> + sd->entity.ops = &rkcsi_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 = &rkcsi_internal_ops; > >> + sd->owner = THIS_MODULE; > >> + snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s", > >> + dev_name(csi_dev->dev)); > >> + > >> + pads[RKCSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK | > >> + MEDIA_PAD_FL_MUST_CONNECT; > >> + pads[RKCSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; > >> + ret = media_entity_pads_init(&sd->entity, RKCSI_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 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(&csi_dev->notifier); > >> + v4l2_async_nf_cleanup(&csi_dev->notifier); > >> +err: > >> + return ret; > >> +} > >> + > >> +static void rkcsi_unregister(struct rkcsi_device *csi_dev) > >> +{ > >> + struct v4l2_subdev *sd = &csi_dev->sd; > >> + > >> + v4l2_async_unregister_subdev(sd); > >> + v4l2_subdev_cleanup(sd); > >> + media_entity_cleanup(&sd->entity); > >> + v4l2_async_nf_unregister(&csi_dev->notifier); > >> + v4l2_async_nf_cleanup(&csi_dev->notifier); > >> +} > >> + > >> +static const struct of_device_id rkcsi_of_match[] = { > >> + { > >> + .compatible = "rockchip,rk3568-mipi-csi", > >> + }, > >> + {} > >> +}; > >> +MODULE_DEVICE_TABLE(of, rkcsi_of_match); > >> + > >> +static int rkcsi_probe(struct platform_device *pdev) > >> +{ > >> + struct device *dev = &pdev->dev; > >> + struct rkcsi_device *csi_dev; > >> + int ret; > >> + > >> + csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL); > >> + if (!csi_dev) > >> + return -ENOMEM; > >> + csi_dev->dev = dev; > >> + dev_set_drvdata(dev, csi_dev); > >> + > >> + csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0); > >> + if (IS_ERR(csi_dev->base_addr)) > >> + return PTR_ERR(csi_dev->base_addr); > >> + > >> + ret = devm_clk_bulk_get_all(dev, &csi_dev->clks); > >> + if (ret != RKCSI_CLKS_MAX) > >> + return dev_err_probe(dev, -ENODEV, "failed to get clocks\n"); > >> + csi_dev->clks_num = ret; > >> + > >> + csi_dev->phy = devm_phy_get(dev, NULL); > >> + if (IS_ERR(csi_dev->phy)) > >> + return dev_err_probe(dev, PTR_ERR(csi_dev->phy), > >> + "failed to get MIPI CSI PHY\n"); > >> + > >> + csi_dev->reset = devm_reset_control_get_exclusive(dev, NULL); > >> + if (IS_ERR(csi_dev->reset)) > >> + return dev_err_probe(dev, PTR_ERR(csi_dev->reset), > >> + "failed to get reset\n"); > >> + > >> + csi_dev->formats = formats; > >> + csi_dev->formats_num = ARRAY_SIZE(formats); > >> + > >> + pm_runtime_enable(dev); > >> + > >> + ret = phy_init(csi_dev->phy); > >> + if (ret) { > >> + ret = dev_err_probe(dev, ret, > >> + "failed to initialize MIPI CSI PHY\n"); > >> + goto err_pm_runtime_disable; > >> + } > >> + > >> + ret = rkcsi_register(csi_dev); > >> + if (ret) > >> + goto err_phy_exit; > >> + > >> + return 0; > >> + > >> +err_phy_exit: > >> + phy_exit(csi_dev->phy); > >> +err_pm_runtime_disable: > >> + pm_runtime_disable(dev); > >> + return ret; > >> +} > >> + > >> +static void rkcsi_remove(struct platform_device *pdev) > >> +{ > >> + struct rkcsi_device *csi_dev = platform_get_drvdata(pdev); > >> + struct device *dev = &pdev->dev; > >> + > >> + rkcsi_unregister(csi_dev); > >> + phy_exit(csi_dev->phy); > >> + pm_runtime_disable(dev); > >> +} > >> + > >> +static int rkcsi_runtime_suspend(struct device *dev) > >> +{ > >> + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); > >> + > >> + clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks); > >> + > >> + return 0; > >> +} > >> + > >> +static int rkcsi_runtime_resume(struct device *dev) > >> +{ > >> + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); > >> + int ret; > >> + > >> + reset_control_assert(csi_dev->reset); > >> + udelay(5); > >> + reset_control_deassert(csi_dev->reset); > >> + > >> + ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks); > >> + if (ret) { > >> + dev_err(dev, "failed to enable clocks\n"); > >> + return ret; > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +static const struct dev_pm_ops rkcsi_pm_ops = { > >> + .runtime_suspend = rkcsi_runtime_suspend, > >> + .runtime_resume = rkcsi_runtime_resume, > >> +}; > >> + > >> +static struct platform_driver rkcsi_drv = { > >> + .driver = { > >> + .name = "rockchip-mipi-csi", > >> + .of_match_table = rkcsi_of_match, > >> + .pm = &rkcsi_pm_ops, > >> + }, > >> + .probe = rkcsi_probe, > >> + .remove = rkcsi_remove, > >> +}; > >> +module_platform_driver(rkcsi_drv); > >> + > >> +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver"); > >> +MODULE_LICENSE("GPL"); > >> > >> -- > >> 2.39.5 > >> > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver 2026-01-13 21:09 ` [PATCH v3 2/3] media: rockchip: add driver for the " Michael Riesch via B4 Relay 2026-01-13 21:59 ` Frank Li @ 2026-01-14 9:34 ` Sakari Ailus 2026-01-15 14:50 ` Michael Riesch 1 sibling, 1 reply; 10+ messages in thread From: Sakari Ailus @ 2026-01-14 9:34 UTC (permalink / raw) To: michael.riesch Cc: Mehdi Djait, Bryan O'Donoghue, Laurent Pinchart, Hans Verkuil, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Philipp Zabel, Sebastian Reichel, Nicolas Dufresne, Collabora Kernel Team, linux-media, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel Hi Michael, Thanks for the set. On Tue, Jan 13, 2026 at 10:09:28PM +0100, Michael Riesch via B4 Relay wrote: > From: Michael Riesch <michael.riesch@collabora.com> > > 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. > > Add a V4L2 subdevice driver for this unit. > > Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net> > Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> > Reviewed-by: Mehdi Djait <mehdi.djait@linux.intel.com> > Signed-off-by: Michael Riesch <michael.riesch@collabora.com> > --- > MAINTAINERS | 1 + > drivers/media/platform/rockchip/Kconfig | 1 + > drivers/media/platform/rockchip/Makefile | 1 + > drivers/media/platform/rockchip/rkcsi/Kconfig | 16 + > drivers/media/platform/rockchip/rkcsi/Makefile | 3 + > drivers/media/platform/rockchip/rkcsi/rkcsi.c | 742 +++++++++++++++++++++++++ > 6 files changed, 764 insertions(+) > > diff --git a/MAINTAINERS b/MAINTAINERS > index dafa8ef7a000..4cfbd82afc3b 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -22641,6 +22641,7 @@ M: Michael Riesch <michael.riesch@collabora.com> > L: linux-media@vger.kernel.org > S: Maintained > F: Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml > +F: drivers/media/platform/rockchip/rkcsi/ > > ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT > M: Daniel Golle <daniel@makrotopia.org> > diff --git a/drivers/media/platform/rockchip/Kconfig b/drivers/media/platform/rockchip/Kconfig > index ba401d32f01b..54b698c4cd2c 100644 > --- a/drivers/media/platform/rockchip/Kconfig > +++ b/drivers/media/platform/rockchip/Kconfig > @@ -4,5 +4,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/rkcsi/Kconfig" > source "drivers/media/platform/rockchip/rkisp1/Kconfig" > source "drivers/media/platform/rockchip/rkvdec/Kconfig" > diff --git a/drivers/media/platform/rockchip/Makefile b/drivers/media/platform/rockchip/Makefile > index 0e0b2cbbd4bd..522a7d3e30b0 100644 > --- a/drivers/media/platform/rockchip/Makefile > +++ b/drivers/media/platform/rockchip/Makefile > @@ -1,5 +1,6 @@ > # SPDX-License-Identifier: GPL-2.0-only > obj-y += rga/ > obj-y += rkcif/ > +obj-y += rkcsi/ > obj-y += rkisp1/ > obj-y += rkvdec/ > diff --git a/drivers/media/platform/rockchip/rkcsi/Kconfig b/drivers/media/platform/rockchip/rkcsi/Kconfig > new file mode 100644 > index 000000000000..d8004198c386 > --- /dev/null > +++ b/drivers/media/platform/rockchip/rkcsi/Kconfig > @@ -0,0 +1,16 @@ > +config VIDEO_ROCKCHIP_CSI > + tristate "Rockchip MIPI CSI-2 Receiver" > + depends on VIDEO_DEV > + depends on ARCH_ROCKCHIP || COMPILE_TEST > + depends on V4L_PLATFORM_DRIVERS > + depends on PM && COMMON_CLK > + select MEDIA_CONTROLLER > + select V4L2_FWNODE > + select VIDEO_V4L2_SUBDEV_API > + help > + This is a driver for Rockchip MIPI CSI-2 Receiver. It is featured > + in various Rockchips SoCs, usually in combination with a Video > + Capture (VICAP) unit (see Rockchip Camera Interface (CIF) driver). > + > + To compile this driver as a module, choose M here: the module > + will be called rockchip-mipi-csi. > diff --git a/drivers/media/platform/rockchip/rkcsi/Makefile b/drivers/media/platform/rockchip/rkcsi/Makefile > new file mode 100644 > index 000000000000..147712cbb68a > --- /dev/null > +++ b/drivers/media/platform/rockchip/rkcsi/Makefile > @@ -0,0 +1,3 @@ > +# SPDX-License-Identifier: GPL-2.0 > +obj-$(CONFIG_VIDEO_ROCKCHIP_CSI) += rockchip-mipi-csi.o > +rockchip-mipi-csi-objs += rkcsi.o > diff --git a/drivers/media/platform/rockchip/rkcsi/rkcsi.c b/drivers/media/platform/rockchip/rkcsi/rkcsi.c > new file mode 100644 > index 000000000000..03597f03f08f > --- /dev/null > +++ b/drivers/media/platform/rockchip/rkcsi/rkcsi.c > @@ -0,0 +1,742 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Rockchip MIPI CSI-2 Receiver Driver > + * > + * Copyright (C) 2019 Rockchip Electronics Co., Ltd. > + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net> > + * Copyright (C) 2026 Collabora, Ltd. > + */ > + > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/io.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_graph.h> > +#include <linux/of_platform.h> Are the above two needed? > +#include <linux/phy/phy.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> #include <linux/property.h> > +#include <linux/reset.h> > + > +#include <media/mipi-csi2.h> > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-fwnode.h> > +#include <media/v4l2-subdev.h> > + > +#define CSI2HOST_N_LANES 0x04 > +#define CSI2HOST_CSI2_RESETN 0x10 > +#define CSI2HOST_PHY_STATE 0x14 > +#define CSI2HOST_ERR1 0x20 > +#define CSI2HOST_ERR2 0x24 > +#define CSI2HOST_MSK1 0x28 > +#define CSI2HOST_MSK2 0x2c > +#define CSI2HOST_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 RKCSI_CLKS_MAX 1 > + > +enum { > + RKCSI_PAD_SINK, > + RKCSI_PAD_SRC, > + RKCSI_PAD_MAX, > +}; > + > +struct rkcsi_format { > + u32 code; > + u8 depth; > + u8 csi_dt; > +}; > + > +struct rkcsi_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 rkcsi_format *formats; > + unsigned int formats_num; > + > + struct media_pad pads[RKCSI_PAD_MAX]; > + struct v4l2_async_notifier notifier; > + struct v4l2_fwnode_endpoint vep; > + struct v4l2_subdev sd; > + > + struct v4l2_subdev *source_sd; > + u32 source_pad; > +}; > + > +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 rkcsi_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 rkcsi_device *to_rkcsi(struct v4l2_subdev *sd) > +{ > + return container_of(sd, struct rkcsi_device, sd); > +} > + > +static inline __maybe_unused void rkcsi_write(struct rkcsi_device *csi_dev, > + unsigned int addr, u32 val) > +{ > + writel(val, csi_dev->base_addr + addr); > +} > + > +static inline __maybe_unused u32 rkcsi_read(struct rkcsi_device *csi_dev, > + unsigned int addr) > +{ > + return readl(csi_dev->base_addr + addr); > +} > + > +static const struct rkcsi_format * > +rkcsi_find_format(struct rkcsi_device *csi_dev, u32 mbus_code) > +{ > + const struct rkcsi_format *format; This can be declared within the loop. > + > + WARN_ON(csi_dev->formats_num == 0); > + > + for (int i = 0; i < csi_dev->formats_num; i++) { unsigned int? > + format = &csi_dev->formats[i]; > + if (format->code == mbus_code) > + return format; > + } > + > + return NULL; > +} > + > +static int rkcsi_start(struct rkcsi_device *csi_dev) > +{ > + struct media_pad *source_pad = > + &csi_dev->source_sd->entity.pads[csi_dev->source_pad]; > + enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type; > + union phy_configure_opts opts; > + s64 link_freq; > + u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes; > + u32 control = 0; > + int ret; > + > + if (lanes < 1 || lanes > 4) > + return -EINVAL; > + > + /* 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 -EINVAL; Could you return link_freq here? > + > + if (bus_type == V4L2_MBUS_CSI2_DPHY) { > + struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy; > + > + ret = phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, > + lanes, cfg); > + if (ret) > + return ret; > + > + ret = phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY); > + if (ret) > + return ret; > + > + ret = phy_configure(csi_dev->phy, &opts); > + if (ret) > + return ret; > + > + control |= SW_CPHY_EN(0); > + > + } else if (bus_type == V4L2_MBUS_CSI2_CPHY) { > + /* TODO: implement CPHY configuration */ > + return -EOPNOTSUPP; How about a switch()? > + } else { > + return -EINVAL; > + } > + > + control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) | > + SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03); > + > + rkcsi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1); > + rkcsi_write(csi_dev, CSI2HOST_CONTROL, control); > + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1); > + > + ret = phy_power_on(csi_dev->phy); return ...; > + if (ret) > + return ret; > + > + return 0; > +} > + > +static void rkcsi_stop(struct rkcsi_device *csi_dev) > +{ > + phy_power_off(csi_dev->phy); > + > + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0); > + rkcsi_write(csi_dev, CSI2HOST_MSK1, ~0); > + rkcsi_write(csi_dev, CSI2HOST_MSK2, ~0); > +} > + > +static const struct media_entity_operations rkcsi_media_ops = { > + .link_validate = v4l2_subdev_link_validate, > +}; > + > +static int rkcsi_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + struct rkcsi_device *csi_dev = to_rkcsi(sd); > + > + if (code->pad == RKCSI_PAD_SRC) { > + const struct v4l2_mbus_framefmt *sink_fmt; > + > + if (code->index) > + return -EINVAL; > + > + sink_fmt = v4l2_subdev_state_get_format(sd_state, > + RKCSI_PAD_SINK); > + code->code = sink_fmt->code; > + > + return 0; > + } else if (code->pad == RKCSI_PAD_SINK) { switch() would be nice here. > + if (code->index > csi_dev->formats_num) > + return -EINVAL; > + > + code->code = csi_dev->formats[code->index].code; > + return 0; > + } > + > + return -EINVAL; > +} > + > +static int rkcsi_set_fmt(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, > + struct v4l2_subdev_format *format) > +{ > + struct rkcsi_device *csi_dev = to_rkcsi(sd); > + const struct rkcsi_format *fmt; > + struct v4l2_mbus_framefmt *sink, *src; > + > + /* the format on the source pad always matches the sink pad */ > + if (format->pad == RKCSI_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 = rkcsi_find_format(csi_dev, 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 rkcsi_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_with_fmt(sd, state, routing, > + &default_format); return ...; > + if (ret) > + return ret; > + > + return 0; > +} > + > +static int rkcsi_enable_streams(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, u32 pad, > + u64 streams_mask) > +{ > + struct rkcsi_device *csi_dev = to_rkcsi(sd); > + struct v4l2_subdev *remote_sd; > + struct media_pad *sink_pad, *remote_pad; > + struct device *dev = csi_dev->dev; > + u64 mask; > + int ret; > + > + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, > + RKCSI_PAD_SRC, &streams_mask); > + > + ret = pm_runtime_resume_and_get(dev); > + if (ret) > + goto err; > + > + ret = rkcsi_start(csi_dev); > + 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: > + rkcsi_stop(csi_dev); > +err_pm_runtime_put: > + pm_runtime_put_sync(dev); > +err: > + return ret; > +} > + > +static int rkcsi_disable_streams(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, u32 pad, > + u64 streams_mask) > +{ > + struct rkcsi_device *csi_dev = to_rkcsi(sd); > + struct v4l2_subdev *remote_sd; > + struct media_pad *sink_pad, *remote_pad; > + struct device *dev = csi_dev->dev; > + u64 mask; > + int ret; > + > + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, > + RKCSI_PAD_SRC, &streams_mask); > + > + ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); > + > + rkcsi_stop(csi_dev); > + > + pm_runtime_mark_last_busy(dev); Redundant, please drop. > + pm_runtime_put_autosuspend(dev); The driver doesn't enable autosuspend so the autosuspend variant isn't useful. Maybe just switch to pm_runtime_put(), as resume probably takes very little time? > + > + return ret; > +} > + > +static const struct v4l2_subdev_pad_ops rkcsi_pad_ops = { > + .enum_mbus_code = rkcsi_enum_mbus_code, > + .get_fmt = v4l2_subdev_get_fmt, > + .set_fmt = rkcsi_set_fmt, > + .set_routing = rkcsi_set_routing, > + .enable_streams = rkcsi_enable_streams, > + .disable_streams = rkcsi_disable_streams, > +}; > + > +static const struct v4l2_subdev_ops rkcsi_ops = { > + .pad = &rkcsi_pad_ops, > +}; > + > +static int rkcsi_init_state(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state) > +{ > + struct v4l2_subdev_route routes[] = { > + { > + .sink_pad = RKCSI_PAD_SINK, > + .sink_stream = 0, > + .source_pad = RKCSI_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, > + }; > + int ret; > + > + ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing, > + &default_format); return ...; > + > + return ret; > +} > + > +static const struct v4l2_subdev_internal_ops rkcsi_internal_ops = { > + .init_state = rkcsi_init_state, > +}; > + > +static int rkcsi_notifier_bound(struct v4l2_async_notifier *notifier, > + struct v4l2_subdev *sd, > + struct v4l2_async_connection *asd) > +{ > + struct rkcsi_device *csi_dev = > + container_of(notifier, struct rkcsi_device, notifier); > + int source_pad; > + > + source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode, > + MEDIA_PAD_FL_SOURCE); Could you use v4l2_create_fwnode_links_to_pad()? > + if (source_pad < 0) { > + dev_err(csi_dev->dev, "failed to find source pad for %s\n", > + sd->name); > + return source_pad; > + } > + > + csi_dev->source_sd = sd; I'd just use media_pad_remote_pad_unique() to obtain the remote pad when needed. > + csi_dev->source_pad = source_pad; > + > + return media_create_pad_link(&sd->entity, source_pad, > + &csi_dev->sd.entity, RKCSI_PAD_SINK, > + MEDIA_LNK_FL_ENABLED); > +} > + > +static const struct v4l2_async_notifier_operations rkcsi_notifier_ops = { > + .bound = rkcsi_notifier_bound, > +}; > + > +static int rkcsi_register_notifier(struct rkcsi_device *csi_dev) > +{ > + struct v4l2_async_connection *asd; > + struct v4l2_async_notifier *ntf = &csi_dev->notifier; > + struct v4l2_fwnode_endpoint *vep = &csi_dev->vep; > + struct v4l2_subdev *sd = &csi_dev->sd; > + struct device *dev = csi_dev->dev; > + struct fwnode_handle *ep; > + int ret = 0; Redundant initialisation. > + > + ep = 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; If you only need the number of lanes and PHY type, I'd store them to the driver's own context struct. struct v4l2_fwnode_endpoint isn't tiny. > + ret = v4l2_fwnode_endpoint_parse(ep, vep); > + if (ret) { > + ret = dev_err_probe(dev, ret, "failed to parse endpoint\n"); > + goto out; > + } > + > + if (vep->bus_type != V4L2_MBUS_CSI2_DPHY && > + vep->bus_type != V4L2_MBUS_CSI2_CPHY) { > + ret = dev_err_probe(dev, -EINVAL, > + "invalid bus type of endpoint\n"); > + goto out; > + } > + > + v4l2_async_subdev_nf_init(ntf, sd); > + ntf->ops = &rkcsi_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; > + } > + > + goto out; > + > +err_nf_cleanup: > + v4l2_async_nf_cleanup(ntf); > +out: > + fwnode_handle_put(ep); > + return ret; > +} > + > +static int rkcsi_register(struct rkcsi_device *csi_dev) > +{ > + struct media_pad *pads = csi_dev->pads; > + struct v4l2_subdev *sd = &csi_dev->sd; > + int ret; > + > + ret = rkcsi_register_notifier(csi_dev); > + if (ret) > + goto err; > + > + v4l2_subdev_init(sd, &rkcsi_ops); > + sd->dev = csi_dev->dev; > + sd->entity.ops = &rkcsi_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 = &rkcsi_internal_ops; > + sd->owner = THIS_MODULE; > + snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s", > + dev_name(csi_dev->dev)); Do you need to give it an explicit name? (v4l2_i2c_subdev_init() already does that.) > + > + pads[RKCSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK | > + MEDIA_PAD_FL_MUST_CONNECT; > + pads[RKCSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; > + ret = media_entity_pads_init(&sd->entity, RKCSI_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 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(&csi_dev->notifier); > + v4l2_async_nf_cleanup(&csi_dev->notifier); > +err: > + return ret; > +} > + > +static void rkcsi_unregister(struct rkcsi_device *csi_dev) > +{ > + struct v4l2_subdev *sd = &csi_dev->sd; > + > + v4l2_async_unregister_subdev(sd); > + v4l2_subdev_cleanup(sd); > + media_entity_cleanup(&sd->entity); > + v4l2_async_nf_unregister(&csi_dev->notifier); > + v4l2_async_nf_cleanup(&csi_dev->notifier); > +} > + > +static const struct of_device_id rkcsi_of_match[] = { > + { > + .compatible = "rockchip,rk3568-mipi-csi", > + }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, rkcsi_of_match); > + > +static int rkcsi_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct rkcsi_device *csi_dev; > + int ret; > + > + csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL); > + if (!csi_dev) > + return -ENOMEM; > + csi_dev->dev = dev; > + dev_set_drvdata(dev, csi_dev); > + > + csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(csi_dev->base_addr)) > + return PTR_ERR(csi_dev->base_addr); > + > + ret = devm_clk_bulk_get_all(dev, &csi_dev->clks); > + if (ret != RKCSI_CLKS_MAX) > + return dev_err_probe(dev, -ENODEV, "failed to get clocks\n"); > + csi_dev->clks_num = ret; > + > + csi_dev->phy = devm_phy_get(dev, NULL); > + if (IS_ERR(csi_dev->phy)) > + return dev_err_probe(dev, PTR_ERR(csi_dev->phy), > + "failed to get MIPI CSI PHY\n"); > + > + csi_dev->reset = devm_reset_control_get_exclusive(dev, NULL); > + if (IS_ERR(csi_dev->reset)) > + return dev_err_probe(dev, PTR_ERR(csi_dev->reset), > + "failed to get reset\n"); > + > + csi_dev->formats = formats; > + csi_dev->formats_num = ARRAY_SIZE(formats); > + > + pm_runtime_enable(dev); You're not resuming the device in probe(). I guess you depend on CONFIG_PM? > + > + ret = phy_init(csi_dev->phy); > + if (ret) { > + ret = dev_err_probe(dev, ret, > + "failed to initialize MIPI CSI PHY\n"); > + goto err_pm_runtime_disable; > + } > + > + ret = rkcsi_register(csi_dev); > + if (ret) > + goto err_phy_exit; > + > + return 0; > + > +err_phy_exit: > + phy_exit(csi_dev->phy); > +err_pm_runtime_disable: > + pm_runtime_disable(dev); A newline would be nice here. > + return ret; > +} > + > +static void rkcsi_remove(struct platform_device *pdev) > +{ > + struct rkcsi_device *csi_dev = platform_get_drvdata(pdev); > + struct device *dev = &pdev->dev; > + > + rkcsi_unregister(csi_dev); > + phy_exit(csi_dev->phy); > + pm_runtime_disable(dev); > +} > + > +static int rkcsi_runtime_suspend(struct device *dev) > +{ > + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); > + > + clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks); > + > + return 0; > +} > + > +static int rkcsi_runtime_resume(struct device *dev) > +{ > + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); > + int ret; > + > + reset_control_assert(csi_dev->reset); > + udelay(5); > + reset_control_deassert(csi_dev->reset); > + > + ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks); > + if (ret) { > + dev_err(dev, "failed to enable clocks\n"); > + return ret; > + } > + > + return 0; > +} > + > +static const struct dev_pm_ops rkcsi_pm_ops = { > + .runtime_suspend = rkcsi_runtime_suspend, > + .runtime_resume = rkcsi_runtime_resume, > +}; Could you use DEFINE_RUNTIME_DEV_PM_OPS() (add static in front)? > + > +static struct platform_driver rkcsi_drv = { > + .driver = { > + .name = "rockchip-mipi-csi", > + .of_match_table = rkcsi_of_match, > + .pm = &rkcsi_pm_ops, > + }, > + .probe = rkcsi_probe, > + .remove = rkcsi_remove, > +}; > +module_platform_driver(rkcsi_drv); > + > +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver"); > +MODULE_LICENSE("GPL"); > -- Kind regards, Sakari Ailus ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver 2026-01-14 9:34 ` Sakari Ailus @ 2026-01-15 14:50 ` Michael Riesch 2026-01-16 12:15 ` Sakari Ailus 0 siblings, 1 reply; 10+ messages in thread From: Michael Riesch @ 2026-01-15 14:50 UTC (permalink / raw) To: Sakari Ailus Cc: Mehdi Djait, Bryan O'Donoghue, Laurent Pinchart, Hans Verkuil, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Philipp Zabel, Sebastian Reichel, Nicolas Dufresne, Collabora Kernel Team, linux-media, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel Hi Sakari, On 1/14/26 10:34, Sakari Ailus wrote: > Hi Michael, > > Thanks for the set. Thanks for the review :-) >> [...] >> diff --git a/drivers/media/platform/rockchip/rkcsi/rkcsi.c b/drivers/media/platform/rockchip/rkcsi/rkcsi.c >> new file mode 100644 >> index 000000000000..03597f03f08f >> --- /dev/null >> +++ b/drivers/media/platform/rockchip/rkcsi/rkcsi.c >> @@ -0,0 +1,742 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +/* >> + * Rockchip MIPI CSI-2 Receiver Driver >> + * >> + * Copyright (C) 2019 Rockchip Electronics Co., Ltd. >> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net> >> + * Copyright (C) 2026 Collabora, Ltd. >> + */ >> + >> +#include <linux/clk.h> >> +#include <linux/delay.h> >> +#include <linux/io.h> >> +#include <linux/module.h> >> +#include <linux/of.h> >> +#include <linux/of_graph.h> >> +#include <linux/of_platform.h> > > Are the above two needed? They are not, apparently. I'll remove them. > >> +#include <linux/phy/phy.h> >> +#include <linux/platform_device.h> >> +#include <linux/pm_runtime.h> > > #include <linux/property.h> Ack. >> +#include <linux/reset.h> >> [...] >> + >> +static const struct rkcsi_format * >> +rkcsi_find_format(struct rkcsi_device *csi_dev, u32 mbus_code) >> +{ >> + const struct rkcsi_format *format; > > This can be declared within the loop. Ack. > >> + >> + WARN_ON(csi_dev->formats_num == 0); >> + >> + for (int i = 0; i < csi_dev->formats_num; i++) { > > unsigned int? Ack. > >> + format = &csi_dev->formats[i]; >> + if (format->code == mbus_code) >> + return format; >> + } >> + >> + return NULL; >> +} >> + >> +static int rkcsi_start(struct rkcsi_device *csi_dev) >> +{ >> + struct media_pad *source_pad = >> + &csi_dev->source_sd->entity.pads[csi_dev->source_pad]; >> + enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type; >> + union phy_configure_opts opts; >> + s64 link_freq; >> + u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes; >> + u32 control = 0; >> + int ret; >> + >> + if (lanes < 1 || lanes > 4) >> + return -EINVAL; >> + >> + /* 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 -EINVAL; > > Could you return link_freq here? Ack. > >> + >> + if (bus_type == V4L2_MBUS_CSI2_DPHY) { >> + struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy; >> + >> + ret = phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, >> + lanes, cfg); >> + if (ret) >> + return ret; >> + >> + ret = phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY); >> + if (ret) >> + return ret; >> + >> + ret = phy_configure(csi_dev->phy, &opts); >> + if (ret) >> + return ret; >> + >> + control |= SW_CPHY_EN(0); >> + >> + } else if (bus_type == V4L2_MBUS_CSI2_CPHY) { >> + /* TODO: implement CPHY configuration */ >> + return -EOPNOTSUPP; > > How about a switch()? Ok! > >> + } else { >> + return -EINVAL; >> + } >> + >> + control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) | >> + SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03); >> + >> + rkcsi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1); >> + rkcsi_write(csi_dev, CSI2HOST_CONTROL, control); >> + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1); >> + >> + ret = phy_power_on(csi_dev->phy); > > return ...; Ack. > >> + if (ret) >> + return ret; >> + >> + return 0; >> +} >> + >> +static void rkcsi_stop(struct rkcsi_device *csi_dev) >> +{ >> + phy_power_off(csi_dev->phy); >> + >> + rkcsi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0); >> + rkcsi_write(csi_dev, CSI2HOST_MSK1, ~0); >> + rkcsi_write(csi_dev, CSI2HOST_MSK2, ~0); >> +} >> + >> +static const struct media_entity_operations rkcsi_media_ops = { >> + .link_validate = v4l2_subdev_link_validate, >> +}; >> + >> +static int rkcsi_enum_mbus_code(struct v4l2_subdev *sd, >> + struct v4l2_subdev_state *sd_state, >> + struct v4l2_subdev_mbus_code_enum *code) >> +{ >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); >> + >> + if (code->pad == RKCSI_PAD_SRC) { >> + const struct v4l2_mbus_framefmt *sink_fmt; >> + >> + if (code->index) >> + return -EINVAL; >> + >> + sink_fmt = v4l2_subdev_state_get_format(sd_state, >> + RKCSI_PAD_SINK); >> + code->code = sink_fmt->code; >> + >> + return 0; >> + } else if (code->pad == RKCSI_PAD_SINK) { > > switch() would be nice here. Ack. > >> + if (code->index > csi_dev->formats_num) >> + return -EINVAL; >> + >> + code->code = csi_dev->formats[code->index].code; >> + return 0; >> + } >> + >> + return -EINVAL; >> +} >> + >> +static int rkcsi_set_fmt(struct v4l2_subdev *sd, >> + struct v4l2_subdev_state *state, >> + struct v4l2_subdev_format *format) >> +{ >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); >> + const struct rkcsi_format *fmt; >> + struct v4l2_mbus_framefmt *sink, *src; >> + >> + /* the format on the source pad always matches the sink pad */ >> + if (format->pad == RKCSI_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 = rkcsi_find_format(csi_dev, 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 rkcsi_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_with_fmt(sd, state, routing, >> + &default_format); > > return ...; Ack. > >> + if (ret) >> + return ret; >> + >> + return 0; >> +} >> + >> +static int rkcsi_enable_streams(struct v4l2_subdev *sd, >> + struct v4l2_subdev_state *state, u32 pad, >> + u64 streams_mask) >> +{ >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); >> + struct v4l2_subdev *remote_sd; >> + struct media_pad *sink_pad, *remote_pad; >> + struct device *dev = csi_dev->dev; >> + u64 mask; >> + int ret; >> + >> + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, >> + RKCSI_PAD_SRC, &streams_mask); >> + >> + ret = pm_runtime_resume_and_get(dev); >> + if (ret) >> + goto err; >> + >> + ret = rkcsi_start(csi_dev); >> + 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: >> + rkcsi_stop(csi_dev); >> +err_pm_runtime_put: >> + pm_runtime_put_sync(dev); >> +err: >> + return ret; >> +} >> + >> +static int rkcsi_disable_streams(struct v4l2_subdev *sd, >> + struct v4l2_subdev_state *state, u32 pad, >> + u64 streams_mask) >> +{ >> + struct rkcsi_device *csi_dev = to_rkcsi(sd); >> + struct v4l2_subdev *remote_sd; >> + struct media_pad *sink_pad, *remote_pad; >> + struct device *dev = csi_dev->dev; >> + u64 mask; >> + int ret; >> + >> + sink_pad = &sd->entity.pads[RKCSI_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, RKCSI_PAD_SINK, >> + RKCSI_PAD_SRC, &streams_mask); >> + >> + ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask); >> + >> + rkcsi_stop(csi_dev); >> + >> + pm_runtime_mark_last_busy(dev); > > Redundant, please drop. Ack. > >> + pm_runtime_put_autosuspend(dev); > > The driver doesn't enable autosuspend so the autosuspend variant isn't > useful. Maybe just switch to pm_runtime_put(), as resume probably takes > very little time? Ack. > >> + >> + return ret; >> +} >> + >> +static const struct v4l2_subdev_pad_ops rkcsi_pad_ops = { >> + .enum_mbus_code = rkcsi_enum_mbus_code, >> + .get_fmt = v4l2_subdev_get_fmt, >> + .set_fmt = rkcsi_set_fmt, >> + .set_routing = rkcsi_set_routing, >> + .enable_streams = rkcsi_enable_streams, >> + .disable_streams = rkcsi_disable_streams, >> +}; >> + >> +static const struct v4l2_subdev_ops rkcsi_ops = { >> + .pad = &rkcsi_pad_ops, >> +}; >> + >> +static int rkcsi_init_state(struct v4l2_subdev *sd, >> + struct v4l2_subdev_state *state) >> +{ >> + struct v4l2_subdev_route routes[] = { >> + { >> + .sink_pad = RKCSI_PAD_SINK, >> + .sink_stream = 0, >> + .source_pad = RKCSI_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, >> + }; >> + int ret; >> + >> + ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing, >> + &default_format); > > return ...; Ack. > >> + >> + return ret; >> +} >> + >> +static const struct v4l2_subdev_internal_ops rkcsi_internal_ops = { >> + .init_state = rkcsi_init_state, >> +}; >> + >> +static int rkcsi_notifier_bound(struct v4l2_async_notifier *notifier, >> + struct v4l2_subdev *sd, >> + struct v4l2_async_connection *asd) >> +{ >> + struct rkcsi_device *csi_dev = >> + container_of(notifier, struct rkcsi_device, notifier); >> + int source_pad; >> + >> + source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode, >> + MEDIA_PAD_FL_SOURCE); > > Could you use v4l2_create_fwnode_links_to_pad()? Ack. > >> + if (source_pad < 0) { >> + dev_err(csi_dev->dev, "failed to find source pad for %s\n", >> + sd->name); >> + return source_pad; >> + } >> + >> + csi_dev->source_sd = sd; > > I'd just use media_pad_remote_pad_unique() to obtain the remote pad when > needed. Ack. > >> + csi_dev->source_pad = source_pad; >> + >> + return media_create_pad_link(&sd->entity, source_pad, >> + &csi_dev->sd.entity, RKCSI_PAD_SINK, >> + MEDIA_LNK_FL_ENABLED); >> +} >> + >> +static const struct v4l2_async_notifier_operations rkcsi_notifier_ops = { >> + .bound = rkcsi_notifier_bound, >> +}; >> + >> +static int rkcsi_register_notifier(struct rkcsi_device *csi_dev) >> +{ >> + struct v4l2_async_connection *asd; >> + struct v4l2_async_notifier *ntf = &csi_dev->notifier; >> + struct v4l2_fwnode_endpoint *vep = &csi_dev->vep; >> + struct v4l2_subdev *sd = &csi_dev->sd; >> + struct device *dev = csi_dev->dev; >> + struct fwnode_handle *ep; >> + int ret = 0; > > Redundant initialisation. Ack. > >> + >> + ep = 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; > > If you only need the number of lanes and PHY type, I'd store them to the > driver's own context struct. struct v4l2_fwnode_endpoint isn't tiny. Ack. > >> + ret = v4l2_fwnode_endpoint_parse(ep, vep); >> + if (ret) { >> + ret = dev_err_probe(dev, ret, "failed to parse endpoint\n"); >> + goto out; >> + } >> + >> + if (vep->bus_type != V4L2_MBUS_CSI2_DPHY && >> + vep->bus_type != V4L2_MBUS_CSI2_CPHY) { >> + ret = dev_err_probe(dev, -EINVAL, >> + "invalid bus type of endpoint\n"); >> + goto out; >> + } >> + >> + v4l2_async_subdev_nf_init(ntf, sd); >> + ntf->ops = &rkcsi_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; >> + } >> + >> + goto out; >> + >> +err_nf_cleanup: >> + v4l2_async_nf_cleanup(ntf); >> +out: >> + fwnode_handle_put(ep); >> + return ret; >> +} >> + >> +static int rkcsi_register(struct rkcsi_device *csi_dev) >> +{ >> + struct media_pad *pads = csi_dev->pads; >> + struct v4l2_subdev *sd = &csi_dev->sd; >> + int ret; >> + >> + ret = rkcsi_register_notifier(csi_dev); >> + if (ret) >> + goto err; >> + >> + v4l2_subdev_init(sd, &rkcsi_ops); >> + sd->dev = csi_dev->dev; >> + sd->entity.ops = &rkcsi_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 = &rkcsi_internal_ops; >> + sd->owner = THIS_MODULE; >> + snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s", >> + dev_name(csi_dev->dev)); > > Do you need to give it an explicit name? (v4l2_i2c_subdev_init() already > does that.) I do, actually. I don't call the _i2c_ variant. This is a platform device. > >> + >> + pads[RKCSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK | >> + MEDIA_PAD_FL_MUST_CONNECT; >> + pads[RKCSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; >> + ret = media_entity_pads_init(&sd->entity, RKCSI_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 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(&csi_dev->notifier); >> + v4l2_async_nf_cleanup(&csi_dev->notifier); >> +err: >> + return ret; >> +} >> + >> +static void rkcsi_unregister(struct rkcsi_device *csi_dev) >> +{ >> + struct v4l2_subdev *sd = &csi_dev->sd; >> + >> + v4l2_async_unregister_subdev(sd); >> + v4l2_subdev_cleanup(sd); >> + media_entity_cleanup(&sd->entity); >> + v4l2_async_nf_unregister(&csi_dev->notifier); >> + v4l2_async_nf_cleanup(&csi_dev->notifier); >> +} >> + >> +static const struct of_device_id rkcsi_of_match[] = { >> + { >> + .compatible = "rockchip,rk3568-mipi-csi", >> + }, >> + {} >> +}; >> +MODULE_DEVICE_TABLE(of, rkcsi_of_match); >> + >> +static int rkcsi_probe(struct platform_device *pdev) >> +{ >> + struct device *dev = &pdev->dev; >> + struct rkcsi_device *csi_dev; >> + int ret; >> + >> + csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL); >> + if (!csi_dev) >> + return -ENOMEM; >> + csi_dev->dev = dev; >> + dev_set_drvdata(dev, csi_dev); >> + >> + csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0); >> + if (IS_ERR(csi_dev->base_addr)) >> + return PTR_ERR(csi_dev->base_addr); >> + >> + ret = devm_clk_bulk_get_all(dev, &csi_dev->clks); >> + if (ret != RKCSI_CLKS_MAX) >> + return dev_err_probe(dev, -ENODEV, "failed to get clocks\n"); >> + csi_dev->clks_num = ret; >> + >> + csi_dev->phy = devm_phy_get(dev, NULL); >> + if (IS_ERR(csi_dev->phy)) >> + return dev_err_probe(dev, PTR_ERR(csi_dev->phy), >> + "failed to get MIPI CSI PHY\n"); >> + >> + csi_dev->reset = devm_reset_control_get_exclusive(dev, NULL); >> + if (IS_ERR(csi_dev->reset)) >> + return dev_err_probe(dev, PTR_ERR(csi_dev->reset), >> + "failed to get reset\n"); >> + >> + csi_dev->formats = formats; >> + csi_dev->formats_num = ARRAY_SIZE(formats); >> + >> + pm_runtime_enable(dev); > > You're not resuming the device in probe(). I guess you depend on CONFIG_PM? I do depend on CONFIG_PM. I don't think there is need to resume in probe -> resume in _enable_streams should do the trick, right? > >> + >> + ret = phy_init(csi_dev->phy); >> + if (ret) { >> + ret = dev_err_probe(dev, ret, >> + "failed to initialize MIPI CSI PHY\n"); >> + goto err_pm_runtime_disable; >> + } >> + >> + ret = rkcsi_register(csi_dev); >> + if (ret) >> + goto err_phy_exit; >> + >> + return 0; >> + >> +err_phy_exit: >> + phy_exit(csi_dev->phy); >> +err_pm_runtime_disable: >> + pm_runtime_disable(dev); > > A newline would be nice here. Ack. > >> + return ret; >> +} >> + >> +static void rkcsi_remove(struct platform_device *pdev) >> +{ >> + struct rkcsi_device *csi_dev = platform_get_drvdata(pdev); >> + struct device *dev = &pdev->dev; >> + >> + rkcsi_unregister(csi_dev); >> + phy_exit(csi_dev->phy); >> + pm_runtime_disable(dev); >> +} >> + >> +static int rkcsi_runtime_suspend(struct device *dev) >> +{ >> + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); >> + >> + clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks); >> + >> + return 0; >> +} >> + >> +static int rkcsi_runtime_resume(struct device *dev) >> +{ >> + struct rkcsi_device *csi_dev = dev_get_drvdata(dev); >> + int ret; >> + >> + reset_control_assert(csi_dev->reset); >> + udelay(5); >> + reset_control_deassert(csi_dev->reset); >> + >> + ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks); >> + if (ret) { >> + dev_err(dev, "failed to enable clocks\n"); >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static const struct dev_pm_ops rkcsi_pm_ops = { >> + .runtime_suspend = rkcsi_runtime_suspend, >> + .runtime_resume = rkcsi_runtime_resume, >> +}; > > Could you use DEFINE_RUNTIME_DEV_PM_OPS() (add static in front)? Ack. v4 coming up soon! Best regards, Michael > >> + >> +static struct platform_driver rkcsi_drv = { >> + .driver = { >> + .name = "rockchip-mipi-csi", >> + .of_match_table = rkcsi_of_match, >> + .pm = &rkcsi_pm_ops, >> + }, >> + .probe = rkcsi_probe, >> + .remove = rkcsi_remove, >> +}; >> +module_platform_driver(rkcsi_drv); >> + >> +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver"); >> +MODULE_LICENSE("GPL"); >> > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver 2026-01-15 14:50 ` Michael Riesch @ 2026-01-16 12:15 ` Sakari Ailus 0 siblings, 0 replies; 10+ messages in thread From: Sakari Ailus @ 2026-01-16 12:15 UTC (permalink / raw) To: Michael Riesch Cc: Mehdi Djait, Bryan O'Donoghue, Laurent Pinchart, Hans Verkuil, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Philipp Zabel, Sebastian Reichel, Nicolas Dufresne, Collabora Kernel Team, linux-media, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel Hi Michael, On Thu, Jan 15, 2026 at 03:50:59PM +0100, Michael Riesch wrote: > >> + /* 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 -EINVAL; > > > > Could you return link_freq here? > > Ack. You should also test for < 0; v4l2_get_link_freq() returns the frequency or an error (and 0 isn't valid). ... > >> +static int rkcsi_register(struct rkcsi_device *csi_dev) > >> +{ > >> + struct media_pad *pads = csi_dev->pads; > >> + struct v4l2_subdev *sd = &csi_dev->sd; > >> + int ret; > >> + > >> + ret = rkcsi_register_notifier(csi_dev); > >> + if (ret) > >> + goto err; > >> + > >> + v4l2_subdev_init(sd, &rkcsi_ops); > >> + sd->dev = csi_dev->dev; > >> + sd->entity.ops = &rkcsi_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 = &rkcsi_internal_ops; > >> + sd->owner = THIS_MODULE; > >> + snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s", > >> + dev_name(csi_dev->dev)); > > > > Do you need to give it an explicit name? (v4l2_i2c_subdev_init() already > > does that.) > > I do, actually. I don't call the _i2c_ variant. This is a platform device. Ah, I somehow thought this would be an I²C device. Please ignore the comment then. ... > >> + pm_runtime_enable(dev); > > > > You're not resuming the device in probe(). I guess you depend on CONFIG_PM? > > I do depend on CONFIG_PM. > > I don't think there is need to resume in probe -> resume in > _enable_streams should do the trick, right? Ack; that's fine with CONFIG_PM. -- Kind regards, Sakari Ailus ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 3/3] arm64: defconfig: enable rockchip mipi csi-2 receiver 2026-01-13 21:09 [PATCH v3 0/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver Michael Riesch via B4 Relay 2026-01-13 21:09 ` [PATCH v3 1/3] media: dt-bindings: add " Michael Riesch via B4 Relay 2026-01-13 21:09 ` [PATCH v3 2/3] media: rockchip: add driver for the " Michael Riesch via B4 Relay @ 2026-01-13 21:09 ` Michael Riesch via B4 Relay 2 siblings, 0 replies; 10+ messages in thread From: Michael Riesch via B4 Relay @ 2026-01-13 21:09 UTC (permalink / raw) To: Michael Riesch, Mehdi Djait, Bryan O'Donoghue, Laurent Pinchart, Hans Verkuil, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Philipp Zabel, Sebastian Reichel, Nicolas Dufresne, Collabora Kernel Team, Sakari Ailus Cc: linux-media, devicetree, linux-arm-kernel, linux-rockchip, linux-kernel From: Michael Riesch <michael.riesch@collabora.com> The Rockchip MIPI CSI-2 Receiver is integrated into recent Rockchip SoCs, such as the RK3568 and the RK3588. Enable the driver for it in the default configuration. Signed-off-by: Michael Riesch <michael.riesch@collabora.com> --- arch/arm64/configs/defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 45288ec9eaf7..898e90b79332 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -896,6 +896,7 @@ CONFIG_VIDEO_RENESAS_FDP1=m CONFIG_VIDEO_RENESAS_VSP1=m CONFIG_VIDEO_RCAR_DRIF=m CONFIG_VIDEO_ROCKCHIP_CIF=m +CONFIG_VIDEO_ROCKCHIP_CSI=m CONFIG_VIDEO_ROCKCHIP_RGA=m CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC=m CONFIG_VIDEO_SAMSUNG_S5P_JPEG=m -- 2.39.5 ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-01-16 12:15 UTC | newest] Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-01-13 21:09 [PATCH v3 0/3] media: rockchip: add driver for the rockchip mipi csi-2 receiver Michael Riesch via B4 Relay 2026-01-13 21:09 ` [PATCH v3 1/3] media: dt-bindings: add " Michael Riesch via B4 Relay 2026-01-13 21:09 ` [PATCH v3 2/3] media: rockchip: add driver for the " Michael Riesch via B4 Relay 2026-01-13 21:59 ` Frank Li 2026-01-13 22:44 ` Michael Riesch 2026-01-13 23:00 ` Frank Li 2026-01-14 9:34 ` Sakari Ailus 2026-01-15 14:50 ` Michael Riesch 2026-01-16 12:15 ` Sakari Ailus 2026-01-13 21:09 ` [PATCH v3 3/3] arm64: defconfig: enable " Michael Riesch via B4 Relay
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®