1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2016 NextThing Co 4 * Copyright (C) 2016-2019 Bootlin 5 * 6 * Author: Maxime Ripard <maxime.ripard@bootlin.com> 7 */ 8 9 #include <linux/clk.h> 10 #include <linux/interrupt.h> 11 #include <linux/module.h> 12 #include <linux/mutex.h> 13 #include <linux/of.h> 14 #include <linux/of_graph.h> 15 #include <linux/platform_device.h> 16 #include <linux/pm_runtime.h> 17 #include <linux/reset.h> 18 #include <linux/videodev2.h> 19 20 #include <media/v4l2-dev.h> 21 #include <media/v4l2-device.h> 22 #include <media/v4l2-fwnode.h> 23 #include <media/v4l2-ioctl.h> 24 #include <media/v4l2-mediabus.h> 25 26 #include <media/videobuf2-core.h> 27 #include <media/videobuf2-dma-contig.h> 28 29 #include "sun4i_csi.h" 30 31 static const struct media_entity_operations sun4i_csi_video_entity_ops = { 32 .link_validate = v4l2_subdev_link_validate, 33 }; 34 35 static int sun4i_csi_notify_bound(struct v4l2_async_notifier *notifier, 36 struct v4l2_subdev *subdev, 37 struct v4l2_async_subdev *asd) 38 { 39 struct sun4i_csi *csi = container_of(notifier, struct sun4i_csi, 40 notifier); 41 42 csi->src_subdev = subdev; 43 csi->src_pad = media_entity_get_fwnode_pad(&subdev->entity, 44 subdev->fwnode, 45 MEDIA_PAD_FL_SOURCE); 46 if (csi->src_pad < 0) { 47 dev_err(csi->dev, "Couldn't find output pad for subdev %s\n", 48 subdev->name); 49 return csi->src_pad; 50 } 51 52 dev_dbg(csi->dev, "Bound %s pad: %d\n", subdev->name, csi->src_pad); 53 return 0; 54 } 55 56 static int sun4i_csi_notify_complete(struct v4l2_async_notifier *notifier) 57 { 58 struct sun4i_csi *csi = container_of(notifier, struct sun4i_csi, 59 notifier); 60 struct v4l2_subdev *subdev = &csi->subdev; 61 struct video_device *vdev = &csi->vdev; 62 int ret; 63 64 ret = v4l2_device_register_subdev(&csi->v4l, subdev); 65 if (ret < 0) 66 return ret; 67 68 ret = sun4i_csi_v4l2_register(csi); 69 if (ret < 0) 70 return ret; 71 72 ret = media_device_register(&csi->mdev); 73 if (ret) 74 return ret; 75 76 /* Create link from subdev to main device */ 77 ret = media_create_pad_link(&subdev->entity, CSI_SUBDEV_SOURCE, 78 &vdev->entity, 0, 79 MEDIA_LNK_FL_ENABLED | 80 MEDIA_LNK_FL_IMMUTABLE); 81 if (ret) 82 goto err_clean_media; 83 84 ret = media_create_pad_link(&csi->src_subdev->entity, csi->src_pad, 85 &subdev->entity, CSI_SUBDEV_SINK, 86 MEDIA_LNK_FL_ENABLED | 87 MEDIA_LNK_FL_IMMUTABLE); 88 if (ret) 89 goto err_clean_media; 90 91 ret = v4l2_device_register_subdev_nodes(&csi->v4l); 92 if (ret < 0) 93 goto err_clean_media; 94 95 return 0; 96 97 err_clean_media: 98 media_device_unregister(&csi->mdev); 99 100 return ret; 101 } 102 103 static const struct v4l2_async_notifier_operations sun4i_csi_notify_ops = { 104 .bound = sun4i_csi_notify_bound, 105 .complete = sun4i_csi_notify_complete, 106 }; 107 108 static int sun4i_csi_notifier_init(struct sun4i_csi *csi) 109 { 110 struct v4l2_fwnode_endpoint vep = { 111 .bus_type = V4L2_MBUS_PARALLEL, 112 }; 113 struct fwnode_handle *ep; 114 int ret; 115 116 v4l2_async_notifier_init(&csi->notifier); 117 118 ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0, 119 FWNODE_GRAPH_ENDPOINT_NEXT); 120 if (!ep) 121 return -EINVAL; 122 123 ret = v4l2_fwnode_endpoint_parse(ep, &vep); 124 if (ret) 125 goto out; 126 127 csi->bus = vep.bus.parallel; 128 129 ret = v4l2_async_notifier_add_fwnode_remote_subdev(&csi->notifier, 130 ep, &csi->asd); 131 if (ret) 132 goto out; 133 134 csi->notifier.ops = &sun4i_csi_notify_ops; 135 136 out: 137 fwnode_handle_put(ep); 138 return ret; 139 } 140 141 static int sun4i_csi_probe(struct platform_device *pdev) 142 { 143 struct v4l2_subdev *subdev; 144 struct video_device *vdev; 145 struct sun4i_csi *csi; 146 struct resource *res; 147 int ret; 148 int irq; 149 150 csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL); 151 if (!csi) 152 return -ENOMEM; 153 platform_set_drvdata(pdev, csi); 154 csi->dev = &pdev->dev; 155 subdev = &csi->subdev; 156 vdev = &csi->vdev; 157 158 csi->mdev.dev = csi->dev; 159 strscpy(csi->mdev.model, "Allwinner Video Capture Device", 160 sizeof(csi->mdev.model)); 161 csi->mdev.hw_revision = 0; 162 media_device_init(&csi->mdev); 163 csi->v4l.mdev = &csi->mdev; 164 165 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 166 csi->regs = devm_ioremap_resource(&pdev->dev, res); 167 if (IS_ERR(csi->regs)) 168 return PTR_ERR(csi->regs); 169 170 irq = platform_get_irq(pdev, 0); 171 if (irq < 0) 172 return irq; 173 174 csi->bus_clk = devm_clk_get(&pdev->dev, "bus"); 175 if (IS_ERR(csi->bus_clk)) { 176 dev_err(&pdev->dev, "Couldn't get our bus clock\n"); 177 return PTR_ERR(csi->bus_clk); 178 } 179 180 csi->isp_clk = devm_clk_get(&pdev->dev, "isp"); 181 if (IS_ERR(csi->isp_clk)) { 182 dev_err(&pdev->dev, "Couldn't get our ISP clock\n"); 183 return PTR_ERR(csi->isp_clk); 184 } 185 186 csi->ram_clk = devm_clk_get(&pdev->dev, "ram"); 187 if (IS_ERR(csi->ram_clk)) { 188 dev_err(&pdev->dev, "Couldn't get our ram clock\n"); 189 return PTR_ERR(csi->ram_clk); 190 } 191 192 csi->rst = devm_reset_control_get(&pdev->dev, NULL); 193 if (IS_ERR(csi->rst)) { 194 dev_err(&pdev->dev, "Couldn't get our reset line\n"); 195 return PTR_ERR(csi->rst); 196 } 197 198 /* Initialize subdev */ 199 v4l2_subdev_init(subdev, &sun4i_csi_subdev_ops); 200 subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; 201 subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; 202 subdev->owner = THIS_MODULE; 203 snprintf(subdev->name, sizeof(subdev->name), "sun4i-csi-0"); 204 v4l2_set_subdevdata(subdev, csi); 205 206 csi->subdev_pads[CSI_SUBDEV_SINK].flags = 207 MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; 208 csi->subdev_pads[CSI_SUBDEV_SOURCE].flags = MEDIA_PAD_FL_SOURCE; 209 ret = media_entity_pads_init(&subdev->entity, CSI_SUBDEV_PADS, 210 csi->subdev_pads); 211 if (ret < 0) 212 return ret; 213 214 csi->vdev_pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; 215 vdev->entity.ops = &sun4i_csi_video_entity_ops; 216 ret = media_entity_pads_init(&vdev->entity, 1, &csi->vdev_pad); 217 if (ret < 0) 218 return ret; 219 220 ret = sun4i_csi_dma_register(csi, irq); 221 if (ret) 222 goto err_clean_pad; 223 224 ret = sun4i_csi_notifier_init(csi); 225 if (ret) 226 goto err_unregister_media; 227 228 ret = v4l2_async_notifier_register(&csi->v4l, &csi->notifier); 229 if (ret) { 230 dev_err(csi->dev, "Couldn't register our notifier.\n"); 231 goto err_unregister_media; 232 } 233 234 pm_runtime_enable(&pdev->dev); 235 236 return 0; 237 238 err_unregister_media: 239 media_device_unregister(&csi->mdev); 240 sun4i_csi_dma_unregister(csi); 241 242 err_clean_pad: 243 media_device_cleanup(&csi->mdev); 244 245 return ret; 246 } 247 248 static int sun4i_csi_remove(struct platform_device *pdev) 249 { 250 struct sun4i_csi *csi = platform_get_drvdata(pdev); 251 252 v4l2_async_notifier_unregister(&csi->notifier); 253 v4l2_async_notifier_cleanup(&csi->notifier); 254 media_device_unregister(&csi->mdev); 255 sun4i_csi_dma_unregister(csi); 256 media_device_cleanup(&csi->mdev); 257 258 return 0; 259 } 260 261 static const struct of_device_id sun4i_csi_of_match[] = { 262 { .compatible = "allwinner,sun7i-a20-csi0" }, 263 { /* Sentinel */ } 264 }; 265 MODULE_DEVICE_TABLE(of, sun4i_csi_of_match); 266 267 static int __maybe_unused sun4i_csi_runtime_resume(struct device *dev) 268 { 269 struct sun4i_csi *csi = dev_get_drvdata(dev); 270 271 reset_control_deassert(csi->rst); 272 clk_prepare_enable(csi->bus_clk); 273 clk_prepare_enable(csi->ram_clk); 274 clk_set_rate(csi->isp_clk, 80000000); 275 clk_prepare_enable(csi->isp_clk); 276 277 writel(1, csi->regs + CSI_EN_REG); 278 279 return 0; 280 } 281 282 static int __maybe_unused sun4i_csi_runtime_suspend(struct device *dev) 283 { 284 struct sun4i_csi *csi = dev_get_drvdata(dev); 285 286 clk_disable_unprepare(csi->isp_clk); 287 clk_disable_unprepare(csi->ram_clk); 288 clk_disable_unprepare(csi->bus_clk); 289 290 reset_control_assert(csi->rst); 291 292 return 0; 293 } 294 295 static const struct dev_pm_ops sun4i_csi_pm_ops = { 296 SET_RUNTIME_PM_OPS(sun4i_csi_runtime_suspend, 297 sun4i_csi_runtime_resume, 298 NULL) 299 }; 300 301 static struct platform_driver sun4i_csi_driver = { 302 .probe = sun4i_csi_probe, 303 .remove = sun4i_csi_remove, 304 .driver = { 305 .name = "sun4i-csi", 306 .of_match_table = sun4i_csi_of_match, 307 .pm = &sun4i_csi_pm_ops, 308 }, 309 }; 310 module_platform_driver(sun4i_csi_driver); 311 312 MODULE_DESCRIPTION("Allwinner A10 Camera Sensor Interface driver"); 313 MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>"); 314 MODULE_LICENSE("GPL"); 315