532 lines
12 KiB
C

/*
* drivers/video/tegra/dc/ext/cursor.c
*
* Copyright (c) 2011-2013, NVIDIA CORPORATION, All rights reserved.
*
* Author: Robert Morell <rmorell@nvidia.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.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <video/tegra_dc_ext.h>
#include "tegra_dc_ext_priv.h"
/* ugh */
#include "../dc_priv.h"
#include "../dc_reg.h"
#include "../dc_config.h"
int tegra_dc_ext_get_cursor(struct tegra_dc_ext_user *user)
{
struct tegra_dc_ext *ext = user->ext;
int ret = 0;
mutex_lock(&ext->cursor.lock);
if (!ext->cursor.user)
ext->cursor.user = user;
else if (ext->cursor.user != user)
ret = -EBUSY;
mutex_unlock(&ext->cursor.lock);
return ret;
}
int tegra_dc_ext_put_cursor(struct tegra_dc_ext_user *user)
{
struct tegra_dc_ext *ext = user->ext;
int ret = 0;
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user == user)
ext->cursor.user = 0;
else
ret = -EACCES;
mutex_unlock(&ext->cursor.lock);
return ret;
}
static unsigned int set_cursor_start_addr(struct tegra_dc *dc,
u32 size, dma_addr_t phys_addr)
{
unsigned long val;
int clip_win;
BUG_ON(phys_addr & ~CURSOR_START_ADDR_MASK);
switch (size) {
case TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_64x64:
val = CURSOR_SIZE_64;
break;
case TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_128x128:
val = CURSOR_SIZE_128;
break;
case TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_256x256:
val = CURSOR_SIZE_256;
break;
default:
val = 0;
}
/* Get the cursor clip window number */
clip_win = CURSOR_CLIP_GET_WINDOW(tegra_dc_readl(dc,
DC_DISP_CURSOR_START_ADDR));
val |= CURSOR_CLIP_SHIFT_BITS(clip_win);
tegra_dc_writel(dc, val | CURSOR_START_ADDR_LOW(phys_addr),
DC_DISP_CURSOR_START_ADDR);
if (tegra_dc_feature_has_cursor_hi_regs(dc)) {
/* TO DO: check calculation with HW */
tegra_dc_writel(dc, CURSOR_START_ADDR_HI(phys_addr),
DC_DISP_CURSOR_START_ADDR_HI);
}
if (is_tegra124()) {
tegra_dc_writel(dc, CURSOR_UPDATE, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL);
return 0;
}
return 1;
}
static int set_cursor_position(struct tegra_dc *dc, s16 x, s16 y)
{
tegra_dc_writel(dc, CURSOR_POSITION(x, y), DC_DISP_CURSOR_POSITION);
if (is_tegra124()) {
tegra_dc_writel(dc, CURSOR_UPDATE, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL);
return 0;
}
return 1;
}
static int set_cursor_activation_control(struct tegra_dc *dc)
{
if (is_tegra124()) {
u32 reg = tegra_dc_readl(dc, DC_CMD_REG_ACT_CONTROL);
if ((reg & (1 << CURSOR_ACT_CNTR_SEL)) ==
(CURSOR_ACT_CNTR_SEL_V << CURSOR_ACT_CNTR_SEL)) {
reg &= ~(1 << CURSOR_ACT_CNTR_SEL);
reg |= (CURSOR_ACT_CNTR_SEL_V << CURSOR_ACT_CNTR_SEL);
tegra_dc_writel(dc, reg, DC_CMD_REG_ACT_CONTROL);
return 1;
}
}
return 0;
}
static int set_cursor_enable(struct tegra_dc *dc, bool enable)
{
u32 val = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
if (!!(val & CURSOR_ENABLE) != enable) {
val &= ~CURSOR_ENABLE;
if (enable)
val |= CURSOR_ENABLE;
tegra_dc_writel(dc, val, DC_DISP_DISP_WIN_OPTIONS);
return 1;
}
return 0;
}
static int set_cursor_blend(struct tegra_dc *dc, u32 format)
{
u32 val = tegra_dc_readl(dc, DC_DISP_BLEND_CURSOR_CONTROL);
u32 newval = WINH_CURS_SELECT(0);
switch (format) {
case TEGRA_DC_EXT_CURSOR_FORMAT_2BIT_LEGACY:
newval |= CURSOR_MODE_SELECT(0);
break;
case TEGRA_DC_EXT_CURSOR_FORMAT_RGBA_NON_PREMULT_ALPHA:
case TEGRA_DC_EXT_CURSOR_FORMAT_RGBA_PREMULT_ALPHA:
newval |= CURSOR_MODE_SELECT(1);
if (is_tegra124()) {
newval |= CURSOR_ALPHA |
CURSOR_DST_BLEND_FACTOR_SELECT(2);
if (format ==
TEGRA_DC_EXT_CURSOR_FORMAT_RGBA_PREMULT_ALPHA)
newval |= CURSOR_SRC_BLEND_FACTOR_SELECT(0);
else
newval |= CURSOR_SRC_BLEND_FACTOR_SELECT(1);
}
break;
}
if (val != newval) {
tegra_dc_writel(dc, newval, DC_DISP_BLEND_CURSOR_CONTROL);
return 1;
}
return 0;
}
static int set_cursor_fg_bg(struct tegra_dc *dc,
struct tegra_dc_ext_cursor_image *args)
{
int general_update_needed = 0;
u32 fg = CURSOR_COLOR(args->foreground.r,
args->foreground.g,
args->foreground.b);
u32 bg = CURSOR_COLOR(args->background.r,
args->background.g,
args->background.b);
if (fg != tegra_dc_readl(dc, DC_DISP_CURSOR_FOREGROUND)) {
tegra_dc_writel(dc, fg, DC_DISP_CURSOR_FOREGROUND);
general_update_needed |= 1;
}
if (bg != tegra_dc_readl(dc, DC_DISP_CURSOR_BACKGROUND)) {
tegra_dc_writel(dc, bg, DC_DISP_CURSOR_BACKGROUND);
general_update_needed |= 1;
}
return general_update_needed;
}
static bool check_cursor_size(struct tegra_dc *dc, u32 size)
{
if (size != TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_32x32 &&
size != TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_64x64 &&
size != TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_128x128 &&
size != TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE_256x256)
return false;
return true;
}
int tegra_dc_ext_set_cursor_image(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor_image *args)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc *dc = ext->dc;
struct tegra_dc_dmabuf *handle, *old_handle;
dma_addr_t phys_addr;
u32 size;
int ret;
int need_general_update = 0;
u32 format = TEGRA_DC_EXT_CURSOR_FORMAT_FLAGS(args->flags);
if (!user->nvmap)
return -EFAULT;
size = TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE(args->flags);
if (!check_cursor_size(dc, size))
return -EINVAL;
switch (format) {
case TEGRA_DC_EXT_CURSOR_FORMAT_2BIT_LEGACY:
break;
case TEGRA_DC_EXT_CURSOR_FORMAT_RGBA_NON_PREMULT_ALPHA:
break;
#if defined(CONFIG_ARCH_TEGRA_124_SOC)
case TEGRA_DC_EXT_CURSOR_FORMAT_RGBA_PREMULT_ALPHA:
break;
#endif
default:
return -EINVAL;
}
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user != user) {
ret = -EACCES;
goto unlock;
}
if (!ext->enabled) {
ret = -ENXIO;
goto unlock;
}
old_handle = ext->cursor.cur_handle;
ret = tegra_dc_ext_pin_window(user, args->buff_id, &handle, &phys_addr);
if (ret)
goto unlock;
ext->cursor.cur_handle = handle;
mutex_lock(&dc->lock);
tegra_dc_get(dc);
need_general_update |= set_cursor_start_addr(dc, size, phys_addr);
need_general_update |= set_cursor_fg_bg(dc, args);
need_general_update |= set_cursor_blend(dc, format);
if (need_general_update) {
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
tegra_dc_put(dc);
/* XXX sync here? */
mutex_unlock(&dc->lock);
mutex_unlock(&ext->cursor.lock);
if (old_handle) {
dma_buf_unmap_attachment(old_handle->attach,
old_handle->sgt, DMA_TO_DEVICE);
dma_buf_detach(old_handle->buf, old_handle->attach);
dma_buf_put(old_handle->buf);
kfree(old_handle);
}
return 0;
unlock:
mutex_unlock(&ext->cursor.lock);
return ret;
}
int tegra_dc_ext_set_cursor(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor *args)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc *dc = ext->dc;
bool enable;
int ret;
int need_general_update = 0;
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user != user) {
ret = -EACCES;
goto unlock;
}
if (!ext->enabled) {
ret = -ENXIO;
goto unlock;
}
enable = !!(args->flags & TEGRA_DC_EXT_CURSOR_FLAGS_VISIBLE);
mutex_lock(&dc->lock);
tegra_dc_get(dc);
need_general_update |= set_cursor_enable(dc, enable);
need_general_update |= set_cursor_position(dc, args->x, args->y);
need_general_update |= set_cursor_activation_control(dc);
if (need_general_update) {
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
/* TODO: need to sync here? hopefully can avoid this, but need to
* figure out interaction w/ rest of GENERAL_ACT_REQ */
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
mutex_unlock(&ext->cursor.lock);
return 0;
unlock:
mutex_unlock(&ext->cursor.lock);
return ret;
}
int tegra_dc_ext_cursor_clip(struct tegra_dc_ext_user *user,
int *args)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc *dc = ext->dc;
int ret;
unsigned long reg_val;
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user != user) {
ret = -EACCES;
goto unlock;
}
if (!ext->enabled) {
ret = -ENXIO;
goto unlock;
}
mutex_lock(&dc->lock);
tegra_dc_get(dc);
reg_val = tegra_dc_readl(dc, DC_DISP_CURSOR_START_ADDR);
reg_val &= ~CURSOR_CLIP_SHIFT_BITS(3); /* Clear out the old value */
tegra_dc_writel(dc, reg_val | CURSOR_CLIP_SHIFT_BITS(*args),
DC_DISP_CURSOR_START_ADDR);
if(is_tegra124()) {
tegra_dc_writel(dc, CURSOR_UPDATE, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL);
}
else {
tegra_dc_writel(dc, GENERAL_UPDATE, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
mutex_unlock(&ext->cursor.lock);
return 0;
unlock:
mutex_unlock(&ext->cursor.lock);
return ret;
}
int tegra_dc_ext_set_cursor_image_low_latency(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor_image *args)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc *dc = ext->dc;
int ret;
int need_general_update = 0;
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user != user) {
ret = -EACCES;
goto unlock;
}
if (!ext->enabled) {
ret = -ENXIO;
goto unlock;
}
mutex_lock(&dc->lock);
tegra_dc_get(dc);
need_general_update |= set_cursor_fg_bg(dc, args);
need_general_update |= set_cursor_blend(dc, !!args->mode);
if (need_general_update) {
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
mutex_unlock(&ext->cursor.lock);
return 0;
unlock:
mutex_unlock(&ext->cursor.lock);
return ret;
}
int tegra_dc_ext_set_cursor_low_latency(struct tegra_dc_ext_user *user,
struct tegra_dc_ext_cursor_image *args)
{
struct tegra_dc_ext *ext = user->ext;
struct tegra_dc *dc = ext->dc;
u32 size;
int ret;
struct tegra_dc_dmabuf *handle, *old_handle;
dma_addr_t phys_addr;
bool enable = !!(args->vis & TEGRA_DC_EXT_CURSOR_FLAGS_VISIBLE);
int need_general_update = 0;
if (!user->nvmap)
return -EFAULT;
size = TEGRA_DC_EXT_CURSOR_IMAGE_FLAGS_SIZE(args->flags);
if (!check_cursor_size(dc, size))
return -EINVAL;
mutex_lock(&ext->cursor.lock);
if (ext->cursor.user != user) {
ret = -EACCES;
goto unlock;
}
if (!ext->enabled) {
ret = -ENXIO;
goto unlock;
}
old_handle = ext->cursor.cur_handle;
ret = tegra_dc_ext_pin_window(user, args->buff_id, &handle, &phys_addr);
if (ret)
goto unlock;
ext->cursor.cur_handle = handle;
mutex_lock(&dc->lock);
tegra_dc_get(dc);
need_general_update |= set_cursor_start_addr(dc, size, phys_addr);
need_general_update |= set_cursor_position(dc, args->x, args->y);
need_general_update |= set_cursor_activation_control(dc);
need_general_update |= set_cursor_enable(dc, enable);
if (need_general_update) {
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
}
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
mutex_unlock(&ext->cursor.lock);
if (old_handle) {
dma_buf_unmap_attachment(old_handle->attach,
old_handle->sgt, DMA_TO_DEVICE);
dma_buf_detach(old_handle->buf, handle->attach);
dma_buf_put(old_handle->buf);
kfree(old_handle);
}
return 0;
unlock:
mutex_unlock(&ext->cursor.lock);
return ret;
}