551 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright 2019-2020 NXP4 */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.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 subdevs30 */31 32struct mxc_isi_async_subdev {33 struct v4l2_async_connection asd;34 unsigned int port;35};36 37static inline struct mxc_isi_async_subdev *38asd_to_mxc_isi_async_subdev(struct v4l2_async_connection *asd)39{40 return container_of(asd, struct mxc_isi_async_subdev, asd);41};42 43static inline struct mxc_isi_dev *44notifier_to_mxc_isi_dev(struct v4l2_async_notifier *n)45{46 return container_of(n, struct mxc_isi_dev, notifier);47};48 49static int mxc_isi_async_notifier_bound(struct v4l2_async_notifier *notifier,50 struct v4l2_subdev *sd,51 struct v4l2_async_connection *asc)52{53 const unsigned int link_flags = MEDIA_LNK_FL_IMMUTABLE54 | 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(asc);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) and65 * the ISI (consumer). The source will be suspended before and resume66 * 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 78static 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 95static 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 100static 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 111static void mxc_isi_pipe_unregister(struct mxc_isi_pipe *pipe)112{113 mxc_isi_video_unregister(pipe);114}115 116static 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, v4l2_dev);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(&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 212err_m2m:213 mxc_isi_m2m_unregister(isi);214 v4l2_async_nf_cleanup(&isi->notifier);215err_v4l2:216 v4l2_device_unregister(v4l2_dev);217err_media:218 media_device_cleanup(media_dev);219 return ret;220}221 222static 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 information244 */245 246/* Panic will assert when the buffers are 50% full */247 248/* For i.MX8QXP C0 and i.MX8MN ISI IER version */249static 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 */260static 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 */271static 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 277static const struct clk_bulk_data mxc_imx8mn_clks[] = {278 { .id = "axi" },279 { .id = "apb" },280};281 282static 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 .gasket_ops = &mxc_imx8_gasket_ops,293 .has_36bit_dma = false,294};295 296static 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 .gasket_ops = &mxc_imx8_gasket_ops,307 .has_36bit_dma = true,308};309 310static const struct mxc_isi_plat_data mxc_imx93_data = {311 .model = MXC_ISI_IMX93,312 .num_ports = 1,313 .num_channels = 1,314 .reg_offset = 0,315 .ier_reg = &mxc_imx8_isi_ier_v2,316 .set_thd = &mxc_imx8_isi_thd_v1,317 .clks = mxc_imx8mn_clks,318 .num_clks = ARRAY_SIZE(mxc_imx8mn_clks),319 .buf_active_reverse = true,320 .gasket_ops = &mxc_imx93_gasket_ops,321 .has_36bit_dma = false,322};323 324/* -----------------------------------------------------------------------------325 * Power management326 */327 328static int mxc_isi_pm_suspend(struct device *dev)329{330 struct mxc_isi_dev *isi = dev_get_drvdata(dev);331 unsigned int i;332 333 for (i = 0; i < isi->pdata->num_channels; ++i) {334 struct mxc_isi_pipe *pipe = &isi->pipes[i];335 336 mxc_isi_video_suspend(pipe);337 }338 339 return pm_runtime_force_suspend(dev);340}341 342static int mxc_isi_pm_resume(struct device *dev)343{344 struct mxc_isi_dev *isi = dev_get_drvdata(dev);345 unsigned int i;346 int err = 0;347 int ret;348 349 ret = pm_runtime_force_resume(dev);350 if (ret < 0)351 return ret;352 353 for (i = 0; i < isi->pdata->num_channels; ++i) {354 struct mxc_isi_pipe *pipe = &isi->pipes[i];355 356 ret = mxc_isi_video_resume(pipe);357 if (ret) {358 dev_err(dev, "Failed to resume pipeline %u (%d)\n", i,359 ret);360 /*361 * Record the last error as it's as meaningful as any,362 * and continue resuming the other pipelines.363 */364 err = ret;365 }366 }367 368 return err;369}370 371static int mxc_isi_runtime_suspend(struct device *dev)372{373 struct mxc_isi_dev *isi = dev_get_drvdata(dev);374 375 clk_bulk_disable_unprepare(isi->pdata->num_clks, isi->clks);376 377 return 0;378}379 380static int mxc_isi_runtime_resume(struct device *dev)381{382 struct mxc_isi_dev *isi = dev_get_drvdata(dev);383 int ret;384 385 ret = clk_bulk_prepare_enable(isi->pdata->num_clks, isi->clks);386 if (ret) {387 dev_err(dev, "Failed to enable clocks (%d)\n", ret);388 return ret;389 }390 391 return 0;392}393 394static const struct dev_pm_ops mxc_isi_pm_ops = {395 SYSTEM_SLEEP_PM_OPS(mxc_isi_pm_suspend, mxc_isi_pm_resume)396 RUNTIME_PM_OPS(mxc_isi_runtime_suspend, mxc_isi_runtime_resume, NULL)397};398 399/* -----------------------------------------------------------------------------400 * Probe, remove & driver401 */402 403static int mxc_isi_clk_get(struct mxc_isi_dev *isi)404{405 unsigned int size = isi->pdata->num_clks406 * sizeof(*isi->clks);407 int ret;408 409 isi->clks = devm_kmemdup(isi->dev, isi->pdata->clks, size, GFP_KERNEL);410 if (!isi->clks)411 return -ENOMEM;412 413 ret = devm_clk_bulk_get(isi->dev, isi->pdata->num_clks,414 isi->clks);415 if (ret < 0) {416 dev_err(isi->dev, "Failed to acquire clocks: %d\n",417 ret);418 return ret;419 }420 421 return 0;422}423 424static int mxc_isi_probe(struct platform_device *pdev)425{426 struct device *dev = &pdev->dev;427 struct mxc_isi_dev *isi;428 unsigned int dma_size;429 unsigned int i;430 int ret = 0;431 432 isi = devm_kzalloc(dev, sizeof(*isi), GFP_KERNEL);433 if (!isi)434 return -ENOMEM;435 436 isi->dev = dev;437 platform_set_drvdata(pdev, isi);438 439 isi->pdata = of_device_get_match_data(dev);440 441 isi->pipes = kcalloc(isi->pdata->num_channels, sizeof(isi->pipes[0]),442 GFP_KERNEL);443 if (!isi->pipes)444 return -ENOMEM;445 446 ret = mxc_isi_clk_get(isi);447 if (ret < 0) {448 dev_err(dev, "Failed to get clocks\n");449 return ret;450 }451 452 isi->regs = devm_platform_ioremap_resource(pdev, 0);453 if (IS_ERR(isi->regs)) {454 dev_err(dev, "Failed to get ISI register map\n");455 return PTR_ERR(isi->regs);456 }457 458 if (isi->pdata->gasket_ops) {459 isi->gasket = syscon_regmap_lookup_by_phandle(dev->of_node,460 "fsl,blk-ctrl");461 if (IS_ERR(isi->gasket)) {462 ret = PTR_ERR(isi->gasket);463 dev_err(dev, "failed to get gasket: %d\n", ret);464 return ret;465 }466 }467 468 dma_size = isi->pdata->has_36bit_dma ? 36 : 32;469 ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size));470 if (ret) {471 dev_err(dev, "failed to set DMA mask\n");472 return ret;473 }474 475 pm_runtime_enable(dev);476 477 ret = mxc_isi_crossbar_init(isi);478 if (ret) {479 dev_err(dev, "Failed to initialize crossbar: %d\n", ret);480 goto err_pm;481 }482 483 for (i = 0; i < isi->pdata->num_channels; ++i) {484 ret = mxc_isi_pipe_init(isi, i);485 if (ret < 0) {486 dev_err(dev, "Failed to initialize pipe%u: %d\n", i,487 ret);488 goto err_xbar;489 }490 }491 492 ret = mxc_isi_v4l2_init(isi);493 if (ret < 0) {494 dev_err(dev, "Failed to initialize V4L2: %d\n", ret);495 goto err_xbar;496 }497 498 mxc_isi_debug_init(isi);499 500 return 0;501 502err_xbar:503 mxc_isi_crossbar_cleanup(&isi->crossbar);504err_pm:505 pm_runtime_disable(isi->dev);506 return ret;507}508 509static void mxc_isi_remove(struct platform_device *pdev)510{511 struct mxc_isi_dev *isi = platform_get_drvdata(pdev);512 unsigned int i;513 514 mxc_isi_debug_cleanup(isi);515 516 for (i = 0; i < isi->pdata->num_channels; ++i) {517 struct mxc_isi_pipe *pipe = &isi->pipes[i];518 519 mxc_isi_pipe_cleanup(pipe);520 }521 522 mxc_isi_crossbar_cleanup(&isi->crossbar);523 mxc_isi_v4l2_cleanup(isi);524 525 pm_runtime_disable(isi->dev);526}527 528static const struct of_device_id mxc_isi_of_match[] = {529 { .compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data },530 { .compatible = "fsl,imx8mp-isi", .data = &mxc_imx8mp_data },531 { .compatible = "fsl,imx93-isi", .data = &mxc_imx93_data },532 { /* sentinel */ },533};534MODULE_DEVICE_TABLE(of, mxc_isi_of_match);535 536static struct platform_driver mxc_isi_driver = {537 .probe = mxc_isi_probe,538 .remove_new = mxc_isi_remove,539 .driver = {540 .of_match_table = mxc_isi_of_match,541 .name = MXC_ISI_DRIVER_NAME,542 .pm = pm_ptr(&mxc_isi_pm_ops),543 }544};545module_platform_driver(mxc_isi_driver);546 547MODULE_ALIAS("ISI");548MODULE_AUTHOR("Freescale Semiconductor, Inc.");549MODULE_DESCRIPTION("IMX8 Image Sensing Interface driver");550MODULE_LICENSE("GPL");551