1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright 2019-2020 NXP 4 */ 5 6 #include <linux/clk.h> 7 #include <linux/device.h> 8 #include <linux/errno.h> 9 #include <linux/kernel.h> 10 #include <linux/mfd/syscon.h> 11 #include <linux/module.h> 12 #include <linux/of_device.h> 13 #include <linux/platform_device.h> 14 #include <linux/pm.h> 15 #include <linux/pm_runtime.h> 16 #include <linux/property.h> 17 #include <linux/slab.h> 18 #include <linux/string.h> 19 #include <linux/types.h> 20 21 #include <media/media-device.h> 22 #include <media/v4l2-async.h> 23 #include <media/v4l2-device.h> 24 #include <media/v4l2-mc.h> 25 26 #include "imx8-isi-core.h" 27 28 /* ----------------------------------------------------------------------------- 29 * V4L2 async subdevs 30 */ 31 32 struct mxc_isi_async_subdev { 33 struct v4l2_async_subdev asd; 34 unsigned int port; 35 }; 36 37 static inline struct mxc_isi_async_subdev * 38 asd_to_mxc_isi_async_subdev(struct v4l2_async_subdev *asd) 39 { 40 return container_of(asd, struct mxc_isi_async_subdev, asd); 41 }; 42 43 static inline struct mxc_isi_dev * 44 notifier_to_mxc_isi_dev(struct v4l2_async_notifier *n) 45 { 46 return container_of(n, struct mxc_isi_dev, notifier); 47 }; 48 49 static int mxc_isi_async_notifier_bound(struct v4l2_async_notifier *notifier, 50 struct v4l2_subdev *sd, 51 struct v4l2_async_subdev *asd) 52 { 53 const unsigned int link_flags = MEDIA_LNK_FL_IMMUTABLE 54 | MEDIA_LNK_FL_ENABLED; 55 struct mxc_isi_dev *isi = notifier_to_mxc_isi_dev(notifier); 56 struct mxc_isi_async_subdev *masd = asd_to_mxc_isi_async_subdev(asd); 57 struct media_pad *pad = &isi->crossbar.pads[masd->port]; 58 struct device_link *link; 59 60 dev_dbg(isi->dev, "Bound subdev %s to crossbar input %u\n", sd->name, 61 masd->port); 62 63 /* 64 * Enforce suspend/resume ordering between the source (supplier) and 65 * the ISI (consumer). The source will be suspended before and resume 66 * after the ISI. 67 */ 68 link = device_link_add(isi->dev, sd->dev, DL_FLAG_STATELESS); 69 if (!link) { 70 dev_err(isi->dev, 71 "Failed to create device link to source %s\n", sd->name); 72 return -EINVAL; 73 } 74 75 return v4l2_create_fwnode_links_to_pad(sd, pad, link_flags); 76 } 77 78 static int mxc_isi_async_notifier_complete(struct v4l2_async_notifier *notifier) 79 { 80 struct mxc_isi_dev *isi = notifier_to_mxc_isi_dev(notifier); 81 int ret; 82 83 dev_dbg(isi->dev, "All subdevs bound\n"); 84 85 ret = v4l2_device_register_subdev_nodes(&isi->v4l2_dev); 86 if (ret < 0) { 87 dev_err(isi->dev, 88 "Failed to register subdev nodes: %d\n", ret); 89 return ret; 90 } 91 92 return media_device_register(&isi->media_dev); 93 } 94 95 static const struct v4l2_async_notifier_operations mxc_isi_async_notifier_ops = { 96 .bound = mxc_isi_async_notifier_bound, 97 .complete = mxc_isi_async_notifier_complete, 98 }; 99 100 static int mxc_isi_pipe_register(struct mxc_isi_pipe *pipe) 101 { 102 int ret; 103 104 ret = v4l2_device_register_subdev(&pipe->isi->v4l2_dev, &pipe->sd); 105 if (ret < 0) 106 return ret; 107 108 return mxc_isi_video_register(pipe, &pipe->isi->v4l2_dev); 109 } 110 111 static void mxc_isi_pipe_unregister(struct mxc_isi_pipe *pipe) 112 { 113 mxc_isi_video_unregister(pipe); 114 } 115 116 static int mxc_isi_v4l2_init(struct mxc_isi_dev *isi) 117 { 118 struct fwnode_handle *node = dev_fwnode(isi->dev); 119 struct media_device *media_dev = &isi->media_dev; 120 struct v4l2_device *v4l2_dev = &isi->v4l2_dev; 121 unsigned int i; 122 int ret; 123 124 /* Initialize the media device. */ 125 strscpy(media_dev->model, "FSL Capture Media Device", 126 sizeof(media_dev->model)); 127 media_dev->dev = isi->dev; 128 129 media_device_init(media_dev); 130 131 /* Initialize and register the V4L2 device. */ 132 v4l2_dev->mdev = media_dev; 133 strscpy(v4l2_dev->name, "mx8-img-md", sizeof(v4l2_dev->name)); 134 135 ret = v4l2_device_register(isi->dev, v4l2_dev); 136 if (ret < 0) { 137 dev_err(isi->dev, 138 "Failed to register V4L2 device: %d\n", ret); 139 goto err_media; 140 } 141 142 /* Register the crossbar switch subdev. */ 143 ret = mxc_isi_crossbar_register(&isi->crossbar); 144 if (ret < 0) { 145 dev_err(isi->dev, "Failed to register crossbar: %d\n", ret); 146 goto err_v4l2; 147 } 148 149 /* Register the pipeline subdevs and link them to the crossbar switch. */ 150 for (i = 0; i < isi->pdata->num_channels; ++i) { 151 struct mxc_isi_pipe *pipe = &isi->pipes[i]; 152 153 ret = mxc_isi_pipe_register(pipe); 154 if (ret < 0) { 155 dev_err(isi->dev, "Failed to register pipe%u: %d\n", i, 156 ret); 157 goto err_v4l2; 158 } 159 160 ret = media_create_pad_link(&isi->crossbar.sd.entity, 161 isi->crossbar.num_sinks + i, 162 &pipe->sd.entity, 163 MXC_ISI_PIPE_PAD_SINK, 164 MEDIA_LNK_FL_IMMUTABLE | 165 MEDIA_LNK_FL_ENABLED); 166 if (ret < 0) 167 goto err_v4l2; 168 } 169 170 /* Register the M2M device. */ 171 ret = mxc_isi_m2m_register(isi, v4l2_dev); 172 if (ret < 0) { 173 dev_err(isi->dev, "Failed to register M2M device: %d\n", ret); 174 goto err_v4l2; 175 } 176 177 /* Initialize, fill and register the async notifier. */ 178 v4l2_async_nf_init(&isi->notifier); 179 isi->notifier.ops = &mxc_isi_async_notifier_ops; 180 181 for (i = 0; i < isi->pdata->num_ports; ++i) { 182 struct mxc_isi_async_subdev *masd; 183 struct fwnode_handle *ep; 184 185 ep = fwnode_graph_get_endpoint_by_id(node, i, 0, 186 FWNODE_GRAPH_ENDPOINT_NEXT); 187 188 if (!ep) 189 continue; 190 191 masd = v4l2_async_nf_add_fwnode_remote(&isi->notifier, ep, 192 struct mxc_isi_async_subdev); 193 fwnode_handle_put(ep); 194 195 if (IS_ERR(masd)) { 196 ret = PTR_ERR(masd); 197 goto err_m2m; 198 } 199 200 masd->port = i; 201 } 202 203 ret = v4l2_async_nf_register(v4l2_dev, &isi->notifier); 204 if (ret < 0) { 205 dev_err(isi->dev, 206 "Failed to register async notifier: %d\n", ret); 207 goto err_m2m; 208 } 209 210 return 0; 211 212 err_m2m: 213 mxc_isi_m2m_unregister(isi); 214 v4l2_async_nf_cleanup(&isi->notifier); 215 err_v4l2: 216 v4l2_device_unregister(v4l2_dev); 217 err_media: 218 media_device_cleanup(media_dev); 219 return ret; 220 } 221 222 static void mxc_isi_v4l2_cleanup(struct mxc_isi_dev *isi) 223 { 224 unsigned int i; 225 226 v4l2_async_nf_unregister(&isi->notifier); 227 v4l2_async_nf_cleanup(&isi->notifier); 228 229 v4l2_device_unregister(&isi->v4l2_dev); 230 media_device_unregister(&isi->media_dev); 231 232 mxc_isi_m2m_unregister(isi); 233 234 for (i = 0; i < isi->pdata->num_channels; ++i) 235 mxc_isi_pipe_unregister(&isi->pipes[i]); 236 237 mxc_isi_crossbar_unregister(&isi->crossbar); 238 239 media_device_cleanup(&isi->media_dev); 240 } 241 242 /* ----------------------------------------------------------------------------- 243 * Device information 244 */ 245 246 /* Panic will assert when the buffers are 50% full */ 247 248 /* For i.MX8QXP C0 and i.MX8MN ISI IER version */ 249 static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = { 250 .oflw_y_buf_en = { .offset = 19, .mask = 0x80000 }, 251 .oflw_u_buf_en = { .offset = 21, .mask = 0x200000 }, 252 .oflw_v_buf_en = { .offset = 23, .mask = 0x800000 }, 253 254 .panic_y_buf_en = {.offset = 20, .mask = 0x100000 }, 255 .panic_u_buf_en = {.offset = 22, .mask = 0x400000 }, 256 .panic_v_buf_en = {.offset = 24, .mask = 0x1000000 }, 257 }; 258 259 /* For i.MX8MP ISI IER version */ 260 static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = { 261 .oflw_y_buf_en = { .offset = 18, .mask = 0x40000 }, 262 .oflw_u_buf_en = { .offset = 20, .mask = 0x100000 }, 263 .oflw_v_buf_en = { .offset = 22, .mask = 0x400000 }, 264 265 .panic_y_buf_en = {.offset = 19, .mask = 0x80000 }, 266 .panic_u_buf_en = {.offset = 21, .mask = 0x200000 }, 267 .panic_v_buf_en = {.offset = 23, .mask = 0x800000 }, 268 }; 269 270 /* Panic will assert when the buffers are 50% full */ 271 static const struct mxc_isi_set_thd mxc_imx8_isi_thd_v1 = { 272 .panic_set_thd_y = { .mask = 0x0000f, .offset = 0, .threshold = 0x7 }, 273 .panic_set_thd_u = { .mask = 0x00f00, .offset = 8, .threshold = 0x7 }, 274 .panic_set_thd_v = { .mask = 0xf0000, .offset = 16, .threshold = 0x7 }, 275 }; 276 277 static const struct clk_bulk_data mxc_imx8mn_clks[] = { 278 { .id = "axi" }, 279 { .id = "apb" }, 280 }; 281 282 static const struct mxc_isi_plat_data mxc_imx8mn_data = { 283 .model = MXC_ISI_IMX8MN, 284 .num_ports = 1, 285 .num_channels = 1, 286 .reg_offset = 0, 287 .ier_reg = &mxc_imx8_isi_ier_v1, 288 .set_thd = &mxc_imx8_isi_thd_v1, 289 .clks = mxc_imx8mn_clks, 290 .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), 291 .buf_active_reverse = false, 292 .has_gasket = true, 293 .has_36bit_dma = false, 294 }; 295 296 static const struct mxc_isi_plat_data mxc_imx8mp_data = { 297 .model = MXC_ISI_IMX8MP, 298 .num_ports = 2, 299 .num_channels = 2, 300 .reg_offset = 0x2000, 301 .ier_reg = &mxc_imx8_isi_ier_v2, 302 .set_thd = &mxc_imx8_isi_thd_v1, 303 .clks = mxc_imx8mn_clks, 304 .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), 305 .buf_active_reverse = true, 306 .has_gasket = true, 307 .has_36bit_dma = true, 308 }; 309 310 /* ----------------------------------------------------------------------------- 311 * Power management 312 */ 313 314 static int mxc_isi_pm_suspend(struct device *dev) 315 { 316 struct mxc_isi_dev *isi = dev_get_drvdata(dev); 317 unsigned int i; 318 319 for (i = 0; i < isi->pdata->num_channels; ++i) { 320 struct mxc_isi_pipe *pipe = &isi->pipes[i]; 321 322 mxc_isi_video_suspend(pipe); 323 } 324 325 return pm_runtime_force_suspend(dev); 326 } 327 328 static int mxc_isi_pm_resume(struct device *dev) 329 { 330 struct mxc_isi_dev *isi = dev_get_drvdata(dev); 331 unsigned int i; 332 int err = 0; 333 int ret; 334 335 ret = pm_runtime_force_resume(dev); 336 if (ret < 0) 337 return ret; 338 339 for (i = 0; i < isi->pdata->num_channels; ++i) { 340 struct mxc_isi_pipe *pipe = &isi->pipes[i]; 341 342 ret = mxc_isi_video_resume(pipe); 343 if (ret) { 344 dev_err(dev, "Failed to resume pipeline %u (%d)\n", i, 345 ret); 346 /* 347 * Record the last error as it's as meaningful as any, 348 * and continue resuming the other pipelines. 349 */ 350 err = ret; 351 } 352 } 353 354 return err; 355 } 356 357 static int mxc_isi_runtime_suspend(struct device *dev) 358 { 359 struct mxc_isi_dev *isi = dev_get_drvdata(dev); 360 361 clk_bulk_disable_unprepare(isi->pdata->num_clks, isi->clks); 362 363 return 0; 364 } 365 366 static int mxc_isi_runtime_resume(struct device *dev) 367 { 368 struct mxc_isi_dev *isi = dev_get_drvdata(dev); 369 int ret; 370 371 ret = clk_bulk_prepare_enable(isi->pdata->num_clks, isi->clks); 372 if (ret) { 373 dev_err(dev, "Failed to enable clocks (%d)\n", ret); 374 return ret; 375 } 376 377 return 0; 378 } 379 380 static const struct dev_pm_ops mxc_isi_pm_ops = { 381 SET_SYSTEM_SLEEP_PM_OPS(mxc_isi_pm_suspend, mxc_isi_pm_resume) 382 SET_RUNTIME_PM_OPS(mxc_isi_runtime_suspend, mxc_isi_runtime_resume, NULL) 383 }; 384 385 /* ----------------------------------------------------------------------------- 386 * Probe, remove & driver 387 */ 388 389 static int mxc_isi_clk_get(struct mxc_isi_dev *isi) 390 { 391 unsigned int size = isi->pdata->num_clks 392 * sizeof(*isi->clks); 393 int ret; 394 395 isi->clks = devm_kmalloc(isi->dev, size, GFP_KERNEL); 396 if (!isi->clks) 397 return -ENOMEM; 398 399 memcpy(isi->clks, isi->pdata->clks, size); 400 401 ret = devm_clk_bulk_get(isi->dev, isi->pdata->num_clks, 402 isi->clks); 403 if (ret < 0) { 404 dev_err(isi->dev, "Failed to acquire clocks: %d\n", 405 ret); 406 return ret; 407 } 408 409 return 0; 410 } 411 412 static int mxc_isi_probe(struct platform_device *pdev) 413 { 414 struct device *dev = &pdev->dev; 415 struct mxc_isi_dev *isi; 416 unsigned int dma_size; 417 unsigned int i; 418 int ret = 0; 419 420 isi = devm_kzalloc(dev, sizeof(*isi), GFP_KERNEL); 421 if (!isi) 422 return -ENOMEM; 423 424 isi->dev = dev; 425 platform_set_drvdata(pdev, isi); 426 427 isi->pdata = of_device_get_match_data(dev); 428 429 isi->pipes = kcalloc(isi->pdata->num_channels, sizeof(isi->pipes[0]), 430 GFP_KERNEL); 431 if (!isi->pipes) 432 return -ENOMEM; 433 434 ret = mxc_isi_clk_get(isi); 435 if (ret < 0) { 436 dev_err(dev, "Failed to get clocks\n"); 437 return ret; 438 } 439 440 isi->regs = devm_platform_ioremap_resource(pdev, 0); 441 if (IS_ERR(isi->regs)) { 442 dev_err(dev, "Failed to get ISI register map\n"); 443 return PTR_ERR(isi->regs); 444 } 445 446 if (isi->pdata->has_gasket) { 447 isi->gasket = syscon_regmap_lookup_by_phandle(dev->of_node, 448 "fsl,blk-ctrl"); 449 if (IS_ERR(isi->gasket)) { 450 ret = PTR_ERR(isi->gasket); 451 dev_err(dev, "failed to get gasket: %d\n", ret); 452 return ret; 453 } 454 } 455 456 dma_size = isi->pdata->has_36bit_dma ? 36 : 32; 457 ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size)); 458 if (ret) { 459 dev_err(dev, "failed to set DMA mask\n"); 460 return ret; 461 } 462 463 pm_runtime_enable(dev); 464 465 ret = mxc_isi_crossbar_init(isi); 466 if (ret) { 467 dev_err(dev, "Failed to initialize crossbar: %d\n", ret); 468 goto err_pm; 469 } 470 471 for (i = 0; i < isi->pdata->num_channels; ++i) { 472 ret = mxc_isi_pipe_init(isi, i); 473 if (ret < 0) { 474 dev_err(dev, "Failed to initialize pipe%u: %d\n", i, 475 ret); 476 goto err_xbar; 477 } 478 } 479 480 ret = mxc_isi_v4l2_init(isi); 481 if (ret < 0) { 482 dev_err(dev, "Failed to initialize V4L2: %d\n", ret); 483 goto err_xbar; 484 } 485 486 mxc_isi_debug_init(isi); 487 488 return 0; 489 490 err_xbar: 491 mxc_isi_crossbar_cleanup(&isi->crossbar); 492 err_pm: 493 pm_runtime_disable(isi->dev); 494 return ret; 495 } 496 497 static int mxc_isi_remove(struct platform_device *pdev) 498 { 499 struct mxc_isi_dev *isi = platform_get_drvdata(pdev); 500 unsigned int i; 501 502 mxc_isi_debug_cleanup(isi); 503 504 for (i = 0; i < isi->pdata->num_channels; ++i) { 505 struct mxc_isi_pipe *pipe = &isi->pipes[i]; 506 507 mxc_isi_pipe_cleanup(pipe); 508 } 509 510 mxc_isi_crossbar_cleanup(&isi->crossbar); 511 mxc_isi_v4l2_cleanup(isi); 512 513 pm_runtime_disable(isi->dev); 514 515 return 0; 516 } 517 518 static const struct of_device_id mxc_isi_of_match[] = { 519 { .compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data }, 520 { .compatible = "fsl,imx8mp-isi", .data = &mxc_imx8mp_data }, 521 { /* sentinel */ }, 522 }; 523 MODULE_DEVICE_TABLE(of, mxc_isi_of_match); 524 525 static struct platform_driver mxc_isi_driver = { 526 .probe = mxc_isi_probe, 527 .remove = mxc_isi_remove, 528 .driver = { 529 .of_match_table = mxc_isi_of_match, 530 .name = MXC_ISI_DRIVER_NAME, 531 .pm = &mxc_isi_pm_ops, 532 } 533 }; 534 module_platform_driver(mxc_isi_driver); 535 536 MODULE_ALIAS("ISI"); 537 MODULE_AUTHOR("Freescale Semiconductor, Inc."); 538 MODULE_DESCRIPTION("IMX8 Image Sensing Interface driver"); 539 MODULE_LICENSE("GPL"); 540