2307 lines
63 KiB
C
2307 lines
63 KiB
C
/*
|
|
* sor.c: Functions implementing tegra dc sor interface.
|
|
*
|
|
* Copyright (c) 2011-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/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/nvhost.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/fb.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/tegra_prod.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/version.h>
|
|
#include <linux/tegra_pm_domains.h>
|
|
#include <uapi/video/tegra_dc_ext.h>
|
|
|
|
#include "dc.h"
|
|
#include "sor.h"
|
|
#include "sor_regs.h"
|
|
#include "dc_priv.h"
|
|
#include "dp.h"
|
|
#include "dc_common.h"
|
|
|
|
static const struct tegra_dc_sor_link_speed link_speed_table[] = {
|
|
[TEGRA_DC_SOR_LINK_SPEED_G1_62] = {
|
|
.prod_prop = "prod_c_rbr",
|
|
.max_link_bw = 1620,
|
|
.link_rate = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_1_62_GBPS,
|
|
},
|
|
[TEGRA_DC_SOR_LINK_SPEED_G2_7] = {
|
|
.prod_prop = "prod_c_hbr",
|
|
.max_link_bw = 2700,
|
|
.link_rate = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_2_70_GBPS,
|
|
},
|
|
[TEGRA_DC_SOR_LINK_SPEED_G5_4] = {
|
|
.prod_prop = "prod_c_hbr2",
|
|
.max_link_bw = 5400,
|
|
.link_rate = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_5_40_GBPS,
|
|
},
|
|
[TEGRA_DC_SOR_LINK_SPEED_G8_1] = {
|
|
.prod_prop = "prod_c_hbr3",
|
|
.max_link_bw = 8100,
|
|
.link_rate = NV_DPCD_MAX_LINK_BANDWIDTH_VAL_8_10_GBPS,
|
|
},
|
|
};
|
|
|
|
static const struct tegra_dc_dp_training_pattern training_pattern_table[] = {
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_DISABLE] = {
|
|
.chan_coding = true,
|
|
.scrambling = true,
|
|
.dpcd_val = NV_DPCD_TRAINING_PATTERN_SET_TPS_NONE |
|
|
NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_F,
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_NOPATTERN,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_1] = {
|
|
.chan_coding = true,
|
|
.scrambling = false,
|
|
.dpcd_val = NV_DPCD_TRAINING_PATTERN_SET_TPS_TP1 |
|
|
NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_T,
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_TRAINING1,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_2] = {
|
|
.chan_coding = true,
|
|
.scrambling = false,
|
|
.dpcd_val = NV_DPCD_TRAINING_PATTERN_SET_TPS_TP2 |
|
|
NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_T,
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_TRAINING2,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_3] = {
|
|
.chan_coding = true,
|
|
.scrambling = false,
|
|
.dpcd_val = NV_DPCD_TRAINING_PATTERN_SET_TPS_TP3 |
|
|
NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_T,
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_TRAINING3,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_D102] = {
|
|
.chan_coding = true,
|
|
.scrambling = false,
|
|
.dpcd_val = 0, /* unused */
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_D102,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_SBLERRRATE] = {
|
|
.chan_coding = true,
|
|
.scrambling = true,
|
|
.dpcd_val = 0, /* unused */
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_SBLERRRATE,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_PRBS7] = {
|
|
.chan_coding = false,
|
|
.scrambling = false,
|
|
.dpcd_val = 0, /* unused */
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_PRBS7,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_CSTM] = {
|
|
.chan_coding = false,
|
|
.scrambling = false,
|
|
.dpcd_val = 0, /* unused */
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_CSTM,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_HBR2_COMPLIANCE] = {
|
|
.chan_coding = true,
|
|
.scrambling = true,
|
|
.dpcd_val = 0, /* unused */
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_HBR2_COMPLIANCE,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_CP2520_PAT1] = {
|
|
.chan_coding = true,
|
|
.scrambling = true,
|
|
.dpcd_val = 0, /* unused */
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_CP2520_PAT1,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_CP2520_PAT3] = {
|
|
.chan_coding = true,
|
|
.scrambling = true,
|
|
.dpcd_val = 0, /* unused */
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_CP2520_PAT3,
|
|
},
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_4] = {
|
|
.chan_coding = true,
|
|
.scrambling = true,
|
|
.dpcd_val = NV_DPCD_TRAINING_PATTERN_SET_TPS_TP4 |
|
|
NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_F,
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_TRAINING4,
|
|
},
|
|
/*
|
|
* On T194, the HW pattern generator sends the RD_RESET (running
|
|
* disparity reset) signal one clock cycle too early. This specifically
|
|
* affects TPS4 since TPS4 requires scrambling, so whatever symbol is
|
|
* sent during this early cycle will be random. As a result, the RD of
|
|
* the first symbol of TPS4 will be random as well.
|
|
*
|
|
* In order to WAR this issue, we will send a custom BS (blanking start)
|
|
* pattern when switching from TPS1 to TPS4 during equalization in order
|
|
* to control what the RD of the "early symbol" will be.
|
|
*/
|
|
[TEGRA_DC_DP_TRAINING_PATTERN_BS_CSTM] = {
|
|
.chan_coding = true,
|
|
.scrambling = true,
|
|
.dpcd_val = 0, /* unused */
|
|
.sor_reg_val = NV_SOR_DP_TPG_LANE0_PATTERN_CSTM,
|
|
},
|
|
};
|
|
|
|
static struct of_device_id tegra_sor_pd[] = {
|
|
{ .compatible = "nvidia,tegra210-sor-pd", },
|
|
{ .compatible = "nvidia,tegra186-disa-pd", },
|
|
{ .compatible = "nvidia,tegra194-disa-pd", },
|
|
{},
|
|
};
|
|
|
|
static struct tegra_dc_mode min_mode = {
|
|
.h_ref_to_sync = 0,
|
|
.v_ref_to_sync = 1,
|
|
.h_sync_width = 1,
|
|
.v_sync_width = 1,
|
|
.h_back_porch = 20,
|
|
.v_back_porch = 0,
|
|
.h_active = 16,
|
|
.v_active = 16,
|
|
.h_front_porch = 1,
|
|
.v_front_porch = 2,
|
|
};
|
|
|
|
unsigned long
|
|
tegra_dc_sor_poll_register(struct tegra_dc_sor_data *sor,
|
|
u32 reg, u32 mask, u32 exp_val,
|
|
u32 poll_interval_us, u32 timeout_ms)
|
|
{
|
|
unsigned long timeout_jf = jiffies + msecs_to_jiffies(timeout_ms);
|
|
u32 reg_val = 0;
|
|
|
|
if (tegra_platform_is_vdk())
|
|
return 0;
|
|
|
|
do {
|
|
reg_val = tegra_sor_readl(sor, reg);
|
|
if ((reg_val & mask) == exp_val)
|
|
return 0; /* success */
|
|
|
|
udelay(poll_interval_us);
|
|
} while (time_after(timeout_jf, jiffies));
|
|
|
|
dev_err(&sor->dc->ndev->dev,
|
|
"sor_poll_register 0x%x: timeout\n", reg);
|
|
|
|
return jiffies - timeout_jf + 1;
|
|
}
|
|
|
|
void tegra_sor_config_safe_clk(struct tegra_dc_sor_data *sor)
|
|
{
|
|
struct clk *clk;
|
|
int flag;
|
|
|
|
/*
|
|
* For nvdisplay, sor->sor_clk was previously being used as the SOR
|
|
* reference clk instead of the orclk. In order to be consistent with
|
|
* the previous naming scheme, I'm using sor->ref_clk here to avoid
|
|
* breaking existing drivers. This needs to be cleaned up later.
|
|
*/
|
|
clk = (tegra_dc_is_nvdisplay()) ? sor->ref_clk : sor->sor_clk;
|
|
flag = tegra_dc_is_clk_enabled(clk);
|
|
|
|
if (sor->clk_type == TEGRA_SOR_SAFE_CLK)
|
|
return;
|
|
|
|
/*
|
|
* HW bug 1425607
|
|
* Disable clocks to avoid glitch when switching
|
|
* between safe clock and macro pll clock
|
|
*/
|
|
if (flag)
|
|
tegra_sor_clk_disable(sor);
|
|
|
|
if (tegra_platform_is_silicon())
|
|
clk_set_parent(sor->sor_clk, sor->safe_clk);
|
|
|
|
if (flag)
|
|
tegra_sor_clk_enable(sor);
|
|
|
|
sor->clk_type = TEGRA_SOR_SAFE_CLK;
|
|
}
|
|
|
|
void tegra_sor_config_dp_clk_t21x(struct tegra_dc_sor_data *sor)
|
|
{
|
|
int flag = tegra_dc_is_clk_enabled(sor->sor_clk);
|
|
struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(sor->dc);
|
|
const int64_t pll_dp_rate = 270000000; /* fixed pll_dp@270MHz */
|
|
|
|
if (sor->clk_type == TEGRA_SOR_MACRO_CLK)
|
|
return;
|
|
|
|
/*
|
|
* HW bug 1425607
|
|
* Disable clocks to avoid glitch when switching
|
|
* between safe clock and macro pll clock
|
|
*
|
|
* Select alternative -- DP -- DVFS table for SOR clock (if SOR clock
|
|
* has single DVFS table for all modes, nothing changes).
|
|
*/
|
|
if (flag)
|
|
tegra_sor_clk_disable(sor);
|
|
|
|
#ifdef CONFIG_TEGRA_CORE_DVFS
|
|
tegra_dvfs_use_alt_freqs_on_clk(sor->sor_clk, true);
|
|
#endif
|
|
|
|
#ifdef CONFIG_TEGRA_CLK_FRAMEWORK
|
|
if (tegra_platform_is_silicon())
|
|
tegra_clk_cfg_ex(sor->sor_clk, TEGRA_CLK_SOR_CLK_SEL, 1);
|
|
#else
|
|
if (sor->ctrl_num == 0)
|
|
/* For DP on sor0, set pll_dp as parent of sor clock */
|
|
clk_set_parent(sor->sor_clk, dp->parent_clk);
|
|
else
|
|
/* For DP on sor1, set sor1 pad output clk to be the parent */
|
|
clk_set_parent(sor->sor_clk, sor->pad_clk);
|
|
#endif
|
|
|
|
if (flag)
|
|
tegra_sor_clk_enable(sor);
|
|
|
|
sor->clk_type = TEGRA_SOR_MACRO_CLK;
|
|
|
|
/*
|
|
* Set the pad_clk so that clock rate and DVFS are upto date.
|
|
* Divide link clock by 10 to get sor clock.
|
|
*/
|
|
clk_set_rate(sor->pad_clk, (pll_dp_rate * dp->link_cfg.link_bw / 10));
|
|
|
|
}
|
|
|
|
int tegra_dc_sor_crc_get(struct tegra_dc_sor_data *sor, u32 *crc)
|
|
{
|
|
int ret = 0;
|
|
u32 val;
|
|
|
|
tegra_dc_io_start(sor->dc);
|
|
tegra_sor_clk_enable(sor);
|
|
|
|
val = tegra_sor_readl(sor, NV_SOR_CRCA);
|
|
val = (val & NV_SOR_CRCA_VALID_DEFAULT_MASK) >> NV_SOR_CRCA_VALID_SHIFT;
|
|
if (val != NV_SOR_CRCA_VALID_TRUE) {
|
|
ret = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
*crc = tegra_sor_readl(sor, NV_SOR_CRCB);
|
|
tegra_sor_writel(sor, NV_SOR_CRCA, NV_SOR_CRCA_VALID_RST);
|
|
|
|
done:
|
|
tegra_sor_clk_disable(sor);
|
|
tegra_dc_io_end(sor->dc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
u32 tegra_dc_sor_debugfs_get_crc(struct tegra_dc_sor_data *sor, int *timeout)
|
|
{
|
|
struct tegra_dc *dc = sor->dc;
|
|
u32 reg_val;
|
|
|
|
tegra_dc_io_start(sor->dc);
|
|
tegra_sor_clk_enable(sor);
|
|
|
|
reg_val = tegra_sor_readl(sor, NV_SOR_CRC_CNTRL);
|
|
reg_val &= NV_SOR_CRC_CNTRL_ARM_CRC_ENABLE_DEFAULT_MASK;
|
|
if (reg_val == NV_SOR_CRC_CNTRL_ARM_CRC_ENABLE_NO) {
|
|
pr_err("SOR CRC is DISABLED, aborting with CRC=0\n");
|
|
goto exit;
|
|
}
|
|
if (tegra_dc_sor_poll_register(sor, NV_SOR_CRCA,
|
|
NV_SOR_CRCA_VALID_DEFAULT_MASK,
|
|
NV_SOR_CRCA_VALID_TRUE,
|
|
100, TEGRA_SOR_TIMEOUT_MS)) {
|
|
dev_err(&sor->dc->ndev->dev,
|
|
"NV_SOR[%d]_CRCA_VALID_TRUE timeout\n", sor->ctrl_num);
|
|
if (timeout)
|
|
*timeout = 1;
|
|
goto exit;
|
|
}
|
|
mutex_lock(&dc->lock);
|
|
reg_val = tegra_sor_readl(sor, NV_SOR_CRCB);
|
|
mutex_unlock(&dc->lock);
|
|
|
|
exit:
|
|
tegra_sor_clk_disable(sor);
|
|
tegra_dc_io_end(sor->dc);
|
|
return reg_val;
|
|
}
|
|
|
|
void tegra_dc_sor_crc_en_dis(struct tegra_dc_sor_data *sor,
|
|
struct tegra_dc_ext_crc_sor_params params, bool en)
|
|
{
|
|
u32 reg;
|
|
|
|
tegra_dc_io_start(sor->dc);
|
|
tegra_sor_clk_enable(sor);
|
|
|
|
if (en) {
|
|
reg = NV_SOR_CRCA_VALID_RST << NV_SOR_CRCA_VALID_SHIFT;
|
|
tegra_sor_write_field(sor, NV_SOR_CRCA,
|
|
NV_SOR_CRCA_VALID_DEFAULT_MASK, reg);
|
|
|
|
reg = params.stage << NV_SOR_TEST_CRC_SHIFT;
|
|
tegra_sor_write_field(sor, NV_SOR_TEST,
|
|
NV_SOR_TEST_CRC_DEFAULT_MASK, reg);
|
|
|
|
reg = params.data << NV_SOR_STATE1_ASY_CRCMODE_SHIFT;
|
|
tegra_sor_write_field(sor, NV_SOR_STATE1,
|
|
NV_SOR_STATE1_ASY_CRCMODE_DEFAULT_MASK,
|
|
reg);
|
|
|
|
reg = NV_SOR_STATE0_UPDATE_UPDATE << NV_SOR_STATE0_UPDATE_SHIFT;
|
|
tegra_sor_write_field(sor, NV_SOR_STATE0,
|
|
NV_SOR_STATE0_UPDATE_DEFAULT_MASK, reg);
|
|
}
|
|
|
|
tegra_sor_readl(sor, NV_SOR_CRC_CNTRL);
|
|
reg = en << NV_SOR_CRC_CNTRL_ARM_CRC_ENABLE_SHIFT;
|
|
tegra_sor_write_field(sor, NV_SOR_CRC_CNTRL,
|
|
NV_SOR_CRC_CNTRL_ARM_CRC_ENABLE_DEFAULT_MASK,
|
|
reg);
|
|
tegra_sor_readl(sor, NV_SOR_CRC_CNTRL);
|
|
|
|
tegra_sor_clk_disable(sor);
|
|
tegra_dc_io_end(sor->dc);
|
|
}
|
|
|
|
void tegra_dc_sor_toggle_crc(struct tegra_dc_sor_data *sor, u32 val)
|
|
{
|
|
struct tegra_dc_ext_crc_sor_params params;
|
|
|
|
params.stage = TEGRA_DC_EXT_CRC_SOR_STAGE_PRE_SERIALIZE;
|
|
params.data = val & NV_SOR_STATE1_ASY_CRCMODE_DEFAULT_MASK;
|
|
params.data >>= NV_SOR_STATE1_ASY_CRCMODE_SHIFT;
|
|
|
|
tegra_dc_sor_crc_en_dis(sor, params, val & 0x1);
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int dbg_sor_show(struct seq_file *s, void *unused)
|
|
{
|
|
struct tegra_dc_sor_data *sor = s->private;
|
|
int hdmi_dump = 0;
|
|
|
|
#define DUMP_REG(a) seq_printf(s, "%-32s %03x %08x\n", \
|
|
#a, a, tegra_sor_readl(sor, a));
|
|
|
|
if (tegra_dc_is_t21x()) {
|
|
if (!tegra_powergate_is_powered(sor->powergate_id)) {
|
|
seq_puts(s, "SOR is powergated\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
tegra_dc_io_start(sor->dc);
|
|
tegra_sor_clk_enable(sor);
|
|
|
|
DUMP_REG(NV_SOR_SUPER_STATE0);
|
|
DUMP_REG(NV_SOR_SUPER_STATE1);
|
|
DUMP_REG(NV_SOR_STATE0);
|
|
DUMP_REG(NV_SOR_STATE1);
|
|
DUMP_REG(nv_sor_head_state0(0));
|
|
DUMP_REG(nv_sor_head_state0(1));
|
|
DUMP_REG(nv_sor_head_state1(0));
|
|
DUMP_REG(nv_sor_head_state1(1));
|
|
DUMP_REG(nv_sor_head_state2(0));
|
|
DUMP_REG(nv_sor_head_state2(1));
|
|
DUMP_REG(nv_sor_head_state3(0));
|
|
DUMP_REG(nv_sor_head_state3(1));
|
|
DUMP_REG(nv_sor_head_state4(0));
|
|
DUMP_REG(nv_sor_head_state4(1));
|
|
DUMP_REG(nv_sor_head_state5(0));
|
|
DUMP_REG(nv_sor_head_state5(1));
|
|
DUMP_REG(NV_SOR_CRC_CNTRL);
|
|
DUMP_REG(NV_SOR_CLK_CNTRL);
|
|
DUMP_REG(NV_SOR_CAP);
|
|
DUMP_REG(NV_SOR_PWR);
|
|
DUMP_REG(NV_SOR_TEST);
|
|
DUMP_REG(nv_sor_pll0());
|
|
DUMP_REG(nv_sor_pll1());
|
|
DUMP_REG(nv_sor_pll2());
|
|
DUMP_REG(nv_sor_pll3());
|
|
if (tegra_dc_is_nvdisplay())
|
|
DUMP_REG(nv_sor_pll4());
|
|
if (tegra_dc_is_t19x())
|
|
DUMP_REG(nv_sor_pll5());
|
|
DUMP_REG(NV_SOR_CSTM);
|
|
DUMP_REG(NV_SOR_LVDS);
|
|
DUMP_REG(NV_SOR_CRCA);
|
|
DUMP_REG(NV_SOR_CRCB);
|
|
DUMP_REG(NV_SOR_SEQ_CTL);
|
|
DUMP_REG(NV_SOR_LANE_SEQ_CTL);
|
|
DUMP_REG(NV_SOR_SEQ_INST(0));
|
|
DUMP_REG(NV_SOR_SEQ_INST(1));
|
|
DUMP_REG(NV_SOR_SEQ_INST(2));
|
|
DUMP_REG(NV_SOR_SEQ_INST(3));
|
|
DUMP_REG(NV_SOR_SEQ_INST(4));
|
|
DUMP_REG(NV_SOR_SEQ_INST(5));
|
|
DUMP_REG(NV_SOR_SEQ_INST(6));
|
|
DUMP_REG(NV_SOR_SEQ_INST(7));
|
|
DUMP_REG(NV_SOR_SEQ_INST(8));
|
|
DUMP_REG(NV_SOR_PWM_DIV);
|
|
DUMP_REG(NV_SOR_PWM_CTL);
|
|
DUMP_REG(NV_SOR_MSCHECK);
|
|
DUMP_REG(NV_SOR_XBAR_CTRL);
|
|
DUMP_REG(NV_SOR_XBAR_POL);
|
|
DUMP_REG(NV_SOR_DP_LINKCTL(0));
|
|
DUMP_REG(NV_SOR_DP_LINKCTL(1));
|
|
DUMP_REG(NV_SOR_DC(0));
|
|
DUMP_REG(NV_SOR_DC(1));
|
|
DUMP_REG(NV_SOR_LANE_DRIVE_CURRENT(0));
|
|
DUMP_REG(NV_SOR_PR(0));
|
|
DUMP_REG(NV_SOR_LANE4_PREEMPHASIS(0));
|
|
DUMP_REG(NV_SOR_POSTCURSOR(0));
|
|
DUMP_REG(NV_SOR_DP_CONFIG(0));
|
|
DUMP_REG(NV_SOR_DP_CONFIG(1));
|
|
DUMP_REG(NV_SOR_DP_MN(0));
|
|
DUMP_REG(NV_SOR_DP_MN(1));
|
|
DUMP_REG(nv_sor_dp_padctl(0));
|
|
DUMP_REG(nv_sor_dp_padctl(1));
|
|
if (tegra_dc_is_nvdisplay())
|
|
DUMP_REG(nv_sor_dp_padctl(2));
|
|
DUMP_REG(NV_SOR_DP_DEBUG(0));
|
|
DUMP_REG(NV_SOR_DP_DEBUG(1));
|
|
DUMP_REG(NV_SOR_DP_SPARE(0));
|
|
DUMP_REG(NV_SOR_DP_SPARE(1));
|
|
DUMP_REG(NV_SOR_DP_TPG);
|
|
DUMP_REG(NV_SOR_HDMI_CTRL);
|
|
DUMP_REG(NV_SOR_HDMI2_CTRL);
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
DUMP_REG(nv_sor_dp_misc1_override());
|
|
DUMP_REG(nv_sor_dp_misc1_bit6());
|
|
|
|
if (tegra_platform_is_vdk())
|
|
DUMP_REG(NV_SOR_FPGA_HDMI_HEAD_SEL);
|
|
hdmi_dump = 1; /* SOR and SOR1 have same registers */
|
|
} else {
|
|
hdmi_dump = sor->ctrl_num; /*SOR and SOR1 have diff registers*/
|
|
}
|
|
/* TODO: we should check if the feature is present
|
|
* and not the ctrl_num
|
|
*/
|
|
if (hdmi_dump) {
|
|
DUMP_REG(NV_SOR_DP_AUDIO_CTRL);
|
|
DUMP_REG(NV_SOR_DP_AUDIO_HBLANK_SYMBOLS);
|
|
DUMP_REG(NV_SOR_DP_AUDIO_VBLANK_SYMBOLS);
|
|
DUMP_REG(NV_SOR_DP_GENERIC_INFOFRAME_HEADER);
|
|
DUMP_REG(NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(0));
|
|
DUMP_REG(NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(1));
|
|
DUMP_REG(NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(2));
|
|
DUMP_REG(NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(3));
|
|
DUMP_REG(NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(4));
|
|
DUMP_REG(NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(5));
|
|
DUMP_REG(NV_SOR_DP_GENERIC_INFOFRAME_SUBPACK(6));
|
|
|
|
DUMP_REG(NV_SOR_DP_OUTPUT_CHANNEL_STATUS1);
|
|
DUMP_REG(NV_SOR_DP_OUTPUT_CHANNEL_STATUS2);
|
|
|
|
DUMP_REG(NV_SOR_HDMI_AUDIO_N);
|
|
DUMP_REG(NV_SOR_HDMI2_CTRL);
|
|
|
|
DUMP_REG(NV_SOR_AUDIO_CTRL);
|
|
DUMP_REG(NV_SOR_AUDIO_DEBUG);
|
|
DUMP_REG(NV_SOR_AUDIO_NVAL_0320);
|
|
DUMP_REG(NV_SOR_AUDIO_NVAL_0441);
|
|
DUMP_REG(NV_SOR_AUDIO_NVAL_0882);
|
|
DUMP_REG(NV_SOR_AUDIO_NVAL_1764);
|
|
DUMP_REG(NV_SOR_AUDIO_NVAL_0480);
|
|
DUMP_REG(NV_SOR_AUDIO_NVAL_0960);
|
|
DUMP_REG(NV_SOR_AUDIO_NVAL_1920);
|
|
|
|
DUMP_REG(NV_SOR_AUDIO_AVAL_0320);
|
|
DUMP_REG(NV_SOR_AUDIO_AVAL_0441);
|
|
DUMP_REG(NV_SOR_AUDIO_AVAL_0882);
|
|
DUMP_REG(NV_SOR_AUDIO_AVAL_1764);
|
|
DUMP_REG(NV_SOR_AUDIO_AVAL_0480);
|
|
DUMP_REG(NV_SOR_AUDIO_AVAL_0960);
|
|
DUMP_REG(NV_SOR_AUDIO_AVAL_1920);
|
|
|
|
DUMP_REG(NV_SOR_DP_AUDIO_CRC);
|
|
DUMP_REG(NV_SOR_DP_AUDIO_TIMESTAMP_0320);
|
|
DUMP_REG(NV_SOR_DP_AUDIO_TIMESTAMP_0441);
|
|
DUMP_REG(NV_SOR_DP_AUDIO_TIMESTAMP_0882);
|
|
DUMP_REG(NV_SOR_DP_AUDIO_TIMESTAMP_1764);
|
|
DUMP_REG(NV_SOR_DP_AUDIO_TIMESTAMP_0480);
|
|
DUMP_REG(NV_SOR_DP_AUDIO_TIMESTAMP_0960);
|
|
DUMP_REG(NV_SOR_DP_AUDIO_TIMESTAMP_1920);
|
|
|
|
DUMP_REG(NV_SOR_HDMI_GENERIC_CTRL);
|
|
DUMP_REG(NV_SOR_HDMI_GENERIC_HEADER);
|
|
DUMP_REG(NV_SOR_HDMI_GENERIC_SUBPACK0_LOW);
|
|
DUMP_REG(NV_SOR_HDMI_GENERIC_SUBPACK0_HIGH);
|
|
DUMP_REG(NV_SOR_HDMI_GENERIC_SUBPACK1_LOW);
|
|
DUMP_REG(NV_SOR_HDMI_GENERIC_SUBPACK1_HIGH);
|
|
DUMP_REG(NV_SOR_HDMI_GENERIC_SUBPACK2_LOW);
|
|
DUMP_REG(NV_SOR_HDMI_GENERIC_SUBPACK2_HIGH);
|
|
DUMP_REG(NV_SOR_HDMI_GENERIC_SUBPACK3_LOW);
|
|
DUMP_REG(NV_SOR_HDMI_GENERIC_SUBPACK3_HIGH);
|
|
|
|
DUMP_REG(NV_SOR_HDMI_AVI_INFOFRAME_CTRL);
|
|
DUMP_REG(NV_SOR_HDMI_AVI_INFOFRAME_HEADER);
|
|
DUMP_REG(NV_SOR_HDMI_AVI_INFOFRAME_SUBPACK0_LOW);
|
|
DUMP_REG(NV_SOR_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH);
|
|
DUMP_REG(NV_SOR_HDMI_AVI_INFOFRAME_SUBPACK1_LOW_0);
|
|
DUMP_REG(NV_SOR_HDMI_AVI_INFOFRAME_SUBPACK1_HIGH_0);
|
|
}
|
|
|
|
tegra_sor_clk_disable(sor);
|
|
tegra_dc_io_end(sor->dc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dbg_sor_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, dbg_sor_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations dbg_fops = {
|
|
.open = dbg_sor_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int sor_crc_show(struct seq_file *s, void *unused)
|
|
{
|
|
struct tegra_dc_sor_data *sor = s->private;
|
|
u32 reg_val;
|
|
int timeout = 0;
|
|
|
|
reg_val = tegra_dc_sor_debugfs_get_crc(sor, &timeout);
|
|
|
|
if (!timeout)
|
|
seq_printf(s, "NV_SOR[%x]_CRCB = 0x%08x\n",
|
|
sor->ctrl_num, reg_val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sor_crc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, sor_crc_show, inode->i_private);
|
|
}
|
|
|
|
static ssize_t sor_crc_write(struct file *file,
|
|
const char __user *user_buf, size_t count, loff_t *off)
|
|
{
|
|
struct seq_file *s = file->private_data;
|
|
struct tegra_dc_sor_data *sor = s->private;
|
|
u32 data;
|
|
|
|
/* autodetect radix */
|
|
if (kstrtouint_from_user(user_buf, count, 0, &data) < 0)
|
|
return -EINVAL;
|
|
|
|
/* at this point:
|
|
* data[0:0] = 1|0: enable|disable CRC
|
|
* data[5:4] contains ASY_CRCMODE */
|
|
|
|
tegra_dc_sor_toggle_crc(sor, data);
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations crc_fops = {
|
|
.open = sor_crc_open,
|
|
.read = seq_read,
|
|
.write = sor_crc_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int dbg_hw_index_show(struct seq_file *m, void *unused)
|
|
{
|
|
struct tegra_dc_sor_data *sor = m->private;
|
|
|
|
if (WARN_ON(!sor))
|
|
return -EINVAL;
|
|
|
|
seq_printf(m, "Hardware index: %d\n", sor->ctrl_num);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dbg_hw_index_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, dbg_hw_index_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations dbg_hw_index_ops = {
|
|
.open = dbg_hw_index_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void tegra_dc_sor_debug_create(struct tegra_dc_sor_data *sor,
|
|
const char *res_name)
|
|
{
|
|
struct dentry *retval;
|
|
char sor_path[16];
|
|
|
|
BUG_ON(!res_name);
|
|
|
|
snprintf(sor_path, sizeof(sor_path), "tegra_%s", res_name ? : "sor");
|
|
sor->debugdir = debugfs_create_dir(sor_path, NULL);
|
|
if (!sor->debugdir)
|
|
return;
|
|
|
|
retval = debugfs_create_file("regs", 0444, sor->debugdir, sor,
|
|
&dbg_fops);
|
|
if (!retval)
|
|
goto free_out;
|
|
|
|
retval = debugfs_create_file("crc", 0644, sor->debugdir,
|
|
sor, &crc_fops);
|
|
if (!retval)
|
|
goto free_out;
|
|
|
|
retval = debugfs_create_file("hw_index", 0444, sor->debugdir,
|
|
sor, &dbg_hw_index_ops);
|
|
if (!retval)
|
|
goto free_out;
|
|
|
|
return;
|
|
free_out:
|
|
debugfs_remove_recursive(sor->debugdir);
|
|
sor->debugdir = NULL;
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_sor_debug_create);
|
|
|
|
static void tegra_dc_sor_debug_destroy(struct tegra_dc_sor_data *sor)
|
|
{
|
|
debugfs_remove_recursive(sor->debugdir);
|
|
sor->debugdir = NULL;
|
|
debugfs_remove(sor->dc->sor_link);
|
|
}
|
|
#else
|
|
static inline void tegra_dc_sor_debug_create(struct tegra_dc_sor_data *sor,
|
|
const char *res_name)
|
|
{ }
|
|
static inline void tegra_dc_sor_debug_destroy(struct tegra_dc_sor_data *sor)
|
|
{ }
|
|
#endif
|
|
|
|
static void tegra_sor_fpga_settings(struct tegra_dc *dc,
|
|
struct tegra_dc_sor_data *sor)
|
|
{
|
|
u32 mode_sel = NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD1_MODE_FIELD |
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD1_OUT_EN_FIELD;
|
|
u32 head_sel = NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD1_MODE_HDMI |
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD1_OUT_EN_ENABLE;
|
|
|
|
/* continue for system fpga and HDMI */
|
|
if ((!tegra_platform_is_vdk()) || (dc->out->type != TEGRA_DC_OUT_HDMI))
|
|
return;
|
|
|
|
if (dc->ndev->id == 0) {/* HEAD 0 */
|
|
mode_sel =
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD0_MODE_FIELD |
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD0_OUT_EN_FIELD;
|
|
|
|
head_sel =
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD0_MODE_HDMI |
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD0_OUT_EN_ENABLE;
|
|
|
|
} else if (dc->ndev->id == 1) {/* HEAD 1 */
|
|
mode_sel =
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD1_MODE_FIELD |
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD1_OUT_EN_FIELD;
|
|
|
|
head_sel =
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD1_MODE_HDMI |
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD1_OUT_EN_ENABLE;
|
|
|
|
} else if (dc->ndev->id == 2) {/* HEAD 2 */
|
|
mode_sel =
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD2_MODE_FIELD |
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD2_OUT_EN_FIELD;
|
|
|
|
head_sel =
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD2_MODE_HDMI |
|
|
NV_SOR_FPGA_HDMI_HEAD_SEL_FPGA_HEAD2_OUT_EN_ENABLE;
|
|
|
|
}
|
|
tegra_sor_write_field(sor, NV_SOR_FPGA_HDMI_HEAD_SEL,
|
|
mode_sel, head_sel);
|
|
|
|
return;
|
|
}
|
|
|
|
struct tegra_dc_sor_data *tegra_dc_sor_init(struct tegra_dc *dc,
|
|
const struct tegra_dc_dp_link_config *cfg)
|
|
{
|
|
u32 temp;
|
|
int err, i;
|
|
char res_name[CHAR_BUF_SIZE_MAX] = {0};
|
|
char io_pinctrl_en_name[CHAR_BUF_SIZE_MAX] = {0};
|
|
char io_pinctrl_dis_name[CHAR_BUF_SIZE_MAX] = {0};
|
|
struct clk *sor_clk = NULL;
|
|
struct clk *safe_clk = NULL;
|
|
struct clk *pad_clk = NULL;
|
|
struct clk *ref_clk = NULL;
|
|
struct tegra_dc_sor_data *sor;
|
|
struct tegra_dc_sor_info *sor_cap;
|
|
struct device_node *sor_np;
|
|
|
|
if (!dc) {
|
|
pr_err("%s: dc pointer cannot be NULL\n", __func__);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
sor_np = tegra_dc_get_conn_np(dc);
|
|
if (!sor_np) {
|
|
dev_err(&dc->ndev->dev, "%s: error getting connector np\n",
|
|
__func__);
|
|
err = -ENODEV;
|
|
goto err_allocate;
|
|
}
|
|
|
|
sor = devm_kzalloc(&dc->ndev->dev, sizeof(*sor), GFP_KERNEL);
|
|
if (!sor) {
|
|
err = -ENOMEM;
|
|
goto err_allocate;
|
|
}
|
|
|
|
sor->link_speeds = link_speed_table;
|
|
sor->num_link_speeds = ARRAY_SIZE(link_speed_table);
|
|
|
|
sor->training_patterns = training_pattern_table;
|
|
sor->num_training_patterns = ARRAY_SIZE(training_pattern_table);
|
|
|
|
if (!of_property_read_u32(sor_np, "nvidia,sor-ctrlnum", &temp)) {
|
|
sor->ctrl_num = (unsigned long)temp;
|
|
} else {
|
|
dev_err(&dc->ndev->dev, "mandatory property %s for %s not found\n",
|
|
"nvidia,sor-ctrlnum",
|
|
of_node_full_name(sor_np));
|
|
err = -ENOENT;
|
|
goto err_free_sor;
|
|
}
|
|
|
|
snprintf(res_name, CHAR_BUF_SIZE_MAX, "sor%d", sor->ctrl_num);
|
|
|
|
sor->base = of_iomap(sor_np, 0);
|
|
if (!sor->base) {
|
|
dev_err(&dc->ndev->dev, "%s: %s registers can't be mapped\n",
|
|
__func__, res_name);
|
|
err = IS_ERR(sor->base) ? PTR_ERR(sor->base) : -ENOENT;
|
|
goto err_free_sor;
|
|
}
|
|
|
|
sor_clk = tegra_disp_of_clk_get_by_name(sor_np, res_name);
|
|
if (IS_ERR_OR_NULL(sor_clk)) {
|
|
dev_err(&dc->ndev->dev, "%s: can't get clock %s\n",
|
|
__func__, res_name);
|
|
err = IS_ERR(sor_clk) ? PTR_ERR(sor_clk) : -ENOENT;
|
|
goto err_iounmap_reg;
|
|
}
|
|
|
|
safe_clk = tegra_disp_of_clk_get_by_name(sor_np, "sor_safe");
|
|
if (IS_ERR_OR_NULL(safe_clk)) {
|
|
dev_err(&dc->ndev->dev, "sor: can't get safe clock\n");
|
|
err = IS_ERR(safe_clk) ? PTR_ERR(safe_clk) : -ENOENT;
|
|
goto err_safe;
|
|
}
|
|
|
|
if (!(tegra_dc_is_t21x() && sor->ctrl_num == 0)) {
|
|
snprintf(res_name, CHAR_BUF_SIZE_MAX, "sor%d_pad_clkout",
|
|
sor->ctrl_num);
|
|
pad_clk = tegra_disp_of_clk_get_by_name(sor_np, res_name);
|
|
if (IS_ERR_OR_NULL(pad_clk)) {
|
|
dev_err(&dc->ndev->dev, "sor: can't get %s\n",
|
|
res_name);
|
|
err = IS_ERR(pad_clk) ? PTR_ERR(pad_clk) : -ENOENT;
|
|
goto err_pad;
|
|
}
|
|
|
|
snprintf(res_name, CHAR_BUF_SIZE_MAX, "sor%d_ref",
|
|
sor->ctrl_num);
|
|
ref_clk = tegra_disp_of_clk_get_by_name(sor_np, res_name);
|
|
if (IS_ERR_OR_NULL(ref_clk)) {
|
|
dev_err(&dc->ndev->dev, "sor: can't get %s\n",
|
|
res_name);
|
|
err = IS_ERR(ref_clk) ? PTR_ERR(ref_clk) : -ENOENT;
|
|
goto err_ref;
|
|
}
|
|
|
|
/* change res_name back to sor%d */
|
|
snprintf(res_name, CHAR_BUF_SIZE_MAX, "sor%d", sor->ctrl_num);
|
|
}
|
|
|
|
err = tegra_get_sor_reset_ctrl(sor, sor_np, res_name);
|
|
if (err) {
|
|
dev_err(&dc->ndev->dev, "sor%d: can't get reset control\n",
|
|
sor->ctrl_num);
|
|
goto err_rst;
|
|
}
|
|
|
|
for (i = 0; i < sizeof(sor->xbar_ctrl)/sizeof(u32); i++)
|
|
sor->xbar_ctrl[i] = i;
|
|
|
|
if (tegra_dc_is_t21x() && (sor->ctrl_num == 1)) { /* todo: fix this */
|
|
snprintf(io_pinctrl_en_name, CHAR_BUF_SIZE_MAX,
|
|
"hdmi-dpd-enable");
|
|
snprintf(io_pinctrl_dis_name, CHAR_BUF_SIZE_MAX,
|
|
"hdmi-dpd-disable");
|
|
} else {
|
|
snprintf(io_pinctrl_en_name, CHAR_BUF_SIZE_MAX,
|
|
"hdmi-dp%d-dpd-enable", sor->ctrl_num);
|
|
snprintf(io_pinctrl_dis_name, CHAR_BUF_SIZE_MAX,
|
|
"hdmi-dp%d-dpd-disable", sor->ctrl_num);
|
|
}
|
|
|
|
sor->pinctrl_sor = devm_pinctrl_get(&dc->ndev->dev);
|
|
if (IS_ERR_OR_NULL(sor->pinctrl_sor)) {
|
|
dev_err(&dc->ndev->dev, "pinctrl get fail: %ld\n",
|
|
PTR_ERR(sor->pinctrl_sor));
|
|
sor->pinctrl_sor = NULL;
|
|
goto bypass_pads;
|
|
}
|
|
|
|
if (sor->pinctrl_sor) {
|
|
sor->dpd_enable = pinctrl_lookup_state(sor->pinctrl_sor,
|
|
io_pinctrl_en_name);
|
|
if (IS_ERR_OR_NULL(sor->dpd_enable)) {
|
|
dev_err(&dc->ndev->dev, "dpd enable lookup fail:%ld\n",
|
|
PTR_ERR(sor->dpd_enable));
|
|
sor->dpd_enable = NULL;
|
|
goto bypass_pads;
|
|
}
|
|
|
|
sor->dpd_disable = pinctrl_lookup_state(sor->pinctrl_sor,
|
|
io_pinctrl_dis_name);
|
|
if (IS_ERR_OR_NULL(sor->dpd_disable)) {
|
|
dev_err(&dc->ndev->dev, "dpd disable lookup fail:%ld\n",
|
|
PTR_ERR(sor->dpd_disable));
|
|
sor->dpd_disable = NULL;
|
|
}
|
|
}
|
|
|
|
bypass_pads:
|
|
if (of_property_read_u32_array(sor_np, "nvidia,xbar-ctrl",
|
|
sor->xbar_ctrl, sizeof(sor->xbar_ctrl)/sizeof(u32)))
|
|
dev_err(&dc->ndev->dev, "%s: error reading nvidia,xbar-ctrl\n",
|
|
__func__);
|
|
|
|
if (of_property_read_bool(sor_np, "nvidia,sor-audio-not-supported"))
|
|
sor->audio_support = false;
|
|
else
|
|
sor->audio_support = true;
|
|
|
|
sor_cap = tegra_dc_get_sor_cap();
|
|
if (IS_ERR_OR_NULL(sor_cap)) {
|
|
dev_info(&dc->ndev->dev, "sor: can't get sor cap.\n");
|
|
sor->hdcp_support = false;
|
|
} else {
|
|
sor->hdcp_support = sor_cap[sor->ctrl_num].hdcp_supported;
|
|
}
|
|
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
sor->win_state_arr = devm_kzalloc(&dc->ndev->dev,
|
|
tegra_dc_get_numof_dispwindows() *
|
|
sizeof(*sor->win_state_arr),
|
|
GFP_KERNEL);
|
|
if (!sor->win_state_arr) {
|
|
err = -ENOMEM;
|
|
goto err_rst;
|
|
}
|
|
}
|
|
|
|
sor->dc = dc;
|
|
sor->np = sor_np;
|
|
sor->sor_clk = sor_clk;
|
|
sor->safe_clk = safe_clk;
|
|
sor->pad_clk = pad_clk;
|
|
sor->ref_clk = ref_clk;
|
|
sor->link_cfg = cfg;
|
|
sor->portnum = 0;
|
|
sor->powergate_id = tegra_pd_get_powergate_id(tegra_sor_pd);
|
|
sor->sor_state = SOR_DETACHED;
|
|
|
|
tegra_dc_sor_debug_create(sor, res_name);
|
|
|
|
if (tegra_dc_is_nvdisplay())
|
|
tegra_sor_fpga_settings(dc, sor);
|
|
init_rwsem(&sor->reset_lock);
|
|
|
|
return sor;
|
|
|
|
err_rst: __maybe_unused
|
|
clk_put(ref_clk);
|
|
err_ref: __maybe_unused
|
|
clk_put(pad_clk);
|
|
err_pad: __maybe_unused
|
|
clk_put(safe_clk);
|
|
err_safe: __maybe_unused
|
|
clk_put(sor_clk);
|
|
err_iounmap_reg:
|
|
iounmap(sor->base);
|
|
err_free_sor:
|
|
devm_kfree(&dc->ndev->dev, sor);
|
|
err_allocate:
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
int tegra_dc_sor_set_power_state(struct tegra_dc_sor_data *sor, int pu_pd)
|
|
{
|
|
u32 reg_val;
|
|
u32 orig_val;
|
|
|
|
orig_val = tegra_sor_readl(sor, NV_SOR_PWR);
|
|
|
|
reg_val = pu_pd ? NV_SOR_PWR_NORMAL_STATE_PU :
|
|
NV_SOR_PWR_NORMAL_STATE_PD; /* normal state only */
|
|
|
|
if (reg_val == orig_val)
|
|
return 0; /* No update needed */
|
|
|
|
reg_val |= NV_SOR_PWR_SETTING_NEW_TRIGGER;
|
|
tegra_sor_writel(sor, NV_SOR_PWR, reg_val);
|
|
|
|
/* Poll to confirm it is done */
|
|
if (tegra_dc_sor_poll_register(sor, NV_SOR_PWR,
|
|
NV_SOR_PWR_SETTING_NEW_DEFAULT_MASK,
|
|
NV_SOR_PWR_SETTING_NEW_DONE,
|
|
100, TEGRA_SOR_TIMEOUT_MS)) {
|
|
dev_err(&sor->dc->ndev->dev,
|
|
"dc timeout waiting for SOR_PWR = NEW_DONE\n");
|
|
return -EFAULT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void tegra_dc_sor_destroy(struct tegra_dc_sor_data *sor)
|
|
{
|
|
struct device *dev;
|
|
|
|
if (!sor) {
|
|
pr_err("%s: invalid input\n", __func__);
|
|
return;
|
|
}
|
|
dev = &sor->dc->ndev->dev;
|
|
|
|
clk_put(sor->ref_clk);
|
|
clk_put(sor->pad_clk);
|
|
clk_put(sor->safe_clk);
|
|
clk_put(sor->sor_clk);
|
|
|
|
tegra_dc_sor_debug_destroy(sor);
|
|
|
|
iounmap(sor->base);
|
|
|
|
devm_pinctrl_put(sor->pinctrl_sor);
|
|
sor->dpd_enable = NULL;
|
|
sor->dpd_disable = NULL;
|
|
|
|
if (tegra_dc_is_nvdisplay())
|
|
devm_kfree(dev, sor->win_state_arr);
|
|
devm_kfree(dev, sor);
|
|
}
|
|
|
|
void tegra_sor_tpg(struct tegra_dc_sor_data *sor, u32 tp, u32 total_lanes)
|
|
{
|
|
bool chan_coding = sor->training_patterns[tp].chan_coding;
|
|
bool scrambling = sor->training_patterns[tp].scrambling;
|
|
u32 tps_sor_val = sor->training_patterns[tp].sor_reg_val;
|
|
u32 val = 0; /* The value written to the reg gets constructed here */
|
|
unsigned int lane;
|
|
|
|
for (lane = 0; lane < total_lanes; lane++) {
|
|
u32 tp_shift = NV_SOR_DP_TPG_LANE1_PATTERN_SHIFT * lane;
|
|
|
|
val |= tps_sor_val << tp_shift;
|
|
val |= chan_coding << (tp_shift +
|
|
NV_SOR_DP_TPG_LANE0_CHANNELCODING_SHIFT);
|
|
val |= scrambling << (tp_shift +
|
|
NV_SOR_DP_TPG_LANE0_SCRAMBLEREN_SHIFT);
|
|
}
|
|
|
|
tegra_sor_writel(sor, NV_SOR_DP_TPG, val);
|
|
}
|
|
|
|
void tegra_sor_port_enable(struct tegra_dc_sor_data *sor, bool enb)
|
|
{
|
|
tegra_sor_write_field(sor, NV_SOR_DP_LINKCTL(sor->portnum),
|
|
NV_SOR_DP_LINKCTL_ENABLE_YES,
|
|
(enb ? NV_SOR_DP_LINKCTL_ENABLE_YES :
|
|
NV_SOR_DP_LINKCTL_ENABLE_NO));
|
|
}
|
|
|
|
static int tegra_dc_sor_enable_lane_sequencer(struct tegra_dc_sor_data *sor,
|
|
bool pu)
|
|
{
|
|
u32 reg_val;
|
|
|
|
/* SOR lane sequencer */
|
|
reg_val = NV_SOR_LANE_SEQ_CTL_SETTING_NEW_TRIGGER |
|
|
NV_SOR_LANE_SEQ_CTL_SEQUENCE_DOWN |
|
|
(15 << NV_SOR_LANE_SEQ_CTL_DELAY_SHIFT);
|
|
reg_val |= pu ? NV_SOR_LANE_SEQ_CTL_NEW_POWER_STATE_PU :
|
|
NV_SOR_LANE_SEQ_CTL_NEW_POWER_STATE_PD;
|
|
|
|
if (tegra_dc_sor_poll_register(sor, NV_SOR_LANE_SEQ_CTL,
|
|
NV_SOR_LANE_SEQ_CTL_SEQ_STATE_BUSY,
|
|
NV_SOR_LANE_SEQ_CTL_SEQ_STATE_IDLE,
|
|
100, TEGRA_SOR_SEQ_BUSY_TIMEOUT_MS)) {
|
|
dev_dbg(&sor->dc->ndev->dev,
|
|
"dp: timeout, sor lane sequencer busy\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
tegra_sor_writel(sor, NV_SOR_LANE_SEQ_CTL, reg_val);
|
|
if (tegra_dc_sor_poll_register(sor, NV_SOR_LANE_SEQ_CTL,
|
|
NV_SOR_LANE_SEQ_CTL_SETTING_MASK,
|
|
NV_SOR_LANE_SEQ_CTL_SETTING_NEW_DONE,
|
|
100, TEGRA_SOR_TIMEOUT_MS)) {
|
|
dev_dbg(&sor->dc->ndev->dev,
|
|
"dp: timeout, SOR lane sequencer power up/down\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 tegra_sor_get_pd_tx_bitmap(struct tegra_dc_sor_data *sor,
|
|
u32 lane_count)
|
|
{
|
|
int i;
|
|
u32 val = 0;
|
|
|
|
for (i = 0; i < lane_count; i++) {
|
|
u32 index = sor->xbar_ctrl[i];
|
|
|
|
switch (index) {
|
|
case 0:
|
|
val |= NV_SOR_DP_PADCTL_PD_TXD_0_NO;
|
|
break;
|
|
case 1:
|
|
val |= NV_SOR_DP_PADCTL_PD_TXD_1_NO;
|
|
break;
|
|
case 2:
|
|
val |= NV_SOR_DP_PADCTL_PD_TXD_2_NO;
|
|
break;
|
|
case 3:
|
|
val |= NV_SOR_DP_PADCTL_PD_TXD_3_NO;
|
|
break;
|
|
default:
|
|
dev_err(&sor->dc->ndev->dev,
|
|
"dp: incorrect lane cnt\n");
|
|
}
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
int tegra_sor_power_lanes(struct tegra_dc_sor_data *sor,
|
|
u32 lane_count, bool pu)
|
|
{
|
|
u32 val = 0;
|
|
|
|
if (pu)
|
|
val = tegra_sor_get_pd_tx_bitmap(sor, lane_count);
|
|
|
|
tegra_sor_write_field(sor, nv_sor_dp_padctl(sor->portnum),
|
|
NV_SOR_DP_PADCTL_PD_TXD_MASK, val);
|
|
|
|
if (pu)
|
|
tegra_dc_sor_set_lane_count(sor, lane_count);
|
|
|
|
return tegra_dc_sor_enable_lane_sequencer(sor, pu);
|
|
}
|
|
|
|
/* power on/off pad calibration logic */
|
|
void tegra_sor_pad_cal_power(struct tegra_dc_sor_data *sor,
|
|
bool power_up)
|
|
{
|
|
u32 val = power_up ? NV_SOR_DP_PADCTL_PAD_CAL_PD_POWERUP :
|
|
NV_SOR_DP_PADCTL_PAD_CAL_PD_POWERDOWN;
|
|
|
|
/* !!TODO: need to enable panel power through GPIO operations */
|
|
/* Check bug 790854 for HW progress */
|
|
|
|
tegra_sor_write_field(sor, nv_sor_dp_padctl(sor->portnum),
|
|
NV_SOR_DP_PADCTL_PAD_CAL_PD_POWERDOWN, val);
|
|
}
|
|
|
|
void tegra_dc_sor_termination_cal(struct tegra_dc_sor_data *sor)
|
|
{
|
|
u32 nv_sor_pll1_reg = nv_sor_pll1();
|
|
u32 termadj;
|
|
u32 cur_try;
|
|
u32 reg_val;
|
|
|
|
termadj = cur_try = 0x8;
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll1_reg,
|
|
NV_SOR_PLL1_TMDS_TERMADJ_DEFAULT_MASK,
|
|
termadj << NV_SOR_PLL1_TMDS_TERMADJ_SHIFT);
|
|
|
|
while (cur_try) {
|
|
/* binary search the right value */
|
|
usleep_range(100, 200);
|
|
reg_val = tegra_sor_readl(sor, nv_sor_pll1_reg);
|
|
|
|
if (reg_val & NV_SOR_PLL1_TERM_COMPOUT_HIGH)
|
|
termadj -= cur_try;
|
|
cur_try >>= 1;
|
|
termadj += cur_try;
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll1_reg,
|
|
NV_SOR_PLL1_TMDS_TERMADJ_DEFAULT_MASK,
|
|
termadj << NV_SOR_PLL1_TMDS_TERMADJ_SHIFT);
|
|
}
|
|
}
|
|
|
|
static void tegra_dc_sor_config_pwm(struct tegra_dc_sor_data *sor, u32 pwm_div,
|
|
u32 pwm_dutycycle)
|
|
{
|
|
tegra_sor_writel(sor, NV_SOR_PWM_DIV, pwm_div);
|
|
tegra_sor_writel(sor, NV_SOR_PWM_CTL,
|
|
(pwm_dutycycle & NV_SOR_PWM_CTL_DUTY_CYCLE_MASK) |
|
|
NV_SOR_PWM_CTL_SETTING_NEW_TRIGGER);
|
|
|
|
if (tegra_dc_sor_poll_register(sor, NV_SOR_PWM_CTL,
|
|
NV_SOR_PWM_CTL_SETTING_NEW_SHIFT,
|
|
NV_SOR_PWM_CTL_SETTING_NEW_DONE,
|
|
100, TEGRA_SOR_TIMEOUT_MS)) {
|
|
dev_dbg(&sor->dc->ndev->dev,
|
|
"dp: timeout while waiting for SOR PWM setting\n");
|
|
}
|
|
}
|
|
|
|
static inline void tegra_dc_sor_super_update(struct tegra_dc_sor_data *sor)
|
|
{
|
|
tegra_sor_writel(sor, NV_SOR_SUPER_STATE0, 0);
|
|
tegra_sor_writel(sor, NV_SOR_SUPER_STATE0, 1);
|
|
tegra_sor_writel(sor, NV_SOR_SUPER_STATE0, 0);
|
|
}
|
|
|
|
static inline void tegra_dc_sor_update(struct tegra_dc_sor_data *sor)
|
|
{
|
|
tegra_sor_writel(sor, NV_SOR_STATE0, 0);
|
|
tegra_sor_writel(sor, NV_SOR_STATE0, 1);
|
|
tegra_sor_writel(sor, NV_SOR_STATE0, 0);
|
|
}
|
|
|
|
static void tegra_dc_sor_io_set_dpd(struct tegra_dc_sor_data *sor, bool up)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (tegra_platform_is_vdk())
|
|
return;
|
|
|
|
if (!sor->pinctrl_sor)
|
|
return;
|
|
|
|
if (up) {
|
|
if (sor->dpd_disable)
|
|
ret = pinctrl_select_state(sor->pinctrl_sor,
|
|
sor->dpd_disable);
|
|
} else {
|
|
if (sor->dpd_enable)
|
|
ret = pinctrl_select_state(sor->pinctrl_sor,
|
|
sor->dpd_enable);
|
|
}
|
|
if (ret < 0)
|
|
dev_err(&sor->dc->ndev->dev, "io pad power %s fail:%d\n",
|
|
up ? "disable" : "enable", ret);
|
|
}
|
|
|
|
/* hdmi uses sor sequencer for pad power up */
|
|
void tegra_sor_hdmi_pad_power_up(struct tegra_dc_sor_data *sor)
|
|
{
|
|
u32 nv_sor_pll0_reg = nv_sor_pll0();
|
|
u32 nv_sor_pll1_reg = nv_sor_pll1();
|
|
u32 nv_sor_pll2_reg = nv_sor_pll2();
|
|
int ret = 0;
|
|
|
|
/* seamless */
|
|
if (sor->dc->initialized)
|
|
return;
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX9_LVDSEN_OVERRIDE,
|
|
NV_SOR_PLL2_AUX9_LVDSEN_OVERRIDE);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX0_MASK,
|
|
NV_SOR_PLL2_AUX0_SEQ_PLL_PULLDOWN_OVERRIDE);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_CLKGEN_MODE_MASK,
|
|
NV_SOR_PLL2_CLKGEN_MODE_DP_TMDS);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX2_MASK,
|
|
NV_SOR_PLL2_AUX2_OVERRIDE_POWERDOWN);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX1_SEQ_MASK,
|
|
NV_SOR_PLL2_AUX1_SEQ_PLLCAPPD_OVERRIDE);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_MASK,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_ENABLE);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_MASK,
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_ENABLE);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK,
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_ENABLE);
|
|
tegra_sor_write_field(sor, nv_sor_pll0_reg, NV_SOR_PLL0_PWR_MASK,
|
|
NV_SOR_PLL0_PWR_OFF);
|
|
tegra_sor_write_field(sor, nv_sor_pll0_reg, NV_SOR_PLL0_VCOPD_MASK,
|
|
NV_SOR_PLL0_VCOPD_ASSERT);
|
|
tegra_sor_pad_cal_power(sor, false);
|
|
usleep_range(20, 70);
|
|
|
|
if (sor->dpd_disable) {
|
|
ret = pinctrl_select_state(sor->pinctrl_sor, sor->dpd_disable);
|
|
if (ret < 0)
|
|
dev_err(&sor->dc->ndev->dev,
|
|
"io pad power-up fail:%d\n", ret);
|
|
}
|
|
usleep_range(20, 70);
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_MASK,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_DISABLE);
|
|
usleep_range(50, 100);
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll0_reg, NV_SOR_PLL0_PWR_MASK,
|
|
NV_SOR_PLL0_PWR_ON);
|
|
tegra_sor_write_field(sor, nv_sor_pll0_reg, NV_SOR_PLL0_VCOPD_MASK,
|
|
NV_SOR_PLL0_VCOPD_RESCIND);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK,
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_DISABLE);
|
|
usleep_range(250, 300);
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_MASK,
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_DISABLE);
|
|
|
|
/*
|
|
* TERM_ENABLE is disabled at the end of rterm calibration. Re-enable it
|
|
* here.
|
|
*/
|
|
tegra_sor_write_field(sor, nv_sor_pll1_reg,
|
|
NV_SOR_PLL1_TMDS_TERM_ENABLE,
|
|
NV_SOR_PLL1_TMDS_TERM_ENABLE);
|
|
usleep_range(10, 20);
|
|
}
|
|
|
|
void tegra_sor_hdmi_pad_power_down(struct tegra_dc_sor_data *sor)
|
|
{
|
|
u32 nv_sor_pll0_reg = nv_sor_pll0();
|
|
u32 nv_sor_pll2_reg = nv_sor_pll2();
|
|
int ret = 0;
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_MASK,
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_ENABLE);
|
|
usleep_range(25, 30);
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll0_reg, NV_SOR_PLL0_PWR_MASK |
|
|
NV_SOR_PLL0_VCOPD_MASK, NV_SOR_PLL0_PWR_OFF |
|
|
NV_SOR_PLL0_VCOPD_ASSERT);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg, NV_SOR_PLL2_AUX1_SEQ_MASK |
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK,
|
|
NV_SOR_PLL2_AUX1_SEQ_PLLCAPPD_OVERRIDE |
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_ENABLE);
|
|
usleep_range(25, 30);
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_MASK,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_ENABLE);
|
|
tegra_sor_pad_cal_power(sor, false);
|
|
|
|
if (sor->dpd_enable) {
|
|
ret = pinctrl_select_state(sor->pinctrl_sor, sor->dpd_enable);
|
|
if (ret < 0)
|
|
dev_err(&sor->dc->ndev->dev,
|
|
"io pad power-down fail:%d\n", ret);
|
|
}
|
|
usleep_range(20, 70);
|
|
}
|
|
|
|
/* The SOR power sequencer does not work for t124 so SW has to
|
|
go through the power sequence manually */
|
|
/* Power up steps from spec: */
|
|
/* STEP PDPORT PDPLL PDBG PLLVCOD PLLCAPD E_DPD PDCAL */
|
|
/* 1 1 1 1 1 1 1 1 */
|
|
/* 2 1 1 1 1 1 0 1 */
|
|
/* 3 1 1 0 1 1 0 1 */
|
|
/* 4 1 0 0 0 0 0 1 */
|
|
/* 5 0 0 0 0 0 0 1 */
|
|
static void tegra_sor_dp_pad_power_up(struct tegra_dc_sor_data *sor)
|
|
{
|
|
u32 nv_sor_pll0_reg = nv_sor_pll0();
|
|
u32 nv_sor_pll1_reg = nv_sor_pll1();
|
|
u32 nv_sor_pll2_reg = nv_sor_pll2();
|
|
|
|
if (sor->power_is_up)
|
|
return;
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX2_MASK,
|
|
NV_SOR_PLL2_AUX2_OVERRIDE_POWERDOWN);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX1_SEQ_MASK,
|
|
NV_SOR_PLL2_AUX1_SEQ_PLLCAPPD_OVERRIDE);
|
|
|
|
/* step 1 */
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_MASK | /* PDPORT */
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_MASK | /* PDBG */
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK, /* PLLCAPD */
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_ENABLE |
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_ENABLE |
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_ENABLE);
|
|
tegra_sor_write_field(sor, nv_sor_pll0_reg,
|
|
NV_SOR_PLL0_PWR_MASK | /* PDPLL */
|
|
NV_SOR_PLL0_VCOPD_MASK, /* PLLVCOPD */
|
|
NV_SOR_PLL0_PWR_OFF |
|
|
NV_SOR_PLL0_VCOPD_ASSERT);
|
|
tegra_sor_pad_cal_power(sor, false); /* PDCAL */
|
|
|
|
/* step 2 */
|
|
tegra_dc_sor_io_set_dpd(sor, true);
|
|
usleep_range(5, 100); /* sleep > 5us */
|
|
|
|
/* step 3 */
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_MASK,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_DISABLE);
|
|
usleep_range(100, 150);
|
|
|
|
/* step 4 */
|
|
tegra_sor_write_field(sor, nv_sor_pll0_reg,
|
|
NV_SOR_PLL0_PWR_MASK | /* PDPLL */
|
|
NV_SOR_PLL0_VCOPD_MASK, /* PLLVCOPD */
|
|
NV_SOR_PLL0_PWR_ON | NV_SOR_PLL0_VCOPD_RESCIND);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK, /* PLLCAPD */
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_DISABLE);
|
|
usleep_range(200, 1000);
|
|
|
|
/* step 5 */
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_MASK, /* PDPORT */
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_DISABLE);
|
|
|
|
/*
|
|
* TERM_ENABLE is disabled at the end of rterm calibration. Re-enable it
|
|
* here.
|
|
*/
|
|
tegra_sor_write_field(sor, nv_sor_pll1_reg,
|
|
NV_SOR_PLL1_TMDS_TERM_ENABLE,
|
|
NV_SOR_PLL1_TMDS_TERM_ENABLE);
|
|
usleep_range(10, 20);
|
|
|
|
sor->power_is_up = true;
|
|
}
|
|
|
|
/* Powerdown steps from the spec: */
|
|
/* STEP PDPORT PDPLL PDBG PLLVCOD PLLCAPD E_DPD PDCAL */
|
|
/* 1 0 0 0 0 0 0 1 */
|
|
/* 2 1 0 0 0 0 0 1 */
|
|
/* 3 1 1 0 1 1 0 1 */
|
|
/* 4 1 1 1 1 1 0 1 */
|
|
/* 5 1 1 1 1 1 1 1 */
|
|
static void tegra_sor_dp_pad_power_down(struct tegra_dc_sor_data *sor)
|
|
{
|
|
u32 nv_sor_pll0_reg = nv_sor_pll0();
|
|
u32 nv_sor_pll2_reg = nv_sor_pll2();
|
|
|
|
if (!sor->power_is_up)
|
|
return;
|
|
|
|
/* step 1 -- not necessary */
|
|
|
|
/* step 2 */
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_MASK, /* PDPORT */
|
|
NV_SOR_PLL2_AUX7_PORT_POWERDOWN_ENABLE);
|
|
usleep_range(25, 30);
|
|
|
|
/* step 3 */
|
|
tegra_sor_write_field(sor, nv_sor_pll0_reg,
|
|
NV_SOR_PLL0_PWR_MASK | /* PDPLL */
|
|
NV_SOR_PLL0_VCOPD_MASK, /* PLLVCOPD */
|
|
NV_SOR_PLL0_PWR_OFF | NV_SOR_PLL0_VCOPD_ASSERT);
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX1_SEQ_MASK |
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_MASK, /* PLLCAPD */
|
|
NV_SOR_PLL2_AUX1_SEQ_PLLCAPPD_OVERRIDE |
|
|
NV_SOR_PLL2_AUX8_SEQ_PLLCAPPD_ENFORCE_ENABLE);
|
|
usleep_range(25, 30);
|
|
|
|
/* step 4 */
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_MASK,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_ENABLE);
|
|
tegra_sor_pad_cal_power(sor, false); /* PDCAL */
|
|
usleep_range(70, 120);
|
|
|
|
/* step 5 */
|
|
tegra_dc_sor_io_set_dpd(sor, false);
|
|
usleep_range(70, 120);
|
|
|
|
sor->power_is_up = false;
|
|
}
|
|
|
|
static u32 tegra_sor_hdmi_get_pixel_depth(struct tegra_dc *dc)
|
|
{
|
|
int yuv_flag = dc->mode.vmode & FB_VMODE_YUV_MASK;
|
|
int yuv_bypass_mode = dc->mode.vmode & FB_VMODE_BYPASS;
|
|
|
|
if (!yuv_flag)
|
|
return NV_SOR_STATE1_ASY_PIXELDEPTH_DEFAULTVAL;
|
|
|
|
if (!yuv_bypass_mode) {
|
|
if (tegra_dc_is_yuv420_8bpc(&dc->mode)) {
|
|
if (tegra_dc_is_t19x())
|
|
return tegra_sor_yuv420_8bpc_pixel_depth_t19x();
|
|
} else if (yuv_flag & FB_VMODE_Y422) {
|
|
if (yuv_flag & FB_VMODE_Y24)
|
|
return NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_16_422;
|
|
else if (yuv_flag & FB_VMODE_Y30)
|
|
return NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_20_422;
|
|
else if (yuv_flag & FB_VMODE_Y36)
|
|
return NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_24_422;
|
|
} else {
|
|
if (yuv_flag & FB_VMODE_Y24)
|
|
return NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_24_444;
|
|
else if (yuv_flag & FB_VMODE_Y30)
|
|
return NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_30_444;
|
|
else if (yuv_flag & FB_VMODE_Y36)
|
|
return NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_36_444;
|
|
}
|
|
} else {
|
|
return NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_24_444;
|
|
}
|
|
|
|
return NV_SOR_STATE1_ASY_PIXELDEPTH_DEFAULTVAL;
|
|
}
|
|
|
|
static u32 tegra_sor_dp_get_pixel_depth(struct tegra_dc *dc)
|
|
{
|
|
int yuv_flag = dc->mode.vmode & FB_VMODE_YUV_MASK;
|
|
int yuv_bypass_mode = dc->mode.vmode & FB_VMODE_BYPASS;
|
|
|
|
if (yuv_flag) {
|
|
if (!yuv_bypass_mode) {
|
|
if (yuv_flag & FB_VMODE_Y422) {
|
|
if (yuv_flag & FB_VMODE_Y24)
|
|
return
|
|
NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_16_422;
|
|
} else if (IS_RGB(yuv_flag) ||
|
|
(yuv_flag & FB_VMODE_Y444)) {
|
|
if (yuv_flag & FB_VMODE_Y24)
|
|
return
|
|
NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_24_444;
|
|
else if (yuv_flag & FB_VMODE_Y30)
|
|
return
|
|
NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_30_444;
|
|
else if (yuv_flag & FB_VMODE_Y36)
|
|
return
|
|
NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_36_444;
|
|
} else {
|
|
dev_err(&dc->ndev->dev, "%s: Unsupported mode with vmode: 0x%x for DP\n",
|
|
__func__, dc->mode.vmode);
|
|
}
|
|
} else {
|
|
dev_err(&dc->ndev->dev, "%s: Unsupported bypass mode with vmode: 0x%x for DP\n",
|
|
__func__, dc->mode.vmode);
|
|
}
|
|
} else {
|
|
return (dc->out->depth > 18 || !dc->out->depth) ?
|
|
NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_24_444 :
|
|
NV_SOR_STATE1_ASY_PIXELDEPTH_BPP_18_444;
|
|
}
|
|
|
|
return NV_SOR_STATE1_ASY_PIXELDEPTH_DEFAULTVAL;
|
|
}
|
|
|
|
static u32 tegra_sor_get_pixel_depth(struct tegra_dc *dc)
|
|
{
|
|
if (dc->out->type == TEGRA_DC_OUT_HDMI)
|
|
return tegra_sor_hdmi_get_pixel_depth(dc);
|
|
else if ((dc->out->type == TEGRA_DC_OUT_DP) ||
|
|
(dc->out->type == TEGRA_DC_OUT_FAKE_DP))
|
|
return tegra_sor_dp_get_pixel_depth(dc);
|
|
|
|
dev_err(&dc->ndev->dev, "%s: unsupported out_type=%d\n",
|
|
__func__, dc->out->type);
|
|
return 0;
|
|
}
|
|
|
|
static u32 tegra_sor_get_range_compress(struct tegra_dc *dc)
|
|
{
|
|
if ((dc->mode.vmode & FB_VMODE_BYPASS) ||
|
|
!(dc->mode.vmode & FB_VMODE_LIMITED_RANGE))
|
|
return NV_HEAD_STATE0_RANGECOMPRESS_DISABLE;
|
|
|
|
return NV_HEAD_STATE0_RANGECOMPRESS_ENABLE;
|
|
}
|
|
|
|
static u32 tegra_sor_get_dynamic_range(struct tegra_dc *dc)
|
|
{
|
|
if ((dc->mode.vmode & FB_VMODE_BYPASS) ||
|
|
!(dc->mode.vmode & FB_VMODE_LIMITED_RANGE))
|
|
return NV_HEAD_STATE0_DYNRANGE_VESA;
|
|
|
|
return NV_HEAD_STATE0_DYNRANGE_CEA;
|
|
}
|
|
|
|
static u32 tegra_sor_get_color_space(struct tegra_dc *dc)
|
|
{
|
|
int yuv_flag = dc->mode.vmode & FB_VMODE_YUV_MASK;
|
|
u32 color_space = NV_HEAD_STATE0_COLORSPACE_RGB;
|
|
|
|
if (!IS_RGB(yuv_flag)) {
|
|
u32 ec = dc->mode.vmode & FB_VMODE_EC_MASK;
|
|
|
|
switch (ec) {
|
|
case FB_VMODE_EC_ADOBE_YCC601:
|
|
case FB_VMODE_EC_SYCC601:
|
|
case FB_VMODE_EC_XVYCC601:
|
|
color_space = NV_HEAD_STATE0_COLORSPACE_YUV_601;
|
|
break;
|
|
case FB_VMODE_EC_XVYCC709:
|
|
color_space = NV_HEAD_STATE0_COLORSPACE_YUV_709;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return color_space;
|
|
}
|
|
|
|
static inline u32 tegra_sor_get_adjusted_hblank(struct tegra_dc *dc,
|
|
u32 hblank_end)
|
|
{
|
|
/* For native HDMI420 8bpc, HBLANK_END needs to be halved. */
|
|
if (tegra_dc_is_t19x() &&
|
|
tegra_dc_is_yuv420_8bpc(&dc->mode) &&
|
|
!(dc->mode.vmode & FB_VMODE_BYPASS)
|
|
&& dc->out->type == TEGRA_DC_OUT_HDMI)
|
|
hblank_end = hblank_end / 2;
|
|
|
|
return hblank_end;
|
|
}
|
|
|
|
static void tegra_dc_sor_config_panel(struct tegra_dc_sor_data *sor,
|
|
bool is_lvds)
|
|
{
|
|
struct tegra_dc *dc = sor->dc;
|
|
const struct tegra_dc_mode *dc_mode = &dc->mode;
|
|
int head_num = dc->ctrl_num;
|
|
u32 reg_val = NV_SOR_HEADNUM(head_num);
|
|
u32 vtotal, htotal;
|
|
u32 vsync_end, hsync_end;
|
|
u32 vblank_end, hblank_end;
|
|
u32 vblank_start, hblank_start;
|
|
int out_type = dc->out->type;
|
|
|
|
if (out_type == TEGRA_DC_OUT_HDMI)
|
|
reg_val |= NV_SOR_STATE1_ASY_PROTOCOL_SINGLE_TMDS_A;
|
|
else if ((out_type == TEGRA_DC_OUT_DP) ||
|
|
(out_type == TEGRA_DC_OUT_FAKE_DP))
|
|
reg_val |= NV_SOR_STATE1_ASY_PROTOCOL_DP_A;
|
|
else
|
|
reg_val |= NV_SOR_STATE1_ASY_PROTOCOL_LVDS_CUSTOM;
|
|
|
|
reg_val |= NV_SOR_STATE1_ASY_SUBOWNER_NONE |
|
|
NV_SOR_STATE1_ASY_CRCMODE_COMPLETE_RASTER;
|
|
|
|
if (dc_mode->flags & TEGRA_DC_MODE_FLAG_NEG_H_SYNC)
|
|
reg_val |= NV_SOR_STATE1_ASY_HSYNCPOL_NEGATIVE_TRUE;
|
|
else
|
|
reg_val |= NV_SOR_STATE1_ASY_HSYNCPOL_POSITIVE_TRUE;
|
|
|
|
if (dc_mode->flags & TEGRA_DC_MODE_FLAG_NEG_V_SYNC)
|
|
reg_val |= NV_SOR_STATE1_ASY_VSYNCPOL_NEGATIVE_TRUE;
|
|
else
|
|
reg_val |= NV_SOR_STATE1_ASY_VSYNCPOL_POSITIVE_TRUE;
|
|
|
|
reg_val |= tegra_sor_get_pixel_depth(dc);
|
|
tegra_sor_writel(sor, NV_SOR_STATE1, reg_val);
|
|
|
|
/* Interlaced is not supported in hw */
|
|
reg_val = NV_HEAD_STATE0_INTERLACED_PROGRESSIVE;
|
|
reg_val |= tegra_sor_get_range_compress(sor->dc);
|
|
reg_val |= tegra_sor_get_dynamic_range(sor->dc);
|
|
reg_val |= tegra_sor_get_color_space(sor->dc);
|
|
tegra_sor_writel(sor, nv_sor_head_state0(head_num), reg_val);
|
|
|
|
BUG_ON(!dc_mode);
|
|
vtotal = dc_mode->v_sync_width + dc_mode->v_back_porch +
|
|
dc_mode->v_active + dc_mode->v_front_porch;
|
|
htotal = dc_mode->h_sync_width + dc_mode->h_back_porch +
|
|
dc_mode->h_active + dc_mode->h_front_porch;
|
|
tegra_sor_writel(sor, nv_sor_head_state1(head_num),
|
|
vtotal << NV_HEAD_STATE1_VTOTAL_SHIFT |
|
|
htotal << NV_HEAD_STATE1_HTOTAL_SHIFT);
|
|
|
|
vsync_end = dc_mode->v_sync_width - 1;
|
|
hsync_end = dc_mode->h_sync_width - 1;
|
|
tegra_sor_writel(sor, nv_sor_head_state2(head_num),
|
|
vsync_end << NV_HEAD_STATE2_VSYNC_END_SHIFT |
|
|
hsync_end << NV_HEAD_STATE2_HSYNC_END_SHIFT);
|
|
|
|
vblank_end = vsync_end + dc_mode->v_back_porch;
|
|
hblank_end = hsync_end + dc_mode->h_back_porch;
|
|
hblank_end = tegra_sor_get_adjusted_hblank(dc, hblank_end);
|
|
tegra_sor_writel(sor, nv_sor_head_state3(head_num),
|
|
vblank_end << NV_HEAD_STATE3_VBLANK_END_SHIFT |
|
|
hblank_end << NV_HEAD_STATE3_HBLANK_END_SHIFT);
|
|
|
|
vblank_start = vblank_end + dc_mode->v_active;
|
|
hblank_start = hblank_end + dc_mode->h_active;
|
|
tegra_sor_writel(sor, nv_sor_head_state4(head_num),
|
|
vblank_start << NV_HEAD_STATE4_VBLANK_START_SHIFT |
|
|
hblank_start << NV_HEAD_STATE4_HBLANK_START_SHIFT);
|
|
|
|
tegra_sor_writel(sor, nv_sor_head_state5(head_num), 0x1);
|
|
|
|
tegra_sor_write_field(sor, NV_SOR_CSTM,
|
|
NV_SOR_CSTM_ROTCLK_DEFAULT_MASK |
|
|
NV_SOR_CSTM_LVDS_EN_ENABLE,
|
|
2 << NV_SOR_CSTM_ROTCLK_SHIFT |
|
|
is_lvds ? NV_SOR_CSTM_LVDS_EN_ENABLE :
|
|
NV_SOR_CSTM_LVDS_EN_DISABLE);
|
|
|
|
tegra_dc_sor_config_pwm(sor, 1024, 1024);
|
|
}
|
|
|
|
static void tegra_dc_sor_enable_dc(struct tegra_dc_sor_data *sor)
|
|
{
|
|
struct tegra_dc *dc = sor->dc;
|
|
u32 reg_val;
|
|
|
|
tegra_dc_get(dc);
|
|
|
|
reg_val = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS);
|
|
|
|
if (tegra_platform_is_vdk())
|
|
tegra_dc_writel(dc, reg_val | WRITE_MUX_ASSEMBLY,
|
|
DC_CMD_STATE_ACCESS);
|
|
else
|
|
tegra_dc_writel(dc, reg_val | WRITE_MUX_ACTIVE,
|
|
DC_CMD_STATE_ACCESS);
|
|
|
|
if (tegra_dc_is_t21x()) {
|
|
if (tegra_platform_is_fpga()) {
|
|
tegra_dc_writel(dc, 0, DC_DISP_DISP_CLOCK_CONTROL);
|
|
tegra_dc_writel(dc, 0xe, DC_DISP_DC_MCCIF_FIFOCTRL);
|
|
}
|
|
|
|
tegra_dc_writel(dc, VSYNC_H_POSITION(1),
|
|
DC_DISP_DISP_TIMING_OPTIONS);
|
|
}
|
|
|
|
/* Enable DC */
|
|
if (dc->out->vrr) {
|
|
if (tegra_dc_is_nvdisplay())
|
|
tegra_nvdisp_set_vrr_mode(dc);
|
|
else
|
|
tegra_dc_writel(dc, DISP_CTRL_MODE_C_DISPLAY,
|
|
DC_CMD_DISPLAY_COMMAND);
|
|
}
|
|
else if (dc->frm_lck_info.frame_lock_enable &&
|
|
((dc->out->type == TEGRA_DC_OUT_HDMI) ||
|
|
(dc->out->type == TEGRA_DC_OUT_DP) ||
|
|
(dc->out->type == TEGRA_DC_OUT_FAKE_DP))) {
|
|
int ret;
|
|
mutex_unlock(&dc->lock);
|
|
ret = tegra_dc_common_sync_frames(dc, DC_CMD_DISPLAY_COMMAND,
|
|
DC_CMD_STATE_ACCESS, DISP_CTRL_MODE_C_DISPLAY);
|
|
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);
|
|
} else {
|
|
tegra_dc_writel(dc, DISP_CTRL_MODE_C_DISPLAY,
|
|
DC_CMD_DISPLAY_COMMAND);
|
|
}
|
|
|
|
tegra_dc_writel(dc, reg_val, DC_CMD_STATE_ACCESS);
|
|
|
|
tegra_dc_put(dc);
|
|
}
|
|
|
|
void tegra_sor_cal(struct tegra_dc_sor_data *sor)
|
|
{
|
|
u32 nv_sor_pll1_reg = nv_sor_pll1();
|
|
u32 nv_sor_pll2_reg = nv_sor_pll2();
|
|
|
|
if (sor->dc->initialized)
|
|
return;
|
|
|
|
/* For HDMI, rterm calibration is currently enabled only on T19x. */
|
|
if (!tegra_dc_is_t19x() && sor->dc->out->type == TEGRA_DC_OUT_HDMI)
|
|
return;
|
|
|
|
tegra_dc_sor_io_set_dpd(sor, true);
|
|
usleep_range(5, 20);
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_MASK,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_DISABLE);
|
|
tegra_sor_write_field(sor, nv_sor_pll1_reg,
|
|
NV_SOR_PLL1_TMDS_TERM_ENABLE,
|
|
NV_SOR_PLL1_TMDS_TERM_ENABLE);
|
|
usleep_range(20, 100);
|
|
|
|
tegra_sor_pad_cal_power(sor, true);
|
|
usleep_range(10, 20);
|
|
|
|
tegra_dc_sor_termination_cal(sor);
|
|
|
|
tegra_sor_pad_cal_power(sor, false);
|
|
usleep_range(10, 20);
|
|
|
|
tegra_sor_write_field(sor, nv_sor_pll2_reg,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_MASK,
|
|
NV_SOR_PLL2_AUX6_BANDGAP_POWERDOWN_ENABLE);
|
|
tegra_sor_write_field(sor, nv_sor_pll1_reg,
|
|
NV_SOR_PLL1_TMDS_TERM_ENABLE,
|
|
NV_SOR_PLL1_TMDS_TERM_DISABLE);
|
|
usleep_range(20, 100);
|
|
|
|
tegra_dc_sor_io_set_dpd(sor, false);
|
|
usleep_range(5, 20);
|
|
}
|
|
|
|
void tegra_sor_config_xbar(struct tegra_dc_sor_data *sor)
|
|
{
|
|
u32 val = 0, mask = 0, shift = 0;
|
|
u32 i = 0;
|
|
|
|
mask = (NV_SOR_XBAR_BYPASS_MASK | NV_SOR_XBAR_LINK_SWAP_MASK);
|
|
for (i = 0, shift = 2; i < sizeof(sor->xbar_ctrl)/sizeof(u32);
|
|
shift += 3, i++) {
|
|
mask |= NV_SOR_XBAR_LINK_XSEL_MASK << shift;
|
|
val |= sor->xbar_ctrl[i] << shift;
|
|
}
|
|
|
|
tegra_sor_write_field(sor, NV_SOR_XBAR_CTRL, mask, val);
|
|
tegra_sor_writel(sor, NV_SOR_XBAR_POL, 0);
|
|
}
|
|
|
|
void tegra_dc_sor_enable_dp(struct tegra_dc_sor_data *sor)
|
|
{
|
|
if (!sor->dc->initialized) {
|
|
tegra_sor_cal(sor);
|
|
tegra_sor_dp_pad_power_up(sor);
|
|
tegra_sor_power_lanes(sor, sor->link_cfg->lane_count, true);
|
|
} else {
|
|
/* Update sor power state for seamless */
|
|
sor->power_is_up = true;
|
|
}
|
|
|
|
}
|
|
|
|
static void tegra_dc_sor_enable_sor(struct tegra_dc_sor_data *sor, bool enable)
|
|
{
|
|
struct tegra_dc *dc = sor->dc;
|
|
|
|
/* Do not disable SOR during seamless boot */
|
|
if (dc->initialized && !enable)
|
|
return;
|
|
|
|
if (tegra_dc_is_t21x()) {
|
|
u32 reg_val = tegra_dc_readl(sor->dc, DC_DISP_DISP_WIN_OPTIONS);
|
|
u32 enb = sor->ctrl_num ? SOR1_ENABLE : SOR_ENABLE;
|
|
|
|
if (dc->out->type == TEGRA_DC_OUT_HDMI)
|
|
enb |= SOR1_TIMING_CYA;
|
|
|
|
reg_val = enable ? reg_val | enb : reg_val & ~enb;
|
|
tegra_dc_writel(dc, reg_val, DC_DISP_DISP_WIN_OPTIONS);
|
|
} else if (tegra_dc_is_t18x()) {
|
|
tegra_dc_enable_sor_t18x(dc, sor->ctrl_num, enable);
|
|
} else if (tegra_dc_is_t19x()) {
|
|
tegra_dc_enable_sor_t19x(dc, sor->ctrl_num, enable);
|
|
} else {
|
|
pr_err("%s: Unknown Tegra SOC\n", __func__);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void tegra_dc_sor_attach(struct tegra_dc_sor_data *sor)
|
|
{
|
|
struct tegra_dc *dc = sor->dc;
|
|
u32 reg_val;
|
|
|
|
if (sor->sor_state == SOR_ATTACHED)
|
|
return;
|
|
|
|
tegra_dc_get(dc);
|
|
|
|
reg_val = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS);
|
|
|
|
if (tegra_platform_is_vdk())
|
|
tegra_dc_writel(dc, reg_val | WRITE_MUX_ASSEMBLY,
|
|
DC_CMD_STATE_ACCESS);
|
|
else
|
|
tegra_dc_writel(dc, reg_val | WRITE_MUX_ACTIVE,
|
|
DC_CMD_STATE_ACCESS);
|
|
|
|
tegra_dc_sor_config_panel(sor, false);
|
|
tegra_dc_sor_update(sor);
|
|
|
|
/* Sleep request */
|
|
tegra_sor_writel(sor, NV_SOR_SUPER_STATE1,
|
|
NV_SOR_SUPER_STATE1_ASY_HEAD_OP_SLEEP |
|
|
NV_SOR_SUPER_STATE1_ASY_ORMODE_SAFE |
|
|
NV_SOR_SUPER_STATE1_ATTACHED_YES);
|
|
tegra_dc_sor_super_update(sor);
|
|
|
|
if (tegra_dc_sor_poll_register(sor, NV_SOR_TEST,
|
|
NV_SOR_TEST_ATTACHED_DEFAULT_MASK,
|
|
NV_SOR_TEST_ATTACHED_TRUE,
|
|
100, TEGRA_SOR_ATTACH_TIMEOUT_MS)) {
|
|
dev_err(&dc->ndev->dev,
|
|
"dc timeout waiting for ATTACH = TRUE\n");
|
|
}
|
|
|
|
tegra_sor_writel(sor, NV_SOR_SUPER_STATE1,
|
|
NV_SOR_SUPER_STATE1_ASY_HEAD_OP_SLEEP |
|
|
NV_SOR_SUPER_STATE1_ASY_ORMODE_NORMAL |
|
|
NV_SOR_SUPER_STATE1_ATTACHED_YES);
|
|
tegra_dc_sor_super_update(sor);
|
|
|
|
tegra_dc_sor_enable_dc(sor);
|
|
|
|
tegra_dc_sor_enable_sor(sor, true);
|
|
|
|
tegra_sor_writel(sor, NV_SOR_SUPER_STATE1,
|
|
NV_SOR_SUPER_STATE1_ASY_HEAD_OP_AWAKE |
|
|
NV_SOR_SUPER_STATE1_ASY_ORMODE_NORMAL |
|
|
NV_SOR_SUPER_STATE1_ATTACHED_YES);
|
|
tegra_dc_sor_super_update(sor);
|
|
|
|
tegra_dc_writel(dc, reg_val, DC_CMD_STATE_ACCESS);
|
|
tegra_dc_put(dc);
|
|
|
|
sor->sor_state = SOR_ATTACHED;
|
|
}
|
|
|
|
/* Disable windows and set minimum raster timings */
|
|
static void
|
|
tegra_dc_sor_disable_win_short_raster_t21x(struct tegra_dc *dc, int *dc_reg_ctx)
|
|
{
|
|
int selected_windows, i;
|
|
|
|
selected_windows = tegra_dc_readl(dc, DC_CMD_DISPLAY_WINDOW_HEADER);
|
|
|
|
/* Store and clear window options */
|
|
for_each_set_bit(i, &dc->valid_windows,
|
|
tegra_dc_get_numof_dispwindows()) {
|
|
tegra_dc_writel(dc, WINDOW_A_SELECT << i,
|
|
DC_CMD_DISPLAY_WINDOW_HEADER);
|
|
dc_reg_ctx[i] = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS);
|
|
tegra_dc_writel(dc, 0, DC_WIN_WIN_OPTIONS);
|
|
tegra_dc_writel(dc, WIN_A_ACT_REQ << i, DC_CMD_STATE_CONTROL);
|
|
}
|
|
|
|
tegra_dc_writel(dc, selected_windows, DC_CMD_DISPLAY_WINDOW_HEADER);
|
|
|
|
/* Store current raster timings and set minimum timings */
|
|
dc_reg_ctx[i++] = tegra_dc_readl(dc, DC_DISP_REF_TO_SYNC);
|
|
tegra_dc_writel(dc, min_mode.h_ref_to_sync |
|
|
(min_mode.v_ref_to_sync << 16), DC_DISP_REF_TO_SYNC);
|
|
|
|
dc_reg_ctx[i++] = tegra_dc_readl(dc, DC_DISP_SYNC_WIDTH);
|
|
tegra_dc_writel(dc, min_mode.h_sync_width |
|
|
(min_mode.v_sync_width << 16), DC_DISP_SYNC_WIDTH);
|
|
|
|
dc_reg_ctx[i++] = tegra_dc_readl(dc, DC_DISP_BACK_PORCH);
|
|
tegra_dc_writel(dc, min_mode.h_back_porch |
|
|
((min_mode.v_back_porch - min_mode.v_ref_to_sync) << 16),
|
|
DC_DISP_BACK_PORCH);
|
|
|
|
dc_reg_ctx[i++] = tegra_dc_readl(dc, DC_DISP_FRONT_PORCH);
|
|
tegra_dc_writel(dc, min_mode.h_front_porch |
|
|
((min_mode.v_front_porch + min_mode.v_ref_to_sync) << 16),
|
|
DC_DISP_FRONT_PORCH);
|
|
|
|
dc_reg_ctx[i++] = tegra_dc_readl(dc, DC_DISP_DISP_ACTIVE);
|
|
tegra_dc_writel(dc, min_mode.h_active | (min_mode.v_active << 16),
|
|
DC_DISP_DISP_ACTIVE);
|
|
|
|
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
|
}
|
|
|
|
/* Restore previous windows status and raster timings */
|
|
static void
|
|
tegra_dc_sor_restore_win_and_raster_t21x(struct tegra_dc *dc, int *dc_reg_ctx)
|
|
{
|
|
int selected_windows, i;
|
|
|
|
selected_windows = tegra_dc_readl(dc, DC_CMD_DISPLAY_WINDOW_HEADER);
|
|
|
|
for_each_set_bit(i, &dc->valid_windows,
|
|
tegra_dc_get_numof_dispwindows()) {
|
|
tegra_dc_writel(dc, WINDOW_A_SELECT << i,
|
|
DC_CMD_DISPLAY_WINDOW_HEADER);
|
|
tegra_dc_writel(dc, dc_reg_ctx[i], DC_WIN_WIN_OPTIONS);
|
|
tegra_dc_writel(dc, WIN_A_ACT_REQ << i, DC_CMD_STATE_CONTROL);
|
|
}
|
|
|
|
tegra_dc_writel(dc, selected_windows, DC_CMD_DISPLAY_WINDOW_HEADER);
|
|
|
|
tegra_dc_writel(dc, dc_reg_ctx[i++], DC_DISP_REF_TO_SYNC);
|
|
tegra_dc_writel(dc, dc_reg_ctx[i++], DC_DISP_SYNC_WIDTH);
|
|
tegra_dc_writel(dc, dc_reg_ctx[i++], DC_DISP_BACK_PORCH);
|
|
tegra_dc_writel(dc, dc_reg_ctx[i++], DC_DISP_FRONT_PORCH);
|
|
tegra_dc_writel(dc, dc_reg_ctx[i++], DC_DISP_DISP_ACTIVE);
|
|
|
|
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
|
}
|
|
|
|
static inline void tegra_sor_save_dc_state(struct tegra_dc_sor_data *sor)
|
|
{
|
|
if (tegra_dc_is_nvdisplay())
|
|
tegra_nvdisp_disable_wins(sor->dc, sor->win_state_arr);
|
|
else
|
|
tegra_dc_sor_disable_win_short_raster_t21x(sor->dc,
|
|
sor->dc_reg_ctx);
|
|
}
|
|
|
|
static inline void tegra_sor_restore_dc_state(struct tegra_dc_sor_data *sor)
|
|
{
|
|
if (tegra_dc_is_nvdisplay())
|
|
tegra_nvdisp_restore_wins(sor->dc, sor->win_state_arr);
|
|
else
|
|
tegra_dc_sor_restore_win_and_raster_t21x(sor->dc,
|
|
sor->dc_reg_ctx);
|
|
}
|
|
|
|
void tegra_sor_stop_dc(struct tegra_dc_sor_data *sor)
|
|
{
|
|
struct tegra_dc *dc = sor->dc;
|
|
|
|
tegra_dc_get(dc);
|
|
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
/*SOR should be attached if the Display command != STOP */
|
|
/* Stop DC */
|
|
tegra_dc_writel(dc, DISP_CTRL_MODE_STOP,
|
|
DC_CMD_DISPLAY_COMMAND);
|
|
tegra_dc_enable_general_act(dc);
|
|
|
|
/* Stop DC->SOR path */
|
|
tegra_dc_sor_enable_sor(sor, false);
|
|
} else {
|
|
/* Stop DC->SOR path */
|
|
tegra_dc_sor_enable_sor(sor, false);
|
|
tegra_dc_enable_general_act(dc);
|
|
|
|
/* Stop DC */
|
|
tegra_dc_writel(dc, DISP_CTRL_MODE_STOP,
|
|
DC_CMD_DISPLAY_COMMAND);
|
|
}
|
|
tegra_dc_enable_general_act(dc);
|
|
|
|
tegra_dc_put(dc);
|
|
}
|
|
|
|
void tegra_dc_sor_sleep(struct tegra_dc_sor_data *sor)
|
|
{
|
|
struct tegra_dc *dc = sor->dc;
|
|
|
|
if (sor->sor_state == SOR_SLEEP)
|
|
return;
|
|
|
|
/* set OR mode to SAFE */
|
|
tegra_sor_writel(sor, NV_SOR_SUPER_STATE1,
|
|
NV_SOR_SUPER_STATE1_ASY_HEAD_OP_AWAKE |
|
|
NV_SOR_SUPER_STATE1_ASY_ORMODE_SAFE |
|
|
NV_SOR_SUPER_STATE1_ATTACHED_YES);
|
|
tegra_dc_sor_super_update(sor);
|
|
if (tegra_dc_sor_poll_register(sor, NV_SOR_PWR,
|
|
NV_SOR_PWR_MODE_DEFAULT_MASK,
|
|
NV_SOR_PWR_MODE_SAFE,
|
|
100, TEGRA_SOR_ATTACH_TIMEOUT_MS)) {
|
|
dev_err(&dc->ndev->dev,
|
|
"dc timeout waiting for OR MODE = SAFE\n");
|
|
}
|
|
|
|
/* set HEAD mode to SLEEP */
|
|
tegra_sor_writel(sor, NV_SOR_SUPER_STATE1,
|
|
NV_SOR_SUPER_STATE1_ASY_HEAD_OP_SLEEP |
|
|
NV_SOR_SUPER_STATE1_ASY_ORMODE_SAFE |
|
|
NV_SOR_SUPER_STATE1_ATTACHED_YES);
|
|
tegra_dc_sor_super_update(sor);
|
|
|
|
if (tegra_dc_sor_poll_register(sor, NV_SOR_TEST,
|
|
NV_SOR_TEST_ACT_HEAD_OPMODE_DEFAULT_MASK,
|
|
NV_SOR_TEST_ACT_HEAD_OPMODE_SLEEP,
|
|
100, TEGRA_SOR_ATTACH_TIMEOUT_MS)) {
|
|
dev_err(&dc->ndev->dev,
|
|
"dc timeout waiting for HEAD MODE = SLEEP\n");
|
|
}
|
|
sor->sor_state = SOR_SLEEP;
|
|
|
|
}
|
|
|
|
void tegra_dc_sor_pre_detach(struct tegra_dc_sor_data *sor)
|
|
{
|
|
struct tegra_dc *dc = sor->dc;
|
|
|
|
if (sor->sor_state != SOR_ATTACHED && sor->sor_state != SOR_SLEEP)
|
|
return;
|
|
|
|
tegra_dc_get(dc);
|
|
|
|
tegra_dc_sor_sleep(sor);
|
|
|
|
tegra_sor_save_dc_state(sor);
|
|
|
|
sor->sor_state = SOR_DETACHING;
|
|
tegra_dc_put(dc);
|
|
}
|
|
|
|
void tegra_dc_sor_detach(struct tegra_dc_sor_data *sor)
|
|
{
|
|
struct tegra_dc *dc = sor->dc;
|
|
unsigned long dc_int_mask;
|
|
|
|
if (sor->sor_state == SOR_DETACHED)
|
|
return;
|
|
|
|
tegra_dc_get(dc);
|
|
|
|
/* Mask DC interrupts during the 2 dummy frames required for detach */
|
|
dc_int_mask = tegra_dc_readl(dc, DC_CMD_INT_MASK);
|
|
tegra_dc_writel(dc, 0, DC_CMD_INT_MASK);
|
|
|
|
if (sor->sor_state != SOR_DETACHING)
|
|
tegra_dc_sor_pre_detach(sor);
|
|
|
|
/* detach SOR */
|
|
tegra_sor_writel(sor, NV_SOR_SUPER_STATE1,
|
|
NV_SOR_SUPER_STATE1_ASY_HEAD_OP_SLEEP |
|
|
NV_SOR_SUPER_STATE1_ASY_ORMODE_SAFE |
|
|
NV_SOR_SUPER_STATE1_ATTACHED_NO);
|
|
tegra_dc_sor_super_update(sor);
|
|
if (tegra_dc_sor_poll_register(sor, NV_SOR_TEST,
|
|
NV_SOR_TEST_ATTACHED_DEFAULT_MASK,
|
|
NV_SOR_TEST_ATTACHED_FALSE,
|
|
100, TEGRA_SOR_ATTACH_TIMEOUT_MS)) {
|
|
dev_err(&dc->ndev->dev,
|
|
"dc timeout waiting for ATTACH = FALSE\n");
|
|
}
|
|
|
|
tegra_sor_writel(sor, NV_SOR_STATE1,
|
|
NV_SOR_STATE1_ASY_OWNER_NONE |
|
|
NV_SOR_STATE1_ASY_SUBOWNER_NONE |
|
|
NV_SOR_STATE1_ASY_PROTOCOL_LVDS_CUSTOM);
|
|
tegra_dc_sor_update(sor);
|
|
|
|
tegra_sor_stop_dc(sor);
|
|
|
|
tegra_sor_restore_dc_state(sor);
|
|
|
|
tegra_dc_writel(dc, dc_int_mask, DC_CMD_INT_MASK);
|
|
sor->sor_state = SOR_DETACHED;
|
|
tegra_dc_put(dc);
|
|
}
|
|
|
|
void tegra_dc_sor_disable(struct tegra_dc_sor_data *sor)
|
|
{
|
|
struct tegra_dc *dc = sor->dc;
|
|
|
|
tegra_sor_config_safe_clk(sor);
|
|
|
|
/* Power down DP lanes */
|
|
if (tegra_sor_power_lanes(sor, 4, false)) {
|
|
dev_err(&dc->ndev->dev,
|
|
"Failed to power down dp lanes\n");
|
|
return;
|
|
}
|
|
|
|
/* Power down pad macro */
|
|
tegra_sor_dp_pad_power_down(sor);
|
|
|
|
if (tegra_platform_is_vdk())
|
|
return;
|
|
|
|
/* Reset SOR */
|
|
tegra_sor_reset(sor);
|
|
tegra_sor_clk_disable(sor);
|
|
}
|
|
|
|
void tegra_dc_sor_set_internal_panel(struct tegra_dc_sor_data *sor, bool is_int)
|
|
{
|
|
u32 reg_val;
|
|
|
|
reg_val = tegra_sor_readl(sor, NV_SOR_DP_SPARE(sor->portnum));
|
|
if (is_int)
|
|
reg_val |= NV_SOR_DP_SPARE_PANEL_INTERNAL;
|
|
else
|
|
reg_val &= ~NV_SOR_DP_SPARE_PANEL_INTERNAL;
|
|
|
|
reg_val |= NV_SOR_DP_SPARE_SOR_CLK_SEL_MACRO_SORCLK;
|
|
|
|
tegra_sor_writel(sor, NV_SOR_DP_SPARE(sor->portnum), reg_val);
|
|
|
|
if (sor->dc->out->type == TEGRA_DC_OUT_HDMI) {
|
|
if (tegra_dc_is_nvdisplay())
|
|
tegra_sor_write_field(sor,
|
|
NV_SOR_DP_SPARE(sor->portnum),
|
|
NV_SOR_DP_SPARE_VIDEO_PREANBLE_CYA_MASK,
|
|
NV_SOR_DP_SPARE_VIDEO_PREANBLE_CYA_DISABLE);
|
|
else
|
|
tegra_sor_write_field(sor,
|
|
NV_SOR_DP_SPARE(sor->portnum),
|
|
NV_SOR_DP_SPARE_VIDEO_PREANBLE_CYA_MASK,
|
|
NV_SOR_DP_SPARE_VIDEO_PREANBLE_CYA_ENABLE);
|
|
}
|
|
}
|
|
|
|
void tegra_dc_sor_set_link_bandwidth(struct tegra_dc_sor_data *sor, u8 link_bw)
|
|
{
|
|
WARN_ON(sor->sor_state == SOR_ATTACHED);
|
|
|
|
/* FIXME: does order matter with dettached SOR? */
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
/* link rate = fixed pll_dp@270MHz * link_bw / 10 */
|
|
clk_set_rate(sor->pad_clk, 270000000UL * link_bw / 10);
|
|
}
|
|
|
|
tegra_sor_write_field(sor, NV_SOR_CLK_CNTRL,
|
|
NV_SOR_CLK_CNTRL_DP_LINK_SPEED_MASK,
|
|
link_bw << NV_SOR_CLK_CNTRL_DP_LINK_SPEED_SHIFT);
|
|
|
|
/* It can take upto 200us for PLLs in analog macro to settle */
|
|
udelay(300);
|
|
}
|
|
|
|
void tegra_dc_sor_set_lane_count(struct tegra_dc_sor_data *sor, u8 lane_count)
|
|
{
|
|
u32 reg_lane_cnt = 0;
|
|
|
|
switch (lane_count) {
|
|
case 0:
|
|
reg_lane_cnt = NV_SOR_DP_LINKCTL_LANECOUNT_ZERO;
|
|
break;
|
|
case 1:
|
|
reg_lane_cnt = NV_SOR_DP_LINKCTL_LANECOUNT_ONE;
|
|
break;
|
|
case 2:
|
|
reg_lane_cnt = NV_SOR_DP_LINKCTL_LANECOUNT_TWO;
|
|
break;
|
|
case 4:
|
|
reg_lane_cnt = NV_SOR_DP_LINKCTL_LANECOUNT_FOUR;
|
|
break;
|
|
default:
|
|
/* 0 should be handled earlier. */
|
|
dev_err(&sor->dc->ndev->dev, "dp: Invalid lane count %d\n",
|
|
lane_count);
|
|
return;
|
|
}
|
|
|
|
tegra_sor_write_field(sor, NV_SOR_DP_LINKCTL(sor->portnum),
|
|
NV_SOR_DP_LINKCTL_LANECOUNT_MASK,
|
|
reg_lane_cnt);
|
|
}
|
|
|
|
void tegra_sor_setup_clk(struct tegra_dc_sor_data *sor, struct clk *clk,
|
|
bool is_lvds)
|
|
{
|
|
struct clk *dc_parent_clk;
|
|
struct tegra_dc *dc = sor->dc;
|
|
|
|
if (tegra_platform_is_vdk())
|
|
return;
|
|
|
|
if (clk == dc->clk) {
|
|
dc_parent_clk = clk_get_parent(clk);
|
|
BUG_ON(!dc_parent_clk);
|
|
|
|
/* Change for seamless */
|
|
if (!dc->initialized) {
|
|
if (dc->mode.pclk != clk_get_rate(dc_parent_clk)) {
|
|
clk_set_rate(dc_parent_clk, dc->mode.pclk);
|
|
clk_set_rate(clk, dc->mode.pclk);
|
|
}
|
|
}
|
|
|
|
if (!tegra_dc_is_nvdisplay())
|
|
return;
|
|
|
|
/*
|
|
* For t18x plldx cannot go below 27MHz.
|
|
* Real HW limit is lesser though.
|
|
* 27Mz is chosen to have a safe margin.
|
|
*/
|
|
if (dc->mode.pclk < 27000000) {
|
|
if ((2 * dc->mode.pclk) != clk_get_rate(dc_parent_clk))
|
|
clk_set_rate(dc_parent_clk, 2 * dc->mode.pclk);
|
|
if (dc->mode.pclk != clk_get_rate(dc->clk))
|
|
clk_set_rate(dc->clk, dc->mode.pclk);
|
|
}
|
|
}
|
|
}
|
|
|
|
void tegra_sor_precharge_lanes(struct tegra_dc_sor_data *sor)
|
|
{
|
|
const struct tegra_dc_dp_link_config *cfg = sor->link_cfg;
|
|
u32 nv_sor_dp_padctl_reg = nv_sor_dp_padctl(sor->portnum);
|
|
u32 val = 0;
|
|
|
|
val = tegra_sor_get_pd_tx_bitmap(sor, cfg->lane_count);
|
|
|
|
/* force lanes to output common mode voltage */
|
|
tegra_sor_write_field(sor, nv_sor_dp_padctl_reg,
|
|
(0xf << NV_SOR_DP_PADCTL_COMODE_TXD_0_DP_TXD_2_SHIFT),
|
|
(val << NV_SOR_DP_PADCTL_COMODE_TXD_0_DP_TXD_2_SHIFT));
|
|
|
|
/* precharge for atleast 10us */
|
|
usleep_range(20, 100);
|
|
|
|
/* fallback to normal operation */
|
|
tegra_sor_write_field(sor, nv_sor_dp_padctl_reg,
|
|
(0xf << NV_SOR_DP_PADCTL_COMODE_TXD_0_DP_TXD_2_SHIFT), 0);
|
|
}
|
|
|
|
void tegra_dc_sor_modeset_notifier(struct tegra_dc_sor_data *sor, bool is_lvds)
|
|
{
|
|
if (sor->dc->initialized)
|
|
return;
|
|
|
|
if (!sor->clk_type)
|
|
tegra_sor_config_safe_clk(sor);
|
|
tegra_sor_clk_enable(sor);
|
|
|
|
tegra_dc_sor_config_panel(sor, is_lvds);
|
|
tegra_dc_sor_update(sor);
|
|
tegra_dc_sor_super_update(sor);
|
|
|
|
tegra_sor_clk_disable(sor);
|
|
}
|
|
|