tegrakernel/kernel/nvidia/drivers/video/tegra/dc/window.c

1176 lines
33 KiB
C
Raw Normal View History

2022-02-16 09:13:02 -06:00
/*
* window.c: tegra dc window interface.
*
* Copyright (C) 2010 Google, Inc.
*
* Copyright (c) 2010-2020, NVIDIA CORPORATION, All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/err.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/export.h>
#include <linux/delay.h>
#include <soc/tegra/fuse.h>
#include <trace/events/display.h>
#include <linux/fb.h>
#include <linux/version.h>
#include "dc.h"
#include "dc_reg.h"
#include "dc_config.h"
#include "dc_priv.h"
#include "dc_common.h"
int no_vsync;
module_param_named(no_vsync, no_vsync, int, 0644);
static bool tegra_dc_windows_are_clean(struct tegra_dc_win *windows[],
int n)
{
int i;
for (i = 0; i < n; i++) {
struct tegra_dc *dc = windows[i]->dc;
struct tegra_dc_win *win =
tegra_dc_get_window(dc, windows[i]->idx);
if (win->dirty) {
return false;
}
}
return true;
}
static int get_topmost_window(u32 *depths, unsigned long *wins, int win_num)
{
int idx, best = -1;
for_each_set_bit(idx, wins, win_num) {
if (best == -1 || depths[idx] < depths[best])
best = idx;
}
clear_bit(best, wins);
return best;
}
static u32 blend_topwin(u32 flags)
{
if (flags & TEGRA_WIN_FLAG_BLEND_COVERAGE)
return BLEND(NOKEY, ALPHA, 0xff, 0xff);
else if (flags & TEGRA_WIN_FLAG_BLEND_PREMULT)
return BLEND(NOKEY, PREMULT, 0xff, 0xff);
else
return BLEND(NOKEY, FIX, 0xff, 0xff);
}
static u32 blend_2win(int idx, unsigned long behind_mask,
u32 *flags, int xy, int win_num)
{
int other;
for (other = 0; other < win_num; other++) {
if (other != idx && (xy-- == 0))
break;
}
if (BIT(other) & behind_mask)
return blend_topwin(flags[idx]);
else if (flags[other])
return BLEND(NOKEY, DEPENDANT, 0x00, 0x00);
else
return BLEND(NOKEY, FIX, 0x00, 0x00);
}
static u32 blend_3win(int idx, unsigned long behind_mask,
u32 *flags, int win_num)
{
unsigned long infront_mask;
int first, second;
infront_mask = ~(behind_mask | BIT(idx));
infront_mask &= (BIT(win_num) - 1);
first = ffs(infront_mask) - 1;
if (win_num != 3) {
if (!infront_mask)
return blend_topwin(flags[idx]);
else if (behind_mask && first != -1 && flags[first])
return BLEND(NOKEY, DEPENDANT, 0x00, 0x00);
else
return BLEND(NOKEY, FIX, 0x0, 0x0);
} else {
if (!infront_mask) {
return blend_topwin(flags[idx]);
} else if (behind_mask) {
/* If the first (top) window is not opaque, check if the
* current (middle) window is not opaque. If yes then we need
* to enable appropriate blending mode otherwise current window
* is dependent on the first window. If the first window is
* opaque then the current window has no contribution and we
* set its weight to 0.
*/
if (flags[first]) {
if (flags[idx])
return blend_topwin(flags[idx]);
else
return BLEND(NOKEY, DEPENDANT, 0x00, 0x00);
} else {
return BLEND(NOKEY, FIX, 0x00, 0x00);
}
} else {
infront_mask &= ~(BIT(first));
second = ffs(infront_mask) - 1;
/* If both windows above the bottom window are not opaque then
* the bottom window is dependent otherwise the bottom window
* has no contribution and we set its weight to 0.
*/
if (flags[first] && flags[second])
return BLEND(NOKEY, DEPENDANT, 0x00, 0x00);
else
return BLEND(NOKEY, FIX, 0x00, 0x00);
}
}
}
static void tegra_dc_blend_parallel(struct tegra_dc *dc,
struct tegra_dc_blend *blend)
{
int win_num = dc->gen1_blend_num;
unsigned long mask = BIT(win_num) - 1;
tegra_dc_io_start(dc);
while (mask) {
int idx = get_topmost_window(blend->z, &mask, win_num);
tegra_dc_writel(dc, WINDOW_A_SELECT << idx,
DC_CMD_DISPLAY_WINDOW_HEADER);
tegra_dc_writel(dc, BLEND(NOKEY, FIX, 0xff, 0xff),
DC_WIN_BLEND_NOKEY);
tegra_dc_writel(dc, blend_2win(idx, mask, blend->flags, 0,
win_num), DC_WIN_BLEND_2WIN_X);
tegra_dc_writel(dc, blend_2win(idx, mask, blend->flags, 1,
win_num), DC_WIN_BLEND_2WIN_Y);
tegra_dc_writel(dc, blend_3win(idx, mask, blend->flags,
win_num), DC_WIN_BLEND_3WIN_XY);
}
tegra_dc_io_end(dc);
}
static void tegra_dc_blend_sequential(struct tegra_dc *dc,
struct tegra_dc_blend *blend)
{
int idx;
unsigned long mask = dc->valid_windows;
tegra_dc_io_start(dc);
for_each_set_bit(idx, &mask, tegra_dc_get_numof_dispwindows()) {
if (!tegra_dc_feature_is_gen2_blender(dc, idx))
continue;
tegra_dc_writel(dc, WINDOW_A_SELECT << idx,
DC_CMD_DISPLAY_WINDOW_HEADER);
if (blend->flags[idx] & TEGRA_WIN_FLAG_BLEND_COVERAGE) {
tegra_dc_writel(dc,
WIN_K1(blend->alpha[idx]) |
WIN_K2(0xff) |
WIN_BLEND_ENABLE |
WIN_DEPTH(dc->blend.z[idx]),
DC_WINBUF_BLEND_LAYER_CONTROL);
tegra_dc_writel(dc,
WIN_BLEND_FACT_SRC_COLOR_MATCH_SEL_K1_TIMES_SRC |
WIN_BLEND_FACT_DST_COLOR_MATCH_SEL_NEG_K1_TIMES_SRC |
WIN_BLEND_FACT_SRC_ALPHA_MATCH_SEL_K2 |
WIN_BLEND_FACT_DST_ALPHA_MATCH_SEL_ZERO,
DC_WINBUF_BLEND_MATCH_SELECT);
tegra_dc_writel(dc,
WIN_BLEND_FACT_SRC_COLOR_NOMATCH_SEL_K1_TIMES_SRC |
WIN_BLEND_FACT_DST_COLOR_NOMATCH_SEL_NEG_K1_TIMES_SRC |
WIN_BLEND_FACT_SRC_ALPHA_NOMATCH_SEL_K2 |
WIN_BLEND_FACT_DST_ALPHA_NOMATCH_SEL_ZERO,
DC_WINBUF_BLEND_NOMATCH_SELECT);
tegra_dc_writel(dc,
WIN_ALPHA_1BIT_WEIGHT0(0) |
WIN_ALPHA_1BIT_WEIGHT1(0xff),
DC_WINBUF_BLEND_ALPHA_1BIT);
} else if (blend->flags[idx] & TEGRA_WIN_FLAG_BLEND_PREMULT) {
tegra_dc_writel(dc,
WIN_K1(blend->alpha[idx]) |
WIN_K2(0xff) |
WIN_BLEND_ENABLE |
WIN_DEPTH(dc->blend.z[idx]),
DC_WINBUF_BLEND_LAYER_CONTROL);
tegra_dc_writel(dc,
WIN_BLEND_FACT_SRC_COLOR_MATCH_SEL_K1 |
WIN_BLEND_FACT_DST_COLOR_MATCH_SEL_NEG_K1_TIMES_SRC |
WIN_BLEND_FACT_SRC_ALPHA_MATCH_SEL_K2 |
WIN_BLEND_FACT_DST_ALPHA_MATCH_SEL_ZERO,
DC_WINBUF_BLEND_MATCH_SELECT);
tegra_dc_writel(dc,
WIN_BLEND_FACT_SRC_COLOR_NOMATCH_SEL_NEG_K1_TIMES_DST |
WIN_BLEND_FACT_DST_COLOR_NOMATCH_SEL_K1 |
WIN_BLEND_FACT_SRC_ALPHA_NOMATCH_SEL_K2 |
WIN_BLEND_FACT_DST_ALPHA_NOMATCH_SEL_ZERO,
DC_WINBUF_BLEND_NOMATCH_SELECT);
tegra_dc_writel(dc,
WIN_ALPHA_1BIT_WEIGHT0(0) |
WIN_ALPHA_1BIT_WEIGHT1(0xff),
DC_WINBUF_BLEND_ALPHA_1BIT);
} else if (blend->flags[idx] & TEGRA_WIN_FLAG_BLEND_ADD) {
tegra_dc_writel(dc,
WIN_K1(0xff) |
WIN_K2(0xff) |
WIN_BLEND_ENABLE |
WIN_DEPTH(dc->blend.z[idx]),
DC_WINBUF_BLEND_LAYER_CONTROL);
tegra_dc_writel(dc,
/* note: WIN_BLEND_FACT_SRC_COLOR_MATCH_SEL_ONE is not
* supported. Use K1 set to one instead. */
WIN_BLEND_FACT_SRC_COLOR_MATCH_SEL_K1 |
WIN_BLEND_FACT_DST_COLOR_MATCH_SEL_ONE|
WIN_BLEND_FACT_SRC_ALPHA_MATCH_SEL_ZERO |
WIN_BLEND_FACT_DST_ALPHA_MATCH_SEL_ZERO,
DC_WINBUF_BLEND_MATCH_SELECT);
tegra_dc_writel(dc,
WIN_BLEND_FACT_SRC_COLOR_NOMATCH_SEL_ZERO |
WIN_BLEND_FACT_DST_COLOR_NOMATCH_SEL_ZERO |
WIN_BLEND_FACT_SRC_ALPHA_NOMATCH_SEL_ZERO |
WIN_BLEND_FACT_DST_ALPHA_NOMATCH_SEL_ZERO,
DC_WINBUF_BLEND_NOMATCH_SELECT);
tegra_dc_writel(dc,
WIN_ALPHA_1BIT_WEIGHT0(0) |
WIN_ALPHA_1BIT_WEIGHT1(0xff),
DC_WINBUF_BLEND_ALPHA_1BIT);
} else {
tegra_dc_writel(dc,
WIN_BLEND_BYPASS |
WIN_DEPTH(dc->blend.z[idx]),
DC_WINBUF_BLEND_LAYER_CONTROL);
}
}
tegra_dc_io_end(dc);
}
/* does not support syncing windows on multiple dcs in one call */
int tegra_dc_sync_windows(struct tegra_dc_win *windows[], int n)
{
int ret = 0;
struct tegra_dc *dc = windows[0]->dc;
if (dc == NULL)
return -EFAULT;
if (n < 1 || n > tegra_dc_get_numof_dispwindows())
return -EINVAL;
if (!dc->enabled)
return -EFAULT;
trace_sync_windows(dc);
mutex_lock(&dc->lock);
/*
* Putting the task state as TASK_UINTERRUPTIBLE makes
* task wait till windows status promoted or timeout occurred
* and wont be interrupted by signal or any other reason.
*/
ret = ___wait_event(dc->wq,
___wait_cond_timeout(tegra_dc_windows_are_clean(windows, n)),
TASK_UNINTERRUPTIBLE, 0, HZ,
mutex_unlock(&dc->lock);
__ret = schedule_timeout(__ret);
mutex_lock(&dc->lock));
mutex_unlock(&dc->lock);
if (dc->out_ops && dc->out_ops->release)
dc->out_ops->release(dc);
return ret;
}
EXPORT_SYMBOL(tegra_dc_sync_windows);
static inline u32 compute_dda_inc(fixed20_12 in, unsigned out_int,
bool v, unsigned Bpp)
{
/*
* min(round((prescaled_size_in_pixels) * 0x1000 /
* (post_scaled_size_in_pixels)), MAX)
* Where the value of MAX is as follows:
* For V_DDA_INCREMENT: 15.0 (0xF000)
* For H_DDA_INCREMENT: 4.0 (0x4000) for 4 Bytes/pix formats.
* 8.0 (0x8000) for all other formats.
*/
fixed20_12 out = dfixed_init(out_int);
u32 dda_inc;
int max;
if (v) {
max = 15;
} else {
switch (Bpp) {
default:
WARN_ON_ONCE(1);
/* fallthrough */
case 1:
case 2:
max = 8;
break;
case 4:
max = 4;
break;
}
}
out.full = max_t(u32, out.full, dfixed_const(1));
dda_inc = dfixed_div(in, out);
dda_inc = min_t(u32, dda_inc, dfixed_const(max));
return dda_inc;
}
static inline u32 compute_initial_dda(fixed20_12 in)
{
return dfixed_frac(in);
}
static inline void __maybe_unused tegra_dc_update_scaling(
struct tegra_dc *dc,
struct tegra_dc_win *win, unsigned Bpp,
unsigned Bpp_bw, bool scan_column)
{
u32 h_dda, h_dda_init;
u32 v_dda, v_dda_init;
h_dda_init = compute_initial_dda(win->x);
v_dda_init = compute_initial_dda(win->y);
if (scan_column) {
if (tegra_dc_feature_has_interlace(dc, win->idx)
&& (dc->mode.vmode == FB_VMODE_INTERLACED)) {
if (WIN_IS_INTERLACE(win))
tegra_dc_writel(dc,
V_PRESCALED_SIZE(dfixed_trunc(win->w)) |
H_PRESCALED_SIZE(dfixed_trunc(win->h)
* Bpp),
DC_WIN_PRESCALED_SIZE);
else
tegra_dc_writel(dc,
V_PRESCALED_SIZE(dfixed_trunc(win->w))
| H_PRESCALED_SIZE(dfixed_trunc(win->h)
* Bpp), DC_WIN_PRESCALED_SIZE);
} else {
tegra_dc_writel(dc,
V_PRESCALED_SIZE(dfixed_trunc(win->w)) |
H_PRESCALED_SIZE(dfixed_trunc(win->h) * Bpp),
DC_WIN_PRESCALED_SIZE);
}
tegra_dc_writel(dc, v_dda_init, DC_WIN_H_INITIAL_DDA);
tegra_dc_writel(dc, h_dda_init, DC_WIN_V_INITIAL_DDA);
h_dda = compute_dda_inc(win->h, win->out_w,
false, Bpp_bw);
v_dda = compute_dda_inc(win->w, win->out_h,
true, Bpp_bw);
} else {
if (tegra_dc_feature_has_interlace(dc, win->idx)
&& (dc->mode.vmode == FB_VMODE_INTERLACED)) {
if (WIN_IS_INTERLACE(win))
tegra_dc_writel(dc,
V_PRESCALED_SIZE(dfixed_trunc(win->h)
>> 1) |
H_PRESCALED_SIZE(dfixed_trunc(win->w)
* Bpp), DC_WIN_PRESCALED_SIZE);
else
tegra_dc_writel(dc,
V_PRESCALED_SIZE(dfixed_trunc(win->h)) |
H_PRESCALED_SIZE(dfixed_trunc(win->w)
* Bpp),
DC_WIN_PRESCALED_SIZE);
} else {
tegra_dc_writel(dc,
V_PRESCALED_SIZE(dfixed_trunc(win->h)) |
H_PRESCALED_SIZE(dfixed_trunc(win->w) * Bpp),
DC_WIN_PRESCALED_SIZE);
}
tegra_dc_writel(dc, h_dda_init, DC_WIN_H_INITIAL_DDA);
tegra_dc_writel(dc, v_dda_init, DC_WIN_V_INITIAL_DDA);
h_dda = compute_dda_inc(win->w, win->out_w,
false, Bpp_bw);
v_dda = compute_dda_inc(win->h, win->out_h,
true, Bpp_bw);
}
if (tegra_dc_feature_has_interlace(dc, win->idx) &&
(dc->mode.vmode == FB_VMODE_INTERLACED)) {
if (WIN_IS_INTERLACE(win))
tegra_dc_writel(dc, V_DDA_INC(v_dda) |
H_DDA_INC(h_dda), DC_WIN_DDA_INCREMENT);
else
tegra_dc_writel(dc, V_DDA_INC(v_dda << 1) |
H_DDA_INC(h_dda), DC_WIN_DDA_INCREMENT);
} else {
tegra_dc_writel(dc, V_DDA_INC(v_dda) |
H_DDA_INC(h_dda), DC_WIN_DDA_INCREMENT);
}
}
/*
* The immediate flip with hsync can change only few registers, so this call
* should filter out changes from previous flip that may cause a change to
* these registers.
*/
bool update_is_hsync_safe(struct tegra_dc_win *cur_win,
struct tegra_dc_win *new_win)
{
return ((cur_win->fmt == new_win->fmt) &&
(cur_win->flags == new_win->flags) &&
(dfixed_trunc(cur_win->x) == dfixed_trunc(new_win->x)) &&
(dfixed_trunc(cur_win->y) == dfixed_trunc(new_win->y)) &&
(dfixed_trunc(cur_win->w) == dfixed_trunc(new_win->w)) &&
(dfixed_trunc(cur_win->h) == dfixed_trunc(new_win->h)) &&
(cur_win->out_x == new_win->out_x) &&
(cur_win->out_y == new_win->out_y) &&
(cur_win->out_w == new_win->out_w) &&
(cur_win->out_h == new_win->out_h) &&
(cur_win->z == new_win->z) &&
(cur_win->dc == new_win->dc) &&
(!memcmp(&cur_win->win_csc, &new_win->win_csc,
sizeof(cur_win->win_csc)))
);
}
void tegra_dc_set_background_color(struct tegra_dc *dc,
u32 background_color)
{
tegra_dc_writel(dc, background_color, DC_DISP_BLEND_BACKGROUND_COLOR);
}
void tegra_dc_win_partial_update(struct tegra_dc *dc, struct tegra_dc_win *win,
unsigned int xoff, unsigned int yoff, unsigned int width,
unsigned int height)
{
if (!win->out_w || !win->out_h ||
(win->out_x >= (xoff + width)) ||
(win->out_y >= (yoff + height)) ||
(xoff >= (win->out_x + win->out_w)) ||
(yoff >= (win->out_y + win->out_h))) {
tegra_dc_writel(dc, 0, DC_WIN_WIN_OPTIONS);
return;
} else {
u64 tmp_64;
fixed20_12 fixed_tmp;
unsigned int xoff_2;
unsigned int yoff_2;
unsigned int width_2;
unsigned int height_2;
xoff_2 = (win->out_x < xoff) ? xoff : win->out_x;
yoff_2 = (win->out_y < yoff) ? yoff : win->out_y;
width_2 = ((win->out_x + win->out_w) > (xoff + width)) ?
(xoff + width - xoff_2) :
(win->out_x + win->out_w - xoff_2);
height_2 = ((win->out_y + win->out_h) > (yoff + height)) ?
(yoff + height - yoff_2) :
(win->out_y + win->out_h - yoff_2);
tmp_64 = (u64)(xoff_2 - win->out_x) * win->w.full;
do_div(tmp_64, win->out_w);
fixed_tmp.full = (u32)tmp_64;
win->x.full += dfixed_floor(fixed_tmp);
tmp_64 = (u64)(yoff_2 - win->out_y) * win->h.full;
do_div(tmp_64, win->out_h);
fixed_tmp.full = (u32)tmp_64;
win->y.full += dfixed_floor(fixed_tmp);
tmp_64 = (u64)(width_2) * win->w.full;
do_div(tmp_64, win->out_w);
fixed_tmp.full = (u32)tmp_64;
win->w.full = dfixed_floor(fixed_tmp);
tmp_64 = (u64)(height_2) * win->h.full;
do_div(tmp_64, win->out_h);
fixed_tmp.full = (u32)tmp_64;
win->h.full = dfixed_floor(fixed_tmp);
/* Move the partial region to the up-left corner
* so dc can only scan out this region. */
win->out_x = xoff_2 - xoff;
win->out_y = yoff_2 - yoff;
win->out_w = width_2;
win->out_h = height_2;
/* Update shadow registers */
memcpy(&dc->shadow_windows[win->idx], win,
sizeof(struct tegra_dc_win));
}
}
static void tegra_dc_vrr_flip_time(struct tegra_dc *dc)
{
struct timespec time_now;
struct tegra_vrr *vrr = dc->out->vrr;
if (!vrr || !vrr->capability)
return;
if (vrr->enable) {
vrr->lastenable = 1;
getnstimeofday(&time_now);
vrr->curr_flip_us = (s64)time_now.tv_sec * 1000000 +
time_now.tv_nsec / 1000;
vrr->flip = 1;
} else {
vrr->curr_flip_us = 0;
vrr->last_flip_us = 0;
}
}
static void tegra_dc_vrr_cancel_vfp(struct tegra_dc *dc)
{
struct tegra_vrr *vrr = dc->out->vrr;
if (!vrr || !vrr->capability)
return;
if (vrr->enable) {
if (dc->out->type == TEGRA_DC_OUT_DSI)
tegra_dc_set_act_vfp(dc, vrr->vfp_shrink);
else
tegra_dc_set_act_vfp(dc, dc->mode.v_front_porch);
} else {
if (dc->out->type == TEGRA_DC_OUT_DSI) {
if (vrr->lastenable && vrr->dcb <= vrr->db_tolerance) {
tegra_dc_set_act_vfp(dc,
dc->mode.v_front_porch);
vrr->lastenable = 0;
vrr->frame_type = 0;
vrr->last_frame_us = 0;
vrr->flip_interval_us = 0;
vrr->frame_count = 0;
vrr->flip_count = 0;
vrr->vfp_shrink = vrr->v_front_porch_min;
vrr->vfp_extend = vrr->v_front_porch_max;
}
} else
tegra_dc_set_act_vfp(dc, dc->mode.v_front_porch);
}
}
/* Program registers for each window. struct tegra_dc_win --> Assembly registers
*/
static int _tegra_dc_program_windows(struct tegra_dc *dc,
struct tegra_dc_win *windows[], int n, u16 *dirty_rect,
bool wait_for_vblank, bool lock_flip)
{
unsigned long update_mask = GENERAL_ACT_REQ;
unsigned long act_control = 0;
unsigned long win_options;
bool update_blend_par = false;
bool update_blend_seq = false;
int i;
bool do_partial_update = false;
unsigned int xoff;
unsigned int yoff;
unsigned int width;
unsigned int height;
enum tegra_revision rev;
if (dirty_rect) {
xoff = dirty_rect[0];
yoff = dirty_rect[1];
width = dirty_rect[2];
height = dirty_rect[3];
do_partial_update = !dc->out_ops->partial_update(dc,
&xoff, &yoff, &width, &height);
if (do_partial_update) {
tegra_dc_writel(dc, width | (height << 16),
DC_DISP_DISP_ACTIVE);
dc->disp_active_dirty = true;
}
}
/* If any of the window updates requires vsync to program the window
update safely, vsync all windows in this flip. Safety overrides both
the requested wait_for_vblank, and also the no_vsync global. */
for (i = 0; i < n; i++) {
struct tegra_dc_win *win = windows[i];
if ((!wait_for_vblank &&
!update_is_hsync_safe(&dc->shadow_windows[win->idx],
win)) || do_partial_update)
wait_for_vblank = 1;
memcpy(&dc->shadow_windows[win->idx], win,
sizeof(struct tegra_dc_win));
}
for (i = 0; i < n; i++) {
struct tegra_dc_win *win = windows[i];
struct tegra_dc_win *dc_win = tegra_dc_get_window(dc, win->idx);
bool scan_column = 0;
fixed20_12 h_offset, v_offset;
bool invert_h = (win->flags & TEGRA_WIN_FLAG_INVERT_H) != 0;
bool invert_v = (win->flags & TEGRA_WIN_FLAG_INVERT_V) != 0;
bool yuv = tegra_dc_is_yuv(win->fmt);
bool yuvp = tegra_dc_is_yuv_planar(win->fmt);
bool yuvsp = tegra_dc_is_yuv_semi_planar(win->fmt);
unsigned Bpp = tegra_dc_fmt_bpp(win->fmt) / 8;
/* Bytes per pixel of bandwidth, used for dda_inc calculation */
unsigned Bpp_bw = Bpp * ((yuvp || yuvsp) ? 2 : 1);
bool filter_h;
bool filter_v;
u32 color = DISP_BLEND_BACKGROUND_COLOR_DEFAULT;
scan_column = (win->flags & TEGRA_WIN_FLAG_SCAN_COLUMN);
tegra_dc_writel(dc, WINDOW_A_SELECT << win->idx,
DC_CMD_DISPLAY_WINDOW_HEADER);
if (!no_vsync)
update_mask |= WIN_A_ACT_REQ << win->idx;
if (!WIN_IS_ENABLED(win)) {
dc_win->dirty = no_vsync ? 0 : 1;
tegra_dc_writel(dc, 0, DC_WIN_WIN_OPTIONS);
if (dc->yuv_bypass) {
/* Note, that YUV420/10-bit packing does not
* have a simple background color, because the
* bit pattern is not same for each pixel. A
* special background pattern needs to be
* shown instead,
* see tegra_dc_ext_should_show_background().
*/
if (tegra_dc_is_yuv420_8bpc(&dc->mode))
color = RGB_TO_YUV420_8BPC_BLACK_PIX;
else if (tegra_dc_is_yuv422_12bpc(&dc->mode))
color = RGB_TO_YUV422_10BPC_BLACK_PIX;
else if (tegra_dc_is_yuv444_8bpc(&dc->mode))
color = RGB_TO_YUV444_8BPC_BLACK_PIX;
tegra_dc_set_background_color(dc, color);
}
continue;
}
filter_h = win_use_h_filter(dc, win);
filter_v = win_use_v_filter(dc, win);
/* Update blender */
if ((win->z != dc->blend.z[win->idx]) ||
((win->flags & TEGRA_WIN_BLEND_FLAGS_MASK) !=
dc->blend.flags[win->idx])) {
dc->blend.z[win->idx] = win->z;
dc->blend.flags[win->idx] =
win->flags & TEGRA_WIN_BLEND_FLAGS_MASK;
if (tegra_dc_feature_is_gen2_blender(dc, win->idx))
update_blend_seq = true;
else
update_blend_par = true;
}
tegra_dc_writel(dc, WINDOW_A_SELECT << win->idx,
DC_CMD_DISPLAY_WINDOW_HEADER);
update_mask |= WIN_A_ACT_REQ << win->idx;
if (wait_for_vblank)
act_control &= ~WIN_ACT_CNTR_SEL_HCOUNTER(win->idx);
else
act_control |= WIN_ACT_CNTR_SEL_HCOUNTER(win->idx);
if (win->cde.cde_addr) {
tegra_dc_writel(dc, ENABLESURFACE0,
DC_WINBUF_CDE_CONTROL);
tegra_dc_writel(dc, tegra_dc_reg_l32(win->cde.cde_addr),
DC_WINBUF_CDE_COMPTAG_BASE_0);
tegra_dc_writel(dc, tegra_dc_reg_h32(win->cde.cde_addr),
DC_WINBUF_CDE_COMPTAG_BASEHI_0);
tegra_dc_writel(dc, win->cde.zbc_color,
DC_WINBUF_CDE_ZBC_COLOR_0);
tegra_dc_writel(dc, win->cde.offset_x |
((u32)win->cde.offset_y << 16),
DC_WINBUF_CDE_SURFACE_OFFSET_0);
tegra_dc_writel(dc, win->cde.ctb_entry,
DC_WINBUF_CDE_CTB_ENTRY_0);
rev = tegra_chip_get_revision();
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0))
if (tegra_get_chip_id() == TEGRA210
&& ((rev == TEGRA_REVISION_A01) ||
(rev == TEGRA_REVISION_A01q)))
#else
if (rev == TEGRA210_REVISION_A01 ||
rev == TEGRA210_REVISION_A01q)
#endif
tegra_dc_writel(dc, 0, DC_WINBUF_CDE_CG_SW_OVR);
} else {
tegra_dc_writel(dc, 0, DC_WINBUF_CDE_CONTROL);
tegra_dc_writel(dc, 0x00000001,
DC_WINBUF_CDE_CG_SW_OVR);
}
tegra_dc_writel(dc, tegra_dc_fmt(win->fmt),
DC_WIN_COLOR_DEPTH);
tegra_dc_writel(dc, tegra_dc_fmt_byteorder(win->fmt),
DC_WIN_BYTE_SWAP);
if (do_partial_update)
tegra_dc_win_partial_update(dc, win, xoff, yoff,
width, height);
tegra_dc_writel(dc,
V_POSITION(win->out_y) | H_POSITION(win->out_x),
DC_WIN_POSITION);
if (tegra_dc_feature_has_interlace(dc, win->idx) &&
(dc->mode.vmode == FB_VMODE_INTERLACED)) {
tegra_dc_writel(dc,
V_SIZE((win->out_h) >> 1) |
H_SIZE(win->out_w),
DC_WIN_SIZE);
} else {
tegra_dc_writel(dc,
V_SIZE(win->out_h) | H_SIZE(win->out_w),
DC_WIN_SIZE);
}
win_options = WIN_ENABLE;
if (scan_column)
win_options |= WIN_SCAN_COLUMN;
if (!dc->yuv_bypass) {
win_options |= H_FILTER_ENABLE(filter_h);
win_options |= V_FILTER_ENABLE(filter_v);
}
/* Update scaling registers if window supports scaling. */
if (likely(tegra_dc_feature_has_scaling(dc, win->idx)))
tegra_dc_update_scaling(dc, win, Bpp, Bpp_bw,
scan_column);
tegra_dc_writel(dc, tegra_dc_reg_l32(win->phys_addr),
DC_WINBUF_START_ADDR);
tegra_dc_writel(dc, tegra_dc_reg_h32(win->phys_addr),
DC_WINBUF_START_ADDR_HI);
if (!yuvp && !yuvsp) {
tegra_dc_writel(dc, win->stride, DC_WIN_LINE_STRIDE);
} else if (yuvp) {
tegra_dc_writel(dc, tegra_dc_reg_l32(win->phys_addr_u),
DC_WINBUF_START_ADDR_U);
tegra_dc_writel(dc, tegra_dc_reg_h32(win->phys_addr_u),
DC_WINBUF_START_ADDR_HI_U);
tegra_dc_writel(dc, tegra_dc_reg_l32(win->phys_addr_v),
DC_WINBUF_START_ADDR_V);
tegra_dc_writel(dc, tegra_dc_reg_h32(win->phys_addr_v),
DC_WINBUF_START_ADDR_HI_V);
tegra_dc_writel(dc,
LINE_STRIDE(win->stride) |
UV_LINE_STRIDE(win->stride_uv),
DC_WIN_LINE_STRIDE);
} else {
tegra_dc_writel(dc, tegra_dc_reg_l32(win->phys_addr_u),
DC_WINBUF_START_ADDR_U);
tegra_dc_writel(dc, tegra_dc_reg_h32(win->phys_addr_u),
DC_WINBUF_START_ADDR_HI_U);
tegra_dc_writel(dc,
LINE_STRIDE(win->stride) |
UV_LINE_STRIDE(win->stride_uv),
DC_WIN_LINE_STRIDE);
}
if (invert_h) {
h_offset.full = win->x.full + win->w.full;
h_offset.full = dfixed_floor(h_offset) * Bpp;
h_offset.full -= dfixed_const(1);
} else {
h_offset.full = dfixed_floor(win->x) * Bpp;
}
v_offset = win->y;
if (invert_v)
v_offset.full += win->h.full - dfixed_const(1);
tegra_dc_writel(dc, dfixed_trunc(h_offset),
DC_WINBUF_ADDR_H_OFFSET);
tegra_dc_writel(dc, dfixed_trunc(v_offset),
DC_WINBUF_ADDR_V_OFFSET);
if ((dc->mode.vmode == FB_VMODE_INTERLACED) && WIN_IS_FB(win)) {
if (!WIN_IS_INTERLACE(win))
win->phys_addr2 = win->phys_addr;
}
if ((tegra_dc_feature_has_interlace(dc, win->idx)) &&
(dc->mode.vmode == FB_VMODE_INTERLACED)) {
tegra_dc_writel(dc, win->phys_addr2,
DC_WINBUF_START_ADDR_FIELD2);
if (yuvp) {
tegra_dc_writel(dc, tegra_dc_reg_l32(win->phys_addr_u2),
DC_WINBUF_START_ADDR_FIELD2_U);
tegra_dc_writel(dc, tegra_dc_reg_h32(win->phys_addr_u2),
DC_WINBUF_START_ADDR_FIELD2_HI_U);
tegra_dc_writel(dc, tegra_dc_reg_l32(win->phys_addr_v2),
DC_WINBUF_START_ADDR_FIELD2_V);
tegra_dc_writel(dc, tegra_dc_reg_h32(win->phys_addr_v2),
DC_WINBUF_START_ADDR_FIELD2_HI_V);
} else if (yuvsp) {
tegra_dc_writel(dc, tegra_dc_reg_l32(win->phys_addr_u2),
DC_WINBUF_START_ADDR_FIELD2_U);
tegra_dc_writel(dc, tegra_dc_reg_h32(win->phys_addr_u2),
DC_WINBUF_START_ADDR_FIELD2_HI_U);
}
tegra_dc_writel(dc, dfixed_trunc(h_offset),
DC_WINBUF_ADDR_H_OFFSET_FIELD2);
if (WIN_IS_INTERLACE(win)) {
tegra_dc_writel(dc, dfixed_trunc(v_offset),
DC_WINBUF_ADDR_V_OFFSET_FIELD2);
} else {
v_offset.full += dfixed_const(1);
tegra_dc_writel(dc, dfixed_trunc(v_offset),
DC_WINBUF_ADDR_V_OFFSET_FIELD2);
}
}
if (tegra_dc_feature_has_tiling(dc, win->idx)) {
if (WIN_IS_TILED(win))
tegra_dc_writel(dc,
DC_WIN_BUFFER_ADDR_MODE_TILE |
DC_WIN_BUFFER_ADDR_MODE_TILE_UV,
DC_WIN_BUFFER_ADDR_MODE);
else
tegra_dc_writel(dc,
DC_WIN_BUFFER_ADDR_MODE_LINEAR |
DC_WIN_BUFFER_ADDR_MODE_LINEAR_UV,
DC_WIN_BUFFER_ADDR_MODE);
}
if (tegra_dc_feature_has_blocklinear(dc, win->idx) ||
tegra_dc_feature_has_tiling(dc, win->idx)) {
if (WIN_IS_BLOCKLINEAR(win)) {
tegra_dc_writel(dc,
DC_WIN_BUFFER_SURFACE_BL_16B2 |
(win->block_height_log2
<< BLOCK_HEIGHT_SHIFT),
DC_WIN_BUFFER_SURFACE_KIND);
} else if (WIN_IS_TILED(win)) {
tegra_dc_writel(dc,
DC_WIN_BUFFER_SURFACE_TILED,
DC_WIN_BUFFER_SURFACE_KIND);
} else {
tegra_dc_writel(dc,
DC_WIN_BUFFER_SURFACE_PITCH,
DC_WIN_BUFFER_SURFACE_KIND);
}
}
if (yuv)
win_options |= CSC_ENABLE;
else if (tegra_dc_fmt_bpp(win->fmt) < 24)
win_options |= COLOR_EXPAND;
/*
* For gen2 blending, change in the global alpha needs rewrite
* to blending regs.
*/
if ((dc->blend.alpha[win->idx] != win->global_alpha) &&
(tegra_dc_feature_is_gen2_blender(dc, win->idx)))
update_blend_seq = true;
/* Cache the global alpha of each window here. It is necessary
* for in-order blending settings. */
dc->blend.alpha[win->idx] = win->global_alpha;
if (!tegra_dc_feature_is_gen2_blender(dc, win->idx)) {
/* Update global alpha if blender is gen1. */
if (win->global_alpha == 255) {
tegra_dc_writel(dc, 0, DC_WIN_GLOBAL_ALPHA);
} else {
tegra_dc_writel(dc, GLOBAL_ALPHA_ENABLE |
win->global_alpha, DC_WIN_GLOBAL_ALPHA);
win_options |= CP_ENABLE;
}
}
if (win->ppflags & TEGRA_WIN_PPFLAG_CP_ENABLE)
win_options |= CP_ENABLE;
win_options |= H_DIRECTION_DECREMENT(invert_h);
win_options |= V_DIRECTION_DECREMENT(invert_v);
if (tegra_dc_feature_has_interlace(dc, win->idx)) {
if (dc->mode.vmode == FB_VMODE_INTERLACED)
win_options |= INTERLACE_ENABLE;
}
if (dc_win->csc_dirty) {
tegra_dc_set_win_csc(dc, &dc_win->win_csc);
dc_win->csc_dirty = false;
}
if (dc->yuv_bypass && win->fmt != TEGRA_DC_EXT_FMT_T_P8)
win_options &= ~CP_ENABLE;
tegra_dc_writel(dc, win_options, DC_WIN_WIN_OPTIONS);
dc_win->dirty = 1;
trace_window_update(dc, win);
}
if (update_blend_par || update_blend_seq) {
if (update_blend_par)
tegra_dc_blend_parallel(dc, &dc->blend);
if (update_blend_seq)
tegra_dc_blend_sequential(dc, &dc->blend);
for_each_set_bit(i, &dc->valid_windows,
tegra_dc_get_numof_dispwindows()) {
struct tegra_dc_win *win = tegra_dc_get_window(dc, i);
win->dirty = 1;
update_mask |= WIN_A_ACT_REQ << i;
}
}
tegra_dc_set_dynamic_emc(dc);
/* prevent FIFO from taking in stale data after a reset */
if (tegra_dc_is_t21x())
tegra_dc_writel(dc, MEMFETCH_RESET, DC_WINBUF_MEMFETCH_CONTROL);
/* WIN_x_UPDATE is the same as WIN_x_ACT_REQ << 8 */
tegra_dc_writel(dc, update_mask << 8, DC_CMD_STATE_CONTROL);
if (tegra_cpu_is_asim())
tegra_dc_writel(dc, FRAME_END_INT | V_BLANK_INT,
DC_CMD_INT_STATUS);
tegra_dc_writel(dc, act_control, DC_CMD_REG_ACT_CONTROL);
if (wait_for_vblank) {
/* Use the interrupt handler. ISR will clear the dirty flags
when the flip is completed */
set_bit(V_BLANK_FLIP, &dc->vblank_ref_count);
tegra_dc_unmask_interrupt(dc,
FRAME_END_INT | V_BLANK_INT | ALL_UF_INT());
set_bit(V_PULSE2_FLIP, &dc->vpulse2_ref_count);
tegra_dc_unmask_interrupt(dc, V_PULSE2_INT);
}
if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) {
schedule_delayed_work(&dc->one_shot_work,
msecs_to_jiffies(dc->one_shot_delay_ms));
}
dc->crc_pending = true;
/* update EMC clock if calculated bandwidth has changed */
tegra_dc_program_bandwidth(dc, false);
if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)
update_mask |= NC_HOST_TRIG;
if (lock_flip)
dc->act_req_mask = update_mask;
else
tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL);
if (!wait_for_vblank) {
/* Don't use a interrupt handler for the update, but leave
vblank interrupts unmasked since they could be used by other
windows. One window could flip on HBLANK while others flip
on VBLANK. Poll HW until this window update is completed
which could block for as long as it takes to display one
scanline. */
unsigned int winmask = update_mask & WIN_ALL_ACT_REQ;
while (tegra_dc_windows_are_dirty(dc, winmask))
udelay(1);
for_each_set_bit(i, &dc->valid_windows, n)
tegra_dc_get_window(dc, windows[i]->idx)->dirty = 0;
}
return 0;
}
/* Does not support updating windows on multiple dcs in one call.
* Requires a matching sync_windows to avoid leaking ref-count on clocks.
*/
int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n,
u16 *dirty_rect, bool wait_for_vblank, bool lock_flip)
{
struct tegra_dc *dc;
int i;
int e = 0;
dc = windows[0]->dc;
trace_update_windows(dc);
if (dc == NULL)
return -EINVAL;
/* check that window arguments are valid */
for (i = 0; i < n; i++) {
struct tegra_dc_win *win = windows[i];
struct tegra_dc_win *dc_win =
win ? tegra_dc_get_window(dc, win->idx) : NULL;
if (WARN_ONCE(!dc_win, "ignoring invalid window %d request", i))
return -EINVAL;
}
if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) {
/* Acquire one_shot_lock to avoid race condition between
* cancellation of old delayed work and schedule of new
* delayed work. */
mutex_lock(&dc->one_shot_lock);
cancel_delayed_work_sync(&dc->one_shot_work);
tegra_dc_get(dc);
}
mutex_lock(&dc->lock);
if (!dc->enabled) {
e = -EFAULT;
goto done;
}
/* If there is a request to tegra_dc_update_windows without flip lock,
* then wait for the current flips (with flip lock) to finish.
* Requesting process are queued until the ongoing request is
* completed.
*/
if (!lock_flip) {
DEFINE_WAIT(wait);
if (dc->frm_lck_info.job_pending) {
prepare_to_wait_exclusive(
&dc->frm_lck_info.win_upd_reqs,
&wait, TASK_INTERRUPTIBLE);
if (dc->frm_lck_info.job_pending) {
mutex_unlock(&dc->lock);
schedule();
mutex_lock(&dc->lock);
}
finish_wait(&dc->frm_lck_info.win_upd_reqs, &wait);
}
}
tegra_dc_io_start(dc);
if (dc->out_ops && dc->out_ops->hold)
dc->out_ops->hold(dc);
if (no_vsync)
wait_for_vblank = 0;
WARN_ONCE((!wait_for_vblank && dirty_rect),
"Can't do partial window update without vsync!");
if (tegra_dc_is_nvdisplay())
e = tegra_nvdisp_update_windows(dc, windows, n, dirty_rect,
wait_for_vblank, (lock_flip & wait_for_vblank));
else
e = _tegra_dc_program_windows(dc, windows, n, dirty_rect,
wait_for_vblank, (lock_flip & wait_for_vblank));
if (lock_flip && wait_for_vblank) {
int ret;
dc->frm_lck_info.job_pending = true;
mutex_unlock(&dc->lock);
ret = tegra_dc_common_sync_flips(dc, dc->act_req_mask,
DC_CMD_STATE_CONTROL);
mutex_lock(&dc->lock);
if (ret)
dev_err(&dc->ndev->dev,
"failed to submit job for tegradc.%d with error : %d\n",
dc->ctrl_num, ret);
dc->act_req_mask = 0UL;
dc->frm_lck_info.job_pending = false;
wake_up(&dc->frm_lck_info.win_upd_reqs);
}
/*
* tegra_dc_put() called at frame end, for one shot.
* out_ops->release called in tegra_dc_sync_windows.
*/
tegra_dc_io_end(dc);
if (WARN_ONCE(e, "horrible failure")) /* horrible failure */
goto done;
if (dc->out->type == TEGRA_DC_OUT_DSI)
tegra_dc_vrr_flip_time(dc);
tegra_dc_vrr_cancel_vfp(dc);
done:
mutex_unlock(&dc->lock);
if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)
mutex_unlock(&dc->one_shot_lock);
return e;
}
EXPORT_SYMBOL(tegra_dc_update_windows);
void tegra_dc_trigger_windows(struct tegra_dc *dc)
{
u32 val, i;
u32 completed = 0;
u32 dirty = 0;
bool interlace_done = true;
if (dc->mode.vmode == FB_VMODE_INTERLACED) {
val = tegra_dc_readl(dc, DC_DISP_INTERLACE_CONTROL);
interlace_done = (val & INTERLACE_MODE_ENABLE) &&
(val & INTERLACE_STATUS_FIELD_2);
}
val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
for_each_set_bit(i, &dc->valid_windows,
tegra_dc_get_numof_dispwindows()) {
struct tegra_dc_win *win = tegra_dc_get_window(dc, i);
if (!(val & (WIN_A_ACT_REQ << i)) && interlace_done) {
win->dirty = 0;
completed = 1;
} else {
dirty = 1;
}
}
if (!dirty) {
if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)
&& !atomic_read(&dc->frame_end_ref))
tegra_dc_mask_interrupt(dc, FRAME_END_INT);
}
if (completed)
wake_up(&dc->wq);
}