| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0+ |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * MIPI CSI-2 Receiver Subdev for Freescale i.MX6 SOC. |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (c) 2012-2017 Mentor Graphics Inc. |
|---|
| 5 | | - * |
|---|
| 6 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 7 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 8 | | - * the Free Software Foundation; either version 2 of the License, or |
|---|
| 9 | | - * (at your option) any later version. |
|---|
| 10 | 6 | */ |
|---|
| 11 | 7 | #include <linux/clk.h> |
|---|
| 12 | 8 | #include <linux/interrupt.h> |
|---|
| .. | .. |
|---|
| 18 | 14 | #include <linux/platform_device.h> |
|---|
| 19 | 15 | #include <media/v4l2-device.h> |
|---|
| 20 | 16 | #include <media/v4l2-fwnode.h> |
|---|
| 17 | +#include <media/v4l2-mc.h> |
|---|
| 21 | 18 | #include <media/v4l2-subdev.h> |
|---|
| 22 | 19 | #include "imx-media.h" |
|---|
| 23 | 20 | |
|---|
| .. | .. |
|---|
| 39 | 36 | struct csi2_dev { |
|---|
| 40 | 37 | struct device *dev; |
|---|
| 41 | 38 | struct v4l2_subdev sd; |
|---|
| 39 | + struct v4l2_async_notifier notifier; |
|---|
| 42 | 40 | struct media_pad pad[CSI2_NUM_PADS]; |
|---|
| 43 | 41 | struct clk *dphy_clk; |
|---|
| 44 | 42 | struct clk *pllref_clk; |
|---|
| .. | .. |
|---|
| 92 | 90 | static inline struct csi2_dev *sd_to_dev(struct v4l2_subdev *sdev) |
|---|
| 93 | 91 | { |
|---|
| 94 | 92 | return container_of(sdev, struct csi2_dev, sd); |
|---|
| 93 | +} |
|---|
| 94 | + |
|---|
| 95 | +static inline struct csi2_dev *notifier_to_dev(struct v4l2_async_notifier *n) |
|---|
| 96 | +{ |
|---|
| 97 | + return container_of(n, struct csi2_dev, notifier); |
|---|
| 95 | 98 | } |
|---|
| 96 | 99 | |
|---|
| 97 | 100 | /* |
|---|
| .. | .. |
|---|
| 501 | 504 | return ret; |
|---|
| 502 | 505 | } |
|---|
| 503 | 506 | |
|---|
| 504 | | -/* |
|---|
| 505 | | - * retrieve our pads parsed from the OF graph by the media device |
|---|
| 506 | | - */ |
|---|
| 507 | 507 | static int csi2_registered(struct v4l2_subdev *sd) |
|---|
| 508 | 508 | { |
|---|
| 509 | 509 | struct csi2_dev *csi2 = sd_to_dev(sd); |
|---|
| 510 | | - int i, ret; |
|---|
| 511 | | - |
|---|
| 512 | | - for (i = 0; i < CSI2_NUM_PADS; i++) { |
|---|
| 513 | | - csi2->pad[i].flags = (i == CSI2_SINK_PAD) ? |
|---|
| 514 | | - MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; |
|---|
| 515 | | - } |
|---|
| 516 | 510 | |
|---|
| 517 | 511 | /* set a default mbus format */ |
|---|
| 518 | | - ret = imx_media_init_mbus_fmt(&csi2->format_mbus, |
|---|
| 512 | + return imx_media_init_mbus_fmt(&csi2->format_mbus, |
|---|
| 519 | 513 | 640, 480, 0, V4L2_FIELD_NONE, NULL); |
|---|
| 520 | | - if (ret) |
|---|
| 521 | | - return ret; |
|---|
| 522 | | - |
|---|
| 523 | | - return media_entity_pads_init(&sd->entity, CSI2_NUM_PADS, csi2->pad); |
|---|
| 524 | 514 | } |
|---|
| 525 | 515 | |
|---|
| 526 | 516 | static const struct media_entity_operations csi2_entity_ops = { |
|---|
| 527 | 517 | .link_setup = csi2_link_setup, |
|---|
| 528 | 518 | .link_validate = v4l2_subdev_link_validate, |
|---|
| 519 | + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, |
|---|
| 529 | 520 | }; |
|---|
| 530 | 521 | |
|---|
| 531 | 522 | static const struct v4l2_subdev_video_ops csi2_video_ops = { |
|---|
| .. | .. |
|---|
| 547 | 538 | .registered = csi2_registered, |
|---|
| 548 | 539 | }; |
|---|
| 549 | 540 | |
|---|
| 550 | | -static int csi2_parse_endpoints(struct csi2_dev *csi2) |
|---|
| 541 | +static int csi2_notify_bound(struct v4l2_async_notifier *notifier, |
|---|
| 542 | + struct v4l2_subdev *sd, |
|---|
| 543 | + struct v4l2_async_subdev *asd) |
|---|
| 551 | 544 | { |
|---|
| 552 | | - struct device_node *node = csi2->dev->of_node; |
|---|
| 553 | | - struct device_node *epnode; |
|---|
| 554 | | - struct v4l2_fwnode_endpoint ep; |
|---|
| 545 | + struct csi2_dev *csi2 = notifier_to_dev(notifier); |
|---|
| 546 | + struct media_pad *sink = &csi2->sd.entity.pads[CSI2_SINK_PAD]; |
|---|
| 555 | 547 | |
|---|
| 556 | | - epnode = of_graph_get_endpoint_by_regs(node, 0, -1); |
|---|
| 557 | | - if (!epnode) { |
|---|
| 558 | | - v4l2_err(&csi2->sd, "failed to get sink endpoint node\n"); |
|---|
| 559 | | - return -EINVAL; |
|---|
| 560 | | - } |
|---|
| 548 | + return v4l2_create_fwnode_links_to_pad(sd, sink); |
|---|
| 549 | +} |
|---|
| 561 | 550 | |
|---|
| 562 | | - v4l2_fwnode_endpoint_parse(of_fwnode_handle(epnode), &ep); |
|---|
| 563 | | - of_node_put(epnode); |
|---|
| 551 | +static const struct v4l2_async_notifier_operations csi2_notify_ops = { |
|---|
| 552 | + .bound = csi2_notify_bound, |
|---|
| 553 | +}; |
|---|
| 564 | 554 | |
|---|
| 565 | | - if (ep.bus_type != V4L2_MBUS_CSI2) { |
|---|
| 566 | | - v4l2_err(&csi2->sd, "invalid bus type, must be MIPI CSI2\n"); |
|---|
| 567 | | - return -EINVAL; |
|---|
| 568 | | - } |
|---|
| 555 | +static int csi2_async_register(struct csi2_dev *csi2) |
|---|
| 556 | +{ |
|---|
| 557 | + struct v4l2_fwnode_endpoint vep = { |
|---|
| 558 | + .bus_type = V4L2_MBUS_CSI2_DPHY, |
|---|
| 559 | + }; |
|---|
| 560 | + struct v4l2_async_subdev *asd; |
|---|
| 561 | + struct fwnode_handle *ep; |
|---|
| 562 | + int ret; |
|---|
| 569 | 563 | |
|---|
| 570 | | - csi2->bus = ep.bus.mipi_csi2; |
|---|
| 564 | + v4l2_async_notifier_init(&csi2->notifier); |
|---|
| 565 | + |
|---|
| 566 | + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi2->dev), 0, 0, |
|---|
| 567 | + FWNODE_GRAPH_ENDPOINT_NEXT); |
|---|
| 568 | + if (!ep) |
|---|
| 569 | + return -ENOTCONN; |
|---|
| 570 | + |
|---|
| 571 | + ret = v4l2_fwnode_endpoint_parse(ep, &vep); |
|---|
| 572 | + if (ret) |
|---|
| 573 | + goto err_parse; |
|---|
| 574 | + |
|---|
| 575 | + csi2->bus = vep.bus.mipi_csi2; |
|---|
| 571 | 576 | |
|---|
| 572 | 577 | dev_dbg(csi2->dev, "data lanes: %d\n", csi2->bus.num_data_lanes); |
|---|
| 573 | 578 | dev_dbg(csi2->dev, "flags: 0x%08x\n", csi2->bus.flags); |
|---|
| 574 | | - return 0; |
|---|
| 579 | + |
|---|
| 580 | + asd = v4l2_async_notifier_add_fwnode_remote_subdev( |
|---|
| 581 | + &csi2->notifier, ep, sizeof(*asd)); |
|---|
| 582 | + fwnode_handle_put(ep); |
|---|
| 583 | + |
|---|
| 584 | + if (IS_ERR(asd)) |
|---|
| 585 | + return PTR_ERR(asd); |
|---|
| 586 | + |
|---|
| 587 | + csi2->notifier.ops = &csi2_notify_ops; |
|---|
| 588 | + |
|---|
| 589 | + ret = v4l2_async_subdev_notifier_register(&csi2->sd, |
|---|
| 590 | + &csi2->notifier); |
|---|
| 591 | + if (ret) |
|---|
| 592 | + return ret; |
|---|
| 593 | + |
|---|
| 594 | + return v4l2_async_register_subdev(&csi2->sd); |
|---|
| 595 | + |
|---|
| 596 | +err_parse: |
|---|
| 597 | + fwnode_handle_put(ep); |
|---|
| 598 | + return ret; |
|---|
| 575 | 599 | } |
|---|
| 576 | 600 | |
|---|
| 577 | 601 | static int csi2_probe(struct platform_device *pdev) |
|---|
| 578 | 602 | { |
|---|
| 579 | 603 | struct csi2_dev *csi2; |
|---|
| 580 | 604 | struct resource *res; |
|---|
| 581 | | - int ret; |
|---|
| 605 | + int i, ret; |
|---|
| 582 | 606 | |
|---|
| 583 | 607 | csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL); |
|---|
| 584 | 608 | if (!csi2) |
|---|
| .. | .. |
|---|
| 593 | 617 | csi2->sd.dev = &pdev->dev; |
|---|
| 594 | 618 | csi2->sd.owner = THIS_MODULE; |
|---|
| 595 | 619 | csi2->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; |
|---|
| 596 | | - strcpy(csi2->sd.name, DEVICE_NAME); |
|---|
| 620 | + strscpy(csi2->sd.name, DEVICE_NAME, sizeof(csi2->sd.name)); |
|---|
| 597 | 621 | csi2->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; |
|---|
| 598 | 622 | csi2->sd.grp_id = IMX_MEDIA_GRP_ID_CSI2; |
|---|
| 599 | 623 | |
|---|
| 600 | | - ret = csi2_parse_endpoints(csi2); |
|---|
| 624 | + for (i = 0; i < CSI2_NUM_PADS; i++) { |
|---|
| 625 | + csi2->pad[i].flags = (i == CSI2_SINK_PAD) ? |
|---|
| 626 | + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; |
|---|
| 627 | + } |
|---|
| 628 | + |
|---|
| 629 | + ret = media_entity_pads_init(&csi2->sd.entity, CSI2_NUM_PADS, |
|---|
| 630 | + csi2->pad); |
|---|
| 601 | 631 | if (ret) |
|---|
| 602 | 632 | return ret; |
|---|
| 603 | 633 | |
|---|
| 604 | 634 | csi2->pllref_clk = devm_clk_get(&pdev->dev, "ref"); |
|---|
| 605 | 635 | if (IS_ERR(csi2->pllref_clk)) { |
|---|
| 606 | 636 | v4l2_err(&csi2->sd, "failed to get pll reference clock\n"); |
|---|
| 607 | | - ret = PTR_ERR(csi2->pllref_clk); |
|---|
| 608 | | - return ret; |
|---|
| 637 | + return PTR_ERR(csi2->pllref_clk); |
|---|
| 609 | 638 | } |
|---|
| 610 | 639 | |
|---|
| 611 | 640 | csi2->dphy_clk = devm_clk_get(&pdev->dev, "dphy"); |
|---|
| 612 | 641 | if (IS_ERR(csi2->dphy_clk)) { |
|---|
| 613 | 642 | v4l2_err(&csi2->sd, "failed to get dphy clock\n"); |
|---|
| 614 | | - ret = PTR_ERR(csi2->dphy_clk); |
|---|
| 615 | | - return ret; |
|---|
| 643 | + return PTR_ERR(csi2->dphy_clk); |
|---|
| 616 | 644 | } |
|---|
| 617 | 645 | |
|---|
| 618 | 646 | csi2->pix_clk = devm_clk_get(&pdev->dev, "pix"); |
|---|
| 619 | 647 | if (IS_ERR(csi2->pix_clk)) { |
|---|
| 620 | 648 | v4l2_err(&csi2->sd, "failed to get pixel clock\n"); |
|---|
| 621 | | - ret = PTR_ERR(csi2->pix_clk); |
|---|
| 622 | | - return ret; |
|---|
| 649 | + return PTR_ERR(csi2->pix_clk); |
|---|
| 623 | 650 | } |
|---|
| 624 | 651 | |
|---|
| 625 | 652 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
|---|
| .. | .. |
|---|
| 629 | 656 | } |
|---|
| 630 | 657 | |
|---|
| 631 | 658 | csi2->base = devm_ioremap(&pdev->dev, res->start, PAGE_SIZE); |
|---|
| 632 | | - if (!csi2->base) { |
|---|
| 633 | | - v4l2_err(&csi2->sd, "failed to map CSI-2 registers\n"); |
|---|
| 659 | + if (!csi2->base) |
|---|
| 634 | 660 | return -ENOMEM; |
|---|
| 635 | | - } |
|---|
| 636 | 661 | |
|---|
| 637 | 662 | mutex_init(&csi2->lock); |
|---|
| 638 | 663 | |
|---|
| .. | .. |
|---|
| 650 | 675 | |
|---|
| 651 | 676 | platform_set_drvdata(pdev, &csi2->sd); |
|---|
| 652 | 677 | |
|---|
| 653 | | - ret = v4l2_async_register_subdev(&csi2->sd); |
|---|
| 678 | + ret = csi2_async_register(csi2); |
|---|
| 654 | 679 | if (ret) |
|---|
| 655 | | - goto dphy_off; |
|---|
| 680 | + goto clean_notifier; |
|---|
| 656 | 681 | |
|---|
| 657 | 682 | return 0; |
|---|
| 658 | 683 | |
|---|
| 659 | | -dphy_off: |
|---|
| 684 | +clean_notifier: |
|---|
| 685 | + v4l2_async_notifier_unregister(&csi2->notifier); |
|---|
| 686 | + v4l2_async_notifier_cleanup(&csi2->notifier); |
|---|
| 660 | 687 | clk_disable_unprepare(csi2->dphy_clk); |
|---|
| 661 | 688 | pllref_off: |
|---|
| 662 | 689 | clk_disable_unprepare(csi2->pllref_clk); |
|---|
| .. | .. |
|---|
| 670 | 697 | struct v4l2_subdev *sd = platform_get_drvdata(pdev); |
|---|
| 671 | 698 | struct csi2_dev *csi2 = sd_to_dev(sd); |
|---|
| 672 | 699 | |
|---|
| 700 | + v4l2_async_notifier_unregister(&csi2->notifier); |
|---|
| 701 | + v4l2_async_notifier_cleanup(&csi2->notifier); |
|---|
| 673 | 702 | v4l2_async_unregister_subdev(sd); |
|---|
| 674 | 703 | clk_disable_unprepare(csi2->dphy_clk); |
|---|
| 675 | 704 | clk_disable_unprepare(csi2->pllref_clk); |
|---|