Skip to content

Commit f0b3984

Browse files
djrscallyHans Verkuil
authored andcommitted
media: platform: Add Renesas Input Video Control block driver
Add a driver for the Input Video Control block in an RZ/V2H(P) SoC which feeds data into the Arm Mali-C55 ISP. [ivc: Remove check on buffers list in start_streaming] [ivc: put_autosuspend() implies mark_last_busy()] [media: rzv2h-ivc: Do not delay frame completion] Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com> Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org> [hverkuil: remove deprecated vb2_ops_wait_prepare/finish callbacks] [hverkuil: replace "select PM" by "depends on PM"]
1 parent ef5a75b commit f0b3984

8 files changed

Lines changed: 1313 additions & 0 deletions

File tree

drivers/media/platform/renesas/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ config VIDEO_SH_VOU
4242
source "drivers/media/platform/renesas/rcar-isp/Kconfig"
4343
source "drivers/media/platform/renesas/rcar-vin/Kconfig"
4444
source "drivers/media/platform/renesas/rzg2l-cru/Kconfig"
45+
source "drivers/media/platform/renesas/rzv2h-ivc/Kconfig"
4546

4647
# Mem2mem drivers
4748

drivers/media/platform/renesas/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
obj-y += rcar-isp/
77
obj-y += rcar-vin/
88
obj-y += rzg2l-cru/
9+
obj-y += rzv2h-ivc/
910
obj-y += vsp1/
1011

1112
obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
3+
config VIDEO_RZV2H_IVC
4+
tristate "Renesas RZ/V2H(P) Input Video Control block driver"
5+
depends on V4L_PLATFORM_DRIVERS
6+
depends on VIDEO_DEV
7+
depends on ARCH_RENESAS || COMPILE_TEST
8+
depends on OF
9+
depends on PM
10+
select VIDEOBUF2_DMA_CONTIG
11+
select MEDIA_CONTROLLER
12+
select VIDEO_V4L2_SUBDEV_API
13+
help
14+
Support for the Renesas RZ/V2H(P) Input Video Control Block
15+
(IVC).
16+
17+
To compile this driver as a module, choose M here: the
18+
module will be called rzv2h-ivc.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SPDX-License-Identifier: GPL-2.0
2+
3+
rzv2h-ivc-y := rzv2h-ivc-dev.o rzv2h-ivc-subdev.o rzv2h-ivc-video.o
4+
5+
obj-$(CONFIG_VIDEO_RZV2H_IVC) += rzv2h-ivc.o
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Renesas RZ/V2H(P) Input Video Control Block driver
4+
*
5+
* Copyright (C) 2025 Ideas on Board Oy
6+
*/
7+
8+
#include "rzv2h-ivc.h"
9+
10+
#include <linux/device.h>
11+
#include <linux/interrupt.h>
12+
#include <linux/io.h>
13+
#include <linux/platform_device.h>
14+
#include <linux/pm_runtime.h>
15+
#include <linux/reset.h>
16+
17+
void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val)
18+
{
19+
writel(val, ivc->base + addr);
20+
}
21+
22+
void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr,
23+
u32 mask, u32 val)
24+
{
25+
u32 orig, new;
26+
27+
orig = readl(ivc->base + addr);
28+
29+
new = orig & ~mask;
30+
new |= val & mask;
31+
32+
if (new != orig)
33+
writel(new, ivc->base + addr);
34+
}
35+
36+
static int rzv2h_ivc_get_hardware_resources(struct rzv2h_ivc *ivc,
37+
struct platform_device *pdev)
38+
{
39+
static const char * const resource_names[RZV2H_IVC_NUM_HW_RESOURCES] = {
40+
"reg",
41+
"axi",
42+
"isp",
43+
};
44+
struct resource *res;
45+
int ret;
46+
47+
ivc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
48+
if (IS_ERR(ivc->base))
49+
return dev_err_probe(ivc->dev, PTR_ERR(ivc->base),
50+
"failed to map IO memory\n");
51+
52+
for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
53+
ivc->clks[i].id = resource_names[i];
54+
55+
ret = devm_clk_bulk_get(ivc->dev, ARRAY_SIZE(resource_names), ivc->clks);
56+
if (ret)
57+
return dev_err_probe(ivc->dev, ret, "failed to acquire clks\n");
58+
59+
for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
60+
ivc->resets[i].id = resource_names[i];
61+
62+
ret = devm_reset_control_bulk_get_optional_shared(ivc->dev,
63+
ARRAY_SIZE(resource_names),
64+
ivc->resets);
65+
if (ret)
66+
return dev_err_probe(ivc->dev, ret, "failed to acquire resets\n");
67+
68+
return 0;
69+
}
70+
71+
static void rzv2h_ivc_global_config(struct rzv2h_ivc *ivc)
72+
{
73+
/* Currently we only support single-exposure input */
74+
rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PLNUM, RZV2H_IVC_ONE_EXPOSURE);
75+
76+
/*
77+
* Datasheet says we should disable the interrupts before changing mode
78+
* to avoid spurious IFP interrupt.
79+
*/
80+
rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, 0x0);
81+
82+
/*
83+
* RZ/V2H(P) documentation says software controlled single context mode
84+
* is not supported, and currently the driver does not support the
85+
* multi-context mode. That being so we just set single context sw-hw
86+
* mode.
87+
*/
88+
rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_CONTEXT,
89+
RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG);
90+
91+
/*
92+
* We enable the frame end interrupt so that we know when we should send
93+
* follow-up frames.
94+
*/
95+
rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, RZV2H_IVC_VVAL_IFPE);
96+
}
97+
98+
static irqreturn_t rzv2h_ivc_isr(int irq, void *context)
99+
{
100+
struct device *dev = context;
101+
struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
102+
103+
guard(spinlock)(&ivc->spinlock);
104+
105+
/* IRQ should never be triggered before vvalid_ifp has been reset to 2 */
106+
if (WARN_ON(!ivc->vvalid_ifp))
107+
return IRQ_HANDLED;
108+
109+
/*
110+
* The first interrupt indicates that the buffer transfer has been
111+
* completed.
112+
*/
113+
if (--ivc->vvalid_ifp) {
114+
rzv2h_ivc_buffer_done(ivc);
115+
return IRQ_HANDLED;
116+
}
117+
118+
/*
119+
* The second interrupt indicates that the post-frame transfer VBLANK
120+
* has completed, we can now schedule a new frame transfer, if any.
121+
*/
122+
queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
123+
124+
return IRQ_HANDLED;
125+
}
126+
127+
static int rzv2h_ivc_runtime_resume(struct device *dev)
128+
{
129+
struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
130+
int ret;
131+
132+
ret = clk_bulk_prepare_enable(ARRAY_SIZE(ivc->clks), ivc->clks);
133+
if (ret) {
134+
dev_err(ivc->dev, "failed to enable clocks\n");
135+
return ret;
136+
}
137+
138+
ret = reset_control_bulk_deassert(ARRAY_SIZE(ivc->resets), ivc->resets);
139+
if (ret) {
140+
dev_err(ivc->dev, "failed to deassert resets\n");
141+
goto err_disable_clks;
142+
}
143+
144+
rzv2h_ivc_global_config(ivc);
145+
146+
ret = request_irq(ivc->irqnum, rzv2h_ivc_isr, 0, dev_driver_string(dev),
147+
dev);
148+
if (ret) {
149+
dev_err(dev, "failed to request irq\n");
150+
goto err_assert_resets;
151+
}
152+
153+
return 0;
154+
155+
err_assert_resets:
156+
reset_control_bulk_assert(ARRAY_SIZE(ivc->resets), ivc->resets);
157+
err_disable_clks:
158+
clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
159+
160+
return ret;
161+
}
162+
163+
static int rzv2h_ivc_runtime_suspend(struct device *dev)
164+
{
165+
struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
166+
167+
reset_control_bulk_assert(ARRAY_SIZE(ivc->resets), ivc->resets);
168+
clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
169+
free_irq(ivc->irqnum, dev);
170+
171+
return 0;
172+
}
173+
174+
static const struct dev_pm_ops rzv2h_ivc_pm_ops = {
175+
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
176+
RUNTIME_PM_OPS(rzv2h_ivc_runtime_suspend, rzv2h_ivc_runtime_resume,
177+
NULL)
178+
};
179+
180+
static int rzv2h_ivc_probe(struct platform_device *pdev)
181+
{
182+
struct device *dev = &pdev->dev;
183+
struct rzv2h_ivc *ivc;
184+
int ret;
185+
186+
ivc = devm_kzalloc(dev, sizeof(*ivc), GFP_KERNEL);
187+
if (!ivc)
188+
return -ENOMEM;
189+
190+
ivc->dev = dev;
191+
platform_set_drvdata(pdev, ivc);
192+
193+
ret = devm_mutex_init(dev, &ivc->lock);
194+
if (ret)
195+
return ret;
196+
197+
spin_lock_init(&ivc->spinlock);
198+
199+
ret = rzv2h_ivc_get_hardware_resources(ivc, pdev);
200+
if (ret)
201+
return ret;
202+
203+
pm_runtime_set_autosuspend_delay(dev, 2000);
204+
pm_runtime_use_autosuspend(dev);
205+
pm_runtime_enable(dev);
206+
207+
ivc->irqnum = platform_get_irq(pdev, 0);
208+
if (ivc->irqnum < 0)
209+
return ivc->irqnum;
210+
211+
ret = rzv2h_ivc_initialise_subdevice(ivc);
212+
if (ret)
213+
goto err_disable_pm_runtime;
214+
215+
return 0;
216+
217+
err_disable_pm_runtime:
218+
pm_runtime_disable(dev);
219+
220+
return ret;
221+
}
222+
223+
static void rzv2h_ivc_remove(struct platform_device *pdev)
224+
{
225+
struct rzv2h_ivc *ivc = platform_get_drvdata(pdev);
226+
227+
rzv2h_deinit_video_dev_and_queue(ivc);
228+
rzv2h_ivc_deinit_subdevice(ivc);
229+
}
230+
231+
static const struct of_device_id rzv2h_ivc_of_match[] = {
232+
{ .compatible = "renesas,r9a09g057-ivc", },
233+
{ /* Sentinel */ }
234+
};
235+
MODULE_DEVICE_TABLE(of, rzv2h_ivc_of_match);
236+
237+
static struct platform_driver rzv2h_ivc_driver = {
238+
.driver = {
239+
.name = "rzv2h-ivc",
240+
.of_match_table = rzv2h_ivc_of_match,
241+
.pm = &rzv2h_ivc_pm_ops,
242+
},
243+
.probe = rzv2h_ivc_probe,
244+
.remove = rzv2h_ivc_remove,
245+
};
246+
247+
module_platform_driver(rzv2h_ivc_driver);
248+
249+
MODULE_AUTHOR("Daniel Scally <dan.scally@ideasonboard.com>");
250+
MODULE_DESCRIPTION("Renesas RZ/V2H(P) Input Video Control Block driver");
251+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)