blob: b600c70286e36577ed02f6660540d75a7e5effab [file] [log] [blame]
/*
* drivers/video/tegra/dc/mode.c
*
* Copyright (C) 2010 Google, Inc.
*
* Copyright (c) 2010-2014, 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/clk.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/clk/tegra.h>
#include <drm/drm_mode.h>
#include <mach/dc.h>
#include <mach/mc.h>
#include <trace/events/display.h>
#include "dc_reg.h"
#include "dc_priv.h"
/* return non-zero if constraint is violated */
static int calc_h_ref_to_sync(const struct tegra_dc_mode *mode, int *href)
{
long a, b;
/* Constraint 5: H_REF_TO_SYNC >= 0 */
a = 0;
/* Constraint 6: H_FRONT_PORT >= (H_REF_TO_SYNC + 1) */
b = mode->h_front_porch - 1;
/* Constraint 1: H_REF_TO_SYNC + H_SYNC_WIDTH + H_BACK_PORCH > 11 */
if (a + mode->h_sync_width + mode->h_back_porch <= 11)
a = 1 + 11 - mode->h_sync_width - mode->h_back_porch;
/* check Constraint 1 and 6 */
if (a > b)
return 1;
/* Constraint 4: H_SYNC_WIDTH >= 1 */
if (mode->h_sync_width < 1)
return 4;
/* Constraint 7: H_DISP_ACTIVE >= 16 */
if (mode->h_active < 16)
return 7;
if (href) {
if (b > a && a % 2)
*href = a + 1; /* use smallest even value */
else
*href = a; /* even or only possible value */
}
return 0;
}
static int calc_v_ref_to_sync(const struct tegra_dc_mode *mode, int *vref)
{
long a;
/* Constraint 5: V_REF_TO_SYNC >= 1 */
a = mode->v_front_porch - 1;
/* Constraint 2: V_REF_TO_SYNC + V_SYNC_WIDTH + V_BACK_PORCH > 1 */
if (a + mode->v_sync_width + mode->v_back_porch <= 1)
a = 1 + 1 - mode->v_sync_width - mode->v_back_porch;
/* Constraint 6 */
if (mode->v_front_porch < a + 1)
a = mode->v_front_porch - 1;
/* Constraint 4: V_SYNC_WIDTH >= 1 */
if (mode->v_sync_width < 1)
return 4;
/* Constraint 7: V_DISP_ACTIVE >= 16 */
if (mode->v_active < 16)
return 7;
if (vref)
*vref = a;
return 0;
}
static int calc_ref_to_sync(struct tegra_dc_mode *mode)
{
int ret;
ret = calc_h_ref_to_sync(mode, &mode->h_ref_to_sync);
if (ret)
return ret;
ret = calc_v_ref_to_sync(mode, &mode->v_ref_to_sync);
if (ret)
return ret;
return 0;
}
static bool check_ref_to_sync(struct tegra_dc_mode *mode)
{
/* Constraint 1: H_REF_TO_SYNC + H_SYNC_WIDTH + H_BACK_PORCH > 11. */
if (mode->h_ref_to_sync + mode->h_sync_width + mode->h_back_porch <= 11)
return false;
/* Constraint 2: V_REF_TO_SYNC + V_SYNC_WIDTH + V_BACK_PORCH > 1. */
if (mode->v_ref_to_sync + mode->v_sync_width + mode->v_back_porch <= 1)
return false;
/* Constraint 3: V_FRONT_PORCH + V_SYNC_WIDTH + V_BACK_PORCH > 1
* (vertical blank). */
if (mode->v_front_porch + mode->v_sync_width + mode->v_back_porch <= 1)
return false;
/* Constraint 4: V_SYNC_WIDTH >= 1; H_SYNC_WIDTH >= 1. */
if (mode->v_sync_width < 1 || mode->h_sync_width < 1)
return false;
/* Constraint 5: V_REF_TO_SYNC >= 1; H_REF_TO_SYNC >= 0. */
if (mode->v_ref_to_sync < 1 || mode->h_ref_to_sync < 0)
return false;
/* Constraint 6: V_FRONT_PORT >= (V_REF_TO_SYNC + 1);
* H_FRONT_PORT >= (H_REF_TO_SYNC + 1). */
if (mode->v_front_porch < mode->v_ref_to_sync + 1 ||
mode->h_front_porch < mode->h_ref_to_sync + 1)
return false;
/* Constraint 7: H_DISP_ACTIVE >= 16; V_DISP_ACTIVE >= 16. */
if (mode->h_active < 16 || mode->v_active < 16)
return false;
return true;
}
static s64 calc_frametime_ns(const struct tegra_dc_mode *m)
{
long h_total, v_total;
h_total = m->h_active + m->h_front_porch + m->h_back_porch +
m->h_sync_width;
v_total = m->v_active + m->v_front_porch + m->v_back_porch +
m->v_sync_width;
return (!m->pclk) ? 0 : (s64)(div_s64(((s64)h_total * v_total *
1000000000ULL), m->pclk));
}
/* return in 1000ths of a Hertz */
int tegra_dc_calc_refresh(const struct tegra_dc_mode *m)
{
long h_total, v_total, refresh;
long pclk;
if (m->rated_pclk > 0)
pclk = m->rated_pclk;
else
pclk = m->pclk;
h_total = m->h_active + m->h_front_porch + m->h_back_porch +
m->h_sync_width;
v_total = m->v_active + m->v_front_porch + m->v_back_porch +
m->v_sync_width;
if (!pclk || !h_total || !v_total || pclk < h_total)
return 0;
refresh = pclk / h_total;
refresh *= 1000;
refresh /= v_total;
return refresh;
}
static u8 calc_default_avi_m(struct tegra_dc *dc)
{
if (dc->out) { /* if ratio is unspecified, detect a default */
unsigned h_size = dc->out->h_size;
unsigned v_size = dc->out->v_size;
/* get aspect ratio */
if (h_size * 18 > v_size * 31 && h_size * 18 < v_size * 33)
return TEGRA_DC_MODE_AVI_M_16_9;
if (h_size * 18 > v_size * 23 && h_size * 18 < v_size * 25)
return TEGRA_DC_MODE_AVI_M_4_3;
}
return 0;
}
static bool check_mode_timings(struct tegra_dc *dc, struct tegra_dc_mode *mode)
{
if (dc->out->type == TEGRA_DC_OUT_HDMI) {
/* HDMI controller requires h_ref=1, v_ref=1 */
mode->h_ref_to_sync = 1;
mode->v_ref_to_sync = 1;
} else {
calc_ref_to_sync(mode);
}
if (!check_ref_to_sync(mode)) {
dev_err(&dc->ndev->dev,
"Display timing doesn't meet restrictions.\n");
return false;
}
dev_dbg(&dc->ndev->dev, "Using mode %dx%d pclk=%d href=%d vref=%d\n",
mode->h_active, mode->v_active, mode->pclk,
mode->h_ref_to_sync, mode->v_ref_to_sync
);
return true;
}
#ifdef DEBUG
static void print_mode(struct tegra_dc *dc,
const struct tegra_dc_mode *mode, const char *note)
{
if (mode) {
int refresh = tegra_dc_calc_refresh(mode);
dev_info(&dc->ndev->dev, "%s():MODE:%dx%d@%d.%03uHz pclk=%d\n",
note ? note : "",
mode->h_active, mode->v_active,
refresh / 1000, refresh % 1000,
mode->pclk);
}
}
#else /* !DEBUG */
static inline void print_mode(struct tegra_dc *dc,
const struct tegra_dc_mode *mode, const char *note) { }
#endif /* DEBUG */
int tegra_dc_program_mode(struct tegra_dc *dc, struct tegra_dc_mode *mode)
{
unsigned long val;
unsigned long rate;
unsigned long div;
unsigned long pclk;
unsigned long v_back_porch;
unsigned long v_front_porch;
unsigned long v_sync_width;
unsigned long v_active;
v_back_porch = mode->v_back_porch;
v_front_porch = mode->v_front_porch;
v_sync_width = mode->v_sync_width;
v_active = mode->v_active;
if (mode->vmode == FB_VMODE_INTERLACED) {
v_back_porch /= 2;
v_front_porch /= 2;
v_sync_width /= 2;
v_active /= 2;
}
print_mode(dc, mode, __func__);
tegra_dc_get(dc);
/* use default EMC rate when switching modes */
#ifdef CONFIG_TEGRA_ISOMGR
dc->new_bw_kbps = tegra_dc_calc_min_bandwidth(dc);
#else
dc->new_bw_kbps = tegra_emc_freq_req_to_bw(
tegra_dc_get_default_emc_clk_rate(dc) / 1000);
#endif
tegra_dc_program_bandwidth(dc, true);
tegra_dc_writel(dc, 0x0, DC_DISP_DISP_TIMING_OPTIONS);
tegra_dc_writel(dc, mode->h_ref_to_sync | (mode->v_ref_to_sync << 16),
DC_DISP_REF_TO_SYNC);
tegra_dc_writel(dc, mode->h_sync_width | (v_sync_width << 16),
DC_DISP_SYNC_WIDTH);
if ((dc->out->type == TEGRA_DC_OUT_DP) ||
(dc->out->type == TEGRA_DC_OUT_NVSR_DP) ||
(dc->out->type == TEGRA_DC_OUT_LVDS)) {
tegra_dc_writel(dc, mode->h_back_porch |
((v_back_porch - mode->v_ref_to_sync) << 16),
DC_DISP_BACK_PORCH);
tegra_dc_writel(dc, mode->h_front_porch |
((v_front_porch + mode->v_ref_to_sync) << 16),
DC_DISP_FRONT_PORCH);
} else {
tegra_dc_writel(dc, mode->h_back_porch |
(v_back_porch << 16),
DC_DISP_BACK_PORCH);
tegra_dc_writel(dc, mode->h_front_porch |
(v_front_porch << 16),
DC_DISP_FRONT_PORCH);
}
tegra_dc_writel(dc, mode->h_active | (v_active << 16),
DC_DISP_DISP_ACTIVE);
#if defined(CONFIG_TEGRA_DC_INTERLACE)
if (mode->vmode == FB_VMODE_INTERLACED)
tegra_dc_writel(dc, INTERLACE_MODE_ENABLE |
INTERLACE_START_FIELD_1
| INTERLACE_STATUS_FIELD_1,
DC_DISP_INTERLACE_CONTROL);
else
tegra_dc_writel(dc, INTERLACE_MODE_DISABLE,
DC_DISP_INTERLACE_CONTROL);
if (mode->vmode == FB_VMODE_INTERLACED) {
tegra_dc_writel(dc, (mode->h_ref_to_sync |
((mode->h_sync_width + mode->h_back_porch +
mode->h_active + mode->h_front_porch) >> 1)
<< 16), DC_DISP_INTERLACE_FIELD2_REF_TO_SYNC);
tegra_dc_writel(dc, mode->h_sync_width |
(v_sync_width << 16),
DC_DISP_INTERLACE_FIELD2_SYNC_WIDTH);
tegra_dc_writel(dc, mode->h_back_porch |
((v_back_porch + 1) << 16),
DC_DISP_INTERLACE_FIELD2_BACK_PORCH);
tegra_dc_writel(dc, mode->h_active |
(v_active << 16),
DC_DISP_INTERLACE_FIELD2_DISP_ACTIVE);
tegra_dc_writel(dc, mode->h_front_porch |
(v_front_porch << 16),
DC_DISP_INTERLACE_FIELD2_FRONT_PORCH);
}
#endif
tegra_dc_writel(dc, DE_SELECT_ACTIVE | DE_CONTROL_NORMAL,
DC_DISP_DATA_ENABLE_OPTIONS);
/* TODO: MIPI/CRT/HDMI clock cals */
val = 0;
if (!(dc->out->type == TEGRA_DC_OUT_DSI ||
dc->out->type == TEGRA_DC_OUT_HDMI)) {
val = DISP_DATA_FORMAT_DF1P1C;
if (dc->out->align == TEGRA_DC_ALIGN_MSB)
val |= DISP_DATA_ALIGNMENT_MSB;
else
val |= DISP_DATA_ALIGNMENT_LSB;
if (dc->out->order == TEGRA_DC_ORDER_RED_BLUE)
val |= DISP_DATA_ORDER_RED_BLUE;
else
val |= DISP_DATA_ORDER_BLUE_RED;
}
tegra_dc_writel(dc, val, DC_DISP_DISP_INTERFACE_CONTROL);
rate = tegra_dc_clk_get_rate(dc);
pclk = tegra_dc_pclk_round_rate(dc, mode->pclk);
div = (rate * 2 / pclk) - 2;
dev_info(&dc->ndev->dev,
"nominal-pclk:%d parent:%lu div:%lu.%lu pclk:%lu %d~%d\n",
mode->pclk, rate, (div + 2) / 2, ((div + 2) % 2) * 5, pclk,
mode->pclk / 100 * 99, mode->pclk / 100 * 109);
if (!pclk || pclk < (mode->pclk / 100 * 99) ||
pclk > (mode->pclk / 100 * 109)) {
dev_err(&dc->ndev->dev, "pclk out of range!\n");
return -EINVAL;
}
/* SW WAR for bug 1045373. To make the shift clk dividor effect under
* all circumstances, write N+2 to SHIFT_CLK_DIVIDER and activate it.
* After 2us delay, write the target values to it. */
#if defined(CONFIG_ARCH_TEGRA_14x_SOC)
tegra_dc_writel(dc, PIXEL_CLK_DIVIDER_PCD1 | SHIFT_CLK_DIVIDER(div + 2),
DC_DISP_DISP_CLOCK_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
udelay(2);
#endif
#if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC)
/* Deprecated on t11x and t14x. */
tegra_dc_writel(dc, 0x00010001,
DC_DISP_SHIFT_CLOCK_OPTIONS);
#endif
tegra_dc_writel(dc, PIXEL_CLK_DIVIDER_PCD1 | SHIFT_CLK_DIVIDER(div),
DC_DISP_DISP_CLOCK_CONTROL);
#ifdef CONFIG_SWITCH
switch_set_state(&dc->modeset_switch,
(mode->h_active << 16) | mode->v_active);
#endif
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
if (dc->out_ops && dc->out_ops->modeset_notifier)
dc->out_ops->modeset_notifier(dc);
tegra_dc_put(dc);
dc->mode_dirty = false;
trace_display_mode(dc, &dc->mode);
return 0;
}
static int panel_sync_rate;
int tegra_dc_get_panel_sync_rate(void)
{
return panel_sync_rate;
}
EXPORT_SYMBOL(tegra_dc_get_panel_sync_rate);
static int _tegra_dc_set_mode(struct tegra_dc *dc,
const struct tegra_dc_mode *mode)
{
if (memcmp(&dc->mode, mode, sizeof(dc->mode)) == 0) {
/* mode is unchanged, just return */
return 0;
}
memcpy(&dc->mode, mode, sizeof(dc->mode));
dc->mode_dirty = true;
if (dc->out->type == TEGRA_DC_OUT_RGB)
panel_sync_rate = tegra_dc_calc_refresh(mode);
else if (dc->out->type == TEGRA_DC_OUT_DSI)
panel_sync_rate = dc->out->dsi->rated_refresh_rate * 1000;
print_mode(dc, mode, __func__);
dc->frametime_ns = calc_frametime_ns(mode);
return 0;
}
int tegra_dc_set_mode(struct tegra_dc *dc, const struct tegra_dc_mode *mode)
{
mutex_lock(&dc->lock);
_tegra_dc_set_mode(dc, mode);
mutex_unlock(&dc->lock);
return 0;
}
EXPORT_SYMBOL(tegra_dc_set_mode);
int tegra_dc_to_fb_videomode(struct fb_videomode *fbmode,
const struct tegra_dc_mode *mode)
{
long mode_pclk;
if (!fbmode || !mode || !mode->pclk) {
if (fbmode)
memset(fbmode, 0, sizeof(*fbmode));
return -EINVAL;
}
if (mode->rated_pclk >= 1000) /* handle DSI one-shot modes */
mode_pclk = mode->rated_pclk;
else if (mode->pclk >= 1000) /* normal continous modes */
mode_pclk = mode->pclk;
else
mode_pclk = 0;
memset(fbmode, 0, sizeof(*fbmode));
fbmode->right_margin = mode->h_front_porch;
fbmode->lower_margin = mode->v_front_porch;
fbmode->hsync_len = mode->h_sync_width;
fbmode->vsync_len = mode->v_sync_width;
fbmode->left_margin = mode->h_back_porch;
fbmode->upper_margin = mode->v_back_porch;
fbmode->xres = mode->h_active;
fbmode->yres = mode->v_active;
fbmode->vmode = mode->vmode;
if (mode->stereo_mode) {
#ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT
/* Double the pixel clock and update v_active only for
* frame packed mode */
mode_pclk /= 2;
/* total v_active = yres*2 + activespace */
fbmode->yres = (mode->v_active - mode->v_sync_width -
mode->v_back_porch - mode->v_front_porch) / 2;
fbmode->vmode |= FB_VMODE_STEREO_FRAME_PACK;
#else
fbmode->vmode |= FB_VMODE_STEREO_LEFT_RIGHT;
#endif
}
if (!(mode->flags & TEGRA_DC_MODE_FLAG_NEG_H_SYNC))
fbmode->sync |= FB_SYNC_HOR_HIGH_ACT;
if (!(mode->flags & TEGRA_DC_MODE_FLAG_NEG_V_SYNC))
fbmode->sync |= FB_SYNC_VERT_HIGH_ACT;
if (mode->avi_m == TEGRA_DC_MODE_AVI_M_16_9)
fbmode->flag |= FB_FLAG_RATIO_16_9;
else if (mode->avi_m == TEGRA_DC_MODE_AVI_M_4_3)
fbmode->flag |= FB_FLAG_RATIO_4_3;
if (mode_pclk >= 1000) /* else 0 */
fbmode->pixclock = KHZ2PICOS(mode_pclk / 1000);
fbmode->refresh = tegra_dc_calc_refresh(mode) / 1000;
return 0;
}
int tegra_dc_update_mode(struct tegra_dc *dc)
{
if (dc->mode_dirty)
return tegra_dc_program_mode(dc, &dc->mode);
return 0;
}
int tegra_dc_set_drm_mode(struct tegra_dc *dc,
const struct drm_mode_modeinfo *dmode, bool stereo_mode)
{
struct tegra_dc_mode mode;
if (!dmode->clock)
return -EINVAL;
memset(&mode, 0, sizeof(mode));
mode.pclk = dmode->clock * 1000;
mode.h_sync_width = dmode->hsync_end - dmode->hsync_start;
mode.v_sync_width = dmode->vsync_end - dmode->vsync_start;
mode.h_back_porch = dmode->htotal - dmode->hsync_end;
mode.v_back_porch = dmode->vtotal - dmode->vsync_end;
mode.h_active = dmode->hdisplay;
mode.v_active = dmode->vdisplay;
mode.h_front_porch = dmode->hsync_start - dmode->hdisplay;
mode.v_front_porch = dmode->vsync_start - dmode->vdisplay;
mode.stereo_mode = stereo_mode;
if (dmode->flags & DRM_MODE_FLAG_INTERLACE)
mode.vmode |= FB_VMODE_INTERLACED;
mode.avi_m = calc_default_avi_m(dc);
if (!check_mode_timings(dc, &mode))
return -EINVAL;
#ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT
/* Double the pixel clock and update v_active only for
* frame packed mode */
if (mode.stereo_mode) {
mode.pclk *= 2;
/* total v_active = yres*2 + activespace */
mode.v_active = dmode->vtotal + dmode->vdisplay;
}
#endif
mode.flags = 0;
if (!(dmode->flags & DRM_MODE_FLAG_PHSYNC))
mode.flags |= TEGRA_DC_MODE_FLAG_NEG_H_SYNC;
if (!(dmode->flags & DRM_MODE_FLAG_PVSYNC))
mode.flags |= TEGRA_DC_MODE_FLAG_NEG_V_SYNC;
return tegra_dc_set_mode(dc, &mode);
}
EXPORT_SYMBOL(tegra_dc_set_drm_mode);
int tegra_dc_set_fb_mode(struct tegra_dc *dc,
const struct fb_videomode *fbmode, bool stereo_mode)
{
struct tegra_dc_mode mode;
if (!fbmode->pixclock)
return -EINVAL;
memset(&mode, 0, sizeof(mode));
mode.pclk = PICOS2KHZ(fbmode->pixclock) * 1000;
mode.h_sync_width = fbmode->hsync_len;
mode.v_sync_width = fbmode->vsync_len;
mode.h_back_porch = fbmode->left_margin;
mode.v_back_porch = fbmode->upper_margin;
mode.h_active = fbmode->xres;
mode.v_active = fbmode->yres;
mode.h_front_porch = fbmode->right_margin;
mode.v_front_porch = fbmode->lower_margin;
mode.stereo_mode = stereo_mode;
mode.vmode = fbmode->vmode;
if (fbmode->flag & FB_FLAG_RATIO_16_9)
mode.avi_m = TEGRA_DC_MODE_AVI_M_16_9;
else if (fbmode->flag & FB_FLAG_RATIO_4_3)
mode.avi_m = TEGRA_DC_MODE_AVI_M_4_3;
else
mode.avi_m = calc_default_avi_m(dc);
if (!check_mode_timings(dc, &mode))
return -EINVAL;
#ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT
/* Double the pixel clock and update v_active only for
* frame packed mode */
if (mode.stereo_mode) {
mode.pclk *= 2;
/* total v_active = yres*2 + activespace */
mode.v_active = fbmode->yres * 2 +
fbmode->vsync_len +
fbmode->upper_margin +
fbmode->lower_margin;
}
#endif
mode.flags = 0;
if (!(fbmode->sync & FB_SYNC_HOR_HIGH_ACT))
mode.flags |= TEGRA_DC_MODE_FLAG_NEG_H_SYNC;
if (!(fbmode->sync & FB_SYNC_VERT_HIGH_ACT))
mode.flags |= TEGRA_DC_MODE_FLAG_NEG_V_SYNC;
return _tegra_dc_set_mode(dc, &mode);
}
EXPORT_SYMBOL(tegra_dc_set_fb_mode);