drm/tegra: dc: Universal plane support

This allows the primary plane and cursor to be exposed as regular
DRM/KMS planes, which is a prerequisite for atomic modesetting and gives
userspace more flexibility over controlling them.

Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
Thierry Reding 2014-10-21 13:51:53 +02:00
parent 03a6056976
commit c7679306a9
1 changed files with 330 additions and 157 deletions

View File

@ -332,11 +332,255 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index,
return 0;
}
static int tegra_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)
static int tegra_window_plane_disable(struct drm_plane *plane)
{
struct tegra_dc *dc = to_tegra_dc(plane->crtc);
struct tegra_plane *p = to_tegra_plane(plane);
u32 value;
if (!plane->crtc)
return 0;
value = WINDOW_A_SELECT << p->index;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER);
value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS);
value &= ~WIN_ENABLE;
tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS);
tegra_dc_window_commit(dc, p->index);
return 0;
}
static void tegra_plane_destroy(struct drm_plane *plane)
{
struct tegra_plane *p = to_tegra_plane(plane);
drm_plane_cleanup(plane);
kfree(p);
}
static const u32 tegra_primary_plane_formats[] = {
DRM_FORMAT_XBGR8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_RGB565,
};
static int tegra_primary_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 tegra_bo *bo = tegra_fb_get_plane(fb, 0);
struct tegra_plane *p = to_tegra_plane(plane);
struct tegra_dc *dc = to_tegra_dc(crtc);
struct tegra_dc_window window;
int err;
memset(&window, 0, sizeof(window));
window.src.x = src_x >> 16;
window.src.y = src_y >> 16;
window.src.w = src_w >> 16;
window.src.h = src_h >> 16;
window.dst.x = crtc_x;
window.dst.y = crtc_y;
window.dst.w = crtc_w;
window.dst.h = crtc_h;
window.format = tegra_dc_format(fb->pixel_format, &window.swap);
window.bits_per_pixel = fb->bits_per_pixel;
window.bottom_up = tegra_fb_is_bottom_up(fb);
err = tegra_fb_get_tiling(fb, &window.tiling);
if (err < 0)
return err;
window.base[0] = bo->paddr + fb->offsets[0];
window.stride[0] = fb->pitches[0];
err = tegra_dc_setup_window(dc, p->index, &window);
if (err < 0)
return err;
return 0;
}
static void tegra_primary_plane_destroy(struct drm_plane *plane)
{
tegra_window_plane_disable(plane);
tegra_plane_destroy(plane);
}
static const struct drm_plane_funcs tegra_primary_plane_funcs = {
.update_plane = tegra_primary_plane_update,
.disable_plane = tegra_window_plane_disable,
.destroy = tegra_primary_plane_destroy,
};
static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm,
struct tegra_dc *dc)
{
struct tegra_plane *plane;
unsigned int num_formats;
const u32 *formats;
int err;
plane = kzalloc(sizeof(*plane), GFP_KERNEL);
if (!plane)
return ERR_PTR(-ENOMEM);
num_formats = ARRAY_SIZE(tegra_primary_plane_formats);
formats = tegra_primary_plane_formats;
err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe,
&tegra_primary_plane_funcs, formats,
num_formats, DRM_PLANE_TYPE_PRIMARY);
if (err < 0) {
kfree(plane);
return ERR_PTR(err);
}
return &plane->base;
}
static const u32 tegra_cursor_plane_formats[] = {
DRM_FORMAT_RGBA8888,
};
static int tegra_cursor_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 tegra_bo *bo = tegra_fb_get_plane(fb, 0);
struct tegra_dc *dc = to_tegra_dc(crtc);
u32 value = CURSOR_CLIP_DISPLAY;
/* scaling not supported for cursor */
if ((src_w >> 16 != crtc_w) || (src_h >> 16 != crtc_h))
return -EINVAL;
/* only square cursors supported */
if (src_w != src_h)
return -EINVAL;
switch (crtc_w) {
case 32:
value |= CURSOR_SIZE_32x32;
break;
case 64:
value |= CURSOR_SIZE_64x64;
break;
case 128:
value |= CURSOR_SIZE_128x128;
break;
case 256:
value |= CURSOR_SIZE_256x256;
break;
default:
return -EINVAL;
}
value |= (bo->paddr >> 10) & 0x3fffff;
tegra_dc_writel(dc, value, DC_DISP_CURSOR_START_ADDR);
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
value = (bo->paddr >> 32) & 0x3;
tegra_dc_writel(dc, value, DC_DISP_CURSOR_START_ADDR_HI);
#endif
/* enable cursor and set blend mode */
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value |= CURSOR_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
value = tegra_dc_readl(dc, DC_DISP_BLEND_CURSOR_CONTROL);
value &= ~CURSOR_DST_BLEND_MASK;
value &= ~CURSOR_SRC_BLEND_MASK;
value |= CURSOR_MODE_NORMAL;
value |= CURSOR_DST_BLEND_NEG_K1_TIMES_SRC;
value |= CURSOR_SRC_BLEND_K1_TIMES_SRC;
value |= CURSOR_ALPHA;
tegra_dc_writel(dc, value, DC_DISP_BLEND_CURSOR_CONTROL);
/* position the cursor */
value = (crtc_y & 0x3fff) << 16 | (crtc_x & 0x3fff);
tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION);
/* apply changes */
tegra_dc_cursor_commit(dc);
tegra_dc_commit(dc);
return 0;
}
static int tegra_cursor_plane_disable(struct drm_plane *plane)
{
struct tegra_dc *dc = to_tegra_dc(plane->crtc);
u32 value;
if (!plane->crtc)
return 0;
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value &= ~CURSOR_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
tegra_dc_cursor_commit(dc);
tegra_dc_commit(dc);
return 0;
}
static const struct drm_plane_funcs tegra_cursor_plane_funcs = {
.update_plane = tegra_cursor_plane_update,
.disable_plane = tegra_cursor_plane_disable,
.destroy = tegra_plane_destroy,
};
static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm,
struct tegra_dc *dc)
{
struct tegra_plane *plane;
unsigned int num_formats;
const u32 *formats;
int err;
plane = kzalloc(sizeof(*plane), GFP_KERNEL);
if (!plane)
return ERR_PTR(-ENOMEM);
num_formats = ARRAY_SIZE(tegra_cursor_plane_formats);
formats = tegra_cursor_plane_formats;
err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe,
&tegra_cursor_plane_funcs, formats,
num_formats, DRM_PLANE_TYPE_CURSOR);
if (err < 0) {
kfree(plane);
return ERR_PTR(err);
}
return &plane->base;
}
static int tegra_overlay_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 tegra_plane *p = to_tegra_plane(plane);
struct tegra_dc *dc = to_tegra_dc(crtc);
@ -382,43 +626,19 @@ static int tegra_plane_update(struct drm_plane *plane, struct drm_crtc *crtc,
return tegra_dc_setup_window(dc, p->index, &window);
}
static int tegra_plane_disable(struct drm_plane *plane)
static void tegra_overlay_plane_destroy(struct drm_plane *plane)
{
struct tegra_dc *dc = to_tegra_dc(plane->crtc);
struct tegra_plane *p = to_tegra_plane(plane);
unsigned long value;
if (!plane->crtc)
return 0;
value = WINDOW_A_SELECT << p->index;
tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER);
value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS);
value &= ~WIN_ENABLE;
tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS);
tegra_dc_window_commit(dc, p->index);
return 0;
tegra_window_plane_disable(plane);
tegra_plane_destroy(plane);
}
static void tegra_plane_destroy(struct drm_plane *plane)
{
struct tegra_plane *p = to_tegra_plane(plane);
tegra_plane_disable(plane);
drm_plane_cleanup(plane);
kfree(p);
}
static const struct drm_plane_funcs tegra_plane_funcs = {
.update_plane = tegra_plane_update,
.disable_plane = tegra_plane_disable,
.destroy = tegra_plane_destroy,
static const struct drm_plane_funcs tegra_overlay_plane_funcs = {
.update_plane = tegra_overlay_plane_update,
.disable_plane = tegra_window_plane_disable,
.destroy = tegra_overlay_plane_destroy,
};
static const uint32_t plane_formats[] = {
static const uint32_t tegra_overlay_plane_formats[] = {
DRM_FORMAT_XBGR8888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_RGB565,
@ -428,27 +648,44 @@ static const uint32_t plane_formats[] = {
DRM_FORMAT_YUV422,
};
static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm,
struct tegra_dc *dc,
unsigned int index)
{
struct tegra_plane *plane;
unsigned int num_formats;
const u32 *formats;
int err;
plane = kzalloc(sizeof(*plane), GFP_KERNEL);
if (!plane)
return ERR_PTR(-ENOMEM);
plane->index = index;
num_formats = ARRAY_SIZE(tegra_overlay_plane_formats);
formats = tegra_overlay_plane_formats;
err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe,
&tegra_overlay_plane_funcs, formats,
num_formats, DRM_PLANE_TYPE_OVERLAY);
if (err < 0) {
kfree(plane);
return ERR_PTR(err);
}
return &plane->base;
}
static int tegra_dc_add_planes(struct drm_device *drm, struct tegra_dc *dc)
{
struct drm_plane *plane;
unsigned int i;
int err = 0;
for (i = 0; i < 2; i++) {
struct tegra_plane *plane;
plane = kzalloc(sizeof(*plane), GFP_KERNEL);
if (!plane)
return -ENOMEM;
plane->index = 1 + i;
err = drm_plane_init(drm, &plane->base, 1 << dc->pipe,
&tegra_plane_funcs, plane_formats,
ARRAY_SIZE(plane_formats), false);
if (err < 0) {
kfree(plane);
return err;
}
plane = tegra_dc_overlay_plane_create(drm, dc, 1 + i);
if (IS_ERR(plane))
return PTR_ERR(plane);
}
return 0;
@ -568,103 +805,6 @@ void tegra_dc_disable_vblank(struct tegra_dc *dc)
spin_unlock_irqrestore(&dc->lock, flags);
}
static int tegra_dc_cursor_set2(struct drm_crtc *crtc, struct drm_file *file,
uint32_t handle, uint32_t width,
uint32_t height, int32_t hot_x, int32_t hot_y)
{
unsigned long value = CURSOR_CLIP_DISPLAY;
struct tegra_dc *dc = to_tegra_dc(crtc);
struct drm_gem_object *gem;
struct tegra_bo *bo = NULL;
if (!dc->soc->supports_cursor)
return -ENXIO;
if (width != height)
return -EINVAL;
switch (width) {
case 32:
value |= CURSOR_SIZE_32x32;
break;
case 64:
value |= CURSOR_SIZE_64x64;
break;
case 128:
value |= CURSOR_SIZE_128x128;
case 256:
value |= CURSOR_SIZE_256x256;
break;
default:
return -EINVAL;
}
if (handle) {
gem = drm_gem_object_lookup(crtc->dev, file, handle);
if (!gem)
return -ENOENT;
bo = to_tegra_bo(gem);
}
if (bo) {
unsigned long addr = (bo->paddr & 0xfffffc00) >> 10;
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
unsigned long high = (bo->paddr & 0xfffffffc) >> 32;
#endif
tegra_dc_writel(dc, value | addr, DC_DISP_CURSOR_START_ADDR);
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
tegra_dc_writel(dc, high, DC_DISP_CURSOR_START_ADDR_HI);
#endif
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value |= CURSOR_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
value = tegra_dc_readl(dc, DC_DISP_BLEND_CURSOR_CONTROL);
value &= ~CURSOR_DST_BLEND_MASK;
value &= ~CURSOR_SRC_BLEND_MASK;
value |= CURSOR_MODE_NORMAL;
value |= CURSOR_DST_BLEND_NEG_K1_TIMES_SRC;
value |= CURSOR_SRC_BLEND_K1_TIMES_SRC;
value |= CURSOR_ALPHA;
tegra_dc_writel(dc, value, DC_DISP_BLEND_CURSOR_CONTROL);
} else {
value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
value &= ~CURSOR_ENABLE;
tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS);
}
tegra_dc_cursor_commit(dc);
tegra_dc_commit(dc);
return 0;
}
static int tegra_dc_cursor_move(struct drm_crtc *crtc, int x, int y)
{
struct tegra_dc *dc = to_tegra_dc(crtc);
unsigned long value;
if (!dc->soc->supports_cursor)
return -ENXIO;
value = ((y & 0x3fff) << 16) | (x & 0x3fff);
tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION);
tegra_dc_cursor_commit(dc);
/* XXX: only required on generations earlier than Tegra124? */
tegra_dc_commit(dc);
return 0;
}
static void tegra_dc_finish_page_flip(struct tegra_dc *dc)
{
struct drm_device *drm = dc->base.dev;
@ -741,8 +881,6 @@ static void tegra_dc_destroy(struct drm_crtc *crtc)
}
static const struct drm_crtc_funcs tegra_crtc_funcs = {
.cursor_set2 = tegra_dc_cursor_set2,
.cursor_move = tegra_dc_cursor_move,
.page_flip = tegra_dc_page_flip,
.set_config = drm_crtc_helper_set_config,
.destroy = tegra_dc_destroy,
@ -756,7 +894,7 @@ static void tegra_crtc_disable(struct drm_crtc *crtc)
drm_for_each_legacy_plane(plane, &drm->mode_config.plane_list) {
if (plane->crtc == crtc) {
tegra_plane_disable(plane);
tegra_window_plane_disable(plane);
plane->crtc = NULL;
if (plane->fb) {
@ -767,6 +905,7 @@ static void tegra_crtc_disable(struct drm_crtc *crtc)
}
drm_vblank_off(drm, dc->pipe);
tegra_dc_commit(dc);
}
static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc,
@ -1293,6 +1432,8 @@ static int tegra_dc_init(struct host1x_client *client)
struct drm_device *drm = dev_get_drvdata(client->parent);
struct tegra_dc *dc = host1x_client_to_dc(client);
struct tegra_drm *tegra = drm->dev_private;
struct drm_plane *primary = NULL;
struct drm_plane *cursor = NULL;
int err;
if (tegra->domain) {
@ -1306,7 +1447,25 @@ static int tegra_dc_init(struct host1x_client *client)
dc->domain = tegra->domain;
}
drm_crtc_init(drm, &dc->base, &tegra_crtc_funcs);
primary = tegra_dc_primary_plane_create(drm, dc);
if (IS_ERR(primary)) {
err = PTR_ERR(primary);
goto cleanup;
}
if (dc->soc->supports_cursor) {
cursor = tegra_dc_cursor_plane_create(drm, dc);
if (IS_ERR(cursor)) {
err = PTR_ERR(cursor);
goto cleanup;
}
}
err = drm_crtc_init_with_planes(drm, &dc->base, primary, cursor,
&tegra_crtc_funcs);
if (err < 0)
goto cleanup;
drm_mode_crtc_set_gamma_size(&dc->base, 256);
drm_crtc_helper_add(&dc->base, &tegra_crtc_helper_funcs);
@ -1320,12 +1479,12 @@ static int tegra_dc_init(struct host1x_client *client)
err = tegra_dc_rgb_init(drm, dc);
if (err < 0 && err != -ENODEV) {
dev_err(dc->dev, "failed to initialize RGB output: %d\n", err);
return err;
goto cleanup;
}
err = tegra_dc_add_planes(drm, dc);
if (err < 0)
return err;
goto cleanup;
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
err = tegra_dc_debugfs_init(dc, drm->primary);
@ -1338,10 +1497,24 @@ static int tegra_dc_init(struct host1x_client *client)
if (err < 0) {
dev_err(dc->dev, "failed to request IRQ#%u: %d\n", dc->irq,
err);
return err;
goto cleanup;
}
return 0;
cleanup:
if (cursor)
drm_plane_cleanup(cursor);
if (primary)
drm_plane_cleanup(primary);
if (tegra->domain) {
iommu_detach_device(tegra->domain, dc->dev);
dc->domain = NULL;
}
return err;
}
static int tegra_dc_exit(struct host1x_client *client)