From e08e934d6c289ed0d6bc222271f69dc35fb2caa5 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Thu, 17 Oct 2019 15:43:32 +0200 Subject: drm: rcar-du: Add support for CMM Add a driver for the R-Car Display Unit Color Correction Module. In most of Gen3 SoCs, each DU output channel is provided with a CMM unit to perform image enhancement and color correction. Add support for CMM through a driver that supports configuration of the 1-dimensional LUT table. More advanced CMM features will be implemented on top of this initial one. Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham Signed-off-by: Jacopo Mondi Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/Kconfig | 8 ++ drivers/gpu/drm/rcar-du/Makefile | 1 + drivers/gpu/drm/rcar-du/rcar_cmm.c | 217 +++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/rcar-du/rcar_cmm.h | 58 ++++++++++ 4 files changed, 284 insertions(+) create mode 100644 drivers/gpu/drm/rcar-du/rcar_cmm.c create mode 100644 drivers/gpu/drm/rcar-du/rcar_cmm.h (limited to 'drivers/gpu/drm/rcar-du') 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 + */ + +#include +#include +#include +#include +#include + +#include + +#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 "); +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 + */ + +#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__ */ -- cgit v1.2.3 From 8de707aeb4524140d9c37a3cf607e57e753d8529 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Wed, 16 Oct 2019 10:55:44 +0200 Subject: drm: rcar-du: kms: Initialize CMM instances Implement device tree parsing to collect the available CMM instances described by the 'renesas,cmms' property. Associate CMMs with CRTCs and store a mask of active CMMs in the DU group for later enablement. Enforce the probe and suspend/resume ordering of DU and CMM by creating a stateless device link between the two. Reviewed-by: Kieran Bingham Reviewed-by: Laurent Pinchart Signed-off-by: Jacopo Mondi Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 6 +++ drivers/gpu/drm/rcar-du/rcar_du_crtc.h | 2 + drivers/gpu/drm/rcar-du/rcar_du_drv.h | 2 + drivers/gpu/drm/rcar-du/rcar_du_group.h | 2 + drivers/gpu/drm/rcar-du/rcar_du_kms.c | 76 +++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+) (limited to 'drivers/gpu/drm/rcar-du') diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c index 2da46e3dc4ae..23f1d6cc1719 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -1194,6 +1194,12 @@ 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_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.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 #include +#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.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..186422ac552b 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 #include +#include #include +#include #include #include "rcar_du_crtc.h" @@ -618,6 +620,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 +779,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; -- cgit v1.2.3 From 78b6bb1d24dbf094a4743bae1ee7c020e8193f25 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Thu, 17 Oct 2019 15:44:09 +0200 Subject: drm: rcar-du: crtc: Control CMM operations Implement CMM handling in the crtc begin and enable atomic callbacks, and enable CMM unit through the Display Extensional Functions register at group setup time. Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham Signed-off-by: Jacopo Mondi [Fix printk format modifier for size_t variable] Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 61 +++++++++++++++++++++++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_group.c | 10 ++++++ drivers/gpu/drm/rcar-du/rcar_du_regs.h | 5 +++ 3 files changed, 76 insertions(+) (limited to 'drivers/gpu/drm/rcar-du') diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c index 23f1d6cc1719..84477d27e145 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -21,6 +21,7 @@ #include #include +#include "rcar_cmm.h" #include "rcar_du_crtc.h" #include "rcar_du_drv.h" #include "rcar_du_encoder.h" @@ -474,6 +475,45 @@ static void rcar_du_crtc_wait_page_flip(struct rcar_du_crtc *rcrtc) rcar_du_crtc_finish_page_flip(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 */ @@ -619,6 +659,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. @@ -642,6 +685,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; @@ -667,6 +715,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); /* @@ -686,6 +736,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, @@ -739,6 +796,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); } 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_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 */ -- cgit v1.2.3 From b28a931476bcc4552936bfa47194cae2ad6a15a5 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Wed, 16 Oct 2019 10:55:46 +0200 Subject: drm: rcar-du: crtc: Register GAMMA_LUT properties Enable the GAMMA_LUT KMS property using the framework helpers to register the property and set the associated gamma table maximum size. Reviewed-by: Kieran Bingham Reviewed-by: Ulrich Hecht Reviewed-by: Laurent Pinchart Signed-off-by: Jacopo Mondi Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/gpu/drm/rcar-du') diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c index 84477d27e145..fd1116ad88db 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -1136,6 +1136,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, }; /* ----------------------------------------------------------------------------- @@ -1259,6 +1260,9 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex, 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); -- cgit v1.2.3 From 7b627ce80fbd05885b27f711a5f9820f2b40749a Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 5 Nov 2019 19:35:03 +0100 Subject: drm: rcar-du: Recognize "renesas,vsps" in addition to "vsps" The Renesas-specific "vsps" property lacks a vendor prefix. Add a "renesas," prefix to comply with DT best practises. Retain backward compatibility with old DTBs by falling back to "vsps" when needed. Fixes: 6d62ef3ac30be756 ("drm: rcar-du: Expose the VSP1 compositor through KMS planes") Signed-off-by: Geert Uytterhoeven Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/rcar_du_kms.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'drivers/gpu/drm/rcar-du') diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c index 186422ac552b..fcfd916227d1 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c @@ -544,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; @@ -559,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; @@ -589,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; -- cgit v1.2.3 From 593885b085d699c9f49992c3ea50f372e0c7008d Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 16 Oct 2019 02:06:52 +0300 Subject: drm: rcar-du: lvds: Get mode from state The R-Car LVDS encoder driver implements the bridge .mode_set() operation for the sole purpose of storing the mode in the LVDS private data, to be used later when enabling the encoder. Switch to the bridge .atomic_enable() and .atomic_disable() operations in order to access the global atomic state, and get the mode from the state instead. Remove both the unneeded .mode_set() operation and the display_mode and mode fields storing state data from the rcar_lvds private structure. As a side effect we get the CRTC from the state, replace the CRTC pointer retrieved through the bridge's encoder that shouldn't be used by atomic drivers. While at it, clarify a few error messages in rcar_lvds_get_lvds_mode() and turn them into warnings as they are not fatal. Signed-off-by: Laurent Pinchart Reviewed-by: Fabrizio Castro Tested-by: Fabrizio Castro --- drivers/gpu/drm/rcar-du/rcar_lvds.c | 140 +++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 66 deletions(-) (limited to 'drivers/gpu/drm/rcar-du') diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c index 8c6c172bbf2e..284f10d0307f 100644 --- a/drivers/gpu/drm/rcar-du/rcar_lvds.c +++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c @@ -65,9 +65,6 @@ 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; }; @@ -402,10 +399,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; @@ -416,7 +456,8 @@ static void rcar_lvds_enable(struct drm_bridge *bridge) /* Enable the companion LVDS encoder in dual-link mode. */ if (lvds->dual_link && lvds->companion) - lvds->companion->funcs->enable(lvds->companion); + __rcar_lvds_atomic_enable(lvds->companion, state, crtc, + connector); /* * Hardcode the channels and control signals routing for now. @@ -452,18 +493,20 @@ static void rcar_lvds_enable(struct drm_bridge *bridge) * PLL clock configuration on all instances but the companion in * dual-link mode. */ - if (!lvds->dual_link || lvds->companion) + if (!lvds->dual_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 +563,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); @@ -535,7 +592,7 @@ static void rcar_lvds_disable(struct drm_bridge *bridge) /* Disable the companion LVDS encoder in dual-link mode. */ if (lvds->dual_link && lvds->companion) - lvds->companion->funcs->disable(lvds->companion); + lvds->companion->funcs->atomic_disable(lvds->companion, state); clk_disable_unprepare(lvds->clocks.mod); } @@ -558,54 +615,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,10 +656,9 @@ 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) -- cgit v1.2.3 From 990e378db4450f930aefbe8c31a50b8504fe0e8b Mon Sep 17 00:00:00 2001 From: Fabrizio Castro Date: Tue, 17 Dec 2019 13:45:57 +0000 Subject: drm: rcar-du: lvds: Improve identification of panels Dual-LVDS panels are mistakenly identified as bridges, this commit replaces the current logic with a call to drm_of_find_panel_or_bridge to sort that out. Signed-off-by: Fabrizio Castro Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/rcar_lvds.c | 75 +++++-------------------------------- 1 file changed, 10 insertions(+), 65 deletions(-) (limited to 'drivers/gpu/drm/rcar-du') diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c index 284f10d0307f..ec63546ade7e 100644 --- a/drivers/gpu/drm/rcar-du/rcar_lvds.c +++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -712,79 +713,23 @@ 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->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) && + lvds->next_bridge) + lvds->dual_link = lvds->next_bridge->timings + ? lvds->next_bridge->timings->dual_link + : false; if (lvds->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. -- cgit v1.2.3 From 65112cfa56c32030a7f04a8a4c28251b89b5cf26 Mon Sep 17 00:00:00 2001 From: Fabrizio Castro Date: Tue, 17 Dec 2019 13:45:58 +0000 Subject: drm: rcar-du: lvds: Get dual link configuration from DT For dual-LVDS configurations, it is now possible to mark the DT port nodes for the sink with boolean properties (like dual-lvds-even-pixels and dual-lvds-odd-pixels) to let drivers know the encoders need to be configured in dual-LVDS mode. Rework the implementation of rcar_lvds_parse_dt_companion to make use of the DT markers while keeping backward compatibility. Signed-off-by: Fabrizio Castro Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/rcar_lvds.c | 54 +++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) (limited to 'drivers/gpu/drm/rcar-du') diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c index ec63546ade7e..1749b9c48d5d 100644 --- a/drivers/gpu/drm/rcar-du/rcar_lvds.c +++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c @@ -678,7 +678,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. */ @@ -697,13 +700,54 @@ 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); + + if (dual_link >= DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) + lvds->dual_link = true; + else if (lvds->next_bridge && lvds->next_bridge->timings) + /* + * Early dual-link bridge specific implementations populate the + * timings field of drm_bridge, read the dual_link flag off the + * bridge directly for backward compatibility. + */ + lvds->dual_link = lvds->next_bridge->timings->dual_link; + + if (!lvds->dual_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); + + /* + * 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->dual_link = true; done: of_node_put(companion); @@ -720,13 +764,7 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds) if (ret) goto done; - if ((lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) && - lvds->next_bridge) - lvds->dual_link = lvds->next_bridge->timings - ? lvds->next_bridge->timings->dual_link - : false; - - if (lvds->dual_link) + if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) ret = rcar_lvds_parse_dt_companion(lvds); done: -- cgit v1.2.3 From 59c1f061c97e70d81b046e90b259589b645bb87f Mon Sep 17 00:00:00 2001 From: Fabrizio Castro Date: Tue, 17 Dec 2019 13:45:59 +0000 Subject: drm: rcar-du: lvds: Allow for even and odd pixels swap DT properties dual-lvds-even-pixels and dual-lvds-odd-pixels can be used to work out if the driver needs to swap even and odd pixels around. This patch makes use of the return value from function drm_of_lvds_get_dual_link_pixel_order to determine if we need to swap odd and even pixels around for things to work properly. The dual_link boolean field from struct rcar_lvds is not sufficient to describe the type of LVDS link anymore, since we now have information related to pixel order, therefore rename it to link_type and repurpose its usage to fit the new requirements. Signed-off-by: Fabrizio Castro Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/rcar_lvds.c | 77 ++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 19 deletions(-) (limited to 'drivers/gpu/drm/rcar-du') diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c index 1749b9c48d5d..fd5577001b7e 100644 --- a/drivers/gpu/drm/rcar-du/rcar_lvds.c +++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c @@ -37,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) */ @@ -67,7 +73,7 @@ struct rcar_lvds { } clocks; struct drm_bridge *companion; - bool dual_link; + enum rcar_lvds_link_type link_type; }; #define bridge_to_rcar_lvds(b) \ @@ -456,7 +462,7 @@ static void __rcar_lvds_atomic_enable(struct drm_bridge *bridge, return; /* Enable the companion LVDS encoder in dual-link mode. */ - if (lvds->dual_link && lvds->companion) + if (lvds->link_type != RCAR_LVDS_SINGLE_LINK && lvds->companion) __rcar_lvds_atomic_enable(lvds->companion, state, crtc, connector); @@ -482,19 +488,38 @@ static void __rcar_lvds_atomic_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 = @@ -592,7 +617,7 @@ static void rcar_lvds_atomic_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) + if (lvds->link_type != RCAR_LVDS_SINGLE_LINK && lvds->companion) lvds->companion->funcs->atomic_disable(lvds->companion, state); clk_disable_unprepare(lvds->clocks.mod); @@ -666,7 +691,7 @@ 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); @@ -712,17 +737,28 @@ static int rcar_lvds_parse_dt_companion(struct rcar_lvds *lvds) of_node_put(port0); of_node_put(port1); - if (dual_link >= DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) - lvds->dual_link = true; - else if (lvds->next_bridge && lvds->next_bridge->timings) + 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, read the dual_link flag off the - * bridge directly for backward compatibility. + * 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. */ - lvds->dual_link = lvds->next_bridge->timings->dual_link; + 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->dual_link) { + if (lvds->link_type == RCAR_LVDS_SINGLE_LINK) { dev_dbg(dev, "Single-link configuration detected\n"); goto done; } @@ -737,6 +773,9 @@ static int rcar_lvds_parse_dt_companion(struct rcar_lvds *lvds) "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 @@ -747,7 +786,7 @@ static int rcar_lvds_parse_dt_companion(struct rcar_lvds *lvds) * for the time being. */ companion_lvds = bridge_to_rcar_lvds(lvds->companion); - companion_lvds->dual_link = true; + companion_lvds->link_type = lvds->link_type; done: of_node_put(companion); -- cgit v1.2.3 From c267782c5f0efbd20c560101738e68bb30d4fad5 Mon Sep 17 00:00:00 2001 From: Kieran Bingham Date: Wed, 11 Sep 2019 20:25:01 +0100 Subject: drm: rcar-du: Add r8a77980 support Add direct support for the r8a77980 (V3H). The V3H shares a common, compatible configuration with the r8a77970 (V3M) so that device info structure is reused. Signed-off-by: Kieran Bingham Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/gpu/drm/rcar-du/rcar_du_drv.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/gpu/drm/rcar-du') 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 }, { } -- cgit v1.2.3