diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/gpu/drm/pl111 | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/pl111')
-rw-r--r-- | drivers/gpu/drm/pl111/Kconfig | 15 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/Makefile | 10 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_debugfs.c | 61 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_display.c | 592 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_drm.h | 94 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_drv.c | 471 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_nomadik.c | 36 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_nomadik.h | 18 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_versatile.c | 408 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_versatile.h | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_vexpress.c | 135 | ||||
-rw-r--r-- | drivers/gpu/drm/pl111/pl111_vexpress.h | 29 |
12 files changed, 1878 insertions, 0 deletions
diff --git a/drivers/gpu/drm/pl111/Kconfig b/drivers/gpu/drm/pl111/Kconfig new file mode 100644 index 000000000..e5e2abd66 --- /dev/null +++ b/drivers/gpu/drm/pl111/Kconfig @@ -0,0 +1,15 @@ +config DRM_PL111 + tristate "DRM Support for PL111 CLCD Controller" + depends on DRM + depends on ARM || ARM64 || COMPILE_TEST + depends on COMMON_CLK + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_GEM_CMA_HELPER + select DRM_BRIDGE + select DRM_PANEL_BRIDGE + select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE + 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..0c70f0e91 --- /dev/null +++ b/drivers/gpu/drm/pl111/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +pl111_drm-y += pl111_display.o \ + pl111_versatile.o \ + pl111_drv.o + +pl111_drm-$(CONFIG_ARCH_VEXPRESS) += pl111_vexpress.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..7ddc7e3b9 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_debugfs.c @@ -0,0 +1,61 @@ +/* + * Copyright © 2017 Broadcom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/amba/clcd-regs.h> +#include <linux/seq_file.h> +#include <drm/drm_debugfs.h> +#include <drm/drmP.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), +}; + +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}, +}; + +int +pl111_debugfs_init(struct drm_minor *minor) +{ + return 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..6d9f78612 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_display.c @@ -0,0 +1,592 @@ +/* + * (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 + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms of + * such GNU licence. + * + */ + +#include <linux/amba/clcd-regs.h> +#include <linux/clk.h> +#include <linux/version.h> +#include <linux/dma-buf.h> +#include <linux/of_graph.h> + +#include <drm/drmP.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_fb_cma_helper.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_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct drm_device *drm = crtc->dev; + struct pl111_drm_dev_private *priv = drm->dev_private; + u32 cpp = priv->variant->fb_bpp / 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_cma_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; + 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) { + dev_err(drm->dev, + "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_NEGEDGE) + tim2 |= TIM2_IPC; + } + + 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); + + /* Hard-code TFT panel */ + 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 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); +} + +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_cma_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, + .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, +}; + +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 long pl111_clk_div_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + int div = pl111_clk_div_choose_div(hw, rate, prate, true); + + return DIV_ROUND_UP_ULL(*prate, div); +} + +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, + .round_rate = pl111_clk_div_round_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)) { + dev_err(drm->dev, "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; + struct device *dev = drm->dev; + struct device_node *endpoint; + u32 tft_r0b0g0[3]; + int ret; + + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!endpoint) + return -ENODEV; + + if (of_property_read_u32_array(endpoint, + "arm,pl11x,tft-r0g0b0-pads", + tft_r0b0g0, + ARRAY_SIZE(tft_r0b0g0)) != 0) { + dev_err(dev, "arm,pl11x,tft-r0g0b0-pads should be 3 ints\n"); + of_node_put(endpoint); + return -ENOENT; + } + of_node_put(endpoint); + + 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..1aa015cca --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm.h @@ -0,0 +1,94 @@ +/* + * + * (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 + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms of + * such GNU licence. + * + */ + +#ifndef _PL111_DRM_H_ +#define _PL111_DRM_H_ + +#include <drm/drm_gem.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> +#include <drm/drm_panel.h> +#include <drm/drm_bridge.h> +#include <linux/clk-provider.h> +#include <linux/interrupt.h> + +#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_bpp: desired bits 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_bpp; +}; + +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); +int 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..47fe30223 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drv.c @@ -0,0 +1,471 @@ +/* + * (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 + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms of + * such GNU licence. + * + */ + +/** + * DOC: ARM PrimeCell PL111 CLCD Driver + * + * The PL111 is a simple LCD controller that can support TFT and STN + * displays. This driver exposes a standard KMS interface for them. + * + * This driver uses the same Device Tree binding as the fbdev CLCD + * driver. While the fbdev driver supports panels that may be + * connected to the CLCD internally to the CLCD driver, in DRM the + * panels get split out to drivers/gpu/drm/panels/. This means that, + * in converting from using fbdev to using DRM, you also need to write + * a panel driver (which may be as simple as an entry in + * panel-simple.c). + * + * 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 X11 software cursor layer. + * + * TODO: + * + * - Fix race between setting plane base address and getting IRQ for + * vsync firing the pageflip completion. + * + * - Use the "max-memory-bandwidth" DT property to filter the + * supported formats. + * + * - 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/amba/clcd-regs.h> +#include <linux/version.h> +#include <linux/shmem_fs.h> +#include <linux/dma-buf.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/of_reserved_mem.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_bridge.h> +#include <drm/drm_panel.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 = 0; + int i; + + drm_mode_config_init(dev); + 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; + + dev_dbg(dev->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 */ + dev_err(dev->dev, + "endpoint %d returns %d\n", i, ret); + } + } + + if (tmp_panel) { + dev_info(dev->dev, + "found panel on endpoint %d\n", i); + panel = tmp_panel; + } + if (tmp_bridge) { + dev_info(dev->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(panel, + DRM_MODE_CONNECTOR_Unknown); + if (IS_ERR(bridge)) { + ret = PTR_ERR(bridge); + goto out_config; + } + } else if (bridge) { + dev_info(dev->dev, "Using non-panel bridge\n"); + } else { + dev_err(dev->dev, "No bridge, exiting\n"); + return -ENODEV; + } + + priv->bridge = bridge; + if (panel) { + priv->panel = panel; + priv->connector = panel->connector; + } + + ret = pl111_display_init(dev); + if (ret != 0) { + dev_err(dev->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) { + dev_err(dev->dev, "Failed to init vblank\n"); + goto out_bridge; + } + } + + drm_mode_config_reset(dev); + + drm_fb_cma_fbdev_init(dev, priv->variant->fb_bpp, 0); + + drm_kms_helper_poll_init(dev); + + goto finish; + +out_bridge: + if (panel) + drm_panel_bridge_remove(bridge); +out_config: + drm_mode_config_cleanup(dev); +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_cma_prime_import_sg_table(dev, attach, sgt); +} + +DEFINE_DRM_GEM_CMA_FOPS(drm_fops); + +static struct drm_driver pl111_drm_driver = { + .driver_features = + DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | DRIVER_ATOMIC, + .lastclose = drm_fb_helper_lastclose, + .ioctls = NULL, + .fops = &drm_fops, + .name = "pl111", + .desc = DRIVER_DESC, + .date = "20170317", + .major = 1, + .minor = 0, + .patchlevel = 0, + .dumb_create = drm_gem_cma_dumb_create, + .gem_free_object_unlocked = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_import_sg_table = pl111_gem_import_sg_table, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_mmap = drm_gem_cma_prime_mmap, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + +#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) { + dev_info(dev, "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)) { + dev_info(dev, "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)) { + dev_err(dev, "%s failed mmio\n", __func__); + ret = PTR_ERR(priv->regs); + goto dev_put; + } + + /* This may override some variant settings */ + ret = pl111_versatile_init(dev, priv); + if (ret) + goto dev_put; + + pl111_nomadik_init(dev); + + /* 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) { + dev_err(dev, "%s failed irq %d\n", __func__, ret); + return ret; + } + + ret = pl111_modeset_init(drm); + if (ret != 0) + goto dev_put; + + ret = drm_dev_register(drm, 0); + if (ret < 0) + goto dev_put; + + return 0; + +dev_put: + drm_dev_put(drm); + of_reserved_mem_device_release(dev); + + return ret; +} + +static int 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_fb_cma_fbdev_fini(drm); + if (priv->panel) + drm_panel_bridge_remove(priv->bridge); + drm_mode_config_cleanup(drm); + drm_dev_put(drm); + of_reserved_mem_device_release(dev); + + return 0; +} + +/* + * 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_bpp = 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_bpp = 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_bpp = 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}, +}; + +static struct amba_driver pl111_amba_driver __maybe_unused = { + .drv = { + .name = "drm-clcd-pl111", + }, + .probe = pl111_amba_probe, + .remove = pl111_amba_remove, + .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..6f385e59b --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_nomadik.c @@ -0,0 +1,36 @@ +// 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 "pl111_nomadik.h" + +#define PMU_CTRL_OFFSET 0x0000 +#define PMU_CTRL_LCDNDIF BIT(26) + +void pl111_nomadik_init(struct 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); + dev_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..19d663d46 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_nomadik.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include <linux/device.h> + +#ifndef PL111_NOMADIK_H +#define PL111_NOMADIK_H +#endif + +#ifdef CONFIG_ARCH_NOMADIK + +void pl111_nomadik_init(struct device *dev); + +#else + +static inline void pl111_nomadik_init(struct 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..1c318ad32 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_versatile.c @@ -0,0 +1,408 @@ +#include <linux/amba/clcd-regs.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/bitops.h> +#include <linux/module.h> +#include <drm/drmP.h> +#include "pl111_versatile.h" +#include "pl111_vexpress.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_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, + }, + {}, +}; + +/* + * 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; + + dev_info(drm->dev, "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: + dev_err(drm->dev, "unhandled format on Integrator 0x%08x\n", + format); + break; + } + + regmap_update_bits(versatile_syscon_map, + INTEGRATOR_HDR_CTRL_OFFSET, + INTEGRATOR_CLCD_MASK, + val); +} + +/* + * 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) +{ + dev_info(drm->dev, "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; + + dev_info(drm->dev, "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: + dev_err(drm->dev, "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) +{ + dev_info(drm->dev, "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) +{ + dev_info(drm->dev, "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_bpp = 16, +}; + +/* + * 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_bpp = 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_bpp = 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_bpp = 16, + .broken_clockdivider = true, +}; + +int pl111_versatile_init(struct 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; + int ret; + + 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) { + struct platform_device *pdev; + + /* Registers a driver for the muxfpga */ + ret = vexpress_muxfpga_init(); + if (ret) { + dev_err(dev, "unable to initialize muxfpga driver\n"); + of_node_put(np); + return ret; + } + + /* Call into deep Vexpress configuration API */ + pdev = of_find_device_by_node(np); + if (!pdev) { + dev_err(dev, "can't find the sysreg device, deferring\n"); + of_node_put(np); + return -EPROBE_DEFER; + } + map = dev_get_drvdata(&pdev->dev); + if (!map) { + dev_err(dev, "sysreg has not yet probed\n"); + platform_device_put(pdev); + of_node_put(np); + return -EPROBE_DEFER; + } + } else { + map = syscon_node_to_regmap(np); + } + of_node_put(np); + + if (IS_ERR(map)) { + dev_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; + dev_info(dev, "set up callbacks for Integrator 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; + dev_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; + dev_info(dev, "set up callbacks for RealView PL111\n"); + break; + case VEXPRESS_CLCD_V2M: + priv->variant = &pl111_vexpress; + dev_info(dev, "initializing Versatile Express PL111\n"); + ret = pl111_vexpress_clcd_init(dev, priv, map); + if (ret) + return ret; + break; + default: + dev_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..41aa6d969 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_versatile.h @@ -0,0 +1,9 @@ +#include <linux/device.h> +#include "pl111_drm.h" + +#ifndef PL111_VERSATILE_H +#define PL111_VERSATILE_H + +int pl111_versatile_init(struct device *dev, struct pl111_drm_dev_private *priv); + +#endif diff --git a/drivers/gpu/drm/pl111/pl111_vexpress.c b/drivers/gpu/drm/pl111/pl111_vexpress.c new file mode 100644 index 000000000..5fa0441bb --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_vexpress.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Versatile Express PL111 handling + * Copyright (C) 2018 Linus Walleij + * + * This module binds to the "arm,vexpress-muxfpga" device on the + * Versatile Express configuration bus and sets up which CLCD instance + * gets muxed out on the DVI bridge. + */ +#include <linux/device.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/vexpress.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include "pl111_drm.h" +#include "pl111_vexpress.h" + +#define VEXPRESS_FPGAMUX_MOTHERBOARD 0x00 +#define VEXPRESS_FPGAMUX_DAUGHTERBOARD_1 0x01 +#define VEXPRESS_FPGAMUX_DAUGHTERBOARD_2 0x02 + +int pl111_vexpress_clcd_init(struct device *dev, + struct pl111_drm_dev_private *priv, + struct regmap *map) +{ + struct device_node *root; + struct device_node *child; + struct device_node *ct_clcd = NULL; + bool has_coretile_clcd = false; + bool has_coretile_hdlcd = false; + bool mux_motherboard = true; + u32 val; + int ret; + + /* + * 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; + break; + } + if (of_device_is_compatible(child, "arm,hdlcd")) { + has_coretile_hdlcd = true; + break; + } + } + + /* + * 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) { + dev_info(dev, "DVI muxed to motherboard CLCD\n"); + val = VEXPRESS_FPGAMUX_MOTHERBOARD; + } else if (ct_clcd == dev->of_node) { + dev_info(dev, + "DVI muxed to daughterboard 1 (core tile) CLCD\n"); + val = VEXPRESS_FPGAMUX_DAUGHTERBOARD_1; + } else { + dev_info(dev, "core tile graphics present\n"); + dev_info(dev, "this device will be deactivated\n"); + return -ENODEV; + } + + ret = regmap_write(map, 0, val); + if (ret) { + dev_err(dev, "error setting DVI muxmode\n"); + return -ENODEV; + } + + return 0; +} + +/* + * This sets up the regmap pointer that will then be retrieved by + * the detection code in pl111_versatile.c and passed in to the + * pl111_vexpress_clcd_init() function above. + */ +static int vexpress_muxfpga_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct regmap *map; + + map = devm_regmap_init_vexpress_config(&pdev->dev); + if (IS_ERR(map)) + return PTR_ERR(map); + dev_set_drvdata(dev, map); + + return 0; +} + +static const struct of_device_id vexpress_muxfpga_match[] = { + { .compatible = "arm,vexpress-muxfpga", }, + {} +}; + +static struct platform_driver vexpress_muxfpga_driver = { + .driver = { + .name = "vexpress-muxfpga", + .of_match_table = of_match_ptr(vexpress_muxfpga_match), + }, + .probe = vexpress_muxfpga_probe, +}; + +int vexpress_muxfpga_init(void) +{ + int ret; + + ret = platform_driver_register(&vexpress_muxfpga_driver); + /* -EBUSY just means this driver is already registered */ + if (ret == -EBUSY) + ret = 0; + return ret; +} diff --git a/drivers/gpu/drm/pl111/pl111_vexpress.h b/drivers/gpu/drm/pl111/pl111_vexpress.h new file mode 100644 index 000000000..5d3681bb4 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_vexpress.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 + +struct device; +struct pl111_drm_dev_private; +struct regmap; + +#ifdef CONFIG_ARCH_VEXPRESS + +int pl111_vexpress_clcd_init(struct device *dev, + struct pl111_drm_dev_private *priv, + struct regmap *map); + +int vexpress_muxfpga_init(void); + +#else + +static inline int pl111_vexpress_clcd_init(struct device *dev, + struct pl111_drm_dev_private *priv, + struct regmap *map) +{ + return -ENODEV; +} + +static inline int vexpress_muxfpga_init(void) +{ + return 0; +} + +#endif |