From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/gpu/drm/meson/Kconfig | 29 + drivers/gpu/drm/meson/Makefile | 9 + drivers/gpu/drm/meson/meson_crtc.c | 719 ++++++++++ drivers/gpu/drm/meson/meson_crtc.h | 20 + drivers/gpu/drm/meson/meson_drv.c | 575 ++++++++ drivers/gpu/drm/meson/meson_drv.h | 185 +++ drivers/gpu/drm/meson/meson_dw_hdmi.c | 874 ++++++++++++ drivers/gpu/drm/meson/meson_dw_hdmi.h | 160 +++ drivers/gpu/drm/meson/meson_dw_mipi_dsi.c | 355 +++++ drivers/gpu/drm/meson/meson_dw_mipi_dsi.h | 160 +++ drivers/gpu/drm/meson/meson_encoder_cvbs.c | 295 ++++ drivers/gpu/drm/meson/meson_encoder_cvbs.h | 30 + drivers/gpu/drm/meson/meson_encoder_dsi.c | 170 +++ drivers/gpu/drm/meson/meson_encoder_dsi.h | 13 + drivers/gpu/drm/meson/meson_encoder_hdmi.c | 494 +++++++ drivers/gpu/drm/meson/meson_encoder_hdmi.h | 13 + drivers/gpu/drm/meson/meson_osd_afbcd.c | 402 ++++++ drivers/gpu/drm/meson/meson_osd_afbcd.h | 29 + drivers/gpu/drm/meson/meson_overlay.c | 867 ++++++++++++ drivers/gpu/drm/meson/meson_overlay.h | 14 + drivers/gpu/drm/meson/meson_plane.c | 572 ++++++++ drivers/gpu/drm/meson/meson_plane.h | 18 + drivers/gpu/drm/meson/meson_rdma.c | 135 ++ drivers/gpu/drm/meson/meson_rdma.h | 21 + drivers/gpu/drm/meson/meson_registers.h | 1910 ++++++++++++++++++++++++++ drivers/gpu/drm/meson/meson_vclk.c | 1131 ++++++++++++++++ drivers/gpu/drm/meson/meson_vclk.h | 37 + drivers/gpu/drm/meson/meson_venc.c | 2000 ++++++++++++++++++++++++++++ drivers/gpu/drm/meson/meson_venc.h | 79 ++ drivers/gpu/drm/meson/meson_viu.c | 506 +++++++ drivers/gpu/drm/meson/meson_viu.h | 72 + drivers/gpu/drm/meson/meson_vpp.c | 165 +++ drivers/gpu/drm/meson/meson_vpp.h | 30 + 33 files changed, 12089 insertions(+) create mode 100644 drivers/gpu/drm/meson/Kconfig create mode 100644 drivers/gpu/drm/meson/Makefile create mode 100644 drivers/gpu/drm/meson/meson_crtc.c create mode 100644 drivers/gpu/drm/meson/meson_crtc.h create mode 100644 drivers/gpu/drm/meson/meson_drv.c create mode 100644 drivers/gpu/drm/meson/meson_drv.h create mode 100644 drivers/gpu/drm/meson/meson_dw_hdmi.c create mode 100644 drivers/gpu/drm/meson/meson_dw_hdmi.h create mode 100644 drivers/gpu/drm/meson/meson_dw_mipi_dsi.c create mode 100644 drivers/gpu/drm/meson/meson_dw_mipi_dsi.h create mode 100644 drivers/gpu/drm/meson/meson_encoder_cvbs.c create mode 100644 drivers/gpu/drm/meson/meson_encoder_cvbs.h create mode 100644 drivers/gpu/drm/meson/meson_encoder_dsi.c create mode 100644 drivers/gpu/drm/meson/meson_encoder_dsi.h create mode 100644 drivers/gpu/drm/meson/meson_encoder_hdmi.c create mode 100644 drivers/gpu/drm/meson/meson_encoder_hdmi.h create mode 100644 drivers/gpu/drm/meson/meson_osd_afbcd.c create mode 100644 drivers/gpu/drm/meson/meson_osd_afbcd.h create mode 100644 drivers/gpu/drm/meson/meson_overlay.c create mode 100644 drivers/gpu/drm/meson/meson_overlay.h create mode 100644 drivers/gpu/drm/meson/meson_plane.c create mode 100644 drivers/gpu/drm/meson/meson_plane.h create mode 100644 drivers/gpu/drm/meson/meson_rdma.c create mode 100644 drivers/gpu/drm/meson/meson_rdma.h create mode 100644 drivers/gpu/drm/meson/meson_registers.h create mode 100644 drivers/gpu/drm/meson/meson_vclk.c create mode 100644 drivers/gpu/drm/meson/meson_vclk.h create mode 100644 drivers/gpu/drm/meson/meson_venc.c create mode 100644 drivers/gpu/drm/meson/meson_venc.h create mode 100644 drivers/gpu/drm/meson/meson_viu.c create mode 100644 drivers/gpu/drm/meson/meson_viu.h create mode 100644 drivers/gpu/drm/meson/meson_vpp.c create mode 100644 drivers/gpu/drm/meson/meson_vpp.h (limited to 'drivers/gpu/drm/meson') diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig new file mode 100644 index 000000000..417f79829 --- /dev/null +++ b/drivers/gpu/drm/meson/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_MESON + tristate "DRM Support for Amlogic Meson Display Controller" + depends on DRM && OF && (ARM || ARM64 || COMPILE_TEST) + depends on ARCH_MESON || COMPILE_TEST + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + select DRM_GEM_DMA_HELPER + select DRM_DISPLAY_CONNECTOR + select VIDEOMODE_HELPERS + select REGMAP_MMIO + select MESON_CANVAS + select CEC_CORE if CEC_NOTIFIER + +config DRM_MESON_DW_HDMI + tristate "HDMI Synopsys Controller support for Amlogic Meson Display" + depends on DRM_MESON + default y if DRM_MESON + select DRM_DW_HDMI + imply DRM_DW_HDMI_I2S_AUDIO + +config DRM_MESON_DW_MIPI_DSI + tristate "MIPI DSI Synopsys Controller support for Amlogic Meson Display" + depends on DRM_MESON + default y if DRM_MESON + select DRM_DW_MIPI_DSI + select GENERIC_PHY_MIPI_DPHY diff --git a/drivers/gpu/drm/meson/Makefile b/drivers/gpu/drm/meson/Makefile new file mode 100644 index 000000000..43071bdbd --- /dev/null +++ b/drivers/gpu/drm/meson/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +meson-drm-y := meson_drv.o meson_plane.o meson_crtc.o meson_encoder_cvbs.o +meson-drm-y += meson_viu.o meson_vpp.o meson_venc.o meson_vclk.o meson_overlay.o +meson-drm-y += meson_rdma.o meson_osd_afbcd.o +meson-drm-y += meson_encoder_hdmi.o meson_encoder_dsi.o + +obj-$(CONFIG_DRM_MESON) += meson-drm.o +obj-$(CONFIG_DRM_MESON_DW_HDMI) += meson_dw_hdmi.o +obj-$(CONFIG_DRM_MESON_DW_MIPI_DSI) += meson_dw_mipi_dsi.o diff --git a/drivers/gpu/drm/meson/meson_crtc.c b/drivers/gpu/drm/meson/meson_crtc.c new file mode 100644 index 000000000..b77d2d24e --- /dev/null +++ b/drivers/gpu/drm/meson/meson_crtc.c @@ -0,0 +1,719 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 BayLibre, SAS + * Author: Neil Armstrong + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + * Copyright (C) 2014 Endless Mobile + * + * Written by: + * Jasper St. Pierre + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "meson_crtc.h" +#include "meson_plane.h" +#include "meson_registers.h" +#include "meson_venc.h" +#include "meson_viu.h" +#include "meson_rdma.h" +#include "meson_vpp.h" +#include "meson_osd_afbcd.h" + +#define MESON_G12A_VIU_OFFSET 0x5ec0 + +/* CRTC definition */ + +struct meson_crtc { + struct drm_crtc base; + struct drm_pending_vblank_event *event; + struct meson_drm *priv; + void (*enable_osd1)(struct meson_drm *priv); + void (*enable_vd1)(struct meson_drm *priv); + void (*enable_osd1_afbc)(struct meson_drm *priv); + void (*disable_osd1_afbc)(struct meson_drm *priv); + unsigned int viu_offset; + bool vsync_forced; + bool vsync_disabled; +}; +#define to_meson_crtc(x) container_of(x, struct meson_crtc, base) + +/* CRTC */ + +static int meson_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct meson_crtc *meson_crtc = to_meson_crtc(crtc); + struct meson_drm *priv = meson_crtc->priv; + + meson_crtc->vsync_disabled = false; + meson_venc_enable_vsync(priv); + + return 0; +} + +static void meson_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct meson_crtc *meson_crtc = to_meson_crtc(crtc); + struct meson_drm *priv = meson_crtc->priv; + + if (!meson_crtc->vsync_forced) { + meson_crtc->vsync_disabled = true; + meson_venc_disable_vsync(priv); + } +} + +static const struct drm_crtc_funcs meson_crtc_funcs = { + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_config = drm_atomic_helper_set_config, + .enable_vblank = meson_crtc_enable_vblank, + .disable_vblank = meson_crtc_disable_vblank, + +}; + +static void meson_g12a_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct meson_crtc *meson_crtc = to_meson_crtc(crtc); + struct drm_crtc_state *crtc_state = crtc->state; + struct meson_drm *priv = meson_crtc->priv; + + DRM_DEBUG_DRIVER("\n"); + + if (!crtc_state) { + DRM_ERROR("Invalid crtc_state\n"); + return; + } + + /* VD1 Preblend vertical start/end */ + writel(FIELD_PREP(GENMASK(11, 0), 2303), + priv->io_base + _REG(VPP_PREBLEND_VD1_V_START_END)); + + /* Setup Blender */ + writel(crtc_state->mode.hdisplay | + crtc_state->mode.vdisplay << 16, + priv->io_base + _REG(VPP_POSTBLEND_H_SIZE)); + + writel_relaxed(0 << 16 | + (crtc_state->mode.hdisplay - 1), + priv->io_base + _REG(VPP_OSD1_BLD_H_SCOPE)); + writel_relaxed(0 << 16 | + (crtc_state->mode.vdisplay - 1), + priv->io_base + _REG(VPP_OSD1_BLD_V_SCOPE)); + writel_relaxed(crtc_state->mode.hdisplay << 16 | + crtc_state->mode.vdisplay, + priv->io_base + _REG(VPP_OUT_H_V_SIZE)); + + drm_crtc_vblank_on(crtc); +} + +static void meson_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct meson_crtc *meson_crtc = to_meson_crtc(crtc); + struct drm_crtc_state *crtc_state = crtc->state; + struct meson_drm *priv = meson_crtc->priv; + + DRM_DEBUG_DRIVER("\n"); + + if (!crtc_state) { + DRM_ERROR("Invalid crtc_state\n"); + return; + } + + /* Enable VPP Postblend */ + writel(crtc_state->mode.hdisplay, + priv->io_base + _REG(VPP_POSTBLEND_H_SIZE)); + + /* VD1 Preblend vertical start/end */ + writel(FIELD_PREP(GENMASK(11, 0), 2303), + priv->io_base + _REG(VPP_PREBLEND_VD1_V_START_END)); + + writel_bits_relaxed(VPP_POSTBLEND_ENABLE, VPP_POSTBLEND_ENABLE, + priv->io_base + _REG(VPP_MISC)); + + drm_crtc_vblank_on(crtc); +} + +static void meson_g12a_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct meson_crtc *meson_crtc = to_meson_crtc(crtc); + struct meson_drm *priv = meson_crtc->priv; + + DRM_DEBUG_DRIVER("\n"); + + drm_crtc_vblank_off(crtc); + + priv->viu.osd1_enabled = false; + priv->viu.osd1_commit = false; + + priv->viu.vd1_enabled = false; + priv->viu.vd1_commit = false; + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + + crtc->state->event = NULL; + } +} + +static void meson_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct meson_crtc *meson_crtc = to_meson_crtc(crtc); + struct meson_drm *priv = meson_crtc->priv; + + DRM_DEBUG_DRIVER("\n"); + + drm_crtc_vblank_off(crtc); + + priv->viu.osd1_enabled = false; + priv->viu.osd1_commit = false; + + priv->viu.vd1_enabled = false; + priv->viu.vd1_commit = false; + + /* Disable VPP Postblend */ + writel_bits_relaxed(VPP_OSD1_POSTBLEND | VPP_VD1_POSTBLEND | + VPP_VD1_PREBLEND | VPP_POSTBLEND_ENABLE, 0, + priv->io_base + _REG(VPP_MISC)); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + + crtc->state->event = NULL; + } +} + +static void meson_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct meson_crtc *meson_crtc = to_meson_crtc(crtc); + unsigned long flags; + + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + meson_crtc->event = crtc->state->event; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + crtc->state->event = NULL; + } +} + +static void meson_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct meson_crtc *meson_crtc = to_meson_crtc(crtc); + struct meson_drm *priv = meson_crtc->priv; + + priv->viu.osd1_commit = true; + priv->viu.vd1_commit = true; +} + +static const struct drm_crtc_helper_funcs meson_crtc_helper_funcs = { + .atomic_begin = meson_crtc_atomic_begin, + .atomic_flush = meson_crtc_atomic_flush, + .atomic_enable = meson_crtc_atomic_enable, + .atomic_disable = meson_crtc_atomic_disable, +}; + +static const struct drm_crtc_helper_funcs meson_g12a_crtc_helper_funcs = { + .atomic_begin = meson_crtc_atomic_begin, + .atomic_flush = meson_crtc_atomic_flush, + .atomic_enable = meson_g12a_crtc_atomic_enable, + .atomic_disable = meson_g12a_crtc_atomic_disable, +}; + +static void meson_crtc_enable_osd1(struct meson_drm *priv) +{ + writel_bits_relaxed(VPP_OSD1_POSTBLEND, VPP_OSD1_POSTBLEND, + priv->io_base + _REG(VPP_MISC)); +} + +static void meson_crtc_g12a_enable_osd1_afbc(struct meson_drm *priv) +{ + writel_relaxed(priv->viu.osd1_blk2_cfg4, + priv->io_base + _REG(VIU_OSD1_BLK2_CFG_W4)); + + writel_bits_relaxed(OSD_MEM_LINEAR_ADDR, OSD_MEM_LINEAR_ADDR, + priv->io_base + _REG(VIU_OSD1_CTRL_STAT)); + + writel_relaxed(priv->viu.osd1_blk1_cfg4, + priv->io_base + _REG(VIU_OSD1_BLK1_CFG_W4)); + + meson_viu_g12a_enable_osd1_afbc(priv); + + writel_bits_relaxed(OSD_MEM_LINEAR_ADDR, OSD_MEM_LINEAR_ADDR, + priv->io_base + _REG(VIU_OSD1_CTRL_STAT)); + + writel_bits_relaxed(OSD_MALI_SRC_EN, OSD_MALI_SRC_EN, + priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W0)); +} + +static void meson_g12a_crtc_enable_osd1(struct meson_drm *priv) +{ + writel_relaxed(priv->viu.osd_blend_din0_scope_h, + priv->io_base + + _REG(VIU_OSD_BLEND_DIN0_SCOPE_H)); + writel_relaxed(priv->viu.osd_blend_din0_scope_v, + priv->io_base + + _REG(VIU_OSD_BLEND_DIN0_SCOPE_V)); + writel_relaxed(priv->viu.osb_blend0_size, + priv->io_base + + _REG(VIU_OSD_BLEND_BLEND0_SIZE)); + writel_relaxed(priv->viu.osb_blend1_size, + priv->io_base + + _REG(VIU_OSD_BLEND_BLEND1_SIZE)); + writel_bits_relaxed(3 << 8, 3 << 8, + priv->io_base + _REG(OSD1_BLEND_SRC_CTRL)); +} + +static void meson_crtc_enable_vd1(struct meson_drm *priv) +{ + writel_bits_relaxed(VPP_VD1_PREBLEND | VPP_VD1_POSTBLEND | + VPP_COLOR_MNG_ENABLE, + VPP_VD1_PREBLEND | VPP_VD1_POSTBLEND | + VPP_COLOR_MNG_ENABLE, + priv->io_base + _REG(VPP_MISC)); + + writel_bits_relaxed(VIU_CTRL0_AFBC_TO_VD1, + priv->viu.vd1_afbc ? VIU_CTRL0_AFBC_TO_VD1 : 0, + priv->io_base + _REG(VIU_MISC_CTRL0)); +} + +static void meson_g12a_crtc_enable_vd1(struct meson_drm *priv) +{ + writel_relaxed(VD_BLEND_PREBLD_SRC_VD1 | + VD_BLEND_PREBLD_PREMULT_EN | + VD_BLEND_POSTBLD_SRC_VD1 | + VD_BLEND_POSTBLD_PREMULT_EN, + priv->io_base + _REG(VD1_BLEND_SRC_CTRL)); + + writel_relaxed(priv->viu.vd1_afbc ? + (VD1_AXI_SEL_AFBC | AFBC_VD1_SEL) : 0, + priv->io_base + _REG(VD1_AFBCD0_MISC_CTRL)); +} + +void meson_crtc_irq(struct meson_drm *priv) +{ + struct meson_crtc *meson_crtc = to_meson_crtc(priv->crtc); + unsigned long flags; + + /* Update the OSD registers */ + if (priv->viu.osd1_enabled && priv->viu.osd1_commit) { + writel_relaxed(priv->viu.osd1_ctrl_stat, + priv->io_base + _REG(VIU_OSD1_CTRL_STAT)); + writel_relaxed(priv->viu.osd1_ctrl_stat2, + priv->io_base + _REG(VIU_OSD1_CTRL_STAT2)); + writel_relaxed(priv->viu.osd1_blk0_cfg[0], + priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W0)); + writel_relaxed(priv->viu.osd1_blk0_cfg[1], + priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W1)); + writel_relaxed(priv->viu.osd1_blk0_cfg[2], + priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W2)); + writel_relaxed(priv->viu.osd1_blk0_cfg[3], + priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W3)); + writel_relaxed(priv->viu.osd1_blk0_cfg[4], + priv->io_base + _REG(VIU_OSD1_BLK0_CFG_W4)); + + if (priv->viu.osd1_afbcd) { + if (meson_crtc->enable_osd1_afbc) + meson_crtc->enable_osd1_afbc(priv); + } else { + if (meson_crtc->disable_osd1_afbc) + meson_crtc->disable_osd1_afbc(priv); + if (priv->afbcd.ops) { + priv->afbcd.ops->reset(priv); + priv->afbcd.ops->disable(priv); + } + meson_crtc->vsync_forced = false; + } + + writel_relaxed(priv->viu.osd_sc_ctrl0, + priv->io_base + _REG(VPP_OSD_SC_CTRL0)); + writel_relaxed(priv->viu.osd_sc_i_wh_m1, + priv->io_base + _REG(VPP_OSD_SCI_WH_M1)); + writel_relaxed(priv->viu.osd_sc_o_h_start_end, + priv->io_base + _REG(VPP_OSD_SCO_H_START_END)); + writel_relaxed(priv->viu.osd_sc_o_v_start_end, + priv->io_base + _REG(VPP_OSD_SCO_V_START_END)); + writel_relaxed(priv->viu.osd_sc_v_ini_phase, + priv->io_base + _REG(VPP_OSD_VSC_INI_PHASE)); + writel_relaxed(priv->viu.osd_sc_v_phase_step, + priv->io_base + _REG(VPP_OSD_VSC_PHASE_STEP)); + writel_relaxed(priv->viu.osd_sc_h_ini_phase, + priv->io_base + _REG(VPP_OSD_HSC_INI_PHASE)); + writel_relaxed(priv->viu.osd_sc_h_phase_step, + priv->io_base + _REG(VPP_OSD_HSC_PHASE_STEP)); + writel_relaxed(priv->viu.osd_sc_h_ctrl0, + priv->io_base + _REG(VPP_OSD_HSC_CTRL0)); + writel_relaxed(priv->viu.osd_sc_v_ctrl0, + priv->io_base + _REG(VPP_OSD_VSC_CTRL0)); + + if (!priv->viu.osd1_afbcd) + meson_canvas_config(priv->canvas, priv->canvas_id_osd1, + priv->viu.osd1_addr, + priv->viu.osd1_stride, + priv->viu.osd1_height, + MESON_CANVAS_WRAP_NONE, + MESON_CANVAS_BLKMODE_LINEAR, 0); + + /* Enable OSD1 */ + if (meson_crtc->enable_osd1) + meson_crtc->enable_osd1(priv); + + if (priv->viu.osd1_afbcd) { + priv->afbcd.ops->reset(priv); + priv->afbcd.ops->setup(priv); + priv->afbcd.ops->enable(priv); + meson_crtc->vsync_forced = true; + } + + priv->viu.osd1_commit = false; + } + + /* Update the VD1 registers */ + if (priv->viu.vd1_enabled && priv->viu.vd1_commit) { + + if (priv->viu.vd1_afbc) { + writel_relaxed(priv->viu.vd1_afbc_head_addr, + priv->io_base + + _REG(AFBC_HEAD_BADDR)); + writel_relaxed(priv->viu.vd1_afbc_body_addr, + priv->io_base + + _REG(AFBC_BODY_BADDR)); + writel_relaxed(priv->viu.vd1_afbc_en, + priv->io_base + + _REG(AFBC_ENABLE)); + writel_relaxed(priv->viu.vd1_afbc_mode, + priv->io_base + + _REG(AFBC_MODE)); + writel_relaxed(priv->viu.vd1_afbc_size_in, + priv->io_base + + _REG(AFBC_SIZE_IN)); + writel_relaxed(priv->viu.vd1_afbc_dec_def_color, + priv->io_base + + _REG(AFBC_DEC_DEF_COLOR)); + writel_relaxed(priv->viu.vd1_afbc_conv_ctrl, + priv->io_base + + _REG(AFBC_CONV_CTRL)); + writel_relaxed(priv->viu.vd1_afbc_size_out, + priv->io_base + + _REG(AFBC_SIZE_OUT)); + writel_relaxed(priv->viu.vd1_afbc_vd_cfmt_ctrl, + priv->io_base + + _REG(AFBC_VD_CFMT_CTRL)); + writel_relaxed(priv->viu.vd1_afbc_vd_cfmt_w, + priv->io_base + + _REG(AFBC_VD_CFMT_W)); + writel_relaxed(priv->viu.vd1_afbc_mif_hor_scope, + priv->io_base + + _REG(AFBC_MIF_HOR_SCOPE)); + writel_relaxed(priv->viu.vd1_afbc_mif_ver_scope, + priv->io_base + + _REG(AFBC_MIF_VER_SCOPE)); + writel_relaxed(priv->viu.vd1_afbc_pixel_hor_scope, + priv->io_base+ + _REG(AFBC_PIXEL_HOR_SCOPE)); + writel_relaxed(priv->viu.vd1_afbc_pixel_ver_scope, + priv->io_base + + _REG(AFBC_PIXEL_VER_SCOPE)); + writel_relaxed(priv->viu.vd1_afbc_vd_cfmt_h, + priv->io_base + + _REG(AFBC_VD_CFMT_H)); + } else { + switch (priv->viu.vd1_planes) { + case 3: + meson_canvas_config(priv->canvas, + priv->canvas_id_vd1_2, + priv->viu.vd1_addr2, + priv->viu.vd1_stride2, + priv->viu.vd1_height2, + MESON_CANVAS_WRAP_NONE, + MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + fallthrough; + case 2: + meson_canvas_config(priv->canvas, + priv->canvas_id_vd1_1, + priv->viu.vd1_addr1, + priv->viu.vd1_stride1, + priv->viu.vd1_height1, + MESON_CANVAS_WRAP_NONE, + MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + fallthrough; + case 1: + meson_canvas_config(priv->canvas, + priv->canvas_id_vd1_0, + priv->viu.vd1_addr0, + priv->viu.vd1_stride0, + priv->viu.vd1_height0, + MESON_CANVAS_WRAP_NONE, + MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + } + + writel_relaxed(0, priv->io_base + _REG(AFBC_ENABLE)); + } + + writel_relaxed(priv->viu.vd1_if0_gen_reg, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_GEN_REG)); + writel_relaxed(priv->viu.vd1_if0_gen_reg, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_GEN_REG)); + writel_relaxed(priv->viu.vd1_if0_gen_reg2, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_GEN_REG2)); + writel_relaxed(priv->viu.viu_vd1_fmt_ctrl, + priv->io_base + meson_crtc->viu_offset + + _REG(VIU_VD1_FMT_CTRL)); + writel_relaxed(priv->viu.viu_vd1_fmt_ctrl, + priv->io_base + meson_crtc->viu_offset + + _REG(VIU_VD2_FMT_CTRL)); + writel_relaxed(priv->viu.viu_vd1_fmt_w, + priv->io_base + meson_crtc->viu_offset + + _REG(VIU_VD1_FMT_W)); + writel_relaxed(priv->viu.viu_vd1_fmt_w, + priv->io_base + meson_crtc->viu_offset + + _REG(VIU_VD2_FMT_W)); + writel_relaxed(priv->viu.vd1_if0_canvas0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_CANVAS0)); + writel_relaxed(priv->viu.vd1_if0_canvas0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_CANVAS1)); + writel_relaxed(priv->viu.vd1_if0_canvas0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_CANVAS0)); + writel_relaxed(priv->viu.vd1_if0_canvas0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_CANVAS1)); + writel_relaxed(priv->viu.vd1_if0_luma_x0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_LUMA_X0)); + writel_relaxed(priv->viu.vd1_if0_luma_x0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_LUMA_X1)); + writel_relaxed(priv->viu.vd1_if0_luma_x0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_LUMA_X0)); + writel_relaxed(priv->viu.vd1_if0_luma_x0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_LUMA_X1)); + writel_relaxed(priv->viu.vd1_if0_luma_y0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_LUMA_Y0)); + writel_relaxed(priv->viu.vd1_if0_luma_y0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_LUMA_Y1)); + writel_relaxed(priv->viu.vd1_if0_luma_y0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_LUMA_Y0)); + writel_relaxed(priv->viu.vd1_if0_luma_y0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_LUMA_Y1)); + writel_relaxed(priv->viu.vd1_if0_chroma_x0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_CHROMA_X0)); + writel_relaxed(priv->viu.vd1_if0_chroma_x0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_CHROMA_X1)); + writel_relaxed(priv->viu.vd1_if0_chroma_x0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_CHROMA_X0)); + writel_relaxed(priv->viu.vd1_if0_chroma_x0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_CHROMA_X1)); + writel_relaxed(priv->viu.vd1_if0_chroma_y0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_CHROMA_Y0)); + writel_relaxed(priv->viu.vd1_if0_chroma_y0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_CHROMA_Y1)); + writel_relaxed(priv->viu.vd1_if0_chroma_y0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_CHROMA_Y0)); + writel_relaxed(priv->viu.vd1_if0_chroma_y0, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_CHROMA_Y1)); + writel_relaxed(priv->viu.vd1_if0_repeat_loop, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_RPT_LOOP)); + writel_relaxed(priv->viu.vd1_if0_repeat_loop, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_RPT_LOOP)); + writel_relaxed(priv->viu.vd1_if0_luma0_rpt_pat, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_LUMA0_RPT_PAT)); + writel_relaxed(priv->viu.vd1_if0_luma0_rpt_pat, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_LUMA0_RPT_PAT)); + writel_relaxed(priv->viu.vd1_if0_luma0_rpt_pat, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_LUMA1_RPT_PAT)); + writel_relaxed(priv->viu.vd1_if0_luma0_rpt_pat, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_LUMA1_RPT_PAT)); + writel_relaxed(priv->viu.vd1_if0_chroma0_rpt_pat, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_CHROMA0_RPT_PAT)); + writel_relaxed(priv->viu.vd1_if0_chroma0_rpt_pat, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_CHROMA0_RPT_PAT)); + writel_relaxed(priv->viu.vd1_if0_chroma0_rpt_pat, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_CHROMA1_RPT_PAT)); + writel_relaxed(priv->viu.vd1_if0_chroma0_rpt_pat, + priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_CHROMA1_RPT_PAT)); + writel_relaxed(0, priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_LUMA_PSEL)); + writel_relaxed(0, priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_CHROMA_PSEL)); + writel_relaxed(0, priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_LUMA_PSEL)); + writel_relaxed(0, priv->io_base + meson_crtc->viu_offset + + _REG(VD2_IF0_CHROMA_PSEL)); + writel_relaxed(priv->viu.vd1_range_map_y, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_RANGE_MAP_Y)); + writel_relaxed(priv->viu.vd1_range_map_cb, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_RANGE_MAP_CB)); + writel_relaxed(priv->viu.vd1_range_map_cr, + priv->io_base + meson_crtc->viu_offset + + _REG(VD1_IF0_RANGE_MAP_CR)); + writel_relaxed(VPP_VSC_BANK_LENGTH(4) | + VPP_HSC_BANK_LENGTH(4) | + VPP_SC_VD_EN_ENABLE | + VPP_SC_TOP_EN_ENABLE | + VPP_SC_HSC_EN_ENABLE | + VPP_SC_VSC_EN_ENABLE, + priv->io_base + _REG(VPP_SC_MISC)); + writel_relaxed(priv->viu.vpp_pic_in_height, + priv->io_base + _REG(VPP_PIC_IN_HEIGHT)); + writel_relaxed(priv->viu.vpp_postblend_vd1_h_start_end, + priv->io_base + _REG(VPP_POSTBLEND_VD1_H_START_END)); + writel_relaxed(priv->viu.vpp_blend_vd2_h_start_end, + priv->io_base + _REG(VPP_BLEND_VD2_H_START_END)); + writel_relaxed(priv->viu.vpp_postblend_vd1_v_start_end, + priv->io_base + _REG(VPP_POSTBLEND_VD1_V_START_END)); + writel_relaxed(priv->viu.vpp_blend_vd2_v_start_end, + priv->io_base + _REG(VPP_BLEND_VD2_V_START_END)); + writel_relaxed(priv->viu.vpp_hsc_region12_startp, + priv->io_base + _REG(VPP_HSC_REGION12_STARTP)); + writel_relaxed(priv->viu.vpp_hsc_region34_startp, + priv->io_base + _REG(VPP_HSC_REGION34_STARTP)); + writel_relaxed(priv->viu.vpp_hsc_region4_endp, + priv->io_base + _REG(VPP_HSC_REGION4_ENDP)); + writel_relaxed(priv->viu.vpp_hsc_start_phase_step, + priv->io_base + _REG(VPP_HSC_START_PHASE_STEP)); + writel_relaxed(priv->viu.vpp_hsc_region1_phase_slope, + priv->io_base + _REG(VPP_HSC_REGION1_PHASE_SLOPE)); + writel_relaxed(priv->viu.vpp_hsc_region3_phase_slope, + priv->io_base + _REG(VPP_HSC_REGION3_PHASE_SLOPE)); + writel_relaxed(priv->viu.vpp_line_in_length, + priv->io_base + _REG(VPP_LINE_IN_LENGTH)); + writel_relaxed(priv->viu.vpp_preblend_h_size, + priv->io_base + _REG(VPP_PREBLEND_H_SIZE)); + writel_relaxed(priv->viu.vpp_vsc_region12_startp, + priv->io_base + _REG(VPP_VSC_REGION12_STARTP)); + writel_relaxed(priv->viu.vpp_vsc_region34_startp, + priv->io_base + _REG(VPP_VSC_REGION34_STARTP)); + writel_relaxed(priv->viu.vpp_vsc_region4_endp, + priv->io_base + _REG(VPP_VSC_REGION4_ENDP)); + writel_relaxed(priv->viu.vpp_vsc_start_phase_step, + priv->io_base + _REG(VPP_VSC_START_PHASE_STEP)); + writel_relaxed(priv->viu.vpp_vsc_ini_phase, + priv->io_base + _REG(VPP_VSC_INI_PHASE)); + writel_relaxed(priv->viu.vpp_vsc_phase_ctrl, + priv->io_base + _REG(VPP_VSC_PHASE_CTRL)); + writel_relaxed(priv->viu.vpp_hsc_phase_ctrl, + priv->io_base + _REG(VPP_HSC_PHASE_CTRL)); + writel_relaxed(0x42, priv->io_base + _REG(VPP_SCALE_COEF_IDX)); + + /* Enable VD1 */ + if (meson_crtc->enable_vd1) + meson_crtc->enable_vd1(priv); + + priv->viu.vd1_commit = false; + } + + if (meson_crtc->vsync_disabled) + return; + + drm_crtc_handle_vblank(priv->crtc); + + spin_lock_irqsave(&priv->drm->event_lock, flags); + if (meson_crtc->event) { + drm_crtc_send_vblank_event(priv->crtc, meson_crtc->event); + drm_crtc_vblank_put(priv->crtc); + meson_crtc->event = NULL; + } + spin_unlock_irqrestore(&priv->drm->event_lock, flags); +} + +int meson_crtc_create(struct meson_drm *priv) +{ + struct meson_crtc *meson_crtc; + struct drm_crtc *crtc; + int ret; + + meson_crtc = devm_kzalloc(priv->drm->dev, sizeof(*meson_crtc), + GFP_KERNEL); + if (!meson_crtc) + return -ENOMEM; + + meson_crtc->priv = priv; + crtc = &meson_crtc->base; + ret = drm_crtc_init_with_planes(priv->drm, crtc, + priv->primary_plane, NULL, + &meson_crtc_funcs, "meson_crtc"); + if (ret) { + dev_err(priv->drm->dev, "Failed to init CRTC\n"); + return ret; + } + + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { + meson_crtc->enable_osd1 = meson_g12a_crtc_enable_osd1; + meson_crtc->enable_vd1 = meson_g12a_crtc_enable_vd1; + meson_crtc->viu_offset = MESON_G12A_VIU_OFFSET; + meson_crtc->enable_osd1_afbc = + meson_crtc_g12a_enable_osd1_afbc; + meson_crtc->disable_osd1_afbc = + meson_viu_g12a_disable_osd1_afbc; + drm_crtc_helper_add(crtc, &meson_g12a_crtc_helper_funcs); + } else { + meson_crtc->enable_osd1 = meson_crtc_enable_osd1; + meson_crtc->enable_vd1 = meson_crtc_enable_vd1; + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM)) { + meson_crtc->enable_osd1_afbc = + meson_viu_gxm_enable_osd1_afbc; + meson_crtc->disable_osd1_afbc = + meson_viu_gxm_disable_osd1_afbc; + } + drm_crtc_helper_add(crtc, &meson_crtc_helper_funcs); + } + + priv->crtc = crtc; + + return 0; +} diff --git a/drivers/gpu/drm/meson/meson_crtc.h b/drivers/gpu/drm/meson/meson_crtc.h new file mode 100644 index 000000000..8e3998cab --- /dev/null +++ b/drivers/gpu/drm/meson/meson_crtc.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 BayLibre, SAS + * Author: Neil Armstrong + * Copyright (C) 2014 Endless Mobile + * + * Written by: + * Jasper St. Pierre + */ + +#ifndef __MESON_CRTC_H +#define __MESON_CRTC_H + +#include "meson_drv.h" + +int meson_crtc_create(struct meson_drm *priv); + +void meson_crtc_irq(struct meson_drm *priv); + +#endif /* __MESON_CRTC_H */ diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c new file mode 100644 index 000000000..49ff9f1f1 --- /dev/null +++ b/drivers/gpu/drm/meson/meson_drv.c @@ -0,0 +1,575 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 BayLibre, SAS + * Author: Neil Armstrong + * Copyright (C) 2014 Endless Mobile + * + * Written by: + * Jasper St. Pierre + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "meson_crtc.h" +#include "meson_drv.h" +#include "meson_overlay.h" +#include "meson_plane.h" +#include "meson_osd_afbcd.h" +#include "meson_registers.h" +#include "meson_encoder_cvbs.h" +#include "meson_encoder_hdmi.h" +#include "meson_encoder_dsi.h" +#include "meson_viu.h" +#include "meson_vpp.h" +#include "meson_rdma.h" + +#define DRIVER_NAME "meson" +#define DRIVER_DESC "Amlogic Meson DRM driver" + +/** + * DOC: Video Processing Unit + * + * VPU Handles the Global Video Processing, it includes management of the + * clocks gates, blocks reset lines and power domains. + * + * What is missing : + * + * - Full reset of entire video processing HW blocks + * - Scaling and setup of the VPU clock + * - Bus clock gates + * - Powering up video processing HW blocks + * - Powering Up HDMI controller and PHY + */ + +static const struct drm_mode_config_funcs meson_mode_config_funcs = { + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .fb_create = drm_gem_fb_create, +}; + +static const struct drm_mode_config_helper_funcs meson_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +static irqreturn_t meson_irq(int irq, void *arg) +{ + struct drm_device *dev = arg; + struct meson_drm *priv = dev->dev_private; + + (void)readl_relaxed(priv->io_base + _REG(VENC_INTFLAG)); + + meson_crtc_irq(priv); + + return IRQ_HANDLED; +} + +static int meson_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + /* + * We need 64bytes aligned stride, and PAGE aligned size + */ + args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), SZ_64); + args->size = PAGE_ALIGN(args->pitch * args->height); + + return drm_gem_dma_dumb_create_internal(file, dev, args); +} + +DEFINE_DRM_GEM_DMA_FOPS(fops); + +static const struct drm_driver meson_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + + /* DMA Ops */ + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(meson_dumb_create), + DRM_FBDEV_DMA_DRIVER_OPS, + + /* Misc */ + .fops = &fops, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = 1, + .minor = 0, +}; + +static bool meson_vpu_has_available_connectors(struct device *dev) +{ + struct device_node *ep, *remote; + + /* Parses each endpoint and check if remote exists */ + for_each_endpoint_of_node(dev->of_node, ep) { + /* If the endpoint node exists, consider it enabled */ + remote = of_graph_get_remote_port(ep); + if (remote) { + of_node_put(remote); + of_node_put(ep); + return true; + } + } + + return false; +} + +static const struct regmap_config meson_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x1000, +}; + +static void meson_vpu_init(struct meson_drm *priv) +{ + u32 value; + + /* + * Slave dc0 and dc5 connected to master port 1. + * By default other slaves are connected to master port 0. + */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1) | + VPU_RDARB_SLAVE_TO_MASTER_PORT(5, 1); + writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C1)); + + /* Slave dc0 connected to master port 1 */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(0, 1); + writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L1C2)); + + /* Slave dc4 and dc7 connected to master port 1 */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(4, 1) | + VPU_RDARB_SLAVE_TO_MASTER_PORT(7, 1); + writel_relaxed(value, priv->io_base + _REG(VPU_RDARB_MODE_L2C1)); + + /* Slave dc1 connected to master port 1 */ + value = VPU_RDARB_SLAVE_TO_MASTER_PORT(1, 1); + writel_relaxed(value, priv->io_base + _REG(VPU_WRARB_MODE_L2C1)); +} + +struct meson_drm_soc_attr { + struct meson_drm_soc_limits limits; + const struct soc_device_attribute *attrs; +}; + +static const struct meson_drm_soc_attr meson_drm_soc_attrs[] = { + /* S805X/S805Y HDMI PLL won't lock for HDMI PHY freq > 1,65GHz */ + { + .limits = { + .max_hdmi_phy_freq = 1650000000, + }, + .attrs = (const struct soc_device_attribute []) { + { .soc_id = "GXL (S805*)", }, + { /* sentinel */ } + } + }, +}; + +static int meson_drv_bind_master(struct device *dev, bool has_components) +{ + struct platform_device *pdev = to_platform_device(dev); + const struct meson_drm_match_data *match; + struct meson_drm *priv; + struct drm_device *drm; + struct resource *res; + void __iomem *regs; + int ret, i; + + /* Checks if an output connector is available */ + if (!meson_vpu_has_available_connectors(dev)) { + dev_err(dev, "No output connector available\n"); + return -ENODEV; + } + + match = of_device_get_match_data(dev); + if (!match) + return -ENODEV; + + drm = drm_dev_alloc(&meson_driver, dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto free_drm; + } + drm->dev_private = priv; + priv->drm = drm; + priv->dev = dev; + priv->compat = match->compat; + priv->afbcd.ops = match->afbcd_ops; + + regs = devm_platform_ioremap_resource_byname(pdev, "vpu"); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto free_drm; + } + + priv->io_base = regs; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hhi"); + if (!res) { + ret = -EINVAL; + goto free_drm; + } + /* Simply ioremap since it may be a shared register zone */ + regs = devm_ioremap(dev, res->start, resource_size(res)); + if (!regs) { + ret = -EADDRNOTAVAIL; + goto free_drm; + } + + priv->hhi = devm_regmap_init_mmio(dev, regs, + &meson_regmap_config); + if (IS_ERR(priv->hhi)) { + dev_err(&pdev->dev, "Couldn't create the HHI regmap\n"); + ret = PTR_ERR(priv->hhi); + goto free_drm; + } + + priv->canvas = meson_canvas_get(dev); + if (IS_ERR(priv->canvas)) { + ret = PTR_ERR(priv->canvas); + goto free_drm; + } + + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1); + if (ret) + goto free_drm; + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_0); + if (ret) + goto free_canvas_osd1; + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_1); + if (ret) + goto free_canvas_vd1_0; + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_2); + if (ret) + goto free_canvas_vd1_1; + + priv->vsync_irq = platform_get_irq(pdev, 0); + + ret = drm_vblank_init(drm, 1); + if (ret) + goto free_canvas_vd1_2; + + /* Assign limits per soc revision/package */ + for (i = 0 ; i < ARRAY_SIZE(meson_drm_soc_attrs) ; ++i) { + if (soc_device_match(meson_drm_soc_attrs[i].attrs)) { + priv->limits = &meson_drm_soc_attrs[i].limits; + break; + } + } + + /* + * Remove early framebuffers (ie. simplefb). The framebuffer can be + * located anywhere in RAM + */ + ret = aperture_remove_all_conflicting_devices(meson_driver.name); + if (ret) + goto free_canvas_vd1_2; + + ret = drmm_mode_config_init(drm); + if (ret) + goto free_canvas_vd1_2; + drm->mode_config.max_width = 3840; + drm->mode_config.max_height = 2160; + drm->mode_config.funcs = &meson_mode_config_funcs; + drm->mode_config.helper_private = &meson_mode_config_helpers; + + /* Hardware Initialization */ + + meson_vpu_init(priv); + meson_venc_init(priv); + meson_vpp_init(priv); + meson_viu_init(priv); + if (priv->afbcd.ops) { + ret = priv->afbcd.ops->init(priv); + if (ret) + goto free_canvas_vd1_2; + } + + /* Encoder Initialization */ + + ret = meson_encoder_cvbs_probe(priv); + if (ret) + goto exit_afbcd; + + if (has_components) { + ret = component_bind_all(dev, drm); + if (ret) { + dev_err(drm->dev, "Couldn't bind all components\n"); + /* Do not try to unbind */ + has_components = false; + goto exit_afbcd; + } + } + + ret = meson_encoder_hdmi_probe(priv); + if (ret) + goto exit_afbcd; + + if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { + ret = meson_encoder_dsi_probe(priv); + if (ret) + goto exit_afbcd; + } + + ret = meson_plane_create(priv); + if (ret) + goto exit_afbcd; + + ret = meson_overlay_create(priv); + if (ret) + goto exit_afbcd; + + ret = meson_crtc_create(priv); + if (ret) + goto exit_afbcd; + + ret = request_irq(priv->vsync_irq, meson_irq, 0, drm->driver->name, drm); + if (ret) + goto exit_afbcd; + + drm_mode_config_reset(drm); + + drm_kms_helper_poll_init(drm); + + platform_set_drvdata(pdev, priv); + + ret = drm_dev_register(drm, 0); + if (ret) + goto uninstall_irq; + + drm_client_setup(drm, NULL); + + return 0; + +uninstall_irq: + free_irq(priv->vsync_irq, drm); +exit_afbcd: + if (priv->afbcd.ops) + priv->afbcd.ops->exit(priv); +free_canvas_vd1_2: + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_2); +free_canvas_vd1_1: + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1); +free_canvas_vd1_0: + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); +free_canvas_osd1: + meson_canvas_free(priv->canvas, priv->canvas_id_osd1); +free_drm: + drm_dev_put(drm); + + meson_encoder_dsi_remove(priv); + meson_encoder_hdmi_remove(priv); + meson_encoder_cvbs_remove(priv); + + if (has_components) + component_unbind_all(dev, drm); + + return ret; +} + +static int meson_drv_bind(struct device *dev) +{ + return meson_drv_bind_master(dev, true); +} + +static void meson_drv_unbind(struct device *dev) +{ + struct meson_drm *priv = dev_get_drvdata(dev); + struct drm_device *drm = priv->drm; + + if (priv->canvas) { + meson_canvas_free(priv->canvas, priv->canvas_id_osd1); + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1); + meson_canvas_free(priv->canvas, priv->canvas_id_vd1_2); + } + + drm_dev_unregister(drm); + drm_kms_helper_poll_fini(drm); + drm_atomic_helper_shutdown(drm); + free_irq(priv->vsync_irq, drm); + drm_dev_put(drm); + + meson_encoder_dsi_remove(priv); + meson_encoder_hdmi_remove(priv); + meson_encoder_cvbs_remove(priv); + + component_unbind_all(dev, drm); + + if (priv->afbcd.ops) + priv->afbcd.ops->exit(priv); +} + +static const struct component_master_ops meson_drv_master_ops = { + .bind = meson_drv_bind, + .unbind = meson_drv_unbind, +}; + +static int __maybe_unused meson_drv_pm_suspend(struct device *dev) +{ + struct meson_drm *priv = dev_get_drvdata(dev); + + if (!priv) + return 0; + + return drm_mode_config_helper_suspend(priv->drm); +} + +static int __maybe_unused meson_drv_pm_resume(struct device *dev) +{ + struct meson_drm *priv = dev_get_drvdata(dev); + + if (!priv) + return 0; + + meson_vpu_init(priv); + meson_venc_init(priv); + meson_vpp_init(priv); + meson_viu_init(priv); + if (priv->afbcd.ops) + priv->afbcd.ops->init(priv); + + return drm_mode_config_helper_resume(priv->drm); +} + +static void meson_drv_shutdown(struct platform_device *pdev) +{ + struct meson_drm *priv = dev_get_drvdata(&pdev->dev); + + if (!priv) + return; + + drm_kms_helper_poll_fini(priv->drm); + drm_atomic_helper_shutdown(priv->drm); +} + +/* + * Only devices to use as components + * TOFIX: get rid of components when we can finally + * get meson_dx_hdmi to stop using the meson_drm + * private structure for HHI registers. + */ +static const struct of_device_id components_dev_match[] = { + { .compatible = "amlogic,meson-gxbb-dw-hdmi" }, + { .compatible = "amlogic,meson-gxl-dw-hdmi" }, + { .compatible = "amlogic,meson-gxm-dw-hdmi" }, + { .compatible = "amlogic,meson-g12a-dw-hdmi" }, + {} +}; + +static int meson_drv_probe(struct platform_device *pdev) +{ + struct component_match *match = NULL; + struct device_node *np = pdev->dev.of_node; + struct device_node *ep, *remote; + int count = 0; + + for_each_endpoint_of_node(np, ep) { + remote = of_graph_get_remote_port_parent(ep); + if (!remote || !of_device_is_available(remote)) { + of_node_put(remote); + continue; + } + + if (of_match_node(components_dev_match, remote)) { + component_match_add(&pdev->dev, &match, component_compare_of, remote); + + dev_dbg(&pdev->dev, "parent %pOF remote match add %pOF parent %s\n", + np, remote, dev_name(&pdev->dev)); + } + + of_node_put(remote); + + ++count; + } + + if (count && !match) + return meson_drv_bind_master(&pdev->dev, false); + + /* If some endpoints were found, initialize the nodes */ + if (count) { + dev_info(&pdev->dev, "Queued %d outputs on vpu\n", count); + + return component_master_add_with_match(&pdev->dev, + &meson_drv_master_ops, + match); + } + + /* If no output endpoints were available, simply bail out */ + return 0; +}; + +static void meson_drv_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &meson_drv_master_ops); +} + +static struct meson_drm_match_data meson_drm_gxbb_data = { + .compat = VPU_COMPATIBLE_GXBB, +}; + +static struct meson_drm_match_data meson_drm_gxl_data = { + .compat = VPU_COMPATIBLE_GXL, +}; + +static struct meson_drm_match_data meson_drm_gxm_data = { + .compat = VPU_COMPATIBLE_GXM, + .afbcd_ops = &meson_afbcd_gxm_ops, +}; + +static struct meson_drm_match_data meson_drm_g12a_data = { + .compat = VPU_COMPATIBLE_G12A, + .afbcd_ops = &meson_afbcd_g12a_ops, +}; + +static const struct of_device_id dt_match[] = { + { .compatible = "amlogic,meson-gxbb-vpu", + .data = (void *)&meson_drm_gxbb_data }, + { .compatible = "amlogic,meson-gxl-vpu", + .data = (void *)&meson_drm_gxl_data }, + { .compatible = "amlogic,meson-gxm-vpu", + .data = (void *)&meson_drm_gxm_data }, + { .compatible = "amlogic,meson-g12a-vpu", + .data = (void *)&meson_drm_g12a_data }, + {} +}; +MODULE_DEVICE_TABLE(of, dt_match); + +static const struct dev_pm_ops meson_drv_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(meson_drv_pm_suspend, meson_drv_pm_resume) +}; + +static struct platform_driver meson_drm_platform_driver = { + .probe = meson_drv_probe, + .remove = meson_drv_remove, + .shutdown = meson_drv_shutdown, + .driver = { + .name = "meson-drm", + .of_match_table = dt_match, + .pm = &meson_drv_pm_ops, + }, +}; + +drm_module_platform_driver(meson_drm_platform_driver); + +MODULE_AUTHOR("Jasper St. Pierre "); +MODULE_AUTHOR("Neil Armstrong "); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/meson/meson_drv.h b/drivers/gpu/drm/meson/meson_drv.h new file mode 100644 index 000000000..be4b0e4df --- /dev/null +++ b/drivers/gpu/drm/meson/meson_drv.h @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 BayLibre, SAS + * Author: Neil Armstrong + */ + +#ifndef __MESON_DRV_H +#define __MESON_DRV_H + +#include +#include +#include + +struct drm_crtc; +struct drm_device; +struct drm_plane; +struct meson_drm; +struct meson_afbcd_ops; + +enum vpu_compatible { + VPU_COMPATIBLE_GXBB = 0, + VPU_COMPATIBLE_GXL = 1, + VPU_COMPATIBLE_GXM = 2, + VPU_COMPATIBLE_G12A = 3, +}; + +enum { + MESON_ENC_CVBS = 0, + MESON_ENC_HDMI, + MESON_ENC_DSI, + MESON_ENC_LAST, +}; + +struct meson_drm_match_data { + enum vpu_compatible compat; + struct meson_afbcd_ops *afbcd_ops; +}; + +struct meson_drm_soc_limits { + unsigned long long max_hdmi_phy_freq; +}; + +struct meson_drm { + struct device *dev; + enum vpu_compatible compat; + void __iomem *io_base; + struct regmap *hhi; + int vsync_irq; + + struct meson_canvas *canvas; + u8 canvas_id_osd1; + u8 canvas_id_vd1_0; + u8 canvas_id_vd1_1; + u8 canvas_id_vd1_2; + + struct drm_device *drm; + struct drm_crtc *crtc; + struct drm_plane *primary_plane; + struct drm_plane *overlay_plane; + void *encoders[MESON_ENC_LAST]; + + const struct meson_drm_soc_limits *limits; + + /* Components Data */ + struct { + bool osd1_enabled; + bool osd1_interlace; + bool osd1_commit; + bool osd1_afbcd; + uint32_t osd1_ctrl_stat; + uint32_t osd1_ctrl_stat2; + uint32_t osd1_blk0_cfg[5]; + uint32_t osd1_blk1_cfg4; + uint32_t osd1_blk2_cfg4; + uint32_t osd1_addr; + uint32_t osd1_stride; + uint32_t osd1_height; + uint32_t osd1_width; + uint32_t osd_sc_ctrl0; + uint32_t osd_sc_i_wh_m1; + uint32_t osd_sc_o_h_start_end; + uint32_t osd_sc_o_v_start_end; + uint32_t osd_sc_v_ini_phase; + uint32_t osd_sc_v_phase_step; + uint32_t osd_sc_h_ini_phase; + uint32_t osd_sc_h_phase_step; + uint32_t osd_sc_h_ctrl0; + uint32_t osd_sc_v_ctrl0; + uint32_t osd_blend_din0_scope_h; + uint32_t osd_blend_din0_scope_v; + uint32_t osb_blend0_size; + uint32_t osb_blend1_size; + + bool vd1_enabled; + bool vd1_commit; + bool vd1_afbc; + unsigned int vd1_planes; + uint32_t vd1_if0_gen_reg; + uint32_t vd1_if0_luma_x0; + uint32_t vd1_if0_luma_y0; + uint32_t vd1_if0_chroma_x0; + uint32_t vd1_if0_chroma_y0; + uint32_t vd1_if0_repeat_loop; + uint32_t vd1_if0_luma0_rpt_pat; + uint32_t vd1_if0_chroma0_rpt_pat; + uint32_t vd1_range_map_y; + uint32_t vd1_range_map_cb; + uint32_t vd1_range_map_cr; + uint32_t viu_vd1_fmt_w; + uint32_t vd1_if0_canvas0; + uint32_t vd1_if0_gen_reg2; + uint32_t viu_vd1_fmt_ctrl; + uint32_t vd1_addr0; + uint32_t vd1_addr1; + uint32_t vd1_addr2; + uint32_t vd1_stride0; + uint32_t vd1_stride1; + uint32_t vd1_stride2; + uint32_t vd1_height0; + uint32_t vd1_height1; + uint32_t vd1_height2; + uint32_t vd1_afbc_mode; + uint32_t vd1_afbc_en; + uint32_t vd1_afbc_head_addr; + uint32_t vd1_afbc_body_addr; + uint32_t vd1_afbc_conv_ctrl; + uint32_t vd1_afbc_dec_def_color; + uint32_t vd1_afbc_vd_cfmt_ctrl; + uint32_t vd1_afbc_vd_cfmt_w; + uint32_t vd1_afbc_vd_cfmt_h; + uint32_t vd1_afbc_mif_hor_scope; + uint32_t vd1_afbc_mif_ver_scope; + uint32_t vd1_afbc_size_out; + uint32_t vd1_afbc_pixel_hor_scope; + uint32_t vd1_afbc_pixel_ver_scope; + uint32_t vd1_afbc_size_in; + uint32_t vpp_pic_in_height; + uint32_t vpp_postblend_vd1_h_start_end; + uint32_t vpp_postblend_vd1_v_start_end; + uint32_t vpp_hsc_region12_startp; + uint32_t vpp_hsc_region34_startp; + uint32_t vpp_hsc_region4_endp; + uint32_t vpp_hsc_start_phase_step; + uint32_t vpp_hsc_region1_phase_slope; + uint32_t vpp_hsc_region3_phase_slope; + uint32_t vpp_line_in_length; + uint32_t vpp_preblend_h_size; + uint32_t vpp_vsc_region12_startp; + uint32_t vpp_vsc_region34_startp; + uint32_t vpp_vsc_region4_endp; + uint32_t vpp_vsc_start_phase_step; + uint32_t vpp_vsc_ini_phase; + uint32_t vpp_vsc_phase_ctrl; + uint32_t vpp_hsc_phase_ctrl; + uint32_t vpp_blend_vd2_h_start_end; + uint32_t vpp_blend_vd2_v_start_end; + } viu; + + struct { + unsigned int current_mode; + bool hdmi_repeat; + bool venc_repeat; + bool hdmi_use_enci; + } venc; + + struct { + dma_addr_t addr_dma; + uint32_t *addr; + unsigned int offset; + } rdma; + + struct { + struct meson_afbcd_ops *ops; + u64 modifier; + u32 format; + } afbcd; +}; + +static inline int meson_vpu_is_compatible(struct meson_drm *priv, + enum vpu_compatible family) +{ + return priv->compat == family; +} + +#endif /* __MESON_DRV_H */ diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c new file mode 100644 index 000000000..fef1702ac --- /dev/null +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -0,0 +1,874 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 BayLibre, SAS + * Author: Neil Armstrong + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "meson_drv.h" +#include "meson_dw_hdmi.h" +#include "meson_registers.h" + +#define DRIVER_NAME "meson-dw-hdmi" +#define DRIVER_DESC "Amlogic Meson HDMI-TX DRM driver" + +/** + * DOC: HDMI Output + * + * HDMI Output is composed of : + * + * - A Synopsys DesignWare HDMI Controller IP + * - A TOP control block controlling the Clocks and PHY + * - A custom HDMI PHY in order convert video to TMDS signal + * + * .. code:: + * + * ___________________________________ + * | HDMI TOP |<= HPD + * |___________________________________| + * | | | + * | Synopsys HDMI | HDMI PHY |=> TMDS + * | Controller |________________| + * |___________________________________|<=> DDC + * + * + * The HDMI TOP block only supports HPD sensing. + * The Synopsys HDMI Controller interrupt is routed + * through the TOP Block interrupt. + * Communication to the TOP Block and the Synopsys + * HDMI Controller is done a pair of addr+read/write + * registers. + * The HDMI PHY is configured by registers in the + * HHI register block. + * + * Pixel data arrives in 4:4:4 format from the VENC + * block and the VPU HDMI mux selects either the ENCI + * encoder for the 576i or 480i formats or the ENCP + * encoder for all the other formats including + * interlaced HD formats. + * The VENC uses a DVI encoder on top of the ENCI + * or ENCP encoders to generate DVI timings for the + * HDMI controller. + * + * GXBB, GXL and GXM embeds the Synopsys DesignWare + * HDMI TX IP version 2.01a with HDCP and I2C & S/PDIF + * audio source interfaces. + * + * We handle the following features : + * + * - HPD Rise & Fall interrupt + * - HDMI Controller Interrupt + * - HDMI PHY Init for 480i to 1080p60 + * - VENC & HDMI Clock setup for 480i to 1080p60 + * - VENC Mode setup for 480i to 1080p60 + * + * What is missing : + * + * - PHY, Clock and Mode setup for 2k && 4k modes + * - SDDC Scrambling mode for HDMI 2.0a + * - HDCP Setup + * - CEC Management + */ + +/* TOP Block Communication Channel */ +#define HDMITX_TOP_ADDR_REG 0x0 +#define HDMITX_TOP_DATA_REG 0x4 +#define HDMITX_TOP_CTRL_REG 0x8 +#define HDMITX_TOP_G12A_OFFSET 0x8000 + +/* Controller Communication Channel */ +#define HDMITX_DWC_ADDR_REG 0x10 +#define HDMITX_DWC_DATA_REG 0x14 +#define HDMITX_DWC_CTRL_REG 0x18 + +/* HHI Registers */ +#define HHI_MEM_PD_REG0 0x100 /* 0x40 */ +#define HHI_HDMI_CLK_CNTL 0x1cc /* 0x73 */ +#define HHI_HDMI_PHY_CNTL0 0x3a0 /* 0xe8 */ +#define HHI_HDMI_PHY_CNTL1 0x3a4 /* 0xe9 */ +#define PHY_CNTL1_INIT 0x03900000 +#define PHY_INVERT BIT(17) +#define HHI_HDMI_PHY_CNTL2 0x3a8 /* 0xea */ +#define HHI_HDMI_PHY_CNTL3 0x3ac /* 0xeb */ +#define HHI_HDMI_PHY_CNTL4 0x3b0 /* 0xec */ +#define HHI_HDMI_PHY_CNTL5 0x3b4 /* 0xed */ + +static DEFINE_SPINLOCK(reg_lock); + +enum meson_venc_source { + MESON_VENC_SOURCE_NONE = 0, + MESON_VENC_SOURCE_ENCI = 1, + MESON_VENC_SOURCE_ENCP = 2, +}; + +struct meson_dw_hdmi; + +struct meson_dw_hdmi_data { + unsigned int (*top_read)(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr); + void (*top_write)(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr, unsigned int data); + unsigned int (*dwc_read)(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr); + void (*dwc_write)(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr, unsigned int data); + u32 cntl0_init; + u32 cntl1_init; +}; + +struct meson_dw_hdmi { + struct dw_hdmi_plat_data dw_plat_data; + struct meson_drm *priv; + struct device *dev; + void __iomem *hdmitx; + const struct meson_dw_hdmi_data *data; + struct reset_control *hdmitx_apb; + struct reset_control *hdmitx_ctrl; + struct reset_control *hdmitx_phy; + u32 irq_stat; + struct dw_hdmi *hdmi; + struct drm_bridge *bridge; +}; + +static inline int dw_hdmi_is_compatible(struct meson_dw_hdmi *dw_hdmi, + const char *compat) +{ + return of_device_is_compatible(dw_hdmi->dev->of_node, compat); +} + +/* PHY (via TOP bridge) and Controller dedicated register interface */ + +static unsigned int dw_hdmi_top_read(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr) +{ + unsigned long flags; + unsigned int data; + + spin_lock_irqsave(®_lock, flags); + + /* ADDR must be written twice */ + writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); + writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); + + /* Read needs a second DATA read */ + data = readl(dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG); + data = readl(dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG); + + spin_unlock_irqrestore(®_lock, flags); + + return data; +} + +static unsigned int dw_hdmi_g12a_top_read(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr) +{ + return readl(dw_hdmi->hdmitx + HDMITX_TOP_G12A_OFFSET + (addr << 2)); +} + +static inline void dw_hdmi_top_write(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr, unsigned int data) +{ + unsigned long flags; + + spin_lock_irqsave(®_lock, flags); + + /* ADDR must be written twice */ + writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); + writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_TOP_ADDR_REG); + + /* Write needs single DATA write */ + writel(data, dw_hdmi->hdmitx + HDMITX_TOP_DATA_REG); + + spin_unlock_irqrestore(®_lock, flags); +} + +static inline void dw_hdmi_g12a_top_write(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr, unsigned int data) +{ + writel(data, dw_hdmi->hdmitx + HDMITX_TOP_G12A_OFFSET + (addr << 2)); +} + +/* Helper to change specific bits in PHY registers */ +static inline void dw_hdmi_top_write_bits(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr, + unsigned int mask, + unsigned int val) +{ + unsigned int data = dw_hdmi->data->top_read(dw_hdmi, addr); + + data &= ~mask; + data |= val; + + dw_hdmi->data->top_write(dw_hdmi, addr, data); +} + +static unsigned int dw_hdmi_dwc_read(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr) +{ + unsigned long flags; + unsigned int data; + + spin_lock_irqsave(®_lock, flags); + + /* ADDR must be written twice */ + writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); + writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); + + /* Read needs a second DATA read */ + data = readl(dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG); + data = readl(dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG); + + spin_unlock_irqrestore(®_lock, flags); + + return data; +} + +static unsigned int dw_hdmi_g12a_dwc_read(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr) +{ + return readb(dw_hdmi->hdmitx + addr); +} + +static inline void dw_hdmi_dwc_write(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr, unsigned int data) +{ + unsigned long flags; + + spin_lock_irqsave(®_lock, flags); + + /* ADDR must be written twice */ + writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); + writel(addr & 0xffff, dw_hdmi->hdmitx + HDMITX_DWC_ADDR_REG); + + /* Write needs single DATA write */ + writel(data, dw_hdmi->hdmitx + HDMITX_DWC_DATA_REG); + + spin_unlock_irqrestore(®_lock, flags); +} + +static inline void dw_hdmi_g12a_dwc_write(struct meson_dw_hdmi *dw_hdmi, + unsigned int addr, unsigned int data) +{ + writeb(data, dw_hdmi->hdmitx + addr); +} + +/* Bridge */ + +/* Setup PHY bandwidth modes */ +static void meson_hdmi_phy_setup_mode(struct meson_dw_hdmi *dw_hdmi, + const struct drm_display_mode *mode, + bool mode_is_420) +{ + struct meson_drm *priv = dw_hdmi->priv; + unsigned int pixel_clock = mode->clock; + + /* For 420, pixel clock is half unlike venc clock */ + if (mode_is_420) pixel_clock /= 2; + + if (dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxl-dw-hdmi") || + dw_hdmi_is_compatible(dw_hdmi, "amlogic,meson-gxm-dw-hdmi")) { + if (pixel_clock >= 371250) { + /* 5.94Gbps, 3.7125Gbps */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x333d3282); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2136315b); + } else if (pixel_clock >= 297000) { + /* 2.97Gbps */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33303382); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2036315b); + } else if (pixel_clock >= 148500) { + /* 1.485Gbps */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33303362); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2016315b); + } else { + /* 742.5Mbps, and below */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33604142); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x0016315b); + } + } else if (dw_hdmi_is_compatible(dw_hdmi, + "amlogic,meson-gxbb-dw-hdmi")) { + if (pixel_clock >= 371250) { + /* 5.94Gbps, 3.7125Gbps */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33353245); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2100115b); + } else if (pixel_clock >= 297000) { + /* 2.97Gbps */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33634283); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0xb000115b); + } else { + /* 1.485Gbps, and below */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33632122); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2000115b); + } + } else if (dw_hdmi_is_compatible(dw_hdmi, + "amlogic,meson-g12a-dw-hdmi")) { + if (pixel_clock >= 371250) { + /* 5.94Gbps, 3.7125Gbps */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x37eb65c4); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2ab0ff3b); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL5, 0x0000080b); + } else if (pixel_clock >= 297000) { + /* 2.97Gbps */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33eb6262); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2ab0ff3b); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL5, 0x00000003); + } else { + /* 1.485Gbps, and below */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, 0x33eb4242); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL3, 0x2ab0ff3b); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL5, 0x00000003); + } + } +} + +static inline void meson_dw_hdmi_phy_reset(struct meson_dw_hdmi *dw_hdmi) +{ + struct meson_drm *priv = dw_hdmi->priv; + + /* Enable and software reset */ + regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0xf); + + mdelay(2); + + /* Enable and unreset */ + regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0xe); + + mdelay(2); +} + +static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, + const struct drm_display_info *display, + const struct drm_display_mode *mode) +{ + struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; + bool is_hdmi2_sink = display->hdmi.scdc.supported; + struct meson_drm *priv = dw_hdmi->priv; + unsigned int wr_clk = + readl_relaxed(priv->io_base + _REG(VPU_HDMI_SETTING)); + bool mode_is_420 = false; + + DRM_DEBUG_DRIVER("\"%s\" div%d\n", mode->name, + mode->clock > 340000 ? 40 : 10); + + if (drm_mode_is_420_only(display, mode) || + (!is_hdmi2_sink && drm_mode_is_420_also(display, mode)) || + dw_hdmi_bus_fmt_is_420(hdmi)) + mode_is_420 = true; + + /* TMDS pattern setup */ + if (mode->clock > 340000 && !mode_is_420) { + dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, + 0); + dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, + 0x03ff03ff); + } else { + dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_01, + 0x001f001f); + dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_23, + 0x001f001f); + } + + /* Load TMDS pattern */ + dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x1); + msleep(20); + dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_TMDS_CLK_PTTN_CNTL, 0x2); + + /* Setup PHY parameters */ + meson_hdmi_phy_setup_mode(dw_hdmi, mode, mode_is_420); + + /* Disable clock, fifo, fifo_wr */ + regmap_update_bits(priv->hhi, HHI_HDMI_PHY_CNTL1, 0xf, 0); + + dw_hdmi_set_high_tmds_clock_ratio(hdmi, display); + + msleep(100); + + /* Reset PHY 3 times in a row */ + meson_dw_hdmi_phy_reset(dw_hdmi); + meson_dw_hdmi_phy_reset(dw_hdmi); + meson_dw_hdmi_phy_reset(dw_hdmi); + + /* Temporary Disable VENC video stream */ + if (priv->venc.hdmi_use_enci) + writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN)); + else + writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN)); + + /* Temporary Disable HDMI video stream to HDMI-TX */ + writel_bits_relaxed(0x3, 0, + priv->io_base + _REG(VPU_HDMI_SETTING)); + writel_bits_relaxed(0xf << 8, 0, + priv->io_base + _REG(VPU_HDMI_SETTING)); + + /* Re-Enable VENC video stream */ + if (priv->venc.hdmi_use_enci) + writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN)); + else + writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN)); + + /* Push back HDMI clock settings */ + writel_bits_relaxed(0xf << 8, wr_clk & (0xf << 8), + priv->io_base + _REG(VPU_HDMI_SETTING)); + + /* Enable and Select HDMI video source for HDMI-TX */ + if (priv->venc.hdmi_use_enci) + writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCI, + priv->io_base + _REG(VPU_HDMI_SETTING)); + else + writel_bits_relaxed(0x3, MESON_VENC_SOURCE_ENCP, + priv->io_base + _REG(VPU_HDMI_SETTING)); + + return 0; +} + +static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi, + void *data) +{ + struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; + struct meson_drm *priv = dw_hdmi->priv; + + DRM_DEBUG_DRIVER("\n"); + + /* Fallback to init mode */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, dw_hdmi->data->cntl1_init); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, dw_hdmi->data->cntl0_init); +} + +static enum drm_connector_status dw_hdmi_read_hpd(struct dw_hdmi *hdmi, + void *data) +{ + struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; + + return !!dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_STAT0) ? + connector_status_connected : connector_status_disconnected; +} + +static void dw_hdmi_setup_hpd(struct dw_hdmi *hdmi, + void *data) +{ + struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; + + /* Setup HPD Filter */ + dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_HPD_FILTER, + (0xa << 12) | 0xa0); + + /* Clear interrupts */ + dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, + HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL); + + /* Unmask interrupts */ + dw_hdmi_top_write_bits(dw_hdmi, HDMITX_TOP_INTR_MASKN, + HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL, + HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL); +} + +static const struct dw_hdmi_phy_ops meson_dw_hdmi_phy_ops = { + .init = dw_hdmi_phy_init, + .disable = dw_hdmi_phy_disable, + .read_hpd = dw_hdmi_read_hpd, + .setup_hpd = dw_hdmi_setup_hpd, +}; + +static irqreturn_t dw_hdmi_top_irq(int irq, void *dev_id) +{ + struct meson_dw_hdmi *dw_hdmi = dev_id; + u32 stat; + + stat = dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_INTR_STAT); + dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, stat); + + /* HPD Events, handle in the threaded interrupt handler */ + if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) { + dw_hdmi->irq_stat = stat; + return IRQ_WAKE_THREAD; + } + + /* HDMI Controller Interrupt */ + if (stat & 1) + return IRQ_NONE; + + /* TOFIX Handle HDCP Interrupts */ + + return IRQ_HANDLED; +} + +/* Threaded interrupt handler to manage HPD events */ +static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id) +{ + struct meson_dw_hdmi *dw_hdmi = dev_id; + u32 stat = dw_hdmi->irq_stat; + + /* HPD Events */ + if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) { + bool hpd_connected = false; + + if (stat & HDMITX_TOP_INTR_HPD_RISE) + hpd_connected = true; + + dw_hdmi_setup_rx_sense(dw_hdmi->hdmi, hpd_connected, + hpd_connected); + + drm_helper_hpd_irq_event(dw_hdmi->bridge->dev); + drm_bridge_hpd_notify(dw_hdmi->bridge, + hpd_connected ? connector_status_connected + : connector_status_disconnected); + } + + return IRQ_HANDLED; +} + +/* DW HDMI Regmap */ + +static int meson_dw_hdmi_reg_read(void *context, unsigned int reg, + unsigned int *result) +{ + struct meson_dw_hdmi *dw_hdmi = context; + + *result = dw_hdmi->data->dwc_read(dw_hdmi, reg); + + return 0; + +} + +static int meson_dw_hdmi_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct meson_dw_hdmi *dw_hdmi = context; + + dw_hdmi->data->dwc_write(dw_hdmi, reg, val); + + return 0; +} + +static const struct regmap_config meson_dw_hdmi_regmap_config = { + .reg_bits = 32, + .val_bits = 8, + .reg_read = meson_dw_hdmi_reg_read, + .reg_write = meson_dw_hdmi_reg_write, + .max_register = 0x10000, + .fast_io = true, +}; + +static const struct meson_dw_hdmi_data meson_dw_hdmi_gxbb_data = { + .top_read = dw_hdmi_top_read, + .top_write = dw_hdmi_top_write, + .dwc_read = dw_hdmi_dwc_read, + .dwc_write = dw_hdmi_dwc_write, + .cntl0_init = 0x0, + .cntl1_init = PHY_CNTL1_INIT | PHY_INVERT, +}; + +static const struct meson_dw_hdmi_data meson_dw_hdmi_gxl_data = { + .top_read = dw_hdmi_top_read, + .top_write = dw_hdmi_top_write, + .dwc_read = dw_hdmi_dwc_read, + .dwc_write = dw_hdmi_dwc_write, + .cntl0_init = 0x0, + .cntl1_init = PHY_CNTL1_INIT, +}; + +static const struct meson_dw_hdmi_data meson_dw_hdmi_g12a_data = { + .top_read = dw_hdmi_g12a_top_read, + .top_write = dw_hdmi_g12a_top_write, + .dwc_read = dw_hdmi_g12a_dwc_read, + .dwc_write = dw_hdmi_g12a_dwc_write, + .cntl0_init = 0x000b4242, /* Bandgap */ + .cntl1_init = PHY_CNTL1_INIT, +}; + +static void meson_dw_hdmi_init(struct meson_dw_hdmi *meson_dw_hdmi) +{ + struct meson_drm *priv = meson_dw_hdmi->priv; + + /* Enable clocks */ + regmap_update_bits(priv->hhi, HHI_HDMI_CLK_CNTL, 0xffff, 0x100); + + /* Bring HDMITX MEM output of power down */ + regmap_update_bits(priv->hhi, HHI_MEM_PD_REG0, 0xff << 8, 0); + + /* Reset HDMITX APB & TX & PHY */ + reset_control_reset(meson_dw_hdmi->hdmitx_apb); + reset_control_reset(meson_dw_hdmi->hdmitx_ctrl); + reset_control_reset(meson_dw_hdmi->hdmitx_phy); + + /* Enable APB3 fail on error */ + if (!meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { + writel_bits_relaxed(BIT(15), BIT(15), + meson_dw_hdmi->hdmitx + HDMITX_TOP_CTRL_REG); + writel_bits_relaxed(BIT(15), BIT(15), + meson_dw_hdmi->hdmitx + HDMITX_DWC_CTRL_REG); + } + + /* Bring out of reset */ + meson_dw_hdmi->data->top_write(meson_dw_hdmi, + HDMITX_TOP_SW_RESET, 0); + + msleep(20); + + meson_dw_hdmi->data->top_write(meson_dw_hdmi, + HDMITX_TOP_CLK_CNTL, 0xff); + + /* Enable normal output to PHY */ + meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_BIST_CNTL, BIT(12)); + + /* Setup PHY */ + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, meson_dw_hdmi->data->cntl1_init); + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, meson_dw_hdmi->data->cntl0_init); + + /* Enable HDMI-TX Interrupt */ + meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, + HDMITX_TOP_INTR_CORE); + + meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_INTR_MASKN, + HDMITX_TOP_INTR_CORE); + +} + +static void meson_disable_clk(void *data) +{ + clk_disable_unprepare(data); +} + +static int meson_enable_clk(struct device *dev, char *name) +{ + struct clk *clk; + int ret; + + clk = devm_clk_get(dev, name); + if (IS_ERR(clk)) { + dev_err(dev, "Unable to get %s pclk\n", name); + return PTR_ERR(clk); + } + + ret = clk_prepare_enable(clk); + if (!ret) + ret = devm_add_action_or_reset(dev, meson_disable_clk, clk); + + return ret; +} + +static int meson_dw_hdmi_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + const struct meson_dw_hdmi_data *match; + struct meson_dw_hdmi *meson_dw_hdmi; + struct drm_device *drm = data; + struct meson_drm *priv = drm->dev_private; + struct dw_hdmi_plat_data *dw_plat_data; + int irq; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + match = of_device_get_match_data(&pdev->dev); + if (!match) { + dev_err(&pdev->dev, "failed to get match data\n"); + return -ENODEV; + } + + meson_dw_hdmi = devm_kzalloc(dev, sizeof(*meson_dw_hdmi), + GFP_KERNEL); + if (!meson_dw_hdmi) + return -ENOMEM; + + meson_dw_hdmi->priv = priv; + meson_dw_hdmi->dev = dev; + meson_dw_hdmi->data = match; + dw_plat_data = &meson_dw_hdmi->dw_plat_data; + + ret = devm_regulator_get_enable_optional(dev, "hdmi"); + if (ret < 0 && ret != -ENODEV) + return ret; + + meson_dw_hdmi->hdmitx_apb = devm_reset_control_get_exclusive(dev, + "hdmitx_apb"); + if (IS_ERR(meson_dw_hdmi->hdmitx_apb)) { + dev_err(dev, "Failed to get hdmitx_apb reset\n"); + return PTR_ERR(meson_dw_hdmi->hdmitx_apb); + } + + meson_dw_hdmi->hdmitx_ctrl = devm_reset_control_get_exclusive(dev, + "hdmitx"); + if (IS_ERR(meson_dw_hdmi->hdmitx_ctrl)) { + dev_err(dev, "Failed to get hdmitx reset\n"); + return PTR_ERR(meson_dw_hdmi->hdmitx_ctrl); + } + + meson_dw_hdmi->hdmitx_phy = devm_reset_control_get_exclusive(dev, + "hdmitx_phy"); + if (IS_ERR(meson_dw_hdmi->hdmitx_phy)) { + dev_err(dev, "Failed to get hdmitx_phy reset\n"); + return PTR_ERR(meson_dw_hdmi->hdmitx_phy); + } + + meson_dw_hdmi->hdmitx = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(meson_dw_hdmi->hdmitx)) + return PTR_ERR(meson_dw_hdmi->hdmitx); + + ret = meson_enable_clk(dev, "isfr"); + if (ret) + return ret; + + ret = meson_enable_clk(dev, "iahb"); + if (ret) + return ret; + + ret = meson_enable_clk(dev, "venci"); + if (ret) + return ret; + + dw_plat_data->regm = devm_regmap_init(dev, NULL, meson_dw_hdmi, + &meson_dw_hdmi_regmap_config); + if (IS_ERR(dw_plat_data->regm)) + return PTR_ERR(dw_plat_data->regm); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, dw_hdmi_top_irq, + dw_hdmi_top_thread_irq, IRQF_SHARED, + "dw_hdmi_top_irq", meson_dw_hdmi); + if (ret) { + dev_err(dev, "Failed to request hdmi top irq\n"); + return ret; + } + + meson_dw_hdmi_init(meson_dw_hdmi); + + /* Bridge / Connector */ + + dw_plat_data->priv_data = meson_dw_hdmi; + dw_plat_data->phy_ops = &meson_dw_hdmi_phy_ops; + dw_plat_data->phy_name = "meson_dw_hdmi_phy"; + dw_plat_data->phy_data = meson_dw_hdmi; + dw_plat_data->input_bus_encoding = V4L2_YCBCR_ENC_709; + dw_plat_data->ycbcr_420_allowed = true; + dw_plat_data->disable_cec = true; + dw_plat_data->output_port = 1; + + if (dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-gxl-dw-hdmi") || + dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-gxm-dw-hdmi") || + dw_hdmi_is_compatible(meson_dw_hdmi, "amlogic,meson-g12a-dw-hdmi")) + dw_plat_data->use_drm_infoframe = true; + + platform_set_drvdata(pdev, meson_dw_hdmi); + + meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, &meson_dw_hdmi->dw_plat_data); + if (IS_ERR(meson_dw_hdmi->hdmi)) + return PTR_ERR(meson_dw_hdmi->hdmi); + + meson_dw_hdmi->bridge = of_drm_find_and_get_bridge(pdev->dev.of_node); + + DRM_DEBUG_DRIVER("HDMI controller initialized\n"); + + return 0; +} + +static void meson_dw_hdmi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); + struct platform_device *pdev = to_platform_device(dev); + int irq = platform_get_irq(pdev, 0); + + devm_free_irq(dev, irq, meson_dw_hdmi); + dw_hdmi_unbind(meson_dw_hdmi->hdmi); + drm_bridge_put(meson_dw_hdmi->bridge); +} + +static const struct component_ops meson_dw_hdmi_ops = { + .bind = meson_dw_hdmi_bind, + .unbind = meson_dw_hdmi_unbind, +}; + +static int __maybe_unused meson_dw_hdmi_pm_suspend(struct device *dev) +{ + struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); + + if (!meson_dw_hdmi) + return 0; + + /* Reset TOP */ + meson_dw_hdmi->data->top_write(meson_dw_hdmi, + HDMITX_TOP_SW_RESET, 0); + + return 0; +} + +static int __maybe_unused meson_dw_hdmi_pm_resume(struct device *dev) +{ + struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); + + if (!meson_dw_hdmi) + return 0; + + meson_dw_hdmi_init(meson_dw_hdmi); + + dw_hdmi_resume(meson_dw_hdmi->hdmi); + + return 0; +} + +static int meson_dw_hdmi_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &meson_dw_hdmi_ops); +} + +static void meson_dw_hdmi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &meson_dw_hdmi_ops); +} + +static const struct dev_pm_ops meson_dw_hdmi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(meson_dw_hdmi_pm_suspend, + meson_dw_hdmi_pm_resume) +}; + +static const struct of_device_id meson_dw_hdmi_of_table[] = { + { .compatible = "amlogic,meson-gxbb-dw-hdmi", + .data = &meson_dw_hdmi_gxbb_data }, + { .compatible = "amlogic,meson-gxl-dw-hdmi", + .data = &meson_dw_hdmi_gxl_data }, + { .compatible = "amlogic,meson-gxm-dw-hdmi", + .data = &meson_dw_hdmi_gxl_data }, + { .compatible = "amlogic,meson-g12a-dw-hdmi", + .data = &meson_dw_hdmi_g12a_data }, + { } +}; +MODULE_DEVICE_TABLE(of, meson_dw_hdmi_of_table); + +static struct platform_driver meson_dw_hdmi_platform_driver = { + .probe = meson_dw_hdmi_probe, + .remove = meson_dw_hdmi_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = meson_dw_hdmi_of_table, + .pm = &meson_dw_hdmi_pm_ops, + }, +}; +module_platform_driver(meson_dw_hdmi_platform_driver); + +MODULE_AUTHOR("Neil Armstrong "); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.h b/drivers/gpu/drm/meson/meson_dw_hdmi.h new file mode 100644 index 000000000..08e1c14e4 --- /dev/null +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 BayLibre, SAS + * Author: Neil Armstrong + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + */ + +#ifndef __MESON_DW_HDMI_H +#define __MESON_DW_HDMI_H + +/* + * Bit 15-10: RW Reserved. Default 1 starting from G12A + * Bit 9 RW sw_reset_i2c starting from G12A + * Bit 8 RW sw_reset_axiarb starting from G12A + * Bit 7 RW Reserved. Default 1, sw_reset_emp starting from G12A + * Bit 6 RW Reserved. Default 1, sw_reset_flt starting from G12A + * Bit 5 RW Reserved. Default 1, sw_reset_hdcp22 starting from G12A + * Bit 4 RW sw_reset_phyif: PHY interface. 1=Apply reset; 0=Release from reset. + * Default 1. + * Bit 3 RW sw_reset_intr: interrupt module. 1=Apply reset; + * 0=Release from reset. + * Default 1. + * Bit 2 RW sw_reset_mem: KSV/REVOC mem. 1=Apply reset; 0=Release from reset. + * Default 1. + * Bit 1 RW sw_reset_rnd: random number interface to HDCP. 1=Apply reset; + * 0=Release from reset. Default 1. + * Bit 0 RW sw_reset_core: connects to IP's ~irstz. 1=Apply reset; + * 0=Release from reset. Default 1. + */ +#define HDMITX_TOP_SW_RESET (0x000) + +/* + * Bit 31 RW free_clk_en: 0=Enable clock gating for power saving; 1= Disable + * Bit 12 RW i2s_ws_inv:1=Invert i2s_ws; 0=No invert. Default 0. + * Bit 11 RW i2s_clk_inv: 1=Invert i2s_clk; 0=No invert. Default 0. + * Bit 10 RW spdif_clk_inv: 1=Invert spdif_clk; 0=No invert. Default 0. + * Bit 9 RW tmds_clk_inv: 1=Invert tmds_clk; 0=No invert. Default 0. + * Bit 8 RW pixel_clk_inv: 1=Invert pixel_clk; 0=No invert. Default 0. + * Bit 7 RW hdcp22_skpclk_en: starting from G12A, 1=enable; 0=disable + * Bit 6 RW hdcp22_esmclk_en: starting from G12A, 1=enable; 0=disable + * Bit 5 RW hdcp22_tmdsclk_en: starting from G12A, 1=enable; 0=disable + * Bit 4 RW cec_clk_en: 1=enable cec_clk; 0=disable. Default 0. Reserved for G12A + * Bit 3 RW i2s_clk_en: 1=enable i2s_clk; 0=disable. Default 0. + * Bit 2 RW spdif_clk_en: 1=enable spdif_clk; 0=disable. Default 0. + * Bit 1 RW tmds_clk_en: 1=enable tmds_clk; 0=disable. Default 0. + * Bit 0 RW pixel_clk_en: 1=enable pixel_clk; 0=disable. Default 0. + */ +#define HDMITX_TOP_CLK_CNTL (0x001) + +/* + * Bit 31:28 RW rxsense_glitch_width: starting from G12A + * Bit 27:16 RW rxsense_valid_width: starting from G12A + * Bit 11: 0 RW hpd_valid_width: filter out width <= M*1024. Default 0. + * Bit 15:12 RW hpd_glitch_width: filter out glitch <= N. Default 0. + */ +#define HDMITX_TOP_HPD_FILTER (0x002) + +/* + * intr_maskn: MASK_N, one bit per interrupt source. + * 1=Enable interrupt source; 0=Disable interrupt source. Default 0. + * [ 7] rxsense_fall starting from G12A + * [ 6] rxsense_rise starting from G12A + * [ 5] err_i2c_timeout starting from G12A + * [ 4] hdcp22_rndnum_err + * [ 3] nonce_rfrsh_rise + * [ 2] hpd_fall_intr + * [ 1] hpd_rise_intr + * [ 0] core_intr + */ +#define HDMITX_TOP_INTR_MASKN (0x003) + +/* + * Bit 30: 0 RW intr_stat: For each bit, write 1 to manually set the interrupt + * bit, read back the interrupt status. + * Bit 31 R IP interrupt status + * Bit 7 RW rxsense_fall starting from G12A + * Bit 6 RW rxsense_rise starting from G12A + * Bit 5 RW err_i2c_timeout starting from G12A + * Bit 2 RW hpd_fall + * Bit 1 RW hpd_rise + * Bit 0 RW IP interrupt + */ +#define HDMITX_TOP_INTR_STAT (0x004) + +/* + * [7] rxsense_fall starting from G12A + * [6] rxsense_rise starting from G12A + * [5] err_i2c_timeout starting from G12A + * [4] hdcp22_rndnum_err + * [3] nonce_rfrsh_rise + * [2] hpd_fall + * [1] hpd_rise + * [0] core_intr_rise + */ +#define HDMITX_TOP_INTR_STAT_CLR (0x005) + +#define HDMITX_TOP_INTR_CORE BIT(0) +#define HDMITX_TOP_INTR_HPD_RISE BIT(1) +#define HDMITX_TOP_INTR_HPD_FALL BIT(2) +#define HDMITX_TOP_INTR_RXSENSE_RISE BIT(6) +#define HDMITX_TOP_INTR_RXSENSE_FALL BIT(7) + +/* + * Bit 14:12 RW tmds_sel: 3'b000=Output zero; 3'b001=Output normal TMDS data; + * 3'b010=Output PRBS data; 3'b100=Output shift pattern. Default 0. + * Bit 11: 9 RW shift_pttn_repeat: 0=New pattern every clk cycle; 1=New pattern + * every 2 clk cycles; ...; 7=New pattern every 8 clk cycles. Default 0. + * Bit 8 RW shift_pttn_en: 1= Enable shift pattern generator; 0=Disable. + * Default 0. + * Bit 4: 3 RW prbs_pttn_mode: 0=PRBS11; 1=PRBS15; 2=PRBS7; 3=PRBS31. Default 0. + * Bit 2: 1 RW prbs_pttn_width: 0=idle; 1=output 8-bit pattern; + * 2=Output 1-bit pattern; 3=output 10-bit pattern. Default 0. + * Bit 0 RW prbs_pttn_en: 1=Enable PRBS generator; 0=Disable. Default 0. + */ +#define HDMITX_TOP_BIST_CNTL (0x006) + +/* Bit 29:20 RW shift_pttn_data[59:50]. Default 0. */ +/* Bit 19:10 RW shift_pttn_data[69:60]. Default 0. */ +/* Bit 9: 0 RW shift_pttn_data[79:70]. Default 0. */ +#define HDMITX_TOP_SHIFT_PTTN_012 (0x007) + +/* Bit 29:20 RW shift_pttn_data[29:20]. Default 0. */ +/* Bit 19:10 RW shift_pttn_data[39:30]. Default 0. */ +/* Bit 9: 0 RW shift_pttn_data[49:40]. Default 0. */ +#define HDMITX_TOP_SHIFT_PTTN_345 (0x008) + +/* Bit 19:10 RW shift_pttn_data[ 9: 0]. Default 0. */ +/* Bit 9: 0 RW shift_pttn_data[19:10]. Default 0. */ +#define HDMITX_TOP_SHIFT_PTTN_67 (0x009) + +/* Bit 25:16 RW tmds_clk_pttn[19:10]. Default 0. */ +/* Bit 9: 0 RW tmds_clk_pttn[ 9: 0]. Default 0. */ +#define HDMITX_TOP_TMDS_CLK_PTTN_01 (0x00A) + +/* Bit 25:16 RW tmds_clk_pttn[39:30]. Default 0. */ +/* Bit 9: 0 RW tmds_clk_pttn[29:20]. Default 0. */ +#define HDMITX_TOP_TMDS_CLK_PTTN_23 (0x00B) + +/* + * Bit 1 RW shift_tmds_clk_pttn:1=Enable shifting clk pattern, + * used when TMDS CLK rate = TMDS character rate /4. Default 0. + * Bit 0 R Reserved. Default 0. + * [ 1] shift_tmds_clk_pttn + * [ 0] load_tmds_clk_pttn + */ +#define HDMITX_TOP_TMDS_CLK_PTTN_CNTL (0x00C) + +/* + * Bit 0 RW revocmem_wr_fail: Read back 1 to indicate Host write REVOC MEM + * failure, write 1 to clear the failure flag. Default 0. + */ +#define HDMITX_TOP_REVOCMEM_STAT (0x00D) + +/* + * Bit 1 R filtered RxSense status + * Bit 0 R filtered HPD status. + */ +#define HDMITX_TOP_STAT0 (0x00E) + +#endif /* __MESON_DW_HDMI_H */ diff --git a/drivers/gpu/drm/meson/meson_dw_mipi_dsi.c b/drivers/gpu/drm/meson/meson_dw_mipi_dsi.c new file mode 100644 index 000000000..867918e9f --- /dev/null +++ b/drivers/gpu/drm/meson/meson_dw_mipi_dsi.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 BayLibre, SAS + * Author: Neil Armstrong + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include