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