File Index
Files and directories
Sign in
FILE

imx8-isi-core.c

File preview with syntax highlighting.

linux-6.19.14/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c 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.