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