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

974 lines
25 KiB
C
Raw Normal View History

2022-02-16 09:13:02 -06:00
/*
* mode.c: dc and fb mode functions.
*
* Copyright (C) 2010 Google, Inc.
*
* Copyright (c) 2010-2019, 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 <trace/events/display.h>
#include <linux/platform/tegra/mc.h>
#include "dc.h"
#include "dc_reg.h"
#include "dc_priv.h"
#include "dsi.h"
#include "edid.h"
#define CHK(c, s, ...) do { \
if (unlikely(c)) { \
if (verbose) \
pr_err(s, ## __VA_ARGS__); \
return false; \
} \
} while (0)
/* 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 > 20 */
if (a + mode->h_sync_width + mode->h_back_porch <= 20)
a = 1 + 20 - 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, bool verbose)
{
/* Constraint 1: H_REF_TO_SYNC + H_SYNC_WIDTH + H_BACK_PORCH > 20. */
CHK(mode->h_ref_to_sync + mode->h_sync_width +
mode->h_back_porch <= 20,
"H_REF_TO_SYNC + H_SYNC_WIDTH + H_BACK_PORCH <= 20\n");
/* Constraint 2: V_REF_TO_SYNC + V_SYNC_WIDTH + V_BACK_PORCH > 1. */
CHK(mode->v_ref_to_sync + mode->v_sync_width + mode->v_back_porch <= 1,
"V_REF_TO_SYNC + V_SYNC_WIDTH + V_BACK_PORCH <= 1\n");
/* Constraint 3: V_FRONT_PORCH + V_SYNC_WIDTH + V_BACK_PORCH > 1
* (vertical blank). */
CHK(mode->v_front_porch + mode->v_sync_width + mode->v_back_porch <= 1,
"V_FRONT_PORCH + V_SYNC_WIDTH + V_BACK_PORCH <= 1\n");
/* Constraint 4: V_SYNC_WIDTH >= 1; H_SYNC_WIDTH >= 1. */
CHK(mode->v_sync_width < 1 || mode->h_sync_width < 1,
"V_SYNC_WIDTH >= 1; H_SYNC_WIDTH < 1\n");
/* Constraint 5: V_REF_TO_SYNC >= 1; H_REF_TO_SYNC >= 0. */
CHK(mode->v_ref_to_sync < 1 || mode->h_ref_to_sync < 0,
"V_REF_TO_SYNC >= 1; H_REF_TO_SYNC < 0\n");
/* Constraint 6: V_FRONT_PORCH >= (V_REF_TO_SYNC + 1) */
CHK(mode->v_front_porch < mode->v_ref_to_sync + 1,
"V_FRONT_PORCH < (V_REF_TO_SYNC + 1)\n");
/* Constraint 7: H_FRONT_PORCH >= (H_REF_TO_SYNC + 1) */
CHK(mode->h_front_porch < mode->h_ref_to_sync + 1,
"H_FRONT_PORCH < (H_REF_TO_SYNC + 1)\n");
/* Constraint 8: H_DISP_ACTIVE >= 16 */
CHK(mode->h_active < 16, "H_DISP_ACTIVE < 16\n");
/* Constraint 9: V_DISP_ACTIVE >= 16 */
CHK(mode->v_active < 16, "V_DISP_ACTIVE < 16\n");
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
* TODO: Extend to handle other refresh rates and pclk
*/
static inline int _tegra_dc_calc_refresh(long pclk, long h_total, long v_total)
{
long refresh;
if (!pclk || !h_total || !v_total || pclk < h_total)
return 0;
refresh = pclk / h_total;
refresh *= 1000;
refresh /= v_total;
return refresh;
}
/* return in 1000ths of a Hertz */
int tegra_dc_calc_refresh(const struct tegra_dc_mode *m)
{
long h_total, v_total;
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;
return _tegra_dc_calc_refresh(pclk, h_total, v_total);
}
/* return in 1000ths of a Hertz */
int tegra_dc_calc_fb_refresh(const struct fb_videomode *fbmode)
{
long h_total, v_total;
h_total = fbmode->xres + fbmode->right_margin +
fbmode->left_margin + fbmode->hsync_len;
v_total = fbmode->yres + fbmode->upper_margin +
fbmode->lower_margin + fbmode->vsync_len;
return _tegra_dc_calc_refresh(PICOS2KHZ(fbmode->pixclock) * 1000,
h_total, v_total);
}
static u8 _calc_default_avi_m(unsigned h_size, unsigned v_size)
{
#define is_avi_m( \
h_size, v_size, \
h_avi_m, v_avi_m) \
(((h_size) * (v_avi_m)) > ((v_size) * ((h_avi_m) - 1)) && \
((h_size) * (v_avi_m)) < ((v_size) * ((h_avi_m) + 1))) \
if (!h_size || !v_size)
pr_warn("invalid h_size %u or v_size %u\n", h_size, v_size);
if (is_avi_m(h_size, v_size, 256, 135))
return TEGRA_DC_MODE_AVI_M_256_135;
else if (is_avi_m(h_size, v_size, 64, 27))
return TEGRA_DC_MODE_AVI_M_64_27;
else if (is_avi_m(h_size, v_size, 16, 9))
return TEGRA_DC_MODE_AVI_M_16_9;
else if (is_avi_m(h_size, v_size, 4, 3))
return TEGRA_DC_MODE_AVI_M_4_3;
return TEGRA_DC_MODE_AVI_M_NO_DATA;
#undef is_avi_m
}
static u8 calc_default_avi_m(struct tegra_dc *dc)
{
#define EDID_AVI_M_256_135 91
#define EDID_AVI_M_64_27 138
#define EDID_AVI_M_16_9 79
#define EDID_AVI_M_4_3 34
if (dc->out) {
unsigned h_size = tegra_dc_get_out_width(dc);
unsigned v_size = tegra_dc_get_out_height(dc);
/* extract picture aspect ratio from real screen sizes */
if (h_size && v_size)
return _calc_default_avi_m(h_size, v_size);
/* assign edid data */
h_size = dc->out->h_size;
v_size = dc->out->v_size;
if (!h_size && !v_size)
return TEGRA_DC_MODE_AVI_M_NO_DATA;
/* edid has picture aspect ratio stored */
if (!h_size || !v_size) {
unsigned temp = h_size ? : v_size;
switch (temp) {
case EDID_AVI_M_256_135:
return TEGRA_DC_MODE_AVI_M_256_135;
case EDID_AVI_M_64_27:
return TEGRA_DC_MODE_AVI_M_64_27;
case EDID_AVI_M_16_9:
return TEGRA_DC_MODE_AVI_M_16_9;
case EDID_AVI_M_4_3:
return TEGRA_DC_MODE_AVI_M_4_3;
default:
/* unsupported picture aspect ratio */
return TEGRA_DC_MODE_AVI_M_NO_DATA;
};
}
}
return 0;
#undef EDID_AVI_M_4_3
#undef EDID_AVI_M_16_9
#undef EDID_AVI_M_64_27
#undef EDID_AVI_M_256_135
}
u32 tegra_dc_get_aspect_ratio(struct tegra_dc *dc)
{
u32 aspect_ratio = 0;
if (!dc)
return 0;
switch (calc_default_avi_m(dc)) {
case TEGRA_DC_MODE_AVI_M_256_135:
aspect_ratio = FB_FLAG_RATIO_256_135;
break;
case TEGRA_DC_MODE_AVI_M_64_27:
aspect_ratio = FB_FLAG_RATIO_64_27;
break;
case TEGRA_DC_MODE_AVI_M_16_9:
aspect_ratio = FB_FLAG_RATIO_16_9;
break;
case TEGRA_DC_MODE_AVI_M_4_3:
aspect_ratio = FB_FLAG_RATIO_4_3;
break;
default:
aspect_ratio = 0;
};
return aspect_ratio;
}
static bool check_yuv_timings(struct tegra_dc_mode *mode, bool verbose)
{
if (mode->vmode & FB_VMODE_BYPASS)
return true;
/*
* For YUV420/422, the following timing constraints must be enforced:
* 1) H_ACTIVE should be even
* 2) H_SYNC_WIDTH should be even
* 3) H_BACK_PORCH should be even
* 4) H_FRONT_PORCH should be even
*
* For YUV420 only, the following timing constraints must be enforced:
* 5) V_ACTIVE should be even
*/
if ((mode->vmode & FB_VMODE_Y422) ||
tegra_dc_is_yuv420_8bpc(mode)) {
/* Constraint 1 */
CHK(mode->h_active & 0x1, "H_ACTIVE not even for vmode: 0x%x\n",
mode->vmode);
/* Constraint 2 */
CHK(mode->h_sync_width & 0x1,
"H_SYNC_WIDTH not even for vmode: 0x%x\n", mode->vmode);
/* Constraint 3 */
CHK(mode->h_back_porch & 0x1,
"H_BACK_PORCH not even for vmode: 0x%x\n", mode->vmode);
/* Constraint 4 */
CHK(mode->h_front_porch & 0x1,
"H_FRONT_PORCH not even for vmode: 0x%x\n",
mode->vmode);
/* Constraint 5 */
if (tegra_dc_is_yuv420_8bpc(mode))
CHK(mode->v_active & 0x1,
"V_ACTIVE not even for vmode: 0x%x\n",
mode->vmode);
}
return true;
}
static bool check_t21x_mode_timings(const struct tegra_dc *dc,
struct tegra_dc_mode *mode, bool verbose)
{
if ((mode->vmode & FB_VMODE_Y420) ||
(mode->vmode & FB_VMODE_Y420_ONLY))
mode->v_ref_to_sync = 1;
else
calc_ref_to_sync(mode);
mode->h_ref_to_sync = 1;
if (dc->out->type == TEGRA_DC_OUT_DSI && dc->out->vrr) {
mode->h_ref_to_sync =
dc->out->modes[dc->out->n_modes - 1].h_ref_to_sync;
mode->v_ref_to_sync =
dc->out->modes[dc->out->n_modes - 1].v_ref_to_sync;
}
if (dc->out->type == TEGRA_DC_OUT_DP) {
mode->h_ref_to_sync = 1;
mode->v_ref_to_sync = 1;
}
if (!check_ref_to_sync(mode, verbose)) {
if (verbose)
dev_err(&dc->ndev->dev,
"Display timing doesn't meet restrictions.\n");
return false;
}
return true;
}
static bool check_nvdisp_mode_timings(const struct tegra_dc *dc,
struct tegra_dc_mode *mode, bool verbose)
{
int h_total, v_total;
/*
* Constraint 1: V_SYNC_WIDTH >= 1
* H_SYNC_WIDTH >= 1
* V_FRONT_PORCH >= 1
* H_FRONT_PORCH >= 1
*/
CHK(mode->v_sync_width < 1 || mode->h_sync_width < 1 ||
mode->v_front_porch < 1 || mode->h_front_porch < 1,
"{V,H}_SYNC_WIDTH >= 1, {V,H}_FRONT_PORCH >= 1\n");
/*
* Constraint 2: H_DISP_ACTIVE >= 8
* V_DISP_ACTIVE >= 4
*/
CHK(mode->h_active < 8 || mode->v_active < 4,
"H_DISP_ACTIVE >= 8, V_DISP_ACTIVE >= 4\n");
/*
* Constraint 3: H_TOTAL < 32768
* V_TOTAL < 32768
*/
h_total = mode->h_sync_width + mode->h_back_porch + mode->h_active +
mode->h_front_porch;
v_total = mode->v_sync_width + mode->v_back_porch + mode->v_active +
mode->v_front_porch;
CHK(h_total >= 32768 || v_total >= 32768,
"H_TOTAL < 32768, V_TOTAL < 32768\n");
/* Constraint 4: H_BLANK >= 25 */
CHK(h_total - mode->h_active < 25,
"H_BLANK >= 25\n");
return true;
}
static bool check_mode_timings(const struct tegra_dc *dc,
struct tegra_dc_mode *mode, bool verbose)
{
bool check;
if (!check_yuv_timings(mode, verbose))
return false;
if (tegra_dc_is_nvdisplay())
check = check_nvdisp_mode_timings(dc, mode, verbose);
else
check = check_t21x_mode_timings(dc, mode, verbose);
if (verbose)
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 check;
}
bool check_fb_videomode_timings(const struct tegra_dc *dc,
const struct fb_videomode *fbmode)
{
struct tegra_dc_mode mode;
if (!dc || !fbmode)
return false;
/* Only copy the relevant info */
mode.h_front_porch = fbmode->right_margin;
mode.v_front_porch = fbmode->lower_margin;
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.vmode = fbmode->vmode;
return check_mode_timings(dc, &mode, false);
}
#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;
tegra_dc_get(dc);
if (dc->out_ops && dc->out_ops->modeset_notifier)
dc->out_ops->modeset_notifier(dc);
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__);
/* 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_FAKE_DP) ||
(dc->out->type == TEGRA_DC_OUT_NULL) ||
(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 (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);
}
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_FAKE_DSIA ||
dc->out->type == TEGRA_DC_OUT_FAKE_DSIB ||
dc->out->type == TEGRA_DC_OUT_FAKE_DSI_GANGED ||
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);
if (pclk == 0) {
dev_err(&dc->ndev->dev, "pclk is zero!\n");
return -ERANGE;
}
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);
/* skip pclk range check for TEGRA_DC_OUT_NULL */
if (dc->out->type != TEGRA_DC_OUT_NULL) {
if (!pclk || pclk < (mode->pclk / 100 * 99) ||
pclk > (mode->pclk / 100 * 109)) {
dev_err(&dc->ndev->dev, "pclk out of range!\n");
return -EINVAL;
}
}
tegra_dc_writel(dc, PIXEL_CLK_DIVIDER_PCD1 | SHIFT_CLK_DIVIDER(div),
DC_DISP_DISP_CLOCK_CONTROL);
#ifdef CONFIG_SWITCH
if (dc->switchdev_registered)
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->mode_dirty)
memcpy(&dc->cached_mode, &dc->mode, sizeof(dc->mode));
tegra_dc_put(dc);
dc->mode_dirty = false;
trace_display_mode(dc, &dc->mode);
tegra_dc_ext_process_modechange(dc->ndev->id);
tegra_dc_client_handle_event(dc, NOTIFY_MODESET_EVENT);
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);
int _tegra_dc_set_mode(struct tegra_dc *dc,
const struct tegra_dc_mode *mode)
{
struct tegra_dc_mode new_mode = *mode;
bool yuv_bypass_vmode = false;
yuv_bypass_vmode = (new_mode.vmode & FB_VMODE_YUV_MASK) &&
(new_mode.vmode & FB_VMODE_BYPASS);
/*
* On T21x, the bypass flag is exclusively sent as part of the flip, and
* not as part of the modeset.
*/
if (yuv_bypass_vmode || tegra_dc_is_t21x()) {
if (tegra_dc_is_yuv420_8bpc(&new_mode)) {
new_mode.h_back_porch /= 2;
new_mode.h_front_porch /= 2;
new_mode.h_sync_width /= 2;
new_mode.h_active /= 2;
new_mode.pclk /= 2;
} else if (tegra_dc_is_yuv420_10bpc(&new_mode)) {
new_mode.h_back_porch = (new_mode.h_back_porch * 5) / 8;
new_mode.h_front_porch =
(new_mode.h_front_porch * 5) / 8;
new_mode.h_sync_width = (new_mode.h_sync_width * 5) / 8;
new_mode.h_active = (new_mode.h_active * 5) / 8;
new_mode.pclk = (new_mode.pclk / 8) * 5;
}
}
if (memcmp(&dc->mode, &new_mode, sizeof(dc->mode)) == 0) {
/* mode is unchanged, just return */
return 0;
}
memcpy(&dc->mode, &new_mode, sizeof(dc->mode));
dc->mode_dirty = true;
if (dc->out->type == TEGRA_DC_OUT_RGB)
panel_sync_rate = tegra_dc_calc_refresh(&new_mode);
else if (dc->out->type == TEGRA_DC_OUT_DSI)
panel_sync_rate = dc->out->dsi->rated_refresh_rate * 1000;
if (dc->out->type == TEGRA_DC_OUT_FAKE_DSIA ||
dc->out->type == TEGRA_DC_OUT_FAKE_DSIB ||
dc->out->type == TEGRA_DC_OUT_FAKE_DSI_GANGED) {
tegra_dsi_init_clock_param(dc);
}
/* Update cached mode */
memcpy(&dc->cached_mode, &dc->mode, sizeof(dc->mode));
print_mode(dc, &new_mode, __func__);
dc->frametime_ns = calc_frametime_ns(&new_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) {
/* 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;
}
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_256_135)
fbmode->flag |= FB_FLAG_RATIO_256_135;
else if (mode->avi_m == TEGRA_DC_MODE_AVI_M_64_27)
fbmode->flag |= FB_FLAG_RATIO_64_27;
else 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);
#if defined(CONFIG_FB_MODE_PIXCLOCK_HZ)
fbmode->pixclock_hz = mode_pclk;
#endif
}
fbmode->refresh = tegra_dc_calc_refresh(mode) / 1000;
return 0;
}
int tegra_dc_update_mode(struct tegra_dc *dc)
{
int ret = 0;
if (dc->mode_dirty) {
if (tegra_dc_is_nvdisplay())
ret = tegra_nvdisp_program_mode(dc, &dc->mode);
else
ret = tegra_dc_program_mode(dc, &dc->mode);
}
/* Update tracing constants */
if (!dc->mode_dirty) {
int line_width = dc->mode.h_sync_width +
dc->mode.h_back_porch +
dc->mode.h_active +
dc->mode.h_front_porch;
dc->mode_metadata.line_in_nsec = ((u64)line_width *
(u64)NSEC_PER_SEC) /
(u64)dc->mode.pclk;
dc->mode_metadata.vtotal_lines = dc->mode.v_sync_width +
dc->mode.v_back_porch +
dc->mode.v_active +
dc->mode.v_front_porch;
dc->mode_metadata.vblank_lines =
dc->mode_metadata.vtotal_lines -
dc->mode.v_front_porch;
}
return ret;
}
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));
#if defined(CONFIG_FB_MODE_PIXCLOCK_HZ)
if (fbmode->pixclock_hz) {
mode.pclk = fbmode->pixclock_hz;
mode.pclk_hz_used = true;
} else {
mode.pclk = PICOS2KHZ(fbmode->pixclock) * 1000;
mode.pclk_hz_used = false;
}
#else
mode.pclk = PICOS2KHZ(fbmode->pixclock) * 1000;
mode.pclk_hz_used = false;
#endif
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_256_135)
mode.avi_m = TEGRA_DC_MODE_AVI_M_256_135;
else if (fbmode->flag & FB_FLAG_RATIO_64_27)
mode.avi_m = TEGRA_DC_MODE_AVI_M_64_27;
else 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, true))
return -EINVAL;
/* 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;
}
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);
int tegra_dc_set_fbcon_boot_mode(struct tegra_dc *dc, struct tegra_edid *edid)
{
struct fb_monspecs specs;
int ret = 0;
specs.modedb = NULL;
if (tegra_fb_is_console_enabled(dc->pdata)) {
switch (dc->out->type) {
case TEGRA_DC_OUT_HDMI:
break;
case TEGRA_DC_OUT_DP:
if (!tegra_dc_is_ext_panel(dc))
return 0;
break;
default:
return -EINVAL;
}
/* In case of seamless display, dc mode would already be set */
if (!dc->initialized) {
if (dc->out->fbcon_default_mode) {
ret = tegra_dc_set_fb_mode(dc,
dc->out->fbcon_default_mode, false);
if (ret) {
dev_err(&dc->ndev->dev,
"%s: setting default fbcon mode failed,"
" err = %d\n", __func__, ret);
}
} else if (edid && tegra_dc_hpd(dc)) {
if (!tegra_edid_get_monspecs(edid, &specs)) {
ret = tegra_dc_set_fb_mode(dc,
specs.modedb, false);
if (ret) {
dev_err(&dc->ndev->dev,
"%s: set DC mode from modedb,"
" err = %d\n", __func__, ret);
}
} else {
/* Reading edid from monitor failed */
ret = tegra_dc_set_fb_mode(dc,
&tegra_dc_vga_mode, false);
if (ret) {
dev_err(&dc->ndev->dev,
"%s: no edid, set VGA mode,"
" err=%d\n", __func__, ret);
}
}
} else {
/* HPD not detected */
ret = tegra_dc_set_fb_mode(dc,
&tegra_dc_vga_mode, false);
if (ret) {
dev_err(&dc->ndev->dev,
"%s: fallback to VGA mode, err=%d\n",
__func__, ret);
}
}
}
}
if (specs.modedb != NULL)
kfree(specs.modedb);
return ret;
}
EXPORT_SYMBOL(tegra_dc_set_fbcon_boot_mode);