Commit 4bf8e196 authored by Laurent Pinchart's avatar Laurent Pinchart Committed by Dave Airlie

drm: Renesas R-Car Display Unit DRM driver

The R-Car Display Unit (DU) DRM driver supports both superposition
processors and all eight planes in RGB and YUV formats with alpha
blending.

Only VGA and LVDS encoders and connectors are currently supported.
Signed-off-by: default avatarLaurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent 0ab3691f
......@@ -213,6 +213,8 @@ source "drivers/gpu/drm/mgag200/Kconfig"
source "drivers/gpu/drm/cirrus/Kconfig"
source "drivers/gpu/drm/rcar-du/Kconfig"
source "drivers/gpu/drm/shmobile/Kconfig"
source "drivers/gpu/drm/omapdrm/Kconfig"
......
......@@ -49,6 +49,7 @@ obj-$(CONFIG_DRM_EXYNOS) +=exynos/
obj-$(CONFIG_DRM_GMA500) += gma500/
obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
obj-$(CONFIG_DRM_OMAP) += omapdrm/
obj-$(CONFIG_DRM_TILCDC) += tilcdc/
......
config DRM_RCAR_DU
tristate "DRM Support for R-Car Display Unit"
depends on DRM && ARM
select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER
select DRM_GEM_CMA_HELPER
help
Choose this option if you have an R-Car chipset.
If M is selected the module will be called rcar-du-drm.
rcar-du-drm-y := rcar_du_crtc.o \
rcar_du_drv.o \
rcar_du_kms.o \
rcar_du_lvds.o \
rcar_du_plane.o \
rcar_du_vga.o
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
/*
* rcar_du_crtc.c -- R-Car Display Unit CRTCs
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/clk.h>
#include <linux/mutex.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include "rcar_du_crtc.h"
#include "rcar_du_drv.h"
#include "rcar_du_kms.h"
#include "rcar_du_lvds.h"
#include "rcar_du_plane.h"
#include "rcar_du_regs.h"
#include "rcar_du_vga.h"
#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc)
static u32 rcar_du_crtc_read(struct rcar_du_crtc *rcrtc, u32 reg)
{
struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
return rcar_du_read(rcdu, rcrtc->mmio_offset + reg);
}
static void rcar_du_crtc_write(struct rcar_du_crtc *rcrtc, u32 reg, u32 data)
{
struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
rcar_du_write(rcdu, rcrtc->mmio_offset + reg, data);
}
static void rcar_du_crtc_clr(struct rcar_du_crtc *rcrtc, u32 reg, u32 clr)
{
struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
rcar_du_write(rcdu, rcrtc->mmio_offset + reg,
rcar_du_read(rcdu, rcrtc->mmio_offset + reg) & ~clr);
}
static void rcar_du_crtc_set(struct rcar_du_crtc *rcrtc, u32 reg, u32 set)
{
struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
rcar_du_write(rcdu, rcrtc->mmio_offset + reg,
rcar_du_read(rcdu, rcrtc->mmio_offset + reg) | set);
}
static void rcar_du_crtc_clr_set(struct rcar_du_crtc *rcrtc, u32 reg,
u32 clr, u32 set)
{
struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
u32 value = rcar_du_read(rcdu, rcrtc->mmio_offset + reg);
rcar_du_write(rcdu, rcrtc->mmio_offset + reg, (value & ~clr) | set);
}
static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
{
struct drm_crtc *crtc = &rcrtc->crtc;
struct rcar_du_device *rcdu = crtc->dev->dev_private;
const struct drm_display_mode *mode = &crtc->mode;
unsigned long clk;
u32 value;
u32 div;
/* Dot clock */
clk = clk_get_rate(rcdu->clock);
div = DIV_ROUND_CLOSEST(clk, mode->clock * 1000);
div = clamp(div, 1U, 64U) - 1;
rcar_du_write(rcdu, rcrtc->index ? ESCR2 : ESCR,
ESCR_DCLKSEL_CLKS | div);
rcar_du_write(rcdu, rcrtc->index ? OTAR2 : OTAR, 0);
/* Signal polarities */
value = ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : DSMR_VSL)
| ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : DSMR_HSL)
| DSMR_DIPM_DE;
rcar_du_crtc_write(rcrtc, DSMR, value);
/* Display timings */
rcar_du_crtc_write(rcrtc, HDSR, mode->htotal - mode->hsync_start - 19);
rcar_du_crtc_write(rcrtc, HDER, mode->htotal - mode->hsync_start +
mode->hdisplay - 19);
rcar_du_crtc_write(rcrtc, HSWR, mode->hsync_end -
mode->hsync_start - 1);
rcar_du_crtc_write(rcrtc, HCR, mode->htotal - 1);
rcar_du_crtc_write(rcrtc, VDSR, mode->vtotal - mode->vsync_end - 2);
rcar_du_crtc_write(rcrtc, VDER, mode->vtotal - mode->vsync_end +
mode->vdisplay - 2);
rcar_du_crtc_write(rcrtc, VSPR, mode->vtotal - mode->vsync_end +
mode->vsync_start - 1);
rcar_du_crtc_write(rcrtc, VCR, mode->vtotal - 1);
rcar_du_crtc_write(rcrtc, DESR, mode->htotal - mode->hsync_start);
rcar_du_crtc_write(rcrtc, DEWR, mode->hdisplay);
}
static void rcar_du_crtc_set_routing(struct rcar_du_crtc *rcrtc)
{
struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
u32 dorcr = rcar_du_read(rcdu, DORCR);
dorcr &= ~(DORCR_PG2T | DORCR_DK2S | DORCR_PG2D_MASK);
/* Set the DU1 pins sources. Select CRTC 0 if explicitly requested and
* CRTC 1 in all other cases to avoid cloning CRTC 0 to DU0 and DU1 by
* default.
*/
if (rcrtc->outputs & (1 << 1) && rcrtc->index == 0)
dorcr |= DORCR_PG2D_DS1;
else
dorcr |= DORCR_PG2T | DORCR_DK2S | DORCR_PG2D_DS2;
rcar_du_write(rcdu, DORCR, dorcr);
}
static void __rcar_du_start_stop(struct rcar_du_device *rcdu, bool start)
{
rcar_du_write(rcdu, DSYSR,
(rcar_du_read(rcdu, DSYSR) & ~(DSYSR_DRES | DSYSR_DEN)) |
(start ? DSYSR_DEN : DSYSR_DRES));
}
static void rcar_du_start_stop(struct rcar_du_device *rcdu, bool start)
{
/* Many of the configuration bits are only updated when the display
* reset (DRES) bit in DSYSR is set to 1, disabling *both* CRTCs. Some
* of those bits could be pre-configured, but others (especially the
* bits related to plane assignment to display timing controllers) need
* to be modified at runtime.
*
* Restart the display controller if a start is requested. Sorry for the
* flicker. It should be possible to move most of the "DRES-update" bits
* setup to driver initialization time and minimize the number of cases
* when the display controller will have to be restarted.
*/
if (start) {
if (rcdu->used_crtcs++ != 0)
__rcar_du_start_stop(rcdu, false);
__rcar_du_start_stop(rcdu, true);
} else {
if (--rcdu->used_crtcs == 0)
__rcar_du_start_stop(rcdu, false);
}
}
void rcar_du_crtc_route_output(struct drm_crtc *crtc, unsigned int output)
{
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
/* Store the route from the CRTC output to the DU output. The DU will be
* configured when starting the CRTC.
*/
rcrtc->outputs |= 1 << output;
}
void rcar_du_crtc_update_planes(struct drm_crtc *crtc)
{
struct rcar_du_device *rcdu = crtc->dev->dev_private;
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
struct rcar_du_plane *planes[RCAR_DU_NUM_HW_PLANES];
unsigned int num_planes = 0;
unsigned int prio = 0;
unsigned int i;
u32 dptsr = 0;
u32 dspr = 0;
for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) {
struct rcar_du_plane *plane = &rcdu->planes.planes[i];
unsigned int j;
if (plane->crtc != &rcrtc->crtc || !plane->enabled)
continue;
/* Insert the plane in the sorted planes array. */
for (j = num_planes++; j > 0; --j) {
if (planes[j-1]->zpos <= plane->zpos)
break;
planes[j] = planes[j-1];
}
planes[j] = plane;
prio += plane->format->planes * 4;
}
for (i = 0; i < num_planes; ++i) {
struct rcar_du_plane *plane = planes[i];
unsigned int index = plane->hwindex;
prio -= 4;
dspr |= (index + 1) << prio;
dptsr |= DPTSR_PnDK(index) | DPTSR_PnTS(index);
if (plane->format->planes == 2) {
index = (index + 1) % 8;
prio -= 4;
dspr |= (index + 1) << prio;
dptsr |= DPTSR_PnDK(index) | DPTSR_PnTS(index);
}
}
/* Select display timing and dot clock generator 2 for planes associated
* with superposition controller 2.
*/
if (rcrtc->index) {
u32 value = rcar_du_read(rcdu, DPTSR);
/* The DPTSR register is updated when the display controller is
* stopped. We thus need to restart the DU. Once again, sorry
* for the flicker. One way to mitigate the issue would be to
* pre-associate planes with CRTCs (either with a fixed 4/4
* split, or through a module parameter). Flicker would then
* occur only if we need to break the pre-association.
*/
if (value != dptsr) {
rcar_du_write(rcdu, DPTSR, dptsr);
if (rcdu->used_crtcs) {
__rcar_du_start_stop(rcdu, false);
__rcar_du_start_stop(rcdu, true);
}
}
}
rcar_du_write(rcdu, rcrtc->index ? DS2PR : DS1PR, dspr);
}
static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
{
struct drm_crtc *crtc = &rcrtc->crtc;
struct rcar_du_device *rcdu = crtc->dev->dev_private;
unsigned int i;
if (rcrtc->started)
return;
if (WARN_ON(rcrtc->plane->format == NULL))
return;
/* Set display off and background to black */
rcar_du_crtc_write(rcrtc, DOOR, DOOR_RGB(0, 0, 0));
rcar_du_crtc_write(rcrtc, BPOR, BPOR_RGB(0, 0, 0));
/* Configure display timings and output routing */
rcar_du_crtc_set_display_timing(rcrtc);
rcar_du_crtc_set_routing(rcrtc);
mutex_lock(&rcdu->planes.lock);
rcrtc->plane->enabled = true;
rcar_du_crtc_update_planes(crtc);
mutex_unlock(&rcdu->planes.lock);
/* Setup planes. */
for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) {
struct rcar_du_plane *plane = &rcdu->planes.planes[i];
if (plane->crtc != crtc || !plane->enabled)
continue;
rcar_du_plane_setup(plane);
}
/* Select master sync mode. This enables display operation in master
* sync mode (with the HSYNC and VSYNC signals configured as outputs and
* actively driven).
*/
rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_MASTER);
rcar_du_start_stop(rcdu, true);
rcrtc->started = true;
}
static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
{
struct drm_crtc *crtc = &rcrtc->crtc;
struct rcar_du_device *rcdu = crtc->dev->dev_private;
if (!rcrtc->started)
return;
mutex_lock(&rcdu->planes.lock);
rcrtc->plane->enabled = false;
rcar_du_crtc_update_planes(crtc);
mutex_unlock(&rcdu->planes.lock);
/* Select switch sync mode. This stops display operation and configures
* the HSYNC and VSYNC signals as inputs.
*/
rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_SWITCH);
rcar_du_start_stop(rcdu, false);
rcrtc->started = false;
}
void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc)
{
struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
rcar_du_crtc_stop(rcrtc);
rcar_du_put(rcdu);
}
void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc)
{
struct rcar_du_device *rcdu = rcrtc->crtc.dev->dev_private;
if (rcrtc->dpms != DRM_MODE_DPMS_ON)
return;
rcar_du_get(rcdu);
rcar_du_crtc_start(rcrtc);
}
static void rcar_du_crtc_update_base(struct rcar_du_crtc *rcrtc)
{
struct drm_crtc *crtc = &rcrtc->crtc;
rcar_du_plane_compute_base(rcrtc->plane, crtc->fb);
rcar_du_plane_update_base(rcrtc->plane);
}
static void rcar_du_crtc_dpms(struct drm_crtc *crtc, int mode)
{
struct rcar_du_device *rcdu = crtc->dev->dev_private;
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
if (rcrtc->dpms == mode)
return;
if (mode == DRM_MODE_DPMS_ON) {
rcar_du_get(rcdu);
rcar_du_crtc_start(rcrtc);
} else {
rcar_du_crtc_stop(rcrtc);
rcar_du_put(rcdu);
}
rcrtc->dpms = mode;
}
static bool rcar_du_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
/* TODO Fixup modes */
return true;
}
static void rcar_du_crtc_mode_prepare(struct drm_crtc *crtc)
{
struct rcar_du_device *rcdu = crtc->dev->dev_private;
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
/* We need to access the hardware during mode set, acquire a reference
* to the DU.
*/
rcar_du_get(rcdu);
/* Stop the CRTC and release the plane. Force the DPMS mode to off as a
* result.
*/
rcar_du_crtc_stop(rcrtc);
rcar_du_plane_release(rcrtc->plane);
rcrtc->dpms = DRM_MODE_DPMS_OFF;
}
static int rcar_du_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode,
int x, int y,
struct drm_framebuffer *old_fb)
{
struct rcar_du_device *rcdu = crtc->dev->dev_private;
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
const struct rcar_du_format_info *format;
int ret;
format = rcar_du_format_info(crtc->fb->pixel_format);
if (format == NULL) {
dev_dbg(rcdu->dev, "mode_set: unsupported format %08x\n",
crtc->fb->pixel_format);
ret = -EINVAL;
goto error;
}
ret = rcar_du_plane_reserve(rcrtc->plane, format);
if (ret < 0)
goto error;
rcrtc->plane->format = format;
rcrtc->plane->pitch = crtc->fb->pitches[0];
rcrtc->plane->src_x = x;
rcrtc->plane->src_y = y;
rcrtc->plane->width = mode->hdisplay;
rcrtc->plane->height = mode->vdisplay;
rcar_du_plane_compute_base(rcrtc->plane, crtc->fb);
rcrtc->outputs = 0;
return 0;
error:
/* There's no rollback/abort operation to clean up in case of error. We
* thus need to release the reference to the DU acquired in prepare()
* here.
*/
rcar_du_put(rcdu);
return ret;
}
static void rcar_du_crtc_mode_commit(struct drm_crtc *crtc)
{
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
/* We're done, restart the CRTC and set the DPMS mode to on. The
* reference to the DU acquired at prepare() time will thus be released
* by the DPMS handler (possibly called by the disable() handler).
*/
rcar_du_crtc_start(rcrtc);
rcrtc->dpms = DRM_MODE_DPMS_ON;
}
static int rcar_du_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
struct drm_framebuffer *old_fb)
{
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
rcrtc->plane->src_x = x;
rcrtc->plane->src_y = y;
rcar_du_crtc_update_base(to_rcar_crtc(crtc));
return 0;
}
static void rcar_du_crtc_disable(struct drm_crtc *crtc)
{
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
rcar_du_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
rcar_du_plane_release(rcrtc->plane);
}
static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
.dpms = rcar_du_crtc_dpms,
.mode_fixup = rcar_du_crtc_mode_fixup,
.prepare = rcar_du_crtc_mode_prepare,
.commit = rcar_du_crtc_mode_commit,
.mode_set = rcar_du_crtc_mode_set,
.mode_set_base = rcar_du_crtc_mode_set_base,
.disable = rcar_du_crtc_disable,
};
void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc,
struct drm_file *file)
{
struct drm_pending_vblank_event *event;
struct drm_device *dev = rcrtc->crtc.dev;
unsigned long flags;
/* Destroy the pending vertical blanking event associated with the
* pending page flip, if any, and disable vertical blanking interrupts.
*/
spin_lock_irqsave(&dev->event_lock, flags);
event = rcrtc->event;
if (event && event->base.file_priv == file) {
rcrtc->event = NULL;
event->base.destroy(&event->base);
drm_vblank_put(dev, rcrtc->index);
}
spin_unlock_irqrestore(&dev->event_lock, flags);
}
static void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc)
{
struct drm_pending_vblank_event *event;
struct drm_device *dev = rcrtc->crtc.dev;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
event = rcrtc->event;
rcrtc->event = NULL;
spin_unlock_irqrestore(&dev->event_lock, flags);
if (event == NULL)
return;
spin_lock_irqsave(&dev->event_lock, flags);
drm_send_vblank_event(dev, rcrtc->index, event);
spin_unlock_irqrestore(&dev->event_lock, flags);
drm_vblank_put(dev, rcrtc->index);
}
static int rcar_du_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event)
{
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
struct drm_device *dev = rcrtc->crtc.dev;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
if (rcrtc->event != NULL) {
spin_unlock_irqrestore(&dev->event_lock, flags);
return -EBUSY;
}
spin_unlock_irqrestore(&dev->event_lock, flags);
crtc->fb = fb;
rcar_du_crtc_update_base(rcrtc);
if (event) {
event->pipe = rcrtc->index;
drm_vblank_get(dev, rcrtc->index);
spin_lock_irqsave(&dev->event_lock, flags);
rcrtc->event = event;
spin_unlock_irqrestore(&dev->event_lock, flags);
}
return 0;
}
static const struct drm_crtc_funcs crtc_funcs = {
.destroy = drm_crtc_cleanup,
.set_config = drm_crtc_helper_set_config,
.page_flip = rcar_du_crtc_page_flip,
};
int rcar_du_crtc_create(struct rcar_du_device *rcdu, unsigned int index)
{
struct rcar_du_crtc *rcrtc = &rcdu->crtcs[index];
struct drm_crtc *crtc = &rcrtc->crtc;
int ret;
rcrtc->mmio_offset = index ? DISP2_REG_OFFSET : 0;
rcrtc->index = index;
rcrtc->dpms = DRM_MODE_DPMS_OFF;
rcrtc->plane = &rcdu->planes.planes[index];
rcrtc->plane->crtc = crtc;
ret = drm_crtc_init(rcdu->ddev, crtc, &crtc_funcs);
if (ret < 0)
return ret;
drm_crtc_helper_add(crtc, &crtc_helper_funcs);
return 0;
}
void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable)
{
if (enable) {
rcar_du_crtc_write(rcrtc, DSRCR, DSRCR_VBCL);
rcar_du_crtc_set(rcrtc, DIER, DIER_VBE);
} else {
rcar_du_crtc_clr(rcrtc, DIER, DIER_VBE);
}
}
void rcar_du_crtc_irq(struct rcar_du_crtc *rcrtc)
{
u32 status;
status = rcar_du_crtc_read(rcrtc, DSSR);
rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK);
if (status & DSSR_VBK) {
drm_handle_vblank(rcrtc->crtc.dev, rcrtc->index);
rcar_du_crtc_finish_page_flip(rcrtc);
}
}
/*
* rcar_du_crtc.h -- R-Car Display Unit CRTCs
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __RCAR_DU_CRTC_H__
#define __RCAR_DU_CRTC_H__
#include <linux/mutex.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
struct rcar_du_device;
struct rcar_du_plane;
struct rcar_du_crtc {
struct drm_crtc crtc;
unsigned int mmio_offset;
unsigned int index;
bool started;
struct drm_pending_vblank_event *event;
unsigned int outputs;
int dpms;
struct rcar_du_plane *plane;
};
int rcar_du_crtc_create(struct rcar_du_device *rcdu, unsigned int index);
void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable);
void rcar_du_crtc_irq(struct rcar_du_crtc *rcrtc);
void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc,
struct drm_file *file);
void rcar_du_crtc_suspend(struct rcar_du_crtc *rcrtc);
void rcar_du_crtc_resume(struct rcar_du_crtc *rcrtc);
void rcar_du_crtc_route_output(struct drm_crtc *crtc, unsigned int output);
void rcar_du_crtc_update_planes(struct drm_crtc *crtc);
#endif /* __RCAR_DU_CRTC_H__ */
/*
* rcar_du_drv.c -- R-Car Display Unit DRM driver
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include "rcar_du_crtc.h"
#include "rcar_du_drv.h"
#include "rcar_du_kms.h"
#include "rcar_du_regs.h"
/* -----------------------------------------------------------------------------
* Core device operations
*/
/*
* rcar_du_get - Acquire a reference to the DU
*
* Acquiring a reference enables the device clock and setup core registers. A
* reference must be held before accessing any hardware registers.
*
* This function must be called with the DRM mode_config lock held.
*
* Return 0 in case of success or a negative error code otherwise.
*/
int rcar_du_get(struct rcar_du_device *rcdu)
{
int ret;
if (rcdu->use_count)
goto done;
/* Enable clocks before accessing the hardware. */
ret = clk_prepare_enable(rcdu->clock);
if (ret < 0)
return ret;
/* Enable extended features */
rcar_du_write(rcdu, DEFR, DEFR_CODE | DEFR_DEFE);
rcar_du_write(rcdu, DEFR2, DEFR2_CODE | DEFR2_DEFE2G);
rcar_du_write(rcdu, DEFR3, DEFR3_CODE | DEFR3_DEFE3);
rcar_du_write(rcdu, DEFR4, DEFR4_CODE);
rcar_du_write(rcdu, DEFR5, DEFR5_CODE | DEFR5_DEFE5);
/* Use DS1PR and DS2PR to configure planes priorities and connects the
* superposition 0 to DU0 pins. DU1 pins will be configured dynamically.
*/
rcar_du_write(rcdu, DORCR, DORCR_PG1D_DS1 | DORCR_DPRS);
done:
rcdu->use_count++;
return 0;
}
/*
* rcar_du_put - Release a reference to the DU
*
* Releasing the last reference disables the device clock.
*
* This function must be called with the DRM mode_config lock held.
*/
void rcar_du_put(struct rcar_du_device *rcdu)
{
if (--rcdu->use_count)
return;
clk_disable_unprepare(rcdu->clock);
}
/* -----------------------------------------------------------------------------
* DRM operations
*/
static int rcar_du_unload(struct drm_device *dev)
{
drm_kms_helper_poll_fini(dev);
drm_mode_config_cleanup(dev);
drm_vblank_cleanup(dev);
drm_irq_uninstall(dev);
dev->dev_private = NULL;
return 0;
}
static int rcar_du_load(struct drm_device *dev, unsigned long flags)
{
struct platform_device *pdev = dev->platformdev;
struct rcar_du_platform_data *pdata = pdev->dev.platform_data;
struct rcar_du_device *rcdu;
struct resource *ioarea;
struct resource *mem;
int ret;
if (pdata == NULL) {
dev_err(dev->dev, "no platform data\n");
return -ENODEV;
}
rcdu = devm_kzalloc(&pdev->dev, sizeof(*rcdu), GFP_KERNEL);
if (rcdu == NULL) {
dev_err(dev->dev, "failed to allocate private data\n");
return -ENOMEM;
}
rcdu->dev = &pdev->dev;
rcdu->pdata = pdata;
rcdu->ddev = dev;
dev->dev_private = rcdu;
/* I/O resources and clocks */
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem == NULL) {
dev_err(&pdev->dev, "failed to get memory resource\n");
return -EINVAL;
}
ioarea = devm_request_mem_region(&pdev->dev, mem->start,
resource_size(mem), pdev->name);
if (ioarea == NULL) {
dev_err(&pdev->dev, "failed to request memory region\n");
return -EBUSY;
}
rcdu->mmio = devm_ioremap_nocache(&pdev->dev, ioarea->start,
resource_size(ioarea));
if (rcdu->mmio == NULL) {
dev_err(&pdev->dev, "failed to remap memory resource\n");
return -ENOMEM;
}
rcdu->clock = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(rcdu->clock)) {
dev_err(&pdev->dev, "failed to get clock\n");
return -ENOENT;
}
/* DRM/KMS objects */
ret = rcar_du_modeset_init(rcdu);
if (ret < 0) {
dev_err(&pdev->dev, "failed to initialize DRM/KMS\n");
goto done;
}
/* IRQ and vblank handling */
ret = drm_vblank_init(dev, (1 << rcdu->num_crtcs) - 1);
if (ret < 0) {
dev_err(&pdev->dev, "failed to initialize vblank\n");
goto done;
}
ret = drm_irq_install(dev);
if (ret < 0) {
dev_err(&pdev->dev, "failed to install IRQ handler\n");
goto done;
}
platform_set_drvdata(pdev, rcdu);
done:
if (ret)
rcar_du_unload(dev);
return ret;
}
static void rcar_du_preclose(struct drm_device *dev, struct drm_file *file)
{
struct rcar_du_device *rcdu = dev->dev_private;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i)
rcar_du_crtc_cancel_page_flip(&rcdu->crtcs[i], file);
}
static irqreturn_t rcar_du_irq(int irq, void *arg)
{
struct drm_device *dev = arg;
struct rcar_du_device *rcdu = dev->dev_private;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i)
rcar_du_crtc_irq(&rcdu->crtcs[i]);
return IRQ_HANDLED;
}
static int rcar_du_enable_vblank(struct drm_device *dev, int crtc)
{
struct rcar_du_device *rcdu = dev->dev_private;
rcar_du_crtc_enable_vblank(&rcdu->crtcs[crtc], true);
return 0;
}
static void rcar_du_disable_vblank(struct drm_device *dev, int crtc)
{
struct rcar_du_device *rcdu = dev->dev_private;
rcar_du_crtc_enable_vblank(&rcdu->crtcs[crtc], false);
}
static const struct file_operations rcar_du_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = drm_compat_ioctl,
#endif
.poll = drm_poll,
.read = drm_read,
.fasync = drm_fasync,
.llseek = no_llseek,
.mmap = drm_gem_cma_mmap,
};
static struct drm_driver rcar_du_driver = {
.driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET
| DRIVER_PRIME,
.load = rcar_du_load,
.unload = rcar_du_unload,
.preclose = rcar_du_preclose,
.irq_handler = rcar_du_irq,
.get_vblank_counter = drm_vblank_count,
.enable_vblank = rcar_du_enable_vblank,
.disable_vblank = rcar_du_disable_vblank,
.gem_free_object = 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_cma_dmabuf_import,
.gem_prime_export = drm_gem_cma_dmabuf_export,
.dumb_create = drm_gem_cma_dumb_create,
.dumb_map_offset = drm_gem_cma_dumb_map_offset,
.dumb_destroy = drm_gem_cma_dumb_destroy,
.fops = &rcar_du_fops,
.name = "rcar-du",
.desc = "Renesas R-Car Display Unit",
.date = "20130110",
.major = 1,
.minor = 0,
};
/* -----------------------------------------------------------------------------
* Power management
*/
#if CONFIG_PM_SLEEP
static int rcar_du_pm_suspend(struct device *dev)
{
struct rcar_du_device *rcdu = dev_get_drvdata(dev);
drm_kms_helper_poll_disable(rcdu->ddev);
/* TODO Suspend the CRTC */
return 0;
}
static int rcar_du_pm_resume(struct device *dev)
{
struct rcar_du_device *rcdu = dev_get_drvdata(dev);
/* TODO Resume the CRTC */
drm_kms_helper_poll_enable(rcdu->ddev);
return 0;
}
#endif
static const struct dev_pm_ops rcar_du_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(rcar_du_pm_suspend, rcar_du_pm_resume)
};
/* -----------------------------------------------------------------------------
* Platform driver
*/
static int rcar_du_probe(struct platform_device *pdev)
{
return drm_platform_init(&rcar_du_driver, pdev);
}
static int rcar_du_remove(struct platform_device *pdev)
{
drm_platform_exit(&rcar_du_driver, pdev);
return 0;
}
static struct platform_driver rcar_du_platform_driver = {
.probe = rcar_du_probe,
.remove = rcar_du_remove,
.driver = {
.owner = THIS_MODULE,
.name = "rcar-du",
.pm = &rcar_du_pm_ops,
},
};
module_platform_driver(rcar_du_platform_driver);
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver");
MODULE_LICENSE("GPL");
/*
* rcar_du_drv.h -- R-Car Display Unit DRM driver
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __RCAR_DU_DRV_H__
#define __RCAR_DU_DRV_H__
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/platform_data/rcar-du.h>
#include "rcar_du_crtc.h"
#include "rcar_du_plane.h"
struct clk;
struct device;
struct drm_device;
struct rcar_du_device {
struct device *dev;
const struct rcar_du_platform_data *pdata;
void __iomem *mmio;
struct clk *clock;
unsigned int use_count;
struct drm_device *ddev;
struct rcar_du_crtc crtcs[2];
unsigned int used_crtcs;
unsigned int num_crtcs;
struct {
struct rcar_du_plane planes[RCAR_DU_NUM_SW_PLANES];
unsigned int free;
struct mutex lock;
struct drm_property *alpha;
struct drm_property *colorkey;
struct drm_property *zpos;
} planes;
};
int rcar_du_get(struct rcar_du_device *rcdu);
void rcar_du_put(struct rcar_du_device *rcdu);
static inline u32 rcar_du_read(struct rcar_du_device *rcdu, u32 reg)
{
return ioread32(rcdu->mmio + reg);
}
static inline void rcar_du_write(struct rcar_du_device *rcdu, u32 reg, u32 data)
{
iowrite32(data, rcdu->mmio + reg);
}
#endif /* __RCAR_DU_DRV_H__ */
/*
* rcar_du_kms.c -- R-Car Display Unit Mode Setting
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include "rcar_du_crtc.h"
#include "rcar_du_drv.h"
#include "rcar_du_kms.h"
#include "rcar_du_lvds.h"
#include "rcar_du_regs.h"
#include "rcar_du_vga.h"
/* -----------------------------------------------------------------------------
* Format helpers
*/
static const struct rcar_du_format_info rcar_du_format_infos[] = {
{
.fourcc = DRM_FORMAT_RGB565,
.bpp = 16,
.planes = 1,
.pnmr = PnMR_SPIM_TP | PnMR_DDDF_16BPP,
.edf = PnDDCR4_EDF_NONE,
}, {
.fourcc = DRM_FORMAT_ARGB1555,
.bpp = 16,
.planes = 1,
.pnmr = PnMR_SPIM_ALP | PnMR_DDDF_ARGB,
.edf = PnDDCR4_EDF_NONE,
}, {
.fourcc = DRM_FORMAT_XRGB1555,
.bpp = 16,
.planes = 1,
.pnmr = PnMR_SPIM_ALP | PnMR_DDDF_ARGB,
.edf = PnDDCR4_EDF_NONE,
}, {
.fourcc = DRM_FORMAT_XRGB8888,
.bpp = 32,
.planes = 1,
.pnmr = PnMR_SPIM_TP | PnMR_DDDF_16BPP,
.edf = PnDDCR4_EDF_RGB888,
}, {
.fourcc = DRM_FORMAT_ARGB8888,
.bpp = 32,
.planes = 1,
.pnmr = PnMR_SPIM_ALP | PnMR_DDDF_16BPP,
.edf = PnDDCR4_EDF_ARGB8888,
}, {
.fourcc = DRM_FORMAT_UYVY,
.bpp = 16,
.planes = 1,
.pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
.edf = PnDDCR4_EDF_NONE,
}, {
.fourcc = DRM_FORMAT_YUYV,
.bpp = 16,
.planes = 1,
.pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
.edf = PnDDCR4_EDF_NONE,
}, {
.fourcc = DRM_FORMAT_NV12,
.bpp = 12,
.planes = 2,
.pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
.edf = PnDDCR4_EDF_NONE,
}, {
.fourcc = DRM_FORMAT_NV21,
.bpp = 12,
.planes = 2,
.pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
.edf = PnDDCR4_EDF_NONE,
}, {
/* In YUV 4:2:2, only NV16 is supported (NV61 isn't) */
.fourcc = DRM_FORMAT_NV16,
.bpp = 16,
.planes = 2,
.pnmr = PnMR_SPIM_TP_OFF | PnMR_DDDF_YC,
.edf = PnDDCR4_EDF_NONE,
},
};
const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(rcar_du_format_infos); ++i) {
if (rcar_du_format_infos[i].fourcc == fourcc)
return &rcar_du_format_infos[i];
}
return NULL;
}
/* -----------------------------------------------------------------------------
* Common connector and encoder functions
*/
struct drm_encoder *
rcar_du_connector_best_encoder(struct drm_connector *connector)
{
struct rcar_du_connector *rcon = to_rcar_connector(connector);
return &rcon->encoder->encoder;
}
void rcar_du_encoder_mode_prepare(struct drm_encoder *encoder)
{
}
void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
rcar_du_crtc_route_output(encoder->crtc, renc->output);
}
void rcar_du_encoder_mode_commit(struct drm_encoder *encoder)
{
}
/* -----------------------------------------------------------------------------
* Frame buffer
*/
static struct drm_framebuffer *
rcar_du_fb_create(struct drm_device *dev, struct drm_file *file_priv,
struct drm_mode_fb_cmd2 *mode_cmd)
{
const struct rcar_du_format_info *format;
format = rcar_du_format_info(mode_cmd->pixel_format);
if (format == NULL) {
dev_dbg(dev->dev, "unsupported pixel format %08x\n",
mode_cmd->pixel_format);
return ERR_PTR(-EINVAL);
}
if (mode_cmd->pitches[0] & 15 || mode_cmd->pitches[0] >= 8192) {
dev_dbg(dev->dev, "invalid pitch value %u\n",
mode_cmd->pitches[0]);
return ERR_PTR(-EINVAL);
}
if (format->planes == 2) {
if (mode_cmd->pitches[1] != mode_cmd->pitches[0]) {
dev_dbg(dev->dev,
"luma and chroma pitches do not match\n");
return ERR_PTR(-EINVAL);
}
}
return drm_fb_cma_create(dev, file_priv, mode_cmd);
}
static const struct drm_mode_config_funcs rcar_du_mode_config_funcs = {
.fb_create = rcar_du_fb_create,
};
int rcar_du_modeset_init(struct rcar_du_device *rcdu)
{
struct drm_device *dev = rcdu->ddev;
struct drm_encoder *encoder;
unsigned int i;
int ret;
drm_mode_config_init(rcdu->ddev);
rcdu->ddev->mode_config.min_width = 0;
rcdu->ddev->mode_config.min_height = 0;
rcdu->ddev->mode_config.max_width = 4095;
rcdu->ddev->mode_config.max_height = 2047;
rcdu->ddev->mode_config.funcs = &rcar_du_mode_config_funcs;
ret = rcar_du_plane_init(rcdu);
if (ret < 0)
return ret;
for (i = 0; i < ARRAY_SIZE(rcdu->crtcs); ++i)
rcar_du_crtc_create(rcdu, i);
rcdu->used_crtcs = 0;
rcdu->num_crtcs = i;
for (i = 0; i < rcdu->pdata->num_encoders; ++i) {
const struct rcar_du_encoder_data *pdata =
&rcdu->pdata->encoders[i];
if (pdata->output >= ARRAY_SIZE(rcdu->crtcs)) {
dev_warn(rcdu->dev,
"encoder %u references unexisting output %u, skipping\n",
i, pdata->output);
continue;
}
switch (pdata->encoder) {
case RCAR_DU_ENCODER_VGA:
rcar_du_vga_init(rcdu, &pdata->u.vga, pdata->output);
break;
case RCAR_DU_ENCODER_LVDS:
rcar_du_lvds_init(rcdu, &pdata->u.lvds, pdata->output);
break;
default:
break;
}
}
/* Set the possible CRTCs and possible clones. All encoders can be
* driven by the CRTC associated with the output they're connected to,
* as well as by CRTC 0.
*/
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
encoder->possible_crtcs = (1 << 0) | (1 << renc->output);
encoder->possible_clones = 1 << 0;
}
ret = rcar_du_plane_register(rcdu);
if (ret < 0)
return ret;
drm_kms_helper_poll_init(rcdu->ddev);
drm_helper_disable_unused_functions(rcdu->ddev);
return 0;
}
/*
* rcar_du_kms.h -- R-Car Display Unit Mode Setting
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __RCAR_DU_KMS_H__
#define __RCAR_DU_KMS_H__
#include <linux/types.h>
#include <drm/drm_crtc.h>
struct rcar_du_device;
struct rcar_du_format_info {
u32 fourcc;
unsigned int bpp;
unsigned int planes;
unsigned int pnmr;
unsigned int edf;
};
struct rcar_du_encoder {
struct drm_encoder encoder;
unsigned int output;
};
#define to_rcar_encoder(e) \
container_of(e, struct rcar_du_encoder, encoder)
struct rcar_du_connector {
struct drm_connector connector;
struct rcar_du_encoder *encoder;
};
#define to_rcar_connector(c) \
container_of(c, struct rcar_du_connector, connector)
const struct rcar_du_format_info *rcar_du_format_info(u32 fourcc);
struct drm_encoder *
rcar_du_connector_best_encoder(struct drm_connector *connector);
void rcar_du_encoder_mode_prepare(struct drm_encoder *encoder);
void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
void rcar_du_encoder_mode_commit(struct drm_encoder *encoder);
int rcar_du_modeset_init(struct rcar_du_device *rcdu);
#endif /* __RCAR_DU_KMS_H__ */
/*
* rcar_du_lvds.c -- R-Car Display Unit LVDS Encoder and Connector
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include "rcar_du_drv.h"
#include "rcar_du_kms.h"
#include "rcar_du_lvds.h"
struct rcar_du_lvds_connector {
struct rcar_du_connector connector;
const struct rcar_du_panel_data *panel;
};
#define to_rcar_lvds_connector(c) \
container_of(c, struct rcar_du_lvds_connector, connector.connector)
/* -----------------------------------------------------------------------------
* Connector
*/
static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector)
{
struct rcar_du_lvds_connector *lvdscon = to_rcar_lvds_connector(connector);
struct drm_display_mode *mode;
mode = drm_mode_create(connector->dev);
if (mode == NULL)
return 0;
mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;
mode->clock = lvdscon->panel->mode.clock;
mode->hdisplay = lvdscon->panel->mode.hdisplay;
mode->hsync_start = lvdscon->panel->mode.hsync_start;
mode->hsync_end = lvdscon->panel->mode.hsync_end;
mode->htotal = lvdscon->panel->mode.htotal;
mode->vdisplay = lvdscon->panel->mode.vdisplay;
mode->vsync_start = lvdscon->panel->mode.vsync_start;
mode->vsync_end = lvdscon->panel->mode.vsync_end;
mode->vtotal = lvdscon->panel->mode.vtotal;
mode->flags = lvdscon->panel->mode.flags;
drm_mode_set_name(mode);
drm_mode_probed_add(connector, mode);
return 1;
}
static int rcar_du_lvds_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static const struct drm_connector_helper_funcs connector_helper_funcs = {
.get_modes = rcar_du_lvds_connector_get_modes,
.mode_valid = rcar_du_lvds_connector_mode_valid,
.best_encoder = rcar_du_connector_best_encoder,
};
static void rcar_du_lvds_connector_destroy(struct drm_connector *connector)
{
drm_sysfs_connector_remove(connector);
drm_connector_cleanup(connector);
}
static enum drm_connector_status
rcar_du_lvds_connector_detect(struct drm_connector *connector, bool force)
{
return connector_status_connected;
}
static const struct drm_connector_funcs connector_funcs = {
.dpms = drm_helper_connector_dpms,
.detect = rcar_du_lvds_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = rcar_du_lvds_connector_destroy,
};
static int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc,
const struct rcar_du_panel_data *panel)
{
struct rcar_du_lvds_connector *lvdscon;
struct drm_connector *connector;
int ret;
lvdscon = devm_kzalloc(rcdu->dev, sizeof(*lvdscon), GFP_KERNEL);
if (lvdscon == NULL)
return -ENOMEM;
lvdscon->panel = panel;
connector = &lvdscon->connector.connector;
connector->display_info.width_mm = panel->width_mm;
connector->display_info.height_mm = panel->height_mm;
ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
DRM_MODE_CONNECTOR_LVDS);
if (ret < 0)
return ret;
drm_connector_helper_add(connector, &connector_helper_funcs);
ret = drm_sysfs_connector_add(connector);
if (ret < 0)
return ret;
drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF);
drm_object_property_set_value(&connector->base,
rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF);
ret = drm_mode_connector_attach_encoder(connector, &renc->encoder);
if (ret < 0)
return ret;
connector->encoder = &renc->encoder;
lvdscon->connector.encoder = renc;
return 0;
}
/* -----------------------------------------------------------------------------
* Encoder
*/
static void rcar_du_lvds_encoder_dpms(struct drm_encoder *encoder, int mode)
{
}
static bool rcar_du_lvds_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
const struct drm_display_mode *panel_mode;
struct drm_device *dev = encoder->dev;
struct drm_connector *connector;
bool found = false;
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (connector->encoder == encoder) {
found = true;
break;
}
}
if (!found) {
dev_dbg(dev->dev, "mode_fixup: no connector found\n");
return false;
}
if (list_empty(&connector->modes)) {
dev_dbg(dev->dev, "mode_fixup: empty modes list\n");
return false;
}
panel_mode = list_first_entry(&connector->modes,
struct drm_display_mode, head);
/* We're not allowed to modify the resolution. */
if (mode->hdisplay != panel_mode->hdisplay ||
mode->vdisplay != panel_mode->vdisplay)
return false;
/* The flat panel mode is fixed, just copy it to the adjusted mode. */
drm_mode_copy(adjusted_mode, panel_mode);
return true;
}
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
.dpms = rcar_du_lvds_encoder_dpms,
.mode_fixup = rcar_du_lvds_encoder_mode_fixup,
.prepare = rcar_du_encoder_mode_prepare,
.commit = rcar_du_encoder_mode_commit,
.mode_set = rcar_du_encoder_mode_set,
};
static const struct drm_encoder_funcs encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
int rcar_du_lvds_init(struct rcar_du_device *rcdu,
const struct rcar_du_encoder_lvds_data *data,
unsigned int output)
{
struct rcar_du_encoder *renc;
int ret;
renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL);
if (renc == NULL)
return -ENOMEM;
renc->output = output;
ret = drm_encoder_init(rcdu->ddev, &renc->encoder, &encoder_funcs,
DRM_MODE_ENCODER_LVDS);
if (ret < 0)
return ret;
drm_encoder_helper_add(&renc->encoder, &encoder_helper_funcs);
return rcar_du_lvds_connector_init(rcdu, renc, &data->panel);
}
/*
* rcar_du_lvds.h -- R-Car Display Unit LVDS Encoder and Connector
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __RCAR_DU_LVDS_H__
#define __RCAR_DU_LVDS_H__
struct rcar_du_device;
struct rcar_du_encoder_lvds_data;
int rcar_du_lvds_init(struct rcar_du_device *rcdu,
const struct rcar_du_encoder_lvds_data *data,
unsigned int output);
#endif /* __RCAR_DU_LVDS_H__ */
/*
* rcar_du_plane.c -- R-Car Display Unit Planes
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include "rcar_du_drv.h"
#include "rcar_du_kms.h"
#include "rcar_du_plane.h"
#include "rcar_du_regs.h"
#define RCAR_DU_COLORKEY_NONE (0 << 24)
#define RCAR_DU_COLORKEY_SOURCE (1 << 24)
#define RCAR_DU_COLORKEY_MASK (1 << 24)
struct rcar_du_kms_plane {
struct drm_plane plane;
struct rcar_du_plane *hwplane;
};
static inline struct rcar_du_plane *to_rcar_plane(struct drm_plane *plane)
{
return container_of(plane, struct rcar_du_kms_plane, plane)->hwplane;
}
static u32 rcar_du_plane_read(struct rcar_du_device *rcdu,
unsigned int index, u32 reg)
{
return rcar_du_read(rcdu, index * PLANE_OFF + reg);
}
static void rcar_du_plane_write(struct rcar_du_device *rcdu,
unsigned int index, u32 reg, u32 data)
{
rcar_du_write(rcdu, index * PLANE_OFF + reg, data);
}
int rcar_du_plane_reserve(struct rcar_du_plane *plane,
const struct rcar_du_format_info *format)
{
struct rcar_du_device *rcdu = plane->dev;
unsigned int i;
int ret = -EBUSY;
mutex_lock(&rcdu->planes.lock);
for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) {
if (!(rcdu->planes.free & (1 << i)))
continue;
if (format->planes == 1 ||
rcdu->planes.free & (1 << ((i + 1) % 8)))
break;
}
if (i == ARRAY_SIZE(rcdu->planes.planes))
goto done;
rcdu->planes.free &= ~(1 << i);
if (format->planes == 2)
rcdu->planes.free &= ~(1 << ((i + 1) % 8));
plane->hwindex = i;
ret = 0;
done:
mutex_unlock(&rcdu->planes.lock);
return ret;
}
void rcar_du_plane_release(struct rcar_du_plane *plane)
{
struct rcar_du_device *rcdu = plane->dev;
if (plane->hwindex == -1)
return;
mutex_lock(&rcdu->planes.lock);
rcdu->planes.free |= 1 << plane->hwindex;
if (plane->format->planes == 2)
rcdu->planes.free |= 1 << ((plane->hwindex + 1) % 8);
mutex_unlock(&rcdu->planes.lock);
plane->hwindex = -1;
}
void rcar_du_plane_update_base(struct rcar_du_plane *plane)
{
struct rcar_du_device *rcdu = plane->dev;
unsigned int index = plane->hwindex;
/* According to the datasheet the Y position is expressed in raster line
* units. However, 32bpp formats seem to require a doubled Y position
* value. Similarly, for the second plane, NV12 and NV21 formats seem to
* require a halved Y position value.
*/
rcar_du_plane_write(rcdu, index, PnSPXR, plane->src_x);
rcar_du_plane_write(rcdu, index, PnSPYR, plane->src_y *
(plane->format->bpp == 32 ? 2 : 1));
rcar_du_plane_write(rcdu, index, PnDSA0R, plane->dma[0]);
if (plane->format->planes == 2) {
index = (index + 1) % 8;
rcar_du_plane_write(rcdu, index, PnSPXR, plane->src_x);
rcar_du_plane_write(rcdu, index, PnSPYR, plane->src_y *
(plane->format->bpp == 16 ? 2 : 1) / 2);
rcar_du_plane_write(rcdu, index, PnDSA0R, plane->dma[1]);
}
}
void rcar_du_plane_compute_base(struct rcar_du_plane *plane,
struct drm_framebuffer *fb)
{
struct drm_gem_cma_object *gem;
gem = drm_fb_cma_get_gem_obj(fb, 0);
plane->dma[0] = gem->paddr + fb->offsets[0];
if (plane->format->planes == 2) {
gem = drm_fb_cma_get_gem_obj(fb, 1);
plane->dma[1] = gem->paddr + fb->offsets[1];
}
}
static void rcar_du_plane_setup_mode(struct rcar_du_plane *plane,
unsigned int index)
{
struct rcar_du_device *rcdu = plane->dev;
u32 colorkey;
u32 pnmr;
/* The PnALPHAR register controls alpha-blending in 16bpp formats
* (ARGB1555 and XRGB1555).
*
* For ARGB, set the alpha value to 0, and enable alpha-blending when
* the A bit is 0. This maps A=0 to alpha=0 and A=1 to alpha=255.
*
* For XRGB, set the alpha value to the plane-wide alpha value and
* enable alpha-blending regardless of the X bit value.
*/
if (plane->format->fourcc != DRM_FORMAT_XRGB1555)
rcar_du_plane_write(rcdu, index, PnALPHAR, PnALPHAR_ABIT_0);
else
rcar_du_plane_write(rcdu, index, PnALPHAR,
PnALPHAR_ABIT_X | plane->alpha);
pnmr = PnMR_BM_MD | plane->format->pnmr;
/* Disable color keying when requested. YUV formats have the
* PnMR_SPIM_TP_OFF bit set in their pnmr field, disabling color keying
* automatically.
*/
if ((plane->colorkey & RCAR_DU_COLORKEY_MASK) == RCAR_DU_COLORKEY_NONE)
pnmr |= PnMR_SPIM_TP_OFF;
/* For packed YUV formats we need to select the U/V order. */
if (plane->format->fourcc == DRM_FORMAT_YUYV)
pnmr |= PnMR_YCDF_YUYV;
rcar_du_plane_write(rcdu, index, PnMR, pnmr);
switch (plane->format->fourcc) {
case DRM_FORMAT_RGB565:
colorkey = ((plane->colorkey & 0xf80000) >> 8)
| ((plane->colorkey & 0x00fc00) >> 5)
| ((plane->colorkey & 0x0000f8) >> 3);
rcar_du_plane_write(rcdu, index, PnTC2R, colorkey);
break;
case DRM_FORMAT_ARGB1555:
case DRM_FORMAT_XRGB1555:
colorkey = ((plane->colorkey & 0xf80000) >> 9)
| ((plane->colorkey & 0x00f800) >> 6)
| ((plane->colorkey & 0x0000f8) >> 3);
rcar_du_plane_write(rcdu, index, PnTC2R, colorkey);
break;
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
rcar_du_plane_write(rcdu, index, PnTC3R,
PnTC3R_CODE | (plane->colorkey & 0xffffff));
break;
}
}
static void __rcar_du_plane_setup(struct rcar_du_plane *plane,
unsigned int index)
{
struct rcar_du_device *rcdu = plane->dev;
u32 ddcr2 = PnDDCR2_CODE;
u32 ddcr4;
u32 mwr;
/* Data format
*
* The data format is selected by the DDDF field in PnMR and the EDF
* field in DDCR4.
*/
ddcr4 = rcar_du_plane_read(rcdu, index, PnDDCR4);
ddcr4 &= ~PnDDCR4_EDF_MASK;
ddcr4 |= plane->format->edf | PnDDCR4_CODE;
rcar_du_plane_setup_mode(plane, index);
if (plane->format->planes == 2) {
if (plane->hwindex != index) {
if (plane->format->fourcc == DRM_FORMAT_NV12 ||
plane->format->fourcc == DRM_FORMAT_NV21)
ddcr2 |= PnDDCR2_Y420;
if (plane->format->fourcc == DRM_FORMAT_NV21)
ddcr2 |= PnDDCR2_NV21;
ddcr2 |= PnDDCR2_DIVU;
} else {
ddcr2 |= PnDDCR2_DIVY;
}
}
rcar_du_plane_write(rcdu, index, PnDDCR2, ddcr2);
rcar_du_plane_write(rcdu, index, PnDDCR4, ddcr4);
/* Memory pitch (expressed in pixels) */
if (plane->format->planes == 2)
mwr = plane->pitch;
else
mwr = plane->pitch * 8 / plane->format->bpp;
rcar_du_plane_write(rcdu, index, PnMWR, mwr);
/* Destination position and size */
rcar_du_plane_write(rcdu, index, PnDSXR, plane->width);
rcar_du_plane_write(rcdu, index, PnDSYR, plane->height);
rcar_du_plane_write(rcdu, index, PnDPXR, plane->dst_x);
rcar_du_plane_write(rcdu, index, PnDPYR, plane->dst_y);
/* Wrap-around and blinking, disabled */
rcar_du_plane_write(rcdu, index, PnWASPR, 0);
rcar_du_plane_write(rcdu, index, PnWAMWR, 4095);
rcar_du_plane_write(rcdu, index, PnBTR, 0);
rcar_du_plane_write(rcdu, index, PnMLR, 0);
}
void rcar_du_plane_setup(struct rcar_du_plane *plane)
{
__rcar_du_plane_setup(plane, plane->hwindex);
if (plane->format->planes == 2)
__rcar_du_plane_setup(plane, (plane->hwindex + 1) % 8);
rcar_du_plane_update_base(plane);
}
static int
rcar_du_plane_update(struct drm_plane *plane, struct drm_crtc *crtc,
struct drm_framebuffer *fb, int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h)
{
struct rcar_du_plane *rplane = to_rcar_plane(plane);
struct rcar_du_device *rcdu = plane->dev->dev_private;
const struct rcar_du_format_info *format;
unsigned int nplanes;
int ret;
format = rcar_du_format_info(fb->pixel_format);
if (format == NULL) {
dev_dbg(rcdu->dev, "%s: unsupported format %08x\n", __func__,
fb->pixel_format);
return -EINVAL;
}
if (src_w >> 16 != crtc_w || src_h >> 16 != crtc_h) {
dev_dbg(rcdu->dev, "%s: scaling not supported\n", __func__);
return -EINVAL;
}
nplanes = rplane->format ? rplane->format->planes : 0;
/* Reallocate hardware planes if the number of required planes has
* changed.
*/
if (format->planes != nplanes) {
rcar_du_plane_release(rplane);
ret = rcar_du_plane_reserve(rplane, format);
if (ret < 0)
return ret;
}
rplane->crtc = crtc;
rplane->format = format;
rplane->pitch = fb->pitches[0];
rplane->src_x = src_x >> 16;
rplane->src_y = src_y >> 16;
rplane->dst_x = crtc_x;
rplane->dst_y = crtc_y;
rplane->width = crtc_w;
rplane->height = crtc_h;
rcar_du_plane_compute_base(rplane, fb);
rcar_du_plane_setup(rplane);
mutex_lock(&rcdu->planes.lock);
rplane->enabled = true;
rcar_du_crtc_update_planes(rplane->crtc);
mutex_unlock(&rcdu->planes.lock);
return 0;
}
static int rcar_du_plane_disable(struct drm_plane *plane)
{
struct rcar_du_device *rcdu = plane->dev->dev_private;
struct rcar_du_plane *rplane = to_rcar_plane(plane);
if (!rplane->enabled)
return 0;
mutex_lock(&rcdu->planes.lock);
rplane->enabled = false;
rcar_du_crtc_update_planes(rplane->crtc);
mutex_unlock(&rcdu->planes.lock);
rcar_du_plane_release(rplane);
rplane->crtc = NULL;
rplane->format = NULL;
return 0;
}
/* Both the .set_property and the .update_plane operations are called with the
* mode_config lock held. There is this no need to explicitly protect access to
* the alpha and colorkey fields and the mode register.
*/
static void rcar_du_plane_set_alpha(struct rcar_du_plane *plane, u32 alpha)
{
if (plane->alpha == alpha)
return;
plane->alpha = alpha;
if (!plane->enabled || plane->format->fourcc != DRM_FORMAT_XRGB1555)
return;
rcar_du_plane_setup_mode(plane, plane->hwindex);
}
static void rcar_du_plane_set_colorkey(struct rcar_du_plane *plane,
u32 colorkey)
{
if (plane->colorkey == colorkey)
return;
plane->colorkey = colorkey;
if (!plane->enabled)
return;
rcar_du_plane_setup_mode(plane, plane->hwindex);
}
static void rcar_du_plane_set_zpos(struct rcar_du_plane *plane,
unsigned int zpos)
{
struct rcar_du_device *rcdu = plane->dev;
mutex_lock(&rcdu->planes.lock);
if (plane->zpos == zpos)
goto done;
plane->zpos = zpos;
if (!plane->enabled)
goto done;
rcar_du_crtc_update_planes(plane->crtc);
done:
mutex_unlock(&rcdu->planes.lock);
}
static int rcar_du_plane_set_property(struct drm_plane *plane,
struct drm_property *property,
uint64_t value)
{
struct rcar_du_device *rcdu = plane->dev->dev_private;
struct rcar_du_plane *rplane = to_rcar_plane(plane);
if (property == rcdu->planes.alpha)
rcar_du_plane_set_alpha(rplane, value);
else if (property == rcdu->planes.colorkey)
rcar_du_plane_set_colorkey(rplane, value);
else if (property == rcdu->planes.zpos)
rcar_du_plane_set_zpos(rplane, value);
else
return -EINVAL;
return 0;
}
static const struct drm_plane_funcs rcar_du_plane_funcs = {
.update_plane = rcar_du_plane_update,
.disable_plane = rcar_du_plane_disable,
.set_property = rcar_du_plane_set_property,
.destroy = drm_plane_cleanup,
};
static const uint32_t formats[] = {
DRM_FORMAT_RGB565,
DRM_FORMAT_ARGB1555,
DRM_FORMAT_XRGB1555,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_UYVY,
DRM_FORMAT_YUYV,
DRM_FORMAT_NV12,
DRM_FORMAT_NV21,
DRM_FORMAT_NV16,
};
int rcar_du_plane_init(struct rcar_du_device *rcdu)
{
unsigned int i;
mutex_init(&rcdu->planes.lock);
rcdu->planes.free = 0xff;
rcdu->planes.alpha =
drm_property_create_range(rcdu->ddev, 0, "alpha", 0, 255);
if (rcdu->planes.alpha == NULL)
return -ENOMEM;
/* The color key is expressed as an RGB888 triplet stored in a 32-bit
* integer in XRGB8888 format. Bit 24 is used as a flag to disable (0)
* or enable source color keying (1).
*/
rcdu->planes.colorkey =
drm_property_create_range(rcdu->ddev, 0, "colorkey",
0, 0x01ffffff);
if (rcdu->planes.colorkey == NULL)
return -ENOMEM;
rcdu->planes.zpos =
drm_property_create_range(rcdu->ddev, 0, "zpos", 1, 7);
if (rcdu->planes.zpos == NULL)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(rcdu->planes.planes); ++i) {
struct rcar_du_plane *plane = &rcdu->planes.planes[i];
plane->dev = rcdu;
plane->hwindex = -1;
plane->alpha = 255;
plane->colorkey = RCAR_DU_COLORKEY_NONE;
plane->zpos = 0;
}
return 0;
}
int rcar_du_plane_register(struct rcar_du_device *rcdu)
{
unsigned int i;
int ret;
for (i = 0; i < RCAR_DU_NUM_KMS_PLANES; ++i) {
struct rcar_du_kms_plane *plane;
plane = devm_kzalloc(rcdu->dev, sizeof(*plane), GFP_KERNEL);
if (plane == NULL)
return -ENOMEM;
plane->hwplane = &rcdu->planes.planes[i + 2];
plane->hwplane->zpos = 1;
ret = drm_plane_init(rcdu->ddev, &plane->plane,
(1 << rcdu->num_crtcs) - 1,
&rcar_du_plane_funcs, formats,
ARRAY_SIZE(formats), false);
if (ret < 0)
return ret;
drm_object_attach_property(&plane->plane.base,
rcdu->planes.alpha, 255);
drm_object_attach_property(&plane->plane.base,
rcdu->planes.colorkey,
RCAR_DU_COLORKEY_NONE);
drm_object_attach_property(&plane->plane.base,
rcdu->planes.zpos, 1);
}
return 0;
}
/*
* rcar_du_plane.h -- R-Car Display Unit Planes
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __RCAR_DU_PLANE_H__
#define __RCAR_DU_PLANE_H__
struct drm_crtc;
struct drm_framebuffer;
struct rcar_du_device;
struct rcar_du_format_info;
/* The RCAR DU has 8 hardware planes, shared between KMS planes and CRTCs. As
* using KMS planes requires at least one of the CRTCs being enabled, no more
* than 7 KMS planes can be available. We thus create 7 KMS planes and
* 9 software planes (one for each KMS planes and one for each CRTC).
*/
#define RCAR_DU_NUM_KMS_PLANES 7
#define RCAR_DU_NUM_HW_PLANES 8
#define RCAR_DU_NUM_SW_PLANES 9
struct rcar_du_plane {
struct rcar_du_device *dev;
struct drm_crtc *crtc;
bool enabled;
int hwindex; /* 0-based, -1 means unused */
unsigned int alpha;
unsigned int colorkey;
unsigned int zpos;
const struct rcar_du_format_info *format;
unsigned long dma[2];
unsigned int pitch;
unsigned int width;
unsigned int height;
unsigned int src_x;
unsigned int src_y;
unsigned int dst_x;
unsigned int dst_y;
};
int rcar_du_plane_init(struct rcar_du_device *rcdu);
int rcar_du_plane_register(struct rcar_du_device *rcdu);
void rcar_du_plane_setup(struct rcar_du_plane *plane);
void rcar_du_plane_update_base(struct rcar_du_plane *plane);
void rcar_du_plane_compute_base(struct rcar_du_plane *plane,
struct drm_framebuffer *fb);
int rcar_du_plane_reserve(struct rcar_du_plane *plane,
const struct rcar_du_format_info *format);
void rcar_du_plane_release(struct rcar_du_plane *plane);
#endif /* __RCAR_DU_PLANE_H__ */
/*
* rcar_du_regs.h -- R-Car Display Unit Registers Definitions
*
* Copyright (C) 2013 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* 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.
*/
#ifndef __RCAR_DU_REGS_H__
#define __RCAR_DU_REGS_H__
#define DISP2_REG_OFFSET 0x30000
/* -----------------------------------------------------------------------------
* Display Control Registers
*/
#define DSYSR 0x00000 /* display 1 */
#define D2SYSR 0x30000 /* display 2 */
#define DSYSR_ILTS (1 << 29)
#define DSYSR_DSEC (1 << 20)
#define DSYSR_IUPD (1 << 16)
#define DSYSR_DRES (1 << 9)
#define DSYSR_DEN (1 << 8)
#define DSYSR_TVM_MASTER (0 << 6)
#define DSYSR_TVM_SWITCH (1 << 6)
#define DSYSR_TVM_TVSYNC (2 << 6)
#define DSYSR_TVM_MASK (3 << 6)
#define DSYSR_SCM_INT_NONE (0 << 4)
#define DSYSR_SCM_INT_SYNC (2 << 4)
#define DSYSR_SCM_INT_VIDEO (3 << 4)
#define DSMR 0x00004
#define D2SMR 0x30004
#define DSMR_VSPM (1 << 28)
#define DSMR_ODPM (1 << 27)
#define DSMR_DIPM_DISP (0 << 25)
#define DSMR_DIPM_CSYNC (1 << 25)
#define DSMR_DIPM_DE (3 << 25)
#define DSMR_DIPM_MASK (3 << 25)
#define DSMR_CSPM (1 << 24)
#define DSMR_DIL (1 << 19)
#define DSMR_VSL (1 << 18)
#define DSMR_HSL (1 << 17)
#define DSMR_DDIS (1 << 16)
#define DSMR_CDEL (1 << 15)
#define DSMR_CDEM_CDE (0 << 13)
#define DSMR_CDEM_LOW (2 << 13)
#define DSMR_CDEM_HIGH (3 << 13)
#define DSMR_CDEM_MASK (3 << 13)
#define DSMR_CDED (1 << 12)
#define DSMR_ODEV (1 << 8)
#define DSMR_CSY_VH_OR (0 << 6)
#define DSMR_CSY_333 (2 << 6)
#define DSMR_CSY_222 (3 << 6)
#define DSMR_CSY_MASK (3 << 6)
#define DSSR 0x00008
#define D2SSR 0x30008
#define DSSR_VC1FB_DSA0 (0 << 30)
#define DSSR_VC1FB_DSA1 (1 << 30)
#define DSSR_VC1FB_DSA2 (2 << 30)
#define DSSR_VC1FB_INIT (3 << 30)
#define DSSR_VC1FB_MASK (3 << 30)
#define DSSR_VC0FB_DSA0 (0 << 28)
#define DSSR_VC0FB_DSA1 (1 << 28)
#define DSSR_VC0FB_DSA2 (2 << 28)
#define DSSR_VC0FB_INIT (3 << 28)
#define DSSR_VC0FB_MASK (3 << 28)
#define DSSR_DFB(n) (1 << ((n)+15))
#define DSSR_TVR (1 << 15)
#define DSSR_FRM (1 << 14)
#define DSSR_VBK (1 << 11)
#define DSSR_RINT (1 << 9)
#define DSSR_HBK (1 << 8)
#define DSSR_ADC(n) (1 << ((n)-1))
#define DSRCR 0x0000c
#define D2SRCR 0x3000c
#define DSRCR_TVCL (1 << 15)
#define DSRCR_FRCL (1 << 14)
#define DSRCR_VBCL (1 << 11)
#define DSRCR_RICL (1 << 9)
#define DSRCR_HBCL (1 << 8)
#define DSRCR_ADCL(n) (1 << ((n)-1))
#define DSRCR_MASK 0x0000cbff
#define DIER 0x00010
#define D2IER 0x30010
#define DIER_TVE (1 << 15)
#define DIER_FRE (1 << 14)
#define DIER_VBE (1 << 11)
#define DIER_RIE (1 << 9)
#define DIER_HBE (1 << 8)
#define DIER_ADCE(n) (1 << ((n)-1))
#define CPCR 0x00014
#define CPCR_CP4CE (1 << 19)
#define CPCR_CP3CE (1 << 18)
#define CPCR_CP2CE (1 << 17)
#define CPCR_CP1CE (1 << 16)
#define DPPR 0x00018
#define DPPR_DPE(n) (1 << ((n)*4-1))
#define DPPR_DPS(n, p) (((p)-1) << DPPR_DPS_SHIFT(n))
#define DPPR_DPS_SHIFT(n) (((n)-1)*4)
#define DPPR_BPP16 (DPPR_DPE(8) | DPPR_DPS(8, 1)) /* plane1 */
#define DPPR_BPP32_P1 (DPPR_DPE(7) | DPPR_DPS(7, 1))
#define DPPR_BPP32_P2 (DPPR_DPE(8) | DPPR_DPS(8, 2))
#define DPPR_BPP32 (DPPR_BPP32_P1 | DPPR_BPP32_P2) /* plane1 & 2 */
#define DEFR 0x00020
#define D2EFR 0x30020
#define DEFR_CODE (0x7773 << 16)
#define DEFR_EXSL (1 << 12)
#define DEFR_EXVL (1 << 11)
#define DEFR_EXUP (1 << 5)
#define DEFR_VCUP (1 << 4)
#define DEFR_DEFE (1 << 0)
#define DAPCR 0x00024
#define DAPCR_CODE (0x7773 << 16)
#define DAPCR_AP2E (1 << 4)
#define DAPCR_AP1E (1 << 0)
#define DCPCR 0x00028
#define DCPCR_CODE (0x7773 << 16)
#define DCPCR_CA2B (1 << 13)
#define DCPCR_CD2F (1 << 12)
#define DCPCR_DC2E (1 << 8)
#define DCPCR_CAB (1 << 5)
#define DCPCR_CDF (1 << 4)
#define DCPCR_DCE (1 << 0)
#define DEFR2 0x00034
#define D2EFR2 0x30034
#define DEFR2_CODE (0x7775 << 16)
#define DEFR2_DEFE2G (1 << 0)
#define DEFR3 0x00038
#define D2EFR3 0x30038
#define DEFR3_CODE (0x7776 << 16)
#define DEFR3_EVDA (1 << 14)
#define DEFR3_EVDM_1 (1 << 12)
#define DEFR3_EVDM_2 (2 << 12)
#define DEFR3_EVDM_3 (3 << 12)
#define DEFR3_VMSM2_EMA (1 << 6)
#define DEFR3_VMSM1_ENA (1 << 4)
#define DEFR3_DEFE3 (1 << 0)
#define DEFR4 0x0003c
#define D2EFR4 0x3003c
#define DEFR4_CODE (0x7777 << 16)
#define DEFR4_LRUO (1 << 5)
#define DEFR4_SPCE (1 << 4)
#define DVCSR 0x000d0
#define DVCSR_VCnFB2_DSA0(n) (0 << ((n)*2+16))
#define DVCSR_VCnFB2_DSA1(n) (1 << ((n)*2+16))
#define DVCSR_VCnFB2_DSA2(n) (2 << ((n)*2+16))
#define DVCSR_VCnFB2_INIT(n) (3 << ((n)*2+16))
#define DVCSR_VCnFB2_MASK(n) (3 << ((n)*2+16))
#define DVCSR_VCnFB_DSA0(n) (0 << ((n)*2))
#define DVCSR_VCnFB_DSA1(n) (1 << ((n)*2))
#define DVCSR_VCnFB_DSA2(n) (2 << ((n)*2))
#define DVCSR_VCnFB_INIT(n) (3 << ((n)*2))
#define DVCSR_VCnFB_MASK(n) (3 << ((n)*2))
#define DEFR5 0x000e0
#define DEFR5_CODE (0x66 << 24)
#define DEFR5_YCRGB2_DIS (0 << 14)
#define DEFR5_YCRGB2_PRI1 (1 << 14)
#define DEFR5_YCRGB2_PRI2 (2 << 14)
#define DEFR5_YCRGB2_PRI3 (3 << 14)
#define DEFR5_YCRGB2_MASK (3 << 14)
#define DEFR5_YCRGB1_DIS (0 << 12)
#define DEFR5_YCRGB1_PRI1 (1 << 12)
#define DEFR5_YCRGB1_PRI2 (2 << 12)
#define DEFR5_YCRGB1_PRI3 (3 << 12)
#define DEFR5_YCRGB1_MASK (3 << 12)
#define DEFR5_DEFE5 (1 << 0)
#define DDLTR 0x000e4
#define DDLTR_CODE (0x7766 << 16)
#define DDLTR_DLAR2 (1 << 6)
#define DDLTR_DLAY2 (1 << 5)
#define DDLTR_DLAY1 (1 << 1)
#define DEFR6 0x000e8
#define DEFR6_CODE (0x7778 << 16)
#define DEFR6_ODPM22_D2SMR (0 << 10)
#define DEFR6_ODPM22_DISP (2 << 10)
#define DEFR6_ODPM22_CDE (3 << 10)
#define DEFR6_ODPM22_MASK (3 << 10)
#define DEFR6_ODPM12_DSMR (0 << 8)
#define DEFR6_ODPM12_DISP (2 << 8)
#define DEFR6_ODPM12_CDE (3 << 8)
#define DEFR6_ODPM12_MASK (3 << 8)
#define DEFR6_TCNE2 (1 << 6)
#define DEFR6_MLOS1 (1 << 2)
#define DEFR6_DEFAULT (DEFR6_CODE | DEFR6_TCNE2)
/* -----------------------------------------------------------------------------
* Display Timing Generation Registers
*/
#define HDSR 0x00040
#define HDER 0x00044
#define VDSR 0x00048
#define VDER 0x0004c
#define HCR 0x00050
#define HSWR 0x00054
#define VCR 0x00058
#define VSPR 0x0005c
#define EQWR 0x00060
#define SPWR 0x00064
#define CLAMPSR 0x00070
#define CLAMPWR 0x00074
#define DESR 0x00078
#define DEWR 0x0007c
/* -----------------------------------------------------------------------------
* Display Attribute Registers
*/
#define CP1TR 0x00080
#define CP2TR 0x00084
#define CP3TR 0x00088
#define CP4TR 0x0008c
#define DOOR 0x00090
#define DOOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2))
#define CDER 0x00094
#define CDER_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2))
#define BPOR 0x00098
#define BPOR_RGB(r, g, b) (((r) << 18) | ((g) << 10) | ((b) << 2))
#define RINTOFSR 0x0009c
#define DSHPR 0x000c8
#define DSHPR_CODE (0x7776 << 16)
#define DSHPR_PRIH (0xa << 4)
#define DSHPR_PRIL_BPP16 (0x8 << 0)
#define DSHPR_PRIL_BPP32 (0x9 << 0)
/* -----------------------------------------------------------------------------
* Display Plane Registers
*/
#define PLANE_OFF 0x00100
#define PnMR 0x00100 /* plane 1 */
#define PnMR_VISL_VIN0 (0 << 26) /* use Video Input 0 */
#define PnMR_VISL_VIN1 (1 << 26) /* use Video Input 1 */
#define PnMR_VISL_VIN2 (2 << 26) /* use Video Input 2 */
#define PnMR_VISL_VIN3 (3 << 26) /* use Video Input 3 */
#define PnMR_YCDF_YUYV (1 << 20) /* YUYV format */
#define PnMR_TC_R (0 << 17) /* Tranparent color is PnTC1R */
#define PnMR_TC_CP (1 << 17) /* Tranparent color is color palette */
#define PnMR_WAE (1 << 16) /* Wrap around Enable */
#define PnMR_SPIM_TP (0 << 12) /* Transparent Color */
#define PnMR_SPIM_ALP (1 << 12) /* Alpha Blending */
#define PnMR_SPIM_EOR (2 << 12) /* EOR */
#define PnMR_SPIM_TP_OFF (1 << 14) /* No Transparent Color */
#define PnMR_CPSL_CP1 (0 << 8) /* Color Palette selected 1 */
#define PnMR_CPSL_CP2 (1 << 8) /* Color Palette selected 2 */
#define PnMR_CPSL_CP3 (2 << 8) /* Color Palette selected 3 */
#define PnMR_CPSL_CP4 (3 << 8) /* Color Palette selected 4 */
#define PnMR_DC (1 << 7) /* Display Area Change */
#define PnMR_BM_MD (0 << 4) /* Manual Display Change Mode */
#define PnMR_BM_AR (1 << 4) /* Auto Rendering Mode */
#define PnMR_BM_AD (2 << 4) /* Auto Display Change Mode */
#define PnMR_BM_VC (3 << 4) /* Video Capture Mode */
#define PnMR_DDDF_8BPP (0 << 0) /* 8bit */
#define PnMR_DDDF_16BPP (1 << 0) /* 16bit or 32bit */
#define PnMR_DDDF_ARGB (2 << 0) /* ARGB */
#define PnMR_DDDF_YC (3 << 0) /* YC */
#define PnMR_DDDF_MASK (3 << 0)
#define PnMWR 0x00104
#define PnALPHAR 0x00108
#define PnALPHAR_ABIT_1 (0 << 12)
#define PnALPHAR_ABIT_0 (1 << 12)
#define PnALPHAR_ABIT_X (2 << 12)
#define PnDSXR 0x00110
#define PnDSYR 0x00114
#define PnDPXR 0x00118
#define PnDPYR 0x0011c
#define PnDSA0R 0x00120
#define PnDSA1R 0x00124
#define PnDSA2R 0x00128
#define PnDSA_MASK 0xfffffff0
#define PnSPXR 0x00130
#define PnSPYR 0x00134
#define PnWASPR 0x00138
#define PnWAMWR 0x0013c
#define PnBTR 0x00140
#define PnTC1R 0x00144
#define PnTC2R 0x00148
#define PnTC3R 0x0014c
#define PnTC3R_CODE (0x66 << 24)
#define PnMLR 0x00150
#define PnSWAPR 0x00180
#define PnSWAPR_DIGN (1 << 4)
#define PnSWAPR_SPQW (1 << 3)
#define PnSWAPR_SPLW (1 << 2)
#define PnSWAPR_SPWD (1 << 1)
#define PnSWAPR_SPBY (1 << 0)
#define PnDDCR 0x00184
#define PnDDCR_CODE (0x7775 << 16)
#define PnDDCR_LRGB1 (1 << 11)
#define PnDDCR_LRGB0 (1 << 10)
#define PnDDCR2 0x00188
#define PnDDCR2_CODE (0x7776 << 16)
#define PnDDCR2_NV21 (1 << 5)
#define PnDDCR2_Y420 (1 << 4)
#define PnDDCR2_DIVU (1 << 1)
#define PnDDCR2_DIVY (1 << 0)
#define PnDDCR4 0x00190
#define PnDDCR4_CODE (0x7766 << 16)
#define PnDDCR4_SDFS_RGB (0 << 4)
#define PnDDCR4_SDFS_YC (5 << 4)
#define PnDDCR4_SDFS_MASK (7 << 4)
#define PnDDCR4_EDF_NONE (0 << 0)
#define PnDDCR4_EDF_ARGB8888 (1 << 0)
#define PnDDCR4_EDF_RGB888 (2 << 0)
#define PnDDCR4_EDF_RGB666 (3 << 0)
#define PnDDCR4_EDF_MASK (7 << 0)
#define APnMR 0x0a100
#define APnMR_WAE (1 << 16) /* Wrap around Enable */
#define APnMR_DC (1 << 7) /* Display Area Change */
#define APnMR_BM_MD (0 << 4) /* Manual Display Change Mode */
#define APnMR_BM_AD (2 << 4) /* Auto Display Change Mode */
#define APnMWR 0x0a104
#define APnDSA0R 0x0a120
#define APnDSA1R 0x0a124
#define APnDSA2R 0x0a128
#define APnMLR 0x0a150
/* -----------------------------------------------------------------------------
* Display Capture Registers
*/
#define DCMWR 0x0c104
#define DC2MWR 0x0c204
#define DCSAR 0x0c120
#define DC2SAR 0x0c220
#define DCMLR 0x0c150
#define DC2MLR 0x0c250
/* -----------------------------------------------------------------------------
* Color Palette Registers
*/
#define CP1_000R 0x01000
#define CP1_255R 0x013fc
#define CP2_000R 0x02000
#define CP2_255R 0x023fc
#define CP3_000R 0x03000
#define CP3_255R 0x033fc
#define CP4_000R 0x04000
#define CP4_255R 0x043fc
/* -----------------------------------------------------------------------------
* External Synchronization Control Registers
*/
#define ESCR 0x10000
#define ESCR2 0x31000
#define ESCR_DCLKOINV (1 << 25)
#define ESCR_DCLKSEL_DCLKIN (0 << 20)
#define ESCR_DCLKSEL_CLKS (1 << 20)
#define ESCR_DCLKSEL_MASK (1 << 20)
#define ESCR_DCLKDIS (1 << 16)
#define ESCR_SYNCSEL_OFF (0 << 8)
#define ESCR_SYNCSEL_EXVSYNC (2 << 8)
#define ESCR_SYNCSEL_EXHSYNC (3 << 8)
#define ESCR_FRQSEL_MASK (0x3f << 0)
#define OTAR 0x10004
#define OTAR2 0x31004
/* -----------------------------------------------------------------------------
* Dual Display Output Control Registers
*/
#define DORCR 0x11000
#define DORCR_PG2T (1 << 30)
#define DORCR_DK2S (1 << 28)
#define DORCR_PG2D_DS1 (0 << 24)
#define DORCR_PG2D_DS2 (1 << 24)
#define DORCR_PG2D_FIX0 (2 << 24)
#define DORCR_PG2D_DOOR (3 << 24)
#define DORCR_PG2D_MASK (3 << 24)
#define DORCR_DR1D (1 << 21)
#define DORCR_PG1D_DS1 (0 << 16)
#define DORCR_PG1D_DS2 (1 << 16)
#define DORCR_PG1D_FIX0 (2 << 16)
#define DORCR_PG1D_DOOR (3 << 16)
#define DORCR_PG1D_MASK (3 << 16)
#define DORCR_RGPV (1 << 4)
#define DORCR_DPRS (1 << 0)
#define DPTSR 0x11004
#define DPTSR_PnDK(n) (1 << ((n) + 16))
#define DPTSR_PnTS(n) (1 << (n))
#define DAPTSR 0x11008
#define DAPTSR_APnDK(n) (1 << ((n) + 16))
#define DAPTSR_APnTS(n) (1 << (n))
#define DS1PR 0x11020
#define DS2PR 0x11024
/* -----------------------------------------------------------------------------
* YC-RGB Conversion Coefficient Registers
*/
#define YNCR 0x11080
#define YNOR 0x11084
#define CRNOR 0x11088
#define CBNOR 0x1108c
#define RCRCR 0x11090
#define GCRCR 0x11094
#define GCBCR 0x11098
#define BCBCR 0x1109c
#endif /* __RCAR_DU_REGS_H__ */
/*
* rcar_du_vga.c -- R-Car Display Unit VGA DAC and Connector
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include "rcar_du_drv.h"
#include "rcar_du_kms.h"
#include "rcar_du_vga.h"
/* -----------------------------------------------------------------------------
* Connector
*/
static int rcar_du_vga_connector_get_modes(struct drm_connector *connector)
{
return 0;
}
static int rcar_du_vga_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static const struct drm_connector_helper_funcs connector_helper_funcs = {
.get_modes = rcar_du_vga_connector_get_modes,
.mode_valid = rcar_du_vga_connector_mode_valid,
.best_encoder = rcar_du_connector_best_encoder,
};
static void rcar_du_vga_connector_destroy(struct drm_connector *connector)
{
drm_sysfs_connector_remove(connector);
drm_connector_cleanup(connector);
}
static enum drm_connector_status
rcar_du_vga_connector_detect(struct drm_connector *connector, bool force)
{
return connector_status_unknown;
}
static const struct drm_connector_funcs connector_funcs = {
.dpms = drm_helper_connector_dpms,
.detect = rcar_du_vga_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = rcar_du_vga_connector_destroy,
};
static int rcar_du_vga_connector_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc)
{
struct rcar_du_connector *rcon;
struct drm_connector *connector;
int ret;
rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
if (rcon == NULL)
return -ENOMEM;
connector = &rcon->connector;
connector->display_info.width_mm = 0;
connector->display_info.height_mm = 0;
ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
DRM_MODE_CONNECTOR_VGA);
if (ret < 0)
return ret;
drm_connector_helper_add(connector, &connector_helper_funcs);
ret = drm_sysfs_connector_add(connector);
if (ret < 0)
return ret;
drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF);
drm_object_property_set_value(&connector->base,
rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF);
ret = drm_mode_connector_attach_encoder(connector, &renc->encoder);
if (ret < 0)
return ret;
connector->encoder = &renc->encoder;
rcon->encoder = renc;
return 0;
}
/* -----------------------------------------------------------------------------
* Encoder
*/
static void rcar_du_vga_encoder_dpms(struct drm_encoder *encoder, int mode)
{
}
static bool rcar_du_vga_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
.dpms = rcar_du_vga_encoder_dpms,
.mode_fixup = rcar_du_vga_encoder_mode_fixup,
.prepare = rcar_du_encoder_mode_prepare,
.commit = rcar_du_encoder_mode_commit,
.mode_set = rcar_du_encoder_mode_set,
};
static const struct drm_encoder_funcs encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
int rcar_du_vga_init(struct rcar_du_device *rcdu,
const struct rcar_du_encoder_vga_data *data,
unsigned int output)
{
struct rcar_du_encoder *renc;
int ret;
renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL);
if (renc == NULL)
return -ENOMEM;
renc->output = output;
ret = drm_encoder_init(rcdu->ddev, &renc->encoder, &encoder_funcs,
DRM_MODE_ENCODER_DAC);
if (ret < 0)
return ret;
drm_encoder_helper_add(&renc->encoder, &encoder_helper_funcs);
return rcar_du_vga_connector_init(rcdu, renc);
}
/*
* rcar_du_vga.h -- R-Car Display Unit VGA DAC and Connector
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __RCAR_DU_VGA_H__
#define __RCAR_DU_VGA_H__
struct rcar_du_device;
struct rcar_du_encoder_vga_data;
int rcar_du_vga_init(struct rcar_du_device *rcdu,
const struct rcar_du_encoder_vga_data *data,
unsigned int output);
#endif /* __RCAR_DU_VGA_H__ */
/*
* rcar_du.h -- R-Car Display Unit DRM driver
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __RCAR_DU_H__
#define __RCAR_DU_H__
#include <drm/drm_mode.h>
enum rcar_du_encoder_type {
RCAR_DU_ENCODER_UNUSED = 0,
RCAR_DU_ENCODER_VGA,
RCAR_DU_ENCODER_LVDS,
};
struct rcar_du_panel_data {
unsigned int width_mm; /* Panel width in mm */
unsigned int height_mm; /* Panel height in mm */
struct drm_mode_modeinfo mode;
};
struct rcar_du_encoder_lvds_data {
struct rcar_du_panel_data panel;
};
struct rcar_du_encoder_vga_data {
/* TODO: Add DDC information for EDID retrieval */
};
struct rcar_du_encoder_data {
enum rcar_du_encoder_type encoder;
unsigned int output;
union {
struct rcar_du_encoder_lvds_data lvds;
struct rcar_du_encoder_vga_data vga;
} u;
};
struct rcar_du_platform_data {
struct rcar_du_encoder_data *encoders;
unsigned int num_encoders;
};
#endif /* __RCAR_DU_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