diff options
Diffstat (limited to 'drivers/video/tegra/dc/window.c')
-rw-r--r-- | drivers/video/tegra/dc/window.c | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/drivers/video/tegra/dc/window.c b/drivers/video/tegra/dc/window.c new file mode 100644 index 000000000000..af18564f45b9 --- /dev/null +++ b/drivers/video/tegra/dc/window.c @@ -0,0 +1,493 @@ +/* + * drivers/video/tegra/dc/window.c + * + * Copyright (C) 2010 Google, Inc. + * + * Copyright (c) 2010-2012, 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 <mach/dc.h> + +#include "dc_reg.h" +#include "dc_config.h" +#include "dc_priv.h" + +static int no_vsync; +#ifndef CONFIG_ANDROID +static atomic_t frame_end_ref = ATOMIC_INIT(0); +#endif /* !CONFIG_ANDROID */ + +module_param_named(no_vsync, no_vsync, int, S_IRUGO | S_IWUSR); + +static bool tegra_dc_windows_are_clean(struct tegra_dc_win *windows[], + int n) +{ + int i; + + for (i = 0; i < n; i++) { + if (windows[i]->dirty) + return false; + } + + return true; +} + +#ifndef CONFIG_ANDROID +int tegra_dc_config_frame_end_intr(struct tegra_dc *dc, bool enable) +{ + tegra_dc_writel(dc, FRAME_END_INT, DC_CMD_INT_STATUS); + if (enable) { + atomic_inc(&frame_end_ref); + tegra_dc_unmask_interrupt(dc, FRAME_END_INT); + } else if (!atomic_dec_return(&frame_end_ref)) + tegra_dc_mask_interrupt(dc, FRAME_END_INT); + return 0; +} +#endif /* !CONFIG_ANDROID */ + +static int get_topmost_window(u32 *depths, unsigned long *wins) +{ + int idx, best = -1; + + for_each_set_bit(idx, wins, DC_N_WINDOWS) { + 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 other; + + for (other = 0; other < DC_N_WINDOWS; 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) +{ + unsigned long infront_mask; + int first; + + infront_mask = ~(behind_mask | BIT(idx)); + infront_mask &= (BIT(DC_N_WINDOWS) - 1); + first = ffs(infront_mask) - 1; + + 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); +} + +static void tegra_dc_set_blending(struct tegra_dc *dc, + struct tegra_dc_blend *blend) +{ + unsigned long mask = BIT(DC_N_WINDOWS) - 1; + + while (mask) { + int idx = get_topmost_window(blend->z, &mask); + + 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(NOKEY, FIX, 0xff, 0xff), + DC_WIN_BLEND_1WIN); + tegra_dc_writel(dc, blend_2win(idx, mask, blend->flags, 0), + DC_WIN_BLEND_2WIN_X); + tegra_dc_writel(dc, blend_2win(idx, mask, blend->flags, 1), + DC_WIN_BLEND_2WIN_Y); + tegra_dc_writel(dc, blend_3win(idx, mask, blend->flags), + DC_WIN_BLEND_3WIN_XY); + } +} + +/* 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; + if (n < 1 || n > DC_N_WINDOWS) + return -EINVAL; + + if (!windows[0]->dc->enabled) + return -EFAULT; + +#ifdef CONFIG_TEGRA_SIMULATION_PLATFORM + /* Don't want to timeout on simulator */ + ret = wait_event_interruptible(windows[0]->dc->wq, + tegra_dc_windows_are_clean(windows, n)); +#else + trace_printk("%s:Before wait_event_interruptible_timeout\n", + windows[0]->dc->ndev->name); + ret = wait_event_interruptible_timeout(windows[0]->dc->wq, + tegra_dc_windows_are_clean(windows, n), + HZ); + trace_printk("%s:After wait_event_interruptible_timeout\n", + windows[0]->dc->ndev->name); +#endif + 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 - 1) * 0x1000 / + * (post_scaled_size_in_pixels - 1)), 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 2 Bytes/pix 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 4: + max = 4; + break; + case 2: + max = 8; + break; + } + } + + out.full = max_t(u32, out.full - dfixed_const(1), dfixed_const(1)); + in.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); +} + +/* does not support updating windows on multiple dcs in one call */ +int tegra_dc_update_windows(struct tegra_dc_win *windows[], int n) +{ + struct tegra_dc *dc; + unsigned long update_mask = GENERAL_ACT_REQ; + unsigned long val; + bool update_blend = false; + int i; + + dc = windows[0]->dc; + + 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); + } + mutex_lock(&dc->lock); + + if (!dc->enabled) { + mutex_unlock(&dc->lock); + if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) + mutex_unlock(&dc->one_shot_lock); + return -EFAULT; + } + + tegra_dc_hold_dc_out(dc); + + if (no_vsync) + tegra_dc_writel(dc, WRITE_MUX_ACTIVE | READ_MUX_ACTIVE, + DC_CMD_STATE_ACCESS); + else + tegra_dc_writel(dc, WRITE_MUX_ASSEMBLY | READ_MUX_ASSEMBLY, + DC_CMD_STATE_ACCESS); + + for (i = 0; i < n; i++) { + struct tegra_dc_win *win = windows[i]; + unsigned h_dda; + unsigned v_dda; + 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); + unsigned Bpp = tegra_dc_fmt_bpp(win->fmt) / 8; + /* Bytes per pixel of bandwidth, used for dda_inc calculation */ + unsigned Bpp_bw = Bpp * (yuvp ? 2 : 1); + const bool filter_h = win_use_h_filter(dc, win); + const bool filter_v = win_use_v_filter(dc, win); + + if (win->z != dc->blend.z[win->idx]) { + dc->blend.z[win->idx] = win->z; + update_blend = true; + } + if ((win->flags & TEGRA_WIN_BLEND_FLAGS_MASK) != + dc->blend.flags[win->idx]) { + dc->blend.flags[win->idx] = + win->flags & TEGRA_WIN_BLEND_FLAGS_MASK; + update_blend = true; + } + + 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->windows[i].dirty = 1; + tegra_dc_writel(dc, 0, DC_WIN_WIN_OPTIONS); + continue; + } + + tegra_dc_writel(dc, win->fmt & 0x1f, DC_WIN_COLOR_DEPTH); + tegra_dc_writel(dc, win->fmt >> 6, DC_WIN_BYTE_SWAP); + + tegra_dc_writel(dc, + V_POSITION(win->out_y) | H_POSITION(win->out_x), + DC_WIN_POSITION); + tegra_dc_writel(dc, + V_SIZE(win->out_h) | H_SIZE(win->out_w), + DC_WIN_SIZE); + + if (tegra_dc_feature_has_scaling(dc, win->idx)) { + tegra_dc_writel(dc, + V_PRESCALED_SIZE(dfixed_trunc(win->h)) | + H_PRESCALED_SIZE(dfixed_trunc(win->w) * Bpp), + DC_WIN_PRESCALED_SIZE); + + 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); + tegra_dc_writel(dc, V_DDA_INC(v_dda) | + H_DDA_INC(h_dda), DC_WIN_DDA_INCREMENT); + h_dda = compute_initial_dda(win->x); + v_dda = compute_initial_dda(win->y); + tegra_dc_writel(dc, h_dda, DC_WIN_H_INITIAL_DDA); + tegra_dc_writel(dc, v_dda, DC_WIN_V_INITIAL_DDA); + } + + tegra_dc_writel(dc, 0, DC_WIN_BUF_STRIDE); + tegra_dc_writel(dc, 0, DC_WIN_UV_BUF_STRIDE); + tegra_dc_writel(dc, (unsigned long)win->phys_addr, + DC_WINBUF_START_ADDR); + + if (!yuvp) { + tegra_dc_writel(dc, win->stride, DC_WIN_LINE_STRIDE); + } else { + tegra_dc_writel(dc, + (unsigned long)win->phys_addr_u, + DC_WINBUF_START_ADDR_U); + tegra_dc_writel(dc, + (unsigned long)win->phys_addr_v, + DC_WINBUF_START_ADDR_V); + tegra_dc_writel(dc, + LINE_STRIDE(win->stride) | + UV_LINE_STRIDE(win->stride_uv), + DC_WIN_LINE_STRIDE); + } + + h_offset = win->x; + if (invert_h) { + h_offset.full += win->w.full - dfixed_const(1); + } + + v_offset = win->y; + if (invert_v) { + v_offset.full += win->h.full - dfixed_const(1); + } + + tegra_dc_writel(dc, dfixed_trunc(h_offset) * Bpp, + DC_WINBUF_ADDR_H_OFFSET); + tegra_dc_writel(dc, dfixed_trunc(v_offset), + DC_WINBUF_ADDR_V_OFFSET); + + 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); + } + + val = WIN_ENABLE; + if (yuv) + val |= CSC_ENABLE; + else if (tegra_dc_fmt_bpp(win->fmt) < 24) + val |= COLOR_EXPAND; + + if (win->ppflags & TEGRA_WIN_PPFLAG_CP_ENABLE) + val |= CP_ENABLE; + + if (filter_h) + val |= H_FILTER_ENABLE; + if (filter_v) + val |= V_FILTER_ENABLE; + + if (invert_h) + val |= H_DIRECTION_DECREMENT; + if (invert_v) + val |= V_DIRECTION_DECREMENT; + + tegra_dc_writel(dc, val, DC_WIN_WIN_OPTIONS); + +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + 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); +#endif + + win->dirty = no_vsync ? 0 : 1; + + dev_dbg(&dc->ndev->dev, "%s():idx=%d z=%d x=%d y=%d w=%d h=%d " + "out_x=%u out_y=%u out_w=%u out_h=%u " + "fmt=%d yuvp=%d Bpp=%u filter_h=%d filter_v=%d", + __func__, win->idx, win->z, + dfixed_trunc(win->x), dfixed_trunc(win->y), + dfixed_trunc(win->w), dfixed_trunc(win->h), + win->out_x, win->out_y, win->out_w, win->out_h, + win->fmt, yuvp, Bpp, filter_h, filter_v); + trace_printk("%s:win%u in:%ux%u out:%ux%u fmt=%d\n", + dc->ndev->name, win->idx, dfixed_trunc(win->w), + dfixed_trunc(win->h), win->out_w, win->out_h, win->fmt); + } + + if (update_blend) { + tegra_dc_set_blending(dc, &dc->blend); + for (i = 0; i < DC_N_WINDOWS; i++) { + if (!no_vsync) + dc->windows[i].dirty = 1; + update_mask |= WIN_A_ACT_REQ << i; + } + } + + tegra_dc_set_dynamic_emc(windows, n); + + tegra_dc_writel(dc, update_mask << 8, DC_CMD_STATE_CONTROL); + + tegra_dc_writel(dc, FRAME_END_INT | V_BLANK_INT, DC_CMD_INT_STATUS); + if (!no_vsync) { + set_bit(V_BLANK_FLIP, &dc->vblank_ref_count); + tegra_dc_unmask_interrupt(dc, + FRAME_END_INT | V_BLANK_INT | ALL_UF_INT); + } else { + clear_bit(V_BLANK_FLIP, &dc->vblank_ref_count); + tegra_dc_mask_interrupt(dc, +#ifndef CONFIG_ANDROID + V_BLANK_INT | ALL_UF_INT); + if (!atomic_read(&frame_end_ref)) + tegra_dc_mask_interrupt(dc, FRAME_END_INT); +#else /* !CONFIG_ANDROID */ + FRAME_END_INT | V_BLANK_INT | ALL_UF_INT); +#endif /* !CONFIG_ANDROID */ + } + + 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)); + + /* 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; + + tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL); + trace_printk("%s:update_mask=%#lx\n", dc->ndev->name, update_mask); + + tegra_dc_release_dc_out(dc); + mutex_unlock(&dc->lock); + if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) + mutex_unlock(&dc->one_shot_lock); + + return 0; +} +EXPORT_SYMBOL(tegra_dc_update_windows); + +void tegra_dc_trigger_windows(struct tegra_dc *dc) +{ + u32 val, i; + u32 completed = 0; + u32 dirty = 0; + + val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); + for (i = 0; i < DC_N_WINDOWS; i++) { +#ifdef CONFIG_TEGRA_SIMULATION_PLATFORM + /* FIXME: this is not needed when the simulator + clears WIN_x_UPDATE bits as in HW */ + dc->windows[i].dirty = 0; + completed = 1; +#else + if (!(val & (WIN_A_ACT_REQ << i))) { + dc->windows[i].dirty = 0; + completed = 1; + } else { + dirty = 1; + } +#endif + } + + if (!dirty) { + if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) +#ifndef CONFIG_ANDROID + && !atomic_read(&frame_end_ref)) +#else /* !CONFIG_ANDROID */ + ) +#endif /* !CONFIG_ANDROID */ + tegra_dc_mask_interrupt(dc, FRAME_END_INT); + } + + if (completed) + wake_up(&dc->wq); +} + |