Merge drm/drm-next into drm-misc-next
authorThomas Zimmermann <tzimmermann@suse.de>
Mon, 19 Jun 2023 14:33:14 +0000 (16:33 +0200)
committerThomas Zimmermann <tzimmermann@suse.de>
Mon, 19 Jun 2023 14:33:14 +0000 (16:33 +0200)
Backmerging into drm-misc-next to get commit 2c1c7ba457d4
("drm/amdgpu: support partition drm devices"), which is required to fix
commit 0adec22702d4 ("drm: Remove struct drm_driver.gem_prime_mmap").

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
16 files changed:
1  2 
MAINTAINERS
drivers/accel/ivpu/ivpu_hw_mtl.c
drivers/accel/ivpu/ivpu_job.c
drivers/accel/ivpu/ivpu_mmu.c
drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
drivers/gpu/drm/msm/msm_drv.c
drivers/gpu/drm/msm/msm_gem.c
drivers/gpu/drm/nouveau/nouveau_drm.c
drivers/gpu/drm/pl111/pl111_drv.c
drivers/gpu/drm/renesas/rcar-du/rcar_cmm.c
drivers/gpu/drm/renesas/rcar-du/rcar_du_drv.c
drivers/gpu/drm/renesas/rcar-du/rcar_dw_hdmi.c
drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c
drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c
drivers/gpu/drm/renesas/rcar-du/rzg2l_mipi_dsi.c
drivers/gpu/drm/vc4/vc4_hdmi.c

diff --cc MAINTAINERS
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 0000000000000000000000000000000000000000,e2a67dda46584d9627edfd83b3ad0c0cfa9ecf38..26a2f5ad8ee5d4ecc8cb6698e16e373a5045bf3d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,217 +1,215 @@@
 -static int rcar_cmm_remove(struct platform_device *pdev)
+ // SPDX-License-Identifier: GPL-2.0+
+ /*
+  * R-Car Display Unit Color Management Module
+  *
+  * Copyright (C) 2019 Jacopo Mondi <jacopo+renesas@jmondi.org>
+  */
+ #include <linux/io.h>
+ #include <linux/module.h>
+ #include <linux/of.h>
+ #include <linux/platform_device.h>
+ #include <linux/pm_runtime.h>
+ #include <drm/drm_color_mgmt.h>
+ #include "rcar_cmm.h"
+ #define CM2_LUT_CTRL          0x0000
+ #define CM2_LUT_CTRL_LUT_EN   BIT(0)
+ #define CM2_LUT_TBL_BASE      0x0600
+ #define CM2_LUT_TBL(__i)      (CM2_LUT_TBL_BASE + (__i) * 4)
+ struct rcar_cmm {
+       void __iomem *base;
+       /*
+        * @lut:                1D-LUT state
+        * @lut.enabled:        1D-LUT enabled flag
+        */
+       struct {
+               bool enabled;
+       } lut;
+ };
+ static inline int rcar_cmm_read(struct rcar_cmm *rcmm, u32 reg)
+ {
+       return ioread32(rcmm->base + reg);
+ }
+ static inline void rcar_cmm_write(struct rcar_cmm *rcmm, u32 reg, u32 data)
+ {
+       iowrite32(data, rcmm->base + reg);
+ }
+ /*
+  * rcar_cmm_lut_write() - Scale the DRM LUT table entries to hardware precision
+  *                      and write to the CMM registers
+  * @rcmm: Pointer to the CMM device
+  * @drm_lut: Pointer to the DRM LUT table
+  */
+ static void rcar_cmm_lut_write(struct rcar_cmm *rcmm,
+                              const struct drm_color_lut *drm_lut)
+ {
+       unsigned int i;
+       for (i = 0; i < CM2_LUT_SIZE; ++i) {
+               u32 entry = drm_color_lut_extract(drm_lut[i].red, 8) << 16
+                         | drm_color_lut_extract(drm_lut[i].green, 8) << 8
+                         | drm_color_lut_extract(drm_lut[i].blue, 8);
+               rcar_cmm_write(rcmm, CM2_LUT_TBL(i), entry);
+       }
+ }
+ /*
+  * rcar_cmm_setup() - Configure the CMM unit
+  * @pdev: The platform device associated with the CMM instance
+  * @config: The CMM unit configuration
+  *
+  * Configure the CMM unit with the given configuration. Currently enabling,
+  * disabling and programming of the 1-D LUT unit is supported.
+  *
+  * As rcar_cmm_setup() accesses the CMM registers the unit should be powered
+  * and its functional clock enabled. To guarantee this, before any call to
+  * this function is made, the CMM unit has to be enabled by calling
+  * rcar_cmm_enable() first.
+  *
+  * TODO: Add support for LUT double buffer operations to avoid updating the
+  * LUT table entries while a frame is being displayed.
+  */
+ int rcar_cmm_setup(struct platform_device *pdev,
+                  const struct rcar_cmm_config *config)
+ {
+       struct rcar_cmm *rcmm = platform_get_drvdata(pdev);
+       /* Disable LUT if no table is provided. */
+       if (!config->lut.table) {
+               if (rcmm->lut.enabled) {
+                       rcar_cmm_write(rcmm, CM2_LUT_CTRL, 0);
+                       rcmm->lut.enabled = false;
+               }
+               return 0;
+       }
+       /* Enable LUT and program the new gamma table values. */
+       if (!rcmm->lut.enabled) {
+               rcar_cmm_write(rcmm, CM2_LUT_CTRL, CM2_LUT_CTRL_LUT_EN);
+               rcmm->lut.enabled = true;
+       }
+       rcar_cmm_lut_write(rcmm, config->lut.table);
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(rcar_cmm_setup);
+ /*
+  * rcar_cmm_enable() - Enable the CMM unit
+  * @pdev: The platform device associated with the CMM instance
+  *
+  * When the output of the corresponding DU channel is routed to the CMM unit,
+  * the unit shall be enabled before the DU channel is started, and remain
+  * enabled until the channel is stopped. The CMM unit shall be disabled with
+  * rcar_cmm_disable().
+  *
+  * Calls to rcar_cmm_enable() and rcar_cmm_disable() are not reference-counted.
+  * It is an error to attempt to enable an already enabled CMM unit, or to
+  * attempt to disable a disabled unit.
+  */
+ int rcar_cmm_enable(struct platform_device *pdev)
+ {
+       int ret;
+       ret = pm_runtime_resume_and_get(&pdev->dev);
+       if (ret < 0)
+               return ret;
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(rcar_cmm_enable);
+ /*
+  * rcar_cmm_disable() - Disable the CMM unit
+  * @pdev: The platform device associated with the CMM instance
+  *
+  * See rcar_cmm_enable() for usage information.
+  *
+  * Disabling the CMM unit disable all the internal processing blocks. The CMM
+  * state shall thus be restored with rcar_cmm_setup() when re-enabling the CMM
+  * unit after the next rcar_cmm_enable() call.
+  */
+ void rcar_cmm_disable(struct platform_device *pdev)
+ {
+       struct rcar_cmm *rcmm = platform_get_drvdata(pdev);
+       rcar_cmm_write(rcmm, CM2_LUT_CTRL, 0);
+       rcmm->lut.enabled = false;
+       pm_runtime_put(&pdev->dev);
+ }
+ EXPORT_SYMBOL_GPL(rcar_cmm_disable);
+ /*
+  * rcar_cmm_init() - Initialize the CMM unit
+  * @pdev: The platform device associated with the CMM instance
+  *
+  * Return: 0 on success, -EPROBE_DEFER if the CMM is not available yet,
+  *         -ENODEV if the DRM_RCAR_CMM config option is disabled
+  */
+ int rcar_cmm_init(struct platform_device *pdev)
+ {
+       struct rcar_cmm *rcmm = platform_get_drvdata(pdev);
+       if (!rcmm)
+               return -EPROBE_DEFER;
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(rcar_cmm_init);
+ static int rcar_cmm_probe(struct platform_device *pdev)
+ {
+       struct rcar_cmm *rcmm;
+       rcmm = devm_kzalloc(&pdev->dev, sizeof(*rcmm), GFP_KERNEL);
+       if (!rcmm)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, rcmm);
+       rcmm->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(rcmm->base))
+               return PTR_ERR(rcmm->base);
+       pm_runtime_enable(&pdev->dev);
+       return 0;
+ }
 -
 -      return 0;
++static void rcar_cmm_remove(struct platform_device *pdev)
+ {
+       pm_runtime_disable(&pdev->dev);
 -      .remove         = rcar_cmm_remove,
+ }
+ static const struct of_device_id rcar_cmm_of_table[] = {
+       { .compatible = "renesas,rcar-gen3-cmm", },
+       { .compatible = "renesas,rcar-gen2-cmm", },
+       { },
+ };
+ MODULE_DEVICE_TABLE(of, rcar_cmm_of_table);
+ static struct platform_driver rcar_cmm_platform_driver = {
+       .probe          = rcar_cmm_probe,
++      .remove_new     = rcar_cmm_remove,
+       .driver         = {
+               .name   = "rcar-cmm",
+               .of_match_table = rcar_cmm_of_table,
+       },
+ };
+ module_platform_driver(rcar_cmm_platform_driver);
+ MODULE_AUTHOR("Jacopo Mondi <jacopo+renesas@jmondi.org>");
+ MODULE_DESCRIPTION("Renesas R-Car CMM Driver");
+ MODULE_LICENSE("GPL v2");
index 0000000000000000000000000000000000000000,1ffde19cb87fe1fb672e4e67586ecc5cb8138db5..4280ff5fa91f276a49ffff530f6fe419e0070fad
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,744 +1,741 @@@
 -      .gem_prime_mmap         = drm_gem_prime_mmap,
+ // SPDX-License-Identifier: GPL-2.0+
+ /*
+  * R-Car Display Unit DRM driver
+  *
+  * Copyright (C) 2013-2015 Renesas Electronics Corporation
+  *
+  * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+  */
+ #include <linux/clk.h>
+ #include <linux/dma-mapping.h>
+ #include <linux/io.h>
+ #include <linux/mm.h>
+ #include <linux/module.h>
+ #include <linux/of_device.h>
+ #include <linux/platform_device.h>
+ #include <linux/pm.h>
+ #include <linux/slab.h>
+ #include <linux/wait.h>
+ #include <drm/drm_atomic_helper.h>
+ #include <drm/drm_drv.h>
+ #include <drm/drm_fbdev_generic.h>
+ #include <drm/drm_gem_dma_helper.h>
+ #include <drm/drm_managed.h>
+ #include <drm/drm_probe_helper.h>
+ #include "rcar_du_drv.h"
+ #include "rcar_du_kms.h"
+ /* -----------------------------------------------------------------------------
+  * Device Information
+  */
+ static const struct rcar_du_device_info rzg1_du_r8a7743_info = {
+       .gen = 2,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A774[34] has one RGB output and one LVDS output
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(1) | BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 1,
+               },
+       },
+       .num_lvds = 1,
+       .num_rpf = 4,
+ };
+ static const struct rcar_du_device_info rzg1_du_r8a7745_info = {
+       .gen = 2,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A7745 has two RGB outputs
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_DPAD1] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+       },
+       .num_rpf = 4,
+ };
+ static const struct rcar_du_device_info rzg1_du_r8a77470_info = {
+       .gen = 2,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A77470 has two RGB outputs, one LVDS output, and
+                * one (currently unsupported) analog video output
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_DPAD1] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0) | BIT(1),
+                       .port = 2,
+               },
+       },
+       .num_rpf = 4,
+ };
+ static const struct rcar_du_device_info rcar_du_r8a774a1_info = {
+       .gen = 3,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_VSP1_SOURCE
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(2) | BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A774A1 has one RGB output, one LVDS output and one HDMI
+                * output.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(2),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_HDMI0] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 2,
+               },
+       },
+       .num_lvds = 1,
+       .num_rpf = 5,
+       .dpll_mask =  BIT(1),
+ };
+ static const struct rcar_du_device_info rcar_du_r8a774b1_info = {
+       .gen = 3,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_VSP1_SOURCE
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(3) | BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A774B1 has one RGB output, one LVDS output and one HDMI
+                * output.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(2),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_HDMI0] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 2,
+               },
+       },
+       .num_lvds = 1,
+       .num_rpf = 5,
+       .dpll_mask =  BIT(1),
+ };
+ static const struct rcar_du_device_info rcar_du_r8a774c0_info = {
+       .gen = 3,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_VSP1_SOURCE,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A774C0 has one RGB output and two LVDS outputs
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(0) | BIT(1),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 1,
+               },
+               [RCAR_DU_OUTPUT_LVDS1] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 2,
+               },
+       },
+       .num_lvds = 2,
+       .num_rpf = 4,
+       .lvds_clk_mask =  BIT(1) | BIT(0),
+ };
+ static const struct rcar_du_device_info rcar_du_r8a774e1_info = {
+       .gen = 3,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_VSP1_SOURCE
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(3) | BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A774E1 has one RGB output, one LVDS output and one HDMI
+                * output.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(2),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_HDMI0] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 2,
+               },
+       },
+       .num_lvds = 1,
+       .num_rpf = 5,
+       .dpll_mask =  BIT(1),
+ };
+ static const struct rcar_du_device_info rcar_du_r8a7779_info = {
+       .gen = 1,
+       .features = RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A7779 has two RGB outputs and one (currently unsupported)
+                * TCON output.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_DPAD1] = {
+                       .possible_crtcs = BIT(1) | BIT(0),
+                       .port = 1,
+               },
+       },
+ };
+ static const struct rcar_du_device_info rcar_du_r8a7790_info = {
+       .gen = 2,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .quirks = RCAR_DU_QUIRK_ALIGN_128B,
+       .channels_mask = BIT(2) | BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A7742 and R8A7790 each have one RGB output and two LVDS
+                * outputs. Additionally R8A7790 supports one TCON output
+                * (currently unsupported by the driver).
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(2) | BIT(1) | BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 1,
+               },
+               [RCAR_DU_OUTPUT_LVDS1] = {
+                       .possible_crtcs = BIT(2) | BIT(1),
+                       .port = 2,
+               },
+       },
+       .num_lvds = 2,
+       .num_rpf = 4,
+ };
+ /* M2-W (r8a7791) and M2-N (r8a7793) are identical */
+ static const struct rcar_du_device_info rcar_du_r8a7791_info = {
+       .gen = 2,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A779[13] has one RGB output, one LVDS output and one
+                * (currently unsupported) TCON output.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(1) | BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 1,
+               },
+       },
+       .num_lvds = 1,
+       .num_rpf = 4,
+ };
+ static const struct rcar_du_device_info rcar_du_r8a7792_info = {
+       .gen = 2,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /* R8A7792 has two RGB outputs. */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_DPAD1] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+       },
+       .num_rpf = 4,
+ };
+ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
+       .gen = 2,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A7794 has two RGB outputs and one (currently unsupported)
+                * TCON output.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_DPAD1] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+       },
+       .num_rpf = 4,
+ };
+ static const struct rcar_du_device_info rcar_du_r8a7795_info = {
+       .gen = 3,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_VSP1_SOURCE
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(3) | BIT(2) | BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A7795 has one RGB output, two HDMI outputs and one
+                * LVDS output.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(3),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_HDMI0] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+               [RCAR_DU_OUTPUT_HDMI1] = {
+                       .possible_crtcs = BIT(2),
+                       .port = 2,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 3,
+               },
+       },
+       .num_lvds = 1,
+       .num_rpf = 5,
+       .dpll_mask =  BIT(2) | BIT(1),
+ };
+ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
+       .gen = 3,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_VSP1_SOURCE
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(2) | BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A7796 has one RGB output, one LVDS output and one HDMI
+                * output.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(2),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_HDMI0] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 2,
+               },
+       },
+       .num_lvds = 1,
+       .num_rpf = 5,
+       .dpll_mask =  BIT(1),
+ };
+ static const struct rcar_du_device_info rcar_du_r8a77965_info = {
+       .gen = 3,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_VSP1_SOURCE
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(3) | BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A77965 has one RGB output, one LVDS output and one HDMI
+                * output.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(2),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_HDMI0] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 2,
+               },
+       },
+       .num_lvds = 1,
+       .num_rpf = 5,
+       .dpll_mask =  BIT(1),
+ };
+ static const struct rcar_du_device_info rcar_du_r8a77970_info = {
+       .gen = 3,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_VSP1_SOURCE
+                 | RCAR_DU_FEATURE_INTERLACED
+                 | RCAR_DU_FEATURE_TVM_SYNC,
+       .channels_mask = BIT(0),
+       .routes = {
+               /*
+                * R8A77970 and R8A77980 have one RGB output and one LVDS
+                * output.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 1,
+               },
+       },
+       .num_lvds = 1,
+       .num_rpf = 5,
+ };
+ static const struct rcar_du_device_info rcar_du_r8a7799x_info = {
+       .gen = 3,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_CRTC_CLOCK
+                 | RCAR_DU_FEATURE_VSP1_SOURCE,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /*
+                * R8A77990 and R8A77995 have one RGB output and two LVDS
+                * outputs.
+                */
+               [RCAR_DU_OUTPUT_DPAD0] = {
+                       .possible_crtcs = BIT(0) | BIT(1),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_LVDS0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 1,
+               },
+               [RCAR_DU_OUTPUT_LVDS1] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 2,
+               },
+       },
+       .num_lvds = 2,
+       .num_rpf = 5,
+       .lvds_clk_mask =  BIT(1) | BIT(0),
+ };
+ static const struct rcar_du_device_info rcar_du_r8a779a0_info = {
+       .gen = 4,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_VSP1_SOURCE
+                 | RCAR_DU_FEATURE_NO_BLENDING,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /* R8A779A0 has two MIPI DSI outputs. */
+               [RCAR_DU_OUTPUT_DSI0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_DSI1] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+       },
+       .num_rpf = 5,
+       .dsi_clk_mask =  BIT(1) | BIT(0),
+ };
+ static const struct rcar_du_device_info rcar_du_r8a779g0_info = {
+       .gen = 4,
+       .features = RCAR_DU_FEATURE_CRTC_IRQ
+                 | RCAR_DU_FEATURE_VSP1_SOURCE
+                 | RCAR_DU_FEATURE_NO_BLENDING,
+       .channels_mask = BIT(1) | BIT(0),
+       .routes = {
+               /* R8A779G0 has two MIPI DSI outputs. */
+               [RCAR_DU_OUTPUT_DSI0] = {
+                       .possible_crtcs = BIT(0),
+                       .port = 0,
+               },
+               [RCAR_DU_OUTPUT_DSI1] = {
+                       .possible_crtcs = BIT(1),
+                       .port = 1,
+               },
+       },
+       .num_rpf = 5,
+       .dsi_clk_mask =  BIT(1) | BIT(0),
+ };
+ static const struct of_device_id rcar_du_of_table[] = {
+       { .compatible = "renesas,du-r8a7742", .data = &rcar_du_r8a7790_info },
+       { .compatible = "renesas,du-r8a7743", .data = &rzg1_du_r8a7743_info },
+       { .compatible = "renesas,du-r8a7744", .data = &rzg1_du_r8a7743_info },
+       { .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info },
+       { .compatible = "renesas,du-r8a77470", .data = &rzg1_du_r8a77470_info },
+       { .compatible = "renesas,du-r8a774a1", .data = &rcar_du_r8a774a1_info },
+       { .compatible = "renesas,du-r8a774b1", .data = &rcar_du_r8a774b1_info },
+       { .compatible = "renesas,du-r8a774c0", .data = &rcar_du_r8a774c0_info },
+       { .compatible = "renesas,du-r8a774e1", .data = &rcar_du_r8a774e1_info },
+       { .compatible = "renesas,du-r8a7779", .data = &rcar_du_r8a7779_info },
+       { .compatible = "renesas,du-r8a7790", .data = &rcar_du_r8a7790_info },
+       { .compatible = "renesas,du-r8a7791", .data = &rcar_du_r8a7791_info },
+       { .compatible = "renesas,du-r8a7792", .data = &rcar_du_r8a7792_info },
+       { .compatible = "renesas,du-r8a7793", .data = &rcar_du_r8a7791_info },
+       { .compatible = "renesas,du-r8a7794", .data = &rcar_du_r8a7794_info },
+       { .compatible = "renesas,du-r8a7795", .data = &rcar_du_r8a7795_info },
+       { .compatible = "renesas,du-r8a7796", .data = &rcar_du_r8a7796_info },
+       { .compatible = "renesas,du-r8a77961", .data = &rcar_du_r8a7796_info },
+       { .compatible = "renesas,du-r8a77965", .data = &rcar_du_r8a77965_info },
+       { .compatible = "renesas,du-r8a77970", .data = &rcar_du_r8a77970_info },
+       { .compatible = "renesas,du-r8a77980", .data = &rcar_du_r8a77970_info },
+       { .compatible = "renesas,du-r8a77990", .data = &rcar_du_r8a7799x_info },
+       { .compatible = "renesas,du-r8a77995", .data = &rcar_du_r8a7799x_info },
+       { .compatible = "renesas,du-r8a779a0", .data = &rcar_du_r8a779a0_info },
+       { .compatible = "renesas,du-r8a779g0", .data = &rcar_du_r8a779g0_info },
+       { }
+ };
+ MODULE_DEVICE_TABLE(of, rcar_du_of_table);
+ const char *rcar_du_output_name(enum rcar_du_output output)
+ {
+       static const char * const names[] = {
+               [RCAR_DU_OUTPUT_DPAD0] = "DPAD0",
+               [RCAR_DU_OUTPUT_DPAD1] = "DPAD1",
+               [RCAR_DU_OUTPUT_DSI0] = "DSI0",
+               [RCAR_DU_OUTPUT_DSI1] = "DSI1",
+               [RCAR_DU_OUTPUT_HDMI0] = "HDMI0",
+               [RCAR_DU_OUTPUT_HDMI1] = "HDMI1",
+               [RCAR_DU_OUTPUT_LVDS0] = "LVDS0",
+               [RCAR_DU_OUTPUT_LVDS1] = "LVDS1",
+               [RCAR_DU_OUTPUT_TCON] = "TCON",
+       };
+       if (output >= ARRAY_SIZE(names) || !names[output])
+               return "UNKNOWN";
+       return names[output];
+ }
+ /* -----------------------------------------------------------------------------
+  * DRM operations
+  */
+ DEFINE_DRM_GEM_DMA_FOPS(rcar_du_fops);
+ static const struct drm_driver rcar_du_driver = {
+       .driver_features        = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+       .dumb_create            = rcar_du_dumb_create,
+       .prime_handle_to_fd     = drm_gem_prime_handle_to_fd,
+       .prime_fd_to_handle     = drm_gem_prime_fd_to_handle,
+       .gem_prime_import_sg_table = rcar_du_gem_prime_import_sg_table,
 -static int rcar_du_remove(struct platform_device *pdev)
+       .fops                   = &rcar_du_fops,
+       .name                   = "rcar-du",
+       .desc                   = "Renesas R-Car Display Unit",
+       .date                   = "20130110",
+       .major                  = 1,
+       .minor                  = 0,
+ };
+ /* -----------------------------------------------------------------------------
+  * Power management
+  */
+ static int rcar_du_pm_suspend(struct device *dev)
+ {
+       struct rcar_du_device *rcdu = dev_get_drvdata(dev);
+       return drm_mode_config_helper_suspend(&rcdu->ddev);
+ }
+ static int rcar_du_pm_resume(struct device *dev)
+ {
+       struct rcar_du_device *rcdu = dev_get_drvdata(dev);
+       return drm_mode_config_helper_resume(&rcdu->ddev);
+ }
+ static DEFINE_SIMPLE_DEV_PM_OPS(rcar_du_pm_ops,
+                               rcar_du_pm_suspend, rcar_du_pm_resume);
+ /* -----------------------------------------------------------------------------
+  * Platform driver
+  */
 -
 -      return 0;
++static void rcar_du_remove(struct platform_device *pdev)
+ {
+       struct rcar_du_device *rcdu = platform_get_drvdata(pdev);
+       struct drm_device *ddev = &rcdu->ddev;
+       drm_dev_unregister(ddev);
+       drm_atomic_helper_shutdown(ddev);
+       drm_kms_helper_poll_fini(ddev);
 -      .remove         = rcar_du_remove,
+ }
+ static void rcar_du_shutdown(struct platform_device *pdev)
+ {
+       struct rcar_du_device *rcdu = platform_get_drvdata(pdev);
+       drm_atomic_helper_shutdown(&rcdu->ddev);
+ }
+ static int rcar_du_probe(struct platform_device *pdev)
+ {
+       struct rcar_du_device *rcdu;
+       unsigned int mask;
+       int ret;
+       if (drm_firmware_drivers_only())
+               return -ENODEV;
+       /* Allocate and initialize the R-Car device structure. */
+       rcdu = devm_drm_dev_alloc(&pdev->dev, &rcar_du_driver,
+                                 struct rcar_du_device, ddev);
+       if (IS_ERR(rcdu))
+               return PTR_ERR(rcdu);
+       rcdu->dev = &pdev->dev;
+       rcdu->info = of_device_get_match_data(rcdu->dev);
+       platform_set_drvdata(pdev, rcdu);
+       /* I/O resources */
+       rcdu->mmio = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(rcdu->mmio))
+               return PTR_ERR(rcdu->mmio);
+       /*
+        * Set the DMA coherent mask to reflect the DU 32-bit DMA address space
+        * limitations. When sourcing frames from a VSP the DU doesn't perform
+        * any memory access so set the mask to 40 bits to accept all buffers.
+        */
+       mask = rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE) ? 40 : 32;
+       ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(mask));
+       if (ret)
+               return ret;
+       /* DRM/KMS objects */
+       ret = rcar_du_modeset_init(rcdu);
+       if (ret < 0) {
+               if (ret != -EPROBE_DEFER)
+                       dev_err(&pdev->dev,
+                               "failed to initialize DRM/KMS (%d)\n", ret);
+               goto error;
+       }
+       /*
+        * Register the DRM device with the core and the connectors with
+        * sysfs.
+        */
+       ret = drm_dev_register(&rcdu->ddev, 0);
+       if (ret)
+               goto error;
+       DRM_INFO("Device %s probed\n", dev_name(&pdev->dev));
+       drm_fbdev_generic_setup(&rcdu->ddev, 32);
+       return 0;
+ error:
+       drm_kms_helper_poll_fini(&rcdu->ddev);
+       return ret;
+ }
+ static struct platform_driver rcar_du_platform_driver = {
+       .probe          = rcar_du_probe,
++      .remove_new     = rcar_du_remove,
+       .shutdown       = rcar_du_shutdown,
+       .driver         = {
+               .name   = "rcar-du",
+               .pm     = pm_sleep_ptr(&rcar_du_pm_ops),
+               .of_match_table = rcar_du_of_table,
+       },
+ };
+ module_platform_driver(rcar_du_platform_driver);
+ MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+ MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver");
+ MODULE_LICENSE("GPL");
index 0000000000000000000000000000000000000000,18ed14911b981b7869dc5e54e1f5a8f9540ffd33..119d69d20b230184c01d5b3fe38a6ac5a2b65375
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,124 +1,122 @@@
 -static int rcar_dw_hdmi_remove(struct platform_device *pdev)
+ // SPDX-License-Identifier: GPL-2.0+
+ /*
+  * R-Car Gen3 HDMI PHY
+  *
+  * Copyright (C) 2016 Renesas Electronics Corporation
+  *
+  * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+  */
+ #include <linux/mod_devicetable.h>
+ #include <linux/module.h>
+ #include <linux/platform_device.h>
+ #include <drm/bridge/dw_hdmi.h>
+ #include <drm/drm_modes.h>
+ #define RCAR_HDMI_PHY_OPMODE_PLLCFG   0x06    /* Mode of operation and PLL dividers */
+ #define RCAR_HDMI_PHY_PLLCURRGMPCTRL  0x10    /* PLL current and Gmp (conductance) */
+ #define RCAR_HDMI_PHY_PLLDIVCTRL      0x11    /* PLL dividers */
+ struct rcar_hdmi_phy_params {
+       unsigned long mpixelclock;
+       u16 opmode_div; /* Mode of operation and PLL dividers */
+       u16 curr_gmp;   /* PLL current and Gmp (conductance) */
+       u16 div;        /* PLL dividers */
+ };
+ static const struct rcar_hdmi_phy_params rcar_hdmi_phy_params[] = {
+       { 35500000,  0x0003, 0x0344, 0x0328 },
+       { 44900000,  0x0003, 0x0285, 0x0128 },
+       { 71000000,  0x0002, 0x1184, 0x0314 },
+       { 90000000,  0x0002, 0x1144, 0x0114 },
+       { 140250000, 0x0001, 0x20c4, 0x030a },
+       { 182750000, 0x0001, 0x2084, 0x010a },
+       { 281250000, 0x0000, 0x0084, 0x0305 },
+       { 297000000, 0x0000, 0x0084, 0x0105 },
+       { ~0UL,      0x0000, 0x0000, 0x0000 },
+ };
+ static enum drm_mode_status
+ rcar_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
+                    const struct drm_display_info *info,
+                    const struct drm_display_mode *mode)
+ {
+       /*
+        * The maximum supported clock frequency is 297 MHz, as shown in the PHY
+        * parameters table.
+        */
+       if (mode->clock > 297000)
+               return MODE_CLOCK_HIGH;
+       return MODE_OK;
+ }
+ static int rcar_hdmi_phy_configure(struct dw_hdmi *hdmi, void *data,
+                                  unsigned long mpixelclock)
+ {
+       const struct rcar_hdmi_phy_params *params = rcar_hdmi_phy_params;
+       for (; params->mpixelclock != ~0UL; ++params) {
+               if (mpixelclock <= params->mpixelclock)
+                       break;
+       }
+       if (params->mpixelclock == ~0UL)
+               return -EINVAL;
+       dw_hdmi_phy_i2c_write(hdmi, params->opmode_div,
+                             RCAR_HDMI_PHY_OPMODE_PLLCFG);
+       dw_hdmi_phy_i2c_write(hdmi, params->curr_gmp,
+                             RCAR_HDMI_PHY_PLLCURRGMPCTRL);
+       dw_hdmi_phy_i2c_write(hdmi, params->div, RCAR_HDMI_PHY_PLLDIVCTRL);
+       return 0;
+ }
+ static const struct dw_hdmi_plat_data rcar_dw_hdmi_plat_data = {
+       .output_port = 1,
+       .mode_valid = rcar_hdmi_mode_valid,
+       .configure_phy  = rcar_hdmi_phy_configure,
+ };
+ static int rcar_dw_hdmi_probe(struct platform_device *pdev)
+ {
+       struct dw_hdmi *hdmi;
+       hdmi = dw_hdmi_probe(pdev, &rcar_dw_hdmi_plat_data);
+       if (IS_ERR(hdmi))
+               return PTR_ERR(hdmi);
+       platform_set_drvdata(pdev, hdmi);
+       return 0;
+ }
 -
 -      return 0;
++static void rcar_dw_hdmi_remove(struct platform_device *pdev)
+ {
+       struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
+       dw_hdmi_remove(hdmi);
 -      .remove         = rcar_dw_hdmi_remove,
+ }
+ static const struct of_device_id rcar_dw_hdmi_of_table[] = {
+       { .compatible = "renesas,rcar-gen3-hdmi" },
+       { /* Sentinel */ },
+ };
+ MODULE_DEVICE_TABLE(of, rcar_dw_hdmi_of_table);
+ static struct platform_driver rcar_dw_hdmi_platform_driver = {
+       .probe          = rcar_dw_hdmi_probe,
++      .remove_new     = rcar_dw_hdmi_remove,
+       .driver         = {
+               .name   = "rcar-dw-hdmi",
+               .of_match_table = rcar_dw_hdmi_of_table,
+       },
+ };
+ module_platform_driver(rcar_dw_hdmi_platform_driver);
+ MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+ MODULE_DESCRIPTION("Renesas R-Car Gen3 HDMI Encoder Driver");
+ MODULE_LICENSE("GPL");
index 0000000000000000000000000000000000000000,ca215b588fd7e0917921af3b1187fc772b245baa..92ba43a6fe38753dcbcaff277e8b86ea83d000e2
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1035 +1,1033 @@@
 -static int rcar_lvds_remove(struct platform_device *pdev)
+ // SPDX-License-Identifier: GPL-2.0
+ /*
+  * R-Car LVDS Encoder
+  *
+  * Copyright (C) 2013-2018 Renesas Electronics Corporation
+  *
+  * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+  */
+ #include <linux/clk.h>
+ #include <linux/delay.h>
+ #include <linux/io.h>
+ #include <linux/media-bus-format.h>
+ #include <linux/module.h>
+ #include <linux/of.h>
+ #include <linux/of_device.h>
+ #include <linux/of_graph.h>
+ #include <linux/platform_device.h>
+ #include <linux/pm_runtime.h>
+ #include <linux/reset.h>
+ #include <linux/slab.h>
+ #include <linux/sys_soc.h>
+ #include <drm/drm_atomic.h>
+ #include <drm/drm_atomic_helper.h>
+ #include <drm/drm_bridge.h>
+ #include <drm/drm_of.h>
+ #include <drm/drm_panel.h>
+ #include <drm/drm_print.h>
+ #include <drm/drm_probe_helper.h>
+ #include "rcar_lvds.h"
+ #include "rcar_lvds_regs.h"
+ struct rcar_lvds;
+ /* Keep in sync with the LVDCR0.LVMD hardware register values. */
+ enum rcar_lvds_mode {
+       RCAR_LVDS_MODE_JEIDA = 0,
+       RCAR_LVDS_MODE_MIRROR = 1,
+       RCAR_LVDS_MODE_VESA = 4,
+ };
+ enum rcar_lvds_link_type {
+       RCAR_LVDS_SINGLE_LINK = 0,
+       RCAR_LVDS_DUAL_LINK_EVEN_ODD_PIXELS = 1,
+       RCAR_LVDS_DUAL_LINK_ODD_EVEN_PIXELS = 2,
+ };
+ #define RCAR_LVDS_QUIRK_LANES         BIT(0)  /* LVDS lanes 1 and 3 inverted */
+ #define RCAR_LVDS_QUIRK_GEN3_LVEN     BIT(1)  /* LVEN bit needs to be set on R8A77970/R8A7799x */
+ #define RCAR_LVDS_QUIRK_PWD           BIT(2)  /* PWD bit available (all of Gen3 but E3) */
+ #define RCAR_LVDS_QUIRK_EXT_PLL               BIT(3)  /* Has extended PLL */
+ #define RCAR_LVDS_QUIRK_DUAL_LINK     BIT(4)  /* Supports dual-link operation */
+ struct rcar_lvds_device_info {
+       unsigned int gen;
+       unsigned int quirks;
+       void (*pll_setup)(struct rcar_lvds *lvds, unsigned int freq);
+ };
+ struct rcar_lvds {
+       struct device *dev;
+       const struct rcar_lvds_device_info *info;
+       struct reset_control *rstc;
+       struct drm_bridge bridge;
+       struct drm_bridge *next_bridge;
+       struct drm_panel *panel;
+       void __iomem *mmio;
+       struct {
+               struct clk *mod;                /* CPG module clock */
+               struct clk *extal;              /* External clock */
+               struct clk *dotclkin[2];        /* External DU clocks */
+       } clocks;
+       struct drm_bridge *companion;
+       enum rcar_lvds_link_type link_type;
+ };
+ #define bridge_to_rcar_lvds(b) \
+       container_of(b, struct rcar_lvds, bridge)
+ static u32 rcar_lvds_read(struct rcar_lvds *lvds, u32 reg)
+ {
+       return ioread32(lvds->mmio + reg);
+ }
+ static void rcar_lvds_write(struct rcar_lvds *lvds, u32 reg, u32 data)
+ {
+       iowrite32(data, lvds->mmio + reg);
+ }
+ /* -----------------------------------------------------------------------------
+  * PLL Setup
+  */
+ static void rcar_lvds_pll_setup_gen2(struct rcar_lvds *lvds, unsigned int freq)
+ {
+       u32 val;
+       if (freq < 39000000)
+               val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
+       else if (freq < 61000000)
+               val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
+       else if (freq < 121000000)
+               val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
+       else
+               val = LVDPLLCR_PLLDLYCNT_150M;
+       rcar_lvds_write(lvds, LVDPLLCR, val);
+ }
+ static void rcar_lvds_pll_setup_gen3(struct rcar_lvds *lvds, unsigned int freq)
+ {
+       u32 val;
+       if (freq < 42000000)
+               val = LVDPLLCR_PLLDIVCNT_42M;
+       else if (freq < 85000000)
+               val = LVDPLLCR_PLLDIVCNT_85M;
+       else if (freq < 128000000)
+               val = LVDPLLCR_PLLDIVCNT_128M;
+       else
+               val = LVDPLLCR_PLLDIVCNT_148M;
+       rcar_lvds_write(lvds, LVDPLLCR, val);
+ }
+ struct pll_info {
+       unsigned long diff;
+       unsigned int pll_m;
+       unsigned int pll_n;
+       unsigned int pll_e;
+       unsigned int div;
+       u32 clksel;
+ };
+ static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk,
+                                    unsigned long target, struct pll_info *pll,
+                                    u32 clksel, bool dot_clock_only)
+ {
+       unsigned int div7 = dot_clock_only ? 1 : 7;
+       unsigned long output;
+       unsigned long fin;
+       unsigned int m_min;
+       unsigned int m_max;
+       unsigned int m;
+       int error;
+       if (!clk)
+               return;
+       /*
+        * The LVDS PLL is made of a pre-divider and a multiplier (strangely
+        * enough called M and N respectively), followed by a post-divider E.
+        *
+        *         ,-----.         ,-----.     ,-----.         ,-----.
+        * Fin --> | 1/M | -Fpdf-> | PFD | --> | VCO | -Fvco-> | 1/E | --> Fout
+        *         `-----'     ,-> |     |     `-----'   |     `-----'
+        *                     |   `-----'               |
+        *                     |         ,-----.         |
+        *                     `-------- | 1/N | <-------'
+        *                               `-----'
+        *
+        * The clock output by the PLL is then further divided by a programmable
+        * divider DIV to achieve the desired target frequency. Finally, an
+        * optional fixed /7 divider is used to convert the bit clock to a pixel
+        * clock (as LVDS transmits 7 bits per lane per clock sample).
+        *
+        *          ,-------.     ,-----.     |\
+        * Fout --> | 1/DIV | --> | 1/7 | --> | |
+        *          `-------'  |  `-----'     | | --> dot clock
+        *                     `------------> | |
+        *                                    |/
+        *
+        * The /7 divider is optional, it is enabled when the LVDS PLL is used
+        * to drive the LVDS encoder, and disabled when  used to generate a dot
+        * clock for the DU RGB output, without using the LVDS encoder.
+        *
+        * The PLL allowed input frequency range is 12 MHz to 192 MHz.
+        */
+       fin = clk_get_rate(clk);
+       if (fin < 12000000 || fin > 192000000)
+               return;
+       /*
+        * The comparison frequency range is 12 MHz to 24 MHz, which limits the
+        * allowed values for the pre-divider M (normal range 1-8).
+        *
+        * Fpfd = Fin / M
+        */
+       m_min = max_t(unsigned int, 1, DIV_ROUND_UP(fin, 24000000));
+       m_max = min_t(unsigned int, 8, fin / 12000000);
+       for (m = m_min; m <= m_max; ++m) {
+               unsigned long fpfd;
+               unsigned int n_min;
+               unsigned int n_max;
+               unsigned int n;
+               /*
+                * The VCO operating range is 900 Mhz to 1800 MHz, which limits
+                * the allowed values for the multiplier N (normal range
+                * 60-120).
+                *
+                * Fvco = Fin * N / M
+                */
+               fpfd = fin / m;
+               n_min = max_t(unsigned int, 60, DIV_ROUND_UP(900000000, fpfd));
+               n_max = min_t(unsigned int, 120, 1800000000 / fpfd);
+               for (n = n_min; n < n_max; ++n) {
+                       unsigned long fvco;
+                       unsigned int e_min;
+                       unsigned int e;
+                       /*
+                        * The output frequency is limited to 1039.5 MHz,
+                        * limiting again the allowed values for the
+                        * post-divider E (normal value 1, 2 or 4).
+                        *
+                        * Fout = Fvco / E
+                        */
+                       fvco = fpfd * n;
+                       e_min = fvco > 1039500000 ? 1 : 0;
+                       for (e = e_min; e < 3; ++e) {
+                               unsigned long fout;
+                               unsigned long diff;
+                               unsigned int div;
+                               /*
+                                * Finally we have a programable divider after
+                                * the PLL, followed by a an optional fixed /7
+                                * divider.
+                                */
+                               fout = fvco / (1 << e) / div7;
+                               div = max(1UL, DIV_ROUND_CLOSEST(fout, target));
+                               diff = abs(fout / div - target);
+                               if (diff < pll->diff) {
+                                       pll->diff = diff;
+                                       pll->pll_m = m;
+                                       pll->pll_n = n;
+                                       pll->pll_e = e;
+                                       pll->div = div;
+                                       pll->clksel = clksel;
+                                       if (diff == 0)
+                                               goto done;
+                               }
+                       }
+               }
+       }
+ done:
+       output = fin * pll->pll_n / pll->pll_m / (1 << pll->pll_e)
+              / div7 / pll->div;
+       error = (long)(output - target) * 10000 / (long)target;
+       dev_dbg(lvds->dev,
+               "%pC %lu Hz -> Fout %lu Hz (target %lu Hz, error %d.%02u%%), PLL M/N/E/DIV %u/%u/%u/%u\n",
+               clk, fin, output, target, error / 100,
+               error < 0 ? -error % 100 : error % 100,
+               pll->pll_m, pll->pll_n, pll->pll_e, pll->div);
+ }
+ static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds,
+                                     unsigned int freq, bool dot_clock_only)
+ {
+       struct pll_info pll = { .diff = (unsigned long)-1 };
+       u32 lvdpllcr;
+       rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[0], freq, &pll,
+                                LVDPLLCR_CKSEL_DU_DOTCLKIN(0), dot_clock_only);
+       rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[1], freq, &pll,
+                                LVDPLLCR_CKSEL_DU_DOTCLKIN(1), dot_clock_only);
+       rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.extal, freq, &pll,
+                                LVDPLLCR_CKSEL_EXTAL, dot_clock_only);
+       lvdpllcr = LVDPLLCR_PLLON | pll.clksel | LVDPLLCR_CLKOUT
+                | LVDPLLCR_PLLN(pll.pll_n - 1) | LVDPLLCR_PLLM(pll.pll_m - 1);
+       if (pll.pll_e > 0)
+               lvdpllcr |= LVDPLLCR_STP_CLKOUTE | LVDPLLCR_OUTCLKSEL
+                        |  LVDPLLCR_PLLE(pll.pll_e - 1);
+       if (dot_clock_only)
+               lvdpllcr |= LVDPLLCR_OCKSEL;
+       rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
+       if (pll.div > 1)
+               /*
+                * The DIVRESET bit is a misnomer, setting it to 1 deasserts the
+                * divisor reset.
+                */
+               rcar_lvds_write(lvds, LVDDIV, LVDDIV_DIVSEL |
+                               LVDDIV_DIVRESET | LVDDIV_DIV(pll.div - 1));
+       else
+               rcar_lvds_write(lvds, LVDDIV, 0);
+ }
+ /* -----------------------------------------------------------------------------
+  * Enable/disable
+  */
+ static enum rcar_lvds_mode rcar_lvds_get_lvds_mode(struct rcar_lvds *lvds,
+                                       const struct drm_connector *connector)
+ {
+       const struct drm_display_info *info;
+       enum rcar_lvds_mode mode;
+       /*
+        * There is no API yet to retrieve LVDS mode from a bridge, only panels
+        * are supported.
+        */
+       if (!lvds->panel)
+               return RCAR_LVDS_MODE_JEIDA;
+       info = &connector->display_info;
+       if (!info->num_bus_formats || !info->bus_formats) {
+               dev_warn(lvds->dev,
+                        "no LVDS bus format reported, using JEIDA\n");
+               return RCAR_LVDS_MODE_JEIDA;
+       }
+       switch (info->bus_formats[0]) {
+       case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+       case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+               mode = RCAR_LVDS_MODE_JEIDA;
+               break;
+       case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+               mode = RCAR_LVDS_MODE_VESA;
+               break;
+       default:
+               dev_warn(lvds->dev,
+                        "unsupported LVDS bus format 0x%04x, using JEIDA\n",
+                        info->bus_formats[0]);
+               return RCAR_LVDS_MODE_JEIDA;
+       }
+       if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
+               mode |= RCAR_LVDS_MODE_MIRROR;
+       return mode;
+ }
+ static void rcar_lvds_enable(struct drm_bridge *bridge,
+                            struct drm_atomic_state *state,
+                            struct drm_crtc *crtc,
+                            struct drm_connector *connector)
+ {
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       u32 lvdhcr;
+       u32 lvdcr0;
+       int ret;
+       ret = pm_runtime_resume_and_get(lvds->dev);
+       if (ret)
+               return;
+       /* Enable the companion LVDS encoder in dual-link mode. */
+       if (lvds->link_type != RCAR_LVDS_SINGLE_LINK && lvds->companion)
+               rcar_lvds_enable(lvds->companion, state, crtc, connector);
+       /*
+        * Hardcode the channels and control signals routing for now.
+        *
+        * HSYNC -> CTRL0
+        * VSYNC -> CTRL1
+        * DISP  -> CTRL2
+        * 0     -> CTRL3
+        */
+       rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
+                       LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
+                       LVDCTRCR_CTR0SEL_HSYNC);
+       if (lvds->info->quirks & RCAR_LVDS_QUIRK_LANES)
+               lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
+                      | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
+       else
+               lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
+                      | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
+       rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
+       if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) {
+               u32 lvdstripe = 0;
+               if (lvds->link_type != RCAR_LVDS_SINGLE_LINK) {
+                       /*
+                        * By default we generate even pixels from the primary
+                        * encoder and odd pixels from the companion encoder.
+                        * Swap pixels around if the sink requires odd pixels
+                        * from the primary encoder and even pixels from the
+                        * companion encoder.
+                        */
+                       bool swap_pixels = lvds->link_type ==
+                               RCAR_LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
+                       /*
+                        * Configure vertical stripe since we are dealing with
+                        * an LVDS dual-link connection.
+                        *
+                        * ST_SWAP is reserved for the companion encoder, only
+                        * set it in the primary encoder.
+                        */
+                       lvdstripe = LVDSTRIPE_ST_ON
+                                 | (lvds->companion && swap_pixels ?
+                                    LVDSTRIPE_ST_SWAP : 0);
+               }
+               rcar_lvds_write(lvds, LVDSTRIPE, lvdstripe);
+       }
+       /*
+        * PLL clock configuration on all instances but the companion in
+        * dual-link mode.
+        *
+        * The extended PLL has been turned on by an explicit call to
+        * rcar_lvds_pclk_enable() from the DU driver.
+        */
+       if ((lvds->link_type == RCAR_LVDS_SINGLE_LINK || lvds->companion) &&
+           !(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
+               const struct drm_crtc_state *crtc_state =
+                       drm_atomic_get_new_crtc_state(state, crtc);
+               const struct drm_display_mode *mode =
+                       &crtc_state->adjusted_mode;
+               lvds->info->pll_setup(lvds, mode->clock * 1000);
+       }
+       /* Set the LVDS mode and select the input. */
+       lvdcr0 = rcar_lvds_get_lvds_mode(lvds, connector) << LVDCR0_LVMD_SHIFT;
+       if (lvds->bridge.encoder) {
+               if (drm_crtc_index(crtc) == 2)
+                       lvdcr0 |= LVDCR0_DUSEL;
+       }
+       rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       /* Turn all the channels on. */
+       rcar_lvds_write(lvds, LVDCR1,
+                       LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
+                       LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
+       if (lvds->info->gen < 3) {
+               /* Enable LVDS operation and turn the bias circuitry on. */
+               lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
+               rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       }
+       if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
+               /*
+                * Turn the PLL on (simple PLL only, extended PLL is fully
+                * controlled through LVDPLLCR).
+                */
+               lvdcr0 |= LVDCR0_PLLON;
+               rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       }
+       if (lvds->info->quirks & RCAR_LVDS_QUIRK_PWD) {
+               /* Set LVDS normal mode. */
+               lvdcr0 |= LVDCR0_PWD;
+               rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       }
+       if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN3_LVEN) {
+               /*
+                * Turn on the LVDS PHY. On D3, the LVEN and LVRES bit must be
+                * set at the same time, so don't write the register yet.
+                */
+               lvdcr0 |= LVDCR0_LVEN;
+               if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_PWD))
+                       rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       }
+       if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
+               /* Wait for the PLL startup delay (simple PLL only). */
+               usleep_range(100, 150);
+       }
+       /* Turn the output on. */
+       lvdcr0 |= LVDCR0_LVRES;
+       rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+ }
+ static void rcar_lvds_disable(struct drm_bridge *bridge)
+ {
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       u32 lvdcr0;
+       /*
+        * Clear the LVDCR0 bits in the order specified by the hardware
+        * documentation, ending with a write of 0 to the full register to
+        * clear all remaining bits.
+        */
+       lvdcr0 = rcar_lvds_read(lvds, LVDCR0);
+       lvdcr0 &= ~LVDCR0_LVRES;
+       rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN3_LVEN) {
+               lvdcr0 &= ~LVDCR0_LVEN;
+               rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       }
+       if (lvds->info->quirks & RCAR_LVDS_QUIRK_PWD) {
+               lvdcr0 &= ~LVDCR0_PWD;
+               rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       }
+       if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
+               lvdcr0 &= ~LVDCR0_PLLON;
+               rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       }
+       rcar_lvds_write(lvds, LVDCR0, 0);
+       rcar_lvds_write(lvds, LVDCR1, 0);
+       /* The extended PLL is turned off in rcar_lvds_pclk_disable(). */
+       if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL))
+               rcar_lvds_write(lvds, LVDPLLCR, 0);
+       /* Disable the companion LVDS encoder in dual-link mode. */
+       if (lvds->link_type != RCAR_LVDS_SINGLE_LINK && lvds->companion)
+               rcar_lvds_disable(lvds->companion);
+       pm_runtime_put_sync(lvds->dev);
+ }
+ /* -----------------------------------------------------------------------------
+  * Clock - D3/E3 only
+  */
+ int rcar_lvds_pclk_enable(struct drm_bridge *bridge, unsigned long freq,
+                         bool dot_clk_only)
+ {
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       int ret;
+       if (WARN_ON(!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)))
+               return -ENODEV;
+       dev_dbg(lvds->dev, "enabling LVDS PLL, freq=%luHz\n", freq);
+       ret = pm_runtime_resume_and_get(lvds->dev);
+       if (ret)
+               return ret;
+       rcar_lvds_pll_setup_d3_e3(lvds, freq, dot_clk_only);
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(rcar_lvds_pclk_enable);
+ void rcar_lvds_pclk_disable(struct drm_bridge *bridge, bool dot_clk_only)
+ {
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       if (WARN_ON(!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)))
+               return;
+       dev_dbg(lvds->dev, "disabling LVDS PLL\n");
+       if (!dot_clk_only)
+               rcar_lvds_disable(bridge);
+       rcar_lvds_write(lvds, LVDPLLCR, 0);
+       pm_runtime_put_sync(lvds->dev);
+ }
+ EXPORT_SYMBOL_GPL(rcar_lvds_pclk_disable);
+ /* -----------------------------------------------------------------------------
+  * Bridge
+  */
+ static void rcar_lvds_atomic_enable(struct drm_bridge *bridge,
+                                   struct drm_bridge_state *old_bridge_state)
+ {
+       struct drm_atomic_state *state = old_bridge_state->base.state;
+       struct drm_connector *connector;
+       struct drm_crtc *crtc;
+       connector = drm_atomic_get_new_connector_for_encoder(state,
+                                                            bridge->encoder);
+       crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
+       rcar_lvds_enable(bridge, state, crtc, connector);
+ }
+ static void rcar_lvds_atomic_disable(struct drm_bridge *bridge,
+                                    struct drm_bridge_state *old_bridge_state)
+ {
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       /*
+        * For D3 and E3, disabling the LVDS encoder before the DU would stall
+        * the DU, causing a vblank wait timeout when stopping the DU. This has
+        * been traced to clearing the LVEN bit, but the exact reason is
+        * unknown. Keep the encoder enabled, it will be disabled by an explicit
+        * call to rcar_lvds_pclk_disable() from the DU driver.
+        *
+        * We could clear the LVRES bit already to disable the LVDS output, but
+        * that's likely pointless.
+        */
+       if (lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)
+               return;
+       rcar_lvds_disable(bridge);
+ }
+ static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
+                                const struct drm_display_mode *mode,
+                                struct drm_display_mode *adjusted_mode)
+ {
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       int min_freq;
+       /*
+        * The internal LVDS encoder has a restricted clock frequency operating
+        * range, from 5MHz to 148.5MHz on D3 and E3, and from 31MHz to
+        * 148.5MHz on all other platforms. Clamp the clock accordingly.
+        */
+       min_freq = lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL ? 5000 : 31000;
+       adjusted_mode->clock = clamp(adjusted_mode->clock, min_freq, 148500);
+       return true;
+ }
+ static int rcar_lvds_attach(struct drm_bridge *bridge,
+                           enum drm_bridge_attach_flags flags)
+ {
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       if (!lvds->next_bridge)
+               return 0;
+       return drm_bridge_attach(bridge->encoder, lvds->next_bridge, bridge,
+                                flags);
+ }
+ static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
+       .attach = rcar_lvds_attach,
+       .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+       .atomic_reset = drm_atomic_helper_bridge_reset,
+       .atomic_enable = rcar_lvds_atomic_enable,
+       .atomic_disable = rcar_lvds_atomic_disable,
+       .mode_fixup = rcar_lvds_mode_fixup,
+ };
+ bool rcar_lvds_dual_link(struct drm_bridge *bridge)
+ {
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       return lvds->link_type != RCAR_LVDS_SINGLE_LINK;
+ }
+ EXPORT_SYMBOL_GPL(rcar_lvds_dual_link);
+ bool rcar_lvds_is_connected(struct drm_bridge *bridge)
+ {
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       return lvds->next_bridge != NULL;
+ }
+ EXPORT_SYMBOL_GPL(rcar_lvds_is_connected);
+ /* -----------------------------------------------------------------------------
+  * Probe & Remove
+  */
+ static int rcar_lvds_parse_dt_companion(struct rcar_lvds *lvds)
+ {
+       const struct of_device_id *match;
+       struct device_node *companion;
+       struct device_node *port0, *port1;
+       struct rcar_lvds *companion_lvds;
+       struct device *dev = lvds->dev;
+       int dual_link;
+       int ret = 0;
+       /* Locate the companion LVDS encoder for dual-link operation, if any. */
+       companion = of_parse_phandle(dev->of_node, "renesas,companion", 0);
+       if (!companion)
+               return 0;
+       /*
+        * Sanity check: the companion encoder must have the same compatible
+        * string.
+        */
+       match = of_match_device(dev->driver->of_match_table, dev);
+       if (!of_device_is_compatible(companion, match->compatible)) {
+               dev_err(dev, "Companion LVDS encoder is invalid\n");
+               ret = -ENXIO;
+               goto done;
+       }
+       /*
+        * We need to work out if the sink is expecting us to function in
+        * dual-link mode. We do this by looking at the DT port nodes we are
+        * connected to, if they are marked as expecting even pixels and
+        * odd pixels than we need to enable vertical stripe output.
+        */
+       port0 = of_graph_get_port_by_id(dev->of_node, 1);
+       port1 = of_graph_get_port_by_id(companion, 1);
+       dual_link = drm_of_lvds_get_dual_link_pixel_order(port0, port1);
+       of_node_put(port0);
+       of_node_put(port1);
+       switch (dual_link) {
+       case DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS:
+               lvds->link_type = RCAR_LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
+               break;
+       case DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS:
+               lvds->link_type = RCAR_LVDS_DUAL_LINK_EVEN_ODD_PIXELS;
+               break;
+       default:
+               /*
+                * Early dual-link bridge specific implementations populate the
+                * timings field of drm_bridge. If the flag is set, we assume
+                * that we are expected to generate even pixels from the primary
+                * encoder, and odd pixels from the companion encoder.
+                */
+               if (lvds->next_bridge->timings &&
+                   lvds->next_bridge->timings->dual_link)
+                       lvds->link_type = RCAR_LVDS_DUAL_LINK_EVEN_ODD_PIXELS;
+               else
+                       lvds->link_type = RCAR_LVDS_SINGLE_LINK;
+       }
+       if (lvds->link_type == RCAR_LVDS_SINGLE_LINK) {
+               dev_dbg(dev, "Single-link configuration detected\n");
+               goto done;
+       }
+       lvds->companion = of_drm_find_bridge(companion);
+       if (!lvds->companion) {
+               ret = -EPROBE_DEFER;
+               goto done;
+       }
+       dev_dbg(dev,
+               "Dual-link configuration detected (companion encoder %pOF)\n",
+               companion);
+       if (lvds->link_type == RCAR_LVDS_DUAL_LINK_ODD_EVEN_PIXELS)
+               dev_dbg(dev, "Data swapping required\n");
+       /*
+        * FIXME: We should not be messing with the companion encoder private
+        * data from the primary encoder, we should rather let the companion
+        * encoder work things out on its own. However, the companion encoder
+        * doesn't hold a reference to the primary encoder, and
+        * drm_of_lvds_get_dual_link_pixel_order needs to be given references
+        * to the output ports of both encoders, therefore leave it like this
+        * for the time being.
+        */
+       companion_lvds = bridge_to_rcar_lvds(lvds->companion);
+       companion_lvds->link_type = lvds->link_type;
+ done:
+       of_node_put(companion);
+       return ret;
+ }
+ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
+ {
+       int ret;
+       ret = drm_of_find_panel_or_bridge(lvds->dev->of_node, 1, 0,
+                                         &lvds->panel, &lvds->next_bridge);
+       if (ret)
+               goto done;
+       if (lvds->panel) {
+               lvds->next_bridge = devm_drm_panel_bridge_add(lvds->dev,
+                                                             lvds->panel);
+               if (IS_ERR_OR_NULL(lvds->next_bridge)) {
+                       ret = -EINVAL;
+                       goto done;
+               }
+       }
+       if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK)
+               ret = rcar_lvds_parse_dt_companion(lvds);
+ done:
+       /*
+        * On D3/E3 the LVDS encoder provides a clock to the DU, which can be
+        * used for the DPAD output even when the LVDS output is not connected.
+        * Don't fail probe in that case as the DU will need the bridge to
+        * control the clock.
+        */
+       if (lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)
+               return ret == -ENODEV ? 0 : ret;
+       return ret;
+ }
+ static struct clk *rcar_lvds_get_clock(struct rcar_lvds *lvds, const char *name,
+                                      bool optional)
+ {
+       struct clk *clk;
+       clk = devm_clk_get(lvds->dev, name);
+       if (!IS_ERR(clk))
+               return clk;
+       if (PTR_ERR(clk) == -ENOENT && optional)
+               return NULL;
+       dev_err_probe(lvds->dev, PTR_ERR(clk), "failed to get %s clock\n",
+                     name ? name : "module");
+       return clk;
+ }
+ static int rcar_lvds_get_clocks(struct rcar_lvds *lvds)
+ {
+       lvds->clocks.mod = rcar_lvds_get_clock(lvds, NULL, false);
+       if (IS_ERR(lvds->clocks.mod))
+               return PTR_ERR(lvds->clocks.mod);
+       /*
+        * LVDS encoders without an extended PLL have no external clock inputs.
+        */
+       if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL))
+               return 0;
+       lvds->clocks.extal = rcar_lvds_get_clock(lvds, "extal", true);
+       if (IS_ERR(lvds->clocks.extal))
+               return PTR_ERR(lvds->clocks.extal);
+       lvds->clocks.dotclkin[0] = rcar_lvds_get_clock(lvds, "dclkin.0", true);
+       if (IS_ERR(lvds->clocks.dotclkin[0]))
+               return PTR_ERR(lvds->clocks.dotclkin[0]);
+       lvds->clocks.dotclkin[1] = rcar_lvds_get_clock(lvds, "dclkin.1", true);
+       if (IS_ERR(lvds->clocks.dotclkin[1]))
+               return PTR_ERR(lvds->clocks.dotclkin[1]);
+       /* At least one input to the PLL must be available. */
+       if (!lvds->clocks.extal && !lvds->clocks.dotclkin[0] &&
+           !lvds->clocks.dotclkin[1]) {
+               dev_err(lvds->dev,
+                       "no input clock (extal, dclkin.0 or dclkin.1)\n");
+               return -EINVAL;
+       }
+       return 0;
+ }
+ static const struct rcar_lvds_device_info rcar_lvds_r8a7790es1_info = {
+       .gen = 2,
+       .quirks = RCAR_LVDS_QUIRK_LANES,
+       .pll_setup = rcar_lvds_pll_setup_gen2,
+ };
+ static const struct soc_device_attribute lvds_quirk_matches[] = {
+       {
+               .soc_id = "r8a7790", .revision = "ES1.*",
+               .data = &rcar_lvds_r8a7790es1_info,
+       },
+       { /* sentinel */ }
+ };
+ static int rcar_lvds_probe(struct platform_device *pdev)
+ {
+       const struct soc_device_attribute *attr;
+       struct rcar_lvds *lvds;
+       int ret;
+       lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
+       if (lvds == NULL)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, lvds);
+       lvds->dev = &pdev->dev;
+       lvds->info = of_device_get_match_data(&pdev->dev);
+       attr = soc_device_match(lvds_quirk_matches);
+       if (attr)
+               lvds->info = attr->data;
+       ret = rcar_lvds_parse_dt(lvds);
+       if (ret < 0)
+               return ret;
+       lvds->bridge.funcs = &rcar_lvds_bridge_ops;
+       lvds->bridge.of_node = pdev->dev.of_node;
+       lvds->mmio = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(lvds->mmio))
+               return PTR_ERR(lvds->mmio);
+       ret = rcar_lvds_get_clocks(lvds);
+       if (ret < 0)
+               return ret;
+       lvds->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+       if (IS_ERR(lvds->rstc))
+               return dev_err_probe(&pdev->dev, PTR_ERR(lvds->rstc),
+                                    "failed to get cpg reset\n");
+       pm_runtime_enable(&pdev->dev);
+       drm_bridge_add(&lvds->bridge);
+       return 0;
+ }
 -
 -      return 0;
++static void rcar_lvds_remove(struct platform_device *pdev)
+ {
+       struct rcar_lvds *lvds = platform_get_drvdata(pdev);
+       drm_bridge_remove(&lvds->bridge);
+       pm_runtime_disable(&pdev->dev);
 -      .remove         = rcar_lvds_remove,
+ }
+ static const struct rcar_lvds_device_info rcar_lvds_gen2_info = {
+       .gen = 2,
+       .pll_setup = rcar_lvds_pll_setup_gen2,
+ };
+ static const struct rcar_lvds_device_info rcar_lvds_gen3_info = {
+       .gen = 3,
+       .quirks = RCAR_LVDS_QUIRK_PWD,
+       .pll_setup = rcar_lvds_pll_setup_gen3,
+ };
+ static const struct rcar_lvds_device_info rcar_lvds_r8a77970_info = {
+       .gen = 3,
+       .quirks = RCAR_LVDS_QUIRK_PWD | RCAR_LVDS_QUIRK_GEN3_LVEN,
+       .pll_setup = rcar_lvds_pll_setup_gen2,
+ };
+ static const struct rcar_lvds_device_info rcar_lvds_r8a77990_info = {
+       .gen = 3,
+       .quirks = RCAR_LVDS_QUIRK_GEN3_LVEN | RCAR_LVDS_QUIRK_EXT_PLL
+               | RCAR_LVDS_QUIRK_DUAL_LINK,
+ };
+ static const struct rcar_lvds_device_info rcar_lvds_r8a77995_info = {
+       .gen = 3,
+       .quirks = RCAR_LVDS_QUIRK_GEN3_LVEN | RCAR_LVDS_QUIRK_PWD
+               | RCAR_LVDS_QUIRK_EXT_PLL | RCAR_LVDS_QUIRK_DUAL_LINK,
+ };
+ static const struct of_device_id rcar_lvds_of_table[] = {
+       { .compatible = "renesas,r8a7742-lvds", .data = &rcar_lvds_gen2_info },
+       { .compatible = "renesas,r8a7743-lvds", .data = &rcar_lvds_gen2_info },
+       { .compatible = "renesas,r8a7744-lvds", .data = &rcar_lvds_gen2_info },
+       { .compatible = "renesas,r8a774a1-lvds", .data = &rcar_lvds_gen3_info },
+       { .compatible = "renesas,r8a774b1-lvds", .data = &rcar_lvds_gen3_info },
+       { .compatible = "renesas,r8a774c0-lvds", .data = &rcar_lvds_r8a77990_info },
+       { .compatible = "renesas,r8a774e1-lvds", .data = &rcar_lvds_gen3_info },
+       { .compatible = "renesas,r8a7790-lvds", .data = &rcar_lvds_gen2_info },
+       { .compatible = "renesas,r8a7791-lvds", .data = &rcar_lvds_gen2_info },
+       { .compatible = "renesas,r8a7793-lvds", .data = &rcar_lvds_gen2_info },
+       { .compatible = "renesas,r8a7795-lvds", .data = &rcar_lvds_gen3_info },
+       { .compatible = "renesas,r8a7796-lvds", .data = &rcar_lvds_gen3_info },
+       { .compatible = "renesas,r8a77961-lvds", .data = &rcar_lvds_gen3_info },
+       { .compatible = "renesas,r8a77965-lvds", .data = &rcar_lvds_gen3_info },
+       { .compatible = "renesas,r8a77970-lvds", .data = &rcar_lvds_r8a77970_info },
+       { .compatible = "renesas,r8a77980-lvds", .data = &rcar_lvds_gen3_info },
+       { .compatible = "renesas,r8a77990-lvds", .data = &rcar_lvds_r8a77990_info },
+       { .compatible = "renesas,r8a77995-lvds", .data = &rcar_lvds_r8a77995_info },
+       { }
+ };
+ MODULE_DEVICE_TABLE(of, rcar_lvds_of_table);
+ static int rcar_lvds_runtime_suspend(struct device *dev)
+ {
+       struct rcar_lvds *lvds = dev_get_drvdata(dev);
+       clk_disable_unprepare(lvds->clocks.mod);
+       reset_control_assert(lvds->rstc);
+       return 0;
+ }
+ static int rcar_lvds_runtime_resume(struct device *dev)
+ {
+       struct rcar_lvds *lvds = dev_get_drvdata(dev);
+       int ret;
+       ret = reset_control_deassert(lvds->rstc);
+       if (ret)
+               return ret;
+       ret = clk_prepare_enable(lvds->clocks.mod);
+       if (ret < 0)
+               goto err_reset_assert;
+       return 0;
+ err_reset_assert:
+       reset_control_assert(lvds->rstc);
+       return ret;
+ }
+ static const struct dev_pm_ops rcar_lvds_pm_ops = {
+       SET_RUNTIME_PM_OPS(rcar_lvds_runtime_suspend, rcar_lvds_runtime_resume, NULL)
+ };
+ static struct platform_driver rcar_lvds_platform_driver = {
+       .probe          = rcar_lvds_probe,
++      .remove_new     = rcar_lvds_remove,
+       .driver         = {
+               .name   = "rcar-lvds",
+               .pm     = &rcar_lvds_pm_ops,
+               .of_match_table = rcar_lvds_of_table,
+       },
+ };
+ module_platform_driver(rcar_lvds_platform_driver);
+ MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+ MODULE_DESCRIPTION("Renesas R-Car LVDS Encoder Driver");
+ MODULE_LICENSE("GPL");
index 0000000000000000000000000000000000000000,e10e4d4b89a22bd48e16d0e5120749b6b7d9c46e..305123a671c622c13c77a641c3dfcb45ffbebb17
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1106 +1,1104 @@@
 -static int rcar_mipi_dsi_remove(struct platform_device *pdev)
+ // SPDX-License-Identifier: GPL-2.0
+ /*
+  * R-Car MIPI DSI Encoder
+  *
+  * Copyright (C) 2020 Renesas Electronics Corporation
+  */
+ #include <linux/clk.h>
+ #include <linux/delay.h>
+ #include <linux/io.h>
+ #include <linux/iopoll.h>
+ #include <linux/math64.h>
+ #include <linux/module.h>
+ #include <linux/of.h>
+ #include <linux/of_device.h>
+ #include <linux/of_graph.h>
+ #include <linux/platform_device.h>
+ #include <linux/reset.h>
+ #include <linux/slab.h>
+ #include <drm/drm_atomic.h>
+ #include <drm/drm_atomic_helper.h>
+ #include <drm/drm_bridge.h>
+ #include <drm/drm_mipi_dsi.h>
+ #include <drm/drm_of.h>
+ #include <drm/drm_panel.h>
+ #include <drm/drm_probe_helper.h>
+ #include "rcar_mipi_dsi.h"
+ #include "rcar_mipi_dsi_regs.h"
+ #define MHZ(v) ((u32)((v) * 1000000U))
+ enum rcar_mipi_dsi_hw_model {
+       RCAR_DSI_V3U,
+       RCAR_DSI_V4H,
+ };
+ struct rcar_mipi_dsi_device_info {
+       enum rcar_mipi_dsi_hw_model model;
+       const struct dsi_clk_config *clk_cfg;
+       u8 clockset2_m_offset;
+       u8 n_min;
+       u8 n_max;
+       u8 n_mul;
+       unsigned long fpfd_min;
+       unsigned long fpfd_max;
+       u16 m_min;
+       u16 m_max;
+       unsigned long fout_min;
+       unsigned long fout_max;
+ };
+ struct rcar_mipi_dsi {
+       struct device *dev;
+       const struct rcar_mipi_dsi_device_info *info;
+       struct reset_control *rstc;
+       struct mipi_dsi_host host;
+       struct drm_bridge bridge;
+       struct drm_bridge *next_bridge;
+       struct drm_connector connector;
+       void __iomem *mmio;
+       struct {
+               struct clk *mod;
+               struct clk *pll;
+               struct clk *dsi;
+       } clocks;
+       enum mipi_dsi_pixel_format format;
+       unsigned int num_data_lanes;
+       unsigned int lanes;
+ };
+ struct dsi_setup_info {
+       unsigned long hsfreq;
+       u16 hsfreqrange;
+       unsigned long fout;
+       u16 m;
+       u16 n;
+       u16 vclk_divider;
+       const struct dsi_clk_config *clkset;
+ };
+ static inline struct rcar_mipi_dsi *
+ bridge_to_rcar_mipi_dsi(struct drm_bridge *bridge)
+ {
+       return container_of(bridge, struct rcar_mipi_dsi, bridge);
+ }
+ static inline struct rcar_mipi_dsi *
+ host_to_rcar_mipi_dsi(struct mipi_dsi_host *host)
+ {
+       return container_of(host, struct rcar_mipi_dsi, host);
+ }
+ static const u32 hsfreqrange_table[][2] = {
+       {   MHZ(80), 0x00 }, {   MHZ(90), 0x10 }, {  MHZ(100), 0x20 },
+       {  MHZ(110), 0x30 }, {  MHZ(120), 0x01 }, {  MHZ(130), 0x11 },
+       {  MHZ(140), 0x21 }, {  MHZ(150), 0x31 }, {  MHZ(160), 0x02 },
+       {  MHZ(170), 0x12 }, {  MHZ(180), 0x22 }, {  MHZ(190), 0x32 },
+       {  MHZ(205), 0x03 }, {  MHZ(220), 0x13 }, {  MHZ(235), 0x23 },
+       {  MHZ(250), 0x33 }, {  MHZ(275), 0x04 }, {  MHZ(300), 0x14 },
+       {  MHZ(325), 0x25 }, {  MHZ(350), 0x35 }, {  MHZ(400), 0x05 },
+       {  MHZ(450), 0x16 }, {  MHZ(500), 0x26 }, {  MHZ(550), 0x37 },
+       {  MHZ(600), 0x07 }, {  MHZ(650), 0x18 }, {  MHZ(700), 0x28 },
+       {  MHZ(750), 0x39 }, {  MHZ(800), 0x09 }, {  MHZ(850), 0x19 },
+       {  MHZ(900), 0x29 }, {  MHZ(950), 0x3a }, { MHZ(1000), 0x0a },
+       { MHZ(1050), 0x1a }, { MHZ(1100), 0x2a }, { MHZ(1150), 0x3b },
+       { MHZ(1200), 0x0b }, { MHZ(1250), 0x1b }, { MHZ(1300), 0x2b },
+       { MHZ(1350), 0x3c }, { MHZ(1400), 0x0c }, { MHZ(1450), 0x1c },
+       { MHZ(1500), 0x2c }, { MHZ(1550), 0x3d }, { MHZ(1600), 0x0d },
+       { MHZ(1650), 0x1d }, { MHZ(1700), 0x2e }, { MHZ(1750), 0x3e },
+       { MHZ(1800), 0x0e }, { MHZ(1850), 0x1e }, { MHZ(1900), 0x2f },
+       { MHZ(1950), 0x3f }, { MHZ(2000), 0x0f }, { MHZ(2050), 0x40 },
+       { MHZ(2100), 0x41 }, { MHZ(2150), 0x42 }, { MHZ(2200), 0x43 },
+       { MHZ(2250), 0x44 }, { MHZ(2300), 0x45 }, { MHZ(2350), 0x46 },
+       { MHZ(2400), 0x47 }, { MHZ(2450), 0x48 }, { MHZ(2500), 0x49 },
+       { /* sentinel */ },
+ };
+ struct dsi_clk_config {
+       u32 min_freq;
+       u32 max_freq;
+       u8 vco_cntrl;
+       u8 cpbias_cntrl;
+       u8 gmp_cntrl;
+       u8 int_cntrl;
+       u8 prop_cntrl;
+ };
+ static const struct dsi_clk_config dsi_clk_cfg_v3u[] = {
+       {   MHZ(40),    MHZ(55), 0x3f, 0x10, 0x01, 0x00, 0x0b },
+       {   MHZ(52.5),  MHZ(80), 0x39, 0x10, 0x01, 0x00, 0x0b },
+       {   MHZ(80),   MHZ(110), 0x2f, 0x10, 0x01, 0x00, 0x0b },
+       {  MHZ(105),   MHZ(160), 0x29, 0x10, 0x01, 0x00, 0x0b },
+       {  MHZ(160),   MHZ(220), 0x1f, 0x10, 0x01, 0x00, 0x0b },
+       {  MHZ(210),   MHZ(320), 0x19, 0x10, 0x01, 0x00, 0x0b },
+       {  MHZ(320),   MHZ(440), 0x0f, 0x10, 0x01, 0x00, 0x0b },
+       {  MHZ(420),   MHZ(660), 0x09, 0x10, 0x01, 0x00, 0x0b },
+       {  MHZ(630),  MHZ(1149), 0x03, 0x10, 0x01, 0x00, 0x0b },
+       { MHZ(1100),  MHZ(1152), 0x01, 0x10, 0x01, 0x00, 0x0b },
+       { MHZ(1150),  MHZ(1250), 0x01, 0x10, 0x01, 0x00, 0x0c },
+       { /* sentinel */ },
+ };
+ static const struct dsi_clk_config dsi_clk_cfg_v4h[] = {
+       {   MHZ(40),    MHZ(45.31),  0x2b, 0x00, 0x00, 0x08, 0x0a },
+       {   MHZ(45.31), MHZ(54.66),  0x28, 0x00, 0x00, 0x08, 0x0a },
+       {   MHZ(54.66), MHZ(62.5),   0x28, 0x00, 0x00, 0x08, 0x0a },
+       {   MHZ(62.5),  MHZ(75),     0x27, 0x00, 0x00, 0x08, 0x0a },
+       {   MHZ(75),    MHZ(90.63),  0x23, 0x00, 0x00, 0x08, 0x0a },
+       {   MHZ(90.63), MHZ(109.37), 0x20, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(109.37), MHZ(125),    0x20, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(125),    MHZ(150),    0x1f, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(150),    MHZ(181.25), 0x1b, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(181.25), MHZ(218.75), 0x18, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(218.75), MHZ(250),    0x18, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(250),    MHZ(300),    0x17, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(300),    MHZ(362.5),  0x13, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(362.5),  MHZ(455.48), 0x10, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(455.48), MHZ(500),    0x10, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(500),    MHZ(600),    0x0f, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(600),    MHZ(725),    0x0b, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(725),    MHZ(875),    0x08, 0x00, 0x00, 0x08, 0x0a },
+       {  MHZ(875),   MHZ(1000),    0x08, 0x00, 0x00, 0x08, 0x0a },
+       { MHZ(1000),   MHZ(1200),    0x07, 0x00, 0x00, 0x08, 0x0a },
+       { MHZ(1200),   MHZ(1250),    0x03, 0x00, 0x00, 0x08, 0x0a },
+       { /* sentinel */ },
+ };
+ static void rcar_mipi_dsi_write(struct rcar_mipi_dsi *dsi, u32 reg, u32 data)
+ {
+       iowrite32(data, dsi->mmio + reg);
+ }
+ static u32 rcar_mipi_dsi_read(struct rcar_mipi_dsi *dsi, u32 reg)
+ {
+       return ioread32(dsi->mmio + reg);
+ }
+ static void rcar_mipi_dsi_clr(struct rcar_mipi_dsi *dsi, u32 reg, u32 clr)
+ {
+       rcar_mipi_dsi_write(dsi, reg, rcar_mipi_dsi_read(dsi, reg) & ~clr);
+ }
+ static void rcar_mipi_dsi_set(struct rcar_mipi_dsi *dsi, u32 reg, u32 set)
+ {
+       rcar_mipi_dsi_write(dsi, reg, rcar_mipi_dsi_read(dsi, reg) | set);
+ }
+ static int rcar_mipi_dsi_write_phtw(struct rcar_mipi_dsi *dsi, u32 phtw)
+ {
+       u32 status;
+       int ret;
+       rcar_mipi_dsi_write(dsi, PHTW, phtw);
+       ret = read_poll_timeout(rcar_mipi_dsi_read, status,
+                               !(status & (PHTW_DWEN | PHTW_CWEN)),
+                               2000, 10000, false, dsi, PHTW);
+       if (ret < 0) {
+               dev_err(dsi->dev, "PHY test interface write timeout (0x%08x)\n",
+                       phtw);
+               return ret;
+       }
+       return ret;
+ }
+ static int rcar_mipi_dsi_write_phtw_arr(struct rcar_mipi_dsi *dsi,
+                                       const u32 *phtw, unsigned int size)
+ {
+       for (unsigned int i = 0; i < size; i++) {
+               int ret = rcar_mipi_dsi_write_phtw(dsi, phtw[i]);
+               if (ret < 0)
+                       return ret;
+       }
+       return 0;
+ }
+ #define WRITE_PHTW(...)                                               \
+       ({                                                            \
+               static const u32 phtw[] = { __VA_ARGS__ };            \
+               int ret;                                              \
+               ret = rcar_mipi_dsi_write_phtw_arr(dsi, phtw,         \
+                                                  ARRAY_SIZE(phtw)); \
+               ret;                                                  \
+       })
+ static int rcar_mipi_dsi_init_phtw_v3u(struct rcar_mipi_dsi *dsi)
+ {
+       return WRITE_PHTW(0x01020114, 0x01600115, 0x01030116, 0x0102011d,
+                         0x011101a4, 0x018601a4, 0x014201a0, 0x010001a3,
+                         0x0101011f);
+ }
+ static int rcar_mipi_dsi_post_init_phtw_v3u(struct rcar_mipi_dsi *dsi)
+ {
+       return WRITE_PHTW(0x010c0130, 0x010c0140, 0x010c0150, 0x010c0180,
+                         0x010c0190, 0x010a0160, 0x010a0170, 0x01800164,
+                         0x01800174);
+ }
+ static int rcar_mipi_dsi_init_phtw_v4h(struct rcar_mipi_dsi *dsi,
+                                      const struct dsi_setup_info *setup_info)
+ {
+       int ret;
+       if (setup_info->hsfreq < MHZ(450)) {
+               ret = WRITE_PHTW(0x01010100, 0x011b01ac);
+               if (ret)
+                       return ret;
+       }
+       ret = WRITE_PHTW(0x01010100, 0x01030173, 0x01000174, 0x01500175,
+                        0x01030176, 0x01040166, 0x010201ad);
+       if (ret)
+               return ret;
+       if (setup_info->hsfreq <= MHZ(1000))
+               ret = WRITE_PHTW(0x01020100, 0x01910170, 0x01020171,
+                                0x01110172);
+       else if (setup_info->hsfreq <= MHZ(1500))
+               ret = WRITE_PHTW(0x01020100, 0x01980170, 0x01030171,
+                                0x01100172);
+       else if (setup_info->hsfreq <= MHZ(2500))
+               ret = WRITE_PHTW(0x01020100, 0x0144016b, 0x01000172);
+       else
+               return -EINVAL;
+       if (ret)
+               return ret;
+       if (dsi->lanes <= 1) {
+               ret = WRITE_PHTW(0x01070100, 0x010e010b);
+               if (ret)
+                       return ret;
+       }
+       if (dsi->lanes <= 2) {
+               ret = WRITE_PHTW(0x01090100, 0x010e010b);
+               if (ret)
+                       return ret;
+       }
+       if (dsi->lanes <= 3) {
+               ret = WRITE_PHTW(0x010b0100, 0x010e010b);
+               if (ret)
+                       return ret;
+       }
+       if (setup_info->hsfreq <= MHZ(1500)) {
+               ret = WRITE_PHTW(0x01010100, 0x01c0016e);
+               if (ret)
+                       return ret;
+       }
+       return 0;
+ }
+ static int
+ rcar_mipi_dsi_post_init_phtw_v4h(struct rcar_mipi_dsi *dsi,
+                                const struct dsi_setup_info *setup_info)
+ {
+       u32 status;
+       int ret;
+       if (setup_info->hsfreq <= MHZ(1500)) {
+               WRITE_PHTW(0x01020100, 0x00000180);
+               ret = read_poll_timeout(rcar_mipi_dsi_read, status,
+                                       status & PHTR_TEST, 2000, 10000, false,
+                                       dsi, PHTR);
+               if (ret < 0) {
+                       dev_err(dsi->dev, "failed to test PHTR\n");
+                       return ret;
+               }
+               WRITE_PHTW(0x01010100, 0x0100016e);
+       }
+       return 0;
+ }
+ /* -----------------------------------------------------------------------------
+  * Hardware Setup
+  */
+ static void rcar_mipi_dsi_pll_calc(struct rcar_mipi_dsi *dsi,
+                                  unsigned long fin_rate,
+                                  unsigned long fout_target,
+                                  struct dsi_setup_info *setup_info)
+ {
+       unsigned int best_err = -1;
+       const struct rcar_mipi_dsi_device_info *info = dsi->info;
+       for (unsigned int n = info->n_min; n <= info->n_max; n++) {
+               unsigned long fpfd;
+               fpfd = fin_rate / n;
+               if (fpfd < info->fpfd_min || fpfd > info->fpfd_max)
+                       continue;
+               for (unsigned int m = info->m_min; m <= info->m_max; m++) {
+                       unsigned int err;
+                       u64 fout;
+                       fout = div64_u64((u64)fpfd * m, dsi->info->n_mul);
+                       if (fout < info->fout_min || fout > info->fout_max)
+                               continue;
+                       fout = div64_u64(fout, setup_info->vclk_divider);
+                       if (fout < setup_info->clkset->min_freq ||
+                           fout > setup_info->clkset->max_freq)
+                               continue;
+                       err = abs((long)(fout - fout_target) * 10000 /
+                                 (long)fout_target);
+                       if (err < best_err) {
+                               setup_info->m = m;
+                               setup_info->n = n;
+                               setup_info->fout = (unsigned long)fout;
+                               best_err = err;
+                               if (err == 0)
+                                       return;
+                       }
+               }
+       }
+ }
+ static void rcar_mipi_dsi_parameters_calc(struct rcar_mipi_dsi *dsi,
+                                         struct clk *clk, unsigned long target,
+                                         struct dsi_setup_info *setup_info)
+ {
+       const struct dsi_clk_config *clk_cfg;
+       unsigned long fout_target;
+       unsigned long fin_rate;
+       unsigned int i;
+       unsigned int err;
+       /*
+        * Calculate Fout = dot clock * ColorDepth / (2 * Lane Count)
+        * The range out Fout is [40 - 1250] Mhz
+        */
+       fout_target = target * mipi_dsi_pixel_format_to_bpp(dsi->format)
+                   / (2 * dsi->lanes);
+       if (fout_target < MHZ(40) || fout_target > MHZ(1250))
+               return;
+       /* Find PLL settings */
+       for (clk_cfg = dsi->info->clk_cfg; clk_cfg->min_freq != 0; clk_cfg++) {
+               if (fout_target > clk_cfg->min_freq &&
+                   fout_target <= clk_cfg->max_freq) {
+                       setup_info->clkset = clk_cfg;
+                       break;
+               }
+       }
+       fin_rate = clk_get_rate(clk);
+       switch (dsi->info->model) {
+       case RCAR_DSI_V3U:
+       default:
+               setup_info->vclk_divider = 1 << ((clk_cfg->vco_cntrl >> 4) & 0x3);
+               break;
+       case RCAR_DSI_V4H:
+               setup_info->vclk_divider = 1 << (((clk_cfg->vco_cntrl >> 3) & 0x7) + 1);
+               break;
+       }
+       rcar_mipi_dsi_pll_calc(dsi, fin_rate, fout_target, setup_info);
+       /* Find hsfreqrange */
+       setup_info->hsfreq = setup_info->fout * 2;
+       for (i = 0; i < ARRAY_SIZE(hsfreqrange_table); i++) {
+               if (hsfreqrange_table[i][0] >= setup_info->hsfreq) {
+                       setup_info->hsfreqrange = hsfreqrange_table[i][1];
+                       break;
+               }
+       }
+       err = abs((long)(setup_info->fout - fout_target) * 10000 / (long)fout_target);
+       dev_dbg(dsi->dev,
+               "Fout = %u * %lu / (%u * %u * %u) = %lu (target %lu Hz, error %d.%02u%%)\n",
+               setup_info->m, fin_rate, dsi->info->n_mul, setup_info->n,
+               setup_info->vclk_divider, setup_info->fout, fout_target,
+               err / 100, err % 100);
+       dev_dbg(dsi->dev,
+               "vco_cntrl = 0x%x\tprop_cntrl = 0x%x\thsfreqrange = 0x%x\n",
+               clk_cfg->vco_cntrl, clk_cfg->prop_cntrl,
+               setup_info->hsfreqrange);
+ }
+ static void rcar_mipi_dsi_set_display_timing(struct rcar_mipi_dsi *dsi,
+                                            const struct drm_display_mode *mode)
+ {
+       u32 setr;
+       u32 vprmset0r;
+       u32 vprmset1r;
+       u32 vprmset2r;
+       u32 vprmset3r;
+       u32 vprmset4r;
+       /* Configuration for Pixel Stream and Packet Header */
+       if (mipi_dsi_pixel_format_to_bpp(dsi->format) == 24)
+               rcar_mipi_dsi_write(dsi, TXVMPSPHSETR, TXVMPSPHSETR_DT_RGB24);
+       else if (mipi_dsi_pixel_format_to_bpp(dsi->format) == 18)
+               rcar_mipi_dsi_write(dsi, TXVMPSPHSETR, TXVMPSPHSETR_DT_RGB18);
+       else if (mipi_dsi_pixel_format_to_bpp(dsi->format) == 16)
+               rcar_mipi_dsi_write(dsi, TXVMPSPHSETR, TXVMPSPHSETR_DT_RGB16);
+       else {
+               dev_warn(dsi->dev, "unsupported format");
+               return;
+       }
+       /* Configuration for Blanking sequence and Input Pixel */
+       setr = TXVMSETR_HSABPEN_EN | TXVMSETR_HBPBPEN_EN
+            | TXVMSETR_HFPBPEN_EN | TXVMSETR_SYNSEQ_PULSES
+            | TXVMSETR_PIXWDTH | TXVMSETR_VSTPM;
+       rcar_mipi_dsi_write(dsi, TXVMSETR, setr);
+       /* Configuration for Video Parameters */
+       vprmset0r = (mode->flags & DRM_MODE_FLAG_PVSYNC ?
+                    TXVMVPRMSET0R_VSPOL_HIG : TXVMVPRMSET0R_VSPOL_LOW)
+                 | (mode->flags & DRM_MODE_FLAG_PHSYNC ?
+                    TXVMVPRMSET0R_HSPOL_HIG : TXVMVPRMSET0R_HSPOL_LOW)
+                 | TXVMVPRMSET0R_CSPC_RGB | TXVMVPRMSET0R_BPP_24;
+       vprmset1r = TXVMVPRMSET1R_VACTIVE(mode->vdisplay)
+                 | TXVMVPRMSET1R_VSA(mode->vsync_end - mode->vsync_start);
+       vprmset2r = TXVMVPRMSET2R_VFP(mode->vsync_start - mode->vdisplay)
+                 | TXVMVPRMSET2R_VBP(mode->vtotal - mode->vsync_end);
+       vprmset3r = TXVMVPRMSET3R_HACTIVE(mode->hdisplay)
+                 | TXVMVPRMSET3R_HSA(mode->hsync_end - mode->hsync_start);
+       vprmset4r = TXVMVPRMSET4R_HFP(mode->hsync_start - mode->hdisplay)
+                 | TXVMVPRMSET4R_HBP(mode->htotal - mode->hsync_end);
+       rcar_mipi_dsi_write(dsi, TXVMVPRMSET0R, vprmset0r);
+       rcar_mipi_dsi_write(dsi, TXVMVPRMSET1R, vprmset1r);
+       rcar_mipi_dsi_write(dsi, TXVMVPRMSET2R, vprmset2r);
+       rcar_mipi_dsi_write(dsi, TXVMVPRMSET3R, vprmset3r);
+       rcar_mipi_dsi_write(dsi, TXVMVPRMSET4R, vprmset4r);
+ }
+ static int rcar_mipi_dsi_startup(struct rcar_mipi_dsi *dsi,
+                                const struct drm_display_mode *mode)
+ {
+       struct dsi_setup_info setup_info = {};
+       unsigned int timeout;
+       int ret;
+       int dsi_format;
+       u32 phy_setup;
+       u32 clockset2, clockset3;
+       u32 ppisetr;
+       u32 vclkset;
+       /* Checking valid format */
+       dsi_format = mipi_dsi_pixel_format_to_bpp(dsi->format);
+       if (dsi_format < 0) {
+               dev_warn(dsi->dev, "invalid format");
+               return -EINVAL;
+       }
+       /* Parameters Calculation */
+       rcar_mipi_dsi_parameters_calc(dsi, dsi->clocks.pll,
+                                     mode->clock * 1000, &setup_info);
+       /* LPCLK enable */
+       rcar_mipi_dsi_set(dsi, LPCLKSET, LPCLKSET_CKEN);
+       /* CFGCLK enabled */
+       rcar_mipi_dsi_set(dsi, CFGCLKSET, CFGCLKSET_CKEN);
+       rcar_mipi_dsi_clr(dsi, PHYSETUP, PHYSETUP_RSTZ);
+       rcar_mipi_dsi_clr(dsi, PHYSETUP, PHYSETUP_SHUTDOWNZ);
+       rcar_mipi_dsi_set(dsi, PHTC, PHTC_TESTCLR);
+       rcar_mipi_dsi_clr(dsi, PHTC, PHTC_TESTCLR);
+       /* PHY setting */
+       phy_setup = rcar_mipi_dsi_read(dsi, PHYSETUP);
+       phy_setup &= ~PHYSETUP_HSFREQRANGE_MASK;
+       phy_setup |= PHYSETUP_HSFREQRANGE(setup_info.hsfreqrange);
+       rcar_mipi_dsi_write(dsi, PHYSETUP, phy_setup);
+       switch (dsi->info->model) {
+       case RCAR_DSI_V3U:
+       default:
+               ret = rcar_mipi_dsi_init_phtw_v3u(dsi);
+               if (ret < 0)
+                       return ret;
+               break;
+       case RCAR_DSI_V4H:
+               ret = rcar_mipi_dsi_init_phtw_v4h(dsi, &setup_info);
+               if (ret < 0)
+                       return ret;
+               break;
+       }
+       /* PLL Clock Setting */
+       rcar_mipi_dsi_clr(dsi, CLOCKSET1, CLOCKSET1_SHADOW_CLEAR);
+       rcar_mipi_dsi_set(dsi, CLOCKSET1, CLOCKSET1_SHADOW_CLEAR);
+       rcar_mipi_dsi_clr(dsi, CLOCKSET1, CLOCKSET1_SHADOW_CLEAR);
+       clockset2 = CLOCKSET2_M(setup_info.m - dsi->info->clockset2_m_offset)
+                 | CLOCKSET2_N(setup_info.n - 1)
+                 | CLOCKSET2_VCO_CNTRL(setup_info.clkset->vco_cntrl);
+       clockset3 = CLOCKSET3_PROP_CNTRL(setup_info.clkset->prop_cntrl)
+                 | CLOCKSET3_INT_CNTRL(setup_info.clkset->int_cntrl)
+                 | CLOCKSET3_CPBIAS_CNTRL(setup_info.clkset->cpbias_cntrl)
+                 | CLOCKSET3_GMP_CNTRL(setup_info.clkset->gmp_cntrl);
+       rcar_mipi_dsi_write(dsi, CLOCKSET2, clockset2);
+       rcar_mipi_dsi_write(dsi, CLOCKSET3, clockset3);
+       rcar_mipi_dsi_clr(dsi, CLOCKSET1, CLOCKSET1_UPDATEPLL);
+       rcar_mipi_dsi_set(dsi, CLOCKSET1, CLOCKSET1_UPDATEPLL);
+       udelay(10);
+       rcar_mipi_dsi_clr(dsi, CLOCKSET1, CLOCKSET1_UPDATEPLL);
+       ppisetr = PPISETR_DLEN_3 | PPISETR_CLEN;
+       rcar_mipi_dsi_write(dsi, PPISETR, ppisetr);
+       rcar_mipi_dsi_set(dsi, PHYSETUP, PHYSETUP_SHUTDOWNZ);
+       rcar_mipi_dsi_set(dsi, PHYSETUP, PHYSETUP_RSTZ);
+       usleep_range(400, 500);
+       /* Checking PPI clock status register */
+       for (timeout = 10; timeout > 0; --timeout) {
+               if ((rcar_mipi_dsi_read(dsi, PPICLSR) & PPICLSR_STPST) &&
+                   (rcar_mipi_dsi_read(dsi, PPIDLSR) & PPIDLSR_STPST) &&
+                   (rcar_mipi_dsi_read(dsi, CLOCKSET1) & CLOCKSET1_LOCK))
+                       break;
+               usleep_range(1000, 2000);
+       }
+       if (!timeout) {
+               dev_err(dsi->dev, "failed to enable PPI clock\n");
+               return -ETIMEDOUT;
+       }
+       switch (dsi->info->model) {
+       case RCAR_DSI_V3U:
+       default:
+               ret = rcar_mipi_dsi_post_init_phtw_v3u(dsi);
+               if (ret < 0)
+                       return ret;
+               break;
+       case RCAR_DSI_V4H:
+               ret = rcar_mipi_dsi_post_init_phtw_v4h(dsi, &setup_info);
+               if (ret < 0)
+                       return ret;
+               break;
+       }
+       /* Enable DOT clock */
+       vclkset = VCLKSET_CKEN;
+       rcar_mipi_dsi_write(dsi, VCLKSET, vclkset);
+       if (dsi_format == 24)
+               vclkset |= VCLKSET_BPP_24;
+       else if (dsi_format == 18)
+               vclkset |= VCLKSET_BPP_18;
+       else if (dsi_format == 16)
+               vclkset |= VCLKSET_BPP_16;
+       else {
+               dev_warn(dsi->dev, "unsupported format");
+               return -EINVAL;
+       }
+       vclkset |= VCLKSET_COLOR_RGB | VCLKSET_LANE(dsi->lanes - 1);
+       switch (dsi->info->model) {
+       case RCAR_DSI_V3U:
+       default:
+               vclkset |= VCLKSET_DIV_V3U(__ffs(setup_info.vclk_divider));
+               break;
+       case RCAR_DSI_V4H:
+               vclkset |= VCLKSET_DIV_V4H(__ffs(setup_info.vclk_divider) - 1);
+               break;
+       }
+       rcar_mipi_dsi_write(dsi, VCLKSET, vclkset);
+       /* After setting VCLKSET register, enable VCLKEN */
+       rcar_mipi_dsi_set(dsi, VCLKEN, VCLKEN_CKEN);
+       dev_dbg(dsi->dev, "DSI device is started\n");
+       return 0;
+ }
+ static void rcar_mipi_dsi_shutdown(struct rcar_mipi_dsi *dsi)
+ {
+       /* Disable VCLKEN */
+       rcar_mipi_dsi_write(dsi, VCLKSET, 0);
+       /* Disable DOT clock */
+       rcar_mipi_dsi_write(dsi, VCLKSET, 0);
+       rcar_mipi_dsi_clr(dsi, PHYSETUP, PHYSETUP_RSTZ);
+       rcar_mipi_dsi_clr(dsi, PHYSETUP, PHYSETUP_SHUTDOWNZ);
+       /* CFGCLK disable */
+       rcar_mipi_dsi_clr(dsi, CFGCLKSET, CFGCLKSET_CKEN);
+       /* LPCLK disable */
+       rcar_mipi_dsi_clr(dsi, LPCLKSET, LPCLKSET_CKEN);
+       dev_dbg(dsi->dev, "DSI device is shutdown\n");
+ }
+ static int rcar_mipi_dsi_clk_enable(struct rcar_mipi_dsi *dsi)
+ {
+       int ret;
+       reset_control_deassert(dsi->rstc);
+       ret = clk_prepare_enable(dsi->clocks.mod);
+       if (ret < 0)
+               goto err_reset;
+       ret = clk_prepare_enable(dsi->clocks.dsi);
+       if (ret < 0)
+               goto err_clock;
+       return 0;
+ err_clock:
+       clk_disable_unprepare(dsi->clocks.mod);
+ err_reset:
+       reset_control_assert(dsi->rstc);
+       return ret;
+ }
+ static void rcar_mipi_dsi_clk_disable(struct rcar_mipi_dsi *dsi)
+ {
+       clk_disable_unprepare(dsi->clocks.dsi);
+       clk_disable_unprepare(dsi->clocks.mod);
+       reset_control_assert(dsi->rstc);
+ }
+ static int rcar_mipi_dsi_start_hs_clock(struct rcar_mipi_dsi *dsi)
+ {
+       /*
+        * In HW manual, we need to check TxDDRClkHS-Q Stable? but it dont
+        * write how to check. So we skip this check in this patch
+        */
+       u32 status;
+       int ret;
+       /* Start HS clock. */
+       rcar_mipi_dsi_set(dsi, PPICLCR, PPICLCR_TXREQHS);
+       ret = read_poll_timeout(rcar_mipi_dsi_read, status,
+                               status & PPICLSR_TOHS,
+                               2000, 10000, false, dsi, PPICLSR);
+       if (ret < 0) {
+               dev_err(dsi->dev, "failed to enable HS clock\n");
+               return ret;
+       }
+       rcar_mipi_dsi_set(dsi, PPICLSCR, PPICLSCR_TOHS);
+       return 0;
+ }
+ static int rcar_mipi_dsi_start_video(struct rcar_mipi_dsi *dsi)
+ {
+       u32 status;
+       int ret;
+       /* Wait for the link to be ready. */
+       ret = read_poll_timeout(rcar_mipi_dsi_read, status,
+                               !(status & (LINKSR_LPBUSY | LINKSR_HSBUSY)),
+                               2000, 10000, false, dsi, LINKSR);
+       if (ret < 0) {
+               dev_err(dsi->dev, "Link failed to become ready\n");
+               return ret;
+       }
+       /* De-assert video FIFO clear. */
+       rcar_mipi_dsi_clr(dsi, TXVMCR, TXVMCR_VFCLR);
+       ret = read_poll_timeout(rcar_mipi_dsi_read, status,
+                               status & TXVMSR_VFRDY,
+                               2000, 10000, false, dsi, TXVMSR);
+       if (ret < 0) {
+               dev_err(dsi->dev, "Failed to de-assert video FIFO clear\n");
+               return ret;
+       }
+       /* Enable transmission in video mode. */
+       rcar_mipi_dsi_set(dsi, TXVMCR, TXVMCR_EN_VIDEO);
+       ret = read_poll_timeout(rcar_mipi_dsi_read, status,
+                               status & TXVMSR_RDY,
+                               2000, 10000, false, dsi, TXVMSR);
+       if (ret < 0) {
+               dev_err(dsi->dev, "Failed to enable video transmission\n");
+               return ret;
+       }
+       return 0;
+ }
+ static void rcar_mipi_dsi_stop_video(struct rcar_mipi_dsi *dsi)
+ {
+       u32 status;
+       int ret;
+       /* Disable transmission in video mode. */
+       rcar_mipi_dsi_clr(dsi, TXVMCR, TXVMCR_EN_VIDEO);
+       ret = read_poll_timeout(rcar_mipi_dsi_read, status,
+                               !(status & TXVMSR_ACT),
+                               2000, 100000, false, dsi, TXVMSR);
+       if (ret < 0) {
+               dev_err(dsi->dev, "Failed to disable video transmission\n");
+               return;
+       }
+       /* Assert video FIFO clear. */
+       rcar_mipi_dsi_set(dsi, TXVMCR, TXVMCR_VFCLR);
+       ret = read_poll_timeout(rcar_mipi_dsi_read, status,
+                               !(status & TXVMSR_VFRDY),
+                               2000, 100000, false, dsi, TXVMSR);
+       if (ret < 0) {
+               dev_err(dsi->dev, "Failed to assert video FIFO clear\n");
+               return;
+       }
+ }
+ /* -----------------------------------------------------------------------------
+  * Bridge
+  */
+ static int rcar_mipi_dsi_attach(struct drm_bridge *bridge,
+                               enum drm_bridge_attach_flags flags)
+ {
+       struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge);
+       return drm_bridge_attach(bridge->encoder, dsi->next_bridge, bridge,
+                                flags);
+ }
+ static void rcar_mipi_dsi_atomic_enable(struct drm_bridge *bridge,
+                                       struct drm_bridge_state *old_bridge_state)
+ {
+       struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge);
+       rcar_mipi_dsi_start_video(dsi);
+ }
+ static void rcar_mipi_dsi_atomic_disable(struct drm_bridge *bridge,
+                                        struct drm_bridge_state *old_bridge_state)
+ {
+       struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge);
+       rcar_mipi_dsi_stop_video(dsi);
+ }
+ void rcar_mipi_dsi_pclk_enable(struct drm_bridge *bridge,
+                              struct drm_atomic_state *state)
+ {
+       struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge);
+       const struct drm_display_mode *mode;
+       struct drm_connector *connector;
+       struct drm_crtc *crtc;
+       int ret;
+       connector = drm_atomic_get_new_connector_for_encoder(state,
+                                                            bridge->encoder);
+       crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
+       mode = &drm_atomic_get_new_crtc_state(state, crtc)->adjusted_mode;
+       ret = rcar_mipi_dsi_clk_enable(dsi);
+       if (ret < 0) {
+               dev_err(dsi->dev, "failed to enable DSI clocks\n");
+               return;
+       }
+       ret = rcar_mipi_dsi_startup(dsi, mode);
+       if (ret < 0)
+               goto err_dsi_startup;
+       rcar_mipi_dsi_set_display_timing(dsi, mode);
+       ret = rcar_mipi_dsi_start_hs_clock(dsi);
+       if (ret < 0)
+               goto err_dsi_start_hs;
+       return;
+ err_dsi_start_hs:
+       rcar_mipi_dsi_shutdown(dsi);
+ err_dsi_startup:
+       rcar_mipi_dsi_clk_disable(dsi);
+ }
+ EXPORT_SYMBOL_GPL(rcar_mipi_dsi_pclk_enable);
+ void rcar_mipi_dsi_pclk_disable(struct drm_bridge *bridge)
+ {
+       struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge);
+       rcar_mipi_dsi_shutdown(dsi);
+       rcar_mipi_dsi_clk_disable(dsi);
+ }
+ EXPORT_SYMBOL_GPL(rcar_mipi_dsi_pclk_disable);
+ static enum drm_mode_status
+ rcar_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge,
+                               const struct drm_display_info *info,
+                               const struct drm_display_mode *mode)
+ {
+       if (mode->clock > 297000)
+               return MODE_CLOCK_HIGH;
+       return MODE_OK;
+ }
+ static const struct drm_bridge_funcs rcar_mipi_dsi_bridge_ops = {
+       .attach = rcar_mipi_dsi_attach,
+       .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+       .atomic_reset = drm_atomic_helper_bridge_reset,
+       .atomic_enable = rcar_mipi_dsi_atomic_enable,
+       .atomic_disable = rcar_mipi_dsi_atomic_disable,
+       .mode_valid = rcar_mipi_dsi_bridge_mode_valid,
+ };
+ /* -----------------------------------------------------------------------------
+  * Host setting
+  */
+ static int rcar_mipi_dsi_host_attach(struct mipi_dsi_host *host,
+                                    struct mipi_dsi_device *device)
+ {
+       struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host);
+       int ret;
+       if (device->lanes > dsi->num_data_lanes)
+               return -EINVAL;
+       dsi->lanes = device->lanes;
+       dsi->format = device->format;
+       dsi->next_bridge = devm_drm_of_get_bridge(dsi->dev, dsi->dev->of_node,
+                                                 1, 0);
+       if (IS_ERR(dsi->next_bridge)) {
+               ret = PTR_ERR(dsi->next_bridge);
+               dev_err(dsi->dev, "failed to get next bridge: %d\n", ret);
+               return ret;
+       }
+       /* Initialize the DRM bridge. */
+       dsi->bridge.funcs = &rcar_mipi_dsi_bridge_ops;
+       dsi->bridge.of_node = dsi->dev->of_node;
+       drm_bridge_add(&dsi->bridge);
+       return 0;
+ }
+ static int rcar_mipi_dsi_host_detach(struct mipi_dsi_host *host,
+                                       struct mipi_dsi_device *device)
+ {
+       struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host);
+       drm_bridge_remove(&dsi->bridge);
+       return 0;
+ }
+ static const struct mipi_dsi_host_ops rcar_mipi_dsi_host_ops = {
+       .attach = rcar_mipi_dsi_host_attach,
+       .detach = rcar_mipi_dsi_host_detach,
+ };
+ /* -----------------------------------------------------------------------------
+  * Probe & Remove
+  */
+ static int rcar_mipi_dsi_parse_dt(struct rcar_mipi_dsi *dsi)
+ {
+       int ret;
+       ret = drm_of_get_data_lanes_count_ep(dsi->dev->of_node, 1, 0, 1, 4);
+       if (ret < 0) {
+               dev_err(dsi->dev, "missing or invalid data-lanes property\n");
+               return ret;
+       }
+       dsi->num_data_lanes = ret;
+       return 0;
+ }
+ static struct clk *rcar_mipi_dsi_get_clock(struct rcar_mipi_dsi *dsi,
+                                          const char *name,
+                                          bool optional)
+ {
+       struct clk *clk;
+       clk = devm_clk_get(dsi->dev, name);
+       if (!IS_ERR(clk))
+               return clk;
+       if (PTR_ERR(clk) == -ENOENT && optional)
+               return NULL;
+       dev_err_probe(dsi->dev, PTR_ERR(clk), "failed to get %s clock\n",
+                     name ? name : "module");
+       return clk;
+ }
+ static int rcar_mipi_dsi_get_clocks(struct rcar_mipi_dsi *dsi)
+ {
+       dsi->clocks.mod = rcar_mipi_dsi_get_clock(dsi, NULL, false);
+       if (IS_ERR(dsi->clocks.mod))
+               return PTR_ERR(dsi->clocks.mod);
+       dsi->clocks.pll = rcar_mipi_dsi_get_clock(dsi, "pll", true);
+       if (IS_ERR(dsi->clocks.pll))
+               return PTR_ERR(dsi->clocks.pll);
+       dsi->clocks.dsi = rcar_mipi_dsi_get_clock(dsi, "dsi", true);
+       if (IS_ERR(dsi->clocks.dsi))
+               return PTR_ERR(dsi->clocks.dsi);
+       if (!dsi->clocks.pll && !dsi->clocks.dsi) {
+               dev_err(dsi->dev, "no input clock (pll, dsi)\n");
+               return -EINVAL;
+       }
+       return 0;
+ }
+ static int rcar_mipi_dsi_probe(struct platform_device *pdev)
+ {
+       struct rcar_mipi_dsi *dsi;
+       struct resource *mem;
+       int ret;
+       dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL);
+       if (dsi == NULL)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, dsi);
+       dsi->dev = &pdev->dev;
+       dsi->info = of_device_get_match_data(&pdev->dev);
+       ret = rcar_mipi_dsi_parse_dt(dsi);
+       if (ret < 0)
+               return ret;
+       /* Acquire resources. */
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       dsi->mmio = devm_ioremap_resource(dsi->dev, mem);
+       if (IS_ERR(dsi->mmio))
+               return PTR_ERR(dsi->mmio);
+       ret = rcar_mipi_dsi_get_clocks(dsi);
+       if (ret < 0)
+               return ret;
+       dsi->rstc = devm_reset_control_get(dsi->dev, NULL);
+       if (IS_ERR(dsi->rstc)) {
+               dev_err(dsi->dev, "failed to get cpg reset\n");
+               return PTR_ERR(dsi->rstc);
+       }
+       /* Initialize the DSI host. */
+       dsi->host.dev = dsi->dev;
+       dsi->host.ops = &rcar_mipi_dsi_host_ops;
+       ret = mipi_dsi_host_register(&dsi->host);
+       if (ret < 0)
+               return ret;
+       return 0;
+ }
 -
 -      return 0;
++static void rcar_mipi_dsi_remove(struct platform_device *pdev)
+ {
+       struct rcar_mipi_dsi *dsi = platform_get_drvdata(pdev);
+       mipi_dsi_host_unregister(&dsi->host);
 -      .remove         = rcar_mipi_dsi_remove,
+ }
+ static const struct rcar_mipi_dsi_device_info v3u_data = {
+       .model = RCAR_DSI_V3U,
+       .clk_cfg = dsi_clk_cfg_v3u,
+       .clockset2_m_offset = 2,
+       .n_min = 3,
+       .n_max = 8,
+       .n_mul = 1,
+       .fpfd_min = MHZ(2),
+       .fpfd_max = MHZ(8),
+       .m_min = 64,
+       .m_max = 625,
+       .fout_min = MHZ(320),
+       .fout_max = MHZ(1250),
+ };
+ static const struct rcar_mipi_dsi_device_info v4h_data = {
+       .model = RCAR_DSI_V4H,
+       .clk_cfg = dsi_clk_cfg_v4h,
+       .clockset2_m_offset = 0,
+       .n_min = 1,
+       .n_max = 8,
+       .n_mul = 2,
+       .fpfd_min = MHZ(8),
+       .fpfd_max = MHZ(24),
+       .m_min = 167,
+       .m_max = 1000,
+       .fout_min = MHZ(2000),
+       .fout_max = MHZ(4000),
+ };
+ static const struct of_device_id rcar_mipi_dsi_of_table[] = {
+       { .compatible = "renesas,r8a779a0-dsi-csi2-tx", .data = &v3u_data },
+       { .compatible = "renesas,r8a779g0-dsi-csi2-tx", .data = &v4h_data },
+       { }
+ };
+ MODULE_DEVICE_TABLE(of, rcar_mipi_dsi_of_table);
+ static struct platform_driver rcar_mipi_dsi_platform_driver = {
+       .probe          = rcar_mipi_dsi_probe,
++      .remove_new     = rcar_mipi_dsi_remove,
+       .driver         = {
+               .name   = "rcar-mipi-dsi",
+               .of_match_table = rcar_mipi_dsi_of_table,
+       },
+ };
+ module_platform_driver(rcar_mipi_dsi_platform_driver);
+ MODULE_DESCRIPTION("Renesas R-Car MIPI DSI Encoder Driver");
+ MODULE_LICENSE("GPL");
index 0000000000000000000000000000000000000000,aa95b85a29643710e5cdad5cf57c37ce5274cdbc..a97fc4c5d1c8b65d3dc337c648bb267dcf66d034
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,816 +1,814 @@@
 -static int rzg2l_mipi_dsi_remove(struct platform_device *pdev)
+ // SPDX-License-Identifier: GPL-2.0
+ /*
+  * RZ/G2L MIPI DSI Encoder Driver
+  *
+  * Copyright (C) 2022 Renesas Electronics Corporation
+  */
+ #include <linux/clk.h>
+ #include <linux/delay.h>
+ #include <linux/io.h>
+ #include <linux/iopoll.h>
+ #include <linux/module.h>
+ #include <linux/of.h>
+ #include <linux/of_device.h>
+ #include <linux/of_graph.h>
+ #include <linux/platform_device.h>
+ #include <linux/pm_runtime.h>
+ #include <linux/reset.h>
+ #include <linux/slab.h>
+ #include <drm/drm_atomic.h>
+ #include <drm/drm_atomic_helper.h>
+ #include <drm/drm_bridge.h>
+ #include <drm/drm_mipi_dsi.h>
+ #include <drm/drm_of.h>
+ #include <drm/drm_panel.h>
+ #include <drm/drm_probe_helper.h>
+ #include "rzg2l_mipi_dsi_regs.h"
+ struct rzg2l_mipi_dsi {
+       struct device *dev;
+       void __iomem *mmio;
+       struct reset_control *rstc;
+       struct reset_control *arstc;
+       struct reset_control *prstc;
+       struct mipi_dsi_host host;
+       struct drm_bridge bridge;
+       struct drm_bridge *next_bridge;
+       struct clk *vclk;
+       enum mipi_dsi_pixel_format format;
+       unsigned int num_data_lanes;
+       unsigned int lanes;
+       unsigned long mode_flags;
+ };
+ static inline struct rzg2l_mipi_dsi *
+ bridge_to_rzg2l_mipi_dsi(struct drm_bridge *bridge)
+ {
+       return container_of(bridge, struct rzg2l_mipi_dsi, bridge);
+ }
+ static inline struct rzg2l_mipi_dsi *
+ host_to_rzg2l_mipi_dsi(struct mipi_dsi_host *host)
+ {
+       return container_of(host, struct rzg2l_mipi_dsi, host);
+ }
+ struct rzg2l_mipi_dsi_timings {
+       unsigned long hsfreq_max;
+       u32 t_init;
+       u32 tclk_prepare;
+       u32 ths_prepare;
+       u32 tclk_zero;
+       u32 tclk_pre;
+       u32 tclk_post;
+       u32 tclk_trail;
+       u32 ths_zero;
+       u32 ths_trail;
+       u32 ths_exit;
+       u32 tlpx;
+ };
+ static const struct rzg2l_mipi_dsi_timings rzg2l_mipi_dsi_global_timings[] = {
+       {
+               .hsfreq_max = 80000,
+               .t_init = 79801,
+               .tclk_prepare = 8,
+               .ths_prepare = 13,
+               .tclk_zero = 33,
+               .tclk_pre = 24,
+               .tclk_post = 94,
+               .tclk_trail = 10,
+               .ths_zero = 23,
+               .ths_trail = 17,
+               .ths_exit = 13,
+               .tlpx = 6,
+       },
+       {
+               .hsfreq_max = 125000,
+               .t_init = 79801,
+               .tclk_prepare = 8,
+               .ths_prepare = 12,
+               .tclk_zero = 33,
+               .tclk_pre = 15,
+               .tclk_post = 94,
+               .tclk_trail = 10,
+               .ths_zero = 23,
+               .ths_trail = 17,
+               .ths_exit = 13,
+               .tlpx = 6,
+       },
+       {
+               .hsfreq_max = 250000,
+               .t_init = 79801,
+               .tclk_prepare = 8,
+               .ths_prepare = 12,
+               .tclk_zero = 33,
+               .tclk_pre = 13,
+               .tclk_post = 94,
+               .tclk_trail = 10,
+               .ths_zero = 23,
+               .ths_trail = 16,
+               .ths_exit = 13,
+               .tlpx = 6,
+       },
+       {
+               .hsfreq_max = 360000,
+               .t_init = 79801,
+               .tclk_prepare = 8,
+               .ths_prepare = 10,
+               .tclk_zero = 33,
+               .tclk_pre = 4,
+               .tclk_post = 35,
+               .tclk_trail = 7,
+               .ths_zero = 16,
+               .ths_trail = 9,
+               .ths_exit = 13,
+               .tlpx = 6,
+       },
+       {
+               .hsfreq_max = 720000,
+               .t_init = 79801,
+               .tclk_prepare = 8,
+               .ths_prepare = 9,
+               .tclk_zero = 33,
+               .tclk_pre = 4,
+               .tclk_post = 35,
+               .tclk_trail = 7,
+               .ths_zero = 16,
+               .ths_trail = 9,
+               .ths_exit = 13,
+               .tlpx = 6,
+       },
+       {
+               .hsfreq_max = 1500000,
+               .t_init = 79801,
+               .tclk_prepare = 8,
+               .ths_prepare = 9,
+               .tclk_zero = 33,
+               .tclk_pre = 4,
+               .tclk_post = 35,
+               .tclk_trail = 7,
+               .ths_zero = 16,
+               .ths_trail = 9,
+               .ths_exit = 13,
+               .tlpx = 6,
+       },
+ };
+ static void rzg2l_mipi_dsi_phy_write(struct rzg2l_mipi_dsi *dsi, u32 reg, u32 data)
+ {
+       iowrite32(data, dsi->mmio + reg);
+ }
+ static void rzg2l_mipi_dsi_link_write(struct rzg2l_mipi_dsi *dsi, u32 reg, u32 data)
+ {
+       iowrite32(data, dsi->mmio + LINK_REG_OFFSET + reg);
+ }
+ static u32 rzg2l_mipi_dsi_phy_read(struct rzg2l_mipi_dsi *dsi, u32 reg)
+ {
+       return ioread32(dsi->mmio + reg);
+ }
+ static u32 rzg2l_mipi_dsi_link_read(struct rzg2l_mipi_dsi *dsi, u32 reg)
+ {
+       return ioread32(dsi->mmio + LINK_REG_OFFSET + reg);
+ }
+ /* -----------------------------------------------------------------------------
+  * Hardware Setup
+  */
+ static int rzg2l_mipi_dsi_dphy_init(struct rzg2l_mipi_dsi *dsi,
+                                   unsigned long hsfreq)
+ {
+       const struct rzg2l_mipi_dsi_timings *dphy_timings;
+       unsigned int i;
+       u32 dphyctrl0;
+       u32 dphytim0;
+       u32 dphytim1;
+       u32 dphytim2;
+       u32 dphytim3;
+       int ret;
+       /* All DSI global operation timings are set with recommended setting */
+       for (i = 0; i < ARRAY_SIZE(rzg2l_mipi_dsi_global_timings); ++i) {
+               dphy_timings = &rzg2l_mipi_dsi_global_timings[i];
+               if (hsfreq <= dphy_timings->hsfreq_max)
+                       break;
+       }
+       /* Initializing DPHY before accessing LINK */
+       dphyctrl0 = DSIDPHYCTRL0_CAL_EN_HSRX_OFS | DSIDPHYCTRL0_CMN_MASTER_EN |
+                   DSIDPHYCTRL0_RE_VDD_DETVCCQLV18 | DSIDPHYCTRL0_EN_BGR;
+       rzg2l_mipi_dsi_phy_write(dsi, DSIDPHYCTRL0, dphyctrl0);
+       usleep_range(20, 30);
+       dphyctrl0 |= DSIDPHYCTRL0_EN_LDO1200;
+       rzg2l_mipi_dsi_phy_write(dsi, DSIDPHYCTRL0, dphyctrl0);
+       usleep_range(10, 20);
+       dphytim0 = DSIDPHYTIM0_TCLK_MISS(0) |
+                  DSIDPHYTIM0_T_INIT(dphy_timings->t_init);
+       dphytim1 = DSIDPHYTIM1_THS_PREPARE(dphy_timings->ths_prepare) |
+                  DSIDPHYTIM1_TCLK_PREPARE(dphy_timings->tclk_prepare) |
+                  DSIDPHYTIM1_THS_SETTLE(0) |
+                  DSIDPHYTIM1_TCLK_SETTLE(0);
+       dphytim2 = DSIDPHYTIM2_TCLK_TRAIL(dphy_timings->tclk_trail) |
+                  DSIDPHYTIM2_TCLK_POST(dphy_timings->tclk_post) |
+                  DSIDPHYTIM2_TCLK_PRE(dphy_timings->tclk_pre) |
+                  DSIDPHYTIM2_TCLK_ZERO(dphy_timings->tclk_zero);
+       dphytim3 = DSIDPHYTIM3_TLPX(dphy_timings->tlpx) |
+                  DSIDPHYTIM3_THS_EXIT(dphy_timings->ths_exit) |
+                  DSIDPHYTIM3_THS_TRAIL(dphy_timings->ths_trail) |
+                  DSIDPHYTIM3_THS_ZERO(dphy_timings->ths_zero);
+       rzg2l_mipi_dsi_phy_write(dsi, DSIDPHYTIM0, dphytim0);
+       rzg2l_mipi_dsi_phy_write(dsi, DSIDPHYTIM1, dphytim1);
+       rzg2l_mipi_dsi_phy_write(dsi, DSIDPHYTIM2, dphytim2);
+       rzg2l_mipi_dsi_phy_write(dsi, DSIDPHYTIM3, dphytim3);
+       ret = reset_control_deassert(dsi->rstc);
+       if (ret < 0)
+               return ret;
+       udelay(1);
+       return 0;
+ }
+ static void rzg2l_mipi_dsi_dphy_exit(struct rzg2l_mipi_dsi *dsi)
+ {
+       u32 dphyctrl0;
+       dphyctrl0 = rzg2l_mipi_dsi_phy_read(dsi, DSIDPHYCTRL0);
+       dphyctrl0 &= ~(DSIDPHYCTRL0_EN_LDO1200 | DSIDPHYCTRL0_EN_BGR);
+       rzg2l_mipi_dsi_phy_write(dsi, DSIDPHYCTRL0, dphyctrl0);
+       reset_control_assert(dsi->rstc);
+ }
+ static int rzg2l_mipi_dsi_startup(struct rzg2l_mipi_dsi *dsi,
+                                 const struct drm_display_mode *mode)
+ {
+       unsigned long hsfreq;
+       unsigned int bpp;
+       u32 txsetr;
+       u32 clstptsetr;
+       u32 lptrnstsetr;
+       u32 clkkpt;
+       u32 clkbfht;
+       u32 clkstpt;
+       u32 golpbkt;
+       int ret;
+       /*
+        * Relationship between hsclk and vclk must follow
+        * vclk * bpp = hsclk * 8 * lanes
+        * where vclk: video clock (Hz)
+        *       bpp: video pixel bit depth
+        *       hsclk: DSI HS Byte clock frequency (Hz)
+        *       lanes: number of data lanes
+        *
+        * hsclk(bit) = hsclk(byte) * 8
+        */
+       bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+       hsfreq = (mode->clock * bpp * 8) / (8 * dsi->lanes);
+       ret = pm_runtime_resume_and_get(dsi->dev);
+       if (ret < 0)
+               return ret;
+       clk_set_rate(dsi->vclk, mode->clock * 1000);
+       ret = rzg2l_mipi_dsi_dphy_init(dsi, hsfreq);
+       if (ret < 0)
+               goto err_phy;
+       /* Enable Data lanes and Clock lanes */
+       txsetr = TXSETR_DLEN | TXSETR_NUMLANEUSE(dsi->lanes - 1) | TXSETR_CLEN;
+       rzg2l_mipi_dsi_link_write(dsi, TXSETR, txsetr);
+       /*
+        * Global timings characteristic depends on high speed Clock Frequency
+        * Currently MIPI DSI-IF just supports maximum FHD@60 with:
+        * - videoclock = 148.5 (MHz)
+        * - bpp: maximum 24bpp
+        * - data lanes: maximum 4 lanes
+        * Therefore maximum hsclk will be 891 Mbps.
+        */
+       if (hsfreq > 445500) {
+               clkkpt = 12;
+               clkbfht = 15;
+               clkstpt = 48;
+               golpbkt = 75;
+       } else if (hsfreq > 250000) {
+               clkkpt = 7;
+               clkbfht = 8;
+               clkstpt = 27;
+               golpbkt = 40;
+       } else {
+               clkkpt = 8;
+               clkbfht = 6;
+               clkstpt = 24;
+               golpbkt = 29;
+       }
+       clstptsetr = CLSTPTSETR_CLKKPT(clkkpt) | CLSTPTSETR_CLKBFHT(clkbfht) |
+                    CLSTPTSETR_CLKSTPT(clkstpt);
+       rzg2l_mipi_dsi_link_write(dsi, CLSTPTSETR, clstptsetr);
+       lptrnstsetr = LPTRNSTSETR_GOLPBKT(golpbkt);
+       rzg2l_mipi_dsi_link_write(dsi, LPTRNSTSETR, lptrnstsetr);
+       return 0;
+ err_phy:
+       rzg2l_mipi_dsi_dphy_exit(dsi);
+       pm_runtime_put(dsi->dev);
+       return ret;
+ }
+ static void rzg2l_mipi_dsi_stop(struct rzg2l_mipi_dsi *dsi)
+ {
+       rzg2l_mipi_dsi_dphy_exit(dsi);
+       pm_runtime_put(dsi->dev);
+ }
+ static void rzg2l_mipi_dsi_set_display_timing(struct rzg2l_mipi_dsi *dsi,
+                                             const struct drm_display_mode *mode)
+ {
+       u32 vich1ppsetr;
+       u32 vich1vssetr;
+       u32 vich1vpsetr;
+       u32 vich1hssetr;
+       u32 vich1hpsetr;
+       int dsi_format;
+       u32 delay[2];
+       u8 index;
+       /* Configuration for Pixel Packet */
+       dsi_format = mipi_dsi_pixel_format_to_bpp(dsi->format);
+       switch (dsi_format) {
+       case 24:
+               vich1ppsetr = VICH1PPSETR_DT_RGB24;
+               break;
+       case 18:
+               vich1ppsetr = VICH1PPSETR_DT_RGB18;
+               break;
+       }
+       if ((dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) &&
+           !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST))
+               vich1ppsetr |= VICH1PPSETR_TXESYNC_PULSE;
+       rzg2l_mipi_dsi_link_write(dsi, VICH1PPSETR, vich1ppsetr);
+       /* Configuration for Video Parameters */
+       vich1vssetr = VICH1VSSETR_VACTIVE(mode->vdisplay) |
+                     VICH1VSSETR_VSA(mode->vsync_end - mode->vsync_start);
+       vich1vssetr |= (mode->flags & DRM_MODE_FLAG_PVSYNC) ?
+                       VICH1VSSETR_VSPOL_HIGH : VICH1VSSETR_VSPOL_LOW;
+       vich1vpsetr = VICH1VPSETR_VFP(mode->vsync_start - mode->vdisplay) |
+                     VICH1VPSETR_VBP(mode->vtotal - mode->vsync_end);
+       vich1hssetr = VICH1HSSETR_HACTIVE(mode->hdisplay) |
+                     VICH1HSSETR_HSA(mode->hsync_end - mode->hsync_start);
+       vich1hssetr |= (mode->flags & DRM_MODE_FLAG_PHSYNC) ?
+                       VICH1HSSETR_HSPOL_HIGH : VICH1HSSETR_HSPOL_LOW;
+       vich1hpsetr = VICH1HPSETR_HFP(mode->hsync_start - mode->hdisplay) |
+                     VICH1HPSETR_HBP(mode->htotal - mode->hsync_end);
+       rzg2l_mipi_dsi_link_write(dsi, VICH1VSSETR, vich1vssetr);
+       rzg2l_mipi_dsi_link_write(dsi, VICH1VPSETR, vich1vpsetr);
+       rzg2l_mipi_dsi_link_write(dsi, VICH1HSSETR, vich1hssetr);
+       rzg2l_mipi_dsi_link_write(dsi, VICH1HPSETR, vich1hpsetr);
+       /*
+        * Configuration for Delay Value
+        * Delay value based on 2 ranges of video clock.
+        * 74.25MHz is videoclock of HD@60p or FHD@30p
+        */
+       if (mode->clock > 74250) {
+               delay[0] = 231;
+               delay[1] = 216;
+       } else {
+               delay[0] = 220;
+               delay[1] = 212;
+       }
+       if (dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)
+               index = 0;
+       else
+               index = 1;
+       rzg2l_mipi_dsi_link_write(dsi, VICH1SET1R,
+                                 VICH1SET1R_DLY(delay[index]));
+ }
+ static int rzg2l_mipi_dsi_start_hs_clock(struct rzg2l_mipi_dsi *dsi)
+ {
+       bool is_clk_cont;
+       u32 hsclksetr;
+       u32 status;
+       int ret;
+       is_clk_cont = !(dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS);
+       /* Start HS clock */
+       hsclksetr = HSCLKSETR_HSCLKRUN_HS | (is_clk_cont ?
+                                            HSCLKSETR_HSCLKMODE_CONT :
+                                            HSCLKSETR_HSCLKMODE_NON_CONT);
+       rzg2l_mipi_dsi_link_write(dsi, HSCLKSETR, hsclksetr);
+       if (is_clk_cont) {
+               ret = read_poll_timeout(rzg2l_mipi_dsi_link_read, status,
+                                       status & PLSR_CLLP2HS,
+                                       2000, 20000, false, dsi, PLSR);
+               if (ret < 0) {
+                       dev_err(dsi->dev, "failed to start HS clock\n");
+                       return ret;
+               }
+       }
+       dev_dbg(dsi->dev, "Start High Speed Clock with %s clock mode",
+               is_clk_cont ? "continuous" : "non-continuous");
+       return 0;
+ }
+ static int rzg2l_mipi_dsi_stop_hs_clock(struct rzg2l_mipi_dsi *dsi)
+ {
+       bool is_clk_cont;
+       u32 status;
+       int ret;
+       is_clk_cont = !(dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS);
+       /* Stop HS clock */
+       rzg2l_mipi_dsi_link_write(dsi, HSCLKSETR,
+                                 is_clk_cont ? HSCLKSETR_HSCLKMODE_CONT :
+                                 HSCLKSETR_HSCLKMODE_NON_CONT);
+       if (is_clk_cont) {
+               ret = read_poll_timeout(rzg2l_mipi_dsi_link_read, status,
+                                       status & PLSR_CLHS2LP,
+                                       2000, 20000, false, dsi, PLSR);
+               if (ret < 0) {
+                       dev_err(dsi->dev, "failed to stop HS clock\n");
+                       return ret;
+               }
+       }
+       return 0;
+ }
+ static int rzg2l_mipi_dsi_start_video(struct rzg2l_mipi_dsi *dsi)
+ {
+       u32 vich1set0r;
+       u32 status;
+       int ret;
+       /* Configuration for Blanking sequence and start video input*/
+       vich1set0r = VICH1SET0R_HFPNOLP | VICH1SET0R_HBPNOLP |
+                    VICH1SET0R_HSANOLP | VICH1SET0R_VSTART;
+       rzg2l_mipi_dsi_link_write(dsi, VICH1SET0R, vich1set0r);
+       ret = read_poll_timeout(rzg2l_mipi_dsi_link_read, status,
+                               status & VICH1SR_VIRDY,
+                               2000, 20000, false, dsi, VICH1SR);
+       if (ret < 0)
+               dev_err(dsi->dev, "Failed to start video signal input\n");
+       return ret;
+ }
+ static int rzg2l_mipi_dsi_stop_video(struct rzg2l_mipi_dsi *dsi)
+ {
+       u32 status;
+       int ret;
+       rzg2l_mipi_dsi_link_write(dsi, VICH1SET0R, VICH1SET0R_VSTPAFT);
+       ret = read_poll_timeout(rzg2l_mipi_dsi_link_read, status,
+                               (status & VICH1SR_STOP) && (!(status & VICH1SR_RUNNING)),
+                               2000, 20000, false, dsi, VICH1SR);
+       if (ret < 0)
+               goto err;
+       ret = read_poll_timeout(rzg2l_mipi_dsi_link_read, status,
+                               !(status & LINKSR_HSBUSY),
+                               2000, 20000, false, dsi, LINKSR);
+       if (ret < 0)
+               goto err;
+       return 0;
+ err:
+       dev_err(dsi->dev, "Failed to stop video signal input\n");
+       return ret;
+ }
+ /* -----------------------------------------------------------------------------
+  * Bridge
+  */
+ static int rzg2l_mipi_dsi_attach(struct drm_bridge *bridge,
+                                enum drm_bridge_attach_flags flags)
+ {
+       struct rzg2l_mipi_dsi *dsi = bridge_to_rzg2l_mipi_dsi(bridge);
+       return drm_bridge_attach(bridge->encoder, dsi->next_bridge, bridge,
+                                flags);
+ }
+ static void rzg2l_mipi_dsi_atomic_enable(struct drm_bridge *bridge,
+                                        struct drm_bridge_state *old_bridge_state)
+ {
+       struct drm_atomic_state *state = old_bridge_state->base.state;
+       struct rzg2l_mipi_dsi *dsi = bridge_to_rzg2l_mipi_dsi(bridge);
+       const struct drm_display_mode *mode;
+       struct drm_connector *connector;
+       struct drm_crtc *crtc;
+       int ret;
+       connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
+       crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
+       mode = &drm_atomic_get_new_crtc_state(state, crtc)->adjusted_mode;
+       ret = rzg2l_mipi_dsi_startup(dsi, mode);
+       if (ret < 0)
+               return;
+       rzg2l_mipi_dsi_set_display_timing(dsi, mode);
+       ret = rzg2l_mipi_dsi_start_hs_clock(dsi);
+       if (ret < 0)
+               goto err_stop;
+       ret = rzg2l_mipi_dsi_start_video(dsi);
+       if (ret < 0)
+               goto err_stop_clock;
+       return;
+ err_stop_clock:
+       rzg2l_mipi_dsi_stop_hs_clock(dsi);
+ err_stop:
+       rzg2l_mipi_dsi_stop(dsi);
+ }
+ static void rzg2l_mipi_dsi_atomic_disable(struct drm_bridge *bridge,
+                                         struct drm_bridge_state *old_bridge_state)
+ {
+       struct rzg2l_mipi_dsi *dsi = bridge_to_rzg2l_mipi_dsi(bridge);
+       rzg2l_mipi_dsi_stop_video(dsi);
+       rzg2l_mipi_dsi_stop_hs_clock(dsi);
+       rzg2l_mipi_dsi_stop(dsi);
+ }
+ static enum drm_mode_status
+ rzg2l_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge,
+                                const struct drm_display_info *info,
+                                const struct drm_display_mode *mode)
+ {
+       if (mode->clock > 148500)
+               return MODE_CLOCK_HIGH;
+       return MODE_OK;
+ }
+ static const struct drm_bridge_funcs rzg2l_mipi_dsi_bridge_ops = {
+       .attach = rzg2l_mipi_dsi_attach,
+       .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+       .atomic_reset = drm_atomic_helper_bridge_reset,
+       .atomic_enable = rzg2l_mipi_dsi_atomic_enable,
+       .atomic_disable = rzg2l_mipi_dsi_atomic_disable,
+       .mode_valid = rzg2l_mipi_dsi_bridge_mode_valid,
+ };
+ /* -----------------------------------------------------------------------------
+  * Host setting
+  */
+ static int rzg2l_mipi_dsi_host_attach(struct mipi_dsi_host *host,
+                                     struct mipi_dsi_device *device)
+ {
+       struct rzg2l_mipi_dsi *dsi = host_to_rzg2l_mipi_dsi(host);
+       int ret;
+       if (device->lanes > dsi->num_data_lanes) {
+               dev_err(dsi->dev,
+                       "Number of lines of device (%u) exceeds host (%u)\n",
+                       device->lanes, dsi->num_data_lanes);
+               return -EINVAL;
+       }
+       switch (mipi_dsi_pixel_format_to_bpp(device->format)) {
+       case 24:
+       case 18:
+               break;
+       default:
+               dev_err(dsi->dev, "Unsupported format 0x%04x\n", device->format);
+               return -EINVAL;
+       }
+       dsi->lanes = device->lanes;
+       dsi->format = device->format;
+       dsi->mode_flags = device->mode_flags;
+       dsi->next_bridge = devm_drm_of_get_bridge(dsi->dev, dsi->dev->of_node,
+                                                 1, 0);
+       if (IS_ERR(dsi->next_bridge)) {
+               ret = PTR_ERR(dsi->next_bridge);
+               dev_err(dsi->dev, "failed to get next bridge: %d\n", ret);
+               return ret;
+       }
+       drm_bridge_add(&dsi->bridge);
+       return 0;
+ }
+ static int rzg2l_mipi_dsi_host_detach(struct mipi_dsi_host *host,
+                                     struct mipi_dsi_device *device)
+ {
+       struct rzg2l_mipi_dsi *dsi = host_to_rzg2l_mipi_dsi(host);
+       drm_bridge_remove(&dsi->bridge);
+       return 0;
+ }
+ static const struct mipi_dsi_host_ops rzg2l_mipi_dsi_host_ops = {
+       .attach = rzg2l_mipi_dsi_host_attach,
+       .detach = rzg2l_mipi_dsi_host_detach,
+ };
+ /* -----------------------------------------------------------------------------
+  * Power Management
+  */
+ static int __maybe_unused rzg2l_mipi_pm_runtime_suspend(struct device *dev)
+ {
+       struct rzg2l_mipi_dsi *dsi = dev_get_drvdata(dev);
+       reset_control_assert(dsi->prstc);
+       reset_control_assert(dsi->arstc);
+       return 0;
+ }
+ static int __maybe_unused rzg2l_mipi_pm_runtime_resume(struct device *dev)
+ {
+       struct rzg2l_mipi_dsi *dsi = dev_get_drvdata(dev);
+       int ret;
+       ret = reset_control_deassert(dsi->arstc);
+       if (ret < 0)
+               return ret;
+       ret = reset_control_deassert(dsi->prstc);
+       if (ret < 0)
+               reset_control_assert(dsi->arstc);
+       return ret;
+ }
+ static const struct dev_pm_ops rzg2l_mipi_pm_ops = {
+       SET_RUNTIME_PM_OPS(rzg2l_mipi_pm_runtime_suspend, rzg2l_mipi_pm_runtime_resume, NULL)
+ };
+ /* -----------------------------------------------------------------------------
+  * Probe & Remove
+  */
+ static int rzg2l_mipi_dsi_probe(struct platform_device *pdev)
+ {
+       unsigned int num_data_lanes;
+       struct rzg2l_mipi_dsi *dsi;
+       u32 txsetr;
+       int ret;
+       dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL);
+       if (!dsi)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, dsi);
+       dsi->dev = &pdev->dev;
+       ret = drm_of_get_data_lanes_count_ep(dsi->dev->of_node, 1, 0, 1, 4);
+       if (ret < 0)
+               return dev_err_probe(dsi->dev, ret,
+                                    "missing or invalid data-lanes property\n");
+       num_data_lanes = ret;
+       dsi->mmio = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(dsi->mmio))
+               return PTR_ERR(dsi->mmio);
+       dsi->vclk = devm_clk_get(dsi->dev, "vclk");
+       if (IS_ERR(dsi->vclk))
+               return PTR_ERR(dsi->vclk);
+       dsi->rstc = devm_reset_control_get_exclusive(dsi->dev, "rst");
+       if (IS_ERR(dsi->rstc))
+               return dev_err_probe(dsi->dev, PTR_ERR(dsi->rstc),
+                                    "failed to get rst\n");
+       dsi->arstc = devm_reset_control_get_exclusive(dsi->dev, "arst");
+       if (IS_ERR(dsi->arstc))
+               return dev_err_probe(&pdev->dev, PTR_ERR(dsi->arstc),
+                                    "failed to get arst\n");
+       dsi->prstc = devm_reset_control_get_exclusive(dsi->dev, "prst");
+       if (IS_ERR(dsi->prstc))
+               return dev_err_probe(dsi->dev, PTR_ERR(dsi->prstc),
+                                    "failed to get prst\n");
+       platform_set_drvdata(pdev, dsi);
+       pm_runtime_enable(dsi->dev);
+       ret = pm_runtime_resume_and_get(dsi->dev);
+       if (ret < 0)
+               goto err_pm_disable;
+       /*
+        * TXSETR register can be read only after DPHY init. But during probe
+        * mode->clock and format are not available. So initialize DPHY with
+        * timing parameters for 80Mbps.
+        */
+       ret = rzg2l_mipi_dsi_dphy_init(dsi, 80000);
+       if (ret < 0)
+               goto err_phy;
+       txsetr = rzg2l_mipi_dsi_link_read(dsi, TXSETR);
+       dsi->num_data_lanes = min(((txsetr >> 16) & 3) + 1, num_data_lanes);
+       rzg2l_mipi_dsi_dphy_exit(dsi);
+       pm_runtime_put(dsi->dev);
+       /* Initialize the DRM bridge. */
+       dsi->bridge.funcs = &rzg2l_mipi_dsi_bridge_ops;
+       dsi->bridge.of_node = dsi->dev->of_node;
+       /* Init host device */
+       dsi->host.dev = dsi->dev;
+       dsi->host.ops = &rzg2l_mipi_dsi_host_ops;
+       ret = mipi_dsi_host_register(&dsi->host);
+       if (ret < 0)
+               goto err_pm_disable;
+       return 0;
+ err_phy:
+       rzg2l_mipi_dsi_dphy_exit(dsi);
+       pm_runtime_put(dsi->dev);
+ err_pm_disable:
+       pm_runtime_disable(dsi->dev);
+       return ret;
+ }
 -
 -      return 0;
++static void rzg2l_mipi_dsi_remove(struct platform_device *pdev)
+ {
+       struct rzg2l_mipi_dsi *dsi = platform_get_drvdata(pdev);
+       mipi_dsi_host_unregister(&dsi->host);
+       pm_runtime_disable(&pdev->dev);
 -      .remove = rzg2l_mipi_dsi_remove,
+ }
+ static const struct of_device_id rzg2l_mipi_dsi_of_table[] = {
+       { .compatible = "renesas,rzg2l-mipi-dsi" },
+       { /* sentinel */ }
+ };
+ MODULE_DEVICE_TABLE(of, rzg2l_mipi_dsi_of_table);
+ static struct platform_driver rzg2l_mipi_dsi_platform_driver = {
+       .probe  = rzg2l_mipi_dsi_probe,
++      .remove_new = rzg2l_mipi_dsi_remove,
+       .driver = {
+               .name = "rzg2l-mipi-dsi",
+               .pm = &rzg2l_mipi_pm_ops,
+               .of_match_table = rzg2l_mipi_dsi_of_table,
+       },
+ };
+ module_platform_driver(rzg2l_mipi_dsi_platform_driver);
+ MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
+ MODULE_DESCRIPTION("Renesas RZ/G2L MIPI DSI Encoder Driver");
+ MODULE_LICENSE("GPL");
Simple merge