Commit 96f60e37 authored by Russell King's avatar Russell King

DRM: Armada: Add Armada DRM driver

This patch adds support for the pair of LCD controllers on the Marvell
Armada 510 SoCs.  This driver supports:
- multiple contiguous scanout buffers for video and graphics
- shm backed cacheable buffer objects for X pixmaps for Vivante GPU
  acceleration
- dual lcd0 and lcd1 crt operation
- video overlay on each LCD crt via DRM planes
- page flipping of the main scanout buffers
- DRM prime for buffer export/import

This driver is trivial to extend to other Armada SoCs.

Included in this commit is the core driver with no output support; output
support is platform and encoder driver dependent.
Tested-by: default avatarSebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Reviewed-by: default avatarRob Clark <robdclark@gmail.com>
Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
parent 15c03dd4
......@@ -225,6 +225,8 @@ source "drivers/gpu/drm/mgag200/Kconfig"
source "drivers/gpu/drm/cirrus/Kconfig"
source "drivers/gpu/drm/armada/Kconfig"
source "drivers/gpu/drm/rcar-du/Kconfig"
source "drivers/gpu/drm/shmobile/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_ARMADA) += armada/
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
obj-$(CONFIG_DRM_OMAP) += omapdrm/
......
config DRM_ARMADA
tristate "DRM support for Marvell Armada SoCs"
depends on DRM && HAVE_CLK
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
select DRM_KMS_HELPER
help
Support the "LCD" controllers found on the Marvell Armada 510
devices. There are two controllers on the device, each controller
supports graphics and video overlays.
This driver provides no built-in acceleration; acceleration is
performed by other IP found on the SoC. This driver provides
kernel mode setting and buffer management to userspace.
armada-y := armada_crtc.o armada_drv.o armada_fb.o armada_fbdev.o \
armada_gem.o armada_output.o armada_overlay.o \
armada_slave.o
armada-y += armada_510.o
armada-$(CONFIG_DEBUG_FS) += armada_debugfs.o
obj-$(CONFIG_DRM_ARMADA) := armada.o
/*
* Copyright (C) 2012 Russell King
*
* 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.
*
* Armada 510 (aka Dove) variant support
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include "armada_crtc.h"
#include "armada_drm.h"
#include "armada_hw.h"
static int armada510_init(struct armada_private *priv, struct device *dev)
{
priv->extclk[0] = devm_clk_get(dev, "ext_ref_clk_1");
if (IS_ERR(priv->extclk[0]) && PTR_ERR(priv->extclk[0]) == -ENOENT)
priv->extclk[0] = ERR_PTR(-EPROBE_DEFER);
return PTR_RET(priv->extclk[0]);
}
static int armada510_crtc_init(struct armada_crtc *dcrtc)
{
/* Lower the watermark so to eliminate jitter at higher bandwidths */
armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F);
return 0;
}
/*
* Armada510 specific SCLK register selection.
* This gets called with sclk = NULL to test whether the mode is
* supportable, and again with sclk != NULL to set the clocks up for
* that. The former can return an error, but the latter is expected
* not to.
*
* We currently are pretty rudimentary here, always selecting
* EXT_REF_CLK_1 for LCD0 and erroring LCD1. This needs improvement!
*/
static int armada510_crtc_compute_clock(struct armada_crtc *dcrtc,
const struct drm_display_mode *mode, uint32_t *sclk)
{
struct armada_private *priv = dcrtc->crtc.dev->dev_private;
struct clk *clk = priv->extclk[0];
int ret;
if (dcrtc->num == 1)
return -EINVAL;
if (IS_ERR(clk))
return PTR_ERR(clk);
if (dcrtc->clk != clk) {
ret = clk_prepare_enable(clk);
if (ret)
return ret;
dcrtc->clk = clk;
}
if (sclk) {
uint32_t rate, ref, div;
rate = mode->clock * 1000;
ref = clk_round_rate(clk, rate);
div = DIV_ROUND_UP(ref, rate);
if (div < 1)
div = 1;
clk_set_rate(clk, ref);
*sclk = div | SCLK_510_EXTCLK1;
}
return 0;
}
const struct armada_variant armada510_ops = {
.has_spu_adv_reg = true,
.init = armada510_init,
.crtc_init = armada510_crtc_init,
.crtc_compute_clock = armada510_crtc_compute_clock,
};
This diff is collapsed.
/*
* Copyright (C) 2012 Russell King
*
* 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 ARMADA_CRTC_H
#define ARMADA_CRTC_H
struct armada_gem_object;
struct armada_regs {
uint32_t offset;
uint32_t mask;
uint32_t val;
};
#define armada_reg_queue_mod(_r, _i, _v, _m, _o) \
do { \
struct armada_regs *__reg = _r; \
__reg[_i].offset = _o; \
__reg[_i].mask = ~(_m); \
__reg[_i].val = _v; \
_i++; \
} while (0)
#define armada_reg_queue_set(_r, _i, _v, _o) \
armada_reg_queue_mod(_r, _i, _v, ~0, _o)
#define armada_reg_queue_end(_r, _i) \
armada_reg_queue_mod(_r, _i, 0, 0, ~0)
struct armada_frame_work;
struct armada_crtc {
struct drm_crtc crtc;
unsigned num;
void __iomem *base;
struct clk *clk;
struct {
uint32_t spu_v_h_total;
uint32_t spu_v_porch;
uint32_t spu_adv_reg;
} v[2];
bool interlaced;
uint8_t csc_yuv_mode;
uint8_t csc_rgb_mode;
struct drm_plane *plane;
int dpms;
uint32_t cfg_dumb_ctrl;
uint32_t dumb_ctrl;
uint32_t spu_iopad_ctrl;
wait_queue_head_t frame_wait;
struct armada_frame_work *frame_work;
spinlock_t irq_lock;
uint32_t irq_ena;
struct list_head vbl_list;
};
#define drm_to_armada_crtc(c) container_of(c, struct armada_crtc, crtc)
int armada_drm_crtc_create(struct drm_device *, unsigned, struct resource *);
void armada_drm_crtc_gamma_set(struct drm_crtc *, u16, u16, u16, int);
void armada_drm_crtc_gamma_get(struct drm_crtc *, u16 *, u16 *, u16 *, int);
void armada_drm_crtc_irq(struct armada_crtc *, u32);
void armada_drm_crtc_disable_irq(struct armada_crtc *, u32);
void armada_drm_crtc_enable_irq(struct armada_crtc *, u32);
void armada_drm_crtc_update_regs(struct armada_crtc *, struct armada_regs *);
#endif
/*
* Copyright (C) 2012 Russell King
* Rewritten from the dovefb driver, and Armada510 manuals.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/ctype.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <drm/drmP.h>
#include "armada_crtc.h"
#include "armada_drm.h"
static int armada_debugfs_gem_linear_show(struct seq_file *m, void *data)
{
struct drm_info_node *node = m->private;
struct drm_device *dev = node->minor->dev;
struct armada_private *priv = dev->dev_private;
int ret;
mutex_lock(&dev->struct_mutex);
ret = drm_mm_dump_table(m, &priv->linear);
mutex_unlock(&dev->struct_mutex);
return ret;
}
static int armada_debugfs_reg_show(struct seq_file *m, void *data)
{
struct drm_device *dev = m->private;
struct armada_private *priv = dev->dev_private;
int n, i;
if (priv) {
for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) {
struct armada_crtc *dcrtc = priv->dcrtc[n];
if (!dcrtc)
continue;
for (i = 0x84; i <= 0x1c4; i += 4) {
uint32_t v = readl_relaxed(dcrtc->base + i);
seq_printf(m, "%u: 0x%04x: 0x%08x\n", n, i, v);
}
}
}
return 0;
}
static int armada_debugfs_reg_r_open(struct inode *inode, struct file *file)
{
return single_open(file, armada_debugfs_reg_show, inode->i_private);
}
static const struct file_operations fops_reg_r = {
.owner = THIS_MODULE,
.open = armada_debugfs_reg_r_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int armada_debugfs_write(struct file *file, const char __user *ptr,
size_t len, loff_t *off)
{
struct drm_device *dev = file->private_data;
struct armada_private *priv = dev->dev_private;
struct armada_crtc *dcrtc = priv->dcrtc[0];
char buf[32], *p;
uint32_t reg, val;
int ret;
if (*off != 0)
return 0;
if (len > sizeof(buf) - 1)
len = sizeof(buf) - 1;
ret = strncpy_from_user(buf, ptr, len);
if (ret < 0)
return ret;
buf[len] = '\0';
reg = simple_strtoul(buf, &p, 16);
if (!isspace(*p))
return -EINVAL;
val = simple_strtoul(p + 1, NULL, 16);
if (reg >= 0x84 && reg <= 0x1c4)
writel(val, dcrtc->base + reg);
return len;
}
static int armada_debugfs_reg_w_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static const struct file_operations fops_reg_w = {
.owner = THIS_MODULE,
.open = armada_debugfs_reg_w_open,
.write = armada_debugfs_write,
.llseek = noop_llseek,
};
static struct drm_info_list armada_debugfs_list[] = {
{ "gem_linear", armada_debugfs_gem_linear_show, 0 },
};
#define ARMADA_DEBUGFS_ENTRIES ARRAY_SIZE(armada_debugfs_list)
static int drm_add_fake_info_node(struct drm_minor *minor, struct dentry *ent,
const void *key)
{
struct drm_info_node *node;
node = kmalloc(sizeof(struct drm_info_node), GFP_KERNEL);
if (node == NULL) {
debugfs_remove(ent);
return -ENOMEM;
}
node->minor = minor;
node->dent = ent;
node->info_ent = (void *) key;
mutex_lock(&minor->debugfs_lock);
list_add(&node->list, &minor->debugfs_list);
mutex_unlock(&minor->debugfs_lock);
return 0;
}
static int armada_debugfs_create(struct dentry *root, struct drm_minor *minor,
const char *name, umode_t mode, const struct file_operations *fops)
{
struct dentry *de;
de = debugfs_create_file(name, mode, root, minor->dev, fops);
return drm_add_fake_info_node(minor, de, fops);
}
int armada_drm_debugfs_init(struct drm_minor *minor)
{
int ret;
ret = drm_debugfs_create_files(armada_debugfs_list,
ARMADA_DEBUGFS_ENTRIES,
minor->debugfs_root, minor);
if (ret)
return ret;
ret = armada_debugfs_create(minor->debugfs_root, minor,
"reg", S_IFREG | S_IRUSR, &fops_reg_r);
if (ret)
goto err_1;
ret = armada_debugfs_create(minor->debugfs_root, minor,
"reg_wr", S_IFREG | S_IWUSR, &fops_reg_w);
if (ret)
goto err_2;
return ret;
err_2:
drm_debugfs_remove_files((struct drm_info_list *)&fops_reg_r, 1, minor);
err_1:
drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
minor);
return ret;
}
void armada_drm_debugfs_cleanup(struct drm_minor *minor)
{
drm_debugfs_remove_files((struct drm_info_list *)&fops_reg_w, 1, minor);
drm_debugfs_remove_files((struct drm_info_list *)&fops_reg_r, 1, minor);
drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES,
minor);
}
/*
* Copyright (C) 2012 Russell King
*
* 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 ARMADA_DRM_H
#define ARMADA_DRM_H
#include <linux/kfifo.h>
#include <linux/io.h>
#include <linux/workqueue.h>
#include <drm/drmP.h>
struct armada_crtc;
struct armada_gem_object;
struct clk;
struct drm_fb_helper;
static inline void
armada_updatel(uint32_t val, uint32_t mask, void __iomem *ptr)
{
uint32_t ov, v;
ov = v = readl_relaxed(ptr);
v = (v & ~mask) | val;
if (ov != v)
writel_relaxed(v, ptr);
}
static inline uint32_t armada_pitch(uint32_t width, uint32_t bpp)
{
uint32_t pitch = bpp != 4 ? width * ((bpp + 7) / 8) : width / 2;
/* 88AP510 spec recommends pitch be a multiple of 128 */
return ALIGN(pitch, 128);
}
struct armada_vbl_event {
struct list_head node;
void *data;
void (*fn)(struct armada_crtc *, void *);
};
void armada_drm_vbl_event_add(struct armada_crtc *,
struct armada_vbl_event *);
void armada_drm_vbl_event_remove(struct armada_crtc *,
struct armada_vbl_event *);
void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *,
struct armada_vbl_event *);
#define armada_drm_vbl_event_init(_e, _f, _d) do { \
struct armada_vbl_event *__e = _e; \
INIT_LIST_HEAD(&__e->node); \
__e->data = _d; \
__e->fn = _f; \
} while (0)
struct armada_private;
struct armada_variant {
bool has_spu_adv_reg;
int (*init)(struct armada_private *, struct device *);
int (*crtc_init)(struct armada_crtc *);
int (*crtc_compute_clock)(struct armada_crtc *,
const struct drm_display_mode *,
uint32_t *);
};
/* Variant ops */
extern const struct armada_variant armada510_ops;
struct armada_private {
const struct armada_variant *variant;
struct work_struct fb_unref_work;
DECLARE_KFIFO(fb_unref, struct drm_framebuffer *, 8);
struct drm_fb_helper *fbdev;
struct armada_crtc *dcrtc[2];
struct drm_mm linear;
struct clk *extclk[2];
struct drm_property *csc_yuv_prop;
struct drm_property *csc_rgb_prop;
struct drm_property *colorkey_prop;
struct drm_property *colorkey_min_prop;
struct drm_property *colorkey_max_prop;
struct drm_property *colorkey_val_prop;
struct drm_property *colorkey_alpha_prop;
struct drm_property *colorkey_mode_prop;
struct drm_property *brightness_prop;
struct drm_property *contrast_prop;
struct drm_property *saturation_prop;
#ifdef CONFIG_DEBUG_FS
struct dentry *de;
#endif
};
void __armada_drm_queue_unref_work(struct drm_device *,
struct drm_framebuffer *);
void armada_drm_queue_unref_work(struct drm_device *,
struct drm_framebuffer *);
extern const struct drm_mode_config_funcs armada_drm_mode_config_funcs;
int armada_fbdev_init(struct drm_device *);
void armada_fbdev_fini(struct drm_device *);
int armada_overlay_plane_create(struct drm_device *, unsigned long);
int armada_drm_debugfs_init(struct drm_minor *);
void armada_drm_debugfs_cleanup(struct drm_minor *);
#endif
This diff is collapsed.
/*
* Copyright (C) 2012 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
#include "armada_drm.h"
#include "armada_fb.h"
#include "armada_gem.h"
#include "armada_hw.h"
static void armada_fb_destroy(struct drm_framebuffer *fb)
{
struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
drm_framebuffer_cleanup(&dfb->fb);
drm_gem_object_unreference_unlocked(&dfb->obj->obj);
kfree(dfb);
}
static int armada_fb_create_handle(struct drm_framebuffer *fb,
struct drm_file *dfile, unsigned int *handle)
{
struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
return drm_gem_handle_create(dfile, &dfb->obj->obj, handle);
}
static const struct drm_framebuffer_funcs armada_fb_funcs = {
.destroy = armada_fb_destroy,
.create_handle = armada_fb_create_handle,
};
struct armada_framebuffer *armada_framebuffer_create(struct drm_device *dev,
struct drm_mode_fb_cmd2 *mode, struct armada_gem_object *obj)
{
struct armada_framebuffer *dfb;
uint8_t format, config;
int ret;
switch (mode->pixel_format) {
#define FMT(drm, fmt, mod) \
case DRM_FORMAT_##drm: \
format = CFG_##fmt; \
config = mod; \
break
FMT(RGB565, 565, CFG_SWAPRB);
FMT(BGR565, 565, 0);
FMT(ARGB1555, 1555, CFG_SWAPRB);
FMT(ABGR1555, 1555, 0);
FMT(RGB888, 888PACK, CFG_SWAPRB);
FMT(BGR888, 888PACK, 0);
FMT(XRGB8888, X888, CFG_SWAPRB);
FMT(XBGR8888, X888, 0);
FMT(ARGB8888, 8888, CFG_SWAPRB);
FMT(ABGR8888, 8888, 0);
FMT(YUYV, 422PACK, CFG_YUV2RGB | CFG_SWAPYU | CFG_SWAPUV);
FMT(UYVY, 422PACK, CFG_YUV2RGB);
FMT(VYUY, 422PACK, CFG_YUV2RGB | CFG_SWAPUV);
FMT(YVYU, 422PACK, CFG_YUV2RGB | CFG_SWAPYU);
FMT(YUV422, 422, CFG_YUV2RGB);
FMT(YVU422, 422, CFG_YUV2RGB | CFG_SWAPUV);
FMT(YUV420, 420, CFG_YUV2RGB);
FMT(YVU420, 420, CFG_YUV2RGB | CFG_SWAPUV);
FMT(C8, PSEUDO8, 0);
#undef FMT
default:
return ERR_PTR(-EINVAL);
}
dfb = kzalloc(sizeof(*dfb), GFP_KERNEL);
if (!dfb) {
DRM_ERROR("failed to allocate Armada fb object\n");
return ERR_PTR(-ENOMEM);
}
dfb->fmt = format;
dfb->mod = config;
dfb->obj = obj;
drm_helper_mode_fill_fb_struct(&dfb->fb, mode);
ret = drm_framebuffer_init(dev, &dfb->fb, &armada_fb_funcs);
if (ret) {
kfree(dfb);
return ERR_PTR(ret);
}
/*
* Take a reference on our object as we're successful - the
* caller already holds a reference, which keeps us safe for
* the above call, but the caller will drop their reference
* to it. Hence we need to take our own reference.
*/
drm_gem_object_reference(&obj->obj);
return dfb;
}
static struct drm_framebuffer *armada_fb_create(struct drm_device *dev,
struct drm_file *dfile, struct drm_mode_fb_cmd2 *mode)
{
struct armada_gem_object *obj;
struct armada_framebuffer *dfb;
int ret;
DRM_DEBUG_DRIVER("w%u h%u pf%08x f%u p%u,%u,%u\n",
mode->width, mode->height, mode->pixel_format,
mode->flags, mode->pitches[0], mode->pitches[1],
mode->pitches[2]);
/* We can only handle a single plane at the moment */
if (drm_format_num_planes(mode->pixel_format) > 1 &&
(mode->handles[0] != mode->handles[1] ||
mode->handles[0] != mode->handles[2])) {
ret = -EINVAL;
goto err;
}
obj = armada_gem_object_lookup(dev, dfile, mode->handles[0]);
if (!obj) {
ret = -ENOENT;
goto err;
}
if (obj->obj.import_attach && !obj->sgt) {
ret = armada_gem_map_import(obj);
if (ret)
goto err_unref;
}
/* Framebuffer objects must have a valid device address for scanout */
if (obj->dev_addr == DMA_ERROR_CODE) {
ret = -EINVAL;
goto err_unref;
}
dfb = armada_framebuffer_create(dev, mode, obj);
if (IS_ERR(dfb)) {
ret = PTR_ERR(dfb);
goto err;
}
drm_gem_object_unreference_unlocked(&obj->obj);
return &dfb->fb;
err_unref:
drm_gem_object_unreference_unlocked(&obj->obj);
err:
DRM_ERROR("failed to initialize framebuffer: %d\n", ret);
return ERR_PTR(ret);
}
static void armada_output_poll_changed(struct drm_device *dev)
{
struct armada_private *priv = dev->dev_private;
struct drm_fb_helper *fbh = priv->fbdev;
if (fbh)
drm_fb_helper_hotplug_event(fbh);
}
const struct drm_mode_config_funcs armada_drm_mode_config_funcs = {
.fb_create = armada_fb_create,
.output_poll_changed = armada_output_poll_changed,
};
/*
* Copyright (C) 2012 Russell King
*
* 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 ARMADA_FB_H
#define ARMADA_FB_H
struct armada_framebuffer {
struct drm_framebuffer fb;
struct armada_gem_object *obj;
uint8_t fmt;
uint8_t mod;
};
#define drm_fb_to_armada_fb(dfb) \
container_of(dfb, struct armada_framebuffer, fb)
#define drm_fb_obj(fb) drm_fb_to_armada_fb(fb)->obj
struct armada_framebuffer *armada_framebuffer_create(struct drm_device *,
struct drm_mode_fb_cmd2 *, struct armada_gem_object *);
#endif
/*
* Copyright (C) 2012 Russell King
* Written from the i915 driver.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/errno.h>
#include <linux/fb.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <drm/drmP.h>
#include <drm/drm_fb_helper.h>
#include "armada_crtc.h"
#include "armada_drm.h"
#include "armada_fb.h"
#include "armada_gem.h"
static /*const*/ struct fb_ops armada_fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = drm_fb_helper_check_var,
.fb_set_par = drm_fb_helper_set_par,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_pan_display = drm_fb_helper_pan_display,
.fb_blank = drm_fb_helper_blank,
.fb_setcmap = drm_fb_helper_setcmap,
.fb_debug_enter = drm_fb_helper_debug_enter,
.fb_debug_leave = drm_fb_helper_debug_leave,
};
static int armada_fb_create(struct drm_fb_helper *fbh,
struct drm_fb_helper_surface_size *sizes)
{
struct drm_device *dev = fbh->dev;
struct drm_mode_fb_cmd2 mode;
struct armada_framebuffer *dfb;
struct armada_gem_object *obj;
struct fb_info *info;
int size, ret;
void *ptr;
memset(&mode, 0, sizeof(mode));
mode.width = sizes->surface_width;
mode.height = sizes->surface_height;
mode.pitches[0] = armada_pitch(mode.width, sizes->surface_bpp);
mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
sizes->surface_depth);
size = mode.pitches[0] * mode.height;
obj = armada_gem_alloc_private_object(dev, size);
if (!obj) {
DRM_ERROR("failed to allocate fb memory\n");
return -ENOMEM;
}
ret = armada_gem_linear_back(dev, obj);
if (ret) {
drm_gem_object_unreference_unlocked(&obj->obj);
return ret;
}
ptr = armada_gem_map_object(dev, obj);
if (!ptr) {
drm_gem_object_unreference_unlocked(&obj->obj);
return -ENOMEM;
}
dfb = armada_framebuffer_create(dev, &mode, obj);
/*
* A reference is now held by the framebuffer object if
* successful, otherwise this drops the ref for the error path.
*/
drm_gem_object_unreference_unlocked(&obj->obj);
if (IS_ERR(dfb))
return PTR_ERR(dfb);
info = framebuffer_alloc(0, dev->dev);
if (!info) {
ret = -ENOMEM;
goto err_fballoc;
}
ret = fb_alloc_cmap(&info->cmap, 256, 0);
if (ret) {
ret = -ENOMEM;
goto err_fbcmap;
}
strlcpy(info->fix.id, "armada-drmfb", sizeof(info->fix.id));
info->par = fbh;
info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT;
info->fbops = &armada_fb_ops;
info->fix.smem_start = obj->phys_addr;
info->fix.smem_len = obj->obj.size;
info->screen_size = obj->obj.size;
info->screen_base = ptr;
fbh->fb = &dfb->fb;
fbh->fbdev = info;
drm_fb_helper_fill_fix(info, dfb->fb.pitches[0], dfb->fb.depth);
drm_fb_helper_fill_var(info, fbh, sizes->fb_width, sizes->fb_height);
DRM_DEBUG_KMS("allocated %dx%d %dbpp fb: 0x%08x\n",
dfb->fb.width, dfb->fb.height,
dfb->fb.bits_per_pixel, obj->phys_addr);
return 0;
err_fbcmap:
framebuffer_release(info);
err_fballoc:
dfb->fb.funcs->destroy(&dfb->fb);
return ret;
}
static int armada_fb_probe(struct drm_fb_helper *fbh,
struct drm_fb_helper_surface_size *sizes)
{
int ret = 0;
if (!fbh->fb) {
ret = armada_fb_create(fbh, sizes);
if (ret == 0)
ret = 1;
}
return ret;
}
static struct drm_fb_helper_funcs armada_fb_helper_funcs = {
.gamma_set = armada_drm_crtc_gamma_set,
.gamma_get = armada_drm_crtc_gamma_get,
.fb_probe = armada_fb_probe,
};
int armada_fbdev_init(struct drm_device *dev)
{
struct armada_private *priv = dev->dev_private;
struct drm_fb_helper *fbh;
int ret;
fbh = devm_kzalloc(dev->dev, sizeof(*fbh), GFP_KERNEL);
if (!fbh)
return -ENOMEM;
priv->fbdev = fbh;
fbh->funcs = &armada_fb_helper_funcs;
ret = drm_fb_helper_init(dev, fbh, 1, 1);
if (ret) {
DRM_ERROR("failed to initialize drm fb helper\n");
goto err_fb_helper;
}
ret = drm_fb_helper_single_add_all_connectors(fbh);
if (ret) {
DRM_ERROR("failed to add fb connectors\n");
goto err_fb_setup;
}
ret = drm_fb_helper_initial_config(fbh, 32);
if (ret) {
DRM_ERROR("failed to set initial config\n");
goto err_fb_setup;
}
return 0;
err_fb_setup:
drm_fb_helper_fini(fbh);
err_fb_helper:
priv->fbdev = NULL;
return ret;
}
void armada_fbdev_fini(struct drm_device *dev)
{
struct armada_private *priv = dev->dev_private;
struct drm_fb_helper *fbh = priv->fbdev;
if (fbh) {
struct fb_info *info = fbh->fbdev;
if (info) {
unregister_framebuffer(info);
if (info->cmap.len)
fb_dealloc_cmap(&info->cmap);
framebuffer_release(info);
}
if (fbh->fb)
fbh->fb->funcs->destroy(fbh->fb);
drm_fb_helper_fini(fbh);
priv->fbdev = NULL;
}
}
This diff is collapsed.
/*
* Copyright (C) 2012 Russell King
*
* 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 ARMADA_GEM_H
#define ARMADA_GEM_H
/* GEM */
struct armada_gem_object {
struct drm_gem_object obj;
void *addr;
phys_addr_t phys_addr;
resource_size_t dev_addr;
struct drm_mm_node *linear; /* for linear backed */
struct page *page; /* for page backed */
struct sg_table *sgt; /* for imported */
void (*update)(void *);
void *update_data;
};
extern const struct vm_operations_struct armada_gem_vm_ops;
#define drm_to_armada_gem(o) container_of(o, struct armada_gem_object, obj)
void armada_gem_free_object(struct drm_gem_object *);
int armada_gem_linear_back(struct drm_device *, struct armada_gem_object *);
void *armada_gem_map_object(struct drm_device *, struct armada_gem_object *);
struct armada_gem_object *armada_gem_alloc_private_object(struct drm_device *,
size_t);
int armada_gem_dumb_create(struct drm_file *, struct drm_device *,
struct drm_mode_create_dumb *);
int armada_gem_dumb_map_offset(struct drm_file *, struct drm_device *,
uint32_t, uint64_t *);
int armada_gem_dumb_destroy(struct drm_file *, struct drm_device *,
uint32_t);
struct dma_buf *armada_gem_prime_export(struct drm_device *dev,
struct drm_gem_object *obj, int flags);
struct drm_gem_object *armada_gem_prime_import(struct drm_device *,
struct dma_buf *);
int armada_gem_map_import(struct armada_gem_object *);
static inline struct armada_gem_object *armada_gem_object_lookup(
struct drm_device *dev, struct drm_file *dfile, unsigned handle)
{
struct drm_gem_object *obj = drm_gem_object_lookup(dev, dfile, handle);
return obj ? drm_to_armada_gem(obj) : NULL;
}
#endif
/*
* Copyright (C) 2012 Russell King
* Rewritten from the dovefb driver, and Armada510 manuals.
*
* 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 ARMADA_HW_H
#define ARMADA_HW_H
/*
* Note: the following registers are written from IRQ context:
* LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
* LCD_SPU_DMA_START_ADDR_[YUV][01], LCD_SPU_DMA_PITCH_YC,
* LCD_SPU_DMA_PITCH_UV, LCD_SPU_DMA_OVSA_HPXL_VLN,
* LCD_SPU_DMA_HPXL_VLN, LCD_SPU_DZM_HPXL_VLN, LCD_SPU_DMA_CTRL0
*/
enum {
LCD_SPU_ADV_REG = 0x0084, /* Armada 510 */
LCD_SPU_DMA_START_ADDR_Y0 = 0x00c0,
LCD_SPU_DMA_START_ADDR_U0 = 0x00c4,
LCD_SPU_DMA_START_ADDR_V0 = 0x00c8,
LCD_CFG_DMA_START_ADDR_0 = 0x00cc,
LCD_SPU_DMA_START_ADDR_Y1 = 0x00d0,
LCD_SPU_DMA_START_ADDR_U1 = 0x00d4,
LCD_SPU_DMA_START_ADDR_V1 = 0x00d8,
LCD_CFG_DMA_START_ADDR_1 = 0x00dc,
LCD_SPU_DMA_PITCH_YC = 0x00e0,
LCD_SPU_DMA_PITCH_UV = 0x00e4,
LCD_SPU_DMA_OVSA_HPXL_VLN = 0x00e8,
LCD_SPU_DMA_HPXL_VLN = 0x00ec,
LCD_SPU_DZM_HPXL_VLN = 0x00f0,
LCD_CFG_GRA_START_ADDR0 = 0x00f4,
LCD_CFG_GRA_START_ADDR1 = 0x00f8,
LCD_CFG_GRA_PITCH = 0x00fc,
LCD_SPU_GRA_OVSA_HPXL_VLN = 0x0100,
LCD_SPU_GRA_HPXL_VLN = 0x0104,
LCD_SPU_GZM_HPXL_VLN = 0x0108,
LCD_SPU_HWC_OVSA_HPXL_VLN = 0x010c,
LCD_SPU_HWC_HPXL_VLN = 0x0110,
LCD_SPUT_V_H_TOTAL = 0x0114,
LCD_SPU_V_H_ACTIVE = 0x0118,
LCD_SPU_H_PORCH = 0x011c,
LCD_SPU_V_PORCH = 0x0120,
LCD_SPU_BLANKCOLOR = 0x0124,
LCD_SPU_ALPHA_COLOR1 = 0x0128,
LCD_SPU_ALPHA_COLOR2 = 0x012c,
LCD_SPU_COLORKEY_Y = 0x0130,
LCD_SPU_COLORKEY_U = 0x0134,
LCD_SPU_COLORKEY_V = 0x0138,
LCD_CFG_RDREG4F = 0x013c, /* Armada 510 */
LCD_SPU_SPI_RXDATA = 0x0140,
LCD_SPU_ISA_RXDATA = 0x0144,
LCD_SPU_HWC_RDDAT = 0x0158,
LCD_SPU_GAMMA_RDDAT = 0x015c,
LCD_SPU_PALETTE_RDDAT = 0x0160,
LCD_SPU_IOPAD_IN = 0x0178,
LCD_CFG_RDREG5F = 0x017c,
LCD_SPU_SPI_CTRL = 0x0180,
LCD_SPU_SPI_TXDATA = 0x0184,
LCD_SPU_SMPN_CTRL = 0x0188,
LCD_SPU_DMA_CTRL0 = 0x0190,
LCD_SPU_DMA_CTRL1 = 0x0194,
LCD_SPU_SRAM_CTRL = 0x0198,
LCD_SPU_SRAM_WRDAT = 0x019c,
LCD_SPU_SRAM_PARA0 = 0x01a0, /* Armada 510 */
LCD_SPU_SRAM_PARA1 = 0x01a4,
LCD_CFG_SCLK_DIV = 0x01a8,
LCD_SPU_CONTRAST = 0x01ac,
LCD_SPU_SATURATION = 0x01b0,
LCD_SPU_CBSH_HUE = 0x01b4,
LCD_SPU_DUMB_CTRL = 0x01b8,
LCD_SPU_IOPAD_CONTROL = 0x01bc,
LCD_SPU_IRQ_ENA = 0x01c0,
LCD_SPU_IRQ_ISR = 0x01c4,
};
/* For LCD_SPU_ADV_REG */
enum {
ADV_VSYNC_L_OFF = 0xfff << 20,
ADV_GRACOLORKEY = 1 << 19,
ADV_VIDCOLORKEY = 1 << 18,
ADV_HWC32BLEND = 1 << 15,
ADV_HWC32ARGB = 1 << 14,
ADV_HWC32ENABLE = 1 << 13,
ADV_VSYNCOFFEN = 1 << 12,
ADV_VSYNC_H_OFF = 0xfff << 0,
};
enum {
CFG_565 = 0,
CFG_1555 = 1,
CFG_888PACK = 2,
CFG_X888 = 3,
CFG_8888 = 4,
CFG_422PACK = 5,
CFG_422 = 6,
CFG_420 = 7,
CFG_PSEUDO4 = 9,
CFG_PSEUDO8 = 10,
CFG_SWAPRB = 1 << 4,
CFG_SWAPUV = 1 << 3,
CFG_SWAPYU = 1 << 2,
CFG_YUV2RGB = 1 << 1,
};
/* For LCD_SPU_DMA_CTRL0 */
enum {
CFG_NOBLENDING = 1 << 31,
CFG_GAMMA_ENA = 1 << 30,
CFG_CBSH_ENA = 1 << 29,
CFG_PALETTE_ENA = 1 << 28,
CFG_ARBFAST_ENA = 1 << 27,
CFG_HWC_1BITMOD = 1 << 26,
CFG_HWC_1BITENA = 1 << 25,
CFG_HWC_ENA = 1 << 24,
CFG_DMAFORMAT = 0xf << 20,
#define CFG_DMA_FMT(x) ((x) << 20)
CFG_GRAFORMAT = 0xf << 16,
#define CFG_GRA_FMT(x) ((x) << 16)
#define CFG_GRA_MOD(x) ((x) << 8)
CFG_GRA_FTOGGLE = 1 << 15,
CFG_GRA_HSMOOTH = 1 << 14,
CFG_GRA_TSTMODE = 1 << 13,
CFG_GRA_ENA = 1 << 8,
#define CFG_DMA_MOD(x) ((x) << 0)
CFG_DMA_FTOGGLE = 1 << 7,
CFG_DMA_HSMOOTH = 1 << 6,
CFG_DMA_TSTMODE = 1 << 5,
CFG_DMA_ENA = 1 << 0,
};
enum {
CKMODE_DISABLE = 0,
CKMODE_Y = 1,
CKMODE_U = 2,
CKMODE_RGB = 3,
CKMODE_V = 4,
CKMODE_R = 5,
CKMODE_G = 6,
CKMODE_B = 7,
};
/* For LCD_SPU_DMA_CTRL1 */
enum {
CFG_FRAME_TRIG = 1 << 31,
CFG_VSYNC_INV = 1 << 27,
CFG_CKMODE_MASK = 0x7 << 24,
#define CFG_CKMODE(x) ((x) << 24)
CFG_CARRY = 1 << 23,
CFG_GATED_CLK = 1 << 21,
CFG_PWRDN_ENA = 1 << 20,
CFG_DSCALE_MASK = 0x3 << 18,
CFG_DSCALE_NONE = 0x0 << 18,
CFG_DSCALE_HALF = 0x1 << 18,
CFG_DSCALE_QUAR = 0x2 << 18,
CFG_ALPHAM_MASK = 0x3 << 16,
CFG_ALPHAM_VIDEO = 0x0 << 16,
CFG_ALPHAM_GRA = 0x1 << 16,
CFG_ALPHAM_CFG = 0x2 << 16,
CFG_ALPHA_MASK = 0xff << 8,
CFG_PIXCMD_MASK = 0xff,
};
/* For LCD_SPU_SRAM_CTRL */
enum {
SRAM_READ = 0 << 14,
SRAM_WRITE = 2 << 14,
SRAM_INIT = 3 << 14,
SRAM_HWC32_RAMR = 0xc << 8,
SRAM_HWC32_RAMG = 0xd << 8,
SRAM_HWC32_RAMB = 0xe << 8,
SRAM_HWC32_TRAN = 0xf << 8,
SRAM_HWC = 0xf << 8,
};
/* For LCD_SPU_SRAM_PARA1 */
enum {
CFG_CSB_256x32 = 1 << 15, /* cursor */
CFG_CSB_256x24 = 1 << 14, /* palette */
CFG_CSB_256x8 = 1 << 13, /* gamma */
CFG_PDWN1920x32 = 1 << 8, /* Armada 510: power down vscale ram */
CFG_PDWN256x32 = 1 << 7, /* power down cursor */
CFG_PDWN256x24 = 1 << 6, /* power down palette */
CFG_PDWN256x8 = 1 << 5, /* power down gamma */
CFG_PDWNHWC = 1 << 4, /* Armada 510: power down all hwc ram */
CFG_PDWN32x32 = 1 << 3, /* power down slave->smart ram */
CFG_PDWN16x66 = 1 << 2, /* power down UV fifo */
CFG_PDWN32x66 = 1 << 1, /* power down Y fifo */
CFG_PDWN64x66 = 1 << 0, /* power down graphic fifo */
};
/* For LCD_CFG_SCLK_DIV */
enum {
/* Armada 510 */
SCLK_510_AXI = 0x0 << 30,
SCLK_510_EXTCLK0 = 0x1 << 30,
SCLK_510_PLL = 0x2 << 30,
SCLK_510_EXTCLK1 = 0x3 << 30,
SCLK_510_DIV_CHANGE = 1 << 29,
SCLK_510_FRAC_DIV_MASK = 0xfff << 16,
SCLK_510_INT_DIV_MASK = 0xffff << 0,
/* Armada 16x */
SCLK_16X_AHB = 0x0 << 28,
SCLK_16X_PCLK = 0x1 << 28,
SCLK_16X_AXI = 0x4 << 28,
SCLK_16X_PLL = 0x8 << 28,
SCLK_16X_FRAC_DIV_MASK = 0xfff << 16,
SCLK_16X_INT_DIV_MASK = 0xffff << 0,
};
/* For LCD_SPU_DUMB_CTRL */
enum {
DUMB16_RGB565_0 = 0x0 << 28,
DUMB16_RGB565_1 = 0x1 << 28,
DUMB18_RGB666_0 = 0x2 << 28,
DUMB18_RGB666_1 = 0x3 << 28,
DUMB12_RGB444_0 = 0x4 << 28,
DUMB12_RGB444_1 = 0x5 << 28,
DUMB24_RGB888_0 = 0x6 << 28,
DUMB_BLANK = 0x7 << 28,
DUMB_MASK = 0xf << 28,
CFG_BIAS_OUT = 1 << 8,
CFG_REV_RGB = 1 << 7,
CFG_INV_CBLANK = 1 << 6,
CFG_INV_CSYNC = 1 << 5, /* Normally active high */
CFG_INV_HENA = 1 << 4,
CFG_INV_VSYNC = 1 << 3, /* Normally active high */
CFG_INV_HSYNC = 1 << 2, /* Normally active high */
CFG_INV_PCLK = 1 << 1,
CFG_DUMB_ENA = 1 << 0,
};
/* For LCD_SPU_IOPAD_CONTROL */
enum {
CFG_VSCALE_LN_EN = 3 << 18,
CFG_GRA_VM_ENA = 1 << 15,
CFG_DMA_VM_ENA = 1 << 13,
CFG_CMD_VM_ENA = 1 << 11,
CFG_CSC_MASK = 3 << 8,
CFG_CSC_YUV_CCIR709 = 1 << 9,
CFG_CSC_YUV_CCIR601 = 0 << 9,
CFG_CSC_RGB_STUDIO = 1 << 8,
CFG_CSC_RGB_COMPUTER = 0 << 8,
CFG_IOPAD_MASK = 0xf << 0,
CFG_IOPAD_DUMB24 = 0x0 << 0,
CFG_IOPAD_DUMB18SPI = 0x1 << 0,
CFG_IOPAD_DUMB18GPIO = 0x2 << 0,
CFG_IOPAD_DUMB16SPI = 0x3 << 0,
CFG_IOPAD_DUMB16GPIO = 0x4 << 0,
CFG_IOPAD_DUMB12GPIO = 0x5 << 0,
CFG_IOPAD_SMART18 = 0x6 << 0,
CFG_IOPAD_SMART16 = 0x7 << 0,
CFG_IOPAD_SMART8 = 0x8 << 0,
};
#define IOPAD_DUMB24 0x0
/* For LCD_SPU_IRQ_ENA */
enum {
DMA_FRAME_IRQ0_ENA = 1 << 31,
DMA_FRAME_IRQ1_ENA = 1 << 30,
DMA_FRAME_IRQ_ENA = DMA_FRAME_IRQ0_ENA | DMA_FRAME_IRQ1_ENA,
DMA_FF_UNDERFLOW_ENA = 1 << 29,
GRA_FRAME_IRQ0_ENA = 1 << 27,
GRA_FRAME_IRQ1_ENA = 1 << 26,
GRA_FRAME_IRQ_ENA = GRA_FRAME_IRQ0_ENA | GRA_FRAME_IRQ1_ENA,
GRA_FF_UNDERFLOW_ENA = 1 << 25,
VSYNC_IRQ_ENA = 1 << 23,
DUMB_FRAMEDONE_ENA = 1 << 22,
TWC_FRAMEDONE_ENA = 1 << 21,
HWC_FRAMEDONE_ENA = 1 << 20,
SLV_IRQ_ENA = 1 << 19,
SPI_IRQ_ENA = 1 << 18,
PWRDN_IRQ_ENA = 1 << 17,
ERR_IRQ_ENA = 1 << 16,
CLEAN_SPU_IRQ_ISR = 0xffff,
};
/* For LCD_SPU_IRQ_ISR */
enum {
DMA_FRAME_IRQ0 = 1 << 31,
DMA_FRAME_IRQ1 = 1 << 30,
DMA_FRAME_IRQ = DMA_FRAME_IRQ0 | DMA_FRAME_IRQ1,
DMA_FF_UNDERFLOW = 1 << 29,
GRA_FRAME_IRQ0 = 1 << 27,
GRA_FRAME_IRQ1 = 1 << 26,
GRA_FRAME_IRQ = GRA_FRAME_IRQ0 | GRA_FRAME_IRQ1,
GRA_FF_UNDERFLOW = 1 << 25,
VSYNC_IRQ = 1 << 23,
DUMB_FRAMEDONE = 1 << 22,
TWC_FRAMEDONE = 1 << 21,
HWC_FRAMEDONE = 1 << 20,
SLV_IRQ = 1 << 19,
SPI_IRQ = 1 << 18,
PWRDN_IRQ = 1 << 17,
ERR_IRQ = 1 << 16,
DMA_FRAME_IRQ0_LEVEL = 1 << 15,
DMA_FRAME_IRQ1_LEVEL = 1 << 14,
DMA_FRAME_CNT_ISR = 3 << 12,
GRA_FRAME_IRQ0_LEVEL = 1 << 11,
GRA_FRAME_IRQ1_LEVEL = 1 << 10,
GRA_FRAME_CNT_ISR = 3 << 8,
VSYNC_IRQ_LEVEL = 1 << 7,
DUMB_FRAMEDONE_LEVEL = 1 << 6,
TWC_FRAMEDONE_LEVEL = 1 << 5,
HWC_FRAMEDONE_LEVEL = 1 << 4,
SLV_FF_EMPTY = 1 << 3,
DMA_FF_ALLEMPTY = 1 << 2,
GRA_FF_ALLEMPTY = 1 << 1,
PWRDN_IRQ_LEVEL = 1 << 0,
};
#endif
/*
* Copyright (C) 2012 Russell King
*
* 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 ARMADA_IOCTLP_H
#define ARMADA_IOCTLP_H
#define ARMADA_IOCTL_PROTO(name)\
extern int armada_##name##_ioctl(struct drm_device *, void *, struct drm_file *)
ARMADA_IOCTL_PROTO(gem_create);
ARMADA_IOCTL_PROTO(gem_mmap);
ARMADA_IOCTL_PROTO(gem_pwrite);
#endif
/*
* Copyright (C) 2012 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_encoder_slave.h>
#include "armada_output.h"
#include "armada_drm.h"
struct armada_connector {
struct drm_connector conn;
const struct armada_output_type *type;
};
#define drm_to_armada_conn(c) container_of(c, struct armada_connector, conn)
struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn)
{
struct drm_encoder *enc = conn->encoder;
return enc ? enc : drm_encoder_find(conn->dev, conn->encoder_ids[0]);
}
static enum drm_connector_status armada_drm_connector_detect(
struct drm_connector *conn, bool force)
{
struct armada_connector *dconn = drm_to_armada_conn(conn);
enum drm_connector_status status = connector_status_disconnected;
if (dconn->type->detect) {
status = dconn->type->detect(conn, force);
} else {
struct drm_encoder *enc = armada_drm_connector_encoder(conn);
if (enc)
status = encoder_helper_funcs(enc)->detect(enc, conn);
}
return status;
}
static void armada_drm_connector_destroy(struct drm_connector *conn)
{
struct armada_connector *dconn = drm_to_armada_conn(conn);
drm_sysfs_connector_remove(conn);
drm_connector_cleanup(conn);
kfree(dconn);
}
static int armada_drm_connector_set_property(struct drm_connector *conn,
struct drm_property *property, uint64_t value)
{
struct armada_connector *dconn = drm_to_armada_conn(conn);
if (!dconn->type->set_property)
return -EINVAL;
return dconn->type->set_property(conn, property, value);
}
static const struct drm_connector_funcs armada_drm_conn_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = armada_drm_connector_detect,
.destroy = armada_drm_connector_destroy,
.set_property = armada_drm_connector_set_property,
};
void armada_drm_encoder_prepare(struct drm_encoder *encoder)
{
encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_OFF);
}
void armada_drm_encoder_commit(struct drm_encoder *encoder)
{
encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_ON);
}
bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode, struct drm_display_mode *adjusted)
{
return true;
}
/* Shouldn't this be a generic helper function? */
int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
struct drm_display_mode *mode)
{
struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
int valid = MODE_BAD;
if (encoder) {
struct drm_encoder_slave *slave = to_encoder_slave(encoder);
valid = slave->slave_funcs->mode_valid(encoder, mode);
}
return valid;
}
int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
struct drm_property *property, uint64_t value)
{
struct drm_encoder *encoder = armada_drm_connector_encoder(conn);
int rc = -EINVAL;
if (encoder) {
struct drm_encoder_slave *slave = to_encoder_slave(encoder);
rc = slave->slave_funcs->set_property(encoder, conn, property,
value);
}
return rc;
}
int armada_output_create(struct drm_device *dev,
const struct armada_output_type *type, const void *data)
{
struct armada_connector *dconn;
int ret;
dconn = kzalloc(sizeof(*dconn), GFP_KERNEL);
if (!dconn)
return -ENOMEM;
dconn->type = type;
ret = drm_connector_init(dev, &dconn->conn, &armada_drm_conn_funcs,
type->connector_type);
if (ret) {
DRM_ERROR("unable to init connector\n");
goto err_destroy_dconn;
}
ret = type->create(&dconn->conn, data);
if (ret)
goto err_conn;
ret = drm_sysfs_connector_add(&dconn->conn);
if (ret)
goto err_sysfs;
return 0;
err_sysfs:
if (dconn->conn.encoder)
dconn->conn.encoder->funcs->destroy(dconn->conn.encoder);
err_conn:
drm_connector_cleanup(&dconn->conn);
err_destroy_dconn:
kfree(dconn);
return ret;
}
/*
* Copyright (C) 2012 Russell King
*
* 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 ARMADA_CONNETOR_H
#define ARMADA_CONNETOR_H
#define encoder_helper_funcs(encoder) \
((struct drm_encoder_helper_funcs *)encoder->helper_private)
struct armada_output_type {
int connector_type;
enum drm_connector_status (*detect)(struct drm_connector *, bool);
int (*create)(struct drm_connector *, const void *);
int (*set_property)(struct drm_connector *, struct drm_property *,
uint64_t);
};
struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn);
void armada_drm_encoder_prepare(struct drm_encoder *encoder);
void armada_drm_encoder_commit(struct drm_encoder *encoder);
bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode, struct drm_display_mode *adj);
int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn,
struct drm_display_mode *mode);
int armada_drm_slave_encoder_set_property(struct drm_connector *conn,
struct drm_property *property, uint64_t value);
int armada_output_create(struct drm_device *dev,
const struct armada_output_type *type, const void *data);
#endif
This diff is collapsed.
/*
* Copyright (C) 2012 Russell King
* Rewritten from the dovefb driver, and Armada510 manuals.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_encoder_slave.h>
#include "armada_drm.h"
#include "armada_output.h"
#include "armada_slave.h"
static int armada_drm_slave_get_modes(struct drm_connector *conn)
{
struct drm_encoder *enc = armada_drm_connector_encoder(conn);
int count = 0;
if (enc) {
struct drm_encoder_slave *slave = to_encoder_slave(enc);
count = slave->slave_funcs->get_modes(enc, conn);
}
return count;
}
static void armada_drm_slave_destroy(struct drm_encoder *enc)
{
struct drm_encoder_slave *slave = to_encoder_slave(enc);
struct i2c_client *client = drm_i2c_encoder_get_client(enc);
if (slave->slave_funcs)
slave->slave_funcs->destroy(enc);
if (client)
i2c_put_adapter(client->adapter);
drm_encoder_cleanup(&slave->base);
kfree(slave);
}
static const struct drm_encoder_funcs armada_drm_slave_encoder_funcs = {
.destroy = armada_drm_slave_destroy,
};
static const struct drm_connector_helper_funcs armada_drm_slave_helper_funcs = {
.get_modes = armada_drm_slave_get_modes,
.mode_valid = armada_drm_slave_encoder_mode_valid,
.best_encoder = armada_drm_connector_encoder,
};
static const struct drm_encoder_helper_funcs drm_slave_encoder_helpers = {
.dpms = drm_i2c_encoder_dpms,
.save = drm_i2c_encoder_save,
.restore = drm_i2c_encoder_restore,
.mode_fixup = drm_i2c_encoder_mode_fixup,
.prepare = drm_i2c_encoder_prepare,
.commit = drm_i2c_encoder_commit,
.mode_set = drm_i2c_encoder_mode_set,
.detect = drm_i2c_encoder_detect,
};
static int
armada_drm_conn_slave_create(struct drm_connector *conn, const void *data)
{
const struct armada_drm_slave_config *config = data;
struct drm_encoder_slave *slave;
struct i2c_adapter *adap;
int ret;
conn->interlace_allowed = config->interlace_allowed;
conn->doublescan_allowed = config->doublescan_allowed;
conn->polled = config->polled;
drm_connector_helper_add(conn, &armada_drm_slave_helper_funcs);
slave = kzalloc(sizeof(*slave), GFP_KERNEL);
if (!slave)
return -ENOMEM;
slave->base.possible_crtcs = config->crtcs;
adap = i2c_get_adapter(config->i2c_adapter_id);
if (!adap) {
kfree(slave);
return -EPROBE_DEFER;
}
ret = drm_encoder_init(conn->dev, &slave->base,
&armada_drm_slave_encoder_funcs,
DRM_MODE_ENCODER_TMDS);
if (ret) {
DRM_ERROR("unable to init encoder\n");
i2c_put_adapter(adap);
kfree(slave);
return ret;
}
ret = drm_i2c_encoder_init(conn->dev, slave, adap, &config->info);
i2c_put_adapter(adap);
if (ret) {
DRM_ERROR("unable to init encoder slave\n");
armada_drm_slave_destroy(&slave->base);
return ret;
}
drm_encoder_helper_add(&slave->base, &drm_slave_encoder_helpers);
ret = slave->slave_funcs->create_resources(&slave->base, conn);
if (ret) {
armada_drm_slave_destroy(&slave->base);
return ret;
}
ret = drm_mode_connector_attach_encoder(conn, &slave->base);
if (ret) {
armada_drm_slave_destroy(&slave->base);
return ret;
}
conn->encoder = &slave->base;
return ret;
}
static const struct armada_output_type armada_drm_conn_slave = {
.connector_type = DRM_MODE_CONNECTOR_HDMIA,
.create = armada_drm_conn_slave_create,
.set_property = armada_drm_slave_encoder_set_property,
};
int armada_drm_connector_slave_create(struct drm_device *dev,
const struct armada_drm_slave_config *config)
{
return armada_output_create(dev, &armada_drm_conn_slave, config);
}
/*
* Copyright (C) 2012 Russell King
*
* 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 ARMADA_SLAVE_H
#define ARMADA_SLAVE_H
#include <linux/i2c.h>
#include <drm/drmP.h>
struct armada_drm_slave_config {
int i2c_adapter_id;
uint32_t crtcs;
uint8_t polled;
bool interlace_allowed;
bool doublescan_allowed;
struct i2c_board_info info;
};
int armada_drm_connector_slave_create(struct drm_device *dev,
const struct armada_drm_slave_config *);
#endif
......@@ -1135,4 +1135,21 @@ extern int drm_format_horz_chroma_subsampling(uint32_t format);
extern int drm_format_vert_chroma_subsampling(uint32_t format);
extern const char *drm_get_format_name(uint32_t format);
/* Helpers */
static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev,
uint32_t id)
{
struct drm_mode_object *mo;
mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_CRTC);
return mo ? obj_to_crtc(mo) : NULL;
}
static inline struct drm_encoder *drm_encoder_find(struct drm_device *dev,
uint32_t id)
{
struct drm_mode_object *mo;
mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER);
return mo ? obj_to_encoder(mo) : NULL;
}
#endif /* __DRM_CRTC_H__ */
/*
* Copyright (C) 2012 Russell King
* With inspiration from the i915 driver
*
* 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 DRM_ARMADA_IOCTL_H
#define DRM_ARMADA_IOCTL_H
#define DRM_ARMADA_GEM_CREATE 0x00
#define DRM_ARMADA_GEM_MMAP 0x02
#define DRM_ARMADA_GEM_PWRITE 0x03
#define ARMADA_IOCTL(dir, name, str) \
DRM_##dir(DRM_COMMAND_BASE + DRM_ARMADA_##name, struct drm_armada_##str)
struct drm_armada_gem_create {
uint32_t handle;
uint32_t size;
};
#define DRM_IOCTL_ARMADA_GEM_CREATE \
ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create)
struct drm_armada_gem_mmap {
uint32_t handle;
uint32_t pad;
uint64_t offset;
uint64_t size;
uint64_t addr;
};
#define DRM_IOCTL_ARMADA_GEM_MMAP \
ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap)
struct drm_armada_gem_pwrite {
uint64_t ptr;
uint32_t handle;
uint32_t offset;
uint32_t size;
};
#define DRM_IOCTL_ARMADA_GEM_PWRITE \
ARMADA_IOCTL(IOW, GEM_PWRITE, gem_pwrite)
#endif
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