File Index
Files and directories
Sign in
FILE

rzv2h-ivc-dev.c

File preview with syntax highlighting.

linux-6.19.14/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c C
  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
 17void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val)
 18{
 19	writel(val, ivc->base + addr);
 20}
 21
 22void 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
 36static 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
 71static 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
 98static 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
127static 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
155err_assert_resets:
156	reset_control_bulk_assert(ARRAY_SIZE(ivc->resets), ivc->resets);
157err_disable_clks:
158	clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
159
160	return ret;
161}
162
163static 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
174static 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
180static 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
217err_disable_pm_runtime:
218	pm_runtime_disable(dev);
219
220	return ret;
221}
222
223static 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
231static const struct of_device_id rzv2h_ivc_of_match[] = {
232	{ .compatible = "renesas,r9a09g057-ivc", },
233	{ /* Sentinel */ }
234};
235MODULE_DEVICE_TABLE(of, rzv2h_ivc_of_match);
236
237static 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
247module_platform_driver(rzv2h_ivc_driver);
248
249MODULE_AUTHOR("Daniel Scally <dan.scally@ideasonboard.com>");
250MODULE_DESCRIPTION("Renesas RZ/V2H(P) Input Video Control Block driver");
251MODULE_LICENSE("GPL");
~/ Read-only file preview.