summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/rcar-du
diff options
context:
space:
mode:
authorDaniel Vetter <daniel.vetter@ffwll.ch>2019-12-18 18:19:26 +0300
committerDaniel Vetter <daniel.vetter@ffwll.ch>2019-12-18 18:19:26 +0300
commit66af4a9ddac62a2614af5ce80169b6421b5c2d95 (patch)
tree91568f17d3d2c7d3743d23be0209f98563da0e81 /drivers/gpu/drm/rcar-du
parentbe452c4e8d1434a0095a9baa6523bb9772258d59 (diff)
parentc267782c5f0efbd20c560101738e68bb30d4fad5 (diff)
downloadlinux-66af4a9ddac62a2614af5ce80169b6421b5c2d95.tar.xz
Merge tag 'du-next-20191218' of git://linuxtv.org/pinchartl/media into drm-next
R-Car Display Unit changes: - Color Management Module support - LVDS encoder dual-link support enhancements - R8A77980 support Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Link: https://patchwork.freedesktop.org/patch/msgid/20191218151710.GA13830@pendragon.ideasonboard.com
Diffstat (limited to 'drivers/gpu/drm/rcar-du')
-rw-r--r--drivers/gpu/drm/rcar-du/Kconfig8
-rw-r--r--drivers/gpu/drm/rcar-du/Makefile1
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_cmm.c217
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_cmm.h58
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_crtc.c71
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_crtc.h2
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_drv.c6
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_drv.h2
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_group.c10
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_group.h2
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_kms.c93
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_regs.h5
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_lvds.c318
13 files changed, 643 insertions, 150 deletions
diff --git a/drivers/gpu/drm/rcar-du/Kconfig b/drivers/gpu/drm/rcar-du/Kconfig
index 1529849e217e..0919f1f159a4 100644
--- a/drivers/gpu/drm/rcar-du/Kconfig
+++ b/drivers/gpu/drm/rcar-du/Kconfig
@@ -4,6 +4,7 @@ config DRM_RCAR_DU
depends on DRM && OF
depends on ARM || ARM64
depends on ARCH_RENESAS || COMPILE_TEST
+ imply DRM_RCAR_CMM
imply DRM_RCAR_LVDS
select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER
@@ -13,6 +14,13 @@ config DRM_RCAR_DU
Choose this option if you have an R-Car chipset.
If M is selected the module will be called rcar-du-drm.
+config DRM_RCAR_CMM
+ tristate "R-Car DU Color Management Module (CMM) Support"
+ depends on DRM && OF
+ depends on DRM_RCAR_DU
+ help
+ Enable support for R-Car Color Management Module (CMM).
+
config DRM_RCAR_DW_HDMI
tristate "R-Car DU Gen3 HDMI Encoder Support"
depends on DRM && OF
diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile
index 6c2ed9c46467..4d1187ccc3e5 100644
--- a/drivers/gpu/drm/rcar-du/Makefile
+++ b/drivers/gpu/drm/rcar-du/Makefile
@@ -15,6 +15,7 @@ rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_of.o \
rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o
rcar-du-drm-$(CONFIG_DRM_RCAR_WRITEBACK) += rcar_du_writeback.o
+obj-$(CONFIG_DRM_RCAR_CMM) += rcar_cmm.o
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o
obj-$(CONFIG_DRM_RCAR_LVDS) += rcar_lvds.o
diff --git a/drivers/gpu/drm/rcar-du/rcar_cmm.c b/drivers/gpu/drm/rcar-du/rcar_cmm.c
new file mode 100644
index 000000000000..c578095b09a5
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_cmm.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * rcar_cmm.c -- 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_get_sync(&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;
+}
+
+static int rcar_cmm_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+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 = 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");
diff --git a/drivers/gpu/drm/rcar-du/rcar_cmm.h b/drivers/gpu/drm/rcar-du/rcar_cmm.h
new file mode 100644
index 000000000000..b5f7ec6db04a
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_cmm.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * rcar_cmm.h -- R-Car Display Unit Color Management Module
+ *
+ * Copyright (C) 2019 Jacopo Mondi <jacopo+renesas@jmondi.org>
+ */
+
+#ifndef __RCAR_CMM_H__
+#define __RCAR_CMM_H__
+
+#define CM2_LUT_SIZE 256
+
+struct drm_color_lut;
+struct platform_device;
+
+/**
+ * struct rcar_cmm_config - CMM configuration
+ *
+ * @lut: 1D-LUT configuration
+ * @lut.table: 1D-LUT table entries. Disable LUT operations when NULL
+ */
+struct rcar_cmm_config {
+ struct {
+ struct drm_color_lut *table;
+ } lut;
+};
+
+#if IS_ENABLED(CONFIG_DRM_RCAR_CMM)
+int rcar_cmm_init(struct platform_device *pdev);
+
+int rcar_cmm_enable(struct platform_device *pdev);
+void rcar_cmm_disable(struct platform_device *pdev);
+
+int rcar_cmm_setup(struct platform_device *pdev,
+ const struct rcar_cmm_config *config);
+#else
+static inline int rcar_cmm_init(struct platform_device *pdev)
+{
+ return -ENODEV;
+}
+
+static inline int rcar_cmm_enable(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static inline void rcar_cmm_disable(struct platform_device *pdev)
+{
+}
+
+static inline int rcar_cmm_setup(struct platform_device *pdev,
+ const struct rcar_cmm_config *config)
+{
+ return 0;
+}
+#endif /* IS_ENABLED(CONFIG_DRM_RCAR_CMM) */
+
+#endif /* __RCAR_CMM_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
index f2ae4c410244..d73e88ddecd0 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -22,6 +22,7 @@
#include <drm/drm_plane_helper.h>
#include <drm/drm_vblank.h>
+#include "rcar_cmm.h"
#include "rcar_du_crtc.h"
#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
@@ -476,6 +477,45 @@ static void rcar_du_crtc_wait_page_flip(struct rcar_du_crtc *rcrtc)
}
/* -----------------------------------------------------------------------------
+ * Color Management Module (CMM)
+ */
+
+static int rcar_du_cmm_check(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ struct drm_property_blob *drm_lut = state->gamma_lut;
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+ struct device *dev = rcrtc->dev->dev;
+
+ if (!drm_lut)
+ return 0;
+
+ /* We only accept fully populated LUT tables. */
+ if (drm_color_lut_size(drm_lut) != CM2_LUT_SIZE) {
+ dev_err(dev, "invalid gamma lut size: %zu bytes\n",
+ drm_lut->length);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void rcar_du_cmm_setup(struct drm_crtc *crtc)
+{
+ struct drm_property_blob *drm_lut = crtc->state->gamma_lut;
+ struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
+ struct rcar_cmm_config cmm_config = {};
+
+ if (!rcrtc->cmm)
+ return;
+
+ if (drm_lut)
+ cmm_config.lut.table = (struct drm_color_lut *)drm_lut->data;
+
+ rcar_cmm_setup(rcrtc->cmm, &cmm_config);
+}
+
+/* -----------------------------------------------------------------------------
* Start/Stop and Suspend/Resume
*/
@@ -620,6 +660,9 @@ static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
rcar_du_vsp_disable(rcrtc);
+ if (rcrtc->cmm)
+ rcar_cmm_disable(rcrtc->cmm);
+
/*
* Select switch sync mode. This stops display operation and configures
* the HSYNC and VSYNC signals as inputs.
@@ -643,6 +686,11 @@ static int rcar_du_crtc_atomic_check(struct drm_crtc *crtc,
{
struct rcar_du_crtc_state *rstate = to_rcar_crtc_state(state);
struct drm_encoder *encoder;
+ int ret;
+
+ ret = rcar_du_cmm_check(crtc, state);
+ if (ret)
+ return ret;
/* Store the routes from the CRTC output to the DU outputs. */
rstate->outputs = 0;
@@ -668,6 +716,8 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc,
struct rcar_du_crtc_state *rstate = to_rcar_crtc_state(crtc->state);
struct rcar_du_device *rcdu = rcrtc->dev;
+ if (rcrtc->cmm)
+ rcar_cmm_enable(rcrtc->cmm);
rcar_du_crtc_get(rcrtc);
/*
@@ -688,6 +738,13 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc,
}
rcar_du_crtc_start(rcrtc);
+
+ /*
+ * TODO: The chip manual indicates that CMM tables should be written
+ * after the DU channel has been activated. Investigate the impact
+ * of this restriction on the first displayed frame.
+ */
+ rcar_du_cmm_setup(crtc);
}
static void rcar_du_crtc_atomic_disable(struct drm_crtc *crtc,
@@ -743,6 +800,10 @@ static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc,
*/
rcar_du_crtc_get(rcrtc);
+ /* If the active state changed, we let .atomic_enable handle CMM. */
+ if (crtc->state->color_mgmt_changed && !crtc->state->active_changed)
+ rcar_du_cmm_setup(crtc);
+
if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
rcar_du_vsp_atomic_begin(rcrtc);
}
@@ -1079,6 +1140,7 @@ static const struct drm_crtc_funcs crtc_funcs_gen3 = {
.set_crc_source = rcar_du_crtc_set_crc_source,
.verify_crc_source = rcar_du_crtc_verify_crc_source,
.get_crc_sources = rcar_du_crtc_get_crc_sources,
+ .gamma_set = drm_atomic_helper_legacy_gamma_set,
};
/* -----------------------------------------------------------------------------
@@ -1198,6 +1260,15 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex,
if (ret < 0)
return ret;
+ /* CMM might be disabled for this CRTC. */
+ if (rcdu->cmms[swindex]) {
+ rcrtc->cmm = rcdu->cmms[swindex];
+ rgrp->cmms_mask |= BIT(hwindex % 2);
+
+ drm_mode_crtc_set_gamma_size(crtc, CM2_LUT_SIZE);
+ drm_crtc_enable_color_mgmt(crtc, 0, false, CM2_LUT_SIZE);
+ }
+
drm_crtc_helper_add(crtc, &crtc_helper_funcs);
/* Start with vertical blanking interrupt reporting disabled. */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
index 3b7fc668996f..5f2940c42225 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
@@ -39,6 +39,7 @@ struct rcar_du_vsp;
* @vblank_wait: wait queue used to signal vertical blanking
* @vblank_count: number of vertical blanking interrupts to wait for
* @group: CRTC group this CRTC belongs to
+ * @cmm: CMM associated with this CRTC
* @vsp: VSP feeding video to this CRTC
* @vsp_pipe: index of the VSP pipeline feeding video to this CRTC
* @writeback: the writeback connector
@@ -64,6 +65,7 @@ struct rcar_du_crtc {
unsigned int vblank_count;
struct rcar_du_group *group;
+ struct platform_device *cmm;
struct rcar_du_vsp *vsp;
unsigned int vsp_pipe;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
index f266c17b907a..654e2dd08146 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
@@ -399,7 +399,10 @@ static const struct rcar_du_device_info rcar_du_r8a77970_info = {
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(0),
.routes = {
- /* R8A77970 has one RGB output and one LVDS output. */
+ /*
+ * R8A77970 and R8A77980 have one RGB output and one LVDS
+ * output.
+ */
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0),
.port = 0,
@@ -457,6 +460,7 @@ static const struct of_device_id rcar_du_of_table[] = {
{ .compatible = "renesas,du-r8a7796", .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 },
{ }
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
index 1327cd0df90a..61504c54e2ec 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
@@ -13,6 +13,7 @@
#include <linux/kernel.h>
#include <linux/wait.h>
+#include "rcar_cmm.h"
#include "rcar_du_crtc.h"
#include "rcar_du_group.h"
#include "rcar_du_vsp.h"
@@ -85,6 +86,7 @@ struct rcar_du_device {
struct rcar_du_encoder *encoders[RCAR_DU_OUTPUT_MAX];
struct rcar_du_group groups[RCAR_DU_MAX_GROUPS];
+ struct platform_device *cmms[RCAR_DU_MAX_CRTCS];
struct rcar_du_vsp vsps[RCAR_DU_MAX_VSPS];
struct {
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_group.c b/drivers/gpu/drm/rcar-du/rcar_du_group.c
index 9eee47969e77..88a783ceb3e9 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_group.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_group.c
@@ -135,6 +135,7 @@ static void rcar_du_group_setup_didsr(struct rcar_du_group *rgrp)
static void rcar_du_group_setup(struct rcar_du_group *rgrp)
{
struct rcar_du_device *rcdu = rgrp->dev;
+ u32 defr7 = DEFR7_CODE;
/* Enable extended features */
rcar_du_group_write(rgrp, DEFR, DEFR_CODE | DEFR_DEFE);
@@ -147,6 +148,15 @@ static void rcar_du_group_setup(struct rcar_du_group *rgrp)
rcar_du_group_setup_pins(rgrp);
+ /*
+ * TODO: Handle routing of the DU output to CMM dynamically, as we
+ * should bypass CMM completely when no color management feature is
+ * used.
+ */
+ defr7 |= (rgrp->cmms_mask & BIT(1) ? DEFR7_CMME1 : 0) |
+ (rgrp->cmms_mask & BIT(0) ? DEFR7_CMME0 : 0);
+ rcar_du_group_write(rgrp, DEFR7, defr7);
+
if (rcdu->info->gen >= 2) {
rcar_du_group_setup_defr8(rgrp);
rcar_du_group_setup_didsr(rgrp);
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_group.h b/drivers/gpu/drm/rcar-du/rcar_du_group.h
index 87950c1f6a52..e9906609c635 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_group.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_group.h
@@ -22,6 +22,7 @@ struct rcar_du_device;
* @mmio_offset: registers offset in the device memory map
* @index: group index
* @channels_mask: bitmask of populated DU channels in this group
+ * @cmms_mask: bitmask of available CMMs in this group
* @num_crtcs: number of CRTCs in this group (1 or 2)
* @use_count: number of users of the group (rcar_du_group_(get|put))
* @used_crtcs: number of CRTCs currently in use
@@ -37,6 +38,7 @@ struct rcar_du_group {
unsigned int index;
unsigned int channels_mask;
+ unsigned int cmms_mask;
unsigned int num_crtcs;
unsigned int use_count;
unsigned int used_crtcs;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
index 0d59f390de19..fcfd916227d1 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
@@ -17,7 +17,9 @@
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
+#include <linux/device.h>
#include <linux/of_graph.h>
+#include <linux/of_platform.h>
#include <linux/wait.h>
#include "rcar_du_crtc.h"
@@ -542,6 +544,7 @@ static int rcar_du_properties_init(struct rcar_du_device *rcdu)
static int rcar_du_vsps_init(struct rcar_du_device *rcdu)
{
const struct device_node *np = rcdu->dev->of_node;
+ const char *vsps_prop_name = "renesas,vsps";
struct of_phandle_args args;
struct {
struct device_node *np;
@@ -557,15 +560,21 @@ static int rcar_du_vsps_init(struct rcar_du_device *rcdu)
* entry contains a pointer to the VSP DT node and a bitmask of the
* connected DU CRTCs.
*/
- cells = of_property_count_u32_elems(np, "vsps") / rcdu->num_crtcs - 1;
+ ret = of_property_count_u32_elems(np, vsps_prop_name);
+ if (ret < 0) {
+ /* Backward compatibility with old DTBs. */
+ vsps_prop_name = "vsps";
+ ret = of_property_count_u32_elems(np, vsps_prop_name);
+ }
+ cells = ret / rcdu->num_crtcs - 1;
if (cells > 1)
return -EINVAL;
for (i = 0; i < rcdu->num_crtcs; ++i) {
unsigned int j;
- ret = of_parse_phandle_with_fixed_args(np, "vsps", cells, i,
- &args);
+ ret = of_parse_phandle_with_fixed_args(np, vsps_prop_name,
+ cells, i, &args);
if (ret < 0)
goto error;
@@ -587,8 +596,8 @@ static int rcar_du_vsps_init(struct rcar_du_device *rcdu)
/*
* Store the VSP pointer and pipe index in the CRTC. If the
- * second cell of the 'vsps' specifier isn't present, default
- * to 0 to remain compatible with older DT bindings.
+ * second cell of the 'renesas,vsps' specifier isn't present,
+ * default to 0 to remain compatible with older DT bindings.
*/
rcdu->crtcs[i].vsp = &rcdu->vsps[j];
rcdu->crtcs[i].vsp_pipe = cells >= 1 ? args.args[0] : 0;
@@ -618,6 +627,75 @@ error:
return ret;
}
+static int rcar_du_cmm_init(struct rcar_du_device *rcdu)
+{
+ const struct device_node *np = rcdu->dev->of_node;
+ unsigned int i;
+ int cells;
+
+ cells = of_property_count_u32_elems(np, "renesas,cmms");
+ if (cells == -EINVAL)
+ return 0;
+
+ if (cells > rcdu->num_crtcs) {
+ dev_err(rcdu->dev,
+ "Invalid number of entries in 'renesas,cmms'\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < cells; ++i) {
+ struct platform_device *pdev;
+ struct device_link *link;
+ struct device_node *cmm;
+ int ret;
+
+ cmm = of_parse_phandle(np, "renesas,cmms", i);
+ if (IS_ERR(cmm)) {
+ dev_err(rcdu->dev,
+ "Failed to parse 'renesas,cmms' property\n");
+ return PTR_ERR(cmm);
+ }
+
+ if (!of_device_is_available(cmm)) {
+ /* It's fine to have a phandle to a non-enabled CMM. */
+ of_node_put(cmm);
+ continue;
+ }
+
+ pdev = of_find_device_by_node(cmm);
+ if (IS_ERR(pdev)) {
+ dev_err(rcdu->dev, "No device found for CMM%u\n", i);
+ of_node_put(cmm);
+ return PTR_ERR(pdev);
+ }
+
+ of_node_put(cmm);
+
+ /*
+ * -ENODEV is used to report that the CMM config option is
+ * disabled: return 0 and let the DU continue probing.
+ */
+ ret = rcar_cmm_init(pdev);
+ if (ret)
+ return ret == -ENODEV ? 0 : ret;
+
+ /*
+ * Enforce suspend/resume ordering by making the CMM a provider
+ * of the DU: CMM is suspended after and resumed before the DU.
+ */
+ link = device_link_add(rcdu->dev, &pdev->dev, DL_FLAG_STATELESS);
+ if (!link) {
+ dev_err(rcdu->dev,
+ "Failed to create device link to CMM%u\n", i);
+ return -EINVAL;
+ }
+
+ rcdu->cmms[i] = pdev;
+ }
+
+ return 0;
+}
+
int rcar_du_modeset_init(struct rcar_du_device *rcdu)
{
static const unsigned int mmio_offsets[] = {
@@ -708,6 +786,11 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
return ret;
}
+ /* Initialize the Color Management Modules. */
+ ret = rcar_du_cmm_init(rcdu);
+ if (ret)
+ return ret;
+
/* Create the CRTCs. */
for (swindex = 0, hwindex = 0; swindex < rcdu->num_crtcs; ++hwindex) {
struct rcar_du_group *rgrp;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_regs.h b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
index bc87f080b170..fb9964949368 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_regs.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_regs.h
@@ -197,6 +197,11 @@
#define DEFR6_MLOS1 (1 << 2)
#define DEFR6_DEFAULT (DEFR6_CODE | DEFR6_TCNE1)
+#define DEFR7 0x000ec
+#define DEFR7_CODE (0x7779 << 16)
+#define DEFR7_CMME1 BIT(6)
+#define DEFR7_CMME0 BIT(4)
+
/* -----------------------------------------------------------------------------
* R8A7790-only Control Registers
*/
diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c
index 2cf44b91853c..8ffa4fbbdeb3 100644
--- a/drivers/gpu/drm/rcar-du/rcar_lvds.c
+++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c
@@ -21,6 +21,7 @@
#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_probe_helper.h>
@@ -36,6 +37,12 @@ enum rcar_lvds_mode {
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) */
@@ -65,11 +72,8 @@ struct rcar_lvds {
struct clk *dotclkin[2]; /* External DU clocks */
} clocks;
- struct drm_display_mode display_mode;
- enum rcar_lvds_mode mode;
-
struct drm_bridge *companion;
- bool dual_link;
+ enum rcar_lvds_link_type link_type;
};
#define bridge_to_rcar_lvds(b) \
@@ -402,10 +406,53 @@ EXPORT_SYMBOL_GPL(rcar_lvds_clk_disable);
* Bridge
*/
-static void rcar_lvds_enable(struct drm_bridge *bridge)
+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_atomic_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);
- const struct drm_display_mode *mode = &lvds->display_mode;
u32 lvdhcr;
u32 lvdcr0;
int ret;
@@ -415,8 +462,9 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
return;
/* Enable the companion LVDS encoder in dual-link mode. */
- if (lvds->dual_link && lvds->companion)
- lvds->companion->funcs->enable(lvds->companion);
+ if (lvds->link_type != RCAR_LVDS_SINGLE_LINK && lvds->companion)
+ __rcar_lvds_atomic_enable(lvds->companion, state, crtc,
+ connector);
/*
* Hardcode the channels and control signals routing for now.
@@ -440,30 +488,51 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) {
- /*
- * Configure vertical stripe based on the mode of operation of
- * the connected device.
- */
- rcar_lvds_write(lvds, LVDSTRIPE,
- lvds->dual_link ? LVDSTRIPE_ST_ON : 0);
+ 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.
*/
- if (!lvds->dual_link || lvds->companion)
+ if (lvds->link_type == RCAR_LVDS_SINGLE_LINK || lvds->companion) {
+ 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 = lvds->mode << LVDCR0_LVMD_SHIFT;
+ lvdcr0 = rcar_lvds_get_lvds_mode(lvds, connector) << LVDCR0_LVMD_SHIFT;
if (lvds->bridge.encoder) {
- /*
- * FIXME: We should really retrieve the CRTC through the state,
- * but how do we get a state pointer?
- */
- if (drm_crtc_index(lvds->bridge.encoder->crtc) == 2)
+ if (drm_crtc_index(crtc) == 2)
lvdcr0 |= LVDCR0_DUSEL;
}
@@ -520,7 +589,21 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
}
}
-static void rcar_lvds_disable(struct drm_bridge *bridge)
+static void rcar_lvds_atomic_enable(struct drm_bridge *bridge,
+ struct drm_atomic_state *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_atomic_enable(bridge, state, crtc, connector);
+}
+
+static void rcar_lvds_atomic_disable(struct drm_bridge *bridge,
+ struct drm_atomic_state *state)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
@@ -534,8 +617,8 @@ static void rcar_lvds_disable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDPLLCR, 0);
/* Disable the companion LVDS encoder in dual-link mode. */
- if (lvds->dual_link && lvds->companion)
- lvds->companion->funcs->disable(lvds->companion);
+ if (lvds->link_type != RCAR_LVDS_SINGLE_LINK && lvds->companion)
+ lvds->companion->funcs->atomic_disable(lvds->companion, state);
clk_disable_unprepare(lvds->clocks.mod);
}
@@ -558,54 +641,6 @@ static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
return true;
}
-static void rcar_lvds_get_lvds_mode(struct rcar_lvds *lvds)
-{
- struct drm_display_info *info = &lvds->connector.display_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;
-
- if (!info->num_bus_formats || !info->bus_formats) {
- dev_err(lvds->dev, "no LVDS bus format reported\n");
- return;
- }
-
- 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_err(lvds->dev, "unsupported LVDS bus format 0x%04x\n",
- info->bus_formats[0]);
- return;
- }
-
- if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
- mode |= RCAR_LVDS_MODE_MIRROR;
-
- lvds->mode = mode;
-}
-
-static void rcar_lvds_mode_set(struct drm_bridge *bridge,
- const struct drm_display_mode *mode,
- const struct drm_display_mode *adjusted_mode)
-{
- struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
-
- lvds->display_mode = *adjusted_mode;
-
- rcar_lvds_get_lvds_mode(lvds);
-}
-
static int rcar_lvds_attach(struct drm_bridge *bridge)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
@@ -647,17 +682,16 @@ static void rcar_lvds_detach(struct drm_bridge *bridge)
static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
.attach = rcar_lvds_attach,
.detach = rcar_lvds_detach,
- .enable = rcar_lvds_enable,
- .disable = rcar_lvds_disable,
+ .atomic_enable = rcar_lvds_atomic_enable,
+ .atomic_disable = rcar_lvds_atomic_disable,
.mode_fixup = rcar_lvds_mode_fixup,
- .mode_set = rcar_lvds_mode_set,
};
bool rcar_lvds_dual_link(struct drm_bridge *bridge)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
- return lvds->dual_link;
+ return lvds->link_type != RCAR_LVDS_SINGLE_LINK;
}
EXPORT_SYMBOL_GPL(rcar_lvds_dual_link);
@@ -669,7 +703,10 @@ 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. */
@@ -688,13 +725,68 @@ static int rcar_lvds_parse_dt_companion(struct rcar_lvds *lvds)
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 && 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, "Found companion encoder %pOF\n", companion);
+ 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);
@@ -704,79 +796,17 @@ done:
static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
{
- struct device_node *local_output = NULL;
- struct device_node *remote_input = NULL;
- struct device_node *remote = NULL;
- struct device_node *node;
- bool is_bridge = false;
- int ret = 0;
-
- local_output = of_graph_get_endpoint_by_regs(lvds->dev->of_node, 1, 0);
- if (!local_output) {
- dev_dbg(lvds->dev, "unconnected port@1\n");
- ret = -ENODEV;
- goto done;
- }
-
- /*
- * Locate the connected entity and infer its type from the number of
- * endpoints.
- */
- remote = of_graph_get_remote_port_parent(local_output);
- if (!remote) {
- dev_dbg(lvds->dev, "unconnected endpoint %pOF\n", local_output);
- ret = -ENODEV;
- goto done;
- }
+ int ret;
- if (!of_device_is_available(remote)) {
- dev_dbg(lvds->dev, "connected entity %pOF is disabled\n",
- remote);
- ret = -ENODEV;
+ ret = drm_of_find_panel_or_bridge(lvds->dev->of_node, 1, 0,
+ &lvds->panel, &lvds->next_bridge);
+ if (ret)
goto done;
- }
-
- remote_input = of_graph_get_remote_endpoint(local_output);
- for_each_endpoint_of_node(remote, node) {
- if (node != remote_input) {
- /*
- * We've found one endpoint other than the input, this
- * must be a bridge.
- */
- is_bridge = true;
- of_node_put(node);
- break;
- }
- }
-
- if (is_bridge) {
- lvds->next_bridge = of_drm_find_bridge(remote);
- if (!lvds->next_bridge) {
- ret = -EPROBE_DEFER;
- goto done;
- }
-
- if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK)
- lvds->dual_link = lvds->next_bridge->timings
- ? lvds->next_bridge->timings->dual_link
- : false;
- } else {
- lvds->panel = of_drm_find_panel(remote);
- if (IS_ERR(lvds->panel)) {
- ret = PTR_ERR(lvds->panel);
- goto done;
- }
- }
-
- if (lvds->dual_link)
+ if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK)
ret = rcar_lvds_parse_dt_companion(lvds);
done:
- of_node_put(local_output);
- of_node_put(remote_input);
- of_node_put(remote);
-
/*
* 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.