1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Driver for Renesas RZ/G2L CRU 4 * 5 * Copyright (C) 2022 Renesas Electronics Corp. 6 * 7 * Based on Renesas R-Car VIN 8 * Copyright (C) 2011-2013 Renesas Solutions Corp. 9 * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com> 10 * Copyright (C) 2008 Magnus Damm 11 */ 12 13#include <linux/clk.h> 14#include <linux/module.h> 15#include <linux/mod_devicetable.h> 16#include <linux/of.h> 17#include <linux/of_graph.h> 18#include <linux/platform_device.h> 19#include <linux/pm_runtime.h> 20 21#include <media/v4l2-fwnode.h> 22#include <media/v4l2-mc.h> 23 24#include "rzg2l-cru.h" 25 26static inline struct rzg2l_cru_dev *notifier_to_cru(struct v4l2_async_notifier *n) 27{ 28 return container_of(n, struct rzg2l_cru_dev, notifier); 29} 30 31static const struct media_device_ops rzg2l_cru_media_ops = { 32 .link_notify = v4l2_pipeline_link_notify, 33}; 34 35/* ----------------------------------------------------------------------------- 36 * Group async notifier 37 */ 38 39static int rzg2l_cru_group_notify_complete(struct v4l2_async_notifier *notifier) 40{ 41 struct rzg2l_cru_dev *cru = notifier_to_cru(notifier); 42 struct media_entity *source, *sink; 43 int ret; 44 45 ret = rzg2l_cru_ip_subdev_register(cru); 46 if (ret) 47 return ret; 48 49 ret = v4l2_device_register_subdev_nodes(&cru->v4l2_dev); 50 if (ret) { 51 dev_err(cru->dev, "Failed to register subdev nodes\n"); 52 return ret; 53 } 54 55 ret = rzg2l_cru_video_register(cru); 56 if (ret) 57 return ret; 58 59 /* 60 * CRU can be connected either to CSI2 or PARALLEL device 61 * For now we are only supporting CSI2 62 * 63 * Create media device link between CSI-2 <-> CRU IP 64 */ 65 source = &cru->csi.subdev->entity; 66 sink = &cru->ip.subdev.entity; 67 ret = media_create_pad_link(source, 1, sink, 0, 68 MEDIA_LNK_FL_ENABLED | 69 MEDIA_LNK_FL_IMMUTABLE); 70 if (ret) { 71 dev_err(cru->dev, "Error creating link from %s to %s\n", 72 source->name, sink->name); 73 return ret; 74 } 75 cru->csi.channel = 0; 76 cru->ip.remote = cru->csi.subdev; 77 78 /* Create media device link between CRU IP <-> CRU OUTPUT */ 79 source = &cru->ip.subdev.entity; 80 sink = &cru->vdev.entity; 81 ret = media_create_pad_link(source, 1, sink, 0, 82 MEDIA_LNK_FL_ENABLED | 83 MEDIA_LNK_FL_IMMUTABLE); 84 if (ret) { 85 dev_err(cru->dev, "Error creating link from %s to %s\n", 86 source->name, sink->name); 87 return ret; 88 } 89 90 return 0; 91} 92 93static void rzg2l_cru_group_notify_unbind(struct v4l2_async_notifier *notifier, 94 struct v4l2_subdev *subdev, 95 struct v4l2_async_connection *asd) 96{ 97 struct rzg2l_cru_dev *cru = notifier_to_cru(notifier); 98 99 rzg2l_cru_ip_subdev_unregister(cru); 100 101 mutex_lock(&cru->mdev_lock); 102 103 if (cru->csi.asd == asd) { 104 cru->csi.subdev = NULL; 105 dev_dbg(cru->dev, "Unbind CSI-2 %s\n", subdev->name); 106 } 107 108 mutex_unlock(&cru->mdev_lock); 109} 110 111static int rzg2l_cru_group_notify_bound(struct v4l2_async_notifier *notifier, 112 struct v4l2_subdev *subdev, 113 struct v4l2_async_connection *asd) 114{ 115 struct rzg2l_cru_dev *cru = notifier_to_cru(notifier); 116 117 mutex_lock(&cru->mdev_lock); 118 119 if (cru->csi.asd == asd) { 120 cru->csi.subdev = subdev; 121 dev_dbg(cru->dev, "Bound CSI-2 %s\n", subdev->name); 122 } 123 124 mutex_unlock(&cru->mdev_lock); 125 126 return 0; 127} 128 129static const struct v4l2_async_notifier_operations rzg2l_cru_async_ops = { 130 .bound = rzg2l_cru_group_notify_bound, 131 .unbind = rzg2l_cru_group_notify_unbind, 132 .complete = rzg2l_cru_group_notify_complete, 133}; 134 135static int rzg2l_cru_mc_parse_of(struct rzg2l_cru_dev *cru) 136{ 137 struct v4l2_fwnode_endpoint vep = { 138 .bus_type = V4L2_MBUS_CSI2_DPHY, 139 }; 140 struct fwnode_handle *ep, *fwnode; 141 struct v4l2_async_connection *asd; 142 int ret; 143 144 ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(cru->dev), 1, 0, 0); 145 if (!ep) 146 return 0; 147 148 fwnode = fwnode_graph_get_remote_endpoint(ep); 149 ret = v4l2_fwnode_endpoint_parse(ep, &vep); 150 fwnode_handle_put(ep); 151 if (ret) { 152 dev_err(cru->dev, "Failed to parse %pOF\n", to_of_node(fwnode)); 153 ret = -EINVAL; 154 goto out; 155 } 156 157 if (!of_device_is_available(to_of_node(fwnode))) { 158 dev_dbg(cru->dev, "OF device %pOF disabled, ignoring\n", 159 to_of_node(fwnode)); 160 ret = -ENOTCONN; 161 goto out; 162 } 163 164 asd = v4l2_async_nf_add_fwnode(&cru->notifier, fwnode, 165 struct v4l2_async_connection); 166 if (IS_ERR(asd)) { 167 ret = PTR_ERR(asd); 168 goto out; 169 } 170 171 cru->csi.asd = asd; 172 173 dev_dbg(cru->dev, "Added OF device %pOF to slot %u\n", 174 to_of_node(fwnode), vep.base.id); 175out: 176 fwnode_handle_put(fwnode); 177 178 return ret; 179} 180 181static int rzg2l_cru_mc_parse_of_graph(struct rzg2l_cru_dev *cru) 182{ 183 int ret; 184 185 v4l2_async_nf_init(&cru->notifier, &cru->v4l2_dev); 186 187 ret = rzg2l_cru_mc_parse_of(cru); 188 if (ret) 189 return ret; 190 191 cru->notifier.ops = &rzg2l_cru_async_ops; 192 193 if (list_empty(&cru->notifier.waiting_list)) 194 return 0; 195 196 ret = v4l2_async_nf_register(&cru->notifier); 197 if (ret < 0) { 198 dev_err(cru->dev, "Notifier registration failed\n"); 199 v4l2_async_nf_cleanup(&cru->notifier); 200 return ret; 201 } 202 203 return 0; 204} 205 206static int rzg2l_cru_media_init(struct rzg2l_cru_dev *cru) 207{ 208 struct media_device *mdev = NULL; 209 const struct of_device_id *match; 210 int ret; 211 212 cru->pad.flags = MEDIA_PAD_FL_SINK; 213 ret = media_entity_pads_init(&cru->vdev.entity, 1, &cru->pad); 214 if (ret) 215 return ret; 216 217 mutex_init(&cru->mdev_lock); 218 mdev = &cru->mdev; 219 mdev->dev = cru->dev; 220 mdev->ops = &rzg2l_cru_media_ops; 221 222 match = of_match_node(cru->dev->driver->of_match_table, 223 cru->dev->of_node); 224 225 strscpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name)); 226 strscpy(mdev->model, match->compatible, sizeof(mdev->model)); 227 228 cru->v4l2_dev.mdev = &cru->mdev; 229 230 media_device_init(mdev); 231 232 ret = rzg2l_cru_mc_parse_of_graph(cru); 233 if (ret) { 234 mutex_lock(&cru->mdev_lock); 235 cru->v4l2_dev.mdev = NULL; 236 mutex_unlock(&cru->mdev_lock); 237 } 238 239 return 0; 240} 241 242static int rzg2l_cru_probe(struct platform_device *pdev) 243{ 244 struct rzg2l_cru_dev *cru; 245 int ret; 246 247 cru = devm_kzalloc(&pdev->dev, sizeof(*cru), GFP_KERNEL); 248 if (!cru) 249 return -ENOMEM; 250 251 cru->base = devm_platform_ioremap_resource(pdev, 0); 252 if (IS_ERR(cru->base)) 253 return PTR_ERR(cru->base); 254 255 cru->presetn = devm_reset_control_get_shared(&pdev->dev, "presetn"); 256 if (IS_ERR(cru->presetn)) 257 return dev_err_probe(&pdev->dev, PTR_ERR(cru->presetn), 258 "Failed to get cpg presetn\n"); 259 260 cru->aresetn = devm_reset_control_get_exclusive(&pdev->dev, "aresetn"); 261 if (IS_ERR(cru->aresetn)) 262 return dev_err_probe(&pdev->dev, PTR_ERR(cru->aresetn), 263 "Failed to get cpg aresetn\n"); 264 265 cru->vclk = devm_clk_get(&pdev->dev, "video"); 266 if (IS_ERR(cru->vclk)) 267 return dev_err_probe(&pdev->dev, PTR_ERR(cru->vclk), 268 "Failed to get video clock\n"); 269 270 cru->dev = &pdev->dev; 271 cru->info = of_device_get_match_data(&pdev->dev); 272 273 cru->image_conv_irq = platform_get_irq(pdev, 0); 274 if (cru->image_conv_irq < 0) 275 return cru->image_conv_irq; 276 277 platform_set_drvdata(pdev, cru); 278 279 ret = rzg2l_cru_dma_register(cru); 280 if (ret) 281 return ret; 282 283 cru->num_buf = RZG2L_CRU_HW_BUFFER_DEFAULT; 284 pm_suspend_ignore_children(&pdev->dev, true); 285 pm_runtime_enable(&pdev->dev); 286 287 ret = rzg2l_cru_media_init(cru); 288 if (ret) 289 goto error_dma_unregister; 290 291 return 0; 292 293error_dma_unregister: 294 rzg2l_cru_dma_unregister(cru); 295 pm_runtime_disable(&pdev->dev); 296 297 return ret; 298} 299 300static void rzg2l_cru_remove(struct platform_device *pdev) 301{ 302 struct rzg2l_cru_dev *cru = platform_get_drvdata(pdev); 303 304 pm_runtime_disable(&pdev->dev); 305 306 v4l2_async_nf_unregister(&cru->notifier); 307 v4l2_async_nf_cleanup(&cru->notifier); 308 309 rzg2l_cru_video_unregister(cru); 310 media_device_cleanup(&cru->mdev); 311 mutex_destroy(&cru->mdev_lock); 312 313 rzg2l_cru_dma_unregister(cru); 314} 315 316static const struct of_device_id rzg2l_cru_of_id_table[] = { 317 { .compatible = "renesas,rzg2l-cru", }, 318 { /* sentinel */ } 319}; 320MODULE_DEVICE_TABLE(of, rzg2l_cru_of_id_table); 321 322static struct platform_driver rzg2l_cru_driver = { 323 .driver = { 324 .name = "rzg2l-cru", 325 .of_match_table = rzg2l_cru_of_id_table, 326 }, 327 .probe = rzg2l_cru_probe, 328 .remove_new = rzg2l_cru_remove, 329}; 330 331module_platform_driver(rzg2l_cru_driver); 332 333MODULE_AUTHOR("Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>"); 334MODULE_DESCRIPTION("Renesas RZ/G2L CRU driver"); 335MODULE_LICENSE("GPL"); 336