diff options
| author | Breno Leitao <leitao@debian.org> | 2026-08-05 07:52:36 -0700 |
|---|---|---|
| committer | Tejun Heo <tj@kernel.org> | 2026-08-10 11:47:20 -1000 |
| commit | a5bde5d8fde8a8cb28e59a672d5ddc5b9c1e7656 (patch) | |
| tree | 39f5b258386e1127d253d293194e9a2991bf3c34 /drivers/gpu/drm/renesas | |
| download | linux-stable-a5bde5d8fde8a8cb28e59a672d5ddc5b9c1e7656.tar.gz linux-stable-a5bde5d8fde8a8cb28e59a672d5ddc5b9c1e7656.zip | |
workqueue: skip the node_nr_active update for non-unbound workqueuesgrafted
apply_wqattrs_commit() updates node_nr_active->max unconditionally.
wq->node_nr_active[] is only allocated for unbound workqueues, so guard
the call before per-cpu workqueues start using this path.
No functional change: only unbound workqueues reach apply_wqattrs_*()
today.
Signed-off-by: Breno Leitao <leitao@debian.org>
Signed-off-by: Tejun Heo <tj@kernel.org>
Diffstat (limited to 'drivers/gpu/drm/renesas')
55 files changed, 15600 insertions, 0 deletions
diff --git a/drivers/gpu/drm/renesas/Kconfig b/drivers/gpu/drm/renesas/Kconfig new file mode 100644 index 000000000..21862a8ef --- /dev/null +++ b/drivers/gpu/drm/renesas/Kconfig @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +source "drivers/gpu/drm/renesas/rcar-du/Kconfig" +source "drivers/gpu/drm/renesas/rz-du/Kconfig" +source "drivers/gpu/drm/renesas/shmobile/Kconfig" diff --git a/drivers/gpu/drm/renesas/Makefile b/drivers/gpu/drm/renesas/Makefile new file mode 100644 index 000000000..b8d8bc539 --- /dev/null +++ b/drivers/gpu/drm/renesas/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-y += rcar-du/ +obj-y += rz-du/ +obj-$(CONFIG_DRM_SHMOBILE) += shmobile/ diff --git a/drivers/gpu/drm/renesas/rcar-du/Kconfig b/drivers/gpu/drm/renesas/rcar-du/Kconfig new file mode 100644 index 000000000..840305fde --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/Kconfig @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-2.0 +config DRM_RCAR_DU + tristate "DRM Support for R-Car Display Unit" + depends on DRM && OF + depends on ARM || ARM64 || COMPILE_TEST + depends on ARCH_RENESAS || COMPILE_TEST + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + select DRM_GEM_DMA_HELPER + select VIDEOMODE_HELPERS + help + 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_USE_CMM + bool "R-Car DU Color Management Module (CMM) Support" + depends on DRM_RCAR_DU + default DRM_RCAR_DU + help + Enable support for R-Car Color Management Module (CMM). + +config DRM_RCAR_CMM + def_tristate DRM_RCAR_DU + depends on DRM_RCAR_USE_CMM + +config DRM_RCAR_DW_HDMI + tristate "R-Car Gen3 and RZ/G2 DU HDMI Encoder Support" + depends on DRM && OF + depends on DRM_RCAR_DU || COMPILE_TEST + select DRM_DW_HDMI + help + Enable support for R-Car Gen3 or RZ/G2 internal HDMI encoder. + +config DRM_RCAR_USE_LVDS + bool "R-Car DU LVDS Encoder Support" + depends on DRM_BRIDGE && OF + depends on DRM_RCAR_DU || COMPILE_TEST + default DRM_RCAR_DU + help + Enable support for the R-Car Display Unit embedded LVDS encoders. + +config DRM_RCAR_LVDS + def_tristate DRM_RCAR_DU + depends on DRM_RCAR_USE_LVDS + depends on PM + select DRM_KMS_HELPER + select DRM_PANEL + select RESET_CONTROLLER + +config DRM_RCAR_USE_MIPI_DSI + bool "R-Car DU MIPI DSI Encoder Support" + depends on DRM_BRIDGE && OF + depends on DRM_RCAR_DU || COMPILE_TEST + default DRM_RCAR_DU + help + Enable support for the R-Car Display Unit embedded MIPI DSI encoders. + +config DRM_RCAR_MIPI_DSI + def_tristate DRM_RCAR_DU + depends on DRM_RCAR_USE_MIPI_DSI + select DRM_MIPI_DSI + select RESET_CONTROLLER + +config DRM_RCAR_VSP + bool "R-Car DU VSP Compositor Support" if ARM + default y if ARM64 + depends on DRM_RCAR_DU + depends on VIDEO_RENESAS_VSP1=y || (VIDEO_RENESAS_VSP1 && DRM_RCAR_DU=m) + help + Enable support to expose the R-Car VSP Compositor as KMS planes. + +config DRM_RCAR_WRITEBACK + bool + default y if ARM64 + depends on DRM_RCAR_DU diff --git a/drivers/gpu/drm/renesas/rcar-du/Makefile b/drivers/gpu/drm/renesas/rcar-du/Makefile new file mode 100644 index 000000000..6f132325c --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +rcar-du-drm-y := rcar_du_crtc.o \ + rcar_du_drv.o \ + rcar_du_encoder.o \ + rcar_du_group.o \ + rcar_du_kms.o \ + rcar_du_plane.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 +obj-$(CONFIG_DRM_RCAR_MIPI_DSI) += rcar_mipi_dsi.o diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_cmm.c b/drivers/gpu/drm/renesas/rcar-du/rcar_cmm.c new file mode 100644 index 000000000..5bced9d77 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_cmm.c @@ -0,0 +1,210 @@ +// 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 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 + * @dev: The 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 device *dev, + const struct rcar_cmm_config *config) +{ + struct rcar_cmm *rcmm = dev_get_drvdata(dev); + + /* 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 + * @dev: The 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 device *dev) +{ + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(rcar_cmm_enable); + +/* + * rcar_cmm_disable() - Disable the CMM unit + * @dev: The 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 device *dev) +{ + struct rcar_cmm *rcmm = dev_get_drvdata(dev); + + rcar_cmm_write(rcmm, CM2_LUT_CTRL, 0); + rcmm->lut.enabled = false; + + pm_runtime_put(dev); +} +EXPORT_SYMBOL_GPL(rcar_cmm_disable); + +/* + * rcar_cmm_init() - Initialize the CMM unit + * @dev: The 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 device *dev) +{ + struct rcar_cmm *rcmm = dev_get_drvdata(dev); + + 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 void rcar_cmm_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); +} + +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/renesas/rcar-du/rcar_cmm.h b/drivers/gpu/drm/renesas/rcar-du/rcar_cmm.h new file mode 100644 index 000000000..c42011343 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_cmm.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * 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 device; +struct drm_color_lut; + +/** + * 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 device *dev); + +int rcar_cmm_enable(struct device *dev); +void rcar_cmm_disable(struct device *dev); + +int rcar_cmm_setup(struct device *dev, + const struct rcar_cmm_config *config); +#else +static inline int rcar_cmm_init(struct device *dev) +{ + return -ENODEV; +} + +static inline int rcar_cmm_enable(struct device *dev) +{ + return 0; +} + +static inline void rcar_cmm_disable(struct device *dev) +{ +} + +static inline int rcar_cmm_setup(struct device *dev, + 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/renesas/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_crtc.c new file mode 100644 index 000000000..6c07c406f --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_crtc.c @@ -0,0 +1,1339 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * R-Car Display Unit CRTCs + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include <linux/clk.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_print.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" +#include "rcar_du_kms.h" +#include "rcar_du_plane.h" +#include "rcar_du_regs.h" +#include "rcar_du_vsp.h" +#include "rcar_lvds.h" +#include "rcar_mipi_dsi.h" + +static u32 rcar_du_crtc_read(struct rcar_du_crtc *rcrtc, u32 reg) +{ + struct rcar_du_device *rcdu = rcrtc->dev; + + return rcar_du_read(rcdu, rcrtc->mmio_offset + reg); +} + +static void rcar_du_crtc_write(struct rcar_du_crtc *rcrtc, u32 reg, u32 data) +{ + struct rcar_du_device *rcdu = rcrtc->dev; + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, data); +} + +static void rcar_du_crtc_clr(struct rcar_du_crtc *rcrtc, u32 reg, u32 clr) +{ + struct rcar_du_device *rcdu = rcrtc->dev; + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, + rcar_du_read(rcdu, rcrtc->mmio_offset + reg) & ~clr); +} + +static void rcar_du_crtc_set(struct rcar_du_crtc *rcrtc, u32 reg, u32 set) +{ + struct rcar_du_device *rcdu = rcrtc->dev; + + rcar_du_write(rcdu, rcrtc->mmio_offset + reg, + rcar_du_read(rcdu, rcrtc->mmio_offset + reg) | set); +} + +void rcar_du_crtc_dsysr_clr_set(struct rcar_du_crtc *rcrtc, u32 clr, u32 set) +{ + struct rcar_du_device *rcdu = rcrtc->dev; + + rcrtc->dsysr = (rcrtc->dsysr & ~clr) | set; + rcar_du_write(rcdu, rcrtc->mmio_offset + DSYSR, rcrtc->dsysr); +} + +/* ----------------------------------------------------------------------------- + * Hardware Setup + */ + +struct dpll_info { + unsigned int output; + unsigned int fdpll; + unsigned int n; + unsigned int m; +}; + +static void rcar_du_dpll_divider(struct rcar_du_crtc *rcrtc, + struct dpll_info *dpll, + unsigned long input, + unsigned long target) +{ + unsigned long best_diff = (unsigned long)-1; + unsigned long diff; + unsigned int fdpll; + unsigned int m; + unsigned int n; + + /* + * fin fvco fout fclkout + * in --> [1/M] --> |PD| -> [LPF] -> [VCO] -> [1/P] -+-> [1/FDPLL] -> out + * +-> | | | + * | | + * +---------------- [1/N] <------------+ + * + * fclkout = fvco / P / FDPLL -- (1) + * + * fin/M = fvco/P/N + * + * fvco = fin * P * N / M -- (2) + * + * (1) + (2) indicates + * + * fclkout = fin * N / M / FDPLL + * + * NOTES + * N : (n + 1) + * M : (m + 1) + * FDPLL : (fdpll + 1) + * P : 2 + * 2kHz < fvco < 4096MHz + * + * To minimize the jitter, + * N : as large as possible + * M : as small as possible + */ + for (m = 0; m < 4; m++) { + for (n = 119; n > 38; n--) { + /* + * This code only runs on 64-bit architectures, the + * unsigned long type can thus be used for 64-bit + * computation. It will still compile without any + * warning on 32-bit architectures. + * + * To optimize calculations, use fout instead of fvco + * to verify the VCO frequency constraint. + */ + unsigned long fout = input * (n + 1) / (m + 1); + + if (fout < 1000 || fout > 2048 * 1000 * 1000U) + continue; + + for (fdpll = 1; fdpll < 32; fdpll++) { + unsigned long output; + + output = fout / (fdpll + 1); + if (output >= 400 * 1000 * 1000) + continue; + + diff = abs((long)output - (long)target); + if (best_diff > diff) { + best_diff = diff; + dpll->n = n; + dpll->m = m; + dpll->fdpll = fdpll; + dpll->output = output; + } + + if (diff == 0) + goto done; + } + } + } + +done: + dev_dbg(rcrtc->dev->dev, + "output:%u, fdpll:%u, n:%u, m:%u, diff:%lu\n", + dpll->output, dpll->fdpll, dpll->n, dpll->m, best_diff); +} + +struct du_clk_params { + struct clk *clk; + unsigned long rate; + unsigned long diff; + u32 escr; +}; + +static void rcar_du_escr_divider(struct clk *clk, unsigned long target, + u32 escr, struct du_clk_params *params) +{ + unsigned long rate; + unsigned long diff; + u32 div; + + /* + * If the target rate has already been achieved perfectly we can't do + * better. + */ + if (params->diff == 0) + return; + + /* + * Compute the input clock rate and internal divisor values to obtain + * the clock rate closest to the target frequency. + */ + rate = clk_round_rate(clk, target); + div = clamp(DIV_ROUND_CLOSEST(rate, target), 1UL, 64UL) - 1; + diff = abs(rate / (div + 1) - target); + + /* + * Store the parameters if the resulting frequency is better than any + * previously calculated value. + */ + if (diff < params->diff) { + params->clk = clk; + params->rate = rate; + params->diff = diff; + params->escr = escr | div; + } +} + +static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) +{ + const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode; + struct rcar_du_device *rcdu = rcrtc->dev; + unsigned long mode_clock = mode->clock * 1000; + unsigned int hdse_offset; + u32 dsmr; + u32 escr; + + if (rcdu->info->dpll_mask & (1 << rcrtc->index)) { + unsigned long target = mode_clock; + struct dpll_info dpll = { 0 }; + unsigned long extclk; + u32 dpllcr; + u32 div = 0; + + /* + * DU channels that have a display PLL can't use the internal + * system clock, and have no internal clock divider. + */ + extclk = clk_get_rate(rcrtc->extclock); + rcar_du_dpll_divider(rcrtc, &dpll, extclk, target); + + dpllcr = DPLLCR_CODE | DPLLCR_CLKE + | DPLLCR_FDPLL(dpll.fdpll) + | DPLLCR_N(dpll.n) | DPLLCR_M(dpll.m) + | DPLLCR_STBY; + + if (rcrtc->index == 1) + dpllcr |= DPLLCR_PLCS1 + | DPLLCR_INCS_DOTCLKIN1; + else + dpllcr |= DPLLCR_PLCS0 + | DPLLCR_INCS_DOTCLKIN0; + + rcar_du_group_write(rcrtc->group, DPLLCR, dpllcr); + + escr = ESCR_DCLKSEL_DCLKIN | div; + } else if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index) || + rcdu->info->dsi_clk_mask & BIT(rcrtc->index)) { + /* + * Use the external LVDS or DSI PLL output as the dot clock when + * outputting to the LVDS or DSI encoder on an SoC that supports + * this clock routing option. We use the clock directly in that + * case, without any additional divider. + */ + escr = ESCR_DCLKSEL_DCLKIN; + } else { + struct du_clk_params params = { .diff = (unsigned long)-1 }; + + rcar_du_escr_divider(rcrtc->clock, mode_clock, + ESCR_DCLKSEL_CLKS, ¶ms); + if (rcrtc->extclock) + rcar_du_escr_divider(rcrtc->extclock, mode_clock, + ESCR_DCLKSEL_DCLKIN, ¶ms); + + dev_dbg(rcrtc->dev->dev, "mode clock %lu %s rate %lu\n", + mode_clock, params.clk == rcrtc->clock ? "cpg" : "ext", + params.rate); + + clk_set_rate(params.clk, params.rate); + escr = params.escr; + } + + /* + * The ESCR register only exists in DU channels that can output to an + * LVDS or DPAT, and the OTAR register in DU channels that can output + * to a DPAD. + */ + if ((rcdu->info->routes[RCAR_DU_OUTPUT_DPAD0].possible_crtcs | + rcdu->info->routes[RCAR_DU_OUTPUT_DPAD1].possible_crtcs | + rcdu->info->routes[RCAR_DU_OUTPUT_LVDS0].possible_crtcs | + rcdu->info->routes[RCAR_DU_OUTPUT_LVDS1].possible_crtcs) & + BIT(rcrtc->index)) { + dev_dbg(rcrtc->dev->dev, "%s: ESCR 0x%08x\n", __func__, escr); + + rcar_du_crtc_write(rcrtc, rcrtc->index % 2 ? ESCR13 : ESCR02, escr); + } + + if ((rcdu->info->routes[RCAR_DU_OUTPUT_DPAD0].possible_crtcs | + rcdu->info->routes[RCAR_DU_OUTPUT_DPAD1].possible_crtcs) & + BIT(rcrtc->index)) + rcar_du_crtc_write(rcrtc, rcrtc->index % 2 ? OTAR13 : OTAR02, 0); + + /* Signal polarities */ + dsmr = ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? DSMR_VSL : 0) + | ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? DSMR_HSL : 0) + | ((mode->flags & DRM_MODE_FLAG_INTERLACE) ? DSMR_ODEV : 0) + | DSMR_DIPM_DISP | DSMR_CSPM; + rcar_du_crtc_write(rcrtc, DSMR, dsmr); + + /* + * When the CMM is enabled, an additional offset of 25 pixels must be + * subtracted from the HDS (horizontal display start) and HDE + * (horizontal display end) registers. + */ + hdse_offset = 19; + if (rcrtc->group->cmms_mask & BIT(rcrtc->index % 2)) + hdse_offset += 25; + + /* Display timings */ + rcar_du_crtc_write(rcrtc, HDSR, mode->htotal - mode->hsync_start - + hdse_offset); + rcar_du_crtc_write(rcrtc, HDER, mode->htotal - mode->hsync_start + + mode->hdisplay - hdse_offset); + rcar_du_crtc_write(rcrtc, HSWR, mode->hsync_end - + mode->hsync_start - 1); + rcar_du_crtc_write(rcrtc, HCR, mode->htotal - 1); + + rcar_du_crtc_write(rcrtc, VDSR, mode->crtc_vtotal - + mode->crtc_vsync_end - 2); + rcar_du_crtc_write(rcrtc, VDER, mode->crtc_vtotal - + mode->crtc_vsync_end + + mode->crtc_vdisplay - 2); + rcar_du_crtc_write(rcrtc, VSPR, mode->crtc_vtotal - + mode->crtc_vsync_end + + mode->crtc_vsync_start - 1); + rcar_du_crtc_write(rcrtc, VCR, mode->crtc_vtotal - 1); + + rcar_du_crtc_write(rcrtc, DESR, mode->htotal - mode->hsync_start - 1); + rcar_du_crtc_write(rcrtc, DEWR, mode->hdisplay); +} + +static unsigned int plane_zpos(struct rcar_du_plane *plane) +{ + return plane->plane.state->normalized_zpos; +} + +static const struct rcar_du_format_info * +plane_format(struct rcar_du_plane *plane) +{ + return to_rcar_plane_state(plane->plane.state)->format; +} + +static void rcar_du_crtc_update_planes(struct rcar_du_crtc *rcrtc) +{ + struct rcar_du_plane *planes[RCAR_DU_NUM_HW_PLANES]; + struct rcar_du_device *rcdu = rcrtc->dev; + unsigned int num_planes = 0; + unsigned int dptsr_planes; + unsigned int hwplanes = 0; + unsigned int prio = 0; + unsigned int i; + u32 dspr = 0; + + for (i = 0; i < rcrtc->group->num_planes; ++i) { + struct rcar_du_plane *plane = &rcrtc->group->planes[i]; + unsigned int j; + + if (plane->plane.state->crtc != &rcrtc->crtc || + !plane->plane.state->visible) + continue; + + /* Insert the plane in the sorted planes array. */ + for (j = num_planes++; j > 0; --j) { + if (plane_zpos(planes[j-1]) <= plane_zpos(plane)) + break; + planes[j] = planes[j-1]; + } + + planes[j] = plane; + prio += plane_format(plane)->planes * 4; + } + + for (i = 0; i < num_planes; ++i) { + struct rcar_du_plane *plane = planes[i]; + struct drm_plane_state *state = plane->plane.state; + unsigned int index = to_rcar_plane_state(state)->hwindex; + + prio -= 4; + dspr |= (index + 1) << prio; + hwplanes |= 1 << index; + + if (plane_format(plane)->planes == 2) { + index = (index + 1) % 8; + + prio -= 4; + dspr |= (index + 1) << prio; + hwplanes |= 1 << index; + } + } + + /* If VSP+DU integration is enabled the plane assignment is fixed. */ + if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE)) { + if (rcdu->info->gen < 3) { + dspr = (rcrtc->index % 2) + 1; + hwplanes = 1 << (rcrtc->index % 2); + } else { + dspr = (rcrtc->index % 2) ? 3 : 1; + hwplanes = 1 << ((rcrtc->index % 2) ? 2 : 0); + } + } + + /* + * Update the planes to display timing and dot clock generator + * associations. + * + * Updating the DPTSR register requires restarting the CRTC group, + * resulting in visible flicker. To mitigate the issue only update the + * association if needed by enabled planes. Planes being disabled will + * keep their current association. + */ + mutex_lock(&rcrtc->group->lock); + + dptsr_planes = rcrtc->index % 2 ? rcrtc->group->dptsr_planes | hwplanes + : rcrtc->group->dptsr_planes & ~hwplanes; + + if (dptsr_planes != rcrtc->group->dptsr_planes) { + rcar_du_group_write(rcrtc->group, DPTSR, + (dptsr_planes << 16) | dptsr_planes); + rcrtc->group->dptsr_planes = dptsr_planes; + + if (rcrtc->group->used_crtcs) + rcar_du_group_restart(rcrtc->group); + } + + /* Restart the group if plane sources have changed. */ + if (rcrtc->group->need_restart) + rcar_du_group_restart(rcrtc->group); + + mutex_unlock(&rcrtc->group->lock); + + rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, + dspr); +} + +/* ----------------------------------------------------------------------------- + * Page Flip + */ + +void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = rcrtc->event; + rcrtc->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + if (event == NULL) + return; + + spin_lock_irqsave(&dev->event_lock, flags); + drm_crtc_send_vblank_event(&rcrtc->crtc, event); + wake_up(&rcrtc->flip_wait); + spin_unlock_irqrestore(&dev->event_lock, flags); + + drm_crtc_vblank_put(&rcrtc->crtc); +} + +static bool rcar_du_crtc_page_flip_pending(struct rcar_du_crtc *rcrtc) +{ + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + bool pending; + + spin_lock_irqsave(&dev->event_lock, flags); + pending = rcrtc->event != NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + return pending; +} + +static void rcar_du_crtc_wait_page_flip(struct rcar_du_crtc *rcrtc) +{ + struct rcar_du_device *rcdu = rcrtc->dev; + + if (wait_event_timeout(rcrtc->flip_wait, + !rcar_du_crtc_page_flip_pending(rcrtc), + msecs_to_jiffies(50))) + return; + + dev_warn(rcdu->dev, "page flip timeout\n"); + + 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->dev, &cmm_config); +} + +/* ----------------------------------------------------------------------------- + * Start/Stop and Suspend/Resume + */ + +static void rcar_du_crtc_setup(struct rcar_du_crtc *rcrtc) +{ + /* Set display off and background to black */ + rcar_du_crtc_write(rcrtc, DOOR, DOOR_RGB(0, 0, 0)); + rcar_du_crtc_write(rcrtc, BPOR, BPOR_RGB(0, 0, 0)); + + /* Configure display timings and output routing */ + rcar_du_crtc_set_display_timing(rcrtc); + rcar_du_group_set_routing(rcrtc->group); + + /* Start with all planes disabled. */ + rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, 0); + + /* Enable the VSP compositor. */ + if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE)) + rcar_du_vsp_enable(rcrtc); + + /* Turn vertical blanking interrupt reporting on. */ + drm_crtc_vblank_on(&rcrtc->crtc); +} + +static int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc) +{ + int ret; + + /* + * Guard against double-get, as the function is called from both the + * .atomic_enable() and .atomic_begin() handlers. + */ + if (rcrtc->initialized) + return 0; + + ret = clk_prepare_enable(rcrtc->clock); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(rcrtc->extclock); + if (ret < 0) + goto error_clock; + + ret = rcar_du_group_get(rcrtc->group); + if (ret < 0) + goto error_group; + + rcar_du_crtc_setup(rcrtc); + rcrtc->initialized = true; + + return 0; + +error_group: + clk_disable_unprepare(rcrtc->extclock); +error_clock: + clk_disable_unprepare(rcrtc->clock); + return ret; +} + +static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc) +{ + rcar_du_group_put(rcrtc->group); + + clk_disable_unprepare(rcrtc->extclock); + clk_disable_unprepare(rcrtc->clock); + + rcrtc->initialized = false; +} + +static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc) +{ + bool interlaced; + + /* + * Select master sync mode. This enables display operation in master + * sync mode (with the HSYNC and VSYNC signals configured as outputs and + * actively driven). + */ + interlaced = rcrtc->crtc.mode.flags & DRM_MODE_FLAG_INTERLACE; + rcar_du_crtc_dsysr_clr_set(rcrtc, DSYSR_TVM_MASK | DSYSR_SCM_MASK, + (interlaced ? DSYSR_SCM_INT_VIDEO : 0) | + DSYSR_TVM_MASTER); + + rcar_du_group_start_stop(rcrtc->group, true); +} + +static void rcar_du_crtc_disable_planes(struct rcar_du_crtc *rcrtc) +{ + struct rcar_du_device *rcdu = rcrtc->dev; + struct drm_crtc *crtc = &rcrtc->crtc; + u32 status; + + /* Make sure vblank interrupts are enabled. */ + drm_crtc_vblank_get(crtc); + + /* + * Disable planes and calculate how many vertical blanking interrupts we + * have to wait for. If a vertical blanking interrupt has been triggered + * but not processed yet, we don't know whether it occurred before or + * after the planes got disabled. We thus have to wait for two vblank + * interrupts in that case. + */ + spin_lock_irq(&rcrtc->vblank_lock); + rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, 0); + status = rcar_du_crtc_read(rcrtc, DSSR); + rcrtc->vblank_count = status & DSSR_VBK ? 2 : 1; + spin_unlock_irq(&rcrtc->vblank_lock); + + if (!wait_event_timeout(rcrtc->vblank_wait, rcrtc->vblank_count == 0, + msecs_to_jiffies(100))) + dev_warn(rcdu->dev, "vertical blanking timeout\n"); + + drm_crtc_vblank_put(crtc); +} + +static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc) +{ + struct drm_crtc *crtc = &rcrtc->crtc; + + /* + * Disable all planes and wait for the change to take effect. This is + * required as the plane enable registers are updated on vblank, and no + * vblank will occur once the CRTC is stopped. Disabling planes when + * starting the CRTC thus wouldn't be enough as it would start scanning + * out immediately from old frame buffers until the next vblank. + * + * This increases the CRTC stop delay, especially when multiple CRTCs + * are stopped in one operation as we now wait for one vblank per CRTC. + * Whether this can be improved needs to be researched. + */ + rcar_du_crtc_disable_planes(rcrtc); + + /* + * Disable vertical blanking interrupt reporting. We first need to wait + * for page flip completion before stopping the CRTC as userspace + * expects page flips to eventually complete. + */ + rcar_du_crtc_wait_page_flip(rcrtc); + drm_crtc_vblank_off(crtc); + + /* Disable the VSP compositor. */ + if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE)) + rcar_du_vsp_disable(rcrtc); + + if (rcrtc->cmm) + rcar_cmm_disable(rcrtc->cmm->dev); + + /* + * Select switch sync mode. This stops display operation and configures + * the HSYNC and VSYNC signals as inputs. + * + * TODO: Find another way to stop the display for DUs that don't support + * TVM sync. + */ + if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_TVM_SYNC)) + rcar_du_crtc_dsysr_clr_set(rcrtc, DSYSR_TVM_MASK, + DSYSR_TVM_SWITCH); + + rcar_du_group_start_stop(rcrtc->group, false); +} + +/* ----------------------------------------------------------------------------- + * CRTC Functions + */ + +static int rcar_du_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + struct rcar_du_crtc_state *rstate = to_rcar_crtc_state(crtc_state); + struct drm_encoder *encoder; + int ret; + + ret = rcar_du_cmm_check(crtc, crtc_state); + if (ret) + return ret; + + /* Store the routes from the CRTC output to the DU outputs. */ + rstate->outputs = 0; + + drm_for_each_encoder_mask(encoder, crtc->dev, + crtc_state->encoder_mask) { + struct rcar_du_encoder *renc; + + /* Skip the writeback encoder. */ + if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL) + continue; + + renc = to_rcar_encoder(encoder); + rstate->outputs |= BIT(renc->output); + } + + return 0; +} + +static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct rcar_du_crtc *rcrtc = to_rcar_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->dev); + rcar_du_crtc_get(rcrtc); + + /* + * On D3/E3 the dot clock is provided by the LVDS encoder attached to + * the DU channel. We need to enable its clock output explicitly before + * starting the CRTC, as the bridge hasn't been enabled by the atomic + * helpers yet. + */ + if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index)) { + bool dot_clk_only = rstate->outputs == BIT(RCAR_DU_OUTPUT_DPAD0); + struct drm_bridge *bridge = rcdu->lvds[rcrtc->index]; + const struct drm_display_mode *mode = + &crtc->state->adjusted_mode; + + rcar_lvds_pclk_enable(bridge, mode->clock * 1000, dot_clk_only); + } + + /* + * Similarly to LVDS, on V3U the dot clock is provided by the DSI + * encoder, and we need to enable the DSI clocks before enabling the CRTC. + */ + if ((rcdu->info->dsi_clk_mask & BIT(rcrtc->index)) && + (rstate->outputs & + (BIT(RCAR_DU_OUTPUT_DSI0) | BIT(RCAR_DU_OUTPUT_DSI1)))) { + struct drm_bridge *bridge = rcdu->dsi[rcrtc->index]; + + rcar_mipi_dsi_pclk_enable(bridge, state); + } + + 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, + struct drm_atomic_commit *state) +{ + struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, + crtc); + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + struct rcar_du_crtc_state *rstate = to_rcar_crtc_state(old_state); + struct rcar_du_device *rcdu = rcrtc->dev; + + rcar_du_crtc_stop(rcrtc); + rcar_du_crtc_put(rcrtc); + + if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index)) { + bool dot_clk_only = rstate->outputs == BIT(RCAR_DU_OUTPUT_DPAD0); + struct drm_bridge *bridge = rcdu->lvds[rcrtc->index]; + + /* + * Disable the LVDS clock output, see + * rcar_du_crtc_atomic_enable(). When the LVDS output is used, + * this also disables the LVDS encoder. + */ + rcar_lvds_pclk_disable(bridge, dot_clk_only); + } + + if ((rcdu->info->dsi_clk_mask & BIT(rcrtc->index)) && + (rstate->outputs & + (BIT(RCAR_DU_OUTPUT_DSI0) | BIT(RCAR_DU_OUTPUT_DSI1)))) { + struct drm_bridge *bridge = rcdu->dsi[rcrtc->index]; + + /* + * Disable the DSI clock output, see + * rcar_du_crtc_atomic_enable(). + */ + rcar_mipi_dsi_pclk_disable(bridge); + } + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + WARN_ON(!crtc->state->enable); + + /* + * If a mode set is in progress we can be called with the CRTC disabled. + * We thus need to first get and setup the CRTC in order to configure + * planes. We must *not* put the CRTC in .atomic_flush(), as it must be + * kept awake until the .atomic_enable() call that will follow. The get + * operation in .atomic_enable() will in that case be a no-op, and the + * CRTC will be put later in .atomic_disable(). + * + * If a mode set is not in progress the CRTC is enabled, and the + * following get call will be a no-op. There is thus no need to balance + * it in .atomic_flush() either. + */ + 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); +} + +static void rcar_du_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + rcar_du_crtc_update_planes(rcrtc); + + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&dev->event_lock, flags); + rcrtc->event = crtc->state->event; + crtc->state->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + } + + if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE)) + rcar_du_vsp_atomic_flush(rcrtc); +} + +static enum drm_mode_status +rcar_du_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + struct rcar_du_device *rcdu = rcrtc->dev; + bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; + unsigned int min_sync_porch; + unsigned int vbp; + + if (interlaced && !rcar_du_has(rcdu, RCAR_DU_FEATURE_INTERLACED)) + return MODE_NO_INTERLACE; + + /* + * The hardware requires a minimum combined horizontal sync and back + * porch of 20 pixels (when CMM isn't used) or 45 pixels (when CMM is + * used), and a minimum vertical back porch of 3 lines. + */ + min_sync_porch = 20; + if (rcrtc->group->cmms_mask & BIT(rcrtc->index % 2)) + min_sync_porch += 25; + + if (mode->htotal - mode->hsync_start < min_sync_porch) + return MODE_HBLANK_NARROW; + + vbp = (mode->vtotal - mode->vsync_end) / (interlaced ? 2 : 1); + if (vbp < 3) + return MODE_VBLANK_NARROW; + + return MODE_OK; +} + +static const struct drm_crtc_helper_funcs crtc_helper_funcs = { + .atomic_check = rcar_du_crtc_atomic_check, + .atomic_begin = rcar_du_crtc_atomic_begin, + .atomic_flush = rcar_du_crtc_atomic_flush, + .atomic_enable = rcar_du_crtc_atomic_enable, + .atomic_disable = rcar_du_crtc_atomic_disable, + .mode_valid = rcar_du_crtc_mode_valid, +}; + +static void rcar_du_crtc_crc_init(struct rcar_du_crtc *rcrtc) +{ + struct rcar_du_device *rcdu = rcrtc->dev; + const char **sources; + unsigned int count; + int i = -1; + + /* CRC available only on Gen3 HW. */ + if (rcdu->info->gen < 3) + return; + + /* Reserve 1 for "auto" source. */ + count = rcrtc->vsp->num_planes + 1; + + sources = kmalloc_array(count, sizeof(*sources), GFP_KERNEL); + if (!sources) + return; + + sources[0] = kstrdup("auto", GFP_KERNEL); + if (!sources[0]) + goto error; + + for (i = 0; i < rcrtc->vsp->num_planes; ++i) { + struct drm_plane *plane = &rcrtc->vsp->planes[i].plane; + char name[16]; + + sprintf(name, "plane%u", plane->base.id); + sources[i + 1] = kstrdup(name, GFP_KERNEL); + if (!sources[i + 1]) + goto error; + } + + rcrtc->sources = sources; + rcrtc->sources_count = count; + return; + +error: + while (i >= 0) { + kfree(sources[i]); + i--; + } + kfree(sources); +} + +static void rcar_du_crtc_crc_cleanup(struct rcar_du_crtc *rcrtc) +{ + unsigned int i; + + if (!rcrtc->sources) + return; + + for (i = 0; i < rcrtc->sources_count; i++) + kfree(rcrtc->sources[i]); + kfree(rcrtc->sources); + + rcrtc->sources = NULL; + rcrtc->sources_count = 0; +} + +static struct drm_crtc_state * +rcar_du_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct rcar_du_crtc_state *state; + struct rcar_du_crtc_state *copy; + + if (WARN_ON(!crtc->state)) + return NULL; + + state = to_rcar_crtc_state(crtc->state); + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (copy == NULL) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, ©->state); + + return ©->state; +} + +static void rcar_du_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + __drm_atomic_helper_crtc_destroy_state(state); + kfree(to_rcar_crtc_state(state)); +} + +static void rcar_du_crtc_cleanup(struct drm_crtc *crtc) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + rcar_du_crtc_crc_cleanup(rcrtc); + + drm_crtc_cleanup(crtc); +} + +static void rcar_du_crtc_reset(struct drm_crtc *crtc) +{ + struct rcar_du_crtc_state *state; + + if (crtc->state) { + rcar_du_crtc_atomic_destroy_state(crtc, crtc->state); + crtc->state = NULL; + } + + state = kzalloc_obj(*state); + if (state == NULL) + return; + + state->crc.source = VSP1_DU_CRC_NONE; + state->crc.index = 0; + + __drm_atomic_helper_crtc_reset(crtc, &state->state); +} + +static int rcar_du_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + rcar_du_crtc_write(rcrtc, DSRCR, DSRCR_VBCL); + rcar_du_crtc_set(rcrtc, DIER, DIER_VBE); + rcrtc->vblank_enable = true; + + return 0; +} + +static void rcar_du_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + rcar_du_crtc_clr(rcrtc, DIER, DIER_VBE); + rcrtc->vblank_enable = false; +} + +static int rcar_du_crtc_parse_crc_source(struct rcar_du_crtc *rcrtc, + const char *source_name, + enum vsp1_du_crc_source *source) +{ + unsigned int index; + int ret; + + /* + * Parse the source name. Supported values are "plane%u" to compute the + * CRC on an input plane (%u is the plane ID), and "auto" to compute the + * CRC on the composer (VSP) output. + */ + + if (!source_name) { + *source = VSP1_DU_CRC_NONE; + return 0; + } else if (!strcmp(source_name, "auto")) { + *source = VSP1_DU_CRC_OUTPUT; + return 0; + } else if (strstarts(source_name, "plane")) { + unsigned int i; + + *source = VSP1_DU_CRC_PLANE; + + ret = kstrtouint(source_name + strlen("plane"), 10, &index); + if (ret < 0) + return ret; + + for (i = 0; i < rcrtc->vsp->num_planes; ++i) { + if (index == rcrtc->vsp->planes[i].plane.base.id) + return i; + } + } + + return -EINVAL; +} + +static int rcar_du_crtc_verify_crc_source(struct drm_crtc *crtc, + const char *source_name, + size_t *values_cnt) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + enum vsp1_du_crc_source source; + + if (rcar_du_crtc_parse_crc_source(rcrtc, source_name, &source) < 0) { + DRM_DEBUG_DRIVER("unknown source %s\n", source_name); + return -EINVAL; + } + + *values_cnt = 1; + return 0; +} + +static const char *const * +rcar_du_crtc_get_crc_sources(struct drm_crtc *crtc, size_t *count) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + *count = rcrtc->sources_count; + return rcrtc->sources; +} + +static int rcar_du_crtc_set_crc_source(struct drm_crtc *crtc, + const char *source_name) +{ + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + struct drm_modeset_acquire_ctx ctx; + struct drm_crtc_state *crtc_state; + struct drm_atomic_commit *state; + enum vsp1_du_crc_source source; + unsigned int index; + int ret; + + ret = rcar_du_crtc_parse_crc_source(rcrtc, source_name, &source); + if (ret < 0) + return ret; + + index = ret; + + /* Perform an atomic commit to set the CRC source. */ + drm_modeset_acquire_init(&ctx, 0); + + state = drm_atomic_commit_alloc(crtc->dev); + if (!state) { + ret = -ENOMEM; + goto unlock; + } + + state->acquire_ctx = &ctx; + +retry: + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (!IS_ERR(crtc_state)) { + struct rcar_du_crtc_state *rcrtc_state; + + rcrtc_state = to_rcar_crtc_state(crtc_state); + rcrtc_state->crc.source = source; + rcrtc_state->crc.index = index; + + ret = drm_atomic_commit(state); + } else { + ret = PTR_ERR(crtc_state); + } + + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + drm_modeset_backoff(&ctx); + goto retry; + } + + drm_atomic_commit_put(state); + +unlock: + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + return ret; +} + +static const struct drm_crtc_funcs crtc_funcs_gen2 = { + .reset = rcar_du_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = rcar_du_crtc_atomic_duplicate_state, + .atomic_destroy_state = rcar_du_crtc_atomic_destroy_state, + .enable_vblank = rcar_du_crtc_enable_vblank, + .disable_vblank = rcar_du_crtc_disable_vblank, +}; + +static const struct drm_crtc_funcs crtc_funcs_gen3 = { + .reset = rcar_du_crtc_reset, + .destroy = rcar_du_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = rcar_du_crtc_atomic_duplicate_state, + .atomic_destroy_state = rcar_du_crtc_atomic_destroy_state, + .enable_vblank = rcar_du_crtc_enable_vblank, + .disable_vblank = rcar_du_crtc_disable_vblank, + .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, +}; + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static irqreturn_t rcar_du_crtc_irq(int irq, void *arg) +{ + struct rcar_du_crtc *rcrtc = arg; + struct rcar_du_device *rcdu = rcrtc->dev; + irqreturn_t ret = IRQ_NONE; + u32 status; + + spin_lock(&rcrtc->vblank_lock); + + status = rcar_du_crtc_read(rcrtc, DSSR); + rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK); + + if (status & DSSR_VBK) { + /* + * Wake up the vblank wait if the counter reaches 0. This must + * be protected by the vblank_lock to avoid races in + * rcar_du_crtc_disable_planes(). + */ + if (rcrtc->vblank_count) { + if (--rcrtc->vblank_count == 0) + wake_up(&rcrtc->vblank_wait); + } + } + + spin_unlock(&rcrtc->vblank_lock); + + if (status & DSSR_VBK) { + if (rcdu->info->gen < 3) { + drm_crtc_handle_vblank(&rcrtc->crtc); + rcar_du_crtc_finish_page_flip(rcrtc); + } + + ret = IRQ_HANDLED; + } + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex, + unsigned int hwindex) +{ + static const unsigned int mmio_offsets[] = { + DU0_REG_OFFSET, DU1_REG_OFFSET, DU2_REG_OFFSET, DU3_REG_OFFSET + }; + + struct rcar_du_device *rcdu = rgrp->dev; + struct platform_device *pdev = to_platform_device(rcdu->dev); + struct rcar_du_crtc *rcrtc = &rcdu->crtcs[swindex]; + struct drm_crtc *crtc = &rcrtc->crtc; + struct drm_plane *primary; + unsigned int irqflags; + struct clk *clk; + char clk_name[9]; + char *name; + int irq; + int ret; + + /* Get the CRTC clock and the optional external clock. */ + if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CRTC_CLOCK)) { + sprintf(clk_name, "du.%u", hwindex); + name = clk_name; + } else { + name = NULL; + } + + rcrtc->clock = devm_clk_get(rcdu->dev, name); + if (IS_ERR(rcrtc->clock)) { + dev_err(rcdu->dev, "no clock for DU channel %u\n", hwindex); + return PTR_ERR(rcrtc->clock); + } + + sprintf(clk_name, "dclkin.%u", hwindex); + clk = devm_clk_get(rcdu->dev, clk_name); + if (!IS_ERR(clk)) { + rcrtc->extclock = clk; + } else if (PTR_ERR(clk) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (rcdu->info->dpll_mask & BIT(hwindex)) { + /* + * DU channels that have a display PLL can't use the internal + * system clock and thus require an external clock. + */ + ret = PTR_ERR(clk); + dev_err(rcdu->dev, "can't get dclkin.%u: %d\n", hwindex, ret); + return ret; + } + + init_waitqueue_head(&rcrtc->flip_wait); + init_waitqueue_head(&rcrtc->vblank_wait); + spin_lock_init(&rcrtc->vblank_lock); + + rcrtc->dev = rcdu; + rcrtc->group = rgrp; + rcrtc->mmio_offset = mmio_offsets[hwindex]; + rcrtc->index = hwindex; + rcrtc->dsysr = rcrtc->index % 2 ? 0 : DSYSR_DRES; + + if (rcar_du_has(rcdu, RCAR_DU_FEATURE_TVM_SYNC)) + rcrtc->dsysr |= DSYSR_TVM_TVSYNC; + + if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE)) + primary = &rcrtc->vsp->planes[rcrtc->vsp_pipe].plane; + else + primary = &rgrp->planes[swindex % 2].plane; + + ret = drm_crtc_init_with_planes(&rcdu->ddev, crtc, primary, NULL, + rcdu->info->gen <= 2 ? + &crtc_funcs_gen2 : &crtc_funcs_gen3, + NULL); + if (ret < 0) + return ret; + + /* CMM might be disabled for this CRTC. */ + if (rcdu->cmms[swindex].dev) { + 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); + + /* Register the interrupt handler. */ + if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CRTC_IRQ)) { + /* The IRQ's are associated with the CRTC (sw)index. */ + irq = platform_get_irq(pdev, swindex); + irqflags = 0; + } else { + irq = platform_get_irq(pdev, 0); + irqflags = IRQF_SHARED; + } + + if (irq < 0) { + dev_err(rcdu->dev, "no IRQ for CRTC %u\n", swindex); + return irq; + } + + ret = devm_request_irq(rcdu->dev, irq, rcar_du_crtc_irq, irqflags, + dev_name(rcdu->dev), rcrtc); + if (ret < 0) { + dev_err(rcdu->dev, + "failed to register IRQ for CRTC %u\n", swindex); + return ret; + } + + rcar_du_crtc_crc_init(rcrtc); + + return 0; +} diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/renesas/rcar-du/rcar_du_crtc.h new file mode 100644 index 000000000..8857926e1 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_crtc.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * R-Car Display Unit CRTCs + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __RCAR_DU_CRTC_H__ +#define __RCAR_DU_CRTC_H__ + +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/wait.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_writeback.h> + +#include <media/vsp1.h> + +struct rcar_du_cmm; +struct rcar_du_group; +struct rcar_du_vsp; + +/** + * struct rcar_du_crtc - the CRTC, representing a DU superposition processor + * @crtc: base DRM CRTC + * @dev: the DU device + * @clock: the CRTC functional clock + * @extclock: external pixel dot clock (optional) + * @mmio_offset: offset of the CRTC registers in the DU MMIO block + * @index: CRTC hardware index + * @initialized: whether the CRTC has been initialized and clocks enabled + * @dsysr: cached value of the DSYSR register + * @vblank_enable: whether vblank events are enabled on this CRTC + * @event: event to post when the pending page flip completes + * @flip_wait: wait queue used to signal page flip completion + * @vblank_lock: protects vblank_wait and vblank_count + * @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 + */ +struct rcar_du_crtc { + struct drm_crtc crtc; + + struct rcar_du_device *dev; + struct clk *clock; + struct clk *extclock; + unsigned int mmio_offset; + unsigned int index; + bool initialized; + + u32 dsysr; + + bool vblank_enable; + struct drm_pending_vblank_event *event; + wait_queue_head_t flip_wait; + + spinlock_t vblank_lock; + wait_queue_head_t vblank_wait; + unsigned int vblank_count; + + struct rcar_du_group *group; + struct rcar_du_cmm *cmm; + struct rcar_du_vsp *vsp; + unsigned int vsp_pipe; + + const char *const *sources; + unsigned int sources_count; + + struct drm_writeback_connector writeback; +}; + +#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc) +#define wb_to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, writeback) + +/** + * struct rcar_du_crtc_state - Driver-specific CRTC state + * @state: base DRM CRTC state + * @crc: CRC computation configuration + * @outputs: bitmask of the outputs (enum rcar_du_output) driven by this CRTC + */ +struct rcar_du_crtc_state { + struct drm_crtc_state state; + + struct vsp1_du_crc_config crc; + unsigned int outputs; +}; + +#define to_rcar_crtc_state(s) container_of(s, struct rcar_du_crtc_state, state) + +int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex, + unsigned int hwindex); + +void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc); + +void rcar_du_crtc_dsysr_clr_set(struct rcar_du_crtc *rcrtc, u32 clr, u32 set); + +#endif /* __RCAR_DU_CRTC_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_drv.c new file mode 100644 index 000000000..031d07f45 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_drv.c @@ -0,0 +1,763 @@ +// 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.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_dma.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_print.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 rcar_du_device_info rcar_du_r8a779h0_info = { + .gen = 4, + .features = RCAR_DU_FEATURE_CRTC_IRQ + | RCAR_DU_FEATURE_VSP1_SOURCE + | RCAR_DU_FEATURE_NO_BLENDING, + .channels_mask = BIT(0), + .routes = { + /* R8A779H0 has one MIPI DSI output. */ + [RCAR_DU_OUTPUT_DSI0] = { + .possible_crtcs = BIT(0), + .port = 0, + }, + }, + .num_rpf = 5, + .dsi_clk_mask = 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 }, + { .compatible = "renesas,du-r8a779h0", .data = &rcar_du_r8a779h0_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, + .gem_prime_import_sg_table = rcar_du_gem_prime_import_sg_table, + DRM_FBDEV_DMA_DRIVER_OPS, + .fops = &rcar_du_fops, + .name = "rcar-du", + .desc = "Renesas R-Car Display Unit", + .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 + */ + +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); +} + +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) { + /* + * Don't use dev_err_probe(), as it would overwrite the probe + * deferral reason recorded in rcar_du_modeset_init(). + */ + 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(&rcdu->ddev, "Device %s probed\n", dev_name(&pdev->dev)); + + drm_client_setup(&rcdu->ddev, NULL); + + 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 = 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"); diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/renesas/rcar-du/rcar_du_drv.h new file mode 100644 index 000000000..de9c6617a --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_drv.h @@ -0,0 +1,158 @@ +/* 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) + */ + +#ifndef __RCAR_DU_DRV_H__ +#define __RCAR_DU_DRV_H__ + +#include <linux/kernel.h> +#include <linux/wait.h> + +#include <drm/drm_device.h> + +#include "rcar_cmm.h" +#include "rcar_du_crtc.h" +#include "rcar_du_group.h" +#include "rcar_du_vsp.h" + +struct clk; +struct device; +struct device_link; +struct drm_bridge; +struct drm_property; +struct rcar_du_device; + +#define RCAR_DU_FEATURE_CRTC_IRQ BIT(0) /* Per-CRTC IRQ */ +#define RCAR_DU_FEATURE_CRTC_CLOCK BIT(1) /* Per-CRTC clock */ +#define RCAR_DU_FEATURE_VSP1_SOURCE BIT(2) /* Has inputs from VSP1 */ +#define RCAR_DU_FEATURE_INTERLACED BIT(3) /* HW supports interlaced */ +#define RCAR_DU_FEATURE_TVM_SYNC BIT(4) /* Has TV switch/sync modes */ +#define RCAR_DU_FEATURE_NO_BLENDING BIT(5) /* PnMR.SPIM does not have ALP nor EOR bits */ + +#define RCAR_DU_QUIRK_ALIGN_128B BIT(0) /* Align pitches to 128 bytes */ + +enum rcar_du_output { + RCAR_DU_OUTPUT_DPAD0, + RCAR_DU_OUTPUT_DPAD1, + RCAR_DU_OUTPUT_DSI0, + RCAR_DU_OUTPUT_DSI1, + RCAR_DU_OUTPUT_HDMI0, + RCAR_DU_OUTPUT_HDMI1, + RCAR_DU_OUTPUT_LVDS0, + RCAR_DU_OUTPUT_LVDS1, + RCAR_DU_OUTPUT_TCON, + RCAR_DU_OUTPUT_MAX, +}; + +/* + * struct rcar_du_output_routing - Output routing specification + * @possible_crtcs: bitmask of possible CRTCs for the output + * @port: device tree port number corresponding to this output route + * + * The DU has 5 possible outputs (DPAD0/1, LVDS0/1, TCON). Output routing data + * specify the valid SoC outputs, which CRTCs can drive the output, and the type + * of in-SoC encoder for the output. + */ +struct rcar_du_output_routing { + unsigned int possible_crtcs; + unsigned int port; +}; + +/* + * struct rcar_du_device_info - DU model-specific information + * @gen: device generation (2 or 3) + * @features: device features (RCAR_DU_FEATURE_*) + * @quirks: device quirks (RCAR_DU_QUIRK_*) + * @channels_mask: bit mask of available DU channels + * @routes: array of CRTC to output routes, indexed by output (RCAR_DU_OUTPUT_*) + * @num_lvds: number of internal LVDS encoders + * @num_rpf: number of RPFs in VSP + * @dpll_mask: bit mask of DU channels equipped with a DPLL + * @dsi_clk_mask: bitmask of channels that can use the DSI clock as dot clock + * @lvds_clk_mask: bitmask of channels that can use the LVDS clock as dot clock + */ +struct rcar_du_device_info { + unsigned int gen; + unsigned int features; + unsigned int quirks; + unsigned int channels_mask; + struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX]; + unsigned int num_lvds; + unsigned int num_rpf; + unsigned int dpll_mask; + unsigned int dsi_clk_mask; + unsigned int lvds_clk_mask; +}; + +struct rcar_du_cmm { + struct device *dev; + struct device_link *link; +}; + +#define RCAR_DU_MAX_CRTCS 4 +#define RCAR_DU_MAX_GROUPS DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2) +#define RCAR_DU_MAX_VSPS 4 +#define RCAR_DU_MAX_LVDS 2 +#define RCAR_DU_MAX_DSI 2 + +struct rcar_du_device { + struct device *dev; + const struct rcar_du_device_info *info; + + void __iomem *mmio; + + struct drm_device ddev; + + struct rcar_du_crtc crtcs[RCAR_DU_MAX_CRTCS]; + unsigned int num_crtcs; + + struct rcar_du_group groups[RCAR_DU_MAX_GROUPS]; + struct rcar_du_cmm cmms[RCAR_DU_MAX_CRTCS]; + struct rcar_du_vsp vsps[RCAR_DU_MAX_VSPS]; + struct drm_bridge *lvds[RCAR_DU_MAX_LVDS]; + struct drm_bridge *dsi[RCAR_DU_MAX_DSI]; + + struct { + struct drm_property *colorkey; + } props; + + unsigned int dpad0_source; + unsigned int dpad1_source; + unsigned int vspd1_sink; +}; + +static inline struct rcar_du_device *to_rcar_du_device(struct drm_device *dev) +{ + return container_of(dev, struct rcar_du_device, ddev); +} + +static inline bool rcar_du_has(struct rcar_du_device *rcdu, + unsigned int feature) +{ + return rcdu->info->features & feature; +} + +static inline bool rcar_du_needs(struct rcar_du_device *rcdu, + unsigned int quirk) +{ + return rcdu->info->quirks & quirk; +} + +static inline u32 rcar_du_read(struct rcar_du_device *rcdu, u32 reg) +{ + return ioread32(rcdu->mmio + reg); +} + +static inline void rcar_du_write(struct rcar_du_device *rcdu, u32 reg, u32 data) +{ + iowrite32(data, rcdu->mmio + reg); +} + +const char *rcar_du_output_name(enum rcar_du_output output); + +#endif /* __RCAR_DU_DRV_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c new file mode 100644 index 000000000..db2088529 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * R-Car Display Unit Encoder + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include <linux/export.h> +#include <linux/of.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_panel.h> + +#include "rcar_du_drv.h" +#include "rcar_du_encoder.h" +#include "rcar_lvds.h" + +/* ----------------------------------------------------------------------------- + * Encoder + */ + +static unsigned int rcar_du_encoder_count_ports(struct device_node *node) +{ + struct device_node *ports; + struct device_node *port; + unsigned int num_ports = 0; + + ports = of_get_child_by_name(node, "ports"); + if (!ports) + ports = of_node_get(node); + + for_each_child_of_node(ports, port) { + if (of_node_name_eq(port, "port")) + num_ports++; + } + + of_node_put(ports); + + return num_ports; +} + +static const struct drm_encoder_funcs rcar_du_encoder_funcs = { +}; + +int rcar_du_encoder_init(struct rcar_du_device *rcdu, + enum rcar_du_output output, + struct device_node *enc_node) +{ + struct rcar_du_encoder *renc; + struct drm_connector *connector; + struct drm_bridge *bridge __free(drm_bridge_put) = NULL; + int ret; + + /* + * Locate the DRM bridge from the DT node. For the DPAD outputs, if the + * DT node has a single port, assume that it describes a panel and + * create a panel bridge. + */ + if ((output == RCAR_DU_OUTPUT_DPAD0 || + output == RCAR_DU_OUTPUT_DPAD1) && + rcar_du_encoder_count_ports(enc_node) == 1) { + struct drm_panel *panel = of_drm_find_panel(enc_node); + + if (IS_ERR(panel)) + return PTR_ERR(panel); + + bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(bridge)) + return PTR_ERR(no_free_ptr(bridge)); + + /* + * The reference taken by devm_drm_panel_bridge_add_typed() is + * released automatically. Take a second one for the __free() + * when this function will return. + */ + drm_bridge_get(bridge); + } else { + bridge = of_drm_find_and_get_bridge(enc_node); + if (!bridge) + return -EPROBE_DEFER; + + if (output == RCAR_DU_OUTPUT_LVDS0 || + output == RCAR_DU_OUTPUT_LVDS1) + rcdu->lvds[output - RCAR_DU_OUTPUT_LVDS0] = + drm_bridge_get(bridge); + + if (output == RCAR_DU_OUTPUT_DSI0 || + output == RCAR_DU_OUTPUT_DSI1) + rcdu->dsi[output - RCAR_DU_OUTPUT_DSI0] = + drm_bridge_get(bridge); + } + + /* + * Create and initialize the encoder. On Gen3, skip the LVDS1 output if + * the LVDS1 encoder is used as a companion for LVDS0 in dual-link + * mode, or any LVDS output if it isn't connected. The latter may happen + * on D3 or E3 as the LVDS encoders are needed to provide the pixel + * clock to the DU, even when the LVDS outputs are not used. + */ + if (rcdu->info->gen >= 3) { + if (output == RCAR_DU_OUTPUT_LVDS1 && + rcar_lvds_dual_link(bridge)) + return -ENOLINK; + + if ((output == RCAR_DU_OUTPUT_LVDS0 || + output == RCAR_DU_OUTPUT_LVDS1) && + !rcar_lvds_is_connected(bridge)) + return -ENOLINK; + } + + dev_dbg(rcdu->dev, "initializing encoder %pOF for output %s\n", + enc_node, rcar_du_output_name(output)); + + renc = drmm_encoder_alloc(&rcdu->ddev, struct rcar_du_encoder, base, + &rcar_du_encoder_funcs, DRM_MODE_ENCODER_NONE, + NULL); + if (IS_ERR(renc)) + return PTR_ERR(renc); + + renc->output = output; + + /* Attach the bridge to the encoder. */ + ret = drm_bridge_attach(&renc->base, bridge, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + dev_err(rcdu->dev, + "failed to attach bridge %pOF for output %s (%d)\n", + bridge->of_node, rcar_du_output_name(output), ret); + return ret; + } + + /* Create the connector for the chain of bridges. */ + connector = drm_bridge_connector_init(&rcdu->ddev, &renc->base); + if (IS_ERR(connector)) { + dev_err(rcdu->dev, + "failed to created connector for output %s (%ld)\n", + rcar_du_output_name(output), PTR_ERR(connector)); + return PTR_ERR(connector); + } + + return 0; +} + +void rcar_du_encoder_cleanup(struct rcar_du_device *rcdu) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcdu->lvds); i++) + drm_bridge_put(rcdu->lvds[i]); + for (i = 0; i < ARRAY_SIZE(rcdu->dsi); i++) + drm_bridge_put(rcdu->dsi[i]); +} diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.h new file mode 100644 index 000000000..b2b5e93f3 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * R-Car Display Unit Encoder + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __RCAR_DU_ENCODER_H__ +#define __RCAR_DU_ENCODER_H__ + +#include <drm/drm_encoder.h> + +struct rcar_du_device; + +struct rcar_du_encoder { + struct drm_encoder base; + enum rcar_du_output output; +}; + +#define to_rcar_encoder(e) \ + container_of(e, struct rcar_du_encoder, base) + +int rcar_du_encoder_init(struct rcar_du_device *rcdu, + enum rcar_du_output output, + struct device_node *enc_node); +void rcar_du_encoder_cleanup(struct rcar_du_device *rcdu); + +#endif /* __RCAR_DU_ENCODER_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_group.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_group.c new file mode 100644 index 000000000..068c106e5 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_group.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * R-Car Display Unit Channels Pair + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +/* + * The R8A7779 DU is split in per-CRTC resources (scan-out engine, blending + * unit, timings generator, ...) and device-global resources (start/stop + * control, planes, ...) shared between the two CRTCs. + * + * The R8A7790 introduced a third CRTC with its own set of global resources. + * This would be modeled as two separate DU device instances if it wasn't for + * a handful or resources that are shared between the three CRTCs (mostly + * related to input and output routing). For this reason the R8A7790 DU must be + * modeled as a single device with three CRTCs, two sets of "semi-global" + * resources, and a few device-global resources. + * + * The rcar_du_group object is a driver specific object, without any real + * counterpart in the DU documentation, that models those semi-global resources. + */ + +#include <linux/clk.h> +#include <linux/io.h> + +#include "rcar_du_drv.h" +#include "rcar_du_group.h" +#include "rcar_du_regs.h" + +u32 rcar_du_group_read(struct rcar_du_group *rgrp, u32 reg) +{ + return rcar_du_read(rgrp->dev, rgrp->mmio_offset + reg); +} + +void rcar_du_group_write(struct rcar_du_group *rgrp, u32 reg, u32 data) +{ + rcar_du_write(rgrp->dev, rgrp->mmio_offset + reg, data); +} + +static void rcar_du_group_setup_pins(struct rcar_du_group *rgrp) +{ + u32 defr6 = DEFR6_CODE; + + if (rgrp->channels_mask & BIT(0)) + defr6 |= DEFR6_ODPM02_DISP; + + if (rgrp->channels_mask & BIT(1)) + defr6 |= DEFR6_ODPM12_DISP; + + rcar_du_group_write(rgrp, DEFR6, defr6); +} + +static void rcar_du_group_setup_defr8(struct rcar_du_group *rgrp) +{ + struct rcar_du_device *rcdu = rgrp->dev; + u32 defr8 = DEFR8_CODE; + + if (rcdu->info->gen < 3) { + defr8 |= DEFR8_DEFE8; + + /* + * On Gen2 the DEFR8 register for the first group also controls + * RGB output routing to DPAD0 and VSPD1 routing to DU0/1/2 for + * DU instances that support it. + */ + if (rgrp->index == 0) { + defr8 |= DEFR8_DRGBS_DU(rcdu->dpad0_source); + if (rgrp->dev->vspd1_sink == 2) + defr8 |= DEFR8_VSCS; + } + } else { + /* + * On Gen3 VSPD routing can't be configured, and DPAD routing + * is set in the group corresponding to the DPAD output (no Gen3 + * SoC has multiple DPAD sources belonging to separate groups). + */ + if (rgrp->index == rcdu->dpad0_source / 2) + defr8 |= DEFR8_DRGBS_DU(rcdu->dpad0_source); + } + + rcar_du_group_write(rgrp, DEFR8, defr8); +} + +static void rcar_du_group_setup_didsr(struct rcar_du_group *rgrp) +{ + struct rcar_du_device *rcdu = rgrp->dev; + struct rcar_du_crtc *rcrtc; + unsigned int num_crtcs = 0; + unsigned int i; + u32 didsr; + + /* + * Configure input dot clock routing with a hardcoded configuration. If + * the DU channel can use the LVDS encoder output clock as the dot + * clock, do so. Otherwise route DU_DOTCLKINn signal to DUn. + * + * Each channel can then select between the dot clock configured here + * and the clock provided by the CPG through the ESCR register. + */ + if (rcdu->info->gen < 3 && rgrp->index == 0) { + /* + * On Gen2 a single register in the first group controls dot + * clock selection for all channels. + */ + rcrtc = rcdu->crtcs; + num_crtcs = rcdu->num_crtcs; + } else if ((rcdu->info->gen == 3 && rgrp->num_crtcs > 1) || + rcdu->info->gen == 4) { + /* + * On Gen3 dot clocks are setup through per-group registers, + * only available when the group has two channels. + * On Gen4 the registers are there for single channel too. + */ + rcrtc = &rcdu->crtcs[rgrp->index * 2]; + num_crtcs = rgrp->num_crtcs; + } + + if (!num_crtcs) + return; + + didsr = DIDSR_CODE; + for (i = 0; i < num_crtcs; ++i, ++rcrtc) { + if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index)) + didsr |= DIDSR_LDCS_LVDS0(i) + | DIDSR_PDCS_CLK(i, 0); + else if (rcdu->info->dsi_clk_mask & BIT(rcrtc->index)) + didsr |= DIDSR_LDCS_DSI(i); + else + didsr |= DIDSR_LDCS_DCLKIN(i) + | DIDSR_PDCS_CLK(i, 0); + } + + rcar_du_group_write(rgrp, DIDSR, didsr); +} + +static void rcar_du_group_setup(struct rcar_du_group *rgrp) +{ + struct rcar_du_device *rcdu = rgrp->dev; + u32 defr7 = DEFR7_CODE; + u32 dorcr; + + /* Enable extended features */ + rcar_du_group_write(rgrp, DEFR, DEFR_CODE | DEFR_DEFE); + if (rcdu->info->gen < 3) { + rcar_du_group_write(rgrp, DEFR2, DEFR2_CODE | DEFR2_DEFE2G); + rcar_du_group_write(rgrp, DEFR3, DEFR3_CODE | DEFR3_DEFE3); + rcar_du_group_write(rgrp, DEFR4, DEFR4_CODE); + } + rcar_du_group_write(rgrp, DEFR5, DEFR5_CODE | DEFR5_DEFE5); + + if (rcdu->info->gen < 4) + rcar_du_group_setup_pins(rgrp); + + if (rcdu->info->gen < 4) { + /* + * 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) { + if (rcdu->info->gen < 4) + rcar_du_group_setup_defr8(rgrp); + rcar_du_group_setup_didsr(rgrp); + } + + if (rcdu->info->gen >= 3) + rcar_du_group_write(rgrp, DEFR10, DEFR10_CODE | DEFR10_DEFE10); + + /* + * Use DS1PR and DS2PR to configure planes priorities and connects the + * superposition 0 to DU0 pins. DU1 pins will be configured dynamically. + * + * Groups that have a single channel have a hardcoded configuration. On + * Gen3 and newer, the documentation requires PG1T, DK1S and PG1D_DS1 to + * always be set in this case. + */ + dorcr = DORCR_PG0D_DS0 | DORCR_DPRS; + if (rcdu->info->gen >= 3 && rgrp->num_crtcs == 1) + dorcr |= DORCR_PG1T | DORCR_DK1S | DORCR_PG1D_DS1; + rcar_du_group_write(rgrp, DORCR, dorcr); + + /* + * DPTSR is used to select the source for the planes of a group. The + * first source is chosen by writing 0 to the respective bits, and this + * is always the default value of the register. In other words, writing + * DPTSR is only needed if the SoC supports choosing the second source. + * + * The SoCs documentations seems to confirm this, as the DPTSR register + * is not documented if only the first source exists on that SoC. + */ + if (rgrp->channels_mask & BIT(1)) { + mutex_lock(&rgrp->lock); + rcar_du_group_write(rgrp, DPTSR, (rgrp->dptsr_planes << 16) | + rgrp->dptsr_planes); + mutex_unlock(&rgrp->lock); + } +} + +/* + * rcar_du_group_get - Acquire a reference to the DU channels group + * + * Acquiring the first reference setups core registers. A reference must be held + * before accessing any hardware registers. + * + * This function must be called with the DRM mode_config lock held. + * + * Return 0 in case of success or a negative error code otherwise. + */ +int rcar_du_group_get(struct rcar_du_group *rgrp) +{ + if (rgrp->use_count) + goto done; + + rcar_du_group_setup(rgrp); + +done: + rgrp->use_count++; + return 0; +} + +/* + * rcar_du_group_put - Release a reference to the DU + * + * This function must be called with the DRM mode_config lock held. + */ +void rcar_du_group_put(struct rcar_du_group *rgrp) +{ + --rgrp->use_count; +} + +static void __rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start) +{ + struct rcar_du_device *rcdu = rgrp->dev; + + /* + * Group start/stop is controlled by the DRES and DEN bits of DSYSR0 + * for the first group and DSYSR2 for the second group. On most DU + * instances, this maps to the first CRTC of the group, and we can just + * use rcar_du_crtc_dsysr_clr_set() to access the correct DSYSR. On + * M3-N, however, DU2 doesn't exist, but DSYSR2 does. We thus need to + * access the register directly using group read/write. + */ + if (rcdu->info->channels_mask & BIT(rgrp->index * 2)) { + struct rcar_du_crtc *rcrtc = &rgrp->dev->crtcs[rgrp->index * 2]; + + rcar_du_crtc_dsysr_clr_set(rcrtc, DSYSR_DRES | DSYSR_DEN, + start ? DSYSR_DEN : DSYSR_DRES); + } else { + rcar_du_group_write(rgrp, DSYSR, + start ? DSYSR_DEN : DSYSR_DRES); + } +} + +void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start) +{ + /* + * Many of the configuration bits are only updated when the display + * reset (DRES) bit in DSYSR is set to 1, disabling *both* CRTCs. Some + * of those bits could be pre-configured, but others (especially the + * bits related to plane assignment to display timing controllers) need + * to be modified at runtime. + * + * Restart the display controller if a start is requested. Sorry for the + * flicker. It should be possible to move most of the "DRES-update" bits + * setup to driver initialization time and minimize the number of cases + * when the display controller will have to be restarted. + */ + if (start) { + if (rgrp->used_crtcs++ != 0) + __rcar_du_group_start_stop(rgrp, false); + __rcar_du_group_start_stop(rgrp, true); + } else { + if (--rgrp->used_crtcs == 0) + __rcar_du_group_start_stop(rgrp, false); + } +} + +void rcar_du_group_restart(struct rcar_du_group *rgrp) +{ + rgrp->need_restart = false; + + __rcar_du_group_start_stop(rgrp, false); + __rcar_du_group_start_stop(rgrp, true); +} + +int rcar_du_set_dpad0_vsp1_routing(struct rcar_du_device *rcdu) +{ + struct rcar_du_group *rgrp; + struct rcar_du_crtc *crtc; + unsigned int index; + int ret; + + if (rcdu->info->gen < 2) + return 0; + + /* + * RGB output routing to DPAD0 and VSP1D routing to DU0/1/2 are + * configured in the DEFR8 register of the first group on Gen2 and the + * last group on Gen3. As this function can be called with the DU + * channels of the corresponding CRTCs disabled, we need to enable the + * group clock before accessing the register. + */ + index = rcdu->info->gen < 3 ? 0 : DIV_ROUND_UP(rcdu->num_crtcs, 2) - 1; + rgrp = &rcdu->groups[index]; + crtc = &rcdu->crtcs[index * 2]; + + ret = clk_prepare_enable(crtc->clock); + if (ret < 0) + return ret; + + rcar_du_group_setup_defr8(rgrp); + + clk_disable_unprepare(crtc->clock); + + return 0; +} + +static void rcar_du_group_set_dpad_levels(struct rcar_du_group *rgrp) +{ + static const u32 doflr_values[2] = { + DOFLR_HSYCFL0 | DOFLR_VSYCFL0 | DOFLR_ODDFL0 | + DOFLR_DISPFL0 | DOFLR_CDEFL0 | DOFLR_RGBFL0, + DOFLR_HSYCFL1 | DOFLR_VSYCFL1 | DOFLR_ODDFL1 | + DOFLR_DISPFL1 | DOFLR_CDEFL1 | DOFLR_RGBFL1, + }; + static const u32 dpad_mask = BIT(RCAR_DU_OUTPUT_DPAD1) + | BIT(RCAR_DU_OUTPUT_DPAD0); + struct rcar_du_device *rcdu = rgrp->dev; + u32 doflr = DOFLR_CODE; + unsigned int i; + + if (rcdu->info->gen < 2) + return; + + /* + * The DPAD outputs can't be controlled directly. However, the parallel + * output of the DU channels routed to DPAD can be set to fixed levels + * through the DOFLR group register. Use this to turn the DPAD on or off + * by driving fixed low-level signals at the output of any DU channel + * not routed to a DPAD output. This doesn't affect the DU output + * signals going to other outputs, such as the internal LVDS and HDMI + * encoders. + */ + + for (i = 0; i < rgrp->num_crtcs; ++i) { + struct rcar_du_crtc_state *rstate; + struct rcar_du_crtc *rcrtc; + + rcrtc = &rcdu->crtcs[rgrp->index * 2 + i]; + rstate = to_rcar_crtc_state(rcrtc->crtc.state); + + if (!(rstate->outputs & dpad_mask)) + doflr |= doflr_values[i]; + } + + rcar_du_group_write(rgrp, DOFLR, doflr); +} + +int rcar_du_group_set_routing(struct rcar_du_group *rgrp) +{ + struct rcar_du_device *rcdu = rgrp->dev; + u32 dorcr = rcar_du_group_read(rgrp, DORCR); + + dorcr &= ~(DORCR_PG1T | DORCR_DK1S | DORCR_PG1D_MASK); + + /* + * Set the DPAD1 pins sources. Select CRTC 0 if explicitly requested and + * CRTC 1 in all other cases to avoid cloning CRTC 0 to DPAD0 and DPAD1 + * by default. + */ + if (rcdu->dpad1_source == rgrp->index * 2) + dorcr |= DORCR_PG1D_DS0; + else + dorcr |= DORCR_PG1T | DORCR_DK1S | DORCR_PG1D_DS1; + + rcar_du_group_write(rgrp, DORCR, dorcr); + + rcar_du_group_set_dpad_levels(rgrp); + + return rcar_du_set_dpad0_vsp1_routing(rgrp->dev); +} diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_group.h b/drivers/gpu/drm/renesas/rcar-du/rcar_du_group.h new file mode 100644 index 000000000..55649ad86 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_group.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * R-Car Display Unit Planes and CRTCs Group + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __RCAR_DU_GROUP_H__ +#define __RCAR_DU_GROUP_H__ + +#include <linux/mutex.h> + +#include "rcar_du_plane.h" + +struct rcar_du_device; + +/* + * struct rcar_du_group - CRTCs and planes group + * @dev: the 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 + * @lock: protects the dptsr_planes field and the DPTSR register + * @dptsr_planes: bitmask of planes driven by dot-clock and timing generator 1 + * @num_planes: number of planes in the group + * @planes: planes handled by the group + * @need_restart: the group needs to be restarted due to a configuration change + */ +struct rcar_du_group { + struct rcar_du_device *dev; + unsigned int mmio_offset; + unsigned int index; + + unsigned int channels_mask; + unsigned int cmms_mask; + unsigned int num_crtcs; + unsigned int use_count; + unsigned int used_crtcs; + + struct mutex lock; + unsigned int dptsr_planes; + + unsigned int num_planes; + struct rcar_du_plane planes[RCAR_DU_NUM_KMS_PLANES]; + bool need_restart; +}; + +u32 rcar_du_group_read(struct rcar_du_group *rgrp, u32 reg); +void rcar_du_group_write(struct rcar_du_group *rgrp, u32 reg, u32 data); + +int rcar_du_group_get(struct rcar_du_group *rgrp); +void rcar_du_group_put(struct rcar_du_group *rgrp); +void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start); +void rcar_du_group_restart(struct rcar_du_group *rgrp); +int rcar_du_group_set_routing(struct rcar_du_group *rgrp); + +int rcar_du_set_dpad0_vsp1_routing(struct rcar_du_device *rcdu); + +#endif /* __RCAR_DU_GROUP_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_kms.c new file mode 100644 index 000000000..9228a7ad0 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_kms.c @@ -0,0 +1,1010 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * R-Car Display Unit Mode Setting + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_dumb_buffers.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/dma-buf.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/wait.h> + +#include "rcar_du_crtc.h" +#include "rcar_du_drv.h" +#include "rcar_du_encoder.h" +#include "rcar_du_kms.h" +#include "rcar_du_regs.h" +#include "rcar_du_vsp.h" +#include "rcar_du_writeback.h" + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +static const struct rcar_du_format_info rcar_du_format_infos[] = { + { + .fourcc = DRM_FORMAT_RGB565, + .v4l2 = V4L2_PIX_FMT_RGB565, + .bpp = 16, + .planes = 1, + .hsub = 1, + .pnmr = PnMR_SPIM_TP | PnMR_DDDF_16BPP, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_ARGB1555, + .v4l2 = V4L2_PIX_FMT_ARGB555, + .bpp = 16, + .planes = 1, + .hsub = 1, + .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_ARGB, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_XRGB1555, + .v4l2 = V4L2_PIX_FMT_XRGB555, + .bpp = 16, + .planes = 1, + .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_ARGB, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_XRGB8888, + .v4l2 = V4L2_PIX_FMT_XBGR32, + .bpp = 32, + .planes = 1, + .hsub = 1, + .pnmr = PnMR_SPIM_TP | PnMR_DDDF_16BPP, + .edf = PnDDCR4_EDF_RGB888, + }, { + .fourcc = DRM_FORMAT_ARGB8888, + .v4l2 = V4L2_PIX_FMT_ABGR32, + .bpp = 32, + .planes = 1, + .hsub = 1, + .pnmr = PnMR_SPIM_ALP | PnMR_DDDF_16BPP, + .edf = PnDDCR4_EDF_ARGB8888, + }, { + .fourcc = DRM_FORMAT_UYVY, + .v4l2 = V4L2_PIX_FMT_UYVY, + .bpp = 16, + .planes = 1, + .hsub = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_YUYV, + .v4l2 = V4L2_PIX_FMT_YUYV, + .bpp = 16, + .planes = 1, + .hsub = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_NV12, + .v4l2 = V4L2_PIX_FMT_NV12M, + .bpp = 12, + .planes = 2, + .hsub = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_NV21, + .v4l2 = V4L2_PIX_FMT_NV21M, + .bpp = 12, + .planes = 2, + .hsub = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, { + .fourcc = DRM_FORMAT_NV16, + .v4l2 = V4L2_PIX_FMT_NV16M, + .bpp = 16, + .planes = 2, + .hsub = 2, + .pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC, + .edf = PnDDCR4_EDF_NONE, + }, + /* + * The following formats are not supported on Gen2 and thus have no + * associated .pnmr or .edf settings. + */ + { + .fourcc = DRM_FORMAT_RGB332, + .v4l2 = V4L2_PIX_FMT_RGB332, + .bpp = 8, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_ARGB4444, + .v4l2 = V4L2_PIX_FMT_ARGB444, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_XRGB4444, + .v4l2 = V4L2_PIX_FMT_XRGB444, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_RGBA4444, + .v4l2 = V4L2_PIX_FMT_RGBA444, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_RGBX4444, + .v4l2 = V4L2_PIX_FMT_RGBX444, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_ABGR4444, + .v4l2 = V4L2_PIX_FMT_ABGR444, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_XBGR4444, + .v4l2 = V4L2_PIX_FMT_XBGR444, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_BGRA4444, + .v4l2 = V4L2_PIX_FMT_BGRA444, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_BGRX4444, + .v4l2 = V4L2_PIX_FMT_BGRX444, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_RGBA5551, + .v4l2 = V4L2_PIX_FMT_RGBA555, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_RGBX5551, + .v4l2 = V4L2_PIX_FMT_RGBX555, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_ABGR1555, + .v4l2 = V4L2_PIX_FMT_ABGR555, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_XBGR1555, + .v4l2 = V4L2_PIX_FMT_XBGR555, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_BGRA5551, + .v4l2 = V4L2_PIX_FMT_BGRA555, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_BGRX5551, + .v4l2 = V4L2_PIX_FMT_BGRX555, + .bpp = 16, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_BGR888, + .v4l2 = V4L2_PIX_FMT_RGB24, + .bpp = 24, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_RGB888, + .v4l2 = V4L2_PIX_FMT_BGR24, + .bpp = 24, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_RGBA8888, + .v4l2 = V4L2_PIX_FMT_BGRA32, + .bpp = 32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_RGBX8888, + .v4l2 = V4L2_PIX_FMT_BGRX32, + .bpp = 32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_ABGR8888, + .v4l2 = V4L2_PIX_FMT_RGBA32, + .bpp = 32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_XBGR8888, + .v4l2 = V4L2_PIX_FMT_RGBX32, + .bpp = 32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_BGRA8888, + .v4l2 = V4L2_PIX_FMT_ARGB32, + .bpp = 32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_BGRX8888, + .v4l2 = V4L2_PIX_FMT_XRGB32, + .bpp = 32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_RGBX1010102, + .v4l2 = V4L2_PIX_FMT_RGBX1010102, + .bpp = 32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_RGBA1010102, + .v4l2 = V4L2_PIX_FMT_RGBA1010102, + .bpp = 32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_ARGB2101010, + .v4l2 = V4L2_PIX_FMT_ARGB2101010, + .bpp = 32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_YVYU, + .v4l2 = V4L2_PIX_FMT_YVYU, + .bpp = 16, + .planes = 1, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_NV61, + .v4l2 = V4L2_PIX_FMT_NV61M, + .bpp = 16, + .planes = 2, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YUV420, + .v4l2 = V4L2_PIX_FMT_YUV420M, + .bpp = 12, + .planes = 3, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YVU420, + .v4l2 = V4L2_PIX_FMT_YVU420M, + .bpp = 12, + .planes = 3, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YUV422, + .v4l2 = V4L2_PIX_FMT_YUV422M, + .bpp = 16, + .planes = 3, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YVU422, + .v4l2 = V4L2_PIX_FMT_YVU422M, + .bpp = 16, + .planes = 3, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YUV444, + .v4l2 = V4L2_PIX_FMT_YUV444M, + .bpp = 24, + .planes = 3, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_YVU444, + .v4l2 = V4L2_PIX_FMT_YVU444M, + .bpp = 24, + .planes = 3, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_Y210, + .v4l2 = V4L2_PIX_FMT_Y210, + .bpp = 32, + .planes = 1, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_Y212, + .v4l2 = V4L2_PIX_FMT_Y212, + .bpp = 32, + .planes = 1, + .hsub = 2, + }, +}; + +const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcar_du_format_infos); ++i) { + if (rcar_du_format_infos[i].fourcc == fourcc) + return &rcar_du_format_infos[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Frame buffer + */ + +static const struct drm_gem_object_funcs rcar_du_gem_funcs = { + .free = drm_gem_dma_object_free, + .print_info = drm_gem_dma_object_print_info, + .get_sg_table = drm_gem_dma_object_get_sg_table, + .vmap = drm_gem_dma_object_vmap, + .mmap = drm_gem_dma_object_mmap, + .vm_ops = &drm_gem_dma_vm_ops, +}; + +struct drm_gem_object *rcar_du_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sgt) +{ + struct rcar_du_device *rcdu = to_rcar_du_device(dev); + struct drm_gem_dma_object *dma_obj; + struct drm_gem_object *gem_obj; + int ret; + + if (!rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE)) + return drm_gem_dma_prime_import_sg_table(dev, attach, sgt); + + /* Create a DMA GEM buffer. */ + dma_obj = kzalloc_obj(*dma_obj); + if (!dma_obj) + return ERR_PTR(-ENOMEM); + + gem_obj = &dma_obj->base; + gem_obj->funcs = &rcar_du_gem_funcs; + + drm_gem_private_object_init(dev, gem_obj, attach->dmabuf->size); + dma_obj->map_noncoherent = false; + + ret = drm_gem_create_mmap_offset(gem_obj); + if (ret) { + drm_gem_object_release(gem_obj); + kfree(dma_obj); + return ERR_PTR(ret); + } + + dma_obj->dma_addr = 0; + dma_obj->sgt = sgt; + + return gem_obj; +} + +int rcar_du_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct rcar_du_device *rcdu = to_rcar_du_device(dev); + unsigned int align; + int ret; + + /* + * The R8A7779 DU requires a 16 pixels pitch alignment as documented, + * but the R8A7790 DU seems to require a 128 bytes pitch alignment. + */ + if (rcar_du_needs(rcdu, RCAR_DU_QUIRK_ALIGN_128B)) + align = 128; + else + align = 16 * args->bpp / 8; + + ret = drm_mode_size_dumb(dev, args, align, 0); + if (ret) + return ret; + + return drm_gem_dma_dumb_create_internal(file, dev, args); +} + +static struct drm_framebuffer * +rcar_du_fb_create(struct drm_device *dev, struct drm_file *file_priv, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct rcar_du_device *rcdu = to_rcar_du_device(dev); + const struct rcar_du_format_info *format; + unsigned int chroma_pitch; + unsigned int max_pitch; + unsigned int align; + unsigned int i; + + format = rcar_du_format_info(mode_cmd->pixel_format); + if (format == NULL) { + dev_dbg(dev->dev, "unsupported pixel format %p4cc\n", + &mode_cmd->pixel_format); + return ERR_PTR(-EINVAL); + } + + if (rcdu->info->gen < 3) { + /* + * On Gen2 the DU limits the pitch to 4095 pixels and requires + * buffers to be aligned to a 16 pixels boundary (or 128 bytes + * on some platforms). + */ + unsigned int bpp = format->planes == 1 ? format->bpp / 8 : 1; + + max_pitch = 4095 * bpp; + + if (rcar_du_needs(rcdu, RCAR_DU_QUIRK_ALIGN_128B)) + align = 128; + else + align = 16 * bpp; + } else { + /* + * On Gen3 the memory interface is handled by the VSP that + * limits the pitch to 65535 bytes and has no alignment + * constraint. + */ + max_pitch = 65535; + align = 1; + } + + if (mode_cmd->pitches[0] & (align - 1) || + mode_cmd->pitches[0] > max_pitch) { + dev_dbg(dev->dev, "invalid pitch value %u\n", + mode_cmd->pitches[0]); + return ERR_PTR(-EINVAL); + } + + /* + * Calculate the chroma plane(s) pitch using the horizontal subsampling + * factor. For semi-planar formats, the U and V planes are combined, the + * pitch must thus be doubled. + */ + chroma_pitch = mode_cmd->pitches[0] / format->hsub; + if (format->planes == 2) + chroma_pitch *= 2; + + for (i = 1; i < format->planes; ++i) { + if (mode_cmd->pitches[i] != chroma_pitch) { + dev_dbg(dev->dev, + "luma and chroma pitches are not compatible\n"); + return ERR_PTR(-EINVAL); + } + } + + return drm_gem_fb_create(dev, file_priv, info, mode_cmd); +} + +/* ----------------------------------------------------------------------------- + * Atomic Check and Update + */ + +static int rcar_du_atomic_check(struct drm_device *dev, + struct drm_atomic_commit *state) +{ + struct rcar_du_device *rcdu = to_rcar_du_device(dev); + int ret; + + ret = drm_atomic_helper_check(dev, state); + if (ret) + return ret; + + if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE)) + return 0; + + return rcar_du_atomic_check_planes(dev, state); +} + +static void rcar_du_atomic_commit_tail(struct drm_atomic_commit *old_state) +{ + struct drm_device *dev = old_state->dev; + struct rcar_du_device *rcdu = to_rcar_du_device(dev); + struct drm_crtc_state *crtc_state; + struct drm_crtc *crtc; + unsigned int i; + + /* + * Store RGB routing to DPAD0 and DPAD1, the hardware will be configured + * when starting the CRTCs. + */ + rcdu->dpad1_source = -1; + + for_each_new_crtc_in_state(old_state, crtc, crtc_state, i) { + struct rcar_du_crtc_state *rcrtc_state = + to_rcar_crtc_state(crtc_state); + struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); + + if (rcrtc_state->outputs & BIT(RCAR_DU_OUTPUT_DPAD0)) + rcdu->dpad0_source = rcrtc->index; + + if (rcrtc_state->outputs & BIT(RCAR_DU_OUTPUT_DPAD1)) + rcdu->dpad1_source = rcrtc->index; + } + + /* Apply the atomic update. */ + drm_atomic_helper_commit_modeset_disables(dev, old_state); + drm_atomic_helper_commit_planes(dev, old_state, + DRM_PLANE_COMMIT_ACTIVE_ONLY); + drm_atomic_helper_commit_modeset_enables(dev, old_state); + + drm_atomic_helper_commit_hw_done(old_state); + drm_atomic_helper_wait_for_flip_done(dev, old_state); + + drm_atomic_helper_cleanup_planes(dev, old_state); +} + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +static const struct drm_mode_config_helper_funcs rcar_du_mode_config_helper = { + .atomic_commit_tail = rcar_du_atomic_commit_tail, +}; + +static const struct drm_mode_config_funcs rcar_du_mode_config_funcs = { + .fb_create = rcar_du_fb_create, + .atomic_check = rcar_du_atomic_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, + enum rcar_du_output output, + struct of_endpoint *ep) +{ + struct device_node *entity __free(device_node) = NULL; + int ret; + + /* Locate the connected entity and initialize the encoder. */ + entity = of_graph_get_remote_port_parent(ep->local_node); + if (!entity) { + dev_dbg(rcdu->dev, "unconnected endpoint %pOF, skipping\n", + ep->local_node); + return -ENODEV; + } + + if (!of_device_is_available(entity)) { + dev_dbg(rcdu->dev, + "connected entity %pOF is disabled, skipping\n", + entity); + return -ENODEV; + } + + ret = rcar_du_encoder_init(rcdu, output, entity); + if (ret && ret != -EPROBE_DEFER && ret != -ENOLINK) + dev_warn(rcdu->dev, + "failed to initialize encoder %pOF on output %s (%d), skipping\n", + entity, rcar_du_output_name(output), ret); + + return ret; +} + +static int rcar_du_encoders_init(struct rcar_du_device *rcdu) +{ + struct device_node *ep_node __free(device_node) = NULL; + struct device_node *np = rcdu->dev->of_node; + unsigned int num_encoders = 0; + + /* + * Iterate over the endpoints and create one encoder for each output + * pipeline. + */ + for_each_endpoint_of_node(np, ep_node) { + enum rcar_du_output output; + struct of_endpoint ep; + unsigned int i; + int ret; + + ret = of_graph_parse_endpoint(ep_node, &ep); + if (ret < 0) + return ret; + + /* Find the output route corresponding to the port number. */ + for (i = 0; i < RCAR_DU_OUTPUT_MAX; ++i) { + if (rcdu->info->routes[i].possible_crtcs && + rcdu->info->routes[i].port == ep.port) { + output = i; + break; + } + } + + if (i == RCAR_DU_OUTPUT_MAX) { + dev_warn(rcdu->dev, + "port %u references unexisting output, skipping\n", + ep.port); + continue; + } + + /* Process the output pipeline. */ + ret = rcar_du_encoders_init_one(rcdu, output, &ep); + if (ret < 0) { + if (ret == -EPROBE_DEFER) + return ret; + + continue; + } + + num_encoders++; + } + + return num_encoders; +} + +static int rcar_du_properties_init(struct rcar_du_device *rcdu) +{ + /* + * The color key is expressed as an RGB888 triplet stored in a 32-bit + * integer in XRGB8888 format. Bit 24 is used as a flag to disable (0) + * or enable source color keying (1). + */ + rcdu->props.colorkey = + drm_property_create_range(&rcdu->ddev, 0, "colorkey", + 0, 0x01ffffff); + if (rcdu->props.colorkey == NULL) + return -ENOMEM; + + return 0; +} + +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; + unsigned int crtcs_mask; + } vsps[RCAR_DU_MAX_VSPS] = { { NULL, }, }; + unsigned int vsps_count = 0; + unsigned int cells; + unsigned int i; + int ret; + + /* + * First parse the DT vsps property to populate the list of VSPs. Each + * entry contains a pointer to the VSP DT node and a bitmask of the + * connected DU CRTCs. + */ + 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_prop_name, + cells, i, &args); + if (ret < 0) + goto done; + + /* + * Add the VSP to the list or update the corresponding existing + * entry if the VSP has already been added. + */ + for (j = 0; j < vsps_count; ++j) { + if (vsps[j].np == args.np) + break; + } + + if (j < vsps_count) + of_node_put(args.np); + else + vsps[vsps_count++].np = args.np; + + vsps[j].crtcs_mask |= BIT(i); + + /* + * Store the VSP pointer and pipe index in the CRTC. If the + * 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; + } + + /* + * Then initialize all the VSPs from the node pointers and CRTCs bitmask + * computed previously. + */ + for (i = 0; i < vsps_count; ++i) { + struct rcar_du_vsp *vsp = &rcdu->vsps[i]; + + vsp->index = i; + vsp->dev = rcdu; + + ret = rcar_du_vsp_init(vsp, vsps[i].np, vsps[i].crtcs_mask); + if (ret) + goto done; + } + +done: + for (i = 0; i < ARRAY_SIZE(vsps); ++i) + of_node_put(vsps[i].np); + + 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 device_node *cmm_node __free(device_node) = NULL; + struct rcar_du_cmm *cmm = &rcdu->cmms[i]; + struct platform_device *pdev; + int ret; + + cmm_node = of_parse_phandle(np, "renesas,cmms", i); + if (!cmm_node) { + dev_err(rcdu->dev, + "Failed to parse 'renesas,cmms' property\n"); + return -EINVAL; + } + + if (!of_device_is_available(cmm_node)) + /* It's fine to have a phandle to a non-enabled CMM. */ + continue; + + pdev = of_find_device_by_node(cmm_node); + if (!pdev) { + dev_err(rcdu->dev, "No device found for CMM%u\n", i); + return -EINVAL; + } + + /* + * -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->dev); + if (ret) { + platform_device_put(pdev); + return ret == -ENODEV ? 0 : ret; + } + + cmm->dev = &pdev->dev; + + /* + * Enforce suspend/resume ordering by making the CMM a provider + * of the DU: CMM is suspended after and resumed before the DU. + */ + cmm->link = device_link_add(rcdu->dev, cmm->dev, + DL_FLAG_STATELESS); + if (!cmm->link) { + dev_err(rcdu->dev, + "Failed to create device link to CMM%u\n", i); + return -EINVAL; + } + } + + return 0; +} + +static void rcar_du_modeset_cleanup(struct drm_device *dev, void *res) +{ + struct rcar_du_device *rcdu = to_rcar_du_device(dev); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcdu->cmms); ++i) { + struct rcar_du_cmm *cmm = &rcdu->cmms[i]; + + if (cmm->link) + device_link_del(cmm->link); + + put_device(cmm->dev); + } + + rcar_du_encoder_cleanup(rcdu); +} + +int rcar_du_modeset_init(struct rcar_du_device *rcdu) +{ + static const unsigned int mmio_offsets[] = { + DU0_REG_OFFSET, DU2_REG_OFFSET + }; + + struct drm_device *dev = &rcdu->ddev; + struct drm_encoder *encoder; + unsigned int dpad0_sources; + unsigned int num_encoders; + unsigned int num_groups; + unsigned int swindex; + unsigned int hwindex; + unsigned int i; + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + ret = drmm_add_action(&rcdu->ddev, rcar_du_modeset_cleanup, NULL); + if (ret) + return ret; + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.normalize_zpos = true; + dev->mode_config.funcs = &rcar_du_mode_config_funcs; + dev->mode_config.helper_private = &rcar_du_mode_config_helper; + + if (rcdu->info->gen < 3) { + dev->mode_config.max_width = 4095; + dev->mode_config.max_height = 2047; + } else { + /* + * The Gen3 DU uses the VSP1 for memory access, and is limited + * to frame sizes of 8190x8190. + */ + dev->mode_config.max_width = 8190; + dev->mode_config.max_height = 8190; + } + + rcdu->num_crtcs = hweight8(rcdu->info->channels_mask); + + ret = rcar_du_properties_init(rcdu); + if (ret < 0) + return ret; + + /* + * Initialize vertical blanking interrupts handling. Start with vblank + * disabled for all CRTCs. + */ + ret = drm_vblank_init(dev, rcdu->num_crtcs); + if (ret < 0) + return ret; + + /* Initialize the groups. */ + num_groups = DIV_ROUND_UP(rcdu->num_crtcs, 2); + + for (i = 0; i < num_groups; ++i) { + struct rcar_du_group *rgrp = &rcdu->groups[i]; + + mutex_init(&rgrp->lock); + + rgrp->dev = rcdu; + rgrp->mmio_offset = mmio_offsets[i]; + rgrp->index = i; + /* Extract the channel mask for this group only. */ + rgrp->channels_mask = (rcdu->info->channels_mask >> (2 * i)) + & GENMASK(1, 0); + rgrp->num_crtcs = hweight8(rgrp->channels_mask); + + /* + * If we have more than one CRTCs in this group pre-associate + * the low-order planes with CRTC 0 and the high-order planes + * with CRTC 1 to minimize flicker occurring when the + * association is changed. + */ + rgrp->dptsr_planes = rgrp->num_crtcs > 1 + ? (rcdu->info->gen >= 3 ? 0x04 : 0xf0) + : 0; + + if (!rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE)) { + ret = rcar_du_planes_init(rgrp); + if (ret < 0) + return ret; + } + } + + /* Initialize the compositors. */ + if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE)) { + ret = rcar_du_vsps_init(rcdu); + if (ret < 0) + return ret; + } + + /* Initialize the Color Management Modules. */ + ret = rcar_du_cmm_init(rcdu); + if (ret) + return dev_err_probe(rcdu->dev, ret, + "failed to initialize CMM\n"); + + /* Create the CRTCs. */ + for (swindex = 0, hwindex = 0; swindex < rcdu->num_crtcs; ++hwindex) { + struct rcar_du_group *rgrp; + + /* Skip unpopulated DU channels. */ + if (!(rcdu->info->channels_mask & BIT(hwindex))) + continue; + + rgrp = &rcdu->groups[hwindex / 2]; + + ret = rcar_du_crtc_create(rgrp, swindex++, hwindex); + if (ret < 0) + return ret; + } + + /* Initialize the encoders. */ + ret = rcar_du_encoders_init(rcdu); + if (ret < 0) + return dev_err_probe(rcdu->dev, ret, + "failed to initialize encoders\n"); + + if (ret == 0) { + dev_err(rcdu->dev, "error: no encoder could be initialized\n"); + return -EINVAL; + } + + num_encoders = ret; + + /* + * Set the possible CRTCs and possible clones. There's always at least + * one way for all encoders to clone each other, set all bits in the + * possible clones field. + */ + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct rcar_du_encoder *renc = to_rcar_encoder(encoder); + const struct rcar_du_output_routing *route = + &rcdu->info->routes[renc->output]; + + encoder->possible_crtcs = route->possible_crtcs; + encoder->possible_clones = (1 << num_encoders) - 1; + } + + /* Create the writeback connectors. */ + if (rcdu->info->gen >= 3) { + for (i = 0; i < rcdu->num_crtcs; ++i) { + struct rcar_du_crtc *rcrtc = &rcdu->crtcs[i]; + + ret = rcar_du_writeback_init(rcdu, rcrtc); + if (ret < 0) + return ret; + } + } + + /* + * Initialize the default DPAD0 source to the index of the first DU + * channel that can be connected to DPAD0. The exact value doesn't + * matter as it should be overwritten by mode setting for the RGB + * output, but it is nonetheless required to ensure a valid initial + * hardware configuration on Gen3 where DU0 can't always be connected to + * DPAD0. + */ + dpad0_sources = rcdu->info->routes[RCAR_DU_OUTPUT_DPAD0].possible_crtcs; + rcdu->dpad0_source = ffs(dpad0_sources) - 1; + + drm_mode_config_reset(dev); + + drm_kms_helper_poll_init(dev); + + return 0; +} diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_kms.h b/drivers/gpu/drm/renesas/rcar-du/rcar_du_kms.h new file mode 100644 index 000000000..f31afeeee --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_kms.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * R-Car Display Unit Mode Setting + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __RCAR_DU_KMS_H__ +#define __RCAR_DU_KMS_H__ + +#include <linux/types.h> + +struct dma_buf_attachment; +struct drm_file; +struct drm_device; +struct drm_gem_object; +struct drm_mode_create_dumb; +struct rcar_du_device; +struct sg_table; + +struct rcar_du_format_info { + u32 fourcc; + u32 v4l2; + unsigned int bpp; + unsigned int planes; + unsigned int hsub; + unsigned int pnmr; + unsigned int edf; +}; + +const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc); + +int rcar_du_modeset_init(struct rcar_du_device *rcdu); + +int rcar_du_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args); + +struct drm_gem_object *rcar_du_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sgt); + +#endif /* __RCAR_DU_KMS_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_plane.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_plane.c new file mode 100644 index 000000000..8870766b9 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_plane.c @@ -0,0 +1,842 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * R-Car Display Unit Planes + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_blend.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_fb_dma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_dma_helper.h> + +#include "rcar_du_drv.h" +#include "rcar_du_group.h" +#include "rcar_du_kms.h" +#include "rcar_du_plane.h" +#include "rcar_du_regs.h" + +/* ----------------------------------------------------------------------------- + * Atomic hardware plane allocator + * + * The hardware plane allocator is solely based on the atomic plane states + * without keeping any external state to avoid races between .atomic_check() + * and .atomic_commit(). + * + * The core idea is to avoid using a free planes bitmask that would need to be + * shared between check and commit handlers with a collective knowledge based on + * the allocated hardware plane(s) for each KMS plane. The allocator then loops + * over all plane states to compute the free planes bitmask, allocates hardware + * planes based on that bitmask, and stores the result back in the plane states. + * + * For this to work we need to access the current state of planes not touched by + * the atomic update. To ensure that it won't be modified, we need to lock all + * planes using drm_atomic_get_plane_state(). This effectively serializes atomic + * updates from .atomic_check() up to completion (when swapping the states if + * the check step has succeeded) or rollback (when freeing the states if the + * check step has failed). + * + * Allocation is performed in the .atomic_check() handler and applied + * automatically when the core swaps the old and new states. + */ + +static bool rcar_du_plane_needs_realloc( + const struct rcar_du_plane_state *old_state, + const struct rcar_du_plane_state *new_state) +{ + /* + * Lowering the number of planes doesn't strictly require reallocation + * as the extra hardware plane will be freed when committing, but doing + * so could lead to more fragmentation. + */ + if (!old_state->format || + old_state->format->planes != new_state->format->planes) + return true; + + /* Reallocate hardware planes if the source has changed. */ + if (old_state->source != new_state->source) + return true; + + return false; +} + +static unsigned int rcar_du_plane_hwmask(struct rcar_du_plane_state *state) +{ + unsigned int mask; + + if (state->hwindex == -1) + return 0; + + mask = 1 << state->hwindex; + if (state->format->planes == 2) + mask |= 1 << ((state->hwindex + 1) % 8); + + return mask; +} + +/* + * The R8A7790 DU can source frames directly from the VSP1 devices VSPD0 and + * VSPD1. VSPD0 feeds DU0/1 plane 0, and VSPD1 feeds either DU2 plane 0 or + * DU0/1 plane 1. + * + * Allocate the correct fixed plane when sourcing frames from VSPD0 or VSPD1, + * and allocate planes in reverse index order otherwise to ensure maximum + * availability of planes 0 and 1. + * + * The caller is responsible for ensuring that the requested source is + * compatible with the DU revision. + */ +static int rcar_du_plane_hwalloc(struct rcar_du_plane *plane, + struct rcar_du_plane_state *state, + unsigned int free) +{ + unsigned int num_planes = state->format->planes; + int fixed = -1; + int i; + + if (state->source == RCAR_DU_PLANE_VSPD0) { + /* VSPD0 feeds plane 0 on DU0/1. */ + if (plane->group->index != 0) + return -EINVAL; + + fixed = 0; + } else if (state->source == RCAR_DU_PLANE_VSPD1) { + /* VSPD1 feeds plane 1 on DU0/1 or plane 0 on DU2. */ + fixed = plane->group->index == 0 ? 1 : 0; + } + + if (fixed >= 0) + return free & (1 << fixed) ? fixed : -EBUSY; + + for (i = RCAR_DU_NUM_HW_PLANES - 1; i >= 0; --i) { + if (!(free & (1 << i))) + continue; + + if (num_planes == 1 || free & (1 << ((i + 1) % 8))) + break; + } + + return i < 0 ? -EBUSY : i; +} + +int rcar_du_atomic_check_planes(struct drm_device *dev, + struct drm_atomic_commit *state) +{ + struct rcar_du_device *rcdu = to_rcar_du_device(dev); + unsigned int group_freed_planes[RCAR_DU_MAX_GROUPS] = { 0, }; + unsigned int group_free_planes[RCAR_DU_MAX_GROUPS] = { 0, }; + bool needs_realloc = false; + unsigned int groups = 0; + unsigned int i; + struct drm_plane *drm_plane; + struct drm_plane_state *old_drm_plane_state; + struct drm_plane_state *new_drm_plane_state; + + /* Check if hardware planes need to be reallocated. */ + for_each_oldnew_plane_in_state(state, drm_plane, old_drm_plane_state, + new_drm_plane_state, i) { + struct rcar_du_plane_state *old_plane_state; + struct rcar_du_plane_state *new_plane_state; + struct rcar_du_plane *plane; + unsigned int index; + + plane = to_rcar_plane(drm_plane); + old_plane_state = to_rcar_plane_state(old_drm_plane_state); + new_plane_state = to_rcar_plane_state(new_drm_plane_state); + + dev_dbg(rcdu->dev, "%s: checking plane (%u,%tu)\n", __func__, + plane->group->index, plane - plane->group->planes); + + /* + * If the plane is being disabled we don't need to go through + * the full reallocation procedure. Just mark the hardware + * plane(s) as freed. + */ + if (!new_plane_state->format) { + dev_dbg(rcdu->dev, "%s: plane is being disabled\n", + __func__); + index = plane - plane->group->planes; + group_freed_planes[plane->group->index] |= 1 << index; + new_plane_state->hwindex = -1; + continue; + } + + /* + * If the plane needs to be reallocated mark it as such, and + * mark the hardware plane(s) as free. + */ + if (rcar_du_plane_needs_realloc(old_plane_state, new_plane_state)) { + dev_dbg(rcdu->dev, "%s: plane needs reallocation\n", + __func__); + groups |= 1 << plane->group->index; + needs_realloc = true; + + index = plane - plane->group->planes; + group_freed_planes[plane->group->index] |= 1 << index; + new_plane_state->hwindex = -1; + } + } + + if (!needs_realloc) + return 0; + + /* + * Grab all plane states for the groups that need reallocation to ensure + * locking and avoid racy updates. This serializes the update operation, + * but there's not much we can do about it as that's the hardware + * design. + * + * Compute the used planes mask for each group at the same time to avoid + * looping over the planes separately later. + */ + while (groups) { + unsigned int index = ffs(groups) - 1; + struct rcar_du_group *group = &rcdu->groups[index]; + unsigned int used_planes = 0; + + dev_dbg(rcdu->dev, "%s: finding free planes for group %u\n", + __func__, index); + + for (i = 0; i < group->num_planes; ++i) { + struct rcar_du_plane *plane = &group->planes[i]; + struct rcar_du_plane_state *new_plane_state; + struct drm_plane_state *s; + + s = drm_atomic_get_plane_state(state, &plane->plane); + if (IS_ERR(s)) + return PTR_ERR(s); + + /* + * If the plane has been freed in the above loop its + * hardware planes must not be added to the used planes + * bitmask. However, the current state doesn't reflect + * the free state yet, as we've modified the new state + * above. Use the local freed planes list to check for + * that condition instead. + */ + if (group_freed_planes[index] & (1 << i)) { + dev_dbg(rcdu->dev, + "%s: plane (%u,%tu) has been freed, skipping\n", + __func__, plane->group->index, + plane - plane->group->planes); + continue; + } + + new_plane_state = to_rcar_plane_state(s); + used_planes |= rcar_du_plane_hwmask(new_plane_state); + + dev_dbg(rcdu->dev, + "%s: plane (%u,%tu) uses %u hwplanes (index %d)\n", + __func__, plane->group->index, + plane - plane->group->planes, + new_plane_state->format ? + new_plane_state->format->planes : 0, + new_plane_state->hwindex); + } + + group_free_planes[index] = 0xff & ~used_planes; + groups &= ~(1 << index); + + dev_dbg(rcdu->dev, "%s: group %u free planes mask 0x%02x\n", + __func__, index, group_free_planes[index]); + } + + /* Reallocate hardware planes for each plane that needs it. */ + for_each_oldnew_plane_in_state(state, drm_plane, old_drm_plane_state, + new_drm_plane_state, i) { + struct rcar_du_plane_state *old_plane_state; + struct rcar_du_plane_state *new_plane_state; + struct rcar_du_plane *plane; + unsigned int crtc_planes; + unsigned int free; + int idx; + + plane = to_rcar_plane(drm_plane); + old_plane_state = to_rcar_plane_state(old_drm_plane_state); + new_plane_state = to_rcar_plane_state(new_drm_plane_state); + + dev_dbg(rcdu->dev, "%s: allocating plane (%u,%tu)\n", __func__, + plane->group->index, plane - plane->group->planes); + + /* + * Skip planes that are being disabled or don't need to be + * reallocated. + */ + if (!new_plane_state->format || + !rcar_du_plane_needs_realloc(old_plane_state, new_plane_state)) + continue; + + /* + * Try to allocate the plane from the free planes currently + * associated with the target CRTC to avoid restarting the CRTC + * group and thus minimize flicker. If it fails fall back to + * allocating from all free planes. + */ + crtc_planes = to_rcar_crtc(new_plane_state->state.crtc)->index % 2 + ? plane->group->dptsr_planes + : ~plane->group->dptsr_planes; + free = group_free_planes[plane->group->index]; + + idx = rcar_du_plane_hwalloc(plane, new_plane_state, + free & crtc_planes); + if (idx < 0) + idx = rcar_du_plane_hwalloc(plane, new_plane_state, + free); + if (idx < 0) { + dev_dbg(rcdu->dev, "%s: no available hardware plane\n", + __func__); + return idx; + } + + dev_dbg(rcdu->dev, "%s: allocated %u hwplanes (index %u)\n", + __func__, new_plane_state->format->planes, idx); + + new_plane_state->hwindex = idx; + + group_free_planes[plane->group->index] &= + ~rcar_du_plane_hwmask(new_plane_state); + + dev_dbg(rcdu->dev, "%s: group %u free planes mask 0x%02x\n", + __func__, plane->group->index, + group_free_planes[plane->group->index]); + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Plane Setup + */ + +#define RCAR_DU_COLORKEY_NONE (0 << 24) +#define RCAR_DU_COLORKEY_SOURCE (1 << 24) +#define RCAR_DU_COLORKEY_MASK (1 << 24) + +static void rcar_du_plane_write(struct rcar_du_group *rgrp, + unsigned int index, u32 reg, u32 data) +{ + rcar_du_write(rgrp->dev, rgrp->mmio_offset + index * PLANE_OFF + reg, + data); +} + +static void rcar_du_plane_setup_scanout(struct rcar_du_group *rgrp, + const struct rcar_du_plane_state *state) +{ + unsigned int src_x = state->state.src.x1 >> 16; + unsigned int src_y = state->state.src.y1 >> 16; + unsigned int index = state->hwindex; + unsigned int pitch; + bool interlaced; + u32 dma[2]; + + interlaced = state->state.crtc->state->adjusted_mode.flags + & DRM_MODE_FLAG_INTERLACE; + + if (state->source == RCAR_DU_PLANE_MEMORY) { + struct drm_framebuffer *fb = state->state.fb; + struct drm_gem_dma_object *gem; + unsigned int i; + + if (state->format->planes == 2) + pitch = fb->pitches[0]; + else + pitch = fb->pitches[0] * 8 / state->format->bpp; + + for (i = 0; i < state->format->planes; ++i) { + gem = drm_fb_dma_get_gem_obj(fb, i); + dma[i] = gem->dma_addr + fb->offsets[i]; + } + } else { + pitch = drm_rect_width(&state->state.src) >> 16; + dma[0] = 0; + dma[1] = 0; + } + + /* + * Memory pitch (expressed in pixels). Must be doubled for interlaced + * operation with 32bpp formats. + */ + rcar_du_plane_write(rgrp, index, PnMWR, + (interlaced && state->format->bpp == 32) ? + pitch * 2 : pitch); + + /* + * The Y position is expressed in raster line units and must be doubled + * for 32bpp formats, according to the R8A7790 datasheet. No mention of + * doubling the Y position is found in the R8A7779 datasheet, but the + * rule seems to apply there as well. + * + * Despite not being documented, doubling seem not to be needed when + * operating in interlaced mode. + * + * Similarly, for the second plane, NV12 and NV21 formats seem to + * require a halved Y position value, in both progressive and interlaced + * modes. + */ + rcar_du_plane_write(rgrp, index, PnSPXR, src_x); + rcar_du_plane_write(rgrp, index, PnSPYR, src_y * + (!interlaced && state->format->bpp == 32 ? 2 : 1)); + + rcar_du_plane_write(rgrp, index, PnDSA0R, dma[0]); + + if (state->format->planes == 2) { + index = (index + 1) % 8; + + rcar_du_plane_write(rgrp, index, PnMWR, pitch); + + rcar_du_plane_write(rgrp, index, PnSPXR, src_x); + rcar_du_plane_write(rgrp, index, PnSPYR, src_y * + (state->format->bpp == 16 ? 2 : 1) / 2); + + rcar_du_plane_write(rgrp, index, PnDSA0R, dma[1]); + } +} + +static void rcar_du_plane_setup_mode(struct rcar_du_group *rgrp, + unsigned int index, + const struct rcar_du_plane_state *state) +{ + u32 colorkey; + u32 pnmr; + + /* + * The PnALPHAR register controls alpha-blending in 16bpp formats + * (ARGB1555 and XRGB1555). + * + * For ARGB, set the alpha value to 0, and enable alpha-blending when + * the A bit is 0. This maps A=0 to alpha=0 and A=1 to alpha=255. + * + * For XRGB, set the alpha value to the plane-wide alpha value and + * enable alpha-blending regardless of the X bit value. + */ + if (state->format->fourcc != DRM_FORMAT_XRGB1555) + rcar_du_plane_write(rgrp, index, PnALPHAR, PnALPHAR_ABIT_0); + else + rcar_du_plane_write(rgrp, index, PnALPHAR, + PnALPHAR_ABIT_X | state->state.alpha >> 8); + + pnmr = PnMR_BM_MD | state->format->pnmr; + + /* + * Disable color keying when requested. YUV formats have the + * PnMR_SPIM_TP_OFF bit set in their pnmr field, disabling color keying + * automatically. + */ + if ((state->colorkey & RCAR_DU_COLORKEY_MASK) == RCAR_DU_COLORKEY_NONE) + pnmr |= PnMR_SPIM_TP_OFF; + + /* For packed YUV formats we need to select the U/V order. */ + if (state->format->fourcc == DRM_FORMAT_YUYV) + pnmr |= PnMR_YCDF_YUYV; + + rcar_du_plane_write(rgrp, index, PnMR, pnmr); + + switch (state->format->fourcc) { + case DRM_FORMAT_RGB565: + colorkey = ((state->colorkey & 0xf80000) >> 8) + | ((state->colorkey & 0x00fc00) >> 5) + | ((state->colorkey & 0x0000f8) >> 3); + rcar_du_plane_write(rgrp, index, PnTC2R, colorkey); + break; + + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + colorkey = ((state->colorkey & 0xf80000) >> 9) + | ((state->colorkey & 0x00f800) >> 6) + | ((state->colorkey & 0x0000f8) >> 3); + rcar_du_plane_write(rgrp, index, PnTC2R, colorkey); + break; + + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + rcar_du_plane_write(rgrp, index, PnTC3R, + PnTC3R_CODE | (state->colorkey & 0xffffff)); + break; + } +} + +static void rcar_du_plane_setup_format_gen2(struct rcar_du_group *rgrp, + unsigned int index, + const struct rcar_du_plane_state *state) +{ + u32 ddcr2 = PnDDCR2_CODE; + u32 ddcr4; + + /* + * Data format + * + * The data format is selected by the DDDF field in PnMR and the EDF + * field in DDCR4. + */ + + rcar_du_plane_setup_mode(rgrp, index, state); + + if (state->format->planes == 2) { + if (state->hwindex != index) { + if (state->format->fourcc == DRM_FORMAT_NV12 || + state->format->fourcc == DRM_FORMAT_NV21) + ddcr2 |= PnDDCR2_Y420; + + if (state->format->fourcc == DRM_FORMAT_NV21) + ddcr2 |= PnDDCR2_NV21; + + ddcr2 |= PnDDCR2_DIVU; + } else { + ddcr2 |= PnDDCR2_DIVY; + } + } + + rcar_du_plane_write(rgrp, index, PnDDCR2, ddcr2); + + ddcr4 = state->format->edf | PnDDCR4_CODE; + if (state->source != RCAR_DU_PLANE_MEMORY) + ddcr4 |= PnDDCR4_VSPS; + + rcar_du_plane_write(rgrp, index, PnDDCR4, ddcr4); +} + +static void rcar_du_plane_setup_format_gen3(struct rcar_du_group *rgrp, + unsigned int index, + const struct rcar_du_plane_state *state) +{ + struct rcar_du_device *rcdu = rgrp->dev; + u32 pnmr = state->format->pnmr | PnMR_SPIM_TP_OFF; + + if (rcdu->info->features & RCAR_DU_FEATURE_NO_BLENDING) { + /* No blending. ALP and EOR are not supported. */ + pnmr &= ~(PnMR_SPIM_ALP | PnMR_SPIM_EOR); + } + + rcar_du_plane_write(rgrp, index, PnMR, pnmr); + + rcar_du_plane_write(rgrp, index, PnDDCR4, + state->format->edf | PnDDCR4_CODE); + + /* + * On Gen3, some DU channels have two planes, each being wired to a + * separate VSPD instance. The DU can then blend two planes. While + * this feature isn't used by the driver, issues related to alpha + * blending (such as incorrect colors or planes being invisible) may + * still occur if the PnALPHAR register has a stale value. Set the + * register to 0 to avoid this. + */ + + rcar_du_plane_write(rgrp, index, PnALPHAR, 0); +} + +static void rcar_du_plane_setup_format(struct rcar_du_group *rgrp, + unsigned int index, + const struct rcar_du_plane_state *state) +{ + struct rcar_du_device *rcdu = rgrp->dev; + const struct drm_rect *dst = &state->state.dst; + + if (rcdu->info->gen < 3) + rcar_du_plane_setup_format_gen2(rgrp, index, state); + else + rcar_du_plane_setup_format_gen3(rgrp, index, state); + + /* Destination position and size */ + rcar_du_plane_write(rgrp, index, PnDSXR, drm_rect_width(dst)); + rcar_du_plane_write(rgrp, index, PnDSYR, drm_rect_height(dst)); + rcar_du_plane_write(rgrp, index, PnDPXR, dst->x1); + rcar_du_plane_write(rgrp, index, PnDPYR, dst->y1); + + if (rcdu->info->gen < 3) { + /* Wrap-around and blinking, disabled */ + rcar_du_plane_write(rgrp, index, PnWASPR, 0); + rcar_du_plane_write(rgrp, index, PnWAMWR, 4095); + rcar_du_plane_write(rgrp, index, PnBTR, 0); + rcar_du_plane_write(rgrp, index, PnMLR, 0); + } +} + +void __rcar_du_plane_setup(struct rcar_du_group *rgrp, + const struct rcar_du_plane_state *state) +{ + struct rcar_du_device *rcdu = rgrp->dev; + + rcar_du_plane_setup_format(rgrp, state->hwindex, state); + if (state->format->planes == 2) + rcar_du_plane_setup_format(rgrp, (state->hwindex + 1) % 8, + state); + + if (rcdu->info->gen >= 3) + return; + + rcar_du_plane_setup_scanout(rgrp, state); + + if (state->source == RCAR_DU_PLANE_VSPD1) { + unsigned int vspd1_sink = rgrp->index ? 2 : 0; + + if (rcdu->vspd1_sink != vspd1_sink) { + rcdu->vspd1_sink = vspd1_sink; + rcar_du_set_dpad0_vsp1_routing(rcdu); + + /* + * Changes to the VSP1 sink take effect on DRES and thus + * need a restart of the group. + */ + rgrp->need_restart = true; + } + } +} + +int __rcar_du_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state, + const struct rcar_du_format_info **format) +{ + struct drm_device *dev = plane->dev; + struct drm_crtc_state *crtc_state; + int ret; + + if (!state->crtc) { + /* + * The visible field is not reset by the DRM core but only + * updated by drm_atomic_helper_check_plane_state(), set it + * manually. + */ + state->visible = false; + *format = NULL; + return 0; + } + + crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + ret = drm_atomic_helper_check_plane_state(state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, true); + if (ret < 0) + return ret; + + if (!state->visible) { + *format = NULL; + return 0; + } + + *format = rcar_du_format_info(state->fb->format->format); + if (*format == NULL) { + dev_dbg(dev->dev, "%s: unsupported format %p4cc\n", __func__, + &state->fb->format->format); + return -EINVAL; + } + + return 0; +} + +static int rcar_du_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct rcar_du_plane_state *rstate = to_rcar_plane_state(new_plane_state); + + return __rcar_du_plane_atomic_check(plane, new_plane_state, + &rstate->format); +} + +static void rcar_du_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); + struct rcar_du_plane *rplane = to_rcar_plane(plane); + struct rcar_du_plane_state *old_rstate; + struct rcar_du_plane_state *new_rstate; + + if (!new_state->visible) + return; + + rcar_du_plane_setup(rplane); + + /* + * Check whether the source has changed from memory to live source or + * from live source to memory. The source has been configured by the + * VSPS bit in the PnDDCR4 register. Although the datasheet states that + * the bit is updated during vertical blanking, it seems that updates + * only occur when the DU group is held in reset through the DSYSR.DRES + * bit. We thus need to restart the group if the source changes. + */ + old_rstate = to_rcar_plane_state(old_state); + new_rstate = to_rcar_plane_state(new_state); + + if ((old_rstate->source == RCAR_DU_PLANE_MEMORY) != + (new_rstate->source == RCAR_DU_PLANE_MEMORY)) + rplane->group->need_restart = true; +} + +static const struct drm_plane_helper_funcs rcar_du_plane_helper_funcs = { + .atomic_check = rcar_du_plane_atomic_check, + .atomic_update = rcar_du_plane_atomic_update, +}; + +static const struct drm_plane_helper_funcs rcar_du_primary_plane_helper_funcs = { + .atomic_check = rcar_du_plane_atomic_check, + .atomic_update = rcar_du_plane_atomic_update, + .get_scanout_buffer = drm_fb_dma_get_scanout_buffer, +}; + +static struct drm_plane_state * +rcar_du_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct rcar_du_plane_state *state; + struct rcar_du_plane_state *copy; + + if (WARN_ON(!plane->state)) + return NULL; + + state = to_rcar_plane_state(plane->state); + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (copy == NULL) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, ©->state); + + return ©->state; +} + +static void rcar_du_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(to_rcar_plane_state(state)); +} + +static void rcar_du_plane_reset(struct drm_plane *plane) +{ + struct rcar_du_plane_state *state; + + if (plane->state) { + rcar_du_plane_atomic_destroy_state(plane, plane->state); + plane->state = NULL; + } + + state = kzalloc_obj(*state); + if (state == NULL) + return; + + __drm_atomic_helper_plane_reset(plane, &state->state); + + state->hwindex = -1; + state->source = RCAR_DU_PLANE_MEMORY; + state->colorkey = RCAR_DU_COLORKEY_NONE; +} + +static int rcar_du_plane_atomic_set_property(struct drm_plane *plane, + struct drm_plane_state *state, + struct drm_property *property, + uint64_t val) +{ + struct rcar_du_plane_state *rstate = to_rcar_plane_state(state); + struct rcar_du_device *rcdu = to_rcar_plane(plane)->group->dev; + + if (property == rcdu->props.colorkey) + rstate->colorkey = val; + else + return -EINVAL; + + return 0; +} + +static int rcar_du_plane_atomic_get_property(struct drm_plane *plane, + const struct drm_plane_state *state, struct drm_property *property, + uint64_t *val) +{ + const struct rcar_du_plane_state *rstate = + container_of(state, const struct rcar_du_plane_state, state); + struct rcar_du_device *rcdu = to_rcar_plane(plane)->group->dev; + + if (property == rcdu->props.colorkey) + *val = rstate->colorkey; + else + return -EINVAL; + + return 0; +} + +static const struct drm_plane_funcs rcar_du_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = rcar_du_plane_reset, + .destroy = drm_plane_cleanup, + .atomic_duplicate_state = rcar_du_plane_atomic_duplicate_state, + .atomic_destroy_state = rcar_du_plane_atomic_destroy_state, + .atomic_set_property = rcar_du_plane_atomic_set_property, + .atomic_get_property = rcar_du_plane_atomic_get_property, +}; + +static const uint32_t formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV16, +}; + +int rcar_du_planes_init(struct rcar_du_group *rgrp) +{ + struct rcar_du_device *rcdu = rgrp->dev; + unsigned int crtcs; + unsigned int i; + int ret; + + /* + * Create one primary plane per CRTC in this group and seven overlay + * planes. + */ + rgrp->num_planes = rgrp->num_crtcs + 7; + + crtcs = ((1 << rcdu->num_crtcs) - 1) & (3 << (2 * rgrp->index)); + + for (i = 0; i < rgrp->num_planes; ++i) { + enum drm_plane_type type = i < rgrp->num_crtcs + ? DRM_PLANE_TYPE_PRIMARY + : DRM_PLANE_TYPE_OVERLAY; + struct rcar_du_plane *plane = &rgrp->planes[i]; + + plane->group = rgrp; + + ret = drm_universal_plane_init(&rcdu->ddev, &plane->plane, + crtcs, &rcar_du_plane_funcs, + formats, ARRAY_SIZE(formats), + NULL, type, NULL); + if (ret < 0) + return ret; + + if (type == DRM_PLANE_TYPE_PRIMARY) + drm_plane_helper_add(&plane->plane, + &rcar_du_primary_plane_helper_funcs); + else + drm_plane_helper_add(&plane->plane, + &rcar_du_plane_helper_funcs); + + drm_plane_create_alpha_property(&plane->plane); + + if (type == DRM_PLANE_TYPE_PRIMARY) { + drm_plane_create_zpos_immutable_property(&plane->plane, + 0); + } else { + drm_object_attach_property(&plane->plane.base, + rcdu->props.colorkey, + RCAR_DU_COLORKEY_NONE); + drm_plane_create_zpos_property(&plane->plane, 1, 1, 7); + } + } + + return 0; +} diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_plane.h b/drivers/gpu/drm/renesas/rcar-du/rcar_du_plane.h new file mode 100644 index 000000000..49970f098 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_plane.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * R-Car Display Unit Planes + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __RCAR_DU_PLANE_H__ +#define __RCAR_DU_PLANE_H__ + +#include <drm/drm_plane.h> + +struct rcar_du_format_info; +struct rcar_du_group; + +/* + * The R-Car DU has 8 hardware planes, shared between primary and overlay planes. + * As using overlay planes requires at least one of the CRTCs being enabled, no + * more than 7 overlay planes can be available. We thus create 1 primary plane + * per CRTC and 7 overlay planes, for a total of up to 9 KMS planes. + */ +#define RCAR_DU_NUM_KMS_PLANES 9 +#define RCAR_DU_NUM_HW_PLANES 8 + +enum rcar_du_plane_source { + RCAR_DU_PLANE_MEMORY, + RCAR_DU_PLANE_VSPD0, + RCAR_DU_PLANE_VSPD1, +}; + +struct rcar_du_plane { + struct drm_plane plane; + struct rcar_du_group *group; +}; + +static inline struct rcar_du_plane *to_rcar_plane(struct drm_plane *plane) +{ + return container_of(plane, struct rcar_du_plane, plane); +} + +/** + * struct rcar_du_plane_state - Driver-specific plane state + * @state: base DRM plane state + * @format: information about the pixel format used by the plane + * @hwindex: 0-based hardware plane index, -1 means unused + * @colorkey: value of the plane colorkey property + */ +struct rcar_du_plane_state { + struct drm_plane_state state; + + const struct rcar_du_format_info *format; + int hwindex; + enum rcar_du_plane_source source; + + unsigned int colorkey; +}; + +static inline struct rcar_du_plane_state * +to_rcar_plane_state(struct drm_plane_state *state) +{ + return container_of(state, struct rcar_du_plane_state, state); +} + +int rcar_du_atomic_check_planes(struct drm_device *dev, + struct drm_atomic_commit *state); + +int __rcar_du_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state, + const struct rcar_du_format_info **format); + +int rcar_du_planes_init(struct rcar_du_group *rgrp); + +void __rcar_du_plane_setup(struct rcar_du_group *rgrp, + const struct rcar_du_plane_state *state); + +static inline void rcar_du_plane_setup(struct rcar_du_plane *plane) +{ + struct rcar_du_plane_state *state = + to_rcar_plane_state(plane->plane.state); + + return __rcar_du_plane_setup(plane->group, state); +} + +#endif /* __RCAR_DU_PLANE_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_regs.h b/drivers/gpu/drm/renesas/rcar-du/rcar_du_regs.h new file mode 100644 index 000000000..391de6661 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_regs.h @@ -0,0 +1,553 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * R-Car Display Unit Registers Definitions + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __RCAR_DU_REGS_H__ +#define __RCAR_DU_REGS_H__ + +#define DU0_REG_OFFSET 0x00000 +#define DU1_REG_OFFSET 0x30000 +#define DU2_REG_OFFSET 0x40000 +#define DU3_REG_OFFSET 0x70000 + +/* ----------------------------------------------------------------------------- + * Display Control Registers + */ + +#define DSYSR 0x00000 /* display 1 */ +#define DSYSR_ILTS (1 << 29) +#define DSYSR_DSEC (1 << 20) +#define DSYSR_IUPD (1 << 16) +#define DSYSR_DRES (1 << 9) +#define DSYSR_DEN (1 << 8) +#define DSYSR_TVM_MASTER (0 << 6) +#define DSYSR_TVM_SWITCH (1 << 6) +#define DSYSR_TVM_TVSYNC (2 << 6) +#define DSYSR_TVM_MASK (3 << 6) +#define DSYSR_SCM_INT_NONE (0 << 4) +#define DSYSR_SCM_INT_SYNC (2 << 4) +#define DSYSR_SCM_INT_VIDEO (3 << 4) +#define DSYSR_SCM_MASK (3 << 4) + +#define DSMR 0x00004 +#define DSMR_VSPM (1 << 28) +#define DSMR_ODPM (1 << 27) +#define DSMR_DIPM_DISP (0 << 25) +#define DSMR_DIPM_CSYNC (1 << 25) +#define DSMR_DIPM_DE (3 << 25) +#define DSMR_DIPM_MASK (3 << 25) +#define DSMR_CSPM (1 << 24) +#define DSMR_DIL (1 << 19) +#define DSMR_VSL (1 << 18) +#define DSMR_HSL (1 << 17) +#define DSMR_DDIS (1 << 16) +#define DSMR_CDEL (1 << 15) +#define DSMR_CDEM_CDE (0 << 13) +#define DSMR_CDEM_LOW (2 << 13) +#define DSMR_CDEM_HIGH (3 << 13) +#define DSMR_CDEM_MASK (3 << 13) +#define DSMR_CDED (1 << 12) +#define DSMR_ODEV (1 << 8) +#define DSMR_CSY_VH_OR (0 << 6) +#define DSMR_CSY_333 (2 << 6) +#define DSMR_CSY_222 (3 << 6) +#define DSMR_CSY_MASK (3 << 6) + +#define DSSR 0x00008 +#define DSSR_VC1FB_DSA0 (0 << 30) +#define DSSR_VC1FB_DSA1 (1 << 30) +#define DSSR_VC1FB_DSA2 (2 << 30) +#define DSSR_VC1FB_INIT (3 << 30) +#define DSSR_VC1FB_MASK (3 << 30) +#define DSSR_VC0FB_DSA0 (0 << 28) +#define DSSR_VC0FB_DSA1 (1 << 28) +#define DSSR_VC0FB_DSA2 (2 << 28) +#define DSSR_VC0FB_INIT (3 << 28) +#define DSSR_VC0FB_MASK (3 << 28) +#define DSSR_DFB(n) (1 << ((n)+15)) +#define DSSR_TVR (1 << 15) +#define DSSR_FRM (1 << 14) +#define DSSR_VBK (1 << 11) +#define DSSR_RINT (1 << 9) +#define DSSR_HBK (1 << 8) +#define DSSR_ADC(n) (1 << ((n)-1)) + +#define DSRCR 0x0000c +#define DSRCR_TVCL (1 << 15) +#define DSRCR_FRCL (1 << 14) +#define DSRCR_VBCL (1 << 11) +#define DSRCR_RICL (1 << 9) +#define DSRCR_HBCL (1 << 8) +#define DSRCR_ADCL(n) (1 << ((n)-1)) +#define DSRCR_MASK 0x0000cbff + +#define DIER 0x00010 +#define DIER_TVE (1 << 15) +#define DIER_FRE (1 << 14) +#define DIER_VBE (1 << 11) +#define DIER_RIE (1 << 9) +#define DIER_HBE (1 << 8) +#define DIER_ADCE(n) (1 << ((n)-1)) + +#define CPCR 0x00014 +#define CPCR_CP4CE (1 << 19) +#define CPCR_CP3CE (1 << 18) +#define CPCR_CP2CE (1 << 17) +#define CPCR_CP1CE (1 << 16) + +#define DPPR 0x00018 +#define DPPR_DPE(n) (1 << ((n)*4-1)) +#define DPPR_DPS(n, p) (((p)-1) << DPPR_DPS_SHIFT(n)) +#define DPPR_DPS_SHIFT(n) (((n)-1)*4) +#define DPPR_BPP16 (DPPR_DPE(8) | DPPR_DPS(8, 1)) /* plane1 */ +#define DPPR_BPP32_P1 (DPPR_DPE(7) | DPPR_DPS(7, 1)) +#define DPPR_BPP32_P2 (DPPR_DPE(8) | DPPR_DPS(8, 2)) +#define DPPR_BPP32 (DPPR_BPP32_P1 | DPPR_BPP32_P2) /* plane1 & 2 */ + +#define DEFR 0x00020 +#define DEFR_CODE (0x7773 << 16) +#define DEFR_EXSL (1 << 12) +#define DEFR_EXVL (1 << 11) +#define DEFR_EXUP (1 << 5) +#define DEFR_VCUP (1 << 4) +#define DEFR_DEFE (1 << 0) + +#define DAPCR 0x00024 +#define DAPCR_CODE (0x7773 << 16) +#define DAPCR_AP2E (1 << 4) +#define DAPCR_AP1E (1 << 0) + +#define DCPCR 0x00028 +#define DCPCR_CODE (0x7773 << 16) +#define DCPCR_CA2B (1 << 13) +#define DCPCR_CD2F (1 << 12) +#define DCPCR_DC2E (1 << 8) +#define DCPCR_CAB (1 << 5) +#define DCPCR_CDF (1 << 4) +#define DCPCR_DCE (1 << 0) + +#define DEFR2 0x00034 +#define DEFR2_CODE (0x7775 << 16) +#define DEFR2_DEFE2G (1 << 0) + +#define DEFR3 0x00038 +#define DEFR3_CODE (0x7776 << 16) +#define DEFR3_EVDA (1 << 14) +#define DEFR3_EVDM_1 (1 << 12) +#define DEFR3_EVDM_2 (2 << 12) +#define DEFR3_EVDM_3 (3 << 12) +#define DEFR3_VMSM2_EMA (1 << 6) +#define DEFR3_VMSM1_ENA (1 << 4) +#define DEFR3_DEFE3 (1 << 0) + +#define DEFR4 0x0003c +#define DEFR4_CODE (0x7777 << 16) +#define DEFR4_LRUO (1 << 5) +#define DEFR4_SPCE (1 << 4) + +#define DVCSR 0x000d0 +#define DVCSR_VCnFB2_DSA0(n) (0 << ((n)*2+16)) +#define DVCSR_VCnFB2_DSA1(n) (1 << ((n)*2+16)) +#define DVCSR_VCnFB2_DSA2(n) (2 << ((n)*2+16)) +#define DVCSR_VCnFB2_INIT(n) (3 << ((n)*2+16)) +#define DVCSR_VCnFB2_MASK(n) (3 << ((n)*2+16)) +#define DVCSR_VCnFB_DSA0(n) (0 << ((n)*2)) +#define DVCSR_VCnFB_DSA1(n) (1 << ((n)*2)) +#define DVCSR_VCnFB_DSA2(n) (2 << ((n)*2)) +#define DVCSR_VCnFB_INIT(n) (3 << ((n)*2)) +#define DVCSR_VCnFB_MASK(n) (3 << ((n)*2)) + +#define DEFR5 0x000e0 +#define DEFR5_CODE (0x66 << 24) +#define DEFR5_YCRGB2_DIS (0 << 14) +#define DEFR5_YCRGB2_PRI1 (1 << 14) +#define DEFR5_YCRGB2_PRI2 (2 << 14) +#define DEFR5_YCRGB2_PRI3 (3 << 14) +#define DEFR5_YCRGB2_MASK (3 << 14) +#define DEFR5_YCRGB1_DIS (0 << 12) +#define DEFR5_YCRGB1_PRI1 (1 << 12) +#define DEFR5_YCRGB1_PRI2 (2 << 12) +#define DEFR5_YCRGB1_PRI3 (3 << 12) +#define DEFR5_YCRGB1_MASK (3 << 12) +#define DEFR5_DEFE5 (1 << 0) + +#define DDLTR 0x000e4 +#define DDLTR_CODE (0x7766 << 16) +#define DDLTR_DLAR2 (1 << 6) +#define DDLTR_DLAY2 (1 << 5) +#define DDLTR_DLAY1 (1 << 1) + +#define DEFR6 0x000e8 +#define DEFR6_CODE (0x7778 << 16) +#define DEFR6_ODPM12_DSMR (0 << 10) +#define DEFR6_ODPM12_DISP (2 << 10) +#define DEFR6_ODPM12_CDE (3 << 10) +#define DEFR6_ODPM12_MASK (3 << 10) +#define DEFR6_ODPM02_DSMR (0 << 8) +#define DEFR6_ODPM02_DISP (2 << 8) +#define DEFR6_ODPM02_CDE (3 << 8) +#define DEFR6_ODPM02_MASK (3 << 8) +#define DEFR6_TCNE1 (1 << 6) +#define DEFR6_TCNE0 (1 << 4) +#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 + */ + +#define DD1SSR 0x20008 +#define DD1SSR_TVR (1 << 15) +#define DD1SSR_FRM (1 << 14) +#define DD1SSR_BUF (1 << 12) +#define DD1SSR_VBK (1 << 11) +#define DD1SSR_RINT (1 << 9) +#define DD1SSR_HBK (1 << 8) +#define DD1SSR_ADC(n) (1 << ((n)-1)) + +#define DD1SRCR 0x2000c +#define DD1SRCR_TVR (1 << 15) +#define DD1SRCR_FRM (1 << 14) +#define DD1SRCR_BUF (1 << 12) +#define DD1SRCR_VBK (1 << 11) +#define DD1SRCR_RINT (1 << 9) +#define DD1SRCR_HBK (1 << 8) +#define DD1SRCR_ADC(n) (1 << ((n)-1)) + +#define DD1IER 0x20010 +#define DD1IER_TVR (1 << 15) +#define DD1IER_FRM (1 << 14) +#define DD1IER_BUF (1 << 12) +#define DD1IER_VBK (1 << 11) +#define DD1IER_RINT (1 << 9) +#define DD1IER_HBK (1 << 8) +#define DD1IER_ADC(n) (1 << ((n)-1)) + +#define DEFR8 0x20020 +#define DEFR8_CODE (0x7790 << 16) +#define DEFR8_VSCS (1 << 6) +#define DEFR8_DRGBS_DU(n) ((n) << 4) +#define DEFR8_DRGBS_MASK (3 << 4) +#define DEFR8_DEFE8 (1 << 0) + +#define DOFLR 0x20024 +#define DOFLR_CODE (0x7790 << 16) +#define DOFLR_HSYCFL1 (1 << 13) +#define DOFLR_VSYCFL1 (1 << 12) +#define DOFLR_ODDFL1 (1 << 11) +#define DOFLR_DISPFL1 (1 << 10) +#define DOFLR_CDEFL1 (1 << 9) +#define DOFLR_RGBFL1 (1 << 8) +#define DOFLR_HSYCFL0 (1 << 5) +#define DOFLR_VSYCFL0 (1 << 4) +#define DOFLR_ODDFL0 (1 << 3) +#define DOFLR_DISPFL0 (1 << 2) +#define DOFLR_CDEFL0 (1 << 1) +#define DOFLR_RGBFL0 (1 << 0) + +#define DIDSR 0x20028 +#define DIDSR_CODE (0x7790 << 16) +#define DIDSR_LDCS_DCLKIN(n) (0 << (8 + (n) * 2)) +#define DIDSR_LDCS_DSI(n) (2 << (8 + (n) * 2)) /* V3U only */ +#define DIDSR_LDCS_LVDS0(n) (2 << (8 + (n) * 2)) +#define DIDSR_LDCS_LVDS1(n) (3 << (8 + (n) * 2)) +#define DIDSR_LDCS_MASK(n) (3 << (8 + (n) * 2)) +#define DIDSR_PDCS_CLK(n, clk) (clk << ((n) * 2)) +#define DIDSR_PDCS_MASK(n) (3 << ((n) * 2)) + +#define DEFR10 0x20038 +#define DEFR10_CODE (0x7795 << 16) +#define DEFR10_VSPF1_RGB (0 << 14) +#define DEFR10_VSPF1_YC (1 << 14) +#define DEFR10_DOCF1_RGB (0 << 12) +#define DEFR10_DOCF1_YC (1 << 12) +#define DEFR10_YCDF0_YCBCR444 (0 << 11) +#define DEFR10_YCDF0_YCBCR422 (1 << 11) +#define DEFR10_VSPF0_RGB (0 << 10) +#define DEFR10_VSPF0_YC (1 << 10) +#define DEFR10_DOCF0_RGB (0 << 8) +#define DEFR10_DOCF0_YC (1 << 8) +#define DEFR10_TSEL_H3_TCON1 (0 << 1) /* DEFR102 register only (DU2/DU3) */ +#define DEFR10_DEFE10 (1 << 0) + +#define DPLLCR 0x20044 +#define DPLLCR_CODE (0x95 << 24) +#define DPLLCR_PLCS1 (1 << 23) +#define DPLLCR_PLCS0 (1 << 21) +#define DPLLCR_CLKE (1 << 18) +#define DPLLCR_FDPLL(n) ((n) << 12) +#define DPLLCR_N(n) ((n) << 5) +#define DPLLCR_M(n) ((n) << 3) +#define DPLLCR_STBY (1 << 2) +#define DPLLCR_INCS_DOTCLKIN0 (0 << 0) +#define DPLLCR_INCS_DOTCLKIN1 (1 << 1) + +#define DPLLC2R 0x20048 +#define DPLLC2R_CODE (0x95 << 24) +#define DPLLC2R_SELC (1 << 12) +#define DPLLC2R_M(n) ((n) << 8) +#define DPLLC2R_FDPLL(n) ((n) << 0) + +/* ----------------------------------------------------------------------------- + * Display Timing Generation Registers + */ + +#define HDSR 0x00040 +#define HDER 0x00044 +#define VDSR 0x00048 +#define VDER 0x0004c +#define HCR 0x00050 +#define HSWR 0x00054 +#define VCR 0x00058 +#define VSPR 0x0005c +#define EQWR 0x00060 +#define SPWR 0x00064 +#define CLAMPSR 0x00070 +#define CLAMPWR 0x00074 +#define DESR 0x00078 +#define DEWR 0x0007c + +/* ----------------------------------------------------------------------------- + * Display Attribute Registers + */ + +#define CP1TR 0x00080 +#define CP2TR 0x00084 +#define CP3TR 0x00088 +#define CP4TR 0x0008c + +#define DOOR 0x00090 +#define DOOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2)) +#define CDER 0x00094 +#define CDER_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2)) +#define BPOR 0x00098 +#define BPOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2)) + +#define RINTOFSR 0x0009c + +#define DSHPR 0x000c8 +#define DSHPR_CODE (0x7776 << 16) +#define DSHPR_PRIH (0xa << 4) +#define DSHPR_PRIL_BPP16 (0x8 << 0) +#define DSHPR_PRIL_BPP32 (0x9 << 0) + +/* ----------------------------------------------------------------------------- + * Display Plane Registers + */ + +#define PLANE_OFF 0x00100 + +#define PnMR 0x00100 /* plane 1 */ +#define PnMR_VISL_VIN0 (0 << 26) /* use Video Input 0 */ +#define PnMR_VISL_VIN1 (1 << 26) /* use Video Input 1 */ +#define PnMR_VISL_VIN2 (2 << 26) /* use Video Input 2 */ +#define PnMR_VISL_VIN3 (3 << 26) /* use Video Input 3 */ +#define PnMR_YCDF_YUYV (1 << 20) /* YUYV format */ +#define PnMR_TC_R (0 << 17) /* Tranparent color is PnTC1R */ +#define PnMR_TC_CP (1 << 17) /* Tranparent color is color palette */ +#define PnMR_WAE (1 << 16) /* Wrap around Enable */ +#define PnMR_SPIM_TP (0 << 12) /* Transparent Color */ +#define PnMR_SPIM_ALP (1 << 12) /* Alpha Blending */ +#define PnMR_SPIM_EOR (2 << 12) /* EOR */ +#define PnMR_SPIM_TP_OFF (1 << 14) /* No Transparent Color */ +#define PnMR_CPSL_CP1 (0 << 8) /* Color Palette selected 1 */ +#define PnMR_CPSL_CP2 (1 << 8) /* Color Palette selected 2 */ +#define PnMR_CPSL_CP3 (2 << 8) /* Color Palette selected 3 */ +#define PnMR_CPSL_CP4 (3 << 8) /* Color Palette selected 4 */ +#define PnMR_DC (1 << 7) /* Display Area Change */ +#define PnMR_BM_MD (0 << 4) /* Manual Display Change Mode */ +#define PnMR_BM_AR (1 << 4) /* Auto Rendering Mode */ +#define PnMR_BM_AD (2 << 4) /* Auto Display Change Mode */ +#define PnMR_BM_VC (3 << 4) /* Video Capture Mode */ +#define PnMR_DDDF_8BPP (0 << 0) /* 8bit */ +#define PnMR_DDDF_16BPP (1 << 0) /* 16bit or 32bit */ +#define PnMR_DDDF_ARGB (2 << 0) /* ARGB */ +#define PnMR_DDDF_YC (3 << 0) /* YC */ +#define PnMR_DDDF_MASK (3 << 0) + +#define PnMWR 0x00104 + +#define PnALPHAR 0x00108 +#define PnALPHAR_ABIT_1 (0 << 12) +#define PnALPHAR_ABIT_0 (1 << 12) +#define PnALPHAR_ABIT_X (2 << 12) + +#define PnDSXR 0x00110 +#define PnDSYR 0x00114 +#define PnDPXR 0x00118 +#define PnDPYR 0x0011c + +#define PnDSA0R 0x00120 +#define PnDSA1R 0x00124 +#define PnDSA2R 0x00128 +#define PnDSA_MASK 0xfffffff0 + +#define PnSPXR 0x00130 +#define PnSPYR 0x00134 +#define PnWASPR 0x00138 +#define PnWAMWR 0x0013c + +#define PnBTR 0x00140 + +#define PnTC1R 0x00144 +#define PnTC2R 0x00148 +#define PnTC3R 0x0014c +#define PnTC3R_CODE (0x66 << 24) + +#define PnMLR 0x00150 + +#define PnSWAPR 0x00180 +#define PnSWAPR_DIGN (1 << 4) +#define PnSWAPR_SPQW (1 << 3) +#define PnSWAPR_SPLW (1 << 2) +#define PnSWAPR_SPWD (1 << 1) +#define PnSWAPR_SPBY (1 << 0) + +#define PnDDCR 0x00184 +#define PnDDCR_CODE (0x7775 << 16) +#define PnDDCR_LRGB1 (1 << 11) +#define PnDDCR_LRGB0 (1 << 10) + +#define PnDDCR2 0x00188 +#define PnDDCR2_CODE (0x7776 << 16) +#define PnDDCR2_NV21 (1 << 5) +#define PnDDCR2_Y420 (1 << 4) +#define PnDDCR2_DIVU (1 << 1) +#define PnDDCR2_DIVY (1 << 0) + +#define PnDDCR4 0x00190 +#define PnDDCR4_CODE (0x7766 << 16) +#define PnDDCR4_VSPS (1 << 13) +#define PnDDCR4_SDFS_RGB (0 << 4) +#define PnDDCR4_SDFS_YC (5 << 4) +#define PnDDCR4_SDFS_MASK (7 << 4) +#define PnDDCR4_EDF_NONE (0 << 0) +#define PnDDCR4_EDF_ARGB8888 (1 << 0) +#define PnDDCR4_EDF_RGB888 (2 << 0) +#define PnDDCR4_EDF_RGB666 (3 << 0) +#define PnDDCR4_EDF_MASK (7 << 0) + +#define APnMR 0x0a100 +#define APnMR_WAE (1 << 16) /* Wrap around Enable */ +#define APnMR_DC (1 << 7) /* Display Area Change */ +#define APnMR_BM_MD (0 << 4) /* Manual Display Change Mode */ +#define APnMR_BM_AD (2 << 4) /* Auto Display Change Mode */ + +#define APnMWR 0x0a104 + +#define APnDSXR 0x0a110 +#define APnDSYR 0x0a114 +#define APnDPXR 0x0a118 +#define APnDPYR 0x0a11c + +#define APnDSA0R 0x0a120 +#define APnDSA1R 0x0a124 +#define APnDSA2R 0x0a128 + +#define APnSPXR 0x0a130 +#define APnSPYR 0x0a134 +#define APnWASPR 0x0a138 +#define APnWAMWR 0x0a13c + +#define APnBTR 0x0a140 + +#define APnMLR 0x0a150 +#define APnSWAPR 0x0a180 + +/* ----------------------------------------------------------------------------- + * Display Capture Registers + */ + +#define DCMR 0x0c100 +#define DCMWR 0x0c104 +#define DCSAR 0x0c120 +#define DCMLR 0x0c150 + +/* ----------------------------------------------------------------------------- + * Color Palette Registers + */ + +#define CP1_000R 0x01000 +#define CP1_255R 0x013fc +#define CP2_000R 0x02000 +#define CP2_255R 0x023fc +#define CP3_000R 0x03000 +#define CP3_255R 0x033fc +#define CP4_000R 0x04000 +#define CP4_255R 0x043fc + +/* ----------------------------------------------------------------------------- + * External Synchronization Control Registers + */ + +#define ESCR02 0x10000 +#define ESCR13 0x01000 +#define ESCR_DCLKOINV (1 << 25) +#define ESCR_DCLKSEL_DCLKIN (0 << 20) +#define ESCR_DCLKSEL_CLKS (1 << 20) +#define ESCR_DCLKSEL_MASK (1 << 20) +#define ESCR_DCLKDIS (1 << 16) +#define ESCR_SYNCSEL_OFF (0 << 8) +#define ESCR_SYNCSEL_EXVSYNC (2 << 8) +#define ESCR_SYNCSEL_EXHSYNC (3 << 8) +#define ESCR_FRQSEL_MASK (0x3f << 0) + +#define OTAR02 0x10004 +#define OTAR13 0x01004 + +/* ----------------------------------------------------------------------------- + * Dual Display Output Control Registers + */ + +#define DORCR 0x11000 +#define DORCR_PG1T (1 << 30) +#define DORCR_DK1S (1 << 28) +#define DORCR_PG1D_DS0 (0 << 24) +#define DORCR_PG1D_DS1 (1 << 24) +#define DORCR_PG1D_FIX0 (2 << 24) +#define DORCR_PG1D_DOOR (3 << 24) +#define DORCR_PG1D_MASK (3 << 24) +#define DORCR_DR0D (1 << 21) +#define DORCR_PG0D_DS0 (0 << 16) +#define DORCR_PG0D_DS1 (1 << 16) +#define DORCR_PG0D_FIX0 (2 << 16) +#define DORCR_PG0D_DOOR (3 << 16) +#define DORCR_PG0D_MASK (3 << 16) +#define DORCR_RGPV (1 << 4) +#define DORCR_DPRS (1 << 0) + +#define DPTSR 0x11004 +#define DPTSR_PnDK(n) (1 << ((n) + 16)) +#define DPTSR_PnTS(n) (1 << (n)) + +#define DAPTSR 0x11008 +#define DAPTSR_APnDK(n) (1 << ((n) + 16)) +#define DAPTSR_APnTS(n) (1 << (n)) + +#define DS1PR 0x11020 +#define DS2PR 0x11024 + +/* ----------------------------------------------------------------------------- + * YC-RGB Conversion Coefficient Registers + */ + +#define YNCR 0x11080 +#define YNOR 0x11084 +#define CRNOR 0x11088 +#define CBNOR 0x1108c +#define RCRCR 0x11090 +#define GCRCR 0x11094 +#define GCBCR 0x11098 +#define BCBCR 0x1109c + +#endif /* __RCAR_DU_REGS_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_vsp.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_vsp.c new file mode 100644 index 000000000..ae9f381b0 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_vsp.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * R-Car Display Unit VSP-Based Compositor + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_blend.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_dma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_vblank.h> + +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include <media/vsp1.h> + +#include "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_vsp.h" +#include "rcar_du_writeback.h" + +static void rcar_du_vsp_complete(void *private, unsigned int status, u32 crc) +{ + struct rcar_du_crtc *crtc = private; + + if (crtc->vblank_enable) + drm_crtc_handle_vblank(&crtc->crtc); + + if (status & VSP1_DU_STATUS_COMPLETE) + rcar_du_crtc_finish_page_flip(crtc); + if (status & VSP1_DU_STATUS_WRITEBACK) + rcar_du_writeback_complete(crtc); + + drm_crtc_add_crc_entry(&crtc->crtc, false, 0, &crc); +} + +void rcar_du_vsp_enable(struct rcar_du_crtc *crtc) +{ + const struct drm_display_mode *mode = &crtc->crtc.state->adjusted_mode; + struct rcar_du_device *rcdu = crtc->dev; + struct vsp1_du_lif_config cfg = { + .width = mode->hdisplay, + .height = mode->vdisplay, + .interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE, + .callback = rcar_du_vsp_complete, + .callback_data = crtc, + }; + struct rcar_du_plane_state state = { + .state = { + .alpha = DRM_BLEND_ALPHA_OPAQUE, + .crtc = &crtc->crtc, + .dst.x1 = 0, + .dst.y1 = 0, + .dst.x2 = mode->hdisplay, + .dst.y2 = mode->vdisplay, + .src.x1 = 0, + .src.y1 = 0, + .src.x2 = mode->hdisplay << 16, + .src.y2 = mode->vdisplay << 16, + .zpos = 0, + }, + .format = rcar_du_format_info(DRM_FORMAT_XRGB8888), + .source = RCAR_DU_PLANE_VSPD1, + .colorkey = 0, + }; + + if (rcdu->info->gen >= 3) + state.hwindex = (crtc->index % 2) ? 2 : 0; + else + state.hwindex = crtc->index % 2; + + __rcar_du_plane_setup(crtc->group, &state); + + vsp1_du_enable(crtc->vsp->vsp, crtc->vsp_pipe, &cfg); +} + +void rcar_du_vsp_disable(struct rcar_du_crtc *crtc) +{ + vsp1_du_disable(crtc->vsp->vsp, crtc->vsp_pipe); +} + +void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc) +{ + vsp1_du_atomic_begin(crtc->vsp->vsp, crtc->vsp_pipe); +} + +void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc) +{ + struct vsp1_du_atomic_pipe_config cfg = { { 0, } }; + struct rcar_du_crtc_state *state; + + state = to_rcar_crtc_state(crtc->crtc.state); + cfg.crc = state->crc; + + rcar_du_writeback_setup(crtc, &cfg.writeback); + + vsp1_du_atomic_flush(crtc->vsp->vsp, crtc->vsp_pipe, &cfg); +} + +static const u32 rcar_du_vsp_formats[] = { + DRM_FORMAT_RGB332, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV16, + DRM_FORMAT_NV61, + DRM_FORMAT_YUV420, + DRM_FORMAT_YVU420, + DRM_FORMAT_YUV422, + DRM_FORMAT_YVU422, + DRM_FORMAT_YUV444, + DRM_FORMAT_YVU444, +}; + +/* + * Gen4 supports the same formats as above, and additionally 2-10-10-10 RGB + * formats and Y210 & Y212 formats. + */ +static const u32 rcar_du_vsp_formats_gen4[] = { + DRM_FORMAT_RGB332, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGBX1010102, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV16, + DRM_FORMAT_NV61, + DRM_FORMAT_YUV420, + DRM_FORMAT_YVU420, + DRM_FORMAT_YUV422, + DRM_FORMAT_YVU422, + DRM_FORMAT_YUV444, + DRM_FORMAT_YVU444, + DRM_FORMAT_Y210, + DRM_FORMAT_Y212, +}; + +static u32 rcar_du_vsp_state_get_format(struct rcar_du_vsp_plane_state *state) +{ + u32 fourcc = state->format->fourcc; + + if (state->state.pixel_blend_mode == DRM_MODE_BLEND_PIXEL_NONE) { + switch (fourcc) { + case DRM_FORMAT_ARGB1555: + fourcc = DRM_FORMAT_XRGB1555; + break; + + case DRM_FORMAT_ARGB4444: + fourcc = DRM_FORMAT_XRGB4444; + break; + + case DRM_FORMAT_ARGB8888: + fourcc = DRM_FORMAT_XRGB8888; + break; + + case DRM_FORMAT_ABGR8888: + fourcc = DRM_FORMAT_XBGR8888; + break; + + case DRM_FORMAT_BGRA8888: + fourcc = DRM_FORMAT_BGRX8888; + break; + + case DRM_FORMAT_RGBA1010102: + fourcc = DRM_FORMAT_RGBX1010102; + break; + } + } + + return fourcc; +} + +static void rcar_du_vsp_plane_setup(struct rcar_du_vsp_plane *plane) +{ + struct rcar_du_vsp_plane_state *state = + to_rcar_vsp_plane_state(plane->plane.state); + struct rcar_du_crtc *crtc = to_rcar_crtc(state->state.crtc); + struct drm_framebuffer *fb = plane->plane.state->fb; + const struct rcar_du_format_info *format; + struct vsp1_du_atomic_config cfg = { + .pixelformat = 0, + .pitch = fb->pitches[0], + .alpha = state->state.alpha >> 8, + .zpos = state->state.zpos, + }; + u32 fourcc = rcar_du_vsp_state_get_format(state); + unsigned int i; + + cfg.src.left = state->state.src.x1 >> 16; + cfg.src.top = state->state.src.y1 >> 16; + cfg.src.width = drm_rect_width(&state->state.src) >> 16; + cfg.src.height = drm_rect_height(&state->state.src) >> 16; + + cfg.dst.left = state->state.dst.x1; + cfg.dst.top = state->state.dst.y1; + cfg.dst.width = drm_rect_width(&state->state.dst); + cfg.dst.height = drm_rect_height(&state->state.dst); + + for (i = 0; i < state->format->planes; ++i) + cfg.mem[i] = sg_dma_address(state->sg_tables[i].sgl) + + fb->offsets[i]; + + format = rcar_du_format_info(fourcc); + cfg.pixelformat = format->v4l2; + + cfg.premult = state->state.pixel_blend_mode == DRM_MODE_BLEND_PREMULTI; + + vsp1_du_atomic_update(plane->vsp->vsp, crtc->vsp_pipe, + plane->index, &cfg); +} + +int rcar_du_vsp_map_fb(struct rcar_du_vsp *vsp, struct drm_framebuffer *fb, + struct sg_table sg_tables[3]) +{ + struct rcar_du_device *rcdu = vsp->dev; + unsigned int i, j; + int ret; + + for (i = 0; i < fb->format->num_planes; ++i) { + struct drm_gem_dma_object *gem = drm_fb_dma_get_gem_obj(fb, i); + struct sg_table *sgt = &sg_tables[i]; + + if (gem->sgt) { + struct scatterlist *src; + struct scatterlist *dst; + + /* + * If the GEM buffer has a scatter gather table, it has + * been imported from a dma-buf and has no physical + * address as it might not be physically contiguous. + * Copy the original scatter gather table to map it to + * the VSP. + */ + ret = sg_alloc_table(sgt, gem->sgt->orig_nents, + GFP_KERNEL); + if (ret) + goto fail; + + src = gem->sgt->sgl; + dst = sgt->sgl; + for (j = 0; j < gem->sgt->orig_nents; ++j) { + sg_set_page(dst, sg_page(src), src->length, + src->offset); + src = sg_next(src); + dst = sg_next(dst); + } + } else { + ret = dma_get_sgtable(rcdu->dev, sgt, gem->vaddr, + gem->dma_addr, gem->base.size); + if (ret) + goto fail; + } + + ret = vsp1_du_map_sg(vsp->vsp, sgt); + if (ret) { + sg_free_table(sgt); + goto fail; + } + } + + return 0; + +fail: + while (i--) { + struct sg_table *sgt = &sg_tables[i]; + + vsp1_du_unmap_sg(vsp->vsp, sgt); + sg_free_table(sgt); + } + + return ret; +} + +static int rcar_du_vsp_plane_prepare_fb(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct rcar_du_vsp_plane_state *rstate = to_rcar_vsp_plane_state(state); + struct rcar_du_vsp *vsp = to_rcar_vsp_plane(plane)->vsp; + int ret; + + /* + * There's no need to prepare (and unprepare) the framebuffer when the + * plane is not visible, as it will not be displayed. + */ + if (!state->visible) + return 0; + + ret = rcar_du_vsp_map_fb(vsp, state->fb, rstate->sg_tables); + if (ret < 0) + return ret; + + return drm_gem_plane_helper_prepare_fb(plane, state); +} + +void rcar_du_vsp_unmap_fb(struct rcar_du_vsp *vsp, struct drm_framebuffer *fb, + struct sg_table sg_tables[3]) +{ + unsigned int i; + + for (i = 0; i < fb->format->num_planes; ++i) { + struct sg_table *sgt = &sg_tables[i]; + + vsp1_du_unmap_sg(vsp->vsp, sgt); + sg_free_table(sgt); + } +} + +static void rcar_du_vsp_plane_cleanup_fb(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct rcar_du_vsp_plane_state *rstate = to_rcar_vsp_plane_state(state); + struct rcar_du_vsp *vsp = to_rcar_vsp_plane(plane)->vsp; + + if (!state->visible) + return; + + rcar_du_vsp_unmap_fb(vsp, state->fb, rstate->sg_tables); +} + +static int rcar_du_vsp_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct rcar_du_vsp_plane_state *rstate = to_rcar_vsp_plane_state(new_plane_state); + + return __rcar_du_plane_atomic_check(plane, new_plane_state, + &rstate->format); +} + +static void rcar_du_vsp_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); + struct rcar_du_vsp_plane *rplane = to_rcar_vsp_plane(plane); + struct rcar_du_crtc *crtc = to_rcar_crtc(old_state->crtc); + + if (new_state->visible) + rcar_du_vsp_plane_setup(rplane); + else if (old_state->crtc) + vsp1_du_atomic_update(rplane->vsp->vsp, crtc->vsp_pipe, + rplane->index, NULL); +} + +static const struct drm_plane_helper_funcs rcar_du_vsp_plane_helper_funcs = { + .prepare_fb = rcar_du_vsp_plane_prepare_fb, + .cleanup_fb = rcar_du_vsp_plane_cleanup_fb, + .atomic_check = rcar_du_vsp_plane_atomic_check, + .atomic_update = rcar_du_vsp_plane_atomic_update, +}; + +static struct drm_plane_state * +rcar_du_vsp_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct rcar_du_vsp_plane_state *copy; + + if (WARN_ON(!plane->state)) + return NULL; + + copy = kzalloc_obj(*copy); + if (copy == NULL) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, ©->state); + + return ©->state; +} + +static void rcar_du_vsp_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(to_rcar_vsp_plane_state(state)); +} + +static void rcar_du_vsp_plane_reset(struct drm_plane *plane) +{ + struct rcar_du_vsp_plane_state *state; + + if (plane->state) { + rcar_du_vsp_plane_atomic_destroy_state(plane, plane->state); + plane->state = NULL; + } + + state = kzalloc_obj(*state); + if (state == NULL) + return; + + __drm_atomic_helper_plane_reset(plane, &state->state); +} + +static const struct drm_plane_funcs rcar_du_vsp_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = rcar_du_vsp_plane_reset, + .destroy = drm_plane_cleanup, + .atomic_duplicate_state = rcar_du_vsp_plane_atomic_duplicate_state, + .atomic_destroy_state = rcar_du_vsp_plane_atomic_destroy_state, +}; + +static void rcar_du_vsp_cleanup(struct drm_device *dev, void *res) +{ + struct rcar_du_vsp *vsp = res; + unsigned int i; + + for (i = 0; i < vsp->num_planes; ++i) { + struct rcar_du_vsp_plane *plane = &vsp->planes[i]; + + drm_plane_cleanup(&plane->plane); + } + + kfree(vsp->planes); + + if (vsp->link) + device_link_del(vsp->link); + + put_device(vsp->vsp); +} + +int rcar_du_vsp_init(struct rcar_du_vsp *vsp, struct device_node *np, + unsigned int crtcs) +{ + struct rcar_du_device *rcdu = vsp->dev; + struct platform_device *pdev; + unsigned int num_crtcs = hweight32(crtcs); + unsigned int num_planes; + unsigned int i; + int ret; + + /* Find the VSP device and initialize it. */ + pdev = of_find_device_by_node(np); + if (!pdev) + return -ENXIO; + + vsp->vsp = &pdev->dev; + + ret = drmm_add_action_or_reset(&rcdu->ddev, rcar_du_vsp_cleanup, vsp); + if (ret < 0) + return ret; + + /* + * Enforce suspend/resume ordering between the DU (consumer) and the + * VSP (supplier). The DU will be suspended before and resume after the + * VSP. + */ + vsp->link = device_link_add(rcdu->dev, vsp->vsp, DL_FLAG_STATELESS); + if (!vsp->link) { + dev_err(rcdu->dev, "Failed to create device link to VSP %s\n", + dev_name(vsp->vsp)); + return -EINVAL; + } + + ret = vsp1_du_init(vsp->vsp); + if (ret < 0) + return ret; + + num_planes = rcdu->info->num_rpf; + + vsp->planes = kzalloc_objs(*vsp->planes, num_planes); + if (!vsp->planes) + return -ENOMEM; + + for (i = 0; i < num_planes; ++i) { + enum drm_plane_type type = i < num_crtcs + ? DRM_PLANE_TYPE_PRIMARY + : DRM_PLANE_TYPE_OVERLAY; + struct rcar_du_vsp_plane *plane = &vsp->planes[i]; + unsigned int num_formats; + const u32 *formats; + + if (rcdu->info->gen < 4) { + num_formats = ARRAY_SIZE(rcar_du_vsp_formats); + formats = rcar_du_vsp_formats; + } else { + num_formats = ARRAY_SIZE(rcar_du_vsp_formats_gen4); + formats = rcar_du_vsp_formats_gen4; + } + + plane->vsp = vsp; + plane->index = i; + + ret = drm_universal_plane_init(&rcdu->ddev, &plane->plane, + crtcs, &rcar_du_vsp_plane_funcs, + formats, num_formats, + NULL, type, NULL); + if (ret < 0) + return ret; + + drm_plane_helper_add(&plane->plane, + &rcar_du_vsp_plane_helper_funcs); + + drm_plane_create_alpha_property(&plane->plane); + drm_plane_create_zpos_property(&plane->plane, i, 0, + num_planes - 1); + + drm_plane_create_blend_mode_property(&plane->plane, + BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE)); + + vsp->num_planes++; + } + + return 0; +} diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_vsp.h b/drivers/gpu/drm/renesas/rcar-du/rcar_du_vsp.h new file mode 100644 index 000000000..a6731249d --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_vsp.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * R-Car Display Unit VSP-Based Compositor + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __RCAR_DU_VSP_H__ +#define __RCAR_DU_VSP_H__ + +#include <drm/drm_plane.h> + +struct device_link; +struct drm_framebuffer; +struct rcar_du_format_info; +struct rcar_du_vsp; +struct sg_table; + +struct rcar_du_vsp_plane { + struct drm_plane plane; + struct rcar_du_vsp *vsp; + unsigned int index; +}; + +struct rcar_du_vsp { + unsigned int index; + struct device *vsp; + struct device_link *link; + struct rcar_du_device *dev; + struct rcar_du_vsp_plane *planes; + unsigned int num_planes; +}; + +static inline struct rcar_du_vsp_plane *to_rcar_vsp_plane(struct drm_plane *p) +{ + return container_of(p, struct rcar_du_vsp_plane, plane); +} + +/** + * struct rcar_du_vsp_plane_state - Driver-specific plane state + * @state: base DRM plane state + * @format: information about the pixel format used by the plane + * @sg_tables: scatter-gather tables for the frame buffer memory + */ +struct rcar_du_vsp_plane_state { + struct drm_plane_state state; + + const struct rcar_du_format_info *format; + struct sg_table sg_tables[3]; +}; + +static inline struct rcar_du_vsp_plane_state * +to_rcar_vsp_plane_state(struct drm_plane_state *state) +{ + return container_of(state, struct rcar_du_vsp_plane_state, state); +} + +#ifdef CONFIG_DRM_RCAR_VSP +int rcar_du_vsp_init(struct rcar_du_vsp *vsp, struct device_node *np, + unsigned int crtcs); +void rcar_du_vsp_enable(struct rcar_du_crtc *crtc); +void rcar_du_vsp_disable(struct rcar_du_crtc *crtc); +void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc); +void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc); +int rcar_du_vsp_map_fb(struct rcar_du_vsp *vsp, struct drm_framebuffer *fb, + struct sg_table sg_tables[3]); +void rcar_du_vsp_unmap_fb(struct rcar_du_vsp *vsp, struct drm_framebuffer *fb, + struct sg_table sg_tables[3]); +#else +static inline int rcar_du_vsp_init(struct rcar_du_vsp *vsp, + struct device_node *np, + unsigned int crtcs) +{ + return -ENXIO; +} +static inline void rcar_du_vsp_enable(struct rcar_du_crtc *crtc) { }; +static inline void rcar_du_vsp_disable(struct rcar_du_crtc *crtc) { }; +static inline void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc) { }; +static inline void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc) { }; +static inline int rcar_du_vsp_map_fb(struct rcar_du_vsp *vsp, + struct drm_framebuffer *fb, + struct sg_table sg_tables[3]) +{ + return -ENXIO; +} +static inline void rcar_du_vsp_unmap_fb(struct rcar_du_vsp *vsp, + struct drm_framebuffer *fb, + struct sg_table sg_tables[3]) +{ +} +#endif + +#endif /* __RCAR_DU_VSP_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_writeback.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_writeback.c new file mode 100644 index 000000000..e5e6e6a15 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_writeback.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * R-Car Display Unit Writeback Support + * + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_device.h> +#include <drm/drm_edid.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_writeback.h> + +#include "rcar_du_crtc.h" +#include "rcar_du_drv.h" +#include "rcar_du_kms.h" +#include "rcar_du_writeback.h" + +/** + * struct rcar_du_wb_conn_state - Driver-specific writeback connector state + * @state: base DRM connector state + * @format: format of the writeback framebuffer + */ +struct rcar_du_wb_conn_state { + struct drm_connector_state state; + const struct rcar_du_format_info *format; +}; + +#define to_rcar_wb_conn_state(s) \ + container_of(s, struct rcar_du_wb_conn_state, state) + +/** + * struct rcar_du_wb_job - Driver-private data for writeback jobs + * @sg_tables: scatter-gather tables for the framebuffer memory + */ +struct rcar_du_wb_job { + struct sg_table sg_tables[3]; +}; + +static int rcar_du_wb_conn_get_modes(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + + return drm_add_modes_noedid(connector, dev->mode_config.max_width, + dev->mode_config.max_height); +} + +static int rcar_du_wb_prepare_job(struct drm_writeback_connector *connector, + struct drm_writeback_job *job) +{ + struct rcar_du_crtc *rcrtc = wb_to_rcar_crtc(connector); + struct rcar_du_wb_job *rjob; + int ret; + + if (!job->fb) + return 0; + + rjob = kzalloc_obj(*rjob); + if (!rjob) + return -ENOMEM; + + /* Map the framebuffer to the VSP. */ + ret = rcar_du_vsp_map_fb(rcrtc->vsp, job->fb, rjob->sg_tables); + if (ret < 0) { + kfree(rjob); + return ret; + } + + job->priv = rjob; + return 0; +} + +static void rcar_du_wb_cleanup_job(struct drm_writeback_connector *connector, + struct drm_writeback_job *job) +{ + struct rcar_du_crtc *rcrtc = wb_to_rcar_crtc(connector); + struct rcar_du_wb_job *rjob = job->priv; + + if (!job->fb) + return; + + rcar_du_vsp_unmap_fb(rcrtc->vsp, job->fb, rjob->sg_tables); + kfree(rjob); +} + +static const struct drm_connector_helper_funcs rcar_du_wb_conn_helper_funcs = { + .get_modes = rcar_du_wb_conn_get_modes, + .prepare_writeback_job = rcar_du_wb_prepare_job, + .cleanup_writeback_job = rcar_du_wb_cleanup_job, +}; + +static struct drm_connector_state * +rcar_du_wb_conn_duplicate_state(struct drm_connector *connector) +{ + struct rcar_du_wb_conn_state *copy; + + if (WARN_ON(!connector->state)) + return NULL; + + copy = kzalloc_obj(*copy); + if (!copy) + return NULL; + + __drm_atomic_helper_connector_duplicate_state(connector, ©->state); + + return ©->state; +} + +static void rcar_du_wb_conn_destroy_state(struct drm_connector *connector, + struct drm_connector_state *state) +{ + __drm_atomic_helper_connector_destroy_state(state); + kfree(to_rcar_wb_conn_state(state)); +} + +static void rcar_du_wb_conn_reset(struct drm_connector *connector) +{ + struct rcar_du_wb_conn_state *state; + + if (connector->state) { + rcar_du_wb_conn_destroy_state(connector, connector->state); + connector->state = NULL; + } + + state = kzalloc_obj(*state); + if (state == NULL) + return; + + __drm_atomic_helper_connector_reset(connector, &state->state); +} + +static const struct drm_connector_funcs rcar_du_wb_conn_funcs = { + .reset = rcar_du_wb_conn_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = rcar_du_wb_conn_duplicate_state, + .atomic_destroy_state = rcar_du_wb_conn_destroy_state, +}; + +static int rcar_du_wb_enc_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct rcar_du_wb_conn_state *wb_state = + to_rcar_wb_conn_state(conn_state); + const struct drm_display_mode *mode = &crtc_state->mode; + struct drm_device *dev = encoder->dev; + struct drm_framebuffer *fb; + + if (!conn_state->writeback_job) + return 0; + + fb = conn_state->writeback_job->fb; + + /* + * Verify that the framebuffer format is supported and that its size + * matches the current mode. + */ + if (fb->width != mode->hdisplay || fb->height != mode->vdisplay) { + dev_dbg(dev->dev, "%s: invalid framebuffer size %ux%u\n", + __func__, fb->width, fb->height); + return -EINVAL; + } + + wb_state->format = rcar_du_format_info(fb->format->format); + if (wb_state->format == NULL) { + dev_dbg(dev->dev, "%s: unsupported format %p4cc\n", __func__, + &fb->format->format); + return -EINVAL; + } + + return 0; +} + +static const struct drm_encoder_helper_funcs rcar_du_wb_enc_helper_funcs = { + .atomic_check = rcar_du_wb_enc_atomic_check, +}; + +/* + * Only RGB formats are currently supported as the VSP outputs RGB to the DU + * and can't convert to YUV separately for writeback. + */ +static const u32 writeback_formats[] = { + DRM_FORMAT_RGB332, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, +}; + +int rcar_du_writeback_init(struct rcar_du_device *rcdu, + struct rcar_du_crtc *rcrtc) +{ + struct drm_writeback_connector *wb_conn = &rcrtc->writeback; + + drm_connector_helper_add(&wb_conn->base, + &rcar_du_wb_conn_helper_funcs); + + return drm_writeback_connector_init(&rcdu->ddev, wb_conn, + &rcar_du_wb_conn_funcs, + &rcar_du_wb_enc_helper_funcs, + writeback_formats, + ARRAY_SIZE(writeback_formats), + 1 << drm_crtc_index(&rcrtc->crtc)); +} + +void rcar_du_writeback_setup(struct rcar_du_crtc *rcrtc, + struct vsp1_du_writeback_config *cfg) +{ + struct rcar_du_wb_conn_state *wb_state; + struct drm_connector_state *state; + struct rcar_du_wb_job *rjob; + struct drm_framebuffer *fb; + unsigned int i; + + state = rcrtc->writeback.base.state; + if (!state || !state->writeback_job) + return; + + fb = state->writeback_job->fb; + rjob = state->writeback_job->priv; + wb_state = to_rcar_wb_conn_state(state); + + cfg->pixelformat = wb_state->format->v4l2; + cfg->pitch = fb->pitches[0]; + + for (i = 0; i < wb_state->format->planes; ++i) + cfg->mem[i] = sg_dma_address(rjob->sg_tables[i].sgl) + + fb->offsets[i]; + + drm_writeback_queue_job(&rcrtc->writeback, state); +} + +void rcar_du_writeback_complete(struct rcar_du_crtc *rcrtc) +{ + drm_writeback_signal_completion(&rcrtc->writeback, 0); +} diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_writeback.h b/drivers/gpu/drm/renesas/rcar-du/rcar_du_writeback.h new file mode 100644 index 000000000..a71c9c08c --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_writeback.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * R-Car Display Unit Writeback Support + * + * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#ifndef __RCAR_DU_WRITEBACK_H__ +#define __RCAR_DU_WRITEBACK_H__ + +#include <drm/drm_plane.h> + +struct rcar_du_crtc; +struct rcar_du_device; +struct vsp1_du_atomic_pipe_config; + +#ifdef CONFIG_DRM_RCAR_WRITEBACK +int rcar_du_writeback_init(struct rcar_du_device *rcdu, + struct rcar_du_crtc *rcrtc); +void rcar_du_writeback_setup(struct rcar_du_crtc *rcrtc, + struct vsp1_du_writeback_config *cfg); +void rcar_du_writeback_complete(struct rcar_du_crtc *rcrtc); +#else +static inline int rcar_du_writeback_init(struct rcar_du_device *rcdu, + struct rcar_du_crtc *rcrtc) +{ + return -ENXIO; +} +static inline void +rcar_du_writeback_setup(struct rcar_du_crtc *rcrtc, + struct vsp1_du_writeback_config *cfg) +{ +} +static inline void rcar_du_writeback_complete(struct rcar_du_crtc *rcrtc) +{ +} +#endif + +#endif /* __RCAR_DU_WRITEBACK_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_dw_hdmi.c b/drivers/gpu/drm/renesas/rcar-du/rcar_dw_hdmi.c new file mode 100644 index 000000000..c0176e5de --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_dw_hdmi.c @@ -0,0 +1,122 @@ +// 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; +} + +static void rcar_dw_hdmi_remove(struct platform_device *pdev) +{ + struct dw_hdmi *hdmi = platform_get_drvdata(pdev); + + dw_hdmi_remove(hdmi); +} + +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 = 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"); diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c new file mode 100644 index 000000000..154410745 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c @@ -0,0 +1,1041 @@ +// 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_commit *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_atomic_commit *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_atomic_commit *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 void rcar_lvds_destroy(struct drm_bridge *bridge) +{ + struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge); + + drm_bridge_put(lvds->companion); +} + +static int rcar_lvds_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + 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(encoder, lvds->next_bridge, bridge, + flags); +} + +static const struct drm_bridge_funcs rcar_lvds_bridge_ops = { + .attach = rcar_lvds_attach, + .destroy = rcar_lvds_destroy, + .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_and_get_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_drm_bridge_alloc(&pdev->dev, struct rcar_lvds, bridge, + &rcar_lvds_bridge_ops); + if (IS_ERR(lvds)) + return PTR_ERR(lvds); + + 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.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; +} + +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); +} + +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 = { + 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 = rcar_lvds_remove, + .driver = { + .name = "rcar-lvds", + .pm = pm_ptr(&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"); diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.h b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.h new file mode 100644 index 000000000..887c63500 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * R-Car LVDS Encoder + * + * Copyright (C) 2013-2018 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __RCAR_LVDS_H__ +#define __RCAR_LVDS_H__ + +struct drm_bridge; + +#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS) +int rcar_lvds_pclk_enable(struct drm_bridge *bridge, unsigned long freq, + bool dot_clk_only); +void rcar_lvds_pclk_disable(struct drm_bridge *bridge, bool dot_clk_only); +bool rcar_lvds_dual_link(struct drm_bridge *bridge); +bool rcar_lvds_is_connected(struct drm_bridge *bridge); +#else +static inline int rcar_lvds_pclk_enable(struct drm_bridge *bridge, + unsigned long freq, bool dot_clk_only) +{ + return -ENOSYS; +} +static inline void rcar_lvds_pclk_disable(struct drm_bridge *bridge, + bool dot_clock_only) +{ +} +static inline bool rcar_lvds_dual_link(struct drm_bridge *bridge) +{ + return false; +} +static inline bool rcar_lvds_is_connected(struct drm_bridge *bridge) +{ + return false; +} +#endif /* CONFIG_DRM_RCAR_LVDS */ + +#endif /* __RCAR_LVDS_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_lvds_regs.h b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds_regs.h new file mode 100644 index 000000000..ab0406a27 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds_regs.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * R-Car LVDS Interface Registers Definitions + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __RCAR_LVDS_REGS_H__ +#define __RCAR_LVDS_REGS_H__ + +#define LVDCR0 0x0000 +#define LVDCR0_DUSEL (1 << 15) +#define LVDCR0_DMD (1 << 12) /* Gen2 only */ +#define LVDCR0_LVMD_MASK (0xf << 8) +#define LVDCR0_LVMD_SHIFT 8 +#define LVDCR0_PLLON (1 << 4) +#define LVDCR0_PWD (1 << 2) /* Gen3 only */ +#define LVDCR0_BEN (1 << 2) /* Gen2 only */ +#define LVDCR0_LVEN (1 << 1) +#define LVDCR0_LVRES (1 << 0) + +#define LVDCR1 0x0004 +#define LVDCR1_CKSEL (1 << 15) /* Gen2 only */ +#define LVDCR1_CHSTBY(n) (3 << (2 + (n) * 2)) +#define LVDCR1_CLKSTBY (3 << 0) + +#define LVDPLLCR 0x0008 +/* Gen2 & V3M */ +#define LVDPLLCR_CEEN (1 << 14) +#define LVDPLLCR_FBEN (1 << 13) +#define LVDPLLCR_COSEL (1 << 12) +#define LVDPLLCR_PLLDLYCNT_150M (0x1bf << 0) +#define LVDPLLCR_PLLDLYCNT_121M (0x22c << 0) +#define LVDPLLCR_PLLDLYCNT_60M (0x77b << 0) +#define LVDPLLCR_PLLDLYCNT_38M (0x69a << 0) +#define LVDPLLCR_PLLDLYCNT_MASK (0x7ff << 0) +/* Gen3 but V3M,D3 and E3 */ +#define LVDPLLCR_PLLDIVCNT_42M (0x014cb << 0) +#define LVDPLLCR_PLLDIVCNT_85M (0x00a45 << 0) +#define LVDPLLCR_PLLDIVCNT_128M (0x006c3 << 0) +#define LVDPLLCR_PLLDIVCNT_148M (0x046c1 << 0) +#define LVDPLLCR_PLLDIVCNT_MASK (0x7ffff << 0) +/* D3 and E3 */ +#define LVDPLLCR_PLLON (1 << 22) +#define LVDPLLCR_PLLSEL_PLL0 (0 << 20) +#define LVDPLLCR_PLLSEL_LVX (1 << 20) +#define LVDPLLCR_PLLSEL_PLL1 (2 << 20) +#define LVDPLLCR_CKSEL_LVX (1 << 17) +#define LVDPLLCR_CKSEL_EXTAL (3 << 17) +#define LVDPLLCR_CKSEL_DU_DOTCLKIN(n) ((5 + (n) * 2) << 17) +#define LVDPLLCR_OCKSEL (1 << 16) +#define LVDPLLCR_STP_CLKOUTE (1 << 14) +#define LVDPLLCR_OUTCLKSEL (1 << 12) +#define LVDPLLCR_CLKOUT (1 << 11) +#define LVDPLLCR_PLLE(n) ((n) << 10) +#define LVDPLLCR_PLLN(n) ((n) << 3) +#define LVDPLLCR_PLLM(n) ((n) << 0) + +#define LVDCTRCR 0x000c +#define LVDCTRCR_CTR3SEL_ZERO (0 << 12) +#define LVDCTRCR_CTR3SEL_ODD (1 << 12) +#define LVDCTRCR_CTR3SEL_CDE (2 << 12) +#define LVDCTRCR_CTR3SEL_MASK (7 << 12) +#define LVDCTRCR_CTR2SEL_DISP (0 << 8) +#define LVDCTRCR_CTR2SEL_ODD (1 << 8) +#define LVDCTRCR_CTR2SEL_CDE (2 << 8) +#define LVDCTRCR_CTR2SEL_HSYNC (3 << 8) +#define LVDCTRCR_CTR2SEL_VSYNC (4 << 8) +#define LVDCTRCR_CTR2SEL_MASK (7 << 8) +#define LVDCTRCR_CTR1SEL_VSYNC (0 << 4) +#define LVDCTRCR_CTR1SEL_DISP (1 << 4) +#define LVDCTRCR_CTR1SEL_ODD (2 << 4) +#define LVDCTRCR_CTR1SEL_CDE (3 << 4) +#define LVDCTRCR_CTR1SEL_HSYNC (4 << 4) +#define LVDCTRCR_CTR1SEL_MASK (7 << 4) +#define LVDCTRCR_CTR0SEL_HSYNC (0 << 0) +#define LVDCTRCR_CTR0SEL_VSYNC (1 << 0) +#define LVDCTRCR_CTR0SEL_DISP (2 << 0) +#define LVDCTRCR_CTR0SEL_ODD (3 << 0) +#define LVDCTRCR_CTR0SEL_CDE (4 << 0) +#define LVDCTRCR_CTR0SEL_MASK (7 << 0) + +#define LVDCHCR 0x0010 +#define LVDCHCR_CHSEL_CH(n, c) ((((c) - (n)) & 3) << ((n) * 4)) +#define LVDCHCR_CHSEL_MASK(n) (3 << ((n) * 4)) + +/* All registers below are specific to D3 and E3 */ +#define LVDSTRIPE 0x0014 +#define LVDSTRIPE_ST_TRGSEL_DISP (0 << 2) +#define LVDSTRIPE_ST_TRGSEL_HSYNC_R (1 << 2) +#define LVDSTRIPE_ST_TRGSEL_HSYNC_F (2 << 2) +#define LVDSTRIPE_ST_SWAP (1 << 1) +#define LVDSTRIPE_ST_ON (1 << 0) + +#define LVDSCR 0x0018 +#define LVDSCR_DEPTH(n) (((n) - 1) << 29) +#define LVDSCR_BANDSET (1 << 28) +#define LVDSCR_TWGCNT(n) ((((n) - 256) / 16) << 24) +#define LVDSCR_SDIV(n) ((n) << 22) +#define LVDSCR_MODE (1 << 21) +#define LVDSCR_RSTN (1 << 20) + +#define LVDDIV 0x001c +#define LVDDIV_DIVSEL (1 << 8) +#define LVDDIV_DIVRESET (1 << 7) +#define LVDDIV_DIVSTP (1 << 6) +#define LVDDIV_DIV(n) ((n) << 0) + +#endif /* __RCAR_LVDS_REGS_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c new file mode 100644 index 000000000..aaafee1c0 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c @@ -0,0 +1,1367 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * R-Car MIPI DSI Encoder + * + * Copyright (C) 2020 Renesas Electronics Corporation + */ + +#include <linux/bitfield.h> +#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_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 long mode_flags; + unsigned int num_data_lanes; + unsigned int lanes; +}; + +struct dsi_setup_info { + unsigned long hsfreq; + u16 hsfreqrange; + + unsigned long fout; + u16 m; + u16 n; + 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_TESTDOUT_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 unsigned int rcar_mipi_dsi_vclk_divider(struct rcar_mipi_dsi *dsi, + struct dsi_setup_info *setup_info) +{ + switch (dsi->info->model) { + case RCAR_DSI_V3U: + default: + return (setup_info->clkset->vco_cntrl >> 4) & 0x3; + + case RCAR_DSI_V4H: + return (setup_info->clkset->vco_cntrl >> 3) & 0x7; + } +} + +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, + u16 vclk_divider) +{ + 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, 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 div; + unsigned int err; + u16 vclk_divider; + + /* + * 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); + + div = rcar_mipi_dsi_vclk_divider(dsi, setup_info); + + switch (dsi->info->model) { + case RCAR_DSI_V3U: + default: + vclk_divider = BIT_U16(div); + break; + + case RCAR_DSI_V4H: + vclk_divider = BIT_U16(div + 1); + break; + } + + rcar_mipi_dsi_pll_calc(dsi, fin_rate, fout_target, setup_info, vclk_divider); + + /* 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, + 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 */ + switch (mipi_dsi_pixel_format_to_bpp(dsi->format)) { + case 24: + rcar_mipi_dsi_write(dsi, TXVMPSPHSETR, TXVMPSPHSETR_DT_RGB24); + break; + case 18: + rcar_mipi_dsi_write(dsi, TXVMPSPHSETR, TXVMPSPHSETR_DT_RGB18); + break; + case 16: + rcar_mipi_dsi_write(dsi, TXVMPSPHSETR, TXVMPSPHSETR_DT_RGB16); + break; + default: + dev_warn(dsi->dev, "unsupported format"); + return; + } + + /* Configuration for Blanking sequence and Input Pixel */ + setr = TXVMSETR_PIXWDTH | TXVMSETR_VSTPM; + + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) + setr |= TXVMSETR_SYNSEQ_EVENTS; + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HFP)) + setr |= TXVMSETR_HFPBPEN; + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HBP)) + setr |= TXVMSETR_HBPBPEN; + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HSA)) + setr |= TXVMSETR_HSABPEN; + } + + rcar_mipi_dsi_write(dsi, TXVMSETR, setr); + + /* Configuration for Video Parameters, input is always RGB888 */ + vprmset0r = TXVMVPRMSET0R_BPP_24; + if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + vprmset0r |= TXVMVPRMSET0R_VSPOL_LOW; + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + vprmset0r |= TXVMVPRMSET0R_HSPOL_LOW; + + 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); + + rcar_mipi_dsi_clr(dsi, TXSETR, TXSETR_LANECNT_MASK); + rcar_mipi_dsi_set(dsi, TXSETR, dsi->lanes - 1); + + ppisetr = ((BIT(dsi->lanes) - 1) & PPISETR_DLEN_MASK) | 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_PHY)) + 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); + + /* Output is always RGB, never YCbCr */ + 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_LANE(dsi->lanes - 1); + + switch (dsi->info->model) { + case RCAR_DSI_V3U: + default: + vclkset |= VCLKSET_DIV_V3U(rcar_mipi_dsi_vclk_divider(dsi, &setup_info)); + break; + + case RCAR_DSI_V4H: + vclkset |= VCLKSET_DIV_V4H(rcar_mipi_dsi_vclk_divider(dsi, &setup_info)); + 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, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct rcar_mipi_dsi *dsi = bridge_to_rcar_mipi_dsi(bridge); + + return drm_bridge_attach(encoder, dsi->next_bridge, bridge, + flags); +} + +static void rcar_mipi_dsi_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *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_atomic_commit *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_commit *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->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; + } + + /* Initialize the DRM bridge. */ + 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 ssize_t rcar_mipi_dsi_host_tx_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg, + bool is_rx_xfer) +{ + const bool is_tx_long = mipi_dsi_packet_format_is_long(msg->type); + struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host); + struct mipi_dsi_packet packet; + u8 payload[16] = { 0 }; + u32 status; + int ret; + + ret = mipi_dsi_create_packet(&packet, msg); + if (ret) + return ret; + + /* Configure LP or HS command transfer. */ + rcar_mipi_dsi_write(dsi, TXCMSETR, (msg->flags & MIPI_DSI_MSG_USE_LPM) ? + TXCMSETR_SPDTYP : 0); + + /* Register access mode for RX transfer. */ + if (is_rx_xfer) + rcar_mipi_dsi_write(dsi, RXPSETR, 0); + + /* Do not use IRQ, poll for completion, the completion is quick. */ + rcar_mipi_dsi_write(dsi, TXCMIER, 0); + + /* + * Send the header: + * header[0] = Virtual Channel + Data Type + * header[1] = Word Count LSB (LP) or first param (SP) + * header[2] = Word Count MSB (LP) or second param (SP) + */ + rcar_mipi_dsi_write(dsi, TXCMPHDR, + (is_tx_long ? TXCMPHDR_FMT : 0) | + TXCMPHDR_VC(msg->channel) | + TXCMPHDR_DT(msg->type) | + TXCMPHDR_DATA1(packet.header[2]) | + TXCMPHDR_DATA0(packet.header[1])); + + if (is_tx_long) { + memcpy(payload, packet.payload, + min(msg->tx_len, sizeof(payload))); + + rcar_mipi_dsi_write(dsi, TXCMPPD0R, + (payload[3] << 24) | (payload[2] << 16) | + (payload[1] << 8) | payload[0]); + rcar_mipi_dsi_write(dsi, TXCMPPD1R, + (payload[7] << 24) | (payload[6] << 16) | + (payload[5] << 8) | payload[4]); + rcar_mipi_dsi_write(dsi, TXCMPPD2R, + (payload[11] << 24) | (payload[10] << 16) | + (payload[9] << 8) | payload[8]); + rcar_mipi_dsi_write(dsi, TXCMPPD3R, + (payload[15] << 24) | (payload[14] << 16) | + (payload[13] << 8) | payload[12]); + } + + /* Start the transfer, RX with BTA, TX without BTA. */ + if (is_rx_xfer) { + rcar_mipi_dsi_write(dsi, TXCMCR, TXCMCR_BTAREQ); + + /* Wait until the transmission, BTA, reception completed. */ + ret = read_poll_timeout(rcar_mipi_dsi_read, status, + (status & RXPSR_BTAREQEND), + 2000, 50000, false, dsi, RXPSR); + } else { + rcar_mipi_dsi_write(dsi, TXCMCR, TXCMCR_TXREQ); + + /* Wait until the transmission completed. */ + ret = read_poll_timeout(rcar_mipi_dsi_read, status, + (status & TXCMSR_TXREQEND), + 2000, 50000, false, dsi, TXCMSR); + } + + if (ret < 0) { + dev_err(dsi->dev, "Command transfer timeout (0x%08x)\n", + status); + return ret; + } + + return packet.size; +} + +static ssize_t rcar_mipi_dsi_host_rx_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host); + u8 *rx_buf = (u8 *)(msg->rx_buf); + u32 reg, data, status, wc; + int i, ret; + + /* RX transfer received data validation and parsing starts here. */ + reg = rcar_mipi_dsi_read(dsi, TOSR); + if (reg & TOSR_TATO) { /* Turn-Around TimeOut. */ + /* Clear TATO Turn-Around TimeOut bit. */ + rcar_mipi_dsi_write(dsi, TOSR, TOSR_TATO); + return -ETIMEDOUT; + } + + reg = rcar_mipi_dsi_read(dsi, RXPSR); + + if (msg->flags & MIPI_DSI_MSG_REQ_ACK) { + /* Transfer with zero-length RX. */ + if (!(reg & RXPSR_RCVACK)) { + /* No ACK on RX response received. */ + return -EINVAL; + } + } else { + /* Transfer with non-zero-length RX. */ + if (!(reg & RXPSR_RCVRESP)) { + /* No packet header of RX response received. */ + return -EINVAL; + } + + if (reg & (RXPSR_CRCERR | RXPSR_WCERR | RXPSR_AXIERR | RXPSR_OVRERR)) { + /* Incorrect response payload. */ + return -ENODATA; + } + + data = rcar_mipi_dsi_read(dsi, RXPHDR); + if (data & RXPHDR_FMT) { /* Long Packet Response. */ + /* Read Long Packet Response length from packet header. */ + wc = data & 0xffff; + if (wc > msg->rx_len) { + dev_warn(dsi->dev, + "Long Packet Response longer than RX buffer (%d), limited to %zu Bytes\n", + wc, msg->rx_len); + wc = msg->rx_len; + } + + if (wc > 16) { + dev_warn(dsi->dev, + "Long Packet Response too long (%d), limited to 16 Bytes\n", + wc); + wc = 16; + } + + for (i = 0; i < msg->rx_len; i++) { + if (!(i % 4)) + data = rcar_mipi_dsi_read(dsi, RXPPD0R + i); + + rx_buf[i] = data & 0xff; + data >>= 8; + } + } else { /* Short Packet Response. */ + if (msg->rx_len >= 1) + rx_buf[0] = data & 0xff; + if (msg->rx_len >= 2) + rx_buf[1] = (data >> 8) & 0xff; + if (msg->rx_len >= 3) { + dev_warn(dsi->dev, + "Expected Short Packet Response too long (%zu), limited to 2 Bytes\n", + msg->rx_len); + } + } + } + + if (reg & RXPSR_RCVAKE) { + /* Acknowledge and Error report received. */ + return -EFAULT; + } + + /* Wait until the bus handover to host processor completed. */ + ret = read_poll_timeout(rcar_mipi_dsi_read, status, + !(status & PPIDL0SR_DIR), + 2000, 50000, false, dsi, PPIDL0SR); + if (ret < 0) { + dev_err(dsi->dev, "Command RX DIR timeout (0x%08x)\n", status); + return ret; + } + + /* Wait until the data lane is in LP11 stop state. */ + ret = read_poll_timeout(rcar_mipi_dsi_read, status, + status & PPIDL0SR_STPST, + 2000, 50000, false, dsi, PPIDL0SR); + if (ret < 0) { + dev_err(dsi->dev, "Command RX STPST timeout (0x%08x)\n", status); + return ret; + } + + return 0; +} + +static ssize_t rcar_mipi_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + const bool is_rx_xfer = (msg->flags & MIPI_DSI_MSG_REQ_ACK) || msg->rx_len; + struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host); + int ret; + + if (msg->tx_len > 16 || msg->rx_len > 16) { + /* ToDo: Implement Memory on AXI bus command mode. */ + dev_warn(dsi->dev, + "Register-based command mode supports only up to 16 Bytes long payload\n"); + return -EOPNOTSUPP; + } + + ret = rcar_mipi_dsi_host_tx_transfer(host, msg, is_rx_xfer); + + /* If TX transfer succeeded and this transfer has RX part. */ + if (ret >= 0 && is_rx_xfer) { + ret = rcar_mipi_dsi_host_rx_transfer(host, msg); + if (ret) + return ret; + + ret = msg->rx_len; + } + + /* + * Wait a bit between commands, otherwise panels based on ILI9881C + * TCON may fail to correctly receive all commands sent to them. + * Until we can actually test with another DSI device, keep the + * delay here, but eventually this delay might have to be moved + * into the ILI9881C panel driver. + */ + usleep_range(1000, 2000); + + /* Clear the completion interrupt. */ + if (!msg->rx_len) + rcar_mipi_dsi_write(dsi, TXCMSR, TXCMSR_TXREQEND); + + return ret; +} + +static const struct mipi_dsi_host_ops rcar_mipi_dsi_host_ops = { + .attach = rcar_mipi_dsi_host_attach, + .detach = rcar_mipi_dsi_host_detach, + .transfer = rcar_mipi_dsi_host_transfer +}; + +/* ----------------------------------------------------------------------------- + * 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; + int ret; + + dsi = devm_drm_bridge_alloc(&pdev->dev, struct rcar_mipi_dsi, bridge, + &rcar_mipi_dsi_bridge_ops); + if (IS_ERR(dsi)) + return PTR_ERR(dsi); + + 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. */ + dsi->mmio = devm_platform_ioremap_resource(pdev, 0); + 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; +} + +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); +} + +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 }, + /* DSI in r8a779h0 is identical to r8a779g0 */ + { .compatible = "renesas,r8a779h0-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 = 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"); diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.h b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.h new file mode 100644 index 000000000..cfb5d70a6 --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * R-Car DSI Encoder + * + * Copyright (C) 2022 Renesas Electronics Corporation + * + * Contact: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> + */ + +#ifndef __RCAR_MIPI_DSI_H__ +#define __RCAR_MIPI_DSI_H__ + +struct drm_atomic_commit; +struct drm_bridge; + +#if IS_ENABLED(CONFIG_DRM_RCAR_MIPI_DSI) +void rcar_mipi_dsi_pclk_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *state); +void rcar_mipi_dsi_pclk_disable(struct drm_bridge *bridge); +#else +static inline void rcar_mipi_dsi_pclk_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ +} + +static inline void rcar_mipi_dsi_pclk_disable(struct drm_bridge *bridge) +{ +} +#endif /* CONFIG_DRM_RCAR_MIPI_DSI */ + +#endif /* __RCAR_MIPI_DSI_H__ */ diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi_regs.h b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi_regs.h new file mode 100644 index 000000000..b6fb58c2f --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi_regs.h @@ -0,0 +1,319 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * R-Car MIPI DSI Interface Registers Definitions + * + * Copyright (C) 2020 Renesas Electronics Corporation + */ + +#ifndef __RCAR_MIPI_DSI_REGS_H__ +#define __RCAR_MIPI_DSI_REGS_H__ + +#define LINKSR 0x010 +#define LINKSR_LPBUSY BIT_U32(1) +#define LINKSR_HSBUSY BIT_U32(0) + +#define TXSETR 0x100 +#define TXSETR_LANECNT_MASK GENMASK_U32(1, 0) + +/* + * DSI Command Transfer Registers + */ +#define TXCMSETR 0x110 +#define TXCMSETR_SPDTYP BIT_U32(8) /* 0:HS 1:LP */ +#define TXCMSETR_LPPDACC BIT_U32(0) +#define TXCMCR 0x120 +#define TXCMCR_BTATYP BIT_U32(2) +#define TXCMCR_BTAREQ BIT_U32(1) +#define TXCMCR_TXREQ BIT_U32(0) +#define TXCMSR 0x130 +#define TXCMSR_CLSNERR BIT_U32(18) +#define TXCMSR_AXIERR BIT_U32(16) +#define TXCMSR_TXREQEND BIT_U32(0) +#define TXCMSCR 0x134 +#define TXCMSCR_CLSNERR BIT_U32(18) +#define TXCMSCR_AXIERR BIT_U32(16) +#define TXCMSCR_TXREQEND BIT_U32(0) +#define TXCMIER 0x138 +#define TXCMIER_CLSNERR BIT_U32(18) +#define TXCMIER_AXIERR BIT_U32(16) +#define TXCMIER_TXREQEND BIT_U32(0) +#define TXCMADDRSET0R 0x140 +#define TXCMPHDR 0x150 +#define TXCMPHDR_FMT BIT_U32(24) /* 0:SP 1:LP */ +#define TXCMPHDR_VC_MASK GENMASK_U32(23, 22) +#define TXCMPHDR_VC(n) FIELD_PREP(TXCMPHDR_VC_MASK, (n)) +#define TXCMPHDR_DT_MASK GENMASK_U32(21, 16) +#define TXCMPHDR_DT(n) FIELD_PREP(TXCMPHDR_DT_MASK, (n)) +#define TXCMPHDR_DATA1_MASK GENMASK_U32(15, 8) +#define TXCMPHDR_DATA1(n) FIELD_PREP(TXCMPHDR_DATA1_MASK, (n)) +#define TXCMPHDR_DATA0_MASK GENMASK_U32(7, 0) +#define TXCMPHDR_DATA0(n) FIELD_PREP(TXCMPHDR_DATA0_MASK, (n)) +#define TXCMPPD0R 0x160 +#define TXCMPPD1R 0x164 +#define TXCMPPD2R 0x168 +#define TXCMPPD3R 0x16c + +#define RXSETR 0x200 +#define RXSETR_CRCEN_MASK GENMASK_U32(27, 24) +#define RXSETR_ECCEN_MASK GENMASK_U32(19, 16) +#define RXPSETR 0x210 +#define RXPSETR_LPPDACC BIT_U32(0) +#define RXPSR 0x220 +#define RXPSR_ECCERR1B BIT_U32(28) +#define RXPSR_UEXTRGERR BIT_U32(25) +#define RXPSR_RESPTOERR BIT_U32(24) +#define RXPSR_OVRERR BIT_U32(23) +#define RXPSR_AXIERR BIT_U32(22) +#define RXPSR_CRCERR BIT_U32(21) +#define RXPSR_WCERR BIT_U32(20) +#define RXPSR_UEXDTERR BIT_U32(19) +#define RXPSR_UEXPKTERR BIT_U32(18) +#define RXPSR_ECCERR BIT_U32(17) +#define RXPSR_MLFERR BIT_U32(16) +#define RXPSR_RCVACK BIT_U32(14) +#define RXPSR_RCVEOT BIT_U32(10) +#define RXPSR_RCVAKE BIT_U32(9) +#define RXPSR_RCVRESP BIT_U32(8) +#define RXPSR_BTAREQEND BIT_U32(0) +#define RXPSCR 0x224 +#define RXPSCR_ECCERR1B BIT_U32(28) +#define RXPSCR_UEXTRGERR BIT_U32(25) +#define RXPSCR_RESPTOERR BIT_U32(24) +#define RXPSCR_OVRERR BIT_U32(23) +#define RXPSCR_AXIERR BIT_U32(22) +#define RXPSCR_CRCERR BIT_U32(21) +#define RXPSCR_WCERR BIT_U32(20) +#define RXPSCR_UEXDTERR BIT_U32(19) +#define RXPSCR_UEXPKTERR BIT_U32(18) +#define RXPSCR_ECCERR BIT_U32(17) +#define RXPSCR_MLFERR BIT_U32(16) +#define RXPSCR_RCVACK BIT_U32(14) +#define RXPSCR_RCVEOT BIT_U32(10) +#define RXPSCR_RCVAKE BIT_U32(9) +#define RXPSCR_RCVRESP BIT_U32(8) +#define RXPSCR_BTAREQEND BIT_U32(0) +#define RXPIER 0x228 +#define RXPIER_ECCERR1B BIT_U32(28) +#define RXPIER_UEXTRGERR BIT_U32(25) +#define RXPIER_RESPTOERR BIT_U32(24) +#define RXPIER_OVRERR BIT_U32(23) +#define RXPIER_AXIERR BIT_U32(22) +#define RXPIER_CRCERR BIT_U32(21) +#define RXPIER_WCERR BIT_U32(20) +#define RXPIER_UEXDTERR BIT_U32(19) +#define RXPIER_UEXPKTERR BIT_U32(18) +#define RXPIER_ECCERR BIT_U32(17) +#define RXPIER_MLFERR BIT_U32(16) +#define RXPIER_RCVACK BIT_U32(14) +#define RXPIER_RCVEOT BIT_U32(10) +#define RXPIER_RCVAKE BIT_U32(9) +#define RXPIER_RCVRESP BIT_U32(8) +#define RXPIER_BTAREQEND BIT_U32(0) +#define RXPADDRSET0R 0x230 +#define RXPSIZESETR 0x238 +#define RXPSIZESETR_SIZE_MASK GENMASK_U32(6, 3) +#define RXPHDR 0x240 +#define RXPHDR_FMT BIT_U32(24) /* 0:SP 1:LP */ +#define RXPHDR_VC_MASK GENMASK_U32(23, 22) +#define RXPHDR_DT_MASK GENMASK_U32(21, 16) +#define RXPHDR_DATA1_MASK GENMASK_U32(15, 8) +#define RXPHDR_DATA0_MASK GENMASK_U32(7, 0) +#define RXPPD0R 0x250 +#define RXPPD1R 0x254 +#define RXPPD2R 0x258 +#define RXPPD3R 0x25c +#define AKEPR 0x300 +#define AKEPR_VC_MASK GENMASK_U32(23, 22) +#define AKEPR_DT_MASK GENMASK_U32(21, 16) +#define AKEPR_ERRRPT_MASK GENMASK_U32(15, 0) +#define RXRESPTOSETR 0x400 +#define TACR 0x500 +#define TASR 0x510 +#define TASCR 0x514 +#define TAIER 0x518 +#define TOSR 0x610 +#define TOSR_TATO BIT_U32(2) +#define TOSR_LRXHTO BIT_U32(1) +#define TOSR_HRXTO BIT_U32(0) +#define TOSCR 0x614 +#define TOSCR_TATO BIT_U32(2) +#define TOSCR_LRXHTO BIT_U32(1) +#define TOSCR_HRXTO BIT_U32(0) + +/* + * Video Mode Register + */ +#define TXVMSETR 0x180 +#define TXVMSETR_SYNSEQ_EVENTS BIT_U32(16) /* 0:Pulses 1:Events */ +#define TXVMSETR_VSTPM BIT_U32(15) +#define TXVMSETR_PIXWDTH_MASK GENMASK_U32(10, 8) +#define TXVMSETR_PIXWDTH BIT_U32(8) /* Only allowed value */ +#define TXVMSETR_VSEN BIT_U32(4) +#define TXVMSETR_HFPBPEN BIT_U32(2) +#define TXVMSETR_HBPBPEN BIT_U32(1) +#define TXVMSETR_HSABPEN BIT_U32(0) + +#define TXVMCR 0x190 +#define TXVMCR_VFCLR BIT_U32(12) +#define TXVMCR_EN_VIDEO BIT_U32(0) + +#define TXVMSR 0x1a0 +#define TXVMSR_STR BIT_U32(16) +#define TXVMSR_VFRDY BIT_U32(12) +#define TXVMSR_ACT BIT_U32(8) +#define TXVMSR_RDY BIT_U32(0) + +#define TXVMSCR 0x1a4 +#define TXVMSCR_STR BIT_U32(16) + +#define TXVMPSPHSETR 0x1c0 +#define TXVMPSPHSETR_DT_MASK (0x3f << 16) +#define TXVMPSPHSETR_DT_RGB16 FIELD_PREP(TXVMPSPHSETR_DT_MASK, 0x0e) +#define TXVMPSPHSETR_DT_RGB18 FIELD_PREP(TXVMPSPHSETR_DT_MASK, 0x1e) +#define TXVMPSPHSETR_DT_RGB18_LS FIELD_PREP(TXVMPSPHSETR_DT_MASK, 0x2e) +#define TXVMPSPHSETR_DT_RGB24 FIELD_PREP(TXVMPSPHSETR_DT_MASK, 0x3e) +#define TXVMPSPHSETR_DT_YCBCR16 FIELD_PREP(TXVMPSPHSETR_DT_MASK, 0x2c) + +#define TXVMVPRMSET0R 0x1d0 +#define TXVMVPRMSET0R_HSPOL_LOW BIT_U32(17) /* 0:High 1:Low */ +#define TXVMVPRMSET0R_VSPOL_LOW BIT_U32(16) /* 0:High 1:Low */ +#define TXVMVPRMSET0R_CSPC_YCbCr BIT_U32(4) /* 0:RGB 1:YCbCr */ +#define TXVMVPRMSET0R_BPP_MASK GENMASK_U32(2, 0) +#define TXVMVPRMSET0R_BPP_16 FIELD_PREP(TXVMVPRMSET0R_BPP_MASK, 0) +#define TXVMVPRMSET0R_BPP_18 FIELD_PREP(TXVMVPRMSET0R_BPP_MASK, 1) +#define TXVMVPRMSET0R_BPP_24 FIELD_PREP(TXVMVPRMSET0R_BPP_MASK, 2) + +#define TXVMVPRMSET1R 0x1d4 +#define TXVMVPRMSET1R_VACTIVE_MASK GENMASK_U32(30, 16) +#define TXVMVPRMSET1R_VACTIVE(n) FIELD_PREP(TXVMVPRMSET1R_VACTIVE_MASK, (n)) +#define TXVMVPRMSET1R_VSA_MASK GENMASK_U32(11, 0) +#define TXVMVPRMSET1R_VSA(n) FIELD_PREP(TXVMVPRMSET1R_VSA_MASK, (n)) + +#define TXVMVPRMSET2R 0x1d8 +#define TXVMVPRMSET2R_VFP_MASK GENMASK_U32(28, 16) +#define TXVMVPRMSET2R_VFP(n) FIELD_PREP(TXVMVPRMSET2R_VFP_MASK, (n)) +#define TXVMVPRMSET2R_VBP_MASK GENMASK_U32(12, 0) +#define TXVMVPRMSET2R_VBP(n) FIELD_PREP(TXVMVPRMSET2R_VBP_MASK, (n)) + +#define TXVMVPRMSET3R 0x1dc +#define TXVMVPRMSET3R_HACTIVE_MASK GENMASK_U32(30, 16) +#define TXVMVPRMSET3R_HACTIVE(n) FIELD_PREP(TXVMVPRMSET3R_HACTIVE_MASK, (n)) +#define TXVMVPRMSET3R_HSA_MASK GENMASK_U32(11, 0) +#define TXVMVPRMSET3R_HSA(n) FIELD_PREP(TXVMVPRMSET3R_HSA_MASK, (n)) + +#define TXVMVPRMSET4R 0x1e0 +#define TXVMVPRMSET4R_HFP_MASK GENMASK_U32(28, 16) +#define TXVMVPRMSET4R_HFP(n) FIELD_PREP(TXVMVPRMSET4R_HFP_MASK, (n)) +#define TXVMVPRMSET4R_HBP_MASK GENMASK_U32(12, 0) +#define TXVMVPRMSET4R_HBP(n) FIELD_PREP(TXVMVPRMSET4R_HBP_MASK, (n)) + +/* + * PHY-Protocol Interface (PPI) Registers + */ +#define PPISETR 0x700 +#define PPISETR_DLEN_MASK GENMASK_U32(3, 0) +#define PPISETR_CLEN BIT_U32(8) + +#define PPICLCR 0x710 +#define PPICLCR_TXREQHS BIT_U32(8) +#define PPICLCR_TXULPSEXT BIT_U32(1) +#define PPICLCR_TXULPSCLK BIT_U32(0) + +#define PPICLSR 0x720 +#define PPICLSR_HSTOLP BIT_U32(27) +#define PPICLSR_TOHS BIT_U32(26) +#define PPICLSR_STPST BIT_U32(0) + +#define PPICLSCR 0x724 +#define PPICLSCR_HSTOLP BIT_U32(27) +#define PPICLSCR_TOHS BIT_U32(26) + +#define PPIDL0SR 0x740 +#define PPIDL0SR_DIR BIT_U32(10) +#define PPIDL0SR_STPST BIT_U32(6) + +#define PPIDLSR 0x760 +#define PPIDLSR_STPST GENMASK_U32(3, 0) + +/* + * Clocks registers + */ +#define LPCLKSET 0x1000 +#define LPCLKSET_CKEN BIT_U32(8) +#define LPCLKSET_LPCLKDIV_MASK GENMASK_U32(5, 0) + +#define CFGCLKSET 0x1004 +#define CFGCLKSET_CKEN BIT_U32(8) +#define CFGCLKSET_CFGCLKDIV_MASK GENMASK_U32(5, 0) + +#define DOTCLKDIV 0x1008 +#define DOTCLKDIV_CKEN BIT_U32(8) +#define DOTCLKDIV_DOTCLKDIV_MASK GENMASK_U32(5, 0) + +#define VCLKSET 0x100c +#define VCLKSET_CKEN BIT_U32(16) +#define VCLKSET_COLOR_YCC BIT_U32(8) /* 0:RGB 1:YCbCr */ +#define VCLKSET_DIV_V3U_MASK GENMASK_U32(5, 4) +#define VCLKSET_DIV_V3U(n) FIELD_PREP(VCLKSET_DIV_V3U_MASK, (n)) +#define VCLKSET_DIV_V4H_MASK GENMASK_U32(6, 4) +#define VCLKSET_DIV_V4H(n) FIELD_PREP(VCLKSET_DIV_V4H_MASK, (n)) +#define VCLKSET_BPP_MASK GENMASK_U32(3, 2) +#define VCLKSET_BPP_16 FIELD_PREP(VCLKSET_BPP_MASK, 0) +#define VCLKSET_BPP_18 FIELD_PREP(VCLKSET_BPP_MASK, 1) +#define VCLKSET_BPP_18L FIELD_PREP(VCLKSET_BPP_MASK, 2) +#define VCLKSET_BPP_24 FIELD_PREP(VCLKSET_BPP_MASK, 3) +#define VCLKSET_LANE_MASK GENMASK_U32(1, 0) +#define VCLKSET_LANE(n) FIELD_PREP(VCLKSET_LANE_MASK, (n)) + +#define VCLKEN 0x1010 +#define VCLKEN_CKEN BIT_U32(0) + +#define PHYSETUP 0x1014 +#define PHYSETUP_HSFREQRANGE_MASK GENMASK_U32(22, 16) +#define PHYSETUP_HSFREQRANGE(n) FIELD_PREP(PHYSETUP_HSFREQRANGE_MASK, (n)) +#define PHYSETUP_CFGCLKFREQRANGE_MASK GENMASK_U32(13, 8) +#define PHYSETUP_SHUTDOWNZ BIT_U32(1) +#define PHYSETUP_RSTZ BIT_U32(0) + +#define CLOCKSET1 0x101c +#define CLOCKSET1_LOCK_PHY BIT_U32(17) +#define CLOCKSET1_CLKSEL BIT_U32(8) +#define CLOCKSET1_CLKINSEL_MASK GENMASK_U32(3, 2) +#define CLOCKSET1_CLKINSEL_EXTAL FIELD_PREP(CLOCKSET1_CLKINSEL_MASK, 0) +#define CLOCKSET1_CLKINSEL_DIG FIELD_PREP(CLOCKSET1_CLKINSEL_MASK, 1) +#define CLOCKSET1_CLKINSEL_DU FIELD_PREP(CLOCKSET1_CLKINSEL_MASK, 2) +#define CLOCKSET1_SHADOW_CLEAR BIT_U32(1) +#define CLOCKSET1_UPDATEPLL BIT_U32(0) + +#define CLOCKSET2 0x1020 +#define CLOCKSET2_M_MASK GENMASK_U32(27, 16) +#define CLOCKSET2_M(n) FIELD_PREP(CLOCKSET2_M_MASK, (n)) +#define CLOCKSET2_VCO_CNTRL_MASK GENMASK_U32(13, 8) +#define CLOCKSET2_VCO_CNTRL(n) FIELD_PREP(CLOCKSET2_VCO_CNTRL_MASK, (n)) +#define CLOCKSET2_N_MASK GENMASK_U32(3, 0) +#define CLOCKSET2_N(n) FIELD_PREP(CLOCKSET2_N_MASK, (n)) + +#define CLOCKSET3 0x1024 +#define CLOCKSET3_PROP_CNTRL_MASK GENMASK_U32(29, 24) +#define CLOCKSET3_PROP_CNTRL(n) FIELD_PREP(CLOCKSET3_PROP_CNTRL_MASK, (n)) +#define CLOCKSET3_INT_CNTRL_MASK GENMASK_U32(21, 16) +#define CLOCKSET3_INT_CNTRL(n) FIELD_PREP(CLOCKSET3_INT_CNTRL_MASK, (n)) +#define CLOCKSET3_CPBIAS_CNTRL_MASK GENMASK_U32(14, 8) +#define CLOCKSET3_CPBIAS_CNTRL(n) FIELD_PREP(CLOCKSET3_CPBIAS_CNTRL_MASK, (n)) +#define CLOCKSET3_GMP_CNTRL_MASK GENMASK_U32(1, 0) +#define CLOCKSET3_GMP_CNTRL(n) FIELD_PREP(CLOCKSET3_GMP_CNTRL_MASK, (n)) + +#define PHTW 0x1034 +#define PHTW_DWEN BIT_U32(24) +#define PHTW_TESTDIN_DATA_MASK GENMASK_U32(23, 16) +#define PHTW_CWEN BIT_U32(8) +#define PHTW_TESTDIN_CODE_MASK GENMASK_U32(7, 0) + +#define PHTR 0x1038 +#define PHTR_TESTDOUT GENMASK_U32(23, 16) +#define PHTR_TESTDOUT_TEST BIT_U32(16) + +#define PHTC 0x103c +#define PHTC_TESTCLR BIT_U32(0) + +#endif /* __RCAR_MIPI_DSI_REGS_H__ */ diff --git a/drivers/gpu/drm/renesas/rz-du/Kconfig b/drivers/gpu/drm/renesas/rz-du/Kconfig new file mode 100644 index 000000000..7f2ef7137 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/Kconfig @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +config DRM_RZG2L_DU + tristate "DRM Support for RZ/G2L Display Unit" + depends on ARCH_RENESAS || COMPILE_TEST + depends on DRM && OF + depends on VIDEO_RENESAS_VSP1 + select DRM_CLIENT_SELECTION + select DRM_GEM_DMA_HELPER + select DRM_KMS_HELPER + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + select VIDEOMODE_HELPERS + help + Choose this option if you have an RZ/G2L alike chipset. + If M is selected the module will be called rzg2l-du-drm. + +config DRM_RZG2L_USE_MIPI_DSI + bool "RZ/G2L MIPI DSI Encoder Support" + depends on DRM_BRIDGE && OF + depends on DRM_RZG2L_DU || COMPILE_TEST + default DRM_RZG2L_DU + help + Enable support for the RZ/G2L Display Unit embedded MIPI DSI encoders. + +config DRM_RZG2L_MIPI_DSI + def_tristate DRM_RZG2L_DU + depends on DRM_RZG2L_USE_MIPI_DSI + select DRM_MIPI_DSI diff --git a/drivers/gpu/drm/renesas/rz-du/Makefile b/drivers/gpu/drm/renesas/rz-du/Makefile new file mode 100644 index 000000000..2987900ea --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +rzg2l-du-drm-y := rzg2l_du_crtc.o \ + rzg2l_du_drv.o \ + rzg2l_du_encoder.o \ + rzg2l_du_kms.o \ + +rzg2l-du-drm-$(CONFIG_VIDEO_RENESAS_VSP1) += rzg2l_du_vsp.o +obj-$(CONFIG_DRM_RZG2L_DU) += rzg2l-du-drm.o + +obj-$(CONFIG_DRM_RZG2L_MIPI_DSI) += rzg2l_mipi_dsi.o diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.c new file mode 100644 index 000000000..d0f01aa64 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * RZ/G2L Display Unit CRTCs + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_crtc.c + */ + +#include <linux/clk.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_vblank.h> + +#include "rzg2l_du_crtc.h" +#include "rzg2l_du_drv.h" +#include "rzg2l_du_encoder.h" +#include "rzg2l_du_kms.h" +#include "rzg2l_du_vsp.h" + +#define DU_MCR0 0x00 +#define DU_MCR0_DPI_EN BIT(0) +#define DU_MCR0_DI_EN BIT(8) + +#define DU_DITR0 0x10 +#define DU_DITR0_DEMD_HIGH (BIT(8) | BIT(9)) +#define DU_DITR0_VSPOL BIT(16) +#define DU_DITR0_HSPOL BIT(17) + +#define DU_DITR1 0x14 +#define DU_DITR1_VSA(x) ((x) << 0) +#define DU_DITR1_VACTIVE(x) ((x) << 16) + +#define DU_DITR2 0x18 +#define DU_DITR2_VBP(x) ((x) << 0) +#define DU_DITR2_VFP(x) ((x) << 16) + +#define DU_DITR3 0x1c +#define DU_DITR3_HSA(x) ((x) << 0) +#define DU_DITR3_HACTIVE(x) ((x) << 16) + +#define DU_DITR4 0x20 +#define DU_DITR4_HBP(x) ((x) << 0) +#define DU_DITR4_HFP(x) ((x) << 16) + +#define DU_MCR1 0x40 +#define DU_MCR1_PB_AUTOCLR BIT(16) + +#define DU_PBCR0 0x4c +#define DU_PBCR0_PB_DEP(x) ((x) << 0) + +/* ----------------------------------------------------------------------------- + * Hardware Setup + */ + +static void rzg2l_du_crtc_set_display_timing(struct rzg2l_du_crtc *rcrtc) +{ + const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode; + unsigned long mode_clock = mode->clock * 1000; + u32 ditr0, ditr1, ditr2, ditr3, ditr4, pbcr0; + struct rzg2l_du_device *rcdu = rcrtc->dev; + + clk_prepare_enable(rcrtc->rzg2l_clocks.dclk); + clk_set_rate(rcrtc->rzg2l_clocks.dclk, mode_clock); + + ditr0 = (DU_DITR0_DEMD_HIGH + | ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? DU_DITR0_VSPOL : 0) + | ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? DU_DITR0_HSPOL : 0)); + + ditr1 = DU_DITR1_VSA(mode->vsync_end - mode->vsync_start) + | DU_DITR1_VACTIVE(mode->vdisplay); + + ditr2 = DU_DITR2_VBP(mode->vtotal - mode->vsync_end) + | DU_DITR2_VFP(mode->vsync_start - mode->vdisplay); + + ditr3 = DU_DITR3_HSA(mode->hsync_end - mode->hsync_start) + | DU_DITR3_HACTIVE(mode->hdisplay); + + ditr4 = DU_DITR4_HBP(mode->htotal - mode->hsync_end) + | DU_DITR4_HFP(mode->hsync_start - mode->hdisplay); + + pbcr0 = DU_PBCR0_PB_DEP(0x1f); + + writel(ditr0, rcdu->mmio + DU_DITR0); + writel(ditr1, rcdu->mmio + DU_DITR1); + writel(ditr2, rcdu->mmio + DU_DITR2); + writel(ditr3, rcdu->mmio + DU_DITR3); + writel(ditr4, rcdu->mmio + DU_DITR4); + writel(pbcr0, rcdu->mmio + DU_PBCR0); + + /* Enable auto clear */ + writel(DU_MCR1_PB_AUTOCLR, rcdu->mmio + DU_MCR1); +} + +/* ----------------------------------------------------------------------------- + * Page Flip + */ + +void rzg2l_du_crtc_finish_page_flip(struct rzg2l_du_crtc *rcrtc) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = rcrtc->event; + rcrtc->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + if (!event) + return; + + spin_lock_irqsave(&dev->event_lock, flags); + drm_crtc_send_vblank_event(&rcrtc->crtc, event); + wake_up(&rcrtc->flip_wait); + spin_unlock_irqrestore(&dev->event_lock, flags); + + drm_crtc_vblank_put(&rcrtc->crtc); +} + +static bool rzg2l_du_crtc_page_flip_pending(struct rzg2l_du_crtc *rcrtc) +{ + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + bool pending; + + spin_lock_irqsave(&dev->event_lock, flags); + pending = rcrtc->event; + spin_unlock_irqrestore(&dev->event_lock, flags); + + return pending; +} + +static void rzg2l_du_crtc_wait_page_flip(struct rzg2l_du_crtc *rcrtc) +{ + struct rzg2l_du_device *rcdu = rcrtc->dev; + + if (wait_event_timeout(rcrtc->flip_wait, + !rzg2l_du_crtc_page_flip_pending(rcrtc), + msecs_to_jiffies(50))) + return; + + dev_warn(rcdu->dev, "page flip timeout\n"); + + rzg2l_du_crtc_finish_page_flip(rcrtc); +} + +/* ----------------------------------------------------------------------------- + * Start/Stop and Suspend/Resume + */ + +static void rzg2l_du_crtc_setup(struct rzg2l_du_crtc *rcrtc) +{ + /* Configure display timings and output routing */ + rzg2l_du_crtc_set_display_timing(rcrtc); + + /* Enable the VSP compositor. */ + rzg2l_du_vsp_enable(rcrtc); + + /* Turn vertical blanking interrupt reporting on. */ + drm_crtc_vblank_on(&rcrtc->crtc); +} + +static int rzg2l_du_crtc_get(struct rzg2l_du_crtc *rcrtc) +{ + int ret; + + /* + * Guard against double-get, as the function is called from both the + * .atomic_enable() and .atomic_flush() handlers. + */ + if (rcrtc->initialized) + return 0; + + ret = clk_prepare_enable(rcrtc->rzg2l_clocks.aclk); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(rcrtc->rzg2l_clocks.pclk); + if (ret < 0) + goto error_bus_clock; + + ret = reset_control_deassert(rcrtc->rstc); + if (ret < 0) + goto error_peri_clock; + + rzg2l_du_crtc_setup(rcrtc); + rcrtc->initialized = true; + + return 0; + +error_peri_clock: + clk_disable_unprepare(rcrtc->rzg2l_clocks.pclk); +error_bus_clock: + clk_disable_unprepare(rcrtc->rzg2l_clocks.aclk); + return ret; +} + +static void rzg2l_du_crtc_put(struct rzg2l_du_crtc *rcrtc) +{ + clk_disable_unprepare(rcrtc->rzg2l_clocks.dclk); + reset_control_assert(rcrtc->rstc); + clk_disable_unprepare(rcrtc->rzg2l_clocks.pclk); + clk_disable_unprepare(rcrtc->rzg2l_clocks.aclk); + + rcrtc->initialized = false; +} + +static void rzg2l_du_start_stop(struct rzg2l_du_crtc *rcrtc, bool start) +{ + struct rzg2l_du_device *rcdu = rcrtc->dev; + u32 val = DU_MCR0_DI_EN; + + if (start && rzg2l_du_has(rcdu, RZG2L_DU_FEATURE_DPIO_OE)) + val |= DU_MCR0_DPI_EN; + + writel(start ? val : 0, rcdu->mmio + DU_MCR0); +} + +static void rzg2l_du_crtc_start(struct rzg2l_du_crtc *rcrtc) +{ + rzg2l_du_start_stop(rcrtc, true); +} + +static void rzg2l_du_crtc_stop(struct rzg2l_du_crtc *rcrtc) +{ + struct drm_crtc *crtc = &rcrtc->crtc; + + /* + * Disable vertical blanking interrupt reporting. We first need to wait + * for page flip completion before stopping the CRTC as userspace + * expects page flips to eventually complete. + */ + rzg2l_du_crtc_wait_page_flip(rcrtc); + drm_crtc_vblank_off(crtc); + + /* Disable the VSP compositor. */ + rzg2l_du_vsp_disable(rcrtc); + + rzg2l_du_start_stop(rcrtc, false); +} + +/* ----------------------------------------------------------------------------- + * CRTC Functions + */ + +static void rzg2l_du_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc); + + rzg2l_du_crtc_get(rcrtc); + + rzg2l_du_crtc_start(rcrtc); +} + +static void rzg2l_du_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc); + + rzg2l_du_crtc_stop(rcrtc); + rzg2l_du_crtc_put(rcrtc); + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +static void rzg2l_du_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc); + struct drm_device *dev = rcrtc->crtc.dev; + unsigned long flags; + + WARN_ON(!crtc->state->enable); + + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&dev->event_lock, flags); + rcrtc->event = crtc->state->event; + crtc->state->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + } + + rzg2l_du_vsp_atomic_flush(rcrtc); +} + +static const struct drm_crtc_helper_funcs crtc_helper_funcs = { + .atomic_flush = rzg2l_du_crtc_atomic_flush, + .atomic_enable = rzg2l_du_crtc_atomic_enable, + .atomic_disable = rzg2l_du_crtc_atomic_disable, +}; + +static struct drm_crtc_state * +rzg2l_du_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct rzg2l_du_crtc_state *state; + struct rzg2l_du_crtc_state *copy; + + if (WARN_ON(!crtc->state)) + return NULL; + + state = to_rzg2l_crtc_state(crtc->state); + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (!copy) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, ©->state); + + return ©->state; +} + +static void rzg2l_du_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + __drm_atomic_helper_crtc_destroy_state(state); + kfree(to_rzg2l_crtc_state(state)); +} + +static void rzg2l_du_crtc_reset(struct drm_crtc *crtc) +{ + struct rzg2l_du_crtc_state *state; + + if (crtc->state) { + rzg2l_du_crtc_atomic_destroy_state(crtc, crtc->state); + crtc->state = NULL; + } + + state = kzalloc_obj(*state); + if (!state) + return; + + __drm_atomic_helper_crtc_reset(crtc, &state->state); +} + +static int rzg2l_du_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc); + + rcrtc->vblank_enable = true; + + return 0; +} + +static void rzg2l_du_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct rzg2l_du_crtc *rcrtc = to_rzg2l_crtc(crtc); + + rcrtc->vblank_enable = false; +} + +static const struct drm_crtc_funcs crtc_funcs_rz = { + .reset = rzg2l_du_crtc_reset, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = rzg2l_du_crtc_atomic_duplicate_state, + .atomic_destroy_state = rzg2l_du_crtc_atomic_destroy_state, + .enable_vblank = rzg2l_du_crtc_enable_vblank, + .disable_vblank = rzg2l_du_crtc_disable_vblank, +}; + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +int rzg2l_du_crtc_create(struct rzg2l_du_device *rcdu) +{ + struct rzg2l_du_crtc *rcrtc = &rcdu->crtcs[0]; + struct drm_crtc *crtc = &rcrtc->crtc; + struct drm_plane *primary; + int ret; + + rcrtc->rstc = devm_reset_control_get_optional_shared(rcdu->dev, NULL); + if (IS_ERR(rcrtc->rstc)) { + dev_err(rcdu->dev, "can't get cpg reset\n"); + return PTR_ERR(rcrtc->rstc); + } + + rcrtc->rzg2l_clocks.aclk = devm_clk_get(rcdu->dev, "aclk"); + if (IS_ERR(rcrtc->rzg2l_clocks.aclk)) { + dev_err(rcdu->dev, "no axi clock for DU\n"); + return PTR_ERR(rcrtc->rzg2l_clocks.aclk); + } + + rcrtc->rzg2l_clocks.pclk = devm_clk_get(rcdu->dev, "pclk"); + if (IS_ERR(rcrtc->rzg2l_clocks.pclk)) { + dev_err(rcdu->dev, "no peripheral clock for DU\n"); + return PTR_ERR(rcrtc->rzg2l_clocks.pclk); + } + + rcrtc->rzg2l_clocks.dclk = devm_clk_get(rcdu->dev, "vclk"); + if (IS_ERR(rcrtc->rzg2l_clocks.dclk)) { + dev_err(rcdu->dev, "no video clock for DU\n"); + return PTR_ERR(rcrtc->rzg2l_clocks.dclk); + } + + init_waitqueue_head(&rcrtc->flip_wait); + rcrtc->dev = rcdu; + + primary = rzg2l_du_vsp_get_drm_plane(rcrtc, rcrtc->vsp_pipe); + if (IS_ERR(primary)) + return PTR_ERR(primary); + + ret = drmm_crtc_init_with_planes(&rcdu->ddev, crtc, primary, NULL, + &crtc_funcs_rz, NULL); + if (ret < 0) + return ret; + + drm_crtc_helper_add(crtc, &crtc_helper_funcs); + + return 0; +} diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.h b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.h new file mode 100644 index 000000000..cbba38acc --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_crtc.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * RZ/G2L Display Unit CRTCs + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_crtc.h + */ + +#ifndef __RZG2L_DU_CRTC_H__ +#define __RZG2L_DU_CRTC_H__ + +#include <linux/container_of.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/wait.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_writeback.h> + +#include <media/vsp1.h> + +struct clk; +struct reset_control; +struct rzg2l_du_vsp; +struct rzg2l_du_format_info; + +/** + * struct rzg2l_du_crtc - the CRTC, representing a DU superposition processor + * @crtc: base DRM CRTC + * @dev: the DU device + * @initialized: whether the CRTC has been initialized and clocks enabled + * @vblank_enable: whether vblank events are enabled on this CRTC + * @event: event to post when the pending page flip completes + * @flip_wait: wait queue used to signal page flip completion + * @vsp: VSP feeding video to this CRTC + * @vsp_pipe: index of the VSP pipeline feeding video to this CRTC + * @rstc: reset controller + * @rzg2l_clocks: the bus, main and video clock + */ +struct rzg2l_du_crtc { + struct drm_crtc crtc; + + struct rzg2l_du_device *dev; + bool initialized; + + bool vblank_enable; + struct drm_pending_vblank_event *event; + wait_queue_head_t flip_wait; + + struct rzg2l_du_vsp *vsp; + unsigned int vsp_pipe; + + const char *const *sources; + unsigned int sources_count; + + struct reset_control *rstc; + struct { + struct clk *aclk; + struct clk *pclk; + struct clk *dclk; + } rzg2l_clocks; +}; + +static inline struct rzg2l_du_crtc *to_rzg2l_crtc(struct drm_crtc *c) +{ + return container_of(c, struct rzg2l_du_crtc, crtc); +} + +/** + * struct rzg2l_du_crtc_state - Driver-specific CRTC state + * @state: base DRM CRTC state + * @outputs: bitmask of the outputs (enum rzg2l_du_output) driven by this CRTC + */ +struct rzg2l_du_crtc_state { + struct drm_crtc_state state; + unsigned int outputs; +}; + +static inline struct rzg2l_du_crtc_state *to_rzg2l_crtc_state(struct drm_crtc_state *s) +{ + return container_of(s, struct rzg2l_du_crtc_state, state); +} + +int rzg2l_du_crtc_create(struct rzg2l_du_device *rcdu); + +void rzg2l_du_crtc_finish_page_flip(struct rzg2l_du_crtc *rcrtc); + +#endif /* __RZG2L_DU_CRTC_H__ */ diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_drv.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_drv.c new file mode 100644 index 000000000..3d13f61d3 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_drv.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * RZ/G2L Display Unit DRM driver + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_drv.c + */ + +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_dma.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "rzg2l_du_drv.h" +#include "rzg2l_du_kms.h" + +/* ----------------------------------------------------------------------------- + * Device Information + */ + +static const struct rzg2l_du_device_info rzg2l_du_r9a07g043u_info = { + .channels_mask = BIT(0), + .routes = { + [RZG2L_DU_OUTPUT_DPAD0] = { + .possible_outputs = BIT(0), + .port = 0, + }, + }, + .mode_clock_min = 20875, + .mode_clock_max = 83500, +}; + +static const struct rzg2l_du_device_info rzg2l_du_r9a07g044_info = { + .channels_mask = BIT(0), + .routes = { + [RZG2L_DU_OUTPUT_DSI0] = { + .possible_outputs = BIT(0), + .port = 0, + }, + [RZG2L_DU_OUTPUT_DPAD0] = { + .possible_outputs = BIT(0), + .port = 1, + } + }, + .mode_clock_min = 20875, + .mode_clock_max = 83500, +}; + +static const struct rzg2l_du_device_info rzg2l_du_r9a09g057_info = { + .channels_mask = BIT(0), + .routes = { + [RZG2L_DU_OUTPUT_DSI0] = { + .possible_outputs = BIT(0), + .port = 0, + }, + }, +}; + +static const struct rzg2l_du_device_info rzg2l_du_r9a09g077_info = { + .channels_mask = BIT(0), + .routes = { + [RZG2L_DU_OUTPUT_DPAD0] = { + .possible_outputs = BIT(0), + .port = 0, + }, + }, + .features = RZG2L_DU_FEATURE_DPIO_OE, + .mode_clock_min = 5000, + .mode_clock_max = 100000, +}; + +static const struct of_device_id rzg2l_du_of_table[] = { + { .compatible = "renesas,r9a07g043u-du", .data = &rzg2l_du_r9a07g043u_info }, + { .compatible = "renesas,r9a07g044-du", .data = &rzg2l_du_r9a07g044_info }, + { .compatible = "renesas,r9a09g057-du", .data = &rzg2l_du_r9a09g057_info }, + { .compatible = "renesas,r9a09g077-du", .data = &rzg2l_du_r9a09g077_info }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, rzg2l_du_of_table); + +const char *rzg2l_du_output_name(enum rzg2l_du_output output) +{ + static const char * const names[] = { + [RZG2L_DU_OUTPUT_DSI0] = "DSI0", + [RZG2L_DU_OUTPUT_DPAD0] = "DPAD0" + }; + + if (output >= ARRAY_SIZE(names)) + return "UNKNOWN"; + + return names[output]; +} + +/* ----------------------------------------------------------------------------- + * DRM operations + */ + +DEFINE_DRM_GEM_DMA_FOPS(rzg2l_du_fops); + +static const struct drm_driver rzg2l_du_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(rzg2l_du_dumb_create), + DRM_FBDEV_DMA_DRIVER_OPS, + .fops = &rzg2l_du_fops, + .name = "rzg2l-du", + .desc = "Renesas RZ/G2L Display Unit", + .major = 1, + .minor = 0, +}; + +/* ----------------------------------------------------------------------------- + * Platform driver + */ + +static void rzg2l_du_remove(struct platform_device *pdev) +{ + struct rzg2l_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); +} + +static void rzg2l_du_shutdown(struct platform_device *pdev) +{ + struct rzg2l_du_device *rcdu = platform_get_drvdata(pdev); + + drm_atomic_helper_shutdown(&rcdu->ddev); +} + +static int rzg2l_du_probe(struct platform_device *pdev) +{ + struct rzg2l_du_device *rcdu; + int ret; + + if (drm_firmware_drivers_only()) + return -ENODEV; + + /* Allocate and initialize the RZ/G2L device structure. */ + rcdu = devm_drm_dev_alloc(&pdev->dev, &rzg2l_du_driver, + struct rzg2l_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); + + ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + /* DRM/KMS objects */ + ret = rzg2l_du_modeset_init(rcdu); + if (ret < 0) { + /* + * Don't use dev_err_probe(), as it would overwrite the probe + * deferral reason recorded in rzg2l_du_modeset_init(). + */ + 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(&rcdu->ddev, "Device %s probed\n", dev_name(&pdev->dev)); + + drm_client_setup(&rcdu->ddev, NULL); + + return 0; + +error: + drm_kms_helper_poll_fini(&rcdu->ddev); + return ret; +} + +static struct platform_driver rzg2l_du_platform_driver = { + .probe = rzg2l_du_probe, + .remove = rzg2l_du_remove, + .shutdown = rzg2l_du_shutdown, + .driver = { + .name = "rzg2l-du", + .of_match_table = rzg2l_du_of_table, + }, +}; + +module_platform_driver(rzg2l_du_platform_driver); + +MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>"); +MODULE_DESCRIPTION("Renesas RZ/G2L Display Unit DRM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_drv.h b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_drv.h new file mode 100644 index 000000000..baf076d69 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_drv.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * RZ/G2L Display Unit DRM driver + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_drv.h + */ + +#ifndef __RZG2L_DU_DRV_H__ +#define __RZG2L_DU_DRV_H__ + +#include <linux/kernel.h> + +#include <drm/drm_device.h> + +#include "rzg2l_du_crtc.h" +#include "rzg2l_du_vsp.h" + +struct device; +struct drm_property; + +#define RZG2L_DU_FEATURE_DPIO_OE BIT(0) /* Has DPIO output enable control */ + +enum rzg2l_du_output { + RZG2L_DU_OUTPUT_DSI0, + RZG2L_DU_OUTPUT_DPAD0, + RZG2L_DU_OUTPUT_MAX, +}; + +/* + * struct rzg2l_du_output_routing - Output routing specification + * @possible_outputs: bitmask of possible outputs + * @port: device tree port number corresponding to this output route + * + * The DU has 2 possible outputs (DPAD0, DSI0). Output routing data + * specify the valid SoC outputs, which CRTC can drive the output, and the type + * of in-SoC encoder for the output. + */ +struct rzg2l_du_output_routing { + unsigned int possible_outputs; + unsigned int port; +}; + +/* + * struct rzg2l_du_device_info - DU model-specific information + * @channels_mask: bit mask of available DU channels + * @routes: array of CRTC to output routes, indexed by output (RZG2L_DU_OUTPUT_*) + * @mode_clock_min: minimum pixel clock in kHz + * @mode_clock_max: maximum pixel clock in kHz + * @features: device features (RZG2L_DU_FEATURE_*) + */ +struct rzg2l_du_device_info { + unsigned int channels_mask; + struct rzg2l_du_output_routing routes[RZG2L_DU_OUTPUT_MAX]; + u32 mode_clock_min; + u32 mode_clock_max; + unsigned int features; +}; + +#define RZG2L_DU_MAX_CRTCS 1 +#define RZG2L_DU_MAX_VSPS 1 +#define RZG2L_DU_MAX_DSI 1 + +struct rzg2l_du_device { + struct device *dev; + const struct rzg2l_du_device_info *info; + + void __iomem *mmio; + + struct drm_device ddev; + + struct rzg2l_du_crtc crtcs[RZG2L_DU_MAX_CRTCS]; + unsigned int num_crtcs; + + struct rzg2l_du_vsp vsps[RZG2L_DU_MAX_VSPS]; +}; + +static inline struct rzg2l_du_device *to_rzg2l_du_device(struct drm_device *dev) +{ + return container_of(dev, struct rzg2l_du_device, ddev); +} + +static inline bool rzg2l_du_has(struct rzg2l_du_device *rcdu, + unsigned int feature) +{ + return rcdu->info->features & feature; +} + +const char *rzg2l_du_output_name(enum rzg2l_du_output output); + +#endif /* __RZG2L_DU_DRV_H__ */ diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c new file mode 100644 index 000000000..f50d166b7 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * RZ/G2L Display Unit Encoder + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_encoder.c + */ + +#include <linux/export.h> +#include <linux/of.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_panel.h> + +#include "rzg2l_du_drv.h" +#include "rzg2l_du_encoder.h" + +/* ----------------------------------------------------------------------------- + * Encoder + */ + +static unsigned int rzg2l_du_encoder_count_ports(struct device_node *node) +{ + struct device_node *ports; + struct device_node *port; + unsigned int num_ports = 0; + + ports = of_get_child_by_name(node, "ports"); + if (!ports) + ports = of_node_get(node); + + for_each_child_of_node(ports, port) { + if (of_node_name_eq(port, "port")) + num_ports++; + } + + of_node_put(ports); + + return num_ports; +} + +static const struct drm_encoder_funcs rzg2l_du_encoder_funcs = { +}; + +static enum drm_mode_status +rzg2l_du_encoder_mode_valid(struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + struct rzg2l_du_encoder *renc = to_rzg2l_encoder(encoder); + struct rzg2l_du_device *rcdu = to_rzg2l_du_device(renc->base.dev); + const struct rzg2l_du_device_info *info = rcdu->info; + + if (renc->output != RZG2L_DU_OUTPUT_DPAD0) + return MODE_OK; + + if (mode->clock < info->mode_clock_min) + return MODE_CLOCK_LOW; + if (mode->clock > info->mode_clock_max) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_encoder_helper_funcs rzg2l_du_encoder_helper_funcs = { + .mode_valid = rzg2l_du_encoder_mode_valid, +}; + +int rzg2l_du_encoder_init(struct rzg2l_du_device *rcdu, + enum rzg2l_du_output output, + struct device_node *enc_node) +{ + struct rzg2l_du_encoder *renc; + struct drm_connector *connector; + struct drm_bridge *bridge __free(drm_bridge_put) = NULL; + int ret; + + /* + * Locate the DRM bridge from the DT node. For the DPAD outputs, if the + * DT node has a single port, assume that it describes a panel and + * create a panel bridge. + */ + if (output == RZG2L_DU_OUTPUT_DPAD0 && rzg2l_du_encoder_count_ports(enc_node) == 1) { + struct drm_panel *panel = of_drm_find_panel(enc_node); + + if (IS_ERR(panel)) + return PTR_ERR(panel); + + bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(bridge)) + return PTR_ERR(no_free_ptr(bridge)); + + /* + * The reference taken by devm_drm_panel_bridge_add_typed() is + * released automatically. Take a second one for the __free() + * when this function will return. + */ + drm_bridge_get(bridge); + } else { + bridge = of_drm_find_and_get_bridge(enc_node); + if (!bridge) + return -EPROBE_DEFER; + } + + dev_dbg(rcdu->dev, "initializing encoder %pOF for output %s\n", + enc_node, rzg2l_du_output_name(output)); + + renc = drmm_encoder_alloc(&rcdu->ddev, struct rzg2l_du_encoder, base, + &rzg2l_du_encoder_funcs, DRM_MODE_ENCODER_NONE, + NULL); + if (IS_ERR(renc)) + return PTR_ERR(renc); + + renc->output = output; + drm_encoder_helper_add(&renc->base, &rzg2l_du_encoder_helper_funcs); + + /* Attach the bridge to the encoder. */ + ret = drm_bridge_attach(&renc->base, bridge, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + dev_err(rcdu->dev, + "failed to attach bridge %pOF for output %s (%d)\n", + bridge->of_node, rzg2l_du_output_name(output), ret); + return ret; + } + + /* Create the connector for the chain of bridges. */ + connector = drm_bridge_connector_init(&rcdu->ddev, &renc->base); + if (IS_ERR(connector)) { + dev_err(rcdu->dev, + "failed to created connector for output %s (%ld)\n", + rzg2l_du_output_name(output), PTR_ERR(connector)); + return PTR_ERR(connector); + } + + return 0; +} diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.h b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.h new file mode 100644 index 000000000..3e430c1f6 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * RZ/G2L Display Unit Encoder + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_encoder.h + */ + +#ifndef __RZG2L_DU_ENCODER_H__ +#define __RZG2L_DU_ENCODER_H__ + +#include <drm/drm_encoder.h> +#include <linux/container_of.h> + +struct rzg2l_du_device; + +struct rzg2l_du_encoder { + struct drm_encoder base; + enum rzg2l_du_output output; +}; + +static inline struct rzg2l_du_encoder *to_rzg2l_encoder(struct drm_encoder *e) +{ + return container_of(e, struct rzg2l_du_encoder, base); +} + +int rzg2l_du_encoder_init(struct rzg2l_du_device *rcdu, + enum rzg2l_du_output output, + struct device_node *enc_node); + +#endif /* __RZG2L_DU_ENCODER_H__ */ diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_kms.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_kms.c new file mode 100644 index 000000000..87f171145 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_kms.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * RZ/G2L Display Unit Mode Setting + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_kms.c + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include <linux/device.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +#include "rzg2l_du_crtc.h" +#include "rzg2l_du_drv.h" +#include "rzg2l_du_encoder.h" +#include "rzg2l_du_kms.h" +#include "rzg2l_du_vsp.h" + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +static const struct rzg2l_du_format_info rzg2l_du_format_infos[] = { + { + .fourcc = DRM_FORMAT_RGB332, + .v4l2 = V4L2_PIX_FMT_RGB332, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_ARGB4444, + .v4l2 = V4L2_PIX_FMT_ARGB444, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_XRGB4444, + .v4l2 = V4L2_PIX_FMT_XRGB444, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_ARGB1555, + .v4l2 = V4L2_PIX_FMT_ARGB555, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_XRGB1555, + .v4l2 = V4L2_PIX_FMT_XRGB555, + .planes = 1, + }, { + .fourcc = DRM_FORMAT_RGB565, + .v4l2 = V4L2_PIX_FMT_RGB565, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_BGR888, + .v4l2 = V4L2_PIX_FMT_RGB24, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_RGB888, + .v4l2 = V4L2_PIX_FMT_BGR24, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_BGRA8888, + .v4l2 = V4L2_PIX_FMT_ARGB32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_BGRX8888, + .v4l2 = V4L2_PIX_FMT_XRGB32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_ARGB8888, + .v4l2 = V4L2_PIX_FMT_ABGR32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_XRGB8888, + .v4l2 = V4L2_PIX_FMT_XBGR32, + .planes = 1, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_UYVY, + .v4l2 = V4L2_PIX_FMT_UYVY, + .planes = 1, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YUYV, + .v4l2 = V4L2_PIX_FMT_YUYV, + .planes = 1, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YVYU, + .v4l2 = V4L2_PIX_FMT_YVYU, + .planes = 1, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_NV12, + .v4l2 = V4L2_PIX_FMT_NV12M, + .planes = 2, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_NV21, + .v4l2 = V4L2_PIX_FMT_NV21M, + .planes = 2, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_NV16, + .v4l2 = V4L2_PIX_FMT_NV16M, + .planes = 2, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_NV61, + .v4l2 = V4L2_PIX_FMT_NV61M, + .planes = 2, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YUV420, + .v4l2 = V4L2_PIX_FMT_YUV420M, + .planes = 3, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YVU420, + .v4l2 = V4L2_PIX_FMT_YVU420M, + .planes = 3, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YUV422, + .v4l2 = V4L2_PIX_FMT_YUV422M, + .planes = 3, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YVU422, + .v4l2 = V4L2_PIX_FMT_YVU422M, + .planes = 3, + .hsub = 2, + }, { + .fourcc = DRM_FORMAT_YUV444, + .v4l2 = V4L2_PIX_FMT_YUV444M, + .planes = 3, + .hsub = 1, + }, { + .fourcc = DRM_FORMAT_YVU444, + .v4l2 = V4L2_PIX_FMT_YVU444M, + .planes = 3, + .hsub = 1, + } +}; + +const struct rzg2l_du_format_info *rzg2l_du_format_info(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rzg2l_du_format_infos); ++i) { + if (rzg2l_du_format_infos[i].fourcc == fourcc) + return &rzg2l_du_format_infos[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Frame buffer + */ + +int rzg2l_du_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8); + unsigned int align = 16 * args->bpp / 8; + + args->pitch = roundup(min_pitch, align); + + return drm_gem_dma_dumb_create_internal(file, dev, args); +} + +static struct drm_framebuffer * +rzg2l_du_fb_create(struct drm_device *dev, struct drm_file *file_priv, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + const struct rzg2l_du_format_info *format; + unsigned int max_pitch; + + format = rzg2l_du_format_info(mode_cmd->pixel_format); + if (!format) { + dev_dbg(dev->dev, "unsupported pixel format %p4cc\n", + &mode_cmd->pixel_format); + return ERR_PTR(-EINVAL); + } + + /* + * On RZ/G2L the memory interface is handled by the VSP that limits the + * pitch to 65535 bytes. + */ + max_pitch = 65535; + if (mode_cmd->pitches[0] > max_pitch) { + dev_dbg(dev->dev, "invalid pitch value %u\n", + mode_cmd->pitches[0]); + return ERR_PTR(-EINVAL); + } + + return drm_gem_fb_create(dev, file_priv, info, mode_cmd); +} + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +static const struct drm_mode_config_helper_funcs rzg2l_du_mode_config_helper = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +static const struct drm_mode_config_funcs rzg2l_du_mode_config_funcs = { + .fb_create = rzg2l_du_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static int rzg2l_du_encoders_init_one(struct rzg2l_du_device *rcdu, + enum rzg2l_du_output output, + struct of_endpoint *ep) +{ + struct device_node *entity; + int ret; + + /* Locate the connected entity and initialize the encoder. */ + entity = of_graph_get_remote_port_parent(ep->local_node); + if (!entity) { + dev_dbg(rcdu->dev, "unconnected endpoint %pOF, skipping\n", + ep->local_node); + return -ENODEV; + } + + if (!of_device_is_available(entity)) { + dev_dbg(rcdu->dev, + "connected entity %pOF is disabled, skipping\n", + entity); + of_node_put(entity); + return -ENODEV; + } + + ret = rzg2l_du_encoder_init(rcdu, output, entity); + if (ret && ret != -EPROBE_DEFER && ret != -ENOLINK) + dev_warn(rcdu->dev, + "failed to initialize encoder %pOF on output %s (%d), skipping\n", + entity, rzg2l_du_output_name(output), ret); + + of_node_put(entity); + + return ret; +} + +static int rzg2l_du_encoders_init(struct rzg2l_du_device *rcdu) +{ + struct device_node *np = rcdu->dev->of_node; + struct device_node *ep_node; + unsigned int num_encoders = 0; + + /* + * Iterate over the endpoints and create one encoder for each output + * pipeline. + */ + for_each_endpoint_of_node(np, ep_node) { + enum rzg2l_du_output output; + struct of_endpoint ep; + unsigned int i; + int ret; + + ret = of_graph_parse_endpoint(ep_node, &ep); + if (ret < 0) { + of_node_put(ep_node); + return ret; + } + + /* Find the output route corresponding to the port number. */ + for (i = 0; i < RZG2L_DU_OUTPUT_MAX; ++i) { + if (rcdu->info->routes[i].possible_outputs && + rcdu->info->routes[i].port == ep.port) { + output = i; + break; + } + } + + if (i == RZG2L_DU_OUTPUT_MAX) { + dev_warn(rcdu->dev, + "port %u references unexisting output, skipping\n", + ep.port); + continue; + } + + /* Process the output pipeline. */ + ret = rzg2l_du_encoders_init_one(rcdu, output, &ep); + if (ret < 0) { + if (ret == -EPROBE_DEFER) { + of_node_put(ep_node); + return ret; + } + + continue; + } + + num_encoders++; + } + + return num_encoders; +} + +static int rzg2l_du_vsps_init(struct rzg2l_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; + unsigned int crtcs_mask; + } vsps[RZG2L_DU_MAX_VSPS] = { { NULL, }, }; + unsigned int vsps_count = 0; + unsigned int cells; + unsigned int i; + int ret; + + /* + * First parse the DT vsps property to populate the list of VSPs. Each + * entry contains a pointer to the VSP DT node and a bitmask of the + * connected DU CRTCs. + */ + 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_prop_name, + cells, i, &args); + if (ret < 0) + goto done; + + /* + * Add the VSP to the list or update the corresponding existing + * entry if the VSP has already been added. + */ + for (j = 0; j < vsps_count; ++j) { + if (vsps[j].np == args.np) + break; + } + + if (j < vsps_count) + of_node_put(args.np); + else + vsps[vsps_count++].np = args.np; + + vsps[j].crtcs_mask |= BIT(i); + + /* + * Store the VSP pointer and pipe index in the CRTC. If the + * second cell of the 'renesas,vsps' specifier isn't present, + * default to 0. + */ + rcdu->crtcs[i].vsp = &rcdu->vsps[j]; + rcdu->crtcs[i].vsp_pipe = cells >= 1 ? args.args[0] : 0; + } + + /* + * Then initialize all the VSPs from the node pointers and CRTCs bitmask + * computed previously. + */ + for (i = 0; i < vsps_count; ++i) { + struct rzg2l_du_vsp *vsp = &rcdu->vsps[i]; + + vsp->index = i; + vsp->dev = rcdu; + + ret = rzg2l_du_vsp_init(vsp, vsps[i].np, vsps[i].crtcs_mask); + if (ret) + goto done; + } + +done: + for (i = 0; i < ARRAY_SIZE(vsps); ++i) + of_node_put(vsps[i].np); + + return ret; +} + +int rzg2l_du_modeset_init(struct rzg2l_du_device *rcdu) +{ + struct drm_device *dev = &rcdu->ddev; + struct drm_encoder *encoder; + unsigned int num_encoders; + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.normalize_zpos = true; + dev->mode_config.funcs = &rzg2l_du_mode_config_funcs; + dev->mode_config.helper_private = &rzg2l_du_mode_config_helper; + + /* + * The RZ DU was designed to support a frame size of 1920x1200 (landscape) + * or 1200x1920 (portrait). + */ + dev->mode_config.max_width = 1920; + dev->mode_config.max_height = 1920; + + rcdu->num_crtcs = hweight8(rcdu->info->channels_mask); + + /* + * Initialize vertical blanking interrupts handling. Start with vblank + * disabled for all CRTCs. + */ + ret = drm_vblank_init(dev, rcdu->num_crtcs); + if (ret < 0) + return ret; + + /* Initialize the compositors. */ + ret = rzg2l_du_vsps_init(rcdu); + if (ret < 0) + return ret; + + /* Create the CRTCs. */ + ret = rzg2l_du_crtc_create(rcdu); + if (ret < 0) + return ret; + + /* Initialize the encoders. */ + ret = rzg2l_du_encoders_init(rcdu); + if (ret < 0) + return dev_err_probe(rcdu->dev, ret, + "failed to initialize encoders\n"); + + if (ret == 0) { + dev_err(rcdu->dev, "error: no encoder could be initialized\n"); + return -EINVAL; + } + + num_encoders = ret; + + /* + * Set the possible CRTCs and possible clones. There's always at least + * one way for all encoders to clone each other, set all bits in the + * possible clones field. + */ + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + struct rzg2l_du_encoder *renc = to_rzg2l_encoder(encoder); + const struct rzg2l_du_output_routing *route = + &rcdu->info->routes[renc->output]; + + encoder->possible_crtcs = route->possible_outputs; + encoder->possible_clones = (1 << num_encoders) - 1; + } + + drm_mode_config_reset(dev); + + drm_kms_helper_poll_init(dev); + + return 0; +} diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_kms.h b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_kms.h new file mode 100644 index 000000000..e2c599f11 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_kms.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * RZ/G2L Display Unit Mode Setting + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_kms.h + */ + +#ifndef __RZG2L_DU_KMS_H__ +#define __RZG2L_DU_KMS_H__ + +#include <linux/types.h> + +struct dma_buf_attachment; +struct drm_file; +struct drm_device; +struct drm_gem_object; +struct drm_mode_create_dumb; +struct rzg2l_du_device; +struct sg_table; + +struct rzg2l_du_format_info { + u32 fourcc; + u32 v4l2; + unsigned int planes; + unsigned int hsub; +}; + +const struct rzg2l_du_format_info *rzg2l_du_format_info(u32 fourcc); + +int rzg2l_du_modeset_init(struct rzg2l_du_device *rcdu); + +int rzg2l_du_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args); + +struct drm_gem_object * +rzg2l_du_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sgt); + +#endif /* __RZG2L_DU_KMS_H__ */ diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_vsp.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_vsp.c new file mode 100644 index 000000000..1efa0f045 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_vsp.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * RZ/G2L Display Unit VSP-Based Compositor + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_vsp.c + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_blend.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_dma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_vblank.h> + +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/scatterlist.h> + +#include <media/vsp1.h> + +#include "rzg2l_du_drv.h" +#include "rzg2l_du_kms.h" +#include "rzg2l_du_vsp.h" + +static void rzg2l_du_vsp_complete(void *private, unsigned int status, u32 crc) +{ + struct rzg2l_du_crtc *crtc = private; + + if (crtc->vblank_enable) + drm_crtc_handle_vblank(&crtc->crtc); + + if (status & VSP1_DU_STATUS_COMPLETE) + rzg2l_du_crtc_finish_page_flip(crtc); + + drm_crtc_add_crc_entry(&crtc->crtc, false, 0, &crc); +} + +void rzg2l_du_vsp_enable(struct rzg2l_du_crtc *crtc) +{ + const struct drm_display_mode *mode = &crtc->crtc.state->adjusted_mode; + struct vsp1_du_lif_config cfg = { + .width = mode->hdisplay, + .height = mode->vdisplay, + .interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE, + .callback = rzg2l_du_vsp_complete, + .callback_data = crtc, + }; + + vsp1_du_enable(crtc->vsp->vsp, crtc->vsp_pipe, &cfg); +} + +void rzg2l_du_vsp_disable(struct rzg2l_du_crtc *crtc) +{ + vsp1_du_disable(crtc->vsp->vsp, crtc->vsp_pipe); +} + +void rzg2l_du_vsp_atomic_flush(struct rzg2l_du_crtc *crtc) +{ + struct vsp1_du_atomic_pipe_config cfg = { { 0, } }; + + vsp1_du_atomic_flush(crtc->vsp->vsp, crtc->vsp_pipe, &cfg); +} + +struct drm_plane *rzg2l_du_vsp_get_drm_plane(struct rzg2l_du_crtc *crtc, + unsigned int pipe_index) +{ + struct rzg2l_du_device *rcdu = crtc->vsp->dev; + struct drm_plane *plane = NULL; + + drm_for_each_plane(plane, &rcdu->ddev) { + struct rzg2l_du_vsp_plane *vsp_plane = to_rzg2l_vsp_plane(plane); + + if (vsp_plane->index == pipe_index) + break; + } + + return plane ? plane : ERR_PTR(-EINVAL); +} + +static const u32 rzg2l_du_vsp_formats[] = { + DRM_FORMAT_RGB332, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV16, + DRM_FORMAT_NV61, + DRM_FORMAT_YUV420, + DRM_FORMAT_YVU420, + DRM_FORMAT_YUV422, + DRM_FORMAT_YVU422, + DRM_FORMAT_YUV444, + DRM_FORMAT_YVU444, +}; + +static void rzg2l_du_vsp_plane_setup(struct rzg2l_du_vsp_plane *plane) +{ + struct rzg2l_du_vsp_plane_state *state = + to_rzg2l_vsp_plane_state(plane->plane.state); + struct rzg2l_du_crtc *crtc = to_rzg2l_crtc(state->state.crtc); + struct drm_framebuffer *fb = plane->plane.state->fb; + const struct rzg2l_du_format_info *format; + struct vsp1_du_atomic_config cfg = { + .pixelformat = 0, + .pitch = fb->pitches[0], + .alpha = state->state.alpha >> 8, + .zpos = state->state.zpos, + }; + u32 fourcc = state->format->fourcc; + unsigned int i; + + cfg.src.left = state->state.src.x1 >> 16; + cfg.src.top = state->state.src.y1 >> 16; + cfg.src.width = drm_rect_width(&state->state.src) >> 16; + cfg.src.height = drm_rect_height(&state->state.src) >> 16; + + cfg.dst.left = state->state.dst.x1; + cfg.dst.top = state->state.dst.y1; + cfg.dst.width = drm_rect_width(&state->state.dst); + cfg.dst.height = drm_rect_height(&state->state.dst); + + for (i = 0; i < state->format->planes; ++i) { + struct drm_gem_dma_object *gem; + + gem = drm_fb_dma_get_gem_obj(fb, i); + cfg.mem[i] = gem->dma_addr + fb->offsets[i]; + } + + if (state->state.pixel_blend_mode == DRM_MODE_BLEND_PIXEL_NONE) { + switch (fourcc) { + case DRM_FORMAT_ARGB1555: + fourcc = DRM_FORMAT_XRGB1555; + break; + + case DRM_FORMAT_ARGB4444: + fourcc = DRM_FORMAT_XRGB4444; + break; + + case DRM_FORMAT_ARGB8888: + fourcc = DRM_FORMAT_XRGB8888; + break; + } + } + + format = rzg2l_du_format_info(fourcc); + cfg.pixelformat = format->v4l2; + + cfg.premult = state->state.pixel_blend_mode == DRM_MODE_BLEND_PREMULTI; + + vsp1_du_atomic_update(plane->vsp->vsp, crtc->vsp_pipe, + plane->index, &cfg); +} + +static int __rzg2l_du_vsp_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state, + const struct rzg2l_du_format_info **format) +{ + struct drm_crtc_state *crtc_state; + int ret; + + if (!state->crtc) { + /* + * The visible field is not reset by the DRM core but only + * updated by drm_atomic_helper_check_plane_state, set it + * manually. + */ + state->visible = false; + *format = NULL; + return 0; + } + + crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + ret = drm_atomic_helper_check_plane_state(state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, true); + if (ret < 0) + return ret; + + if (!state->visible) { + *format = NULL; + return 0; + } + + *format = rzg2l_du_format_info(state->fb->format->format); + + return 0; +} + +static int rzg2l_du_vsp_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct rzg2l_du_vsp_plane_state *rstate = to_rzg2l_vsp_plane_state(new_plane_state); + + return __rzg2l_du_vsp_plane_atomic_check(plane, new_plane_state, &rstate->format); +} + +static void rzg2l_du_vsp_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); + struct rzg2l_du_vsp_plane *rplane = to_rzg2l_vsp_plane(plane); + struct rzg2l_du_crtc *crtc = to_rzg2l_crtc(old_state->crtc); + + if (new_state->visible) + rzg2l_du_vsp_plane_setup(rplane); + else if (old_state->crtc) + vsp1_du_atomic_update(rplane->vsp->vsp, crtc->vsp_pipe, + rplane->index, NULL); +} + +static const struct drm_plane_helper_funcs rzg2l_du_vsp_plane_helper_funcs = { + .atomic_check = rzg2l_du_vsp_plane_atomic_check, + .atomic_update = rzg2l_du_vsp_plane_atomic_update, +}; + +static struct drm_plane_state * +rzg2l_du_vsp_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct rzg2l_du_vsp_plane_state *copy; + + if (WARN_ON(!plane->state)) + return NULL; + + copy = kzalloc_obj(*copy); + if (!copy) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, ©->state); + + return ©->state; +} + +static void rzg2l_du_vsp_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(to_rzg2l_vsp_plane_state(state)); +} + +static void rzg2l_du_vsp_plane_reset(struct drm_plane *plane) +{ + struct rzg2l_du_vsp_plane_state *state; + + if (plane->state) { + rzg2l_du_vsp_plane_atomic_destroy_state(plane, plane->state); + plane->state = NULL; + } + + state = kzalloc_obj(*state); + if (!state) + return; + + __drm_atomic_helper_plane_reset(plane, &state->state); +} + +static const struct drm_plane_funcs rzg2l_du_vsp_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = rzg2l_du_vsp_plane_reset, + .atomic_duplicate_state = rzg2l_du_vsp_plane_atomic_duplicate_state, + .atomic_destroy_state = rzg2l_du_vsp_plane_atomic_destroy_state, +}; + +static void rzg2l_du_vsp_cleanup(struct drm_device *dev, void *res) +{ + struct rzg2l_du_vsp *vsp = res; + + if (vsp->link) + device_link_del(vsp->link); + + put_device(vsp->vsp); +} + +int rzg2l_du_vsp_init(struct rzg2l_du_vsp *vsp, struct device_node *np, + unsigned int crtcs) +{ + struct rzg2l_du_device *rcdu = vsp->dev; + struct platform_device *pdev; + unsigned int num_crtcs = hweight32(crtcs); + unsigned int num_planes = 2; + unsigned int i; + int ret; + + /* Find the VSP device and initialize it. */ + pdev = of_find_device_by_node(np); + if (!pdev) + return -ENXIO; + + vsp->vsp = &pdev->dev; + + ret = drmm_add_action_or_reset(&rcdu->ddev, rzg2l_du_vsp_cleanup, vsp); + if (ret < 0) + return ret; + + /* + * Enforce suspend/resume ordering between the DU (consumer) and the + * VSP (supplier). The DU will be suspended before and resume after the + * VSP. + */ + vsp->link = device_link_add(rcdu->dev, vsp->vsp, DL_FLAG_STATELESS); + if (!vsp->link) { + dev_err(rcdu->dev, "Failed to create device link to VSP %s\n", + dev_name(vsp->vsp)); + return -EINVAL; + } + + ret = vsp1_du_init(vsp->vsp); + if (ret < 0) + return ret; + + for (i = 0; i < num_planes; ++i) { + enum drm_plane_type type = i < num_crtcs + ? DRM_PLANE_TYPE_PRIMARY + : DRM_PLANE_TYPE_OVERLAY; + struct rzg2l_du_vsp_plane *plane; + + plane = drmm_universal_plane_alloc(&rcdu->ddev, struct rzg2l_du_vsp_plane, + plane, crtcs, &rzg2l_du_vsp_plane_funcs, + rzg2l_du_vsp_formats, + ARRAY_SIZE(rzg2l_du_vsp_formats), + NULL, type, NULL); + if (IS_ERR(plane)) + return PTR_ERR(plane); + + plane->vsp = vsp; + plane->index = i; + + drm_plane_helper_add(&plane->plane, + &rzg2l_du_vsp_plane_helper_funcs); + + drm_plane_create_alpha_property(&plane->plane); + drm_plane_create_zpos_property(&plane->plane, i, 0, + num_planes - 1); + + drm_plane_create_blend_mode_property(&plane->plane, + BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE)); + } + + return 0; +} diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_vsp.h b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_vsp.h new file mode 100644 index 000000000..a22aaf084 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_vsp.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * RZ/G2L Display Unit VSP-Based Compositor + * + * Copyright (C) 2023 Renesas Electronics Corporation + * + * Based on rcar_du_vsp.h + */ + +#ifndef __RZG2L_DU_VSP_H__ +#define __RZG2L_DU_VSP_H__ + +#include <drm/drm_plane.h> +#include <linux/container_of.h> +#include <linux/scatterlist.h> + +struct device; +struct device_link; +struct drm_framebuffer; +struct rzg2l_du_device; +struct rzg2l_du_format_info; +struct rzg2l_du_vsp; + +struct rzg2l_du_vsp_plane { + struct drm_plane plane; + struct rzg2l_du_vsp *vsp; + unsigned int index; +}; + +struct rzg2l_du_vsp { + unsigned int index; + struct device *vsp; + struct device_link *link; + struct rzg2l_du_device *dev; +}; + +static inline struct rzg2l_du_vsp_plane *to_rzg2l_vsp_plane(struct drm_plane *p) +{ + return container_of(p, struct rzg2l_du_vsp_plane, plane); +} + +/** + * struct rzg2l_du_vsp_plane_state - Driver-specific plane state + * @state: base DRM plane state + * @format: information about the pixel format used by the plane + */ +struct rzg2l_du_vsp_plane_state { + struct drm_plane_state state; + + const struct rzg2l_du_format_info *format; +}; + +static inline struct rzg2l_du_vsp_plane_state * +to_rzg2l_vsp_plane_state(struct drm_plane_state *state) +{ + return container_of(state, struct rzg2l_du_vsp_plane_state, state); +} + +#if IS_ENABLED(CONFIG_VIDEO_RENESAS_VSP1) +int rzg2l_du_vsp_init(struct rzg2l_du_vsp *vsp, struct device_node *np, + unsigned int crtcs); +void rzg2l_du_vsp_enable(struct rzg2l_du_crtc *crtc); +void rzg2l_du_vsp_disable(struct rzg2l_du_crtc *crtc); +void rzg2l_du_vsp_atomic_flush(struct rzg2l_du_crtc *crtc); +struct drm_plane *rzg2l_du_vsp_get_drm_plane(struct rzg2l_du_crtc *crtc, + unsigned int pipe_index); +#else +static inline int rzg2l_du_vsp_init(struct rzg2l_du_vsp *vsp, struct device_node *np, + unsigned int crtcs) +{ + return -ENXIO; +} + +static inline void rzg2l_du_vsp_enable(struct rzg2l_du_crtc *crtc) { }; +static inline void rzg2l_du_vsp_disable(struct rzg2l_du_crtc *crtc) { }; +static inline void rzg2l_du_vsp_atomic_flush(struct rzg2l_du_crtc *crtc) { }; +static inline struct drm_plane *rzg2l_du_vsp_get_drm_plane(struct rzg2l_du_crtc *crtc, + unsigned int pipe_index) +{ + return ERR_PTR(-ENXIO); +} +#endif + +#endif /* __RZG2L_DU_VSP_H__ */ diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c new file mode 100644 index 000000000..a2c864860 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c @@ -0,0 +1,1560 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RZ/G2L MIPI DSI Encoder Driver + * + * Copyright (C) 2022 Renesas Electronics Corporation + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk/renesas.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/of.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/units.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 <video/mipi_display.h> + +#include "rzg2l_mipi_dsi_regs.h" + +MODULE_IMPORT_NS("RZV2H_CPG"); + +#define RZG2L_DCS_BUF_SIZE 128 /* Maximum DCS buffer size in external memory. */ + +#define RZ_MIPI_DSI_FEATURE_16BPP BIT(0) + +struct rzg2l_mipi_dsi; + +struct rzg2l_mipi_dsi_hw_info { + int (*dphy_init)(struct rzg2l_mipi_dsi *dsi, u64 hsfreq_millihz); + void (*dphy_startup_late_init)(struct rzg2l_mipi_dsi *dsi); + void (*dphy_exit)(struct rzg2l_mipi_dsi *dsi); + int (*dphy_conf_clks)(struct rzg2l_mipi_dsi *dsi, unsigned long mode_freq, + u64 *hsfreq_millihz); + unsigned int (*dphy_mode_clk_check)(struct rzg2l_mipi_dsi *dsi, + unsigned long mode_freq); + struct { + const struct rzv2h_pll_limits **limits; + const u8 *table; + const u8 table_size; + } cpg_plldsi; + u32 phy_reg_offset; + u32 link_reg_offset; + unsigned long min_dclk; + unsigned long max_dclk; + u8 features; +}; + +struct rzv2h_dsi_mode_calc { + unsigned long mode_freq_khz; + struct rzv2h_pll_pars dsi_parameters; +}; + +struct rzg2l_mipi_dsi { + struct device *dev; + void __iomem *mmio; + + const struct rzg2l_mipi_dsi_hw_info *info; + + 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; + struct clk *lpclk; + + enum mipi_dsi_pixel_format format; + unsigned int num_data_lanes; + unsigned int lanes; + unsigned long mode_flags; + + struct rzv2h_dsi_mode_calc mode_calc; + + /* DCS buffer pointers when using external memory. */ + dma_addr_t dcs_buf_phys; + u8 *dcs_buf_virt; +}; + +static const struct rzv2h_pll_limits rzv2h_plldsi_div_limits = { + .fout = { .min = 80 * MEGA, .max = 1500 * MEGA }, + .fvco = { .min = 1050 * MEGA, .max = 2100 * MEGA }, + .m = { .min = 64, .max = 1023 }, + .p = { .min = 1, .max = 4 }, + .s = { .min = 0, .max = 5 }, + .k = { .min = -32768, .max = 32767 }, +}; + +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 = 80000000, + .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 = 125000000, + .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 = 250000000, + .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 = 360000000, + .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 = 720000000, + .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 = 1500000000, + .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, + }, +}; + +/** + * struct rzv2h_mipi_dsi_timings - Timing parameter table structure + * + * @hsfreq: Pointer to frequency threshold array + * @len: Number of entries in the hsfreq array + * @base_value: Base register value offset for this timing parameter + * + * Each timing parameter (TCLK*, THS*, etc.) has its own table with + * frequency thresholds and corresponding base register values. + */ +struct rzv2h_mipi_dsi_timings { + const u8 *hsfreq; + u8 len; + u8 base_value; +}; + +/* + * enum rzv2h_dsi_timing_idx - MIPI DSI timing parameter indices + * + * These enums correspond to different MIPI DSI PHY timing parameters. + */ +enum rzv2h_dsi_timing_idx { + TCLKPRPRCTL, + TCLKZEROCTL, + TCLKPOSTCTL, + TCLKTRAILCTL, + THSPRPRCTL, + THSZEROCTL, + THSTRAILCTL, + TLPXCTL, + THSEXITCTL, +}; + +/* + * RZ/V2H(P) Frequency threshold lookup tables for D-PHY timing parameters + * + * - Each array contains frequency thresholds (in units of 10 Mbps), + * taken directly from the table 9.5-4 hardware manual. + * - These thresholds define the frequency ranges for which timing + * register values must be programmed. + * - The actual register value is calculated in + * rzv2h_dphy_find_timings_val(): + * + * register_value = timings->base_value + table_index + * + * Example (TCLKPRPRCTL, from HW manual): + * 0-150 Mbps -> index 0 -> register_value = base + 0 = 0 + 0 = 0 + * 151-260 Mbps -> index 1 -> register_value = base + 1 = 0 + 1 = 1 + * 261-370 Mbps -> index 2 -> register_value = base + 2 = 0 + 2 = 2 + * + * Each of the following arrays corresponds to a specific timing + * parameter (TCLKPRPRCTL, TCLKZEROCTL, TCLKPOSTCTL, etc.). + */ +static const u8 tclkprprctl[] = { + 15, 26, 37, 47, 58, 69, 79, 90, 101, 111, 122, 133, 143, 150, +}; + +static const u8 tclkzeroctl[] = { + 9, 11, 13, 15, 18, 21, 23, 24, 25, 27, 29, 31, 34, 36, 38, + 41, 43, 45, 47, 50, 52, 54, 57, 59, 61, 63, 66, 68, 70, 73, + 75, 77, 79, 82, 84, 86, 89, 91, 93, 95, 98, 100, 102, 105, + 107, 109, 111, 114, 116, 118, 121, 123, 125, 127, 130, 132, + 134, 137, 139, 141, 143, 146, 148, 150, +}; + +static const u8 tclkpostctl[] = { + 8, 21, 34, 48, 61, 74, 88, 101, 114, 128, 141, 150, +}; + +static const u8 tclktrailctl[] = { + 14, 25, 37, 48, 59, 71, 82, 94, 105, 117, 128, 139, 150, +}; + +static const u8 thsprprctl[] = { + 11, 19, 29, 40, 50, 61, 72, 82, 93, 103, 114, 125, 135, 146, 150, +}; + +static const u8 thszeroctl[] = { + 18, 24, 29, 35, 40, 46, 51, 57, 62, 68, 73, 79, 84, 90, + 95, 101, 106, 112, 117, 123, 128, 134, 139, 145, 150, +}; + +static const u8 thstrailctl[] = { + 10, 21, 32, 42, 53, 64, 75, 85, 96, 107, 118, 128, 139, 150, +}; + +static const u8 tlpxctl[] = { + 13, 26, 39, 53, 66, 79, 93, 106, 119, 133, 146, 150, +}; + +static const u8 thsexitctl[] = { + 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, + 95, 103, 111, 119, 127, 135, 143, 150, +}; + +/* + * rzv2h_dsi_timings_tables - main timing parameter lookup table + * Maps timing parameter enum to its frequency table, array length and + * base register offset value. + */ +static const struct rzv2h_mipi_dsi_timings rzv2h_dsi_timings_tables[] = { + [TCLKPRPRCTL] = { + .hsfreq = tclkprprctl, + .len = ARRAY_SIZE(tclkprprctl), + .base_value = 0, + }, + [TCLKZEROCTL] = { + .hsfreq = tclkzeroctl, + .len = ARRAY_SIZE(tclkzeroctl), + .base_value = 2, + }, + [TCLKPOSTCTL] = { + .hsfreq = tclkpostctl, + .len = ARRAY_SIZE(tclkpostctl), + .base_value = 6, + }, + [TCLKTRAILCTL] = { + .hsfreq = tclktrailctl, + .len = ARRAY_SIZE(tclktrailctl), + .base_value = 1, + }, + [THSPRPRCTL] = { + .hsfreq = thsprprctl, + .len = ARRAY_SIZE(thsprprctl), + .base_value = 0, + }, + [THSZEROCTL] = { + .hsfreq = thszeroctl, + .len = ARRAY_SIZE(thszeroctl), + .base_value = 0, + }, + [THSTRAILCTL] = { + .hsfreq = thstrailctl, + .len = ARRAY_SIZE(thstrailctl), + .base_value = 3, + }, + [TLPXCTL] = { + .hsfreq = tlpxctl, + .len = ARRAY_SIZE(tlpxctl), + .base_value = 0, + }, + [THSEXITCTL] = { + .hsfreq = thsexitctl, + .len = ARRAY_SIZE(thsexitctl), + .base_value = 1, + }, +}; + +/** + * rzv2h_dphy_find_ulpsexit - Find ULP Exit timing value based on frequency + * The function maps frequency ranges to ULP exit timing values. + * Thresholds in the local hsfreq[] are expressed in Hz already. + * + * @freq: Input frequency in Hz + * + * Return: ULP exit timing value + */ +static u16 rzv2h_dphy_find_ulpsexit(unsigned long freq) +{ + /* Frequency thresholds in Hz for ULP exit timing selection */ + static const unsigned long hsfreq[] = { + 1953125UL, + 3906250UL, + 7812500UL, + 15625000UL, + }; + /* Corresponding ULP exit timing values for each frequency range */ + static const u16 ulpsexit[] = {49, 98, 195, 391}; + unsigned int i; + + /* Find the appropriate frequency range */ + for (i = 0; i < ARRAY_SIZE(hsfreq); i++) { + if (freq <= hsfreq[i]) + break; + } + + /* If frequency exceeds all thresholds, use the highest range */ + if (i == ARRAY_SIZE(hsfreq)) + i--; + + return ulpsexit[i]; +} + +/** + * rzv2h_dphy_find_timings_val - Find timing parameter value from lookup tables + * @freq: Input frequency in Hz + * @index: Index to select timing parameter table (see enum rzv2h_dsi_timing_idx) + * + * Selects the timing table for the requested parameter, finds the + * frequency range entry and returns the register value to program: + * + * register_value = timings->base_value + table_index + * + * Note: frequency table entries are stored as small integers (units of 10): + * threshold_in_hz = (unsigned long)table_entry * 10 * MEGA + * + * Return: timing register value to be programmed into hardware + */ +static u16 rzv2h_dphy_find_timings_val(unsigned long freq, u8 index) +{ + const struct rzv2h_mipi_dsi_timings *timings; + u16 i; + + /* Get the timing table structure for the requested parameter */ + timings = &rzv2h_dsi_timings_tables[index]; + + /* + * Search through frequency table to find appropriate range + * timings->hsfreq[i] contains frequency values from HW manual + * Convert to Hz by multiplying by 10 * MEGA. + */ + for (i = 0; i < timings->len; i++) { + unsigned long hsfreq = timings->hsfreq[i] * 10 * MEGA; + + if (freq <= hsfreq) + break; + } + + /* If frequency exceeds table range, use the last entry */ + if (i == timings->len) + i--; + + /* + * Calculate final register value: + * - timings->base_value: base value for this timing parameter + * - i: index into frequency table (0-based) + * Combined they give the exact register value to program + */ + return timings->base_value + i; +}; + +static void rzg2l_mipi_dsi_phy_write(struct rzg2l_mipi_dsi *dsi, u32 reg, u32 data) +{ + iowrite32(data, dsi->mmio + dsi->info->phy_reg_offset + reg); +} + +static void rzg2l_mipi_dsi_link_write(struct rzg2l_mipi_dsi *dsi, u32 reg, u32 data) +{ + iowrite32(data, dsi->mmio + dsi->info->link_reg_offset + reg); +} + +static u32 rzg2l_mipi_dsi_phy_read(struct rzg2l_mipi_dsi *dsi, u32 reg) +{ + return ioread32(dsi->mmio + dsi->info->phy_reg_offset + reg); +} + +static u32 rzg2l_mipi_dsi_link_read(struct rzg2l_mipi_dsi *dsi, u32 reg) +{ + return ioread32(dsi->mmio + dsi->info->link_reg_offset + reg); +} + +/* ----------------------------------------------------------------------------- + * Hardware Setup + */ + +static int rzg2l_mipi_dsi_dphy_init(struct rzg2l_mipi_dsi *dsi, + u64 hsfreq_millihz) +{ + unsigned long hsfreq = DIV_ROUND_CLOSEST_ULL(hsfreq_millihz, MILLI); + const struct rzg2l_mipi_dsi_timings *dphy_timings; + unsigned int i; + u32 dphyctrl0; + u32 dphytim0; + u32 dphytim1; + u32 dphytim2; + u32 dphytim3; + + /* 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); + + 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); +} + +static int rzg2l_dphy_conf_clks(struct rzg2l_mipi_dsi *dsi, unsigned long mode_freq, + u64 *hsfreq_millihz) +{ + unsigned long vclk_rate; + unsigned int bpp; + + clk_set_rate(dsi->vclk, mode_freq * KILO); + vclk_rate = clk_get_rate(dsi->vclk); + if (vclk_rate != mode_freq * KILO) + dev_dbg(dsi->dev, "Requested vclk rate %lu, actual %lu mismatch\n", + mode_freq * KILO, vclk_rate); + /* + * 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 = hsfreq + */ + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + *hsfreq_millihz = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(vclk_rate, bpp * MILLI), + dsi->lanes); + + return 0; +} + +static unsigned int rzv2h_dphy_mode_clk_check(struct rzg2l_mipi_dsi *dsi, + unsigned long mode_freq) +{ + u64 hsfreq_millihz, mode_freq_hz, mode_freq_millihz; + struct rzv2h_pll_div_pars cpg_dsi_parameters; + struct rzv2h_pll_pars dsi_parameters; + bool parameters_found; + unsigned int bpp; + + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + mode_freq_hz = mul_u32_u32(mode_freq, KILO); + mode_freq_millihz = mode_freq_hz * MILLI; + parameters_found = + rzv2h_get_pll_divs_pars(dsi->info->cpg_plldsi.limits[0], + &cpg_dsi_parameters, + dsi->info->cpg_plldsi.table, + dsi->info->cpg_plldsi.table_size, + mode_freq_millihz); + if (!parameters_found) + return MODE_CLOCK_RANGE; + + hsfreq_millihz = DIV_ROUND_CLOSEST_ULL(cpg_dsi_parameters.div.freq_millihz * bpp, + dsi->lanes); + parameters_found = rzv2h_get_pll_pars(&rzv2h_plldsi_div_limits, + &dsi_parameters, hsfreq_millihz); + if (!parameters_found) + return MODE_CLOCK_RANGE; + + if (abs(dsi_parameters.error_millihz) >= 500) + return MODE_CLOCK_RANGE; + + memcpy(&dsi->mode_calc.dsi_parameters, &dsi_parameters, sizeof(dsi_parameters)); + dsi->mode_calc.mode_freq_khz = mode_freq; + + return MODE_OK; +} + +static int rzv2h_dphy_conf_clks(struct rzg2l_mipi_dsi *dsi, unsigned long mode_freq, + u64 *hsfreq_millihz) +{ + struct rzv2h_pll_pars *dsi_parameters = &dsi->mode_calc.dsi_parameters; + unsigned long status; + + if (dsi->mode_calc.mode_freq_khz != mode_freq) { + status = rzv2h_dphy_mode_clk_check(dsi, mode_freq); + if (status != MODE_OK) { + dev_err(dsi->dev, "No PLL parameters found for mode clk %lu\n", + mode_freq); + return -EINVAL; + } + } + + *hsfreq_millihz = dsi_parameters->freq_millihz; + + return 0; +} + +static int rzv2h_mipi_dsi_dphy_init(struct rzg2l_mipi_dsi *dsi, + u64 hsfreq_millihz) +{ + struct rzv2h_pll_pars *dsi_parameters = &dsi->mode_calc.dsi_parameters; + unsigned long lpclk_rate = clk_get_rate(dsi->lpclk); + u32 phytclksetr, phythssetr, phytlpxsetr, phycr; + struct rzg2l_mipi_dsi_timings dphy_timings; + u16 ulpsexit; + u64 hsfreq; + + hsfreq = DIV_ROUND_CLOSEST_ULL(hsfreq_millihz, MILLI); + + if (dsi_parameters->freq_millihz != hsfreq_millihz && + !rzv2h_get_pll_pars(&rzv2h_plldsi_div_limits, dsi_parameters, + hsfreq_millihz)) { + dev_err(dsi->dev, "No PLL parameters found for HSFREQ %lluHz\n", hsfreq); + return -EINVAL; + } + + dphy_timings.tclk_trail = + rzv2h_dphy_find_timings_val(hsfreq, TCLKTRAILCTL); + dphy_timings.tclk_post = + rzv2h_dphy_find_timings_val(hsfreq, TCLKPOSTCTL); + dphy_timings.tclk_zero = + rzv2h_dphy_find_timings_val(hsfreq, TCLKZEROCTL); + dphy_timings.tclk_prepare = + rzv2h_dphy_find_timings_val(hsfreq, TCLKPRPRCTL); + dphy_timings.ths_exit = + rzv2h_dphy_find_timings_val(hsfreq, THSEXITCTL); + dphy_timings.ths_trail = + rzv2h_dphy_find_timings_val(hsfreq, THSTRAILCTL); + dphy_timings.ths_zero = + rzv2h_dphy_find_timings_val(hsfreq, THSZEROCTL); + dphy_timings.ths_prepare = + rzv2h_dphy_find_timings_val(hsfreq, THSPRPRCTL); + dphy_timings.tlpx = + rzv2h_dphy_find_timings_val(hsfreq, TLPXCTL); + ulpsexit = rzv2h_dphy_find_ulpsexit(lpclk_rate); + + phytclksetr = FIELD_PREP(PHYTCLKSETR_TCLKTRAILCTL, dphy_timings.tclk_trail) | + FIELD_PREP(PHYTCLKSETR_TCLKPOSTCTL, dphy_timings.tclk_post) | + FIELD_PREP(PHYTCLKSETR_TCLKZEROCTL, dphy_timings.tclk_zero) | + FIELD_PREP(PHYTCLKSETR_TCLKPRPRCTL, dphy_timings.tclk_prepare); + phythssetr = FIELD_PREP(PHYTHSSETR_THSEXITCTL, dphy_timings.ths_exit) | + FIELD_PREP(PHYTHSSETR_THSTRAILCTL, dphy_timings.ths_trail) | + FIELD_PREP(PHYTHSSETR_THSZEROCTL, dphy_timings.ths_zero) | + FIELD_PREP(PHYTHSSETR_THSPRPRCTL, dphy_timings.ths_prepare); + phytlpxsetr = rzg2l_mipi_dsi_phy_read(dsi, PHYTLPXSETR) & ~PHYTLPXSETR_TLPXCTL; + phytlpxsetr |= FIELD_PREP(PHYTLPXSETR_TLPXCTL, dphy_timings.tlpx); + phycr = rzg2l_mipi_dsi_phy_read(dsi, PHYCR) & ~GENMASK(9, 0); + phycr |= FIELD_PREP(PHYCR_ULPSEXIT, ulpsexit); + + /* Setting all D-PHY Timings Registers */ + rzg2l_mipi_dsi_phy_write(dsi, PHYTCLKSETR, phytclksetr); + rzg2l_mipi_dsi_phy_write(dsi, PHYTHSSETR, phythssetr); + rzg2l_mipi_dsi_phy_write(dsi, PHYTLPXSETR, phytlpxsetr); + rzg2l_mipi_dsi_phy_write(dsi, PHYCR, phycr); + + rzg2l_mipi_dsi_phy_write(dsi, PLLCLKSET0R, + FIELD_PREP(PLLCLKSET0R_PLL_S, dsi_parameters->s) | + FIELD_PREP(PLLCLKSET0R_PLL_P, dsi_parameters->p) | + FIELD_PREP(PLLCLKSET0R_PLL_M, dsi_parameters->m)); + rzg2l_mipi_dsi_phy_write(dsi, PLLCLKSET1R, + FIELD_PREP(PLLCLKSET1R_PLL_K, dsi_parameters->k)); + + /* + * From RZ/V2H HW manual (Rev.1.20) section 9.5.3 Operation, + * (C) After write to D-PHY registers we need to wait for more than 1 x tp + * + * tp = 1 / (PLLREFCLK / PLLCLKSET0R.PLL_P) + * PLLREFCLK = 24MHz + * PLLCLKSET0R.PLL_P = {1, 2, 3, 4} + * + * To handle all the cases lets use PLLCLKSET0R.PLL_P = 4 + * tp = 1 / (24MHz / 4) = 1 / 6MHz = 166.67ns + */ + ndelay(200); + + rzg2l_mipi_dsi_phy_write(dsi, PLLENR, PLLENR_PLLEN); + /* + * From RZ/V2H HW manual (Rev.1.20) section 9.5.3 Operation, + * (D) After write to PLLENR.PLLEN we need to wait for more than 3000 x tp + * + * 3000 x tp = 3000 x 0.16667 ns = 500.01 microseconds + */ + usleep_range(510, 520); + + return 0; +} + +static void rzv2h_mipi_dsi_dphy_startup_late_init(struct rzg2l_mipi_dsi *dsi) +{ + /* + * From RZ/V2H HW manual (Rev.1.20) section 9.5.3 Operation, + * (E) After write to TXSETR we need to wait for more than 200 microseconds + * and then write to PHYRSTR + */ + usleep_range(210, 220); + rzg2l_mipi_dsi_phy_write(dsi, PHYRSTR, PHYRSTR_PHYMRSTN); +} + +static void rzv2h_mipi_dsi_dphy_exit(struct rzg2l_mipi_dsi *dsi) +{ + rzg2l_mipi_dsi_phy_write(dsi, PLLENR, 0); +} + +static int rzg2l_mipi_dsi_startup(struct rzg2l_mipi_dsi *dsi, + const struct drm_display_mode *mode) +{ + unsigned long hsfreq; + u64 hsfreq_millihz; + u32 txsetr; + u32 clstptsetr; + u32 lptrnstsetr; + u32 clkkpt; + u32 clkbfht; + u32 clkstpt; + u32 golpbkt; + u32 dsisetr; + int ret; + + ret = pm_runtime_resume_and_get(dsi->dev); + if (ret < 0) + return ret; + + ret = dsi->info->dphy_conf_clks(dsi, mode->clock, &hsfreq_millihz); + if (ret < 0) + goto err_phy; + + ret = dsi->info->dphy_init(dsi, hsfreq_millihz); + 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); + + if (dsi->info->dphy_startup_late_init) + dsi->info->dphy_startup_late_init(dsi); + + hsfreq = DIV_ROUND_CLOSEST_ULL(hsfreq_millihz, MILLI); + /* + * 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 > 445500000) { + clkkpt = 12; + clkbfht = 15; + clkstpt = 48; + golpbkt = 75; + } else if (hsfreq > 250000000) { + 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); + + /* + * Increase MRPSZ as the default value of 1 will result in long read + * commands payload not being saved to memory. + */ + dsisetr = rzg2l_mipi_dsi_link_read(dsi, DSISETR); + FIELD_MODIFY(DSISETR_MRPSZ, &dsisetr, RZG2L_DCS_BUF_SIZE); + rzg2l_mipi_dsi_link_write(dsi, DSISETR, dsisetr); + + if (dsi->rstc) { + ret = reset_control_deassert(dsi->rstc); + if (ret < 0) + goto err_phy; + + fsleep(1000); + } + + return 0; + +err_phy: + dsi->info->dphy_exit(dsi); + pm_runtime_put(dsi->dev); + + return ret; +} + +static void rzg2l_mipi_dsi_stop(struct rzg2l_mipi_dsi *dsi) +{ + reset_control_assert(dsi->rstc); + dsi->info->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; + case 16: + vich1ppsetr = VICH1PPSETR_DT_RGB16; + 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, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct rzg2l_mipi_dsi *dsi = bridge_to_rzg2l_mipi_dsi(bridge); + + return drm_bridge_attach(encoder, dsi->next_bridge, bridge, + flags); +} + +static void rzg2l_mipi_dsi_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *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; + + 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; + + rzg2l_mipi_dsi_startup(dsi, mode); +} + +static void rzg2l_mipi_dsi_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *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; + + ret = rzg2l_mipi_dsi_start_hs_clock(dsi); + if (ret < 0) + goto err_stop; + + 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; + + rzg2l_mipi_dsi_set_display_timing(dsi, mode); + + 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_atomic_commit *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); +} + +static void rzg2l_mipi_dsi_atomic_post_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct rzg2l_mipi_dsi *dsi = bridge_to_rzg2l_mipi_dsi(bridge); + + 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) +{ + struct rzg2l_mipi_dsi *dsi = bridge_to_rzg2l_mipi_dsi(bridge); + + if (mode->clock > dsi->info->max_dclk) + return MODE_CLOCK_HIGH; + + if (mode->clock < dsi->info->min_dclk) + return MODE_CLOCK_LOW; + + if (dsi->info->dphy_mode_clk_check) { + enum drm_mode_status status; + + status = dsi->info->dphy_mode_clk_check(dsi, mode->clock); + if (status != MODE_OK) + return status; + } + + 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_pre_enable = rzg2l_mipi_dsi_atomic_pre_enable, + .atomic_enable = rzg2l_mipi_dsi_atomic_enable, + .atomic_disable = rzg2l_mipi_dsi_atomic_disable, + .atomic_post_disable = rzg2l_mipi_dsi_atomic_post_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 bpp; + 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; + } + + bpp = mipi_dsi_pixel_format_to_bpp(device->format); + switch (bpp) { + case 24: + break; + case 18: + break; + case 16: + if (!(dsi->info->features & RZ_MIPI_DSI_FEATURE_16BPP)) { + dev_err(dsi->dev, "Unsupported format 0x%04x\n", + device->format); + return -EINVAL; + } + 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); + + /* + * Report the required division ratio setting for the MIPI clock dividers. + * + * vclk * bpp = hsclk * 8 * num_lanes + * + * vclk * DSI_AB_divider = hsclk * 16 + * + * which simplifies to... + * DSI_AB_divider = bpp * 2 / num_lanes + */ + rzg2l_cpg_dsi_div_set_divider(bpp * 2 / dsi->lanes, PLL5_TARGET_DSI); + + 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 ssize_t rzg2l_mipi_dsi_read_response(struct rzg2l_mipi_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + u8 *msg_rx = msg->rx_buf; + u8 datatype; + u32 result; + u16 size; + + result = rzg2l_mipi_dsi_link_read(dsi, RXRSS0R); + if (result & RXRSS0R_RXPKTDFAIL) { + dev_err(dsi->dev, "packet rx data did not save correctly\n"); + return -EPROTO; + } + + if (result & RXRSS0R_RXFAIL) { + dev_err(dsi->dev, "packet rx failure\n"); + return -EPROTO; + } + + if (!(result & RXRSS0R_RXSUC)) + return -EPROTO; + + datatype = FIELD_GET(RXRSS0R_DT, result); + + switch (datatype) { + case 0: + dev_dbg(dsi->dev, "ACK\n"); + return 0; + case MIPI_DSI_RX_END_OF_TRANSMISSION: + dev_dbg(dsi->dev, "EoTp\n"); + return 0; + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + dev_dbg(dsi->dev, "Acknowledge and error report: $%02x%02x\n", + (u8)FIELD_GET(RXRSS0R_DATA1, result), + (u8)FIELD_GET(RXRSS0R_DATA0, result)); + return 0; + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + msg_rx[0] = FIELD_GET(RXRSS0R_DATA0, result); + return 1; + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + msg_rx[0] = FIELD_GET(RXRSS0R_DATA0, result); + msg_rx[1] = FIELD_GET(RXRSS0R_DATA1, result); + return 2; + case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE: + case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE: + size = FIELD_GET(RXRSS0R_WC, result); + + if (size > msg->rx_len) { + dev_err(dsi->dev, "rx buffer too small"); + return -ENOSPC; + } + + memcpy(msg_rx, dsi->dcs_buf_virt, size); + return size; + default: + dev_err(dsi->dev, "unhandled response type: %02x\n", datatype); + return -EPROTO; + } +} + +static ssize_t rzg2l_mipi_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct rzg2l_mipi_dsi *dsi = host_to_rzg2l_mipi_dsi(host); + struct mipi_dsi_packet packet; + bool need_bta; + u32 value; + int ret; + + ret = mipi_dsi_create_packet(&packet, msg); + if (ret < 0) + return ret; + + /* Terminate operation after this descriptor is finished */ + value = SQCH0DSC0AR_NXACT_TERM; + + if (msg->flags & MIPI_DSI_MSG_REQ_ACK) { + need_bta = true; /* Message with explicitly requested ACK */ + value |= FIELD_PREP(SQCH0DSC0AR_BTA, SQCH0DSC0AR_BTA_NON_READ); + } else if (msg->rx_buf && msg->rx_len > 0) { + need_bta = true; /* Read request */ + value |= FIELD_PREP(SQCH0DSC0AR_BTA, SQCH0DSC0AR_BTA_READ); + } else { + need_bta = false; + value |= FIELD_PREP(SQCH0DSC0AR_BTA, SQCH0DSC0AR_BTA_NONE); + } + + /* Set transmission speed */ + if (msg->flags & MIPI_DSI_MSG_USE_LPM) + value |= SQCH0DSC0AR_SPD_LOW; + else + value |= SQCH0DSC0AR_SPD_HIGH; + + /* Write TX packet header */ + value |= FIELD_PREP(SQCH0DSC0AR_DT, packet.header[0]) | + FIELD_PREP(SQCH0DSC0AR_DATA0, packet.header[1]) | + FIELD_PREP(SQCH0DSC0AR_DATA1, packet.header[2]); + + if (mipi_dsi_packet_format_is_long(msg->type)) { + value |= SQCH0DSC0AR_FMT_LONG; + + if (packet.payload_length > RZG2L_DCS_BUF_SIZE) { + dev_err(dsi->dev, "Packet Tx payload size (%d) too large", + (unsigned int)packet.payload_length); + return -ENOSPC; + } + + /* Copy TX packet payload data to memory space */ + memcpy(dsi->dcs_buf_virt, packet.payload, packet.payload_length); + } else { + value |= SQCH0DSC0AR_FMT_SHORT; + } + + rzg2l_mipi_dsi_link_write(dsi, SQCH0DSC0AR, value); + + /* + * Write: specify payload data source location, only used for + * long packet. + * Read: specify payload data storage location of response + * packet. Note: a read packet is always a short packet. + * If the response packet is a short packet or a long packet + * with WC = 0 (no payload), DTSEL is meaningless. + */ + rzg2l_mipi_dsi_link_write(dsi, SQCH0DSC0BR, SQCH0DSC0BR_DTSEL_MEM_SPACE); + + /* + * Set SQCHxSR.AACTFIN bit when descriptor actions are finished. + * Read: set Rx result save slot number to 0 (ACTCODE). + */ + rzg2l_mipi_dsi_link_write(dsi, SQCH0DSC0CR, SQCH0DSC0CR_FINACT); + + /* Set rx/tx payload data address, only relevant for long packet. */ + rzg2l_mipi_dsi_link_write(dsi, SQCH0DSC0DR, (u32)dsi->dcs_buf_phys); + + /* Start sequence 0 operation */ + value = rzg2l_mipi_dsi_link_read(dsi, SQCH0SET0R); + value |= SQCH0SET0R_START; + rzg2l_mipi_dsi_link_write(dsi, SQCH0SET0R, value); + + /* Wait for operation to finish */ + ret = read_poll_timeout(rzg2l_mipi_dsi_link_read, + value, value & SQCH0SR_ADESFIN, + 2000, 20000, false, dsi, SQCH0SR); + if (ret == 0) { + /* Success: clear status bit */ + rzg2l_mipi_dsi_link_write(dsi, SQCH0SCR, SQCH0SCR_ADESFIN); + + if (need_bta) + ret = rzg2l_mipi_dsi_read_response(dsi, msg); + else + ret = packet.payload_length; + } + + return ret; +} + +static const struct mipi_dsi_host_ops rzg2l_mipi_dsi_host_ops = { + .attach = rzg2l_mipi_dsi_host_attach, + .detach = rzg2l_mipi_dsi_host_detach, + .transfer = rzg2l_mipi_dsi_host_transfer, +}; + +/* ----------------------------------------------------------------------------- + * Power Management + */ + +static int 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 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 = { + 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_drm_bridge_alloc(&pdev->dev, struct rzg2l_mipi_dsi, bridge, + &rzg2l_mipi_dsi_bridge_ops); + if (IS_ERR(dsi)) + return PTR_ERR(dsi); + + platform_set_drvdata(pdev, dsi); + dsi->dev = &pdev->dev; + + dsi->info = of_device_get_match_data(&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->lpclk = devm_clk_get(dsi->dev, "lpclk"); + if (IS_ERR(dsi->lpclk)) + return PTR_ERR(dsi->lpclk); + + dsi->rstc = devm_reset_control_get_optional_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"); + + dsi->dcs_buf_virt = dmam_alloc_coherent(dsi->dev, RZG2L_DCS_BUF_SIZE, + &dsi->dcs_buf_phys, GFP_KERNEL); + if (!dsi->dcs_buf_virt) + return -ENOMEM; + + 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 = dsi->info->dphy_init(dsi, 80000000ULL * MILLI); + 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); + dsi->info->dphy_exit(dsi); + pm_runtime_put(dsi->dev); + + /* Initialize the DRM bridge. */ + 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: + dsi->info->dphy_exit(dsi); + pm_runtime_put(dsi->dev); +err_pm_disable: + pm_runtime_disable(dsi->dev); + return ret; +} + +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); +} + +RZV2H_CPG_PLL_DSI_LIMITS(rzv2h_cpg_pll_dsi_limits); + +static const struct rzv2h_pll_limits *rzv2h_plldsi_limits[] = { + &rzv2h_cpg_pll_dsi_limits, +}; + +static const u8 rzv2h_cpg_div_table[] = { + 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, +}; + +static const struct rzg2l_mipi_dsi_hw_info rzv2h_mipi_dsi_info = { + .dphy_init = rzv2h_mipi_dsi_dphy_init, + .dphy_startup_late_init = rzv2h_mipi_dsi_dphy_startup_late_init, + .dphy_exit = rzv2h_mipi_dsi_dphy_exit, + .dphy_mode_clk_check = rzv2h_dphy_mode_clk_check, + .dphy_conf_clks = rzv2h_dphy_conf_clks, + .cpg_plldsi.limits = rzv2h_plldsi_limits, + .cpg_plldsi.table = rzv2h_cpg_div_table, + .cpg_plldsi.table_size = ARRAY_SIZE(rzv2h_cpg_div_table), + .phy_reg_offset = 0x10000, + .link_reg_offset = 0, + .min_dclk = 5440, + .max_dclk = 187500, + .features = RZ_MIPI_DSI_FEATURE_16BPP, +}; + +static const struct rzg2l_mipi_dsi_hw_info rzg2l_mipi_dsi_info = { + .dphy_init = rzg2l_mipi_dsi_dphy_init, + .dphy_exit = rzg2l_mipi_dsi_dphy_exit, + .dphy_conf_clks = rzg2l_dphy_conf_clks, + .link_reg_offset = 0x10000, + .min_dclk = 5803, + .max_dclk = 148500, +}; + +static const struct of_device_id rzg2l_mipi_dsi_of_table[] = { + { .compatible = "renesas,r9a09g057-mipi-dsi", .data = &rzv2h_mipi_dsi_info, }, + { .compatible = "renesas,rzg2l-mipi-dsi", .data = &rzg2l_mipi_dsi_info, }, + { /* 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 = rzg2l_mipi_dsi_remove, + .driver = { + .name = "rzg2l-mipi-dsi", + .pm = pm_ptr(&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"); diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h new file mode 100644 index 000000000..2bef20566 --- /dev/null +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi_regs.h @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * RZ/G2L MIPI DSI Interface Registers Definitions + * + * Copyright (C) 2022 Renesas Electronics Corporation + */ + +#ifndef __RZG2L_MIPI_DSI_REGS_H__ +#define __RZG2L_MIPI_DSI_REGS_H__ + +#include <linux/bits.h> + +/* DPHY Registers */ +#define DSIDPHYCTRL0 0x00 +#define DSIDPHYCTRL0_CAL_EN_HSRX_OFS BIT(16) +#define DSIDPHYCTRL0_CMN_MASTER_EN BIT(8) +#define DSIDPHYCTRL0_RE_VDD_DETVCCQLV18 BIT(2) +#define DSIDPHYCTRL0_EN_LDO1200 BIT(1) +#define DSIDPHYCTRL0_EN_BGR BIT(0) + +#define DSIDPHYTIM0 0x04 +#define DSIDPHYTIM0_TCLK_MISS(x) ((x) << 24) +#define DSIDPHYTIM0_T_INIT(x) ((x) << 0) + +#define DSIDPHYTIM1 0x08 +#define DSIDPHYTIM1_THS_PREPARE(x) ((x) << 24) +#define DSIDPHYTIM1_TCLK_PREPARE(x) ((x) << 16) +#define DSIDPHYTIM1_THS_SETTLE(x) ((x) << 8) +#define DSIDPHYTIM1_TCLK_SETTLE(x) ((x) << 0) + +#define DSIDPHYTIM2 0x0c +#define DSIDPHYTIM2_TCLK_TRAIL(x) ((x) << 24) +#define DSIDPHYTIM2_TCLK_POST(x) ((x) << 16) +#define DSIDPHYTIM2_TCLK_PRE(x) ((x) << 8) +#define DSIDPHYTIM2_TCLK_ZERO(x) ((x) << 0) + +#define DSIDPHYTIM3 0x10 +#define DSIDPHYTIM3_TLPX(x) ((x) << 24) +#define DSIDPHYTIM3_THS_EXIT(x) ((x) << 16) +#define DSIDPHYTIM3_THS_TRAIL(x) ((x) << 8) +#define DSIDPHYTIM3_THS_ZERO(x) ((x) << 0) + +/* RZ/V2H DPHY Registers */ +#define PLLENR 0x000 +#define PLLENR_PLLEN BIT(0) + +#define PHYRSTR 0x004 +#define PHYRSTR_PHYMRSTN BIT(0) + +#define PLLCLKSET0R 0x010 +#define PLLCLKSET0R_PLL_S GENMASK(2, 0) +#define PLLCLKSET0R_PLL_P GENMASK(13, 8) +#define PLLCLKSET0R_PLL_M GENMASK(25, 16) + +#define PLLCLKSET1R 0x014 +#define PLLCLKSET1R_PLL_K GENMASK(15, 0) + +#define PHYTCLKSETR 0x020 +#define PHYTCLKSETR_TCLKTRAILCTL GENMASK(7, 0) +#define PHYTCLKSETR_TCLKPOSTCTL GENMASK(15, 8) +#define PHYTCLKSETR_TCLKZEROCTL GENMASK(23, 16) +#define PHYTCLKSETR_TCLKPRPRCTL GENMASK(31, 24) + +#define PHYTHSSETR 0x024 +#define PHYTHSSETR_THSEXITCTL GENMASK(7, 0) +#define PHYTHSSETR_THSTRAILCTL GENMASK(15, 8) +#define PHYTHSSETR_THSZEROCTL GENMASK(23, 16) +#define PHYTHSSETR_THSPRPRCTL GENMASK(31, 24) + +#define PHYTLPXSETR 0x028 +#define PHYTLPXSETR_TLPXCTL GENMASK(7, 0) + +#define PHYCR 0x030 +#define PHYCR_ULPSEXIT GENMASK(9, 0) + +/* --------------------------------------------------------*/ + +/* Link Status Register */ +#define LINKSR 0x10 +#define LINKSR_LPBUSY BIT(13) +#define LINKSR_HSBUSY BIT(12) +#define LINKSR_VICHRUN1 BIT(8) +#define LINKSR_SQCHRUN1 BIT(4) +#define LINKSR_SQCHRUN0 BIT(0) + +/* Tx Set Register */ +#define TXSETR 0x100 +#define TXSETR_NUMLANECAP (0x3 << 16) +#define TXSETR_DLEN (1 << 9) +#define TXSETR_CLEN (1 << 8) +#define TXSETR_NUMLANEUSE(x) (((x) & 0x3) << 0) + +/* HS Clock Set Register */ +#define HSCLKSETR 0x104 +#define HSCLKSETR_HSCLKMODE_CONT (1 << 1) +#define HSCLKSETR_HSCLKMODE_NON_CONT (0 << 1) +#define HSCLKSETR_HSCLKRUN_HS (1 << 0) +#define HSCLKSETR_HSCLKRUN_LP (0 << 0) + +/* Reset Control Register */ +#define RSTCR 0x110 +#define RSTCR_SWRST BIT(0) +#define RSTCR_FCETXSTP BIT(16) + +/* Reset Status Register */ +#define RSTSR 0x114 +#define RSTSR_DL0DIR (1 << 15) +#define RSTSR_DLSTPST (0xf << 8) +#define RSTSR_SWRSTV1 (1 << 4) +#define RSTSR_SWRSTIB (1 << 3) +#define RSTSR_SWRSTAPB (1 << 2) +#define RSTSR_SWRSTLP (1 << 1) +#define RSTSR_SWRSTHS (1 << 0) + +/* DSI Set Register */ +#define DSISETR 0x120 +#define DSISETR_MRPSZ GENMASK(15, 0) + +/* Rx Result Save Slot 0 Register */ +#define RXRSS0R 0x240 +#define RXRSS0R_RXPKTDFAIL BIT(28) +#define RXRSS0R_RXFAIL BIT(27) +#define RXRSS0R_RXSUC BIT(25) +#define RXRSS0R_DT GENMASK(21, 16) +#define RXRSS0R_DATA1 GENMASK(15, 8) +#define RXRSS0R_DATA0 GENMASK(7, 0) +#define RXRSS0R_WC GENMASK(15, 0) /* Word count for long packet. */ + +/* Clock Lane Stop Time Set Register */ +#define CLSTPTSETR 0x314 +#define CLSTPTSETR_CLKKPT(x) ((x) << 24) +#define CLSTPTSETR_CLKBFHT(x) ((x) << 16) +#define CLSTPTSETR_CLKSTPT(x) ((x) << 2) + +/* LP Transition Time Set Register */ +#define LPTRNSTSETR 0x318 +#define LPTRNSTSETR_GOLPBKT(x) ((x) << 0) + +/* Physical Lane Status Register */ +#define PLSR 0x320 +#define PLSR_CLHS2LP BIT(27) +#define PLSR_CLLP2HS BIT(26) + +/* Video-Input Channel 1 Set 0 Register */ +#define VICH1SET0R 0x400 +#define VICH1SET0R_VSEN BIT(12) +#define VICH1SET0R_HFPNOLP BIT(10) +#define VICH1SET0R_HBPNOLP BIT(9) +#define VICH1SET0R_HSANOLP BIT(8) +#define VICH1SET0R_VSTPAFT BIT(1) +#define VICH1SET0R_VSTART BIT(0) + +/* Video-Input Channel 1 Set 1 Register */ +#define VICH1SET1R 0x404 +#define VICH1SET1R_DLY(x) (((x) & 0xfff) << 2) + +/* Video-Input Channel 1 Status Register */ +#define VICH1SR 0x410 +#define VICH1SR_VIRDY BIT(3) +#define VICH1SR_RUNNING BIT(2) +#define VICH1SR_STOP BIT(1) +#define VICH1SR_START BIT(0) + +/* Video-Input Channel 1 Pixel Packet Set Register */ +#define VICH1PPSETR 0x420 +#define VICH1PPSETR_DT_RGB16 (0x0e << 16) +#define VICH1PPSETR_DT_RGB18 (0x1e << 16) +#define VICH1PPSETR_DT_RGB18_LS (0x2e << 16) +#define VICH1PPSETR_DT_RGB24 (0x3e << 16) +#define VICH1PPSETR_TXESYNC_PULSE (1 << 15) +#define VICH1PPSETR_VC(x) ((x) << 22) + +/* Video-Input Channel 1 Vertical Size Set Register */ +#define VICH1VSSETR 0x428 +#define VICH1VSSETR_VACTIVE(x) (((x) & 0x7fff) << 16) +#define VICH1VSSETR_VSPOL_LOW (1 << 15) +#define VICH1VSSETR_VSPOL_HIGH (0 << 15) +#define VICH1VSSETR_VSA(x) (((x) & 0xfff) << 0) + +/* Video-Input Channel 1 Vertical Porch Set Register */ +#define VICH1VPSETR 0x42c +#define VICH1VPSETR_VFP(x) (((x) & 0x1fff) << 16) +#define VICH1VPSETR_VBP(x) (((x) & 0x1fff) << 0) + +/* Video-Input Channel 1 Horizontal Size Set Register */ +#define VICH1HSSETR 0x430 +#define VICH1HSSETR_HACTIVE(x) (((x) & 0x7fff) << 16) +#define VICH1HSSETR_HSPOL_LOW (1 << 15) +#define VICH1HSSETR_HSPOL_HIGH (0 << 15) +#define VICH1HSSETR_HSA(x) (((x) & 0xfff) << 0) + +/* Video-Input Channel 1 Horizontal Porch Set Register */ +#define VICH1HPSETR 0x434 +#define VICH1HPSETR_HFP(x) (((x) & 0x1fff) << 16) +#define VICH1HPSETR_HBP(x) (((x) & 0x1fff) << 0) + +/* Sequence Channel 0 Set 0 Register */ +#define SQCH0SET0R 0x5c0 +#define SQCH0SET0R_START BIT(0) + +/* Sequence Channel 0 Status Register */ +#define SQCH0SR 0x5d0 +#define SQCH0SR_ADESFIN BIT(8) + +/* Sequence Channel 0 Status Clear Register */ +#define SQCH0SCR 0x5d4 +#define SQCH0SCR_ADESFIN BIT(8) + +/* Sequence Channel 0 Descriptor 0-A Register */ +#define SQCH0DSC0AR 0x780 +#define SQCH0DSC0AR_NXACT_TERM 0 /* Bit 28 */ +#define SQCH0DSC0AR_BTA GENMASK(27, 26) +#define SQCH0DSC0AR_BTA_NONE 0 +#define SQCH0DSC0AR_BTA_NON_READ 1 +#define SQCH0DSC0AR_BTA_READ 2 +#define SQCH0DSC0AR_BTA_ONLY 3 +#define SQCH0DSC0AR_SPD_HIGH 0 +#define SQCH0DSC0AR_SPD_LOW BIT(25) +#define SQCH0DSC0AR_FMT_SHORT 0 +#define SQCH0DSC0AR_FMT_LONG BIT(24) +#define SQCH0DSC0AR_DT GENMASK(21, 16) +#define SQCH0DSC0AR_DATA1 GENMASK(15, 8) +#define SQCH0DSC0AR_DATA0 GENMASK(7, 0) + +/* Sequence Channel 0 Descriptor 0-B Register */ +#define SQCH0DSC0BR 0x784 +#define SQCH0DSC0BR_DTSEL_MEM_SPACE BIT(24) /* Use external memory */ + +/* Sequence Channel 0 Descriptor 0-C Register */ +#define SQCH0DSC0CR 0x788 +#define SQCH0DSC0CR_FINACT BIT(0) +#define SQCH0DSC0CR_AUXOP BIT(22) + +/* Sequence Channel 0 Descriptor 0-D Register */ +#define SQCH0DSC0DR 0x78c + +#endif /* __RZG2L_MIPI_DSI_REGS_H__ */ diff --git a/drivers/gpu/drm/renesas/shmobile/Kconfig b/drivers/gpu/drm/renesas/shmobile/Kconfig new file mode 100644 index 000000000..52e160464 --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +config DRM_SHMOBILE + tristate "DRM Support for SH Mobile" + depends on DRM && PM + depends on ARCH_RENESAS || ARCH_SHMOBILE || COMPILE_TEST + select BACKLIGHT_CLASS_DEVICE + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + select DRM_GEM_DMA_HELPER + select VIDEOMODE_HELPERS + help + Choose this option if you have an SH Mobile chipset. + If M is selected the module will be called shmob-drm. + diff --git a/drivers/gpu/drm/renesas/shmobile/Makefile b/drivers/gpu/drm/renesas/shmobile/Makefile new file mode 100644 index 000000000..2679555d6 --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +shmob-drm-y := shmob_drm_crtc.o \ + shmob_drm_drv.o \ + shmob_drm_kms.o \ + shmob_drm_plane.o + +obj-$(CONFIG_DRM_SHMOBILE) += shmob-drm.o diff --git a/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c b/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c new file mode 100644 index 000000000..1a2b9b68a --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * shmob_drm_crtc.c -- SH Mobile DRM CRTCs + * + * Copyright (C) 2012 Renesas Electronics Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include <linux/clk.h> +#include <linux/media-bus-format.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/pm_runtime.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_dma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_panel.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/drm_vblank.h> + +#include <video/videomode.h> + +#include "shmob_drm_crtc.h" +#include "shmob_drm_drv.h" +#include "shmob_drm_kms.h" +#include "shmob_drm_plane.h" +#include "shmob_drm_regs.h" + +/* ----------------------------------------------------------------------------- + * Page Flip + */ + +void shmob_drm_crtc_finish_page_flip(struct shmob_drm_crtc *scrtc) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = scrtc->base.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = scrtc->event; + scrtc->event = NULL; + if (event) { + drm_crtc_send_vblank_event(&scrtc->base, event); + wake_up(&scrtc->flip_wait); + drm_crtc_vblank_put(&scrtc->base); + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static bool shmob_drm_crtc_page_flip_pending(struct shmob_drm_crtc *scrtc) +{ + struct drm_device *dev = scrtc->base.dev; + unsigned long flags; + bool pending; + + spin_lock_irqsave(&dev->event_lock, flags); + pending = scrtc->event != NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + return pending; +} + +static void shmob_drm_crtc_wait_page_flip(struct shmob_drm_crtc *scrtc) +{ + struct drm_crtc *crtc = &scrtc->base; + struct shmob_drm_device *sdev = to_shmob_device(crtc->dev); + + if (wait_event_timeout(scrtc->flip_wait, + !shmob_drm_crtc_page_flip_pending(scrtc), + msecs_to_jiffies(50))) + return; + + dev_warn(sdev->dev, "page flip timeout\n"); + + shmob_drm_crtc_finish_page_flip(scrtc); +} + +/* ----------------------------------------------------------------------------- + * CRTC + */ + +static const struct { + u32 fmt; + u32 ldmt1r; +} shmob_drm_bus_fmts[] = { + { MEDIA_BUS_FMT_RGB888_3X8, LDMT1R_MIFTYP_RGB8 }, + { MEDIA_BUS_FMT_RGB666_2X9_BE, LDMT1R_MIFTYP_RGB9 }, + { MEDIA_BUS_FMT_RGB888_2X12_BE, LDMT1R_MIFTYP_RGB12A }, + { MEDIA_BUS_FMT_RGB444_1X12, LDMT1R_MIFTYP_RGB12B }, + { MEDIA_BUS_FMT_RGB565_1X16, LDMT1R_MIFTYP_RGB16 }, + { MEDIA_BUS_FMT_RGB666_1X18, LDMT1R_MIFTYP_RGB18 }, + { MEDIA_BUS_FMT_RGB888_1X24, LDMT1R_MIFTYP_RGB24 }, + { MEDIA_BUS_FMT_UYVY8_1X16, LDMT1R_MIFTYP_YCBCR }, +}; + +static void shmob_drm_crtc_setup_geometry(struct shmob_drm_crtc *scrtc) +{ + struct drm_crtc *crtc = &scrtc->base; + struct shmob_drm_device *sdev = to_shmob_device(crtc->dev); + const struct drm_display_info *info = &sdev->connector->display_info; + const struct drm_display_mode *mode = &crtc->mode; + unsigned int i; + u32 value; + + if (!info->num_bus_formats || !info->bus_formats) { + dev_warn(sdev->dev, "No bus format reported, using RGB888\n"); + value = LDMT1R_MIFTYP_RGB24; + } else { + for (i = 0; i < ARRAY_SIZE(shmob_drm_bus_fmts); i++) { + if (shmob_drm_bus_fmts[i].fmt == info->bus_formats[0]) + break; + } + if (i < ARRAY_SIZE(shmob_drm_bus_fmts)) { + value = shmob_drm_bus_fmts[i].ldmt1r; + } else { + dev_warn(sdev->dev, + "unsupported bus format 0x%x, using RGB888\n", + info->bus_formats[0]); + value = LDMT1R_MIFTYP_RGB24; + } + } + + if (info->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE) + value |= LDMT1R_DWPOL; + if (info->bus_flags & DRM_BUS_FLAG_DE_LOW) + value |= LDMT1R_DIPOL; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + value |= LDMT1R_VPOL; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + value |= LDMT1R_HPOL; + lcdc_write(sdev, LDMT1R, value); + + value = ((mode->hdisplay / 8) << 16) /* HDCN */ + | (mode->htotal / 8); /* HTCN */ + lcdc_write(sdev, LDHCNR, value); + + value = (((mode->hsync_end - mode->hsync_start) / 8) << 16) /* HSYNW */ + | (mode->hsync_start / 8); /* HSYNP */ + lcdc_write(sdev, LDHSYNR, value); + + value = ((mode->hdisplay & 7) << 24) | ((mode->htotal & 7) << 16) + | (((mode->hsync_end - mode->hsync_start) & 7) << 8) + | (mode->hsync_start & 7); + lcdc_write(sdev, LDHAJR, value); + + value = ((mode->vdisplay) << 16) /* VDLN */ + | mode->vtotal; /* VTLN */ + lcdc_write(sdev, LDVLNR, value); + + value = ((mode->vsync_end - mode->vsync_start) << 16) /* VSYNW */ + | mode->vsync_start; /* VSYNP */ + lcdc_write(sdev, LDVSYNR, value); +} + +static void shmob_drm_crtc_start_stop(struct shmob_drm_crtc *scrtc, bool start) +{ + struct shmob_drm_device *sdev = to_shmob_device(scrtc->base.dev); + u32 value; + + value = lcdc_read(sdev, LDCNT2R); + if (start) + lcdc_write(sdev, LDCNT2R, value | LDCNT2R_DO); + else + lcdc_write(sdev, LDCNT2R, value & ~LDCNT2R_DO); + + /* Wait until power is applied/stopped. */ + while (1) { + value = lcdc_read(sdev, LDPMR) & LDPMR_LPS; + if ((start && value) || (!start && !value)) + break; + + cpu_relax(); + } + + if (!start) { + /* Stop the dot clock. */ + lcdc_write(sdev, LDDCKSTPR, LDDCKSTPR_DCKSTP); + } +} + +static inline struct shmob_drm_crtc *to_shmob_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct shmob_drm_crtc, base); +} + +static void shmob_drm_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); + struct shmob_drm_device *sdev = to_shmob_device(crtc->dev); + unsigned int clk_div = sdev->config.clk_div; + struct device *dev = sdev->dev; + u32 value; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return; + + /* Reset and enable the LCDC. */ + lcdc_write(sdev, LDCNT2R, lcdc_read(sdev, LDCNT2R) | LDCNT2R_BR); + lcdc_wait_bit(sdev, LDCNT2R, LDCNT2R_BR, 0); + lcdc_write(sdev, LDCNT2R, LDCNT2R_ME); + + /* Stop the LCDC first and disable all interrupts. */ + shmob_drm_crtc_start_stop(scrtc, false); + lcdc_write(sdev, LDINTR, 0); + + /* Configure power supply, dot clocks and start them. */ + lcdc_write(sdev, LDPMR, 0); + + value = sdev->lddckr; + if (clk_div) { + /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider + * denominator. + */ + lcdc_write(sdev, LDDCKPAT1R, 0); + lcdc_write(sdev, LDDCKPAT2R, (1 << (clk_div / 2)) - 1); + + if (clk_div == 1) + value |= LDDCKR_MOSEL; + else + value |= clk_div; + } + + lcdc_write(sdev, LDDCKR, value); + lcdc_write(sdev, LDDCKSTPR, 0); + lcdc_wait_bit(sdev, LDDCKSTPR, ~0, 0); + + /* Setup geometry, format, frame buffer memory and operation mode. */ + shmob_drm_crtc_setup_geometry(scrtc); + + lcdc_write(sdev, LDSM1R, 0); + + /* Enable the display output. */ + lcdc_write(sdev, LDCNT1R, LDCNT1R_DE); + + shmob_drm_crtc_start_stop(scrtc, true); + + /* Turn vertical blank interrupt reporting back on. */ + drm_crtc_vblank_on(crtc); +} + +static void shmob_drm_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); + struct shmob_drm_device *sdev = to_shmob_device(crtc->dev); + + /* + * Disable vertical blank interrupt reporting. We first need to wait + * for page flip completion before stopping the CRTC as userspace + * expects page flips to eventually complete. + */ + shmob_drm_crtc_wait_page_flip(scrtc); + drm_crtc_vblank_off(crtc); + + /* Stop the LCDC. */ + shmob_drm_crtc_start_stop(scrtc, false); + + /* Disable the display output. */ + lcdc_write(sdev, LDCNT1R, 0); + + pm_runtime_put(sdev->dev); +} + +static void shmob_drm_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct drm_pending_vblank_event *event; + struct drm_device *dev = crtc->dev; + unsigned long flags; + + if (crtc->state->event) { + spin_lock_irqsave(&dev->event_lock, flags); + event = crtc->state->event; + crtc->state->event = NULL; + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irqrestore(&dev->event_lock, flags); + } +} + +static const struct drm_crtc_helper_funcs crtc_helper_funcs = { + .atomic_flush = shmob_drm_crtc_atomic_flush, + .atomic_enable = shmob_drm_crtc_atomic_enable, + .atomic_disable = shmob_drm_crtc_atomic_disable, +}; + +static int shmob_drm_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event, + uint32_t page_flip_flags, + struct drm_modeset_acquire_ctx *ctx) +{ + struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); + struct drm_device *dev = scrtc->base.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (scrtc->event != NULL) { + spin_unlock_irqrestore(&dev->event_lock, flags); + return -EBUSY; + } + spin_unlock_irqrestore(&dev->event_lock, flags); + + drm_atomic_set_fb_for_plane(crtc->primary->state, fb); + + if (event) { + event->pipe = 0; + drm_crtc_vblank_get(&scrtc->base); + spin_lock_irqsave(&dev->event_lock, flags); + scrtc->event = event; + spin_unlock_irqrestore(&dev->event_lock, flags); + } + + return 0; +} + +static void shmob_drm_crtc_enable_vblank(struct shmob_drm_device *sdev, + bool enable) +{ + unsigned long flags; + u32 ldintr; + + /* Be careful not to acknowledge any pending interrupt. */ + spin_lock_irqsave(&sdev->irq_lock, flags); + ldintr = lcdc_read(sdev, LDINTR) | LDINTR_STATUS_MASK; + if (enable) + ldintr |= LDINTR_VEE; + else + ldintr &= ~LDINTR_VEE; + lcdc_write(sdev, LDINTR, ldintr); + spin_unlock_irqrestore(&sdev->irq_lock, flags); +} + +static int shmob_drm_enable_vblank(struct drm_crtc *crtc) +{ + struct shmob_drm_device *sdev = to_shmob_device(crtc->dev); + + shmob_drm_crtc_enable_vblank(sdev, true); + + return 0; +} + +static void shmob_drm_disable_vblank(struct drm_crtc *crtc) +{ + struct shmob_drm_device *sdev = to_shmob_device(crtc->dev); + + shmob_drm_crtc_enable_vblank(sdev, false); +} + +static const struct drm_crtc_funcs crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = shmob_drm_crtc_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .enable_vblank = shmob_drm_enable_vblank, + .disable_vblank = shmob_drm_disable_vblank, +}; + +int shmob_drm_crtc_create(struct shmob_drm_device *sdev) +{ + struct drm_crtc *crtc = &sdev->crtc.base; + struct drm_plane *primary, *plane; + unsigned int i; + int ret; + + init_waitqueue_head(&sdev->crtc.flip_wait); + + primary = shmob_drm_plane_create(sdev, DRM_PLANE_TYPE_PRIMARY, 0); + if (IS_ERR(primary)) + return PTR_ERR(primary); + + for (i = 1; i < 5; ++i) { + plane = shmob_drm_plane_create(sdev, DRM_PLANE_TYPE_OVERLAY, i); + if (IS_ERR(plane)) + return PTR_ERR(plane); + } + + ret = drm_crtc_init_with_planes(&sdev->ddev, crtc, primary, NULL, + &crtc_funcs, NULL); + if (ret < 0) + return ret; + + drm_crtc_helper_add(crtc, &crtc_helper_funcs); + + /* Start with vertical blank interrupt reporting disabled. */ + drm_crtc_vblank_off(crtc); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Legacy Encoder + */ + +static bool shmob_drm_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = encoder->dev; + struct shmob_drm_device *sdev = to_shmob_device(dev); + struct drm_connector *connector = sdev->connector; + const struct drm_display_mode *panel_mode; + + if (list_empty(&connector->modes)) { + dev_dbg(dev->dev, "mode_fixup: empty modes list\n"); + return false; + } + + /* The flat panel mode is fixed, just copy it to the adjusted mode. */ + panel_mode = list_first_entry(&connector->modes, + struct drm_display_mode, head); + drm_mode_copy(adjusted_mode, panel_mode); + + return true; +} + +static const struct drm_encoder_helper_funcs encoder_helper_funcs = { + .mode_fixup = shmob_drm_encoder_mode_fixup, +}; + +/* ----------------------------------------------------------------------------- + * Encoder + */ + +int shmob_drm_encoder_create(struct shmob_drm_device *sdev) +{ + struct drm_encoder *encoder = &sdev->encoder; + struct drm_bridge *bridge; + int ret; + + encoder->possible_crtcs = 1; + + ret = drm_simple_encoder_init(&sdev->ddev, encoder, + DRM_MODE_ENCODER_DPI); + if (ret < 0) + return ret; + + if (sdev->pdata) { + drm_encoder_helper_add(encoder, &encoder_helper_funcs); + return 0; + } + + /* Create a panel bridge */ + bridge = devm_drm_of_get_bridge(sdev->dev, sdev->dev->of_node, 0, 0); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + + /* Attach the bridge to the encoder */ + ret = drm_bridge_attach(encoder, bridge, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + dev_err(sdev->dev, "failed to attach bridge: %pe\n", + ERR_PTR(ret)); + return ret; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Legacy Connector + */ + +static inline struct shmob_drm_connector *to_shmob_connector(struct drm_connector *connector) +{ + return container_of(connector, struct shmob_drm_connector, base); +} + +static int shmob_drm_connector_get_modes(struct drm_connector *connector) +{ + struct shmob_drm_connector *scon = to_shmob_connector(connector); + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (mode == NULL) + return 0; + + mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; + + drm_display_mode_from_videomode(scon->mode, mode); + + drm_mode_probed_add(connector, mode); + + return 1; +} + +static struct drm_encoder * +shmob_drm_connector_best_encoder(struct drm_connector *connector) +{ + struct shmob_drm_connector *scon = to_shmob_connector(connector); + + return scon->encoder; +} + +static const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = shmob_drm_connector_get_modes, + .best_encoder = shmob_drm_connector_best_encoder, +}; + +static void shmob_drm_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); + + kfree(connector); +} + +static const struct drm_connector_funcs connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = shmob_drm_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static struct drm_connector * +shmob_drm_connector_init(struct shmob_drm_device *sdev, + struct drm_encoder *encoder) +{ + u32 bus_fmt = sdev->pdata->iface.bus_fmt; + struct shmob_drm_connector *scon; + struct drm_connector *connector; + struct drm_display_info *info; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(shmob_drm_bus_fmts); i++) { + if (shmob_drm_bus_fmts[i].fmt == bus_fmt) + break; + } + if (i == ARRAY_SIZE(shmob_drm_bus_fmts)) { + dev_err(sdev->dev, "unsupported bus format 0x%x\n", bus_fmt); + return ERR_PTR(-EINVAL); + } + + scon = kzalloc_obj(*scon); + if (!scon) + return ERR_PTR(-ENOMEM); + + connector = &scon->base; + scon->encoder = encoder; + scon->mode = &sdev->pdata->panel.mode; + + info = &connector->display_info; + info->width_mm = sdev->pdata->panel.width_mm; + info->height_mm = sdev->pdata->panel.height_mm; + + if (scon->mode->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) + info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE; + if (scon->mode->flags & DISPLAY_FLAGS_DE_LOW) + info->bus_flags |= DRM_BUS_FLAG_DE_LOW; + + ret = drm_display_info_set_bus_formats(info, &bus_fmt, 1); + if (ret < 0) { + kfree(scon); + return ERR_PTR(ret); + } + + ret = drm_connector_init(&sdev->ddev, connector, &connector_funcs, + DRM_MODE_CONNECTOR_DPI); + if (ret < 0) { + kfree(scon); + return ERR_PTR(ret); + } + + drm_connector_helper_add(connector, &connector_helper_funcs); + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret < 0) { + drm_connector_cleanup(connector); + kfree(scon); + return ERR_PTR(ret); + } + + return connector; +} + +/* ----------------------------------------------------------------------------- + * Connector + */ + +int shmob_drm_connector_create(struct shmob_drm_device *sdev, + struct drm_encoder *encoder) +{ + struct drm_connector *connector; + + if (sdev->pdata) + connector = shmob_drm_connector_init(sdev, encoder); + else + connector = drm_bridge_connector_init(&sdev->ddev, encoder); + if (IS_ERR(connector)) { + dev_err(sdev->dev, "failed to created connector: %pe\n", + connector); + return PTR_ERR(connector); + } + + connector->dpms = DRM_MODE_DPMS_OFF; + + sdev->connector = connector; + + return 0; +} diff --git a/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.h b/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.h new file mode 100644 index 000000000..16e1712dd --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * shmob_drm_crtc.h -- SH Mobile DRM CRTCs + * + * Copyright (C) 2012 Renesas Electronics Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __SHMOB_DRM_CRTC_H__ +#define __SHMOB_DRM_CRTC_H__ + +#include <drm/drm_crtc.h> +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> + +#include <linux/wait.h> + +#include <video/videomode.h> + +struct drm_pending_vblank_event; +struct shmob_drm_device; +struct shmob_drm_format_info; + +struct shmob_drm_crtc { + struct drm_crtc base; + + struct drm_pending_vblank_event *event; + wait_queue_head_t flip_wait; +}; + +/* Legacy connector */ +struct shmob_drm_connector { + struct drm_connector base; + struct drm_encoder *encoder; + const struct videomode *mode; +}; + +int shmob_drm_crtc_create(struct shmob_drm_device *sdev); +void shmob_drm_crtc_finish_page_flip(struct shmob_drm_crtc *scrtc); + +int shmob_drm_encoder_create(struct shmob_drm_device *sdev); +int shmob_drm_connector_create(struct shmob_drm_device *sdev, + struct drm_encoder *encoder); + +#endif /* __SHMOB_DRM_CRTC_H__ */ diff --git a/drivers/gpu/drm/renesas/shmobile/shmob_drm_drv.c b/drivers/gpu/drm/renesas/shmobile/shmob_drm_drv.c new file mode 100644 index 000000000..2f31822b2 --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/shmob_drm_drv.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * shmob_drm_drv.c -- SH Mobile DRM driver + * + * Copyright (C) 2012 Renesas Electronics Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_dma.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_module.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "shmob_drm_drv.h" +#include "shmob_drm_kms.h" +#include "shmob_drm_plane.h" +#include "shmob_drm_regs.h" + +/* ----------------------------------------------------------------------------- + * Hardware initialization + */ + +static int shmob_drm_setup_clocks(struct shmob_drm_device *sdev, + enum shmob_drm_clk_source clksrc) +{ + struct clk *clk; + char *clkname; + + switch (clksrc) { + case SHMOB_DRM_CLK_BUS: + clkname = "fck"; + sdev->lddckr = LDDCKR_ICKSEL_BUS; + break; + case SHMOB_DRM_CLK_PERIPHERAL: + clkname = "media"; + sdev->lddckr = LDDCKR_ICKSEL_MIPI; + break; + case SHMOB_DRM_CLK_EXTERNAL: + clkname = "lclk"; + sdev->lddckr = LDDCKR_ICKSEL_HDMI; + break; + default: + return -EINVAL; + } + + clk = devm_clk_get(sdev->dev, clkname); + if (IS_ERR(clk)) { + dev_err(sdev->dev, "cannot get dot clock %s\n", clkname); + return PTR_ERR(clk); + } + + sdev->clock = clk; + return 0; +} + +/* ----------------------------------------------------------------------------- + * DRM operations + */ + +static irqreturn_t shmob_drm_irq(int irq, void *arg) +{ + struct drm_device *dev = arg; + struct shmob_drm_device *sdev = to_shmob_device(dev); + unsigned long flags; + u32 status; + + /* Acknowledge interrupts. Putting interrupt enable and interrupt flag + * bits in the same register is really brain-dead design and requires + * taking a spinlock. + */ + spin_lock_irqsave(&sdev->irq_lock, flags); + status = lcdc_read(sdev, LDINTR); + lcdc_write(sdev, LDINTR, status ^ LDINTR_STATUS_MASK); + spin_unlock_irqrestore(&sdev->irq_lock, flags); + + if (status & LDINTR_VES) { + drm_crtc_handle_vblank(&sdev->crtc.base); + shmob_drm_crtc_finish_page_flip(&sdev->crtc); + } + + return IRQ_HANDLED; +} + +DEFINE_DRM_GEM_DMA_FOPS(shmob_drm_fops); + +static const struct drm_driver shmob_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + DRM_GEM_DMA_DRIVER_OPS, + DRM_FBDEV_DMA_DRIVER_OPS, + .fops = &shmob_drm_fops, + .name = "shmob-drm", + .desc = "Renesas SH Mobile DRM", + .major = 1, + .minor = 0, +}; + +/* ----------------------------------------------------------------------------- + * Power management + */ + +static int shmob_drm_pm_suspend(struct device *dev) +{ + struct shmob_drm_device *sdev = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(&sdev->ddev); +} + +static int shmob_drm_pm_resume(struct device *dev) +{ + struct shmob_drm_device *sdev = dev_get_drvdata(dev); + + return drm_mode_config_helper_resume(&sdev->ddev); +} + +static int shmob_drm_pm_runtime_suspend(struct device *dev) +{ + struct shmob_drm_device *sdev = dev_get_drvdata(dev); + + if (sdev->clock) + clk_disable_unprepare(sdev->clock); + + return 0; +} + +static int shmob_drm_pm_runtime_resume(struct device *dev) +{ + struct shmob_drm_device *sdev = dev_get_drvdata(dev); + int ret; + + if (sdev->clock) { + ret = clk_prepare_enable(sdev->clock); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct dev_pm_ops shmob_drm_pm_ops = { + SYSTEM_SLEEP_PM_OPS(shmob_drm_pm_suspend, shmob_drm_pm_resume) + RUNTIME_PM_OPS(shmob_drm_pm_runtime_suspend, + shmob_drm_pm_runtime_resume, NULL) +}; + +/* ----------------------------------------------------------------------------- + * Platform driver + */ + +static void shmob_drm_remove(struct platform_device *pdev) +{ + struct shmob_drm_device *sdev = platform_get_drvdata(pdev); + struct drm_device *ddev = &sdev->ddev; + + drm_dev_unregister(ddev); + drm_atomic_helper_shutdown(ddev); + drm_kms_helper_poll_fini(ddev); +} + +static void shmob_drm_shutdown(struct platform_device *pdev) +{ + struct shmob_drm_device *sdev = platform_get_drvdata(pdev); + + drm_atomic_helper_shutdown(&sdev->ddev); +} + +static int shmob_drm_probe(struct platform_device *pdev) +{ + struct shmob_drm_platform_data *pdata = pdev->dev.platform_data; + const struct shmob_drm_config *config; + struct shmob_drm_device *sdev; + struct drm_device *ddev; + int ret; + + config = of_device_get_match_data(&pdev->dev); + if (!config && !pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + + /* + * Allocate and initialize the DRM device, driver private data, I/O + * resources and clocks. + */ + sdev = devm_drm_dev_alloc(&pdev->dev, &shmob_drm_driver, + struct shmob_drm_device, ddev); + if (IS_ERR(sdev)) + return PTR_ERR(sdev); + + ddev = &sdev->ddev; + sdev->dev = &pdev->dev; + if (config) { + sdev->config = *config; + } else { + sdev->pdata = pdata; + sdev->config.clk_source = pdata->clk_source; + sdev->config.clk_div = pdata->iface.clk_div; + } + spin_lock_init(&sdev->irq_lock); + + platform_set_drvdata(pdev, sdev); + + sdev->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(sdev->mmio)) + return PTR_ERR(sdev->mmio); + + ret = shmob_drm_setup_clocks(sdev, sdev->config.clk_source); + if (ret < 0) + return ret; + + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) + return ret; + + ret = drm_vblank_init(ddev, 1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to initialize vblank\n"); + return ret; + } + + ret = shmob_drm_modeset_init(sdev); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "failed to initialize mode setting\n"); + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto err_modeset_cleanup; + sdev->irq = ret; + + ret = devm_request_irq(&pdev->dev, sdev->irq, shmob_drm_irq, 0, + ddev->driver->name, ddev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to install IRQ handler\n"); + goto err_modeset_cleanup; + } + + /* + * Register the DRM device with the core and the connectors with + * sysfs. + */ + ret = drm_dev_register(ddev, 0); + if (ret < 0) + goto err_modeset_cleanup; + + drm_client_setup_with_fourcc(ddev, DRM_FORMAT_RGB565); + + return 0; + +err_modeset_cleanup: + drm_kms_helper_poll_fini(ddev); + return ret; +} + +static const struct shmob_drm_config shmob_arm_config = { + .clk_source = SHMOB_DRM_CLK_BUS, + .clk_div = 5, +}; + +static const struct of_device_id shmob_drm_of_table[] __maybe_unused = { + { .compatible = "renesas,r8a7740-lcdc", .data = &shmob_arm_config, }, + { .compatible = "renesas,sh73a0-lcdc", .data = &shmob_arm_config, }, + { /* sentinel */ } +}; + +static struct platform_driver shmob_drm_platform_driver = { + .probe = shmob_drm_probe, + .remove = shmob_drm_remove, + .shutdown = shmob_drm_shutdown, + .driver = { + .name = "shmob-drm", + .of_match_table = of_match_ptr(shmob_drm_of_table), + .pm = &shmob_drm_pm_ops, + }, +}; + +drm_module_platform_driver(shmob_drm_platform_driver); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("Renesas SH Mobile DRM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/renesas/shmobile/shmob_drm_drv.h b/drivers/gpu/drm/renesas/shmobile/shmob_drm_drv.h new file mode 100644 index 000000000..088ac5381 --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/shmob_drm_drv.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * shmob_drm.h -- SH Mobile DRM driver + * + * Copyright (C) 2012 Renesas Electronics Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __SHMOB_DRM_DRV_H__ +#define __SHMOB_DRM_DRV_H__ + +#include <linux/kernel.h> +#include <linux/platform_data/shmob_drm.h> +#include <linux/spinlock.h> + +#include "shmob_drm_crtc.h" + +struct clk; +struct device; +struct drm_device; + +struct shmob_drm_config { + enum shmob_drm_clk_source clk_source; + unsigned int clk_div; +}; + +struct shmob_drm_device { + struct device *dev; + const struct shmob_drm_platform_data *pdata; + struct shmob_drm_config config; + + void __iomem *mmio; + struct clk *clock; + u32 lddckr; + + unsigned int irq; + spinlock_t irq_lock; /* Protects hardware LDINTR register */ + + struct drm_device ddev; + + struct shmob_drm_crtc crtc; + struct drm_encoder encoder; + struct drm_connector *connector; +}; + +static inline struct shmob_drm_device *to_shmob_device(struct drm_device *dev) +{ + return container_of(dev, struct shmob_drm_device, ddev); +} + +#endif /* __SHMOB_DRM_DRV_H__ */ diff --git a/drivers/gpu/drm/renesas/shmobile/shmob_drm_kms.c b/drivers/gpu/drm/renesas/shmobile/shmob_drm_kms.c new file mode 100644 index 000000000..fd9460da1 --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/shmob_drm_kms.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * shmob_drm_kms.c -- SH Mobile DRM Mode Setting + * + * Copyright (C) 2012 Renesas Electronics Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_probe_helper.h> + +#include "shmob_drm_crtc.h" +#include "shmob_drm_drv.h" +#include "shmob_drm_kms.h" +#include "shmob_drm_plane.h" +#include "shmob_drm_regs.h" + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +static const struct shmob_drm_format_info shmob_drm_format_infos[] = { + { + .fourcc = DRM_FORMAT_RGB565, + .bpp = 16, + .lddfr = LDDFR_PKF_RGB16, + .ldddsr = LDDDSR_LS | LDDDSR_WS, + .ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW | + LDBBSIFR_RY | LDBBSIFR_RPKF_RGB16, + }, { + .fourcc = DRM_FORMAT_RGB888, + .bpp = 24, + .lddfr = LDDFR_PKF_RGB24, + .ldddsr = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS, + .ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW | + LDBBSIFR_SWPB | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB24, + }, { + .fourcc = DRM_FORMAT_ARGB8888, + .bpp = 32, + .lddfr = LDDFR_PKF_ARGB32, + .ldddsr = LDDDSR_LS, + .ldbbsifr = LDBBSIFR_AL_PK | LDBBSIFR_SWPL | LDBBSIFR_RY | + LDBBSIFR_RPKF_ARGB32, + }, { + .fourcc = DRM_FORMAT_XRGB8888, + .bpp = 32, + .lddfr = LDDFR_PKF_ARGB32, + .ldddsr = LDDDSR_LS, + .ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_RY | + LDBBSIFR_RPKF_ARGB32, + }, { + .fourcc = DRM_FORMAT_NV12, + .bpp = 12, + .lddfr = LDDFR_CC | LDDFR_YF_420, + .ldddsr = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS, + .ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW | + LDBBSIFR_SWPB | LDBBSIFR_CHRR_420, + }, { + .fourcc = DRM_FORMAT_NV21, + .bpp = 12, + .lddfr = LDDFR_CC | LDDFR_YF_420, + .ldddsr = LDDDSR_LS | LDDDSR_WS, + .ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW | + LDBBSIFR_CHRR_420, + }, { + .fourcc = DRM_FORMAT_NV16, + .bpp = 16, + .lddfr = LDDFR_CC | LDDFR_YF_422, + .ldddsr = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS, + .ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW | + LDBBSIFR_SWPB | LDBBSIFR_CHRR_422, + }, { + .fourcc = DRM_FORMAT_NV61, + .bpp = 16, + .lddfr = LDDFR_CC | LDDFR_YF_422, + .ldddsr = LDDDSR_LS | LDDDSR_WS, + .ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW | + LDBBSIFR_CHRR_422, + }, { + .fourcc = DRM_FORMAT_NV24, + .bpp = 24, + .lddfr = LDDFR_CC | LDDFR_YF_444, + .ldddsr = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS, + .ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW | + LDBBSIFR_SWPB | LDBBSIFR_CHRR_444, + }, { + .fourcc = DRM_FORMAT_NV42, + .bpp = 24, + .lddfr = LDDFR_CC | LDDFR_YF_444, + .ldddsr = LDDDSR_LS | LDDDSR_WS, + .ldbbsifr = LDBBSIFR_AL_1 | LDBBSIFR_SWPL | LDBBSIFR_SWPW | + LDBBSIFR_CHRR_444, + }, +}; + +const struct shmob_drm_format_info *shmob_drm_format_info(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(shmob_drm_format_infos); ++i) { + if (shmob_drm_format_infos[i].fourcc == fourcc) + return &shmob_drm_format_infos[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Frame buffer + */ + +static struct drm_framebuffer * +shmob_drm_fb_create(struct drm_device *dev, struct drm_file *file_priv, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + const struct shmob_drm_format_info *format; + + format = shmob_drm_format_info(mode_cmd->pixel_format); + if (format == NULL) { + dev_dbg(dev->dev, "unsupported pixel format %p4cc\n", + &mode_cmd->pixel_format); + return ERR_PTR(-EINVAL); + } + + if (mode_cmd->pitches[0] & 7 || mode_cmd->pitches[0] >= 65536) { + dev_dbg(dev->dev, "invalid pitch value %u\n", + mode_cmd->pitches[0]); + return ERR_PTR(-EINVAL); + } + + if (shmob_drm_format_is_yuv(format)) { + unsigned int chroma_cpp = format->bpp == 24 ? 2 : 1; + + if (mode_cmd->pitches[1] != mode_cmd->pitches[0] * chroma_cpp) { + dev_dbg(dev->dev, + "luma and chroma pitches do not match\n"); + return ERR_PTR(-EINVAL); + } + } + + return drm_gem_fb_create(dev, file_priv, info, mode_cmd); +} + +static const struct drm_mode_config_funcs shmob_drm_mode_config_funcs = { + .fb_create = shmob_drm_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +int shmob_drm_modeset_init(struct shmob_drm_device *sdev) +{ + struct drm_device *dev = &sdev->ddev; + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + ret = shmob_drm_crtc_create(sdev); + if (ret < 0) + return ret; + + ret = shmob_drm_encoder_create(sdev); + if (ret < 0) + return ret; + + ret = shmob_drm_connector_create(sdev, &sdev->encoder); + if (ret < 0) + return ret; + + drm_mode_config_reset(dev); + + drm_kms_helper_poll_init(dev); + + sdev->ddev.mode_config.min_width = 0; + sdev->ddev.mode_config.min_height = 0; + sdev->ddev.mode_config.max_width = 4095; + sdev->ddev.mode_config.max_height = 4095; + sdev->ddev.mode_config.funcs = &shmob_drm_mode_config_funcs; + + return 0; +} diff --git a/drivers/gpu/drm/renesas/shmobile/shmob_drm_kms.h b/drivers/gpu/drm/renesas/shmobile/shmob_drm_kms.h new file mode 100644 index 000000000..590162c3d --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/shmob_drm_kms.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * shmob_drm_kms.h -- SH Mobile DRM Mode Setting + * + * Copyright (C) 2012 Renesas Electronics Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __SHMOB_DRM_KMS_H__ +#define __SHMOB_DRM_KMS_H__ + +#include <linux/types.h> + +struct drm_gem_dma_object; +struct shmob_drm_device; + +struct shmob_drm_format_info { + u32 fourcc; + u32 lddfr; /* LCD Data Format Register */ + u16 ldbbsifr; /* CHn Source Image Format Register low bits */ + u8 ldddsr; /* LCDC Input Image Data Swap Register low bits */ + u8 bpp; +}; + +#define shmob_drm_format_is_yuv(format) ((format)->lddfr & LDDFR_CC) + +const struct shmob_drm_format_info *shmob_drm_format_info(u32 fourcc); + +int shmob_drm_modeset_init(struct shmob_drm_device *sdev); + +#endif /* __SHMOB_DRM_KMS_H__ */ diff --git a/drivers/gpu/drm/renesas/shmobile/shmob_drm_plane.c b/drivers/gpu/drm/renesas/shmobile/shmob_drm_plane.c new file mode 100644 index 000000000..148de34af --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/shmob_drm_plane.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * shmob_drm_plane.c -- SH Mobile DRM Planes + * + * Copyright (C) 2012 Renesas Electronics Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_dma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_dma_helper.h> + +#include "shmob_drm_drv.h" +#include "shmob_drm_kms.h" +#include "shmob_drm_plane.h" +#include "shmob_drm_regs.h" + +struct shmob_drm_plane { + struct drm_plane base; + unsigned int index; +}; + +struct shmob_drm_plane_state { + struct drm_plane_state base; + + const struct shmob_drm_format_info *format; + u32 dma[2]; +}; + +static inline struct shmob_drm_plane *to_shmob_plane(struct drm_plane *plane) +{ + return container_of(plane, struct shmob_drm_plane, base); +} + +static inline struct shmob_drm_plane_state *to_shmob_plane_state(struct drm_plane_state *state) +{ + return container_of(state, struct shmob_drm_plane_state, base); +} + +static void shmob_drm_plane_compute_base(struct shmob_drm_plane_state *sstate) +{ + struct drm_framebuffer *fb = sstate->base.fb; + unsigned int x = sstate->base.src_x >> 16; + unsigned int y = sstate->base.src_y >> 16; + struct drm_gem_dma_object *gem; + unsigned int bpp; + + bpp = shmob_drm_format_is_yuv(sstate->format) ? 8 : sstate->format->bpp; + gem = drm_fb_dma_get_gem_obj(fb, 0); + sstate->dma[0] = gem->dma_addr + fb->offsets[0] + + y * fb->pitches[0] + x * bpp / 8; + + if (shmob_drm_format_is_yuv(sstate->format)) { + bpp = sstate->format->bpp - 8; + gem = drm_fb_dma_get_gem_obj(fb, 1); + sstate->dma[1] = gem->dma_addr + fb->offsets[1] + + y / (bpp == 4 ? 2 : 1) * fb->pitches[1] + + x * (bpp == 16 ? 2 : 1); + } +} + +static void shmob_drm_primary_plane_setup(struct shmob_drm_plane *splane, + struct drm_plane_state *state) +{ + struct shmob_drm_plane_state *sstate = to_shmob_plane_state(state); + struct shmob_drm_device *sdev = to_shmob_device(splane->base.dev); + struct drm_framebuffer *fb = state->fb; + + /* TODO: Handle YUV colorspaces. Hardcode REC709 for now. */ + lcdc_write(sdev, LDDFR, sstate->format->lddfr | LDDFR_CF1); + lcdc_write(sdev, LDMLSR, fb->pitches[0]); + + /* Word and long word swap. */ + lcdc_write(sdev, LDDDSR, sstate->format->ldddsr); + + lcdc_write_mirror(sdev, LDSA1R, sstate->dma[0]); + if (shmob_drm_format_is_yuv(sstate->format)) + lcdc_write_mirror(sdev, LDSA2R, sstate->dma[1]); + + lcdc_write(sdev, LDRCNTR, lcdc_read(sdev, LDRCNTR) ^ LDRCNTR_MRS); +} + +static void shmob_drm_overlay_plane_setup(struct shmob_drm_plane *splane, + struct drm_plane_state *state) +{ + struct shmob_drm_plane_state *sstate = to_shmob_plane_state(state); + struct shmob_drm_device *sdev = to_shmob_device(splane->base.dev); + struct drm_framebuffer *fb = state->fb; + u32 format; + + /* TODO: Support ROP3 mode */ + format = LDBBSIFR_EN | ((state->alpha >> 8) << LDBBSIFR_LAY_SHIFT) | + sstate->format->ldbbsifr; + +#define plane_reg_dump(sdev, splane, reg) \ + dev_dbg(sdev->ddev.dev, "%s(%u): %s 0x%08x 0x%08x\n", __func__, \ + splane->index, #reg, \ + lcdc_read(sdev, reg(splane->index)), \ + lcdc_read(sdev, reg(splane->index) + LCDC_SIDE_B_OFFSET)) + + plane_reg_dump(sdev, splane, LDBnBSIFR); + plane_reg_dump(sdev, splane, LDBnBSSZR); + plane_reg_dump(sdev, splane, LDBnBLOCR); + plane_reg_dump(sdev, splane, LDBnBSMWR); + plane_reg_dump(sdev, splane, LDBnBSAYR); + plane_reg_dump(sdev, splane, LDBnBSACR); + + lcdc_write(sdev, LDBCR, LDBCR_UPC(splane->index)); + dev_dbg(sdev->ddev.dev, "%s(%u): %s 0x%08x\n", __func__, splane->index, + "LDBCR", lcdc_read(sdev, LDBCR)); + + lcdc_write(sdev, LDBnBSIFR(splane->index), format); + + lcdc_write(sdev, LDBnBSSZR(splane->index), + (state->crtc_h << LDBBSSZR_BVSS_SHIFT) | + (state->crtc_w << LDBBSSZR_BHSS_SHIFT)); + lcdc_write(sdev, LDBnBLOCR(splane->index), + (state->crtc_y << LDBBLOCR_CVLC_SHIFT) | + (state->crtc_x << LDBBLOCR_CHLC_SHIFT)); + lcdc_write(sdev, LDBnBSMWR(splane->index), + fb->pitches[0] << LDBBSMWR_BSMW_SHIFT); + + lcdc_write(sdev, LDBnBSAYR(splane->index), sstate->dma[0]); + if (shmob_drm_format_is_yuv(sstate->format)) + lcdc_write(sdev, LDBnBSACR(splane->index), sstate->dma[1]); + + lcdc_write(sdev, LDBCR, + LDBCR_UPF(splane->index) | LDBCR_UPD(splane->index)); + dev_dbg(sdev->ddev.dev, "%s(%u): %s 0x%08x\n", __func__, splane->index, + "LDBCR", lcdc_read(sdev, LDBCR)); + + plane_reg_dump(sdev, splane, LDBnBSIFR); + plane_reg_dump(sdev, splane, LDBnBSSZR); + plane_reg_dump(sdev, splane, LDBnBLOCR); + plane_reg_dump(sdev, splane, LDBnBSMWR); + plane_reg_dump(sdev, splane, LDBnBSAYR); + plane_reg_dump(sdev, splane, LDBnBSACR); +} + +static int shmob_drm_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct shmob_drm_plane_state *sstate = to_shmob_plane_state(new_plane_state); + struct drm_crtc_state *crtc_state; + bool is_primary = plane->type == DRM_PLANE_TYPE_PRIMARY; + int ret; + + if (!new_plane_state->crtc) { + /* + * The visible field is not reset by the DRM core but only + * updated by drm_atomic_helper_check_plane_state(), set it + * manually. + */ + new_plane_state->visible = false; + sstate->format = NULL; + return 0; + } + + crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + !is_primary, true); + if (ret < 0) + return ret; + + if (!new_plane_state->visible) { + sstate->format = NULL; + return 0; + } + + sstate->format = shmob_drm_format_info(new_plane_state->fb->format->format); + if (!sstate->format) { + dev_dbg(plane->dev->dev, + "plane_atomic_check: unsupported format %p4cc\n", + &new_plane_state->fb->format->format); + return -EINVAL; + } + + shmob_drm_plane_compute_base(sstate); + + return 0; +} + +static void shmob_drm_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct shmob_drm_plane *splane = to_shmob_plane(plane); + + if (!new_plane_state->visible) + return; + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) + shmob_drm_primary_plane_setup(splane, new_plane_state); + else + shmob_drm_overlay_plane_setup(splane, new_plane_state); +} + +static void shmob_drm_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); + struct shmob_drm_device *sdev = to_shmob_device(plane->dev); + struct shmob_drm_plane *splane = to_shmob_plane(plane); + + if (!old_state->crtc) + return; + + if (plane->type != DRM_PLANE_TYPE_OVERLAY) + return; + + lcdc_write(sdev, LDBCR, LDBCR_UPC(splane->index)); + lcdc_write(sdev, LDBnBSIFR(splane->index), 0); + lcdc_write(sdev, LDBCR, + LDBCR_UPF(splane->index) | LDBCR_UPD(splane->index)); +} + +static struct drm_plane_state * +shmob_drm_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct shmob_drm_plane_state *state; + struct shmob_drm_plane_state *copy; + + if (WARN_ON(!plane->state)) + return NULL; + + state = to_shmob_plane_state(plane->state); + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (copy == NULL) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, ©->base); + + return ©->base; +} + +static void shmob_drm_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(to_shmob_plane_state(state)); +} + +static void shmob_drm_plane_reset(struct drm_plane *plane) +{ + struct shmob_drm_plane_state *state; + + if (plane->state) { + shmob_drm_plane_atomic_destroy_state(plane, plane->state); + plane->state = NULL; + } + + state = kzalloc_obj(*state); + if (state == NULL) + return; + + __drm_atomic_helper_plane_reset(plane, &state->base); +} + +static const struct drm_plane_helper_funcs shmob_drm_plane_helper_funcs = { + .atomic_check = shmob_drm_plane_atomic_check, + .atomic_update = shmob_drm_plane_atomic_update, + .atomic_disable = shmob_drm_plane_atomic_disable, +}; + +static const struct drm_plane_helper_funcs shmob_drm_primary_plane_helper_funcs = { + .atomic_check = shmob_drm_plane_atomic_check, + .atomic_update = shmob_drm_plane_atomic_update, + .atomic_disable = shmob_drm_plane_atomic_disable, + .get_scanout_buffer = drm_fb_dma_get_scanout_buffer, +}; + +static const struct drm_plane_funcs shmob_drm_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = shmob_drm_plane_reset, + .atomic_duplicate_state = shmob_drm_plane_atomic_duplicate_state, + .atomic_destroy_state = shmob_drm_plane_atomic_destroy_state, +}; + +static const uint32_t formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV16, + DRM_FORMAT_NV61, + DRM_FORMAT_NV24, + DRM_FORMAT_NV42, +}; + +struct drm_plane *shmob_drm_plane_create(struct shmob_drm_device *sdev, + enum drm_plane_type type, + unsigned int index) +{ + struct shmob_drm_plane *splane; + + splane = drmm_universal_plane_alloc(&sdev->ddev, + struct shmob_drm_plane, base, 1, + &shmob_drm_plane_funcs, formats, + ARRAY_SIZE(formats), NULL, type, + NULL); + if (IS_ERR(splane)) + return ERR_CAST(splane); + + splane->index = index; + + if (type == DRM_PLANE_TYPE_PRIMARY) + drm_plane_helper_add(&splane->base, + &shmob_drm_primary_plane_helper_funcs); + else + drm_plane_helper_add(&splane->base, + &shmob_drm_plane_helper_funcs); + + return &splane->base; +} diff --git a/drivers/gpu/drm/renesas/shmobile/shmob_drm_plane.h b/drivers/gpu/drm/renesas/shmobile/shmob_drm_plane.h new file mode 100644 index 000000000..dcfddd605 --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/shmob_drm_plane.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * shmob_drm_plane.h -- SH Mobile DRM Planes + * + * Copyright (C) 2012 Renesas Electronics Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __SHMOB_DRM_PLANE_H__ +#define __SHMOB_DRM_PLANE_H__ + +struct drm_plane; +struct shmob_drm_device; + +struct drm_plane *shmob_drm_plane_create(struct shmob_drm_device *sdev, + enum drm_plane_type type, + unsigned int index); + +#endif /* __SHMOB_DRM_PLANE_H__ */ diff --git a/drivers/gpu/drm/renesas/shmobile/shmob_drm_regs.h b/drivers/gpu/drm/renesas/shmobile/shmob_drm_regs.h new file mode 100644 index 000000000..058533685 --- /dev/null +++ b/drivers/gpu/drm/renesas/shmobile/shmob_drm_regs.h @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * shmob_drm_regs.h -- SH Mobile DRM registers + * + * Copyright (C) 2012 Renesas Electronics Corporation + * + * Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __SHMOB_DRM_REGS_H__ +#define __SHMOB_DRM_REGS_H__ + +#include <linux/io.h> +#include <linux/jiffies.h> + +#include "shmob_drm_drv.h" + +/* Register definitions */ +#define LDDCKPAT1R 0x400 +#define LDDCKPAT2R 0x404 +#define LDDCKR 0x410 +#define LDDCKR_ICKSEL_BUS (0 << 16) +#define LDDCKR_ICKSEL_MIPI (1 << 16) +#define LDDCKR_ICKSEL_HDMI (2 << 16) +#define LDDCKR_ICKSEL_EXT (3 << 16) +#define LDDCKR_ICKSEL_MASK (7 << 16) +#define LDDCKR_MOSEL (1 << 6) +#define LDDCKSTPR 0x414 +#define LDDCKSTPR_DCKSTS (1 << 16) +#define LDDCKSTPR_DCKSTP (1 << 0) +#define LDMT1R 0x418 +#define LDMT1R_VPOL (1 << 28) +#define LDMT1R_HPOL (1 << 27) +#define LDMT1R_DWPOL (1 << 26) +#define LDMT1R_DIPOL (1 << 25) +#define LDMT1R_DAPOL (1 << 24) +#define LDMT1R_HSCNT (1 << 17) +#define LDMT1R_DWCNT (1 << 16) +#define LDMT1R_IFM (1 << 12) +#define LDMT1R_MIFTYP_RGB8 (0x0 << 0) +#define LDMT1R_MIFTYP_RGB9 (0x4 << 0) +#define LDMT1R_MIFTYP_RGB12A (0x5 << 0) +#define LDMT1R_MIFTYP_RGB12B (0x6 << 0) +#define LDMT1R_MIFTYP_RGB16 (0x7 << 0) +#define LDMT1R_MIFTYP_RGB18 (0xa << 0) +#define LDMT1R_MIFTYP_RGB24 (0xb << 0) +#define LDMT1R_MIFTYP_YCBCR (0xf << 0) +#define LDMT1R_MIFTYP_SYS8A (0x0 << 0) +#define LDMT1R_MIFTYP_SYS8B (0x1 << 0) +#define LDMT1R_MIFTYP_SYS8C (0x2 << 0) +#define LDMT1R_MIFTYP_SYS8D (0x3 << 0) +#define LDMT1R_MIFTYP_SYS9 (0x4 << 0) +#define LDMT1R_MIFTYP_SYS12 (0x5 << 0) +#define LDMT1R_MIFTYP_SYS16A (0x7 << 0) +#define LDMT1R_MIFTYP_SYS16B (0x8 << 0) +#define LDMT1R_MIFTYP_SYS16C (0x9 << 0) +#define LDMT1R_MIFTYP_SYS18 (0xa << 0) +#define LDMT1R_MIFTYP_SYS24 (0xb << 0) +#define LDMT1R_MIFTYP_MASK (0xf << 0) +#define LDMT2R 0x41c +#define LDMT2R_CSUP_MASK (7 << 26) +#define LDMT2R_CSUP_SHIFT 26 +#define LDMT2R_RSV (1 << 25) +#define LDMT2R_VSEL (1 << 24) +#define LDMT2R_WCSC_MASK (0xff << 16) +#define LDMT2R_WCSC_SHIFT 16 +#define LDMT2R_WCEC_MASK (0xff << 8) +#define LDMT2R_WCEC_SHIFT 8 +#define LDMT2R_WCLW_MASK (0xff << 0) +#define LDMT2R_WCLW_SHIFT 0 +#define LDMT3R 0x420 +#define LDMT3R_RDLC_MASK (0x3f << 24) +#define LDMT3R_RDLC_SHIFT 24 +#define LDMT3R_RCSC_MASK (0xff << 16) +#define LDMT3R_RCSC_SHIFT 16 +#define LDMT3R_RCEC_MASK (0xff << 8) +#define LDMT3R_RCEC_SHIFT 8 +#define LDMT3R_RCLW_MASK (0xff << 0) +#define LDMT3R_RCLW_SHIFT 0 +#define LDDFR 0x424 +#define LDDFR_CF1 (1 << 18) +#define LDDFR_CF0 (1 << 17) +#define LDDFR_CC (1 << 16) +#define LDDFR_YF_420 (0 << 8) +#define LDDFR_YF_422 (1 << 8) +#define LDDFR_YF_444 (2 << 8) +#define LDDFR_YF_MASK (3 << 8) +#define LDDFR_PKF_ARGB32 (0x00 << 0) +#define LDDFR_PKF_RGB16 (0x03 << 0) +#define LDDFR_PKF_RGB24 (0x0b << 0) +#define LDDFR_PKF_MASK (0x1f << 0) +#define LDSM1R 0x428 +#define LDSM1R_OS (1 << 0) +#define LDSM2R 0x42c +#define LDSM2R_OSTRG (1 << 0) +#define LDSA1R 0x430 +#define LDSA2R 0x434 +#define LDMLSR 0x438 +#define LDWBFR 0x43c +#define LDWBCNTR 0x440 +#define LDWBAR 0x444 +#define LDHCNR 0x448 +#define LDHSYNR 0x44c +#define LDVLNR 0x450 +#define LDVSYNR 0x454 +#define LDHPDR 0x458 +#define LDVPDR 0x45c +#define LDPMR 0x460 +#define LDPMR_LPS (3 << 0) +#define LDINTR 0x468 +#define LDINTR_FE (1 << 10) +#define LDINTR_VSE (1 << 9) +#define LDINTR_VEE (1 << 8) +#define LDINTR_FS (1 << 2) +#define LDINTR_VSS (1 << 1) +#define LDINTR_VES (1 << 0) +#define LDINTR_STATUS_MASK (0xff << 0) +#define LDSR 0x46c +#define LDSR_MSS (1 << 10) +#define LDSR_MRS (1 << 8) +#define LDSR_AS (1 << 1) +#define LDCNT1R 0x470 +#define LDCNT1R_DE (1 << 0) +#define LDCNT2R 0x474 +#define LDCNT2R_BR (1 << 8) +#define LDCNT2R_MD (1 << 3) +#define LDCNT2R_SE (1 << 2) +#define LDCNT2R_ME (1 << 1) +#define LDCNT2R_DO (1 << 0) +#define LDRCNTR 0x478 +#define LDRCNTR_SRS (1 << 17) +#define LDRCNTR_SRC (1 << 16) +#define LDRCNTR_MRS (1 << 1) +#define LDRCNTR_MRC (1 << 0) +#define LDDDSR 0x47c +#define LDDDSR_LS (1 << 2) +#define LDDDSR_WS (1 << 1) +#define LDDDSR_BS (1 << 0) +#define LDHAJR 0x4a0 + +#define LDDWD0R 0x800 +#define LDDWDxR_WDACT (1 << 28) +#define LDDWDxR_RSW (1 << 24) +#define LDDRDR 0x840 +#define LDDRDR_RSR (1 << 24) +#define LDDRDR_DRD_MASK (0x3ffff << 0) +#define LDDWAR 0x900 +#define LDDWAR_WA (1 << 0) +#define LDDRAR 0x904 +#define LDDRAR_RA (1 << 0) + +#define LDBCR 0xb00 +#define LDBCR_UPC(n) (1 << ((n) + 16)) +#define LDBCR_UPF(n) (1 << ((n) + 8)) +#define LDBCR_UPD(n) (1 << ((n) + 0)) +#define LDBnBSIFR(n) (0xb20 + (n) * 0x20 + 0x00) +#define LDBBSIFR_EN (1 << 31) +#define LDBBSIFR_VS (1 << 29) +#define LDBBSIFR_BRSEL (1 << 28) +#define LDBBSIFR_MX (1 << 27) +#define LDBBSIFR_MY (1 << 26) +#define LDBBSIFR_CV3 (3 << 24) +#define LDBBSIFR_CV2 (2 << 24) +#define LDBBSIFR_CV1 (1 << 24) +#define LDBBSIFR_CV0 (0 << 24) +#define LDBBSIFR_CV_MASK (3 << 24) +#define LDBBSIFR_LAY_MASK (0xff << 16) +#define LDBBSIFR_LAY_SHIFT 16 +#define LDBBSIFR_ROP3_MASK (0xff << 16) +#define LDBBSIFR_ROP3_SHIFT 16 +#define LDBBSIFR_AL_PL8 (3 << 14) +#define LDBBSIFR_AL_PL1 (2 << 14) +#define LDBBSIFR_AL_PK (1 << 14) +#define LDBBSIFR_AL_1 (0 << 14) +#define LDBBSIFR_AL_MASK (3 << 14) +#define LDBBSIFR_SWPL (1 << 10) +#define LDBBSIFR_SWPW (1 << 9) +#define LDBBSIFR_SWPB (1 << 8) +#define LDBBSIFR_RY (1 << 7) +#define LDBBSIFR_CHRR_420 (2 << 0) +#define LDBBSIFR_CHRR_422 (1 << 0) +#define LDBBSIFR_CHRR_444 (0 << 0) +#define LDBBSIFR_RPKF_ARGB32 (0x00 << 0) +#define LDBBSIFR_RPKF_RGB16 (0x03 << 0) +#define LDBBSIFR_RPKF_RGB24 (0x0b << 0) +#define LDBBSIFR_RPKF_MASK (0x1f << 0) +#define LDBnBSSZR(n) (0xb20 + (n) * 0x20 + 0x04) +#define LDBBSSZR_BVSS_MASK (0xfff << 16) +#define LDBBSSZR_BVSS_SHIFT 16 +#define LDBBSSZR_BHSS_MASK (0xfff << 0) +#define LDBBSSZR_BHSS_SHIFT 0 +#define LDBnBLOCR(n) (0xb20 + (n) * 0x20 + 0x08) +#define LDBBLOCR_CVLC_MASK (0xfff << 16) +#define LDBBLOCR_CVLC_SHIFT 16 +#define LDBBLOCR_CHLC_MASK (0xfff << 0) +#define LDBBLOCR_CHLC_SHIFT 0 +#define LDBnBSMWR(n) (0xb20 + (n) * 0x20 + 0x0c) +#define LDBBSMWR_BSMWA_MASK (0xffff << 16) +#define LDBBSMWR_BSMWA_SHIFT 16 +#define LDBBSMWR_BSMW_MASK (0xffff << 0) +#define LDBBSMWR_BSMW_SHIFT 0 +#define LDBnBSAYR(n) (0xb20 + (n) * 0x20 + 0x10) +#define LDBBSAYR_FG1A_MASK (0xff << 24) +#define LDBBSAYR_FG1A_SHIFT 24 +#define LDBBSAYR_FG1R_MASK (0xff << 16) +#define LDBBSAYR_FG1R_SHIFT 16 +#define LDBBSAYR_FG1G_MASK (0xff << 8) +#define LDBBSAYR_FG1G_SHIFT 8 +#define LDBBSAYR_FG1B_MASK (0xff << 0) +#define LDBBSAYR_FG1B_SHIFT 0 +#define LDBnBSACR(n) (0xb20 + (n) * 0x20 + 0x14) +#define LDBBSACR_FG2A_MASK (0xff << 24) +#define LDBBSACR_FG2A_SHIFT 24 +#define LDBBSACR_FG2R_MASK (0xff << 16) +#define LDBBSACR_FG2R_SHIFT 16 +#define LDBBSACR_FG2G_MASK (0xff << 8) +#define LDBBSACR_FG2G_SHIFT 8 +#define LDBBSACR_FG2B_MASK (0xff << 0) +#define LDBBSACR_FG2B_SHIFT 0 +#define LDBnBSAAR(n) (0xb20 + (n) * 0x20 + 0x18) +#define LDBBSAAR_AP_MASK (0xff << 24) +#define LDBBSAAR_AP_SHIFT 24 +#define LDBBSAAR_R_MASK (0xff << 16) +#define LDBBSAAR_R_SHIFT 16 +#define LDBBSAAR_GY_MASK (0xff << 8) +#define LDBBSAAR_GY_SHIFT 8 +#define LDBBSAAR_B_MASK (0xff << 0) +#define LDBBSAAR_B_SHIFT 0 +#define LDBnBPPCR(n) (0xb20 + (n) * 0x20 + 0x1c) +#define LDBBPPCR_AP_MASK (0xff << 24) +#define LDBBPPCR_AP_SHIFT 24 +#define LDBBPPCR_R_MASK (0xff << 16) +#define LDBBPPCR_R_SHIFT 16 +#define LDBBPPCR_GY_MASK (0xff << 8) +#define LDBBPPCR_GY_SHIFT 8 +#define LDBBPPCR_B_MASK (0xff << 0) +#define LDBBPPCR_B_SHIFT 0 +#define LDBnBBGCL(n) (0xb10 + (n) * 0x04) +#define LDBBBGCL_BGA_MASK (0xff << 24) +#define LDBBBGCL_BGA_SHIFT 24 +#define LDBBBGCL_BGR_MASK (0xff << 16) +#define LDBBBGCL_BGR_SHIFT 16 +#define LDBBBGCL_BGG_MASK (0xff << 8) +#define LDBBBGCL_BGG_SHIFT 8 +#define LDBBBGCL_BGB_MASK (0xff << 0) +#define LDBBBGCL_BGB_SHIFT 0 + +#define LCDC_SIDE_B_OFFSET 0x1000 +#define LCDC_MIRROR_OFFSET 0x2000 + +static inline bool lcdc_is_banked(u32 reg) +{ + switch (reg) { + case LDMT1R: + case LDMT2R: + case LDMT3R: + case LDDFR: + case LDSM1R: + case LDSA1R: + case LDSA2R: + case LDMLSR: + case LDWBFR: + case LDWBCNTR: + case LDWBAR: + case LDHCNR: + case LDHSYNR: + case LDVLNR: + case LDVSYNR: + case LDHPDR: + case LDVPDR: + case LDHAJR: + return true; + default: + return reg >= LDBnBBGCL(0) && reg <= LDBnBPPCR(3); + } +} + +static inline void lcdc_write_mirror(struct shmob_drm_device *sdev, u32 reg, + u32 data) +{ + iowrite32(data, sdev->mmio + reg + LCDC_MIRROR_OFFSET); +} + +static inline void lcdc_write(struct shmob_drm_device *sdev, u32 reg, u32 data) +{ + iowrite32(data, sdev->mmio + reg); + if (lcdc_is_banked(reg)) + iowrite32(data, sdev->mmio + reg + LCDC_SIDE_B_OFFSET); +} + +static inline u32 lcdc_read(struct shmob_drm_device *sdev, u32 reg) +{ + return ioread32(sdev->mmio + reg); +} + +static inline int lcdc_wait_bit(struct shmob_drm_device *sdev, u32 reg, + u32 mask, u32 until) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(5); + + while ((lcdc_read(sdev, reg) & mask) != until) { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + cpu_relax(); + } + + return 0; +} + +#endif /* __SHMOB_DRM_REGS_H__ */ |
