FILE
rzv2h-ivc-dev.c
File preview with syntax highlighting.
linux-6.19.14/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.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.