// 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_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
|