brintos

brintos / linux-shallow public Read only

0
0
Text · 13.5 KiB · c201399 Raw
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