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(n: notifier);
57 struct mxc_isi_async_subdev *masd = asd_to_mxc_isi_async_subdev(asd: 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(consumer: isi->dev, supplier: 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(src_sd: sd, sink: pad, flags: 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(n: notifier);
82 int ret;
83
84 dev_dbg(isi->dev, "All subdevs bound\n");
85
86 ret = v4l2_device_register_subdev_nodes(v4l2_dev: &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, v4l2_dev: &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(mdev: 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(dev: 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(xbar: &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(source: &isi->crossbar.sd.entity,
162 source_pad: isi->crossbar.num_sinks + i,
163 sink: &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(notifier: &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(fwnode: node, port: i, endpoint: 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(fwnode: ep);
195
196 if (IS_ERR(ptr: masd)) {
197 ret = PTR_ERR(ptr: masd);
198 goto err_m2m;
199 }
200
201 masd->port = i;
202 }
203
204 ret = v4l2_async_nf_register(notifier: &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(notifier: &isi->notifier);
216err_v4l2:
217 v4l2_device_unregister(v4l2_dev);
218err_media:
219 media_device_cleanup(mdev: 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(notifier: &isi->notifier);
228 v4l2_async_nf_cleanup(notifier: &isi->notifier);
229
230 v4l2_device_unregister(v4l2_dev: &isi->v4l2_dev);
231 media_device_unregister(mdev: &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(pipe: &isi->pipes[i]);
237
238 mxc_isi_crossbar_unregister(xbar: &isi->crossbar);
239
240 media_device_cleanup(mdev: &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(m2m: &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(m2m: &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(num_clks: isi->num_clks, 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(num_clks: isi->num_clks, 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, size: sizeof(*isi), GFP_KERNEL);
469 if (!isi)
470 return -ENOMEM;
471
472 isi->dev = dev;
473 platform_set_drvdata(pdev, data: 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, clks: &isi->clks);
483 if (isi->num_clks < 0)
484 return dev_err_probe(dev, err: isi->num_clks, fmt: "Failed to get clocks\n");
485
486 isi->regs = devm_platform_ioremap_resource(pdev, index: 0);
487 if (IS_ERR(ptr: isi->regs))
488 return dev_err_probe(dev, err: PTR_ERR(ptr: isi->regs),
489 fmt: "Failed to get ISI register map\n");
490
491 if (isi->pdata->gasket_ops) {
492 isi->gasket = syscon_regmap_lookup_by_phandle(np: dev->of_node,
493 property: "fsl,blk-ctrl");
494 if (IS_ERR(ptr: isi->gasket))
495 return dev_err_probe(dev, err: PTR_ERR(ptr: isi->gasket),
496 fmt: "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, id: 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(xbar: &isi->crossbar);
531err_pm:
532 pm_runtime_disable(dev: 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(xbar: &isi->crossbar);
550 mxc_isi_v4l2_cleanup(isi);
551
552 pm_runtime_disable(dev: 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");
582

source code of linux/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c