Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

media: rockchip: add driver for the rockchip camera interface

Add the skeleton for a media controller centric V4L2 driver for the
Rockchip Camera Interface (CIF).
The skeleton features support for the PX30 Video Input Processor (VIP)
and the RK3568 Video Capture (VICAP) unit.

Tested-by: Gerald Loacker <gerald.loacker@wolfvision.net>
Reviewed-by: Gerald Loacker <gerald.loacker@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>
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>

authored by

Michael Riesch and committed by
Hans Verkuil
f53fb31a 60836eec

+342
+1
MAINTAINERS
··· 22271 22271 F: Documentation/admin-guide/media/rkcif* 22272 22272 F: Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml 22273 22273 F: Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml 22274 + F: drivers/media/platform/rockchip/rkcif/ 22274 22275 22275 22276 ROCKCHIP CRYPTO DRIVERS 22276 22277 M: Corentin Labbe <clabbe@baylibre.com>
+1
drivers/media/platform/rockchip/Kconfig
··· 3 3 comment "Rockchip media platform drivers" 4 4 5 5 source "drivers/media/platform/rockchip/rga/Kconfig" 6 + source "drivers/media/platform/rockchip/rkcif/Kconfig" 6 7 source "drivers/media/platform/rockchip/rkisp1/Kconfig" 7 8 source "drivers/media/platform/rockchip/rkvdec/Kconfig"
+1
drivers/media/platform/rockchip/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0-only 2 2 obj-y += rga/ 3 + obj-y += rkcif/ 3 4 obj-y += rkisp1/ 4 5 obj-y += rkvdec/
+18
drivers/media/platform/rockchip/rkcif/Kconfig
··· 1 + config VIDEO_ROCKCHIP_CIF 2 + tristate "Rockchip Camera Interface (CIF)" 3 + depends on VIDEO_DEV 4 + depends on ARCH_ROCKCHIP || COMPILE_TEST 5 + depends on V4L_PLATFORM_DRIVERS 6 + depends on PM && COMMON_CLK 7 + select MEDIA_CONTROLLER 8 + select VIDEOBUF2_DMA_CONTIG 9 + select V4L2_FWNODE 10 + select VIDEO_V4L2_SUBDEV_API 11 + help 12 + This is a driver for Rockchip Camera Interface (CIF). It is featured 13 + in many Rockchips SoCs in different variations, such as the PX30 14 + Video Input Processor (VIP, one Digital Video Port (DVP)) or the 15 + RK3568 Video Capture (VICAP, one DVP, one MIPI CSI-2 receiver) unit. 16 + 17 + To compile this driver as a module, choose M here: the module 18 + will be called rockchip-cif.
+4
drivers/media/platform/rockchip/rkcif/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-cif.o 3 + 4 + rockchip-cif-objs += rkcif-dev.o
+55
drivers/media/platform/rockchip/rkcif/rkcif-common.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Rockchip Camera Interface (CIF) Driver 4 + * 5 + * Copyright (C) 2018 Rockchip Electronics Co., Ltd. 6 + * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com> 7 + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net> 8 + * Copyright (C) 2025 Collabora, Ltd. 9 + */ 10 + 11 + #ifndef _RKCIF_COMMON_H 12 + #define _RKCIF_COMMON_H 13 + 14 + #include <linux/clk.h> 15 + #include <linux/mutex.h> 16 + #include <linux/regmap.h> 17 + 18 + #include <media/media-device.h> 19 + #include <media/media-entity.h> 20 + #include <media/v4l2-common.h> 21 + #include <media/v4l2-device.h> 22 + #include <media/v4l2-fwnode.h> 23 + #include <media/v4l2-mc.h> 24 + #include <media/v4l2-subdev.h> 25 + #include <media/videobuf2-v4l2.h> 26 + 27 + #define RKCIF_DRIVER_NAME "rockchip-cif" 28 + #define RKCIF_CLK_MAX 4 29 + 30 + struct rkcif_remote { 31 + struct v4l2_async_connection async_conn; 32 + struct v4l2_subdev *sd; 33 + }; 34 + 35 + struct rkcif_match_data { 36 + const char *const *clks; 37 + unsigned int clks_num; 38 + }; 39 + 40 + struct rkcif_device { 41 + struct device *dev; 42 + 43 + const struct rkcif_match_data *match_data; 44 + struct clk_bulk_data clks[RKCIF_CLK_MAX]; 45 + unsigned int clks_num; 46 + struct regmap *grf; 47 + struct reset_control *reset; 48 + void __iomem *base_addr; 49 + 50 + struct media_device media_dev; 51 + struct v4l2_device v4l2_dev; 52 + struct v4l2_async_notifier notifier; 53 + }; 54 + 55 + #endif
+262
drivers/media/platform/rockchip/rkcif/rkcif-dev.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Rockchip Camera Interface (CIF) Driver 4 + * 5 + * Copyright (C) 2018 Rockchip Electronics Co., Ltd. 6 + * Copyright (C) 2020 Maxime Chevallier <maxime.chevallier@bootlin.com> 7 + * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com> 8 + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net> 9 + * Copyright (C) 2025 Collabora, Ltd. 10 + */ 11 + 12 + #include <linux/clk.h> 13 + #include <linux/delay.h> 14 + #include <linux/interrupt.h> 15 + #include <linux/mfd/syscon.h> 16 + #include <linux/module.h> 17 + #include <linux/of.h> 18 + #include <linux/of_graph.h> 19 + #include <linux/of_platform.h> 20 + #include <linux/pm_runtime.h> 21 + #include <linux/reset.h> 22 + 23 + #include <media/v4l2-fwnode.h> 24 + #include <media/v4l2-mc.h> 25 + 26 + #include "rkcif-common.h" 27 + 28 + static const char *const px30_vip_clks[] = { 29 + "aclk", 30 + "hclk", 31 + "pclk", 32 + }; 33 + 34 + static const struct rkcif_match_data px30_vip_match_data = { 35 + .clks = px30_vip_clks, 36 + .clks_num = ARRAY_SIZE(px30_vip_clks), 37 + }; 38 + 39 + static const char *const rk3568_vicap_clks[] = { 40 + "aclk", 41 + "hclk", 42 + "dclk", 43 + "iclk", 44 + }; 45 + 46 + static const struct rkcif_match_data rk3568_vicap_match_data = { 47 + .clks = rk3568_vicap_clks, 48 + .clks_num = ARRAY_SIZE(rk3568_vicap_clks), 49 + }; 50 + 51 + static const struct of_device_id rkcif_plat_of_match[] = { 52 + { 53 + .compatible = "rockchip,px30-vip", 54 + .data = &px30_vip_match_data, 55 + }, 56 + { 57 + .compatible = "rockchip,rk3568-vicap", 58 + .data = &rk3568_vicap_match_data, 59 + }, 60 + {} 61 + }; 62 + MODULE_DEVICE_TABLE(of, rkcif_plat_of_match); 63 + 64 + static int rkcif_register(struct rkcif_device *rkcif) 65 + { 66 + return 0; 67 + } 68 + 69 + static void rkcif_unregister(struct rkcif_device *rkcif) 70 + { 71 + } 72 + 73 + static int rkcif_notifier_bound(struct v4l2_async_notifier *notifier, 74 + struct v4l2_subdev *sd, 75 + struct v4l2_async_connection *asd) 76 + { 77 + struct rkcif_remote *remote = 78 + container_of(asd, struct rkcif_remote, async_conn); 79 + 80 + remote->sd = sd; 81 + 82 + return 0; 83 + } 84 + 85 + static int rkcif_notifier_complete(struct v4l2_async_notifier *notifier) 86 + { 87 + struct rkcif_device *rkcif = 88 + container_of(notifier, struct rkcif_device, notifier); 89 + 90 + return v4l2_device_register_subdev_nodes(&rkcif->v4l2_dev); 91 + } 92 + 93 + static const struct v4l2_async_notifier_operations rkcif_notifier_ops = { 94 + .bound = rkcif_notifier_bound, 95 + .complete = rkcif_notifier_complete, 96 + }; 97 + 98 + static irqreturn_t rkcif_isr(int irq, void *ctx) 99 + { 100 + irqreturn_t ret = IRQ_NONE; 101 + 102 + return ret; 103 + } 104 + 105 + static int rkcif_probe(struct platform_device *pdev) 106 + { 107 + struct device *dev = &pdev->dev; 108 + struct rkcif_device *rkcif; 109 + int ret, irq; 110 + 111 + rkcif = devm_kzalloc(dev, sizeof(*rkcif), GFP_KERNEL); 112 + if (!rkcif) 113 + return -ENOMEM; 114 + 115 + rkcif->match_data = of_device_get_match_data(dev); 116 + if (!rkcif->match_data) 117 + return -ENODEV; 118 + 119 + dev_set_drvdata(dev, rkcif); 120 + rkcif->dev = dev; 121 + 122 + rkcif->base_addr = devm_platform_ioremap_resource(pdev, 0); 123 + if (IS_ERR(rkcif->base_addr)) 124 + return PTR_ERR(rkcif->base_addr); 125 + 126 + irq = platform_get_irq(pdev, 0); 127 + if (irq < 0) 128 + return irq; 129 + 130 + ret = devm_request_irq(dev, irq, rkcif_isr, IRQF_SHARED, 131 + dev_driver_string(dev), dev); 132 + if (ret) 133 + return dev_err_probe(dev, ret, "failed to request irq\n"); 134 + 135 + if (rkcif->match_data->clks_num > RKCIF_CLK_MAX) 136 + return dev_err_probe(dev, -EINVAL, "invalid number of clocks\n"); 137 + 138 + rkcif->clks_num = rkcif->match_data->clks_num; 139 + for (unsigned int i = 0; i < rkcif->clks_num; i++) 140 + rkcif->clks[i].id = rkcif->match_data->clks[i]; 141 + ret = devm_clk_bulk_get(dev, rkcif->clks_num, rkcif->clks); 142 + if (ret) 143 + return dev_err_probe(dev, ret, "failed to get clocks\n"); 144 + 145 + rkcif->reset = devm_reset_control_array_get_exclusive(dev); 146 + if (IS_ERR(rkcif->reset)) 147 + return PTR_ERR(rkcif->reset); 148 + 149 + rkcif->grf = syscon_regmap_lookup_by_phandle(dev->of_node, 150 + "rockchip,grf"); 151 + if (IS_ERR(rkcif->grf)) 152 + rkcif->grf = NULL; 153 + 154 + pm_runtime_enable(&pdev->dev); 155 + 156 + rkcif->media_dev.dev = dev; 157 + strscpy(rkcif->media_dev.model, RKCIF_DRIVER_NAME, 158 + sizeof(rkcif->media_dev.model)); 159 + media_device_init(&rkcif->media_dev); 160 + 161 + rkcif->v4l2_dev.mdev = &rkcif->media_dev; 162 + ret = v4l2_device_register(dev, &rkcif->v4l2_dev); 163 + if (ret) 164 + goto err_media_dev_cleanup; 165 + 166 + ret = media_device_register(&rkcif->media_dev); 167 + if (ret < 0) { 168 + dev_err(dev, "failed to register media device: %d\n", ret); 169 + goto err_v4l2_dev_unregister; 170 + } 171 + 172 + v4l2_async_nf_init(&rkcif->notifier, &rkcif->v4l2_dev); 173 + rkcif->notifier.ops = &rkcif_notifier_ops; 174 + 175 + ret = rkcif_register(rkcif); 176 + if (ret) { 177 + dev_err(dev, "failed to register media entities: %d\n", ret); 178 + goto err_notifier_cleanup; 179 + } 180 + 181 + ret = v4l2_async_nf_register(&rkcif->notifier); 182 + if (ret) 183 + goto err_rkcif_unregister; 184 + 185 + return 0; 186 + 187 + err_rkcif_unregister: 188 + rkcif_unregister(rkcif); 189 + err_notifier_cleanup: 190 + v4l2_async_nf_cleanup(&rkcif->notifier); 191 + media_device_unregister(&rkcif->media_dev); 192 + err_v4l2_dev_unregister: 193 + v4l2_device_unregister(&rkcif->v4l2_dev); 194 + err_media_dev_cleanup: 195 + media_device_cleanup(&rkcif->media_dev); 196 + pm_runtime_disable(&pdev->dev); 197 + return ret; 198 + } 199 + 200 + static void rkcif_remove(struct platform_device *pdev) 201 + { 202 + struct rkcif_device *rkcif = platform_get_drvdata(pdev); 203 + 204 + v4l2_async_nf_unregister(&rkcif->notifier); 205 + rkcif_unregister(rkcif); 206 + v4l2_async_nf_cleanup(&rkcif->notifier); 207 + media_device_unregister(&rkcif->media_dev); 208 + v4l2_device_unregister(&rkcif->v4l2_dev); 209 + media_device_cleanup(&rkcif->media_dev); 210 + pm_runtime_disable(&pdev->dev); 211 + } 212 + 213 + static int rkcif_runtime_suspend(struct device *dev) 214 + { 215 + struct rkcif_device *rkcif = dev_get_drvdata(dev); 216 + 217 + /* 218 + * Reset CIF (CRU, DMA, FIFOs) to allow a clean resume. 219 + * Since this resets the IOMMU too, we cannot issue this reset when 220 + * resuming. 221 + */ 222 + reset_control_assert(rkcif->reset); 223 + udelay(5); 224 + reset_control_deassert(rkcif->reset); 225 + 226 + clk_bulk_disable_unprepare(rkcif->clks_num, rkcif->clks); 227 + 228 + return 0; 229 + } 230 + 231 + static int rkcif_runtime_resume(struct device *dev) 232 + { 233 + struct rkcif_device *rkcif = dev_get_drvdata(dev); 234 + int ret; 235 + 236 + ret = clk_bulk_prepare_enable(rkcif->clks_num, rkcif->clks); 237 + if (ret) { 238 + dev_err(dev, "failed to enable clocks\n"); 239 + return ret; 240 + } 241 + 242 + return 0; 243 + } 244 + 245 + static const struct dev_pm_ops rkcif_plat_pm_ops = { 246 + .runtime_suspend = rkcif_runtime_suspend, 247 + .runtime_resume = rkcif_runtime_resume, 248 + }; 249 + 250 + static struct platform_driver rkcif_plat_drv = { 251 + .driver = { 252 + .name = RKCIF_DRIVER_NAME, 253 + .of_match_table = rkcif_plat_of_match, 254 + .pm = &rkcif_plat_pm_ops, 255 + }, 256 + .probe = rkcif_probe, 257 + .remove = rkcif_remove, 258 + }; 259 + module_platform_driver(rkcif_plat_drv); 260 + 261 + MODULE_DESCRIPTION("Rockchip Camera Interface (CIF) platform driver"); 262 + MODULE_LICENSE("GPL");