Commit 8e22d792 authored by Liviu Dudau's avatar Liviu Dudau

drm: Add support for ARM's HDLCD controller.

The HDLCD controller is a display controller that supports resolutions
up to 4096x4096 pixels. It is present on various development boards
produced by ARM Ltd and emulated by the latest Fast Models from the
company.

Cc: David Airlie <airlied@linux.ie>
Cc: Robin Murphy <robin.murphy@arm.com>
Signed-off-by: default avatarLiviu Dudau <Liviu.Dudau@arm.com>
[Kconfig cleanup and !CONFIG_PM fixes]
Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
Acked-by: default avatarDaniel Vetter <daniel.vetter@ffwll.ch>
parent 10c1b618
...@@ -106,6 +106,8 @@ config DRM_TDFX ...@@ -106,6 +106,8 @@ config DRM_TDFX
Choose this option if you have a 3dfx Banshee or Voodoo3 (or later), Choose this option if you have a 3dfx Banshee or Voodoo3 (or later),
graphics card. If M is selected, the module will be called tdfx. graphics card. If M is selected, the module will be called tdfx.
source "drivers/gpu/drm/arm/Kconfig"
config DRM_R128 config DRM_R128
tristate "ATI Rage 128" tristate "ATI Rage 128"
depends on DRM && PCI depends on DRM && PCI
......
...@@ -33,6 +33,7 @@ CFLAGS_drm_trace_points.o := -I$(src) ...@@ -33,6 +33,7 @@ CFLAGS_drm_trace_points.o := -I$(src)
obj-$(CONFIG_DRM) += drm.o obj-$(CONFIG_DRM) += drm.o
obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o
obj-$(CONFIG_DRM_ARM) += arm/
obj-$(CONFIG_DRM_TTM) += ttm/ obj-$(CONFIG_DRM_TTM) += ttm/
obj-$(CONFIG_DRM_TDFX) += tdfx/ obj-$(CONFIG_DRM_TDFX) += tdfx/
obj-$(CONFIG_DRM_R128) += r128/ obj-$(CONFIG_DRM_R128) += r128/
......
config DRM_ARM
bool
help
Choose this option to select drivers for ARM's devices
config DRM_HDLCD
tristate "ARM HDLCD"
depends on DRM && OF && (ARM || ARM64)
depends on COMMON_CLK
select DRM_ARM
select DRM_KMS_HELPER
select DRM_KMS_FB_HELPER
select DRM_KMS_CMA_HELPER
help
Choose this option if you have an ARM High Definition Colour LCD
controller.
If M is selected the module will be called hdlcd.
config DRM_HDLCD_SHOW_UNDERRUN
bool "Show underrun conditions"
depends on DRM_HDLCD
default n
help
Enable this option to show in red colour the pixels that the
HDLCD device did not fetch from framebuffer due to underrun
conditions.
hdlcd-y := hdlcd_drv.o hdlcd_crtc.o
obj-$(CONFIG_DRM_HDLCD) += hdlcd.o
/*
* Copyright (C) 2013-2015 ARM Limited
* Author: Liviu Dudau <Liviu.Dudau@arm.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*
* Implementation of a CRTC class for the HDLCD driver.
*/
#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_plane_helper.h>
#include <linux/clk.h>
#include <linux/of_graph.h>
#include <linux/platform_data/simplefb.h>
#include <video/videomode.h>
#include "hdlcd_drv.h"
#include "hdlcd_regs.h"
/*
* The HDLCD controller is a dumb RGB streamer that gets connected to
* a single HDMI transmitter or in the case of the ARM Models it gets
* emulated by the software that does the actual rendering.
*
*/
static const struct drm_crtc_funcs hdlcd_crtc_funcs = {
.destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.reset = drm_atomic_helper_crtc_reset,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
};
static struct simplefb_format supported_formats[] = SIMPLEFB_FORMATS;
/*
* Setup the HDLCD registers for decoding the pixels out of the framebuffer
*/
static int hdlcd_set_pxl_fmt(struct drm_crtc *crtc)
{
unsigned int btpp;
struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
uint32_t pixel_format;
struct simplefb_format *format = NULL;
int i;
pixel_format = crtc->primary->state->fb->pixel_format;
for (i = 0; i < ARRAY_SIZE(supported_formats); i++) {
if (supported_formats[i].fourcc == pixel_format)
format = &supported_formats[i];
}
if (WARN_ON(!format))
return 0;
/* HDLCD uses 'bytes per pixel', zero means 1 byte */
btpp = (format->bits_per_pixel + 7) / 8;
hdlcd_write(hdlcd, HDLCD_REG_PIXEL_FORMAT, (btpp - 1) << 3);
/*
* The format of the HDLCD_REG_<color>_SELECT register is:
* - bits[23:16] - default value for that color component
* - bits[11:8] - number of bits to extract for each color component
* - bits[4:0] - index of the lowest bit to extract
*
* The default color value is used when bits[11:8] are zero, when the
* pixel is outside the visible frame area or when there is a
* buffer underrun.
*/
hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->red.offset |
#ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN
0x00ff0000 | /* show underruns in red */
#endif
((format->red.length & 0xf) << 8));
hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset |
((format->green.length & 0xf) << 8));
hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->blue.offset |
((format->blue.length & 0xf) << 8));
return 0;
}
static void hdlcd_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
struct drm_display_mode *m = &crtc->state->adjusted_mode;
struct videomode vm;
unsigned int polarities, line_length, err;
vm.vfront_porch = m->crtc_vsync_start - m->crtc_vdisplay;
vm.vback_porch = m->crtc_vtotal - m->crtc_vsync_end;
vm.vsync_len = m->crtc_vsync_end - m->crtc_vsync_start;
vm.hfront_porch = m->crtc_hsync_start - m->crtc_hdisplay;
vm.hback_porch = m->crtc_htotal - m->crtc_hsync_end;
vm.hsync_len = m->crtc_hsync_end - m->crtc_hsync_start;
polarities = HDLCD_POLARITY_DATAEN | HDLCD_POLARITY_DATA;
if (m->flags & DRM_MODE_FLAG_PHSYNC)
polarities |= HDLCD_POLARITY_HSYNC;
if (m->flags & DRM_MODE_FLAG_PVSYNC)
polarities |= HDLCD_POLARITY_VSYNC;
line_length = crtc->primary->state->fb->pitches[0];
/* Allow max number of outstanding requests and largest burst size */
hdlcd_write(hdlcd, HDLCD_REG_BUS_OPTIONS,
HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16);
hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_LENGTH, line_length);
hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_PITCH, line_length);
hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_COUNT, m->crtc_vdisplay - 1);
hdlcd_write(hdlcd, HDLCD_REG_V_DATA, m->crtc_vdisplay - 1);
hdlcd_write(hdlcd, HDLCD_REG_V_BACK_PORCH, vm.vback_porch - 1);
hdlcd_write(hdlcd, HDLCD_REG_V_FRONT_PORCH, vm.vfront_porch - 1);
hdlcd_write(hdlcd, HDLCD_REG_V_SYNC, vm.vsync_len - 1);
hdlcd_write(hdlcd, HDLCD_REG_H_BACK_PORCH, vm.hback_porch - 1);
hdlcd_write(hdlcd, HDLCD_REG_H_FRONT_PORCH, vm.hfront_porch - 1);
hdlcd_write(hdlcd, HDLCD_REG_H_SYNC, vm.hsync_len - 1);
hdlcd_write(hdlcd, HDLCD_REG_H_DATA, m->crtc_hdisplay - 1);
hdlcd_write(hdlcd, HDLCD_REG_POLARITIES, polarities);
err = hdlcd_set_pxl_fmt(crtc);
if (err)
return;
clk_set_rate(hdlcd->clk, m->crtc_clock * 1000);
}
static void hdlcd_crtc_enable(struct drm_crtc *crtc)
{
struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
clk_prepare_enable(hdlcd->clk);
hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 1);
drm_crtc_vblank_on(crtc);
}
static void hdlcd_crtc_disable(struct drm_crtc *crtc)
{
struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
if (!crtc->primary->fb)
return;
clk_disable_unprepare(hdlcd->clk);
hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0);
drm_crtc_vblank_off(crtc);
}
static int hdlcd_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
struct drm_display_mode *mode = &state->adjusted_mode;
long rate, clk_rate = mode->clock * 1000;
rate = clk_round_rate(hdlcd->clk, clk_rate);
if (rate != clk_rate) {
/* clock required by mode not supported by hardware */
return -EINVAL;
}
return 0;
}
static void hdlcd_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc);
unsigned long flags;
if (crtc->state->event) {
struct drm_pending_vblank_event *event = crtc->state->event;
crtc->state->event = NULL;
event->pipe = drm_crtc_index(crtc);
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
spin_lock_irqsave(&crtc->dev->event_lock, flags);
list_add_tail(&event->base.link, &hdlcd->event_list);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
}
}
static void hdlcd_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
}
static bool hdlcd_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static const struct drm_crtc_helper_funcs hdlcd_crtc_helper_funcs = {
.mode_fixup = hdlcd_crtc_mode_fixup,
.mode_set = drm_helper_crtc_mode_set,
.mode_set_base = drm_helper_crtc_mode_set_base,
.mode_set_nofb = hdlcd_crtc_mode_set_nofb,
.enable = hdlcd_crtc_enable,
.disable = hdlcd_crtc_disable,
.prepare = hdlcd_crtc_disable,
.commit = hdlcd_crtc_enable,
.atomic_check = hdlcd_crtc_atomic_check,
.atomic_begin = hdlcd_crtc_atomic_begin,
.atomic_flush = hdlcd_crtc_atomic_flush,
};
static int hdlcd_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state)
{
return 0;
}
static void hdlcd_plane_atomic_update(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct hdlcd_drm_private *hdlcd;
struct drm_gem_cma_object *gem;
dma_addr_t scanout_start;
if (!plane->state->crtc || !plane->state->fb)
return;
hdlcd = crtc_to_hdlcd_priv(plane->state->crtc);
gem = drm_fb_cma_get_gem_obj(plane->state->fb, 0);
scanout_start = gem->paddr;
hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start);
}
static const struct drm_plane_helper_funcs hdlcd_plane_helper_funcs = {
.prepare_fb = NULL,
.cleanup_fb = NULL,
.atomic_check = hdlcd_plane_atomic_check,
.atomic_update = hdlcd_plane_atomic_update,
};
static void hdlcd_plane_destroy(struct drm_plane *plane)
{
drm_plane_helper_disable(plane);
drm_plane_cleanup(plane);
}
static const struct drm_plane_funcs hdlcd_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = hdlcd_plane_destroy,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
static struct drm_plane *hdlcd_plane_init(struct drm_device *drm)
{
struct hdlcd_drm_private *hdlcd = drm->dev_private;
struct drm_plane *plane = NULL;
u32 formats[ARRAY_SIZE(supported_formats)], i;
int ret;
plane = devm_kzalloc(drm->dev, sizeof(*plane), GFP_KERNEL);
if (!plane)
return ERR_PTR(-ENOMEM);
for (i = 0; i < ARRAY_SIZE(supported_formats); i++)
formats[i] = supported_formats[i].fourcc;
ret = drm_universal_plane_init(drm, plane, 0xff, &hdlcd_plane_funcs,
formats, ARRAY_SIZE(formats),
DRM_PLANE_TYPE_PRIMARY, NULL);
if (ret) {
devm_kfree(drm->dev, plane);
return ERR_PTR(ret);
}
drm_plane_helper_add(plane, &hdlcd_plane_helper_funcs);
hdlcd->plane = plane;
return plane;
}
void hdlcd_crtc_suspend(struct drm_crtc *crtc)
{
hdlcd_crtc_disable(crtc);
}
void hdlcd_crtc_resume(struct drm_crtc *crtc)
{
hdlcd_crtc_enable(crtc);
}
int hdlcd_setup_crtc(struct drm_device *drm)
{
struct hdlcd_drm_private *hdlcd = drm->dev_private;
struct drm_plane *primary;
int ret;
primary = hdlcd_plane_init(drm);
if (IS_ERR(primary))
return PTR_ERR(primary);
ret = drm_crtc_init_with_planes(drm, &hdlcd->crtc, primary, NULL,
&hdlcd_crtc_funcs, NULL);
if (ret) {
hdlcd_plane_destroy(primary);
devm_kfree(drm->dev, primary);
return ret;
}
drm_crtc_helper_add(&hdlcd->crtc, &hdlcd_crtc_helper_funcs);
return 0;
}
This diff is collapsed.
/*
* ARM HDLCD Controller register definition
*/
#ifndef __HDLCD_DRV_H__
#define __HDLCD_DRV_H__
struct hdlcd_drm_private {
void __iomem *mmio;
struct clk *clk;
struct drm_fbdev_cma *fbdev;
struct drm_framebuffer *fb;
struct list_head event_list;
struct drm_crtc crtc;
struct drm_plane *plane;
#ifdef CONFIG_DEBUG_FS
atomic_t buffer_underrun_count;
atomic_t bus_error_count;
atomic_t vsync_count;
atomic_t dma_end_count;
#endif
};
#define crtc_to_hdlcd_priv(x) container_of(x, struct hdlcd_drm_private, crtc)
static inline void hdlcd_write(struct hdlcd_drm_private *hdlcd,
unsigned int reg, u32 value)
{
writel(value, hdlcd->mmio + reg);
}
static inline u32 hdlcd_read(struct hdlcd_drm_private *hdlcd, unsigned int reg)
{
return readl(hdlcd->mmio + reg);
}
int hdlcd_setup_crtc(struct drm_device *dev);
void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd);
void hdlcd_crtc_suspend(struct drm_crtc *crtc);
void hdlcd_crtc_resume(struct drm_crtc *crtc);
#endif /* __HDLCD_DRV_H__ */
/*
* Copyright (C) 2013,2014 ARM Limited
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*
* ARM HDLCD Controller register definition
*/
#ifndef __HDLCD_REGS_H__
#define __HDLCD_REGS_H__
/* register offsets */
#define HDLCD_REG_VERSION 0x0000 /* ro */
#define HDLCD_REG_INT_RAWSTAT 0x0010 /* rw */
#define HDLCD_REG_INT_CLEAR 0x0014 /* wo */
#define HDLCD_REG_INT_MASK 0x0018 /* rw */
#define HDLCD_REG_INT_STATUS 0x001c /* ro */
#define HDLCD_REG_FB_BASE 0x0100 /* rw */
#define HDLCD_REG_FB_LINE_LENGTH 0x0104 /* rw */
#define HDLCD_REG_FB_LINE_COUNT 0x0108 /* rw */
#define HDLCD_REG_FB_LINE_PITCH 0x010c /* rw */
#define HDLCD_REG_BUS_OPTIONS 0x0110 /* rw */
#define HDLCD_REG_V_SYNC 0x0200 /* rw */
#define HDLCD_REG_V_BACK_PORCH 0x0204 /* rw */
#define HDLCD_REG_V_DATA 0x0208 /* rw */
#define HDLCD_REG_V_FRONT_PORCH 0x020c /* rw */
#define HDLCD_REG_H_SYNC 0x0210 /* rw */
#define HDLCD_REG_H_BACK_PORCH 0x0214 /* rw */
#define HDLCD_REG_H_DATA 0x0218 /* rw */
#define HDLCD_REG_H_FRONT_PORCH 0x021c /* rw */
#define HDLCD_REG_POLARITIES 0x0220 /* rw */
#define HDLCD_REG_COMMAND 0x0230 /* rw */
#define HDLCD_REG_PIXEL_FORMAT 0x0240 /* rw */
#define HDLCD_REG_RED_SELECT 0x0244 /* rw */
#define HDLCD_REG_GREEN_SELECT 0x0248 /* rw */
#define HDLCD_REG_BLUE_SELECT 0x024c /* rw */
/* version */
#define HDLCD_PRODUCT_ID 0x1CDC0000
#define HDLCD_PRODUCT_MASK 0xFFFF0000
#define HDLCD_VERSION_MAJOR_MASK 0x0000FF00
#define HDLCD_VERSION_MINOR_MASK 0x000000FF
/* interrupts */
#define HDLCD_INTERRUPT_DMA_END (1 << 0)
#define HDLCD_INTERRUPT_BUS_ERROR (1 << 1)
#define HDLCD_INTERRUPT_VSYNC (1 << 2)
#define HDLCD_INTERRUPT_UNDERRUN (1 << 3)
#define HDLCD_DEBUG_INT_MASK (HDLCD_INTERRUPT_DMA_END | \
HDLCD_INTERRUPT_BUS_ERROR | \
HDLCD_INTERRUPT_UNDERRUN)
/* polarities */
#define HDLCD_POLARITY_VSYNC (1 << 0)
#define HDLCD_POLARITY_HSYNC (1 << 1)
#define HDLCD_POLARITY_DATAEN (1 << 2)
#define HDLCD_POLARITY_DATA (1 << 3)
#define HDLCD_POLARITY_PIXELCLK (1 << 4)
/* commands */
#define HDLCD_COMMAND_DISABLE (0 << 0)
#define HDLCD_COMMAND_ENABLE (1 << 0)
/* pixel format */
#define HDLCD_PIXEL_FMT_LITTLE_ENDIAN (0 << 31)
#define HDLCD_PIXEL_FMT_BIG_ENDIAN (1 << 31)
#define HDLCD_BYTES_PER_PIXEL_MASK (3 << 3)
/* bus options */
#define HDLCD_BUS_BURST_MASK 0x01f
#define HDLCD_BUS_MAX_OUTSTAND 0xf00
#define HDLCD_BUS_BURST_NONE (0 << 0)
#define HDLCD_BUS_BURST_1 (1 << 0)
#define HDLCD_BUS_BURST_2 (1 << 1)
#define HDLCD_BUS_BURST_4 (1 << 2)
#define HDLCD_BUS_BURST_8 (1 << 3)
#define HDLCD_BUS_BURST_16 (1 << 4)
/* Max resolution supported is 4096x4096, 32bpp */
#define HDLCD_MAX_XRES 4096
#define HDLCD_MAX_YRES 4096
#define NR_PALETTE 256
#endif /* __HDLCD_REGS_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment