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
This diff is collapsed.
/*
* 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__ */
This diff is collapsed.
/*
* 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__ */
This diff is collapsed.
/*
* 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