diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/gpu/drm/pl111 | |
| download | linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip | |
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
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) <mhiramat@kernel.org>
Signed-off-by: Jinke Han <jinkehan@didiglobal.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Acked-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/gpu/drm/pl111')
| -rw-r--r-- | drivers/gpu/drm/pl111/Kconfig | 16 | ||||
| -rw-r--r-- | drivers/gpu/drm/pl111/Makefile | 9 | ||||
| -rw-r--r-- | drivers/gpu/drm/pl111/pl111_debugfs.c | 59 | ||||
| -rw-r--r-- | drivers/gpu/drm/pl111/pl111_display.c | 600 | ||||
| -rw-r--r-- | drivers/gpu/drm/pl111/pl111_drm.h | 162 | ||||
| -rw-r--r-- | drivers/gpu/drm/pl111/pl111_drv.c | 453 | ||||
| -rw-r--r-- | drivers/gpu/drm/pl111/pl111_nomadik.c | 37 | ||||
| -rw-r--r-- | drivers/gpu/drm/pl111/pl111_nomadik.h | 19 | ||||
| -rw-r--r-- | drivers/gpu/drm/pl111/pl111_versatile.c | 566 | ||||
| -rw-r--r-- | drivers/gpu/drm/pl111/pl111_versatile.h | 12 |
10 files changed, 1933 insertions, 0 deletions
diff --git a/drivers/gpu/drm/pl111/Kconfig b/drivers/gpu/drm/pl111/Kconfig new file mode 100644 index 000000000..82e918820 --- /dev/null +++ b/drivers/gpu/drm/pl111/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_PL111 + tristate "DRM Support for PL111 CLCD Controller" + depends on DRM + depends on ARM || ARM64 || COMPILE_TEST + depends on VEXPRESS_CONFIG || VEXPRESS_CONFIG=n + depends on COMMON_CLK + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_GEM_DMA_HELPER + select DRM_BRIDGE + select DRM_PANEL_BRIDGE + help + Choose this option for DRM support for the PL111 CLCD controller. + If M is selected the module will be called pl111_drm. + diff --git a/drivers/gpu/drm/pl111/Makefile b/drivers/gpu/drm/pl111/Makefile new file mode 100644 index 000000000..67d430d43 --- /dev/null +++ b/drivers/gpu/drm/pl111/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +pl111_drm-y += pl111_display.o \ + pl111_versatile.o \ + pl111_drv.o + +pl111_drm-$(CONFIG_ARCH_NOMADIK) += pl111_nomadik.o +pl111_drm-$(CONFIG_DEBUG_FS) += pl111_debugfs.o + +obj-$(CONFIG_DRM_PL111) += pl111_drm.o diff --git a/drivers/gpu/drm/pl111/pl111_debugfs.c b/drivers/gpu/drm/pl111/pl111_debugfs.c new file mode 100644 index 000000000..6744fa16f --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_debugfs.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2017 Broadcom + */ + +#include <linux/seq_file.h> + +#include <drm/drm_debugfs.h> +#include <drm/drm_file.h> + +#include "pl111_drm.h" + +#define REGDEF(reg) { reg, #reg } +static const struct { + u32 reg; + const char *name; +} pl111_reg_defs[] = { + REGDEF(CLCD_TIM0), + REGDEF(CLCD_TIM1), + REGDEF(CLCD_TIM2), + REGDEF(CLCD_TIM3), + REGDEF(CLCD_UBAS), + REGDEF(CLCD_LBAS), + REGDEF(CLCD_PL111_CNTL), + REGDEF(CLCD_PL111_IENB), + REGDEF(CLCD_PL111_RIS), + REGDEF(CLCD_PL111_MIS), + REGDEF(CLCD_PL111_ICR), + REGDEF(CLCD_PL111_UCUR), + REGDEF(CLCD_PL111_LCUR), +}; + +static int pl111_debugfs_regs(struct seq_file *m, void *unused) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct pl111_drm_dev_private *priv = dev->dev_private; + int i; + + for (i = 0; i < ARRAY_SIZE(pl111_reg_defs); i++) { + seq_printf(m, "%s (0x%04x): 0x%08x\n", + pl111_reg_defs[i].name, pl111_reg_defs[i].reg, + readl(priv->regs + pl111_reg_defs[i].reg)); + } + + return 0; +} + +static const struct drm_info_list pl111_debugfs_list[] = { + {"regs", pl111_debugfs_regs, 0}, +}; + +void +pl111_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(pl111_debugfs_list, + ARRAY_SIZE(pl111_debugfs_list), + minor->debugfs_root, minor); +} diff --git a/drivers/gpu/drm/pl111/pl111_display.c b/drivers/gpu/drm/pl111/pl111_display.c new file mode 100644 index 000000000..5d10bc5fd --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_display.c @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved. + * + * Parts of this file were based on sources as follows: + * + * Copyright (c) 2006-2008 Intel Corporation + * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> + * Copyright (C) 2011 Texas Instruments + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-buf.h> +#include <linux/media-bus-format.h> +#include <linux/of_graph.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_print.h> +#include <drm/drm_vblank.h> + +#include "pl111_drm.h" + +irqreturn_t pl111_irq(int irq, void *data) +{ + struct pl111_drm_dev_private *priv = data; + u32 irq_stat; + irqreturn_t status = IRQ_NONE; + + irq_stat = readl(priv->regs + CLCD_PL111_MIS); + + if (!irq_stat) + return IRQ_NONE; + + if (irq_stat & CLCD_IRQ_NEXTBASE_UPDATE) { + drm_crtc_handle_vblank(&priv->pipe.crtc); + + status = IRQ_HANDLED; + } + + /* Clear the interrupt once done */ + writel(irq_stat, priv->regs + CLCD_PL111_ICR); + + return status; +} + +static enum drm_mode_status +pl111_mode_valid(struct drm_simple_display_pipe *pipe, + const struct drm_display_mode *mode) +{ + struct drm_device *drm = pipe->crtc.dev; + struct pl111_drm_dev_private *priv = drm->dev_private; + u32 cpp = DIV_ROUND_UP(priv->variant->fb_depth, 8); + u64 bw; + + /* + * We use the pixelclock to also account for interlaced modes, the + * resulting bandwidth is in bytes per second. + */ + bw = mode->clock * 1000ULL; /* In Hz */ + bw = bw * mode->hdisplay * mode->vdisplay * cpp; + bw = div_u64(bw, mode->htotal * mode->vtotal); + + /* + * If no bandwidth constraints, anything goes, else + * check if we are too fast. + */ + if (priv->memory_bw && (bw > priv->memory_bw)) { + DRM_DEBUG_KMS("%d x %d @ %d Hz, %d cpp, bw %llu too fast\n", + mode->hdisplay, mode->vdisplay, + mode->clock * 1000, cpp, bw); + + return MODE_BAD; + } + DRM_DEBUG_KMS("%d x %d @ %d Hz, %d cpp, bw %llu bytes/s OK\n", + mode->hdisplay, mode->vdisplay, + mode->clock * 1000, cpp, bw); + + return MODE_OK; +} + +static int pl111_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 (mode->hdisplay % 16) + return -EINVAL; + + if (fb) { + u32 offset = drm_fb_dma_get_gem_addr(fb, pstate, 0); + + /* FB base address must be dword aligned. */ + if (offset & 3) + return -EINVAL; + + /* There's no pitch register -- the mode's hdisplay + * controls it. + */ + if (fb->pitches[0] != mode->hdisplay * fb->format->cpp[0]) + 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 void pl111_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 pl111_drm_dev_private *priv = drm->dev_private; + const struct drm_display_mode *mode = &cstate->mode; + struct drm_framebuffer *fb = plane->state->fb; + struct drm_connector *connector = priv->connector; + struct drm_bridge *bridge = priv->bridge; + bool grayscale = false; + u32 cntl; + u32 ppl, hsw, hfp, hbp; + u32 lpp, vsw, vfp, vbp; + u32 cpl, tim2; + int ret; + + ret = clk_set_rate(priv->clk, mode->clock * 1000); + if (ret) { + drm_err(drm, + "Failed to set pixel clock rate to %d: %d\n", + mode->clock * 1000, ret); + } + + clk_prepare_enable(priv->clk); + + ppl = (mode->hdisplay / 16) - 1; + hsw = mode->hsync_end - mode->hsync_start - 1; + hfp = mode->hsync_start - mode->hdisplay - 1; + hbp = mode->htotal - mode->hsync_end - 1; + + lpp = mode->vdisplay - 1; + vsw = mode->vsync_end - mode->vsync_start - 1; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + cpl = mode->hdisplay - 1; + + writel((ppl << 2) | + (hsw << 8) | + (hfp << 16) | + (hbp << 24), + priv->regs + CLCD_TIM0); + writel(lpp | + (vsw << 10) | + (vfp << 16) | + (vbp << 24), + priv->regs + CLCD_TIM1); + + spin_lock(&priv->tim2_lock); + + tim2 = readl(priv->regs + CLCD_TIM2); + tim2 &= (TIM2_BCD | TIM2_PCD_LO_MASK | TIM2_PCD_HI_MASK); + + if (priv->variant->broken_clockdivider) + tim2 |= TIM2_BCD; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + tim2 |= TIM2_IHS; + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + tim2 |= TIM2_IVS; + + if (connector) { + if (connector->display_info.bus_flags & DRM_BUS_FLAG_DE_LOW) + tim2 |= TIM2_IOE; + + if (connector->display_info.bus_flags & + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) + tim2 |= TIM2_IPC; + + if (connector->display_info.num_bus_formats == 1 && + connector->display_info.bus_formats[0] == + MEDIA_BUS_FMT_Y8_1X8) + grayscale = true; + + /* + * The AC pin bias frequency is set to max count when using + * grayscale so at least once in a while we will reverse + * polarity and get rid of any DC built up that could + * damage the display. + */ + if (grayscale) + tim2 |= TIM2_ACB_MASK; + } + + if (bridge) { + const struct drm_bridge_timings *btimings = bridge->timings; + + /* + * Here is when things get really fun. Sometimes the bridge + * timings are such that the signal out from PL11x is not + * stable before the receiving bridge (such as a dumb VGA DAC + * or similar) samples it. If that happens, we compensate by + * the only method we have: output the data on the opposite + * edge of the clock so it is for sure stable when it gets + * sampled. + * + * The PL111 manual does not contain proper timining diagrams + * or data for these details, but we know from experiments + * that the setup time is more than 3000 picoseconds (3 ns). + * If we have a bridge that requires the signal to be stable + * earlier than 3000 ps before the clock pulse, we have to + * output the data on the opposite edge to avoid flicker. + */ + if (btimings && btimings->setup_time_ps >= 3000) + tim2 ^= TIM2_IPC; + } + + tim2 |= cpl << 16; + writel(tim2, priv->regs + CLCD_TIM2); + spin_unlock(&priv->tim2_lock); + + writel(0, priv->regs + CLCD_TIM3); + + /* + * Detect grayscale bus format. We do not support a grayscale mode + * toward userspace, instead we expose an RGB24 buffer and then the + * hardware will activate its grayscaler to convert to the grayscale + * format. + */ + if (grayscale) + cntl = CNTL_LCDEN | CNTL_LCDMONO8; + else + /* Else we assume TFT display */ + cntl = CNTL_LCDEN | CNTL_LCDTFT | CNTL_LCDVCOMP(1); + + /* On the ST Micro variant, assume all 24 bits are connected */ + if (priv->variant->st_bitmux_control) + cntl |= CNTL_ST_CDWID_24; + + /* + * Note that the ARM hardware's format reader takes 'r' from + * the low bit, while DRM formats list channels from high bit + * to low bit as you read left to right. The ST Micro version of + * the PL110 (LCDC) however uses the standard DRM format. + */ + switch (fb->format->format) { + case DRM_FORMAT_BGR888: + /* Only supported on the ST Micro variant */ + if (priv->variant->st_bitmux_control) + cntl |= CNTL_ST_LCDBPP24_PACKED | CNTL_BGR; + break; + case DRM_FORMAT_RGB888: + /* Only supported on the ST Micro variant */ + if (priv->variant->st_bitmux_control) + cntl |= CNTL_ST_LCDBPP24_PACKED; + break; + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + if (priv->variant->st_bitmux_control) + cntl |= CNTL_LCDBPP24 | CNTL_BGR; + else + cntl |= CNTL_LCDBPP24; + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + if (priv->variant->st_bitmux_control) + cntl |= CNTL_LCDBPP24; + else + cntl |= CNTL_LCDBPP24 | CNTL_BGR; + break; + case DRM_FORMAT_BGR565: + if (priv->variant->is_pl110) + cntl |= CNTL_LCDBPP16; + else if (priv->variant->st_bitmux_control) + cntl |= CNTL_LCDBPP16 | CNTL_ST_1XBPP_565 | CNTL_BGR; + else + cntl |= CNTL_LCDBPP16_565; + break; + case DRM_FORMAT_RGB565: + if (priv->variant->is_pl110) + cntl |= CNTL_LCDBPP16 | CNTL_BGR; + else if (priv->variant->st_bitmux_control) + cntl |= CNTL_LCDBPP16 | CNTL_ST_1XBPP_565; + else + cntl |= CNTL_LCDBPP16_565 | CNTL_BGR; + break; + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_XBGR1555: + cntl |= CNTL_LCDBPP16; + if (priv->variant->st_bitmux_control) + cntl |= CNTL_ST_1XBPP_5551 | CNTL_BGR; + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + cntl |= CNTL_LCDBPP16; + if (priv->variant->st_bitmux_control) + cntl |= CNTL_ST_1XBPP_5551; + else + cntl |= CNTL_BGR; + break; + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_XBGR4444: + cntl |= CNTL_LCDBPP16_444; + if (priv->variant->st_bitmux_control) + cntl |= CNTL_ST_1XBPP_444 | CNTL_BGR; + break; + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_XRGB4444: + cntl |= CNTL_LCDBPP16_444; + if (priv->variant->st_bitmux_control) + cntl |= CNTL_ST_1XBPP_444; + else + cntl |= CNTL_BGR; + break; + default: + WARN_ONCE(true, "Unknown FB format 0x%08x\n", + fb->format->format); + break; + } + + /* The PL110 in Integrator/Versatile does the BGR routing externally */ + if (priv->variant->external_bgr) + cntl &= ~CNTL_BGR; + + /* Power sequence: first enable and chill */ + writel(cntl, priv->regs + priv->ctrl); + + /* + * We expect this delay to stabilize the contrast + * voltage Vee as stipulated by the manual + */ + msleep(20); + + if (priv->variant_display_enable) + priv->variant_display_enable(drm, fb->format->format); + + /* Power Up */ + cntl |= CNTL_LCDPWR; + writel(cntl, priv->regs + priv->ctrl); + + if (!priv->variant->broken_vblank) + drm_crtc_vblank_on(crtc); +} + +static void pl111_display_disable(struct drm_simple_display_pipe *pipe) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_device *drm = crtc->dev; + struct pl111_drm_dev_private *priv = drm->dev_private; + u32 cntl; + + if (!priv->variant->broken_vblank) + drm_crtc_vblank_off(crtc); + + /* Power Down */ + cntl = readl(priv->regs + priv->ctrl); + if (cntl & CNTL_LCDPWR) { + cntl &= ~CNTL_LCDPWR; + writel(cntl, priv->regs + priv->ctrl); + } + + /* + * We expect this delay to stabilize the contrast voltage Vee as + * stipulated by the manual + */ + msleep(20); + + if (priv->variant_display_disable) + priv->variant_display_disable(drm); + + /* Disable */ + writel(0, priv->regs + priv->ctrl); + + clk_disable_unprepare(priv->clk); +} + +static void pl111_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 pl111_drm_dev_private *priv = drm->dev_private; + 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; + + if (fb) { + u32 addr = drm_fb_dma_get_gem_addr(fb, pstate, 0); + + writel(addr, priv->regs + CLCD_UBAS); + } + + if (event) { + crtc->state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +static int pl111_display_enable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_device *drm = crtc->dev; + struct pl111_drm_dev_private *priv = drm->dev_private; + + writel(CLCD_IRQ_NEXTBASE_UPDATE, priv->regs + priv->ienb); + + return 0; +} + +static void pl111_display_disable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_device *drm = crtc->dev; + struct pl111_drm_dev_private *priv = drm->dev_private; + + writel(0, priv->regs + priv->ienb); +} + +static struct drm_simple_display_pipe_funcs pl111_display_funcs = { + .mode_valid = pl111_mode_valid, + .check = pl111_display_check, + .enable = pl111_display_enable, + .disable = pl111_display_disable, + .update = pl111_display_update, +}; + +static int pl111_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 << (TIM2_PCD_LO_BITS + TIM2_PCD_HI_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 pl111_clk_div_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + int div = pl111_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 pl111_clk_div_recalc_rate(struct clk_hw *hw, + unsigned long prate) +{ + struct pl111_drm_dev_private *priv = + container_of(hw, struct pl111_drm_dev_private, clk_div); + u32 tim2 = readl(priv->regs + CLCD_TIM2); + int div; + + if (tim2 & TIM2_BCD) + return prate; + + div = tim2 & TIM2_PCD_LO_MASK; + div |= (tim2 & TIM2_PCD_HI_MASK) >> + (TIM2_PCD_HI_SHIFT - TIM2_PCD_LO_BITS); + div += 2; + + return DIV_ROUND_UP_ULL(prate, div); +} + +static int pl111_clk_div_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long prate) +{ + struct pl111_drm_dev_private *priv = + container_of(hw, struct pl111_drm_dev_private, clk_div); + int div = pl111_clk_div_choose_div(hw, rate, &prate, false); + u32 tim2; + + spin_lock(&priv->tim2_lock); + tim2 = readl(priv->regs + CLCD_TIM2); + tim2 &= ~(TIM2_BCD | TIM2_PCD_LO_MASK | TIM2_PCD_HI_MASK); + + if (div == 1) { + tim2 |= TIM2_BCD; + } else { + div -= 2; + tim2 |= div & TIM2_PCD_LO_MASK; + tim2 |= (div >> TIM2_PCD_LO_BITS) << TIM2_PCD_HI_SHIFT; + } + + writel(tim2, priv->regs + CLCD_TIM2); + spin_unlock(&priv->tim2_lock); + + return 0; +} + +static const struct clk_ops pl111_clk_div_ops = { + .recalc_rate = pl111_clk_div_recalc_rate, + .determine_rate = pl111_clk_div_determine_rate, + .set_rate = pl111_clk_div_set_rate, +}; + +static int +pl111_init_clock_divider(struct drm_device *drm) +{ + struct pl111_drm_dev_private *priv = drm->dev_private; + struct clk *parent = devm_clk_get(drm->dev, "clcdclk"); + struct clk_hw *div = &priv->clk_div; + const char *parent_name; + struct clk_init_data init = { + .name = "pl111_div", + .ops = &pl111_clk_div_ops, + .parent_names = &parent_name, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }; + int ret; + + if (IS_ERR(parent)) { + drm_err(drm, "CLCD: unable to get clcdclk.\n"); + return PTR_ERR(parent); + } + + spin_lock_init(&priv->tim2_lock); + + /* If the clock divider is broken, use the parent directly */ + if (priv->variant->broken_clockdivider) { + priv->clk = parent; + return 0; + } + parent_name = __clk_get_name(parent); + div->init = &init; + + ret = devm_clk_hw_register(drm->dev, div); + + priv->clk = div->clk; + return ret; +} + +int pl111_display_init(struct drm_device *drm) +{ + struct pl111_drm_dev_private *priv = drm->dev_private; + int ret; + + ret = pl111_init_clock_divider(drm); + if (ret) + return ret; + + if (!priv->variant->broken_vblank) { + pl111_display_funcs.enable_vblank = pl111_display_enable_vblank; + pl111_display_funcs.disable_vblank = pl111_display_disable_vblank; + } + + ret = drm_simple_display_pipe_init(drm, &priv->pipe, + &pl111_display_funcs, + priv->variant->formats, + priv->variant->nformats, + NULL, + priv->connector); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/gpu/drm/pl111/pl111_drm.h b/drivers/gpu/drm/pl111/pl111_drm.h new file mode 100644 index 000000000..d1fe75644 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm.h @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved. + * + * Parts of this file were based on sources as follows: + * + * Copyright (c) 2006-2008 Intel Corporation + * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> + * Copyright (C) 2011 Texas Instruments + */ + +#ifndef _PL111_DRM_H_ +#define _PL111_DRM_H_ + +#include <linux/clk-provider.h> +#include <linux/interrupt.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> +#include <drm/drm_gem.h> +#include <drm/drm_panel.h> +#include <drm/drm_simple_kms_helper.h> + +/* + * CLCD Controller Internal Register addresses + */ +#define CLCD_TIM0 0x00000000 +#define CLCD_TIM1 0x00000004 +#define CLCD_TIM2 0x00000008 +#define CLCD_TIM3 0x0000000c +#define CLCD_UBAS 0x00000010 +#define CLCD_LBAS 0x00000014 + +#define CLCD_PL110_IENB 0x00000018 +#define CLCD_PL110_CNTL 0x0000001c +#define CLCD_PL110_STAT 0x00000020 +#define CLCD_PL110_INTR 0x00000024 +#define CLCD_PL110_UCUR 0x00000028 +#define CLCD_PL110_LCUR 0x0000002C + +#define CLCD_PL111_CNTL 0x00000018 +#define CLCD_PL111_IENB 0x0000001c +#define CLCD_PL111_RIS 0x00000020 +#define CLCD_PL111_MIS 0x00000024 +#define CLCD_PL111_ICR 0x00000028 +#define CLCD_PL111_UCUR 0x0000002c +#define CLCD_PL111_LCUR 0x00000030 + +#define CLCD_PALL 0x00000200 +#define CLCD_PALETTE 0x00000200 + +#define TIM2_PCD_LO_MASK GENMASK(4, 0) +#define TIM2_PCD_LO_BITS 5 +#define TIM2_CLKSEL (1 << 5) +#define TIM2_ACB_MASK GENMASK(10, 6) +#define TIM2_IVS (1 << 11) +#define TIM2_IHS (1 << 12) +#define TIM2_IPC (1 << 13) +#define TIM2_IOE (1 << 14) +#define TIM2_BCD (1 << 26) +#define TIM2_PCD_HI_MASK GENMASK(31, 27) +#define TIM2_PCD_HI_BITS 5 +#define TIM2_PCD_HI_SHIFT 27 + +#define CNTL_LCDEN (1 << 0) +#define CNTL_LCDBPP1 (0 << 1) +#define CNTL_LCDBPP2 (1 << 1) +#define CNTL_LCDBPP4 (2 << 1) +#define CNTL_LCDBPP8 (3 << 1) +#define CNTL_LCDBPP16 (4 << 1) +#define CNTL_LCDBPP16_565 (6 << 1) +#define CNTL_LCDBPP16_444 (7 << 1) +#define CNTL_LCDBPP24 (5 << 1) +#define CNTL_LCDBW (1 << 4) +#define CNTL_LCDTFT (1 << 5) +#define CNTL_LCDMONO8 (1 << 6) +#define CNTL_LCDDUAL (1 << 7) +#define CNTL_BGR (1 << 8) +#define CNTL_BEBO (1 << 9) +#define CNTL_BEPO (1 << 10) +#define CNTL_LCDPWR (1 << 11) +#define CNTL_LCDVCOMP(x) ((x) << 12) +#define CNTL_LDMAFIFOTIME (1 << 15) +#define CNTL_WATERMARK (1 << 16) + +/* ST Microelectronics variant bits */ +#define CNTL_ST_1XBPP_444 0x0 +#define CNTL_ST_1XBPP_5551 (1 << 17) +#define CNTL_ST_1XBPP_565 (1 << 18) +#define CNTL_ST_CDWID_12 0x0 +#define CNTL_ST_CDWID_16 (1 << 19) +#define CNTL_ST_CDWID_18 (1 << 20) +#define CNTL_ST_CDWID_24 ((1 << 19) | (1 << 20)) +#define CNTL_ST_CEAEN (1 << 21) +#define CNTL_ST_LCDBPP24_PACKED (6 << 1) + +#define CLCD_IRQ_NEXTBASE_UPDATE BIT(2) + +struct drm_minor; + +/** + * struct pl111_variant_data - encodes IP differences + * @name: the name of this variant + * @is_pl110: this is the early PL110 variant + * @is_lcdc: this is the ST Microelectronics Nomadik LCDC variant + * @external_bgr: this is the Versatile Pl110 variant with external + * BGR/RGB routing + * @broken_clockdivider: the clock divider is broken and we need to + * use the supplied clock directly + * @broken_vblank: the vblank IRQ is broken on this variant + * @st_bitmux_control: this variant is using the ST Micro bitmux + * extensions to the control register + * @formats: array of supported pixel formats on this variant + * @nformats: the length of the array of supported pixel formats + * @fb_depth: desired depth per pixel on the default framebuffer + */ +struct pl111_variant_data { + const char *name; + bool is_pl110; + bool is_lcdc; + bool external_bgr; + bool broken_clockdivider; + bool broken_vblank; + bool st_bitmux_control; + const u32 *formats; + unsigned int nformats; + unsigned int fb_depth; +}; + +struct pl111_drm_dev_private { + struct drm_device *drm; + + struct drm_connector *connector; + struct drm_panel *panel; + struct drm_bridge *bridge; + struct drm_simple_display_pipe pipe; + + void *regs; + u32 memory_bw; + u32 ienb; + u32 ctrl; + /* The pixel clock (a reference to our clock divider off of CLCDCLK). */ + struct clk *clk; + /* pl111's internal clock divider. */ + struct clk_hw clk_div; + /* Lock to sync access to CLCD_TIM2 between the common clock + * subsystem and pl111_display_enable(). + */ + spinlock_t tim2_lock; + const struct pl111_variant_data *variant; + void (*variant_display_enable) (struct drm_device *drm, u32 format); + void (*variant_display_disable) (struct drm_device *drm); + bool use_device_memory; +}; + +int pl111_display_init(struct drm_device *dev); +irqreturn_t pl111_irq(int irq, void *data); +void pl111_debugfs_init(struct drm_minor *minor); + +#endif /* _PL111_DRM_H_ */ diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c new file mode 100644 index 000000000..8ec659b3c --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drv.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved. + * + * Parts of this file were based on sources as follows: + * + * Copyright (c) 2006-2008 Intel Corporation + * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> + * Copyright (C) 2011 Texas Instruments + */ + +/** + * DOC: ARM PrimeCell PL110 and PL111 CLCD Driver + * + * The PL110/PL111 is a simple LCD controller that can support TFT + * and STN displays. This driver exposes a standard KMS interface + * for them. + * + * The driver currently doesn't expose the cursor. The DRM API for + * cursors requires support for 64x64 ARGB8888 cursor images, while + * the hardware can only support 64x64 monochrome with masking + * cursors. While one could imagine trying to hack something together + * to look at the ARGB8888 and program reasonable in monochrome, we + * just don't expose the cursor at all instead, and leave cursor + * support to the application software cursor layer. + * + * TODO: + * + * - Fix race between setting plane base address and getting IRQ for + * vsync firing the pageflip completion. + * + * - Read back hardware state at boot to skip reprogramming the + * hardware when doing a no-op modeset. + * + * - Use the CLKSEL bit to support switching between the two external + * clock parents. + */ + +#include <linux/amba/bus.h> +#include <linux/dma-buf.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_reserved_mem.h> +#include <linux/shmem_fs.h> +#include <linux/slab.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_fbdev_dma.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "pl111_drm.h" +#include "pl111_versatile.h" +#include "pl111_nomadik.h" + +#define DRIVER_DESC "DRM module for PL111" + +static const struct drm_mode_config_funcs mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static int pl111_modeset_init(struct drm_device *dev) +{ + struct drm_mode_config *mode_config; + struct pl111_drm_dev_private *priv = dev->dev_private; + struct device_node *np = dev->dev->of_node; + struct device_node *remote; + struct drm_panel *panel = NULL; + struct drm_bridge *bridge = NULL; + bool defer = false; + int ret; + int i; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + mode_config = &dev->mode_config; + mode_config->funcs = &mode_config_funcs; + mode_config->min_width = 1; + mode_config->max_width = 1024; + mode_config->min_height = 1; + mode_config->max_height = 768; + + i = 0; + for_each_endpoint_of_node(np, remote) { + struct drm_panel *tmp_panel; + struct drm_bridge *tmp_bridge; + + drm_dbg(dev, "checking endpoint %d\n", i); + + ret = drm_of_find_panel_or_bridge(dev->dev->of_node, + 0, i, + &tmp_panel, + &tmp_bridge); + if (ret) { + if (ret == -EPROBE_DEFER) { + /* + * Something deferred, but that is often just + * another way of saying -ENODEV, but let's + * cast a vote for later deferral. + */ + defer = true; + } else if (ret != -ENODEV) { + /* Continue, maybe something else is working */ + drm_err(dev, + "endpoint %d returns %d\n", i, ret); + } + } + + if (tmp_panel) { + drm_info(dev, + "found panel on endpoint %d\n", i); + panel = tmp_panel; + } + if (tmp_bridge) { + drm_info(dev, + "found bridge on endpoint %d\n", i); + bridge = tmp_bridge; + } + + i++; + } + + /* + * If we can't find neither panel nor bridge on any of the + * endpoints, and any of them retured -EPROBE_DEFER, then + * let's defer this driver too. + */ + if ((!panel && !bridge) && defer) + return -EPROBE_DEFER; + + if (panel) { + bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_Unknown); + drm_panel_put(panel); + if (IS_ERR(bridge)) { + ret = PTR_ERR(bridge); + goto finish; + } + } else if (bridge) { + drm_info(dev, "Using non-panel bridge\n"); + } else { + drm_err(dev, "No bridge, exiting\n"); + return -ENODEV; + } + + priv->bridge = bridge; + if (panel) { + priv->panel = panel; + priv->connector = drm_panel_bridge_connector(bridge); + } + + ret = pl111_display_init(dev); + if (ret != 0) { + drm_err(dev, "Failed to init display\n"); + goto out_bridge; + } + + ret = drm_simple_display_pipe_attach_bridge(&priv->pipe, + bridge); + if (ret) + return ret; + + if (!priv->variant->broken_vblank) { + ret = drm_vblank_init(dev, 1); + if (ret != 0) { + drm_err(dev, "Failed to init vblank\n"); + goto out_bridge; + } + } + + drm_mode_config_reset(dev); + + drm_kms_helper_poll_init(dev); + + goto finish; + +out_bridge: + if (panel) + drm_panel_bridge_remove(bridge); +finish: + return ret; +} + +static struct drm_gem_object * +pl111_gem_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sgt) +{ + struct pl111_drm_dev_private *priv = dev->dev_private; + + /* + * When using device-specific reserved memory we can't import + * DMA buffers: those are passed by reference in any global + * memory and we can only handle a specific range of memory. + */ + if (priv->use_device_memory) + return ERR_PTR(-EINVAL); + + return drm_gem_dma_prime_import_sg_table(dev, attach, sgt); +} + +DEFINE_DRM_GEM_DMA_FOPS(drm_fops); + +static const struct drm_driver pl111_drm_driver = { + .driver_features = + DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .ioctls = NULL, + .fops = &drm_fops, + .name = "pl111", + .desc = DRIVER_DESC, + .major = 1, + .minor = 0, + .patchlevel = 0, + .dumb_create = drm_gem_dma_dumb_create, + .gem_prime_import_sg_table = pl111_gem_import_sg_table, + DRM_FBDEV_DMA_DRIVER_OPS, + +#if defined(CONFIG_DEBUG_FS) + .debugfs_init = pl111_debugfs_init, +#endif +}; + +static int pl111_amba_probe(struct amba_device *amba_dev, + const struct amba_id *id) +{ + struct device *dev = &amba_dev->dev; + struct pl111_drm_dev_private *priv; + const struct pl111_variant_data *variant = id->data; + struct drm_device *drm; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + drm = drm_dev_alloc(&pl111_drm_driver, dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + amba_set_drvdata(amba_dev, drm); + priv->drm = drm; + drm->dev_private = priv; + priv->variant = variant; + + ret = of_reserved_mem_device_init(dev); + if (!ret) { + drm_info(drm, "using device-specific reserved memory\n"); + priv->use_device_memory = true; + } + + if (of_property_read_u32(dev->of_node, "max-memory-bandwidth", + &priv->memory_bw)) { + drm_info(drm, "no max memory bandwidth specified, assume unlimited\n"); + priv->memory_bw = 0; + } + + /* The two main variants swap this register */ + if (variant->is_pl110 || variant->is_lcdc) { + priv->ienb = CLCD_PL110_IENB; + priv->ctrl = CLCD_PL110_CNTL; + } else { + priv->ienb = CLCD_PL111_IENB; + priv->ctrl = CLCD_PL111_CNTL; + } + + priv->regs = devm_ioremap_resource(dev, &amba_dev->res); + if (IS_ERR(priv->regs)) { + drm_err(drm, "%s failed mmio\n", __func__); + ret = PTR_ERR(priv->regs); + goto dev_put; + } + + /* This may override some variant settings */ + ret = pl111_versatile_init(drm, priv); + if (ret) + goto dev_put; + + pl111_nomadik_init(drm); + + /* turn off interrupts before requesting the irq */ + writel(0, priv->regs + priv->ienb); + + ret = devm_request_irq(dev, amba_dev->irq[0], pl111_irq, 0, + variant->name, priv); + if (ret != 0) { + drm_err(drm, "%s failed irq %d\n", __func__, ret); + goto dev_put; + } + + ret = pl111_modeset_init(drm); + if (ret != 0) + goto dev_put; + + ret = drm_dev_register(drm, 0); + if (ret < 0) + goto dev_put; + + drm_client_setup_with_color_mode(drm, priv->variant->fb_depth); + + return 0; + +dev_put: + drm_dev_put(drm); + of_reserved_mem_device_release(dev); + + return ret; +} + +static void pl111_amba_remove(struct amba_device *amba_dev) +{ + struct device *dev = &amba_dev->dev; + struct drm_device *drm = amba_get_drvdata(amba_dev); + struct pl111_drm_dev_private *priv = drm->dev_private; + + drm_dev_unregister(drm); + drm_atomic_helper_shutdown(drm); + if (priv->panel) + drm_panel_bridge_remove(priv->bridge); + drm_dev_put(drm); + of_reserved_mem_device_release(dev); +} + +static void pl111_amba_shutdown(struct amba_device *amba_dev) +{ + drm_atomic_helper_shutdown(amba_get_drvdata(amba_dev)); +} + +/* + * This early variant lacks the 565 and 444 pixel formats. + */ +static const u32 pl110_pixel_formats[] = { + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, +}; + +static const struct pl111_variant_data pl110_variant = { + .name = "PL110", + .is_pl110 = true, + .formats = pl110_pixel_formats, + .nformats = ARRAY_SIZE(pl110_pixel_formats), + .fb_depth = 16, +}; + +/* RealView, Versatile Express etc use this modern variant */ +static const u32 pl111_pixel_formats[] = { + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_BGR565, + DRM_FORMAT_RGB565, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, +}; + +static const struct pl111_variant_data pl111_variant = { + .name = "PL111", + .formats = pl111_pixel_formats, + .nformats = ARRAY_SIZE(pl111_pixel_formats), + .fb_depth = 32, +}; + +static const u32 pl110_nomadik_pixel_formats[] = { + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_BGR565, + DRM_FORMAT_RGB565, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, +}; + +static const struct pl111_variant_data pl110_nomadik_variant = { + .name = "LCDC (PL110 Nomadik)", + .formats = pl110_nomadik_pixel_formats, + .nformats = ARRAY_SIZE(pl110_nomadik_pixel_formats), + .is_lcdc = true, + .st_bitmux_control = true, + .broken_vblank = true, + .fb_depth = 16, +}; + +static const struct amba_id pl111_id_table[] = { + { + .id = 0x00041110, + .mask = 0x000fffff, + .data = (void *)&pl110_variant, + }, + { + .id = 0x00180110, + .mask = 0x00fffffe, + .data = (void *)&pl110_nomadik_variant, + }, + { + .id = 0x00041111, + .mask = 0x000fffff, + .data = (void *)&pl111_variant, + }, + {0, 0}, +}; +MODULE_DEVICE_TABLE(amba, pl111_id_table); + +static struct amba_driver pl111_amba_driver __maybe_unused = { + .drv = { + .name = "drm-clcd-pl111", + }, + .probe = pl111_amba_probe, + .remove = pl111_amba_remove, + .shutdown = pl111_amba_shutdown, + .id_table = pl111_id_table, +}; + +#ifdef CONFIG_ARM_AMBA +module_amba_driver(pl111_amba_driver); +#endif + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("ARM Ltd."); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/pl111/pl111_nomadik.c b/drivers/gpu/drm/pl111/pl111_nomadik.c new file mode 100644 index 000000000..0e5153deb --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_nomadik.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include <linux/device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/bitops.h> +#include <linux/module.h> +#include <drm/drm_print.h> +#include "pl111_nomadik.h" + +#define PMU_CTRL_OFFSET 0x0000 +#define PMU_CTRL_LCDNDIF BIT(26) + +void pl111_nomadik_init(struct drm_device *dev) +{ + struct regmap *pmu_regmap; + + /* + * Just bail out of this is not found, we could be running + * multiplatform on something else than Nomadik. + */ + pmu_regmap = + syscon_regmap_lookup_by_compatible("stericsson,nomadik-pmu"); + if (IS_ERR(pmu_regmap)) + return; + + /* + * This bit in the PMU controller multiplexes the two graphics + * blocks found in the Nomadik STn8815. The other one is called + * MDIF (Master Display Interface) and gets muxed out here. + */ + regmap_update_bits(pmu_regmap, + PMU_CTRL_OFFSET, + PMU_CTRL_LCDNDIF, + 0); + drm_info(dev, "set Nomadik PMU mux to CLCD mode\n"); +} +EXPORT_SYMBOL_GPL(pl111_nomadik_init); diff --git a/drivers/gpu/drm/pl111/pl111_nomadik.h b/drivers/gpu/drm/pl111/pl111_nomadik.h new file mode 100644 index 000000000..b2c9f7cc1 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_nomadik.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#ifndef PL111_NOMADIK_H +#define PL111_NOMADIK_H +#endif + +struct device; + +#ifdef CONFIG_ARCH_NOMADIK + +void pl111_nomadik_init(struct drm_device *dev); + +#else + +static inline void pl111_nomadik_init(struct drm_device *dev) +{ +} + +#endif diff --git a/drivers/gpu/drm/pl111/pl111_versatile.c b/drivers/gpu/drm/pl111/pl111_versatile.c new file mode 100644 index 000000000..0d8331a39 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_versatile.c @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Versatile family (ARM reference designs) handling for the PL11x. + * This is based on code and know-how in the previous frame buffer + * driver in drivers/video/fbdev/amba-clcd.c: + * Copyright (C) 2001 ARM Limited, by David A Rusling + * Updated to 2.5 by Deep Blue Solutions Ltd. + * Major contributions and discoveries by Russell King. + */ + +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/vexpress.h> + +#include <drm/drm_fourcc.h> +#include <drm/drm_print.h> + +#include "pl111_versatile.h" +#include "pl111_drm.h" + +static struct regmap *versatile_syscon_map; + +/* + * We detect the different syscon types from the compatible strings. + */ +enum versatile_clcd { + INTEGRATOR_IMPD1, + INTEGRATOR_CLCD_CM, + VERSATILE_CLCD, + REALVIEW_CLCD_EB, + REALVIEW_CLCD_PB1176, + REALVIEW_CLCD_PB11MP, + REALVIEW_CLCD_PBA8, + REALVIEW_CLCD_PBX, + VEXPRESS_CLCD_V2M, +}; + +static const struct of_device_id versatile_clcd_of_match[] = { + { + .compatible = "arm,core-module-integrator", + .data = (void *)INTEGRATOR_CLCD_CM, + }, + { + .compatible = "arm,versatile-sysreg", + .data = (void *)VERSATILE_CLCD, + }, + { + .compatible = "arm,realview-eb-syscon", + .data = (void *)REALVIEW_CLCD_EB, + }, + { + .compatible = "arm,realview-pb1176-syscon", + .data = (void *)REALVIEW_CLCD_PB1176, + }, + { + .compatible = "arm,realview-pb11mp-syscon", + .data = (void *)REALVIEW_CLCD_PB11MP, + }, + { + .compatible = "arm,realview-pba8-syscon", + .data = (void *)REALVIEW_CLCD_PBA8, + }, + { + .compatible = "arm,realview-pbx-syscon", + .data = (void *)REALVIEW_CLCD_PBX, + }, + { + .compatible = "arm,vexpress-muxfpga", + .data = (void *)VEXPRESS_CLCD_V2M, + }, + {}, +}; + +static const struct of_device_id impd1_clcd_of_match[] = { + { + .compatible = "arm,im-pd1-syscon", + .data = (void *)INTEGRATOR_IMPD1, + }, + {}, +}; + +/* + * Core module CLCD control on the Integrator/CP, bits + * 8 thru 19 of the CM_CONTROL register controls a bunch + * of CLCD settings. + */ +#define INTEGRATOR_HDR_CTRL_OFFSET 0x0C +#define INTEGRATOR_CLCD_LCDBIASEN BIT(8) +#define INTEGRATOR_CLCD_LCDBIASUP BIT(9) +#define INTEGRATOR_CLCD_LCDBIASDN BIT(10) +/* Bits 11,12,13 controls the LCD or VGA bridge type */ +#define INTEGRATOR_CLCD_LCDMUX_LCD24 BIT(11) +#define INTEGRATOR_CLCD_LCDMUX_SHARP (BIT(11)|BIT(12)) +#define INTEGRATOR_CLCD_LCDMUX_VGA555 BIT(13) +#define INTEGRATOR_CLCD_LCDMUX_VGA24 (BIT(11)|BIT(12)|BIT(13)) +#define INTEGRATOR_CLCD_LCD0_EN BIT(14) +#define INTEGRATOR_CLCD_LCD1_EN BIT(15) +/* R/L flip on Sharp */ +#define INTEGRATOR_CLCD_LCD_STATIC1 BIT(16) +/* U/D flip on Sharp */ +#define INTEGRATOR_CLCD_LCD_STATIC2 BIT(17) +/* No connection on Sharp */ +#define INTEGRATOR_CLCD_LCD_STATIC BIT(18) +/* 0 = 24bit VGA, 1 = 18bit VGA */ +#define INTEGRATOR_CLCD_LCD_N24BITEN BIT(19) + +#define INTEGRATOR_CLCD_MASK GENMASK(19, 8) + +static void pl111_integrator_enable(struct drm_device *drm, u32 format) +{ + u32 val; + + drm_info(drm, "enable Integrator CLCD connectors\n"); + + /* FIXME: really needed? */ + val = INTEGRATOR_CLCD_LCD_STATIC1 | INTEGRATOR_CLCD_LCD_STATIC2 | + INTEGRATOR_CLCD_LCD0_EN | INTEGRATOR_CLCD_LCD1_EN; + + switch (format) { + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_XRGB8888: + /* 24bit formats */ + val |= INTEGRATOR_CLCD_LCDMUX_VGA24; + break; + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_XRGB1555: + /* Pseudocolor, RGB555, BGR555 */ + val |= INTEGRATOR_CLCD_LCDMUX_VGA555; + break; + default: + drm_err(drm, "unhandled format on Integrator 0x%08x\n", + format); + break; + } + + regmap_update_bits(versatile_syscon_map, + INTEGRATOR_HDR_CTRL_OFFSET, + INTEGRATOR_CLCD_MASK, + val); +} + +#define IMPD1_CTRL_OFFSET 0x18 +#define IMPD1_CTRL_DISP_LCD (0 << 0) +#define IMPD1_CTRL_DISP_VGA (1 << 0) +#define IMPD1_CTRL_DISP_LCD1 (2 << 0) +#define IMPD1_CTRL_DISP_ENABLE (1 << 2) +#define IMPD1_CTRL_DISP_MASK (7 << 0) + +static void pl111_impd1_enable(struct drm_device *drm, u32 format) +{ + u32 val; + + drm_info(drm, "enable IM-PD1 CLCD connectors\n"); + val = IMPD1_CTRL_DISP_VGA | IMPD1_CTRL_DISP_ENABLE; + + regmap_update_bits(versatile_syscon_map, + IMPD1_CTRL_OFFSET, + IMPD1_CTRL_DISP_MASK, + val); +} + +static void pl111_impd1_disable(struct drm_device *drm) +{ + drm_info(drm, "disable IM-PD1 CLCD connectors\n"); + + regmap_update_bits(versatile_syscon_map, + IMPD1_CTRL_OFFSET, + IMPD1_CTRL_DISP_MASK, + 0); +} + +/* + * This configuration register in the Versatile and RealView + * family is uniformly present but appears more and more + * unutilized starting with the RealView series. + */ +#define SYS_CLCD 0x50 +#define SYS_CLCD_MODE_MASK (BIT(0)|BIT(1)) +#define SYS_CLCD_MODE_888 0 +#define SYS_CLCD_MODE_5551 BIT(0) +#define SYS_CLCD_MODE_565_R_LSB BIT(1) +#define SYS_CLCD_MODE_565_B_LSB (BIT(0)|BIT(1)) +#define SYS_CLCD_CONNECTOR_MASK (BIT(2)|BIT(3)|BIT(4)|BIT(5)) +#define SYS_CLCD_NLCDIOON BIT(2) +#define SYS_CLCD_VDDPOSSWITCH BIT(3) +#define SYS_CLCD_PWR3V5SWITCH BIT(4) +#define SYS_CLCD_VDDNEGSWITCH BIT(5) + +static void pl111_versatile_disable(struct drm_device *drm) +{ + drm_info(drm, "disable Versatile CLCD connectors\n"); + regmap_update_bits(versatile_syscon_map, + SYS_CLCD, + SYS_CLCD_CONNECTOR_MASK, + 0); +} + +static void pl111_versatile_enable(struct drm_device *drm, u32 format) +{ + u32 val = 0; + + drm_info(drm, "enable Versatile CLCD connectors\n"); + + switch (format) { + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + val |= SYS_CLCD_MODE_888; + break; + case DRM_FORMAT_BGR565: + val |= SYS_CLCD_MODE_565_R_LSB; + break; + case DRM_FORMAT_RGB565: + val |= SYS_CLCD_MODE_565_B_LSB; + break; + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + val |= SYS_CLCD_MODE_5551; + break; + default: + drm_err(drm, "unhandled format on Versatile 0x%08x\n", + format); + break; + } + + /* Set up the MUX */ + regmap_update_bits(versatile_syscon_map, + SYS_CLCD, + SYS_CLCD_MODE_MASK, + val); + + /* Then enable the display */ + regmap_update_bits(versatile_syscon_map, + SYS_CLCD, + SYS_CLCD_CONNECTOR_MASK, + SYS_CLCD_NLCDIOON | SYS_CLCD_PWR3V5SWITCH); +} + +static void pl111_realview_clcd_disable(struct drm_device *drm) +{ + drm_info(drm, "disable RealView CLCD connectors\n"); + regmap_update_bits(versatile_syscon_map, + SYS_CLCD, + SYS_CLCD_CONNECTOR_MASK, + 0); +} + +static void pl111_realview_clcd_enable(struct drm_device *drm, u32 format) +{ + drm_info(drm, "enable RealView CLCD connectors\n"); + regmap_update_bits(versatile_syscon_map, + SYS_CLCD, + SYS_CLCD_CONNECTOR_MASK, + SYS_CLCD_NLCDIOON | SYS_CLCD_PWR3V5SWITCH); +} + +/* PL110 pixel formats for Integrator, vanilla PL110 */ +static const u32 pl110_integrator_pixel_formats[] = { + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, +}; + +/* Extended PL110 pixel formats for Integrator and Versatile */ +static const u32 pl110_versatile_pixel_formats[] = { + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_BGR565, /* Uses external PLD */ + DRM_FORMAT_RGB565, /* Uses external PLD */ + DRM_FORMAT_ABGR1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, +}; + +static const u32 pl111_realview_pixel_formats[] = { + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_BGR565, + DRM_FORMAT_RGB565, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, +}; + +/* + * The Integrator variant is a PL110 with a bunch of broken, or not + * yet implemented features + */ +static const struct pl111_variant_data pl110_integrator = { + .name = "PL110 Integrator", + .is_pl110 = true, + .broken_clockdivider = true, + .broken_vblank = true, + .formats = pl110_integrator_pixel_formats, + .nformats = ARRAY_SIZE(pl110_integrator_pixel_formats), + .fb_depth = 16, +}; + +/* + * The IM-PD1 variant is a PL110 with a bunch of broken, or not + * yet implemented features + */ +static const struct pl111_variant_data pl110_impd1 = { + .name = "PL110 IM-PD1", + .is_pl110 = true, + .broken_clockdivider = true, + .broken_vblank = true, + .formats = pl110_integrator_pixel_formats, + .nformats = ARRAY_SIZE(pl110_integrator_pixel_formats), + .fb_depth = 15, +}; + +/* + * This is the in-between PL110 variant found in the ARM Versatile, + * supporting RGB565/BGR565 + */ +static const struct pl111_variant_data pl110_versatile = { + .name = "PL110 Versatile", + .is_pl110 = true, + .external_bgr = true, + .formats = pl110_versatile_pixel_formats, + .nformats = ARRAY_SIZE(pl110_versatile_pixel_formats), + .fb_depth = 16, +}; + +/* + * RealView PL111 variant, the only real difference from the vanilla + * PL111 is that we select 16bpp framebuffer by default to be able + * to get 1024x768 without saturating the memory bus. + */ +static const struct pl111_variant_data pl111_realview = { + .name = "PL111 RealView", + .formats = pl111_realview_pixel_formats, + .nformats = ARRAY_SIZE(pl111_realview_pixel_formats), + .fb_depth = 16, +}; + +/* + * Versatile Express PL111 variant, again we just push the maximum + * BPP to 16 to be able to get 1024x768 without saturating the memory + * bus. The clockdivider also seems broken on the Versatile Express. + */ +static const struct pl111_variant_data pl111_vexpress = { + .name = "PL111 Versatile Express", + .formats = pl111_realview_pixel_formats, + .nformats = ARRAY_SIZE(pl111_realview_pixel_formats), + .fb_depth = 16, + .broken_clockdivider = true, +}; + +#define VEXPRESS_FPGAMUX_MOTHERBOARD 0x00 +#define VEXPRESS_FPGAMUX_DAUGHTERBOARD_1 0x01 +#define VEXPRESS_FPGAMUX_DAUGHTERBOARD_2 0x02 + +static int pl111_vexpress_clcd_init(struct drm_device *dev, struct device_node *np, + struct pl111_drm_dev_private *priv) +{ + struct platform_device *pdev; + struct device_node *root; + struct device_node *child; + struct device_node *ct_clcd = NULL; + struct regmap *map; + bool has_coretile_clcd = false; + bool has_coretile_hdlcd = false; + bool mux_motherboard = true; + u32 val; + int ret; + + if (!IS_ENABLED(CONFIG_VEXPRESS_CONFIG)) + return -ENODEV; + + /* + * Check if we have a CLCD or HDLCD on the core tile by checking if a + * CLCD or HDLCD is available in the root of the device tree. + */ + root = of_find_node_by_path("/"); + if (!root) + return -EINVAL; + + for_each_available_child_of_node(root, child) { + if (of_device_is_compatible(child, "arm,pl111")) { + has_coretile_clcd = true; + ct_clcd = child; + of_node_put(child); + break; + } + if (of_device_is_compatible(child, "arm,hdlcd")) { + has_coretile_hdlcd = true; + of_node_put(child); + break; + } + } + + of_node_put(root); + + /* + * If there is a coretile HDLCD and it has a driver, + * do not mux the CLCD on the motherboard to the DVI. + */ + if (has_coretile_hdlcd && IS_ENABLED(CONFIG_DRM_HDLCD)) + mux_motherboard = false; + + /* + * On the Vexpress CA9 we let the CLCD on the coretile + * take precedence, so also in this case do not mux the + * motherboard to the DVI. + */ + if (has_coretile_clcd) + mux_motherboard = false; + + if (mux_motherboard) { + drm_info(dev, "DVI muxed to motherboard CLCD\n"); + val = VEXPRESS_FPGAMUX_MOTHERBOARD; + } else if (ct_clcd == dev->dev->of_node) { + drm_info(dev, + "DVI muxed to daughterboard 1 (core tile) CLCD\n"); + val = VEXPRESS_FPGAMUX_DAUGHTERBOARD_1; + } else { + drm_info(dev, "core tile graphics present\n"); + drm_info(dev, "this device will be deactivated\n"); + return -ENODEV; + } + + /* Call into deep Vexpress configuration API */ + pdev = of_find_device_by_node(np); + if (!pdev) { + drm_err(dev, "can't find the sysreg device, deferring\n"); + return -EPROBE_DEFER; + } + + map = devm_regmap_init_vexpress_config(&pdev->dev); + if (IS_ERR(map)) { + platform_device_put(pdev); + return PTR_ERR(map); + } + + ret = regmap_write(map, 0, val); + platform_device_put(pdev); + if (ret) { + drm_err(dev, "error setting DVI muxmode\n"); + return -ENODEV; + } + + priv->variant = &pl111_vexpress; + drm_info(dev, "initializing Versatile Express PL111\n"); + + return 0; +} + +int pl111_versatile_init(struct drm_device *dev, struct pl111_drm_dev_private *priv) +{ + const struct of_device_id *clcd_id; + enum versatile_clcd versatile_clcd_type; + struct device_node *np; + struct regmap *map; + + np = of_find_matching_node_and_match(NULL, versatile_clcd_of_match, + &clcd_id); + if (!np) { + /* Non-ARM reference designs, just bail out */ + return 0; + } + + versatile_clcd_type = (enum versatile_clcd)clcd_id->data; + + /* Versatile Express special handling */ + if (versatile_clcd_type == VEXPRESS_CLCD_V2M) { + int ret = pl111_vexpress_clcd_init(dev, np, priv); + of_node_put(np); + if (ret) + drm_err(dev, "Versatile Express init failed - %d", ret); + return ret; + } + + /* + * On the Integrator, check if we should use the IM-PD1 instead, + * if we find it, it will take precedence. This is on the Integrator/AP + * which only has this option for PL110 graphics. + */ + if (versatile_clcd_type == INTEGRATOR_CLCD_CM) { + np = of_find_matching_node_and_match(NULL, impd1_clcd_of_match, + &clcd_id); + if (np) + versatile_clcd_type = (enum versatile_clcd)clcd_id->data; + } + + map = syscon_node_to_regmap(np); + of_node_put(np); + if (IS_ERR(map)) { + drm_err(dev, "no Versatile syscon regmap\n"); + return PTR_ERR(map); + } + + switch (versatile_clcd_type) { + case INTEGRATOR_CLCD_CM: + versatile_syscon_map = map; + priv->variant = &pl110_integrator; + priv->variant_display_enable = pl111_integrator_enable; + drm_info(dev, "set up callbacks for Integrator PL110\n"); + break; + case INTEGRATOR_IMPD1: + versatile_syscon_map = map; + priv->variant = &pl110_impd1; + priv->variant_display_enable = pl111_impd1_enable; + priv->variant_display_disable = pl111_impd1_disable; + drm_info(dev, "set up callbacks for IM-PD1 PL110\n"); + break; + case VERSATILE_CLCD: + versatile_syscon_map = map; + /* This can do RGB565 with external PLD */ + priv->variant = &pl110_versatile; + priv->variant_display_enable = pl111_versatile_enable; + priv->variant_display_disable = pl111_versatile_disable; + /* + * The Versatile has a variant halfway between PL110 + * and PL111 where these two registers have already been + * swapped. + */ + priv->ienb = CLCD_PL111_IENB; + priv->ctrl = CLCD_PL111_CNTL; + drm_info(dev, "set up callbacks for Versatile PL110\n"); + break; + case REALVIEW_CLCD_EB: + case REALVIEW_CLCD_PB1176: + case REALVIEW_CLCD_PB11MP: + case REALVIEW_CLCD_PBA8: + case REALVIEW_CLCD_PBX: + versatile_syscon_map = map; + priv->variant = &pl111_realview; + priv->variant_display_enable = pl111_realview_clcd_enable; + priv->variant_display_disable = pl111_realview_clcd_disable; + drm_info(dev, "set up callbacks for RealView PL111\n"); + break; + default: + drm_info(dev, "unknown Versatile system controller\n"); + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(pl111_versatile_init); diff --git a/drivers/gpu/drm/pl111/pl111_versatile.h b/drivers/gpu/drm/pl111/pl111_versatile.h new file mode 100644 index 000000000..7a15c5f7e --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_versatile.h @@ -0,0 +1,12 @@ +#include <linux/device.h> +#include "pl111_drm.h" + +#ifndef PL111_VERSATILE_H +#define PL111_VERSATILE_H + +struct device; +struct pl111_drm_dev_private; + +int pl111_versatile_init(struct drm_device *dev, struct pl111_drm_dev_private *priv); + +#endif |
