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>
This commit is contained in:
Michael Riesch
2025-11-14 16:20:16 +01:00
committed by Hans Verkuil
parent 60836eeca0
commit f53fb31a38
7 changed files with 342 additions and 0 deletions

View File

@@ -22271,6 +22271,7 @@ S: Maintained
F: Documentation/admin-guide/media/rkcif*
F: Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml
F: Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml
F: drivers/media/platform/rockchip/rkcif/
ROCKCHIP CRYPTO DRIVERS
M: Corentin Labbe <clabbe@baylibre.com>

View File

@@ -3,5 +3,6 @@
comment "Rockchip media platform drivers"
source "drivers/media/platform/rockchip/rga/Kconfig"
source "drivers/media/platform/rockchip/rkcif/Kconfig"
source "drivers/media/platform/rockchip/rkisp1/Kconfig"
source "drivers/media/platform/rockchip/rkvdec/Kconfig"

View File

@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += rga/
obj-y += rkcif/
obj-y += rkisp1/
obj-y += rkvdec/

View File

@@ -0,0 +1,18 @@
config VIDEO_ROCKCHIP_CIF
tristate "Rockchip Camera Interface (CIF)"
depends on VIDEO_DEV
depends on ARCH_ROCKCHIP || COMPILE_TEST
depends on V4L_PLATFORM_DRIVERS
depends on PM && COMMON_CLK
select MEDIA_CONTROLLER
select VIDEOBUF2_DMA_CONTIG
select V4L2_FWNODE
select VIDEO_V4L2_SUBDEV_API
help
This is a driver for Rockchip Camera Interface (CIF). It is featured
in many Rockchips SoCs in different variations, such as the PX30
Video Input Processor (VIP, one Digital Video Port (DVP)) or the
RK3568 Video Capture (VICAP, one DVP, one MIPI CSI-2 receiver) unit.
To compile this driver as a module, choose M here: the module
will be called rockchip-cif.

View File

@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-cif.o
rockchip-cif-objs += rkcif-dev.o

View File

@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Rockchip Camera Interface (CIF) Driver
*
* Copyright (C) 2018 Rockchip Electronics Co., Ltd.
* Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
* Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
* Copyright (C) 2025 Collabora, Ltd.
*/
#ifndef _RKCIF_COMMON_H
#define _RKCIF_COMMON_H
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <media/media-device.h>
#include <media/media-entity.h>
#include <media/v4l2-common.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-mc.h>
#include <media/v4l2-subdev.h>
#include <media/videobuf2-v4l2.h>
#define RKCIF_DRIVER_NAME "rockchip-cif"
#define RKCIF_CLK_MAX 4
struct rkcif_remote {
struct v4l2_async_connection async_conn;
struct v4l2_subdev *sd;
};
struct rkcif_match_data {
const char *const *clks;
unsigned int clks_num;
};
struct rkcif_device {
struct device *dev;
const struct rkcif_match_data *match_data;
struct clk_bulk_data clks[RKCIF_CLK_MAX];
unsigned int clks_num;
struct regmap *grf;
struct reset_control *reset;
void __iomem *base_addr;
struct media_device media_dev;
struct v4l2_device v4l2_dev;
struct v4l2_async_notifier notifier;
};
#endif

View File

@@ -0,0 +1,262 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Rockchip Camera Interface (CIF) Driver
*
* Copyright (C) 2018 Rockchip Electronics Co., Ltd.
* Copyright (C) 2020 Maxime Chevallier <maxime.chevallier@bootlin.com>
* Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
* Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
* Copyright (C) 2025 Collabora, Ltd.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-mc.h>
#include "rkcif-common.h"
static const char *const px30_vip_clks[] = {
"aclk",
"hclk",
"pclk",
};
static const struct rkcif_match_data px30_vip_match_data = {
.clks = px30_vip_clks,
.clks_num = ARRAY_SIZE(px30_vip_clks),
};
static const char *const rk3568_vicap_clks[] = {
"aclk",
"hclk",
"dclk",
"iclk",
};
static const struct rkcif_match_data rk3568_vicap_match_data = {
.clks = rk3568_vicap_clks,
.clks_num = ARRAY_SIZE(rk3568_vicap_clks),
};
static const struct of_device_id rkcif_plat_of_match[] = {
{
.compatible = "rockchip,px30-vip",
.data = &px30_vip_match_data,
},
{
.compatible = "rockchip,rk3568-vicap",
.data = &rk3568_vicap_match_data,
},
{}
};
MODULE_DEVICE_TABLE(of, rkcif_plat_of_match);
static int rkcif_register(struct rkcif_device *rkcif)
{
return 0;
}
static void rkcif_unregister(struct rkcif_device *rkcif)
{
}
static int rkcif_notifier_bound(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_connection *asd)
{
struct rkcif_remote *remote =
container_of(asd, struct rkcif_remote, async_conn);
remote->sd = sd;
return 0;
}
static int rkcif_notifier_complete(struct v4l2_async_notifier *notifier)
{
struct rkcif_device *rkcif =
container_of(notifier, struct rkcif_device, notifier);
return v4l2_device_register_subdev_nodes(&rkcif->v4l2_dev);
}
static const struct v4l2_async_notifier_operations rkcif_notifier_ops = {
.bound = rkcif_notifier_bound,
.complete = rkcif_notifier_complete,
};
static irqreturn_t rkcif_isr(int irq, void *ctx)
{
irqreturn_t ret = IRQ_NONE;
return ret;
}
static int rkcif_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rkcif_device *rkcif;
int ret, irq;
rkcif = devm_kzalloc(dev, sizeof(*rkcif), GFP_KERNEL);
if (!rkcif)
return -ENOMEM;
rkcif->match_data = of_device_get_match_data(dev);
if (!rkcif->match_data)
return -ENODEV;
dev_set_drvdata(dev, rkcif);
rkcif->dev = dev;
rkcif->base_addr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(rkcif->base_addr))
return PTR_ERR(rkcif->base_addr);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(dev, irq, rkcif_isr, IRQF_SHARED,
dev_driver_string(dev), dev);
if (ret)
return dev_err_probe(dev, ret, "failed to request irq\n");
if (rkcif->match_data->clks_num > RKCIF_CLK_MAX)
return dev_err_probe(dev, -EINVAL, "invalid number of clocks\n");
rkcif->clks_num = rkcif->match_data->clks_num;
for (unsigned int i = 0; i < rkcif->clks_num; i++)
rkcif->clks[i].id = rkcif->match_data->clks[i];
ret = devm_clk_bulk_get(dev, rkcif->clks_num, rkcif->clks);
if (ret)
return dev_err_probe(dev, ret, "failed to get clocks\n");
rkcif->reset = devm_reset_control_array_get_exclusive(dev);
if (IS_ERR(rkcif->reset))
return PTR_ERR(rkcif->reset);
rkcif->grf = syscon_regmap_lookup_by_phandle(dev->of_node,
"rockchip,grf");
if (IS_ERR(rkcif->grf))
rkcif->grf = NULL;
pm_runtime_enable(&pdev->dev);
rkcif->media_dev.dev = dev;
strscpy(rkcif->media_dev.model, RKCIF_DRIVER_NAME,
sizeof(rkcif->media_dev.model));
media_device_init(&rkcif->media_dev);
rkcif->v4l2_dev.mdev = &rkcif->media_dev;
ret = v4l2_device_register(dev, &rkcif->v4l2_dev);
if (ret)
goto err_media_dev_cleanup;
ret = media_device_register(&rkcif->media_dev);
if (ret < 0) {
dev_err(dev, "failed to register media device: %d\n", ret);
goto err_v4l2_dev_unregister;
}
v4l2_async_nf_init(&rkcif->notifier, &rkcif->v4l2_dev);
rkcif->notifier.ops = &rkcif_notifier_ops;
ret = rkcif_register(rkcif);
if (ret) {
dev_err(dev, "failed to register media entities: %d\n", ret);
goto err_notifier_cleanup;
}
ret = v4l2_async_nf_register(&rkcif->notifier);
if (ret)
goto err_rkcif_unregister;
return 0;
err_rkcif_unregister:
rkcif_unregister(rkcif);
err_notifier_cleanup:
v4l2_async_nf_cleanup(&rkcif->notifier);
media_device_unregister(&rkcif->media_dev);
err_v4l2_dev_unregister:
v4l2_device_unregister(&rkcif->v4l2_dev);
err_media_dev_cleanup:
media_device_cleanup(&rkcif->media_dev);
pm_runtime_disable(&pdev->dev);
return ret;
}
static void rkcif_remove(struct platform_device *pdev)
{
struct rkcif_device *rkcif = platform_get_drvdata(pdev);
v4l2_async_nf_unregister(&rkcif->notifier);
rkcif_unregister(rkcif);
v4l2_async_nf_cleanup(&rkcif->notifier);
media_device_unregister(&rkcif->media_dev);
v4l2_device_unregister(&rkcif->v4l2_dev);
media_device_cleanup(&rkcif->media_dev);
pm_runtime_disable(&pdev->dev);
}
static int rkcif_runtime_suspend(struct device *dev)
{
struct rkcif_device *rkcif = dev_get_drvdata(dev);
/*
* Reset CIF (CRU, DMA, FIFOs) to allow a clean resume.
* Since this resets the IOMMU too, we cannot issue this reset when
* resuming.
*/
reset_control_assert(rkcif->reset);
udelay(5);
reset_control_deassert(rkcif->reset);
clk_bulk_disable_unprepare(rkcif->clks_num, rkcif->clks);
return 0;
}
static int rkcif_runtime_resume(struct device *dev)
{
struct rkcif_device *rkcif = dev_get_drvdata(dev);
int ret;
ret = clk_bulk_prepare_enable(rkcif->clks_num, rkcif->clks);
if (ret) {
dev_err(dev, "failed to enable clocks\n");
return ret;
}
return 0;
}
static const struct dev_pm_ops rkcif_plat_pm_ops = {
.runtime_suspend = rkcif_runtime_suspend,
.runtime_resume = rkcif_runtime_resume,
};
static struct platform_driver rkcif_plat_drv = {
.driver = {
.name = RKCIF_DRIVER_NAME,
.of_match_table = rkcif_plat_of_match,
.pm = &rkcif_plat_pm_ops,
},
.probe = rkcif_probe,
.remove = rkcif_remove,
};
module_platform_driver(rkcif_plat_drv);
MODULE_DESCRIPTION("Rockchip Camera Interface (CIF) platform driver");
MODULE_LICENSE("GPL");