422 lines
10 KiB
C
422 lines
10 KiB
C
/*
|
|
* cursor.c: Function required to implement cursor interface.
|
|
*
|
|
* Copyright (c) 2011-2019, NVIDIA CORPORATION, All rights reserved.
|
|
*
|
|
* Author:
|
|
* Robert Morell <rmorell@nvidia.com>
|
|
* Jon Mayo <jmayo@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 <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/mutex.h>
|
|
|
|
#include "dc_priv.h"
|
|
#include "dc_reg.h"
|
|
|
|
/* modify val with cursor field set for a given size.
|
|
* ignore val if it is NULL.
|
|
* return non-zero on error, and clear val. */
|
|
static inline int cursor_size_value(enum tegra_dc_cursor_size size, u32 *val)
|
|
{
|
|
u32 scratch = 0;
|
|
|
|
if (!val)
|
|
val = &scratch;
|
|
switch (size) {
|
|
case TEGRA_DC_CURSOR_SIZE_32X32:
|
|
*val |= CURSOR_SIZE_32;
|
|
return 0;
|
|
case TEGRA_DC_CURSOR_SIZE_64X64:
|
|
*val |= CURSOR_SIZE_64;
|
|
return 0;
|
|
case TEGRA_DC_CURSOR_SIZE_128X128:
|
|
*val |= CURSOR_SIZE_128;
|
|
return 0;
|
|
case TEGRA_DC_CURSOR_SIZE_256X256:
|
|
*val |= CURSOR_SIZE_256;
|
|
return 0;
|
|
}
|
|
*val = 0;
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* modify val with cursor blend format.
|
|
* ignore val if it is NULL.
|
|
* return non-zero on error, and clear val. */
|
|
static inline u32 cursor_blendfmt_value
|
|
(enum tegra_dc_cursor_blend_format blendfmt, u32 *val)
|
|
{
|
|
u32 scratch = 0;
|
|
|
|
if (!val)
|
|
val = &scratch;
|
|
switch (blendfmt) {
|
|
case TEGRA_DC_CURSOR_FORMAT_2BIT_LEGACY:
|
|
/* MODE_SELECT_LEGACY */
|
|
*val |= CURSOR_MODE_SELECT(0);
|
|
return 0;
|
|
case TEGRA_DC_CURSOR_FORMAT_RGBA_NON_PREMULT_ALPHA:
|
|
if (tegra_dc_is_t21x())
|
|
/* MODE_SELECT_NORMAL */
|
|
*val |= CURSOR_MODE_SELECT(1);
|
|
/* K1_TIMES_SRC, NEG_K1_TIMES_SRC */
|
|
*val |= CURSOR_DST_BLEND_FACTOR_SELECT(2);
|
|
*val |= CURSOR_SRC_BLEND_FACTOR_SELECT(1);
|
|
return 0;
|
|
case TEGRA_DC_CURSOR_FORMAT_RGBA_PREMULT_ALPHA:
|
|
*val |= CURSOR_MODE_SELECT(1);
|
|
*val |= CURSOR_DST_BLEND_FACTOR_SELECT(2);
|
|
*val |= CURSOR_SRC_BLEND_FACTOR_SELECT(0);
|
|
return 0;
|
|
case TEGRA_DC_CURSOR_FORMAT_RGBA_XOR:
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
/* MODE_SELECT_NORMAL */
|
|
*val |= CURSOR_COMP_MODE(1);
|
|
/* K1, NEG_K1_TIMES_SRC */
|
|
*val |= CURSOR_DST_BLEND_FACTOR_SELECT(1);
|
|
*val |= CURSOR_SRC_BLEND_FACTOR_SELECT(1);
|
|
return 0;
|
|
}
|
|
pr_err("%s: invalid blend format 0x%08x\n", __func__, blendfmt);
|
|
break;
|
|
default:
|
|
pr_err("%s: invalid blend format 0x%08x\n", __func__, blendfmt);
|
|
break;
|
|
}
|
|
*val = 0;
|
|
return -EINVAL;
|
|
}
|
|
|
|
static inline u32 cursor_alpha_value(struct tegra_dc *dc, u32 *val)
|
|
{
|
|
u32 retval = 0;
|
|
u32 data;
|
|
|
|
if (!val | !dc)
|
|
return -EINVAL;
|
|
|
|
data = *val & (~TEGRA_DC_EXT_CURSOR_FORMAT_ALPHA_MSK);
|
|
|
|
if (dc->cursor.alpha > TEGRA_DC_EXT_CURSOR_FORMAT_ALPHA_MAX)
|
|
return -EINVAL;
|
|
|
|
if (tegra_dc_is_nvdisplay())
|
|
data |= dc->cursor.alpha;
|
|
else
|
|
data |= CURSOR_ALPHA(TEGRA_DC_EXT_CURSOR_FORMAT_ALPHA_MAX);
|
|
|
|
*val = data;
|
|
return retval;
|
|
}
|
|
|
|
static inline u32 cursor_start_address_hi(dma_addr_t phys_addr)
|
|
{
|
|
|
|
/*For nvdisplay arch cursor physical addr size is 40 bits, so mask 8 bits*/
|
|
#define NVDISP_CURSOR_START_ADDR_HI_MASK 0xff
|
|
/*For t21x cursor physical addr size is 34 bits, so mask 2 bits*/
|
|
#define CURSOR_START_ADDR_HI_MASK 0x3
|
|
|
|
u32 hi_addr = 0;
|
|
|
|
if (tegra_dc_is_nvdisplay())
|
|
hi_addr = ((phys_addr >> 32) &
|
|
NVDISP_CURSOR_START_ADDR_HI_MASK);
|
|
else
|
|
hi_addr = ((phys_addr >> 32) &
|
|
CURSOR_START_ADDR_HI_MASK);
|
|
|
|
#undef NVDISP_CURSOR_START_ADDR_HI_MASK
|
|
#undef CURSOR_START_ADDR_HI_MASK
|
|
|
|
return hi_addr;
|
|
}
|
|
|
|
static unsigned int set_cursor_start_addr(struct tegra_dc *dc,
|
|
enum tegra_dc_cursor_size size, dma_addr_t phys_addr)
|
|
{
|
|
u32 val = 0;
|
|
u32 cursor_addr_hi = 0;
|
|
int clip_win;
|
|
|
|
BUG_ON(phys_addr & ~CURSOR_START_ADDR_MASK);
|
|
|
|
dc->cursor.phys_addr = phys_addr;
|
|
dc->cursor.size = size;
|
|
/* this should not fail, as tegra_dc_cursor_image() checks the size */
|
|
if (WARN(cursor_size_value(size, &val), "invalid cursor size."))
|
|
return 0;
|
|
|
|
clip_win = dc->cursor.clip_win;
|
|
val |= CURSOR_CLIP_SHIFT_BITS(clip_win);
|
|
cursor_addr_hi = cursor_start_address_hi(phys_addr);
|
|
|
|
/* TODO: check calculation with HW */
|
|
tegra_dc_writel(dc, cursor_addr_hi,
|
|
DC_DISP_CURSOR_START_ADDR_HI);
|
|
tegra_dc_writel(dc, (u32)(val | CURSOR_START_ADDR_LOW(phys_addr)),
|
|
DC_DISP_CURSOR_START_ADDR);
|
|
|
|
if (tegra_dc_is_nvdisplay())
|
|
WARN_ON((phys_addr & 0x3FF) != 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_cursor_position(struct tegra_dc *dc, s16 x, s16 y)
|
|
{
|
|
if (tegra_dc_is_nvdisplay())
|
|
nvdisp_set_cursor_position(dc, x, y);
|
|
else
|
|
/* todo: Bug 2671921: disable cursor if offscreen */
|
|
tegra_dc_writel(dc, CURSOR_POSITION(x, y,
|
|
H_CURSOR_POSITION_SIZE),
|
|
DC_DISP_CURSOR_POSITION);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_cursor_activation_control(struct tegra_dc *dc)
|
|
{
|
|
if (tegra_dc_is_t21x()) {
|
|
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)
|
|
{
|
|
bool need_general_update = false;
|
|
u32 val = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
|
|
|
|
if (dc->cursor.enabled != enable) {
|
|
val &= ~CURSOR_ENABLE;
|
|
if (enable)
|
|
val |= CURSOR_ENABLE;
|
|
tegra_dc_writel(dc, val, DC_DISP_DISP_WIN_OPTIONS);
|
|
need_general_update = true;
|
|
}
|
|
|
|
dc->cursor.enabled = enable;
|
|
|
|
return need_general_update;
|
|
}
|
|
|
|
static int set_cursor_blend(struct tegra_dc *dc, u32 blendfmt)
|
|
{
|
|
u32 val = tegra_dc_readl(dc, DC_DISP_BLEND_CURSOR_CONTROL);
|
|
u32 newval = WINH_CURS_SELECT(0);
|
|
int ret = 0;
|
|
|
|
/* this should not fail, as tegra_dc_cursor_image() checks the format */
|
|
if (WARN(cursor_blendfmt_value(blendfmt, &newval),
|
|
"invalid cursor blend format"))
|
|
return 0;
|
|
if (WARN(cursor_alpha_value(dc, &newval),
|
|
"invalid cursor alpha"))
|
|
return 0;
|
|
|
|
if (val != newval) {
|
|
tegra_dc_writel(dc, newval, DC_DISP_BLEND_CURSOR_CONTROL);
|
|
ret = 1;
|
|
}
|
|
dc->cursor.blendfmt = blendfmt;
|
|
|
|
if (tegra_dc_is_nvdisplay())
|
|
nvdisp_set_cursor_colorfmt(dc); /* color fmt */
|
|
return ret;
|
|
}
|
|
|
|
static int set_cursor_fg_bg(struct tegra_dc *dc, u32 fg, u32 bg)
|
|
{
|
|
int general_update_needed = 0;
|
|
|
|
if (tegra_dc_is_t21x()) {
|
|
/* TODO: check fg/bg against data structure,
|
|
* don't read the HW.
|
|
*/
|
|
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;
|
|
}
|
|
dc->cursor.fg = fg;
|
|
dc->cursor.bg = bg;
|
|
}
|
|
|
|
return general_update_needed;
|
|
}
|
|
|
|
static void tegra_dc_cursor_do_update(struct tegra_dc *dc,
|
|
bool need_general_update)
|
|
{
|
|
tegra_dc_writel(dc, CURSOR_UPDATE, DC_CMD_STATE_CONTROL);
|
|
tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL);
|
|
if (need_general_update) {
|
|
tegra_dc_writel(dc, GENERAL_UPDATE, DC_CMD_STATE_CONTROL);
|
|
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
|
}
|
|
}
|
|
|
|
static int tegra_dc_cursor_program(struct tegra_dc *dc, bool enable,
|
|
bool wait_for_activation)
|
|
{
|
|
bool need_general_update = false;
|
|
|
|
if (!dc->enabled)
|
|
return 0;
|
|
/* these checks are redundant */
|
|
if (cursor_size_value(dc->cursor.size, NULL))
|
|
return -EINVAL;
|
|
if (cursor_blendfmt_value(dc->cursor.blendfmt, NULL))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&dc->lock);
|
|
if (!dc->cursor.dirty) {
|
|
mutex_unlock(&dc->lock);
|
|
return 0;
|
|
}
|
|
tegra_dc_get(dc);
|
|
|
|
need_general_update |= set_cursor_start_addr(dc, dc->cursor.size,
|
|
dc->cursor.phys_addr);
|
|
|
|
need_general_update |= set_cursor_fg_bg(dc,
|
|
dc->cursor.fg, dc->cursor.bg);
|
|
|
|
need_general_update |= set_cursor_blend(dc, dc->cursor.blendfmt);
|
|
|
|
need_general_update |= set_cursor_enable(dc, enable);
|
|
|
|
need_general_update |= set_cursor_position(dc,
|
|
dc->cursor.x, dc->cursor.y);
|
|
|
|
need_general_update |= set_cursor_activation_control(dc);
|
|
|
|
tegra_dc_cursor_do_update(dc, need_general_update);
|
|
|
|
if (wait_for_activation)
|
|
if (tegra_dc_poll_register(dc, DC_CMD_STATE_CONTROL,
|
|
CURSOR_ACT_REQ, 0, 1,
|
|
TEGRA_DC_POLL_TIMEOUT_MS))
|
|
dev_err(&dc->ndev->dev,
|
|
"dc timeout waiting for cursor act_req\n");
|
|
|
|
tegra_dc_put(dc);
|
|
dc->cursor.dirty = false;
|
|
mutex_unlock(&dc->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tegra_dc_cursor_image(struct tegra_dc *dc,
|
|
enum tegra_dc_cursor_blend_format blendfmt,
|
|
enum tegra_dc_cursor_size size,
|
|
u32 fg, u32 bg, dma_addr_t phys_addr,
|
|
enum tegra_dc_cursor_color_format colorfmt, u32 alpha, u32 flags,
|
|
bool wait_for_activation)
|
|
{
|
|
if (cursor_size_value(size, NULL))
|
|
return -EINVAL;
|
|
if (cursor_blendfmt_value(blendfmt, NULL))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&dc->lock);
|
|
dc->cursor.fg = fg;
|
|
dc->cursor.bg = bg;
|
|
dc->cursor.size = size;
|
|
dc->cursor.blendfmt = blendfmt;
|
|
dc->cursor.colorfmt = colorfmt;
|
|
dc->cursor.phys_addr = phys_addr;
|
|
dc->cursor.dirty = true;
|
|
|
|
if (TEGRA_DC_EXT_CURSOR_COLORFMT_FLAGS(flags) ==
|
|
TEGRA_DC_EXT_CURSOR_COLORFMT_LEGACY) {
|
|
alpha = TEGRA_DC_EXT_CURSOR_FORMAT_ALPHA_MAX;
|
|
if (tegra_dc_is_nvdisplay())
|
|
dc->cursor.colorfmt =
|
|
TEGRA_DC_EXT_CURSOR_COLORFMT_A8R8G8B8;
|
|
else
|
|
dc->cursor.colorfmt =
|
|
TEGRA_DC_EXT_CURSOR_COLORFMT_R8G8B8A8;
|
|
}
|
|
dc->cursor.alpha = alpha;
|
|
mutex_unlock(&dc->lock);
|
|
|
|
return tegra_dc_cursor_program(dc, dc->cursor.enabled,
|
|
wait_for_activation);
|
|
}
|
|
|
|
int tegra_dc_cursor_set(struct tegra_dc *dc, bool enable, int x, int y)
|
|
{
|
|
mutex_lock(&dc->lock);
|
|
dc->cursor.x = x;
|
|
dc->cursor.y = y;
|
|
dc->cursor.dirty = true;
|
|
mutex_unlock(&dc->lock);
|
|
|
|
return tegra_dc_cursor_program(dc, enable, false);
|
|
}
|
|
|
|
/* clip:
|
|
* 0 - display
|
|
* 1 - window A
|
|
* 2 - window B
|
|
* 3 - window C
|
|
*/
|
|
int tegra_dc_cursor_clip(struct tegra_dc *dc, unsigned clip)
|
|
{
|
|
mutex_lock(&dc->lock);
|
|
dc->cursor.clip_win = clip;
|
|
dc->cursor.dirty = true;
|
|
mutex_unlock(&dc->lock);
|
|
|
|
return tegra_dc_cursor_program(dc, dc->cursor.enabled, false);
|
|
}
|
|
|
|
/* disable the cursor on suspend. but leave the state unmodified */
|
|
int tegra_dc_cursor_suspend(struct tegra_dc *dc)
|
|
{
|
|
if (!dc->enabled)
|
|
return 0;
|
|
mutex_lock(&dc->lock);
|
|
tegra_dc_get(dc);
|
|
set_cursor_enable(dc, false);
|
|
tegra_dc_cursor_do_update(dc, true);
|
|
dc->cursor.dirty = true;
|
|
tegra_dc_put(dc);
|
|
mutex_unlock(&dc->lock);
|
|
return 0;
|
|
}
|
|
|
|
/* restore the state */
|
|
int tegra_dc_cursor_resume(struct tegra_dc *dc)
|
|
{
|
|
return tegra_dc_cursor_program(dc, dc->cursor.enabled, false);
|
|
}
|