diff options
| author | Adrian Betschart <adrian.betschart@cinemaone.ch> | 2026-09-11 17:13:14 +0200 |
|---|---|---|
| committer | Alex Deucher <alexander.deucher@amd.com> | 2026-10-01 17:18:05 -0400 |
| commit | 106b4a2a78b2a2f14885604891957b84bba502db (patch) | |
| tree | 5d40eef2428fb1a4791ab46a4b19091a5910eac2 /drivers/gpu/drm/mcde | |
| download | linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.tar.gz linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.zip | |
drm/amd/display: Try RGB before YCbCr 4:4:4 in stream validationgrafted
amdgpu_dm_create_validate_stream_for_sink() walks encoding_order[] and
uses the first encoding that validates. YCbCr 4:4:4 is listed before
RGB, so an HDMI sink that advertises 4:4:4 gets YCbCr 4:4:4 whenever the
"color format" property is left at AUTO, even though RGB fits the same
link.
That contradicts the documented AUTO behaviour for HDMI in enum
drm_connector_color_format (RGB, falling back to YCbCr 4:2:0 only when
the bandwidth is not available or the mode is 4:2:0-only), which the
amdgpu implementation of the property also describes. It also leaves
the "Broadcast RGB" property without effect on such sinks, since the
quantization range it selects only applies to RGB output.
Try RGB first. The mask still holds every encoding the sink supports,
so a mode that cannot carry RGB falls back exactly as before.
For reference, v7.2 picked RGB here unless YCbCr 4:4:4 was forced
through debugfs, while earlier kernels picked YCbCr 4:4:4 for any HDMI
sink that advertised it.
Fixes: 0b0ff65d3ca1 ("drm/amd/display: Refactor stream validation")
Suggested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Assisted-by: Claude Code:claude-fable-5-1
Signed-off-by: Adrian Betschart <adrian.betschart@cinemaone.ch>
Reviewed-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Tested-by: Daniel Wheeler <daniel.wheeler@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(cherry picked from commit 7de432bc753133764dc40d37f9388da56de251e7)
Diffstat (limited to 'drivers/gpu/drm/mcde')
| -rw-r--r-- | drivers/gpu/drm/mcde/Kconfig | 18 | ||||
| -rw-r--r-- | drivers/gpu/drm/mcde/Makefile | 3 | ||||
| -rw-r--r-- | drivers/gpu/drm/mcde/mcde_clk_div.c | 196 | ||||
| -rw-r--r-- | drivers/gpu/drm/mcde/mcde_display.c | 1523 | ||||
| -rw-r--r-- | drivers/gpu/drm/mcde/mcde_display_regs.h | 605 | ||||
| -rw-r--r-- | drivers/gpu/drm/mcde/mcde_drm.h | 118 | ||||
| -rw-r--r-- | drivers/gpu/drm/mcde/mcde_drv.c | 522 | ||||
| -rw-r--r-- | drivers/gpu/drm/mcde/mcde_dsi.c | 1242 | ||||
| -rw-r--r-- | drivers/gpu/drm/mcde/mcde_dsi_regs.h | 385 |
9 files changed, 4612 insertions, 0 deletions
diff --git a/drivers/gpu/drm/mcde/Kconfig b/drivers/gpu/drm/mcde/Kconfig new file mode 100644 index 000000000..3516c8d2a --- /dev/null +++ b/drivers/gpu/drm/mcde/Kconfig @@ -0,0 +1,18 @@ +config DRM_MCDE + tristate "DRM Support for ST-Ericsson MCDE (Multichannel Display Engine)" + depends on DRM + depends on CMA + depends on ARM || COMPILE_TEST + depends on OF + depends on COMMON_CLK + select MFD_SYSCON + select DRM_CLIENT_SELECTION + select DRM_MIPI_DSI + select DRM_BRIDGE + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_GEM_DMA_HELPER + help + Choose this option for DRM support for the ST-Ericsson MCDE + Multi-Channel Display Engine. + If M is selected the module will be called mcde_drm. diff --git a/drivers/gpu/drm/mcde/Makefile b/drivers/gpu/drm/mcde/Makefile new file mode 100644 index 000000000..15d9c89a3 --- /dev/null +++ b/drivers/gpu/drm/mcde/Makefile @@ -0,0 +1,3 @@ +mcde_drm-y += mcde_drv.o mcde_dsi.o mcde_clk_div.o mcde_display.o + +obj-$(CONFIG_DRM_MCDE) += mcde_drm.o diff --git a/drivers/gpu/drm/mcde/mcde_clk_div.c b/drivers/gpu/drm/mcde/mcde_clk_div.c new file mode 100644 index 000000000..8c5af2677 --- /dev/null +++ b/drivers/gpu/drm/mcde/mcde_clk_div.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/regulator/consumer.h> + +#include "mcde_drm.h" +#include "mcde_display_regs.h" + +/* The MCDE internal clock dividers for FIFO A and B */ +struct mcde_clk_div { + struct clk_hw hw; + struct mcde *mcde; + u32 cr; + u32 cr_div; +}; + +static int mcde_clk_div_enable(struct clk_hw *hw) +{ + struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw); + struct mcde *mcde = cdiv->mcde; + u32 val; + + spin_lock(&mcde->fifo_crx1_lock); + val = readl(mcde->regs + cdiv->cr); + /* + * Select the PLL72 (LCD) clock as parent + * FIXME: implement other parents. + */ + val &= ~MCDE_CRX1_CLKSEL_MASK; + val |= MCDE_CRX1_CLKSEL_CLKPLL72 << MCDE_CRX1_CLKSEL_SHIFT; + /* Internal clock */ + val |= MCDE_CRA1_CLKTYPE_TVXCLKSEL1; + + /* Clear then set the divider */ + val &= ~(MCDE_CRX1_BCD | MCDE_CRX1_PCD_MASK); + val |= cdiv->cr_div; + + writel(val, mcde->regs + cdiv->cr); + spin_unlock(&mcde->fifo_crx1_lock); + + return 0; +} + +static int mcde_clk_div_choose_div(struct clk_hw *hw, unsigned long rate, + unsigned long *prate, bool set_parent) +{ + int best_div = 1, div; + struct clk_hw *parent = clk_hw_get_parent(hw); + unsigned long best_prate = 0; + unsigned long best_diff = ~0ul; + int max_div = (1 << MCDE_CRX1_PCD_BITS) - 1; + + for (div = 1; div < max_div; div++) { + unsigned long this_prate, div_rate, diff; + + if (set_parent) + this_prate = clk_hw_round_rate(parent, rate * div); + else + this_prate = *prate; + div_rate = DIV_ROUND_UP_ULL(this_prate, div); + diff = abs(rate - div_rate); + + if (diff < best_diff) { + best_div = div; + best_diff = diff; + best_prate = this_prate; + } + } + + *prate = best_prate; + return best_div; +} + +static int mcde_clk_div_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + int div = mcde_clk_div_choose_div(hw, req->rate, + &req->best_parent_rate, true); + + req->rate = DIV_ROUND_UP_ULL(req->best_parent_rate, div); + + return 0; +} + +static unsigned long mcde_clk_div_recalc_rate(struct clk_hw *hw, + unsigned long prate) +{ + struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw); + struct mcde *mcde = cdiv->mcde; + u32 cr; + int div; + + /* + * If the MCDE is not powered we can't access registers. + * It will come up with 0 in the divider register bits, which + * means "divide by 2". + */ + if (!regulator_is_enabled(mcde->epod)) + return DIV_ROUND_UP_ULL(prate, 2); + + cr = readl(mcde->regs + cdiv->cr); + if (cr & MCDE_CRX1_BCD) + return prate; + + /* 0 in the PCD means "divide by 2", 1 means "divide by 3" etc */ + div = cr & MCDE_CRX1_PCD_MASK; + div += 2; + + return DIV_ROUND_UP_ULL(prate, div); +} + +static int mcde_clk_div_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long prate) +{ + struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw); + int div = mcde_clk_div_choose_div(hw, rate, &prate, false); + u32 cr = 0; + + /* + * We cache the CR bits to set the divide in the state so that + * we can call this before we can even write to the hardware. + */ + if (div == 1) { + /* Bypass clock divider */ + cr |= MCDE_CRX1_BCD; + } else { + div -= 2; + cr |= div & MCDE_CRX1_PCD_MASK; + } + cdiv->cr_div = cr; + + return 0; +} + +static const struct clk_ops mcde_clk_div_ops = { + .enable = mcde_clk_div_enable, + .recalc_rate = mcde_clk_div_recalc_rate, + .determine_rate = mcde_clk_div_determine_rate, + .set_rate = mcde_clk_div_set_rate, +}; + +int mcde_init_clock_divider(struct mcde *mcde) +{ + struct device *dev = mcde->dev; + struct mcde_clk_div *fifoa; + struct mcde_clk_div *fifob; + const char *parent_name; + struct clk_init_data fifoa_init = { + .name = "fifoa", + .ops = &mcde_clk_div_ops, + .parent_names = &parent_name, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }; + struct clk_init_data fifob_init = { + .name = "fifob", + .ops = &mcde_clk_div_ops, + .parent_names = &parent_name, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }; + int ret; + + spin_lock_init(&mcde->fifo_crx1_lock); + parent_name = __clk_get_name(mcde->lcd_clk); + + /* Allocate 2 clocks */ + fifoa = devm_kzalloc(dev, sizeof(*fifoa), GFP_KERNEL); + if (!fifoa) + return -ENOMEM; + fifob = devm_kzalloc(dev, sizeof(*fifob), GFP_KERNEL); + if (!fifob) + return -ENOMEM; + + fifoa->mcde = mcde; + fifoa->cr = MCDE_CRA1; + fifoa->hw.init = &fifoa_init; + ret = devm_clk_hw_register(dev, &fifoa->hw); + if (ret) { + dev_err(dev, "error registering FIFO A clock divider\n"); + return ret; + } + mcde->fifoa_clk = fifoa->hw.clk; + + fifob->mcde = mcde; + fifob->cr = MCDE_CRB1; + fifob->hw.init = &fifob_init; + ret = devm_clk_hw_register(dev, &fifob->hw); + if (ret) { + dev_err(dev, "error registering FIFO B clock divider\n"); + return ret; + } + mcde->fifob_clk = fifob->hw.clk; + + return 0; +} diff --git a/drivers/gpu/drm/mcde/mcde_display.c b/drivers/gpu/drm/mcde/mcde_display.c new file mode 100644 index 000000000..257a6e84d --- /dev/null +++ b/drivers/gpu/drm/mcde/mcde_display.c @@ -0,0 +1,1523 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Linus Walleij <linus.walleij@linaro.org> + * Parts of this file were based on the MCDE driver by Marcus Lorentzon + * (C) ST-Ericsson SA 2013 + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-buf.h> +#include <linux/regulator/consumer.h> +#include <linux/media-bus-format.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_atomic_helper.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_print.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_vblank.h> +#include <video/mipi_display.h> + +#include "mcde_drm.h" +#include "mcde_display_regs.h" + +enum mcde_fifo { + MCDE_FIFO_A, + MCDE_FIFO_B, + /* TODO: implement FIFO C0 and FIFO C1 */ +}; + +enum mcde_channel { + MCDE_CHANNEL_0 = 0, + MCDE_CHANNEL_1, + MCDE_CHANNEL_2, + MCDE_CHANNEL_3, +}; + +enum mcde_extsrc { + MCDE_EXTSRC_0 = 0, + MCDE_EXTSRC_1, + MCDE_EXTSRC_2, + MCDE_EXTSRC_3, + MCDE_EXTSRC_4, + MCDE_EXTSRC_5, + MCDE_EXTSRC_6, + MCDE_EXTSRC_7, + MCDE_EXTSRC_8, + MCDE_EXTSRC_9, +}; + +enum mcde_overlay { + MCDE_OVERLAY_0 = 0, + MCDE_OVERLAY_1, + MCDE_OVERLAY_2, + MCDE_OVERLAY_3, + MCDE_OVERLAY_4, + MCDE_OVERLAY_5, +}; + +enum mcde_formatter { + MCDE_DSI_FORMATTER_0 = 0, + MCDE_DSI_FORMATTER_1, + MCDE_DSI_FORMATTER_2, + MCDE_DSI_FORMATTER_3, + MCDE_DSI_FORMATTER_4, + MCDE_DSI_FORMATTER_5, + MCDE_DPI_FORMATTER_0, + MCDE_DPI_FORMATTER_1, +}; + +void mcde_display_irq(struct mcde *mcde) +{ + u32 mispp, misovl, mischnl; + bool vblank = false; + + /* Handle display IRQs */ + mispp = readl(mcde->regs + MCDE_MISPP); + misovl = readl(mcde->regs + MCDE_MISOVL); + mischnl = readl(mcde->regs + MCDE_MISCHNL); + + /* + * Handle IRQs from the DSI link. All IRQs from the DSI links + * are just latched onto the MCDE IRQ line, so we need to traverse + * any active DSI masters and check if an IRQ is originating from + * them. + * + * TODO: Currently only one DSI link is supported. + */ + if (!mcde->dpi_output && mcde_dsi_irq(mcde->mdsi)) { + u32 val; + + /* + * In oneshot mode we do not send continuous updates + * to the display, instead we only push out updates when + * the update function is called, then we disable the + * flow on the channel once we get the TE IRQ. + */ + if (mcde->flow_mode == MCDE_COMMAND_ONESHOT_FLOW) { + spin_lock(&mcde->flow_lock); + if (--mcde->flow_active == 0) { + dev_dbg(mcde->dev, "TE0 IRQ\n"); + /* Disable FIFO A flow */ + val = readl(mcde->regs + MCDE_CRA0); + val &= ~MCDE_CRX0_FLOEN; + writel(val, mcde->regs + MCDE_CRA0); + } + spin_unlock(&mcde->flow_lock); + } + } + + /* Vblank from one of the channels */ + if (mispp & MCDE_PP_VCMPA) { + dev_dbg(mcde->dev, "chnl A vblank IRQ\n"); + vblank = true; + } + if (mispp & MCDE_PP_VCMPB) { + dev_dbg(mcde->dev, "chnl B vblank IRQ\n"); + vblank = true; + } + if (mispp & MCDE_PP_VCMPC0) + dev_dbg(mcde->dev, "chnl C0 vblank IRQ\n"); + if (mispp & MCDE_PP_VCMPC1) + dev_dbg(mcde->dev, "chnl C1 vblank IRQ\n"); + if (mispp & MCDE_PP_VSCC0) + dev_dbg(mcde->dev, "chnl C0 TE IRQ\n"); + if (mispp & MCDE_PP_VSCC1) + dev_dbg(mcde->dev, "chnl C1 TE IRQ\n"); + writel(mispp, mcde->regs + MCDE_RISPP); + + if (vblank) + drm_crtc_handle_vblank(&mcde->pipe.crtc); + + if (misovl) + dev_info(mcde->dev, "some stray overlay IRQ %08x\n", misovl); + writel(misovl, mcde->regs + MCDE_RISOVL); + + if (mischnl) + dev_info(mcde->dev, "some stray channel error IRQ %08x\n", + mischnl); + writel(mischnl, mcde->regs + MCDE_RISCHNL); +} + +void mcde_display_disable_irqs(struct mcde *mcde) +{ + /* Disable all IRQs */ + writel(0, mcde->regs + MCDE_IMSCPP); + writel(0, mcde->regs + MCDE_IMSCOVL); + writel(0, mcde->regs + MCDE_IMSCCHNL); + + /* Clear any pending IRQs */ + writel(0xFFFFFFFF, mcde->regs + MCDE_RISPP); + writel(0xFFFFFFFF, mcde->regs + MCDE_RISOVL); + writel(0xFFFFFFFF, mcde->regs + MCDE_RISCHNL); +} + +static int mcde_display_check(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *pstate, + struct drm_crtc_state *cstate) +{ + const struct drm_display_mode *mode = &cstate->mode; + struct drm_framebuffer *old_fb = pipe->plane.state->fb; + struct drm_framebuffer *fb = pstate->fb; + + if (fb) { + u32 offset = drm_fb_dma_get_gem_addr(fb, pstate, 0); + + /* FB base address must be dword aligned. */ + if (offset & 3) { + DRM_DEBUG_KMS("FB not 32-bit aligned\n"); + return -EINVAL; + } + + /* + * There's no pitch register, the mode's hdisplay + * controls this. + */ + if (fb->pitches[0] != mode->hdisplay * fb->format->cpp[0]) { + DRM_DEBUG_KMS("can't handle pitches\n"); + return -EINVAL; + } + + /* + * We can't change the FB format in a flicker-free + * manner (and only update it during CRTC enable). + */ + if (old_fb && old_fb->format != fb->format) + cstate->mode_changed = true; + } + + return 0; +} + +static int mcde_configure_extsrc(struct mcde *mcde, enum mcde_extsrc src, + u32 format) +{ + u32 val; + u32 conf; + u32 cr; + + switch (src) { + case MCDE_EXTSRC_0: + conf = MCDE_EXTSRC0CONF; + cr = MCDE_EXTSRC0CR; + break; + case MCDE_EXTSRC_1: + conf = MCDE_EXTSRC1CONF; + cr = MCDE_EXTSRC1CR; + break; + case MCDE_EXTSRC_2: + conf = MCDE_EXTSRC2CONF; + cr = MCDE_EXTSRC2CR; + break; + case MCDE_EXTSRC_3: + conf = MCDE_EXTSRC3CONF; + cr = MCDE_EXTSRC3CR; + break; + case MCDE_EXTSRC_4: + conf = MCDE_EXTSRC4CONF; + cr = MCDE_EXTSRC4CR; + break; + case MCDE_EXTSRC_5: + conf = MCDE_EXTSRC5CONF; + cr = MCDE_EXTSRC5CR; + break; + case MCDE_EXTSRC_6: + conf = MCDE_EXTSRC6CONF; + cr = MCDE_EXTSRC6CR; + break; + case MCDE_EXTSRC_7: + conf = MCDE_EXTSRC7CONF; + cr = MCDE_EXTSRC7CR; + break; + case MCDE_EXTSRC_8: + conf = MCDE_EXTSRC8CONF; + cr = MCDE_EXTSRC8CR; + break; + case MCDE_EXTSRC_9: + conf = MCDE_EXTSRC9CONF; + cr = MCDE_EXTSRC9CR; + break; + } + + /* + * Configure external source 0 one buffer (buffer 0) + * primary overlay ID 0. + * From mcde_hw.c ovly_update_registers() in the vendor tree + */ + val = 0 << MCDE_EXTSRCXCONF_BUF_ID_SHIFT; + val |= 1 << MCDE_EXTSRCXCONF_BUF_NB_SHIFT; + val |= 0 << MCDE_EXTSRCXCONF_PRI_OVLID_SHIFT; + + switch (format) { + case DRM_FORMAT_ARGB8888: + val |= MCDE_EXTSRCXCONF_BPP_ARGB8888 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + break; + case DRM_FORMAT_ABGR8888: + val |= MCDE_EXTSRCXCONF_BPP_ARGB8888 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + val |= MCDE_EXTSRCXCONF_BGR; + break; + case DRM_FORMAT_XRGB8888: + val |= MCDE_EXTSRCXCONF_BPP_XRGB8888 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + break; + case DRM_FORMAT_XBGR8888: + val |= MCDE_EXTSRCXCONF_BPP_XRGB8888 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + val |= MCDE_EXTSRCXCONF_BGR; + break; + case DRM_FORMAT_RGB888: + val |= MCDE_EXTSRCXCONF_BPP_RGB888 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + break; + case DRM_FORMAT_BGR888: + val |= MCDE_EXTSRCXCONF_BPP_RGB888 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + val |= MCDE_EXTSRCXCONF_BGR; + break; + case DRM_FORMAT_ARGB4444: + val |= MCDE_EXTSRCXCONF_BPP_ARGB4444 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + break; + case DRM_FORMAT_ABGR4444: + val |= MCDE_EXTSRCXCONF_BPP_ARGB4444 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + val |= MCDE_EXTSRCXCONF_BGR; + break; + case DRM_FORMAT_XRGB4444: + val |= MCDE_EXTSRCXCONF_BPP_RGB444 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + break; + case DRM_FORMAT_XBGR4444: + val |= MCDE_EXTSRCXCONF_BPP_RGB444 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + val |= MCDE_EXTSRCXCONF_BGR; + break; + case DRM_FORMAT_XRGB1555: + val |= MCDE_EXTSRCXCONF_BPP_IRGB1555 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + break; + case DRM_FORMAT_XBGR1555: + val |= MCDE_EXTSRCXCONF_BPP_IRGB1555 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + val |= MCDE_EXTSRCXCONF_BGR; + break; + case DRM_FORMAT_RGB565: + val |= MCDE_EXTSRCXCONF_BPP_RGB565 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + break; + case DRM_FORMAT_BGR565: + val |= MCDE_EXTSRCXCONF_BPP_RGB565 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + val |= MCDE_EXTSRCXCONF_BGR; + break; + case DRM_FORMAT_YUV422: + val |= MCDE_EXTSRCXCONF_BPP_YCBCR422 << + MCDE_EXTSRCXCONF_BPP_SHIFT; + break; + default: + dev_err(mcde->dev, "Unknown pixel format 0x%08x\n", + format); + return -EINVAL; + } + writel(val, mcde->regs + conf); + + /* Software select, primary */ + val = MCDE_EXTSRCXCR_SEL_MOD_SOFTWARE_SEL; + val |= MCDE_EXTSRCXCR_MULTIOVL_CTRL_PRIMARY; + writel(val, mcde->regs + cr); + + return 0; +} + +static void mcde_configure_overlay(struct mcde *mcde, enum mcde_overlay ovl, + enum mcde_extsrc src, + enum mcde_channel ch, + const struct drm_display_mode *mode, + u32 format, int cpp) +{ + u32 val; + u32 conf1; + u32 conf2; + u32 crop; + u32 ljinc; + u32 cr; + u32 comp; + u32 pixel_fetcher_watermark; + + switch (ovl) { + case MCDE_OVERLAY_0: + conf1 = MCDE_OVL0CONF; + conf2 = MCDE_OVL0CONF2; + crop = MCDE_OVL0CROP; + ljinc = MCDE_OVL0LJINC; + cr = MCDE_OVL0CR; + comp = MCDE_OVL0COMP; + break; + case MCDE_OVERLAY_1: + conf1 = MCDE_OVL1CONF; + conf2 = MCDE_OVL1CONF2; + crop = MCDE_OVL1CROP; + ljinc = MCDE_OVL1LJINC; + cr = MCDE_OVL1CR; + comp = MCDE_OVL1COMP; + break; + case MCDE_OVERLAY_2: + conf1 = MCDE_OVL2CONF; + conf2 = MCDE_OVL2CONF2; + crop = MCDE_OVL2CROP; + ljinc = MCDE_OVL2LJINC; + cr = MCDE_OVL2CR; + comp = MCDE_OVL2COMP; + break; + case MCDE_OVERLAY_3: + conf1 = MCDE_OVL3CONF; + conf2 = MCDE_OVL3CONF2; + crop = MCDE_OVL3CROP; + ljinc = MCDE_OVL3LJINC; + cr = MCDE_OVL3CR; + comp = MCDE_OVL3COMP; + break; + case MCDE_OVERLAY_4: + conf1 = MCDE_OVL4CONF; + conf2 = MCDE_OVL4CONF2; + crop = MCDE_OVL4CROP; + ljinc = MCDE_OVL4LJINC; + cr = MCDE_OVL4CR; + comp = MCDE_OVL4COMP; + break; + case MCDE_OVERLAY_5: + conf1 = MCDE_OVL5CONF; + conf2 = MCDE_OVL5CONF2; + crop = MCDE_OVL5CROP; + ljinc = MCDE_OVL5LJINC; + cr = MCDE_OVL5CR; + comp = MCDE_OVL5COMP; + break; + } + + val = mode->hdisplay << MCDE_OVLXCONF_PPL_SHIFT; + val |= mode->vdisplay << MCDE_OVLXCONF_LPF_SHIFT; + /* Use external source 0 that we just configured */ + val |= src << MCDE_OVLXCONF_EXTSRC_ID_SHIFT; + writel(val, mcde->regs + conf1); + + val = MCDE_OVLXCONF2_BP_PER_PIXEL_ALPHA; + val |= 0xff << MCDE_OVLXCONF2_ALPHAVALUE_SHIFT; + /* OPQ: overlay is opaque */ + switch (format) { + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XBGR1555: + /* No OPQ */ + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_YUV422: + val |= MCDE_OVLXCONF2_OPQ; + break; + default: + dev_err(mcde->dev, "Unknown pixel format 0x%08x\n", + format); + break; + } + + /* + * Pixel fetch watermark level is max 0x1FFF pixels. + * Two basic rules should be followed: + * 1. The value should be at least 256 bits. + * 2. The sum of all active overlays pixelfetch watermark level + * multiplied with bits per pixel, should be lower than the + * size of input_fifo_size in bits. + * 3. The value should be a multiple of a line (256 bits). + */ + switch (cpp) { + case 2: + pixel_fetcher_watermark = 128; + break; + case 3: + pixel_fetcher_watermark = 96; + break; + case 4: + pixel_fetcher_watermark = 48; + break; + default: + pixel_fetcher_watermark = 48; + break; + } + dev_dbg(mcde->dev, "pixel fetcher watermark level %d pixels\n", + pixel_fetcher_watermark); + val |= pixel_fetcher_watermark << MCDE_OVLXCONF2_PIXELFETCHERWATERMARKLEVEL_SHIFT; + writel(val, mcde->regs + conf2); + + /* Number of bytes to fetch per line */ + writel(mcde->stride, mcde->regs + ljinc); + /* No cropping */ + writel(0, mcde->regs + crop); + + /* Set up overlay control register */ + val = MCDE_OVLXCR_OVLEN; + val |= MCDE_OVLXCR_COLCCTRL_DISABLED; + val |= MCDE_OVLXCR_BURSTSIZE_8W << + MCDE_OVLXCR_BURSTSIZE_SHIFT; + val |= MCDE_OVLXCR_MAXOUTSTANDING_8_REQ << + MCDE_OVLXCR_MAXOUTSTANDING_SHIFT; + /* Not using rotation but set it up anyways */ + val |= MCDE_OVLXCR_ROTBURSTSIZE_8W << + MCDE_OVLXCR_ROTBURSTSIZE_SHIFT; + writel(val, mcde->regs + cr); + + /* + * Set up the overlay compositor to route the overlay out to + * the desired channel + */ + val = ch << MCDE_OVLXCOMP_CH_ID_SHIFT; + writel(val, mcde->regs + comp); +} + +static void mcde_configure_channel(struct mcde *mcde, enum mcde_channel ch, + enum mcde_fifo fifo, + const struct drm_display_mode *mode) +{ + u32 val; + u32 conf; + u32 sync; + u32 stat; + u32 bgcol; + u32 mux; + + switch (ch) { + case MCDE_CHANNEL_0: + conf = MCDE_CHNL0CONF; + sync = MCDE_CHNL0SYNCHMOD; + stat = MCDE_CHNL0STAT; + bgcol = MCDE_CHNL0BCKGNDCOL; + mux = MCDE_CHNL0MUXING; + break; + case MCDE_CHANNEL_1: + conf = MCDE_CHNL1CONF; + sync = MCDE_CHNL1SYNCHMOD; + stat = MCDE_CHNL1STAT; + bgcol = MCDE_CHNL1BCKGNDCOL; + mux = MCDE_CHNL1MUXING; + break; + case MCDE_CHANNEL_2: + conf = MCDE_CHNL2CONF; + sync = MCDE_CHNL2SYNCHMOD; + stat = MCDE_CHNL2STAT; + bgcol = MCDE_CHNL2BCKGNDCOL; + mux = MCDE_CHNL2MUXING; + break; + case MCDE_CHANNEL_3: + conf = MCDE_CHNL3CONF; + sync = MCDE_CHNL3SYNCHMOD; + stat = MCDE_CHNL3STAT; + bgcol = MCDE_CHNL3BCKGNDCOL; + mux = MCDE_CHNL3MUXING; + return; + } + + /* Set up channel 0 sync (based on chnl_update_registers()) */ + switch (mcde->flow_mode) { + case MCDE_COMMAND_ONESHOT_FLOW: + /* Oneshot is achieved with software sync */ + val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SOFTWARE + << MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT; + break; + case MCDE_COMMAND_TE_FLOW: + val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_HARDWARE + << MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT; + val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE0 + << MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT; + break; + case MCDE_COMMAND_BTA_TE_FLOW: + val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_HARDWARE + << MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT; + /* + * TODO: + * The vendor driver uses the formatter as sync source + * for BTA TE mode. Test to use TE if you have a panel + * that uses this mode. + */ + val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_FORMATTER + << MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT; + break; + case MCDE_VIDEO_TE_FLOW: + val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_HARDWARE + << MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT; + val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE0 + << MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT; + break; + case MCDE_VIDEO_FORMATTER_FLOW: + case MCDE_DPI_FORMATTER_FLOW: + val = MCDE_CHNLXSYNCHMOD_SRC_SYNCH_HARDWARE + << MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT; + val |= MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_FORMATTER + << MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT; + break; + default: + dev_err(mcde->dev, "unknown flow mode %d\n", + mcde->flow_mode); + return; + } + + writel(val, mcde->regs + sync); + + /* Set up pixels per line and lines per frame */ + val = (mode->hdisplay - 1) << MCDE_CHNLXCONF_PPL_SHIFT; + val |= (mode->vdisplay - 1) << MCDE_CHNLXCONF_LPF_SHIFT; + writel(val, mcde->regs + conf); + + /* + * Normalize color conversion: + * black background, OLED conversion disable on channel + */ + val = MCDE_CHNLXSTAT_CHNLBLBCKGND_EN | + MCDE_CHNLXSTAT_CHNLRD; + writel(val, mcde->regs + stat); + writel(0, mcde->regs + bgcol); + + /* Set up muxing: connect the channel to the desired FIFO */ + switch (fifo) { + case MCDE_FIFO_A: + writel(MCDE_CHNLXMUXING_FIFO_ID_FIFO_A, + mcde->regs + mux); + break; + case MCDE_FIFO_B: + writel(MCDE_CHNLXMUXING_FIFO_ID_FIFO_B, + mcde->regs + mux); + break; + } + + /* + * If using DPI configure the sync event. + * TODO: this is for LCD only, it does not cover TV out. + */ + if (mcde->dpi_output) { + u32 stripwidth; + + stripwidth = 0xF000 / (mode->vdisplay * 4); + dev_info(mcde->dev, "stripwidth: %d\n", stripwidth); + + val = MCDE_SYNCHCONF_HWREQVEVENT_ACTIVE_VIDEO | + (mode->hdisplay - 1 - stripwidth) << MCDE_SYNCHCONF_HWREQVCNT_SHIFT | + MCDE_SYNCHCONF_SWINTVEVENT_ACTIVE_VIDEO | + (mode->hdisplay - 1 - stripwidth) << MCDE_SYNCHCONF_SWINTVCNT_SHIFT; + + switch (fifo) { + case MCDE_FIFO_A: + writel(val, mcde->regs + MCDE_SYNCHCONFA); + break; + case MCDE_FIFO_B: + writel(val, mcde->regs + MCDE_SYNCHCONFB); + break; + } + } +} + +static void mcde_configure_fifo(struct mcde *mcde, enum mcde_fifo fifo, + enum mcde_formatter fmt, + int fifo_wtrmrk) +{ + u32 val; + u32 ctrl; + u32 cr0, cr1; + + switch (fifo) { + case MCDE_FIFO_A: + ctrl = MCDE_CTRLA; + cr0 = MCDE_CRA0; + cr1 = MCDE_CRA1; + break; + case MCDE_FIFO_B: + ctrl = MCDE_CTRLB; + cr0 = MCDE_CRB0; + cr1 = MCDE_CRB1; + break; + } + + val = fifo_wtrmrk << MCDE_CTRLX_FIFOWTRMRK_SHIFT; + + /* + * Select the formatter to use for this FIFO + * + * The register definitions imply that different IDs should be used + * by the DSI formatters depending on if they are in VID or CMD + * mode, and the manual says they are dedicated but identical. + * The vendor code uses them as it seems fit. + */ + switch (fmt) { + case MCDE_DSI_FORMATTER_0: + val |= MCDE_CTRLX_FORMTYPE_DSI << MCDE_CTRLX_FORMTYPE_SHIFT; + val |= MCDE_CTRLX_FORMID_DSI0VID << MCDE_CTRLX_FORMID_SHIFT; + break; + case MCDE_DSI_FORMATTER_1: + val |= MCDE_CTRLX_FORMTYPE_DSI << MCDE_CTRLX_FORMTYPE_SHIFT; + val |= MCDE_CTRLX_FORMID_DSI0CMD << MCDE_CTRLX_FORMID_SHIFT; + break; + case MCDE_DSI_FORMATTER_2: + val |= MCDE_CTRLX_FORMTYPE_DSI << MCDE_CTRLX_FORMTYPE_SHIFT; + val |= MCDE_CTRLX_FORMID_DSI1VID << MCDE_CTRLX_FORMID_SHIFT; + break; + case MCDE_DSI_FORMATTER_3: + val |= MCDE_CTRLX_FORMTYPE_DSI << MCDE_CTRLX_FORMTYPE_SHIFT; + val |= MCDE_CTRLX_FORMID_DSI1CMD << MCDE_CTRLX_FORMID_SHIFT; + break; + case MCDE_DSI_FORMATTER_4: + val |= MCDE_CTRLX_FORMTYPE_DSI << MCDE_CTRLX_FORMTYPE_SHIFT; + val |= MCDE_CTRLX_FORMID_DSI2VID << MCDE_CTRLX_FORMID_SHIFT; + break; + case MCDE_DSI_FORMATTER_5: + val |= MCDE_CTRLX_FORMTYPE_DSI << MCDE_CTRLX_FORMTYPE_SHIFT; + val |= MCDE_CTRLX_FORMID_DSI2CMD << MCDE_CTRLX_FORMID_SHIFT; + break; + case MCDE_DPI_FORMATTER_0: + val |= MCDE_CTRLX_FORMTYPE_DPITV << MCDE_CTRLX_FORMTYPE_SHIFT; + val |= MCDE_CTRLX_FORMID_DPIA << MCDE_CTRLX_FORMID_SHIFT; + break; + case MCDE_DPI_FORMATTER_1: + val |= MCDE_CTRLX_FORMTYPE_DPITV << MCDE_CTRLX_FORMTYPE_SHIFT; + val |= MCDE_CTRLX_FORMID_DPIB << MCDE_CTRLX_FORMID_SHIFT; + break; + } + writel(val, mcde->regs + ctrl); + + /* Blend source with Alpha 0xff on FIFO */ + val = MCDE_CRX0_BLENDEN | + 0xff << MCDE_CRX0_ALPHABLEND_SHIFT; + writel(val, mcde->regs + cr0); + + spin_lock(&mcde->fifo_crx1_lock); + val = readl(mcde->regs + cr1); + /* + * Set-up from mcde_fmtr_dsi.c, fmtr_dsi_enable_video() + * FIXME: a different clock needs to be selected for TV out. + */ + if (mcde->dpi_output) { + struct drm_connector *connector = drm_panel_bridge_connector(mcde->bridge); + u32 bus_format; + + /* Assume RGB888 24 bit if we have no further info */ + if (!connector->display_info.num_bus_formats) { + dev_info(mcde->dev, "panel does not specify bus format, assume RGB888\n"); + bus_format = MEDIA_BUS_FMT_RGB888_1X24; + } else { + bus_format = connector->display_info.bus_formats[0]; + } + + /* + * Set up the CDWIN and OUTBPP for the LCD + * + * FIXME: fill this in if you know the correspondance between the MIPI + * DPI specification and the media bus formats. + */ + val &= ~MCDE_CRX1_CDWIN_MASK; + val &= ~MCDE_CRX1_OUTBPP_MASK; + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + val |= MCDE_CRX1_CDWIN_24BPP << MCDE_CRX1_CDWIN_SHIFT; + val |= MCDE_CRX1_OUTBPP_24BPP << MCDE_CRX1_OUTBPP_SHIFT; + break; + default: + dev_err(mcde->dev, "unknown bus format, assume RGB888\n"); + val |= MCDE_CRX1_CDWIN_24BPP << MCDE_CRX1_CDWIN_SHIFT; + val |= MCDE_CRX1_OUTBPP_24BPP << MCDE_CRX1_OUTBPP_SHIFT; + break; + } + } else { + /* Use the MCDE clock for DSI */ + val &= ~MCDE_CRX1_CLKSEL_MASK; + val |= MCDE_CRX1_CLKSEL_MCDECLK << MCDE_CRX1_CLKSEL_SHIFT; + } + writel(val, mcde->regs + cr1); + spin_unlock(&mcde->fifo_crx1_lock); +}; + +static void mcde_configure_dsi_formatter(struct mcde *mcde, + enum mcde_formatter fmt, + u32 formatter_frame, + int pkt_size) +{ + u32 val; + u32 conf0; + u32 frame; + u32 pkt; + u32 sync; + u32 cmdw; + u32 delay0, delay1; + + switch (fmt) { + case MCDE_DSI_FORMATTER_0: + conf0 = MCDE_DSIVID0CONF0; + frame = MCDE_DSIVID0FRAME; + pkt = MCDE_DSIVID0PKT; + sync = MCDE_DSIVID0SYNC; + cmdw = MCDE_DSIVID0CMDW; + delay0 = MCDE_DSIVID0DELAY0; + delay1 = MCDE_DSIVID0DELAY1; + break; + case MCDE_DSI_FORMATTER_1: + conf0 = MCDE_DSIVID1CONF0; + frame = MCDE_DSIVID1FRAME; + pkt = MCDE_DSIVID1PKT; + sync = MCDE_DSIVID1SYNC; + cmdw = MCDE_DSIVID1CMDW; + delay0 = MCDE_DSIVID1DELAY0; + delay1 = MCDE_DSIVID1DELAY1; + break; + case MCDE_DSI_FORMATTER_2: + conf0 = MCDE_DSIVID2CONF0; + frame = MCDE_DSIVID2FRAME; + pkt = MCDE_DSIVID2PKT; + sync = MCDE_DSIVID2SYNC; + cmdw = MCDE_DSIVID2CMDW; + delay0 = MCDE_DSIVID2DELAY0; + delay1 = MCDE_DSIVID2DELAY1; + break; + default: + dev_err(mcde->dev, "tried to configure a non-DSI formatter as DSI\n"); + return; + } + + /* + * Enable formatter + * 8 bit commands and DCS commands (notgen = not generic) + */ + val = MCDE_DSICONF0_CMD8 | MCDE_DSICONF0_DCSVID_NOTGEN; + if (mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) + val |= MCDE_DSICONF0_VID_MODE_VID; + switch (mcde->mdsi->format) { + case MIPI_DSI_FMT_RGB888: + val |= MCDE_DSICONF0_PACKING_RGB888 << + MCDE_DSICONF0_PACKING_SHIFT; + break; + case MIPI_DSI_FMT_RGB666: + val |= MCDE_DSICONF0_PACKING_RGB666 << + MCDE_DSICONF0_PACKING_SHIFT; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + dev_err(mcde->dev, + "we cannot handle the packed RGB666 format\n"); + val |= MCDE_DSICONF0_PACKING_RGB666 << + MCDE_DSICONF0_PACKING_SHIFT; + break; + case MIPI_DSI_FMT_RGB565: + val |= MCDE_DSICONF0_PACKING_RGB565 << + MCDE_DSICONF0_PACKING_SHIFT; + break; + default: + dev_err(mcde->dev, "unknown DSI format\n"); + return; + } + writel(val, mcde->regs + conf0); + + writel(formatter_frame, mcde->regs + frame); + writel(pkt_size, mcde->regs + pkt); + writel(0, mcde->regs + sync); + /* Define the MIPI command: we want to write into display memory */ + val = MIPI_DCS_WRITE_MEMORY_CONTINUE << + MCDE_DSIVIDXCMDW_CMDW_CONTINUE_SHIFT; + val |= MIPI_DCS_WRITE_MEMORY_START << + MCDE_DSIVIDXCMDW_CMDW_START_SHIFT; + writel(val, mcde->regs + cmdw); + + /* + * FIXME: the vendor driver has some hack around this value in + * CMD mode with autotrig. + */ + writel(0, mcde->regs + delay0); + writel(0, mcde->regs + delay1); +} + +static void mcde_enable_fifo(struct mcde *mcde, enum mcde_fifo fifo) +{ + u32 val; + u32 cr; + + switch (fifo) { + case MCDE_FIFO_A: + cr = MCDE_CRA0; + break; + case MCDE_FIFO_B: + cr = MCDE_CRB0; + break; + default: + dev_err(mcde->dev, "cannot enable FIFO %c\n", + 'A' + fifo); + return; + } + + spin_lock(&mcde->flow_lock); + val = readl(mcde->regs + cr); + val |= MCDE_CRX0_FLOEN; + writel(val, mcde->regs + cr); + mcde->flow_active++; + spin_unlock(&mcde->flow_lock); +} + +static void mcde_disable_fifo(struct mcde *mcde, enum mcde_fifo fifo, + bool wait_for_drain) +{ + int timeout = 100; + u32 val; + u32 cr; + + switch (fifo) { + case MCDE_FIFO_A: + cr = MCDE_CRA0; + break; + case MCDE_FIFO_B: + cr = MCDE_CRB0; + break; + default: + dev_err(mcde->dev, "cannot disable FIFO %c\n", + 'A' + fifo); + return; + } + + spin_lock(&mcde->flow_lock); + val = readl(mcde->regs + cr); + val &= ~MCDE_CRX0_FLOEN; + writel(val, mcde->regs + cr); + mcde->flow_active = 0; + spin_unlock(&mcde->flow_lock); + + if (!wait_for_drain) + return; + + /* Check that we really drained and stopped the flow */ + while (readl(mcde->regs + cr) & MCDE_CRX0_FLOEN) { + usleep_range(1000, 1500); + if (!--timeout) { + dev_err(mcde->dev, + "FIFO timeout while clearing FIFO %c\n", + 'A' + fifo); + return; + } + } +} + +/* + * This drains a pipe i.e. a FIFO connected to a certain channel + */ +static void mcde_drain_pipe(struct mcde *mcde, enum mcde_fifo fifo, + enum mcde_channel ch) +{ + u32 val; + u32 ctrl; + u32 synsw; + + switch (fifo) { + case MCDE_FIFO_A: + ctrl = MCDE_CTRLA; + break; + case MCDE_FIFO_B: + ctrl = MCDE_CTRLB; + break; + } + + switch (ch) { + case MCDE_CHANNEL_0: + synsw = MCDE_CHNL0SYNCHSW; + break; + case MCDE_CHANNEL_1: + synsw = MCDE_CHNL1SYNCHSW; + break; + case MCDE_CHANNEL_2: + synsw = MCDE_CHNL2SYNCHSW; + break; + case MCDE_CHANNEL_3: + synsw = MCDE_CHNL3SYNCHSW; + return; + } + + val = readl(mcde->regs + ctrl); + if (!(val & MCDE_CTRLX_FIFOEMPTY)) { + dev_err(mcde->dev, "Channel A FIFO not empty (handover)\n"); + /* Attempt to clear the FIFO */ + mcde_enable_fifo(mcde, fifo); + /* Trigger a software sync out on respective channel (0-3) */ + writel(MCDE_CHNLXSYNCHSW_SW_TRIG, mcde->regs + synsw); + /* Disable FIFO A flow again */ + mcde_disable_fifo(mcde, fifo, true); + } +} + +static int mcde_dsi_get_pkt_div(int ppl, int fifo_size) +{ + /* + * DSI command mode line packets should be split into an even number of + * packets smaller than or equal to the fifo size. + */ + int div; + const int max_div = DIV_ROUND_UP(MCDE_MAX_WIDTH, fifo_size); + + for (div = 1; div < max_div; div++) + if (ppl % div == 0 && ppl / div <= fifo_size) + return div; + return 1; +} + +static void mcde_setup_dpi(struct mcde *mcde, const struct drm_display_mode *mode, + int *fifo_wtrmrk_lvl) +{ + struct drm_connector *connector = drm_panel_bridge_connector(mcde->bridge); + u32 hsw, hfp, hbp; + u32 vsw, vfp, vbp; + u32 val; + + /* FIXME: we only support LCD, implement TV out */ + hsw = mode->hsync_end - mode->hsync_start; + hfp = mode->hsync_start - mode->hdisplay; + hbp = mode->htotal - mode->hsync_end; + vsw = mode->vsync_end - mode->vsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + dev_info(mcde->dev, "output on DPI LCD from channel A\n"); + /* Display actual values */ + dev_info(mcde->dev, "HSW: %d, HFP: %d, HBP: %d, VSW: %d, VFP: %d, VBP: %d\n", + hsw, hfp, hbp, vsw, vfp, vbp); + + /* + * The pixel fetcher is 128 64-bit words deep = 1024 bytes. + * One overlay of 32bpp (4 cpp) assumed, fetch 160 pixels. + * 160 * 4 = 640 bytes. + */ + *fifo_wtrmrk_lvl = 640; + + /* Set up the main control, watermark level at 7 */ + val = 7 << MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT; + + /* + * This sets up the internal silicon muxing of the DPI + * lines. This is how the silicon connects out to the + * external pins, then the pins need to be further + * configured into "alternate functions" using pin control + * to actually get the signals out. + * + * FIXME: this is hardcoded to the only setting found in + * the wild. If we need to use different settings for + * different DPI displays, make this parameterizable from + * the device tree. + */ + /* 24 bits DPI: connect Ch A LSB to D[0:7] */ + val |= 0 << MCDE_CONF0_OUTMUX0_SHIFT; + /* 24 bits DPI: connect Ch A MID to D[8:15] */ + val |= 1 << MCDE_CONF0_OUTMUX1_SHIFT; + /* Don't care about this muxing */ + val |= 0 << MCDE_CONF0_OUTMUX2_SHIFT; + /* Don't care about this muxing */ + val |= 0 << MCDE_CONF0_OUTMUX3_SHIFT; + /* 24 bits DPI: connect Ch A MSB to D[32:39] */ + val |= 2 << MCDE_CONF0_OUTMUX4_SHIFT; + /* Syncmux bits zero: DPI channel A */ + writel(val, mcde->regs + MCDE_CONF0); + + /* This hammers us into LCD mode */ + writel(0, mcde->regs + MCDE_TVCRA); + + /* Front porch and sync width */ + val = (vsw << MCDE_TVBL1_BEL1_SHIFT); + val |= (vfp << MCDE_TVBL1_BSL1_SHIFT); + writel(val, mcde->regs + MCDE_TVBL1A); + /* The vendor driver sets the same value into TVBL2A */ + writel(val, mcde->regs + MCDE_TVBL2A); + + /* Vertical back porch */ + val = (vbp << MCDE_TVDVO_DVO1_SHIFT); + /* The vendor drivers sets the same value into TVDVOA */ + val |= (vbp << MCDE_TVDVO_DVO2_SHIFT); + writel(val, mcde->regs + MCDE_TVDVOA); + + /* Horizontal back porch, as 0 = 1 cycle we need to subtract 1 */ + writel((hbp - 1), mcde->regs + MCDE_TVTIM1A); + + /* Horizongal sync width and horizonal front porch, 0 = 1 cycle */ + val = ((hsw - 1) << MCDE_TVLBALW_LBW_SHIFT); + val |= ((hfp - 1) << MCDE_TVLBALW_ALW_SHIFT); + writel(val, mcde->regs + MCDE_TVLBALWA); + + /* Blank some TV registers we don't use */ + writel(0, mcde->regs + MCDE_TVISLA); + writel(0, mcde->regs + MCDE_TVBLUA); + + /* Set up sync inversion etc */ + val = 0; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + val |= MCDE_LCDTIM1B_IHS; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + val |= MCDE_LCDTIM1B_IVS; + if (connector->display_info.bus_flags & DRM_BUS_FLAG_DE_LOW) + val |= MCDE_LCDTIM1B_IOE; + if (connector->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) + val |= MCDE_LCDTIM1B_IPC; + writel(val, mcde->regs + MCDE_LCDTIM1A); +} + +static void mcde_setup_dsi(struct mcde *mcde, const struct drm_display_mode *mode, + int cpp, int *fifo_wtrmrk_lvl, int *dsi_formatter_frame, + int *dsi_pkt_size) +{ + u32 formatter_ppl = mode->hdisplay; /* pixels per line */ + u32 formatter_lpf = mode->vdisplay; /* lines per frame */ + int formatter_frame; + int formatter_cpp; + int fifo_wtrmrk; + u32 pkt_div; + int pkt_size; + u32 val; + + dev_info(mcde->dev, "output in %s mode, format %dbpp\n", + (mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? + "VIDEO" : "CMD", + mipi_dsi_pixel_format_to_bpp(mcde->mdsi->format)); + formatter_cpp = + mipi_dsi_pixel_format_to_bpp(mcde->mdsi->format) / 8; + dev_info(mcde->dev, "Overlay CPP: %d bytes, DSI formatter CPP %d bytes\n", + cpp, formatter_cpp); + + /* Set up the main control, watermark level at 7 */ + val = 7 << MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT; + + /* + * This is the internal silicon muxing of the DPI + * (parallell display) lines. Since we are not using + * this at all (we are using DSI) these are just + * dummy values from the vendor tree. + */ + val |= 3 << MCDE_CONF0_OUTMUX0_SHIFT; + val |= 3 << MCDE_CONF0_OUTMUX1_SHIFT; + val |= 0 << MCDE_CONF0_OUTMUX2_SHIFT; + val |= 4 << MCDE_CONF0_OUTMUX3_SHIFT; + val |= 5 << MCDE_CONF0_OUTMUX4_SHIFT; + writel(val, mcde->regs + MCDE_CONF0); + + /* Calculations from mcde_fmtr_dsi.c, fmtr_dsi_enable_video() */ + + /* + * Set up FIFO A watermark level: + * 128 for LCD 32bpp video mode + * 48 for LCD 32bpp command mode + * 128 for LCD 16bpp video mode + * 64 for LCD 16bpp command mode + * 128 for HDMI 32bpp + * 192 for HDMI 16bpp + */ + fifo_wtrmrk = mode->hdisplay; + if (mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + fifo_wtrmrk = min(fifo_wtrmrk, 128); + pkt_div = 1; + } else { + fifo_wtrmrk = min(fifo_wtrmrk, 48); + /* The FIFO is 640 entries deep on this v3 hardware */ + pkt_div = mcde_dsi_get_pkt_div(mode->hdisplay, 640); + } + dev_dbg(mcde->dev, "FIFO watermark after flooring: %d bytes\n", + fifo_wtrmrk); + dev_dbg(mcde->dev, "Packet divisor: %d bytes\n", pkt_div); + + /* NOTE: pkt_div is 1 for video mode */ + pkt_size = (formatter_ppl * formatter_cpp) / pkt_div; + /* Commands CMD8 need one extra byte */ + if (!(mcde->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO)) + pkt_size++; + + dev_dbg(mcde->dev, "DSI packet size: %d * %d bytes per line\n", + pkt_size, pkt_div); + dev_dbg(mcde->dev, "Overlay frame size: %u bytes\n", + mode->hdisplay * mode->vdisplay * cpp); + /* NOTE: pkt_div is 1 for video mode */ + formatter_frame = pkt_size * pkt_div * formatter_lpf; + dev_dbg(mcde->dev, "Formatter frame size: %u bytes\n", formatter_frame); + + *fifo_wtrmrk_lvl = fifo_wtrmrk; + *dsi_pkt_size = pkt_size; + *dsi_formatter_frame = formatter_frame; +} + +static void mcde_display_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *cstate, + struct drm_plane_state *plane_state) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_plane *plane = &pipe->plane; + struct drm_device *drm = crtc->dev; + struct mcde *mcde = to_mcde(drm); + const struct drm_display_mode *mode = &cstate->mode; + struct drm_framebuffer *fb = plane->state->fb; + u32 format = fb->format->format; + int dsi_pkt_size; + int fifo_wtrmrk; + int cpp = fb->format->cpp[0]; + u32 dsi_formatter_frame; + u32 val; + int ret; + + /* This powers up the entire MCDE block and the DSI hardware */ + ret = regulator_enable(mcde->epod); + if (ret) { + dev_err(drm->dev, "can't re-enable EPOD regulator\n"); + return; + } + + dev_info(drm->dev, "enable MCDE, %d x %d format %p4cc\n", + mode->hdisplay, mode->vdisplay, &format); + + + /* Clear any pending interrupts */ + mcde_display_disable_irqs(mcde); + writel(0, mcde->regs + MCDE_IMSCERR); + writel(0xFFFFFFFF, mcde->regs + MCDE_RISERR); + + if (mcde->dpi_output) + mcde_setup_dpi(mcde, mode, &fifo_wtrmrk); + else + mcde_setup_dsi(mcde, mode, cpp, &fifo_wtrmrk, + &dsi_formatter_frame, &dsi_pkt_size); + + mcde->stride = mode->hdisplay * cpp; + dev_dbg(drm->dev, "Overlay line stride: %u bytes\n", + mcde->stride); + + /* Drain the FIFO A + channel 0 pipe so we have a clean slate */ + mcde_drain_pipe(mcde, MCDE_FIFO_A, MCDE_CHANNEL_0); + + /* + * We set up our display pipeline: + * EXTSRC 0 -> OVERLAY 0 -> CHANNEL 0 -> FIFO A -> DSI FORMATTER 0 + * + * First configure the external source (memory) on external source 0 + * using the desired bitstream/bitmap format + */ + mcde_configure_extsrc(mcde, MCDE_EXTSRC_0, format); + + /* + * Configure overlay 0 according to format and mode and take input + * from external source 0 and route the output of this overlay to + * channel 0 + */ + mcde_configure_overlay(mcde, MCDE_OVERLAY_0, MCDE_EXTSRC_0, + MCDE_CHANNEL_0, mode, format, cpp); + + /* + * Configure pixel-per-line and line-per-frame for channel 0 and then + * route channel 0 to FIFO A + */ + mcde_configure_channel(mcde, MCDE_CHANNEL_0, MCDE_FIFO_A, mode); + + if (mcde->dpi_output) { + unsigned long lcd_freq; + + /* Configure FIFO A to use DPI formatter 0 */ + mcde_configure_fifo(mcde, MCDE_FIFO_A, MCDE_DPI_FORMATTER_0, + fifo_wtrmrk); + + /* Set up and enable the LCD clock */ + lcd_freq = clk_round_rate(mcde->fifoa_clk, mode->clock * 1000); + ret = clk_set_rate(mcde->fifoa_clk, lcd_freq); + if (ret) + dev_err(mcde->dev, "failed to set LCD clock rate %lu Hz\n", + lcd_freq); + ret = clk_prepare_enable(mcde->fifoa_clk); + if (ret) { + dev_err(mcde->dev, "failed to enable FIFO A DPI clock\n"); + return; + } + dev_info(mcde->dev, "LCD FIFO A clk rate %lu Hz\n", + clk_get_rate(mcde->fifoa_clk)); + } else { + /* Configure FIFO A to use DSI formatter 0 */ + mcde_configure_fifo(mcde, MCDE_FIFO_A, MCDE_DSI_FORMATTER_0, + fifo_wtrmrk); + + /* + * This brings up the DSI bridge which is tightly connected + * to the MCDE DSI formatter. + */ + mcde_dsi_enable(mcde->bridge); + + /* Configure the DSI formatter 0 for the DSI panel output */ + mcde_configure_dsi_formatter(mcde, MCDE_DSI_FORMATTER_0, + dsi_formatter_frame, dsi_pkt_size); + } + + switch (mcde->flow_mode) { + case MCDE_COMMAND_TE_FLOW: + case MCDE_COMMAND_BTA_TE_FLOW: + case MCDE_VIDEO_TE_FLOW: + /* We are using TE in some combination */ + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + val = MCDE_VSCRC_VSPOL; + else + val = 0; + writel(val, mcde->regs + MCDE_VSCRC0); + /* Enable VSYNC capture on TE0 */ + val = readl(mcde->regs + MCDE_CRC); + val |= MCDE_CRC_SYCEN0; + writel(val, mcde->regs + MCDE_CRC); + break; + default: + /* No TE capture */ + break; + } + + drm_crtc_vblank_on(crtc); + + /* + * If we're using oneshot mode we don't start the flow + * until each time the display is given an update, and + * then we disable it immediately after. For all other + * modes (command or video) we start the FIFO flow + * right here. This is necessary for the hardware to + * behave right. + */ + if (mcde->flow_mode != MCDE_COMMAND_ONESHOT_FLOW) { + mcde_enable_fifo(mcde, MCDE_FIFO_A); + dev_dbg(mcde->dev, "started MCDE video FIFO flow\n"); + } + + /* Enable MCDE with automatic clock gating */ + val = readl(mcde->regs + MCDE_CR); + val |= MCDE_CR_MCDEEN | MCDE_CR_AUTOCLKG_EN; + writel(val, mcde->regs + MCDE_CR); + + dev_info(drm->dev, "MCDE display is enabled\n"); +} + +static void mcde_display_disable(struct drm_simple_display_pipe *pipe) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_device *drm = crtc->dev; + struct mcde *mcde = to_mcde(drm); + struct drm_pending_vblank_event *event; + int ret; + + drm_crtc_vblank_off(crtc); + + /* Disable FIFO A flow */ + mcde_disable_fifo(mcde, MCDE_FIFO_A, true); + + if (mcde->dpi_output) { + clk_disable_unprepare(mcde->fifoa_clk); + } else { + /* This disables the DSI bridge */ + mcde_dsi_disable(mcde->bridge); + } + + event = crtc->state->event; + if (event) { + crtc->state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } + + ret = regulator_disable(mcde->epod); + if (ret) + dev_err(drm->dev, "can't disable EPOD regulator\n"); + /* Make sure we are powered down (before we may power up again) */ + usleep_range(50000, 70000); + + dev_info(drm->dev, "MCDE display is disabled\n"); +} + +static void mcde_start_flow(struct mcde *mcde) +{ + /* Request a TE ACK only in TE+BTA mode */ + if (mcde->flow_mode == MCDE_COMMAND_BTA_TE_FLOW) + mcde_dsi_te_request(mcde->mdsi); + + /* Enable FIFO A flow */ + mcde_enable_fifo(mcde, MCDE_FIFO_A); + + /* + * If oneshot mode is enabled, the flow will be disabled + * when the TE0 IRQ arrives in the interrupt handler. Otherwise + * updates are continuously streamed to the display after this + * point. + */ + + if (mcde->flow_mode == MCDE_COMMAND_ONESHOT_FLOW) { + /* Trigger a software sync out on channel 0 */ + writel(MCDE_CHNLXSYNCHSW_SW_TRIG, + mcde->regs + MCDE_CHNL0SYNCHSW); + + /* + * Disable FIFO A flow again: since we are using TE sync we + * need to wait for the FIFO to drain before we continue + * so repeated calls to this function will not cause a mess + * in the hardware by pushing updates will updates are going + * on already. + */ + mcde_disable_fifo(mcde, MCDE_FIFO_A, true); + } + + dev_dbg(mcde->dev, "started MCDE FIFO flow\n"); +} + +static void mcde_set_extsrc(struct mcde *mcde, u32 buffer_address) +{ + /* Write bitmap base address to register */ + writel(buffer_address, mcde->regs + MCDE_EXTSRCXA0); + /* + * Base address for next line this is probably only used + * in interlace modes. + */ + writel(buffer_address + mcde->stride, mcde->regs + MCDE_EXTSRCXA1); +} + +static void mcde_display_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_pstate) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_device *drm = crtc->dev; + struct mcde *mcde = to_mcde(drm); + struct drm_pending_vblank_event *event = crtc->state->event; + struct drm_plane *plane = &pipe->plane; + struct drm_plane_state *pstate = plane->state; + struct drm_framebuffer *fb = pstate->fb; + + /* + * Handle any pending event first, we need to arm the vblank + * interrupt before sending any update to the display so we don't + * miss the interrupt. + */ + if (event) { + crtc->state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + /* + * Hardware must be on before we can arm any vblank event, + * this is not a scanout controller where there is always + * some periodic update going on, it is completely frozen + * until we get an update. If MCDE output isn't yet enabled, + * we just send a vblank dummy event back. + */ + if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0) { + dev_dbg(mcde->dev, "arm vblank event\n"); + drm_crtc_arm_vblank_event(crtc, event); + } else { + dev_dbg(mcde->dev, "insert fake vblank event\n"); + drm_crtc_send_vblank_event(crtc, event); + } + + spin_unlock_irq(&crtc->dev->event_lock); + } + + /* + * We do not start sending framebuffer updates before the + * display is enabled. Update events will however be dispatched + * from the DRM core before the display is enabled. + */ + if (fb) { + mcde_set_extsrc(mcde, drm_fb_dma_get_gem_addr(fb, pstate, 0)); + dev_info_once(mcde->dev, "first update of display contents\n"); + /* + * Usually the flow is already active, unless we are in + * oneshot mode, then we need to kick the flow right here. + */ + if (mcde->flow_active == 0) + mcde_start_flow(mcde); + } else { + /* + * If an update is receieved before the MCDE is enabled + * (before mcde_display_enable() is called) we can't really + * do much with that buffer. + */ + dev_info(mcde->dev, "ignored a display update\n"); + } +} + +static int mcde_display_enable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_device *drm = crtc->dev; + struct mcde *mcde = to_mcde(drm); + u32 val; + + /* Enable all VBLANK IRQs */ + val = MCDE_PP_VCMPA | + MCDE_PP_VCMPB | + MCDE_PP_VSCC0 | + MCDE_PP_VSCC1 | + MCDE_PP_VCMPC0 | + MCDE_PP_VCMPC1; + writel(val, mcde->regs + MCDE_IMSCPP); + + return 0; +} + +static void mcde_display_disable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_device *drm = crtc->dev; + struct mcde *mcde = to_mcde(drm); + + /* Disable all VBLANK IRQs */ + writel(0, mcde->regs + MCDE_IMSCPP); + /* Clear any pending IRQs */ + writel(0xFFFFFFFF, mcde->regs + MCDE_RISPP); +} + +static struct drm_simple_display_pipe_funcs mcde_display_funcs = { + .check = mcde_display_check, + .enable = mcde_display_enable, + .disable = mcde_display_disable, + .update = mcde_display_update, + .enable_vblank = mcde_display_enable_vblank, + .disable_vblank = mcde_display_disable_vblank, +}; + +int mcde_display_init(struct drm_device *drm) +{ + struct mcde *mcde = to_mcde(drm); + int ret; + static const u32 formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XBGR4444, + /* These are actually IRGB1555 so intensity bit is lost */ + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_YUV422, + }; + + ret = mcde_init_clock_divider(mcde); + if (ret) + return ret; + + ret = drm_simple_display_pipe_init(drm, &mcde->pipe, + &mcde_display_funcs, + formats, ARRAY_SIZE(formats), + NULL, + mcde->connector); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(mcde_display_init); diff --git a/drivers/gpu/drm/mcde/mcde_display_regs.h b/drivers/gpu/drm/mcde/mcde_display_regs.h new file mode 100644 index 000000000..2ad78c59d --- /dev/null +++ b/drivers/gpu/drm/mcde/mcde_display_regs.h @@ -0,0 +1,605 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DRM_MCDE_DISPLAY_REGS +#define __DRM_MCDE_DISPLAY_REGS + +/* PP (pixel processor) interrupts */ +#define MCDE_IMSCPP 0x00000104 +#define MCDE_RISPP 0x00000114 +#define MCDE_MISPP 0x00000124 +#define MCDE_SISPP 0x00000134 + +#define MCDE_PP_VCMPA BIT(0) +#define MCDE_PP_VCMPB BIT(1) +#define MCDE_PP_VSCC0 BIT(2) +#define MCDE_PP_VSCC1 BIT(3) +#define MCDE_PP_VCMPC0 BIT(4) +#define MCDE_PP_VCMPC1 BIT(5) +#define MCDE_PP_ROTFD_A BIT(6) +#define MCDE_PP_ROTFD_B BIT(7) + +/* Overlay interrupts */ +#define MCDE_IMSCOVL 0x00000108 +#define MCDE_RISOVL 0x00000118 +#define MCDE_MISOVL 0x00000128 +#define MCDE_SISOVL 0x00000138 + +/* Channel interrupts */ +#define MCDE_IMSCCHNL 0x0000010C +#define MCDE_RISCHNL 0x0000011C +#define MCDE_MISCHNL 0x0000012C +#define MCDE_SISCHNL 0x0000013C + +/* X = 0..9 */ +#define MCDE_EXTSRCXA0 0x00000200 +#define MCDE_EXTSRCXA0_GROUPOFFSET 0x20 +#define MCDE_EXTSRCXA0_BASEADDRESS0_SHIFT 3 +#define MCDE_EXTSRCXA0_BASEADDRESS0_MASK 0xFFFFFFF8 + +#define MCDE_EXTSRCXA1 0x00000204 +#define MCDE_EXTSRCXA1_GROUPOFFSET 0x20 +#define MCDE_EXTSRCXA1_BASEADDRESS1_SHIFT 3 +#define MCDE_EXTSRCXA1_BASEADDRESS1_MASK 0xFFFFFFF8 + +/* External sources 0..9 */ +#define MCDE_EXTSRC0CONF 0x0000020C +#define MCDE_EXTSRC1CONF 0x0000022C +#define MCDE_EXTSRC2CONF 0x0000024C +#define MCDE_EXTSRC3CONF 0x0000026C +#define MCDE_EXTSRC4CONF 0x0000028C +#define MCDE_EXTSRC5CONF 0x000002AC +#define MCDE_EXTSRC6CONF 0x000002CC +#define MCDE_EXTSRC7CONF 0x000002EC +#define MCDE_EXTSRC8CONF 0x0000030C +#define MCDE_EXTSRC9CONF 0x0000032C +#define MCDE_EXTSRCXCONF_GROUPOFFSET 0x20 +#define MCDE_EXTSRCXCONF_BUF_ID_SHIFT 0 +#define MCDE_EXTSRCXCONF_BUF_ID_MASK 0x00000003 +#define MCDE_EXTSRCXCONF_BUF_NB_SHIFT 2 +#define MCDE_EXTSRCXCONF_BUF_NB_MASK 0x0000000C +#define MCDE_EXTSRCXCONF_PRI_OVLID_SHIFT 4 +#define MCDE_EXTSRCXCONF_PRI_OVLID_MASK 0x000000F0 +#define MCDE_EXTSRCXCONF_BPP_SHIFT 8 +#define MCDE_EXTSRCXCONF_BPP_MASK 0x00000F00 +#define MCDE_EXTSRCXCONF_BPP_1BPP_PAL 0 +#define MCDE_EXTSRCXCONF_BPP_2BPP_PAL 1 +#define MCDE_EXTSRCXCONF_BPP_4BPP_PAL 2 +#define MCDE_EXTSRCXCONF_BPP_8BPP_PAL 3 +#define MCDE_EXTSRCXCONF_BPP_RGB444 4 +#define MCDE_EXTSRCXCONF_BPP_ARGB4444 5 +#define MCDE_EXTSRCXCONF_BPP_IRGB1555 6 +#define MCDE_EXTSRCXCONF_BPP_RGB565 7 +#define MCDE_EXTSRCXCONF_BPP_RGB888 8 +#define MCDE_EXTSRCXCONF_BPP_XRGB8888 9 +#define MCDE_EXTSRCXCONF_BPP_ARGB8888 10 +#define MCDE_EXTSRCXCONF_BPP_YCBCR422 11 +#define MCDE_EXTSRCXCONF_BGR BIT(12) +#define MCDE_EXTSRCXCONF_BEBO BIT(13) +#define MCDE_EXTSRCXCONF_BEPO BIT(14) +#define MCDE_EXTSRCXCONF_TUNNELING_BUFFER_HEIGHT_SHIFT 16 +#define MCDE_EXTSRCXCONF_TUNNELING_BUFFER_HEIGHT_MASK 0x0FFF0000 + +/* External sources 0..9 */ +#define MCDE_EXTSRC0CR 0x00000210 +#define MCDE_EXTSRC1CR 0x00000230 +#define MCDE_EXTSRC2CR 0x00000250 +#define MCDE_EXTSRC3CR 0x00000270 +#define MCDE_EXTSRC4CR 0x00000290 +#define MCDE_EXTSRC5CR 0x000002B0 +#define MCDE_EXTSRC6CR 0x000002D0 +#define MCDE_EXTSRC7CR 0x000002F0 +#define MCDE_EXTSRC8CR 0x00000310 +#define MCDE_EXTSRC9CR 0x00000330 +#define MCDE_EXTSRCXCR_SEL_MOD_SHIFT 0 +#define MCDE_EXTSRCXCR_SEL_MOD_MASK 0x00000003 +#define MCDE_EXTSRCXCR_SEL_MOD_EXTERNAL_SEL 0 +#define MCDE_EXTSRCXCR_SEL_MOD_AUTO_TOGGLE 1 +#define MCDE_EXTSRCXCR_SEL_MOD_SOFTWARE_SEL 2 +#define MCDE_EXTSRCXCR_MULTIOVL_CTRL_PRIMARY BIT(2) /* 0 = all */ +#define MCDE_EXTSRCXCR_FS_DIV_DISABLE BIT(3) +#define MCDE_EXTSRCXCR_FORCE_FS_DIV BIT(4) + +/* Only external source 6 has a second address register */ +#define MCDE_EXTSRC6A2 0x000002C8 + +/* 6 overlays */ +#define MCDE_OVL0CR 0x00000400 +#define MCDE_OVL1CR 0x00000420 +#define MCDE_OVL2CR 0x00000440 +#define MCDE_OVL3CR 0x00000460 +#define MCDE_OVL4CR 0x00000480 +#define MCDE_OVL5CR 0x000004A0 +#define MCDE_OVLXCR_OVLEN BIT(0) +#define MCDE_OVLXCR_COLCCTRL_DISABLED 0 +#define MCDE_OVLXCR_COLCCTRL_ENABLED_NO_SAT (1 << 1) +#define MCDE_OVLXCR_COLCCTRL_ENABLED_SAT (2 << 1) +#define MCDE_OVLXCR_CKEYGEN BIT(3) +#define MCDE_OVLXCR_ALPHAPMEN BIT(4) +#define MCDE_OVLXCR_OVLF BIT(5) +#define MCDE_OVLXCR_OVLR BIT(6) +#define MCDE_OVLXCR_OVLB BIT(7) +#define MCDE_OVLXCR_FETCH_ROPC_SHIFT 8 +#define MCDE_OVLXCR_FETCH_ROPC_MASK 0x0000FF00 +#define MCDE_OVLXCR_STBPRIO_SHIFT 16 +#define MCDE_OVLXCR_STBPRIO_MASK 0x000F0000 +#define MCDE_OVLXCR_BURSTSIZE_SHIFT 20 +#define MCDE_OVLXCR_BURSTSIZE_MASK 0x00F00000 +#define MCDE_OVLXCR_BURSTSIZE_1W 0 +#define MCDE_OVLXCR_BURSTSIZE_2W 1 +#define MCDE_OVLXCR_BURSTSIZE_4W 2 +#define MCDE_OVLXCR_BURSTSIZE_8W 3 +#define MCDE_OVLXCR_BURSTSIZE_16W 4 +#define MCDE_OVLXCR_BURSTSIZE_HW_1W 8 +#define MCDE_OVLXCR_BURSTSIZE_HW_2W 9 +#define MCDE_OVLXCR_BURSTSIZE_HW_4W 10 +#define MCDE_OVLXCR_BURSTSIZE_HW_8W 11 +#define MCDE_OVLXCR_BURSTSIZE_HW_16W 12 +#define MCDE_OVLXCR_MAXOUTSTANDING_SHIFT 24 +#define MCDE_OVLXCR_MAXOUTSTANDING_MASK 0x0F000000 +#define MCDE_OVLXCR_MAXOUTSTANDING_1_REQ 0 +#define MCDE_OVLXCR_MAXOUTSTANDING_2_REQ 1 +#define MCDE_OVLXCR_MAXOUTSTANDING_4_REQ 2 +#define MCDE_OVLXCR_MAXOUTSTANDING_8_REQ 3 +#define MCDE_OVLXCR_MAXOUTSTANDING_16_REQ 4 +#define MCDE_OVLXCR_ROTBURSTSIZE_SHIFT 28 +#define MCDE_OVLXCR_ROTBURSTSIZE_MASK 0xF0000000 +#define MCDE_OVLXCR_ROTBURSTSIZE_1W 0 +#define MCDE_OVLXCR_ROTBURSTSIZE_2W 1 +#define MCDE_OVLXCR_ROTBURSTSIZE_4W 2 +#define MCDE_OVLXCR_ROTBURSTSIZE_8W 3 +#define MCDE_OVLXCR_ROTBURSTSIZE_16W 4 +#define MCDE_OVLXCR_ROTBURSTSIZE_HW_1W 8 +#define MCDE_OVLXCR_ROTBURSTSIZE_HW_2W 9 +#define MCDE_OVLXCR_ROTBURSTSIZE_HW_4W 10 +#define MCDE_OVLXCR_ROTBURSTSIZE_HW_8W 11 +#define MCDE_OVLXCR_ROTBURSTSIZE_HW_16W 12 + +#define MCDE_OVL0CONF 0x00000404 +#define MCDE_OVL1CONF 0x00000424 +#define MCDE_OVL2CONF 0x00000444 +#define MCDE_OVL3CONF 0x00000464 +#define MCDE_OVL4CONF 0x00000484 +#define MCDE_OVL5CONF 0x000004A4 +#define MCDE_OVLXCONF_PPL_SHIFT 0 +#define MCDE_OVLXCONF_PPL_MASK 0x000007FF +#define MCDE_OVLXCONF_EXTSRC_ID_SHIFT 11 +#define MCDE_OVLXCONF_EXTSRC_ID_MASK 0x00007800 +#define MCDE_OVLXCONF_LPF_SHIFT 16 +#define MCDE_OVLXCONF_LPF_MASK 0x07FF0000 + +#define MCDE_OVL0CONF2 0x00000408 +#define MCDE_OVL1CONF2 0x00000428 +#define MCDE_OVL2CONF2 0x00000448 +#define MCDE_OVL3CONF2 0x00000468 +#define MCDE_OVL4CONF2 0x00000488 +#define MCDE_OVL5CONF2 0x000004A8 +#define MCDE_OVLXCONF2_BP_PER_PIXEL_ALPHA 0 +#define MCDE_OVLXCONF2_BP_CONSTANT_ALPHA BIT(0) +#define MCDE_OVLXCONF2_ALPHAVALUE_SHIFT 1 +#define MCDE_OVLXCONF2_ALPHAVALUE_MASK 0x000001FE +#define MCDE_OVLXCONF2_OPQ BIT(9) +#define MCDE_OVLXCONF2_PIXOFF_SHIFT 10 +#define MCDE_OVLXCONF2_PIXOFF_MASK 0x0000FC00 +#define MCDE_OVLXCONF2_PIXELFETCHERWATERMARKLEVEL_SHIFT 16 +#define MCDE_OVLXCONF2_PIXELFETCHERWATERMARKLEVEL_MASK 0x1FFF0000 + +#define MCDE_OVL0LJINC 0x0000040C +#define MCDE_OVL1LJINC 0x0000042C +#define MCDE_OVL2LJINC 0x0000044C +#define MCDE_OVL3LJINC 0x0000046C +#define MCDE_OVL4LJINC 0x0000048C +#define MCDE_OVL5LJINC 0x000004AC + +#define MCDE_OVL0CROP 0x00000410 +#define MCDE_OVL1CROP 0x00000430 +#define MCDE_OVL2CROP 0x00000450 +#define MCDE_OVL3CROP 0x00000470 +#define MCDE_OVL4CROP 0x00000490 +#define MCDE_OVL5CROP 0x000004B0 +#define MCDE_OVLXCROP_TMRGN_SHIFT 0 +#define MCDE_OVLXCROP_TMRGN_MASK 0x003FFFFF +#define MCDE_OVLXCROP_LMRGN_SHIFT 22 +#define MCDE_OVLXCROP_LMRGN_MASK 0xFFC00000 + +#define MCDE_OVL0COMP 0x00000414 +#define MCDE_OVL1COMP 0x00000434 +#define MCDE_OVL2COMP 0x00000454 +#define MCDE_OVL3COMP 0x00000474 +#define MCDE_OVL4COMP 0x00000494 +#define MCDE_OVL5COMP 0x000004B4 +#define MCDE_OVLXCOMP_XPOS_SHIFT 0 +#define MCDE_OVLXCOMP_XPOS_MASK 0x000007FF +#define MCDE_OVLXCOMP_CH_ID_SHIFT 11 +#define MCDE_OVLXCOMP_CH_ID_MASK 0x00007800 +#define MCDE_OVLXCOMP_YPOS_SHIFT 16 +#define MCDE_OVLXCOMP_YPOS_MASK 0x07FF0000 +#define MCDE_OVLXCOMP_Z_SHIFT 27 +#define MCDE_OVLXCOMP_Z_MASK 0x78000000 + +/* DPI/TV configuration registers, channel A and B */ +#define MCDE_TVCRA 0x00000838 +#define MCDE_TVCRB 0x00000A38 +#define MCDE_TVCR_MOD_TV BIT(0) /* 0 = LCD mode */ +#define MCDE_TVCR_INTEREN BIT(1) +#define MCDE_TVCR_IFIELD BIT(2) +#define MCDE_TVCR_TVMODE_SDTV_656P (0 << 3) +#define MCDE_TVCR_TVMODE_SDTV_656P_LE (3 << 3) +#define MCDE_TVCR_TVMODE_SDTV_656P_BE (4 << 3) +#define MCDE_TVCR_SDTVMODE_Y0CBY1CR (0 << 6) +#define MCDE_TVCR_SDTVMODE_CBY0CRY1 (1 << 6) +#define MCDE_TVCR_AVRGEN BIT(8) +#define MCDE_TVCR_CKINV BIT(9) + +/* TV blanking control register 1, channel A and B */ +#define MCDE_TVBL1A 0x0000083C +#define MCDE_TVBL1B 0x00000A3C +#define MCDE_TVBL1_BEL1_SHIFT 0 /* VFP vertical front porch 11 bits */ +#define MCDE_TVBL1_BSL1_SHIFT 16 /* VSW vertical sync pulse width 11 bits */ + +/* Pixel processing TV start line, channel A and B */ +#define MCDE_TVISLA 0x00000840 +#define MCDE_TVISLB 0x00000A40 +#define MCDE_TVISL_FSL1_SHIFT 0 /* Field 1 identification start line 11 bits */ +#define MCDE_TVISL_FSL2_SHIFT 16 /* Field 2 identification start line 11 bits */ + +/* Pixel processing TV DVO offset */ +#define MCDE_TVDVOA 0x00000844 +#define MCDE_TVDVOB 0x00000A44 +#define MCDE_TVDVO_DVO1_SHIFT 0 /* VBP vertical back porch 0 = 0 */ +#define MCDE_TVDVO_DVO2_SHIFT 16 + +/* + * Pixel processing TV Timing 1 + * HBP horizontal back porch 11 bits horizontal offset + * 0 = 1 pixel HBP, 255 = 256 pixels, so actual value - 1 + */ +#define MCDE_TVTIM1A 0x0000084C +#define MCDE_TVTIM1B 0x00000A4C + +/* Pixel processing TV LBALW */ +/* 0 = 1 clock cycle, 255 = 256 clock cycles */ +#define MCDE_TVLBALWA 0x00000850 +#define MCDE_TVLBALWB 0x00000A50 +#define MCDE_TVLBALW_LBW_SHIFT 0 /* HSW horizonal sync width, line blanking width 11 bits */ +#define MCDE_TVLBALW_ALW_SHIFT 16 /* HFP horizontal front porch, active line width 11 bits */ + +/* TV blanking control register 1, channel A and B */ +#define MCDE_TVBL2A 0x00000854 +#define MCDE_TVBL2B 0x00000A54 +#define MCDE_TVBL2_BEL2_SHIFT 0 /* Field 2 blanking end line 11 bits */ +#define MCDE_TVBL2_BSL2_SHIFT 16 /* Field 2 blanking start line 11 bits */ + +/* Pixel processing TV background */ +#define MCDE_TVBLUA 0x00000858 +#define MCDE_TVBLUB 0x00000A58 +#define MCDE_TVBLU_TVBLU_SHIFT 0 /* 8 bits luminance */ +#define MCDE_TVBLU_TVBCB_SHIFT 8 /* 8 bits Cb chrominance */ +#define MCDE_TVBLU_TVBCR_SHIFT 16 /* 8 bits Cr chrominance */ + +/* Pixel processing LCD timing 1 */ +#define MCDE_LCDTIM1A 0x00000860 +#define MCDE_LCDTIM1B 0x00000A60 +/* inverted vertical sync pulse for HRTFT 0 = active low, 1 active high */ +#define MCDE_LCDTIM1B_IVP BIT(19) +/* inverted vertical sync, 0 = active high (the normal), 1 = active low */ +#define MCDE_LCDTIM1B_IVS BIT(20) +/* inverted horizontal sync, 0 = active high (the normal), 1 = active low */ +#define MCDE_LCDTIM1B_IHS BIT(21) +/* inverted panel clock 0 = rising edge data out, 1 = falling edge data out */ +#define MCDE_LCDTIM1B_IPC BIT(22) +/* invert output enable 0 = active high, 1 = active low */ +#define MCDE_LCDTIM1B_IOE BIT(23) + +#define MCDE_CRC 0x00000C00 +#define MCDE_CRC_C1EN BIT(2) +#define MCDE_CRC_C2EN BIT(3) +#define MCDE_CRC_SYCEN0 BIT(7) +#define MCDE_CRC_SYCEN1 BIT(8) +#define MCDE_CRC_SIZE1 BIT(9) +#define MCDE_CRC_SIZE2 BIT(10) +#define MCDE_CRC_YUVCONVC1EN BIT(15) +#define MCDE_CRC_CS1EN BIT(16) +#define MCDE_CRC_CS2EN BIT(17) +#define MCDE_CRC_CS1POL BIT(19) +#define MCDE_CRC_CS2POL BIT(20) +#define MCDE_CRC_CD1POL BIT(21) +#define MCDE_CRC_CD2POL BIT(22) +#define MCDE_CRC_WR1POL BIT(23) +#define MCDE_CRC_WR2POL BIT(24) +#define MCDE_CRC_RD1POL BIT(25) +#define MCDE_CRC_RD2POL BIT(26) +#define MCDE_CRC_SYNCCTRL_SHIFT 29 +#define MCDE_CRC_SYNCCTRL_MASK 0x60000000 +#define MCDE_CRC_SYNCCTRL_NO_SYNC 0 +#define MCDE_CRC_SYNCCTRL_DBI0 1 +#define MCDE_CRC_SYNCCTRL_DBI1 2 +#define MCDE_CRC_SYNCCTRL_PING_PONG 3 +#define MCDE_CRC_CLAMPC1EN BIT(31) + +#define MCDE_VSCRC0 0x00000C5C +#define MCDE_VSCRC1 0x00000C60 +#define MCDE_VSCRC_VSPMIN_MASK 0x00000FFF +#define MCDE_VSCRC_VSPMAX_SHIFT 12 +#define MCDE_VSCRC_VSPMAX_MASK 0x00FFF000 +#define MCDE_VSCRC_VSPDIV_SHIFT 24 +#define MCDE_VSCRC_VSPDIV_MASK 0x07000000 +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_1 0 +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_2 1 +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_4 2 +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_8 3 +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_16 4 +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_32 5 +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_64 6 +#define MCDE_VSCRC_VSPDIV_MCDECLK_DIV_128 7 +#define MCDE_VSCRC_VSPOL BIT(27) /* 0 active high, 1 active low */ +#define MCDE_VSCRC_VSSEL BIT(28) /* 0 VSYNC0, 1 VSYNC1 */ +#define MCDE_VSCRC_VSDBL BIT(29) + +/* Channel config 0..3 */ +#define MCDE_CHNL0CONF 0x00000600 +#define MCDE_CHNL1CONF 0x00000620 +#define MCDE_CHNL2CONF 0x00000640 +#define MCDE_CHNL3CONF 0x00000660 +#define MCDE_CHNLXCONF_PPL_SHIFT 0 +#define MCDE_CHNLXCONF_PPL_MASK 0x000007FF +#define MCDE_CHNLXCONF_LPF_SHIFT 16 +#define MCDE_CHNLXCONF_LPF_MASK 0x07FF0000 +#define MCDE_MAX_WIDTH 2048 + +/* Channel status 0..3 */ +#define MCDE_CHNL0STAT 0x00000604 +#define MCDE_CHNL1STAT 0x00000624 +#define MCDE_CHNL2STAT 0x00000644 +#define MCDE_CHNL3STAT 0x00000664 +#define MCDE_CHNLXSTAT_CHNLRD BIT(0) +#define MCDE_CHNLXSTAT_CHNLA BIT(1) +#define MCDE_CHNLXSTAT_CHNLBLBCKGND_EN BIT(16) +#define MCDE_CHNLXSTAT_PPLX2_V422 BIT(17) +#define MCDE_CHNLXSTAT_LPFX2_V422 BIT(18) + +/* Sync settings for channel 0..3 */ +#define MCDE_CHNL0SYNCHMOD 0x00000608 +#define MCDE_CHNL1SYNCHMOD 0x00000628 +#define MCDE_CHNL2SYNCHMOD 0x00000648 +#define MCDE_CHNL3SYNCHMOD 0x00000668 + +#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SHIFT 0 +#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_MASK 0x00000003 +#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_HARDWARE 0 +#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_NO_SYNCH 1 +#define MCDE_CHNLXSYNCHMOD_SRC_SYNCH_SOFTWARE 2 +#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_SHIFT 2 +#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_MASK 0x0000001C +#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_FORMATTER 0 +#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE0 1 +#define MCDE_CHNLXSYNCHMOD_OUT_SYNCH_SRC_TE1 2 + +/* Software sync triggers for channel 0..3 */ +#define MCDE_CHNL0SYNCHSW 0x0000060C +#define MCDE_CHNL1SYNCHSW 0x0000062C +#define MCDE_CHNL2SYNCHSW 0x0000064C +#define MCDE_CHNL3SYNCHSW 0x0000066C +#define MCDE_CHNLXSYNCHSW_SW_TRIG BIT(0) + +#define MCDE_CHNL0BCKGNDCOL 0x00000610 +#define MCDE_CHNL1BCKGNDCOL 0x00000630 +#define MCDE_CHNL2BCKGNDCOL 0x00000650 +#define MCDE_CHNL3BCKGNDCOL 0x00000670 +#define MCDE_CHNLXBCKGNDCOL_B_SHIFT 0 +#define MCDE_CHNLXBCKGNDCOL_B_MASK 0x000000FF +#define MCDE_CHNLXBCKGNDCOL_G_SHIFT 8 +#define MCDE_CHNLXBCKGNDCOL_G_MASK 0x0000FF00 +#define MCDE_CHNLXBCKGNDCOL_R_SHIFT 16 +#define MCDE_CHNLXBCKGNDCOL_R_MASK 0x00FF0000 + +#define MCDE_CHNL0MUXING 0x00000614 +#define MCDE_CHNL1MUXING 0x00000634 +#define MCDE_CHNL2MUXING 0x00000654 +#define MCDE_CHNL3MUXING 0x00000674 +#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_A 0 +#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_B 1 +#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_C0 2 +#define MCDE_CHNLXMUXING_FIFO_ID_FIFO_C1 3 + +/* Pixel processing control registers for channel A B, */ +#define MCDE_CRA0 0x00000800 +#define MCDE_CRB0 0x00000A00 +#define MCDE_CRX0_FLOEN BIT(0) +#define MCDE_CRX0_POWEREN BIT(1) +#define MCDE_CRX0_BLENDEN BIT(2) +#define MCDE_CRX0_AFLICKEN BIT(3) +#define MCDE_CRX0_PALEN BIT(4) +#define MCDE_CRX0_DITHEN BIT(5) +#define MCDE_CRX0_GAMEN BIT(6) +#define MCDE_CRX0_KEYCTRL_SHIFT 7 +#define MCDE_CRX0_KEYCTRL_MASK 0x00000380 +#define MCDE_CRX0_KEYCTRL_OFF 0 +#define MCDE_CRX0_KEYCTRL_ALPHA_RGB 1 +#define MCDE_CRX0_KEYCTRL_RGB 2 +#define MCDE_CRX0_KEYCTRL_FALPHA_FRGB 4 +#define MCDE_CRX0_KEYCTRL_FRGB 5 +#define MCDE_CRX0_BLENDCTRL BIT(10) +#define MCDE_CRX0_FLICKMODE_SHIFT 11 +#define MCDE_CRX0_FLICKMODE_MASK 0x00001800 +#define MCDE_CRX0_FLICKMODE_FORCE_FILTER_0 0 +#define MCDE_CRX0_FLICKMODE_ADAPTIVE 1 +#define MCDE_CRX0_FLICKMODE_TEST_MODE 2 +#define MCDE_CRX0_FLOCKFORMAT_RGB BIT(13) /* 0 = YCVCR */ +#define MCDE_CRX0_PALMODE_GAMMA BIT(14) /* 0 = palette */ +#define MCDE_CRX0_OLEDEN BIT(15) +#define MCDE_CRX0_ALPHABLEND_SHIFT 16 +#define MCDE_CRX0_ALPHABLEND_MASK 0x00FF0000 +#define MCDE_CRX0_ROTEN BIT(24) + +#define MCDE_CRA1 0x00000804 +#define MCDE_CRB1 0x00000A04 +#define MCDE_CRX1_PCD_SHIFT 0 +#define MCDE_CRX1_PCD_MASK 0x000003FF +#define MCDE_CRX1_PCD_BITS 10 +#define MCDE_CRX1_CLKSEL_SHIFT 10 +#define MCDE_CRX1_CLKSEL_MASK 0x00001C00 +#define MCDE_CRX1_CLKSEL_CLKPLL72 0 +#define MCDE_CRX1_CLKSEL_CLKPLL27 2 +#define MCDE_CRX1_CLKSEL_TV1CLK 3 +#define MCDE_CRX1_CLKSEL_TV2CLK 4 +#define MCDE_CRX1_CLKSEL_MCDECLK 5 +#define MCDE_CRX1_CDWIN_SHIFT 13 +#define MCDE_CRX1_CDWIN_MASK 0x0001E000 +#define MCDE_CRX1_CDWIN_8BPP_C1 0 +#define MCDE_CRX1_CDWIN_12BPP_C1 1 +#define MCDE_CRX1_CDWIN_12BPP_C2 2 +#define MCDE_CRX1_CDWIN_16BPP_C1 3 +#define MCDE_CRX1_CDWIN_16BPP_C2 4 +#define MCDE_CRX1_CDWIN_16BPP_C3 5 +#define MCDE_CRX1_CDWIN_18BPP_C1 6 +#define MCDE_CRX1_CDWIN_18BPP_C2 7 +#define MCDE_CRX1_CDWIN_24BPP 8 +#define MCDE_CRX1_OUTBPP_SHIFT 25 +#define MCDE_CRX1_OUTBPP_MASK 0x1E000000 +#define MCDE_CRX1_OUTBPP_MONO1 0 +#define MCDE_CRX1_OUTBPP_MONO2 1 +#define MCDE_CRX1_OUTBPP_MONO4 2 +#define MCDE_CRX1_OUTBPP_MONO8 3 +#define MCDE_CRX1_OUTBPP_8BPP 4 +#define MCDE_CRX1_OUTBPP_12BPP 5 +#define MCDE_CRX1_OUTBPP_15BPP 6 +#define MCDE_CRX1_OUTBPP_16BPP 7 +#define MCDE_CRX1_OUTBPP_18BPP 8 +#define MCDE_CRX1_OUTBPP_24BPP 9 +#define MCDE_CRX1_BCD BIT(29) +#define MCDE_CRA1_CLKTYPE_TVXCLKSEL1 BIT(30) /* 0 = TVXCLKSEL1 */ + +#define MCDE_COLKEYA 0x00000808 +#define MCDE_COLKEYB 0x00000A08 + +#define MCDE_FCOLKEYA 0x0000080C +#define MCDE_FCOLKEYB 0x00000A0C + +#define MCDE_RGBCONV1A 0x00000810 +#define MCDE_RGBCONV1B 0x00000A10 + +#define MCDE_RGBCONV2A 0x00000814 +#define MCDE_RGBCONV2B 0x00000A14 + +#define MCDE_RGBCONV3A 0x00000818 +#define MCDE_RGBCONV3B 0x00000A18 + +#define MCDE_RGBCONV4A 0x0000081C +#define MCDE_RGBCONV4B 0x00000A1C + +#define MCDE_RGBCONV5A 0x00000820 +#define MCDE_RGBCONV5B 0x00000A20 + +#define MCDE_RGBCONV6A 0x00000824 +#define MCDE_RGBCONV6B 0x00000A24 + +/* Rotation */ +#define MCDE_ROTACONF 0x0000087C +#define MCDE_ROTBCONF 0x00000A7C + +/* Synchronization event configuration */ +#define MCDE_SYNCHCONFA 0x00000880 +#define MCDE_SYNCHCONFB 0x00000A80 +#define MCDE_SYNCHCONF_HWREQVEVENT_SHIFT 0 +#define MCDE_SYNCHCONF_HWREQVEVENT_VSYNC (0 << 0) +#define MCDE_SYNCHCONF_HWREQVEVENT_BACK_PORCH (1 << 0) +#define MCDE_SYNCHCONF_HWREQVEVENT_ACTIVE_VIDEO (2 << 0) +#define MCDE_SYNCHCONF_HWREQVEVENT_FRONT_PORCH (3 << 0) +#define MCDE_SYNCHCONF_HWREQVCNT_SHIFT 2 /* 14 bits */ +#define MCDE_SYNCHCONF_SWINTVEVENT_VSYNC (0 << 16) +#define MCDE_SYNCHCONF_SWINTVEVENT_BACK_PORCH (1 << 16) +#define MCDE_SYNCHCONF_SWINTVEVENT_ACTIVE_VIDEO (2 << 16) +#define MCDE_SYNCHCONF_SWINTVEVENT_FRONT_PORCH (3 << 16) +#define MCDE_SYNCHCONF_SWINTVCNT_SHIFT 18 /* 14 bits */ + +/* Channel A+B control registers */ +#define MCDE_CTRLA 0x00000884 +#define MCDE_CTRLB 0x00000A84 +#define MCDE_CTRLX_FIFOWTRMRK_SHIFT 0 +#define MCDE_CTRLX_FIFOWTRMRK_MASK 0x000003FF +#define MCDE_CTRLX_FIFOEMPTY BIT(12) +#define MCDE_CTRLX_FIFOFULL BIT(13) +#define MCDE_CTRLX_FORMID_SHIFT 16 +#define MCDE_CTRLX_FORMID_MASK 0x00070000 +#define MCDE_CTRLX_FORMID_DSI0VID 0 +#define MCDE_CTRLX_FORMID_DSI0CMD 1 +#define MCDE_CTRLX_FORMID_DSI1VID 2 +#define MCDE_CTRLX_FORMID_DSI1CMD 3 +#define MCDE_CTRLX_FORMID_DSI2VID 4 +#define MCDE_CTRLX_FORMID_DSI2CMD 5 +#define MCDE_CTRLX_FORMID_DPIA 0 +#define MCDE_CTRLX_FORMID_DPIB 1 +#define MCDE_CTRLX_FORMTYPE_SHIFT 20 +#define MCDE_CTRLX_FORMTYPE_MASK 0x00700000 +#define MCDE_CTRLX_FORMTYPE_DPITV 0 +#define MCDE_CTRLX_FORMTYPE_DBI 1 +#define MCDE_CTRLX_FORMTYPE_DSI 2 + +#define MCDE_DSIVID0CONF0 0x00000E00 +#define MCDE_DSICMD0CONF0 0x00000E20 +#define MCDE_DSIVID1CONF0 0x00000E40 +#define MCDE_DSICMD1CONF0 0x00000E60 +#define MCDE_DSIVID2CONF0 0x00000E80 +#define MCDE_DSICMD2CONF0 0x00000EA0 +#define MCDE_DSICONF0_BLANKING_SHIFT 0 +#define MCDE_DSICONF0_BLANKING_MASK 0x000000FF +#define MCDE_DSICONF0_VID_MODE_CMD 0 +#define MCDE_DSICONF0_VID_MODE_VID BIT(12) +#define MCDE_DSICONF0_CMD8 BIT(13) +#define MCDE_DSICONF0_BIT_SWAP BIT(16) +#define MCDE_DSICONF0_BYTE_SWAP BIT(17) +#define MCDE_DSICONF0_DCSVID_NOTGEN BIT(18) +#define MCDE_DSICONF0_PACKING_SHIFT 20 +#define MCDE_DSICONF0_PACKING_MASK 0x00700000 +#define MCDE_DSICONF0_PACKING_RGB565 0 +#define MCDE_DSICONF0_PACKING_RGB666 1 +#define MCDE_DSICONF0_PACKING_RGB888 2 +#define MCDE_DSICONF0_PACKING_BGR888 3 +#define MCDE_DSICONF0_PACKING_HDTV 4 + +#define MCDE_DSIVID0FRAME 0x00000E04 +#define MCDE_DSICMD0FRAME 0x00000E24 +#define MCDE_DSIVID1FRAME 0x00000E44 +#define MCDE_DSICMD1FRAME 0x00000E64 +#define MCDE_DSIVID2FRAME 0x00000E84 +#define MCDE_DSICMD2FRAME 0x00000EA4 + +#define MCDE_DSIVID0PKT 0x00000E08 +#define MCDE_DSICMD0PKT 0x00000E28 +#define MCDE_DSIVID1PKT 0x00000E48 +#define MCDE_DSICMD1PKT 0x00000E68 +#define MCDE_DSIVID2PKT 0x00000E88 +#define MCDE_DSICMD2PKT 0x00000EA8 + +#define MCDE_DSIVID0SYNC 0x00000E0C +#define MCDE_DSICMD0SYNC 0x00000E2C +#define MCDE_DSIVID1SYNC 0x00000E4C +#define MCDE_DSICMD1SYNC 0x00000E6C +#define MCDE_DSIVID2SYNC 0x00000E8C +#define MCDE_DSICMD2SYNC 0x00000EAC + +#define MCDE_DSIVID0CMDW 0x00000E10 +#define MCDE_DSICMD0CMDW 0x00000E30 +#define MCDE_DSIVID1CMDW 0x00000E50 +#define MCDE_DSICMD1CMDW 0x00000E70 +#define MCDE_DSIVID2CMDW 0x00000E90 +#define MCDE_DSICMD2CMDW 0x00000EB0 +#define MCDE_DSIVIDXCMDW_CMDW_CONTINUE_SHIFT 0 +#define MCDE_DSIVIDXCMDW_CMDW_CONTINUE_MASK 0x0000FFFF +#define MCDE_DSIVIDXCMDW_CMDW_START_SHIFT 16 +#define MCDE_DSIVIDXCMDW_CMDW_START_MASK 0xFFFF0000 + +#define MCDE_DSIVID0DELAY0 0x00000E14 +#define MCDE_DSICMD0DELAY0 0x00000E34 +#define MCDE_DSIVID1DELAY0 0x00000E54 +#define MCDE_DSICMD1DELAY0 0x00000E74 +#define MCDE_DSIVID2DELAY0 0x00000E94 +#define MCDE_DSICMD2DELAY0 0x00000EB4 + +#define MCDE_DSIVID0DELAY1 0x00000E18 +#define MCDE_DSICMD0DELAY1 0x00000E38 +#define MCDE_DSIVID1DELAY1 0x00000E58 +#define MCDE_DSICMD1DELAY1 0x00000E78 +#define MCDE_DSIVID2DELAY1 0x00000E98 +#define MCDE_DSICMD2DELAY1 0x00000EB8 + +#endif /* __DRM_MCDE_DISPLAY_REGS */ diff --git a/drivers/gpu/drm/mcde/mcde_drm.h b/drivers/gpu/drm/mcde/mcde_drm.h new file mode 100644 index 000000000..ecb70b4b7 --- /dev/null +++ b/drivers/gpu/drm/mcde/mcde_drm.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2018 Linus Walleij <linus.walleij@linaro.org> + * Parts of this file were based on the MCDE driver by Marcus Lorentzon + * (C) ST-Ericsson SA 2013 + */ +#include <drm/drm_simple_kms_helper.h> + +#ifndef _MCDE_DRM_H_ +#define _MCDE_DRM_H_ + +/* Shared basic registers */ +#define MCDE_CR 0x00000000 +#define MCDE_CR_IFIFOEMPTYLINECOUNT_V422_SHIFT 0 +#define MCDE_CR_IFIFOEMPTYLINECOUNT_V422_MASK 0x0000003F +#define MCDE_CR_IFIFOCTRLEN BIT(15) +#define MCDE_CR_UFRECOVERY_MODE_V422 BIT(16) +#define MCDE_CR_WRAP_MODE_V422_SHIFT BIT(17) +#define MCDE_CR_AUTOCLKG_EN BIT(30) +#define MCDE_CR_MCDEEN BIT(31) + +#define MCDE_CONF0 0x00000004 +#define MCDE_CONF0_SYNCMUX0 BIT(0) +#define MCDE_CONF0_SYNCMUX1 BIT(1) +#define MCDE_CONF0_SYNCMUX2 BIT(2) +#define MCDE_CONF0_SYNCMUX3 BIT(3) +#define MCDE_CONF0_SYNCMUX4 BIT(4) +#define MCDE_CONF0_SYNCMUX5 BIT(5) +#define MCDE_CONF0_SYNCMUX6 BIT(6) +#define MCDE_CONF0_SYNCMUX7 BIT(7) +#define MCDE_CONF0_IFIFOCTRLWTRMRKLVL_SHIFT 12 +#define MCDE_CONF0_IFIFOCTRLWTRMRKLVL_MASK 0x00007000 +#define MCDE_CONF0_OUTMUX0_SHIFT 16 +#define MCDE_CONF0_OUTMUX0_MASK 0x00070000 +#define MCDE_CONF0_OUTMUX1_SHIFT 19 +#define MCDE_CONF0_OUTMUX1_MASK 0x00380000 +#define MCDE_CONF0_OUTMUX2_SHIFT 22 +#define MCDE_CONF0_OUTMUX2_MASK 0x01C00000 +#define MCDE_CONF0_OUTMUX3_SHIFT 25 +#define MCDE_CONF0_OUTMUX3_MASK 0x0E000000 +#define MCDE_CONF0_OUTMUX4_SHIFT 28 +#define MCDE_CONF0_OUTMUX4_MASK 0x70000000 + +#define MCDE_SSP 0x00000008 +#define MCDE_AIS 0x00000100 +#define MCDE_IMSCERR 0x00000110 +#define MCDE_RISERR 0x00000120 +#define MCDE_MISERR 0x00000130 +#define MCDE_SISERR 0x00000140 + +enum mcde_flow_mode { + /* One-shot mode: flow stops after one frame */ + MCDE_COMMAND_ONESHOT_FLOW, + /* Command mode with tearing effect (TE) IRQ sync */ + MCDE_COMMAND_TE_FLOW, + /* + * Command mode with bus turn-around (BTA) and tearing effect + * (TE) IRQ sync. + */ + MCDE_COMMAND_BTA_TE_FLOW, + /* Video mode with tearing effect (TE) sync IRQ */ + MCDE_VIDEO_TE_FLOW, + /* Video mode with the formatter itself as sync source */ + MCDE_VIDEO_FORMATTER_FLOW, + /* DPI video with the formatter itsels as sync source */ + MCDE_DPI_FORMATTER_FLOW, +}; + +struct mcde { + struct drm_device drm; + struct device *dev; + struct drm_panel *panel; + struct drm_bridge *bridge; + struct drm_connector *connector; + struct drm_simple_display_pipe pipe; + struct mipi_dsi_device *mdsi; + bool dpi_output; + s16 stride; + enum mcde_flow_mode flow_mode; + unsigned int flow_active; + spinlock_t flow_lock; /* Locks the channel flow control */ + + void __iomem *regs; + + struct clk *mcde_clk; + struct clk *lcd_clk; + struct clk *hdmi_clk; + /* Handles to the clock dividers for FIFO A and B */ + struct clk *fifoa_clk; + struct clk *fifob_clk; + /* Locks the MCDE FIFO control register A and B */ + spinlock_t fifo_crx1_lock; + + struct regulator *epod; + struct regulator *vana; +}; + +#define to_mcde(dev) container_of(dev, struct mcde, drm) + +static inline bool mcde_flow_is_video(struct mcde *mcde) +{ + return (mcde->flow_mode == MCDE_VIDEO_TE_FLOW || + mcde->flow_mode == MCDE_VIDEO_FORMATTER_FLOW); +} + +bool mcde_dsi_irq(struct mipi_dsi_device *mdsi); +void mcde_dsi_te_request(struct mipi_dsi_device *mdsi); +void mcde_dsi_enable(struct drm_bridge *bridge); +void mcde_dsi_disable(struct drm_bridge *bridge); +extern struct platform_driver mcde_dsi_driver; + +void mcde_display_irq(struct mcde *mcde); +void mcde_display_disable_irqs(struct mcde *mcde); +int mcde_display_init(struct drm_device *drm); + +int mcde_init_clock_divider(struct mcde *mcde); + +#endif /* _MCDE_DRM_H_ */ diff --git a/drivers/gpu/drm/mcde/mcde_drv.c b/drivers/gpu/drm/mcde/mcde_drv.c new file mode 100644 index 000000000..53275b575 --- /dev/null +++ b/drivers/gpu/drm/mcde/mcde_drv.c @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Linus Walleij <linus.walleij@linaro.org> + * Parts of this file were based on the MCDE driver by Marcus Lorentzon + * (C) ST-Ericsson SA 2013 + */ + +/** + * DOC: ST-Ericsson MCDE Driver + * + * The MCDE (short for multi-channel display engine) is a graphics + * controller found in the Ux500 chipsets, such as NovaThor U8500. + * It was initially conceptualized by ST Microelectronics for the + * successor of the Nomadik line, STn8500 but productified in the + * ST-Ericsson U8500 where is was used for mass-market deployments + * in Android phones from Samsung and Sony Ericsson. + * + * It can do 1080p30 on SDTV CCIR656, DPI-2, DBI-2 or DSI for + * panels with or without frame buffering and can convert most + * input formats including most variants of RGB and YUV. + * + * The hardware has four display pipes, and the layout is a little + * bit like this:: + * + * Memory -> Overlay -> Channel -> FIFO -> 8 formatters -> DSI/DPI + * External 0..5 0..3 A,B, 6 x DSI bridge + * source 0..9 C0,C1 2 x DPI + * + * FIFOs A and B are for LCD and HDMI while FIFO CO/C1 are for + * panels with embedded buffer. + * 6 of the formatters are for DSI, 3 pairs for VID/CMD respectively. + * 2 of the formatters are for DPI. + * + * Behind the formatters are the DSI or DPI ports that route to + * the external pins of the chip. As there are 3 DSI ports and one + * DPI port, it is possible to configure up to 4 display pipelines + * (effectively using channels 0..3) for concurrent use. + * + * In the current DRM/KMS setup, we use one external source, one overlay, + * one FIFO and one formatter which we connect to the simple DMA framebuffer + * helpers. We then provide a bridge to the DSI port, and on the DSI port + * bridge we connect hang a panel bridge or other bridge. This may be subject + * to change as we exploit more of the hardware capabilities. + * + * TODO: + * + * - Enabled damaged rectangles using drm_plane_enable_fb_damage_clips() + * so we can selectively just transmit the damaged area to a + * command-only display. + * - Enable mixing of more planes, possibly at the cost of moving away + * from using the simple framebuffer pipeline. + * - Enable output to bridges such as the AV8100 HDMI encoder from + * the DSI bridge. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/dma-buf.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_dma_helper.h> +#include <drm/drm_fbdev_dma.h> +#include <drm/drm_gem.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_of.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_panel.h> +#include <drm/drm_vblank.h> + +#include "mcde_drm.h" + +#define DRIVER_DESC "DRM module for MCDE" + +#define MCDE_PID 0x000001FC +#define MCDE_PID_METALFIX_VERSION_SHIFT 0 +#define MCDE_PID_METALFIX_VERSION_MASK 0x000000FF +#define MCDE_PID_DEVELOPMENT_VERSION_SHIFT 8 +#define MCDE_PID_DEVELOPMENT_VERSION_MASK 0x0000FF00 +#define MCDE_PID_MINOR_VERSION_SHIFT 16 +#define MCDE_PID_MINOR_VERSION_MASK 0x00FF0000 +#define MCDE_PID_MAJOR_VERSION_SHIFT 24 +#define MCDE_PID_MAJOR_VERSION_MASK 0xFF000000 + +static const struct drm_mode_config_funcs mcde_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const struct drm_mode_config_helper_funcs mcde_mode_config_helpers = { + /* + * Using this function is necessary to commit atomic updates + * that need the CRTC to be enabled before a commit, as is + * the case with e.g. DSI displays. + */ + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +static irqreturn_t mcde_irq(int irq, void *data) +{ + struct mcde *mcde = data; + u32 val; + + val = readl(mcde->regs + MCDE_MISERR); + + mcde_display_irq(mcde); + + if (val) + dev_info(mcde->dev, "some error IRQ\n"); + writel(val, mcde->regs + MCDE_RISERR); + + return IRQ_HANDLED; +} + +static int mcde_modeset_init(struct drm_device *drm) +{ + struct drm_mode_config *mode_config; + struct mcde *mcde = to_mcde(drm); + int ret; + + /* + * If no other bridge was found, check if we have a DPI panel or + * any other bridge connected directly to the MCDE DPI output. + * If a DSI bridge is found, DSI will take precedence. + * + * TODO: more elaborate bridge selection if we have more than one + * thing attached to the system. + */ + if (!mcde->bridge) { + struct drm_panel *panel; + struct drm_bridge *bridge; + + ret = drm_of_find_panel_or_bridge(drm->dev->of_node, + 0, 0, &panel, &bridge); + if (ret) { + dev_err(drm->dev, + "Could not locate any output bridge or panel\n"); + return ret; + } + if (panel) { + bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); + if (IS_ERR(bridge)) { + dev_err(drm->dev, + "Could not connect panel bridge\n"); + return PTR_ERR(bridge); + } + } + mcde->dpi_output = true; + mcde->bridge = bridge; + mcde->flow_mode = MCDE_DPI_FORMATTER_FLOW; + } + + mode_config = &drm->mode_config; + mode_config->funcs = &mcde_mode_config_funcs; + mode_config->helper_private = &mcde_mode_config_helpers; + /* This hardware can do 1080p */ + mode_config->min_width = 1; + mode_config->max_width = 1920; + mode_config->min_height = 1; + mode_config->max_height = 1080; + + ret = drm_vblank_init(drm, 1); + if (ret) { + dev_err(drm->dev, "failed to init vblank\n"); + return ret; + } + + ret = mcde_display_init(drm); + if (ret) { + dev_err(drm->dev, "failed to init display\n"); + return ret; + } + + /* Attach the bridge. */ + ret = drm_simple_display_pipe_attach_bridge(&mcde->pipe, + mcde->bridge); + if (ret) { + dev_err(drm->dev, "failed to attach display output bridge\n"); + return ret; + } + + drm_mode_config_reset(drm); + drm_kms_helper_poll_init(drm); + + return 0; +} + +DEFINE_DRM_GEM_DMA_FOPS(drm_fops); + +static const struct drm_driver mcde_drm_driver = { + .driver_features = + DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .ioctls = NULL, + .fops = &drm_fops, + .name = "mcde", + .desc = DRIVER_DESC, + .major = 1, + .minor = 0, + .patchlevel = 0, + DRM_GEM_DMA_DRIVER_OPS, + DRM_FBDEV_DMA_DRIVER_OPS, +}; + +static int mcde_drm_bind(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + int ret; + + ret = drmm_mode_config_init(drm); + if (ret) + return ret; + + ret = component_bind_all(drm->dev, drm); + if (ret) { + dev_err(dev, "can't bind component devices\n"); + return ret; + } + + ret = mcde_modeset_init(drm); + if (ret) + goto unbind; + + ret = drm_dev_register(drm, 0); + if (ret < 0) + goto unbind; + + drm_client_setup(drm, NULL); + + return 0; + +unbind: + component_unbind_all(drm->dev, drm); + return ret; +} + +static void mcde_drm_unbind(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + drm_dev_unregister(drm); + drm_atomic_helper_shutdown(drm); + component_unbind_all(drm->dev, drm); +} + +static const struct component_master_ops mcde_drm_comp_ops = { + .bind = mcde_drm_bind, + .unbind = mcde_drm_unbind, +}; + +static struct platform_driver *const mcde_component_drivers[] = { + &mcde_dsi_driver, +}; + +static int mcde_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct drm_device *drm; + struct mcde *mcde; + struct component_match *match = NULL; + u32 pid; + int irq; + int ret; + int i; + + mcde = devm_drm_dev_alloc(dev, &mcde_drm_driver, struct mcde, drm); + if (IS_ERR(mcde)) + return PTR_ERR(mcde); + drm = &mcde->drm; + mcde->dev = dev; + platform_set_drvdata(pdev, drm); + + /* First obtain and turn on the main power */ + mcde->epod = devm_regulator_get(dev, "epod"); + if (IS_ERR(mcde->epod)) { + ret = PTR_ERR(mcde->epod); + dev_err(dev, "can't get EPOD regulator\n"); + return ret; + } + ret = regulator_enable(mcde->epod); + if (ret) { + dev_err(dev, "can't enable EPOD regulator\n"); + return ret; + } + mcde->vana = devm_regulator_get(dev, "vana"); + if (IS_ERR(mcde->vana)) { + ret = PTR_ERR(mcde->vana); + dev_err(dev, "can't get VANA regulator\n"); + goto regulator_epod_off; + } + ret = regulator_enable(mcde->vana); + if (ret) { + dev_err(dev, "can't enable VANA regulator\n"); + goto regulator_epod_off; + } + /* + * The vendor code uses ESRAM (onchip RAM) and need to activate + * the v-esram34 regulator, but we don't use that yet + */ + + /* Clock the silicon so we can access the registers */ + mcde->mcde_clk = devm_clk_get(dev, "mcde"); + if (IS_ERR(mcde->mcde_clk)) { + dev_err(dev, "unable to get MCDE main clock\n"); + ret = PTR_ERR(mcde->mcde_clk); + goto regulator_off; + } + ret = clk_prepare_enable(mcde->mcde_clk); + if (ret) { + dev_err(dev, "failed to enable MCDE main clock\n"); + goto regulator_off; + } + dev_info(dev, "MCDE clk rate %lu Hz\n", clk_get_rate(mcde->mcde_clk)); + + mcde->lcd_clk = devm_clk_get(dev, "lcd"); + if (IS_ERR(mcde->lcd_clk)) { + dev_err(dev, "unable to get LCD clock\n"); + ret = PTR_ERR(mcde->lcd_clk); + goto clk_disable; + } + mcde->hdmi_clk = devm_clk_get(dev, "hdmi"); + if (IS_ERR(mcde->hdmi_clk)) { + dev_err(dev, "unable to get HDMI clock\n"); + ret = PTR_ERR(mcde->hdmi_clk); + goto clk_disable; + } + + mcde->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mcde->regs)) { + dev_err(dev, "no MCDE regs\n"); + ret = -EINVAL; + goto clk_disable; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto clk_disable; + } + + ret = devm_request_irq(dev, irq, mcde_irq, 0, "mcde", mcde); + if (ret) { + dev_err(dev, "failed to request irq %d\n", ret); + goto clk_disable; + } + + /* + * Check hardware revision, we only support U8500v2 version + * as this was the only version used for mass market deployment, + * but surely you can add more versions if you have them and + * need them. + */ + pid = readl(mcde->regs + MCDE_PID); + dev_info(dev, "found MCDE HW revision %d.%d (dev %d, metal fix %d)\n", + (pid & MCDE_PID_MAJOR_VERSION_MASK) + >> MCDE_PID_MAJOR_VERSION_SHIFT, + (pid & MCDE_PID_MINOR_VERSION_MASK) + >> MCDE_PID_MINOR_VERSION_SHIFT, + (pid & MCDE_PID_DEVELOPMENT_VERSION_MASK) + >> MCDE_PID_DEVELOPMENT_VERSION_SHIFT, + (pid & MCDE_PID_METALFIX_VERSION_MASK) + >> MCDE_PID_METALFIX_VERSION_SHIFT); + if (pid != 0x03000800) { + dev_err(dev, "unsupported hardware revision\n"); + ret = -ENODEV; + goto clk_disable; + } + + /* Disable and clear any pending interrupts */ + mcde_display_disable_irqs(mcde); + writel(0, mcde->regs + MCDE_IMSCERR); + writel(0xFFFFFFFF, mcde->regs + MCDE_RISERR); + + /* Spawn child devices for the DSI ports */ + devm_of_platform_populate(dev); + + /* Create something that will match the subdrivers when we bind */ + for (i = 0; i < ARRAY_SIZE(mcde_component_drivers); i++) { + struct device_driver *drv = &mcde_component_drivers[i]->driver; + struct device *p = NULL, *d; + + while ((d = platform_find_device_by_driver(p, drv))) { + put_device(p); + component_match_add(dev, &match, component_compare_dev, d); + p = d; + } + put_device(p); + } + if (!match) { + dev_err(dev, "no matching components\n"); + ret = -ENODEV; + goto clk_disable; + } + if (IS_ERR(match)) { + dev_err(dev, "could not create component match\n"); + ret = PTR_ERR(match); + goto clk_disable; + } + + /* + * Perform an invasive reset of the MCDE and all blocks by + * cutting the power to the subsystem, then bring it back up + * later when we enable the display as a result of + * component_master_add_with_match(). + */ + ret = regulator_disable(mcde->epod); + if (ret) { + dev_err(dev, "can't disable EPOD regulator\n"); + return ret; + } + /* Wait 50 ms so we are sure we cut the power */ + usleep_range(50000, 70000); + + ret = component_master_add_with_match(&pdev->dev, &mcde_drm_comp_ops, + match); + if (ret) { + dev_err(dev, "failed to add component master\n"); + /* + * The EPOD regulator is already disabled at this point so some + * special errorpath code is needed + */ + clk_disable_unprepare(mcde->mcde_clk); + regulator_disable(mcde->vana); + return ret; + } + + return 0; + +clk_disable: + clk_disable_unprepare(mcde->mcde_clk); +regulator_off: + regulator_disable(mcde->vana); +regulator_epod_off: + regulator_disable(mcde->epod); + return ret; + +} + +static void mcde_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + struct mcde *mcde = to_mcde(drm); + + component_master_del(&pdev->dev, &mcde_drm_comp_ops); + clk_disable_unprepare(mcde->mcde_clk); + regulator_disable(mcde->vana); + regulator_disable(mcde->epod); +} + +static void mcde_shutdown(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + if (drm->registered) + drm_atomic_helper_shutdown(drm); +} + +static const struct of_device_id mcde_of_match[] = { + { + .compatible = "ste,mcde", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mcde_of_match); + +static struct platform_driver mcde_driver = { + .driver = { + .name = "mcde", + .of_match_table = mcde_of_match, + }, + .probe = mcde_probe, + .remove = mcde_remove, + .shutdown = mcde_shutdown, +}; + +static struct platform_driver *const component_drivers[] = { + &mcde_dsi_driver, +}; + +static int __init mcde_drm_register(void) +{ + int ret; + + if (drm_firmware_drivers_only()) + return -ENODEV; + + ret = platform_register_drivers(component_drivers, + ARRAY_SIZE(component_drivers)); + if (ret) + return ret; + + return platform_driver_register(&mcde_driver); +} + +static void __exit mcde_drm_unregister(void) +{ + platform_unregister_drivers(component_drivers, + ARRAY_SIZE(component_drivers)); + platform_driver_unregister(&mcde_driver); +} + +module_init(mcde_drm_register); +module_exit(mcde_drm_unregister); + +MODULE_ALIAS("platform:mcde-drm"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c new file mode 100644 index 000000000..694372581 --- /dev/null +++ b/drivers/gpu/drm/mcde/mcde_dsi.c @@ -0,0 +1,1242 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <video/mipi_display.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_encoder.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "mcde_drm.h" +#include "mcde_dsi_regs.h" + +#define DSI_DEFAULT_LP_FREQ_HZ 19200000 +#define DSI_DEFAULT_HS_FREQ_HZ 420160000 + +/* PRCMU DSI reset registers */ +#define PRCM_DSI_SW_RESET 0x324 +#define PRCM_DSI_SW_RESET_DSI0_SW_RESETN BIT(0) +#define PRCM_DSI_SW_RESET_DSI1_SW_RESETN BIT(1) +#define PRCM_DSI_SW_RESET_DSI2_SW_RESETN BIT(2) + +struct mcde_dsi { + struct device *dev; + struct mcde *mcde; + struct drm_bridge bridge; + struct drm_panel *panel; + struct mipi_dsi_host dsi_host; + struct mipi_dsi_device *mdsi; + const struct drm_display_mode *mode; + struct clk *hs_clk; + struct clk *lp_clk; + unsigned long hs_freq; + unsigned long lp_freq; + bool unused; + + void __iomem *regs; + struct regmap *prcmu; +}; + +static inline struct mcde_dsi *bridge_to_mcde_dsi(struct drm_bridge *bridge) +{ + return container_of(bridge, struct mcde_dsi, bridge); +} + +static inline struct mcde_dsi *host_to_mcde_dsi(struct mipi_dsi_host *h) +{ + return container_of(h, struct mcde_dsi, dsi_host); +} + +bool mcde_dsi_irq(struct mipi_dsi_device *mdsi) +{ + struct mcde_dsi *d; + u32 val; + bool te_received = false; + + d = host_to_mcde_dsi(mdsi->host); + + dev_dbg(d->dev, "%s called\n", __func__); + + val = readl(d->regs + DSI_DIRECT_CMD_STS_FLAG); + if (val) + dev_dbg(d->dev, "DSI_DIRECT_CMD_STS_FLAG = %08x\n", val); + if (val & DSI_DIRECT_CMD_STS_WRITE_COMPLETED) + dev_dbg(d->dev, "direct command write completed\n"); + if (val & DSI_DIRECT_CMD_STS_TE_RECEIVED) { + te_received = true; + dev_dbg(d->dev, "direct command TE received\n"); + } + if (val & DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED) + dev_err(d->dev, "direct command ACK ERR received\n"); + if (val & DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR) + dev_err(d->dev, "direct command read ERR received\n"); + /* Mask off the ACK value and clear status */ + writel(val, d->regs + DSI_DIRECT_CMD_STS_CLR); + + val = readl(d->regs + DSI_CMD_MODE_STS_FLAG); + if (val) + dev_dbg(d->dev, "DSI_CMD_MODE_STS_FLAG = %08x\n", val); + if (val & DSI_CMD_MODE_STS_ERR_NO_TE) + /* This happens all the time (safe to ignore) */ + dev_dbg(d->dev, "CMD mode no TE\n"); + if (val & DSI_CMD_MODE_STS_ERR_TE_MISS) + /* This happens all the time (safe to ignore) */ + dev_dbg(d->dev, "CMD mode TE miss\n"); + if (val & DSI_CMD_MODE_STS_ERR_SDI1_UNDERRUN) + dev_err(d->dev, "CMD mode SD1 underrun\n"); + if (val & DSI_CMD_MODE_STS_ERR_SDI2_UNDERRUN) + dev_err(d->dev, "CMD mode SD2 underrun\n"); + if (val & DSI_CMD_MODE_STS_ERR_UNWANTED_RD) + dev_err(d->dev, "CMD mode unwanted RD\n"); + writel(val, d->regs + DSI_CMD_MODE_STS_CLR); + + val = readl(d->regs + DSI_DIRECT_CMD_RD_STS_FLAG); + if (val) + dev_dbg(d->dev, "DSI_DIRECT_CMD_RD_STS_FLAG = %08x\n", val); + writel(val, d->regs + DSI_DIRECT_CMD_RD_STS_CLR); + + val = readl(d->regs + DSI_TG_STS_FLAG); + if (val) + dev_dbg(d->dev, "DSI_TG_STS_FLAG = %08x\n", val); + writel(val, d->regs + DSI_TG_STS_CLR); + + val = readl(d->regs + DSI_VID_MODE_STS_FLAG); + if (val) + dev_dbg(d->dev, "DSI_VID_MODE_STS_FLAG = %08x\n", val); + if (val & DSI_VID_MODE_STS_VSG_RUNNING) + dev_dbg(d->dev, "VID mode VSG running\n"); + if (val & DSI_VID_MODE_STS_ERR_MISSING_DATA) + dev_err(d->dev, "VID mode missing data\n"); + if (val & DSI_VID_MODE_STS_ERR_MISSING_HSYNC) + dev_err(d->dev, "VID mode missing HSYNC\n"); + if (val & DSI_VID_MODE_STS_ERR_MISSING_VSYNC) + dev_err(d->dev, "VID mode missing VSYNC\n"); + if (val & DSI_VID_MODE_STS_REG_ERR_SMALL_LENGTH) + dev_err(d->dev, "VID mode less bytes than expected between two HSYNC\n"); + if (val & DSI_VID_MODE_STS_REG_ERR_SMALL_HEIGHT) + dev_err(d->dev, "VID mode less lines than expected between two VSYNC\n"); + if (val & (DSI_VID_MODE_STS_ERR_BURSTWRITE | + DSI_VID_MODE_STS_ERR_LINEWRITE | + DSI_VID_MODE_STS_ERR_LONGREAD)) + dev_err(d->dev, "VID mode read/write error\n"); + if (val & DSI_VID_MODE_STS_ERR_VRS_WRONG_LENGTH) + dev_err(d->dev, "VID mode received packets differ from expected size\n"); + if (val & DSI_VID_MODE_STS_VSG_RECOVERY) + dev_err(d->dev, "VID mode VSG in recovery mode\n"); + writel(val, d->regs + DSI_VID_MODE_STS_CLR); + + return te_received; +} + +static void mcde_dsi_attach_to_mcde(struct mcde_dsi *d) +{ + d->mcde->mdsi = d->mdsi; + + /* + * Select the way the DSI data flow is pushing to the display: + * currently we just support video or command mode depending + * on the type of display. Video mode defaults to using the + * formatter itself for synchronization (stateless video panel). + * + * FIXME: add flags to struct mipi_dsi_device .flags to indicate + * displays that require BTA (bus turn around) so we can handle + * such displays as well. Figure out how to properly handle + * single frame on-demand updates with DRM for command mode + * displays (MCDE_COMMAND_ONESHOT_FLOW). + */ + if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) + d->mcde->flow_mode = MCDE_VIDEO_FORMATTER_FLOW; + else + d->mcde->flow_mode = MCDE_COMMAND_TE_FLOW; +} + +static int mcde_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *mdsi) +{ + struct mcde_dsi *d = host_to_mcde_dsi(host); + + if (mdsi->lanes < 1 || mdsi->lanes > 2) { + DRM_ERROR("dsi device params invalid, 1 or 2 lanes supported\n"); + return -EINVAL; + } + + dev_info(d->dev, "attached DSI device with %d lanes\n", mdsi->lanes); + /* MIPI_DSI_FMT_RGB88 etc */ + dev_info(d->dev, "format %08x, %dbpp\n", mdsi->format, + mipi_dsi_pixel_format_to_bpp(mdsi->format)); + dev_info(d->dev, "mode flags: %08lx\n", mdsi->mode_flags); + + d->mdsi = mdsi; + if (d->mcde) + mcde_dsi_attach_to_mcde(d); + + return 0; +} + +static int mcde_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *mdsi) +{ + struct mcde_dsi *d = host_to_mcde_dsi(host); + + d->mdsi = NULL; + if (d->mcde) + d->mcde->mdsi = NULL; + + return 0; +} + +#define MCDE_DSI_HOST_IS_READ(type) \ + ((type == MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM) || \ + (type == MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM) || \ + (type == MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM) || \ + (type == MIPI_DSI_DCS_READ)) + +static int mcde_dsi_execute_transfer(struct mcde_dsi *d, + const struct mipi_dsi_msg *msg) +{ + const u32 loop_delay_us = 10; /* us */ + u32 loop_counter; + size_t txlen = msg->tx_len; + size_t rxlen = msg->rx_len; + int i; + u32 val; + int ret; + + writel(~0, d->regs + DSI_DIRECT_CMD_STS_CLR); + writel(~0, d->regs + DSI_CMD_MODE_STS_CLR); + /* Send command */ + writel(1, d->regs + DSI_DIRECT_CMD_SEND); + + loop_counter = 1000 * 1000 / loop_delay_us; + if (MCDE_DSI_HOST_IS_READ(msg->type)) { + /* Read command */ + while (!(readl(d->regs + DSI_DIRECT_CMD_STS) & + (DSI_DIRECT_CMD_STS_READ_COMPLETED | + DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR)) + && --loop_counter) + usleep_range(loop_delay_us, (loop_delay_us * 3) / 2); + if (!loop_counter) { + dev_err(d->dev, "DSI read timeout!\n"); + /* Set exit code and retry */ + return -ETIME; + } + } else { + /* Writing only */ + while (!(readl(d->regs + DSI_DIRECT_CMD_STS) & + DSI_DIRECT_CMD_STS_WRITE_COMPLETED) + && --loop_counter) + usleep_range(loop_delay_us, (loop_delay_us * 3) / 2); + + if (!loop_counter) { + /* Set exit code and retry */ + dev_err(d->dev, "DSI write timeout!\n"); + return -ETIME; + } + } + + val = readl(d->regs + DSI_DIRECT_CMD_STS); + if (val & DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR) { + dev_err(d->dev, "read completed with error\n"); + writel(1, d->regs + DSI_DIRECT_CMD_RD_INIT); + return -EIO; + } + if (val & DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED) { + val >>= DSI_DIRECT_CMD_STS_ACK_VAL_SHIFT; + dev_err(d->dev, "error during transmission: %04x\n", + val); + return -EIO; + } + + if (!MCDE_DSI_HOST_IS_READ(msg->type)) { + /* Return number of bytes written */ + ret = txlen; + } else { + /* OK this is a read command, get the response */ + u32 rdsz; + u32 rddat; + u8 *rx = msg->rx_buf; + + rdsz = readl(d->regs + DSI_DIRECT_CMD_RD_PROPERTY); + rdsz &= DSI_DIRECT_CMD_RD_PROPERTY_RD_SIZE_MASK; + rddat = readl(d->regs + DSI_DIRECT_CMD_RDDAT); + if (rdsz < rxlen) { + dev_err(d->dev, "read error, requested %zd got %d\n", + rxlen, rdsz); + return -EIO; + } + /* FIXME: read more than 4 bytes */ + for (i = 0; i < 4 && i < rxlen; i++) + rx[i] = (rddat >> (i * 8)) & 0xff; + ret = rdsz; + } + + /* Successful transmission */ + return ret; +} + +static ssize_t mcde_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct mcde_dsi *d = host_to_mcde_dsi(host); + const u8 *tx = msg->tx_buf; + size_t txlen = msg->tx_len; + size_t rxlen = msg->rx_len; + unsigned int retries = 0; + u32 val; + int ret; + int i; + + if (txlen > 16) { + dev_err(d->dev, + "dunno how to write more than 16 bytes yet\n"); + return -EIO; + } + if (rxlen > 4) { + dev_err(d->dev, + "dunno how to read more than 4 bytes yet\n"); + return -EIO; + } + + dev_dbg(d->dev, + "message to channel %d, write %zd bytes read %zd bytes\n", + msg->channel, txlen, rxlen); + + /* Command "nature" */ + if (MCDE_DSI_HOST_IS_READ(msg->type)) + /* MCTL_MAIN_DATA_CTL already set up */ + val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_READ; + else + val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_WRITE; + /* + * More than 2 bytes will not fit in a single packet, so it's + * time to set the "long not short" bit. One byte is used by + * the MIPI DCS command leaving just one byte for the payload + * in a short package. + */ + if (mipi_dsi_packet_format_is_long(msg->type)) + val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT; + val |= 0 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT; + val |= txlen << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT; + val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN; + val |= msg->type << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT; + writel(val, d->regs + DSI_DIRECT_CMD_MAIN_SETTINGS); + + /* MIPI DCS command is part of the data */ + if (txlen > 0) { + val = 0; + for (i = 0; i < 4 && i < txlen; i++) + val |= tx[i] << (i * 8); + } + writel(val, d->regs + DSI_DIRECT_CMD_WRDAT0); + if (txlen > 4) { + val = 0; + for (i = 0; i < 4 && (i + 4) < txlen; i++) + val |= tx[i + 4] << (i * 8); + writel(val, d->regs + DSI_DIRECT_CMD_WRDAT1); + } + if (txlen > 8) { + val = 0; + for (i = 0; i < 4 && (i + 8) < txlen; i++) + val |= tx[i + 8] << (i * 8); + writel(val, d->regs + DSI_DIRECT_CMD_WRDAT2); + } + if (txlen > 12) { + val = 0; + for (i = 0; i < 4 && (i + 12) < txlen; i++) + val |= tx[i + 12] << (i * 8); + writel(val, d->regs + DSI_DIRECT_CMD_WRDAT3); + } + + while (retries < 3) { + ret = mcde_dsi_execute_transfer(d, msg); + if (ret >= 0) + break; + retries++; + } + if (ret < 0 && retries) + dev_err(d->dev, "gave up after %d retries\n", retries); + + /* Clear any errors */ + writel(~0, d->regs + DSI_DIRECT_CMD_STS_CLR); + writel(~0, d->regs + DSI_CMD_MODE_STS_CLR); + + return ret; +} + +static const struct mipi_dsi_host_ops mcde_dsi_host_ops = { + .attach = mcde_dsi_host_attach, + .detach = mcde_dsi_host_detach, + .transfer = mcde_dsi_host_transfer, +}; + +/* This sends a direct (short) command to request TE */ +void mcde_dsi_te_request(struct mipi_dsi_device *mdsi) +{ + struct mcde_dsi *d; + u32 val; + + d = host_to_mcde_dsi(mdsi->host); + + /* Command "nature" TE request */ + val = DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_TE_REQ; + val |= 0 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT; + val |= 2 << DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT; + val |= DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN; + val |= MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM << + DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT; + writel(val, d->regs + DSI_DIRECT_CMD_MAIN_SETTINGS); + + /* Clear TE reveived and error status bits and enables them */ + writel(DSI_DIRECT_CMD_STS_CLR_TE_RECEIVED_CLR | + DSI_DIRECT_CMD_STS_CLR_ACKNOWLEDGE_WITH_ERR_RECEIVED_CLR, + d->regs + DSI_DIRECT_CMD_STS_CLR); + val = readl(d->regs + DSI_DIRECT_CMD_STS_CTL); + val |= DSI_DIRECT_CMD_STS_CTL_TE_RECEIVED_EN; + val |= DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_WITH_ERR_EN; + writel(val, d->regs + DSI_DIRECT_CMD_STS_CTL); + + /* Clear and enable no TE or TE missing status */ + writel(DSI_CMD_MODE_STS_CLR_ERR_NO_TE_CLR | + DSI_CMD_MODE_STS_CLR_ERR_TE_MISS_CLR, + d->regs + DSI_CMD_MODE_STS_CLR); + val = readl(d->regs + DSI_CMD_MODE_STS_CTL); + val |= DSI_CMD_MODE_STS_CTL_ERR_NO_TE_EN; + val |= DSI_CMD_MODE_STS_CTL_ERR_TE_MISS_EN; + writel(val, d->regs + DSI_CMD_MODE_STS_CTL); + + /* Send this TE request command */ + writel(1, d->regs + DSI_DIRECT_CMD_SEND); +} + +static void mcde_dsi_setup_video_mode(struct mcde_dsi *d, + const struct drm_display_mode *mode) +{ + /* cpp, characters per pixel, number of bytes per pixel */ + u8 cpp = mipi_dsi_pixel_format_to_bpp(d->mdsi->format) / 8; + u64 pclk; + u64 bpl; + int hfp; + int hbp; + int hsa; + u32 blkline_pck, line_duration; + u32 val; + + val = 0; + if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) + val |= DSI_VID_MAIN_CTL_BURST_MODE; + if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + val |= DSI_VID_MAIN_CTL_SYNC_PULSE_ACTIVE; + val |= DSI_VID_MAIN_CTL_SYNC_PULSE_HORIZONTAL; + } + /* RGB header and pixel mode */ + switch (d->mdsi->format) { + case MIPI_DSI_FMT_RGB565: + val |= MIPI_DSI_PACKED_PIXEL_STREAM_16 << + DSI_VID_MAIN_CTL_HEADER_SHIFT; + val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_16BITS; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + val |= MIPI_DSI_PACKED_PIXEL_STREAM_18 << + DSI_VID_MAIN_CTL_HEADER_SHIFT; + val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS; + break; + case MIPI_DSI_FMT_RGB666: + val |= MIPI_DSI_PIXEL_STREAM_3BYTE_18 + << DSI_VID_MAIN_CTL_HEADER_SHIFT; + val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS_LOOSE; + break; + case MIPI_DSI_FMT_RGB888: + val |= MIPI_DSI_PACKED_PIXEL_STREAM_24 << + DSI_VID_MAIN_CTL_HEADER_SHIFT; + val |= DSI_VID_MAIN_CTL_VID_PIXEL_MODE_24BITS; + break; + default: + dev_err(d->dev, "unknown pixel mode\n"); + return; + } + + /* TODO: TVG (test video generator) could be enabled here */ + + /* + * During vertical blanking: go to LP mode + * Like with the EOL setting, if this is not set, the EOL area will be + * filled with NULL or blanking packets in the vblank area. + * FIXME: some Samsung phones and display panels such as s6e63m0 use + * DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_BLANKING here instead, + * figure out how to properly configure that from the panel. + */ + val |= DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_0; + /* + * During EOL: go to LP mode. If this is not set, the EOL area will be + * filled with NULL or blanking packets. + */ + val |= DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0; + /* Recovery mode 1 */ + val |= 1 << DSI_VID_MAIN_CTL_RECOVERY_MODE_SHIFT; + /* All other fields zero */ + writel(val, d->regs + DSI_VID_MAIN_CTL); + + /* Vertical frame parameters are pretty straight-forward */ + val = mode->vdisplay << DSI_VID_VSIZE_VACT_LENGTH_SHIFT; + /* vertical front porch */ + val |= (mode->vsync_start - mode->vdisplay) + << DSI_VID_VSIZE_VFP_LENGTH_SHIFT; + /* vertical sync active */ + val |= (mode->vsync_end - mode->vsync_start) + << DSI_VID_VSIZE_VSA_LENGTH_SHIFT; + /* vertical back porch */ + val |= (mode->vtotal - mode->vsync_end) + << DSI_VID_VSIZE_VBP_LENGTH_SHIFT; + writel(val, d->regs + DSI_VID_VSIZE); + + /* + * Horizontal frame parameters: + * horizontal resolution is given in pixels but must be re-calculated + * into bytes since this is what the hardware expects, these registers + * define the payload size of the packet. + * + * hfp = horizontal front porch in bytes + * hbp = horizontal back porch in bytes + * hsa = horizontal sync active in bytes + * + * 6 + 2 is HFP header + checksum + */ + hfp = (mode->hsync_start - mode->hdisplay) * cpp - 6 - 2; + if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + /* + * Use sync pulse for sync: explicit HSA time + * 6 is HBP header + checksum + * 4 is RGB header + checksum + */ + hbp = (mode->htotal - mode->hsync_end) * cpp - 4 - 6; + /* + * 6 is HBP header + checksum + * 4 is HSW packet bytes + * 4 is RGB header + checksum + */ + hsa = (mode->hsync_end - mode->hsync_start) * cpp - 4 - 4 - 6; + } else { + /* + * Use event for sync: HBP includes both back porch and sync + * 6 is HBP header + checksum + * 4 is HSW packet bytes + * 4 is RGB header + checksum + */ + hbp = (mode->htotal - mode->hsync_start) * cpp - 4 - 4 - 6; + /* HSA is not present in this mode and set to 0 */ + hsa = 0; + } + if (hfp < 0) { + dev_info(d->dev, "hfp negative, set to 0\n"); + hfp = 0; + } + if (hbp < 0) { + dev_info(d->dev, "hbp negative, set to 0\n"); + hbp = 0; + } + if (hsa < 0) { + dev_info(d->dev, "hsa negative, set to 0\n"); + hsa = 0; + } + dev_dbg(d->dev, "hfp: %u, hbp: %u, hsa: %u bytes\n", + hfp, hbp, hsa); + + /* Frame parameters: horizontal sync active */ + val = hsa << DSI_VID_HSIZE1_HSA_LENGTH_SHIFT; + /* horizontal back porch */ + val |= hbp << DSI_VID_HSIZE1_HBP_LENGTH_SHIFT; + /* horizontal front porch */ + val |= hfp << DSI_VID_HSIZE1_HFP_LENGTH_SHIFT; + writel(val, d->regs + DSI_VID_HSIZE1); + + /* RGB data length (visible bytes on one scanline) */ + val = mode->hdisplay * cpp; + writel(val, d->regs + DSI_VID_HSIZE2); + dev_dbg(d->dev, "RGB length, visible area on a line: %u bytes\n", val); + + /* + * Calculate the time between two pixels in picoseconds using + * the supplied refresh rate and total resolution including + * porches and sync. + */ + /* (ps/s) / (pixels/s) = ps/pixels */ + pclk = DIV_ROUND_UP_ULL(1000000000000, (mode->clock * 1000)); + dev_dbg(d->dev, "picoseconds between two pixels: %llu\n", + pclk); + + /* + * How many bytes per line will this update frequency yield? + * + * Calculate the number of picoseconds for one scanline (1), then + * divide by 1000000000000 (2) to get in pixels per second we + * want to output. + * + * Multiply with number of bytes per second at this video display + * frequency (3) to get number of bytes transferred during this + * time. Notice that we use the frequency the display wants, + * not what we actually get from the DSI PLL, which is hs_freq. + * + * These arithmetics are done in a different order to avoid + * overflow. + */ + bpl = pclk * mode->htotal; /* (1) picoseconds per line */ + dev_dbg(d->dev, "picoseconds per line: %llu\n", bpl); + /* Multiply with bytes per second (3) */ + bpl *= (d->mdsi->hs_rate / 8); + /* Pixels per second (2) */ + bpl = DIV_ROUND_DOWN_ULL(bpl, 1000000); /* microseconds */ + bpl = DIV_ROUND_DOWN_ULL(bpl, 1000000); /* seconds */ + /* parallel transactions in all lanes */ + bpl *= d->mdsi->lanes; + dev_dbg(d->dev, + "calculated bytes per line: %llu @ %d Hz with HS %lu Hz\n", + bpl, drm_mode_vrefresh(mode), d->mdsi->hs_rate); + + /* + * 6 is header + checksum, header = 4 bytes, checksum = 2 bytes + * 4 is short packet for vsync/hsync + */ + if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + /* Set the event packet size to 0 (not used) */ + writel(0, d->regs + DSI_VID_BLKSIZE1); + /* + * FIXME: isn't the hsync width in pixels? The porch and + * sync area size is in pixels here, but this -6 + * seems to be for bytes. It looks like this in the vendor + * code though. Is it completely untested? + */ + blkline_pck = bpl - (mode->hsync_end - mode->hsync_start) - 6; + val = blkline_pck << DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_SHIFT; + writel(val, d->regs + DSI_VID_BLKSIZE2); + } else { + /* Set the sync pulse packet size to 0 (not used) */ + writel(0, d->regs + DSI_VID_BLKSIZE2); + /* Specifying payload size in bytes (-4-6 from manual) */ + blkline_pck = bpl - 4 - 6; + if (blkline_pck > 0x1FFF) + dev_err(d->dev, "blkline_pck too big %d bytes\n", + blkline_pck); + val = blkline_pck << DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_SHIFT; + val &= DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_MASK; + writel(val, d->regs + DSI_VID_BLKSIZE1); + } + + /* + * The line duration is used to scale back the frequency from + * the max frequency supported by the HS clock to the desired + * update frequency in vrefresh. + */ + line_duration = blkline_pck + 6; + /* + * The datasheet contains this complex condition to decreasing + * the line duration by 1 under very specific circumstances. + * Here we also imply that LP is used during burst EOL. + */ + if (d->mdsi->lanes == 2 && (hsa & 0x01) && (hfp & 0x01) + && (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)) + line_duration--; + line_duration = DIV_ROUND_CLOSEST(line_duration, d->mdsi->lanes); + dev_dbg(d->dev, "line duration %u bytes\n", line_duration); + val = line_duration << DSI_VID_DPHY_TIME_REG_LINE_DURATION_SHIFT; + /* + * This is the time to perform LP->HS on D-PHY + * FIXME: nowhere to get this from: DT property on the DSI? + * The manual says this is "system dependent". + * values like 48 and 72 seen in the vendor code. + */ + val |= 48 << DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_SHIFT; + writel(val, d->regs + DSI_VID_DPHY_TIME); + + /* + * See the manual figure 657 page 2203 for understanding the impact + * of the different burst mode settings. + */ + if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) { + int blkeol_pck, blkeol_duration; + /* + * Packet size at EOL for burst mode, this is only used + * if DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0 is NOT set, + * but we instead send NULL or blanking packets at EOL. + * This is given in number of bytes. + * + * See the manual page 2198 for the 13 reg_blkeol_pck bits. + */ + blkeol_pck = bpl - (mode->htotal * cpp) - 6; + if (blkeol_pck < 0) { + dev_err(d->dev, "video block does not fit on line!\n"); + dev_err(d->dev, + "calculated bytes per line: %llu @ %d Hz\n", + bpl, drm_mode_vrefresh(mode)); + dev_err(d->dev, + "bytes per line (blkline_pck) %u bytes\n", + blkline_pck); + dev_err(d->dev, + "blkeol_pck becomes %d bytes\n", blkeol_pck); + return; + } + dev_dbg(d->dev, "BLKEOL packet: %d bytes\n", blkeol_pck); + + val = readl(d->regs + DSI_VID_BLKSIZE1); + val &= ~DSI_VID_BLKSIZE1_BLKEOL_PCK_MASK; + val |= blkeol_pck << DSI_VID_BLKSIZE1_BLKEOL_PCK_SHIFT; + writel(val, d->regs + DSI_VID_BLKSIZE1); + /* Use the same value for exact burst limit */ + val = blkeol_pck << + DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_SHIFT; + val &= DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_MASK; + writel(val, d->regs + DSI_VID_VCA_SETTING2); + /* + * This BLKEOL duration is claimed to be the duration in clock + * cycles of the BLLP end-of-line (EOL) period for each line if + * DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0 is set. + * + * It is hard to trust the manuals' claim that this is in clock + * cycles as we mimic the behaviour of the vendor code, which + * appears to write a number of bytes that would have been + * transferred on a single lane. + * + * See the manual figure 657 page 2203 and page 2198 for the 13 + * reg_blkeol_duration bits. + * + * FIXME: should this also be set up also for non-burst mode + * according to figure 565 page 2202? + */ + blkeol_duration = DIV_ROUND_CLOSEST(blkeol_pck + 6, + d->mdsi->lanes); + dev_dbg(d->dev, "BLKEOL duration: %d clock cycles\n", + blkeol_duration); + + val = readl(d->regs + DSI_VID_PCK_TIME); + val &= ~DSI_VID_PCK_TIME_BLKEOL_DURATION_MASK; + val |= blkeol_duration << + DSI_VID_PCK_TIME_BLKEOL_DURATION_SHIFT; + writel(val, d->regs + DSI_VID_PCK_TIME); + + /* Max burst limit, this is given in bytes */ + val = readl(d->regs + DSI_VID_VCA_SETTING1); + val &= ~DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_MASK; + val |= (blkeol_pck - 6) << + DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_SHIFT; + writel(val, d->regs + DSI_VID_VCA_SETTING1); + } + + /* Maximum line limit */ + val = readl(d->regs + DSI_VID_VCA_SETTING2); + val &= ~DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_MASK; + val |= (blkline_pck - 6) << + DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_SHIFT; + writel(val, d->regs + DSI_VID_VCA_SETTING2); + dev_dbg(d->dev, "blkline pck: %d bytes\n", blkline_pck - 6); +} + +static void mcde_dsi_start(struct mcde_dsi *d) +{ + unsigned long hs_freq; + u32 val; + int i; + + /* No integration mode */ + writel(0, d->regs + DSI_MCTL_INTEGRATION_MODE); + + /* Enable the DSI port, from drivers/video/mcde/dsilink_v2.c */ + val = DSI_MCTL_MAIN_DATA_CTL_LINK_EN | + DSI_MCTL_MAIN_DATA_CTL_BTA_EN | + DSI_MCTL_MAIN_DATA_CTL_READ_EN | + DSI_MCTL_MAIN_DATA_CTL_REG_TE_EN; + if (!(d->mdsi->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)) + val |= DSI_MCTL_MAIN_DATA_CTL_HOST_EOT_GEN; + writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); + + /* Set a high command timeout, clear other fields */ + val = 0x3ff << DSI_CMD_MODE_CTL_TE_TIMEOUT_SHIFT; + writel(val, d->regs + DSI_CMD_MODE_CTL); + + /* + * UI_X4 is described as "unit interval times four" + * I guess since DSI packets are 4 bytes wide, one unit + * is one byte. + */ + hs_freq = clk_get_rate(d->hs_clk); + hs_freq /= 1000000; /* MHz */ + val = 4000 / hs_freq; + dev_dbg(d->dev, "UI value: %d\n", val); + val <<= DSI_MCTL_DPHY_STATIC_UI_X4_SHIFT; + val &= DSI_MCTL_DPHY_STATIC_UI_X4_MASK; + writel(val, d->regs + DSI_MCTL_DPHY_STATIC); + + /* + * Enable clocking: 0x0f (something?) between each burst, + * enable the second lane if needed, enable continuous clock if + * needed, enable switch into ULPM (ultra-low power mode) on + * all the lines. + */ + val = 0x0f << DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME_SHIFT; + if (d->mdsi->lanes == 2) + val |= DSI_MCTL_MAIN_PHY_CTL_LANE2_EN; + if (!(d->mdsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)) + val |= DSI_MCTL_MAIN_PHY_CTL_CLK_CONTINUOUS; + val |= DSI_MCTL_MAIN_PHY_CTL_CLK_ULPM_EN | + DSI_MCTL_MAIN_PHY_CTL_DAT1_ULPM_EN | + DSI_MCTL_MAIN_PHY_CTL_DAT2_ULPM_EN; + writel(val, d->regs + DSI_MCTL_MAIN_PHY_CTL); + + val = (1 << DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME_SHIFT) | + (1 << DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME_SHIFT); + writel(val, d->regs + DSI_MCTL_ULPOUT_TIME); + + writel(DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_0_90, + d->regs + DSI_DPHY_LANES_TRIM); + + /* High PHY timeout */ + val = (0x0f << DSI_MCTL_DPHY_TIMEOUT_CLK_DIV_SHIFT) | + (0x3fff << DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL_SHIFT) | + (0x3fff << DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL_SHIFT); + writel(val, d->regs + DSI_MCTL_DPHY_TIMEOUT); + + val = DSI_MCTL_MAIN_EN_PLL_START | + DSI_MCTL_MAIN_EN_CKLANE_EN | + DSI_MCTL_MAIN_EN_DAT1_EN | + DSI_MCTL_MAIN_EN_IF1_EN; + if (d->mdsi->lanes == 2) + val |= DSI_MCTL_MAIN_EN_DAT2_EN; + writel(val, d->regs + DSI_MCTL_MAIN_EN); + + /* Wait for the PLL to lock and the clock and data lines to come up */ + i = 0; + val = DSI_MCTL_MAIN_STS_PLL_LOCK | + DSI_MCTL_MAIN_STS_CLKLANE_READY | + DSI_MCTL_MAIN_STS_DAT1_READY; + if (d->mdsi->lanes == 2) + val |= DSI_MCTL_MAIN_STS_DAT2_READY; + while ((readl(d->regs + DSI_MCTL_MAIN_STS) & val) != val) { + /* Sleep for a millisecond */ + usleep_range(1000, 1500); + if (i++ == 100) { + dev_warn(d->dev, "DSI lanes did not start up\n"); + return; + } + } + + /* TODO needed? */ + + /* Command mode, clear IF1 ID */ + val = readl(d->regs + DSI_CMD_MODE_CTL); + /* + * If we enable low-power mode here, + * then display updates become really slow. + */ + if (d->mdsi->mode_flags & MIPI_DSI_MODE_LPM) + val |= DSI_CMD_MODE_CTL_IF1_LP_EN; + val &= ~DSI_CMD_MODE_CTL_IF1_ID_MASK; + writel(val, d->regs + DSI_CMD_MODE_CTL); + + /* Wait for DSI PHY to initialize */ + usleep_range(100, 200); + dev_info(d->dev, "DSI link enabled\n"); +} + +/* + * Notice that this is called from inside the display controller + * and not from the bridge callbacks. + */ +void mcde_dsi_enable(struct drm_bridge *bridge) +{ + struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); + unsigned long hs_freq, lp_freq; + u32 val; + int ret; + + /* Copy maximum clock frequencies */ + if (d->mdsi->lp_rate) + lp_freq = d->mdsi->lp_rate; + else + lp_freq = DSI_DEFAULT_LP_FREQ_HZ; + if (d->mdsi->hs_rate) + hs_freq = d->mdsi->hs_rate; + else + hs_freq = DSI_DEFAULT_HS_FREQ_HZ; + + /* Enable LP (Low Power, Energy Save, ES) and HS (High Speed) clocks */ + d->lp_freq = clk_round_rate(d->lp_clk, lp_freq); + ret = clk_set_rate(d->lp_clk, d->lp_freq); + if (ret) + dev_err(d->dev, "failed to set LP clock rate %lu Hz\n", + d->lp_freq); + + d->hs_freq = clk_round_rate(d->hs_clk, hs_freq); + ret = clk_set_rate(d->hs_clk, d->hs_freq); + if (ret) + dev_err(d->dev, "failed to set HS clock rate %lu Hz\n", + d->hs_freq); + + /* Start clocks */ + ret = clk_prepare_enable(d->lp_clk); + if (ret) + dev_err(d->dev, "failed to enable LP clock\n"); + else + dev_info(d->dev, "DSI LP clock rate %lu Hz\n", + d->lp_freq); + ret = clk_prepare_enable(d->hs_clk); + if (ret) + dev_err(d->dev, "failed to enable HS clock\n"); + else + dev_info(d->dev, "DSI HS clock rate %lu Hz\n", + d->hs_freq); + + /* Assert RESET through the PRCMU, active low */ + /* FIXME: which DSI block? */ + regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET, + PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0); + + usleep_range(100, 200); + + /* De-assert RESET again */ + regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET, + PRCM_DSI_SW_RESET_DSI0_SW_RESETN, + PRCM_DSI_SW_RESET_DSI0_SW_RESETN); + + /* Start up the hardware */ + mcde_dsi_start(d); + + if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + /* Set up the video mode from the DRM mode */ + mcde_dsi_setup_video_mode(d, d->mode); + + /* Put IF1 into video mode */ + val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); + val |= DSI_MCTL_MAIN_DATA_CTL_IF1_MODE; + writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); + + /* Disable command mode on IF1 */ + val = readl(d->regs + DSI_CMD_MODE_CTL); + val &= ~DSI_CMD_MODE_CTL_IF1_LP_EN; + writel(val, d->regs + DSI_CMD_MODE_CTL); + + /* Enable some error interrupts */ + val = readl(d->regs + DSI_VID_MODE_STS_CTL); + val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC; + val |= DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA; + writel(val, d->regs + DSI_VID_MODE_STS_CTL); + + /* Enable video mode */ + val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); + val |= DSI_MCTL_MAIN_DATA_CTL_VID_EN; + writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); + } else { + /* Command mode, clear IF1 ID */ + val = readl(d->regs + DSI_CMD_MODE_CTL); + /* + * If we enable low-power mode here + * the display updates become really slow. + */ + if (d->mdsi->mode_flags & MIPI_DSI_MODE_LPM) + val |= DSI_CMD_MODE_CTL_IF1_LP_EN; + val &= ~DSI_CMD_MODE_CTL_IF1_ID_MASK; + writel(val, d->regs + DSI_CMD_MODE_CTL); + } + + dev_info(d->dev, "enabled MCDE DSI master\n"); +} + +static void mcde_dsi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj) +{ + struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); + + if (!d->mdsi) { + dev_err(d->dev, "no DSI device attached to encoder!\n"); + return; + } + + d->mode = mode; + + dev_info(d->dev, "set DSI master to %dx%d %u Hz %s mode\n", + mode->hdisplay, mode->vdisplay, mode->clock * 1000, + (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? "VIDEO" : "CMD" + ); +} + +static void mcde_dsi_wait_for_command_mode_stop(struct mcde_dsi *d) +{ + u32 val; + int i; + + /* + * Wait until we get out of command mode + * CSM = Command State Machine + */ + i = 0; + val = DSI_CMD_MODE_STS_CSM_RUNNING; + while ((readl(d->regs + DSI_CMD_MODE_STS) & val) == val) { + /* Sleep for a millisecond */ + usleep_range(1000, 2000); + if (i++ == 100) { + dev_warn(d->dev, + "could not get out of command mode\n"); + return; + } + } +} + +static void mcde_dsi_wait_for_video_mode_stop(struct mcde_dsi *d) +{ + u32 val; + int i; + + /* Wait until we get out og video mode */ + i = 0; + val = DSI_VID_MODE_STS_VSG_RUNNING; + while ((readl(d->regs + DSI_VID_MODE_STS) & val) == val) { + /* Sleep for a millisecond */ + usleep_range(1000, 2000); + if (i++ == 100) { + dev_warn(d->dev, + "could not get out of video mode\n"); + return; + } + } +} + +/* + * Notice that this is called from inside the display controller + * and not from the bridge callbacks. + */ +void mcde_dsi_disable(struct drm_bridge *bridge) +{ + struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); + u32 val; + + if (d->mdsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + /* Stop video mode */ + val = readl(d->regs + DSI_MCTL_MAIN_DATA_CTL); + val &= ~DSI_MCTL_MAIN_DATA_CTL_VID_EN; + writel(val, d->regs + DSI_MCTL_MAIN_DATA_CTL); + mcde_dsi_wait_for_video_mode_stop(d); + } else { + /* Stop command mode */ + mcde_dsi_wait_for_command_mode_stop(d); + } + + /* + * Stop clocks and terminate any DSI traffic here so the panel can + * send commands to shut down the display using DSI direct write until + * this point. + */ + + /* Disable all error interrupts */ + writel(0, d->regs + DSI_VID_MODE_STS_CTL); + clk_disable_unprepare(d->hs_clk); + clk_disable_unprepare(d->lp_clk); +} + +static int mcde_dsi_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); + struct drm_device *drm = bridge->dev; + + if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { + dev_err(d->dev, "we need atomic updates\n"); + return -ENOTSUPP; + } + + /* Attach the DSI bridge to the output (panel etc) bridge */ + return drm_bridge_attach(encoder, d->bridge.next_bridge, bridge, flags); +} + +static const struct drm_bridge_funcs mcde_dsi_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .attach = mcde_dsi_bridge_attach, + .mode_set = mcde_dsi_bridge_mode_set, +}; + +static int mcde_dsi_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_device *drm = data; + struct mcde *mcde = to_mcde(drm); + struct mcde_dsi *d = dev_get_drvdata(dev); + struct device_node *child; + struct drm_panel *panel = NULL; + struct drm_bridge *bridge __free(drm_bridge_put) = NULL; + + if (!of_get_available_child_count(dev->of_node)) { + dev_info(dev, "unused DSI interface\n"); + d->unused = true; + return 0; + } + d->mcde = mcde; + /* If the display attached before binding, set this up */ + if (d->mdsi) + mcde_dsi_attach_to_mcde(d); + + /* Obtain the clocks */ + d->hs_clk = devm_clk_get(dev, "hs"); + if (IS_ERR(d->hs_clk)) { + dev_err(dev, "unable to get HS clock\n"); + return PTR_ERR(d->hs_clk); + } + + d->lp_clk = devm_clk_get(dev, "lp"); + if (IS_ERR(d->lp_clk)) { + dev_err(dev, "unable to get LP clock\n"); + return PTR_ERR(d->lp_clk); + } + + /* Look for a panel as a child to this node */ + for_each_available_child_of_node(dev->of_node, child) { + panel = of_drm_find_panel(child); + if (IS_ERR(panel)) { + dev_err(dev, "failed to find panel try bridge (%ld)\n", + PTR_ERR(panel)); + panel = NULL; + + bridge = of_drm_find_and_get_bridge(child); + if (!bridge) { + dev_err(dev, "failed to find bridge\n"); + of_node_put(child); + return -EINVAL; + } + } + + if (panel || bridge) { + of_node_put(child); + break; + } + } + if (panel) { + bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_DSI); + drm_panel_put(panel); + if (IS_ERR(bridge)) { + dev_err(dev, "error adding panel bridge\n"); + return PTR_ERR(bridge); + } + drm_bridge_get(bridge); + dev_info(dev, "connected to panel\n"); + d->panel = panel; + } else if (bridge) { + /* TODO: AV8100 HDMI encoder goes here for example */ + dev_info(dev, "connected to non-panel bridge (unsupported)\n"); + return -ENODEV; + } else { + dev_err(dev, "no panel or bridge\n"); + return -ENODEV; + } + + d->bridge.next_bridge = drm_bridge_get(bridge); + + /* Create a bridge for this DSI channel */ + d->bridge.of_node = dev->of_node; + drm_bridge_add(&d->bridge); + + /* TODO: first come first serve, use a list */ + mcde->bridge = &d->bridge; + + dev_info(dev, "initialized MCDE DSI bridge\n"); + + return 0; +} + +static void mcde_dsi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct mcde_dsi *d = dev_get_drvdata(dev); + + if (d->panel) + drm_panel_bridge_remove(d->bridge.next_bridge); + regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET, + PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0); +} + +static const struct component_ops mcde_dsi_component_ops = { + .bind = mcde_dsi_bind, + .unbind = mcde_dsi_unbind, +}; + +static int mcde_dsi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mcde_dsi *d; + struct mipi_dsi_host *host; + u32 dsi_id; + int ret; + + d = devm_drm_bridge_alloc(dev, struct mcde_dsi, bridge, &mcde_dsi_bridge_funcs); + if (IS_ERR(d)) + return PTR_ERR(d); + d->dev = dev; + platform_set_drvdata(pdev, d); + + /* Get a handle on the PRCMU so we can do reset */ + d->prcmu = + syscon_regmap_lookup_by_compatible("stericsson,db8500-prcmu"); + if (IS_ERR(d->prcmu)) { + dev_err(dev, "no PRCMU regmap\n"); + return PTR_ERR(d->prcmu); + } + + d->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(d->regs)) + return PTR_ERR(d->regs); + + dsi_id = readl(d->regs + DSI_ID_REG); + dev_info(dev, "HW revision 0x%08x\n", dsi_id); + + host = &d->dsi_host; + host->dev = dev; + host->ops = &mcde_dsi_host_ops; + ret = mipi_dsi_host_register(host); + if (ret < 0) { + dev_err(dev, "failed to register DSI host: %d\n", ret); + return ret; + } + dev_info(dev, "registered DSI host\n"); + + platform_set_drvdata(pdev, d); + return component_add(dev, &mcde_dsi_component_ops); +} + +static void mcde_dsi_remove(struct platform_device *pdev) +{ + struct mcde_dsi *d = platform_get_drvdata(pdev); + + component_del(&pdev->dev, &mcde_dsi_component_ops); + mipi_dsi_host_unregister(&d->dsi_host); +} + +static const struct of_device_id mcde_dsi_of_match[] = { + { + .compatible = "ste,mcde-dsi", + }, + {}, +}; + +struct platform_driver mcde_dsi_driver = { + .driver = { + .name = "mcde-dsi", + .of_match_table = mcde_dsi_of_match, + }, + .probe = mcde_dsi_probe, + .remove = mcde_dsi_remove, +}; diff --git a/drivers/gpu/drm/mcde/mcde_dsi_regs.h b/drivers/gpu/drm/mcde/mcde_dsi_regs.h new file mode 100644 index 000000000..16551af10 --- /dev/null +++ b/drivers/gpu/drm/mcde/mcde_dsi_regs.h @@ -0,0 +1,385 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DRM_MCDE_DSI_REGS +#define __DRM_MCDE_DSI_REGS + +#define DSI_MCTL_INTEGRATION_MODE 0x00000000 + +#define DSI_MCTL_MAIN_DATA_CTL 0x00000004 +#define DSI_MCTL_MAIN_DATA_CTL_LINK_EN BIT(0) +#define DSI_MCTL_MAIN_DATA_CTL_IF1_MODE BIT(1) +#define DSI_MCTL_MAIN_DATA_CTL_VID_EN BIT(2) +#define DSI_MCTL_MAIN_DATA_CTL_TVG_SEL BIT(3) +#define DSI_MCTL_MAIN_DATA_CTL_TBG_SEL BIT(4) +#define DSI_MCTL_MAIN_DATA_CTL_IF1_TE_EN BIT(5) +#define DSI_MCTL_MAIN_DATA_CTL_IF2_TE_EN BIT(6) +#define DSI_MCTL_MAIN_DATA_CTL_REG_TE_EN BIT(7) +#define DSI_MCTL_MAIN_DATA_CTL_READ_EN BIT(8) +#define DSI_MCTL_MAIN_DATA_CTL_BTA_EN BIT(9) +#define DSI_MCTL_MAIN_DATA_CTL_DISP_GEN_ECC BIT(10) +#define DSI_MCTL_MAIN_DATA_CTL_DISP_GEN_CHECKSUM BIT(11) +#define DSI_MCTL_MAIN_DATA_CTL_HOST_EOT_GEN BIT(12) +#define DSI_MCTL_MAIN_DATA_CTL_DISP_EOT_GEN BIT(13) +#define DSI_MCTL_MAIN_DATA_CTL_DLX_REMAP_EN BIT(14) +#define DSI_MCTL_MAIN_DATA_CTL_TE_POLLING_EN BIT(15) + +#define DSI_MCTL_MAIN_PHY_CTL 0x00000008 +#define DSI_MCTL_MAIN_PHY_CTL_LANE2_EN BIT(0) +#define DSI_MCTL_MAIN_PHY_CTL_FORCE_STOP_MODE BIT(1) +#define DSI_MCTL_MAIN_PHY_CTL_CLK_CONTINUOUS BIT(2) +#define DSI_MCTL_MAIN_PHY_CTL_CLK_ULPM_EN BIT(3) +#define DSI_MCTL_MAIN_PHY_CTL_DAT1_ULPM_EN BIT(4) +#define DSI_MCTL_MAIN_PHY_CTL_DAT2_ULPM_EN BIT(5) +#define DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME_SHIFT 6 +#define DSI_MCTL_MAIN_PHY_CTL_WAIT_BURST_TIME_MASK 0x000003C0 +#define DSI_MCTL_MAIN_PHY_CTL_CLOCK_FORCE_STOP_MODE BIT(10) + +#define DSI_MCTL_PLL_CTL 0x0000000C +#define DSI_MCTL_LANE_STS 0x00000010 + +#define DSI_MCTL_DPHY_TIMEOUT 0x00000014 +#define DSI_MCTL_DPHY_TIMEOUT_CLK_DIV_SHIFT 0 +#define DSI_MCTL_DPHY_TIMEOUT_CLK_DIV_MASK 0x0000000F +#define DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL_SHIFT 4 +#define DSI_MCTL_DPHY_TIMEOUT_HSTX_TO_VAL_MASK 0x0003FFF0 +#define DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL_SHIFT 18 +#define DSI_MCTL_DPHY_TIMEOUT_LPRX_TO_VAL_MASK 0xFFFC0000 + +#define DSI_MCTL_ULPOUT_TIME 0x00000018 +#define DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME_SHIFT 0 +#define DSI_MCTL_ULPOUT_TIME_CKLANE_ULPOUT_TIME_MASK 0x000001FF +#define DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME_SHIFT 9 +#define DSI_MCTL_ULPOUT_TIME_DATA_ULPOUT_TIME_MASK 0x0003FE00 + +#define DSI_MCTL_DPHY_STATIC 0x0000001C +#define DSI_MCTL_DPHY_STATIC_SWAP_PINS_CLK BIT(0) +#define DSI_MCTL_DPHY_STATIC_HS_INVERT_CLK BIT(1) +#define DSI_MCTL_DPHY_STATIC_SWAP_PINS_DAT1 BIT(2) +#define DSI_MCTL_DPHY_STATIC_HS_INVERT_DAT1 BIT(3) +#define DSI_MCTL_DPHY_STATIC_SWAP_PINS_DAT2 BIT(4) +#define DSI_MCTL_DPHY_STATIC_HS_INVERT_DAT2 BIT(5) +#define DSI_MCTL_DPHY_STATIC_UI_X4_SHIFT 6 +#define DSI_MCTL_DPHY_STATIC_UI_X4_MASK 0x00000FC0 + +#define DSI_MCTL_MAIN_EN 0x00000020 +#define DSI_MCTL_MAIN_EN_PLL_START BIT(0) +#define DSI_MCTL_MAIN_EN_CKLANE_EN BIT(3) +#define DSI_MCTL_MAIN_EN_DAT1_EN BIT(4) +#define DSI_MCTL_MAIN_EN_DAT2_EN BIT(5) +#define DSI_MCTL_MAIN_EN_CLKLANE_ULPM_REQ BIT(6) +#define DSI_MCTL_MAIN_EN_DAT1_ULPM_REQ BIT(7) +#define DSI_MCTL_MAIN_EN_DAT2_ULPM_REQ BIT(8) +#define DSI_MCTL_MAIN_EN_IF1_EN BIT(9) +#define DSI_MCTL_MAIN_EN_IF2_EN BIT(10) + +#define DSI_MCTL_MAIN_STS 0x00000024 +#define DSI_MCTL_MAIN_STS_PLL_LOCK BIT(0) +#define DSI_MCTL_MAIN_STS_CLKLANE_READY BIT(1) +#define DSI_MCTL_MAIN_STS_DAT1_READY BIT(2) +#define DSI_MCTL_MAIN_STS_DAT2_READY BIT(3) +#define DSI_MCTL_MAIN_STS_HSTX_TO_ERR BIT(4) +#define DSI_MCTL_MAIN_STS_LPRX_TO_ERR BIT(5) +#define DSI_MCTL_MAIN_STS_CRS_UNTERM_PCK BIT(6) +#define DSI_MCTL_MAIN_STS_VRS_UNTERM_PCK BIT(7) + +#define DSI_MCTL_DPHY_ERR 0x00000028 +#define DSI_INT_VID_RDDATA 0x00000030 +#define DSI_INT_VID_GNT 0x00000034 +#define DSI_INT_CMD_RDDATA 0x00000038 +#define DSI_INT_CMD_GNT 0x0000003C +#define DSI_INT_INTERRUPT_CTL 0x00000040 + +#define DSI_CMD_MODE_CTL 0x00000050 +#define DSI_CMD_MODE_CTL_IF1_ID_SHIFT 0 +#define DSI_CMD_MODE_CTL_IF1_ID_MASK 0x00000003 +#define DSI_CMD_MODE_CTL_IF2_ID_SHIFT 2 +#define DSI_CMD_MODE_CTL_IF2_ID_MASK 0x0000000C +#define DSI_CMD_MODE_CTL_IF1_LP_EN BIT(4) +#define DSI_CMD_MODE_CTL_IF2_LP_EN BIT(5) +#define DSI_CMD_MODE_CTL_ARB_MODE BIT(6) +#define DSI_CMD_MODE_CTL_ARB_PRI BIT(7) +#define DSI_CMD_MODE_CTL_FIL_VALUE_SHIFT 8 +#define DSI_CMD_MODE_CTL_FIL_VALUE_MASK 0x0000FF00 +#define DSI_CMD_MODE_CTL_TE_TIMEOUT_SHIFT 16 +#define DSI_CMD_MODE_CTL_TE_TIMEOUT_MASK 0x03FF0000 + +#define DSI_CMD_MODE_STS 0x00000054 +#define DSI_CMD_MODE_STS_ERR_NO_TE BIT(0) +#define DSI_CMD_MODE_STS_ERR_TE_MISS BIT(1) +#define DSI_CMD_MODE_STS_ERR_SDI1_UNDERRUN BIT(2) +#define DSI_CMD_MODE_STS_ERR_SDI2_UNDERRUN BIT(3) +#define DSI_CMD_MODE_STS_ERR_UNWANTED_RD BIT(4) +#define DSI_CMD_MODE_STS_CSM_RUNNING BIT(5) + +#define DSI_DIRECT_CMD_SEND 0x00000060 + +#define DSI_DIRECT_CMD_MAIN_SETTINGS 0x00000064 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_SHIFT 0 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_MASK 0x00000007 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_WRITE 0 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_READ 1 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_TE_REQ 4 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_TRIG_REQ 5 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_NAT_BTA_REQ 6 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LONGNOTSHORT BIT(3) +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_SHIFT 8 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_HEAD_MASK 0x00003F00 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_ID_SHIFT 14 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_SIZE_SHIFT 16 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_CMD_LP_EN BIT(21) +#define DSI_DIRECT_CMD_MAIN_SETTINGS_TRIGGER_VAL_SHIFT 24 +#define DSI_DIRECT_CMD_MAIN_SETTINGS_TRIGGER_VAL_MASK 0x0F000000 + +#define DSI_DIRECT_CMD_STS 0x00000068 +#define DSI_DIRECT_CMD_STS_CMD_TRANSMISSION BIT(0) +#define DSI_DIRECT_CMD_STS_WRITE_COMPLETED BIT(1) +#define DSI_DIRECT_CMD_STS_TRIGGER_COMPLETED BIT(2) +#define DSI_DIRECT_CMD_STS_READ_COMPLETED BIT(3) +#define DSI_DIRECT_CMD_STS_ACKNOWLEDGE_RECEIVED_SHIFT BIT(4) +#define DSI_DIRECT_CMD_STS_ACKNOWLEDGE_WITH_ERR_RECEIVED BIT(5) +#define DSI_DIRECT_CMD_STS_TRIGGER_RECEIVED BIT(6) +#define DSI_DIRECT_CMD_STS_TE_RECEIVED BIT(7) +#define DSI_DIRECT_CMD_STS_BTA_COMPLETED BIT(8) +#define DSI_DIRECT_CMD_STS_BTA_FINISHED BIT(9) +#define DSI_DIRECT_CMD_STS_READ_COMPLETED_WITH_ERR BIT(10) +#define DSI_DIRECT_CMD_STS_TRIGGER_VAL_MASK 0x00007800 +#define DSI_DIRECT_CMD_STS_TRIGGER_VAL_SHIFT 11 +#define DSI_DIRECT_CMD_STS_ACK_VAL_SHIFT 16 +#define DSI_DIRECT_CMD_STS_ACK_VAL_MASK 0xFFFF0000 + +#define DSI_DIRECT_CMD_RD_INIT 0x0000006C +#define DSI_DIRECT_CMD_RD_INIT_RESET_SHIFT 0 +#define DSI_DIRECT_CMD_RD_INIT_RESET_MASK 0xFFFFFFFF + +#define DSI_DIRECT_CMD_WRDAT0 0x00000070 +#define DSI_DIRECT_CMD_WRDAT1 0x00000074 +#define DSI_DIRECT_CMD_WRDAT2 0x00000078 +#define DSI_DIRECT_CMD_WRDAT3 0x0000007C + +#define DSI_DIRECT_CMD_RDDAT 0x00000080 + +#define DSI_DIRECT_CMD_RD_PROPERTY 0x00000084 +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_SIZE_SHIFT 0 +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_SIZE_MASK 0x0000FFFF +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_ID_SHIFT 16 +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_ID_MASK 0x00030000 +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_DCSNOTGENERIC_SHIFT 18 +#define DSI_DIRECT_CMD_RD_PROPERTY_RD_DCSNOTGENERIC_MASK 0x00040000 + +#define DSI_DIRECT_CMD_RD_STS 0x00000088 + +#define DSI_VID_MAIN_CTL 0x00000090 +#define DSI_VID_MAIN_CTL_START_MODE_SHIFT 0 +#define DSI_VID_MAIN_CTL_START_MODE_MASK 0x00000003 +#define DSI_VID_MAIN_CTL_STOP_MODE_SHIFT 2 +#define DSI_VID_MAIN_CTL_STOP_MODE_MASK 0x0000000C +#define DSI_VID_MAIN_CTL_VID_ID_SHIFT 4 +#define DSI_VID_MAIN_CTL_VID_ID_MASK 0x00000030 +#define DSI_VID_MAIN_CTL_HEADER_SHIFT 6 +#define DSI_VID_MAIN_CTL_HEADER_MASK 0x00000FC0 +#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_16BITS 0 +#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS BIT(12) +#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_18BITS_LOOSE BIT(13) +#define DSI_VID_MAIN_CTL_VID_PIXEL_MODE_24BITS (BIT(12) | BIT(13)) +#define DSI_VID_MAIN_CTL_BURST_MODE BIT(14) +#define DSI_VID_MAIN_CTL_SYNC_PULSE_ACTIVE BIT(15) +#define DSI_VID_MAIN_CTL_SYNC_PULSE_HORIZONTAL BIT(16) +#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_NULL 0 +#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_BLANKING BIT(17) +#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_0 BIT(18) +#define DSI_VID_MAIN_CTL_REG_BLKLINE_MODE_LP_1 (BIT(17) | BIT(18)) +#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_NULL 0 +#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_BLANKING BIT(19) +#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_0 BIT(20) +#define DSI_VID_MAIN_CTL_REG_BLKEOL_MODE_LP_1 (BIT(19) | BIT(20)) +#define DSI_VID_MAIN_CTL_RECOVERY_MODE_SHIFT 21 +#define DSI_VID_MAIN_CTL_RECOVERY_MODE_MASK 0x00600000 + +#define DSI_VID_VSIZE 0x00000094 +#define DSI_VID_VSIZE_VSA_LENGTH_SHIFT 0 +#define DSI_VID_VSIZE_VSA_LENGTH_MASK 0x0000003F +#define DSI_VID_VSIZE_VBP_LENGTH_SHIFT 6 +#define DSI_VID_VSIZE_VBP_LENGTH_MASK 0x00000FC0 +#define DSI_VID_VSIZE_VFP_LENGTH_SHIFT 12 +#define DSI_VID_VSIZE_VFP_LENGTH_MASK 0x000FF000 +#define DSI_VID_VSIZE_VACT_LENGTH_SHIFT 20 +#define DSI_VID_VSIZE_VACT_LENGTH_MASK 0x7FF00000 + +#define DSI_VID_HSIZE1 0x00000098 +#define DSI_VID_HSIZE1_HSA_LENGTH_SHIFT 0 +#define DSI_VID_HSIZE1_HSA_LENGTH_MASK 0x000003FF +#define DSI_VID_HSIZE1_HBP_LENGTH_SHIFT 10 +#define DSI_VID_HSIZE1_HBP_LENGTH_MASK 0x000FFC00 +#define DSI_VID_HSIZE1_HFP_LENGTH_SHIFT 20 +#define DSI_VID_HSIZE1_HFP_LENGTH_MASK 0x7FF00000 + +#define DSI_VID_HSIZE2 0x0000009C +#define DSI_VID_HSIZE2_RGB_SIZE_SHIFT 0 +#define DSI_VID_HSIZE2_RGB_SIZE_MASK 0x00001FFF + +#define DSI_VID_BLKSIZE1 0x000000A0 +#define DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_SHIFT 0 +#define DSI_VID_BLKSIZE1_BLKLINE_EVENT_PCK_MASK 0x00001FFF +#define DSI_VID_BLKSIZE1_BLKEOL_PCK_SHIFT 13 +#define DSI_VID_BLKSIZE1_BLKEOL_PCK_MASK 0x03FFE000 + +#define DSI_VID_BLKSIZE2 0x000000A4 +#define DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_SHIFT 0 +#define DSI_VID_BLKSIZE2_BLKLINE_PULSE_PCK_MASK 0x00001FFF + +#define DSI_VID_PCK_TIME 0x000000A8 +#define DSI_VID_PCK_TIME_BLKEOL_DURATION_SHIFT 0 +#define DSI_VID_PCK_TIME_BLKEOL_DURATION_MASK 0x00000FFF + +#define DSI_VID_DPHY_TIME 0x000000AC +#define DSI_VID_DPHY_TIME_REG_LINE_DURATION_SHIFT 0 +#define DSI_VID_DPHY_TIME_REG_LINE_DURATION_MASK 0x00001FFF +#define DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_SHIFT 13 +#define DSI_VID_DPHY_TIME_REG_WAKEUP_TIME_MASK 0x00FFE000 + +#define DSI_VID_MODE_STS 0x000000BC +#define DSI_VID_MODE_STS_VSG_RUNNING BIT(0) +#define DSI_VID_MODE_STS_ERR_MISSING_DATA BIT(1) +#define DSI_VID_MODE_STS_ERR_MISSING_HSYNC BIT(2) +#define DSI_VID_MODE_STS_ERR_MISSING_VSYNC BIT(3) +#define DSI_VID_MODE_STS_REG_ERR_SMALL_LENGTH BIT(4) +#define DSI_VID_MODE_STS_REG_ERR_SMALL_HEIGHT BIT(5) +#define DSI_VID_MODE_STS_ERR_BURSTWRITE BIT(6) +#define DSI_VID_MODE_STS_ERR_LINEWRITE BIT(7) +#define DSI_VID_MODE_STS_ERR_LONGREAD BIT(8) +#define DSI_VID_MODE_STS_ERR_VRS_WRONG_LENGTH BIT(9) +#define DSI_VID_MODE_STS_VSG_RECOVERY BIT(10) + +#define DSI_VID_VCA_SETTING1 0x000000C0 +#define DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_SHIFT 0 +#define DSI_VID_VCA_SETTING1_MAX_BURST_LIMIT_MASK 0x0000FFFF +#define DSI_VID_VCA_SETTING1_BURST_LP BIT(16) + +#define DSI_VID_VCA_SETTING2 0x000000C4 +#define DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_SHIFT 0 +#define DSI_VID_VCA_SETTING2_EXACT_BURST_LIMIT_MASK 0x0000FFFF +#define DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_SHIFT 16 +#define DSI_VID_VCA_SETTING2_MAX_LINE_LIMIT_MASK 0xFFFF0000 + +#define DSI_CMD_MODE_STS_CTL 0x000000F4 +#define DSI_CMD_MODE_STS_CTL_ERR_NO_TE_EN BIT(0) +#define DSI_CMD_MODE_STS_CTL_ERR_TE_MISS_EN BIT(1) +#define DSI_CMD_MODE_STS_CTL_ERR_SDI1_UNDERRUN_EN BIT(2) +#define DSI_CMD_MODE_STS_CTL_ERR_SDI2_UNDERRUN_EN BIT(3) +#define DSI_CMD_MODE_STS_CTL_ERR_UNWANTED_RD_EN BIT(4) +#define DSI_CMD_MODE_STS_CTL_CSM_RUNNING_EN BIT(5) +#define DSI_CMD_MODE_STS_CTL_ERR_NO_TE_EDGE BIT(16) +#define DSI_CMD_MODE_STS_CTL_ERR_TE_MISS_EDGE BIT(17) +#define DSI_CMD_MODE_STS_CTL_ERR_SDI1_UNDERRUN_EDGE BIT(18) +#define DSI_CMD_MODE_STS_CTL_ERR_SDI2_UNDERRUN_EDGE BIT(19) +#define DSI_CMD_MODE_STS_CTL_ERR_UNWANTED_RD_EDGE BIT(20) +#define DSI_CMD_MODE_STS_CTL_CSM_RUNNING_EDGE BIT(21) + +#define DSI_DIRECT_CMD_STS_CTL 0x000000F8 +#define DSI_DIRECT_CMD_STS_CTL_CMD_TRANSMISSION_EN BIT(0) +#define DSI_DIRECT_CMD_STS_CTL_WRITE_COMPLETED_EN BIT(1) +#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_COMPLETED_EN BIT(2) +#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_EN BIT(3) +#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_RECEIVED_EN BIT(4) +#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_WITH_ERR_EN BIT(5) +#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_RECEIVED_EN BIT(6) +#define DSI_DIRECT_CMD_STS_CTL_TE_RECEIVED_EN BIT(7) +#define DSI_DIRECT_CMD_STS_CTL_BTA_COMPLETED_EN BIT(8) +#define DSI_DIRECT_CMD_STS_CTL_BTA_FINISHED_EN BIT(9) +#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_WITH_ERR_EN BIT(10) +#define DSI_DIRECT_CMD_STS_CTL_CMD_TRANSMISSION_EDGE BIT(16) +#define DSI_DIRECT_CMD_STS_CTL_WRITE_COMPLETED_EDGE BIT(17) +#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_COMPLETED_EDGE BIT(18) +#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_EDGE BIT(19) +#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_RECEIVED_EDGE BIT(20) +#define DSI_DIRECT_CMD_STS_CTL_ACKNOWLEDGE_WITH_ERR_EDGE BIT(21) +#define DSI_DIRECT_CMD_STS_CTL_TRIGGER_RECEIVED_EDGE BIT(22) +#define DSI_DIRECT_CMD_STS_CTL_TE_RECEIVED_EDGE BIT(23) +#define DSI_DIRECT_CMD_STS_CTL_BTA_COMPLETED_EDGE BIT(24) +#define DSI_DIRECT_CMD_STS_CTL_BTA_FINISHED_EDGE BIT(25) +#define DSI_DIRECT_CMD_STS_CTL_READ_COMPLETED_WITH_ERR_EDGE BIT(26) + +#define DSI_VID_MODE_STS_CTL 0x00000100 +#define DSI_VID_MODE_STS_CTL_VSG_RUNNING BIT(0) +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA BIT(1) +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_HSYNC BIT(2) +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC BIT(3) +#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_LENGTH BIT(4) +#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_HEIGHT BIT(5) +#define DSI_VID_MODE_STS_CTL_ERR_BURSTWRITE BIT(6) +#define DSI_VID_MODE_STS_CTL_ERR_LONGWRITE BIT(7) +#define DSI_VID_MODE_STS_CTL_ERR_LONGREAD BIT(8) +#define DSI_VID_MODE_STS_CTL_ERR_VRS_WRONG_LENGTH BIT(9) +#define DSI_VID_MODE_STS_CTL_VSG_RUNNING_EDGE BIT(16) +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_DATA_EDGE BIT(17) +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_HSYNC_EDGE BIT(18) +#define DSI_VID_MODE_STS_CTL_ERR_MISSING_VSYNC_EDGE BIT(19) +#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_LENGTH_EDGE BIT(20) +#define DSI_VID_MODE_STS_CTL_REG_ERR_SMALL_HEIGHT_EDGE BIT(21) +#define DSI_VID_MODE_STS_CTL_ERR_BURSTWRITE_EDGE BIT(22) +#define DSI_VID_MODE_STS_CTL_ERR_LONGWRITE_EDGE BIT(23) +#define DSI_VID_MODE_STS_CTL_ERR_LONGREAD_EDGE BIT(24) +#define DSI_VID_MODE_STS_CTL_ERR_VRS_WRONG_LENGTH_EDGE BIT(25) +#define DSI_VID_MODE_STS_CTL_VSG_RECOVERY_EDGE BIT(26) + +#define DSI_TG_STS_CTL 0x00000104 +#define DSI_MCTL_DHPY_ERR_CTL 0x00000108 +#define DSI_MCTL_MAIN_STS_CLR 0x00000110 + +#define DSI_CMD_MODE_STS_CLR 0x00000114 +#define DSI_CMD_MODE_STS_CLR_ERR_NO_TE_CLR BIT(0) +#define DSI_CMD_MODE_STS_CLR_ERR_TE_MISS_CLR BIT(1) +#define DSI_CMD_MODE_STS_CLR_ERR_SDI1_UNDERRUN_CLR BIT(2) +#define DSI_CMD_MODE_STS_CLR_ERR_SDI2_UNDERRUN_CLR BIT(3) +#define DSI_CMD_MODE_STS_CLR_ERR_UNWANTED_RD_CLR BIT(4) +#define DSI_CMD_MODE_STS_CLR_CSM_RUNNING_CLR BIT(5) + +#define DSI_DIRECT_CMD_STS_CLR 0x00000118 +#define DSI_DIRECT_CMD_STS_CLR_CMD_TRANSMISSION_CLR BIT(0) +#define DSI_DIRECT_CMD_STS_CLR_WRITE_COMPLETED_CLR BIT(1) +#define DSI_DIRECT_CMD_STS_CLR_TRIGGER_COMPLETED_CLR BIT(2) +#define DSI_DIRECT_CMD_STS_CLR_READ_COMPLETED_CLR BIT(3) +#define DSI_DIRECT_CMD_STS_CLR_ACKNOWLEDGE_RECEIVED_CLR BIT(4) +#define DSI_DIRECT_CMD_STS_CLR_ACKNOWLEDGE_WITH_ERR_RECEIVED_CLR BIT(5) +#define DSI_DIRECT_CMD_STS_CLR_TRIGGER_RECEIVED_CLR BIT(6) +#define DSI_DIRECT_CMD_STS_CLR_TE_RECEIVED_CLR BIT(7) +#define DSI_DIRECT_CMD_STS_CLR_BTA_COMPLETED_CLR BIT(8) +#define DSI_DIRECT_CMD_STS_CLR_BTA_FINISHED_CLR BIT(9) +#define DSI_DIRECT_CMD_STS_CLR_READ_COMPLETED_WITH_ERR_CLR BIT(10) + +#define DSI_DIRECT_CMD_RD_STS_CLR 0x0000011C +#define DSI_VID_MODE_STS_CLR 0x00000120 +#define DSI_TG_STS_CLR 0x00000124 +#define DSI_MCTL_DPHY_ERR_CLR 0x00000128 +#define DSI_MCTL_MAIN_STS_FLAG 0x00000130 +#define DSI_CMD_MODE_STS_FLAG 0x00000134 +#define DSI_DIRECT_CMD_STS_FLAG 0x00000138 +#define DSI_DIRECT_CMD_RD_STS_FLAG 0x0000013C +#define DSI_VID_MODE_STS_FLAG 0x00000140 +#define DSI_TG_STS_FLAG 0x00000144 + +#define DSI_DPHY_LANES_TRIM 0x00000150 +#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_DAT1_SHIFT 0 +#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_DAT1_MASK 0x00000003 +#define DSI_DPHY_LANES_TRIM_DPHY_CD_OFF_DAT1 BIT(2) +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_UP_DAT1 BIT(3) +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_DOWN_DAT1 BIT(4) +#define DSI_DPHY_LANES_TRIM_DPHY_TEST_RESERVED_1_DAT1 BIT(5) +#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_CLK_SHIFT 6 +#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_CLK_MASK 0x000000C0 +#define DSI_DPHY_LANES_TRIM_DPHY_LP_RX_VIL_CLK_SHIFT 8 +#define DSI_DPHY_LANES_TRIM_DPHY_LP_RX_VIL_CLK_MASK 0x00000300 +#define DSI_DPHY_LANES_TRIM_DPHY_LP_TX_SLEWRATE_CLK_SHIFT 10 +#define DSI_DPHY_LANES_TRIM_DPHY_LP_TX_SLEWRATE_CLK_MASK 0x00000C00 +#define DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_0_81 0 +#define DSI_DPHY_LANES_TRIM_DPHY_SPECS_90_81B_0_90 BIT(12) +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_UP_CLK BIT(13) +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_DOWN_CLK BIT(14) +#define DSI_DPHY_LANES_TRIM_DPHY_TEST_RESERVED_1_CLK BIT(15) +#define DSI_DPHY_LANES_TRIM_DPHY_SKEW_DAT2 BIT(16) +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_UP_DAT2 BIT(18) +#define DSI_DPHY_LANES_TRIM_DPHY_HSTX_SLEWRATE_DOWN_DAT2 BIT(19) +#define DSI_DPHY_LANES_TRIM_DPHY_TEST_RESERVED_1_DAT2 BIT(20) + +#define DSI_ID_REG 0x00000FF0 + +#endif /* __DRM_MCDE_DSI_REGS */ |
