// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2019-2020 NXP */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imx8-isi-core.h" /* ----------------------------------------------------------------------------- * V4L2 async subdevs */ struct mxc_isi_async_subdev { struct v4l2_async_connection asd; unsigned int port; }; static inline struct mxc_isi_async_subdev * asd_to_mxc_isi_async_subdev(struct v4l2_async_connection *asd) { return container_of(asd, struct mxc_isi_async_subdev, asd); }; static inline struct mxc_isi_dev * notifier_to_mxc_isi_dev(struct v4l2_async_notifier *n) { return container_of(n, struct mxc_isi_dev, notifier); }; static int mxc_isi_async_notifier_bound(struct v4l2_async_notifier *notifier, struct v4l2_subdev *sd, struct v4l2_async_connection *asc) { const unsigned int link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED; struct mxc_isi_dev *isi = notifier_to_mxc_isi_dev(notifier); struct mxc_isi_async_subdev *masd = asd_to_mxc_isi_async_subdev(asc); struct media_pad *pad = &isi->crossbar.pads[masd->port]; struct device_link *link; dev_dbg(isi->dev, "Bound subdev %s to crossbar input %u\n", sd->name, masd->port); /* * Enforce suspend/resume ordering between the source (supplier) and * the ISI (consumer). The source will be suspended before and resume * after the ISI. */ link = device_link_add(isi->dev, sd->dev, DL_FLAG_STATELESS); if (!link) { dev_err(isi->dev, "Failed to create device link to source %s\n", sd->name); return -EINVAL; } return v4l2_create_fwnode_links_to_pad(sd, pad, link_flags); } static int mxc_isi_async_notifier_complete(struct v4l2_async_notifier *notifier) { struct mxc_isi_dev *isi = notifier_to_mxc_isi_dev(notifier); int ret; dev_dbg(isi->dev, "All subdevs bound\n"); ret = v4l2_device_register_subdev_nodes(&isi->v4l2_dev); if (ret < 0) { dev_err(isi->dev, "Failed to register subdev nodes: %d\n", ret); return ret; } return media_device_register(&isi->media_dev); } static const struct v4l2_async_notifier_operations mxc_isi_async_notifier_ops = { .bound = mxc_isi_async_notifier_bound, .complete = mxc_isi_async_notifier_complete, }; static int mxc_isi_pipe_register(struct mxc_isi_pipe *pipe) { int ret; ret = v4l2_device_register_subdev(&pipe->isi->v4l2_dev, &pipe->sd); if (ret < 0) return ret; return mxc_isi_video_register(pipe, &pipe->isi->v4l2_dev); } static void mxc_isi_pipe_unregister(struct mxc_isi_pipe *pipe) { mxc_isi_video_unregister(pipe); } static int mxc_isi_v4l2_init(struct mxc_isi_dev *isi) { struct fwnode_handle *node = dev_fwnode(isi->dev); struct media_device *media_dev = &isi->media_dev; struct v4l2_device *v4l2_dev = &isi->v4l2_dev; unsigned int i; int ret; /* Initialize the media device. */ strscpy(media_dev->model, "FSL Capture Media Device", sizeof(media_dev->model)); media_dev->dev = isi->dev; media_device_init(media_dev); /* Initialize and register the V4L2 device. */ v4l2_dev->mdev = media_dev; strscpy(v4l2_dev->name, "mx8-img-md", sizeof(v4l2_dev->name)); ret = v4l2_device_register(isi->dev, v4l2_dev); if (ret < 0) { dev_err(isi->dev, "Failed to register V4L2 device: %d\n", ret); goto err_media; } /* Register the crossbar switch subdev. */ ret = mxc_isi_crossbar_register(&isi->crossbar); if (ret < 0) { dev_err(isi->dev, "Failed to register crossbar: %d\n", ret); goto err_v4l2; } /* Register the pipeline subdevs and link them to the crossbar switch. */ for (i = 0; i < isi->pdata->num_channels; ++i) { struct mxc_isi_pipe *pipe = &isi->pipes[i]; ret = mxc_isi_pipe_register(pipe); if (ret < 0) { dev_err(isi->dev, "Failed to register pipe%u: %d\n", i, ret); goto err_v4l2; } ret = media_create_pad_link(&isi->crossbar.sd.entity, isi->crossbar.num_sinks + i, &pipe->sd.entity, MXC_ISI_PIPE_PAD_SINK, MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); if (ret < 0) goto err_v4l2; } /* Register the M2M device. */ ret = mxc_isi_m2m_register(isi, v4l2_dev); if (ret < 0) { dev_err(isi->dev, "Failed to register M2M device: %d\n", ret); goto err_v4l2; } /* Initialize, fill and register the async notifier. */ v4l2_async_nf_init(&isi->notifier, v4l2_dev); isi->notifier.ops = &mxc_isi_async_notifier_ops; for (i = 0; i < isi->pdata->num_ports; ++i) { struct mxc_isi_async_subdev *masd; struct fwnode_handle *ep; ep = fwnode_graph_get_endpoint_by_id(node, i, 0, FWNODE_GRAPH_ENDPOINT_NEXT); if (!ep) continue; masd = v4l2_async_nf_add_fwnode_remote(&isi->notifier, ep, struct mxc_isi_async_subdev); fwnode_handle_put(ep); if (IS_ERR(masd)) { ret = PTR_ERR(masd); goto err_m2m; } masd->port = i; } ret = v4l2_async_nf_register(&isi->notifier); if (ret < 0) { dev_err(isi->dev, "Failed to register async notifier: %d\n", ret); goto err_m2m; } return 0; err_m2m: mxc_isi_m2m_unregister(isi); v4l2_async_nf_cleanup(&isi->notifier); err_v4l2: v4l2_device_unregister(v4l2_dev); err_media: media_device_cleanup(media_dev); return ret; } static void mxc_isi_v4l2_cleanup(struct mxc_isi_dev *isi) { unsigned int i; v4l2_async_nf_unregister(&isi->notifier); v4l2_async_nf_cleanup(&isi->notifier); v4l2_device_unregister(&isi->v4l2_dev); media_device_unregister(&isi->media_dev); mxc_isi_m2m_unregister(isi); for (i = 0; i < isi->pdata->num_channels; ++i) mxc_isi_pipe_unregister(&isi->pipes[i]); mxc_isi_crossbar_unregister(&isi->crossbar); media_device_cleanup(&isi->media_dev); } /* ----------------------------------------------------------------------------- * Device information */ /* Panic will assert when the buffers are 50% full */ /* For i.MX8QXP C0 and i.MX8MN ISI IER version */ static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = { .oflw_y_buf_en = { .offset = 19, .mask = 0x80000 }, .oflw_u_buf_en = { .offset = 21, .mask = 0x200000 }, .oflw_v_buf_en = { .offset = 23, .mask = 0x800000 }, .panic_y_buf_en = {.offset = 20, .mask = 0x100000 }, .panic_u_buf_en = {.offset = 22, .mask = 0x400000 }, .panic_v_buf_en = {.offset = 24, .mask = 0x1000000 }, }; /* For i.MX8MP ISI IER version */ static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = { .oflw_y_buf_en = { .offset = 18, .mask = 0x40000 }, .oflw_u_buf_en = { .offset = 20, .mask = 0x100000 }, .oflw_v_buf_en = { .offset = 22, .mask = 0x400000 }, .panic_y_buf_en = {.offset = 19, .mask = 0x80000 }, .panic_u_buf_en = {.offset = 21, .mask = 0x200000 }, .panic_v_buf_en = {.offset = 23, .mask = 0x800000 }, }; /* Panic will assert when the buffers are 50% full */ static const struct mxc_isi_set_thd mxc_imx8_isi_thd_v1 = { .panic_set_thd_y = { .mask = 0x0000f, .offset = 0, .threshold = 0x7 }, .panic_set_thd_u = { .mask = 0x00f00, .offset = 8, .threshold = 0x7 }, .panic_set_thd_v = { .mask = 0xf0000, .offset = 16, .threshold = 0x7 }, }; static const struct clk_bulk_data mxc_imx8mn_clks[] = { { .id = "axi" }, { .id = "apb" }, }; static const struct mxc_isi_plat_data mxc_imx8mn_data = { .model = MXC_ISI_IMX8MN, .num_ports = 1, .num_channels = 1, .reg_offset = 0, .ier_reg = &mxc_imx8_isi_ier_v1, .set_thd = &mxc_imx8_isi_thd_v1, .clks = mxc_imx8mn_clks, .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), .buf_active_reverse = false, .gasket_ops = &mxc_imx8_gasket_ops, .has_36bit_dma = false, }; static const struct mxc_isi_plat_data mxc_imx8mp_data = { .model = MXC_ISI_IMX8MP, .num_ports = 2, .num_channels = 2, .reg_offset = 0x2000, .ier_reg = &mxc_imx8_isi_ier_v2, .set_thd = &mxc_imx8_isi_thd_v1, .clks = mxc_imx8mn_clks, .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), .buf_active_reverse = true, .gasket_ops = &mxc_imx8_gasket_ops, .has_36bit_dma = true, }; static const struct mxc_isi_plat_data mxc_imx93_data = { .model = MXC_ISI_IMX93, .num_ports = 1, .num_channels = 1, .reg_offset = 0, .ier_reg = &mxc_imx8_isi_ier_v2, .set_thd = &mxc_imx8_isi_thd_v1, .clks = mxc_imx8mn_clks, .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), .buf_active_reverse = true, .gasket_ops = &mxc_imx93_gasket_ops, .has_36bit_dma = false, }; /* ----------------------------------------------------------------------------- * Power management */ static int mxc_isi_pm_suspend(struct device *dev) { struct mxc_isi_dev *isi = dev_get_drvdata(dev); unsigned int i; for (i = 0; i < isi->pdata->num_channels; ++i) { struct mxc_isi_pipe *pipe = &isi->pipes[i]; mxc_isi_video_suspend(pipe); } return pm_runtime_force_suspend(dev); } static int mxc_isi_pm_resume(struct device *dev) { struct mxc_isi_dev *isi = dev_get_drvdata(dev); unsigned int i; int err = 0; int ret; ret = pm_runtime_force_resume(dev); if (ret < 0) return ret; for (i = 0; i < isi->pdata->num_channels; ++i) { struct mxc_isi_pipe *pipe = &isi->pipes[i]; ret = mxc_isi_video_resume(pipe); if (ret) { dev_err(dev, "Failed to resume pipeline %u (%d)\n", i, ret); /* * Record the last error as it's as meaningful as any, * and continue resuming the other pipelines. */ err = ret; } } return err; } static int mxc_isi_runtime_suspend(struct device *dev) { struct mxc_isi_dev *isi = dev_get_drvdata(dev); clk_bulk_disable_unprepare(isi->pdata->num_clks, isi->clks); return 0; } static int mxc_isi_runtime_resume(struct device *dev) { struct mxc_isi_dev *isi = dev_get_drvdata(dev); int ret; ret = clk_bulk_prepare_enable(isi->pdata->num_clks, isi->clks); if (ret) { dev_err(dev, "Failed to enable clocks (%d)\n", ret); return ret; } return 0; } static const struct dev_pm_ops mxc_isi_pm_ops = { SYSTEM_SLEEP_PM_OPS(mxc_isi_pm_suspend, mxc_isi_pm_resume) RUNTIME_PM_OPS(mxc_isi_runtime_suspend, mxc_isi_runtime_resume, NULL) }; /* ----------------------------------------------------------------------------- * Probe, remove & driver */ static int mxc_isi_clk_get(struct mxc_isi_dev *isi) { unsigned int size = isi->pdata->num_clks * sizeof(*isi->clks); int ret; isi->clks = devm_kmemdup(isi->dev, isi->pdata->clks, size, GFP_KERNEL); if (!isi->clks) return -ENOMEM; ret = devm_clk_bulk_get(isi->dev, isi->pdata->num_clks, isi->clks); if (ret < 0) { dev_err(isi->dev, "Failed to acquire clocks: %d\n", ret); return ret; } return 0; } static int mxc_isi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mxc_isi_dev *isi; unsigned int dma_size; unsigned int i; int ret = 0; isi = devm_kzalloc(dev, sizeof(*isi), GFP_KERNEL); if (!isi) return -ENOMEM; isi->dev = dev; platform_set_drvdata(pdev, isi); isi->pdata = of_device_get_match_data(dev); isi->pipes = kcalloc(isi->pdata->num_channels, sizeof(isi->pipes[0]), GFP_KERNEL); if (!isi->pipes) return -ENOMEM; ret = mxc_isi_clk_get(isi); if (ret < 0) { dev_err(dev, "Failed to get clocks\n"); return ret; } isi->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(isi->regs)) { dev_err(dev, "Failed to get ISI register map\n"); return PTR_ERR(isi->regs); } if (isi->pdata->gasket_ops) { isi->gasket = syscon_regmap_lookup_by_phandle(dev->of_node, "fsl,blk-ctrl"); if (IS_ERR(isi->gasket)) { ret = PTR_ERR(isi->gasket); dev_err(dev, "failed to get gasket: %d\n", ret); return ret; } } dma_size = isi->pdata->has_36bit_dma ? 36 : 32; ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size)); if (ret) { dev_err(dev, "failed to set DMA mask\n"); return ret; } pm_runtime_enable(dev); ret = mxc_isi_crossbar_init(isi); if (ret) { dev_err(dev, "Failed to initialize crossbar: %d\n", ret); goto err_pm; } for (i = 0; i < isi->pdata->num_channels; ++i) { ret = mxc_isi_pipe_init(isi, i); if (ret < 0) { dev_err(dev, "Failed to initialize pipe%u: %d\n", i, ret); goto err_xbar; } } ret = mxc_isi_v4l2_init(isi); if (ret < 0) { dev_err(dev, "Failed to initialize V4L2: %d\n", ret); goto err_xbar; } mxc_isi_debug_init(isi); return 0; err_xbar: mxc_isi_crossbar_cleanup(&isi->crossbar); err_pm: pm_runtime_disable(isi->dev); return ret; } static void mxc_isi_remove(struct platform_device *pdev) { struct mxc_isi_dev *isi = platform_get_drvdata(pdev); unsigned int i; mxc_isi_debug_cleanup(isi); for (i = 0; i < isi->pdata->num_channels; ++i) { struct mxc_isi_pipe *pipe = &isi->pipes[i]; mxc_isi_pipe_cleanup(pipe); } mxc_isi_crossbar_cleanup(&isi->crossbar); mxc_isi_v4l2_cleanup(isi); pm_runtime_disable(isi->dev); } static const struct of_device_id mxc_isi_of_match[] = { { .compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data }, { .compatible = "fsl,imx8mp-isi", .data = &mxc_imx8mp_data }, { .compatible = "fsl,imx93-isi", .data = &mxc_imx93_data }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, mxc_isi_of_match); static struct platform_driver mxc_isi_driver = { .probe = mxc_isi_probe, .remove_new = mxc_isi_remove, .driver = { .of_match_table = mxc_isi_of_match, .name = MXC_ISI_DRIVER_NAME, .pm = pm_ptr(&mxc_isi_pm_ops), } }; module_platform_driver(mxc_isi_driver); MODULE_ALIAS("ISI"); MODULE_AUTHOR("Freescale Semiconductor, Inc."); MODULE_DESCRIPTION("IMX8 Image Sensing Interface driver"); MODULE_LICENSE("GPL");