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/dma-mapping.h> 11 #include <linux/interrupt.h> 12 #include <linux/module.h> 13 #include <linux/mutex.h> 14 #include <linux/of.h> 15 #include <linux/of_device.h> 16 #include <linux/of_graph.h> 17 #include <linux/platform_device.h> 18 #include <linux/pm_runtime.h> 19 #include <linux/reset.h> 20 #include <linux/videodev2.h> 21 22 #include <media/v4l2-dev.h> 23 #include <media/v4l2-device.h> 24 #include <media/v4l2-fwnode.h> 25 #include <media/v4l2-ioctl.h> 26 #include <media/v4l2-mediabus.h> 27 28 #include <media/videobuf2-core.h> 29 #include <media/videobuf2-dma-contig.h> 30 31 #include "sun4i_csi.h" 32 33 struct sun4i_csi_traits { 34 unsigned int channels; 35 unsigned int max_width; 36 bool has_isp; 37 }; 38 39 static const struct media_entity_operations sun4i_csi_video_entity_ops = { 40 .link_validate = v4l2_subdev_link_validate, 41 }; 42 43 static int sun4i_csi_notify_bound(struct v4l2_async_notifier *notifier, 44 struct v4l2_subdev *subdev, 45 struct v4l2_async_subdev *asd) 46 { 47 struct sun4i_csi *csi = container_of(notifier, struct sun4i_csi, 48 notifier); 49 50 csi->src_subdev = subdev; 51 csi->src_pad = media_entity_get_fwnode_pad(&subdev->entity, 52 subdev->fwnode, 53 MEDIA_PAD_FL_SOURCE); 54 if (csi->src_pad < 0) { 55 dev_err(csi->dev, "Couldn't find output pad for subdev %s\n", 56 subdev->name); 57 return csi->src_pad; 58 } 59 60 dev_dbg(csi->dev, "Bound %s pad: %d\n", subdev->name, csi->src_pad); 61 return 0; 62 } 63 64 static int sun4i_csi_notify_complete(struct v4l2_async_notifier *notifier) 65 { 66 struct sun4i_csi *csi = container_of(notifier, struct sun4i_csi, 67 notifier); 68 struct v4l2_subdev *subdev = &csi->subdev; 69 struct video_device *vdev = &csi->vdev; 70 int ret; 71 72 ret = v4l2_device_register_subdev(&csi->v4l, subdev); 73 if (ret < 0) 74 return ret; 75 76 ret = sun4i_csi_v4l2_register(csi); 77 if (ret < 0) 78 return ret; 79 80 ret = media_device_register(&csi->mdev); 81 if (ret) 82 return ret; 83 84 /* Create link from subdev to main device */ 85 ret = media_create_pad_link(&subdev->entity, CSI_SUBDEV_SOURCE, 86 &vdev->entity, 0, 87 MEDIA_LNK_FL_ENABLED | 88 MEDIA_LNK_FL_IMMUTABLE); 89 if (ret) 90 goto err_clean_media; 91 92 ret = media_create_pad_link(&csi->src_subdev->entity, csi->src_pad, 93 &subdev->entity, CSI_SUBDEV_SINK, 94 MEDIA_LNK_FL_ENABLED | 95 MEDIA_LNK_FL_IMMUTABLE); 96 if (ret) 97 goto err_clean_media; 98 99 ret = v4l2_device_register_subdev_nodes(&csi->v4l); 100 if (ret < 0) 101 goto err_clean_media; 102 103 return 0; 104 105 err_clean_media: 106 media_device_unregister(&csi->mdev); 107 108 return ret; 109 } 110 111 static const struct v4l2_async_notifier_operations sun4i_csi_notify_ops = { 112 .bound = sun4i_csi_notify_bound, 113 .complete = sun4i_csi_notify_complete, 114 }; 115 116 static int sun4i_csi_notifier_init(struct sun4i_csi *csi) 117 { 118 struct v4l2_fwnode_endpoint vep = { 119 .bus_type = V4L2_MBUS_PARALLEL, 120 }; 121 struct fwnode_handle *ep; 122 int ret; 123 124 v4l2_async_notifier_init(&csi->notifier); 125 126 ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0, 127 FWNODE_GRAPH_ENDPOINT_NEXT); 128 if (!ep) 129 return -EINVAL; 130 131 ret = v4l2_fwnode_endpoint_parse(ep, &vep); 132 if (ret) 133 goto out; 134 135 csi->bus = vep.bus.parallel; 136 137 ret = v4l2_async_notifier_add_fwnode_remote_subdev(&csi->notifier, 138 ep, &csi->asd); 139 if (ret) 140 goto out; 141 142 csi->notifier.ops = &sun4i_csi_notify_ops; 143 144 out: 145 fwnode_handle_put(ep); 146 return ret; 147 } 148 149 static int sun4i_csi_probe(struct platform_device *pdev) 150 { 151 struct v4l2_subdev *subdev; 152 struct video_device *vdev; 153 struct sun4i_csi *csi; 154 struct resource *res; 155 int ret; 156 int irq; 157 158 csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL); 159 if (!csi) 160 return -ENOMEM; 161 platform_set_drvdata(pdev, csi); 162 csi->dev = &pdev->dev; 163 subdev = &csi->subdev; 164 vdev = &csi->vdev; 165 166 csi->traits = of_device_get_match_data(&pdev->dev); 167 if (!csi->traits) 168 return -EINVAL; 169 170 /* 171 * On Allwinner SoCs, some high memory bandwidth devices do DMA 172 * directly over the memory bus (called MBUS), instead of the 173 * system bus. The memory bus has a different addressing scheme 174 * without the DRAM starting offset. 175 * 176 * In some cases this can be described by an interconnect in 177 * the device tree. In other cases where the hardware is not 178 * fully understood and the interconnect is left out of the 179 * device tree, fall back to a default offset. 180 */ 181 if (of_find_property(csi->dev->of_node, "interconnects", NULL)) { 182 ret = of_dma_configure(csi->dev, csi->dev->of_node, true); 183 if (ret) 184 return ret; 185 } else { 186 /* 187 * XXX(hch): this has no business in a driver and needs to move 188 * to the device tree. 189 */ 190 #ifdef PHYS_PFN_OFFSET 191 ret = dma_direct_set_offset(csi->dev, PHYS_OFFSET, 0, SZ_4G); 192 if (ret) 193 return ret; 194 #endif 195 } 196 197 csi->mdev.dev = csi->dev; 198 strscpy(csi->mdev.model, "Allwinner Video Capture Device", 199 sizeof(csi->mdev.model)); 200 csi->mdev.hw_revision = 0; 201 snprintf(csi->mdev.bus_info, sizeof(csi->mdev.bus_info), "platform:%s", 202 dev_name(csi->dev)); 203 media_device_init(&csi->mdev); 204 csi->v4l.mdev = &csi->mdev; 205 206 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 207 csi->regs = devm_ioremap_resource(&pdev->dev, res); 208 if (IS_ERR(csi->regs)) 209 return PTR_ERR(csi->regs); 210 211 irq = platform_get_irq(pdev, 0); 212 if (irq < 0) 213 return irq; 214 215 csi->bus_clk = devm_clk_get(&pdev->dev, "bus"); 216 if (IS_ERR(csi->bus_clk)) { 217 dev_err(&pdev->dev, "Couldn't get our bus clock\n"); 218 return PTR_ERR(csi->bus_clk); 219 } 220 221 if (csi->traits->has_isp) { 222 csi->isp_clk = devm_clk_get(&pdev->dev, "isp"); 223 if (IS_ERR(csi->isp_clk)) { 224 dev_err(&pdev->dev, "Couldn't get our ISP clock\n"); 225 return PTR_ERR(csi->isp_clk); 226 } 227 } 228 229 csi->ram_clk = devm_clk_get(&pdev->dev, "ram"); 230 if (IS_ERR(csi->ram_clk)) { 231 dev_err(&pdev->dev, "Couldn't get our ram clock\n"); 232 return PTR_ERR(csi->ram_clk); 233 } 234 235 csi->rst = devm_reset_control_get(&pdev->dev, NULL); 236 if (IS_ERR(csi->rst)) { 237 dev_err(&pdev->dev, "Couldn't get our reset line\n"); 238 return PTR_ERR(csi->rst); 239 } 240 241 /* Initialize subdev */ 242 v4l2_subdev_init(subdev, &sun4i_csi_subdev_ops); 243 subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; 244 subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; 245 subdev->owner = THIS_MODULE; 246 snprintf(subdev->name, sizeof(subdev->name), "sun4i-csi-0"); 247 v4l2_set_subdevdata(subdev, csi); 248 249 csi->subdev_pads[CSI_SUBDEV_SINK].flags = 250 MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; 251 csi->subdev_pads[CSI_SUBDEV_SOURCE].flags = MEDIA_PAD_FL_SOURCE; 252 ret = media_entity_pads_init(&subdev->entity, CSI_SUBDEV_PADS, 253 csi->subdev_pads); 254 if (ret < 0) 255 return ret; 256 257 csi->vdev_pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; 258 vdev->entity.ops = &sun4i_csi_video_entity_ops; 259 ret = media_entity_pads_init(&vdev->entity, 1, &csi->vdev_pad); 260 if (ret < 0) 261 return ret; 262 263 ret = sun4i_csi_dma_register(csi, irq); 264 if (ret) 265 goto err_clean_pad; 266 267 ret = sun4i_csi_notifier_init(csi); 268 if (ret) 269 goto err_unregister_media; 270 271 ret = v4l2_async_notifier_register(&csi->v4l, &csi->notifier); 272 if (ret) { 273 dev_err(csi->dev, "Couldn't register our notifier.\n"); 274 goto err_unregister_media; 275 } 276 277 pm_runtime_enable(&pdev->dev); 278 279 return 0; 280 281 err_unregister_media: 282 media_device_unregister(&csi->mdev); 283 sun4i_csi_dma_unregister(csi); 284 285 err_clean_pad: 286 media_device_cleanup(&csi->mdev); 287 288 return ret; 289 } 290 291 static int sun4i_csi_remove(struct platform_device *pdev) 292 { 293 struct sun4i_csi *csi = platform_get_drvdata(pdev); 294 295 v4l2_async_notifier_unregister(&csi->notifier); 296 v4l2_async_notifier_cleanup(&csi->notifier); 297 vb2_video_unregister_device(&csi->vdev); 298 media_device_unregister(&csi->mdev); 299 sun4i_csi_dma_unregister(csi); 300 media_device_cleanup(&csi->mdev); 301 302 return 0; 303 } 304 305 static const struct sun4i_csi_traits sun4i_a10_csi1_traits = { 306 .channels = 1, 307 .max_width = 24, 308 .has_isp = false, 309 }; 310 311 static const struct sun4i_csi_traits sun7i_a20_csi0_traits = { 312 .channels = 4, 313 .max_width = 16, 314 .has_isp = true, 315 }; 316 317 static const struct of_device_id sun4i_csi_of_match[] = { 318 { .compatible = "allwinner,sun4i-a10-csi1", .data = &sun4i_a10_csi1_traits }, 319 { .compatible = "allwinner,sun7i-a20-csi0", .data = &sun7i_a20_csi0_traits }, 320 { /* Sentinel */ } 321 }; 322 MODULE_DEVICE_TABLE(of, sun4i_csi_of_match); 323 324 static int __maybe_unused sun4i_csi_runtime_resume(struct device *dev) 325 { 326 struct sun4i_csi *csi = dev_get_drvdata(dev); 327 328 reset_control_deassert(csi->rst); 329 clk_prepare_enable(csi->bus_clk); 330 clk_prepare_enable(csi->ram_clk); 331 clk_set_rate(csi->isp_clk, 80000000); 332 clk_prepare_enable(csi->isp_clk); 333 334 writel(1, csi->regs + CSI_EN_REG); 335 336 return 0; 337 } 338 339 static int __maybe_unused sun4i_csi_runtime_suspend(struct device *dev) 340 { 341 struct sun4i_csi *csi = dev_get_drvdata(dev); 342 343 clk_disable_unprepare(csi->isp_clk); 344 clk_disable_unprepare(csi->ram_clk); 345 clk_disable_unprepare(csi->bus_clk); 346 347 reset_control_assert(csi->rst); 348 349 return 0; 350 } 351 352 static const struct dev_pm_ops sun4i_csi_pm_ops = { 353 SET_RUNTIME_PM_OPS(sun4i_csi_runtime_suspend, 354 sun4i_csi_runtime_resume, 355 NULL) 356 }; 357 358 static struct platform_driver sun4i_csi_driver = { 359 .probe = sun4i_csi_probe, 360 .remove = sun4i_csi_remove, 361 .driver = { 362 .name = "sun4i-csi", 363 .of_match_table = sun4i_csi_of_match, 364 .pm = &sun4i_csi_pm_ops, 365 }, 366 }; 367 module_platform_driver(sun4i_csi_driver); 368 369 MODULE_DESCRIPTION("Allwinner A10 Camera Sensor Interface driver"); 370 MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>"); 371 MODULE_LICENSE("GPL"); 372