365 lines
9.3 KiB
C
365 lines
9.3 KiB
C
|
/*
|
||
|
* drivers/video/tegra/dc/nvdisp/nvdisp_t19x.c
|
||
|
*
|
||
|
* Copyright (c) 2017-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 "dc_priv.h"
|
||
|
#include "nvdisp.h"
|
||
|
#include "hw_nvdisp_t19x_nvdisp.h"
|
||
|
|
||
|
#define RG_STATUS_STALLED 0x08
|
||
|
#define RG_STATUS_POLL_TIMEOUT_MS 10
|
||
|
#define RG_STATUS_POLL_INTERVAL_US 1
|
||
|
#define TEGRA_WINBUF_ADDR_FLAG_BLOCKLINEAR ((dma_addr_t)0x1 << 39)
|
||
|
|
||
|
/*
|
||
|
* As per nvdisplay programming guidelines, only hubclk,dispclk and
|
||
|
* pclk0 clocks should be enabled before accessing any display register
|
||
|
* in head0 power domain. But, as BL is unconditionally unpowergating
|
||
|
* all display powerdomains, enabling pclk1 and pclk2 and pclk3 clocks
|
||
|
* even here as WAR to avoid display register access issues in kernel
|
||
|
* Bug 200343370 tracks issue in BL.
|
||
|
*/
|
||
|
static struct tegra_dc_pd_clk_info t19x_disp_pd0_clk_info[] = {
|
||
|
{
|
||
|
.name = "nvdisplayhub",
|
||
|
.clk = NULL,
|
||
|
},
|
||
|
{
|
||
|
.name = "nvdisplay_disp",
|
||
|
.clk = NULL,
|
||
|
},
|
||
|
{
|
||
|
.name = "nvdisplay_p0",
|
||
|
.clk = NULL,
|
||
|
},
|
||
|
{
|
||
|
.name = "nvdisplay_p1",
|
||
|
.clk = NULL,
|
||
|
},
|
||
|
{
|
||
|
.name = "nvdisplay_p2",
|
||
|
.clk = NULL,
|
||
|
},
|
||
|
{
|
||
|
.name = "nvdisplay_p3",
|
||
|
.clk = NULL,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct tegra_dc_pd_clk_info t19x_disp_pd1_clk_info[] = {
|
||
|
{
|
||
|
.name = "nvdisplay_p1",
|
||
|
.clk = NULL,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct tegra_dc_pd_clk_info t19x_disp_pd2_clk_info[] = {
|
||
|
{
|
||
|
.name = "nvdisplay_p2",
|
||
|
.clk = NULL,
|
||
|
},
|
||
|
{
|
||
|
.name = "nvdisplay_p3",
|
||
|
.clk = NULL,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* NOTE: Keep the following power domains ordered according to their head owner.
|
||
|
*/
|
||
|
static struct tegra_dc_pd_info t19x_disp_pd_info[] = {
|
||
|
/* Head0 power domain */
|
||
|
{
|
||
|
.of_id = {
|
||
|
{ .compatible = "nvidia,tegra194-disa-pd", },
|
||
|
{},
|
||
|
},
|
||
|
.pg_id = -1,
|
||
|
.head_owner = 0,
|
||
|
.head_mask = 0x1, /* Head(s): 0 */
|
||
|
.win_mask = 0x1, /* Window(s): 0 */
|
||
|
.domain_clks = t19x_disp_pd0_clk_info,
|
||
|
.nclks = ARRAY_SIZE(t19x_disp_pd0_clk_info),
|
||
|
.ref_cnt = 0,
|
||
|
},
|
||
|
/* Head1 power domain */
|
||
|
{
|
||
|
.of_id = {
|
||
|
{ .compatible = "nvidia,tegra194-disb-pd", },
|
||
|
{},
|
||
|
},
|
||
|
.pg_id = -1,
|
||
|
.head_owner = 1,
|
||
|
.head_mask = 0x2, /* Head(s): 1 */
|
||
|
.win_mask = 0x6, /* Window(s): 1,2 */
|
||
|
.domain_clks = t19x_disp_pd1_clk_info,
|
||
|
.nclks = ARRAY_SIZE(t19x_disp_pd1_clk_info),
|
||
|
.ref_cnt = 0,
|
||
|
},
|
||
|
/* Head2 power domain */
|
||
|
{
|
||
|
.of_id = {
|
||
|
{ .compatible = "nvidia,tegra194-disc-pd", },
|
||
|
{},
|
||
|
},
|
||
|
.pg_id = -1,
|
||
|
.head_owner = 2,
|
||
|
.head_mask = 0xc, /* Head(s): 2,3 */
|
||
|
.win_mask = 0x38, /* Window(s): 3,4,5 */
|
||
|
.domain_clks = t19x_disp_pd2_clk_info,
|
||
|
.nclks = ARRAY_SIZE(t19x_disp_pd2_clk_info),
|
||
|
.ref_cnt = 0,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static struct tegra_dc_pd_table t19x_disp_pd_table = {
|
||
|
.pd_entries = t19x_disp_pd_info,
|
||
|
.npd = ARRAY_SIZE(t19x_disp_pd_info),
|
||
|
};
|
||
|
|
||
|
int tegra_nvdisp_set_control_t19x(struct tegra_dc *dc)
|
||
|
{
|
||
|
u32 reg, protocol;
|
||
|
|
||
|
if (dc->out->type == TEGRA_DC_OUT_HDMI) {
|
||
|
|
||
|
/* sor in the function name is irrelevant */
|
||
|
protocol = nvdisp_t19x_sor_control_protocol_tmdsa_f();
|
||
|
} else if ((dc->out->type == TEGRA_DC_OUT_DP) ||
|
||
|
(dc->out->type == TEGRA_DC_OUT_FAKE_DP)) {
|
||
|
|
||
|
/* sor in the function name is irrelevant */
|
||
|
protocol = nvdisp_t19x_sor_control_protocol_dpa_f();
|
||
|
} else {
|
||
|
dev_err(&dc->ndev->dev, "%s: unsupported out_type=%d\n",
|
||
|
__func__, dc->out->type);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
switch (dc->out_ops->get_connector_instance(dc)) {
|
||
|
case 0:
|
||
|
reg = nvdisp_t19x_sor_control_r();
|
||
|
break;
|
||
|
case 1:
|
||
|
reg = nvdisp_t19x_sor1_control_r();
|
||
|
break;
|
||
|
case 2:
|
||
|
reg = nvdisp_t19x_sor2_control_r();
|
||
|
break;
|
||
|
case 3:
|
||
|
reg = nvdisp_t19x_sor3_control_r();
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("%s: invalid sor_num:%d\n", __func__,
|
||
|
dc->out_ops->get_connector_instance(dc));
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
tegra_dc_writel(dc, protocol, reg);
|
||
|
tegra_dc_enable_general_act(dc);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void tegra_dc_enable_sor_t19x(struct tegra_dc *dc, int sor_num, bool enable)
|
||
|
{
|
||
|
u32 enb;
|
||
|
u32 reg_val = tegra_dc_readl(dc, nvdisp_t19x_win_options_r());
|
||
|
|
||
|
switch (sor_num) {
|
||
|
case 0:
|
||
|
enb = nvdisp_t19x_win_options_sor_set_sor_enable_f();
|
||
|
break;
|
||
|
case 1:
|
||
|
enb = nvdisp_t19x_win_options_sor1_set_sor1_enable_f();
|
||
|
break;
|
||
|
case 2:
|
||
|
enb = nvdisp_t19x_win_options_sor2_set_sor2_enable_f();
|
||
|
break;
|
||
|
case 3:
|
||
|
enb = nvdisp_t19x_win_options_sor3_set_sor3_enable_f();
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("%s: invalid sor_num:%d\n", __func__, sor_num);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
reg_val = enable ? reg_val | enb : reg_val & ~enb;
|
||
|
tegra_dc_writel(dc, reg_val, nvdisp_t19x_win_options_r());
|
||
|
}
|
||
|
|
||
|
inline void tegra_nvdisp_set_rg_unstall_t19x(struct tegra_dc *dc)
|
||
|
{
|
||
|
if (dc->mode.vmode & FB_VMODE_BYPASS)
|
||
|
return;
|
||
|
|
||
|
if (tegra_dc_is_yuv420_8bpc(&dc->mode))
|
||
|
tegra_dc_writel(dc,
|
||
|
tegra_dc_readl(dc, nvdisp_t19x_rg_status_r()) |
|
||
|
nvdisp_t19x_rg_status_unstall_force_even_set_enable_f(),
|
||
|
nvdisp_t19x_rg_status_r());
|
||
|
}
|
||
|
|
||
|
static struct tegra_dc_sor_info t19x_sor_info[] = {
|
||
|
{ .hdcp_supported = true }, /* SOR0 */
|
||
|
{ .hdcp_supported = true }, /* SOR1 */
|
||
|
{ .hdcp_supported = true }, /* SOR2 */
|
||
|
{ .hdcp_supported = true }, /* SOR3 */
|
||
|
};
|
||
|
|
||
|
void tegra_dc_populate_t19x_hw_data(struct tegra_dc_hw_data *hw_data)
|
||
|
{
|
||
|
if (!hw_data)
|
||
|
return;
|
||
|
|
||
|
hw_data->nheads = 4;
|
||
|
hw_data->nwins = 6;
|
||
|
hw_data->nsors = 4;
|
||
|
hw_data->sor_info = t19x_sor_info;
|
||
|
hw_data->pd_table = &t19x_disp_pd_table;
|
||
|
hw_data->valid = true;
|
||
|
hw_data->version = TEGRA_DC_HW_T19x;
|
||
|
}
|
||
|
|
||
|
dma_addr_t nvdisp_t19x_get_addr_flag(struct tegra_dc_win *win)
|
||
|
{
|
||
|
dma_addr_t addr_flag = 0x0;
|
||
|
|
||
|
if (win->flags & TEGRA_WIN_FLAG_BLOCKLINEAR)
|
||
|
addr_flag |= TEGRA_WINBUF_ADDR_FLAG_BLOCKLINEAR;
|
||
|
|
||
|
return addr_flag;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* tegra_dc_get_vsync_timestamp_t19x - read the vsync from
|
||
|
* the ptimer regs.
|
||
|
* @dc: pointer to struct tegra_dc for the current head.
|
||
|
*
|
||
|
* Return : timestamp value in ns.
|
||
|
*/
|
||
|
uint64_t tegra_dc_get_vsync_timestamp_t19x(struct tegra_dc *dc)
|
||
|
{
|
||
|
u32 lsb = 0;
|
||
|
u64 msb = 0;
|
||
|
|
||
|
lsb = tegra_dc_readl(dc, nvdisp_t19x_rg_vsync_ptimer0_r());
|
||
|
msb = tegra_dc_readl(dc, nvdisp_t19x_rg_vsync_ptimer1_r());
|
||
|
|
||
|
return (((msb << 32) | lsb) << 5);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* nvdisp_t19x_program_raster_lock_seq - program the raster lock
|
||
|
* sequence for t19x.
|
||
|
* @dc : head pointer for which raster_lock is required.
|
||
|
* @value : informs if head is to be programmed in continuous or
|
||
|
* non_continuous mode. This value is passed on from the source as is.
|
||
|
*
|
||
|
* Return: 0 if successful else -ENODEV if dc isn't found or isn't
|
||
|
* enabled.
|
||
|
*/
|
||
|
int nvdisp_t19x_program_raster_lock_seq(struct tegra_dc *dc, u32 value)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
u32 old_val;
|
||
|
|
||
|
if (!dc)
|
||
|
return -ENODEV;
|
||
|
/**
|
||
|
* For nvdisp_t19x_state_access_r() reg, the implicitly expected value
|
||
|
* is WRITE_ASSEMBLY | READ_ACTIVE and ideally we need not program this
|
||
|
* combination again. However currently in raster lock workflow the
|
||
|
* value of command_state_access is overwritten to write_active in sor.c
|
||
|
* and hence we have to explicitly cache and restore here.
|
||
|
*/
|
||
|
|
||
|
old_val = tegra_dc_readl(dc, nvdisp_t19x_state_access_r());
|
||
|
|
||
|
tegra_dc_writel(dc, nvdisp_t19x_state_access_write_mux_assembly_f()
|
||
|
| nvdisp_t19x_state_access_read_mux_active_f(),
|
||
|
nvdisp_t19x_state_access_r());
|
||
|
|
||
|
tegra_dc_writel(dc, nvdisp_t19x_display_cmd_option_msf_src_glb_ctrl_f()
|
||
|
| nvdisp_t19x_display_cmd_option_msf_enable_enable_f(),
|
||
|
nvdisp_t19x_display_cmd_option_r());
|
||
|
|
||
|
tegra_dc_writel(dc,
|
||
|
nvdisp_t19x_display_command_control_mode_nc_display_f(),
|
||
|
nvdisp_t19x_display_command_r());
|
||
|
|
||
|
tegra_dc_writel(dc, nvdisp_t19x_cmd_state_ctrl_host_trig_enable_f() |
|
||
|
nvdisp_t19x_cmd_state_ctrl_general_act_req_enable_f(),
|
||
|
nvdisp_t19x_cmd_state_ctrl_r());
|
||
|
|
||
|
ret = tegra_dc_poll_register(dc, nvdisp_t19x_rg_status_r(),
|
||
|
RG_STATUS_STALLED, nvdisp_t19x_rg_status_stalled_yes_f(),
|
||
|
RG_STATUS_POLL_INTERVAL_US, RG_STATUS_POLL_TIMEOUT_MS);
|
||
|
if (ret) {
|
||
|
dev_err(&dc->ndev->dev,
|
||
|
"dc timeout waiting for RG to stall\n");
|
||
|
goto restore_cmd_access_state;
|
||
|
}
|
||
|
|
||
|
if (!(value & nvdisp_t19x_display_command_control_mode_c_display_f()))
|
||
|
goto restore_cmd_access_state;
|
||
|
|
||
|
tegra_dc_writel(dc,
|
||
|
nvdisp_t19x_display_command_control_mode_c_display_f(),
|
||
|
nvdisp_t19x_display_command_r());
|
||
|
|
||
|
tegra_dc_writel(dc,
|
||
|
nvdisp_t19x_cmd_state_ctrl_general_act_req_enable_f(),
|
||
|
nvdisp_t19x_cmd_state_ctrl_r());
|
||
|
|
||
|
restore_cmd_access_state:
|
||
|
tegra_dc_writel(dc, old_val, nvdisp_t19x_state_access_r());
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* nvdisp_t19x_enable_raster_lock - programs global_control signal
|
||
|
* required for multi-head RG unstall.
|
||
|
* @dc : head pointer
|
||
|
* @valid_heads : a bit_array consisting of ctrl_nums of heads
|
||
|
* participating in the frame_lock.
|
||
|
*
|
||
|
* Return : void
|
||
|
*/
|
||
|
void nvdisp_t19x_enable_raster_lock(struct tegra_dc *dc,
|
||
|
const ulong valid_heads)
|
||
|
{
|
||
|
tegra_dc_writel(dc, valid_heads, nvdisp_t19x_glb_ctrl_r());
|
||
|
|
||
|
tegra_dc_readl(dc, nvdisp_t19x_glb_ctrl_r()); /* flush */
|
||
|
|
||
|
tegra_dc_writel(dc, 0x00, nvdisp_t19x_glb_ctrl_r());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* tegra_nvdisp_program_common_win_batch_size_t19x - programs the default win
|
||
|
* request batch size (8) for T19x
|
||
|
* @dc : head pointer
|
||
|
*
|
||
|
* Return: void
|
||
|
*/
|
||
|
inline void tegra_nvdisp_program_common_win_batch_size_t19x(struct tegra_dc *dc)
|
||
|
{
|
||
|
tegra_dc_writel(dc,
|
||
|
nvdisp_t19x_ihub_common_config_request_batch_size_8_f(),
|
||
|
nvdisp_t19x_ihub_common_config_r());
|
||
|
}
|